/* SPDX-License-Identifier: GPL-2.0-only */
/*
 * wifimngr.c - provides "wifi" object for management and control.
 *
 * Copyright (C) 2019-2024 Iopsys Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 * Author: Anjan Chanda <anjan.chanda@genexis.eu>
 */

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <limits.h>
#include <net/if.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dlfcn.h>
#include <time.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 <libubus.h>

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

#include "wifimngr.h"
#include "version.h"
#include "help.h"
#include "debug.h"
#include "policy.c"

#if defined(WIFIMNGR_BUILD_TR181_PLUGIN) && defined(HAS_UBUS)
#include "tr181/bbfdm_plugin.h"
#endif

char *helpbuf = NULL;
size_t helpbuflen;

#ifndef BIT
#define BIT(x)	(1 << (x))
#endif


static int signal_pending;

static void wifimngr_sighandler(int sig)
{
	signal_pending = sig;
}

void *libsta_ratings;
float (*sta_ratings_calc)(void *libctx, const char *ap_ifname, struct wifi_sta *sta);
void (*sta_ratings_free_lib)(void *libctx);

/* if IF_OPER_* not defined */
#ifndef IF_OPER_UNKNOWN
enum {
	IF_OPER_UNKNOWN,
	IF_OPER_NOTPRESENT,
	IF_OPER_DOWN,
	IF_OPER_LOWERLAYERDOWN,
	IF_OPER_TESTING,
	IF_OPER_DORMANT,
	IF_OPER_UP,
};
#endif /* IF_OPER_UNKNOWN */

const char *standard_str[] = {
	"b",
	"g",
	"a",
	"n",
	"ac",
	"ax",
	"be",
	"unknown"
};

const char *guard_interval_str[] = {
	"400ns",
	"800ns",
	"1xLTF_800ns",
	"1xLTF_1600ns",
	"2xLTF_800ns",
	"2xLTF_1600ns",
	"4xLTF_800ns",
	"4xLTF_3200ns",
	"Auto"
};

#if 0	// TODO: new
const char *auth_str[] = {
	"none",
	"wep",
	"psk",
	"psk2",
	"wpa",
	"wpa2",
	"unknown"
};

const char *cipher_str[] = {
	"cipher_grp",
	"wep40",
	"tkip",
	"",
	"ccmp",
	"wep104",
	"cmac",
	"cipher_nogrp",
	"gcmp",
	"gcmp256",
	"ccmp256",
	"gmac",
	"gmac256",
	"cmac256",
	"unknown"
};
#else
/* deprecate */
const char *auth_str[] = {"OPEN", "SHARED", "WEPAUTO", "WPANONE", "WPA1", \
			"WPAPSK", "WPA2", "WPA2PSK", "Unknown"};

const char *cipher_str[] = {"NONE", "WEP", "TKIP", "AES", "CCMP256", \
			"GCMP128", "GCMP256", "Unknown"};
#endif

const char *wifi_secstr[] = {
	"NONE",
	"WEP64",
	"WEP128",
	"WPAPSK",
	"WPA2PSK",
	"WPA2PSK+FT",
	"WPA3PSK",
	"WPA2PSK+WPA3PSK",
	"WPA3PSK+FT",
	"WPA",
	"WPA2",
	"WPA3",
};

const char *wifi_cipherstr[] = {
	"CIPHER_GRP",
	"WEP40",
	"TKIP",
	"",
	"CCMP",
	"WEP104",
	"CMAC",
	"CIPHER_NOGRP",
	"GCMP",
	"GCMP256",
	"CCMP256",
	"GMAC",
	"GMAC256",
	"CMAC256",
};


const char *akm_str[] = {
	"none",
	"wpa",
	"psk",
	"ft-wpa",
	"ft-psk",
	"wpa-sha256",
	"psk-sha256",
	"tdls",
	"sae",
	"ft-sae",
	"ap-peerkey",
	"suiteB",
	"suiteB-gmac",
	"suiteB-gmac256",
	"suiteB-cmac256",
	"unknown",
};


const char *operstate_str[] = {
	"up",
	"down",
	"unknown",
	"dormant",
	"notpresent",
	"lowerdown",
	"error",
};

uint32_t bw_value(enum wifi_bw bw)
{
	switch (bw) {
	case BW20: return 20;
	case BW40: return 40;
	case BW80: return 80;
	case BW160: return 160;
	case BW320: return 320;
	case BW8080: return 8080;
	case BW_AUTO:
	case BW_UNKNOWN:
	default:
		return 0;
	}
}

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

	return BAND_UNKNOWN;
}

static const char *radio_band_to_band(enum wifi_band band)
{
	switch (band) {
	case BAND_2:
		return "2g";
	case BAND_5:
		return "5g";
	case BAND_6:
		return "6g";
	case BAND_ANY:
		return "any";
	default:
		break;
	}

	return "unknown";
}

const char *radio_band_to_band_ghz(enum wifi_band band)
{
	switch (band) {
	case BAND_2:
		return "2.4GHz";
	case BAND_5:
		return "5GHz";
	case BAND_6:
		return "6GHz";
	case BAND_60:
		return "60GHz";
	default:
		break;
	}

	return "unknown";
}

static int wifimngr_extract_help(void)
{
	size_t helpstrlen = strlen(helpstr);
	size_t hlen = helpstrlen * 3 / 4 + 2;
	uint8_t *buf;
	int ret = 0;

	buf = calloc(1, hlen);
	if (!buf)
		return -1;

	ret = base64_decode((const uint8_t *)helpstr, helpstrlen, buf, &hlen);
	if (ret) {
		wifimngr_dbg("failed to extract help\n");
		free(buf);
		return -1;
	}

	helpbuf = (char *)buf;
	helpbuflen = hlen;
	return 0;
}

static void wifimngr_free_help()
{
	free(helpbuf);
	helpbuf = NULL;
	helpbuflen = 0;
}

int wl_help_command(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg, const char *command)
{
	const struct blobmsg_policy attr[1] = {
		[0] = { .name = command, .type = BLOBMSG_TYPE_ARRAY },
	};
	struct blob_buf bb = {0}, bo = {0};
	struct blob_attr *tb[1];
	int ret = 0;

	blob_buf_init(&bb, 0);
	blob_buf_init(&bo, 0);

	if (!helpbuf || !blobmsg_add_json_from_string(&bb, helpbuf)) {
		wifimngr_dbg("failed to load help from string\n");
		goto err;
	}

	ret = blobmsg_parse(attr, 1, tb, blobmsg_data(bb.head), blobmsg_len(bb.head));
	if (!ret && tb[0]) {
		blobmsg_add_blob(&bo, tb[0]);
		ubus_send_reply(ctx, req, bo.head);
	}
err:
	blob_buf_free(&bb);
	blob_buf_free(&bo);
	return ret;
}

static const char *ifstatus_str(ifstatus_t f)
{
	const char *s;

	if (f & IFF_UP) {
		s = (f & IFF_RUNNING) ? "running" : "up";
	} else {
		s = "down";
	}

	return s;
}

static char *wifi_security_str(uint32_t sec, char *out, size_t size)
{
	char x[128] = {0};
	bool sep = false;
	int i, len;


	len = sizeof(wifi_secstr)/sizeof(wifi_secstr[0]);

	for (i = 0; i < len; i++) {
		if (!!(sec & (1 << i)) && strlen(wifi_secstr[i])) {
			snprintf(x + strlen(x), 127, "%s%s", sep ? "/" : "",
						wifi_secstr[i]);
			sep = true;
		}
	}

	strncpy(out, x, size);
	out[size - 1] = '\0';

	return out;
}

static char *etostr(uint32_t e, char *out, size_t sizeof_out, int elen, const char **arr)
{
	char *c = "";
	int i;

	for (i = 0; i < elen; i++) {
		if (e & (1 << i)) {
			snprintf(out + strlen(out), sizeof_out - 1, "%s%s", c, arr[i]);
			c = "/";
		}
	}

	return out;
}

struct wifimngr_device *wifimngr_ifname_to_device(struct wifimngr *w, const char *ifname)
{
	const char *device = NULL;
	int i;

	for (i = 0; i < w->num_wifi_iface; i++) {
		if (!strncmp(w->ifs[i].iface, ifname, strlen(ifname))) {
			device = w->ifs[i].device;
			break;
		}
	}

	if (i == w->num_wifi_iface)
		return NULL;

	for (i = 0; i < w->num_wifi_device; i++) {
		if (!strncmp(w->wdev[i].device, device, strlen(device)))
			return &w->wdev[i];
	}

	return NULL;
}

const char *ubus_objname_to_ifname(struct ubus_object *obj)
{
	if (strstr(obj->name, WIFI_RADIO_OBJECT_PREFIX))
		return obj->name + strlen(WIFI_RADIO_OBJECT_PREFIX);

	if (strstr(obj->name, WIFI_APMLD_OBJECT_PREFIX))
		return obj->name + strlen(WIFI_APMLD_OBJECT_PREFIX);

	if (strstr(obj->name, WIFI_BSTAMLD_OBJECT_PREFIX))
		return obj->name + strlen(WIFI_BSTAMLD_OBJECT_PREFIX);

	if (strstr(obj->name, WIFI_AP_OBJECT_PREFIX))
		return obj->name + strlen(WIFI_AP_OBJECT_PREFIX);

	if (strstr(obj->name, WIFI_BSTA_OBJECT_PREFIX))
		return obj->name + strlen(WIFI_BSTA_OBJECT_PREFIX);

	return "\0";
}

#define ubus_ap_to_ifname(o)	ubus_objname_to_ifname(o)
#define ubus_sta_to_ifname(o)	ubus_objname_to_ifname(o)

static int ieee80211_readint(const char *path)
{
	int fd;
	int rv = -1;
	char buffer[16];

	if ((fd = open(path, O_RDONLY)) > -1)
	{
		if (read(fd, buffer, sizeof(buffer)) > 0)
			rv = atoi(buffer);

		close(fd);
	}

	return rv;
}

static const char *ieee80211_phy_path_str(const char *phyname)
{
	static char path[PATH_MAX];
	const char *prefix = "/sys/devices/";
	int prefix_len = strlen(prefix);
	int buf_len, offset;
	struct dirent *e;
	char buf[512], *link;
	int phy_idx;
	int seq = 0;
	DIR *d;

	snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", phyname);
	phy_idx = ieee80211_readint(buf);
	if (phy_idx < 0)
		return NULL;

	buf_len = snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/device", phyname);
	link = realpath(buf, path);
	if (!link)
		return NULL;

	if (strncmp(link, prefix, prefix_len) != 0)
		return NULL;

	link += prefix_len;

	prefix = "platform/";
	prefix_len = strlen(prefix);
	if (!strncmp(link, prefix, prefix_len) && strstr(link, "/pci"))
		link += prefix_len;

	snprintf(buf + buf_len, sizeof(buf) - buf_len, "/ieee80211");
	d = opendir(buf);
	if (!d)
		return link;

	while ((e = readdir(d)) != NULL) {
		int cur_idx;

		snprintf(buf, sizeof(buf), "/sys/class/ieee80211/%s/index", e->d_name);
		cur_idx = ieee80211_readint(buf);
		if (cur_idx < 0)
			continue;

		if (cur_idx >= phy_idx)
			continue;

		seq++;
	}

	closedir(d);

	if (!seq)
		return link;

	offset = link - path + strlen(link);
	snprintf(path + offset, sizeof(path) - offset, "+%d", seq);

	return link;
}

int find_phy_from_device_path(char *path, char *phy, size_t size)
{
	bool found = false;
	struct dirent *p;
	DIR *d;


	d = opendir("/sys/class/ieee80211");
	if (!d)
		return -1;

	while ((p = readdir(d))) {
		const char *phypath;

		if (!strcmp(p->d_name, "."))
			continue;

		if (!strcmp(p->d_name, ".."))
			continue;

		phypath = ieee80211_phy_path_str(p->d_name);
		if (!phypath)
			continue;

		wifimngr_dbg("%s called |%s| vs |%s|\n", __func__, phypath, path);
		if (!strcmp(phypath, path)) {
			strncpy(phy, p->d_name, size);
			found = true;
		}

		 if (found)
			 break;
	}

	closedir(d);
	return found ? 0 : -1;
}

bool phy_dir_exist(char *phy)
{
	bool found = false;
	struct dirent *p;
	DIR *d;

	d = opendir("/sys/class/ieee80211");
	if (!d)
		return found;

	while ((p = readdir(d))) {
		if (!strcmp(p->d_name, "."))
			continue;

		if (!strcmp(p->d_name, ".."))
			continue;

		if (!strcmp(phy, p->d_name)) {
			found = true;
			break;
		}
	}

	closedir(d);
	return found;
}

int wifimngr_get_wifi_devices(struct wifimngr *w, const char *conffile)
{
	memset(w->wdev, 0, WIFI_DEV_MAX_NUM * sizeof(struct wifimngr_device));
	w->num_wifi_device = 0;

	return uci_get_wifi_devices(w, conffile);
}

int wifimngr_get_wifi_interfaces(struct wifimngr *w, const char *conffile)
{
	memset(w->ifs, 0, WIFI_IF_MAX_NUM * sizeof(struct wifimngr_iface));
	w->num_wifi_iface = 0;

	return uci_get_wifi_interfaces(w, conffile);
}

int wifimngr_get_wifi_mlds(struct wifimngr *w, const char *conffile)
{
	memset(w->mld, 0, sizeof(struct wifimngr_mld) * WIFI_MLD_MAX_NUM);
	w->num_wifi_mld = 0;

	return uci_get_wifi_mlds(w, conffile);
}

