summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/runner.c2
-rw-r--r--tests/t_buf.c205
-rw-r--r--tests/t_ip.c217
-rw-r--r--tests/test.h2
4 files changed, 426 insertions, 0 deletions
diff --git a/tests/runner.c b/tests/runner.c
index 370b92b..10b6625 100644
--- a/tests/runner.c
+++ b/tests/runner.c
@@ -20,6 +20,8 @@ static const struct module modules[] = {{"scan_urlencoded_query", test_scan_urle
20 {"ot_random", test_random}, 20 {"ot_random", test_random},
21 {"ot_hex", test_hex}, 21 {"ot_hex", test_hex},
22 {"ot_scan", test_scan}, 22 {"ot_scan", test_scan},
23 {"ot_buf", test_buf},
24 {"ot_ip", test_ip},
23 {"ot_accesslist", test_accesslist}, 25 {"ot_accesslist", test_accesslist},
24 {NULL, NULL}}; 26 {NULL, NULL}};
25 27
diff --git a/tests/t_buf.c b/tests/t_buf.c
new file mode 100644
index 0000000..dcfb0bb
--- /dev/null
+++ b/tests/t_buf.c
@@ -0,0 +1,205 @@
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 <stdint.h>
8#include <string.h>
9
10/* Opentracker */
11#include "ot_buf.h"
12#include "test.h"
13
14static void case_starts_empty(void) {
15 ot_buf buf = {NULL, 0};
16
17 test_case("a fresh buffer holds nothing and has no data pointer");
18 CHECK_PTR(buf.data, NULL);
19 CHECK_INT(buf.size, 0);
20}
21
22static void case_cat_appends(void) {
23 ot_buf buf = {NULL, 0};
24
25 test_case("appends concatenate in order and report success");
26 CHECK_INT(buf_cat(&buf, "GET /ann", 8), 0);
27 CHECK(buf.data != NULL);
28 CHECK_INT(buf.size, 8);
29 CHECK_INT(buf_cat(&buf, "ounce", 5), 0);
30 CHECK_INT(buf.size, 13);
31 CHECK_MEM(buf.data, "GET /announce", 13);
32 buf_reset(&buf);
33}
34
35static void case_cat_of_nothing_allocates_nothing(void) {
36 ot_buf buf = {NULL, 0};
37
38 /* handle_read tells an untouched buffer from a partial request by the data
39 pointer, so a zero length append must not quietly allocate one */
40 test_case("appending zero bytes leaves the buffer untouched");
41 CHECK_INT(buf_cat(&buf, "ignored", 0), 0);
42 CHECK_PTR(buf.data, NULL);
43 CHECK_INT(buf.size, 0);
44 buf_reset(&buf);
45}
46
47static void case_grows_across_many_appends(void) {
48 ot_buf buf = {NULL, 0};
49 char chunk[100];
50 size_t i;
51
52 test_case("content survives growing one packet at a time");
53 memset(chunk, 'x', sizeof(chunk));
54 for (i = 0; i < 100; ++i)
55 if (buf_cat(&buf, chunk, sizeof(chunk))) {
56 test_failf(__FILE__, __LINE__, "append %zu was refused", i);
57 buf_reset(&buf);
58 return;
59 }
60 ++test_checks;
61 CHECK_INT(buf.size, 10000);
62 for (i = 0; i < 10000; ++i)
63 if (buf.data[i] != 'x') {
64 test_failf(__FILE__, __LINE__, "byte %zu was not preserved across growth", i);
65 buf_reset(&buf);
66 return;
67 }
68 ++test_checks;
69 buf_reset(&buf);
70}
71
72static void case_grows_one_byte_at_a_time(void) {
73 ot_buf buf = {NULL, 0};
74 size_t i;
75
76 /* The shape a trickling client produces. Nothing here is optimised for it,
77 so the point is only that it stays correct. */
78 test_case("a byte at a time accumulates correctly");
79 for (i = 0; i < 2048; ++i) {
80 char c = (char)('a' + (i % 26));
81 if (buf_cat(&buf, &c, 1)) {
82 test_failf(__FILE__, __LINE__, "append %zu was refused", i);
83 buf_reset(&buf);
84 return;
85 }
86 }
87 ++test_checks;
88 CHECK_INT(buf.size, 2048);
89 for (i = 0; i < 2048; ++i)
90 if (buf.data[i] != (char)('a' + (i % 26))) {
91 test_failf(__FILE__, __LINE__, "byte %zu differs", i);
92 buf_reset(&buf);
93 return;
94 }
95 ++test_checks;
96 buf_reset(&buf);
97}
98
99static void case_truncate_shrinks_only(void) {
100 ot_buf buf = {NULL, 0};
101
102 test_case("truncate drops the tail but never grows");
103 buf_cat(&buf, "0123456789", 10);
104 buf_truncate(&buf, 4);
105 CHECK_INT(buf.size, 4);
106 CHECK_MEM(buf.data, "0123", 4);
107
108 buf_truncate(&buf, 9);
109 CHECK_INT(buf.size, 4);
110 buf_truncate(&buf, 4);
111 CHECK_INT(buf.size, 4);
112
113 buf_truncate(&buf, 0);
114 CHECK_INT(buf.size, 0);
115 buf_reset(&buf);
116}
117
118static void case_append_after_truncate(void) {
119 ot_buf buf = {NULL, 0};
120
121 test_case("appending after a truncate overwrites the dropped tail");
122 buf_cat(&buf, "keepdrop", 8);
123 buf_truncate(&buf, 4);
124 CHECK_INT(buf_cat(&buf, "MORE", 4), 0);
125 CHECK_INT(buf.size, 8);
126 CHECK_MEM(buf.data, "keepMORE", 8);
127 buf_reset(&buf);
128}
129
130static void case_reset_clears_everything(void) {
131 ot_buf buf = {NULL, 0};
132
133 test_case("reset releases the buffer and restores the initial state");
134 buf_cat(&buf, "something", 9);
135 buf_reset(&buf);
136 CHECK_PTR(buf.data, NULL);
137 CHECK_INT(buf.size, 0);
138}
139
140static void case_reset_of_empty_is_safe(void) {
141 ot_buf buf = {NULL, 0};
142
143 test_case("resetting a buffer that was never used is harmless");
144 buf_reset(&buf);
145 buf_reset(&buf);
146 CHECK_PTR(buf.data, NULL);
147 CHECK_INT(buf.size, 0);
148}
149
150static void case_refused_append_is_harmless(void) {
151 ot_buf buf = {NULL, 0};
152
153 /* A length that would wrap the size is rejected before src is read and
154 before anything is allocated, so a short string is safe to pass here.
155 A refused append must not destroy the partial request already collected:
156 the caller answers and closes rather than losing both. */
157 test_case("a length that would wrap the size is refused");
158 CHECK_INT(buf_cat(&buf, "already here", 12), 0);
159 CHECK_INT(buf_cat(&buf, "x", (size_t)-1), -1);
160
161 test_case("and earlier content survives it");
162 CHECK_INT(buf.size, 12);
163 CHECK_MEM(buf.data, "already here", 12);
164
165 test_case("and the buffer still works afterwards");
166 CHECK_INT(buf_cat(&buf, "!", 1), 0);
167 CHECK_INT(buf.size, 13);
168 CHECK_MEM(buf.data, "already here!", 13);
169 buf_reset(&buf);
170}
171
172static void case_accumulates_a_split_request(void) {
173 /* What handle_read actually does: collect a request that arrived in
174 pieces, then hand over the whole of it */
175 ot_buf buf = {NULL, 0};
176 const char *pieces[] = {"GET /announce?info_hash=", "0123456789abcdefghij", " HTTP/1.0\r\n\r\n"};
177 size_t i;
178
179 test_case("a request split over several reads is reassembled");
180 for (i = 0; i < 3; ++i)
181 if (buf_cat(&buf, pieces[i], strlen(pieces[i]))) {
182 test_failf(__FILE__, __LINE__, "piece %zu was refused", i);
183 buf_reset(&buf);
184 return;
185 }
186 ++test_checks;
187
188 CHECK_INT(buf.size, 24 + 20 + 13);
189 CHECK_MEM(buf.data, "GET /announce?info_hash=0123456789abcdefghij HTTP/1.0\r\n\r\n", 57);
190 buf_reset(&buf);
191}
192
193void test_buf(void) {
194 case_starts_empty();
195 case_cat_appends();
196 case_cat_of_nothing_allocates_nothing();
197 case_grows_across_many_appends();
198 case_grows_one_byte_at_a_time();
199 case_truncate_shrinks_only();
200 case_append_after_truncate();
201 case_reset_clears_everything();
202 case_reset_of_empty_is_safe();
203 case_refused_append_is_harmless();
204 case_accumulates_a_split_request();
205}
diff --git a/tests/t_ip.c b/tests/t_ip.c
new file mode 100644
index 0000000..2fe3a72
--- /dev/null
+++ b/tests/t_ip.c
@@ -0,0 +1,217 @@
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 <stdint.h>
8#include <string.h>
9
10/* Opentracker */
11#include "ot_ip.h"
12#include "test.h"
13
14static char text[64];
15
16static const char *formatted(const char *ip) {
17 text[ip_format(text, ip)] = 0;
18 return text;
19}
20
21/* ------------------------------------------------------------------ IPv4 */
22
23/* ------------------------------------------------------- RFC 5952 output */
24
25static void case_format_rfc5952(void) {
26 test_case("4.1 leading zeros in a group are suppressed");
27 CHECK_STR(formatted("\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), "2001:db8::1");
28
29 test_case("4.2.1 the longest run of zero groups becomes ::");
30 CHECK_STR(formatted("\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x01"), "2001:db8::2:1");
31
32 test_case("4.2.2 a single zero group is never shortened");
33 CHECK_STR(formatted("\x20\x01\x0d\xb8\x00\x00\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01"), "2001:db8:0:1:1:1:1:1");
34
35 test_case("4.2.1 a longer run wins over an earlier shorter one");
36 CHECK_STR(formatted("\x20\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x01"), "2001:0:0:1::1");
37
38 test_case("4.2.3 of two runs of equal length the first is shortened");
39 CHECK_STR(formatted("\x20\x01\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01\x00\x01"), "2001::1:0:0:1:1");
40
41 test_case("4.3 hexadecimal digits are lower case");
42 CHECK_STR(formatted("\xab\xcd\xef\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), "abcd:ef01::1");
43
44 test_case("the unspecified and loopback addresses");
45 CHECK_STR(formatted("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"), "::");
46 CHECK_STR(formatted("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"), "::1");
47
48 test_case("a full address with no zero group");
49 CHECK_STR(formatted("\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88"), "1111:2222:3333:4444:5555:6666:7777:8888");
50}
51
52static void case_format_trailing_zero_group(void) {
53 /* libowfat's formatter looked two bytes past the address to decide this
54 one, so a peer address ending in a zero group came out as "::" whenever
55 the bytes that happened to follow were not themselves zero. In
56 ot_stats.c those bytes are the peer's port. */
57 const char ip[] = "\x20\x01\x0d\xb8\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x00";
58
59 test_case("a single trailing zero group is written out, not shortened");
60 CHECK_STR(formatted(ip), "2001:db8:1:2:3:4:5:0");
61
62 test_case("and nothing beyond the sixteen bytes is consulted");
63 {
64 char padded[20];
65 memcpy(padded, ip, 16);
66 memset(padded + 16, 0xff, 4); /* a port and flags, as a peer record has */
67 CHECK_STR(formatted(padded), "2001:db8:1:2:3:4:5:0");
68 memset(padded + 16, 0x00, 4);
69 CHECK_STR(formatted(padded), "2001:db8:1:2:3:4:5:0");
70 }
71}
72
73static void case_v4mapped(void) {
74 const char mapped[] = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x0a\x00\x00\x01";
75 const char unmapped[] = "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01";
76
77 test_case("a v4 mapped address is recognised");
78 CHECK(ip_is_v4mapped(mapped));
79 CHECK_INT(ip_is_v4mapped(unmapped), 0);
80
81 /* The tracker has only ever shown the plain quad, so that is the one
82 form the formatter produces; "::ffff:10.0.0.1" is not written anywhere */
83 test_case("a mapped address is written as the plain dotted quad");
84 CHECK_STR(formatted(mapped), "10.0.0.1");
85 CHECK_STR(formatted("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00"), "0.0.0.0");
86 CHECK_STR(formatted("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff"), "255.255.255.255");
87
88 test_case("anything else follows RFC 5952");
89 CHECK_STR(formatted(unmapped), "2001:db8::1");
90}
91
92/* -------------------------------------------------------- RFC 4291 input */
93
94static void case_scan_forms(void) {
95 char ip[16];
96
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);
99 CHECK_MEM(ip, "\x11\x11\x22\x22\x33\x33\x44\x44\x55\x55\x66\x66\x77\x77\x88\x88", 16);
100
101 test_case("2.2 form 2, zero groups abbreviated");
102 CHECK_INT(ip_scan("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);
104 CHECK_INT(ip_scan("::1", ip), 3);
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);
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);
109 CHECK_MEM(ip, "\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", 16);
110
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);
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);
115 CHECK_MEM(ip, "\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x01\x02\x03\x04", 16);
116
117 test_case("upper case input is accepted");
118 CHECK_INT(ip_scan("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);
120}
121
122static void case_scan_accepts_bare_ipv4(void) {
123 char ip[16];
124
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 */
127 test_case("a bare IPv4 address is stored v4 mapped");
128 CHECK_INT(ip_scan("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);
130 CHECK(ip_is_v4mapped(ip));
131}
132
133static void case_scan_reports_length(void) {
134 char ip[16];
135
136 /* The config parser continues from where the address ended, which is how
137 "address:port" and "address/bits" are read */
138 test_case("scanning stops at the character after the address");
139 CHECK_INT(ip_scan("10.0.0.1:6969", ip), 8);
140 CHECK_INT(ip_scan("2001:db8::1/64", ip), 11);
141 CHECK_INT(ip_scan("::1]", ip), 3);
142 CHECK_INT(ip_scan("1:2:3:4:5:6:7:8 trailing", ip), 15);
143}
144
145static void case_scan_rejects(void) {
146 char ip[16];
147
148 test_case("an empty or junk string is refused");
149 CHECK_INT(ip_scan("", ip), 0);
150 CHECK_INT(ip_scan("x", ip), 0);
151 CHECK_INT(ip_scan(":", ip), 0);
152 CHECK_INT(ip_scan(":1:2", ip), 0);
153
154 test_case("too few groups without :: are refused");
155 CHECK_INT(ip_scan("1:2:3:4:5:6:7", ip), 0);
156 CHECK_INT(ip_scan("2001:db8:1", ip), 0);
157
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
160 the text comes from a config file or an X-Forwarded-For header. */
161 test_case("a second :: is refused rather than truncated");
162 CHECK_INT(ip_scan("1::2::3", ip), 0);
163 CHECK_INT(ip_scan("::1::", ip), 0);
164 CHECK_INT(ip_scan("::::", ip), 0);
165
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);
168
169 test_case("a group of more than four digits is refused, not truncated");
170 CHECK_INT(ip_scan("12345::1", ip), 0);
171 CHECK_INT(ip_scan("fffff::1", ip), 0);
172}
173
174static void case_round_trip(void) {
175 /* Whatever the formatter writes, the scanner has to read back */
176 static const char *const samples[] = {"::",
177 "::1",
178 "1::",
179 "2001:db8::1",
180 "2001:db8:1:2:3:4:5:0",
181 "1111:2222:3333:4444:5555:6666:7777:8888",
182 "2001::1:0:0:1",
183 "abcd:ef01::1",
184 NULL};
185 char ip[16], again[16];
186 size_t i;
187
188 test_case("every formatted address scans back to the same bytes");
189 for (i = 0; samples[i]; ++i) {
190 size_t n = ip_scan(samples[i], ip);
191 if (n != strlen(samples[i])) {
192 test_failf(__FILE__, __LINE__, "\"%s\" consumed %zu of %zu", samples[i], n, strlen(samples[i]));
193 return;
194 }
195 text[ip_format(text, ip)] = 0;
196 if (strcmp(text, samples[i])) {
197 test_failf(__FILE__, __LINE__, "\"%s\" formatted back as \"%s\"", samples[i], text);
198 return;
199 }
200 if (!ip_scan(text, again) || memcmp(ip, again, 16)) {
201 test_failf(__FILE__, __LINE__, "\"%s\" did not survive a second pass", samples[i]);
202 return;
203 }
204 }
205 ++test_checks;
206}
207
208void test_ip(void) {
209 case_format_rfc5952();
210 case_format_trailing_zero_group();
211 case_v4mapped();
212 case_scan_forms();
213 case_scan_accepts_bare_ipv4();
214 case_scan_reports_length();
215 case_scan_rejects();
216 case_round_trip();
217}
diff --git a/tests/test.h b/tests/test.h
index f0f2360..7449b73 100644
--- a/tests/test.h
+++ b/tests/test.h
@@ -83,5 +83,7 @@ void test_random(void);
83void test_hex(void); 83void test_hex(void);
84void test_accesslist(void); 84void test_accesslist(void);
85void test_scan(void); 85void test_scan(void);
86void test_buf(void);
87void test_ip(void);
86 88
87#endif 89#endif