summaryrefslogtreecommitdiffstats
path: root/tests/libknot
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/libknot
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/libknot/test_control.c221
-rw-r--r--tests/libknot/test_cookies.c174
-rw-r--r--tests/libknot/test_db.c287
-rw-r--r--tests/libknot/test_descriptor.c361
-rw-r--r--tests/libknot/test_dname.c618
-rw-r--r--tests/libknot/test_dynarray.c137
-rw-r--r--tests/libknot/test_edns.c502
-rw-r--r--tests/libknot/test_edns_ecs.c271
-rw-r--r--tests/libknot/test_endian.c70
-rw-r--r--tests/libknot/test_lookup.c66
-rw-r--r--tests/libknot/test_pkt.c199
-rw-r--r--tests/libknot/test_probe.c96
-rw-r--r--tests/libknot/test_rdata.c60
-rw-r--r--tests/libknot/test_rdataset.c227
-rw-r--r--tests/libknot/test_rrset-wire.c264
-rw-r--r--tests/libknot/test_rrset.c121
-rw-r--r--tests/libknot/test_tsig.c204
-rw-r--r--tests/libknot/test_wire.c46
-rw-r--r--tests/libknot/test_xdp_tcp.c633
-rw-r--r--tests/libknot/test_yparser.c346
-rw-r--r--tests/libknot/test_ypschema.c417
-rw-r--r--tests/libknot/test_yptrafo.c404
22 files changed, 5724 insertions, 0 deletions
diff --git a/tests/libknot/test_control.c b/tests/libknot/test_control.c
new file mode 100644
index 0000000..3846f31
--- /dev/null
+++ b/tests/libknot/test_control.c
@@ -0,0 +1,221 @@
+/* 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 <sys/wait.h>
+#include <unistd.h>
+#include <tap/basic.h>
+#include <tap/files.h>
+
+#define CTL_BUFF_SIZE 18
+#include "libknot/control/control.c"
+
+#define fake_ok(condition, msg, ...) \
+ if (!(condition)) { \
+ if (msg != NULL) { \
+ printf("error: " msg "\n", ##__VA_ARGS__); \
+ } \
+ exit(-1); \
+ }
+
+static void ctl_client(const char *socket, size_t argc, knot_ctl_data_t *argv)
+{
+ knot_ctl_t *ctl = knot_ctl_alloc();
+ fake_ok(ctl != NULL, "Allocate control");
+
+ int ret;
+ for (int i = 0; i < 20; i++) {
+ ret = knot_ctl_connect(ctl, socket);
+ if (ret == KNOT_EOK) {
+ break;
+ }
+ usleep(100000);
+ }
+ fake_ok(ret == KNOT_EOK, "Connect to socket");
+
+ diag("BEGIN: Client -> Server");
+
+ if (argc > 0) {
+ for (size_t i = 0; i < argc; i++) {
+ if (argv[i][KNOT_CTL_IDX_CMD] != NULL &&
+ argv[i][KNOT_CTL_IDX_CMD][0] == '\0') {
+ ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL);
+ fake_ok(ret == KNOT_EOK, "Client send data block end type");
+ } else {
+ ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &argv[i]);
+ fake_ok(ret == KNOT_EOK, "Client send data %zu", i);
+ }
+ }
+ }
+
+ ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL);
+ fake_ok(ret == KNOT_EOK, "Client send final data");
+
+ diag("END: Client -> Server");
+ diag("BEGIN: Client <- Server");
+
+ size_t count = 0;
+ knot_ctl_data_t data;
+ knot_ctl_type_t type = KNOT_CTL_TYPE_DATA;
+ while ((ret = knot_ctl_receive(ctl, &type, &data)) == KNOT_EOK) {
+ if (type == KNOT_CTL_TYPE_END) {
+ break;
+ }
+ if (argv[count][KNOT_CTL_IDX_CMD] != NULL &&
+ argv[count][KNOT_CTL_IDX_CMD][0] == '\0') {
+ fake_ok(type == KNOT_CTL_TYPE_BLOCK, "Receive block end type");
+ } else {
+ fake_ok(type == KNOT_CTL_TYPE_DATA, "Check data type");
+ for (size_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) {
+ fake_ok((data[i] == NULL && argv[count][i] == NULL) ||
+ (data[i] != NULL && argv[count][i] != NULL),
+ "Client compare input item occupation %zu", i);
+ if (data[i] == NULL) {
+ continue;
+ }
+
+ fake_ok(strcmp(data[i], argv[count][i]) == 0,
+ "Client compare input item '%s", argv[count][i]);
+ }
+ }
+ count++;
+ }
+ fake_ok(ret == KNOT_EOK, "Receive OK check");
+ fake_ok(type == KNOT_CTL_TYPE_END, "Receive EOF type");
+ fake_ok(count == argc, "Client compare input count '%zu'", argc);
+
+ diag("END: Client <- Server");
+
+ knot_ctl_close(ctl);
+ knot_ctl_free(ctl);
+}
+
+static void ctl_server(const char *socket, size_t argc, knot_ctl_data_t *argv)
+{
+ knot_ctl_t *ctl = knot_ctl_alloc();
+ ok(ctl != NULL, "Allocate control");
+
+ int ret = knot_ctl_bind(ctl, socket);
+ is_int(KNOT_EOK, ret, "Bind control socket");
+
+ ret = knot_ctl_accept(ctl);
+ is_int(KNOT_EOK, ret, "Accept a connection");
+
+ diag("BEGIN: Server <- Client");
+
+ size_t count = 0;
+ knot_ctl_data_t data;
+ knot_ctl_type_t type = KNOT_CTL_TYPE_DATA;
+ while ((ret = knot_ctl_receive(ctl, &type, &data)) == KNOT_EOK) {
+ if (type == KNOT_CTL_TYPE_END) {
+ break;
+ }
+ if (argv[count][KNOT_CTL_IDX_CMD] != NULL &&
+ argv[count][KNOT_CTL_IDX_CMD][0] == '\0') {
+ ok(type == KNOT_CTL_TYPE_BLOCK, "Receive block end type");
+ } else {
+ ok(type == KNOT_CTL_TYPE_DATA, "Check data type");
+ for (size_t i = 0; i < KNOT_CTL_IDX__COUNT; i++) {
+ ok((data[i] == NULL && argv[count][i] == NULL) ||
+ (data[i] != NULL && argv[count][i] != NULL),
+ "Server compare input item occupation %zu", i);
+ if (data[i] == NULL) {
+ continue;
+ }
+
+ ok(strcmp(data[i], argv[count][i]) == 0,
+ "Server compare input item '%s", argv[count][i]);
+ }
+ }
+ count++;
+ }
+ is_int(KNOT_EOK, ret, "Receive OK check");
+ ok(type == KNOT_CTL_TYPE_END, "Receive EOF type");
+ ok(count == argc, "Server compare input count '%zu'", argc);
+
+ diag("END: Server <- Client");
+ diag("BEGIN: Server -> Client");
+
+ if (argc > 0) {
+ for (size_t i = 0; i < argc; i++) {
+ if (argv[i][KNOT_CTL_IDX_CMD] != NULL &&
+ argv[i][KNOT_CTL_IDX_CMD][0] == '\0') {
+ ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL);
+ is_int(KNOT_EOK, ret, "Client send data block end type");
+ } else {
+ ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &argv[i]);
+ is_int(KNOT_EOK, ret, "Server send data %zu", i);
+ }
+ }
+ }
+
+ ret = knot_ctl_send(ctl, KNOT_CTL_TYPE_END, NULL);
+ is_int(KNOT_EOK, ret, "Server send final data");
+
+ diag("END: Server -> Client");
+
+ knot_ctl_close(ctl);
+ knot_ctl_unbind(ctl);
+ knot_ctl_free(ctl);
+}
+
+static void test_client_server_client(void)
+{
+ char *socket = test_mktemp();
+ ok(socket != NULL, "Make a temporary socket file '%s'", socket);
+
+ size_t data_len = 5;
+ knot_ctl_data_t data[] = {
+ { "command", "error", "section", "item", "identifier",
+ "zone", "owner", "ttl", "type", "data" },
+ { [KNOT_CTL_IDX_DATA] = "\x01\x02" },
+ { [KNOT_CTL_IDX_CMD] = "\0" }, // This means block end in this test!
+ { NULL },
+ { [KNOT_CTL_IDX_ERROR] = "Ultra long message" }
+ };
+
+ // Fork a client process.
+ pid_t child_pid = fork();
+ if (child_pid == -1) {
+ ok(child_pid >= 0, "Process fork");
+ return;
+ }
+ if (child_pid == 0) {
+ ctl_client(socket, data_len, data);
+ free(socket);
+ return;
+ } else {
+ ctl_server(socket, data_len, data);
+ }
+
+ int status = 0;
+ wait(&status);
+ ok(WIFEXITED(status), "Wait for client");
+
+ test_rm_rf(socket);
+ free(socket);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ diag("Client -> Server -> Client");
+ test_client_server_client();
+
+ return 0;
+}
diff --git a/tests/libknot/test_cookies.c b/tests/libknot/test_cookies.c
new file mode 100644
index 0000000..82ba502
--- /dev/null
+++ b/tests/libknot/test_cookies.c
@@ -0,0 +1,174 @@
+/* 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 <tap/basic.h>
+#include <time.h>
+
+#include "libknot/cookies.h"
+#include "libknot/endian.h"
+#include "libknot/errcode.h"
+#include "contrib/sockaddr.h"
+
+static knot_edns_cookie_t client_generate(
+ struct sockaddr_storage *s_addr, const uint8_t *c_secret,
+ const char *msg, int code, const char *ref)
+{
+ knot_edns_cookie_params_t params = {
+ .server_addr = s_addr,
+ };
+ memcpy(params.secret, c_secret, sizeof(params.secret));
+
+ knot_edns_cookie_t cc;
+ int ret = knot_edns_cookie_client_generate(&cc, &params);
+ is_int(code, ret, "client_generate ret: %s", msg);
+ if (ret == KNOT_EOK) {
+ ok(cc.len == KNOT_EDNS_COOKIE_CLNT_SIZE && memcmp(cc.data, ref, cc.len) == 0,
+ "client_generate value: %s", msg);
+ }
+ return cc;
+}
+
+static knot_edns_cookie_t server_generate(
+ struct sockaddr_storage *c_addr, const uint8_t *s_secret, uint32_t timestamp,
+ const knot_edns_cookie_t *cc, const char *msg, int code, const char *ref)
+{
+ knot_edns_cookie_params_t params = {
+ .version = KNOT_EDNS_COOKIE_VERSION,
+ .timestamp = timestamp,
+ .client_addr = c_addr,
+ };
+ memcpy(params.secret, s_secret, sizeof(params.secret));
+
+ knot_edns_cookie_t sc;
+ int ret = knot_edns_cookie_server_generate(&sc, cc, &params);
+ is_int(code, ret, "server_generate ret: %s", msg);
+ if (ret == KNOT_EOK) {
+ ok(sc.len == 16 && memcmp(sc.data, ref, sc.len) == 0,
+ "server_generate value: %s", msg);
+ }
+ return sc;
+}
+
+static void client_check(
+ struct sockaddr_storage *s_addr, const uint8_t *secret,
+ knot_edns_cookie_t *cc, const char *msg, int code)
+{
+ knot_edns_cookie_params_t params = {
+ .server_addr = s_addr,
+ };
+ memcpy(params.secret, secret, sizeof(params.secret));
+
+ int ret = knot_edns_cookie_client_check(cc, &params);
+ is_int(code, ret, "client_check ret: %s", msg);
+}
+
+static void server_check(
+ struct sockaddr_storage *c_addr, const uint8_t *secret,
+ knot_edns_cookie_t *sc, knot_edns_cookie_t *cc, uint32_t timestamp,
+ const char *msg, int code)
+{
+ knot_edns_cookie_params_t params = {
+ .version = KNOT_EDNS_COOKIE_VERSION,
+ .timestamp = timestamp,
+ .lifetime_before = 3600,
+ .lifetime_after = 300,
+ .client_addr = c_addr,
+ };
+ memcpy(params.secret, secret, sizeof(params.secret));
+
+ int ret = knot_edns_cookie_server_check(sc, cc, &params);
+ is_int(code, ret, "server_check ret: %s", msg);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ knot_edns_cookie_t cc;
+ knot_edns_cookie_t sc;
+
+ const uint8_t c_secret1[] = "\x3F\x66\x51\xC9\x81\xC1\xD7\x3E\x58\x79\x25\xD2\xF9\x98\x5F\x08";
+ const uint8_t c_secret2[] = "\x4C\x31\x15\x17\xFA\xB6\xBF\xE2\xE1\x49\xAB\x74\xEC\x1B\xC9\xA0";
+ const uint8_t s_secret1[] = "\xE5\xE9\x73\xE5\xA6\xB2\xA4\x3F\x48\xE7\xDC\x84\x9E\x37\xBF\xCF";
+
+ struct sockaddr_storage c4_sa1 = { 0 };
+ struct sockaddr_storage c4_sa2 = { 0 };
+ struct sockaddr_storage s4_sa = { 0 };
+ sockaddr_set(&c4_sa1, AF_INET, "198.51.100.100", 0);
+ sockaddr_set(&c4_sa2, AF_INET, "203.0.113.203", 0);
+ sockaddr_set(&s4_sa, AF_INET, "192.0.2.53", 0);
+
+ struct sockaddr_storage c6_sa = { 0 };
+ struct sockaddr_storage s6_sa = { 0 };
+ sockaddr_set(&c6_sa, AF_INET6, "2001:db8:220:1:59de:d0f4:8769:82b8", 0);
+ sockaddr_set(&s6_sa, AF_INET6, "2001:db8:8f::53", 0);
+
+ const uint8_t c_secret6[] = "\x3B\x49\x5B\xA6\xA5\xB7\xFD\x87\x73\x5B\xD5\x8F\x1E\xF7\x26\x1D";
+ const uint8_t s_secret6[] = "\xDD\x3B\xDF\x93\x44\xB6\x78\xB1\x85\xA6\xF5\xCB\x60\xFC\xA7\x15";
+ const uint8_t s_secret7[] = "\x44\x55\x36\xBC\xD2\x51\x32\x98\x07\x5A\x5D\x37\x96\x63\xC9\x62";
+
+ // Learning a new Server Cookie
+
+ cc = client_generate(&s4_sa, c_secret1, "IPv4", KNOT_EOK,
+ "\x24\x64\xC4\xAB\xCF\x10\xC9\x57");
+ client_check(&s4_sa, c_secret1, &cc, "IPv4", KNOT_EOK);
+ sc = server_generate(&c4_sa1, s_secret1, 1559731985, &cc, "IPv4", KNOT_EOK,
+ "\x01\x00\x00\x00\x5C\xF7\x9F\x11\x1F\x81\x30\xC3\xEE\xE2\x94\x80");
+ server_check(&c4_sa1, s_secret1, &sc, &cc, 1559731985, "IPv4", KNOT_EOK);
+
+ // The same client learning a renewed (fresh) Server Cookie
+
+ server_generate(&c4_sa1, s_secret1, 1559734385, &cc, "IPv4", KNOT_EOK,
+ "\x01\x00\x00\x00\x5C\xF7\xA8\x71\xD4\xA5\x64\xA1\x44\x2A\xCA\x77");
+
+ // Another client learning a renewed Server Cookie
+
+ cc = client_generate(&s4_sa, c_secret2, "IPv4", KNOT_EOK,
+ "\xFC\x93\xFC\x62\x80\x7D\xDB\x86");
+ char *sc_reserved = "\x01\xAB\xCD\xEF\x5C\xF7\x8F\x71\xA3\x14\x22\x7B\x66\x79\xEB\xF5";
+ memcpy(sc.data, sc_reserved, strlen(sc_reserved));
+ server_check(&c4_sa2, s_secret1, &sc, &cc, 1559727985, "IPv4", KNOT_EOK);
+
+ // Version check
+
+ sc.data[0] = 10;
+ server_check(&c4_sa2, s_secret1, &sc, &cc, 1559727985, "version", KNOT_ENOTSUP);
+
+ // IPv6 query with rolled over secret
+
+ cc = client_generate(&s6_sa, c_secret6, "IPv6", KNOT_EOK,
+ "\x22\x68\x1A\xB9\x7D\x52\xC2\x98");
+ client_check(&s6_sa, c_secret6, &cc, "IPv6", KNOT_EOK);
+ sc = server_generate(&c6_sa, s_secret6, 1559741817, &cc, "IPv6", KNOT_EOK,
+ "\x01\x00\x00\x00\x5C\xF7\xC5\x79\x26\x55\x6B\xD0\x93\x4C\x72\xF8");
+ server_check(&c6_sa, s_secret6, &sc, &cc, 1559741961, "IPv6", KNOT_EOK);
+ sc = server_generate(&c6_sa, s_secret7, 1559741961, &cc, "IPv6", KNOT_EOK,
+ "\x01\x00\x00\x00\x5C\xF7\xC6\x09\xA6\xBB\x79\xD1\x66\x25\x50\x7A");
+
+ // Past lifetime check
+
+ server_check(&c6_sa, s_secret7, &sc, &cc, 1559741961 + 3600, "last old", KNOT_EOK);
+ server_check(&c6_sa, s_secret7, &sc, &cc, 1559741961 + 3601, "too old", KNOT_ERANGE);
+
+ // Future lifetime check
+
+ server_check(&c6_sa, s_secret7, &sc, &cc, 1559741961 - 300, "last new", KNOT_EOK);
+ server_check(&c6_sa, s_secret7, &sc, &cc, 1559741961 - 301, "too new", KNOT_ERANGE);
+
+ return 0;
+}
diff --git a/tests/libknot/test_db.c b/tests/libknot/test_db.c
new file mode 100644
index 0000000..e409ad8
--- /dev/null
+++ b/tests/libknot/test_db.c
@@ -0,0 +1,287 @@
+/* Copyright (C) 2011 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 <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <tap/basic.h>
+#include <tap/files.h>
+
+#include "contrib/string.h"
+#include "libknot/libknot.h"
+#include "contrib/mempattern.h"
+#include "contrib/openbsd/strlcpy.h"
+#include "contrib/ucw/mempool.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
+#define KEY_SET(key, str) key.data = (str); key.len = strlen(str) + 1
+
+/*! \brief Generate random key. */
+static const char *alphabet = "abcdefghijklmn0123456789";
+static char *str_key_rand(size_t len, knot_mm_t *pool)
+{
+ char *s = mm_alloc(pool, len);
+ memset(s, 0, len);
+ for (unsigned i = 0; i < len - 1; ++i) {
+ s[i] = alphabet[rand() % strlen(alphabet)];
+ }
+ return s;
+}
+
+static void knot_db_test_set(unsigned nkeys, char **keys, void *opts,
+ const knot_db_api_t *api, knot_mm_t *pool)
+{
+ if (api == NULL) {
+ skip("API not compiled in");
+ return;
+ }
+
+ /* Create database */
+ knot_db_t *db = NULL;
+ int ret = api->init(&db, pool, opts);
+ ok(ret == KNOT_EOK && db != NULL, "%s: create", api->name);
+
+ /* Start WR transaction. */
+ knot_db_txn_t txn;
+ ret = api->txn_begin(db, &txn, 0);
+ is_int(KNOT_EOK, ret, "%s: txn_begin(WR)", api->name);
+
+ /* Insert keys */
+ knot_db_val_t key, val;
+ bool passed = true;
+ for (unsigned i = 0; i < nkeys; ++i) {
+ KEY_SET(key, keys[i]);
+ val.len = sizeof(void*);
+ val.data = &key.data;
+
+ ret = api->insert(&txn, &key, &val, 0);
+ if (ret != KNOT_EOK && ret != KNOT_EEXIST) {
+ passed = false;
+ break;
+ }
+ }
+ ok(passed, "%s: insert", api->name);
+
+ /* Commit WR transaction. */
+ ret = api->txn_commit(&txn);
+ is_int(KNOT_EOK, ret, "%s: txn_commit(WR)", api->name);
+
+ /* Start RD transaction. */
+ ret = api->txn_begin(db, &txn, KNOT_DB_RDONLY);
+ is_int(KNOT_EOK, ret, "%s: txn_begin(RD)", api->name);
+
+ /* Lookup all keys */
+ passed = true;
+ for (unsigned i = 0; i < nkeys; ++i) {
+ KEY_SET(key, keys[i]);
+
+ ret = api->find(&txn, &key, &val, 0);
+ if (ret != KNOT_EOK) {
+ passed = false;
+ break;
+ }
+
+ const char **stored_key = val.data;
+ if (strcmp(*stored_key, keys[i]) != 0) {
+ diag("%s: mismatch on element '%u'", api->name, i);
+ passed = false;
+ break;
+ }
+ }
+ ok(passed, "%s: lookup all keys", api->name);
+
+ /* Fetch dataset size. */
+ int db_size = api->count(&txn);
+ ok(db_size > 0 && db_size <= nkeys, "%s: count %d", api->name, db_size);
+
+ /* Unsorted iteration */
+ int iterated = 0;
+ knot_db_iter_t *it = api->iter_begin(&txn, 0);
+ while (it != NULL) {
+ ++iterated;
+ it = api->iter_next(it);
+ }
+ api->iter_finish(it);
+ is_int(db_size, iterated, "%s: unsorted iteration", api->name);
+
+ /* Sorted iteration. */
+ char first_key[KEY_MAXLEN] = { '\0' };
+ char second_key[KEY_MAXLEN] = { '\0' };
+ char last_key[KEY_MAXLEN] = { '\0' };
+ char key_buf[KEY_MAXLEN] = {'\0'};
+ iterated = 0;
+ memset(&key, 0, sizeof(key));
+ it = api->iter_begin(&txn, KNOT_DB_SORTED);
+ while (it != NULL) {
+ api->iter_key(it, &key);
+ if (iterated > 0) { /* Only if previous exists. */
+ if (strcmp(key_buf, key.data) > 0) {
+ diag("%s: iter_sort '%s' <= '%s' FAIL\n",
+ api->name, key_buf, (const char *)key.data);
+ break;
+ }
+ if (iterated == 1) {
+ memcpy(second_key, key.data, key.len);
+ }
+ } else {
+ memcpy(first_key, key.data, key.len);
+ }
+ ++iterated;
+ memcpy(key_buf, key.data, key.len);
+ it = api->iter_next(it);
+ }
+ strlcpy(last_key, key_buf, sizeof(last_key));
+ is_int(db_size, iterated, "%s: sorted iteration", api->name);
+ api->iter_finish(it);
+
+ /* Interactive iteration. */
+ it = api->iter_begin(&txn, KNOT_DB_NOOP);
+ if (it != NULL) { /* If supported. */
+ ret = 0;
+ /* Check if first and last keys are reachable */
+ it = api->iter_seek(it, NULL, KNOT_DB_FIRST);
+ ret += api->iter_key(it, &key);
+ is_string(first_key, key.data, "%s: iter_set(FIRST)", api->name);
+ /* Check left/right iteration. */
+ it = api->iter_seek(it, &key, KNOT_DB_NEXT);
+ ret += api->iter_key(it, &key);
+ is_string(second_key, key.data, "%s: iter_set(NEXT)", api->name);
+ it = api->iter_seek(it, &key, KNOT_DB_PREV);
+ ret += api->iter_key(it, &key);
+ is_string(first_key, key.data, "%s: iter_set(PREV)", api->name);
+ it = api->iter_seek(it, &key, KNOT_DB_LAST);
+ ret += api->iter_key(it, &key);
+ is_string(last_key, key.data, "%s: iter_set(LAST)", api->name);
+ /* Check if prev(last_key + 1) is the last_key */
+ strlcpy(key_buf, last_key, sizeof(key_buf));
+ key_buf[0] += 1;
+ KEY_SET(key, key_buf);
+ it = api->iter_seek(it, &key, KNOT_DB_LEQ);
+ ret += api->iter_key(it, &key);
+ is_string(last_key, key.data, "%s: iter_set(LEQ)", api->name);
+ /* Check if next(first_key - 1) is the first_key */
+ strlcpy(key_buf, first_key, sizeof(key_buf));
+ key_buf[0] -= 1;
+ KEY_SET(key, key_buf);
+ it = api->iter_seek(it, &key, KNOT_DB_GEQ);
+ ret += api->iter_key(it, &key);
+ is_string(first_key, key.data, "%s: iter_set(GEQ)", api->name);
+ api->iter_finish(it);
+ is_int(ret, 0, "%s: iter_* error codes", api->name);
+ }
+ api->txn_abort(&txn);
+
+ /* Deleting during iteration. */
+ const uint8_t DEL_MAX_CNT = 3;
+ api->txn_begin(db, &txn, 0);
+ api->clear(&txn);
+ for (uint8_t i = 0; i < DEL_MAX_CNT; ++i) {
+ key.data = &i;
+ key.len = sizeof(i);
+ val.data = NULL;
+ val.len = 0;
+
+ ret = api->insert(&txn, &key, &val, 0);
+ is_int(KNOT_EOK, ret, "%s: add key '%u'", api->name, i);
+ }
+ it = api->iter_begin(&txn, KNOT_DB_NOOP);
+ if (it != NULL) { /* If supported. */
+ is_int(DEL_MAX_CNT, api->count(&txn), "%s: key count before", api->name);
+ it = api->iter_seek(it, NULL, KNOT_DB_FIRST);
+ uint8_t pos = 0;
+ while (it != NULL) {
+ ret = api->iter_key(it, &key);
+ is_int(KNOT_EOK, ret, "%s: iter key before del", api->name);
+ is_int(pos, ((uint8_t *)(key.data))[0], "%s: iter compare key '%u'",
+ api->name, pos);
+
+ ret = knot_db_lmdb_iter_del(it);
+ is_int(KNOT_EOK, ret, "%s: iter del", api->name);
+
+ it = api->iter_next(it);
+
+ ret = api->iter_key(it, &key);
+ if (++pos < DEL_MAX_CNT) {
+ is_int(KNOT_EOK, ret, "%s: iter key after del", api->name);
+ is_int(pos, ((uint8_t *)key.data)[0], "%s: iter compare key '%u",
+ api->name, pos);
+ } else {
+ is_int(KNOT_EINVAL, ret, "%s: iter key after del", api->name);
+ }
+ }
+ api->iter_finish(it);
+ is_int(0, api->count(&txn), "%s: key count after", api->name);
+ }
+ api->txn_abort(&txn);
+
+ /* Clear database and recheck. */
+ ret = api->txn_begin(db, &txn, 0);
+ ret += api->clear(&txn);
+ ret += api->txn_commit(&txn);
+ is_int(0, ret, "%s: clear()", api->name);
+
+ /* Check if the database is empty. */
+ api->txn_begin(db, &txn, KNOT_DB_RDONLY);
+ db_size = api->count(&txn);
+ is_int(0, db_size, "%s: count after clear = %d", api->name, db_size);
+ api->txn_abort(&txn);
+
+ api->deinit(db);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ knot_mm_t pool;
+ mm_ctx_mempool(&pool, MM_DEFAULT_BLKSIZE);
+
+ char *dbid = test_mkdtemp();
+ ok(dbid != NULL, "make temporary directory");
+
+ /* Random keys. */
+ unsigned nkeys = 10000;
+ char **keys = mm_alloc(&pool, sizeof(char*) * nkeys);
+ for (unsigned i = 0; i < nkeys; ++i) {
+ keys[i] = str_key_rand(KEY_MAXLEN, &pool);
+ }
+
+ /* Sort random keys. */
+ str_key_sort(keys, nkeys);
+
+ /* Execute test set for all backends. */
+ struct knot_db_lmdb_opts lmdb_opts = KNOT_DB_LMDB_OPTS_INITIALIZER;
+ lmdb_opts.path = dbid;
+ struct knot_db_trie_opts trie_opts = KNOT_DB_TRIE_OPTS_INITIALIZER;
+ knot_db_test_set(nkeys, keys, &lmdb_opts, knot_db_lmdb_api(), &pool);
+ knot_db_test_set(nkeys, keys, &trie_opts, knot_db_trie_api(), &pool);
+
+ /* Cleanup. */
+ mp_delete(pool.ctx);
+ test_rm_rf(dbid);
+ free(dbid);
+
+ return 0;
+}
diff --git a/tests/libknot/test_descriptor.c b/tests/libknot/test_descriptor.c
new file mode 100644
index 0000000..8c25e2c
--- /dev/null
+++ b/tests/libknot/test_descriptor.c
@@ -0,0 +1,361 @@
+/* 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 <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "libknot/descriptor.h"
+
+#define BUF_LEN 256
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ const knot_rdata_descriptor_t *descr;
+ char name[BUF_LEN] = "";
+ int ret;
+ uint16_t num;
+
+ // Get descriptor, type num to string:
+ // 1. TYPE0
+ descr = knot_get_rdata_descriptor(0);
+ ok(descr->type_name == 0, "get TYPE0 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE0 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE0 descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(0, name, BUF_LEN);
+ ok(ret != -1, "get TYPE0 ret");
+ ok(strcmp(name, "TYPE0") == 0, "get TYPE0 name");
+
+ // 2. A
+ descr = knot_get_rdata_descriptor(1);
+ ok(strcmp(descr->type_name, "A") == 0, "get A descriptor name");
+ ok(descr->block_types[0] == 4,
+ "get A descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get A descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(1, name, BUF_LEN);
+ ok(ret != -1, "get A ret");
+ ok(strcmp(name, "A") == 0, "get A name");
+
+ // 3. CNAME
+ descr = knot_get_rdata_descriptor(5);
+ ok(strcmp(descr->type_name, "CNAME") == 0, "get CNAME descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_COMPRESSIBLE_DNAME,
+ "get CNAME descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get CNAME descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(5, name, BUF_LEN);
+ ok(ret != -1, "get CNAME ret");
+ ok(strcmp(name, "CNAME") == 0, "get CNAME name");
+
+ // 4. TYPE38 (A6)
+ descr = knot_get_rdata_descriptor(38);
+ ok(descr->type_name == 0, "get TYPE38 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE38 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE38 descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(38, name, BUF_LEN);
+ ok(ret != -1, "get TYPE38 ret");
+ ok(strcmp(name, "TYPE38") == 0, "get TYPE38 name");
+
+ // 5. ANY
+ descr = knot_get_rdata_descriptor(255);
+ ok(strcmp(descr->type_name, "ANY") == 0, "get ANY descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get ANY descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get ANY descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(255, name, BUF_LEN);
+ ok(ret != -1, "get ANY ret");
+ ok(strcmp(name, "ANY") == 0, "get ANY name");
+
+ // 6. TYPE65535
+ descr = knot_get_rdata_descriptor(65535);
+ ok(descr->type_name == 0, "get TYPE65535 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE65535 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE65535 descriptor 2. item type");
+
+ ret = knot_rrtype_to_string(65535, name, BUF_LEN);
+ ok(ret != -1, "get TYPE65535 ret");
+ ok(strcmp(name, "TYPE65535") == 0, "get TYPE65535 name");
+
+ // Class num to string:
+ // 7. CLASS0
+ ret = knot_rrclass_to_string(0, name, BUF_LEN);
+ ok(ret != -1, "get CLASS0 ret");
+ ok(strcmp(name, "CLASS0") == 0, "get CLASS0 name");
+
+ // 8. IN
+ ret = knot_rrclass_to_string(1, name, BUF_LEN);
+ ok(ret != -1, "get IN ret");
+ ok(strcmp(name, "IN") == 0, "get IN name");
+
+ // 9. ANY
+ ret = knot_rrclass_to_string(255, name, BUF_LEN);
+ ok(ret != -1, "get ANY ret");
+ ok(strcmp(name, "ANY") == 0, "get ANY name");
+
+ // 10. CLASS65535
+ ret = knot_rrclass_to_string(65535, name, BUF_LEN);
+ ok(ret != -1, "get CLASS65535 ret");
+ ok(strcmp(name, "CLASS65535") == 0, "get CLASS65535 name");
+
+ // String to type num:
+ // 11. A
+ ret = knot_rrtype_from_string("A", &num);
+ ok(ret != -1, "get A num ret");
+ ok(num == 1, "get A num");
+
+ // 12. a
+ ret = knot_rrtype_from_string("a", &num);
+ ok(ret != -1, "get a num ret");
+ ok(num == 1, "get a num");
+
+ // 13. AaAa
+ ret = knot_rrtype_from_string("AaAa", &num);
+ ok(ret != -1, "get AaAa num ret");
+ ok(num == 28, "get AaAa num");
+
+ // 14. ""
+ ret = knot_rrtype_from_string("", &num);
+ ok(ret == -1, "get "" num ret");
+
+ // 15. DUMMY
+ ret = knot_rrtype_from_string("DUMMY", &num);
+ ok(ret == -1, "get DUMMY num ret");
+
+ // 16. TypE33
+ ret = knot_rrtype_from_string("TypE33", &num);
+ ok(ret != -1, "get TypE33 num ret");
+ ok(num == 33, "get TypE33 num");
+
+ // 17. TYPE
+ ret = knot_rrtype_from_string("TYPE", &num);
+ ok(ret == -1, "get TYPE num ret");
+
+ // 18. TYPE0
+ ret = knot_rrtype_from_string("TYPE0", &num);
+ ok(ret != -1, "get TYPE0 num ret");
+ ok(num == 0, "get TYPE0 num");
+
+ // 19. TYPE65535
+ ret = knot_rrtype_from_string("TYPE65535", &num);
+ ok(ret != -1, "get TYPE65535 num ret");
+ ok(num == 65535, "get TYPE65535 num");
+
+ // 20. TYPE65536
+ ret = knot_rrtype_from_string("TYPE65536", &num);
+ ok(ret == -1, "get TYPE65536 num ret");
+
+ // String to class num:
+ // 21. In
+ ret = knot_rrclass_from_string("In", &num);
+ ok(ret != -1, "get In num ret");
+ ok(num == 1, "get In num");
+
+ // 22. ANY
+ ret = knot_rrclass_from_string("ANY", &num);
+ ok(ret != -1, "get ANY num ret");
+ ok(num == 255, "get ANY num");
+
+ // 23. ""
+ ret = knot_rrclass_from_string("", &num);
+ ok(ret == -1, "get "" num ret");
+
+ // 24. DUMMY
+ ret = knot_rrclass_from_string("DUMMY", &num);
+ ok(ret == -1, "get DUMMY num ret");
+
+ // 25. CLass33
+ ret = knot_rrclass_from_string("CLass33", &num);
+ ok(ret != -1, "get CLass33 num ret");
+ ok(num == 33, "get CLass33 num");
+
+ // 26. CLASS
+ ret = knot_rrclass_from_string("CLASS", &num);
+ ok(ret == -1, "get CLASS num ret");
+
+ // 27. CLASS0
+ ret = knot_rrclass_from_string("CLASS0", &num);
+ ok(ret != -1, "get CLASS0 num ret");
+ ok(num == 0, "get CLASS0 num");
+
+ // 28. CLASS65535
+ ret = knot_rrclass_from_string("CLASS65535", &num);
+ ok(ret != -1, "get CLASS65535 num ret");
+ ok(num == 65535, "get CLASS65535 num");
+
+ // 29. CLASS65536
+ ret = knot_rrclass_from_string("CLASS65536", &num);
+ ok(ret == -1, "get CLASS65536 num ret");
+
+ // Get obsolete descriptor:
+ // 30. TYPE0
+ descr = knot_get_obsolete_rdata_descriptor(0);
+ ok(descr->type_name == 0, "get TYPE0 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE0 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE0 descriptor 2. item type");
+
+ // 31. MD
+ descr = knot_get_obsolete_rdata_descriptor(3);
+ ok(strcmp(descr->type_name, "MD") == 0, "get MD descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME,
+ "get A descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get A descriptor 2. item type");
+
+ // 32. NXT
+ descr = knot_get_obsolete_rdata_descriptor(30);
+ ok(strcmp(descr->type_name, "NXT") == 0, "get NXT descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME,
+ "get CNAME descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_REMAINDER,
+ "get CNAME descriptor 2. item type");
+ ok(descr->block_types[2] == KNOT_RDATA_WF_END,
+ "get CNAME descriptor 3. item type");
+
+ // 33. TYPE38 (A6)
+ descr = knot_get_obsolete_rdata_descriptor(38);
+ ok(descr->type_name == 0, "get TYPE38 descriptor name");
+ ok(descr->block_types[0] == KNOT_RDATA_WF_REMAINDER,
+ "get TYPE38 descriptor 1. item type");
+ ok(descr->block_types[1] == KNOT_RDATA_WF_END,
+ "get TYPE38 descriptor 2. item type");
+
+ // knot_rrtype_to_string invalid output buffer size
+ ret = knot_rrtype_to_string(1, NULL, 0);
+ ok(ret == -1, "knot_rrtype_to_string: invalid output buffer size");
+
+ // knot_rrclass_to_string invalid output buffer size
+ ret = knot_rrclass_to_string(1, NULL, 0);
+ ok(ret == -1, "knot_rrclass_to_string: invalid output buffer size");
+
+ // knot_rrtype_is_metatype
+ ok(knot_rrtype_is_metatype(0) == 0,
+ "rrtype is not metatype");
+ ok(knot_rrtype_is_metatype(KNOT_RRTYPE_SIG) != 0,
+ "rrtype is SIG");
+ ok(knot_rrtype_is_metatype(KNOT_RRTYPE_OPT) != 0,
+ "rrtype is OPT");
+ ok(knot_rrtype_is_metatype(KNOT_RRTYPE_TKEY) != 0,
+ "rrtype is TKEY");
+ ok(knot_rrtype_is_metatype(KNOT_RRTYPE_TSIG) != 0,
+ "rrtype is TSIG");
+ ok(knot_rrtype_is_metatype(KNOT_RRTYPE_IXFR) != 0,
+ "rrtype is IXFR");
+ ok(knot_rrtype_is_metatype(KNOT_RRTYPE_AXFR) != 0,
+ "rrtype is AXFR");
+ ok(knot_rrtype_is_metatype(KNOT_RRTYPE_ANY) != 0,
+ "rrtype is ANY");
+
+ // knot_rrtype_is_dnssec
+ ok(knot_rrtype_is_dnssec(0) == 0,
+ "rrtype is not DNSSEC");
+ ok(knot_rrtype_is_dnssec(KNOT_RRTYPE_DNSKEY) != 0,
+ "rrtype is DNSKEY");
+ ok(knot_rrtype_is_dnssec(KNOT_RRTYPE_RRSIG) != 0,
+ "rrtype is RRSIG");
+ ok(knot_rrtype_is_dnssec(KNOT_RRTYPE_NSEC) != 0,
+ "rrtype is NSEC");
+ ok(knot_rrtype_is_dnssec(KNOT_RRTYPE_NSEC3) != 0,
+ "rrtype is NSEC3");
+ ok(knot_rrtype_is_dnssec(KNOT_RRTYPE_NSEC3PARAM) != 0,
+ "rrtype is NSEC3PARAM");
+ ok(knot_rrtype_is_dnssec(KNOT_RRTYPE_CDNSKEY) != 0,
+ "rrtype is CDNSKEY");
+
+ // knot_rrtype_additional_needed
+ ok(knot_rrtype_additional_needed(0) == 0,
+ "rrtype is not additional needed");
+ ok(knot_rrtype_additional_needed(KNOT_RRTYPE_NS) != 0,
+ "rrtype is NS");
+ ok(knot_rrtype_additional_needed(KNOT_RRTYPE_MX) != 0,
+ "rrtype is MX");
+ ok(knot_rrtype_additional_needed(KNOT_RRTYPE_SRV) != 0,
+ "rrtype is SRV");
+
+ // knot_rrtype_should_be_lowercased
+ ok(knot_rrtype_should_be_lowercased(0) == 0,
+ "rrtype should not be lowercased");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_NS) != 0,
+ "rrtype is NS");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_MD) != 0,
+ "rrtype is MD");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_MF) != 0,
+ "rrtype is MF");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_CNAME) != 0,
+ "rrtype is CNAME");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_SOA) != 0,
+ "rrtype is SOA");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_MB) != 0,
+ "rrtype is MB");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_MG) != 0,
+ "rrtype is MG");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_MR) != 0,
+ "rrtype is MR");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_PTR) != 0,
+ "rrtype is PTR");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_MINFO) != 0,
+ "rrtype is MINFO");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_MX) != 0,
+ "rrtype is MX");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_RP) != 0,
+ "rrtype is RP");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_AFSDB) != 0,
+ "rrtype is AFSDB");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_RT) != 0,
+ "rrtype is RT");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_SIG) != 0,
+ "rrtype is SIG");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_PX) != 0,
+ "rrtype is PX");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_NXT) != 0,
+ "rrtype is NXT");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_NAPTR) != 0,
+ "rrtype is NAPTR");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_KX) != 0,
+ "rrtype is KX");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_SRV) != 0,
+ "rrtype is SRV");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_DNAME) != 0,
+ "rrtype is DNAME");
+ ok(knot_rrtype_should_be_lowercased(KNOT_RRTYPE_RRSIG) != 0,
+ "rrtype is RRSIG");
+
+ ret = knot_opt_code_to_string(0, name, BUF_LEN);
+ ok(ret != -1 && strcmp(name, "CODE0") == 0, "opt to str, code 0");
+ ret = knot_opt_code_to_string(10, name, BUF_LEN);
+ ok(ret != -1 && strcmp(name, "COOKIE") == 0, "opt to str, code 10");
+ ret = knot_opt_code_to_string(65535, name, BUF_LEN);
+ ok(ret != -1 && strcmp(name, "CODE65535") == 0, "opt to str, code 65535");
+
+ return 0;
+}
diff --git a/tests/libknot/test_dname.c b/tests/libknot/test_dname.c
new file mode 100644
index 0000000..2480135
--- /dev/null
+++ b/tests/libknot/test_dname.c
@@ -0,0 +1,618 @@
+/* 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 <stdlib.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "libknot/dname.h"
+
+/* Test dname_parse_from_wire */
+static int test_fw(size_t l, const char *w) {
+ const uint8_t *np = NULL;
+ if (w != NULL) {
+ np = (const uint8_t *)w + l;
+ }
+ return knot_dname_wire_check((const uint8_t *)w, np, NULL) > 0;
+}
+
+/* Test dname to/from string operations */
+static void test_str(const char *in_str, const char *in_bin, size_t bin_len)
+{
+ knot_dname_storage_t d1;
+ knot_dname_txt_storage_t s1;
+ knot_dname_t *d2 = NULL, *aux_d = NULL;
+ char *s2 = NULL, *aux_s = NULL;
+ int ret = 0;
+
+ /* dname_from_str */
+ aux_d = knot_dname_from_str(d1, in_str, sizeof(d1));
+ ok(aux_d != NULL, "dname_from_str: %s", in_str);
+ if (aux_d == NULL) {
+ skip_block(10, "dname_from_str: %s", in_str);
+ return;
+ }
+
+ /* dname_wire_check */
+ ret = knot_dname_wire_check(d1, d1 + sizeof(d1), NULL);
+ ok(ret == bin_len, "dname_wire_check: %s", in_str);
+
+ /* dname compare */
+ ok(memcmp(d1, in_bin, bin_len) == 0, "dname compare: %s", in_str);
+
+ /* dname_to_str */
+ aux_s = knot_dname_to_str(s1, d1, sizeof(s1));
+ ok(aux_s != NULL, "dname_to_str: %s", in_str);
+ if (aux_s == NULL) {
+ skip_block(7, "dname_to_str: %s", in_str);
+ return;
+ }
+
+ /* dname_from_str_alloc */
+ d2 = knot_dname_from_str_alloc(s1);
+ ok(d2 != NULL, "dname_from_str_alloc: %s", s1);
+ if (d2 == NULL) {
+ skip_block(6, "dname_from_str_alloc: %s", s1);
+ return;
+ }
+
+ /* dname_wire_check */
+ ret = knot_dname_wire_check(d2, d2 + bin_len, NULL);
+ ok(ret == bin_len, "dname_wire_check: %s", s1);
+
+ /* dname compare */
+ ok(d2 && memcmp(d2, in_bin, bin_len) == 0, "dname compare: %s", s1);
+
+ /* dname_to_str_alloc */
+ s2 = knot_dname_to_str_alloc(d2);
+ knot_dname_free(d2, NULL);
+ ok(s2 != NULL, "dname_to_str_alloc: %s", s1);
+ if (s2 == NULL) {
+ skip_block(3, "dname_to_str_alloc: %s", s1);
+ return;
+ }
+
+ /* As the string representation is ambiguous, the following steps
+ * are just for comparison in wire form.
+ */
+ d2 = knot_dname_from_str_alloc(s2);
+ ok(d2 != NULL, "dname_from_str_alloc: %s", s2);
+ if (aux_d == NULL) {
+ skip_block(2, "dname_from_str_alloc: %s", s2);
+ free(s2);
+ return;
+ }
+
+ /* dname_wire_check */
+ ret = knot_dname_wire_check(d2, d2 + bin_len, NULL);
+ ok(ret == bin_len, "dname_wire_check: %s", s2);
+
+ /* dname compare */
+ ok(d2 && memcmp(d2, in_bin, bin_len) == 0, "dname compare: %s", s2);
+
+ knot_dname_free(d2, NULL);
+ free(s2);
+}
+
+static void test_dname_lf(void)
+{
+ knot_dname_storage_t storage;
+
+ /* Maximal DNAME length */
+ const knot_dname_t *in = (uint8_t *)
+ "\x3f""iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii"
+ "\x3f""hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"
+ "\x3f""ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg"
+ "\x1f""fffffffffffffffffffffffffffffff"
+ "\x0f""eeeeeeeeeeeeeee"
+ "\x07""ddddddd"
+ "\x03""ccc"
+ "\x01""b"
+ "\x00";
+ const uint8_t *ref = (uint8_t *)
+ "\xFE"
+ "b""\x00"
+ "ccc""\00"
+ "ddddddd""\x00"
+ "eeeeeeeeeeeeeee""\x00"
+ "fffffffffffffffffffffffffffffff""\x00"
+ "ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg""\x00"
+ "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh""\x00"
+ "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii""\x00";
+ assert(strlen((const char *)in) == KNOT_DNAME_MAXLEN - 1);
+ uint8_t *out = knot_dname_lf(in, storage);
+ ok(out != NULL && memcmp(ref, out, KNOT_DNAME_MAXLEN) == 0,
+ "knot_dname_lf: max-length DNAME converted");
+
+ /* Zero label DNAME*/
+ in = (uint8_t *) "\x00";
+ out = knot_dname_lf(in, storage);
+ ok(out != NULL && out[0] == '\x00', "knot_dname_lf: zero-label DNAME converted");
+}
+
+static void test_dname_storage(void)
+{
+ const knot_dname_t *dname = (uint8_t *)"\x04""test";
+ size_t dname_len = knot_dname_size(dname);
+
+ knot_dname_storage_t storage;
+ size_t store_len = knot_dname_store(storage, dname);
+ size_t storage_len = knot_dname_size(storage);
+
+ ok(store_len == dname_len && storage_len == dname_len &&
+ memcmp(storage, dname, dname_len) == 0,
+ "knot_dname_storage: valid name");
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ knot_dname_t *d = NULL, *d2 = NULL;
+ const char *w = NULL, *t = NULL;
+ char *s = NULL;
+
+ /* DNAME WIRE CHECKS */
+
+ /* NULL wire */
+ ok(!test_fw(0, NULL), "parsing NULL dname");
+
+ /* empty label */
+ ok(test_fw(1, ""), "parsing empty dname");
+
+ /* incomplete dname */
+ ok(!test_fw(5, "\x08" "dddd"), "parsing incomplete wire");
+
+ /* non-fqdn */
+ ok(!test_fw(3, "\x02" "ab"), "parsing non-fqdn name");
+
+ /* label length == 63 */
+ w = "\x3f" "123456789012345678901234567890123456789012345678901234567890123";
+ ok(test_fw(1 + 63 + 1, w), "parsing label length == 63");
+
+ /* label length > 63 */
+ w = "\x40" "1234567890123456789012345678901234567890123456789012345678901234";
+ ok(!test_fw(1 + 64 + 1, w), "parsing label length > 63");
+
+ /* label count == 127 (also maximal dname length) */
+ w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+ ok(test_fw(127 * 2 + 1, w), "parsing label count == 127");
+
+ /* label count > 127 */
+ w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+ ok(!test_fw(128 * 2 + 1, w), "parsing label count > 127");
+
+ /* dname length > 255 */
+ w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x02\x64\x64";
+ ok(!test_fw(126 * 2 + 3 + 1, w), "parsing dname len > 255");
+
+ /* DNAME STRING CHECKS */
+
+ /* root dname */
+ test_str(".", "\x00", 1);
+
+ /* 1-char dname */
+ test_str("a.", "\x01""a", 2 + 1);
+
+ /* 1-char dname - non-fqdn */
+ test_str("a", "\x01""a", 2 + 1);
+
+ /* wildcard and asterisks */
+ test_str("*.*a.a*a.**.",
+ "\x01" "*" "\x02" "*a" "\x03" "a*a" "\x02" "**",
+ 2 + 3 + 4 + 3 + 1);
+
+ /* special label */
+ test_str("\\000\\0320\\ \\\\\\\"\\.\\@\\*.",
+ "\x09" "\x00\x20\x30\x20\x5c\x22.@*",
+ 10 + 1);
+
+ /* unescaped special characters */
+ test_str("_a.b-c./d.",
+ "\x02" "_a" "\x03" "b-c" "\x02" "/d",
+ 3 + 4 + 3 + 1);
+
+ /* all possible characters */
+ test_str("\\000\\001\\002\\003\\004\\005\\006\\007\\008\\009\\010\\011\\012\\013\\014\\015\\016\\017\\018\\019",
+ "\x14" "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13",
+ 22);
+ test_str("\\020\\021\\022\\023\\024\\025\\026\\027\\028\\029\\030\\031\\032\\033\\034\\035\\036\\037\\038\\039",
+ "\x14" "\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27",
+ 22);
+ test_str("\\040\\041\\042\\043\\044\\045\\046\\047\\048\\049\\050\\051\\052\\053\\054\\055\\056\\057\\058\\059",
+ "\x14" "\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b",
+ 22);
+ test_str("\\060\\061\\062\\063\\064\\065\\066\\067\\068\\069\\070\\071\\072\\073\\074\\075\\076\\077\\078\\079",
+ "\x14" "\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f",
+ 22);
+ test_str("\\080\\081\\082\\083\\084\\085\\086\\087\\088\\089\\090\\091\\092\\093\\094\\095\\096\\097\\098\\099",
+ "\x14" "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63",
+ 22);
+ test_str("\\100\\101\\102\\103\\104\\105\\106\\107\\108\\109\\110\\111\\112\\113\\114\\115\\116\\117\\118\\119",
+ "\x14" "\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77",
+ 22);
+ test_str("\\120\\121\\122\\123\\124\\125\\126\\127\\128\\129\\130\\131\\132\\133\\134\\135\\136\\137\\138\\139",
+ "\x14" "\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b",
+ 22);
+ test_str("\\140\\141\\142\\143\\144\\145\\146\\147\\148\\149\\150\\151\\152\\153\\154\\155\\156\\157\\158\\159",
+ "\x14" "\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f",
+ 22);
+ test_str("\\160\\161\\162\\163\\164\\165\\166\\167\\168\\169\\170\\171\\172\\173\\174\\175\\176\\177\\178\\179",
+ "\x14" "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3",
+ 22);
+ test_str("\\180\\181\\182\\183\\184\\185\\186\\187\\188\\189\\190\\191\\192\\193\\194\\195\\196\\197\\198\\199",
+ "\x14" "\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7",
+ 22);
+ test_str("\\200\\201\\202\\203\\204\\205\\206\\207\\208\\209\\210\\211\\212\\213\\214\\215\\216\\217\\218\\219",
+ "\x14" "\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb",
+ 22);
+ test_str("\\220\\221\\222\\223\\224\\225\\226\\227\\228\\229\\230\\231\\232\\233\\234\\235\\236\\237\\238\\239",
+ "\x14" "\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef",
+ 22);
+ test_str("\\240\\241\\242\\243\\244\\245\\246\\247\\248\\249\\250\\251\\252\\253\\254\\255",
+ "\x10" "\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff",
+ 18);
+
+ /* maximal dname label length */
+ test_str("12345678901234567890123456789012345678901234567890123456789012\\063",
+ "\x3f" "12345678901234567890123456789012345678901234567890123456789012?",
+ 65);
+
+ /* maximal dname length */
+ test_str("1234567890123456789012345678901234567890123456789."
+ "1234567890123456789012345678901234567890123456789."
+ "1234567890123456789012345678901234567890123456789."
+ "1234567890123456789012345678901234567890123456789."
+ "\\#234567890123456789012345678901234567890123456789012\\063",
+ "\x31" "1234567890123456789012345678901234567890123456789"
+ "\x31" "1234567890123456789012345678901234567890123456789"
+ "\x31" "1234567890123456789012345678901234567890123456789"
+ "\x31" "1234567890123456789012345678901234567890123456789"
+ "\x35" "#234567890123456789012345678901234567890123456789012?",
+ 255);
+
+ /* NULL output, positive maxlen */
+ w = "\x02" "aa";
+ s = knot_dname_to_str(NULL, (const uint8_t *)w, 1);
+ ok(s != NULL, "dname_to_str: null dname");
+ if (s != NULL) {
+ ok(memcmp(s, "aa.", 4) == 0, "dname_to_str: null dname compare");
+ free(s);
+ } else {
+ skip("dname_to_str: null dname");
+ }
+
+ /* non-NULL output, zero maxlen */
+ char s_small[2];
+ s = knot_dname_to_str(s_small, (const uint8_t *)w, 0);
+ ok(s == NULL, "dname_to_str: non-NULL output, zero maxlen");
+
+ /* small buffer */
+ s = knot_dname_to_str(s_small, (const uint8_t *)w, 1);
+ ok(s == NULL, "dname_to_str: small buffer");
+
+ /* NULL dname */
+ s = knot_dname_to_str_alloc(NULL);
+ ok(s == NULL, "dname_to_str: null dname");
+
+ /* empty dname is considered as a root dname */
+ w = "";
+ s = knot_dname_to_str_alloc((const uint8_t *)w);
+ ok(s != NULL, "dname_to_str: empty dname");
+ if (s != NULL) {
+ ok(memcmp(s, ".", 1) == 0, "dname_to_str: empty dname is root dname");
+ free(s);
+ } else {
+ skip("dname_to_str: empty dname");
+ }
+
+ /* incomplete dname */
+ /* ASAN: global-buffer-overflow
+ w = "\x08" "dddd";
+ s = knot_dname_to_str_alloc((const uint8_t *)w);
+ ok(s != NULL, "dname_to_str: incomplete dname");
+ free(s);
+ */
+
+ /* non-fqdn */
+ w = "\x02" "ab";
+ s = knot_dname_to_str_alloc((const uint8_t *)w);
+ ok(s != NULL, "dname_to_str: non-fqdn");
+ free(s);
+
+ /* label length > 63 */
+ w = "\x40" "1234567890123456789012345678901234567890123456789012345678901234";
+ s = knot_dname_to_str_alloc((const uint8_t *)w);
+ ok(s != NULL, "dname_to_str: label length > 63");
+ free(s);
+
+ /* label count > 127 */
+ w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64";
+ s = knot_dname_to_str_alloc((const uint8_t *)w);
+ ok(s != NULL, "dname_to_str: label count > 127");
+ free(s);
+
+ /* dname length > 255 */
+ w = "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64"
+ "\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x01\x64\x02\x64\x64";
+ s = knot_dname_to_str_alloc((const uint8_t *)w);
+ ok(s != NULL, "dname_to_str: dname length > 255");
+ free(s);
+
+ /* output overflow sanity check */
+ uint8_t in[4] = "\x02""\x00\x00""\x00";
+ for (uint16_t i = 0; i < UINT16_MAX; i++) {
+ memcpy(in + 1, &i, sizeof(i));
+ for (int j = 3; j < 8; j++) {
+ char tmp[j];
+ char *out_static = knot_dname_to_str(tmp, in, sizeof(tmp));
+ char *out_dynamic = knot_dname_to_str_alloc(in);
+ if (out_dynamic == NULL) {
+ ok(out_dynamic != NULL, "dname_to_str_alloc: invalid input");
+ } else if (strlen(out_dynamic) < sizeof(tmp) - 1 &&
+ out_static == NULL) {
+ ok(out_static != NULL, "dname_to_str: invalid input");
+ }
+ free(out_dynamic);
+ }
+ }
+
+ /* NULL output, positive maxlen */
+ s = "aa.";
+ d = knot_dname_from_str(NULL, s, 1);
+ ok(s != NULL, "dname_from_str: null name");
+ if (s != NULL) {
+ ok(memcmp(d, "\x02" "aa", 4) == 0, "dname_from_str: null name compare");
+ free(d);
+ } else {
+ skip("dname_from_str: null name");
+ }
+
+ /* non-NULL output, zero maxlen */
+ uint8_t d_small[2];
+ d = knot_dname_from_str(d_small, s, 0);
+ ok(d == NULL, "dname_from_str: non-NULL output, zero maxlen");
+
+ /* small buffer */
+ d = knot_dname_from_str(d_small, s, 1);
+ ok(d == NULL, "dname_from_str: small buffer");
+
+ /* NULL string */
+ d = knot_dname_from_str_alloc(NULL);
+ ok(d == NULL, "dname_from_str: null string");
+
+ /* empty string */
+ t = "";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: empty string");
+
+ /* empty label */
+ t = "..";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: empty label");
+
+ /* leading dot */
+ t = ".a";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: leading dot");
+
+ /* incomplete decimal notation I */
+ t = "\\1";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: incomplete decimal I");
+
+ /* incomplete decimal notation II */
+ t = "\\12";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: incomplete decimal II");
+
+ /* invalid decimal notation I */
+ t = "\\256";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: invalid decimal I");
+
+ /* invalid decimal notation II */
+ t = "\\2x6";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: invalid decimal II");
+
+ /* invalid escape notation */
+ t = "\\2";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: invalid escape");
+
+ /* label length > 63 I */
+ t = "1234567890123456789012345678901234567890123456789012345678901234";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: label length > 63 I");
+
+ /* label length > 63 II */
+ t = "123456789012345678901234567890123456789012345678901234567890123\\?";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: label length > 63 II");
+
+ /* label length > 63 III */
+ t = "123456789012345678901234567890123456789012345678901234567890123\\063";
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: label length > 63 III");
+
+ /* dname length > 255 */
+ t = "1234567890123456789012345678901234567890123456789."
+ "1234567890123456789012345678901234567890123456789."
+ "1234567890123456789012345678901234567890123456789."
+ "1234567890123456789012345678901234567890123456789."
+ "123456789012345678901234567890123456789012345678901234.",
+ d = knot_dname_from_str_alloc(t);
+ ok(d == NULL, "dname_from_str: dname length > 255");
+
+ /* DNAME SUBDOMAIN CHECKS */
+
+ /* equal name is subdomain */
+ t = "ab.cd.ef";
+ d2 = knot_dname_from_str_alloc(t);
+ t = "ab.cd.ef";
+ d = knot_dname_from_str_alloc(t);
+ ok(knot_dname_in_bailiwick(d, d2) == 0, "dname_subdomain: equal name");
+ knot_dname_free(d, NULL);
+
+ /* true subdomain */
+ t = "0.ab.cd.ef";
+ d = knot_dname_from_str_alloc(t);
+ ok(knot_dname_in_bailiwick(d, d2) == 1, "dname_subdomain: true subdomain");
+ knot_dname_free(d, NULL);
+
+ /* not subdomain */
+ t = "cd.ef";
+ d = knot_dname_from_str_alloc(t);
+ ok(knot_dname_in_bailiwick(d, d2) < 0, "dname_subdomain: not subdomain");
+ knot_dname_free(d, NULL);
+
+ /* root subdomain */
+ t = ".";
+ d = knot_dname_from_str_alloc(t);
+ ok(knot_dname_in_bailiwick(d2, d) == 3, "dname_subdomain: root subdomain");
+ knot_dname_free(d, NULL);
+ knot_dname_free(d2, NULL);
+
+ /* DNAME EQUALITY CHECKS */
+
+ t = "ab.cd.ef";
+ d = knot_dname_from_str_alloc(t);
+ ok(knot_dname_is_equal(d, d), "dname_is_equal: equal names");
+
+ t = "ab.cd.fe";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: same label count");
+ knot_dname_free(d2, NULL);
+
+ t = "ab.cd";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: len(d1) < len(d2)");
+ knot_dname_free(d2, NULL);
+
+ t = "ab.cd.ef.gh";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: len(d1) > len(d2)");
+ knot_dname_free(d2, NULL);
+
+ t = "ab.cd.efe";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: last label longer");
+ knot_dname_free(d2, NULL);
+
+ t = "ab.cd.e";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(!knot_dname_is_equal(d, d2), "dname_is_equal: last label shorter");
+ knot_dname_free(d2, NULL);
+
+ knot_dname_free(d, NULL);
+
+ /* DNAME EQUALITY CHECKS IGNORING CASE */
+
+ t = "aB.cd.ef";
+ d = knot_dname_from_str_alloc(t);
+ ok(knot_dname_is_case_equal(d, d), "dname_is_case_equal: equal case");
+
+ t = "aB.cD.ef";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(knot_dname_is_case_equal(d, d2), "dname_is_case_equal: different case");
+ knot_dname_free(d2, NULL);
+
+ t = "aB.dc.ef";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(!knot_dname_is_case_equal(d, d2), "dname_is_case_equal: different name");
+ knot_dname_free(d2, NULL);
+
+ t = "aB.cd";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(!knot_dname_is_case_equal(d, d2), "dname_is_case_equal: different length");
+ knot_dname_free(d2, NULL);
+
+ t = "aB.cdx.ef";
+ d2 = knot_dname_from_str_alloc(t);
+ ok(!knot_dname_is_case_equal(d, d2), "dname_is_case_equal: different label");
+ knot_dname_free(d2, NULL);
+
+ knot_dname_free(d, NULL);
+
+ /* OTHER CHECKS */
+
+ test_dname_lf();
+
+ test_dname_storage();
+
+ return 0;
+}
diff --git a/tests/libknot/test_dynarray.c b/tests/libknot/test_dynarray.c
new file mode 100644
index 0000000..ac1e0f1
--- /dev/null
+++ b/tests/libknot/test_dynarray.c
@@ -0,0 +1,137 @@
+/* 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 <string.h>
+#include <tap/basic.h>
+
+#include "libknot/dynarray.h"
+
+#define test_capacity 5
+// minimum 3
+
+typedef struct {
+ int x;
+ int x2;
+} quadrate_t;
+
+knot_dynarray_declare(q, quadrate_t, DYNARRAY_VISIBILITY_STATIC, test_capacity);
+knot_dynarray_define(q, quadrate_t, DYNARRAY_VISIBILITY_STATIC);
+
+static q_dynarray_t q_fill(size_t howmany)
+{
+ quadrate_t q = { 0 };
+ q_dynarray_t qd = { 0 };
+
+ for (size_t i = 0; i < howmany; i++) {
+ q.x2 = q.x * q.x;
+ q_dynarray_add(&qd, &q);
+ q.x++;
+ }
+ return qd;
+}
+
+static void check_arr(q_dynarray_t *q, size_t count, size_t index, const char *msg)
+{
+ quadrate_t *arr = q->arr(q);
+ ok(arr[index].x == index && arr[index].x2 == index * index,
+ "%s check: index %zu", msg, index);
+
+ size_t i = 0;
+ knot_dynarray_foreach(q, quadrate_t, p, *q) {
+ ok(p->x == i && p->x2 == i * i, "%s foreach: index %zu", msg, i);
+ i++;
+ }
+
+ ok(i == count, "%s foreach: whole array", msg);
+}
+
+static size_t q_set_dups(q_dynarray_t *q, double dup_percentage, const quadrate_t *dupval)
+{
+ size_t dup_cnt = 0;
+ int threshold = (int)(dup_percentage / 100 * ((double)RAND_MAX + 1.0));
+
+ knot_dynarray_foreach(q, quadrate_t, item, *q) {
+ if (rand() < threshold && q_dynarray_memb_cmp(item, dupval) != 0) {
+ *item = *dupval;
+ dup_cnt++;
+ }
+ }
+
+ return dup_cnt;
+}
+
+static void check_dups(q_dynarray_t *q, const quadrate_t *dupval,
+ const size_t expected, const char *msg)
+{
+ size_t cnt = 0;
+ knot_dynarray_foreach(q, quadrate_t, item, *q) {
+ if (q_dynarray_memb_cmp(item, dupval) == 0) {
+ cnt++;
+ }
+ }
+ ok(cnt == expected, "duplicate items: %s", msg);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ // first fill
+ q_dynarray_t q = q_fill(test_capacity - 1);
+ check_arr(&q, test_capacity - 1, test_capacity - 3, "initial");
+ q_dynarray_free(&q);
+
+ // second fill
+ q = q_fill(test_capacity + 3);
+ check_arr(&q, test_capacity + 3, test_capacity + 1, "second");
+ q_dynarray_free(&q);
+
+ // third fill
+ q = q_fill(test_capacity * 5);
+ check_arr(&q, test_capacity * 5, test_capacity * 4, "third");
+ q_dynarray_free(&q);
+
+ // duplicate items removal test
+ q = q_fill(test_capacity * 10);
+ quadrate_t dup_item = { .x = 0, .x2 = 0 }; // matches the first item
+ size_t dups = q_set_dups(&q, 50, &dup_item);
+ ok(q.size == test_capacity * 10, "duplicate items: created");
+ check_dups(&q, &dup_item, dups + 1, "created all");
+
+ q_dynarray_remove(&q, &dup_item);
+ ok(q.size == test_capacity * 10 - dups - 1, "duplicate items: removed");
+ check_dups(&q, &dup_item, 0, "removed all");
+
+ q_dynarray_free(&q);
+
+ // binary search removal test
+ q = q_fill(test_capacity * 10);
+ for (int i = 0; i < test_capacity * 10; i++) {
+ quadrate_t qu = { i, i * i };
+ if ((qu.x % 2) == 0) {
+ q_dynarray_remove(&q, &qu);
+ }
+ }
+ q_dynarray_sort(&q);
+ for (int i = 0; i < test_capacity * 10; i++) {
+ quadrate_t qu = { i, i * i };
+ int present = (q_dynarray_bsearch(&q, &qu) != NULL ? 1 : 0);
+ ok(present == (i % 2), "presence in sorted array %d", i);
+ }
+ q_dynarray_free(&q);
+
+ return 0;
+}
diff --git a/tests/libknot/test_edns.c b/tests/libknot/test_edns.c
new file mode 100644
index 0000000..d2b4fd8
--- /dev/null
+++ b/tests/libknot/test_edns.c
@@ -0,0 +1,502 @@
+/* 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 <tap/basic.h>
+
+#include <assert.h>
+#include "libknot/libknot.h"
+#include "libknot/rrtype/opt.h"
+#include "libknot/descriptor.h"
+#include "libknot/wire.h"
+#include "contrib/sockaddr.h"
+
+static const uint16_t E_MAX_PLD = 10000;
+static const uint16_t E_MAX_PLD2 = 20000;
+static const uint8_t E_VERSION = 1;
+static const uint8_t E_VERSION2 = 2;
+static const uint8_t E_RCODE = 0;
+static const uint8_t E_RCODE2 = 200;
+
+static const char *E_NSID_STR = "FooBar";
+static const char *E_NSID_STR2 = "BarFoo";
+static const uint16_t E_NSID_LEN = 6;
+
+#define E_NSID_SIZE (4 + E_NSID_LEN)
+
+static const uint16_t E_OPT3_CODE = 15;
+static const char *E_OPT3_FAKE_DATA = "Not used";
+static const char *E_OPT3_DATA = NULL;
+static const uint16_t E_OPT3_LEN = 0;
+static const uint16_t E_OPT3_FAKE_LEN = 8;
+
+#define E_OPT3_SIZE (4 + E_OPT3_LEN)
+
+static const uint16_t E_OPT4_CODE = 30;
+static const char *E_OPT4_DATA = NULL;
+static const uint16_t E_OPT4_LEN = 0;
+
+#define E_OPT4_SIZE (4 + E_OPT4_LEN)
+
+enum offsets {
+ /*! \brief Offset of Extended RCODE in wire order of TTL. */
+ OFFSET_ERCODE = 0,
+ /*! \brief Offset of Version in wire order of TTL. */
+ OFFSET_VER = 1,
+ /*! \brief Offset of Flags in wire order of TTL. */
+ OFFSET_FLAGS = 2,
+ /*! \brief Offset of OPTION code in one OPTION in RDATA. */
+ OFFSET_OPT_CODE = 0,
+ /*! \brief Offset of OPTION size in one OPTION in RDATA. */
+ OFFSET_OPT_SIZE = 2,
+ /*! \brief Offset of OPTION data in one OPTION in RDATA. */
+ OFFSET_OPT_DATA = 4
+};
+
+static const uint16_t DO_FLAG = (uint16_t)1 << 15;
+
+static void check_ttl(knot_rrset_t *rrset, uint8_t ext_rcode, uint8_t ver,
+ uint16_t flags, char *msg)
+{
+ if (rrset == NULL) {
+ return;
+ }
+
+ /* TTL should be stored in machine byte order.
+ We need network byte order to compare its parts. */
+ uint8_t ttl_wire[4] = { 0, 0, 0, 0 };
+ knot_wire_write_u32(ttl_wire, rrset->ttl);
+
+ /* Convert Flags from EDNS parameters to wire format for comparison. */
+ uint8_t flags_wire[2] = { 0, 0 };
+ knot_wire_write_u16(flags_wire, flags);
+
+ /* TTL = Ext RCODE + Version + Flags */
+ bool check = (ttl_wire[OFFSET_ERCODE] == ext_rcode);
+ ok(check, "%s: extended RCODE", msg);
+
+ check = (ttl_wire[OFFSET_VER] == ver);
+ ok(check, "%s: version", msg);
+
+ check = (memcmp(flags_wire, ttl_wire + OFFSET_FLAGS, 2) == 0);
+ ok(check, "%s: flags", msg);
+}
+
+static void check_option(knot_rdata_t *rdata, uint16_t opt_code,
+ uint16_t opt_len, uint8_t *opt_data, char *msg)
+{
+ assert(rdata != NULL);
+
+ uint8_t *data = rdata->data;
+ uint16_t data_len = rdata->len;
+
+ /* Check RDLENGTH according to given data length. */
+ bool check = (data_len >= 4 + opt_len);
+ ok(check, "%s: RDLENGTH (%u)", msg, data_len);
+
+ /* Find the desired option. */
+ bool found = false;
+ int pos = 0;
+ while (pos <= data_len - 4) {
+ uint16_t code = knot_wire_read_u16(data + pos + OFFSET_OPT_CODE);
+ if (code == opt_code) {
+ found = true;
+ break;
+ }
+ uint16_t len = knot_wire_read_u16(data + pos + OFFSET_OPT_SIZE);
+ pos += 4 + len;
+ }
+
+ /* Check that the option is present. */
+ ok(found, "%s: find OPTION %u in OPT RR", msg, opt_code);
+
+ /* Check that the first OPTION's size si the size of the option data. */
+ uint16_t opt_size = knot_wire_read_u16(data + pos + OFFSET_OPT_SIZE);
+ check = (opt_size == opt_len);
+ ok(check, "%s: OPTION data size", msg);
+
+ /* Check the actual NSID data. */
+ check = (opt_data == 0 || memcmp(data + pos + OFFSET_OPT_DATA, opt_data, opt_len) == 0);
+ ok(check, "%s: OPTION data", msg);
+}
+
+static void check_header(knot_rrset_t *opt_rr, uint16_t payload, uint8_t ver,
+ uint16_t flags, uint8_t ext_rcode, char *msg)
+{
+ assert(opt_rr != NULL);
+ bool check;
+
+ /* Check values in OPT RR by hand. */
+ /* CLASS == Max UDP payload */
+ check = (opt_rr->rclass == payload);
+ ok(check, "%s: max payload", msg);
+
+ /* The OPT RR should have exactly one RDATA. */
+ check = (opt_rr->rrs.count == 1);
+ ok(check, "%s: RR count == 1", msg);
+
+ knot_rdata_t *rdata = opt_rr->rrs.rdata;
+ check = (rdata != NULL);
+ ok(check, "%s: RDATA exists", msg);
+
+ check_ttl(opt_rr, ext_rcode, ver, flags, msg);
+}
+
+static void test_getters(knot_rrset_t *opt_rr)
+{
+ assert(opt_rr != NULL);
+
+ /* These values should be set from the setters test:
+ * Max UDP payload: E_MAX_PLD2
+ * Version: E_VERSION2
+ * RCODE: E_RCODE2
+ * Flags: E_FLAGS | KNOT_EDNS_FLAG_DO
+ * OPTIONs: 1) KNOT_EDNS_OPTION_NSID, E_NSID_LEN, E_NSID_STR
+ * 2) E_OPT3_CODE, 0, 0
+ * 3) E_OPT4_CODE, 0, 0
+ * 4) KNOT_EDNS_OPTION_NSID, E_NSID_LEN, E_NSID_STR2
+ */
+
+ /* Payload */
+ bool check = (knot_edns_get_payload(opt_rr) == E_MAX_PLD2);
+ ok(check, "OPT RR getters: payload");
+
+ /* Extended RCODE */
+ check = (knot_edns_get_ext_rcode(opt_rr) == E_RCODE2);
+ ok(check, "OPT RR getters: extended RCODE");
+
+ /* Extended RCODE */
+ check = (knot_edns_get_version(opt_rr) == E_VERSION2);
+ ok(check, "OPT RR getters: version");
+
+ /* DO bit */
+ check = knot_edns_do(opt_rr);
+ ok(check, "OPT RR getters: DO bit check");
+
+ /* Wire size */
+ size_t total_size = KNOT_EDNS_MIN_SIZE
+ + 2 * E_NSID_SIZE + E_OPT3_SIZE + E_OPT4_SIZE;
+ size_t actual_size = knot_edns_wire_size(opt_rr);
+ check = actual_size == total_size;
+ ok(check, "OPT RR getters: wire size (expected: %zu, actual: %zu)",
+ total_size, actual_size);
+
+ /* NSID */
+ uint8_t *nsid1 = knot_edns_get_option(opt_rr, KNOT_EDNS_OPTION_NSID, NULL);
+ check = nsid1 != NULL;
+ ok(check, "OPT RR getters: NSID check");
+ check = memcmp(knot_edns_opt_get_data(nsid1), E_NSID_STR, knot_edns_opt_get_length(nsid1)) == 0;
+ ok(check, "OPT RR getters: NSID value check");
+
+ /* Another NSID */
+ uint8_t *nsid2 = knot_edns_get_option(opt_rr, KNOT_EDNS_OPTION_NSID, nsid1);
+ check = nsid2 != NULL;
+ ok(check, "OPT RR getters: another NSID check");
+ check = memcmp(knot_edns_opt_get_data(nsid2), E_NSID_STR2, knot_edns_opt_get_length(nsid2)) == 0;
+ ok(check, "OPT RR getters: another NSID value check");
+
+ /* Other OPTIONs */
+ check = knot_edns_get_option(opt_rr, E_OPT3_CODE, NULL) != NULL;
+ ok(check, "OPT RR getters: empty option 1");
+
+ check = knot_edns_get_option(opt_rr, E_OPT4_CODE, NULL) != NULL;
+ ok(check, "OPT RR getters: empty option 2");
+
+ uint16_t code = knot_edns_opt_get_code((const uint8_t *)"\x00\x0a" "\x00\x00");
+ ok(code == KNOT_EDNS_OPTION_COOKIE, "OPT RR getters: EDNS OPT code");
+}
+
+static void test_setters(knot_rrset_t *opt_rr)
+{
+ assert(opt_rr != NULL);
+
+ /* Header-related setters. */
+ knot_edns_set_payload(opt_rr, E_MAX_PLD2);
+ knot_edns_set_ext_rcode(opt_rr, E_RCODE2);
+ knot_edns_set_version(opt_rr, E_VERSION2);
+ knot_edns_set_do(opt_rr);
+
+ check_header(opt_rr, E_MAX_PLD2, E_VERSION2, DO_FLAG, E_RCODE2,
+ "OPT RR setters");
+
+ /* OPTION(RDATA)-related setters. */
+
+ /* Proper option NSID. */
+ int ret = knot_edns_add_option(opt_rr, KNOT_EDNS_OPTION_NSID,
+ E_NSID_LEN, (uint8_t *)E_NSID_STR, NULL);
+ is_int(KNOT_EOK, ret, "OPT RR setters: add option with data (ret = %s)",
+ knot_strerror(ret));
+
+ /* Wrong argument: no OPT RR. */
+ ret = knot_edns_add_option(NULL, E_OPT3_CODE, E_OPT3_FAKE_LEN,
+ (uint8_t *)E_OPT3_FAKE_DATA, NULL);
+ is_int(KNOT_EINVAL, ret, "OPT RR setters: add option (rr == NULL) "
+ "(ret = %s)", knot_strerror(ret));
+
+ /* Wrong argument: option length != 0 && data == NULL. */
+ ret = knot_edns_add_option(opt_rr, E_OPT3_CODE, E_OPT3_FAKE_LEN, NULL,
+ NULL);
+ is_int(KNOT_EINVAL, ret, "OPT RR setters: add option (data == NULL, "
+ "len != 0) (ret = %s)", knot_strerror(ret));
+
+ /* Empty OPTION (length 0, data != NULL). */
+ ret = knot_edns_add_option(opt_rr, E_OPT3_CODE, E_OPT3_LEN,
+ (uint8_t *)E_OPT3_FAKE_DATA, NULL);
+ is_int(KNOT_EOK, ret, "OPT RR setters: add empty option 1 (ret = %s)",
+ knot_strerror(ret));
+
+ /* Empty OPTION (length 0, data == NULL). */
+ ret = knot_edns_add_option(opt_rr, E_OPT4_CODE, E_OPT4_LEN,
+ (uint8_t *)E_OPT4_DATA, NULL);
+ is_int(KNOT_EOK, ret, "OPT RR setters: add empty option 2 (ret = %s)",
+ knot_strerror(ret));
+
+ /* Another option NSID. */
+ ret = knot_edns_add_option(opt_rr, KNOT_EDNS_OPTION_NSID,
+ E_NSID_LEN, (uint8_t *)E_NSID_STR2, NULL);
+ is_int(KNOT_EOK, ret, "OPT RR setters: add option with data (ret = %s)",
+ knot_strerror(ret));
+
+ knot_rdata_t *rdata = opt_rr->rrs.rdata;
+ ok(rdata != NULL, "OPT RR setters: non-empty RDATA");
+
+ /* Check proper option NSID */
+ check_option(rdata, KNOT_EDNS_OPTION_NSID, E_NSID_LEN,
+ (uint8_t *)E_NSID_STR, "OPT RR setters (proper option)");
+
+ /* Check empty option 1 */
+ check_option(rdata, E_OPT3_CODE, E_OPT3_LEN,
+ (uint8_t *)E_OPT3_DATA, "OPT RR setters (empty option 1)");
+
+ /* Check empty option 2 */
+ check_option(rdata, E_OPT4_CODE, E_OPT4_LEN,
+ (uint8_t *)E_OPT4_DATA, "OPT RR setters (empty option 2)");
+}
+
+static void test_alignment(void)
+{
+ int ret;
+
+ ret = knot_edns_alignment_size(1, 1, 1);
+ ok(ret == -1, "no alignment");
+
+ ret = knot_edns_alignment_size(1, 1, 2);
+ ok(ret == -1, "no alignment");
+
+ ret = knot_edns_alignment_size(1, 1, 3);
+ ok(ret == (6 - (1 + 1 + KNOT_EDNS_OPTION_HDRLEN)), "%i-Byte alignment", ret);
+
+ ret = knot_edns_alignment_size(1, 1, 4);
+ ok(ret == (8 - (1 + 1 + KNOT_EDNS_OPTION_HDRLEN)), "%i-Byte alignment", ret);
+
+ ret = knot_edns_alignment_size(1, 1, 512);
+ ok(ret == (512 - (1 + 1 + KNOT_EDNS_OPTION_HDRLEN)), "%i-Byte alignment", ret);
+}
+
+static void test_keepalive(void)
+{
+ typedef struct {
+ char *msg;
+ uint16_t opt_len;
+ char *opt;
+ uint16_t val;
+ } test_t;
+
+ // OK tests.
+
+ static const test_t TESTS[] = {
+ { "ok 0", 0, "", 0 },
+ { "ok 1", 2, "\x00\x01", 1 },
+ { "ok 258", 2, "\x01\x02", 258 },
+ { "ok 65535", 2, "\xFF\xFF", 65535 },
+ { NULL }
+ };
+
+ for (const test_t *t = TESTS; t->msg != NULL; t++) {
+ uint16_t len = knot_edns_keepalive_size(t->val);
+ ok(len == t->opt_len, "%s: %s, size", __func__, t->msg);
+
+ uint8_t wire[8] = { 0 };
+ int ret = knot_edns_keepalive_write(wire, sizeof(wire), t->val);
+ is_int(KNOT_EOK, ret, "%s: %s, write, return", __func__, t->msg);
+ ok(memcmp(wire, t->opt, t->opt_len) == 0, "%s: %s, write, value",
+ __func__, t->msg);
+
+ uint16_t timeout = 0;
+ ret = knot_edns_keepalive_parse(&timeout, (uint8_t *)t->opt, t->opt_len);
+ is_int(KNOT_EOK, ret, "%s: %s, parse, return", __func__, t->msg);
+ ok(timeout == t->val, "%s: %s, parse, value", __func__, t->msg);
+ }
+
+ // Error tests.
+
+ uint8_t wire[8] = { 0 };
+ ok(knot_edns_keepalive_write(NULL, 0, 0) == KNOT_EINVAL,
+ "%s: write, NULL", __func__);
+ ok(knot_edns_keepalive_write(wire, 1, 1) == KNOT_ESPACE,
+ "%s: write, no room", __func__);
+
+ uint16_t timeout = 0;
+ ok(knot_edns_keepalive_parse(NULL, (const uint8_t *)"", 0) == KNOT_EINVAL,
+ "%s: parse, NULL", __func__);
+ ok(knot_edns_keepalive_parse(&timeout, NULL, 0) == KNOT_EINVAL,
+ "%s: parse, NULL", __func__);
+ ok(knot_edns_keepalive_parse(&timeout, (const uint8_t *)"\x01", 1) == KNOT_EMALF,
+ "%s: parse, malformed", __func__);
+}
+
+static void test_chain(void)
+{
+ typedef struct {
+ char *msg;
+ uint16_t opt_len;
+ knot_dname_t *dname;
+ } test_t;
+
+ // OK tests.
+
+ static const test_t TESTS[] = {
+ { ".", 1, (knot_dname_t *)"" },
+ { "a.", 3, (knot_dname_t *)"\x01" "a" },
+ { NULL }
+ };
+
+ for (const test_t *t = TESTS; t->msg != NULL; t++) {
+ uint16_t len = knot_edns_chain_size(t->dname);
+ ok(len == t->opt_len, "%s: dname %s, size", __func__, t->msg);
+
+ uint8_t wire[8] = { 0 };
+ int ret = knot_edns_chain_write(wire, sizeof(wire), t->dname);
+ is_int(KNOT_EOK, ret, "%s: dname %s, write, return", __func__, t->msg);
+ ok(memcmp(wire, t->dname, t->opt_len) == 0, "%s: dname %s, write, value",
+ __func__, t->msg);
+
+ knot_dname_t *dname = NULL;
+ ret = knot_edns_chain_parse(&dname, (uint8_t *)t->dname, t->opt_len, NULL);
+ is_int(KNOT_EOK, ret, "%s: dname %s, parse, return", __func__, t->msg);
+ ok(knot_dname_is_equal(dname, t->dname), "%s: dname %s, parse, value",
+ __func__, t->msg);
+ knot_dname_free(dname, NULL);
+ }
+
+ // Error tests.
+
+ ok(knot_edns_chain_size(NULL) == 0, "%s: size, NULL", __func__);
+
+ uint8_t wire[8] = { 0 };
+ ok(knot_edns_chain_write(NULL, 0, wire) == KNOT_EINVAL,
+ "%s: write, NULL", __func__);
+ ok(knot_edns_chain_write(wire, 0, NULL) == KNOT_EINVAL,
+ "%s: write, NULL", __func__);
+ ok(knot_edns_chain_write(wire, 0, (const knot_dname_t *)"") == KNOT_ESPACE,
+ "%s: write, no room", __func__);
+
+ knot_dname_t *dname = NULL;
+ ok(knot_edns_chain_parse(NULL, wire, 0, NULL) == KNOT_EINVAL && dname == NULL,
+ "%s: parse, NULL", __func__);
+ ok(knot_edns_chain_parse(&dname, NULL, 0, NULL) == KNOT_EINVAL && dname == NULL,
+ "%s: parse, NULL", __func__);
+ ok(knot_edns_chain_parse(&dname, (const uint8_t *)"\x01", 1, NULL) == KNOT_EMALF &&
+ dname == NULL, "%s: parse, malformed", __func__);
+}
+
+static void check_cookie_parse(const char *opt, knot_edns_cookie_t *cc,
+ knot_edns_cookie_t *sc, int code, const char *msg)
+{
+ const uint8_t *data = NULL;
+ uint16_t data_len = 0;
+ if (opt != NULL) {
+ data = knot_edns_opt_get_data((uint8_t *)opt);
+ data_len = knot_edns_opt_get_length((uint8_t *)opt);
+ }
+
+ int ret = knot_edns_cookie_parse(cc, sc, data, data_len);
+ is_int(code, ret, "cookie parse ret: %s", msg);
+}
+
+static void ok_cookie_check(const char *opt, knot_edns_cookie_t *cc,
+ knot_edns_cookie_t *sc, uint16_t cc_len, uint16_t sc_len,
+ const char *msg)
+{
+ check_cookie_parse(opt, cc, sc, KNOT_EOK, msg);
+
+ is_int(cc->len, cc_len, "cookie parse cc len: %s", msg);
+ is_int(sc->len, sc_len, "cookie parse cc len: %s", msg);
+
+ uint16_t size = knot_edns_cookie_size(cc, sc);
+ is_int(size, cc_len + sc_len, "cookie len: %s", msg);
+
+ uint8_t buf[64];
+ int ret = knot_edns_cookie_write(buf, sizeof(buf), cc, sc);
+ is_int(KNOT_EOK, ret, "cookie write ret: %s", msg);
+}
+
+static void test_cookie(void)
+{
+ const char *good[] = {
+ "\x00\x0a" "\x00\x08" "\x00\x01\x02\x03\x04\x05\x06\x07", /* Only client cookie. */
+ "\x00\x0a" "\x00\x10" "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f", /* 8 octets long server cookie. */
+ "\x00\x0a" "\x00\x28" "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27" /* 32 octets long server cookie. */
+ };
+
+ const char *bad[] = {
+ "\x00\x0a" "\x00\x00", /* Zero length cookie. */
+ "\x00\x0a" "\x00\x01" "\x00", /* Short client cookie. */
+ "\x00\x0a" "\x00\x07" "\x00\x01\x02\x03\x04\x05\x06", /* Short client cookie. */
+ "\x00\x0a" "\x00\x09" "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08", /* Short server cookie. */
+ "\x00\x0a" "\x00\x0f" "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e", /* Short server cookie. */
+ "\x00\x0a" "\x00\x29" "\x00\x01\x02\x03\x04\x05\x06\x07" "\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28", /* Long server cookie. */
+ };
+
+ knot_edns_cookie_t cc, sc;
+
+ ok_cookie_check(good[0], &cc, &sc, 8, 0, "good cookie 0");
+ ok_cookie_check(good[1], &cc, &sc, 8, 8, "good cookie 1");
+ ok_cookie_check(good[2], &cc, &sc, 8, 32, "good cookie 2");
+
+ check_cookie_parse(NULL, &cc, &sc, KNOT_EINVAL, "no data");
+ check_cookie_parse(good[0], NULL, &sc, KNOT_EINVAL, "no client cookie");
+ check_cookie_parse(good[1], &cc, NULL, KNOT_EINVAL, "no server cookie");
+
+ check_cookie_parse(bad[0], &cc, &sc, KNOT_EMALF, "bad cookie 0");
+ check_cookie_parse(bad[1], &cc, &sc, KNOT_EMALF, "bad cookie 1");
+ check_cookie_parse(bad[2], &cc, &sc, KNOT_EMALF, "bad cookie 2");
+ check_cookie_parse(bad[3], &cc, &sc, KNOT_EMALF, "bad cookie 3");
+ check_cookie_parse(bad[4], &cc, &sc, KNOT_EMALF, "bad cookie 4");
+ check_cookie_parse(bad[5], &cc, &sc, KNOT_EMALF, "bad cookie 5");
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ knot_rrset_t opt_rr;
+ int ret = knot_edns_init(&opt_rr, E_MAX_PLD, E_RCODE, E_VERSION, NULL);
+ is_int(KNOT_EOK, ret, "OPT RR: init");
+
+ /* Check initialized values (no NSID yet). */
+ check_header(&opt_rr, E_MAX_PLD, E_VERSION, 0, E_RCODE, "OPT RR: check header");
+
+ test_setters(&opt_rr);
+ test_getters(&opt_rr);
+ test_alignment();
+ test_keepalive();
+ test_chain();
+ test_cookie();
+
+ knot_rrset_clear(&opt_rr, NULL);
+
+ return 0;
+}
diff --git a/tests/libknot/test_edns_ecs.c b/tests/libknot/test_edns_ecs.c
new file mode 100644
index 0000000..9397d84
--- /dev/null
+++ b/tests/libknot/test_edns_ecs.c
@@ -0,0 +1,271 @@
+/* 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 <assert.h>
+#include <tap/basic.h>
+
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <netdb.h>
+
+#include "contrib/sockaddr.h"
+#include "libknot/errcode.h"
+#include "libknot/rrtype/opt.h"
+
+#define GARBAGE_BYTE 0xdb
+
+static void test_size(void)
+{
+ struct test {
+ const char *msg;
+ size_t expected;
+ knot_edns_client_subnet_t ecs;
+ };
+
+ static struct test const TESTS[] = {
+ // invalid
+ { "zero family", 0, { 0 } },
+ { "zero family & source", 0, { 0, 1 } },
+ { "unknown family", 0, { 42, 0 } },
+ { "unknown family & source", 0, { 42, 1 } },
+ // IPv4 bit ops
+ { "IPv4, zero source", 4, { 1 } },
+ { "IPv4, 7 bits in last byte", 7, { 1, 23 } },
+ { "IPv4, 8 bits in last byte", 7, { 1, 24 } },
+ { "IPv4, 1 bit in last byte", 8, { 1, 25 } },
+ // IPv6 bit ops
+ { "IPv6, zero source", 4, { 2 } },
+ { "IPv6, 7 bits in last byte", 19, { 2, 113 } },
+ { "IPv6, 8 bits in last byte", 19, { 2, 120 } },
+ { "IPv6, 1 bit in last byte", 20, { 2, 121 } },
+ // sources
+ { "IPv4, source < max", 8, { 1, 31 } },
+ { "IPv4, source = max", 8, { 1, 32 } },
+ { "IPv4, source > max", 0, { 1, 33 } },
+ // scopes
+ { "IPv6, scope < source", 12, { 2, 64, 48 } },
+ { "IPv6, scope = source", 20, { 2, 128, 128 } },
+ { "IPv6, scope > max", 0, { 2, 128, 129 } },
+ { NULL }
+ };
+
+ is_int(0, knot_edns_client_subnet_size(NULL), "%s: null", __func__);
+
+ for (struct test const *t = TESTS; t->msg != NULL; t++) {
+ int r = knot_edns_client_subnet_size(&t->ecs);
+ is_int(t->expected, r, "%s: %s", __func__, t->msg);
+ }
+}
+
+struct test_io {
+ const char *msg;
+ int expected;
+ size_t option_len;
+ const char *option;
+ knot_edns_client_subnet_t ecs;
+};
+
+static void test_write(void)
+{
+ static struct test_io const TESTS[] = {
+ // invalid
+ { "unset family", KNOT_EINVAL, 0, NULL, { 0 } },
+ { "invalid family", KNOT_EINVAL, 0, NULL, { 3 } },
+ { "small buffer", KNOT_ESPACE, 4, NULL, { 1, 1 } },
+ // IPv4 prefix
+ { "IPv4, zero source", KNOT_EOK, 4, "\x00\x01\x00\x00", { 1 } },
+ { "IPv4, 7 bits in LSB", KNOT_EOK, 6, "\x00\x01\x0f\x00\xff\xfe", { 1, 15, 0, "\xff\xff\xff\xff" } },
+ { "IPv4, 8 bits in LSB", KNOT_EOK, 6, "\x00\x01\x10\x00\xff\xff", { 1, 16, 0, "\xff\xff\xff\xff" } },
+ { "IPv4, 1 bit in LSB", KNOT_EOK, 7, "\x00\x01\x11\x00\xff\xff\x80", { 1, 17, 0, "\xff\xff\xff\xff" } },
+ { "IPv4, source = max", KNOT_EOK, 8, "\x00\x01\x20\x00\xaa\xbb\xcc\xdd", { 1, 32, 0, "\xaa\xbb\xcc\xdd" } },
+ { "IPv6, source > max", KNOT_EINVAL, 0, NULL, { 2, 129 } },
+ // IPv6 scope
+ { "IPv6, scope < source", KNOT_EOK, 6, "\x00\x02\x10\x0e\xff\xff", { 2, 16, 14, "\xff\xff\xff\xff" } },
+ { "IPv6, scope = source", KNOT_EOK, 6, "\x00\x02\x08\x08\xff", { 2, 8, 8, "\xff\xff\xff\xff" } },
+ { "IPv6, scope > max", KNOT_EINVAL, 0, NULL, { 2, 128, 129 } },
+ // other
+ { "larger buffer", KNOT_EOK, 7, "\x00\x01\x10\x0e\xff\xff\x00", { 1, 16, 14, "\xff\xff\xff\xff" } },
+ { NULL }
+ };
+
+ for (struct test_io const *t = TESTS; t->msg != NULL; t++) {
+ uint8_t option[64];
+ assert(sizeof(option) >= t->option_len);
+ memset(option, GARBAGE_BYTE, sizeof(option));
+
+ int r = knot_edns_client_subnet_write(option, t->option_len, &t->ecs);
+ ok(r == t->expected &&
+ (t->expected != KNOT_EOK || memcmp(option, t->option, t->option_len) == 0),
+ "%s: %s", __func__, t->msg);
+ }
+}
+
+static void test_parse(void)
+{
+ static struct test_io const TESTS[] = {
+ // invalid
+ { "null", KNOT_EINVAL, 0, NULL },
+ { "empty buffer", KNOT_EMALF, 0, "" },
+ { "incomplete header", KNOT_EMALF, 3, "\x00\x01\x00" },
+ { "incomplete source", KNOT_EMALF, 5, "\x00\x0a\x00\x00\xff\xff" },
+ { "zero family", KNOT_EMALF, 4, "\x00\x00\x00\x00" },
+ { "unknown family", KNOT_EMALF, 4, "\x00\x03\x00\x00" },
+ // IPv4 prefix
+ { "IPv4, zero source", KNOT_EOK, 4, "\x00\x01\x00\x00", { 1 } },
+ { "IPv4, 7 bits in LSB", KNOT_EOK, 6, "\x00\x01\x0f\x00\xff\xfe", { 1, 15, 0, "\xff\xfe" } },
+ { "IPv4, 9 bits in LSB", KNOT_EOK, 6, "\x00\x01\x10\x00\xff\xff", { 1, 16, 0, "\xff\xff" } },
+ { "IPv4, 1 bit in LSB", KNOT_EOK, 7, "\x00\x01\x11\x00\xff\xff\x80", { 1, 17, 0, "\xff\xff\x80" } },
+ { "IPv4, source = max", KNOT_EOK, 8, "\x00\x01\x20\x00\xaa\xbb\xcc\xdd", { 1, 32, 0, "\xaa\xbb\xcc\xdd" } },
+ { "IPv4, dirty source", KNOT_EOK, 8, "\x00\x01\x0b\x00\xff\xff\xff\xff", { 1, 11, 0, "\xff\xe0" } },
+ { "IPv4, source > max", KNOT_EMALF, 9, "\x00\x01\x21\x00\xaa\xbb\xcc\xdd\xee" },
+ // IPv6 scope
+ { "IPv6 scope < source", KNOT_EOK, 5, "\x00\x02\x07\x05\xff", { 2, 7, 5, "\xfe" } },
+ { "IPv6 scope = source", KNOT_EOK, 5, "\x00\x02\x06\x06\xff", { 2, 6, 6, "\xfc" } },
+ { "IPv6 scope > max", KNOT_EMALF, 5, "\x00\x02\x06\x81\xff" },
+ // extra buffer size
+ { "extra space", KNOT_EOK, 6, "\x00\x01\x00\x00\xff\x00", { 1 } },
+ { "extra space", KNOT_EOK, 6, "\x00\x01\x01\x00\xff\x00", { 1, 1, 0, "\x80" } },
+ { NULL }
+ };
+
+ for (struct test_io const *t = TESTS; t->msg != NULL; t++) {
+ knot_edns_client_subnet_t ecs = { 0 };
+ memset(&ecs, GARBAGE_BYTE, sizeof(ecs));
+
+ int r = knot_edns_client_subnet_parse(&ecs, (uint8_t *)t->option, t->option_len);
+ ok(r == t->expected &&
+ (t->expected != KNOT_EOK || memcmp(&ecs, &t->ecs, sizeof(ecs)) == 0),
+ "%s: %s", __func__, t->msg);
+ }
+}
+
+static struct sockaddr_storage addr_init(const char *addr)
+{
+ struct sockaddr_storage sa = { 0 };
+
+ struct addrinfo hints = { .ai_flags = AI_NUMERICHOST };
+ struct addrinfo *info = NULL;
+ int r = getaddrinfo(addr, NULL, &hints, &info);
+ (void)r;
+ assert(r == 0);
+ memcpy(&sa, info->ai_addr, info->ai_addrlen);
+ freeaddrinfo(info);
+
+ return sa;
+}
+
+static void test_set_address(void)
+{
+ int r;
+ knot_edns_client_subnet_t ecs = { 0 };
+ struct sockaddr_storage ss = { 0 };
+
+ r = knot_edns_client_subnet_set_addr(NULL, &ss);
+ is_int(KNOT_EINVAL, r, "%s: missing ECS", __func__);
+
+ r = knot_edns_client_subnet_set_addr(&ecs, NULL);
+ is_int(KNOT_EINVAL, r, "%s: missing address", __func__);
+
+ memset(&ecs, GARBAGE_BYTE, sizeof(ecs));
+ ss = addr_init("198.51.100.42");
+ assert(ss.ss_family == AF_INET);
+ const uint8_t raw4[4] = { 198, 51, 100, 42 };
+
+ r = knot_edns_client_subnet_set_addr(&ecs, &ss);
+ ok(r == KNOT_EOK &&
+ ecs.family == 1 && ecs.source_len == 32 && ecs.scope_len == 0 &&
+ memcmp(ecs.address, raw4, sizeof(raw4)) == 0,
+ "%s: IPv4", __func__);
+
+ memset(&ecs, GARBAGE_BYTE, sizeof(ecs));
+ ss = addr_init("2001:db8::dead:beef");
+ assert(ss.ss_family == AF_INET6);
+ const uint8_t raw6[16] = "\x20\x01\x0d\xb8\x00\x00\x00\x00"
+ "\x00\x00\x00\x00\xde\xad\xbe\xef";
+ r = knot_edns_client_subnet_set_addr(&ecs, &ss);
+ ok(r == KNOT_EOK &&
+ ecs.family == 2 && ecs.source_len == 128 && ecs.scope_len == 0 &&
+ memcmp(ecs.address, raw6, sizeof(raw6)) == 0,
+ "%s: IPv6", __func__);
+
+ const struct sockaddr_storage ss_unix = { .ss_family = AF_UNIX };
+ r = knot_edns_client_subnet_set_addr(&ecs, &ss_unix);
+ is_int(KNOT_ENOTSUP, r, "%s: UNIX not supported", __func__);
+}
+
+static bool sockaddr_eq(const struct sockaddr_storage *a, const struct sockaddr_storage *b)
+{
+ return sockaddr_cmp(a, b, true) == 0;
+}
+
+static void test_get_address(void)
+{
+ struct test {
+ const char *msg;
+ int expected;
+ const char *addr_str;
+ knot_edns_client_subnet_t ecs;
+ };
+
+ static struct test const TESTS[] = {
+ // invalid
+ { "unset family", KNOT_ENOTSUP, NULL, { 0 } },
+ { "unknown family", KNOT_ENOTSUP, NULL, { 3 } },
+ // zero source
+ { "IPv4, any", KNOT_EOK, "0.0.0.0", { 1 } },
+ { "IPv6, any", KNOT_EOK, "::0" , { 2 } },
+ // IPv4
+ { "IPv4, 7 bits in LSB", KNOT_EOK, "198.50.0.0", { 1, 15, 0, "\xc6\x33\xff\xff" } },
+ { "IPv4, 8 bits in LSB", KNOT_EOK, "198.51.0.0", { 1, 16, 0, "\xc6\x33\xff\xff" } },
+ { "IPv4, 1 bit in LSB", KNOT_EOK, "198.51.128.0", { 1, 17, 0, "\xc6\x33\xff\xff" } },
+ { "IPv4, source = max", KNOT_EOK, "198.51.128.1", { 1, 32, 0, "\xc6\x33\x80\x01" } },
+ // IPv6
+ { "IPv6, 7 bits in LSB", KNOT_EOK, "2001:db8:200::", { 2, 39, 0, "\x20\x01\x0d\xb8\x03\xff" } },
+ { "IPv6, 8 bits in LSB", KNOT_EOK, "2001:db8:100::", { 2, 40, 0, "\x20\x01\x0d\xb8\x01\xff" } },
+ { "IPv6, 1 bit in LSB", KNOT_EOK, "2001:db8:180::", { 2, 41, 0, "\x20\x01\x0d\xb8\x01\xff" } },
+ { "IPv6, source = max", KNOT_EOK, "2001:db8::1", { 2, 128, 0, "\x20\x01\x0d\xb8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" } },
+ { NULL }
+ };
+
+ for (struct test const *t = TESTS; t->msg != NULL; t++) {
+ struct sockaddr_storage result = { 0 };
+ int r = knot_edns_client_subnet_get_addr(&result, &t->ecs);
+ bool valid = false;
+
+ if (t->expected == KNOT_EOK) {
+ struct sockaddr_storage addr = addr_init(t->addr_str);
+ assert(addr.ss_family != AF_UNSPEC);
+ valid = (r == t->expected && sockaddr_eq(&result, &addr));
+ } else {
+ valid = (r == t->expected);
+ }
+
+ ok(valid, "%s: %s", __func__, t->msg);
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ test_size();
+ test_write();
+ test_parse();
+ test_set_address();
+ test_get_address();
+
+ return 0;
+}
diff --git a/tests/libknot/test_endian.c b/tests/libknot/test_endian.c
new file mode 100644
index 0000000..30f16f7
--- /dev/null
+++ b/tests/libknot/test_endian.c
@@ -0,0 +1,70 @@
+/* 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 <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "libknot/endian.h"
+
+int main(int argc, char *argv[])
+{
+ plan(12);
+
+ typedef union {
+ uint16_t value;
+ uint8_t array[2];
+ } trafo16_t;
+
+ const uint16_t host16 = 0x0102;
+ const trafo16_t be16 = { .array = { 0x01, 0x02 } };
+ const trafo16_t le16 = { .array = { 0x02, 0x01 } };
+ ok(htobe16(host16) == be16.value, "htobe16");
+ ok(htole16(host16) == le16.value, "htole16");
+ ok(be16toh(be16.value) == host16, "be16toh");
+ ok(le16toh(le16.value) == host16, "le16toh");
+
+ typedef union {
+ uint32_t value;
+ uint8_t array[4];
+ } trafo32_t;
+
+ const uint32_t host32 = 0x01020304;
+ const trafo32_t be32 = { .array = { 0x01, 0x02, 0x03, 0x04 } };
+ const trafo32_t le32 = { .array = { 0x04, 0x03, 0x02, 0x01 } };
+ ok(htobe32(host32) == be32.value, "htobe32");
+ ok(htole32(host32) == le32.value, "htole32");
+ ok(be32toh(be32.value) == host32, "be32toh");
+ ok(le32toh(le32.value) == host32, "le32toh");
+
+ typedef union {
+ uint64_t value;
+ uint8_t array[8];
+ } trafo64_t;
+
+ const uint64_t host64 = 0x0102030405060708;
+ const trafo64_t be64 = { .array = { 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x06, 0x07, 0x08 } };
+ const trafo64_t le64 = { .array = { 0x08, 0x07, 0x06, 0x05,
+ 0x04, 0x03, 0x02, 0x01 } };
+ ok(htobe64(host64) == be64.value, "htobe64");
+ ok(htole64(host64) == le64.value, "htole64");
+ ok(be64toh(be64.value) == host64, "be64toh");
+ ok(le64toh(le64.value) == host64, "le64toh");
+
+ return 0;
+}
diff --git a/tests/libknot/test_lookup.c b/tests/libknot/test_lookup.c
new file mode 100644
index 0000000..3f7b514
--- /dev/null
+++ b/tests/libknot/test_lookup.c
@@ -0,0 +1,66 @@
+/* Copyright (C) 2014 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 "libknot/lookup.h"
+
+const knot_lookup_t test_table[] = {
+ { 0, "test item 0" },
+ { 10, "" },
+ { 2, "test item 2" },
+ { -1, "test item -1" },
+ { 0, NULL }
+};
+
+int main(int argc, char *argv[])
+{
+ plan(9);
+
+ /* Lookup by ID. */
+ const knot_lookup_t *found = knot_lookup_by_id(test_table, 3);
+ ok(found == NULL, "lookup table: find by id - non-existent ID");
+
+ found = knot_lookup_by_id(test_table, 2);
+ ok(found && found->id == 2 && strcmp(found->name, "test item 2") == 0,
+ "lookup table: find by id - ID 2 (unordered IDs)");
+
+ found = knot_lookup_by_id(NULL, 2);
+ ok(found == NULL, "lookup table: find by id - table == NULL");
+
+ /* Lookup by name. */
+ found = knot_lookup_by_name(test_table, "test item 2");
+ ok(found && found->id == 2 && strcmp(found->name, "test item 2") == 0,
+ "lookup table: find by name - existent");
+
+ found = knot_lookup_by_name(test_table, "");
+ ok(found && found->id == 10 && strcmp(found->name, "") == 0,
+ "lookup table: find by name - empty string");
+
+ found = knot_lookup_by_name(test_table, NULL);
+ ok(found == NULL, "lookup table: find by name - NULL name");
+
+ found = knot_lookup_by_name(NULL, "test item 2");
+ ok(found == NULL, "lookup table: find by name - NULL table");
+
+ found = knot_lookup_by_name(NULL, NULL);
+ ok(found == NULL, "lookup table: find by name - NULL table & NULL name");
+
+ found = knot_lookup_by_name(test_table, "non existent name");
+ ok(found == NULL, "lookup table: find by name - non-existent name");
+
+ return 0;
+}
diff --git a/tests/libknot/test_pkt.c b/tests/libknot/test_pkt.c
new file mode 100644
index 0000000..fe31d3c
--- /dev/null
+++ b/tests/libknot/test_pkt.c
@@ -0,0 +1,199 @@
+/* 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 "libknot/libknot.h"
+#include "libknot/packet/pkt.c"
+#include "contrib/ucw/mempool.h"
+
+#define TTL 7200
+#define NAMECOUNT 3
+#define DATACOUNT 3
+const char *g_names[NAMECOUNT] = {
+ "example.com",
+ "ns1.example.com",
+ "ns2.example.com"
+};
+
+const char *g_rdata[DATACOUNT] = {
+ "\x04" "\xc2\x0c\x00\x01", /* 4B, 194.0.12.1" */
+ "\x11" "\x03""ns1""\x07""example""\x03""com""\x00", /* domain name */
+ "\x11" "\x03""ns2""\x07""example""\x03""com""\x00", /* domain name */
+};
+
+#define RDVAL(i) ((const uint8_t*)(g_rdata[(i)] + 1))
+#define RDLEN(i) ((uint16_t)(g_rdata[(i)][0]))
+
+/* @note Packet equivalence test, 5 checks. */
+static void packet_match(knot_pkt_t *in, knot_pkt_t *out)
+{
+ assert(in);
+ assert(out);
+
+ /* Check counts */
+ is_int(knot_wire_get_qdcount(out->wire),
+ knot_wire_get_qdcount(in->wire), "pkt: QD match");
+ is_int(knot_wire_get_ancount(out->wire),
+ knot_wire_get_ancount(in->wire), "pkt: AN match");
+ is_int(knot_wire_get_nscount(out->wire),
+ knot_wire_get_nscount(in->wire), "pkt: NS match");
+ is_int(knot_wire_get_arcount(out->wire),
+ knot_wire_get_arcount(in->wire), "pkt: AR match");
+
+ /* Check RRs */
+ int rr_matched = 0;
+ for (unsigned i = 0; i < NAMECOUNT; ++i) {
+ if (knot_rrset_equal(&out->rr[i], &in->rr[i], true) > 0) {
+ ++rr_matched;
+ }
+ }
+ is_int(NAMECOUNT, rr_matched, "pkt: RR content match");
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ /* Create memory pool context. */
+ int ret = 0;
+ knot_mm_t mm;
+ mm_ctx_mempool(&mm, MM_DEFAULT_BLKSIZE);
+
+ /* Create names and data. */
+ knot_dname_t* dnames[NAMECOUNT] = {0};
+ knot_rrset_t* rrsets[NAMECOUNT] = {0};
+ for (unsigned i = 0; i < NAMECOUNT; ++i) {
+ dnames[i] = knot_dname_from_str_alloc(g_names[i]);
+ }
+
+ uint8_t *edns_str = (uint8_t *)"ab";
+ /* Create OPT RR. */
+ knot_rrset_t opt_rr = { 0 };
+ ret = knot_edns_init(&opt_rr, 1024, 0, 0, &mm);
+ is_int(KNOT_EOK, ret, "initialize OPT RR");
+
+ /* Add NSID */
+ ret = knot_edns_add_option(&opt_rr, KNOT_EDNS_OPTION_NSID,
+ strlen((char *)edns_str), edns_str, &mm);
+ is_int(KNOT_EOK, ret, "initialize NSID in OPT RR");
+
+ /*
+ * Packet writer tests.
+ */
+
+ /* Create packet. */
+ knot_pkt_t *out = knot_pkt_new(NULL, MM_DEFAULT_BLKSIZE, &mm);
+ ok(out != NULL, "pkt: new");
+ assert(out);
+
+ /* Mark as response (not part of the test). */
+ knot_wire_set_qr(out->wire);
+
+ /* Secure packet. */
+ const char *tsig_secret = "abcd";
+ knot_tsig_key_t tsig_key;
+ tsig_key.algorithm = DNSSEC_TSIG_HMAC_MD5;
+ tsig_key.name = dnames[0];
+ tsig_key.secret.data = (uint8_t *)strdup(tsig_secret);
+ tsig_key.secret.size = strlen(tsig_secret);
+ ret = knot_pkt_reserve(out, knot_tsig_wire_size(&tsig_key));
+ is_int(KNOT_EOK, ret, "pkt: set TSIG key");
+
+ /* Write question. */
+ ret = knot_pkt_put_question(out, dnames[0], KNOT_CLASS_IN, KNOT_RRTYPE_A);
+ is_int(KNOT_EOK, ret, "pkt: put question");
+
+ /* Add OPT to packet (empty NSID). */
+ ret = knot_pkt_reserve(out, knot_edns_wire_size(&opt_rr));
+ is_int(KNOT_EOK, ret, "pkt: reserve OPT RR");
+
+ /* Begin ANSWER section. */
+ ret = knot_pkt_begin(out, KNOT_ANSWER);
+ is_int(KNOT_EOK, ret, "pkt: begin ANSWER");
+
+ /* Write ANSWER section. */
+ rrsets[0] = knot_rrset_new(dnames[0], KNOT_RRTYPE_A, KNOT_CLASS_IN, TTL, NULL);
+ knot_dname_free(dnames[0], NULL);
+ knot_rrset_add_rdata(rrsets[0], RDVAL(0), RDLEN(0), NULL);
+ ret = knot_pkt_put(out, KNOT_COMPR_HINT_QNAME, rrsets[0], 0);
+ is_int(KNOT_EOK, ret, "pkt: write ANSWER");
+
+ /* Begin AUTHORITY. */
+ ret = knot_pkt_begin(out, KNOT_AUTHORITY);
+ is_int(KNOT_EOK, ret, "pkt: begin AUTHORITY");
+
+ /* Write rest to AUTHORITY. */
+ ret = KNOT_EOK;
+ for (unsigned i = 1; i < NAMECOUNT; ++i) {
+ rrsets[i] = knot_rrset_new(dnames[i], KNOT_RRTYPE_NS, KNOT_CLASS_IN, TTL, NULL);
+ knot_dname_free(dnames[i], NULL);
+ knot_rrset_add_rdata(rrsets[i], RDVAL(i), RDLEN(i), NULL);
+ ret |= knot_pkt_put(out, KNOT_COMPR_HINT_NONE, rrsets[i], 0);
+ }
+ is_int(KNOT_EOK, ret, "pkt: write AUTHORITY(%u)", NAMECOUNT - 1);
+
+ /* Begin ADDITIONALS */
+ ret = knot_pkt_begin(out, KNOT_ADDITIONAL);
+ is_int(KNOT_EOK, ret, "pkt: begin ADDITIONALS");
+
+ /* Encode OPT RR. */
+ ret = knot_pkt_put(out, KNOT_COMPR_HINT_NONE, &opt_rr, 0);
+ is_int(KNOT_EOK, ret, "pkt: write OPT RR");
+
+ /*
+ * Packet reader tests.
+ */
+
+ /* Create new packet from query packet. */
+ knot_pkt_t *in = knot_pkt_new(out->wire, out->size, &out->mm);
+ ok(in != NULL, "pkt: create packet for parsing");
+
+ /* Read packet header. */
+ ret = knot_pkt_parse_question(in);
+ is_int(KNOT_EOK, ret, "pkt: read header");
+
+ /* Read packet payload. */
+ ret = parse_payload(in, 0);
+ is_int(KNOT_EOK, ret, "pkt: read payload");
+
+ /* Compare parsed packet to written packet. */
+ packet_match(in, out);
+
+ /*
+ * Copied packet tests.
+ */
+ knot_pkt_t *copy = knot_pkt_new(NULL, in->max_size, &in->mm);
+ ret = knot_pkt_copy(copy, in);
+ is_int(KNOT_EOK, ret, "pkt: create packet copy");
+
+ /* Compare copied packet to original. */
+ packet_match(in, copy);
+
+ /* Free packets. */
+ knot_pkt_free(copy);
+ knot_pkt_free(out);
+ knot_pkt_free(in);
+
+ /* Free extra data. */
+ for (unsigned i = 0; i < NAMECOUNT; ++i) {
+ knot_rrset_free(rrsets[i], NULL);
+ }
+ free(tsig_key.secret.data);
+ mp_delete((struct mempool *)mm.ctx);
+
+ return 0;
+}
diff --git a/tests/libknot/test_probe.c b/tests/libknot/test_probe.c
new file mode 100644
index 0000000..37437f4
--- /dev/null
+++ b/tests/libknot/test_probe.c
@@ -0,0 +1,96 @@
+/* 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 <tap/basic.h>
+#include <tap/files.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+
+#include "contrib/sockaddr.h"
+#include "libknot/packet/pkt.c"
+#include "libknot/probe/probe.h"
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ knot_probe_t *probe_out = knot_probe_alloc();
+ ok(probe_out != NULL, "probe: initialize output probe");
+ knot_probe_t *probe_in = knot_probe_alloc();
+ ok(probe_in != NULL, "probe: initialize input probe");
+
+ int fd = knot_probe_fd(probe_out);
+ ok(fd < 0, "probe: unavailable fd");
+
+ char *workdir = test_mkdtemp();
+ ok(workdir != NULL, "probe: create temporary workdir");
+
+ int ret = knot_probe_set_producer(probe_out, workdir, 1);
+ ok(ret == KNOT_ECONN, "probe: connect producer");
+
+ ret = knot_probe_set_consumer(probe_in, workdir, 1);
+ ok(ret == KNOT_EOK, "probe: connect consumer");
+ fd = knot_probe_fd(probe_in);
+ ok(fd >= 0, "probe: get input probe fd");
+
+ ret = knot_probe_set_producer(probe_out, workdir, 1);
+ ok(ret == KNOT_EOK, "probe: reconnect producer");
+ fd = knot_probe_fd(probe_out);
+ ok(fd >= 0, "probe: get output probe fd");
+
+ struct sockaddr_storage addr;
+ ret = sockaddr_set(&addr, AF_INET, "192.168.0.1", 53);
+ ok(ret == KNOT_EOK, "probe: set address");
+
+ knot_pkt_t *query = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL);
+ ok(query != NULL, "probe: create query");
+ knot_pkt_t *reply = knot_pkt_new(NULL, KNOT_WIRE_MAX_PKTSIZE, NULL);
+ ok(reply != NULL, "probe: create reply");
+
+ ret = knot_pkt_put_question(query, (const uint8_t *)"\x04test\x00",
+ KNOT_CLASS_IN, KNOT_RRTYPE_SOA);
+ ok(ret == KNOT_EOK, "probe: put query");
+
+ knot_probe_data_t data_out;
+ ret = knot_probe_data_set(&data_out, KNOT_PROBE_PROTO_UDP,
+ &addr, &addr, query, reply, KNOT_RCODE_NXDOMAIN);
+ ok(ret == KNOT_EOK, "probe: connect producer");
+
+ knot_pkt_free(query);
+ knot_pkt_free(reply);
+
+ ret = knot_probe_produce(probe_out, &data_out, 1);
+ ok(ret == KNOT_EOK, "probe: produce datagram");
+
+ knot_probe_data_t data_in;
+ ret = knot_probe_consume(probe_in, &data_in, 1, 20);
+ ok(ret == 1, "probe: consume datagram");
+
+ ret = memcmp(&data_in, &data_out, offsetof(knot_probe_data_t, query.qname));
+ ok(ret == 0, "probe: data comparison");
+
+ ret = knot_dname_cmp(data_in.query.qname, data_out.query.qname);
+ ok(ret == 0, "probe: qname comparison");
+
+ knot_probe_free(probe_in);
+ knot_probe_free(probe_out);
+
+ test_rm_rf(workdir);
+ free(workdir);
+
+ return 0;
+}
diff --git a/tests/libknot/test_rdata.c b/tests/libknot/test_rdata.c
new file mode 100644
index 0000000..754cd7f
--- /dev/null
+++ b/tests/libknot/test_rdata.c
@@ -0,0 +1,60 @@
+/* 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 <stdbool.h>
+#include <tap/basic.h>
+
+#include "libknot/rdata.h"
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ // Test array size
+ ok(knot_rdata_size(1) == 2 + 1 + 1, "rdata: array size odd.");
+ ok(knot_rdata_size(2) == 2 + 2, "rdata: array size even.");
+
+ // Test init
+ const size_t data_size = 16;
+ uint8_t buf1[knot_rdata_size(data_size)];
+ knot_rdata_t *rdata = (knot_rdata_t *)buf1;
+ uint8_t payload[] = "abcdefghijklmnop";
+ knot_rdata_init(rdata, data_size, payload);
+ const bool set_ok = rdata->len == data_size &&
+ memcmp(rdata->data, payload, data_size) == 0;
+ ok(set_ok, "rdata: init.");
+
+ // Test compare
+ rdata->len = data_size;
+ ok(knot_rdata_cmp(rdata, rdata) == 0, "rdata: cmp eq.");
+
+ knot_rdata_t *lower = rdata;
+ uint8_t buf2[knot_rdata_size(data_size)];
+ knot_rdata_t *greater = (knot_rdata_t *)buf2;
+ knot_rdata_init(greater, data_size, (uint8_t *)"qrstuvwxyz123456");
+ ok(knot_rdata_cmp(lower, greater) < 0, "rdata: cmp lower.");
+ ok(knot_rdata_cmp(greater, lower) > 0, "rdata: cmp greater.");
+
+ // Payloads will be the same.
+ memcpy(greater->data, lower->data, data_size);
+ assert(knot_rdata_cmp(lower, greater) == 0);
+
+ lower->len = data_size - 1;
+ ok(knot_rdata_cmp(lower, greater) < 0, "rdata: cmp lower size.");
+ ok(knot_rdata_cmp(greater, lower) > 0, "rdata: cmp greater size.");
+
+ return 0;
+}
diff --git a/tests/libknot/test_rdataset.c b/tests/libknot/test_rdataset.c
new file mode 100644
index 0000000..d364be3
--- /dev/null
+++ b/tests/libknot/test_rdataset.c
@@ -0,0 +1,227 @@
+/* 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 <assert.h>
+#include <tap/basic.h>
+#include <string.h>
+
+#include "libknot/rdataset.c"
+#include "libknot/libknot.h"
+
+// Inits rdataset with given rdata.
+#define RDATASET_INIT_WITH(set, rdata) \
+ knot_rdataset_clear(&set, NULL); \
+ ret = knot_rdataset_add(&set, rdata, NULL); \
+ assert(ret == KNOT_EOK);
+
+static size_t rdataset_size(const knot_rdataset_t *rrs)
+{
+ if (rrs == NULL || rrs->count == 0) {
+ return 0;
+ }
+
+ const knot_rdata_t *last = rr_seek(rrs, rrs->count - 1);
+ return (uint8_t *)last + knot_rdata_size(last->len) - (uint8_t *)rrs->rdata;
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ // Test init
+ knot_rdataset_t rdataset;
+ knot_rdataset_init(&rdataset);
+ ok(rdataset.rdata == NULL && rdataset.count == 0 && rdataset.size == 0, "rdataset: init.");
+
+ // Test rdata addition
+ uint8_t buf_gt[knot_rdata_size(4)];
+ knot_rdata_t *rdata_gt = (knot_rdata_t *)buf_gt;
+ knot_rdata_init(rdata_gt, 4, (uint8_t *)"wxyz");
+
+ int ret = knot_rdataset_add(NULL, NULL, NULL);
+ is_int(KNOT_EINVAL, ret, "rdataset: add NULL.");
+ ret = knot_rdataset_add(&rdataset, rdata_gt, NULL);
+ bool add_ok = ret == KNOT_EOK && rdataset.count == 1 &&
+ knot_rdata_cmp(rdata_gt, rdataset.rdata) == 0;
+ ok(add_ok, "rdataset: add.");
+
+ uint8_t buf_lo[knot_rdata_size(4)];
+ knot_rdata_t *rdata_lo = (knot_rdata_t *)buf_lo;
+ knot_rdata_init(rdata_lo, 4, (uint8_t *)"abcd");
+ ret = knot_rdataset_add(&rdataset, rdata_lo, NULL);
+ add_ok = ret == KNOT_EOK && rdataset.count == 2 &&
+ knot_rdata_cmp(rdata_lo, rdataset.rdata) == 0;
+ ok(add_ok, "rdataset: add lower.");
+
+ // Test getters
+ ok(knot_rdata_cmp(knot_rdataset_at(&rdataset, 0), rdata_lo) == 0 &&
+ knot_rdata_cmp(knot_rdataset_at(&rdataset, 1), rdata_gt) == 0,
+ "rdataset: at.");
+
+ ok(rdataset_size(&rdataset) == knot_rdata_size(4) * 2,
+ "rdataset: size.");
+ ok(rdataset.size == rdataset_size(&rdataset), "rdataset: size precomputed (%u %zu).",
+ rdataset.size, rdataset_size(&rdataset));
+
+ // Test copy
+ ok(knot_rdataset_copy(NULL, NULL, NULL) == KNOT_EINVAL,
+ "rdataset: copy NULL.");
+ knot_rdataset_t copy;
+ ret = knot_rdataset_copy(&copy, &rdataset, NULL);
+ const bool copy_ok = ret == KNOT_EOK && copy.count == rdataset.count &&
+ rdataset_size(&copy) == rdataset_size(&rdataset) &&
+ rdataset.rdata != NULL && copy.rdata != NULL &&
+ memcmp(rdataset.rdata, copy.rdata,
+ rdataset_size(&rdataset)) == 0;
+ ok(copy_ok, "rdataset: copy");
+ ok(copy.size == rdataset_size(&copy), "copy: size precomputed.");
+
+ // Test eq
+ ok(knot_rdataset_eq(&rdataset, &copy), "rdataset: equal");
+
+ // Test clear
+ knot_rdataset_clear(&copy, NULL);
+ ok(copy.count == 0 && copy.rdata == NULL, "rdataset: clear.");
+
+ // Test not equal (different count)
+ ok(!knot_rdataset_eq(&rdataset, &copy), "rdataset: not equal - count");
+
+ // Test member
+ uint8_t buf_not[knot_rdata_size(1)];
+ knot_rdata_t *not_a_member = (knot_rdata_t *)buf_not;
+ knot_rdata_init(not_a_member, 1, (uint8_t *)"?");
+ ok(knot_rdataset_member(&rdataset, rdata_gt), "rdataset: is member.");
+ ok(!knot_rdataset_member(&rdataset, not_a_member), "rdataset: is not member.");
+
+ // Test merge
+ ok(knot_rdataset_merge(NULL, NULL, NULL) == KNOT_EINVAL,
+ "rdataset: merge NULL.");
+ knot_rdataset_t empty;
+ knot_rdataset_init(&empty);
+ ret = knot_rdataset_merge(&empty, &rdataset, NULL);
+ bool merge_ok = ret == KNOT_EOK && knot_rdataset_eq(&empty, &rdataset);
+ ok(merge_ok, "rdataset: merge empty.");
+ knot_rdata_t *data_before = rdataset.rdata;
+ ret = knot_rdataset_merge(&rdataset, &rdataset, NULL);
+ merge_ok = ret == KNOT_EOK && rdataset.count == 2 &&
+ data_before == rdataset.rdata;
+ ok(merge_ok, "rdataset: merge self.");
+
+ knot_rdataset_clear(&empty, NULL);
+
+ // Init structs for merge sort testing
+ knot_rdataset_t rdataset_lo; // "Lower" rdataset
+ knot_rdataset_init(&rdataset_lo);
+ RDATASET_INIT_WITH(rdataset_lo, rdata_lo);
+ knot_rdataset_t rdataset_gt; // "Greater" rdataset
+ knot_rdataset_init(&rdataset_gt);
+ RDATASET_INIT_WITH(rdataset_gt, rdata_gt);
+
+ // Test not equal - different data
+ ok(!knot_rdataset_eq(&rdataset_gt, &rdataset_lo), "rdataset: data not equal.");
+
+ // Test that merge keeps the sorted order
+ ret = knot_rdataset_merge(&rdataset_lo, &rdataset_gt, NULL);
+ merge_ok = ret == KNOT_EOK && knot_rdataset_eq(&rdataset_lo, &rdataset);
+ ok(merge_ok, "rdataset: merge into lower.");
+
+ RDATASET_INIT_WITH(rdataset_lo, rdata_lo);
+ RDATASET_INIT_WITH(rdataset_gt, rdata_gt);
+ ret = knot_rdataset_merge(&rdataset_gt, &rdataset_lo, NULL);
+ merge_ok = ret == KNOT_EOK && knot_rdataset_eq(&rdataset_gt, &rdataset);
+ ok(merge_ok, "rdataset: merge into greater.");
+
+ // Test intersect
+ ok(knot_rdataset_intersect(NULL, NULL, NULL, NULL) == KNOT_EINVAL,
+ "rdataset: intersect NULL.");
+
+ knot_rdataset_t intersection;
+ ret = knot_rdataset_intersect(&rdataset, &rdataset, &intersection, NULL);
+ bool intersect_ok = ret == KNOT_EOK && knot_rdataset_eq(&rdataset, &intersection);
+ ok(intersect_ok, "rdataset: intersect self.");
+ knot_rdataset_clear(&intersection, NULL);
+
+ RDATASET_INIT_WITH(rdataset_lo, rdata_lo);
+ RDATASET_INIT_WITH(rdataset_gt, rdata_gt);
+ ret = knot_rdataset_intersect(&rdataset_lo, &rdataset_gt, &intersection, NULL);
+ intersect_ok = ret == KNOT_EOK && intersection.count == 0;
+ ok(intersect_ok, "rdataset: intersect no common.");
+
+ ret = knot_rdataset_intersect(&rdataset, &rdataset_lo, &intersection, NULL);
+ intersect_ok = ret == KNOT_EOK && knot_rdataset_eq(&intersection, &rdataset_lo);
+ ok(intersect_ok, "rdataset: intersect normal.");
+ knot_rdataset_clear(&intersection, NULL);
+
+ // Test intersect2
+ ok(knot_rdataset_intersect2(NULL, NULL, NULL) == KNOT_EINVAL,
+ "rdataset: intersect2 NULL.");
+
+ ret = knot_rdataset_intersect2(&rdataset, &rdataset, NULL);
+ intersect_ok = ret == KNOT_EOK && knot_rdataset_eq(&rdataset, &rdataset);
+ ok(intersect_ok, "rdataset: intersect2 self.");
+ knot_rdataset_clear(&intersection, NULL);
+
+ RDATASET_INIT_WITH(rdataset_lo, rdata_lo);
+ RDATASET_INIT_WITH(rdataset_gt, rdata_gt);
+ ret = knot_rdataset_intersect2(&rdataset_lo, &rdataset_gt, NULL);
+ intersect_ok = ret == KNOT_EOK && rdataset_lo.count == 0;
+ ok(intersect_ok, "rdataset: intersect2 no common.");
+
+ ret = knot_rdataset_copy(&copy, &rdataset, NULL);
+ assert(ret == KNOT_EOK);
+ ret = knot_rdataset_intersect2(&copy, &rdataset_lo, NULL);
+ intersect_ok = ret == KNOT_EOK && knot_rdataset_eq(&copy, &rdataset_lo);
+ ok(intersect_ok, "rdataset: intersect2 normal.");
+ knot_rdataset_clear(&copy, NULL);
+
+ // Test subtract
+ ok(knot_rdataset_subtract(NULL, NULL, NULL) == KNOT_EINVAL,
+ "rdataset: subtract NULL.");
+ ret = knot_rdataset_copy(&copy, &rdataset, NULL);
+ assert(ret == KNOT_EOK);
+ ok(knot_rdataset_subtract(&copy, &copy, NULL) == KNOT_EOK &&
+ copy.count == 0, "rdataset: subtract self.");
+
+ ret = knot_rdataset_copy(&copy, &rdataset, NULL);
+ assert(ret == KNOT_EOK);
+ ret = knot_rdataset_subtract(&copy, &rdataset, NULL);
+ bool subtract_ok = ret == KNOT_EOK && copy.count == 0;
+ ok(subtract_ok, "rdataset: subtract identical.");
+
+ RDATASET_INIT_WITH(rdataset_lo, rdata_lo);
+ RDATASET_INIT_WITH(rdataset_gt, rdata_gt);
+ data_before = rdataset_lo.rdata;
+ ret = knot_rdataset_subtract(&rdataset_lo, &rdataset_gt, NULL);
+ subtract_ok = ret == KNOT_EOK && rdataset_lo.count == 1 &&
+ rdataset_lo.rdata == data_before;
+ ok(subtract_ok, "rdataset: subtract no common.");
+
+ ret = knot_rdataset_subtract(&rdataset, &rdataset_gt, NULL);
+ subtract_ok = ret == KNOT_EOK && rdataset.count == 1;
+ ok(subtract_ok, "rdataset: subtract the second.");
+
+ ret = knot_rdataset_subtract(&rdataset, &rdataset_lo, NULL);
+ subtract_ok = ret == KNOT_EOK && rdataset.count == 0 &&
+ rdataset.rdata == NULL;
+ ok(subtract_ok, "rdataset: subtract last.");
+
+ knot_rdataset_clear(&copy, NULL);
+ knot_rdataset_clear(&rdataset, NULL);
+ knot_rdataset_clear(&rdataset_lo, NULL);
+ knot_rdataset_clear(&rdataset_gt, NULL);
+
+ return EXIT_SUCCESS;
+}
diff --git a/tests/libknot/test_rrset-wire.c b/tests/libknot/test_rrset-wire.c
new file mode 100644
index 0000000..35dc1bc
--- /dev/null
+++ b/tests/libknot/test_rrset-wire.c
@@ -0,0 +1,264 @@
+/* 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 <assert.h>
+#include <tap/basic.h>
+
+#include "libknot/packet/rrset-wire.h"
+#include "libknot/descriptor.h"
+#include "libknot/errcode.h"
+
+// Wire initializers
+
+#define MESSAGE_HEADER(AN, AUTH, ADD) 0xd4, 0xec, 0x81, 0xa0, 0x00, 0x01, \
+ 0x00, AN, 0x00, AUTH, 0x00, ADD
+
+#define QUERY(qname, type) qname, 0x00, type, 0x00, 0x01
+
+#define RR_HEADER(owner, type, rdlength0, rdlength1) owner, 0x00, type, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, rdlength0, rdlength1
+
+#define QNAME_POINTER 0xc0, 0x0c
+
+// Initializers' sizes
+
+#define QUERY_SIZE 12 + 4
+#define RR_HEADER_SIZE 10
+
+// Sample domain names
+
+#define QNAME 0x03, 0x6e, 0x69, 0x63, 0x02, 0x63, 0x7a, 0x00
+#define QNAME_SIZE 8
+#define QNAME_LONG \
+0x3f,'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', \
+'m', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', \
+'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', \
+'m', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'w', 'y', \
+'z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 0x3f,\
+'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', \
+'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', \
+'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', \
+'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'x', 'y', 'z', \
+'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 0x3f,'a', \
+'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', \
+'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', \
+'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', \
+'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'x', 'y', 'z', 'a', \
+'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'i', 'k', 0x3d,'a', 'b', \
+'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', \
+'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'a', 'b', \
+'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', \
+'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'x', 'y', 'z', 'a', 'b', \
+'c', 'd', 'e', 'f', 'g', 'h', 'i', 0x00
+#define QNAME_LONG_SIZE 255
+#define POINTER_SIZE 2
+
+struct wire_data {
+ uint8_t wire[65535];
+ size_t size;
+ size_t pos;
+ int code;
+ const char *msg;
+};
+
+#define FROM_CASE_COUNT 17
+
+static const struct wire_data FROM_CASES[FROM_CASE_COUNT] = {
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_A)},
+ .size = QUERY_SIZE + QNAME_SIZE,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "No header" },
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_A), 0x00, 0x00, 0x01},
+ .size = QUERY_SIZE + QNAME_SIZE + 3,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "Partial header" },
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_A),
+ RR_HEADER(QNAME, KNOT_RRTYPE_A, 0x00, 0x04) },
+ .size = QUERY_SIZE + RR_HEADER_SIZE + QNAME_SIZE * 2,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "No RDATA" },
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_A),
+ RR_HEADER(QNAME, KNOT_RRTYPE_A, 0x00, 0x04), 0x01 },
+ .size = QUERY_SIZE + RR_HEADER_SIZE + QNAME_SIZE * 2 + 1,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "Partial RDATA" },
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_A),
+ RR_HEADER(QNAME, KNOT_RRTYPE_A, 0x00, 0x04), 0x01, 0x02, 0x03, 0x04 },
+ .size = QUERY_SIZE + RR_HEADER_SIZE + QNAME_SIZE * 2 + 4,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EOK,
+ .msg = "OK RDATA" },
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_A),
+ RR_HEADER(QNAME, KNOT_RRTYPE_A, 0x00, 0x05), 0x01, 0x02, 0x03, 0x04, 0x05 },
+ .size = QUERY_SIZE + RR_HEADER_SIZE + QNAME_SIZE * 2 + 5,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "Trailing RDATA" },
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME_LONG, KNOT_RRTYPE_SOA),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_SOA, 0x00, 0x18), QNAME_POINTER, QNAME_POINTER,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
+ .size = QUERY_SIZE + RR_HEADER_SIZE + QNAME_LONG_SIZE + 6 + 20,
+ .pos = QUERY_SIZE + QNAME_LONG_SIZE,
+ .code = KNOT_EOK,
+ .msg = "Max DNAME" },
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_SIG),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_SIG, 0xff, 0xdb),
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, QNAME },
+ .size = 65535,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EOK,
+ .msg = "Max RDLENGTH" },
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME_LONG, KNOT_RRTYPE_SIG),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_SIG, 0xff, 0xff),
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, QNAME_POINTER },
+ .size = 65535 + QNAME_LONG_SIZE + QUERY_SIZE + RR_HEADER_SIZE + 2,
+ .pos = QUERY_SIZE + QNAME_LONG_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "Max RDLENGTH + compression"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_NSEC),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_NSEC, 0x00, 0x03),
+ QNAME_POINTER, 0x00},
+ .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2 + 2 + 1,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EOK,
+ .msg = "DNAME wrong compression"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_NAPTR),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_NAPTR, 0x00, 0x01),
+ 0x00},
+ .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2 + 1,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "NAPTR missing header"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_NAPTR),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_NAPTR, 0x00, 0x09),
+ 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, QNAME_POINTER},
+ .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2 + 9,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "NAPTR bad offset"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_NAPTR),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_NAPTR, 0x00, 0x09),
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+ .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2 + 7,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "NAPTR no DNAME"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_NAPTR),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_NAPTR, 0x00, 0x0c),
+ 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x01, 0xff, 0x01, 0xff, QNAME_POINTER},
+ .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2 + 10 + 2,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EOK,
+ .msg = "NAPTR valid"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_APL),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_APL, 0x00, 0x00) },
+ .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EOK,
+ .msg = "Valid 0 RDATA"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_TXT),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_TXT, 0x00, 0x00) },
+ .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EMALF,
+ .msg = "Invalid 0 RDATA"},
+{ .wire = { MESSAGE_HEADER(1, 0, 0), QUERY(QNAME, KNOT_RRTYPE_PX),
+ RR_HEADER(QNAME_POINTER, KNOT_RRTYPE_PX, 0x00, 0x06),
+ 0x00, 0x00, QNAME_POINTER, QNAME_POINTER },
+ .size = QUERY_SIZE + QNAME_SIZE + RR_HEADER_SIZE + 2 + 6,
+ .pos = QUERY_SIZE + QNAME_SIZE,
+ .code = KNOT_EOK,
+ .msg = "Obsolete RR type"},
+};
+
+#define TEST_CASE_FROM(rrset, i) size_t _pos##i = FROM_CASES[i].pos; \
+ ok(knot_rrset_rr_from_wire(FROM_CASES[i].wire, &_pos##i, FROM_CASES[i].size, \
+ rrset, NULL, true) == FROM_CASES[i].code, "rrset wire: %s", FROM_CASES[i].msg)
+
+static void test_inputs(void)
+{
+ for (size_t i = 0; i < FROM_CASE_COUNT; ++i) {
+ knot_rrset_t rrset;
+ knot_rrset_init_empty(&rrset);
+ TEST_CASE_FROM(&rrset, i);
+ knot_rrset_clear(&rrset, NULL);
+ }
+}
+
+static void check_canon(uint8_t *wire, size_t size, size_t pos, bool canon,
+ knot_dname_t *qname, knot_dname_t *dname)
+{
+ knot_rrset_t rrset;
+ knot_rrset_init_empty(&rrset);
+
+ int ret = knot_rrset_rr_from_wire(wire, &pos, size, &rrset, NULL, canon);
+ is_int(KNOT_EOK, ret, "OK %s canonization", canon ? "with" : "without");
+ ok(memcmp(rrset.owner, qname, knot_dname_size(qname)) == 0, "compare owner");
+
+ uint8_t *rdata = rrset.rrs.rdata->data;
+ ok(memcmp(rdata, dname, knot_dname_size(dname)) == 0, "compare rdata dname");
+
+ knot_rrset_clear(&rrset, NULL);
+}
+
+static void test_canonization(void)
+{
+ #define UPP_QNAME_SIZE 5
+ #define UPP_QNAME 0x01, 0x41, 0x01, 0x5a, 0x00 // A.Z.
+ #define LOW_QNAME 0x01, 0x61, 0x01, 0x7a, 0x00 // a.z.
+
+ #define UPP_DNAME_SIZE 3
+ #define UPP_DNAME 0x01, 0x58, 0x00 // X.
+ #define LOW_DNAME 0x01, 0x78, 0x00 // x.
+
+ uint8_t wire[] = {
+ MESSAGE_HEADER(1, 0, 0), QUERY(UPP_QNAME, KNOT_RRTYPE_NS),
+ RR_HEADER(UPP_QNAME, KNOT_RRTYPE_NS, 0x00, UPP_DNAME_SIZE), UPP_DNAME
+ };
+ size_t size = QUERY_SIZE + RR_HEADER_SIZE + UPP_QNAME_SIZE * 2 + UPP_DNAME_SIZE;
+ size_t pos = QUERY_SIZE + UPP_QNAME_SIZE;
+
+ knot_dname_t upp_qname[] = { UPP_QNAME };
+ knot_dname_t upp_dname[] = { UPP_DNAME };
+ check_canon(wire, size, pos, false, upp_qname, upp_dname);
+
+ knot_dname_t low_qname[] = { LOW_QNAME };
+ knot_dname_t low_dname[] = { LOW_DNAME };
+ check_canon(wire, size, pos, true, low_qname, low_dname);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ diag("Test NULL parameters");
+ int ret = knot_rrset_rr_from_wire(NULL, NULL, 0, NULL, NULL, true);
+ is_int(KNOT_EINVAL, ret, "rr wire: Invalid params");
+
+ diag("Test various inputs");
+ test_inputs();
+
+ diag("Test canonization");
+ test_canonization();
+
+ return 0;
+}
diff --git a/tests/libknot/test_rrset.c b/tests/libknot/test_rrset.c
new file mode 100644
index 0000000..cc67e0f
--- /dev/null
+++ b/tests/libknot/test_rrset.c
@@ -0,0 +1,121 @@
+/* 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 <assert.h>
+#include <inttypes.h>
+#include <tap/basic.h>
+
+#include "libknot/rrset.h"
+#include "libknot/descriptor.h"
+
+static bool check_rrset(const knot_rrset_t *rrset, const knot_dname_t *owner,
+ uint16_t type, uint16_t rclass, uint32_t ttl)
+{
+ if (!rrset) {
+ return false;
+ }
+
+ const bool dname_cmp = owner == NULL ? rrset->owner == NULL :
+ knot_dname_is_equal(rrset->owner, owner);
+ return rrset->type == type && rrset->rclass == rclass && dname_cmp &&
+ rrset->ttl == ttl && rrset->rrs.count == 0; // We do not test rdataset here
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ // Test new
+ knot_dname_t *dummy_owner = knot_dname_from_str_alloc("test.");
+ assert(dummy_owner);
+
+ knot_rrset_t *rrset = knot_rrset_new(dummy_owner, KNOT_RRTYPE_TXT,
+ KNOT_CLASS_IN, 3600, NULL);
+ ok(rrset != NULL, "rrset: create.");
+ assert(rrset);
+
+ ok(check_rrset(rrset, dummy_owner, KNOT_RRTYPE_TXT, KNOT_CLASS_IN, 3600),
+ "rrset: set fields during create.");
+
+ // Test init
+ knot_dname_free(dummy_owner, NULL);
+ dummy_owner = knot_dname_from_str_alloc("test2.");
+ assert(dummy_owner);
+
+ knot_dname_free(rrset->owner, NULL);
+ knot_rrset_init(rrset, dummy_owner, KNOT_RRTYPE_A, KNOT_CLASS_CH, 7200);
+ ok(check_rrset(rrset, dummy_owner, KNOT_RRTYPE_A, KNOT_CLASS_CH, 7200),
+ "rrset: init.");
+
+ // Test copy
+ knot_rrset_t *copy = knot_rrset_copy(rrset, NULL);
+ ok(copy != NULL, "rrset: copy.");
+ ok(check_rrset(copy, rrset->owner, rrset->type, rrset->rclass, 7200),
+ "rrset: set fields during copy.");
+ ok(knot_rrset_copy(NULL, NULL) == NULL, "rrset: copy NULL.");
+ assert(copy);
+
+ // Test equal - same TTL
+ ok(knot_rrset_equal(rrset, copy, true), "rrset: cmp same TTL");
+
+ // Test equal - different TTL
+ copy->ttl++;
+ ok(!knot_rrset_equal(rrset, copy, true), "rrset: cmp different TTL");
+
+ // Test equal - ignore TTL
+ ok(knot_rrset_equal(rrset, copy, false), "rrset: cmp ignore TTL");
+
+ copy->ttl = rrset->ttl;
+
+ // Test equal - different type
+ copy->type++;
+ ok(!knot_rrset_equal(rrset, copy, true), "rrset: cmp different type");
+
+ copy->type = rrset->type;
+
+ // Test equal - owners
+ knot_dname_free(rrset->owner, NULL);
+ rrset->owner = NULL;
+ ok(!knot_rrset_equal(rrset, copy, true), "rrset: cmp NULL owner");
+
+ knot_dname_free(copy->owner, NULL);
+ copy->owner = NULL;
+ ok(knot_rrset_equal(rrset, copy, true), "rrset: cmp NULL owners");
+
+ // Test equal - different rdata
+ knot_rrset_add_rdata(copy, (const uint8_t *)"abc", 3, NULL);
+ ok(!knot_rrset_equal(rrset, copy, true), "rrset: cmp different rdata");
+
+ // Test clear
+ knot_rrset_clear(rrset, NULL);
+ ok(rrset->owner == NULL, "rrset: clear.");
+
+ // Test empty
+ ok(knot_rrset_empty(rrset), "rrset: empty.");
+ ok(knot_rrset_empty(NULL), "rrset: empty NULL.");
+ copy->rrs.count = 1;
+ ok(!knot_rrset_empty(copy), "rrset: not empty.");
+
+ // Test init empty
+ knot_rrset_init_empty(rrset);
+ ok(check_rrset(rrset, NULL, 0, KNOT_CLASS_IN, 0), "rrset: init empty.");
+
+ // "Test" freeing
+ knot_rrset_free(rrset, NULL);
+ knot_rrset_free(copy, NULL);
+
+ return 0;
+}
diff --git a/tests/libknot/test_tsig.c b/tests/libknot/test_tsig.c
new file mode 100644
index 0000000..a34e7ca
--- /dev/null
+++ b/tests/libknot/test_tsig.c
@@ -0,0 +1,204 @@
+/* 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 <assert.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "libknot/errcode.h"
+#include "libknot/tsig.h"
+
+static bool key_is_eq(const knot_tsig_key_t *a, const knot_tsig_key_t *b)
+{
+ if (a == NULL && b == NULL) {
+ return true;
+ }
+
+ if (a == NULL || b == NULL) {
+ return false;
+ }
+
+ return a->algorithm == b->algorithm &&
+ knot_dname_is_equal(a->name, b->name) &&
+ dnssec_binary_cmp(&a->secret, &b->secret) == 0;
+}
+
+#define test_function(function, msg, expected, ...) \
+ knot_tsig_key_t key = { 0 }; \
+ int r = function(&key, __VA_ARGS__); \
+ ok((r != KNOT_EOK && expected == NULL) || \
+ (r == KNOT_EOK && key_is_eq(&key, expected)), \
+ "%s: %s", #function, msg); \
+ knot_tsig_key_deinit(&key);
+
+static void test_init(const char *msg, const knot_tsig_key_t *expected,
+ const char *algo, const char *name, const char *secret)
+{
+ test_function(knot_tsig_key_init, msg, expected, algo, name, secret);
+}
+
+static void test_init_str(const char *msg, const knot_tsig_key_t *expected,
+ const char *params)
+{
+ test_function(knot_tsig_key_init_str, msg, expected, params);
+}
+
+static void test_init_file(const char *msg, const knot_tsig_key_t *expected,
+ const char *filename)
+{
+ test_function(knot_tsig_key_init_file, msg, expected, filename);
+}
+
+static void test_init_file_content(const char *msg,
+ const knot_tsig_key_t *expected,
+ const char *content)
+{
+ char filename[] = "testkey.XXXXXX";
+
+ int fd = mkstemp(filename);
+ if (fd == -1) {
+ bail("failed to create temporary file");
+ return;
+ }
+
+ ok(write(fd, content, strlen(content)) != -1, "file write");
+ close(fd);
+
+ test_init_file(msg, expected, filename);
+
+ unlink(filename);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ // initialization from parameters
+
+ test_init("missing name", NULL, "hmac-md5", NULL, "Wg==");
+ test_init("missing secret", NULL, "hmac-md5", "name", NULL);
+ test_init("invalid HMAC", NULL, "hmac-sha11", "name", "Wg==");
+ {
+ static const knot_tsig_key_t key = {
+ .algorithm = DNSSEC_TSIG_HMAC_SHA256,
+ .name = (uint8_t *)"\x3""key""\x4""name",
+ .secret.size = 1,
+ .secret.data = (uint8_t *)"\x5a"
+ };
+ test_init("default algorithm", &key, NULL, "key.name", "Wg==");
+ }
+ {
+ static const knot_tsig_key_t key = {
+ .algorithm = DNSSEC_TSIG_HMAC_SHA1,
+ .name = (uint8_t *)"\x4""knot""\x3""dns",
+ .secret.size = 6,
+ .secret.data = (uint8_t *)"secret"
+ };
+ test_init("sha1", &key, "hmac-sha1", "knot.dns.", "c2VjcmV0");
+ }
+
+ // initialization from string
+
+ test_init_str("missing value", NULL, NULL);
+ test_init_str("malformed", NULL, "this is malformed");
+ test_init_str("invalid HMAC", NULL, "hmac-sha51299:key:Wg==");
+ {
+ static const knot_tsig_key_t key = {
+ .algorithm = DNSSEC_TSIG_HMAC_SHA256,
+ .name = (uint8_t *)"\x4""tsig""\x3""key",
+ .secret.size = 9,
+ .secret.data = (uint8_t *)"bananakey"
+ };
+ test_init_str("default algorithm", &key, "tsig.key:YmFuYW5ha2V5");
+ }
+ {
+ static const knot_tsig_key_t key = {
+ .algorithm = DNSSEC_TSIG_HMAC_SHA384,
+ .name = (uint8_t *)"\x6""strong""\x3""key",
+ .secret.size = 8,
+ .secret.data = (uint8_t *)"applekey"
+ };
+ test_init_str("sha384", &key, "hmac-sha384:strong.KEY:YXBwbGVrZXk=");
+ }
+
+ // initialization from a file
+
+ test_init_file("no filename", NULL, NULL);
+ test_init_file("not-existing", NULL, "/this-really-should-not-exist");
+ test_init_file_content("malformed content", NULL, "malformed\n");
+ {
+ static const knot_tsig_key_t key = {
+ .algorithm = DNSSEC_TSIG_HMAC_SHA512,
+ .name = (uint8_t *)"\x6""django""\x3""one",
+ .secret.size = 40,
+ .secret.data = (uint8_t *)"Who's that stumbling around in the dark?"
+ };
+ test_init_file_content("sha512", &key,
+ "hmac-sha512:django.one:V2hvJ3MgdGhhdCB"
+ "zdHVtYmxpbmcgYXJvdW5kIGluIHRoZSBkYXJrP"
+ "w==\n\n\n");
+ }
+ {
+ static const knot_tsig_key_t key = {
+ .algorithm = DNSSEC_TSIG_HMAC_SHA512,
+ .name = (uint8_t *)"\x6""django""\x3""two",
+ .secret.size = 22,
+ .secret.data = (uint8_t *)"Prepare to get winged!"
+ };
+ test_init_file_content("sha512 without newline", &key,
+ "hmac-sha512:django.two:UHJlcGFyZSB0byB"
+ "nZXQgd2luZ2VkIQ==");
+ }
+ {
+ static const knot_tsig_key_t key = {
+ .algorithm = DNSSEC_TSIG_HMAC_SHA1,
+ .name = (uint8_t *)"\x4""test",
+ .secret.size = 1,
+ .secret.data = (uint8_t *)"\x5a"
+ };
+ test_init_file_content("leading and trailing white spaces", &key,
+ "\thmac-sha1:test:Wg== \n");
+ }
+
+ // tsig key duplication
+
+ {
+ static const knot_tsig_key_t key = {
+ .algorithm = DNSSEC_TSIG_HMAC_SHA1,
+ .name = (uint8_t *)"\x4""copy""\x2""me",
+ .secret.size = 6,
+ .secret.data = (uint8_t *)"orange"
+ };
+
+ knot_tsig_key_t copy = { 0 };
+ int r;
+
+ r = knot_tsig_key_copy(NULL, &key);
+ ok(r != KNOT_EOK, "knot_tsig_key_copy: no destination");
+ r = knot_tsig_key_copy(&copy, NULL);
+ ok(r != KNOT_EOK, "knot_tsig_key_copy: no source");
+ r = knot_tsig_key_copy(&copy, &key);
+ ok(r == KNOT_EOK && key_is_eq(&copy, &key) &&
+ copy.secret.data != key.secret.data && copy.name != key.name,
+ "knot_tsig_key_copy: simple copy");
+
+ knot_tsig_key_deinit(&copy);
+ }
+
+ return 0;
+}
diff --git a/tests/libknot/test_wire.c b/tests/libknot/test_wire.c
new file mode 100644
index 0000000..3e2468c
--- /dev/null
+++ b/tests/libknot/test_wire.c
@@ -0,0 +1,46 @@
+/* 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 <tap/basic.h>
+
+#include "libknot/wire.h"
+
+#define write_test(size, value, ...) { \
+ const uint8_t expect[] = { __VA_ARGS__ }; \
+ uint8_t wdata[sizeof(expect)] = { 0x00 }; \
+ knot_wire_write_u ## size(wdata, value); \
+ ok(memcmp(wdata, expect, sizeof(expect)) == 0, "%d-bit write", size); \
+}
+
+int main(int argc, char *argv[])
+{
+ plan(8);
+
+ const uint8_t rdata[] = { 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
+
+ is_hex( 0x8899, knot_wire_read_u16(rdata), "16-bit read");
+ is_hex( 0x8899aabb, knot_wire_read_u32(rdata), "32-bit read");
+ is_hex( 0x8899aabbccdd, knot_wire_read_u48(rdata), "48-bit read");
+ is_hex(0x8899aabbccddeeff, knot_wire_read_u64(rdata), "64-bit read");
+
+ write_test(16, 0x1122, 0x11, 0x22);
+ write_test(32, 0x66778899, 0x66, 0x77, 0x88, 0x99);
+ write_test(48, 0xbbccdd778899, 0xbb, 0xcc, 0xdd, 0x77, 0x88, 0x99);
+ write_test(64, 0xbbccddee66778899, 0xbb, 0xcc, 0xdd, 0xee,
+ 0x66, 0x77, 0x88, 0x99);
+
+ return 0;
+}
diff --git a/tests/libknot/test_xdp_tcp.c b/tests/libknot/test_xdp_tcp.c
new file mode 100644
index 0000000..64e3bdc
--- /dev/null
+++ b/tests/libknot/test_xdp_tcp.c
@@ -0,0 +1,633 @@
+/* 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 <unistd.h>
+
+#include "tap/basic.h"
+#include "libknot/error.h"
+#include "libknot/xdp/msg_init.h"
+#include "libknot/xdp/tcp.c"
+#include "libknot/xdp/tcp_iobuf.c"
+#include "libknot/xdp/bpf-user.h"
+
+#define INFTY INT32_MAX
+
+knot_tcp_table_t *test_table = NULL;
+knot_tcp_table_t *test_syn_table = NULL;
+#define TEST_TABLE_SIZE 100
+
+size_t sent_acks = 0;
+size_t sent_rsts = 0;
+size_t sent_syns = 0;
+size_t sent_fins = 0;
+uint32_t sent_seqno = 0;
+uint32_t sent_ackno = 0;
+size_t sent2_data = 0;
+size_t send2_mss = 0;
+
+knot_xdp_socket_t *test_sock = NULL;
+
+struct sockaddr_in test_addr = { AF_INET, 0, { 127 + (1 << 24) }, { 0 } };
+
+knot_tcp_conn_t *test_conn = NULL;
+
+/*!
+ * \brief Length of timeout-watching list.
+ */
+static size_t tcp_table_timeout_length(knot_tcp_table_t *table)
+{
+ return list_size(tcp_table_timeout(table));
+}
+
+/*!
+ * \brief Clean up old TCP connection w/o sending RST or FIN.
+ *
+ * \param tcp_table TCP connection table to clean up.
+ * \param timeout Remove connections older than this (usecs).
+ * \param at_least Remove at least this number of connections.
+ */
+static void tcp_cleanup(knot_tcp_table_t *tcp_table, uint32_t timeout,
+ uint32_t at_least)
+{
+ uint32_t now = get_timestamp(), i = 0;
+ knot_tcp_conn_t *conn, *next;
+ WALK_LIST_DELSAFE(conn, next, *tcp_table_timeout(tcp_table)) {
+ if (i++ < at_least || now - conn->last_active >= timeout) {
+ tcp_table_remove(tcp_table_re_lookup(conn, tcp_table), tcp_table);
+ del_conn(conn);
+ }
+ }
+}
+
+/*!
+ * \brief Find connection related to incoming message.
+ */
+static knot_tcp_conn_t *tcp_table_find(knot_tcp_table_t *table, knot_xdp_msg_t *msg_recv)
+{
+ uint64_t unused = 0;
+ return *tcp_table_lookup(&msg_recv->ip_from, &msg_recv->ip_to, &unused, table);
+}
+
+static int mock_send(_unused_ knot_xdp_socket_t *sock, const knot_xdp_msg_t msgs[],
+ uint32_t n_msgs, _unused_ uint32_t *sent)
+{
+ ok(n_msgs <= 20, "send: not too many at once");
+ for (uint32_t i = 0; i < n_msgs; i++) {
+ const knot_xdp_msg_t *msg = msgs + i;
+
+ ok(msg->flags & KNOT_XDP_MSG_TCP, "send: is TCP message");
+ ok(msg->payload.iov_len == 0, "send: is empty payload");
+
+ if (msg->flags & KNOT_XDP_MSG_RST) {
+ ok(!(msg->flags & KNOT_XDP_MSG_ACK), "send: no RST+ACK");
+ sent_rsts++;
+ } else if (msg->flags & KNOT_XDP_MSG_SYN) {
+ ok(msg->flags & KNOT_XDP_MSG_ACK, "send: is SYN+ACK");
+ sent_syns++;
+ } else if (msg->flags & KNOT_XDP_MSG_FIN) {
+ ok(msg->flags & KNOT_XDP_MSG_ACK, "send: FIN has always ACK");
+ sent_fins++;
+ } else {
+ ok(msg->flags & KNOT_XDP_MSG_ACK, "send: is ACK");
+ sent_acks++;
+ }
+
+ sent_seqno = msg->seqno;
+ sent_ackno = msg->ackno;
+ }
+ return KNOT_EOK;
+}
+
+static int mock_send_nocheck(_unused_ knot_xdp_socket_t *sock, const knot_xdp_msg_t msgs[],
+ uint32_t n_msgs, _unused_ uint32_t *sent)
+{
+ for (uint32_t i = 0; i < n_msgs; i++) {
+ const knot_xdp_msg_t *msg = msgs + i;
+ if (msg->flags & KNOT_XDP_MSG_RST) {
+ sent_rsts++;
+ } else if (msg->flags & KNOT_XDP_MSG_SYN) {
+ sent_syns++;
+ } else if (msg->flags & KNOT_XDP_MSG_FIN) {
+ sent_fins++;
+ } else {
+ sent_acks++;
+ }
+ sent_seqno = msg->seqno;
+ sent_ackno = msg->ackno;
+ }
+ return KNOT_EOK;
+}
+
+static int mock_send2(_unused_ knot_xdp_socket_t *sock, const knot_xdp_msg_t msgs[],
+ uint32_t n_msgs, _unused_ uint32_t *sent)
+{
+ ok(n_msgs <= 20, "send2: not too many at once");
+ for (uint32_t i = 0; i < n_msgs; i++) {
+ const knot_xdp_msg_t *msg = msgs + i;
+ ok(msg->flags & KNOT_XDP_MSG_TCP, "send2: is TCP message");
+ ok(msg->flags & KNOT_XDP_MSG_ACK, "send2: has ACK");
+ ok(msg->payload.iov_len <= send2_mss, "send2: fulfilled MSS");
+ sent2_data += msg->payload.iov_len;
+
+ sent_seqno = msg->seqno;
+ sent_ackno = msg->ackno;
+ }
+ return KNOT_EOK;
+}
+
+static void clean_table(void)
+{
+ (void)tcp_cleanup(test_table, 0, INFTY);
+}
+
+static void clean_sent(void)
+{
+ sent_acks = 0;
+ sent_rsts = 0;
+ sent_syns = 0;
+ sent_fins = 0;
+}
+
+static void check_sent(size_t expect_acks, size_t expect_rsts, size_t expect_syns, size_t expect_fins)
+{
+ is_int(expect_acks, sent_acks, "sent ACKs");
+ is_int(expect_rsts, sent_rsts, "sent RSTs");
+ is_int(expect_syns, sent_syns, "sent SYNs");
+ is_int(expect_fins, sent_fins, "sent FINs");
+ clean_sent();
+}
+
+static void prepare_msg(knot_xdp_msg_t *msg, int flags, uint16_t sport, uint16_t dport)
+{
+ msg_init(msg, flags | KNOT_XDP_MSG_TCP);
+ memcpy(&msg->ip_from, &test_addr, sizeof(test_addr));
+ memcpy(&msg->ip_to, &test_addr, sizeof(test_addr));
+ msg->ip_from.sin6_port = htobe16(sport);
+ msg->ip_to.sin6_port = htobe16(dport);
+}
+
+static void prepare_seqack(knot_xdp_msg_t *msg, int seq_shift, int ack_shift)
+{
+ msg->seqno = sent_ackno + seq_shift;
+ msg->ackno = sent_seqno + ack_shift;
+}
+
+static void prepare_data(knot_xdp_msg_t *msg, const char *bytes, size_t n)
+{
+ msg->payload.iov_len = n;
+ msg->payload.iov_base = (void *)bytes;
+}
+
+static void fix_seqack(knot_xdp_msg_t *msg)
+{
+ knot_tcp_conn_t *conn = tcp_table_find(test_table, msg);
+ if (conn == NULL) {
+ conn = tcp_table_find(test_syn_table, msg);
+ }
+ assert(conn != NULL);
+ msg->seqno = conn->seqno;
+ msg->ackno = conn->ackno;
+}
+
+static void fix_seqacks(knot_xdp_msg_t *msgs, size_t count)
+{
+ for (size_t i = 0; i < count; i++) {
+ fix_seqack(&msgs[i]);
+ }
+}
+
+void test_syn(void)
+{
+ knot_xdp_msg_t msg;
+ knot_tcp_relay_t rl = { 0 };
+ prepare_msg(&msg, KNOT_XDP_MSG_SYN, 1, 2);
+ int ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "SYN: relay OK");
+ ret = knot_tcp_send(test_sock, &rl, 1, 1);
+ is_int(KNOT_EOK, ret, "SYN: send OK");
+ is_int(msg.seqno + 1, sent_ackno, "SYN: ackno");
+ check_sent(0, 0, 1, 0);
+ is_int(XDP_TCP_SYN, rl.action, "SYN: relay action");
+ is_int(XDP_TCP_NOOP, rl.answer, "SYN: relay answer");
+ is_int(0, rl.inbufs_count, "SYN: no payload");
+ is_int(0, test_table->usage, "SYN: no connection in normal table");
+ is_int(1, test_syn_table->usage, "SYN: one connection in SYN table");
+ knot_tcp_conn_t *conn = tcp_table_find(test_syn_table, &msg);
+ ok(conn != NULL, "SYN: connection present");
+ assert(conn);
+ ok(conn == rl.conn, "SYN: relay points to connection");
+ is_int(XDP_TCP_ESTABLISHING, conn->state, "SYN: connection state");
+ ok(memcmp(&conn->ip_rem, &msg.ip_from, sizeof(msg.ip_from)) == 0, "SYN: conn IP from");
+ ok(memcmp(&conn->ip_loc, &msg.ip_to, sizeof(msg.ip_to)) == 0, "SYN: conn IP to");
+
+ knot_tcp_cleanup(test_syn_table, &rl, 1);
+ test_conn = conn;
+}
+
+void test_establish(void)
+{
+ knot_xdp_msg_t msg;
+ knot_tcp_relay_t rl = { 0 };
+ prepare_msg(&msg, KNOT_XDP_MSG_ACK, 1, 2);
+ prepare_seqack(&msg, 0, 1);
+ int ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "establish: relay OK");
+ is_int(0, test_syn_table->usage, "SYN: no connection in SYN table");
+ is_int(1, test_table->usage, "SYN: one connection in normal table");
+ ret = knot_tcp_send(test_sock, &rl, 1, 1);
+ is_int(KNOT_EOK, ret, "establish: send OK");
+ check_sent(0, 0, 0, 0);
+ is_int(0, rl.auto_answer, "establish: no auto answer");
+
+ knot_tcp_cleanup(test_table, &rl, 1);
+ clean_table();
+}
+
+void test_syn_ack(void)
+{
+ knot_xdp_msg_t msg;
+ knot_tcp_relay_t rl = { 0 };
+ prepare_msg(&msg, KNOT_XDP_MSG_SYN | KNOT_XDP_MSG_ACK, 1000, 2000);
+ int ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "SYN+ACK: relay OK");
+ ret = knot_tcp_send(test_sock, &rl, 1, 1);
+ is_int(KNOT_EOK, ret, "SYN+ACK: send OK");
+ is_int(msg.seqno + 1, sent_ackno, "SYN+ACK: ackno");
+ check_sent(1, 0, 0, 0);
+ is_int(XDP_TCP_ESTABLISH, rl.action, "SYN+ACK: relay action");
+ ok(rl.conn != NULL, "SYN+ACK: connection present");
+
+ test_conn = rl.conn;
+ knot_tcp_cleanup(test_table, &rl, 1);
+}
+
+void test_data_fragments(void)
+{
+ const size_t CONNS = 4;
+ knot_xdp_msg_t msgs[CONNS];
+ knot_tcp_relay_t rls[CONNS];
+ memset(rls, 0, CONNS * sizeof(*rls));
+
+ // first msg contains one whole payload and one fragment
+ prepare_msg(&msgs[0], KNOT_XDP_MSG_ACK, 1000, 2000);
+ prepare_seqack(&msgs[0], 0, 0);
+ prepare_data(&msgs[0], "\x00\x03""xyz""\x00\x04""ab", 9);
+
+ // second msg contains just fragment not completing anything
+ prepare_msg(&msgs[1], KNOT_XDP_MSG_ACK, 1000, 2000);
+ prepare_seqack(&msgs[1], 9, 0);
+ prepare_data(&msgs[1], "c", 1);
+
+ // third msg finishes fragment, contains one whole, and starts new fragment by just half of length info
+ prepare_msg(&msgs[2], KNOT_XDP_MSG_ACK, 1000, 2000);
+ prepare_seqack(&msgs[2], 10, 0);
+ prepare_data(&msgs[2], "d""\x00\x01""i""\x00", 5);
+
+ // fourth msg completes fragment and starts never-finishing one
+ prepare_msg(&msgs[3], KNOT_XDP_MSG_ACK, 1000, 2000);
+ prepare_seqack(&msgs[3], 15, 0);
+ prepare_data(&msgs[3], "\x02""AB""\xff\xff""abcdefghijklmnopqrstuvwxyz...", 34);
+
+ assert(test_table);
+ int ret = knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "fragments: relay OK");
+ assert(test_sock);
+ ret = knot_tcp_send(test_sock, rls, CONNS, CONNS);
+ is_int(KNOT_EOK, ret, "fragments: send OK");
+ is_int(msgs[3].ackno, sent_seqno, "fragments: seqno");
+ is_int(msgs[3].seqno + msgs[3].payload.iov_len, sent_ackno, "fragments: ackno");
+ check_sent(4, 0, 0, 0);
+
+ is_int(KNOT_XDP_MSG_ACK, rls[0].auto_answer, "fragments[0]: auto answer");
+ ok(rls[0].conn != NULL, "fragments0: connection present");
+ ok(rls[0].conn == test_conn, "fragments0: same connection");
+ is_int(1, rls[0].inbufs_count, "fragments0: inbufs count");
+ is_int(3, rls[0].inbufs[0].iov_len, "fragments0: data length");
+ is_int(0, memcmp("xyz", rls[0].inbufs[0].iov_base, rls[0].inbufs[0].iov_len), "fragments0: data");
+
+ is_int(KNOT_XDP_MSG_ACK, rls[1].auto_answer, "fragments[1]: auto answer");
+ is_int(XDP_TCP_NOOP, rls[1].action, "fragments[1]: action"); // NOTE: NOOP
+ ok(rls[0].conn != NULL, "fragments1: connection present");
+ ok(rls[0].conn == test_conn, "fragments1: same connection");
+ is_int(0, rls[1].inbufs_count, "fragments1: inbufs count");
+
+ is_int(KNOT_XDP_MSG_ACK, rls[2].auto_answer, "fragments[2]: auto answer");
+ ok(rls[0].conn != NULL, "fragments2: connection present");
+ ok(rls[0].conn == test_conn, "fragments2: same connection");
+ is_int(2, rls[2].inbufs_count, "fragments2: inbufs count");
+ is_int(4, rls[2].inbufs[0].iov_len, "fragments2-0: data length");
+ is_int(0, memcmp("abcd", rls[2].inbufs[0].iov_base, rls[2].inbufs[0].iov_len), "fragments2-0: data");
+ is_int(1, rls[2].inbufs[1].iov_len, "fragments2-1: data length");
+ is_int(0, memcmp("i", rls[2].inbufs[1].iov_base, rls[2].inbufs[1].iov_len), "fragments2-1: data");
+
+ is_int(KNOT_XDP_MSG_ACK, rls[3].auto_answer, "fragments[3]: auto answer");
+ ok(rls[0].conn != NULL, "fragments3: connection present");
+ ok(rls[0].conn == test_conn, "fragments3: same connection");
+ is_int(1, rls[3].inbufs_count, "fragments3: inbufs count");
+ is_int(2, rls[3].inbufs[0].iov_len, "fragments3: data length");
+ is_int(0, memcmp("AB", rls[3].inbufs[0].iov_base, rls[3].inbufs[0].iov_len), "fragments3: data");
+
+ knot_tcp_cleanup(test_table, rls, 4);
+}
+
+void test_close(void)
+{
+ size_t conns_pre = test_table->usage;
+
+ knot_xdp_msg_t msg;
+ knot_tcp_relay_t rl = { 0 };
+ prepare_msg(&msg, KNOT_XDP_MSG_FIN | KNOT_XDP_MSG_ACK,
+ be16toh(test_conn->ip_rem.sin6_port),
+ be16toh(test_conn->ip_loc.sin6_port));
+ prepare_seqack(&msg, 0, 0);
+
+ // test wrong ackno synack, shall reply with RST with same
+ knot_xdp_msg_t wrong = msg;
+ wrong.seqno += INT32_MAX;
+ wrong.ackno += INT32_MAX;
+ int ret = knot_tcp_recv(&rl, &wrong, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "close: relay 0 OK");
+ is_int(KNOT_XDP_MSG_RST, rl.auto_answer, "close: reset wrong ackno");
+ is_int(rl.auto_seqno, wrong.ackno, "close: reset seqno");
+ ret = knot_tcp_send(test_sock, &rl, 1, 1);
+ is_int(KNOT_EOK, ret, "close: send 0 OK");
+ check_sent(0, 1, 0, 0);
+ is_int(sent_seqno, wrong.ackno, "close: reset seqno sent");
+
+ ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "close: relay 1 OK");
+ ret = knot_tcp_send(test_sock, &rl, 1, 1);
+ is_int(KNOT_EOK, ret, "close: send OK");
+ check_sent(0, 0, 0, 1);
+ is_int(XDP_TCP_CLOSE, rl.action, "close: relay action");
+ assert(rl.conn);
+ ok(rl.conn == test_conn, "close: same connection");
+ is_int(XDP_TCP_CLOSING2, rl.conn->state, "close: conn state");
+
+ msg.flags &= ~KNOT_XDP_MSG_FIN;
+ prepare_seqack(&msg, 0, 0);
+ ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "close: relay 2 OK");
+ ret = knot_tcp_send(test_sock, &rl, 1, 1);
+ is_int(KNOT_EOK, ret, "close: send 2 OK");
+ check_sent(0, 0, 0, 0);
+ is_int(conns_pre - 1, test_table->usage, "close: connection removed");
+ is_int(conns_pre - 1, tcp_table_timeout_length(test_table), "close: timeout list size");
+ knot_tcp_cleanup(test_table, &rl, 1);
+}
+
+void test_many(void)
+{
+ size_t CONNS = test_table->size * test_table->size;
+ size_t i_survive = CONNS / 2;
+ uint32_t timeout_time = 1000000;
+
+ knot_xdp_msg_t *msgs = malloc(CONNS * sizeof(*msgs));
+ assert(msgs != NULL);
+ for (size_t i = 0; i < CONNS; i++) {
+ prepare_msg(&msgs[i], KNOT_XDP_MSG_SYN, i + 2, 1);
+ }
+ knot_tcp_relay_t *rls = malloc(CONNS * sizeof(*rls));
+
+ int ret = knot_tcp_recv(rls, msgs, CONNS, test_table, NULL, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "many: relay OK");
+ ret = knot_tcp_send(test_sock, rls, CONNS, CONNS);
+ is_int(KNOT_EOK, ret, "many: relay send OK");
+ check_sent(0, 0, CONNS, 0);
+ is_int(CONNS, test_table->usage, "many: table usage");
+
+ knot_tcp_cleanup(test_table, rls, CONNS);
+ usleep(timeout_time);
+ knot_xdp_msg_t *survive = &msgs[i_survive];
+ knot_tcp_relay_t surv_rl = { 0 };
+ survive->flags = (KNOT_XDP_MSG_TCP | KNOT_XDP_MSG_ACK);
+ knot_tcp_conn_t *surv_conn = tcp_table_find(test_table, survive);
+ fix_seqack(survive);
+ prepare_data(survive, "\x00\x00", 2);
+ assert(test_table);
+ ret = knot_tcp_recv(&surv_rl, survive, 1, test_table, NULL, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "many/survivor: OK");
+ clean_sent();
+
+ knot_sweep_stats_t stats = { 0 };
+ ret = knot_tcp_sweep(test_table, timeout_time, INFTY, INFTY, INFTY, INFTY,
+ INFTY, rls, CONNS, &stats);
+ is_int(KNOT_EOK, ret, "many/timeout1: OK");
+ is_int(CONNS - 1, stats.counters[KNOT_SWEEP_CTR_TIMEOUT], "many/timeout1: close count");
+ is_int(0, stats.counters[KNOT_SWEEP_CTR_LIMIT_CONN], "may/timeout1: reset count");
+ ret = knot_tcp_send(test_sock, rls, CONNS, CONNS);
+ is_int(KNOT_EOK, ret, "many/timeout1: send OK");
+ check_sent(0, 0, 0, CONNS - 1);
+
+ knot_sweep_stats_reset(&stats);
+ ret = knot_tcp_sweep(test_table, INFTY, timeout_time, INFTY, INFTY, INFTY,
+ INFTY, rls, CONNS, &stats);
+ is_int(KNOT_EOK, ret, "many/timeout2: OK");
+ is_int(0, stats.counters[KNOT_SWEEP_CTR_TIMEOUT], "many/timeout2: close count");
+ is_int(CONNS - 1, stats.counters[KNOT_SWEEP_CTR_LIMIT_CONN], "may/timeout2: reset count");
+ ret = knot_tcp_send(test_sock, rls, CONNS, CONNS);
+ is_int(KNOT_EOK, ret, "many/timeout2: send OK");
+ check_sent(0, CONNS - 1, 0, 0);
+ knot_tcp_cleanup(test_table, rls, CONNS);
+ is_int(1, test_table->usage, "many/timeout: one survivor");
+ is_int(1, tcp_table_timeout_length(test_table), "many/timeout: one survivor in timeout list");
+ ok(surv_conn != NULL, "many/timeout: survivor connection present");
+ ok(surv_conn == surv_rl.conn, "many/timeout: same connection");
+ knot_tcp_cleanup(test_table, &surv_rl, 1);
+
+ free(msgs);
+ free(rls);
+}
+
+void test_ibufs_size(void)
+{
+ int CONNS = 4;
+ knot_xdp_msg_t msgs[CONNS];
+ knot_tcp_relay_t rls[CONNS];
+
+ // just open connections
+ for (int i = 0; i < CONNS; i++) {
+ prepare_msg(&msgs[i], KNOT_XDP_MSG_SYN, i + 2000, 1);
+ }
+ int ret = knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "ibufs: open OK");
+ ret = knot_tcp_send(test_sock, rls, CONNS, CONNS);
+ is_int(KNOT_EOK, ret, "ibufs: first send OK");
+ check_sent(0, 0, CONNS, 0);
+ for (int i = 0; i < CONNS; i++) {
+ msgs[i].flags = KNOT_XDP_MSG_TCP | KNOT_XDP_MSG_ACK;
+ }
+ fix_seqacks(msgs, CONNS);
+ (void)knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+
+ is_int(0, test_table->inbufs_total, "inbufs: initial total zero");
+
+ // first connection will start a fragment buf then finish it
+ fix_seqack(&msgs[0]);
+ prepare_data(&msgs[0], "\x00\x0a""lorem", 7);
+ ret = knot_tcp_recv(&rls[0], &msgs[0], 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "ibufs: must be OK");
+ ret = knot_tcp_send(test_sock, &rls[0], 1, 1);
+ is_int(KNOT_EOK, ret, "ibufs: must send OK");
+ check_sent(1, 0, 0, 0);
+ is_int(7, test_table->inbufs_total, "inbufs: first inbuf");
+ knot_tcp_cleanup(test_table, &rls[0], 1);
+
+ // other connection will just store fragments
+ fix_seqacks(msgs, CONNS);
+ prepare_data(&msgs[0], "ipsum", 5);
+ prepare_data(&msgs[1], "\x00\xff""12345", 7);
+ prepare_data(&msgs[2], "\xff\xff""abcde", 7);
+ prepare_data(&msgs[3], "\xff\xff""abcde", 7);
+ ret = knot_tcp_recv(rls, msgs, CONNS, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "inbufs: relay OK");
+ ret = knot_tcp_send(test_sock, rls, CONNS, CONNS);
+ is_int(KNOT_EOK, ret, "inbufs: send OK");
+ check_sent(CONNS, 0, 0, 0);
+ is_int(21, test_table->inbufs_total, "inbufs: after change");
+ is_int(0, rls[1].action, "inbufs: one relay");
+ is_int(10, rls[0].inbufs[0].iov_len, "inbufs: data length");
+ knot_tcp_cleanup(test_table, rls, CONNS);
+
+ // now free some
+ knot_sweep_stats_t stats = { 0 };
+ ret = knot_tcp_sweep(test_table, INFTY, INFTY, INFTY, INFTY,
+ test_table->inbufs_total - 8, INFTY, rls,
+ CONNS, &stats);
+ is_int(KNOT_EOK, ret, "inbufs: timeout OK");
+ ret = knot_tcp_send(test_sock, rls, CONNS, CONNS);
+ is_int(KNOT_EOK, ret, "inbufs: timeout send OK");
+ check_sent(0, 2, 0, 0);
+ is_int(0, stats.counters[KNOT_SWEEP_CTR_TIMEOUT], "inbufs: close count");
+ is_int(2, stats.counters[KNOT_SWEEP_CTR_LIMIT_IBUF], "inbufs: reset count");
+ knot_tcp_cleanup(test_table, rls, CONNS);
+ is_int(7, test_table->inbufs_total, "inbufs: final state");
+ ok(NULL != tcp_table_find(test_table, &msgs[0]), "inbufs: first conn survived");
+ ok(NULL == tcp_table_find(test_table, &msgs[1]), "inbufs: second conn not survived");
+ ok(NULL == tcp_table_find(test_table, &msgs[2]), "inbufs: third conn not survived");
+ ok(NULL != tcp_table_find(test_table, &msgs[3]), "inbufs: fourth conn survived");
+
+ clean_table();
+}
+
+void test_obufs(void)
+{
+ knot_xdp_msg_t msg;
+ knot_tcp_relay_t rl = { 0 };
+
+ prepare_msg(&msg, KNOT_XDP_MSG_SYN, 1, 2);
+ (void)knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); // SYN
+ (void)knot_tcp_send(test_sock, &rl, 1, 1); // SYN+ACK
+ prepare_msg(&msg, KNOT_XDP_MSG_ACK, 1, 2);
+ prepare_seqack(&msg, 0, 1);
+ (void)knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE); // ACK
+
+ size_t TEST_MSS = 1111;
+ size_t DATA_LEN = 65535; // with 2-byte len prefix, this is > 64k == window_size
+ uint8_t *data = calloc(DATA_LEN, 1);
+ assert(rl.conn);
+ rl.conn->mss = TEST_MSS;
+ rl.conn->window_size = 65536;
+ send2_mss = TEST_MSS;
+
+ int ret = knot_tcp_reply_data(&rl, test_table, false, data, DATA_LEN), i = 0;
+ is_int(KNOT_EOK, ret, "obufs: fill with data");
+ for (knot_tcp_outbuf_t *ob = rl.conn->outbufs; ob != NULL; ob = ob->next, i++) {
+ if (ob->next == NULL) {
+ ok(ob->len > 0, "init last ob[%d]: non-trivial", i);
+ ok(ob->len <= TEST_MSS, "init last ob[%d]: fulfills MSS", i);
+ } else {
+ is_int(TEST_MSS, ob->len, "init ob[%d]: exactly MSS", i);
+ }
+ ok(!ob->sent, "init ob[%d]: not sent", i);
+ }
+ ret = knot_tcp_send(test_sock, &rl, 1, 20), i = 0;
+ is_int(KNOT_EOK, ret, "obufs: send OK");
+ is_int((DATA_LEN + 2) / TEST_MSS * TEST_MSS, sent2_data, "obufs: sent all but one MSS");
+ for (knot_tcp_outbuf_t *ob = rl.conn->outbufs; ob != NULL; ob = ob->next, i++) {
+ if (ob->next == NULL) {
+ ok(!ob->sent, "last ob[%d]: not sent", i);
+ } else {
+ ok(ob->sent, "ob[%d]: sent", i);
+ if (ob->next->next != NULL) {
+ is_int(ob->seqno + ob->len, ob->next->seqno, "init ob[%d+1]: seqno", i);
+ }
+ }
+ }
+ knot_tcp_cleanup(test_table, &rl, 1);
+
+ prepare_seqack(&msg, 0, TEST_MSS);
+ ret = knot_tcp_recv(&rl, &msg, 1, test_table, test_syn_table, XDP_TCP_IGNORE_NONE);
+ is_int(KNOT_EOK, ret, "obufs: ACKed data");
+ assert(rl.conn);
+ rl.conn->window_size = 65536;
+ knot_tcp_outbuf_t *surv_ob = rl.conn->outbufs;
+ ok(surv_ob != NULL, "obufs: unACKed survived");
+ assert(surv_ob);
+ ok(surv_ob->next == NULL, "obufs: just one survived");
+ ok(!surv_ob->sent, "obufs: survivor not sent");
+ ret = knot_tcp_send(test_sock, &rl, 1, 20);
+ is_int(KNOT_EOK, ret, "obufs: send rest OK");
+ is_int(DATA_LEN + 2, sent2_data, "obufs: sent all");
+ ok(surv_ob->sent, "obufs: survivor sent");
+ is_int(sent_seqno, surv_ob->seqno, "obufs: survivor seqno");
+
+ knot_tcp_cleanup(test_table, &rl, 1);
+ clean_table();
+ free(data);
+}
+
+static void init_mock(knot_xdp_socket_t **socket, void *send_mock)
+{
+ *socket = calloc(1, sizeof(**socket));
+ if (*socket != NULL) {
+ (*socket)->send_mock = send_mock;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ test_table = knot_tcp_table_new(TEST_TABLE_SIZE, NULL);
+ assert(test_table != NULL);
+ test_syn_table = knot_tcp_table_new(TEST_TABLE_SIZE, test_table);
+
+ init_mock(&test_sock, mock_send);
+
+ test_syn();
+ test_establish();
+
+ test_syn_ack();
+ test_data_fragments();
+ test_close();
+
+ test_ibufs_size();
+
+ knot_xdp_deinit(test_sock);
+ init_mock(&test_sock, mock_send_nocheck);
+ test_many();
+
+ knot_xdp_deinit(test_sock);
+ init_mock(&test_sock, mock_send2);
+ test_obufs();
+
+ knot_xdp_deinit(test_sock);
+ knot_tcp_table_free(test_table);
+ knot_tcp_table_free(test_syn_table);
+
+ return 0;
+}
diff --git a/tests/libknot/test_yparser.c b/tests/libknot/test_yparser.c
new file mode 100644
index 0000000..8655096
--- /dev/null
+++ b/tests/libknot/test_yparser.c
@@ -0,0 +1,346 @@
+/* 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 <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "libknot/yparser/yparser.h"
+#include "libknot/libknot.h"
+
+const char *syntax_ok =
+ "#comment\n"
+ " # comment\n"
+ "a:\n"
+ "a :\n"
+ "a : #comment\n"
+ "\n"
+ "b: \"b\"\n"
+ "b: b #comment\n"
+ "b : b\n"
+ "b: [ b] # comment\n"
+ "b: [b ]\n"
+ "b: [ b ]\n"
+ "\n"
+ " f: \"f\"\n"
+ " f: f #comment\n"
+ " f : f\n"
+ " f: [ f] # comment\n"
+ " f: [f ]\n"
+ " f: [ f ]\n"
+ " f: [ \"f\" ]\n"
+ "\n"
+ "c: [a,b]\n"
+ "c: [a, b]\n"
+ "c: [a ,b]\n"
+ "c: [a , b]\n"
+ "c: [ a , b ]\n"
+ "c: [ \"a\" , \"b\" ]\n"
+ "\n"
+ "- d: d\n"
+ "- d : d # comment\n"
+ "\n"
+ "e: \"a#b' c[d,]\"\n"
+ "\n"
+ "zone:\n"
+ "#comment\n"
+ " # comment\n"
+ " - domain: example. # comment\n"
+ " master: bind\n"
+ " - domain: example.\n"
+ " master: bind\n"
+ "zone2:\n"
+ " - a: b # different indentation";
+
+const char *syntax_error1 =
+ "f:\n"
+ " - a: b\n"
+ " - b: c\n";
+
+const char *syntax_error2 =
+ "f:\n"
+ " - a: b\n"
+ " c: d\n";
+
+const char *syntax_error3 =
+ "f:\n"
+ " a: b\n"
+ " c: d\n";
+
+const char *tab_error1 =
+ "a:\n"
+ "b:\t\n";
+
+const char *tab_error2 =
+ "a:\n"
+ "b: c\t\n";
+
+const char *tab_error3 =
+ "a:\n"
+ "\t\n";
+
+const char *dname_ok =
+ ".:\n"
+ "dom-ain:\n"
+ "\\070-\\071.\\072.:\n"
+ "*.wildchar.com:\n"
+ "_ldap._tcp.example.com:\n";
+
+const char *quotes_ok =
+ "g: \"\"\n"
+ "g: a\\ b\n"
+ "g: \"\\# 1 00\"\n"
+ "g: \"\\\"\\\"\"\n"
+ "g: \" a \\\" b \\\" \\\"c\\\" \"\n"
+ "g: \"\\@ \\[ \\# \\, \\]\"\n";
+
+const char *utf8_ok =
+ "key: příšera\n";
+
+static void test_syntax_ok(yp_parser_t *yp)
+{
+ // OK input.
+ int ret = yp_set_input_string(yp, syntax_ok, strlen(syntax_ok));
+ is_int(KNOT_EOK, ret, "set input string");
+
+ size_t line = 3;
+ for (int i = 0; i < 3; i++) {
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse %i. key0", i);
+ ok(yp->key_len == 1 && yp->key[0] == 'a' &&
+ yp->data_len == 0 && yp->event == YP_EKEY0 &&
+ yp->line_count == line + i, "compare %i. key0", i);
+ }
+
+ line += 4;
+ for (int i = 0; i < 6; i++) {
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse %i. key0 with value", i);
+ ok(yp->key_len == 1 && yp->key[0] == 'b' &&
+ yp->data_len == 1 && yp->data[0] == 'b' &&
+ yp->event == YP_EKEY0 && yp->line_count == line + i,
+ "compare %i. key0 with value", i);
+ }
+
+ line += 7;
+ for (int i = 0; i < 7; i++) {
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse %i. key1 with value", i);
+ ok(yp->key_len == 1 && yp->key[0] == 'f' &&
+ yp->data_len == 1 && yp->data[0] == 'f' &&
+ yp->event == YP_EKEY1 && yp->line_count == line + i,
+ "compare %i. key1 with value", i);
+ }
+
+ line += 8;
+ for (int i = 0; i < 6; i++) {
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse %i. key0 with first value", i);
+ ok(yp->key_len == 1 && yp->key[0] == 'c' &&
+ yp->data_len == 1 && yp->data[0] == 'a' &&
+ yp->event == YP_EKEY0 && yp->line_count == line + i,
+ "compare %i. key0 with first value", i);
+
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse %i. key0 with second value", i);
+ ok(yp->key_len == 1 && yp->key[0] == 'c' &&
+ yp->data_len == 1 && yp->data[0] == 'b' &&
+ yp->event == YP_EKEY0 && yp->line_count == line + i,
+ "compare %i. key0 with second value", i);
+ }
+
+ line += 7;
+ for (int i = 0; i < 2; i++) {
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse %i. id", i);
+ ok(yp->key_len == 1 && yp->key[0] == 'd' &&
+ yp->data_len == 1 && yp->data[0] == 'd' &&
+ yp->event == YP_EID && yp->line_count == line + i,
+ "compare %i. id", i);
+ }
+
+ line += 3;
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse key0 with quoted value");
+ ok(yp->key_len == 1 && yp->key[0] == 'e' && yp->data_len == 10 &&
+ memcmp(yp->data, "a#b' c[d,]", yp->data_len) == 0 &&
+ yp->event == YP_EKEY0 && yp->line_count == line,
+ "compare key0 with quoted value");
+
+ line += 2;
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse key0");
+ ok(yp->key_len == 4 && strcmp(yp->key, "zone") == 0 &&
+ yp->data_len == 0 &&
+ yp->event == YP_EKEY0 && yp->line_count == line,
+ "compare key0 value");
+
+ line += 3;
+ for (int i = 0; i < 2; i++) {
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse %i. id", i);
+ ok(yp->key_len == 6 && strcmp(yp->key, "domain") == 0 &&
+ yp->data_len == 8 && strcmp(yp->data, "example.") == 0 &&
+ yp->event == YP_EID && yp->line_count == line + 2 * i,
+ "compare id");
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse %i. key1", i);
+ ok(yp->key_len == 6 && strcmp(yp->key, "master") == 0 &&
+ yp->data_len == 4 && strcmp(yp->data, "bind") == 0 &&
+ yp->event == YP_EKEY1 && yp->line_count == line + 2 * i + 1,
+ "compare key1");
+ }
+
+ line += 4;
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse key0");
+ ok(yp->key_len == 5 && strcmp(yp->key, "zone2") == 0 &&
+ yp->data_len == 0 &&
+ yp->event == YP_EKEY0 && yp->line_count == line,
+ "compare key0 value");
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse key1");
+ ok(yp->key_len == 1 && strcmp(yp->key, "a") == 0 &&
+ yp->data_len == 1 && strcmp(yp->data, "b") == 0 &&
+ yp->event == YP_EID && yp->line_count == line + 1,
+ "compare key1 value");
+
+ ret = yp_parse(yp);
+ is_int(KNOT_EOF, ret, "parse EOF");
+}
+
+static void test_syntax_error(yp_parser_t *yp, const char *input)
+{
+ static int count = 1;
+
+ int ret = yp_set_input_string(yp, input, strlen(input));
+ is_int(KNOT_EOK, ret, "set syntax error input string %i", count++);
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse key0");
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse key1");
+ ret = yp_parse(yp);
+ is_int(KNOT_YP_EINVAL_INDENT, ret, "parse key1 - invalid indentation");
+ is_int(yp->line_count, 3, "invalid indentation line");
+}
+
+static void test_tab_error(yp_parser_t *yp, const char *input)
+{
+ static int count = 1;
+
+ int ret = yp_set_input_string(yp, input, strlen(input));
+ is_int(KNOT_EOK, ret, "set tab error input string %i", count++);
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse key0");
+ ret = yp_parse(yp);
+ is_int(KNOT_YP_ECHAR_TAB, ret, "invalid tabulator");
+ is_int(yp->line_count, 2, "invalid tabulator line");
+}
+
+static void test_dname(yp_parser_t *yp)
+{
+#define CHECK_DNAME(str) \
+ ret = yp_parse(yp); \
+ is_int(KNOT_EOK, ret, "parse dname " str); \
+ ok(yp->key_len == strlen(str) && strcmp(yp->key, str) == 0 && yp->data_len == 0 && \
+ yp->event == YP_EKEY0 && yp->line_count == line++, "compare " str);
+
+ // Dname key value.
+ int ret = yp_set_input_string(yp, dname_ok, strlen(dname_ok));
+ is_int(KNOT_EOK, ret, "set input string");
+
+ size_t line = 1;
+ CHECK_DNAME(".");
+ CHECK_DNAME("dom-ain");
+ CHECK_DNAME("\\070-\\071.\\072.");
+ CHECK_DNAME("*.wildchar.com");
+ CHECK_DNAME("_ldap._tcp.example.com");
+}
+
+static void test_quotes(yp_parser_t *yp)
+{
+#define CHECK_QUOTE(str) \
+ ret = yp_parse(yp); \
+ is_int(KNOT_EOK, ret, "parse quoted " str); \
+ ok(yp->key_len == 1 && yp->key[0] == 'g' && \
+ yp->data_len == strlen(str) && strcmp(yp->data, str) == 0 && \
+ yp->event == YP_EKEY0 && yp->line_count == line++, "compare " str);
+
+ int ret = yp_set_input_string(yp, quotes_ok, strlen(quotes_ok));
+ is_int(KNOT_EOK, ret, "set input string");
+
+ size_t line = 1;
+ CHECK_QUOTE("");
+ CHECK_QUOTE("a\\ b");
+ CHECK_QUOTE("\\# 1 00");
+ CHECK_QUOTE("\"\"");
+ CHECK_QUOTE(" a \" b \" \"c\" ");
+ CHECK_QUOTE("\\@ \\[ \\# \\, \\]");
+}
+
+// Check that wrong wildcard dname is NOT parsed as valid dname.
+static void test_wildcard(yp_parser_t *yp)
+{
+#define CHECK_NOT_WILDCARD(str) \
+ ret = yp_set_input_string(yp, str, strlen(str)); \
+ is_int(KNOT_EOK, ret, "set input string");\
+ ret = yp_parse(yp); \
+ is_int(KNOT_EPARSEFAIL, ret, str " is not wildcard"); \
+ ok(yp->key_len != strlen(str) || strcmp(yp->key, str) != 0 || \
+ yp->event != YP_EKEY0, "compare " str);
+
+ int ret;
+ CHECK_NOT_WILDCARD("a.*.example.com.");
+ CHECK_NOT_WILDCARD("**.example.com.");
+ CHECK_NOT_WILDCARD("*example.com.");
+}
+
+static void test_utf8(yp_parser_t *yp)
+{
+ int ret = yp_set_input_string(yp, utf8_ok, strlen(utf8_ok));
+ is_int(KNOT_EOK, ret, "set input string");
+
+ ret = yp_parse(yp);
+ is_int(KNOT_EOK, ret, "parse key with a value in UTF-8");
+ ok(yp->key_len == 3 && strcmp(yp->key, "key") == 0 &&
+ yp->data_len == 10 && strcmp(yp->data, "p""\xC5\x99\xC3\xAD\xC5\xA1""era") == 0,
+ "compare UTF-8 value");
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ yp_parser_t yp;
+ yp_init(&yp);
+
+ test_syntax_ok(&yp);
+ test_syntax_error(&yp, syntax_error1);
+ test_syntax_error(&yp, syntax_error2);
+ test_syntax_error(&yp, syntax_error3);
+ test_tab_error(&yp, tab_error1);
+ test_tab_error(&yp, tab_error2);
+ test_tab_error(&yp, tab_error3);
+ test_dname(&yp);
+ test_quotes(&yp);
+ test_wildcard(&yp);
+ test_utf8(&yp);
+
+ yp_deinit(&yp);
+
+ return 0;
+}
diff --git a/tests/libknot/test_ypschema.c b/tests/libknot/test_ypschema.c
new file mode 100644
index 0000000..728a847
--- /dev/null
+++ b/tests/libknot/test_ypschema.c
@@ -0,0 +1,417 @@
+/* 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 <stdint.h>
+#include <string.h>
+#include <tap/basic.h>
+
+#include "libknot/yparser/ypschema.h"
+#include "libknot/yparser/yptrafo.h"
+#include "libknot/libknot.h"
+
+#define C_ID "\x02""id"
+#define C_INT "\x07""integer"
+#define C_BOOL "\x04""bool"
+#define C_OPT "\x06""option"
+#define C_STR "\x06""string"
+#define C_ADDR "\x07""address"
+#define C_DNAME "\x05""dname"
+#define C_HEX "\x03""hex"
+#define C_BASE64 "\x06""base64"
+#define C_DATA "\x04""data"
+#define C_REF "\x09""reference"
+#define C_GRP "\x05""group"
+#define C_MULTIGRP "\x0B""multi-group"
+
+static const yp_item_t group[] = {
+ { C_INT, YP_TINT, YP_VINT = { 0, 100, YP_NIL } },
+ { C_STR, YP_TSTR, YP_VNONE, YP_FMULTI },
+ { NULL }
+};
+
+static const yp_item_t multi_group[] = {
+ { C_ID, YP_TSTR, YP_VNONE },
+ { C_HEX, YP_THEX, YP_VNONE },
+ { C_BASE64, YP_TB64, YP_VNONE },
+ { NULL }
+};
+
+static const knot_lookup_t opts[] = {
+ { 1, "one" },
+ { 10, "ten" },
+ { 0, NULL }
+ };
+
+static const yp_item_t static_schema[] = {
+ { C_OPT, YP_TOPT, YP_VOPT = { opts } },
+ { C_BOOL, YP_TBOOL, YP_VNONE },
+ { C_DNAME, YP_TDNAME, YP_VNONE },
+ { C_GRP, YP_TGRP, YP_VGRP = { group } },
+ { C_MULTIGRP, YP_TGRP, YP_VGRP = { multi_group }, YP_FMULTI },
+ { C_REF, YP_TREF, YP_VREF = { C_MULTIGRP } },
+ { C_DATA, YP_TDATA, YP_VNONE },
+ { NULL }
+};
+
+static void schema_find_test(void)
+{
+ yp_item_t *schema = NULL;
+
+ int ret = yp_schema_copy(&schema, static_schema);
+ is_int(KNOT_EOK, ret, "schema copy");
+
+ const yp_item_t *i = yp_schema_find(C_OPT, NULL, schema);
+ ok(i != NULL, "schema find");
+ if (i == NULL) {
+ goto error_schema;
+ }
+ ok(strcmp(&i->name[1], &C_OPT[1]) == 0, "name check");
+
+ i = yp_schema_find(C_STR, C_GRP, schema);
+ ok(i != NULL, "schema find with parent");
+ if (i == NULL) {
+ goto error_schema;
+ }
+ ok(strcmp(&i->name[1], &C_STR[1]) == 0, "name check");
+
+ i = yp_schema_find(C_ADDR, NULL, schema);
+ ok(i == NULL, "schema not find");
+
+ i = yp_schema_find(C_ADDR, C_GRP, schema);
+ ok(i == NULL, "schema not find with parent");
+
+error_schema:
+ yp_schema_free(schema);
+}
+
+static void schema_merge_test(void)
+{
+ static const yp_item_t items1[] = {
+ { "\x01""1", YP_TSTR, YP_VNONE },
+ { "\x01""2", YP_TSTR, YP_VNONE },
+ { NULL }
+ };
+
+ static const yp_item_t items2[] = {
+ { "\x01""3", YP_TSTR, YP_VNONE },
+ { "\x01""4", YP_TSTR, YP_VNONE },
+ { NULL }
+ };
+
+ yp_item_t *schema = NULL;
+ yp_item_t *tmp = NULL;
+
+ int ret = yp_schema_copy(&tmp, items1);
+ is_int(KNOT_EOK, ret, "schema copy");
+
+ ret = yp_schema_merge(&schema, items1, items2);
+ is_int(KNOT_EOK, ret, "schema merge");
+
+ yp_schema_free(tmp);
+
+ for (uint8_t i = 0; i < 4; i++) {
+ yp_name_t name[3] = { '\x01', '1' + i };
+ const yp_item_t *item = yp_schema_find(name, NULL, schema);
+ ok(item != NULL, "schema find");
+ }
+
+ yp_schema_free(schema);
+}
+
+#define SET_INPUT_STR(str) \
+ ret = yp_set_input_string(yp, str, strlen(str)); \
+ is_int(KNOT_EOK, ret, "set input string");
+
+#define PARSER_CHECK(depth) \
+ ret = yp_parse(yp); \
+ is_int(KNOT_EOK, ret, "parse"); \
+ ret = yp_schema_check_parser(ctx, yp); \
+ is_int(KNOT_EOK, ret, "check parser"); \
+ node = &ctx->nodes[ctx->current]; \
+ parent = node->parent; \
+ ok(ctx->current == depth, "depth check");
+
+#define PARSER_RET_CHECK(code) \
+ ret = yp_parse(yp); \
+ is_int(KNOT_EOK, ret, "parse"); \
+ ret = yp_schema_check_parser(ctx, yp); \
+ ok(ret == code, "return check parser");
+
+static void parser_test(void)
+{
+ yp_parser_t yparser;
+ yp_parser_t *yp = &yparser;
+ yp_item_t *schema = NULL;
+ yp_check_ctx_t *ctx = NULL;
+
+ yp_init(yp);
+
+ int ret = yp_schema_copy(&schema, static_schema);
+ is_int(KNOT_EOK, ret, "schema copy");
+ if (ret != KNOT_EOK) {
+ goto error_parser;
+ }
+
+ ctx = yp_schema_check_init(&schema);
+ ok(ctx != NULL, "create check ctx");
+ if (ctx == NULL) {
+ goto error_parser;
+ }
+
+ yp_node_t *node;
+ yp_node_t *parent;
+ const yp_item_t *id;
+
+ diag("parser key0 test");
+ SET_INPUT_STR("option: one");
+ PARSER_CHECK(0);
+ ok(strcmp(node->item->name + 1, "option") == 0, "name check");
+ ok(node->item->type == YP_TOPT, "type check");
+ ok(yp_opt(node->data) == 1, "value check");
+
+ diag("parser group test");
+ SET_INPUT_STR("group:\n integer: 20\n string: [short, \"long string\"]");
+ PARSER_CHECK(0);
+ ok(strcmp(node->item->name + 1, "group") == 0, "name check");
+ ok(node->item->type == YP_TGRP, "type check");
+ ok(node->data_len == 0, "value length check");
+ PARSER_CHECK(1);
+ ok(strcmp(node->item->name + 1, "integer") == 0, "name check");
+ ok(node->item->type == YP_TINT, "type check");
+ ok(yp_int(node->data) == 20, "value check");
+ PARSER_CHECK(1);
+ ok(strcmp(node->item->name + 1, "string") == 0, "name check");
+ ok(node->item->type == YP_TSTR, "type check");
+ ok(strcmp(yp_str(node->data), "short") == 0, "value check");
+ PARSER_CHECK(1);
+ ok(strcmp(node->item->name + 1, "string") == 0, "name check");
+ ok(node->item->type == YP_TSTR, "type check");
+ ok(strcmp(yp_str(node->data), "long string") == 0, "value check");
+
+ diag("parser multi-group test");
+ SET_INPUT_STR("multi-group:\n - id: foo\n base64: Zm9vYmFy\nreference: foo");
+ PARSER_CHECK(0);
+ ok(strcmp(node->item->name + 1, "multi-group") == 0, "name check");
+ ok(node->item->type == YP_TGRP, "type check");
+ ok(node->data_len == 0, "value length check");
+ PARSER_CHECK(0);
+ ok(node->id_len > 0, "id check");
+ ok(strcmp(node->item->name + 1, "multi-group") == 0, "name check");
+ ok(node->item->type == YP_TGRP, "type check");
+ ok(node->data_len == 0, "value length check");
+ id = node->item->var.g.id;
+ ok(strcmp(id->name + 1, "id") == 0, "name check");
+ ok(id->type == YP_TSTR, "type check");
+ ok(strcmp(yp_str(node->id), "foo") == 0, "value check");
+ PARSER_CHECK(1);
+ id = parent->item->var.g.id;
+ ok(strcmp(parent->item->name + 1, "multi-group") == 0, "name check");
+ ok(parent->item->type == YP_TGRP, "type check");
+ ok(parent->data_len == 0, "value length check");
+ ok(strcmp(yp_str(parent->id), "foo") == 0, "value check");
+ ok(strcmp(id->name + 1, "id") == 0, "name check");
+ ok(id->type == YP_TSTR, "type check");
+ ok(strcmp(node->item->name + 1, "base64") == 0, "name check");
+ ok(node->item->type == YP_TB64, "type check");
+ ok(memcmp(yp_bin(node->data), "foobar", yp_bin_len(node->data)) == 0,
+ "value check");
+ ok(node->id_len == 0, "id length check");
+ PARSER_CHECK(0);
+ ok(strcmp(node->item->name + 1, "reference") == 0, "name check");
+ ok(node->item->type == YP_TREF, "type check");
+ ok(strcmp(yp_str(node->data), "foo") == 0, "value check");
+
+ diag("parser check return");
+ SET_INPUT_STR("unknown:");
+ PARSER_RET_CHECK(KNOT_YP_EINVAL_ITEM);
+
+ SET_INPUT_STR("group:\n unknown:");
+ PARSER_RET_CHECK(KNOT_EOK);
+ PARSER_RET_CHECK(KNOT_YP_EINVAL_ITEM);
+
+ SET_INPUT_STR("group:\n - unknown: data");
+ PARSER_RET_CHECK(KNOT_EOK);
+ PARSER_RET_CHECK(KNOT_YP_EINVAL_ITEM);
+
+ SET_INPUT_STR("group:\n - hex: data");
+ PARSER_RET_CHECK(KNOT_EOK);
+ PARSER_RET_CHECK(KNOT_YP_EINVAL_ITEM);
+
+ SET_INPUT_STR("dname:");
+ PARSER_RET_CHECK(KNOT_EINVAL);
+
+ SET_INPUT_STR("group: data");
+ PARSER_RET_CHECK(KNOT_YP_ENOTSUP_DATA);
+
+ SET_INPUT_STR("group:\n integer:");
+ PARSER_RET_CHECK(KNOT_EOK);
+ PARSER_RET_CHECK(KNOT_EINVAL);
+
+ SET_INPUT_STR("multi-group:\n id:");
+ PARSER_RET_CHECK(KNOT_EOK);
+ PARSER_RET_CHECK(KNOT_YP_ENODATA);
+
+ SET_INPUT_STR("multi-group:\n hex:");
+ PARSER_RET_CHECK(KNOT_EOK);
+ PARSER_RET_CHECK(KNOT_YP_ENOID);
+
+error_parser:
+ yp_schema_check_deinit(ctx);
+ yp_schema_free(schema);
+ yp_deinit(yp);
+}
+
+#define STR_CHECK(depth, key0, key1, id, data) \
+ ret = yp_schema_check_str(ctx, key0, key1, id, data); \
+ is_int(KNOT_EOK, ret, "check str"); \
+ ok(ctx->current == depth, "depth check"); \
+ node = &ctx->nodes[ctx->current]; \
+ parent = node->parent;
+
+#define STR_RET_CHECK(code, key0, key1, id, data) \
+ ret = yp_schema_check_str(ctx, key0, key1, id, data); \
+ ok(ret == code, "return check str");
+
+static void str_test(void)
+{
+ yp_item_t *schema;
+ yp_check_ctx_t *ctx = NULL;
+
+ int ret = yp_schema_copy(&schema, static_schema);
+ is_int(KNOT_EOK, ret, "schema copy");
+ if (ret != KNOT_EOK) {
+ goto error_str;
+ }
+
+ ctx = yp_schema_check_init(&schema);
+ ok(ctx != NULL, "create check ctx");
+ if (ctx == NULL) {
+ goto error_str;
+ }
+
+ yp_node_t *node;
+ yp_node_t *parent;
+ const yp_item_t *id;
+
+ diag("str key0 test");
+ STR_CHECK(0, "option", NULL, NULL, "one");
+ ok(strcmp(node->item->name + 1, "option") == 0, "name check");
+ ok(node->item->type == YP_TOPT, "type check");
+ ok(yp_opt(node->data) == 1, "value check");
+
+ diag("str group test");
+ STR_CHECK(0, "group", NULL, NULL, NULL);
+ ok(strcmp(node->item->name + 1, "group") == 0, "name check");
+ ok(node->item->type == YP_TGRP, "type check");
+ ok(node->data_len == 0, "value length check");
+ STR_CHECK(1, "group", "integer", NULL, "20");
+ ok(strcmp(node->item->name + 1, "integer") == 0, "name check");
+ ok(node->item->type == YP_TINT, "type check");
+ ok(yp_int(node->data) == 20, "value check");
+ STR_CHECK(1, "group", "string", NULL, "short");
+ ok(strcmp(node->item->name + 1, "string") == 0, "name check");
+ ok(node->item->type == YP_TSTR, "type check");
+ ok(strcmp(yp_str(node->data), "short") == 0, "value check");
+ STR_CHECK(1, "group", "string", NULL, "long string");
+ ok(strcmp(node->item->name + 1, "string") == 0, "name check");
+ ok(node->item->type == YP_TSTR, "type check");
+ ok(strcmp(yp_str(node->data), "long string") == 0, "value check");
+
+ diag("str multi-group test");
+ STR_CHECK(0, "multi-group", NULL, NULL, NULL);
+ ok(strcmp(node->item->name + 1, "multi-group") == 0, "name check");
+ ok(node->item->type == YP_TGRP, "type check");
+ ok(node->data_len == 0, "value length check");
+ STR_CHECK(0, "multi-group", NULL, "foo", NULL);
+ ok(node->id_len > 0, "id check");
+ ok(strcmp(node->item->name + 1, "multi-group") == 0, "name check");
+ ok(node->item->type == YP_TGRP, "type check");
+ ok(node->data_len == 0, "value length check");
+ id = node->item->var.g.id;
+ ok(strcmp(id->name + 1, "id") == 0, "name check");
+ ok(id->type == YP_TSTR, "type check");
+ ok(strcmp(yp_str(node->id), "foo") == 0, "value check");
+ STR_CHECK(1, "multi-group", "base64", "foo", "Zm9vYmFy");
+ id = parent->item->var.g.id;
+ ok(strcmp(parent->item->name + 1, "multi-group") == 0, "name check");
+ ok(parent->item->type == YP_TGRP, "type check");
+ ok(parent->data_len == 0, "value length check");
+ ok(strcmp(yp_str(parent->id), "foo") == 0, "value check");
+ ok(strcmp(id->name + 1, "id") == 0, "name check");
+ ok(id->type == YP_TSTR, "type check");
+ ok(strcmp(node->item->name + 1, "base64") == 0, "name check");
+ ok(node->item->type == YP_TB64, "type check");
+ ok(memcmp(yp_bin(node->data), "foobar", yp_bin_len(node->data)) == 0,
+ "value check");
+ ok(node->id_len == 0, "id length check");
+ STR_CHECK(0, "reference", NULL, NULL, "foo");
+ ok(strcmp(node->item->name + 1, "reference") == 0, "name check");
+ ok(node->item->type == YP_TREF, "type check");
+ ok(strcmp(yp_str(node->data), "foo") == 0, "value check");
+
+ diag("str check return");
+ STR_RET_CHECK(KNOT_YP_EINVAL_ITEM, "", "", "", "");
+ STR_RET_CHECK(KNOT_YP_EINVAL_ITEM, NULL, NULL, NULL, NULL);
+ STR_RET_CHECK(KNOT_YP_EINVAL_ITEM, "unknown", NULL, NULL, NULL);
+ STR_RET_CHECK(KNOT_YP_EINVAL_ITEM, NULL, "unknown", NULL, NULL);
+ STR_RET_CHECK(KNOT_EINVAL, "dname", "", "", "");
+ STR_RET_CHECK(KNOT_EOK, "dname", NULL, NULL, NULL);
+ STR_RET_CHECK(KNOT_EOK, "dname", NULL, NULL, ".");
+ STR_RET_CHECK(KNOT_EINVAL, "dname", NULL, NULL, "..");
+ STR_RET_CHECK(KNOT_YP_ENOTSUP_ID, "dname", NULL, "id", NULL);
+ STR_RET_CHECK(KNOT_YP_EINVAL_ITEM, "dname", "unknown", NULL, NULL);
+
+ STR_RET_CHECK(KNOT_EOK, "group", "", "", "");
+ STR_RET_CHECK(KNOT_EOK, "group", NULL, NULL, NULL);
+ STR_RET_CHECK(KNOT_YP_ENOTSUP_DATA, "group", "", "", "data");
+ STR_RET_CHECK(KNOT_YP_EINVAL_ITEM, "group", "unknown", NULL, NULL);
+ STR_RET_CHECK(KNOT_EOK, "group", "string", NULL, NULL);
+ STR_RET_CHECK(KNOT_EOK, "group", "string", NULL, "data");
+ STR_RET_CHECK(KNOT_EOK, "group", "string", NULL, "");
+ STR_RET_CHECK(KNOT_YP_ENOTSUP_ID, "group", "", "id", NULL);
+ STR_RET_CHECK(KNOT_YP_ENOTSUP_ID, "group", "string", "id", NULL);
+
+ STR_RET_CHECK(KNOT_EOK, "multi-group", "", "", "");
+ STR_RET_CHECK(KNOT_EOK, "multi-group", NULL, NULL, NULL);
+ STR_RET_CHECK(KNOT_YP_ENOTSUP_DATA, "multi-group", NULL, NULL, "data");
+ STR_RET_CHECK(KNOT_EOK, "multi-group", NULL, "idval", NULL);
+ STR_RET_CHECK(KNOT_YP_ENOTSUP_DATA, "multi-group", NULL, "idval", "data");
+ STR_RET_CHECK(KNOT_EOK, "multi-group", "hex", "idval", NULL);
+ STR_RET_CHECK(KNOT_EOK, "multi-group", "hex", "idval", "data");
+ STR_RET_CHECK(KNOT_EOK, "multi-group", "hex", NULL, NULL);
+ STR_RET_CHECK(KNOT_EOK, "multi-group", "hex", NULL, "data");
+ STR_RET_CHECK(KNOT_EOK, "multi-group", "id", "", NULL);
+ STR_RET_CHECK(KNOT_EOK, "multi-group", "id", NULL, "idval");
+ STR_RET_CHECK(KNOT_EOK, "multi-group", "id", "idval", NULL);
+ STR_RET_CHECK(KNOT_YP_ENOTSUP_DATA, "multi-group", "id", "idval", "data");
+
+error_str:
+ yp_schema_check_deinit(ctx);
+ yp_schema_free(schema);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ schema_find_test();
+ schema_merge_test();
+ parser_test();
+ str_test();
+
+ return 0;
+}
diff --git a/tests/libknot/test_yptrafo.c b/tests/libknot/test_yptrafo.c
new file mode 100644
index 0000000..cca9635
--- /dev/null
+++ b/tests/libknot/test_yptrafo.c
@@ -0,0 +1,404 @@
+/* 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 <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <tap/basic.h>
+
+#include "libknot/yparser/yptrafo.h"
+#include "libknot/libknot.h"
+
+static void int_test(const char *txt, int64_t num, yp_style_t s,
+ int64_t min, int64_t max)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_TINT, YP_VINT = { min, max, YP_NIL, s } };
+
+ diag("integer \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ok(yp_int(b) == num, "compare");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, s | YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+static void int_bad_test(const char *txt, int code, yp_style_t s,
+ int64_t min, int64_t max)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ yp_item_t i = { NULL, YP_TINT, YP_VINT = { min, max, YP_NIL, s } };
+
+ diag("integer \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ ok(ret == code, "invalid txt to bin");
+}
+
+static void bool_test(const char *txt, bool val)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_TBOOL, YP_VNONE };
+
+ diag("boolean \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ok(yp_bool(b) == val, "compare");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+static void bool_bad_test(const char *txt, int code)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ yp_item_t i = { NULL, YP_TBOOL, YP_VNONE };
+
+ diag("boolean \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ ok(ret == code, "invalid txt to bin");
+}
+
+static void opt_test(const char *txt, unsigned val, const knot_lookup_t *opts)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_TOPT, YP_VOPT = { opts } };
+
+ diag("option \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ok(b_len == 1, "compare length");
+ ok(yp_opt(b) == val, "compare");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+static void opt_bad_test(const char *txt, int code, const knot_lookup_t *opts)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ yp_item_t i = { NULL, YP_TOPT, YP_VOPT = { opts } };
+
+ diag("option \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ ok(ret == code, "invalid txt to bin");
+}
+
+static void str_test(const char *txt, const char *val)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_TSTR, YP_VNONE };
+
+ diag("string \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ok(b_len == strlen(txt) + 1, "compare length");
+ ok(memcmp(yp_str(b), val, b_len) == 0, "compare");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+static void addr_test(const char *txt, bool port)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_TADDR, YP_VNONE };
+
+ diag("address \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ bool no_port;
+ yp_addr(b, &no_port);
+ ok(no_port == port, "compare port presence");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+static void addr_bad_test(const char *txt, int code)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ yp_item_t i = { NULL, YP_TADDR, YP_VNONE };
+
+ diag("address \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ ok(ret == code, "invalid txt to bin");
+}
+
+static void addr_range_test(const char *txt)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_TNET, YP_VNONE };
+
+ diag("address range \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+static void addr_range_bad_test(const char *txt, int code)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ yp_item_t i = { NULL, YP_TNET, YP_VNONE };
+
+ diag("address range \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ ok(ret == code, "invalid txt to bin");
+}
+
+static void dname_test(const char *txt, const char *val)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_TDNAME, YP_VNONE };
+
+ diag("dname \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ok(memcmp(yp_dname(b), val, b_len) == 0, "compare");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+static void hex_test(const char *txt, const char *val, const char *txt_out)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_THEX, YP_VNONE };
+
+ if (txt_out == NULL) {
+ txt_out = txt;
+ }
+
+ diag("hex \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ok(memcmp(yp_bin(b), val, yp_bin_len(b)) == 0, "compare");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt_out) == t_len, "txt length");
+ ok(memcmp(txt_out, t, t_len) == 0, "compare");
+}
+
+static void hex_bad_test(const char *txt, int code)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ yp_item_t i = { NULL, YP_THEX, YP_VNONE };
+
+ diag("hex \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ ok(ret == code, "invalid txt to bin");
+}
+
+static void base64_test(const char *txt, const char *val)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t i = { NULL, YP_TB64, YP_VNONE };
+
+ diag("base64 \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ok(memcmp(yp_bin(b), val, yp_bin_len(b)) == 0, "compare");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+static void ref_test(const char *txt, bool val)
+{
+ int ret;
+ uint8_t b[64];
+ size_t b_len = sizeof(b);
+ char t[64];
+ size_t t_len = sizeof(t);
+ yp_item_t id = { NULL, YP_TBOOL, YP_VNONE };
+ yp_item_t ref = { NULL, YP_TGRP, YP_VNONE };
+ yp_item_t i = { NULL, YP_TREF, YP_VNONE };
+ ref.var.g.id = &id;
+ i.var.r.ref = &ref;
+
+ diag("reference to boolean \"%s\":", txt);
+ ret = yp_item_to_bin(&i, txt, strlen(txt), b, &b_len);
+ is_int(KNOT_EOK, ret, "txt to bin");
+ ok(yp_bool(b) == val, "compare");
+ ret = yp_item_to_txt(&i, b, b_len, t, &t_len, YP_SNOQUOTE);
+ is_int(KNOT_EOK, ret, "bin to txt");
+ ok(strlen(t) == t_len, "txt ret length");
+ ok(strlen(txt) == t_len, "txt length");
+ ok(memcmp(txt, t, t_len) == 0, "compare");
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ /* Integer tests. */
+ int64_t min = -20000000000, max = 20000000000;
+ int_test("5", 5, YP_SNONE, min, max);
+ int_test("0", 0, YP_SNONE, min, max);
+ int_test("-5", -5, YP_SNONE, min, max);
+ int_test("20000000000", max, YP_SNONE, min, max);
+ int_test("-20000000000", min, YP_SNONE, min, max);
+ int_test("11B", 11LL * 1, YP_SSIZE, min, max);
+ int_test("11K", 11LL * 1024, YP_SSIZE, min, max);
+ int_test("11M", 11LL * 1024 * 1024, YP_SSIZE, min, max);
+ int_test("11G", 11LL * 1024 * 1024 * 1024, YP_SSIZE, min, max);
+ int_test("11s", 11LL * 1, YP_STIME, min, max);
+ int_test("11m", 11LL * 60, YP_STIME, min, max);
+ int_test("11h", 11LL * 3600, YP_STIME, min, max);
+ int_test("11d", 11LL * 24 * 3600, YP_STIME, min, max);
+ int_test("1025B", 1025LL, YP_SSIZE, min, max);
+ int_test("61s", 61LL, YP_STIME, min, max);
+ int_bad_test("20000000001", KNOT_ERANGE, YP_SNONE, min, max);
+ int_bad_test("-20000000001", KNOT_ERANGE, YP_SNONE, min, max);
+ int_bad_test("1x", KNOT_EINVAL, YP_SNONE, min, max);
+ int_bad_test("1sx", KNOT_EINVAL, YP_STIME, min, max);
+
+ /* Boolean tests. */
+ bool_test("on", true);
+ bool_test("off", false);
+ bool_bad_test("onx", KNOT_EINVAL);
+ bool_bad_test("enable", KNOT_EINVAL);
+
+ /* Option tests. */
+ static const knot_lookup_t opts[] = {
+ { 1, "one" },
+ { 10, "ten" },
+ { 255, "max" },
+ { 0, NULL }
+ };
+ opt_test("one", 1, opts);
+ opt_test("ten", 10, opts);
+ opt_test("max", 255, opts);
+ opt_bad_test("onex", KNOT_EINVAL, opts);
+ opt_bad_test("word", KNOT_EINVAL, opts);
+
+ /* String tests. */
+ str_test("Test string!", "Test string!");
+
+ /* Address tests. */
+ addr_test("192.168.123.1", true);
+ addr_test("192.168.123.1@12345", false);
+ addr_test("2001:db8::1", true);
+ addr_test("::1@12345", false);
+ addr_test("/tmp/test.sock", true);
+ addr_test("eth1@53", true);
+ addr_bad_test("192.168.123.x", KNOT_EINVAL);
+ addr_bad_test("192.168.123.1@", KNOT_EINVAL);
+ addr_bad_test("192.168.123.1@1x", KNOT_EINVAL);
+ addr_bad_test("192.168.123.1@65536", KNOT_ERANGE);
+
+ /* Address range tests. */
+ addr_range_test("1.1.1.1");
+ addr_range_test("1.1.1.1/0");
+ addr_range_test("1.1.1.1/32");
+ addr_range_test("1.1.1.1-1.2.3.4");
+ addr_range_test("::1");
+ addr_range_test("::1/0");
+ addr_range_test("::1/32");
+ addr_range_test("1::-5::");
+ addr_range_bad_test("unix", KNOT_EINVAL);
+ addr_range_bad_test("1.1.1", KNOT_EINVAL);
+ addr_range_bad_test("1.1.1.1/", KNOT_EINVAL);
+ addr_range_bad_test("1.1.1.1/33", KNOT_ERANGE);
+ addr_range_bad_test("1.1.1.1-", KNOT_EINVAL);
+ addr_range_bad_test("1.1.1.1-::1", KNOT_EINVAL);
+
+ /* Dname tests. */
+ dname_test("example.com.", "\x07""example""\x03""com""\x00");
+
+ /* Hex tests. */
+ hex_test("", "", NULL);
+ hex_test("0x", "", "");
+ hex_test("Hello World!", "Hello World!", NULL);
+ hex_test("0x0155FF", "\x01\x55\xFF", NULL);
+ hex_bad_test("0xA", KNOT_EINVAL);
+
+ /* Base64 tests. */
+ base64_test("Zm9vYmFy", "foobar");
+
+ /* Ref tests. */
+ ref_test("on", true);
+
+ return 0;
+}