/*
 * lib/route/link/macsec.c	MACsec Link Info
 *
 *	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 version 2.1
 *	of the License.
 *
 * Copyright (c) 2016 Sabrina Dubroca <sd@queasysnail.net>
 */

/**
 * @ingroup link
 * @defgroup macsec MACsec
 * MACsec link module
 *
 * @details
 * \b Link Type Name: "macsec"
 *
 * @route_doc{link_macsec, MACsec Documentation}
 *
 * @{
 */
#include <netlink-private/netlink.h>
#include <netlink/netlink.h>
#include <netlink/attr.h>
#include <netlink/utils.h>
#include <netlink/object.h>
#include <netlink/route/rtnl.h>
#include <netlink/route/link/macsec.h>
#include <netlink-private/route/link/api.h>
#include <netlink-private/utils.h>

#include <linux/if_macsec.h>

/** @cond SKIP */
#define MACSEC_ATTR_SCI			(1 << 0)
#define MACSEC_ATTR_ICV_LEN		(1 << 1)
#define MACSEC_ATTR_CIPHER_SUITE	(1 << 2)
#define MACSEC_ATTR_WINDOW		(1 << 3)
#define MACSEC_ATTR_ENCODING_SA		(1 << 4)
#define MACSEC_ATTR_ENCRYPT		(1 << 5)
#define MACSEC_ATTR_PROTECT		(1 << 6)
#define MACSEC_ATTR_INC_SCI		(1 << 7)
#define MACSEC_ATTR_ES			(1 << 8)
#define MACSEC_ATTR_SCB			(1 << 9)
#define MACSEC_ATTR_REPLAY_PROTECT	(1 << 10)
#define MACSEC_ATTR_VALIDATION		(1 << 11)
#define MACSEC_ATTR_PORT		(1 << 12)

struct macsec_info {
	int ifindex;
	uint64_t sci;
	uint16_t port;
	uint64_t cipher_suite;
	uint16_t icv_len;
	uint32_t window;
	enum macsec_validation_type validate;
	uint8_t encoding_sa;

	uint8_t send_sci, end_station, scb, replay_protect, protect, encrypt;

	uint32_t ce_mask;
};

#define DEFAULT_ICV_LEN 16

/** @endcond */

static struct nla_policy macsec_policy[IFLA_MACSEC_MAX+1] = {
	[IFLA_MACSEC_SCI] = { .type = NLA_U64 },
	[IFLA_MACSEC_ICV_LEN] = { .type = NLA_U8 },
	[IFLA_MACSEC_CIPHER_SUITE] = { .type = NLA_U64 },
	[IFLA_MACSEC_WINDOW] = { .type = NLA_U32 },
	[IFLA_MACSEC_ENCODING_SA] = { .type = NLA_U8 },
	[IFLA_MACSEC_ENCRYPT] = { .type = NLA_U8 },
	[IFLA_MACSEC_PROTECT] = { .type = NLA_U8 },
	[IFLA_MACSEC_INC_SCI] = { .type = NLA_U8 },
	[IFLA_MACSEC_ES] = { .type = NLA_U8 },
	[IFLA_MACSEC_SCB] = { .type = NLA_U8 },
	[IFLA_MACSEC_REPLAY_PROTECT] = { .type = NLA_U8 },
	[IFLA_MACSEC_VALIDATION] = { .type = NLA_U8 },
};

/**
 * @name MACsec Object
 * @{
 */

/**
 * Allocate link object of type MACsec
 *
 * @return Allocated link object or NULL.
 */
static int macsec_alloc(struct rtnl_link *link)
{
	struct macsec_info *info;

	if (!link->l_info) {
		link->l_info = malloc(sizeof(struct macsec_info));
		if (!link->l_info)
			return -NLE_NOMEM;
	}

	memset(link->l_info, 0, sizeof(struct macsec_info));
	info = link->l_info;

	info->cipher_suite = MACSEC_DEFAULT_CIPHER_ID;
	info->icv_len = DEFAULT_ICV_LEN;
	info->ce_mask = MACSEC_ATTR_CIPHER_SUITE | MACSEC_ATTR_ICV_LEN;

	return 0;
}

