knot-dns/tests/knot/test_confdb.c
2025-03-24 09:53:50 +01:00

478 lines
14 KiB
C

/* Copyright (C) CZ.NIC, z.s.p.o. and contributors
* SPDX-License-Identifier: GPL-2.0-or-later
* For more information, see <https://www.knot-dns.cz/>
*/
#include <tap/basic.h>
#include "test_conf.h"
#include "knot/conf/confdb.c"
static void check_db_content(conf_t *conf, knot_db_txn_t *txn, int count)
{
ok(db_check_version(conf, txn) == KNOT_EOK, "Version check");
if (count >= 0) {
ok(conf->api->count(txn) == 1 + count, "Check DB entries count");
}
}
static void check_code(
conf_t *conf,
knot_db_txn_t *txn,
uint8_t section_code,
const yp_name_t *name,
db_action_t action,
int ret,
uint8_t ref_code)
{
uint8_t code = 0;
ok(db_code(conf, txn, section_code, name, action, &code) == ret,
"Compare DB code return");
if (ret != KNOT_EOK) {
return;
}
uint8_t k[64] = { section_code, 0 };
memcpy(k + 2, name + 1, name[0]);
knot_db_val_t key = { .data = k, .len = 2 + name[0] };
knot_db_val_t val;
ret = conf->api->find(txn, &key, &val, 0);
switch (action) {
case DB_GET:
case DB_SET:
ok(code == ref_code, "Compare DB code");
is_int(KNOT_EOK, ret, "Find DB code");
ok(val.len == 1, "Compare DB code length");
ok(((uint8_t *)val.data)[0] == code, "Compare DB code value");
break;
case DB_DEL:
is_int(KNOT_ENOENT, ret, "Find item code");
break;
}
}
static void test_db_code(conf_t *conf, knot_db_txn_t *txn)
{
// Add codes.
check_code(conf, txn, 0, C_SERVER, DB_SET, KNOT_EOK, KEY1_FIRST);
check_db_content(conf, txn, 1);
check_code(conf, txn, 0, C_LOG, DB_SET, KNOT_EOK, KEY1_FIRST + 1);
check_db_content(conf, txn, 2);
check_code(conf, txn, 2, C_IDENT, DB_SET, KNOT_EOK, KEY1_FIRST);
check_db_content(conf, txn, 3);
check_code(conf, txn, 0, C_ZONE, DB_SET, KNOT_EOK, KEY1_FIRST + 2);
check_db_content(conf, txn, 4);
check_code(conf, txn, 2, C_VERSION, DB_SET, KNOT_EOK, KEY1_FIRST + 1);
check_db_content(conf, txn, 5);
// Add existing code (no change).
check_code(conf, txn, 0, C_SERVER, DB_SET, KNOT_EOK, KEY1_FIRST);
check_db_content(conf, txn, 5);
// Get codes.
check_code(conf, txn, 0, C_SERVER, DB_GET, KNOT_EOK, KEY1_FIRST);
check_db_content(conf, txn, 5);
check_code(conf, txn, 0, C_RMT, DB_GET, KNOT_ENOENT, 0);
check_db_content(conf, txn, 5);
// Delete not existing code.
check_code(conf, txn, 0, C_COMMENT, DB_DEL, KNOT_ENOENT, 0);
check_db_content(conf, txn, 5);
// Delete codes.
check_code(conf, txn, 0, C_SERVER, DB_DEL, KNOT_EOK, 0);
check_db_content(conf, txn, 4);
check_code(conf, txn, 2, C_IDENT, DB_DEL, KNOT_EOK, 0);
check_db_content(conf, txn, 3);
// Reuse deleted codes.
check_code(conf, txn, 0, C_ACL, DB_SET, KNOT_EOK, KEY1_FIRST);
check_db_content(conf, txn, 4);
check_code(conf, txn, 2, C_NSID, DB_SET, KNOT_EOK, KEY1_FIRST);
check_db_content(conf, txn, 5);
}
static void check_set(
conf_t *conf,
knot_db_txn_t *txn,
const yp_name_t *key0,
const yp_name_t *key1,
const uint8_t *id,
size_t id_len,
int ret,
const uint8_t *data,
size_t data_len,
const uint8_t *exp_data,
size_t exp_data_len)
{
ok(conf_db_set(conf, txn, key0, key1, id, id_len, data, data_len) == ret,
"Check set return");
if (ret != KNOT_EOK || (key1 == NULL && id == NULL)) {
return;
}
uint8_t section_code, item_code;
section_code = 0, item_code = 0; // prevents Wuninitialized
ok(db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &section_code) == KNOT_EOK,
"Get DB section code");
if (key1 != NULL) {
ok(db_code(conf, txn, section_code, key1, DB_GET, &item_code) == KNOT_EOK,
"Get DB item code");
} else {
item_code = KEY1_ID;
}
uint8_t k[64] = { section_code, item_code };
if (id != NULL) {
memcpy(k + 2, id, id_len);
}
knot_db_val_t key = { .data = k, .len = 2 + id_len };
knot_db_val_t val;
ok(conf->api->find(txn, &key, &val, 0) == KNOT_EOK, "Get inserted data");
ok(val.len == exp_data_len, "Compare data length");
ok(memcmp(val.data, exp_data, exp_data_len) == 0, "Compare data");
check_db_content(conf, txn, -1);
}
static void test_conf_db_set(conf_t *conf, knot_db_txn_t *txn)
{
// Set section without item - noop.
check_set(conf, txn, C_INCL, NULL, NULL, 0, KNOT_EOK, NULL, 0, NULL, 0);
// Set singlevalued item.
check_set(conf, txn, C_SERVER, C_RUNDIR, NULL, 0, KNOT_EOK,
(uint8_t *)"\0", 1, (uint8_t *)"\0", 1);
check_set(conf, txn, C_SERVER, C_RUNDIR, NULL, 0, KNOT_EOK,
(uint8_t *)"a b\0", 4, (uint8_t *)"a b\0", 4);
// Set multivalued item.
check_set(conf, txn, C_SERVER, C_LISTEN, NULL, 0, KNOT_EOK,
(uint8_t *)"\0", 1, (uint8_t *)"\x00\x01""\0", 3);
check_set(conf, txn, C_SERVER, C_LISTEN, NULL, 0, KNOT_EOK,
(uint8_t *)"a\0", 2, (uint8_t *)"\x00\x01""\0""\x00\x02""a\0", 7);
check_set(conf, txn, C_SERVER, C_LISTEN, NULL, 0, KNOT_EOK,
(uint8_t *)"b\0", 2, (uint8_t *)"\x00\x01""\0""\x00\x02""a\0""\x00\x02""b\0", 11);
// Set group id.
check_set(conf, txn, C_ZONE, NULL, (uint8_t *)"id", 2, KNOT_EOK,
NULL, 0, (uint8_t *)"", 0);
// Set singlevalued item with id.
check_set(conf, txn, C_ZONE, C_FILE, (uint8_t *)"id", 2, KNOT_EOK,
(uint8_t *)"\0", 1, (uint8_t *)"\0", 1);
check_set(conf, txn, C_ZONE, C_FILE, (uint8_t *)"id", 2, KNOT_EOK,
(uint8_t *)"a b\0", 4, (uint8_t *)"a b\0", 4);
// Set multivalued item with id.
check_set(conf, txn, C_ZONE, C_MASTER, (uint8_t *)"id", 2, KNOT_EOK,
(uint8_t *)"\0", 1, (uint8_t *)"\x00\x01""\0", 3);
check_set(conf, txn, C_ZONE, C_MASTER, (uint8_t *)"id", 2, KNOT_EOK,
(uint8_t *)"a\0", 2, (uint8_t *)"\x00\x01""\0""\x00\x02""a\0", 7);
check_set(conf, txn, C_ZONE, C_MASTER, (uint8_t *)"id", 2, KNOT_EOK,
(uint8_t *)"b\0", 2, (uint8_t *)"\x00\x01""\0""\x00\x02""a\0""\x00\x02""b\0", 11);
// ERR set invalid section.
check_set(conf, txn, C_MASTER, NULL, NULL, 0, KNOT_YP_EINVAL_ITEM,
NULL, 0, NULL, 0);
// ERR set invalid item.
check_set(conf, txn, C_SERVER, C_DOMAIN, NULL, 0, KNOT_YP_EINVAL_ITEM,
NULL, 0, NULL, 0);
// ERR redefine section id.
check_set(conf, txn, C_ZONE, NULL, (uint8_t *)"id", 2, KNOT_CONF_EREDEFINE,
NULL, 0, NULL, 0);
// ERR set singlevalued item with non-existing id.
check_set(conf, txn, C_ZONE, C_FILE, (uint8_t *)"idx", 3, KNOT_YP_EINVAL_ID,
NULL, 0, NULL, 0);
}
static void check_get(
conf_t *conf,
knot_db_txn_t *txn,
const yp_name_t *key0,
const yp_name_t *key1,
const uint8_t *id,
size_t id_len,
int ret,
const uint8_t *exp_data,
size_t exp_data_len)
{
conf_val_t val;
ok(conf_db_get(conf, txn, key0, key1, id, id_len, &val) == ret,
"Check get return");
if (ret != KNOT_EOK) {
return;
}
ok(val.blob_len == exp_data_len, "Compare data length");
ok(val.blob != NULL && memcmp(val.blob, exp_data, exp_data_len) == 0,
"Compare data");
check_db_content(conf, txn, -1);
}
static void test_conf_db_get(conf_t *conf, knot_db_txn_t *txn)
{
// Get singlevalued item.
check_get(conf, txn, C_SERVER, C_RUNDIR, NULL, 0, KNOT_EOK,
(uint8_t *)"a b\0", 4);
// Get multivalued item.
check_get(conf, txn, C_SERVER, C_LISTEN, NULL, 0, KNOT_EOK,
(uint8_t *)"\x00\x01""\0""\x00\x02""a\0""\x00\x02""b\0", 11);
// Get group id.
check_get(conf, txn, C_ZONE, NULL, (uint8_t *)"id", 2, KNOT_EOK,
(uint8_t *)"", 0);
// Get singlevalued item with id.
check_get(conf, txn, C_ZONE, C_FILE, (uint8_t *)"id", 2, KNOT_EOK,
(uint8_t *)"a b\0", 4);
// Get multivalued item with id.
check_get(conf, txn, C_ZONE, C_MASTER, (uint8_t *)"id", 2, KNOT_EOK,
(uint8_t *)"\x00\x01""\0""\x00\x02""a\0""\x00\x02""b\0", 11);
// ERR get section without item.
check_get(conf, txn, C_INCL, NULL, NULL, 0, KNOT_EINVAL, NULL, 0);
// ERR get invalid section.
check_get(conf, txn, C_MASTER, NULL, NULL, 0, KNOT_YP_EINVAL_ITEM,
NULL, 0);
// ERR get invalid item.
check_get(conf, txn, C_SERVER, C_DOMAIN, NULL, 0, KNOT_YP_EINVAL_ITEM,
NULL, 0);
// ERR get singlevalued item with non-existing id.
check_get(conf, txn, C_ZONE, C_FILE, (uint8_t *)"idx", 3, KNOT_YP_EINVAL_ID,
NULL, 0);
}
static void check_unset(
conf_t *conf,
knot_db_txn_t *txn,
const yp_name_t *key0,
const yp_name_t *key1,
const uint8_t *id,
size_t id_len,
int ret,
const uint8_t *data,
size_t data_len,
const uint8_t *exp_data,
size_t exp_data_len)
{
ok(conf_db_unset(conf, txn, key0, key1, id, id_len, data, data_len, false) == ret,
"Check unset return");
if (ret != KNOT_EOK) {
return;
}
uint8_t section_code, item_code;
ok(db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &section_code) == KNOT_EOK,
"Get DB section code");
if (key1 != NULL) {
ok(db_code(conf, txn, section_code, key1, DB_GET, &item_code) == KNOT_EOK,
"Get DB item code");
} else {
item_code = KEY1_ID;
}
uint8_t k[64] = { section_code, item_code };
if (id != NULL) {
memcpy(k + 2, id, id_len);
}
knot_db_val_t key = { .data = k, .len = 2 + id_len };
knot_db_val_t val;
ret = conf->api->find(txn, &key, &val, 0);
if (exp_data != NULL) {
is_int(KNOT_EOK, ret, "Get deleted data");
ok(val.len == exp_data_len, "Compare data length");
ok(memcmp(val.data, exp_data, exp_data_len) == 0, "Compare data");
} else {
is_int(KNOT_ENOENT, ret, "Get deleted data");
}
check_db_content(conf, txn, -1);
}
static void check_unset_key(
conf_t *conf,
knot_db_txn_t *txn,
const yp_name_t *key0,
const yp_name_t *key1,
const uint8_t *id,
size_t id_len,
int ret)
{
ok(conf_db_unset(conf, txn, key0, key1, id, id_len, NULL, 0, true) == ret,
"Check unset return");
if (ret != KNOT_EOK) {
return;
}
uint8_t section_code, item_code;
ret = db_code(conf, txn, KEY0_ROOT, key0, DB_GET, &section_code);
if (key1 == NULL && id_len == 0) {
is_int(KNOT_ENOENT, ret, "Get DB section code");
} else {
is_int(KNOT_EOK, ret, "Get DB section code");
ret = db_code(conf, txn, section_code, key1, DB_GET, &item_code);
is_int(KNOT_ENOENT, ret, "Get DB item code");
}
check_db_content(conf, txn, -1);
}
static void test_conf_db_unset(conf_t *conf, knot_db_txn_t *txn)
{
// ERR unset section without item.
check_unset(conf, txn, C_INCL, NULL, NULL, 0, KNOT_ENOENT,
NULL, 0, NULL, 0);
// ERR unset invalid section.
check_unset(conf, txn, C_MASTER, NULL, NULL, 0, KNOT_YP_EINVAL_ITEM,
NULL, 0, NULL, 0);
// ERR unset invalid item.
check_unset(conf, txn, C_SERVER, C_DOMAIN, NULL, 0, KNOT_YP_EINVAL_ITEM,
NULL, 0, NULL, 0);
// ERR unset singlevalued item with non-existing id.
check_unset(conf, txn, C_ZONE, C_FILE, (uint8_t *)"idx", 3, KNOT_YP_EINVAL_ID,
NULL, 0, NULL, 0);
// ERR unset singlevalued item invalid value.
check_unset(conf, txn, C_SERVER, C_RUNDIR, NULL, 0, KNOT_ENOENT,
(uint8_t *)"x\0", 2, NULL, 0);
// Unset singlevalued item data.
check_unset(conf, txn, C_SERVER, C_RUNDIR, NULL, 0, KNOT_EOK,
(uint8_t *)"a b\0", 4, NULL, 0);
// Unset item.
check_unset_key(conf, txn, C_SERVER, C_RUNDIR, NULL, 0, KNOT_EOK);
// Unset multivalued item.
check_unset(conf, txn, C_SERVER, C_LISTEN, NULL, 0, KNOT_EOK,
(uint8_t *)"a", 2, (uint8_t *)"\x00\x01""\0""\x00\x02""b\0", 7);
check_unset(conf, txn, C_SERVER, C_LISTEN, NULL, 0, KNOT_EOK,
(uint8_t *)"", 1, (uint8_t *)"\x00\x02""b\0", 4);
check_unset(conf, txn, C_SERVER, C_LISTEN, NULL, 0, KNOT_EOK,
(uint8_t *)"b", 2, NULL, 0);
// Unset item.
check_unset_key(conf, txn, C_SERVER, C_LISTEN, NULL, 0, KNOT_EOK);
// Unset section.
check_unset_key(conf, txn, C_SERVER, NULL, NULL, 0, KNOT_EOK);
// Unset singlevalued item with id - all data at one step.
check_unset(conf, txn, C_ZONE, C_FILE, (uint8_t *)"id", 2, KNOT_EOK,
NULL, 0, NULL, 0);
// Unset multivalued item with id - all data at one step (non-null data!).
check_unset(conf, txn, C_ZONE, C_MASTER, (uint8_t *)"id", 2, KNOT_EOK,
(void *)8, 0, NULL, 0);
// Unset group id.
check_unset(conf, txn, C_ZONE, NULL, (uint8_t *)"id", 2, KNOT_EOK,
NULL, 0, NULL, 0);
}
static void test_conf_db_iter(conf_t *conf, knot_db_txn_t *txn)
{
const size_t total = 4;
char names[][10] = { "alfa", "beta", "delta", "epsilon" };
// Prepare identifiers to iterate through.
for (size_t i = 0; i < total; i++) {
check_set(conf, txn, C_RMT, NULL, (uint8_t *)names[i],
strlen(names[i]), KNOT_EOK, NULL, 0, (uint8_t *)"", 0);
}
// Create section iterator.
conf_iter_t iter;
int ret = conf_db_iter_begin(conf, txn, C_RMT, &iter);
is_int(KNOT_EOK, ret, "Create iterator");
// Iterate through the section.
size_t count = 0;
while (ret == KNOT_EOK) {
const uint8_t *id;
size_t id_len;
id = NULL, id_len = 0; // prevents Wuninitialized
ret = conf_db_iter_id(conf, &iter, &id, &id_len);
is_int(KNOT_EOK, ret, "Get iteration id");
ok(id_len == strlen(names[count]), "Compare iteration id length");
ok(memcmp(id, names[count], id_len) == 0, "Compare iteration id");
ok(conf_db_iter_del(conf, &iter) == KNOT_EOK, "Delete iteration key");
count++;
ret = conf_db_iter_next(conf, &iter);
}
is_int(KNOT_EOF, ret, "Finished iteration");
ok(count == total, "Check iteration count");
// Check empty section.
ret = conf_db_iter_begin(conf, txn, C_RMT, &iter);
is_int(KNOT_ENOENT, ret, "Create iterator");
// ERR non-iterable section.
ok(conf_db_iter_begin(conf, txn, C_SERVER, &iter) == KNOT_ENOTSUP, "Create iterator");
// ERR empty section.
ok(conf_db_iter_begin(conf, txn, C_ZONE, &iter) == KNOT_ENOENT, "Create iterator");
// ERR section with no code.
ok(conf_db_iter_begin(conf, txn, C_LOG, &iter) == KNOT_ENOENT, "Create iterator");
}
int main(int argc, char *argv[])
{
plan_lazy();
ok(test_conf("", NULL) == KNOT_EOK, "Prepare configuration");
check_db_content(conf(), &conf()->read_txn, 0);
knot_db_txn_t txn;
ok(conf()->api->txn_begin(conf()->db, &txn, 0) == KNOT_EOK, "Begin transaction");
diag("db_code");
test_db_code(conf(), &txn);
conf()->api->txn_abort(&txn);
ok(conf()->api->txn_begin(conf()->db, &txn, 0) == KNOT_EOK, "Begin transaction");
diag("conf_db_set");
test_conf_db_set(conf(), &txn);
diag("conf_db_get");
test_conf_db_get(conf(), &txn);
diag("conf_db_unset");
test_conf_db_unset(conf(), &txn);
conf()->api->txn_abort(&txn);
ok(conf()->api->txn_begin(conf()->db, &txn, 0) == KNOT_EOK, "Begin transaction");
diag("conf_db_iter");
test_conf_db_iter(conf(), &txn);
conf()->api->txn_abort(&txn);
conf_free(conf());
return 0;
}