/*
 *
 *  Embedded Linux library
 *
 *  Copyright (C) 2011-2014  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 <stdio.h>
#include <linux/genetlink.h>
#include <ell/ell.h>

static bool do_print = false;

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

	printf("%s%s\n", prefix, str);
}

static unsigned char set_station_request[] = {
	0x34, 0x00, 0x00, 0x00, 0x17, 0x00, 0x05, 0x00, 0x8b, 0x53, 0x0d, 0x55,
	0x14, 0x0e, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00,
	0x03, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x06, 0x00, 0x24, 0xa2, 0xe1, 0xec,
	0x17, 0x04, 0x00, 0x00, 0x0c, 0x00, 0x43, 0x00, 0x02, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x00, 0x00,
};

static void parse_set_station(const void *data)
{
	static const unsigned char mac[6] =
		{ 0x24, 0xa2, 0xe1, 0xec, 0x17, 0x04 };
	struct nlmsghdr *nlmsg;
	struct l_genl_msg *msg;
	struct l_genl_attr attr;
	bool result;
	uint16_t type;
	uint16_t len;
	const void *payload;

	nlmsg = (struct nlmsghdr *) set_station_request;
	msg = l_genl_msg_new_from_data(nlmsg, sizeof(set_station_request));
	assert(msg);

	assert(l_genl_msg_get_command(msg) == 18);

	result = l_genl_attr_init(&attr, msg);
	assert(result);

	/*Interface Index: 3 (0x00000003) */
	assert(l_genl_attr_next(&attr, &type, &len, &payload));
	assert(type == 3);
	assert(len == 4);
	assert(*((unsigned int *) payload) == 3);

	/* MAC Address 24:A2:E1:EC:17:04 */
	assert(l_genl_attr_next(&attr, &type, &len, &payload));
	assert(type == 6);
	assert(len == 6);
	assert(!memcmp(payload, mac, 6));

	/* Station Flags 2: len 8
	 *     Mask: 0x00000002
	 *         Authorized
	 *     Set: 0x00000002
	 *         Authorized
	 */
	assert(l_genl_attr_next(&attr, &type, &len, &payload));
	assert(type == 67);
	assert(len == 8);
	assert(((unsigned int *) payload)[0] == 2);
	assert(((unsigned int *) payload)[1] == 2);

	l_genl_msg_unref(msg);
}

static void build_set_station(const void *data)
{
	static uint32_t index = 3;
	static const unsigned char mac[6] =
		{ 0x24, 0xa2, 0xe1, 0xec, 0x17, 0x04 };
	static uint32_t flags[] = { 2, 2 };
	struct l_genl_msg *msg;
	const void *raw;
	size_t size;

	msg = l_genl_msg_new_sized(18, 512);
	assert(msg);

	assert(l_genl_msg_append_attr(msg, 3, 4, &index));
	assert(l_genl_msg_append_attr(msg, 6, 6, mac));
	assert(l_genl_msg_append_attr(msg, 67, 8, flags));

	raw = l_genl_msg_to_data(msg, 0x17, 0x05, 0x550d538b, 3604, &size);

	if (do_print) {
		l_util_hexdump(false, raw, size, do_debug, "[MSG] ");
		l_util_hexdump(true, set_station_request, size,
					do_debug, "[MSG] ");
	}

	assert(size == sizeof(set_station_request));
	assert(!memcmp(raw, set_station_request, size));

	l_genl_msg_unref(msg);
}

static const unsigned char set_rekey_offload_request[] = {
	0x54, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x05, 0x00, 0x59, 0xa3, 0xe1, 0x53,
	0xba, 0x02, 0x40, 0xe7, 0x4f, 0x00, 0x00, 0x00, 0x08, 0x00, 0x03, 0x00,
	0x03, 0x00, 0x00, 0x00, 0x38, 0x00, 0x7a, 0x80, 0x14, 0x00, 0x01, 0x00,
	0x2f, 0x82, 0xbb, 0x0d, 0x93, 0x56, 0x60, 0x4b, 0xb1, 0x55, 0x1c, 0x85,
	0xc0, 0xeb, 0x32, 0x8b, 0x14, 0x00, 0x02, 0x00, 0x43, 0x25, 0xcf, 0x08,
	0x0b, 0x92, 0xa7, 0x2d, 0x86, 0xdc, 0x43, 0x21, 0xd6, 0x0c, 0x12, 0x03,
	0x0c, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
};