static int macsec_parse(struct rtnl_link *link, struct nlattr *data,
		      struct nlattr *xstats)
{
	struct nlattr *tb[IFLA_MACSEC_MAX+1];
	struct macsec_info *info;
	int err;

	NL_DBG(3, "Parsing MACsec link info\n");

	if ((err = nla_parse_nested(tb, IFLA_MACSEC_MAX, data, macsec_policy)) < 0)
		goto errout;

	if ((err = macsec_alloc(link)) < 0)
		goto errout;

	info = link->l_info;

	if (tb[IFLA_MACSEC_SCI]) {
		info->sci = nla_get_u64(tb[IFLA_MACSEC_SCI]);
		info->ce_mask |= MACSEC_ATTR_SCI;
	}

	if (tb[IFLA_MACSEC_PROTECT]) {
		info->protect = nla_get_u8(tb[IFLA_MACSEC_PROTECT]);
		info->ce_mask |= MACSEC_ATTR_PROTECT;
	}

	if (tb[IFLA_MACSEC_CIPHER_SUITE]) {
		info->cipher_suite = nla_get_u64(tb[IFLA_MACSEC_CIPHER_SUITE]);
		info->ce_mask |= MACSEC_ATTR_CIPHER_SUITE;
	}

	if (tb[IFLA_MACSEC_ICV_LEN]) {
		info->icv_len = nla_get_u8(tb[IFLA_MACSEC_ICV_LEN]);
		info->ce_mask |= MACSEC_ATTR_ICV_LEN;
	}

	if (tb[IFLA_MACSEC_ENCODING_SA]) {
		info->encoding_sa = nla_get_u8(tb[IFLA_MACSEC_ENCODING_SA]);
		info->ce_mask |= MACSEC_ATTR_ENCODING_SA;
	}

	if (tb[IFLA_MACSEC_VALIDATION]) {
		info->validate = nla_get_u8(tb[IFLA_MACSEC_VALIDATION]);
		info->ce_mask |= MACSEC_ATTR_VALIDATION;
	}

	if (tb[IFLA_MACSEC_ENCRYPT]) {
		info->encrypt = nla_get_u8(tb[IFLA_MACSEC_ENCRYPT]);
		info->ce_mask |= MACSEC_ATTR_ENCRYPT;
	}

	if (tb[IFLA_MACSEC_INC_SCI]) {
		info->send_sci = nla_get_u8(tb[IFLA_MACSEC_INC_SCI]);
		info->ce_mask |= MACSEC_ATTR_INC_SCI;
	}

	if (tb[IFLA_MACSEC_ES]) {
		info->end_station = nla_get_u8(tb[IFLA_MACSEC_ES]);
		info->ce_mask |= MACSEC_ATTR_ES;
	}

	if (tb[IFLA_MACSEC_SCB]) {
		info->scb = nla_get_u8(tb[IFLA_MACSEC_SCB]);
		info->ce_mask |= MACSEC_ATTR_SCB;
	}

	if (tb[IFLA_MACSEC_REPLAY_PROTECT]) {
		info->replay_protect = nla_get_u8(tb[IFLA_MACSEC_REPLAY_PROTECT]);
		info->ce_mask |= MACSEC_ATTR_REPLAY_PROTECT;
	}

	if (tb[IFLA_MACSEC_WINDOW]) {
		info->window = nla_get_u32(tb[IFLA_MACSEC_WINDOW]);
		info->ce_mask |= MACSEC_ATTR_WINDOW;
	}

	err = 0;
errout:
	return err;
}

static void macsec_free(struct rtnl_link *link)
{
	free(link->l_info);
	link->l_info = NULL;
}

static const char *values_on_off[] = {	"off", "on" };

static const char *VALIDATE_STR[] = {
	[MACSEC_VALIDATE_DISABLED] = "disabled",
	[MACSEC_VALIDATE_CHECK] = "check",
	[MACSEC_VALIDATE_STRICT] = "strict",
};

