/*
 * Copyright (C) 1998-2001, 2003-2005, 2007, 2009, 2011-2013, 2015, 2016  Internet Systems Consortium, Inc. ("ISC")
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

/* $Id: t_rbt.c,v 1.35 2011/03/12 04:59:46 tbox Exp $ */

#include <config.h>

#include <ctype.h>
#include <stdlib.h>

#include <isc/entropy.h>
#include <isc/mem.h>
#include <isc/util.h>
#include <isc/hash.h>
#include <isc/string.h>

#include <dns/fixedname.h>
#include <dns/rbt.h>
#include <dns/result.h>

#include <tests/t_api.h>

#define		BUFLEN	1024
#define		DNSNAMELEN 255

char		*progname;
char		*Tokens[T_MAXTOKS];

static int
t_dns_rbtnodechain_init(char *dbfile, char *findname,
			char *firstname, char *firstorigin,
			char *nextname, char *nextorigin,
			char *prevname, char *prevorigin,
			char *lastname, char *lastorigin);
static char *
fixedname_totext(dns_fixedname_t *name);

static int
fixedname_cmp(dns_fixedname_t *dns_name, char *txtname);

static char *
dnsname_totext(dns_name_t *name);

static int
t_namechk(isc_result_t dns_result, dns_fixedname_t *dns_name, char *exp_name,
	  dns_fixedname_t *dns_origin, char *exp_origin,
	  isc_result_t exp_result);

/*
 * Parts adapted from the original rbt_test.c.
 */
static int
fixedname_cmp(dns_fixedname_t *dns_name, char *txtname) {
	char	*name;

	name = dnsname_totext(dns_fixedname_name(dns_name));
	if (strcmp(txtname, "NULL") == 0) {
		if ((name == NULL) || (*name == '\0'))
			return(0);
		return(1);
	} else {
		return(strcmp(name, txtname));
	}
}

static char *
dnsname_totext(dns_name_t *name) {
	static char	buf[BUFLEN];
	isc_buffer_t	target;

	isc_buffer_init(&target, buf, BUFLEN);
	dns_name_totext(name, ISC_FALSE, &target);
	*((char *)(target.base) + target.used) = '\0';
	return(target.base);
}

static char *
fixedname_totext(dns_fixedname_t *name) {
	static char	buf[BUFLEN];
	isc_buffer_t	target;

	memset(buf, 0, BUFLEN);
	isc_buffer_init(&target, buf, BUFLEN);
	dns_name_totext(dns_fixedname_name(name), ISC_FALSE, &target);
	*((char *)(target.base) + target.used) = '\0';
	return(target.base);
}

#ifdef	NEED_PRINT_DATA

static isc_result_t
print_data(void *data) {
	isc_result_t	dns_result;
	isc_buffer_t	target;
	char		*buffer[DNSNAMELEN];

	isc_buffer_init(&target, buffer, sizeof(buffer));

	dns_result = dns_name_totext(data, ISC_FALSE, &target);
	if (dns_result != ISC_R_SUCCESS) {
		t_info("dns_name_totext failed %s\n",
				dns_result_totext(dns_result));
	}
	return(dns_result);
}

#endif	/* NEED_PRINT_DATA */

static int
create_name(char *s, isc_mem_t *mctx, dns_name_t **dns_name) {
	int		nfails;
	int		length;
	isc_result_t	result;
	isc_buffer_t	source;
	isc_buffer_t	target;
	dns_name_t	*name;

	nfails = 0;

	if (s && *s) {

		length = strlen(s);

		isc_buffer_init(&source, s, length);
		isc_buffer_add(&source, length);

		/*
		 * The buffer for the actual name will immediately follow the
		 * name structure.
		 */
		name = isc_mem_get(mctx, sizeof(*name) + DNSNAMELEN);
		if (name == NULL) {
			t_info("isc_mem_get failed\n");
			++nfails;
		} else {

			dns_name_init(name, NULL);
			isc_buffer_init(&target, name + 1, DNSNAMELEN);

			result = dns_name_fromtext(name, &source, dns_rootname,
						   0, &target);

			if (result != ISC_R_SUCCESS) {
				++nfails;
				t_info("dns_name_fromtext(%s) failed %s\n",
				       s, dns_result_totext(result));
				 isc_mem_put(mctx, name,
					     sizeof(*name) + DNSNAMELEN);
			} else
				*dns_name = name;
		}
	} else {
		++nfails;
		t_info("create_name: empty name\n");
	}

	return(nfails);
}

static void
delete_name(void *data, void *arg) {
	isc_mem_put((isc_mem_t *)arg, data, sizeof(dns_name_t) + DNSNAMELEN);
}


/*
 * Adapted from the original rbt_test.c.
 */
static int
t1_add(char *name, dns_rbt_t *rbt, isc_mem_t *mctx, isc_result_t *dns_result) {
	int		nprobs;
	dns_name_t	*dns_name;

	nprobs = 0;
	if (name && dns_result) {
		if (create_name(name, mctx, &dns_name) == 0) {
			if (T_debug)
				t_info("dns_rbt_addname succeeded\n");
			*dns_result = dns_rbt_addname(rbt, dns_name, dns_name);
			if (*dns_result != ISC_R_SUCCESS) {
				delete_name(dns_name, mctx);
				t_info("dns_rbt_addname failed %s\n",
				       dns_result_totext(*dns_result));
				++nprobs;
			}
		} else {
			++nprobs;
		}
	} else {
		++nprobs;
	}
	return(nprobs);
}

static int
t1_delete(char *name, dns_rbt_t *rbt, isc_mem_t *mctx,
	  isc_result_t *dns_result)
{
	int		nprobs;
	dns_name_t	*dns_name;

	nprobs = 0;
	if (name && dns_result) {
		if (create_name(name, mctx, &dns_name) == 0) {
			*dns_result = dns_rbt_deletename(rbt, dns_name,
							 ISC_FALSE);
			delete_name(dns_name, mctx);
		} else {
			++nprobs;
		}
	} else {
		++nprobs;
	}
	return(nprobs);
}