static void wl_dump_capabilities(enum wifi_band band, struct blob_buf *bb,
				 struct wifi_caps *caps, uint8_t *bitmap,
				 int bitmap_len)
{
	char bitmap_str[128] = {0};
	void *f;
	int i;

	f = blobmsg_open_table(bb, "capabilities");

	if ((sizeof(bitmap_str) - 1) >= (2 * bitmap_len)) {
		btostr(bitmap, bitmap_len, bitmap_str);
		blobmsg_add_string(bb, "bitmap", bitmap_str);
	}

	blobmsg_add_u8(bb, "wmm", wifi_cap_isset(bitmap, WIFI_CAP_WMM) ? true : false);
	blobmsg_add_u8(bb, "apsd", wifi_cap_isset(bitmap, WIFI_CAP_APSD) ? true : false);
	blobmsg_add_u8(bb, "shortslot", wifi_cap_isset(bitmap, WIFI_CAP_SHORT_SLOT) ? true : false);
	blobmsg_add_u8(bb, "dot11h", wifi_cap_isset(bitmap, WIFI_CAP_SPECTRUM_MGMT) ? true : false);
	blobmsg_add_u8(bb, "mu_edca", wifi_cap_isset(bitmap, WIFI_CAP_MU_EDCA) ? true : false);

	if (!!(caps->valid & WIFI_CAP_EXT_VALID)) {
		blobmsg_add_u8(bb, "2040coex", wifi_cap_isset(bitmap, WIFI_CAP_2040_COEX) ? true : false);
		blobmsg_add_u8(bb, "psmp", wifi_cap_isset(bitmap, WIFI_CAP_PSMP) ? true : false);
		blobmsg_add_u8(bb, "proxy_arp", wifi_cap_isset(bitmap, WIFI_CAP_PROXY_ARP) ? true : false);
		blobmsg_add_u8(bb, "dot11v_btm", wifi_cap_isset(bitmap, WIFI_CAP_11V_BSS_TRANS) ? true : false);
		blobmsg_add_u8(bb, "multi_bssid", wifi_cap_isset(bitmap, WIFI_CAP_MULTI_BSSID) ? true : false);
		blobmsg_add_u8(bb, "ssidlist", wifi_cap_isset(bitmap, WIFI_CAP_SSID_LIST) ? true : false);
		blobmsg_add_u8(bb, "interworking", wifi_cap_isset(bitmap, WIFI_CAP_INTERWORKING) ? true : false);
		blobmsg_add_u8(bb, "qosmap", wifi_cap_isset(bitmap, WIFI_CAP_QOSMAP) ? true : false);
		blobmsg_add_u8(bb, "tdls", wifi_cap_isset(bitmap, WIFI_CAP_TDLS) ? true : false);
		blobmsg_add_u8(bb, "qos_scs", wifi_cap_isset(bitmap, WIFI_CAP_SCS) ? true : false);
		blobmsg_add_u8(bb, "qload_report", wifi_cap_isset(bitmap, WIFI_CAP_QLOAD_REPORT) ? true : false);
		blobmsg_add_u8(bb, "omi", wifi_cap_isset(bitmap, WIFI_CAP_OMI) ? true : false);
		blobmsg_add_u8(bb, "qos_mscs", wifi_cap_isset(bitmap, WIFI_CAP_MSCS) ? true : false);
		blobmsg_add_u8(bb, "twt_requester", wifi_cap_isset(bitmap, WIFI_CAP_TWT_REQ) ? true : false);
		blobmsg_add_u8(bb, "twt_responder", wifi_cap_isset(bitmap, WIFI_CAP_TWT_RSP) ? true : false);
	}

	if (!!(caps->valid & WIFI_CAP_HT_VALID)) {
		uint8_t *supp_mcs = caps->ht.supp_mcs;
		int max_mcs = -1;
		int octet = 0;
		void *n;
		int l;

		n = blobmsg_open_table(bb, "dot11n");
		blobmsg_add_u8(bb, "dot11n_ldpc", wifi_cap_isset(bitmap, WIFI_CAP_HT_LDPC) ? true : false);
		blobmsg_add_u8(bb, "dot11n_40", wifi_cap_isset(bitmap, WIFI_CAP_2040) ? true : false);
		blobmsg_add_u8(bb, "dot11n_ps", wifi_cap_isset(bitmap, WIFI_CAP_HT_SMPS) ? true : false);
		blobmsg_add_u8(bb, "dot11n_sgi20", wifi_cap_isset(bitmap, WIFI_CAP_SGI20) ? true : false);
		blobmsg_add_u8(bb, "dot11n_sgi40", wifi_cap_isset(bitmap, WIFI_CAP_SGI40) ? true : false);
		blobmsg_add_u8(bb, "dot11n_tx_stbc", wifi_cap_isset(bitmap, WIFI_CAP_HT_TX_STBC) ? true : false);
		blobmsg_add_u8(bb, "dot11n_rx_stbc", wifi_cap_isset(bitmap, WIFI_CAP_HT_RX_STBC) ? true : false);

		for (l = 0; l < 77; l++) {
			if (l && !(l % 8))
				octet++;

			if (!!(supp_mcs[octet] & (1 << (l % 8))))
				max_mcs++;
		}

		blobmsg_add_u32(bb, "dot11n_supp_max_mcs", max_mcs);
		blobmsg_close_table(bb, n);
	}/* else {
		blobmsg_add_u8(bb, "dot11n", false);
	}*/

	if (!!(caps->valid & WIFI_CAP_VHT_VALID)) {
		void *ac;

		ac = blobmsg_open_table(bb, "dot11ac");
		blobmsg_add_u8(bb, "dot11ac_160", wifi_cap_isset(bitmap, WIFI_CAP_160) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_8080", wifi_cap_isset(bitmap, WIFI_CAP_8080) ? true : false);
		if (wifi_cap_isset(bitmap, WIFI_CAP_VHT_MPDU_11454))
			blobmsg_add_u32(bb, "dot11ac_mpdu_max", 11454);
		else if (wifi_cap_isset(bitmap, WIFI_CAP_VHT_MPDU_7991))
			blobmsg_add_u32(bb, "dot11ac_mpdu_max", 7991);
		else if (wifi_cap_isset(bitmap, WIFI_CAP_VHT_MPDU_3895))
			blobmsg_add_u32(bb, "dot11ac_mpdu_max", 3895);
		blobmsg_add_u8(bb, "dot11ac_sgi80", wifi_cap_isset(bitmap, WIFI_CAP_SGI80) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_sgi160", wifi_cap_isset(bitmap, WIFI_CAP_SGI160) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_rx_ldpc", wifi_cap_isset(bitmap, WIFI_CAP_VHT_RX_LDPC) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_tx_stbc", wifi_cap_isset(bitmap, WIFI_CAP_VHT_TX_STBC) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_rx_stbc_1ss", wifi_cap_isset(bitmap, WIFI_CAP_VHT_RX_STBC_1SS) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_rx_stbc_2ss", wifi_cap_isset(bitmap, WIFI_CAP_VHT_RX_STBC_2SS) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_rx_stbc_3ss", wifi_cap_isset(bitmap, WIFI_CAP_VHT_RX_STBC_3SS) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_rx_stbc_4ss", wifi_cap_isset(bitmap, WIFI_CAP_VHT_RX_STBC_4SS) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_su_beamformer", wifi_cap_isset(bitmap, WIFI_CAP_VHT_SU_BFR) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_su_beamformee", wifi_cap_isset(bitmap, WIFI_CAP_VHT_SU_BFE) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_mu_beamformer", wifi_cap_isset(bitmap, WIFI_CAP_VHT_MU_BFR) ? true : false);
		blobmsg_add_u8(bb, "dot11ac_mu_beamformee", wifi_cap_isset(bitmap, WIFI_CAP_VHT_MU_BFE) ? true : false);

		for (i = 0; i < 2; i++) {
			uint8_t *mcs_map = &caps->vht.supp_mcs[i*4];
			int max_mcs = -1;
			int octet = 0;
			int nss = 0;
			int l;

			for (l = 0; l < 16; l += 2) {
				uint8_t supp_mcs_mask = 0;

				if (l && !(l % 8))
					octet++;

				supp_mcs_mask = mcs_map[octet] & (0x3 << (l % 8));
				supp_mcs_mask >>= (l % 8);
				if (supp_mcs_mask == 3)
					break;

				nss++;
				if (supp_mcs_mask == 0)
					max_mcs = 7;
				else if (supp_mcs_mask == 1)
					max_mcs = 8;
				else if (supp_mcs_mask == 2)
					max_mcs = 9;
			}

			if (i == 0) {
				blobmsg_add_u32(bb, "dot11ac_supp_max_rx_mcs", max_mcs);
				blobmsg_add_u32(bb, "dot11ac_supp_max_rx_nss", nss);
			} else {
				blobmsg_add_u32(bb, "dot11ac_supp_max_tx_mcs", max_mcs);
				blobmsg_add_u32(bb, "dot11ac_supp_max_tx_nss", nss);
			}
		}

		blobmsg_close_table(bb, ac);
	}/* else {
		blobmsg_add_u8(bb, "dot11ac", false);
	}*/

	if (!!(caps->valid & WIFI_CAP_HE_VALID)) {
		uint8_t supp_chwidth = (caps->he.byte_phy[0] & 0xf7) >> 1;
		bool b0 = !!(supp_chwidth & BIT(0));
		//bool b1 = !!(supp_chwidth & BIT(1));
		bool b2 = !!(supp_chwidth & BIT(2));
		bool b3 = !!(supp_chwidth & BIT(3));
		int mcs_maplen = 4;
		uint8_t *mcs_map;
		void *ax;

		ax = blobmsg_open_table(bb, "dot11ax");
		blobmsg_add_u8(bb, "dot11ax_twt_requester", wifi_cap_isset(bitmap, WIFI_CAP_HE_TWT_REQ) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_twt_responder", wifi_cap_isset(bitmap, WIFI_CAP_HE_TWT_RSP) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_all_ack", wifi_cap_isset(bitmap, WIFI_CAP_HE_ALL_ACK) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_trs", wifi_cap_isset(bitmap, WIFI_CAP_HE_TRS) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_bsr", wifi_cap_isset(bitmap, WIFI_CAP_HE_BSR) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_twt_bcast", wifi_cap_isset(bitmap, WIFI_CAP_HE_BCAST_TWT) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_ba_32bit", wifi_cap_isset(bitmap, WIFI_CAP_HE_32BIT_BA_BMP) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_om_control", wifi_cap_isset(bitmap, WIFI_CAP_HE_OM_CONTROL) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_ofdma_ra", wifi_cap_isset(bitmap, WIFI_CAP_HE_OFDMA_RA) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_amsdu_frag", wifi_cap_isset(bitmap, WIFI_CAP_HE_AMSDU_FRAG) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_twt_flexible", wifi_cap_isset(bitmap, WIFI_CAP_HE_FLEX_TWT) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_qtp", wifi_cap_isset(bitmap, WIFI_CAP_HE_QTP) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_bqr", wifi_cap_isset(bitmap, WIFI_CAP_HE_BQR) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_srp_responder", wifi_cap_isset(bitmap, WIFI_CAP_HE_SRP_RSP) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_ops", wifi_cap_isset(bitmap, WIFI_CAP_HE_OPS) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_amsdu_in_ampdu", wifi_cap_isset(bitmap, WIFI_CAP_HE_AMSDU_IN_AMPDU) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_dynamic_smps", wifi_cap_isset(bitmap, WIFI_CAP_HE_DYN_SMPS) ? true : false);

		blobmsg_add_u8(bb, "dot11ax_2g_40", wifi_cap_isset(bitmap, WIFI_CAP_HE_40_BAND2) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_5g_4080", wifi_cap_isset(bitmap, WIFI_CAP_HE_4080_BAND5) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_5g_160", wifi_cap_isset(bitmap, WIFI_CAP_HE_160_BAND5) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_5g_160_and_8080", wifi_cap_isset(bitmap, WIFI_CAP_HE_160_8080_BAND5) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_2g_242ru", wifi_cap_isset(bitmap, WIFI_CAP_HE_242RU_BAND2) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_5g_242ru", wifi_cap_isset(bitmap, WIFI_CAP_HE_242RU_BAND2) ? true : false);

		blobmsg_add_u8(bb, "dot11ax_ldpc_payload", wifi_cap_isset(bitmap, WIFI_CAP_HE_LDPC_PAYLOAD) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_tx_stbc_80", wifi_cap_isset(bitmap, WIFI_CAP_HE_STBC_TX_80) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_rx_stbc_80", wifi_cap_isset(bitmap, WIFI_CAP_HE_STBC_RX_80) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_ul_mumimo_full", wifi_cap_isset(bitmap, WIFI_CAP_HE_FULL_BW_UL_MUMIMO) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_ul_mumimo_partial", wifi_cap_isset(bitmap, WIFI_CAP_HE_PART_BW_UL_MUMIMO) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_su_beamformer", wifi_cap_isset(bitmap, WIFI_CAP_HE_SU_BFR) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_su_beamformee", wifi_cap_isset(bitmap, WIFI_CAP_HE_SU_BFE) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_mu_beamformer", wifi_cap_isset(bitmap, WIFI_CAP_HE_MU_BFR) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_bfe_sts_le_80", wifi_cap_isset(bitmap, WIFI_CAP_HE_BFE_STS_LE_80MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_bfe_sts_gt_80", wifi_cap_isset(bitmap, WIFI_CAP_HE_BFE_STS_GT_80MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_ng16_su_feedback", wifi_cap_isset(bitmap, WIFI_CAP_HE_NG16_SU_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_ng16_mu_feedback", wifi_cap_isset(bitmap, WIFI_CAP_HE_NG16_MU_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_codebook_su_feedback", wifi_cap_isset(bitmap, WIFI_CAP_HE_CODEBOOK_42_SU_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_codebook_mu_feedback", wifi_cap_isset(bitmap, WIFI_CAP_HE_CODEBOOK_75_MU_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_triggered_su_bf_feedback", wifi_cap_isset(bitmap, WIFI_CAP_HE_TRIGGERED_SU_BF_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_triggered_mu_bf_partial_bw_feedback", wifi_cap_isset(bitmap, WIFI_CAP_HE_TRIGGERED_MU_BF_PARTIAL_BW_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_triggered_cqi_feedback", wifi_cap_isset(bitmap, WIFI_CAP_HE_TRIGGERED_CQI_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_partial_bw_er", wifi_cap_isset(bitmap, WIFI_CAP_HE_PARTIAL_BW_ER) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_dl_mumimo_partial", wifi_cap_isset(bitmap, WIFI_CAP_HE_PARTIAL_BW_DL_MUMIMO) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_ppe_thresh_present", wifi_cap_isset(bitmap, WIFI_CAP_HE_PPE_THRESHOLDS_PRESENT) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_psr_sr", wifi_cap_isset(bitmap, WIFI_CAP_HE_PSR_SR) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_doppler_tx", wifi_cap_isset(bitmap, WIFI_CAP_HE_DOPPLER_TX) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_doppler_rx", wifi_cap_isset(bitmap, WIFI_CAP_HE_DOPPLER_RX) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_stbc_tx_gt_80", wifi_cap_isset(bitmap, WIFI_CAP_HE_STBC_TX_GT_80MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_stbc_rx_gt_80", wifi_cap_isset(bitmap, WIFI_CAP_HE_STBC_RX_GT_80MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_rts", wifi_cap_isset(bitmap, WIFI_CAP_HE_RTS_TXOP_BASED) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_mu_rts", wifi_cap_isset(bitmap, WIFI_CAP_MU_RTS) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_multi_bssid", wifi_cap_isset(bitmap, WIFI_CAP_MULTI_BSSID) ? true : false);
		blobmsg_add_u8(bb, "dot11ax_mu_edca", wifi_cap_isset(bitmap, WIFI_CAP_MU_EDCA) ? true : false);


		/* get max MCS and NSS */
		if (band == BAND_5 || band == BAND_6) {
			if (b2)
				mcs_maplen += 4;
			if (b3)
				mcs_maplen += 4;
		}

		for (i = 0; i < mcs_maplen; i += 2) {
			int max_mcs = -1;
			int octet = 0;
			int nss = 0;
			int l;


			mcs_map = &caps->he.byte_opt[i];

			for (l = 0; l < 16; l += 2) {
				uint8_t supp_mcs_mask = 0;

				if (l && !(l % 8))
					octet++;

				supp_mcs_mask = mcs_map[octet] & (0x3 << (l % 8));
				supp_mcs_mask >>= (l % 8);
				if (supp_mcs_mask == 3)
					break;

				nss++;
				if (supp_mcs_mask == 0)
					max_mcs = 7;
				else if (supp_mcs_mask == 1)
					max_mcs = 9;
				else if (supp_mcs_mask == 2)
					max_mcs = 11;
			}

			if (max_mcs < 0)
				continue;

			switch (i) {
			case 0:
				if (band == BAND_2) {
					if (b0) {
						blobmsg_add_u32(bb, "dot11ax_supp_max_rx_mcs_40", max_mcs);
						blobmsg_add_u32(bb, "dot11ax_supp_max_rx_nss_40", nss);
					} else {
						blobmsg_add_u32(bb, "dot11ax_supp_max_rx_mcs_20", max_mcs);
						blobmsg_add_u32(bb, "dot11ax_supp_max_rx_nss_20", nss);
					}
				} else {
					blobmsg_add_u32(bb, "dot11ax_supp_max_rx_mcs_80", max_mcs);
					blobmsg_add_u32(bb, "dot11ax_supp_max_rx_nss_80", nss);
				}
				break;
			case 2:
				if (band == BAND_2) {
					if (b0) {
						blobmsg_add_u32(bb, "dot11ax_supp_max_tx_mcs_40", max_mcs);
						blobmsg_add_u32(bb, "dot11ax_supp_max_tx_nss_40", nss);
					} else {
						blobmsg_add_u32(bb, "dot11ax_supp_max_tx_mcs_20", max_mcs);
						blobmsg_add_u32(bb, "dot11ax_supp_max_tx_nss_20", nss);
					}
				} else {
					blobmsg_add_u32(bb, "dot11ax_supp_max_tx_mcs_80", max_mcs);
					blobmsg_add_u32(bb, "dot11ax_supp_max_tx_nss_80", nss);
				}
				break;
			case 4:
				blobmsg_add_u32(bb, "dot11ax_supp_max_rx_mcs_160", max_mcs);
				blobmsg_add_u32(bb, "dot11ax_supp_max_rx_nss_160", nss);
				break;
			case 6:
				blobmsg_add_u32(bb, "dot11ax_supp_max_tx_mcs_160", max_mcs);
				blobmsg_add_u32(bb, "dot11ax_supp_max_tx_nss_160", nss);
				break;
			case 8:
				blobmsg_add_u32(bb, "dot11ax_supp_max_rx_mcs_8080", max_mcs);
				blobmsg_add_u32(bb, "dot11ax_supp_max_rx_nss_8080", nss);
				break;
			case 10:
				blobmsg_add_u32(bb, "dot11ax_supp_max_tx_mcs_8080", max_mcs);
				blobmsg_add_u32(bb, "dot11ax_supp_max_tx_nss_8080", nss);
				break;
			default:
				break;
			}
		}
		blobmsg_close_table(bb, ax);
	}

	if (!!(caps->valid & WIFI_CAP_EHT_VALID)) {
		void *be;

		be = blobmsg_open_table(bb, "dot11be");
		blobmsg_add_u8(bb, "dot11be_epcs", wifi_cap_isset(bitmap, WIFI_CAP_EHT_EPCS) ? true : false);
		blobmsg_add_u8(bb, "dot11be_om_control", wifi_cap_isset(bitmap, WIFI_CAP_EHT_OM_CONTROL) ? true : false);
		blobmsg_add_u8(bb, "dot11be_triggered_txop_mode1", wifi_cap_isset(bitmap, WIFI_CAP_EHT_TRIGGERED_TXOP_MODE_1) ? true : false);
		blobmsg_add_u8(bb, "dot11be_triggered_txop_mode2", wifi_cap_isset(bitmap, WIFI_CAP_EHT_TRIGGERED_TXOP_MODE_2) ? true : false);
		blobmsg_add_u8(bb, "dot11be_rtwt", wifi_cap_isset(bitmap, WIFI_CAP_EHT_RTWT) ? true : false);
		blobmsg_add_u8(bb, "dot11be_scs_tdesc", wifi_cap_isset(bitmap, WIFI_CAP_EHT_SCS_TDESC) ? true : false);
		if (wifi_cap_isset(bitmap, WIFI_CAP_EHT_MPDU_11454))
			blobmsg_add_u32(bb, "dot11be_mpdu_max", 11454);
		else if (wifi_cap_isset(bitmap, WIFI_CAP_EHT_MPDU_7991))
			blobmsg_add_u32(bb, "dot11be_mpdu_max", 7991);
		else if (wifi_cap_isset(bitmap, WIFI_CAP_EHT_MPDU_3895))
			blobmsg_add_u32(bb, "dot11be_mpdu_max", 3895);

		blobmsg_add_u8(bb, "dot11be_trs", wifi_cap_isset(bitmap, WIFI_CAP_EHT_TRS) ? true : false);
		blobmsg_add_u8(bb, "dot11be_txop_return_mode2", wifi_cap_isset(bitmap, WIFI_CAP_EHT_TXOP_RETURN_IN_MODE_2) ? true : false);
		blobmsg_add_u8(bb, "dot11be_two_bqr", wifi_cap_isset(bitmap, WIFI_CAP_EHT_TWO_BQR) ? true : false);
		blobmsg_add_u8(bb, "dot11be_link_adapt", wifi_cap_isset(bitmap, WIFI_CAP_EHT_LINK_ADAPT) ? true : false);
		blobmsg_add_u8(bb, "dot11be_6g_320", wifi_cap_isset(bitmap, WIFI_CAP_EHT_320_BAND6) ? true : false);
		blobmsg_add_u8(bb, "dot11be_242ru_bw20_plus", wifi_cap_isset(bitmap, WIFI_CAP_EHT_242RU_IN_BW_GT_20MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ndp_4xltf_gi32", wifi_cap_isset(bitmap, WIFI_CAP_EHT_NDP_WITH_4xEHT_LTF_AND_3_2_GI) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ul_mumimo_partial", wifi_cap_isset(bitmap, WIFI_CAP_EHT_PARTIAL_BW_UL_MUMIMO) ? true : false);
		blobmsg_add_u8(bb, "dot11be_dl_mumimo_partial", wifi_cap_isset(bitmap, WIFI_CAP_EHT_PARTIAL_BW_DL_MUMIMO) ? true : false);
		blobmsg_add_u8(bb, "dot11be_su_beamformer", wifi_cap_isset(bitmap, WIFI_CAP_EHT_SU_BFR) ? true : false);
		blobmsg_add_u8(bb, "dot11be_su_beamformee", wifi_cap_isset(bitmap, WIFI_CAP_EHT_SU_BFE) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ng16_su_feedback", wifi_cap_isset(bitmap, WIFI_CAP_EHT_NG16_SU_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ng16_mu_feedback", wifi_cap_isset(bitmap, WIFI_CAP_EHT_NG16_MU_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11be_codebook_su_feedback", wifi_cap_isset(bitmap, WIFI_CAP_EHT_CODEBOOK_42_SU_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11be_codebook_mu_feedback", wifi_cap_isset(bitmap, WIFI_CAP_EHT_CODEBOOK_75_MU_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11be_triggered_cqi_feedback", wifi_cap_isset(bitmap, WIFI_CAP_EHT_TRIGGERED_CQI_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11be_psr_sr", wifi_cap_isset(bitmap, WIFI_CAP_EHT_PSR_SR) ? true : false);
		blobmsg_add_u8(bb, "dot11be_power_boost", wifi_cap_isset(bitmap, WIFI_CAP_EHT_POWER_BOOST_FACTOR) ? true : false);
		blobmsg_add_u8(bb, "dot11be_mu_ppdu_4xltf_gi08", wifi_cap_isset(bitmap, WIFI_CAP_EHT_MU_PPDU_WITH_4xEHT_LTF_AND_0_8_GI) ? true : false);
		blobmsg_add_u8(bb, "dot11be_non_triggered_cqi_feedback", wifi_cap_isset(bitmap, WIFI_CAP_EHT_NON_TRIGGERED_CQI_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11be_tx_1k_4k_qam_lt_242ru", wifi_cap_isset(bitmap, WIFI_CAP_EHT_TX_1K_4K_QAM_WITH_LT_242RU) ? true : false);
		blobmsg_add_u8(bb, "dot11be_rx_1k_4k_qam_lt_242ru", wifi_cap_isset(bitmap, WIFI_CAP_EHT_RX_1K_4K_QAM_WITH_LT_242RU) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ppe_thresh_present", wifi_cap_isset(bitmap, WIFI_CAP_EHT_PPE_THRESHOLDS_PRESENT) ? true : false);
		blobmsg_add_u8(bb, "dot11be_dup_in_6g", wifi_cap_isset(bitmap, WIFI_CAP_EHT_DUP_IN_BAND6) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ul_non_ofdma_80", wifi_cap_isset(bitmap, WIFI_CAP_EHT_NON_OFDMA_UL_MUMIMO_80MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ul_non_ofdma_160", wifi_cap_isset(bitmap, WIFI_CAP_EHT_NON_OFDMA_UL_MUMIMO_160MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ul_non_ofdma_320", wifi_cap_isset(bitmap, WIFI_CAP_EHT_NON_OFDMA_UL_MUMIMO_320MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11be_mu_beamformer_80", wifi_cap_isset(bitmap, WIFI_CAP_EHT_MU_BFR_80MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11be_mu_beamformer_160", wifi_cap_isset(bitmap, WIFI_CAP_EHT_MU_BFR_160MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11be_mu_beamformer_320", wifi_cap_isset(bitmap, WIFI_CAP_EHT_MU_BFR_320MHZ) ? true : false);
		blobmsg_add_u8(bb, "dot11be_ratelimit_tb_sounding", wifi_cap_isset(bitmap, WIFI_CAP_EHT_TB_SOUND_FEEDBACK_RATELIMIT) ? true : false);
		blobmsg_add_u8(bb, "dot11be_rx_1k_qam_dl_ofdma_wider_bw", wifi_cap_isset(bitmap, WIFI_CAP_EHT_RX_1K_QAM_DL_OFDMA_WIDER_BW) ? true : false);
		blobmsg_add_u8(bb, "dot11be_rx_4k_qam_dl_ofdma_wider_bw", wifi_cap_isset(bitmap, WIFI_CAP_EHT_RX_4K_QAM_DL_OFDMA_WIDER_BW) ? true : false);
		blobmsg_add_u8(bb, "dot11be_20_only", wifi_cap_isset(bitmap, WIFI_CAP_EHT_20MHZ_ONLY) ? true : false);
		blobmsg_add_u8(bb, "dot11be_20_only_triggered_mu_bf_full_bw_feedback", wifi_cap_isset(bitmap, WIFI_CAP_EHT_20MHZ_ONLY_TRIGGERED_MU_BF_FULL_BW_FEEDBACK) ? true : false);
		blobmsg_add_u8(bb, "dot11be_20_only_mru", wifi_cap_isset(bitmap, WIFI_CAP_EHT_20MHZ_ONLY_MRU) ? true : false);

		blobmsg_close_table(bb, be);
	}


	if (!!(caps->valid & WIFI_CAP_RM_VALID)) {
		void *k;

		k = blobmsg_open_table(bb, "dot11k");
		blobmsg_add_u8(bb, "dot11k_link_meas", wifi_cap_isset(bitmap, WIFI_CAP_RM_LINK) ? true : false);
		blobmsg_add_u8(bb, "dot11k_nbr_report", wifi_cap_isset(bitmap, WIFI_CAP_RM_NBR_REPORT) ? true : false);
		blobmsg_add_u8(bb, "dot11k_bcn_passive", wifi_cap_isset(bitmap, WIFI_CAP_RM_BCN_PASSIVE) ? true : false);
		blobmsg_add_u8(bb, "dot11k_bcn_active", wifi_cap_isset(bitmap, WIFI_CAP_RM_BCN_ACTIVE) ? true : false);
		blobmsg_add_u8(bb, "dot11k_bcn_table", wifi_cap_isset(bitmap, WIFI_CAP_RM_BCN_TABLE) ? true : false);
		blobmsg_add_u8(bb, "dot11k_rcpi", wifi_cap_isset(bitmap, WIFI_CAP_RM_RCPI) ? true : false);
		blobmsg_add_u8(bb, "dot11k_rsni", wifi_cap_isset(bitmap, WIFI_CAP_RM_RSNI) ? true : false);
		blobmsg_close_table(bb, k);
	} /* else
		blobmsg_add_u8(bb, "dot11k", false); */

	//blobmsg_add_u8(bb, "dot11r", wifi_cap_isset(bitmap, WIFI_CAP_FT_BSS) ? true : false);
	blobmsg_close_table(bb, f);
}

static void wl_print_sta_stats(struct blob_buf *bb, struct wifi_sta_stats *stats)
{
	void *t;

	t = blobmsg_open_table(bb, "stats");
	blobmsg_add_u64(bb, "tx_total_pkts", stats->tx_pkts);
	blobmsg_add_u64(bb, "tx_total_bytes", stats->tx_bytes);
	blobmsg_add_u64(bb, "tx_failures", stats->tx_fail_pkts);
	blobmsg_add_u64(bb, "tx_pkts_retries", stats->tx_retry_pkts);

	blobmsg_add_u64(bb, "tx_ucast_pkts", stats->tx_ucast_pkts);
	blobmsg_add_u64(bb, "tx_ucast_bytes", stats->tx_ucast_bytes);
	blobmsg_add_u64(bb, "tx_mcast_pkts", stats->tx_mcast_pkts);
	blobmsg_add_u64(bb, "tx_mcast_bytes", stats->tx_mcast_bytes);

	blobmsg_add_u64(bb, "rx_data_pkts", stats->rx_pkts);
	blobmsg_add_u64(bb, "rx_data_bytes", stats->rx_bytes);
	blobmsg_add_u64(bb, "rx_failures", stats->rx_fail_pkts);

	blobmsg_add_u64(bb, "rx_ucast_pkts", stats->rx_ucast_pkts);
	blobmsg_add_u64(bb, "rx_ucast_bytes", stats->rx_ucast_bytes);
	blobmsg_add_u64(bb, "rx_mcast_pkts", stats->rx_mcast_pkts);
	blobmsg_add_u64(bb, "rx_mcast_bytes", stats->rx_mcast_bytes);

	blobmsg_close_table(bb, t);
}

static void wl_print_rate(struct blob_buf *bb, const char *label, struct wifi_rate *rate)
{
	void *t;

	t = blobmsg_open_table(bb, label);
	blobmsg_add_u32(bb, "rate", rate->rate);
	blobmsg_add_u32(bb, "mcs", rate->m.mcs);
	blobmsg_add_u32(bb, "bandwidth", rate->m.bw);
	blobmsg_add_u32(bb, "sgi", rate->m.sgi);
	blobmsg_add_u32(bb, "nss", rate->m.nss);
	blobmsg_add_u32(bb, "phy", rate->phy);
	blobmsg_close_table(bb, t);
}

static void wl_print_rssi_chains(struct blob_buf *bb, const char *label, int8_t rssi[])
{
	void *t;

	t = blobmsg_open_array(bb, label);
	for (int j = 0; j < WIFI_NUM_ANTENNA; j++)
		blobmsg_add_u32(bb, "", rssi[j]);
	blobmsg_close_array(bb, t);
}

static void wifi_print_radio_stats(struct blob_buf *bb,
					struct wifi_radio_stats *s)
{
	blobmsg_add_u64(bb, "tx_bytes", s->tx_bytes);
	blobmsg_add_u64(bb, "tx_packets", s->tx_pkts);
	blobmsg_add_u64(bb, "tx_error_packets", s->tx_err_pkts);
	blobmsg_add_u64(bb, "tx_dropped_packets", s->tx_dropped_pkts);

	blobmsg_add_u64(bb, "rx_bytes", s->rx_bytes);
	blobmsg_add_u64(bb, "rx_packets", s->rx_pkts);
	blobmsg_add_u64(bb, "rx_error_packets", s->rx_err_pkts);
	blobmsg_add_u64(bb, "rx_dropped_packets", s->rx_dropped_pkts);
	blobmsg_add_u64(bb, "rx_plcp_error_packets", s->rx_plcp_err_pkts);
	blobmsg_add_u64(bb, "rx_fcs_error_packets", s->rx_fcs_err_pkts);
	blobmsg_add_u64(bb, "rx_mac_error_packets", s->rx_mac_err_pkts);
	blobmsg_add_u64(bb, "rx_unknown_packets", s->rx_unknown_pkts);
}

static void wifi_print_radio_diagnostics(struct blob_buf *bb,
					struct wifi_radio_diagnostic *d)
{
	void *t;

	t = blobmsg_open_table(bb, "diagnostics");
	blobmsg_add_u64(bb, "channel_busy", d->channel_busy);
	blobmsg_add_u64(bb, "tx_airtime", d->tx_airtime);
	blobmsg_add_u64(bb, "rx_airtime", d->rx_airtime);
	blobmsg_add_u64(bb, "obss_airtime", d->obss_airtime);
	blobmsg_add_u64(bb, "cca_time", d->cca_time);
	blobmsg_add_u64(bb, "false_cca_count", d->false_cca_count);
	blobmsg_close_table(bb, t);
}

static void wifi_print_radio_akms(struct blob_buf *bb, const char *label,
				  uint32_t *akms, int num_akms)
{
	void *t;

	t = blobmsg_open_array(bb, label);
	for (int i = 0; i < num_akms; i++) {
		char akmstr[16] = "";

		snprintf(akmstr, sizeof(akmstr), "%x", akms[i]);
		blobmsg_add_string(bb, "", akmstr);
	}
	blobmsg_close_array(bb, t);
}

static void wifi_print_radio_supp_bands(struct blob_buf *bb,
					uint32_t supp_band)
{
	void *a;

	a = blobmsg_open_array(bb, "supp_bands");
	if (supp_band & BAND_2)
		blobmsg_add_string(bb, "", "2.4GHz");
	if (supp_band & BAND_5)
		blobmsg_add_string(bb, "", "5GHz");
	if (supp_band & BAND_6)
		blobmsg_add_string(bb, "", "6GHz");
	if (supp_band & BAND_60)
		blobmsg_add_string(bb, "", "60GHz");
	blobmsg_close_array(bb, a);
}

static void wifi_print_radio_supp_std(struct blob_buf *bb,
				      uint32_t supp_std)
{
	void *a;

	a = blobmsg_open_array(bb, "supp_std");
	if (supp_std & WIFI_B)
		blobmsg_add_string(bb, "", "11b");
	if (supp_std & WIFI_G)
		blobmsg_add_string(bb, "", "11g");
	if (supp_std & WIFI_A)
		blobmsg_add_string(bb, "", "11a");
	if (supp_std & WIFI_N)
		blobmsg_add_string(bb, "", "11n");
	if (supp_std & WIFI_AC)
		blobmsg_add_string(bb, "", "11ac");
	if (supp_std & WIFI_AX)
		blobmsg_add_string(bb, "", "11ax");
	if (supp_std & WIFI_BE)
		blobmsg_add_string(bb, "", "11be");
	blobmsg_close_array(bb, a);
}

static void wifi_print_radio_supp_bw(struct blob_buf *bb,
				     uint32_t supp_bw)
{
	void *a;

	a = blobmsg_open_array(bb, "supp_bw");
	if (supp_bw & BIT(BW20))
		blobmsg_add_string(bb, "", "20MHz");
	if (supp_bw & BIT(BW40))
		blobmsg_add_string(bb, "", "40MHz");
	if (supp_bw & BIT(BW80))
		blobmsg_add_string(bb, "", "80MHz");
	if (supp_bw & BIT(BW160))
		blobmsg_add_string(bb, "", "160MHz");
	if (supp_bw & BIT(BW8080))
		blobmsg_add_string(bb, "", "80+80MHz");
	if (supp_bw & BIT(BW320))
		blobmsg_add_string(bb, "", "320MHz");
	blobmsg_close_array(bb, a);
}

static void wifi_print_band(struct blob_buf *bb, enum wifi_band band)
{
	switch (band) {
	case BAND_2:
		blobmsg_add_string(bb, "band", "2.4GHz");
		break;
	case BAND_5:
		blobmsg_add_string(bb, "band", "5GHz");
		break;
	case BAND_6:
		blobmsg_add_string(bb, "band", "6GHz");
		break;
	default:
		blobmsg_add_string(bb, "band", "unknown");
		break;
	}
}
static void wifi_print_acs_mode(struct blob_buf *bb, enum wifi_acs_mode mode)
{
	switch (mode) {
	case WIFI_ACS_MODE_DISABLE:
		blobmsg_add_string(bb, "acs_mode", "disabled");
		break;
	case WIFI_ACS_MODE_ENABLE:
		blobmsg_add_string(bb, "acs_mode", "enabled");
		break;
	case WIFI_ACS_MODE_MONITOR:
		blobmsg_add_string(bb, "acs_mode", "monitor");
		break;
	default:
		blobmsg_add_string(bb, "acs_mode", "unknown");
		break;
	}
}

static void wifi_print_mode(struct blob_buf *bb, enum wifi_mode mode)
{
	switch (mode) {
	case WIFI_MODE_AP:
		blobmsg_add_string(bb, "mode", "ap");
		break;
	case WIFI_MODE_AP_VLAN:
		blobmsg_add_string(bb, "mode", "ap-vlan");
		break;
	case WIFI_MODE_STA:
		blobmsg_add_string(bb, "mode", "sta");
		break;
	case WIFI_MODE_MONITOR:
		blobmsg_add_string(bb, "mode", "monitor");
		break;
	default:
		blobmsg_add_string(bb, "mode", "unknown");
		break;
	}
}

static void wifi_print_radio_cac_methods(struct blob_buf *bb,
					 uint32_t cac_methods)
{
	void *a;

	a = blobmsg_open_array(bb, "cac_methods");
	if (cac_methods & BIT(WIFI_CAC_CONTINUOUS))
		blobmsg_add_string(bb, "", "continous");
	if (cac_methods & BIT(WIFI_CAC_DEDICATED))
		blobmsg_add_string(bb, "", "continous-dedicated");
	if (cac_methods & BIT(WIFI_CAC_MIMO_REDUCED))
		blobmsg_add_string(bb, "", "mimo-reduced");
	if (cac_methods & BIT(WIFI_CAC_TIME_SLICED))
		blobmsg_add_string(bb, "", "time-sliced");
	blobmsg_close_array(bb, a);
}

static void wifi_print_radio_caps(struct blob_buf *bb, struct wifi_caps *caps, enum wifi_mode m)
{
	char bitmap_str[512] = {0};
	void *f;

	if (caps->valid & WIFI_CAP_HT_VALID) {
		f = blobmsg_open_table(bb, "ht");

		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->ht.byte))) {
			bitmap_str[0] = '\0';
			btostr(caps->ht.byte, sizeof(caps->ht.byte), bitmap_str);
			blobmsg_add_string(bb, "caps", bitmap_str);
		}
		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->ht.supp_mcs))) {
			bitmap_str[0] = '\0';
			btostr(caps->ht.supp_mcs, sizeof(caps->ht.supp_mcs), bitmap_str);
			blobmsg_add_string(bb, "mcs", bitmap_str);
		}

		blobmsg_close_table(bb, f);
	}

	if (caps->valid & WIFI_CAP_VHT_VALID) {
		f = blobmsg_open_table(bb, "vht");

		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->vht.byte))) {
			bitmap_str[0] = '\0';
			btostr(caps->vht.byte, sizeof(caps->vht.byte), bitmap_str);
			blobmsg_add_string(bb, "caps", bitmap_str);
		}
		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->vht.supp_mcs))) {
			bitmap_str[0] = '\0';
			btostr(caps->vht.supp_mcs, sizeof(caps->vht.supp_mcs), bitmap_str);
			blobmsg_add_string(bb, "mcs", bitmap_str);
		}

		blobmsg_close_table(bb, f);
	}

	if (caps->valid & WIFI_CAP_HE_VALID) {
		f = blobmsg_open_table(bb, "he");

		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->eht.byte_phy))) {
			bitmap_str[0] = '\0';
			btostr(caps->he.byte_phy, sizeof(caps->he.byte_phy), bitmap_str);
			blobmsg_add_string(bb, "phy_caps", bitmap_str);
		}
		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->eht.byte_mac))) {
			bitmap_str[0] = '\0';
			btostr(caps->he.byte_mac, sizeof(caps->he.byte_mac), bitmap_str);
			blobmsg_add_string(bb, "mac_caps", bitmap_str);
		}
		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->he.byte_mcs))) {
			bitmap_str[0] = '\0';
			btostr(caps->he.byte_mcs, caps->he.byte_mcs_len, bitmap_str);
			blobmsg_add_string(bb, "mcs", bitmap_str);
		}
		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->he.byte_ppe))) {
			bitmap_str[0] = '\0';
			btostr(caps->he.byte_ppe, caps->he.byte_ppe_len, bitmap_str);
			blobmsg_add_string(bb, "ppe", bitmap_str);
		}

		blobmsg_close_table(bb, f);
	}

	if (caps->valid & WIFI_CAP_EHT_VALID) {
		f = blobmsg_open_table(bb, "eht");

		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->eht.byte_phy))) {
			bitmap_str[0] = '\0';
			btostr(caps->eht.byte_phy, sizeof(caps->eht.byte_phy), bitmap_str);
			blobmsg_add_string(bb, "phy_caps", bitmap_str);
		}
		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->eht.byte_mac))) {
			bitmap_str[0] = '\0';
			btostr(caps->eht.byte_mac, sizeof(caps->eht.byte_mac), bitmap_str);
			blobmsg_add_string(bb, "mac_caps", bitmap_str);
		}
		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->eht.supp_mcs))) {
			bitmap_str[0] = '\0';
			btostr(caps->eht.supp_mcs, caps->eht.supp_mcs_len, bitmap_str);
			blobmsg_add_string(bb, "mcs", bitmap_str);
		}
		if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->eht.byte_ppe_th))) {
			bitmap_str[0] = '\0';
			btostr(caps->eht.byte_ppe_th, caps->eht.byte_ppe_th_len, bitmap_str);
			blobmsg_add_string(bb, "ppe", bitmap_str);
		}

		blobmsg_close_table(bb, f);
	}

	if (caps->valid & WIFI_CAP_ML_VALID) {
		f = blobmsg_open_table(bb, "ml");

		if (caps->ml.valid & WIFI_CAP_ML_EML_VALID) {
			if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->ml.eml))) {
				bitmap_str[0] = '\0';
				btostr(caps->ml.eml, sizeof(caps->ml.eml), bitmap_str);
				blobmsg_add_string(bb, "eml", bitmap_str);
			}
		}

		if (caps->ml.valid & WIFI_CAP_ML_MLD_VALID) {
			if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->ml.mld))) {
				bitmap_str[0] = '\0';
				btostr(caps->ml.eml, sizeof(caps->ml.mld), bitmap_str);
				blobmsg_add_string(bb, "mld", bitmap_str);
			}
		}

		if (caps->ml.valid & WIFI_CAP_ML_EMLD_VALID) {
			if ((sizeof(bitmap_str) - 1) >= (2 * sizeof(caps->ml.emld))) {
				bitmap_str[0] = '\0';
				btostr(caps->ml.emld, sizeof(caps->ml.emld), bitmap_str);
				blobmsg_add_string(bb, "emld", bitmap_str);
			}
		}

		blobmsg_close_table(bb, f);
	}

	if (caps->valid & WIFI_CAP_ML_VALID) {
		if (!!(caps->ml.valid & WIFI_CAP_ML_EML_VALID)) {
			uint8_t *eml = caps->ml.eml;

			blobmsg_add_u8(bb, "emlsr_supported", !!(eml[0] & BIT(0)) ? true : false);
			blobmsg_add_u8(bb, "emlmr_supported", !!(eml[0] & BIT(7)) ? true : false);
		}

		if (!!(caps->ml.valid & WIFI_CAP_ML_MLD_VALID)) {
			uint8_t *mld = caps->ml.mld;
			uint8_t max_simlinks = (mld[0] & 0x0f);
			bool nstr_mobile_ap = !!(mld[0] & 0x80);

			if (max_simlinks > 0) {
				if (m == WIFI_MODE_AP && nstr_mobile_ap)
					blobmsg_add_u8(bb, "nstr", true);
				else
					blobmsg_add_u8(bb, "nstr", false);

				if (m == WIFI_MODE_AP)
					blobmsg_add_u8(bb, "str", !nstr_mobile_ap ? true : false);
				else
					blobmsg_add_u8(bb, "str", true);
			}

			blobmsg_add_u32(bb, "max_links", max_simlinks);
			blobmsg_add_u32(bb, "ttlm", ((mld[0] >> 1) & 0x3));
		}
	}
}

int wl_radio_help(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	return wl_help_command(ctx, obj, req, method, msg, WIFI_RADIO_OBJECT);
}

