/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * i1905_wifi.c - WiFi HAL API wrapper implementation for Easy-soc-libs libwifi.so
 *
 * 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 <limits.h>

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

#include <easy/easy.h>
#include <wifi.h>

#include "debug.h"
#include "util.h"
#include "timer.h"
#include "i1905_dm.h"
#include "i1905_wifi.h"

#include "1905_tlvs.h"


uint8_t wifi_rssi_to_rcpi(int rssi)
{
	if (!rssi)
		return 255;

	if (rssi < -110)
		return 0;

	if (rssi > 0)
		return 220;

	return (rssi + 110) * 2;
}

uint8_t wifi_band_to_mhz(enum wifi_band band)
{
	switch (band) {
	case BAND_2:
		return 2;
	case BAND_5:
		return 5;
	case BAND_6:
		return 6;
	case BAND_60:
		return 60;
	default:
		break;
	}

	return 0;
}

int is_wifi_interface(const char *ifname)
{
	char parent[16] = {0};
	char path[512] = {0};
	char rpath[PATH_MAX] = {0};
	struct stat s;

	memset(&s, 0, sizeof(struct stat));
	snprintf(path, 512, "/sys/class/net/%s/phy80211", ifname);
	if (!realpath(path, rpath))
		return 0;

	if (lstat(rpath, &s) != -1) {
		if (S_ISDIR(s.st_mode)) {
			return 1;
		}
	}

	/* WDS interface also has WiFi mediatype */
	if (platform_wifi_get_4addr_parent(ifname, parent) == 0 &&
	    strlen(parent)) {
		return 1;
	}

	return 0;
}

int platform_wifi_get_mediainfo(const char *ifname, enum i1905_mediatype *media,
				int *num_mediainfo, void **mediainfo)
{
	enum i1905_mediatype std = I1905_MEDIA_UNKNOWN;
	uint32_t role = IEEE80211_ROLE_UNKNOWN;
	struct i1905_wifi_mediainfo *winfo;
	struct wifi_mlo_link link[4] = {0};
	int num = ARRAY_SIZE(link);
	uint32_t bandwidth;
	uint32_t band = 0;
	uint32_t seg0_idx;
	uint32_t seg1_idx;
	uint32_t channel;
	int ret;

	*num_mediainfo = 0;
	*mediainfo = NULL;

	ret = wifi_get_mlo_links(ifname, BAND_ANY, link, &num);
	if (ret)
		num = 0;

	if (num > 0) {
		winfo = calloc(num, sizeof(*winfo));
		if (!winfo)
			return -1;

		for (int i = 0; i < num; i++) {
#ifdef WIFI_EASYMESH
			winfo[i].media = I1905_802_11BE;
#endif
			memcpy(winfo[i].info.bssid, link[i].macaddr, 6);

			if (link[i].mode == WIFI_MODE_AP)
				winfo[i].info.role = IEEE80211_ROLE_AP;
			else if (link[i].mode == WIFI_MODE_STA)
				winfo[i].info.role = IEEE80211_ROLE_STA;

			winfo[i].info.ap_bandwidth = wifi_bw_to_enum_bw(link[i].bandwidth);
			winfo[i].info.ap_channel_seg0_idx = link[i].ccfs0;
			winfo[i].info.ap_channel_seg1_idx = link[i].ccfs1;
			winfo[i].band = wifi_band_to_mhz(link[i].band);
		}

		*media = winfo[0].media;
		*num_mediainfo = num;
		*mediainfo = winfo;
		return 0;
	}

	/* pre-ML legacy case */
	winfo = calloc(1, sizeof(*winfo));
	if (!winfo)
		return -1;

	/* get role */
	ret = platform_wifi_get_role(ifname, &role);
	if (!ret)
		winfo->info.role = role;

	/* get bssid */
	ret = platform_wifi_get_bssid(ifname, winfo->info.bssid);
	if (!ret)
		i1905_dbg(LOG_DM, "%s: bssid = "MACFMT"\n", ifname, MAC2STR(winfo->info.bssid));
	else
		i1905_warn(LOG_DM, "error platform_wifi_get_bssid()\n");

	/* get operating band */
	ret = platform_wifi_get_freqband(ifname, &band);
	if (!ret)
		winfo->band = band;

	/* get ap channel and bandwidth */
	if (role == IEEE80211_ROLE_AP ||
	    (role == IEEE80211_ROLE_STA && !hwaddr_is_zero(winfo->info.bssid))) {
		ret = platform_wifi_get_channel(ifname, &channel,
						&bandwidth,
						&seg0_idx,
						&seg1_idx);
		if (!ret) {
			winfo->info.ap_channel_seg0_idx = seg0_idx;
			winfo->info.ap_channel_seg1_idx = seg1_idx;
			winfo->info.ap_bandwidth = bandwidth;

			if (band == 0) {
				if (channel > 0 && channel <= 14)
					band = 2;
				else if (channel >= 36 && channel < 200)
					band = 5;
			}
		}
	}

	/* get standard */
	if (role == IEEE80211_ROLE_AP) {
		ret = platform_wifi_get_standard(ifname, &std);
	} else if (role == IEEE80211_ROLE_STA) {
		if (hwaddr_is_zero(winfo->info.bssid))
			ret = platform_wifi_get_supp_standard(ifname, &std);
		else
			ret = platform_wifi_get_standard(ifname, &std);
	}

	winfo->media = std;

	/* if cannot determine media, assume based on band */
	if (winfo->media == I1905_MEDIA_UNKNOWN)
		winfo->media = band == 2 ? I1905_802_11G_2_4_GHZ : I1905_802_11AC_5_GHZ;

	*media = winfo->media;
	*num_mediainfo = 1;
	*mediainfo = winfo;

	return 0;
}