static char *replay_protect_str(char *buf, uint8_t replay_protect, uint8_t window)
{
	if (replay_protect == 1) {
		sprintf(buf, "replay_protect on window %d", window);
	} else if (replay_protect == 0) {
		sprintf(buf, "replay_protect off");
	} else {
		buf[0] = '\0';
	}

	return buf;
}

/** @cond SKIP */
#define PRINT_FLAG(buf, i, field, c) ({ if (i->field == 1) *buf++ = c; })
/** @endcond */
static char *flags_str(char *buf, unsigned char len, struct macsec_info *info)
{
	char *tmp = buf;
	memset(tmp, 0, len);

	PRINT_FLAG(tmp, info, protect, 'P');
	PRINT_FLAG(tmp, info, encrypt, 'E');
	PRINT_FLAG(tmp, info, send_sci, 'S');
	PRINT_FLAG(tmp, info, end_station, 'e');
	PRINT_FLAG(tmp, info, scb, 's');
	PRINT_FLAG(tmp, info, replay_protect, 'R');

	*tmp++ = ' ';
	*tmp++ = 'v';
	switch (info->validate) {
	case MACSEC_VALIDATE_DISABLED:
		*tmp++ = 'd';
		break;
	case MACSEC_VALIDATE_CHECK:
		*tmp++ = 'c';
		break;
	case MACSEC_VALIDATE_STRICT:
		*tmp++ = 's';
		break;
	default:
		break;
	}

	sprintf(tmp, " %d", info->encoding_sa);

	return buf;
}

static void macsec_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
{
	struct macsec_info *info = link->l_info;
	char tmp[128];

	nl_dump(p, "sci %016llx <%s>", ntohll(info->sci), flags_str(tmp, sizeof(tmp), info));
}

static void macsec_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
{
	struct macsec_info *info = link->l_info;
	char tmp[128];

	nl_dump(p, "    sci %016llx protect %s encoding_sa %d encrypt %s send_sci %s validate %s %s\n",
		ntohll(info->sci), values_on_off[info->protect], info->encoding_sa, values_on_off[info->encrypt], values_on_off[info->send_sci],
		VALIDATE_STR[info->validate],
		replay_protect_str(tmp, info->replay_protect, info->window));
	nl_dump(p, "    cipher suite: %016llx, icv_len %d\n",
		info->cipher_suite, info->icv_len);
}

static int macsec_clone(struct rtnl_link *dst, struct rtnl_link *src)
{
	struct macsec_info *copy, *info = src->l_info;
	int err;

	dst->l_info = NULL;
	if ((err = rtnl_link_set_type(dst, "macsec")) < 0)
		return err;
	copy = dst->l_info;

	if (!info || !copy)
		return -NLE_NOMEM;

	memcpy(copy, info, sizeof(struct macsec_info));

	return 0;
}

