summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile50
-rw-r--r--opentracker.c119
-rw-r--r--ot_accesslist.c36
-rw-r--r--ot_accesslist.h6
-rw-r--r--ot_clean.c3
-rw-r--r--ot_config.c31
-rw-r--r--ot_event.c332
-rw-r--r--ot_event.h39
-rw-r--r--ot_fdstate.c194
-rw-r--r--ot_fdstate.h61
-rw-r--r--ot_fmt.c42
-rw-r--r--ot_fmt.h21
-rw-r--r--ot_fullscrape.c55
-rw-r--r--ot_hex.c4
-rw-r--r--ot_hex.h4
-rw-r--r--ot_http.c166
-rw-r--r--ot_http.h6
-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_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_timeout.h39
-rw-r--r--ot_udp.c3
-rw-r--r--ot_urlencoded.c (renamed from scan_urlencoded_query.c)29
-rw-r--r--ot_urlencoded.h (renamed from scan_urlencoded_query.h)14
-rw-r--r--ot_vector.c57
-rw-r--r--ot_vector.h2
-rw-r--r--proxy.c21
-rw-r--r--tests/integration/.gitignore2
-rw-r--r--tests/integration/harness.py166
-rw-r--r--tests/integration/test_announce.py129
-rw-r--r--tests/integration/test_dualstack.py89
-rw-r--r--tests/runner.c5
-rw-r--r--tests/t_event.c222
-rw-r--r--tests/t_fdstate.c224
-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)95
-rw-r--r--tests/t_vector.c62
-rw-r--r--tests/test.h5
-rw-r--r--trackerlogic.c61
-rw-r--r--trackerlogic.h2
54 files changed, 2456 insertions, 707 deletions
diff --git a/Makefile b/Makefile
index c7105d4..ae01373 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,13 +60,18 @@ 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_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_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_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)
76OBJECTS_debug = $(SOURCES:%.c=%.debug.o) 77OBJECTS_debug = $(SOURCES:%.c=%.debug.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 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_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,25 @@ $(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# Black box tests: these drive the real binary over the wire, so they cover
138# the event loop and the parsers working together, which the unit tests
139# cannot reach. Built with the sanitizers on, so the tracker reports its own
140# memory errors and the harness turns those into failures.
141PYTHON?=python3
142FEATURES_integration=$(FEATURES) -DWANT_IP_FROM_QUERY_STRING
143CFLAGS_integration=$(CFLAGS) $(OPTS_debug) $(FEATURES_integration) -fsanitize=address,undefined -fno-omit-frame-pointer
144LDFLAGS_integration=$(LDFLAGS) -fsanitize=address,undefined
145
146opentracker.integration: $(SOURCES) $(HEADERS)
147 $(CC) -o $@ $(CFLAGS_integration) $(SOURCES) $(LDFLAGS_integration)
148
149check-integration: opentracker.integration
150 OPENTRACKER=./opentracker.integration $(PYTHON) -m unittest discover -s tests/integration
151
152.PHONY: all check check-integration clean install
137 153
138clean: 154clean:
139 rm -rf opentracker opentracker.debug proxy proxy.debug $(TESTBIN) *.o *~ 155 rm -rf opentracker opentracker.debug opentracker.integration proxy proxy.debug $(TESTBIN) *.o *~
140 156
141install: 157install:
142 install -m 755 opentracker $(DESTDIR)$(BINDIR) 158 install -m 755 opentracker $(DESTDIR)$(BINDIR)
diff --git a/opentracker.c b/opentracker.c
index 60bdceb..21eeb42 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,8 +31,9 @@
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"
35#include "ot_fdstate.h"
37#include "ot_socket.h" 36#include "ot_socket.h"
38#include "ot_timeout.h"
39#include "ot_stats.h" 37#include "ot_stats.h"
40#include "ot_udp.h" 38#include "ot_udp.h"
41#include "trackerlogic.h" 39#include "trackerlogic.h"
@@ -118,7 +116,7 @@ static ssize_t header_complete(char *request, ssize_t byte_count) {
118} 116}
119 117
120static void handle_dead(const int64_t sock) { 118static void handle_dead(const int64_t sock) {
121 struct http_data *cookie = io_getcookie(sock); 119 struct http_data *cookie = fdstate_cookie(sock);
122 if (cookie) { 120 if (cookie) {
123 if (cookie->batch) 121 if (cookie->batch)
124 batch_reset(cookie->batch); 122 batch_reset(cookie->batch);
@@ -128,14 +126,17 @@ static void handle_dead(const int64_t sock) {
128 mutex_workqueue_canceltask(sock); 126 mutex_workqueue_canceltask(sock);
129 free(cookie); 127 free(cookie);
130 } 128 }
131 io_close(sock); 129 /* The generation has to move before the kernel can reissue the number */
130 fdstate_close(sock);
131 event_del((int)sock);
132 close((int)sock);
132} 133}
133 134
134static void handle_read(const int64_t sock, struct ot_workstruct *ws) { 135static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
135 struct http_data *cookie = io_getcookie(sock); 136 struct http_data *cookie = fdstate_cookie(sock);
136 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);
137 138
138 if (byte_count == 0 || byte_count == -3) { 139 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
139 handle_dead(sock); 140 handle_dead(sock);
140 return; 141 return;
141 } 142 }
@@ -176,7 +177,7 @@ static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
176} 177}
177 178
178static void handle_write(const int64_t sock) { 179static void handle_write(const int64_t sock) {
179 struct http_data *cookie = io_getcookie(sock); 180 struct http_data *cookie = fdstate_cookie(sock);
180 int chunked = 0; 181 int chunked = 0;
181 182
182 if (cookie) { 183 if (cookie) {
@@ -195,7 +196,7 @@ static void handle_write(const int64_t sock) {
195 196
196 /* In a chunked transfer, wait for the next piece once everything queued has gone out */ 197 /* In a chunked transfer, wait for the next piece once everything queued has gone out */
197 if (chunked) 198 if (chunked)
198 io_dontwantwrite(sock); 199 http_interest_drop(sock, cookie, EVENT_OUT);
199 else 200 else
200 handle_dead(sock); 201 handle_dead(sock);
201} 202}
@@ -208,26 +209,30 @@ static void handle_accept(const int64_t serversocket) {
208 209
209 while ((sock = socket_accept(serversocket, ip, &port)) != -1) { 210 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
210 211
211 /* Put fd into a non-blocking mode */ 212 socket_set_blocking((int)sock, 0);
212 io_nonblock(sock);
213 213
214 if (!io_fd(sock) || !(cookie = (struct http_data *)malloc(sizeof(struct http_data)))) { 214 if (!(cookie = (struct http_data *)malloc(sizeof(struct http_data)))) {
215 io_close(sock); 215 close((int)sock);
216 continue; 216 continue;
217 } 217 }
218 memset(cookie, 0, sizeof(struct http_data)); 218 memset(cookie, 0, sizeof(struct http_data));
219 memcpy(cookie->ip, ip, sizeof(ot_ip6)); 219 memcpy(cookie->ip, ip, sizeof(ot_ip6));
220 cookie->interest = EVENT_IN;
220 221
221 io_setcookie(sock, cookie); 222 if (event_add((int)sock, EVENT_IN, fdstate_open(sock, cookie, g_now_seconds + OT_CLIENT_TIMEOUT)) == -1) {
222 io_wantread(sock); 223 fdstate_close(sock);
224 free(cookie);
225 close((int)sock);
226 continue;
227 }
223 228
224 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); 229 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
225
226 timeout_in(sock, OT_CLIENT_TIMEOUT);
227 } 230 }
228 io_eagain(serversocket);
229} 231}
230 232
233/* How many ready descriptors one wait hands back at a time */
234#define OT_EVENT_BATCH 512
235
231static void *server_mainloop(void *args) { 236static void *server_mainloop(void *args) {
232 struct ot_workstruct ws; 237 struct ot_workstruct ws;
233 time_t next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL; 238 time_t next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL;
@@ -250,30 +255,46 @@ static void *server_mainloop(void *args) {
250 random_bytes(ws.rand48_state, sizeof(ws.rand48_state)); 255 random_bytes(ws.rand48_state, sizeof(ws.rand48_state));
251 256
252 for (;;) { 257 for (;;) {
253 int64_t sock; 258 ot_event ready[OT_EVENT_BATCH];
259 int64_t sock;
260 int count, i;
261
262 count = event_wait(ready, OT_EVENT_BATCH, OT_CLIENT_TIMEOUT_CHECKINTERVAL * 1000);
254 263
255 io_wait(); 264 for (i = 0; i < count; ++i) {
265 const void *cookie;
266
267 sock = ready[i].fd;
268 /* A descriptor closed earlier in this same batch may already have been
269 handed out again by accept, so an event is only ours if the
270 generation it was registered under is still the one in place */
271 if (fdstate_generation((int)sock) != ready[i].generation)
272 continue;
273 cookie = fdstate_cookie((int)sock);
256 274
257 while ((sock = io_canread()) != -1) {
258 const void *cookie = io_getcookie(sock);
259 if ((intptr_t)cookie == FLAG_TCP) 275 if ((intptr_t)cookie == FLAG_TCP)
260 handle_accept(sock); 276 handle_accept(sock);
261 else if ((intptr_t)cookie == FLAG_UDP) 277 else if ((intptr_t)cookie == FLAG_UDP)
262 handle_udp6(sock, &ws); 278 handle_udp6(sock, &ws);
263 else if ((intptr_t)cookie == FLAG_SELFPIPE) 279 else if ((intptr_t)cookie == FLAG_SELFPIPE)
264 io_tryread(sock, ws.inbuf, G_INBUF_SIZE); 280 while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0)
265 else 281 ;
266 handle_read(sock, &ws); 282 else {
283 if (ready[i].what & EVENT_IN)
284 handle_read(sock, &ws);
285 /* The read may have closed it, so look again before writing */
286 if ((ready[i].what & EVENT_OUT) && fdstate_generation((int)sock) == ready[i].generation)
287 handle_write(sock);
288 }
267 } 289 }
268 290
269 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) 291 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1)
270 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); 292 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial);
271 293
272 while ((sock = io_canwrite()) != -1)
273 handle_write(sock);
274
275 if (g_now_seconds > next_timeout_check) { 294 if (g_now_seconds > next_timeout_check) {
276 while ((sock = io_timeouted()) != -1) 295 int cursor = 0;
296
297 while ((sock = fdstate_next_expired(g_now_seconds, &cursor)) != -1)
277 handle_dead(sock); 298 handle_dead(sock);
278 next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL; 299 next_timeout_check = g_now_seconds + OT_CLIENT_TIMEOUT_CHECKINTERVAL;
279 } 300 }
@@ -316,11 +337,8 @@ static void listen_bind_one(ot_listen *entry) {
316 panic("socket_udp_bind"); 337 panic("socket_udp_bind");
317 } 338 }
318 339
319 if (!io_fd(entry->sock)) 340 if (event_add((int)entry->sock, EVENT_IN, fdstate_open(entry->sock, (void *)entry->proto, 0)) == -1)
320 panic("io_fd"); 341 panic("event_add");
321
322 io_setcookie(entry->sock, (void *)entry->proto);
323 io_wantread(entry->sock);
324 342
325#ifdef _DEBUG 343#ifdef _DEBUG
326 fputs(" success.\n", stderr); 344 fputs(" success.\n", stderr);
@@ -349,7 +367,8 @@ static void listen_start_udp_workers(void) {
349 if (entry->proto != FLAG_UDP || entry->workers <= 0) 367 if (entry->proto != FLAG_UDP || entry->workers <= 0)
350 continue; 368 continue;
351 369
352 io_block(entry->sock); 370 socket_set_blocking((int)entry->sock, 1);
371 event_del((int)entry->sock);
353 udp_workers_install(entry->sock, (unsigned int)entry->workers); 372 udp_workers_install(entry->sock, (unsigned int)entry->workers);
354 } 373 }
355} 374}
@@ -372,14 +391,14 @@ void load_state(const char *const state_filename) {
372 while (fgets(inbuf, sizeof(inbuf), state_filehandle)) { 391 while (fgets(inbuf, sizeof(inbuf), state_filehandle)) {
373 size_t i; 392 size_t i;
374 393
375 if (!hex_tohash(infohash, inbuf)) 394 if (!hex_to_hash(infohash, inbuf))
376 continue; 395 continue;
377 i = 2 * sizeof(ot_hash); 396 i = 2 * sizeof(ot_hash);
378 397
379 if (inbuf[i++] != ':' || !(consumed = scan_udec(inbuf + i, &base, UINT64_MAX))) 398 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &base, UINT64_MAX)))
380 continue; 399 continue;
381 i += consumed; 400 i += consumed;
382 if (inbuf[i++] != ':' || !(consumed = scan_udec(inbuf + i, &downcount, UINT64_MAX))) 401 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, UINT64_MAX)))
383 continue; 402 continue;
384 add_torrent_from_saved_state(infohash, base, downcount); 403 add_torrent_from_saved_state(infohash, base, downcount);
385 } 404 }
@@ -448,6 +467,10 @@ int main(int argc, char **argv) {
448 pthread_t thread_id; /* time cacher */ 467 pthread_t thread_id; /* time cacher */
449 468
450 random_init(); 469 random_init();
470 if (fdstate_init() == -1)
471 panic("fdstate_init");
472 if (event_init() == -1)
473 panic("event_init");
451 config_parse(argc, argv); 474 config_parse(argc, argv);
452 475
453 listen_bind_all(); 476 listen_bind_all();
@@ -466,15 +489,15 @@ int main(int argc, char **argv) {
466 pthread_create(&thread_id, NULL, time_caching_worker, NULL); 489 pthread_create(&thread_id, NULL, time_caching_worker, NULL);
467 490
468 /* Create our self pipe which allows us to interrupt mainloops 491 /* Create our self pipe which allows us to interrupt mainloops
469 io_wait in case some data is available to send out */ 492 wait in case some data is available to send out */
470 if (pipe(g_self_pipe) == -1) 493 if (pipe(g_self_pipe) == -1)
471 panic("selfpipe failed: "); 494 panic("selfpipe failed: ");
472 if (!io_fd(g_self_pipe[0])) 495 /* Both ends have to be non blocking: the loop drains the read end until it
473 panic("selfpipe io_fd failed: "); 496 is empty, and a worker nudging a full pipe has already made its point */
474 if (!io_fd(g_self_pipe[1])) 497 socket_set_blocking(g_self_pipe[0], 0);
475 panic("selfpipe io_fd failed: "); 498 socket_set_blocking(g_self_pipe[1], 0);
476 io_setcookie(g_self_pipe[0], (void *)FLAG_SELFPIPE); 499 if (event_add(g_self_pipe[0], EVENT_IN, fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0)) == -1)
477 io_wantread(g_self_pipe[0]); 500 panic("selfpipe event_add");
478 501
479 /* Init all sub systems. This call may fail with an exit() */ 502 /* Init all sub systems. This call may fail with an exit() */
480 trackerlogic_init(); 503 trackerlogic_init();
@@ -494,5 +517,7 @@ int main(int argc, char **argv) {
494 517
495 server_mainloop(0); 518 server_mainloop(0);
496 519
520 event_deinit();
521 fdstate_deinit();
497 return 0; 522 return 0;
498} 523}
diff --git a/ot_accesslist.c b/ot_accesslist.c
index d7e88ee..724978e 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))
@@ -308,7 +306,7 @@ static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_
308 if (linelen < 41) 306 if (linelen < 41)
309 continue; 307 continue;
310 308
311 if (!hex_tohash(info_hash, line)) 309 if (!hex_to_hash(info_hash, line))
312 continue; 310 continue;
313 311
314 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the 312 /* From now on we modify g_accesslist_add and g_accesslist_delete, so prevent the
@@ -339,7 +337,7 @@ static void *accesslist_adddel_worker(const char *fifoname, ot_accesslist *_
339 } 337 }
340 } else { 338 } else {
341 int exactmatch = 0; 339 int exactmatch = 0;
342 ot_hash *insert_point = binary_search(info_hash, (*adding_to)->list, (*adding_to)->size, sizeof(ot_hash), OT_HASH_COMPARE_SIZE, &exactmatch); 340 ot_hash *insert_point = vector_binary_search(info_hash, (*adding_to)->list, (*adding_to)->size, sizeof(ot_hash), OT_HASH_COMPARE_SIZE, &exactmatch);
343 341
344 /* Only if the info hash is not in the adding_to list, create a new head with that entry */ 342 /* Only if the info hash is not in the adding_to list, create a new head with that entry */
345 if (!exactmatch) { 343 if (!exactmatch) {
@@ -416,7 +414,7 @@ void accesslist_cleanup(void) {
416} 414}
417#endif 415#endif
418 416
419int address_in_net(const ot_ip6 address, const ot_net *net) { 417int accesslist_address_in_net(const ot_ip6 address, const ot_net *net) {
420 int bits = net->bits, checkbits = (0x7f00 >> (bits & 7)); 418 int bits = net->bits, checkbits = (0x7f00 >> (bits & 7));
421 int result = memcmp(address, &net->address, bits >> 3); 419 int result = memcmp(address, &net->address, bits >> 3);
422 if (!result && (bits & 7)) 420 if (!result && (bits & 7))
@@ -424,7 +422,7 @@ int address_in_net(const ot_ip6 address, const ot_net *net) {
424 return result == 0; 422 return result == 0;
425} 423}
426 424
427void *set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size) { 425void *accesslist_set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size) {
428 size_t i; 426 size_t i;
429 int exactmatch; 427 int exactmatch;
430 428
@@ -435,7 +433,7 @@ void *set_value_for_net(const ot_net *net, ot_vector *vector, const void *value,
435 /* Check each net in vector for overlap */ 433 /* Check each net in vector for overlap */
436 uint8_t *member = ((uint8_t *)vector->data); 434 uint8_t *member = ((uint8_t *)vector->data);
437 for (i = 0; i < vector->size; ++i) { 435 for (i = 0; i < vector->size; ++i) {
438 if (address_in_net(*(ot_ip6 *)member, net) || address_in_net(net->address, (ot_net *)member)) 436 if (accesslist_address_in_net(*(ot_ip6 *)member, net) || accesslist_address_in_net(net->address, (ot_net *)member))
439 return 0; 437 return 0;
440 member += member_size; 438 member += member_size;
441 } 439 }
@@ -451,16 +449,16 @@ void *set_value_for_net(const ot_net *net, ot_vector *vector, const void *value,
451 449
452/* Takes a vector filled with { ot_net net, uint8_t[x] value }; 450/* Takes a vector filled with { ot_net net, uint8_t[x] value };
453 Returns value associated with the net, or NULL if not found */ 451 Returns value associated with the net, or NULL if not found */
454void *get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size) { 452void *accesslist_get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size) {
455 int exactmatch; 453 int exactmatch;
456 /* This binary search will return a pointer to the first non-containing network... */ 454 /* This binary search will return a pointer to the first non-containing network... */
457 ot_net *net = binary_search(address, vector->data, vector->size, member_size, sizeof(ot_ip6), &exactmatch); 455 ot_net *net = vector_binary_search(address, vector->data, vector->size, member_size, sizeof(ot_ip6), &exactmatch);
458 if (!net) 456 if (!net)
459 return NULL; 457 return NULL;
460 /* ... so we'll need to move back one step unless we've exactly hit the first address in network */ 458 /* ... so we'll need to move back one step unless we've exactly hit the first address in network */
461 if (!exactmatch && ((void *)net > vector->data)) 459 if (!exactmatch && ((void *)net > vector->data))
462 --net; 460 --net;
463 if (!address_in_net(address, net)) 461 if (!accesslist_address_in_net(address, net))
464 return NULL; 462 return NULL;
465 return (void *)net; 463 return (void *)net;
466} 464}
@@ -472,7 +470,7 @@ static pthread_mutex_t g_lognets_list_mutex = PTHREAD_MUTEX_INITIALIZER;
472 470
473void loglist_add_network(const ot_net *net) { 471void loglist_add_network(const ot_net *net) {
474 pthread_mutex_lock(&g_lognets_list_mutex); 472 pthread_mutex_lock(&g_lognets_list_mutex);
475 set_value_for_net(net, &g_lognets_list, NULL, sizeof(ot_net)); 473 accesslist_set_value_for_net(net, &g_lognets_list, NULL, sizeof(ot_net));
476 pthread_mutex_unlock(&g_lognets_list_mutex); 474 pthread_mutex_unlock(&g_lognets_list_mutex);
477} 475}
478 476
@@ -487,7 +485,7 @@ void loglist_reset(void) {
487int loglist_check_address(const ot_ip6 address) { 485int loglist_check_address(const ot_ip6 address) {
488 int result; 486 int result;
489 pthread_mutex_lock(&g_lognets_list_mutex); 487 pthread_mutex_lock(&g_lognets_list_mutex);
490 result = (NULL != get_value_for_net(address, &g_lognets_list, sizeof(ot_net))); 488 result = (NULL != accesslist_get_value_for_net(address, &g_lognets_list, sizeof(ot_net)));
491 pthread_mutex_unlock(&g_lognets_list_mutex); 489 pthread_mutex_unlock(&g_lognets_list_mutex);
492 return result; 490 return result;
493} 491}
@@ -508,17 +506,17 @@ int proxylist_add_network(const ot_net *proxy, const ot_net *net) {
508 pthread_mutex_lock(&g_proxies_list_mutex); 506 pthread_mutex_lock(&g_proxies_list_mutex);
509 507
510 /* If we have a direct hit, use and extend the vector there */ 508 /* If we have a direct hit, use and extend the vector there */
511 map = binary_search(proxy, g_proxies_list.data, g_proxies_list.size, sizeof(ot_proxymap), sizeof(ot_net), &exactmatch); 509 map = vector_binary_search(proxy, g_proxies_list.data, g_proxies_list.size, sizeof(ot_proxymap), sizeof(ot_net), &exactmatch);
512 510
513 if (!map || !exactmatch) { 511 if (!map || !exactmatch) {
514 /* else see, if we've got overlapping networks 512 /* else see, if we've got overlapping networks
515 and get a new empty vector if not */ 513 and get a new empty vector if not */
516 ot_vector empty; 514 ot_vector empty;
517 memset(&empty, 0, sizeof(ot_vector)); 515 memset(&empty, 0, sizeof(ot_vector));
518 map = set_value_for_net(proxy, &g_proxies_list, &empty, sizeof(ot_proxymap)); 516 map = accesslist_set_value_for_net(proxy, &g_proxies_list, &empty, sizeof(ot_proxymap));
519 } 517 }
520 518
521 if (map && set_value_for_net(net, &map->networks, NULL, sizeof(ot_net))) 519 if (map && accesslist_set_value_for_net(net, &map->networks, NULL, sizeof(ot_net)))
522 result = 1; 520 result = 1;
523 521
524 pthread_mutex_unlock(&g_proxies_list_mutex); 522 pthread_mutex_unlock(&g_proxies_list_mutex);
@@ -531,8 +529,8 @@ int proxylist_check_proxy(const ot_ip6 proxy, const ot_ip6 address) {
531 529
532 pthread_mutex_lock(&g_proxies_list_mutex); 530 pthread_mutex_lock(&g_proxies_list_mutex);
533 531
534 if ((map = get_value_for_net(proxy, &g_proxies_list, sizeof(ot_proxymap)))) 532 if ((map = accesslist_get_value_for_net(proxy, &g_proxies_list, sizeof(ot_proxymap))))
535 if (!address || get_value_for_net(address, &map->networks, sizeof(ot_net))) 533 if (!address || accesslist_get_value_for_net(address, &map->networks, sizeof(ot_net)))
536 result = 1; 534 result = 1;
537 535
538 pthread_mutex_unlock(&g_proxies_list_mutex); 536 pthread_mutex_unlock(&g_proxies_list_mutex);
@@ -587,7 +585,7 @@ int accesslist_bless_net(ot_net *net, ot_permissions permissions) {
587int accesslist_is_blessed(ot_ip6 ip, ot_permissions permissions) { 585int accesslist_is_blessed(ot_ip6 ip, ot_permissions permissions) {
588 size_t i; 586 size_t i;
589 for (i = 0; i < g_admin_nets_count; ++i) 587 for (i = 0; i < g_admin_nets_count; ++i)
590 if (address_in_net(ip, g_admin_nets + i) && (g_admin_nets_permissions[i] & permissions)) 588 if (accesslist_address_in_net(ip, g_admin_nets + i) && (g_admin_nets_permissions[i] & permissions))
591 return 1; 589 return 1;
592 return 0; 590 return 0;
593} 591}
diff --git a/ot_accesslist.h b/ot_accesslist.h
index 9dfdd95..0a8bf4c 100644
--- a/ot_accesslist.h
+++ b/ot_accesslist.h
@@ -40,7 +40,7 @@ extern const char *g_accesslist_pipe_delete;
40#endif 40#endif
41 41
42/* Test if an address is subset of an ot_net, return value is considered a bool */ 42/* Test if an address is subset of an ot_net, return value is considered a bool */
43int address_in_net(const ot_ip6 address, const ot_net *net); 43int accesslist_address_in_net(const ot_ip6 address, const ot_net *net);
44 44
45/* Store a value into a vector of struct { ot_net net, uint8_t[x] value } member; 45/* Store a value into a vector of struct { ot_net net, uint8_t[x] value } member;
46 returns NULL 46 returns NULL
@@ -51,13 +51,13 @@ int address_in_net(const ot_ip6 address, const ot_net *net);
51 returns pointer to new member in vector for success 51 returns pointer to new member in vector for success
52 member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping 52 member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping
53*/ 53*/
54void *set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size); 54void *accesslist_set_value_for_net(const ot_net *net, ot_vector *vector, const void *value, const size_t member_size);
55 55
56/* Takes a vector filled with struct { ot_net net, uint8_t[x] value } member; 56/* Takes a vector filled with struct { ot_net net, uint8_t[x] value } member;
57 Returns pointer to _member_ associated with the net, or NULL if not found 57 Returns pointer to _member_ associated with the net, or NULL if not found
58 member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping 58 member_size can be sizeof(ot_net) to reduce the lookup to a boolean mapping
59*/ 59*/
60void *get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size); 60void *accesslist_get_value_for_net(const ot_ip6 address, const ot_vector *vector, const size_t member_size);
61 61
62#ifdef WANT_IP_FROM_PROXY 62#ifdef WANT_IP_FROM_PROXY
63int proxylist_add_network(const ot_net *proxy, const ot_net *net); 63int proxylist_add_network(const ot_net *proxy, const ot_net *net);
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 e9666f9..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))
@@ -264,7 +264,7 @@ static config_kind parse_config_key(char **p) {
264 return CONFIG_UNKNOWN; 264 return CONFIG_UNKNOWN;
265} 265}
266 266
267int parse_configfile(char *config_filename) { 267static int parse_configfile(char *config_filename) {
268 FILE *config_file_handle; 268 FILE *config_file_handle;
269 char inbuf[512]; 269 char inbuf[512];
270 ot_ip6 tmpip; 270 ot_ip6 tmpip;
@@ -324,12 +324,12 @@ 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
new file mode 100644
index 0000000..537bcb0
--- /dev/null
+++ b/ot_fdstate.c
@@ -0,0 +1,194 @@
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 <stdatomic.h>
8#include <stdint.h>
9#include <stdlib.h>
10#include <sys/mman.h>
11#include <sys/resource.h>
12#include <sys/types.h>
13
14/* Opentracker */
15#include "ot_fdstate.h"
16
17enum { FD_FREE, FD_IDLE, FD_BUSY, FD_CLOSING };
18
19/* Generation and state share one word so that taking the state is also a
20 check that the generation has not moved */
21#define FD_GEN(w) ((uint32_t)((w) >> 32))
22#define FD_ST(w) ((uint32_t)(((w) >> 24) & 0xff))
23#define FD_WORD(g, s) (((uint64_t)(g) << 32) | ((uint64_t)(s) << 24))
24#define FD_SET_ST(w, s) (((w) & ~(uint64_t)0xff000000) | ((uint64_t)(s) << 24))
25
26typedef struct {
27 _Atomic uint64_t state;
28 _Atomic(void *) cookie;
29 _Atomic ot_time expiry;
30} ot_fdslot;
31
32/* A descriptor the kernel can hand out is below the limit it was handed out
33 under, but the limit can be raised after we sized the table, so an out of
34 range descriptor is rejected */
35#define FD_SLOTS_MAX (1u << 20)
36
37static ot_fdslot *g_slots;
38static size_t g_slot_count;
39/* Descriptors are handed out lowest first, so a sweep only has to walk as
40 far as the highest one ever seen rather than the whole table. */
41static _Atomic size_t g_slot_highest;
42
43int fdstate_init(void) {
44 struct rlimit limit;
45 size_t count = FD_SLOTS_MAX;
46
47 if (getrlimit(RLIMIT_NOFILE, &limit) == 0 && limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < FD_SLOTS_MAX)
48 count = (size_t)limit.rlim_cur;
49
50 g_slots = mmap(NULL, count * sizeof(ot_fdslot), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
51 if (g_slots == MAP_FAILED) {
52 g_slots = NULL;
53 return -1;
54 }
55
56 g_slot_count = count;
57 atomic_store(&g_slot_highest, 0);
58 return 0;
59}
60
61void fdstate_deinit(void) {
62 if (g_slots)
63 munmap(g_slots, g_slot_count * sizeof(ot_fdslot));
64 g_slots = NULL;
65 g_slot_count = 0;
66 atomic_store(&g_slot_highest, 0);
67}
68
69size_t fdstate_slots(void) { return g_slot_count; }
70
71static ot_fdslot *fdstate_slot(int fd) {
72 if (fd < 0 || (size_t)fd >= g_slot_count)
73 return NULL;
74 return g_slots + fd;
75}
76
77uint32_t fdstate_open(int fd, void *cookie, ot_time expiry) {
78 ot_fdslot *slot = fdstate_slot(fd);
79 uint64_t word;
80
81 if (!slot)
82 return 0;
83
84 {
85 size_t highest = atomic_load_explicit(&g_slot_highest, memory_order_relaxed);
86
87 while ((size_t)fd > highest &&
88 !atomic_compare_exchange_weak_explicit(&g_slot_highest, &highest, (size_t)fd, memory_order_relaxed,
89 memory_order_relaxed))
90 ;
91 }
92
93 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
94 atomic_store_explicit(&slot->cookie, cookie, memory_order_relaxed);
95 atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed);
96 /* Publishes the two above to whoever claims the slot next */
97 atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word), FD_IDLE), memory_order_release);
98
99 return FD_GEN(word);
100}
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
108int fdstate_claim(int fd, uint32_t generation) {
109 ot_fdslot *slot = fdstate_slot(fd);
110 uint64_t word;
111
112 if (!slot)
113 return 0;
114
115 word = atomic_load_explicit(&slot->state, memory_order_acquire);
116 for (;;) {
117 if (FD_GEN(word) != generation || FD_ST(word) != FD_IDLE)
118 return 0;
119 if (atomic_compare_exchange_weak_explicit(&slot->state, &word, FD_SET_ST(word, FD_BUSY), memory_order_acquire,
120 memory_order_relaxed))
121 return 1;
122 /* word now holds what was really there, so the test above runs again */
123 }
124}
125
126void fdstate_release(int fd) {
127 ot_fdslot *slot = fdstate_slot(fd);
128 uint64_t word;
129
130 if (!slot)
131 return;
132
133 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
134 atomic_store_explicit(&slot->state, FD_SET_ST(word, FD_IDLE), memory_order_release);
135}
136
137int fdstate_claim_expired(int fd, ot_time now) {
138 ot_fdslot *slot = fdstate_slot(fd);
139 uint64_t word;
140 ot_time expiry;
141
142 if (!slot)
143 return 0;
144
145 /* Reading before asking for the slot keeps the common case, a descriptor
146 that has not expired, off the bus entirely */
147 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
148 if (FD_ST(word) != FD_IDLE)
149 return 0;
150 expiry = atomic_load_explicit(&slot->expiry, memory_order_relaxed);
151 if (!expiry || expiry > now)
152 return 0;
153
154 /* Nothing can move the expiry while the slot sits at FD_IDLE, so winning
155 the slot is the last word */
156 return atomic_compare_exchange_strong_explicit(&slot->state, &word, FD_SET_ST(word, FD_CLOSING), memory_order_acquire,
157 memory_order_relaxed);
158}
159
160void fdstate_close(int fd) {
161 ot_fdslot *slot = fdstate_slot(fd);
162 uint64_t word;
163
164 if (!slot)
165 return;
166
167 word = atomic_load_explicit(&slot->state, memory_order_relaxed);
168 atomic_store_explicit(&slot->cookie, NULL, memory_order_relaxed);
169 atomic_store_explicit(&slot->expiry, 0, memory_order_relaxed);
170 atomic_store_explicit(&slot->state, FD_WORD(FD_GEN(word) + 1, FD_FREE), memory_order_release);
171}
172
173int fdstate_next_expired(ot_time now, int *cursor) {
174 size_t highest = atomic_load_explicit(&g_slot_highest, memory_order_relaxed);
175
176 for (; (size_t)*cursor <= highest; ++*cursor)
177 if (fdstate_claim_expired(*cursor, now))
178 return (*cursor)++;
179
180 return -1;
181}
182
183void *fdstate_cookie(int fd) {
184 ot_fdslot *slot = fdstate_slot(fd);
185
186 return slot ? atomic_load_explicit(&slot->cookie, memory_order_relaxed) : NULL;
187}
188
189void fdstate_set_expiry(int fd, ot_time expiry) {
190 ot_fdslot *slot = fdstate_slot(fd);
191
192 if (slot)
193 atomic_store_explicit(&slot->expiry, expiry, memory_order_relaxed);
194}
diff --git a/ot_fdstate.h b/ot_fdstate.h
new file mode 100644
index 0000000..c09f018
--- /dev/null
+++ b/ot_fdstate.h
@@ -0,0 +1,61 @@
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_FDSTATE_H__
7#define OT_FDSTATE_H__
8
9#include <stdint.h>
10
11#include "trackerlogic.h"
12
13/* What the tracker knows about a file descriptor: who owns it, when it
14 should be given up on, and whether anyone is working on it right now.
15
16 A descriptor number says nothing about identity, since the kernel hands
17 the same number out again as soon as it is closed. Every slot therefore
18 carries a generation that is bumped when the descriptor is given up, and
19 every caller that was told about a descriptor has to say which generation
20 it was told about. A caller holding a stale generation is turned away
21 instead of being handed somebody else's connection.
22
23 The table is sized once from RLIMIT_NOFILE and never moves, so looking a
24 descriptor up needs no synchronisation at all. */
25
26int fdstate_init(void);
27void fdstate_deinit(void);
28size_t fdstate_slots(void);
29
30/* Take a freshly accepted descriptor into the table and return the
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);
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
38/* Ask for exclusive use. Returns 0 if the descriptor has been recycled since
39 generation was handed out, or if somebody else is using it. */
40int fdstate_claim(int fd, uint32_t generation);
41void fdstate_release(int fd);
42
43/* The cleaner's side of the same thing: takes the descriptor only if nobody
44 is working on it and it really has expired. Never waits. */
45int fdstate_claim_expired(int fd, ot_time now);
46
47/* Give the descriptor up. The generation is bumped here, which has to happen
48 before close(2) so that the kernel cannot reissue the number while events
49 quoting the old generation are still in flight. */
50void fdstate_close(int fd);
51
52/* Walks the table for descriptors that have expired, handing back one at a
53 time until it returns -1. The cursor belongs to the caller, so several
54 sweepers can run without sharing anything. */
55int fdstate_next_expired(ot_time now, int *cursor);
56
57/* Both only valid while the caller holds the descriptor. */
58void *fdstate_cookie(int fd);
59void fdstate_set_expiry(int fd, ot_time expiry);
60
61#endif
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 eade79d..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));
@@ -122,16 +125,16 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre
122 r += 8; 125 r += 8;
123 break; 126 break;
124 case TASK_FULLSCRAPE_TPB_URLENCODED: 127 case TASK_FULLSCRAPE_TPB_URLENCODED:
125 r += fmt_urlencoded(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 331a457..36819c7 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,11 @@
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"
26#include "ot_fdstate.h"
27#include "ot_fmt.h"
28#include "ot_stats.h" 28#include "ot_stats.h"
29#include "ot_timeout.h" 29#include "ot_urlencoded.h"
30#include "scan_urlencoded_query.h"
31#include "trackerlogic.h" 30#include "trackerlogic.h"
32 31
33#define OT_MAXMULTISCRAPE_COUNT 64 32#define OT_MAXMULTISCRAPE_COUNT 64
@@ -39,11 +38,11 @@ ssize_t g_stats_path_len;
39enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 }; 38enum { SUCCESS_HTTP_HEADER_LENGTH = 80, SUCCESS_HTTP_SIZE_OFF = 17 };
40 39
41static void http_senddata(const int64_t sock, struct ot_workstruct *ws) { 40static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
42 struct http_data *cookie = io_getcookie(sock); 41 struct http_data *cookie = fdstate_cookie(sock);
43 ssize_t written_size; 42 ssize_t written_size;
44 43
45 if (!cookie) { 44 if (!cookie) {
46 io_close(sock); 45 event_del((int)sock), close((int)sock);
47 return; 46 return;
48 } 47 }
49 48
@@ -67,7 +66,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)) { 66 if ((written_size < 0) || ((written_size == ws->reply_size) && !ws->keep_alive)) {
68 buf_reset(&cookie->request); 67 buf_reset(&cookie->request);
69 free(cookie); 68 free(cookie);
70 io_close(sock); 69 event_del((int)sock), close((int)sock);
71 return; 70 return;
72 } 71 }
73 72
@@ -77,7 +76,7 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
77 if (!(outbuf = malloc(ws->reply_size - written_size))) { 76 if (!(outbuf = malloc(ws->reply_size - written_size))) {
78 buf_reset(&cookie->request); 77 buf_reset(&cookie->request);
79 free(cookie); 78 free(cookie);
80 io_close(sock); 79 event_del((int)sock), close((int)sock);
81 return; 80 return;
82 } 81 }
83 82
@@ -88,7 +87,7 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
88 free(outbuf); 87 free(outbuf);
89 buf_reset(&cookie->request); 88 buf_reset(&cookie->request);
90 free(cookie); 89 free(cookie);
91 io_close(sock); 90 event_del((int)sock), close((int)sock);
92 return; 91 return;
93 } 92 }
94 } 93 }
@@ -97,10 +96,10 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
97 96
98 /* writeable short data sockets just have a tcp timeout */ 97 /* writeable short data sockets just have a tcp timeout */
99 if (!ws->keep_alive) { 98 if (!ws->keep_alive) {
100 timeout_never(sock); 99 fdstate_set_expiry(sock, 0);
101 io_dontwantread(sock); 100 http_interest_drop(sock, cookie, EVENT_IN);
102 } 101 }
103 io_wantwrite(sock); 102 http_interest_add(sock, cookie, EVENT_OUT);
104 } 103 }
105} 104}
106 105
@@ -113,6 +112,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) 112#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) 113#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) 114#define HTTPERROR_500 return http_issue_error(sock, ws, CODE_HTTPERROR_500)
115static void http_interest(const int64_t sock, struct http_data *cookie, int what) {
116 if (cookie->interest == what)
117 return;
118 cookie->interest = what;
119 event_modify((int)sock, what, fdstate_generation((int)sock));
120}
121
122void http_interest_add(const int64_t sock, struct http_data *cookie, int what) {
123 http_interest(sock, cookie, cookie->interest | what);
124}
125
126void http_interest_drop(const int64_t sock, struct http_data *cookie, int what) {
127 http_interest(sock, cookie, cookie->interest & ~what);
128}
129
116ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code) { 130ssize_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", 131 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"}; 132 "403 Not Modest", "403 Access Denied", "404 Not Found", "500 Internal Server Error"};
@@ -134,7 +148,7 @@ ssize_t http_issue_error(const int64_t sock, struct ot_workstruct *ws, int code)
134} 148}
135 149
136ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) { 150ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t iovec_entries, struct iovec *iovector, int is_partial) {
137 struct http_data *cookie = io_getcookie(sock); 151 struct http_data *cookie = fdstate_cookie(sock);
138 char *header; 152 char *header;
139 const char *encoding = ""; 153 const char *encoding = "";
140 size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector); 154 size_t header_size, size = iovec_length(&iovec_entries, (const struct iovec **)&iovector);
@@ -157,7 +171,7 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t
157 171
158 /* If we came here, wait for the answer is over */ 172 /* If we came here, wait for the answer is over */
159 if (cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK) { 173 if (cookie->flag & STRUCT_HTTP_FLAG_WAITINGFORTASK) {
160 io_dontwantread(sock); 174 http_interest_drop(sock, cookie, EVENT_IN);
161 cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK; 175 cookie->flag &= ~STRUCT_HTTP_FLAG_WAITINGFORTASK;
162 } 176 }
163 177
@@ -207,8 +221,8 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t
207 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER; 221 cookie->flag &= ~STRUCT_HTTP_FLAG_CHUNKED_IN_TRANSFER;
208 } 222 }
209 223
210 timeout_in(sock, OT_CLIENT_TIMEOUT_SEND); 224 fdstate_set_expiry(sock, g_now_seconds + OT_CLIENT_TIMEOUT_SEND);
211 io_wantwrite(sock); 225 http_interest_add(sock, cookie, EVENT_OUT);
212 return 0; 226 return 0;
213} 227}
214 228
@@ -243,7 +257,8 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
243 {"url", TASK_FULLSCRAPE_TPB_URLENCODED}, {"txt", TASK_FULLSCRAPE_TPB_ASCII}, 257 {"url", TASK_FULLSCRAPE_TPB_URLENCODED}, {"txt", TASK_FULLSCRAPE_TPB_ASCII},
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 = io_getcookie(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:
@@ -301,9 +316,9 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
301 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; 316 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
302 317
303 /* Clients waiting for us should not easily timeout */ 318 /* Clients waiting for us should not easily timeout */
304 timeout_never(sock); 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
@@ -314,13 +329,15 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
314 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK; 329 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK;
315 330
316 /* Complex stats also include expensive memory debugging tools */ 331 /* Complex stats also include expensive memory debugging tools */
317 timeout_never(sock); 332 fdstate_set_expiry(sock, 0);
318 stats_deliver(sock, mode); 333 stats_deliver(sock, mode);
319 return ws->reply_size = -2; 334 return ws->reply_size = -2;
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
@@ -334,7 +351,7 @@ typedef struct {
334 351
335#ifdef WANT_FULLSCRAPE 352#ifdef WANT_FULLSCRAPE
336static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) { 353static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *ws) {
337 struct http_data *cookie = io_getcookie(sock); 354 struct http_data *cookie = fdstate_cookie(sock);
338 int format = 0; 355 int format = 0;
339 356
340#ifdef WANT_MODEST_FULLSCRAPES 357#ifdef WANT_MODEST_FULLSCRAPES
@@ -394,9 +411,9 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *
394 /* Pass this task to the worker thread */ 411 /* Pass this task to the worker thread */
395 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED; 412 cookie->flag |= STRUCT_HTTP_FLAG_WAITINGFORTASK | STRUCT_HTTP_FLAG_CHUNKED;
396 /* Clients waiting for us should not easily timeout */ 413 /* Clients waiting for us should not easily timeout */
397 timeout_never(sock); 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
@@ -404,9 +421,10 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *
404static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { 421static 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}}; 422 static const ot_keywords keywords_scrape[] = {{"info_hash", 1}, {NULL, -3}};
406 423
407 ot_hash *multiscrape_buf = (ot_hash *)ws->request; 424 ot_hash *multiscrape_buf = (ot_hash *)ws->request;
408 int scanon = 1; 425 char *reply = ws->reply;
409 size_t numwant = 0; 426 int scanon = 1;
427 size_t numwant = 0;
410 428
411 /* This is to hack around stupid clients that send "scrape ?info_hash" */ 429 /* This is to hack around stupid clients that send "scrape ?info_hash" */
412 if (read_ptr[-1] != '?') { 430 if (read_ptr[-1] != '?') {
@@ -418,18 +436,18 @@ static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws,
418 } 436 }
419 437
420 while (scanon) { 438 while (scanon) {
421 switch (scan_find_keywords(keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM)) { 439 switch (urlencoded_match_keyword(keywords_scrape, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
422 case -2: 440 case -2:
423 scanon = 0; 441 scanon = 0;
424 break; /* TERMINATOR */ 442 break; /* TERMINATOR */
425 default: 443 default:
426 HTTPERROR_400_PARAM; /* PARSE ERROR */ 444 HTTPERROR_400_PARAM; /* PARSE ERROR */
427 case -3: 445 case -3:
428 scan_urlencoded_skipvalue(&read_ptr); 446 urlencoded_skip_value(&read_ptr);
429 break; 447 break;
430 case 1: /* matched "info_hash" */ 448 case 1: /* matched "info_hash" */
431 /* ignore this, when we have less than 20 bytes */ 449 /* 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)) 450 if (urlencoded_parse(&read_ptr, (char *)(multiscrape_buf + numwant++), SCAN_SEARCHPATH_VALUE) != (ssize_t)sizeof(ot_hash))
433 HTTPERROR_400_PARAM; 451 HTTPERROR_400_PARAM;
434 break; 452 break;
435 } 453 }
@@ -444,7 +462,8 @@ static ssize_t http_handle_scrape(const int64_t sock, struct ot_workstruct *ws,
444 numwant = OT_MAXMULTISCRAPE_COUNT; 462 numwant = OT_MAXMULTISCRAPE_COUNT;
445 463
446 /* Enough for http header + whole scrape string */ 464 /* Enough for http header + whole scrape string */
447 ws->reply_size = return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, ws->reply); 465 return_tcp_scrape_for_torrent((const ot_hash *)multiscrape_buf, numwant, &reply, ws->outbuf + G_OUTBUF_SIZE);
466 ws->reply_size = reply - ws->reply;
448 stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size); 467 stats_issue_event(EVENT_SCRAPE, FLAG_TCP, ws->reply_size);
449 return ws->reply_size; 468 return ws->reply_size;
450} 469}
@@ -480,13 +499,24 @@ static ot_keywords keywords_announce[] = {{"port", 1}, {"left", 2}, {"event",
480#endif 499#endif
481 {"peer_id", 9}, {NULL, -3}}; 500 {"peer_id", 9}, {NULL, -3}};
482static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}}; 501static ot_keywords keywords_announce_event[] = {{"completed", 1}, {"stopped", 2}, {NULL, -3}};
502
503/* For the replies that are one fixed string and nothing else, where the only
504 thing the caller wants back is how long it turned out */
505static ssize_t reply_literal(struct ot_workstruct *ws, const char *text) {
506 char *reply = ws->reply;
507
508 fmt_bytes(&reply, ws->outbuf + G_OUTBUF_SIZE, text, strlen(text));
509 return reply - ws->reply;
510}
511
483static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) { 512static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct *ws, char *read_ptr) {
484 size_t numwant; 513 size_t numwant;
485 int tmp, scanon; 514 int64_t tmp;
515 int scanon;
486 unsigned short port = 0; 516 unsigned short port = 0;
487 char *write_ptr; 517 char *write_ptr;
488 ssize_t len; 518 ssize_t len;
489 struct http_data *cookie = io_getcookie(sock); 519 struct http_data *cookie = fdstate_cookie(sock);
490 520
491 /* This is to hack around stupid clients that send "announce ?info_hash" */ 521 /* This is to hack around stupid clients that send "announce ?info_hash" */
492 if (read_ptr[-1] != '?') { 522 if (read_ptr[-1] != '?') {
@@ -501,7 +531,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)) { 531 if (accesslist_is_blessed(cookie->ip, OT_PERMISSION_MAY_PROXY)) {
502 ot_ip6 proxied_ip; 532 ot_ip6 proxied_ip;
503 char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for"); 533 char *fwd = http_header(ws->request, ws->header_size, "x-forwarded-for");
504 if (fwd && ip_scan(fwd, proxied_ip)) { 534 if (fwd && ip_parse(fwd, proxied_ip)) {
505 OT_SETIP(ws->peer, proxied_ip); 535 OT_SETIP(ws->peer, proxied_ip);
506 } else 536 } else
507 OT_SETIP(ws->peer, cookie->ip); 537 OT_SETIP(ws->peer, cookie->ip);
@@ -518,32 +548,32 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
518 scanon = 1; 548 scanon = 1;
519 549
520 while (scanon) { 550 while (scanon) {
521 switch (scan_find_keywords(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM)) { 551 switch (urlencoded_match_keyword(keywords_announce, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
522 case -2: 552 case -2:
523 scanon = 0; 553 scanon = 0;
524 break; /* TERMINATOR */ 554 break; /* TERMINATOR */
525 case -1: 555 case -1:
526 HTTPERROR_400_PARAM; /* PARSE ERROR */ 556 HTTPERROR_400_PARAM; /* PARSE ERROR */
527 case -3: 557 case -3:
528 scan_urlencoded_skipvalue(&read_ptr); 558 urlencoded_skip_value(&read_ptr);
529 break; 559 break;
530 case 1: /* matched "port" */ 560 case 1: /* matched "port" */
531 len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); 561 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)) 562 if ((len <= 0) || scan_int(write_ptr, len, &tmp) || (tmp > 0xffff))
533 HTTPERROR_400_PARAM; 563 HTTPERROR_400_PARAM;
534 port = htons(tmp); 564 port = htons((uint16_t)tmp);
535 OT_SETPORT(&ws->peer, &port); 565 OT_SETPORT(&ws->peer, &port);
536 break; 566 break;
537 case 2: /* matched "left" */ 567 case 2: /* matched "left" */
538 if ((len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) 568 if ((len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
539 HTTPERROR_400_PARAM; 569 HTTPERROR_400_PARAM;
540 if (scan_fixed_int(write_ptr, len, &tmp)) 570 if (scan_int(write_ptr, len, &tmp))
541 tmp = 0; 571 tmp = 0;
542 if (!tmp) 572 if (!tmp)
543 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING; 573 OT_PEERFLAG(&ws->peer) |= PEER_FLAG_SEEDING;
544 break; 574 break;
545 case 3: /* matched "event" */ 575 case 3: /* matched "event" */
546 switch (scan_find_keywords(keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE)) { 576 switch (urlencoded_match_keyword(keywords_announce_event, &read_ptr, SCAN_SEARCHPATH_VALUE)) {
547 case -1: 577 case -1:
548 HTTPERROR_400_PARAM; 578 HTTPERROR_400_PARAM;
549 case 1: /* matched "completed" */ 579 case 1: /* matched "completed" */
@@ -557,18 +587,18 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
557 } 587 }
558 break; 588 break;
559 case 4: /* matched "numwant" */ 589 case 4: /* matched "numwant" */
560 len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE); 590 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE);
561 if ((len <= 0) || scan_fixed_int(write_ptr, len, &tmp)) 591 if ((len <= 0) || scan_int(write_ptr, len, &tmp))
562 HTTPERROR_400_PARAM; 592 HTTPERROR_400_PARAM;
563 if (tmp < 0) 593 if (tmp < 0)
564 tmp = 50; 594 tmp = 50;
565 if (tmp > 200) 595 if (tmp > 200)
566 tmp = 200; 596 tmp = 200;
567 numwant = tmp; 597 numwant = (size_t)tmp;
568 break; 598 break;
569 case 5: /* matched "compact" */ 599 case 5: /* matched "compact" */
570 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);
571 if ((len <= 0) || scan_fixed_int(write_ptr, len, &tmp)) 601 if ((len <= 0) || scan_int(write_ptr, len, &tmp))
572 HTTPERROR_400_PARAM; 602 HTTPERROR_400_PARAM;
573 if (!tmp) 603 if (!tmp)
574 HTTPERROR_400_COMPACT; 604 HTTPERROR_400_COMPACT;
@@ -577,7 +607,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
577 if (ws->hash) 607 if (ws->hash)
578 HTTPERROR_400_DOUBLEHASH; 608 HTTPERROR_400_DOUBLEHASH;
579 /* ignore this, when we have less than 20 bytes */ 609 /* ignore this, when we have less than 20 bytes */
580 if (scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20) 610 if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20)
581 HTTPERROR_400_PARAM; 611 HTTPERROR_400_PARAM;
582 ws->hash = (ot_hash *)write_ptr; 612 ws->hash = (ot_hash *)write_ptr;
583 break; 613 break;
@@ -585,9 +615,9 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
585 case 7: /* matched "ip" */ 615 case 7: /* matched "ip" */
586 { 616 {
587 char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16; 617 char *tmp_buf1 = ws->reply, *tmp_buf2 = ws->reply + 16;
588 len = scan_urlencoded_query(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE); 618 len = urlencoded_parse(&read_ptr, tmp_buf2, SCAN_SEARCHPATH_VALUE);
589 tmp_buf2[len] = 0; 619 tmp_buf2[len] = 0;
590 if ((len <= 0) || !ip_scan(tmp_buf2, tmp_buf1)) 620 if ((len <= 0) || ip_parse(tmp_buf2, tmp_buf1) != (size_t)len)
591 HTTPERROR_400_PARAM; 621 HTTPERROR_400_PARAM;
592 OT_SETIP(&ws->peer, tmp_buf1); 622 OT_SETIP(&ws->peer, tmp_buf1);
593 } break; 623 } break;
@@ -601,21 +631,21 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
601 uint64_t value; 631 uint64_t value;
602 signed short parsed, bits; 632 signed short parsed, bits;
603 633
604 len = scan_urlencoded_query(&read_ptr, tmp_buf, SCAN_SEARCHPATH_VALUE); 634 len = urlencoded_parse(&read_ptr, tmp_buf, SCAN_SEARCHPATH_VALUE);
605 tmp_buf[len] = 0; 635 tmp_buf[len] = 0;
606 if (len <= 0) 636 if (len <= 0)
607 HTTPERROR_400_PARAM; 637 HTTPERROR_400_PARAM;
608 if (*tmp_buf == '-') { 638 if (*tmp_buf == '-') {
609 loglist_reset(); 639 loglist_reset();
610 return ws->reply_size = sprintf(ws->reply, "Successfully removed.\n"); 640 return ws->reply_size = reply_literal(ws, "Successfully removed.\n");
611 } 641 }
612 parsed = ip_scan(tmp_buf, net.address); 642 parsed = ip_parse(tmp_buf, net.address);
613 if (!parsed) 643 if (!parsed)
614 HTTPERROR_400_PARAM; 644 HTTPERROR_400_PARAM;
615 if (tmp_buf[parsed++] != '/') 645 if (tmp_buf[parsed++] != '/')
616 bits = 128; 646 bits = 128;
617 else { 647 else {
618 parsed = scan_udec(tmp_buf + parsed, &value, 128); 648 parsed = scan_uint(tmp_buf + parsed, &value, 128);
619 if (!parsed) 649 if (!parsed)
620 HTTPERROR_400_PARAM; 650 HTTPERROR_400_PARAM;
621 bits = (signed short)value; 651 bits = (signed short)value;
@@ -624,13 +654,13 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
624 } 654 }
625 net.bits = bits; 655 net.bits = bits;
626 loglist_add_network(&net); 656 loglist_add_network(&net);
627 return ws->reply_size = sprintf(ws->reply, "Successfully added.\n"); 657 return ws->reply_size = reply_literal(ws, "Successfully added.\n");
628 //} 658 //}
629 } break; 659 } break;
630#endif 660#endif
631 case 9: /* matched "peer_id" */ 661 case 9: /* matched "peer_id" */
632 /* ignore this, when we have less than 20 bytes */ 662 /* ignore this, when we have less than 20 bytes */
633 if (scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20) 663 if (urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_SEARCHPATH_VALUE) != 20)
634 HTTPERROR_400_PARAM; 664 HTTPERROR_400_PARAM;
635 ws->peer_id = write_ptr; 665 ws->peer_id = write_ptr;
636 break; 666 break;
@@ -647,7 +677,7 @@ static ssize_t http_handle_announce(const int64_t sock, struct ot_workstruct
647 677
648 /* Scanned whole query string */ 678 /* Scanned whole query string */
649 if (!ws->hash) 679 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"); 680 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 681
652 if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED) 682 if (OT_PEERFLAG(&ws->peer) & PEER_FLAG_STOPPED)
653 ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws); 683 ws->reply_size = remove_peer_from_torrent(FLAG_TCP, ws);
@@ -663,7 +693,7 @@ ssize_t http_handle_request(const int64_t sock, struct ot_workstruct *ws) {
663 char *read_ptr = ws->request, *write_ptr; 693 char *read_ptr = ws->request, *write_ptr;
664 694
665#ifdef WANT_FULLLOG_NETWORKS 695#ifdef WANT_FULLLOG_NETWORKS
666 struct http_data *cookie = io_getcookie(sock); 696 struct http_data *cookie = fdstate_cookie(sock);
667 if (loglist_check_address(cookie->ip)) { 697 if (loglist_check_address(cookie->ip)) {
668 ot_log *log = malloc(sizeof(ot_log)); 698 ot_log *log = malloc(sizeof(ot_log));
669 if (log) { 699 if (log) {
@@ -709,7 +739,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 739 In reality we abandoned requiring the url to be correct. This now
710 only decodes url encoded characters, we check for announces and 740 only decodes url encoded characters, we check for announces and
711 scrapes by looking for "a*" or "sc" */ 741 scrapes by looking for "a*" or "sc" */
712 len = scan_urlencoded_query(&read_ptr, write_ptr = read_ptr, SCAN_PATH); 742 len = urlencoded_parse(&read_ptr, write_ptr = read_ptr, SCAN_PATH);
713 743
714 /* If parsing returned an error, leave with not found */ 744 /* If parsing returned an error, leave with not found */
715 if (g_redirecturl && (len == -2)) 745 if (g_redirecturl && (len == -2))
@@ -758,9 +788,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); 788 reply_off = SUCCESS_HTTP_SIZE_OFF - snprintf(ws->outbuf, 0, "%zd", ws->reply_size);
759 ws->reply = ws->outbuf + reply_off; 789 ws->reply = ws->outbuf + reply_off;
760 790
761 /* 2. Now we sprintf our header so that sprintf writes its terminating '\0' exactly one byte before content starts. Complete 791 /* 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 */ 792 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); 793 one byte '\n', we will copy over '\0' in next step */
794 ws->reply_size += 1 + snprintf(ws->reply, (size_t)(SUCCESS_HTTP_HEADER_LENGTH - reply_off),
795 "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: %zd\r\n\r", ws->reply_size);
764 796
765 /* 3. Finally we join both blocks neatly */ 797 /* 3. Finally we join both blocks neatly */
766 ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n'; 798 ws->outbuf[SUCCESS_HTTP_HEADER_LENGTH - 1] = '\n';
diff --git a/ot_http.h b/ot_http.h
index 003b545..1631220 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
@@ -25,11 +26,16 @@ typedef enum {
25 26
26struct http_data { 27struct http_data {
27 ot_buf request; 28 ot_buf request;
29 int interest; /* EVENT_IN and EVENT_OUT as asked for */
28 ot_batch *batch; 30 ot_batch *batch;
29 ot_ip6 ip; 31 ot_ip6 ip;
30 STRUCT_HTTP_FLAG flag; 32 STRUCT_HTTP_FLAG flag;
31}; 33};
32 34
35/* Add to or drop from what this connection is waiting to be told about */
36void http_interest_add(const int64_t s, struct http_data *cookie, int what);
37void http_interest_drop(const int64_t s, struct http_data *cookie, int what);
38
33ssize_t http_handle_request(const int64_t s, struct ot_workstruct *ws); 39ssize_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); 40ssize_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); 41ssize_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_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_timeout.h b/ot_timeout.h
deleted file mode 100644
index 1027c2e..0000000
--- a/ot_timeout.h
+++ /dev/null
@@ -1,39 +0,0 @@
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_TIMEOUT_H__
7#define OT_TIMEOUT_H__
8
9#include <stdint.h>
10#include <string.h>
11
12/* Libowfat */
13#include "io.h"
14
15#include "trackerlogic.h"
16
17/* io_timeout wants an absolute TAI64 deadline and io_timeouted skips any
18 socket whose deadline is zero, so that is how a socket is told to wait
19 indefinitely.
20
21 The deadline is taken from the cached clock rather than from
22 gettimeofday, which is too expensive to call at this rate on FreeBSD. */
23
24static inline void timeout_never(int64_t sock) {
25 tai6464 t;
26
27 memset(&t, 0, sizeof(t));
28 io_timeout(sock, t);
29}
30
31static inline void timeout_in(int64_t sock, unsigned int seconds) {
32 tai6464 t;
33
34 memset(&t, 0, sizeof(t));
35 tai_unix(&t.sec, g_now_seconds + seconds);
36 io_timeout(sock, t);
37}
38
39#endif
diff --git a/ot_udp.c b/ot_udp.c
index 7f6517f..5bf9d52 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"
diff --git a/scan_urlencoded_query.c b/ot_urlencoded.c
index 4825a4a..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,21 +146,8 @@ 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 fmt_urlencoded(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[] = "$/.=~-_";
165 static const char hexdigit[] = "0123456789abcdef"; 152 static const char hexdigit[] = "0123456789abcdef";
166 size_t written = 0, i; 153 size_t written = 0, i;
diff --git a/scan_urlencoded_query.h b/ot_urlencoded.h
index f71752d..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,22 +40,16 @@ 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. */
59size_t fmt_urlencoded(char *dest, const uint8_t *src, size_t len); 53size_t urlencoded_format(char *dest, const uint8_t *src, size_t len);
60 54
61#endif 55#endif
diff --git a/ot_vector.c b/ot_vector.c
index 7f18246..a56d55b 100644
--- a/ot_vector.c
+++ b/ot_vector.c
@@ -19,7 +19,7 @@ static int vector_compare_peer4(const void *peer1, const void *peer2) { return m
19 no exact match is found. In that case it sets exactmatch 0 and gives 19 no exact match is found. In that case it sets exactmatch 0 and gives
20 calling functions the chance to insert data 20 calling functions the chance to insert data
21*/ 21*/
22void *binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch) { 22void *vector_binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch) {
23 size_t interval = member_count; 23 size_t interval = member_count;
24 24
25 while (interval) { 25 while (interval) {
@@ -50,70 +50,75 @@ static uint8_t vector_hash_peer(ot_peer const *peer, size_t compare_size, size_t
50 50
51/* This is the generic insert operation for our vector type. 51/* This is the generic insert operation for our vector type.
52 It tries to locate the object at "key" with size "member_size" by comparing its first "compare_size" bytes with 52 It tries to locate the object at "key" with size "member_size" by comparing its first "compare_size" bytes with
53 those of objects in vector. Our special "binary_search" function does that and either returns the match or a 53 those of objects in vector. Our special "vector_binary_search" function does that and either returns the match or a
54 pointer to where the object is to be inserted. vector_find_or_insert makes space for the object and copies it, 54 pointer to where the object is to be inserted. vector_find_or_insert makes space for the object and copies it,
55 if it wasn't found in vector. Caller needs to check the passed "exactmatch" variable to see, whether an insert 55 if it wasn't found in vector. Caller needs to check the passed "exactmatch" variable to see, whether an insert
56 took place. If resizing the vector failed, NULL is returned, else the pointer to the object in vector. 56 took place. If resizing the vector failed, NULL is returned, else the pointer to the object in vector.
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 = 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)
91 vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size); 96 vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size);
92 match = binary_search(peer, vector->data, vector->size, peer_size, compare_size, exactmatch); 97 match = vector_binary_search(peer, vector->data, vector->size, peer_size, compare_size, exactmatch);
93 98
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;
@@ -137,7 +142,7 @@ int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size)
137 vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size); 142 vector = ((ot_vector *)vector->data) + vector_hash_peer(peer, compare_size, vector->size);
138 143
139 end = ((ot_peer *)vector->data) + peer_size * vector->size; 144 end = ((ot_peer *)vector->data) + peer_size * vector->size;
140 match = (ot_peer *)binary_search(peer, vector->data, vector->size, peer_size, compare_size, &exactmatch); 145 match = (ot_peer *)vector_binary_search(peer, vector->data, vector->size, peer_size, compare_size, &exactmatch);
141 if (!exactmatch) 146 if (!exactmatch)
142 return 0; 147 return 0;
143 148
@@ -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/ot_vector.h b/ot_vector.h
index fbc75f4..d553956 100644
--- a/ot_vector.h
+++ b/ot_vector.h
@@ -26,7 +26,7 @@ typedef struct {
26/* trackerlogic.h needs ot_vector, break circular dependency here */ 26/* trackerlogic.h needs ot_vector, break circular dependency here */
27#include "trackerlogic.h" 27#include "trackerlogic.h"
28 28
29void *binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch); 29void *vector_binary_search(const void *const key, const void *base, const size_t member_count, const size_t member_size, size_t compare_size, int *exactmatch);
30void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch); 30void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch);
31ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch); 31ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch);
32 32
diff --git a/proxy.c b/proxy.c
index 63b4c4f..a5c6bb7 100644
--- a/proxy.c
+++ b/proxy.c
@@ -19,6 +19,7 @@
19 19
20/* Opentracker */ 20/* Opentracker */
21#include "ot_batch.h" 21#include "ot_batch.h"
22#include "ot_fdstate.h"
22#include "ot_ip.h" 23#include "ot_ip.h"
23#include "ot_mutex.h" 24#include "ot_mutex.h"
24#include "ot_random.h" 25#include "ot_random.h"
@@ -148,7 +149,7 @@ size_t add_peer_to_torrent_proxy(ot_hash hash, ot_peer *peer, size_t peer_size)
148size_t remove_peer_from_torrent_proxy(ot_hash hash, ot_peer *peer, size_t peer_size) { 149size_t remove_peer_from_torrent_proxy(ot_hash hash, ot_peer *peer, size_t peer_size) {
149 int exactmatch; 150 int exactmatch;
150 ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash); 151 ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash);
151 ot_torrent *torrent = binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); 152 ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
152 153
153 if (exactmatch) { 154 if (exactmatch) {
154 ot_peerlist *peer_list = peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4; 155 ot_peerlist *peer_list = peer_list = peer_size == OT_PEER_SIZE6 ? torrent->peer_list6 : torrent->peer_list4;
@@ -267,7 +268,7 @@ static void handle_reconnects(void) {
267 continue; /* No socket for you */ 268 continue; /* No socket for you */
268 io_fd(newfd); 269 io_fd(newfd);
269 io_wantwrite(newfd); /* So we will be informed when it is connected */ 270 io_wantwrite(newfd); /* So we will be informed when it is connected */
270 io_setcookie(newfd, g_connections + i); 271 fdstate_open(newfd, g_connections + i, 0);
271 272
272 /* Prepare connection info block */ 273 /* Prepare connection info block */
273 reset_info_block(g_connections + i); 274 reset_info_block(g_connections + i);
@@ -310,7 +311,7 @@ static void handle_accept(int64_t serversocket) {
310 g_connections[i].port = port; 311 g_connections[i].port = port;
311 g_connections[i].fd = newfd; 312 g_connections[i].fd = newfd;
312 313
313 io_setcookie(newfd, g_connections + i); 314 fdstate_open(newfd, g_connections + i, 0);
314 315
315 /* We expect the connecting side to begin with its tracker_id */ 316 /* We expect the connecting side to begin with its tracker_id */
316 io_wantread(newfd); 317 io_wantread(newfd);
@@ -325,7 +326,7 @@ static void handle_read(int64_t peersocket) {
325 int i; 326 int i;
326 int64_t datalen; 327 int64_t datalen;
327 uint32_t tracker_id; 328 uint32_t tracker_id;
328 proxy_peer *peer = io_getcookie(peersocket); 329 proxy_peer *peer = fdstate_cookie(peersocket);
329 330
330 if (!peer) { 331 if (!peer) {
331 /* Can't happen ;) */ 332 /* Can't happen ;) */
@@ -391,7 +392,7 @@ static void handle_read(int64_t peersocket) {
391 392
392/* Can write new sync data to the stream */ 393/* Can write new sync data to the stream */
393static void handle_write(int64_t peersocket) { 394static void handle_write(int64_t peersocket) {
394 proxy_peer *peer = io_getcookie(peersocket); 395 proxy_peer *peer = fdstate_cookie(peersocket);
395 396
396 if (!peer) { 397 if (!peer) {
397 /* Can't happen ;) */ 398 /* Can't happen ;) */
@@ -464,7 +465,7 @@ static void server_mainloop() {
464 465
465 /* Loop over readable sockets */ 466 /* Loop over readable sockets */
466 while ((sock = io_canread()) != -1) { 467 while ((sock = io_canread()) != -1) {
467 const void *cookie = io_getcookie(sock); 468 const void *cookie = fdstate_cookie(sock);
468 if ((uintptr_t)cookie == FLAG_SERVERSOCKET) 469 if ((uintptr_t)cookie == FLAG_SERVERSOCKET)
469 handle_accept(sock); 470 handle_accept(sock);
470 else 471 else
@@ -493,7 +494,7 @@ static int64_t ot_try_bind(ot_ip6 ip, uint16_t port) {
493 if (!io_fd(sock)) 494 if (!io_fd(sock))
494 panic("io_fd"); 495 panic("io_fd");
495 496
496 io_setcookie(sock, (void *)FLAG_SERVERSOCKET); 497 fdstate_open(sock, (void *)FLAG_SERVERSOCKET, 0);
497 io_wantread(sock); 498 io_wantread(sock);
498 return sock; 499 return sock;
499} 500}
@@ -506,7 +507,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
506 ++s; 507 ++s;
507 if (*s == '[') 508 if (*s == '[')
508 ++s, ++bracket; /* for v6 style notation */ 509 ++s, ++bracket; /* for v6 style notation */
509 if (!(off = ip_scan(s, ip))) 510 if (!(off = ip_parse(s, ip)))
510 return 0; 511 return 0;
511 s += off; 512 s += off;
512 if (*s == 0 || isspace(*s)) 513 if (*s == 0 || isspace(*s))
@@ -521,7 +522,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
521 if (*(s++) != ':') 522 if (*(s++) != ':')
522 return 0; 523 return 0;
523 } 524 }
524 if (!(off = scan_udec(s, &value, 65535))) 525 if (!(off = scan_uint(s, &value, 65535)))
525 return 0; 526 return 0;
526 *port = (uint16_t)value; 527 *port = (uint16_t)value;
527 return off + s - src; 528 return off + s - src;
@@ -535,6 +536,8 @@ int main(int argc, char **argv) {
535 int scanon = 1, lbound = 0, sbound = 0; 536 int scanon = 1, lbound = 0, sbound = 0;
536 537
537 random_init(); 538 random_init();
539 if (fdstate_init() == -1)
540 panic("fdstate_init");
538 g_tracker_id = random_uint32(); 541 g_tracker_id = random_uint32();
539 542
540 while (scanon) { 543 while (scanon) {
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..c8da1e2
--- /dev/null
+++ b/tests/integration/harness.py
@@ -0,0 +1,166 @@
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 socket
11import struct
12import subprocess
13import sys
14import tempfile
15import time
16import unittest
17import urllib.error
18import urllib.parse
19import urllib.request
20
21TRACKER = os.environ.get("OPENTRACKER", os.path.join(os.path.dirname(__file__), "..", "..", "opentracker.debug"))
22
23
24def bdecode(data):
25 """Enough bencode for what a tracker answers."""
26
27 def value(i):
28 if data[i : i + 1] == b"d":
29 i, out = i + 1, {}
30 while data[i : i + 1] != b"e":
31 i, key = value(i)
32 i, out[key] = value(i)
33 return i + 1, out
34 if data[i : i + 1] == b"l":
35 i, out = i + 1, []
36 while data[i : i + 1] != b"e":
37 i, item = value(i)
38 out.append(item)
39 return i + 1, out
40 if data[i : i + 1] == b"i":
41 end = data.index(b"e", i)
42 return end + 1, int(data[i + 1 : end])
43 colon = data.index(b":", i)
44 length = int(data[i:colon])
45 return colon + 1 + length, data[colon + 1 : colon + 1 + length]
46
47 return value(0)[1]
48
49
50def free_port():
51 probe = socket.socket()
52 probe.bind(("127.0.0.1", 0))
53 port = probe.getsockname()[1]
54 probe.close()
55 return port
56
57
58class Tracker:
59 """One tracker process, listening on loopback."""
60
61 def __init__(self, extra_config=""):
62 self.port = free_port()
63 self.dir = tempfile.mkdtemp(prefix="ot-integration-")
64 self.config = os.path.join(self.dir, "opentracker.conf")
65 with open(self.config, "w") as handle:
66 handle.write("listen.tcp 127.0.0.1:%d\n" % self.port)
67 handle.write("listen.udp 127.0.0.1:%d\n" % self.port)
68 handle.write(extra_config)
69 self.log = open(os.path.join(self.dir, "tracker.log"), "w+b")
70 environment = dict(os.environ, ASAN_OPTIONS="detect_leaks=0:abort_on_error=0")
71 self.process = subprocess.Popen([TRACKER, "-f", self.config], stdout=self.log, stderr=self.log, env=environment)
72 self._wait_for_port()
73
74 def _wait_for_port(self):
75 for _ in range(200):
76 if self.process.poll() is not None:
77 raise AssertionError("tracker exited at once:\n" + self.output())
78 try:
79 probe = socket.create_connection(("127.0.0.1", self.port), timeout=0.2)
80 probe.close()
81 return
82 except OSError:
83 time.sleep(0.05)
84 raise AssertionError("tracker never listened on %d:\n%s" % (self.port, self.output()))
85
86 def output(self):
87 self.log.flush()
88 self.log.seek(0)
89 return self.log.read().decode("utf-8", "replace")
90
91 def stop(self):
92 """Returns anything the tracker complained about on the way out."""
93 if self.process.poll() is None:
94 self.process.kill()
95 self.process.wait()
96 complaints = [
97 line
98 for line in self.output().splitlines()
99 if "Sanitizer" in line or "runtime error" in line or line.startswith("ERROR")
100 ]
101 self.log.close()
102 return complaints
103
104 # ----------------------------------------------------------- over http
105
106 def get(self, path, timeout=10):
107 return urllib.request.urlopen("http://127.0.0.1:%d%s" % (self.port, path), timeout=timeout).read()
108
109 def announce(self, info_hash, peer_id=b"-OT0001-aaaaaaaaaaaa", port=6881, left=0, **extra):
110 query = {
111 "info_hash": urllib.parse.quote_from_bytes(info_hash, safe=""),
112 "peer_id": urllib.parse.quote_from_bytes(peer_id, safe=""),
113 "port": port,
114 "left": left,
115 }
116 query.update(extra)
117 return bdecode(self.get("/announce?" + "&".join("%s=%s" % item for item in query.items())))
118
119 def scrape(self, info_hash):
120 return bdecode(self.get("/scrape?info_hash=" + urllib.parse.quote_from_bytes(info_hash, safe="")))
121
122 def stats(self, mode):
123 return self.get("/stats?mode=" + mode)
124
125 def torrent_count(self):
126 return int(self.stats("torr").split()[0])
127
128 # ------------------------------------------------------------ over udp
129
130 def udp_connect(self):
131 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
132 sock.settimeout(5)
133 sock.sendto(struct.pack(">QII", 0x41727101980, 0, 1), ("127.0.0.1", self.port))
134 answer = sock.recv(2048)
135 action, _, connection_id = struct.unpack(">IIQ", answer[:16])
136 assert action == 0, "udp connect answered action %d" % action
137 return sock, connection_id
138
139 def udp_announce(self, sock, connection_id, info_hash, left=0, event=0, port=6881, numwant=-1):
140 sock.sendto(
141 struct.pack(
142 ">QII20s20sQQQIIIiH", connection_id, 1, 2, info_hash, b"p" * 20, 0, left, 0, event, 0, 0, numwant, port
143 ),
144 ("127.0.0.1", self.port),
145 )
146 answer = sock.recv(2048)
147 action, _, interval, leechers, seeders = struct.unpack(">IIIII", answer[:20])
148 assert action == 1, "udp announce answered action %d" % action
149 return {"interval": interval, "leechers": leechers, "seeders": seeders, "peers": answer[20:]}
150
151
152class TrackerTestCase(unittest.TestCase):
153 """Gives every test its own tracker and fails it on any sanitizer output."""
154
155 config = ""
156
157 def setUp(self):
158 self.tracker = Tracker(self.config)
159
160 def tearDown(self):
161 complaints = self.tracker.stop()
162 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints))
163
164 def hash_for(self, name):
165 """A distinct info hash per test, so tests cannot leak into each other."""
166 return (name.encode() + b"\0" * 20)[:20]
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/runner.c b/tests/runner.c
index 148b928..a92315e 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -14,7 +14,7 @@ struct module {
14 void (*run)(void); 14 void (*run)(void);
15}; 15};
16 16
17static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urlencoded_query}, 17static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
18 {"ot_iovec", test_iovec}, 18 {"ot_iovec", test_iovec},
19 {"ot_vector", test_vector}, 19 {"ot_vector", test_vector},
20 {"ot_random", test_random}, 20 {"ot_random", test_random},
@@ -25,6 +25,9 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle
25 {"ot_socket", test_socket}, 25 {"ot_socket", test_socket},
26 {"ot_mutex", test_mutex}, 26 {"ot_mutex", test_mutex},
27 {"ot_batch", test_batch}, 27 {"ot_batch", test_batch},
28 {"ot_fdstate", test_fdstate},
29 {"ot_event", test_event},
30 {"ot_fmt", test_fmt},
28 {"ot_accesslist", test_accesslist}, 31 {"ot_accesslist", test_accesslist},
29 {NULL, NULL}}; 32 {NULL, NULL}};
30 33
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_fdstate.c b/tests/t_fdstate.c
new file mode 100644
index 0000000..bf8724a
--- /dev/null
+++ b/tests/t_fdstate.c
@@ -0,0 +1,224 @@
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 <pthread.h>
8#include <stdatomic.h>
9#include <stdint.h>
10#include <stdlib.h>
11#include <string.h>
12
13/* Opentracker */
14#include "ot_fdstate.h"
15#include "test.h"
16
17/* ------------------------------------------------------- one thread first */
18
19static void case_open_claim_release(void) {
20 char cookie[4];
21 uint32_t generation;
22
23 test_case("a descriptor taken into the table can be claimed and gives back its cookie");
24 generation = fdstate_open(7, cookie, 0);
25 CHECK_INT(fdstate_claim(7, generation), 1);
26 CHECK_PTR(fdstate_cookie(7), cookie);
27 fdstate_release(7);
28
29 test_case("and can be claimed again once released");
30 CHECK_INT(fdstate_claim(7, generation), 1);
31 fdstate_release(7);
32 fdstate_close(7);
33}
34
35static void case_one_holder_at_a_time(void) {
36 uint32_t generation = fdstate_open(8, NULL, 0);
37
38 test_case("a descriptor somebody holds cannot be claimed again");
39 CHECK_INT(fdstate_claim(8, generation), 1);
40 CHECK_INT(fdstate_claim(8, generation), 0);
41 fdstate_release(8);
42 CHECK_INT(fdstate_claim(8, generation), 1);
43 fdstate_release(8);
44 fdstate_close(8);
45}
46
47static void case_stale_generation_is_refused(void) {
48 char first[4], second[4];
49 uint32_t old, new;
50
51 test_case("a caller quoting the generation of a closed descriptor is turned away");
52 old = fdstate_open(9, first, 0);
53 fdstate_close(9);
54 new = fdstate_open(9, second, 0);
55 CHECK(old != new);
56 CHECK_INT(fdstate_claim(9, old), 0);
57
58 test_case("while the one quoting the new generation gets the new cookie");
59 CHECK_INT(fdstate_claim(9, new), 1);
60 CHECK_PTR(fdstate_cookie(9), second);
61 fdstate_release(9);
62 fdstate_close(9);
63}
64
65static void case_expiry(void) {
66 uint32_t generation = fdstate_open(10, NULL, 100);
67
68 test_case("a descriptor past its expiry is collected");
69 CHECK_INT(fdstate_claim_expired(10, 99), 0);
70 CHECK_INT(fdstate_claim_expired(10, 100), 1);
71 fdstate_close(10);
72
73 test_case("one that never expires is never collected");
74 generation = fdstate_open(10, NULL, 0);
75 CHECK_INT(fdstate_claim_expired(10, 1 << 30), 0);
76
77 test_case("and one somebody is working on is left alone");
78 fdstate_set_expiry(10, 1);
79 CHECK_INT(fdstate_claim(10, generation), 1);
80 CHECK_INT(fdstate_claim_expired(10, 1 << 30), 0);
81 fdstate_release(10);
82 CHECK_INT(fdstate_claim_expired(10, 1 << 30), 1);
83 fdstate_close(10);
84}
85
86static void case_out_of_range(void) {
87 test_case("a descriptor outside the table is refused rather than trusted");
88 CHECK_INT(fdstate_claim(-1, 0), 0);
89 CHECK_INT(fdstate_claim((int)fdstate_slots(), 0), 0);
90 CHECK_INT(fdstate_claim_expired(-1, 0), 0);
91 CHECK_PTR(fdstate_cookie(-1), NULL);
92}
93
94/* --------------------------------------------------------- then in anger */
95
96/* The race this is about only exists while a descriptor is being recycled,
97 so recycling is driven by work done rather than by a clock: every slot is
98 given up after RECYCLE_AFTER claims, whatever the machine's speed. */
99
100#define STRESS_SLOTS 64
101#define RECYCLE_AFTER 32
102#define STRESS_CLAIMS 200000
103#define STRESS_GIVEUP (STRESS_CLAIMS * 100)
104
105static _Atomic long stress_claims, stress_recycles, stress_violations, stress_attempts;
106static _Atomic int stress_lives[STRESS_SLOTS];
107static _Atomic int stress_live[STRESS_SLOTS];
108
109/* Generation and cookie are published in one word so a worker can never read
110 a cookie that belongs to a different generation than the one it claimed
111 with. Each connection gets a cookie nobody else ever gets. */
112static _Atomic uint64_t stress_conn[STRESS_SLOTS];
113static _Atomic uint64_t stress_next_cookie;
114
115#define STRESS_CONN(cookie, gen) (((uint64_t)(cookie) << 32) | (uint32_t)(gen))
116#define STRESS_CONN_COOKIE(w) ((void *)(uintptr_t)((w) >> 32))
117#define STRESS_CONN_GEN(w) ((uint32_t)(w))
118
119static int stress_running(void) {
120 return atomic_load(&stress_claims) < STRESS_CLAIMS && atomic_load(&stress_attempts) < STRESS_GIVEUP;
121}
122
123static void *stress_worker(void *arg) {
124 unsigned seed = (unsigned)(uintptr_t)arg;
125
126 while (stress_running()) {
127 int fd = (int)(rand_r(&seed) % STRESS_SLOTS);
128 uint64_t conn = atomic_load(&stress_conn[fd]);
129
130 atomic_fetch_add(&stress_attempts, 1);
131 if (!fdstate_claim(fd, STRESS_CONN_GEN(conn)))
132 continue;
133
134 if (fdstate_cookie(fd) != STRESS_CONN_COOKIE(conn))
135 atomic_fetch_add(&stress_violations, 1);
136 if (atomic_fetch_sub(&stress_lives[fd], 1) <= 1)
137 fdstate_set_expiry(fd, 1); /* retired by its holder, as a handler would */
138 if (fdstate_cookie(fd) != STRESS_CONN_COOKIE(conn))
139 atomic_fetch_add(&stress_violations, 1);
140
141 fdstate_release(fd);
142 atomic_fetch_add(&stress_claims, 1);
143 }
144 return NULL;
145}
146
147static void *stress_keeper(void *arg) {
148 (void)arg;
149 while (stress_running()) {
150 int fd;
151
152 for (fd = 0; fd < STRESS_SLOTS; ++fd) {
153 if (atomic_load(&stress_live[fd])) {
154 if (!fdstate_claim_expired(fd, 1))
155 continue; /* a worker has it, come back */
156 fdstate_close(fd);
157 atomic_store(&stress_live[fd], 0);
158 atomic_fetch_add(&stress_recycles, 1);
159 }
160 {
161 void *cookie = (void *)(uintptr_t)(atomic_fetch_add(&stress_next_cookie, 1) + 1);
162 uint32_t gen = fdstate_open(fd, cookie, 0);
163
164 atomic_store(&stress_lives[fd], RECYCLE_AFTER);
165 atomic_store(&stress_conn[fd], STRESS_CONN((uintptr_t)cookie, gen));
166 atomic_store(&stress_live[fd], 1);
167 }
168 }
169 }
170 return NULL;
171}
172
173static void case_recycling_under_load(void) {
174 pthread_t worker[4], keeper;
175 size_t i;
176
177 test_case("no worker is ever handed a connection from another generation");
178 atomic_store(&stress_claims, 0);
179 atomic_store(&stress_recycles, 0);
180 atomic_store(&stress_violations, 0);
181 atomic_store(&stress_attempts, 0);
182 atomic_store(&stress_next_cookie, 0);
183 /* Every slot is opened and published before any worker runs: a worker only
184 ever learns of a descriptor from whoever registered it, and never
185 invents a generation of its own. */
186 for (i = 0; i < STRESS_SLOTS; ++i) {
187 void *cookie = (void *)(uintptr_t)(atomic_fetch_add(&stress_next_cookie, 1) + 1);
188 uint32_t gen = fdstate_open((int)i, cookie, 0);
189
190 atomic_store(&stress_lives[i], RECYCLE_AFTER);
191 atomic_store(&stress_conn[i], STRESS_CONN((uintptr_t)cookie, gen));
192 atomic_store(&stress_live[i], 1);
193 }
194
195 pthread_create(&keeper, NULL, stress_keeper, NULL);
196 for (i = 0; i < sizeof(worker) / sizeof(worker[0]); ++i)
197 pthread_create(&worker[i], NULL, stress_worker, (void *)(uintptr_t)(i + 1));
198 for (i = 0; i < sizeof(worker) / sizeof(worker[0]); ++i)
199 pthread_join(worker[i], NULL);
200 pthread_join(keeper, NULL);
201
202 CHECK_INT(atomic_load(&stress_violations), 0);
203
204 test_case("and the run actually recycled descriptors, or it proved nothing");
205 CHECK(atomic_load(&stress_claims) >= STRESS_CLAIMS);
206 CHECK(atomic_load(&stress_recycles) > STRESS_CLAIMS / (RECYCLE_AFTER * 4));
207
208 for (i = 0; i < STRESS_SLOTS; ++i)
209 fdstate_close((int)i);
210}
211
212void test_fdstate(void) {
213 CHECK_INT(fdstate_init(), 0);
214 CHECK(fdstate_slots() > STRESS_SLOTS);
215
216 case_open_claim_release();
217 case_one_holder_at_a_time();
218 case_stale_generation_is_refused();
219 case_expiry();
220 case_out_of_range();
221 case_recycling_under_load();
222
223 fdstate_deinit();
224}
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 f660ea3..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. */
@@ -265,7 +238,7 @@ static void case_fmt_passes_safe_bytes(void) {
265 size_t n; 238 size_t n;
266 239
267 test_case("alphanumerics and the safe punctuation pass through"); 240 test_case("alphanumerics and the safe punctuation pass through");
268 n = fmt_urlencoded(dest, src, 11); 241 n = urlencoded_format(dest, src, 11);
269 dest[n] = 0; 242 dest[n] = 0;
270 CHECK_INT(n, 11); 243 CHECK_INT(n, 11);
271 CHECK_STR(dest, "aZ09$-./=_~"); 244 CHECK_STR(dest, "aZ09$-./=_~");
@@ -277,7 +250,7 @@ static void case_fmt_escapes_the_rest(void) {
277 size_t n; 250 size_t n;
278 251
279 test_case("everything else becomes a percent escape"); 252 test_case("everything else becomes a percent escape");
280 n = fmt_urlencoded(dest, src, sizeof(src)); 253 n = urlencoded_format(dest, src, sizeof(src));
281 dest[n] = 0; 254 dest[n] = 0;
282 CHECK_INT(n, 18); 255 CHECK_INT(n, 18);
283 CHECK_STR(dest, "%20%25%26%2b%3a%3f"); 256 CHECK_STR(dest, "%20%25%26%2b%3a%3f");
@@ -289,7 +262,7 @@ static void case_fmt_hex_is_lower_case(void) {
289 size_t n; 262 size_t n;
290 263
291 test_case("escapes use lower case hex digits"); 264 test_case("escapes use lower case hex digits");
292 n = fmt_urlencoded(dest, src, sizeof(src)); 265 n = urlencoded_format(dest, src, sizeof(src));
293 dest[n] = 0; 266 dest[n] = 0;
294 CHECK_INT(n, 9); 267 CHECK_INT(n, 9);
295 CHECK_STR(dest, "%00%ab%ff"); 268 CHECK_STR(dest, "%00%ab%ff");
@@ -300,7 +273,7 @@ static void case_fmt_zero_length(void) {
300 273
301 test_case("a zero length input writes nothing"); 274 test_case("a zero length input writes nothing");
302 memset(dest, 0x5a, sizeof(dest)); 275 memset(dest, 0x5a, sizeof(dest));
303 CHECK_INT(fmt_urlencoded(dest, (const uint8_t *)"abc", 0), 0); 276 CHECK_INT(urlencoded_format(dest, (const uint8_t *)"abc", 0), 0);
304 CHECK_MEM(dest, "\x5a\x5a\x5a\x5a", 4); 277 CHECK_MEM(dest, "\x5a\x5a\x5a\x5a", 4);
305} 278}
306 279
@@ -312,7 +285,7 @@ static void case_fmt_worst_case_fits(void) {
312 test_case("a hash of entirely unsafe bytes needs three times its length"); 285 test_case("a hash of entirely unsafe bytes needs three times its length");
313 for (i = 0; i < sizeof(src); ++i) 286 for (i = 0; i < sizeof(src); ++i)
314 src[i] = 0xff; 287 src[i] = 0xff;
315 n = fmt_urlencoded(dest, src, sizeof(src)); 288 n = urlencoded_format(dest, src, sizeof(src));
316 dest[n] = 0; 289 dest[n] = 0;
317 CHECK_INT(n, 60); 290 CHECK_INT(n, 60);
318 for (i = 0; i < 20; ++i) 291 for (i = 0; i < 20; ++i)
@@ -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];
@@ -338,15 +311,15 @@ static void case_fmt_decoder_disagrees_on_dollar(void) {
338 ssize_t got; 311 ssize_t got;
339 312
340 test_case("the encoder passes '$' but the decoder rejects it"); 313 test_case("the encoder passes '$' but the decoder rejects it");
341 n = fmt_urlencoded(dest, src, sizeof(src)); 314 n = urlencoded_format(dest, src, sizeof(src));
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 a8186b2..6e048e1 100644
--- a/tests/t_vector.c
+++ b/tests/t_vector.c
@@ -13,7 +13,7 @@
13#include "trackerlogic.h" 13#include "trackerlogic.h"
14#include "test.h" 14#include "test.h"
15 15
16/* binary_search returns the matching member when there is one, and otherwise 16/* vector_binary_search returns the matching member when there is one, and otherwise
17 the position at which the key would have to be inserted to keep the vector 17 the position at which the key would have to be inserted to keep the vector
18 sorted. exactmatch distinguishes the two. */ 18 sorted. exactmatch distinguishes the two. */
19 19
@@ -25,7 +25,7 @@ static void case_search_finds_member(void) {
25 void *found; 25 void *found;
26 26
27 test_case("an existing key is found"); 27 test_case("an existing key is found");
28 found = binary_search(key, sorted, 5, 4, 4, &exactmatch); 28 found = vector_binary_search(key, sorted, 5, 4, 4, &exactmatch);
29 CHECK(exactmatch != 0); 29 CHECK(exactmatch != 0);
30 CHECK_PTR(found, sorted[2]); 30 CHECK_PTR(found, sorted[2]);
31} 31}
@@ -36,9 +36,9 @@ static void case_search_finds_first_and_last(void) {
36 int exactmatch; 36 int exactmatch;
37 37
38 test_case("the first and last members are found"); 38 test_case("the first and last members are found");
39 CHECK_PTR(binary_search(first, sorted, 5, 4, 4, &exactmatch), sorted[0]); 39 CHECK_PTR(vector_binary_search(first, sorted, 5, 4, 4, &exactmatch), sorted[0]);
40 CHECK(exactmatch != 0); 40 CHECK(exactmatch != 0);
41 CHECK_PTR(binary_search(last, sorted, 5, 4, 4, &exactmatch), sorted[4]); 41 CHECK_PTR(vector_binary_search(last, sorted, 5, 4, 4, &exactmatch), sorted[4]);
42 CHECK(exactmatch != 0); 42 CHECK(exactmatch != 0);
43} 43}
44 44
@@ -49,11 +49,11 @@ static void case_search_insert_position(void) {
49 void *slot; 49 void *slot;
50 50
51 test_case("a missing key yields its insert position"); 51 test_case("a missing key yields its insert position");
52 slot = binary_search(middle, sorted, 5, 4, 4, &exactmatch); 52 slot = vector_binary_search(middle, sorted, 5, 4, 4, &exactmatch);
53 CHECK_INT(exactmatch, 0); 53 CHECK_INT(exactmatch, 0);
54 CHECK_PTR(slot, sorted[2]); 54 CHECK_PTR(slot, sorted[2]);
55 55
56 slot = binary_search(before, sorted, 5, 4, 4, &exactmatch); 56 slot = vector_binary_search(before, sorted, 5, 4, 4, &exactmatch);
57 CHECK_INT(exactmatch, 0); 57 CHECK_INT(exactmatch, 0);
58 CHECK_PTR(slot, sorted[0]); 58 CHECK_PTR(slot, sorted[0]);
59} 59}
@@ -64,7 +64,7 @@ static void case_search_past_the_end(void) {
64 void *slot; 64 void *slot;
65 65
66 test_case("a key above every member yields the position past the end"); 66 test_case("a key above every member yields the position past the end");
67 slot = binary_search(beyond, sorted, 5, 4, 4, &exactmatch); 67 slot = vector_binary_search(beyond, sorted, 5, 4, 4, &exactmatch);
68 CHECK_INT(exactmatch, 0); 68 CHECK_INT(exactmatch, 0);
69 CHECK_PTR(slot, sorted[5]); 69 CHECK_PTR(slot, sorted[5]);
70} 70}
@@ -74,7 +74,7 @@ static void case_search_empty(void) {
74 void *base = (void *)sorted; 74 void *base = (void *)sorted;
75 75
76 test_case("searching an empty vector reports no match at the base"); 76 test_case("searching an empty vector reports no match at the base");
77 CHECK_PTR(binary_search(sorted[0], base, 0, 4, 4, &exactmatch), base); 77 CHECK_PTR(vector_binary_search(sorted[0], base, 0, 4, 4, &exactmatch), base);
78 CHECK_INT(exactmatch, 0); 78 CHECK_INT(exactmatch, 0);
79} 79}
80 80
@@ -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 fd574c2..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);
@@ -88,5 +88,8 @@ void test_ip(void);
88void test_socket(void); 88void test_socket(void);
89void test_mutex(void); 89void test_mutex(void);
90void test_batch(void); 90void test_batch(void);
91void test_fdstate(void);
92void test_event(void);
93void test_fmt(void);
91 94
92#endif 95#endif
diff --git a/trackerlogic.c b/trackerlogic.c
index 74fea6d..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}
@@ -381,7 +383,7 @@ size_t return_peers_for_torrent(struct ot_workstruct *ws, ot_torrent *torrent, s
381size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) { 383size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) {
382 int exactmatch, delta_torrentcount = 0; 384 int exactmatch, delta_torrentcount = 0;
383 ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash); 385 ot_vector *torrents_list = mutex_bucket_lock_by_hash(hash);
384 ot_torrent *torrent = binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); 386 ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
385 387
386 if (!exactmatch) { 388 if (!exactmatch) {
387 memset(reply, 0, 12); 389 memset(reply, 0, 12);
@@ -403,47 +405,41 @@ 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;
415 ot_hash const *hash = hash_list + i; 416 ot_hash const *hash = hash_list + i;
416 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*hash); 417 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*hash);
417 ot_torrent *torrent = binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); 418 ot_torrent *torrent = vector_binary_search(hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
418 419
419 if (exactmatch) { 420 if (exactmatch) {
420 if (clean_single_torrent(torrent)) { 421 if (clean_single_torrent(torrent)) {
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;
443size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) { 439size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) {
444 int exactmatch; 440 int exactmatch;
445 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*ws->hash); 441 ot_vector *torrents_list = mutex_bucket_lock_by_hash(*ws->hash);
446 ot_torrent *torrent = binary_search(ws->hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch); 442 ot_torrent *torrent = vector_binary_search(ws->hash, torrents_list->data, torrents_list->size, sizeof(ot_torrent), OT_HASH_COMPARE_SIZE, &exactmatch);
447 ot_peerlist *peer_list = &dummy_list; 443 ot_peerlist *peer_list = &dummy_list;
448 size_t peer_size; /* initialized in next line */ 444 size_t peer_size; /* initialized in next line */
449 ot_peer const *peer_src = peer_from_peer6(&ws->peer, &peer_size); 445 ot_peer const *peer_src = peer_from_peer6(&ws->peer, &peer_size);
@@ -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