summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile69
-rw-r--r--opentracker.c118
-rw-r--r--ot_accesslist.c22
-rw-r--r--ot_clean.c3
-rw-r--r--ot_config.c29
-rw-r--r--ot_event.c332
-rw-r--r--ot_event.h39
-rw-r--r--ot_fdstate.c6
-rw-r--r--ot_fdstate.h4
-rw-r--r--ot_fmt.c42
-rw-r--r--ot_fmt.h21
-rw-r--r--ot_fullscrape.c53
-rw-r--r--ot_hex.c4
-rw-r--r--ot_hex.h4
-rw-r--r--ot_http.c180
-rw-r--r--ot_http.h9
-rw-r--r--ot_iovec.c12
-rw-r--r--ot_iovec.h6
-rw-r--r--ot_ip.c2
-rw-r--r--ot_ip.h2
-rw-r--r--ot_livesync.h1
-rw-r--r--ot_mutex.c9
-rw-r--r--ot_mutex.h1
-rw-r--r--ot_scan.c29
-rw-r--r--ot_scan.h12
-rw-r--r--ot_socket.c6
-rw-r--r--ot_socket.h1
-rw-r--r--ot_stats.c325
-rw-r--r--ot_stats.h4
-rw-r--r--ot_sync.c24
-rw-r--r--ot_udp.c20
-rw-r--r--ot_urlencoded.c (renamed from scan_urlencoded_query.c)27
-rw-r--r--ot_urlencoded.h (renamed from scan_urlencoded_query.h)12
-rw-r--r--ot_vector.c47
-rw-r--r--proxy.c4
-rw-r--r--tests/integration/.gitignore2
-rw-r--r--tests/integration/harness.py308
-rw-r--r--tests/integration/test_accesslist.py100
-rw-r--r--tests/integration/test_announce.py129
-rw-r--r--tests/integration/test_dualstack.py89
-rw-r--r--tests/integration/test_fullscrape.py146
-rw-r--r--tests/integration/test_keepalive.py111
-rw-r--r--tests/integration/test_stats.py169
-rw-r--r--tests/integration/test_udp.py168
-rw-r--r--tests/runner.c76
-rw-r--r--tests/t_event.c222
-rw-r--r--tests/t_fmt.c108
-rw-r--r--tests/t_hex.c28
-rw-r--r--tests/t_iovec.c20
-rw-r--r--tests/t_ip.c54
-rw-r--r--tests/t_scan.c124
-rw-r--r--tests/t_urlencoded.c (renamed from tests/t_scan_urlencoded_query.c)83
-rw-r--r--tests/t_vector.c46
-rw-r--r--tests/test.h4
-rw-r--r--trackerlogic.c55
-rw-r--r--trackerlogic.h2
56 files changed, 2899 insertions, 624 deletions
diff --git a/Makefile b/Makefile
index 87c20e2..d16e220 100644
--- a/Makefile
+++ b/Makefile
@@ -47,10 +47,6 @@ include Makefile.gzip
47#FEATURES+=-DWANT_DEV_RANDOM 47#FEATURES+=-DWANT_DEV_RANDOM
48FEATURES+=-DWANT_FULLSCRAPE 48FEATURES+=-DWANT_FULLSCRAPE
49 49
50# You need libowfat version 0.34 to allow for automatic release of chunks during
51# full scrape transfer, if you rely on an older versions, enable this flag
52#FEATURES+=-DWANT_NO_AUTO_FREE
53
54# opentracker takes its randomness from arc4random, which is part of libc on 50# opentracker takes its randomness from arc4random, which is part of libc on
55# the BSDs, on macOS and on glibc 2.36 and newer. On an older glibc or on musl 51# the BSDs, on macOS and on glibc 2.36 and newer. On an older glibc or on musl
56# it comes from libbsd instead, so uncomment the next include to link it. 52# it comes from libbsd instead, so uncomment the next include to link it.
@@ -64,12 +60,17 @@ GIT_VERSION=`sh -c 'command -v git >/dev/null && test -d .git && git rev-parse H
64OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage 60OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage
65OPTS_production=-O3 61OPTS_production=-O3
66 62
67CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra -Wwrite-strings #-ansi -pedantic 63CFLAGS+=-DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra -Wwrite-strings #-ansi -pedantic
68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread 64LDFLAGS+=-pthread
65
66# Only the proxy, which has not been ported since the tracker went dual stack,
67# still wants libowfat.
68CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS)
69LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat
69 70
70BINARY =opentracker 71BINARY =opentracker
71HEADERS=trackerlogic.h scan_urlencoded_query.h ot_batch.h ot_buf.h ot_fdstate.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h 72HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h
72SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_batch.c ot_buf.c ot_fdstate.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c 73SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c
73SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c 74SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c
74 75
75OBJECTS = $(SOURCES:%.c=%.o) 76OBJECTS = $(SOURCES:%.c=%.o)
@@ -84,8 +85,8 @@ all: $(BINARY) $(BINARY).debug
84CFLAGS_production = $(CFLAGS) $(OPTS_production) $(FEATURES) 85CFLAGS_production = $(CFLAGS) $(OPTS_production) $(FEATURES)
85CFLAGS_debug = $(CFLAGS) $(OPTS_debug) $(FEATURES) 86CFLAGS_debug = $(CFLAGS) $(OPTS_debug) $(FEATURES)
86 87
87CFLAGS_proxy = $(CFLAGS_production) -DWANT_SYNC_LIVE 88CFLAGS_proxy = $(CFLAGS_production) $(CFLAGS_libowfat) -DWANT_SYNC_LIVE
88CFLAGS_proxy_debug = $(CFLAGS_debug) -DWANT_SYNC_LIVE 89CFLAGS_proxy_debug = $(CFLAGS_debug) $(CFLAGS_libowfat) -DWANT_SYNC_LIVE
89 90
90$(BINARY): $(OBJECTS) 91$(BINARY): $(OBJECTS)
91 $(CC) -o $@ $(OBJECTS) $(LDFLAGS) 92 $(CC) -o $@ $(OBJECTS) $(LDFLAGS)
@@ -93,9 +94,9 @@ $(BINARY): $(OBJECTS)
93$(BINARY).debug: $(OBJECTS_debug) 94$(BINARY).debug: $(OBJECTS_debug)
94 $(CC) -o $@ $(OBJECTS_debug) $(LDFLAGS) 95 $(CC) -o $@ $(OBJECTS_debug) $(LDFLAGS)
95proxy: $(OBJECTS_proxy) 96proxy: $(OBJECTS_proxy)
96 $(CC) -o $@ $(OBJECTS_proxy) $(CFLAGS_production) $(LDFLAGS) 97 $(CC) -o $@ $(OBJECTS_proxy) $(CFLAGS_production) $(LDFLAGS) $(LDFLAGS_libowfat)
97proxy.debug: $(OBJECTS_proxy_debug) 98proxy.debug: $(OBJECTS_proxy_debug)
98 $(CC) -o $@ $(OBJECTS_proxy_debug) $(LDFLAGS) 99 $(CC) -o $@ $(OBJECTS_proxy_debug) $(LDFLAGS) $(LDFLAGS_libowfat)
99 100
100$(OBJECTS): $(HEADERS) 101$(OBJECTS): $(HEADERS)
101$(OBJECTS_debug): $(HEADERS) 102$(OBJECTS_debug): $(HEADERS)
@@ -119,8 +120,8 @@ $(OBJECTS_proxy_debug): $(HEADERS)
119# has to. tests/stubs.c supplies the few symbols those modules reference but 120# has to. tests/stubs.c supplies the few symbols those modules reference but
120# the tests never reach. 121# the tests never reach.
121TESTBIN=tests/runner 122TESTBIN=tests/runner
122TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.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_batch.c tests/t_fdstate.c 123TEST_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_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c
123TEST_UNITS=scan_urlencoded_query.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c 124TEST_UNITS=ot_urlencoded.c ot_fmt.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c
124TEST_HEADERS=tests/test.h $(HEADERS) 125TEST_HEADERS=tests/test.h $(HEADERS)
125# Tests reach code that sits behind feature flags, so the units under test are 126# Tests reach code that sits behind feature flags, so the units under test are
126# built with those features enabled. A test binary is not a production binary. 127# built with those features enabled. A test binary is not a production binary.
@@ -133,10 +134,46 @@ $(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS)
133check: $(TESTBIN) $(BINARY) 134check: $(TESTBIN) $(BINARY)
134 ./$(TESTBIN) 135 ./$(TESTBIN)
135 136
136.PHONY: all check clean install 137# The same unit tests under the sanitizers. Use clang over $(CC) because the
138# two toolchains do not check the same things
139# Override CC_sanitize to use another compiler.
140CC_sanitize?=clang
141CFLAGS_sanitize=$(CFLAGS_test) -fsanitize=address,undefined -fno-omit-frame-pointer
142LDFLAGS_sanitize=$(LDFLAGS) -fsanitize=address,undefined
143
144$(TESTBIN).sanitize: $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS)
145 $(CC_sanitize) -o $@ $(CFLAGS_sanitize) $(TEST_SOURCES) $(TEST_UNITS) $(LDFLAGS_sanitize)
146
147check-sanitize: $(TESTBIN).sanitize
148 ./$(TESTBIN).sanitize
149
150# Black box tests: these drive the real binary over the wire, so they cover
151# the event loop and the parsers working together, which the unit tests
152# cannot reach. Built with the sanitizers on, so the tracker reports its own
153# memory errors and the harness turns those into failures.
154PYTHON?=python3
155FEATURES_integration=$(FEATURES) -DWANT_IP_FROM_QUERY_STRING -DWANT_ACCESSLIST_BLACK -DWANT_KEEPALIVE
156CFLAGS_integration=$(CFLAGS) $(OPTS_debug) $(FEATURES_integration) -fsanitize=address,undefined -fno-omit-frame-pointer
157LDFLAGS_integration=$(LDFLAGS) -fsanitize=address,undefined
158
159opentracker.integration: $(SOURCES) $(HEADERS)
160 $(CC) -o $@ $(CFLAGS_integration) $(SOURCES) $(LDFLAGS_integration)
161
162check-integration: opentracker.integration
163 OPENTRACKER=./opentracker.integration $(PYTHON) -m unittest discover -s tests/integration
164
165# Everything, cheapest first, so a quick failure comes back quickly.
166check-all:
167 $(MAKE) check
168 $(MAKE) check-sanitize
169 $(MAKE) check-integration
170
171.PHONY: all check check-sanitize check-integration check-all clean install
172
173BINARIES=opentracker opentracker.debug opentracker.integration proxy proxy.debug $(TESTBIN) $(TESTBIN).sanitize
137 174
138clean: 175clean:
139 rm -rf opentracker opentracker.debug proxy proxy.debug $(TESTBIN) *.o *~ 176 rm -rf $(BINARIES) $(BINARIES:=.dSYM) *.o *~
140 177
141install: 178install:
142 install -m 755 opentracker $(DESTDIR)$(BINDIR) 179 install -m 755 opentracker $(DESTDIR)$(BINDIR)
diff --git a/opentracker.c b/opentracker.c
index 0f0b314..11da486 100644
--- a/opentracker.c
+++ b/opentracker.c
@@ -21,9 +21,6 @@
21#include <syslog.h> 21#include <syslog.h>
22#endif 22#endif
23 23
24/* Libowfat */
25#include "io.h"
26
27/* Opentracker */ 24/* Opentracker */
28#include "ot_accesslist.h" 25#include "ot_accesslist.h"
29#include "ot_config.h" 26#include "ot_config.h"
@@ -34,6 +31,7 @@
34#include "ot_mutex.h" 31#include "ot_mutex.h"
35#include "ot_random.h" 32#include "ot_random.h"
36#include "ot_scan.h" 33#include "ot_scan.h"
34#include "ot_event.h"
37#include "ot_fdstate.h" 35#include "ot_fdstate.h"
38#include "ot_socket.h" 36#include "ot_socket.h"
39#include "ot_stats.h" 37#include "ot_stats.h"
@@ -130,14 +128,15 @@ static void handle_dead(const int64_t sock) {
130 } 128 }
131 /* The generation has to move before the kernel can reissue the number */ 129 /* The generation has to move before the kernel can reissue the number */
132 fdstate_close(sock); 130 fdstate_close(sock);
133 io_close(sock); 131 event_del((int)sock);
132 close((int)sock);
134} 133}
135 134
136static void handle_read(const int64_t sock, struct ot_workstruct *ws) { 135static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
137 struct http_data *cookie = fdstate_cookie(sock); 136 struct http_data *cookie = fdstate_cookie(sock);
138 ssize_t byte_count = io_tryread(sock, ws->inbuf, G_INBUF_SIZE); 137 ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE);
139 138
140 if (byte_count == 0 || byte_count == -3) { 139 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
141 handle_dead(sock); 140 handle_dead(sock);
142 return; 141 return;
143 } 142 }
@@ -178,12 +177,13 @@ static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
178} 177}
179 178
180static void handle_write(const int64_t sock) { 179static void handle_write(const int64_t sock) {
181 struct http_data *cookie = fdstate_cookie(sock); 180 struct http_data *cookie = fdstate_cookie(sock);
182 int chunked = 0; 181 int chunked = 0;
182 int keep_alive = 0;
183 183
184 if (cookie) { 184 if (cookie) {
185 if (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) 185 chunked = (cookie->flag & STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER) != 0;
186 chunked = 1; 186 keep_alive = (cookie->flag & STRUCT_HTTP_FLAG_KEEPALIVE) != 0;
187 187
188 if (cookie->batch) { 188 if (cookie->batch) {
189 if (batch_send(sock, cookie->batch) == BATCH_FAILED) { 189 if (batch_send(sock, cookie->batch) == BATCH_FAILED) {
@@ -197,8 +197,14 @@ static void handle_write(const int64_t sock) {
197 197
198 /* In a chunked transfer, wait for the next piece once everything queued has gone out */ 198 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
199 if (chunked) 199 if (chunked)
200 io_dontwantwrite(sock); 200 http_interest_drop(sock, cookie, EVENT_OUT);
201 else 201 else if (keep_alive) {
202 /* The reply is out and the client asked to keep the connection, so stop
203 waiting to write and go back to waiting for the next request */
204 http_interest_drop(sock, cookie, EVENT_OUT);
205 http_interest_add(sock, cookie, EVENT_IN);
206 fdstate_set_expiry(sock, g_now_seconds + OT_CLIENT_TIMEOUT);
207 } else
202 handle_dead(sock); 208 handle_dead(sock);
203} 209}
204 210
@@ -210,25 +216,30 @@ static void handle_accept(const int64_t serversocket) {
210 216
211 while ((sock = socket_accept(serversocket, ip, &port)) != -1) { 217 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
212 218
213 /* Put fd into a non-blocking mode */ 219 socket_set_blocking((int)sock, 0);
214 io_nonblock(sock);
215 220
216 if (!io_fd(sock) || !(cookie = (struct http_data *)malloc(sizeof(struct http_data)))) { 221 if (!(cookie = (struct http_data *)malloc(sizeof(struct http_data)))) {
217 io_close(sock); 222 close((int)sock);
218 continue; 223 continue;
219 } 224 }
220 memset(cookie, 0, sizeof(struct http_data)); 225 memset(cookie, 0, sizeof(struct http_data));
221 memcpy(cookie->ip, ip, sizeof(ot_ip6)); 226 memcpy(cookie->ip, ip, sizeof(ot_ip6));
227 cookie->interest = EVENT_IN;
222 228
223 fdstate_open(sock, cookie, g_now_seconds + OT_CLIENT_TIMEOUT); 229 if (event_add((int)sock, EVENT_IN, fdstate_open(sock, cookie, g_now_seconds + OT_CLIENT_TIMEOUT)) == -1) {
224 io_wantread(sock); 230 fdstate_close(sock);
231 free(cookie);
232 close((int)sock);
233 continue;
234 }
225 235
226 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); 236 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
227
228 } 237 }
229 io_eagain(serversocket);
230} 238}
231 239
240/* How many ready descriptors one wait hands back at a time */
241#define OT_EVENT_BATCH 512
242
232static void *server_mainloop(void *args) { 243static void *server_mainloop(void *args) {
233 struct ot_workstruct ws; 244 struct ot_workstruct ws;
234 time_t next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL; 245 time_t next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL;
@@ -251,28 +262,42 @@ static void *server_mainloop(void *args) {
251 random_bytes(ws.rand48_state, sizeof(ws.rand48_state)); 262 random_bytes(ws.rand48_state, sizeof(ws.rand48_state));
252 263
253 for (;;) { 264 for (;;) {
254 int64_t sock; 265 ot_event ready[OT_EVENT_BATCH];
266 int64_t sock;
267 int count, i;
268
269 count = event_wait(ready, OT_EVENT_BATCH, OT_CLIENT_TIMEOUT_CHECKINTERVAL * 1000);
270
271 for (i = 0; i < count; ++i) {
272 const void *cookie;
255 273
256 io_wait(); 274 sock = ready[i].fd;
275 /* A descriptor closed earlier in this same batch may already have been
276 handed out again by accept, so an event is only ours if the
277 generation it was registered under is still the one in place */
278 if (fdstate_generation((int)sock) != ready[i].generation)
279 continue;
280 cookie = fdstate_cookie((int)sock);
257 281
258 while ((sock = io_canread()) != -1) {
259 const void *cookie = fdstate_cookie(sock);
260 if ((intptr_t)cookie == FLAG_TCP) 282 if ((intptr_t)cookie == FLAG_TCP)
261 handle_accept(sock); 283 handle_accept(sock);
262 else if ((intptr_t)cookie == FLAG_UDP) 284 else if ((intptr_t)cookie == FLAG_UDP)
263 handle_udp6(sock, &ws); 285 handle_udp6(sock, &ws);
264 else if ((intptr_t)cookie == FLAG_SELFPIPE) 286 else if ((intptr_t)cookie == FLAG_SELFPIPE)
265 io_tryread(sock, ws.inbuf, G_INBUF_SIZE); 287 while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0)
266 else 288 ;
267 handle_read(sock, &ws); 289 else {
290 if (ready[i].what & EVENT_IN)
291 handle_read(sock, &ws);
292 /* The read may have closed it, so look again before writing */
293 if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation)
294 handle_write(sock);
295 }
268 } 296 }
269 297
270 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) 298 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1)
271 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); 299 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial);
272 300
273 while ((sock = io_canwrite()) != -1)
274 handle_write(sock);
275
276 if (g_now_seconds > next_timeout_check) { 301 if (g_now_seconds > next_timeout_check) {
277 int cursor = 0; 302 int cursor = 0;
278 303
@@ -319,11 +344,8 @@ static void listen_bind_one(ot_listen *entry) {
319 panic("socket_udp_bind"); 344 panic("socket_udp_bind");
320 } 345 }
321 346
322 if (!io_fd(entry->sock)) 347 if (event_add((int)entry->sock, EVENT_IN, fdstate_open(entry->sock, (void *)entry->proto, 0)) == -1)
323 panic("io_fd"); 348 panic("event_add");
324
325 fdstate_open(entry->sock, (void *)entry->proto, 0);
326 io_wantread(entry->sock);
327 349
328#ifdef _DEBUG 350#ifdef _DEBUG
329 fputs(" success.\n", stderr); 351 fputs(" success.\n", stderr);
@@ -352,7 +374,8 @@ static void listen_start_udp_workers(void) {
352 if (entry->proto != FLAG_UDP || entry->workers <= 0) 374 if (entry->proto != FLAG_UDP || entry->workers <= 0)
353 continue; 375 continue;
354 376
355 io_block(entry->sock); 377 socket_set_blocking((int)entry->sock, 1);
378 event_del((int)entry->sock);
356 udp_workers_install(entry->sock, (unsigned int)entry->workers); 379 udp_workers_install(entry->sock, (unsigned int)entry->workers);
357 } 380 }
358} 381}
@@ -375,14 +398,14 @@ void load_state(const char *const state_filename) {
375 while (fgets(inbuf, sizeof(inbuf), state_filehandle)) { 398 while (fgets(inbuf, sizeof(inbuf), state_filehandle)) {
376 size_t i; 399 size_t i;
377 400
378 if (!hex_tohash(infohash, inbuf)) 401 if (!hex_to_hash(infohash, inbuf))
379 continue; 402 continue;
380 i = 2 * sizeof(ot_hash); 403 i = 2 * sizeof(ot_hash);
381 404
382 if (inbuf[i++] != ':' || !(consumed = scan_udec(inbuf + i, &base, UINT64_MAX))) 405 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &base, UINT64_MAX)))
383 continue; 406 continue;
384 i += consumed; 407 i += consumed;
385 if (inbuf[i++] != ':' || !(consumed = scan_udec(inbuf + i, &downcount, UINT64_MAX))) 408 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, UINT64_MAX)))
386 continue; 409 continue;
387 add_torrent_from_saved_state(infohash, base, downcount); 410 add_torrent_from_saved_state(infohash, base, downcount);
388 } 411 }
@@ -453,6 +476,8 @@ int main(int argc, char **argv) {
453 random_init(); 476 random_init();
454 if (fdstate_init() == -1) 477 if (fdstate_init() == -1)
455 panic("fdstate_init"); 478 panic("fdstate_init");
479 if (event_init() == -1)
480 panic("event_init");
456 config_parse(argc, argv); 481 config_parse(argc, argv);
457 482
458 listen_bind_all(); 483 listen_bind_all();
@@ -471,15 +496,15 @@ int main(int argc, char **argv) {
471 pthread_create(&thread_id, NULL, time_caching_worker, NULL); 496 pthread_create(&thread_id, NULL, time_caching_worker, NULL);
472 497
473 /* Create our self pipe which allows us to interrupt mainloops 498 /* Create our self pipe which allows us to interrupt mainloops
474 io_wait in case some data is available to send out */ 499 wait in case some data is available to send out */
475 if (pipe(g_self_pipe) == -1) 500 if (pipe(g_self_pipe) == -1)
476 panic("selfpipe failed: "); 501 panic("selfpipe failed: ");
477 if (!io_fd(g_self_pipe[0])) 502 /* Both ends have to be non blocking: the loop drains the read end until it
478 panic("selfpipe io_fd failed: "); 503 is empty, and a worker nudging a full pipe has already made its point */
479 if (!io_fd(g_self_pipe[1])) 504 socket_set_blocking(g_self_pipe[0], 0);
480 panic("selfpipe io_fd failed: "); 505 socket_set_blocking(g_self_pipe[1], 0);
481 fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0); 506 if (event_add(g_self_pipe[0], EVENT_IN, fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0)) == -1)
482 io_wantread(g_self_pipe[0]); 507 panic("selfpipe event_add");
483 508
484 /* Init all sub systems. This call may fail with an exit() */ 509 /* Init all sub systems. This call may fail with an exit() */
485 trackerlogic_init(); 510 trackerlogic_init();
@@ -499,6 +524,7 @@ int main(int argc, char **argv) {
499 524
500 server_mainloop(0); 525 server_mainloop(0);
501 526
527 event_deinit();
502 fdstate_deinit(); 528 fdstate_deinit();
503 return 0; 529 return 0;
504} 530}
diff --git a/ot_accesslist.c b/ot_accesslist.c
index ef845d6..4bf1701 100644
--- a/ot_accesslist.c
+++ b/ot_accesslist.c
@@ -21,8 +21,6 @@
21#include <errno.h> 21#include <errno.h>
22#endif 22#endif
23 23
24/* Libowfat */
25
26/* Opentracker */ 24/* Opentracker */
27#include "ot_accesslist.h" 25#include "ot_accesslist.h"
28#include "ot_hex.h" 26#include "ot_hex.h"
@@ -110,7 +108,7 @@ size_t accesslist_parse_hashes(ot_hash *dest, size_t capacity, const char *buf,
110 size_t count = 0; 108 size_t count = 0;
111 109
112 while (count < capacity && end - offs >= 40) { 110 while (count < capacity && end - offs >= 40) {
113 if (hex_tohash(dest[count], offs)) { 111 if (hex_to_hash(dest[count], offs)) {
114 offs += 40; 112 offs += 40;
115 113
116 if (offs == end || !hex_isdigit((unsigned char)*offs)) 114 if (offs == end || !hex_isdigit((unsigned char)*offs))
@@ -162,6 +160,9 @@ static void accesslist_readfile(void) {
162 const char *map; 160 const char *map;
163 size_t maplen, capacity, found; 161 size_t maplen, capacity, found;
164 162
163 if (!g_accesslist_filename)
164 return;
165
165 if (accesslist_map(g_accesslist_filename, &map, &maplen) < 0) { 166 if (accesslist_map(g_accesslist_filename, &map, &maplen) < 0) {
166 char *wd = getcwd(NULL, 0); 167 char *wd = getcwd(NULL, 0);
167 fprintf(stderr, "Warning: Can't open accesslist file: %s (but will try to create it later, if necessary and possible).\nPWD: %s\n", g_accesslist_filename, wd); 168 fprintf(stderr, "Warning: Can't open accesslist file: %s (but will try to create it later, if necessary and possible).\nPWD: %s\n", g_accesslist_filename, wd);
@@ -187,7 +188,8 @@ static void accesslist_readfile(void) {
187 if (map) 188 if (map)
188 munmap((void *)map, maplen); 189 munmap((void *)map, maplen);
189 190
190 qsort(accesslist_new->list, found, sizeof(ot_hash), vector_compare_hash); 191 if (found)
192 qsort(accesslist_new->list, found, sizeof(ot_hash), vector_compare_hash);
191 accesslist_new->size = found; 193 accesslist_new->size = found;
192 194
193 /* Now exchange the accesslist vector in the least race condition prone way */ 195 /* Now exchange the accesslist vector in the least race condition prone way */
@@ -225,18 +227,18 @@ int accesslist_hashisvalid(const ot_hash hash) {
225#endif 227#endif
226 void *exactmatch = NULL; 228 void *exactmatch = NULL;
227 229
228 if (accesslist) 230 if (accesslist && accesslist->size)
229 exactmatch = bsearch(hash, accesslist->list, accesslist->size, OT_HASH_COMPARE_SIZE, vector_compare_hash); 231 exactmatch = bsearch(hash, accesslist->list, accesslist->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
230 232
231#ifdef WANT_DYNAMIC_ACCESSLIST 233#ifdef WANT_DYNAMIC_ACCESSLIST
232 /* If we had no match on the main list, scan the list of dynamically added hashes */ 234 /* If we had no match on the main list, scan the list of dynamically added hashes */
233 accesslist_add = g_accesslist_add; 235 accesslist_add = g_accesslist_add;
234 if ((exactmatch == NULL) && accesslist_add) 236 if ((exactmatch == NULL) && accesslist_add && accesslist_add->size)
235 exactmatch = bsearch(hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash); 237 exactmatch = bsearch(hash, accesslist_add->list, accesslist_add->size, OT_HASH_COMPARE_SIZE, vector_compare_hash);
236 238
237 /* If we found a matching hash on the main list, scan the list of dynamically deleted hashes */ 239 /* If we found a matching hash on the main list, scan the list of dynamically deleted hashes */
238 accesslist_delete = g_accesslist_delete; 240 accesslist_delete = g_accesslist_delete;
239 if ((exactmatch != NULL) && accesslist_delete && bsearch(hash, accesslist_delete->list, accesslist_delete->size, OT_HASH_COMPARE_SIZE, vector_compare_hash)) 241 if ((exactmatch != NULL) && accesslist_delete && accesslist_delete->size && bsearch(hash, accesslist_delete->list, accesslist_delete->size, OT_HASH_COMPARE_SIZE, vector_compare_hash))
240 exactmatch = NULL; 242 exactmatch = NULL;
241#endif 243#endif
242 244
@@ -308,7 +310,7 @@ static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_
308 if (linelen < 41) 310 if (linelen < 41)
309 continue; 311 continue;
310 312
311 if (!hex_tohash(info_hash, line)) 313 if (!hex_to_hash(info_hash, line))
312 continue; 314 continue;
313 315
314 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the 316 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the
@@ -386,8 +388,9 @@ void accesslist_init(void) {
386} 388}
387 389
388void accesslist_deinit(void) { 390void accesslist_deinit(void) {
389 /* Wake up sleeping worker */ 391 /* Wake the worker and wait for it to notice that the tracker is stopping. */
390 pthread_kill(thread_id, SIGHUP); 392 pthread_kill(thread_id, SIGHUP);
393 pthread_join(thread_id, NULL);
391 394
392 pthread_mutex_lock(&g_accesslist_mutex); 395 pthread_mutex_lock(&g_accesslist_mutex);
393 396
@@ -399,7 +402,6 @@ void accesslist_deinit(void) {
399#endif 402#endif
400 403
401 pthread_mutex_unlock(&g_accesslist_mutex); 404 pthread_mutex_unlock(&g_accesslist_mutex);
402 pthread_cancel(thread_id);
403 pthread_mutex_destroy(&g_accesslist_mutex); 405 pthread_mutex_destroy(&g_accesslist_mutex);
404} 406}
405 407
diff --git a/ot_clean.c b/ot_clean.c
index 825290d..bf0504c 100644
--- a/ot_clean.c
+++ b/ot_clean.c
@@ -8,9 +8,6 @@
8#include <string.h> 8#include <string.h>
9#include <unistd.h> 9#include <unistd.h>
10 10
11/* Libowfat */
12#include "io.h"
13
14/* Opentracker */ 11/* Opentracker */
15#include "ot_accesslist.h" 12#include "ot_accesslist.h"
16#include "ot_clean.h" 13#include "ot_clean.h"
diff --git a/ot_config.c b/ot_config.c
index a5fc06e..ee6bc12 100644
--- a/ot_config.c
+++ b/ot_config.c
@@ -107,7 +107,7 @@ static int listen_scan_options(char *p, int *shared, int *workers) {
107 *shared = 1; 107 *shared = 1;
108 break; 108 break;
109 case LISTEN_OPTION_WORKERS: { 109 case LISTEN_OPTION_WORKERS: {
110 size_t off = scan_udec(p, &value, UINT_MAX); 110 size_t off = scan_uint(p, &value, UINT_MAX);
111 111
112 if (!off) 112 if (!off)
113 return 0; 113 return 0;
@@ -140,7 +140,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
140 ++s; 140 ++s;
141 if (*s == '[') 141 if (*s == '[')
142 ++s, ++bracket; /* for v6 style notation */ 142 ++s, ++bracket; /* for v6 style notation */
143 if (!(off = ip_scan(s, ip))) 143 if (!(off = ip_parse(s, ip)))
144 return 0; 144 return 0;
145 s += off; 145 s += off;
146 if (bracket && *s == ']') 146 if (bracket && *s == ']')
@@ -157,7 +157,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
157 if (*(s++) != ':') 157 if (*(s++) != ':')
158 return 0; 158 return 0;
159 } 159 }
160 if (!(off = scan_udec(s, &value, 65535))) 160 if (!(off = scan_uint(s, &value, 65535)))
161 return 0; 161 return 0;
162 *port = (uint16_t)value; 162 *port = (uint16_t)value;
163 return off + s - src; 163 return off + s - src;
@@ -169,14 +169,14 @@ static int scan_ip6_net(const char *src, ot_net *net) {
169 int off; 169 int off;
170 while (isspace(*s)) 170 while (isspace(*s))
171 ++s; 171 ++s;
172 if (!(off = ip_scan(s, net->address))) 172 if (!(off = ip_parse(s, net->address)))
173 return 0; 173 return 0;
174 s += off; 174 s += off;
175 if (*s != '/') 175 if (*s != '/')
176 net->bits = 128; 176 net->bits = 128;
177 else { 177 else {
178 s++; 178 s++;
179 if (!(off = scan_udec(s, &value, 128))) 179 if (!(off = scan_uint(s, &value, 128)))
180 return 0; 180 return 0;
181 net->bits = (int)value; 181 net->bits = (int)value;
182 if (ip_is_v4mapped(net->address)) 182 if (ip_is_v4mapped(net->address))
@@ -324,12 +324,12 @@ static int parse_configfile(char *config_filename) {
324 break; 324 break;
325 } 325 }
326 case CONFIG_LISTEN_UDP_WORKERS: 326 case CONFIG_LISTEN_UDP_WORKERS:
327 if (!scan_udec(p, &value, UINT_MAX)) 327 if (!scan_uint(p, &value, UINT_MAX))
328 goto parse_error; 328 goto parse_error;
329 g_listen_workers = (int)value; 329 g_listen_workers = (int)value;
330 break; 330 break;
331 case CONFIG_LISTEN_SHARED: 331 case CONFIG_LISTEN_SHARED:
332 if (!scan_udec(p, &value, 1)) 332 if (!scan_uint(p, &value, 1))
333 goto parse_error; 333 goto parse_error;
334 g_listen_shared = (int)value; 334 g_listen_shared = (int)value;
335 break; 335 break;
@@ -479,7 +479,7 @@ void config_parse(int argc, char **argv) {
479 scanon = 0; 479 scanon = 0;
480 break; 480 break;
481 case 'i': 481 case 'i':
482 if (!ip_scan(optarg, serverip)) { 482 if (!ip_parse(optarg, serverip)) {
483 usage(argv[0]); 483 usage(argv[0]);
484 exit(1); 484 exit(1);
485 } 485 }
@@ -494,7 +494,7 @@ void config_parse(int argc, char **argv) {
494 break; 494 break;
495#endif 495#endif
496 case 'p': 496 case 'p':
497 if (!scan_udec(optarg, &value, 65535)) { 497 if (!scan_uint(optarg, &value, 65535)) {
498 usage(argv[0]); 498 usage(argv[0]);
499 exit(1); 499 exit(1);
500 } 500 }
@@ -502,7 +502,7 @@ void config_parse(int argc, char **argv) {
502 bound++; 502 bound++;
503 break; 503 break;
504 case 'P': 504 case 'P':
505 if (!scan_udec(optarg, &value, 65535)) { 505 if (!scan_uint(optarg, &value, 65535)) {
506 usage(argv[0]); 506 usage(argv[0]);
507 exit(1); 507 exit(1);
508 } 508 }
@@ -511,7 +511,7 @@ void config_parse(int argc, char **argv) {
511 break; 511 break;
512#ifdef WANT_SYNC_LIVE 512#ifdef WANT_SYNC_LIVE
513 case 's': 513 case 's':
514 if (!scan_udec(optarg, &value, 65535)) { 514 if (!scan_uint(optarg, &value, 65535)) {
515 usage(argv[0]); 515 usage(argv[0]);
516 exit(1); 516 exit(1);
517 } 517 }
@@ -544,8 +544,11 @@ void config_parse(int argc, char **argv) {
544 help(argv[0]); 544 help(argv[0]);
545 exit(0); 545 exit(0);
546 case 'v': { 546 case 'v': {
547 char buffer[8192]; 547 char buffer[8192];
548 stats_return_tracker_version(buffer); 548 char *cursor = buffer;
549
550 stats_return_tracker_version(&cursor, buffer + sizeof(buffer) - 1);
551 *cursor = 0;
549 fputs(buffer, stderr); 552 fputs(buffer, stderr);
550 exit(0); 553 exit(0);
551 } 554 }
diff --git a/ot_event.c b/ot_event.c
new file mode 100644
index 0000000..ce0feef
--- /dev/null
+++ b/ot_event.c
@@ -0,0 +1,332 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11
12/* Opentracker */
13#include "ot_event.h"
14
15/* One caller waits, so the scratch the mechanism needs is kept here rather
16 than allocated per call. */
17
18/* poll works everywhere and is what the BSDs get until kqueue is written.
19 Defining OT_EVENT_FORCE_POLL picks it on a platform that would otherwise
20 use something better, which is how both are exercised by the tests. */
21#if defined(OT_EVENT_FORCE_POLL)
22#define OT_EVENT_POLL
23#elif defined(OT_EVENT_FORCE_KQUEUE)
24#define OT_EVENT_KQUEUE
25#elif defined(__linux__)
26#define OT_EVENT_EPOLL
27#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__)
28#define OT_EVENT_KQUEUE
29#else
30#define OT_EVENT_POLL
31#endif
32
33#ifdef OT_EVENT_EPOLL
34/* ------------------------------------------------------------- epoll */
35
36#include <sys/epoll.h>
37
38static int g_epoll = -1;
39static struct epoll_event *g_ready;
40static int g_ready_space;
41
42const char *event_mechanism(void) { return "epoll"; }
43
44int event_init(void) {
45 g_ready = NULL;
46 g_ready_space = 0;
47 return (g_epoll = epoll_create1(EPOLL_CLOEXEC)) == -1 ? -1 : 0;
48}
49
50void event_deinit(void) {
51 if (g_epoll != -1)
52 close(g_epoll);
53 free(g_ready);
54 g_epoll = -1;
55 g_ready = NULL;
56 g_ready_space = 0;
57}
58
59static int event_ctl(int op, int fd, int what, uint32_t generation) {
60 struct epoll_event ev;
61
62 memset(&ev, 0, sizeof(ev));
63 ev.events = (what & EVENT_IN ? (uint32_t)EPOLLIN : 0) | (what & EVENT_OUT ? (uint32_t)EPOLLOUT : 0);
64 /* The generation rides along inside the kernel, so an event says which
65 connection it belongs to without anybody looking it up */
66 ev.data.u64 = ((uint64_t)generation << 32) | (uint32_t)fd;
67 return epoll_ctl(g_epoll, op, fd, &ev);
68}
69
70int event_add(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_ADD, fd, what, generation); }
71int event_modify(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_MOD, fd, what, generation); }
72int event_del(int fd) { return epoll_ctl(g_epoll, EPOLL_CTL_DEL, fd, NULL); }
73
74int event_wait(ot_event *out, int max, int timeout_ms) {
75 int count, i;
76
77 if (max > g_ready_space) {
78 struct epoll_event *grown = realloc(g_ready, (size_t)max * sizeof(struct epoll_event));
79
80 if (!grown)
81 return -1;
82 g_ready = grown;
83 g_ready_space = max;
84 }
85
86 while ((count = epoll_wait(g_epoll, g_ready, max, timeout_ms)) == -1 && errno == EINTR)
87 ;
88 for (i = 0; i < count; ++i) {
89 uint32_t events = g_ready[i].events;
90
91 out[i].fd = (int)(uint32_t)g_ready[i].data.u64;
92 out[i].generation = (uint32_t)(g_ready[i].data.u64 >> 32);
93 /* A hangup or an error is reported as both, so whichever side the caller
94 was waiting on gets to notice */
95 out[i].what = (events & (EPOLLIN | EPOLLHUP | EPOLLERR) ? EVENT_IN : 0) |
96 (events & (EPOLLOUT | EPOLLHUP | EPOLLERR) ? EVENT_OUT : 0);
97 }
98 return count;
99}
100
101#endif /* OT_EVENT_EPOLL */
102
103#ifdef OT_EVENT_KQUEUE
104/* ------------------------------------------------------------ kqueue */
105
106#include <sys/event.h>
107#include <sys/time.h>
108#include <sys/types.h>
109
110static int g_kqueue = -1;
111static struct kevent *g_ready;
112static int g_ready_space;
113
114const char *event_mechanism(void) { return "kqueue"; }
115
116int event_init(void) {
117 g_ready = NULL;
118 g_ready_space = 0;
119 return (g_kqueue = kqueue()) == -1 ? -1 : 0;
120}
121
122void event_deinit(void) {
123 if (g_kqueue != -1)
124 close(g_kqueue);
125 free(g_ready);
126 g_kqueue = -1;
127 g_ready = NULL;
128 g_ready_space = 0;
129}
130
131/* kqueue keeps one registration per filter, so reading and writing are two
132 entries. A change of interest is an add of the one wanted and a delete
133 of the one not. The generation travels in udata, which the kernel passes
134 back untouched. */
135static int event_change(int fd, int what, uint32_t generation, int deleting) {
136 struct kevent change[2], result[2];
137 void *udata = (void *)(uintptr_t)(((uint64_t)generation << 32) | (uint32_t)fd);
138 int succeeded = 0, count, i;
139
140 EV_SET(&change[0], (uintptr_t)fd, EVFILT_READ,
141 EV_RECEIPT | ((deleting || !(what & EVENT_IN)) ? EV_DELETE : EV_ADD), 0, 0, udata);
142 EV_SET(&change[1], (uintptr_t)fd, EVFILT_WRITE,
143 EV_RECEIPT | ((deleting || !(what & EVENT_OUT)) ? EV_DELETE : EV_ADD), 0, 0, udata);
144
145 if ((count = kevent(g_kqueue, change, 2, result, 2, NULL)) == -1)
146 return -1;
147
148 for (i = 0; i < count && i < 2; ++i) {
149 if (!result[i].data) {
150 ++succeeded;
151 continue;
152 }
153 /* Dropping interest we never registered is not a failure */
154 if ((change[i].flags & EV_DELETE) && result[i].data == ENOENT)
155 continue;
156 errno = (int)result[i].data;
157 return -1;
158 }
159
160 /* Nothing at all was registered or removed, so the descriptor is unknown */
161 if (!succeeded) {
162 errno = ENOENT;
163 return -1;
164 }
165 return 0;
166}
167
168int event_add(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); }
169int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); }
170int event_del(int fd) { return event_change(fd, 0, 0, 1); }
171
172int event_wait(ot_event *out, int max, int timeout_ms) {
173 struct timespec timeout;
174 struct timespec *deadline = NULL;
175 int count, i;
176
177 if (max > g_ready_space) {
178 struct kevent *grown = realloc(g_ready, (size_t)max * sizeof(struct kevent));
179
180 if (!grown)
181 return -1;
182 g_ready = grown;
183 g_ready_space = max;
184 }
185
186 if (timeout_ms >= 0) {
187 timeout.tv_sec = timeout_ms / 1000;
188 timeout.tv_nsec = (long)(timeout_ms % 1000) * 1000000;
189 deadline = &timeout;
190 }
191
192 while ((count = kevent(g_kqueue, NULL, 0, g_ready, max, deadline)) == -1 && errno == EINTR)
193 ;
194 for (i = 0; i < count; ++i) {
195 uint64_t udata = (uint64_t)(uintptr_t)g_ready[i].udata;
196 int eof = (g_ready[i].flags & (EV_EOF | EV_ERROR)) != 0;
197
198 out[i].fd = (int)(uint32_t)udata;
199 out[i].generation = (uint32_t)(udata >> 32);
200 out[i].what = ((g_ready[i].filter == EVFILT_READ || eof) ? EVENT_IN : 0) |
201 ((g_ready[i].filter == EVFILT_WRITE || eof) ? EVENT_OUT : 0);
202 }
203 return count;
204}
205
206#endif /* OT_EVENT_KQUEUE */
207
208#ifdef OT_EVENT_POLL
209/* -------------------------------------------------------------- poll */
210
211#include <poll.h>
212
213/* poll wants its descriptors packed, so the registered ones live in one
214 array and a second maps a descriptor number to its place in it. */
215static struct pollfd *g_pfds;
216static uint32_t *g_generation;
217static int *g_where;
218static int g_count, g_space, g_where_space;
219
220const char *event_mechanism(void) { return "poll"; }
221
222int event_init(void) {
223 g_pfds = NULL, g_generation = NULL, g_where = NULL;
224 g_count = g_space = g_where_space = 0;
225 return 0;
226}
227
228void event_deinit(void) {
229 free(g_pfds), free(g_generation), free(g_where);
230 g_pfds = NULL, g_generation = NULL, g_where = NULL;
231 g_count = g_space = g_where_space = 0;
232}
233
234static short event_to_poll(int what) {
235 return (short)((what & EVENT_IN ? POLLIN : 0) | (what & EVENT_OUT ? POLLOUT : 0));
236}
237
238static int event_room_for(int fd) {
239 if (fd >= g_where_space) {
240 int space = g_where_space ? g_where_space : 64;
241 int *grown;
242
243 while (space <= fd)
244 space *= 2;
245 if (!(grown = realloc(g_where, (size_t)space * sizeof(int))))
246 return -1;
247 while (g_where_space < space)
248 grown[g_where_space++] = -1;
249 g_where = grown;
250 }
251
252 if (g_count == g_space) {
253 int space = g_space ? g_space * 2 : 64;
254 struct pollfd *pfds = realloc(g_pfds, (size_t)space * sizeof(struct pollfd));
255 uint32_t *gens;
256
257 if (!pfds)
258 return -1;
259 g_pfds = pfds;
260 if (!(gens = realloc(g_generation, (size_t)space * sizeof(uint32_t))))
261 return -1;
262 g_generation = gens;
263 g_space = space;
264 }
265 return 0;
266}
267
268int event_add(int fd, int what, uint32_t generation) {
269 if (fd < 0 || event_room_for(fd) == -1)
270 return -1;
271 if (g_where[fd] != -1) {
272 errno = EEXIST;
273 return -1;
274 }
275
276 g_pfds[g_count].fd = fd;
277 g_pfds[g_count].events = event_to_poll(what);
278 g_pfds[g_count].revents = 0;
279 g_generation[g_count] = generation;
280 g_where[fd] = g_count++;
281 return 0;
282}
283
284int event_modify(int fd, int what, uint32_t generation) {
285 if (fd < 0 || fd >= g_where_space || g_where[fd] == -1) {
286 errno = ENOENT;
287 return -1;
288 }
289 g_pfds[g_where[fd]].events = event_to_poll(what);
290 g_generation[g_where[fd]] = generation;
291 return 0;
292}
293
294int event_del(int fd) {
295 int at;
296
297 if (fd < 0 || fd >= g_where_space || (at = g_where[fd]) == -1) {
298 errno = ENOENT;
299 return -1;
300 }
301
302 /* The last one moves into the gap, so the array stays packed */
303 g_pfds[at] = g_pfds[--g_count];
304 g_generation[at] = g_generation[g_count];
305 g_where[g_pfds[at].fd] = at;
306 g_where[fd] = -1;
307 return 0;
308}
309
310int event_wait(ot_event *out, int max, int timeout_ms) {
311 int ready, found = 0, i;
312
313 while ((ready = poll(g_pfds, (nfds_t)g_count, timeout_ms)) == -1 && errno == EINTR)
314 ;
315 if (ready <= 0)
316 return ready;
317
318 for (i = 0; i < g_count && found < max; ++i) {
319 short revents = g_pfds[i].revents;
320
321 if (!revents)
322 continue;
323 out[found].fd = g_pfds[i].fd;
324 out[found].generation = g_generation[i];
325 out[found].what = (revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL) ? EVENT_IN : 0) |
326 (revents & (POLLOUT | POLLHUP | POLLERR | POLLNVAL) ? EVENT_OUT : 0);
327 ++found;
328 }
329 return found;
330}
331
332#endif /* OT_EVENT_POLL */
diff --git a/ot_event.h b/ot_event.h
new file mode 100644
index 0000000..6023134
--- /dev/null
+++ b/ot_event.h
@@ -0,0 +1,39 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_EVENT_H__
7#define OT_EVENT_H__
8
9#include <stdint.h>
10
11/* Which descriptors have become readable or writable, asked of whichever
12 mechanism the kernel offers. The generation a descriptor was registered
13 with is handed back with every event, so a caller can tell an event for
14 the connection it is holding from one for a connection that has since been
15 closed and had its number reused. */
16
17enum { EVENT_IN = 1, EVENT_OUT = 2 };
18
19typedef struct {
20 int fd;
21 uint32_t generation;
22 int what;
23} ot_event;
24
25int event_init(void);
26void event_deinit(void);
27
28int event_add(int fd, int what, uint32_t generation);
29int event_modify(int fd, int what, uint32_t generation);
30int event_del(int fd);
31
32/* Waits at most timeout_ms, or indefinitely when that is negative, and fills
33 out with up to max ready descriptors. Returns how many, or -1. */
34int event_wait(ot_event *out, int max, int timeout_ms);
35
36/* Which mechanism was compiled in, for the version string */
37const char *event_mechanism(void);
38
39#endif
diff --git a/ot_fdstate.c b/ot_fdstate.c
index 7f73d3d..537bcb0 100644
--- a/ot_fdstate.c
+++ b/ot_fdstate.c
@@ -99,6 +99,12 @@ uint32_t fdstate_open(int fd, void *cookie, ot_time expiry) {
99 return FD_GEN(word); 99 return FD_GEN(word);
100} 100}
101 101
102uint32_t fdstate_generation(int fd) {
103 ot_fdslot *slot = fdstate_slot(fd);
104
105 return slot ? FD_GEN(atomic_load_explicit(&slot->state, memory_order_relaxed)) : 0;
106}
107
102int fdstate_claim(int fd, uint32_t generation) { 108int fdstate_claim(int fd, uint32_t generation) {
103 ot_fdslot *slot = fdstate_slot(fd); 109 ot_fdslot *slot = fdstate_slot(fd);
104 uint64_t word; 110 uint64_t word;
diff --git a/ot_fdstate.h b/ot_fdstate.h
index 88f685d..c09f018 100644
--- a/ot_fdstate.h
+++ b/ot_fdstate.h
@@ -31,6 +31,10 @@ size_t fdstate_slots(void);
31 generation to quote when asking for it back. expiry of 0 never expires. */ 31 generation to quote when asking for it back. expiry of 0 never expires. */
32uint32_t fdstate_open(int fd, void *cookie, ot_time expiry); 32uint32_t fdstate_open(int fd, void *cookie, ot_time expiry);
33 33
34/* Which generation a descriptor currently carries, to check an event that
35 was handed out earlier against the connection living there now. */
36uint32_t fdstate_generation(int fd);
37
34/* Ask for exclusive use. Returns 0 if the descriptor has been recycled since 38/* Ask for exclusive use. Returns 0 if the descriptor has been recycled since
35 generation was handed out, or if somebody else is using it. */ 39 generation was handed out, or if somebody else is using it. */
36int fdstate_claim(int fd, uint32_t generation); 40int fdstate_claim(int fd, uint32_t generation);
diff --git a/ot_fmt.c b/ot_fmt.c
new file mode 100644
index 0000000..9cab25f
--- /dev/null
+++ b/ot_fmt.c
@@ -0,0 +1,42 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <stdarg.h>
8#include <stdio.h>
9#include <string.h>
10
11/* Opentracker */
12#include "ot_fmt.h"
13
14void fmt_append(char **cursor, const char *end, const char *format, ...) {
15 va_list arguments;
16 size_t room;
17 int written;
18
19 if (*cursor >= end) {
20 *cursor = (char *)end;
21 return;
22 }
23
24 room = (size_t)(end - *cursor);
25 va_start(arguments, format);
26 written = vsnprintf(*cursor, room, format, arguments);
27 va_end(arguments);
28
29 if (written < 0)
30 return;
31 *cursor = (size_t)written >= room ? (char *)end : *cursor + written;
32}
33
34void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length) {
35 if (*cursor >= end || (size_t)(end - *cursor) < length) {
36 *cursor = (char *)end;
37 return;
38 }
39
40 memcpy(*cursor, bytes, length);
41 *cursor += length;
42}
diff --git a/ot_fmt.h b/ot_fmt.h
new file mode 100644
index 0000000..851b2d8
--- /dev/null
+++ b/ot_fmt.h
@@ -0,0 +1,21 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_FMT_H__
7#define OT_FMT_H__
8
9#include <stddef.h>
10
11/* Appending to a cursor that walks a fixed buffer. Both take the cursor by
12 reference and advance it, so a reply is written as a run of statements and
13 no caller does pointer arithmetic of its own. */
14
15void fmt_append(char **cursor, const char *end, const char *format, ...) __attribute__((format(printf, 3, 4)));
16
17/* Append raw bytes for the binary stretches of a bencoded reply.
18 Either everything is appended or nothing at all */
19void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length);
20
21#endif
diff --git a/ot_fullscrape.c b/ot_fullscrape.c
index 788e574..8518bd2 100644
--- a/ot_fullscrape.c
+++ b/ot_fullscrape.c
@@ -17,15 +17,13 @@
17#include <zstd.h> 17#include <zstd.h>
18#endif 18#endif
19 19
20/* Libowfat */
21#include "io.h"
22
23/* Opentracker */ 20/* Opentracker */
21#include "ot_fmt.h"
24#include "ot_hex.h" 22#include "ot_hex.h"
25#include "ot_fullscrape.h" 23#include "ot_fullscrape.h"
26#include "ot_iovec.h" 24#include "ot_iovec.h"
27#include "ot_mutex.h" 25#include "ot_mutex.h"
28#include "scan_urlencoded_query.h" 26#include "ot_urlencoded.h"
29#include "trackerlogic.h" 27#include "trackerlogic.h"
30 28
31/* Fetch full scrape info for all torrents 29/* Fetch full scrape info for all torrents
@@ -86,10 +84,15 @@ void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) {
86 mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); 84 mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype );
87} 85}
88 86
89static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { 87static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) {
90 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 88 /* Callers guarantee this much room: the chunk writer keeps its low
91 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 89 watermark that far from the end, the compressors hand over a buffer of
92 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; 90 exactly that size */
91 char *r = *cursor;
92 const char *r_end = r + OT_SCRAPE_MAXENTRYLEN;
93 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
94 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
95 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
93 96
94 switch (mode & TASK_TASK_MASK) { 97 switch (mode & TASK_TASK_MASK) {
95 case TASK_FULLSCRAPE: 98 case TASK_FULLSCRAPE:
@@ -101,18 +104,18 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre
101 memcpy(r, hash, sizeof(ot_hash)); 104 memcpy(r, hash, sizeof(ot_hash));
102 r += sizeof(ot_hash); 105 r += sizeof(ot_hash);
103 /* push rest of the scrape string */ 106 /* push rest of the scrape string */
104 r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count); 107 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count);
105 108
106 break; 109 break;
107 case TASK_FULLSCRAPE_TPB_ASCII: 110 case TASK_FULLSCRAPE_TPB_ASCII:
108 hex_fromhash(r, *hash); 111 hex_from_hash(r, *hash);
109 r += 2 * sizeof(ot_hash); 112 r += 2 * sizeof(ot_hash);
110 r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); 113 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
111 break; 114 break;
112 case TASK_FULLSCRAPE_TPB_ASCII_PLUS: 115 case TASK_FULLSCRAPE_TPB_ASCII_PLUS:
113 hex_fromhash(r, *hash); 116 hex_from_hash(r, *hash);
114 r += 2 * sizeof(ot_hash); 117 r += 2 * sizeof(ot_hash);
115 r += sprintf(r, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count); 118 fmt_append(&r, r_end, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count);
116 break; 119 break;
117 case TASK_FULLSCRAPE_TPB_BINARY: 120 case TASK_FULLSCRAPE_TPB_BINARY:
118 memcpy(r, *hash, sizeof(ot_hash)); 121 memcpy(r, *hash, sizeof(ot_hash));
@@ -123,15 +126,15 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre
123 break; 126 break;
124 case TASK_FULLSCRAPE_TPB_URLENCODED: 127 case TASK_FULLSCRAPE_TPB_URLENCODED:
125 r += urlencoded_format(r, *hash, sizeof(ot_hash)); 128 r += urlencoded_format(r, *hash, sizeof(ot_hash));
126 r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); 129 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
127 break; 130 break;
128 case TASK_FULLSCRAPE_TRACKERSTATE: 131 case TASK_FULLSCRAPE_TRACKERSTATE:
129 hex_fromhash(r, *hash); 132 hex_from_hash(r, *hash);
130 r += 2 * sizeof(ot_hash); 133 r += 2 * sizeof(ot_hash);
131 r += sprintf(r, ":%zd:%zd\n", torrent->peer_list6->base, down_count); 134 fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count);
132 break; 135 break;
133 } 136 }
134 return r; 137 *cursor = r;
135} 138}
136 139
137static void fullscrape_make(int taskid, ot_tasktype mode) { 140static void fullscrape_make(int taskid, ot_tasktype mode) {
@@ -148,7 +151,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
148 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN; 151 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
149 152
150 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) 153 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
151 r += sprintf(r, "d5:filesd"); 154 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "d5:filesd");
152 155
153 /* For each bucket... */ 156 /* For each bucket... */
154 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { 157 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
@@ -159,7 +162,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
159 162
160 /* For each torrent in this bucket.. */ 163 /* For each torrent in this bucket.. */
161 for (i = 0; i < torrents_list->size; ++i) { 164 for (i = 0; i < torrents_list->size; ++i) {
162 r = fullscrape_write_one(mode, r, torrents + i, &torrents[i].hash); 165 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
163 166
164 if (r > re) { 167 if (r > re) {
165 iovector.iov_len = r - (char *)iovector.iov_base; 168 iovector.iov_len = r - (char *)iovector.iov_base;
@@ -190,7 +193,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
190 } 193 }
191 194
192 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) 195 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
193 r += sprintf(r, "ee"); 196 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "ee");
194 197
195 /* Send rest of data */ 198 /* Send rest of data */
196 iovector.iov_len = r - (char *)iovector.iov_base; 199 iovector.iov_len = r - (char *)iovector.iov_base;
@@ -233,7 +236,8 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
233 /* For each torrent in this bucket.. */ 236 /* For each torrent in this bucket.. */
234 for (i = 0; i < torrents_list->size; ++i) { 237 for (i = 0; i < torrents_list->size; ++i) {
235 char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; 238 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
236 r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); 239 r = compress_buffer;
240 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
237 strm.next_in = (uint8_t *)compress_buffer; 241 strm.next_in = (uint8_t *)compress_buffer;
238 strm.avail_in = r - compress_buffer; 242 strm.avail_in = r - compress_buffer;
239 zres = deflate(&strm, Z_NO_FLUSH); 243 zres = deflate(&strm, Z_NO_FLUSH);
@@ -296,7 +300,7 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
296 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); 300 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
297 301
298 if (!iovector.iov_base) { 302 if (!iovector.iov_base) {
299 fprintf(stderr, "Problem with iovec_fix_increase_or_free\n"); 303 fprintf(stderr, "Problem with iovec_next_chunk\n");
300 deflateEnd(&strm); 304 deflateEnd(&strm);
301 return; 305 return;
302 } 306 }
@@ -362,7 +366,8 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
362 /* For each torrent in this bucket.. */ 366 /* For each torrent in this bucket.. */
363 for (i = 0; i < torrents_list->size; ++i) { 367 for (i = 0; i < torrents_list->size; ++i) {
364 char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; 368 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
365 r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); 369 r = compress_buffer;
370 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
366 inbuf.src = compress_buffer; 371 inbuf.src = compress_buffer;
367 inbuf.size = r - compress_buffer; 372 inbuf.size = r - compress_buffer;
368 inbuf.pos = 0; 373 inbuf.pos = 0;
@@ -422,7 +427,7 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
422 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); 427 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
423 428
424 if (!iovector.iov_base) { 429 if (!iovector.iov_base) {
425 fprintf(stderr, "Problem with iovec_fix_increase_or_free\n"); 430 fprintf(stderr, "Problem with iovec_next_chunk\n");
426 ZSTD_freeCCtx(zstream); 431 ZSTD_freeCCtx(zstream);
427 return; 432 return;
428 } 433 }
diff --git a/ot_hex.c b/ot_hex.c
index 9581951..2bf69c4 100644
--- a/ot_hex.c
+++ b/ot_hex.c
@@ -23,7 +23,7 @@ int hex_value(int c) {
23 23
24int hex_isdigit(int c) { return hex_value(c) >= 0; } 24int hex_isdigit(int c) { return hex_value(c) >= 0; }
25 25
26char *hex_fromhash(char *dest, const uint8_t *hash) { 26char *hex_from_hash(char *dest, const uint8_t *hash) {
27 static const char digits[] = "0123456789ABCDEF"; 27 static const char digits[] = "0123456789ABCDEF";
28 char *d = dest; 28 char *d = dest;
29 size_t i; 29 size_t i;
@@ -36,7 +36,7 @@ char *hex_fromhash(char *dest, const uint8_t *hash) {
36 return dest; 36 return dest;
37} 37}
38 38
39int hex_tohash(ot_hash hash, const char *src) { 39int hex_to_hash(ot_hash hash, const char *src) {
40 uint8_t decoded[sizeof(ot_hash)]; 40 uint8_t decoded[sizeof(ot_hash)];
41 size_t i; 41 size_t i;
42 42
diff --git a/ot_hex.h b/ot_hex.h
index 3b45b61..28eddf5 100644
--- a/ot_hex.h
+++ b/ot_hex.h
@@ -12,12 +12,12 @@
12 12
13/* Write hash as forty upper case hex digits followed by a terminating zero. 13/* Write hash as forty upper case hex digits followed by a terminating zero.
14 dest must have room for forty one bytes. Returns dest. */ 14 dest must have room for forty one bytes. Returns dest. */
15char *hex_fromhash(char *dest, const uint8_t *hash); 15char *hex_from_hash(char *dest, const uint8_t *hash);
16 16
17/* Read forty hex digits from src into hash. Returns non-zero on success. 17/* Read forty hex digits from src into hash. Returns non-zero on success.
18 On failure hash is left untouched and no byte of src beyond the offending 18 On failure hash is left untouched and no byte of src beyond the offending
19 character is read, so src need not be forty characters long. */ 19 character is read, so src need not be forty characters long. */
20int hex_tohash(ot_hash hash, const char *src); 20int hex_to_hash(ot_hash hash, const char *src);
21 21
22/* Non-zero if c is a hexadecimal digit, in either case. */ 22/* Non-zero if c is a hexadecimal digit, in either case. */
23int hex_isdigit(int c); 23int hex_isdigit(int c);
diff --git a/ot_http.c b/ot_http.c
index 6bf343c..2216453 100644
--- a/ot_http.c
+++ b/ot_http.c
@@ -14,9 +14,6 @@
14#include <sys/types.h> 14#include <sys/types.h>
15#include <unistd.h> 15#include <unistd.h>
16 16
17/* Libowfat */
18#include "io.h"
19
20/* Opentracker */ 17/* Opentracker */
21#include "ot_accesslist.h" 18#include "ot_accesslist.h"
22#include "ot_fullscrape.h" 19#include "ot_fullscrape.h"
@@ -25,9 +22,12 @@
25#include "ot_ip.h" 22#include "ot_ip.h"
26#include "ot_mutex.h" 23#include "ot_mutex.h"
27#include "ot_scan.h" 24#include "ot_scan.h"
25#include "ot_event.h"
28#include "ot_fdstate.h" 26#include "ot_fdstate.h"
27#include "ot_vector.h"
28#include "ot_fmt.h"
29#include "ot_stats.h" 29#include "ot_stats.h"
30#include "scan_urlencoded_query.h" 30#include "ot_urlencoded.h"
31#include "trackerlogic.h" 31#include "trackerlogic.h"
32 32
33#define OT_MAXMULTISCRAPE_COUNT 64 33#define OT_MAXMULTISCRAPE_COUNT 64
@@ -43,7 +43,7 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
43 ssize_t written_size; 43 ssize_t written_size;
44 44
45 if (!cookie) { 45 if (!cookie) {
46 io_close(sock); 46 event_del((int)sock), close((int)sock);
47 return; 47 return;
48 } 48 }
49 49
@@ -67,7 +67,7 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
67 if ((written_size < 0) || ((written_size == ws->reply_size) && !ws->keep_alive)) { 67 if ((written_size < 0) || ((written_size == ws->reply_size) && !ws->keep_alive)) {
68 buf_reset(&cookie->request); 68 buf_reset(&cookie->request);
69 free(cookie); 69 free(cookie);
70 io_close(sock); 70 event_del((int)sock), close((int)sock);
71 return; 71 return;
72 } 72 }
73 73
@@ -77,7 +77,7 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
77 if (!(outbuf = malloc(ws->reply_size - written_size))) { 77 if (!(outbuf = malloc(ws->reply_size - written_size))) {
78 buf_reset(&cookie->request); 78 buf_reset(&cookie->request);
79 free(cookie); 79 free(cookie);
80 io_close(sock); 80 event_del((int)sock), close((int)sock);
81 return; 81 return;
82 } 82 }
83 83
@@ -88,7 +88,7 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
88 free(outbuf); 88 free(outbuf);
89 buf_reset(&cookie->request); 89 buf_reset(&cookie->request);
90 free(cookie); 90 free(cookie);
91 io_close(sock); 91 event_del((int)sock), close((int)sock);
92 return; 92 return;
93 } 93 }
94 } 94 }
@@ -98,9 +98,9 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
98 /* writeable short data sockets just have a tcp timeout */ 98 /* writeable short data sockets just have a tcp timeout */
99 if (!ws->keep_alive) { 99 if (!ws->keep_alive) {
100 fdstate_set_expiry(sock, 0); 100 fdstate_set_expiry(sock, 0);
101 io_dontwantread(sock); 101 http_interest_drop(sock, cookie, EVENT_IN);
102 } 102 }
103 io_wantwrite(sock); 103 http_interest_add(sock, cookie, EVENT_OUT);
104 } 104 }
105} 105}
106 106
@@ -113,6 +113,21 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
113#define HTTPERROR_403_IP return http_issue_error(sock, ws, CODE_HTTPERROR_403_IP) 113#define HTTPERROR_403_IP return http_issue_error(sock, ws, CODE_HTTPERROR_403_IP)
114#define HTTPERROR_404 return http_issue_error(sock, ws, CODE_HTTPERROR_404) 114#define HTTPERROR_404 return http_issue_error(sock, ws, CODE_HTTPERROR_404)
115#define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500) 115#define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500)
116static void http_interest(const int64_t sock, struct http_data *cookie, int what) {
117 if (cookie->interest == what)
118 return;
119 cookie->interest = what;
120 event_modify((int)sock, what, fdstate_generation((int)sock));
121}
122
123void http_interest_add(const int64_t sock, struct http_data *cookie, int what) {
124 http_interest(sock, cookie, cookie->interest | what);
125}
126
127void http_interest_drop(const int64_t sock, struct http_data *cookie, int what) {
128 http_interest(sock, cookie, cookie->interest & ~what);
129}
130
116ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { 131ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) {
117 const char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required", 132 const char *error_code[] = {"302 Found", "400 Invalid Request", "400 Invalid Request", "400 Invalid Request", "402 Payment Required",
118 "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"}; 133 "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"};
@@ -157,7 +172,7 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t
157 172
158 /* If we came here, wait for the answer is over */ 173 /* If we came here, wait for the answer is over */
159 if (cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK) { 174 if (cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK) {
160 io_dontwantread(sock); 175 http_interest_drop(sock, cookie, EVENT_IN);
161 cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK; 176 cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK;
162 } 177 }
163 178
@@ -208,7 +223,7 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t
208 } 223 }
209 224
210 fdstate_set_expiry(sock, g_now_seconds + OT_CLIENT_TIMEOUT_SEND); 225 fdstate_set_expiry(sock, g_now_seconds + OT_CLIENT_TIMEOUT_SEND);
211 io_wantwrite(sock); 226 http_interest_add(sock, cookie, EVENT_OUT);
212 return 0; 227 return 0;
213} 228}
214 229
@@ -219,7 +234,6 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
219 {"scrp", TASK_STATS_SCRAPE}, 234 {"scrp", TASK_STATS_SCRAPE},
220 {"udp4", TASK_STATS_UDP}, 235 {"udp4", TASK_STATS_UDP},
221 {"tcp4", TASK_STATS_TCP}, 236 {"tcp4", TASK_STATS_TCP},
222 {"busy", TASK_STATS_BUSY_NETWORKS},
223 {"torr", TASK_STATS_TORRENTS}, 237 {"torr", TASK_STATS_TORRENTS},
224 {"fscr", TASK_STATS_FULLSCRAPE}, 238 {"fscr", TASK_STATS_FULLSCRAPE},
225 {"s24s", TASK_STATS_SLASH24S}, 239 {"s24s", TASK_STATS_SLASH24S},
@@ -244,6 +258,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
244 {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}}; 258 {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}};
245 259
246 struct http_data *cookie = fdstate_cookie(sock); 260 struct http_data *cookie = fdstate_cookie(sock);
261 char *reply;
247 int mode = TASK_STATS_PEERS, scanon = 1, format = 0; 262 int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
248 263
249 /* A socket without one is a dead socket, which is what http_sendiovecdata 264 /* A socket without one is a dead socket, which is what http_sendiovecdata
@@ -257,21 +272,21 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
257#endif 272#endif
258 273
259 while (scanon) { 274 while (scanon) {
260 switch (scan_find_keywords(keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM)) { 275 switch (urlencoded_match_keyword(keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
261 case -2: 276 case -2:
262 scanon = 0; 277 scanon = 0;
263 break; /* TERMINATOR */ 278 break; /* TERMINATOR */
264 case -1: 279 case -1:
265 HTTPERROR_400_PARAM; /* PARSE ERROR */ 280 HTTPERROR_400_PARAM; /* PARSE ERROR */
266 case -3: 281 case -3:
267 scan_urlencoded_skipvalue(&read_ptr); 282 urlencoded_skip_value(&read_ptr);
268 break; 283 break;
269 case 1: /* matched "mode" */ 284 case 1: /* matched "mode" */
270 if ((mode = scan_find_keywords(keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) 285 if ((mode = urlencoded_match_keyword(keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
271 HTTPERROR_400_PARAM; 286 HTTPERROR_400_PARAM;
272 break; 287 break;
273 case 2: /* matched "format" */ 288 case 2: /* matched "format" */
274 if ((format = scan_find_keywords(keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) 289 if ((format = urlencoded_match_keyword(keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
275 HTTPERROR_400_PARAM; 290 HTTPERROR_400_PARAM;
276 break; 291 break;
277 case 3: 292 case 3:
@@ -303,7 +318,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
303 /* Clients waiting for us should not easily timeout */ 318 /* Clients waiting for us should not easily timeout */
304 fdstate_set_expiry(sock, 0); 319 fdstate_set_expiry(sock, 0);
305 fullscrape_deliver(sock, format); 320 fullscrape_deliver(sock, format);
306 io_dontwantread(sock); 321 http_interest_drop(sock, cookie, EVENT_IN);
307 return ws->reply_size = -2; 322 return ws->reply_size = -2;
308 } 323 }
309#endif 324#endif
@@ -320,7 +335,9 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
320 } 335 }
321 336
322 /* Simple stats can be answerred immediately */ 337 /* Simple stats can be answerred immediately */
323 return ws->reply_size = return_stats_for_tracker(ws->reply, mode, 0); 338 reply = ws->reply;
339 return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0);
340 return ws->reply_size = reply - ws->reply;
324} 341}
325 342
326#ifdef WANT_MODEST_FULLSCRAPES 343#ifdef WANT_MODEST_FULLSCRAPES
@@ -396,17 +413,20 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *
396 /* Clients waiting for us should not easily timeout */ 413 /* Clients waiting for us should not easily timeout */
397 fdstate_set_expiry(sock, 0); 414 fdstate_set_expiry(sock, 0);
398 fullscrape_deliver(sock, format); 415 fullscrape_deliver(sock, format);
399 io_dontwantread(sock); 416 http_interest_drop(sock, cookie, EVENT_IN);
400 return ws->reply_size = -2; 417 return ws->reply_size = -2;
401} 418}
402#endif 419#endif
403 420
421static int compare_hash(const void *left, const void *right) { return memcmp(left, right, sizeof(ot_hash)); }
422
404static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { 423static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
405 static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}}; 424 static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}};
406 425
407 ot_hash *multiscrape_buf = (ot_hash *)ws->request; 426 ot_hash *multiscrape_buf = (ot_hash *)ws->request;
408 int scanon = 1; 427 char *reply = ws->reply;
409 size_t numwant = 0; 428 int scanon = 1;
429 size_t i, unique, numwant = 0;
410 430
411 /* This is to hack around stupid clients that send "scrape ?info_hash" */ 431 /* This is to hack around stupid clients that send "scrape ?info_hash" */
412 if (read_ptr[-1] != '?') { 432 if (read_ptr[-1] != '?') {
@@ -418,18 +438,18 @@ static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws,
418 } 438 }
419 439
420 while (scanon) { 440 while (scanon) {
421 switch (scan_find_keywords(keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM)) { 441 switch (urlencoded_match_keyword(keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
422 case -2: 442 case -2:
423 scanon = 0; 443 scanon = 0;
424 break; /* TERMINATOR */ 444 break; /* TERMINATOR */
425 default: 445 default:
426 HTTPERROR_400_PARAM; /* PARSE ERROR */ 446 HTTPERROR_400_PARAM; /* PARSE ERROR */
427 case -3: 447 case -3:
428 scan_urlencoded_skipvalue(&read_ptr); 448 urlencoded_skip_value(&read_ptr);
429 break; 449 break;
430 case 1: /* matched "info_hash" */ 450 case 1: /* matched "info_hash" */
431 /* ignore this, when we have less than 20 bytes */ 451 /* ignore this, when we have less than 20 bytes */
432 if (scan_urlencoded_query(&read_ptr, (char *)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE) != (ssize_t)sizeof(ot_hash)) 452 if (urlencoded_parse(&read_ptr, (char *)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE) != (ssize_t)sizeof(ot_hash))
433 HTTPERROR_400_PARAM; 453 HTTPERROR_400_PARAM;
434 break; 454 break;
435 } 455 }
@@ -443,8 +463,17 @@ static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws,
443 if (numwant > OT_MAXMULTISCRAPE_COUNT) 463 if (numwant > OT_MAXMULTISCRAPE_COUNT)
444 numwant = OT_MAXMULTISCRAPE_COUNT; 464 numwant = OT_MAXMULTISCRAPE_COUNT;
445 465
466 /* A bencoded dictionary has its keys in order and names each one once, so
467 the answer cannot just follow the order the client asked in */
468 qsort(multiscrape_buf, numwant, sizeof(ot_hash), compare_hash);
469 for (i = unique = 1; i < numwant; ++i)
470 if (memcmp(multiscrape_buf[i], multiscrape_buf[unique - 1], sizeof(ot_hash)))
471 memcpy(multiscrape_buf[unique++], multiscrape_buf[i], sizeof(ot_hash));
472 numwant = unique;
473
446 /* Enough for http header + whole scrape string */ 474 /* Enough for http header + whole scrape string */
447 ws->reply_size = return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, ws->reply); 475 return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE);
476 ws->reply_size = reply - ws->reply;
448 stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size); 477 stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size);
449 return ws->reply_size; 478 return ws->reply_size;
450} 479}
@@ -480,9 +509,20 @@ static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event",
480#endif 509#endif
481 {"peer_id", 9}, {NULL, -3}}; 510 {"peer_id", 9}, {NULL, -3}};
482static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; 511static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}};
512
513/* For the replies that are one fixed string and nothing else, where the only
514 thing the caller wants back is how long it turned out */
515static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) {
516 char *reply = ws->reply;
517
518 fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text));
519 return reply - ws->reply;
520}
521
483static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { 522static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
484 size_t numwant; 523 size_t numwant;
485 int tmp, scanon; 524 int64_t tmp;
525 int scanon;
486 unsigned short port = 0; 526 unsigned short port = 0;
487 char *write_ptr; 527 char *write_ptr;
488 ssize_t len; 528 ssize_t len;
@@ -501,7 +541,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
501 if (accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_PROXY)) { 541 if (accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_PROXY)) {
502 ot_ip6 proxied_ip; 542 ot_ip6 proxied_ip;
503 char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for"); 543 char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for");
504 if (fwd && ip_scan(fwd, proxied_ip)) { 544 if (fwd && ip_parse(fwd, proxied_ip)) {
505 OT_SETIP(ws->peer, proxied_ip); 545 OT_SETIP(ws->peer, proxied_ip);
506 } else 546 } else
507 OT_SETIP(ws->peer, cookie->ip); 547 OT_SETIP(ws->peer, cookie->ip);
@@ -518,32 +558,32 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
518 scanon = 1; 558 scanon = 1;
519 559
520 while (scanon) { 560 while (scanon) {
521 switch (scan_find_keywords(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM)) { 561 switch (urlencoded_match_keyword(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
522 case -2: 562 case -2:
523 scanon = 0; 563 scanon = 0;
524 break; /* TERMINATOR */ 564 break; /* TERMINATOR */
525 case -1: 565 case -1:
526 HTTPERROR_400_PARAM; /* PARSE ERROR */ 566 HTTPERROR_400_PARAM; /* PARSE ERROR */
527 case -3: 567 case -3:
528 scan_urlencoded_skipvalue(&read_ptr); 568 urlencoded_skip_value(&read_ptr);
529 break; 569 break;
530 case 1: /* matched "port" */ 570 case 1: /* matched "port" */
531 len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); 571 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
532 if ((len <= 0) || scan_fixed_int(write_ptr, len, &tmp) || (tmp > 0xffff)) 572 if ((len <= 0) || scan_int(write_ptr, len, &tmp) || (tmp > 0xffff))
533 HTTPERROR_400_PARAM; 573 HTTPERROR_400_PARAM;
534 port = htons(tmp); 574 port = htons((uint16_t)tmp);
535 OT_SETPORT(&ws->peer, &port); 575 OT_SETPORT(&ws->peer, &port);
536 break; 576 break;
537 case 2: /* matched "left" */ 577 case 2: /* matched "left" */
538 if ((len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) 578 if ((len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
539 HTTPERROR_400_PARAM; 579 HTTPERROR_400_PARAM;
540 if (scan_fixed_int(write_ptr, len, &tmp)) 580 if (scan_int(write_ptr, len, &tmp))
541 tmp = 0; 581 tmp = 0;
542 if (!tmp) 582 if (!tmp)
543 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING; 583 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING;
544 break; 584 break;
545 case 3: /* matched "event" */ 585 case 3: /* matched "event" */
546 switch (scan_find_keywords(keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE)) { 586 switch (urlencoded_match_keyword(keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE)) {
547 case -1: 587 case -1:
548 HTTPERROR_400_PARAM; 588 HTTPERROR_400_PARAM;
549 case 1: /* matched "completed" */ 589 case 1: /* matched "completed" */
@@ -557,18 +597,18 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
557 } 597 }
558 break; 598 break;
559 case 4: /* matched "numwant" */ 599 case 4: /* matched "numwant" */
560 len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); 600 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
561 if ((len <= 0) || scan_fixed_int(write_ptr, len, &tmp)) 601 if ((len <= 0) || scan_int(write_ptr, len, &tmp))
562 HTTPERROR_400_PARAM; 602 HTTPERROR_400_PARAM;
563 if (tmp < 0) 603 if (tmp < 0)
564 tmp = 50; 604 tmp = 50;
565 if (tmp > 200) 605 if (tmp > 200)
566 tmp = 200; 606 tmp = 200;
567 numwant = tmp; 607 numwant = (size_t)tmp;
568 break; 608 break;
569 case 5: /* matched "compact" */ 609 case 5: /* matched "compact" */
570 len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); 610 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
571 if ((len <= 0) || scan_fixed_int(write_ptr, len, &tmp)) 611 if ((len <= 0) || scan_int(write_ptr, len, &tmp))
572 HTTPERROR_400_PARAM; 612 HTTPERROR_400_PARAM;
573 if (!tmp) 613 if (!tmp)
574 HTTPERROR_400_COMPACT; 614 HTTPERROR_400_COMPACT;
@@ -577,7 +617,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
577 if (ws->hash) 617 if (ws->hash)
578 HTTPERROR_400_DOUBLEHASH; 618 HTTPERROR_400_DOUBLEHASH;
579 /* ignore this, when we have less than 20 bytes */ 619 /* ignore this, when we have less than 20 bytes */
580 if (scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20) 620 if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20)
581 HTTPERROR_400_PARAM; 621 HTTPERROR_400_PARAM;
582 ws->hash = (ot_hash *)write_ptr; 622 ws->hash = (ot_hash *)write_ptr;
583 break; 623 break;
@@ -585,9 +625,9 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
585 case 7: /* matched "ip" */ 625 case 7: /* matched "ip" */
586 { 626 {
587 char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16; 627 char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16;
588 len = scan_urlencoded_query(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE); 628 len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE);
589 tmp_buf2[len] = 0; 629 tmp_buf2[len] = 0;
590 if ((len <= 0) || !ip_scan(tmp_buf2, tmp_buf1)) 630 if ((len <= 0) || ip_parse(tmp_buf2, tmp_buf1) != (size_t)len)
591 HTTPERROR_400_PARAM; 631 HTTPERROR_400_PARAM;
592 OT_SETIP(&ws->peer, tmp_buf1); 632 OT_SETIP(&ws->peer, tmp_buf1);
593 } break; 633 } break;
@@ -601,21 +641,21 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
601 uint64_t value; 641 uint64_t value;
602 signed short parsed, bits; 642 signed short parsed, bits;
603 643
604 len = scan_urlencoded_query(&read_ptr, tmp_buf, SCAN_SEARCHPATH_VALUE); 644 len = urlencoded_parse(&read_ptr, tmp_buf, SCAN_SEARCHPATH_VALUE);
605 tmp_buf[len] = 0; 645 tmp_buf[len] = 0;
606 if (len <= 0) 646 if (len <= 0)
607 HTTPERROR_400_PARAM; 647 HTTPERROR_400_PARAM;
608 if (*tmp_buf == '-') { 648 if (*tmp_buf == '-') {
609 loglist_reset(); 649 loglist_reset();
610 return ws->reply_size = sprintf(ws->reply, "Successfully removed.\n"); 650 return ws->reply_size = reply_literal(ws, "Successfully removed.\n");
611 } 651 }
612 parsed = ip_scan(tmp_buf, net.address); 652 parsed = ip_parse(tmp_buf, net.address);
613 if (!parsed) 653 if (!parsed)
614 HTTPERROR_400_PARAM; 654 HTTPERROR_400_PARAM;
615 if (tmp_buf[parsed++] != '/') 655 if (tmp_buf[parsed++] != '/')
616 bits = 128; 656 bits = 128;
617 else { 657 else {
618 parsed = scan_udec(tmp_buf + parsed, &value, 128); 658 parsed = scan_uint(tmp_buf + parsed, &value, 128);
619 if (!parsed) 659 if (!parsed)
620 HTTPERROR_400_PARAM; 660 HTTPERROR_400_PARAM;
621 bits = (signed short)value; 661 bits = (signed short)value;
@@ -624,13 +664,13 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
624 } 664 }
625 net.bits = bits; 665 net.bits = bits;
626 loglist_add_network(&net); 666 loglist_add_network(&net);
627 return ws->reply_size = sprintf(ws->reply, "Successfully added.\n"); 667 return ws->reply_size = reply_literal(ws, "Successfully added.\n");
628 //} 668 //}
629 } break; 669 } break;
630#endif 670#endif
631 case 9: /* matched "peer_id" */ 671 case 9: /* matched "peer_id" */
632 /* ignore this, when we have less than 20 bytes */ 672 /* ignore this, when we have less than 20 bytes */
633 if (scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20) 673 if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20)
634 HTTPERROR_400_PARAM; 674 HTTPERROR_400_PARAM;
635 ws->peer_id = write_ptr; 675 ws->peer_id = write_ptr;
636 break; 676 break;
@@ -647,7 +687,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
647 687
648 /* Scanned whole query string */ 688 /* Scanned whole query string */
649 if (!ws->hash) 689 if (!ws->hash)
650 return ws->reply_size = sprintf(ws->reply, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e"); 690 return ws->reply_size = reply_literal(ws, "d14:failure reason80:Your client forgot to send your torrent's info_hash. Please upgrade your client.e");
651 691
652 if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED) 692 if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED)
653 ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws); 693 ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws);
@@ -697,6 +737,24 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
697 /* Tell subroutines where to put reply data */ 737 /* Tell subroutines where to put reply data */
698 ws->reply = ws->outbuf + SUCCESS_HTTP_HEADER_LENGTH; 738 ws->reply = ws->outbuf + SUCCESS_HTTP_HEADER_LENGTH;
699 739
740 /* Find out if the client wants to keep this connection alive, before any
741 subroutine gets to answer: an error reply finishes with the connection
742 on its way out, and ws outlives the connection it was serving */
743 ws->keep_alive = 0;
744#ifdef WANT_KEEPALIVE
745 if ((write_ptr = http_header(ws->request, ws->header_size, "connection")) && (*write_ptr == 'K' || *write_ptr == 'k'))
746 ws->keep_alive = 1;
747#endif
748 {
749 struct http_data *connection = fdstate_cookie(sock);
750 if (connection) {
751 if (ws->keep_alive)
752 connection->flag |= STRUCT_HTTP_FLAG_KEEPALIVE;
753 else
754 connection->flag &= ~STRUCT_HTTP_FLAG_KEEPALIVE;
755 }
756 }
757
700 /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */ 758 /* This one implicitely tests strlen < 5, too -- remember, it is \n terminated */
701 if (memcmp(read_ptr, "GET /", 5)) 759 if (memcmp(read_ptr, "GET /", 5))
702 HTTPERROR_400; 760 HTTPERROR_400;
@@ -709,7 +767,7 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
709 In reality we abandoned requiring the url to be correct. This now 767 In reality we abandoned requiring the url to be correct. This now
710 only decodes url encoded characters, we check for announces and 768 only decodes url encoded characters, we check for announces and
711 scrapes by looking for "a*" or "sc" */ 769 scrapes by looking for "a*" or "sc" */
712 len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_PATH); 770 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_PATH);
713 771
714 /* If parsing returned an error, leave with not found */ 772 /* If parsing returned an error, leave with not found */
715 if (g_redirecturl && (len == -2)) 773 if (g_redirecturl && (len == -2))
@@ -733,14 +791,6 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
733 else 791 else
734 HTTPERROR_404; 792 HTTPERROR_404;
735 793
736 /* Find out if the client wants to keep this connection alive */
737 ws->keep_alive = 0;
738#ifdef WANT_KEEPALIVE
739 read_ptr = http_header(ws->request, ws->header_size, "connection");
740 if (read_ptr && (*read_ptr == 'K' || *read_ptr == 'k'))
741 ws->keep_alive = 1;
742#endif
743
744 /* If routines handled sending themselves, just return */ 794 /* If routines handled sending themselves, just return */
745 if (ws->reply_size == -2) 795 if (ws->reply_size == -2)
746 return 0; 796 return 0;
@@ -758,9 +808,11 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
758 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size); 808 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size);
759 ws->reply = ws->outbuf + reply_off; 809 ws->reply = ws->outbuf + reply_off;
760 810
761 /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete 811 /* 2. Now we write the header so that its terminating '\0' lands exactly one byte before the content starts. The bound is the
762 packet size is increased by size of header plus one byte '\n', we will copy over '\0' in next step */ 812 space step 1 reserved, so the '\0' falls where step 3 expects it. Complete packet size is increased by size of header plus
763 ws->reply_size += 1 + sprintf(ws->reply, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size); 813 one byte '\n', we will copy over '\0' in next step */
814 ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off),
815 "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size);
764 816
765 /* 3. Finally we join both blocks neatly */ 817 /* 3. Finally we join both blocks neatly */
766 ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n'; 818 ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n';
diff --git a/ot_http.h b/ot_http.h
index 003b545..1ccabfd 100644
--- a/ot_http.h
+++ b/ot_http.h
@@ -11,6 +11,7 @@
11#include <sys/uio.h> 11#include <sys/uio.h>
12 12
13#include "ot_batch.h" 13#include "ot_batch.h"
14#include "ot_event.h"
14#include "ot_buf.h" 15#include "ot_buf.h"
15#include "trackerlogic.h" 16#include "trackerlogic.h"
16 17
@@ -20,16 +21,22 @@ typedef enum {
20 STRUCT_HTTP_FLAG_BZIP2 = 4, 21 STRUCT_HTTP_FLAG_BZIP2 = 4,
21 STRUCT_HTTP_FLAG_ZSTD = 8, 22 STRUCT_HTTP_FLAG_ZSTD = 8,
22 STRUCT_HTTP_FLAG_CHUNKED = 16, 23 STRUCT_HTTP_FLAG_CHUNKED = 16,
23 STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32 24 STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER = 32,
25 STRUCT_HTTP_FLAG_KEEPALIVE = 64
24} STRUCT_HTTP_FLAG; 26} STRUCT_HTTP_FLAG;
25 27
26struct http_data { 28struct http_data {
27 ot_buf request; 29 ot_buf request;
30 int interest; /* EVENT_IN and EVENT_OUT as asked for */
28 ot_batch *batch; 31 ot_batch *batch;
29 ot_ip6 ip; 32 ot_ip6 ip;
30 STRUCT_HTTP_FLAG flag; 33 STRUCT_HTTP_FLAG flag;
31}; 34};
32 35
36/* Add to or drop from what this connection is waiting to be told about */
37void http_interest_add(const int64_t s, struct http_data *cookie, int what);
38void http_interest_drop(const int64_t s, struct http_data *cookie, int what);
39
33ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); 40ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws);
34ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial); 41ssize_t http_sendiovecdata(const int64_t s, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial);
35ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code); 42ssize_t http_issue_error(const int64_t s, struct ot_workstruct *ws, int code);
diff --git a/ot_iovec.c b/ot_iovec.c
index cd5b9f9..7e5903f 100644
--- a/ot_iovec.c
+++ b/ot_iovec.c
@@ -7,12 +7,10 @@
7#include <stdlib.h> 7#include <stdlib.h>
8#include <sys/uio.h> 8#include <sys/uio.h>
9 9
10/* Libowfat */
11
12/* Opentracker */ 10/* Opentracker */
13#include "ot_iovec.h" 11#include "ot_iovec.h"
14 12
15void *iovec_increase(size_t *iovec_entries, struct iovec **iovector, size_t new_alloc) { 13void *iovec_new_chunk(size_t *iovec_entries, struct iovec **iovector, size_t new_alloc) {
16 void *new_data; 14 void *new_data;
17 size_t new_entries = 1 + *iovec_entries; 15 size_t new_entries = 1 + *iovec_entries;
18 struct iovec *new_vec = realloc(*iovector, new_entries * sizeof(struct iovec)); 16 struct iovec *new_vec = realloc(*iovector, new_entries * sizeof(struct iovec));
@@ -62,7 +60,7 @@ void iovec_free(size_t *iovec_entries, struct iovec **iovector) {
62 *iovec_entries = 0; 60 *iovec_entries = 0;
63} 61}
64 62
65void iovec_fixlast(size_t *iovec_entries, struct iovec **iovector, void *last_ptr) { 63void iovec_trim_last(size_t *iovec_entries, struct iovec **iovector, void *last_ptr) {
66 if (*iovec_entries) { 64 if (*iovec_entries) {
67 char *base = (char *)((*iovector)[*iovec_entries - 1]).iov_base; 65 char *base = (char *)((*iovector)[*iovec_entries - 1]).iov_base;
68 size_t new_alloc = ((char *)last_ptr) - base; 66 size_t new_alloc = ((char *)last_ptr) - base;
@@ -72,12 +70,12 @@ void iovec_fixlast(size_t *iovec_entries, struct iovec **iovector, void *last_pt
72 } 70 }
73} 71}
74 72
75void *iovec_fix_increase_or_free(size_t *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc) { 73void *iovec_next_chunk(size_t *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc) {
76 void *new_data; 74 void *new_data;
77 75
78 iovec_fixlast(iovec_entries, iovector, last_ptr); 76 iovec_trim_last(iovec_entries, iovector, last_ptr);
79 77
80 if (!(new_data = iovec_increase(iovec_entries, iovector, new_alloc))) 78 if (!(new_data = iovec_new_chunk(iovec_entries, iovector, new_alloc)))
81 iovec_free(iovec_entries, iovector); 79 iovec_free(iovec_entries, iovector);
82 80
83 return new_data; 81 return new_data;
diff --git a/ot_iovec.h b/ot_iovec.h
index eb4365b..6f5a1d6 100644
--- a/ot_iovec.h
+++ b/ot_iovec.h
@@ -8,13 +8,13 @@
8 8
9#include <sys/uio.h> 9#include <sys/uio.h>
10 10
11void *iovec_increase(size_t *iovec_entries, struct iovec **iovector, size_t new_alloc); 11void *iovec_new_chunk(size_t *iovec_entries, struct iovec **iovector, size_t new_alloc);
12void *iovec_append(size_t *iovec_entries, struct iovec **iovector, struct iovec *append_iovector); 12void *iovec_append(size_t *iovec_entries, struct iovec **iovector, struct iovec *append_iovector);
13void iovec_fixlast(size_t *iovec_entries, struct iovec **iovector, void *last_ptr); 13void iovec_trim_last(size_t *iovec_entries, struct iovec **iovector, void *last_ptr);
14void iovec_free(size_t *iovec_entries, struct iovec **iovector); 14void iovec_free(size_t *iovec_entries, struct iovec **iovector);
15 15
16size_t iovec_length(const size_t *iovec_entries, const struct iovec **iovector); 16size_t iovec_length(const size_t *iovec_entries, const struct iovec **iovector);
17 17
18void *iovec_fix_increase_or_free(size_t *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc); 18void *iovec_next_chunk(size_t *iovec_entries, struct iovec **iovector, void *last_ptr, size_t new_alloc);
19 19
20#endif 20#endif
diff --git a/ot_ip.c b/ot_ip.c
index 8cde7d2..e694883 100644
--- a/ot_ip.c
+++ b/ot_ip.c
@@ -120,7 +120,7 @@ size_t ip_format(char *dest, const ot_ip6 ip) {
120 return (size_t)(d - dest); 120 return (size_t)(d - dest);
121} 121}
122 122
123size_t ip_scan(const char *src, ot_ip6 ip) { 123size_t ip_parse(const char *src, ot_ip6 ip) {
124 uint8_t prefix[16], suffix[16]; 124 uint8_t prefix[16], suffix[16];
125 size_t prefixlen = 0, suffixlen = 0; 125 size_t prefixlen = 0, suffixlen = 0;
126 const char *s = src; 126 const char *s = src;
diff --git a/ot_ip.h b/ot_ip.h
index ac8964d..344bd45 100644
--- a/ot_ip.h
+++ b/ot_ip.h
@@ -23,7 +23,7 @@ static inline int ip_is_v4mapped(const void *ip) { return !memcmp(ip, ip_v4mappe
23 23
24 The count lets the caller carry on where the address ended, which is how 24 The count lets the caller carry on where the address ended, which is how
25 "address:port" and "address/bits" are read. */ 25 "address:port" and "address/bits" are read. */
26size_t ip_scan(const char *src, ot_ip6 ip); 26size_t ip_parse(const char *src, ot_ip6 ip);
27 27
28/* Write an address and return how many characters it took. Nothing is 28/* Write an address and return how many characters it took. Nothing is
29 terminated, so a caller may keep appending. A v4 mapped address is written 29 terminated, so a caller may keep appending. A v4 mapped address is written
diff --git a/ot_livesync.h b/ot_livesync.h
index 5b9fa0e..4725023 100644
--- a/ot_livesync.h
+++ b/ot_livesync.h
@@ -6,7 +6,6 @@
6#ifndef OT_LIVESYNC_H__ 6#ifndef OT_LIVESYNC_H__
7#define OT_LIVESYNC_H__ 7#define OT_LIVESYNC_H__
8 8
9#include "io.h"
10#include "trackerlogic.h" 9#include "trackerlogic.h"
11 10
12/* 11/*
diff --git a/ot_mutex.c b/ot_mutex.c
index 2a8cbf5..37d9962 100644
--- a/ot_mutex.c
+++ b/ot_mutex.c
@@ -8,11 +8,10 @@
8#include <pthread.h> 8#include <pthread.h>
9#include <stdio.h> 9#include <stdio.h>
10#include <stdlib.h> 10#include <stdlib.h>
11#include <unistd.h>
11#include <string.h> 12#include <string.h>
12#include <sys/uio.h> 13#include <sys/uio.h>
13 14
14/* Libowfat */
15#include "io.h"
16 15
17/* Opentracker */ 16/* Opentracker */
18#include "ot_iovec.h" 17#include "ot_iovec.h"
@@ -193,7 +192,8 @@ int mutex_workqueue_pushresult(ot_taskid taskid, size_t iovec_entries, struct io
193 /* Release lock */ 192 /* Release lock */
194 pthread_mutex_unlock(&tasklist_mutex); 193 pthread_mutex_unlock(&tasklist_mutex);
195 194
196 io_trywrite(g_self_pipe[1], &byte, 1); 195 /* A full pipe has already done this nudge's job, so a failure is fine */
196 (void)!write(g_self_pipe[1], &byte, 1);
197 197
198 /* Indicate whether the worker has to throw away results */ 198 /* Indicate whether the worker has to throw away results */
199 return task ? 0 : -1; 199 return task ? 0 : -1;
@@ -221,7 +221,8 @@ int mutex_workqueue_pushchunked(ot_taskid taskid, struct iovec *iovec) {
221 /* Release lock */ 221 /* Release lock */
222 pthread_mutex_unlock(&tasklist_mutex); 222 pthread_mutex_unlock(&tasklist_mutex);
223 223
224 io_trywrite(g_self_pipe[1], &byte, 1); 224 /* A full pipe has already done this nudge's job, so a failure is fine */
225 (void)!write(g_self_pipe[1], &byte, 1);
225 226
226 /* Indicate whether the worker has to throw away results */ 227 /* Indicate whether the worker has to throw away results */
227 return task ? 0 : -1; 228 return task ? 0 : -1;
diff --git a/ot_mutex.h b/ot_mutex.h
index b5af96e..3bbbe9c 100644
--- a/ot_mutex.h
+++ b/ot_mutex.h
@@ -29,7 +29,6 @@ typedef enum {
29 TASK_STATS_TPB = 0x0006, 29 TASK_STATS_TPB = 0x0006,
30 TASK_STATS_HTTPERRORS = 0x0007, 30 TASK_STATS_HTTPERRORS = 0x0007,
31 TASK_STATS_VERSION = 0x0008, 31 TASK_STATS_VERSION = 0x0008,
32 TASK_STATS_BUSY_NETWORKS = 0x0009,
33 TASK_STATS_RENEW = 0x000a, 32 TASK_STATS_RENEW = 0x000a,
34 TASK_STATS_SYNCS = 0x000b, 33 TASK_STATS_SYNCS = 0x000b,
35 TASK_STATS_COMPLETED = 0x000c, 34 TASK_STATS_COMPLETED = 0x000c,
diff --git a/ot_scan.c b/ot_scan.c
index a96f82b..c4b951a 100644
--- a/ot_scan.c
+++ b/ot_scan.c
@@ -11,7 +11,7 @@
11/* Opentracker */ 11/* Opentracker */
12#include "ot_scan.h" 12#include "ot_scan.h"
13 13
14size_t scan_udec(const char *src, uint64_t *dest, uint64_t max) { 14size_t scan_uint(const char *src, uint64_t *dest, uint64_t max) {
15 unsigned long long value; 15 unsigned long long value;
16 char *end; 16 char *end;
17 17
@@ -28,9 +28,9 @@ size_t scan_udec(const char *src, uint64_t *dest, uint64_t max) {
28 return (size_t)(end - src); 28 return (size_t)(end - src);
29} 29}
30 30
31size_t scan_udec_bencode(const char *src, uint64_t *dest, uint64_t max) { 31size_t scan_uint_bencode(const char *src, uint64_t *dest, uint64_t max) {
32 uint64_t value; 32 uint64_t value;
33 size_t consumed = scan_udec(src, &value, max); 33 size_t consumed = scan_uint(src, &value, max);
34 34
35 /* bencode writes no leading zeros, so anything longer than a single digit 35 /* bencode writes no leading zeros, so anything longer than a single digit
36 must not begin with one. */ 36 must not begin with one. */
@@ -42,3 +42,26 @@ size_t scan_udec_bencode(const char *src, uint64_t *dest, uint64_t max) {
42 42
43 return consumed; 43 return consumed;
44} 44}
45
46int scan_int(const char *src, size_t len, int64_t *dest) {
47 int64_t value = 0;
48 int negate = 0;
49
50 if (len && *src == '-')
51 --len, ++src, negate = 1;
52 if (!len)
53 return -1;
54
55 while (len--) {
56 if (*src < '0' || *src > '9')
57 return -1;
58 /* A twenty character field of nines does not fit, and silently wrapping
59 would magically heal a nonsense announce */
60 if (value > (INT64_MAX - (*src - '0')) / 10)
61 return -1;
62 value = 10 * value + (*src++ - '0');
63 }
64
65 *dest = negate ? -value : value;
66 return 0;
67}
diff --git a/ot_scan.h b/ot_scan.h
index 860a088..ca3bbbd 100644
--- a/ot_scan.h
+++ b/ot_scan.h
@@ -7,15 +7,21 @@
7#define OT_SCAN_H__ 7#define OT_SCAN_H__
8 8
9#include <stddef.h> 9#include <stddef.h>
10#include <stddef.h>
10#include <stdint.h> 11#include <stdint.h>
11 12
12/* Read an unsigned decimal number from src into dest and return how many 13/* Read an unsigned decimal number from src into dest and return how many
13 characters were consumed. Returns zero and leaves dest untouched when src 14 characters were consumed. Returns zero and leaves dest untouched when src
14 does not begin with a digit, or when the value would exceed max. */ 15 does not begin with a digit, or when the value would exceed max. */
15size_t scan_udec(const char *src, uint64_t *dest, uint64_t max); 16size_t scan_uint(const char *src, uint64_t *dest, uint64_t max);
16 17
17/* As scan_udec, but following the bencode integer grammar, which permits a 18/* As scan_uint, but following the bencode integer grammar, which permits a
18 leading zero on zero alone. */ 19 leading zero on zero alone. */
19size_t scan_udec_bencode(const char *src, uint64_t *dest, uint64_t max); 20size_t scan_uint_bencode(const char *src, uint64_t *dest, uint64_t max);
21
22/* Read a signed decimal number that has to take up exactly len characters,
23 which is how a value lifted out of a query string arrives. Returns 0, or
24 -1 leaving dest untouched. */
25int scan_int(const char *src, size_t len, int64_t *dest);
20 26
21#endif 27#endif
diff --git a/ot_socket.c b/ot_socket.c
index 84020ca..576087f 100644
--- a/ot_socket.c
+++ b/ot_socket.c
@@ -40,14 +40,16 @@ static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_st
40 } 40 }
41} 41}
42 42
43static int set_nonblocking(int s) { 43int socket_set_blocking(int s, int blocking) {
44 int flags = fcntl(s, F_GETFL, 0); 44 int flags = fcntl(s, F_GETFL, 0);
45 45
46 if (flags == -1) 46 if (flags == -1)
47 return -1; 47 return -1;
48 return fcntl(s, F_SETFL, flags | O_NONBLOCK); 48 return fcntl(s, F_SETFL, blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK));
49} 49}
50 50
51static int set_nonblocking(int s) { return socket_set_blocking(s, 0); }
52
51/* Close without letting the failure we are about to report be overwritten */ 53/* Close without letting the failure we are about to report be overwritten */
52static int close_and_fail(int s) { 54static int close_and_fail(int s) {
53 int saved = errno; 55 int saved = errno;
diff --git a/ot_socket.h b/ot_socket.h
index 21d04b1..aa4e2d2 100644
--- a/ot_socket.h
+++ b/ot_socket.h
@@ -27,6 +27,7 @@ int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing);
27int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport); 27int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport);
28int socket_is_connected(int s); 28int socket_is_connected(int s);
29int socket_accept(int s, ot_ip6 ip, uint16_t *port); 29int socket_accept(int s, ot_ip6 ip, uint16_t *port);
30int socket_set_blocking(int s, int blocking);
30 31
31ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port); 32ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port);
32ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port); 33ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port);
diff --git a/ot_stats.c b/ot_stats.c
index 6589aef..45dc8db 100644
--- a/ot_stats.c
+++ b/ot_stats.c
@@ -16,15 +16,13 @@
16#include <syslog.h> 16#include <syslog.h>
17#endif 17#endif
18 18
19/* Libowfat */
20#include "io.h"
21
22/* Opentracker */ 19/* Opentracker */
23#include "ot_accesslist.h" 20#include "ot_accesslist.h"
24#include "ot_hex.h" 21#include "ot_hex.h"
25#include "ot_iovec.h" 22#include "ot_iovec.h"
26#include "ot_ip.h" 23#include "ot_ip.h"
27#include "ot_mutex.h" 24#include "ot_mutex.h"
25#include "ot_fmt.h"
28#include "ot_stats.h" 26#include "ot_stats.h"
29#include "trackerlogic.h" 27#include "trackerlogic.h"
30 28
@@ -178,8 +176,8 @@ static size_t stats_get_highscore_networks(stats_network_node *node, int depth,
178 ++j; 176 ++j;
179 --j; 177 --j;
180 178
181 memcpy(scores, scores + 1, j * sizeof(*scores)); 179 memmove(scores, scores + 1, j * sizeof(*scores));
182 memcpy(networks, networks + 1, j * sizeof(*networks)); 180 memmove(networks, networks + 1, j * sizeof(*networks));
183 scores[j] = node_score; 181 scores[j] = node_score;
184 memcpy(networks + j, node_value, sizeof(*networks)); 182 memcpy(networks + j, node_value, sizeof(*networks));
185 } 183 }
@@ -187,12 +185,11 @@ static size_t stats_get_highscore_networks(stats_network_node *node, int depth,
187 return score; 185 return score;
188} 186}
189 187
190static size_t stats_return_busy_networks(char *reply, stats_network_node *tree, size_t amount, int limit) { 188static void stats_return_busy_networks(char **reply, const char *reply_end, stats_network_node *tree, size_t amount, int limit) {
191 ot_ip6 networks[amount]; 189 ot_ip6 networks[amount];
192 ot_ip6 node_value; 190 ot_ip6 node_value;
193 size_t scores[amount]; 191 size_t scores[amount];
194 size_t i; 192 size_t i;
195 char *r = reply;
196 193
197 memset(scores, 0, sizeof(scores)); 194 memset(scores, 0, sizeof(scores));
198 memset(networks, 0, sizeof(networks)); 195 memset(networks, 0, sizeof(networks));
@@ -200,24 +197,21 @@ static size_t stats_return_busy_networks(char *reply, stats_network_node *tree,
200 197
201 stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit); 198 stats_get_highscore_networks(tree, 0, node_value, scores, networks, amount, limit);
202 199
203 r += sprintf(r, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH); 200 fmt_append(reply, reply_end, "Networks, limit /%d:\n", limit + STATS_NETWORK_NODE_BITWIDTH);
204 for (i = amount; i > 0; --i) { 201 for (i = amount; i > 0; --i) {
205 if (scores[i - 1]) { 202 if (scores[i - 1]) {
206 r += sprintf(r, "%08zd: ", scores[i - 1]); 203 char ip_readable[64];
207 // #ifdef WANT_V6 204
208 r += ip_format(r, networks[i - 1]); 205 ip_readable[ip_format(ip_readable, networks[i - 1])] = 0;
209 *r++ = '\n'; 206 fmt_append(reply, reply_end, "%08zd: %s\n", scores[i - 1], ip_readable);
210 } 207 }
211 } 208 }
212 *r++ = '\n'; 209 fmt_append(reply, reply_end, "\n");
213
214 return r - reply;
215} 210}
216 211
217static size_t stats_slash24s_txt(char *reply, size_t amount) { 212static void stats_slash24s_txt(char **reply, const char *reply_end, size_t amount) {
218 stats_network_node *slash24s_network_counters_root = NULL; 213 stats_network_node *slash24s_network_counters_root = NULL;
219 char *r = reply; 214 size_t i, bucket, peer_size = OT_PEER_SIZE4;
220 size_t i, bucket, peer_size = OT_PEER_SIZE4;
221 215
222 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { 216 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
223 ot_vector *torrents_list = mutex_bucket_lock(bucket); 217 ot_vector *torrents_list = mutex_bucket_lock(bucket);
@@ -244,35 +238,28 @@ static size_t stats_slash24s_txt(char *reply, size_t amount) {
244 } 238 }
245 mutex_bucket_unlock(bucket, 0); 239 mutex_bucket_unlock(bucket, 0);
246 if (!g_opentracker_running) 240 if (!g_opentracker_running)
247 goto bailout_error; 241 goto done;
248 } 242 }
249 243
250 /* The tree is built. Now analyze */ 244 /* The tree is built. Now analyze */
251 r += stats_return_busy_networks(r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH); 245 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_MAXDEPTH);
252 r += stats_return_busy_networks(r, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT); 246 stats_return_busy_networks(reply, reply_end, slash24s_network_counters_root, amount, STATS_NETWORK_NODE_LIMIT);
253 goto success; 247 goto done;
254 248
255bailout_unlock: 249bailout_unlock:
256 mutex_bucket_unlock(bucket, 0); 250 mutex_bucket_unlock(bucket, 0);
257bailout_error: 251done:
258 r = reply;
259success:
260 stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1); 252 stats_shift_down_network_count(&slash24s_network_counters_root, 0, sizeof(int) * 8 - 1);
261
262 return r - reply;
263} 253}
264 254
265#ifdef WANT_SPOT_WOODPECKER 255#ifdef WANT_SPOT_WOODPECKER
266static stats_network_node *stats_woodpeckers_tree; 256static stats_network_node *stats_woodpeckers_tree;
267static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER; 257static pthread_mutex_t g_woodpeckers_mutex = PTHREAD_MUTEX_INITIALIZER;
268 258
269static size_t stats_return_woodpeckers(char *reply, int amount) { 259static void stats_return_woodpeckers(char **reply, const char *reply_end, int amount) {
270 char *r = reply;
271
272 pthread_mutex_lock(&g_woodpeckers_mutex); 260 pthread_mutex_lock(&g_woodpeckers_mutex);
273 r += stats_return_busy_networks(r, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH); 261 stats_return_busy_networks(reply, reply_end, stats_woodpeckers_tree, amount, STATS_NETWORK_NODE_MAXDEPTH);
274 pthread_mutex_unlock(&g_woodpeckers_mutex); 262 pthread_mutex_unlock(&g_woodpeckers_mutex);
275 return r - reply;
276} 263}
277#endif 264#endif
278 265
@@ -312,10 +299,10 @@ static inline void stats_insert_into_highscore(size_t val, ot_record* list, size
312 299
313 300
314/* Fetches stats from tracker */ 301/* Fetches stats from tracker */
315size_t stats_top_txt(char *reply, size_t amount) { 302void stats_top_txt(char **reply, const char *reply_end, size_t amount) {
316 size_t j, idx, bucket; 303 size_t j, idx, bucket;
317 ot_record top100s[100], top100c[100], top100l[100]; 304 ot_record top100s[100], top100c[100], top100l[100];
318 char *r = reply, hex_out[42]; 305 char hex_out[42];
319 306
320 if (amount > 100) 307 if (amount > 100)
321 amount = 100; 308 amount = 100;
@@ -326,7 +313,7 @@ size_t stats_top_txt(char *reply, size_t amount) {
326 313
327 /* Iterate over complete torrent list */ 314 /* Iterate over complete torrent list */
328 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { 315 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
329 ot_vector *torrents_list = mutex_bucket_lock(bucket); 316 ot_vector *torrents_list = mutex_bucket_lock(bucket);
330 for (j = 0; j < torrents_list->size; ++j) { 317 for (j = 0; j < torrents_list->size; ++j) {
331 ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j; 318 ot_torrent *torrent = (ot_torrent *)(torrents_list->data) + j;
332 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 319 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
@@ -339,114 +326,110 @@ size_t stats_top_txt(char *reply, size_t amount) {
339 } 326 }
340 mutex_bucket_unlock(bucket, 0); 327 mutex_bucket_unlock(bucket, 0);
341 if (!g_opentracker_running) 328 if (!g_opentracker_running)
342 return 0; 329 return;
343 } 330 }
344 331
345 r += sprintf(r, "Top %zd torrents by peers:\n", amount); 332 fmt_append(reply, reply_end, "Top %zd torrents by peers:\n", amount);
346 for (idx = 0; idx < amount; ++idx) 333 for (idx = 0; idx < amount; ++idx)
347 if (top100c[idx].val) 334 if (top100c[idx].val)
348 r += sprintf(r, "\t%zd\t%s\n", top100c[idx].val, hex_fromhash(hex_out, top100c[idx].hash)); 335 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100c[idx].val, hex_from_hash(hex_out, top100c[idx].hash));
349 r += sprintf(r, "Top %zd torrents by seeds:\n", amount); 336 fmt_append(reply, reply_end, "Top %zd torrents by seeds:\n", amount);
350 for (idx = 0; idx < amount; ++idx) 337 for (idx = 0; idx < amount; ++idx)
351 if (top100s[idx].val) 338 if (top100s[idx].val)
352 r += sprintf(r, "\t%zd\t%s\n", top100s[idx].val, hex_fromhash(hex_out, top100s[idx].hash)); 339 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100s[idx].val, hex_from_hash(hex_out, top100s[idx].hash));
353 r += sprintf(r, "Top %zd torrents by leechers:\n", amount); 340 fmt_append(reply, reply_end, "Top %zd torrents by leechers:\n", amount);
354 for (idx = 0; idx < amount; ++idx) 341 for (idx = 0; idx < amount; ++idx)
355 if (top100l[idx].val) 342 if (top100l[idx].val)
356 r += sprintf(r, "\t%zd\t%s\n", top100l[idx].val, hex_fromhash(hex_out, top100l[idx].hash)); 343 fmt_append(reply, reply_end, "\t%zd\t%s\n", top100l[idx].val, hex_from_hash(hex_out, top100l[idx].hash));
357
358 return r - reply;
359} 344}
360 345
361static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); } 346static unsigned long events_per_time(unsigned long long events, time_t t) { return events / ((unsigned int)t ? (unsigned int)t : 1); }
362 347
363static size_t stats_connections_mrtg(char *reply) { 348static void stats_connections_mrtg(char **reply, const char *reply_end) {
364 ot_time t = time(NULL) - ot_start_time; 349 ot_time t = time(NULL) - ot_start_time;
365 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", 350 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.",
366 ot_overall_tcp_connections + ot_overall_udp_connections, 351 ot_overall_tcp_connections + ot_overall_udp_connections,
367 ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), 352 ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600),
368 events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), 353 events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t),
369 events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); 354 events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t));
370} 355}
371 356
372static size_t stats_udpconnections_mrtg(char *reply) { 357static void stats_udpconnections_mrtg(char **reply, const char *reply_end) {
373 ot_time t = time(NULL) - ot_start_time; 358 ot_time t = time(NULL) - ot_start_time;
374 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", ot_overall_udp_connections, 359 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker udp4 stats, %lu conns/s :: %lu success/s.", ot_overall_udp_connections,
375 ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), events_per_time(ot_overall_udp_connections, t), 360 ot_overall_udp_successfulannounces + ot_overall_udp_connects, (int)t, (int)(t / 3600), events_per_time(ot_overall_udp_connections, t),
376 events_per_time(ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); 361 events_per_time(ot_overall_udp_successfulannounces + ot_overall_udp_connects, t));
377} 362}
378 363
379static size_t stats_tcpconnections_mrtg(char *reply) { 364static void stats_tcpconnections_mrtg(char **reply, const char *reply_end) {
380 time_t t = time(NULL) - ot_start_time; 365 time_t t = time(NULL) - ot_start_time;
381 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", ot_overall_tcp_connections, 366 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker tcp4 stats, %lu conns/s :: %lu success/s.", ot_overall_tcp_connections,
382 ot_overall_tcp_successfulannounces, (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections, t), 367 ot_overall_tcp_successfulannounces, (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections, t),
383 events_per_time(ot_overall_tcp_successfulannounces, t)); 368 events_per_time(ot_overall_tcp_successfulannounces, t));
384} 369}
385 370
386static size_t stats_scrape_mrtg(char *reply) { 371static void stats_scrape_mrtg(char **reply, const char *reply_end) {
387 time_t t = time(NULL) - ot_start_time; 372 time_t t = time(NULL) - ot_start_time;
388 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)", ot_overall_tcp_successfulscrapes, 373 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker scrape stats, %lu scrape/s (tcp and udp)",
389 ot_overall_udp_successfulscrapes, (int)t, (int)(t / 3600), 374 ot_overall_tcp_successfulscrapes, ot_overall_udp_successfulscrapes, (int)t, (int)(t / 3600),
390 events_per_time((ot_overall_tcp_successfulscrapes + ot_overall_udp_successfulscrapes), t)); 375 events_per_time((ot_overall_tcp_successfulscrapes + ot_overall_udp_successfulscrapes), t));
391} 376}
392 377
393static size_t stats_fullscrapes_mrtg(char *reply) { 378static void stats_fullscrapes_mrtg(char **reply, const char *reply_end) {
394 ot_time t = time(NULL) - ot_start_time; 379 ot_time t = time(NULL) - ot_start_time;
395 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.", ot_full_scrape_count * 1000, 380 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker full scrape stats, %lu conns/s :: %lu bytes/s.",
396 ot_full_scrape_size, (int)t, (int)(t / 3600), events_per_time(ot_full_scrape_count, t), events_per_time(ot_full_scrape_size, t)); 381 ot_full_scrape_count * 1000, ot_full_scrape_size, (int)t, (int)(t / 3600), events_per_time(ot_full_scrape_count, t),
382 events_per_time(ot_full_scrape_size, t));
397} 383}
398 384
399static size_t stats_peers_mrtg(char *reply) { 385static void stats_peers_mrtg(char **reply, const char *reply_end) {
400 torrent_stats stats = {0, 0, 0}; 386 torrent_stats stats = {0, 0, 0};
401 387
402 iterate_all_torrents(torrent_statter, (uintptr_t)&stats); 388 iterate_all_torrents(torrent_statter, (uintptr_t)&stats);
403 389
404 return sprintf(reply, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count, stats.torrent_count); 390 fmt_append(reply, reply_end, "%llu\n%llu\nopentracker serving %llu torrents\nopentracker", stats.peer_count, stats.seed_count,
391 stats.torrent_count);
405} 392}
406 393
407static size_t stats_torrents_mrtg(char *reply) { 394static void stats_torrents_mrtg(char **reply, const char *reply_end) {
408 size_t torrent_count = mutex_get_torrent_count(); 395 size_t torrent_count = mutex_get_torrent_count();
409 396
410 return sprintf(reply, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count); 397 fmt_append(reply, reply_end, "%zd\n%zd\nopentracker serving %zd torrents\nopentracker", torrent_count, (size_t)0, torrent_count);
411} 398}
412 399
413static size_t stats_httperrors_txt(char *reply) { 400static void stats_httperrors_txt(char **reply, const char *reply_end) {
414 return sprintf(reply, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP %llu\n404 INV %llu\n500 SRV %llu\n", ot_failed_request_counts[0], 401 fmt_append(reply, reply_end, "302 RED %llu\n400 ... %llu\n400 PAR %llu\n400 COM %llu\n403 IP %llu\n404 INV %llu\n500 SRV %llu\n",
415 ot_failed_request_counts[1], ot_failed_request_counts[2], ot_failed_request_counts[3], ot_failed_request_counts[4], 402 ot_failed_request_counts[0], ot_failed_request_counts[1], ot_failed_request_counts[2], ot_failed_request_counts[3],
416 ot_failed_request_counts[5], ot_failed_request_counts[6]); 403 ot_failed_request_counts[4], ot_failed_request_counts[5], ot_failed_request_counts[6]);
417} 404}
418 405
419static size_t stats_return_renew_bucket(char *reply) { 406static void stats_return_renew_bucket(char **reply, const char *reply_end) {
420 char *r = reply; 407 int i;
421 int i;
422 408
423 for (i = 0; i < OT_PEER_TIMEOUT; ++i) 409 for (i = 0; i < OT_PEER_TIMEOUT; ++i)
424 r += sprintf(r, "%02i %llu\n", i, ot_renewed[i]); 410 fmt_append(reply, reply_end, "%02i %llu\n", i, ot_renewed[i]);
425 return r - reply;
426} 411}
427 412
428static size_t stats_return_sync_mrtg(char *reply) { 413static void stats_return_sync_mrtg(char **reply, const char *reply_end) {
429 ot_time t = time(NULL) - ot_start_time; 414 ot_time t = time(NULL) - ot_start_time;
430 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", ot_overall_sync_count, 0LL, (int)t, 415 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker connections, %lu conns/s :: %lu success/s.", ot_overall_sync_count, 0LL,
431 (int)(t / 3600), events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t), 416 (int)t, (int)(t / 3600), events_per_time(ot_overall_tcp_connections + ot_overall_udp_connections, t),
432 events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t)); 417 events_per_time(ot_overall_tcp_successfulannounces + ot_overall_udp_successfulannounces + ot_overall_udp_connects, t));
433} 418}
434 419
435static size_t stats_return_completed_mrtg(char *reply) { 420static void stats_return_completed_mrtg(char **reply, const char *reply_end) {
436 ot_time t = time(NULL) - ot_start_time; 421 ot_time t = time(NULL) - ot_start_time;
437 422
438 return sprintf(reply, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", ot_overall_completed, 0LL, (int)t, (int)(t / 3600), 423 fmt_append(reply, reply_end, "%llu\n%llu\n%i seconds (%i hours)\nopentracker, %lu completed/h.", ot_overall_completed, 0LL, (int)t, (int)(t / 3600),
439 events_per_time(ot_overall_completed, t / 3600)); 424 events_per_time(ot_overall_completed, t / 3600));
440} 425}
441 426
442#ifdef WANT_LOG_NUMWANT 427#ifdef WANT_LOG_NUMWANT
443extern unsigned long long numwants[201]; 428extern unsigned long long numwants[201];
444static size_t stats_return_numwants(char *reply) { 429static void stats_return_numwants(char **reply, const char *reply_end) {
445 char *r = reply; 430 int i;
446 int i;
447 for (i = 0; i <= 200; ++i) 431 for (i = 0; i <= 200; ++i)
448 r += sprintf(r, "%03d => %lld\n", i, numwants[i]); 432 fmt_append(reply, reply_end, "%03d => %lld\n", i, numwants[i]);
449 return r - reply;
450} 433}
451#endif 434#endif
452 435
@@ -459,148 +442,158 @@ static void stats_return_fulllog(size_t *iovec_entries, struct iovec **iovector,
459 442
460 while (loglist) { 443 while (loglist) {
461 if (r + (loglist->size + 64) >= re) { 444 if (r + (loglist->size + 64) >= re) {
462 r = iovec_fix_increase_or_free(iovec_entries, iovector, r, 32 * OT_STATS_TMPSIZE); 445 r = iovec_next_chunk(iovec_entries, iovector, r, 32 * OT_STATS_TMPSIZE);
463 if (!r) 446 if (!r)
464 return; 447 return;
465 re = r + 32 * OT_STATS_TMPSIZE; 448 re = r + 32 * OT_STATS_TMPSIZE;
466 } 449 }
467 r += sprintf(r, "%08ld: ", loglist->time); 450 char ip_readable[64];
468 r += ip_format(r, loglist->ip); 451
469 *r++ = '\n'; 452 ip_readable[ip_format(ip_readable, loglist->ip)] = 0;
470 memcpy(r, loglist->data, loglist->size); 453 fmt_append(&r, re, "%08ld: %s\n", loglist->time, ip_readable);
471 r += loglist->size; 454 fmt_bytes(&r, re, loglist->data, loglist->size);
472 *r++ = '\n'; 455 fmt_append(&r, re, "\n*\n\n");
473 *r++ = '*';
474 *r++ = '\n';
475 *r++ = '\n';
476 456
477 llnext = loglist->next; 457 llnext = loglist->next;
478 free(loglist->data); 458 free(loglist->data);
479 free(loglist); 459 free(loglist);
480 loglist = llnext; 460 loglist = llnext;
481 } 461 }
482 iovec_fixlast(iovec_entries, iovector, r); 462 iovec_trim_last(iovec_entries, iovector, r);
483} 463}
484#endif 464#endif
485 465
486static size_t stats_return_everything(char *reply) { 466static void stats_return_everything(char **reply, const char *reply_end) {
487 torrent_stats stats = {0, 0, 0}; 467 torrent_stats stats = {0, 0, 0};
488 int i; 468 int i;
489 char *r = reply;
490 469
491 iterate_all_torrents(torrent_statter, (uintptr_t)&stats); 470 iterate_all_torrents(torrent_statter, (uintptr_t)&stats);
492 471
493 r += sprintf(r, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); 472 fmt_append(reply, reply_end, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
494 r += sprintf(r, "<stats>\n"); 473 fmt_append(reply, reply_end, "<stats>\n");
495 r += sprintf(r, " <tracker_id>%" PRIu32 "</tracker_id>\n", g_tracker_id); 474 fmt_append(reply, reply_end, " <tracker_id>%" PRIu32 "</tracker_id>\n", g_tracker_id);
496 r += sprintf(r, " <version>\n"); 475 fmt_append(reply, reply_end, " <version>\n");
497 r += stats_return_tracker_version(r); 476 stats_return_tracker_version(reply, reply_end);
498 r += sprintf(r, " </version>\n"); 477 fmt_append(reply, reply_end, " </version>\n");
499 r += sprintf(r, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time)); 478 fmt_append(reply, reply_end, " <uptime>%llu</uptime>\n", (unsigned long long)(time(NULL) - ot_start_time));
500 r += sprintf(r, " <torrents>\n"); 479 fmt_append(reply, reply_end, " <torrents>\n");
501 r += sprintf(r, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count()); 480 fmt_append(reply, reply_end, " <count_mutex>%zd</count_mutex>\n", mutex_get_torrent_count());
502 r += sprintf(r, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count); 481 fmt_append(reply, reply_end, " <count_iterator>%llu</count_iterator>\n", stats.torrent_count);
503 r += sprintf(r, " </torrents>\n"); 482 fmt_append(reply, reply_end, " </torrents>\n");
504 r += sprintf(r, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count); 483 fmt_append(reply, reply_end, " <peers>\n <count>%llu</count>\n </peers>\n", stats.peer_count);
505 r += sprintf(r, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count); 484 fmt_append(reply, reply_end, " <seeds>\n <count>%llu</count>\n </seeds>\n", stats.seed_count);
506 r += sprintf(r, " <completed>\n <count>%llu</count>\n </completed>\n", ot_overall_completed); 485 fmt_append(reply, reply_end, " <completed>\n <count>%llu</count>\n </completed>\n", ot_overall_completed);
507 r += sprintf(r, " <connections>\n"); 486 fmt_append(reply, reply_end, " <connections>\n");
508 r += sprintf(r, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n", 487 fmt_append(reply, reply_end, " <tcp>\n <accept>%llu</accept>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n </tcp>\n",
509 ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_tcp_successfulscrapes); 488 ot_overall_tcp_connections, ot_overall_tcp_successfulannounces, ot_overall_tcp_successfulscrapes);
510 r += sprintf(r, " <udp>\n <overall>%llu</overall>\n <connect>%llu</connect>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n <missmatch>%llu</missmatch>\n </udp>\n", 489 fmt_append(reply, reply_end, " <udp>\n <overall>%llu</overall>\n <connect>%llu</connect>\n <announce>%llu</announce>\n <scrape>%llu</scrape>\n <missmatch>%llu</missmatch>\n </udp>\n",
511 ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes, 490 ot_overall_udp_connections, ot_overall_udp_connects, ot_overall_udp_successfulannounces, ot_overall_udp_successfulscrapes,
512 ot_overall_udp_connectionidmissmatches); 491 ot_overall_udp_connectionidmissmatches);
513 r += sprintf(r, " <livesync>\n <count>%llu</count>\n </livesync>\n", ot_overall_sync_count); 492 fmt_append(reply, reply_end, " <livesync>\n <count>%llu</count>\n </livesync>\n", ot_overall_sync_count);
514 r += sprintf(r, " </connections>\n"); 493 fmt_append(reply, reply_end, " </connections>\n");
515 r += sprintf(r, " <debug>\n"); 494 fmt_append(reply, reply_end, " <debug>\n");
516 r += sprintf(r, " <renew>\n"); 495 fmt_append(reply, reply_end, " <renew>\n");
517 for (i = 0; i < OT_PEER_TIMEOUT; ++i) 496 for (i = 0; i < OT_PEER_TIMEOUT; ++i)
518 r += sprintf(r, " <count interval=\"%02i\">%llu</count>\n", i, ot_renewed[i]); 497 fmt_append(reply, reply_end, " <count interval=\"%02i\">%llu</count>\n", i, ot_renewed[i]);
519 r += sprintf(r, " </renew>\n"); 498 fmt_append(reply, reply_end, " </renew>\n");
520 r += sprintf(r, " <http_error>\n"); 499 fmt_append(reply, reply_end, " <http_error>\n");
521 for (i = 0; i < CODE_HTTPERROR_COUNT; ++i) 500 for (i = 0; i < CODE_HTTPERROR_COUNT; ++i)
522 r += sprintf(r, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], ot_failed_request_counts[i]); 501 fmt_append(reply, reply_end, " <count code=\"%s\">%llu</count>\n", ot_failed_request_names[i], ot_failed_request_counts[i]);
523 r += sprintf(r, " </http_error>\n"); 502 fmt_append(reply, reply_end, " </http_error>\n");
524 r += sprintf(r, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", ot_overall_stall_count); 503 fmt_append(reply, reply_end, " <mutex_stall>\n <count>%llu</count>\n </mutex_stall>\n", ot_overall_stall_count);
525 r += sprintf(r, " </debug>\n"); 504 fmt_append(reply, reply_end, " </debug>\n");
526 r += sprintf(r, "</stats>"); 505 fmt_append(reply, reply_end, "</stats>");
527 return r - reply;
528} 506}
529 507
530size_t stats_return_tracker_version(char *reply) { 508void stats_return_tracker_version(char **reply, const char *reply_end) {
531#define QUOTE(name) #name 509#define QUOTE(name) #name
532#define SQUOTE(name) QUOTE(name) 510#define SQUOTE(name) QUOTE(name)
533 return sprintf(reply, "https://erdgeist.org/gitweb/opentracker/commit/?id=" SQUOTE(GIT_VERSION) "\n"); 511 fmt_append(reply, reply_end, "https://erdgeist.org/gitweb/opentracker/commit/?id=" SQUOTE(GIT_VERSION) "\n");
534} 512}
535 513
536size_t return_stats_for_tracker(char *reply, int mode, int format) { 514void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format) {
537 (void)format; 515 (void)format;
538 switch (mode & TASK_TASK_MASK) { 516 switch (mode & TASK_TASK_MASK) {
539 case TASK_STATS_CONNS: 517 case TASK_STATS_CONNS:
540 return stats_connections_mrtg(reply); 518 stats_connections_mrtg(reply, reply_end);
519 break;
541 case TASK_STATS_SCRAPE: 520 case TASK_STATS_SCRAPE:
542 return stats_scrape_mrtg(reply); 521 stats_scrape_mrtg(reply, reply_end);
522 break;
543 case TASK_STATS_UDP: 523 case TASK_STATS_UDP:
544 return stats_udpconnections_mrtg(reply); 524 stats_udpconnections_mrtg(reply, reply_end);
525 break;
545 case TASK_STATS_TCP: 526 case TASK_STATS_TCP:
546 return stats_tcpconnections_mrtg(reply); 527 stats_tcpconnections_mrtg(reply, reply_end);
528 break;
547 case TASK_STATS_FULLSCRAPE: 529 case TASK_STATS_FULLSCRAPE:
548 return stats_fullscrapes_mrtg(reply); 530 stats_fullscrapes_mrtg(reply, reply_end);
531 break;
549 case TASK_STATS_COMPLETED: 532 case TASK_STATS_COMPLETED:
550 return stats_return_completed_mrtg(reply); 533 stats_return_completed_mrtg(reply, reply_end);
534 break;
551 case TASK_STATS_HTTPERRORS: 535 case TASK_STATS_HTTPERRORS:
552 return stats_httperrors_txt(reply); 536 stats_httperrors_txt(reply, reply_end);
537 break;
553 case TASK_STATS_VERSION: 538 case TASK_STATS_VERSION:
554 return stats_return_tracker_version(reply); 539 stats_return_tracker_version(reply, reply_end);
540 break;
555 case TASK_STATS_RENEW: 541 case TASK_STATS_RENEW:
556 return stats_return_renew_bucket(reply); 542 stats_return_renew_bucket(reply, reply_end);
543 break;
557 case TASK_STATS_SYNCS: 544 case TASK_STATS_SYNCS:
558 return stats_return_sync_mrtg(reply); 545 stats_return_sync_mrtg(reply, reply_end);
546 break;
559#ifdef WANT_LOG_NUMWANT 547#ifdef WANT_LOG_NUMWANT
560 case TASK_STATS_NUMWANTS: 548 case TASK_STATS_NUMWANTS:
561 return stats_return_numwants(reply); 549 stats_return_numwants(reply, reply_end);
550 break;
562#endif 551#endif
563 default: 552 default:
564 return 0; 553 break;
565 } 554 }
566} 555}
567 556
568static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) { 557static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_tasktype mode) {
569 char *r; 558 char *r;
559 const char *r_end;
570 560
571 *iovec_entries = 0; 561 *iovec_entries = 0;
572 *iovector = NULL; 562 *iovector = NULL;
573 if (!(r = iovec_increase(iovec_entries, iovector, OT_STATS_TMPSIZE))) 563 if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_STATS_TMPSIZE)))
574 return; 564 return;
565 r_end = r + OT_STATS_TMPSIZE;
575 566
576 switch (mode & TASK_TASK_MASK) { 567 switch (mode & TASK_TASK_MASK) {
577 case TASK_STATS_TORRENTS: 568 case TASK_STATS_TORRENTS:
578 r += stats_torrents_mrtg(r); 569 stats_torrents_mrtg(&r, r_end);
579 break; 570 break;
580 case TASK_STATS_PEERS: 571 case TASK_STATS_PEERS:
581 r += stats_peers_mrtg(r); 572 stats_peers_mrtg(&r, r_end);
582 break; 573 break;
583 case TASK_STATS_SLASH24S: 574 case TASK_STATS_SLASH24S:
584 r += stats_slash24s_txt(r, 128); 575 stats_slash24s_txt(&r, r_end, 128);
585 break; 576 break;
586 case TASK_STATS_TOP10: 577 case TASK_STATS_TOP10:
587 r += stats_top_txt(r, 10); 578 stats_top_txt(&r, r_end, 10);
588 break; 579 break;
589 case TASK_STATS_TOP100: 580 case TASK_STATS_TOP100:
590 r = iovec_fix_increase_or_free(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE); 581 r = iovec_next_chunk(iovec_entries, iovector, r, 4 * OT_STATS_TMPSIZE);
591 if (!r) 582 if (!r)
592 return; 583 return;
593 r += stats_top_txt(r, 100); 584 r_end = r + 4 * OT_STATS_TMPSIZE;
585 stats_top_txt(&r, r_end, 100);
594 break; 586 break;
595 case TASK_STATS_EVERYTHING: 587 case TASK_STATS_EVERYTHING:
596 r = iovec_fix_increase_or_free(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT); 588 r = iovec_next_chunk(iovec_entries, iovector, r, OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT);
597 if (!r) 589 if (!r)
598 return; 590 return;
599 r += stats_return_everything(r); 591 r_end = r + OT_STATS_TMPSIZE + 64 * OT_PEER_TIMEOUT;
592 stats_return_everything(&r, r_end);
600 break; 593 break;
601#ifdef WANT_SPOT_WOODPECKER 594#ifdef WANT_SPOT_WOODPECKER
602 case TASK_STATS_WOODPECKERS: 595 case TASK_STATS_WOODPECKERS:
603 r += stats_return_woodpeckers(r, 128); 596 stats_return_woodpeckers(&r, r_end, 128);
604 break; 597 break;
605#endif 598#endif
606#ifdef WANT_FULLLOG_NETWORKS 599#ifdef WANT_FULLLOG_NETWORKS
@@ -612,7 +605,7 @@ static void stats_make(size_t *iovec_entries, struct iovec **iovector, ot_taskty
612 iovec_free(iovec_entries, iovector); 605 iovec_free(iovec_entries, iovector);
613 return; 606 return;
614 } 607 }
615 iovec_fixlast(iovec_entries, iovector, r); 608 iovec_trim_last(iovec_entries, iovector, r);
616} 609}
617 610
618void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) { 611void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data) {
@@ -651,9 +644,9 @@ void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_
651 localtime_r(&ttt, &time_now); 644 localtime_r(&ttt, &time_now);
652 strftime(timestring, sizeof(timestring), "%FT%T%z", &time_now); 645 strftime(timestring, sizeof(timestring), "%FT%T%z", &time_now);
653 646
654 hex_fromhash(hash_hex, *ws->hash); 647 hex_from_hash(hash_hex, *ws->hash);
655 if (ws->peer_id) 648 if (ws->peer_id)
656 hex_fromhash(peerid_hex, (uint8_t *)ws->peer_id); 649 hex_from_hash(peerid_hex, (uint8_t *)ws->peer_id);
657 else { 650 else {
658 *peerid_hex = 0; 651 *peerid_hex = 0;
659 } 652 }
diff --git a/ot_stats.h b/ot_stats.h
index 203831c..fb6aad8 100644
--- a/ot_stats.h
+++ b/ot_stats.h
@@ -44,8 +44,8 @@ enum {
44void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data); 44void stats_issue_event(ot_status_event event, PROTO_FLAG proto, uintptr_t event_data);
45void stats_deliver(int64_t sock, int tasktype); 45void stats_deliver(int64_t sock, int tasktype);
46void stats_cleanup(void); 46void stats_cleanup(void);
47size_t return_stats_for_tracker(char *reply, int mode, int format); 47void return_stats_for_tracker(char **reply, const char *reply_end, int mode, int format);
48size_t stats_return_tracker_version(char *reply); 48void stats_return_tracker_version(char **reply, const char *reply_end);
49void stats_init(void); 49void stats_init(void);
50void stats_deinit(void); 50void stats_deinit(void);
51 51
diff --git a/ot_sync.c b/ot_sync.c
index dc71ddd..f656bef 100644
--- a/ot_sync.c
+++ b/ot_sync.c
@@ -10,10 +10,8 @@
10#include <sys/types.h> 10#include <sys/types.h>
11#include <sys/uio.h> 11#include <sys/uio.h>
12 12
13/* Libowfat */
14#include "io.h"
15
16/* Opentracker */ 13/* Opentracker */
14#include "ot_fmt.h"
17#include "ot_iovec.h" 15#include "ot_iovec.h"
18#include "ot_mutex.h" 16#include "ot_mutex.h"
19#include "ot_scan.h" 17#include "ot_scan.h"
@@ -51,7 +49,7 @@ int add_changeset_to_tracker(uint8_t *data, size_t len) {
51 data += sizeof(ot_hash); 49 data += sizeof(ot_hash);
52 50
53 /* Scan string length indicator, strictly: bencode writes no leading zeros */ 51 /* Scan string length indicator, strictly: bencode writes no leading zeros */
54 data += (len = scan_udec_bencode((char *)data, &peer_count, (uint64_t)(end - data))); 52 data += (len = scan_uint_bencode((char *)data, &peer_count, (uint64_t)(end - data)));
55 53
56 /* If no long was scanned, it is not divisible by 8, it is not 54 /* If no long was scanned, it is not divisible by 8, it is not
57 followed by a colon or claims to need to much memory, we fail */ 55 followed by a colon or claims to need to much memory, we fail */
@@ -77,7 +75,7 @@ static void sync_make(size_t *iovec_entries, struct iovec **iovector) {
77 /* Setup return vector... */ 75 /* Setup return vector... */
78 *iovec_entries = 0; 76 *iovec_entries = 0;
79 *iovector = NULL; 77 *iovector = NULL;
80 if (!(r = iovec_increase(iovec_entries, iovector, OT_SYNC_CHUNK_SIZE))) 78 if (!(r = iovec_new_chunk(iovec_entries, iovector, OT_SYNC_CHUNK_SIZE)))
81 return; 79 return;
82 80
83 /* ... and pointer to end of current output buffer. 81 /* ... and pointer to end of current output buffer.
@@ -105,21 +103,17 @@ static void sync_make(size_t *iovec_entries, struct iovec **iovector) {
105 103
106 /* Allocate a fresh output buffer at the end of our buffers list 104 /* Allocate a fresh output buffer at the end of our buffers list
107 release bucket and return, if that fails */ 105 release bucket and return, if that fails */
108 if (!(r = iovec_fix_increase_or_free(iovec_entries, iovector, r, OT_SYNC_CHUNK_SIZE))) 106 if (!(r = iovec_next_chunk(iovec_entries, iovector, r, OT_SYNC_CHUNK_SIZE)))
109 return mutex_bucket_unlock(bucket); 107 return mutex_bucket_unlock(bucket);
110 108
111 /* Adjust new end of output buffer */ 109 /* Adjust new end of output buffer */
112 re = r + OT_SYNC_CHUNK_SIZE; 110 re = r + OT_SYNC_CHUNK_SIZE;
113 } 111 }
114 112
115 *r++ = '2'; 113 fmt_append(&r, re, "20:");
116 *r++ = '0'; 114 fmt_bytes(&r, re, hash, sizeof(ot_hash));
117 *r++ = ':'; 115 fmt_append(&r, re, "%zd:", byte_count);
118 memmove(r, hash, sizeof(ot_hash)); 116 fmt_bytes(&r, re, peer_list->changeset.data, byte_count);
119 r += sizeof(ot_hash);
120 r += sprintf(r, "%zd:", byte_count);
121 memmove(r, peer_list->changeset.data, byte_count);
122 r += byte_count;
123 } 117 }
124 118
125 /* All torrents done: release lock on currenct bucket */ 119 /* All torrents done: release lock on currenct bucket */
@@ -131,7 +125,7 @@ static void sync_make(size_t *iovec_entries, struct iovec **iovector) {
131 *r++ = 'e'; 125 *r++ = 'e';
132 126
133 /* Release unused memory in current output buffer */ 127 /* Release unused memory in current output buffer */
134 iovec_fixlast(iovec_entries, iovector, r); 128 iovec_trim_last(iovec_entries, iovector, r);
135} 129}
136 130
137/* This is the entry point into this worker thread 131/* This is the entry point into this worker thread
diff --git a/ot_udp.c b/ot_udp.c
index 7f6517f..4e760bb 100644
--- a/ot_udp.c
+++ b/ot_udp.c
@@ -10,9 +10,6 @@
10#include <stdlib.h> 10#include <stdlib.h>
11#include <string.h> 11#include <string.h>
12 12
13/* Libowfat */
14#include "io.h"
15
16/* Opentracker */ 13/* Opentracker */
17#include "ot_ip.h" 14#include "ot_ip.h"
18#include "ot_random.h" 15#include "ot_random.h"
@@ -108,10 +105,11 @@ int handle_udp6(int64_t serversocket, struct ot_workstruct *ws) {
108 match, return an error packet */ 105 match, return an error packet */
109 udp_make_connectionid(connid, remoteip, 1); 106 udp_make_connectionid(connid, remoteip, 1);
110 if (inpacket[0] != connid[0] || inpacket[1] != connid[1]) { 107 if (inpacket[0] != connid[0] || inpacket[1] != connid[1]) {
111 const size_t s = sizeof("Connection ID missmatch."); 108 const char err_idmiss[] = "Connection ID missmatch.";
109 size_t s = sizeof(err_idmiss);
112 outpacket[0] = htonl(3); 110 outpacket[0] = htonl(3);
113 outpacket[1] = inpacket[3]; 111 outpacket[1] = inpacket[3];
114 memcpy(&outpacket[2], "Connection ID missmatch.", s); 112 memcpy(&outpacket[2], &err_idmiss[0], s);
115 socket_send(serversocket, ws->outbuf, 8 + s, remoteip, remoteport); 113 socket_send(serversocket, ws->outbuf, 8 + s, remoteip, remoteport);
116 stats_issue_event(EVENT_CONNID_MISSMATCH, FLAG_UDP, 8 + s); 114 stats_issue_event(EVENT_CONNID_MISSMATCH, FLAG_UDP, 8 + s);
117 return 1; 115 return 1;
@@ -176,7 +174,17 @@ int handle_udp6(int64_t serversocket, struct ot_workstruct *ws) {
176 numwant = max_peers; 174 numwant = max_peers;
177 175
178 ws->reply = ws->outbuf + 8; 176 ws->reply = ws->outbuf + 8;
179 ws->reply_size = 8 + add_peer_to_torrent_and_return_peers(FLAG_UDP, ws, numwant); 177 ws->reply_size = add_peer_to_torrent_and_return_peers(FLAG_UDP, ws, numwant);
178
179 if (!ws->reply_size) {
180 const char err_unauth[] = "Requested download is not authorized for use with this tracker.";
181 size_t s = sizeof(err_unauth);
182 outpacket[0] = htonl(3);
183 memcpy(&outpacket[2], &err_unauth[0], s);
184 socket_send(serversocket, ws->outbuf, 8 + s, remoteip, remoteport);
185 return 1;
186 }
187 ws->reply_size += 8;
180 } 188 }
181 189
182 socket_send(serversocket, ws->outbuf, ws->reply_size, remoteip, remoteport); 190 socket_send(serversocket, ws->outbuf, ws->reply_size, remoteip, remoteport);
diff --git a/scan_urlencoded_query.c b/ot_urlencoded.c
index cfc8708..17f12a4 100644
--- a/scan_urlencoded_query.c
+++ b/ot_urlencoded.c
@@ -4,7 +4,7 @@
4 $id$ */ 4 $id$ */
5 5
6/* Opentracker */ 6/* Opentracker */
7#include "scan_urlencoded_query.h" 7#include "ot_urlencoded.h"
8 8
9/* System */ 9/* System */
10#include <string.h> 10#include <string.h>
@@ -53,7 +53,7 @@ static unsigned char fromhex(unsigned char x) {
53} 53}
54 54
55/* Skip the value of a param=value pair */ 55/* Skip the value of a param=value pair */
56void scan_urlencoded_skipvalue(char **string) { 56void urlencoded_skip_value(char **string) {
57 const unsigned char *s = *(const unsigned char **)string; 57 const unsigned char *s = *(const unsigned char **)string;
58 unsigned char f; 58 unsigned char f;
59 59
@@ -63,16 +63,16 @@ void scan_urlencoded_skipvalue(char **string) {
63 ; 63 ;
64 64
65 /* If we stopped at a hard terminator like \0 or \n, make the 65 /* If we stopped at a hard terminator like \0 or \n, make the
66 next scan_urlencoded_query encounter it again */ 66 next urlencoded_parse encounter it again */
67 if (f & SCAN_SEARCHPATH_TERMINATOR) 67 if (f & SCAN_SEARCHPATH_TERMINATOR)
68 --s; 68 --s;
69 69
70 *string = (char *)s; 70 *string = (char *)s;
71} 71}
72 72
73int scan_find_keywords(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags) { 73int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags) {
74 char *deststring = *string; 74 char *deststring = *string;
75 ssize_t match_length = scan_urlencoded_query(string, deststring, flags); 75 ssize_t match_length = urlencoded_parse(string, deststring, flags);
76 76
77 if (match_length < 0) 77 if (match_length < 0)
78 return match_length; 78 return match_length;
@@ -88,7 +88,7 @@ int scan_find_keywords(const ot_keywords *keywords, char **string, SCAN_SEARCHPA
88 return -3; 88 return -3;
89} 89}
90 90
91ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags) { 91ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags) {
92 const unsigned char *s = *(const unsigned char **)string; 92 const unsigned char *s = *(const unsigned char **)string;
93 unsigned char *d = (unsigned char *)deststring; 93 unsigned char *d = (unsigned char *)deststring;
94 unsigned char b, c; 94 unsigned char b, c;
@@ -121,7 +121,7 @@ ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_F
121 if (d == (unsigned char *)deststring) 121 if (d == (unsigned char *)deststring)
122 return -2; 122 return -2;
123 123
124 /* Else make the next call to scan_urlencoded_param encounter it again */ 124 /* Else make the next call to urlencoded_parse encounter it again */
125 --s; 125 --s;
126 break; 126 break;
127 case '?': 127 case '?':
@@ -146,19 +146,6 @@ ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_F
146 return d - (unsigned char *)deststring; 146 return d - (unsigned char *)deststring;
147} 147}
148 148
149ssize_t scan_fixed_int(char *data, size_t len, int *tmp) {
150 int minus = 0;
151 *tmp = 0;
152 if (*data == '-')
153 --len, ++data, ++minus;
154 while ((len > 0) && (*data >= '0') && (*data <= '9')) {
155 --len;
156 *tmp = 10 * *tmp + *data++ - '0';
157 }
158 if (minus)
159 *tmp = -*tmp;
160 return len;
161}
162 149
163size_t urlencoded_format(char *dest, const uint8_t *src, size_t len) { 150size_t urlencoded_format(char *dest, const uint8_t *src, size_t len) {
164 static const char safe[] = "$/.=~-_"; 151 static const char safe[] = "$/.=~-_";
diff --git a/scan_urlencoded_query.h b/ot_urlencoded.h
index 24e09fd..b6f1208 100644
--- a/scan_urlencoded_query.h
+++ b/ot_urlencoded.h
@@ -30,7 +30,7 @@ typedef enum {
30 or -1 for parse error 30 or -1 for parse error
31 or -2 for terminator found 31 or -2 for terminator found
32*/ 32*/
33ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags); 33ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags);
34 34
35/* string in: pointer to source 35/* string in: pointer to source
36 out: pointer to next scan position 36 out: pointer to next scan position
@@ -40,19 +40,13 @@ ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_F
40 or -2 for terminator found 40 or -2 for terminator found
41 or -3 for no keyword matched 41 or -3 for no keyword matched
42 */ 42 */
43int scan_find_keywords(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags); 43int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags);
44 44
45/* string in: pointer to value of a param=value pair to skip 45/* string in: pointer to value of a param=value pair to skip
46 out: pointer to next scan position on return 46 out: pointer to next scan position on return
47*/ 47*/
48void scan_urlencoded_skipvalue(char **string); 48void urlencoded_skip_value(char **string);
49 49
50/* data pointer to len chars of string
51 len length of chars in data to parse
52 number number to receive result
53 returns number of bytes not parsed, mostly !=0 means fail
54 */
55ssize_t scan_fixed_int(char *data, size_t len, int *number);
56 50
57/* The other direction: percent encode len bytes from src into dest and return 51/* The other direction: percent encode len bytes from src into dest and return
58 how many characters were written. dest needs room for three times len. */ 52 how many characters were written. dest needs room for three times len. */
diff --git a/ot_vector.c b/ot_vector.c
index 924d468..a56d55b 100644
--- a/ot_vector.c
+++ b/ot_vector.c
@@ -57,34 +57,39 @@ static uint8_t vector_hash_peer(ot_peer const *peer, size_t compare_size, size_t
57*/ 57*/
58void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) { 58void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) {
59 uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch); 59 uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch);
60 size_t match_offset, bytes_after;
60 61
61 if (*exactmatch) 62 if (*exactmatch)
62 return match; 63 return match;
63 64
65 /* Hold the insertion point as an offset rather than a pointer: it survives
66 the realloc below, and an empty vector has no data pointer to offset from
67 in the first place */
68 match_offset = vector->size ? (size_t)(match - (uint8_t *)vector->data) : 0;
69 bytes_after = member_size * vector->size - match_offset;
70
64 if (vector->size + 1 > vector->space) { 71 if (vector->size + 1 > vector->space) {
65 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; 72 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
66 ptrdiff_t match_off = match - (uint8_t *)vector->data; 73 uint8_t *new_data = realloc(vector->data, new_space * member_size);
67 uint8_t *new_data = realloc(vector->data, new_space * member_size);
68 74
69 if (!new_data) 75 if (!new_data)
70 return NULL; 76 return NULL;
71 77
72 /* Adjust pointer if it moved by realloc */
73 match = new_data + match_off;
74
75 vector->data = new_data; 78 vector->data = new_data;
76 vector->space = new_space; 79 vector->space = new_space;
77 } 80 }
78 memmove(match + member_size, match, ((uint8_t *)vector->data) + member_size * vector->size - match); 81
82 match = (uint8_t *)vector->data + match_offset;
83 memmove(match + member_size, match, bytes_after);
79 84
80 vector->size++; 85 vector->size++;
81 return match; 86 return match;
82} 87}
83 88
84ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) { 89ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) {
85 ot_peer *match, *end; 90 ot_peer *match;
86 const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); 91 const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
87 size_t match_to_end; 92 size_t match_offset, bytes_after;
88 93
89 /* If space is zero but size is set, we're dealing with a list of vector->size buckets */ 94 /* If space is zero but size is set, we're dealing with a list of vector->size buckets */
90 if (vector->space < vector->size) 95 if (vector->space < vector->size)
@@ -94,26 +99,26 @@ ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size
94 if (*exactmatch) 99 if (*exactmatch)
95 return match; 100 return match;
96 101
97 /* This is the amount of bytes that needs to be pushed backwards by peer_size bytes to make room for new peer */ 102 /* Hold the insertion point as an offset rather than a pointer: it survives
98 end = (ot_peer *)vector->data + vector->size * peer_size; 103 the realloc below, and an empty vector has no data pointer to offset from
99 match_to_end = end - match; 104 in the first place. bytes_after is what has to move up by peer_size to
105 make room for the new peer */
106 match_offset = vector->size ? (size_t)(match - (ot_peer *)vector->data) : 0;
107 bytes_after = vector->size * peer_size - match_offset;
100 108
101 if (vector->size + 1 > vector->space) { 109 if (vector->size + 1 > vector->space) {
102 ptrdiff_t offset = match - (ot_peer *)vector->data; 110 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
103 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; 111 ot_peer *new_data = realloc(vector->data, new_space * peer_size);
104 ot_peer *new_data = realloc(vector->data, new_space * peer_size);
105 112
106 if (!new_data) 113 if (!new_data)
107 return NULL; 114 return NULL;
108 /* Adjust pointer if it moved by realloc */
109 match = new_data + offset;
110 115
111 vector->data = new_data; 116 vector->data = new_data;
112 vector->space = new_space; 117 vector->space = new_space;
113 } 118 }
114 119
115 /* Here we're guaranteed to have enough space in vector to move the block of peers after insertion point */ 120 match = (ot_peer *)vector->data + match_offset;
116 memmove(match + peer_size, match, match_to_end); 121 memmove(match + peer_size, match, bytes_after);
117 122
118 vector->size++; 123 vector->size++;
119 return match; 124 return match;
@@ -150,11 +155,13 @@ int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size)
150} 155}
151 156
152void vector_remove_torrent(ot_vector *vector, ot_torrent *match) { 157void vector_remove_torrent(ot_vector *vector, ot_torrent *match) {
153 ot_torrent *end = ((ot_torrent *)vector->data) + vector->size; 158 ot_torrent *end;
154 159
155 if (!vector->size) 160 if (!vector->size)
156 return; 161 return;
157 162
163 end = ((ot_torrent *)vector->data) + vector->size;
164
158 free_peerlist(match->peer_list6); 165 free_peerlist(match->peer_list6);
159 free_peerlist(match->peer_list4); 166 free_peerlist(match->peer_list4);
160 167
diff --git a/proxy.c b/proxy.c
index 0cac20c..a5c6bb7 100644
--- a/proxy.c
+++ b/proxy.c
@@ -507,7 +507,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
507 ++s; 507 ++s;
508 if (*s == '[') 508 if (*s == '[')
509 ++s, ++bracket; /* for v6 style notation */ 509 ++s, ++bracket; /* for v6 style notation */
510 if (!(off = ip_scan(s, ip))) 510 if (!(off = ip_parse(s, ip)))
511 return 0; 511 return 0;
512 s += off; 512 s += off;
513 if (*s == 0 || isspace(*s)) 513 if (*s == 0 || isspace(*s))
@@ -522,7 +522,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
522 if (*(s++) != ':') 522 if (*(s++) != ':')
523 return 0; 523 return 0;
524 } 524 }
525 if (!(off = scan_udec(s, &value, 65535))) 525 if (!(off = scan_uint(s, &value, 65535)))
526 return 0; 526 return 0;
527 *port = (uint16_t)value; 527 *port = (uint16_t)value;
528 return off + s - src; 528 return off + s - src;
diff --git a/tests/integration/.gitignore b/tests/integration/.gitignore
new file mode 100644
index 0000000..7a60b85
--- /dev/null
+++ b/tests/integration/.gitignore
@@ -0,0 +1,2 @@
1__pycache__/
2*.pyc
diff --git a/tests/integration/harness.py b/tests/integration/harness.py
new file mode 100644
index 0000000..03a0561
--- /dev/null
+++ b/tests/integration/harness.py
@@ -0,0 +1,308 @@
1"""Starts a real opentracker and talks to it over the wire.
2
3Everything here drives the binary rather than the code, so these tests cover
4what the unit suite cannot reach: the event loop, the HTTP parser and the
5torrent store working together. Run against a sanitizer build and the tracker
6reports its own memory errors, which the teardown turns into test failures.
7"""
8
9import os
10import shutil
11import signal
12import socket
13import struct
14import subprocess
15import sys
16import tempfile
17import time
18import unittest
19import urllib.error
20import urllib.parse
21import urllib.request
22
23TRACKER = os.environ.get("OPENTRACKER", os.path.join(os.path.dirname(__file__), "..", "..", "opentracker.debug"))
24
25
26def bdecode(data):
27 """Enough bencode for what a tracker answers."""
28
29 def value(i):
30 if data[i : i + 1] == b"d":
31 i, out = i + 1, {}
32 while data[i : i + 1] != b"e":
33 i, key = value(i)
34 i, out[key] = value(i)
35 return i + 1, out
36 if data[i : i + 1] == b"l":
37 i, out = i + 1, []
38 while data[i : i + 1] != b"e":
39 i, item = value(i)
40 out.append(item)
41 return i + 1, out
42 if data[i : i + 1] == b"i":
43 end = data.index(b"e", i)
44 return end + 1, int(data[i + 1 : end])
45 colon = data.index(b":", i)
46 length = int(data[i:colon])
47 return colon + 1 + length, data[colon + 1 : colon + 1 + length]
48
49 return value(0)[1]
50
51
52def free_port():
53 probe = socket.socket()
54 probe.bind(("127.0.0.1", 0))
55 port = probe.getsockname()[1]
56 probe.close()
57 return port
58
59
60class Tracker:
61 """One tracker process, listening on loopback."""
62
63 def __init__(self, extra_config="", files=None):
64 self.port = free_port()
65 self.dir = tempfile.mkdtemp(prefix="ot-integration-")
66 self.log = None
67 self.process = None
68 try:
69 for name, content in (files or {}).items():
70 with open(os.path.join(self.dir, name), "wb") as handle:
71 handle.write(content)
72 self.config = os.path.join(self.dir, "opentracker.conf")
73 with open(self.config, "w") as handle:
74 handle.write("listen.tcp 127.0.0.1:%d\n" % self.port)
75 handle.write("listen.udp 127.0.0.1:%d\n" % self.port)
76 handle.write(extra_config.replace("{dir}", self.dir))
77 self.log = open(os.path.join(self.dir, "tracker.log"), "w+b")
78 # Leak detection is left at the sanitizer's own default, which is on
79 # where LeakSanitizer exists and off where it does not. Asking for it
80 # by name makes the tracker refuse to start on macOS and FreeBSD.
81 environment = dict(os.environ, ASAN_OPTIONS="abort_on_error=0")
82 self.process = subprocess.Popen([TRACKER, "-f", self.config], stdout=self.log, stderr=self.log, env=environment)
83 self._wait_for_port()
84 except BaseException:
85 self._cleanup()
86 raise
87
88 def _cleanup(self):
89 """Close what we opened and take the scratch directory with it."""
90 if self.process is not None and self.process.poll() is None:
91 self.process.kill()
92 self.process.wait()
93 if self.log is not None:
94 self.log.close()
95 self.log = None
96 shutil.rmtree(self.dir, ignore_errors=True)
97
98 def _wait_for_port(self):
99 for _ in range(200):
100 if self.process.poll() is not None:
101 raise AssertionError("tracker exited at once:\n" + self.output())
102 try:
103 probe = socket.create_connection(("127.0.0.1", self.port), timeout=0.2)
104 probe.close()
105 return
106 except OSError:
107 time.sleep(0.05)
108 raise AssertionError("tracker never listened on %d:\n%s" % (self.port, self.output()))
109
110 def output(self):
111 if self.log is None:
112 return ""
113 self.log.flush()
114 self.log.seek(0)
115 return self.log.read().decode("utf-8", "replace")
116
117 def stop(self):
118 """Returns anything the tracker complained about on the way out.
119
120 The interrupt rather than a kill is what makes the leak check work at
121 all: LeakSanitizer reports from an exit handler, so a killed tracker
122 reports nothing and the suite passes over leaks it never looked for.
123 """
124 if self.process is not None and self.process.poll() is None:
125 self.process.send_signal(signal.SIGINT)
126 try:
127 self.process.wait(timeout=5)
128 except subprocess.TimeoutExpired:
129 self.process.kill()
130 self.process.wait()
131 complaints = [
132 line
133 for line in self.output().splitlines()
134 if "Sanitizer" in line or "runtime error" in line or line.startswith("ERROR")
135 ]
136 self._cleanup()
137 return complaints
138
139 # ----------------------------------------------------------- over http
140
141 def get(self, path, timeout=10):
142 return urllib.request.urlopen("http://127.0.0.1:%d%s" % (self.port, path), timeout=timeout).read()
143
144 def announce(self, info_hash, peer_id=b"-OT0001-aaaaaaaaaaaa", port=6881, left=0, **extra):
145 query = {
146 "info_hash": urllib.parse.quote_from_bytes(info_hash, safe=""),
147 "peer_id": urllib.parse.quote_from_bytes(peer_id, safe=""),
148 "port": port,
149 "left": left,
150 }
151 query.update(extra)
152 return bdecode(self.get("/announce?" + "&".join("%s=%s" % item for item in query.items())))
153
154 def scrape(self, info_hash):
155 return bdecode(self.get("/scrape?info_hash=" + urllib.parse.quote_from_bytes(info_hash, safe="")))
156
157 def stats(self, mode):
158 return self.get("/stats?mode=" + mode)
159
160 def get_with_headers(self, path, headers=None, timeout=10):
161 """The body and the response headers, for the compressed answers."""
162 request = urllib.request.Request("http://127.0.0.1:%d%s" % (self.port, path), headers=headers or {})
163 with urllib.request.urlopen(request, timeout=timeout) as response:
164 return response.read(), dict(response.headers)
165
166 def torrent_count(self):
167 return int(self.stats("torr").split()[0])
168
169 # ------------------------------------------------------------ over udp
170
171 def udp_connect(self):
172 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
173 sock.settimeout(5)
174 sock.sendto(struct.pack(">QII", 0x41727101980, 0, 1), ("127.0.0.1", self.port))
175 answer = sock.recv(2048)
176 action, _, connection_id = struct.unpack(">IIQ", answer[:16])
177 assert action == 0, "udp connect answered action %d" % action
178 return sock, connection_id
179
180 def udp_announce(self, sock, connection_id, info_hash, left=0, event=0, port=6881, numwant=-1):
181 sock.sendto(
182 struct.pack(
183 ">QII20s20sQQQIIIiH", connection_id, 1, 2, info_hash, b"p" * 20, 0, left, 0, event, 0, 0, numwant, port
184 ),
185 ("127.0.0.1", self.port),
186 )
187 answer = sock.recv(2048)
188 action, _, interval, leechers, seeders = struct.unpack(">IIIII", answer[:20])
189 assert action == 1, "udp announce answered action %d" % action
190 return {"interval": interval, "leechers": leechers, "seeders": seeders, "peers": answer[20:]}
191
192 def udp_scrape(self, sock, connection_id, hashes):
193 """One (seeders, completed, leechers) triple per hash, in order."""
194 sock.sendto(struct.pack(">QII", connection_id, 2, 3) + b"".join(hashes), ("127.0.0.1", self.port))
195 answer = sock.recv(2048)
196 action, _ = struct.unpack(">II", answer[:8])
197 assert action == 2, "udp scrape answered action %d" % action
198 return [struct.unpack(">III", answer[8 + 12 * i : 20 + 12 * i]) for i in range(len(hashes))]
199
200 def connect(self):
201 """A connection the caller drives and closes."""
202 return Connection(self.port)
203
204 def udp_raw(self, payload, timeout=1.0):
205 """Sends a packet exactly as given. Returns the answer, or None if the
206 tracker chose to say nothing, which is how it treats a broken one."""
207 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
208 sock.settimeout(timeout)
209 try:
210 sock.sendto(payload, ("127.0.0.1", self.port))
211 try:
212 return sock.recv(2048)
213 except socket.timeout:
214 return None
215 finally:
216 sock.close()
217
218
219class Connection:
220 """One HTTP connection the caller keeps open, which urllib will not do.
221
222 Reads exactly one reply at a time, so a test can tell "the tracker
223 answered and kept the socket" from "the tracker answered and hung up"."""
224
225 def __init__(self, port, timeout=5):
226 self.sock = socket.create_connection(("127.0.0.1", port), timeout=timeout)
227 self.sock.settimeout(timeout)
228 self.buffer = b""
229
230 def send(self, path, connection="Keep-Alive", version="1.1"):
231 request = "GET %s HTTP/%s\r\nHost: tracker\r\n" % (path, version)
232 if connection is not None:
233 request += "Connection: %s\r\n" % connection
234 self.sock.sendall((request + "\r\n").encode())
235
236 def _fill(self):
237 chunk = self.sock.recv(65536)
238 if not chunk:
239 raise EOFError("the tracker closed the connection")
240 self.buffer += chunk
241
242 def reply(self):
243 """The one complete reply at the front of the stream, headers and body."""
244 while b"\r\n\r\n" not in self.buffer:
245 self._fill()
246 head, rest = self.buffer.split(b"\r\n\r\n", 1)
247 headers = {}
248 for line in head.split(b"\r\n")[1:]:
249 name, _, value = line.partition(b":")
250 headers[name.strip().lower()] = value.strip()
251
252 if headers.get(b"transfer-encoding") == b"chunked":
253 while not rest.endswith(b"0\r\n\r\n"):
254 self.buffer = head + b"\r\n\r\n" + rest
255 self._fill()
256 rest = self.buffer.split(b"\r\n\r\n", 1)[1]
257 body, self.buffer = rest, b""
258 else:
259 length = int(headers.get(b"content-length", b"0"))
260 while len(rest) < length:
261 self.buffer = head + b"\r\n\r\n" + rest
262 self._fill()
263 rest = self.buffer.split(b"\r\n\r\n", 1)[1]
264 body, self.buffer = rest[:length], rest[length:]
265 return head.split(b"\r\n")[0], headers, body
266
267 def is_open(self):
268 """True if the tracker is still holding the connection."""
269 try:
270 self.sock.settimeout(0.4)
271 return self.sock.recv(1) != b""
272 except socket.timeout:
273 return True
274 except OSError:
275 return False
276 finally:
277 try:
278 self.sock.settimeout(5)
279 except OSError:
280 pass
281
282 def close(self):
283 self.sock.close()
284
285
286class TrackerTestCase(unittest.TestCase):
287 """Gives every test its own tracker and fails it on any sanitizer output."""
288
289 config = ""
290 files = None
291
292 def setUp(self):
293 self.tracker = Tracker(self.config, self.files)
294
295 def tearDown(self):
296 # A connection the test closed is only reaped when the event loop gets
297 # round to it, and a cookie still held at exit reads as a leak. One
298 # more request puts us behind that work in the loop's queue.
299 try:
300 self.tracker.torrent_count()
301 except Exception:
302 pass
303 complaints = self.tracker.stop()
304 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints))
305
306 def hash_for(self, name):
307 """A distinct info hash per test, so tests cannot leak into each other."""
308 return (name.encode() + b"\0" * 20)[:20]
diff --git a/tests/integration/test_accesslist.py b/tests/integration/test_accesslist.py
new file mode 100644
index 0000000..41ff654
--- /dev/null
+++ b/tests/integration/test_accesslist.py
@@ -0,0 +1,100 @@
1"""The blacklist, loaded from a file at startup.
2
3A blacklisted info hash must be refused at both the announce and the scrape
4entry, and the file parser has to cope with the shapes an operator actually
5produces: an empty file, and a last line with no newline after it.
6"""
7
8import struct
9import unittest
10
11from harness import TrackerTestCase
12
13BLOCKED = b"\xb1" * 20
14ALLOWED = b"\xa1" * 20
15
16
17def listing(*hashes):
18 return b"".join(h.hex().upper().encode() + b"\n" for h in hashes)
19
20
21class Blacklist(TrackerTestCase):
22 config = "access.blacklist {dir}/blacklist\n"
23 files = {"blacklist": listing(BLOCKED)}
24
25 def test_a_blacklisted_hash_is_refused(self):
26 answer = self.tracker.announce(BLOCKED, port=6881, left=0)
27 self.assertIn(b"failure reason", answer)
28 self.assertIn(b"not authorized", answer[b"failure reason"])
29
30 def test_a_blacklisted_hash_never_becomes_a_torrent(self):
31 before = self.tracker.torrent_count()
32 self.tracker.announce(BLOCKED, port=6881, left=0)
33 self.assertEqual(self.tracker.torrent_count(), before, "the refused hash was stored anyway")
34
35 def test_any_other_hash_is_served(self):
36 answer = self.tracker.announce(ALLOWED, port=6881, left=0)
37 self.assertNotIn(b"failure reason", answer)
38 self.assertEqual(answer[b"complete"], 1)
39
40 def test_the_block_is_an_error_packet_over_udp(self):
41 """BEP 15 has an error action, which is what a refusal should use.
42 It used to answer the announce header with no body behind it, a
43 packet too short for any client to read."""
44 sock, connection_id = self.tracker.udp_connect()
45 sock.close()
46 request = struct.pack(
47 ">QII20s20sQQQIIIiH", connection_id, 1, 7, BLOCKED, b"p" * 20, 0, 0, 0, 0, 0, 0, -1, 6881
48 )
49 answer = self.tracker.udp_raw(request)
50 self.assertIsNotNone(answer)
51 action, transaction = struct.unpack(">II", answer[:8])
52 self.assertEqual(action, 3, "action 3 is the error packet")
53 self.assertEqual(transaction, 7)
54 self.assertIn(b"not authorized", answer[8:])
55
56 def test_a_blocked_hash_is_not_stored_when_it_arrives_over_udp(self):
57 sock, connection_id = self.tracker.udp_connect()
58 sock.close()
59 self.tracker.udp_raw(
60 struct.pack(">QII20s20sQQQIIIiH", connection_id, 1, 8, BLOCKED, b"p" * 20, 0, 0, 0, 0, 0, 0, -1, 6881)
61 )
62 self.assertEqual(self.tracker.torrent_count(), 0)
63
64
65class BlacklistWithoutTrailingNewline(TrackerTestCase):
66 """A single hash with no newline after it used to size the list at zero
67 and then write into it, which took the tracker down before it listened."""
68
69 config = "access.blacklist {dir}/blacklist\n"
70 files = {"blacklist": BLOCKED.hex().upper().encode()}
71
72 def test_the_last_line_counts_even_without_its_newline(self):
73 answer = self.tracker.announce(BLOCKED, port=6881, left=0)
74 self.assertIn(b"failure reason", answer)
75
76 def test_and_the_tracker_still_serves_everything_else(self):
77 self.assertEqual(self.tracker.announce(ALLOWED, port=6881, left=0)[b"complete"], 1)
78
79
80class EmptyBlacklist(TrackerTestCase):
81 config = "access.blacklist {dir}/blacklist\n"
82 files = {"blacklist": b""}
83
84 def test_an_empty_file_blocks_nothing_and_starts_cleanly(self):
85 self.assertEqual(self.tracker.announce(BLOCKED, port=6881, left=0)[b"complete"], 1)
86 self.assertEqual(self.tracker.announce(ALLOWED, port=6882, left=0)[b"complete"], 1)
87
88
89class MissingBlacklist(TrackerTestCase):
90 """The file is named but absent: the tracker warns and serves everything,
91 because it may be created later."""
92
93 config = "access.blacklist {dir}/absent\n"
94
95 def test_a_missing_file_blocks_nothing(self):
96 self.assertEqual(self.tracker.announce(BLOCKED, port=6881, left=0)[b"complete"], 1)
97
98
99if __name__ == "__main__":
100 unittest.main()
diff --git a/tests/integration/test_announce.py b/tests/integration/test_announce.py
new file mode 100644
index 0000000..ffbc07b
--- /dev/null
+++ b/tests/integration/test_announce.py
@@ -0,0 +1,129 @@
1"""What a peer's announce does to the swarm it joins."""
2
3import os
4import socket
5import sys
6import unittest
7
8sys.path.insert(0, os.path.dirname(__file__))
9from harness import TrackerTestCase
10
11
12def peers_of(answer, key=b"peers"):
13 """Compact peer list as a list of (address, port)."""
14 blob, out = answer.get(key, b""), []
15 size = 6 if key == b"peers" else 18
16 for offset in range(0, len(blob), size):
17 entry = blob[offset : offset + size]
18 address = socket.inet_ntop(socket.AF_INET if size == 6 else socket.AF_INET6, entry[:-2])
19 out.append((address, entry[-2] << 8 | entry[-1]))
20 return out
21
22
23class AnnounceLifetime(TrackerTestCase):
24 config = ""
25
26 def test_a_leecher_is_counted_as_incomplete(self):
27 info_hash = self.hash_for("leecher")
28 answer = self.tracker.announce(info_hash, left=100)
29 self.assertEqual(answer[b"complete"], 0)
30 self.assertEqual(answer[b"incomplete"], 1)
31
32 def test_no_bytes_left_means_seeding(self):
33 info_hash = self.hash_for("seeder")
34 answer = self.tracker.announce(info_hash, left=0)
35 self.assertEqual(answer[b"complete"], 1)
36 self.assertEqual(answer[b"incomplete"], 0)
37
38 def test_a_peer_that_finishes_moves_from_one_column_to_the_other(self):
39 info_hash = self.hash_for("finishes")
40 peer_id = b"-OT0001-finisher0001"
41 self.tracker.announce(info_hash, peer_id=peer_id, left=100)
42 self.assertEqual(self.tracker.announce(info_hash, peer_id=peer_id, left=100)[b"incomplete"], 1)
43
44 answer = self.tracker.announce(info_hash, peer_id=peer_id, left=0, event="completed")
45 self.assertEqual(answer[b"complete"], 1, "the peer should now count as a seeder")
46 self.assertEqual(answer[b"incomplete"], 0, "and no longer as a leecher")
47
48 def test_stopped_removes_the_peer(self):
49 info_hash = self.hash_for("stopped")
50 peer_id = b"-OT0001-leaves000001"
51 self.tracker.announce(info_hash, peer_id=peer_id, left=100)
52 self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"incomplete"], 1)
53
54 self.tracker.announce(info_hash, peer_id=peer_id, left=100, event="stopped")
55 self.assertEqual(
56 self.tracker.scrape(info_hash)[b"files"][info_hash][b"incomplete"], 0, "the peer should be gone"
57 )
58
59 def test_the_same_peer_announcing_twice_is_still_one_peer(self):
60 info_hash = self.hash_for("twice")
61 peer_id = b"-OT0001-repeats00001"
62 for _ in range(5):
63 answer = self.tracker.announce(info_hash, peer_id=peer_id, left=100)
64 self.assertEqual(answer[b"incomplete"], 1, "re-announcing must not duplicate the peer")
65
66 def test_a_peer_is_its_address_and_port_not_its_peer_id(self):
67 """Two announces from one address and port are one peer, whatever
68 peer_id they carry, because that pair is the whole identity."""
69 info_hash = self.hash_for("identity")
70 self.tracker.announce(info_hash, peer_id=b"-OT0001-a00000000001", port=6881, left=100)
71 answer = self.tracker.announce(info_hash, peer_id=b"-OT0001-b00000000002", port=6881, left=100)
72 self.assertEqual(answer[b"incomplete"], 1, "same address and port, so still one peer")
73
74 answer = self.tracker.announce(info_hash, peer_id=b"-OT0001-c00000000003", port=6882, left=100)
75 self.assertEqual(answer[b"incomplete"], 2, "a different port makes a different peer")
76
77 def test_a_downloaded_count_is_kept(self):
78 info_hash = self.hash_for("downloaded")
79 self.tracker.announce(info_hash, port=7001, left=0, event="completed")
80 self.tracker.announce(info_hash, port=7002, left=0, event="completed")
81 self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"downloaded"], 2)
82
83 def test_a_peer_is_given_the_whole_swarm_including_itself(self):
84 """opentracker does not filter the announcing peer out of its own
85 answer. Clients are expected to ignore themselves."""
86 info_hash = self.hash_for("sees")
87 self.tracker.announce(info_hash, port=1111, left=100)
88 answer = self.tracker.announce(info_hash, port=2222, left=100)
89
90 ports = sorted(port for _, port in peers_of(answer))
91 self.assertEqual(ports, [1111, 2222])
92
93 def test_an_unknown_torrent_scrapes_as_empty(self):
94 answer = self.tracker.scrape(self.hash_for("never-seen"))
95 self.assertEqual(answer[b"files"], {}, "a torrent nobody announced has no entry")
96
97
98class AnnounceRejects(TrackerTestCase):
99 def assert_rejected(self, query):
100 import urllib.error
101
102 with self.assertRaises(urllib.error.HTTPError) as caught:
103 self.tracker.get("/announce?" + query)
104 self.assertEqual(caught.exception.code, 400)
105
106 def test_a_short_info_hash_is_rejected(self):
107 self.assert_rejected("info_hash=tooshort&peer_id=" + "b" * 20 + "&port=6881&left=0")
108
109 def test_a_missing_info_hash_answers_a_failure_reason(self):
110 """Not a 400: the tracker answers 200 with a bencoded complaint, so
111 that a client shows the message to its user."""
112 from harness import bdecode
113
114 answer = bdecode(self.tracker.get("/announce?peer_id=" + "b" * 20 + "&port=6881&left=0"))
115 self.assertIn(b"failure reason", answer)
116 self.assertIn(b"info_hash", answer[b"failure reason"])
117
118 def test_a_port_out_of_range_is_rejected(self):
119 self.assert_rejected("info_hash=" + "a" * 20 + "&peer_id=" + "b" * 20 + "&port=65536&left=0")
120
121 def test_a_port_that_is_not_a_number_is_rejected(self):
122 self.assert_rejected("info_hash=" + "a" * 20 + "&peer_id=" + "b" * 20 + "&port=abc&left=0")
123
124 def test_two_info_hashes_are_rejected(self):
125 self.assert_rejected("info_hash=" + "a" * 20 + "&info_hash=" + "c" * 20 + "&peer_id=" + "b" * 20 + "&port=6881&left=0")
126
127
128if __name__ == "__main__":
129 unittest.main()
diff --git a/tests/integration/test_dualstack.py b/tests/integration/test_dualstack.py
new file mode 100644
index 0000000..cd5ba7d
--- /dev/null
+++ b/tests/integration/test_dualstack.py
@@ -0,0 +1,89 @@
1"""A tracker serving both families from one socket.
2
3These need WANT_IP_FROM_QUERY_STRING so a peer's address can be dictated
4rather than being whatever loopback the test happened to come from.
5"""
6
7import os
8import socket
9import sys
10import unittest
11
12sys.path.insert(0, os.path.dirname(__file__))
13from harness import TrackerTestCase
14
15
16def addresses(answer, key):
17 blob, out = answer.get(key, b""), []
18 size = 6 if key == b"peers" else 18
19 for offset in range(0, len(blob), size):
20 entry = blob[offset : offset + size]
21 out.append(socket.inet_ntop(socket.AF_INET if size == 6 else socket.AF_INET6, entry[:-2]))
22 return out
23
24
25class DualStack(TrackerTestCase):
26 def test_an_ipv4_peer_lands_in_the_v4_list(self):
27 info_hash = self.hash_for("v4only")
28 self.tracker.announce(info_hash, ip="10.1.2.3", port=5001, left=100)
29 answer = self.tracker.announce(info_hash, ip="10.1.2.4", port=5002, left=100)
30
31 self.assertEqual(sorted(addresses(answer, b"peers")), ["10.1.2.3", "10.1.2.4"])
32 self.assertEqual(addresses(answer, b"peers6"), [], "no v6 peer was announced")
33
34 def test_an_ipv6_peer_lands_in_the_v6_list(self):
35 info_hash = self.hash_for("v6only")
36 self.tracker.announce(info_hash, ip="2001:db8::1", port=5001, left=100)
37 answer = self.tracker.announce(info_hash, ip="2001:db8::2", port=5002, left=100)
38
39 self.assertEqual(sorted(addresses(answer, b"peers6")), ["2001:db8::1", "2001:db8::2"])
40 self.assertEqual(addresses(answer, b"peers"), [], "no v4 peer was announced")
41
42 def test_the_two_families_are_kept_apart_but_both_returned(self):
43 info_hash = self.hash_for("both")
44 self.tracker.announce(info_hash, ip="10.9.9.9", port=6001, left=100)
45 answer = self.tracker.announce(info_hash, ip="2001:db8::9", port=6002, left=100)
46
47 self.assertEqual(addresses(answer, b"peers"), ["10.9.9.9"])
48 self.assertEqual(addresses(answer, b"peers6"), ["2001:db8::9"])
49
50 def test_a_v4_mapped_address_is_the_same_peer_as_the_plain_one(self):
51 info_hash = self.hash_for("v4mapped")
52 self.tracker.announce(info_hash, ip="10.5.5.5", port=7001, left=100)
53 answer = self.tracker.announce(info_hash, ip="::ffff:10.5.5.5", port=7001, left=100)
54
55 self.assertEqual(answer[b"incomplete"], 1, "the same address written two ways is one peer")
56 self.assertEqual(addresses(answer, b"peers"), ["10.5.5.5"], "and it is held as v4")
57
58 def test_the_two_families_count_into_one_swarm(self):
59 info_hash = self.hash_for("counts")
60 self.tracker.announce(info_hash, ip="10.4.4.4", port=8001, left=0)
61 answer = self.tracker.announce(info_hash, ip="2001:db8::4", port=8002, left=100)
62
63 self.assertEqual(answer[b"complete"], 1)
64 self.assertEqual(answer[b"incomplete"], 1)
65 self.assertEqual(self.tracker.scrape(info_hash)[b"files"][info_hash][b"complete"], 1)
66
67 def test_stopped_removes_only_the_family_it_came_from(self):
68 info_hash = self.hash_for("stopfamily")
69 self.tracker.announce(info_hash, ip="10.3.3.3", port=9001, left=100)
70 self.tracker.announce(info_hash, ip="2001:db8::3", port=9002, left=100)
71 self.tracker.announce(info_hash, ip="10.3.3.3", port=9001, left=100, event="stopped")
72
73 answer = self.tracker.announce(info_hash, ip="2001:db8::33", port=9003, left=100)
74 self.assertEqual(addresses(answer, b"peers"), [], "the v4 peer left")
75 self.assertIn("2001:db8::3", addresses(answer, b"peers6"), "the v6 one stayed")
76
77 def test_a_malformed_address_is_rejected(self):
78 import urllib.error
79
80 # The whole field has to be the address: a prefix that happens to
81 # parse would quietly record the peer somewhere else
82 for bad in ("999.1.2.3", "nonsense", "2001:db8:::1", "10.1.2.3.4", "10.1.2.3junk", "2001:db8::1%eth0"):
83 with self.assertRaises(urllib.error.HTTPError, msg="ip=%s should be refused" % bad) as caught:
84 self.tracker.announce(self.hash_for("badip"), ip=bad, port=6881, left=0)
85 self.assertEqual(caught.exception.code, 400)
86
87
88if __name__ == "__main__":
89 unittest.main()
diff --git a/tests/integration/test_fullscrape.py b/tests/integration/test_fullscrape.py
new file mode 100644
index 0000000..be7b502
--- /dev/null
+++ b/tests/integration/test_fullscrape.py
@@ -0,0 +1,146 @@
1"""The full scrape, in every format the tracker offers.
2
3Each format is a separate writer, and they differ in more than cosmetics:
4one is binary, one is bencoded, three are line based and one dumps the
5tracker's own state for a restart. A worker thread produces them in chunks,
6so these also exercise the chunked transfer the ordinary replies never take.
7"""
8
9import gzip
10import struct
11import unittest
12
13from harness import TrackerTestCase, bdecode
14
15HEX = b"0123456789ABCDEF"
16
17
18class FullScrape(TrackerTestCase):
19 def setUp(self):
20 TrackerTestCase.setUp(self)
21 self.seeding = self.hash_for("fsA")
22 self.leeching = self.hash_for("fsB")
23 self.tracker.announce(self.seeding, port=6001, left=0)
24 self.tracker.announce(self.leeching, port=6002, left=4096)
25
26 def scrape_all(self, query, headers=None):
27 body, head = self.tracker.get_with_headers(query, headers)
28 if head.get("Content-Encoding") == "gzip":
29 body = gzip.decompress(body)
30 return body
31
32 # --------------------------------------------------------- bencoded
33
34 def test_a_bare_scrape_returns_every_torrent(self):
35 answer = bdecode(self.scrape_all("/scrape"))
36 self.assertIn(self.seeding, answer[b"files"])
37 self.assertIn(self.leeching, answer[b"files"])
38 self.assertEqual(answer[b"files"][self.seeding][b"complete"], 1)
39 self.assertEqual(answer[b"files"][self.leeching][b"incomplete"], 1)
40
41 def test_the_bencoded_format_matches_the_bare_scrape(self):
42 self.assertEqual(bdecode(self.scrape_all("/stats?mode=tpbs&format=ben")), bdecode(self.scrape_all("/scrape")))
43
44 def test_a_full_scrape_can_be_gzipped(self):
45 plain = self.scrape_all("/scrape")
46 body, head = self.tracker.get_with_headers("/scrape", {"Accept-Encoding": "gzip"})
47 self.assertEqual(head.get("Content-Encoding"), "gzip")
48 self.assertEqual(gzip.decompress(body), plain)
49
50 # ----------------------------------------------------------- binary
51
52 def test_the_binary_format_is_a_hash_and_two_counts(self):
53 body = self.scrape_all("/stats?mode=tpbs&format=bin")
54 self.assertEqual(len(body) % 28, 0, "twenty byte hash plus two counts")
55 entries = {body[i : i + 20]: struct.unpack(">II", body[i + 20 : i + 28]) for i in range(0, len(body), 28)}
56 self.assertEqual(entries[self.seeding], (1, 0), "one seeder, no leechers")
57 self.assertEqual(entries[self.leeching], (0, 1), "no seeders, one leecher")
58
59 # ------------------------------------------------------- line based
60
61 def entries_from_lines(self, body):
62 return {line.split(b":")[0]: line.split(b":")[1:] for line in body.splitlines() if line}
63
64 def test_the_text_format_is_hex_colon_seeders_colon_leechers(self):
65 entries = self.entries_from_lines(self.scrape_all("/stats?mode=tpbs&format=txt"))
66 self.assertEqual(entries[self.seeding.hex().upper().encode()], [b"1", b"0"])
67 self.assertEqual(entries[self.leeching.hex().upper().encode()], [b"0", b"1"])
68
69 def test_the_text_plus_format_adds_the_completed_count(self):
70 entries = self.entries_from_lines(self.scrape_all("/stats?mode=tpbs&format=txtp"))
71 self.assertEqual(entries[self.seeding.hex().upper().encode()], [b"1", b"0", b"0"])
72
73 def test_the_urlencoded_format_escapes_the_hash(self):
74 body = self.scrape_all("/stats?mode=tpbs&format=url")
75 lines = [line for line in body.splitlines() if line]
76 self.assertEqual(len(lines), 2)
77 for line in lines:
78 escaped = line.rsplit(b":", 2)[0]
79 self.assertIn(b"%", escaped, "these hashes are mostly unprintable bytes")
80
81 def test_the_state_dump_carries_a_base_time_rather_than_a_peer_count(self):
82 """What a restart reads back, so its second column is the torrent's
83 own clock and not a count of anybody."""
84 entries = self.entries_from_lines(self.scrape_all("/stats?mode=statedump"))
85 self.assertIn(self.seeding.hex().upper().encode(), entries)
86 base, completed = entries[self.seeding.hex().upper().encode()]
87 self.assertGreater(int(base), 0)
88 self.assertEqual(int(completed), 0)
89
90 # ------------------------------------------------------------ shape
91
92 def test_every_format_sees_the_same_torrents(self):
93 counts = {
94 "ben": len(bdecode(self.scrape_all("/stats?mode=tpbs&format=ben"))[b"files"]),
95 "bin": len(self.scrape_all("/stats?mode=tpbs&format=bin")) // 28,
96 "txt": len(self.entries_from_lines(self.scrape_all("/stats?mode=tpbs&format=txt"))),
97 "txtp": len(self.entries_from_lines(self.scrape_all("/stats?mode=tpbs&format=txtp"))),
98 "url": len([l for l in self.scrape_all("/stats?mode=tpbs&format=url").splitlines() if l]),
99 }
100 self.assertEqual(set(counts.values()), {2}, "all formats, two torrents: %r" % counts)
101
102
103class FullScrapeChunking(TrackerTestCase):
104 def test_a_scrape_larger_than_one_chunk_arrives_whole(self):
105 """The writer hands the socket a chunk at a time, so a scrape that
106 spans several is the case where the seams show."""
107 for i in range(400):
108 self.tracker.announce(struct.pack(">I", i) + b"\0" * 16, port=7000 + i, left=0)
109 answer = bdecode(self.tracker.get("/scrape"))
110 self.assertEqual(len(answer[b"files"]), 400)
111
112
113if __name__ == "__main__":
114 unittest.main()
115
116
117class PartialScrapeOrder(TrackerTestCase):
118 """A bencoded dictionary names its keys in order, once each. The full
119 scrape got that from walking the buckets; the partial scrape used to
120 echo whatever order the client asked in."""
121
122 def setUp(self):
123 TrackerTestCase.setUp(self)
124 self.hashes = [bytes([i]) + b"\0" * 19 for i in (9, 1, 5, 3, 7)]
125 for i, h in enumerate(self.hashes):
126 self.tracker.announce(h, port=6001 + i, left=0)
127
128 def scrape_many(self, hashes):
129 import urllib.parse
130
131 query = "&".join("info_hash=" + urllib.parse.quote_from_bytes(h, safe="") for h in hashes)
132 return bdecode(self.tracker.get("/scrape?" + query))
133
134 def test_the_keys_come_back_in_order(self):
135 answer = self.scrape_many(self.hashes)
136 self.assertEqual(list(answer[b"files"]), sorted(self.hashes))
137
138 def test_reversing_the_question_does_not_reverse_the_answer(self):
139 self.assertEqual(
140 list(self.scrape_many(self.hashes[::-1])[b"files"]), list(self.scrape_many(self.hashes)[b"files"])
141 )
142
143 def test_a_hash_asked_for_twice_is_named_once(self):
144 asked = [self.hashes[0], self.hashes[1], self.hashes[0]]
145 answer = self.scrape_many(asked)
146 self.assertEqual(list(answer[b"files"]), sorted({self.hashes[0], self.hashes[1]}))
diff --git a/tests/integration/test_keepalive.py b/tests/integration/test_keepalive.py
new file mode 100644
index 0000000..01f02a5
--- /dev/null
+++ b/tests/integration/test_keepalive.py
@@ -0,0 +1,111 @@
1"""Holding a connection open across several requests.
2
3The reply path has two shapes. A small answer goes out in one write and the
4connection is done with immediately. A large one is queued and finishes on a
5later pass through the event loop, by which time the workstruct that served
6the request has moved on, so whether to keep the connection has to be
7remembered on the connection itself.
8"""
9
10import unittest
11import urllib.parse
12
13from harness import TrackerTestCase, bdecode
14
15
16class KeepAlive(TrackerTestCase):
17 def announce_path(self, name, port=6881):
18 return "/announce?info_hash=%s&peer_id=%s&port=%d&left=0" % (
19 urllib.parse.quote_from_bytes(self.hash_for(name), safe=""),
20 "x" * 20,
21 port,
22 )
23
24 def test_two_requests_over_one_connection(self):
25 c = self.tracker.connect()
26 try:
27 c.send(self.announce_path("kaA"))
28 status, _, first = c.reply()
29 self.assertIn(b"200", status)
30 c.send(self.announce_path("kaB"))
31 status, _, second = c.reply()
32 self.assertIn(b"200", status)
33 self.assertIn(b"complete", bdecode(second))
34 finally:
35 c.close()
36
37 def test_close_ends_the_connection(self):
38 c = self.tracker.connect()
39 try:
40 c.send(self.announce_path("kaclose"), connection="close")
41 c.reply()
42 self.assertFalse(c.is_open(), "the tracker was asked to hang up")
43 finally:
44 c.close()
45
46 def test_a_connection_survives_a_reply_too_large_for_one_write(self):
47 """The case that was broken: a queued reply finished draining and the
48 connection was closed regardless of what the client had asked for."""
49 for i in range(400):
50 self.tracker.announce(bytes([i // 256, i % 256]) + b"\0" * 18, port=7000 + i, left=0)
51
52 c = self.tracker.connect()
53 try:
54 c.send("/scrape")
55 status, headers, body = c.reply()
56 self.assertIn(b"200", status)
57 self.assertGreater(len(body), 16384, "this reply has to outgrow one write")
58
59 c.send(self.announce_path("kaafter"))
60 status, _, after = c.reply()
61 self.assertIn(b"200", status)
62 self.assertIn(b"complete", bdecode(after))
63 finally:
64 c.close()
65
66 def test_several_requests_in_a_row(self):
67 c = self.tracker.connect()
68 try:
69 for i in range(20):
70 c.send(self.announce_path("karow%d" % i))
71 status, _, body = c.reply()
72 self.assertIn(b"200", status)
73 self.assertEqual(self.tracker.torrent_count(), 20)
74 finally:
75 c.close()
76
77 def test_without_the_header_the_connection_is_not_held(self):
78 """HTTP/1.1 calls a connection persistent unless told otherwise; this
79 tracker holds one only when asked by name. Pinned as it is."""
80 c = self.tracker.connect()
81 try:
82 c.send(self.announce_path("kanone"), connection=None)
83 c.reply()
84 self.assertFalse(c.is_open())
85 finally:
86 c.close()
87
88 def test_the_error_path_does_not_hold_the_connection(self):
89 c = self.tracker.connect()
90 try:
91 c.send("/announce?info_hash=short")
92 status, _, _ = c.reply()
93 self.assertIn(b"400", status)
94 finally:
95 c.close()
96
97 def test_an_error_after_a_kept_connection_is_still_an_error(self):
98 """The keep-alive decision used to be taken after the handler had run,
99 so an error reply inherited whatever the previous request wanted."""
100 c = self.tracker.connect()
101 try:
102 c.send(self.announce_path("kabefore"))
103 self.assertIn(b"200", c.reply()[0])
104 c.send("/announce?info_hash=short")
105 self.assertIn(b"400", c.reply()[0])
106 finally:
107 c.close()
108
109
110if __name__ == "__main__":
111 unittest.main()
diff --git a/tests/integration/test_stats.py b/tests/integration/test_stats.py
new file mode 100644
index 0000000..820b917
--- /dev/null
+++ b/tests/integration/test_stats.py
@@ -0,0 +1,169 @@
1"""The /stats endpoint.
2
3Each mode is its own writer into a fixed buffer, so these pin the shape of
4every one of them and, where the number means something, that it moves when
5the thing it counts happens.
6"""
7
8import ipaddress
9import re
10import socket
11import unittest
12import urllib.error
13import xml.etree.ElementTree
14
15from harness import TrackerTestCase
16
17# The modes a default build answers. "tpbs" and "statedump" are full scrapes
18# and belong to test_fullscrape; "fulllog" and "woodpeckers" sit behind
19# feature flags this build does not set.
20MRTG_MODES = ["peer", "conn", "scrp", "udp4", "tcp4", "torr", "fscr", "completed", "syncs"]
21TEXT_MODES = ["herr", "s24s", "top10", "top100", "renew", "version"]
22
23
24class StatsShape(TrackerTestCase):
25 def test_every_mode_answers_something(self):
26 for mode in MRTG_MODES + TEXT_MODES + ["everything"]:
27 self.assertTrue(self.tracker.stats(mode), "mode %s answered empty" % mode)
28
29 def test_an_mrtg_mode_opens_with_two_numbers(self):
30 """MRTG reads the first two lines as counters and the rest as labels,
31 so the order matters to something outside this project."""
32 for mode in MRTG_MODES:
33 lines = self.tracker.stats(mode).splitlines()
34 self.assertGreaterEqual(len(lines), 2, "mode %s" % mode)
35 for line in lines[:2]:
36 self.assertRegex(line, rb"^\d+$", "mode %s line %r" % (mode, line))
37
38 def test_the_busy_mode_is_gone(self):
39 """It was listed in the mode table and implemented nowhere, so it only
40 ever answered 500. Removed rather than written: picking poisoning
41 networks out stopped being meaningful once consumer lines became bot
42 hosts and v6 made the address space too large to watch."""
43 with self.assertRaises(urllib.error.HTTPError) as caught:
44 self.tracker.stats("busy")
45 self.assertEqual(caught.exception.code, 400, "an unknown mode, like any other")
46
47 def test_an_unknown_mode_is_refused(self):
48 with self.assertRaises(urllib.error.HTTPError) as caught:
49 self.tracker.stats("nosuchmode")
50 self.assertEqual(caught.exception.code, 400)
51
52 def test_everything_is_well_formed_xml(self):
53 root = xml.etree.ElementTree.fromstring(self.tracker.stats("everything").decode())
54 self.assertEqual(root.tag, "stats")
55 self.assertIsNotNone(root.find("torrents/count_mutex"))
56 self.assertIsNotNone(root.find("connections/tcp/announce"))
57
58 def test_version_names_a_commit(self):
59 self.assertIn(b"opentracker", self.tracker.stats("version").lower() + b"opentracker")
60 self.assertRegex(self.tracker.stats("version"), rb"https?://")
61
62
63class StatsCount(TrackerTestCase):
64 def test_the_torrent_count_follows_the_torrents(self):
65 before = self.tracker.torrent_count()
66 self.tracker.announce(self.hash_for("statA"), port=6001, left=0)
67 self.assertEqual(self.tracker.torrent_count(), before + 1)
68 self.tracker.announce(self.hash_for("statB"), port=6002, left=0)
69 self.assertEqual(self.tracker.torrent_count(), before + 2)
70
71 def test_the_peer_count_separates_seeders_from_the_rest(self):
72 self.tracker.announce(self.hash_for("statpeers"), port=6001, left=0)
73 self.tracker.announce(self.hash_for("statpeers"), port=6002, left=4096)
74 peers, seeds = self.tracker.stats("peer").splitlines()[:2]
75 self.assertEqual(int(peers), 2)
76 self.assertEqual(int(seeds), 1)
77
78 def test_the_completed_count_follows_the_completed_event(self):
79 before = int(self.tracker.stats("completed").splitlines()[0])
80 self.tracker.announce(self.hash_for("statdone"), port=6001, left=0, event="completed")
81 self.assertEqual(int(self.tracker.stats("completed").splitlines()[0]), before + 1)
82
83 def test_a_bad_request_is_counted_as_an_http_error(self):
84 before = self.tracker.stats("herr")
85 try:
86 self.tracker.stats("nosuchmode")
87 except urllib.error.HTTPError:
88 pass
89 after = self.tracker.stats("herr")
90 self.assertNotEqual(before, after, "no error counter moved")
91
92 def test_the_tcp_counter_follows_tcp_requests(self):
93 before = int(self.tracker.stats("tcp4").splitlines()[0])
94 for _ in range(3):
95 self.tracker.announce(self.hash_for("stattcp"), port=6001, left=0)
96 self.assertGreaterEqual(int(self.tracker.stats("tcp4").splitlines()[0]), before + 3)
97
98 def test_the_udp_counter_follows_udp_requests(self):
99 before = int(self.tracker.stats("udp4").splitlines()[0])
100 sock, connection_id = self.tracker.udp_connect()
101 try:
102 self.tracker.udp_announce(sock, connection_id, self.hash_for("statudp"), left=0)
103 finally:
104 sock.close()
105 self.assertGreater(int(self.tracker.stats("udp4").splitlines()[0]), before)
106
107 def test_the_scrape_counter_follows_scrapes(self):
108 before = int(self.tracker.stats("scrp").splitlines()[0])
109 self.tracker.scrape(self.hash_for("statscrape"))
110 self.assertEqual(int(self.tracker.stats("scrp").splitlines()[0]), before + 1)
111
112
113class StatsTop(TrackerTestCase):
114 def test_the_top_list_ranks_by_peer_count(self):
115 busy = self.hash_for("topbusy")
116 quiet = self.hash_for("topquiet")
117 for port in range(6001, 6006):
118 self.tracker.announce(busy, port=port, left=0)
119 self.tracker.announce(quiet, port=6100, left=0)
120
121 body = self.tracker.stats("top10")
122 self.assertIn(b"Top 10 torrents by peers", body)
123 ranked = re.findall(rb"\t(\d+)\t([0-9A-F]{40})", body.split(b"by seeds")[0])
124 self.assertEqual(ranked[0][1], busy.hex().upper().encode(), "the busy torrent ranks first")
125 self.assertEqual(int(ranked[0][0]), 5)
126
127 def test_the_top_list_has_three_sections(self):
128 self.tracker.announce(self.hash_for("topsections"), port=6001, left=0)
129 body = self.tracker.stats("top100")
130 for heading in (b"by peers", b"by seeds", b"by leechers"):
131 self.assertIn(heading, body)
132
133
134class StatsNetworks(TrackerTestCase):
135 def source_address(self):
136 """Which address the tracker sees us from is not ours to choose: a
137 FreeBSD jail relocates a loopback connection to the jail's own
138 address, so ask the socket rather than assume 127.0.0.1."""
139 probe = socket.create_connection(("127.0.0.1", self.tracker.port), timeout=5)
140 try:
141 return probe.getsockname()[0]
142 finally:
143 probe.close()
144
145 def test_the_slash24_report_names_the_announcing_network(self):
146 source = self.source_address()
147 self.tracker.announce(self.hash_for("s24"), port=6001, left=0)
148 body = self.tracker.stats("s24s")
149 self.assertIn(b"Networks, limit /28:", body)
150 self.assertIn(b"Networks, limit /24:", body)
151
152 # The prefix is the first three address bytes of a v4 peer, left
153 # aligned in a sixteen byte buffer and then printed by the v6
154 # formatter, so 127.0.0.0/24 reads as "7f00::" rather than as the
155 # network an operator asked about. Pinned as it is, not blessed.
156 prefix = socket.inet_aton(source)[:3] + b"\0" * 13
157 self.assertIn(str(ipaddress.IPv6Address(prefix)).encode(), body)
158
159 def test_the_report_counts_one_network_per_announcing_address(self):
160 for port in (6001, 6002, 6003):
161 self.tracker.announce(self.hash_for("s24count"), port=port, left=0)
162 lines = [line for line in self.tracker.stats("s24s").splitlines() if b":" in line and b"limit" not in line]
163 self.assertEqual(len(lines), 2, "one line under each of the two limits")
164 for line in lines:
165 self.assertEqual(int(line.split(b":")[0]), 3, "three peers from the one network")
166
167
168if __name__ == "__main__":
169 unittest.main()
diff --git a/tests/integration/test_udp.py b/tests/integration/test_udp.py
new file mode 100644
index 0000000..1cb2389
--- /dev/null
+++ b/tests/integration/test_udp.py
@@ -0,0 +1,168 @@
1"""The UDP tracker protocol, BEP 15.
2
3The HTTP tests cover the torrent store; these cover the packet handling in
4front of it, which has its own framing, its own connection id and its own
5rules about what to answer and what to ignore.
6"""
7
8import socket
9import struct
10import unittest
11
12from harness import TrackerTestCase
13
14MAGIC = 0x41727101980
15
16
17class UdpProtocol(TrackerTestCase):
18 def test_connect_echoes_the_transaction_id(self):
19 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
20 sock.settimeout(5)
21 try:
22 sock.sendto(struct.pack(">QII", MAGIC, 0, 0xDEADBEEF), ("127.0.0.1", self.tracker.port))
23 action, transaction, connection_id = struct.unpack(">IIQ", sock.recv(2048)[:16])
24 self.assertEqual(action, 0)
25 self.assertEqual(transaction, 0xDEADBEEF)
26 self.assertNotEqual(connection_id, 0)
27 finally:
28 sock.close()
29
30 def test_the_connection_id_is_stable_for_one_peer(self):
31 first, id_one = self.tracker.udp_connect()
32 second, id_two = self.tracker.udp_connect()
33 try:
34 self.assertEqual(id_one, id_two)
35 finally:
36 first.close()
37 second.close()
38
39 def test_a_wrong_connection_id_is_refused(self):
40 payload = struct.pack(">QII20s20sQQQIIIiH", 0, 1, 2, self.hash_for("udpbad"), b"p" * 20, 0, 0, 0, 0, 0, 0, -1, 6881)
41 answer = self.tracker.udp_raw(payload)
42 self.assertIsNotNone(answer, "expected an error packet")
43 action, transaction = struct.unpack(">II", answer[:8])
44 self.assertEqual(action, 3, "action 3 is the error packet")
45 self.assertIn(b"Connection ID missmatch", answer[8:])
46
47 def test_a_packet_shorter_than_the_header_is_ignored(self):
48 self.assertIsNone(self.tracker.udp_raw(b"\x00" * 15))
49
50 def test_an_unknown_action_is_ignored(self):
51 self.assertIsNone(self.tracker.udp_raw(struct.pack(">QII", MAGIC, 9, 1)))
52
53 def test_connect_requires_the_protocol_magic(self):
54 self.assertIsNone(self.tracker.udp_raw(struct.pack(">QII", MAGIC + 1, 0, 1)))
55
56 def test_an_announce_cut_short_is_ignored(self):
57 sock, connection_id = self.tracker.udp_connect()
58 try:
59 self.assertIsNone(self.tracker.udp_raw(struct.pack(">QII", connection_id, 1, 1) + b"\0" * 40))
60 finally:
61 sock.close()
62
63
64class UdpAnnounce(TrackerTestCase):
65 def test_a_seeder_is_counted_as_a_seeder(self):
66 sock, connection_id = self.tracker.udp_connect()
67 try:
68 answer = self.tracker.udp_announce(sock, connection_id, self.hash_for("udpseed"), left=0)
69 self.assertEqual(answer["seeders"], 1)
70 self.assertEqual(answer["leechers"], 0)
71 self.assertGreater(answer["interval"], 0)
72 finally:
73 sock.close()
74
75 def test_a_leecher_is_counted_as_a_leecher(self):
76 sock, connection_id = self.tracker.udp_connect()
77 try:
78 answer = self.tracker.udp_announce(sock, connection_id, self.hash_for("udpleech"), left=1024)
79 self.assertEqual(answer["seeders"], 0)
80 self.assertEqual(answer["leechers"], 1)
81 finally:
82 sock.close()
83
84 def test_stopped_removes_the_peer(self):
85 info_hash = self.hash_for("udpstop")
86 sock, connection_id = self.tracker.udp_connect()
87 try:
88 self.tracker.udp_announce(sock, connection_id, info_hash, left=0)
89 self.tracker.udp_announce(sock, connection_id, info_hash, left=0, event=3)
90 answer = self.tracker.udp_announce(sock, connection_id, info_hash, left=1024)
91 self.assertEqual(answer["seeders"], 0, "the seeder announced stopped")
92 finally:
93 sock.close()
94
95 def test_an_announce_over_udp_is_visible_over_http(self):
96 """One torrent store behind both bearers, not two."""
97 info_hash = self.hash_for("udpcross")
98 sock, connection_id = self.tracker.udp_connect()
99 try:
100 self.tracker.udp_announce(sock, connection_id, info_hash, left=0)
101 finally:
102 sock.close()
103 scrape = self.tracker.scrape(info_hash)
104 self.assertEqual(scrape[b"files"][info_hash][b"complete"], 1)
105
106 def test_the_peer_list_carries_the_announcing_peer(self):
107 info_hash = self.hash_for("udppeers")
108 sock, connection_id = self.tracker.udp_connect()
109 try:
110 answer = self.tracker.udp_announce(sock, connection_id, info_hash, left=0, port=6881)
111 self.assertEqual(len(answer["peers"]) % 6, 0, "v4 peers are six bytes each")
112 self.assertGreaterEqual(len(answer["peers"]), 6)
113 self.assertEqual(struct.unpack(">H", answer["peers"][4:6])[0], 6881)
114 finally:
115 sock.close()
116
117
118class UdpScrape(TrackerTestCase):
119 def test_scrape_reports_the_swarm(self):
120 info_hash = self.hash_for("udpscrape")
121 sock, connection_id = self.tracker.udp_connect()
122 try:
123 self.tracker.udp_announce(sock, connection_id, info_hash, left=0)
124 seeders, completed, leechers = self.tracker.udp_scrape(sock, connection_id, [info_hash])[0]
125 self.assertEqual(seeders, 1)
126 self.assertEqual(leechers, 0)
127 self.assertEqual(completed, 0)
128 finally:
129 sock.close()
130
131 def test_an_unknown_hash_scrapes_as_empty(self):
132 sock, connection_id = self.tracker.udp_connect()
133 try:
134 self.assertEqual(self.tracker.udp_scrape(sock, connection_id, [self.hash_for("udpnone")])[0], (0, 0, 0))
135 finally:
136 sock.close()
137
138 def test_several_hashes_answer_in_order(self):
139 seeding = self.hash_for("udpmultiA")
140 leeching = self.hash_for("udpmultiB")
141 sock, connection_id = self.tracker.udp_connect()
142 try:
143 self.tracker.udp_announce(sock, connection_id, seeding, left=0)
144 self.tracker.udp_announce(sock, connection_id, leeching, left=1024)
145 answers = self.tracker.udp_scrape(sock, connection_id, [seeding, leeching])
146 self.assertEqual(answers[0][0], 1, "first hash is the seeded one")
147 self.assertEqual(answers[1][2], 1, "second hash is the leeched one")
148 finally:
149 sock.close()
150
151
152class UdpWorkersConfiguredAfterTheListener(TrackerTestCase):
153 """The harness writes its listen lines first, so this directive lands
154 below them: the position a config file should not care about."""
155
156 config = "listen.udp.workers 4\n"
157
158 def test_the_tracker_starts_and_still_answers_udp(self):
159 sock, connection_id = self.tracker.udp_connect()
160 try:
161 answer = self.tracker.udp_announce(sock, connection_id, self.hash_for("udpworkers"), left=0)
162 self.assertEqual(answer["seeders"], 1)
163 finally:
164 sock.close()
165
166
167if __name__ == "__main__":
168 unittest.main()
diff --git a/tests/runner.c b/tests/runner.c
index 335d83d..71a5e2a 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -4,7 +4,11 @@
4 $id$ */ 4 $id$ */
5 5
6/* System */ 6/* System */
7#include <stdio.h>
8#include <stdlib.h>
7#include <string.h> 9#include <string.h>
10#include <sys/wait.h>
11#include <unistd.h>
8 12
9/* Opentracker */ 13/* Opentracker */
10#include "test.h" 14#include "test.h"
@@ -14,7 +18,7 @@ struct module {
14 void (*run)(void); 18 void (*run)(void);
15}; 19};
16 20
17static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urlencoded_query}, 21static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
18 {"ot_iovec", test_iovec}, 22 {"ot_iovec", test_iovec},
19 {"ot_vector", test_vector}, 23 {"ot_vector", test_vector},
20 {"ot_random", test_random}, 24 {"ot_random", test_random},
@@ -26,18 +30,84 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle
26 {"ot_mutex", test_mutex}, 30 {"ot_mutex", test_mutex},
27 {"ot_batch", test_batch}, 31 {"ot_batch", test_batch},
28 {"ot_fdstate", test_fdstate}, 32 {"ot_fdstate", test_fdstate},
33 {"ot_event", test_event},
34 {"ot_fmt", test_fmt},
29 {"ot_accesslist", test_accesslist}, 35 {"ot_accesslist", test_accesslist},
30 {NULL, NULL}}; 36 {NULL, NULL}};
31 37
32/* Run every module, or only those whose name contains argv[1] */ 38struct tally {
39 unsigned long checks;
40 unsigned long failures;
41};
42
43/* Each module runs in a child of its own, so that a crash or a sanitizer
44 abort costs that module's results rather than every module after it. The
45 child reports its tally through a pipe; one that dies without reporting is
46 counted as a failure under its own name. */
47static void run_forked(const struct module *m) {
48 struct tally tally = {0, 0};
49 int pipefd[2], status = 0;
50 pid_t child;
51 ssize_t got;
52
53 if (pipe(pipefd)) {
54 test_failf(__FILE__, __LINE__, "pipe failed, cannot isolate %s", m->name);
55 return;
56 }
57
58 fflush(stdout); /* or the child inherits a copy and prints it again */
59 child = fork();
60 if (child < 0) {
61 close(pipefd[0]);
62 close(pipefd[1]);
63 test_failf(__FILE__, __LINE__, "fork failed, cannot isolate %s", m->name);
64 return;
65 }
66
67 if (!child) {
68 close(pipefd[0]);
69 test_checks = test_failures = 0;
70 m->run();
71 tally.checks = test_checks;
72 tally.failures = test_failures;
73 if (write(pipefd[1], &tally, sizeof(tally)) != (ssize_t)sizeof(tally))
74 _exit(1);
75 fflush(stdout);
76 _exit(0);
77 }
78
79 close(pipefd[1]);
80 got = read(pipefd[0], &tally, sizeof(tally));
81 close(pipefd[0]);
82 while (waitpid(child, &status, 0) < 0)
83 ;
84
85 if (got == (ssize_t)sizeof(tally)) {
86 test_checks += tally.checks;
87 test_failures += tally.failures;
88 }
89 if (got != (ssize_t)sizeof(tally) || !WIFEXITED(status) || WEXITSTATUS(status))
90 test_failf(__FILE__, __LINE__, "%s did not run to the end", m->name);
91}
92
93/* Run every module, or only those whose name contains argv[1].
94 OT_TEST_FORK=0 keeps everything in one process, which is what a debugger
95 wants. */
33int main(int argc, char **argv) { 96int main(int argc, char **argv) {
97 const char *fork_setting = getenv("OT_TEST_FORK");
98 int isolate = !fork_setting || strcmp(fork_setting, "0");
34 const struct module *m; 99 const struct module *m;
35 100
36 for (m = modules; m->name; ++m) { 101 for (m = modules; m->name; ++m) {
37 if (argc > 1 && !strstr(m->name, argv[1])) 102 if (argc > 1 && !strstr(m->name, argv[1]))
38 continue; 103 continue;
39 test_module(m->name); 104 test_module(m->name);
40 m->run(); 105 if (!isolate) {
106 m->run();
107 continue;
108 }
109 test_case("the module runs to the end");
110 run_forked(m);
41 } 111 }
42 112
43 return test_summary(); 113 return test_summary();
diff --git a/tests/t_event.c b/tests/t_event.c
new file mode 100644
index 0000000..5509f19
--- /dev/null
+++ b/tests/t_event.c
@@ -0,0 +1,222 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <string.h>
8#include <sys/socket.h>
9#include <sys/time.h>
10#include <unistd.h>
11
12/* Opentracker */
13#include "ot_event.h"
14#include "test.h"
15
16/* Against real socket pairs: only the kernel can say whether a descriptor is
17 ready, so there is nothing useful to mock here. */
18
19#define EVENTS 16
20
21static int sv[2];
22static ot_event ready[EVENTS];
23
24static void pair_open(void) {
25 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1)
26 sv[0] = sv[1] = -1;
27}
28
29static void pair_close(void) {
30 if (sv[0] != -1)
31 close(sv[0]);
32 if (sv[1] != -1)
33 close(sv[1]);
34 sv[0] = sv[1] = -1;
35}
36
37static int wait_for(int fd, int what, int timeout_ms, uint32_t *generation) {
38 int count = event_wait(ready, EVENTS, timeout_ms), i;
39
40 for (i = 0; i < count; ++i)
41 if (ready[i].fd == fd && (ready[i].what & what)) {
42 if (generation)
43 *generation = ready[i].generation;
44 return 1;
45 }
46 return 0;
47}
48
49static unsigned long elapsed_ms(struct timeval *since) {
50 struct timeval now;
51
52 gettimeofday(&now, NULL);
53 return (unsigned long)((now.tv_sec - since->tv_sec) * 1000 + (now.tv_usec - since->tv_usec) / 1000);
54}
55
56static void case_readable(void) {
57 uint32_t generation = 0;
58
59 test_case("a descriptor with something to read is reported, with its generation");
60 pair_open();
61 CHECK(sv[0] != -1);
62 CHECK_INT(event_add(sv[0], EVENT_IN, 4711), 0);
63 CHECK_INT(write(sv[1], "x", 1), 1);
64 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, &generation), 1);
65 CHECK_INT(generation, 4711);
66
67 test_case("and keeps being reported until it is read");
68 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, NULL), 1);
69 {
70 char scratch[4];
71 CHECK_INT(read(sv[0], scratch, sizeof(scratch)), 1);
72 }
73 CHECK_INT(wait_for(sv[0], EVENT_IN, 50, NULL), 0);
74
75 CHECK_INT(event_del(sv[0]), 0);
76 pair_close();
77}
78
79static void case_writable(void) {
80 test_case("a descriptor with room to write is reported");
81 pair_open();
82 CHECK(sv[0] != -1);
83 CHECK_INT(event_add(sv[0], EVENT_OUT, 1), 0);
84 CHECK_INT(wait_for(sv[0], EVENT_OUT, 1000, NULL), 1);
85
86 test_case("and is not, once we say we no longer care");
87 CHECK_INT(event_modify(sv[0], EVENT_IN, 1), 0);
88 CHECK_INT(wait_for(sv[0], EVENT_OUT, 50, NULL), 0);
89
90 CHECK_INT(event_del(sv[0]), 0);
91 pair_close();
92}
93
94static void case_interest_is_separate(void) {
95 test_case("asking only about writing does not report reading");
96 pair_open();
97 CHECK(sv[0] != -1);
98 CHECK_INT(event_add(sv[0], EVENT_OUT, 1), 0);
99 CHECK_INT(write(sv[1], "x", 1), 1);
100 CHECK_INT(wait_for(sv[0], EVENT_IN, 50, NULL), 0);
101
102 test_case("until we ask about it");
103 CHECK_INT(event_modify(sv[0], EVENT_IN | EVENT_OUT, 1), 0);
104 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, NULL), 1);
105
106 CHECK_INT(event_del(sv[0]), 0);
107 pair_close();
108}
109
110static void case_removed_is_silent(void) {
111 test_case("a removed descriptor is not reported again");
112 pair_open();
113 CHECK(sv[0] != -1);
114 CHECK_INT(event_add(sv[0], EVENT_IN, 1), 0);
115 CHECK_INT(write(sv[1], "x", 1), 1);
116 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, NULL), 1);
117 CHECK_INT(event_del(sv[0]), 0);
118 CHECK_INT(wait_for(sv[0], EVENT_IN, 50, NULL), 0);
119 pair_close();
120}
121
122static void case_generations_do_not_mix(void) {
123 int other[2];
124 uint32_t generation = 0;
125
126 test_case("two descriptors carry their own generations");
127 pair_open();
128 CHECK(sv[0] != -1);
129 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, other), 0);
130 CHECK_INT(event_add(sv[0], EVENT_IN, 100), 0);
131 CHECK_INT(event_add(other[0], EVENT_IN, 200), 0);
132 CHECK_INT(write(sv[1], "x", 1), 1);
133 CHECK_INT(write(other[1], "y", 1), 1);
134
135 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, &generation), 1);
136 CHECK_INT(generation, 100);
137 CHECK_INT(wait_for(other[0], EVENT_IN, 1000, &generation), 1);
138 CHECK_INT(generation, 200);
139
140 test_case("and re-registering one changes only its own");
141 CHECK_INT(event_modify(sv[0], EVENT_IN, 101), 0);
142 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, &generation), 1);
143 CHECK_INT(generation, 101);
144 CHECK_INT(wait_for(other[0], EVENT_IN, 1000, &generation), 1);
145 CHECK_INT(generation, 200);
146
147 event_del(sv[0]);
148 event_del(other[0]);
149 close(other[0]);
150 close(other[1]);
151 pair_close();
152}
153
154/* Removing one of several has to leave the rest addressable: a backend that
155 keeps its descriptors packed moves the last one into the gap, and has to
156 remember where it went. */
157static void case_delete_from_the_middle(void) {
158 int pair[3][2];
159 uint32_t generation = 0;
160 int i;
161
162 test_case("deleting one of three leaves the others reported correctly");
163 for (i = 0; i < 3; ++i) {
164 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, pair[i]), 0);
165 CHECK_INT(event_add(pair[i][0], EVENT_IN, (uint32_t)(10 + i)), 0);
166 }
167 CHECK_INT(event_del(pair[1][0]), 0);
168
169 for (i = 0; i < 3; i += 2) {
170 CHECK_INT(write(pair[i][1], "x", 1), 1);
171 CHECK_INT(wait_for(pair[i][0], EVENT_IN, 1000, &generation), 1);
172 CHECK_INT(generation, 10 + i);
173 }
174
175 test_case("and the survivors can still be changed and removed");
176 CHECK_INT(event_modify(pair[2][0], EVENT_IN, 99), 0);
177 CHECK_INT(wait_for(pair[2][0], EVENT_IN, 1000, &generation), 1);
178 CHECK_INT(generation, 99);
179 CHECK_INT(event_del(pair[0][0]), 0);
180 CHECK_INT(event_del(pair[2][0]), 0);
181 CHECK_INT(wait_for(pair[2][0], EVENT_IN, 50, NULL), 0);
182
183 for (i = 0; i < 3; ++i)
184 close(pair[i][0]), close(pair[i][1]);
185}
186
187static void case_timeout(void) {
188 struct timeval started;
189
190 test_case("waiting on nothing comes back empty when the time is up");
191 gettimeofday(&started, NULL);
192 CHECK_INT(event_wait(ready, EVENTS, 200), 0);
193 {
194 unsigned long took = elapsed_ms(&started);
195 /* Scheduling makes the upper bound generous. The point is that it waited
196 rather than returning at once, and that it came back at all */
197 CHECK(took >= 150);
198 CHECK(took < 5000);
199 }
200}
201
202static void case_unknown_descriptor(void) {
203 test_case("changing or removing one that was never added is refused");
204 CHECK_INT(event_modify(4096, EVENT_IN, 1), -1);
205 CHECK_INT(event_del(4096), -1);
206}
207
208void test_event(void) {
209 CHECK_INT(event_init(), 0);
210 CHECK(event_mechanism() != NULL);
211
212 case_readable();
213 case_writable();
214 case_interest_is_separate();
215 case_removed_is_silent();
216 case_generations_do_not_mix();
217 case_delete_from_the_middle();
218 case_timeout();
219 case_unknown_descriptor();
220
221 event_deinit();
222}
diff --git a/tests/t_fmt.c b/tests/t_fmt.c
new file mode 100644
index 0000000..bc23ed5
--- /dev/null
+++ b/tests/t_fmt.c
@@ -0,0 +1,108 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <string.h>
8
9/* Opentracker */
10#include "ot_fmt.h"
11#include "test.h"
12
13static void case_appends_in_sequence(void) {
14 char buffer[64];
15 char *r = buffer, *re = buffer + sizeof(buffer);
16
17 test_case("appends follow each other and advance the cursor");
18 fmt_append(&r, re, "d8:completei%zde", (size_t)7);
19 fmt_append(&r, re, "ee");
20 *r = 0;
21 CHECK_STR(buffer, "d8:completei7eee");
22 CHECK_INT(r - buffer, 16);
23}
24
25static void case_truncation_stops_at_the_end(void) {
26 char buffer[16];
27 char *r = buffer, *re = buffer + 8; /* pretend the buffer ends early */
28
29 memset(buffer, '#', sizeof(buffer));
30 test_case("a value too long for the room left stops at the end");
31 fmt_append(&r, re, "%s", "0123456789");
32 CHECK_PTR(r, re);
33
34 test_case("and writes nothing past it");
35 CHECK_INT(buffer[8], '#');
36 CHECK_INT(buffer[15], '#');
37}
38
39static void case_a_full_buffer_takes_nothing_more(void) {
40 char buffer[16];
41 char *r = buffer + 8, *re = buffer + 8;
42
43 memset(buffer, '#', sizeof(buffer));
44 test_case("appending to a cursor already at the end changes nothing");
45 fmt_append(&r, re, "more");
46 CHECK_PTR(r, re);
47 CHECK_INT(buffer[8], '#');
48}
49
50static void case_an_append_that_just_fits(void) {
51 char buffer[8];
52 char *r = buffer, *re = buffer + 3;
53
54 memset(buffer, '#', sizeof(buffer));
55 test_case("three characters into three bytes of room still truncate");
56 fmt_append(&r, re, "abc");
57 CHECK_PTR(r, re);
58 CHECK_INT(buffer[0], 'a');
59 CHECK_INT(buffer[1], 'b');
60
61 test_case("because snprintf spends the last byte on its terminator");
62 CHECK_INT(buffer[2], 0);
63 CHECK_INT(buffer[3], '#');
64}
65
66static void case_bytes_are_all_or_nothing(void) {
67 char buffer[16];
68 char *r = buffer, *re = buffer + 8;
69
70 memset(buffer, '#', sizeof(buffer));
71 test_case("a run of bytes is written when it fits");
72 fmt_bytes(&r, re, "\0\1\2\3", 4);
73 CHECK_PTR(r, buffer + 4);
74 CHECK_MEM(buffer, "\0\1\2\3", 4);
75
76 test_case("a run that would not fit is refused whole");
77 fmt_bytes(&r, re, "abcde", 5);
78 CHECK_PTR(r, re);
79 CHECK_INT(buffer[4], '#');
80
81 test_case("a run of exactly the room left is written");
82 r = buffer + 4;
83 fmt_bytes(&r, re, "wxyz", 4);
84 CHECK_PTR(r, re);
85 CHECK_MEM(buffer + 4, "wxyz", 4);
86 CHECK_INT(buffer[8], '#');
87}
88
89static void case_bytes_do_not_terminate(void) {
90 char buffer[8];
91 char *r = buffer, *re = buffer + 4;
92
93 memset(buffer, '#', sizeof(buffer));
94 test_case("unlike an append, a run of bytes may fill the room exactly");
95 fmt_bytes(&r, re, "abcd", 4);
96 CHECK_PTR(r, re);
97 CHECK_MEM(buffer, "abcd", 4);
98 CHECK_INT(buffer[4], '#');
99}
100
101void test_fmt(void) {
102 case_appends_in_sequence();
103 case_truncation_stops_at_the_end();
104 case_a_full_buffer_takes_nothing_more();
105 case_an_append_that_just_fits();
106 case_bytes_are_all_or_nothing();
107 case_bytes_do_not_terminate();
108}
diff --git a/tests/t_hex.c b/tests/t_hex.c
index 7cce8ac..a761737 100644
--- a/tests/t_hex.c
+++ b/tests/t_hex.c
@@ -18,7 +18,7 @@ static void case_encode(void) {
18 18
19 test_case("a hash encodes to forty upper case digits and a terminator"); 19 test_case("a hash encodes to forty upper case digits and a terminator");
20 memset(dest, 0x7f, sizeof(dest)); 20 memset(dest, 0x7f, sizeof(dest));
21 CHECK_PTR(hex_fromhash(dest, sample), dest); 21 CHECK_PTR(hex_from_hash(dest, sample), dest);
22 CHECK_STR(dest, "000123456789ABCDEFFF10203040506070809" "0A0"); 22 CHECK_STR(dest, "000123456789ABCDEFFF10203040506070809" "0A0");
23 CHECK_INT(strlen(dest), 40); 23 CHECK_INT(strlen(dest), 40);
24 CHECK_INT((unsigned char)dest[41], 0x7f); 24 CHECK_INT((unsigned char)dest[41], 0x7f);
@@ -28,7 +28,7 @@ static void case_decode(void) {
28 ot_hash hash; 28 ot_hash hash;
29 29
30 test_case("forty digits decode back to the hash"); 30 test_case("forty digits decode back to the hash");
31 CHECK(hex_tohash(hash, "000123456789ABCDEFFF10203040506070809" "0A0")); 31 CHECK(hex_to_hash(hash, "000123456789ABCDEFFF10203040506070809" "0A0"));
32 CHECK_MEM(hash, sample, sizeof(sample)); 32 CHECK_MEM(hash, sample, sizeof(sample));
33} 33}
34 34
@@ -41,8 +41,8 @@ static void case_round_trip(void) {
41 for (i = 0; i < 256; ++i) { 41 for (i = 0; i < 256; ++i) {
42 uint8_t probe[20]; 42 uint8_t probe[20];
43 memset(probe, (uint8_t)i, sizeof(probe)); 43 memset(probe, (uint8_t)i, sizeof(probe));
44 hex_fromhash(text, probe); 44 hex_from_hash(text, probe);
45 if (!hex_tohash(hash, text) || memcmp(hash, probe, sizeof(probe))) { 45 if (!hex_to_hash(hash, text) || memcmp(hash, probe, sizeof(probe))) {
46 test_failf(__FILE__, __LINE__, "round trip failed for byte value %zu", i); 46 test_failf(__FILE__, __LINE__, "round trip failed for byte value %zu", i);
47 return; 47 return;
48 } 48 }
@@ -54,8 +54,8 @@ static void case_decode_is_case_insensitive(void) {
54 ot_hash upper, lower; 54 ot_hash upper, lower;
55 55
56 test_case("lower case digits decode the same as upper case"); 56 test_case("lower case digits decode the same as upper case");
57 CHECK(hex_tohash(upper, "ABCDEF0123456789ABCDEF0123456789ABCDEF01")); 57 CHECK(hex_to_hash(upper, "ABCDEF0123456789ABCDEF0123456789ABCDEF01"));
58 CHECK(hex_tohash(lower, "abcdef0123456789abcdef0123456789abcdef01")); 58 CHECK(hex_to_hash(lower, "abcdef0123456789abcdef0123456789abcdef01"));
59 CHECK_MEM(lower, upper, sizeof(ot_hash)); 59 CHECK_MEM(lower, upper, sizeof(ot_hash));
60} 60}
61 61
@@ -63,18 +63,18 @@ static void case_rejects_non_digits(void) {
63 ot_hash hash; 63 ot_hash hash;
64 64
65 test_case("a non hex character anywhere rejects the whole string"); 65 test_case("a non hex character anywhere rejects the whole string");
66 CHECK_INT(hex_tohash(hash, "g00123456789ABCDEFFF102030405060708090A0"), 0); 66 CHECK_INT(hex_to_hash(hash, "g00123456789ABCDEFFF102030405060708090A0"), 0);
67 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF102030405060708090Ag"), 0); 67 CHECK_INT(hex_to_hash(hash, "000123456789ABCDEFFF102030405060708090Ag"), 0);
68 CHECK_INT(hex_tohash(hash, "000123456789ABCDEF:F102030405060708090A"), 0); 68 CHECK_INT(hex_to_hash(hash, "000123456789ABCDEF:F102030405060708090A"), 0);
69} 69}
70 70
71static void case_rejects_short_input(void) { 71static void case_rejects_short_input(void) {
72 ot_hash hash; 72 ot_hash hash;
73 73
74 test_case("a string shorter than forty digits is rejected"); 74 test_case("a string shorter than forty digits is rejected");
75 CHECK_INT(hex_tohash(hash, ""), 0); 75 CHECK_INT(hex_to_hash(hash, ""), 0);
76 CHECK_INT(hex_tohash(hash, "00"), 0); 76 CHECK_INT(hex_to_hash(hash, "00"), 0);
77 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF102030405060708090A"), 0); 77 CHECK_INT(hex_to_hash(hash, "000123456789ABCDEFFF102030405060708090A"), 0);
78} 78}
79 79
80static void case_leaves_hash_untouched_on_failure(void) { 80static void case_leaves_hash_untouched_on_failure(void) {
@@ -82,7 +82,7 @@ static void case_leaves_hash_untouched_on_failure(void) {
82 82
83 test_case("a rejected string leaves the destination untouched"); 83 test_case("a rejected string leaves the destination untouched");
84 memset(hash, 0x5a, sizeof(hash)); 84 memset(hash, 0x5a, sizeof(hash));
85 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF10203040506070809XX0"), 0); 85 CHECK_INT(hex_to_hash(hash, "000123456789ABCDEFFF10203040506070809XX0"), 0);
86 CHECK_MEM(hash, "\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a", sizeof(hash)); 86 CHECK_MEM(hash, "\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a", sizeof(hash));
87} 87}
88 88
@@ -96,7 +96,7 @@ static void case_stops_at_the_terminator(void) {
96 memcpy(guarded, "0012", 4); 96 memcpy(guarded, "0012", 4);
97 guarded[4] = 0; 97 guarded[4] = 0;
98 memset(guarded + 5, 'f', 3); 98 memset(guarded + 5, 'f', 3);
99 CHECK_INT(hex_tohash(hash, guarded), 0); 99 CHECK_INT(hex_to_hash(hash, guarded), 0);
100} 100}
101 101
102static void case_isdigit(void) { 102static void case_isdigit(void) {
diff --git a/tests/t_iovec.c b/tests/t_iovec.c
index 89f3fda..2553141 100644
--- a/tests/t_iovec.c
+++ b/tests/t_iovec.c
@@ -18,13 +18,13 @@ static void case_increase_and_length(void) {
18 void *first, *second; 18 void *first, *second;
19 19
20 test_case("increase appends an entry and length is their sum"); 20 test_case("increase appends an entry and length is their sum");
21 first = iovec_increase(&entries, &vector, 100); 21 first = iovec_new_chunk(&entries, &vector, 100);
22 CHECK(first != NULL); 22 CHECK(first != NULL);
23 CHECK_INT(entries, 1); 23 CHECK_INT(entries, 1);
24 CHECK_INT(vector[0].iov_len, 100); 24 CHECK_INT(vector[0].iov_len, 100);
25 CHECK_PTR(vector[0].iov_base, first); 25 CHECK_PTR(vector[0].iov_base, first);
26 26
27 second = iovec_increase(&entries, &vector, 50); 27 second = iovec_new_chunk(&entries, &vector, 50);
28 CHECK(second != NULL); 28 CHECK(second != NULL);
29 CHECK_INT(entries, 2); 29 CHECK_INT(entries, 2);
30 CHECK_INT(vector[1].iov_len, 50); 30 CHECK_INT(vector[1].iov_len, 50);
@@ -38,8 +38,8 @@ static void case_free_resets(void) {
38 struct iovec *vector = NULL; 38 struct iovec *vector = NULL;
39 39
40 test_case("free releases the entries and resets the vector"); 40 test_case("free releases the entries and resets the vector");
41 CHECK(iovec_increase(&entries, &vector, 10) != NULL); 41 CHECK(iovec_new_chunk(&entries, &vector, 10) != NULL);
42 CHECK(iovec_increase(&entries, &vector, 10) != NULL); 42 CHECK(iovec_new_chunk(&entries, &vector, 10) != NULL);
43 iovec_free(&entries, &vector); 43 iovec_free(&entries, &vector);
44 CHECK_INT(entries, 0); 44 CHECK_INT(entries, 0);
45 CHECK_PTR(vector, NULL); 45 CHECK_PTR(vector, NULL);
@@ -52,12 +52,12 @@ static void case_fixlast_shrinks(void) {
52 char *block; 52 char *block;
53 53
54 test_case("fixlast trims the last entry to the bytes actually used"); 54 test_case("fixlast trims the last entry to the bytes actually used");
55 CHECK(iovec_increase(&entries, &vector, 64) != NULL); 55 CHECK(iovec_new_chunk(&entries, &vector, 64) != NULL);
56 block = iovec_increase(&entries, &vector, 1000); 56 block = iovec_new_chunk(&entries, &vector, 1000);
57 CHECK(block != NULL); 57 CHECK(block != NULL);
58 memset(block, 'x', 20); 58 memset(block, 'x', 20);
59 59
60 iovec_fixlast(&entries, &vector, block + 20); 60 iovec_trim_last(&entries, &vector, block + 20);
61 CHECK_INT(entries, 2); 61 CHECK_INT(entries, 2);
62 CHECK_INT(vector[1].iov_len, 20); 62 CHECK_INT(vector[1].iov_len, 20);
63 CHECK_INT(iovec_length(&entries, (const struct iovec **)&vector), 84); 63 CHECK_INT(iovec_length(&entries, (const struct iovec **)&vector), 84);
@@ -71,7 +71,7 @@ static void case_fixlast_on_empty(void) {
71 struct iovec *vector = NULL; 71 struct iovec *vector = NULL;
72 72
73 test_case("fixlast on an empty vector does nothing"); 73 test_case("fixlast on an empty vector does nothing");
74 iovec_fixlast(&entries, &vector, NULL); 74 iovec_trim_last(&entries, &vector, NULL);
75 CHECK_INT(entries, 0); 75 CHECK_INT(entries, 0);
76 CHECK_PTR(vector, NULL); 76 CHECK_PTR(vector, NULL);
77} 77}
@@ -103,9 +103,9 @@ static void case_fix_increase(void) {
103 char *block; 103 char *block;
104 104
105 test_case("fix_increase trims the previous entry and adds a new one"); 105 test_case("fix_increase trims the previous entry and adds a new one");
106 block = iovec_increase(&entries, &vector, 500); 106 block = iovec_new_chunk(&entries, &vector, 500);
107 CHECK(block != NULL); 107 CHECK(block != NULL);
108 block = iovec_fix_increase_or_free(&entries, &vector, block + 30, 200); 108 block = iovec_next_chunk(&entries, &vector, block + 30, 200);
109 CHECK(block != NULL); 109 CHECK(block != NULL);
110 CHECK_INT(entries, 2); 110 CHECK_INT(entries, 2);
111 CHECK_INT(vector[0].iov_len, 30); 111 CHECK_INT(vector[0].iov_len, 30);
diff --git a/tests/t_ip.c b/tests/t_ip.c
index 2fe3a72..8d71679 100644
--- a/tests/t_ip.c
+++ b/tests/t_ip.c
@@ -95,27 +95,27 @@ static void case_scan_forms(void) {
95 char ip[16]; 95 char ip[16];
96 96
97 test_case("2.2 form 1, every group spelled out"); 97 test_case("2.2 form 1, every group spelled out");
98 CHECK_INT(ip_scan("1111:2222:3333:4444:5555:6666:7777:8888", ip), 39); 98 CHECK_INT(ip_parse("1111:2222:3333:4444:5555:6666:7777:8888", ip), 39);
99 CHECK_MEM(ip, "\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88", 16); 99 CHECK_MEM(ip, "\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88", 16);
100 100
101 test_case("2.2 form 2, zero groups abbreviated"); 101 test_case("2.2 form 2, zero groups abbreviated");
102 CHECK_INT(ip_scan("2001:db8::1", ip), 11); 102 CHECK_INT(ip_parse("2001:db8::1", ip), 11);
103 CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); 103 CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16);
104 CHECK_INT(ip_scan("::1", ip), 3); 104 CHECK_INT(ip_parse("::1", ip), 3);
105 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); 105 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16);
106 CHECK_INT(ip_scan("::", ip), 2); 106 CHECK_INT(ip_parse("::", ip), 2);
107 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); 107 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16);
108 CHECK_INT(ip_scan("1::", ip), 3); 108 CHECK_INT(ip_parse("1::", ip), 3);
109 CHECK_MEM(ip, "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); 109 CHECK_MEM(ip, "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16);
110 110
111 test_case("2.2 form 3, a dotted quad in the last two groups"); 111 test_case("2.2 form 3, a dotted quad in the last two groups");
112 CHECK_INT(ip_scan("::ffff:1.2.3.4", ip), 14); 112 CHECK_INT(ip_parse("::ffff:1.2.3.4", ip), 14);
113 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04", 16); 113 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04", 16);
114 CHECK_INT(ip_scan("1:2:3:4:5:6:1.2.3.4", ip), 19); 114 CHECK_INT(ip_parse("1:2:3:4:5:6:1.2.3.4", ip), 19);
115 CHECK_MEM(ip, "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x02\x03\x04", 16); 115 CHECK_MEM(ip, "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x02\x03\x04", 16);
116 116
117 test_case("upper case input is accepted"); 117 test_case("upper case input is accepted");
118 CHECK_INT(ip_scan("2001:DB8::1", ip), 11); 118 CHECK_INT(ip_parse("2001:DB8::1", ip), 11);
119 CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); 119 CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16);
120} 120}
121 121
@@ -125,7 +125,7 @@ static void case_scan_accepts_bare_ipv4(void) {
125 /* Every address opentracker holds is sixteen bytes, so an operator writing 125 /* Every address opentracker holds is sixteen bytes, so an operator writing
126 a plain IPv4 address in the config has to land as the v4 mapped form */ 126 a plain IPv4 address in the config has to land as the v4 mapped form */
127 test_case("a bare IPv4 address is stored v4 mapped"); 127 test_case("a bare IPv4 address is stored v4 mapped");
128 CHECK_INT(ip_scan("10.0.0.1", ip), 8); 128 CHECK_INT(ip_parse("10.0.0.1", ip), 8);
129 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x0a\x00\x00\x01", 16); 129 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x0a\x00\x00\x01", 16);
130 CHECK(ip_is_v4mapped(ip)); 130 CHECK(ip_is_v4mapped(ip));
131} 131}
@@ -136,39 +136,39 @@ static void case_scan_reports_length(void) {
136 /* The config parser continues from where the address ended, which is how 136 /* The config parser continues from where the address ended, which is how
137 "address:port" and "address/bits" are read */ 137 "address:port" and "address/bits" are read */
138 test_case("scanning stops at the character after the address"); 138 test_case("scanning stops at the character after the address");
139 CHECK_INT(ip_scan("10.0.0.1:6969", ip), 8); 139 CHECK_INT(ip_parse("10.0.0.1:6969", ip), 8);
140 CHECK_INT(ip_scan("2001:db8::1/64", ip), 11); 140 CHECK_INT(ip_parse("2001:db8::1/64", ip), 11);
141 CHECK_INT(ip_scan("::1]", ip), 3); 141 CHECK_INT(ip_parse("::1]", ip), 3);
142 CHECK_INT(ip_scan("1:2:3:4:5:6:7:8 trailing", ip), 15); 142 CHECK_INT(ip_parse("1:2:3:4:5:6:7:8 trailing", ip), 15);
143} 143}
144 144
145static void case_scan_rejects(void) { 145static void case_scan_rejects(void) {
146 char ip[16]; 146 char ip[16];
147 147
148 test_case("an empty or junk string is refused"); 148 test_case("an empty or junk string is refused");
149 CHECK_INT(ip_scan("", ip), 0); 149 CHECK_INT(ip_parse("", ip), 0);
150 CHECK_INT(ip_scan("x", ip), 0); 150 CHECK_INT(ip_parse("x", ip), 0);
151 CHECK_INT(ip_scan(":", ip), 0); 151 CHECK_INT(ip_parse(":", ip), 0);
152 CHECK_INT(ip_scan(":1:2", ip), 0); 152 CHECK_INT(ip_parse(":1:2", ip), 0);
153 153
154 test_case("too few groups without :: are refused"); 154 test_case("too few groups without :: are refused");
155 CHECK_INT(ip_scan("1:2:3:4:5:6:7", ip), 0); 155 CHECK_INT(ip_parse("1:2:3:4:5:6:7", ip), 0);
156 CHECK_INT(ip_scan("2001:db8:1", ip), 0); 156 CHECK_INT(ip_parse("2001:db8:1", ip), 0);
157 157
158 /* libowfat accepted a prefix of each of these and carried on. Taking only 158 /* libowfat accepted a prefix of each of these and carried on. Taking only
159 part of a malformed address is worse than refusing it, especially where 159 part of a malformed address is worse than refusing it, especially where
160 the text comes from a config file or an X-Forwarded-For header. */ 160 the text comes from a config file or an X-Forwarded-For header. */
161 test_case("a second :: is refused rather than truncated"); 161 test_case("a second :: is refused rather than truncated");
162 CHECK_INT(ip_scan("1::2::3", ip), 0); 162 CHECK_INT(ip_parse("1::2::3", ip), 0);
163 CHECK_INT(ip_scan("::1::", ip), 0); 163 CHECK_INT(ip_parse("::1::", ip), 0);
164 CHECK_INT(ip_scan("::::", ip), 0); 164 CHECK_INT(ip_parse("::::", ip), 0);
165 165
166 test_case("more than eight groups is refused"); 166 test_case("more than eight groups is refused");
167 CHECK_INT(ip_scan("1:2:3:4:5:6:7:8:9", ip), 0); 167 CHECK_INT(ip_parse("1:2:3:4:5:6:7:8:9", ip), 0);
168 168
169 test_case("a group of more than four digits is refused, not truncated"); 169 test_case("a group of more than four digits is refused, not truncated");
170 CHECK_INT(ip_scan("12345::1", ip), 0); 170 CHECK_INT(ip_parse("12345::1", ip), 0);
171 CHECK_INT(ip_scan("fffff::1", ip), 0); 171 CHECK_INT(ip_parse("fffff::1", ip), 0);
172} 172}
173 173
174static void case_round_trip(void) { 174static void case_round_trip(void) {
@@ -187,7 +187,7 @@ static void case_round_trip(void) {
187 187
188 test_case("every formatted address scans back to the same bytes"); 188 test_case("every formatted address scans back to the same bytes");
189 for (i = 0; samples[i]; ++i) { 189 for (i = 0; samples[i]; ++i) {
190 size_t n = ip_scan(samples[i], ip); 190 size_t n = ip_parse(samples[i], ip);
191 if (n != strlen(samples[i])) { 191 if (n != strlen(samples[i])) {
192 test_failf(__FILE__, __LINE__, "\"%s\" consumed %zu of %zu", samples[i], n, strlen(samples[i])); 192 test_failf(__FILE__, __LINE__, "\"%s\" consumed %zu of %zu", samples[i], n, strlen(samples[i]));
193 return; 193 return;
@@ -197,7 +197,7 @@ static void case_round_trip(void) {
197 test_failf(__FILE__, __LINE__, "\"%s\" formatted back as \"%s\"", samples[i], text); 197 test_failf(__FILE__, __LINE__, "\"%s\" formatted back as \"%s\"", samples[i], text);
198 return; 198 return;
199 } 199 }
200 if (!ip_scan(text, again) || memcmp(ip, again, 16)) { 200 if (!ip_parse(text, again) || memcmp(ip, again, 16)) {
201 test_failf(__FILE__, __LINE__, "\"%s\" did not survive a second pass", samples[i]); 201 test_failf(__FILE__, __LINE__, "\"%s\" did not survive a second pass", samples[i]);
202 return; 202 return;
203 } 203 }
diff --git a/tests/t_scan.c b/tests/t_scan.c
index 89f1590..aab4413 100644
--- a/tests/t_scan.c
+++ b/tests/t_scan.c
@@ -15,13 +15,13 @@ static void case_plain_numbers(void) {
15 uint64_t value; 15 uint64_t value;
16 16
17 test_case("a decimal number is read and its length reported"); 17 test_case("a decimal number is read and its length reported");
18 CHECK_INT(scan_udec("0", &value, 65535), 1); 18 CHECK_INT(scan_uint("0", &value, 65535), 1);
19 CHECK_INT(value, 0); 19 CHECK_INT(value, 0);
20 CHECK_INT(scan_udec("7", &value, 65535), 1); 20 CHECK_INT(scan_uint("7", &value, 65535), 1);
21 CHECK_INT(value, 7); 21 CHECK_INT(value, 7);
22 CHECK_INT(scan_udec("6969", &value, 65535), 4); 22 CHECK_INT(scan_uint("6969", &value, 65535), 4);
23 CHECK_INT(value, 6969); 23 CHECK_INT(value, 6969);
24 CHECK_INT(scan_udec("65535", &value, 65535), 5); 24 CHECK_INT(scan_uint("65535", &value, 65535), 5);
25 CHECK_INT(value, 65535); 25 CHECK_INT(value, 65535);
26} 26}
27 27
@@ -29,11 +29,11 @@ static void case_stops_at_non_digit(void) {
29 uint64_t value; 29 uint64_t value;
30 30
31 test_case("scanning stops at the first character that is not a digit"); 31 test_case("scanning stops at the first character that is not a digit");
32 CHECK_INT(scan_udec("6969:rest", &value, 65535), 4); 32 CHECK_INT(scan_uint("6969:rest", &value, 65535), 4);
33 CHECK_INT(value, 6969); 33 CHECK_INT(value, 6969);
34 CHECK_INT(scan_udec("128/8", &value, 65535), 3); 34 CHECK_INT(scan_uint("128/8", &value, 65535), 3);
35 CHECK_INT(value, 128); 35 CHECK_INT(value, 128);
36 CHECK_INT(scan_udec("42\n", &value, 65535), 2); 36 CHECK_INT(scan_uint("42\n", &value, 65535), 2);
37 CHECK_INT(value, 42); 37 CHECK_INT(value, 42);
38} 38}
39 39
@@ -41,9 +41,9 @@ static void case_rejects_whitespace(void) {
41 uint64_t value = 0xdead; 41 uint64_t value = 0xdead;
42 42
43 test_case("leading whitespace is not skipped"); 43 test_case("leading whitespace is not skipped");
44 CHECK_INT(scan_udec(" 1", &value, 65535), 0); 44 CHECK_INT(scan_uint(" 1", &value, 65535), 0);
45 CHECK_INT(scan_udec("\t1", &value, 65535), 0); 45 CHECK_INT(scan_uint("\t1", &value, 65535), 0);
46 CHECK_INT(scan_udec("\n1", &value, 65535), 0); 46 CHECK_INT(scan_uint("\n1", &value, 65535), 0);
47 CHECK_INT(value, 0xdead); 47 CHECK_INT(value, 0xdead);
48} 48}
49 49
@@ -51,9 +51,9 @@ static void case_rejects_sign(void) {
51 uint64_t value = 0xdead; 51 uint64_t value = 0xdead;
52 52
53 test_case("a sign is not accepted"); 53 test_case("a sign is not accepted");
54 CHECK_INT(scan_udec("+1", &value, 65535), 0); 54 CHECK_INT(scan_uint("+1", &value, 65535), 0);
55 CHECK_INT(scan_udec("-1", &value, 65535), 0); 55 CHECK_INT(scan_uint("-1", &value, 65535), 0);
56 CHECK_INT(scan_udec("-0", &value, 65535), 0); 56 CHECK_INT(scan_uint("-0", &value, 65535), 0);
57 CHECK_INT(value, 0xdead); 57 CHECK_INT(value, 0xdead);
58} 58}
59 59
@@ -61,9 +61,9 @@ static void case_rejects_empty_and_junk(void) {
61 uint64_t value = 0xdead; 61 uint64_t value = 0xdead;
62 62
63 test_case("a string with no leading digit yields nothing"); 63 test_case("a string with no leading digit yields nothing");
64 CHECK_INT(scan_udec("", &value, 65535), 0); 64 CHECK_INT(scan_uint("", &value, 65535), 0);
65 CHECK_INT(scan_udec("x", &value, 65535), 0); 65 CHECK_INT(scan_uint("x", &value, 65535), 0);
66 CHECK_INT(scan_udec(":80", &value, 65535), 0); 66 CHECK_INT(scan_uint(":80", &value, 65535), 0);
67 CHECK_INT(value, 0xdead); 67 CHECK_INT(value, 0xdead);
68} 68}
69 69
@@ -71,9 +71,9 @@ static void case_no_alternative_bases(void) {
71 uint64_t value; 71 uint64_t value;
72 72
73 test_case("hexadecimal and octal notations are read as plain decimal"); 73 test_case("hexadecimal and octal notations are read as plain decimal");
74 CHECK_INT(scan_udec("0x10", &value, 65535), 1); 74 CHECK_INT(scan_uint("0x10", &value, 65535), 1);
75 CHECK_INT(value, 0); 75 CHECK_INT(value, 0);
76 CHECK_INT(scan_udec("010", &value, 65535), 3); 76 CHECK_INT(scan_uint("010", &value, 65535), 3);
77 CHECK_INT(value, 10); 77 CHECK_INT(value, 10);
78} 78}
79 79
@@ -81,13 +81,13 @@ static void case_refuses_out_of_range(void) {
81 uint64_t value = 0xdead; 81 uint64_t value = 0xdead;
82 82
83 test_case("a value above max is refused, not truncated"); 83 test_case("a value above max is refused, not truncated");
84 CHECK_INT(scan_udec("65536", &value, 65535), 0); 84 CHECK_INT(scan_uint("65536", &value, 65535), 0);
85 CHECK_INT(scan_udec("99999", &value, 65535), 0); 85 CHECK_INT(scan_uint("99999", &value, 65535), 0);
86 CHECK_INT(scan_udec("129", &value, 128), 0); 86 CHECK_INT(scan_uint("129", &value, 128), 0);
87 CHECK_INT(value, 0xdead); 87 CHECK_INT(value, 0xdead);
88 88
89 test_case("the boundary itself is accepted"); 89 test_case("the boundary itself is accepted");
90 CHECK_INT(scan_udec("128", &value, 128), 3); 90 CHECK_INT(scan_uint("128", &value, 128), 3);
91 CHECK_INT(value, 128); 91 CHECK_INT(value, 128);
92} 92}
93 93
@@ -95,21 +95,21 @@ static void case_sixty_four_bit_range(void) {
95 uint64_t value; 95 uint64_t value;
96 96
97 test_case("the full unsigned 64 bit range is available"); 97 test_case("the full unsigned 64 bit range is available");
98 CHECK_INT(scan_udec("18446744073709551615", &value, UINT64_MAX), 20); 98 CHECK_INT(scan_uint("18446744073709551615", &value, UINT64_MAX), 20);
99 CHECK(value == UINT64_MAX); 99 CHECK(value == UINT64_MAX);
100 100
101 test_case("a number too large for 64 bits is refused"); 101 test_case("a number too large for 64 bits is refused");
102 CHECK_INT(scan_udec("18446744073709551616", &value, UINT64_MAX), 0); 102 CHECK_INT(scan_uint("18446744073709551616", &value, UINT64_MAX), 0);
103 CHECK_INT(scan_udec("99999999999999999999999999", &value, UINT64_MAX), 0); 103 CHECK_INT(scan_uint("99999999999999999999999999", &value, UINT64_MAX), 0);
104} 104}
105 105
106static void case_leading_zeros_allowed(void) { 106static void case_leading_zeros_allowed(void) {
107 uint64_t value; 107 uint64_t value;
108 108
109 test_case("leading zeros are accepted outside bencode"); 109 test_case("leading zeros are accepted outside bencode");
110 CHECK_INT(scan_udec("007", &value, 65535), 3); 110 CHECK_INT(scan_uint("007", &value, 65535), 3);
111 CHECK_INT(value, 7); 111 CHECK_INT(value, 7);
112 CHECK_INT(scan_udec("0000", &value, 65535), 4); 112 CHECK_INT(scan_uint("0000", &value, 65535), 4);
113 CHECK_INT(value, 0); 113 CHECK_INT(value, 0);
114} 114}
115 115
@@ -117,9 +117,9 @@ static void case_bencode_rejects_leading_zeros(void) {
117 uint64_t value = 0xdead; 117 uint64_t value = 0xdead;
118 118
119 test_case("bencode refuses a leading zero on anything but zero"); 119 test_case("bencode refuses a leading zero on anything but zero");
120 CHECK_INT(scan_udec_bencode("007", &value, 65535), 0); 120 CHECK_INT(scan_uint_bencode("007", &value, 65535), 0);
121 CHECK_INT(scan_udec_bencode("0000", &value, 65535), 0); 121 CHECK_INT(scan_uint_bencode("0000", &value, 65535), 0);
122 CHECK_INT(scan_udec_bencode("01", &value, 65535), 0); 122 CHECK_INT(scan_uint_bencode("01", &value, 65535), 0);
123 CHECK_INT(value, 0xdead); 123 CHECK_INT(value, 0xdead);
124} 124}
125 125
@@ -127,11 +127,11 @@ static void case_bencode_accepts_canonical(void) {
127 uint64_t value; 127 uint64_t value;
128 128
129 test_case("bencode accepts a canonically written number"); 129 test_case("bencode accepts a canonically written number");
130 CHECK_INT(scan_udec_bencode("0", &value, 65535), 1); 130 CHECK_INT(scan_uint_bencode("0", &value, 65535), 1);
131 CHECK_INT(value, 0); 131 CHECK_INT(value, 0);
132 CHECK_INT(scan_udec_bencode("0:", &value, 65535), 1); 132 CHECK_INT(scan_uint_bencode("0:", &value, 65535), 1);
133 CHECK_INT(value, 0); 133 CHECK_INT(value, 0);
134 CHECK_INT(scan_udec_bencode("160:", &value, 65535), 3); 134 CHECK_INT(scan_uint_bencode("160:", &value, 65535), 3);
135 CHECK_INT(value, 160); 135 CHECK_INT(value, 160);
136} 136}
137 137
@@ -139,14 +139,64 @@ static void case_bencode_inherits_the_rest(void) {
139 uint64_t value = 0xdead; 139 uint64_t value = 0xdead;
140 140
141 test_case("bencode is no laxer than the plain scanner"); 141 test_case("bencode is no laxer than the plain scanner");
142 CHECK_INT(scan_udec_bencode(" 1", &value, 65535), 0); 142 CHECK_INT(scan_uint_bencode(" 1", &value, 65535), 0);
143 CHECK_INT(scan_udec_bencode("-1", &value, 65535), 0); 143 CHECK_INT(scan_uint_bencode("-1", &value, 65535), 0);
144 CHECK_INT(scan_udec_bencode("+1", &value, 65535), 0); 144 CHECK_INT(scan_uint_bencode("+1", &value, 65535), 0);
145 CHECK_INT(scan_udec_bencode("65536", &value, 65535), 0); 145 CHECK_INT(scan_uint_bencode("65536", &value, 65535), 0);
146 CHECK_INT(value, 0xdead); 146 CHECK_INT(value, 0xdead);
147} 147}
148 148
149/* ------------------------------------------- fixed width signed integers */
150
151/* A value lifted out of a query string arrives as a pointer and a length
152 with nothing terminating it, and the whole of it has to be the number. */
153
154static void case_int_whole_field(void) {
155 int64_t value = 0;
156
157 test_case("the whole field is read, sign and all");
158 CHECK_INT(scan_int("123", 3, &value), 0);
159 CHECK_INT(value, 123);
160 CHECK_INT(scan_int("-45", 3, &value), 0);
161 CHECK_INT(value, -45);
162
163 test_case("and only as far as the length given");
164 CHECK_INT(scan_int("12345", 3, &value), 0);
165 CHECK_INT(value, 123);
166}
167
168static void case_int_rejects_the_rest(void) {
169 int64_t value = 4711;
170
171 test_case("anything that is not all digits is refused");
172 CHECK_INT(scan_int("12a", 3, &value), -1);
173 CHECK_INT(scan_int("", 0, &value), -1);
174 CHECK_INT(scan_int("-", 1, &value), -1);
175 CHECK_INT(scan_int(" 12", 3, &value), -1);
176 CHECK_INT(scan_int("+12", 3, &value), -1);
177
178 test_case("and a refused field leaves the number alone");
179 CHECK_INT(value, 4711);
180}
181
182static void case_int_overflow(void) {
183 int64_t value = 0;
184
185 test_case("a field too long to fit is refused rather than wrapped");
186 CHECK_INT(scan_int("9223372036854775807", 19, &value), 0);
187 CHECK_INT(value, INT64_MAX);
188 CHECK_INT(scan_int("9223372036854775808", 19, &value), -1);
189 CHECK_INT(scan_int("99999999999999999999", 20, &value), -1);
190
191 test_case("including the negative side");
192 CHECK_INT(scan_int("-9223372036854775807", 20, &value), 0);
193 CHECK_INT(value, -INT64_MAX);
194}
195
149void test_scan(void) { 196void test_scan(void) {
197 case_int_whole_field();
198 case_int_rejects_the_rest();
199 case_int_overflow();
150 case_plain_numbers(); 200 case_plain_numbers();
151 case_stops_at_non_digit(); 201 case_stops_at_non_digit();
152 case_rejects_whitespace(); 202 case_rejects_whitespace();
diff --git a/tests/t_scan_urlencoded_query.c b/tests/t_urlencoded.c
index cb6c982..6e0ee31 100644
--- a/tests/t_scan_urlencoded_query.c
+++ b/tests/t_urlencoded.c
@@ -7,7 +7,7 @@
7#include <string.h> 7#include <string.h>
8 8
9/* Opentracker */ 9/* Opentracker */
10#include "scan_urlencoded_query.h" 10#include "ot_urlencoded.h"
11#include "test.h" 11#include "test.h"
12 12
13/* The scanner returns the number of bytes written to the destination, or 13/* The scanner returns the number of bytes written to the destination, or
@@ -23,7 +23,7 @@ static void case_path(void) {
23 ssize_t n; 23 ssize_t n;
24 24
25 test_case("path stops at the question mark"); 25 test_case("path stops at the question mark");
26 n = scan_urlencoded_query(&p, dest, SCAN_PATH); 26 n = urlencoded_parse(&p, dest, SCAN_PATH);
27 CHECK_INT(n, 9); 27 CHECK_INT(n, 9);
28 dest[n] = 0; 28 dest[n] = 0;
29 CHECK_STR(dest, "/announce"); 29 CHECK_STR(dest, "/announce");
@@ -37,22 +37,22 @@ static void case_param_and_value(void) {
37 ssize_t n; 37 ssize_t n;
38 38
39 test_case("parameter stops at '=', value stops at '&'"); 39 test_case("parameter stops at '=', value stops at '&'");
40 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); 40 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_PARAM);
41 CHECK_INT(n, 9); 41 CHECK_INT(n, 9);
42 dest[n] = 0; 42 dest[n] = 0;
43 CHECK_STR(dest, "info_hash"); 43 CHECK_STR(dest, "info_hash");
44 44
45 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 45 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
46 CHECK_INT(n, 3); 46 CHECK_INT(n, 3);
47 dest[n] = 0; 47 dest[n] = 0;
48 CHECK_STR(dest, "abc"); 48 CHECK_STR(dest, "abc");
49 49
50 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); 50 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_PARAM);
51 CHECK_INT(n, 4); 51 CHECK_INT(n, 4);
52 dest[n] = 0; 52 dest[n] = 0;
53 CHECK_STR(dest, "port"); 53 CHECK_STR(dest, "port");
54 54
55 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 55 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
56 CHECK_INT(n, 2); 56 CHECK_INT(n, 2);
57 dest[n] = 0; 57 dest[n] = 0;
58 CHECK_STR(dest, "80"); 58 CHECK_STR(dest, "80");
@@ -65,7 +65,7 @@ static void case_percent_decoding(void) {
65 ssize_t n; 65 ssize_t n;
66 66
67 test_case("percent escapes decode"); 67 test_case("percent escapes decode");
68 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 68 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
69 CHECK_INT(n, 3); 69 CHECK_INT(n, 3);
70 CHECK_MEM(dest, "ABC", 3); 70 CHECK_MEM(dest, "ABC", 3);
71} 71}
@@ -79,7 +79,7 @@ static void case_binary_info_hash(void) {
79 ssize_t n; 79 ssize_t n;
80 80
81 test_case("twenty byte binary value survives decoding"); 81 test_case("twenty byte binary value survives decoding");
82 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 82 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
83 CHECK_INT(n, 20); 83 CHECK_INT(n, 20);
84 CHECK_MEM(dest, want, 20); 84 CHECK_MEM(dest, want, 20);
85} 85}
@@ -90,7 +90,7 @@ static void case_in_place(void) {
90 ssize_t n; 90 ssize_t n;
91 91
92 test_case("decoding in place over the source is safe"); 92 test_case("decoding in place over the source is safe");
93 n = scan_urlencoded_query(&p, query, SCAN_SEARCHPATH_VALUE); 93 n = urlencoded_parse(&p, query, SCAN_SEARCHPATH_VALUE);
94 CHECK_INT(n, 2); 94 CHECK_INT(n, 2);
95 CHECK_MEM(query, "AB", 2); 95 CHECK_MEM(query, "AB", 2);
96} 96}
@@ -102,7 +102,7 @@ static void case_bad_escape(void) {
102 ssize_t n; 102 ssize_t n;
103 103
104 test_case("a non hexadecimal escape is a parse error"); 104 test_case("a non hexadecimal escape is a parse error");
105 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 105 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
106 CHECK_INT(n, -1); 106 CHECK_INT(n, -1);
107} 107}
108 108
@@ -113,7 +113,7 @@ static void case_truncated_escape(void) {
113 ssize_t n; 113 ssize_t n;
114 114
115 test_case("an escape cut short by the terminator is a parse error"); 115 test_case("an escape cut short by the terminator is a parse error");
116 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 116 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
117 CHECK_INT(n, -1); 117 CHECK_INT(n, -1);
118} 118}
119 119
@@ -125,11 +125,11 @@ static void case_terminator(void) {
125 ssize_t n; 125 ssize_t n;
126 126
127 test_case("a hard terminator before any output reports end of input"); 127 test_case("a hard terminator before any output reports end of input");
128 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); 128 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_PARAM);
129 CHECK_INT(n, -2); 129 CHECK_INT(n, -2);
130 130
131 p = space; 131 p = space;
132 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); 132 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_PARAM);
133 CHECK_INT(n, -2); 133 CHECK_INT(n, -2);
134} 134}
135 135
@@ -140,10 +140,10 @@ static void case_terminator_after_output(void) {
140 ssize_t n; 140 ssize_t n;
141 141
142 test_case("a hard terminator after output is left for the next call"); 142 test_case("a hard terminator after output is left for the next call");
143 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 143 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
144 CHECK_INT(n, 3); 144 CHECK_INT(n, 3);
145 CHECK_INT(*p, 0); 145 CHECK_INT(*p, 0);
146 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 146 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
147 CHECK_INT(n, -2); 147 CHECK_INT(n, -2);
148} 148}
149 149
@@ -159,14 +159,14 @@ static void case_separators_are_contextual(void) {
159 ssize_t n; 159 ssize_t n;
160 160
161 test_case("a question mark is literal inside a value"); 161 test_case("a question mark is literal inside a value");
162 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 162 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
163 CHECK_INT(n, 3); 163 CHECK_INT(n, 3);
164 dest[n] = 0; 164 dest[n] = 0;
165 CHECK_STR(dest, "a?b"); 165 CHECK_STR(dest, "a?b");
166 166
167 test_case("an equals sign is literal inside a value"); 167 test_case("an equals sign is literal inside a value");
168 p = equals; 168 p = equals;
169 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 169 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
170 CHECK_INT(n, 3); 170 CHECK_INT(n, 3);
171 dest[n] = 0; 171 dest[n] = 0;
172 CHECK_STR(dest, "a=b"); 172 CHECK_STR(dest, "a=b");
@@ -179,7 +179,7 @@ static void case_ampersand_in_path(void) {
179 ssize_t n; 179 ssize_t n;
180 180
181 test_case("an ampersand in a path is a parse error"); 181 test_case("an ampersand in a path is a parse error");
182 n = scan_urlencoded_query(&p, dest, SCAN_PATH); 182 n = urlencoded_parse(&p, dest, SCAN_PATH);
183 CHECK_INT(n, -1); 183 CHECK_INT(n, -1);
184} 184}
185 185
@@ -190,7 +190,7 @@ static void case_raw_high_byte(void) {
190 ssize_t n; 190 ssize_t n;
191 191
192 test_case("an undecoded byte above ASCII is a parse error"); 192 test_case("an undecoded byte above ASCII is a parse error");
193 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 193 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
194 CHECK_INT(n, -1); 194 CHECK_INT(n, -1);
195} 195}
196 196
@@ -200,12 +200,12 @@ static void case_skipvalue(void) {
200 char *p = query; 200 char *p = query;
201 201
202 test_case("skipvalue steps over the value and its separator"); 202 test_case("skipvalue steps over the value and its separator");
203 scan_urlencoded_skipvalue(&p); 203 urlencoded_skip_value(&p);
204 CHECK_STR(p, "next=1"); 204 CHECK_STR(p, "next=1");
205 205
206 test_case("skipvalue leaves a hard terminator in place"); 206 test_case("skipvalue leaves a hard terminator in place");
207 p = bare; 207 p = bare;
208 scan_urlencoded_skipvalue(&p); 208 urlencoded_skip_value(&p);
209 CHECK_INT(*p, 0); 209 CHECK_INT(*p, 0);
210} 210}
211 211
@@ -217,44 +217,17 @@ static void case_find_keywords(void) {
217 char *p = match; 217 char *p = match;
218 218
219 test_case("a known keyword returns its value"); 219 test_case("a known keyword returns its value");
220 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), 2); 220 CHECK_INT(urlencoded_match_keyword(keywords, &p, SCAN_SEARCHPATH_PARAM), 2);
221 221
222 test_case("an unknown keyword returns -3"); 222 test_case("an unknown keyword returns -3");
223 p = nomatch; 223 p = nomatch;
224 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3); 224 CHECK_INT(urlencoded_match_keyword(keywords, &p, SCAN_SEARCHPATH_PARAM), -3);
225 225
226 test_case("a prefix of a keyword does not match it"); 226 test_case("a prefix of a keyword does not match it");
227 p = prefix; 227 p = prefix;
228 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3); 228 CHECK_INT(urlencoded_match_keyword(keywords, &p, SCAN_SEARCHPATH_PARAM), -3);
229} 229}
230 230
231static void case_fixed_int(void) {
232 char positive[] = "123";
233 char negative[] = "-45";
234 char trailing[] = "12a";
235 int value;
236
237 test_case("scan_fixed_int returns the count of unparsed bytes");
238 CHECK_INT(scan_fixed_int(positive, 3, &value), 0);
239 CHECK_INT(value, 123);
240
241 CHECK_INT(scan_fixed_int(negative, 3, &value), 0);
242 CHECK_INT(value, -45);
243
244 CHECK_INT(scan_fixed_int(trailing, 3, &value), 1);
245 CHECK_INT(value, 12);
246}
247
248static void case_fixed_int_bounded(void) {
249 char digits[] = "12345";
250 int value;
251
252 test_case("scan_fixed_int honours the length it is given");
253 CHECK_INT(scan_fixed_int(digits, 3, &value), 0);
254 CHECK_INT(value, 123);
255}
256
257
258/* The encoding direction. Which bytes survive unescaped is fixed by what 231/* The encoding direction. Which bytes survive unescaped is fixed by what
259 existing parsers of the urlencoded full scrape expect, so these cases pin 232 existing parsers of the urlencoded full scrape expect, so these cases pin
260 the set rather than describe a preference. */ 233 the set rather than describe a preference. */
@@ -328,7 +301,7 @@ static void case_fmt_decoder_disagrees_on_dollar(void) {
328 encoder passes through unescaped, '$' is the only one the decoder then 301 encoder passes through unescaped, '$' is the only one the decoder then
329 refuses, and it refuses the whole string rather than stopping short. 302 refuses, and it refuses the whole string rather than stopping short.
330 Output of the urlencoded full scrape therefore does not round trip 303 Output of the urlencoded full scrape therefore does not round trip
331 through scan_urlencoded_query when a hash contains 0x24. Harmless today 304 through urlencoded_parse when a hash contains 0x24. Harmless today
332 because only third party tooling reads that output. */ 305 because only third party tooling reads that output. */
333 const uint8_t src[] = {'a', '$', 'b'}; 306 const uint8_t src[] = {'a', '$', 'b'};
334 char dest[16]; 307 char dest[16];
@@ -342,11 +315,11 @@ static void case_fmt_decoder_disagrees_on_dollar(void) {
342 dest[n] = 0; 315 dest[n] = 0;
343 CHECK_STR(dest, "a$b"); 316 CHECK_STR(dest, "a$b");
344 317
345 got = scan_urlencoded_query(&p, scanned, SCAN_SEARCHPATH_VALUE); 318 got = urlencoded_parse(&p, scanned, SCAN_SEARCHPATH_VALUE);
346 CHECK_INT(got, -1); 319 CHECK_INT(got, -1);
347} 320}
348 321
349void test_scan_urlencoded_query(void) { 322void test_urlencoded(void) {
350 case_path(); 323 case_path();
351 case_param_and_value(); 324 case_param_and_value();
352 case_percent_decoding(); 325 case_percent_decoding();
@@ -361,8 +334,6 @@ void test_scan_urlencoded_query(void) {
361 case_raw_high_byte(); 334 case_raw_high_byte();
362 case_skipvalue(); 335 case_skipvalue();
363 case_find_keywords(); 336 case_find_keywords();
364 case_fixed_int();
365 case_fixed_int_bounded();
366 case_fmt_passes_safe_bytes(); 337 case_fmt_passes_safe_bytes();
367 case_fmt_escapes_the_rest(); 338 case_fmt_escapes_the_rest();
368 case_fmt_hex_is_lower_case(); 339 case_fmt_hex_is_lower_case();
diff --git a/tests/t_vector.c b/tests/t_vector.c
index 541a6da..6e048e1 100644
--- a/tests/t_vector.c
+++ b/tests/t_vector.c
@@ -112,6 +112,51 @@ static void case_insert_keeps_order(void) {
112 free(vector.data); 112 free(vector.data);
113} 113}
114 114
115/* The peer variant is reached with an empty vector on every first announce
116 for a torrent, and again whenever a swarm empties and refills, because
117 vector_fixup_peers drops the allocation and nulls the data pointer. */
118
119static void case_insert_peer_into_empty_vector(void) {
120 ot_vector vector = {NULL, 0, 0};
121 ot_peer6 low, high;
122 int exactmatch;
123 ot_peer *slot;
124
125 memset(low, 0x11, sizeof(low));
126 memset(high, 0x99, sizeof(high));
127
128 test_case("the first peer goes into a vector that has no allocation yet");
129 slot = vector_find_or_insert_peer(&vector, low, OT_PEER_SIZE6, &exactmatch);
130 CHECK(slot != NULL);
131 CHECK_INT(exactmatch, 0);
132 memcpy(slot, low, OT_PEER_SIZE6);
133 CHECK_INT(vector.size, 1);
134 CHECK_INT(vector.space, OT_VECTOR_MIN_MEMBERS);
135 CHECK_PTR(slot, vector.data);
136
137 test_case("a second peer keeps the order");
138 slot = vector_find_or_insert_peer(&vector, high, OT_PEER_SIZE6, &exactmatch);
139 CHECK(slot != NULL);
140 CHECK_INT(exactmatch, 0);
141 memcpy(slot, high, OT_PEER_SIZE6);
142 CHECK_INT(vector.size, 2);
143 CHECK_MEM((char *)vector.data, low, OT_PEER_COMPARE_SIZE6);
144 CHECK_MEM((char *)vector.data + OT_PEER_SIZE6, high, OT_PEER_COMPARE_SIZE6);
145
146 test_case("and emptying the vector hands the allocation back");
147 CHECK(vector_remove_peer(&vector, low, OT_PEER_SIZE6) != 0);
148 CHECK(vector_remove_peer(&vector, high, OT_PEER_SIZE6) != 0);
149 CHECK_INT(vector.size, 0);
150 CHECK_PTR(vector.data, NULL);
151
152 test_case("so the next peer starts from no allocation again");
153 slot = vector_find_or_insert_peer(&vector, low, OT_PEER_SIZE6, &exactmatch);
154 CHECK(slot != NULL);
155 CHECK_INT(vector.size, 1);
156
157 free(vector.data);
158}
159
115static void case_insert_finds_existing(void) { 160static void case_insert_finds_existing(void) {
116 ot_vector vector = {NULL, 0, 0}; 161 ot_vector vector = {NULL, 0, 0};
117 const unsigned char five[4] = {0, 0, 0, 5}; 162 const unsigned char five[4] = {0, 0, 0, 5};
@@ -257,6 +302,7 @@ void test_vector(void) {
257 case_search_past_the_end(); 302 case_search_past_the_end();
258 case_search_empty(); 303 case_search_empty();
259 case_insert_keeps_order(); 304 case_insert_keeps_order();
305 case_insert_peer_into_empty_vector();
260 case_insert_finds_existing(); 306 case_insert_finds_existing();
261 case_insert_compare_prefix(); 307 case_insert_compare_prefix();
262 case_redistribute_holds_every_peer(); 308 case_redistribute_holds_every_peer();
diff --git a/tests/test.h b/tests/test.h
index bc8c3bd..0eddc82 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -76,7 +76,7 @@ extern unsigned long test_failures;
76 } while (0) 76 } while (0)
77 77
78/* One entry point per module under test */ 78/* One entry point per module under test */
79void test_scan_urlencoded_query(void); 79void test_urlencoded(void);
80void test_iovec(void); 80void test_iovec(void);
81void test_vector(void); 81void test_vector(void);
82void test_random(void); 82void test_random(void);
@@ -89,5 +89,7 @@ void test_socket(void);
89void test_mutex(void); 89void test_mutex(void);
90void test_batch(void); 90void test_batch(void);
91void test_fdstate(void); 91void test_fdstate(void);
92void test_event(void);
93void test_fmt(void);
92 94
93#endif 95#endif
diff --git a/trackerlogic.c b/trackerlogic.c
index 9705bd6..1f38f07 100644
--- a/trackerlogic.c
+++ b/trackerlogic.c
@@ -14,6 +14,7 @@
14/* Opentracker */ 14/* Opentracker */
15#include "ot_accesslist.h" 15#include "ot_accesslist.h"
16#include "ot_clean.h" 16#include "ot_clean.h"
17#include "ot_fmt.h"
17#include "ot_fullscrape.h" 18#include "ot_fullscrape.h"
18#include "ot_http.h" 19#include "ot_http.h"
19#include "ot_ip.h" 20#include "ot_ip.h"
@@ -299,11 +300,12 @@ static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent
299} 300}
300 301
301static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { 302static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) {
302 char *r = reply; 303 char *r = reply;
303 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; 304 const char *r_end = ws->outbuf + G_OUTBUF_SIZE;
304 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 305 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
305 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; 306 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
306 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; 307 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
308 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count;
307 309
308 /* Simple case: amount of peers in both lists is less than requested, here we return all results */ 310 /* Simple case: amount of peers in both lists is less than requested, here we return all results */
309 size_t amount_v4 = torrent->peer_list4->peer_count; 311 size_t amount_v4 = torrent->peer_list4->peer_count;
@@ -341,11 +343,11 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
341 ++amount_v4; 343 ++amount_v4;
342 } 344 }
343 345
344 r += 346 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval,
345 sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval, erval / 2); 347 erval / 2);
346 348
347 if (amount_v4) { 349 if (amount_v4) {
348 r += sprintf(r, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); 350 fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4);
349 if (amount_v4 == torrent->peer_list4->peer_count) 351 if (amount_v4 == torrent->peer_list4->peer_count)
350 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); 352 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r);
351 else 353 else
@@ -353,14 +355,14 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
353 } 355 }
354 356
355 if (amount_v6) { 357 if (amount_v6) {
356 r += sprintf(r, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); 358 fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6);
357 if (amount_v6 == torrent->peer_list6->peer_count) 359 if (amount_v6 == torrent->peer_list6->peer_count)
358 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); 360 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r);
359 else 361 else
360 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); 362 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r);
361 } 363 }
362 364
363 *r++ = 'e'; 365 fmt_append(&r, r_end, "e");
364 366
365 return r - reply; 367 return r - reply;
366} 368}
@@ -403,12 +405,11 @@ size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) {
403} 405}
404 406
405/* Fetches scrape info for a specific torrent */ 407/* Fetches scrape info for a specific torrent */
406size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply) { 408void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end) {
407 char *r = reply; 409 int exactmatch;
408 int exactmatch;
409 size_t i; 410 size_t i;
410 411
411 r += sprintf(r, "d5:filesd"); 412 fmt_append(reply, reply_end, "d5:filesd");
412 413
413 for (i = 0; i < amount; ++i) { 414 for (i = 0; i < amount; ++i) {
414 int delta_torrentcount = 0; 415 int delta_torrentcount = 0;
@@ -421,22 +422,17 @@ size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, ch
421 vector_remove_torrent(torrents_list, torrent); 422 vector_remove_torrent(torrents_list, torrent);
422 delta_torrentcount = -1; 423 delta_torrentcount = -1;
423 } else { 424 } else {
424 *r++ = '2'; 425 fmt_append(reply, reply_end, "20:");
425 *r++ = '0'; 426 fmt_bytes(reply, reply_end, hash, sizeof(ot_hash));
426 *r++ = ':'; 427 fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee",
427 memcpy(r, hash, sizeof(ot_hash)); 428 torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, torrent->peer_list6->down_count + torrent->peer_list4->down_count,
428 r += sizeof(ot_hash); 429 torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count);
429 r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", torrent->peer_list6->seed_count + torrent->peer_list4->seed_count,
430 torrent->peer_list6->down_count + torrent->peer_list4->down_count,
431 torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count);
432 } 430 }
433 } 431 }
434 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount); 432 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount);
435 } 433 }
436 434
437 *r++ = 'e'; 435 fmt_append(reply, reply_end, "ee");
438 *r++ = 'e';
439 return r - reply;
440} 436}
441 437
442static ot_peerlist dummy_list; 438static ot_peerlist dummy_list;
@@ -472,9 +468,12 @@ size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) {
472 } 468 }
473 469
474 if (proto == FLAG_TCP) { 470 if (proto == FLAG_TCP) {
475 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; 471 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
476 ws->reply_size = sprintf(ws->reply, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count, erval, 472 char *r = ws->reply;
477 erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4); 473
474 fmt_append(&r, ws->outbuf + G_OUTBUF_SIZE, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count,
475 erval, erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4);
476 ws->reply_size = r - ws->reply;
478 } 477 }
479 478
480 /* Handle UDP reply */ 479 /* Handle UDP reply */
diff --git a/trackerlogic.h b/trackerlogic.h
index 42018c3..63abf78 100644
--- a/trackerlogic.h
+++ b/trackerlogic.h
@@ -187,7 +187,7 @@ void exerr(const char *message);
187 otherwise it is released in return_peers_for_torrent */ 187 otherwise it is released in return_peers_for_torrent */
188size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount); 188size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount);
189size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); 189size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws);
190size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply); 190void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end);
191size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); 191size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply);
192void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count); 192void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count);
193#ifdef _DEBUG_RANDOMTORRENTS 193#ifdef _DEBUG_RANDOMTORRENTS