int wl_radio_status(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	char std_buf2[32] = "802.11";
	char std_buf[32] = {0};
	uint8_t hwaddr[6] = {0};
	const char *wldev;
	const char *radioname;
	enum wifi_band band = BAND_2;
	struct wifi_metainfo minfo = {0};
	struct wifi_radio radio = {};
	struct wifi_opclass supp_opclass[64] = {0};
	int num_opclass = ARRAY_SIZE(supp_opclass);
	bool radio_disabled;
	struct blob_buf bb;
	void *c, *d, *dd;
	bool multiband = false;
	int i, j;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;


	radioname = ubus_objname_to_ifname(obj);
	wldev = wdev->phy;
	band = wdev->band;
	radio_disabled = wdev->disabled;

	/* Get required information */
	wifi_driver_info(wldev, &minfo);
	wifi_radio_is_multiband(wldev, &multiband);
	if (wifi_radio_get_hwaddr(wldev, hwaddr) != 0)
		if_gethwaddr(wldev, hwaddr);

	if (hwaddr_is_zero(hwaddr) && !hwaddr_is_zero(wdev->macaddr))
		memcpy(hwaddr, wdev->macaddr, 6);

	wifi_radio_info_band(wldev, band, &radio);
	wifi_get_band_supp_opclass(wldev, band, &num_opclass, supp_opclass);

	if (multiband) {
		/* Temporary workaround for multiband/single wiphy device */
		switch (band) {
		case BAND_5:
			hwaddr[5] += 1;
			break;
		case BAND_6:
			hwaddr[5] += 2;
			break;
		default:
			break;
		}
	}

	/* Print required information */
	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "radio", radioname);
	blobmsg_add_string(&bb, "phyname", wldev);
	blobmsg_add_macaddr(&bb, "macaddr", hwaddr);
	blobmsg_add_string(&bb, "firmware", minfo.fw_data);
	blobmsg_add_string(&bb, "vendor_id", minfo.vendor_id);
	blobmsg_add_string(&bb, "device_id", minfo.device_id);

	blobmsg_add_u8(&bb, "isup", !radio_disabled);
	wifi_print_band(&bb, band);

	snprintf(std_buf2 + strlen(std_buf2), 31, "%s",
		etostr(radio.oper_std, std_buf, sizeof(std_buf), WIFI_NUM_STD, standard_str));
	blobmsg_add_string(&bb, "standard", std_buf2);

	blobmsg_add_u32(&bb, "num_iface", radio.num_iface);
	c = blobmsg_open_array(&bb, "iface");
	for (i = 0; i < radio.num_iface; i++) {
		d = blobmsg_open_table(&bb, "");
		blobmsg_add_string(&bb, "name", radio.iface[i].name);
		wifi_print_mode(&bb, radio.iface[i].mode);
		blobmsg_close_table(&bb, d);
	}
	blobmsg_close_array(&bb, c);

	blobmsg_add_u32(&bb, "opclass", radio.curr_opclass);
	blobmsg_add_u32(&bb, "channel", radio.channel);
	blobmsg_add_u32(&bb, "bandwidth", bw_value(radio.curr_bw));
	blobmsg_add_u32(&bb, "ccfs0", radio.ccfs0);
	blobmsg_add_u32(&bb, "ccfs1", radio.ccfs1);
	blobmsg_add_string(&bb, "channel_ext",
			radio.extch == EXTCH_NONE ? "none" :
			radio.extch == EXTCH_ABOVE ? "above" :
			radio.extch == EXTCH_BELOW ? "below" : "auto");
	blobmsg_add_u32(&bb, "tx_streams", radio.tx_streams);
	blobmsg_add_u32(&bb, "rx_streams", radio.rx_streams);
	blobmsg_add_u32(&bb, "tx_antenna_mask", radio.tx_antenna);
	blobmsg_add_u32(&bb, "rx_antenna_mask", radio.rx_antenna);
	blobmsg_add_u32(&bb, "supp_tx_antenna_mask", radio.supp_tx_antenna);
	blobmsg_add_u32(&bb, "supp_rx_antenna_mask", radio.supp_rx_antenna);
	blobmsg_add_u32(&bb, "noise", radio.noise);
	blobmsg_add_string(&bb, "guard_int", guard_interval_str[radio.gi]);
	blobmsg_add_u64(&bb, "maxrate", radio.maxrate);

	if (wdev->acs_enabled)
		radio.acs_mode = WIFI_ACS_MODE_ENABLE;
	else
		radio.acs_mode = WIFI_ACS_MODE_DISABLE;

	wifi_print_acs_mode(&bb, radio.acs_mode);
	blobmsg_add_u8(&bb, "mlo_capable", radio.mlo_capable ? true : false);

	c = blobmsg_open_table(&bb, "ap_caps");
	wifi_print_radio_caps(&bb, &radio.ap_caps, WIFI_MODE_AP);
	blobmsg_close_table(&bb, c);

	c = blobmsg_open_table(&bb, "sta_caps");
	wifi_print_radio_caps(&bb, &radio.sta_caps, WIFI_MODE_STA);
	blobmsg_close_table(&bb, c);

	wifi_print_radio_supp_bands(&bb, radio.supp_band);
	wifi_print_radio_supp_std(&bb, radio.supp_std);
	wifi_print_radio_supp_bw(&bb, radio.supp_bw);
	wifi_print_radio_cac_methods(&bb, radio.cac_methods);

	c = blobmsg_open_array(&bb, "supp_rates");
	for (i = 0; i < 32 && radio.supp_rates[i] != 0; i++) {
		blobmsg_add_u32(&bb, "", radio.supp_rates[i]);
	}

	blobmsg_close_array(&bb, c);
	c = blobmsg_open_array(&bb, "basic_rates");
	for (i = 0; i < 32 && radio.basic_rates[i] != 0; i++) {
		blobmsg_add_u32(&bb, "", radio.basic_rates[i]);
	}
	blobmsg_close_array(&bb, c);

	c = blobmsg_open_array(&bb, "supp_channels");
#if 0
	for (i = 0; i < 32 && radio.supp_channels[i] != 0; i++) {
		blobmsg_add_u32(&bb, "", radio.supp_channels[i]);
	}
#else
	for (i = 0; i < num_opclass; i++) {
		d = blobmsg_open_table(&bb, "");
		blobmsg_add_u32(&bb, "opclass", supp_opclass[i].g_opclass);
		blobmsg_add_u32(&bb, "bandwidth", bw_value(supp_opclass[i].bw));
		blobmsg_add_u32(&bb, "txpower", supp_opclass[i].opchannel.txpower);
		dd = blobmsg_open_array(&bb, "channels");
		for (j = 0; j < supp_opclass[i].opchannel.num; j++)
			blobmsg_add_u32(&bb, "", supp_opclass[i].opchannel.ch[j].channel);
		blobmsg_close_array(&bb, dd);
		blobmsg_close_table(&bb, d);
	}
