summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile61
-rw-r--r--opentracker.c101
-rw-r--r--ot_accesslist.c6
-rw-r--r--ot_clean.c3
-rw-r--r--ot_config.c29
-rw-r--r--ot_event.c332
-rw-r--r--ot_event.h39
-rw-r--r--ot_fdstate.c6
-rw-r--r--ot_fdstate.h4
-rw-r--r--ot_fmt.c42
-rw-r--r--ot_fmt.h21
-rw-r--r--ot_fullscrape.c53
-rw-r--r--ot_hex.c4
-rw-r--r--ot_hex.h4
-rw-r--r--ot_http.c142
-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_udp.c3
-rw-r--r--ot_urlencoded.c (renamed from scan_urlencoded_query.c)27
-rw-r--r--ot_urlencoded.h (renamed from scan_urlencoded_query.h)12
-rw-r--r--ot_vector.c47
-rw-r--r--proxy.c4
-rw-r--r--tests/integration/.gitignore2
-rw-r--r--tests/integration/harness.py198
-rw-r--r--tests/integration/test_announce.py129
-rw-r--r--tests/integration/test_dualstack.py89
-rw-r--r--tests/runner.c76
-rw-r--r--tests/t_event.c222
-rw-r--r--tests/t_fmt.c108
-rw-r--r--tests/t_hex.c28
-rw-r--r--tests/t_iovec.c20
-rw-r--r--tests/t_ip.c54
-rw-r--r--tests/t_scan.c124
-rw-r--r--tests/t_urlencoded.c (renamed from tests/t_scan_urlencoded_query.c)83
-rw-r--r--tests/t_vector.c46
-rw-r--r--tests/test.h4
-rw-r--r--trackerlogic.c55
-rw-r--r--trackerlogic.h2
50 files changed, 2020 insertions, 599 deletions
diff --git a/Makefile b/Makefile
index 87c20e2..b172af6 100644
--- a/Makefile
+++ b/Makefile
@@ -47,10 +47,6 @@ include Makefile.gzip
47#FEATURES+=-DWANT_DEV_RANDOM 47#FEATURES+=-DWANT_DEV_RANDOM
48FEATURES+=-DWANT_FULLSCRAPE 48FEATURES+=-DWANT_FULLSCRAPE
49 49
50# You need libowfat version 0.34 to allow for automatic release of chunks during
51# full scrape transfer, if you rely on an older versions, enable this flag
52#FEATURES+=-DWANT_NO_AUTO_FREE
53
54# opentracker takes its randomness from arc4random, which is part of libc on 50# opentracker takes its randomness from arc4random, which is part of libc on
55# the BSDs, on macOS and on glibc 2.36 and newer. On an older glibc or on musl 51# the BSDs, on macOS and on glibc 2.36 and newer. On an older glibc or on musl
56# it comes from libbsd instead, so uncomment the next include to link it. 52# it comes from libbsd instead, so uncomment the next include to link it.
@@ -64,12 +60,17 @@ GIT_VERSION=`sh -c 'command -v git >/dev/null && test -d .git && git rev-parse H
64OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage 60OPTS_debug=-D_DEBUG -g -ggdb # -pg -fprofile-arcs -ftest-coverage
65OPTS_production=-O3 61OPTS_production=-O3
66 62
67CFLAGS+=-I$(LIBOWFAT_HEADERS) -DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra -Wwrite-strings #-ansi -pedantic 63CFLAGS+=-DGIT_VERSION=$(GIT_VERSION) -Wall -pipe -pthread -Wextra -Wwrite-strings #-ansi -pedantic
68LDFLAGS+=-L$(LIBOWFAT_LIBRARY) -lowfat -pthread 64LDFLAGS+=-pthread
65
66# Only the proxy, which has not been ported since the tracker went dual stack,
67# still wants libowfat.
68CFLAGS_libowfat=-I$(LIBOWFAT_HEADERS)
69LDFLAGS_libowfat=-L$(LIBOWFAT_LIBRARY) -lowfat
69 70
70BINARY =opentracker 71BINARY =opentracker
71HEADERS=trackerlogic.h scan_urlencoded_query.h ot_batch.h ot_buf.h ot_fdstate.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h 72HEADERS=trackerlogic.h ot_urlencoded.h ot_batch.h ot_buf.h ot_event.h ot_fdstate.h ot_fmt.h ot_hex.h ot_ip.h ot_config.h ot_scan.h ot_socket.h ot_mutex.h ot_random.h ot_stats.h ot_vector.h ot_clean.h ot_udp.h ot_iovec.h ot_fullscrape.h ot_accesslist.h ot_http.h ot_livesync.h ot_rijndael.h
72SOURCES=opentracker.c trackerlogic.c scan_urlencoded_query.c ot_batch.c ot_buf.c ot_fdstate.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c 73SOURCES=opentracker.c trackerlogic.c ot_urlencoded.c ot_batch.c ot_buf.c ot_event.c ot_fdstate.c ot_fmt.c ot_hex.c ot_ip.c ot_config.c ot_scan.c ot_socket.c ot_mutex.c ot_random.c ot_stats.c ot_vector.c ot_clean.c ot_udp.c ot_iovec.c ot_fullscrape.c ot_accesslist.c ot_http.c ot_livesync.c ot_rijndael.c
73SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c 74SOURCES_proxy=proxy.c ot_batch.c ot_fdstate.c ot_vector.c ot_mutex.c ot_random.c ot_scan.c ot_ip.c ot_socket.c
74 75
75OBJECTS = $(SOURCES:%.c=%.o) 76OBJECTS = $(SOURCES:%.c=%.o)
@@ -84,8 +85,8 @@ all: $(BINARY) $(BINARY).debug
84CFLAGS_production = $(CFLAGS) $(OPTS_production) $(FEATURES) 85CFLAGS_production = $(CFLAGS) $(OPTS_production) $(FEATURES)
85CFLAGS_debug = $(CFLAGS) $(OPTS_debug) $(FEATURES) 86CFLAGS_debug = $(CFLAGS) $(OPTS_debug) $(FEATURES)
86 87
87CFLAGS_proxy = $(CFLAGS_production) -DWANT_SYNC_LIVE 88CFLAGS_proxy = $(CFLAGS_production) $(CFLAGS_libowfat) -DWANT_SYNC_LIVE
88CFLAGS_proxy_debug = $(CFLAGS_debug) -DWANT_SYNC_LIVE 89CFLAGS_proxy_debug = $(CFLAGS_debug) $(CFLAGS_libowfat) -DWANT_SYNC_LIVE
89 90
90$(BINARY): $(OBJECTS) 91$(BINARY): $(OBJECTS)
91 $(CC) -o $@ $(OBJECTS) $(LDFLAGS) 92 $(CC) -o $@ $(OBJECTS) $(LDFLAGS)
@@ -93,9 +94,9 @@ $(BINARY): $(OBJECTS)
93$(BINARY).debug: $(OBJECTS_debug) 94$(BINARY).debug: $(OBJECTS_debug)
94 $(CC) -o $@ $(OBJECTS_debug) $(LDFLAGS) 95 $(CC) -o $@ $(OBJECTS_debug) $(LDFLAGS)
95proxy: $(OBJECTS_proxy) 96proxy: $(OBJECTS_proxy)
96 $(CC) -o $@ $(OBJECTS_proxy) $(CFLAGS_production) $(LDFLAGS) 97 $(CC) -o $@ $(OBJECTS_proxy) $(CFLAGS_production) $(LDFLAGS) $(LDFLAGS_libowfat)
97proxy.debug: $(OBJECTS_proxy_debug) 98proxy.debug: $(OBJECTS_proxy_debug)
98 $(CC) -o $@ $(OBJECTS_proxy_debug) $(LDFLAGS) 99 $(CC) -o $@ $(OBJECTS_proxy_debug) $(LDFLAGS) $(LDFLAGS_libowfat)
99 100
100$(OBJECTS): $(HEADERS) 101$(OBJECTS): $(HEADERS)
101$(OBJECTS_debug): $(HEADERS) 102$(OBJECTS_debug): $(HEADERS)
@@ -119,8 +120,8 @@ $(OBJECTS_proxy_debug): $(HEADERS)
119# has to. tests/stubs.c supplies the few symbols those modules reference but 120# has to. tests/stubs.c supplies the few symbols those modules reference but
120# the tests never reach. 121# the tests never reach.
121TESTBIN=tests/runner 122TESTBIN=tests/runner
122TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_scan_urlencoded_query.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c 123TEST_SOURCES=tests/runner.c tests/test.c tests/stubs.c tests/t_urlencoded.c tests/t_iovec.c tests/t_vector.c tests/t_random.c tests/t_hex.c tests/t_accesslist.c tests/t_scan.c tests/t_buf.c tests/t_ip.c tests/t_socket.c tests/t_mutex.c tests/t_batch.c tests/t_fdstate.c tests/t_event.c tests/t_fmt.c
123TEST_UNITS=scan_urlencoded_query.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c 124TEST_UNITS=ot_urlencoded.c ot_fmt.c ot_event.c ot_batch.c ot_fdstate.c ot_buf.c ot_ip.c ot_socket.c ot_iovec.c ot_vector.c ot_random.c ot_hex.c ot_scan.c ot_accesslist.c ot_mutex.c
124TEST_HEADERS=tests/test.h $(HEADERS) 125TEST_HEADERS=tests/test.h $(HEADERS)
125# Tests reach code that sits behind feature flags, so the units under test are 126# Tests reach code that sits behind feature flags, so the units under test are
126# built with those features enabled. A test binary is not a production binary. 127# built with those features enabled. A test binary is not a production binary.
@@ -133,10 +134,38 @@ $(TESTBIN): $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS)
133check: $(TESTBIN) $(BINARY) 134check: $(TESTBIN) $(BINARY)
134 ./$(TESTBIN) 135 ./$(TESTBIN)
135 136
136.PHONY: all check clean install 137# The same unit tests under the sanitizers. Use clang over $(CC) because the
138# two toolchains do not check the same things
139# Override CC_sanitize to use another compiler.
140CC_sanitize?=clang
141CFLAGS_sanitize=$(CFLAGS_test) -fsanitize=address,undefined -fno-omit-frame-pointer
142LDFLAGS_sanitize=$(LDFLAGS) -fsanitize=address,undefined
143
144$(TESTBIN).sanitize: $(TEST_SOURCES) $(TEST_UNITS) $(TEST_HEADERS)
145 $(CC_sanitize) -o $@ $(CFLAGS_sanitize) $(TEST_SOURCES) $(TEST_UNITS) $(LDFLAGS_sanitize)
146
147check-sanitize: $(TESTBIN).sanitize
148 ./$(TESTBIN).sanitize
149
150# Black box tests: these drive the real binary over the wire, so they cover
151# the event loop and the parsers working together, which the unit tests
152# cannot reach. Built with the sanitizers on, so the tracker reports its own
153# memory errors and the harness turns those into failures.
154PYTHON?=python3
155FEATURES_integration=$(FEATURES) -DWANT_IP_FROM_QUERY_STRING
156CFLAGS_integration=$(CFLAGS) $(OPTS_debug) $(FEATURES_integration) -fsanitize=address,undefined -fno-omit-frame-pointer
157LDFLAGS_integration=$(LDFLAGS) -fsanitize=address,undefined
158
159opentracker.integration: $(SOURCES) $(HEADERS)
160 $(CC) -o $@ $(CFLAGS_integration) $(SOURCES) $(LDFLAGS_integration)
161
162check-integration: opentracker.integration
163 OPENTRACKER=./opentracker.integration $(PYTHON) -m unittest discover -s tests/integration
164
165.PHONY: all check check-sanitize check-integration clean install
137 166
138clean: 167clean:
139 rm -rf opentracker opentracker.debug proxy proxy.debug $(TESTBIN) *.o *~ 168 rm -rf opentracker opentracker.debug opentracker.integration proxy proxy.debug $(TESTBIN) $(TESTBIN).sanitize *.o *~
140 169
141install: 170install:
142 install -m 755 opentracker $(DESTDIR)$(BINDIR) 171 install -m 755 opentracker $(DESTDIR)$(BINDIR)
diff --git a/opentracker.c b/opentracker.c
index 0f0b314..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,6 +31,7 @@
34#include "ot_mutex.h" 31#include "ot_mutex.h"
35#include "ot_random.h" 32#include "ot_random.h"
36#include "ot_scan.h" 33#include "ot_scan.h"
34#include "ot_event.h"
37#include "ot_fdstate.h" 35#include "ot_fdstate.h"
38#include "ot_socket.h" 36#include "ot_socket.h"
39#include "ot_stats.h" 37#include "ot_stats.h"
@@ -130,14 +128,15 @@ static void handle_dead(const int64_t sock) {
130 } 128 }
131 /* The generation has to move before the kernel can reissue the number */ 129 /* The generation has to move before the kernel can reissue the number */
132 fdstate_close(sock); 130 fdstate_close(sock);
133 io_close(sock); 131 event_del((int)sock);
132 close((int)sock);
134} 133}
135 134
136static void handle_read(const int64_t sock, struct ot_workstruct *ws) { 135static void handle_read(const int64_t sock, struct ot_workstruct *ws) {
137 struct http_data *cookie = fdstate_cookie(sock); 136 struct http_data *cookie = fdstate_cookie(sock);
138 ssize_t byte_count = io_tryread(sock, ws->inbuf, G_INBUF_SIZE); 137 ssize_t byte_count = read((int)sock, ws->inbuf, G_INBUF_SIZE);
139 138
140 if (byte_count == 0 || byte_count == -3) { 139 if (byte_count == 0 || (byte_count == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)) {
141 handle_dead(sock); 140 handle_dead(sock);
142 return; 141 return;
143 } 142 }
@@ -197,7 +196,7 @@ static void handle_write(const int64_t sock) {
197 196
198 /* 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 */
199 if (chunked) 198 if (chunked)
200 io_dontwantwrite(sock); 199 http_interest_drop(sock, cookie, EVENT_OUT);
201 else 200 else
202 handle_dead(sock); 201 handle_dead(sock);
203} 202}
@@ -210,25 +209,30 @@ static void handle_accept(const int64_t serversocket) {
210 209
211 while ((sock = socket_accept(serversocket, ip, &port)) != -1) { 210 while ((sock = socket_accept(serversocket, ip, &port)) != -1) {
212 211
213 /* Put fd into a non-blocking mode */ 212 socket_set_blocking((int)sock, 0);
214 io_nonblock(sock);
215 213
216 if (!io_fd(sock) || !(cookie = (struct http_data *)malloc(sizeof(struct http_data)))) { 214 if (!(cookie = (struct http_data *)malloc(sizeof(struct http_data)))) {
217 io_close(sock); 215 close((int)sock);
218 continue; 216 continue;
219 } 217 }
220 memset(cookie, 0, sizeof(struct http_data)); 218 memset(cookie, 0, sizeof(struct http_data));
221 memcpy(cookie->ip, ip, sizeof(ot_ip6)); 219 memcpy(cookie->ip, ip, sizeof(ot_ip6));
220 cookie->interest = EVENT_IN;
222 221
223 fdstate_open(sock, cookie, g_now_seconds + OT_CLIENT_TIMEOUT); 222 if (event_add((int)sock, EVENT_IN, fdstate_open(sock, cookie, g_now_seconds + OT_CLIENT_TIMEOUT)) == -1) {
224 io_wantread(sock); 223 fdstate_close(sock);
224 free(cookie);
225 close((int)sock);
226 continue;
227 }
225 228
226 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip); 229 stats_issue_event(EVENT_ACCEPT, FLAG_TCP, (uintptr_t)ip);
227
228 } 230 }
229 io_eagain(serversocket);
230} 231}
231 232
233/* How many ready descriptors one wait hands back at a time */
234#define OT_EVENT_BATCH 512
235
232static void *server_mainloop(void *args) { 236static void *server_mainloop(void *args) {
233 struct ot_workstruct ws; 237 struct ot_workstruct ws;
234 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;
@@ -251,28 +255,42 @@ static void *server_mainloop(void *args) {
251 random_bytes(ws.rand48_state, sizeof(ws.rand48_state)); 255 random_bytes(ws.rand48_state, sizeof(ws.rand48_state));
252 256
253 for (;;) { 257 for (;;) {
254 int64_t sock; 258 ot_event ready[OT_EVENT_BATCH];
259 int64_t sock;
260 int count, i;
255 261
256 io_wait(); 262 count = event_wait(ready, OT_EVENT_BATCH, OT_CLIENT_TIMEOUT_CHECKINTERVAL * 1000);
263
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);
257 274
258 while ((sock = io_canread()) != -1) {
259 const void *cookie = fdstate_cookie(sock);
260 if ((intptr_t)cookie == FLAG_TCP) 275 if ((intptr_t)cookie == FLAG_TCP)
261 handle_accept(sock); 276 handle_accept(sock);
262 else if ((intptr_t)cookie == FLAG_UDP) 277 else if ((intptr_t)cookie == FLAG_UDP)
263 handle_udp6(sock, &ws); 278 handle_udp6(sock, &ws);
264 else if ((intptr_t)cookie == FLAG_SELFPIPE) 279 else if ((intptr_t)cookie == FLAG_SELFPIPE)
265 io_tryread(sock, ws.inbuf, G_INBUF_SIZE); 280 while (read((int)sock, ws.inbuf, G_INBUF_SIZE) > 0)
266 else 281 ;
267 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 }
268 } 289 }
269 290
270 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1) 291 while ((sock = mutex_workqueue_popresult(&iovec_entries, &iovector, &is_partial)) != -1)
271 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial); 292 http_sendiovecdata(sock, &ws, iovec_entries, iovector, is_partial);
272 293
273 while ((sock = io_canwrite()) != -1)
274 handle_write(sock);
275
276 if (g_now_seconds > next_timeout_check) { 294 if (g_now_seconds > next_timeout_check) {
277 int cursor = 0; 295 int cursor = 0;
278 296
@@ -319,11 +337,8 @@ static void listen_bind_one(ot_listen *entry) {
319 panic("socket_udp_bind"); 337 panic("socket_udp_bind");
320 } 338 }
321 339
322 if (!io_fd(entry->sock)) 340 if (event_add((int)entry->sock, EVENT_IN, fdstate_open(entry->sock, (void *)entry->proto, 0)) == -1)
323 panic("io_fd"); 341 panic("event_add");
324
325 fdstate_open(entry->sock, (void *)entry->proto, 0);
326 io_wantread(entry->sock);
327 342
328#ifdef _DEBUG 343#ifdef _DEBUG
329 fputs(" success.\n", stderr); 344 fputs(" success.\n", stderr);
@@ -352,7 +367,8 @@ static void listen_start_udp_workers(void) {
352 if (entry->proto != FLAG_UDP || entry->workers <= 0) 367 if (entry->proto != FLAG_UDP || entry->workers <= 0)
353 continue; 368 continue;
354 369
355 io_block(entry->sock); 370 socket_set_blocking((int)entry->sock, 1);
371 event_del((int)entry->sock);
356 udp_workers_install(entry->sock, (unsigned int)entry->workers); 372 udp_workers_install(entry->sock, (unsigned int)entry->workers);
357 } 373 }
358} 374}
@@ -375,14 +391,14 @@ void load_state(const char *const state_filename) {
375 while (fgets(inbuf, sizeof(inbuf), state_filehandle)) { 391 while (fgets(inbuf, sizeof(inbuf), state_filehandle)) {
376 size_t i; 392 size_t i;
377 393
378 if (!hex_tohash(infohash, inbuf)) 394 if (!hex_to_hash(infohash, inbuf))
379 continue; 395 continue;
380 i = 2 * sizeof(ot_hash); 396 i = 2 * sizeof(ot_hash);
381 397
382 if (inbuf[i++] != ':' || !(consumed = scan_udec(inbuf + i, &base, UINT64_MAX))) 398 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &base, UINT64_MAX)))
383 continue; 399 continue;
384 i += consumed; 400 i += consumed;
385 if (inbuf[i++] != ':' || !(consumed = scan_udec(inbuf + i, &downcount, UINT64_MAX))) 401 if (inbuf[i++] != ':' || !(consumed = scan_uint(inbuf + i, &downcount, UINT64_MAX)))
386 continue; 402 continue;
387 add_torrent_from_saved_state(infohash, base, downcount); 403 add_torrent_from_saved_state(infohash, base, downcount);
388 } 404 }
@@ -453,6 +469,8 @@ int main(int argc, char **argv) {
453 random_init(); 469 random_init();
454 if (fdstate_init() == -1) 470 if (fdstate_init() == -1)
455 panic("fdstate_init"); 471 panic("fdstate_init");
472 if (event_init() == -1)
473 panic("event_init");
456 config_parse(argc, argv); 474 config_parse(argc, argv);
457 475
458 listen_bind_all(); 476 listen_bind_all();
@@ -471,15 +489,15 @@ int main(int argc, char **argv) {
471 pthread_create(&thread_id, NULL, time_caching_worker, NULL); 489 pthread_create(&thread_id, NULL, time_caching_worker, NULL);
472 490
473 /* Create our self pipe which allows us to interrupt mainloops 491 /* Create our self pipe which allows us to interrupt mainloops
474 io_wait in case some data is available to send out */ 492 wait in case some data is available to send out */
475 if (pipe(g_self_pipe) == -1) 493 if (pipe(g_self_pipe) == -1)
476 panic("selfpipe failed: "); 494 panic("selfpipe failed: ");
477 if (!io_fd(g_self_pipe[0])) 495 /* Both ends have to be non blocking: the loop drains the read end until it
478 panic("selfpipe io_fd failed: "); 496 is empty, and a worker nudging a full pipe has already made its point */
479 if (!io_fd(g_self_pipe[1])) 497 socket_set_blocking(g_self_pipe[0], 0);
480 panic("selfpipe io_fd failed: "); 498 socket_set_blocking(g_self_pipe[1], 0);
481 fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0); 499 if (event_add(g_self_pipe[0], EVENT_IN, fdstate_open(g_self_pipe[0], (void *)FLAG_SELFPIPE, 0)) == -1)
482 io_wantread(g_self_pipe[0]); 500 panic("selfpipe event_add");
483 501
484 /* Init all sub systems. This call may fail with an exit() */ 502 /* Init all sub systems. This call may fail with an exit() */
485 trackerlogic_init(); 503 trackerlogic_init();
@@ -499,6 +517,7 @@ int main(int argc, char **argv) {
499 517
500 server_mainloop(0); 518 server_mainloop(0);
501 519
520 event_deinit();
502 fdstate_deinit(); 521 fdstate_deinit();
503 return 0; 522 return 0;
504} 523}
diff --git a/ot_accesslist.c b/ot_accesslist.c
index ef845d6..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
diff --git a/ot_clean.c b/ot_clean.c
index 825290d..bf0504c 100644
--- a/ot_clean.c
+++ b/ot_clean.c
@@ -8,9 +8,6 @@
8#include <string.h> 8#include <string.h>
9#include <unistd.h> 9#include <unistd.h>
10 10
11/* Libowfat */
12#include "io.h"
13
14/* Opentracker */ 11/* Opentracker */
15#include "ot_accesslist.h" 12#include "ot_accesslist.h"
16#include "ot_clean.h" 13#include "ot_clean.h"
diff --git a/ot_config.c b/ot_config.c
index a5fc06e..ee6bc12 100644
--- a/ot_config.c
+++ b/ot_config.c
@@ -107,7 +107,7 @@ static int listen_scan_options(char *p, int *shared, int *workers) {
107 *shared = 1; 107 *shared = 1;
108 break; 108 break;
109 case LISTEN_OPTION_WORKERS: { 109 case LISTEN_OPTION_WORKERS: {
110 size_t off = scan_udec(p, &value, UINT_MAX); 110 size_t off = scan_uint(p, &value, UINT_MAX);
111 111
112 if (!off) 112 if (!off)
113 return 0; 113 return 0;
@@ -140,7 +140,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
140 ++s; 140 ++s;
141 if (*s == '[') 141 if (*s == '[')
142 ++s, ++bracket; /* for v6 style notation */ 142 ++s, ++bracket; /* for v6 style notation */
143 if (!(off = ip_scan(s, ip))) 143 if (!(off = ip_parse(s, ip)))
144 return 0; 144 return 0;
145 s += off; 145 s += off;
146 if (bracket && *s == ']') 146 if (bracket && *s == ']')
@@ -157,7 +157,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
157 if (*(s++) != ':') 157 if (*(s++) != ':')
158 return 0; 158 return 0;
159 } 159 }
160 if (!(off = scan_udec(s, &value, 65535))) 160 if (!(off = scan_uint(s, &value, 65535)))
161 return 0; 161 return 0;
162 *port = (uint16_t)value; 162 *port = (uint16_t)value;
163 return off + s - src; 163 return off + s - src;
@@ -169,14 +169,14 @@ static int scan_ip6_net(const char *src, ot_net *net) {
169 int off; 169 int off;
170 while (isspace(*s)) 170 while (isspace(*s))
171 ++s; 171 ++s;
172 if (!(off = ip_scan(s, net->address))) 172 if (!(off = ip_parse(s, net->address)))
173 return 0; 173 return 0;
174 s += off; 174 s += off;
175 if (*s != '/') 175 if (*s != '/')
176 net->bits = 128; 176 net->bits = 128;
177 else { 177 else {
178 s++; 178 s++;
179 if (!(off = scan_udec(s, &value, 128))) 179 if (!(off = scan_uint(s, &value, 128)))
180 return 0; 180 return 0;
181 net->bits = (int)value; 181 net->bits = (int)value;
182 if (ip_is_v4mapped(net->address)) 182 if (ip_is_v4mapped(net->address))
@@ -324,12 +324,12 @@ static int parse_configfile(char *config_filename) {
324 break; 324 break;
325 } 325 }
326 case CONFIG_LISTEN_UDP_WORKERS: 326 case CONFIG_LISTEN_UDP_WORKERS:
327 if (!scan_udec(p, &value, UINT_MAX)) 327 if (!scan_uint(p, &value, UINT_MAX))
328 goto parse_error; 328 goto parse_error;
329 g_listen_workers = (int)value; 329 g_listen_workers = (int)value;
330 break; 330 break;
331 case CONFIG_LISTEN_SHARED: 331 case CONFIG_LISTEN_SHARED:
332 if (!scan_udec(p, &value, 1)) 332 if (!scan_uint(p, &value, 1))
333 goto parse_error; 333 goto parse_error;
334 g_listen_shared = (int)value; 334 g_listen_shared = (int)value;
335 break; 335 break;
@@ -479,7 +479,7 @@ void config_parse(int argc, char **argv) {
479 scanon = 0; 479 scanon = 0;
480 break; 480 break;
481 case 'i': 481 case 'i':
482 if (!ip_scan(optarg, serverip)) { 482 if (!ip_parse(optarg, serverip)) {
483 usage(argv[0]); 483 usage(argv[0]);
484 exit(1); 484 exit(1);
485 } 485 }
@@ -494,7 +494,7 @@ void config_parse(int argc, char **argv) {
494 break; 494 break;
495#endif 495#endif
496 case 'p': 496 case 'p':
497 if (!scan_udec(optarg, &value, 65535)) { 497 if (!scan_uint(optarg, &value, 65535)) {
498 usage(argv[0]); 498 usage(argv[0]);
499 exit(1); 499 exit(1);
500 } 500 }
@@ -502,7 +502,7 @@ void config_parse(int argc, char **argv) {
502 bound++; 502 bound++;
503 break; 503 break;
504 case 'P': 504 case 'P':
505 if (!scan_udec(optarg, &value, 65535)) { 505 if (!scan_uint(optarg, &value, 65535)) {
506 usage(argv[0]); 506 usage(argv[0]);
507 exit(1); 507 exit(1);
508 } 508 }
@@ -511,7 +511,7 @@ void config_parse(int argc, char **argv) {
511 break; 511 break;
512#ifdef WANT_SYNC_LIVE 512#ifdef WANT_SYNC_LIVE
513 case 's': 513 case 's':
514 if (!scan_udec(optarg, &value, 65535)) { 514 if (!scan_uint(optarg, &value, 65535)) {
515 usage(argv[0]); 515 usage(argv[0]);
516 exit(1); 516 exit(1);
517 } 517 }
@@ -544,8 +544,11 @@ void config_parse(int argc, char **argv) {
544 help(argv[0]); 544 help(argv[0]);
545 exit(0); 545 exit(0);
546 case 'v': { 546 case 'v': {
547 char buffer[8192]; 547 char buffer[8192];
548 stats_return_tracker_version(buffer); 548 char *cursor = buffer;
549
550 stats_return_tracker_version(&cursor, buffer + sizeof(buffer) - 1);
551 *cursor = 0;
549 fputs(buffer, stderr); 552 fputs(buffer, stderr);
550 exit(0); 553 exit(0);
551 } 554 }
diff --git a/ot_event.c b/ot_event.c
new file mode 100644
index 0000000..ce0feef
--- /dev/null
+++ b/ot_event.c
@@ -0,0 +1,332 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <errno.h>
8#include <stdlib.h>
9#include <string.h>
10#include <unistd.h>
11
12/* Opentracker */
13#include "ot_event.h"
14
15/* One caller waits, so the scratch the mechanism needs is kept here rather
16 than allocated per call. */
17
18/* poll works everywhere and is what the BSDs get until kqueue is written.
19 Defining OT_EVENT_FORCE_POLL picks it on a platform that would otherwise
20 use something better, which is how both are exercised by the tests. */
21#if defined(OT_EVENT_FORCE_POLL)
22#define OT_EVENT_POLL
23#elif defined(OT_EVENT_FORCE_KQUEUE)
24#define OT_EVENT_KQUEUE
25#elif defined(__linux__)
26#define OT_EVENT_EPOLL
27#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__)
28#define OT_EVENT_KQUEUE
29#else
30#define OT_EVENT_POLL
31#endif
32
33#ifdef OT_EVENT_EPOLL
34/* ------------------------------------------------------------- epoll */
35
36#include <sys/epoll.h>
37
38static int g_epoll = -1;
39static struct epoll_event *g_ready;
40static int g_ready_space;
41
42const char *event_mechanism(void) { return "epoll"; }
43
44int event_init(void) {
45 g_ready = NULL;
46 g_ready_space = 0;
47 return (g_epoll = epoll_create1(EPOLL_CLOEXEC)) == -1 ? -1 : 0;
48}
49
50void event_deinit(void) {
51 if (g_epoll != -1)
52 close(g_epoll);
53 free(g_ready);
54 g_epoll = -1;
55 g_ready = NULL;
56 g_ready_space = 0;
57}
58
59static int event_ctl(int op, int fd, int what, uint32_t generation) {
60 struct epoll_event ev;
61
62 memset(&ev, 0, sizeof(ev));
63 ev.events = (what & EVENT_IN ? (uint32_t)EPOLLIN : 0) | (what & EVENT_OUT ? (uint32_t)EPOLLOUT : 0);
64 /* The generation rides along inside the kernel, so an event says which
65 connection it belongs to without anybody looking it up */
66 ev.data.u64 = ((uint64_t)generation << 32) | (uint32_t)fd;
67 return epoll_ctl(g_epoll, op, fd, &ev);
68}
69
70int event_add(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_ADD, fd, what, generation); }
71int event_modify(int fd, int what, uint32_t generation) { return event_ctl(EPOLL_CTL_MOD, fd, what, generation); }
72int event_del(int fd) { return epoll_ctl(g_epoll, EPOLL_CTL_DEL, fd, NULL); }
73
74int event_wait(ot_event *out, int max, int timeout_ms) {
75 int count, i;
76
77 if (max > g_ready_space) {
78 struct epoll_event *grown = realloc(g_ready, (size_t)max * sizeof(struct epoll_event));
79
80 if (!grown)
81 return -1;
82 g_ready = grown;
83 g_ready_space = max;
84 }
85
86 while ((count = epoll_wait(g_epoll, g_ready, max, timeout_ms)) == -1 && errno == EINTR)
87 ;
88 for (i = 0; i < count; ++i) {
89 uint32_t events = g_ready[i].events;
90
91 out[i].fd = (int)(uint32_t)g_ready[i].data.u64;
92 out[i].generation = (uint32_t)(g_ready[i].data.u64 >> 32);
93 /* A hangup or an error is reported as both, so whichever side the caller
94 was waiting on gets to notice */
95 out[i].what = (events & (EPOLLIN | EPOLLHUP | EPOLLERR) ? EVENT_IN : 0) |
96 (events & (EPOLLOUT | EPOLLHUP | EPOLLERR) ? EVENT_OUT : 0);
97 }
98 return count;
99}
100
101#endif /* OT_EVENT_EPOLL */
102
103#ifdef OT_EVENT_KQUEUE
104/* ------------------------------------------------------------ kqueue */
105
106#include <sys/event.h>
107#include <sys/time.h>
108#include <sys/types.h>
109
110static int g_kqueue = -1;
111static struct kevent *g_ready;
112static int g_ready_space;
113
114const char *event_mechanism(void) { return "kqueue"; }
115
116int event_init(void) {
117 g_ready = NULL;
118 g_ready_space = 0;
119 return (g_kqueue = kqueue()) == -1 ? -1 : 0;
120}
121
122void event_deinit(void) {
123 if (g_kqueue != -1)
124 close(g_kqueue);
125 free(g_ready);
126 g_kqueue = -1;
127 g_ready = NULL;
128 g_ready_space = 0;
129}
130
131/* kqueue keeps one registration per filter, so reading and writing are two
132 entries. A change of interest is an add of the one wanted and a delete
133 of the one not. The generation travels in udata, which the kernel passes
134 back untouched. */
135static int event_change(int fd, int what, uint32_t generation, int deleting) {
136 struct kevent change[2], result[2];
137 void *udata = (void *)(uintptr_t)(((uint64_t)generation << 32) | (uint32_t)fd);
138 int succeeded = 0, count, i;
139
140 EV_SET(&change[0], (uintptr_t)fd, EVFILT_READ,
141 EV_RECEIPT | ((deleting || !(what & EVENT_IN)) ? EV_DELETE : EV_ADD), 0, 0, udata);
142 EV_SET(&change[1], (uintptr_t)fd, EVFILT_WRITE,
143 EV_RECEIPT | ((deleting || !(what & EVENT_OUT)) ? EV_DELETE : EV_ADD), 0, 0, udata);
144
145 if ((count = kevent(g_kqueue, change, 2, result, 2, NULL)) == -1)
146 return -1;
147
148 for (i = 0; i < count && i < 2; ++i) {
149 if (!result[i].data) {
150 ++succeeded;
151 continue;
152 }
153 /* Dropping interest we never registered is not a failure */
154 if ((change[i].flags & EV_DELETE) && result[i].data == ENOENT)
155 continue;
156 errno = (int)result[i].data;
157 return -1;
158 }
159
160 /* Nothing at all was registered or removed, so the descriptor is unknown */
161 if (!succeeded) {
162 errno = ENOENT;
163 return -1;
164 }
165 return 0;
166}
167
168int event_add(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); }
169int event_modify(int fd, int what, uint32_t generation) { return event_change(fd, what, generation, 0); }
170int event_del(int fd) { return event_change(fd, 0, 0, 1); }
171
172int event_wait(ot_event *out, int max, int timeout_ms) {
173 struct timespec timeout;
174 struct timespec *deadline = NULL;
175 int count, i;
176
177 if (max > g_ready_space) {
178 struct kevent *grown = realloc(g_ready, (size_t)max * sizeof(struct kevent));
179
180 if (!grown)
181 return -1;
182 g_ready = grown;
183 g_ready_space = max;
184 }
185
186 if (timeout_ms >= 0) {
187 timeout.tv_sec = timeout_ms / 1000;
188 timeout.tv_nsec = (long)(timeout_ms % 1000) * 1000000;
189 deadline = &timeout;
190 }
191
192 while ((count = kevent(g_kqueue, NULL, 0, g_ready, max, deadline)) == -1 && errno == EINTR)
193 ;
194 for (i = 0; i < count; ++i) {
195 uint64_t udata = (uint64_t)(uintptr_t)g_ready[i].udata;
196 int eof = (g_ready[i].flags & (EV_EOF | EV_ERROR)) != 0;
197
198 out[i].fd = (int)(uint32_t)udata;
199 out[i].generation = (uint32_t)(udata >> 32);
200 out[i].what = ((g_ready[i].filter == EVFILT_READ || eof) ? EVENT_IN : 0) |
201 ((g_ready[i].filter == EVFILT_WRITE || eof) ? EVENT_OUT : 0);
202 }
203 return count;
204}
205
206#endif /* OT_EVENT_KQUEUE */
207
208#ifdef OT_EVENT_POLL
209/* -------------------------------------------------------------- poll */
210
211#include <poll.h>
212
213/* poll wants its descriptors packed, so the registered ones live in one
214 array and a second maps a descriptor number to its place in it. */
215static struct pollfd *g_pfds;
216static uint32_t *g_generation;
217static int *g_where;
218static int g_count, g_space, g_where_space;
219
220const char *event_mechanism(void) { return "poll"; }
221
222int event_init(void) {
223 g_pfds = NULL, g_generation = NULL, g_where = NULL;
224 g_count = g_space = g_where_space = 0;
225 return 0;
226}
227
228void event_deinit(void) {
229 free(g_pfds), free(g_generation), free(g_where);
230 g_pfds = NULL, g_generation = NULL, g_where = NULL;
231 g_count = g_space = g_where_space = 0;
232}
233
234static short event_to_poll(int what) {
235 return (short)((what & EVENT_IN ? POLLIN : 0) | (what & EVENT_OUT ? POLLOUT : 0));
236}
237
238static int event_room_for(int fd) {
239 if (fd >= g_where_space) {
240 int space = g_where_space ? g_where_space : 64;
241 int *grown;
242
243 while (space <= fd)
244 space *= 2;
245 if (!(grown = realloc(g_where, (size_t)space * sizeof(int))))
246 return -1;
247 while (g_where_space < space)
248 grown[g_where_space++] = -1;
249 g_where = grown;
250 }
251
252 if (g_count == g_space) {
253 int space = g_space ? g_space * 2 : 64;
254 struct pollfd *pfds = realloc(g_pfds, (size_t)space * sizeof(struct pollfd));
255 uint32_t *gens;
256
257 if (!pfds)
258 return -1;
259 g_pfds = pfds;
260 if (!(gens = realloc(g_generation, (size_t)space * sizeof(uint32_t))))
261 return -1;
262 g_generation = gens;
263 g_space = space;
264 }
265 return 0;
266}
267
268int event_add(int fd, int what, uint32_t generation) {
269 if (fd < 0 || event_room_for(fd) == -1)
270 return -1;
271 if (g_where[fd] != -1) {
272 errno = EEXIST;
273 return -1;
274 }
275
276 g_pfds[g_count].fd = fd;
277 g_pfds[g_count].events = event_to_poll(what);
278 g_pfds[g_count].revents = 0;
279 g_generation[g_count] = generation;
280 g_where[fd] = g_count++;
281 return 0;
282}
283
284int event_modify(int fd, int what, uint32_t generation) {
285 if (fd < 0 || fd >= g_where_space || g_where[fd] == -1) {
286 errno = ENOENT;
287 return -1;
288 }
289 g_pfds[g_where[fd]].events = event_to_poll(what);
290 g_generation[g_where[fd]] = generation;
291 return 0;
292}
293
294int event_del(int fd) {
295 int at;
296
297 if (fd < 0 || fd >= g_where_space || (at = g_where[fd]) == -1) {
298 errno = ENOENT;
299 return -1;
300 }
301
302 /* The last one moves into the gap, so the array stays packed */
303 g_pfds[at] = g_pfds[--g_count];
304 g_generation[at] = g_generation[g_count];
305 g_where[g_pfds[at].fd] = at;
306 g_where[fd] = -1;
307 return 0;
308}
309
310int event_wait(ot_event *out, int max, int timeout_ms) {
311 int ready, found = 0, i;
312
313 while ((ready = poll(g_pfds, (nfds_t)g_count, timeout_ms)) == -1 && errno == EINTR)
314 ;
315 if (ready <= 0)
316 return ready;
317
318 for (i = 0; i < g_count && found < max; ++i) {
319 short revents = g_pfds[i].revents;
320
321 if (!revents)
322 continue;
323 out[found].fd = g_pfds[i].fd;
324 out[found].generation = g_generation[i];
325 out[found].what = (revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL) ? EVENT_IN : 0) |
326 (revents & (POLLOUT | POLLHUP | POLLERR | POLLNVAL) ? EVENT_OUT : 0);
327 ++found;
328 }
329 return found;
330}
331
332#endif /* OT_EVENT_POLL */
diff --git a/ot_event.h b/ot_event.h
new file mode 100644
index 0000000..6023134
--- /dev/null
+++ b/ot_event.h
@@ -0,0 +1,39 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_EVENT_H__
7#define OT_EVENT_H__
8
9#include <stdint.h>
10
11/* Which descriptors have become readable or writable, asked of whichever
12 mechanism the kernel offers. The generation a descriptor was registered
13 with is handed back with every event, so a caller can tell an event for
14 the connection it is holding from one for a connection that has since been
15 closed and had its number reused. */
16
17enum { EVENT_IN = 1, EVENT_OUT = 2 };
18
19typedef struct {
20 int fd;
21 uint32_t generation;
22 int what;
23} ot_event;
24
25int event_init(void);
26void event_deinit(void);
27
28int event_add(int fd, int what, uint32_t generation);
29int event_modify(int fd, int what, uint32_t generation);
30int event_del(int fd);
31
32/* Waits at most timeout_ms, or indefinitely when that is negative, and fills
33 out with up to max ready descriptors. Returns how many, or -1. */
34int event_wait(ot_event *out, int max, int timeout_ms);
35
36/* Which mechanism was compiled in, for the version string */
37const char *event_mechanism(void);
38
39#endif
diff --git a/ot_fdstate.c b/ot_fdstate.c
index 7f73d3d..537bcb0 100644
--- a/ot_fdstate.c
+++ b/ot_fdstate.c
@@ -99,6 +99,12 @@ uint32_t fdstate_open(int fd, void *cookie, ot_time expiry) {
99 return FD_GEN(word); 99 return FD_GEN(word);
100} 100}
101 101
102uint32_t fdstate_generation(int fd) {
103 ot_fdslot *slot = fdstate_slot(fd);
104
105 return slot ? FD_GEN(atomic_load_explicit(&slot->state, memory_order_relaxed)) : 0;
106}
107
102int fdstate_claim(int fd, uint32_t generation) { 108int fdstate_claim(int fd, uint32_t generation) {
103 ot_fdslot *slot = fdstate_slot(fd); 109 ot_fdslot *slot = fdstate_slot(fd);
104 uint64_t word; 110 uint64_t word;
diff --git a/ot_fdstate.h b/ot_fdstate.h
index 88f685d..c09f018 100644
--- a/ot_fdstate.h
+++ b/ot_fdstate.h
@@ -31,6 +31,10 @@ size_t fdstate_slots(void);
31 generation to quote when asking for it back. expiry of 0 never expires. */ 31 generation to quote when asking for it back. expiry of 0 never expires. */
32uint32_t fdstate_open(int fd, void *cookie, ot_time expiry); 32uint32_t fdstate_open(int fd, void *cookie, ot_time expiry);
33 33
34/* Which generation a descriptor currently carries, to check an event that
35 was handed out earlier against the connection living there now. */
36uint32_t fdstate_generation(int fd);
37
34/* Ask for exclusive use. Returns 0 if the descriptor has been recycled since 38/* Ask for exclusive use. Returns 0 if the descriptor has been recycled since
35 generation was handed out, or if somebody else is using it. */ 39 generation was handed out, or if somebody else is using it. */
36int fdstate_claim(int fd, uint32_t generation); 40int fdstate_claim(int fd, uint32_t generation);
diff --git a/ot_fmt.c b/ot_fmt.c
new file mode 100644
index 0000000..9cab25f
--- /dev/null
+++ b/ot_fmt.c
@@ -0,0 +1,42 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <stdarg.h>
8#include <stdio.h>
9#include <string.h>
10
11/* Opentracker */
12#include "ot_fmt.h"
13
14void fmt_append(char **cursor, const char *end, const char *format, ...) {
15 va_list arguments;
16 size_t room;
17 int written;
18
19 if (*cursor >= end) {
20 *cursor = (char *)end;
21 return;
22 }
23
24 room = (size_t)(end - *cursor);
25 va_start(arguments, format);
26 written = vsnprintf(*cursor, room, format, arguments);
27 va_end(arguments);
28
29 if (written < 0)
30 return;
31 *cursor = (size_t)written >= room ? (char *)end : *cursor + written;
32}
33
34void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length) {
35 if (*cursor >= end || (size_t)(end - *cursor) < length) {
36 *cursor = (char *)end;
37 return;
38 }
39
40 memcpy(*cursor, bytes, length);
41 *cursor += length;
42}
diff --git a/ot_fmt.h b/ot_fmt.h
new file mode 100644
index 0000000..851b2d8
--- /dev/null
+++ b/ot_fmt.h
@@ -0,0 +1,21 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6#ifndef OT_FMT_H__
7#define OT_FMT_H__
8
9#include <stddef.h>
10
11/* Appending to a cursor that walks a fixed buffer. Both take the cursor by
12 reference and advance it, so a reply is written as a run of statements and
13 no caller does pointer arithmetic of its own. */
14
15void fmt_append(char **cursor, const char *end, const char *format, ...) __attribute__((format(printf, 3, 4)));
16
17/* Append raw bytes for the binary stretches of a bencoded reply.
18 Either everything is appended or nothing at all */
19void fmt_bytes(char **cursor, const char *end, const void *bytes, size_t length);
20
21#endif
diff --git a/ot_fullscrape.c b/ot_fullscrape.c
index 788e574..8518bd2 100644
--- a/ot_fullscrape.c
+++ b/ot_fullscrape.c
@@ -17,15 +17,13 @@
17#include <zstd.h> 17#include <zstd.h>
18#endif 18#endif
19 19
20/* Libowfat */
21#include "io.h"
22
23/* Opentracker */ 20/* Opentracker */
21#include "ot_fmt.h"
24#include "ot_hex.h" 22#include "ot_hex.h"
25#include "ot_fullscrape.h" 23#include "ot_fullscrape.h"
26#include "ot_iovec.h" 24#include "ot_iovec.h"
27#include "ot_mutex.h" 25#include "ot_mutex.h"
28#include "scan_urlencoded_query.h" 26#include "ot_urlencoded.h"
29#include "trackerlogic.h" 27#include "trackerlogic.h"
30 28
31/* Fetch full scrape info for all torrents 29/* Fetch full scrape info for all torrents
@@ -86,10 +84,15 @@ void fullscrape_deliver( int64_t sock, ot_tasktype tasktype ) {
86 mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype ); 84 mutex_workqueue_pushtask( sock, TASK_FULLSCRAPE | tasktype );
87} 85}
88 86
89static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torrent, ot_hash *hash ) { 87static void fullscrape_write_one( ot_tasktype mode, char **cursor, ot_torrent *torrent, ot_hash *hash ) {
90 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 88 /* Callers guarantee this much room: the chunk writer keeps its low
91 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count; 89 watermark that far from the end, the compressors hand over a buffer of
92 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; 90 exactly that size */
91 char *r = *cursor;
92 const char *r_end = r + OT_SCRAPE_MAXENTRYLEN;
93 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
94 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count;
95 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
93 96
94 switch (mode & TASK_TASK_MASK) { 97 switch (mode & TASK_TASK_MASK) {
95 case TASK_FULLSCRAPE: 98 case TASK_FULLSCRAPE:
@@ -101,18 +104,18 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre
101 memcpy(r, hash, sizeof(ot_hash)); 104 memcpy(r, hash, sizeof(ot_hash));
102 r += sizeof(ot_hash); 105 r += sizeof(ot_hash);
103 /* push rest of the scrape string */ 106 /* push rest of the scrape string */
104 r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count); 107 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", seed_count, down_count, peer_count - seed_count);
105 108
106 break; 109 break;
107 case TASK_FULLSCRAPE_TPB_ASCII: 110 case TASK_FULLSCRAPE_TPB_ASCII:
108 hex_fromhash(r, *hash); 111 hex_from_hash(r, *hash);
109 r += 2 * sizeof(ot_hash); 112 r += 2 * sizeof(ot_hash);
110 r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); 113 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
111 break; 114 break;
112 case TASK_FULLSCRAPE_TPB_ASCII_PLUS: 115 case TASK_FULLSCRAPE_TPB_ASCII_PLUS:
113 hex_fromhash(r, *hash); 116 hex_from_hash(r, *hash);
114 r += 2 * sizeof(ot_hash); 117 r += 2 * sizeof(ot_hash);
115 r += sprintf(r, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count); 118 fmt_append(&r, r_end, ":%zd:%zd:%zd\n", seed_count, peer_count - seed_count, down_count);
116 break; 119 break;
117 case TASK_FULLSCRAPE_TPB_BINARY: 120 case TASK_FULLSCRAPE_TPB_BINARY:
118 memcpy(r, *hash, sizeof(ot_hash)); 121 memcpy(r, *hash, sizeof(ot_hash));
@@ -123,15 +126,15 @@ static char * fullscrape_write_one( ot_tasktype mode, char *r, ot_torrent *torre
123 break; 126 break;
124 case TASK_FULLSCRAPE_TPB_URLENCODED: 127 case TASK_FULLSCRAPE_TPB_URLENCODED:
125 r += urlencoded_format(r, *hash, sizeof(ot_hash)); 128 r += urlencoded_format(r, *hash, sizeof(ot_hash));
126 r += sprintf(r, ":%zd:%zd\n", seed_count, peer_count - seed_count); 129 fmt_append(&r, r_end, ":%zd:%zd\n", seed_count, peer_count - seed_count);
127 break; 130 break;
128 case TASK_FULLSCRAPE_TRACKERSTATE: 131 case TASK_FULLSCRAPE_TRACKERSTATE:
129 hex_fromhash(r, *hash); 132 hex_from_hash(r, *hash);
130 r += 2 * sizeof(ot_hash); 133 r += 2 * sizeof(ot_hash);
131 r += sprintf(r, ":%zd:%zd\n", torrent->peer_list6->base, down_count); 134 fmt_append(&r, r_end, ":%zd:%zd\n", torrent->peer_list6->base, down_count);
132 break; 135 break;
133 } 136 }
134 return r; 137 *cursor = r;
135} 138}
136 139
137static void fullscrape_make(int taskid, ot_tasktype mode) { 140static void fullscrape_make(int taskid, ot_tasktype mode) {
@@ -148,7 +151,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
148 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN; 151 re = r + OT_SCRAPE_CHUNK_SIZE - OT_SCRAPE_MAXENTRYLEN;
149 152
150 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) 153 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
151 r += sprintf(r, "d5:filesd"); 154 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "d5:filesd");
152 155
153 /* For each bucket... */ 156 /* For each bucket... */
154 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) { 157 for (bucket = 0; bucket < OT_BUCKET_COUNT; ++bucket) {
@@ -159,7 +162,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
159 162
160 /* For each torrent in this bucket.. */ 163 /* For each torrent in this bucket.. */
161 for (i = 0; i < torrents_list->size; ++i) { 164 for (i = 0; i < torrents_list->size; ++i) {
162 r = fullscrape_write_one(mode, r, torrents + i, &torrents[i].hash); 165 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
163 166
164 if (r > re) { 167 if (r > re) {
165 iovector.iov_len = r - (char *)iovector.iov_base; 168 iovector.iov_len = r - (char *)iovector.iov_base;
@@ -190,7 +193,7 @@ static void fullscrape_make(int taskid, ot_tasktype mode) {
190 } 193 }
191 194
192 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE) 195 if ((mode & TASK_TASK_MASK) == TASK_FULLSCRAPE)
193 r += sprintf(r, "ee"); 196 fmt_append(&r, (char *)iovector.iov_base + OT_SCRAPE_CHUNK_SIZE, "ee");
194 197
195 /* Send rest of data */ 198 /* Send rest of data */
196 iovector.iov_len = r - (char *)iovector.iov_base; 199 iovector.iov_len = r - (char *)iovector.iov_base;
@@ -233,7 +236,8 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
233 /* For each torrent in this bucket.. */ 236 /* For each torrent in this bucket.. */
234 for (i = 0; i < torrents_list->size; ++i) { 237 for (i = 0; i < torrents_list->size; ++i) {
235 char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; 238 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
236 r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); 239 r = compress_buffer;
240 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
237 strm.next_in = (uint8_t *)compress_buffer; 241 strm.next_in = (uint8_t *)compress_buffer;
238 strm.avail_in = r - compress_buffer; 242 strm.avail_in = r - compress_buffer;
239 zres = deflate(&strm, Z_NO_FLUSH); 243 zres = deflate(&strm, Z_NO_FLUSH);
@@ -296,7 +300,7 @@ static void fullscrape_make_gzip(int taskid, ot_tasktype mode) {
296 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); 300 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
297 301
298 if (!iovector.iov_base) { 302 if (!iovector.iov_base) {
299 fprintf(stderr, "Problem with iovec_fix_increase_or_free\n"); 303 fprintf(stderr, "Problem with iovec_next_chunk\n");
300 deflateEnd(&strm); 304 deflateEnd(&strm);
301 return; 305 return;
302 } 306 }
@@ -362,7 +366,8 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
362 /* For each torrent in this bucket.. */ 366 /* For each torrent in this bucket.. */
363 for (i = 0; i < torrents_list->size; ++i) { 367 for (i = 0; i < torrents_list->size; ++i) {
364 char compress_buffer[OT_SCRAPE_MAXENTRYLEN]; 368 char compress_buffer[OT_SCRAPE_MAXENTRYLEN];
365 r = fullscrape_write_one(mode, compress_buffer, torrents + i, &torrents[i].hash); 369 r = compress_buffer;
370 fullscrape_write_one(mode, &r, torrents + i, &torrents[i].hash);
366 inbuf.src = compress_buffer; 371 inbuf.src = compress_buffer;
367 inbuf.size = r - compress_buffer; 372 inbuf.size = r - compress_buffer;
368 inbuf.pos = 0; 373 inbuf.pos = 0;
@@ -422,7 +427,7 @@ static void fullscrape_make_zstd(int taskid, ot_tasktype mode) {
422 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE); 427 iovector.iov_base = malloc(OT_SCRAPE_CHUNK_SIZE);
423 428
424 if (!iovector.iov_base) { 429 if (!iovector.iov_base) {
425 fprintf(stderr, "Problem with iovec_fix_increase_or_free\n"); 430 fprintf(stderr, "Problem with iovec_next_chunk\n");
426 ZSTD_freeCCtx(zstream); 431 ZSTD_freeCCtx(zstream);
427 return; 432 return;
428 } 433 }
diff --git a/ot_hex.c b/ot_hex.c
index 9581951..2bf69c4 100644
--- a/ot_hex.c
+++ b/ot_hex.c
@@ -23,7 +23,7 @@ int hex_value(int c) {
23 23
24int hex_isdigit(int c) { return hex_value(c) >= 0; } 24int hex_isdigit(int c) { return hex_value(c) >= 0; }
25 25
26char *hex_fromhash(char *dest, const uint8_t *hash) { 26char *hex_from_hash(char *dest, const uint8_t *hash) {
27 static const char digits[] = "0123456789ABCDEF"; 27 static const char digits[] = "0123456789ABCDEF";
28 char *d = dest; 28 char *d = dest;
29 size_t i; 29 size_t i;
@@ -36,7 +36,7 @@ char *hex_fromhash(char *dest, const uint8_t *hash) {
36 return dest; 36 return dest;
37} 37}
38 38
39int hex_tohash(ot_hash hash, const char *src) { 39int hex_to_hash(ot_hash hash, const char *src) {
40 uint8_t decoded[sizeof(ot_hash)]; 40 uint8_t decoded[sizeof(ot_hash)];
41 size_t i; 41 size_t i;
42 42
diff --git a/ot_hex.h b/ot_hex.h
index 3b45b61..28eddf5 100644
--- a/ot_hex.h
+++ b/ot_hex.h
@@ -12,12 +12,12 @@
12 12
13/* Write hash as forty upper case hex digits followed by a terminating zero. 13/* Write hash as forty upper case hex digits followed by a terminating zero.
14 dest must have room for forty one bytes. Returns dest. */ 14 dest must have room for forty one bytes. Returns dest. */
15char *hex_fromhash(char *dest, const uint8_t *hash); 15char *hex_from_hash(char *dest, const uint8_t *hash);
16 16
17/* Read forty hex digits from src into hash. Returns non-zero on success. 17/* Read forty hex digits from src into hash. Returns non-zero on success.
18 On failure hash is left untouched and no byte of src beyond the offending 18 On failure hash is left untouched and no byte of src beyond the offending
19 character is read, so src need not be forty characters long. */ 19 character is read, so src need not be forty characters long. */
20int hex_tohash(ot_hash hash, const char *src); 20int hex_to_hash(ot_hash hash, const char *src);
21 21
22/* Non-zero if c is a hexadecimal digit, in either case. */ 22/* Non-zero if c is a hexadecimal digit, in either case. */
23int hex_isdigit(int c); 23int hex_isdigit(int c);
diff --git a/ot_http.c b/ot_http.c
index 6bf343c..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"
28#include "ot_fdstate.h" 26#include "ot_fdstate.h"
27#include "ot_fmt.h"
29#include "ot_stats.h" 28#include "ot_stats.h"
30#include "scan_urlencoded_query.h" 29#include "ot_urlencoded.h"
31#include "trackerlogic.h" 30#include "trackerlogic.h"
32 31
33#define OT_MAXMULTISCRAPE_COUNT 64 32#define OT_MAXMULTISCRAPE_COUNT 64
@@ -43,7 +42,7 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
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 }
@@ -98,9 +97,9 @@ static void http_senddata(const int64_t sock, struct ot_workstruct *ws) {
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 fdstate_set_expiry(sock, 0); 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"};
@@ -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
@@ -208,7 +222,7 @@ ssize_t http_sendiovecdata(const int64_t sock, struct ot_workstruct *ws, size_t
208 } 222 }
209 223
210 fdstate_set_expiry(sock, g_now_seconds + 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
@@ -244,6 +258,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
244 {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}}; 258 {"txtp", TASK_FULLSCRAPE_TPB_ASCII_PLUS}, {NULL, -3}};
245 259
246 struct http_data *cookie = fdstate_cookie(sock); 260 struct http_data *cookie = fdstate_cookie(sock);
261 char *reply;
247 int mode = TASK_STATS_PEERS, scanon = 1, format = 0; 262 int mode = TASK_STATS_PEERS, scanon = 1, format = 0;
248 263
249 /* A socket without one is a dead socket, which is what http_sendiovecdata 264 /* A socket without one is a dead socket, which is what http_sendiovecdata
@@ -257,21 +272,21 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
257#endif 272#endif
258 273
259 while (scanon) { 274 while (scanon) {
260 switch (scan_find_keywords(keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM)) { 275 switch (urlencoded_match_keyword(keywords_main, &read_ptr, SCAN_SEARCHPATH_PARAM)) {
261 case -2: 276 case -2:
262 scanon = 0; 277 scanon = 0;
263 break; /* TERMINATOR */ 278 break; /* TERMINATOR */
264 case -1: 279 case -1:
265 HTTPERROR_400_PARAM; /* PARSE ERROR */ 280 HTTPERROR_400_PARAM; /* PARSE ERROR */
266 case -3: 281 case -3:
267 scan_urlencoded_skipvalue(&read_ptr); 282 urlencoded_skip_value(&read_ptr);
268 break; 283 break;
269 case 1: /* matched "mode" */ 284 case 1: /* matched "mode" */
270 if ((mode = scan_find_keywords(keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) 285 if ((mode = urlencoded_match_keyword(keywords_mode, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
271 HTTPERROR_400_PARAM; 286 HTTPERROR_400_PARAM;
272 break; 287 break;
273 case 2: /* matched "format" */ 288 case 2: /* matched "format" */
274 if ((format = scan_find_keywords(keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0) 289 if ((format = urlencoded_match_keyword(keywords_format, &read_ptr, SCAN_SEARCHPATH_VALUE)) <= 0)
275 HTTPERROR_400_PARAM; 290 HTTPERROR_400_PARAM;
276 break; 291 break;
277 case 3: 292 case 3:
@@ -303,7 +318,7 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
303 /* Clients waiting for us should not easily timeout */ 318 /* Clients waiting for us should not easily timeout */
304 fdstate_set_expiry(sock, 0); 319 fdstate_set_expiry(sock, 0);
305 fullscrape_deliver(sock, format); 320 fullscrape_deliver(sock, format);
306 io_dontwantread(sock); 321 http_interest_drop(sock, cookie, EVENT_IN);
307 return ws->reply_size = -2; 322 return ws->reply_size = -2;
308 } 323 }
309#endif 324#endif
@@ -320,7 +335,9 @@ static ssize_t http_handle_stats(const int64_t sock, struct ot_workstruct *ws, c
320 } 335 }
321 336
322 /* Simple stats can be answerred immediately */ 337 /* Simple stats can be answerred immediately */
323 return ws->reply_size = return_stats_for_tracker(ws->reply, mode, 0); 338 reply = ws->reply;
339 return_stats_for_tracker(&reply, ws->outbuf + G_OUTBUF_SIZE, mode, 0);
340 return ws->reply_size = reply - ws->reply;
324} 341}
325 342
326#ifdef WANT_MODEST_FULLSCRAPES 343#ifdef WANT_MODEST_FULLSCRAPES
@@ -396,7 +413,7 @@ static ssize_t http_handle_fullscrape(const int64_t sock, struct ot_workstruct *
396 /* Clients waiting for us should not easily timeout */ 413 /* Clients waiting for us should not easily timeout */
397 fdstate_set_expiry(sock, 0); 414 fdstate_set_expiry(sock, 0);
398 fullscrape_deliver(sock, format); 415 fullscrape_deliver(sock, format);
399 io_dontwantread(sock); 416 http_interest_drop(sock, cookie, EVENT_IN);
400 return ws->reply_size = -2; 417 return ws->reply_size = -2;
401} 418}
402#endif 419#endif
@@ -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,9 +499,20 @@ 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;
@@ -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);
@@ -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_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 cfc8708..17f12a4 100644
--- a/scan_urlencoded_query.c
+++ b/ot_urlencoded.c
@@ -4,7 +4,7 @@
4 $id$ */ 4 $id$ */
5 5
6/* Opentracker */ 6/* Opentracker */
7#include "scan_urlencoded_query.h" 7#include "ot_urlencoded.h"
8 8
9/* System */ 9/* System */
10#include <string.h> 10#include <string.h>
@@ -53,7 +53,7 @@ static unsigned char fromhex(unsigned char x) {
53} 53}
54 54
55/* Skip the value of a param=value pair */ 55/* Skip the value of a param=value pair */
56void scan_urlencoded_skipvalue(char **string) { 56void urlencoded_skip_value(char **string) {
57 const unsigned char *s = *(const unsigned char **)string; 57 const unsigned char *s = *(const unsigned char **)string;
58 unsigned char f; 58 unsigned char f;
59 59
@@ -63,16 +63,16 @@ void scan_urlencoded_skipvalue(char **string) {
63 ; 63 ;
64 64
65 /* If we stopped at a hard terminator like \0 or \n, make the 65 /* If we stopped at a hard terminator like \0 or \n, make the
66 next scan_urlencoded_query encounter it again */ 66 next urlencoded_parse encounter it again */
67 if (f & SCAN_SEARCHPATH_TERMINATOR) 67 if (f & SCAN_SEARCHPATH_TERMINATOR)
68 --s; 68 --s;
69 69
70 *string = (char *)s; 70 *string = (char *)s;
71} 71}
72 72
73int scan_find_keywords(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags) { 73int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags) {
74 char *deststring = *string; 74 char *deststring = *string;
75 ssize_t match_length = scan_urlencoded_query(string, deststring, flags); 75 ssize_t match_length = urlencoded_parse(string, deststring, flags);
76 76
77 if (match_length < 0) 77 if (match_length < 0)
78 return match_length; 78 return match_length;
@@ -88,7 +88,7 @@ int scan_find_keywords(const ot_keywords *keywords, char **string, SCAN_SEARCHPA
88 return -3; 88 return -3;
89} 89}
90 90
91ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags) { 91ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags) {
92 const unsigned char *s = *(const unsigned char **)string; 92 const unsigned char *s = *(const unsigned char **)string;
93 unsigned char *d = (unsigned char *)deststring; 93 unsigned char *d = (unsigned char *)deststring;
94 unsigned char b, c; 94 unsigned char b, c;
@@ -121,7 +121,7 @@ ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_F
121 if (d == (unsigned char *)deststring) 121 if (d == (unsigned char *)deststring)
122 return -2; 122 return -2;
123 123
124 /* Else make the next call to scan_urlencoded_param encounter it again */ 124 /* Else make the next call to urlencoded_parse encounter it again */
125 --s; 125 --s;
126 break; 126 break;
127 case '?': 127 case '?':
@@ -146,19 +146,6 @@ ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_F
146 return d - (unsigned char *)deststring; 146 return d - (unsigned char *)deststring;
147} 147}
148 148
149ssize_t scan_fixed_int(char *data, size_t len, int *tmp) {
150 int minus = 0;
151 *tmp = 0;
152 if (*data == '-')
153 --len, ++data, ++minus;
154 while ((len > 0) && (*data >= '0') && (*data <= '9')) {
155 --len;
156 *tmp = 10 * *tmp + *data++ - '0';
157 }
158 if (minus)
159 *tmp = -*tmp;
160 return len;
161}
162 149
163size_t urlencoded_format(char *dest, const uint8_t *src, size_t len) { 150size_t urlencoded_format(char *dest, const uint8_t *src, size_t len) {
164 static const char safe[] = "$/.=~-_"; 151 static const char safe[] = "$/.=~-_";
diff --git a/scan_urlencoded_query.h b/ot_urlencoded.h
index 24e09fd..b6f1208 100644
--- a/scan_urlencoded_query.h
+++ b/ot_urlencoded.h
@@ -30,7 +30,7 @@ typedef enum {
30 or -1 for parse error 30 or -1 for parse error
31 or -2 for terminator found 31 or -2 for terminator found
32*/ 32*/
33ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags); 33ssize_t urlencoded_parse(char **string, char *deststring, SCAN_SEARCHPATH_FLAG flags);
34 34
35/* string in: pointer to source 35/* string in: pointer to source
36 out: pointer to next scan position 36 out: pointer to next scan position
@@ -40,19 +40,13 @@ ssize_t scan_urlencoded_query(char **string, char *deststring, SCAN_SEARCHPATH_F
40 or -2 for terminator found 40 or -2 for terminator found
41 or -3 for no keyword matched 41 or -3 for no keyword matched
42 */ 42 */
43int scan_find_keywords(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags); 43int urlencoded_match_keyword(const ot_keywords *keywords, char **string, SCAN_SEARCHPATH_FLAG flags);
44 44
45/* string in: pointer to value of a param=value pair to skip 45/* string in: pointer to value of a param=value pair to skip
46 out: pointer to next scan position on return 46 out: pointer to next scan position on return
47*/ 47*/
48void scan_urlencoded_skipvalue(char **string); 48void urlencoded_skip_value(char **string);
49 49
50/* data pointer to len chars of string
51 len length of chars in data to parse
52 number number to receive result
53 returns number of bytes not parsed, mostly !=0 means fail
54 */
55ssize_t scan_fixed_int(char *data, size_t len, int *number);
56 50
57/* The other direction: percent encode len bytes from src into dest and return 51/* The other direction: percent encode len bytes from src into dest and return
58 how many characters were written. dest needs room for three times len. */ 52 how many characters were written. dest needs room for three times len. */
diff --git a/ot_vector.c b/ot_vector.c
index 924d468..a56d55b 100644
--- a/ot_vector.c
+++ b/ot_vector.c
@@ -57,34 +57,39 @@ static uint8_t vector_hash_peer(ot_peer const *peer, size_t compare_size, size_t
57*/ 57*/
58void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) { 58void *vector_find_or_insert(ot_vector *vector, void *key, size_t member_size, size_t compare_size, int *exactmatch) {
59 uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch); 59 uint8_t *match = vector_binary_search(key, vector->data, vector->size, member_size, compare_size, exactmatch);
60 size_t match_offset, bytes_after;
60 61
61 if (*exactmatch) 62 if (*exactmatch)
62 return match; 63 return match;
63 64
65 /* Hold the insertion point as an offset rather than a pointer: it survives
66 the realloc below, and an empty vector has no data pointer to offset from
67 in the first place */
68 match_offset = vector->size ? (size_t)(match - (uint8_t *)vector->data) : 0;
69 bytes_after = member_size * vector->size - match_offset;
70
64 if (vector->size + 1 > vector->space) { 71 if (vector->size + 1 > vector->space) {
65 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; 72 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
66 ptrdiff_t match_off = match - (uint8_t *)vector->data; 73 uint8_t *new_data = realloc(vector->data, new_space * member_size);
67 uint8_t *new_data = realloc(vector->data, new_space * member_size);
68 74
69 if (!new_data) 75 if (!new_data)
70 return NULL; 76 return NULL;
71 77
72 /* Adjust pointer if it moved by realloc */
73 match = new_data + match_off;
74
75 vector->data = new_data; 78 vector->data = new_data;
76 vector->space = new_space; 79 vector->space = new_space;
77 } 80 }
78 memmove(match + member_size, match, ((uint8_t *)vector->data) + member_size * vector->size - match); 81
82 match = (uint8_t *)vector->data + match_offset;
83 memmove(match + member_size, match, bytes_after);
79 84
80 vector->size++; 85 vector->size++;
81 return match; 86 return match;
82} 87}
83 88
84ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) { 89ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size, int *exactmatch) {
85 ot_peer *match, *end; 90 ot_peer *match;
86 const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size); 91 const size_t compare_size = OT_PEER_COMPARE_SIZE_FROM_PEER_SIZE(peer_size);
87 size_t match_to_end; 92 size_t match_offset, bytes_after;
88 93
89 /* If space is zero but size is set, we're dealing with a list of vector->size buckets */ 94 /* If space is zero but size is set, we're dealing with a list of vector->size buckets */
90 if (vector->space < vector->size) 95 if (vector->space < vector->size)
@@ -94,26 +99,26 @@ ot_peer *vector_find_or_insert_peer(ot_vector *vector, ot_peer const *peer, size
94 if (*exactmatch) 99 if (*exactmatch)
95 return match; 100 return match;
96 101
97 /* This is the amount of bytes that needs to be pushed backwards by peer_size bytes to make room for new peer */ 102 /* Hold the insertion point as an offset rather than a pointer: it survives
98 end = (ot_peer *)vector->data + vector->size * peer_size; 103 the realloc below, and an empty vector has no data pointer to offset from
99 match_to_end = end - match; 104 in the first place. bytes_after is what has to move up by peer_size to
105 make room for the new peer */
106 match_offset = vector->size ? (size_t)(match - (ot_peer *)vector->data) : 0;
107 bytes_after = vector->size * peer_size - match_offset;
100 108
101 if (vector->size + 1 > vector->space) { 109 if (vector->size + 1 > vector->space) {
102 ptrdiff_t offset = match - (ot_peer *)vector->data; 110 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS;
103 size_t new_space = vector->space ? OT_VECTOR_GROW_RATIO * vector->space : OT_VECTOR_MIN_MEMBERS; 111 ot_peer *new_data = realloc(vector->data, new_space * peer_size);
104 ot_peer *new_data = realloc(vector->data, new_space * peer_size);
105 112
106 if (!new_data) 113 if (!new_data)
107 return NULL; 114 return NULL;
108 /* Adjust pointer if it moved by realloc */
109 match = new_data + offset;
110 115
111 vector->data = new_data; 116 vector->data = new_data;
112 vector->space = new_space; 117 vector->space = new_space;
113 } 118 }
114 119
115 /* Here we're guaranteed to have enough space in vector to move the block of peers after insertion point */ 120 match = (ot_peer *)vector->data + match_offset;
116 memmove(match + peer_size, match, match_to_end); 121 memmove(match + peer_size, match, bytes_after);
117 122
118 vector->size++; 123 vector->size++;
119 return match; 124 return match;
@@ -150,11 +155,13 @@ int vector_remove_peer(ot_vector *vector, ot_peer const *peer, size_t peer_size)
150} 155}
151 156
152void vector_remove_torrent(ot_vector *vector, ot_torrent *match) { 157void vector_remove_torrent(ot_vector *vector, ot_torrent *match) {
153 ot_torrent *end = ((ot_torrent *)vector->data) + vector->size; 158 ot_torrent *end;
154 159
155 if (!vector->size) 160 if (!vector->size)
156 return; 161 return;
157 162
163 end = ((ot_torrent *)vector->data) + vector->size;
164
158 free_peerlist(match->peer_list6); 165 free_peerlist(match->peer_list6);
159 free_peerlist(match->peer_list4); 166 free_peerlist(match->peer_list4);
160 167
diff --git a/proxy.c b/proxy.c
index 0cac20c..a5c6bb7 100644
--- a/proxy.c
+++ b/proxy.c
@@ -507,7 +507,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
507 ++s; 507 ++s;
508 if (*s == '[') 508 if (*s == '[')
509 ++s, ++bracket; /* for v6 style notation */ 509 ++s, ++bracket; /* for v6 style notation */
510 if (!(off = ip_scan(s, ip))) 510 if (!(off = ip_parse(s, ip)))
511 return 0; 511 return 0;
512 s += off; 512 s += off;
513 if (*s == 0 || isspace(*s)) 513 if (*s == 0 || isspace(*s))
@@ -522,7 +522,7 @@ static int scan_ip6_port(const char *src, ot_ip6 ip, uint16_t *port) {
522 if (*(s++) != ':') 522 if (*(s++) != ':')
523 return 0; 523 return 0;
524 } 524 }
525 if (!(off = scan_udec(s, &value, 65535))) 525 if (!(off = scan_uint(s, &value, 65535)))
526 return 0; 526 return 0;
527 *port = (uint16_t)value; 527 *port = (uint16_t)value;
528 return off + s - src; 528 return off + s - src;
diff --git a/tests/integration/.gitignore b/tests/integration/.gitignore
new file mode 100644
index 0000000..7a60b85
--- /dev/null
+++ b/tests/integration/.gitignore
@@ -0,0 +1,2 @@
1__pycache__/
2*.pyc
diff --git a/tests/integration/harness.py b/tests/integration/harness.py
new file mode 100644
index 0000000..868075d
--- /dev/null
+++ b/tests/integration/harness.py
@@ -0,0 +1,198 @@
1"""Starts a real opentracker and talks to it over the wire.
2
3Everything here drives the binary rather than the code, so these tests cover
4what the unit suite cannot reach: the event loop, the HTTP parser and the
5torrent store working together. Run against a sanitizer build and the tracker
6reports its own memory errors, which the teardown turns into test failures.
7"""
8
9import os
10import shutil
11import signal
12import socket
13import struct
14import subprocess
15import sys
16import tempfile
17import time
18import unittest
19import urllib.error
20import urllib.parse
21import urllib.request
22
23TRACKER = os.environ.get("OPENTRACKER", os.path.join(os.path.dirname(__file__), "..", "..", "opentracker.debug"))
24
25
26def bdecode(data):
27 """Enough bencode for what a tracker answers."""
28
29 def value(i):
30 if data[i : i + 1] == b"d":
31 i, out = i + 1, {}
32 while data[i : i + 1] != b"e":
33 i, key = value(i)
34 i, out[key] = value(i)
35 return i + 1, out
36 if data[i : i + 1] == b"l":
37 i, out = i + 1, []
38 while data[i : i + 1] != b"e":
39 i, item = value(i)
40 out.append(item)
41 return i + 1, out
42 if data[i : i + 1] == b"i":
43 end = data.index(b"e", i)
44 return end + 1, int(data[i + 1 : end])
45 colon = data.index(b":", i)
46 length = int(data[i:colon])
47 return colon + 1 + length, data[colon + 1 : colon + 1 + length]
48
49 return value(0)[1]
50
51
52def free_port():
53 probe = socket.socket()
54 probe.bind(("127.0.0.1", 0))
55 port = probe.getsockname()[1]
56 probe.close()
57 return port
58
59
60class Tracker:
61 """One tracker process, listening on loopback."""
62
63 def __init__(self, extra_config=""):
64 self.port = free_port()
65 self.dir = tempfile.mkdtemp(prefix="ot-integration-")
66 self.log = None
67 self.process = None
68 try:
69 self.config = os.path.join(self.dir, "opentracker.conf")
70 with open(self.config, "w") as handle:
71 handle.write("listen.tcp 127.0.0.1:%d\n" % self.port)
72 handle.write("listen.udp 127.0.0.1:%d\n" % self.port)
73 handle.write(extra_config)
74 self.log = open(os.path.join(self.dir, "tracker.log"), "w+b")
75 # Leak detection is left at the sanitizer's own default, which is on
76 # where LeakSanitizer exists and off where it does not. Asking for it
77 # by name makes the tracker refuse to start on macOS and FreeBSD.
78 environment = dict(os.environ, ASAN_OPTIONS="abort_on_error=0")
79 self.process = subprocess.Popen([TRACKER, "-f", self.config], stdout=self.log, stderr=self.log, env=environment)
80 self._wait_for_port()
81 except BaseException:
82 self._cleanup()
83 raise
84
85 def _cleanup(self):
86 """Close what we opened and take the scratch directory with it."""
87 if self.process is not None and self.process.poll() is None:
88 self.process.kill()
89 self.process.wait()
90 if self.log is not None:
91 self.log.close()
92 self.log = None
93 shutil.rmtree(self.dir, ignore_errors=True)
94
95 def _wait_for_port(self):
96 for _ in range(200):
97 if self.process.poll() is not None:
98 raise AssertionError("tracker exited at once:\n" + self.output())
99 try:
100 probe = socket.create_connection(("127.0.0.1", self.port), timeout=0.2)
101 probe.close()
102 return
103 except OSError:
104 time.sleep(0.05)
105 raise AssertionError("tracker never listened on %d:\n%s" % (self.port, self.output()))
106
107 def output(self):
108 if self.log is None:
109 return ""
110 self.log.flush()
111 self.log.seek(0)
112 return self.log.read().decode("utf-8", "replace")
113
114 def stop(self):
115 """Returns anything the tracker complained about on the way out.
116
117 The interrupt rather than a kill is what makes the leak check work at
118 all: LeakSanitizer reports from an exit handler, so a killed tracker
119 reports nothing and the suite passes over leaks it never looked for.
120 """
121 if self.process is not None and self.process.poll() is None:
122 self.process.send_signal(signal.SIGINT)
123 try:
124 self.process.wait(timeout=5)
125 except subprocess.TimeoutExpired:
126 self.process.kill()
127 self.process.wait()
128 complaints = [
129 line
130 for line in self.output().splitlines()
131 if "Sanitizer" in line or "runtime error" in line or line.startswith("ERROR")
132 ]
133 self._cleanup()
134 return complaints
135
136 # ----------------------------------------------------------- over http
137
138 def get(self, path, timeout=10):
139 return urllib.request.urlopen("http://127.0.0.1:%d%s" % (self.port, path), timeout=timeout).read()
140
141 def announce(self, info_hash, peer_id=b"-OT0001-aaaaaaaaaaaa", port=6881, left=0, **extra):
142 query = {
143 "info_hash": urllib.parse.quote_from_bytes(info_hash, safe=""),
144 "peer_id": urllib.parse.quote_from_bytes(peer_id, safe=""),
145 "port": port,
146 "left": left,
147 }
148 query.update(extra)
149 return bdecode(self.get("/announce?" + "&".join("%s=%s" % item for item in query.items())))
150
151 def scrape(self, info_hash):
152 return bdecode(self.get("/scrape?info_hash=" + urllib.parse.quote_from_bytes(info_hash, safe="")))
153
154 def stats(self, mode):
155 return self.get("/stats?mode=" + mode)
156
157 def torrent_count(self):
158 return int(self.stats("torr").split()[0])
159
160 # ------------------------------------------------------------ over udp
161
162 def udp_connect(self):
163 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
164 sock.settimeout(5)
165 sock.sendto(struct.pack(">QII", 0x41727101980, 0, 1), ("127.0.0.1", self.port))
166 answer = sock.recv(2048)
167 action, _, connection_id = struct.unpack(">IIQ", answer[:16])
168 assert action == 0, "udp connect answered action %d" % action
169 return sock, connection_id
170
171 def udp_announce(self, sock, connection_id, info_hash, left=0, event=0, port=6881, numwant=-1):
172 sock.sendto(
173 struct.pack(
174 ">QII20s20sQQQIIIiH", connection_id, 1, 2, info_hash, b"p" * 20, 0, left, 0, event, 0, 0, numwant, port
175 ),
176 ("127.0.0.1", self.port),
177 )
178 answer = sock.recv(2048)
179 action, _, interval, leechers, seeders = struct.unpack(">IIIII", answer[:20])
180 assert action == 1, "udp announce answered action %d" % action
181 return {"interval": interval, "leechers": leechers, "seeders": seeders, "peers": answer[20:]}
182
183
184class TrackerTestCase(unittest.TestCase):
185 """Gives every test its own tracker and fails it on any sanitizer output."""
186
187 config = ""
188
189 def setUp(self):
190 self.tracker = Tracker(self.config)
191
192 def tearDown(self):
193 complaints = self.tracker.stop()
194 self.assertEqual(complaints, [], "the tracker reported:\n" + "\n".join(complaints))
195
196 def hash_for(self, name):
197 """A distinct info hash per test, so tests cannot leak into each other."""
198 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 335d83d..71a5e2a 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -4,7 +4,11 @@
4 $id$ */ 4 $id$ */
5 5
6/* System */ 6/* System */
7#include <stdio.h>
8#include <stdlib.h>
7#include <string.h> 9#include <string.h>
10#include <sys/wait.h>
11#include <unistd.h>
8 12
9/* Opentracker */ 13/* Opentracker */
10#include "test.h" 14#include "test.h"
@@ -14,7 +18,7 @@ struct module {
14 void (*run)(void); 18 void (*run)(void);
15}; 19};
16 20
17static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urlencoded_query}, 21static const struct module modules[] = {{"ot_urlencoded", test_urlencoded},
18 {"ot_iovec", test_iovec}, 22 {"ot_iovec", test_iovec},
19 {"ot_vector", test_vector}, 23 {"ot_vector", test_vector},
20 {"ot_random", test_random}, 24 {"ot_random", test_random},
@@ -26,18 +30,84 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle
26 {"ot_mutex", test_mutex}, 30 {"ot_mutex", test_mutex},
27 {"ot_batch", test_batch}, 31 {"ot_batch", test_batch},
28 {"ot_fdstate", test_fdstate}, 32 {"ot_fdstate", test_fdstate},
33 {"ot_event", test_event},
34 {"ot_fmt", test_fmt},
29 {"ot_accesslist", test_accesslist}, 35 {"ot_accesslist", test_accesslist},
30 {NULL, NULL}}; 36 {NULL, NULL}};
31 37
32/* Run every module, or only those whose name contains argv[1] */ 38struct tally {
39 unsigned long checks;
40 unsigned long failures;
41};
42
43/* Each module runs in a child of its own, so that a crash or a sanitizer
44 abort costs that module's results rather than every module after it. The
45 child reports its tally through a pipe; one that dies without reporting is
46 counted as a failure under its own name. */
47static void run_forked(const struct module *m) {
48 struct tally tally = {0, 0};
49 int pipefd[2], status = 0;
50 pid_t child;
51 ssize_t got;
52
53 if (pipe(pipefd)) {
54 test_failf(__FILE__, __LINE__, "pipe failed, cannot isolate %s", m->name);
55 return;
56 }
57
58 fflush(stdout); /* or the child inherits a copy and prints it again */
59 child = fork();
60 if (child < 0) {
61 close(pipefd[0]);
62 close(pipefd[1]);
63 test_failf(__FILE__, __LINE__, "fork failed, cannot isolate %s", m->name);
64 return;
65 }
66
67 if (!child) {
68 close(pipefd[0]);
69 test_checks = test_failures = 0;
70 m->run();
71 tally.checks = test_checks;
72 tally.failures = test_failures;
73 if (write(pipefd[1], &tally, sizeof(tally)) != (ssize_t)sizeof(tally))
74 _exit(1);
75 fflush(stdout);
76 _exit(0);
77 }
78
79 close(pipefd[1]);
80 got = read(pipefd[0], &tally, sizeof(tally));
81 close(pipefd[0]);
82 while (waitpid(child, &status, 0) < 0)
83 ;
84
85 if (got == (ssize_t)sizeof(tally)) {
86 test_checks += tally.checks;
87 test_failures += tally.failures;
88 }
89 if (got != (ssize_t)sizeof(tally) || !WIFEXITED(status) || WEXITSTATUS(status))
90 test_failf(__FILE__, __LINE__, "%s did not run to the end", m->name);
91}
92
93/* Run every module, or only those whose name contains argv[1].
94 OT_TEST_FORK=0 keeps everything in one process, which is what a debugger
95 wants. */
33int main(int argc, char **argv) { 96int main(int argc, char **argv) {
97 const char *fork_setting = getenv("OT_TEST_FORK");
98 int isolate = !fork_setting || strcmp(fork_setting, "0");
34 const struct module *m; 99 const struct module *m;
35 100
36 for (m = modules; m->name; ++m) { 101 for (m = modules; m->name; ++m) {
37 if (argc > 1 && !strstr(m->name, argv[1])) 102 if (argc > 1 && !strstr(m->name, argv[1]))
38 continue; 103 continue;
39 test_module(m->name); 104 test_module(m->name);
40 m->run(); 105 if (!isolate) {
106 m->run();
107 continue;
108 }
109 test_case("the module runs to the end");
110 run_forked(m);
41 } 111 }
42 112
43 return test_summary(); 113 return test_summary();
diff --git a/tests/t_event.c b/tests/t_event.c
new file mode 100644
index 0000000..5509f19
--- /dev/null
+++ b/tests/t_event.c
@@ -0,0 +1,222 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <string.h>
8#include <sys/socket.h>
9#include <sys/time.h>
10#include <unistd.h>
11
12/* Opentracker */
13#include "ot_event.h"
14#include "test.h"
15
16/* Against real socket pairs: only the kernel can say whether a descriptor is
17 ready, so there is nothing useful to mock here. */
18
19#define EVENTS 16
20
21static int sv[2];
22static ot_event ready[EVENTS];
23
24static void pair_open(void) {
25 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sv) == -1)
26 sv[0] = sv[1] = -1;
27}
28
29static void pair_close(void) {
30 if (sv[0] != -1)
31 close(sv[0]);
32 if (sv[1] != -1)
33 close(sv[1]);
34 sv[0] = sv[1] = -1;
35}
36
37static int wait_for(int fd, int what, int timeout_ms, uint32_t *generation) {
38 int count = event_wait(ready, EVENTS, timeout_ms), i;
39
40 for (i = 0; i < count; ++i)
41 if (ready[i].fd == fd && (ready[i].what & what)) {
42 if (generation)
43 *generation = ready[i].generation;
44 return 1;
45 }
46 return 0;
47}
48
49static unsigned long elapsed_ms(struct timeval *since) {
50 struct timeval now;
51
52 gettimeofday(&now, NULL);
53 return (unsigned long)((now.tv_sec - since->tv_sec) * 1000 + (now.tv_usec - since->tv_usec) / 1000);
54}
55
56static void case_readable(void) {
57 uint32_t generation = 0;
58
59 test_case("a descriptor with something to read is reported, with its generation");
60 pair_open();
61 CHECK(sv[0] != -1);
62 CHECK_INT(event_add(sv[0], EVENT_IN, 4711), 0);
63 CHECK_INT(write(sv[1], "x", 1), 1);
64 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, &generation), 1);
65 CHECK_INT(generation, 4711);
66
67 test_case("and keeps being reported until it is read");
68 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, NULL), 1);
69 {
70 char scratch[4];
71 CHECK_INT(read(sv[0], scratch, sizeof(scratch)), 1);
72 }
73 CHECK_INT(wait_for(sv[0], EVENT_IN, 50, NULL), 0);
74
75 CHECK_INT(event_del(sv[0]), 0);
76 pair_close();
77}
78
79static void case_writable(void) {
80 test_case("a descriptor with room to write is reported");
81 pair_open();
82 CHECK(sv[0] != -1);
83 CHECK_INT(event_add(sv[0], EVENT_OUT, 1), 0);
84 CHECK_INT(wait_for(sv[0], EVENT_OUT, 1000, NULL), 1);
85
86 test_case("and is not, once we say we no longer care");
87 CHECK_INT(event_modify(sv[0], EVENT_IN, 1), 0);
88 CHECK_INT(wait_for(sv[0], EVENT_OUT, 50, NULL), 0);
89
90 CHECK_INT(event_del(sv[0]), 0);
91 pair_close();
92}
93
94static void case_interest_is_separate(void) {
95 test_case("asking only about writing does not report reading");
96 pair_open();
97 CHECK(sv[0] != -1);
98 CHECK_INT(event_add(sv[0], EVENT_OUT, 1), 0);
99 CHECK_INT(write(sv[1], "x", 1), 1);
100 CHECK_INT(wait_for(sv[0], EVENT_IN, 50, NULL), 0);
101
102 test_case("until we ask about it");
103 CHECK_INT(event_modify(sv[0], EVENT_IN | EVENT_OUT, 1), 0);
104 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, NULL), 1);
105
106 CHECK_INT(event_del(sv[0]), 0);
107 pair_close();
108}
109
110static void case_removed_is_silent(void) {
111 test_case("a removed descriptor is not reported again");
112 pair_open();
113 CHECK(sv[0] != -1);
114 CHECK_INT(event_add(sv[0], EVENT_IN, 1), 0);
115 CHECK_INT(write(sv[1], "x", 1), 1);
116 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, NULL), 1);
117 CHECK_INT(event_del(sv[0]), 0);
118 CHECK_INT(wait_for(sv[0], EVENT_IN, 50, NULL), 0);
119 pair_close();
120}
121
122static void case_generations_do_not_mix(void) {
123 int other[2];
124 uint32_t generation = 0;
125
126 test_case("two descriptors carry their own generations");
127 pair_open();
128 CHECK(sv[0] != -1);
129 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, other), 0);
130 CHECK_INT(event_add(sv[0], EVENT_IN, 100), 0);
131 CHECK_INT(event_add(other[0], EVENT_IN, 200), 0);
132 CHECK_INT(write(sv[1], "x", 1), 1);
133 CHECK_INT(write(other[1], "y", 1), 1);
134
135 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, &generation), 1);
136 CHECK_INT(generation, 100);
137 CHECK_INT(wait_for(other[0], EVENT_IN, 1000, &generation), 1);
138 CHECK_INT(generation, 200);
139
140 test_case("and re-registering one changes only its own");
141 CHECK_INT(event_modify(sv[0], EVENT_IN, 101), 0);
142 CHECK_INT(wait_for(sv[0], EVENT_IN, 1000, &generation), 1);
143 CHECK_INT(generation, 101);
144 CHECK_INT(wait_for(other[0], EVENT_IN, 1000, &generation), 1);
145 CHECK_INT(generation, 200);
146
147 event_del(sv[0]);
148 event_del(other[0]);
149 close(other[0]);
150 close(other[1]);
151 pair_close();
152}
153
154/* Removing one of several has to leave the rest addressable: a backend that
155 keeps its descriptors packed moves the last one into the gap, and has to
156 remember where it went. */
157static void case_delete_from_the_middle(void) {
158 int pair[3][2];
159 uint32_t generation = 0;
160 int i;
161
162 test_case("deleting one of three leaves the others reported correctly");
163 for (i = 0; i < 3; ++i) {
164 CHECK_INT(socketpair(AF_UNIX, SOCK_STREAM, 0, pair[i]), 0);
165 CHECK_INT(event_add(pair[i][0], EVENT_IN, (uint32_t)(10 + i)), 0);
166 }
167 CHECK_INT(event_del(pair[1][0]), 0);
168
169 for (i = 0; i < 3; i += 2) {
170 CHECK_INT(write(pair[i][1], "x", 1), 1);
171 CHECK_INT(wait_for(pair[i][0], EVENT_IN, 1000, &generation), 1);
172 CHECK_INT(generation, 10 + i);
173 }
174
175 test_case("and the survivors can still be changed and removed");
176 CHECK_INT(event_modify(pair[2][0], EVENT_IN, 99), 0);
177 CHECK_INT(wait_for(pair[2][0], EVENT_IN, 1000, &generation), 1);
178 CHECK_INT(generation, 99);
179 CHECK_INT(event_del(pair[0][0]), 0);
180 CHECK_INT(event_del(pair[2][0]), 0);
181 CHECK_INT(wait_for(pair[2][0], EVENT_IN, 50, NULL), 0);
182
183 for (i = 0; i < 3; ++i)
184 close(pair[i][0]), close(pair[i][1]);
185}
186
187static void case_timeout(void) {
188 struct timeval started;
189
190 test_case("waiting on nothing comes back empty when the time is up");
191 gettimeofday(&started, NULL);
192 CHECK_INT(event_wait(ready, EVENTS, 200), 0);
193 {
194 unsigned long took = elapsed_ms(&started);
195 /* Scheduling makes the upper bound generous. The point is that it waited
196 rather than returning at once, and that it came back at all */
197 CHECK(took >= 150);
198 CHECK(took < 5000);
199 }
200}
201
202static void case_unknown_descriptor(void) {
203 test_case("changing or removing one that was never added is refused");
204 CHECK_INT(event_modify(4096, EVENT_IN, 1), -1);
205 CHECK_INT(event_del(4096), -1);
206}
207
208void test_event(void) {
209 CHECK_INT(event_init(), 0);
210 CHECK(event_mechanism() != NULL);
211
212 case_readable();
213 case_writable();
214 case_interest_is_separate();
215 case_removed_is_silent();
216 case_generations_do_not_mix();
217 case_delete_from_the_middle();
218 case_timeout();
219 case_unknown_descriptor();
220
221 event_deinit();
222}
diff --git a/tests/t_fmt.c b/tests/t_fmt.c
new file mode 100644
index 0000000..bc23ed5
--- /dev/null
+++ b/tests/t_fmt.c
@@ -0,0 +1,108 @@
1/* This software was written by Dirk Engling <erdgeist@erdgeist.org>
2 It is considered beerware. Prost. Skol. Cheers or whatever.
3
4 $id$ */
5
6/* System */
7#include <string.h>
8
9/* Opentracker */
10#include "ot_fmt.h"
11#include "test.h"
12
13static void case_appends_in_sequence(void) {
14 char buffer[64];
15 char *r = buffer, *re = buffer + sizeof(buffer);
16
17 test_case("appends follow each other and advance the cursor");
18 fmt_append(&r, re, "d8:completei%zde", (size_t)7);
19 fmt_append(&r, re, "ee");
20 *r = 0;
21 CHECK_STR(buffer, "d8:completei7eee");
22 CHECK_INT(r - buffer, 16);
23}
24
25static void case_truncation_stops_at_the_end(void) {
26 char buffer[16];
27 char *r = buffer, *re = buffer + 8; /* pretend the buffer ends early */
28
29 memset(buffer, '#', sizeof(buffer));
30 test_case("a value too long for the room left stops at the end");
31 fmt_append(&r, re, "%s", "0123456789");
32 CHECK_PTR(r, re);
33
34 test_case("and writes nothing past it");
35 CHECK_INT(buffer[8], '#');
36 CHECK_INT(buffer[15], '#');
37}
38
39static void case_a_full_buffer_takes_nothing_more(void) {
40 char buffer[16];
41 char *r = buffer + 8, *re = buffer + 8;
42
43 memset(buffer, '#', sizeof(buffer));
44 test_case("appending to a cursor already at the end changes nothing");
45 fmt_append(&r, re, "more");
46 CHECK_PTR(r, re);
47 CHECK_INT(buffer[8], '#');
48}
49
50static void case_an_append_that_just_fits(void) {
51 char buffer[8];
52 char *r = buffer, *re = buffer + 3;
53
54 memset(buffer, '#', sizeof(buffer));
55 test_case("three characters into three bytes of room still truncate");
56 fmt_append(&r, re, "abc");
57 CHECK_PTR(r, re);
58 CHECK_INT(buffer[0], 'a');
59 CHECK_INT(buffer[1], 'b');
60
61 test_case("because snprintf spends the last byte on its terminator");
62 CHECK_INT(buffer[2], 0);
63 CHECK_INT(buffer[3], '#');
64}
65
66static void case_bytes_are_all_or_nothing(void) {
67 char buffer[16];
68 char *r = buffer, *re = buffer + 8;
69
70 memset(buffer, '#', sizeof(buffer));
71 test_case("a run of bytes is written when it fits");
72 fmt_bytes(&r, re, "\0\1\2\3", 4);
73 CHECK_PTR(r, buffer + 4);
74 CHECK_MEM(buffer, "\0\1\2\3", 4);
75
76 test_case("a run that would not fit is refused whole");
77 fmt_bytes(&r, re, "abcde", 5);
78 CHECK_PTR(r, re);
79 CHECK_INT(buffer[4], '#');
80
81 test_case("a run of exactly the room left is written");
82 r = buffer + 4;
83 fmt_bytes(&r, re, "wxyz", 4);
84 CHECK_PTR(r, re);
85 CHECK_MEM(buffer + 4, "wxyz", 4);
86 CHECK_INT(buffer[8], '#');
87}
88
89static void case_bytes_do_not_terminate(void) {
90 char buffer[8];
91 char *r = buffer, *re = buffer + 4;
92
93 memset(buffer, '#', sizeof(buffer));
94 test_case("unlike an append, a run of bytes may fill the room exactly");
95 fmt_bytes(&r, re, "abcd", 4);
96 CHECK_PTR(r, re);
97 CHECK_MEM(buffer, "abcd", 4);
98 CHECK_INT(buffer[4], '#');
99}
100
101void test_fmt(void) {
102 case_appends_in_sequence();
103 case_truncation_stops_at_the_end();
104 case_a_full_buffer_takes_nothing_more();
105 case_an_append_that_just_fits();
106 case_bytes_are_all_or_nothing();
107 case_bytes_do_not_terminate();
108}
diff --git a/tests/t_hex.c b/tests/t_hex.c
index 7cce8ac..a761737 100644
--- a/tests/t_hex.c
+++ b/tests/t_hex.c
@@ -18,7 +18,7 @@ static void case_encode(void) {
18 18
19 test_case("a hash encodes to forty upper case digits and a terminator"); 19 test_case("a hash encodes to forty upper case digits and a terminator");
20 memset(dest, 0x7f, sizeof(dest)); 20 memset(dest, 0x7f, sizeof(dest));
21 CHECK_PTR(hex_fromhash(dest, sample), dest); 21 CHECK_PTR(hex_from_hash(dest, sample), dest);
22 CHECK_STR(dest, "000123456789ABCDEFFF10203040506070809" "0A0"); 22 CHECK_STR(dest, "000123456789ABCDEFFF10203040506070809" "0A0");
23 CHECK_INT(strlen(dest), 40); 23 CHECK_INT(strlen(dest), 40);
24 CHECK_INT((unsigned char)dest[41], 0x7f); 24 CHECK_INT((unsigned char)dest[41], 0x7f);
@@ -28,7 +28,7 @@ static void case_decode(void) {
28 ot_hash hash; 28 ot_hash hash;
29 29
30 test_case("forty digits decode back to the hash"); 30 test_case("forty digits decode back to the hash");
31 CHECK(hex_tohash(hash, "000123456789ABCDEFFF10203040506070809" "0A0")); 31 CHECK(hex_to_hash(hash, "000123456789ABCDEFFF10203040506070809" "0A0"));
32 CHECK_MEM(hash, sample, sizeof(sample)); 32 CHECK_MEM(hash, sample, sizeof(sample));
33} 33}
34 34
@@ -41,8 +41,8 @@ static void case_round_trip(void) {
41 for (i = 0; i < 256; ++i) { 41 for (i = 0; i < 256; ++i) {
42 uint8_t probe[20]; 42 uint8_t probe[20];
43 memset(probe, (uint8_t)i, sizeof(probe)); 43 memset(probe, (uint8_t)i, sizeof(probe));
44 hex_fromhash(text, probe); 44 hex_from_hash(text, probe);
45 if (!hex_tohash(hash, text) || memcmp(hash, probe, sizeof(probe))) { 45 if (!hex_to_hash(hash, text) || memcmp(hash, probe, sizeof(probe))) {
46 test_failf(__FILE__, __LINE__, "round trip failed for byte value %zu", i); 46 test_failf(__FILE__, __LINE__, "round trip failed for byte value %zu", i);
47 return; 47 return;
48 } 48 }
@@ -54,8 +54,8 @@ static void case_decode_is_case_insensitive(void) {
54 ot_hash upper, lower; 54 ot_hash upper, lower;
55 55
56 test_case("lower case digits decode the same as upper case"); 56 test_case("lower case digits decode the same as upper case");
57 CHECK(hex_tohash(upper, "ABCDEF0123456789ABCDEF0123456789ABCDEF01")); 57 CHECK(hex_to_hash(upper, "ABCDEF0123456789ABCDEF0123456789ABCDEF01"));
58 CHECK(hex_tohash(lower, "abcdef0123456789abcdef0123456789abcdef01")); 58 CHECK(hex_to_hash(lower, "abcdef0123456789abcdef0123456789abcdef01"));
59 CHECK_MEM(lower, upper, sizeof(ot_hash)); 59 CHECK_MEM(lower, upper, sizeof(ot_hash));
60} 60}
61 61
@@ -63,18 +63,18 @@ static void case_rejects_non_digits(void) {
63 ot_hash hash; 63 ot_hash hash;
64 64
65 test_case("a non hex character anywhere rejects the whole string"); 65 test_case("a non hex character anywhere rejects the whole string");
66 CHECK_INT(hex_tohash(hash, "g00123456789ABCDEFFF102030405060708090A0"), 0); 66 CHECK_INT(hex_to_hash(hash, "g00123456789ABCDEFFF102030405060708090A0"), 0);
67 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF102030405060708090Ag"), 0); 67 CHECK_INT(hex_to_hash(hash, "000123456789ABCDEFFF102030405060708090Ag"), 0);
68 CHECK_INT(hex_tohash(hash, "000123456789ABCDEF:F102030405060708090A"), 0); 68 CHECK_INT(hex_to_hash(hash, "000123456789ABCDEF:F102030405060708090A"), 0);
69} 69}
70 70
71static void case_rejects_short_input(void) { 71static void case_rejects_short_input(void) {
72 ot_hash hash; 72 ot_hash hash;
73 73
74 test_case("a string shorter than forty digits is rejected"); 74 test_case("a string shorter than forty digits is rejected");
75 CHECK_INT(hex_tohash(hash, ""), 0); 75 CHECK_INT(hex_to_hash(hash, ""), 0);
76 CHECK_INT(hex_tohash(hash, "00"), 0); 76 CHECK_INT(hex_to_hash(hash, "00"), 0);
77 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF102030405060708090A"), 0); 77 CHECK_INT(hex_to_hash(hash, "000123456789ABCDEFFF102030405060708090A"), 0);
78} 78}
79 79
80static void case_leaves_hash_untouched_on_failure(void) { 80static void case_leaves_hash_untouched_on_failure(void) {
@@ -82,7 +82,7 @@ static void case_leaves_hash_untouched_on_failure(void) {
82 82
83 test_case("a rejected string leaves the destination untouched"); 83 test_case("a rejected string leaves the destination untouched");
84 memset(hash, 0x5a, sizeof(hash)); 84 memset(hash, 0x5a, sizeof(hash));
85 CHECK_INT(hex_tohash(hash, "000123456789ABCDEFFF10203040506070809XX0"), 0); 85 CHECK_INT(hex_to_hash(hash, "000123456789ABCDEFFF10203040506070809XX0"), 0);
86 CHECK_MEM(hash, "\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a", sizeof(hash)); 86 CHECK_MEM(hash, "\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a\x5a", sizeof(hash));
87} 87}
88 88
@@ -96,7 +96,7 @@ static void case_stops_at_the_terminator(void) {
96 memcpy(guarded, "0012", 4); 96 memcpy(guarded, "0012", 4);
97 guarded[4] = 0; 97 guarded[4] = 0;
98 memset(guarded + 5, 'f', 3); 98 memset(guarded + 5, 'f', 3);
99 CHECK_INT(hex_tohash(hash, guarded), 0); 99 CHECK_INT(hex_to_hash(hash, guarded), 0);
100} 100}
101 101
102static void case_isdigit(void) { 102static void case_isdigit(void) {
diff --git a/tests/t_iovec.c b/tests/t_iovec.c
index 89f3fda..2553141 100644
--- a/tests/t_iovec.c
+++ b/tests/t_iovec.c
@@ -18,13 +18,13 @@ static void case_increase_and_length(void) {
18 void *first, *second; 18 void *first, *second;
19 19
20 test_case("increase appends an entry and length is their sum"); 20 test_case("increase appends an entry and length is their sum");
21 first = iovec_increase(&entries, &vector, 100); 21 first = iovec_new_chunk(&entries, &vector, 100);
22 CHECK(first != NULL); 22 CHECK(first != NULL);
23 CHECK_INT(entries, 1); 23 CHECK_INT(entries, 1);
24 CHECK_INT(vector[0].iov_len, 100); 24 CHECK_INT(vector[0].iov_len, 100);
25 CHECK_PTR(vector[0].iov_base, first); 25 CHECK_PTR(vector[0].iov_base, first);
26 26
27 second = iovec_increase(&entries, &vector, 50); 27 second = iovec_new_chunk(&entries, &vector, 50);
28 CHECK(second != NULL); 28 CHECK(second != NULL);
29 CHECK_INT(entries, 2); 29 CHECK_INT(entries, 2);
30 CHECK_INT(vector[1].iov_len, 50); 30 CHECK_INT(vector[1].iov_len, 50);
@@ -38,8 +38,8 @@ static void case_free_resets(void) {
38 struct iovec *vector = NULL; 38 struct iovec *vector = NULL;
39 39
40 test_case("free releases the entries and resets the vector"); 40 test_case("free releases the entries and resets the vector");
41 CHECK(iovec_increase(&entries, &vector, 10) != NULL); 41 CHECK(iovec_new_chunk(&entries, &vector, 10) != NULL);
42 CHECK(iovec_increase(&entries, &vector, 10) != NULL); 42 CHECK(iovec_new_chunk(&entries, &vector, 10) != NULL);
43 iovec_free(&entries, &vector); 43 iovec_free(&entries, &vector);
44 CHECK_INT(entries, 0); 44 CHECK_INT(entries, 0);
45 CHECK_PTR(vector, NULL); 45 CHECK_PTR(vector, NULL);
@@ -52,12 +52,12 @@ static void case_fixlast_shrinks(void) {
52 char *block; 52 char *block;
53 53
54 test_case("fixlast trims the last entry to the bytes actually used"); 54 test_case("fixlast trims the last entry to the bytes actually used");
55 CHECK(iovec_increase(&entries, &vector, 64) != NULL); 55 CHECK(iovec_new_chunk(&entries, &vector, 64) != NULL);
56 block = iovec_increase(&entries, &vector, 1000); 56 block = iovec_new_chunk(&entries, &vector, 1000);
57 CHECK(block != NULL); 57 CHECK(block != NULL);
58 memset(block, 'x', 20); 58 memset(block, 'x', 20);
59 59
60 iovec_fixlast(&entries, &vector, block + 20); 60 iovec_trim_last(&entries, &vector, block + 20);
61 CHECK_INT(entries, 2); 61 CHECK_INT(entries, 2);
62 CHECK_INT(vector[1].iov_len, 20); 62 CHECK_INT(vector[1].iov_len, 20);
63 CHECK_INT(iovec_length(&entries, (const struct iovec **)&vector), 84); 63 CHECK_INT(iovec_length(&entries, (const struct iovec **)&vector), 84);
@@ -71,7 +71,7 @@ static void case_fixlast_on_empty(void) {
71 struct iovec *vector = NULL; 71 struct iovec *vector = NULL;
72 72
73 test_case("fixlast on an empty vector does nothing"); 73 test_case("fixlast on an empty vector does nothing");
74 iovec_fixlast(&entries, &vector, NULL); 74 iovec_trim_last(&entries, &vector, NULL);
75 CHECK_INT(entries, 0); 75 CHECK_INT(entries, 0);
76 CHECK_PTR(vector, NULL); 76 CHECK_PTR(vector, NULL);
77} 77}
@@ -103,9 +103,9 @@ static void case_fix_increase(void) {
103 char *block; 103 char *block;
104 104
105 test_case("fix_increase trims the previous entry and adds a new one"); 105 test_case("fix_increase trims the previous entry and adds a new one");
106 block = iovec_increase(&entries, &vector, 500); 106 block = iovec_new_chunk(&entries, &vector, 500);
107 CHECK(block != NULL); 107 CHECK(block != NULL);
108 block = iovec_fix_increase_or_free(&entries, &vector, block + 30, 200); 108 block = iovec_next_chunk(&entries, &vector, block + 30, 200);
109 CHECK(block != NULL); 109 CHECK(block != NULL);
110 CHECK_INT(entries, 2); 110 CHECK_INT(entries, 2);
111 CHECK_INT(vector[0].iov_len, 30); 111 CHECK_INT(vector[0].iov_len, 30);
diff --git a/tests/t_ip.c b/tests/t_ip.c
index 2fe3a72..8d71679 100644
--- a/tests/t_ip.c
+++ b/tests/t_ip.c
@@ -95,27 +95,27 @@ static void case_scan_forms(void) {
95 char ip[16]; 95 char ip[16];
96 96
97 test_case("2.2 form 1, every group spelled out"); 97 test_case("2.2 form 1, every group spelled out");
98 CHECK_INT(ip_scan("1111:2222:3333:4444:5555:6666:7777:8888", ip), 39); 98 CHECK_INT(ip_parse("1111:2222:3333:4444:5555:6666:7777:8888", ip), 39);
99 CHECK_MEM(ip, "\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88", 16); 99 CHECK_MEM(ip, "\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88", 16);
100 100
101 test_case("2.2 form 2, zero groups abbreviated"); 101 test_case("2.2 form 2, zero groups abbreviated");
102 CHECK_INT(ip_scan("2001:db8::1", ip), 11); 102 CHECK_INT(ip_parse("2001:db8::1", ip), 11);
103 CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); 103 CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16);
104 CHECK_INT(ip_scan("::1", ip), 3); 104 CHECK_INT(ip_parse("::1", ip), 3);
105 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); 105 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16);
106 CHECK_INT(ip_scan("::", ip), 2); 106 CHECK_INT(ip_parse("::", ip), 2);
107 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); 107 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16);
108 CHECK_INT(ip_scan("1::", ip), 3); 108 CHECK_INT(ip_parse("1::", ip), 3);
109 CHECK_MEM(ip, "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16); 109 CHECK_MEM(ip, "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16);
110 110
111 test_case("2.2 form 3, a dotted quad in the last two groups"); 111 test_case("2.2 form 3, a dotted quad in the last two groups");
112 CHECK_INT(ip_scan("::ffff:1.2.3.4", ip), 14); 112 CHECK_INT(ip_parse("::ffff:1.2.3.4", ip), 14);
113 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04", 16); 113 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x01\x02\x03\x04", 16);
114 CHECK_INT(ip_scan("1:2:3:4:5:6:1.2.3.4", ip), 19); 114 CHECK_INT(ip_parse("1:2:3:4:5:6:1.2.3.4", ip), 19);
115 CHECK_MEM(ip, "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x02\x03\x04", 16); 115 CHECK_MEM(ip, "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x02\x03\x04", 16);
116 116
117 test_case("upper case input is accepted"); 117 test_case("upper case input is accepted");
118 CHECK_INT(ip_scan("2001:DB8::1", ip), 11); 118 CHECK_INT(ip_parse("2001:DB8::1", ip), 11);
119 CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16); 119 CHECK_MEM(ip, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", 16);
120} 120}
121 121
@@ -125,7 +125,7 @@ static void case_scan_accepts_bare_ipv4(void) {
125 /* Every address opentracker holds is sixteen bytes, so an operator writing 125 /* Every address opentracker holds is sixteen bytes, so an operator writing
126 a plain IPv4 address in the config has to land as the v4 mapped form */ 126 a plain IPv4 address in the config has to land as the v4 mapped form */
127 test_case("a bare IPv4 address is stored v4 mapped"); 127 test_case("a bare IPv4 address is stored v4 mapped");
128 CHECK_INT(ip_scan("10.0.0.1", ip), 8); 128 CHECK_INT(ip_parse("10.0.0.1", ip), 8);
129 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x0a\x00\x00\x01", 16); 129 CHECK_MEM(ip, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x0a\x00\x00\x01", 16);
130 CHECK(ip_is_v4mapped(ip)); 130 CHECK(ip_is_v4mapped(ip));
131} 131}
@@ -136,39 +136,39 @@ static void case_scan_reports_length(void) {
136 /* The config parser continues from where the address ended, which is how 136 /* The config parser continues from where the address ended, which is how
137 "address:port" and "address/bits" are read */ 137 "address:port" and "address/bits" are read */
138 test_case("scanning stops at the character after the address"); 138 test_case("scanning stops at the character after the address");
139 CHECK_INT(ip_scan("10.0.0.1:6969", ip), 8); 139 CHECK_INT(ip_parse("10.0.0.1:6969", ip), 8);
140 CHECK_INT(ip_scan("2001:db8::1/64", ip), 11); 140 CHECK_INT(ip_parse("2001:db8::1/64", ip), 11);
141 CHECK_INT(ip_scan("::1]", ip), 3); 141 CHECK_INT(ip_parse("::1]", ip), 3);
142 CHECK_INT(ip_scan("1:2:3:4:5:6:7:8 trailing", ip), 15); 142 CHECK_INT(ip_parse("1:2:3:4:5:6:7:8 trailing", ip), 15);
143} 143}
144 144
145static void case_scan_rejects(void) { 145static void case_scan_rejects(void) {
146 char ip[16]; 146 char ip[16];
147 147
148 test_case("an empty or junk string is refused"); 148 test_case("an empty or junk string is refused");
149 CHECK_INT(ip_scan("", ip), 0); 149 CHECK_INT(ip_parse("", ip), 0);
150 CHECK_INT(ip_scan("x", ip), 0); 150 CHECK_INT(ip_parse("x", ip), 0);
151 CHECK_INT(ip_scan(":", ip), 0); 151 CHECK_INT(ip_parse(":", ip), 0);
152 CHECK_INT(ip_scan(":1:2", ip), 0); 152 CHECK_INT(ip_parse(":1:2", ip), 0);
153 153
154 test_case("too few groups without :: are refused"); 154 test_case("too few groups without :: are refused");
155 CHECK_INT(ip_scan("1:2:3:4:5:6:7", ip), 0); 155 CHECK_INT(ip_parse("1:2:3:4:5:6:7", ip), 0);
156 CHECK_INT(ip_scan("2001:db8:1", ip), 0); 156 CHECK_INT(ip_parse("2001:db8:1", ip), 0);
157 157
158 /* libowfat accepted a prefix of each of these and carried on. Taking only 158 /* libowfat accepted a prefix of each of these and carried on. Taking only
159 part of a malformed address is worse than refusing it, especially where 159 part of a malformed address is worse than refusing it, especially where
160 the text comes from a config file or an X-Forwarded-For header. */ 160 the text comes from a config file or an X-Forwarded-For header. */
161 test_case("a second :: is refused rather than truncated"); 161 test_case("a second :: is refused rather than truncated");
162 CHECK_INT(ip_scan("1::2::3", ip), 0); 162 CHECK_INT(ip_parse("1::2::3", ip), 0);
163 CHECK_INT(ip_scan("::1::", ip), 0); 163 CHECK_INT(ip_parse("::1::", ip), 0);
164 CHECK_INT(ip_scan("::::", ip), 0); 164 CHECK_INT(ip_parse("::::", ip), 0);
165 165
166 test_case("more than eight groups is refused"); 166 test_case("more than eight groups is refused");
167 CHECK_INT(ip_scan("1:2:3:4:5:6:7:8:9", ip), 0); 167 CHECK_INT(ip_parse("1:2:3:4:5:6:7:8:9", ip), 0);
168 168
169 test_case("a group of more than four digits is refused, not truncated"); 169 test_case("a group of more than four digits is refused, not truncated");
170 CHECK_INT(ip_scan("12345::1", ip), 0); 170 CHECK_INT(ip_parse("12345::1", ip), 0);
171 CHECK_INT(ip_scan("fffff::1", ip), 0); 171 CHECK_INT(ip_parse("fffff::1", ip), 0);
172} 172}
173 173
174static void case_round_trip(void) { 174static void case_round_trip(void) {
@@ -187,7 +187,7 @@ static void case_round_trip(void) {
187 187
188 test_case("every formatted address scans back to the same bytes"); 188 test_case("every formatted address scans back to the same bytes");
189 for (i = 0; samples[i]; ++i) { 189 for (i = 0; samples[i]; ++i) {
190 size_t n = ip_scan(samples[i], ip); 190 size_t n = ip_parse(samples[i], ip);
191 if (n != strlen(samples[i])) { 191 if (n != strlen(samples[i])) {
192 test_failf(__FILE__, __LINE__, "\"%s\" consumed %zu of %zu", samples[i], n, strlen(samples[i])); 192 test_failf(__FILE__, __LINE__, "\"%s\" consumed %zu of %zu", samples[i], n, strlen(samples[i]));
193 return; 193 return;
@@ -197,7 +197,7 @@ static void case_round_trip(void) {
197 test_failf(__FILE__, __LINE__, "\"%s\" formatted back as \"%s\"", samples[i], text); 197 test_failf(__FILE__, __LINE__, "\"%s\" formatted back as \"%s\"", samples[i], text);
198 return; 198 return;
199 } 199 }
200 if (!ip_scan(text, again) || memcmp(ip, again, 16)) { 200 if (!ip_parse(text, again) || memcmp(ip, again, 16)) {
201 test_failf(__FILE__, __LINE__, "\"%s\" did not survive a second pass", samples[i]); 201 test_failf(__FILE__, __LINE__, "\"%s\" did not survive a second pass", samples[i]);
202 return; 202 return;
203 } 203 }
diff --git a/tests/t_scan.c b/tests/t_scan.c
index 89f1590..aab4413 100644
--- a/tests/t_scan.c
+++ b/tests/t_scan.c
@@ -15,13 +15,13 @@ static void case_plain_numbers(void) {
15 uint64_t value; 15 uint64_t value;
16 16
17 test_case("a decimal number is read and its length reported"); 17 test_case("a decimal number is read and its length reported");
18 CHECK_INT(scan_udec("0", &value, 65535), 1); 18 CHECK_INT(scan_uint("0", &value, 65535), 1);
19 CHECK_INT(value, 0); 19 CHECK_INT(value, 0);
20 CHECK_INT(scan_udec("7", &value, 65535), 1); 20 CHECK_INT(scan_uint("7", &value, 65535), 1);
21 CHECK_INT(value, 7); 21 CHECK_INT(value, 7);
22 CHECK_INT(scan_udec("6969", &value, 65535), 4); 22 CHECK_INT(scan_uint("6969", &value, 65535), 4);
23 CHECK_INT(value, 6969); 23 CHECK_INT(value, 6969);
24 CHECK_INT(scan_udec("65535", &value, 65535), 5); 24 CHECK_INT(scan_uint("65535", &value, 65535), 5);
25 CHECK_INT(value, 65535); 25 CHECK_INT(value, 65535);
26} 26}
27 27
@@ -29,11 +29,11 @@ static void case_stops_at_non_digit(void) {
29 uint64_t value; 29 uint64_t value;
30 30
31 test_case("scanning stops at the first character that is not a digit"); 31 test_case("scanning stops at the first character that is not a digit");
32 CHECK_INT(scan_udec("6969:rest", &value, 65535), 4); 32 CHECK_INT(scan_uint("6969:rest", &value, 65535), 4);
33 CHECK_INT(value, 6969); 33 CHECK_INT(value, 6969);
34 CHECK_INT(scan_udec("128/8", &value, 65535), 3); 34 CHECK_INT(scan_uint("128/8", &value, 65535), 3);
35 CHECK_INT(value, 128); 35 CHECK_INT(value, 128);
36 CHECK_INT(scan_udec("42\n", &value, 65535), 2); 36 CHECK_INT(scan_uint("42\n", &value, 65535), 2);
37 CHECK_INT(value, 42); 37 CHECK_INT(value, 42);
38} 38}
39 39
@@ -41,9 +41,9 @@ static void case_rejects_whitespace(void) {
41 uint64_t value = 0xdead; 41 uint64_t value = 0xdead;
42 42
43 test_case("leading whitespace is not skipped"); 43 test_case("leading whitespace is not skipped");
44 CHECK_INT(scan_udec(" 1", &value, 65535), 0); 44 CHECK_INT(scan_uint(" 1", &value, 65535), 0);
45 CHECK_INT(scan_udec("\t1", &value, 65535), 0); 45 CHECK_INT(scan_uint("\t1", &value, 65535), 0);
46 CHECK_INT(scan_udec("\n1", &value, 65535), 0); 46 CHECK_INT(scan_uint("\n1", &value, 65535), 0);
47 CHECK_INT(value, 0xdead); 47 CHECK_INT(value, 0xdead);
48} 48}
49 49
@@ -51,9 +51,9 @@ static void case_rejects_sign(void) {
51 uint64_t value = 0xdead; 51 uint64_t value = 0xdead;
52 52
53 test_case("a sign is not accepted"); 53 test_case("a sign is not accepted");
54 CHECK_INT(scan_udec("+1", &value, 65535), 0); 54 CHECK_INT(scan_uint("+1", &value, 65535), 0);
55 CHECK_INT(scan_udec("-1", &value, 65535), 0); 55 CHECK_INT(scan_uint("-1", &value, 65535), 0);
56 CHECK_INT(scan_udec("-0", &value, 65535), 0); 56 CHECK_INT(scan_uint("-0", &value, 65535), 0);
57 CHECK_INT(value, 0xdead); 57 CHECK_INT(value, 0xdead);
58} 58}
59 59
@@ -61,9 +61,9 @@ static void case_rejects_empty_and_junk(void) {
61 uint64_t value = 0xdead; 61 uint64_t value = 0xdead;
62 62
63 test_case("a string with no leading digit yields nothing"); 63 test_case("a string with no leading digit yields nothing");
64 CHECK_INT(scan_udec("", &value, 65535), 0); 64 CHECK_INT(scan_uint("", &value, 65535), 0);
65 CHECK_INT(scan_udec("x", &value, 65535), 0); 65 CHECK_INT(scan_uint("x", &value, 65535), 0);
66 CHECK_INT(scan_udec(":80", &value, 65535), 0); 66 CHECK_INT(scan_uint(":80", &value, 65535), 0);
67 CHECK_INT(value, 0xdead); 67 CHECK_INT(value, 0xdead);
68} 68}
69 69
@@ -71,9 +71,9 @@ static void case_no_alternative_bases(void) {
71 uint64_t value; 71 uint64_t value;
72 72
73 test_case("hexadecimal and octal notations are read as plain decimal"); 73 test_case("hexadecimal and octal notations are read as plain decimal");
74 CHECK_INT(scan_udec("0x10", &value, 65535), 1); 74 CHECK_INT(scan_uint("0x10", &value, 65535), 1);
75 CHECK_INT(value, 0); 75 CHECK_INT(value, 0);
76 CHECK_INT(scan_udec("010", &value, 65535), 3); 76 CHECK_INT(scan_uint("010", &value, 65535), 3);
77 CHECK_INT(value, 10); 77 CHECK_INT(value, 10);
78} 78}
79 79
@@ -81,13 +81,13 @@ static void case_refuses_out_of_range(void) {
81 uint64_t value = 0xdead; 81 uint64_t value = 0xdead;
82 82
83 test_case("a value above max is refused, not truncated"); 83 test_case("a value above max is refused, not truncated");
84 CHECK_INT(scan_udec("65536", &value, 65535), 0); 84 CHECK_INT(scan_uint("65536", &value, 65535), 0);
85 CHECK_INT(scan_udec("99999", &value, 65535), 0); 85 CHECK_INT(scan_uint("99999", &value, 65535), 0);
86 CHECK_INT(scan_udec("129", &value, 128), 0); 86 CHECK_INT(scan_uint("129", &value, 128), 0);
87 CHECK_INT(value, 0xdead); 87 CHECK_INT(value, 0xdead);
88 88
89 test_case("the boundary itself is accepted"); 89 test_case("the boundary itself is accepted");
90 CHECK_INT(scan_udec("128", &value, 128), 3); 90 CHECK_INT(scan_uint("128", &value, 128), 3);
91 CHECK_INT(value, 128); 91 CHECK_INT(value, 128);
92} 92}
93 93
@@ -95,21 +95,21 @@ static void case_sixty_four_bit_range(void) {
95 uint64_t value; 95 uint64_t value;
96 96
97 test_case("the full unsigned 64 bit range is available"); 97 test_case("the full unsigned 64 bit range is available");
98 CHECK_INT(scan_udec("18446744073709551615", &value, UINT64_MAX), 20); 98 CHECK_INT(scan_uint("18446744073709551615", &value, UINT64_MAX), 20);
99 CHECK(value == UINT64_MAX); 99 CHECK(value == UINT64_MAX);
100 100
101 test_case("a number too large for 64 bits is refused"); 101 test_case("a number too large for 64 bits is refused");
102 CHECK_INT(scan_udec("18446744073709551616", &value, UINT64_MAX), 0); 102 CHECK_INT(scan_uint("18446744073709551616", &value, UINT64_MAX), 0);
103 CHECK_INT(scan_udec("99999999999999999999999999", &value, UINT64_MAX), 0); 103 CHECK_INT(scan_uint("99999999999999999999999999", &value, UINT64_MAX), 0);
104} 104}
105 105
106static void case_leading_zeros_allowed(void) { 106static void case_leading_zeros_allowed(void) {
107 uint64_t value; 107 uint64_t value;
108 108
109 test_case("leading zeros are accepted outside bencode"); 109 test_case("leading zeros are accepted outside bencode");
110 CHECK_INT(scan_udec("007", &value, 65535), 3); 110 CHECK_INT(scan_uint("007", &value, 65535), 3);
111 CHECK_INT(value, 7); 111 CHECK_INT(value, 7);
112 CHECK_INT(scan_udec("0000", &value, 65535), 4); 112 CHECK_INT(scan_uint("0000", &value, 65535), 4);
113 CHECK_INT(value, 0); 113 CHECK_INT(value, 0);
114} 114}
115 115
@@ -117,9 +117,9 @@ static void case_bencode_rejects_leading_zeros(void) {
117 uint64_t value = 0xdead; 117 uint64_t value = 0xdead;
118 118
119 test_case("bencode refuses a leading zero on anything but zero"); 119 test_case("bencode refuses a leading zero on anything but zero");
120 CHECK_INT(scan_udec_bencode("007", &value, 65535), 0); 120 CHECK_INT(scan_uint_bencode("007", &value, 65535), 0);
121 CHECK_INT(scan_udec_bencode("0000", &value, 65535), 0); 121 CHECK_INT(scan_uint_bencode("0000", &value, 65535), 0);
122 CHECK_INT(scan_udec_bencode("01", &value, 65535), 0); 122 CHECK_INT(scan_uint_bencode("01", &value, 65535), 0);
123 CHECK_INT(value, 0xdead); 123 CHECK_INT(value, 0xdead);
124} 124}
125 125
@@ -127,11 +127,11 @@ static void case_bencode_accepts_canonical(void) {
127 uint64_t value; 127 uint64_t value;
128 128
129 test_case("bencode accepts a canonically written number"); 129 test_case("bencode accepts a canonically written number");
130 CHECK_INT(scan_udec_bencode("0", &value, 65535), 1); 130 CHECK_INT(scan_uint_bencode("0", &value, 65535), 1);
131 CHECK_INT(value, 0); 131 CHECK_INT(value, 0);
132 CHECK_INT(scan_udec_bencode("0:", &value, 65535), 1); 132 CHECK_INT(scan_uint_bencode("0:", &value, 65535), 1);
133 CHECK_INT(value, 0); 133 CHECK_INT(value, 0);
134 CHECK_INT(scan_udec_bencode("160:", &value, 65535), 3); 134 CHECK_INT(scan_uint_bencode("160:", &value, 65535), 3);
135 CHECK_INT(value, 160); 135 CHECK_INT(value, 160);
136} 136}
137 137
@@ -139,14 +139,64 @@ static void case_bencode_inherits_the_rest(void) {
139 uint64_t value = 0xdead; 139 uint64_t value = 0xdead;
140 140
141 test_case("bencode is no laxer than the plain scanner"); 141 test_case("bencode is no laxer than the plain scanner");
142 CHECK_INT(scan_udec_bencode(" 1", &value, 65535), 0); 142 CHECK_INT(scan_uint_bencode(" 1", &value, 65535), 0);
143 CHECK_INT(scan_udec_bencode("-1", &value, 65535), 0); 143 CHECK_INT(scan_uint_bencode("-1", &value, 65535), 0);
144 CHECK_INT(scan_udec_bencode("+1", &value, 65535), 0); 144 CHECK_INT(scan_uint_bencode("+1", &value, 65535), 0);
145 CHECK_INT(scan_udec_bencode("65536", &value, 65535), 0); 145 CHECK_INT(scan_uint_bencode("65536", &value, 65535), 0);
146 CHECK_INT(value, 0xdead); 146 CHECK_INT(value, 0xdead);
147} 147}
148 148
149/* ------------------------------------------- fixed width signed integers */
150
151/* A value lifted out of a query string arrives as a pointer and a length
152 with nothing terminating it, and the whole of it has to be the number. */
153
154static void case_int_whole_field(void) {
155 int64_t value = 0;
156
157 test_case("the whole field is read, sign and all");
158 CHECK_INT(scan_int("123", 3, &value), 0);
159 CHECK_INT(value, 123);
160 CHECK_INT(scan_int("-45", 3, &value), 0);
161 CHECK_INT(value, -45);
162
163 test_case("and only as far as the length given");
164 CHECK_INT(scan_int("12345", 3, &value), 0);
165 CHECK_INT(value, 123);
166}
167
168static void case_int_rejects_the_rest(void) {
169 int64_t value = 4711;
170
171 test_case("anything that is not all digits is refused");
172 CHECK_INT(scan_int("12a", 3, &value), -1);
173 CHECK_INT(scan_int("", 0, &value), -1);
174 CHECK_INT(scan_int("-", 1, &value), -1);
175 CHECK_INT(scan_int(" 12", 3, &value), -1);
176 CHECK_INT(scan_int("+12", 3, &value), -1);
177
178 test_case("and a refused field leaves the number alone");
179 CHECK_INT(value, 4711);
180}
181
182static void case_int_overflow(void) {
183 int64_t value = 0;
184
185 test_case("a field too long to fit is refused rather than wrapped");
186 CHECK_INT(scan_int("9223372036854775807", 19, &value), 0);
187 CHECK_INT(value, INT64_MAX);
188 CHECK_INT(scan_int("9223372036854775808", 19, &value), -1);
189 CHECK_INT(scan_int("99999999999999999999", 20, &value), -1);
190
191 test_case("including the negative side");
192 CHECK_INT(scan_int("-9223372036854775807", 20, &value), 0);
193 CHECK_INT(value, -INT64_MAX);
194}
195
149void test_scan(void) { 196void test_scan(void) {
197 case_int_whole_field();
198 case_int_rejects_the_rest();
199 case_int_overflow();
150 case_plain_numbers(); 200 case_plain_numbers();
151 case_stops_at_non_digit(); 201 case_stops_at_non_digit();
152 case_rejects_whitespace(); 202 case_rejects_whitespace();
diff --git a/tests/t_scan_urlencoded_query.c b/tests/t_urlencoded.c
index cb6c982..6e0ee31 100644
--- a/tests/t_scan_urlencoded_query.c
+++ b/tests/t_urlencoded.c
@@ -7,7 +7,7 @@
7#include <string.h> 7#include <string.h>
8 8
9/* Opentracker */ 9/* Opentracker */
10#include "scan_urlencoded_query.h" 10#include "ot_urlencoded.h"
11#include "test.h" 11#include "test.h"
12 12
13/* The scanner returns the number of bytes written to the destination, or 13/* The scanner returns the number of bytes written to the destination, or
@@ -23,7 +23,7 @@ static void case_path(void) {
23 ssize_t n; 23 ssize_t n;
24 24
25 test_case("path stops at the question mark"); 25 test_case("path stops at the question mark");
26 n = scan_urlencoded_query(&p, dest, SCAN_PATH); 26 n = urlencoded_parse(&p, dest, SCAN_PATH);
27 CHECK_INT(n, 9); 27 CHECK_INT(n, 9);
28 dest[n] = 0; 28 dest[n] = 0;
29 CHECK_STR(dest, "/announce"); 29 CHECK_STR(dest, "/announce");
@@ -37,22 +37,22 @@ static void case_param_and_value(void) {
37 ssize_t n; 37 ssize_t n;
38 38
39 test_case("parameter stops at '=', value stops at '&'"); 39 test_case("parameter stops at '=', value stops at '&'");
40 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); 40 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_PARAM);
41 CHECK_INT(n, 9); 41 CHECK_INT(n, 9);
42 dest[n] = 0; 42 dest[n] = 0;
43 CHECK_STR(dest, "info_hash"); 43 CHECK_STR(dest, "info_hash");
44 44
45 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 45 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
46 CHECK_INT(n, 3); 46 CHECK_INT(n, 3);
47 dest[n] = 0; 47 dest[n] = 0;
48 CHECK_STR(dest, "abc"); 48 CHECK_STR(dest, "abc");
49 49
50 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); 50 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_PARAM);
51 CHECK_INT(n, 4); 51 CHECK_INT(n, 4);
52 dest[n] = 0; 52 dest[n] = 0;
53 CHECK_STR(dest, "port"); 53 CHECK_STR(dest, "port");
54 54
55 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 55 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
56 CHECK_INT(n, 2); 56 CHECK_INT(n, 2);
57 dest[n] = 0; 57 dest[n] = 0;
58 CHECK_STR(dest, "80"); 58 CHECK_STR(dest, "80");
@@ -65,7 +65,7 @@ static void case_percent_decoding(void) {
65 ssize_t n; 65 ssize_t n;
66 66
67 test_case("percent escapes decode"); 67 test_case("percent escapes decode");
68 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 68 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
69 CHECK_INT(n, 3); 69 CHECK_INT(n, 3);
70 CHECK_MEM(dest, "ABC", 3); 70 CHECK_MEM(dest, "ABC", 3);
71} 71}
@@ -79,7 +79,7 @@ static void case_binary_info_hash(void) {
79 ssize_t n; 79 ssize_t n;
80 80
81 test_case("twenty byte binary value survives decoding"); 81 test_case("twenty byte binary value survives decoding");
82 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 82 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
83 CHECK_INT(n, 20); 83 CHECK_INT(n, 20);
84 CHECK_MEM(dest, want, 20); 84 CHECK_MEM(dest, want, 20);
85} 85}
@@ -90,7 +90,7 @@ static void case_in_place(void) {
90 ssize_t n; 90 ssize_t n;
91 91
92 test_case("decoding in place over the source is safe"); 92 test_case("decoding in place over the source is safe");
93 n = scan_urlencoded_query(&p, query, SCAN_SEARCHPATH_VALUE); 93 n = urlencoded_parse(&p, query, SCAN_SEARCHPATH_VALUE);
94 CHECK_INT(n, 2); 94 CHECK_INT(n, 2);
95 CHECK_MEM(query, "AB", 2); 95 CHECK_MEM(query, "AB", 2);
96} 96}
@@ -102,7 +102,7 @@ static void case_bad_escape(void) {
102 ssize_t n; 102 ssize_t n;
103 103
104 test_case("a non hexadecimal escape is a parse error"); 104 test_case("a non hexadecimal escape is a parse error");
105 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 105 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
106 CHECK_INT(n, -1); 106 CHECK_INT(n, -1);
107} 107}
108 108
@@ -113,7 +113,7 @@ static void case_truncated_escape(void) {
113 ssize_t n; 113 ssize_t n;
114 114
115 test_case("an escape cut short by the terminator is a parse error"); 115 test_case("an escape cut short by the terminator is a parse error");
116 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 116 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
117 CHECK_INT(n, -1); 117 CHECK_INT(n, -1);
118} 118}
119 119
@@ -125,11 +125,11 @@ static void case_terminator(void) {
125 ssize_t n; 125 ssize_t n;
126 126
127 test_case("a hard terminator before any output reports end of input"); 127 test_case("a hard terminator before any output reports end of input");
128 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); 128 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_PARAM);
129 CHECK_INT(n, -2); 129 CHECK_INT(n, -2);
130 130
131 p = space; 131 p = space;
132 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_PARAM); 132 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_PARAM);
133 CHECK_INT(n, -2); 133 CHECK_INT(n, -2);
134} 134}
135 135
@@ -140,10 +140,10 @@ static void case_terminator_after_output(void) {
140 ssize_t n; 140 ssize_t n;
141 141
142 test_case("a hard terminator after output is left for the next call"); 142 test_case("a hard terminator after output is left for the next call");
143 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 143 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
144 CHECK_INT(n, 3); 144 CHECK_INT(n, 3);
145 CHECK_INT(*p, 0); 145 CHECK_INT(*p, 0);
146 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 146 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
147 CHECK_INT(n, -2); 147 CHECK_INT(n, -2);
148} 148}
149 149
@@ -159,14 +159,14 @@ static void case_separators_are_contextual(void) {
159 ssize_t n; 159 ssize_t n;
160 160
161 test_case("a question mark is literal inside a value"); 161 test_case("a question mark is literal inside a value");
162 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 162 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
163 CHECK_INT(n, 3); 163 CHECK_INT(n, 3);
164 dest[n] = 0; 164 dest[n] = 0;
165 CHECK_STR(dest, "a?b"); 165 CHECK_STR(dest, "a?b");
166 166
167 test_case("an equals sign is literal inside a value"); 167 test_case("an equals sign is literal inside a value");
168 p = equals; 168 p = equals;
169 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 169 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
170 CHECK_INT(n, 3); 170 CHECK_INT(n, 3);
171 dest[n] = 0; 171 dest[n] = 0;
172 CHECK_STR(dest, "a=b"); 172 CHECK_STR(dest, "a=b");
@@ -179,7 +179,7 @@ static void case_ampersand_in_path(void) {
179 ssize_t n; 179 ssize_t n;
180 180
181 test_case("an ampersand in a path is a parse error"); 181 test_case("an ampersand in a path is a parse error");
182 n = scan_urlencoded_query(&p, dest, SCAN_PATH); 182 n = urlencoded_parse(&p, dest, SCAN_PATH);
183 CHECK_INT(n, -1); 183 CHECK_INT(n, -1);
184} 184}
185 185
@@ -190,7 +190,7 @@ static void case_raw_high_byte(void) {
190 ssize_t n; 190 ssize_t n;
191 191
192 test_case("an undecoded byte above ASCII is a parse error"); 192 test_case("an undecoded byte above ASCII is a parse error");
193 n = scan_urlencoded_query(&p, dest, SCAN_SEARCHPATH_VALUE); 193 n = urlencoded_parse(&p, dest, SCAN_SEARCHPATH_VALUE);
194 CHECK_INT(n, -1); 194 CHECK_INT(n, -1);
195} 195}
196 196
@@ -200,12 +200,12 @@ static void case_skipvalue(void) {
200 char *p = query; 200 char *p = query;
201 201
202 test_case("skipvalue steps over the value and its separator"); 202 test_case("skipvalue steps over the value and its separator");
203 scan_urlencoded_skipvalue(&p); 203 urlencoded_skip_value(&p);
204 CHECK_STR(p, "next=1"); 204 CHECK_STR(p, "next=1");
205 205
206 test_case("skipvalue leaves a hard terminator in place"); 206 test_case("skipvalue leaves a hard terminator in place");
207 p = bare; 207 p = bare;
208 scan_urlencoded_skipvalue(&p); 208 urlencoded_skip_value(&p);
209 CHECK_INT(*p, 0); 209 CHECK_INT(*p, 0);
210} 210}
211 211
@@ -217,44 +217,17 @@ static void case_find_keywords(void) {
217 char *p = match; 217 char *p = match;
218 218
219 test_case("a known keyword returns its value"); 219 test_case("a known keyword returns its value");
220 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), 2); 220 CHECK_INT(urlencoded_match_keyword(keywords, &p, SCAN_SEARCHPATH_PARAM), 2);
221 221
222 test_case("an unknown keyword returns -3"); 222 test_case("an unknown keyword returns -3");
223 p = nomatch; 223 p = nomatch;
224 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3); 224 CHECK_INT(urlencoded_match_keyword(keywords, &p, SCAN_SEARCHPATH_PARAM), -3);
225 225
226 test_case("a prefix of a keyword does not match it"); 226 test_case("a prefix of a keyword does not match it");
227 p = prefix; 227 p = prefix;
228 CHECK_INT(scan_find_keywords(keywords, &p, SCAN_SEARCHPATH_PARAM), -3); 228 CHECK_INT(urlencoded_match_keyword(keywords, &p, SCAN_SEARCHPATH_PARAM), -3);
229} 229}
230 230
231static void case_fixed_int(void) {
232 char positive[] = "123";
233 char negative[] = "-45";
234 char trailing[] = "12a";
235 int value;
236
237 test_case("scan_fixed_int returns the count of unparsed bytes");
238 CHECK_INT(scan_fixed_int(positive, 3, &value), 0);
239 CHECK_INT(value, 123);
240
241 CHECK_INT(scan_fixed_int(negative, 3, &value), 0);
242 CHECK_INT(value, -45);
243
244 CHECK_INT(scan_fixed_int(trailing, 3, &value), 1);
245 CHECK_INT(value, 12);
246}
247
248static void case_fixed_int_bounded(void) {
249 char digits[] = "12345";
250 int value;
251
252 test_case("scan_fixed_int honours the length it is given");
253 CHECK_INT(scan_fixed_int(digits, 3, &value), 0);
254 CHECK_INT(value, 123);
255}
256
257
258/* The encoding direction. Which bytes survive unescaped is fixed by what 231/* The encoding direction. Which bytes survive unescaped is fixed by what
259 existing parsers of the urlencoded full scrape expect, so these cases pin 232 existing parsers of the urlencoded full scrape expect, so these cases pin
260 the set rather than describe a preference. */ 233 the set rather than describe a preference. */
@@ -328,7 +301,7 @@ static void case_fmt_decoder_disagrees_on_dollar(void) {
328 encoder passes through unescaped, '$' is the only one the decoder then 301 encoder passes through unescaped, '$' is the only one the decoder then
329 refuses, and it refuses the whole string rather than stopping short. 302 refuses, and it refuses the whole string rather than stopping short.
330 Output of the urlencoded full scrape therefore does not round trip 303 Output of the urlencoded full scrape therefore does not round trip
331 through scan_urlencoded_query when a hash contains 0x24. Harmless today 304 through urlencoded_parse when a hash contains 0x24. Harmless today
332 because only third party tooling reads that output. */ 305 because only third party tooling reads that output. */
333 const uint8_t src[] = {'a', '$', 'b'}; 306 const uint8_t src[] = {'a', '$', 'b'};
334 char dest[16]; 307 char dest[16];
@@ -342,11 +315,11 @@ static void case_fmt_decoder_disagrees_on_dollar(void) {
342 dest[n] = 0; 315 dest[n] = 0;
343 CHECK_STR(dest, "a$b"); 316 CHECK_STR(dest, "a$b");
344 317
345 got = scan_urlencoded_query(&p, scanned, SCAN_SEARCHPATH_VALUE); 318 got = urlencoded_parse(&p, scanned, SCAN_SEARCHPATH_VALUE);
346 CHECK_INT(got, -1); 319 CHECK_INT(got, -1);
347} 320}
348 321
349void test_scan_urlencoded_query(void) { 322void test_urlencoded(void) {
350 case_path(); 323 case_path();
351 case_param_and_value(); 324 case_param_and_value();
352 case_percent_decoding(); 325 case_percent_decoding();
@@ -361,8 +334,6 @@ void test_scan_urlencoded_query(void) {
361 case_raw_high_byte(); 334 case_raw_high_byte();
362 case_skipvalue(); 335 case_skipvalue();
363 case_find_keywords(); 336 case_find_keywords();
364 case_fixed_int();
365 case_fixed_int_bounded();
366 case_fmt_passes_safe_bytes(); 337 case_fmt_passes_safe_bytes();
367 case_fmt_escapes_the_rest(); 338 case_fmt_escapes_the_rest();
368 case_fmt_hex_is_lower_case(); 339 case_fmt_hex_is_lower_case();
diff --git a/tests/t_vector.c b/tests/t_vector.c
index 541a6da..6e048e1 100644
--- a/tests/t_vector.c
+++ b/tests/t_vector.c
@@ -112,6 +112,51 @@ static void case_insert_keeps_order(void) {
112 free(vector.data); 112 free(vector.data);
113} 113}
114 114
115/* The peer variant is reached with an empty vector on every first announce
116 for a torrent, and again whenever a swarm empties and refills, because
117 vector_fixup_peers drops the allocation and nulls the data pointer. */
118
119static void case_insert_peer_into_empty_vector(void) {
120 ot_vector vector = {NULL, 0, 0};
121 ot_peer6 low, high;
122 int exactmatch;
123 ot_peer *slot;
124
125 memset(low, 0x11, sizeof(low));
126 memset(high, 0x99, sizeof(high));
127
128 test_case("the first peer goes into a vector that has no allocation yet");
129 slot = vector_find_or_insert_peer(&vector, low, OT_PEER_SIZE6, &exactmatch);
130 CHECK(slot != NULL);
131 CHECK_INT(exactmatch, 0);
132 memcpy(slot, low, OT_PEER_SIZE6);
133 CHECK_INT(vector.size, 1);
134 CHECK_INT(vector.space, OT_VECTOR_MIN_MEMBERS);
135 CHECK_PTR(slot, vector.data);
136
137 test_case("a second peer keeps the order");
138 slot = vector_find_or_insert_peer(&vector, high, OT_PEER_SIZE6, &exactmatch);
139 CHECK(slot != NULL);
140 CHECK_INT(exactmatch, 0);
141 memcpy(slot, high, OT_PEER_SIZE6);
142 CHECK_INT(vector.size, 2);
143 CHECK_MEM((char *)vector.data, low, OT_PEER_COMPARE_SIZE6);
144 CHECK_MEM((char *)vector.data + OT_PEER_SIZE6, high, OT_PEER_COMPARE_SIZE6);
145
146 test_case("and emptying the vector hands the allocation back");
147 CHECK(vector_remove_peer(&vector, low, OT_PEER_SIZE6) != 0);
148 CHECK(vector_remove_peer(&vector, high, OT_PEER_SIZE6) != 0);
149 CHECK_INT(vector.size, 0);
150 CHECK_PTR(vector.data, NULL);
151
152 test_case("so the next peer starts from no allocation again");
153 slot = vector_find_or_insert_peer(&vector, low, OT_PEER_SIZE6, &exactmatch);
154 CHECK(slot != NULL);
155 CHECK_INT(vector.size, 1);
156
157 free(vector.data);
158}
159
115static void case_insert_finds_existing(void) { 160static void case_insert_finds_existing(void) {
116 ot_vector vector = {NULL, 0, 0}; 161 ot_vector vector = {NULL, 0, 0};
117 const unsigned char five[4] = {0, 0, 0, 5}; 162 const unsigned char five[4] = {0, 0, 0, 5};
@@ -257,6 +302,7 @@ void test_vector(void) {
257 case_search_past_the_end(); 302 case_search_past_the_end();
258 case_search_empty(); 303 case_search_empty();
259 case_insert_keeps_order(); 304 case_insert_keeps_order();
305 case_insert_peer_into_empty_vector();
260 case_insert_finds_existing(); 306 case_insert_finds_existing();
261 case_insert_compare_prefix(); 307 case_insert_compare_prefix();
262 case_redistribute_holds_every_peer(); 308 case_redistribute_holds_every_peer();
diff --git a/tests/test.h b/tests/test.h
index bc8c3bd..0eddc82 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -76,7 +76,7 @@ extern unsigned long test_failures;
76 } while (0) 76 } while (0)
77 77
78/* One entry point per module under test */ 78/* One entry point per module under test */
79void test_scan_urlencoded_query(void); 79void test_urlencoded(void);
80void test_iovec(void); 80void test_iovec(void);
81void test_vector(void); 81void test_vector(void);
82void test_random(void); 82void test_random(void);
@@ -89,5 +89,7 @@ void test_socket(void);
89void test_mutex(void); 89void test_mutex(void);
90void test_batch(void); 90void test_batch(void);
91void test_fdstate(void); 91void test_fdstate(void);
92void test_event(void);
93void test_fmt(void);
92 94
93#endif 95#endif
diff --git a/trackerlogic.c b/trackerlogic.c
index 9705bd6..1f38f07 100644
--- a/trackerlogic.c
+++ b/trackerlogic.c
@@ -14,6 +14,7 @@
14/* Opentracker */ 14/* Opentracker */
15#include "ot_accesslist.h" 15#include "ot_accesslist.h"
16#include "ot_clean.h" 16#include "ot_clean.h"
17#include "ot_fmt.h"
17#include "ot_fullscrape.h" 18#include "ot_fullscrape.h"
18#include "ot_http.h" 19#include "ot_http.h"
19#include "ot_ip.h" 20#include "ot_ip.h"
@@ -299,11 +300,12 @@ static size_t return_peers_for_torrent_udp(struct ot_workstruct *ws, ot_torrent
299} 300}
300 301
301static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) { 302static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent *torrent, size_t amount, char *reply) {
302 char *r = reply; 303 char *r = reply;
303 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; 304 const char *r_end = ws->outbuf + G_OUTBUF_SIZE;
304 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count; 305 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
305 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count; 306 size_t seed_count = torrent->peer_list6->seed_count + torrent->peer_list4->seed_count;
306 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count; 307 size_t down_count = torrent->peer_list6->down_count + torrent->peer_list4->down_count;
308 size_t peer_count = torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - seed_count;
307 309
308 /* Simple case: amount of peers in both lists is less than requested, here we return all results */ 310 /* Simple case: amount of peers in both lists is less than requested, here we return all results */
309 size_t amount_v4 = torrent->peer_list4->peer_count; 311 size_t amount_v4 = torrent->peer_list4->peer_count;
@@ -341,11 +343,11 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
341 ++amount_v4; 343 ++amount_v4;
342 } 344 }
343 345
344 r += 346 fmt_append(&r, r_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval,
345 sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zde8:intervali%ie12:min intervali%ie", seed_count, down_count, peer_count, erval, erval / 2); 347 erval / 2);
346 348
347 if (amount_v4) { 349 if (amount_v4) {
348 r += sprintf(r, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4); 350 fmt_append(&r, r_end, PEERS_BENCODED4 "%zd:", OT_PEER_COMPARE_SIZE4 * amount_v4);
349 if (amount_v4 == torrent->peer_list4->peer_count) 351 if (amount_v4 == torrent->peer_list4->peer_count)
350 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r); 352 r += return_peers_all(torrent->peer_list4, OT_PEER_SIZE4, r);
351 else 353 else
@@ -353,14 +355,14 @@ static size_t return_peers_for_torrent_tcp(struct ot_workstruct *ws, ot_torrent
353 } 355 }
354 356
355 if (amount_v6) { 357 if (amount_v6) {
356 r += sprintf(r, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6); 358 fmt_append(&r, r_end, PEERS_BENCODED6 "%zd:", OT_PEER_COMPARE_SIZE6 * amount_v6);
357 if (amount_v6 == torrent->peer_list6->peer_count) 359 if (amount_v6 == torrent->peer_list6->peer_count)
358 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r); 360 r += return_peers_all(torrent->peer_list6, OT_PEER_SIZE6, r);
359 else 361 else
360 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r); 362 r += return_peers_selection(ws, torrent->peer_list6, OT_PEER_SIZE6, amount_v6, r);
361 } 363 }
362 364
363 *r++ = 'e'; 365 fmt_append(&r, r_end, "e");
364 366
365 return r - reply; 367 return r - reply;
366} 368}
@@ -403,12 +405,11 @@ size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply) {
403} 405}
404 406
405/* Fetches scrape info for a specific torrent */ 407/* Fetches scrape info for a specific torrent */
406size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply) { 408void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end) {
407 char *r = reply; 409 int exactmatch;
408 int exactmatch;
409 size_t i; 410 size_t i;
410 411
411 r += sprintf(r, "d5:filesd"); 412 fmt_append(reply, reply_end, "d5:filesd");
412 413
413 for (i = 0; i < amount; ++i) { 414 for (i = 0; i < amount; ++i) {
414 int delta_torrentcount = 0; 415 int delta_torrentcount = 0;
@@ -421,22 +422,17 @@ size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, ch
421 vector_remove_torrent(torrents_list, torrent); 422 vector_remove_torrent(torrents_list, torrent);
422 delta_torrentcount = -1; 423 delta_torrentcount = -1;
423 } else { 424 } else {
424 *r++ = '2'; 425 fmt_append(reply, reply_end, "20:");
425 *r++ = '0'; 426 fmt_bytes(reply, reply_end, hash, sizeof(ot_hash));
426 *r++ = ':'; 427 fmt_append(reply, reply_end, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee",
427 memcpy(r, hash, sizeof(ot_hash)); 428 torrent->peer_list6->seed_count + torrent->peer_list4->seed_count, torrent->peer_list6->down_count + torrent->peer_list4->down_count,
428 r += sizeof(ot_hash); 429 torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count);
429 r += sprintf(r, "d8:completei%zde10:downloadedi%zde10:incompletei%zdee", torrent->peer_list6->seed_count + torrent->peer_list4->seed_count,
430 torrent->peer_list6->down_count + torrent->peer_list4->down_count,
431 torrent->peer_list6->peer_count + torrent->peer_list4->peer_count - torrent->peer_list6->seed_count - torrent->peer_list4->seed_count);
432 } 430 }
433 } 431 }
434 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount); 432 mutex_bucket_unlock_by_hash(*hash, delta_torrentcount);
435 } 433 }
436 434
437 *r++ = 'e'; 435 fmt_append(reply, reply_end, "ee");
438 *r++ = 'e';
439 return r - reply;
440} 436}
441 437
442static ot_peerlist dummy_list; 438static ot_peerlist dummy_list;
@@ -472,9 +468,12 @@ size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws) {
472 } 468 }
473 469
474 if (proto == FLAG_TCP) { 470 if (proto == FLAG_TCP) {
475 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM; 471 int erval = OT_CLIENT_REQUEST_INTERVAL_RANDOM;
476 ws->reply_size = sprintf(ws->reply, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count, erval, 472 char *r = ws->reply;
477 erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4); 473
474 fmt_append(&r, ws->outbuf + G_OUTBUF_SIZE, "d8:completei%zde10:incompletei%zde8:intervali%ie12:min intervali%ie%s0:e", seed_count, peer_count - seed_count,
475 erval, erval / 2, peer_size == OT_PEER_SIZE6 ? PEERS_BENCODED6 : PEERS_BENCODED4);
476 ws->reply_size = r - ws->reply;
478 } 477 }
479 478
480 /* Handle UDP reply */ 479 /* Handle UDP reply */
diff --git a/trackerlogic.h b/trackerlogic.h
index 42018c3..63abf78 100644
--- a/trackerlogic.h
+++ b/trackerlogic.h
@@ -187,7 +187,7 @@ void exerr(const char *message);
187 otherwise it is released in return_peers_for_torrent */ 187 otherwise it is released in return_peers_for_torrent */
188size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount); 188size_t add_peer_to_torrent_and_return_peers(PROTO_FLAG proto, struct ot_workstruct *ws, size_t amount);
189size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws); 189size_t remove_peer_from_torrent(PROTO_FLAG proto, struct ot_workstruct *ws);
190size_t return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char *reply); 190void return_tcp_scrape_for_torrent(ot_hash const *hash_list, size_t amount, char **reply, const char *reply_end);
191size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply); 191size_t return_udp_scrape_for_torrent(ot_hash const hash, char *reply);
192void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count); 192void add_torrent_from_saved_state(ot_hash const hash, ot_time base, size_t down_count);
193#ifdef _DEBUG_RANDOMTORRENTS 193#ifdef _DEBUG_RANDOMTORRENTS