Compare commits

...

7 Commits

Author SHA1 Message Date
Matthijs Mekking
74c4d1e8f8 Refactor getpred code
Move the code to find the predecessor into one function, as it is shares
quite some similarities: In both cases we first need to find the
immediate predecessor/successor, then we need to find the immediate
predecessor if the iterator is not already pointing at it.
2023-12-07 16:55:37 +01:00
Matthijs Mekking
3346164b96 and fix yet another dns_qp_lookup() iterator bug
This one is similar to the bug when searching for a key, reaching a
dead-end branch that doesn't match, because the branch offset point
is after the point where the search key differs.

This fixes the case where we are multiple levels deep. In other
words, we had a more-than-one matches *after* the point where the
search key differs.

For example, consider the following qp-trie:

branch: "[e]", "[m]":
 - leaf: "a.b.c.d.e"
 - branch: "moo[g]", "moo[k]", "moo[n]":
   - leaf: "moog"
   - branch: "mook[e]", "mook[o]"
     - leaf: "mooker"
     - leaf: "mooko"
   - leaf: "moon"

If searching for a key "monky", we would reach the branch with
twigs "moo[k]" and "moo[n]". The key matches on the 'k' on offset=4,
and reaches the branch with twigs "mook[e]" and "mook[o]". This time
we cannot find a twig that matches our key at offset=5, there is no
twig for 'y'. The closest name we found was "mooker".

Note that on a branch it can't detect it is on a dead branch because the
key is not encapsulated in a branch node.

In the previous code we considered "mooker" to be the successor of
"monky" and so we needed to the predecessor of "mooker" to find the
predecessor for "monky". However, since the search key alread differed
before entering this branch, this is not enough. We would be left with
"moog" as the predecessor of "monky", while in this example "a.b.c.d.e"
is the actual predecessor.

Instead, we need to go up a level, find the predecessor and check
again if we are on the right branch, and repeat the process until we
are.

Unit tests to cover the scenario are now added.
2023-12-07 12:12:19 +01:00
Matthijs Mekking
6b4c329005 and fix another dns_qp_lookup() iterator bug
There was yet another edge case in which an iterator could be
positioned at the wrong node after dns_qp_lookup(). When searching for
a key, it's possible to reach a leaf that matches at the given offset,
but because the offset point is *after* the point where the search key
differs from the leaf's contents, we are now at the wrong leaf.

In other words, the bug fixed the previous commit for dead-end branches
must also be applied on matched leaves.

For example, if searching for the key "monpop", we could reach a branch
containing "moop" and "moor". the branch offset point - i.e., the point
after which the branch's leaves differ from each other - is the
fourth character ("p" or "r"). The search key matches the fourth
character "p", and takes that twig to the next node (which can be
a branch for names starting with "moop", or could be a leaf node for
"moop").

The old code failed to detect this condition, and would have
incorrectly left the iterator pointing at some successor, and not
at the predecessor of the "moop".

To find the right predecessor in this case, we need to get to the
previous branch and get the previous from there.

This has been fixed and the unit test now includes several new
scenarios for testing search names that match and unmatch on the
offset but have a different character before the offset.
2023-12-07 11:19:57 +01:00
Evan Hunt
00ec7a2c25 fixup! fix another dns_qp_lookup() iterator bug 2023-12-05 11:27:58 -08:00
Evan Hunt
5cd070fc54 fix another dns_qp_lookup() iterator bug
there was another edge case in which an iterator could be positioned at
the wrong node after dns_qp_lookup().  when searching for a key, it's
possible to reach a dead-end branch that doesn't match, because the
branch offset point is *after* the point where the search key differs
from the branch's contents.

for example, if searching for the key "mop", we could reach a branch
containing "moon" and "moor". the branch offset point - i.e., the
point after which the branch's leaves differ from each other - is the
fourth character ("n" or "r"). however, both leaves differ from the
search key at position *three* ("o" or "p"). the old code failed to
detect this condition, and would have incorrectly left the iterator
pointing at some lower value and not at "moor".