static int
t1_search(char *name, dns_rbt_t *rbt, isc_mem_t *mctx,
	  isc_result_t *dns_result)
{
	int		nprobs;
	dns_name_t	*dns_searchname;
	dns_name_t	*dns_foundname;
	dns_fixedname_t	dns_fixedname;
	void		*data;

	nprobs = 0;
	if (name && dns_result) {
		if (create_name(name, mctx, &dns_searchname) == 0) {
			dns_fixedname_init(&dns_fixedname);
			dns_foundname = dns_fixedname_name(&dns_fixedname);
			data = NULL;
			*dns_result = dns_rbt_findname(rbt, dns_searchname, 0,
						dns_foundname, &data);
			delete_name(dns_searchname, mctx);
		} else {
			++nprobs;
		}
	} else {
		++nprobs;
	}
	return(nprobs);
}

/*
 * Initialize a database from filename.
 */
static int
rbt_init(char *filename, dns_rbt_t **rbt, isc_mem_t *mctx) {
	int		rval;
	isc_result_t	dns_result;
	char		*p;
	FILE		*fp;

	fp = fopen(filename, "r");
	if (fp == NULL) {
		t_info("No such file %s\n", filename);
		return(1);
	}

	dns_result = dns_rbt_create(mctx, delete_name, mctx, rbt);
	if (dns_result != ISC_R_SUCCESS) {
		t_info("dns_rbt_create failed %s\n",
				dns_result_totext(dns_result));
		fclose(fp);
		return(1);
	}

	while ((p = t_fgetbs(fp)) != NULL) {

		/*
		 * Skip any comment lines.
		 */
		if ((*p == '#') || (*p == '\0') || (*p == ' ')) {
			(void)free(p);
			continue;
		}

		if (T_debug)
			t_info("adding name %s to the rbt\n", p);

		rval = t1_add(p, *rbt, mctx, &dns_result);
		if ((rval != 0) || (dns_result != ISC_R_SUCCESS)) {
			t_info("add of %s failed\n", p);
			dns_rbt_destroy(rbt);
			(void) free(p);
			fclose(fp);
			return(1);
		}
		(void) free(p);
	}
	fclose(fp);
	return(0);
}

static int
test_rbt_gen(char *filename, char *command, char *testname,
	     isc_result_t exp_result)
{
	int		rval;
	int		result;
	dns_rbt_t	*rbt;
	isc_result_t	isc_result;
	isc_result_t	dns_result;
	isc_mem_t	*mctx;
	isc_entropy_t	*ectx;
	dns_name_t	*dns_name;

	result = T_UNRESOLVED;

	if (strcmp(command, "create") != 0)
		t_info("testing using name %s\n", testname);

	mctx = NULL;
	ectx = NULL;

	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		return(T_UNRESOLVED);
	}

	isc_result = isc_entropy_create(mctx, &ectx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_entropy_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_hash_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	rbt = NULL;
	if (rbt_init(filename, &rbt, mctx) != 0) {
		if (strcmp(command, "create") == 0)
			result = T_FAIL;
		isc_hash_destroy();
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(result);
	}

	/*
	 * Now try the database command.
	 */
	if (strcmp(command, "create") == 0) {
		result = T_PASS;
	} else if (strcmp(command, "add") == 0) {
		if (create_name(testname, mctx, &dns_name) == 0) {
			dns_result = dns_rbt_addname(rbt, dns_name, dns_name);

			if (dns_result != ISC_R_SUCCESS)
				delete_name(dns_name, mctx);

			if (dns_result == exp_result) {
				if (dns_result == ISC_R_SUCCESS) {
					rval = t1_search(testname, rbt, mctx,
							 &dns_result);
					if (rval == 0) {
					     if (dns_result == ISC_R_SUCCESS) {
						     result = T_PASS;
					     } else {
						     result = T_FAIL;
					     }
					} else {
						t_info("t1_search failed\n");
						result = T_UNRESOLVED;
					}
				} else {
					result = T_PASS;
				}
			} else {
				t_info("dns_rbt_addname returned %s, "
				       "expected %s\n",
				       dns_result_totext(dns_result),
				       dns_result_totext(exp_result));
				result = T_FAIL;
			}
		} else {
			t_info("create_name failed\n");
			result = T_UNRESOLVED;
		}
	} else if ((strcmp(command, "delete") == 0) ||
		   (strcmp(command, "nuke") == 0)) {
		rval = t1_delete(testname, rbt, mctx, &dns_result);
		if (rval == 0) {
			if (dns_result == exp_result) {
				rval = t1_search(testname, rbt, mctx,
						 &dns_result);
				if (rval == 0) {
					if (dns_result == ISC_R_SUCCESS) {
						t_info("dns_rbt_deletename "
						       "didn't delete "
						       "the name");
						result = T_FAIL;
					} else {
						result = T_PASS;
					}
				}
			} else {
				t_info("delete returned %s, expected %s\n",
					dns_result_totext(dns_result),
					dns_result_totext(exp_result));
				result = T_FAIL;
			}
		}
	} else if (strcmp(command, "search") == 0) {
		rval = t1_search(testname, rbt, mctx, &dns_result);
		if (rval == 0) {
			if (dns_result == exp_result) {
				result = T_PASS;
			} else {
				t_info("find returned %s, expected %s\n",
					dns_result_totext(dns_result),
					dns_result_totext(exp_result));
				result = T_FAIL;
			}
		}
	}

	dns_rbt_destroy(&rbt);
	isc_hash_destroy();
	isc_entropy_detach(&ectx);
	isc_mem_destroy(&mctx);
	return(result);
}

static int
test_dns_rbt_x(const char *filename) {
	FILE		*fp;
	char		*p;
	int		line;
	int		cnt;
	int		result;
	int		nfails;
	int		nprobs;

	nfails = 0;
	nprobs = 0;

	fp = fopen(filename, "r");
	if (fp != NULL) {
		line = 0;
		while ((p = t_fgetbs(fp)) != NULL) {

			++line;

			/*
			 * Skip comment lines.
			 */
			if ((isspace((unsigned char)*p)) || (*p == '#')) {
				(void)free(p);
				continue;
			}

			/*
			 * Name of db file, command, testname,
			 * expected result.
			 */
			cnt = t_bustline(p, Tokens);
			if (cnt == 4) {
				result = test_rbt_gen(Tokens[0], Tokens[1],
					     Tokens[2],
					     t_dns_result_fromtext(Tokens[3]));
				if (result != T_PASS)
					++nfails;
			} else {
				t_info("bad format in %s at line %d\n",
						filename, line);
				++nprobs;
			}

			(void)free(p);
		}
		(void)fclose(fp);
	} else {
		t_info("Missing datafile %s\n", filename);
		++nprobs;
	}

	result = T_UNRESOLVED;
	if ((nfails == 0) && (nprobs == 0))
		result = T_PASS;
	else if (nfails)
		result = T_FAIL;

	return(result);
}