int platform_wifi_get_assoc_sta_metric(const char *ifname, uint8_t *sta_macaddr,
				       struct i1905_metric *metric)
{
	struct wifi_sta sta = {0};
	int ret = -1;


	ret = wifi_get_sta_info(ifname, sta_macaddr, &sta);
#ifdef WIFI_EASYMESH
	if (ret) {
		char parent[16] = {0};

		if (!wifi_get_4addr_parent(ifname, parent))
			ret = wifi_get_sta_info(parent, sta_macaddr, &sta);
	}
#endif
	if (!ret) {
		metric->tx_errors = sta.stats.tx_err_pkts;
		metric->rx_errors = sta.stats.rx_fail_pkts;
		metric->tx_packets = sta.stats.tx_pkts;
		metric->rx_packets = sta.stats.rx_pkts;
		metric->available = 100;
		metric->max_rate = sta.maxrate;
		metric->max_phyrate = sta.rate.rate;
		metric->rssi = sta.rssi_avg;
	}

	return ret;
}

int platform_wifi_get_interface_metric(const char *ifname, struct i1905_metric *metric)
{
	struct wifi_sta sta = {0};
	int ret = -1;


	ret = wifi_sta_info(ifname, &sta);
	if (!ret) {
		metric->tx_errors = sta.stats.tx_err_pkts;
		metric->rx_errors = sta.stats.rx_fail_pkts;
		metric->tx_packets = sta.stats.tx_pkts;
		metric->rx_packets = sta.stats.rx_pkts;
		metric->available = 100;
		metric->max_rate = sta.maxrate;
		metric->max_phyrate = sta.rate.rate;
		metric->rssi = sta.rssi_avg;
	}

	return ret;
}

int platform_wifi_get_channel(const char *ifname, uint32_t *channel,
			      uint32_t *bandwidth, uint32_t *channel_seg0_idx,
			      uint32_t *channel_seg1_idx)
{
	struct wifi_channel ch = {0};
	int ret;

	*channel = 0;
	*channel_seg0_idx = 0;
	*channel_seg1_idx = 0;
	ret = wifi_interface_get_channel(ifname, &ch);
	if (!ret) {
		*channel = ch.channel;
		*bandwidth = ch.bandwidth;
		*channel_seg0_idx = ch.ccfs0;
		*channel_seg1_idx = ch.ccfs1;

		i1905_dbg(LOG_DM, "%s: channel = %u, bandwidth = %u, ccfs0 = %u, ccfs1 = %u\n",
			  ifname, *channel, *bandwidth, *channel_seg0_idx, *channel_seg1_idx);
	}

	return ret;
}

int platform_wifi_get_freqband(const char *ifname, uint32_t *band)
{
	struct wifi_channel ch = {0};
	enum wifi_band b;
	int ret;

	*band = 0;
	ret = wifi_interface_get_channel(ifname, &ch);
	if (ret)
		return -1;

	b = wifi_freq_to_band(ch.freq);
	if (b == BAND_UNKNOWN)
		return -1;

	info("%s: Oper-band: 0x%x\n", ifname, b);
	switch (b) {
	case BAND_2:
		*band = 2;
		break;
	case BAND_5:
		*band = 5;
		break;
	case BAND_60:
		*band = 60;
		break;
	case BAND_6:
		*band = 6;
		break;
	default:
		ret = -1;
		break;
	}

	return ret;
}