this has been fixed and the unit test now includes this scenario.
2023-12-05 09:05:47 -08:00
Evan Hunt
dd39b16a8d fix dns_qp_lookup() iterator bug
in some cases it was possible for the iterator to be positioned in the
wrong place by dns_qp_lookup(). previously, when a leaf node was found
which matched the search key at its parent branch's offset point, but
did not match after that point, the code incorrectly assumed the leaf
it had found was a successor to the searched-for name, and stepped the
iterator back to find a predecessor.  however, it was possible for the
non-matching leaf to be the predecessor, in which case stepping the
iterator back was wrong.

(for example: a branch contains "aba" and "abcd", and we are searching
for "abcde". we step down to the twig matching the letter "c" in
position 3. "abcd" is the predecessor of "abcde", so the iterator is
already correctly positioned, but because the twig was an exact match,
we would have moved it back one step to "aba".)

this previously went unnoticed due to a mistake in the qp_test unit
test, which had the wrong expected result for the test case that should
have detected the error. both the code and the test have been fixed.
2023-12-05 09:05:47 -08:00
Evan Hunt
6645ed2ba5 add an iterator argument to dns_qp_lookup()
the 'predecessor' argument to dns_qp_lookup() turns out not to
be sufficient for our needs: the predecessor node in a QP database
could have become "empty" (for the current version) because of an
update or because cache data expired, and in that case the caller
would have to iterate more than one step back to find the predecessor
node that it needs.

it may also be necessary for a caller to iterate forward, in
order to determine whether a node has any children.

for both of these reasons, we now replace the 'predecessor'
argument with an 'iter' argument. if set, this points to memory
with enough space for a dns_qpiter object.

when an exact match is found by the lookup, the iterator will be
pointing to the matching node. if not, it will be pointing to the
lexical predecessor of the nae that was searched for.

a dns_qpiter_current() method has been added for examining
the current value of the iterator without moving it in either
direction.
2023-12-05 09:05:47 -08:00
3 changed files with 309 additions and 100 deletions

View File