static int macsec_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
{
	struct macsec_info *info = link->l_info;
	struct nlattr *data;

	if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
		return -NLE_MSGSIZE;

	if (info->ce_mask & MACSEC_ATTR_SCI)
		NLA_PUT_U64(msg, IFLA_MACSEC_SCI, info->sci);
	else if (info->ce_mask & MACSEC_ATTR_PORT)
		NLA_PUT_U16(msg, IFLA_MACSEC_PORT, htons(info->port));

	if ((info->ce_mask & MACSEC_ATTR_ENCRYPT))
		NLA_PUT_U8(msg, IFLA_MACSEC_ENCRYPT, info->encrypt);

	if (info->cipher_suite != MACSEC_DEFAULT_CIPHER_ID || info->icv_len != DEFAULT_ICV_LEN) {
		NLA_PUT_U64(msg, IFLA_MACSEC_CIPHER_SUITE, info->cipher_suite);
		NLA_PUT_U8(msg, IFLA_MACSEC_ICV_LEN, info->icv_len);
	}

	if ((info->ce_mask & MACSEC_ATTR_INC_SCI))
		NLA_PUT_U8(msg, IFLA_MACSEC_INC_SCI, info->send_sci);

	if ((info->ce_mask & MACSEC_ATTR_ES))
		NLA_PUT_U8(msg, IFLA_MACSEC_ES, info->end_station);

	if ((info->ce_mask & MACSEC_ATTR_SCB))
		NLA_PUT_U8(msg, IFLA_MACSEC_SCB, info->scb);

	if ((info->ce_mask & MACSEC_ATTR_PROTECT))
		NLA_PUT_U8(msg, IFLA_MACSEC_PROTECT, info->protect);

	if ((info->ce_mask & MACSEC_ATTR_REPLAY_PROTECT)) {
		if (info->replay_protect && !(info->ce_mask & MACSEC_ATTR_WINDOW))
			return -NLE_INVAL;

		NLA_PUT_U8(msg, IFLA_MACSEC_REPLAY_PROTECT, info->replay_protect);
		NLA_PUT_U32(msg, IFLA_MACSEC_WINDOW, info->window);
	}

	if ((info->ce_mask & MACSEC_ATTR_VALIDATION))
		NLA_PUT_U8(msg, IFLA_MACSEC_VALIDATION, info->validate);

	if ((info->ce_mask & MACSEC_ATTR_ENCODING_SA))
		NLA_PUT_U8(msg, IFLA_MACSEC_ENCODING_SA, info->encoding_sa);

	nla_nest_end(msg, data);

	return 0;

nla_put_failure:
	return -NLE_MSGSIZE;
}

static int macsec_compare(struct rtnl_link *link_a, struct rtnl_link *link_b,
			  int flags)
{
	struct macsec_info *a = link_a->l_info;
	struct macsec_info *b = link_b->l_info;
	int diff = 0;
	uint32_t attrs = flags & LOOSE_COMPARISON ? b->ce_mask : ~0;

#define MACSEC_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, MACSEC_ATTR_##ATTR, a, b, EXPR)

	if (a->ce_mask & MACSEC_ATTR_SCI && b->ce_mask & MACSEC_ATTR_SCI)
		diff |= MACSEC_DIFF(SCI, a->sci != b->sci);
	else if (a->ce_mask & MACSEC_ATTR_PORT && b->ce_mask & MACSEC_ATTR_PORT)
		diff |= MACSEC_DIFF(PORT, a->port != b->port);

	if (a->ce_mask & MACSEC_ATTR_CIPHER_SUITE && b->ce_mask & MACSEC_ATTR_CIPHER_SUITE) {
		diff |= MACSEC_DIFF(ICV_LEN, a->icv_len != b->icv_len);
		diff |= MACSEC_DIFF(CIPHER_SUITE, a->cipher_suite != b->cipher_suite);
	}

	if (a->ce_mask & MACSEC_ATTR_REPLAY_PROTECT && b->ce_mask & MACSEC_ATTR_REPLAY_PROTECT) {
		int d = MACSEC_DIFF(REPLAY_PROTECT, a->replay_protect != b->replay_protect);
		if (a->replay_protect && b->replay_protect)
			d |= MACSEC_DIFF(WINDOW, a->window != b->window);
		diff |= d;
	}

	diff |= MACSEC_DIFF(ENCODING_SA, a->encoding_sa != b->encoding_sa);
	diff |= MACSEC_DIFF(ENCRYPT, a->encrypt != b->encrypt);
	diff |= MACSEC_DIFF(PROTECT, a->protect != b->protect);
	diff |= MACSEC_DIFF(INC_SCI, a->send_sci != b->send_sci);
	diff |= MACSEC_DIFF(ES, a->end_station != b->end_station);
	diff |= MACSEC_DIFF(SCB, a->scb != b->scb);
	diff |= MACSEC_DIFF(VALIDATION, a->validate != b->validate);
#undef MACSEC_DIFF

	return diff;
}


static struct rtnl_link_info_ops macsec_info_ops = {
	.io_name		= "macsec",
	.io_alloc		= macsec_alloc,
	.io_parse		= macsec_parse,
	.io_dump = {
		[NL_DUMP_LINE] = macsec_dump_line,
		[NL_DUMP_DETAILS]	= macsec_dump_details,
	},
	.io_clone		= macsec_clone,
	.io_put_attrs		= macsec_put_attrs,
	.io_free		= macsec_free,
	.io_compare		= macsec_compare,
};

