/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * Copyright (c) 2008-2013 Thomas Graf <tgraf@suug.ch>
 */

/**
 * @ingroup cls
 * @defgroup cls_basic Basic Classifier
 *
 * @par Introduction
 * The basic classifier is the simplest form of a classifier. It does
 * not have any special classification capabilities, instead it can be
 * used to classify exclusively based on extended matches or to
 * create a "catch-all" filter.
 *
 * @{
 */

#include <netlink-private/netlink.h>
#include <netlink-private/tc.h>
#include <netlink/netlink.h>
#include <netlink-private/route/tc-api.h>
#include <netlink/route/classifier.h>
#include <netlink/route/action.h>
#include <netlink/route/cls/basic.h>
#include <netlink/route/cls/ematch.h>

struct rtnl_basic
{
	uint32_t			b_target;
	struct rtnl_ematch_tree *	b_ematch;
	int				b_mask;
	struct rtnl_act *		b_act;
};

/** @cond SKIP */
#define BASIC_ATTR_TARGET	0x001
#define BASIC_ATTR_EMATCH	0x002
#define BASIC_ATTR_ACTION	0x004
/** @endcond */

static struct nla_policy basic_policy[TCA_BASIC_MAX+1] = {
	[TCA_BASIC_CLASSID]	= { .type = NLA_U32 },
	[TCA_BASIC_EMATCHES]	= { .type = NLA_NESTED },
};

static int basic_clone(void *_dst, void *_src)
{
	return -NLE_OPNOTSUPP;
}

static void basic_free_data(struct rtnl_tc *tc, void *data)
{
	struct rtnl_basic *b = data;

	if (!b)
		return;

	if (b->b_act)
		rtnl_act_put_all(&b->b_act);
	rtnl_ematch_tree_free(b->b_ematch);
}

static int basic_msg_parser(struct rtnl_tc *tc, void *data)
{
	struct nlattr *tb[TCA_BASIC_MAX + 1];
	struct rtnl_basic *b = data;
	int err;

	err = tca_parse(tb, TCA_BASIC_MAX, tc, basic_policy);
	if (err < 0)
		return err;

	if (tb[TCA_BASIC_CLASSID]) {
		b->b_target = nla_get_u32(tb[TCA_BASIC_CLASSID]);
		b->b_mask |= BASIC_ATTR_TARGET;
	}

	if (tb[TCA_BASIC_EMATCHES]) {
		if ((err = rtnl_ematch_parse_attr(tb[TCA_BASIC_EMATCHES],
					     &b->b_ematch)) < 0)
			return err;

		if (b->b_ematch)
			b->b_mask |= BASIC_ATTR_EMATCH;
	}
	if (tb[TCA_BASIC_ACT]) {
		b->b_mask |= BASIC_ATTR_ACTION;
		err = rtnl_act_parse(&b->b_act, tb[TCA_BASIC_ACT]);
		if (err < 0)
			return err;
	}

	return 0;
}

static void basic_dump_line(struct rtnl_tc *tc, void *data,
			    struct nl_dump_params *p)
{
	struct rtnl_basic *b = data;
	char buf[32];

	if (!b)
		return;

	if (b->b_mask & BASIC_ATTR_EMATCH)
		nl_dump(p, " ematch");
	else
		nl_dump(p, " match-all");

	if (b->b_mask & BASIC_ATTR_TARGET)
		nl_dump(p, " target %s",
			rtnl_tc_handle2str(b->b_target, buf, sizeof(buf)));
}

static void basic_dump_details(struct rtnl_tc *tc, void *data,
			       struct nl_dump_params *p)
{
	struct rtnl_basic *b = data;

	if (!b)
		return;

	if (b->b_mask & BASIC_ATTR_EMATCH) {
		nl_dump_line(p, "    ematch ");
		rtnl_ematch_tree_dump(b->b_ematch, p);
	} else
		nl_dump(p, "no options.\n");
}

static int basic_msg_fill(struct rtnl_tc *tc, void *data,
			  struct nl_msg *msg)
{
	struct rtnl_basic *b = data;

	if (!b)
		return 0;

	if (b->b_mask & BASIC_ATTR_TARGET)
		NLA_PUT_U32(msg, TCA_BASIC_CLASSID, b->b_target);