static const char *a1 =	"dns_rbt_create creates a rbt and returns "
			"ISC_R_SUCCESS on success";

static void
t1() {
	int	result;

	t_assert("dns_rbt_create", 1, T_REQUIRED, "%s", a1);
	result = test_dns_rbt_x("dns_rbt_create_1_data");
	t_result(result);
}

static const char *a2 =	"dns_rbt_addname adds a name to a database and "
			"returns ISC_R_SUCCESS on success";

static void
t2() {
	int	result;

	t_assert("dns_rbt_addname", 2, T_REQUIRED, "%s", a2);
	result = test_dns_rbt_x("dns_rbt_addname_1_data");
	t_result(result);
}

static const char *a3 =	"when name already exists, dns_rbt_addname() "
			"returns ISC_R_EXISTS";

static void
t3() {
	int	result;

	t_assert("dns_rbt_addname", 3, T_REQUIRED, "%s", a3);
	result = test_dns_rbt_x("dns_rbt_addname_2_data");
	t_result(result);
}

static const char *a4 =	"when name exists, dns_rbt_deletename() returns "
			"ISC_R_SUCCESS";

static void
t4() {
	int	result;

	t_assert("dns_rbt_deletename", 4, T_REQUIRED, "%s", a4);
	result = test_dns_rbt_x("dns_rbt_deletename_1_data");
	t_result(result);
}

static const char *a5 =	"when name does not exist, dns_rbt_deletename() "
			"returns ISC_R_NOTFOUND";
static void
t5() {
	int	result;

	t_assert("dns_rbt_deletename", 5, T_REQUIRED, "%s", a5);
	result = test_dns_rbt_x("dns_rbt_deletename_2_data");
	t_result(result);
}

static const char *a6 =	"when name exists and exactly matches the "
			"search name dns_rbt_findname() returns ISC_R_SUCCESS";

static void
t6() {
	int	result;

	t_assert("dns_rbt_findname", 6, T_REQUIRED, "%s", a6);
	result = test_dns_rbt_x("dns_rbt_findname_1_data");
	t_result(result);
}

static const char *a7 =	"when a name does not exist, "
			"dns_rbt_findname returns ISC_R_NOTFOUND";

static void
t7() {
	int	result;

	t_assert("dns_rbt_findname", 7, T_REQUIRED, "%s", a7);
	result = test_dns_rbt_x("dns_rbt_findname_2_data");
	t_result(result);
}

static const char *a8 =	"when a superdomain is found with data matching name, "
			"dns_rbt_findname returns DNS_R_PARTIALMATCH";

static void
t8() {
	int	result;

	t_assert("dns_rbt_findname", 8, T_REQUIRED, "%s", a8);
	result = test_dns_rbt_x("dns_rbt_findname_3_data");
	t_result(result);
}


static const char *a9 =	"a call to dns_rbtnodechain_init(chain, mctx) "
			"initializes chain";

static int
t9_walkchain(dns_rbtnodechain_t *chain, dns_rbt_t *rbt) {
	int		cnt;
	int		order;
	unsigned int	nlabels;
	int		nprobs;
	isc_result_t	dns_result;

	dns_fixedname_t	name;
	dns_fixedname_t	origin;
	dns_fixedname_t	fullname1;
	dns_fixedname_t	fullname2;

	cnt = 0;
	nprobs = 0;

	do {

		if (cnt == 0) {
			dns_fixedname_init(&name);
			dns_fixedname_init(&origin);
			dns_result = dns_rbtnodechain_first(chain, rbt,
						dns_fixedname_name(&name),
						dns_fixedname_name(&origin));
			if (dns_result != DNS_R_NEWORIGIN) {
				t_info("dns_rbtnodechain_first returned %s, "
				       "expecting DNS_R_NEWORIGIN\n",
				       dns_result_totext(dns_result));
				++nprobs;
				break;
			}
			t_info("first name:\t<%s>\n", fixedname_totext(&name));
			t_info("first origin:\t<%s>\n",
			       fixedname_totext(&origin));
		} else {
			dns_fixedname_init(&fullname1);
			dns_result = dns_name_concatenate(
			       dns_fixedname_name(&name),
			       dns_name_isabsolute(dns_fixedname_name(&name)) ?
					    NULL : dns_fixedname_name(&origin),
			       dns_fixedname_name(&fullname1), NULL);
			if (dns_result != ISC_R_SUCCESS) {
				t_info("dns_name_concatenate failed %s\n",
				       dns_result_totext(dns_result));
				++nprobs;
				break;
			}

			/*
			 * Expecting origin not to get touched if next
			 * doesn't return NEWORIGIN.
			 */
			dns_fixedname_init(&name);
			dns_result = dns_rbtnodechain_next(chain,
						  dns_fixedname_name(&name),
						  dns_fixedname_name(&origin));
			if ((dns_result != ISC_R_SUCCESS) &&
			    (dns_result != DNS_R_NEWORIGIN)) {
				if (dns_result != ISC_R_NOMORE) {
					t_info("dns_rbtnodechain_next "
					       "failed %s\n",
					       dns_result_totext(dns_result));
					++nprobs;
				}
				break;
			}

			t_info("next name:\t<%s>\n", fixedname_totext(&name));
			t_info("next origin:\t<%s>\n",
			       fixedname_totext(&origin));

			dns_fixedname_init(&fullname2);
			dns_result = dns_name_concatenate(
			       dns_fixedname_name(&name),
			       dns_name_isabsolute(dns_fixedname_name(&name)) ?
					    NULL : dns_fixedname_name(&origin),
			       dns_fixedname_name(&fullname2), NULL);
			if (dns_result != ISC_R_SUCCESS) {
				t_info("dns_name_concatenate failed %s\n",
				       dns_result_totext(dns_result));
				++nprobs;
				break;
			}

			t_info("comparing\t<%s>\n",
			       fixedname_totext(&fullname1));
			t_info("\twith\t<%s>\n", fixedname_totext(&fullname2));

			(void)dns_name_fullcompare(
						dns_fixedname_name(&fullname1),
						dns_fixedname_name(&fullname2),
						&order, &nlabels);

			if (order >= 0) {
			    t_info("unexpected order %s %s %s\n",
			       dnsname_totext(dns_fixedname_name(&fullname1)),
			       order == 0 ? "==" : ">",
			       dnsname_totext(dns_fixedname_name(&fullname2)));
				++nprobs;
			}
		}

		++cnt;
	} while (1);

	return (nprobs);
}

