summaryrefslogtreecommitdiffstats
path: root/tests/contrib
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:24:08 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 15:24:08 +0000
commitf449f278dd3c70e479a035f50a9bb817a9b433ba (patch)
tree8ca2bfb785dda9bb4d573acdf9b42aea9cd51383 /tests/contrib
parentInitial commit. (diff)
downloadknot-f449f278dd3c70e479a035f50a9bb817a9b433ba.tar.xz
knot-f449f278dd3c70e479a035f50a9bb817a9b433ba.zip
Adding upstream version 3.2.6.upstream/3.2.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--tests/contrib/test_base32hex.c267
-rw-r--r--tests/contrib/test_base64.c237
-rw-r--r--tests/contrib/test_base64url.c252
-rw-r--r--tests/contrib/test_heap.c166
-rw-r--r--tests/contrib/test_inet_ntop.c85
-rw-r--r--tests/contrib/test_net.c718
-rw-r--r--tests/contrib/test_net_shortwrite.c151
-rw-r--r--tests/contrib/test_qp-cow.c282
-rw-r--r--tests/contrib/test_qp-trie.c284
-rw-r--r--tests/contrib/test_siphash.c135
-rw-r--r--tests/contrib/test_sockaddr.c229
-rw-r--r--tests/contrib/test_spinlock.c78
-rw-r--r--tests/contrib/test_string.c59
-rw-r--r--tests/contrib/test_strtonum.c156
-rw-r--r--tests/contrib/test_time.c203
-rw-r--r--tests/contrib/test_toeplitz.c93
-rw-r--r--tests/contrib/test_wire_ctx.c287
17 files changed, 3682 insertions, 0 deletions
diff --git a/tests/contrib/test_base32hex.c b/tests/contrib/test_base32hex.c
new file mode 100644
index 0000000..541667c
--- /dev/null
+++ b/tests/contrib/test_base32hex.c
@@ -0,0 +1,267 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "libknot/libknot.h"
+#include "contrib/base32hex.h"
+#include "contrib/openbsd/strlcpy.h"
+
+#define BUF_LEN 256
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 8) * 5)
+
+int main(int argc, char *argv[])
+{
+ plan(67);
+
+ int32_t ret;
+ uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN], *out3;
+ uint32_t in_len, ref_len;
+
+ // 0. test invalid input
+ ret = knot_base32hex_encode(NULL, 0, out, BUF_LEN);
+ is_int(KNOT_EINVAL, ret, "knot_base32hex_encode: NULL input buffer");
+ ret = knot_base32hex_encode(in, BUF_LEN, NULL, 0);
+ is_int(KNOT_EINVAL, ret, "knot_base32hex_encode: NULL output buffer");
+ ret = knot_base32hex_encode(in, MAX_BIN_DATA_LEN + 1, out, BUF_LEN);
+ is_int(KNOT_ERANGE, ret, "knot_base32hex_encode: input buffer too large");
+ ret = knot_base32hex_encode(in, BUF_LEN, out, BUF_LEN);
+ is_int(KNOT_ERANGE, ret, "knot_base32hex_encode: output buffer too small");
+
+ ret = knot_base32hex_encode_alloc(NULL, 0, &out3);
+ is_int(KNOT_EINVAL, ret, "knot_base32hex_encode_alloc: NULL input buffer");
+ ret = knot_base32hex_encode_alloc(in, MAX_BIN_DATA_LEN + 1, &out3);
+ is_int(KNOT_ERANGE, ret, "knot_base32hex_encode_alloc: input buffer too large");
+ ret = knot_base32hex_encode_alloc(in, BUF_LEN, NULL);
+ is_int(KNOT_EINVAL, ret, "knot_base32hex_encode_alloc: NULL output buffer");
+
+ ret = knot_base32hex_decode(NULL, 0, out, BUF_LEN);
+ is_int(KNOT_EINVAL, ret, "knot_base32hex_decode: NULL input buffer");
+ ret = knot_base32hex_decode(in, BUF_LEN, NULL, 0);
+ is_int(KNOT_EINVAL, ret, "knot_base32hex_decode: NULL output buffer");
+ ret = knot_base32hex_decode(in, UINT32_MAX, out, BUF_LEN);
+ is_int(KNOT_ERANGE, ret, "knot_base32hex_decode: input buffer too large");
+ ret = knot_base32hex_decode(in, BUF_LEN, out, 0);
+ is_int(KNOT_ERANGE, ret, "knot_base32hex_decode: output buffer too small");
+
+ ret = knot_base32hex_decode_alloc(NULL, 0, &out3);
+ is_int(KNOT_EINVAL, ret, "knot_base32hex_decode_alloc: NULL input buffer");
+ ret = knot_base32hex_decode_alloc(in, UINT32_MAX, &out3);
+ is_int(KNOT_ERANGE, ret, "knot_base32hex_decode_aloc: input buffer too large");
+ ret = knot_base32hex_decode_alloc(in, BUF_LEN, NULL);
+ is_int(KNOT_EINVAL, ret, "knot_base32hex_decode_alloc: NULL output buffer");
+
+ // 1. test vector -> ENC -> DEC
+ strlcpy((char *)in, "", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "1. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content");
+ }
+ ret = knot_base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "1. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content");
+ }
+
+ // 2. test vector -> ENC -> DEC
+ strlcpy((char *)in, "f", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "co======", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "2. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content");
+ }
+ ret = knot_base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "2. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content");
+ }
+
+ // 3. test vector -> ENC -> DEC
+ strlcpy((char *)in, "fo", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "cpng====", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "3. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content");
+ }
+ ret = knot_base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "3. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content");
+ }
+
+ // 4. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foo", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "cpnmu===", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "4. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content");
+ }
+ ret = knot_base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "4. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content");
+ }
+
+ // 5. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foob", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "cpnmuog=", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "5. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content");
+ }
+ ret = knot_base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "5. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content");
+ }
+
+ // 6. test vector -> ENC -> DEC
+ strlcpy((char *)in, "fooba", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "cpnmuoj1", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "6. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content");
+ }
+ ret = knot_base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "6. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content");
+ }
+
+ // 7. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foobar", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "cpnmuoj1e8======", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base32hex_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "7. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content");
+ }
+ ret = knot_base32hex_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "7. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content");
+ }
+
+ // Bad paddings
+ ret = knot_base32hex_decode((uint8_t *)"AAAAAA==", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 2");
+ ret = knot_base32hex_decode((uint8_t *)"AAA=====", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 5");
+ ret = knot_base32hex_decode((uint8_t *)"A=======", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 7");
+ ret = knot_base32hex_decode((uint8_t *)"========", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding length 8");
+ ret = knot_base32hex_decode((uint8_t *)"AAAAA=A=", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding character on position 2");
+ ret = knot_base32hex_decode((uint8_t *)"AA=A====", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding character on position 5");
+ ret = knot_base32hex_decode((uint8_t *)"=A======", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad padding character on position 7");
+ ret = knot_base32hex_decode((uint8_t *)"CO======CO======", 16, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Two octects with padding");
+
+ // Bad data length
+ ret = knot_base32hex_decode((uint8_t *)"A", 1, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 1");
+ ret = knot_base32hex_decode((uint8_t *)"AA", 2, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 2");
+ ret = knot_base32hex_decode((uint8_t *)"AAA", 3, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 3");
+ ret = knot_base32hex_decode((uint8_t *)"AAAA", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 4");
+ ret = knot_base32hex_decode((uint8_t *)"AAAAA", 5, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 5");
+ ret = knot_base32hex_decode((uint8_t *)"AAAAAA", 6, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 6");
+ ret = knot_base32hex_decode((uint8_t *)"AAAAAAA", 7, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 7");
+ ret = knot_base32hex_decode((uint8_t *)"AAAAAAAAA", 9, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ESIZE, "Bad data length 9");
+
+ // Bad data character
+ ret = knot_base32hex_decode((uint8_t *)"AAAAAAA$", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar");
+ ret = knot_base32hex_decode((uint8_t *)"AAAAAAA ", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character space");
+ ret = knot_base32hex_decode((uint8_t *)"AAAAAA$A", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 7");
+ ret = knot_base32hex_decode((uint8_t *)"AAAAA$AA", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 6");
+ ret = knot_base32hex_decode((uint8_t *)"AAAA$AAA", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 5");
+ ret = knot_base32hex_decode((uint8_t *)"AAA$AAAA", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 4");
+ ret = knot_base32hex_decode((uint8_t *)"AA$AAAAA", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 3");
+ ret = knot_base32hex_decode((uint8_t *)"A$AAAAAA", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 2");
+ ret = knot_base32hex_decode((uint8_t *)"$AAAAAAA", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE32HEX_ECHAR, "Bad data character dollar on position 1");
+
+ return 0;
+}
diff --git a/tests/contrib/test_base64.c b/tests/contrib/test_base64.c
new file mode 100644
index 0000000..82eee7d
--- /dev/null
+++ b/tests/contrib/test_base64.c
@@ -0,0 +1,237 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "libknot/libknot.h"
+#include "contrib/base64.h"
+#include "contrib/openbsd/strlcpy.h"
+
+#define BUF_LEN 256
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3)
+
+int main(int argc, char *argv[])
+{
+ plan(52);
+
+ int32_t ret;
+ uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN], *out3;
+ uint32_t in_len, ref_len;
+
+ // 0. test invalid input
+ ret = knot_base64_encode(NULL, 0, out, BUF_LEN);
+ is_int(KNOT_EINVAL, ret, "knot_base64_encode: NULL input buffer");
+ ret = knot_base64_encode(in, BUF_LEN, NULL, 0);
+ is_int(KNOT_EINVAL, ret, "knot_base64_encode: NULL output buffer");
+ ret = knot_base64_encode(in, MAX_BIN_DATA_LEN + 1, out, BUF_LEN);
+ is_int(KNOT_ERANGE, ret, "knot_base64_encode: input buffer too large");
+ ret = knot_base64_encode(in, BUF_LEN, out, BUF_LEN);
+ is_int(KNOT_ERANGE, ret, "knot_base64_encode: output buffer too small");
+
+ ret = knot_base64_encode_alloc(NULL, 0, &out3);
+ is_int(KNOT_EINVAL, ret, "knot_base64_encode_alloc: NULL input buffer");
+ ret = knot_base64_encode_alloc(in, MAX_BIN_DATA_LEN + 1, &out3);
+ is_int(KNOT_ERANGE, ret, "knot_base64_encode_alloc: input buffer too large");
+ ret = knot_base64_encode_alloc(in, BUF_LEN, NULL);
+ is_int(KNOT_EINVAL, ret, "knot_base64_encode_alloc: NULL output buffer");
+
+ ret = knot_base64_decode(NULL, 0, out, BUF_LEN);
+ is_int(KNOT_EINVAL, ret, "knot_base64_decode: NULL input buffer");
+ ret = knot_base64_decode(in, BUF_LEN, NULL, 0);
+ is_int(KNOT_EINVAL, ret, "knot_base64_decode: NULL output buffer");
+ ret = knot_base64_decode(in, UINT32_MAX, out, BUF_LEN);
+ is_int(KNOT_ERANGE, ret, "knot_base64_decode: input buffer too large");
+ ret = knot_base64_decode(in, BUF_LEN, out, 0);
+ is_int(KNOT_ERANGE, ret, "knot_base64_decode: output buffer too small");
+
+ ret = knot_base64_decode_alloc(NULL, 0, &out3);
+ is_int(KNOT_EINVAL, ret, "knot_base64_decode_alloc: NULL input buffer");
+ ret = knot_base64_decode_alloc(in, UINT32_MAX, &out3);
+ is_int(KNOT_ERANGE, ret, "knot_base64_decode_aloc: input buffer too large");
+ ret = knot_base64_decode_alloc(in, BUF_LEN, NULL);
+ is_int(KNOT_EINVAL, ret, "knot_base64_decode_alloc: NULL output buffer");
+
+ // 1. test vector -> ENC -> DEC
+ strlcpy((char *)in, "", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "1. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content");
+ }
+ ret = knot_base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "1. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content");
+ }
+
+ // 2. test vector -> ENC -> DEC
+ strlcpy((char *)in, "f", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zg==", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "2. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content");
+ }
+ ret = knot_base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "2. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content");
+ }
+
+ // 3. test vector -> ENC -> DEC
+ strlcpy((char *)in, "fo", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm8=", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "3. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content");
+ }
+ ret = knot_base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "3. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content");
+ }
+
+ // 4. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foo", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm9v", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "4. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content");
+ }
+ ret = knot_base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "4. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content");
+ }
+
+ // 5. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foob", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm9vYg==", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "5. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content");
+ }
+ ret = knot_base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "5. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content");
+ }
+
+ // 6. test vector -> ENC -> DEC
+ strlcpy((char *)in, "fooba", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm9vYmE=", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "6. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content");
+ }
+ ret = knot_base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "6. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content");
+ }
+
+ // 7. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foobar", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm9vYmFy", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "7. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content");
+ }
+ ret = knot_base64_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "7. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content");
+ }
+
+ // Bad paddings
+ ret = knot_base64_decode((uint8_t *)"A===", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 3");
+ ret = knot_base64_decode((uint8_t *)"====", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 4");
+ ret = knot_base64_decode((uint8_t *)"AA=A", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding character on position 2");
+ ret = knot_base64_decode((uint8_t *)"Zg==Zg==", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Two quartets with padding");
+
+ // Bad data length
+ ret = knot_base64_decode((uint8_t *)"A", 1, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ESIZE, "Bad data length 1");
+ ret = knot_base64_decode((uint8_t *)"AA", 2, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ESIZE, "Bad data length 2");
+ ret = knot_base64_decode((uint8_t *)"AAA", 3, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ESIZE, "Bad data length 3");
+ ret = knot_base64_decode((uint8_t *)"AAAAA", 5, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ESIZE, "Bad data length 5");
+
+ // Bad data character
+ ret = knot_base64_decode((uint8_t *)"AAA$", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad data character dollar");
+ ret = knot_base64_decode((uint8_t *)"AAA ", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad data character space");
+
+ return 0;
+}
diff --git a/tests/contrib/test_base64url.c b/tests/contrib/test_base64url.c
new file mode 100644
index 0000000..710aa29
--- /dev/null
+++ b/tests/contrib/test_base64url.c
@@ -0,0 +1,252 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "libknot/libknot.h"
+#include "contrib/base64url.h"
+#include "contrib/openbsd/strlcpy.h"
+
+#define BUF_LEN 256
+#define MAX_BIN_DATA_LEN ((INT32_MAX / 4) * 3)
+
+int main(int argc, char *argv[])
+{
+ plan(50);
+
+ int32_t ret;
+ uint8_t in[BUF_LEN], ref[BUF_LEN], out[BUF_LEN], out2[BUF_LEN], *out3;
+ uint32_t in_len, ref_len;
+
+ // 0. test invalid input
+ ret = knot_base64url_encode(NULL, 0, out, BUF_LEN);
+ is_int(KNOT_EINVAL, ret, "knot_base64ulr_encode: NULL input buffer");
+ ret = knot_base64url_encode(in, BUF_LEN, NULL, 0);
+ is_int(KNOT_EINVAL, ret, "knot_base64ulr_encode: NULL output buffer");
+ ret = knot_base64url_encode(in, MAX_BIN_DATA_LEN + 1, out, BUF_LEN);
+ is_int(KNOT_ERANGE, ret, "knot_base64ulr_encode: input buffer too large");
+ ret = knot_base64url_encode(in, BUF_LEN, out, BUF_LEN);
+ is_int(KNOT_ERANGE, ret, "knot_base64ulr_encode: output buffer too small");
+
+ ret = knot_base64url_encode_alloc(NULL, 0, &out3);
+ is_int(KNOT_EINVAL, ret, "knot_base64ulr_encode_alloc: NULL input buffer");
+ ret = knot_base64url_encode_alloc(in, MAX_BIN_DATA_LEN + 1, &out3);
+ is_int(KNOT_ERANGE, ret, "knot_base64ulr_encode_alloc: input buffer too large");
+ ret = knot_base64url_encode_alloc(in, BUF_LEN, NULL);
+ is_int(KNOT_EINVAL, ret, "knot_base64ulr_encode_alloc: NULL output buffer");
+
+ ret = knot_base64url_decode(NULL, 0, out, BUF_LEN);
+ is_int(KNOT_EINVAL, ret, "knot_base64ulr_decode: NULL input buffer");
+ ret = knot_base64url_decode(in, BUF_LEN, NULL, 0);
+ is_int(KNOT_EINVAL, ret, "knot_base64ulr_decode: NULL output buffer");
+ ret = knot_base64url_decode(in, BUF_LEN, out, 0);
+ is_int(KNOT_ERANGE, ret, "knot_base64ulr_decode: output buffer too small");
+
+ ret = knot_base64url_decode_alloc(NULL, 0, &out3);
+ is_int(KNOT_EINVAL, ret, "knot_base64ulr_decode_alloc: NULL input buffer");
+ ret = knot_base64url_decode_alloc(in, BUF_LEN, NULL);
+ is_int(KNOT_EINVAL, ret, "knot_base64ulr_decode_alloc: NULL output buffer");
+
+ // 1. test vector -> ENC -> DEC
+ strlcpy((char *)in, "", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64url_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "1. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "1. test vector - ENC output content");
+ }
+ ret = knot_base64url_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "1. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "1. test vector - DEC output content");
+ }
+
+ // 2. test vector -> ENC -> DEC
+ strlcpy((char *)in, "f", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zg", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64url_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "2. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "2. test vector - ENC output content");
+ }
+ ret = knot_base64url_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "2. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "2. test vector - DEC output content");
+ }
+
+ // 3. test vector -> ENC -> DEC
+ strlcpy((char *)in, "fo", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm8", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64url_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "3. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "3. test vector - ENC output content");
+ }
+ ret = knot_base64url_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "3. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "3. test vector - DEC output content");
+ }
+
+ // 4. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foo", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm9v", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64url_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "4. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "4. test vector - ENC output content");
+ }
+ ret = knot_base64url_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "4. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "4. test vector - DEC output content");
+ }
+
+ // 5. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foob", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm9vYg", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64url_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "5. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "5. test vector - ENC output content");
+ }
+ ret = knot_base64url_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "5. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "5. test vector - DEC output content");
+ }
+
+ // 6. test vector -> ENC -> DEC
+ strlcpy((char *)in, "fooba", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm9vYmE", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64url_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "6. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "6. test vector - ENC output content");
+ }
+ ret = knot_base64url_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "6. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "6. test vector - DEC output content");
+ }
+
+ // 7. test vector -> ENC -> DEC
+ strlcpy((char *)in, "foobar", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "Zm9vYmFy", BUF_LEN);
+ ref_len = strlen((char *)ref);
+ ret = knot_base64url_encode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "7. test vector - ENC output length");
+ if (ret < 0) {
+ skip("Encode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "7. test vector - ENC output content");
+ }
+ ret = knot_base64url_decode(out, ret, out2, BUF_LEN);
+ ok(ret == in_len, "7. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out2, in, ret) == 0, "7. test vector - DEC output content");
+ }
+
+ // 8. ENC (percent-encoded padding) -> DEC
+ strlcpy((char *)in, "Zm9vYmE%3D", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "fooba", BUF_LEN);
+ ref_len = strlen((char *)ref);
+
+ ret = knot_base64url_decode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "8. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "8. test vector - DEC output content");
+ }
+
+ strlcpy((char *)in, "Zm9vYmFyCg%3d%3d", BUF_LEN);
+ in_len = strlen((char *)in);
+ strlcpy((char *)ref, "foobar\n", BUF_LEN);
+ ref_len = strlen((char *)ref);
+
+ ret = knot_base64url_decode(in, in_len, out, BUF_LEN);
+ ok(ret == ref_len, "9. test vector - DEC output length");
+ if (ret < 0) {
+ skip("Decode err");
+ } else {
+ ok(memcmp(out, ref, ret) == 0, "9. test vector - DEC output content");
+ }
+
+ // Bad paddings
+ ret = knot_base64url_decode((uint8_t *)"A", 1, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 3");
+ ret = knot_base64url_decode((uint8_t *)"%3D", 3, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding length 4");
+
+ // Paddings not at the end
+ ret = knot_base64url_decode((uint8_t *)"AB%3DCDEFG", 10, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding 1");
+ ret = knot_base64url_decode((uint8_t *)"AB\0CDEFG", 8, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad padding 2");
+
+ // Bad data character
+ ret = knot_base64url_decode((uint8_t *)"AAA$", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad data character dollar");
+ ret = knot_base64url_decode((uint8_t *)"AAA ", 4, out, BUF_LEN);
+ ok(ret == KNOT_BASE64_ECHAR, "Bad data character space");
+
+ return 0;
+}
diff --git a/tests/contrib/test_heap.c b/tests/contrib/test_heap.c
new file mode 100644
index 0000000..7dc5975
--- /dev/null
+++ b/tests/contrib/test_heap.c
@@ -0,0 +1,166 @@
+/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <tap/basic.h>
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "contrib/ucw/heap.h"
+
+static void seed_random(void)
+{
+ unsigned short int seed[3] = { 0 };
+
+ FILE *f = fopen("/dev/urandom", "r");
+ if (f) {
+ if (fread(seed, sizeof(seed), 1, f) != 1) {
+ diag("failed to seed random source");
+ }
+ fclose(f);
+ }
+
+ diag("seed %hu %hu %hu", seed[0], seed[1], seed[2]);
+ seed48(seed);
+}
+
+struct value {
+ heap_val_t _heap;
+ int data;
+};
+
+static int value_cmp(void *_a, void *_b)
+{
+ const struct value *a = _a;
+ const struct value *b = _b;
+ return (a->data - b->data);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ seed_random();
+
+ static const int VALUE_COUNT = 1000;
+ static const int VALUE_RANGE = 950;
+ static const int VALUE_REPLACE = 300;
+ static const int VALUE_DELETE = 100;
+
+ struct heap heap;
+ heap_init(&heap, value_cmp, 0);
+
+ ok(EMPTY_HEAP(&heap), "heap is empty");
+
+ // fill the heap with random values (with duplicates)
+
+ struct value *values = calloc(VALUE_COUNT, sizeof(struct value));
+ assert(values);
+ assert(VALUE_RANGE < VALUE_COUNT);
+
+ bool valid = true;
+ for (int i = 0; i < VALUE_COUNT; i++) {
+ values[i].data = lrand48() % VALUE_RANGE;
+ if (heap_insert(&heap, &values[i]._heap) == 0) {
+ valid = false;
+ }
+ }
+ ok(valid, "heap_insert");
+ ok(!EMPTY_HEAP(&heap), "heap is non-empty");
+
+ // exercise heap_insert
+
+ valid = true;
+ for (int i = 0; i < VALUE_COUNT; i++) {
+ int pos = heap_find(&heap, &values[i]._heap);
+ if (*HELEMENT(&heap, pos) != &values[i]._heap) {
+ valid = false;
+ }
+ }
+ ok(valid, "heap_find");
+
+ // exercise heap_replace
+
+ assert(VALUE_REPLACE <= VALUE_COUNT);
+ struct value *replaced = calloc(VALUE_REPLACE, sizeof(struct value));
+ assert(replaced);
+
+ valid = true;
+ for (int i = 0; i < VALUE_REPLACE; i++) {
+ replaced[i].data = lrand48() % VALUE_RANGE;
+ int pos = heap_find(&heap, &values[i]._heap);
+ if (pos < 1) {
+ valid = false;
+ continue;
+ }
+
+ heap_replace(&heap, pos, &replaced[i]._heap);
+ int newpos = heap_find(&heap, &replaced[i]._heap);
+ if (newpos < 1) {
+ valid = false;
+ }
+ }
+ ok(valid, "heap_replace");
+
+ // exercise heap_delete
+
+ assert(VALUE_REPLACE + VALUE_DELETE < VALUE_COUNT);
+
+ valid = true;
+ for (int i = 0; i < VALUE_DELETE; i++) {
+ heap_val_t *value = &values[i + VALUE_REPLACE]._heap;
+ int pos = heap_find(&heap, value);
+ if (pos < 1) {
+ valid = false;
+ continue;
+
+ }
+ heap_delete(&heap, pos);
+ pos = heap_find(&heap, value);
+ if (pos != 0) {
+ valid = false;
+ }
+ }
+ ok(valid, "heap_delete");
+
+ // exercise item retrieval
+
+ assert(VALUE_COUNT > VALUE_DELETE);
+
+ valid = true;
+ int current = -1;
+ for (int i = 0; i < VALUE_COUNT - VALUE_DELETE; i++) {
+ struct value *val = (struct value *)*HHEAD(&heap);
+ heap_delmin(&heap);
+ if (current <= val->data) {
+ current = val->data;
+ } else {
+ valid = false;
+ }
+ }
+
+ ok(valid, "heap ordering");
+ ok(EMPTY_HEAP(&heap), "heap_delmin");
+
+ free(replaced);
+ free(values);
+ heap_deinit(&heap);
+
+ return 0;
+}
diff --git a/tests/contrib/test_inet_ntop.c b/tests/contrib/test_inet_ntop.c
new file mode 100644
index 0000000..33e7b7e
--- /dev/null
+++ b/tests/contrib/test_inet_ntop.c
@@ -0,0 +1,85 @@
+/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <tap/basic.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include "contrib/musl/inet_ntop.h"
+
+uint8_t bin[sizeof(struct in6_addr)];
+const socklen_t len = INET6_ADDRSTRLEN;
+char buf[INET6_ADDRSTRLEN];
+const char *txt;
+
+#define CHECK4(addr) \
+ ok(inet_pton(AF_INET, addr, bin) == 1, "inet_pton(%s)", addr); \
+ ok((txt = knot_inet_ntop(AF_INET, bin, buf, len)) != NULL, "knot_inet_ntop(%s)", addr); \
+ ok(strcmp(txt, addr) == 0, "match %s", addr);
+
+#define CHECK6(addr, ref) \
+ ok(inet_pton(AF_INET6, addr, bin) == 1, "inet_pton(%s)", addr); \
+ ok((txt = knot_inet_ntop(AF_INET6, bin, buf, len)) != NULL, "knot_inet_ntop(%s)", addr); \
+ ok(strcmp(txt, ref) == 0, "match %s %s", txt, ref);
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ diag("IPv4 addresses");
+ CHECK4("0.0.0.0");
+ CHECK4("1.2.3.4");
+ CHECK4("11.12.13.14");
+ CHECK4("255.255.255.255");
+
+ diag("IPv6 addresses");
+ CHECK6("::0", "::");
+ CHECK6("::00", "::");
+ CHECK6("::000", "::");
+ CHECK6("::0000", "::");
+
+ CHECK6("::1", "::1");
+ CHECK6("::01", "::1");
+ CHECK6("::001", "::1");
+ CHECK6("::0001", "::1");
+
+ CHECK6("::10", "::10");
+ CHECK6("::100", "::100");
+ CHECK6("::1000", "::1000");
+
+ CHECK6("::1:0", "::1:0");
+ CHECK6("::1:0:0", "::1:0:0");
+ CHECK6("::1:0:0:0", "::1:0:0:0");
+ CHECK6("::1:0:0:0:0", "0:0:0:1::");
+ CHECK6("::1:0:0:0:0:0", "0:0:1::");
+ CHECK6("::1:0:0:0:0:0:0", "0:1::");
+ CHECK6("1:0:0:0:0:0:0:0", "1::");
+
+ // IPv4-Compatible IPv6 Addresses (not supported).
+ CHECK6("::0:1:1", "::1:1");
+ CHECK6("::0:1.2.3.4", "::102:304");
+
+ // IPv4-Mapped IPv6 Addresses.
+ CHECK6("::ffff:1:1", "::ffff:0.1.0.1");
+ CHECK6("::ffff:1.2.3.4", "::ffff:1.2.3.4");
+
+ CHECK6("1::1", "1::1");
+ CHECK6("1000::1", "1000::1");
+ CHECK6("1:20:300:4000:0005:006:07:8", "1:20:300:4000:5:6:7:8");
+
+ return 0;
+}
diff --git a/tests/contrib/test_net.c b/tests/contrib/test_net.c
new file mode 100644
index 0000000..c0061cd
--- /dev/null
+++ b/tests/contrib/test_net.c
@@ -0,0 +1,718 @@
+/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <tap/basic.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <poll.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libknot/errcode.h"
+#include "contrib/net.h"
+#include "contrib/sockaddr.h"
+
+const int TIMEOUT = 5000;
+const int TIMEOUT_SHORT = 500;
+
+/*!
+ * \brief Get loopback socket address with unset port.
+ */
+static struct sockaddr_storage addr_local(void)
+{
+ struct sockaddr_storage addr = { 0 };
+
+ struct sockaddr_in *addr4 = (struct sockaddr_in *)&addr;
+ addr4->sin_family = AF_INET;
+ addr4->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ return addr;
+}
+
+/*!
+ * \brief Get address of a socket.
+ */
+static struct sockaddr_storage addr_from_socket(int sock)
+{
+ struct sockaddr_storage addr = { 0 };
+ socklen_t len = sizeof(addr);
+ int ret = getsockname(sock, (struct sockaddr *)&addr, &len);
+ is_int(0, ret, "check getsockname return");
+
+ return addr;
+}
+
+static const char *socktype_name(int type)
+{
+ switch (type) {
+ case SOCK_STREAM: return "TCP";
+ case SOCK_DGRAM: return "UDP";
+ default: return "unknown";
+ }
+}
+
+static bool socktype_is_stream(int type)
+{
+ return type == SOCK_STREAM;
+}
+
+/* -- mock server ---------------------------------------------------------- */
+
+#define LISTEN_BACKLOG 5
+
+struct server_ctx;
+typedef struct server_ctx server_ctx_t;
+
+typedef void (*server_cb)(int sock, void *data);
+
+/*!
+ * \brief Server context.
+ */
+struct server_ctx {
+ int sock;
+ int type;
+ bool terminate;
+ server_cb handler;
+ void *handler_data;
+
+ pthread_t thr;
+ pthread_mutex_t mx;
+};
+
+static int poll_read(int sock)
+{
+ struct pollfd pfd = { .fd = sock, .events = POLLIN };
+ return poll(&pfd, 1, TIMEOUT);
+}
+
+static void server_handle(server_ctx_t *ctx)
+{
+ int remote = ctx->sock;
+
+ assert(ctx->type == SOCK_STREAM || ctx->type == SOCK_DGRAM);
+
+ if (socktype_is_stream(ctx->type)) {
+ remote = accept(ctx->sock, 0, 0);
+ if (remote < 0) {
+ return;
+ }
+ }
+
+ pthread_mutex_lock(&ctx->mx);
+ server_cb handler = ctx->handler;
+ pthread_mutex_unlock(&ctx->mx);
+ handler(remote, ctx->handler_data);
+
+ if (socktype_is_stream(ctx->type)) {
+ close(remote);
+ }
+}
+
+/*!
+ * \brief Simple server.
+ *
+ * Terminated when a one-byte message is delivered.
+ */
+static void *server_main(void *_ctx)
+{
+ server_ctx_t *ctx = _ctx;
+
+ for (;;) {
+ pthread_mutex_lock(&ctx->mx);
+ bool terminate = ctx->terminate;
+ pthread_mutex_unlock(&ctx->mx);
+ if (terminate) {
+ break;
+ }
+
+ int r = poll_read(ctx->sock);
+ if (r == -1) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ break;
+ }
+ } else if (r == 0) {
+ continue;
+ }
+
+ assert(r == 1);
+ server_handle(ctx);
+ }
+
+ return NULL;
+}
+
+static bool server_start(server_ctx_t *ctx, int sock, int type,
+ server_cb handler, void *handler_data)
+{
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->sock = sock;
+ ctx->type = type;
+ ctx->handler = handler;
+ ctx->handler_data = handler_data;
+
+ ctx->terminate = false;
+
+ pthread_mutex_init(&ctx->mx, NULL);
+ return (pthread_create(&ctx->thr, NULL, server_main, ctx) == 0);
+}
+
+static void server_stop(server_ctx_t *ctx)
+{
+ pthread_mutex_lock(&ctx->mx);
+ ctx->terminate = true;
+ pthread_mutex_unlock(&ctx->mx);
+
+ pthread_kill(ctx->thr, SIGUSR1);
+ pthread_join(ctx->thr, NULL);
+}
+
+/* -- tests ---------------------------------------------------------------- */
+
+static void handler_echo(int sock, void *_server)
+{
+ server_ctx_t *server = _server;
+ uint8_t buffer[16] = { 0 };
+
+ struct sockaddr_storage remote = { 0 };
+ struct sockaddr_storage *addr = NULL;
+ if (!socktype_is_stream(server->type)) {
+ addr = &remote;
+ }
+
+ int in = net_base_recv(sock, buffer, sizeof(buffer), addr, TIMEOUT);
+ if (in <= 0) {
+ return;
+ }
+
+ net_base_send(sock, buffer, in, addr, TIMEOUT);
+}
+
+static void test_connected_one(const struct sockaddr_storage *server_addr,
+ const struct sockaddr_storage *source_addr,
+ int type, const char *name, const char *addr_name)
+{
+ int r;
+
+ int client = net_connected_socket(type, server_addr, source_addr, false);
+ ok(client >= 0, "%s, %s: client, create connected socket", name, addr_name);
+
+ const uint8_t out[] = "test message";
+ const size_t out_len = sizeof(out);
+ if (socktype_is_stream(type)) {
+ r = net_stream_send(client, out, out_len, TIMEOUT);
+ } else {
+ r = net_dgram_send(client, out, out_len, NULL);
+ }
+ is_int(out_len, r, "%s, %s: client, send message", name, addr_name);
+
+ r = net_is_connected(client);
+ ok(r, "%s, %s: client, is connected", name, addr_name);
+
+ uint8_t in[128] = { 0 };
+ if (socktype_is_stream(type)) {
+ r = net_stream_recv(client, in, sizeof(in), TIMEOUT);
+ } else {
+ r = net_dgram_recv(client, in, sizeof(in), TIMEOUT);
+ }
+ is_int(out_len, r, "%s, %s: client, receive message length", name, addr_name);
+ ok(memcmp(out, in, out_len) == 0, "%s, %s: client, receive message", name, addr_name);
+
+ close(client);
+}
+
+static void test_connected(int type)
+{
+ const char *name = socktype_name(type);
+ const struct sockaddr_storage empty_addr = { 0 };
+ const struct sockaddr_storage local_addr = addr_local();
+
+ int r;
+
+ // setup server
+
+ int server = net_bound_socket(type, &local_addr, 0, 0);
+ ok(server >= 0, "%s: server, create bound socket", name);
+
+ if (socktype_is_stream(type)) {
+ r = listen(server, LISTEN_BACKLOG);
+ is_int(0, r, "%s: server, start listening", name);
+ }
+
+ server_ctx_t server_ctx = { 0 };
+ r = server_start(&server_ctx, server, type, handler_echo, &server_ctx);
+ ok(r, "%s: server, start", name);
+
+ const struct sockaddr_storage server_addr = addr_from_socket(server);
+
+ // connected socket, send and receive
+
+ test_connected_one(&server_addr, NULL, type, name, "NULL source");
+ test_connected_one(&server_addr, &empty_addr, type, name, "zero source");
+ test_connected_one(&server_addr, &local_addr, type, name, "valid source");
+
+ // cleanup
+
+ server_stop(&server_ctx);
+ close(server);
+}
+
+static void handler_noop(int sock, void *data)
+{
+}
+
+static void test_unconnected(void)
+{
+ int r = 0;
+ int sock = -1;
+ const struct sockaddr_storage local = addr_local();
+
+ uint8_t buffer[] = { 'k', 'n', 'o', 't' };
+ ssize_t buffer_len = sizeof(buffer);
+
+ // server
+
+ int server = net_bound_socket(SOCK_DGRAM, &local, 0, 0);
+ ok(server >= 0, "UDP, create server socket");
+
+ server_ctx_t server_ctx = { 0 };
+ r = server_start(&server_ctx, server, SOCK_DGRAM, handler_noop, NULL);
+ ok(r, "UDP, start server");
+
+ // UDP
+
+ sock = net_unbound_socket(SOCK_DGRAM, &local);
+ ok(sock >= 0, "UDP, create unbound socket");
+
+ ok(!net_is_connected(sock), "UDP, is not connected");
+
+ r = net_dgram_send(sock, buffer, buffer_len, NULL);
+ is_int(KNOT_ECONN, r, "UDP, send failure on unconnected socket");
+
+ r = net_dgram_recv(sock, buffer, buffer_len, TIMEOUT_SHORT);
+ is_int(KNOT_ETIMEOUT, r, "UDP, receive timeout on unconnected socket");
+
+ struct sockaddr_storage server_addr = addr_from_socket(server);
+ r = net_dgram_send(sock, buffer, buffer_len, &server_addr);
+ is_int(buffer_len, r, "UDP, send on defined address");
+
+ close(sock);
+
+ // TCP
+
+ sock = net_unbound_socket(SOCK_STREAM, &local);
+ ok(sock >= 0, "TCP, create unbound socket");
+
+ ok(!net_is_connected(sock), "TCP, is not connected");
+
+#ifdef __linux__
+ const int expected = KNOT_ECONN;
+ const char *expected_msg = "failure";
+ const int expected_timeout = TIMEOUT;
+#else
+ const int expected = KNOT_ETIMEOUT;
+ const char *expected_msg = "timeout";
+ const int expected_timeout = TIMEOUT_SHORT;
+#endif
+
+ r = net_stream_send(sock, buffer, buffer_len, expected_timeout);
+ is_int(expected, r, "TCP, send %s on unconnected socket", expected_msg);
+
+ r = net_stream_recv(sock, buffer, sizeof(buffer), expected_timeout);
+ is_int(expected, r, "TCP, receive %s on unconnected socket", expected_msg);
+
+ close(sock);
+
+ // server termination
+
+ server_stop(&server_ctx);
+ close(server);
+}
+
+static void test_refused(void)
+{
+ int r = -1;
+
+ struct sockaddr_storage addr = { 0 };
+ uint8_t buffer[1] = { 0 };
+ int server, client;
+
+ // listening, not accepting
+
+ addr = addr_local();
+ server = net_bound_socket(SOCK_STREAM, &addr, 0, 0);
+ ok(server >= 0, "server, create server");
+ addr = addr_from_socket(server);
+
+ r = listen(server, LISTEN_BACKLOG);
+ is_int(0, r, "server, start listening");
+
+ client = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
+ ok(client >= 0, "client, connect");
+
+ r = net_stream_send(client, (uint8_t *)"", 1, TIMEOUT);
+ is_int(1, r, "client, successful write");
+
+ r = net_stream_recv(client, buffer, sizeof(buffer), TIMEOUT_SHORT);
+ is_int(KNOT_ETIMEOUT, r, "client, timeout on read");
+
+ close(client);
+
+ // listening, closed immediately
+
+ client = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
+ ok(client >= 0, "client, connect");
+
+ r = close(server);
+ is_int(0, r, "server, close socket");
+ usleep(50000);
+
+ r = net_stream_send(client, (uint8_t *)"", 1, TIMEOUT);
+ is_int(KNOT_ECONN, r, "client, refused on write");
+
+ close(client);
+}
+
+struct dns_handler_ctx {
+ const uint8_t *expected;
+ int len;
+ bool raw;
+ bool success;
+};
+
+static bool _sync(int remote, int send)
+{
+ uint8_t buf[1] = { 0 };
+ int r;
+ if (send) {
+ r = net_stream_send(remote, buf, sizeof(buf), TIMEOUT);
+ } else {
+ r = net_stream_recv(remote, buf, sizeof(buf), TIMEOUT);
+
+ }
+ return r == sizeof(buf);
+}
+
+static bool sync_signal(int remote)
+{
+ return _sync(remote, true);
+}
+
+static bool sync_wait(int remote)
+{
+ return _sync(remote, false);
+}
+
+static void handler_dns(int sock, void *_ctx)
+{
+ struct dns_handler_ctx *ctx = _ctx;
+
+ uint8_t in[16] = { 0 };
+ int in_len = 0;
+
+ sync_signal(sock);
+
+ if (ctx->raw) {
+ in_len = net_stream_recv(sock, in, sizeof(in), TIMEOUT);
+ } else {
+ in_len = net_dns_tcp_recv(sock, in, sizeof(in), TIMEOUT);
+ }
+
+ ctx->success = in_len == ctx->len &&
+ (ctx->len < 0 || memcmp(in, ctx->expected, in_len) == 0);
+}
+
+static void dns_send_hello(int sock)
+{
+ net_dns_tcp_send(sock, (uint8_t *)"wimbgunts", 9, TIMEOUT, NULL);
+}
+
+static void dns_send_fragmented(int sock)
+{
+ struct fragment { const uint8_t *data; size_t len; };
+
+ const struct fragment fragments[] = {
+ { (uint8_t *)"\x00", 1 },
+ { (uint8_t *)"\x08""qu", 3 },
+ { (uint8_t *)"oopisk", 6 },
+ { NULL }
+ };
+
+ for (const struct fragment *f = fragments; f->len > 0; f++) {
+ net_stream_send(sock, f->data, f->len, TIMEOUT);
+ }
+}
+
+static void dns_send_incomplete(int sock)
+{
+ net_stream_send(sock, (uint8_t *)"\x00\x08""korm", 6, TIMEOUT);
+}
+
+static void dns_send_trailing(int sock)
+{
+ net_stream_send(sock, (uint8_t *)"\x00\x05""bloitxx", 9, TIMEOUT);
+}
+
+static void test_dns_tcp(void)
+{
+ struct testcase {
+ const char *name;
+ const uint8_t *expected;
+ size_t expected_len;
+ bool expected_raw;
+ void (*send_callback)(int sock);
+ };
+
+ const struct testcase testcases[] = {
+ { "single DNS", (uint8_t *)"wimbgunts", 9, false, dns_send_hello },
+ { "single RAW", (uint8_t *)"\x00\x09""wimbgunts", 11, true, dns_send_hello },
+ { "fragmented", (uint8_t *)"quoopisk", 8, false, dns_send_fragmented },
+ { "incomplete", NULL, KNOT_ECONN, false, dns_send_incomplete },
+ { "trailing garbage", (uint8_t *)"bloit", 5, false, dns_send_trailing },
+ { NULL }
+ };
+
+ for (const struct testcase *t = testcases; t->name != NULL; t++) {
+ struct dns_handler_ctx handler_ctx = {
+ .expected = t->expected,
+ .len = t->expected_len,
+ .raw = t->expected_raw,
+ .success = false
+ };
+
+ struct sockaddr_storage addr = addr_local();
+ int server = net_bound_socket(SOCK_STREAM, &addr, 0, 0);
+ ok(server >= 0, "%s, server, create socket", t->name);
+
+ int r = listen(server, LISTEN_BACKLOG);
+ is_int(0, r, "%s, server, start listening", t->name);
+
+ server_ctx_t server_ctx = { 0 };
+ r = server_start(&server_ctx, server, SOCK_STREAM, handler_dns, &handler_ctx);
+ ok(r, "%s, server, start handler", t->name);
+
+ addr = addr_from_socket(server);
+ int client = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
+ ok(client >= 0, "%s, client, create connected socket", t->name);
+
+ r = sync_wait(client);
+ ok(r, "%s, client, wait for stream read", t->name);
+ t->send_callback(client);
+
+ close(client);
+ server_stop(&server_ctx);
+ close(server);
+
+ ok(handler_ctx.success, "%s, expected result", t->name);
+ }
+}
+
+static bool socket_is_blocking(int sock)
+{
+ return fcntl(sock, F_GETFL, O_NONBLOCK) == 0;
+}
+
+static void test_nonblocking_mode(int type)
+{
+ const char *name = socktype_name(type);
+ const struct sockaddr_storage addr = addr_local();
+
+ int client = net_unbound_socket(type, &addr);
+ ok(client >= 0, "%s: unbound, create", name);
+ ok(!socket_is_blocking(client), "%s: unbound, nonblocking mode", name);
+ close(client);
+
+ int server = net_bound_socket(type, &addr, 0, 0);
+ ok(server >= 0, "%s: bound, create", name);
+ ok(!socket_is_blocking(server), "%s: bound, nonblocking mode", name);
+
+ if (socktype_is_stream(type)) {
+ int r = listen(server, LISTEN_BACKLOG);
+ is_int(0, r, "%s: bound, start listening", name);
+ }
+
+ struct sockaddr_storage server_addr = addr_from_socket(server);
+ client = net_connected_socket(type, &server_addr, NULL, false);
+ ok(client >= 0, "%s: connected, create", name);
+ ok(!socket_is_blocking(client), "%s: connected, nonblocking mode", name);
+
+ close(client);
+ close(server);
+}
+
+static void test_nonblocking_accept(void)
+{
+ int r;
+
+ // create server
+
+ struct sockaddr_storage addr_server = addr_local();
+
+ int server = net_bound_socket(SOCK_STREAM, &addr_server, 0, 0);
+ ok(server >= 0, "server, create socket");
+
+ r = listen(server, LISTEN_BACKLOG);
+ is_int(0, r, "server, start listening");
+
+ addr_server = addr_from_socket(server);
+
+ // create client
+
+ int client = net_connected_socket(SOCK_STREAM, &addr_server, NULL, false);
+ ok(client >= 0, "client, create connected socket");
+
+ struct sockaddr_storage addr_client = addr_from_socket(client);
+
+ // accept connection
+
+ r = poll_read(server);
+ is_int(1, r, "server, pending connection");
+
+ struct sockaddr_storage addr_accepted = { 0 };
+ int accepted = net_accept(server, &addr_accepted);
+ ok(accepted >= 0, "server, accept connection");
+
+ ok(!socket_is_blocking(accepted), "accepted, nonblocking mode");
+
+ ok(sockaddr_cmp(&addr_client, &addr_accepted, false) == 0,
+ "accepted, correct address");
+
+ close(client);
+
+ // client reconnect
+
+ close(client);
+ client = net_connected_socket(SOCK_STREAM, &addr_server, NULL, false);
+ ok(client >= 0, "client, reconnect");
+
+ r = poll_read(server);
+ is_int(1, r, "server, pending connection");
+
+ accepted = net_accept(server, NULL);
+ ok(accepted >= 0, "server, accept connection (no remote address)");
+
+ ok(!socket_is_blocking(accepted), "accepted, nonblocking mode");
+
+ // cleanup
+
+ close(client);
+ close(server);
+}
+
+static void test_socket_types(void)
+{
+ struct sockaddr_storage addr = addr_local();
+
+ struct testcase {
+ const char *name;
+ int type;
+ bool is_stream;
+ };
+
+ const struct testcase testcases[] = {
+ { "UDP", SOCK_DGRAM, false },
+ { "TCP", SOCK_STREAM, true },
+ { NULL }
+ };
+
+ for (const struct testcase *t = testcases; t->name != NULL; t++) {
+ int sock = net_unbound_socket(t->type, &addr);
+ ok(sock >= 0, "%s, create socket", t->name);
+
+ is_int(t->type, net_socktype(sock), "%s, socket type", t->name);
+
+ ok(net_is_stream(sock) == t->is_stream, "%s, is stream", t->name);
+
+ close(sock);
+ }
+
+ is_int(AF_UNSPEC, net_socktype(-1), "invalid, socket type");
+ ok(!net_is_stream(-1), "invalid, is stream");
+}
+
+static void test_bind_multiple(void)
+{
+ const struct sockaddr_storage addr = addr_local();
+
+ // bind first socket
+
+ int sock_one = net_bound_socket(SOCK_DGRAM, &addr, NET_BIND_MULTIPLE, 0);
+ if (sock_one == KNOT_ENOTSUP) {
+ skip("not supported on this system");
+ return;
+ }
+ ok(sock_one >= 0, "bind first socket");
+
+ // bind second socket to the same address
+
+ const struct sockaddr_storage addr_one = addr_from_socket(sock_one);
+ int sock_two = net_bound_socket(SOCK_DGRAM, &addr_one, NET_BIND_MULTIPLE, 0);
+ ok(sock_two >= 0, "bind second socket");
+
+ // compare sockets
+
+ ok(sock_one != sock_two, "descriptors are different");
+
+ const struct sockaddr_storage addr_two = addr_from_socket(sock_two);
+ ok(sockaddr_cmp(&addr_one, &addr_two, false) == 0,
+ "addresses are the same");
+
+ close(sock_one);
+ close(sock_two);
+}
+
+static void signal_noop(int sig)
+{
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ signal(SIGUSR1, signal_noop);
+
+ diag("nonblocking mode");
+ test_nonblocking_mode(SOCK_DGRAM);
+ test_nonblocking_mode(SOCK_STREAM);
+ test_nonblocking_accept();
+
+ diag("socket types");
+ test_socket_types();
+
+ diag("connected sockets");
+ test_connected(SOCK_DGRAM);
+ test_connected(SOCK_STREAM);
+
+ diag("unconnected sockets");
+ test_unconnected();
+
+ diag("refused connections");
+ test_refused();
+
+ diag("DNS messages over TCP");
+ test_dns_tcp();
+
+ diag("flag NET_BIND_MULTIPLE");
+ test_bind_multiple();
+
+ return 0;
+}
diff --git a/tests/contrib/test_net_shortwrite.c b/tests/contrib/test_net_shortwrite.c
new file mode 100644
index 0000000..f3d4c6e
--- /dev/null
+++ b/tests/contrib/test_net_shortwrite.c
@@ -0,0 +1,151 @@
+/* Copyright (C) 2023 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <tap/basic.h>
+
+#include <fcntl.h>
+#include <stdint.h>
+#include <string.h>
+#include <pthread.h>
+#include <poll.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include "libknot/errcode.h"
+#include "contrib/net.h"
+#include "contrib/sockaddr.h"
+
+const int TIMEOUT = 2000;
+
+static struct sockaddr_storage localhost(void)
+{
+ struct sockaddr_storage addr = { 0 };
+
+ struct addrinfo *res = NULL;
+ if (getaddrinfo(NULL, "0", NULL, &res) == 0) {
+ memcpy(&addr, res->ai_addr, res->ai_addrlen);
+ freeaddrinfo(res);
+ }
+
+ return addr;
+}
+
+struct data {
+ int server_fd;
+ uint8_t *buffer;
+ size_t size;
+ int result;
+};
+
+static void *thr_receive(void *data)
+{
+ struct data *d = data;
+
+ struct pollfd pfd = { .fd = d->server_fd, .events = POLLIN };
+ int r = poll(&pfd, 1, TIMEOUT);
+ if (r != 1) {
+ d->result = KNOT_ETIMEOUT;
+ return NULL;
+ }
+
+ int client = accept(d->server_fd, NULL, NULL);
+ if (client < 0) {
+ d->result = KNOT_ECONN;
+ return NULL;
+ }
+
+ d->result = net_dns_tcp_recv(client, d->buffer, d->size, TIMEOUT);
+
+ close(client);
+
+ return NULL;
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ int r;
+
+ // create TCP server
+
+ struct sockaddr_storage addr = localhost();
+ int server = net_bound_socket(SOCK_STREAM, &addr, 0, 0);
+ ok(server >= 0, "server: bind socket");
+
+ r = listen(server, 1);
+ ok(r == 0, "server: start listening");
+
+ struct sockaddr *sa = (struct sockaddr *)&addr;
+ socklen_t salen = sockaddr_len(&addr);
+ r = getsockname(server, sa, &salen);
+ ok(r == 0, "server: get bound address");
+
+ // create TCP client
+
+ int client = net_connected_socket(SOCK_STREAM, &addr, NULL, false);
+ ok(client >= 0, "client: connect to server");
+
+ int optval = 8192;
+ socklen_t optlen = sizeof(optval);
+ r = setsockopt(client, SOL_SOCKET, SO_SNDBUF, &optval, optlen);
+ ok(r == 0, "client: configure small send buffer");
+
+ // accept TCP connection on the background
+
+ uint8_t recvbuf[UINT16_MAX] = { 0 };
+ struct data recv_data = {
+ .server_fd = server,
+ .buffer = recvbuf,
+ .size = sizeof(recvbuf)
+ };
+
+ pthread_t thr;
+ r = pthread_create(&thr, NULL, thr_receive, &recv_data);
+ ok(r == 0, "server: start receiver thread");
+
+ // send message (should handle partial-write correctly)
+
+ uint8_t sndbuf[UINT16_MAX];
+ for (size_t i = 0; i < sizeof(sndbuf); i++) {
+ sndbuf[i] = i;
+ }
+ r = net_dns_tcp_send(client, sndbuf, sizeof(sndbuf), TIMEOUT, NULL);
+ ok(r == sizeof(sndbuf), "client: net_dns_tcp_send() with short-write");
+
+ // receive message
+
+ r = pthread_join(thr, NULL);
+ ok(r == 0, "server: wait for receiver thread to terminate");
+
+ ok(recv_data.result == sizeof(recvbuf) &&
+ memcmp(sndbuf, recvbuf, sizeof(recvbuf)) == 0,
+ "server: net_dns_tcp_recv() complete and valid data");
+
+ // clean up
+
+ if (server >= 0) {
+ close(server);
+ }
+
+ if (client >= 0) {
+ close(client);
+ }
+
+ return 0;
+}
diff --git a/tests/contrib/test_qp-cow.c b/tests/contrib/test_qp-cow.c
new file mode 100644
index 0000000..4cd8c6c
--- /dev/null
+++ b/tests/contrib/test_qp-cow.c
@@ -0,0 +1,282 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+ Copyright (C) 2018 Tony Finch <dot@dotat.at>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <err.h>
+#include <unistd.h>
+
+#include "contrib/qp-trie/trie.h"
+#include "contrib/string.h"
+#include "libknot/attribute.h"
+#include "libknot/errcode.h"
+#include "tap/basic.h"
+
+/* Constants. */
+#define MAX_KEYLEN 64
+#define MAX_LEAVES 12345
+#define MAX_MUTATIONS 123
+#define MAX_TRANSACTIONS 1234
+
+enum cowstate {
+ cow_absent, // not in trie
+ cow_unmarked,
+ cow_shared,
+ cow_old, // deleted from new trie
+ cow_new, // added to new trie
+ deadbeef,
+};
+
+struct cowleaf {
+ char *key;
+ size_t len;
+ int cowstate;
+};
+
+static inline size_t
+prng(size_t max) {
+ /* good enough these days */
+ return (size_t)rand() % max;
+}
+
+static struct cowleaf *
+grow_leaves(size_t maxlen, size_t leaves)
+{
+ struct cowleaf *leaf = bcalloc(leaves, sizeof(*leaf));
+
+ trie_t *trie = trie_create(NULL);
+ if (!trie) sysbail("trie_create");
+
+ for (size_t i = 0; i < leaves; i++) {
+ trie_val_t *valp;
+ char *str = NULL;
+ size_t len = 0;
+ do {
+ free(str);
+ len = prng(maxlen);
+ str = bmalloc(len + 1);
+ for (size_t j = 0; j < len; j++)
+ str[j] = "0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ [prng(62)];
+ str[len] = '\0';
+ valp = trie_get_ins(trie, (uint8_t *)str, (uint32_t)len);
+ if (!valp) bail("trie_get_ins");
+ } while (*valp != NULL);
+ *valp = &leaf[i];
+ leaf[i].key = str;
+ leaf[i].len = len;
+ leaf[i].cowstate = cow_absent;
+ }
+ trie_free(trie);
+
+ return (leaf);
+}
+
+static void
+dead_leaves(struct cowleaf *leaf, size_t leaves)
+{
+ for (size_t i = 0; i < leaves; i++)
+ free(leaf[i].key);
+ free(leaf);
+}
+
+static void
+mark_cb(trie_val_t val, const uint8_t *key, size_t len, void *d)
+{
+ struct cowleaf *leaf = val;
+ assert(leaf->cowstate == cow_unmarked &&
+ "leaf should go from unmarked to shared exactly once");
+ leaf->cowstate = cow_shared;
+ (void)key;
+ (void)len;
+ (void)d;
+}
+
+static void
+commit_rollback(trie_val_t val, const uint8_t *key, size_t len, void *d)
+{
+ struct cowleaf *leaf = val;
+ int *commit = d;
+ if (*commit)
+ assert((leaf->cowstate == cow_shared ||
+ leaf->cowstate == cow_old) &&
+ "committing deletes from old trie");
+ else
+ assert((leaf->cowstate == cow_shared ||
+ leaf->cowstate == cow_new) &&
+ "roll back deletes from new trie");
+ if (leaf->cowstate != cow_shared)
+ leaf->cowstate = deadbeef;
+ (void)key;
+ (void)len;
+}
+
+static void
+del_cow(trie_cow_t *x, struct cowleaf *leaf)
+{
+ _unused_ trie_val_t val;
+ assert(KNOT_EOK == trie_del_cow(x,
+ (uint8_t *)leaf->key,
+ (uint32_t)leaf->len,
+ &val));
+ assert(val == leaf);
+}
+
+static void
+usage(void) {
+ fprintf(stderr,
+ "usage: test_qp-cow [-k N] [-l N] [-t N]\n"
+ " -k N maximum key length (default %d)\n"
+ " -l N number of leaves (default %d)\n"
+ " -m N mutations per transaction (default %d)\n"
+ " -t N number of transactions (default %d)\n",
+ MAX_KEYLEN,
+ MAX_LEAVES,
+ MAX_MUTATIONS,
+ MAX_TRANSACTIONS);
+ exit(1);
+}
+
+int
+main(int argc, char *argv[])
+{
+ size_t keylen = MAX_KEYLEN;
+ size_t leaves = MAX_LEAVES;
+ int mutations = MAX_MUTATIONS;
+ int transactions = MAX_TRANSACTIONS;
+
+ int opt;
+ while ((opt = getopt(argc, argv, "k:l:m:t:h")) != -1)
+ switch (opt) {
+ case('k'):
+ keylen = (unsigned)atoi(optarg);
+ continue;
+ case('l'):
+ leaves = (unsigned)atoi(optarg);
+ continue;
+ case('m'):
+ mutations = atoi(optarg);
+ continue;
+ case('t'):
+ transactions = atoi(optarg);
+ continue;
+ default:
+ usage();
+ }
+
+ if (argc != optind)
+ usage();
+
+ plan(transactions);
+
+ struct cowleaf *leaf = grow_leaves(keylen, leaves);
+ trie_t *t = trie_create(NULL);
+
+ for (int round = 0; round < transactions; round++) {
+ trie_cow_t *x = trie_cow(t, mark_cb, NULL);
+ if (!x) sysbail("trie_cow");
+
+ int hits = prng(mutations);
+ for (int hit = 0; hit < hits; hit++) {
+ size_t i = prng(leaves);
+ switch (leaf[i].cowstate) {
+ case(cow_absent): {
+ trie_val_t *val =
+ trie_get_cow(x,
+ (uint8_t *)leaf[i].key,
+ (uint32_t)leaf[i].len);
+ if (!val) sysbail("trie_get_cow");
+ assert(*val == NULL && "new leaf");
+ *val = &leaf[i];
+ leaf[i].cowstate = cow_new;
+ } break;
+ case(cow_unmarked): {
+ del_cow(x, &leaf[i]);
+ assert(leaf[i].cowstate == cow_shared &&
+ "state changed unmarked -> shared");
+ leaf[i].cowstate = cow_old;
+ } break;
+ case(cow_shared): {
+ del_cow(x, &leaf[i]);
+ assert(leaf[i].cowstate == cow_shared &&
+ "state remained shared");
+ leaf[i].cowstate = cow_old;
+ } break;
+ case(cow_new): {
+ del_cow(x, &leaf[i]);
+ assert(leaf[i].cowstate == cow_new &&
+ "state remained new");
+ leaf[i].cowstate = cow_absent;
+ } break;
+ case(cow_old): {
+ // don't want to mess with old tree
+ } break;
+ case(deadbeef): {
+ assert(!"deadbeef should not be possible");
+ } break;
+ default:
+ assert(!"bug - unhandled state");
+ }
+ }
+
+ int commit = !prng(2);
+ if (commit)
+ t = trie_cow_commit(x, commit_rollback, &commit);
+ else
+ t = trie_cow_rollback(x, commit_rollback, &commit);
+
+ trie_it_t *it = trie_it_begin(t);
+ while (!trie_it_finished(it)) {
+ trie_val_t *val = trie_it_val(it);
+ assert(val != NULL);
+ struct cowleaf *l = *val;
+ if (commit)
+ assert((l->cowstate == cow_unmarked ||
+ l->cowstate == cow_shared ||
+ l->cowstate == cow_new) &&
+ "committing expected state");
+ else
+ assert((l->cowstate == cow_unmarked ||
+ l->cowstate == cow_shared ||
+ l->cowstate == cow_old) &&
+ "roll back expected state");
+ l->cowstate = cow_unmarked;
+ trie_it_next(it);
+ }
+ trie_it_free(it);
+
+ for (size_t i = 0; i < leaves; i++) {
+ assert((leaf[i].cowstate == cow_unmarked ||
+ leaf[i].cowstate == cow_absent ||
+ leaf[i].cowstate == deadbeef) &&
+ "cleanup leaves either unmarked or dead");
+ if (leaf[i].cowstate == deadbeef)
+ leaf[i].cowstate = cow_absent;
+ }
+ ok(1, "transaction done");
+ }
+
+ trie_free(t);
+ dead_leaves(leaf, leaves);
+
+ return 0;
+}
diff --git a/tests/contrib/test_qp-trie.c b/tests/contrib/test_qp-trie.c
new file mode 100644
index 0000000..a50661f
--- /dev/null
+++ b/tests/contrib/test_qp-trie.c
@@ -0,0 +1,284 @@
+/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <tap/basic.h>
+
+#include "contrib/qp-trie/trie.h"
+#include "contrib/macros.h"
+#include "contrib/string.h"
+#include "libknot/dname.h"
+#include "libknot/errcode.h"
+
+/* UCW array sorting defines. */
+#define ASORT_PREFIX(X) str_key_##X
+#define ASORT_KEY_TYPE char*
+#define ASORT_LT(x, y) (strcmp((x), (y)) < 0)
+#include "contrib/ucw/array-sort.h"
+
+/* Constants. */
+#define KEY_MAXLEN 64
+
+/* Generate random key. */
+static const char *alphabet = "abcdefghijklmn0123456789";
+static char *str_key_rand(size_t len)
+{
+ char *s = malloc(len);
+ memset(s, 0, len);
+ for (unsigned i = 0; i < len - 1; ++i) {
+ s[i] = alphabet[rand() % strlen(alphabet)];
+ }
+ return s;
+}
+
+/* Check lesser or equal result. */
+static bool str_key_get_leq(trie_t *trie, char **keys, size_t i, size_t size)
+{
+ static char key_buf[KEY_MAXLEN];
+
+ int ret = 0;
+ trie_val_t *val = NULL;
+ const char *key = keys[i];
+ size_t key_len = strlen(key) + 1;
+ memcpy(key_buf, key, key_len);
+
+ /* Count equal first keys. */
+ size_t first_key_count = 1;
+ for (size_t k = 1; k < size; ++k) {
+ if (strcmp(keys[0], keys[k]) == 0) {
+ first_key_count += 1;
+ } else {
+ break;
+ }
+ }
+
+ /* Before current key. */
+ key_buf[key_len - 2] -= 1;
+ if (i < first_key_count) {
+ ret = trie_get_leq(trie, (uint8_t *)key_buf, key_len, &val);
+ if (ret != KNOT_ENOENT) {
+ diag("%s: leq for key BEFORE %zu/'%s' ret = %d", __func__, i, keys[i], ret);
+ return false; /* No key before first. */
+ }
+ } else {
+ ret = trie_get_leq(trie, (uint8_t *)key_buf, key_len, &val);
+ if (ret < KNOT_EOK || strcmp(*val, key_buf) > 0) {
+ diag("%s: '%s' is not before the key %zu/'%s'", __func__, (char*)*val, i, keys[i]);
+ return false; /* Found key must be LEQ than searched. */
+ }
+ }
+
+ /* Current key. */
+ key_buf[key_len - 2] += 1;
+ ret = trie_get_leq(trie, (uint8_t *)key_buf, key_len, &val);
+ if (! (ret == KNOT_EOK && val && strcmp(*val, key_buf) == 0)) {
+ diag("%s: leq for key %zu/'%s' ret = %d", __func__, i, keys[i], ret);
+ return false; /* Must find equal match. */
+ }
+
+ /* After the current key. */
+ key_buf[key_len - 2] += 1;
+ ret = trie_get_leq(trie, (uint8_t *)key_buf, key_len, &val);
+ if (! (ret >= KNOT_EOK && strcmp(*val, key_buf) <= 0)) {
+ diag("%s: leq for key AFTER %zu/'%s' ret = %d %s", __func__, i, keys[i], ret, (char*)*val);
+ return false; /* Every key must have its LEQ match. */
+ }
+
+ return true;
+
+}
+
+static void test_wildcards(void)
+{
+ /* Test zone. */
+ const char *names[] = {
+ "*",
+ "example.cz",
+ "*.example.cz",
+ "+.example.cz",
+
+ "*.exampld.cz",
+ "www.exampld.cz",
+ };
+ /* Query-answer pairs for wildcard search. */
+ const char *qa_pairs[][2] = {
+ { ".", NULL },
+ { "*", "*" },
+ { "bar", "*" },
+ { "foo.test.", "*" },
+ { "example.cz", "example.cz" },
+ { "*.example.cz", "*.example.cz" },
+ { "a.example.cz", "*.example.cz" },
+ { "ab.cd.example.cz", "*.example.cz" },
+ { "a+.example.cz", "*.example.cz" },
+ { "+.example.cz", "+.example.cz" },
+ { "exampld.cz", NULL },
+ { ":.exampld.cz", "*.exampld.cz" },
+ { "ww.exampld.cz", "*.exampld.cz" },
+ };
+
+ trie_t *trie = trie_create(NULL);
+ if (!trie) ok(false, "trie: create");
+
+ /* Insert the whole zone. */
+ for (int i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
+ knot_dname_storage_t dname_st, lf_st;
+ const knot_dname_t
+ *dname = knot_dname_from_str(dname_st, names[i], sizeof(dname_st)),
+ *lf = knot_dname_lf(dname, lf_st);
+ if (!dname || !lf) {
+ ok(false, "trie: converting '%s'", names[i]);
+ return;
+ }
+
+ trie_val_t *val = trie_get_ins(trie, lf + 1 , lf[0]);
+ if (!val || *val != NULL) {
+ ok(false, "trie: inserting '%s' (as dname_lf)", names[i]);
+ return;
+ }
+ *val = (void *)names[i];
+ }
+
+ /* Perform each test query. */
+ for (int i = 0; i < sizeof(qa_pairs) / sizeof(qa_pairs[0]); ++i) {
+ knot_dname_storage_t q_dname_st, q_lf_st;
+ const knot_dname_t *q_dname =
+ knot_dname_from_str(q_dname_st, qa_pairs[i][0], sizeof(q_dname_st));
+ const knot_dname_t *q_lf = knot_dname_lf(q_dname, q_lf_st);
+ if (!q_dname || !q_lf) {
+ ok(false, "trie: converting '%s'", qa_pairs[i][0]);
+ return;
+ }
+
+ const char **ans = (const char **)trie_get_try_wildcard(trie, q_lf + 1, q_lf[0]);
+ bool is_ok = !!ans == !!qa_pairs[i][1] && (!ans || !strcmp(*ans, qa_pairs[i][1]));
+ if (!is_ok) {
+ ok(false, "trie: wildcard test for '%s' -> '%s'",
+ qa_pairs[i][0], ans ? *ans : "<null>");
+ return;
+ }
+ }
+
+ trie_free(trie);
+ ok(true, "trie: wildcard searches");
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ /* Random keys. */
+ srand(time(NULL));
+ unsigned key_count = 100000;
+ char **keys = malloc(sizeof(char*) * key_count);
+ /* key must have at least one char and a nul terminator
+ so that the before/after checks have a char to modify */
+ for (unsigned i = 0; i < key_count; ++i) {
+ keys[i] = str_key_rand(rand() % (KEY_MAXLEN - 2) + 2);
+ }
+
+ /* Sort random keys. */
+ str_key_sort(keys, key_count);
+
+ /* Create trie */
+ trie_val_t *val = NULL;
+ trie_t *trie = trie_create(NULL);
+ ok(trie != NULL, "trie: create");
+
+ /* Insert keys */
+ bool passed = true;
+ size_t inserted = 0;
+ for (unsigned i = 0; i < key_count; ++i) {
+ val = trie_get_ins(trie, (uint8_t *)keys[i], strlen(keys[i]) + 1);
+ if (!val) {
+ passed = false;
+ break;
+ }
+ if (*val == NULL) {
+ *val = keys[i];
+ ++inserted;
+ }
+ }
+ ok(passed, "trie: insert");
+
+ /* Check total insertions against trie weight. */
+ is_int(trie_weight(trie), inserted, "trie: trie weight matches insertions");
+
+ /* Lookup all keys */
+ passed = true;
+ for (unsigned i = 0; i < key_count; ++i) {
+ val = trie_get_try(trie, (uint8_t *)keys[i], strlen(keys[i]) + 1);
+ if (val && (*val == keys[i] || strcmp(*val, keys[i]) == 0)) {
+ continue;
+ } else {
+ diag("trie: mismatch on element '%u'", i);
+ passed = false;
+ break;
+ }
+ }
+ ok(passed, "trie: lookup all keys");
+
+ /* Lesser or equal lookup. */
+ passed = true;
+ for (unsigned i = 0; i < key_count; ++i) {
+ if (!str_key_get_leq(trie, keys, i, key_count)) {
+ passed = false;
+ for (int off = -10; off < 10; ++off) {
+ int k = (int)i + off;
+ if (k < 0 || k >= key_count) {
+ continue;
+ }
+ diag("[%u/%d]: %s%s", i, off, off == 0?">":"",keys[k]);
+ }
+ break;
+ }
+ }
+ ok(passed, "trie: find lesser or equal for all keys");
+
+ /* Sorted iteration. */
+ char key_buf[KEY_MAXLEN] = {'\0'};
+ size_t iterated = 0;
+ trie_it_t *it = trie_it_begin(trie);
+ while (!trie_it_finished(it)) {
+ size_t cur_key_len = 0;
+ const char *cur_key = (const char *)trie_it_key(it, &cur_key_len);
+ if (iterated > 0) { /* Only if previous exists. */
+ if (strcmp(key_buf, cur_key) > 0) {
+ diag("'%s' <= '%s' FAIL\n", key_buf, cur_key);
+ break;
+ }
+ }
+ ++iterated;
+ memcpy(key_buf, cur_key, cur_key_len);
+ trie_it_next(it);
+ }
+ is_int(inserted, iterated, "trie: sorted iteration");
+ trie_it_free(it);
+
+ /* Cleanup */
+ for (unsigned i = 0; i < key_count; ++i) {
+ free(keys[i]);
+ }
+ free(keys);
+ trie_free(trie);
+
+ /* Test trie_get_try_wildcard(). */
+ test_wildcards();
+
+ return 0;
+}
diff --git a/tests/contrib/test_siphash.c b/tests/contrib/test_siphash.c
new file mode 100644
index 0000000..1ca019b
--- /dev/null
+++ b/tests/contrib/test_siphash.c
@@ -0,0 +1,135 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <tap/basic.h>
+
+// Prevent possible linking with a system SiphHash (OpenBSD).
+#include "contrib/openbsd/siphash.c"
+
+// https://github.com/veorq/SipHash
+const uint8_t vectors24[64][8] = {
+ { 0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72 },
+ { 0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74 },
+ { 0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d },
+ { 0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85 },
+ { 0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf },
+ { 0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18 },
+ { 0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb },
+ { 0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab },
+ { 0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93 },
+ { 0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e },
+ { 0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a },
+ { 0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4 },
+ { 0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75 },
+ { 0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14 },
+ { 0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7 },
+ { 0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1 },
+ { 0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f },
+ { 0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69 },
+ { 0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b },
+ { 0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb },
+ { 0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe },
+ { 0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0 },
+ { 0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93 },
+ { 0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8 },
+ { 0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8 },
+ { 0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc },
+ { 0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17 },
+ { 0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f },
+ { 0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde },
+ { 0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6 },
+ { 0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad },
+ { 0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32 },
+ { 0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71 },
+ { 0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7 },
+ { 0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12 },
+ { 0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15 },
+ { 0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31 },
+ { 0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02 },
+ { 0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca },
+ { 0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a },
+ { 0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e },
+ { 0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad },
+ { 0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18 },
+ { 0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4 },
+ { 0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9 },
+ { 0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9 },
+ { 0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb },
+ { 0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0 },
+ { 0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6 },
+ { 0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7 },
+ { 0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee },
+ { 0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1 },
+ { 0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a },
+ { 0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81 },
+ { 0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f },
+ { 0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24 },
+ { 0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7 },
+ { 0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea },
+ { 0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60 },
+ { 0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66 },
+ { 0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c },
+ { 0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f },
+ { 0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5 },
+ { 0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95 },
+};
+
+static void block_test(SIPHASH_KEY *key, uint8_t *data, int data_len, int block_len)
+{
+ int count = data_len / block_len;
+ int rest = data_len % block_len;
+
+ SIPHASH_CTX ctx;
+ SipHash24_Init(&ctx, key);
+
+ for (int i = 0; i < count; i++) {
+ SipHash24_Update(&ctx, data + i * block_len, block_len);
+ }
+ SipHash24_Update(&ctx, data + count * block_len, rest);
+
+ uint64_t hash = SipHash24_End(&ctx);
+ ok(memcmp(&hash, vectors24[data_len], sizeof(uint64_t)) == 0,
+ "siphash24: %i-byte block updates", block_len);
+}
+
+int main(void)
+{
+ plan_lazy();
+
+ SIPHASH_KEY key;
+ memcpy(&key.k0, "\x00\x01\x02\x03\x04\x05\x06\x07", sizeof(uint64_t));
+ memcpy(&key.k1, "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", sizeof(uint64_t));
+
+ uint8_t data[64];
+ for (int i = 0; i < sizeof(data); i++) {
+ data[i] = i;
+ }
+
+ for (int data_len = 0; data_len < sizeof(data); data_len++) {
+ diag("data length %i", data_len);
+
+ uint64_t hash = SipHash24(&key, data, data_len);
+ ok(memcmp(&hash, vectors24[data_len], sizeof(uint64_t)) == 0,
+ "siphash24: 1-block update");
+
+ for (int block_len = 1; block_len <= 8; block_len++) {
+ block_test(&key, data, data_len, block_len);
+ }
+ }
+
+ return 0;
+}
diff --git a/tests/contrib/test_sockaddr.c b/tests/contrib/test_sockaddr.c
new file mode 100644
index 0000000..87d2d77
--- /dev/null
+++ b/tests/contrib/test_sockaddr.c
@@ -0,0 +1,229 @@
+/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <tap/basic.h>
+
+#include "contrib/sockaddr.h"
+#include "libknot/errcode.h"
+
+static void test_sockaddr_is_any(void)
+{
+ struct sockaddr_storage invalid = { 0 };
+ ok(!sockaddr_is_any(&invalid), "sockaddr_is_any: invalid");
+
+ struct sockaddr_storage path = { 0 };
+ path.ss_family = AF_UNIX;
+ ok(!sockaddr_is_any(&path), "sockaddr_is_any: unix");
+
+ struct sockaddr_storage ipv4_local = { 0 };
+ sockaddr_set(&ipv4_local, AF_INET, "127.0.0.1", 0);
+ ok(!sockaddr_is_any(&ipv4_local), "sockaddr_is_any: IPv4 local");
+
+ struct sockaddr_storage ipv4_any = { 0 };
+ sockaddr_set(&ipv4_any, AF_INET, "0.0.0.0", 0);
+ ok(sockaddr_is_any(&ipv4_any), "sockaddr_is_any: IPv4 any");
+
+ struct sockaddr_storage ipv6_local = { 0 };
+ sockaddr_set(&ipv6_local, AF_INET6, "::1", 0);
+ ok(!sockaddr_is_any(&ipv6_local), "sockaddr_is_any: IPv6 local");
+
+ struct sockaddr_storage ipv6_any = { 0 };
+ sockaddr_set(&ipv6_any, AF_INET6, "::", 0);
+ ok(sockaddr_is_any(&ipv6_any), "sockaddr_is_any: IPv6 any");
+}
+
+static void check_sockaddr_set(struct sockaddr_storage *ss, int family,
+ const char *straddr, int port)
+{
+ int ret = sockaddr_set(ss, family, straddr, port);
+ is_int(KNOT_EOK, ret, "set address '%s'", straddr);
+}
+
+static void test_net_match(void)
+{
+ int ret;
+ struct sockaddr_storage t = { 0 };
+
+ // 127 dec ~ 01111111 bin
+ // 170 dec ~ 10101010 bin
+ struct sockaddr_storage ref4 = { 0 };
+ check_sockaddr_set(&ref4, AF_INET, "127.170.170.127", 0);
+
+ // 7F hex ~ 01111111 bin
+ // AA hex ~ 10101010 bin
+ struct sockaddr_storage ref6 = { 0 };
+ check_sockaddr_set(&ref6, AF_INET6, "7FAA::AA7F", 0);
+
+ ret = sockaddr_net_match(&ref4, &ref6, 32);
+ ok(ret == false, "match: family mismatch");
+
+ ret = sockaddr_net_match(NULL, &ref4, 32);
+ ok(ret == false, "match: NULL first parameter");
+ ret = sockaddr_net_match(&ref4, NULL, 32);
+ ok(ret == false, "match: NULL second parameter");
+
+ ret = sockaddr_net_match(&ref4, &ref4, -1);
+ ok(ret == true, "match: ipv4 - identity, auto full prefix");
+ ret = sockaddr_net_match(&ref4, &ref4, 31);
+ ok(ret == true, "match: ipv4 - identity, subnet");
+ ret = sockaddr_net_match(&ref4, &ref4, 32);
+ ok(ret == true, "match: ipv4 - identity, full prefix");
+ ret = sockaddr_net_match(&ref4, &ref4, 33);
+ ok(ret == true, "match: ipv4 - identity, prefix overflow");
+
+ ret = sockaddr_net_match(&ref6, &ref6, -1);
+ ok(ret == true, "match: ipv6 - identity, auto full prefix");
+ ret = sockaddr_net_match(&ref6, &ref6, 127);
+ ok(ret == true, "match: ipv6 - identity, subnet");
+ ret = sockaddr_net_match(&ref6, &ref6, 128);
+ ok(ret == true, "match: ipv6 - identity, full prefix");
+ ret = sockaddr_net_match(&ref6, &ref6, 129);
+ ok(ret == true, "match: ipv6 - identity, prefix overflow");
+
+ // 124 dec ~ 01111100 bin
+ check_sockaddr_set(&t, AF_INET, "124.0.0.0", 0);
+ ret = sockaddr_net_match(&t, &ref4, 5);
+ ok(ret == true, "match: ipv4 - first byte, shorter prefix");
+ ret = sockaddr_net_match(&t, &ref4, 6);
+ ok(ret == true, "match: ipv4 - first byte, precise prefix");
+ ret = sockaddr_net_match(&t, &ref4, 7);
+ ok(ret == false, "match: ipv4 - first byte, not match");
+
+ check_sockaddr_set(&t, AF_INET, "127.170.170.124", 0);
+ ret = sockaddr_net_match(&t, &ref4, 29);
+ ok(ret == true, "match: ipv4 - last byte, shorter prefix");
+ ret = sockaddr_net_match(&t, &ref4, 30);
+ ok(ret == true, "match: ipv4 - last byte, precise prefix");
+ ret = sockaddr_net_match(&t, &ref4, 31);
+ ok(ret == false, "match: ipv4 - last byte, not match");
+
+ // 7C hex ~ 01111100 bin
+ check_sockaddr_set(&t, AF_INET6, "7CAA::", 0);
+ ret = sockaddr_net_match(&t, &ref6, 5);
+ ok(ret == true, "match: ipv6 - first byte, shorter prefix");
+ ret = sockaddr_net_match(&t, &ref6, 6);
+ ok(ret == true, "match: ipv6 - first byte, precise prefix");
+ ret = sockaddr_net_match(&t, &ref6, 7);
+ ok(ret == false, "match: ipv6 - first byte, not match");
+
+ check_sockaddr_set(&t, AF_INET6, "7FAA::AA7C", 0);
+ ret = sockaddr_net_match(&t, &ref6, 125);
+ ok(ret == true, "match: ipv6 - last byte, shorter prefix");
+ ret = sockaddr_net_match(&t, &ref6, 126);
+ ok(ret == true, "match: ipv6 - last byte, precise prefix");
+ ret = sockaddr_net_match(&t, &ref6, 127);
+ ok(ret == false, "match: ipv6 - last byte, not match");
+}
+
+static void test_range_match(void)
+{
+ bool ret;
+ struct sockaddr_storage t = { 0 };
+ struct sockaddr_storage min = { 0 };
+ struct sockaddr_storage max = { 0 };
+
+ // IPv4 tests.
+
+ check_sockaddr_set(&min, AF_INET, "0.0.0.0", 0);
+ check_sockaddr_set(&max, AF_INET, "255.255.255.255", 0);
+
+ check_sockaddr_set(&t, AF_INET, "0.0.0.0", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv4 max range - minimum");
+ check_sockaddr_set(&t, AF_INET, "255.255.255.255", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv4 max range - maximum");
+
+ check_sockaddr_set(&min, AF_INET, "1.13.113.213", 0);
+ check_sockaddr_set(&max, AF_INET, "2.24.124.224", 0);
+
+ check_sockaddr_set(&t, AF_INET, "1.12.113.213", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == false, "match: ipv4 middle range - negative far min");
+ check_sockaddr_set(&t, AF_INET, "1.13.113.212", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == false, "match: ipv4 middle range - negative close min");
+ check_sockaddr_set(&t, AF_INET, "1.13.113.213", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv4 middle range - minimum");
+ check_sockaddr_set(&t, AF_INET, "1.13.213.213", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv4 middle range - middle");
+ check_sockaddr_set(&t, AF_INET, "2.24.124.224", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv4 middle range - max");
+ check_sockaddr_set(&t, AF_INET, "2.24.124.225", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == false, "match: ipv4 middle range - negative close max");
+ check_sockaddr_set(&t, AF_INET, "2.25.124.225", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == false, "match: ipv4 middle range - negative far max");
+
+ // IPv6 tests.
+
+ check_sockaddr_set(&min, AF_INET6, "::0", 0);
+ check_sockaddr_set(&max, AF_INET6,
+ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", 0);
+
+ check_sockaddr_set(&t, AF_INET6, "::0", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv6 max range - minimum");
+ check_sockaddr_set(&t, AF_INET6,
+ "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv6 max range - maximum");
+
+ check_sockaddr_set(&min, AF_INET6, "1:13::ABCD:200B", 0);
+ check_sockaddr_set(&max, AF_INET6, "2:A24::124:224", 0);
+
+ check_sockaddr_set(&t, AF_INET6, "1:12::BCD:2000", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == false, "match: ipv6 middle range - negative far min");
+ check_sockaddr_set(&t, AF_INET6, "1:13::ABCD:200A", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == false, "match: ipv6 middle range - negative close min");
+ check_sockaddr_set(&t, AF_INET6, "1:13::ABCD:200B", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv6 middle range - minimum");
+ check_sockaddr_set(&t, AF_INET6, "1:13:0:12:34:0:ABCD:200B", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv6 middle range - middle");
+ check_sockaddr_set(&t, AF_INET6, "2:A24::124:224", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == true, "match: ipv6 middle range - max");
+ check_sockaddr_set(&t, AF_INET6, "2:A24::124:225", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == false, "match: ipv6 middle range - negative close max");
+ check_sockaddr_set(&t, AF_INET6, "2:FA24::4:24", 0);
+ ret = sockaddr_range_match(&t, &min, &max);
+ ok(ret == false, "match: ipv6 middle range - negative far max");
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ diag("sockaddr_is_any");
+ test_sockaddr_is_any();
+
+ diag("sockaddr_net_match");
+ test_net_match();
+
+ diag("sockaddr_range_match");
+ test_range_match();
+
+ return 0;
+}
diff --git a/tests/contrib/test_spinlock.c b/tests/contrib/test_spinlock.c
new file mode 100644
index 0000000..0d5122b
--- /dev/null
+++ b/tests/contrib/test_spinlock.c
@@ -0,0 +1,78 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <signal.h>
+#include <tap/basic.h>
+
+#include "knot/server/dthreads.h"
+#include "contrib/spinlock.h"
+
+#define THREADS 8
+#define CYCLES 100000
+
+static volatile int counter = 0;
+static volatile int tens_counter = 0;
+static knot_spin_t spinlock;
+
+static int thread(struct dthread *thread)
+{
+ volatile int i, j, k;
+
+ for (i = 0; i < CYCLES; i++) {
+ knot_spin_lock(&spinlock);
+ j = counter;
+ k = tens_counter;
+ if (++j % 10 == 0) {
+ k++;
+ }
+ tens_counter = k;
+ counter = j;
+ knot_spin_unlock(&spinlock);
+ }
+
+ return 0;
+}
+
+// Signal handler
+static void interrupt_handle(int s)
+{
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ // Register service and signal handler
+ struct sigaction sa;
+ sa.sa_handler = interrupt_handle;
+ sigemptyset(&sa.sa_mask);
+ sa.sa_flags = 0;
+ sigaction(SIGALRM, &sa, NULL); // Interrupt
+
+ knot_spin_init(&spinlock);
+
+ dt_unit_t *unit = dt_create(THREADS, thread, NULL, NULL);
+ dt_start(unit);
+ dt_join(unit);
+ dt_delete(&unit);
+
+ knot_spin_destroy(&spinlock);
+
+ is_int(THREADS * CYCLES, counter, "spinlock: protected counter one");
+ is_int(THREADS * CYCLES / 10, tens_counter, "spinlock: protected counter two");
+
+ return 0;
+}
diff --git a/tests/contrib/test_string.c b/tests/contrib/test_string.c
new file mode 100644
index 0000000..681dd61
--- /dev/null
+++ b/tests/contrib/test_string.c
@@ -0,0 +1,59 @@
+/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <tap/basic.h>
+
+#include <stdlib.h>
+
+#include "contrib/string.h"
+
+static void test_strstrip(void)
+{
+ char *c = NULL;
+
+ c = strstrip("hello");
+ is_string("hello", c, "strstrip: no whitespace");
+ free(c);
+
+ c = strstrip("world \n");
+ is_string("world", c, "strstrip: trailing whitespace");
+ free(c);
+
+ c = strstrip(" \n banana");
+ is_string("banana", c, "strstrip: leading whitespace");
+ free(c);
+
+ c = strstrip(" \t hello world \n");
+ is_string("hello world", c, "strstrip: leading and trailing");
+ free(c);
+
+ c = strstrip("");
+ is_string("", c, "strstrip: empty string");
+ free(c);
+
+ c = strstrip(" ");
+ is_string("", c, "strstrip: just whitespaces");
+ free(c);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ test_strstrip();
+
+ return 0;
+}
diff --git a/tests/contrib/test_strtonum.c b/tests/contrib/test_strtonum.c
new file mode 100644
index 0000000..e883575
--- /dev/null
+++ b/tests/contrib/test_strtonum.c
@@ -0,0 +1,156 @@
+/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <tap/basic.h>
+
+#include "contrib/strtonum.h"
+#include "libdnssec/error.h"
+#include "libknot/attribute.h"
+
+static void test_u8(const char *in, uint8_t expected, int errcode)
+{
+ uint8_t out = 0x11;
+ assert(expected != out);
+
+ ok(str_to_u8(in, &out) == errcode &&
+ (errcode != KNOT_EOK || out == expected),
+ "str_to_u8 %s on \"%s\"",
+ (errcode == KNOT_EOK ? "succeeds" : "fails"), in);
+}
+
+static void test_u16(const char *in, uint16_t expected, int errcode)
+{
+ uint16_t out = 0x0101;
+ assert(expected != out);
+
+ ok(str_to_u16(in, &out) == errcode &&
+ (errcode != KNOT_EOK || out == expected),
+ "str_to_u16 %s on \"%s\"",
+ (errcode == KNOT_EOK ? "succeeds" : "fails"), in);
+}
+
+static void test_u32(const char *in, uint32_t expected, int errcode)
+{
+ uint32_t out = 0x010101;
+ assert(expected != out);
+
+ ok(str_to_u32(in, &out) == errcode &&
+ (errcode != KNOT_EOK || out == expected),
+ "str_to_u32 %s on \"%s\"",
+ (errcode == KNOT_EOK ? "succeeds" : "fails"), in);
+}
+
+static void test_int(const char *in, int expected, int errcode)
+{
+ int out = 12345;
+ assert(expected != out);
+
+ ok(str_to_int(in, &out, INT_MIN, INT_MAX) == errcode &&
+ (errcode != KNOT_EOK || out == expected),
+ "str_to_int %s on \"%s\"",
+ (errcode == KNOT_EOK ? "succeeds" : "fails"), in);
+}
+
+static void test_size(const char *in, size_t expected, size_t min, size_t max,
+ int errcode)
+{
+ size_t out = 12345;
+ assert(expected != out);
+
+ ok(str_to_size(in, &out, min, max) == errcode &&
+ (errcode != KNOT_EOK || out == expected),
+ "str_to_int %s on \"%s\"",
+ (errcode == KNOT_EOK ? "succeeds" : "fails"), in);
+}
+
+#define asprintf(args, ...) do { \
+ _unused_ int r = (asprintf)(args, ##__VA_ARGS__); assert(r >= 0); \
+} while (0);
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ test_u8("-1", 0, KNOT_EINVAL);
+ test_u8("256", 0, KNOT_ERANGE);
+ test_u8("0x1", 0, KNOT_EINVAL);
+ test_u8(" 1", 0, KNOT_EINVAL);
+ test_u8("1 ", 0, KNOT_EINVAL);
+ test_u8("0", 0, KNOT_EOK);
+ test_u8("42", 42, KNOT_EOK);
+ test_u8("+84", 84, KNOT_EOK);
+ test_u8("255", UINT8_MAX, KNOT_EOK);
+
+ test_u16("-1", 0, KNOT_EINVAL);
+ test_u16("65536", 0, KNOT_ERANGE);
+ test_u16("0x1", 0, KNOT_EINVAL);
+ test_u16(" 1", 0, KNOT_EINVAL);
+ test_u16("1 ", 0, KNOT_EINVAL);
+ test_u16("0", 0, KNOT_EOK);
+ test_u16("65280", 65280, KNOT_EOK);
+ test_u16("+256", 256, KNOT_EOK);
+ test_u16("65535", UINT16_MAX, KNOT_EOK);
+
+ test_u32("-1", 0, KNOT_EINVAL);
+ test_u32("4294967296", 0, KNOT_ERANGE);
+ test_u32("0x1", 0, KNOT_EINVAL);
+ test_u32(" 1", 0, KNOT_EINVAL);
+ test_u32("1 ", 0, KNOT_EINVAL);
+ test_u32("0", 0, KNOT_EOK);
+ test_u32("65280", 65280, KNOT_EOK);
+ test_u32("+256", 256, KNOT_EOK);
+ test_u32("4294967295", UINT32_MAX, KNOT_EOK);
+
+ test_size("-1", 0, 0, 1, KNOT_EINVAL);
+ test_size("4294967296", 0, 0, 1, KNOT_ERANGE);
+ test_size("0", 0, 1, 2, KNOT_ERANGE);
+ test_size("0x1", 0, 0, 1, KNOT_EINVAL);
+ test_size(" 1", 0, 0, 1, KNOT_EINVAL);
+ test_size("1 ", 0, 0, 1, KNOT_EINVAL);
+ test_size("0", 0, 0, 1, KNOT_EOK);
+ test_size("65280", 65280, 0, 65280, KNOT_EOK);
+ test_size("+256", 256, 0, 65280, KNOT_EOK);
+
+ char *int_under = NULL;
+ asprintf(&int_under, "%lld", (long long)INT_MIN - 1);
+ char *int_min = NULL;
+ asprintf(&int_min, "%lld", (long long)INT_MIN);
+ char *int_max = NULL;
+ asprintf(&int_max, "%lld", (long long)INT_MAX);
+ char *int_over = NULL;
+ asprintf(&int_over, "%lld", (long long)INT_MAX + 1);
+
+ test_int(int_under, 0, KNOT_ERANGE);
+ test_int(int_over, 0, KNOT_ERANGE);
+ test_int("0x1", 0, KNOT_EINVAL);
+ test_int(" 1", 0, KNOT_EINVAL);
+ test_int("1 ", 0, KNOT_EINVAL);
+ test_int(int_min, INT_MIN, KNOT_EOK);
+ test_int("0", 0, KNOT_EOK);
+ test_int("268435459", 268435459, KNOT_EOK);
+ test_int("+1073741827", 1073741827, KNOT_EOK);
+ test_int(int_max, INT_MAX, KNOT_EOK);
+
+ free(int_under);
+ free(int_min);
+ free(int_max);
+ free(int_over);
+
+ return 0;
+}
diff --git a/tests/contrib/test_time.c b/tests/contrib/test_time.c
new file mode 100644
index 0000000..84518f2
--- /dev/null
+++ b/tests/contrib/test_time.c
@@ -0,0 +1,203 @@
+/* Copyright (C) 2017 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <tap/basic.h>
+
+#include <string.h>
+
+#include "contrib/time.h"
+
+static void test_now(void)
+{
+ struct timespec t = time_now();
+ ok(t.tv_sec != 0, "time_now() returns something");
+}
+
+static void test_diff(void)
+{
+ struct timespec t1 = { 10, 1000 };
+ struct timespec t2 = { 50, 1500 };
+ struct timespec t3 = { 70, 500 };
+
+ struct timespec res;
+
+ res = time_diff(&t1, &t2);
+ ok(res.tv_sec == 40 && res.tv_nsec == 500, "time_diff()");
+
+ res = time_diff(&t2, &t3);
+ ok(res.tv_sec == 19 && res.tv_nsec == 999999000, "time_diff() ns overflow");
+
+ res = time_diff(&t3, &t1);
+ ok(res.tv_sec == -60 && res.tv_nsec == 500, "time_diff() negative");
+
+ res = time_diff(&t2, &t1);
+ ok(res.tv_sec == -41 && res.tv_nsec == 999999500, "time_diff() negative");
+}
+
+static void test_diff_ms(void)
+{
+ struct timespec t1 = { 10, 1000 };
+ struct timespec t2 = { 50, 500 };
+
+ float ms = 0.0;
+
+ ms = time_diff_ms(&t1, &t2);
+ ok(39990.0 < ms && ms < 40010.0, "time_diff_ms()");
+
+ ms = time_diff_ms(&t2, &t1);
+ ok(-40010.0 < ms && ms < -39990.0, "time_diff_ms() negative");
+}
+
+static void test_knot_time(void)
+{
+ knot_time_t a = knot_time();
+ knot_time_t inf = 0;
+ knot_time_t c;
+ knot_timediff_t d;
+ int ret;
+
+ ok(a != 0, "knot time not zero");
+
+ ret = knot_time_cmp(a, a);
+ ok(ret == 0, "compare same times");
+
+ ret = knot_time_cmp(a - 1, a + 1);
+ ok(ret == -1, "compare smaller time");
+
+ ret = knot_time_cmp(a + 10, a - 10);
+ ok(ret == 1, "compare bigger time");
+
+ ret = knot_time_cmp(inf, inf);
+ ok(ret == 0, "compare two infinities");
+
+ ret = knot_time_cmp(a, inf);
+ ok(ret == -1, "compare time and infinity");
+
+ ret = knot_time_cmp(inf, a);
+ ok(ret == 1, "compare infinity and time");
+
+ c = knot_time_min(a, a);
+ ok(c == a, "take same time");
+
+ c = knot_time_min(a, a + 1);
+ ok(c == a, "take first smaller");
+
+ c = knot_time_min(a + 1, a);
+ ok(c == a, "take second smaller");
+
+ c = knot_time_min(inf, inf);
+ ok(c == inf, "take same infinity");
+
+ c = knot_time_min(a, inf);
+ ok(c == a, "take first finite");
+
+ c = knot_time_min(inf, a);
+ ok(c == a, "take second finite");
+
+ d = knot_time_diff(a + 1, a);
+ ok(d == 1, "positive diff");
+
+ d = knot_time_diff(a, a + 1);
+ ok(d == -1, "negative diff");
+
+ d = knot_time_diff(inf, inf);
+ ok(d == KNOT_TIMEDIFF_MAX, "positive double infinity diff");
+
+ d = knot_time_diff(inf, a);
+ ok(d == KNOT_TIMEDIFF_MAX, "positive infinity diff");
+
+ d = knot_time_diff(a, inf);
+ ok(d == KNOT_TIMEDIFF_MIN, "negative infinity diff");
+}
+
+static void test_time_parse_expect(int ret, knot_time_t res,
+ knot_time_t expected, const char *msg)
+{
+ ok(ret == 0, "time_parse %s ok", msg);
+ ok(res == expected, "time_parse %s result", msg);
+}
+
+static void test_time_parse(void)
+{
+ knot_time_t res;
+ int ret;
+
+ ret = knot_time_parse("", "", &res);
+ test_time_parse_expect(ret, res, 0, "nihilist");
+
+ ret = knot_time_parse("#", "12345", &res);
+ test_time_parse_expect(ret, res, 12345, "unix");
+
+ ret = knot_time_parse("+-#U", "-1h", &res);
+ test_time_parse_expect(ret, res, knot_time() - 3600, "hour");
+
+ ret = knot_time_parse("+-#u'nths'|+-#u'nutes'", "+1minutes", &res);
+ test_time_parse_expect(ret, res, knot_time() + 60, "minute");
+}
+
+static void test_time_print_expect(int ret, const char *res, int res_len,
+ const char *expected, const char *msg)
+{
+ ok(ret == 0, "time_print %s ok", msg);
+ ok(strncmp(res, expected, res_len) == 0, "time_print %s result", msg);
+}
+
+static void test_time_print(void)
+{
+ char buff[100];
+ int bufl = sizeof(buff);
+ int ret;
+ knot_time_t t = 44000, t2, big;
+
+ ret = knot_time_print(TIME_PRINT_UNIX, t, buff, bufl);
+ test_time_print_expect(ret, buff, bufl, "44000", "unix");
+
+ t2 = knot_time_add(knot_time(), -10000);
+ ret = knot_time_print(TIME_PRINT_RELSEC, t2, buff, bufl);
+ test_time_print_expect(ret, buff, bufl, "-10000", "relsec");
+
+ ret = knot_time_print(TIME_PRINT_ISO8601, t, buff, bufl);
+ buff[11] = '0', buff[12] = '0'; // zeroing 'hours' field to avoid locality issues
+ test_time_print_expect(ret, buff, bufl, "1970-01-01T00:13:20Z", "iso");
+
+ t2 = knot_time_add(knot_time(), -10000);
+ ret = knot_time_print(TIME_PRINT_HUMAN_MIXED, t2, buff, bufl);
+ test_time_print_expect(ret, buff, bufl, "-2h46m40s", "negative human mixed");
+ big = knot_time_add(knot_time(), 2 * 365 * 24 * 3600 + 1);
+ ret = knot_time_print(TIME_PRINT_HUMAN_MIXED, big, buff, bufl);
+ test_time_print_expect(ret, buff, bufl, "+2Y1s", "big human mixed");
+
+ t2 = knot_time_add(knot_time(), -10000);
+ ret = knot_time_print(TIME_PRINT_HUMAN_LOWER, t2, buff, bufl);
+ test_time_print_expect(ret, buff, bufl, "-2h46mi40s", "negative human lower");
+ big = knot_time_add(knot_time(), 2 * 365 * 24 * 3600 + 1);
+ ret = knot_time_print(TIME_PRINT_HUMAN_LOWER, big, buff, bufl);
+ test_time_print_expect(ret, buff, bufl, "+2y1s", "big human lower");
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ test_now();
+ test_diff();
+ test_diff_ms();
+ test_knot_time();
+ test_time_parse();
+ test_time_print();
+
+ return 0;
+}
diff --git a/tests/contrib/test_toeplitz.c b/tests/contrib/test_toeplitz.c
new file mode 100644
index 0000000..244137c
--- /dev/null
+++ b/tests/contrib/test_toeplitz.c
@@ -0,0 +1,93 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <tap/basic.h>
+
+#include "contrib/toeplitz.h"
+#include "contrib/wire_ctx.h"
+
+// Test vectors come from Intel Ethernet Controller X710/XXV710/XL710 Series Datasheet
+const uint8_t key[] = {
+ 0x6d, 0x5a, 0x56, 0xda, 0x25, 0x5b, 0x0e, 0xc2,
+ 0x41, 0x67, 0x25, 0x3d, 0x43, 0xa3, 0x8f, 0xb0,
+ 0xd0, 0xca, 0x2b, 0xcb, 0xae, 0x7b, 0x30, 0xb4,
+ 0x77, 0xcb, 0x2d, 0xa3, 0x80, 0x30, 0xf2, 0x0c,
+ 0x6a, 0x42, 0xb7, 0x3b, 0xbe, 0xac, 0x01, 0xfa,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+};
+
+void toeplitz_check(int family, const char *src_ip, const char *dst_ip,
+ uint16_t src_port, uint16_t dst_port, uint32_t expected)
+{
+ uint8_t data[2 * sizeof(struct in6_addr) + 2 * sizeof(uint16_t)];
+
+ wire_ctx_t ctx = wire_ctx_init(data, sizeof(data));
+
+ struct in_addr src_addr4, dst_addr4;
+ struct in6_addr src_addr6, dst_addr6;
+
+ if (family == AF_INET &&
+ inet_pton(AF_INET, src_ip, &src_addr4) == 1 &&
+ inet_pton(AF_INET, dst_ip, &dst_addr4) == 1) {
+ wire_ctx_write(&ctx, (uint8_t *)&(src_addr4.s_addr), sizeof(struct in_addr));
+ wire_ctx_write(&ctx, (uint8_t *)&(dst_addr4.s_addr), sizeof(struct in_addr));
+ } else if (family == AF_INET6 &&
+ inet_pton(AF_INET6, src_ip, &src_addr6) == 1 &&
+ inet_pton(AF_INET6, dst_ip, &dst_addr6) == 1) {
+ wire_ctx_write(&ctx, (uint8_t *)&(src_addr6.s6_addr), sizeof(struct in6_addr));
+ wire_ctx_write(&ctx, (uint8_t *)&(dst_addr6.s6_addr), sizeof(struct in6_addr));
+ } else {
+ assert(0);
+ }
+
+ wire_ctx_write_u16(&ctx, src_port);
+ wire_ctx_write_u16(&ctx, dst_port);
+
+ if (ctx.error != KNOT_EOK) {
+ assert(0);
+ }
+
+ uint32_t value = toeplitz_hash(key, sizeof(key), data, wire_ctx_offset(&ctx));
+ is_int(expected, value, "toeplitz_hash: %u", expected);
+
+ toeplitz_ctx_t toepl;
+ for (int i = 0; i <= wire_ctx_offset(&ctx); i++) {
+ toeplitz_init(&toepl, i, key, sizeof(key), data, wire_ctx_offset(&ctx));
+ value = toeplitz_finish(&toepl);
+ is_int(expected, value, "toeplitz_init to %i: %u", i, expected);
+ }
+}
+
+int main(void)
+{
+ plan_lazy();
+
+ toeplitz_check(AF_INET, "66.9.149.187", "161.142.100.80", 2794, 1766, 0x51ccc178);
+ toeplitz_check(AF_INET, "199.92.111.2", "65.69.140.83", 14230, 4739, 0xc626b0ea);
+ toeplitz_check(AF_INET, "24.19.198.95", "12.22.207.184", 12898, 38024, 0x5c2b394a);
+ toeplitz_check(AF_INET, "38.27.205.30", "209.142.163.6", 48228, 2217, 0xafc7327f);
+ toeplitz_check(AF_INET, "153.39.163.191", "202.188.127.2", 44251, 1303, 0x10e828a2);
+
+ toeplitz_check(AF_INET6, "3ffe:2501:200:1fff::7", "3ffe:2501:200:3::1", 2794, 1766, 0x40207d3d);
+ toeplitz_check(AF_INET6, "3ffe:501:8::260:97ff:fe40:efab", "ff02::1", 14230, 4739, 0xdde51bbf);
+ toeplitz_check(AF_INET6, "3ffe:1900:4545:3:200:f8ff:fe21:67cf", "fe80::200:f8ff:fe21:67cf", 44251, 38024, 0x02d1feef);
+
+ return 0;
+}
diff --git a/tests/contrib/test_wire_ctx.c b/tests/contrib/test_wire_ctx.c
new file mode 100644
index 0000000..81386c9
--- /dev/null
+++ b/tests/contrib/test_wire_ctx.c
@@ -0,0 +1,287 @@
+/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <tap/basic.h>
+#include <netinet/in.h>
+#include <stdio.h>
+
+#include "libknot/errcode.h"
+#include "contrib/wire_ctx.h"
+
+#define OK(wire) { \
+ is_int(KNOT_EOK, (wire)->error, "check for no error"); \
+}
+
+#define NOK(wire, code) { \
+ is_int(code, (wire)->error, "check for error"); \
+}
+
+void ok_offset(wire_ctx_t *wire, size_t max, size_t i)
+{
+ wire_ctx_set_offset(wire, i);
+ OK(wire);
+ is_int(max - i, wire_ctx_available(wire), "get available %zu", max - i);
+ OK(wire);
+ is_int(i, wire_ctx_offset(wire), "get start position %zu", i);
+ OK(wire);
+}
+
+void nok_offset(wire_ctx_t *wire, size_t max)
+{
+ wire_ctx_set_offset(wire, max);
+ OK(wire);
+ wire_ctx_set_offset(wire, max + 1);
+ NOK(wire, KNOT_ERANGE);
+ is_int(0, wire_ctx_available(wire), "get available %i", 0);
+ NOK(wire, KNOT_ERANGE);
+ is_int(max, wire_ctx_offset(wire), "get last start position %zu", max);
+ NOK(wire, KNOT_ERANGE);
+}
+
+void offset_test(void)
+{
+ diag("offset operation");
+
+ const size_t LEN = 3;
+ uint8_t data[LEN];
+
+ wire_ctx_t wire = wire_ctx_init(data, sizeof(data));
+
+ // First free byte.
+ ok_offset(&wire, LEN, 0);
+ // Last free byte.
+ ok_offset(&wire, LEN, 2);
+ // First non-free byte.
+ ok_offset(&wire, LEN, 3);
+ // Invalid offset.
+ nok_offset(&wire, LEN);
+}
+
+void skip_test(void)
+{
+ diag("skip operation");
+
+ uint8_t data[3];
+
+ // Forward skips.
+
+ wire_ctx_t wire = wire_ctx_init(data, sizeof(data));
+
+ wire_ctx_skip(&wire, 2);
+ OK(&wire);
+ is_int(2, wire_ctx_offset(&wire), "skip by offset %i", 2);
+
+ wire_ctx_skip(&wire, 1);
+ OK(&wire);
+ is_int(3, wire_ctx_offset(&wire), "skip by offset %i", 1);
+
+ // Out-of-bounds skip.
+ wire_ctx_skip(&wire, 1);
+ NOK(&wire, KNOT_ERANGE);
+ is_int(3, wire_ctx_offset(&wire), "out-of-bounds skip by %i", 1);
+
+ // Backward skips.
+
+ wire = wire_ctx_init(data, sizeof(data));
+
+ wire_ctx_set_offset(&wire, 3);
+ OK(&wire);
+
+ wire_ctx_skip(&wire, -2);
+ OK(&wire);
+ is_int(1, wire_ctx_offset(&wire), "skip by offset %i", -2);
+
+ wire_ctx_skip(&wire, -1);
+ OK(&wire);
+ is_int(0, wire_ctx_offset(&wire), "skip by offset %i", -1);
+
+ // Out-of-bounds skip.
+ wire_ctx_skip(&wire, -1);
+ NOK(&wire, KNOT_ERANGE);
+ is_int(0, wire_ctx_offset(&wire), "out-of-bounds skip by %i", -1);
+}
+
+void clear_test(void)
+{
+ diag("clear operation");
+
+ uint8_t data[] = { 1, 2, 3 };
+
+ wire_ctx_t wire = wire_ctx_init(data, sizeof(data));
+
+ wire_ctx_clear(&wire, 10);
+ NOK(&wire, KNOT_ESPACE);
+ is_int(1, data[0], "no attempt to clear");
+
+ wire = wire_ctx_init(data, sizeof(data));
+ wire_ctx_clear(&wire, 3);
+ OK(&wire);
+ is_int(0, wire_ctx_available(&wire), "no space available");
+ for (int i = 0; i < sizeof(data); i++) {
+ is_int(0, data[i], "wire position %i is zero", i);
+ }
+}
+
+#define check_rw(size, value, ...) { \
+ const uint8_t expect[] = { __VA_ARGS__ }; \
+ uint8_t data[sizeof(expect)] = { 0 }; \
+ \
+ wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); \
+ \
+ wire_ctx_write_u ## size(&wire, value); \
+ OK(&wire); \
+ ok(memcmp(data, expect, sizeof(expect)) == 0, "write %i value", size); \
+ is_int(size/8, wire_ctx_offset(&wire), "write %i offset", size); \
+ \
+ wire_ctx_set_offset(&wire, 0); \
+ OK(&wire); \
+ \
+ uint64_t num = wire_ctx_read_u ## size(&wire); \
+ OK(&wire); \
+ is_int(value, num, "read %i value", size); \
+ is_int(size/8, wire_ctx_offset(&wire), "read %i offset", size); \
+}
+
+#define check_general_rw(...) { \
+ const uint8_t expect[] = { __VA_ARGS__ }; \
+ uint8_t data[sizeof(expect)] = { 0 }; \
+ \
+ wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); \
+ \
+ wire_ctx_write(&wire, expect, sizeof(expect)); \
+ OK(&wire); \
+ ok(memcmp(data, expect, sizeof(expect)) == 0, "write value"); \
+ is_int(sizeof(expect), wire_ctx_offset(&wire), "write offset"); \
+ \
+ wire_ctx_set_offset(&wire, 0); \
+ OK(&wire); \
+ \
+ uint8_t d[sizeof(expect)] = { 0 }; \
+ wire_ctx_read(&wire, d, sizeof(expect)); \
+ OK(&wire); \
+ ok(memcmp(d, expect, sizeof(expect)) == 0, "read value"); \
+ is_int(sizeof(expect), wire_ctx_offset(&wire), "read offset"); \
+}
+
+void read_write_test(void)
+{
+ diag("read and write operation");
+
+ check_rw( 8, 0x11, 0x11);
+ check_rw(16, 0x1122, 0x11, 0x22);
+ check_rw(32, 0x11223344, 0x11, 0x22, 0x33, 0x44);
+ check_rw(48, 0x112233445566, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66);
+ check_rw(64, 0x1122334455667788, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88);
+
+ check_general_rw(0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x10);
+}
+
+#define check_rw_over(size) { \
+ uint8_t data[1] = { 0 }; \
+ \
+ wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); \
+ wire_ctx_set_offset(&wire, 1); \
+ OK(&wire); \
+ \
+ wire_ctx_write_u ## size(&wire, 0); \
+ NOK(&wire, KNOT_ESPACE); \
+ is_int(1, wire_ctx_offset(&wire), "err write %i offset", size); \
+ \
+ wire = wire_ctx_init(data, sizeof(data)); \
+ wire_ctx_set_offset(&wire, 1); \
+ OK(&wire); \
+ \
+ uint64_t num = wire_ctx_read_u ## size(&wire); \
+ NOK(&wire, KNOT_EFEWDATA); \
+ is_int(0, num, "err read %i value", size); \
+ is_int(1, wire_ctx_offset(&wire), "err read %i offset", size); \
+}
+
+#define check_general_rw_over(void) { \
+ uint8_t data[1] = { 0 }; \
+ uint8_t d[2] = { 0 }; \
+ \
+ wire_ctx_t wire = wire_ctx_init(data, sizeof(data)); \
+ wire_ctx_write(&wire, d, sizeof(d)); \
+ NOK(&wire, KNOT_ESPACE); \
+ is_int(0, wire_ctx_offset(&wire), "err write offset"); \
+ \
+ wire = wire_ctx_init(data, sizeof(data)); \
+ wire_ctx_read(&wire, d, sizeof(d)); \
+ NOK(&wire, KNOT_EFEWDATA); \
+ is_int(0, wire_ctx_offset(&wire), "err read offset"); \
+}
+
+void read_write_overflow_test(void)
+{
+ diag("overflow read and write operation");
+
+ check_rw_over(8);
+ check_rw_over(16);
+ check_rw_over(32);
+ check_rw_over(48);
+ check_rw_over(64);
+
+ check_general_rw_over();
+}
+
+#define check_ro(size) { \
+ uint8_t data[8] = { 0 }; \
+ \
+ wire_ctx_t wire = wire_ctx_init_const(data, sizeof(data)); \
+ \
+ wire_ctx_write_u ## size(&wire, 0); \
+ NOK(&wire, KNOT_EACCES); \
+ is_int(0, wire_ctx_offset(&wire), "err write %i offset", size); \
+}
+
+#define check_general_ro(void) { \
+ uint8_t data[8] = { 0 }; \
+ uint8_t d[2] = { 0 }; \
+ \
+ wire_ctx_t wire = wire_ctx_init_const(data, sizeof(data)); \
+ \
+ wire_ctx_write(&wire, d, sizeof(d)); \
+ NOK(&wire, KNOT_EACCES); \
+ is_int(0, wire_ctx_offset(&wire), "err write offset"); \
+}
+
+void write_readonly_test(void)
+{
+ diag("readonly write operation");
+
+ check_ro(8);
+ check_ro(16);
+ check_ro(32);
+ check_ro(48);
+ check_ro(64);
+
+ check_general_ro();
+}
+
+int main(void)
+{
+ plan_lazy();
+
+ offset_test();
+ skip_test();
+ clear_test();
+ read_write_test();
+ read_write_overflow_test();
+ write_readonly_test();
+
+ return 0;
+}