/*
 *
 *  Embedded Linux library
 *
 *  Copyright (C) 2018  Intel Corporation. All rights reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <assert.h>
#include <linux/types.h>
#include <netinet/ip.h>
#include <linux/if_arp.h>
#include <errno.h>

#include <ell/ell.h>
#include "ell/dhcp-private.h"

static bool verbose = false;
static uint8_t client_packet[1024];
static size_t client_packet_len;
static uint8_t server_packet[1024];
static size_t server_packet_len;

static void test_request_option(const void *data)
{
	struct l_dhcp_client *dhcp;

	dhcp = l_dhcp_client_new(0);
	assert(dhcp);

	assert(!l_dhcp_client_add_request_option(NULL, 0));

	assert(l_dhcp_client_add_request_option(dhcp,
						L_DHCP_OPTION_SUBNET_MASK));
	assert(l_dhcp_client_add_request_option(dhcp,
						L_DHCP_OPTION_ROUTER));
	assert(l_dhcp_client_add_request_option(dhcp,
						L_DHCP_OPTION_HOST_NAME));
	assert(l_dhcp_client_add_request_option(dhcp,
						L_DHCP_OPTION_DOMAIN_NAME));
	assert(l_dhcp_client_add_request_option(dhcp,
					L_DHCP_OPTION_DOMAIN_NAME_SERVER));
	assert(l_dhcp_client_add_request_option(dhcp,
					L_DHCP_OPTION_NTP_SERVERS));
	assert(!l_dhcp_client_add_request_option(dhcp, 0));
	assert(!l_dhcp_client_add_request_option(dhcp, 255));
	assert(!l_dhcp_client_add_request_option(dhcp, 52));
	assert(!l_dhcp_client_add_request_option(dhcp, 53));
	assert(!l_dhcp_client_add_request_option(dhcp, 55));

	assert(l_dhcp_client_add_request_option(dhcp, 33));
	assert(l_dhcp_client_add_request_option(dhcp, 44));

	l_dhcp_client_destroy(dhcp);
}

static void test_invalid_message_length(const void *data)
{
	struct dhcp_message message;
	struct dhcp_message_iter iter;

	memset(&message, 0, sizeof(message));
	assert(!_dhcp_message_iter_init(&iter, NULL, 0));
	assert(!_dhcp_message_iter_init(&iter, &message, sizeof(message)));
}

static void test_cookie(const void *data)
{
	struct dhcp_message *message;
	size_t len = sizeof(struct dhcp_message);
	uint8_t *opt;
	struct dhcp_message_iter iter;

	message = (struct dhcp_message *) l_new(uint8_t, len);
	opt = (uint8_t *)(&message->magic);
	opt[0] = 0xff;

	assert(!_dhcp_message_iter_init(&iter, message, len));

	opt[0] = 99;
	opt[1] = 130;
	opt[2] = 83;
	opt[3] = 99;

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(!_dhcp_message_iter_next(&iter, NULL, NULL, NULL));

	l_free(message);
}

struct option_test {
	uint8_t sname[64];
	int snamelen;
	uint8_t file[128];
	int filelen;
	uint8_t options[128];
	int len;
};

static const struct option_test option_test_1 = {
	.options = { 42, 5, 65, 66, 67, 68, 69 },
	.len = 7,
};

static const struct option_test option_test_2 = {
	.options = { 42, 5, 65, 66, 67, 68, 69, 0, 0, 53, 1, 5 },
	.len = 12,
};

static const struct option_test option_test_3 = {
	.options = { 8, 255, 70, 71, 72 },
	.len = 5,
};

static const struct option_test option_test_4 = {
	.options = { 0x35, 0x01, 0x05, 0x36, 0x04, 0x01, 0x00, 0xa8,
			0xc0, 0x33, 0x04, 0x00, 0x01, 0x51, 0x80, 0x01,
			0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0,
			0xa8, 0x00, 0x01, 0x06, 0x04, 0xc0, 0xa8, 0x00,
			0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, },
	.len = 40,
};

static const struct option_test option_test_5 = {
	.options = { 53, 1, 2, 42, 3, 0, 0, 0 },
	.len =  8,
};

static const struct option_test option_test_6 = {
	.options = { 42, 2, 1, 2, 44 },
	.len = 5,
};

static const struct option_test option_test_7 = {
	.file = { 222, 3, 1, 2, 3, 53, 1, 6 },
	.filelen = 8,
	.options = { 52, 0x1, 0x1 },
	.len = 3,
};

static const struct option_test option_test_8 = {
	.sname = { 1, 4, 1, 2, 3, 4, 53, 1, 5 },
	.snamelen = 9,
	.file = { 222, 3, 1, 2, 3 },
	.filelen = 5,
	.options = { 52, 0x1, 0x3 },
	.len = 3,
};

static struct dhcp_message *create_message(const struct option_test *test,
						size_t *out_len)
{
	struct dhcp_message *message;
	size_t len = sizeof(struct dhcp_message) + test->len;
	uint8_t *opt;

	message = (struct dhcp_message *) l_new(uint8_t, len);
	opt = (uint8_t *)(&message->magic);

	opt[0] = 99;
	opt[1] = 130;
	opt[2] = 83;
	opt[3] = 99;

	if (test->len)
		memcpy(&opt[4], test->options, test->len);

	if (test->filelen <= 128)
		memcpy(&message->file, test->file, test->filelen);

	if (test->snamelen <= 64)
		memcpy(&message->sname, test->sname, test->snamelen);

	if (out_len)
		*out_len = len;

	return message;
}

static void test_option_1(const void *data)
{
	const struct option_test *test = data;
	struct dhcp_message *message;
	struct dhcp_message_iter iter;
	size_t len;
	uint8_t t,l;

	message = create_message(test, &len);

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 42);
	assert(l == 5);
	assert(!_dhcp_message_iter_next(&iter, &t, &l, NULL));
	l_free(message);
}

static void test_option_2(const void *data)
{
	const struct option_test *test = data;
	struct dhcp_message *message;
	struct dhcp_message_iter iter;
	size_t len;
	uint8_t t,l;

	message = create_message(test, &len);

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 42);
	assert(l == 5);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 53);
	assert(l == 1);
	assert(!_dhcp_message_iter_next(&iter, &t, &l, NULL));
	l_free(message);
}

static void test_option_3(const void *data)
{
	const struct option_test *test = data;
	struct dhcp_message *message;
	struct dhcp_message_iter iter;
	size_t len;

	message = create_message(test, &len);

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(!_dhcp_message_iter_next(&iter, NULL, NULL, NULL));
	l_free(message);
}

static void test_option_4(const void *data)
{
	const struct option_test *test = data;
	struct dhcp_message *message;
	struct dhcp_message_iter iter;
	size_t len;
	uint8_t t,l;

	message = create_message(test, &len);

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 0x35);
	assert(l == 1);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 0x36);
	assert(l == 4);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 0x33);
	assert(l == 4);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 0x1);
	assert(l == 4);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 0x3);
	assert(l == 4);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 0x6);
	assert(l == 4);
	assert(!_dhcp_message_iter_next(&iter, &t, &l, NULL));
	l_free(message);
}

static void test_option_5(const void *data)
{
	const struct option_test *test = data;
	struct dhcp_message *message;
	struct dhcp_message_iter iter;
	size_t len;
	uint8_t t,l;

	message = create_message(test, &len);

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 53);
	assert(l == 1);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 42);
	assert(l == 3);
	assert(!_dhcp_message_iter_next(&iter, &t, &l, NULL));
	l_free(message);
}

static void test_option_6(const void *data)
{
	const struct option_test *test = data;
	struct dhcp_message *message;
	struct dhcp_message_iter iter;
	size_t len;
	uint8_t t,l;

	message = create_message(test, &len);

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 42);
	assert(l == 2);
	assert(!_dhcp_message_iter_next(&iter, NULL, NULL, NULL));
	l_free(message);
}

static void test_option_7(const void *data)
{
	const struct option_test *test = data;
	struct dhcp_message *message;
	struct dhcp_message_iter iter;
	size_t len;
	uint8_t t,l;

	message = create_message(test, &len);

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 222);
	assert(l == 3);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 53);
	assert(l == 1);
	assert(!_dhcp_message_iter_next(&iter, NULL, NULL, NULL));
	l_free(message);
}

static void test_option_8(const void *data)
{
	const struct option_test *test = data;
	struct dhcp_message *message;
	struct dhcp_message_iter iter;
	size_t len;
	uint8_t t, l;

	message = create_message(test, &len);

	assert(_dhcp_message_iter_init(&iter, message, len));
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 222);
	assert(l == 3);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 1);
	assert(l == 4);
	assert(_dhcp_message_iter_next(&iter, &t, &l, NULL));
	assert(t == 53);
	assert(l == 1);
	assert(!_dhcp_message_iter_next(&iter, NULL, NULL, NULL));
	l_free(message);
}

static void test_option_set(const void *data)
{
	struct dhcp_message_builder builder;
	struct dhcp_message *message;
	size_t outlen;
	uint8_t *msg_out;
	unsigned int i;
	static uint8_t result[sizeof(struct dhcp_message) + 64];
	static uint8_t options[64] = {
			160, 2, 0x11, 0x12,
			0,
			31, 8, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
			0,
			55, 3, 0x51, 0x52, 0x53,
			17, 7, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77,
			255
	};

	message = (struct dhcp_message *)result;

	/* test a few failure conditions */
	assert(!_dhcp_message_builder_init(NULL, NULL, 0, 0));
	assert(!_dhcp_message_builder_init(&builder, message, 0, 0));

	_dhcp_message_builder_init(&builder, message, sizeof(result),
					DHCP_MESSAGE_TYPE_DISCOVER);
	_dhcp_message_builder_append(&builder, 160, 2, options + 2);
	_dhcp_message_builder_append(&builder, 0, 0, NULL);
	_dhcp_message_builder_append(&builder, 31, 8, options + 7);
	_dhcp_message_builder_append(&builder, 0, 0, NULL);
	_dhcp_message_builder_append(&builder, 55, 3, options + 18);
	_dhcp_message_builder_append(&builder, 17, 7, options + 23);
	msg_out = _dhcp_message_builder_finalize(&builder, &outlen);

	/*
	 * The builde APIs automatically append the type passed in during init
	 * so we can skip over that in order to test the expected static data
	 */

	msg_out += sizeof(struct dhcp_message) + 3;

	for (i = 0; i < outlen - sizeof(struct dhcp_message); i++) {
		if (msg_out[i] != options[i]) {
			if (verbose) {
				l_info("byte[%d] did not match 0x%02x 0x%02x",
					i, msg_out[i], options[i]);
			}

			assert(false);
		}
	}
}