/*
 * Test by exercising the first|last|next|prev funcs in useful ways.
 */

static int
t_namechk(isc_result_t dns_result, dns_fixedname_t *dns_name, char *exp_name,
	  dns_fixedname_t *dns_origin, char *exp_origin,
	  isc_result_t exp_result)
{
	int	nfails;

	nfails = 0;

	if (fixedname_cmp(dns_name, exp_name)) {
		t_info("\texpected name of %s, got %s\n",
				exp_name, fixedname_totext(dns_name));
		++nfails;
	}
	if (exp_origin != NULL) {
		t_info("checking for DNS_R_NEWORIGIN\n");
		if (dns_result == exp_result) {
			if (fixedname_cmp(dns_origin, exp_origin)) {
				t_info("\torigin %s, expected %s\n",
				       fixedname_totext(dns_origin),
				       exp_origin);
				++nfails;
			}
		} else {
			t_info("\tgot %s\n", dns_result_totext(dns_result));
			++nfails;
		}
	}
	return(nfails);
}

static int
t_dns_rbtnodechain_init(char *dbfile, char *findname,
			char *nextname, char *nextorigin,
			char *prevname, char *prevorigin,
			char *firstname, char *firstorigin,
			char *lastname, char *lastorigin)
{
	int			result;
	int			len;
	int			nfails;
	dns_rbt_t		*rbt;
	dns_rbtnode_t		*node;
	dns_rbtnodechain_t	chain;
	isc_mem_t		*mctx;
	isc_entropy_t		*ectx;
	isc_result_t		isc_result;
	isc_result_t		dns_result;
	dns_fixedname_t		dns_findname;
	dns_fixedname_t		dns_foundname;
	dns_fixedname_t		dns_firstname;
	dns_fixedname_t		dns_lastname;
	dns_fixedname_t		dns_prevname;
	dns_fixedname_t		dns_nextname;
	dns_fixedname_t		dns_origin;
	isc_buffer_t		isc_buffer;

	result = T_UNRESOLVED;

	nfails = 0;
	mctx = NULL;
	ectx = NULL;

	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(result);
	}

	isc_result = isc_entropy_create(mctx, &ectx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_entropy_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_hash_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	dns_rbtnodechain_init(&chain, mctx);

	rbt = NULL;
	if (rbt_init(dbfile, &rbt, mctx)) {
		t_info("rbt_init %s failed\n", dbfile);
		isc_hash_destroy();
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(result);
	}

	len = strlen(findname);
	isc_buffer_init(&isc_buffer, findname, len);
	isc_buffer_add(&isc_buffer, len);

	dns_fixedname_init(&dns_foundname);
	dns_fixedname_init(&dns_findname);
	dns_fixedname_init(&dns_firstname);
	dns_fixedname_init(&dns_origin);
	dns_fixedname_init(&dns_lastname);
	dns_fixedname_init(&dns_prevname);
	dns_fixedname_init(&dns_nextname);

	dns_result = dns_name_fromtext(dns_fixedname_name(&dns_findname),
					&isc_buffer, NULL, 0, NULL);

	if (dns_result != ISC_R_SUCCESS) {
		t_info("dns_name_fromtext failed %s\n",
		       dns_result_totext(dns_result));
		return(result);
	}

	/*
	 * Set the starting node.
	 */
	node = NULL;
	dns_result = dns_rbt_findnode(rbt, dns_fixedname_name(&dns_findname),
				      dns_fixedname_name(&dns_foundname),
				      &node, &chain, DNS_RBTFIND_EMPTYDATA,
				      NULL, NULL);

	if (dns_result != ISC_R_SUCCESS) {
		t_info("dns_rbt_findnode failed %s\n",
		       dns_result_totext(dns_result));
		return(result);
	}

	/*
	 * Check next.
	 */
	t_info("checking for next name of %s and new origin of %s\n",
	       nextname, nextorigin);
	dns_result = dns_rbtnodechain_next(&chain,
					   dns_fixedname_name(&dns_nextname),
					   dns_fixedname_name(&dns_origin));

	if ((dns_result != ISC_R_SUCCESS) &&
	    (dns_result != DNS_R_NEWORIGIN)) {
		t_info("dns_rbtnodechain_next unexpectedly returned %s\n",
		       dns_result_totext(dns_result));
	}

	nfails += t_namechk(dns_result, &dns_nextname, nextname, &dns_origin,
			    nextorigin, DNS_R_NEWORIGIN);

	/*
	 * Set the starting node again.
	 */
	node = NULL;
	dns_fixedname_init(&dns_foundname);
	dns_result = dns_rbt_findnode(rbt, dns_fixedname_name(&dns_findname),
				      dns_fixedname_name(&dns_foundname),
				      &node, &chain, DNS_RBTFIND_EMPTYDATA,
				      NULL, NULL);

	if (dns_result != ISC_R_SUCCESS) {
		t_info("\tdns_rbt_findnode failed %s\n",
		       dns_result_totext(dns_result));
		return(result);
	}

	/*
	 * Check previous.
	 */
	t_info("checking for previous name of %s and new origin of %s\n",
	       prevname, prevorigin);
	dns_fixedname_init(&dns_origin);
	dns_result = dns_rbtnodechain_prev(&chain,
					   dns_fixedname_name(&dns_prevname),
					   dns_fixedname_name(&dns_origin));

	if ((dns_result != ISC_R_SUCCESS) &&
	    (dns_result != DNS_R_NEWORIGIN)) {
		t_info("dns_rbtnodechain_prev unexpectedly returned %s\n",
		       dns_result_totext(dns_result));
	}
	nfails += t_namechk(dns_result, &dns_prevname, prevname, &dns_origin,
			    prevorigin, DNS_R_NEWORIGIN);

	/*
	 * Check first.
	 */
	t_info("checking for first name of %s and new origin of %s\n",
	       firstname, firstorigin);
	dns_result = dns_rbtnodechain_first(&chain, rbt,
					    dns_fixedname_name(&dns_firstname),
					    dns_fixedname_name(&dns_origin));

	if (dns_result != DNS_R_NEWORIGIN) {
		t_info("dns_rbtnodechain_first unexpectedly returned %s\n",
		       dns_result_totext(dns_result));
	}
	nfails += t_namechk(dns_result, &dns_firstname, firstname,
			    &dns_origin, firstorigin, DNS_R_NEWORIGIN);

	/*
	 * Check last.
	 */
	t_info("checking for last name of %s\n", lastname);
	dns_result = dns_rbtnodechain_last(&chain, rbt,
					   dns_fixedname_name(&dns_lastname),
					   dns_fixedname_name(&dns_origin));

	if (dns_result != DNS_R_NEWORIGIN) {
		t_info("dns_rbtnodechain_last unexpectedly returned %s\n",
		       dns_result_totext(dns_result));
	}
	nfails += t_namechk(dns_result, &dns_lastname, lastname, &dns_origin,
			    lastorigin, DNS_R_NEWORIGIN);

	/*
	 * Check node ordering.
	 */
	t_info("checking node ordering\n");
	nfails += t9_walkchain(&chain, rbt);

	if (nfails)
		result = T_FAIL;
	else
		result = T_PASS;

	dns_rbtnodechain_invalidate(&chain);
	dns_rbt_destroy(&rbt);

	isc_hash_destroy();
	isc_entropy_detach(&ectx);
	isc_mem_destroy(&mctx);

	return(result);
}