	if (b->b_mask & BASIC_ATTR_EMATCH &&
	    rtnl_ematch_fill_attr(msg, TCA_BASIC_EMATCHES, b->b_ematch) < 0)
		goto nla_put_failure;

	if (b->b_mask & BASIC_ATTR_ACTION) {
		int err;

		err = rtnl_act_fill(msg, TCA_BASIC_ACT, b->b_act);
		if (err)
			return err;
	}

	return 0;

nla_put_failure:
	return -NLE_NOMEM;
}

/**
 * @name Attribute Modifications
 * @{
 */

void rtnl_basic_set_target(struct rtnl_cls *cls, uint32_t target)
{
	struct rtnl_basic *b;

	if (!(b = rtnl_tc_data(TC_CAST(cls))))
		return;

	b->b_target = target;
	b->b_mask |= BASIC_ATTR_TARGET;
}

uint32_t rtnl_basic_get_target(struct rtnl_cls *cls)
{
	struct rtnl_basic *b;

	if (!(b = rtnl_tc_data(TC_CAST(cls))))
		return 0;

	return b->b_target;
}

void rtnl_basic_set_ematch(struct rtnl_cls *cls, struct rtnl_ematch_tree *tree)
{
	struct rtnl_basic *b;

	if (!(b = rtnl_tc_data(TC_CAST(cls))))
		return;

	if (b->b_ematch) {
		rtnl_ematch_tree_free(b->b_ematch);
		b->b_mask &= ~BASIC_ATTR_EMATCH;
	}

	b->b_ematch = tree;

	if (tree)
		b->b_mask |= BASIC_ATTR_EMATCH;
}

struct rtnl_ematch_tree *rtnl_basic_get_ematch(struct rtnl_cls *cls)
{
	struct rtnl_basic *b;

	if (!(b = rtnl_tc_data(TC_CAST(cls))))
		return NULL;

	return b->b_ematch;
}

int rtnl_basic_add_action(struct rtnl_cls *cls, struct rtnl_act *act)
{
	struct rtnl_basic *b;
	int err;

	if (!act)
		return 0;

	if (!(b = rtnl_tc_data(TC_CAST(cls))))
		return -NLE_NOMEM;

	b->b_mask |= BASIC_ATTR_ACTION;
	if ((err = rtnl_act_append(&b->b_act, act)))
		return err;

	/* In case user frees it */
	rtnl_act_get(act);
	return 0;
}

struct rtnl_act* rtnl_basic_get_action(struct rtnl_cls *cls)
{
	struct rtnl_basic *b;

	if (!(b = rtnl_tc_data_peek(TC_CAST(cls))))
		return NULL;

	if (!(b->b_mask & BASIC_ATTR_ACTION))
		return NULL;

	return b->b_act;
}

int rtnl_basic_del_action(struct rtnl_cls *cls, struct rtnl_act *act)
{
	struct rtnl_basic *b;
	int ret;

	if (!act)
		return 0;

	if (!(b = rtnl_tc_data(TC_CAST(cls))))
		return -NLE_NOMEM;

	if (!(b->b_mask & BASIC_ATTR_ACTION))
		return -NLE_INVAL;
	ret = rtnl_act_remove(&b->b_act, act);
	if (ret)
		return ret;

	if (!b->b_act)
		b->b_mask &= ~BASIC_ATTR_ACTION;
	rtnl_act_put(act);
	return 0;
}
/** @} */

static struct rtnl_tc_ops basic_ops = {
	.to_kind		= "basic",
	.to_type		= RTNL_TC_TYPE_CLS,
	.to_size		= sizeof(struct rtnl_basic),
	.to_msg_parser		= basic_msg_parser,
	.to_clone		= basic_clone,
	.to_free_data		= basic_free_data,
	.to_msg_fill		= basic_msg_fill,
	.to_dump = {
	    [NL_DUMP_LINE]	= basic_dump_line,
	    [NL_DUMP_DETAILS]	= basic_dump_details,
	},
};

static void __init basic_init(void)
{
	rtnl_tc_register(&basic_ops);
}

static void __exit basic_exit(void)
{
	rtnl_tc_unregister(&basic_ops);
}

/** @} */
