/*
 * common.c: Shared helpers for bridgemngr datamodel backends
 *
 * Copyright (C) 2026 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for license related information.
 */

#include "common.h"

#ifdef BRIDGE_ENABLE_FDB_EXT

#include <net/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/neighbour.h>

static int fdb_netlink_request(int sock, unsigned int br_ifindex)
{
	struct {
		struct nlmsghdr nlh;
		struct ndmsg ndm;
	} req;

	memset(&req, 0, sizeof(req));
	req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg));
	req.nlh.nlmsg_type = RTM_GETNEIGH;
	req.nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
	req.nlh.nlmsg_seq = 1;
	req.ndm.ndm_family = AF_BRIDGE;
	req.ndm.ndm_ifindex = (int)br_ifindex;

	return (int)send(sock, &req, req.nlh.nlmsg_len, 0);
}

static void fdb_parse_entry(struct nlmsghdr *nlh, unsigned int port_ifindex,
			    struct dmctx *dmctx, DMNODE *parent_node,
			    struct dm_data *curr_data, int *inst_num)
{
	struct ndmsg *ndm = NLMSG_DATA(nlh);
	struct rtattr *rta;
	int rta_len;
	unsigned char *mac = NULL;
	unsigned int vid = 0;

	/* Filter: only entries on our target port */
	if ((unsigned int)ndm->ndm_ifindex != port_ifindex)
		return;

	rta = (struct rtattr *)((char *)ndm + NLMSG_ALIGN(sizeof(struct ndmsg)));
	rta_len = (int)(nlh->nlmsg_len - NLMSG_LENGTH(sizeof(struct ndmsg)));

	for (; RTA_OK(rta, rta_len); rta = RTA_NEXT(rta, rta_len)) {
		if (rta->rta_type == NDA_LLADDR)
			mac = (unsigned char *)RTA_DATA(rta);
		else if (rta->rta_type == NDA_VLAN)
			vid = *(unsigned short *)RTA_DATA(rta);
	}

	if (!mac)
		return;

	struct fdb_entry_data *fdb = dmcalloc(1, sizeof(struct fdb_entry_data));
	if (!fdb)
		return;

