/* SPDX-License-Identifier: LGPL-2.1-only */
/*
 * Copyright (c) 2018 Volodymyr Bendiuga <volodymyr.bendiuga@gmail.com>
 */

#include "nl-default.h"

#include <linux/ethtool.h>

#include <netlink/netlink.h>
#include <netlink/attr.h>
#include <netlink/utils.h>
#include <netlink/route/classifier.h>
#include <netlink/route/action.h>
#include <netlink/route/cls/flower.h>

#include "tc-api.h"
#include "nl-aux-route/nl-route.h"

/** @cond SKIP */
struct rtnl_flower {
	struct rtnl_act *cf_act;
	int cf_mask;
	uint32_t cf_flags;
	uint16_t cf_proto;
	uint16_t cf_vlan_id;
	uint16_t cf_vlan_ethtype;
	uint8_t cf_vlan_prio;
	uint8_t cf_src_mac[ETH_ALEN];
	uint8_t cf_src_mac_mask[ETH_ALEN];
	uint8_t cf_dst_mac[ETH_ALEN];
	uint8_t cf_dst_mac_mask[ETH_ALEN];
	in_addr_t cf_ipv4_src;
	in_addr_t cf_ipv4_src_mask;
	in_addr_t cf_ipv4_dst;
	in_addr_t cf_ipv4_dst_mask;
	uint8_t cf_ip_dscp;
	uint8_t cf_ip_dscp_mask;
};

#define FLOWER_ATTR_FLAGS         (1 << 0)
#define FLOWER_ATTR_ACTION        (1 << 1)
#define FLOWER_ATTR_VLAN_ID       (1 << 2)
#define FLOWER_ATTR_VLAN_PRIO     (1 << 3)
#define FLOWER_ATTR_VLAN_ETH_TYPE (1 << 4)
#define FLOWER_ATTR_DST_MAC       (1 << 5)
#define FLOWER_ATTR_DST_MAC_MASK  (1 << 6)
#define FLOWER_ATTR_SRC_MAC       (1 << 7)
#define FLOWER_ATTR_SRC_MAC_MASK  (1 << 8)
#define FLOWER_ATTR_IP_DSCP       (1 << 9)
#define FLOWER_ATTR_IP_DSCP_MASK  (1 << 10)
#define FLOWER_ATTR_PROTO         (1 << 11)
#define FLOWER_ATTR_IPV4_SRC      (1 << 12)
#define FLOWER_ATTR_IPV4_SRC_MASK (1 << 13)
#define FLOWER_ATTR_IPV4_DST      (1 << 14)
#define FLOWER_ATTR_IPV4_DST_MASK (1 << 15)
/** @endcond */

#define FLOWER_DSCP_MAX             0xe0
#define FLOWER_DSCP_MASK_MAX        0xe0
#define FLOWER_VID_MAX              4095
#define FLOWER_VLAN_PRIO_MAX        7

static struct nla_policy flower_policy[TCA_FLOWER_MAX + 1] = {
	[TCA_FLOWER_FLAGS]             = { .type = NLA_U32 },
	[TCA_FLOWER_KEY_ETH_TYPE]      = { .type = NLA_U16 },
	[TCA_FLOWER_KEY_ETH_DST]       = { .maxlen = ETH_ALEN },
	[TCA_FLOWER_KEY_ETH_DST_MASK]  = { .maxlen = ETH_ALEN },
	[TCA_FLOWER_KEY_ETH_SRC]       = { .maxlen = ETH_ALEN },
	[TCA_FLOWER_KEY_ETH_SRC_MASK]  = { .maxlen = ETH_ALEN },
	[TCA_FLOWER_KEY_VLAN_ID]       = { .type = NLA_U16 },
	[TCA_FLOWER_KEY_VLAN_PRIO]     = { .type = NLA_U8 },
	[TCA_FLOWER_KEY_IP_TOS]        = { .type = NLA_U8 },
	[TCA_FLOWER_KEY_IP_TOS_MASK]   = { .type = NLA_U8 },
	[TCA_FLOWER_KEY_VLAN_ETH_TYPE] = { .type = NLA_U16 },
	[TCA_FLOWER_KEY_IPV4_SRC]      = { .type = NLA_U32 },
	[TCA_FLOWER_KEY_IPV4_SRC_MASK] = { .type = NLA_U32 },
	[TCA_FLOWER_KEY_IPV4_DST]      = { .type = NLA_U32 },
	[TCA_FLOWER_KEY_IPV4_DST_MASK] = { .type = NLA_U32 },
};

