From: Mukund Sivaraman Date: Thu, 29 May 2014 05:31:57 +0000 (+0530) Subject: [35904] Add various RBT unit tests X-Git-Tag: v9.11.0a1~1596 X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=ce376a81fa674d240197628ceb6113a4fa5a1ab3;p=thirdparty%2Fbind9.git [35904] Add various RBT unit tests No CHANGES entry was added as this commit mainly adds tests related code. Squashed commit of the following: commit d3d44508daa128fb8b60f64b3a8c81f80602273d Author: Evan Hunt Date: Wed May 7 09:36:41 2014 -0700 [rt35904] remove private non-static names from .def file commit dbca45661c3939f21c3bb3f405d08cfe1b35d7aa Author: Mukund Sivaraman Date: Wed May 7 21:39:32 2014 +0530 Remove test for shortcut findnode() The implementation was not included in this review branch, but the tests erroneously made it through. This functionality will be addressed in a different ticket (RT#35906). commit 94ff14576ab3407f2612d34727b7eacfefc3668c Author: Mukund Sivaraman Date: Wed May 7 21:36:50 2014 +0530 Minor indent fix commit 50972f17697bb222996e433faa8224843366f9b2 Author: Evan Hunt Date: Tue May 6 20:05:21 2014 -0700 [rt35904] style commit 5c4d5d41fcc5bfecdeebc008896974385c841b8d Author: Mukund Sivaraman Date: Sun May 4 19:19:36 2014 +0530 RBT related updates * Add various RBT unit tests * Add some helper methods useful in unit testing RBT code * General cleanup --- diff --git a/lib/dns/include/dns/rbt.h b/lib/dns/include/dns/rbt.h index f1a4ee89e9b..34c7f7ef961 100644 --- a/lib/dns/include/dns/rbt.h +++ b/lib/dns/include/dns/rbt.h @@ -726,10 +726,14 @@ dns_rbt_deserialize_tree(void *base_address, size_t filesize, */ void -dns_rbt_printall(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *)); +dns_rbt_printtext(dns_rbt_t *rbt, + void (*data_printer)(FILE *, void *), FILE *f); /*%< * Print an ASCII representation of the internal structure of the red-black - * tree of trees. + * tree of trees to the passed stream. + * + * data_printer is a callback function that is called to print the data + * in a node. It should print it to the passed FILE stream. * * Notes: * \li The name stored at each node, along with the node's color, is printed. @@ -739,9 +743,69 @@ dns_rbt_printall(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *)); */ void -dns_rbt_printnodeinfo(dns_rbtnode_t *n); +dns_rbt_printdot(dns_rbt_t *rbt, isc_boolean_t show_pointers, FILE *f); +/*%< + * Print a GraphViz dot representation of the internal structure of the + * red-black tree of trees to the passed stream. + * + * If show_pointers is TRUE, pointers are also included in the generated + * graph. + * + * Notes: + * \li The name stored at each node, along with the node's color is displayed. + * Then the down pointer, left and right pointers are displayed + * recursively in turn. NULL left, right and down pointers are + * silently omitted. + */ + +void +dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f); /*%< * Print out various information about a node + * + * Requires: + *\li 'n' is a valid pointer. + * + *\li 'f' points to a valid open FILE structure that allows writing. + */ + + +size_t +dns__rbt_getheight(dns_rbt_t *rbt); +/*%< + * Return the maximum height of sub-root nodes found in the red-black + * forest. + * + * The height of a node is defined as the number of nodes in the longest + * path from the node to a leaf. For each subtree in the forest, this + * function determines the height of its root node. Then it returns the + * maximum such height in the forest. + * + * Note: This function exists for testing purposes. Non-test code must + * not use it. + * + * Requires: + * \li rbt is a valid rbt manager. + */ + +isc_boolean_t +dns__rbt_checkproperties(dns_rbt_t *rbt); +/*%< + * Check red-black properties of the forest. + * + * Note: This function exists for testing purposes. Non-test code must + * not use it. + * + * Requires: + * \li rbt is a valid rbt manager. + */ + +size_t +dns__rbtnode_getdistance(dns_rbtnode_t *node); +/*%< + * Return the distance (in nodes) from the node to its upper node of its + * subtree. The root node has a distance of 1. A child of the root node + * has a distance of 2. */ /***** @@ -1036,6 +1100,12 @@ dns_rbtnode_refdecrement(dns_rbtnode_t *node, unsigned int *refs) { #endif #endif /* DNS_RBT_USEISCREFCOUNT */ +void +dns_rbtnode_nodename(dns_rbtnode_t *node, dns_name_t *name); + +dns_rbtnode_t * +dns_rbt_root(dns_rbt_t *rbt); + ISC_LANG_ENDDECLS #endif /* DNS_RBT_H */ diff --git a/lib/dns/rbt.c b/lib/dns/rbt.c index 4e3b51fd164..4cefa8833e5 100644 --- a/lib/dns/rbt.c +++ b/lib/dns/rbt.c @@ -209,6 +209,7 @@ getdata(dns_rbtnode_t *node, file_header_t *header) { #define RIGHT(node) ((node)->right) #define DOWN(node) ((node)->down) #define DATA(node) ((node)->data) +#define IS_EMPTY(node) ((node)->data == NULL) #define HASHNEXT(node) ((node)->hashnext) #define HASHVAL(node) ((node)->hashval) #define COLOR(node) ((node)->color) @@ -280,6 +281,21 @@ NODENAME(dns_rbtnode_t *node, dns_name_t *name) { name->attributes |= DNS_NAMEATTR_READONLY; } +void +dns_rbtnode_nodename(dns_rbtnode_t *node, dns_name_t *name) { + name->length = NAMELEN(node); + name->labels = OFFSETLEN(node); + name->ndata = NAME(node); + name->offsets = OFFSETS(node); + name->attributes = ATTRS(node); + name->attributes |= DNS_NAMEATTR_READONLY; +} + +dns_rbtnode_t * +dns_rbt_root(dns_rbt_t *rbt) { + return rbt->root; +} + #ifdef DNS_RBT_USEHASH static isc_result_t inithash(dns_rbt_t *rbt); @@ -302,8 +318,6 @@ Name(dns_rbtnode_t *node) { return (name); } -static void printnodename(dns_rbtnode_t *node); - static void hexdump(const char *desc, unsigned char *data, size_t size) { char hexdump[BUFSIZ * 2 + 1]; @@ -326,23 +340,47 @@ hexdump(const char *desc, unsigned char *data, size_t size) { } while (size > 0); fprintf(stderr, "\n"); } -#endif +#endif /* DEBUG */ + +/* The passed node must not be NULL. */ +static inline dns_rbtnode_t * +get_subtree_root(dns_rbtnode_t *node) { + while (!IS_ROOT(node)) { + node = PARENT(node); + } + + return (node); +} +/* Upper node is the parent of the root of the passed node's + * subtree. The passed node must not be NULL. + */ static inline dns_rbtnode_t * -find_up(dns_rbtnode_t *node) { - dns_rbtnode_t *root; +get_upper_node(dns_rbtnode_t *node) { + dns_rbtnode_t *root = get_subtree_root(node); /* * Return the node in the level above the argument node that points * to the level the argument node is in. If the argument node is in * the top level, the return value is NULL. */ - for (root = node; ! IS_ROOT(root); root = PARENT(root)) - ; /* Nothing. */ - return (PARENT(root)); } +size_t +dns__rbtnode_getdistance(dns_rbtnode_t *node) { + size_t nodes = 1; + + while (node != NULL) { + if (IS_ROOT(node)) + break; + nodes++; + node = PARENT(node); + } + + return (nodes); +} + /* * Forward declarations. */ @@ -387,7 +425,7 @@ static void deletetreeflat(dns_rbt_t *rbt, unsigned int quantum, dns_rbtnode_t **nodep); static void -printnodename(dns_rbtnode_t *node); +printnodename(dns_rbtnode_t *node, isc_boolean_t quoted, FILE *f); static void freenode(dns_rbt_t *rbt, dns_rbtnode_t **nodep); @@ -1371,15 +1409,14 @@ dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, if (rbt->root == NULL) return (ISC_R_NOTFOUND); - else { - /* - * Appease GCC about variables it incorrectly thinks are - * possibly used uninitialized. - */ - compared = dns_namereln_none; - last_compared = NULL; - order = 0; - } + + /* + * Appease GCC about variables it incorrectly thinks are + * possibly used uninitialized. + */ + compared = dns_namereln_none; + last_compared = NULL; + order = 0; dns_fixedname_init(&fixedcallbackname); callback_name = dns_fixedname_name(&fixedcallbackname); @@ -1405,6 +1442,12 @@ dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, NODENAME(current, ¤t_name); compared = dns_name_fullcompare(search_name, ¤t_name, &order, &common_labels); + /* + * last_compared is used as a shortcut to start (or + * continue rather) finding the stop-node of the search + * when hashing was used (see much below in this + * function). + */ last_compared = current; if (compared == dns_namereln_equal) @@ -1412,6 +1455,16 @@ dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, if (compared == dns_namereln_none) { #ifdef DNS_RBT_USEHASH + /* + * Here, current is pointing at a subtree root + * node. We try to find a matching node using + * the hashtable. We can get one of 3 results + * here: (a) we locate the matching node, (b) we + * find a node to which the current node has a + * subdomain relation, (c) we fail to find (a) + * or (b). + */ + dns_name_t hash_name; dns_rbtnode_t *hnode; dns_rbtnode_t *up_current; @@ -1446,7 +1499,12 @@ dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, hashagain: /* - * Hash includes tail. + * Compute the hash over the full absolute + * name. Look for the smallest suffix match at + * this tree level (hlevel), and then at every + * iteration, look for the next smallest suffix + * match (add another subdomain label to the + * absolute name being hashed). */ dns_name_getlabelsequence(name, nlabels - tlabels, @@ -1457,6 +1515,10 @@ dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, nlabels - tlabels, tlabels, &hash_name); + /* + * Walk all the nodes in the hash bucket pointed + * by the computed hash value. + */ for (hnode = rbt->hashtable[hash % rbt->hashsize]; hnode != NULL; hnode = hnode->hashnext) @@ -1465,8 +1527,16 @@ dns_rbt_findnode(dns_rbt_t *rbt, dns_name_t *name, dns_name_t *foundname, if (hash != HASHVAL(hnode)) continue; - if (find_up(hnode) != up_current) + /* + * This checks that the hashed label + * sequence being looked up is at the + * same tree level, so that we don't + * match a labelsequence from some other + * subdomain. + */ + if (get_upper_node(hnode) != up_current) continue; + dns_name_init(&hnode_name, NULL); NODENAME(hnode, &hnode_name); if (dns_name_equal(&hnode_name, &hash_name)) @@ -1971,7 +2041,7 @@ dns_rbt_deletenode(dns_rbt_t *rbt, dns_rbtnode_t *node, isc_boolean_t recurse) * deleted. If the deleted node is the top level, parent will be set * to NULL. */ - parent = find_up(node); + parent = get_upper_node(node); /* * This node now has no down pointer (either because it didn't @@ -2051,7 +2121,7 @@ dns_rbt_fullnamefromnode(dns_rbtnode_t *node, dns_name_t *name) { if (result != ISC_R_SUCCESS) break; - node = find_up(node); + node = get_upper_node(node); } while (! dns_name_isabsolute(name)); return (result); @@ -2799,42 +2869,140 @@ deletetreeflat(dns_rbt_t *rbt, unsigned int quantum, dns_rbtnode_t **nodep) { goto again; } +static size_t +getheight_helper(dns_rbtnode_t *node) { + size_t dl, dr; + size_t this_height, down_height; + + if (node == NULL) + return (0); + + dl = getheight_helper(LEFT(node)); + dr = getheight_helper(RIGHT(node)); + + this_height = ISC_MAX(dl + 1, dr + 1); + down_height = getheight_helper(DOWN(node)); + + return (ISC_MAX(this_height, down_height)); +} + +size_t +dns__rbt_getheight(dns_rbt_t *rbt) { + return (getheight_helper(rbt->root)); +} + +static isc_boolean_t +check_properties_helper(dns_rbtnode_t *node) { + if (node == NULL) + return (ISC_TRUE); + + if (IS_RED(node)) { + /* Root nodes must be BLACK. */ + if (IS_ROOT(node)) + return (ISC_FALSE); + + /* Both children of RED nodes must be BLACK. */ + if (IS_RED(LEFT(node)) || IS_RED(RIGHT(node))) + return (ISC_FALSE); + } + + /* If node is assigned to the down_ pointer of its parent, it is + * a subtree root and must have the flag set. + */ + if (((!PARENT(node)) || + (DOWN(PARENT(node)) == node)) && + (!IS_ROOT(node))) + { + return (ISC_FALSE); + } + + /* Repeat tests with this node's children. */ + return (check_properties_helper(LEFT(node)) && + check_properties_helper(RIGHT(node)) && + check_properties_helper(DOWN(node))); +} + +static isc_boolean_t +check_black_distance_helper(dns_rbtnode_t *node, size_t *distance) { + size_t dl, dr, dd; + + if (node == NULL) { + *distance = 1; + return (ISC_TRUE); + } + + if (!check_black_distance_helper(LEFT(node), &dl)) + return (ISC_FALSE); + + if (!check_black_distance_helper(RIGHT(node), &dr)) + return (ISC_FALSE); + + if (!check_black_distance_helper(DOWN(node), &dd)) + return (ISC_FALSE); + + /* Left and right side black node counts must match. */ + if (dl != dr) + return (ISC_FALSE); + + if (IS_BLACK(node)) + dl++; + + *distance = dl; + + return (ISC_TRUE); +} + +isc_boolean_t +dns__rbt_checkproperties(dns_rbt_t *rbt) { + size_t dd; + + if (!check_properties_helper(rbt->root)) + return (ISC_FALSE); + + /* Path from a given node to all its leaves must contain the + * same number of BLACK child nodes. This is done separately + * here instead of inside check_properties_helper() as + * it would take (n log n) complexity otherwise. + */ + return (check_black_distance_helper(rbt->root, &dd)); +} + static void -dns_rbt_indent(int depth) { +dns_rbt_indent(FILE *f, int depth) { int i; - printf("%4d ", depth); + fprintf(f, "%4d ", depth); for (i = 0; i < depth; i++) - printf("- "); + fprintf(f, "- "); } void -dns_rbt_printnodeinfo(dns_rbtnode_t *n) { - printf("Node info for nodename: "); - printnodename(n); - printf("\n"); +dns_rbt_printnodeinfo(dns_rbtnode_t *n, FILE *f) { + fprintf(f, "Node info for nodename: "); + printnodename(n, ISC_TRUE, f); + fprintf(f, "\n"); - printf("n = %p\n", n); + fprintf(f, "n = %p\n", n); - printf("Relative pointers: %s%s%s%s%s\n", + fprintf(f, "Relative pointers: %s%s%s%s%s\n", (n->parent_is_relative == 1 ? " P" : ""), (n->right_is_relative == 1 ? " R" : ""), (n->left_is_relative == 1 ? " L" : ""), (n->down_is_relative == 1 ? " D" : ""), (n->data_is_relative == 1 ? " T" : "")); - printf("node lock address = %d\n", n->locknum); + fprintf(f, "node lock address = %d\n", n->locknum); - printf("Parent: %p\n", n->parent); - printf("Right: %p\n", n->right); - printf("Left: %p\n", n->left); - printf("Down: %p\n", n->down); - printf("daTa: %p\n", n->data); + fprintf(f, "Parent: %p\n", n->parent); + fprintf(f, "Right: %p\n", n->right); + fprintf(f, "Left: %p\n", n->left); + fprintf(f, "Down: %p\n", n->down); + fprintf(f, "daTa: %p\n", n->data); } static void -printnodename(dns_rbtnode_t *node) { +printnodename(dns_rbtnode_t *node, isc_boolean_t quoted, FILE *f) { isc_region_t r; dns_name_t name; char buffer[DNS_NAME_FORMATSIZE]; @@ -2848,65 +3016,135 @@ printnodename(dns_rbtnode_t *node) { dns_name_format(&name, buffer, sizeof(buffer)); - printf("\"%s\"", buffer); + if (quoted) + fprintf(f, "\"%s\"", buffer); + else + fprintf(f, "%s", buffer); } static void -dns_rbt_printtree(dns_rbtnode_t *root, dns_rbtnode_t *parent, +print_text_helper(dns_rbtnode_t *root, dns_rbtnode_t *parent, int depth, const char *direction, - void (*data_printer)(FILE *, void *)) + void (*data_printer)(FILE *, void *), FILE *f) { - dns_rbt_indent(depth); + dns_rbt_indent(f, depth); if (root != NULL) { - printnodename(root); - printf(" (%s, %s", direction, IS_RED(root) ? "RED" : "BLACK"); + printnodename(root, ISC_TRUE, f); + fprintf(f, " (%s, %s", direction, + IS_RED(root) ? "RED" : "BLACK"); if ((! IS_ROOT(root) && PARENT(root) != parent) || ( IS_ROOT(root) && depth > 0 && DOWN(PARENT(root)) != root)) { - printf(" (BAD parent pointer! -> "); + fprintf(f, " (BAD parent pointer! -> "); if (PARENT(root) != NULL) - printnodename(PARENT(root)); + printnodename(PARENT(root), ISC_TRUE, f); else - printf("NULL"); - printf(")"); + fprintf(f, "NULL"); + fprintf(f, ")"); } - printf(")"); + fprintf(f, ")"); if (root->data != NULL && data_printer != NULL) { - printf(" data@%p: ", root->data); - data_printer(stdout, root->data); + fprintf(f, " data@%p: ", root->data); + data_printer(f, root->data); } - printf("\n"); + fprintf(f, "\n"); depth++; if (IS_RED(root) && IS_RED(LEFT(root))) - printf("** Red/Red color violation on left\n"); - dns_rbt_printtree(LEFT(root), root, depth, "left", - data_printer); + fprintf(f, "** Red/Red color violation on left\n"); + print_text_helper(LEFT(root), root, depth, "left", + data_printer, f); if (IS_RED(root) && IS_RED(RIGHT(root))) - printf("** Red/Red color violation on right\n"); - dns_rbt_printtree(RIGHT(root), root, depth, "right", - data_printer); + fprintf(f, "** Red/Red color violation on right\n"); + print_text_helper(RIGHT(root), root, depth, "right", + data_printer, f); - dns_rbt_printtree(DOWN(root), NULL, depth, "down", - data_printer); + print_text_helper(DOWN(root), NULL, depth, "down", + data_printer, f); } else { - printf("NULL (%s)\n", direction); + fprintf(f, "NULL (%s)\n", direction); } } void -dns_rbt_printall(dns_rbt_t *rbt, void (*data_printer)(FILE *, void *)) { +dns_rbt_printtext(dns_rbt_t *rbt, + void (*data_printer)(FILE *, void *), FILE *f) +{ + REQUIRE(VALID_RBT(rbt)); + + print_text_helper(rbt->root, NULL, 0, "root", data_printer, f); +} + +static int +print_dot_helper(dns_rbtnode_t *node, unsigned int *nodecount, + isc_boolean_t show_pointers, FILE *f) +{ + unsigned int l, r, d; + + if (node == NULL) + return (0); + + l = print_dot_helper(LEFT(node), nodecount, show_pointers, f); + r = print_dot_helper(RIGHT(node), nodecount, show_pointers, f); + d = print_dot_helper(DOWN(node), nodecount, show_pointers, f); + + *nodecount += 1; + + fprintf(f, "node%u[label = \" | ", *nodecount); + printnodename(node, ISC_FALSE, f); + fprintf(f, "|"); + + if (show_pointers) + fprintf(f, "| n=%p| p=%p", node, PARENT(node)); + + fprintf(f, "\"] ["); + + if (IS_RED(node)) + fprintf(f, "color=red"); + else + fprintf(f, "color=black"); + + /* XXXMUKS: verify that IS_ROOT() indicates subtree root and not + * forest root. + */ + if (IS_ROOT(node)) + fprintf(f, ",penwidth=3"); + + if (IS_EMPTY(node)) + fprintf(f, ",style=filled,fillcolor=lightgrey"); + + fprintf(f, "];\n"); + + if (LEFT(node) != NULL) + fprintf(f, "\"node%u\":f0 -> \"node%u\":f1;\n", *nodecount, l); + + if (DOWN(node) != NULL) + fprintf(f, "\"node%u\":f1 -> \"node%u\":f1 [penwidth=5];\n", + *nodecount, d); + + if (RIGHT(node) != NULL) + fprintf(f, "\"node%u\":f2 -> \"node%u\":f1;\n", *nodecount, r); + + return (*nodecount); +} + +void +dns_rbt_printdot(dns_rbt_t *rbt, isc_boolean_t show_pointers, FILE *f) { + unsigned int nodecount = 0; REQUIRE(VALID_RBT(rbt)); - dns_rbt_printtree(rbt->root, NULL, 0, "root", data_printer); + fprintf(f, "digraph g {\n"); + fprintf(f, "node [shape = record,height=.1];\n"); + print_dot_helper(rbt->root, &nodecount, show_pointers, f); + fprintf(f, "}\n"); } /* @@ -3384,3 +3622,13 @@ dns_rbtnodechain_invalidate(dns_rbtnodechain_t *chain) { chain->magic = 0; } + +/* XXXMUKS: + * + * - worth removing inline as static functions are inlined automatically + * where suitable by modern compilers. + * - bump the size of dns_rbt.nodecount to size_t. + * - the dumpfile header also contains a nodecount that is unsigned + * int. If large files (> 2^32 nodes) are to be supported, the + * allocation for this field should be increased. + */ diff --git a/lib/dns/tests/Makefile.in b/lib/dns/tests/Makefile.in index f89dc84c653..824476714f3 100644 --- a/lib/dns/tests/Makefile.in +++ b/lib/dns/tests/Makefile.in @@ -49,6 +49,7 @@ SRCS = db_test.c \ nsec3_test.c \ private_test.c \ rbt_test.c \ + rbt_serialize_test.c \ rdata_test.c \ rdataset_test.c \ rdatasetstats_test.c \ @@ -69,6 +70,7 @@ TARGETS = db_test@EXEEXT@ \ nsec3_test@EXEEXT@ \ private_test@EXEEXT@ \ rbt_test@EXEEXT@ \ + rbt_serialize_test@EXEEXT@ \ rdata_test@EXEEXT@ \ rdataset_test@EXEEXT@ \ rdatasetstats_test@EXEEXT@ \ @@ -158,6 +160,11 @@ rbt_test@EXEEXT@: rbt_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} rbt_test.@O@ dnstest.@O@ ${DNSLIBS} \ ${ISCLIBS} ${LIBS} +rbt_serialize_test@EXEEXT@: rbt_serialize_test.@O@ dnstest.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + rbt_serialize_test.@O@ dnstest.@O@ ${DNSLIBS} \ + ${ISCLIBS} ${LIBS} + rdata_test@EXEEXT@: rdata_test.@O@ ${ISCDEPLIBS} ${DNSDEPLIBS} ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ rdata_test.@O@ ${DNSLIBS} \ diff --git a/lib/dns/tests/rbt_serialize_test.c b/lib/dns/tests/rbt_serialize_test.c new file mode 100644 index 00000000000..80b5125d921 --- /dev/null +++ b/lib/dns/tests/rbt_serialize_test.c @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2012-2014 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* $Id: rbt_test.c,v 1.1.14.8 2012/02/10 16:24:37 ckb Exp $ */ + +/* ! \file */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_INTTYPES_H +#include /* uintptr_t */ +#endif + +#include +#include +#include +#include +#include "dnstest.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif + +typedef struct data_holder { + int len; + const char *data; +} data_holder_t; + +typedef struct rbt_testdata { + const char *name; + size_t name_len; + data_holder_t data; +} rbt_testdata_t; + +#define DATA_ITEM(name) { (name), sizeof(name) - 1, { sizeof(name), (name) } } + +rbt_testdata_t testdata[] = { + DATA_ITEM("first.com."), + DATA_ITEM("one.net."), + DATA_ITEM("two.com."), + DATA_ITEM("three.org."), + DATA_ITEM("asdf.com."), + DATA_ITEM("ghjkl.com."), + DATA_ITEM("1.edu."), + DATA_ITEM("2.edu."), + DATA_ITEM("3.edu."), + DATA_ITEM("123.edu."), + DATA_ITEM("1236.com."), + DATA_ITEM("and_so_forth.com."), + DATA_ITEM("thisisalongname.com."), + DATA_ITEM("a.b."), + DATA_ITEM("test.net."), + DATA_ITEM("whoknows.org."), + DATA_ITEM("blargh.com."), + DATA_ITEM("www.joe.com."), + DATA_ITEM("test.com."), + DATA_ITEM("isc.org."), + DATA_ITEM("uiop.mil."), + DATA_ITEM("last.fm."), + { NULL, 0, { 0, NULL } } +}; + +static void +delete_data(void *data, void *arg) { + UNUSED(arg); + UNUSED(data); +} + +static isc_result_t +write_data(FILE *file, unsigned char *datap, void *arg, isc_uint64_t *crc) { + isc_result_t result; + size_t ret = 0; + data_holder_t *data = (data_holder_t *)datap; + data_holder_t temp; + off_t where; + + UNUSED(arg); + + REQUIRE(file != NULL); + REQUIRE(crc != NULL); + REQUIRE(data != NULL); + REQUIRE((data->len == 0 && data->data == NULL) || + (data->len != 0 && data->data != NULL)); + + result = isc_stdio_tell(file, &where); + if (result != ISC_R_SUCCESS) + return (result); + + temp = *data; + temp.data = (data->len == 0 + ? NULL + : (char *)((uintptr_t)where + sizeof(data_holder_t))); + + isc_crc64_update(crc, (void *)&temp, sizeof(temp)); + ret = fwrite(&temp, sizeof(data_holder_t), 1, file); + if (ret != 1) + return (ISC_R_FAILURE); + if (data->len > 0) { + isc_crc64_update(crc, (const void *)data->data, data->len); + ret = fwrite(data->data, data->len, 1, file); + if (ret != 1) + return (ISC_R_FAILURE); + } + + return (ISC_R_SUCCESS); +} + +static isc_result_t +fix_data(dns_rbtnode_t *p, void *base, size_t max, void *arg, + isc_uint64_t *crc) +{ + data_holder_t *data = p->data; + size_t size; + + UNUSED(base); + UNUSED(max); + UNUSED(arg); + + REQUIRE(crc != NULL); + REQUIRE(p != NULL); + + + if (data == NULL) + printf("fixing data: data NULL\n"); + else + printf("fixing data: len %d, data %p\n", data->len, data->data); + + if (data == NULL || + (data->len == 0 && data->data != NULL) || + (data->len != 0 && data->data == NULL)) + return (ISC_R_INVALIDFILE); + + size = max - ((char *)p - (char *)base); + + if (data->len > (int) size || data->data > (const char *) max) { + printf("data invalid\n"); + return (ISC_R_INVALIDFILE); + } + + isc_crc64_update(crc, (void *)data, sizeof(*data)); + + data->data = (data->len == 0) + ? NULL + : (char *)data + sizeof(data_holder_t); + + if (data->len > 0) + isc_crc64_update(crc, (const void *)data->data, data->len); + + return (ISC_R_SUCCESS); +} + +/* + * Load test data into the RBT. + */ +static void +add_test_data(isc_mem_t *mctx, dns_rbt_t *rbt) { + char buffer[1024]; + isc_buffer_t b; + isc_result_t result; + dns_fixedname_t fname; + dns_name_t *name; + dns_compress_t cctx; + rbt_testdata_t *testdatap = testdata; + + dns_compress_init(&cctx, -1, mctx); + + while (testdatap->name != NULL && testdatap->data.data != NULL) { + memmove(buffer, testdatap->name, testdatap->name_len); + + isc_buffer_init(&b, buffer, testdatap->name_len); + isc_buffer_add(&b, testdatap->name_len); + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + testdatap++; + continue; + } + + if (name != NULL) { + result = dns_rbt_addname(rbt, name, &testdatap->data); + ATF_CHECK_STREQ(dns_result_totext(result), "success"); + } + testdatap++; + } + + dns_compress_invalidate(&cctx); +} + +/* + * Walk the tree and ensure that all the test nodes are present. + */ +static void +check_test_data(dns_rbt_t *rbt) { + char buffer[1024]; + char *arg; + dns_fixedname_t fname; + dns_fixedname_t fixed; + dns_name_t *name; + isc_buffer_t b; + data_holder_t *data; + isc_result_t result; + dns_name_t *foundname; + rbt_testdata_t *testdatap = testdata; + + dns_fixedname_init(&fixed); + foundname = dns_fixedname_name(&fixed); + + while (testdatap->name != NULL && testdatap->data.data != NULL) { + memmove(buffer, testdatap->name, testdatap->name_len + 1); + arg = buffer; + + isc_buffer_init(&b, arg, testdatap->name_len); + isc_buffer_add(&b, testdatap->name_len); + dns_fixedname_init(&fname); + name = dns_fixedname_name(&fname); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + testdatap++; + continue; + } + + data = NULL; + result = dns_rbt_findname(rbt, name, 0, foundname, + (void *) &data); + ATF_CHECK_STREQ(dns_result_totext(result), "success"); + + testdatap++; + } +} + +static void +data_printer(FILE *out, void *datap) +{ + data_holder_t *data = (data_holder_t *)datap; + + fprintf(out, "%d bytes, %s", data->len, data->data); +} + +ATF_TC(serialize); +ATF_TC_HEAD(serialize, tc) { + atf_tc_set_md_var(tc, "descr", "Test writing an rbt to file"); +} +ATF_TC_BODY(serialize, tc) { + dns_rbt_t *rbt = NULL; + isc_result_t result; + FILE *rbtfile = NULL; + dns_rbt_t *rbt_deserialized = NULL; + off_t offset; + int fd; + off_t filesize = 0; + char *base; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_CHECK_STREQ(dns_result_totext(result), "success"); + result = dns_rbt_create(mctx, delete_data, NULL, &rbt); + ATF_CHECK_STREQ(dns_result_totext(result), "success"); + + add_test_data(mctx, rbt); + + dns_rbt_printtext(rbt, data_printer, stdout); + + /* + * Serialize the tree. + */ + printf("serialization begins.\n"); + rbtfile = fopen("./zone.bin", "w+b"); + ATF_REQUIRE(rbtfile != NULL); + result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL, + &offset); + ATF_REQUIRE(result == ISC_R_SUCCESS); + dns_rbt_destroy(&rbt); + + /* + * Deserialize the tree + */ + printf("deserialization begins.\n"); + + /* + * Map in the whole file in one go + */ + fd = open("zone.bin", O_RDWR); + isc_file_getsizefd(fd, &filesize); + base = mmap(NULL, filesize, + PROT_READ|PROT_WRITE, + MAP_FILE|MAP_PRIVATE, fd, 0); + ATF_REQUIRE(base != NULL && base != MAP_FAILED); + close(fd); + + result = dns_rbt_deserialize_tree(base, filesize, 0, mctx, + delete_data, NULL, fix_data, NULL, + NULL, &rbt_deserialized); + + /* Test to make sure we have a valid tree */ + ATF_REQUIRE(result == ISC_R_SUCCESS); + if (rbt_deserialized == NULL) + atf_tc_fail("deserialized rbt is null!"); /* Abort execution. */ + + check_test_data(rbt_deserialized); + + dns_rbt_printtext(rbt_deserialized, data_printer, stdout); + + dns_rbt_destroy(&rbt_deserialized); + munmap(base, filesize); + unlink("zone.bin"); + dns_test_end(); +} + +ATF_TC(deserialize_corrupt); +ATF_TC_HEAD(deserialize_corrupt, tc) { + atf_tc_set_md_var(tc, "descr", "Test reading a corrupt map file"); +} +ATF_TC_BODY(deserialize_corrupt, tc) { + dns_rbt_t *rbt = NULL; + isc_result_t result; + FILE *rbtfile = NULL; + off_t offset; + int fd; + off_t filesize = 0; + char *base, *p, *q; + isc_uint32_t r; + int i; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Set up map file */ + result = dns_rbt_create(mctx, delete_data, NULL, &rbt); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + add_test_data(mctx, rbt); + rbtfile = fopen("./zone.bin", "w+b"); + ATF_REQUIRE(rbtfile != NULL); + result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL, + &offset); + ATF_REQUIRE(result == ISC_R_SUCCESS); + dns_rbt_destroy(&rbt); + + /* Read back with random fuzzing */ + for (i = 0; i < 256; i++) { + dns_rbt_t *rbt_deserialized = NULL; + + fd = open("zone.bin", O_RDWR); + isc_file_getsizefd(fd, &filesize); + base = mmap(NULL, filesize, + PROT_READ|PROT_WRITE, + MAP_FILE|MAP_PRIVATE, fd, 0); + ATF_REQUIRE(base != NULL && base != MAP_FAILED); + close(fd); + + /* Randomly fuzz a portion of the memory */ + isc_random_get(&r); + p = base + (r % filesize); + q = base + filesize; + isc_random_get(&r); + q -= (r % (q - p)); + while (p++ < q) { + isc_random_get(&r); + *p = r & 0xff; + } + + result = dns_rbt_deserialize_tree(base, filesize, 0, mctx, + delete_data, NULL, + fix_data, NULL, + NULL, &rbt_deserialized); + printf("%d: %s\n", i, isc_result_totext(result)); + + /* Test to make sure we have a valid tree */ + ATF_REQUIRE(result == ISC_R_SUCCESS || + result == ISC_R_INVALIDFILE); + if (result != ISC_R_SUCCESS) + ATF_REQUIRE(rbt_deserialized == NULL); + + if (rbt_deserialized != NULL) + dns_rbt_destroy(&rbt_deserialized); + + munmap(base, filesize); + } + + unlink("zone.bin"); + dns_test_end(); +} + + +ATF_TC(serialize_align); +ATF_TC_HEAD(serialize_align, tc) { + atf_tc_set_md_var(tc, "descr", + "Test the dns_rbt_serialize_align() function."); +} +ATF_TC_BODY(serialize_align, tc) { + UNUSED(tc); + + ATF_CHECK(dns_rbt_serialize_align(0) == 0); + ATF_CHECK(dns_rbt_serialize_align(1) == 8); + ATF_CHECK(dns_rbt_serialize_align(2) == 8); + ATF_CHECK(dns_rbt_serialize_align(3) == 8); + ATF_CHECK(dns_rbt_serialize_align(4) == 8); + ATF_CHECK(dns_rbt_serialize_align(5) == 8); + ATF_CHECK(dns_rbt_serialize_align(6) == 8); + ATF_CHECK(dns_rbt_serialize_align(7) == 8); + ATF_CHECK(dns_rbt_serialize_align(8) == 8); + ATF_CHECK(dns_rbt_serialize_align(9) == 16); + ATF_CHECK(dns_rbt_serialize_align(0xff) == 0x100); + ATF_CHECK(dns_rbt_serialize_align(0x301) == 0x308); +} + +/* + * Main + */ +ATF_TP_ADD_TCS(tp) { + ATF_TP_ADD_TC(tp, serialize); + ATF_TP_ADD_TC(tp, deserialize_corrupt); + ATF_TP_ADD_TC(tp, serialize_align); + + return (atf_no_error()); +} diff --git a/lib/dns/tests/rbt_test.c b/lib/dns/tests/rbt_test.c index 9b1500c057c..268bced32f7 100644 --- a/lib/dns/tests/rbt_test.c +++ b/lib/dns/tests/rbt_test.c @@ -25,7 +25,6 @@ #include #include #include -#include #ifdef HAVE_INTTYPES_H #include /* uintptr_t */ @@ -57,341 +56,854 @@ #include -#ifndef MAP_FILE -#define MAP_FILE 0 -#endif +#include + +typedef struct { + dns_rbt_t *rbt; + dns_rbt_t *rbt_distances; +} test_context_t; + +/* The initial structure of domain tree will be as follows: + * + * . + * | + * b + * / \ + * a d.e.f + * / | \ + * c | g.h + * | | + * w.y i + * / | \ \ + * x | z k + * | | + * p j + * / \ + * o q + */ -typedef struct data_holder { - int len; - const char *data; -} data_holder_t; - -typedef struct rbt_testdata { - const char *name; - size_t name_len; - data_holder_t data; -} rbt_testdata_t; - -#define DATA_ITEM(name) { (name), sizeof(name) - 1, { sizeof(name), (name) } } - -rbt_testdata_t testdata[] = { - DATA_ITEM("first.com."), - DATA_ITEM("one.net."), - DATA_ITEM("two.com."), - DATA_ITEM("three.org."), - DATA_ITEM("asdf.com."), - DATA_ITEM("ghjkl.com."), - DATA_ITEM("1.edu."), - DATA_ITEM("2.edu."), - DATA_ITEM("3.edu."), - DATA_ITEM("123.edu."), - DATA_ITEM("1236.com."), - DATA_ITEM("and_so_forth.com."), - DATA_ITEM("thisisalongname.com."), - DATA_ITEM("a.b."), - DATA_ITEM("test.net."), - DATA_ITEM("whoknows.org."), - DATA_ITEM("blargh.com."), - DATA_ITEM("www.joe.com."), - DATA_ITEM("test.com."), - DATA_ITEM("isc.org."), - DATA_ITEM("uiop.mil."), - DATA_ITEM("last.fm."), - { NULL, 0, { 0, NULL } } +/* The full absolute names of the nodes in the tree (the tree also + * contains "." which is not included in this list). + */ +static const char * const domain_names[] = { + "c", "b", "a", "x.d.e.f", "z.d.e.f", "g.h", "i.g.h", "o.w.y.d.e.f", + "j.z.d.e.f", "p.w.y.d.e.f", "q.w.y.d.e.f", "k.g.h" }; +static const size_t domain_names_count = (sizeof(domain_names) / + sizeof(domain_names[0])); + +/* These are set as the node data for the tree used in distances check + * (for the names in domain_names[] above). + */ +static const int node_distances[] = { + 3, 1, 2, 2, 2, 3, 1, 2, 1, 1, 2, 2 +}; + +/* + * The domain order should be: + * ., a, b, c, d.e.f, x.d.e.f, w.y.d.e.f, o.w.y.d.e.f, p.w.y.d.e.f, + * q.w.y.d.e.f, z.d.e.f, j.z.d.e.f, g.h, i.g.h, k.g.h + * . (no data, can't be found) + * | + * b + * / \ + * a d.e.f + * / | \ + * c | g.h + * | | + * w.y i + * / | \ \ + * x | z k + * | | + * p j + * / \ + * o q + */ + +static const char * const ordered_names[] = { + "a", "b", "c", "d.e.f", "x.d.e.f", "w.y.d.e.f", "o.w.y.d.e.f", + "p.w.y.d.e.f", "q.w.y.d.e.f", "z.d.e.f", "j.z.d.e.f", + "g.h", "i.g.h", "k.g.h"}; + +static const size_t ordered_names_count = (sizeof(ordered_names) / + sizeof(*ordered_names)); + static void delete_data(void *data, void *arg) { UNUSED(arg); - UNUSED(data); + + isc_mem_put(mctx, data, sizeof(size_t)); } -static isc_result_t -write_data(FILE *file, unsigned char *datap, void *arg, isc_uint64_t *crc) { +static void +build_name_from_str(const char *namestr, dns_fixedname_t *fname) { + size_t length; + isc_buffer_t *b = NULL; isc_result_t result; - size_t ret = 0; - data_holder_t *data = (data_holder_t *)datap; - data_holder_t temp; - off_t where; + dns_name_t *name; - UNUSED(arg); + length = strlen(namestr); - REQUIRE(file != NULL); - REQUIRE(crc != NULL); - REQUIRE(data != NULL); - REQUIRE((data->len == 0 && data->data == NULL) || - (data->len != 0 && data->data != NULL)); - - result = isc_stdio_tell(file, &where); - if (result != ISC_R_SUCCESS) - return (result); - - temp = *data; - temp.data = (data->len == 0 - ? NULL - : (char *)((uintptr_t)where + sizeof(data_holder_t))); - - isc_crc64_update(crc, (void *)&temp, sizeof(temp)); - ret = fwrite(&temp, sizeof(data_holder_t), 1, file); - if (ret != 1) - return (ISC_R_FAILURE); - if (data->len > 0) { - isc_crc64_update(crc, (const void *)data->data, data->len); - ret = fwrite(data->data, data->len, 1, file); - if (ret != 1) - return (ISC_R_FAILURE); - } + result = isc_buffer_allocate(mctx, &b, length); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + isc_buffer_putmem(b, (const unsigned char *) namestr, length); + + dns_fixedname_init(fname); + name = dns_fixedname_name(fname); + ATF_REQUIRE(name != NULL); + result = dns_name_fromtext(name, b, dns_rootname, 0, NULL); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); - return (ISC_R_SUCCESS); + isc_buffer_free(&b); } -static isc_result_t -fix_data(dns_rbtnode_t *p, void *base, size_t max, void *arg, - isc_uint64_t *crc) -{ - data_holder_t *data = p->data; - size_t size; +static test_context_t * +test_context_setup(void) { + test_context_t *ctx; + isc_result_t result; + size_t i; - UNUSED(base); - UNUSED(max); - UNUSED(arg); + ctx = isc_mem_get(mctx, sizeof(*ctx)); - REQUIRE(crc != NULL); - REQUIRE(p != NULL); + ctx->rbt = NULL; + result = dns_rbt_create(mctx, delete_data, NULL, &ctx->rbt); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + ctx->rbt_distances = NULL; + result = dns_rbt_create(mctx, delete_data, NULL, &ctx->rbt_distances); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); - if (data == NULL) - printf("fixing data: data NULL\n"); - else - printf("fixing data: len %d, data %p\n", data->len, data->data); + for (i = 0; i < domain_names_count; i++) { + size_t *n; + dns_fixedname_t fname; + dns_name_t *name; - if (data == NULL || - (data->len == 0 && data->data != NULL) || - (data->len != 0 && data->data == NULL)) - return (ISC_R_INVALIDFILE); + build_name_from_str(domain_names[i], &fname); - size = max - ((char *)p - (char *)base); + name = dns_fixedname_name(&fname); - if (data->len > (int) size || data->data > (const char *) max) { - printf("data invalid\n"); - return (ISC_R_INVALIDFILE); - } + n = isc_mem_get(mctx, sizeof(size_t)); + *n = i + 1; + result = dns_rbt_addname(ctx->rbt, name, n); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); - isc_crc64_update(crc, (void *)data, sizeof(*data)); + n = isc_mem_get(mctx, sizeof(size_t)); + *n = node_distances[i]; + result = dns_rbt_addname(ctx->rbt_distances, name, n); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } - data->data = (data->len == 0) - ? NULL - : (char *)data + sizeof(data_holder_t); + return (ctx); +} - if (data->len > 0) - isc_crc64_update(crc, (const void *)data->data, data->len); +static void +test_context_teardown(test_context_t *ctx) { + dns_rbt_destroy(&ctx->rbt); + dns_rbt_destroy(&ctx->rbt_distances); - return (ISC_R_SUCCESS); + isc_mem_put(mctx, ctx, sizeof(*ctx)); } /* - * Load test data into the RBT. + * Walk the tree and ensure that all the test nodes are present. */ static void -add_test_data(isc_mem_t *mctx, dns_rbt_t *rbt) { - char buffer[1024]; - isc_buffer_t b; +check_test_data(dns_rbt_t *rbt) { + dns_fixedname_t fixed; isc_result_t result; - dns_fixedname_t fname; - dns_name_t *name; - dns_compress_t cctx; - rbt_testdata_t *testdatap = testdata; + dns_name_t *foundname; + size_t i; + + dns_fixedname_init(&fixed); + foundname = dns_fixedname_name(&fixed); - dns_compress_init(&cctx, -1, mctx); + for (i = 0; i < domain_names_count; i++) { + dns_fixedname_t fname; + dns_name_t *name; + size_t *n; - while (testdatap->name != NULL && testdatap->data.data != NULL) { - memmove(buffer, testdatap->name, testdatap->name_len); + build_name_from_str(domain_names[i], &fname); - isc_buffer_init(&b, buffer, testdatap->name_len); - isc_buffer_add(&b, testdatap->name_len); - dns_fixedname_init(&fname); name = dns_fixedname_name(&fname); - result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); - if (result != ISC_R_SUCCESS) { - testdatap++; - continue; - } - - if (name != NULL) { - result = dns_rbt_addname(rbt, name, &testdatap->data); - ATF_CHECK_STREQ(dns_result_totext(result), "success"); - } - testdatap++; + n = NULL; + result = dns_rbt_findname(rbt, name, 0, foundname, + (void *) &n); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(*n, i + 1); } +} - dns_compress_invalidate(&cctx); +ATF_TC(rbt_create); +ATF_TC_HEAD(rbt_create, tc) { + atf_tc_set_md_var(tc, "descr", "Test the creation of an rbt"); } +ATF_TC_BODY(rbt_create, tc) { + isc_result_t result; + test_context_t *ctx; + isc_boolean_t tree_ok; -/* - * Walk the tree and ensure that all the test nodes are present. - */ -static void -check_test_data(dns_rbt_t *rbt) { - char buffer[1024]; - char *arg; + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ctx = test_context_setup(); + + check_test_data(ctx->rbt); + + tree_ok = dns__rbt_checkproperties(ctx->rbt); + ATF_CHECK_EQ(tree_ok, ISC_TRUE); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbt_nodecount); +ATF_TC_HEAD(rbt_nodecount, tc) { + atf_tc_set_md_var(tc, "descr", "Test dns_rbt_nodecount() on a tree"); +} +ATF_TC_BODY(rbt_nodecount, tc) { + isc_result_t result; + test_context_t *ctx; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + ctx = test_context_setup(); + + ATF_CHECK_EQ(15, dns_rbt_nodecount(ctx->rbt)); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbtnode_get_distance); +ATF_TC_HEAD(rbtnode_get_distance, tc) { + atf_tc_set_md_var(tc, "descr", + "Test dns_rbtnode_get_distance() on a tree"); +} +ATF_TC_BODY(rbtnode_get_distance, tc) { + isc_result_t result; + test_context_t *ctx; + const char *name_str = "a"; dns_fixedname_t fname; - dns_fixedname_t fixed; dns_name_t *name; - isc_buffer_t b; - data_holder_t *data; - isc_result_t result; - dns_name_t *foundname; - rbt_testdata_t *testdatap = testdata; + dns_rbtnode_t *node = NULL; + dns_rbtnodechain_t chain; - dns_fixedname_init(&fixed); - foundname = dns_fixedname_name(&fixed); + UNUSED(tc); - while (testdatap->name != NULL && testdatap->data.data != NULL) { - memmove(buffer, testdatap->name, testdatap->name_len + 1); - arg = buffer; + isc_mem_debugging = ISC_MEM_DEBUGRECORD; - isc_buffer_init(&b, arg, testdatap->name_len); - isc_buffer_add(&b, testdatap->name_len); - dns_fixedname_init(&fname); - name = dns_fixedname_name(&fname); - result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); - if (result != ISC_R_SUCCESS) { - testdatap++; - continue; - } + result = dns_test_begin(NULL, ISC_TRUE); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); - data = NULL; - result = dns_rbt_findname(rbt, name, 0, foundname, - (void *) &data); - ATF_CHECK_STREQ(dns_result_totext(result), "success"); + ctx = test_context_setup(); + + build_name_from_str(name_str, &fname); + name = dns_fixedname_name(&fname); + + dns_rbtnodechain_init(&chain, mctx); + + result = dns_rbt_findnode(ctx->rbt_distances, name, NULL, + &node, &chain, 0, NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); - testdatap++; + while (node != NULL) { + const size_t *distance = (const size_t *) node->data; + if (distance != NULL) + ATF_CHECK_EQ(*distance, + dns__rbtnode_getdistance(node)); + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result == ISC_R_NOMORE) + break; + dns_rbtnodechain_current(&chain, NULL, NULL, &node); } + + ATF_CHECK_EQ(result, ISC_R_NOMORE); + + dns_rbtnodechain_invalidate(&chain); + + test_context_teardown(ctx); + + dns_test_end(); } -static void -data_printer(FILE *out, void *datap) -{ - data_holder_t *data = (data_holder_t *)datap; +ATF_TC(rbt_check_distance_random); +ATF_TC_HEAD(rbt_check_distance_random, tc) { + atf_tc_set_md_var(tc, "descr", + "Test tree balance, inserting names in random order"); +} +ATF_TC_BODY(rbt_check_distance_random, tc) { + /* This test checks an important performance-related property of + * the red-black tree, which is important for us: the longest + * path from a sub-tree's root to a node is no more than + * 2log(n). This check verifies that the tree is balanced. + */ + dns_rbt_t *mytree = NULL; + const int log_num_nodes = 16; - fprintf(out, "%d bytes, %s", data->len, data->data); + int i; + isc_result_t result; + isc_boolean_t tree_ok; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + result = dns_rbt_create(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Names are inserted in random order. */ + + /* Make a large 65536 node top-level domain tree, i.e., the + * following code inserts names such as: + * + * savoucnsrkrqzpkqypbygwoiliawpbmz. + * wkadamcbbpjtundbxcmuayuycposvngx. + * wzbpznemtooxdpjecdxynsfztvnuyfao. + * yueojmhyffslpvfmgyfwioxegfhepnqq. + */ + for (i = 0; i < (1 << log_num_nodes); i++) { + size_t *n; + char namebuf[34]; + + n = isc_mem_get(mctx, sizeof(size_t)); + *n = i + 1; + + while (1) { + int j; + dns_fixedname_t fname; + dns_name_t *name; + + for (j = 0; j < 32; j++) { + isc_uint32_t v; + isc_random_get(&v); + namebuf[j] = 'a' + (v % 26); + } + namebuf[32] = '.'; + namebuf[33] = 0; + + build_name_from_str(namebuf, &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_addname(mytree, name, n); + if (result == ISC_R_SUCCESS) + break; + } + } + + /* 1 (root . node) + (1 << log_num_nodes) */ + ATF_CHECK_EQ(1 + (1 << log_num_nodes), dns_rbt_nodecount(mytree)); + + /* The distance from each node to its sub-tree root must be less + * than 2 * log(n). + */ + ATF_CHECK((2 * log_num_nodes) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, ISC_TRUE); + + dns_rbt_destroy(&mytree); + + dns_test_end(); } -ATF_TC(rbt); -ATF_TC_HEAD(rbt, tc) { - atf_tc_set_md_var(tc, "descr", "Test the creation of an rbt"); +ATF_TC(rbt_check_distance_ordered); +ATF_TC_HEAD(rbt_check_distance_ordered, tc) { + atf_tc_set_md_var(tc, "descr", + "Test tree balance, inserting names in sorted order"); } -ATF_TC_BODY(rbt, tc) { - dns_rbt_t *rbt = NULL; +ATF_TC_BODY(rbt_check_distance_ordered, tc) { + /* This test checks an important performance-related property of + * the red-black tree, which is important for us: the longest + * path from a sub-tree's root to a node is no more than + * 2log(n). This check verifies that the tree is balanced. + */ + dns_rbt_t *mytree = NULL; + const int log_num_nodes = 16; + + int i; isc_result_t result; + isc_boolean_t tree_ok; UNUSED(tc); isc_mem_debugging = ISC_MEM_DEBUGRECORD; result = dns_test_begin(NULL, ISC_TRUE); - ATF_CHECK_STREQ(dns_result_totext(result), "success"); - result = dns_rbt_create(mctx, delete_data, NULL, &rbt); - ATF_CHECK_STREQ(dns_result_totext(result), "success"); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); - add_test_data(mctx, rbt); + result = dns_rbt_create(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); - check_test_data(rbt); + /* Names are inserted in sorted order. */ - dns_rbt_printall(rbt, data_printer); + /* Make a large 65536 node top-level domain tree, i.e., the + * following code inserts names such as: + * + * name00000000. + * name00000001. + * name00000002. + * name00000003. + */ + for (i = 0; i < (1 << log_num_nodes); i++) { + size_t *n; + char namebuf[14]; + dns_fixedname_t fname; + dns_name_t *name; - dns_rbt_destroy(&rbt); + n = isc_mem_get(mctx, sizeof(size_t)); + *n = i + 1; + + snprintf(namebuf, sizeof(namebuf), "name%08x.", i); + build_name_from_str(namebuf, &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_addname(mytree, name, n); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + /* 1 (root . node) + (1 << log_num_nodes) */ + ATF_CHECK_EQ(1 + (1 << log_num_nodes), dns_rbt_nodecount(mytree)); + + /* The distance from each node to its sub-tree root must be less + * than 2 * log(n). + */ + ATF_CHECK((2 * log_num_nodes) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, ISC_TRUE); + + dns_rbt_destroy(&mytree); dns_test_end(); } -ATF_TC(serialize); -ATF_TC_HEAD(serialize, tc) { - atf_tc_set_md_var(tc, "descr", "Test writing an rbt to file"); +static isc_result_t +insert_helper(dns_rbt_t *rbt, const char *namestr, dns_rbtnode_t **node) { + dns_fixedname_t fname; + dns_name_t *name; + + build_name_from_str(namestr, &fname); + name = dns_fixedname_name(&fname); + + return (dns_rbt_addnode(rbt, name, node)); +} + +static isc_boolean_t +compare_labelsequences(dns_rbtnode_t *node, const char *labelstr) { + dns_name_t name; + isc_result_t result; + char *nodestr = NULL; + isc_boolean_t is_equal; + + dns_name_init(&name, NULL); + dns_rbt_namefromnode(node, &name); + + result = dns_name_tostring(&name, &nodestr, mctx); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + is_equal = strcmp(labelstr, nodestr) == 0 ? ISC_TRUE : ISC_FALSE; + + isc_mem_free(mctx, nodestr); + + return (is_equal); +} + +ATF_TC(rbt_insert); +ATF_TC_HEAD(rbt_insert, tc) { + atf_tc_set_md_var(tc, "descr", "Test insertion into a tree"); } -ATF_TC_BODY(serialize, tc) { - dns_rbt_t *rbt = NULL; +ATF_TC_BODY(rbt_insert, tc) { isc_result_t result; - FILE *rbtfile = NULL; - dns_rbt_t *rbt_deserialized = NULL; - off_t offset; - int fd; - off_t filesize = 0; - char *base; + test_context_t *ctx; + dns_rbtnode_t *node; UNUSED(tc); isc_mem_debugging = ISC_MEM_DEBUGRECORD; result = dns_test_begin(NULL, ISC_TRUE); - ATF_CHECK_STREQ(dns_result_totext(result), "success"); - result = dns_rbt_create(mctx, delete_data, NULL, &rbt); - ATF_CHECK_STREQ(dns_result_totext(result), "success"); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); - add_test_data(mctx, rbt); + ctx = test_context_setup(); - dns_rbt_printall(rbt, data_printer); + /* Check node count before beginning. */ + ATF_CHECK_EQ(15, dns_rbt_nodecount(ctx->rbt)); - /* - * Serialize the tree. + /* Try to insert a node that already exists. */ + node = NULL; + result = insert_helper(ctx->rbt, "d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_EXISTS); + + /* Node count must not have changed. */ + ATF_CHECK_EQ(15, dns_rbt_nodecount(ctx->rbt)); + + /* Try to insert a node that doesn't exist. */ + node = NULL; + result = insert_helper(ctx->rbt, "0", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "0"), ISC_TRUE); + + /* Node count must have increased. */ + ATF_CHECK_EQ(16, dns_rbt_nodecount(ctx->rbt)); + + /* Another. */ + node = NULL; + result = insert_helper(ctx->rbt, "example.com", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_REQUIRE(node != NULL); + ATF_CHECK_EQ(node->data, NULL); + + /* Node count must have increased. */ + ATF_CHECK_EQ(17, dns_rbt_nodecount(ctx->rbt)); + + /* Re-adding it should return EXISTS */ + node = NULL; + result = insert_helper(ctx->rbt, "example.com", &node); + ATF_CHECK_EQ(result, ISC_R_EXISTS); + + /* Node count must not have changed. */ + ATF_CHECK_EQ(17, dns_rbt_nodecount(ctx->rbt)); + + /* Fission the node d.e.f */ + node = NULL; + result = insert_helper(ctx->rbt, "k.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "k"), ISC_TRUE); + + /* Node count must have incremented twice ("d.e.f" fissioned to + * "d" and "e.f", and the newly added "k"). + */ + ATF_CHECK_EQ(19, dns_rbt_nodecount(ctx->rbt)); + + /* Fission the node "g.h" */ + node = NULL; + result = insert_helper(ctx->rbt, "h", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "h"), ISC_TRUE); + + /* Node count must have incremented ("g.h" fissioned to "g" and + * "h"). */ - printf("serialization begins.\n"); - rbtfile = fopen("./zone.bin", "w+b"); - ATF_REQUIRE(rbtfile != NULL); - result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL, - &offset); - ATF_REQUIRE(result == ISC_R_SUCCESS); - dns_rbt_destroy(&rbt); + ATF_CHECK_EQ(20, dns_rbt_nodecount(ctx->rbt)); + + /* Add child domains */ + + node = NULL; + result = insert_helper(ctx->rbt, "m.p.w.y.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "m"), ISC_TRUE); + ATF_CHECK_EQ(21, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "n.p.w.y.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "n"), ISC_TRUE); + ATF_CHECK_EQ(22, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "l.a", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(compare_labelsequences(node, "l"), ISC_TRUE); + ATF_CHECK_EQ(23, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "r.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + node = NULL; + result = insert_helper(ctx->rbt, "s.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + ATF_CHECK_EQ(25, dns_rbt_nodecount(ctx->rbt)); + + node = NULL; + result = insert_helper(ctx->rbt, "h.w.y.d.e.f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + /* Add more nodes one by one to cover left and right rotation + * functions. + */ + node = NULL; + result = insert_helper(ctx->rbt, "f", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "m", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "nm", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "om", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "k", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "l", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "fe", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "ge", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "i", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + node = NULL; + result = insert_helper(ctx->rbt, "ae", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + node = NULL; + result = insert_helper(ctx->rbt, "n", &node); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + test_context_teardown(ctx); + + dns_test_end(); +} + +ATF_TC(rbt_remove); +ATF_TC_HEAD(rbt_remove, tc) { + atf_tc_set_md_var(tc, "descr", "Test removal from a tree"); +} +ATF_TC_BODY(rbt_remove, tc) { /* - * Deserialize the tree + * This testcase checks that after node removal, the + * binary-search tree is valid and all nodes that are supposed + * to exist are present in the correct order. It mainly tests + * DomainTree as a BST, and not particularly as a red-black + * tree. This test checks node deletion when upper nodes have + * data. */ - printf("deserialization begins.\n"); + isc_result_t result; + size_t j; + + UNUSED(tc); + + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); /* - * Map in the whole file in one go + * Delete single nodes and check if the rest of the nodes exist. */ - fd = open("zone.bin", O_RDWR); - isc_file_getsizefd(fd, &filesize); - base = mmap(NULL, filesize, - PROT_READ|PROT_WRITE, - MAP_FILE|MAP_PRIVATE, fd, 0); - ATF_REQUIRE(base != NULL && base != MAP_FAILED); - close(fd); - - result = dns_rbt_deserialize_tree(base, filesize, 0, mctx, - delete_data, NULL, fix_data, NULL, - NULL, &rbt_deserialized); - - /* Test to make sure we have a valid tree */ - ATF_REQUIRE(result == ISC_R_SUCCESS); - if (rbt_deserialized == NULL) - atf_tc_fail("deserialized rbt is null!"); /* Abort execution. */ - - check_test_data(rbt_deserialized); - - dns_rbt_printall(rbt_deserialized, data_printer); - - dns_rbt_destroy(&rbt_deserialized); - munmap(base, filesize); - unlink("zone.bin"); + for (j = 0; j < ordered_names_count; j++) { + dns_rbt_t *mytree = NULL; + dns_rbtnode_t *node; + size_t i; + size_t *n; + isc_boolean_t tree_ok; + dns_rbtnodechain_t chain; + size_t start_node; + + /* Create a tree. */ + result = dns_rbt_create(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Insert test data into the tree. */ + for (i = 0; i < domain_names_count; i++) { + node = NULL; + result = insert_helper(mytree, domain_names[i], &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + /* Check that all names exist in order. */ + for (i = 0; i < ordered_names_count; i++) { + dns_fixedname_t fname; + dns_name_t *name; + + build_name_from_str(ordered_names[i], &fname); + + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, + &node, NULL, + DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + /* Add node data */ + ATF_REQUIRE(node != NULL); + ATF_REQUIRE_EQ(node->data, NULL); + + n = isc_mem_get(mctx, sizeof(size_t)); + *n = i; + + node->data = n; + } + + /* Now, delete the j'th node from the tree. */ + { + dns_fixedname_t fname; + dns_name_t *name; + + build_name_from_str(ordered_names[j], &fname); + + name = dns_fixedname_name(&fname); + + result = dns_rbt_deletename(mytree, name, ISC_FALSE); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + } + + /* Check RB tree properties. */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, ISC_TRUE); + + dns_rbtnodechain_init(&chain, mctx); + + /* Now, walk through nodes in order. */ + if (j == 0) { + /* + * Node for ordered_names[0] was already deleted + * above. We start from node 1. + */ + dns_fixedname_t fname; + dns_name_t *name; + + build_name_from_str(ordered_names[0], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, + &node, NULL, + 0, + NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_NOTFOUND); + + build_name_from_str(ordered_names[1], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, + &node, &chain, + 0, + NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + start_node = 1; + } else { + /* Start from node 0. */ + dns_fixedname_t fname; + dns_name_t *name; + + build_name_from_str(ordered_names[0], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, + &node, &chain, + 0, + NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + start_node = 0; + } + + /* + * node and chain have been set by the code above at + * this point. + */ + for (i = start_node; i < ordered_names_count; i++) { + dns_fixedname_t fname_j, fname_i; + dns_name_t *name_j, *name_i; + + build_name_from_str(ordered_names[j], &fname_j); + name_j = dns_fixedname_name(&fname_j); + build_name_from_str(ordered_names[i], &fname_i); + name_i = dns_fixedname_name(&fname_i); + + if (dns_name_equal(name_i, name_j)) { + /* + * This may be true for the last node if + * we seek ahead in the loop using + * dns_rbtnodechain_next() below. + */ + if (node == NULL) { + break; + } + + /* All ordered nodes have data + * initially. If any node is empty, it + * means it was removed, but an empty + * node exists because it is a + * super-domain. Just skip it. + */ + if (node->data == NULL) { + result = dns_rbtnodechain_next(&chain, + NULL, + NULL); + if (result == ISC_R_NOMORE) { + node = NULL; + } else { + dns_rbtnodechain_current(&chain, + NULL, + NULL, + &node); + } + } + continue; + } + + ATF_REQUIRE(node != NULL); + + n = (size_t *) node->data; + if (n != NULL) { + /* printf("n=%zu, i=%zu\n", *n, i); */ + ATF_CHECK_EQ(*n, i); + } + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result == ISC_R_NOMORE) { + node = NULL; + } else { + dns_rbtnodechain_current(&chain, NULL, NULL, + &node); + } + } + + /* We should have reached the end of the tree. */ + ATF_REQUIRE_EQ(node, NULL); + + dns_rbt_destroy(&mytree); + } + dns_test_end(); } -ATF_TC(deserialize_corrupt); -ATF_TC_HEAD(deserialize_corrupt, tc) { - atf_tc_set_md_var(tc, "descr", "Test reading a corrupt map file"); +ATF_TC(rbt_remove_empty); +ATF_TC_HEAD(rbt_remove_empty, tc) { + atf_tc_set_md_var(tc, "descr", + "Test removal from a tree when " + "upper nodes are empty"); } -ATF_TC_BODY(deserialize_corrupt, tc) { - dns_rbt_t *rbt = NULL; +ATF_TC_BODY(rbt_remove_empty, tc) { + /* + * This test is similar to the rbt_remove test, but checks node + * deletion when upper nodes are empty. + */ isc_result_t result; - FILE *rbtfile = NULL; - off_t offset; - int fd; - off_t filesize = 0; - char *base, *p, *q; - isc_uint32_t r; - int i; + size_t j; UNUSED(tc); @@ -400,94 +912,408 @@ ATF_TC_BODY(deserialize_corrupt, tc) { result = dns_test_begin(NULL, ISC_TRUE); ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); - /* Set up map file */ - result = dns_rbt_create(mctx, delete_data, NULL, &rbt); - ATF_CHECK_EQ(result, ISC_R_SUCCESS); + /* + * Delete single nodes and check if the rest of the nodes exist. + */ + for (j = 0; j < ordered_names_count; j++) { + dns_rbt_t *mytree = NULL; + dns_rbtnode_t *node; + size_t i; + isc_boolean_t tree_ok; + dns_rbtnodechain_t chain; + size_t start_node; + + /* Create a tree. */ + result = dns_rbt_create(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + /* Insert test data into the tree. */ + for (i = 0; i < domain_names_count; i++) { + node = NULL; + result = insert_helper(mytree, domain_names[i], &node); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + } + + /* Check that all names exist in order. */ + for (i = 0; i < ordered_names_count; i++) { + dns_fixedname_t fname; + dns_name_t *name; + + build_name_from_str(ordered_names[i], &fname); + + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, + &node, NULL, + DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + /* Check that node data is empty */ + ATF_REQUIRE(node != NULL); + ATF_REQUIRE_EQ(node->data, NULL); + } + + /* Now, delete the j'th node from the tree. */ + { + dns_fixedname_t fname; + dns_name_t *name; - add_test_data(mctx, rbt); - rbtfile = fopen("./zone.bin", "w+b"); - ATF_REQUIRE(rbtfile != NULL); - result = dns_rbt_serialize_tree(rbtfile, rbt, write_data, NULL, - &offset); - ATF_REQUIRE(result == ISC_R_SUCCESS); - dns_rbt_destroy(&rbt); - - /* Read back with random fuzzing */ - for (i = 0; i < 256; i++) { - dns_rbt_t *rbt_deserialized = NULL; - - fd = open("zone.bin", O_RDWR); - isc_file_getsizefd(fd, &filesize); - base = mmap(NULL, filesize, - PROT_READ|PROT_WRITE, - MAP_FILE|MAP_PRIVATE, fd, 0); - ATF_REQUIRE(base != NULL && base != MAP_FAILED); - close(fd); - - /* Randomly fuzz a portion of the memory */ - isc_random_get(&r); - p = base + (r % filesize); - q = base + filesize; - isc_random_get(&r); - q -= (r % (q - p)); - while (p++ < q) { - isc_random_get(&r); - *p = r & 0xff; + build_name_from_str(ordered_names[j], &fname); + + name = dns_fixedname_name(&fname); + + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, &node, + NULL, DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + result = dns_rbt_deletenode(mytree, node, ISC_FALSE); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); } - result = dns_rbt_deserialize_tree(base, filesize, 0, mctx, - delete_data, NULL, - fix_data, NULL, - NULL, &rbt_deserialized); - printf("%d: %s\n", i, isc_result_totext(result)); + /* Check RB tree properties. */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, ISC_TRUE); + + dns_rbtnodechain_init(&chain, mctx); + + /* Now, walk through nodes in order. */ + if (j == 0) { + /* + * Node for ordered_names[0] was already deleted + * above. We start from node 1. + */ + dns_fixedname_t fname; + dns_name_t *name; + + build_name_from_str(ordered_names[0], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, + &node, NULL, + DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + ATF_CHECK(result != ISC_R_SUCCESS); + + build_name_from_str(ordered_names[1], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, + &node, &chain, + DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + start_node = 1; + } else { + /* Start from node 0. */ + dns_fixedname_t fname; + dns_name_t *name; + + build_name_from_str(ordered_names[0], &fname); + name = dns_fixedname_name(&fname); + node = NULL; + result = dns_rbt_findnode(mytree, name, NULL, + &node, &chain, + DNS_RBTFIND_EMPTYDATA, + NULL, NULL); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + start_node = 0; + } - /* Test to make sure we have a valid tree */ - ATF_REQUIRE(result == ISC_R_SUCCESS || - result == ISC_R_INVALIDFILE); - if (result != ISC_R_SUCCESS) - ATF_REQUIRE(rbt_deserialized == NULL); + /* + * node and chain have been set by the code above at + * this point. + */ + for (i = start_node; i < ordered_names_count; i++) { + dns_fixedname_t fntmp1, fntmp2; + dns_name_t *ntmp1, *ntmp2; + dns_fixedname_t fname_j, fname_i; + dns_name_t *name_j, *name_i; + + build_name_from_str("j.z.d.e.f", &fntmp1); + ntmp1 = dns_fixedname_name(&fntmp1); + build_name_from_str("z.d.e.f", &fntmp2); + ntmp2 = dns_fixedname_name(&fntmp2); + + build_name_from_str(ordered_names[j], &fname_j); + name_j = dns_fixedname_name(&fname_j); + build_name_from_str(ordered_names[i], &fname_i); + name_i = dns_fixedname_name(&fname_i); + + if (dns_name_equal(name_j, ntmp1) && + dns_name_equal(name_j, ntmp2)) + { + /* + * The only special case in the + * tree. Here, "z.d.e.f" will not exist + * as it would have been removed during + * removal of "j.z.d.e.f". + */ + continue; + } + + if (dns_name_equal(name_i, name_j)) { + dns_fixedname_t fntmp3, fntmp4, fntmp5, fntmp6; + dns_name_t *ntmp3, *ntmp4, *ntmp5, *ntmp6; + + /* + * This may be true for the last node if + * we seek ahead in the loop using + * dns_rbtnodechain_next() below. + */ + if (node == NULL) { + break; + } + + /* + * All ordered nodes are empty + * initially. If an empty removed node + * exists because it is a super-domain, + * just skip it. + */ + build_name_from_str("d.e.f", &fntmp3); + ntmp3 = dns_fixedname_name(&fntmp3); + build_name_from_str("w.y.d.e.f", &fntmp4); + ntmp4 = dns_fixedname_name(&fntmp4); + build_name_from_str("z.d.e.f", &fntmp5); + ntmp5 = dns_fixedname_name(&fntmp5); + build_name_from_str("g.h", &fntmp6); + ntmp6 = dns_fixedname_name(&fntmp6); + + if (dns_name_equal(name_j, ntmp3) || + dns_name_equal(name_j, ntmp4) || + dns_name_equal(name_j, ntmp5) || + dns_name_equal(name_j, ntmp6)) + { + result = dns_rbtnodechain_next(&chain, + NULL, + NULL); + if (result == ISC_R_NOMORE) { + node = NULL; + } else { + dns_rbtnodechain_current(&chain, + NULL, + NULL, + &node); + } + } + continue; + } + + ATF_REQUIRE(node != NULL); + + result = dns_rbtnodechain_next(&chain, NULL, NULL); + if (result == ISC_R_NOMORE) { + node = NULL; + } else { + dns_rbtnodechain_current(&chain, NULL, NULL, + &node); + } + } - if (rbt_deserialized != NULL) - dns_rbt_destroy(&rbt_deserialized); + /* We should have reached the end of the tree. */ + ATF_REQUIRE_EQ(node, NULL); - munmap(base, filesize); + dns_rbt_destroy(&mytree); } - unlink("zone.bin"); dns_test_end(); } +static void +insert_nodes(dns_rbt_t *mytree, char **names, + size_t *names_count, isc_uint32_t num_names) +{ + isc_uint32_t i; + + for (i = 0; i < num_names; i++) { + size_t *n; + char namebuf[34]; + + n = isc_mem_get(mctx, sizeof(size_t)); + *n = i; /* Unused value */ + + while (1) { + int j; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + + for (j = 0; j < 32; j++) { + isc_uint32_t v; + isc_random_get(&v); + namebuf[j] = 'a' + (v % 26); + } + namebuf[32] = '.'; + namebuf[33] = 0; + + build_name_from_str(namebuf, &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_addname(mytree, name, n); + if (result == ISC_R_SUCCESS) { + names[*names_count] = isc_mem_strdup(mctx, + namebuf); + *names_count += 1; + break; + } + } + } +} -ATF_TC(serialize_align); -ATF_TC_HEAD(serialize_align, tc) { +static void +remove_nodes(dns_rbt_t *mytree, char **names, + size_t *names_count, isc_uint32_t num_names) +{ + isc_uint32_t i; + + UNUSED(mytree); + + for (i = 0; i < num_names; i++) { + isc_uint32_t node; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + + isc_random_get(&node); + + node %= *names_count; + + build_name_from_str(names[node], &fname); + name = dns_fixedname_name(&fname); + + result = dns_rbt_deletename(mytree, name, ISC_FALSE); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + isc_mem_free(mctx, names[node]); + if (*names_count > 0) { + names[node] = names[*names_count - 1]; + names[*names_count - 1] = NULL; + *names_count -= 1; + } + } +} + +static void +check_tree(dns_rbt_t *mytree, char **names, size_t names_count) { + isc_boolean_t tree_ok; + + UNUSED(names); + + ATF_CHECK_EQ(names_count + 1, dns_rbt_nodecount(mytree)); + + /* + * The distance from each node to its sub-tree root must be less + * than 2 * log_2(1024). + */ + ATF_CHECK((2 * 10) >= dns__rbt_getheight(mytree)); + + /* Also check RB tree properties */ + tree_ok = dns__rbt_checkproperties(mytree); + ATF_CHECK_EQ(tree_ok, ISC_TRUE); +} + +ATF_TC(rbt_insert_and_remove); +ATF_TC_HEAD(rbt_insert_and_remove, tc) { atf_tc_set_md_var(tc, "descr", - "Test the dns_rbt_serialize_align() function."); + "Test insert and remove in a loop"); } -ATF_TC_BODY(serialize_align, tc) { +ATF_TC_BODY(rbt_insert_and_remove, tc) { + /* + * What is the best way to test our red-black tree code? It is + * not a good method to test every case handled in the actual + * code itself. This is because our approach itself may be + * incorrect. + * + * We test our code at the interface level here by exercising the + * tree randomly multiple times, checking that red-black tree + * properties are valid, and all the nodes that are supposed to be + * in the tree exist and are in order. + * + * NOTE: These tests are run within a single tree level in the + * forest. The number of nodes in the tree level doesn't grow + * over 1024. + */ + isc_result_t result; + dns_rbt_t *mytree = NULL; + /* + * We use an array for storing names instead of a set + * structure. It's slow, but works and is good enough for tests. + */ + char *names[1024]; + size_t names_count; + int i; + UNUSED(tc); - ATF_CHECK(dns_rbt_serialize_align(0) == 0); - ATF_CHECK(dns_rbt_serialize_align(1) == 8); - ATF_CHECK(dns_rbt_serialize_align(2) == 8); - ATF_CHECK(dns_rbt_serialize_align(3) == 8); - ATF_CHECK(dns_rbt_serialize_align(4) == 8); - ATF_CHECK(dns_rbt_serialize_align(5) == 8); - ATF_CHECK(dns_rbt_serialize_align(6) == 8); - ATF_CHECK(dns_rbt_serialize_align(7) == 8); - ATF_CHECK(dns_rbt_serialize_align(8) == 8); - ATF_CHECK(dns_rbt_serialize_align(9) == 16); - ATF_CHECK(dns_rbt_serialize_align(0xff) == 0x100); - ATF_CHECK(dns_rbt_serialize_align(0x301) == 0x308); + isc_mem_debugging = ISC_MEM_DEBUGRECORD; + + result = dns_test_begin(NULL, ISC_TRUE); + ATF_CHECK_EQ(result, ISC_R_SUCCESS); + + result = dns_rbt_create(mctx, delete_data, NULL, &mytree); + ATF_REQUIRE_EQ(result, ISC_R_SUCCESS); + + memset(names, 0, sizeof(names)); + names_count = 0; + + /* Repeat the insert/remove test some 4096 times */ + for (i = 0; i < 4096; i++) { + isc_uint32_t num_names; + isc_random_get(&num_names); + + if (names_count < 1024) { + num_names %= 1024 - names_count; + num_names++; + } else { + num_names = 0; + } + + insert_nodes(mytree, names, &names_count, num_names); + check_tree(mytree, names, names_count); + + isc_random_get(&num_names); + if (names_count > 0) { + num_names %= names_count; + num_names++; + } else { + num_names = 0; + } + + remove_nodes(mytree, names, &names_count, num_names); + check_tree(mytree, names, names_count); + } + + /* Remove the rest of the nodes */ + remove_nodes(mytree, names, &names_count, names_count); + check_tree(mytree, names, names_count); + + for (i = 0; i < 1024; i++) { + if (names[i] != NULL) { + isc_mem_free(mctx, names[i]); + } + } + + dns_rbt_destroy(&mytree); + + dns_test_end(); } /* * Main */ ATF_TP_ADD_TCS(tp) { - ATF_TP_ADD_TC(tp, rbt); - ATF_TP_ADD_TC(tp, serialize); - ATF_TP_ADD_TC(tp, deserialize_corrupt); - ATF_TP_ADD_TC(tp, serialize_align); + ATF_TP_ADD_TC(tp, rbt_create); + ATF_TP_ADD_TC(tp, rbt_nodecount); + ATF_TP_ADD_TC(tp, rbtnode_get_distance); + ATF_TP_ADD_TC(tp, rbt_check_distance_random); + ATF_TP_ADD_TC(tp, rbt_check_distance_ordered); + ATF_TP_ADD_TC(tp, rbt_insert); + ATF_TP_ADD_TC(tp, rbt_remove); + ATF_TP_ADD_TC(tp, rbt_remove_empty); + ATF_TP_ADD_TC(tp, rbt_insert_and_remove); return (atf_no_error()); } diff --git a/lib/dns/win32/libdns.def.in b/lib/dns/win32/libdns.def.in index 799e4dcabc1..746ed48a9e5 100644 --- a/lib/dns/win32/libdns.def.in +++ b/lib/dns/win32/libdns.def.in @@ -553,7 +553,8 @@ dns_rbt_fullnamefromnode dns_rbt_hashsize dns_rbt_namefromnode dns_rbt_nodecount -dns_rbt_printall +dns_rbt_printtext +dns_rbt_printdot dns_rbt_printnodeinfo dns_rbt_serialize_align dns_rbt_serialize_tree diff --git a/lib/isc/hash.c b/lib/isc/hash.c index a6edc1f2252..aff12eb21d2 100644 --- a/lib/isc/hash.c +++ b/lib/isc/hash.c @@ -393,3 +393,17 @@ isc_hash_calc(const unsigned char *key, unsigned int keylen, return (hash_calc(hash, key, keylen, case_sensitive)); } + +void +isc__hash_setvec(const uint16_t *vec) { + int i; + hash_random_t *p; + + if (hash == NULL) + return; + + p = hash->rndvector; + for (i = 0; i < 256; i++) { + p[i] = vec[i]; + } +} diff --git a/lib/isc/include/isc/hash.h b/lib/isc/include/isc/hash.h index 0bfe936d7f9..662e37aff4a 100644 --- a/lib/isc/include/isc/hash.h +++ b/lib/isc/include/isc/hash.h @@ -20,6 +20,8 @@ #ifndef ISC_HASH_H #define ISC_HASH_H 1 +#include + /***** ***** Module Info *****/ @@ -180,6 +182,22 @@ isc_hash_calc(const unsigned char *key, unsigned int keylen, */ /*@}*/ +void +isc__hash_setvec(const uint16_t *vec); + +/*!< + * \brief Set the contents of the random vector used in hashing. + * + * WARNING: This function is meant to be used only in testing code. It + * must not be used anywhere in normally running code. + * + * The hash context must have been created beforehand, otherwise this + * function is a nop. + * + * 'vec' is not documented here on purpose. You should know what you are + * doing before using this function. + */ + ISC_LANG_ENDDECLS #endif /* ISC_HASH_H */