static void parse_set_rekey_offload(const void *data)
{
	static const unsigned char kek[] = {
		0x2f, 0x82, 0xbb, 0x0d, 0x93, 0x56, 0x60, 0x4b,
		0xb1, 0x55, 0x1c, 0x85, 0xc0, 0xeb, 0x32, 0x8b };
	static const unsigned char kck[] = {
		0x43, 0x25, 0xcf, 0x08, 0x0b, 0x92, 0xa7, 0x2d,
		0x86, 0xdc, 0x43, 0x21, 0xd6, 0x0c, 0x12, 0x03 };
	static const unsigned char replay_counter[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };
	struct nlmsghdr *nlmsg;
	struct l_genl_msg *msg;
	struct l_genl_attr attr;
	struct l_genl_attr nested;
	bool result;
	uint16_t type;
	uint16_t len;
	const void *payload;

	nlmsg = (struct nlmsghdr *) set_rekey_offload_request;
	msg = l_genl_msg_new_from_data(nlmsg,
					sizeof(set_rekey_offload_request));
	assert(msg);

	assert(l_genl_msg_get_command(msg) == 79);

	result = l_genl_attr_init(&attr, msg);
	assert(result);

	/*Interface Index: 3 (0x00000003) */
	assert(l_genl_attr_next(&attr, &type, &len, &payload));
	assert(type == 3);
	assert(len == 4);
	assert(*((unsigned int *) payload) == 3);

	/*
	 * Rekey Data: len 52
	 *     KEK: len 16
	 *         2f 82 bb 0d 93 56 60 4b b1 55 1c 85 c0 eb 32 8b
	 *     KCK: len 16
	 *         43 25 cf 08 0b 92 a7 2d 86 dc 43 21 d6 0c 12 03
	 *     Replay CTR: len 8
	 *         00 00 00 00 00 00 00 01
	 */
	assert(l_genl_attr_next(&attr, &type, &len, &payload));
	assert(type == 122);
	assert(len == 52);

	assert(l_genl_attr_recurse(&attr, &nested));

	assert(l_genl_attr_next(&nested, &type, &len, &payload));
	assert(type == 1);
	assert(len == 16);
	assert(!memcmp(payload, kek, 16));

	assert(l_genl_attr_next(&nested, &type, &len, &payload));
	assert(type == 2);
	assert(len == 16);
	assert(!memcmp(payload, kck, 16));

	assert(l_genl_attr_next(&nested, &type, &len, &payload));
	assert(type == 3);
	assert(len == 8);
	assert(!memcmp(payload, replay_counter, 8));

	l_genl_msg_unref(msg);
}

static void build_set_rekey_offload(const void *data)
{
	static uint32_t index = 3;
	static const unsigned char kek[] = {
		0x2f, 0x82, 0xbb, 0x0d, 0x93, 0x56, 0x60, 0x4b,
		0xb1, 0x55, 0x1c, 0x85, 0xc0, 0xeb, 0x32, 0x8b };
	static const unsigned char kck[] = {
		0x43, 0x25, 0xcf, 0x08, 0x0b, 0x92, 0xa7, 0x2d,
		0x86, 0xdc, 0x43, 0x21, 0xd6, 0x0c, 0x12, 0x03 };
	static const unsigned char replay_counter[] = {
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 };
	struct l_genl_msg *msg;
	const void *raw;
	size_t size;

	msg = l_genl_msg_new_sized(79, 0);
	assert(msg);

	assert(l_genl_msg_append_attr(msg, 3, 4, &index));

	assert(l_genl_msg_enter_nested(msg, 122));
	assert(l_genl_msg_append_attr(msg, 1, 16, kek));
	assert(l_genl_msg_append_attr(msg, 2, 16, kck));
	assert(l_genl_msg_append_attr(msg, 3, 8, replay_counter));
	assert(l_genl_msg_leave_nested(msg));

	raw = l_genl_msg_to_data(msg, 0x1b, 0x05, 0x53e1a359, 0xe74002ba,
					&size);
	if (do_print) {
		l_util_hexdump(false, raw, size, do_debug, "[MSG] ");
		l_util_hexdump(true, set_rekey_offload_request, size,
					do_debug, "[MSG] ");
	}

	assert(size == sizeof(set_rekey_offload_request));
	assert(!memcmp(raw, set_rekey_offload_request, size));

	l_genl_msg_unref(msg);
}