static int flower_msg_parser(struct rtnl_tc *tc, void *data)
{
	struct rtnl_flower *f = data;
	struct nlattr *tb[TCA_FLOWER_MAX + 1];
	int err;

	err = tca_parse(tb, TCA_FLOWER_MAX, tc, flower_policy);
	if (err < 0)
		return err;

	if (tb[TCA_FLOWER_FLAGS]) {
		f->cf_flags = nla_get_u32(tb[TCA_FLOWER_FLAGS]);
		f->cf_mask |= FLOWER_ATTR_FLAGS;
	}

	if (tb[TCA_FLOWER_ACT]) {
		err = rtnl_act_parse(&f->cf_act, tb[TCA_FLOWER_ACT]);
		if (err)
			return err;

		f->cf_mask |= FLOWER_ATTR_ACTION;
	}

	if (tb[TCA_FLOWER_KEY_ETH_TYPE]) {
		f->cf_proto = nla_get_u16(tb[TCA_FLOWER_KEY_ETH_TYPE]);
		f->cf_mask |= FLOWER_ATTR_PROTO;
	}

	if (tb[TCA_FLOWER_KEY_VLAN_ID]) {
		f->cf_vlan_id = nla_get_u16(tb[TCA_FLOWER_KEY_VLAN_ID]);
		f->cf_mask |= FLOWER_ATTR_VLAN_ID;
	}

	if (tb[TCA_FLOWER_KEY_VLAN_PRIO]) {
		f->cf_vlan_prio = nla_get_u8(tb[TCA_FLOWER_KEY_VLAN_PRIO]);
		f->cf_mask |= FLOWER_ATTR_VLAN_PRIO;
	}

	if (tb[TCA_FLOWER_KEY_VLAN_ETH_TYPE]) {
		f->cf_vlan_ethtype = nla_get_u16(tb[TCA_FLOWER_KEY_VLAN_ETH_TYPE]);
		f->cf_mask |= FLOWER_ATTR_VLAN_ETH_TYPE;
	}

	if (tb[TCA_FLOWER_KEY_ETH_DST]) {
		nla_memcpy(f->cf_dst_mac, tb[TCA_FLOWER_KEY_ETH_DST], ETH_ALEN);
		f->cf_mask |= FLOWER_ATTR_DST_MAC;
	}

	if (tb[TCA_FLOWER_KEY_ETH_DST_MASK]) {
		nla_memcpy(f->cf_dst_mac_mask, tb[TCA_FLOWER_KEY_ETH_DST_MASK], ETH_ALEN);
		f->cf_mask |= FLOWER_ATTR_DST_MAC_MASK;
	}

	if (tb[TCA_FLOWER_KEY_ETH_SRC]) {
		nla_memcpy(f->cf_src_mac, tb[TCA_FLOWER_KEY_ETH_SRC], ETH_ALEN);
		f->cf_mask |= FLOWER_ATTR_SRC_MAC;
	}

	if (tb[TCA_FLOWER_KEY_ETH_SRC_MASK]) {
		nla_memcpy(f->cf_src_mac_mask, tb[TCA_FLOWER_KEY_ETH_SRC_MASK], ETH_ALEN);
		f->cf_mask |= FLOWER_ATTR_SRC_MAC_MASK;
	}

	if (tb[TCA_FLOWER_KEY_IP_TOS]) {
		f->cf_ip_dscp = nla_get_u8(tb[TCA_FLOWER_KEY_IP_TOS]);
		f->cf_mask |= FLOWER_ATTR_IP_DSCP;
	}

	if (tb[TCA_FLOWER_KEY_IP_TOS_MASK]) {
		f->cf_ip_dscp_mask = nla_get_u8(tb[TCA_FLOWER_KEY_IP_TOS_MASK]);
		f->cf_mask |= FLOWER_ATTR_IP_DSCP_MASK;
	}

	if (tb[TCA_FLOWER_KEY_IPV4_SRC]) {
		f->cf_ipv4_src = nla_get_u32(tb[TCA_FLOWER_KEY_IPV4_SRC]);
		f->cf_mask |= FLOWER_ATTR_IPV4_SRC;
	}

	if (tb[TCA_FLOWER_KEY_IPV4_SRC_MASK]) {
		f->cf_ipv4_src_mask =
			nla_get_u32(tb[TCA_FLOWER_KEY_IPV4_SRC_MASK]);
		f->cf_mask |= FLOWER_ATTR_IPV4_SRC_MASK;
	}

	if (tb[TCA_FLOWER_KEY_IPV4_DST]) {
		f->cf_ipv4_dst = nla_get_u32(tb[TCA_FLOWER_KEY_IPV4_DST]);
		f->cf_mask |= FLOWER_ATTR_IPV4_DST;
	}

	if (tb[TCA_FLOWER_KEY_IPV4_DST_MASK]) {
		f->cf_ipv4_dst_mask =
			nla_get_u32(tb[TCA_FLOWER_KEY_IPV4_DST_MASK]);
		f->cf_mask |= FLOWER_ATTR_IPV4_DST_MASK;
	}

	return 0;
}

