diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:24:08 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 15:24:08 +0000 |
commit | f449f278dd3c70e479a035f50a9bb817a9b433ba (patch) | |
tree | 8ca2bfb785dda9bb4d573acdf9b42aea9cd51383 /tests/libknot | |
parent | Initial commit. (diff) | |
download | knot-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.c | 221 | ||||
-rw-r--r-- | tests/libknot/test_cookies.c | 174 | ||||
-rw-r--r-- | tests/libknot/test_db.c | 287 | ||||
-rw-r--r-- | tests/libknot/test_descriptor.c | 361 | ||||
-rw-r--r-- | tests/libknot/test_dname.c | 618 | ||||
-rw-r--r-- | tests/libknot/test_dynarray.c | 137 | ||||
-rw-r--r-- | tests/libknot/test_edns.c | 502 | ||||
-rw-r--r-- | tests/libknot/test_edns_ecs.c | 271 | ||||
-rw-r--r-- | tests/libknot/test_endian.c | 70 | ||||
-rw-r--r-- | tests/libknot/test_lookup.c | 66 | ||||
-rw-r--r-- | tests/libknot/test_pkt.c | 199 | ||||
-rw-r--r-- | tests/libknot/test_probe.c | 96 | ||||
-rw-r--r-- | tests/libknot/test_rdata.c | 60 | ||||
-rw-r--r-- | tests/libknot/test_rdataset.c | 227 | ||||
-rw-r--r-- | tests/libknot/test_rrset-wire.c | 264 | ||||
-rw-r--r-- | tests/libknot/test_rrset.c | 121 | ||||
-rw-r--r-- | tests/libknot/test_tsig.c | 204 | ||||
-rw-r--r-- | tests/libknot/test_wire.c | 46 | ||||
-rw-r--r-- | tests/libknot/test_xdp_tcp.c | 633 | ||||
-rw-r--r-- | tests/libknot/test_yparser.c | 346 | ||||
-rw-r--r-- | tests/libknot/test_ypschema.c | 417 | ||||
-rw-r--r-- | tests/libknot/test_yptrafo.c | 404 |
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, ¶ms); + 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, ¶ms); + 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, ¶ms); + 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, ¶ms); + 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(©, &rdataset, NULL); + const bool copy_ok = ret == KNOT_EOK && copy.count == rdataset.count && + rdataset_size(©) == 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: size precomputed."); + + // Test eq + ok(knot_rdataset_eq(&rdataset, ©), "rdataset: equal"); + + // Test clear + knot_rdataset_clear(©, NULL); + ok(copy.count == 0 && copy.rdata == NULL, "rdataset: clear."); + + // Test not equal (different count) + ok(!knot_rdataset_eq(&rdataset, ©), "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(©, &rdataset, NULL); + assert(ret == KNOT_EOK); + ret = knot_rdataset_intersect2(©, &rdataset_lo, NULL); + intersect_ok = ret == KNOT_EOK && knot_rdataset_eq(©, &rdataset_lo); + ok(intersect_ok, "rdataset: intersect2 normal."); + knot_rdataset_clear(©, NULL); + + // Test subtract + ok(knot_rdataset_subtract(NULL, NULL, NULL) == KNOT_EINVAL, + "rdataset: subtract NULL."); + ret = knot_rdataset_copy(©, &rdataset, NULL); + assert(ret == KNOT_EOK); + ok(knot_rdataset_subtract(©, ©, NULL) == KNOT_EOK && + copy.count == 0, "rdataset: subtract self."); + + ret = knot_rdataset_copy(©, &rdataset, NULL); + assert(ret == KNOT_EOK); + ret = knot_rdataset_subtract(©, &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(©, 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(©, NULL); + ok(r != KNOT_EOK, "knot_tsig_key_copy: no source"); + r = knot_tsig_key_copy(©, &key); + ok(r == KNOT_EOK && key_is_eq(©, &key) && + copy.secret.data != key.secret.data && copy.name != key.name, + "knot_tsig_key_copy: simple copy"); + + knot_tsig_key_deinit(©); + } + + 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; +} |