REQUIRE(VALID_FWDTABLE(fwdtable));
dns_qpmulti_query(fwdtable->table, &qpr);
- result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, &pval, NULL);
+ result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, NULL, &pval,
+ NULL);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
dns_forwarders_t *fwdrs = pval;
*forwardersp = fwdrs;
#include <isc/attributes.h>
+#include <dns/name.h>
#include <dns/types.h>
/*%
typedef uint8_t dns_qpkey_t[DNS_QP_MAXKEY];
/*%
- * A trie iterator describes a path through the trie from the root to
- * a leaf node, for use with `dns_qpiter_init()` and `dns_qpiter_next()`.
+ * A QP iterator traverses a trie starting with the root and passing
+ * though each leaf node in lexicographic order; it is used with
+ * `dns_qpiter_init()` and `dns_qpiter_next()`.
*/
typedef struct dns_qpiter {
unsigned int magic;
} stack[DNS_QP_MAXKEY];
} dns_qpiter_t;
+/*%
+ * A QP chain holds references to each populated node between
+ * the root and a given leaf. It is set while running
+ * `dns_qp_findname_ancestor()`, and can optionally be passed
+ * back to the caller so that individual nodes can be accessed.
+ */
+typedef struct dns_qpchain {
+ unsigned int magic;
+ dns_qpreader_t *qp;
+ uint8_t len;
+ struct {
+ void *node;
+ size_t offset;
+ } chain[DNS_NAME_MAXLABELS];
+} dns_qpchain_t;
+
/*%
* These leaf methods allow the qp-trie code to call back to the code
* responsible for the leaf values that are stored in the trie. The
isc_result_t
dns_qp_findname_ancestor(dns_qpreadable_t qpr, const dns_name_t *name,
dns_qpfind_t options, dns_name_t *foundname,
- void **pval_r, uint32_t *ival_r);
+ dns_qpchain_t *chain, void **pval_r, uint32_t *ival_r);
/*%<
* Find a leaf in a qp-trie that is an ancestor domain of, or equal to, the
* given DNS name.
*
* If 'foundname' is not NULL, it is updated to contain the name found.
*
+ * If 'chain' is not NULL, it is updated to contain a QP chain with
+ * references to the populated nodes in the tree between the root and
+ * the name found.
+ *
* The leaf values are assigned to whichever of `*pval_r` and `*ival_r`
* are not null, unless the return value is ISC_R_NOTFOUND.
*
* \li ISC_R_NOMORE otherwise
*/
+void
+dns_qpchain_init(dns_qpreadable_t qpr, dns_qpchain_t *chain);
+/*%<
+ * Initialize a QP chain.
+ *
+ * Requires:
+ * \li `qpr` is a pointer to a valid qp-trie
+ * \li `chain` is not NULL.
+ */
+
+unsigned int
+dns_qpchain_length(dns_qpchain_t *chain);
+/*%<
+ * Returns the length of a QP chain.
+ *
+ * Requires:
+ * \li `chain` is a pointer to an initialized QP chain object.
+ */
+
+void
+dns_qpchain_node(dns_qpchain_t *chain, unsigned int level, dns_name_t *name,
+ void **pval_r, uint32_t *ival_r);
+/*%<
+ * Sets 'name' to the name of the leaf referenced at `chain->stack[level]`.
+ *
+ * The leaf values are assigned to whichever of `*pval_r` and `*ival_r`
+ * are not null.
+ *
+ * Requires:
+ * \li `chain` is a pointer to an initialized QP chain object.
+ * \li `level` is less than `chain->len`.
+ */
+
/***********************************************************************
*
* functions - transactions
REQUIRE(foundname != NULL);
dns_qpmulti_query(keytable->table, &qpr);
- result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, &pval, NULL);
+ result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, NULL, &pval,
+ NULL);
keynode = pval;
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
REQUIRE(wantdnssecp != NULL);
dns_qpmulti_query(keytable->table, &qpr);
- result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, &pval, NULL);
+ result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, NULL, &pval,
+ NULL);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
keynode = pval;
if (foundname != NULL) {
REQUIRE(VALID_NAMETREE(nametree));
dns_qpmulti_query(nametree->table, &qpr);
- result = dns_qp_findname_ancestor(&qpr, name, 0, found, (void **)&node,
- NULL);
+ result = dns_qp_findname_ancestor(&qpr, name, 0, found, NULL,
+ (void **)&node, NULL);
if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
switch (nametree->type) {
case DNS_NAMETREE_BOOL:
RWLOCK(&ntatable->rwlock, isc_rwlocktype_read);
dns_qpmulti_query(ntatable->table, &qpr);
- result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, &pval, NULL);
+ result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, NULL, &pval,
+ NULL);
nta = pval;
switch (result) {
}
/***********************************************************************
- *
- * iterate
+ * chains
+ */
+static void
+maybe_set_name(dns_qpreader_t *qp, qp_node_t *node, dns_name_t *name) {
+ dns_qpkey_t key;
+ size_t len;
+
+ if (name == NULL) {
+ return;
+ }
+
+ len = leaf_qpkey(qp, node, key);
+ dns_qpkey_toname(key, len, name);
+}
+
+void
+dns_qpchain_init(dns_qpreadable_t qpr, dns_qpchain_t *chain) {
+ dns_qpreader_t *qp = dns_qpreader(qpr);
+ REQUIRE(QP_VALID(qp));
+ REQUIRE(chain != NULL);
+
+ *chain = (dns_qpchain_t){
+ .magic = QPCHAIN_MAGIC,
+ .qp = qp,
+ };
+}
+
+unsigned int
+dns_qpchain_length(dns_qpchain_t *chain) {
+ REQUIRE(QPCHAIN_VALID(chain));
+
+ return (chain->len);
+}
+
+void
+dns_qpchain_node(dns_qpchain_t *chain, unsigned int level, dns_name_t *name,
+ void **pval_r, uint32_t *ival_r) {
+ qp_node_t *node = NULL;
+
+ REQUIRE(QPCHAIN_VALID(chain));
+ REQUIRE(level < chain->len);
+
+ node = chain->chain[level].node;
+ maybe_set_name(chain->qp, node, name);
+ SET_IF_NOT_NULL(pval_r, leaf_pval(node));
+ SET_IF_NOT_NULL(ival_r, leaf_ival(node));
+}
+
+/***********************************************************************
+ * iterators
*/
void
return (dns_qp_getkey(qpr, key, keylen, pval_r, ival_r));
}
+static inline void
+add_link(dns_qpchain_t *chain, qp_node_t *node, size_t offset) {
+ chain->chain[chain->len].node = node;
+ chain->chain[chain->len].offset = offset;
+ chain->len++;
+ INSIST(chain->len <= DNS_NAME_MAXLABELS);
+}
+
isc_result_t
dns_qp_findname_ancestor(dns_qpreadable_t qpr, const dns_name_t *name,
dns_qpfind_t options, dns_name_t *foundname,
- void **pval_r, uint32_t *ival_r) {
+ dns_qpchain_t *chain, void **pval_r,
+ uint32_t *ival_r) {
dns_qpreader_t *qp = dns_qpreader(qpr);
dns_qpkey_t search, found;
size_t searchlen, foundlen;
size_t offset;
- qp_shift_t bit;
- qp_node_t *n = NULL, *twigs = NULL;
+ qp_node_t *n = NULL;
isc_result_t result;
- unsigned int labels = 0;
- struct offref {
- uint32_t off;
- qp_ref_t ref;
- } label[DNS_NAME_MAXLABELS];
+ dns_qpchain_t oc;
REQUIRE(QP_VALID(qp));
REQUIRE(foundname == NULL || ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
result = ISC_R_SUCCESS;
}
+ if (chain == NULL) {
+ chain = &oc;
+ }
+ dns_qpchain_init(qp, chain);
+
n = get_root(qp);
if (n == NULL) {
return (ISC_R_NOTFOUND);
* that we discover along the way. (In general, qp-trie searches can be
* one-pass, by recording their traversal, or two-pass, for less stack
* memory usage.)
- *
- * A shorter key that can be a parent domain always has a leaf node at
- * SHIFT_NOBYTE (indicating end of its key) where our search key has a
- * normal character immediately after a label separator. Note 1: It is
- * OK if `offset - 1` underflows: it will become SIZE_MAX, which is
- * greater than `searchlen`, so `qpkey_bit()` will return SHIFT_NOBYTE,
- * which is what we want when `offset == 0`. Note 2: Any SHIFT_NOBYTE
- * twig is always `twigs[0]`.
*/
while (is_branch(n)) {
prefetch_twigs(qp, n);
- twigs = branch_twigs_vector(qp, n);
+
+ qp_node_t *twigs = branch_twigs_vector(qp, n);
offset = branch_key_offset(n);
- bit = qpkey_bit(search, searchlen, offset);
+ qp_shift_t bit = qpkey_bit(search, searchlen, offset);
+
+ /*
+ * A shorter key that can be a parent domain always has a
+ * leaf node at SHIFT_NOBYTE (indicating end of its key)
+ * where our search key has a normal character immediately
+ * after a label separator.
+ *
+ * Note 1: It is OK if `off - 1` underflows: it will
+ * become SIZE_MAX, which is greater than `searchlen`, so
+ * `qpkey_bit()` will return SHIFT_NOBYTE, which is what we
+ * want when `off == 0`.
+ *
+ * Note 2: Any SHIFT_NOBYTE twig is always `twigs[0]`.
+ */
if (bit != SHIFT_NOBYTE && branch_has_twig(n, SHIFT_NOBYTE) &&
qpkey_bit(search, searchlen, offset - 1) == SHIFT_NOBYTE &&
!is_branch(&twigs[0]))
{
- label[labels].off = offset;
- label[labels].ref = branch_twigs_ref(n);
- labels++;
- INSIST(labels <= DNS_NAME_MAXLABELS);
+ add_link(chain, twigs, offset);
}
if (branch_has_twig(n, bit)) {
+ /* we have the twig we wanted */
n = branch_twig_ptr(qp, n, bit);
- } else if (labels == 0) {
- /* any twig will do */
+ } else if (chain->len == 0) {
+ /*
+ * the twig we're looking for isn't here,
+ * and we haven't found an ancestor yet either.
+ * continue the search with whatever this branch's
+ * first twig is.
+ */
n = &twigs[0];
} else {
- n = ref_ptr(qp, label[labels - 1].ref);
- break;
+ /*
+ * this branch is a dead end, but we do have
+ * a previous leaf node saved in the chain.
+ * we go back to that. it's a leaf, so we'll
+ * fall out of the loop here.
+ */
+ n = chain->chain[chain->len - 1].node;
}
}
if (offset == QPKEY_EQUAL || offset == foundlen) {
SET_IF_NOT_NULL(pval_r, leaf_pval(n));
SET_IF_NOT_NULL(ival_r, leaf_ival(n));
- if (foundname != NULL) {
- dns_qpkey_toname(found, foundlen, foundname);
- }
+ maybe_set_name(qp, n, foundname);
if (offset == QPKEY_EQUAL) {
+ if ((options & DNS_QPFIND_NOEXACT) == 0) {
+ /* add the exact match to the chain */
+ add_link(chain, n, offset);
+ }
return (result);
} else {
return (DNS_R_PARTIALMATCH);
}
}
- while (labels-- > 0) {
- if (offset > label[labels].off) {
- n = ref_ptr(qp, label[labels].ref);
+
+ /*
+ * the requested name was not found, but if an ancestor
+ * was, we can retrieve that from the chain.
+ */
+ int len = chain->len;
+ while (len-- > 0) {
+ if (offset >= chain->chain[len].offset) {
+ n = chain->chain[len].node;
SET_IF_NOT_NULL(pval_r, leaf_pval(n));
SET_IF_NOT_NULL(ival_r, leaf_ival(n));
- if (foundname != NULL) {
- foundlen = leaf_qpkey(qp, n, found);
- dns_qpkey_toname(found, foundlen, foundname);
- }
+ maybe_set_name(qp, n, foundname);
return (DNS_R_PARTIALMATCH);
+ } else {
+ /*
+ * oops, during the search we found and added
+ * a leaf that's longer than the requested
+ * name; remove it from the chain.
+ */
+ chain->len--;
}
}
+
+ /* nothing was found at all */
return (ISC_R_NOTFOUND);
}
#define QP_MAGIC ISC_MAGIC('t', 'r', 'i', 'e')
#define QPITER_MAGIC ISC_MAGIC('q', 'p', 'i', 't')
+#define QPCHAIN_MAGIC ISC_MAGIC('q', 'p', 'c', 'h')
#define QPMULTI_MAGIC ISC_MAGIC('q', 'p', 'm', 'v')
#define QPREADER_MAGIC ISC_MAGIC('q', 'p', 'r', 'x')
#define QPBASE_MAGIC ISC_MAGIC('q', 'p', 'b', 'p')
#define QP_VALID(qp) ISC_MAGIC_VALID(qp, QP_MAGIC)
#define QPITER_VALID(qp) ISC_MAGIC_VALID(qp, QPITER_MAGIC)
+#define QPCHAIN_VALID(qp) ISC_MAGIC_VALID(qp, QPCHAIN_MAGIC)
#define QPMULTI_VALID(qp) ISC_MAGIC_VALID(qp, QPMULTI_MAGIC)
#define QPBASE_VALID(qp) ISC_MAGIC_VALID(qp, QPBASE_MAGIC)
#define QPRCU_VALID(qp) ISC_MAGIC_VALID(qp, QPRCU_MAGIC)
if (exactopts == DNS_ZTFIND_EXACT) {
result = dns_qp_getname(&qpr, name, &pval, NULL);
} else if (exactopts == DNS_ZTFIND_NOEXACT) {
- result = dns_qp_findname_ancestor(
- &qpr, name, DNS_QPFIND_NOEXACT, NULL, &pval, NULL);
+ result = dns_qp_findname_ancestor(&qpr, name,
+ DNS_QPFIND_NOEXACT, NULL,
+ NULL, &pval, NULL);
} else {
- result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, &pval,
- NULL);
+ result = dns_qp_findname_ancestor(&qpr, name, 0, NULL, NULL,
+ &pval, NULL);
}
dns_qpread_destroy(zt->multi, &qpr);
start = isc_time_monotonic();
for (i = 0; i < n; i++) {
name = dns_fixedname_name(&items[i]);
- dns_qp_findname_ancestor(qp, name, 0, NULL, NULL, NULL);
+ dns_qp_findname_ancestor(qp, name, 0, NULL, NULL, NULL, NULL);
}
stop = isc_time_monotonic();
++search->ndata[1];
}
- dns_qp_findname_ancestor(qp, search, 0, NULL, NULL, NULL);
+ dns_qp_findname_ancestor(qp, search, 0, NULL, NULL, NULL, NULL);
}
stop = isc_time_monotonic();
dns_test_namefromstring(check[i].query, &fn1);
result = dns_qp_findname_ancestor(qp, name, check[i].options,
- foundname, &pval, NULL);
+ foundname, NULL, &pval, NULL);
#if 0
fprintf(stderr, "%s (flags %u) %s (expected %s) "
dns_qp_destroy(&qp);
}
+struct check_qpchain {
+ const char *query;
+ dns_qpfind_t options;
+ isc_result_t result;
+ unsigned int length;
+ const char *names[10];
+};
+
+static void
+check_qpchain(dns_qp_t *qp, struct check_qpchain check[]) {
+ for (int i = 0; check[i].query != NULL; i++) {
+ isc_result_t result;
+ dns_fixedname_t fn1;
+ dns_name_t *name = dns_fixedname_initname(&fn1);
+ dns_qpchain_t chain;
+
+ dns_qpchain_init(qp, &chain);
+ dns_test_namefromstring(check[i].query, &fn1);
+ result = dns_qp_findname_ancestor(qp, name, check[i].options,
+ NULL, &chain, NULL, NULL);
+
+#if 0
+ fprintf(stderr, "%s (%d) %s (expected %s), "
+ "len %d (expected %d)\n",
+ check[i].query, check[i].options,
+ isc_result_totext(result),
+ isc_result_totext(check[i].result),
+ dns_qpchain_length(&chain), check[i].length);
+#endif
+ assert_int_equal(result, check[i].result);
+ assert_int_equal(dns_qpchain_length(&chain), check[i].length);
+ for (unsigned int j = 0; j < check[i].length; j++) {
+ dns_fixedname_t fn2, fn3;
+ dns_name_t *expected = dns_fixedname_initname(&fn2);
+ dns_name_t *found = dns_fixedname_initname(&fn3);
+
+ dns_test_namefromstring(check[i].names[j], &fn2);
+ dns_qpchain_node(&chain, j, found, NULL, NULL);
+#if 0
+ char nb[DNS_NAME_FORMATSIZE];
+ dns_name_format(found, nb, sizeof(nb));
+ fprintf(stderr, "got %s, expected %s\n", nb,
+ check[i].names[j]);
+#endif
+ assert_true(dns_name_equal(found, expected));
+ }
+ }
+}
+
+ISC_RUN_TEST_IMPL(qpchain) {
+ 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.", "c.b.a.",
+ "e.d.c.b.a.", "c.b.b.", "c.d.", "a.b.c.d.",
+ "a.b.c.d.e.", "" };
+
+ while (insert[i][0] != '\0') {
+ insert_str(qp, insert[i++]);
+ }
+
+ static struct check_qpchain check1[] = {
+ { "b.", 0, ISC_R_SUCCESS, 2 },
+ { "c.", 0, DNS_R_PARTIALMATCH, 1 },
+ { "e.d.c.b.a.", 0, ISC_R_SUCCESS, 4 },
+ { "a.b.c.d.", 0, ISC_R_SUCCESS, 3 },
+ { "a.b.c.d.", DNS_QPFIND_NOEXACT, DNS_R_PARTIALMATCH, 2 },
+ { NULL, 0, 0, 0 },
+ };
+
+ check1[0].names[0] = ".";
+ check1[0].names[1] = "b.";
+
+ check1[1].names[0] = ".";
+
+ check1[2].names[0] = ".";
+ check1[2].names[1] = "a.";
+ check1[2].names[2] = "c.b.a.";
+ check1[2].names[3] = "e.d.c.b.a.";
+
+ check1[3].names[0] = ".";
+ check1[3].names[1] = "c.d.";
+ check1[3].names[2] = "a.b.c.d.";
+
+ check1[4].names[0] = ".";
+ check1[4].names[1] = "c.d.";
+
+ check_qpchain(qp, check1);
+ 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_ENTRY(qpchain)
ISC_TEST_LIST_END
ISC_TEST_MAIN