summaryrefslogtreecommitdiff
path: root/testing
diff options
context:
space:
mode:
authoriphydf <iphydf@users.noreply.github.com>2016-10-01 00:26:52 +0100
committeriphydf <iphydf@users.noreply.github.com>2016-10-01 02:13:34 +0100
commitc037100747a1a224160cb12defb600ddfe1ba927 (patch)
tree8332a7d288116b802866e1ed98d1b950425206f7 /testing
parent1977d56caaff40ea9bbf6754b69bec9539a5a969 (diff)
Import the hstox SUT interface from hstox.
We'll maintain it in the c-toxcore repo, where it belongs.
Diffstat (limited to 'testing')
-rw-r--r--testing/hstox/.gitignore8
-rw-r--r--testing/hstox/Makefile97
-rw-r--r--testing/hstox/binary_decode.c224
-rw-r--r--testing/hstox/binary_encode.c252
-rw-r--r--testing/hstox/byteswap.h6
-rw-r--r--testing/hstox/driver.c244
-rw-r--r--testing/hstox/driver.h20
-rw-r--r--testing/hstox/errors.h11
-rw-r--r--testing/hstox/fuzz_main.c23
-rw-r--r--testing/hstox/methods.c147
-rw-r--r--testing/hstox/methods.h47
-rw-r--r--testing/hstox/packet_kinds.c48
-rw-r--r--testing/hstox/packet_kinds.h5
-rw-r--r--testing/hstox/test-inputs/.gitignore1
-rw-r--r--testing/hstox/test_main.c72
-rw-r--r--testing/hstox/toxcore-sut.hs31
-rw-r--r--testing/hstox/util.c74
-rw-r--r--testing/hstox/util.h27
18 files changed, 1337 insertions, 0 deletions
diff --git a/testing/hstox/.gitignore b/testing/hstox/.gitignore
new file mode 100644
index 00000000..6fe429cf
--- /dev/null
+++ b/testing/hstox/.gitignore
@@ -0,0 +1,8 @@
1/*.dSYM
2/*_main-*
3/test-findings
4!/Makefile
5*.gcno
6*.gcda
7*.hi
8*.o
diff --git a/testing/hstox/Makefile b/testing/hstox/Makefile
new file mode 100644
index 00000000..ec02719c
--- /dev/null
+++ b/testing/hstox/Makefile
@@ -0,0 +1,97 @@
1# Don't bind to a CPU core. afl-fuzz doesn't release them again.
2export AFL_NO_AFFINITY := 1
3
4CFLAGS := \
5 -std=gnu99 \
6 -Wall \
7 -g3 \
8 -DCONFIGURED=1 \
9 -Imsgpack-c/include \
10 -I../../toxcore \
11
12LDFLAGS := -pthread -lsodium
13ifeq ($(shell echo 'int main(){}' | $(CC) -lrt -xc -o /dev/null - && echo true),true)
14LDFLAGS += -lrt
15endif
16
17SOURCES := $(filter-out %_main.c,$(wildcard *.c msgpack-c/src/*.c ../../toxcore/*.c))
18HEADERS := $(shell find . -name "*.h")
19
20ifeq ($(wildcard test-findings/master/fuzzer_stats),)
21TEST_INPUTS := test-inputs
22else
23TEST_INPUTS := -
24endif
25
26# All the possible test flavours.
27PROGRAMS := \
28 test_main-cov \
29 test_main-cov-asan \
30 test_main-vanilla \
31 test_main-vanilla-asan
32
33ifneq ($(shell which afl-clang),)
34AFL_CLANG := afl-clang
35endif
36
37ifneq ($(shell which afl-clang-fast),)
38AFL_CLANG := afl-clang-fast
39endif
40
41ifneq ($(AFL_CLANG),)
42PROGRAMS += \
43 fuzz_main-afl \
44 fuzz_main-afl-asan
45endif
46
47all: $(PROGRAMS)
48
49check: test_main-sut
50 ./$< ./$<
51
52master: fuzz_main-afl test-findings
53 afl-fuzz -i$(TEST_INPUTS) -o test-findings -M master ./$<
54
55slave%: fuzz_main-afl test-findings
56 afl-fuzz -i test-inputs -o test-findings -S slave$* ./$<
57
58%-afl: %.c $(SOURCES) $(HEADERS)
59 $(AFL_CLANG) $(CFLAGS) -O3 $(filter %.c,$^) $(LDFLAGS) -o $@
60
61%-afl-asan: %.c $(SOURCES) $(HEADERS)
62 $(AFL_CLANG) $(CFLAGS) -O3 -fsanitize=address $(filter %.c,$^) $(LDFLAGS) -o $@
63
64%-cov: %.c $(SOURCES) $(HEADERS)
65 $(CC) $(CFLAGS) -fprofile-arcs -ftest-coverage $(filter %.c,$^) $(LDFLAGS) -o $@
66
67%-cov-asan: %.c $(SOURCES) $(HEADERS)
68 $(CC) $(CFLAGS) -fprofile-arcs -ftest-coverage -fsanitize=address $(filter %.c,$^) $(LDFLAGS) -o $@
69
70%-vanilla: %.c $(SOURCES) $(HEADERS)
71 $(CC) $(CFLAGS) $(filter %.c,$^) $(LDFLAGS) -o $@
72
73%-vanilla-asan: %.c $(SOURCES) $(HEADERS)
74 $(CC) $(CFLAGS) -fsanitize=address $(filter %.c,$^) $(LDFLAGS) -o $@
75
76%-sut: toxcore-sut.hs %.c $(SOURCES) $(HEADERS)
77 ghc -package hstox $< \
78 $(filter %.c,$^) \
79 $(foreach f,$(CFLAGS),-optc $f) \
80 $(foreach f,$(LDFLAGS),-optl $f) \
81 -o $@
82
83ifneq ($(wildcard /Volumes),)
84test-findings: /Volumes/RAM\ Disk
85 mkdir -p "$</$@"
86 ln -sf "$</$@" $@
87
88/Volumes/RAM\ Disk:
89 diskutil erasevolume HFS+ 'RAM Disk' `hdiutil attach -nomount ram://204800`
90else
91test-findings: /dev/shm
92 mkdir -p "$</$@"
93 test -d $@ || ln -sf "$</$@" $@
94endif
95
96clean:
97 rm -rf $(wildcard *_main-* *.dSYM *.gcno *.gcda *.o *.hi test-findings/*)
diff --git a/testing/hstox/binary_decode.c b/testing/hstox/binary_decode.c
new file mode 100644
index 00000000..aa4d7935
--- /dev/null
+++ b/testing/hstox/binary_decode.c
@@ -0,0 +1,224 @@
1#include "methods.h"
2
3#include "byteswap.h"
4#include "packet_kinds.h"
5
6#include <DHT.h>
7
8METHOD(bin, Binary_decode, CipherText)
9{
10 uint64_t length;
11 uint64_t tmp;
12
13 SUCCESS {
14 memcpy(&tmp, args.ptr, sizeof(uint64_t));
15 length = be64toh(tmp);
16
17 if (args.size >= sizeof(uint64_t) && args.size == length + sizeof(uint64_t))
18 {
19 msgpack_pack_bin(res, args.size - sizeof(uint64_t));
20 msgpack_pack_bin_body(res, args.ptr + sizeof(uint64_t), args.size - sizeof(uint64_t));
21 } else {
22 msgpack_pack_nil(res);
23 }
24 }
25 return 0;
26}
27
28METHOD(bin, Binary_decode, DhtPacket)
29{
30 return pending;
31}
32
33METHOD(bin, Binary_decode, HostAddress)
34{
35 return pending;
36}
37
38METHOD(bin, Binary_decode, Word64)
39{
40 return pending;
41}
42
43METHOD(bin, Binary_decode, Key_PublicKey)
44{
45 return pending;
46}
47
48METHOD(bin, Binary_decode, KeyPair)
49{
50 SUCCESS {
51 if (args.size != 64)
52 {
53 msgpack_pack_nil(res);
54 } else {
55 msgpack_pack_array(res, 2);
56 msgpack_pack_bin(res, 32);
57 msgpack_pack_bin_body(res, args.ptr, 32);
58 msgpack_pack_bin(res, 32);
59 msgpack_pack_bin_body(res, args.ptr + 32, 32);
60 }
61 }
62
63 return 0;
64}
65
66METHOD(bin, Binary_decode, NodeInfo)
67{
68 uint16_t data_processed;
69 Node_format node;
70 int len = unpack_nodes(&node, 1, &data_processed, (uint8_t const *)args.ptr, args.size, 1);
71
72 bool ip6_node = node.ip_port.ip.family == AF_INET6 || node.ip_port.ip.family == TCP_INET6;
73 bool tcp = node.ip_port.ip.family == TCP_INET || node.ip_port.ip.family == TCP_INET6;
74
75 uint16_t port = ntohs(node.ip_port.port);
76 uint32_t ip4 = ntohl(node.ip_port.ip.ip4.uint32);
77 uint32_t ip6_0 = ntohl(node.ip_port.ip.ip6.uint32[0]);
78 uint32_t ip6_1 = ntohl(node.ip_port.ip.ip6.uint32[1]);
79 uint32_t ip6_2 = ntohl(node.ip_port.ip.ip6.uint32[2]);
80 uint32_t ip6_3 = ntohl(node.ip_port.ip.ip6.uint32[3]);
81
82 SUCCESS {
83 if (len > 0 && data_processed > 0 && data_processed == args.size)
84 {
85 msgpack_pack_array(res, 3);
86 msgpack_pack_uint8(res, tcp);
87 msgpack_pack_array(res, 2);
88 msgpack_pack_array(res, 2);
89 msgpack_pack_uint8(res, ip6_node);
90
91 if (ip6_node) {
92 msgpack_pack_array(res, 4);
93 msgpack_pack_uint32(res, ip6_0);
94 msgpack_pack_uint32(res, ip6_1);
95 msgpack_pack_uint32(res, ip6_2);
96 msgpack_pack_uint32(res, ip6_3);
97 } else {
98 msgpack_pack_uint32(res, ip4);
99 }
100
101 msgpack_pack_uint16(res, port);
102 msgpack_pack_bin(res, crypto_box_PUBLICKEYBYTES);
103 msgpack_pack_bin_body(res, &node.public_key, crypto_box_PUBLICKEYBYTES);
104 } else {
105 msgpack_pack_nil(res);
106 }
107 }
108
109 return 0;
110}
111
112METHOD(bin, Binary_decode, NodesRequest)
113{
114 return pending;
115}
116
117METHOD(bin, Binary_decode, NodesResponse)
118{
119 return pending;
120}
121
122METHOD(bin, Binary_decode, Packet_Word64)
123{
124 return pending;
125}
126
127METHOD(bin, Binary_decode, PacketKind)
128{
129 SUCCESS {
130 if (args.size != 1)
131 {
132 msgpack_pack_nil(res);
133 } else {
134 uint8_t kind = args.ptr[0];
135 size_t i;
136
137 for (i = 0; i < sizeof packet_kinds / sizeof *packet_kinds; i++)
138 {
139 if (packet_kinds[i] == kind) {
140 msgpack_pack_fix_uint8(res, i);
141 return 0;
142 }
143 }
144
145 // Packet kind not found => error.
146 msgpack_pack_nil(res);
147 }
148 }
149 return 0;
150}
151
152METHOD(bin, Binary_decode, PingPacket)
153{
154 return pending;
155}
156
157METHOD(bin, Binary_decode, PlainText)
158{
159 return pending;
160}
161
162METHOD(bin, Binary_decode, PortNumber)
163{
164 SUCCESS {
165 if (args.size == 2)
166 {
167 uint16_t tmp;
168 memcpy(&tmp, args.ptr, 2);
169 uint16_t port = ntohs(tmp);
170 msgpack_pack_uint16(res, port);
171 } else {
172 msgpack_pack_nil(res);
173 }
174 }
175
176 return 0;
177}
178
179METHOD(bin, Binary_decode, RpcPacket_Word64)
180{
181 return pending;
182}
183
184METHOD(bin, Binary_decode, SocketAddress)
185{
186 return pending;
187}
188
189METHOD(bin, Binary_decode, TransportProtocol)
190{
191 return pending;
192}
193
194METHOD(array, Binary, decode)
195{
196 CHECK_SIZE(args, 2);
197 CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_STR);
198 CHECK_TYPE(args.ptr[1], MSGPACK_OBJECT_BIN);
199
200 msgpack_object_str type = args.ptr[0].via.str;
201#define DISPATCH(TYPE) \
202 if (type.size == sizeof #TYPE - 1 && method_cmp(type.ptr, #TYPE, type.size) == 0) \
203 return Binary_decode_##TYPE(args.ptr[1].via.bin, res)
204 DISPATCH(CipherText);
205 DISPATCH(DhtPacket);
206 DISPATCH(HostAddress);
207 DISPATCH(Word64);
208 DISPATCH(Key_PublicKey);
209 DISPATCH(KeyPair);
210 DISPATCH(NodeInfo);
211 DISPATCH(NodesRequest);
212 DISPATCH(NodesResponse);
213 DISPATCH(Packet_Word64);
214 DISPATCH(PacketKind);
215 DISPATCH(PingPacket);
216 DISPATCH(PlainText);
217 DISPATCH(PortNumber);
218 DISPATCH(RpcPacket_Word64);
219 DISPATCH(SocketAddress);
220 DISPATCH(TransportProtocol);
221#undef DISPATCH
222
223 return unimplemented;
224}
diff --git a/testing/hstox/binary_encode.c b/testing/hstox/binary_encode.c
new file mode 100644
index 00000000..a39533c0
--- /dev/null
+++ b/testing/hstox/binary_encode.c
@@ -0,0 +1,252 @@
1#include "methods.h"
2
3#include "byteswap.h"
4#include "packet_kinds.h"
5
6#include <DHT.h>
7
8METHOD(bin, Binary_encode, CipherText)
9{
10 uint64_t length = be64toh(args.size);
11
12 SUCCESS {
13 msgpack_pack_bin(res, sizeof(uint64_t) + args.size);
14 msgpack_pack_bin_body(res, &length, sizeof(uint64_t));
15 msgpack_pack_bin_body(res, args.ptr, args.size);
16 }
17 return 0;
18}
19
20METHOD(array, Binary_encode, DhtPacket)
21{
22 return pending;
23}
24
25METHOD(array, Binary_encode, HostAddress)
26{
27 return pending;
28}
29
30METHOD(u64, Binary_encode, Word64)
31{
32 return pending;
33}
34
35METHOD(bin, Binary_encode, Key_PublicKey)
36{
37 return pending;
38}
39
40METHOD(array, Binary_encode, KeyPair)
41{
42 CHECK_SIZE(args, 2);
43 CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_BIN);
44 CHECK_TYPE(args.ptr[1], MSGPACK_OBJECT_BIN);
45
46 msgpack_object_bin secret_key = args.ptr[0].via.bin;
47 msgpack_object_bin public_key = args.ptr[1].via.bin;
48
49 CHECK_SIZE(secret_key, 32);
50 CHECK_SIZE(public_key, 32);
51
52 SUCCESS {
53 uint8_t data[64];
54 memcpy(data, secret_key.ptr, 32);
55 memcpy(data + 32, public_key.ptr, 32);
56 msgpack_pack_bin(res, 64);
57 msgpack_pack_bin_body(res, data, 64);
58 }
59
60 return 0;
61}
62
63#define PACKED_NODE_SIZE_IP6 (1 + SIZE_IP6 + sizeof(uint16_t) + crypto_box_PUBLICKEYBYTES)
64METHOD(array, Binary_encode, NodeInfo)
65{
66 CHECK_SIZE(args, 3);
67
68 CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_POSITIVE_INTEGER);
69 uint64_t protocol = args.ptr[0].via.u64;
70
71 CHECK_TYPE(args.ptr[1], MSGPACK_OBJECT_ARRAY);
72 msgpack_object_array address = args.ptr[1].via.array;
73
74 CHECK_SIZE(address, 2);
75 CHECK_TYPE(address.ptr[0], MSGPACK_OBJECT_ARRAY);
76 msgpack_object_array host_address = address.ptr[0].via.array;
77
78 CHECK_TYPE(address.ptr[1], MSGPACK_OBJECT_POSITIVE_INTEGER);
79 uint64_t port_number = address.ptr[1].via.u64;
80
81 CHECK_SIZE(host_address, 2);
82 CHECK_TYPE(host_address.ptr[0], MSGPACK_OBJECT_POSITIVE_INTEGER);
83 uint64_t address_family = host_address.ptr[0].via.u64;
84
85 CHECK_TYPE(args.ptr[2], MSGPACK_OBJECT_BIN);
86 msgpack_object_bin public_key = args.ptr[2].via.bin;
87
88 CHECK_SIZE(public_key, crypto_box_PUBLICKEYBYTES);
89
90 IP_Port ipp;
91 ipp.port = htons(port_number);
92
93 switch (address_family) {
94 case 0: {
95 /* IPv4*/
96 if (protocol == 1) {
97 ipp.ip.family = TCP_INET;
98 } else {
99 ipp.ip.family = AF_INET;
100 }
101
102 CHECK_TYPE(host_address.ptr[1], MSGPACK_OBJECT_POSITIVE_INTEGER);
103 uint64_t addr = host_address.ptr[1].via.u64;
104
105 ipp.ip.ip4.uint32 = htonl(addr);
106 break;
107 }
108
109 case 1: {
110 /* IPv6 */
111 if (protocol == 1) {
112 ipp.ip.family = TCP_INET6;
113 } else {
114 ipp.ip.family = AF_INET6;
115 }
116
117 CHECK_TYPE(host_address.ptr[1], MSGPACK_OBJECT_ARRAY);
118 msgpack_object_array addr = host_address.ptr[1].via.array;
119
120 int i;
121
122 for (i = 0; i < 4; ++i) {
123 CHECK_TYPE(addr.ptr[i], MSGPACK_OBJECT_POSITIVE_INTEGER);
124 uint64_t component = addr.ptr[i].via.u64;
125 ipp.ip.ip6.uint32[i] = htonl(component);
126 }
127
128 break;
129 }
130 }
131
132 Node_format node;
133 node.ip_port = ipp;
134 memcpy(&node.public_key, public_key.ptr, crypto_box_PUBLICKEYBYTES);
135
136 /* We assume IP6 because it's bigger */
137 uint8_t packed_node[PACKED_NODE_SIZE_IP6];
138
139 int len = pack_nodes(packed_node, sizeof packed_node, &node, 1);
140
141 if (len < 0) {
142 return failure;
143 }
144
145 SUCCESS {
146 msgpack_pack_bin(res, len);
147 msgpack_pack_bin_body(res, packed_node, len);
148 }
149 return 0;
150}
151
152METHOD(bin, Binary_encode, NodesRequest)
153{
154 return pending;
155}
156
157METHOD(array, Binary_encode, NodesResponse)
158{
159 return pending;
160}
161
162METHOD(array, Binary_encode, Packet_Word64)
163{
164 return pending;
165}
166
167METHOD(u64, Binary_encode, PacketKind)
168{
169 CHECK(args < sizeof packet_kinds / sizeof * packet_kinds);
170
171 SUCCESS {
172 uint8_t data[] = {packet_kinds[args]};
173 msgpack_pack_bin(res, sizeof data);
174 msgpack_pack_bin_body(res, data, sizeof data);
175 }
176 return 0;
177}
178
179METHOD(u64, Binary_encode, PingPacket)
180{
181 return pending;
182}
183
184METHOD(bin, Binary_encode, PlainText)
185{
186 return pending;
187}
188
189METHOD(u64, Binary_encode, PortNumber)
190{
191 SUCCESS {
192 if (args <= UINT16_MAX)
193 {
194 uint16_t port = ntohs(args);
195 msgpack_pack_bin(res, 2);
196 msgpack_pack_bin_body(res, &port, 2);
197 } else {
198 msgpack_pack_nil(res);
199 }
200 }
201 return 0;
202}
203
204METHOD(array, Binary_encode, RpcPacket_Word64)
205{
206 return pending;
207}
208
209METHOD(array, Binary_encode, SocketAddress)
210{
211 return pending;
212}
213
214METHOD(u64, Binary_encode, TransportProtocol)
215{
216 return pending;
217}
218
219METHOD(array, Binary, encode)
220{
221 CHECK_SIZE(args, 2);
222 CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_STR);
223
224 msgpack_object_str type = args.ptr[0].via.str;
225#define DISPATCH(TYPE, UTYPE, LTYPE) \
226 do { \
227 if (type.size == sizeof #TYPE - 1 && method_cmp(type.ptr, #TYPE, type.size) == 0) { \
228 CHECK_TYPE(args.ptr[1], MSGPACK_OBJECT_##UTYPE); \
229 return Binary_encode_##TYPE(args.ptr[1].via.LTYPE, res); \
230 } \
231 } while (0)
232 DISPATCH(CipherText, BIN, bin);
233 DISPATCH(DhtPacket, ARRAY, array);
234 DISPATCH(HostAddress, ARRAY, array);
235 DISPATCH(Word64, POSITIVE_INTEGER, u64);
236 DISPATCH(Key_PublicKey, BIN, bin);
237 DISPATCH(KeyPair, ARRAY, array);
238 DISPATCH(NodeInfo, ARRAY, array);
239 DISPATCH(NodesRequest, BIN, bin);
240 DISPATCH(NodesResponse, ARRAY, array);
241 DISPATCH(Packet_Word64, ARRAY, array);
242 DISPATCH(PacketKind, POSITIVE_INTEGER, u64);
243 DISPATCH(PingPacket, POSITIVE_INTEGER, u64);
244 DISPATCH(PlainText, BIN, bin);
245 DISPATCH(PortNumber, POSITIVE_INTEGER, u64);
246 DISPATCH(RpcPacket_Word64, ARRAY, array);
247 DISPATCH(SocketAddress, ARRAY, array);
248 DISPATCH(TransportProtocol, POSITIVE_INTEGER, u64);
249#undef DISPATCH
250
251 return unimplemented;
252}
diff --git a/testing/hstox/byteswap.h b/testing/hstox/byteswap.h
new file mode 100644
index 00000000..b5a58f66
--- /dev/null
+++ b/testing/hstox/byteswap.h
@@ -0,0 +1,6 @@
1#ifdef __APPLE__
2#include <libkern/OSByteOrder.h>
3
4#define htobe64(x) OSSwapHostToBigInt64(x)
5#define be64toh(x) OSSwapBigToHostInt64(x)
6#endif
diff --git a/testing/hstox/driver.c b/testing/hstox/driver.c
new file mode 100644
index 00000000..81773190
--- /dev/null
+++ b/testing/hstox/driver.c
@@ -0,0 +1,244 @@
1#include <errno.h>
2#include <fcntl.h>
3#include <netdb.h>
4#include <signal.h>
5#include <signal.h>
6#include <stdarg.h>
7#include <stdio.h>
8#include <string.h>
9#include <sys/socket.h>
10#include <sys/types.h>
11#include <unistd.h>
12
13#include "driver.h"
14#include "errors.h"
15#include "methods.h"
16#include "util.h"
17
18#include <sodium.h>
19
20static void handle_interrupt(int signum)
21{
22 printf("Caught signal %d; exiting cleanly.\n", signum);
23 exit(0);
24}
25
26static int protocol_error(msgpack_packer *pk, char const *fmt, ...)
27{
28 msgpack_pack_array(pk, 4); // 4 elements in the array
29 msgpack_pack_uint8(pk, 1); // 1. type = response
30 // 2. We don't know the msgid, because the packet we received is not a valid
31 // msgpack-rpc packet.
32 msgpack_pack_uint64(pk, 0);
33
34 // 3. Error message.
35 va_list ap;
36 va_start(ap, fmt);
37 int res = msgpack_pack_vstringf(pk, fmt, ap);
38 va_end(ap);
39
40 // 4. No success result.
41 msgpack_pack_array(pk, 0);
42
43 return res;
44}
45
46static bool type_check(msgpack_packer *pk, msgpack_object req, int index,
47 msgpack_object_type type)
48{
49 if (req.via.array.ptr[index].type != type) {
50 protocol_error(pk, "element %d should be %s, but is %s", index, type_name(type),
51 type_name(req.via.array.ptr[index].type));
52 return false;
53 }
54
55 return true;
56}
57
58static int write_sample_input(msgpack_object req)
59{
60 static unsigned int n;
61
62 char filename[256];
63 msgpack_object_str name = req.via.array.ptr[2].via.str;
64 snprintf(filename, sizeof filename - name.size, "test-inputs/%04u-", n++);
65
66 assert(sizeof filename - strlen(filename) > name.size + 4);
67 memcpy(filename + strlen(filename) + name.size, ".mp", 4);
68 memcpy(filename + strlen(filename), name.ptr, name.size);
69
70 int fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
71
72 if (fd < 0)
73 // If we can't open the sample file, we just don't write it.
74 {
75 return E_OK;
76 }
77
78 check_return(E_WRITE, ftruncate(fd, 0));
79
80 msgpack_sbuffer sbuf __attribute__((__cleanup__(msgpack_sbuffer_destroy)));
81 msgpack_sbuffer_init(&sbuf);
82
83 msgpack_packer pk;
84 msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write);
85
86 msgpack_pack_object(&pk, req);
87
88 check_return(E_WRITE, write(fd, sbuf.data, sbuf.size));
89
90 return E_OK;
91}
92
93static int handle_request(struct settings cfg, int write_fd, msgpack_object req)
94{
95 msgpack_sbuffer sbuf __attribute__((__cleanup__(msgpack_sbuffer_destroy))); /* buffer */
96 msgpack_sbuffer_init(&sbuf); /* initialize buffer */
97
98 msgpack_packer pk; /* packer */
99 msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write); /* initialize packer */
100
101 if (req.type != MSGPACK_OBJECT_ARRAY) {
102 protocol_error(&pk, "expected array, but got %s", type_name(req.type));
103 } else if (req.via.array.size != 4) {
104 protocol_error(&pk, "array length should be 4, but is %d", req.via.array.size);
105 } else if (type_check(&pk, req, 0, MSGPACK_OBJECT_POSITIVE_INTEGER) &&
106 type_check(&pk, req, 1, MSGPACK_OBJECT_POSITIVE_INTEGER) &&
107 type_check(&pk, req, 2, MSGPACK_OBJECT_STR) &&
108 type_check(&pk, req, 3, MSGPACK_OBJECT_ARRAY)) {
109 if (cfg.collect_samples) {
110 propagate(write_sample_input(req));
111 }
112
113 uint64_t msgid = req.via.array.ptr[1].via.u64;
114 msgpack_object_str name = req.via.array.ptr[2].via.str;
115 msgpack_object_array args = req.via.array.ptr[3].via.array;
116
117 msgpack_pack_array(&pk, 4); // 4 elements in the array
118 msgpack_pack_uint8(&pk, 1); // 1. type = response
119 msgpack_pack_uint64(&pk, msgid); // 2. msgid
120
121 if (name.size == (sizeof "rpc.capabilities") - 1 &&
122 memcmp(name.ptr, "rpc.capabilities", name.size) == 0) {
123 // 3. Error.
124 msgpack_pack_string(&pk, "Capabilities negiotiation not implemented");
125 // 4. No result.
126 msgpack_pack_nil(&pk);
127 } else {
128 // if error is null, this writes 3. no error, and 4. result
129 char const *error =
130 call_method(name, args, &pk);
131
132 if (error) {
133 if (cfg.debug) {
134 printf("Error '%s' in request: ", error);
135 msgpack_object_print(stdout, req);
136 printf("\n");
137 }
138
139 msgpack_pack_string(&pk, error);
140 msgpack_pack_array(&pk, 0);
141 }
142 }
143 }
144
145 check_return(E_WRITE, write(write_fd, sbuf.data, sbuf.size));
146
147 return E_OK;
148}
149
150int communicate(struct settings cfg, int read_fd, int write_fd)
151{
152 msgpack_unpacker unp __attribute__((__cleanup__(msgpack_unpacker_destroy)));
153 msgpack_unpacker_init(&unp, 128);
154
155 while (true) {
156 char buf[64];
157 int size = check_return(E_READ, read(read_fd, buf, sizeof buf));
158
159 if (size == 0) {
160 break;
161 }
162
163 if (msgpack_unpacker_buffer_capacity(&unp) < size &&
164 !msgpack_unpacker_reserve_buffer(&unp, size)) {
165 return E_NOMEM;
166 }
167
168 memcpy(msgpack_unpacker_buffer(&unp), buf, size);
169 msgpack_unpacker_buffer_consumed(&unp, size);
170
171 msgpack_unpacked req __attribute__((__cleanup__(msgpack_unpacked_destroy)));
172 msgpack_unpacked_init(&req);
173
174 switch (msgpack_unpacker_next(&unp, &req)) {
175 case MSGPACK_UNPACK_SUCCESS:
176 propagate(handle_request(cfg, write_fd, req.data));
177 break;
178
179 case MSGPACK_UNPACK_EXTRA_BYTES:
180 printf("EXTRA_BYTES\n");
181 break;
182
183 case MSGPACK_UNPACK_CONTINUE:
184 break;
185
186 case MSGPACK_UNPACK_PARSE_ERROR:
187 return E_PARSE;
188
189 case MSGPACK_UNPACK_NOMEM_ERROR:
190 return E_NOMEM;
191 }
192 }
193
194 return E_OK;
195}
196
197static int closep(int *fd)
198{
199 return close(*fd);
200}
201
202static int run_tests(struct settings cfg, int port)
203{
204 int listen_fd __attribute__((__cleanup__(closep))) = 0;
205 listen_fd = check_return(E_SOCKET, socket(AF_INET, SOCK_STREAM, 0));
206 check_return(E_SOCKET, setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &(int) {
207 1
208 }, sizeof(int)));
209
210 struct sockaddr_in servaddr;
211 servaddr.sin_family = AF_INET;
212 servaddr.sin_addr.s_addr = htons(INADDR_ANY);
213 servaddr.sin_port = htons(port);
214
215 check_return(E_BIND, bind(listen_fd, (struct sockaddr *)&servaddr, sizeof servaddr));
216 check_return(E_LISTEN, listen(listen_fd, 10));
217
218 while (true) {
219 int comm_fd __attribute__((__cleanup__(closep))) = 0;
220 comm_fd = check_return(E_ACCEPT, accept(listen_fd, NULL, NULL));
221 propagate(communicate(cfg, comm_fd, comm_fd));
222 }
223
224 return E_OK;
225}
226
227uint32_t network_main(struct settings cfg, uint16_t port, unsigned int timeout)
228{
229 signal(SIGALRM, handle_interrupt);
230 signal(SIGINT, handle_interrupt);
231 check_return(E_SODIUM, sodium_init());
232
233 // Kill the process after `timeout` seconds so we don't get lingering
234 // processes bound to the test port when something goes wrong with a test run.
235 alarm(timeout);
236
237 int result = run_tests(cfg, port);
238
239 if (result == E_OK) {
240 return E_OK;
241 }
242
243 return result | (errno << 8);
244}
diff --git a/testing/hstox/driver.h b/testing/hstox/driver.h
new file mode 100644
index 00000000..219df8a1
--- /dev/null
+++ b/testing/hstox/driver.h
@@ -0,0 +1,20 @@
1#pragma once
2
3#include <stdbool.h>
4#include <stdint.h>
5
6// Settings for the test runner.
7struct settings {
8 // Print the msgpack object on test failure.
9 bool debug;
10 // Write test sample files into test-inputs/. These files, one per test
11 // method, are used to seed the fuzzer.
12 bool collect_samples;
13};
14
15// Main loop communicating via read/write file descriptors. The two fds can be
16// the same in case of a network socket.
17int communicate(struct settings cfg, int read_fd, int write_fd);
18
19// Open a TCP socket on the given port and start communicate().
20uint32_t network_main(struct settings cfg, uint16_t port, unsigned int timeout);
diff --git a/testing/hstox/errors.h b/testing/hstox/errors.h
new file mode 100644
index 00000000..42bf864a
--- /dev/null
+++ b/testing/hstox/errors.h
@@ -0,0 +1,11 @@
1#define E_OK 0
2#define E_NOMEM 1
3#define E_SOCKET 2
4#define E_BIND 3
5#define E_LISTEN 4
6#define E_ACCEPT 5
7#define E_PARSE 6
8#define E_OPEN 7
9#define E_READ 8
10#define E_WRITE 9
11#define E_SODIUM 10
diff --git a/testing/hstox/fuzz_main.c b/testing/hstox/fuzz_main.c
new file mode 100644
index 00000000..ff66a166
--- /dev/null
+++ b/testing/hstox/fuzz_main.c
@@ -0,0 +1,23 @@
1#include "driver.h"
2
3#include <unistd.h>
4
5// The number of afl iterations before terminating the process and starting a
6// new one.
7// See https://github.com/mcarpenter/afl/blob/master/llvm_mode/README.llvm.
8#define ITERATIONS 1000
9
10#ifdef __GLASGOW_HASKELL__
11#define main fuzz_main
12#endif
13int main(int argc, char **argv)
14{
15 struct settings cfg = {false, false};
16#ifdef __AFL_LOOP
17
18 while (__AFL_LOOP(ITERATIONS))
19#endif
20 communicate(cfg, STDIN_FILENO, STDOUT_FILENO);
21
22 return 0;
23}
diff --git a/testing/hstox/methods.c b/testing/hstox/methods.c
new file mode 100644
index 00000000..08ad50fa
--- /dev/null
+++ b/testing/hstox/methods.c
@@ -0,0 +1,147 @@
1#include "methods.h"
2
3#include "util.h"
4
5#include <crypto_core.h>
6#include <net_crypto.h>
7
8char const *const failure = "Failure";
9char const *const pending = "Pending";
10char const *const unimplemented = "Unimplemented";
11
12METHOD(array, Box, encrypt)
13{
14 return pending;
15}
16
17METHOD(array, Box, decrypt)
18{
19 return pending;
20}
21
22METHOD(array, CombinedKey, precompute)
23{
24 return pending;
25}
26
27METHOD(array, KeyPair, newKeyPair)
28{
29 uint8_t key1[crypto_box_PUBLICKEYBYTES];
30 uint8_t key2[crypto_box_SECRETKEYBYTES];
31 crypto_box_keypair(key1, key2);
32
33 SUCCESS {
34 // init array
35 msgpack_pack_array(res, 2);
36 msgpack_pack_bin(res, crypto_box_PUBLICKEYBYTES);
37 msgpack_pack_bin_body(res, key1, crypto_box_PUBLICKEYBYTES);
38
39 msgpack_pack_bin(res, crypto_box_SECRETKEYBYTES);
40 msgpack_pack_bin_body(res, key2, crypto_box_SECRETKEYBYTES);
41 }
42 return 0;
43}
44
45METHOD(array, KeyPair, fromSecretKey)
46{
47 CHECK_SIZE(args, 1);
48 CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_BIN);
49 CHECK_SIZE(args.ptr[0].via.bin, crypto_box_SECRETKEYBYTES);
50
51 Net_Crypto c;
52 uint8_t secret_key[crypto_box_SECRETKEYBYTES];
53 memcpy(secret_key, args.ptr[0].via.bin.ptr, crypto_box_SECRETKEYBYTES);
54 load_secret_key(&c, secret_key);
55
56 SUCCESS {
57 msgpack_pack_array(res, 2);
58
59 msgpack_pack_bin(res, crypto_box_PUBLICKEYBYTES);
60 msgpack_pack_bin_body(res, c.self_secret_key, crypto_box_PUBLICKEYBYTES);
61 msgpack_pack_bin(res, crypto_box_SECRETKEYBYTES);
62 msgpack_pack_bin_body(res, c.self_public_key, crypto_box_SECRETKEYBYTES);
63 }
64 return 0;
65}
66
67METHOD(array, Nonce, newNonce)
68{
69 uint8_t nonce[24] = {0};
70 new_nonce(nonce);
71
72 SUCCESS {
73 msgpack_pack_bin(res, sizeof nonce);
74 msgpack_pack_bin_body(res, nonce, sizeof nonce);
75 }
76
77 return 0;
78}
79
80METHOD(array, Nonce, increment)
81{
82 CHECK_SIZE(args, 1);
83 CHECK_TYPE(args.ptr[0], MSGPACK_OBJECT_BIN);
84 CHECK_SIZE(args.ptr[0].via.bin, 24);
85
86 uint8_t nonce[24];
87 memcpy(nonce, args.ptr[0].via.bin.ptr, 24);
88 increment_nonce(nonce);
89
90 SUCCESS {
91 msgpack_pack_bin(res, sizeof nonce);
92 msgpack_pack_bin_body(res, nonce, sizeof nonce);
93 }
94
95 return 0;
96}
97
98METHOD(array, rpc, capabilities)
99{
100 return pending;
101}
102
103char const *call_method(msgpack_object_str name, msgpack_object_array args, msgpack_packer *res)
104{
105#define DISPATCH(SERVICE, NAME) \
106 if (name.size == sizeof #SERVICE "." #NAME - 1 && \
107 memcmp(name.ptr, #SERVICE "." #NAME, name.size) == 0) \
108 return SERVICE##_##NAME(args, res)
109 DISPATCH(Binary, decode);
110 DISPATCH(Binary, encode);
111 DISPATCH(Box, decrypt);
112 DISPATCH(Box, encrypt);
113 DISPATCH(CombinedKey, precompute);
114 DISPATCH(KeyPair, fromSecretKey);
115 DISPATCH(KeyPair, newKeyPair);
116 DISPATCH(Nonce, increment);
117 DISPATCH(Nonce, newNonce);
118#undef DISPATCH
119
120 // Default action: "Unimplemented" exception. New tests should be added here
121 // returning "Pending" until they are properly implemented.
122 return unimplemented;
123}
124
125int method_cmp(char const *ptr, char const *expected, size_t max_size)
126{
127 char *transformed = malloc(max_size);
128
129 if (transformed == NULL) {
130 return memcmp(ptr, expected, max_size);
131 }
132
133 memcpy(transformed, ptr, max_size);
134 size_t i;
135
136 for (i = 0; i < max_size; i++) {
137 switch (transformed[i]) {
138 case '(':
139 case ')':
140 case ' ':
141 transformed[i] = '_';
142 break;
143 }
144 }
145
146 return memcmp(transformed, expected, max_size);
147}
diff --git a/testing/hstox/methods.h b/testing/hstox/methods.h
new file mode 100644
index 00000000..1a7f3059
--- /dev/null
+++ b/testing/hstox/methods.h
@@ -0,0 +1,47 @@
1#pragma once
2
3#include "util.h"
4
5#include <msgpack.h>
6
7#define CHECK(COND) \
8 do { \
9 if (!(COND)) \
10 return #COND; \
11 } while (0)
12
13#define CHECK_SIZE(ARG, SIZE) \
14 do { \
15 if ((ARG).size != (SIZE)) \
16 return ssprintf("%s:%d: Size of `" #ARG "' expected to be %zu, but was %zu", __FILE__, \
17 __LINE__, SIZE, (ARG).size); \
18 } while (0)
19
20#define CHECK_TYPE(ARG, TYPE) \
21 do { \
22 if ((ARG).type != (TYPE)) \
23 return ssprintf("%s:%d: Type of `" #ARG "' expected to be %s, but was %s", __FILE__, \
24 __LINE__, type_name(TYPE), type_name((ARG).type)); \
25 } while (0)
26
27#define SUCCESS \
28 msgpack_pack_array(res, 0); \
29 if (true)
30
31#define METHOD(TYPE, SERVICE, NAME) \
32 char const *SERVICE##_##NAME(msgpack_object_##TYPE args, msgpack_packer *res)
33
34// These are not defined by msgpack.h, but we need them for uniformity in the
35// METHOD macro.
36typedef int64_t msgpack_object_i64;
37typedef uint64_t msgpack_object_u64;
38
39METHOD(array, Binary, decode);
40METHOD(array, Binary, encode);
41
42char const *call_method(msgpack_object_str name, msgpack_object_array args, msgpack_packer *res);
43int method_cmp(char const *ptr, char const *expected, size_t max_size);
44
45extern char const *const failure;
46extern char const *const pending;
47extern char const *const unimplemented;
diff --git a/testing/hstox/packet_kinds.c b/testing/hstox/packet_kinds.c
new file mode 100644
index 00000000..314c814b
--- /dev/null
+++ b/testing/hstox/packet_kinds.c
@@ -0,0 +1,48 @@
1#include "packet_kinds.h"
2
3#include <network.h>
4
5uint8_t const packet_kinds[21] = {
6 // = PingRequest -- 0x00: Ping request
7 NET_PACKET_PING_REQUEST,
8 // | PingResponse -- 0x01: Ping response
9 NET_PACKET_PING_RESPONSE,
10 // | NodesRequest -- 0x02: Nodes request
11 NET_PACKET_GET_NODES,
12 // | NodesResponse -- 0x04: Nodes response
13 NET_PACKET_SEND_NODES_IPV6,
14 // | CookieRequest -- 0x18: Cookie request
15 NET_PACKET_COOKIE_REQUEST,
16 // | CookieResponse -- 0x19: Cookie response
17 NET_PACKET_COOKIE_RESPONSE,
18 // | CryptoHandshake -- 0x1a: Crypto handshake
19 NET_PACKET_CRYPTO_HS,
20 // | CryptoData -- 0x1b: Crypto data
21 NET_PACKET_CRYPTO_DATA,
22 // | Crypto -- 0x20: Encrypted data
23 NET_PACKET_CRYPTO,
24 // | LanDiscovery -- 0x21: LAN discovery
25 NET_PACKET_LAN_DISCOVERY,
26 // | OnionRequest0 -- 0x80: Initial onion request
27 NET_PACKET_ONION_SEND_INITIAL,
28 // | OnionRequest1 -- 0x81: First level wrapped onion request
29 NET_PACKET_ONION_SEND_1,
30 // | OnionRequest2 -- 0x82: Second level wrapped onion request
31 NET_PACKET_ONION_SEND_2,
32 // | AnnounceRequest -- 0x83: Announce request
33 NET_PACKET_ANNOUNCE_REQUEST,
34 // | AnnounceResponse -- 0x84: Announce response
35 NET_PACKET_ANNOUNCE_RESPONSE,
36 // | OnionDataRequest -- 0x85: Onion data request
37 NET_PACKET_ONION_DATA_REQUEST,
38 // | OnionDataResponse -- 0x86: Onion data response
39 NET_PACKET_ONION_DATA_RESPONSE,
40 // | OnionResponse3 -- 0x8c: Third level wrapped onion response
41 NET_PACKET_ONION_RECV_3,
42 // | OnionResponse2 -- 0x8d: Second level wrapped onion response
43 NET_PACKET_ONION_RECV_2,
44 // | OnionResponse1 -- 0x8e: First level wrapped onion response
45 NET_PACKET_ONION_RECV_1,
46 // | BootstrapInfo -- 0xf0: Bootstrap node info request and response
47 BOOTSTRAP_INFO_PACKET_ID,
48};
diff --git a/testing/hstox/packet_kinds.h b/testing/hstox/packet_kinds.h
new file mode 100644
index 00000000..ab019ab1
--- /dev/null
+++ b/testing/hstox/packet_kinds.h
@@ -0,0 +1,5 @@
1#pragma once
2
3#include <stdint.h>
4
5extern uint8_t const packet_kinds[21];
diff --git a/testing/hstox/test-inputs/.gitignore b/testing/hstox/test-inputs/.gitignore
new file mode 100644
index 00000000..5eb3d40d
--- /dev/null
+++ b/testing/hstox/test-inputs/.gitignore
@@ -0,0 +1 @@
/?*.*
diff --git a/testing/hstox/test_main.c b/testing/hstox/test_main.c
new file mode 100644
index 00000000..7ac18da0
--- /dev/null
+++ b/testing/hstox/test_main.c
@@ -0,0 +1,72 @@
1#include "driver.h"
2#include "errors.h"
3
4#include <stdio.h>
5#include <stdlib.h>
6
7// The msgpack-rpc test port expected by the test runner.
8#define PORT 1234
9
10// Timeout in seconds after which the driver shuts down. Currently, a complete
11// test run takes about 7 seconds. The timeout of 2 minutes is a guess so it
12// keeps working for a while, even on a very slow computer.
13#define TIMEOUT 120
14
15static char const *error_desc(int code)
16{
17 switch (code) {
18 case E_OK:
19 return "Success";
20
21 case E_NOMEM:
22 return "Error: Out of memory";
23
24 case E_SOCKET:
25 return "Error: socket creation failed";
26
27 case E_BIND:
28 return "Error: bind failed";
29
30 case E_LISTEN:
31 return "Error: listen failed";
32
33 case E_ACCEPT:
34 return "Error: accept failed";
35
36 case E_PARSE:
37 return "Error: unable to parse msgpack input";
38
39 case E_OPEN:
40 return "Error: open failed";
41
42 case E_READ:
43 return "Error: read failed";
44
45 case E_WRITE:
46 return "Error: write failed";
47
48 case E_SODIUM:
49 return "Error: libsodium initialisation failed";
50 }
51
52 return "Unknown error code";
53}
54
55#ifdef __GLASGOW_HASKELL__
56#define main test_main
57#endif
58int main(void)
59{
60 struct settings cfg = {true, true};
61 uint32_t result = network_main(cfg, PORT, TIMEOUT);
62 int line = result >> 16;
63 int error = (result >> 8) & 0xff;
64 int code = result & 0xff;
65
66 if (code != E_OK) {
67 printf("%s, errno=%d, line=%d\n", error_desc(code), error, line);
68 return EXIT_FAILURE;
69 }
70
71 return EXIT_SUCCESS;
72}
diff --git a/testing/hstox/toxcore-sut.hs b/testing/hstox/toxcore-sut.hs
new file mode 100644
index 00000000..73bdb702
--- /dev/null
+++ b/testing/hstox/toxcore-sut.hs
@@ -0,0 +1,31 @@
1module Main (main) where
2
3import Control.Concurrent (threadDelay)
4import System.Environment (getArgs, withArgs)
5import System.Process (createProcess, proc, terminateProcess)
6
7import Network.Tox.Testing (serve)
8import qualified ToxTestSuite
9
10
11foreign import ccall test_main :: IO ()
12
13
14main :: IO ()
15main = do
16 args <- getArgs
17 case args of
18 ["--sut"] -> test_main
19 self : testArgs -> do
20 -- Start a toxcore SUT (System Under Test) process that will listen on
21 -- port 1234. We call ourselves here, so the branch above is taken.
22 (_, _, _, sut) <- createProcess $ proc self ["--sut"]
23 -- 100ms delay to give the SUT time to set up its socket before we try to
24 -- build connections in the test runner.
25 threadDelay $ 100 * 1000
26 -- ToxTestSuite (the test runner) makes connections to port 1234 to
27 -- communicate with the SUT.
28 withArgs (["--print-cpu-time", "--color"] ++ testArgs) ToxTestSuite.main
29 terminateProcess sut
30 _ ->
31 fail "Usage: toxcore-sut <path-to-toxcore-sut> [test-args...]"
diff --git a/testing/hstox/util.c b/testing/hstox/util.c
new file mode 100644
index 00000000..9461596c
--- /dev/null
+++ b/testing/hstox/util.c
@@ -0,0 +1,74 @@
1#include "util.h"
2
3#include <stdarg.h>
4
5char const *type_name(msgpack_object_type type)
6{
7 switch (type) {
8 case MSGPACK_OBJECT_NIL:
9 return "nil";
10
11 case MSGPACK_OBJECT_BOOLEAN:
12 return "boolean";
13
14 case MSGPACK_OBJECT_POSITIVE_INTEGER:
15 return "positive_integer";
16
17 case MSGPACK_OBJECT_NEGATIVE_INTEGER:
18 return "negative_integer";
19
20 case MSGPACK_OBJECT_FLOAT:
21 return "float";
22
23 case MSGPACK_OBJECT_STR:
24 return "str";
25
26 case MSGPACK_OBJECT_ARRAY:
27 return "array";
28
29 case MSGPACK_OBJECT_MAP:
30 return "map";
31
32 case MSGPACK_OBJECT_BIN:
33 return "bin";
34
35 case MSGPACK_OBJECT_EXT:
36 return "ext";
37 }
38
39 return "<unknown type>";
40}
41
42int msgpack_pack_string(msgpack_packer *pk, char const *str)
43{
44 size_t len = strlen(str);
45 msgpack_pack_str(pk, len);
46 return msgpack_pack_str_body(pk, str, len);
47}
48
49int msgpack_pack_vstringf(msgpack_packer *pk, char const *fmt, va_list ap)
50{
51 char buf[1024];
52 vsnprintf(buf, sizeof buf, fmt, ap);
53 return msgpack_pack_string(pk, buf);
54}
55
56int msgpack_pack_stringf(msgpack_packer *pk, char const *fmt, ...)
57{
58 va_list ap;
59 va_start(ap, fmt);
60 int res = msgpack_pack_vstringf(pk, fmt, ap);
61 va_end(ap);
62 return res;
63}
64
65char const *ssprintf(char const *fmt, ...)
66{
67 static char buf[1024];
68
69 va_list ap;
70 va_start(ap, fmt);
71 vsnprintf(buf, sizeof buf, fmt, ap);
72 va_end(ap);
73 return buf;
74}
diff --git a/testing/hstox/util.h b/testing/hstox/util.h
new file mode 100644
index 00000000..c00578a2
--- /dev/null
+++ b/testing/hstox/util.h
@@ -0,0 +1,27 @@
1#pragma once
2
3#include <msgpack.h>
4
5#define check_return(err, expr) \
6 __extension__({ \
7 __typeof__(expr) _r = (expr); \
8 if (_r < 0) \
9 return err | (__LINE__ << 16); \
10 _r; \
11 })
12
13#define propagate(expr) \
14 do { \
15 __typeof__(expr) _r = (expr); \
16 if (_r != E_OK) \
17 return _r; \
18 } while (0)
19
20char const *type_name(msgpack_object_type type);
21
22// Statically allocated "asprintf".
23char const *ssprintf(char const *fmt, ...);
24
25int msgpack_pack_string(msgpack_packer *pk, char const *str);
26int msgpack_pack_stringf(msgpack_packer *pk, char const *fmt, ...);
27int msgpack_pack_vstringf(msgpack_packer *pk, char const *fmt, va_list ap);