/*
 * NPF layer 2 ruleset tests.
 *
 * Public Domain.
 */

#ifdef _KERNEL
#include <sys/types.h>
#endif

#include "npf_impl.h"
#include "npf_test.h"

#define	RESULT_PASS	0
#define	RESULT_BLOCK	ENETUNREACH

static const struct test_case {
	const char *src;
	const char *dst;
	uint16_t    etype;
	const char *ifname;
	int	    di;
	int	    ret;
} test_cases[] = {
	{
		/* pass ether in final from $mac1 to $mac2 type $E_IPv6 */
		.src = "00:00:5E:00:53:00",	.dst = "00:00:5E:00:53:01",
		.ifname = IFNAME_INT,		.etype = ETHERTYPE_IPV6,
		.di = PFIL_IN,			.ret = RESULT_PASS
	},
	{
		/* block ether in final from $mac2 */
		.src = "00:00:5E:00:53:01",	.dst = "00:00:5E:00:53:02",
		.ifname = IFNAME_INT,		.etype = ETHERTYPE_IP,
		.di = PFIL_IN,			.ret = RESULT_BLOCK
	},

		/* pass ether out final to $mac3 $Apple talk */
	{
		.src = "00:00:5E:00:53:00",	.dst = "00:00:5E:00:53:02",
		.ifname = IFNAME_INT,		.etype = ETHERTYPE_ATALK,
		.di = PFIL_OUT,			.ret = RESULT_PASS
	},
	{
		/* goto default: block all (since direction is not matching ) */
		.src = "00:00:5E:00:53:00",	.dst = "00:00:5E:00:53:02",
		.ifname = IFNAME_INT,		.etype = ETHERTYPE_IP,
		.di = PFIL_IN,			.ret = RESULT_BLOCK
	},
	{
		/* pass from nested options : 03 */
		.src = "00:00:5E:00:53:03",	.dst = "00:00:5E:00:53:5A",
		.ifname = IFNAME_INT,		.etype = ETHERTYPE_IP,
		.di = PFIL_IN,			.ret = RESULT_PASS
	},
	{
		/* pass from nested options : 04 */
		.src = "00:00:5E:00:53:04",	.dst = "00:00:5E:00:53:5A",
		.ifname = IFNAME_INT,		.etype = ETHERTYPE_IP,
		.di = PFIL_IN,			.ret = RESULT_PASS
	},
	{
		/* pass from nested options : 04 */
		.src = "00:00:5E:00:53:05",	.dst = "00:00:5E:00:53:5A",
		.ifname = IFNAME_INT,		.etype = ETHERTYPE_IP,
		.di = PFIL_IN,			.ret = RESULT_PASS
	},
};

static int
run_raw_testcase(unsigned i)
{
	const struct test_case *t = &test_cases[i];
	npf_t *npf = npf_getkernctx();
	npf_cache_t *npc;
	struct mbuf *m;
	npf_rule_t *rl;
	int slock, error;

	m = mbuf_get_frame(t->src, t->dst, htons(t->etype));
	npc = get_cached_pkt(m, t->ifname, NPF_RULE_LAYER_2);

	slock = npf_config_read_enter(npf);
	rl = npf_ruleset_inspect(npc, npf_config_ruleset(npf), t->di, NPF_RULE_LAYER_2);
	if (rl) {
		npf_match_info_t mi;
		error = npf_rule_conclude(rl, &mi);
	} else {
		error = ENOENT;
	}
	npf_config_read_exit(npf, slock);

	put_cached_pkt(npc);
	return error;
}

/* for dynamic testing */
static int
run_handler_testcase(unsigned i)
{
	const struct test_case *t = &test_cases[i];
	ifnet_t *ifp = npf_test_getif(t->ifname);
	npf_t *npf = npf_getkernctx();
	struct mbuf *m;
	int error;

	m = mbuf_get_frame(t->src, t->dst, htons(t->etype));
	error = npfk_layer2_handler(npf, &m, ifp, t->di);
	if (m) {
		m_freem(m);
	}
	return error;
}

static npf_rule_t *
npf_blockall_rule(void)
{
	npf_t *npf = npf_getkernctx();
	nvlist_t *rule = nvlist_create(0);
	npf_rule_t *rl;

	nvlist_add_number(rule, "attr",
		NPF_RULE_IN | NPF_RULE_OUT | NPF_RULE_DYNAMIC | NPF_RULE_LAYER_2);
	rl = npf_rule_alloc(npf, rule);
	nvlist_destroy(rule);
	return rl;
}

static bool
test_static(bool verbose)
{
	for (unsigned i = 0; i < __arraycount(test_cases); i++) {
		const struct test_case *t = &test_cases[i];
		int error;

		if (npf_test_getif(t->ifname) == NULL) {
			printf("Interface %s is not configured.\n", t->ifname);
			return false;
		}

		error = run_handler_testcase(i);

		if (verbose) {
			printf("rule test %d:\texpected %d\n"
				"\t\t-> returned %d\n",
				i + 1, t->ret, error);
		}
		CHECK_TRUE(error == t->ret);
	}
	return true;
}

static bool
test_dynamic(void)
{
	npf_t *npf = npf_getkernctx();
	npf_ruleset_t *rlset;
	npf_rule_t *rl;
	uint64_t id;
	int error;

	/*
	* Test dynamic NPF rules.
	*/

	error = run_raw_testcase(0);
	CHECK_TRUE(error == RESULT_PASS);

	npf_config_enter(npf);
	rlset = npf_config_ruleset(npf);

	rl = npf_blockall_rule();
	error = npf_ruleset_add(rlset, "l2-ruleset", rl);
	CHECK_TRUE(error == 0);

	error = run_raw_testcase(0);
	CHECK_TRUE(error == RESULT_BLOCK);

	id = npf_rule_getid(rl);
	error = npf_ruleset_remove(rlset, "l2-ruleset", id);
	CHECK_TRUE(error == 0);

	npf_config_exit(npf);

	error = run_raw_testcase(0);
	CHECK_TRUE(error == RESULT_PASS);

	return true;
}

bool
npf_layer2_rule_test(bool verbose)
{
	bool ok;

	ok = test_static(verbose);
	CHECK_TRUE(ok);

	ok = test_dynamic();
	CHECK_TRUE(ok);

	return true;
}