/*
 * This example is generated by libnl:
	msg = nlmsg_alloc();
	hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, ops.o_id,
			0, 0, TASKSTATS_CMD_GET, TASKSTATS_GENL_VERSION);
	nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, 1);

	nest1 = nla_nest_start(msg, 0x45);
	nla_put_string(msg, 0x46, "f");
	nla_put_string(msg, 0x47, "foob");
	nla_put_string(msg, 0x48, "foobar");

	nest2 = nla_nest_start(msg, 0x49);
	nla_put_string(msg, 0x50, "ba");
	nla_nest_end(msg, nest2);
	nla_nest_end(msg, nest1);
*/

static const unsigned char libnl_nested[] = {
	0x4c, 0x00, 0x00, 0x00, 0x15, 0x00, 0x05, 0x00, 0x72, 0x05, 0x13, 0x55,
	0x77, 0x68, 0x40, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x01, 0x00,
	0x01, 0x00, 0x00, 0x00, 0x30, 0x00, 0x45, 0x80, 0x06, 0x00, 0x46, 0x00,
	0x66, 0x00, 0x00, 0x00, 0x09, 0x00, 0x47, 0x00, 0x66, 0x6f, 0x6f, 0x62,
	0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x48, 0x00, 0x66, 0x6f, 0x6f, 0x62,
	0x61, 0x72, 0x00, 0x00, 0x0c, 0x00, 0x49, 0x80, 0x07, 0x00, 0x50, 0x00,
	0x62, 0x61, 0x00, 0x00,
};

static void parse_libnl_nested(const void *data)
{
	struct nlmsghdr *nlmsg;
	struct l_genl_msg *msg;
	struct l_genl_attr attr;
	struct l_genl_attr nested1;
	struct l_genl_attr nested2;
	bool result;
	uint16_t type;
	uint16_t len;
	const void *payload;

	nlmsg = (struct nlmsghdr *) libnl_nested;
	msg = l_genl_msg_new_from_data(nlmsg, sizeof(libnl_nested));
	assert(msg);

	assert(l_genl_msg_get_command(msg) == 1);

	result = l_genl_attr_init(&attr, msg);
	assert(result);

	assert(l_genl_attr_next(&attr, &type, &len, &payload));
	assert(type == 1);
	assert(len == 4);
	assert(*((unsigned int *) payload) == 1);

	assert(l_genl_attr_next(&attr, &type, &len, &payload));
	assert(type == 0x45);
	assert(len == 44);

	assert(l_genl_attr_recurse(&attr, &nested1));

	assert(l_genl_attr_next(&nested1, &type, &len, &payload));
	assert(type == 0x46);
	assert(len == 2);
	assert(!strcmp(payload, "f"));

	assert(l_genl_attr_next(&nested1, &type, &len, &payload));
	assert(type == 0x47);
	assert(len == 5);
	assert(!strcmp(payload, "foob"));

	assert(l_genl_attr_next(&nested1, &type, &len, &payload));
	assert(type == 0x48);
	assert(len == 7);
	assert(!strcmp(payload, "foobar"));

	assert(l_genl_attr_next(&nested1, &type, &len, &payload));
	assert(type == 0x49);
	assert(len == 8);

	assert(l_genl_attr_recurse(&nested1, &nested2));
	assert(l_genl_attr_next(&nested2, &type, &len, &payload));
	assert(type == 0x50);
	assert(len == 3);
	assert(!strcmp(payload, "ba"));

	l_genl_msg_unref(msg);
}