static void test_checksum(const void *data)
{
	static const uint8_t buf[20] = {
		0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00,
		0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0xff, 0xff, 0xff, 0xff
	};

	struct iovec iov[2];

	assert(_dhcp_checksum(&buf, 20) == L_BE16_TO_CPU(0x78ae));

	iov[0].iov_base = (void *) buf;
	iov[0].iov_len = 8;
	iov[1].iov_base = (void *) buf + 8;
	iov[1].iov_len = 12;

	assert(_dhcp_checksumv(iov, 2) == L_BE16_TO_CPU(0x78ae));
}

static const uint8_t discover_data_1[] = {
	0x01, 0x01, 0x06, 0x00, 0x4d, 0x7c, 0x67, 0xc6, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
	0x35, 0x01, 0x01, 0x39, 0x02, 0x02, 0x40, 0x37, 0x06, 0x03, 0x2a, 0x0f,
	0x06, 0x01, 0x0c, 0x0c, 0x0a, 0x3c, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
	0x6d, 0x65, 0x3e, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static const uint8_t offer_data_1[] = {
	0x02, 0x01, 0x06, 0x00, 0x4d, 0x7c, 0x67, 0xc6, 0x00, 0x00, 0x80, 0x00,
	0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0xf9, 0xc0, 0xa8, 0x01, 0x01,
	0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
	0x35, 0x01, 0x02, 0x36, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x00,
	0x01, 0x51, 0x80, 0x3a, 0x04, 0x00, 0x00, 0xa8, 0xc0, 0x3b, 0x04, 0x00,
	0x01, 0x27, 0x50, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x1c, 0x04, 0xc0,
	0xa8, 0x01, 0xff, 0x06, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x03, 0x04, 0xc0,
	0xa8, 0x01, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static const uint8_t request_data_1[] = {
	0x01, 0x01, 0x06, 0x00, 0x4d, 0x7c, 0x67, 0xc6, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
	0x35, 0x01, 0x03, 0x32, 0x04, 0xc0, 0xa8, 0x01, 0xf9, 0x36, 0x04, 0xc0,
	0xa8, 0x01, 0x01, 0x39, 0x02, 0x02, 0x40, 0x37, 0x06, 0x03, 0x2a, 0x0f,
	0x06, 0x01, 0x0c, 0x0c, 0x0a, 0x3c, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61,
	0x6d, 0x65, 0x3e, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

static const uint8_t ack_data_1[] = {
	0x02, 0x01, 0x06, 0x00, 0x4d, 0x7c, 0x67, 0xc6, 0x00, 0x00, 0x80, 0x00,
	0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x01, 0xf9, 0xc0, 0xa8, 0x01, 0x01,
	0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0x82, 0x53, 0x63,
	0x35, 0x01, 0x05, 0x36, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x33, 0x04, 0x00,
	0x01, 0x51, 0x80, 0x3a, 0x04, 0x00, 0x00, 0xa8, 0xc0, 0x3b, 0x04, 0x00,
	0x01, 0x27, 0x50, 0x01, 0x04, 0xff, 0xff, 0xff, 0x00, 0x1c, 0x04, 0xc0,
	0xa8, 0x01, 0xff, 0x06, 0x04, 0xc0, 0xa8, 0x01, 0x01, 0x03, 0x04, 0xc0,
	0xa8, 0x01, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

bool event_handler_called = false;

static int dhcp_message_prl_equal(const uint8_t *haystack,
					uint8_t len, const uint8_t *prl)
{
	unsigned int i, j;
	uint8_t needle;

	for (i = 0; i < len; i++) {
		needle = prl[i];

		for (j = 0; j < len; j++)
			if (haystack[j] == needle)
				break;

		if (j == len)
			return false;
	}

	return true;
}

static int dhcp_message_has_option(struct dhcp_message_iter *iter,
					uint8_t type, uint8_t len,
					const uint8_t *data)
{
	uint8_t t,l;
	const void *v;

	while (_dhcp_message_iter_next(iter, &t, &l, &v)) {
		if (t != type)
			continue;

		if (l != len)
			return -EMSGSIZE;

		if (type == 55)
			return dhcp_message_prl_equal(v, len, data);

		if (memcmp(data, v, len))
			return -EINVAL;

		return 0;
	}

	return -ENOENT;
}

static bool dhcp_message_compare(const uint8_t *expected, size_t expected_len,
				const uint8_t *obtained, size_t obtained_len)
{
	struct dhcp_message *e = (struct dhcp_message *) expected;
	struct dhcp_message *o = (struct dhcp_message *) obtained;
	struct dhcp_message_iter ei;
	bool r = true;
	uint8_t t, l;
	const void *v;

	assert(e->op == o->op);
	assert(e->htype == o->htype);
	assert(e->hlen == o->hlen);
	assert(e->hops == o->hops);

	/* Ignore xid & secs */

	assert(e->flags == o->flags);
	assert(e->ciaddr == o->ciaddr);
	assert(e->yiaddr == o->yiaddr);
	assert(e->giaddr == o->giaddr);
	assert(!memcmp(e->chaddr, o->chaddr, sizeof(e->chaddr)));

	assert(_dhcp_message_iter_init(&ei, e, expected_len));

	while (_dhcp_message_iter_next(&ei, &t, &l, &v)) {
		struct dhcp_message_iter oi;
		int err;

		assert(_dhcp_message_iter_init(&oi, o, obtained_len));
		err = dhcp_message_has_option(&oi, t, l, v);

		if (err >= 0)
			continue;

		r = false;
		switch (err) {
		case -EINVAL:
			l_info("Option %s(%hhu) payload doesn't match",
				_dhcp_option_to_string(t), t);
			break;
		case -EMSGSIZE:
			l_info("Option %s(%hhu) length doesn't match",
				_dhcp_option_to_string(t), t);
			break;
		case -ENOENT:
			l_info("Option %s(%hhu) missing",
				_dhcp_option_to_string(t), t);
			break;
		default:
			assert(false);
		}
	}

	return r;
}

static bool client_send_called = false;

static int fake_transport_send(struct dhcp_transport *transport,
				const struct sockaddr_in *dest,
				const void *data, size_t len)
{
	assert(len <= sizeof(client_packet));
	assert(!client_send_called);
	memcpy(client_packet, data, len);
	client_packet_len = len;
	client_send_called = true;

	return len;
}

static int fake_transport_l2_send(struct dhcp_transport *transprot,
					uint32_t saddr, uint16_t sport,
					uint32_t daddr, uint16_t dport,
					const uint8_t *dest_mac,
					const void *data, size_t len)
{
	assert(len <= sizeof(client_packet));
	assert(!client_send_called);
	memcpy(client_packet, data, len);
	client_packet_len = len;
	client_send_called = true;

	return len;
}

static void event_handler_lease_obtained(struct l_dhcp_client *client,
						enum l_dhcp_client_event event,
						void *userdata)
{
	assert(client);
	assert(event == L_DHCP_CLIENT_EVENT_LEASE_OBTAINED);
	event_handler_called = true;
}

static void test_discover(const void *data)
{
	static const uint8_t addr[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
	struct l_dhcp_client *client;
	struct dhcp_transport *transport = l_new(struct dhcp_transport, 1);
	const struct l_dhcp_lease *lease;

	transport->send = fake_transport_send;
	transport->l2_send = fake_transport_l2_send;
	transport->ifindex = 42;

	client = l_dhcp_client_new(42);
	assert(l_dhcp_client_set_address(client, ARPHRD_ETHER, addr, 6));
	assert(l_dhcp_client_set_interface_name(client, "fake"));
	assert(_dhcp_client_set_transport(client, transport));
	assert(l_dhcp_client_set_hostname(client, "<hostname>"));
	_dhcp_client_override_xid(client, 0x4d7c67c6);
	assert(l_dhcp_client_set_event_handler(client,
				event_handler_lease_obtained, NULL, NULL));

	assert(l_dhcp_client_start(client));

	assert(client_send_called);
	client_send_called = false;
	assert(dhcp_message_compare(discover_data_1, sizeof(discover_data_1),
					client_packet, client_packet_len));

	transport->rx_cb(offer_data_1, sizeof(offer_data_1), client, NULL, 0);

	assert(client_send_called);
	client_send_called = false;
	assert(dhcp_message_compare(request_data_1, sizeof(request_data_1),
					client_packet, client_packet_len));

	event_handler_called = false;
	transport->rx_cb(ack_data_1, sizeof(ack_data_1), client, NULL, 0);
	assert(!client_send_called);
	assert(event_handler_called);

	lease = l_dhcp_client_get_lease(client);
	assert(lease);

	assert(lease->server_address == L_CPU_TO_BE32(0xc0a80101));
	assert(lease->subnet_mask == L_CPU_TO_BE32(0xffffff00));
	assert(lease->broadcast == L_CPU_TO_BE32(0xc0a801ff));
	assert(lease->router == L_CPU_TO_BE32(0xc0a80101));
	assert(lease->address == L_CPU_TO_BE32(0xc0a801f9));

	assert(lease->lifetime == 0x00015180);
	assert(lease->t1 == 0x0000a8c0);
	assert(lease->t2 == 0x00012750);

	l_dhcp_client_destroy(client);
	assert(client_send_called);
	client_send_called = false;
}

static bool l2_send_called = false;

static int fake_transport_server_l2_send(struct dhcp_transport *s,
					uint32_t source_ip,
					uint16_t source_port,
					uint32_t dest_ip,
					uint16_t dest_port,
					const uint8_t *dest_arp,
					const void *data, size_t len)
{
	assert(len <= sizeof(server_packet));
	assert(!l2_send_called);
	memcpy(server_packet, data, len);
	server_packet_len = len;

	l2_send_called = true;

	return 0;
}

static void do_debug(const char *str, void *user_data)
{
	const char *prefix = user_data;

	l_info("%s%s", prefix, str);
}

static char *new_client;
static char *expired_client;

static void server_event(struct l_dhcp_server *server,
					enum l_dhcp_server_event event,
					void *user_data,
					const struct l_dhcp_lease *lease)
{
	switch (event) {
	case L_DHCP_SERVER_EVENT_NEW_LEASE:
		assert(!new_client);
		new_client = l_dhcp_lease_get_address(lease);
		break;
	case L_DHCP_SERVER_EVENT_LEASE_EXPIRED:
		assert(!expired_client);
		expired_client = l_dhcp_lease_get_address(lease);
		break;
	}
}

static struct l_dhcp_client *client_init(const uint8_t *mac)
{
	struct l_dhcp_client *client = l_dhcp_client_new(42);
	struct dhcp_transport *transport = l_new(struct dhcp_transport, 1);

	assert(l_dhcp_client_set_address(client, ARPHRD_ETHER, mac, 6));
	assert(l_dhcp_client_set_interface_name(client, "fake"));
	assert(l_dhcp_client_set_hostname(client, "<hostname>"));
	_dhcp_client_override_xid(client, 0x4d7c67c6);
	assert(l_dhcp_client_set_event_handler(client,
				event_handler_lease_obtained, NULL, NULL));

	if (verbose)
		l_dhcp_client_set_debug(client, do_debug, "[DHCP1] ", NULL,
					L_LOG_DEBUG);

	transport->send = fake_transport_send;
	transport->l2_send = fake_transport_l2_send;
	transport->ifindex = 42;

	assert(_dhcp_client_set_transport(client, transport));

	return client;
}

static void client_connect(struct l_dhcp_client *client,
				struct l_dhcp_server *server, bool rapid_commit)
{
	struct dhcp_transport *srv_transport =
					_dhcp_server_get_transport(server);
	struct dhcp_transport *cli_transport =
					_dhcp_client_get_transport(client);
	uint8_t cli_addr[ETH_ALEN];
	const struct dhcp_message *msg = (struct dhcp_message *) client_packet;

	assert(l_dhcp_client_start(client));
	assert(client_send_called);
	client_send_called = false;
	memcpy(cli_addr, msg->chaddr, ETH_ALEN);

	/* RX DISCOVER */
	srv_transport->rx_cb(client_packet, client_packet_len, server,
				cli_addr, 0);
	assert(l2_send_called);
	l2_send_called = false;

	if (!rapid_commit) {
		/* RX OFFER */
		cli_transport->rx_cb(server_packet, server_packet_len, client,
					NULL, 0);
		assert(client_send_called);
		client_send_called = false;

		/* RX REQUEST */
		srv_transport->rx_cb(client_packet, client_packet_len, server,
					cli_addr, 0);
		assert(l2_send_called);
		l2_send_called = false;
	}

	/* RX ACK */
	cli_transport->rx_cb(server_packet, server_packet_len, client, NULL, 0);
	assert(!client_send_called);

	assert(event_handler_called);
}

static struct l_dhcp_server *server_init()
{
	char *dns[] = { "192.168.1.1", "192.168.1.254", NULL };
	struct l_dhcp_server *server = l_dhcp_server_new(41);
	struct dhcp_transport *srv_transport = l_new(struct dhcp_transport, 1);

	assert(l_dhcp_server_set_interface_name(server, "fake"));
	assert(l_dhcp_server_set_ip_address(server, "192.168.1.1"));
	assert(l_dhcp_server_set_netmask(server, "255.255.255.0"));
	assert(l_dhcp_server_set_gateway(server, "192.168.1.1"));
	assert(l_dhcp_server_set_dns(server, dns));
	assert(l_dhcp_server_set_event_handler(server, server_event,
						NULL, NULL));

	if (verbose)
		l_dhcp_server_set_debug(server, do_debug, "[DHCP SERV] ", NULL);

	srv_transport->ifindex = 41;
	srv_transport->l2_send = fake_transport_server_l2_send;

	assert(_dhcp_server_set_transport(server, srv_transport));

	assert(l_dhcp_server_start(server));

	return server;
}

static void test_complete_run(const void *data)
{
	bool rapid_commit = L_PTR_TO_UINT(data);
	static const uint8_t addr1[6] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
	static const uint8_t addr2[6] = { 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c };
	struct dhcp_transport *srv_transport;

	struct l_dhcp_client *client1;
	struct l_dhcp_client *client2;
	struct l_dhcp_server *server;

	const struct l_dhcp_lease *cli_lease;
	/* client IP address */
	char *cli_addr;
	/* servers IP address */
	char *srv_addr;
	char *tmp_addr;
	char **dns_list;

	server = server_init();
	l_dhcp_server_set_enable_rapid_commit(server, rapid_commit);

	assert(l_dhcp_server_set_ip_range(server, "192.168.1.2",
						"192.168.1.100"));

	srv_transport = _dhcp_server_get_transport(server);

	client1 = client_init(addr1);

	client_connect(client1, server, rapid_commit);

	cli_lease = l_dhcp_client_get_lease(client1);
	assert(cli_lease);
	cli_addr = l_dhcp_lease_get_address(cli_lease);
	assert(cli_addr);
	assert(!strcmp(cli_addr, "192.168.1.2"));
	assert(new_client);
	assert(!strcmp(new_client, cli_addr));
	l_free(new_client);
	new_client = NULL;

	srv_addr = l_dhcp_lease_get_server_id(cli_lease);
	assert(!strcmp(srv_addr, "192.168.1.1"));
	l_free(srv_addr);
	l_free(cli_addr);

	tmp_addr = l_dhcp_lease_get_gateway(cli_lease);
	assert(!strcmp(tmp_addr, "192.168.1.1"));
	l_free(tmp_addr);

	tmp_addr = l_dhcp_lease_get_netmask(cli_lease);
	assert(!strcmp(tmp_addr, "255.255.255.0"));
	l_free(tmp_addr);

	assert(l_dhcp_lease_get_prefix_length(cli_lease) == 24);

	dns_list = l_dhcp_lease_get_dns(cli_lease);
	assert(dns_list && dns_list[0] && dns_list[1]);
	assert(!strcmp(dns_list[0], "192.168.1.1"));
	assert(!strcmp(dns_list[1], "192.168.1.254"));
	l_strv_free(dns_list);

	client2 = client_init(addr2);

	client_connect(client2, server, rapid_commit);

	cli_lease = l_dhcp_client_get_lease(client2);
	assert(cli_lease);
	cli_addr = l_dhcp_lease_get_address(cli_lease);
	assert(cli_addr);
	assert(!strcmp(cli_addr, "192.168.1.3"));
	assert(new_client);
	assert(!strcmp(new_client, cli_addr));
	l_free(new_client);
	new_client = NULL;
	srv_addr = l_dhcp_lease_get_server_id(cli_lease);
	assert(!strcmp(srv_addr, "192.168.1.1"));
	l_free(srv_addr);
	l_free(cli_addr);

	l_dhcp_client_stop(client1);
	assert(client_send_called);
	client_send_called = false;
	srv_transport->rx_cb(client_packet, client_packet_len, server, addr1,
				0);
	assert(expired_client);
	assert(!strcmp(expired_client, "192.168.1.2"));
	l_free(expired_client);
	expired_client = NULL;

	l_dhcp_client_stop(client2);
	assert(client_send_called);
	client_send_called = false;
	srv_transport->rx_cb(client_packet, client_packet_len, server, addr2,
				0);
	assert(expired_client);
	assert(!strcmp(expired_client, "192.168.1.3"));
	l_free(expired_client);
	expired_client = NULL;

	l_dhcp_client_destroy(client1);
	assert(!client_send_called);
	l_dhcp_client_destroy(client2);
	assert(!client_send_called);

	l_dhcp_server_stop(server);
	l_dhcp_server_destroy(server);
}

static void test_expired_ip_reuse(const void *data)
{
	uint8_t addr[6] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x00};
	struct l_dhcp_server *server = server_init();
	struct dhcp_transport *srv_transport =
					_dhcp_server_get_transport(server);
	struct l_dhcp_client *client_new;
	const struct l_dhcp_lease *lease;
	char *cli_addr;
	int i;

	l_dhcp_server_set_ip_range(server, "192.168.1.2", "192.168.1.11");
	_dhcp_server_set_max_expired_clients(server, 10);
	l_dhcp_server_set_enable_rapid_commit(server, false);

	/*
	 * Connect and release 10 clients, this should max out the expired
	 * queue (since we are setting it to 10) and force the first client that
	 * expired to be removed allowing 192.168.1.2 to be reused for a new
	 * client
	 */
	for (i = 0; i < 10; i++) {
		struct l_dhcp_client *client;

		addr[5] = i;
		client = client_init(addr);
		client_connect(client, server, false);
		l_free(new_client);
		new_client = NULL;

		l_dhcp_client_destroy(client);
		assert(client_send_called);
		client_send_called = false;
		srv_transport->rx_cb(client_packet, client_packet_len, server,
					addr, 0);
		l_free(expired_client);
		expired_client = NULL;
	}

	addr[5] = i + 1;
	client_new = client_init(addr);
	client_connect(client_new, server, false);
	l_free(new_client);
	new_client = NULL;

	lease = l_dhcp_client_get_lease(client_new);
	assert(lease);
	cli_addr = l_dhcp_lease_get_address(lease);
	assert(!strcmp(cli_addr, "192.168.1.2"));
	l_free(cli_addr);

	l_dhcp_client_destroy(client_new);
	assert(client_send_called);
	client_send_called = false;
	l_dhcp_server_destroy(server);
}

int main(int argc, char *argv[])
{
	l_test_init(&argc, &argv);

	l_test_add("request-option", test_request_option, NULL);
	l_test_add("invalid-message-length", test_invalid_message_length, NULL);
	l_test_add("cookie", test_cookie, NULL);

	l_test_add("option test 1", test_option_1, &option_test_1);
	l_test_add("option test 2", test_option_2, &option_test_2);
	l_test_add("option test 3", test_option_3, &option_test_3);
	l_test_add("option test 4", test_option_4, &option_test_4);
	l_test_add("option test 5", test_option_5, &option_test_5);
	l_test_add("option test 6", test_option_6, &option_test_6);
	l_test_add("option test 7", test_option_7, &option_test_7);
	l_test_add("option test 8", test_option_8, &option_test_8);

	l_test_add("option set", test_option_set, NULL);

	l_test_add("checksum", test_checksum, NULL);

	l_test_add("discover", test_discover, NULL);

	l_test_add("complete run", test_complete_run, L_UINT_TO_PTR(false));
	l_test_add("rapid commit", test_complete_run, L_UINT_TO_PTR(true));
	l_test_add("expired IP reuse", test_expired_ip_reuse, NULL);

	return l_test_run();
}
