/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * i1905_al.c - IEEE-1905 AL functions
 *
 * Copyright (C) 2021-2024 IOPSYS Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <linux/if_packet.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <arpa/inet.h>

#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>

#include "debug.h"
#include "util.h"
#include "timer.h"
#include "config.h"
#include "cmdu.h"
#include "cmdu_ackq.h"
#include "cmdufrag.h"
#include "1905_tlvs.h"
#include "i1905_dm.h"
#include "i1905.h"
#include "i1905_wifi.h"

extern int i1905_update_neigh_brport(struct i1905_private *priv, char *brname);

static enum i1905_mediatype eth_get_mediatype(const char *ifname)
{
	uint32_t speed = 0;
	int ret;

	ret = eth_get_linkspeed(ifname, &speed);
	if (ret)
		return I1905_MEDIA_UNKNOWN;

	switch (speed) {
	case 100:
		return I1905_802_3U_FAST_ETHERNET;
	case 1000:
		return I1905_802_3AB_GIGABIT_ETHERNET;
#ifdef ETH_MEDIA_EXTENSION
	case 2500:
		return I1905_802_3BZ_2_5G_ETHERNET;
	case 5000:
		return I1905_802_3BZ_5G_ETHERNET;
	case 10000:
		return I1905_802_3AE_10G_ETHERNET;
#endif
	default:
		break;
	}

	return I1905_MEDIA_UNKNOWN;
}

int i1905_get_link_info(struct i1905_interface *iface, uint8_t *peer_macaddr)
{

	return 0;
}

int i1905_dm_update_interface_linkmetrics(struct i1905_private *priv,
					  struct i1905_interface *iface)
{
	struct i1905_neighbor_interface *nif = NULL;
	struct if_stats s = {0};


	if (!iface)
		return -1;


	if (!IS_MEDIA_WIFI(iface->media)) {
		int ret;

		ret = if_getstats(iface->ifname, &s);
		if (ret)
			return -1;
	}

	list_for_each_entry(nif, &iface->nbriflist, list) {
		if (nif->media == I1905_MEDIA_UNKNOWN)
			nif->media = iface->media;

		if (!IS_MEDIA_WIFI(iface->media)) {
			uint32_t rate = 0;

			nif->has_bridge = iface->is_brif ? true : false;
			nif->metric.br_present = iface->is_brif ? true : false;
			nif->metric.tx_errors = s.tx_errors;
			nif->metric.rx_errors = s.rx_errors;
			nif->metric.tx_packets = s.tx_packets;
			nif->metric.rx_packets = s.rx_packets;
			nif->metric.available = 100;
			if (!eth_get_linkspeed(iface->ifname, &rate)) {
				nif->metric.max_rate = rate;
				nif->metric.max_phyrate = rate;
			} else {
				nif->metric.max_rate = 0xffff;
				nif->metric.max_phyrate = 0xffff;
			}

			nif->metric.rssi = -1;
		} else {
			struct i1905_wifi_mediainfo *mediainfo =
				(struct i1905_wifi_mediainfo *)iface->mediainfo;
			struct ieee80211_info *wifi =
				(struct ieee80211_info *) &mediainfo->info;
			struct i1905_metric metric = {0};
			int res = -1;

			memset(&metric, 0, sizeof(struct i1905_metric));
			if (wifi->role == IEEE80211_ROLE_AP) {
				res = platform_wifi_get_assoc_sta_metric(iface->ifname, nif->r_macaddr, &metric);
			} else if (wifi->role == IEEE80211_ROLE_STA) {
				res = platform_wifi_get_interface_metric(iface->ifname, &metric);
			}

			if (!res) {
				memcpy(&nif->metric, &metric, sizeof(struct i1905_metric));
				nif->has_bridge = iface->is_brif ? true : false;
				nif->metric.br_present = iface->is_brif ? true : false;
			} else {
				i1905_dbg(LOG_DM, "%s: Failed to update metrics\n", iface->ifname);
			}
		}
	}

	return 0;
}

int i1905_dm_update_interface_non1905_nbrs(struct i1905_private *p,
					   struct i1905_interface *iface)
{
	struct neigh_queue *q = (struct neigh_queue *)&p->neigh_q;
	struct i1905_selfdevice *self = &p->dm.self;
	struct i1905_device *rdev = NULL;
	struct neigh_entry *e = NULL;
	uint8_t stas[768] = {0};
	int numb = 128;
	int idx = 0;
	int i, k;


	/* delete non1905-neighbors of this interface */
	list_flush(&iface->non1905_nbrlist, struct i1905_non1905_neighbor, list);
	iface->num_neighbor_non1905 = 0;

	if (iface->invalid)
		return 0;

	/* get associated STAs */
	platform_wifi_get_assoclist(iface->ifname, stas, &numb);

