/*
 * Copyright (C) 2015 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.
 */

#include <config.h>

#include "dhcpd.h"

#include <atf-c.h>

/*
 * Test the lease queue code.  These tests will verify that we can
 * add, find and remove leases from the lease queue code
 *
 * Thoughout the tests
 * lq will be the lease queue for which we add or removing leases
 * test_leaseX will be the leases we add or remove
 * We only care about the sort_time in the lease structure for these
 * tests but need to do a lease_reference in order to keep the ref
 * count positive to avoid the omapi code trying to free the object.
 * We can't use lease_allocate easily as we haven't set up the omapi
 * object information in the test.
 */

#if defined (BINARY_LEASES)
#define INIT_LQ(LQ) memset(&(LQ), 0, sizeof(struct leasechain))
#else
#define INIT_LQ(LQ) lq = NULL
#endif

/* Test basic leaseq functions with a single lease */
/*- empty, add, get, and remove */
ATF_TC(leaseq_basic);
ATF_TC_HEAD(leaseq_basic, tc)
{
	atf_tc_set_md_var(tc, "descr", "Verify basic functions");
}

ATF_TC_BODY(leaseq_basic, tc)
{
	LEASE_STRUCT lq;
	struct lease test_lease1, *check_lease;

	INIT_LQ(lq);

	memset(&test_lease1, 0, sizeof(struct lease));
	test_lease1.sort_time = 10;
	check_lease = NULL;
	lease_reference(&check_lease, &test_lease1, MDL);

	/* Check that the lq is empty */
	if ((LEASE_NOT_EMPTY(lq)) || (LEASE_NOT_EMPTYP(&lq)))
		atf_tc_fail("lq not empty at start.");

	/* And that getting the first lease is okay when queue is empty  */
	if ((LEASE_GET_FIRST(lq) != NULL) || (LEASE_GET_FIRSTP(&lq) != NULL))
		atf_tc_fail("lease not null");

	/* Add a lease */
	LEASE_INSERTP(&lq, &test_lease1);

	/* lq shouldn't be empty anymore */
	if (!(LEASE_NOT_EMPTY(lq)) || !(LEASE_NOT_EMPTYP(&lq)))
		atf_tc_fail("lq empty after insertion.");

	/* We should have the same lease we inserted */
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease1)
		atf_tc_fail("leases don't match");

	/* We should have the same lease we inserted */
	check_lease = LEASE_GET_FIRSTP(&lq);
	if (check_lease != &test_lease1)
		atf_tc_fail("leases don't match");

	/* Check that doing a get on the last lease returns NULL */
	if ((LEASE_GET_NEXT(lq, check_lease) != NULL) ||
	    (LEASE_GET_NEXTP(&lq, check_lease) != NULL)) {
		atf_tc_fail("Next not null");
	}

	/* Remove the lease */
	LEASE_REMOVEP(&lq, &test_lease1);

	/* and verify the lease queue is empty again */
	if ((LEASE_NOT_EMPTY(lq)) || (LEASE_NOT_EMPTYP(&lq)))
		atf_tc_fail("lq not empty afer removal");

	/* And that getting the first lease is okay when queue is empty  */
	if ((LEASE_GET_FIRST(lq) != NULL) || (LEASE_GET_FIRSTP(&lq) != NULL))
		atf_tc_fail("lease not null");
}

/* Test if we can add leases to the end of the list and remove them
 * from the end */
ATF_TC(leaseq_add_to_end);
ATF_TC_HEAD(leaseq_add_to_end, tc)
{
	atf_tc_set_md_var(tc, "descr", "Verify adding to end of list");
}