static int flower_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
{
	struct rtnl_flower *f = data;
	int err;

	if (!f)
		return 0;

	if (f->cf_mask & FLOWER_ATTR_FLAGS)
		NLA_PUT_U32(msg, TCA_FLOWER_FLAGS, f->cf_flags);

	if (f->cf_mask & FLOWER_ATTR_ACTION) {
		err = rtnl_act_fill(msg, TCA_FLOWER_ACT, f->cf_act);
		if (err)
			return err;
	}

	if (f->cf_mask & FLOWER_ATTR_PROTO)
		NLA_PUT_U16(msg, TCA_FLOWER_KEY_ETH_TYPE, f->cf_proto);

	if (f->cf_mask & FLOWER_ATTR_VLAN_ID)
		NLA_PUT_U16(msg, TCA_FLOWER_KEY_VLAN_ID, f->cf_vlan_id);

	if (f->cf_mask & FLOWER_ATTR_VLAN_PRIO)
		NLA_PUT_U8(msg, TCA_FLOWER_KEY_VLAN_PRIO, f->cf_vlan_prio);

	if (f->cf_mask & FLOWER_ATTR_VLAN_ETH_TYPE)
		NLA_PUT_U16(msg, TCA_FLOWER_KEY_VLAN_ETH_TYPE, f->cf_vlan_ethtype);

	if (f->cf_mask & FLOWER_ATTR_DST_MAC)
		NLA_PUT(msg, TCA_FLOWER_KEY_ETH_DST, ETH_ALEN, f->cf_dst_mac);

	if (f->cf_mask & FLOWER_ATTR_DST_MAC_MASK)
		NLA_PUT(msg, TCA_FLOWER_KEY_ETH_DST_MASK, ETH_ALEN, f->cf_dst_mac_mask);

	if (f->cf_mask & FLOWER_ATTR_SRC_MAC)
		NLA_PUT(msg, TCA_FLOWER_KEY_ETH_SRC, ETH_ALEN, f->cf_src_mac);

	if (f->cf_mask & FLOWER_ATTR_SRC_MAC_MASK)
		NLA_PUT(msg, TCA_FLOWER_KEY_ETH_SRC_MASK, ETH_ALEN, f->cf_src_mac_mask);

	if (f->cf_mask & FLOWER_ATTR_IP_DSCP)
		NLA_PUT_U8(msg, TCA_FLOWER_KEY_IP_TOS, f->cf_ip_dscp);

	if (f->cf_mask & FLOWER_ATTR_IP_DSCP_MASK)
		NLA_PUT_U8(msg, TCA_FLOWER_KEY_IP_TOS_MASK, f->cf_ip_dscp_mask);

	if (f->cf_mask & FLOWER_ATTR_IPV4_SRC)
		NLA_PUT_U32(msg, TCA_FLOWER_KEY_IPV4_SRC, f->cf_ipv4_src);

	if (f->cf_mask & FLOWER_ATTR_IPV4_SRC_MASK)
		NLA_PUT_U32(msg, TCA_FLOWER_KEY_IPV4_SRC_MASK,
			    f->cf_ipv4_src_mask);

	if (f->cf_mask & FLOWER_ATTR_IPV4_DST)
		NLA_PUT_U32(msg, TCA_FLOWER_KEY_IPV4_DST, f->cf_ipv4_dst);

	if (f->cf_mask & FLOWER_ATTR_IPV4_DST_MASK)
		NLA_PUT_U32(msg, TCA_FLOWER_KEY_IPV4_DST_MASK,
			    f->cf_ipv4_dst_mask);

	return 0;

nla_put_failure:
	return -NLE_NOMEM;
}