	snprintf(fdb->mac, sizeof(fdb->mac), "%02x:%02x:%02x:%02x:%02x:%02x",
		 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
	fdb->vid = vid;
	fdb->is_local = (ndm->ndm_flags & NTF_SELF) != 0;

	if (ndm->ndm_state & NUD_PERMANENT)
		strncpy(fdb->status, "Permanent", sizeof(fdb->status) - 1);
	else if (ndm->ndm_state & NUD_NOARP)
		strncpy(fdb->status, "NoARP", sizeof(fdb->status) - 1);
	else if (ndm->ndm_state & NUD_REACHABLE)
		strncpy(fdb->status, "Reachable", sizeof(fdb->status) - 1);
	else if (ndm->ndm_state & NUD_STALE)
		strncpy(fdb->status, "Stale", sizeof(fdb->status) - 1);
	else if (ndm->ndm_state & NUD_DELAY)
		strncpy(fdb->status, "Delay", sizeof(fdb->status) - 1);
	else if (ndm->ndm_state & NUD_PROBE)
		strncpy(fdb->status, "Probe", sizeof(fdb->status) - 1);
	else if (ndm->ndm_state & NUD_FAILED)
		strncpy(fdb->status, "Failed", sizeof(fdb->status) - 1);
	else if (ndm->ndm_state & NUD_INCOMPLETE)
		strncpy(fdb->status, "Incomplete", sizeof(fdb->status) - 1);
	else
		strncpy(fdb->status, "Dynamic", sizeof(fdb->status) - 1);

	curr_data->additional_data = fdb;

	char *inst = handle_instance_without_section(dmctx, parent_node, ++(*inst_num));

	DM_LINK_INST_OBJ(dmctx, parent_node, curr_data, inst);
}

int fdb_browse_port_entries(struct dmctx *dmctx, DMNODE *parent_node,
			    const char *bridge_name, const char *port_name)
{
	struct dm_data curr_data = {0};
	unsigned int br_ifindex, port_ifindex;
	int sock;
	int inst_num = 0;
	char buf[8192];

	br_ifindex = if_nametoindex(bridge_name);
	port_ifindex = if_nametoindex(port_name);
	if (!br_ifindex || !port_ifindex)
		return 0;

	sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
	if (sock < 0)
		return 0;

	if (fdb_netlink_request(sock, br_ifindex) < 0) {
		close(sock);
		return 0;
	}

	while (1) {
		ssize_t len = recv(sock, buf, sizeof(buf), 0);
		if (len <= 0)
			break;

		struct nlmsghdr *nlh;
		for (nlh = (struct nlmsghdr *)buf; NLMSG_OK(nlh, (unsigned int)len); nlh = NLMSG_NEXT(nlh, len)) {
			if (nlh->nlmsg_type == NLMSG_DONE) {
				close(sock);
				return 0;
			}
			if (nlh->nlmsg_type == NLMSG_ERROR) {
				close(sock);
				return 0;
			}
			fdb_parse_entry(nlh, port_ifindex, dmctx, parent_node, &curr_data, &inst_num);
		}
	}

	close(sock);
	return 0;
}

int browseBridgingBridgePortFDBEntryInst(struct dmctx *dmctx, DMNODE *parent_node, void *prev_data, char *prev_instance)
{
	char *bridge_name = NULL;
	char *port_name = NULL;

#ifdef DRIVER_VLAN_BACKEND
	/* Get port name from the port dmmap section */
	dmuci_get_value_by_section_string(((struct dm_data *)prev_data)->dmmap_section, "port", &port_name);
	if (!DM_STRLEN(port_name))
		return 0;

	/* Get bridge interface name from bridge config section */
	struct dm_data *bridge_data = (struct dm_data *)((struct dm_data *)prev_data)->additional_data;
	dmuci_get_value_by_section_string(bridge_data->config_section, "name", &bridge_name);
	if (!DM_STRLEN(bridge_name))
		return 0;
#else
	char *bridge_sec = NULL;
	struct uci_section *br_sec = NULL;

	/* Get port interface name from port config section */
	dmuci_get_value_by_section_string(((struct dm_data *)prev_data)->config_section, "name", &port_name);
	if (!DM_STRLEN(port_name))
		return 0;

	/* Get bridge interface name via the port's bridge reference */
	dmuci_get_value_by_section_string(((struct dm_data *)prev_data)->config_section, "bridge", &bridge_sec);
	if (!DM_STRLEN(bridge_sec))
		return 0;

	br_sec = get_origin_section_from_config("bridging", "bridge", bridge_sec);
	if (!br_sec)
		return 0;

	dmuci_get_value_by_section_string(br_sec, "name", &bridge_name);
	if (!DM_STRLEN(bridge_name))
		return 0;
#endif

	return fdb_browse_port_entries(dmctx, parent_node, bridge_name, port_name);
}

int get_BridgingBridgePort_FDBEntryNumberOfEntries(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	int cnt = get_number_of_entries(ctx, data, instance, browseBridgingBridgePortFDBEntryInst);
	dmasprintf(value, "%d", cnt);
	return 0;
}

/*************************************************************
* FDB ENTRY GETTER FUNCTIONS
**************************************************************/
int get_BridgingBridgePortFDBEntry_MACAddress(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct fdb_entry_data *fdb = (struct fdb_entry_data *)((struct dm_data *)data)->additional_data;

	if (fdb)
		*value = dmstrdup(fdb->mac);
	else
		*value = dmstrdup("");

	return 0;
}

int get_BridgingBridgePortFDBEntry_VID(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct fdb_entry_data *fdb = (struct fdb_entry_data *)((struct dm_data *)data)->additional_data;

	if (fdb)
		dmasprintf(value, "%u", fdb->vid);
	else
		*value = dmstrdup("0");

	return 0;
}

int get_BridgingBridgePortFDBEntry_IsLocal(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct fdb_entry_data *fdb = (struct fdb_entry_data *)((struct dm_data *)data)->additional_data;

	if (fdb)
		*value = dmstrdup(fdb->is_local ? "1" : "0");
	else
		*value = dmstrdup("0");

	return 0;
}

int get_BridgingBridgePortFDBEntry_Status(char *refparam, struct dmctx *ctx, void *data, char *instance, char **value)
{
	struct fdb_entry_data *fdb = (struct fdb_entry_data *)((struct dm_data *)data)->additional_data;

	if (fdb && fdb->status[0])
		*value = dmstrdup(fdb->status);
	else
		*value = dmstrdup("Dynamic");

	return 0;
}

/*** Bridging.Bridge.{i}.Port.{i}.X_<VENDOR>_FDBEntry.{i}. ***/
DMLEAF tBridgingBridgePortFDBEntryParams[] = {
/* PARAM, permission, type, getvalue, setvalue, bbfdm_type, version*/
{"MACAddress", &DMREAD, DMT_STRING, get_BridgingBridgePortFDBEntry_MACAddress, NULL, BBFDM_BOTH},
{"VID", &DMREAD, DMT_UNINT, get_BridgingBridgePortFDBEntry_VID, NULL, BBFDM_BOTH},
{"IsLocal", &DMREAD, DMT_BOOL, get_BridgingBridgePortFDBEntry_IsLocal, NULL, BBFDM_BOTH},
{"Status", &DMREAD, DMT_STRING, get_BridgingBridgePortFDBEntry_Status, NULL, BBFDM_BOTH},
{0}
};

#endif /* BRIDGE_ENABLE_FDB_EXT */