ATF_TC_BODY(leaseq_add_to_end, tc)
{
	LEASE_STRUCT lq;
	struct lease test_lease[3], *check_lease;
	int i;

	INIT_LQ(lq);

	/* create and add 3 leases */
	for (i = 0; i < 3; i++) {
		memset(&test_lease[i], 0, sizeof(struct lease));
		test_lease[i].sort_time = i;
		check_lease = NULL;
		lease_reference(&check_lease, &test_lease[i], MDL);
		LEASE_INSERTP(&lq, &test_lease[i]);
	}

	/* check ordering of leases */
	check_lease = LEASE_GET_FIRST(lq);
	for (i = 0; i < 3; i++) {
		if (check_lease != &test_lease[i])
			atf_tc_fail("leases don't match, %d", i);
		check_lease = LEASE_GET_NEXT(lq, check_lease);
	}
	if (check_lease != NULL)
		atf_tc_fail("lease not null");

	/* Remove the last lease and check the list */
	LEASE_REMOVEP(&lq, &test_lease[2]);
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[0])
		atf_tc_fail("wrong lease after remove, 1");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[1])
		atf_tc_fail("wrong lease after remove, 2");

	LEASE_REMOVEP(&lq, &test_lease[1]);
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[0])
		atf_tc_fail("wrong lease after remove, 3");

	LEASE_REMOVEP(&lq, check_lease);

	/* The lease queue should now be empty */
	if (LEASE_NOT_EMPTY(lq))
		atf_tc_fail("lq not empty afer removal");
}

/* Test if we can add leases to the start of the list and remove them
 * from the start */
ATF_TC(leaseq_add_to_start);
ATF_TC_HEAD(leaseq_add_to_start, tc)
{
	atf_tc_set_md_var(tc, "descr", "Verify adding to start of list");
}

ATF_TC_BODY(leaseq_add_to_start, tc)
{
	LEASE_STRUCT lq;
	struct lease test_lease[3], *check_lease;
	int i;

	INIT_LQ(lq);

	/* create 3 leases */
	for (i = 0; i < 3; i++) {
		memset(&test_lease[i], 0, sizeof(struct lease));
		test_lease[i].sort_time = i;
		check_lease = NULL;
		lease_reference(&check_lease, &test_lease[i], MDL);
	}

	/* Add leases */
	LEASE_INSERTP(&lq, &test_lease[2]);
	LEASE_INSERTP(&lq, &test_lease[1]);
	LEASE_INSERTP(&lq, &test_lease[0]);

	/* check ordering of leases */
	check_lease = LEASE_GET_FIRST(lq);
	for (i = 0; i < 3; i++) {
		if (check_lease != &test_lease[i])
			atf_tc_fail("leases don't match, %d", i);
		check_lease = LEASE_GET_NEXT(lq, check_lease);
	}
	if (check_lease != NULL)
		atf_tc_fail("lease not null");

	/* Remove the first lease and check the next one */
	check_lease = LEASE_GET_FIRST(lq);
	LEASE_REMOVEP(&lq, check_lease);
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[1])
		atf_tc_fail("wrong lease after remove, 1");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[2])
		atf_tc_fail("wrong lease after remove, 2");

	check_lease = LEASE_GET_FIRST(lq);
	LEASE_REMOVEP(&lq, check_lease);
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[2])
		atf_tc_fail("wrong lease after remove, 3");

	LEASE_REMOVEP(&lq, check_lease);

	/* The lease queue should now be empty */
	if (LEASE_NOT_EMPTY(lq))
		atf_tc_fail("lq not empty afer removal");
}

/* Test if we can add leases to the middle of the list and remove them
 * from the middle */
ATF_TC(leaseq_add_to_middle);
ATF_TC_HEAD(leaseq_add_to_middle, tc)
{
	atf_tc_set_md_var(tc, "descr", "Verify adding to end of list");
}