static void flower_free_data(struct rtnl_tc *tc, void *data)
{
	struct rtnl_flower *f = data;

	if (f->cf_act)
		rtnl_act_put_all(&f->cf_act);
}

static int flower_clone(void *_dst, void *_src)
{
	struct rtnl_flower *dst = _dst, *src = _src;

	if (src->cf_act) {
		if (!(dst->cf_act = rtnl_act_alloc()))
			return -NLE_NOMEM;

		memcpy(dst->cf_act, src->cf_act, sizeof(struct rtnl_act));

		/* action nl list next and prev pointers must be updated */
		nl_init_list_head(&dst->cf_act->ce_list);

		if (   src->cf_act->c_opts
		    && !(dst->cf_act->c_opts = nl_data_clone(src->cf_act->c_opts)))
			return -NLE_NOMEM;

		if (   src->cf_act->c_xstats
		    && !(dst->cf_act->c_xstats = nl_data_clone(src->cf_act->c_xstats)))
			return -NLE_NOMEM;

		if (   src->cf_act->c_subdata
		    && !(dst->cf_act->c_subdata = nl_data_clone(src->cf_act->c_subdata)))
			return -NLE_NOMEM;

		if (dst->cf_act->c_link) {
			nl_object_get(OBJ_CAST(dst->cf_act->c_link));
		}

		dst->cf_act->a_next = NULL;   /* Only clone first in chain */
	}

	return 0;
}

static void flower_dump_details(struct rtnl_tc *tc, void *data,
                                struct nl_dump_params *p)
{
	struct rtnl_flower *f = data;
	char addr_str[INET_ADDRSTRLEN];
	char mask_str[INET_ADDRSTRLEN];

	if (!f)
		return;

	if (f->cf_mask & FLOWER_ATTR_FLAGS)
		nl_dump(p, " flags %u", f->cf_flags);

	if (f->cf_mask & FLOWER_ATTR_PROTO)
		nl_dump(p, " protocol %u", f->cf_proto);

	if (f->cf_mask & FLOWER_ATTR_VLAN_ID)
		nl_dump(p, " vlan_id %u", f->cf_vlan_id);

	if (f->cf_mask & FLOWER_ATTR_VLAN_PRIO)
		nl_dump(p, " vlan_prio %u", f->cf_vlan_prio);

	if (f->cf_mask & FLOWER_ATTR_VLAN_ETH_TYPE)
		nl_dump(p, " vlan_ethtype %u", f->cf_vlan_ethtype);

	if (f->cf_mask & FLOWER_ATTR_DST_MAC)
		nl_dump(p, " dst_mac %02x:%02x:%02x:%02x:%02x:%02x",
		        f->cf_dst_mac[0], f->cf_dst_mac[1],
		        f->cf_dst_mac[2], f->cf_dst_mac[3],
		        f->cf_dst_mac[4], f->cf_dst_mac[5]);