static int
test_dns_rbtnodechain_init(const char *filename) {
	FILE		*fp;
	char		*p;
	int		line;
	int		cnt;
	int		result;
	int		nfails;
	int		nprobs;

	nfails = 0;
	nprobs = 0;

	fp = fopen(filename, "r");
	if (fp != NULL) {
		line = 0;
		while ((p = t_fgetbs(fp)) != NULL) {

			++line;

			/*
			 * Skip comment lines.
			 */
			if ((isspace((unsigned char)*p)) || (*p == '#')) {
				(void)free(p);
				continue;
			}

			cnt = t_bustline(p, Tokens);
			if (cnt == 10) {
				result = t_dns_rbtnodechain_init(
						Tokens[0],  /* dbfile */
						Tokens[1],  /* startname */
						Tokens[2],  /* nextname */
						Tokens[3],  /* nextorigin */
						Tokens[4],  /* prevname */
						Tokens[5],  /* prevorigin */
						Tokens[6],  /* firstname */
						Tokens[7],  /* firstorigin */
						Tokens[8],  /* lastname */
						Tokens[9]); /* lastorigin */
				if (result != T_PASS) {
					if (result == T_FAIL)
						++nfails;
					else
						++nprobs;
				}
			} else {
				t_info("bad format in %s at line %d\n",
						filename, line);
				++nprobs;
			}

			(void)free(p);
		}
		(void)fclose(fp);
	} else {
		t_info("Missing datafile %s\n", filename);
		++nprobs;
	}

	result = T_UNRESOLVED;

	if ((nfails == 0) && (nprobs == 0))
		result = T_PASS;
	else if (nfails)
		result = T_FAIL;

	return(result);
}

static void
t9() {
	int	result;

	t_assert("dns_rbtnodechain_init", 9, T_REQUIRED, "%s", a9);
	result = test_dns_rbtnodechain_init("dns_rbtnodechain_init_data");
	t_result(result);
}

static int
t_dns_rbtnodechain_first(char *dbfile, char *expected_firstname,
				char *expected_firstorigin,
				char *expected_nextname,
				char *expected_nextorigin)
{
	int			result;
	int			nfails;
	dns_rbt_t		*rbt;
	dns_rbtnodechain_t	chain;
	isc_mem_t		*mctx;
	isc_entropy_t		*ectx;
	isc_result_t		isc_result;
	isc_result_t		dns_result;
	dns_fixedname_t		dns_name;
	dns_fixedname_t		dns_origin;
	isc_result_t		expected_result;

	REQUIRE(dbfile != NULL);
	REQUIRE(expected_firstname != NULL);
	REQUIRE(expected_firstorigin != NULL);
	REQUIRE(expected_nextname != NULL);
	REQUIRE(expected_nextorigin != NULL);

	result = T_UNRESOLVED;

	nfails = 0;
	mctx = NULL;
	ectx = NULL;

	dns_fixedname_init(&dns_name);
	dns_fixedname_init(&dns_origin);

	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(result);
	}

	isc_result = isc_entropy_create(mctx, &ectx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_entropy_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_hash_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	dns_rbtnodechain_init(&chain, mctx);

	rbt = NULL;
	if (rbt_init(dbfile, &rbt, mctx)) {
		t_info("rbt_init %s failed\n", dbfile);
		isc_hash_destroy();
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(result);
	}

	t_info("testing for first name of %s, origin of %s\n",
	       expected_firstname, expected_firstorigin);

	dns_result = dns_rbtnodechain_first(&chain, rbt,
					    dns_fixedname_name(&dns_name),
					    dns_fixedname_name(&dns_origin));

	if (dns_result != DNS_R_NEWORIGIN)
		t_info("dns_rbtnodechain_first unexpectedly returned %s\n",
		       dns_result_totext(dns_result));

	nfails += t_namechk(dns_result, &dns_name, expected_firstname,
			    &dns_origin, expected_firstorigin, DNS_R_NEWORIGIN);

	dns_fixedname_init(&dns_name);
	dns_result = dns_rbtnodechain_next(&chain,
			dns_fixedname_name(&dns_name),
			dns_fixedname_name(&dns_origin));

	t_info("testing for next name of %s, origin of %s\n",
			expected_nextname, expected_nextorigin);

	if ((dns_result != ISC_R_SUCCESS) && (dns_result != DNS_R_NEWORIGIN))
		t_info("dns_rbtnodechain_next unexpectedly returned %s\n",
		       dns_result_totext(dns_result));

	if (strcasecmp(expected_firstorigin, expected_nextorigin) == 0)
		expected_result = ISC_R_SUCCESS;
	else
		expected_result = DNS_R_NEWORIGIN;
	nfails += t_namechk(dns_result, &dns_name, expected_nextname,
			    &dns_origin, expected_nextorigin, expected_result);

	if (nfails)
		result = T_FAIL;
	else
		result = T_PASS;

	dns_rbtnodechain_invalidate(&chain);

	dns_rbt_destroy(&rbt);
	isc_hash_destroy();
	isc_entropy_detach(&ectx);
	isc_mem_destroy(&mctx);
	return(result);
}