	/* get neigh entries reachable through this interface */
	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		hlist_for_each_entry(e, &q->table[idx], hlist) {
			if (e->is1905 || e->is1905_slave || e->unreachable)
				continue;

			if ((iface->brport && e->brport == iface->brport) ||
			    !strncmp(e->ifname, iface->ifname, 16)) {
				struct i1905_non1905_neighbor *nnbr;

				if (e->type != NEIGH_TYPE_WIFI) {
					struct timeval res, now = { 0 };

					getcurrtime(&now);
					timersub(&now, &e->last_seen, &res);
					if (res.tv_sec >= NEIGH_AGEOUT_DEFAULT)
						continue;

					nnbr = calloc(1, sizeof(*nnbr));
					if (nnbr) {
						memcpy(nnbr->macaddr, e->macaddr, 6);
						list_add_tail(&nnbr->list, &iface->non1905_nbrlist);
						iface->num_neighbor_non1905++;
					}
				} else {
					for (i = 0; i < numb; i++) {
						if (!memcmp(&stas[i*6], e->macaddr, 6)) {
							nnbr = calloc(1, sizeof(*nnbr));
							if (nnbr) {
								memcpy(nnbr->macaddr, e->macaddr, 6);
								list_add_tail(&nnbr->list, &iface->non1905_nbrlist);
								iface->num_neighbor_non1905++;
								break;
							}
						}
					}
				}
			}
		}
	}

	/* discard non1905-neighbors reported by our downstream neighbors */
	list_for_each_entry(rdev, &self->topology.devlist, list) {
		if (rdev->upstream)
			continue;

		for (k = 0; k < rdev->num_neighbor_non1905; k++) {
			struct i1905_non1905_neighbor *xn, *tmp;

			list_for_each_entry_safe(xn, tmp, &iface->non1905_nbrlist, list) {
				if (hwaddr_equal(&rdev->non1905_macaddrs[k*6], xn->macaddr)) {
					list_del(&xn->list);
					iface->num_neighbor_non1905--;
					free(xn);
				}
			}
		}
	}

	return 0;
}

int i1905_dm_update_interface_self(struct i1905_private *p,
				   struct i1905_interface *iface)
{
	struct ip_address ips[32] = {0};
	enum if_mediatype mtype;
	int num = 32;
	int ifindex;
	int ret;


	if (!iface)
		return -1;

	i1905_dbg(LOG_DM, "%s: %s\n", __func__, iface->ifname);

	ifindex = if_nametoindex(iface->ifname);
	if (!ifindex) {
		i1905_err(LOG_DM, "%s: %s not found\n", __func__, iface->ifname);
		return -1;
	}

	if_getflags(iface->ifname, &iface->ifstatus);
	if (!(iface->ifstatus & IFF_UP)) {
		i1905_err(LOG_DM, "%s: %s not up\n", __func__, iface->ifname);
		return 0;
	}

	if (ifindex != iface->ifindex) {
		i1905_dbg(LOG_DM, "%s: %s ifindex mismatch (old = %d, new = %d) rebinding\n",
			__func__, iface->ifname, iface->ifindex, ifindex);

		if (!i1905_rebind_interface(p, iface))
			iface->ifindex = ifindex;
	}

	if (!strncmp(iface->ifname, "lo", 2))
		return 0;

	ret = if_isbridge_interface(iface->ifname);
	if (ret > 0) {
		iface->is_brif = true;
		iface->br_ifindex = ret;
	} else {
		iface->is_brif = false;
		iface->br_ifindex = 0;
	}


	ret = if_getaddrs(iface->ifname, ips, &num);
	if (!ret) {
		if (iface->ipaddrs) {
			free(iface->ipaddrs);
			iface->ipaddrs = NULL;
			iface->num_ipaddrs = 0;
		}

		if (num > 0) {
			iface->ipaddrs = calloc(num, sizeof(struct ip_address));
			if (!iface->ipaddrs) {
				i1905_err(LOG_DM, "%s\n", "-ENOMEM!");
				return -1;
			}

			iface->num_ipaddrs = num;
			memcpy(iface->ipaddrs, ips, num * sizeof(struct ip_address));
#if 1	//debug
			for (int i = 0; i < num; i++) {
				char buf[256] = {0};
				size_t sz = 256;

				if (ips[i].family == AF_INET)
					inet_ntop(AF_INET, &ips[i].addr.ip4, buf, sz);
				else
					inet_ntop(AF_INET6, &ips[i].addr.ip6, buf, sz);

				i1905_dbg(LOG_DM, "%s: ip = %s\n", iface->ifname, buf);
			}
#endif	//debug
		}
	}

	/* consider downstream-only non1905 neighbors */
	if (!iface->upstream)
		i1905_dm_update_interface_non1905_nbrs(p, iface);

