2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 18:19:42 +00:00
bind/tests/dns/qp_test.c
Evan Hunt 29cf7dceb7 modify dns_qp_findname_ancestor() to return found name
add a 'foundname' parameter to dns_qp_findname_ancestor(),
and use it to set the found name in dns_nametree.

this required adding a dns_qpkey_toname() function; that was
done by moving qp_test_keytoname() from the test library to qp.c.
added some more test cases and fixed bugs with the handling of
relative and empty names.
2023-09-28 07:01:13 +00:00

443 lines
11 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <inttypes.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/random.h>
#include <isc/refcount.h>
#include <isc/result.h>
#include <isc/string.h>
#include <isc/urcu.h>
#include <isc/util.h>
#include <dns/name.h>
#include <dns/qp.h>
#include "qp_p.h"
#include <tests/dns.h>
#include <tests/qp.h>
ISC_RUN_TEST_IMPL(qpkey_name) {
struct {
const char *namestr;
uint8_t key[512];
size_t len;
} testcases[] = {
{
.namestr = "",
.key = { 0x02 },
.len = 0,
},
{
.namestr = ".",
.key = { 0x02, 0x02 },
.len = 1,
},
{
.namestr = "\\000",
.key = { 0x03, 0x03, 0x02, 0x02 },
.len = 3,
},
{
.namestr = "com",
.key = { 0x16, 0x22, 0x20, 0x02 },
.len = 4,
},
{
.namestr = "com.",
.key = { 0x02, 0x16, 0x22, 0x20, 0x02 },
.len = 5,
},
{
.namestr = "example.com.",
.key = { 0x02, 0x16, 0x22, 0x20, 0x02, 0x18, 0x2b, 0x14,
0x20, 0x23, 0x1f, 0x18, 0x02, 0x02 },
.len = 13,
},
{
.namestr = "example.com",
.key = { 0x16, 0x22, 0x20, 0x02, 0x18, 0x2b, 0x14, 0x20,
0x23, 0x1f, 0x18, 0x02, 0x02 },
.len = 12,
},
{
.namestr = "EXAMPLE.COM",
.key = { 0x16, 0x22, 0x20, 0x02, 0x18, 0x2b, 0x14, 0x20,
0x23, 0x1f, 0x18, 0x02, 0x02 },
.len = 12,
},
};
for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
size_t len;
dns_qpkey_t key;
dns_fixedname_t fn1, fn2;
dns_name_t *in = NULL, *out = NULL;
in = dns_fixedname_initname(&fn1);
if (testcases[i].len != 0) {
dns_test_namefromstring(testcases[i].namestr, &fn1);
}
len = dns_qpkey_fromname(key, in);
assert_int_equal(testcases[i].len, len);
assert_memory_equal(testcases[i].key, key, len);
/* also check key correctness for empty name */
if (len == 0) {
assert_int_equal(testcases[i].key[0], ((char *)key)[0]);
}
out = dns_fixedname_initname(&fn2);
dns_qpkey_toname(key, len, out);
assert_true(dns_name_equal(in, out));
}
}
ISC_RUN_TEST_IMPL(qpkey_sort) {
struct {
const char *namestr;
dns_name_t *name;
dns_fixedname_t fixed;
size_t len;
dns_qpkey_t key;
} testcases[] = {
{ .namestr = "." },
{ .namestr = "\\000." },
{ .namestr = "example.com." },
{ .namestr = "EXAMPLE.COM." },
{ .namestr = "www.example.com." },
{ .namestr = "exam.com." },
{ .namestr = "exams.com." },
{ .namestr = "exam\\000.com." },
};
for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
dns_test_namefromstring(testcases[i].namestr,
&testcases[i].fixed);
testcases[i].name = dns_fixedname_name(&testcases[i].fixed);
testcases[i].len = dns_qpkey_fromname(testcases[i].key,
testcases[i].name);
}
for (size_t i = 0; i < ARRAY_SIZE(testcases); i++) {
for (size_t j = 0; j < ARRAY_SIZE(testcases); j++) {
int namecmp = dns_name_compare(testcases[i].name,
testcases[j].name);
size_t len = ISC_MIN(testcases[i].len,
testcases[j].len);
/* include extra terminating NOBYTE */
int keycmp = memcmp(testcases[i].key, testcases[j].key,
len + 1);
assert_true((namecmp < 0) == (keycmp < 0));
assert_true((namecmp == 0) == (keycmp == 0));
assert_true((namecmp > 0) == (keycmp > 0));
}
}
}
#define ITER_ITEMS 100
static void
check_leaf(void *uctx, void *pval, uint32_t ival) {
uint32_t *items = uctx;
assert_in_range(ival, 1, ITER_ITEMS - 1);
assert_ptr_equal(items + ival, pval);
}
static size_t
qpiter_makekey(dns_qpkey_t key, void *uctx, void *pval, uint32_t ival) {
check_leaf(uctx, pval, ival);
char str[8];
snprintf(str, sizeof(str), "%03u", ival);
size_t i = 0;
while (str[i] != '\0') {
key[i] = str[i] - '0' + SHIFT_BITMAP;
i++;
}
key[i++] = SHIFT_NOBYTE;
return (i);
}
static void
getname(void *uctx, char *buf, size_t size) {
strlcpy(buf, "test", size);
UNUSED(uctx);
UNUSED(size);
}
const dns_qpmethods_t qpiter_methods = {
check_leaf,
check_leaf,
qpiter_makekey,
getname,
};
ISC_RUN_TEST_IMPL(qpiter) {
dns_qp_t *qp = NULL;
uint32_t item[ITER_ITEMS] = { 0 };
dns_qp_create(mctx, &qpiter_methods, item, &qp);
for (size_t tests = 0; tests < 1234; tests++) {
uint32_t ival = isc_random_uniform(ITER_ITEMS - 1) + 1;
void *pval = &item[ival];
item[ival] = ival;
/* randomly insert or remove */
dns_qpkey_t key;
size_t len = qpiter_makekey(key, item, pval, ival);
if (dns_qp_insert(qp, pval, ival) == ISC_R_EXISTS) {
void *pvald = NULL;
uint32_t ivald = 0;
dns_qp_deletekey(qp, key, len, &pvald, &ivald);
assert_ptr_equal(pval, pvald);
assert_int_equal(ival, ivald);
item[ival] = 0;
}
/* check that we see only valid items in the correct order */
uint32_t prev = 0;
dns_qpiter_t qpi;
dns_qpiter_init(qp, &qpi);
while (dns_qpiter_next(&qpi, &pval, &ival) == ISC_R_SUCCESS) {
assert_in_range(ival, prev + 1, ITER_ITEMS - 1);
assert_int_equal(ival, item[ival]);
assert_ptr_equal(pval, &item[ival]);
item[ival] = ~ival;
prev = ival;
}
/* ensure we saw every item */
for (ival = 0; ival < ITER_ITEMS; ival++) {
if (item[ival] != 0) {
assert_int_equal(item[ival], ~ival);
item[ival] = ival;
}
}
}
dns_qp_destroy(&qp);
}
static void
no_op(void *uctx, void *pval, uint32_t ival) {
UNUSED(uctx);
UNUSED(pval);
UNUSED(ival);
}
static size_t
qpkey_fromstring(dns_qpkey_t key, void *uctx, void *pval, uint32_t ival) {
dns_fixedname_t fixed;
UNUSED(uctx);
UNUSED(ival);
if (*(char *)pval == '\0') {
return (0);
}
dns_test_namefromstring(pval, &fixed);
return (dns_qpkey_fromname(key, dns_fixedname_name(&fixed)));
}
const dns_qpmethods_t string_methods = {
no_op,
no_op,
qpkey_fromstring,
getname,
};
struct check_partialmatch {
const char *query;
dns_qpfind_t options;
isc_result_t result;
const char *found;
};
static void
check_partialmatch(dns_qp_t *qp, struct check_partialmatch check[]) {
for (int i = 0; check[i].query != NULL; i++) {
isc_result_t result;
dns_fixedname_t fn1, fn2;
dns_name_t *name = dns_fixedname_initname(&fn1);
dns_name_t *foundname = dns_fixedname_initname(&fn2);
void *pval = NULL;
dns_test_namefromstring(check[i].query, &fn1);
result = dns_qp_findname_ancestor(qp, name, check[i].options,
foundname, &pval, NULL);
#if 0
fprintf(stderr, "%s (flags %u) %s (expected %s) "
"value \"%s\" (expected \"%s\")\n",
check[i].query, check[i].options,
isc_result_totext(result),
isc_result_totext(check[i].result), (char *)pval,
check[i].found);
#endif
assert_int_equal(result, check[i].result);
if (result == ISC_R_SUCCESS) {
assert_true(dns_name_equal(name, foundname));
} else if (result == DNS_R_PARTIALMATCH) {
/*
* there are cases where we may have passed a
* query name that was relative to the zone apex,
* and gotten back an absolute name from the
* partial match. it's also possible for an
* absolute query to get a partial match on a
* node that had an empty name. in these cases,
* sanity checking the relations between name
* and foundname can trigger an assertion, so
* let's just skip them.
*/
if (dns_name_isabsolute(name) ==
dns_name_isabsolute(foundname))
{
assert_false(dns_name_equal(name, foundname));
assert_true(
dns_name_issubdomain(name, foundname));
}
}
if (check[i].found == NULL) {
assert_null(pval);
} else {
assert_string_equal(pval, check[i].found);
}
}
}
static void
insert_str(dns_qp_t *qp, const char *str) {
isc_result_t result;
uintptr_t pval = (uintptr_t)str;
INSIST((pval & 3) == 0);
result = dns_qp_insert(qp, (void *)pval, 0);
assert_int_equal(result, ISC_R_SUCCESS);
}
ISC_RUN_TEST_IMPL(partialmatch) {
isc_result_t result;
dns_qp_t *qp = NULL;
int i = 0;
dns_qp_create(mctx, &string_methods, NULL, &qp);
/*
* Fixed size strings [16] should ensure leaf-compatible alignment.
*/
const char insert[][16] = {
"a.b.", "b.", "fo.bar.", "foo.bar.",
"fooo.bar.", "web.foo.bar.", ".", "",
};
/*
* omit the root node for now, otherwise we'll get "partial match"
* results when we want "not found".
*/
while (insert[i][0] != '.') {
insert_str(qp, insert[i++]);
}
static struct check_partialmatch check1[] = {
{ "a.b.", 0, ISC_R_SUCCESS, "a.b." },
{ "a.b.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "b." },
{ "b.c.", DNS_QPFIND_NOEXACT, ISC_R_NOTFOUND, NULL },
{ "bar.", 0, ISC_R_NOTFOUND, NULL },
{ "f.bar.", 0, ISC_R_NOTFOUND, NULL },
{ "foo.bar.", 0, ISC_R_SUCCESS, "foo.bar." },
{ "foo.bar.", DNS_QPFIND_NOEXACT, ISC_R_NOTFOUND, NULL },
{ "foooo.bar.", 0, ISC_R_NOTFOUND, NULL },
{ "w.foo.bar.", 0, DNS_R_PARTIALMATCH, "foo.bar." },
{ "www.foo.bar.", 0, DNS_R_PARTIALMATCH, "foo.bar." },
{ "web.foo.bar.", 0, ISC_R_SUCCESS, "web.foo.bar." },
{ "webby.foo.bar.", 0, DNS_R_PARTIALMATCH, "foo.bar." },
{ "my.web.foo.bar.", 0, DNS_R_PARTIALMATCH, "web.foo.bar." },
{ "web.foo.bar.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH,
"foo.bar." },
{ "my.web.foo.bar.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH,
"web.foo.bar." },
{ "my.other.foo.bar.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH,
"foo.bar." },
{ NULL, 0, 0, NULL },
};
check_partialmatch(qp, check1);
/* what if the trie contains the root? */
INSIST(insert[i][0] == '.');
insert_str(qp, insert[i++]);
static struct check_partialmatch check2[] = {
{ "b.c.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "." },
{ "bar.", 0, DNS_R_PARTIALMATCH, "." },
{ "foo.bar.", 0, ISC_R_SUCCESS, "foo.bar." },
{ "foo.bar.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "." },
{ "bar", 0, ISC_R_NOTFOUND, NULL },
{ "bar", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "." },
{ NULL, 0, 0, NULL },
};
check_partialmatch(qp, check2);
/*
* what if entries in the trie are relative to the zone apex
* and there's no root node?
*/
dns_qpkey_t rootkey = { SHIFT_NOBYTE };
result = dns_qp_deletekey(qp, rootkey, 1, NULL, NULL);
assert_int_equal(result, ISC_R_SUCCESS);
check_partialmatch(
qp,
(struct check_partialmatch[]){
{ "bar", 0, ISC_R_NOTFOUND, NULL },
{ "bar", DNS_QPFIND_NOEXACT, ISC_R_NOTFOUND, NULL },
{ "bar.", 0, ISC_R_NOTFOUND, NULL },
{ "bar.", DNS_QPFIND_NOEXACT, ISC_R_NOTFOUND, NULL },
{ NULL, 0, 0, NULL },
});
/* what if there's a root node with an empty key? */
INSIST(insert[i][0] == '\0');
insert_str(qp, insert[i++]);
check_partialmatch(
qp,
(struct check_partialmatch[]){
{ "bar", 0, DNS_R_PARTIALMATCH, "" },
{ "bar", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "" },
{ "bar.", 0, DNS_R_PARTIALMATCH, "" },
{ "bar.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, "" },
{ NULL, 0, 0, NULL },
});
dns_qp_destroy(&qp);
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY(qpkey_name)
ISC_TEST_ENTRY(qpkey_sort)
ISC_TEST_ENTRY(qpiter)
ISC_TEST_ENTRY(partialmatch)
ISC_TEST_LIST_END
ISC_TEST_MAIN