static int
test_dns_rbtnodechain_first(const char *filename) {
	FILE		*fp;
	char		*p;
	int		line;
	int		cnt;
	int		result;
	int		nfails;
	int		nprobs;

	nfails = 0;
	nprobs = 0;

	fp = fopen(filename, "r");
	if (fp != NULL) {
		line = 0;
		while ((p = t_fgetbs(fp)) != NULL) {

			++line;

			/*
			 * Skip comment lines.
			 */
			if ((isspace((unsigned char)*p)) || (*p == '#')) {
				(void)free(p);
				continue;
			}

			cnt = t_bustline(p, Tokens);
			if (cnt == 5) {
				result = t_dns_rbtnodechain_first(
						Tokens[0],  /* dbfile */
						Tokens[1],  /* firstname */
						Tokens[2],  /* firstorigin */
						Tokens[3],  /* nextname */
						Tokens[4]); /* nextorigin */
				if (result != T_PASS) {
					if (result == T_FAIL)
						++nfails;
					else
						++nprobs;
				}
			} else {
				t_info("bad format in %s at line %d\n",
						filename, line);
				++nprobs;
			}

			(void)free(p);
		}
		(void)fclose(fp);
	} else {
		t_info("Missing datafile %s\n", filename);
		++nprobs;
	}

	result = T_UNRESOLVED;

	if ((nfails == 0) && (nprobs == 0))
		result = T_PASS;
	else if (nfails)
		result = T_FAIL;

	return(result);
}

static const char *a10 = "a call to "
			"dns_rbtnodechain_first(chain, rbt, name, origin) "
			"sets name to point to the root of the tree, "
			"origin to point to the origin, "
			"and returns DNS_R_NEWORIGIN";

static void
t10() {
	int	result;

	t_assert("dns_rbtnodechain_first", 10, T_REQUIRED, "%s", a10);
	result = test_dns_rbtnodechain_first("dns_rbtnodechain_first_data");
	t_result(result);
}

static int
t_dns_rbtnodechain_last(char *dbfile, char *expected_lastname,
			char *expected_lastorigin,
			char *expected_prevname,
			char *expected_prevorigin)
{

	int			result;
	int			nfails;
	dns_rbt_t		*rbt;
	dns_rbtnodechain_t	chain;
	isc_mem_t		*mctx;
	isc_entropy_t		*ectx;
	isc_result_t		isc_result;
	isc_result_t		dns_result;
	dns_fixedname_t		dns_name;
	dns_fixedname_t		dns_origin;
	isc_result_t		expected_result;

	REQUIRE(dbfile != NULL);
	REQUIRE(expected_lastname != NULL);
	REQUIRE(expected_lastorigin != NULL);
	REQUIRE(expected_prevname != NULL);
	REQUIRE(expected_prevorigin != NULL);

	result = T_UNRESOLVED;

	nfails = 0;
	mctx = NULL;
	ectx = NULL;

	dns_fixedname_init(&dns_name);
	dns_fixedname_init(&dns_origin);

	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(result);
	}

	isc_result = isc_entropy_create(mctx, &ectx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_entropy_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_hash_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	dns_rbtnodechain_init(&chain, mctx);

	rbt = NULL;
	if (rbt_init(dbfile, &rbt, mctx)) {
		t_info("rbt_init %s failed\n", dbfile);
		isc_hash_destroy();
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(result);
	}

	t_info("testing for last name of %s, origin of %s\n",
	       expected_lastname, expected_lastorigin);

	dns_result = dns_rbtnodechain_last(&chain, rbt,
					   dns_fixedname_name(&dns_name),
					   dns_fixedname_name(&dns_origin));

	if (dns_result != DNS_R_NEWORIGIN) {
		t_info("dns_rbtnodechain_last unexpectedly returned %s\n",
		       dns_result_totext(dns_result));
	}
	nfails += t_namechk(dns_result, &dns_name, expected_lastname,
			    &dns_origin, expected_lastorigin, DNS_R_NEWORIGIN);

	t_info("testing for previous name of %s, origin of %s\n",
	       expected_prevname, expected_prevorigin);

	dns_fixedname_init(&dns_name);
	dns_result = dns_rbtnodechain_prev(&chain,
					   dns_fixedname_name(&dns_name),
					   dns_fixedname_name(&dns_origin));

	if ((dns_result != ISC_R_SUCCESS) &&
	    (dns_result != DNS_R_NEWORIGIN)) {
		t_info("dns_rbtnodechain_prev unexpectedly returned %s\n",
		       dns_result_totext(dns_result));
	}
	if (strcasecmp(expected_lastorigin, expected_prevorigin) == 0)
		expected_result = ISC_R_SUCCESS;
	else
		expected_result = DNS_R_NEWORIGIN;
	nfails += t_namechk(dns_result, &dns_name, expected_prevname,
			    &dns_origin, expected_prevorigin, expected_result);

	if (nfails)
		result = T_FAIL;
	else
		result = T_PASS;

	dns_rbtnodechain_invalidate(&chain);
	dns_rbt_destroy(&rbt);

	isc_hash_destroy();
	isc_entropy_detach(&ectx);
	isc_mem_destroy(&mctx);

	return(result);
}

static int
test_dns_rbtnodechain_last(const char *filename) {
	FILE		*fp;
	char		*p;
	int		line;
	int		cnt;
	int		result;
	int		nfails;
	int		nprobs;

	nfails = 0;
	nprobs = 0;

	fp = fopen(filename, "r");
	if (fp != NULL) {
		line = 0;
		while ((p = t_fgetbs(fp)) != NULL) {

			++line;

			/*
			 * Skip comment lines.
			 */
			if ((isspace((unsigned char)*p)) || (*p == '#')) {
				(void)free(p);
				continue;
			}

			cnt = t_bustline(p, Tokens);
			if (cnt == 5) {
				result = t_dns_rbtnodechain_last(
						Tokens[0],     /* dbfile */
						Tokens[1],     /* lastname */
						Tokens[2],     /* lastorigin */
						Tokens[3],     /* prevname */
						Tokens[4]);    /* prevorigin */
				if (result != T_PASS) {
					if (result == T_FAIL)
						++nfails;
					else
						++nprobs;
				}
			} else {
				t_info("bad format in %s at line %d\n",
				       filename, line);
				++nprobs;
			}

			(void)free(p);
		}
		(void)fclose(fp);
	} else {
		t_info("Missing datafile %s\n", filename);
		++nprobs;
	}

	result = T_UNRESOLVED;

	if ((nfails == 0) && (nprobs == 0))
		result = T_PASS;
	else if (nfails)
		result = T_FAIL;

	return(result);
}

