/*
* 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;
}