ATF_TC_BODY(leaseq_add_to_middle, tc)
{
	LEASE_STRUCT lq;
	struct lease test_lease[3], *check_lease;
	int i;

	INIT_LQ(lq);

	/* create 3 leases */
	for (i = 0; i < 3; i++) {
		memset(&test_lease[i], 0, sizeof(struct lease));
		test_lease[i].sort_time = i;
		check_lease = NULL;
		lease_reference(&check_lease, &test_lease[i], MDL);
	}

	/* Add leases */
	LEASE_INSERTP(&lq, &test_lease[0]);
	LEASE_INSERTP(&lq, &test_lease[2]);
	LEASE_INSERTP(&lq, &test_lease[1]);

	/* check ordering of leases */
	check_lease = LEASE_GET_FIRST(lq);
	for (i = 0; i < 3; i++) {
		if (check_lease != &test_lease[i])
			atf_tc_fail("leases don't match, %d", i);
		check_lease = LEASE_GET_NEXT(lq, check_lease);
	}
	if (check_lease != NULL)
		atf_tc_fail("lease not null");

	/* Remove the middle lease and check the list */
	LEASE_REMOVEP(&lq, &test_lease[1]);
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[0])
		atf_tc_fail("wrong lease after remove, 1");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[2])
		atf_tc_fail("wrong lease after remove, 2");

	LEASE_REMOVEP(&lq, &test_lease[0]);
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[2])
		atf_tc_fail("wrong lease after remove, 3");

	LEASE_REMOVEP(&lq, check_lease);

	/* The lease queue should now be empty */
	if (LEASE_NOT_EMPTY(lq))
		atf_tc_fail("lq not empty afer removal");
}

/* Test if we can cycle the leases we add three leases then remove
 * the first one, update it's sort time and re add it */
ATF_TC(leaseq_cycle);
ATF_TC_HEAD(leaseq_cycle, tc)
{
	atf_tc_set_md_var(tc, "descr", "Verify cycling the list");
}

ATF_TC_BODY(leaseq_cycle, tc)
{
	LEASE_STRUCT lq;
	struct lease test_lease[3], *check_lease;
	int i;

	INIT_LQ(lq);

	/* create and add 3 leases */
	for (i = 0; i < 3; i++) {
		memset(&test_lease[i], 0, sizeof(struct lease));
		test_lease[i].sort_time = i;
		check_lease = NULL;
		lease_reference(&check_lease, &test_lease[i], MDL);
		LEASE_INSERTP(&lq, &test_lease[i]);
	}

	/* Remove first lease, update it and re-insert it */
	LEASE_REMOVEP(&lq, &test_lease[0]);
	test_lease[0].sort_time = 4;
	LEASE_INSERTP(&lq, &test_lease[0]);

	/* check ordering of leases */
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[1])
		atf_tc_fail("leases don't match, 1");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[2])
		atf_tc_fail("leases don't match, 2");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[0])
		atf_tc_fail("leases don't match, 3");

	/* Remove first lease, update it and re-insert it */
	LEASE_REMOVEP(&lq, &test_lease[1]);
	test_lease[1].sort_time = 5;
	LEASE_INSERTP(&lq, &test_lease[1]);

	/* check ordering of leases */
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[2])
		atf_tc_fail("leases don't match, 4");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[0])
		atf_tc_fail("leases don't match, 5");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[1])
		atf_tc_fail("leases don't match, 6");

	/* Remove first lease, update it and re-insert it */
	LEASE_REMOVEP(&lq, &test_lease[2]);
	test_lease[2].sort_time = 6;
	LEASE_INSERTP(&lq, &test_lease[2]);

	/* check ordering of leases */
	check_lease = LEASE_GET_FIRST(lq);
	if (check_lease != &test_lease[0])
		atf_tc_fail("leases don't match, 7");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[1])
		atf_tc_fail("leases don't match, 8");
	check_lease = LEASE_GET_NEXT(lq, check_lease);
	if (check_lease != &test_lease[2])
		atf_tc_fail("leases don't match, 9");
}

/* Test what happens if we set the growth factor to a smallish number
 * and add enough leases to require it to grow multiple times
 * Mostly this is for the binary leases case but we can most of the
 * test for both.
 */

ATF_TC(leaseq_long);
ATF_TC_HEAD(leaseq_long, tc)
{
	atf_tc_set_md_var(tc, "descr", "Verify a long list");
}