	if (f->cf_mask & FLOWER_ATTR_DST_MAC_MASK)
		nl_dump(p, " dst_mac_mask %02x:%02x:%02x:%02x:%02x:%02x",
		        f->cf_dst_mac_mask[0], f->cf_dst_mac_mask[1],
		        f->cf_dst_mac_mask[2], f->cf_dst_mac_mask[3],
		        f->cf_dst_mac_mask[4], f->cf_dst_mac_mask[5]);

	if (f->cf_mask & FLOWER_ATTR_SRC_MAC)
		nl_dump(p, " src_mac %02x:%02x:%02x:%02x:%02x:%02x",
		        f->cf_src_mac[0], f->cf_src_mac[1],
		        f->cf_src_mac[2], f->cf_src_mac[3],
		        f->cf_src_mac[4], f->cf_src_mac[5]);

	if (f->cf_mask & FLOWER_ATTR_SRC_MAC_MASK)
		nl_dump(p, " src_mac_mask %02x:%02x:%02x:%02x:%02x:%02x",
		        f->cf_src_mac_mask[0], f->cf_src_mac_mask[1],
		        f->cf_src_mac_mask[2], f->cf_src_mac_mask[3],
		        f->cf_src_mac_mask[4], f->cf_src_mac_mask[5]);

	if (f->cf_mask & FLOWER_ATTR_IP_DSCP)
		nl_dump(p, " dscp %u", f->cf_ip_dscp);

	if (f->cf_mask & FLOWER_ATTR_IP_DSCP_MASK)
		nl_dump(p, " dscp_mask %u", f->cf_ip_dscp_mask);

	if (f->cf_mask & FLOWER_ATTR_IPV4_SRC) {
		inet_ntop(AF_INET, &f->cf_ipv4_src, addr_str, sizeof(addr_str));
		inet_ntop(AF_INET, &f->cf_ipv4_src_mask, mask_str, sizeof(mask_str));
		nl_dump(p, "IPv4 src %s mask %s\n", addr_str, mask_str);
	}

	if (f->cf_mask & FLOWER_ATTR_IPV4_DST) {
		inet_ntop(AF_INET, &f->cf_ipv4_dst, addr_str, sizeof(addr_str));
		inet_ntop(AF_INET, &f->cf_ipv4_dst_mask, mask_str, sizeof(mask_str));
		nl_dump(p, "IPv4 dst %s mask %s\n", addr_str, mask_str);
	}
}

/**
 * @name Attribute Modification
 * @{
 */

/**
 * Set protocol for flower classifier
 * @arg cls		Flower classifier.
 * @arg proto		protocol (ETH_P_*)
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_proto(struct rtnl_cls *cls, uint16_t proto)
{
	struct rtnl_flower *f;

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

	f->cf_proto = htons(proto);
	f->cf_mask |= FLOWER_ATTR_PROTO;

	return 0;
}

/**
 * Get protocol for flower classifier
 * @arg cls		Flower classifier.
 * @arg proto		protocol
 * @return 0 on success or a negative error code.
*/
int rtnl_flower_get_proto(struct rtnl_cls *cls, uint16_t *proto)
{
	struct rtnl_flower *f;

	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
		return -NLE_INVAL;

	if (!(f->cf_mask & FLOWER_ATTR_PROTO))
		return -NLE_MISSING_ATTR;

	*proto = ntohs(f->cf_proto);

	return 0;
}

/**
 * Set vlan id for flower classifier
 * @arg cls		Flower classifier.
 * @arg vid		vlan id
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_vlan_id(struct rtnl_cls *cls, uint16_t vid)
{
	struct rtnl_flower *f;

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

	if (vid > FLOWER_VID_MAX)
		return -NLE_RANGE;

	f->cf_vlan_id = vid;
	f->cf_mask |= FLOWER_ATTR_VLAN_ID;

	return 0;
}

/**
 * Get vlan id for flower classifier
 * @arg cls		Flower classifier.
 * @arg vid		vlan id
 * @return 0 on success or a negative error code.
*/
int rtnl_flower_get_vlan_id(struct rtnl_cls *cls, uint16_t *vid)
{
	struct rtnl_flower *f;

	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
		return -NLE_INVAL;

	if (!(f->cf_mask & FLOWER_ATTR_VLAN_ID))
		return -NLE_MISSING_ATTR;

	*vid = f->cf_vlan_id;

	return 0;
}

