*
* Keys are `dns_qpkey_t`, which is a string-like thing, usually created
* from a DNS name. You can use both relative and absolute DNS names as
- * keys.
+ * keys, even in the same trie, except for one caveat: if a trie contains
+ * names relative to the zone apex, the natural way to represent the apex
+ * itself (spelled `@` in zone files) is a zero-length name; but a
+ * zero-length name has the same qpkey representation as the root zone
+ * (apart from its length), so they collide.
*
* Leaf values are a pair of a `void *` pointer and a `uint32_t`
* (because that is what fits inside an internal qp-trie leaf node).
DNS_QPGC_ALL,
} dns_qpgc_t;
+/*%
+ * Options for fancy searches such as `dns_qp_findname_parent()`
+ */
+typedef enum dns_qpfind {
+ DNS_QPFIND_NOEXACT = 1 << 0,
+} dns_qpfind_t;
+
/***********************************************************************
*
* functions - create, destory, enquire
/*
* XXXFANF todo, based on what we discover BIND needs
*
- * fancy searches: longest match, lexicographic predecessor
- * (for NSEC), successor (for modification-safe iteration), etc.
+ * more fancy searches: lexicographic predecessor (for NSEC),
+ * successor (for modification-safe iteration), etc.
*
* do we need specific lookup functions to find out if the
* returned value is readonly or mutable?
* \li ISC_R_SUCCESS if the leaf was found
*/
+isc_result_t
+dns_qp_findname_parent(dns_qpreadable_t qpr, const dns_name_t *name,
+ dns_qpfind_t options, void **pval_r, uint32_t *ival_r);
+/*%<
+ * Find a leaf in a qp-trie that is a parent domain of or equal to the
+ * given DNS name.
+ *
+ * If the DNS_QPFIND_NOEXACT option is set, find a strict parent
+ * domain not equal to the search name.
+ *
+ * The leaf values are assigned to `*pval_r` and `*ival_r`
+ *
+ * Requires:
+ * \li `qpr` is a pointer to a readable qp-trie
+ * \li `name` is a pointer to a valid `dns_name_t`
+ * \li `pval_r != NULL`
+ * \li `ival_r != NULL`
+ *
+ * Returns:
+ * \li ISC_R_SUCCESS if an exact match was found
+ * \li ISC_R_PARTIALMATCH if a parent domain was found
+ * \li ISC_R_NOTFOUND if no match was found
+ */
+
isc_result_t
dns_qp_insert(dns_qp_t *qp, void *pval, uint32_t ival);
/*%<
return (QPKEY_EQUAL);
}
+/*
+ * Given a key constructed by dns_qpkey_fromname(), trim it down to the last
+ * label boundary before the `max` length.
+ *
+ * This is used when searching a trie for the best match for a name.
+ */
+static size_t
+qpkey_trim_label(dns_qpkey_t key, size_t len, size_t max) {
+ size_t stop = 0;
+ for (size_t offset = 0; offset < max; offset++) {
+ if (qpkey_bit(key, len, offset) == SHIFT_NOBYTE &&
+ qpkey_bit(key, len, offset + 1) != SHIFT_NOBYTE)
+ {
+ stop = offset + 1;
+ }
+ }
+ return (stop);
+}
+
/***********************************************************************
*
* allocator wrappers
return (dns_qp_getkey(qpr, key, keylen, pval_r, ival_r));
}
+isc_result_t
+dns_qp_findname_parent(dns_qpreadable_t qpr, const dns_name_t *name,
+ dns_qpfind_t options, 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, *twigs;
+ isc_result_t result;
+ unsigned int labels = 0;
+ struct offref {
+ uint32_t off;
+ qp_ref_t ref;
+ } label[DNS_NAME_MAXLABELS];
+
+ REQUIRE(QP_VALID(qp));
+ REQUIRE(pval_r != NULL);
+ REQUIRE(ival_r != NULL);
+
+ searchlen = dns_qpkey_fromname(search, name);
+ if ((options & DNS_QPFIND_NOEXACT) != 0) {
+ searchlen = qpkey_trim_label(search, searchlen, searchlen);
+ result = DNS_R_PARTIALMATCH;
+ } else {
+ result = ISC_R_SUCCESS;
+ }
+
+ n = get_root(qp);
+ if (n == NULL) {
+ return (ISC_R_NOTFOUND);
+ }
+
+ /*
+ * Like `dns_qp_insert()`, we must find a leaf. However, we don't make a
+ * second pass: instead, we keep track of any leaves with shorter keys
+ * 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);
+ offset = branch_key_offset(n);
+ bit = qpkey_bit(search, searchlen, offset);
+ 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);
+ }
+ if (branch_has_twig(n, bit)) {
+ n = branch_twig_ptr(qp, n, bit);
+ } else if (labels == 0) {
+ /* any twig will do */
+ n = &twigs[0];
+ } else {
+ n = ref_ptr(qp, label[labels - 1].ref);
+ break;
+ }
+ }
+
+ /* do the keys differ, and if so, where? */
+ foundlen = leaf_qpkey(qp, n, found);
+ offset = qpkey_compare(search, searchlen, found, foundlen);
+
+ if (offset == QPKEY_EQUAL || offset == foundlen) {
+ *pval_r = leaf_pval(n);
+ *ival_r = leaf_ival(n);
+ if (offset == QPKEY_EQUAL) {
+ return (result);
+ } else {
+ return (DNS_R_PARTIALMATCH);
+ }
+ }
+ while (labels-- > 0) {
+ if (offset > label[labels].off) {
+ n = ref_ptr(qp, label[labels].ref);
+ *pval_r = leaf_pval(n);
+ *ival_r = leaf_ival(n);
+ return (DNS_R_PARTIALMATCH);
+ }
+ }
+ return (ISC_R_NOTFOUND);
+}
+
/**********************************************************************/
dns_qp_destroy(&qp);
}
+static uint32_t
+no_op(void *uctx, void *pval, uint32_t ival) {
+ UNUSED(uctx);
+ UNUSED(pval);
+ UNUSED(ival);
+ return (1);
+}
+
+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 struct dns_qpmethods 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 fixed;
+ dns_name_t *name = dns_fixedname_name(&fixed);
+ void *pval = NULL;
+ uint32_t ival;
+
+#if 0
+ fprintf(stderr, "%s %u %s %s\n", check[i].query,
+ check[i].options, isc_result_totext(check[i].result),
+ check[i].found);
+#endif
+ dns_test_namefromstring(check[i].query, &fixed);
+ result = dns_qp_findname_parent(qp, name, check[i].options,
+ &pval, &ival);
+ assert_int_equal(result, check[i].result);
+ 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;
+
+ 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.", ".", "",
+ };
+
+ int i = 0;
+ 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." },
+ { 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, "." },
+ { NULL, 0, 0, NULL },
+ };
+ check_partialmatch(qp, check2);
+
+ /* what if entries in the trie are relative to the zone apex? */
+ dns_qpkey_t rootkey = { SHIFT_NOBYTE };
+ result = dns_qp_deletekey(qp, rootkey, 1);
+ assert_int_equal(result, ISC_R_SUCCESS);
+ INSIST(insert[i][0] == '\0');
+ insert_str(qp, insert[i++]);
+ check_partialmatch(qp, (struct check_partialmatch[]){
+ { "bar", 0, DNS_R_PARTIALMATCH, "" },
+ { "bar.", 0, 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