// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2024 Robin Jarry

#include "event.h"
#include "iface.h"
#include "log.h"
#include "metrics.h"
#include "module.h"
#include "rcu.h"
#include "vlan.h"

#include <gr_infra.h>

#include <rte_ether.h>
#include <rte_hash.h>

LOG_TYPE("vlan");

struct vlan_key {
	uint16_t parent_id;
	uint16_t vlan_id;
};

static struct rte_hash *vlan_hash;

struct iface *vlan_get_iface(uint16_t parent_id, uint16_t vlan_id) {
	void *data;

	if (rte_hash_lookup_data(vlan_hash, &(struct vlan_key) {parent_id, vlan_id}, &data) < 0)
		return NULL;

	return data;
}

static int iface_vlan_reconfig(
	struct iface *iface,
	uint64_t set_attrs,
	const struct gr_iface *,
	const void *api_info
) {
	struct iface_info_vlan *cur = iface_info_vlan(iface);
	struct vlan_key cur_key = {cur->parent_id, cur->vlan_id};
	const struct gr_iface_info_vlan *next = api_info;
	struct vlan_key next_key = {next->parent_id, next->vlan_id};
	struct iface *cur_parent = NULL, *next_parent = NULL;
	struct gr_iface_info_vlan prev = cur->base;
	uint64_t conf_done = 0;
	int ret = 0;

	if (cur->parent_id != GR_IFACE_ID_UNDEF) {
		if ((cur_parent = iface_from_id(cur->parent_id)) == NULL)
			return -errno;
	}

	if (set_attrs & (GR_VLAN_SET_PARENT | GR_VLAN_SET_VLAN)
	    && memcmp(&next_key, &cur_key, sizeof(cur_key)) != 0) {
		if (next->vlan_id < 1 || next->vlan_id > RTE_ETHER_MAX_VLAN_ID) {
			errno = ERANGE;
			goto err;
		}

		if ((next_parent = iface_from_id(next->parent_id)) == NULL)
			goto err;

		if (rte_hash_lookup(vlan_hash, &next_key) >= 0) {
			errno = EADDRINUSE;
			goto err;
		}

		rte_hash_del_key(vlan_hash, &cur_key);

		if ((ret = rte_hash_add_key_data(vlan_hash, &next_key, iface)) < 0) {
			if (cur_parent != NULL && cur->vlan_id != 0)
				rte_hash_add_key_data(vlan_hash, &cur_key, iface);
			errno = -ret;
			goto err;
		}

		cur->parent_id = next->parent_id;
		cur->vlan_id = next->vlan_id;
		conf_done |= GR_VLAN_SET_PARENT | GR_VLAN_SET_VLAN;
	}

	if (set_attrs & GR_VLAN_SET_MAC) {
		if (iface_set_eth_addr(iface, &next->mac) < 0)
			goto err;
		conf_done |= GR_VLAN_SET_MAC;
	}

	if (cur_parent != next_parent) {
		if (cur_parent != NULL) {
			struct rte_ether_addr parent_mac;
			if (iface_get_eth_addr(cur_parent, &parent_mac) == 0
			    && !rte_is_same_ether_addr(&parent_mac, &cur->mac)) {
				iface_del_eth_addr(cur_parent, &cur->mac);
			}
			iface_del_subinterface(cur_parent, iface);
		}
		if (next_parent != NULL) {
			iface->mtu = next_parent->mtu;
			iface->speed = next_parent->speed;
			if (iface->state & GR_IFACE_S_RUNNING) {
				iface->state &= ~GR_IFACE_S_RUNNING;
				event_push(GR_EVENT_IFACE_STATUS_DOWN, iface);
			}
			iface_add_subinterface(next_parent, iface);

			if ((iface->flags & GR_IFACE_F_UP)
			    && (next_parent->state & GR_IFACE_S_RUNNING)) {
				iface->state |= GR_IFACE_S_RUNNING;
				event_push(GR_EVENT_IFACE_STATUS_UP, iface);
			}
		}
	}

	return 0;

err:
	ret = errno ?: EINVAL;
	if (conf_done & (GR_VLAN_SET_PARENT | GR_VLAN_SET_VLAN)) {
		rte_hash_del_key(vlan_hash, &next_key);
		cur->parent_id = cur_key.parent_id;
		cur->vlan_id = cur_key.vlan_id;
		if (cur_parent != NULL && cur->vlan_id != 0) {
			rte_hash_add_key_data(vlan_hash, &cur_key, iface);
		}
	}
	if (conf_done & GR_VLAN_SET_MAC) {
		iface_set_eth_addr(iface, &prev.mac);
	}
	return errno_set(ret);
}

static int iface_vlan_fini(struct iface *iface) {
	struct iface_info_vlan *vlan = iface_info_vlan(iface);
	struct iface *parent = iface_from_id(vlan->parent_id);
	int ret, status = 0;

	rte_hash_del_key(vlan_hash, &(struct vlan_key) {vlan->parent_id, vlan->vlan_id});

	if ((ret = iface_del_eth_addr(parent, &vlan->mac)) < 0)
		status = status ?: ret;

	iface_del_subinterface(parent, iface);

	return status;
}

static int iface_vlan_init(struct iface *iface, const void *api_info) {
	return iface_vlan_reconfig(iface, IFACE_SET_ALL, NULL, api_info);
}