/**
 * Set vlan priority for flower classifier
 * @arg cls		Flower classifier.
 * @arg prio		vlan priority
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_vlan_prio(struct rtnl_cls *cls, uint8_t prio)
{
	struct rtnl_flower *f;

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

	if (prio > FLOWER_VLAN_PRIO_MAX)
		return -NLE_RANGE;

	f->cf_vlan_prio = prio;
	f->cf_mask |= FLOWER_ATTR_VLAN_PRIO;

	return 0;
}

/**
 * Get vlan prio for flower classifier
 * @arg cls		Flower classifier.
 * @arg prio		vlan priority
 * @return 0 on success or a negative error code.
*/
int rtnl_flower_get_vlan_prio(struct rtnl_cls *cls, uint8_t *prio)
{
	struct rtnl_flower *f;

	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
		return -NLE_INVAL;

	if (!(f->cf_mask & FLOWER_ATTR_VLAN_PRIO))
		return -NLE_MISSING_ATTR;

	*prio = f->cf_vlan_prio;

	return 0;
}

/**
 * Set vlan ethertype for flower classifier
 * @arg cls		Flower classifier.
 * @arg ethtype		vlan ethertype
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_vlan_ethtype(struct rtnl_cls *cls, uint16_t ethtype)
{
	struct rtnl_flower *f;

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

	if (!(f->cf_mask & FLOWER_ATTR_PROTO))
		return -NLE_MISSING_ATTR;

	if (f->cf_proto != htons(ETH_P_8021Q))
		return -NLE_INVAL;

	f->cf_vlan_ethtype = htons(ethtype);
	f->cf_mask |= FLOWER_ATTR_VLAN_ETH_TYPE;

	return 0;
}

/**
 * Set destination mac address for flower classifier
 * @arg cls		Flower classifier.
 * @arg mac		destination mac address
 * @arg mask		mask for mac address
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_dst_mac(struct rtnl_cls *cls, unsigned char *mac,
                            unsigned char *mask)
{
	struct rtnl_flower *f;

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

	if (mac) {
		memcpy(f->cf_dst_mac, mac, ETH_ALEN);
		f->cf_mask |= FLOWER_ATTR_DST_MAC;

		if (mask) {
			memcpy(f->cf_dst_mac_mask, mask, ETH_ALEN);
			f->cf_mask |= FLOWER_ATTR_DST_MAC_MASK;
		}

		return 0;
	}

	return -NLE_FAILURE;
}

/**
 * Get destination mac address for flower classifier
 * @arg cls		Flower classifier.
 * @arg mac		destination mac address
 * @arg mask		mask for mac address
 * @return 0 on success or a negative error code.
*/
int rtnl_flower_get_dst_mac(struct rtnl_cls *cls, unsigned char *mac,
                            unsigned char *mask)
{
	struct rtnl_flower *f;

	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
		return -NLE_INVAL;

	if (!(f->cf_mask & FLOWER_ATTR_DST_MAC))
		return -NLE_MISSING_ATTR;

	if (mac)
		memcpy(mac, f->cf_dst_mac, ETH_ALEN);

	if (mask)
		memcpy(mask, f->cf_dst_mac_mask, ETH_ALEN);

	return 0;
}