int platform_wifi_get_standard(const char *ifname, enum i1905_mediatype *std)
{
	struct wifi_channel ch = {0};
	enum wifi_band b;
	uint8_t s = 0;
	int ret;

	ret = wifi_interface_get_channel(ifname, &ch);
	if (ret)
		return -1;

	b = wifi_freq_to_band(ch.freq);
	if (b == BAND_UNKNOWN)
		return -1;

	ret = wifi_get_oper_stds(ifname, &s);
	if (ret) {
		/* if standard detection failed, try parent interface (for WDS/4addr) */
		char parent[16] = {0};

		if (!platform_wifi_get_4addr_parent(ifname, parent)) {
			ret = wifi_get_oper_stds(parent, &s);
			if (ret)
				return -1;
		} else {
			return -1;
		}
	}

	i1905_dbg(LOG_DM, "%s: Oper-Standards: 0x%02x\n", ifname, s);

#ifdef WIFI_EASYMESH
	/* report the highest supported standard from current oper stds */
	if (!!(s & WIFI_BE)) {
		i1905_dbg(LOG_DM, "%s: 802.11be\n", ifname);
		*std = I1905_802_11BE;
	} else if (!!(s & WIFI_AX)) {
		i1905_dbg(LOG_DM, "%s: 802.11ax\n", ifname);
		*std = I1905_802_11AX;
	} else
#endif
	if (!!(s & WIFI_AC)) {
		*std = I1905_802_11AC_5_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11ac\n", ifname);
	} else if (!!(s & WIFI_N)) {
		*std = b == BAND_2 ? I1905_802_11N_2_4_GHZ : I1905_802_11N_5_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11n\n", ifname);
	} else if (!!(s & WIFI_A)) {
		*std = I1905_802_11A_5_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11a\n", ifname);
	} else if (!!(s & WIFI_G)) {
		*std = I1905_802_11G_2_4_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11g\n", ifname);
	} else {
		*std = I1905_802_11B_2_4_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11b\n", ifname);
	}

	return 0;
}

int platform_wifi_get_supp_standard(const char *ifname, enum i1905_mediatype *std)
{
	struct wifi_channel ch = {0};
	enum wifi_band b;
	uint8_t s = 0;
	int ret;

	ret = wifi_interface_get_channel(ifname, &ch);
	if (ret)
		return -1;

	b = wifi_freq_to_band(ch.freq);
	if (b == BAND_UNKNOWN)
		return -1;

	ret = wifi_get_supp_stds(ifname, &s);
	if (ret)
		return -1;

	i1905_dbg(LOG_DM, "%s: Supp-Standards: 0x%02x\n", ifname, s);

#ifdef WIFI_EASYMESH
	/* report highest supported standard */
	if (!!(s & WIFI_BE)) {
		i1905_dbg(LOG_DM, "%s: 802.11be\n", ifname);
		*std = I1905_802_11BE;
	} else if (!!(s & WIFI_AX)) {
		i1905_dbg(LOG_DM, "%s: 802.11ax\n", ifname);
		*std = I1905_802_11AX;
	} else
#endif
	if (!!(s & WIFI_AC)) {
		*std = I1905_802_11AC_5_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11ac\n", ifname);
	} else if (!!(s & WIFI_N)) {
		*std = b == BAND_2 ? I1905_802_11N_2_4_GHZ : I1905_802_11N_5_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11n\n", ifname);
	} else if (!!(s & WIFI_A)) {
		*std = I1905_802_11A_5_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11a\n", ifname);
	} else if (!!(s & WIFI_G)) {
		*std = I1905_802_11G_2_4_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11g\n", ifname);
	} else {
		*std = I1905_802_11B_2_4_GHZ;
		i1905_dbg(LOG_DM, "%s: 802.11b\n", ifname);
	}

	return 0;
}

int platform_wifi_get_role(const char *ifname, uint32_t *role)
{
	enum wifi_mode mode;
	int ret;


	ret = wifi_get_mode(ifname, &mode);
	if (!ret) {
		if (mode == WIFI_MODE_AP || mode == WIFI_MODE_AP_VLAN)
			*role = IEEE80211_ROLE_AP;
		else if (mode == WIFI_MODE_STA)
			*role = IEEE80211_ROLE_STA;
		else
			*role = IEEE80211_ROLE_UNKNOWN;
	}

	return ret;
}

int platform_wifi_get_assoclist(const char *ifname, uint8_t *sta_macaddrs, int *num)
{
	return wifi_get_assoclist(ifname, sta_macaddrs, num);
}

int platform_wifi_get_bssid(const char *ifname, uint8_t *bssid)
{
	return wifi_get_bssid(ifname, bssid);
}

int platform_wifi_get_wps_status(const char *ifname, enum I1905_WPS_STATUS *status)
{
	enum wps_status wpsstatus = WPS_STATUS_IDLE;

	return wifi_get_wps_status(ifname, &wpsstatus);
}

int platform_wifi_get_4addr_parent(const char *ifname, char *parent)
{
	return wifi_get_4addr_parent(ifname, parent);
}