static int iface_vlan_up_down(struct iface *iface, bool up) {
	struct iface_info_vlan *v = iface_info_vlan(iface);

	if (up) {
		const struct iface *parent = iface_from_id(v->parent_id);
		assert(parent != NULL);
		iface->flags |= GR_IFACE_F_UP;
		if (parent->state & GR_IFACE_S_RUNNING) {
			iface->state |= GR_IFACE_S_RUNNING;
			event_push(GR_EVENT_IFACE_STATUS_UP, iface);
		}
	} else {
		iface->flags &= ~GR_IFACE_F_UP;
		iface->state &= ~GR_IFACE_S_RUNNING;
		event_push(GR_EVENT_IFACE_STATUS_DOWN, iface);
	}

	return 0;
}

static int iface_vlan_get_eth_addr(const struct iface *iface, struct rte_ether_addr *mac) {
	const struct iface_info_vlan *vlan = iface_info_vlan(iface);
	*mac = vlan->mac;
	return 0;
}

static int iface_vlan_set_eth_addr(struct iface *iface, const struct rte_ether_addr *mac) {
	struct iface_info_vlan *vlan = iface_info_vlan(iface);
	struct iface *parent = iface_from_id(vlan->parent_id);
	struct rte_ether_addr parent_mac, next_mac;
	int ret;

	if ((ret = iface_get_eth_addr(parent, &parent_mac)) < 0)
		return ret;

	if (rte_is_zero_ether_addr(mac))
		next_mac = parent_mac;
	else
		next_mac = *mac;

	if (!rte_is_same_ether_addr(&vlan->mac, &next_mac)) {
		if (!rte_is_same_ether_addr(&parent_mac, &vlan->mac))
			iface_del_eth_addr(parent, &vlan->mac);
		if ((ret = iface_add_eth_addr(parent, &next_mac)) < 0)
			return ret;
		vlan->mac = next_mac;
	}

	return 0;
}

static int iface_vlan_add_eth_addr(struct iface *iface, struct iface_mac *m) {
	const struct iface_info_vlan *vlan = iface_info_vlan(iface);
	struct iface *parent = iface_from_id(vlan->parent_id);

	return iface_add_eth_addr(parent, &m->mac);
}

static int iface_vlan_del_eth_addr(struct iface *iface, struct iface_mac *m) {
	const struct iface_info_vlan *vlan = iface_info_vlan(iface);
	struct iface *parent = iface_from_id(vlan->parent_id);

	return iface_del_eth_addr(parent, &m->mac);
}

static int iface_vlan_promisc_set(struct iface *iface, bool enabled) {
	const struct iface_info_vlan *vlan = iface_info_vlan(iface);
	struct iface *parent = iface_from_id(vlan->parent_id);

	return iface_set_promisc(parent, enabled);
}

static void vlan_to_api(void *info, const struct iface *iface) {
	const struct iface_info_vlan *vlan = iface_info_vlan(iface);
	struct gr_iface_info_vlan *api = info;

	*api = vlan->base;
}

static void vlan_metrics_collect(struct metrics_ctx *ctx, const struct iface *iface) {
	const struct iface_info_vlan *vlan = iface_info_vlan(iface);
	const struct iface *parent = iface_from_id(vlan->parent_id);

	metrics_labels_add(ctx, "parent", parent ? parent->name : "[deleted]", NULL);
}

static const struct iface_type iface_type_vlan = {
	.id = GR_IFACE_TYPE_VLAN,
	.pub_size = sizeof(struct gr_iface_info_vlan),
	.priv_size = sizeof(struct iface_info_vlan),
	.init = iface_vlan_init,
	.reconfig = iface_vlan_reconfig,
	.fini = iface_vlan_fini,
	.set_up_down = iface_vlan_up_down,
	.get_eth_addr = iface_vlan_get_eth_addr,
	.set_eth_addr = iface_vlan_set_eth_addr,
	.add_eth_addr = iface_vlan_add_eth_addr,
	.del_eth_addr = iface_vlan_del_eth_addr,
	.set_promisc = iface_vlan_promisc_set,
	.to_api = vlan_to_api,
	.metrics_collect = vlan_metrics_collect,
};

static void vlan_init(struct event_base *) {
	struct rte_hash_parameters params = {
		.name = "vlan",
		.entries = GR_MAX_IFACES,
		.key_len = sizeof(struct vlan_key),
		.socket_id = SOCKET_ID_ANY,
		.extra_flag = RTE_HASH_EXTRA_FLAGS_RW_CONCURRENCY_LF
			| RTE_HASH_EXTRA_FLAGS_TRANS_MEM_SUPPORT,
	};
	vlan_hash = rte_hash_create(&params);
	if (vlan_hash == NULL)
		ABORT("rte_hash_create(vlan)");

	struct rte_hash_rcu_config rcu_config = {
		.v = gr_datapath_rcu(), .mode = RTE_HASH_QSBR_MODE_SYNC
	};
	rte_hash_rcu_qsbr_add(vlan_hash, &rcu_config);
}

static void vlan_fini(struct event_base *) {
	rte_hash_free(vlan_hash);
	vlan_hash = NULL;
}

static struct module vlan_module = {
	.name = "vlan",
	.depends_on = "rcu",
	.init = vlan_init,
	.fini = vlan_fini,
};

RTE_INIT(vlan_constructor) {
	module_register(&vlan_module);
	iface_type_register(&iface_type_vlan);
}