/**
 * Set source mac address for flower classifier
 * @arg cls		Flower classifier.
 * @arg mac		source mac address
 * @arg mask		mask for mac address
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_src_mac(struct rtnl_cls *cls, unsigned char *mac,
                            unsigned char *mask)
{
	struct rtnl_flower *f;

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

	if (mac) {
		memcpy(f->cf_src_mac, mac, ETH_ALEN);
		f->cf_mask |= FLOWER_ATTR_SRC_MAC;

		if (mask) {
			memcpy(f->cf_src_mac_mask, mask, ETH_ALEN);
			f->cf_mask |= FLOWER_ATTR_SRC_MAC_MASK;
		}

		return 0;
	}

	return -NLE_FAILURE;
}

/**
 * Get source mac address for flower classifier
 * @arg cls		Flower classifier.
 * @arg mac		source mac address
 * @arg mask		mask for mac address
 * @return 0 on success or a negative error code.
*/
int rtnl_flower_get_src_mac(struct rtnl_cls *cls, unsigned char *mac,
                            unsigned char *mask)
{
	struct rtnl_flower *f;

	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
		return -NLE_INVAL;

	if (!(f->cf_mask & FLOWER_ATTR_SRC_MAC))
		return -NLE_MISSING_ATTR;

	if (mac)
		memcpy(mac, f->cf_src_mac, ETH_ALEN);

	if (mask)
		memcpy(mask, f->cf_src_mac_mask, ETH_ALEN);

	return 0;
}

/**
 * Set dscp value for flower classifier
 * @arg cls		Flower classifier.
 * @arg dscp		dscp value
 * @arg mask		mask for dscp value
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_ip_dscp(struct rtnl_cls *cls, uint8_t dscp, uint8_t mask)
{
	struct rtnl_flower *f;

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

	if (dscp > FLOWER_DSCP_MAX)
		return -NLE_RANGE;

	if (mask > FLOWER_DSCP_MASK_MAX)
		return -NLE_RANGE;

	f->cf_ip_dscp = dscp;
	f->cf_mask |= FLOWER_ATTR_IP_DSCP;

	if (mask) {
		f->cf_ip_dscp_mask = mask;
		f->cf_mask |= FLOWER_ATTR_IP_DSCP_MASK;
	}

	return 0;
}

/**
 * Get dscp value for flower classifier
 * @arg cls		Flower classifier.
 * @arg dscp		dscp value
 * @arg mask		mask for dscp value
 * @return 0 on success or a negative error code.
*/
int rtnl_flower_get_ip_dscp(struct rtnl_cls *cls, uint8_t *dscp, uint8_t *mask)
{
	struct rtnl_flower *f;

	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
		return -NLE_INVAL;

	if (!(f->cf_mask & FLOWER_ATTR_IP_DSCP))
		return -NLE_MISSING_ATTR;

	*dscp = f->cf_ip_dscp;
	*mask = f->cf_ip_dscp_mask;

	return 0;
}