static const char *a11 = "a call to "
			"dns_rbtnodechain_last(chain, rbt, name, origin) "
			"sets name to point to the last node of the megatree, "
			"origin to the name of the level above it, "
			"and returns DNS_R_NEWORIGIN";

static void
t11() {
	int	result;

	t_assert("dns_rbtnodechain_last", 11, T_REQUIRED, "%s", a11);
	result = test_dns_rbtnodechain_last("dns_rbtnodechain_last_data");
	t_result(result);
}

static int
t_dns_rbtnodechain_next(char *dbfile, char *findname,
			char *nextname, char *nextorigin)
{

	int			result;
	int			len;
	int			nfails;
	dns_rbt_t		*rbt;
	dns_rbtnode_t		*node;
	dns_rbtnodechain_t	chain;
	isc_mem_t		*mctx;
	isc_entropy_t		*ectx;
	isc_result_t		isc_result;
	isc_result_t		dns_result;
	dns_fixedname_t		dns_findname;
	dns_fixedname_t		dns_foundname;
	dns_fixedname_t		dns_nextname;
	dns_fixedname_t		dns_origin;
	isc_buffer_t		isc_buffer;

	result = T_UNRESOLVED;

	nfails = 0;
	mctx = NULL;
	ectx = NULL;

	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(result);
	}

	isc_result = isc_entropy_create(mctx, &ectx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_entropy_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_hash_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	dns_rbtnodechain_init(&chain, mctx);

	rbt = NULL;
	if (rbt_init(dbfile, &rbt, mctx)) {
		t_info("rbt_init %s failed\n", dbfile);
		isc_hash_destroy();
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(result);
	}

	len = strlen(findname);
	isc_buffer_init(&isc_buffer, findname, len);
	isc_buffer_add(&isc_buffer, len);

	dns_fixedname_init(&dns_foundname);
	dns_fixedname_init(&dns_findname);
	dns_fixedname_init(&dns_nextname);
	dns_fixedname_init(&dns_origin);

	dns_result = dns_name_fromtext(dns_fixedname_name(&dns_findname),
				       &isc_buffer, NULL, 0, NULL);

	if (dns_result != ISC_R_SUCCESS) {
		t_info("dns_name_fromtext failed %s\n",
		       dns_result_totext(dns_result));
		return(result);
	}

	/*
	 * Set the starting node.
	 */
	node = NULL;
	dns_result = dns_rbt_findnode(rbt, dns_fixedname_name(&dns_findname),
				      dns_fixedname_name(&dns_foundname),
				      &node, &chain, DNS_RBTFIND_EMPTYDATA,
				      NULL, NULL);

	if (dns_result != ISC_R_SUCCESS) {
		t_info("dns_rbt_findnode failed %s\n",
		       dns_result_totext(dns_result));
		return(result);
	}

	/*
	 * Check next.
	 */
	t_info("checking for next name of %s and new origin of %s\n",
	       nextname, nextorigin);
	dns_result = dns_rbtnodechain_next(&chain,
					   dns_fixedname_name(&dns_nextname),
					   dns_fixedname_name(&dns_origin));

	if ((dns_result != ISC_R_SUCCESS) && (dns_result != DNS_R_NEWORIGIN)) {
		t_info("dns_rbtnodechain_next unexpectedly returned %s\n",
		       dns_result_totext(dns_result));
	}

	nfails += t_namechk(dns_result, &dns_nextname, nextname, &dns_origin,
			    nextorigin, DNS_R_NEWORIGIN);

	if (nfails)
		result = T_FAIL;
	else
		result = T_PASS;

	dns_rbtnodechain_invalidate(&chain);
	dns_rbt_destroy(&rbt);

	isc_hash_destroy();
	isc_entropy_detach(&ectx);
	isc_mem_destroy(&mctx);

	return(result);
}

static int
test_dns_rbtnodechain_next(const char *filename) {
	FILE		*fp;
	char		*p;
	int		line;
	int		cnt;
	int		result;
	int		nfails;
	int		nprobs;

	nfails = 0;
	nprobs = 0;

	fp = fopen(filename, "r");
	if (fp != NULL) {
		line = 0;
		while ((p = t_fgetbs(fp)) != NULL) {

			++line;

			/*
			 * Skip comment lines.
			 */
			if ((isspace((unsigned char)*p)) || (*p == '#')) {
				(void)free(p);
				continue;
			}

			cnt = t_bustline(p, Tokens);
			if (cnt == 4) {
				result = t_dns_rbtnodechain_next(
						Tokens[0],     /* dbfile */
						Tokens[1],     /* findname */
						Tokens[2],     /* nextname */
						Tokens[3]);    /* nextorigin */
				if (result != T_PASS) {
					if (result == T_FAIL)
						++nfails;
					else
						++nprobs;
				}
			} else {
				t_info("bad format in %s at line %d\n",
				       filename, line);
				++nprobs;
			}

			(void)free(p);
		}
		(void)fclose(fp);
	} else {
		t_info("Missing datafile %s\n", filename);
		++nprobs;
	}

	result = T_UNRESOLVED;

	if ((nfails == 0) && (nprobs == 0))
		result = T_PASS;
	else if (nfails)
		result = T_FAIL;

	return(result);
}

static const char *a12 = "a call to "
			"dns_rbtnodechain_next(chain, name, origin) "
			"sets name to point to the next node of the tree "
			"and returns ISC_R_SUCCESS or "
			"DNS_R_NEWORIGIN on success";


static void
t12() {
	int	result;

	t_assert("dns_rbtnodechain_next", 12, T_REQUIRED, "%s", a12);
	result = test_dns_rbtnodechain_next("dns_rbtnodechain_next_data");
	t_result(result);
}