@@ -528,8 +528,8 @@ dns_qp_getname(dns_qpreadable_t qpr, const dns_name_t *name, void **pval_r,
isc_result_t
dns_qp_lookup(dns_qpreadable_t qpr, const dns_name_t *name,
dns_name_t *foundname, dns_name_t *predecessor,
dns_qpchain_t *chain, void **pval_r, uint32_t *ival_r);
dns_name_t *foundname, dns_qpiter_t *iter, dns_qpchain_t *chain,
void **pval_r, uint32_t *ival_r);
/*%<
* Look up a leaf in a qp-trie that is equal to, or an ancestor domain of,
* 'name'.
@@ -547,9 +547,9 @@ dns_qp_lookup(dns_qpreadable_t qpr, const dns_name_t *name,
* ISC_R_SUCCESS then it terminates at the name that was requested.
* If the result is ISC_R_NOTFOUND, 'chain' will not be updated.
*
* If 'predecessor' is not NULL, it will be updated to contain the
* closest predecessor of the searched-for name that exists in the
* trie.
* If 'iter' is not NULL, it will be updated to point to a QP iterator
* which is pointed at the searched-for name if it exists in the trie,
* or the closest predecessor if it doesn't.
*
* The leaf data for the node that was found will be assigned to
* whichever of `*pval_r` and `*ival_r` are not NULL, unless the
@@ -559,7 +559,7 @@ dns_qp_lookup(dns_qpreadable_t qpr, const dns_name_t *name,
* \li `qpr` is a pointer to a readable qp-trie
* \li `name` is a pointer to a valid `dns_name_t`
* \li `foundname` is a pointer to a valid `dns_name_t` with
* buffer and offset space available, or is NULL.
* buffer and offset space available, or is NULL
*
* Returns:
* \li ISC_R_SUCCESS if an exact match was found
@@ -666,6 +666,24 @@ dns_qpiter_prev(dns_qpiter_t *qpi, dns_name_t *name, void **pval_r,
* \li ISC_R_NOMORE otherwise
*/
isc_result_t
dns_qpiter_current(dns_qpiter_t *qpi, dns_name_t *name, void **pval_r,
uint32_t *ival_r);
/*%<
* Sets the values of `name`, `pval_r` and `ival_r` to those at the
* node currently pointed to by `qpi`, but without moving the iterator
* in either direction. If the iterator is not currently pointed at a
* leaf node, ISC_R_FAILURE is returned.
* Requires:
*
* \li `qpi` is a pointer to a valid qp iterator
*
* Returns:
* \li ISC_R_SUCCESS if a leaf was found and pval_r and ival_r were set
* \li ISC_R_FAILURE if the iterator is not initialized or not pointing
* at a leaf node
*/
void
dns_qpchain_init(dns_qpreadable_t qpr, dns_qpchain_t *chain);
/*%<
@@ -673,7 +691,7 @@ dns_qpchain_init(dns_qpreadable_t qpr, dns_qpchain_t *chain);
*
* Requires:
* \li `qpr` is a pointer to a valid qp-trie
* \li `chain` is not NULL.
* \li `chain` is not NULL
*/
unsigned int
@@ -682,7 +700,7 @@ 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.
* \li `chain` is a pointer to an initialized QP chain object
*/
void
@@ -695,8 +713,8 @@ dns_qpchain_node(dns_qpchain_t *chain, unsigned int level, dns_name_t *name,
* are not null.
*
* Requires:
* \li `chain` is a pointer to an initialized QP chain object.
* \li `level` is less than `chain->len`.
* \li `chain` is a pointer to an initialized QP chain object
* \li `level` is less than `chain->len`
*/
/***********************************************************************

View File

@@ -1948,6 +1948,24 @@ dns_qpiter_prev(dns_qpiter_t *qpi, dns_name_t *name, void **pval_r,
return (iterate(false, qpi, name, pval_r, ival_r));
}
isc_result_t
dns_qpiter_current(dns_qpiter_t *qpi, dns_name_t *name, void **pval_r,
uint32_t *ival_r) {
dns_qpnode_t *node = NULL;
REQUIRE(QPITER_VALID(qpi));
node = qpi->stack[qpi->sp];
if (node == NULL || is_branch(node)) {
return (ISC_R_FAILURE);
}
SET_IF_NOT_NULL(pval_r, leaf_pval(node));
SET_IF_NOT_NULL(ival_r, leaf_ival(node));
maybe_set_name(qpi->qp, node, name);
return (ISC_R_SUCCESS);
}
/***********************************************************************
*
* search
@@ -2020,18 +2038,142 @@ prevleaf(dns_qpiter_t *it) {
RUNTIME_CHECK(result == ISC_R_SUCCESS);
}
static inline dns_qpnode_t *
greatest_leaf(dns_qpreadable_t qpr, dns_qpnode_t *n, dns_qpiter_t *iter) {
while (is_branch(n)) {
dns_qpref_t ref = branch_twigs_ref(n) + branch_twigs_size(n) -
1;
iter->stack[++iter->sp] = n;
n = ref_ptr(qpr, ref);
}
return (n);
}
/*
* If we've been passed an iterator, we want it to point at the matching name
* in the case of an exact match, or at the predecessor name for a non-exact
* match.
*
* If the search ended at a leaf, and it was not an exact match, then we're
* now pointing at either some predecessor or some successor of the searched-for
* name. First find the immediate predecessor or successor, then we can the
* qpiter stack to step back one leaf to the predecessor (or if the iterator
* already points to the predecessor, or it was an exact match, we don't need to
* do anything).
*
* If the search ended on a branch, we also first have to find the immediate
* predecessor or successor. If there are no more suitable twig candidates in
* this branch we can again use the qpiter stack to step back one leaf to the
* predecessor. Otherwise, we select the right twig from the branch and walk
* down to the greatest leaf node.
*/
static dns_qpnode_t *
qpgetpred(dns_qpreader_t *qp, dns_qpiter_t *iter, dns_qpnode_t *n,
dns_qpkey_t search, size_t searchlen, dns_qpshift_t bit,
size_t offset) {
size_t to;
dns_qpnode_t *least = n;
dns_qpkey_t found;
size_t foundlen;
dns_qpnode_t *twigs = branch_twigs(qp, n);
/*
* Go down to a leaf node so we can compare something with
* the search key.
*/
while (is_branch(least)) {
least = branch_twigs(qp, least);
}
foundlen = leaf_qpkey(qp, least, found);
to = qpkey_compare(search, searchlen, found, foundlen);
if (to == QPKEY_EQUAL) {
return (n);
}
/*
* As long as the branch offset point is after the point where the
* search key differs, we need to branch up and find a better leaf
* node.
*/
while (to < offset) {
if (to <= searchlen && to <= foundlen && search[to] < found[to])
{
/*
* Every leaf is greater than the one we wanted, so
* go to the parent branch and iterate back to the
* predecessor from that point.
*/
iter->sp--;
prevleaf(iter);
n = iter->stack[iter->sp];
least = n;
} else {
if (is_branch(n)) {
n = greatest_leaf(qp, n, iter);
return (n);
}
break;
}
foundlen = leaf_qpkey(qp, least, found);
to = qpkey_compare(search, searchlen, found, foundlen);
}
if (is_branch(n)) {
/*
* We're on the right branch, so find the best match.
*/
dns_qpweight_t pos = branch_twig_pos(n, bit);
if (pos == 0) {
/*
* Every leaf in the branch is greater than the one we
* wanted; use the iterator to walk back to the
* predecessor.
*/
prevleaf(iter);
n = iter->stack[iter->sp--];
} else {
/*
* The name we want would've been after some twig in
* this branch. point n to that twig, then walk down
* to the highest leaf in that subtree to get
* the predecessor.
*/
n = greatest_leaf(qp, twigs + pos - 1, iter);
}
} else {
/*
* We're on the right leaf, either the iterator already points
* to the rightful predecessor, or it points to an immediate
* successor. If the latter, we can now use the qpiter stack
* we've constructed to step back to the predecessor. Otherwise,
* we don't have to do anything anymore.
*/
if (to <= searchlen && to <= foundlen && search[to] < found[to])
{
prevleaf(iter);
}
}
return (n);
}
isc_result_t
dns_qp_lookup(dns_qpreadable_t qpr, const dns_name_t *name,
dns_name_t *foundname, dns_name_t *predecessor,
dns_qpchain_t *chain, void **pval_r, uint32_t *ival_r) {
dns_name_t *foundname, dns_qpiter_t *iter, 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;
size_t offset = 0;
dns_qpnode_t *n = NULL;
dns_qpshift_t bit = SHIFT_NOBYTE;
dns_qpchain_t oc;
dns_qpiter_t it;
bool matched = true;
bool getpred = true;
REQUIRE(QP_VALID(qp));
REQUIRE(foundname == NULL || ISC_MAGIC_VALID(name, DNS_NAME_MAGIC));
@@ -2041,42 +2183,50 @@ dns_qp_lookup(dns_qpreadable_t qpr, const dns_name_t *name,
if (chain == NULL) {
chain = &oc;
}
if (iter == NULL) {
iter = &it;
getpred = false;
}
dns_qpchain_init(qp, chain);
dns_qpiter_init(qp, &it);
dns_qpiter_init(qp, iter);
n = get_root(qp);
if (n == NULL) {
return (ISC_R_NOTFOUND);
}
it.stack[0] = n;
iter->stack[0] = n;
/*
* 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.)
* 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.)
*/
while (is_branch(n)) {
prefetch_twigs(qp, n);
offset = branch_key_offset(n);
dns_qpshift_t bit = qpkey_bit(search, searchlen, offset);
bit = qpkey_bit(search, searchlen, offset);
dns_qpnode_t *twigs = branch_twigs(qp, n);
/*
* 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.
* 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 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: If SHIFT_NOBYTE twig is present, it will always
* be in position 0, the first localtion in 'twigs'.
* Note 2: If SHIFT_NOBYTE twig is present, it
* will always be in position 0, the first
* localtion in 'twigs'.
*/
if (bit != SHIFT_NOBYTE && branch_has_twig(n, SHIFT_NOBYTE) &&
qpkey_bit(search, searchlen, offset - 1) == SHIFT_NOBYTE &&
@@ -2088,54 +2238,37 @@ dns_qp_lookup(dns_qpreadable_t qpr, const dns_name_t *name,
matched = branch_has_twig(n, bit);
if (matched) {
/*
* found a match: if it's a branch, we keep
* searching, and if it's a leaf, we drop out of
* the loop.
* found a match: if it's a branch, we
* keep searching, and if it's a leaf,
* we drop out of the loop.
*/
n = branch_twig_ptr(qp, n, bit);
} else if (predecessor != NULL) {
} else if (getpred) {
/*
* this branch is a dead end, but the caller wants
* the predecessor to the name we were searching
* for, so let's go find that.
* this branch is a dead end. however,
* the caller passed us an iterator, so
* we need to find the predecessor of
* the searched-for-name. first step:
* find out if we've overshot the search
* key; we do that by finding an
* arbitrary leaf to compare against.
*/
dns_qpweight_t pos = branch_twig_pos(n, bit);
if (pos == 0) {
/*
* this entire branch is greater than
* the key we wanted, so we step back to
* the predecessor using the iterator.
*/
prevleaf(&it);
n = it.stack[it.sp];
} else {
/*
* the name we want would've been between
* two twigs in this branch. point n to the
* lesser of those, then walk down to the
* highest leaf in that subtree to get the
* predecessor.
*/
n = twigs + pos - 1;
while (is_branch(n)) {
prefetch_twigs(qp, n);
it.stack[++it.sp] = n;
pos = branch_twigs_size(n) - 1;
n = ref_ptr(qp,
branch_twigs_ref(n) + pos);
}
}
n = qpgetpred(qp, iter, n, search, searchlen, bit,
offset);
} else {
/*
* this branch is a dead end, and the predecessor
* doesn't matter. now we just need to find a leaf
* to end on so qpkey_leaf() will work below.
* this branch is a dead end, and the
* predecessor doesn't matter. now we
* just need to find a leaf to end on so
* qpkey_leaf() will work below.
*/
if (chain->len > 0) {
/* we saved an ancestor leaf: use that */
/* we saved an ancestor leaf:
* use that */
n = chain->chain[chain->len - 1].node;
} else {
/* walk down to find the leftmost leaf */
/* walk down to find the
* leftmost leaf */
while (is_branch(twigs)) {
twigs = branch_twigs(qp, twigs);
}
@@ -2143,29 +2276,17 @@ dns_qp_lookup(dns_qpreadable_t qpr, const dns_name_t *name,
}
}
it.stack[++it.sp] = n;
iter->stack[++iter->sp] = n;
}
if (getpred && matched) {
n = qpgetpred(qp, iter, n, search, searchlen, bit, offset);
}
/* do the keys differ, and if so, where? */
foundlen = leaf_qpkey(qp, n, found);
offset = qpkey_compare(search, searchlen, found, foundlen);
/*
* if we've been asked to return the predecessor name, we
* work that out here.
*
* if 'matched' is true, the search ended at a leaf. it's either
* an exact match or the immediate successor of the searched-for
* name, and in either case, we can use the qpiter stack we've
* constructed to step back to the predecessor. if 'matched' is
* false, then the search failed at a branch node, and we would
* have already found the predecessor.
*/
if (predecessor != NULL && matched) {
prevleaf(&it);
}
maybe_set_name(qp, it.stack[it.sp], predecessor);
if (offset == QPKEY_EQUAL || offset == foundlen) {
SET_IF_NOT_NULL(pval_r, leaf_pval(n));
SET_IF_NOT_NULL(ival_r, leaf_ival(n));
@@ -2192,9 +2313,10 @@ dns_qp_lookup(dns_qpreadable_t qpr, const dns_name_t *name,
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.
* oops, during the search we found and
* added a leaf that's longer than the
* requested name; remove it from the
* chain.
*/
chain->len--;
}

View File

@@ -204,6 +204,7 @@ ISC_RUN_TEST_IMPL(qpiter) {
int inserted, n;
uint32_t ival;
void *pval = NULL;
isc_result_t result;
dns_qp_create(mctx, &qpiter_methods, item, &qp);
for (size_t tests = 0; tests < 1234; tests++) {
@@ -253,15 +254,30 @@ ISC_RUN_TEST_IMPL(qpiter) {
while (dns_qpiter_prev(&qpi, NULL, NULL, &ival) ==
ISC_R_SUCCESS)
{
assert_int_equal(ival, order[--n]);
--n;
assert_int_equal(ival, order[n]);
/* and check current iterator value as well */
result = dns_qpiter_current(&qpi, NULL, NULL, &ival);
assert_int_equal(result, ISC_R_SUCCESS);
assert_int_equal(ival, order[n]);
}
assert_int_equal(n, 0);
/* ...and forward again */
while (dns_qpiter_next(&qpi, NULL, NULL, &ival) ==
ISC_R_SUCCESS)
{
assert_int_equal(ival, order[n++]);
assert_int_equal(ival, order[n]);
/* and check current iterator value as well */
result = dns_qpiter_current(&qpi, NULL, NULL, &ival);
assert_int_equal(result, ISC_R_SUCCESS);
assert_int_equal(ival, order[n]);
n++;
}
assert_int_equal(n, inserted);
@@ -575,16 +591,36 @@ check_predecessors(dns_qp_t *qp, struct check_predecessors check[]) {
dns_name_t *pred = dns_fixedname_initname(&fn2);
for (int i = 0; check[i].query != NULL; i++) {
dns_qpiter_t it;
char *predname = NULL;
dns_test_namefromstring(check[i].query, &fn1);
result = dns_qp_lookup(qp, name, NULL, pred, NULL, NULL, NULL);
result = dns_qp_lookup(qp, name, NULL, &it, NULL, NULL, NULL);
#if 0
fprintf(stderr, "%s: expected %s got %s\n", check[i].query,
isc_result_totext(check[i].result),
isc_result_totext(result));
#endif
assert_int_equal(result, check[i].result);
if (result == ISC_R_SUCCESS) {
/*
* we found an exact match; iterate to find
* the predecessor.
*/
result = dns_qpiter_prev(&it, pred, NULL, NULL);
if (result == ISC_R_NOMORE) {
result = dns_qpiter_prev(&it, pred, NULL, NULL);
}
} else {
/*
* we didn't find a match, so the iterator should
* already be pointed at the predecessor node.
*/
result = dns_qpiter_current(&it, pred, NULL, NULL);
}
assert_int_equal(result, ISC_R_SUCCESS);
result = dns_name_tostring(pred, &predname, mctx);
#if 0
fprintf(stderr, "... expected predecessor %s got %s\n",
@@ -602,7 +638,8 @@ ISC_RUN_TEST_IMPL(predecessors) {
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.",
"b.a.", "x.k.c.d.", ""
"b.a.", "x.k.c.d.", "moog.", "mooker.",
"mooko.", "moon.", "moops.", ""
};
int i = 0;
@@ -613,8 +650,8 @@ ISC_RUN_TEST_IMPL(predecessors) {
/* first check: no root label in the database */
static struct check_predecessors check1[] = {
{ ".", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "a.", "a.b.c.d.e.", ISC_R_SUCCESS },
{ ".", "moops.", ISC_R_NOTFOUND },
{ "a.", "moops.", ISC_R_SUCCESS },
{ "b.a.", "a.", ISC_R_SUCCESS },
{ "b.", "e.d.c.b.a.", ISC_R_SUCCESS },
{ "aaa.a.", "a.", DNS_R_PARTIALMATCH },
@@ -622,13 +659,29 @@ ISC_RUN_TEST_IMPL(predecessors) {
{ "d.c.", "c.b.b.", ISC_R_NOTFOUND },
{ "1.2.c.b.a.", "c.b.a.", DNS_R_PARTIALMATCH },
{ "a.b.c.e.f.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "z.y.x.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "z.y.x.", "moops.", ISC_R_NOTFOUND },
{ "w.c.d.", "x.k.c.d.", DNS_R_PARTIALMATCH },
{ "z.z.z.z.k.c.d.", "a.b.c.d.", DNS_R_PARTIALMATCH },
{ "z.z.z.z.k.c.d.", "x.k.c.d.", DNS_R_PARTIALMATCH },
{ "w.k.c.d.", "a.b.c.d.", DNS_R_PARTIALMATCH },
{ "d.a.", "e.d.c.b.a.", DNS_R_PARTIALMATCH },
{ "0.b.c.d.e.", "x.k.c.d.", ISC_R_NOTFOUND },
{ "b.d.", "c.b.b.", ISC_R_NOTFOUND },
{ "moor.", "moops.", ISC_R_NOTFOUND },
{ "mopbop.", "moops.", ISC_R_NOTFOUND },
{ "moppop.", "moops.", ISC_R_NOTFOUND },
{ "mopps.", "moops.", ISC_R_NOTFOUND },
{ "mopzop.", "moops.", ISC_R_NOTFOUND },
{ "mop.", "moops.", ISC_R_NOTFOUND },
{ "monbop.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "monpop.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "monps.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "monzop.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "mon.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "moop.", "moon.", ISC_R_NOTFOUND },
{ "moopser.", "moops.", ISC_R_NOTFOUND },
{ "monky.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "monkey.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ "monker.", "a.b.c.d.e.", ISC_R_NOTFOUND },
{ NULL, NULL, 0 }
};
@@ -639,7 +692,7 @@ ISC_RUN_TEST_IMPL(predecessors) {
insert_str(qp, root);
static struct check_predecessors check2[] = {
{ ".", "a.b.c.d.e.", ISC_R_SUCCESS },
{ ".", "moops.", ISC_R_SUCCESS },
{ "a.", ".", ISC_R_SUCCESS },
{ "b.a.", "a.", ISC_R_SUCCESS },
{ "b.", "e.d.c.b.a.", ISC_R_SUCCESS },
@@ -648,12 +701,28 @@ ISC_RUN_TEST_IMPL(predecessors) {
{ "d.c.", "c.b.b.", DNS_R_PARTIALMATCH },
{ "1.2.c.b.a.", "c.b.a.", DNS_R_PARTIALMATCH },
{ "a.b.c.e.f.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "z.y.x.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "z.y.x.", "moops.", DNS_R_PARTIALMATCH },
{ "w.c.d.", "x.k.c.d.", DNS_R_PARTIALMATCH },
{ "z.z.z.z.k.c.d.", "a.b.c.d.", DNS_R_PARTIALMATCH },
{ "z.z.z.z.k.c.d.", "x.k.c.d.", DNS_R_PARTIALMATCH },
{ "w.k.c.d.", "a.b.c.d.", DNS_R_PARTIALMATCH },
{ "d.a.", "e.d.c.b.a.", DNS_R_PARTIALMATCH },
{ "0.b.c.d.e.", "x.k.c.d.", DNS_R_PARTIALMATCH },
{ "moor.", "moops.", DNS_R_PARTIALMATCH },
{ "mopbop.", "moops.", DNS_R_PARTIALMATCH },
{ "moppop.", "moops.", DNS_R_PARTIALMATCH },
{ "mopps.", "moops.", DNS_R_PARTIALMATCH },
{ "mopzop.", "moops.", DNS_R_PARTIALMATCH },
{ "mop.", "moops.", DNS_R_PARTIALMATCH },
{ "monbop.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "monpop.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "monps.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "monzop.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "mon.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "moop.", "moon.", DNS_R_PARTIALMATCH },
{ "moopser.", "moops.", DNS_R_PARTIALMATCH },
{ "monky.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "monkey.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ "monker.", "a.b.c.d.e.", DNS_R_PARTIALMATCH },
{ NULL, NULL, 0 }
};