mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-08-31 14:35:26 +00:00
This change adds a number of support routines for the unit tests, and for benchmarks and fuzz tests to be added later. It isn't necessary to include the support routines in libdns, since they are not needed by BIND's installed programs. So `libtest` seems like the best place for them. The tests themselves verify that dns_qpkey_fromname() behaves as expected.
396 lines
9.4 KiB
C
396 lines
9.4 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 <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
|
|
#include <isc/buffer.h>
|
|
#include <isc/magic.h>
|
|
#include <isc/refcount.h>
|
|
#include <isc/rwlock.h>
|
|
#include <isc/util.h>
|
|
|
|
#include <dns/name.h>
|
|
#include <dns/qp.h>
|
|
#include <dns/types.h>
|
|
|
|
#include "qp_p.h"
|
|
|
|
#include <tests/qp.h>
|
|
|
|
/***********************************************************************
|
|
*
|
|
* key reverse conversions
|
|
*/
|
|
|
|
uint8_t
|
|
qp_test_bittoascii(qp_shift_t bit) {
|
|
uint8_t byte = dns_qp_byte_for_bit[bit];
|
|
if (bit == SHIFT_NOBYTE) {
|
|
return ('.');
|
|
} else if (qp_common_character(byte)) {
|
|
return (byte);
|
|
} else if (byte < '-') {
|
|
return ('#');
|
|
} else if (byte < '_') {
|
|
return ('@');
|
|
} else {
|
|
return ('~' - SHIFT_OFFSET + bit);
|
|
}
|
|
}
|
|
|
|
const char *
|
|
qp_test_keytoascii(dns_qpkey_t key, size_t len) {
|
|
for (size_t offset = 0; offset < len; offset++) {
|
|
key[offset] = qp_test_bittoascii(key[offset]);
|
|
}
|
|
key[len] = '\0';
|
|
return ((const char *)key);
|
|
}
|
|
|
|
void
|
|
qp_test_keytoname(const dns_qpkey_t key, dns_name_t *name) {
|
|
size_t locs[128];
|
|
size_t loc = 0, opos = 0;
|
|
size_t offset;
|
|
|
|
REQUIRE(ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
|
|
REQUIRE(name->buffer != NULL);
|
|
REQUIRE(name->offsets != NULL);
|
|
|
|
isc_buffer_clear(name->buffer);
|
|
|
|
/* Scan the key looking for label boundaries */
|
|
for (offset = 0; offset < 512; offset++) {
|
|
INSIST(key[offset] >= SHIFT_NOBYTE &&
|
|
key[offset] < SHIFT_OFFSET);
|
|
INSIST(loc < 128);
|
|
if (key[offset] == SHIFT_NOBYTE) {
|
|
if (key[offset + 1] == SHIFT_NOBYTE) {
|
|
locs[loc] = offset + 1;
|
|
break;
|
|
}
|
|
locs[loc++] = offset + 1;
|
|
} else if (offset == 0) {
|
|
/* This happens for a relative name */
|
|
locs[loc++] = offset;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* In the key the labels are encoded in reverse order, so
|
|
* we step backward through the label boundaries, then forward
|
|
* through the labels, to create the DNS wire format data.
|
|
*/
|
|
name->labels = loc;
|
|
while (loc-- > 0) {
|
|
uint8_t len = 0, *lenp = NULL;
|
|
|
|
/* Add a length byte to the name data and set an offset */
|
|
lenp = isc_buffer_used(name->buffer);
|
|
isc_buffer_putuint8(name->buffer, 0);
|
|
name->offsets[opos++] = name->length++;
|
|
|
|
/* Convert from escaped byte ranges to ASCII */
|
|
for (offset = locs[loc]; offset < locs[loc + 1] - 1; offset++) {
|
|
uint8_t byte = dns_qp_byte_for_bit[key[offset]];
|
|
if (qp_common_character(byte)) {
|
|
isc_buffer_putuint8(name->buffer, byte);
|
|
} else {
|
|
byte += key[++offset] - SHIFT_BITMAP;
|
|
isc_buffer_putuint8(name->buffer, byte);
|
|
}
|
|
len++;
|
|
}
|
|
|
|
name->length += len;
|
|
*lenp = len;
|
|
}
|
|
|
|
/* Add a root label for absolute names */
|
|
if (key[0] == SHIFT_NOBYTE) {
|
|
name->attributes.absolute = true;
|
|
isc_buffer_putuint8(name->buffer, 0);
|
|
name->length++;
|
|
name->labels++;
|
|
}
|
|
|
|
name->ndata = isc_buffer_base(name->buffer);
|
|
}
|
|
|
|
/***********************************************************************
|
|
*
|
|
* trie properties
|
|
*/
|
|
|
|
static size_t
|
|
getheight(dns_qp_t *qp, qp_node_t *n) {
|
|
if (!is_branch(n)) {
|
|
return (0);
|
|
}
|
|
size_t max_height = 0;
|
|
qp_weight_t size = branch_twigs_size(n);
|
|
qp_node_t *twigs = branch_twigs_vector(qp, n);
|
|
for (qp_weight_t pos = 0; pos < size; pos++) {
|
|
size_t height = getheight(qp, &twigs[pos]);
|
|
max_height = ISC_MAX(max_height, height);
|
|
}
|
|
return (max_height + 1);
|
|
}
|
|
|
|
size_t
|
|
qp_test_getheight(dns_qp_t *qp) {
|
|
return (getheight(qp, &qp->root));
|
|
}
|
|
|
|
static size_t
|
|
maxkeylen(dns_qp_t *qp, qp_node_t *n) {
|
|
if (!is_branch(n)) {
|
|
if (leaf_pval(n) == NULL) {
|
|
return (0);
|
|
} else {
|
|
dns_qpkey_t key;
|
|
return (leaf_qpkey(qp, n, key));
|
|
}
|
|
}
|
|
size_t max_len = 0;
|
|
qp_weight_t size = branch_twigs_size(n);
|
|
qp_node_t *twigs = branch_twigs_vector(qp, n);
|
|
for (qp_weight_t pos = 0; pos < size; pos++) {
|
|
size_t len = maxkeylen(qp, &twigs[pos]);
|
|
max_len = ISC_MAX(max_len, len);
|
|
}
|
|
return (max_len);
|
|
}
|
|
|
|
size_t
|
|
qp_test_maxkeylen(dns_qp_t *qp) {
|
|
return (maxkeylen(qp, &qp->root));
|
|
}
|
|
|
|
/***********************************************************************
|
|
*
|
|
* dump to stdout
|
|
*/
|
|
|
|
static void
|
|
dumpread(dns_qpreadable_t qpr, const char *type, const char *tail) {
|
|
dns_qpread_t *qp = dns_qpreadable_cast(qpr);
|
|
printf("%s %p root %p base %p methods %p%s", type, qp, &qp->root,
|
|
qp->base, qp->methods, tail);
|
|
}
|
|
|
|
static void
|
|
dumpqp(dns_qp_t *qp, const char *type) {
|
|
dumpread(qp, type, " mctx ");
|
|
printf("%p\n", qp->mctx);
|
|
printf("%s %p usage %p generation %u "
|
|
"chunk_max %u bump %u fender %u\n",
|
|
type, qp, qp->usage, qp->generation, qp->chunk_max, qp->bump,
|
|
qp->fender);
|
|
printf("%s %p leaf %u live %u used %u free %u hold %u\n", type, qp,
|
|
qp->leaf_count, qp->used_count - qp->free_count, qp->used_count,
|
|
qp->free_count, qp->hold_count);
|
|
printf("%s %p compact_all=%d shared_arrays=%d"
|
|
" transaction_mode=%d write_protect=%d\n",
|
|
type, qp, qp->compact_all, qp->shared_arrays,
|
|
qp->transaction_mode, qp->write_protect);
|
|
}
|
|
|
|
void
|
|
qp_test_dumpread(dns_qpreadable_t qp) {
|
|
dumpread(qp, "qpread", "\n");
|
|
fflush(stdout);
|
|
}
|
|
|
|
void
|
|
qp_test_dumpsnap(dns_qpsnap_t *qp) {
|
|
dumpread(qp, "qpsnap", " whence ");
|
|
printf("%p\n", qp->whence);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void
|
|
qp_test_dumpqp(dns_qp_t *qp) {
|
|
dumpqp(qp, "qp");
|
|
fflush(stdout);
|
|
}
|
|
|
|
void
|
|
qp_test_dumpmulti(dns_qpmulti_t *multi) {
|
|
dumpqp(&multi->phase[0], "qpmulti->phase[0]");
|
|
dumpqp(&multi->phase[1], "qpmulti->phase[1]");
|
|
printf("qpmulti %p read %p snapshots %u\n", &multi, multi->read,
|
|
multi->snapshots);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void
|
|
qp_test_dumpchunks(dns_qp_t *qp) {
|
|
qp_cell_t used = 0;
|
|
qp_cell_t free = 0;
|
|
dumpqp(qp, "qp");
|
|
for (qp_chunk_t c = 0; c < qp->chunk_max; c++) {
|
|
printf("qp %p chunk %u base %p used %u free %u generation %u\n",
|
|
qp, c, qp->base[c], qp->usage[c].used, qp->usage[c].free,
|
|
qp->usage[c].generation);
|
|
used += qp->usage[c].used;
|
|
free += qp->usage[c].free;
|
|
}
|
|
printf("qp %p total used %u free %u\n", qp, used, free);
|
|
fflush(stdout);
|
|
}
|
|
|
|
void
|
|
qp_test_dumptrie(dns_qpreadable_t qpr) {
|
|
dns_qpread_t *qp = dns_qpreadable_cast(qpr);
|
|
struct {
|
|
qp_ref_t ref;
|
|
qp_shift_t max, pos;
|
|
} stack[512];
|
|
size_t sp = 0;
|
|
qp_cell_t leaf_count = 0;
|
|
|
|
/*
|
|
* fake up a sentinel stack entry corresponding to the root
|
|
* node; the ref is deliberately out of bounds, and pos == max
|
|
* so we will immediately stop scanning it
|
|
*/
|
|
stack[sp].ref = ~0U;
|
|
stack[sp].max = 0;
|
|
stack[sp].pos = 0;
|
|
qp_node_t *n = &qp->root;
|
|
printf("%p ROOT\n", n);
|
|
|
|
for (;;) {
|
|
if (is_branch(n)) {
|
|
qp_ref_t ref = branch_twigs_ref(n);
|
|
qp_weight_t max = branch_twigs_size(n);
|
|
qp_node_t *twigs = ref_ptr(qp, ref);
|
|
|
|
/* brief list of twigs */
|
|
dns_qpkey_t bits;
|
|
size_t len = 0;
|
|
for (qp_shift_t bit = SHIFT_NOBYTE; bit < SHIFT_OFFSET;
|
|
bit++)
|
|
{
|
|
if (branch_has_twig(n, bit)) {
|
|
bits[len++] = bit;
|
|
}
|
|
}
|
|
assert(len == max);
|
|
qp_test_keytoascii(bits, len);
|
|
printf("%*s%p BRANCH %p %d %zu %s\n", (int)sp * 2, "",
|
|
n, twigs, ref, branch_key_offset(n), bits);
|
|
|
|
++sp;
|
|
stack[sp].ref = ref;
|
|
stack[sp].max = max;
|
|
stack[sp].pos = 0;
|
|
} else {
|
|
if (leaf_pval(n) != NULL) {
|
|
dns_qpkey_t key;
|
|
qp_test_keytoascii(key, leaf_qpkey(qp, n, key));
|
|
printf("%*s%p LEAF %p %d %s\n", (int)sp * 2, "",
|
|
n, leaf_pval(n), leaf_ival(n), key);
|
|
leaf_count++;
|
|
} else {
|
|
assert(n == &qp->root);
|
|
assert(leaf_count == 0);
|
|
printf("%p EMPTY", n);
|
|
}
|
|
}
|
|
|
|
while (stack[sp].pos == stack[sp].max) {
|
|
if (sp == 0) {
|
|
printf("LEAVES %d\n", leaf_count);
|
|
fflush(stdout);
|
|
return;
|
|
}
|
|
--sp;
|
|
}
|
|
|
|
n = ref_ptr(qp, stack[sp].ref) + stack[sp].pos;
|
|
stack[sp].pos++;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dumpdot_name(qp_node_t *n) {
|
|
if (is_branch(n)) {
|
|
qp_ref_t ref = branch_twigs_ref(n);
|
|
printf("c%dn%d", ref_chunk(ref), ref_cell(ref));
|
|
} else {
|
|
printf("v%p", leaf_pval(n));
|
|
}
|
|
}
|
|
|
|
static void
|
|
dumpdot_twig(dns_qp_t *qp, qp_node_t *n) {
|
|
if (is_branch(n)) {
|
|
dumpdot_name(n);
|
|
printf(" [shape=record, label=\"{ \\N\\noff %zu | ",
|
|
branch_key_offset(n));
|
|
char sep = '{';
|
|
for (qp_shift_t bit = SHIFT_NOBYTE; bit < SHIFT_OFFSET; bit++) {
|
|
if (branch_has_twig(n, bit)) {
|
|
printf("%c <t%d> %c ", sep,
|
|
branch_twig_pos(n, bit),
|
|
qp_test_bittoascii(bit));
|
|
sep = '|';
|
|
}
|
|
}
|
|
printf("}}\"];\n");
|
|
|
|
qp_weight_t size = branch_twigs_size(n);
|
|
qp_node_t *twigs = branch_twigs_vector(qp, n);
|
|
|
|
for (qp_weight_t pos = 0; pos < size; pos++) {
|
|
dumpdot_name(n);
|
|
printf(":t%d:e -> ", pos);
|
|
dumpdot_name(&twigs[pos]);
|
|
printf(":w;\n");
|
|
}
|
|
|
|
for (qp_weight_t pos = 0; pos < size; pos++) {
|
|
dumpdot_twig(qp, &twigs[pos]);
|
|
}
|
|
|
|
} else {
|
|
dns_qpkey_t key;
|
|
const char *str;
|
|
if (leaf_pval(n) == NULL) {
|
|
str = "EMPTY";
|
|
} else {
|
|
str = qp_test_keytoascii(key, leaf_qpkey(qp, n, key));
|
|
}
|
|
printf("v%p [shape=oval, label=\"\\N ival %d\\n%s\"];\n",
|
|
leaf_pval(n), leaf_ival(n), str);
|
|
}
|
|
}
|
|
|
|
void
|
|
qp_test_dumpdot(dns_qp_t *qp) {
|
|
REQUIRE(VALID_QP(qp));
|
|
qp_node_t *n = &qp->root;
|
|
printf("strict digraph {\nrankdir = \"LR\"; ranksep = 1.0;\n");
|
|
printf("ROOT [shape=point]; ROOT -> ");
|
|
dumpdot_name(n);
|
|
printf(":w;\n");
|
|
dumpdot_twig(qp, n);
|
|
printf("}\n");
|
|
}
|
|
|
|
/**********************************************************************/
|