static int
t_dns_rbtnodechain_prev(char *dbfile, char *findname, char *prevname,
			char *prevorigin)
{
	int			result;
	int			len;
	int			nfails;
	dns_rbt_t		*rbt;
	dns_rbtnode_t		*node;
	dns_rbtnodechain_t	chain;
	isc_mem_t		*mctx;
	isc_entropy_t		*ectx = NULL;
	isc_result_t		isc_result;
	isc_result_t		dns_result;
	dns_fixedname_t		dns_findname;
	dns_fixedname_t		dns_foundname;
	dns_fixedname_t		dns_prevname;
	dns_fixedname_t		dns_origin;
	isc_buffer_t		isc_buffer;

	result = T_UNRESOLVED;

	nfails = 0;
	mctx = NULL;
	ectx = NULL;

	isc_result = isc_mem_create(0, 0, &mctx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_mem_create failed %s\n",
		       isc_result_totext(isc_result));
		return(result);
	}

	isc_result = isc_entropy_create(mctx, &ectx);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_entropy_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	isc_result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE);
	if (isc_result != ISC_R_SUCCESS) {
		t_info("isc_hash_create: %s: exiting\n",
		       dns_result_totext(isc_result));
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(T_UNRESOLVED);
	}

	dns_rbtnodechain_init(&chain, mctx);

	rbt = NULL;
	if (rbt_init(dbfile, &rbt, mctx)) {
		t_info("rbt_init %s failed\n", dbfile);
		isc_hash_destroy();
		isc_entropy_detach(&ectx);
		isc_mem_destroy(&mctx);
		return(result);
	}

	len = strlen(findname);
	isc_buffer_init(&isc_buffer, findname, len);
	isc_buffer_add(&isc_buffer, len);

	dns_fixedname_init(&dns_foundname);
	dns_fixedname_init(&dns_findname);
	dns_fixedname_init(&dns_prevname);
	dns_fixedname_init(&dns_origin);

	dns_result = dns_name_fromtext(dns_fixedname_name(&dns_findname),
				       &isc_buffer, NULL, 0, NULL);

	if (dns_result != ISC_R_SUCCESS) {
		t_info("dns_name_fromtext failed %s\n",
		       dns_result_totext(dns_result));
		return(result);
	}

	/*
	 * Set the starting node.
	 */
	node = NULL;
	dns_result = dns_rbt_findnode(rbt, dns_fixedname_name(&dns_findname),
				      dns_fixedname_name(&dns_foundname),
				      &node, &chain, DNS_RBTFIND_EMPTYDATA,
				      NULL, NULL);

	if (dns_result != ISC_R_SUCCESS) {
		t_info("dns_rbt_findnode failed %s\n",
		       dns_result_totext(dns_result));
		return(result);
	}

	/*
	 * Check next.
	 */
	t_info("checking for next name of %s and new origin of %s\n",
	       prevname, prevorigin);
	dns_result = dns_rbtnodechain_prev(&chain,
					   dns_fixedname_name(&dns_prevname),
					   dns_fixedname_name(&dns_origin));

	if ((dns_result != ISC_R_SUCCESS) && (dns_result != DNS_R_NEWORIGIN)) {
		t_info("dns_rbtnodechain_prev unexpectedly returned %s\n",
		       dns_result_totext(dns_result));
	}

	nfails += t_namechk(dns_result, &dns_prevname, prevname, &dns_origin,
			    prevorigin, DNS_R_NEWORIGIN);

	if (nfails)
		result = T_FAIL;
	else
		result = T_PASS;

	dns_rbtnodechain_invalidate(&chain);
	dns_rbt_destroy(&rbt);

	isc_hash_destroy();
	isc_entropy_detach(&ectx);
	isc_mem_destroy(&mctx);

	return(result);
}

static int
test_dns_rbtnodechain_prev(const char *filename) {
	FILE		*fp;
	char		*p;
	int		line;
	int		cnt;
	int		result;
	int		nfails;
	int		nprobs;

	nfails = 0;
	nprobs = 0;

	fp = fopen(filename, "r");
	if (fp != NULL) {
		line = 0;
		while ((p = t_fgetbs(fp)) != NULL) {

			++line;

			/*
			 * Skip comment lines.
			 */
			if ((isspace((unsigned char)*p)) || (*p == '#')) {
				(void)free(p);
				continue;
			}

			cnt = t_bustline(p, Tokens);
			if (cnt == 4) {
				result = t_dns_rbtnodechain_prev(
						Tokens[0],     /* dbfile */
						Tokens[1],     /* findname */
						Tokens[2],     /* prevname */
						Tokens[3]);    /* prevorigin */
				if (result != T_PASS) {
					if (result == T_FAIL)
						++nfails;
					else
						++nprobs;
				}
			} else {
				t_info("bad format in %s at line %d\n",
						filename, line);
				++nprobs;
			}

			(void)free(p);
		}
		(void)fclose(fp);
	} else {
		t_info("Missing datafile %s\n", filename);
		++nprobs;
	}

	result = T_UNRESOLVED;

	if ((nfails == 0) && (nprobs == 0))
		result = T_PASS;
	else if (nfails)
		result = T_FAIL;

	return(result);
}

static const char *a13 = "a call to "
			"dns_rbtnodechain_prev(chain, name, origin) "
			"sets name to point to the previous node of the tree "
			"and returns ISC_R_SUCCESS or "
			"DNS_R_NEWORIGIN on success";

static void
t13() {
	int	result;

	t_assert("dns_rbtnodechain_prev", 13, T_REQUIRED, "%s", a13);
	result = test_dns_rbtnodechain_prev("dns_rbtnodechain_prev_data");
	t_result(result);
}

testspec_t	T_testlist[] = {
	{	(PFV) t1,	"dns_rbt_create"		},
	{	(PFV) t2,	"dns_rbt_addname 1"		},
	{	(PFV) t3,	"dns_rbt_addname 2"		},
	{	(PFV) t4,	"dns_rbt_deletename 1"		},
	{	(PFV) t5,	"dns_rbt_deletename 2"		},
	{	(PFV) t6,	"dns_rbt_findname 1"		},
	{	(PFV) t7,	"dns_rbt_findname 2"		},
	{	(PFV) t8,	"dns_rbt_findname 3"		},
	{	(PFV) t9,	"dns_rbtnodechain_init"		},
	{	(PFV) t10,	"dns_rbtnodechain_first"	},
	{	(PFV) t11,	"dns_rbtnodechain_last"		},
	{	(PFV) t12,	"dns_rbtnodechain_next"		},
	{	(PFV) t13,	"dns_rbtnodechain_prev"		},
	{	(PFV) 0,	NULL				}
};

#ifdef WIN32
int
main(int argc, char **argv) {
	t_settests(T_testlist);
	return (t_main(argc, argv));
}
#endif