/**
 * Set IPv4 source address for flower classifier
 * @arg cls		Flower classifier.
 * @arg addr		IPv4 source address
 * @arg mask		mask for IPv4 source address
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_ipv4_src(struct rtnl_cls *cls, in_addr_t addr,
			     in_addr_t mask)
{
	struct rtnl_flower *f;

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

	if (addr) {
		f->cf_ipv4_src = addr;
		f->cf_mask |= FLOWER_ATTR_IPV4_SRC;

		if (mask) {
			f->cf_ipv4_src_mask = mask;
			f->cf_mask |= FLOWER_ATTR_IPV4_SRC_MASK;
		}

		return 0;
	}

	return -NLE_FAILURE;
}

/**
 * Get IPv4 source address for flower classifier
 * @arg cls		Flower classifier.
 * @arg addr		IPv4 source address
 * @arg mask		mask for IPv4 source address
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_get_ipv4_src(struct rtnl_cls *cls, in_addr_t *out_addr,
			     in_addr_t *out_mask)
{
	struct rtnl_flower *f;

	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
		return -NLE_INVAL;

	if (!(f->cf_mask & FLOWER_ATTR_IPV4_SRC))
		return -NLE_MISSING_ATTR;

	if (out_addr)
		*out_addr = f->cf_ipv4_src;

	if (out_mask) {
		if (f->cf_mask & FLOWER_ATTR_IPV4_SRC_MASK)
			*out_mask = f->cf_ipv4_src_mask;
		else
			*out_mask = 0xffffffff;
	}

	return 0;
}

/**
 * Set IPv4 destination address for flower classifier
 * @arg cls		Flower classifier.
 * @arg addr		IPv4 destination address
 * @arg mask		mask for IPv4 destination address
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_ipv4_dst(struct rtnl_cls *cls, in_addr_t addr,
			     in_addr_t mask)
{
	struct rtnl_flower *f;

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

	if (addr) {
		f->cf_ipv4_dst = addr;
		f->cf_mask |= FLOWER_ATTR_IPV4_DST;

		if (mask) {
			f->cf_ipv4_dst_mask = mask;
			f->cf_mask |= FLOWER_ATTR_IPV4_DST_MASK;
		}

		return 0;
	}

	return -NLE_FAILURE;
}

/**
 * Get IPv4 destination address for flower classifier
 * @arg cls		Flower classifier.
 * @arg addr		IPv4 destination address
 * @arg mask		mask for IPv4 destination address
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_get_ipv4_dst(struct rtnl_cls *cls, in_addr_t *out_addr,
			     in_addr_t *out_mask)
{
	struct rtnl_flower *f;

	if (!(f = rtnl_tc_data_peek(TC_CAST(cls))))
		return -NLE_INVAL;

	if (!(f->cf_mask & FLOWER_ATTR_IPV4_DST))
		return -NLE_MISSING_ATTR;

	if (out_addr)
		*out_addr = f->cf_ipv4_dst;

	if (out_mask) {
		if (f->cf_mask & FLOWER_ATTR_IPV4_DST_MASK)
			*out_mask = f->cf_ipv4_dst_mask;
		else
			*out_mask = 0xffffffff;
	}

	return 0;
}

/**
 * Append action for flower classifier
 * @arg cls		Flower classifier.
 * @arg act		action to append
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_append_action(struct rtnl_cls *cls, struct rtnl_act *act)
{
	struct rtnl_flower *f;
	int err;

	if (!act)
		return 0;

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

	if ((err = _rtnl_act_append_get(&f->cf_act, act)) < 0)
		return err;

	f->cf_mask |= FLOWER_ATTR_ACTION;
	return 0;
}

/**
 * Delete action from flower classifier
 * @arg cls		Flower classifier.
 * @arg act		action to delete
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_del_action(struct rtnl_cls *cls, struct rtnl_act *act)
{
	struct rtnl_flower *f;
	int ret;

	if (!act)
		return 0;

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

	if (!(f->cf_mask & FLOWER_ATTR_ACTION))
		return -NLE_INVAL;

	ret = rtnl_act_remove(&f->cf_act, act);
	if (ret)
		return ret;

	if (!f->cf_act)
		f->cf_mask &= ~FLOWER_ATTR_ACTION;
	rtnl_act_put(act);

	return 0;
}

/**
 * Get action from flower classifier
 * @arg cls		Flower classifier.
 * @return action on success or NULL on error.
 */
struct rtnl_act* rtnl_flower_get_action(struct rtnl_cls *cls)
{
	struct rtnl_flower *f;

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

	if (!(f->cf_mask & FLOWER_ATTR_ACTION))
		return NULL;

	rtnl_act_get(f->cf_act);

	return f->cf_act;
}

/**
 * Set flags for flower classifier
 * @arg cls		Flower classifier.
 * @arg flags		(TCA_CLS_FLAGS_SKIP_HW | TCA_CLS_FLAGS_SKIP_SW)
 * @return 0 on success or a negative error code.
 */
int rtnl_flower_set_flags(struct rtnl_cls *cls, int flags)
{
	struct rtnl_flower *f;

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

	f->cf_flags = flags;
	f->cf_mask |= FLOWER_ATTR_FLAGS;

	return 0;
}

/** @} */

static struct rtnl_tc_ops flower_ops = {
	.to_kind                = "flower",
	.to_type                = RTNL_TC_TYPE_CLS,
	.to_size                = sizeof(struct rtnl_flower),
	.to_msg_parser          = flower_msg_parser,
	.to_free_data           = flower_free_data,
	.to_clone               = flower_clone,
	.to_msg_fill            = flower_msg_fill,
	.to_dump = {
	    [NL_DUMP_DETAILS]   = flower_dump_details,
	},
};

static void _nl_init flower_init(void)
{
	rtnl_tc_register(&flower_ops);
}

static void _nl_exit flower_exit(void)
{
	rtnl_tc_unregister(&flower_ops);
}