static void __init macsec_init(void)
{
	rtnl_link_register_info(&macsec_info_ops);
}

static void __exit macsec_exit(void)
{
	rtnl_link_unregister_info(&macsec_info_ops);
}

/** @cond SKIP */
#define IS_MACSEC_LINK_ASSERT(link) \
	if ((link)->l_info_ops != &macsec_info_ops) { \
		APPBUG("Link is not a MACsec link. set type \"macsec\" first."); \
		return -NLE_OPNOTSUPP; \
	}
/** @endcond */

struct rtnl_link *rtnl_link_macsec_alloc(void)
{
	struct rtnl_link *link = rtnl_link_alloc();

	if (!link)
		return NULL;

	if (rtnl_link_set_type(link, "macsec") < 0) {
		rtnl_link_put(link);
		return NULL;
	}

	return link;
}

/**
 * Set SCI
 * @arg link		Link object
 * @arg sci		Secure Channel Identifier in network byte order
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_macsec_set_sci(struct rtnl_link *link, uint64_t sci)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	info->sci = sci;
	info->ce_mask |= MACSEC_ATTR_SCI;

	return 0;
}

/**
 * Get SCI
 * @arg link		Link object
 * @arg sci		On return points to the Secure Channel Identifier
 *			in network byte order
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_macsec_get_sci(struct rtnl_link *link, uint64_t *sci)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_SCI))
		return -NLE_NOATTR;

	if (sci)
		*sci = info->sci;

	return 0;
}

/**
 * Set port identifier
 * @arg link		Link object
 * @arg port		Port identifier in host byte order
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_macsec_set_port(struct rtnl_link *link, uint16_t port)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	info->port = port;
	info->ce_mask |= MACSEC_ATTR_PORT;

	return 0;
}

/**
 * Get port identifier
 * @arg link		Link object
 * @arg port		On return points to the port identifier in host byte order
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_macsec_get_port(struct rtnl_link *link, uint16_t *port)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_PORT))
		return -NLE_NOATTR;

	if (port)
		*port = info->port;

	return 0;
}

int rtnl_link_macsec_set_cipher_suite(struct rtnl_link *link, uint64_t cipher_suite)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	info->cipher_suite = cipher_suite;
	info->ce_mask |= MACSEC_ATTR_CIPHER_SUITE;

	return 0;
}

int rtnl_link_macsec_get_cipher_suite(struct rtnl_link *link, uint64_t *cs)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_CIPHER_SUITE))
		return -NLE_NOATTR;

	if (cs)
		*cs = info->cipher_suite;

	return 0;
}

int rtnl_link_macsec_set_icv_len(struct rtnl_link *link, uint16_t icv_len)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (icv_len > MACSEC_STD_ICV_LEN)
		return -NLE_INVAL;

	info->icv_len = icv_len;
	info->ce_mask |= MACSEC_ATTR_ICV_LEN;

	return 0;
}

int rtnl_link_macsec_get_icv_len(struct rtnl_link *link, uint16_t *icv_len)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_ICV_LEN))
		return -NLE_NOATTR;

	if (icv_len)
		*icv_len = info->icv_len;

	return 0;
}

int rtnl_link_macsec_set_protect(struct rtnl_link *link, uint8_t protect)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (protect > 1)
		return -NLE_INVAL;

	info->protect = protect;
	info->ce_mask |= MACSEC_ATTR_PROTECT;

	return 0;
}

int rtnl_link_macsec_get_protect(struct rtnl_link *link, uint8_t *protect)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_PROTECT))
		return -NLE_NOATTR;

	if (protect)
		*protect = info->protect;

	return 0;
}

int rtnl_link_macsec_set_encrypt(struct rtnl_link *link, uint8_t encrypt)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (encrypt > 1)
		return -NLE_INVAL;

	info->encrypt = encrypt;
	info->ce_mask |= MACSEC_ATTR_ENCRYPT;

	return 0;
}

int rtnl_link_macsec_get_encrypt(struct rtnl_link *link, uint8_t *encrypt)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_ENCRYPT))
		return -NLE_NOATTR;

	if (encrypt)
		*encrypt = info->encrypt;

	return 0;
}

int rtnl_link_macsec_set_encoding_sa(struct rtnl_link *link, uint8_t encoding_sa)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (encoding_sa > 3)
		return -NLE_INVAL;

	info->encoding_sa = encoding_sa;
	info->ce_mask |= MACSEC_ATTR_ENCODING_SA;

	return 0;
}

int rtnl_link_macsec_get_encoding_sa(struct rtnl_link *link, uint8_t *encoding_sa)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_ENCODING_SA))
		return -NLE_NOATTR;

	if (encoding_sa)
		*encoding_sa = info->encoding_sa;

	return 0;
}

int rtnl_link_macsec_set_validation_type(struct rtnl_link *link, enum macsec_validation_type validate)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (validate > 1)
		return -NLE_INVAL;

	info->validate = validate;
	info->ce_mask |= MACSEC_ATTR_VALIDATION;

	return 0;
}

int rtnl_link_macsec_get_validation_type(struct rtnl_link *link, enum macsec_validation_type *validate)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_VALIDATION))
		return -NLE_NOATTR;

	if (validate)
		*validate = info->validate;

	return 0;
}

int rtnl_link_macsec_set_replay_protect(struct rtnl_link *link, uint8_t replay_protect)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (replay_protect > 1)
		return -NLE_INVAL;

	info->replay_protect = replay_protect;
	info->ce_mask |= MACSEC_ATTR_REPLAY_PROTECT;

	return 0;
}

int rtnl_link_macsec_get_replay_protect(struct rtnl_link *link, uint8_t *replay_protect)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_REPLAY_PROTECT))
		return -NLE_NOATTR;

	if (replay_protect)
		*replay_protect = info->replay_protect;

	return 0;
}

int rtnl_link_macsec_set_window(struct rtnl_link *link, uint32_t window)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	info->window = window;
	info->ce_mask |= MACSEC_ATTR_WINDOW;

	return 0;
}

int rtnl_link_macsec_get_window(struct rtnl_link *link, uint32_t *window)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_WINDOW))
		return -NLE_NOATTR;

	if (window)
		*window = info->window;

	return 0;
}

int rtnl_link_macsec_set_send_sci(struct rtnl_link *link, uint8_t send_sci)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (send_sci > 1)
		return -NLE_INVAL;

	info->send_sci = send_sci;
	info->ce_mask |= MACSEC_ATTR_INC_SCI;

	return 0;
}

int rtnl_link_macsec_get_send_sci(struct rtnl_link *link, uint8_t *send_sci)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_INC_SCI))
		return -NLE_NOATTR;

	if (send_sci)
		*send_sci = info->send_sci;

	return 0;
}

int rtnl_link_macsec_set_end_station(struct rtnl_link *link, uint8_t end_station)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (end_station > 1)
		return -NLE_INVAL;

	info->end_station = end_station;
	info->ce_mask |= MACSEC_ATTR_ES;

	return 0;
}

int rtnl_link_macsec_get_end_station(struct rtnl_link *link, uint8_t *es)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_ES))
		return -NLE_NOATTR;

	if (es)
		*es = info->end_station;

	return 0;
}

int rtnl_link_macsec_set_scb(struct rtnl_link *link, uint8_t scb)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (scb > 1)
		return -NLE_INVAL;

	info->scb = scb;
	info->ce_mask |= MACSEC_ATTR_SCB;

	return 0;
}

int rtnl_link_macsec_get_scb(struct rtnl_link *link, uint8_t *scb)
{
	struct macsec_info *info = link->l_info;

	IS_MACSEC_LINK_ASSERT(link);

	if (!(info->ce_mask & MACSEC_ATTR_SCB))
		return -NLE_NOATTR;

	if (scb)
		*scb = info->scb;

	return 0;
}

/** @} */

/** @} */