ATF_TC_BODY(leaseq_long, tc)
{
	LEASE_STRUCT lq;
	struct lease test_lease[20], *check_lease;
	int i;

	INIT_LQ(lq);
#if defined (BINARY_LEASES)
	lc_init_growth(&lq, 5);
#endif

	/* create and add 10 leases */
	for (i = 0; i < 10; i++) {
		memset(&test_lease[i], 0, sizeof(struct lease));
		test_lease[i].sort_time = i;
		check_lease = NULL;
		lease_reference(&check_lease, &test_lease[i], MDL);

		LEASE_INSERTP(&lq, &test_lease[i]);
	}

	/* check ordering of leases */
	check_lease = LEASE_GET_FIRST(lq);
	for (i = 0; i < 10; i++) {
		if (check_lease != &test_lease[i])
			atf_tc_fail("leases don't match, %d", i);
		check_lease = LEASE_GET_NEXT(lq, check_lease);
	}

	/* Remove first 5 */
	for (i = 0; i < 5; i++) {
		LEASE_REMOVEP(&lq, &test_lease[i]);
	}

	/* Add 10 more */
	for (i = 10; i < 20; i++) {
		memset(&test_lease[i], 0, sizeof(struct lease));
		test_lease[i].sort_time = i;
		check_lease = NULL;
		lease_reference(&check_lease, &test_lease[i], MDL);

		LEASE_INSERTP(&lq, &test_lease[i]);
	}

	/* and the first 5 */
	for (i = 0; i < 5; i++) {
		LEASE_INSERTP(&lq, &test_lease[i]);
	}

	/* and check them all out */
	check_lease = LEASE_GET_FIRST(lq);
	for (i = 0; i < 20; i++) {
		if (check_lease != &test_lease[i])
			atf_tc_fail("leases don't match, %d", i);
		check_lease = LEASE_GET_NEXT(lq, check_lease);
	}
}

/* Test if we can add leases that all have the same sort time
 */
ATF_TC(leaseq_same_time);
ATF_TC_HEAD(leaseq_same_time, tc)
{
	atf_tc_set_md_var(tc, "descr", "Verify adding leases with same time");
}

ATF_TC_BODY(leaseq_same_time, tc)
{
	LEASE_STRUCT lq;
	struct lease test_lease[20], *check_lease;
	int i;

	INIT_LQ(lq);

	/* create and add 20 leases */
	for (i = 0; i < 20; i++) {
		memset(&test_lease[i], 0, sizeof(struct lease));
		if (i < 10) 
			test_lease[i].sort_time = 10;
		else
			test_lease[i].sort_time = 20;
		check_lease = NULL;
		lease_reference(&check_lease, &test_lease[i], MDL);
		LEASE_INSERTP(&lq, &test_lease[i]);
	}

#if defined (BINARY_LEASES)
	/* the ordering isn't well defined for either but we check
	 * to see what happens in the binary case.  At the start
	 * the leases should stay in the order they were added.
	 */

	/* check ordering of leases */
	check_lease = LEASE_GET_FIRST(lq);
	for (i = 0; i < 20; i++) {
		if (check_lease != &test_lease[i])
			atf_tc_fail("leases don't match 1, %d", i);
		check_lease = LEASE_GET_NEXT(lq, check_lease);
	}
	if (check_lease != NULL)
		atf_tc_fail("lease not null");
#endif

	/* Remove first 10, update their time, but still before
	 * the previous 10 and re add them */
	for (i = 0; i < 10; i++) {
		LEASE_REMOVEP(&lq, &test_lease[i]);
		test_lease[i].sort_time = 15;
		LEASE_INSERTP(&lq, &test_lease[i]);
	}

	/* The ordering becomes random at this point so we don't
	 * check it.  We try to remove them all and see that
	 * we got to an empty queue.
	 */
	for (i = 0; i < 20; i++) {
		LEASE_REMOVEP(&lq, &test_lease[i]);
	}
	if (LEASE_NOT_EMPTY(lq))
		atf_tc_fail("queue not empty");

}

ATF_TP_ADD_TCS(tp)
{
	ATF_TP_ADD_TC(tp, leaseq_basic);
	ATF_TP_ADD_TC(tp, leaseq_add_to_end);
	ATF_TP_ADD_TC(tp, leaseq_add_to_start);
	ATF_TP_ADD_TC(tp, leaseq_add_to_middle);
	ATF_TP_ADD_TC(tp, leaseq_cycle);
	ATF_TP_ADD_TC(tp, leaseq_long);
	ATF_TP_ADD_TC(tp, leaseq_same_time);
	return (atf_no_error());
}