static void build_libnl_nested(const void *data)
{
	static uint32_t index = 1;
	struct l_genl_msg *msg;
	const void *raw;
	size_t size;

	msg = l_genl_msg_new_sized(1, 16);
	assert(msg);

	assert(l_genl_msg_append_attr(msg, 1, 4, &index));

	assert(l_genl_msg_enter_nested(msg, 0x45));
	assert(l_genl_msg_append_attr(msg, 0x46, 2, "f"));
	assert(l_genl_msg_append_attr(msg, 0x47, 5, "foob"));
	assert(l_genl_msg_append_attr(msg, 0x48, 7, "foobar"));
	assert(l_genl_msg_enter_nested(msg, 0x49));
	assert(l_genl_msg_append_attr(msg, 0x50, 3, "ba"));
	assert(l_genl_msg_leave_nested(msg));
	assert(l_genl_msg_leave_nested(msg));

	raw = l_genl_msg_to_data(msg, 0x15, 0x05, 0x55130572, 0x0c406877,
					&size);
	if (do_print) {
		l_util_hexdump(false, raw, size, do_debug, "[MSG] ");
		l_util_hexdump(true, libnl_nested, sizeof(libnl_nested),
							do_debug, "[MSG] ");
	}

	assert(size == sizeof(libnl_nested));
	assert(!memcmp(raw, libnl_nested, size));

	l_genl_msg_unref(msg);
}

static void test_append_attrv(const void *data)
{
	static const int num_blocks = 3;
	unsigned char * const payload = set_station_request;
	const size_t total_len = L_ARRAY_SIZE(set_station_request);
	const size_t block_len = total_len / num_blocks;
	const struct iovec iov[] = {
	    {
		.iov_base = payload,
		.iov_len  = block_len
	    },
	    {
		.iov_base = payload + block_len,
		.iov_len  = block_len
	    },
	    {
		.iov_base = payload + block_len * 2,
		.iov_len  = total_len - block_len * 2
	    }
	};

	struct l_genl_msg *msg;
	const uint16_t type = 2;
	struct l_genl_attr attr;
	uint16_t attr_type;
	uint16_t attr_len;
	const void *attr_payload;

	assert(L_ARRAY_SIZE(iov) == num_blocks);

	msg = l_genl_msg_new_sized(1, NLA_HDRLEN + NLA_ALIGN(total_len));
	assert(msg);

	assert(l_genl_msg_append_attrv(msg, type, iov, L_ARRAY_SIZE(iov)));
	assert(l_genl_attr_init(&attr, msg));
	assert(l_genl_attr_next(&attr, &attr_type, &attr_len, &attr_payload));

	assert(type == attr_type);
	assert(total_len == attr_len);
	assert(memcmp(payload, attr_payload, total_len) == 0);

	l_genl_msg_unref(msg);
}

int main(int argc, char *argv[])
{
	bool little_endian;

#if __BYTE_ORDER == __LITTLE_ENDIAN
	little_endian = true;
#elif __BYTE_ORDER == __BIG_ENDIAN
	little_endian = false;
#else
#error "Unknown byte order"
#endif

	l_test_init(&argc, &argv);

	if (!little_endian)
		goto done;

	l_test_add("Parse Set Station Request", parse_set_station, NULL);
	l_test_add("Parse Set Rekey Offload Request",
				parse_set_rekey_offload, NULL);

	l_test_add("Build Set Station Request", build_set_station, NULL);
	l_test_add("Build Set Rekey Offload Request",
				build_set_rekey_offload, NULL);

	l_test_add("libnl-generated Example with Nesting",
				parse_libnl_nested, NULL);
	l_test_add("Build libnl-generated Example with Nesting",
				build_libnl_nested, NULL);

	l_test_add("Test l_genl_msg_append_attrv",
				test_append_attrv, NULL);

done:
	return l_test_run();
}