	i1905_dbg(LOG_DM, "Updating mediainfo for '%s'\n", iface->ifname);
	if_getmediatype(iface->ifname, &mtype);
	if (mtype == IF_MEDIA_WIFI) {
		if (iface->mediainfo) {
			free(iface->mediainfo);
			iface->mediainfo = NULL;
			iface->num_mediainfo = 0;
			iface->media = I1905_MEDIA_UNKNOWN;
		}

		iface->mediatype = IF_MEDIA_WIFI;
		platform_wifi_get_mediainfo(iface->ifname,
						  &iface->media,
						  &iface->num_mediainfo,
						  (void **)&iface->mediainfo);
	} else {
		if (iface->mediainfo) {
			free(iface->mediainfo);
			iface->mediainfo = NULL;
			iface->num_mediainfo = 0;
		}

		iface->mediainfo = NULL;
		iface->mediatype = IF_MEDIA_ETH;
		iface->media = eth_get_mediatype(iface->ifname);
	}

	i1905_dm_update_interface_linkmetrics(p, iface);

	return 0;
}

int i1905_dm_update_master_interfaces(struct i1905_private *p)
{
	struct i1905_master_interface *m, *tmp;
	struct ip_address ips[32] = {0};
	int num = 32;
	int ret;

	list_for_each_entry_safe(m, tmp, &p->dm.self.miflist, list) {
		int ifindex;

		ifindex = if_nametoindex(m->ifname);
		if (!ifindex) {
			i1905_dbg(LOG_DM, "%s: %s vanished!\n", __func__, m->ifname);
			continue;
		}

		m->ifindex = ifindex;
		if_getflags(m->ifname, &m->ifstatus);
		if (!(m->ifstatus & IFF_UP)) {
			i1905_dbg(LOG_DM, "%s: %s not up\n", __func__, m->ifname);
			continue;
		}

		i1905_dbg(LOG_DM, "%s: %s\n", __func__, m->ifname);
		ret = if_getaddrs(m->ifname, ips, &num);
		if (!ret) {
			if (m->num_ipaddrs > 0) {
				free(m->ipaddrs);
				m->num_ipaddrs = 0;
			}

			if (num > 0) {
				m->ipaddrs = calloc(num, sizeof(struct ip_address));
				if (m->ipaddrs) {
					m->num_ipaddrs = num;
					memcpy(m->ipaddrs, ips, num * sizeof(struct ip_address));
				} else {
					i1905_err(LOG_DM, "%s: -ENOMEM!\n", __func__);
				}
			}
		}

		if (if_isbridge(m->ifname))
			i1905_update_neigh_brport(p, m->ifname);
	}

	return 0;
}

int i1905_dm_refresh_self(struct i1905_private *p)
{
	size_t lsz = 16 * p->dm.self.num_interface + p->dm.self.num_interface; /* , separated */
	struct i1905_interface *iface = NULL, *tmp;
	struct i1905_selfdevice_interface *ifl = NULL;
	char local_ifnames[lsz];
	int ret;


	memset(local_ifnames, 0, lsz);
	i1905_print_interfaces(p);
	list_for_each_entry_safe(iface, tmp, &p->dm.self.iflist, list) {
		int ifindex;

		ifindex = if_nametoindex(iface->ifname);
		if (!ifindex) {
			i1905_dbg(LOG_DM, "%s: %s vanished! Removing object...\n", __func__, iface->ifname);
			ret = i1905_remove_interface_object(p, iface->ifname);

			i1905_teardown_interface(p, iface->ifname);
			i1905_dbg(LOG_DM, "%s: %s\n", __func__, !ret ? "SUCCESS" : "FAILED");
			continue;
		}

		i1905_dm_update_interface_self(p, iface);
		snprintf(local_ifnames + strlen(local_ifnames),
			 lsz - strlen(local_ifnames), ",%s", iface->ifname);
	}


	list_for_each_entry(ifl, &p->dm.self.local_iflist, list) {
		enum if_mediatype mtype;

		if (ifl->exclude || ifl->is_bridge || hwaddr_is_zero(ifl->macaddr) ||
		    strstr_exact(local_ifnames, ifl->ifname))
			continue;

		if_getflags(ifl->ifname, &ifl->ifstatus);
		if_getmediatype(ifl->ifname, &mtype);
		if (mtype == IF_MEDIA_WIFI) {
			enum i1905_mediatype mediatype = I1905_MEDIA_UNKNOWN;
			uint8_t *mediainfo = NULL;
			int num_mediainfo = 0;

			platform_wifi_get_mediainfo(ifl->ifname,
						    &mediatype,
						    &num_mediainfo,
						    (void **)&mediainfo);
			if (num_mediainfo > 0) {
				ifl->mediatype = mediatype;
				memset(ifl->mediainfo, 0, sizeof(ifl->mediainfo));
				memcpy(ifl->mediainfo, mediainfo, sizeof(struct i1905_wifi_mediainfo));
				free(mediainfo);
			}
		} else {
			ifl->mediatype = eth_get_mediatype(ifl->ifname);
			memset(ifl->mediainfo, 0, sizeof(ifl->mediainfo));
		}
	}

	i1905_dm_update_master_interfaces(p);

	return 0;
}