#endif
	blobmsg_close_array(&bb, c);

	if (radio.acs_capable) {
		c = blobmsg_open_table(&bb, "autochannel");
		blobmsg_add_u8(&bb, "enabled", radio.acs_enabled ? true : false);
		blobmsg_add_u32(&bb, "interval", radio.acs_interval);
		blobmsg_close_table(&bb, c);
	}

	blobmsg_add_u32(&bb, "txpower_dbm", radio.txpower_dbm);
	blobmsg_add_u32(&bb, "txpower", radio.txpower);
	blobmsg_add_u8(&bb, "dot11h_capable", radio.dot11h_capable ? true : false);
	blobmsg_add_u8(&bb, "dot11h_enabled", radio.dot11h_enabled ? true : false);
	blobmsg_add_string(&bb, "regdomain", radio.regdomain);

	blobmsg_add_u32(&bb, "beacon_int", radio.beacon_int);
	blobmsg_add_u32(&bb, "dtim_period", radio.dtim_period);

	blobmsg_add_string(&bb, "preamble",
			radio.pr == SHORT_PREAMBLE ? "short" :
			radio.pr == LONG_PREAMBLE ? "long" : "auto");

	blobmsg_add_u32(&bb, "short_retry_limit", radio.srl);
	blobmsg_add_u32(&bb, "long_retry_limit", radio.lrl);
	blobmsg_add_u32(&bb, "frag_threshold", radio.frag);
	blobmsg_add_u32(&bb, "rts_threshold", radio.rts);
	blobmsg_add_u8(&bb, "aggr_enabled", radio.aggr_enable ? true : false);

	c = blobmsg_open_table(&bb, "stats");
	wifi_print_radio_stats(&bb, &radio.stats);
	blobmsg_close_table(&bb, c);

	wifi_print_radio_diagnostics(&bb, &radio.diag);

	blobmsg_add_u32(&bb, "max_akms", radio.max_akms);
	wifi_print_radio_akms(&bb, "ap_akms", radio.ap_akms, radio.num_ap_akms);
	wifi_print_radio_akms(&bb, "sta_akms", radio.sta_akms, radio.num_sta_akms);

	blobmsg_add_u32(&bb, "max_iface_ap", radio.max_iface_ap);
	blobmsg_add_u32(&bb, "max_iface_sta", radio.max_iface_sta);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int wl_radio_stats(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	const char *ifname;
	const char *radioname;
	enum wifi_band band;
	struct wifi_radio_stats s;
	struct wifi_radio_diagnostic diag = {};
	struct blob_buf bb = {0};
	bool multiband = false;
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	radioname = ubus_objname_to_ifname(obj);
	UNUSED(radioname);
	ifname = wdev->phy;
	band = wdev->band;

	wifi_radio_is_multiband(ifname, &multiband);
	if (multiband)
		ret = wifi_radio_get_band_stats(ifname, band, &s);
	else
		ret = wifi_radio_get_stats(ifname, &s);

	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	wifi_get_diagnostic(ifname, band, &diag);
	blob_buf_init(&bb, 0);
	wifi_print_radio_stats(&bb, &s);
	wifi_print_radio_diagnostics(&bb, &diag);
	blobmsg_add_u32(&bb, "noise", s.noise);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

static int wl_dump_beacon(struct ubus_context *ctx, struct ubus_request_data *req,
			  struct blob_buf *bb, const char *ifname, enum wifi_band band,
			  const char *aname)
{
	char iestr[513] = {0};	/* 2 x 256 + 1 */
	int len = 2400;
	uint8_t *bcn;
	uint8_t *ie;
	int ret;
	void *a;

	bcn = calloc(1, len * sizeof(uint8_t));
	if (!bcn)
		return UBUS_STATUS_UNKNOWN_ERROR;

	ret = wifi_get_beacon_ies_band(ifname, band, bcn, &len);
	if (ret) {
		free(bcn);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	a = blobmsg_open_array(bb, aname ? aname : radio_band_to_band(band));
	wifi_foreach_ie(ie, bcn, len) {
		btostr(ie, ie[1] + 2, iestr);
		blobmsg_add_string(bb, "", iestr);
		memset(iestr, 0, sizeof(iestr));
	}
	blobmsg_close_array(bb, a);
	free(bcn);

	return 0;
}

static int wl_dump_beacon_mld(struct ubus_context *ctx, struct ubus_request_data *req,
			      struct blob_buf *bb, const char *ifname, enum wifi_band band)
{
	char iestr[513] = {0};	/* 2 x 256 + 1 */
	int len = 2400;
	uint8_t *bcn;
	uint8_t *ie;
	int ret;
	void *t, *a;

	bcn = calloc(1, len * sizeof(uint8_t));
	if (!bcn)
		return UBUS_STATUS_UNKNOWN_ERROR;

	ret = wifi_get_beacon_ies_band(ifname, band, bcn, &len);
	if (ret) {
		free(bcn);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	t = blobmsg_open_table(bb, "");
	blobmsg_add_string(bb, "band", radio_band_to_band_ghz(band));
	a = blobmsg_open_array(bb, "ies");
	wifi_foreach_ie(ie, bcn, len) {
		btostr(ie, ie[1] + 2, iestr);
		blobmsg_add_string(bb, "", iestr);
		memset(iestr, 0, sizeof(iestr));
	}
	blobmsg_close_array(bb, a);
	blobmsg_close_table(bb, t);
	free(bcn);

	return 0;
}

int wl_ap_dump_beacon(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_iface *iface = (struct wifimngr_iface *)wo->priv;
	struct blob_buf bb = {0};
	const char *ifname;
	int ret;

	if (strlen(iface->mld_netdev) && if_nametoindex(iface->mld_netdev) != 0)
		ifname = iface->mld_netdev;
	else
		ifname = ubus_ap_to_ifname(obj);

	blob_buf_init(&bb, 0);
	ret = wl_dump_beacon(ctx, req, &bb, ifname, iface->band, "beacon-ies");

	if (!ret)
		ubus_send_reply(ctx, req, bb.head);

	blob_buf_free(&bb);
	return ret;
}

int wl_apmld_dump_beacon(struct ubus_context *ctx, struct ubus_object *obj,
			 struct ubus_request_data *req, const char *method,
			 struct blob_attr *msg)
{
	struct blob_attr *tb[__DUMP_BEACON_MAX];
	enum wifi_band band = BAND_ANY;
	struct wifi_mlo_link link[4];
	int num = ARRAY_SIZE(link);
	struct blob_buf bb = {0};
	const char *ifname;
	int ret = 0;
	void *a;
	int i;

	blobmsg_parse(dump_beacon_policy, __DUMP_BEACON_MAX, tb, blob_data(msg),
		      blob_len(msg));

	ifname = ubus_objname_to_ifname(obj);

	if (tb[DUMP_BEACON_BAND])
		band = band_to_radio_band(blobmsg_get_u32(tb[DUMP_BEACON_BAND]));

	if (wifi_get_mlo_links(ifname, band, link, &num))
		return UBUS_STATUS_UNKNOWN_ERROR;
	if (!num)
		return UBUS_STATUS_UNKNOWN_ERROR;

	blob_buf_init(&bb, 0);

	if (num == 1) {
		/* Single band - use same format as wifi.ap.X */
		ret = wl_dump_beacon(ctx, req, &bb, ifname, link[0].band, "beacon-ies");
	} else {
		/* Multiple bands - use new format with band field */
		a = blobmsg_open_array(&bb, "beacon-ies");
		for (i = 0; i < num; i++)
			ret |= wl_dump_beacon_mld(ctx, req, &bb, ifname, link[i].band);
		blobmsg_close_array(&bb, a);
	}

	if (!ret)
		ubus_send_reply(ctx, req, bb.head);

	blob_buf_free(&bb);
	return ret;
}

static void wl_ap_wmm_status(struct blob_buf *bb, struct wifi_ap_wmm_ac ac[])
{
	const char *ac_str[] = { "BE", "BK", "VI", "VO" };
	void *a, *t;
	int i;

	a = blobmsg_open_array(bb, "wmm_params");
	for (i = 0; i < WIFI_NUM_AC; i++) {
		t = blobmsg_open_table(bb, "");
		blobmsg_add_string(bb, "ac", ac_str[ac[i].ac]);
		blobmsg_add_u32(bb, "aifsn", ac[i].aifsn);
		blobmsg_add_u32(bb, "cwmin", ac[i].cwmin);
		blobmsg_add_u32(bb, "cwmax", ac[i].cwmax);
		blobmsg_add_u32(bb, "txop", ac[i].txop);
		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, a);
}

static void wl_dump_supp_security(struct blob_buf *bb, uint32_t sec)
{
	void *a;


	a = blobmsg_open_array(bb, "supp_security");

	if (!!(sec & BIT(WIFI_SECURITY_NONE)))
		blobmsg_add_string(bb, "", "NONE");
	if (!!(sec & BIT(WIFI_SECURITY_WEP64)))
		blobmsg_add_string(bb, "", "WEP64");
	if (!!(sec & BIT(WIFI_SECURITY_WEP128)))
		blobmsg_add_string(bb, "", "WEP128");
	if (!!(sec & BIT(WIFI_SECURITY_WPAPSK)))
		blobmsg_add_string(bb, "", "WPAPSK");
	if (!!(sec & BIT(WIFI_SECURITY_WPA2PSK)))
		blobmsg_add_string(bb, "", "WPA2PSK");
	if (!!(sec & BIT(WIFI_SECURITY_WPA3PSK)))
		blobmsg_add_string(bb, "", "WPA3PSK");
	if (!!(sec & BIT(WIFI_SECURITY_WPAPSK)) && (sec & BIT(WIFI_SECURITY_WPA2PSK)))
		blobmsg_add_string(bb, "", "WPAPSK+WPA2PSK");
	if (!!(sec & BIT(WIFI_SECURITY_WPA2PSK)) && (sec & BIT(WIFI_SECURITY_WPA3PSK)))
		blobmsg_add_string(bb, "", "WPA2PSK+WPA3PSK");
	if (!!(sec & BIT(WIFI_SECURITY_WPA)))
		blobmsg_add_string(bb, "", "WPA");
	if (!!(sec & BIT(WIFI_SECURITY_WPA2)))
		blobmsg_add_string(bb, "", "WPA2");
	if (!!(sec & BIT(WIFI_SECURITY_WPA3)))
		blobmsg_add_string(bb, "", "WPA3");
	if (!!(sec & BIT(WIFI_SECURITY_WPA)) && (sec & BIT(WIFI_SECURITY_WPA2)))
		blobmsg_add_string(bb, "", "WPA+WPA2");
	if (!!(sec & BIT(WIFI_SECURITY_WPA2)) && (sec & BIT(WIFI_SECURITY_WPA3)))
		blobmsg_add_string(bb, "", "WPA2+WPA3");
	if (!!(sec & BIT(WIFI_SECURITY_FT_WPA3PSK)))
		blobmsg_add_string(bb, "", "WPA3PSK+FT");
	if (!!(sec & BIT(WIFI_SECURITY_FT_WPA2PSK)))
		blobmsg_add_string(bb, "", "WPA2PSK+FT");

	blobmsg_close_array(bb, a);
}

int wl_ap_help(struct ubus_context *ctx, struct ubus_object *obj,
	       struct ubus_request_data *req, const char *method,
	       struct blob_attr *msg)
{
	return wl_help_command(ctx, obj, req, method, msg, WIFI_AP_OBJECT);
}

int wl_ap_status(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_iface *iface = (struct wifimngr_iface *)wo->priv;

	struct blob_buf bb = {0};
	struct wifi_ap ap = {0};
	struct wifi_bss *bss;
	ifopstatus_t opstatus;
	ifstatus_t ifs = 0;
	char std_buf2[32] = "802.11";
	char std_buf[32] = {0};
	char sec_str[128] = {0};
	char enc_buf[32] = {0};
	const char *ifname;
	int ret;

	if (strlen(iface->mld_netdev) && if_nametoindex(iface->mld_netdev) != 0)
		ifname = iface->mld_netdev;
	else
		ifname = ubus_ap_to_ifname(obj);

	ret = wifi_ap_info_band(ifname, iface->band, &ap);
	if (ret) {
		fprintf(stderr, "%s: error %d\n", __func__, ret);
		return -1;
	}

	bss = &ap.bss;

	//wifi_get_security(ifname, &akm, &enc, &g_enc);	// TODO: new
	wifi_security_str(ap.bss.security, sec_str, sizeof(sec_str));

	wifi_get_ifstatus(ifname, &ifs);
	wifi_get_ifoperstatus(ifname, &opstatus);
	if (opstatus > IF_OPER_UP) {
		opstatus = IF_OPER_UNKNOWN;
	}

	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "ifname", iface->iface);
	if (strlen(iface->mld_netdev)) {
		blobmsg_add_string(&bb, "mld", iface->mld);
		blobmsg_add_string(&bb, "mld_ifname", iface->mld_netdev);
	}

	if (iface->mlo)
		blobmsg_add_u32(&bb, "linkid", bss->mlo_link_id);
	blobmsg_add_u8(&bb, "enabled", ap.enabled ? true : false);
	blobmsg_add_string(&bb, "status", ifstatus_str(ifs));
	//blobmsg_add_string(&bb, "operstate", operstate_str[opstatus]);
	blobmsg_add_string(&bb, "ssid", (char *)bss->ssid);
	blobmsg_add_macaddr(&bb, "bssid", bss->bssid);
	blobmsg_add_u32(&bb, "beacon_int", bss->beacon_int);
	blobmsg_add_u32(&bb, "dtim_period", bss->dtim_period);

	blobmsg_add_string(&bb, "security", sec_str);	// TODO: new
	blobmsg_add_string(&bb, "encryption", etostr(ap.bss.rsn.pair_ciphers,
			   enc_buf, sizeof(enc_buf), 14, wifi_cipherstr));

	wifi_print_band(&bb, bss->band);
	blobmsg_add_u32(&bb, "channel", bss->channel);
	blobmsg_add_u32(&bb, "bandwidth", bw_value(bss->curr_bw));
	blobmsg_add_u32(&bb, "ccfs0", bss->ccfs0);
	blobmsg_add_u32(&bb, "ccfs1", bss->ccfs1);
	blobmsg_add_u32(&bb, "puncture_bitmap", (int)BUF_GET_LE16(bss->puncture));
	if (!bss->bss_color_disabled)
		blobmsg_add_u32(&bb, "bss_color", bss->bss_color);
	//blobmsg_add_u64(&bb, "rate", rate);
	snprintf(std_buf2 + strlen(std_buf2), 31, "%s",
		etostr(ap.bss.oper_std, std_buf, sizeof(std_buf), WIFI_NUM_STD, standard_str));
	blobmsg_add_string(&bb, "standard", std_buf2);
	blobmsg_add_u32(&bb, "num_stations", ap.bss.load.sta_count);
	blobmsg_add_u32(&bb, "max_stations", ap.assoclist_max);
	blobmsg_add_u32(&bb, "utilization", ap.bss.load.utilization);
	blobmsg_add_u32(&bb, "adm_capacity", ap.bss.load.available);
	blobmsg_add_u8(&bb, "hidden", !ap.ssid_advertised ? true : false);
	blobmsg_add_string(&bb, "eml_mode", ap.bss.mlo_eml_mode == WIFI_EML_MODE_NONE ? "none" :
				 ap.bss.mlo_eml_mode == WIFI_EML_MODE_EMLMR ? "EMLMR" : "EMLSR");

	wl_dump_supp_security(&bb, ap.sec.supp_modes);
	wl_dump_capabilities(bss->band, &bb, &bss->caps, bss->cbitmap, sizeof(bss->cbitmap));
	wl_ap_wmm_status(&bb, ap.ac);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

static void wl_ap_wmm_stats(struct blob_buf *bb, struct wifi_ap_wmm_ac ac[])
{
	const char *ac_str[] = { "BE", "BK", "VI", "VO" };
	void *a, *t;
	int i;

	a = blobmsg_open_array(bb, "wmm_stats");
	for (i = 0; i < WIFI_NUM_AC; i++) {
		t = blobmsg_open_table(bb, "");
		blobmsg_add_string(bb, "ac", ac_str[ac[i].ac]);
		blobmsg_add_u64(bb, "tx_bytes", ac[i].stats.tx_bytes);
		blobmsg_add_u32(bb, "tx_packets", ac[i].stats.tx_pkts);
		blobmsg_add_u32(bb, "tx_error_packets", ac[i].stats.tx_err_pkts);
		blobmsg_add_u32(bb, "tx_retrans_packets", ac[i].stats.tx_rtx_pkts);

		blobmsg_add_u64(bb, "rx_bytes", ac[i].stats.rx_bytes);
		blobmsg_add_u32(bb, "rx_packets", ac[i].stats.rx_pkts);
		blobmsg_add_u32(bb, "rx_error_packets", ac[i].stats.rx_err_pkts);

		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, a);
}

static void print_apstats(struct blob_buf *bb, struct wifi_ap_stats *s)
{
	blobmsg_add_u64(bb, "tx_bytes", s->tx_bytes);
	blobmsg_add_u64(bb, "tx_packets", s->tx_pkts);
	blobmsg_add_u64(bb, "tx_unicast_packets", s->tx_ucast_pkts);
	blobmsg_add_u64(bb, "tx_multicast_packets", s->tx_mcast_pkts);
	blobmsg_add_u64(bb, "tx_broadcast_packets", s->tx_bcast_pkts);
	blobmsg_add_u64(bb, "tx_error_packets", s->tx_err_pkts);
	blobmsg_add_u64(bb, "tx_retrans_packets", s->tx_rtx_pkts);
	blobmsg_add_u64(bb, "tx_retrans_fail_packets", s->tx_rtx_fail_pkts);
	blobmsg_add_u64(bb, "tx_retry_packets", s->tx_retry_pkts);
	blobmsg_add_u64(bb, "tx_multi_retry_packets", s->tx_mretry_pkts);
	blobmsg_add_u64(bb, "tx_dropped_packets", s->tx_dropped_pkts);
	blobmsg_add_u64(bb, "ack_fail_packets", s->ack_fail_pkts);
	blobmsg_add_u64(bb, "aggregate_packets", s->aggr_pkts);

	blobmsg_add_u64(bb, "rx_bytes", s->rx_bytes);
	blobmsg_add_u64(bb, "rx_packets", s->rx_pkts);
	blobmsg_add_u64(bb, "rx_unicast_packets", s->rx_ucast_pkts);
	blobmsg_add_u64(bb, "rx_multicast_packets", s->rx_mcast_pkts);
	blobmsg_add_u64(bb, "rx_broadcast_packets", s->rx_bcast_pkts);
	blobmsg_add_u64(bb, "rx_error_packets", s->rx_err_pkts);
	blobmsg_add_u64(bb, "rx_dropped_packets", s->rx_dropped_pkts);
	blobmsg_add_u64(bb, "rx_unknown_packets", s->rx_unknown_pkts);
}

static void print_aploads(struct blob_buf *bb, struct wifi_ap_load *load)
{
	void *t;

	t = blobmsg_open_table(bb, "load");
	blobmsg_add_u32(bb, "utilization", load->utilization);
	blobmsg_add_u32(bb, "sta_count", load->sta_count);
	blobmsg_close_table(bb, t);
}

int wl_ap_stats(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct wifi_ap_stats s;
	struct wifi_ap ap;
	const char *ifname;
	struct blob_buf bb;
	int ret;

	ifname = ubus_ap_to_ifname(obj);

	ret = wifi_ap_get_stats(ifname, &s);
	if (ret != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	memset(&ap, 0, sizeof(struct wifi_ap));
	ret = wifi_ap_info(ifname, &ap);

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	print_apstats(&bb, &s);
	wl_ap_wmm_stats(&bb, ap.ac);
	print_aploads(&bb, &ap.bss.load);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int wl_assoclist(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_iface *iface = (struct wifimngr_iface *)wo->priv;
	unsigned char stas[768] = {0};	/* max 128 per interface */
	struct blob_buf bb = {0};
	const char *ifname;
	int nr = 128;
	void *a;
	int i;

	if (strlen(iface->mld_netdev) && if_nametoindex(iface->mld_netdev) != 0)
		ifname = iface->mld_netdev;
	else
		ifname = ubus_ap_to_ifname(obj);

	if (wifi_get_assoclist_band(ifname, iface->band, stas, &nr) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	blob_buf_init(&bb, 0);

	a = blobmsg_open_array(&bb, "assoclist");
	for (i = 0; i < nr; i++) {
		blobmsg_add_macaddr(&bb, "", &stas[i*6]);
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int wl_blocked_stas_print(struct blob_buf *bb, const char *ifname,
			  enum wifi_band band, const char *array_name)
{
	unsigned char stas[768] = {0};	/* max 128 per interface */
	int nr = 128;
	void *a;
	int i;

	if (wifi_get_blocked_stas(ifname, band, stas, &nr) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	a = blobmsg_open_array(bb, array_name ? array_name : radio_band_to_band(band));
	for (i = 0; i < nr; i++) {
		blobmsg_add_macaddr(bb, "", &stas[i*6]);
	}
	blobmsg_close_array(bb, a);

	return 0;
}

static int wl_blocked_stas_print_mld(struct blob_buf *bb, const char *ifname,
				     enum wifi_band band)
{
	unsigned char stas[768] = {0};	/* max 128 per interface */
	int nr = 128;
	void *t, *a;
	int i;

	if (wifi_get_blocked_stas(ifname, band, stas, &nr) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	t = blobmsg_open_table(bb, "");
	blobmsg_add_string(bb, "band", radio_band_to_band_ghz(band));
	a = blobmsg_open_array(bb, "stas");
	for (i = 0; i < nr; i++) {
		blobmsg_add_macaddr(bb, "", &stas[i*6]);
	}
	blobmsg_close_array(bb, a);
	blobmsg_close_table(bb, t);

	return 0;
}

int wl_blocked_stas(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	struct blob_buf bb = {0};
	const char *ifname;
	int ret;

	ifname = ubus_ap_to_ifname(obj);

	blob_buf_init(&bb, 0);
	ret = wl_blocked_stas_print(&bb, ifname, BAND_ANY, "blocked");

	if (!ret)
		ubus_send_reply(ctx, req, bb.head);

	blob_buf_free(&bb);
	return ret;
}

int wl_apmld_blocked_stas(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg)
{
	struct blob_attr *tb[__APMLD_BLOCKED_MACLIST_MAX];
	struct blob_buf bb = {0};
	enum wifi_band band = BAND_ANY;
	struct wifi_mlo_link link[4];
	int num = ARRAY_SIZE(link);
	const char *ifname;
	int ret = 0;
	void *a;
	int i;

	blobmsg_parse(apmld_blocked_maclist_policy, __APMLD_BLOCKED_MACLIST_MAX, tb, blob_data(msg),
		      blob_len(msg));

	if (tb[APMLD_BLOCKED_MACLIST_BAND])
		band = band_to_radio_band(blobmsg_get_u32(tb[APMLD_BLOCKED_MACLIST_BAND]));

	ifname = ubus_objname_to_ifname(obj);
	if (wifi_get_mlo_links(ifname, band, link, &num))
		return UBUS_STATUS_UNKNOWN_ERROR;
	if (!num)
		return UBUS_STATUS_UNKNOWN_ERROR;

	blob_buf_init(&bb, 0);

	if (num == 1) {
		/* Single band - use same format as wifi.ap.X */
		ret = wl_blocked_stas_print(&bb, ifname, link[0].band, "blocked");
	} else {
		/* Multiple bands - use new format with band field */
		a = blobmsg_open_array(&bb, "blocked");
		for (i = 0; i < num; i++)
			ret |= wl_blocked_stas_print_mld(&bb, ifname, link[i].band);
		blobmsg_close_array(&bb, a);
	}

	if (!ret)
		ubus_send_reply(ctx, req, bb.head);

	blob_buf_free(&bb);
	return ret;
}

int wl_block_sta(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct blob_attr *tb[__BLOCK_STATION_MAX];
	uint8_t sta[6] = {0};
	const char *ifname;
	int block;
	int ret;

	blobmsg_parse(block_station_policy, __BLOCK_STATION_MAX, tb, blob_data(msg),
		      blob_len(msg));

	ifname = ubus_ap_to_ifname(obj);

	if (!tb[BLOCK_STATION_MACADDR] ||
	    !tb[BLOCK_STATION_BLOCK])
		return UBUS_STATUS_INVALID_ARGUMENT;

	if (hwaddr_aton(blobmsg_get_string(tb[BLOCK_STATION_MACADDR]), sta) == NULL)
		return UBUS_STATUS_INVALID_ARGUMENT;

	block = blobmsg_get_u32(tb[BLOCK_STATION_BLOCK]);

	ret = wifi_block_sta(ifname, BAND_ANY, sta, block);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return 0;
}

int wl_apmld_block_sta(struct ubus_context *ctx, struct ubus_object *obj,
		       struct ubus_request_data *req, const char *method,
		       struct blob_attr *msg)
{
	struct blob_attr *tb[__APMLD_BLOCK_STATION_MAX];
	enum wifi_band band = BAND_ANY;
	uint8_t sta[6] = {0};
	const char *ifname;
	int block;
	int ret;

	blobmsg_parse(apmld_block_station_policy, __APMLD_BLOCK_STATION_MAX, tb,
		      blob_data(msg), blob_len(msg));

	ifname = ubus_objname_to_ifname(obj);

	if (!tb[APMLD_BLOCK_STATION_MACADDR] ||
	    !tb[APMLD_BLOCK_STATION_BLOCK])
		return UBUS_STATUS_INVALID_ARGUMENT;

	if (tb[APMLD_BLOCK_STATION_BAND])
		band = band_to_radio_band(blobmsg_get_u32(tb[APMLD_BLOCK_STATION_BAND]));

	if (hwaddr_aton(blobmsg_get_string(tb[APMLD_BLOCK_STATION_MACADDR]), sta) == NULL)
		return UBUS_STATUS_INVALID_ARGUMENT;

	block = blobmsg_get_u32(tb[APMLD_BLOCK_STATION_BLOCK]);

	ret = wifi_block_sta(ifname, band, sta, block);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return 0;
}

int ap_chan_switch(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg)
{
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_iface *iface = (struct wifimngr_iface *)wo->priv;
	struct blob_attr *tb[__CHAN_SWITCH_MAX];
	struct chan_switch_param param = {
				.count = 5,	/* after these many beacons */
				.blocktx = 1,
	};
	const char *ifname;
	int ret;

	blobmsg_parse(chan_switch_policy, __CHAN_SWITCH_MAX, tb, blob_data(msg),
		      blob_len(msg));

	if (strlen(iface->mld_netdev) && if_nametoindex(iface->mld_netdev) != 0)
		ifname = iface->mld_netdev;
	else
		ifname = ubus_ap_to_ifname(obj);

	if (!tb[CHAN_SWITCH_FREQ] && !tb[CHAN_SWITCH_CHANNEL])
		return UBUS_STATUS_INVALID_ARGUMENT;

	if (tb[CHAN_SWITCH_FREQ]) {
		param.freq = blobmsg_get_u32(tb[CHAN_SWITCH_FREQ]);
	} else {
		uint32_t channel = blobmsg_get_u32(tb[CHAN_SWITCH_CHANNEL]);

		param.freq = wifi_channel_to_freq_ex(channel, iface->band);
		if (param.freq < 0)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[CHAN_SWITCH_COUNT])
		param.count = blobmsg_get_u32(tb[CHAN_SWITCH_COUNT]);

	if (tb[CHAN_SWITCH_SEC_CHAN_OFFSET])
		param.sec_chan_offset = blobmsg_get_u32(tb[CHAN_SWITCH_SEC_CHAN_OFFSET]);
	if (tb[CHAN_SWITCH_CF1])
		param.cf1 = blobmsg_get_u32(tb[CHAN_SWITCH_CF1]);
	if (tb[CHAN_SWITCH_CF2])
		param.cf2 = blobmsg_get_u32(tb[CHAN_SWITCH_CF2]);
	if (tb[CHAN_SWITCH_BW])
		param.bandwidth = blobmsg_get_u32(tb[CHAN_SWITCH_BW]);
	if (tb[CHAN_SWITCH_BLOCK_TX])
		param.blocktx = blobmsg_get_bool(tb[CHAN_SWITCH_BLOCK_TX]);
	if (tb[CHAN_SWITCH_HT])
		param.ht = blobmsg_get_bool(tb[CHAN_SWITCH_HT]);
	if (tb[CHAN_SWITCH_VHT])
		param.vht = blobmsg_get_bool(tb[CHAN_SWITCH_VHT]);
	if (tb[CHAN_SWITCH_HE])
		param.he = blobmsg_get_bool(tb[CHAN_SWITCH_HE]);
	if (tb[CHAN_SWITCH_EHT])
		param.eht = blobmsg_get_bool(tb[CHAN_SWITCH_EHT]);
	if (tb[CHAN_SWITCH_AUTO_HT])
		param.auto_ht = blobmsg_get_bool(tb[CHAN_SWITCH_AUTO_HT]);

	if (!tb[CHAN_SWITCH_HT] && !tb[CHAN_SWITCH_VHT] &&
	    !tb[CHAN_SWITCH_HE] && !tb[CHAN_SWITCH_EHT])
		param.auto_ht = true;

	ret = wifi_chan_switch(ifname, &param);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return 0;
}

static int wl_load_sta_ratings_lib(const char *filename)
{
	char *sofile = getenv(filename);
	int ret = 0;

	if (!sofile)
		return -EINVAL;

	if (libsta_ratings)
		return 0;

	libsta_ratings = dlopen(sofile, RTLD_NOW | RTLD_GLOBAL);
	if (!libsta_ratings) {
		return errno;
	}

	sta_ratings_calc = dlsym(libsta_ratings, "sta_ratings_calculate");
	if (!sta_ratings_calc) {
		ret = -1;
		goto out_error;
	}

	sta_ratings_free_lib = dlsym(libsta_ratings, "sta_ratings_free_lib");
	if (!sta_ratings_free_lib) {
		ret = -1;
		goto out_error;
	}

	return 0;

out_error:
	dlclose(libsta_ratings);
	return ret;
}

static void wl_unload_sta_ratings_lib(void)
{
	if (libsta_ratings) {
		if (sta_ratings_free_lib)
			sta_ratings_free_lib(NULL);
		sta_ratings_calc = NULL;
		sta_ratings_free_lib = NULL;
		dlclose(libsta_ratings);
		libsta_ratings = NULL;
	}
}

float wifimngr_sta_ratings_calc(const char *ap_ifname, struct wifi_sta *sta)
{
	float rating = -1.0;

	if (sta_ratings_calc)
		rating = sta_ratings_calc(NULL, ap_ifname, sta);

	return rating;
}

static int wl_dump_stations(struct blob_buf *bb, const char *ifname,
			    enum wifi_band band, unsigned char *macaddr,
			    struct wifi_mlo_link *link)
{
	void *t, *s, *a;
	int i;
	struct wifi_sta sx;
	enum wifi_bw bw;
	int noise, snr;
	unsigned char stas[768] = {0};
	int nr = 64;

	if (wifi_get_assoclist_band(ifname, band, stas, &nr) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	a = blobmsg_open_array(bb, "stations");
	for (i = 0; i < nr; i++) {
		uint32_t channel = 0;
		char std_buf2[32] = "802.11";
		char std_buf[32] = {0};
		unsigned char *sta = &stas[i*6];

		if (macaddr && !hwaddr_is_zero(macaddr)
				&& memcmp(macaddr, sta, 6))
			continue;

		memset(&sx, 0, sizeof(sx));
		if (wifi_get_sta_info_band(ifname, band, sta, &sx))
			continue;

		wifi_get_band_channel(ifname, band, &channel, &bw);

		if (!sx.noise_avg)
			wifi_get_band_noise(ifname, band, &noise);
		else
			noise = sx.noise_avg;
		snr = sx.rssi_avg - noise;

		t = blobmsg_open_table(bb, "");
		blobmsg_add_macaddr(bb, "macaddr", sx.macaddr);
		if (sta_ratings_calc)
			blobmsg_add_double(bb, "rating",
					wifimngr_sta_ratings_calc(ifname, &sx));

		blobmsg_add_macaddr(bb, "bssid", sx.bssid);
		blobmsg_add_string(bb, "wdev", ifname);
		if (link) {
			blobmsg_add_u32(bb, "link_id", link->id);
			//wifi_print_band(&bb, link->band);
			//blobmsg_add_u32(&bb, "link_channel", link->channel);
			//blobmsg_add_u32(&bb, "link_bandwidth", bw_value(link->bandwidth));
		}

		snprintf(std_buf2 + strlen(std_buf2), 31, "%s",
			 etostr(sx.oper_std, std_buf, sizeof(std_buf), WIFI_NUM_STD, standard_str));
		blobmsg_add_string(bb, "standard", std_buf2);
		blobmsg_add_u32(bb, "channel", channel);
		blobmsg_add_u32(bb, "frequency", wifi_channel_to_freq_ex(channel, band));
		blobmsg_add_u32(bb, "rssi", sx.rssi_avg);
		blobmsg_add_u32(bb, "noise", noise);
		blobmsg_add_u32(bb, "snr", snr);
		blobmsg_add_u32(bb, "idle", sx.idle_time);
		blobmsg_add_u64(bb, "in_network", sx.conn_time);
		blobmsg_add_u32(bb, "tx_airtime", sx.tx_airtime);
		blobmsg_add_u32(bb, "rx_airtime", sx.rx_airtime);
		blobmsg_add_u32(bb, "airtime", sx.airtime);
		blobmsg_add_u32(bb, "maxrate", sx.maxrate);
		blobmsg_add_u32(bb, "nss", sx.rate.m.nss);
		blobmsg_add_u32(bb, "bandwidth", sx.rate.m.bw);
		blobmsg_add_u32(bb, "est_rx_thput", sx.est_rx_thput);
		blobmsg_add_u32(bb, "est_tx_thput", sx.est_tx_thput);

		s = blobmsg_open_table(bb, "status");
		blobmsg_add_u8(bb, "wmm",
			wifi_status_isset(sx.sbitmap, WIFI_STATUS_WMM) ? true : false);
		blobmsg_add_u8(bb, "ps",
			wifi_status_isset(sx.sbitmap, WIFI_STATUS_PS) ? true : false);
		blobmsg_close_table(bb, s);

		wl_dump_capabilities(band, bb, &sx.caps, sx.cbitmap, sizeof(sx.cbitmap));
		wl_print_sta_stats(bb, &sx.stats);
		wl_print_rate(bb, "tx_rate_latest", &sx.tx_rate);
		wl_print_rate(bb, "rx_rate_latest", &sx.rx_rate);
		wl_print_rssi_chains(bb, "rssi_per_antenna", sx.rssi);

		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, a);

	return 0;
}

int wl_stations(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct blob_attr *tb[__STAINFO_MAX];
	char sta_macstr[18] = {0};
	uint8_t sta[6] = {0};
	const char *ifname;
	struct blob_buf bb;
	int ret;

	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_iface *iface = (struct wifimngr_iface *)wo->priv;

	memset(&bb, 0, sizeof(bb));
	blobmsg_parse(stainfo_policy, __STAINFO_MAX, tb, blob_data(msg), blob_len(msg));

	ifname = ubus_ap_to_ifname(obj);

	if (strlen(iface->mld_netdev) && if_nametoindex(iface->mld_netdev) != 0)
		ifname = iface->mld_netdev;

	if ((tb[STAINFO_MACADDR])) {
		strncpy(sta_macstr, blobmsg_data(tb[STAINFO_MACADDR]),
					sizeof(sta_macstr)-1);
		if (hwaddr_aton(sta_macstr, sta) == NULL) {
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
	}

	blob_buf_init(&bb, 0);
	ret = wl_dump_stations(&bb, ifname, iface->band, sta, NULL);
	if (!ret)
		ubus_send_reply(ctx, req, bb.head);

	blob_buf_free(&bb);
	return ret;
}

int wl_sta_ratings(struct ubus_context *ctx, struct ubus_object *obj,
		   struct ubus_request_data *req, const char *method,
		   struct blob_attr *msg)
{
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_iface *iface = (struct wifimngr_iface *)wo->priv;
	struct blob_attr *tb[__STAINFO_MAX];
	uint8_t sta_macaddr[6] = {0};
	int ret = UBUS_STATUS_OK;
	uint8_t stas[768] = {0};
	struct wifi_ap ap = {0};
	char macstr[18] = {0};
	const char *ifname;
	struct blob_buf bb;
	int num_stas = 64;
	void *a;

	memset(&bb, 0, sizeof(bb));
	blobmsg_parse(stainfo_policy, __STAINFO_MAX, tb, blob_data(msg), blob_len(msg));

	ifname = ubus_ap_to_ifname(obj);

	if (strlen(iface->mld_netdev) && if_nametoindex(iface->mld_netdev) != 0)
		ifname = iface->mld_netdev;

	if ((tb[STAINFO_MACADDR])) {
		strncpy(macstr, blobmsg_data(tb[STAINFO_MACADDR]), sizeof(macstr) - 1);
		if (hwaddr_aton(macstr, sta_macaddr) == NULL)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	ret = wifi_get_assoclist_band(ifname, iface->band, stas, &num_stas);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	ret = wifi_ap_info_band(ifname, iface->band, &ap);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	blob_buf_init(&bb, 0);
	a = blobmsg_open_array(&bb, "sta-ratings");
	for (int i = 0; i < num_stas; i++) {
		uint8_t *sta = &stas[i*6];
		struct wifi_sta sx = {0};
		void *t;

		if (!hwaddr_is_zero(sta_macaddr) && memcmp(sta_macaddr, sta, 6))
			continue;

		if (wifi_get_sta_info_band(ifname, iface->band, sta, &sx))
			continue;

		t = blobmsg_open_table(&bb, "");
		blobmsg_add_macaddr(&bb, "macaddr", sx.macaddr);
		if (sta_ratings_calc)
			blobmsg_add_double(&bb, "rating",
					wifimngr_sta_ratings_calc(ifname, &sx));
		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return ret;
}

int wl_scan(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct blob_attr *tb[__SCAN_MAX];
	struct scan_param p = {0};
	const char *ifname;
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	blobmsg_parse(wl_scan_policy, __SCAN_MAX, tb, blob_data(msg),
			blob_len(msg));

	ifname = wdev->phy;

	if (tb[SCAN_SSID])
		strncpy(p.ssid, blobmsg_data(tb[SCAN_SSID]), 32);

	if (tb[SCAN_CHANNEL] && tb[SCAN_OPCLASS]) {
		wifimngr_err("%s(): Either opclass or channel allowed\n",
			     __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[SCAN_CHANNEL])
		p.channel = blobmsg_get_u32(tb[SCAN_CHANNEL]);

	if (tb[SCAN_OPCLASS])
		p.opclass = blobmsg_get_u32(tb[SCAN_OPCLASS]);

	ret = wifi_scan(ifname, &p);

	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

#define UBUS_STATUS_BUSY 16

int wl_scan_ex(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct blob_attr *tb[__SCAN_EX_MAX];
	struct scan_param_ex sp = {0};
	const char *radio;
	const char *country = NULL;
	char alpha2[3] = {0};
	uint32_t opclasses[128];
	uint32_t channels[128];
	int op_idx = 0, ch_idx = 0, ret;
	enum wifi_band band = BAND_2;
	const char *radioname;
	bool multiband = false;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;
	int m, idx = 0;

	blobmsg_parse(wl_scan_ex_policy, __SCAN_EX_MAX, tb, blob_data(msg),
			blob_len(msg));

	radioname = ubus_objname_to_ifname(obj);
	UNUSED(radioname);
	radio = wdev->phy;
	band = wdev->band;

	/* Get country from driver first, fallback to cached UCI value */
	if (!wifi_get_country(radio, alpha2))
		country = alpha2;
	else if (wdev->country[0] != '\0')
		country = wdev->country;
	else
		country = "";

	/* example calls:
	 * ubus call wifi.radio.wl0 scan_ex '{"ssid":["iopsysWrt-44D4376AF4C0"]}'
	 * ubus call wifi.radio.wl0 scan_ex '{"channel":[36]}'
	 * ubus call wifi.radio.wl0 scan_ex '{"opclass":[115]}'
	 * ubus call wifi.radio.wl0 scan_ex '{"opclass":[115, 118], "channel":[36, 40, 52]}'
	 */

	/* scan measurement duration */
	if (tb[SCAN_EX_DURATION]) {
		sp.duration = blobmsg_get_u16(tb[SCAN_EX_DURATION]);
	}

	/* array of ssids */
	if (tb[SCAN_EX_SSID]) {
		struct blob_attr *attr;
		int rem, i = 0;

		sp.flag |= WIFI_SCAN_REQ_SSID;
		sp.num_ssid = blobmsg_check_array(
				tb[SCAN_EX_SSID], BLOBMSG_TYPE_STRING);

		wifimngr_dbg("scan_ex: num_ssid = %d\n", sp.num_ssid);

		blobmsg_for_each_attr(attr, tb[SCAN_EX_SSID], rem) {
			if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
				return UBUS_STATUS_INVALID_ARGUMENT;

			strncpy(sp.ssid[i], blobmsg_data(attr),
								sizeof(sp.ssid[i]) - 1);
			i++;
		}
	}

	/* array of opclasses */
	if (tb[SCAN_EX_OPCLASS]) {
		struct blob_attr *attr;
		struct wifi_opclass supp_opclass[64] = {0};
		int num_sup_opclass = ARRAY_SIZE(supp_opclass);
		int num_opclass;
		uint32_t opclass;
		int rem, j;

		num_opclass = blobmsg_check_array(
				tb[SCAN_EX_OPCLASS], BLOBMSG_TYPE_INT32);

		wifi_get_supp_opclass(radio, &num_sup_opclass, supp_opclass);

		/* Verify opclasses provided are BW20 and supported by radio */
		blobmsg_for_each_attr(attr, tb[SCAN_EX_OPCLASS], rem) {

			if (blobmsg_type(attr) != BLOBMSG_TYPE_INT32)
				return UBUS_STATUS_INVALID_ARGUMENT;
			opclass = blobmsg_get_u32(attr);

			for (j = 0; j < num_sup_opclass; j++) {
				if (supp_opclass[j].opclass == opclass
						&& supp_opclass[j].bw == BW20
						&& supp_opclass[j].band == band) {
					opclasses[op_idx++] = opclass;
					break; /* for */
				}
			}
		}

		if (op_idx != num_opclass) {
			wifimngr_dbg("scan_ex: opclass unsupported or not 20MHz\n");
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
	}

	/* array of channels */
	if (tb[SCAN_EX_CHANNEL]) {
		struct blob_attr *attr;
		uint32_t channel, supp_chan[128] = {0};
		int num_sup_chan;
		int num_channel;
		int rem, k;

		num_channel = blobmsg_check_array(
				tb[SCAN_EX_CHANNEL], BLOBMSG_TYPE_INT32);

		num_sup_chan = ARRAY_SIZE(supp_chan);
		wifi_get_supp_channels(radio, supp_chan, &num_sup_chan, country,
				/* only allow scanning on current operating band */
				band,
				/* only allow 20MHz control channels */
				BW20);

		blobmsg_for_each_attr(attr, tb[SCAN_EX_CHANNEL], rem) {

			if (blobmsg_type(attr) != BLOBMSG_TYPE_INT32)
				return UBUS_STATUS_INVALID_ARGUMENT;

			channel = blobmsg_get_u32(attr);

			/* check channel value supported by the radio */
			for (k = 0; k < num_sup_chan; k++) {
				if (supp_chan[k] == channel) {
					channels[ch_idx++] = channel;
					break; /* for */
				}
			}
		}

		if (ch_idx != num_channel) {
			/* channel from the list unsupported, out of band or not a control 20MHz */
			wifimngr_dbg("scan_ex: unsupported / out of band / not 20MHz control channel\n");
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
	}

	wifimngr_dbg("Num of opclasses=%d, num of channels=%d \n", op_idx, ch_idx);

	/* opclass(es) provided - update channels[] */
	if (op_idx > 0) {
		int op;

		for (op = 0; op < op_idx; op++) {
			uint32_t opcch[128] = {0};
			uint32_t supch[128] = {0};
			int num_opc_ch = ARRAY_SIZE(opcch);
			int num_sup_ch = ARRAY_SIZE(supch);
			int i, j, k;

			/* full list of channels in given opclass */
			if (wifi_opclass_to_channels(opclasses[op], &num_opc_ch, opcch)) {
				wifimngr_err("Coulnd't get channels for opclass %d\n",
						opclasses[op]);
				return UBUS_STATUS_INVALID_ARGUMENT;
			}

			/* list of BW20 channels supported by radio */
			wifi_get_supp_channels(radio, supch, &num_sup_ch, country, band, BW20);

			/* add only supported channels to final channels[] list */
			for (i = 0; i < num_opc_ch; i++) {
				for (j = 0; j < num_sup_ch; j++) {
					if (opcch[i] == supch[j]) {
						/* channel supported */
						for (k = 0; k < ch_idx; k++) {
							if (channels[k] == opcch[i])
								/* already on the list */
								break;
						}
						if (k == ch_idx)
							/* not on the list - add */
							channels[ch_idx++] = opcch[i];
						break;
					}
				}
			}
		}
	}

	/* Translate opclasses & channels to freqs. */
	for (m = 0; m < ch_idx; m++) {
		sp.freq[idx] = wifi_channel_to_freq_ex(channels[m], band);
		if (sp.freq[idx] < 0) {
			wifimngr_err("No freq for channel %d\n", channels[m]);
			continue;
		}
		idx++;
	}
	sp.num_freq = idx;

	wifi_radio_is_multiband(radio, &multiband);
	if (multiband)
		ret = wifi_scan_band_ex(radio, band, &sp);
	else
		ret = wifi_scan_ex(radio, &sp);

	wifimngr_dbg("scan_ex: ret = %d\n", ret);

	if (ret == -EBUSY)
		/* case for an ongoing unfinished scan */
		return UBUS_STATUS_BUSY;
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

struct wifimngr_channel *wifimngr_device_lookup_channel(struct wifimngr_device *wdev,
							uint32_t channel)
{
	for (int i = 0; i < wdev->num_ch; i++) {
		if (wdev->ch[i].channel == channel)
			return &wdev->ch[i];
	}

	return NULL;
}

struct wifimngr_channel *wifimngr_device_lookup_freq(struct wifimngr_device *wdev,
						     uint32_t freq)
{
	for (int i = 0; i < wdev->num_ch; i++) {
		if (wdev->ch[i].freq == freq)
			return &wdev->ch[i];
	}

	return NULL;
}

static void wl_scanresult_print(struct blob_buf *bb, void *buf, bool detail)
{
	struct wifi_bss *b = (struct wifi_bss *)buf;
	//char enc_buf[32] = {0};
	//char cpr_buf[32] = {0};
	char std_buf[32] = {0};
	char std_buf2[32] = "802.11";
	char sec_str[64] = {0};
	char cstr[64] = {0};

	blobmsg_add_string(bb, "ssid", (char *)b->ssid);
	blobmsg_add_macaddr(bb, "bssid", b->bssid);
	if (!!(b->caps.valid & WIFI_CAP_ML_VALID)) {
		blobmsg_add_macaddr(bb, "mld_bssid", b->mld_macaddr);
		blobmsg_add_u32(bb, "mlo_linkid", b->mlo_link_id);
	}
	blobmsg_add_u32(bb, "channel", b->channel);
	blobmsg_add_u32(bb, "bandwidth", bw_value(b->curr_bw));
	blobmsg_add_u32(bb, "ccfs0", b->ccfs0);
	blobmsg_add_u32(bb, "ccfs1", b->ccfs1);
	//wifi_security_str(b->sec, b->enc, b->g_enc, sec_str);
	wifi_security_str(b->security, sec_str, sizeof(sec_str));

	etostr(b->rsn.pair_ciphers, cstr, sizeof(cstr), 14, wifi_cipherstr);

	blobmsg_add_string(bb, "encryption", sec_str);
	blobmsg_add_string(bb, "ciphers", cstr);
	wifi_print_band(bb, b->band);
	blobmsg_add_u32(bb, "rssi", b->rssi);
	//blobmsg_add_u32(bb, "snr", b->snr);
	snprintf(std_buf2 + strlen(std_buf2), 31, "%s",
		etostr(b->oper_std, std_buf, sizeof(std_buf), WIFI_NUM_STD, standard_str));
	blobmsg_add_string(bb, "standard", std_buf2);
	if (!b->bss_color_disabled)
		blobmsg_add_u32(bb, "bss_color", b->bss_color);
	blobmsg_add_u32(bb, "load_stas", b->load.sta_count);
	blobmsg_add_u32(bb, "load_utilization", b->load.utilization);
	blobmsg_add_u32(bb, "load_available", b->load.available);

	wifi_print_radio_caps(bb, &b->caps, WIFI_MODE_AP);

	if (detail) {
		struct wifi_bss_detail *bx =
					(struct wifi_bss_detail *)buf;
		char iebuf[1024] = {0};
		int i;

		for (i = 0; i < bx->ielen; i++) {
			sprintf(iebuf + strlen(iebuf), "%02x", bx->ie[i]);
		}
		blobmsg_add_string(bb, "ielist", iebuf);
	}
}

int wl_scanresults(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;
	struct blob_attr *tb[__SCANRES_MAX];
	struct wifi_bss scanres[NUM_SCANRES] = {0};
	struct wifi_bss *bsss = scanres;
	struct blob_buf bb = {0};
	uint8_t bssid[6] = {0};
	const char *radioname;
	int num = NUM_SCANRES;
	bool detail = false;
	enum wifi_band band;
	bool cache = true;	/* default - return cached results from last scan */
	const char *radio;
	void *a, *t;
	int i;


	blobmsg_parse(wl_scanres_policy, __SCANRES_MAX, tb, blob_data(msg),
		      blob_len(msg));

	radioname = ubus_objname_to_ifname(obj);
	UNUSED(radioname);
	radio = wdev->phy;
	band = wdev->band;

	if (tb[SCANRES_BSSID]) {
		struct wifi_bss_detail bx;
		char bssid_str[18] = {0};

		strncpy(bssid_str, blobmsg_data(tb[SCANRES_BSSID]),
			sizeof(bssid_str)-1);

		if (!hwaddr_aton(bssid_str, bssid)) {
			wifimngr_err("%s(): Invalid bssid\n", __func__);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		detail = true;
		memset(&bx, 0, sizeof(bx));
		if (wifi_get_bss_scan_result(radio, bssid, &bx) != 0)
			return UBUS_STATUS_UNKNOWN_ERROR;

		blob_buf_init(&bb, 0);
		wl_scanresult_print(&bb, &bx, detail);
		ubus_send_reply(ctx, req, bb.head);
		blob_buf_free(&bb);

		return UBUS_STATUS_OK;
	}

	blob_buf_init(&bb, 0);
	a = blobmsg_open_array(&bb, "accesspoints");

#if WIFI_CACHE_SCANRESULTS
	if (tb[SCANRES_CACHE])
		cache = blobmsg_get_bool(tb[SCANRES_CACHE]);

	if (cache) {
		for (i = 0; i < wdev->num_ch; i++) {
			if (!wdev->ch[i].num_scanres)
				continue;

			for (int j = 0; j < wdev->ch[i].num_scanres; j++) {
				t = blobmsg_open_table(&bb, "");
				wl_scanresult_print(&bb, &wdev->ch[i].scanres[j], detail);
				blobmsg_add_u32(&bb, "elapsed", difftime(time(NULL), wdev->ch[i].scanres_ts));
				blobmsg_close_table(&bb, t);
			}
		}
	}
#else
	wifimngr_trace("WIFI_CACHE_SCANRESULTS is not set\n");
	cache = false;	/* fetch from kernel */
#endif /* WIFI_CACHE_SCANRESULTS */

	if (!cache) {
		int ret;

		ret = wifi_get_band_scan_results(radio, band, bsss, &num);
		if (ret) {
			blob_buf_free(&bb);
			return UBUS_STATUS_UNKNOWN_ERROR;
		}

		for (i = 0; i < num; i++) {
			t = blobmsg_open_table(&bb, "");
			wl_scanresult_print(&bb, &bsss[i], detail);
			blobmsg_close_table(&bb, t);
		}
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

/**
 * By default, CS includes all supported opclass and channels supported by radio.
 * Exception list can be provided via one of the following ways -
 *
 * == exclude DFS channels.
 * ubus call wifi.radio.<radioname> autochannel '{"exclude_dfs": true}'
 *
 * == exclude non-PSC channels in 6GHz.
 * ubus call wifi.radio.<radioname> autochannel '{"exclude_6ghz_non_psc": true}'
 *
 * == exclude channels 120, 124, 128 and 144 for all bandwidths.
 * ubus call wifi.radio.<radioname> autochannel '{"exclude_channels":[120,124,128,144]}'
 *
 * == exlucde channels belonging to opclasses 128 and 129.
 * ubus call wifi.radio.<radioname> autochannel '{"exclude_opclass":[128,129]}'
 *
 * == exclude channels 120,124,128 and channels for opclass 115.
 * ubus call wifi.radio.<radioname> autochannel '{"exclude_channels":[120,124,128], "exclude_opclass":[115]}'
 *
 */
int wl_autochannel(struct ubus_context *ctx, struct ubus_object *obj,
		   struct ubus_request_data *req, const char *method,
		   struct blob_attr *msg)
{
	struct blob_attr *tb[NUM_ACS_PARAMS];
	enum wifi_band band = BAND_2;
	struct acs_param p = {0};
	struct blob_buf bb = {0};
	const char *radioname;
	const char *device;
	struct acs_param_channel *ach = NULL, *ach2 = NULL;
	size_t total_num_channels = 0;
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	device = wdev->phy;
	if (!msg || !blob_data(msg) || !blob_len(msg)) {
		/* legacy usage with no args */
		ret = wifi_acs(device, NULL);

		blob_buf_init(&bb, 0);
		blobmsg_add_u32(&bb, "code", ret);
		if (ret == 0) {
			enum wifi_bw bw;
			uint32_t ch;

			ret = wifi_get_channel(device, &ch, &bw);
			if (ret == 0)
				blobmsg_add_u32(&bb, "new_channel", ch);
			blobmsg_add_string(&bb, "status", "success");
		} else
			blobmsg_add_string(&bb, "status", "error");

		ubus_send_reply(ctx, req, bb.head);
		blob_buf_free(&bb);

		return 	UBUS_STATUS_OK;
	}

	radioname = ubus_objname_to_ifname(obj);
	UNUSED(radioname);
	band = wdev->band;

	blobmsg_parse(wl_acs_policy, NUM_ACS_PARAMS, tb, blob_data(msg), blob_len(msg));

	if (tb[ACS_PARAM_MODE]) {
		const char *mode = blobmsg_get_string(tb[ACS_PARAM_MODE]);

		if (!strcmp(mode, "disable")) {
			p.mode = WIFI_ACS_MODE_DISABLE;
		} else if (!strcmp(mode, "enable")) {
			p.mode = WIFI_ACS_MODE_ENABLE;
		} else if (!strcmp(mode, "monitor")) {
			p.mode = WIFI_ACS_MODE_MONITOR;
		} else {
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
	}

	if (tb[ACS_PARAM_EXCLUDE_DFS])
		p.exclude_dfs = blobmsg_get_bool(tb[ACS_PARAM_EXCLUDE_DFS]);

	if (tb[ACS_PARAM_EXCLUDE_6GHZ_NON_PSC])
		p.exclude_6ghz_non_psc = blobmsg_get_bool(tb[ACS_PARAM_EXCLUDE_6GHZ_NON_PSC]);


	if (tb[ACS_PARAM_EXCLUDE_OPCLASS]) {
		int num_opclass = blobmsg_check_array(tb[ACS_PARAM_EXCLUDE_OPCLASS], BLOBMSG_TYPE_INT32);
		struct blob_attr *cur;
		int i = 0;
		int rem;

		if (num_opclass) {
			blobmsg_for_each_attr(cur, tb[ACS_PARAM_EXCLUDE_OPCLASS], rem) {
				uint32_t channels[32] = {0};
				int num_channels = 32;
				uint32_t opclass = 0;
				enum wifi_bw bw;

				if (blobmsg_type(cur) != BLOBMSG_TYPE_INT32) {
					free(ach);
					return UBUS_STATUS_INVALID_ARGUMENT;
				}

				opclass = blobmsg_get_u32(cur);
				ret = wifi_opclass_to_channels(opclass, &num_channels, channels);
				if (ret) {
					free(ach);
					return UBUS_STATUS_INVALID_ARGUMENT;
				}

				ret = wifi_opclass_bandwidth(opclass, &bw);
				if (ret || bw == BW_UNKNOWN) {
					free(ach);
					return UBUS_STATUS_INVALID_ARGUMENT;
				}

				if (num_channels == 0)
					continue;

				ach2 = calloc((total_num_channels + num_channels), sizeof(struct acs_param_channel));
				if (!ach2) {
					free(ach);
					return -1;
				}

				if (ach) {
					memcpy(ach2, ach, total_num_channels * sizeof(struct acs_param_channel));
					free(ach);
				}
				ach = ach2;

				for (i = 0; i < num_channels; i++) {
					ach[total_num_channels + i].channel = channels[i];
					ach[total_num_channels + i].band = band;
					ach[total_num_channels + i].weight = 0;
					ach[total_num_channels + i].bw = bw;

					wifimngr_dbg("%s: Exclude_opclass channel[%d] = %u, band = %d\n",
						     __func__, i,
						     ach[total_num_channels + i].channel,
						     ach[total_num_channels + i].band);
				}
				total_num_channels += num_channels;
			}
		}

		p.num_channels = total_num_channels;
		p.channels = ach;
	}

	if (tb[ACS_PARAM_EXCLUDE_CHANNELS]) {
		int num_channels = blobmsg_check_array(tb[ACS_PARAM_EXCLUDE_CHANNELS], BLOBMSG_TYPE_INT32);
		struct blob_attr *cur;
		int i = 0;
		int rem;

		if (num_channels) {
			ach2 = calloc((total_num_channels + num_channels), sizeof(struct acs_param_channel));
			if (!ach2) {
				free(ach);
				return -1;
			}

			if (ach) {
				memcpy(ach2, ach, total_num_channels * sizeof(struct acs_param_channel));
				free(ach);
			}
			ach = ach2;

			blobmsg_for_each_attr(cur, tb[ACS_PARAM_EXCLUDE_CHANNELS], rem) {
				if (blobmsg_type(cur) != BLOBMSG_TYPE_INT32) {
					free(ach);
					return UBUS_STATUS_INVALID_ARGUMENT;
				}

				ach[total_num_channels + i].channel = blobmsg_get_u32(cur);
				ach[total_num_channels + i].band = band;
				ach[total_num_channels + i].weight = 0;
				ach[total_num_channels + i].bw = BW_UNKNOWN;
				wifimngr_dbg("%s: exclude channel[%d] = %u, band = %d\n",
					     __func__, i,
					     ach[total_num_channels + i].channel,
					     ach[total_num_channels + i].band);
				i++;
			}

			total_num_channels += num_channels;
		}

		p.num_channels = total_num_channels;
		p.channels = ach;
	}

	ret = wifi_acs(device, &p);

	blob_buf_init(&bb, 0);
	blobmsg_add_u32(&bb, "code", ret);
	blobmsg_add_string(&bb, "status", !ret ? "success" : "error");
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	free(ach);

	return 	UBUS_STATUS_OK;
}

int wl_start_cac(struct ubus_context *ctx, struct ubus_object *obj,
		 struct ubus_request_data *req, const char *method,
		 struct blob_attr *msg)
{
	enum wifi_cac_method m = WIFI_CAC_MIMO_REDUCED;
	struct blob_attr *tb[__CAC_MAX];
	enum wifi_bw bw = BW20;
	const char *ifname;
	uint32_t ch = 0;
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;


	ifname = wdev->phy;

	blobmsg_parse(wl_cac_policy, __CAC_MAX, tb, blob_data(msg), blob_len(msg));

	if (!tb[CAC_CHANNEL])
		return UBUS_STATUS_INVALID_ARGUMENT;

	ch = blobmsg_get_u32(tb[CAC_CHANNEL]);

	if (tb[CAC_BANDWIDTH]) {
		int bandwidth;

		bandwidth = blobmsg_get_u32(tb[CAC_BANDWIDTH]);
		if (bandwidth == 20)
			bw = BW20;
		else if (bandwidth == 40)
			bw = BW40;
		else if (bandwidth == 80)
			bw = BW80;
		else if (bandwidth == 160)
			bw = BW160;
		else
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[CAC_METHOD]) {
		m = blobmsg_get_u32(tb[CAC_METHOD]);
		if (m < 0 || m > WIFI_CAC_TIME_SLICED)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	ret = wifi_start_cac(ifname, ch, bw, m);

	return ret == 0 ? UBUS_STATUS_OK : UBUS_STATUS_UNKNOWN_ERROR;
}

int wl_stop_cac(struct ubus_context *ctx, struct ubus_object *obj,
		 struct ubus_request_data *req, const char *method,
		 struct blob_attr *msg)
{
	struct blob_attr *tb[__CAC_MAX];
	const char *ifname;
	uint32_t channel = 0;
	enum wifi_bw bw = BW20;
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	ifname = wdev->phy;
	blobmsg_parse(wl_cac_policy, __CAC_MAX, tb, blob_data(msg), blob_len(msg));

	if (tb[CAC_CHANNEL])
		channel = blobmsg_get_u32(tb[CAC_CHANNEL]);

	if (tb[CAC_BANDWIDTH]) {
		int bandwidth;

		bandwidth = blobmsg_get_u32(tb[CAC_BANDWIDTH]);
		switch (bandwidth) {
		case 40:
			bw = BW40;
			break;
		case 80:
			bw = BW80;
			break;
		case 160:
			bw = BW160;
			break;
		default:
			bw = BW20;
			break;
		}
	}

	ret = wifi_stop_cac(ifname, channel, bw);

	return ret == 0 ? UBUS_STATUS_OK : UBUS_STATUS_UNKNOWN_ERROR;
}

int wl_simulate_radar(struct ubus_context *ctx, struct ubus_object *obj,
		     struct ubus_request_data *req, const char *method,
		     struct blob_attr *msg)
{
	struct wifi_radar_args radar = { 0 };
	struct blob_attr *tb[__RADAR_MAX];
	const char *ifname;
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	ifname = wdev->phy;
	blobmsg_parse(wl_radar_policy, __RADAR_MAX, tb, blob_data(msg), blob_len(msg));

	if (tb[RADAR_CHANNEL]) {
		radar.channel = blobmsg_get_u32(tb[RADAR_CHANNEL]);
	}

	if (tb[RADAR_BANDWIDTH]) {
		int bandwidth;

		bandwidth = blobmsg_get_u32(tb[RADAR_BANDWIDTH]);
		switch (bandwidth) {
		case 20:
			radar.bandwidth = BW20;
			break;
		case 40:
			radar.bandwidth = BW40;
			break;
		case 80:
			radar.bandwidth = BW80;
			break;
		case 160:
			radar.bandwidth = BW160;
			break;
		default:
			radar.bandwidth = BW_UNKNOWN;
		}
	}

	if (tb[RADAR_TYPE]) {
		radar.type = blobmsg_get_u32(tb[RADAR_TYPE]);
	}

	if (tb[RADAR_SUBBAND_MASK]) {
		/* subband mask can be from range 0 - 0x0FF
		 * here is the rule:
		 * bit:         3       2       1      0
		 * low freq | 20MHz | 20MHz | 20MHz| 20MHz | high freq
		 *          |              80MHz           |
		 */
		radar.subband_mask = blobmsg_get_u32(tb[RADAR_SUBBAND_MASK]);
	}

	ret = wifi_simulate_radar(ifname, &radar);

	return ret == 0 ? UBUS_STATUS_OK : UBUS_STATUS_UNKNOWN_ERROR;
}

int wl_add_iface(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
#define ADD_IFACE_ARGS_MAX	17

	enum wifi_mode mode = WIFI_MODE_UNKNOWN;
	struct blob_attr *tb[__ADD_IFACE_MAX];
	char argv[ADD_IFACE_ARGS_MAX * 2 + 1][128] = { 0 };
	char *argvp[ADD_IFACE_ARGS_MAX * 2 + 1] = { 0 };
	bool config = true;
	const char *device;
	int i = 0;
	int ret;
	int rem;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	blobmsg_parse(add_iface_policy, __ADD_IFACE_MAX, tb, blob_data(msg),
						blob_len(msg));

	device = wdev->phy;

	/* add the 'device' option */
	strcpy(argv[i], "device");
	argvp[i] = argv[i];
	strncpy(argv[++i], device, 127);
	argvp[i] = argv[i];
	i++;


	if (tb[ADD_IFACE_CONF])
		config = blobmsg_get_bool(tb[ADD_IFACE_CONF]);


	if (tb[ADD_IFACE_ARGS]) {
		struct blob_attr *attr;

		blobmsg_for_each_attr(attr, tb[ADD_IFACE_ARGS], rem) {
			if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
				return UBUS_STATUS_INVALID_ARGUMENT;


			wifimngr_dbg("name = %s\n", blobmsg_name(attr));
			strncpy(argv[i], blobmsg_name(attr), 127);
			argvp[i] = argv[i];
			memcpy(argv[i + 1], blobmsg_data(attr),
						blobmsg_data_len(attr));
			argvp[i + 1] = argv[i + 1];

			if (!strcmp(argvp[i], "mode")) {
				if (!strcmp(argvp[i + 1], "ap"))
					mode = WIFI_MODE_AP;
				else if (!strcmp(argvp[i + 1], "sta"))
					mode = WIFI_MODE_STA;
				else if (!strcmp(argvp[i + 1], "monitor"))
					mode = WIFI_MODE_MONITOR;
				else
					return UBUS_STATUS_INVALID_ARGUMENT;
			}

			i += 2;
			if (i > 31)
				break;
		}
	}

	/* if mode is not specified, assume "ap" */
	if (mode == WIFI_MODE_UNKNOWN) {
		mode = WIFI_MODE_AP;
		strcpy(argv[i], "mode");
		argvp[i] = argv[i];
		strcpy(argv[++i], "ap");
		argvp[i] = argv[i];
	}

	if (config) {
		ret = uci_add_wifi_iface(argvp);
		if (!ret)
			wifimngr_dbg("Successfully added wifi-iface\n");
	}

	ret = wifi_add_iface(device, mode, argvp);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int wl_del_iface(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct blob_attr *tb[__DEL_IFACE_MAX];
	bool config = true;
	const char *device;
	char ifname[16] = {0};
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	blobmsg_parse(del_iface_policy, __DEL_IFACE_MAX, tb, blob_data(msg),
						blob_len(msg));

	device = wdev->phy;

	if (!tb[DEL_IFACE_IFNAME])
		return UBUS_STATUS_INVALID_ARGUMENT;

	strncpy(ifname, blobmsg_data(tb[DEL_IFACE_IFNAME]), 15);

	if (tb[DEL_IFACE_CONF])
		config = blobmsg_get_bool(tb[DEL_IFACE_CONF]);

	if (config) {
		ret = uci_del_wifi_iface(ifname);
		if (!ret)
			wifimngr_dbg("Successfully removed wifi-iface\n");
	}

	ret = wifi_del_iface(device, ifname);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

static char *dfs_state_str(enum dfs_state state)
{
	switch (state) {
	case WIFI_DFS_STATE_USABLE:
		return "usable";
	case WIFI_DFS_STATE_CAC:
		return "cac";
	case WIFI_DFS_STATE_UNAVAILABLE:
		return "unavailable";
	case WIFI_DFS_STATE_AVAILABLE:
		return "available";
	default:
		break;
	}

	return "unknown";
}

int wl_channels_info(struct ubus_context *ctx, struct ubus_object *obj,
		     struct ubus_request_data *req, const char *method,
		     struct blob_attr *msg)
{
	struct chan_entry channels[32] = {};
	int num = ARRAY_SIZE(channels);
	struct chan_entry *entry;
	struct blob_buf bb;
	const char *device;
	const char *radioname;
	enum wifi_band band;
	bool multiband = false;
	void *a;
	void *t;
	int ret;
	int i;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;


	radioname = ubus_objname_to_ifname(obj);
	UNUSED(radioname);
	device = wdev->phy;
	band = wdev->band;

	wifi_radio_is_multiband(device, &multiband);
	if (multiband)
		ret = wifi_channels_info_band(device, band, channels, &num);
	else
		ret = wifi_channels_info(device, channels, &num);

	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	a = blobmsg_open_array(&bb, "channels");
	for (i = 0; i < num; i++) {
		entry = &channels[i];
		t = blobmsg_open_table(&bb, "");
		blobmsg_add_u32(&bb, "channel", entry->channel);
		blobmsg_add_u32(&bb, "freq", entry->freq);
		blobmsg_add_u32(&bb, "noise", entry->noise);
		if (entry->dfs) {
			blobmsg_add_u8(&bb, "radar", entry->dfs);
			blobmsg_add_string(&bb, "dfs_state", dfs_state_str(entry->dfs_state));
			blobmsg_add_u32(&bb, "cac_time", entry->cac_time);
			if (entry->dfs_state == WIFI_DFS_STATE_UNAVAILABLE)
				blobmsg_add_u32(&bb, "nop_time", entry->nop_time);
		}

		wifi_print_radio_diagnostics(&bb, &entry->survey);
		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int is_opclass_chgrp_supported(uint32_t *ctrl_channels, int num, uint32_t *supp_channels)
{
	for (int k = 0; k < 32 && ctrl_channels[k] != 0; k++) {
		int s;

		for (s = 0; s < num; s++) {
			if (supp_channels[s] == ctrl_channels[k])
				break;
		}

		if (s == num)
			return 0;
	}

	return 1;
}

int wl_channels(struct ubus_context *ctx, struct ubus_object *obj,
		 struct ubus_request_data *req, const char *method,
		 struct blob_attr *msg)
{
	struct wifi_opclass supp_opclass[64] = {0};
	size_t num_opclass = ARRAY_SIZE(supp_opclass);
	struct blob_attr *tb[NUM_DUMP_CHANNELS];
	uint32_t supp_channels[64] = {0};
	enum wifi_band band = BAND_2;
	int num_supp_channels = 64;
	uint8_t channels[256] = {0};
	struct blob_buf bb = {0};
	bool multiband = false;
	const char *radioname;
	const char *ifname;
	uint32_t channel;
	enum wifi_bw bw;
	void *t5;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;


	blob_buf_init(&bb, 0);
	radioname = ubus_objname_to_ifname(obj);
	UNUSED(radioname);
	ifname = wdev->phy;
	band = wdev->band;

	blobmsg_parse(wl_dump_channels_policy, NUM_DUMP_CHANNELS, tb,
		      blob_data(msg), blob_len(msg));

	if (tb[DUMP_CHANNELS_FOR_BANDWIDTH]) {
		int bandwidth;

		bandwidth = blobmsg_get_u32(tb[DUMP_CHANNELS_FOR_BANDWIDTH]);
		if (bandwidth == 20)
			bw = BW20;
		else if (bandwidth == 40)
			bw = BW40;
		else if (bandwidth == 80)
			bw = BW80;
		else if (bandwidth == 160)
			bw = BW160;
		else if (bandwidth == 8080)
			bw = BW8080;
		else if (bandwidth == 320)
			bw = BW320;
		else {
			blob_buf_free(&bb);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
	} else {
		/* use currently set bandwidth */
		wifi_radio_is_multiband(ifname, &multiband);
		if (multiband) {
			wifi_get_band_channel(ifname, band, &channel, &bw);
		} else {
			wifi_get_channel(ifname, &channel, &bw);
		}

		/* in case of no nedev use BW20 */
		if (bw == BW_UNKNOWN)
			bw = BW20;
	}

	wifi_get_opclass_e4table(band, (1 << bw), &num_opclass, supp_opclass);
	wifi_get_supp_channels(ifname, supp_channels, &num_supp_channels, "", band, bw);

	for (int i = 0; i < num_opclass; i++) {
		for (int j = 0; j < supp_opclass[i].opchannel.num; j++) {
			if (is_opclass_chgrp_supported(supp_opclass[i].opchannel.ch[j].ctrl_channels,
							num_supp_channels, supp_channels)) {
				for (int k = 0; k < 32 && supp_opclass[i].opchannel.ch[j].ctrl_channels[k] != 0; k++) {
					channels[supp_opclass[i].opchannel.ch[j].ctrl_channels[k]] = 1;
				}
			}
		}
	}

	t5 = blobmsg_open_array(&bb, "channels");
	for (int i = 0; i < sizeof(channels); i++) {
		if (channels[i] == 1) {
			blobmsg_add_u32(&bb, "", i);
		}
	}
	blobmsg_close_array(&bb, t5);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int wl_opclass_preferences(struct ubus_context *ctx, struct ubus_object *obj,
			   struct ubus_request_data *req, const char *method,
			   struct blob_attr *msg)
{
	struct wifi_opclass supp_opclass[64] = {0};
	int num_opclass = ARRAY_SIZE(supp_opclass);
	void *a, *t, *aa, *tt, *aaa;
	struct blob_buf bb;
	const char *radio;
	const char *radioname;
	enum wifi_band band;
	int i, j, k;
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	radioname = ubus_objname_to_ifname(obj);
	UNUSED(radioname);
	radio = wdev->phy;
	band = wdev->band;

	ret = wifi_get_band_opclass_preferences(radio, band, supp_opclass, &num_opclass);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	a = blobmsg_open_array(&bb, "pref_opclass");
	for (i = 0; i < num_opclass; i++) {
		t = blobmsg_open_table(&bb, "");
		blobmsg_add_u32(&bb, "opclass", supp_opclass[i].g_opclass);
		blobmsg_add_u32(&bb, "bandwidth", bw_value(supp_opclass[i].bw));
		blobmsg_add_u32(&bb, "txpower", supp_opclass[i].opchannel.txpower);
		aa = blobmsg_open_array(&bb, "channels");
		for (j = 0; j < supp_opclass[i].opchannel.num; j++) {
			tt = blobmsg_open_table(&bb, "");
			blobmsg_add_u32(&bb, "channel", supp_opclass[i].opchannel.ch[j].channel);
			blobmsg_add_u32(&bb, "score", supp_opclass[i].opchannel.ch[j].score);
			blobmsg_add_u32(&bb, "dfs", supp_opclass[i].opchannel.ch[j].dfs);
			if (supp_opclass[i].opchannel.ch[j].dfs) {
				blobmsg_add_string(&bb, "dfs_state", dfs_state_str(supp_opclass[i].opchannel.ch[j].dfs_state));
				blobmsg_add_u32(&bb, "cac_time", supp_opclass[i].opchannel.ch[j].cac_time);
				if (supp_opclass[i].opchannel.ch[j].dfs_state == WIFI_DFS_STATE_UNAVAILABLE)
					blobmsg_add_u32(&bb, "nop_time", supp_opclass[i].opchannel.ch[j].nop_time);
			}
			aaa = blobmsg_open_array(&bb, "ctrl_channels");
			for (k = 0; k < ARRAY_SIZE(supp_opclass[i].opchannel.ch[j].ctrl_channels); k++) {
				if (supp_opclass[i].opchannel.ch[j].ctrl_channels[k])
					blobmsg_add_u32(&bb, "", supp_opclass[i].opchannel.ch[j].ctrl_channels[k]);
			}
			blobmsg_close_array(&bb, aaa);
			blobmsg_close_table(&bb, tt);
		}
		blobmsg_close_array(&bb, aa);
		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int sta_disconnect(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__STA_DISCONNECT_MAX];
	uint8_t sta[6] = {0};
	char sta_str[18] = {0};
	const char *ifname;
	uint16_t reason = 0;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(sta_disconnect_policy, __STA_DISCONNECT_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (!(tb[STA_DISCONNECT_STA])) {
		wifimngr_err("%s(): sta mac not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	strncpy(sta_str, blobmsg_data(tb[STA_DISCONNECT_STA]), sizeof(sta_str)-1);
	if (hwaddr_aton(sta_str, sta) == NULL) {
		wifimngr_err("%s(): Invalid address format. Use 00:10:22:..\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[STA_DISCONNECT_REASON])
		reason = blobmsg_get_u32(tb[STA_DISCONNECT_REASON]);


	if (wifi_disconnect_sta(ifname, sta, reason) != 0) {
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

int sta_probe(struct ubus_context *ctx, struct ubus_object *obj,
	      struct ubus_request_data *req, const char *method,
	      struct blob_attr *msg)
{
	struct blob_attr *tb[__STA_MONITOR_MAX];
	uint8_t sta[6] = {0};
	char sta_str[18] = {0};
	const char *ifname;


	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(sta_monitor_policy, __STA_MONITOR_MAX, tb, blob_data(msg),
		      blob_len(msg));

	if (!(tb[STA_MONITOR_ADDR])) {
		wifimngr_err("%s(): sta mac not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	strncpy(sta_str, blobmsg_data(tb[STA_MONITOR_ADDR]), sizeof(sta_str)-1);
	if (hwaddr_aton(sta_str, sta) == NULL) {
		wifimngr_err("%s(): Invalid address format. Use 00:10:22:..\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (wifi_probe_sta(ifname, sta) != 0) {
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

int sta_monitor_add_del(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg, bool enable)
{
	struct blob_attr *tb[__STA_MONITOR_MAX];
	struct wifi_monsta_config cfg = {};
	const char *ifname;
	uint8_t sta[6] = {0};

	cfg.enable = enable;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(sta_monitor_policy, __STA_MONITOR_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (!(tb[STA_MONITOR_ADDR])) {
		wifimngr_err("%s(): sta mac not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (hwaddr_aton(blobmsg_data(tb[STA_MONITOR_ADDR]), sta) == NULL) {
		wifimngr_err("%s(): Invalid address\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (wifi_monitor_sta(ifname, sta, &cfg) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int sta_monitor_add(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	return sta_monitor_add_del(ctx, obj, req, method, msg, true);
}

int sta_monitor_del(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	return sta_monitor_add_del(ctx, obj, req, method, msg, false);
}

static void
sta_monitor_add_entry(struct blob_buf *bb, struct wifi_monsta *mon)
{
	char sta_str[20] = {};
	void *t;
	void *a;
	int i;

	snprintf(sta_str, 19, "%02x:%02x:%02x:%02x:%02x:%02x", MAC2STR(mon->macaddr));

	t = blobmsg_open_table(bb, "sta");
	blobmsg_add_string(bb, "macaddr", sta_str);
	blobmsg_add_u32(bb, "seen", mon->last_seen);
	blobmsg_add_u32(bb, "rssi_avg", mon->rssi_avg);

	a = blobmsg_open_array(bb, "rssi");
	for (i = 0; i < ARRAY_SIZE(mon->rssi); i++)
		blobmsg_add_u32(bb, "", mon->rssi[i]);
	blobmsg_close_array(bb, a);

	blobmsg_close_table(bb, t);
}

int sta_monitor_get(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	struct blob_attr *tb[__STA_MONITOR_MAX];
	struct blob_buf bb;
	const char *ifname;
	struct wifi_monsta stamon = {};
	struct wifi_monsta stas[128] = {};
	int num = ARRAY_SIZE(stas);
	uint8_t sta[6] = {0};
	void *array;
	int i;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(sta_monitor_policy, __STA_MONITOR_MAX, tb,
					blob_data(msg), blob_len(msg));

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	if ((tb[STA_MONITOR_ADDR])) {
		if (hwaddr_aton(blobmsg_data(tb[STA_MONITOR_ADDR]), sta) == NULL) {
			wifimngr_err("%s(): Invalid address\n", __func__);
			blob_buf_free(&bb);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		if (wifi_get_monitor_sta(ifname, sta, &stamon) != 0) {
			blob_buf_free(&bb);
			return UBUS_STATUS_UNKNOWN_ERROR;
		}

		sta_monitor_add_entry(&bb, &stamon);
	} else {
		if (wifi_get_monitor_stas(ifname, stas, &num)) {
			blob_buf_free(&bb);
			return UBUS_STATUS_UNKNOWN_ERROR;
		}

		array = blobmsg_open_array(&bb, "stations");
		for (i = 0; i < num; i++)
			sta_monitor_add_entry(&bb, &stas[i]);
		blobmsg_close_array(&bb, array);
	}

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int subscribe_unsubscribe_frame(struct ubus_context *ctx, struct ubus_object *obj,
				struct ubus_request_data *req, const char *method,
				struct blob_attr *msg, bool subscribe)
{
	struct blob_attr *tb[__SUBSCRIBE_FRAME_MAX];
	const char *ifname;
	unsigned int stype;
	unsigned int type;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(subscribe_frame_policy, __SUBSCRIBE_FRAME_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (!tb[SUBSCRIBE_FRAME_TYPE])
		return UBUS_STATUS_INVALID_ARGUMENT;
	if (!tb[SUBSCRIBE_FRAME_SUBTYPE])
		return UBUS_STATUS_INVALID_ARGUMENT;

	type = blobmsg_get_u32(tb[SUBSCRIBE_FRAME_TYPE]);
	stype = blobmsg_get_u32(tb[SUBSCRIBE_FRAME_SUBTYPE]);

	if (subscribe) {
		if (wifi_subscribe_frame(ifname, type, stype))
			return UBUS_STATUS_UNKNOWN_ERROR;
	} else {
		if (wifi_unsubscribe_frame(ifname, type, stype))
			return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

int subscribe_frame(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	return subscribe_unsubscribe_frame(ctx, obj, req, method, msg, true);
}

int unsubscribe_frame(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	return subscribe_unsubscribe_frame(ctx, obj, req, method, msg, false);
}

int ap_measure_link(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	struct blob_attr *tb[__LINKMEAS_MAX];
	char macstr[18] = {0};
	uint8_t sta[6] = {0};
	const char *ifname;
	int ret;


	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(linkmeas_policy, __LINKMEAS_MAX, tb, blob_data(msg),
		      blob_len(msg));

	if (!(tb[LINKMEAS_STA]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	strncpy(macstr, blobmsg_data(tb[LINKMEAS_STA]), sizeof(macstr)-1);
	if (hwaddr_aton(macstr, sta) == NULL)
		return UBUS_STATUS_INVALID_ARGUMENT;

	ret = wifi_link_measure(ifname, sta);
	if (ret != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int ap_mbo_disallow_assoc(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg)
{
	struct blob_attr *tb[__MBO_DISALLOW_ASSOC_MAX];
	const char *ifname;
	int ret;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(mbo_disallow_assoc_policy, __MBO_DISALLOW_ASSOC_MAX, tb, blob_data(msg),
		      blob_len(msg));

	if (!(tb[MBO_DISALLOW_ASSOC_REASON]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	ret = wifi_mbo_disallow_assoc(ifname, blobmsg_get_u32(tb[MBO_DISALLOW_ASSOC_REASON]));
	if (ret != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int ap_bss_up(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg)
{
	const char *ifname;
	int ret;

	ifname = ubus_ap_to_ifname(obj);

	ret = wifi_ap_set_state(ifname, true);
	if (ret != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int ap_bss_down(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg)
{
	const char *ifname;
	int ret;

	ifname = ubus_ap_to_ifname(obj);

	ret = wifi_ap_set_state(ifname, false);
	if (ret != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int ap_send_action(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg)
{
	const char *ifname;
	struct blob_attr *tb[__ACTION_MAX];
	struct wifi_frame_arg arg = {0};
	int ret;
	uint8_t dst[6] = {0};
	uint8_t src[6] = {0};
	uint32_t wait = 0;
	uint32_t freq = 0;
	size_t framelen = 0;
	uint8_t *frame;
	uint64_t cookie = 0;
	char *framestr;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(send_action_policy, __ACTION_MAX, tb, blob_data(msg),
		      blob_len(msg));

	if (!(tb[ACTION_DST]) || !(tb[ACTION_FRAME]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	if (tb[ACTION_WAIT])
		wait = blobmsg_get_u32(tb[ACTION_WAIT]);

	if (tb[ACTION_FREQ])
		freq = blobmsg_get_u32(tb[ACTION_FREQ]);

	framestr = blobmsg_data(tb[ACTION_FRAME]);
	framelen = strlen(framestr) / 2;
	frame = calloc(1, framelen);
	if (!frame)
		return UBUS_STATUS_UNKNOWN_ERROR;

	if (!strtob(framestr, framelen, frame)) {
		free(frame);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (hwaddr_aton(blobmsg_data(tb[ACTION_DST]), dst) == NULL) {
		ret = UBUS_STATUS_INVALID_ARGUMENT;
		goto out;
	}

	memcpy(arg.dst, dst, 6);
	arg.duration = wait;
	arg.freq = freq;

	if (tb[ACTION_SRC]) {
		if (hwaddr_aton(blobmsg_data(tb[ACTION_DST]), src) == NULL) {
			ret = UBUS_STATUS_INVALID_ARGUMENT;
			goto out;
		}

		memcpy(arg.src, src, 6);
	}

	ret = wifi_send_action_frame(ifname, &arg, frame, framelen, &cookie);
	if (ret != 0) {
		ret = UBUS_STATUS_UNKNOWN_ERROR;
		goto out;
	}

out:
	free(frame);
	return ret;
}

int wl_sta_dpp_listen(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg)
{
	const char *ifname;
	struct blob_attr *tb[__DPP_LISTEN_MAX];
	int ret;
	uint32_t freq = 0;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(dpp_listen_policy, __DPP_LISTEN_MAX, tb, blob_data(msg),
		      blob_len(msg));

	if (!(tb[DPP_LISTEN_FREQ]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	freq = blobmsg_get_u32(tb[DPP_LISTEN_FREQ]);

	ret = wifi_dpp_listen(ifname, freq);
	if (ret != 0)
		ret = UBUS_STATUS_UNKNOWN_ERROR;

	return ret;
}

int wl_sta_dpp_stop_listen(struct ubus_context *ctx, struct ubus_object *obj,
			  struct ubus_request_data *req, const char *method,
			  struct blob_attr *msg)
{
	const char *ifname;
	int ret;

	ifname = ubus_ap_to_ifname(obj);

	ret = wifi_dpp_stop_listen(ifname);
	if (ret != 0)
		ret = UBUS_STATUS_UNKNOWN_ERROR;

	return ret;
}

/*
 * This function receives the QoS map in the following format:
 * (dscp_exc_i, pcp_exc_i,)*(dscp_min_0,dscp_max_0),...,(dscp_min_7,dscp_max_7)
 * First tuples are up to 21 DSCP exceptions: the are made of specific DSCP
 * values and associated UP values.
 * The second group of tuples are DSCP minimum/DSCP maximum values for the UP
 * equal to the index of a tuple.
 *
 * Usage example:
 * ubus call wifi.ap.wlan0_1 set_qos_map '{ "set": \
 * 		[ 255,255,255,255,255,255,255,255,255,255,255,255,0,63,255,255 ] }'
 * This example assigns mandatory UP = 6 to practically all possible DSCP
 * values (range [0, 63]).
 */
int set_qos_map(struct ubus_context *ctx, struct ubus_object *obj,
                struct ubus_request_data *req, const char *method,
                struct blob_attr *msg)
{
	int ret;
	const char *ifname;
	struct blob_attr *tb[__AP_SET_QOS_MAP_SET_MAX];

#define QOS_MAP_MAX_EXCEPTION_COUNT (21)
#define QOS_MAP_MANDATORY_ELEMENT_COUNT (8)
#define QOS_MAP_PAIR_LEN (2)

	uint8_t smap[QOS_MAP_MAX_EXCEPTION_COUNT * QOS_MAP_PAIR_LEN +
		QOS_MAP_MANDATORY_ELEMENT_COUNT * QOS_MAP_PAIR_LEN];
	int smap_exp_len;
	int smap_len;
	struct blob_attr *attr;
	int rem;
	int i;
	struct dscp_exception *exc;
	size_t exc_count;
	struct dscp_pcp_map map;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(ap_set_qos_map_policy, __AP_SET_QOS_MAP_SET_MAX, tb,
	              blob_data(msg), blob_len(msg));

	if (!(tb[AP_SET_QOS_MAP_SET])) {
		wifimngr_err("%s(): QoS map is not specified\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	smap_exp_len = blobmsg_check_array(
		tb[AP_SET_QOS_MAP_SET], BLOBMSG_TYPE_INT32);
	if (smap_exp_len < QOS_MAP_MANDATORY_ELEMENT_COUNT * QOS_MAP_PAIR_LEN) {
		wifimngr_err("%s(): map doesn't have mandatory elements: %d\n",
		    __func__, smap_exp_len);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (smap_exp_len > sizeof(smap) / sizeof(*smap)) {
		wifimngr_err("%s(): input map is too big: %d\n", __func__,
			smap_exp_len);
		return UBUS_STATUS_INVALID_ARGUMENT;

	}

	exc_count = (smap_exp_len - QOS_MAP_MANDATORY_ELEMENT_COUNT *
	    QOS_MAP_PAIR_LEN) / QOS_MAP_PAIR_LEN;
	if (((smap_exp_len - QOS_MAP_MANDATORY_ELEMENT_COUNT *
	    QOS_MAP_PAIR_LEN) % QOS_MAP_PAIR_LEN) != 0) {
		wifimngr_err("%s(): number of DSCP exception elements is invalid\n",
			__func__);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	smap_len = 0;
	blobmsg_for_each_attr(attr, tb[AP_SET_QOS_MAP_SET], rem) {
		if (blobmsg_type(attr) != BLOBMSG_TYPE_INT32)
			return UBUS_STATUS_INVALID_ARGUMENT;

		smap[smap_len++] = blobmsg_get_u32(attr);
	}

	if (smap_len != smap_exp_len) {
		wifimngr_err("%s(): map is parsed incorrectly\n", __func__);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	exc = calloc(exc_count, sizeof(struct dscp_exception));
	if (exc == NULL) {
		wifimngr_err("%s(): failed to allocate memory for DSCP exceptions\n",
		    __func__);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	/* Convert flat sequence to map and exceptions */
	for (i = 0; i < smap_exp_len; ++i) {
		int val = smap[i];
		int idx = i % QOS_MAP_PAIR_LEN;

		if (i < (exc_count * QOS_MAP_PAIR_LEN)) {
			if (idx == 0) {
				exc[i / QOS_MAP_PAIR_LEN].dscp = val;
			} else {
				exc[i / QOS_MAP_PAIR_LEN + 1].output_pcp = val;
			}
		} else {
			int pcp = (i - (exc_count * QOS_MAP_PAIR_LEN)) / QOS_MAP_PAIR_LEN;

			if (idx == 0) {
				map.dscp_to_up[pcp].dscp_min = val;
			} else {
				map.dscp_to_up[pcp].dscp_max = val;
			}
		}
	}

	ret = wifi_ap_set_qos_map(ifname, &map, exc, exc_count);
	free(exc);
	if (ret != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

#undef QOS_MAP_MAX_EXCEPTION_COUNT
#undef QOS_MAP_MANDATORY_ELEMENT_COUNT
#undef QOS_MAP_PAIR_LEN

	return UBUS_STATUS_OK;
}

int send_qos_map_conf(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	const char *ifname;
	int ret;
	struct blob_attr *tb[__AP_SEND_QOS_MAP_CONF_MAX];
	uint8_t sta[6] = {0};

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(ap_send_qos_map_conf_policy, __AP_SEND_QOS_MAP_CONF_MAX, tb,
	              blob_data(msg), blob_len(msg));

	if (!(tb[AP_SEND_QOS_MAP_CONF_ADDR])) {
		wifimngr_err("%s(): sta mac not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (hwaddr_aton(blobmsg_data(tb[AP_SEND_QOS_MAP_CONF_ADDR]), sta) == NULL) {
		wifimngr_err("%s(): Invalid address\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	ret = wifi_ap_send_qos_map_conf(ifname, sta);
	if (ret != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int nbr_add(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__NBR_ADD_MAX];
	const char *ifname;
	char bssid_str[18] = {0};
	uint8_t bssid[6] = {0};
	char binfo_str[12] = {0};
	unsigned int bssid_info = 0;
	unsigned int ch = 0;
	unsigned int phy = 9;
	unsigned int reg = 0;
	struct nbr nbr;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(nbr_add_policy, __NBR_ADD_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (!(tb[NBR_ADD_BSSID])) {
		wifimngr_err("%s(): Neighbor Bssid not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	strncpy(bssid_str, blobmsg_data(tb[NBR_ADD_BSSID]), sizeof(bssid_str)-1);
	if (hwaddr_aton(bssid_str, bssid) == NULL)
		return UBUS_STATUS_INVALID_ARGUMENT;

	if (tb[NBR_ADD_BINFO]) {
		strncpy(binfo_str, blobmsg_data(tb[NBR_ADD_BINFO]), sizeof(binfo_str)-1);
		bssid_info = atoi(binfo_str);
	}

	if (tb[NBR_ADD_CHANNEL])
		ch = blobmsg_get_u32(tb[NBR_ADD_CHANNEL]);

	if (tb[NBR_ADD_REG])
		reg = blobmsg_get_u32(tb[NBR_ADD_REG]);

	if (tb[NBR_ADD_PHY])
		phy = blobmsg_get_u32(tb[NBR_ADD_PHY]);

	/* wifimngr_dbg("Add: %02x:%02x:%02x:%02x:%02x:%02x Ch = %d Phy = %d\n",
			bssid[0], bssid[1], bssid[2],
			bssid[3], bssid[4], bssid[5],
			ch, phy); */

	memset(&nbr, 0, sizeof(struct nbr));
	memcpy(nbr.bssid, bssid, 6);
	nbr.bssid_info = bssid_info;
	nbr.channel = ch;
	nbr.phy = phy;
	nbr.reg = reg;

	if (wifi_add_neighbor(ifname, nbr) != 0) {
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

int nbr_del(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__NBR_DEL_MAX];
	const char *ifname;
	char bssid_str[18] = {0};
	uint8_t bssid[6] = {0};

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(nbr_del_policy, __NBR_DEL_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (!(tb[NBR_DEL_BSSID]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	strncpy(bssid_str, blobmsg_data(tb[NBR_DEL_BSSID]), sizeof(bssid_str)-1);
	if (hwaddr_aton(bssid_str, bssid) == NULL)
		return UBUS_STATUS_INVALID_ARGUMENT;

	if (wifi_del_neighbor(ifname, bssid) != 0) {
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

static int _nbr_list(struct blob_buf *bb, const char *ifname, bool wildcard)
{
	char bssid_str[18] = {0};
	struct nbr *nbr;
	int nr = 32;
	void *a, *t;
	int ret;
	int i;

	nbr = calloc(nr, sizeof(struct nbr));
	if (!nbr) {
		wifimngr_err("OOM get_neighbor_list()\n");
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	ret = wifi_get_neighbor_list(ifname, nbr, &nr);
	if (ret)
		goto out_exit;

	if (!wildcard)
		a = blobmsg_open_array(bb, "neighbors");

	for (i = 0; i < nr; i++) {
		struct nbr *e;

		e = nbr + i;
		t = blobmsg_open_table(bb, "");
		sprintf(bssid_str, "%02X:%02X:%02X:%02X:%02X:%02X",
				e->bssid[0], e->bssid[1], e->bssid[2],
				e->bssid[3], e->bssid[4], e->bssid[5]);
		blobmsg_add_string(bb, "bssid", bssid_str);
		blobmsg_add_u32(bb, "bss_info", e->bssid_info);
		blobmsg_add_u32(bb, "regulatory", e->reg);
		blobmsg_add_u32(bb, "channel", e->channel);
		blobmsg_add_u32(bb, "phy", e->phy);
		blobmsg_close_table(bb, t);
	}

	if (!wildcard)
		blobmsg_close_array(bb, a);

out_exit:
	free(nbr);
	return ret;
}

static int _sta_nbr_list(struct blob_buf *bb, const char *ifname,
						unsigned char *sta)
{
	struct sta_nbr *snbr;
	int nr = 32;
	void *a, *t;
	char bssid_str[18] = {0};
	int ret;
	int i;

	snbr = calloc(nr, sizeof(struct sta_nbr));
	if (!snbr) {
		wifimngr_err("OOM get_neighbor_list()\n");
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	ret = wifi_get_beacon_report(ifname, sta, snbr, &nr);
	if (ret)
		goto out_exit;

	a = blobmsg_open_array(bb, "neighbors");
	for (i = 0; i < nr; i++) {
		struct sta_nbr *e;

		e = snbr + i;
		t = blobmsg_open_table(bb, "");
		sprintf(bssid_str, "%02X:%02X:%02X:%02X:%02X:%02X",
				e->bssid[0], e->bssid[1], e->bssid[2],
				e->bssid[3], e->bssid[4], e->bssid[5]);
		blobmsg_add_string(bb, "bssid", bssid_str);
		blobmsg_add_u32(bb, "rssi", e->rssi);
		blobmsg_add_u32(bb, "rsni", e->rsni);
		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, a);

out_exit:
	free(snbr);
	return ret;
}

int nbr_list(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__NBR_LIST_MAX];
	char sta_macstr[18] = {0};
	int ret = UBUS_STATUS_OK;
	struct blob_buf bb = {0};
	uint8_t sta[6] = {0};
	const char *ifname;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(nbr_list_policy, __NBR_LIST_MAX, tb, blob_data(msg),
		      blob_len(msg));

	blob_buf_init(&bb, 0);
	if ((tb[NBR_LIST_CLIENT])) {
		strncpy(sta_macstr, blobmsg_data(tb[NBR_LIST_CLIENT]),
						sizeof(sta_macstr)-1);
		if (hwaddr_aton(sta_macstr, sta) == NULL) {
			ret = UBUS_STATUS_INVALID_ARGUMENT;
			goto out;
		}

		if (_sta_nbr_list(&bb, ifname, sta) != 0) {
			ret = UBUS_STATUS_UNKNOWN_ERROR;
			goto out;
		}
	} else {
		if (_nbr_list(&bb, ifname, false) != 0) {
			ret = UBUS_STATUS_UNKNOWN_ERROR;
			goto out;
		}
	}

	ubus_send_reply(ctx, req, bb.head);
out:
	blob_buf_free(&bb);
	return ret;
}

int nbr_request(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__NBR_REQ_MAX];
	const char *ifname;
	char macstr[18] = {0};
	uint8_t sta_macaddr[6] = {0};
	char ssid[64];
	char mode[32];
	struct wifi_beacon_req *params;
	uint8_t ssid_len = 0;
	uint8_t *bcn_req;
	uint8_t reporting_detail = 0;
	uint8_t num_element = 0;
	int pos = 0;
	size_t req_size = sizeof(struct wifi_beacon_req);
	int ret = UBUS_STATUS_OK;

	blobmsg_parse(nbr_req_policy, __NBR_REQ_MAX, tb,
					blob_data(msg), blob_len(msg));

	/* Table 9-88 - Optional subelement IDs for Beacon request */
	if (tb[NBR_REQ_SSID]) {
		strncpy(ssid, blobmsg_data(tb[NBR_REQ_SSID]), sizeof(ssid)-1);
		ssid_len = strlen(ssid);
		if (ssid_len > 32)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (ssid_len && tb[NBR_REQ_SSID]) {
		req_size += 2; /* tlv */
		req_size += ssid_len;
	}

	if (tb[NBR_REQ_REP_DETAIL]) {
		reporting_detail = (uint8_t)blobmsg_get_u32(
				tb[NBR_REQ_REP_DETAIL]);
		req_size += 3; /* 1 octet value */
	}

	if (tb[NBR_REQ_ELEMENT_IDS]) {
		if (reporting_detail != 1)
			return UBUS_STATUS_INVALID_ARGUMENT;

		num_element = blobmsg_check_array(
				tb[NBR_REQ_ELEMENT_IDS], BLOBMSG_TYPE_INT32);
		/* 9.4.2.10 - Request element */
		req_size += 2 + num_element;
	}

	/* Allocate space for the request */
	bcn_req = calloc(1, req_size);
	if (!bcn_req)
		return -ENOMEM;

	/* Fill in request parameters */
	params = (struct wifi_beacon_req *) bcn_req;

	if (!(tb[NBR_REQ_CLIENT])) {
		ret = UBUS_STATUS_INVALID_ARGUMENT;
		goto out;
	}

	strncpy(macstr, blobmsg_data(tb[NBR_REQ_CLIENT]), sizeof(macstr)-1);
	if (hwaddr_aton(macstr, sta_macaddr) == NULL) {
		ret = UBUS_STATUS_INVALID_ARGUMENT;
		goto out;
	}

	if (tb[NBR_REQ_OPCLASS])
		params->oper_class = blobmsg_get_u32(tb[NBR_REQ_OPCLASS]);

	if (tb[NBR_REQ_CHANNEL])
		params->channel = blobmsg_get_u32(tb[NBR_REQ_CHANNEL]);

	if (tb[NBR_REQ_DURATION])
		params->duration = blobmsg_get_u32(tb[NBR_REQ_DURATION]);

	params->mode = WIFI_BCNREQ_MODE_UNSET;

	if (tb[NBR_REQ_MODE]) {
		strncpy(mode, blobmsg_data(tb[NBR_REQ_MODE]), sizeof(mode)-1);
		if (strlen(mode) > 8) {
			ret = UBUS_STATUS_INVALID_ARGUMENT;
			goto out;
		}

		if (!strcmp(mode, "active"))
			params->mode = WIFI_BCNREQ_MODE_ACTIVE;
		else if (!strcmp(mode, "passive"))
			params->mode = WIFI_BCNREQ_MODE_PASSIVE;
		else if (!strcmp(mode, "table"))
			params->mode = WIFI_BCNREQ_MODE_TABLE;
	}

	if (tb[NBR_REQ_BSSID]) {
		strncpy(macstr, blobmsg_data(tb[NBR_REQ_BSSID]), sizeof(macstr)-1);
		if (hwaddr_aton(macstr, params->bssid) == NULL) {
			ret = UBUS_STATUS_INVALID_ARGUMENT;
			goto out;
		}
	}

	if (ssid_len && tb[NBR_REQ_SSID]) {
		params->variable[pos] = WIFI_BCNREQ_SSID;
		params->variable[pos+1] = ssid_len;
		memcpy(&params->variable[pos+2], ssid, ssid_len);
		pos += 2 + ssid_len;
	}

	if (tb[NBR_REQ_REP_DETAIL]) {
		params->variable[pos] = WIFI_BCNREQ_REP_DETAIL;
		params->variable[pos+1] = 1;
		params->variable[pos+2] = reporting_detail;
		pos += 3;
	}

	/* example: ubus call wifi.ap.wlan1 request_neighbor '{"client":"44:d4:37:4d:
	 * 84:83", "bssid":"44:d4:37:42:47:bf", "ssid":"iopsysWrt", "mode":"active",
	 * "channel_report":[{"opclass":81,"channels": [1, 6, 13]}, {"opclass":82,
	 * "channels": [1, 6, 13]}], "reporting_detail":1, "request_element":[7, 33]}'
	 */

	if (tb[NBR_REQ_CHAN_REPORT]) {
		int num_report = blobmsg_check_array(
				tb[NBR_REQ_CHAN_REPORT], BLOBMSG_TYPE_TABLE);
		struct blob_attr *cur;
		int rem;

		wifimngr_dbg("nbr_request: num_report = %d\n", num_report);

		/* 9.4.2.36 AP Channel Report element */
		blobmsg_for_each_attr(cur, tb[NBR_REQ_CHAN_REPORT], rem) {
			struct blob_attr *data[2], *attr;
			int num_channels, j = 0;
			int remm;
			uint8_t *bcn_req_ext;
			static const struct blobmsg_policy supp_attrs[2] = {
				[0] = { .name = "opclass", .type = BLOBMSG_TYPE_INT32 },
				[1] = { .name = "channels", .type = BLOBMSG_TYPE_ARRAY },
			};

			//if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE)
			//	continue;

			blobmsg_parse(supp_attrs, 2, data, blobmsg_data(cur),
					blobmsg_data_len(cur));

			if (!data[0] || !data[1])
				continue;

			num_channels = blobmsg_check_array(data[1], BLOBMSG_TYPE_INT32);

			/* Allocate additional space for Channel Report elements:
			 * - 3 octets: Element ID + Length + Operating Class.
			 * - Channel List consists of num_channels octets.
			 * - num_channels can be equal to 0.
			 */
			req_size = req_size + 3 + num_channels;
			bcn_req_ext = realloc(bcn_req, req_size);
			if (!bcn_req_ext) {
				ret = -ENOMEM;
				goto out;
			}
			bcn_req = bcn_req_ext;

			// Iterate through all channels of the opclass
			blobmsg_for_each_attr(attr, data[1], remm) {
				if (blobmsg_type(attr) != BLOBMSG_TYPE_INT32)
					continue;
				/* Channel List */
				params->variable[pos + (3 + j)] = (uint8_t) blobmsg_get_u32(attr);
				j++;
			}

			if (num_channels != j) {
				ret = UBUS_STATUS_INVALID_ARGUMENT;
				goto out;
			}

			/* Element ID */
			params->variable[pos] = WIFI_BCNREQ_AP_CHAN_REPORT;
			/* Length = 1 (for opclass) + number_of_channels [octets] */
			params->variable[pos+1] = 1 + j;
			/* Operating Class */
			params->variable[pos+2] = (uint8_t) blobmsg_get_u32(data[0]);

			pos += (3 + j); /* El ID, len, opclass + channel list */
		}
	}

	/* If the Reporting Detail equals 1, a Request element is optionally
	 * present in the optional subelements field. If included, the Request
	 * element lists the Element IDs of the elements requested to be
	 * reported in the Reported Frame Body of the Beacon Report. */
	if (num_element && reporting_detail == 1) {
		/* 9.4.2.10 - Request element */
		struct blob_attr *attr_id;
		int rem_id;
		int k = 0;

		wifimngr_dbg("nbr_request: num_element = %d\n", num_element);

		params->variable[pos] = WIFI_BCNREQ_REQUEST;
		params->variable[pos+1] = num_element;
		blobmsg_for_each_attr(attr_id, tb[NBR_REQ_ELEMENT_IDS], rem_id) {
				/* Table 9-77 - Element IDs */
				if (blobmsg_type(attr_id) != BLOBMSG_TYPE_INT32)
					continue;
				params->variable[pos + (2 + k)] = (uint8_t) blobmsg_get_u32(attr_id);
				k++;
		}

		if (num_element != k) {
			ret = UBUS_STATUS_INVALID_ARGUMENT;
			goto out;
		}

		pos += (2 + k);
	}

	ifname = ubus_ap_to_ifname(obj);
	if (wifi_req_beacon_report(ifname, sta_macaddr, params, req_size) != 0)
		ret = UBUS_STATUS_UNKNOWN_ERROR;

out:
	free(bcn_req);
	return ret;
}

/* btm_request
 *
 * Example calls:
 *
 * ubus call wifi.ap.wl0 "request_btm" '{"sta":"00:0a:52:06:2f:ab",
 *	"target_ap":[{"bssid":"7a:d4:37:6a:f7:df", "reg":128, "channel":36}]}'
 *
 * ubus call wifi.ap.wl0 "request_btm" '{"sta":"00:0a:52:06:2f:ab",
 *	"target_ap":[{"bssid":"7a:d4:37:6a:f7:df"}]}'
 *
 * ubus call wifi.ap.wl0 "request_btm" '{"sta":"00:0a:52:06:2f:ab",
 *	"target_ap":[{"bssid":"7a:d4:37:6a:f7:df"},
 *	             {"bssid":"44:d4:37:6a:f4:cf", "channel":36}]}'
 *
 * ubus call wifi.ap.wl0 "request_btm" '{"sta":"00:0a:52:06:2f:ab",
 *	"target_ap":[{"bssid":"7a:d4:37:6a:f7:df", "reg":128, "channel":36},
 *	             {"bssid":"44:d4:37:6a:f4:cf", "bssid_info":6319, "phy":14}],
 *	"mode":12,"disassoc_tmo":300,"bssterm_dur":1}'
 */
#define MAX_BTM_BSS_NUM 12
int btm_request(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__BTM_REQ_MAX];
	const char *ifname;
	char sta_macstr[18] = {0};
	uint8_t sta[6] = {0};
	int rem;
	int bss_idx = 0;
	struct wifi_btmreq param = {
		.dialog_token = 0x1,
		.mbo.valid = false,
	};
	struct nbr bsss[MAX_BTM_BSS_NUM] = {0};

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(btmreq_policy, __BTM_REQ_MAX, tb,
					blob_data(msg), blob_len(msg));

	/* STA mac address */
	if (!(tb[BTM_REQ_STA]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	strncpy(sta_macstr, blobmsg_data(tb[BTM_REQ_STA]), sizeof(sta_macstr)-1);
	if (hwaddr_aton(sta_macstr, sta) == NULL)
		return UBUS_STATUS_INVALID_ARGUMENT;

	/* Target BSSID and optional params */
	if (tb[BTM_REQ_TARGET]) {
		struct blob_attr *cur;
		char bss_str[18] = {0};
		uint8_t tbss[6] = {0};
		int num_bss = blobmsg_check_array(tb[BTM_REQ_TARGET], BLOBMSG_TYPE_TABLE);

		wifimngr_dbg("btm_request: num of BSSID = %d\n", num_bss);

		blobmsg_for_each_attr(cur, tb[BTM_REQ_TARGET], rem) {
			struct blob_attr *data[5];
			static const struct blobmsg_policy supp_attrs[5] = {
				[0] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
				[1] = { .name = "bssid_info", .type = BLOBMSG_TYPE_INT32 },
				[2] = { .name = "reg", .type = BLOBMSG_TYPE_INT32 },
				[3] = { .name = "channel", .type = BLOBMSG_TYPE_INT32 },
				[4] = { .name = "phy", .type = BLOBMSG_TYPE_INT32 },
			};

			blobmsg_parse(supp_attrs, 5, data, blobmsg_data(cur),
					blobmsg_data_len(cur));

			if (!data[0])
				return UBUS_STATUS_INVALID_ARGUMENT;

			/* Pass the params */
			strncpy(bss_str, blobmsg_data(data[0]), sizeof(bss_str)-1);
			if (hwaddr_aton(bss_str, tbss) == NULL)
				return UBUS_STATUS_INVALID_ARGUMENT;
			memcpy(&bsss[bss_idx].bssid, tbss, 6);

			if (data[1])
				bsss[bss_idx].bssid_info = blobmsg_get_u32(data[1]);
			if (data[2])
				bsss[bss_idx].reg = (uint8_t) blobmsg_get_u32(data[2]);
			if (data[3])
				bsss[bss_idx].channel = (uint8_t) blobmsg_get_u32(data[3]);
			if (data[4])
				bsss[bss_idx].phy = (uint8_t) blobmsg_get_u32(data[4]);

			if (++bss_idx >= MAX_BTM_BSS_NUM)
				break;
		}
	}

	wifimngr_dbg("btm_request: bss_idx = %d\n", bss_idx);

	if (tb[BTM_REQ_DIALOG_TOKEN]) {
		uint32_t token = blobmsg_get_u32(tb[BTM_REQ_DIALOG_TOKEN]);

		if (token > 255)
			return UBUS_STATUS_INVALID_ARGUMENT;

		param.dialog_token = token;
	}

	if (tb[BTM_REQ_MODE])
		param.mode = blobmsg_get_u32(tb[BTM_REQ_MODE]);
	if (tb[BTM_REQ_DISASSOC_TMO])
		param.disassoc_tmo = blobmsg_get_u32(tb[BTM_REQ_DISASSOC_TMO]);
	if (tb[BTM_REQ_VALIDITY_INT])
		param.validity_int = blobmsg_get_u32(tb[BTM_REQ_VALIDITY_INT]);
	if (tb[BTM_REQ_BSSTERM_DUR])
		param.bssterm_dur = blobmsg_get_u32(tb[BTM_REQ_BSSTERM_DUR]);

	/* MBO parameters */
	if (tb[BTM_REQ_MBO_REASON])
		param.mbo.reason = blobmsg_get_u32(tb[BTM_REQ_MBO_REASON]);
	if (tb[BTM_REQ_MBO_CELL_PREF])
		param.mbo.cell_pref = blobmsg_get_u32(tb[BTM_REQ_MBO_CELL_PREF]);
	if (tb[BTM_REQ_MBO_REASSOC_DELAY])
		param.mbo.reassoc_delay = blobmsg_get_u32(tb[BTM_REQ_MBO_REASSOC_DELAY]);

	if (param.mbo.reason <= 255 || param.mbo.cell_pref <= 255
			|| param.mbo.reassoc_delay <= 65535)
		param.mbo.valid = true;

	if (wifi_req_btm(ifname, sta, bss_idx, bsss, &param) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int assoc_control(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__ASSOC_CNTRL_MAX];
	const char *ifname;
	char sta_macstr[18] = {0};
	uint8_t sta[6] = {0};
	struct blob_attr *attr;
	int enable, rem;

	wifimngr_dbg("%s: entering\n", __func__);

	ifname = ubus_ap_to_ifname(obj);

	blobmsg_parse(assoc_cntrl_policy, __ASSOC_CNTRL_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (!(tb[ASSOC_CNTRL_CLIENT]) || !(tb[ASSOC_CNTRL_ENABLE]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	enable = blobmsg_get_u32(tb[ASSOC_CNTRL_ENABLE]);

	blobmsg_for_each_attr(attr, tb[ASSOC_CNTRL_CLIENT], rem) {
		if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
				continue;

		strncpy(sta_macstr, blobmsg_data(attr), sizeof(sta_macstr)-1);
		if (hwaddr_aton(sta_macstr, sta) == NULL)
			return UBUS_STATUS_INVALID_ARGUMENT;

		if (wifi_restrict_sta(ifname, sta, enable) != 0)
			return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

int vsie_add(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *ureq, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__VSIE_MAX];
	const char *ifname;
	uint8_t vsie_buf[256] = {0};
	struct vendor_iereq *req = (struct vendor_iereq *)vsie_buf;
	uint8_t *data = req->ie.data;
	char data_str[499] = {0};	/* (256 - 7) * 2 + 1 */
	char oui_str[7] = {0};          /* 3 * 2 + 1 */
	uint8_t oui[3] = {0};
	int data_len = 0;
	int data_strlen;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(vsie_policy, __VSIE_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (!(tb[VSIE_OUI]) || !(tb[VSIE_DATA]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	req->mgmt_subtype = 0;
	req->ie.ie_hdr.eid = 0xdd;
	req->ie.ie_hdr.len = 0;

	if (tb[VSIE_MGMT_STYPE])
		req->mgmt_subtype = blobmsg_get_u32(tb[VSIE_MGMT_STYPE]);

	if (blobmsg_data_len(tb[VSIE_OUI]) != sizeof(oui_str))
		return UBUS_STATUS_INVALID_ARGUMENT;

	strncpy(oui_str, blobmsg_data(tb[VSIE_OUI]), 6);
	strtob(oui_str, 7, oui);
	memcpy(req->ie.oui, oui, 3);
	req->ie.ie_hdr.len += 3;

	data_strlen = blobmsg_data_len(tb[VSIE_DATA]);
	if (data_strlen > sizeof(data_str) - 1)
		return UBUS_STATUS_INVALID_ARGUMENT;

	strncpy(data_str, blobmsg_data(tb[VSIE_DATA]), data_strlen);
	strtob(data_str, data_strlen, &data[0]);

	data_len = (data_strlen - 1) / 2;
	req->ie.ie_hdr.len += data_len;

	if (wifi_add_vendor_ie(ifname, req) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int vsie_del(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *ureq, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__VSIE_MAX];
	const char *ifname;
	uint8_t vsie_buf[256] = {0};
	struct vendor_iereq *req = (struct vendor_iereq *)vsie_buf;
	uint8_t *data = req->ie.data;
	char data_str[499] = {0};	/* (256 - 7) * 2 + 1 */
	char oui_str[7] = {0};          /* 3 * 2 + 1 */
	uint8_t oui[3] = {0};
	int data_strlen;
	int data_len = 0;
	//uint32_t mgmt_frames = 0;

	ifname = ubus_ap_to_ifname(obj);
	blobmsg_parse(vsie_policy, __VSIE_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (!(tb[VSIE_OUI]))
		return UBUS_STATUS_INVALID_ARGUMENT;

	req->mgmt_subtype = 0;
	req->ie.ie_hdr.eid = 0xdd;
	req->ie.ie_hdr.len = 0;

	if (tb[VSIE_MGMT_STYPE])
		req->mgmt_subtype = blobmsg_get_u32(tb[VSIE_MGMT_STYPE]);


	if (blobmsg_data_len(tb[VSIE_OUI]) != sizeof(oui_str))
		return UBUS_STATUS_INVALID_ARGUMENT;

	strncpy(oui_str, blobmsg_data(tb[VSIE_OUI]), 6);
	strtob(oui_str, 7, oui);
	memcpy(req->ie.oui, oui, 3);
	req->ie.ie_hdr.len += 3;

	if (tb[VSIE_DATA]) {
		/* match data starting with pattern */
		data_strlen = blobmsg_data_len(tb[VSIE_DATA]);
		if (data_strlen > sizeof(data_str) - 1)
			return UBUS_STATUS_INVALID_ARGUMENT;

		strncpy(data_str, blobmsg_data(tb[VSIE_DATA]), data_strlen);
		strtob(data_str, data_strlen, &data[0]);

		data_len = (data_strlen - 1) / 2;
		req->ie.ie_hdr.len += data_len;
	}

	if (wifi_del_vendor_ie(ifname, req) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

static void print_sta_info(struct blob_buf *bb, struct wifi_sta *sta)
{
	uint8_t bssid[6] = {0};

	char std_buf2[32] = "802.11";
	char std_buf[32] = {0};
	char sec_str[128] = {0};
	char enc_buf[32] = {0};

	int snr;
	void *t;

	if (sta->mlo_capable) {
		blobmsg_add_u32(bb, "mlo_link_id", sta->mlo_link_id);
		//blobmsg_add_macaddr(bb, "mld_macaddr", sta->mld_macaddr);
		//blobmsg_add_macaddr(bb, "mld_bssid", sta->mld_bssid);
	}
	blobmsg_add_u8(bb, "4addr", sta->is4addr);
	blobmsg_add_u8(bb, "setup_link", sta->is_setup_link);
	blobmsg_add_macaddr(bb, "macaddr", sta->macaddr);
	snprintf(std_buf2 + strlen(std_buf2), 31, "%s",
		etostr(sta->oper_std, std_buf, sizeof(std_buf), WIFI_NUM_STD, standard_str));
	blobmsg_add_string(bb, "standard", std_buf2);
	wifi_security_str(sta->sec.curr_mode, sec_str, sizeof(sec_str));
	blobmsg_add_string(bb, "security", sec_str);
	blobmsg_add_string(bb, "encryption",
			etostr(sta->sec.pair_ciphers, enc_buf, sizeof(enc_buf), 14, cipher_str));	//FIXME-CR
	wl_dump_supp_security(bb, sta->sec.supp_modes);

	if (memcmp(bssid, sta->bssid, sizeof(bssid))) {
		snr = sta->rssi_avg - sta->noise_avg;

		blobmsg_add_macaddr(bb, "bssid", sta->bssid);
		blobmsg_add_string(bb, "ssid", (char *)sta->ssid);
		blobmsg_add_u32(bb, "channel", sta->channel);
		blobmsg_add_u32(bb, "bandwidth", bw_value(sta->bandwidth));
		wifi_print_band(bb, sta->band);
		blobmsg_add_u32(bb, "frequency", wifi_channel_to_freq_ex(sta->channel, sta->band));
		blobmsg_add_u32(bb, "rssi", sta->rssi_avg);
		blobmsg_add_u32(bb, "noise", sta->noise_avg);
		blobmsg_add_u32(bb, "snr", snr);

		blobmsg_add_u32(bb, "idle", sta->idle_time);
		blobmsg_add_u64(bb, "in_network", sta->conn_time);
		blobmsg_add_u32(bb, "tx_airtime", sta->tx_airtime);
		blobmsg_add_u32(bb, "rx_airtime", sta->rx_airtime);
		blobmsg_add_u32(bb, "airtime", sta->airtime);
		blobmsg_add_u32(bb, "maxrate", sta->maxrate);
		blobmsg_add_u32(bb, "tx_throughput", sta->tx_thput);
		blobmsg_add_u32(bb, "est_rx_thput", sta->est_rx_thput);
		blobmsg_add_u32(bb, "est_tx_thput", sta->est_tx_thput);

		t = blobmsg_open_table(bb, "status");
		blobmsg_add_u8(bb, "wmm",
			wifi_status_isset(sta->sbitmap, WIFI_STATUS_WMM) ? true : false);
		blobmsg_add_u8(bb, "ps",
			wifi_status_isset(sta->sbitmap, WIFI_STATUS_PS) ? true : false);
		blobmsg_close_table(bb, t);

		wl_dump_capabilities(sta->band, bb, &sta->caps, sta->cbitmap, sizeof(sta->cbitmap));
		wl_print_sta_stats(bb, &sta->stats);
		wl_print_rate(bb, "tx_rate_latest", &sta->tx_rate);
		wl_print_rate(bb, "rx_rate_latest", &sta->rx_rate);
		wl_print_rssi_chains(bb, "rssi_per_antenna", sta->rssi);
	}
}

int wl_sta_status(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct wifi_sta sta = {};
	const char *ifname;
	struct blob_buf bb;
	ifstatus_t ifs = 0;
	int ret;

	ifname = ubus_sta_to_ifname(obj);
	wifi_get_ifstatus(ifname, &ifs);

	ret = wifi_sta_info(ifname, &sta);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "ifname", ifname);
	blobmsg_add_string(&bb, "status", ifstatus_str(ifs));
	print_sta_info(&bb, &sta);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int wl_mldsta_status(struct ubus_context *ctx, struct ubus_object *obj,
		     struct ubus_request_data *req, const char *method,
		     struct blob_attr *msg)
{
	struct wifi_mlsta mlsta = {};
	const char *ifname;
	struct blob_buf bb;
	ifstatus_t ifs = 0;
	int ret;
	void *t, *a;
	int i;

	ifname = ubus_sta_to_ifname(obj);
	wifi_mld_get_ifstatus(ifname, &ifs);

	ret = wifi_mlsta_interface_info(ifname, &mlsta);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "ifname", ifname);
	blobmsg_add_string(&bb, "status", ifstatus_str(ifs));
	blobmsg_add_macaddr(&bb, "macaddr", mlsta.macaddr);
	blobmsg_add_macaddr(&bb, "bssid", mlsta.bssid);
	blobmsg_add_string(&bb, "ssid", (char *)mlsta.ssid);
	blobmsg_add_u8(&bb, "4addr", mlsta.is4addr);

	a = blobmsg_open_array(&bb, "mlo_links");
	for (i = 0; i < mlsta.num_link ; i++) {
		t = blobmsg_open_table(&bb, "");
		print_sta_info(&bb, &mlsta.sta[i]);
		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);

	wl_print_sta_stats(&bb, &mlsta.stats);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int wl_sta_stats(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	const char *ifname;
	struct wifi_sta_stats s;
	struct blob_buf bb;
	int ret;

	ifname = ubus_sta_to_ifname(obj);

	ret = wifi_sta_get_stats(ifname, &s);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	blobmsg_add_string(&bb, "ifname", ifname);
	void *t;
	t = blobmsg_open_table(&bb, "stats");
	blobmsg_add_u64(&bb, "tx_total_pkts", s.tx_pkts);
	blobmsg_add_u64(&bb, "tx_total_bytes", s.tx_bytes);
	blobmsg_add_u64(&bb, "tx_failures", s.tx_fail_pkts);
	blobmsg_add_u64(&bb, "tx_pkts_retries", s.tx_retry_pkts);
	blobmsg_add_u64(&bb, "rx_data_pkts", s.rx_pkts);
	blobmsg_add_u64(&bb, "rx_data_bytes", s.rx_bytes);
	blobmsg_add_u64(&bb, "rx_failures", s.rx_fail_pkts);
	blobmsg_close_table(&bb, t);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int wl_sta_disconnect_ap(struct ubus_context *ctx, struct ubus_object *obj,
			 struct ubus_request_data *req, const char *method,
			 struct blob_attr *msg)
{
	struct blob_attr *tb[__AP_DISCONNECT_MAX];
	unsigned int reason_code = 0;
	const char *ifname;
	int ret;

	ifname = ubus_sta_to_ifname(obj);
	blobmsg_parse(ap_disconnect_policy, __AP_DISCONNECT_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (tb[AP_DISCONNECT_REASON])
		reason_code = blobmsg_get_u32(tb[AP_DISCONNECT_REASON]);

	ret = wifi_sta_disconnect_ap(ifname, reason_code);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int wl_sta_4addr(struct ubus_context *ctx, struct ubus_object *obj,
		 struct ubus_request_data *req, const char *method,
		 struct blob_attr *msg)
{
	struct blob_attr *tb[__STA_4ADDR_MAX];
	struct blob_buf bb;
	bool enable;
	const char *ifname;
	int ret;

	ifname = ubus_sta_to_ifname(obj);
	blobmsg_parse(sta_4addr_policy, __STA_4ADDR_MAX, tb,
					blob_data(msg), blob_len(msg));

	if (tb[STA_4ADDR_ENABLE]) {
		enable = blobmsg_get_u32(tb[STA_4ADDR_ENABLE]);
		ret = wifi_set_4addr(ifname, enable);
		if (ret)
			return UBUS_STATUS_UNKNOWN_ERROR;

		return UBUS_STATUS_OK;
	}

	ret = wifi_get_4addr(ifname, &enable);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	blobmsg_add_u8(&bb, "enable", enable);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int wl_status(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct wifimngr *w = container_of(obj, struct wifimngr, wifi_obj);
	void *t, *tt, *ttt, *tttt, *t5;
	struct blob_buf bb = {0};
	int i;


	blob_buf_init(&bb, 0);
	t = blobmsg_open_array(&bb, "radios");
	for (i = 0; i < w->num_wifi_device; i++) {
		uint32_t channel;
		uint32_t channels[64] = {0};
		char *phyname = NULL;
		ifstatus_t ifstat = 0;
		enum wifi_band band = BAND_2;
		uint32_t supp_band = 0;
		uint8_t supp_std = 0;
		unsigned long maxrate;
		int noise;
		enum wifi_bw bw = BW20;
		//int bwi = 20;
		uint8_t s = 0;
		//char s_str[32] = {0};
		char std_buf[32] = {0};
		char std_buf2[32] = "802.11";
		struct wifi_caps radiocaps;
		char alpha2[3] = {0};
		const char *country;
		bool multiband = false;
		int nr = ARRAY_SIZE(channels);
		bool radio_disabled;
		int j;

		phyname = w->wdev[i].phy;
		if (!phyname)
			continue;

		band = w->wdev[i].band;
		radio_disabled = w->wdev[i].disabled;

		wifi_radio_is_multiband(phyname, &multiband);
		wifi_get_supp_band(phyname, &supp_band);

		/* Get country from driver first, fallback to cached UCI value */
		if (!wifi_get_country(phyname, alpha2))
			country = alpha2;
		else if (w->wdev[i].country[0] != '\0')
			country = w->wdev[i].country;
		else
			country = "";

		wifi_get_supp_channels(phyname, channels, &nr, country, band, bw);

		if (multiband) {
			//wifi_radio_get_band_ifstatus(phyname, band, &ifstat);
			wifi_get_band_channel(phyname, band, &channel, &bw);
			wifi_get_band_supp_stds(phyname, band, &supp_std);
			wifi_radio_get_band_caps(phyname, band, &radiocaps);
			wifi_get_band_maxrate(phyname, band, &maxrate);
			wifi_get_band_noise(phyname, band, &noise);
			wifi_get_band_oper_stds(phyname, band, &s);
		} else {
			//wifi_radio_get_ifstatus(phyname, &ifstat);
			wifi_get_channel(phyname, &channel, &bw);
			wifi_get_supp_stds(phyname, &supp_std);
			wifi_radio_get_caps(phyname, &radiocaps);
			wifi_get_maxrate(phyname, &maxrate);
			wifi_get_noise(phyname, &noise);
			wifi_get_oper_stds(phyname, &s);
		}

		ttt = blobmsg_open_table(&bb, "");

		blobmsg_add_string(&bb, "name", w->wdev[i].device);
		blobmsg_add_string(&bb, "phyname", phyname);
		blobmsg_add_u8(&bb, "isup", !radio_disabled);
		//blobmsg_add_string(&bb, "standard", s_str);

		snprintf(std_buf2 + strlen(std_buf2), 31, "%s",
			etostr(s, std_buf, sizeof(std_buf), WIFI_NUM_STD, standard_str));
		blobmsg_add_string(&bb, "standard", std_buf2);
		blobmsg_add_string(&bb, "country", country);
		wifi_print_band(&bb, band);

		blobmsg_add_u32(&bb, "channel", channel);
		blobmsg_add_u32(&bb, "frequency", wifi_channel_to_freq_ex(channel, band));
		blobmsg_add_u32(&bb, "bandwidth", bw_value(bw));
		blobmsg_add_u32(&bb, "noise", noise);
		blobmsg_add_u64(&bb, "maxrate", maxrate);
		wifi_print_radio_supp_bands(&bb, supp_band);
		wifi_print_radio_supp_std(&bb, supp_std);

		t5 = blobmsg_open_array(&bb, "channels");
		for (j = 0; j < nr && channels[j] != 0; j++) {
			blobmsg_add_u32(&bb, "", channels[j]);
		}
		blobmsg_close_array(&bb, t5);


		tt = blobmsg_open_array(&bb, "accesspoints");
		for (j = 0; j < w->num_wifi_iface; j++) {
			uint8_t ssid[SSID_MAX_LEN + 1] = {0};
			struct wifi_mlo_link link[1] = {0};
			char *ifname = w->ifs[j].iface;
			int nlink = ARRAY_SIZE(link);
			bool is_mlolink = false;
			uint8_t bssid[6] = {0};
			size_t ssidlen = 0;


			if (w->ifs[j].device[0] &&
			    !strncmp(w->ifs[j].device, w->wdev[i].device, 16) &&
			    w->ifs[j].mode == WIFI_MODE_AP) {
				/* wifimngr_dbg("Matched: rad = %s  if = %s\n",
						ifs[j].device, ifname); */
				//memset(w->ifs[j].device, 0, 16);
				;
			} else {
				continue;
			}

			if (!strlen(w->ifs[j].mld_netdev)) {
				wifi_get_ifstatus(ifname, &ifstat);
				if (!!(ifstat & IFF_UP)) {
					wifi_get_bssid(ifname, bssid);
					wifi_get_ssid(ifname, ssid, &ssidlen);
				}
			} else {

				is_mlolink= true;
				wifi_get_mlo_links(w->ifs[j].mld_netdev, w->ifs[j].band, link, &nlink);
				wifi_get_bssid(w->ifs[j].mld_netdev, bssid);
				wifi_get_ssid(w->ifs[j].mld_netdev, ssid, &ssidlen);
				ifstat |= IFF_UP;	//FIXME
			}

			tttt = blobmsg_open_table(&bb, "");
			blobmsg_add_string(&bb, "ifname", ifname);
			blobmsg_add_string(&bb, "status", ifstatus_str(ifstat));
			blobmsg_add_string(&bb, "ssid", (char *)ssid);
			blobmsg_add_macaddr(&bb, "bssid", bssid);
			if (is_mlolink) {
				blobmsg_add_string(&bb, "mld_ifname", w->ifs[j].mld_netdev);
				blobmsg_add_u32(&bb, "mlo_linkid", link[0].id);
				blobmsg_add_macaddr(&bb, "mlo_linkaddr", link[0].macaddr);
			}

			blobmsg_close_table(&bb, tttt);
		}
		blobmsg_close_array(&bb, tt);

		tt = blobmsg_open_array(&bb, "backhauls");
		for (j = 0; j < w->num_wifi_iface; j++) {
			char *ifname = w->ifs[j].iface;
			uint8_t ssid[SSID_MAX_LEN + 1] = {0};
			struct wifi_mlo_link link[8] = {0};
			int nlink = ARRAY_SIZE(link);
			bool is_mlolink = false;
			uint8_t bssid[6] = {0};
			size_t ssidlen = 0;
			//ifopstatus_t opstatus;

			if (w->ifs[j].device[0] &&
			    !strncmp(w->ifs[j].device, w->wdev[i].device, 16) &&
			    w->ifs[j].mode == WIFI_MODE_STA) {
				;
			} else {
				continue;
			}

			if (!strlen(w->ifs[j].mld_netdev)) {
				wifi_get_ifstatus(ifname, &ifstat);
				if (!!(ifstat & IFF_UP)) {
					wifi_get_bssid(ifname, bssid);
					wifi_get_ssid(ifname, ssid, &ssidlen);
				}
			} else {

				is_mlolink= true;
				wifi_get_mlo_links(w->ifs[j].mld_netdev, w->ifs[j].band, link, &nlink);
				wifi_get_bssid(w->ifs[j].mld_netdev, bssid);
				wifi_get_ssid(w->ifs[j].mld_netdev, ssid, &ssidlen);
				ifstat |= IFF_UP;	//FIXME
			}

			tttt = blobmsg_open_table(&bb, "");
			blobmsg_add_string(&bb, "ifname", ifname);
			blobmsg_add_string(&bb, "status", ifstatus_str(ifstat));
			if (is_mlolink)
				blobmsg_add_string(&bb, "mld_ifname", w->ifs[j].mld_netdev);
			if (is_mlolink && nlink == 1) {
				blobmsg_add_string(&bb, "ssid", (char *)ssid);
				blobmsg_add_macaddr(&bb, "bssid", bssid);
				blobmsg_add_u32(&bb, "mlo_linkid", link[0].id);
				blobmsg_add_macaddr(&bb, "mlo_linkaddr", link[0].macaddr);
			}
			blobmsg_close_table(&bb, tttt);
		}
		blobmsg_close_array(&bb, tt);
		blobmsg_close_table(&bb, ttt);
	}
	blobmsg_close_array(&bb, t);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 0;
}

int wl_help(struct ubus_context *ctx, struct ubus_object *obj,
	    struct ubus_request_data *req, const char *method,
	    struct blob_attr *msg)
{
	return wl_help_command(ctx, obj, req, method, msg, WIFI_OBJECT);
}

int wifi_set_debug(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct blob_attr *tb[__DEBUG_LEVEL_MAX];
	char *envval = getenv("LIBWIFI_DEBUG_LEVEL");
	char new_envval[8] = { 0 };
	struct blob_buf bb = {0};
	uint16_t debug_level;

	debug_level = envval ? atoi(envval) : 0;

	blobmsg_parse(wifi_set_debug_policy, __DEBUG_LEVEL_MAX, tb,
			blob_data(msg), blob_len(msg));

	if (!(tb[DEBUG_LEVEL])) {
		blob_buf_init(&bb, 0);
		blobmsg_add_u32(&bb, "LIBWIFI_DEBUG_LEVEL: ", debug_level);
		ubus_send_reply(ctx, req, bb.head);
		blob_buf_free(&bb);
	} else {
		debug_level = blobmsg_get_u32(tb[DEBUG_LEVEL]);
		snprintf(new_envval, 7, "%d", debug_level);
		setenv("LIBWIFI_DEBUG_LEVEL", new_envval, 1);
	}

	return 0;
}

int wl_radio_get_param(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	struct blob_attr *tb[__GET_MAX];
	const char *device;
	struct blob_buf bb;
	int data = 0;	// FIXME: data type based on 'param' name
	int len = 0;	//        also len
	int ret;
	struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	struct wifimngr_device *wdev = (struct wifimngr_device *)wo->priv;

	memset(&bb, 0, sizeof(bb));
	blobmsg_parse(radio_get_param_policy, __GET_MAX, tb,
						blob_data(msg), blob_len(msg));

	device = wdev->phy;

	ret = wifi_radio_get_param(device, "temperature", &len, &data);
	blob_buf_init(&bb, 0);
	blobmsg_add_u32(&bb, "status", ret);
	if (ret == 0) {
		blobmsg_add_u32(&bb, "value_int", data);
	}

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);
	return 	UBUS_STATUS_OK;
}

int wl_apmld_help(struct ubus_context *ctx, struct ubus_object *obj,
		  struct ubus_request_data *req, const char *method,
		  struct blob_attr *msg)
{
	return wl_help_command(ctx, obj, req, method, msg, WIFI_APMLD_OBJECT);
}

int wl_apmld_status(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	enum wifi_mode mode = WIFI_MODE_UNKNOWN;
	uint8_t ssid[SSID_MAX_LEN + 1] = {0};
	struct wifi_ap_stats ifstats = {};
	struct wifi_mlo_link link[4];
	int num = ARRAY_SIZE(link);
	uint8_t macaddr[6] = {0};
	uint8_t bssid[6] = {0};
	struct blob_buf bb = {};
	const char *ifname;
	size_t ssidlen = 0;
	void *a, *t;
	int ret;
	int i;
	ifstatus_t ifs = 0;

	ifname = ubus_objname_to_ifname(obj);
	wifi_get_ssid(ifname, ssid, &ssidlen);
	wifi_get_mode(ifname, &mode);

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

	wifi_ap_get_stats(ifname, &ifstats);
	wifi_mld_get_ifstatus(ifname, &ifs);

	blob_buf_init(&bb, 0);

	blobmsg_add_string(&bb, "ifname", ifname);
	blobmsg_add_string(&bb, "status", ifstatus_str(ifs));
	blobmsg_add_string(&bb, "ssid", (char *)ssid);
	if_gethwaddr(ifname, macaddr);
	blobmsg_add_macaddr(&bb, "macaddr", macaddr);
	wifi_get_bssid(ifname, bssid);
	blobmsg_add_macaddr(&bb, "bssid", bssid);
	wifi_print_mode(&bb, mode);

	a = blobmsg_open_array(&bb, "links");
	for (i = 0; i < num; i++) {
		struct wifi_ap ap = {};
		char std_buf2[32] = "802.11";
		char sec_str[128] = {0};
		char std_buf[32] = {0};
		char enc_buf[32] = {0};

		if (mode != WIFI_MODE_AP)
			continue;

		t = blobmsg_open_table(&bb, "");
		blobmsg_add_u32(&bb, "id", link[i].id);
		wifi_print_band(&bb, link[i].band);
		blobmsg_add_u32(&bb, "channel", link[i].channel);
		blobmsg_add_u32(&bb, "ccfs0", link[i].ccfs0);
		blobmsg_add_u32(&bb, "ccfs1", link[i].ccfs1);
		blobmsg_add_u32(&bb, "bandwidth", bw_value(link[i].bandwidth));
		blobmsg_add_macaddr(&bb, "macaddr", link[i].macaddr);

		if (!wifi_ap_info_band(ifname, link[i].band, &ap)) {
			blobmsg_add_u8(&bb, "enabled", ap.enabled ? true : false);
			blobmsg_add_u32(&bb, "puncture_bitmap", (int)BUF_GET_LE16(ap.bss.puncture));
			wifi_security_str(ap.bss.security, sec_str, sizeof(sec_str));
			blobmsg_add_string(&bb, "security", sec_str);
			blobmsg_add_string(&bb, "encryption", etostr(ap.bss.rsn.pair_ciphers,
					   enc_buf, sizeof(enc_buf), 14, wifi_cipherstr));

			snprintf(std_buf2 + strlen(std_buf2), 31, "%s",
				 etostr(ap.bss.oper_std, std_buf, sizeof(std_buf), WIFI_NUM_STD, standard_str));
			blobmsg_add_string(&bb, "standard", std_buf2);

			blobmsg_add_u32(&bb, "num_stations", ap.bss.load.sta_count);
			blobmsg_add_u32(&bb, "max_stations", ap.assoclist_max);
			blobmsg_add_u32(&bb, "utilization", ap.bss.load.utilization);
			blobmsg_add_u32(&bb, "adm_capacity", ap.bss.load.available);
			blobmsg_add_u8(&bb, "hidden", !ap.ssid_advertised ? true : false);
			blobmsg_add_u32(&bb, "beacon_int", ap.bss.beacon_int);
			blobmsg_add_u32(&bb, "dtim_period", ap.bss.dtim_period);

			wl_dump_supp_security(&bb, ap.sec.supp_modes);
			wl_dump_capabilities(ap.bss.band, &bb,
					     &ap.bss.caps,
					     ap.bss.cbitmap,
					     sizeof(ap.bss.cbitmap));
			//wl_ap_wmm_status(&bb, link[i].ap.ac);
		}
		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);

	t = blobmsg_open_table(&bb, "stats");
	if (num)
		print_apstats(&bb, &ifstats);
	blobmsg_close_table(&bb, t);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return 	UBUS_STATUS_OK;
}

int wl_apmld_stats(struct ubus_context *ctx, struct ubus_object *obj,
		   struct ubus_request_data *req, const char *method,
		   struct blob_attr *msg)
{
	struct wifi_ap_stats ifstats = {};
	struct blob_buf bb = {};
	const char *ifname;
	void *t;

	ifname = ubus_objname_to_ifname(obj);
	wifi_ap_get_stats(ifname, &ifstats);

	blob_buf_init(&bb, 0);
	t = blobmsg_open_table(&bb, "stats");
	print_apstats(&bb, &ifstats);
	blobmsg_close_table(&bb, t);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int wl_apmld_assoclist(struct ubus_context *ctx, struct ubus_object *obj,
		       struct ubus_request_data *req, const char *method,
		       struct blob_attr *msg)
{
	struct blob_buf bb = {0};
	uint8_t stas[768] = {0};	/* 128 * 6 */
	const char *ifname;
	int nr = 128;
	void *a;
	int i;

	ifname = ubus_objname_to_ifname(obj);
	if (wifi_get_assoclist(ifname, stas, &nr) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	blob_buf_init(&bb, 0);
	a = blobmsg_open_array(&bb, "assoclist");
	for (i = 0; i < nr; i++) {
		blobmsg_add_macaddr(&bb, "", &stas[i*6]);
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

static void print_station(struct blob_buf *bb, struct wifi_sta *s, bool mlo)
{
	char std_buf2[32] = "802.11";
	char std_buf[32] = {0};

	blobmsg_add_u32(bb, "link_id", s->mlo_link_id);
	if (mlo) {
		blobmsg_add_macaddr(bb, "link_macaddr", s->macaddr);
		blobmsg_add_macaddr(bb, "link_bssid", s->bssid);
	}
	wifi_print_band(bb, s->band);
	snprintf(std_buf2 + strlen(std_buf2), 31, "%s",
		 etostr(s->oper_std, std_buf, sizeof(std_buf), WIFI_NUM_STD, standard_str));
	blobmsg_add_string(bb, "standard", std_buf2);
	blobmsg_add_u32(bb, "channel", s->channel);
	blobmsg_add_u32(bb, "bandwidth", bw_value(s->bandwidth));
	blobmsg_add_u32(bb, "nss", s->nss);
	blobmsg_add_u32(bb, "rssi_avg", s->rssi_avg);
	blobmsg_add_u32(bb, "idle", s->idle_time);
	blobmsg_add_u64(bb, "in_network", s->conn_time);
	blobmsg_add_u8(bb, "setup_link", s->is_setup_link);
	blobmsg_add_u32(bb, "tx_airtime", s->tx_airtime);
	blobmsg_add_u32(bb, "rx_airtime", s->rx_airtime);
	blobmsg_add_u32(bb, "maxrate", s->maxrate);
	blobmsg_add_u32(bb, "tx_throughput", s->tx_thput);
	blobmsg_add_u32(bb, "est_rx_thput", s->est_rx_thput);
	blobmsg_add_u32(bb, "est_tx_thput", s->est_tx_thput);
	wl_dump_capabilities(s->band, bb, &s->caps, s->cbitmap, sizeof(s->cbitmap));
	wl_print_rate(bb, "tx_rate_latest", &s->tx_rate);
	wl_print_rate(bb, "rx_rate_latest", &s->rx_rate);
	wl_print_rssi_chains(bb, "rssi_per_antenna", s->rssi);
}

int wl_apmld_stations(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[__STAINFO_MAX];
	uint8_t sta_macaddr[6] = {0};
	char sta_macstr[18] = {0};
	struct blob_buf bb = {0};
	uint8_t stas[768] = {0};	/* 128 * 6 */
	const char *ifname;
	int nr = 128;
	void *a, *r, *t;
	int ret = UBUS_STATUS_OK;

	ifname = ubus_objname_to_ifname(obj);
	blobmsg_parse(stainfo_policy, __STAINFO_MAX, tb, blob_data(msg), blob_len(msg));

	if ((tb[STAINFO_MACADDR])) {
		strncpy(sta_macstr, blobmsg_data(tb[STAINFO_MACADDR]), sizeof(sta_macstr)-1);
		if (hwaddr_aton(sta_macstr, sta_macaddr) == NULL)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (wifi_get_assoclist(ifname, stas, &nr) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	blob_buf_init(&bb, 0);
	a = blobmsg_open_array(&bb, "stations");

	for (int i = 0; i < nr; i++) {
		uint8_t *macaddr = &stas[i*6];
		struct wifi_mlsta mlsta = {0};

		if (!hwaddr_is_zero(sta_macaddr) && memcmp(sta_macaddr, macaddr, 6))
			continue;

		ret = wifi_get_mlsta_info(ifname, macaddr, &mlsta);
		if (ret) {
			ret = UBUS_STATUS_UNKNOWN_ERROR;
			goto out;
		}

		t = blobmsg_open_table(&bb, "");
		blobmsg_add_macaddr(&bb, "macaddr", mlsta.sta[0].mld_macaddr);
		if (sta_ratings_calc)
			blobmsg_add_double(&bb, "rating",
					wifimngr_sta_ratings_calc(ifname, &mlsta.sta[0]));
		blobmsg_add_macaddr(&bb, "bssid", mlsta.mlo_capable ? mlsta.sta[0].mld_bssid : mlsta.sta[0].bssid);
		blobmsg_add_u8(&bb, "mlo_capable", mlsta.mlo_capable ? true : false);

		if (mlsta.mlo_capable) {
			r = blobmsg_open_array(&bb, "mlo_links");
			for (int y = 0; y < mlsta.num_link; y++) {
				struct wifi_sta *s = &mlsta.sta[y];
				void *tt;

				tt = blobmsg_open_table(&bb, "");
				print_station(&bb, s, true);
				blobmsg_close_table(&bb, tt);
			}
			blobmsg_close_array(&bb, r);
		} else {
			print_station(&bb, &mlsta.sta[0], false);
		}

		wl_print_sta_stats(&bb, &mlsta.stats);

		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);
	ubus_send_reply(ctx, req, bb.head);
out:
	blob_buf_free(&bb);
	return ret;
}

int wl_apmld_sta_ratings(struct ubus_context *ctx, struct ubus_object *obj,
			 struct ubus_request_data *req, const char *method,
			 struct blob_attr *msg)
{
	//struct wifi_ubus_object *wo = container_of(obj, struct wifi_ubus_object, obj);
	//struct wifimngr_iface *iface = (struct wifimngr_iface *)wo->priv;
	struct blob_attr *tb[__STAINFO_MAX];
	uint8_t sta_macaddr[6] = {0};
	int ret = UBUS_STATUS_OK;
	uint8_t stas[768] = {0};
	//struct wifi_ap ap = {0};
	char macstr[18] = {0};
	const char *ifname;
	struct blob_buf bb = {0};
	int num_stas = 64;
	void *a;

	ifname = ubus_objname_to_ifname(obj);
	blobmsg_parse(stainfo_policy, __STAINFO_MAX, tb, blob_data(msg), blob_len(msg));

	if ((tb[STAINFO_MACADDR])) {
		strncpy(macstr, blobmsg_data(tb[STAINFO_MACADDR]), sizeof(macstr) - 1);
		if (hwaddr_aton(macstr, sta_macaddr) == NULL)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	ret = wifi_get_assoclist(ifname, stas, &num_stas);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	blob_buf_init(&bb, 0);
	a = blobmsg_open_array(&bb, "sta-ratings");
	for (int i = 0; i < num_stas; i++) {
		uint8_t *sta = &stas[i*6];
		struct wifi_mlsta mlsta = {0};
		void *t;

		if (!hwaddr_is_zero(sta_macaddr) && memcmp(sta_macaddr, sta, 6))
			continue;

		if (wifi_get_mlsta_info(ifname, sta, &mlsta))
			continue;

		t = blobmsg_open_table(&bb, "");
		blobmsg_add_macaddr(&bb, "macaddr", mlsta.sta[0].mld_macaddr);
		if (sta_ratings_calc)
			blobmsg_add_double(&bb, "rating",
					wifimngr_sta_ratings_calc(ifname, &mlsta.sta[0]));
		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return ret;
}

int wl_apmld_disconnect_sta(struct ubus_context *ctx, struct ubus_object *obj,
			    struct ubus_request_data *req, const char *method,
			    struct blob_attr *msg)
{
	return sta_disconnect(ctx, obj, req, method, msg);
}

int wl_mldsta_stats(struct ubus_context *ctx, struct ubus_object *obj,
		    struct ubus_request_data *req, const char *method,
		    struct blob_attr *msg)
{
	return 0;
}

int wl_mldsta_disconnect_ap(struct ubus_context *ctx, struct ubus_object *obj,
			    struct ubus_request_data *req, const char *method,
			    struct blob_attr *msg)
{
	return wl_sta_disconnect_ap(ctx, obj, req, method, msg);
}


bool wifimngr_is_wifi_device_object_valid(struct wifimngr *w,
					  struct wifi_ubus_object *wobj)
{
	const char *radioname = ubus_objname_to_ifname(&wobj->obj);
	int i;

	for (i = 0; i < w->num_wifi_device; i++) {
		if (strcmp(radioname, w->wdev[i].device) == 0) {
			wobj->priv = &w->wdev[i];
			return true;
		}
	}

	return false;
}

bool wifimngr_is_wifi_iface_object_valid(struct wifimngr *w,
					  struct wifi_ubus_object *wobj)
{
	const char *ifname = ubus_objname_to_ifname(&wobj->obj);
	int i;

	for (i = 0; i < w->num_wifi_iface; i++) {
		if (strcmp(ifname, w->ifs[i].iface) == 0 &&
		    w->ifs[i].disabled == 0) {
			wobj->priv = &w->ifs[i];
			return true;
		}
	}

	return false;
}

int wifimngr_setup_events(struct wifimngr *w, const char *evmap_file)
{
	struct json_object *jevs, *jev_array;
	int len = 0;
	int ret = 0;
	int i, j;


	if (!w)
		return -1;

	/* read from json file about events to listen for */
	jevs = json_object_from_file(evmap_file);
	if (!jevs) {
		wifimngr_err("Failed to open '%s'\n", evmap_file);
		return -1;
	}

	if (!json_object_is_type(jevs, json_type_object))
		goto out_json;

	json_object_object_get_ex(jevs, "events", &jev_array);
	len = json_object_array_length(jev_array);
	for (i = 0; i < len; i++) {
		struct json_object *jev;
		struct json_object *jev_family, *jev_ifname;
		struct json_object *jev_group_array, *jev_grp;
		const char *ifname, *family, *group;
		int grplen = 0;

		jev = json_object_array_get_idx(jev_array, i);

		json_object_object_get_ex(jev, "ifname", &jev_ifname);
		ifname = json_object_get_string(jev_ifname);

		json_object_object_get_ex(jev, "family", &jev_family);
		family = json_object_get_string(jev_family);

		json_object_object_get_ex(jev, "group", &jev_group_array);
		grplen = json_object_array_length(jev_group_array);
		for (j = 0; j < grplen; j++) {
			jev_grp = json_object_array_get_idx(jev_group_array, j);
			group = json_object_get_string(jev_grp);
			wifimngr_dbg("Setup event (%s, %s, %s)\n",
						ifname, family, group);
			ret = wifimngr_events_register(w, ifname, family, group);
			if (ret < 0)
				wifimngr_err("event_register failed %d\n", ret);
		}
	}

out_json:
	json_object_put(jevs);
	return ret;
}

struct wifimngr_device *wifimngr_lookup_wifi_device(struct wifimngr *w,
						    const char *device)
{
	size_t len;
	int i;

	if (!w || !device)
		return NULL;

	len = strlen(device);
	if (len == 0 || len >= sizeof(w->wdev[0].device))
		return NULL;

	for (i = 0; i < WIFI_DEV_MAX_NUM; i++) {
		if (!strlen(w->wdev[i].device))
			continue;

		if (!strcmp(w->wdev[i].device, device))
			return &w->wdev[i];
	}

	return NULL;
}

struct wifimngr_iface *wifimngr_lookup_wifi_iface(struct wifimngr *w,
						    const char *iface)
{
	size_t len;
	int i;

	if (!w || !iface)
		return NULL;

	len = strlen(iface);
	if (len == 0 || len >= sizeof(w->ifs[0].iface))
		return NULL;

	for (i = 0; i < WIFI_IF_MAX_NUM; i++) {
		if (!strlen(w->ifs[i].iface))
			continue;

		if (!strcmp(w->ifs[i].iface, iface))
			return &w->ifs[i];
	}

	return NULL;
}

int wifimngr_reconfig(struct wifimngr *w)
{
	struct wifi_ubus_object *p = NULL, *tmp;
	int ret;
	int i;

#ifdef WIFI_CACHE_SCANRESULTS
	wifimngr_flush_scanresults(w);
#endif

	/* read radios and interfaces from wireless config */
	ret = wifimngr_get_wifi_devices(w, w->conffile);
	if (ret < 0) {
		wifimngr_err("get_wifi_devices() ret = %d\n", ret);
		return -1;
	}

	/* remove wifi radio objects that are no longer valid and
	 * update 'priv' context for the ones that are still valid.
	 */
	list_for_each_entry_safe(p, tmp, &w->radiolist, list) {
		if (!wifimngr_is_wifi_device_object_valid(w, p)) {
			list_del(&p->list);
			wifimngr_del_object(w, &p->obj, true);
			p->priv = NULL;
			free(p);
		}
	}

	/* add wifi radio objects */
	for (i = 0; i < w->num_wifi_device; i++) {
		ret = wifimngr_add_radio_object(w, &w->wdev[i]);
		if (ret) {
			wifimngr_err("Failed to add '%s.%s' ubus object: %s\n",
				     WIFI_RADIO_OBJECT, w->wdev[i].device,
				     ubus_strerror(ret));
			return -1;
		}
	}

	ret = wifimngr_get_wifi_interfaces(w, w->conffile);
	if (ret < 0) {
		wifimngr_err("get_wifi_interfaces() ret = %d\n", ret);
		return -1;
	}

	ret = wifimngr_get_wifi_mlds(w, w->conffile);
	if (ret < 0) {
		wifimngr_err("get_wifi_mlds() ret = %d\n", ret);
		return -1;
	}

	/* remove wifi.ap* and wifi.bsta* objects that are no longer valid and
	 * update 'priv' context for the ones that are still valid.
	 */
	list_for_each_entry_safe(p, tmp, &w->iflist, list) {
		if (!wifimngr_is_wifi_iface_object_valid(w, p)) {
			list_del(&p->list);
			wifimngr_del_object(w, &p->obj, true);
			p->priv = NULL;
			free(p);
		}
	}


	/* add wifi.ap/bsta.* objects */
	for (i = 0; i < w->num_wifi_iface; i++) {
		if (w->ifs[i].disabled)
			continue;

#ifdef DONOT_CREATE_AFFILIATED_IFACE_OBJECT
		/* skip creating interface object for affiliated interfaces */
		if (strlen(w->ifs[i].mld))
			continue;
#endif
		ret = wifimngr_add_interface_object(w, &w->ifs[i]);
		if (ret) {
			wifimngr_err("Failed to add '%s.%s' ubus object: %s\n",
				     WIFI_AP_OBJECT, w->ifs[i].iface, ubus_strerror(ret));
			return -1;
		}
	}

	/* add wifi.apmld.* or wifi.bstamld.* objects */
	for (i = 0; i < w->num_wifi_mld; i++) {
		ret = wifimngr_add_mld_interface_object(w, &w->mld[i]);
		if (ret) {
			wifimngr_err("Failed to add '%s.%s' ubus object: %s\n",
				     w->mld[i].mode == WIFI_MODE_AP ?
				     WIFI_APMLD_OBJECT : WIFI_BSTAMLD_OBJECT,
				     w->mld[i].ifname, ubus_strerror(ret));
			return -1;
		}
	}

	wifimngr_event_exit(w);
	wifimngr_setup_events(w, w->evmap_file);

#ifdef WIFI_CACHE_SCANRESULTS
	wifimngr_get_initial_scanresults(w);
#endif
	return 0;
}

#ifdef WIFI_CACHE_SCANRESULTS
int wifimngr_device_init_channels(struct wifimngr_device *wdev)
{
	struct wifi_opclass supp_opclass[64] = {0};
	size_t num_opclass = ARRAY_SIZE(supp_opclass);
	uint32_t supp_channels[64] = {0};
	int num_supp_channels = 64;
	uint8_t channels[256] = {0};


	wifi_get_opclass_e4table(wdev->band, WIFI_BANDWIDTH_20, &num_opclass, supp_opclass);
	wifi_get_supp_channels(wdev->phy, supp_channels, &num_supp_channels, "", wdev->band, BW20);

	for (int i = 0; i < num_opclass; i++) {
		for (int j = 0; j < supp_opclass[i].opchannel.num; j++) {
			if (is_opclass_chgrp_supported(supp_opclass[i].opchannel.ch[j].ctrl_channels,
							num_supp_channels, supp_channels)) {
				for (int k = 0; k < 32 && supp_opclass[i].opchannel.ch[j].ctrl_channels[k] != 0; k++) {
					channels[supp_opclass[i].opchannel.ch[j].ctrl_channels[k]] = 1;
				}
			}
		}
	}

	for (int i = 0; i < sizeof(channels); i++) {
		if (channels[i] == 1) {
			wdev->ch[wdev->num_ch].channel = i;
			wdev->ch[wdev->num_ch++].freq = wifi_channel_to_freq_ex(i, wdev->band);
		}
	}

	return 0;
}

static void wifimngr_flush_wifi_device_scanresults(struct wifimngr_device *wdev)
{
	int k;

	for (k = 0; k < wdev->num_ch; k++) {
		if (wdev->ch[k].num_scanres > 0 || wdev->ch[k].scanres) {
			free(wdev->ch[k].scanres);
			wdev->ch[k].scanres = NULL;
			wdev->ch[k].num_scanres = 0;
		}
	}
}

void wifimngr_flush_scanresults(struct wifimngr *w)
{
	int i;

	for (i = 0; i < w->num_wifi_device; i++) {
		struct wifimngr_device *wdev = &w->wdev[i];

		wifimngr_flush_wifi_device_scanresults(wdev);
	}
}

static int wifimngr_update_scanresults(struct wifimngr_device *wdev,
				       int num_scanres,
				       struct wifi_bss *scanres)
{
	int i;

	for (i = 0; i < num_scanres; i++) {
		struct wifimngr_channel *ch =
			wifimngr_device_lookup_channel(wdev, scanres[i].channel);

		if (ch && ch->num_scanres < MAX_NUM_PER_CHANNEL_SCANRES) {
			struct wifi_bss *n;

			n = realloc(ch->scanres, (ch->num_scanres + 1) * sizeof(struct wifi_bss));
			if (!n) {
				wifimngr_warn("%s: failed to realloc() scanres\n", wdev->device);
				return -1;
			}

			ch->scanres = n;
			memset(&ch->scanres[ch->num_scanres], 0, sizeof(struct wifi_bss));
			memcpy(&ch->scanres[ch->num_scanres], &scanres[i], sizeof(struct wifi_bss));
			time(&ch->scanres_ts);
			ch->num_scanres++;
		}
	}

	return 0;
}

int wifimngr_get_initial_scanresults(struct wifimngr *w)
{
	int i;

	for (i = 0; i < w->num_wifi_device; i++) {
		struct wifi_bss scanres[NUM_SCANRES] = {0};
		int num = ARRAY_SIZE(scanres);
		int ret;

		wifimngr_device_init_channels(&w->wdev[i]);

		ret = wifi_get_band_scan_results(w->wdev[i].phy,
						 w->wdev[i].band,
						 scanres,
						 &num);
		if (!ret && num > 0)
			wifimngr_update_scanresults(&w->wdev[i], num, scanres);
	}

	return 0;
}

/* scan event data */
enum scan_event_data_attr {
	SCAN_EVENT_ATTR_FREQ,
	SCAN_EVENT_ATTR_SSID,
	NUM_ATTRS_SCAN_EVENT,
};

static const struct blobmsg_policy scan_event_policy[NUM_ATTRS_SCAN_EVENT] = {
	[SCAN_EVENT_ATTR_FREQ] = { .name = "freq", .type = BLOBMSG_TYPE_ARRAY },
	[SCAN_EVENT_ATTR_SSID] = { .name = "ssid", .type = BLOBMSG_TYPE_ARRAY },
};

int wifimngr_update_scanresults_cache(struct wifimngr_device *wdev, int sz,
				      char *evbuf)
{
	struct blob_attr *tb[NUM_ATTRS_SCAN_EVENT];
	struct wifi_bss scanres[NUM_SCANRES] = {0};
	uint32_t freq[MAX_NUM_SUPP_CHANNELS] = {0};
	int num = ARRAY_SIZE(scanres);
	struct blob_attr *msg, *attr;
	struct blob_buf b = {0};
	int num_freq = 0;
	int num_ssid = 0;
	int nfreq = 0;
	//int nssid = 0;
	int ret = 0;
	int rem, i;

	ret = wifi_get_band_scan_results(wdev->phy, wdev->band, scanres, &num);
	if (ret) {
		wifimngr_warn("%s: failed to get scanresults\n", wdev->device);
		return -1;
	}

	blob_buf_init(&b, 0);
	if (!blobmsg_add_json_from_string(&b, evbuf)) {
		wifimngr_warn("Failed to parse scan-event data: %s\n", evbuf);
		blob_buf_free(&b);
		return -1;
	}

	msg = b.head;
	blobmsg_parse(scan_event_policy, NUM_ATTRS_SCAN_EVENT, tb,
		      blob_data(msg), blob_len(msg));

	if (tb[SCAN_EVENT_ATTR_FREQ])
		num_freq = blobmsg_check_array(tb[SCAN_EVENT_ATTR_FREQ], BLOBMSG_TYPE_INT32);

	if (tb[SCAN_EVENT_ATTR_SSID])
		num_ssid = blobmsg_check_array(tb[SCAN_EVENT_ATTR_SSID], BLOBMSG_TYPE_STRING);

	blobmsg_for_each_attr(attr, tb[SCAN_EVENT_ATTR_FREQ], rem) {
		if (blobmsg_type(attr) != BLOBMSG_TYPE_INT32)
			break;

		freq[nfreq++] = blobmsg_get_u32(attr);
	}

	if (nfreq != num_freq) {
		wifimngr_warn("Ignore invalid freq data in scan-event\n");
		blob_buf_free(&b);
		return -1;
	}

	blobmsg_for_each_attr(attr, tb[SCAN_EVENT_ATTR_SSID], rem) {
		const char *ssid = NULL;

		if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
			break;

		ssid = blobmsg_get_string(attr);
		UNUSED(ssid);
	}

	//TODO: update ssid specific result
	UNUSED(num_ssid);

	/* flush stored scanresults for scannned freqs */
	for (i = 0; i < nfreq; i++) {
		struct wifimngr_channel *ch =
			wifimngr_device_lookup_freq(wdev, freq[i]);

		if (ch && ch->num_scanres > 0) {
			free(ch->scanres);
			ch->scanres = NULL;
			ch->num_scanres = 0;
		}
	}

	wifimngr_update_scanresults(wdev, num, scanres);
	blob_buf_free(&b);

	return 0;
}
#endif /* WIFI_CACHE_SCANRESULTS */

int wl_sta_ratings_recalc(void *libctx)
{
	struct radio_entry radios[4] = {0};
	int num_radio = 4;
	int ret;

	ret = wifi_radio_list(radios, &num_radio);
	if (ret)
		return -1;

	for (int i = 0; i < num_radio; i++) {
		struct iface_entry ifaces[16] = {0};
		int num_iface = 16;

		ret = wifi_list_iface(radios[i].name, ifaces, &num_iface);
		if (ret)
			continue;

		for (int j = 0; j < num_iface; j++) {
			unsigned char stas[384] = {0};
			int num_stas = 64;

			if (ifaces[j].mode != WIFI_MODE_AP)
				continue;

			ret = wifi_get_assoclist(ifaces[j].name, stas, &num_stas);
			if (ret)
				continue;

			for (int k = 0; k < num_stas; k++) {
				struct wifi_sta sta = {0};
				float rating = 0.0;

				ret = wifi_get_sta_info(ifaces[j].name, &stas[k*6], &sta);
				if (ret)
					continue;

				if (sta_ratings_calc) {
					rating = sta_ratings_calc(NULL, ifaces[j].name, &sta);
					wifimngr_dbg("STA " MACFMT": Rating = %.2f\n\n",
						     MAC2STR(sta.macaddr), rating);
				}
			}
		}
	}

	return 0;
}

static void wifimngr_hbtimer_cb(struct uloop_timeout *t)
{
	struct wifimngr *w = container_of(t, struct wifimngr, hbtimer);

	w->ticks++;
	switch (signal_pending) {
	case SIGHUP:
		wifimngr_dbg("Received SIGHUP.\n");
		signal_pending = 0;
		wifimngr_reconfig(w);
		break;
	case SIGINT:
	case SIGTERM:
		wifimngr_dbg("Received signal %d, exiting.\n", signal_pending);
		uloop_end();
		return;
	default:
		break;
	}

	if ((w->ticks % 5 == 0) && libsta_ratings)
		wl_sta_ratings_recalc(NULL);

	uloop_timeout_set(&w->hbtimer, 1000);
}

int wifimngr_init(struct wifimngr **w, struct wifimngr_cmdline_opts *opts)
{
	struct wifimngr *wm;
	int ret;

	set_sighandler(SIGPIPE, SIG_IGN);
	set_sighandler(SIGHUP, wifimngr_sighandler);
	set_sighandler(SIGINT, wifimngr_sighandler);
	set_sighandler(SIGTERM, wifimngr_sighandler);

	wm = calloc(1, sizeof(struct wifimngr));
	if (!wm)
		return -ENOMEM;

	wifimngr_extract_help();

	wm->hbtimer.cb = wifimngr_hbtimer_cb;
	INIT_LIST_HEAD(&wm->radiolist);
	INIT_LIST_HEAD(&wm->iflist);
	INIT_LIST_HEAD(&wm->event_list);
	wm->conffile = opts->conffile;
	wm->evmap_file = opts->evmap_file;
	*w = wm;

	ret = wl_load_sta_ratings_lib("LIBWIFI_STA_RATINGS_SOFILE");
	if (ret < 0)
		wifimngr_warn("failed to load sta-ratings lib (ret = %d)\n", ret);

	ret = wifimngr_get_wifi_devices(wm, opts->conffile);
	if (ret < 0)
		wifimngr_err("failed to read wifi-device (ret = %d)\n", ret);

	ret = wifimngr_get_wifi_interfaces(wm, opts->conffile);
	if (ret < 0)
		wifimngr_err("failed to read wifi-iface (ret = %d)\n", ret);

	uloop_init();
#ifdef HAS_UBUS
	wm->ubus_ctx = ubus_connect(opts->ubus_socket);
	if (!wm->ubus_ctx) {
		wifimngr_err("Failed to connect to ubus\n");
		goto out_error;
	}

	ubus_add_uloop(wm->ubus_ctx);
#endif

	ret = wifimngr_setup_events(wm, opts->evmap_file);
	if (ret)
		wifimngr_err("Failed to setup wifimngr events\n");

	ret = wifimngr_add_objects(wm);
	if (ret) {
		wifimngr_err("Failed to add wifimngr objects! aborting..\n");
		goto out_error;
	}

#ifdef WIFI_CACHE_SCANRESULTS
	wifimngr_get_initial_scanresults(wm);
#endif
	uloop_timeout_set(&wm->hbtimer, 1000);
	return 0;

out_error:
	uloop_done();
	wifimngr_event_exit(wm);
#ifdef HAS_UBUS
	if (wm->ubus_ctx)
		ubus_free(wm->ubus_ctx);
#endif
	wifimngr_free_help();
	wl_unload_sta_ratings_lib();
#ifdef WIFI_CACHE_SCANRESULTS
	wifimngr_flush_scanresults(wm);
#endif
	free(wm);
	*w = NULL;
	return -1;
}

int wifimngr_run(struct wifimngr *w)
{
	UNUSED(w);
	uloop_run();

	return 0;
}

int wifimngr_exit(struct wifimngr *w)
{
	struct wifi_ubus_object *p = NULL, *tmp;

	if (!w)
		return -EINVAL;

	uloop_done();

#ifdef WIFI_CACHE_SCANRESULTS
	wifimngr_flush_scanresults(w);
#endif
	wl_unload_sta_ratings_lib();

	list_for_each_entry_safe(p, tmp, &w->iflist, list) {
		list_del(&p->list);
		wifimngr_del_object(w, &p->obj, false);
		free(p);
	}

	p = NULL;
	list_for_each_entry_safe(p, tmp, &w->radiolist, list) {
		list_del(&p->list);
		wifimngr_del_object(w, &p->obj, false);
		free(p);
	}

	list_del_init(&w->iflist);
	list_del_init(&w->radiolist);

	wifimngr_del_object(w, &w->wifi_obj, false);
	free(w->wifi_obj.type);

	wifimngr_del_object(w, &w->wifi_wps_obj, false);
	free(w->wifi_wps_obj.type);

	wifimngr_event_exit(w);

#ifdef HAS_UBUS
	ubus_free(w->ubus_ctx);
#endif
	w->ubus_ctx = NULL;
	free(w);
	wifimngr_free_help();

	return 0;
}

int wifimngr_main(struct wifimngr_cmdline_opts *opts)
{
	struct wifimngr *w = NULL;
	int ret = 0;

#if defined(WIFIMNGR_BUILD_TR181_PLUGIN) && defined(HAS_UBUS)
	bbfdm_plugin_init(opts->dm_type, opts->loglevel);
#endif

	restart_logging(opts);

	ret = wifimngr_init(&w, opts);
	if (ret)
		goto exit;

#if defined(WIFIMNGR_BUILD_TR181_PLUGIN) && defined(HAS_UBUS)
	ret = bbfdm_plugin_register(w);
	if (ret)
		goto exit;
#endif

	wifimngr_run(w);

#if defined(WIFIMNGR_BUILD_TR181_PLUGIN) && defined(HAS_UBUS)
	bbfdm_plugin_free(w);
#endif

exit:
	wifimngr_exit(w);
	stop_logging();
	return ret;
}

void wifimngr_version(void)
{
	printf("version : %s.%s\n", wifimngr_base_version, wifimngr_xtra_version);
	printf("libwifi version: %s\n", libwifi_get_version());
}
