/*
 * agent.c - Multi-AP easymesh agent
 *
 * Copyright (C) 2019-2023 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for licensing information.
 *
 */
#include "agent.h"

#include <1905_tlvs.h>
#include <cmdu.h>
#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
#include <dpp_api.h>
#endif /* USE_LIBDPP */
#endif
#include <easy/timestamp.h>
#include <easy/utils.h>
#include <easymesh.h>
#include <errno.h>
#include <i1905_wsc.h>
#include <inttypes.h>
#include <json-c/json_object.h>
#include <json-c/json_tokener.h>
#include <json-c/json_types.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubus.h>
#include <map_module.h>
#include <net/if.h>
#include <netlink/socket.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ubusmsg.h>
#include <uci.h>
#include <unistd.h>
#include <wifiutils.h>
#include <timer.h>

#include "agent_cmdu.h"
#include "agent_map.h"
#include "agent_tlv.h"
#include "agent_ubus.h"
#include "agent_ubus_dbg.h"
#include "agent_ubus_events.h"
#include "assoc_ctrl.h"
#include "autoconfig.h"
#include "backhaul.h"
#include "backhaul_blacklist.h"
#include "config.h"
#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
#include "dpp.h"
#include "dpphandler.h"
#endif /* USE_LIBDPP */
#include "qos.h"
#endif
#include "extension.h"
#if (EASYMESH_VERSION >= 6)
#include "mld.h"
#endif
#include "nl.h"
#include "scan.h"
#include "steer_rules.h"
#include "timer_impl.h"
#include "unasta.h"
#include "utils/1905_ubus.h"
#include "utils/debug.h"
#include "utils/liblist.h"
#include "utils/utils.h"
#include "wifi.h"
#include "wifi_caps.h"
#include "vendor.h"

struct json_object;

#define map_plugin	"ieee1905.map"

#define BOOT_UP_SCAN_TIME	(0 * 1000)
#define BOOT_UP_SCAN_ITV	(2 * 1000)
#define BOOT_UP_SCAN_MAX_TRY 5

static struct agent *this_agent;

static int agent_init_interfaces(struct agent *a);
static void parse_radio_stats(struct ubus_request *req, int type,
			      struct blob_attr *msg);
static void agent_free_pending_async(struct agent *a);

static void agent_free_nodes(struct agent *a);
static void agent_free_wifi_assoc_frames(struct agent *a);
static int agent_req_btm_steer(struct agent *a, const char *ifname,
		unsigned char *sta_macaddr, uint8_t pref_num, struct nbr *pref,
		uint32_t validity_int);
static void netif_free(struct netif_ap *ap);
static void netif_reset(struct netif_ap *ap);

static void bsta_steer_cb(atimer_t *t)
{
	struct netif_bk *bk = container_of(t, struct netif_bk, steer_timeout);
	struct agent *a = bk->agent;
	char fmt[64] = {0};
	uint8_t *prev_bssid = bk->bsta_steer.prev_bssid;

	agnt_dbg(LOG_BSTEER, "%s: steer timer expired, restoring old bssid (" MACFMT ") for"\
	    "bsta %s\n", __func__, MAC2STR(prev_bssid), bk->ifname);

	wifi_set_iface_bssid(bk, prev_bssid);

	/* Flawfinder: ignore */
	snprintf(fmt, sizeof(fmt), "set_network_bssid %s " MACFMT,
		 bk->ifname, MAC2STR(prev_bssid));
	agent_exec_platform_scripts(fmt);

	bk->bsta_steer.expired = true;

	if (bk->bsta_steer.trigger == BK_STEER_LOCAL) {
		agent_bsta_steer_clear(bk);
	} else if (a->connected) {
		/* if connected we can send response right away */
		bk->bsta_steer.reason = ERR_REASON_BH_STEERING_NOT_FOUND;
		send_backhaul_sta_steer_response(a, bk,
						 bk->bsta_steer.ul_ifname);
		agent_bsta_steer_clear(bk);
	}
}

int agent_exec_platform_scripts(const char *arg)
{
	char buf[16] = {0};

	warn("/lib/wifi/multiap %s\n", arg);
	chrCmd(buf, sizeof(buf), "/lib/wifi/multiap %s", arg);
	return atoi(buf);
}

/* find node by macaddress */
struct node *agent_find_node(struct agent *a, uint8_t *almac)
{
	struct node *n = NULL;

	list_for_each_entry(n, &a->nodelist, list) {
		if (!memcmp(n->alid, almac, 6))
			return n;
	}

	return NULL;
}

struct node *agent_alloc_node(struct agent *a, uint8_t *almac)
{
	struct node *n;

	n = calloc(1, sizeof(struct node));
	if (!n) {
		warn("OOM: node malloc failed!\n");
		return NULL;
	}

	n->agent = a;
	memcpy(n->alid, almac, 6);
	n->map_profile = MULTIAP_PROFILE_1;

	list_add(&n->list, &a->nodelist);
	a->num_nodes++;

	dbg("%s: ------- " MACFMT "\n", __func__, MAC2STR(almac));
	return n;
}

struct node *agent_add_node(struct agent *a, uint8_t *almac)
{
	struct node *n;

	n = agent_find_node(a, almac);
	if (!n) {
		n = agent_alloc_node(a, almac);
		if (!n) {
			err("%s: failed to allocate node "MACFMT"\n",
			    __func__, MAC2STR(almac));
			return NULL;
		}
	} else {
		return n;
	}

	/* Add actions
	 * 1. Write to UCI?
	 */

#if (EASYMESH_VERSION > 2)
	qos_sync_rules_to_nodes(a);
#endif

	return n;
}


struct wifi_radio_element *agent_get_radio_by_band(struct agent *a,
						   enum wifi_band band)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (re->band == band)
			return re;
	}

	return NULL;
}

struct wifi_radio_element *agent_get_radio_by_name(struct agent *a, const char *name)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (!strncmp(re->name, name, sizeof(re->name)))
			return re;
	}

	return NULL;
}


struct wifi_radio_element *agent_get_radio(struct agent *a, uint8_t *macaddr)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (!memcmp(re->macaddr, macaddr, 6))
			return re;
	}

	return NULL;
}

struct wifi_radio_element *agent_get_radio_with_ap(struct agent *a, uint8_t *bssid)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			if (!memcmp(ap->bssid, bssid, 6))
				return re;
		}
	}

	return NULL;
}

#if (EASYMESH_VERSION >= 6)
struct netif_ap *agent_get_ap_by_bss_index(struct wifi_radio_element *re,
		uint8_t bss_index)
{
	struct netif_ap *ap;

	list_for_each_entry(ap, &re->aplist, list) {
		if (ap->cfg && ap->cfg->bss_index == bss_index)
			return ap;
	}

	return NULL;
}
#endif

struct netif_ap *agent_get_ap_by_ifname(struct agent *a, const char *ifname)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			if (!memcmp(ap->ifname, ifname, sizeof(ap->ifname)))
				return ap;
		}
	}

	return NULL;
}

struct sta *agent_get_sta(struct agent *a, uint8_t *sta)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			struct sta *s = NULL;

			list_for_each_entry(s, &ap->stalist, list) {
				if (memcmp(s->macaddr, sta, 6) == 0) {
					return s;
				}
			}
		}
	}

	return NULL;
}


struct netif_ap *agent_get_ap_with_sta(struct agent *a, uint8_t *sta)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			struct sta *s = NULL;

			trace("Looking for STAs in bssid = " MACFMT "\n",
					MAC2STR(ap->bssid));

			list_for_each_entry(s, &ap->stalist, list) {
				trace("stalist: " MACFMT "\n", MAC2STR(s->macaddr));
				if (!memcmp(s->macaddr, sta, 6))
					return ap;
			}
		}
	}

	return NULL;
}

static const char *wifi_ifname_to_radioname(struct agent *a, char *ifname)
{
	struct agent_config_radio *rcfg;
	const char *name;

	/* do a backup lookup from configs */
	rcfg = get_agent_config_radio_from_iface(&a->cfg, ifname);
	if (rcfg) {
		name = rcfg->name;
		return name;
	}

	trace("%s: couldn't find radio for ifname %s\n",
		   __func__, ifname);

	return NULL;
}

struct wifi_radio_element *agent_get_radio_with_ifname(struct agent *a,
		char *ifname)
{
	const char *radio;

	radio = wifi_ifname_to_radioname(a, ifname);
	if (!radio)
		return NULL;

	return agent_get_radio_by_name(a, radio);
}

static void wifiagent_log_steer(struct agent *a,
				unsigned char *stamac,
				char *vifname,
				char *steer_type,
				unsigned char *to_bss)
{
	char ev[512] = {0};
	unsigned char macaddr[6] = {0};
	char ifname[IFNAMSIZ] = {0};
	char type[32] = {0};

	if (!stamac || !vifname || !steer_type)
		return;

	memcpy(macaddr, stamac, 6);
	strncpy(ifname, vifname, IFNAMSIZ - 1);
	strncpy(type, steer_type, 31);

	snprintf(ev, sizeof(ev), "{\"macaddr\":\""MACFMT"\""
		 ",\"ap\":\"%.16s\",\"action\":\"%.32s\"",
		 MAC2STR(macaddr), ifname, type);
	if (to_bss) {
		snprintf(ev + strlen(ev), sizeof(ev), ",\"to\":\""MACFMT"\"}",
							MAC2STR(to_bss));
	} else {
		snprintf(ev + strlen(ev), sizeof(ev), "%s", "}");
	}

	info("steer: %s\n", ev);
	agent_notify_event(a, "map.agent", ev);
}

#ifdef UBUS_STA_INFO
static void wifiagent_log_stainfo(struct agent *a, struct sta *s)
{
	int i;
	char ev[512] = {0};
	char rssis[64] = {0};

	if (!a || !s)
		return;

	for (i = 0; i < 4; i++) {
		if (s->rssi[i] > -100 && s->rssi[i] <= -10)
			snprintf(rssis + strlen(rssis), sizeof(rssis), "%d%s",
					s->rssi[i], i == 3 ? "" : ",");
	}

	snprintf(ev, sizeof(ev),
			"{\"macaddr\":\""MACFMT"\""
			",\"rssi_avg\":\"%d\""
			",\"rssi\":\"%s\""
			",\"tx_rate\":%u"
			",\"rx_rate\":%u"
			",\"tx_Bps\":%d"
			",\"rx_Bps\":%d"
			",\"tx_pkts\":%" PRIu64
			",\"rx_pkts\":%" PRIu64
			",\"tx_fpkts\":%u,\"rx_fpkts\":%u,\"rtx_pkts\":%u}",
			MAC2STR(s->macaddr), s->rssi_avg[0], rssis,
			s->tx_rate, s->rx_rate,
			s->tx_thput, s->rx_thput,
			s->tx_pkts,
			s->rx_pkts,
			s->tx_fail_pkts,
			s->rx_fail_pkts,
			s->rtx_pkts);

	agent_notify_event(a, "map.agent", ev);
}
#endif

void wifiagent_log_cntlrinfo(struct agent *a, uint8_t *secondary_almac)
{
	char ev[512] = {0};

	dbg("%s: called.\n", __func__);

	if (!a)
		return;

	snprintf(ev, sizeof(ev),
		 "{\"active_cntlr\":%u," \
		 "\"cntlr_almac\":\""MACFMT"\"," \
		 "\"local_cntlr\":%u," \
		 "\"multiple_cntlr_found\":%u}",
		 a->active_cntlr ? true : false,
		 MAC2STR(a->cntlr_almac),
		 is_local_cntlr_running() ? 1 : 0,
		 a->multiple_cntlr ? 1 : 0);

	if (secondary_almac) {
		char sec[64] = {0};

		snprintf(sec, sizeof(sec), ",\"secondary_cntlr_almac\":\""MACFMT"\"",
			 MAC2STR(secondary_almac));
		ev[strlen(ev) - 1] = '\0';
		strncat(ev, sec, sizeof(ev) - strlen(ev) - 1);
		strncat(ev, "}", sizeof(ev) - strlen(ev) - 1);
	}

	trace("cntlrinfo: %s\n", ev);
	agent_notify_event(a, "map.agent", ev);
}

static void async_parse_radio_stats(struct ubus_request *req, int type,
			     	    struct blob_attr *msg)
{
	struct agent_async_request *async_req = container_of(req, struct agent_async_request, ubus_req);

	parse_radio_stats(req, type, msg);

	if (async_req->success_cb)
		async_req->success_cb(async_req);
}


static void async_radio_status_success(struct agent_async_request *req)
{
	struct wifi_radio_element *re = (struct wifi_radio_element *) req->ubus_req.priv;
	struct agent *a = re->agent;
	uint8_t zero_block[SHA256_LENGTH] = {0};

	if (!memcmp(zero_block, re->autconfig.sha256, SHA256_LENGTH))
		autoconfig_radio_hash_from_file(re->macaddr, re->autconfig.sha256);

	a->band_pending_mask &= ~re->band;

	if (a->band_pending_mask == 0) {
		/* all pending radio status calls have returned */
		agent_init_interfaces(a);
	}
}

static void async_radio_timeout(atimer_t *t)
{
	struct agent_async_request *async_req = container_of(t, struct agent_async_request, timeout);
	struct wifi_radio_element *re = (struct wifi_radio_element *) async_req->ubus_req.priv;
	struct agent *a = re->agent;

	ubus_abort_request(async_req->ubus_req.ctx, &async_req->ubus_req);

	a->band_pending_mask &= ~re->band;
	if (a->band_pending_mask == 0) {
		/* all pending radio status calls have returned */
		agent_init_interfaces(a);
	}

	dbg("%s: async cb timed out: %s\n", __func__, async_req->name);
	list_del(&async_req->list);
	free(async_req);
	timer_set(&a->init_ifaces_scheduler, IFACE_TIMEOUT * 1000);
}

struct wifi_assoc_frame *wifi_get_frame(struct agent *a, uint8_t *macaddr)
{
	struct wifi_assoc_frame *f = NULL;

	list_for_each_entry(f, &a->framelist, list) {
		trace("frame mac: " MACFMT " input: " MACFMT "\n",
				MAC2STR(f->macaddr),
				MAC2STR(macaddr));
		if (!memcmp(f->macaddr, macaddr, 6))
			return f;
	}

	return NULL;
}



/* lookup netif_bk struct by device */
struct netif_bk *agent_bsta_in_radio(struct agent *a,
		const char *device)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (re->has_bsta && !strncmp(device, re->name, sizeof(re->name)))
			return &re->bk;
	}

	return NULL;
}

/* get netif_ap based on bssid */
struct netif_ap *agent_get_ap(struct agent *a, uint8_t *bssid)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			if (hwaddr_equal(ap->bssid, bssid))
				return ap;
		}
	}

	return NULL;
}

struct netif_bk *agent_get_bsta_by_band(struct agent *a, enum wifi_band band)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (re->has_bsta && re->band == band)
			return &re->bk;
	}

	return NULL;
}

struct netif_bk *agent_get_bsta_by_ifname(struct agent *a, const char *ifname)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (re->has_bsta && !strncmp(re->bk.ifname, ifname, sizeof(re->bk.ifname)))
			return &re->bk;
	}

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld) {
		if (!strncmp(a->bstamld->bk.ifname, ifname, sizeof(a->bstamld->bk.ifname)))
			return &a->bstamld->bk;
	}
#endif

	return NULL;
}

/* find bkhaul by hwaddr */
struct netif_bk *agent_get_bsta(struct agent *a, uint8_t *macaddr)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (re->has_bsta && !memcmp(re->bk.macaddr, macaddr, 6))
			return &re->bk;
	}

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld) {
		if (!memcmp(a->bstamld->bk.macaddr, macaddr, 6))
			return &a->bstamld->bk;
	}
#endif
	return NULL;
}

/* find wds by interface name */
struct netif_wds *agent_get_wds_by_ifname(struct agent *a, const char *ifname)
{
	struct netif_wds *p = NULL;

	list_for_each_entry(p, &a->wdslist, list) {
		if (!strncmp(p->ifname, ifname, sizeof(p->ifname)))
			return p;
	}

	return NULL;
}

void agent_link_ap_to_cfg(struct agent *a)
{
	struct netif_apcfg *acfg = NULL;

	list_for_each_entry(acfg, &a->cfg.aplist, list) {
		struct netif_ap *f;

		f = agent_get_ap_by_ifname(a, acfg->name);
		if (!f)
			continue;

		f->cfg = acfg;
	}
}

static void agent_config_load_post_action(struct agent *a)
{
	agent_link_ap_to_cfg(a);

	if (a->cfg.pcfg)
		a->pvid = a->cfg.pcfg->pvid;
}

uint32_t agent_config_reload(struct agent *a)
{
	int old_debug_level = a->cfg.debug_level;
	uint32_t diff = CONFIG_DIFF_NONE;

	if (agent_config_load(&a->cfg))
		return CONFIG_DIFF_NONE;

	agent_config_load_post_action(a);

	/* Check for debug level changes */
	if (old_debug_level != a->cfg.debug_level)
		diff |= CONFIG_DIFF_DEBUG;

	return diff;
}

uint8_t agent_steer_next_dialog_token(struct agent *a)
{
	a->steer_dialog_token %= 255;

	return ++a->steer_dialog_token;
}

typedef void (*destructor)(void *);

#define delete_expired_entries(vif, type, h, l, ts_member, tmo, func, _nr) \
do {									   \
	type *e = NULL, *etmp;							   \
	typeof((destructor) func) fptr = (func);			   \
									   \
	list_for_each_entry_safe(e, etmp, h, l) {			   \
		if (timestamp_expired(&e->ts_member, tmo)) {		   \
			dbg("%s: Entry aged out.. delete.\n", __func__);   \
			if (fptr)					   \
				fptr(e);				   \
			list_del(&e->l);				   \
			(_nr)--;                                           \
			free(e);					   \
		}							   \
	}								   \
} while (0)

#ifdef VENDOR_EXTENSION
static int agent_load_extensions(struct agent *a)
{
#define EXTENSION_PLUGIN_MAX_NUM	8
	char *argv[EXTENSION_PLUGIN_MAX_NUM] = {0};
	struct agent_extension_config *e = NULL;
	int argc = 0;
	int ret;


	list_for_each_entry(e, &a->cfg.extlist, list) {
		argv[argc++] = e->name;

		if (argc >= EXTENSION_PLUGIN_MAX_NUM)
			break;
	}

	info("map-agent: load extensions\n");
	INIT_LIST_HEAD(&a->extlist);
	ret = load_extensions(argc, argv, &a->extlist);

	return ret;
}

static void agent_unload_extensions(struct agent *a)
{
	if (a)
		unload_extensions(&a->extlist);
}
#endif

int (*compare)(const void *a, const void *b);

/* Private 'cmp()' function to sort neighbors by RSSI decreasing */
static int cmp_nbr_rssi(void *priv, struct list_head *a, struct list_head *b)
{
	struct pref_neighbor *ea, *eb;

	UNUSED(priv);
	ea = container_of(a, struct pref_neighbor, list);
	eb = container_of(b, struct pref_neighbor, list);

	return eb->rssi - ea->rssi;
}

#define alloc_entry(type)		\
({					\
	type *__n;			\
	__n = calloc(1, sizeof(type));	\
	__n ? &__n->list : NULL;	\
})

#define free_entry(ptr, type)					\
({								\
	if (ptr)						\
		free(container_of(ptr, type, list));		\
})

#define copy_entry(from, to, type)			\
({							\
	type *__s, *__d;				\
	if (from && to)	{				\
		__s = container_of(from, type, list);	\
		__d = container_of(to, type, list);	\
		memcpy(__d, __s, sizeof(type));		\
	}						\
})

static void *alloc_neighbor_entry(void)
{
	struct neighbor *n;

	n = calloc(1, sizeof(struct neighbor));
	if (n)
		return &n->list;

	return NULL;
}

static void free_neighbor_entry(struct list_head *n)
{
	free_entry(n, struct neighbor);
}

static void copy_neighbor_entry(struct list_head *from, struct list_head *to)
{
	copy_entry(from, to, struct neighbor);
}

static void *alloc_sta_neighbor_entry(void)
{
	struct sta_neighbor *n;

	n = calloc(1, sizeof(struct sta_neighbor));
	if (n)
		return &n->list;

	return NULL;
}

static void free_sta_neighbor_entry(struct list_head *n)
{
	free_entry(n, struct sta_neighbor);
}

static void copy_sta_neighbor_entry(struct list_head *from,
		struct list_head *to)
{
	copy_entry(from, to, struct sta_neighbor);
}

/* Function used to match duplicate entries in neighbor list */
static int match_bssid(void *priv, struct list_head *a, struct list_head *b)
{
	struct neighbor *ea;
	struct sta_neighbor *eb;

	UNUSED(priv);
	ea = container_of(a, struct neighbor, list);
	eb = container_of(b, struct sta_neighbor, list);

	return hwaddr_equal(ea->nbr.bssid, eb->nbr.bssid);
}

static struct list_head *create_joined_node(void *priv, struct list_head *a,
		struct list_head *b)
{
	struct pref_neighbor *new;
	struct neighbor *ea = NULL;
	struct sta_neighbor *eb = NULL;
	struct wifi_bss bss;
	struct sta *sta = (struct sta *)priv;
	struct netif_ap *ap = NULL;

	if (a)
		ea = container_of(a, struct neighbor, list);

	if (b)
		eb = container_of(b, struct sta_neighbor, list);

	if (!ea && !eb)
		return NULL;

	new = malloc(sizeof(*new));
	if (!new) {
		warn("OOM: failed to alloc preferred neighbor!\n");
		return NULL;
	}

	memset(new, 0, sizeof(*new));

	if (ea)
		memcpy(new->bssid, ea->nbr.bssid, 6);
	else
		memcpy(new->bssid, eb->nbr.bssid, 6);

	if (sta)
		ap = agent_get_ap(sta->agent, sta->bssid);

	memset(&bss, 0, sizeof(struct wifi_bss));
	if (ap && wifi_scanresults_get_bss(ap->ifname, new->bssid, &bss)) {
		/* new->rssi = bss.rssi; */ /* it is AP's view; meaningless */
		new->ch_util = bss.load.utilization;
		new->num_stas = bss.load.sta_count;
	}

	if (ea) {
		new->bssid_info = ea->nbr.bssid_info;
		new->reg = ea->nbr.reg;
		new->channel = ea->nbr.channel;
		new->phy = ea->nbr.phy;
		new->flags = ea->flags;
		/* TODO: est_bwrate from snr */
	}

	if (eb) {
		new->rssi = eb->nbr.rssi; /* override rssi by sta's view */
		new->rsni = eb->nbr.rsni;
	}

	return &new->list;
}

static void free_joined_node(void *priv, struct list_head *a)
{
	struct pref_neighbor *ea = NULL;

	UNUSED(priv);
	if (a)
		ea = container_of(a, struct pref_neighbor, list);

	free(ea);
}

static void recalc_desired_neighbors(struct sta *s)
{
	struct list_head nbrlist_copy, sta_nbrlist_copy;
	struct netif_ap *ap;


	ap = agent_get_ap(s->agent, s->bssid);
	if (!ap)
		return;

	/**
	 * First, does a splice of the two lists -
	 *	11k neighbor list and
	 *	11k STA beacon list.
	 *
	 * It then rearranges the combined list on the basis of decreasing
	 * 'rssi'.
	 * In rule matching modules, walk the pref_nbrlist to select the
	 * next best neighbor Bss based on rssi, bssload, est_dl_throughput etc.
	 */

	INIT_LIST_HEAD(&nbrlist_copy);
	list_dup(&ap->nbrlist, &nbrlist_copy,
			alloc_neighbor_entry,
			free_neighbor_entry,
			copy_neighbor_entry);

	INIT_LIST_HEAD(&sta_nbrlist_copy);
	list_dup(&s->sta_nbrlist, &sta_nbrlist_copy,
			alloc_sta_neighbor_entry,
			free_sta_neighbor_entry,
			copy_sta_neighbor_entry);

	list_flush(&s->pref_nbrlist, struct pref_neighbor, list);

	dbg_list_print("Neighbor nodes", &nbrlist_copy,
			struct neighbor, list, nbr.bssid);

	list_join_uniq(s, &nbrlist_copy, &sta_nbrlist_copy, &s->pref_nbrlist,
			match_bssid, create_joined_node, free_joined_node,
			free_neighbor_entry, free_sta_neighbor_entry);

	dbg_list_print("STA Neighbors", &s->pref_nbrlist,
			struct pref_neighbor, list, bssid);

	list_flush(&nbrlist_copy, struct neighbor, list);
	list_flush(&sta_nbrlist_copy, struct sta_neighbor, list);

	list_sort(NULL, &s->pref_nbrlist, cmp_nbr_rssi);
}

static void agent_sta_add_rssi(struct sta *s, int8_t new_rssi)
{
	int i;

	/* Shift all elements to the right */
	for (i = MAX_RSSI_MEAS - 1; i > 0; i--)
		s->rssi_avg[i] = s->rssi_avg[i - 1];

	/* Insert new RSSI at index 0 */
	s->rssi_avg[0] = new_rssi;

	/* Update the number of RSSI samples */
	if (s->num_rssi < MAX_RSSI_MEAS)
		s->num_rssi++;
}

static int update_sta_entry(struct agent *a, struct netif_ap *ap, struct wifi_sta *e)
{
	struct sta *s = NULL;

	list_for_each_entry(s, &ap->stalist, list) {
		if (!memcmp(s->macaddr, e->macaddr, 6)) {
			bool no_traffic = true;

			/* dbg("%s: update STA " MACFMT " entry\n", __func__,
			 *	MAC2STR(s->macaddr));
			 */
			timestamp_update(&s->last_update);
			if (s->rssi[0] != e->rssi[0] ||
					s->rssi[1] != e->rssi[1] ||
					s->rssi[2] != e->rssi[2] ||
					s->rssi[3] != e->rssi[3]) {

				no_traffic = false;

				s->rssi[0] = e->rssi[0];
				s->rssi[1] = e->rssi[1];
				s->rssi[2] = e->rssi[2];
				s->rssi[3] = e->rssi[3];
			}

			if (s->rssi_avg[0] != e->rssi_avg)
				no_traffic = false;
			agent_sta_add_rssi(s, (int8_t)e->rssi_avg);

			s->connected_ms = e->conn_time * 1000;
			s->tx_rate = e->tx_rate.rate * 1000;
			s->rx_rate = e->rx_rate.rate * 1000;

			s->rx_airtime = e->tx_airtime / 1000;
			s->tx_airtime = e->rx_airtime / 1000;

			s->tx_thput = 0;
			s->rx_thput = 0;
			if (e->stats.tx_bytes != s->tx_bytes ||
					e->stats.rx_bytes != s->rx_bytes) {
				uint64_t tx_bytes_lastsec;
				uint64_t rx_bytes_lastsec;

				no_traffic = false;

				tx_bytes_lastsec = s->tx_bytes;
				rx_bytes_lastsec = s->rx_bytes;
				s->tx_bytes = e->stats.tx_bytes;
				s->rx_bytes = e->stats.rx_bytes;
				s->tx_thput = s->tx_bytes - tx_bytes_lastsec;
				s->rx_thput = s->rx_bytes - rx_bytes_lastsec;
			}

			s->est_rx_thput = e->est_rx_thput;
			s->est_tx_thput = e->est_tx_thput;

			if (e->stats.tx_pkts != s->tx_pkts ||
					e->stats.rx_pkts != s->rx_pkts) {

				no_traffic = false;

				s->tx_pkts = e->stats.tx_pkts;
				s->rx_pkts = e->stats.rx_pkts;
			}

			if (no_traffic)
				s->stale_stats_cnt++;
			else
				s->stale_stats_cnt = 0;

			s->tx_fail_pkts = e->stats.tx_fail_pkts;
			s->rx_fail_pkts = e->stats.rx_fail_pkts;
			s->rtx_pkts = e->stats.tx_retry_pkts;

			memcpy(s->bssid, e->bssid, 6);

			if (!!(e->caps.valid & WIFI_CAP_RM_VALID)) {
				if (wifi_cap_isset(e->cbitmap, WIFI_CAP_RM_BCN_ACTIVE)) {
					s->rrm_mode |= NBR_PARAM_BCN_ACTIVE;
					s->supports_bcnreport = true;
				}

				if (wifi_cap_isset(e->cbitmap, WIFI_CAP_RM_BCN_PASSIVE)) {
					s->rrm_mode |= NBR_PARAM_BCN_PASSIVE;
					s->supports_bcnreport = true;
				}

				if (wifi_cap_isset(e->cbitmap, WIFI_CAP_RM_BCN_TABLE)) {
					s->rrm_mode |= NBR_PARAM_BCN_TABLE;
					s->supports_bcnreport = true;
				}
			}
#ifdef UBUS_STA_INFO
			wifiagent_log_stainfo(ap->agent, s);
#endif
			return 0;
		}
	}

	return -1;
}

static int agent_req_beacon_metrics(struct agent *a,
	struct netif_ap *ap, uint8_t *sta_addr, uint8_t opclass,
	uint8_t channel, uint8_t *bssid,
	uint8_t rrm_mode, uint8_t reporting_detail,
	uint8_t ssid_len, char *ssid, uint8_t num_report,
	uint8_t *report, uint8_t num_element, uint8_t *element)
{
	struct wifi_request_neighbor_param param = {};

	trace("agent: %s: --->\n", __func__);

	if (!sta_addr)
		return -1;

	param.opclass = opclass;
	param.channel = channel;
	param.bssid = bssid;
	param.reporting_detail = reporting_detail;
	param.ssid_len = ssid_len;
	param.ssid = ssid;
	param.num_report = num_report;
	param.report = report;
	param.num_element = num_element;
	param.element = element;
	param.rrm_mode = rrm_mode;

	return wifi_req_neighbor(ap->ifname, sta_addr, &param);
}

static void wifi_sta_bcn_req(atimer_t *t)
{
	struct sta *s = container_of(t, struct sta, sta_bcn_req_timer);
	struct agent *a = s->agent;
	struct sta_bcn_req *breq;
	struct netif_ap *ap;

	ap = agent_get_ap(s->agent, s->bssid);
	if (!ap)
		return;

	if (s->sta_bcn_req_nr < 1)
		/* No request enqueued */
		return;

	/* LIFO */
	breq = &s->bcn_req_queue[s->sta_bcn_req_nr - 1];

	agent_req_beacon_metrics(a, breq->ap, s->macaddr,
			breq->opclass, breq->channel, breq->bssid,
			s->rrm_mode,
			breq->reporting_detail,
			breq->ssid_len, breq->ssid, 0, NULL,
			breq->num_element, breq->element);

	/* Dequeue */
	memset(breq, 0, sizeof(struct sta_bcn_req));
	s->sta_bcn_req_nr--;

	if (s->sta_bcn_req_nr >= 1)
		/* requests remainig, send next in 3 sec */
		timer_set(&s->sta_bcn_req_timer, 3 * 1000);
}


/* Translate internal 'pref_neighbor' to wifi's 'nbr' struct */
void agent_pref_neighbor_to_nbr(struct pref_neighbor *pref_nbr_src,
		struct nbr *wifi_nbr_dst)
{
	memcpy(wifi_nbr_dst->bssid, pref_nbr_src->bssid, 6);
	wifi_nbr_dst->bssid_info = pref_nbr_src->bssid_info;
	wifi_nbr_dst->reg = pref_nbr_src->reg;
	wifi_nbr_dst->channel = pref_nbr_src->channel;
	wifi_nbr_dst->phy = pref_nbr_src->phy;
}

static int steer_sta(struct sta *s, struct pref_neighbor *pref_nbr)
{
	struct pref_neighbor *pref_nbr_el;
	struct agent *a = s->agent;
	struct nbr wifi_nbr_el = {};
	struct netif_ap *ap;
	int ret = 0;

	ap = agent_get_ap(a, s->bssid);
	if (!ap)
		return -1;

	s->steer_secs++;
	trace("Steer STA " MACFMT " (steer_secs = %u)\n",
				MAC2STR(s->macaddr), s->steer_secs);

	if (pref_nbr) {
		pref_nbr_el = pref_nbr;
	} else {
		if (list_empty(&s->pref_nbrlist)) {
			trace("No better BSS for STA " MACFMT " found\n",
							MAC2STR(s->macaddr));
			return 0;
		}

		pref_nbr_el = list_first_entry(&s->pref_nbrlist,
						struct pref_neighbor, list);
	}

	if (!pref_nbr_el) {
		warn("Unexpected! pref_nbr_el is NULL or empty!\n");
		return -1;
	}

	/* Translate internal 'pref_neighbor' to wifi's 'nbr' struct */
	agent_pref_neighbor_to_nbr(pref_nbr_el, &wifi_nbr_el);

	/* btm steer ? */
	if (!s->steer_btm_cnt) {
		s->steer_btm_cnt++;
		info("Try {%d} to BTM Steer " MACFMT " =======>\n",
				s->steer_btm_cnt, MAC2STR(s->macaddr));

		ret = agent_req_btm_steer(a, ap->ifname, s->macaddr, 1, &wifi_nbr_el, 0);
		if (!ret) {
			wifiagent_log_steer(a, s->macaddr, ap->ifname,
					"steer_btmreq", wifi_nbr_el.bssid);
		} else  {
			warn("Failed to send BTM request to " MACFMT "\n",
					MAC2STR(s->macaddr));
		}

		return ret;
	}

	/* retry btm steer ? */
	if (ap->cfg->steer_btm_retry) {
		if (!((s->steer_secs - 1) % ap->cfg->steer_btm_retry_secs) &&
			(s->steer_btm_cnt < ap->cfg->steer_btm_retry + 1)) {

			s->steer_btm_cnt++;
			info("Try {%d} to BTM Steer " MACFMT " =======>\n",
					s->steer_btm_cnt, MAC2STR(s->macaddr));

			ret = agent_req_btm_steer(a, ap->ifname, s->macaddr,1, &wifi_nbr_el, 0);
			if (!ret)
				wifiagent_log_steer(a, s->macaddr, ap->ifname,
						"steer_btmreq", wifi_nbr_el.bssid);
			else
				warn("Failed to send BTM request to " MACFMT "\n",
						MAC2STR(s->macaddr));

			return ret;
		}
	}

	return ret;
}

static void rebuild_cntlr_preflist(struct agent *a, struct sta *s,
					int cnt, struct pref_neighbor *pref)
{
	int sz = sizeof(int) + cnt * sizeof(*pref);

	if (s->cntlr_preflist)
		free(s->cntlr_preflist);

	s->cntlr_preflist = malloc(sz);
	if (s->cntlr_preflist) {
		memset(s->cntlr_preflist, 0, sz);
		s->cntlr_preflist->num = cnt;
		memcpy(s->cntlr_preflist->pref, pref, cnt);
	} else
		warn("OOM: cntlr_preflist\n");
}

int calculate_steer_verdict(struct sta *s, struct pref_neighbor **nbr)
{
	struct steer_rule *r = NULL;
	steer_verdict_t v = STEER_SKIP;

	list_for_each_entry(r, &regd_steer_rules, list) {
		if (!r->enabled)
			continue;

		if (r->check) {
			loud("STA " MACFMT " Check rule '%s'\n",
					MAC2STR(s->macaddr), r->name);
			v = r->check(r, s, nbr);
		}
		if (v != STEER_SKIP)
			return v;
	}

	return v;
}

/* returns 0 = don't steer, = 1 for steer */
static int should_steer_sta(struct sta *s, unsigned int *res)
{
	*res = 0;

	/* TODO:
	 * Check sta stats viz. active data sessions, retransmits etc. to
	 * decide if should steer sta.
	 * If verdict is OK, steer to cntlr_preflist.
	 */

	return 0;
}

/* returns 0 = don't steer, >= 1 for steer */
static int maybe_steer_sta(struct sta *s, unsigned int *res,
					struct pref_neighbor **nbr)
{
	struct agent *a = s->agent;
	struct stax *x = NULL;

	if (!a->cfg.pcfg) {
		/* Do nothing */
		return 0;
	}

	/* TODO: don't check here ..
	 * flag exclude whenever cfg is updated to exclude a STA.
	 */
	list_for_each_entry(x, &a->cfg.pcfg->steer_excludelist, list) {
		if (!memcmp(s->macaddr, x->macaddr, 6)) {
			loud("STA " MACFMT " in exclude list. Do nothing\n",
					MAC2STR(s->macaddr));
			return 0;
		}
	}

	/* Following if local steering is NOT disallowed */
	if (calculate_steer_verdict(s, nbr) == STEER_OK)
		return 1;

	return 0;
}

static void wifi_sta_steer_timeout(atimer_t *t)
{
	struct sta *s = container_of(t, struct sta, sta_steer_timer);
	unsigned int reason;

	if (timestamp_expired(&s->steer_opportunity, s->steer_opportunity_tmo)) {
		/* close steer opportunity window */
		s->steer_policy &= ~STA_STEER_OPPORTUNITY;
		return;
	}

	if (should_steer_sta(s, &reason)) {
		steer_sta(s, NULL);
		/* log_steer_action("Cntlr", s->macaddr, reason, ret); */
	}

	/* check for steer opportunity again in 1 sec */
	timer_set(&s->sta_steer_timer, 1000);
}

static void wifi_sta_steered_deauth(atimer_t *t)
{
	struct sta *s = container_of(t, struct sta, sta_steer_deauth_timer);
	struct netif_ap *ap;

	ap = agent_get_ap(s->agent, s->bssid);
	if (!ap)
		return;

	warn("%s: Disconnect station: " MACFMT " from interface: %s with reson:%i\n",
		__func__, MAC2STR(s->macaddr), ap->ifname, WIFI_REASON_BSS_TRANSITION_DISASSOC);
	wifi_disconnect_sta(ap->ifname, s->macaddr, WIFI_REASON_BSS_TRANSITION_DISASSOC);
}

static int cond_refresh_sta_neighbor_list(struct agent *a, struct sta *s)
{
	struct netif_ap *ap;
#define STA_NBR_REFRESH_BAD_RSSI	-78
	char ev[256] = {0};

	ap = agent_get_ap(s->agent, s->bssid);
	if (!ap)
		return -1;

	// TODO: use hysteresis
	if (s->rssi_avg[0] > STA_NBR_REFRESH_BAD_RSSI) {
		s->inform_leaving = 0;
		return 0;
	}

#if 0
	s->sta_nbr_invalid %= STA_NBR_REFRESH_CNT * a->cfg.runfreq;
	if (!s->sta_nbr_invalid)
		refresh_sta_neighbor_list(ap, s);

	s->sta_nbr_invalid++;
#endif
#if 0
	if (s->supports_bcnreport && s->sta_nbr_nr == 0)
		refresh_sta_neighbor_list(ap, s);
#endif

	if (!(s->inform_leaving++ % 5)) {
		/* tell cntlr about this bad rssi */
		snprintf(ev, sizeof(ev),
			"{\"macaddr\":\""MACFMT"\""
			",\"ap\":\"%s\""
			",\"action\":\"monitor\""
			",\"lowrssi\":%d}",
			MAC2STR(s->macaddr), ap->ifname, s->rssi_avg[0]);

		agent_notify_event(a, "map.agent", ev);
		/* s->inform_leaving = (s->inform_leaving + 1) % 3; */
		s->wait_for_cntlr_nbr = true;

		/* If no pref_nbr reply from cntlr, and
		 * sta->supports_bcnreport = false, then do not steer.
		 * Either of these is true, then steer based on other
		 * criteria.
		 */
	}

	return 0;
}



static int send_sta_metrics_response(struct agent *a,
		struct sta *s, struct netif_ap *ap)
{
	struct cmdu_buff *cmdu;

	cmdu = agent_gen_assoc_sta_metric_responsex(a, a->cntlr_almac, s, ap);
	if (!cmdu)
		return -1;

	agent_send_cmdu(a, cmdu);
	cmdu_free(cmdu);

	return 0;
}

static int report_assoc_sta_metrics(struct agent *a, struct sta *s, bool notify_evt)
{
	struct netif_ap *ap = NULL;
	struct agent_config_radio *rcfg;
	uint8_t curr_rcpi;
	enum rcpi_state dst_state = RCPI_STATE_UNKNOWN;

	ap = agent_get_ap(a, s->bssid);
	if (!ap || !ap->cfg)
		return -1;

	rcfg = get_agent_config_radio(&a->cfg, ap->cfg->device);
	if (!rcfg)
		return -1;

	curr_rcpi = rssi_to_rcpi(s->rssi_avg[0]);
	if (curr_rcpi == rcfg->report_rcpi_threshold) {
		/* on threshold point: unexpected */
		return -1;
	}

	if (curr_rcpi < rcfg->report_rcpi_threshold)
		dst_state = RCPI_STATE_LOW;
	else
		dst_state = RCPI_STATE_HIGH;

	if (dst_state == s->rep_rcpi_state) {
		agnt_info(LOG_STA, "%s RCPI already reported to cntrl\n",
			  ((dst_state == RCPI_STATE_LOW) ? "Low" : "High"));
		return 0;
	}

	send_sta_metrics_response(a, s, ap);
	s->rep_rcpi_state = dst_state;

	if (notify_evt) {
		char ev_data[512] = {0};

		snprintf(ev_data, sizeof(ev_data),
			"{\"macaddr\":\""MACFMT"\""
			",\"report_rcpi_threshold\":%d"
			",\"rcpi_hysteresis_margin\":%d"
			",\"rcpi\":%d}",
			MAC2STR(s->macaddr),
			rcfg->report_rcpi_threshold,
			rcfg->rcpi_hysteresis_margin,
			curr_rcpi);

		if (s->rep_rcpi_state == RCPI_STATE_LOW)
			agent_notify_iface_event(a, ap->ifname, "sta_low_rcpi", ev_data);
		else
			agent_notify_iface_event(a, ap->ifname, "sta_high_rcpi", ev_data);
	}

	return 0;
}

#define STA_STALE_STATS_CNT_MAX 60
static void wifi_sta_periodic_run(atimer_t *t)
{
	struct sta *s = container_of(t, struct sta, sta_timer);
	struct pref_neighbor *pref_nbr = NULL;
	struct agent *a = s->agent;
	struct wifi_sta sta = {};
	struct netif_ap *ap;
	unsigned int reason;
	int ret = 0;

	ap = agent_get_ap(a, s->bssid);
	if (!ap) {
		dbg("%s: did not find ap:"MACFMT" for sta:"MACFMT"!\n", __func__, MAC2STR(s->bssid), MAC2STR(s->macaddr));
		return;
	}

	/* if (strcmp(ap->ifname, s->vif_name))
	 *	err(stderr, "%s: ap changed under the hood!\n", __func__);
	 */
	if (!ap->cfg || !ap->cfg->enabled)
		return;

	//trace("%s: STA = " MACFMT " ref = %d\n", __func__,
	//					MAC2STR(s->macaddr), s->ref);

	//err("%s: ap:%s mac:"MACFMT"\n",
	//    __func__,
	//    ap->ifname,MAC2STR(s->macaddr));

#if (EASYMESH_VERSION >= 6)
	if (!ap->is_affiliated_ap) {
#endif
		ret = wifi_get_station(ap, s->macaddr, &sta);
		if (ret)
			goto rearm_periodic;
		update_sta_entry(a, ap, &sta);
#if (EASYMESH_VERSION >= 6)
	} else {
		struct wifi_mlsta mlsta = {0};

		ret = wifi_get_mld_station(ap, s->mld_macaddr, s->macaddr, &mlsta);
		if (ret)
			goto rearm_periodic;

		if (hwaddr_is_zero(mlsta.sta[0].macaddr)) {
			dbg("%s: sta:"MACFMT" is no longer connected\n",
			    __func__, MAC2STR(s->macaddr));
			wifi_topology_notification(a, s, ap,
					0x00 /* left */,
					0x01 /* unspecified reason */);
			wifi_del_sta(a, ap->ifname, s->macaddr);
			return;
		}

		if (mlsta.mlo_capable == true &&
		    !hwaddr_is_zero(mlsta.sta[0].bssid))
			stamld_update(a, ap, mlsta.macaddr, mlsta.bssid,
				      mlsta.sta[0].mlo_link_id, s);

		update_sta_entry(a, ap, &(mlsta.sta[0]));
	}
#endif

	cond_refresh_sta_neighbor_list(a, s);

	if (s->stale_stats_cnt > STA_STALE_STATS_CNT_MAX) {
		dbg("Stale STA " MACFMT ", sending disconnect.\n",
		    MAC2STR(s->macaddr));

		/* Inform driver about missing STA */
		wifi_disconnect_sta(ap->ifname, s->macaddr,
				WIFI_REASON_DISASSOC_STA_HAS_LEFT);

		/* Clean up STA in map */
		wifi_topology_notification(a, s, ap,
				    0x00 /* left */,
				    0x01 /* unspecified reason */);
		wifi_del_sta(a, ap->ifname, s->macaddr);
		return;
	}

	if (sta_steer_allowed(s->steer_policy)
			&& !list_empty(&s->pref_nbrlist)
			&& maybe_steer_sta(s, &reason, &pref_nbr)) {

		steer_sta(s, pref_nbr);
	}

	if (!list_empty(&ap->nbrlist) && list_empty(&s->pref_nbrlist))
		recalc_desired_neighbors(s);

	if (s->rssi_avg[0] && s->rssi_avg[1]) {
		struct agent_config_radio *rcfg;
		uint8_t low_rcpi, high_rcpi, rcpi;

		rcfg = get_agent_config_radio(&a->cfg, ap->cfg->device);
		if (!rcfg)
			goto rearm_periodic;

		if (!rcfg->report_rcpi_threshold)
			goto rearm_periodic;

		low_rcpi = rcfg->report_rcpi_threshold - rcfg->rcpi_hysteresis_margin;
		high_rcpi = rcfg->report_rcpi_threshold + rcfg->rcpi_hysteresis_margin;

		rcpi = rssi_to_rcpi(s->rssi_avg[0]);

		if ((rcpi < low_rcpi && s->rep_rcpi_state != RCPI_STATE_LOW) ||
			(rcpi > high_rcpi && s->rep_rcpi_state != RCPI_STATE_HIGH))
			s->num_rssi_rep++;
		else
			s->num_rssi_rep = 0;

		if (s->num_rssi_rep == NUM_RSSI_REP_TRIG)
			report_assoc_sta_metrics(a, s, true);
	}

rearm_periodic:
	timer_set(&s->sta_timer, STA_PERIODIC_RUN_INTERVAL);
}

static void agent_sta_clean_rssi(struct sta *s)
{
	int i;

	for (i = 0; i < MAX_RSSI_MEAS; i++)
		s->rssi_avg[i] = 0;
	s->num_rssi = 0;
	s->num_rssi_rep = 0;
}

static int agent_modify_assoc_status(struct netif_ap *ap, bool allowed)
{
	int ret = 0;

	if (!ap) {
		trace("%s: ap is NULL\n", __func__);
		return -EINVAL;
	}
	if (!ap->cfg) {
		trace("%s: ap has NULL cfg\n", __func__);
		return -EINVAL;
	}

	ret = wifi_set_ap_mbo_association_mode(ap->ifname, !allowed);
	if (!ret) {
		ap->cfg->disallow_assoc = !allowed;
		uci_apply_bss_association_mode(ap->ifname, !allowed);
	}

	return ret;
}

static int agent_notify_assoc_status(struct agent *a, uint8_t *bssid, bool allowed)
{
	trace("%s: --->\n", __func__);

	struct bss_data {
		uint8_t bssid[6];
		uint8_t status;
	} bss_data;
	int ret = 0;

	memcpy(bss_data.bssid, bssid, ETH_ALEN);
	if (allowed)
		bss_data.status = STA_ASSOC_ALLOWED;
	else
		bss_data.status = STA_ASSOC_NOT_ALLOWED;

	ret = send_assoc_status_notification(a, 1, &bss_data);

	return ret;
}

struct sta *wifi_add_sta(struct agent *a, const char *ifname,
				unsigned char *macaddr)
{
	struct netif_ap *ap;
	//struct netif_apcfg *cfg;
	struct sta *sptr, *new;
	struct wifi_assoc_frame *af;

	ap = agent_get_ap_by_ifname(a, ifname);
	if (!ap)
		return NULL;

	sptr = agent_get_sta(a, macaddr);
	if (sptr) {
		if (!memcmp(sptr->bssid, ap->bssid, 6)) {
			dbg("STA " MACFMT "already in list.\n",
					MAC2STR(macaddr));

			/* In case station is already on stalist, cancel steered station
			 * deauthentication timer if it is stil runnning.
			 */
			if (timer_pending(&sptr->sta_steer_deauth_timer))
				timer_del(&sptr->sta_steer_deauth_timer);

			agent_sta_clean_rssi(sptr);
			return 0;
		} else {
			struct netif_ap *oldap;

			oldap = agent_get_ap(a, sptr->bssid);
			if (oldap) {
				if (oldap->num_sta > 0) {
					oldap->num_sta--;
					if (oldap->num_sta == oldap->max_sta - 1) {
						/* Set association mode in driver and update config */
						agent_modify_assoc_status(ap, true);
						/* Notify ctrl that adding STA is allowed again */
						agent_notify_assoc_status(a, oldap->bssid, true);
					}
				}
			}
			clear_sta(sptr);
		}
	}

	/* add new sta */
	new = malloc(sizeof(struct sta));
	if (!new) {
		warn("OOM: new sta malloc failed!\n");
		return NULL;
	}

	memset(new, 0, sizeof(struct sta));
	memcpy(new->macaddr, macaddr, 6);
	memcpy(new->bssid, ap->bssid, 6);
	new->agent = a;
	timer_init(&new->sta_timer, wifi_sta_periodic_run);
	timer_init(&new->sta_bcn_req_timer, wifi_sta_bcn_req);
	timer_init(&new->sta_steer_timer, wifi_sta_steer_timeout);
	timer_init(&new->sta_steer_deauth_timer, wifi_sta_steered_deauth);
	timestamp_update(&new->last_update);
	INIT_LIST_HEAD(&new->sta_nbrlist);
	INIT_LIST_HEAD(&new->pref_nbrlist);
	recalc_desired_neighbors(new);
#if 0
	refresh_sta_neighbor_list(ap, new);
#endif
	new->steer_policy |= STA_STEER_ALLOWED;
	dbg("STA steer policy = 0x%x\n", new->steer_policy);
	af = wifi_get_frame(a, new->macaddr);
	if (af) {
		new->assoc_frame = af;
		list_del(&af->list);
	}
	new->rep_rcpi_state = RCPI_STATE_UNKNOWN;

	ap->num_sta++;

	/* INIT_LIST_HEAD(&new->cntlr_preflist); */
	list_add(&new->list, &ap->stalist);
	/* Remove from unassociated list */
	agent_del_unassoc_sta(a, new->macaddr);

	if (ap->max_sta && ap->num_sta >= ap->max_sta) {
		/* Set association mode in driver and update config */
		agent_modify_assoc_status(ap, false);
		/* Notify ctrl that adding more STA is disallowed */
		agent_notify_assoc_status(a, ap->bssid, false);
	}

	timer_set(&new->sta_timer, 1 * 1000);
	return new;
}

int wifi_del_sta(struct agent *a, const char *ifname,
		uint8_t *macaddr)
{
	struct netif_ap *ap;
	struct sta *s = NULL, *tmp;

	ap = agent_get_ap_by_ifname(a, ifname);
	if (!ap)
		return -1;

	list_for_each_entry_safe(s, tmp, &ap->stalist, list) {
		if (!memcmp(s->macaddr, macaddr, 6)) {
			dbg("Delete STA " MACFMT "record\n",
						MAC2STR(macaddr));
			if (ap->num_sta > 0) {
				ap->num_sta--;
				if (ap->num_sta == ap->max_sta - 1) {
					/* Set association mode in driver and update config */
					agent_modify_assoc_status(ap, true);
					/* Notify ctrl that adding STA is allowed again */
					agent_notify_assoc_status(a, ap->bssid, true);
				}
			}
			clear_sta(s);
			return 0;
		}
	}
	return -2;
}

int wifi_topology_notification(struct agent *a, struct sta *s,
			       struct netif_ap *ap, uint8_t assoc_event,
			       uint16_t reason)
{
	trace("%s: --->\n", __func__);
	struct cmdu_buff *cmdu;

	if (!assoc_event) {
		cmdu = agent_gen_client_disassoc(a, s, reason);
		if (cmdu) {
			agent_send_cmdu(a, cmdu);
			cmdu_free(cmdu);
		}
	}

	cmdu = agent_gen_topology_notification(a, s, ap, assoc_event);
	if (!cmdu)
		return -1;

	agent_send_cmdu(a, cmdu);
	cmdu_free(cmdu);

	return 0;
}

bool is_channel_supported_by_radio(struct wifi_radio_element *re,
				   uint8_t opclass,
				   uint8_t channel)
{
	return wifi_opclass_id_channel_supported(&re->opclass, opclass, channel);
}

/* generate a netif_ap with first available ifname on passed radio */
struct netif_ap *agent_gen_netif_ap(struct agent *a, struct wifi_radio_element *re)
{
	char ifname[16] = {0};
	struct netif_ap *ap;
	if (!wifi_gen_first_ifname(a, re->name, ifname)) {
		err("Failed to find valid interface name, probably "\
			"maximum number of interfaces have "\
			"been reached!\n");
		return NULL;
	}
	ap = netif_alloc_ap(a, ifname);
	if (!ap)
		return NULL;

	ap->agent = a;
	ap->cfg = NULL;
	strncpy(ap->radio_name, re->name, IFNAMSIZ - 1);
	list_add_tail(&ap->list, &re->aplist);
	dbg("%s: new interface added\n", __func__);

	return ap;
}

static void agent_iface_disconnect_all_sta(const struct netif_ap *iface,
					   uint16_t reason)
{
	struct sta *sta = NULL;

	list_for_each_entry(sta, &iface->stalist, list) {
		wifi_disconnect_sta(iface->ifname, sta->macaddr, reason);
	}
}

static void agent_init_ifaces_assoc_mode(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			if (ap->cfg->disallow_assoc) {
				agent_iface_disconnect_all_sta(ap,
					WIFI_REASON_SERVICE_NOT_AUTHORIZED_IN_THIS_LOCATION);
			}
			wifi_set_ap_mbo_association_mode(ap->ifname,
							ap->cfg->disallow_assoc);
		}
	}
}

bool agent_may_enable_fhs(struct agent *a)
{
#ifdef AGENT_ISLAND_PREVENTION
	if (a->cfg.island_prevention)
		return agent_has_active_backhaul(a) ? true : false;
#endif
	return agent_has_active_backhaul(a) ? true : false;
}

int wifi_mod_bridge(struct agent *a, struct netif_bk *bk, bool add)
{
	int ret = -1;
	char *ifname = bk->ifname;

	/* add wds iface to bridge */

	dbg("%s: %s interface %s to bridge %s using libeasy API\n", __func__, (add ? "Adding" : "Deleting"),
	    ifname, a->cfg.al_bridge);

	if (add /* && !if_isbridge_interface(ifname) */) {
		dynbh_update_bk_connect_t(a, bk);

		if (!if_isbridge_interface(ifname))
			ret = br_addif(a->cfg.al_bridge, ifname);
	}
	else if(!add /* && if_isbridge_interface(ifname) */) {
		dynbh_update_bk_disconnect_t(a, bk);

		if (if_isbridge_interface(ifname))
			ret = br_delif(a->cfg.al_bridge, ifname);
	}

	/* explicitly disable if removing from bridge */
	if (!ret && !add) {
		int rc;

		rc = wifi_set_4addr(ifname, false);
		dbg("%s: Disabled 4addr mode for %s (rc:%d)\n", __func__, ifname, rc);
	}
	return ret;
}

void agent_reload_local_cntlr(struct agent *a, bool on)
{
	set_value_by_string("mapcontroller", "controller",
			"enabled", on ? "1" : "0", UCI_TYPE_STRING);
	trace("Reloading mapcontroller\n");

	/* procd is too slow to send signal - manual kill prior to reload
	 * TODO: leverage pidfile
	 */
	runCmd("kill `pidof mapcontroller`");
	uci_reload_services("mapcontroller");

	/* Enable is now set to '1' in config; ensure service is started */
	if (on) {
		runCmd("/etc/init.d/mapcontroller reload");
	}
}

void agent_enable_local_cntlr(atimer_t *t)
{
	struct agent *a = container_of(t, struct agent, cntlr_scheduler);

	dbg("%s: Attempting to start local controller\n", __func__);

	if (is_local_cntlr_running()) {
		warn("Skip starting local mapcontroller: already running\n");
	} else {
		agent_reload_local_cntlr(a, true);
	}
}

void agent_disable_local_cntlr(struct agent *a)
{
	dbg("%s: Disable local controller in UCI\n", __func__);
	agent_reload_local_cntlr(a, false);
}

static void agent_schedule_cntlr_start(struct agent *a, int secs)
{
	dbg("%s: Scheduled controller start in %d sec\n",
			__func__, secs);
	timer_set(&a->cntlr_scheduler, secs * 1000);
}

static bool agent_ap_get_state(struct agent *a, struct netif_ap *ap)
{
	if (!ap)
		return -1;

	return ap->enabled ? true : false;
}

static int agent_ap_set_state(struct agent *a, struct netif_ap *ap, bool up)
{
	bool is_enabled;

	if (!ap)
		return -1;

	is_enabled = agent_ap_get_state(a, ap);

	dbg("%s: setting AP %s %s (current: %s)\n",
	    __func__,
	    ap->ifname, up ? "up" : "down",
	    is_enabled ? "up" : "down");

	if (is_enabled == up)
		return 0;

	if (up && ap->cfg && !ap->cfg->enabled) {
		/* start_disabled set for given ap in cfg */
		dbg("%s: will not enable ap %s - start_disabled set\n",
		    __func__, ap->ifname);
		return 0;
	}

	if (wifi_ap_set_state(ap->ifname, up)) {
		dbg("%s: failed to set AP %s %s\n",
		    __func__, ap->ifname, up ? "up" : "down");
		return -1;
	} /* check if the state has changed */ if (agent_ap_get_state(a, ap) != up) {
		dbg("%s: sheduling iface init\n", __func__);
		timer_set(&a->init_ifaces_scheduler,
				IFACE_TIMEOUT * 1000);
	}
	return 0;
}

static void agent_enable_fronthauls(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		dbg("%s: enabling all fronthauls\n", __func__);

		list_for_each_entry(ap, &re->aplist, list)
			agent_ap_set_state(a, ap, true);
	}
}

static int agent_req_btm_steer(struct agent *a, const char *ifname, unsigned char *sta_macaddr,
		uint8_t pref_num, struct nbr *pref, uint32_t validity_int)
{
	struct wifi_btmreq req = {};

	dbg("Calling wifi.ap.%s request_btm for sta " MACFMT "\n",
			ifname, MAC2STR(sta_macaddr));

	if (pref_num)
		req.mode |= WIFI_BTMREQ_PREF_INC;

	req.dialog_token = agent_steer_next_dialog_token(a);

	/* Inform remaining clients of imminent disassociation */
	/* disassoc_tmo kept as 0*/
	req.mode |= WIFI_BTMREQ_DISASSOC_IMM;

	/* BTM Abridged bit set to 1 */
	req.mode |= STEER_REQUEST_BTM_ABRIDGED;

	req.validity_int = validity_int / WIFI_BEACON_PERIOD_MS;

	if (wifi_req_btm(ifname, sta_macaddr, pref_num, pref, &req)) {
		err("%s: Failed calling wifi.ap.%s request_btm for sta " MACFMT "\n",
		    __func__, ifname, MAC2STR(sta_macaddr));
		return -1;
	}

	return 0;
}

void parse_i1905_info(struct ubus_request *req, int type,
		struct blob_attr *msg)
{
	struct agent *a = (struct agent *)req->priv;
	struct blob_attr *tb[2];
	static const struct blobmsg_policy ieee_attrs[2] = {
		[0] = { .name = "ieee1905id", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "interface", .type = BLOBMSG_TYPE_ARRAY }
	};

	blobmsg_parse(ieee_attrs, 2, tb, blob_data(msg), blob_len(msg));

	if (tb[0]) {
		char *mac;

		mac = blobmsg_get_string(tb[0]);
		hwaddr_aton(mac, a->almac);
		dbg("almac = " MACFMT "\n", MAC2STR(a->almac));
	}
}

void agent_init_ieee1905id(struct agent *a)
{
	for (;;) {
		int ret;

		ret = ubus_lookup_id(a->ubus_ctx, "ieee1905", &a->i1905obj_id);
		if (ret) {
			warn("%s: ieee1905 object missing over UBUS, retry in 2s\n", __func__);
			sleep(2);
			continue;
		}

		ret = ubus_call_object(a->ubus_ctx, a->i1905obj_id, "info",
				       parse_i1905_info, a);
		if (ret) {
			warn("%s: getting alid failed, retry in 2s\n", __func__);
			sleep(2);
			continue;
		}
		break;
	}
}

#ifdef AGENT_ISLAND_PREVENTION
/* Time in seconds until the AP sends a Disassociation frame to the STA */
#define STA_DISCONNECT_TM	30
static int agent_req_btm_bssterm(struct agent *a, const char *ap_name,
				 unsigned char *sta_macaddr,
				 uint32_t disassoc_tmo)
{
	struct wifi_btmreq req = {};

	dbg("Calling wifi.ap.%s request_btm for sta " MACFMT "\n",
			ap_name, MAC2STR(sta_macaddr));

	/* Inform remaining clients of imminent disassociation */
	req.mode |= WIFI_BTMREQ_DISASSOC_IMM;
	/* Inform clients about BSS termination */
	req.mode |= WIFI_BTMREQ_BSSTERM_INC;

	/* Duration - number of minutes for which the BSS is not present.
	 * Value of 0 is reserved. Value 65 535 when the BSS is terminated
	 * for a period longer than or equal to 65 535 minutes.
	 */
	req.bssterm_dur = 1; /* away (min) */
	req.disassoc_tmo = disassoc_tmo / WIFI_BEACON_PERIOD_MS;
	req.dialog_token = agent_steer_next_dialog_token(a);

	if (wifi_req_btm(ap_name, sta_macaddr, 0, NULL, &req)) {
		dbg("%s: failed to send wifi_req_btm\n", __func__);
		return -1;
	}

	return 0;
}

#define STA_DISCONNECT_TM 30
void agent_schedule_fh_disable(struct agent *a)
{
	trace("%s: --->\n", __func__);
	struct wifi_radio_element *re = NULL;

	if (agent_has_active_backhaul(a))
		/* backhaul regained, don't send BTM */
		return;

	dbg("%s: will disable aps in %d seconds\n",
	    __func__, STA_DISCONNECT_TM + 2);
	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			struct sta *s = NULL;

			/* Try to steer all remaining (b)STAs away */
			list_for_each_entry(s, &ap->stalist, list) {
				agent_req_btm_bssterm(a, ap->ifname, s->macaddr, STA_DISCONNECT_TM * 1000);
			}
		}
	}

	/* Disconnect all STAs in in STA_DISCONNECT_TM seconds */
	timer_set(&a->sta_disconnect_timer, STA_DISCONNECT_TM * 1000);
}

static int agent_sta_disconnect(struct agent *a, struct netif_ap *ap, struct sta *s)
{
	dbg("Calling wifi.ap.%s disconnect for sta " MACFMT "\n",
	    ap->ifname, MAC2STR(s->macaddr));

	return wifi_disconnect_sta(ap->ifname, s->macaddr,
			WIFI_REASON_BSS_TRANSITION_DISASSOC);
}

static void agent_fh_disable_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);
	struct agent *a = container_of(t, struct agent, fh_disable_timer);
	struct wifi_radio_element *re = NULL;

	if (agent_has_active_backhaul(a))
		/* backhaul regained, don't disable FHs */
		return;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			agent_ap_set_state(a, ap, false);
		}
	}
}

static void agent_sta_disconnnect_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);
	struct agent *a = container_of(t, struct agent, sta_disconnect_timer);
	struct wifi_radio_element *re = NULL;

	if (agent_has_active_backhaul(a))
		/* backhaul regained, don't disconnect STAs */
		return;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			struct sta *s = NULL;

			list_for_each_entry(s, &ap->stalist, list) {
				agent_sta_disconnect(a, ap, s);
			}
		}
	}

	/* Disable all fronthauls in 2 seconds */
	timer_set(&a->fh_disable_timer, 2 * 1000);
}
#endif /* AGENT_ISLAND_PREVENTION */

int wifiagent_toggle_fh(struct agent *a, bool isl_prev,
			const char *ifname, int enable)
{
#ifdef AGENT_ISLAND_PREVENTION
	if (isl_prev && a->cfg.island_prevention && !enable) {
		/* schedule bringing down all STAs & FHs */
		dbg("%s: island prevention: schedule FHs down\n",
		    __func__);
		agent_schedule_fh_disable(a);
		return 0;
	}
#else
	UNUSED(isl_prev);
#endif

	if (ifname && ifname[0]) {
		/* enable/disable fronthaul(s)  */
		if (strncmp(ifname, "all", 16)) {
			struct netif_ap *ap = NULL;

			ap = agent_get_ap_by_ifname(a, ifname);
			if (!ap) {
				dbg("interface \'%s\' not found\n", ifname);
				return -1;
			}
			agent_ap_set_state(a, ap, enable ? true : false);
		} else { /* all interfaces */
			struct wifi_radio_element *re = NULL;

			list_for_each_entry(re, &a->radiolist, list) {
				struct netif_ap *ap = NULL;

				list_for_each_entry(ap, &re->aplist, list) {
					agent_ap_set_state(a, ap, enable ? true : false);
				}
			}
		}
	}

	return 0;
}

static void preference_report_timer_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);

	struct wifi_radio_element *re = container_of(t, struct wifi_radio_element, preference_report_timer);
	struct cmdu_buff *cmdu;
	uint32_t mid = 0;

	if (!agent_has_active_backhaul(re->agent) &&
			memcmp(re->agent->cntlr_almac, re->agent->almac, 6)) {
		/* ctlr on another node and missing bh: reschedule reporting */
		timer_set(&re->preference_report_timer, 1000);
		return;
	}

	cmdu = agent_gen_channel_preference_report(re->agent, mid, re->agent->cntlr_almac, re->macaddr);
	if (cmdu) {
		int mid;

		mid = agent_send_cmdu(re->agent, cmdu);
		if (!mid || mid == 0xffff) {
			/* nbr undiscovered yet, resend in 5 seconds */
			timer_set(&re->preference_report_timer, 5 * 1000);
		}
		cmdu_free(cmdu);
	}
}

bool agent_has_downstream(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			struct sta *s = NULL;

			if (!ap->cfg)
				continue;

			/* only bbss and combined type interfaces may have downstream APs */
			if (ap->cfg->multi_ap != 1 && ap->cfg->multi_ap != 3)
				continue;

			list_for_each_entry(s, &ap->stalist, list)
				return true;
		}
	}

	return false;
}

void agent_bsta_steer_clear(struct netif_bk *bk)
{
	memset(bk->bsta_steer.target_bssid, 0, 6);
	memset(bk->bsta_steer.prev_bssid, 0, 6);
	bk->bsta_steer.mid = 0;
	memset(bk->bsta_steer.ul_ifname, 0, sizeof(bk->bsta_steer.ul_ifname));
	bk->bsta_steer.expired = false;
}

bool agent_bsta_steer_is_active(struct netif_bk *bk)
{
	return (bk->bsta_steer.mid != 0 && bk->bsta_steer.expired == false);
}

bool agent_bsta_steer_is_active_all(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (re->has_bsta && agent_bsta_steer_is_active(&re->bk))
			return true;
	}

	return false;
}


int agent_bsta_steer(struct agent *a, char *ifname, uint8_t *target_bssid,
		     bool force_disassoc)
{
	char fmt[128] = {0};

	UNUSED(a);

	if (force_disassoc) {
		snprintf(fmt, sizeof(fmt), "bsta_steer_with_disassoc %s",
			 ifname);
	} else
		snprintf(fmt, sizeof(fmt), "bsta_steer %s",
			 ifname);

	if (hwaddr_is_zero(target_bssid)|| hwaddr_is_bcast(target_bssid)) {
		snprintf(fmt + strlen(fmt), sizeof(fmt) - strlen(fmt), " any");
	} else {
		snprintf(fmt + strlen(fmt), sizeof(fmt) - strlen(fmt), /* Flawfinder: ignore */
			 " " MACFMT, MAC2STR(target_bssid));
	}

	return agent_exec_platform_scripts(fmt);
}

int agent_bsta_steer_handler(struct agent *a, struct netif_bk *bk, uint8_t *bssid)
{
	uint8_t *target_bssid = bk->bsta_steer.target_bssid;

	if (!memcmp(bssid, target_bssid, 6) ||
	    /* if target was any BSSID, treat connection over correct
	     * bsta as success */
	    hwaddr_is_zero(target_bssid) || hwaddr_is_bcast(target_bssid)) {
		/* success */
		char fmt[64] = {0};

		/* write to config */
		wifi_set_iface_bssid(bk, bssid);
		snprintf(fmt, sizeof(fmt), "write_bsta_config %s", bk->ifname);
		agent_exec_platform_scripts(fmt);
	} else {
		bk->bsta_steer.reason = ERR_REASON_BH_STEERING_NOT_FOUND;
	}

	if (bk->bsta_steer.trigger == BK_STEER_MANDATE)
		send_backhaul_sta_steer_response(a, bk, bk->ifname);

	agnt_info(LOG_BSTEER, "%s: bssid = " MACFMT ", target = " MACFMT "\n", __func__, MAC2STR(bssid), MAC2STR(target_bssid));

	timer_del(&bk->steer_timeout);
	agent_bsta_steer_clear(bk);
	return 0;
}

static bool wifi_is_band_onboarded(struct agent *a, enum wifi_band band)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (re->has_bsta && re->bk.cfg->onboarded && (re->band & band))
			return true;
	}

	return false;
}

bool wifi_radio_cac_required(struct agent *a, struct wifi_radio_element *re,
				    int channel, int bandwidth, uint32_t *cac_time)
{
	struct wifi_radio_opclass opclass = {};

	if (!wifi_radio_to_ap(a, re))
		return false;

	/* Get fresh opclass preferences */
	if (WARN_ON(wifi_opclass_preferences(re->name, &opclass))) {
		return false;
	}

	return wifi_opclass_cac_required(&opclass, channel, bandwidth, cac_time);
}

static int wifi_bsta_handle_cac_required(struct agent *a,
					 struct wifi_radio_element *re,
					 struct netif_bk *bk)
{
	struct netif_bk *new_bk = NULL;
	uint32_t cac_time = 0;

	if (!a->cfg.ap_follow_sta_dfs)
		return 0;

	re->cac_required = wifi_radio_cac_required(a, re, re->current_channel,
						   re->current_bandwidth, &cac_time);

	dbg("bsta %s connected on %d/%d cac required %d time %us\n", bk->ifname,
	    re->current_channel, re->current_bandwidth, re->cac_required,
	    cac_time);

	if (!re->cac_required)
		return 0;

	list_for_each_entry(re, &a->radiolist, list) {
		/* Find new bsta */
		if (re->has_bsta && !strcmp(re->bk.ifname, bk->ifname))
			continue;

		new_bk = &re->bk;
		break;
	}

	/* Disable current connection */
	config_disable_bsta(bk->cfg);
	bk->connected = false;


	if (new_bk) {
		dbg("[%s] connect cac required use new link %s\n", bk->ifname, new_bk->ifname);
		config_enable_bsta(new_bk->cfg);
		dynbh_bsta_use_link(a, new_bk->ifname);
	} else {
		dbg("connect cac required disconnect current link %s\n", bk->ifname);
		dynbh_bsta_use_link(a, "none");
	}

	wifi_bsta_disconnect(bk, 0);
	cac_time += 20;
	if (!a->dynbh_upgrade_ongoing)
		timer_set(&a->dynbh_scheduler, cac_time * 1000);

	timestamp_update(&bk->cac_start);
	bk->cac_time = cac_time - 5;

	/* Use 80MHz */
	if (re->current_bandwidth < 80)
		re->current_opclass =
			wifi_opclass_find_id_from_channel(&re->opclass,
					re->current_channel, 80);

	/* Start CAC */
	if (WARN_ON(agent_channel_switch(a, re->macaddr, re->current_channel, re->current_opclass))) {
		bk->cac_time = 0;
		if (!a->dynbh_upgrade_ongoing)
			timer_set(&a->dynbh_scheduler, 5 * 1000);
	}

	/* Don't wait - switch bsta */
	dynbh_bsta_scan_on_enabled(a);

	return 1;
}

void wifi_bsta_check_cac_done(struct agent *a)
{
	struct wifi_radio_element *re = NULL;
	struct netif_bk *bk;
	uint8_t enable_prio = 255;
	bool cac_required;
	uint32_t cac_time;

	if (!a->cfg.ap_follow_sta_dfs)
		return;

	/* lower prio is better */
	list_for_each_entry(re, &a->radiolist, list) {
		bk = &re->bk;

		if (!re->has_bsta)
			continue;

		if (!bk->cfg->enabled)
			continue;

		if (bk->cfg->priority < enable_prio)
			enable_prio = bk->cfg->priority;
	}

	re = NULL;
	list_for_each_entry(re, &a->radiolist, list) {
		bk = &re->bk;
		if (re->has_bsta)
			continue;

		if (bk->cac_time) {
			cac_required = wifi_radio_cac_required(a, re,
						re->current_channel,
						re->current_bandwidth,
						&cac_time);
			dbg("[%s] %d/%d cac_required %d\n", bk->ifname, re->current_channel,
				re->current_bandwidth, cac_required);

			if (timestamp_expired(&bk->cac_start, bk->cac_time * 1000) || !cac_required) {
				dbg("[%s] connect enable bsta again, cac end (required %d)\n", bk->ifname, cac_required);
				config_enable_bsta(bk->cfg);
				bk->cac_time = 0;
				if (a->dynbh_upgrade_ongoing) {
					a->dynbh_upgrade_ongoing = false;
					timer_set(&a->dynbh_scheduler, 5 * 1000);
				}
			}
		} else {
			/* In case we loose bk->cac_time during agent stop/start */
			if (bk->cfg && !bk->cfg->enabled && bk->cfg->priority < enable_prio) {
				dbg("[%s] connect !cac_time enable higher prio (%d) bsta\n", bk->ifname, enable_prio);
				config_enable_bsta(bk->cfg);
			}
		}
	}
}

void wifi_bsta_connect(struct agent *a, struct netif_bk *bk,
			      uint8_t *bssid, uint32_t freq)
{
	struct wifi_radio_status radio_status = {};
	struct wifi_radio_element *re = NULL, *radio = NULL;
	struct wifi_bsta_status status = {};
	uint32_t bandwidth = 0;
	uint32_t channel = 0;
	char fmt[64] = {0};

	/* no operation if no bsta on same band is onboarded! */
	if (!wifi_is_band_onboarded(a, bk->cfg->band)) {
		dbg("%s: band %d is not onboard\n", __func__,
		    bk->cfg->band);
		return;
	}

	if (!a->cntlr_select.local)
		agent_disable_local_cntlr(a);

	dbg("%s: connect event received\n", __func__);

	agent_enable_fronthauls(a);

	memcpy(bk->bssid, bssid, 6);

	re = agent_get_radio_with_ifname(a, bk->ifname);
	if (!re)
		return;

	/* Assign bssid to configs */
	if (bk->cfg->onboarded
#if (EASYMESH_VERSION >= 6)
	    /* do not assign bssids to MLD netdev or affiliated STAs
	     * TODO: assign MLD bssid once supported by wireless config
	     */
	    && (!bk->is_affiliated_sta && !bk->cfg->is_mld_netdev)
#endif
	    ) {
		wifi_set_iface_bssid(bk, bssid);
		snprintf(fmt, sizeof(fmt), "write_bsta_config %s", bk->ifname);
		agent_exec_platform_scripts(fmt);
	}

	/* TODO: move to API in autocfg.c when ready */
	list_for_each_entry(radio, &a->radiolist, list)
		radio->state = AUTOCFG_ACTIVE;

	//backhaul_blacklist_clear(a);

	dynbh_update_agent_connect_t(a);
	/* TODO: eth always takes priority */
	if (!agent_is_backhaul_type_eth())
		agent_update_active_uplink(a, bk);
	a->autocfg_interval = 5;
	dbg("%s: Scheduling next ACS in %u seconds\n", __func__,
			a->autocfg_interval);
	timer_set(&a->autocfg_dispatcher, a->autocfg_interval * 1000);

	/* Get channel/bandwidth from bsta */
	if (!wifi_bsta_status(bk->ifname, &status)) {
		channel = status.channel;
		bandwidth = wifi_bw_to_bw(status.bandwidth);
	}

	/* Update radio channel/bw/opclass */
	re->current_channel = channel;
	re->current_bandwidth = bandwidth;
	re->current_opclass = wifi_opclass_find_id_from_channel(&re->opclass,
				re->current_channel, re->current_bandwidth);

	/* Fall back to radio status */
	if (!re->current_channel) {
		if (!wifi_radio_status(re->name, &radio_status)) {
			channel = radio_status.channel;
			bandwidth = radio_status.bandwidth;

			re->current_channel = radio_status.channel;
			re->current_bandwidth = radio_status.bandwidth;
			re->current_opclass = radio_status.opclass;
		}
	}

	/* Check CAC AP/BSTA action required */
	if (wifi_bsta_handle_cac_required(a, re, bk))
		return;

	if (!channel && freq)
		channel = f2c(freq);

	if (channel) {
		char fmt[512] = {};

		snprintf(fmt, sizeof(fmt), "bsta_set_channel %s %u %u", bk->ifname, channel, bandwidth);
		agent_exec_platform_scripts(fmt);
	}

	dynbh_update_bk_connect_t(a, bk);
	bk->connected = true;
	a->connected = true;
}

static void sync_neighbor_lists(struct netif_ap *ap)
{
	struct nbr nbr[64] = {};
	int nbr_num = 64;
	struct neighbor *n = NULL;
	bool found;
	int ret;
	int i;

	/* Update new & changed nbr entries in driver */
	list_for_each_entry(n, &ap->nbrlist, list) {

		if (n->flags & NBR_FLAG_DRV_UPDATED)
			continue;

		/* update nbr in wifi driver */
		dbg("[%s] Update neighbor " \
		    MACFMT " entry in driver\n",
		    ap->ifname,
		    MAC2STR(n->nbr.bssid));
		wifi_del_neighbor(ap->ifname, n->nbr.bssid);
		wifi_add_neighbor(ap->ifname, &n->nbr);
		n->flags |= NBR_FLAG_DRV_UPDATED;
	}

	/* Trigger removal of dead entries from list in driver */
	ret = wifi_get_neighbor_list(ap->ifname, nbr, &nbr_num);
	if (ret)
		return;

	for (i = 0; i < nbr_num; i++) {
		found = false;
		n = NULL;
		list_for_each_entry(n, &ap->nbrlist, list) {
			if (!memcmp(nbr[i].bssid, n->nbr.bssid, 6)) {
				found = true;
				break;
			}
		}

		if (!found) {
			/* Remove dead entry from driver list */
			dbg("[%s] Delete neighbor " \
			    MACFMT " entry in driver\n",
			    ap->ifname, MAC2STR(nbr[i].bssid));
			wifi_del_neighbor(ap->ifname, nbr[i].bssid);
		}
	}
}

static void refresh_neighbor_list(atimer_t *t)
{
	struct netif_ap *ap = container_of(t, struct netif_ap, nbr_timer);

	trace("%s: ap = %s\n", __func__, ap->ifname);

	/* First remove aged out entries from FH's nbrlist */
	delete_expired_entries(ap, struct neighbor, &ap->nbrlist, list,
				tsp, NBR_AGEOUT_INTERVAL, NULL,
				ap->nbr_nr);

	/* Now update list in driver so that it matches FH's nbrlist */
	sync_neighbor_lists(ap);

	/* Check again in couple of minutes */
	timer_set(&ap->nbr_timer, NBR_REFRESH_INTERVAL);
}

static void refresh_bssinfo(atimer_t *t)
{
	struct netif_ap *ap = container_of(t, struct netif_ap, bss_timer);
	struct agent *a = ap->agent;
	struct wifi_ap_status ap_status = {};
	bool bssid_unset = !!hwaddr_is_zero(ap->bssid);
	int i;

	trace("%s: ap = %s\n", __func__, ap->ifname);

	if (!wifi_ap_status(ap->ifname, &ap_status)) {
#if (EASYMESH_VERSION >= 6)
		struct wifi_mlsta mlsta[128] = {0};
		int num_mlsta = 128;
#else
		uint8_t sta[128 * 6] = {};
		int num_sta = 128;
#endif
		ap->channel = ap_status.ap.bss.channel;
		memcpy(ap->bssid, ap_status.ap.bss.bssid, 6);
		if (bssid_unset && !hwaddr_is_zero(ap->bssid)) {
			/* bssid got set, update STA restrictions */
			assoc_ctrl_sync_from_file(a, ap);
		}
		memcpy(ap->ssid, ap_status.ap.bss.ssid, 32);
		/* others.. */
		if (ap_status.ap.bss.load.utilization != 0xff)
			ap->bssload = ap_status.ap.bss.load.utilization;
		ap->max_sta = ap_status.ap.assoclist_max;

		dbg("%s: ap: %s  max_sta: %d  bssid: " MACFMT \
		    "  channel: %d  bssload: %d\n",
			__func__, ap->ifname, ap->max_sta,
			MAC2STR(ap->bssid), ap->channel, ap->bssload);
#if (EASYMESH_VERSION >= 6)
		if (!wifi_get_mld_stations(ap, mlsta, &num_mlsta)) {
			for (i = 0; i < num_mlsta; i++) {
				if (!memcmp(mlsta[i].sta->bssid, ap->bssid, 6))
					wifi_add_sta(a, ap->ifname, mlsta[i].sta->macaddr);
			}
		}
#else
		if (!wifi_get_assoclist(ap, sta, &num_sta)) {
			for (i = 0; i < num_sta; i++)
				wifi_add_sta(a, ap->ifname, &sta[i * 6]);
		}
#endif
	}

	timer_set(&ap->bss_timer, BSS_REFRESH_INTERVAL);
}

static bool radio_diag_valid(struct wifi_radio_diagnostic *diag)
{
	if (diag->tx_airtime > diag->cca_time)
		return false;
	if (diag->rx_airtime > diag->cca_time)
		return false;
	if (diag->obss_airtime > diag->cca_time)
		return false;
	if (diag->channel_busy > diag->cca_time)
		return false;
	if (!diag->cca_time)
		return false;

	return true;
}

static int radio_diag_diff(struct wifi_radio_diagnostic *old,
			   struct wifi_radio_diagnostic *new,
			   struct wifi_radio_diagnostic *diff)
{
	/* consistency check - just in case HW report some garbage */
	if (old->cca_time > new->cca_time)
		return -1;
	if (old->tx_airtime > new->tx_airtime)
		return -1;
	if (old->rx_airtime > new->rx_airtime)
		return -1;
	if (old->channel_busy > new->channel_busy)
		return -1;
	if (old->obss_airtime > new->obss_airtime)
		return -1;

	diff->cca_time = new->cca_time - old->cca_time;
	diff->tx_airtime = new->tx_airtime - old->tx_airtime;
	diff->rx_airtime = new->rx_airtime - old->rx_airtime;
	diff->obss_airtime = new->obss_airtime - old->obss_airtime;
	diff->channel_busy = new->channel_busy - old->channel_busy;

	return 0;
}

static void parse_radio_diag(struct ubus_request *req, int type,
			     struct blob_attr *msg)
{
	struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv;
	static const struct blobmsg_policy radio_diag_attr[] = {
		[0] = { .name = "tx_airtime", .type = BLOBMSG_TYPE_INT64 },
		[1] = { .name = "rx_airtime", .type = BLOBMSG_TYPE_INT64 },
		[2] = { .name = "channel_busy", .type = BLOBMSG_TYPE_INT64 },
		[3] = { .name = "obss_airtime", .type = BLOBMSG_TYPE_INT64 },
		[4] = { .name = "cca_time", .type = BLOBMSG_TYPE_INT64 },
	};
	struct blob_attr *tb[ARRAY_SIZE(radio_diag_attr)];
	struct wifi_radio_diagnostic cur = {};
	struct wifi_radio_diagnostic diff = {};

	blobmsg_parse(radio_diag_attr, ARRAY_SIZE(radio_diag_attr), tb, blobmsg_data(msg), blob_len(msg));

	if (tb[0])
		cur.tx_airtime = blobmsg_get_u64(tb[0]);
	if (tb[1])
		cur.rx_airtime = blobmsg_get_u64(tb[1]);
	if (tb[2])
		cur.channel_busy = blobmsg_get_u64(tb[2]);
	if (tb[3])
		cur.obss_airtime = blobmsg_get_u64(tb[3]);
	if (tb[4])
		cur.cca_time = blobmsg_get_u64(tb[4]);

	if (!radio_diag_valid(&cur)) {
		trace("%s %s diag invalid, skip metrics calculation\n", __func__, re->name);
		return;
	}

	/* recalc here */
	if (radio_diag_diff(&re->diag, &cur, &diff)) {
		trace("%s %s diff invalid, skip metrics calculation\n", __func__, re->name);
		memcpy(&re->diag, &cur, sizeof(re->diag));
		return;
	}

	if (!diff.cca_time)
		return;

	trace("%s %s diff tx %" PRIu64 " rx %" PRIu64 " busy %" PRIu64 " obss %" PRIu64 " time %" PRIu64 "\n",
	      __func__, re->name, diff.tx_airtime, diff.rx_airtime, diff.channel_busy, diff.obss_airtime, diff.cca_time);

	/* get percentage of utilization and scale linearly with 255 */
	re->tx_utilization = (diff.tx_airtime * 255) / diff.cca_time;
	re->rx_utilization = (diff.rx_airtime * 255) / diff.cca_time;
	re->total_utilization = (diff.channel_busy * 255) / diff.cca_time;
	re->other_utilization = (diff.obss_airtime * 255) / diff.cca_time;

	trace("%s %s metrics tx %hhu rx %hhu busy %hhu obss %hhu\n", __func__, re->name,
	      re->tx_utilization, re->rx_utilization, re->total_utilization, re->other_utilization);

	/* store latest diagnostic data */
	memcpy(&re->diag, &cur, sizeof(re->diag));
}

static void parse_radio_stats(struct ubus_request *req, int type,
			      struct blob_attr *msg)
{
	struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv;
	static const struct blobmsg_policy stats_attr[] = {
		[0] = { .name = "tx_bytes", .type = BLOBMSG_TYPE_INT64 },
		[1] = { .name = "tx_packets", .type = BLOBMSG_TYPE_INT64 },
		[2] = { .name = "tx_error_packets", .type = BLOBMSG_TYPE_INT64 },
		[3] = { .name = "tx_dropped_packets", .type = BLOBMSG_TYPE_INT64 },
		[4] = { .name = "rx_bytes", .type = BLOBMSG_TYPE_INT64 },
		[5] = { .name = "rx_packets", .type = BLOBMSG_TYPE_INT64 },
		[6] = { .name = "rx_error_packets", .type = BLOBMSG_TYPE_INT64 },
		[7] = { .name = "rx_dropped_packets", .type = BLOBMSG_TYPE_INT64 },
		[8] = { .name = "rx_plcp_error_packets", .type = BLOBMSG_TYPE_INT64 },
		[9] = { .name = "rx_fcs_error_packets", .type = BLOBMSG_TYPE_INT64 },
		[10] = { .name = "rx_mac_error_packets", .type = BLOBMSG_TYPE_INT64 },
		[11] = { .name = "rx_unknown_packets", .type = BLOBMSG_TYPE_INT64 },
		[12] = { .name = "diagnostics", .type = BLOBMSG_TYPE_TABLE },
		[13] = { .name = "noise", .type = BLOBMSG_TYPE_INT32 },
	};
	struct blob_attr *tb[ARRAY_SIZE(stats_attr)];
	blobmsg_parse(stats_attr, ARRAY_SIZE(stats_attr), tb, blobmsg_data(msg), blob_len(msg));

	if (tb[0])
		re->tx_bytes = blobmsg_get_u64(tb[0]);

	if (tb[1])
		re->tx_packets = blobmsg_get_u64(tb[1]);

	if (tb[2])
		re->tx_error_packets = blobmsg_get_u64(tb[2]);

	if (tb[3])
		re->tx_dropped_packets = blobmsg_get_u64(tb[3]);

	if (tb[4])
		re->rx_bytes = blobmsg_get_u64(tb[4]);

	if (tb[5])
		re->rx_packets = blobmsg_get_u64(tb[5]);

	if (tb[6])
		re->rx_error_packets = blobmsg_get_u64(tb[6]);

	if (tb[7])
		re->rx_dropped_packets = blobmsg_get_u64(tb[7]);

	if (tb[8])
		re->rx_plcp_error_packets = blobmsg_get_u64(tb[8]);

	if (tb[9])
		re->rx_fcs_error_packets = blobmsg_get_u64(tb[9]);

	if (tb[10])
		re->rx_mac_error_packets = blobmsg_get_u64(tb[10]);

	if (tb[11])
		re->rx_unknown_packets = blobmsg_get_u64(tb[11]);

	if (tb[12])
		parse_radio_diag(req, type, tb[12]);

	if (tb[13])
		re->anpi = noise_to_anpi((int)blobmsg_get_u32(tb[13]));
}

static void parse_radio_cac_methods(struct ubus_request *req, int type,
				    struct blob_attr *msg)
{
	struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv;
	struct blob_attr *method;
	int rem_methods;

	blobmsg_for_each_attr(method, msg, rem_methods) {
		if (blobmsg_type(method) != BLOBMSG_TYPE_STRING)
			continue;

		if (!strcmp(blobmsg_data(method), "continous"))
			re->cac_methods |= 1U << WIFI_CAC_CONTINUOUS;
		else if (!strcmp(blobmsg_data(method), "mimo-reduced"))
			re->cac_methods |= 1U << WIFI_CAC_MIMO_REDUCED;
		else if (!strcmp(blobmsg_data(method), "continous-dedicated"))
			re->cac_methods |= 1U << WIFI_CAC_DEDICATED;
		else if (!strcmp(blobmsg_data(method), "time-sliced"))
			re->cac_methods |= 1U << WIFI_CAC_TIME_SLICED;
	}
}

static void parse_radio_akm_list(struct agent_wifi_akm *akm_list, uint8_t *num,
				struct blob_attr *msg)
{
	struct blob_attr *method;
	int rem_methods;
	uint32_t val;

	memset(akm_list, 0, MAX_AKM_SUITES * sizeof(struct agent_wifi_akm));
	*num = 0;

	blobmsg_for_each_attr(method, msg, rem_methods) {
		char *akm_str;

		if (blobmsg_type(method) != BLOBMSG_TYPE_STRING)
			continue;

		if (*num >= MAX_AKM_SUITES)
			break;

		akm_str = blobmsg_data(method);
		val = strtoul(akm_str, NULL, 16);

		akm_list[*num].oui[0] = (val >> 24) & 0xFF;
		akm_list[*num].oui[1] = (val >> 16) & 0xFF;
		akm_list[*num].oui[2] = (val >>  8) & 0xFF;
		akm_list[*num].type = (val >>  0) & 0xFF;

		(*num)++;
	}
}

static void parse_radio_ap_akms(struct ubus_request *req, int type,
				struct blob_attr *msg)
{
	struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv;

	parse_radio_akm_list(re->ap_akms, &re->num_ap_akms, msg);
}

static void parse_radio_sta_akms(struct ubus_request *req, int type,
				 struct blob_attr *msg)
{
	struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv;

	parse_radio_akm_list(re->sta_akms, &re->num_sta_akms, msg);
}

static void parse_radio(struct ubus_request *req, int type,
		struct blob_attr *msg)
{
	struct wifi_radio_element *re = (struct wifi_radio_element *)req->priv;
	static const struct blobmsg_policy radio_attr[] = {
		[0] = { .name = "isup", .type = BLOBMSG_TYPE_BOOL },
		[1] = { .name = "band", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "noise", .type = BLOBMSG_TYPE_INT32 },
		[3] = { .name = "rx_streams", .type = BLOBMSG_TYPE_INT8 },
		[4] = { .name = "tx_streams", .type = BLOBMSG_TYPE_INT8 },
		[5] = { .name = "supp_channels", .type = BLOBMSG_TYPE_ARRAY },
		[6] = { .name = "opclass", .type = BLOBMSG_TYPE_INT32 },
		[7] = { .name = "channel", .type = BLOBMSG_TYPE_INT32 },
		[8] = { .name = "regdomain", .type = BLOBMSG_TYPE_STRING },
		[9] = { .name = "txpower", .type = BLOBMSG_TYPE_INT32 },
		[10] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[11] = { .name = "diagnostics", .type = BLOBMSG_TYPE_TABLE },
		[12] = { .name = "bandwidth", .type = BLOBMSG_TYPE_INT32 },
		[13] = { .name = "vendor_id", .type = BLOBMSG_TYPE_STRING },
		[14] = { .name = "stats", .type = BLOBMSG_TYPE_TABLE },
		[15] = { .name = "cac_methods", .type = BLOBMSG_TYPE_ARRAY },
		[16] = { .name = "ap_caps", .type = BLOBMSG_TYPE_TABLE },
		[17] = { .name = "sta_caps", .type = BLOBMSG_TYPE_TABLE },
		[18] = { .name = "ap_akms", .type = BLOBMSG_TYPE_ARRAY },
		[19] = { .name = "sta_akms", .type = BLOBMSG_TYPE_ARRAY },
		[20] = { .name = "acs_mode", .type = BLOBMSG_TYPE_STRING },
	};
	struct blob_attr *tb[ARRAY_SIZE(radio_attr)] = {0};
	struct agent_async_request *async_req = container_of(req, struct agent_async_request, ubus_req);
	dbg("%s ---> %s\n", __func__, re->name);

	blobmsg_parse(radio_attr, ARRAY_SIZE(radio_attr), tb, blob_data(msg), blob_len(msg));

	if (!tb[1] || !tb[6] || !tb[7] || !tb[10]) {
		struct agent *a = re->agent;

		warn("%s: insufficient radio data (%s)\n", __func__, re->name);
		timer_set(&a->init_ifaces_scheduler, IFACE_TIMEOUT * 1000);
		return;
	}

	if (tb[0])
		re->enabled = blobmsg_get_bool(tb[0]);

	if (tb[1]) {
		char *band;

		band = blobmsg_get_string(tb[1]);
		if (!strncmp(band, "2.4GHz", strlen("2.4GHz")))
			re->band = BAND_2;
		else if (!strncmp(band, "5GHz", strlen("5GHz")))
			re->band = BAND_5;
		else if (!strncmp(band, "6GHz", strlen("6GHz")))
			re->band = BAND_6;
		else
			re->band = BAND_UNKNOWN;
	}

	if (tb[2])
		re->anpi = noise_to_anpi((int)blobmsg_get_u32(tb[2]));

	if (tb[3])
		re->rx_streams = blobmsg_get_u8(tb[3]);

	if (tb[4])
		re->tx_streams = blobmsg_get_u8(tb[4]);
	if (tb[6])
		re->current_opclass = (uint8_t) blobmsg_get_u32(tb[6]);

	if (tb[7])
		re->current_channel = (uint8_t) blobmsg_get_u32(tb[7]);

	if (tb[8])
		memcpy(re->country_code, blobmsg_data(tb[8]), 2);

	if (tb[9])
		re->current_txpower_percent = (uint8_t) blobmsg_get_u32(tb[9]);

	if (tb[10]) {
		char macaddr[18] = {0};

		strncpy(macaddr, blobmsg_data(tb[10]), 17);
		if (!hwaddr_aton(macaddr, re->macaddr))
			return;
	}

	if (tb[11])
		parse_radio_diag(req, type, tb[11]);

	if (tb[12])
		re->current_bandwidth = blobmsg_get_u32(tb[12]);

	if (tb[13])
		strncpy(re->vendor, blobmsg_data(tb[13]),
				sizeof(re->vendor) - 1);

	if (tb[14])
		parse_radio_stats(req, type, tb[14]);

	if (tb[15])
		parse_radio_cac_methods(req, type, tb[15]);

	if (tb[16])
		parse_radio_ap_caps(req, type, tb[16]);

	if (tb[17])
		parse_radio_sta_caps(req, type, tb[17]);

	if (tb[18])
		parse_radio_ap_akms(req, type, tb[18]);

	if (tb[19])
		parse_radio_sta_akms(req, type, tb[19]);

	if (tb[20]) {
		char *acs_mode;

		acs_mode = blobmsg_get_string(tb[20]);
		if (!strncmp(acs_mode, "enabled", 7))
			re->acs_enabled = true;
		else
			re->acs_enabled = false;
	}

	agent_config_opclass(re);

	if (async_req->success_cb)
		async_req->success_cb(async_req);
}

#define ADD_NBR_TIMER 10
void reschedule_nbrlist_update(struct netif_ap *ap)
{
	int elapsed = 0;
	int remaining = timer_remaining_ms(&ap->nbr_timer);

	if (remaining < ADD_NBR_TIMER)
		elapsed = ADD_NBR_TIMER - (remaining == -1 ? 0 : remaining);

	timer_set(&ap->nbr_timer, (ADD_NBR_TIMER - elapsed) * 1000);
}

static void agent_available_scan_timer_cb(atimer_t *t)
{
	dbg("[Status code] SCAN NOT COMPLETED (timeout)\n\n");

	struct wifi_radio_element *re;
	int ret;

	re = container_of(t, struct wifi_radio_element, available_scan_timer);

	if (WARN_ON(!re))
		return;

	if (re->scan_state != SCAN_REQUESTED || !re->scan_req.mid)
		return;

	// TODO: revisit - maybe redundant
	re->scan_req.status = CH_SCAN_STATUS_SCAN_NOT_COMPLETED;
	re->scan_state = SCAN_ERROR;

	ret = scan_send_response(re->agent, re, &re->scan_req);
	if (ret)
		return;

	re->scan_req = (const struct wifi_scan_request_radio){ 0 };
}

static void agent_unassoc_sta_meas_timer_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);

	struct wifi_radio_element *re;

	re = container_of(t, struct wifi_radio_element, unassoc_sta_meas_timer);
	if (WARN_ON(!re))
		return;

	unassoc_sta_send_link_metrics_response(re);
}

struct wifi_radio_element *agent_add_radio(struct agent *a, char *name)
{
	struct wifi_radio_element *re;
	char r_objname[32] = {0};

	re = agent_get_radio_by_name(a, name);
	if (re)
		return re;

	re = calloc(1, sizeof(struct wifi_radio_element));
	if (!re)
		return NULL;

	a->num_radios++;
	re->agent = a;
	list_add(&re->list, &a->radiolist);
	INIT_LIST_HEAD(&re->aplist);
	INIT_LIST_HEAD(&re->unassocstalist);
	strncpy(re->name, name, sizeof(re->name));
	timer_init(&re->available_scan_timer,
				agent_available_scan_timer_cb);
	timer_init(&re->preference_report_timer, preference_report_timer_cb);
	timer_init(&re->bk.steer_timeout, bsta_steer_cb);
	timer_init(&re->unassoc_sta_meas_timer,
				agent_unassoc_sta_meas_timer_cb);

	snprintf(r_objname, 31, "wifi.radio.%s", re->name);
	re->obj_id = ubus_get_object(a->ubus_ctx, r_objname);
	if (re->obj_id == WIFI_OBJECT_INVALID)
		warn("%s: object %s not present!\n", __func__, r_objname);
	return re;
}

static int agent_init_wifi_radios(struct agent *a)
{
	trace("%s: --->\n", __func__);
	struct agent_config_radio *rcfg = NULL;
	int ret = 0;

	a->band_pending_mask = 0;
	list_for_each_entry(rcfg, &a->cfg.radiolist, list) {
		struct wifi_radio_element *re;
		char r_objname[32] = {0};


		re = agent_add_radio(a, rcfg->name);
		if (!re) {
			err("%s: Failed to alloc radio:%s (OOM)\n", __func__,
			    rcfg->name);
			continue;
		}

		snprintf(r_objname, 31, "wifi.radio.%s", re->name);
		if (ubus_call_object_async(a, a->ubus_ctx, r_objname,
					   re->obj_id,
					   "status",
					   parse_radio,
					   async_radio_status_success,
					   async_radio_timeout, re)) {
			warn("%s: error generating invoke for %s->%s, skip\n",
				__func__, r_objname, "status");
			continue;
		}

		a->band_pending_mask |= rcfg->band;

		/* Get fresh opclass preferences after scan */
		wifi_radio_update_opclass_preferences(a, re, 1);
		agent_set_post_scan_action_pref(a, re, true);

		/* Allocate radio_element's scanlist */
		scan_init_scanlist(a, re);
	}

	agent_init_wsc_attributes(a);
	return ret;
}

struct netif_ap *netif_alloc_ap(struct agent *a, const char *ifname)
{
	struct netif_ap *n = NULL;
	char objname[32] = {0};

	n = calloc(1, sizeof(struct netif_ap));
	if (!n)
		return NULL;
	n->agent = a;

	INIT_LIST_HEAD(&n->stalist);
	INIT_LIST_HEAD(&n->nbrlist);
	INIT_LIST_HEAD(&n->restrict_stalist);
	snprintf(n->ifname, sizeof(n->ifname), "%s", ifname);
	snprintf(objname, sizeof(objname), "wifi.ap.%s", ifname);

	timer_init(&n->nbr_timer, refresh_neighbor_list);
	timer_init(&n->bss_timer, refresh_bssinfo);
	timer_set(&n->bss_timer, BSS_REFRESH_INTERVAL);
	n->obj_id = ubus_get_object(a->ubus_ctx, objname);
	if (n->obj_id == WIFI_OBJECT_INVALID)
		warn("%s: object:%s missing over UBUS\n", __func__, objname);
	return n;
}

void netif_init_bsta(struct agent *a, struct netif_bk *bsta,
				 struct netif_bkcfg *cfg)
{
	char objname[32] = {0};
#if (EASYMESH_VERSION > 2)
	struct dpp_chirp *u = NULL;
#endif
	snprintf(objname, 31, "wifi.bsta.%s", cfg->name);
	snprintf(bsta->ifname, sizeof(bsta->ifname), "%s", cfg->name);
	bsta->cfg = cfg;
	bsta->agent = a;
#if (EASYMESH_VERSION >= 6)
	if (cfg->mld_id) {
		struct mld_credential *mldcred;

		mldcred = agent_get_mld_credential_by_id(&a->cfg,
							 cfg->mld_id);
		if (mldcred)
			snprintf(objname, 31, "wifi.bstamld.%s", mldcred->ifname);
	}
#endif
#if (EASYMESH_VERSION > 2)
	u = dpp_chirp_by_ifname(&a->cfg, bsta->ifname);
	if (u)
		bsta->dpp_cfg = u;
	else
		bsta->dpp_cfg = NULL;
#endif

	bsta->obj_id = ubus_get_object(a->ubus_ctx, objname);
	if (bsta->obj_id == WIFI_OBJECT_INVALID)
		warn("%s: object:%s missing over UBUS\n", __func__, objname);
}

struct netif_wds *netif_alloc_wds(struct agent *a, const char *ifname)
{
	struct netif_wds *n = NULL;

	n = calloc(1, sizeof(struct netif_wds));
	if (!n)
		return NULL;

	snprintf(n->ifname, 15, "%s", ifname);

	list_add(&n->list, &a->wdslist);

	return n;
}

void netif_free_wds(struct netif_wds *n)
{
	list_del(&n->list);
	free(n);
}

void netif_free_wds_by_ifname(struct agent *a, const char *ifname)
{
	struct netif_wds *n = NULL;

	n = agent_get_wds_by_ifname(a, ifname);
	if (!n)
		return;

	netif_free_wds(n);
}

/* get first ap on the radio */
struct netif_ap *wifi_radio_to_ap(struct agent *a, struct wifi_radio_element *re)
{
	struct netif_ap *ap = NULL;

	list_for_each_entry(ap, &re->aplist, list) {
		return ap;
	}

	return NULL;
}

void agent_handle_bh_lost(struct agent *a, struct netif_bk *bk, bool immediate)
{
	char ul_ifname[16] = {0};

	if (!bk->cfg->enabled || !bk->cfg->onboarded)
		return;

	dynbh_handle_bh_lost(a, bk, immediate);

	if (agent_get_backhaul_ifname(a, ul_ifname)) {
		if (!strncmp(ul_ifname, bk->ifname, IFNAMSIZ)) {
			char ev[512] = {0};

			sprintf(ev,
				"{\"action\":\"unset_uplink\""
				",\"type\":\"wifi\"}");

			agent_notify_event(a, "map.agent", ev);
			agent_exec_platform_scripts("unset_uplink wifi");
		}
	}
}

void bk_toggle(struct agent *a, struct netif_bk *bk,
		uint8_t *bssid, uint32_t freq)
{
	/* connect */
	if (!hwaddr_is_zero(bssid) && memcmp(bk->macaddr, bssid, 6)) {
#if (EASYMESH_VERSION >= 6)
		if (!a->bstamld || a->cfg.physical_mld)
			dynbh_bsta_disable_lower_priority(a, bk->ifname);
#else
		dynbh_bsta_disable_lower_priority(a, bk->ifname);
#endif

		if (!bk->connected) {
#if (EASYMESH_VERSION >= 6)
			if (a->bstamld && bk != a->bstamld->setup_link)
				return;
#endif
			wifi_bsta_connect(a, bk, bssid, freq);
			timer_set(&a->disable_unconnected_bstas_scheduler, 0);
			timer_del(&a->bh_lost_timer);
			timer_del(&a->bh_reconf_timer);
		}
	} else {
		bk->connected = false;
		/* check if part of the bridge */
		if (if_isbridge_interface(bk->ifname) > 0) {
			wifi_mod_bridge(a, bk, false);
			agent_handle_bh_lost(a, bk, false);
		}
	}
}

void parse_bk(struct ubus_request *req, int type,
		struct blob_attr *msg)
{
	struct netif_bk *bk = (struct netif_bk *)req->priv;
	struct agent *a = bk->agent;
	char bssid_str[18] = {0}, macaddr[18] = {0};
	uint8_t bssid[6] = {0};
	uint32_t freq = 0;
	static const struct blobmsg_policy ap_attr[] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "frequency", .type = BLOBMSG_TYPE_INT32 },
	};
	struct blob_attr *tb[ARRAY_SIZE(ap_attr)];

	blobmsg_parse(ap_attr, ARRAY_SIZE(ap_attr), tb, blob_data(msg), blob_len(msg));

	if (!tb[0]) {
		dbg("%s: bsta (%s) did not have macaddr\n", __func__, bk->ifname);
		return;
	}

	/* macaddr */
	strncpy(macaddr, blobmsg_data(tb[0]), 17);
	hwaddr_aton(macaddr, bk->macaddr);

	/* bssid */
	if (tb[1]) {
		strncpy(bssid_str, blobmsg_data(tb[1]), 17);
		hwaddr_aton(bssid_str, bssid);
	}

	/* freq */
	if (tb[2])
		freq = blobmsg_get_u32(tb[2]);

	/* connect / disconnect + dynbh */
	bk_toggle(a, bk, bssid, freq);
}

static void parse_beacon_params(struct ubus_request *req, int type,
		struct blob_attr *msg)
{
	struct wifi_bss_element *bss = (struct wifi_bss_element *)req->priv;
	int rem;
	struct blob_attr *cur;
	struct blob_attr *data[1];
	static const struct blobmsg_policy beacon_attr[1] = {
		[0] = { .name = "beacon-ies", .type = BLOBMSG_TYPE_ARRAY },
	};

	blobmsg_parse(beacon_attr, 1, data, blob_data(msg), blob_len(msg));

	if (!data[0])
		return;

	blobmsg_for_each_attr(cur, data[0], rem) {
		char *iestr = strdup(blobmsg_get_string(cur));

		if (!iestr)
			return;

		/* WMM */
		if (!strncmp(iestr, "dd", 2) &&
		    !strncmp(iestr + 4, "0050f202", 8) &&
		    strlen(iestr) == 52) {
			uint8_t *ie, *ie1;
			int i, slen, esp_len;
			int ac;		/* Access Category */

			slen = strlen(iestr) / 2;
			ie = calloc(slen, sizeof(uint8_t));
			if (!ie) {
				free(iestr);
				return;
			}

			strtob(iestr, slen, ie);
			esp_len = ie[1];

			ie1 = ie + 10;
			for (i = 0; i < esp_len - 10; i += 4) {
				ac = (*ie1) >> 5;
				ac &= 0x03;

				switch (ac) {
				case BK:
					bss->is_ac_bk = 1;
					memcpy(bss->est_wmm_bk, ie1, 3);
					break;
				case BE:
					bss->is_ac_be = 1;
					memcpy(bss->est_wmm_be, ie1, 3);
					break;
				case VI:
					bss->is_ac_vi = 1;
					memcpy(bss->est_wmm_vi, ie1, 3);
					break;
				case VO:
					bss->is_ac_vo = 1;
					memcpy(bss->est_wmm_vo, ie1, 3);
					break;
				}
				ie1 += 4;
			}

			free(ie);
			free(iestr);
			return;
		}
#if (EASYMESH_VERSION >= 6)
		else if ((!strncmp(iestr, "ff", 2)) &&
			(!strncmp(iestr + 4, "6a", 2))) {
			/*
			* esp string having this format: "ff xx 6a"
			* where xx: 9 or 11
			*/
			uint8_t *ie;
			int slen, len;

			slen = strlen(iestr) / 2;
			ie = calloc(slen, sizeof(uint8_t));
			if (!ie) {
				free(iestr);
				return;
			}

			strtob(iestr, slen, ie);
			len = ie[1];

			memcpy(bss->eht_oper_caps, ie + 3, len - 1);

			free(ie);
			free(iestr);
			return;
		}
#endif
		free(iestr);
	}
}

void parse_ap_stats_load(struct blob_attr *msg,
				struct wifi_bss_element *bss)
{
	static const struct blobmsg_policy ap_stats_load[] = {
		[0] = { .name = "utilization", .type = BLOBMSG_TYPE_INT32},
		[1] = { .name = "sta_count", .type = BLOBMSG_TYPE_INT32},
	};
	struct blob_attr *tb[ARRAY_SIZE(ap_stats_load)];

	blobmsg_parse(ap_stats_load, ARRAY_SIZE(ap_stats_load), tb, blobmsg_data(msg), blob_len(msg));

	if (tb[0])
		bss->utilization = blobmsg_get_u32(tb[0]);
	if (tb[1])
		bss->num_stations = blobmsg_get_u32(tb[1]);
}

void parse_ap_stats(struct ubus_request *req, int type,
		struct blob_attr *msg)
{
	struct wifi_bss_element *bss = (struct wifi_bss_element *)req->priv;
	static const struct blobmsg_policy ap_stats_attr[] = {
		[0] = { .name = "tx_bytes", .type = BLOBMSG_TYPE_INT64},
		[1] = { .name = "rx_bytes", .type = BLOBMSG_TYPE_INT64},
		[2] = { .name = "tx_packets", .type = BLOBMSG_TYPE_INT64},
		[3] = { .name = "rx_packets", .type = BLOBMSG_TYPE_INT64},
		[4] = { .name = "tx_error_packets", .type = BLOBMSG_TYPE_INT64},
		[5] = { .name = "load", .type = BLOBMSG_TYPE_TABLE},
		/* TODO: mcast/bcast bytes not available in UBUS output */
	};
	struct blob_attr *tb[ARRAY_SIZE(ap_stats_attr)];

	blobmsg_parse(ap_stats_attr, ARRAY_SIZE(ap_stats_attr),
		      tb, blob_data(msg), blob_len(msg));

	if (tb[0])
		bss->tx_ucast_bytes = blobmsg_get_u64(tb[0]);

	if (tb[1])
		bss->rx_ucast_bytes = blobmsg_get_u64(tb[1]);

	if (tb[2])
		bss->tx_packets = blobmsg_get_u64(tb[2]);

	if (tb[3])
		bss->rx_packets = blobmsg_get_u64(tb[3]);

	if (tb[4])
		bss->tx_error_packets = blobmsg_get_u64(tb[4]);

	if (tb[5])
		parse_ap_stats_load(tb[5], bss);
}
static void parse_ap(struct ubus_request *req, int type,
		struct blob_attr *msg)
{
	struct netif_ap *ap = (struct netif_ap *)req->priv;
	char bssid[18] = { 0 };
	static const struct blobmsg_policy ap_attr[] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "capabilities", .type = BLOBMSG_TYPE_TABLE },
		[3] = { .name = "channel", .type = BLOBMSG_TYPE_INT32 },
		[4] = { .name = "ssid", .type = BLOBMSG_TYPE_STRING },
		[5] = { .name = "standard", .type = BLOBMSG_TYPE_STRING },
		[6] = { .name = "bandwidth", .type = BLOBMSG_TYPE_INT32 },
		[7] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
		[8] = { .name = "num_stations", .type = BLOBMSG_TYPE_INT32 },
		[9] = { .name = "enabled", .type = BLOBMSG_TYPE_BOOL },
#if (EASYMESH_VERSION >= 6)
		[10] = { .name = "mld", .type = BLOBMSG_TYPE_STRING },
		[11] = { .name = "mld_ifname", .type = BLOBMSG_TYPE_STRING },
		[12] = { .name = "linkid", .type = BLOBMSG_TYPE_INT32 },
#endif
		[13] = { .name = "band", .type = BLOBMSG_TYPE_STRING },
		[14] = { .name = "max_stations", .type = BLOBMSG_TYPE_INT32 },
	};
	struct blob_attr *tb[ARRAY_SIZE(ap_attr)];

	blobmsg_parse(ap_attr, ARRAY_SIZE(ap_attr), tb, blob_data(msg), blob_len(msg));

	if (!tb[1])
		return;

	strncpy(bssid, blobmsg_data(tb[1]), 17);
	if (!hwaddr_aton(bssid, ap->bssid))
		return;

	if (tb[3])
		ap->channel = blobmsg_get_u32(tb[3]);

	if (tb[4])
		strncpy(ap->ssid, blobmsg_data(tb[4]), sizeof(ap->ssid) - 1);

	if (tb[5])
		strncpy(ap->standard, blobmsg_data(tb[5]), sizeof(ap->standard) - 1);

	if (tb[6])
		ap->bandwidth = blobmsg_get_u32(tb[6]);

	if (tb[9])
		ap->enabled = blobmsg_get_bool(tb[9]);

	if (tb[8])
		ap->num_sta = blobmsg_get_u32(tb[8]);


	if (!ap->enabled && agent_may_enable_fhs(ap->agent))
		/* Enable fronthauls */
		timer_set(&ap->agent->enable_fhs_scheduler,
				  (IFACE_TIMEOUT + 1) * 1000);

#if (EASYMESH_VERSION >= 6)
	if (tb[11]) {
		strncpy(ap->mld_ifname, blobmsg_data(tb[11]),
			sizeof(ap->mld_ifname) - 1);
		ap->is_affiliated_ap = true;
	}

	if (tb[12])
		ap->mlo_link_id = blobmsg_get_u32(tb[12]);
#endif

	if (tb[13]) {
		char *band;

		band = blobmsg_get_string(tb[13]);
		if (!strncmp(band, "2.4GHz", strlen("2.4GHz")))
			ap->band = BAND_2;
		else if (!strncmp(band, "5GHz", strlen("5GHz")))
			ap->band = BAND_5;
		else if (!strncmp(band, "6GHz", strlen("6GHz")))
			ap->band = BAND_6;
		else
			ap->band = BAND_UNKNOWN;
	}

	if (tb[14])
		ap->max_sta = blobmsg_get_u32(tb[14]);

}

static int agent_ap_add_stations(struct agent *a, struct netif_ap *ap)
{
	int num_sta = ap->max_sta ? ap->max_sta : 128;
	int i;

#if (EASYMESH_VERSION >= 6)
	if (ap->is_affiliated_ap) {
		struct wifi_mlsta mlsta[128] = {0};

		if (wifi_get_mld_stations(ap, mlsta, &num_sta)) {
			dbg("%s: failed to get mld stations\n", __func__);
			return 0;
		}

		for (i = 0; i < num_sta; i++) {
			if (!memcmp(mlsta[i].sta->bssid, ap->bssid, 6))
				wifi_add_sta(a, ap->ifname, mlsta[i].sta->macaddr);
		}
	} else {
#else
	if (1) {
#endif
		uint8_t sta[128 * 6] = {};

		if (wifi_get_assoclist(ap, sta, &num_sta)) {
			dbg("%s: failed to get assoclist\n", __func__);
			return 0;
		}

		for (i = 0; i < num_sta; i++)
			wifi_add_sta(a, ap->ifname, &sta[i * 6]);
	}

	return num_sta;
}

void agent_init_interfaces_post_actions(struct agent *a)
{
	trace("%s: --->\n", __func__);

	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			int num_sta = 0;

			/* Block STas in WiFi if wifi is restarted at run-time */
			assoc_ctrl_apply_restriction(a, ap);

			/* Add stations to the interface */
			num_sta = agent_ap_add_stations(a, ap);

			/* Notify ctrl once upon start if adding STA is allowed */
			if (ap->max_sta && !ap->sent_assocnotif) {
				agent_notify_assoc_status(a, ap->bssid, num_sta >= ap->max_sta ? false : true);
				ap->sent_assocnotif = true;
			}
		}

		/* Get scan results from driver and store in radio element */
		scan_get_results(a, re);
	}

	agent_exec_platform_scripts("update_acs");
}

int agent_get_system_info(struct agent *a)
{

	char buf[68] = {0};

	chrCmd(buf, sizeof(buf), "/lib/wifi/multiap db_get device.deviceinfo.SerialNumber");

	strncpy(a->device_inventory.serial_number, buf,
		sizeof(a->device_inventory.serial_number) - 1);

	chrCmd(buf, sizeof(buf), "/lib/wifi/multiap db_get device.deviceinfo.SoftwareVersion");

	strncpy(a->device_inventory.sw_version, buf,
		sizeof(a->device_inventory.sw_version) - 1);

	dbg("%s: serial number:%s software version:%s\n", __func__, a->device_inventory.serial_number,
		a->device_inventory.sw_version);
	return 0;
}

static int agent_init_interfaces(struct agent *a)
{
	trace("%s: --->\n", __func__);

	struct agent_config *cfg = &a->cfg;
	struct wifi_radio_element *re = NULL;
	struct netif_apcfg *f = NULL;
	struct netif_bkcfg *b = NULL;
#if (EASYMESH_VERSION >= 6)
	struct mld_credential *mldcred = NULL;
	struct netif_mld *mld = NULL, *tmp;
#endif

	if (list_empty(&a->radiolist)) {
		warn("%s: Failure initializing radios, retry in 10s\n", __func__);
		timer_set(&a->init_ifaces_scheduler, 10 * 1000);
	}

	/* mark APs as torndown before fetching fresh data */
	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		re->has_bsta = false;
		list_for_each_entry(ap, &re->aplist, list) {
			ap->torndown = true;
			ap->enabled = false;
#if (EASYMESH_VERSION >= 6)
			if (ap->is_affiliated_ap)
				mld_del_ap(a, ap);
#endif
		}
	}

#if (EASYMESH_VERSION >= 6)
	list_for_each_entry(mldcred, &cfg->mldlist, list) {
		if ((mldcred->type == MLD_TYPE_BACKHAUL_BSS ||
		     mldcred->type == MLD_TYPE_FRONTHAUL_BSS) &&
		     !agent_get_apmld_by_ifname(a, mldcred->ifname)) {
			mld = agent_alloc_apmld(a, mldcred->ifname);
			if (!mld)
				warn("%s: failed to allocate mld:%s\n", __func__, mldcred->ifname);
			else
				mld->id = mldcred->id;
		}
	}

	/* mark AP MLDs as disabled before fetching fresh data*/
	list_for_each_entry(mld, &a->apmldlist, list)
		mld->enabled = false;
#endif
	a->connected = false;

	list_for_each_entry(f, &cfg->aplist, list) {
		void (*status_cb)(struct ubus_request *, int, struct blob_attr *) = parse_ap;
		void (*stats_cb)(struct ubus_request *, int, struct blob_attr *) = parse_ap_stats;
		struct wifi_bss_element *bss;
		struct netif_ap *fn = NULL;
		char objname[32] = {0};
		struct subscribe_fr {
			uint8_t type;
			uint8_t stype;
		} subfr[] = {{0, 0}, {0, 2}, {0, 13}};
		int num_subfr = ARRAY_SIZE(subfr);
		struct blob_buf bb = {0};
#if (EASYMESH_VERSION >= 6)
		struct netif_mld *mld = NULL;
		int band = 0;
#endif
		uint32_t obj_id = 0;
		int ret;
		int k;

		snprintf(objname, 31, "wifi.ap.%s", f->name);

		re = agent_get_radio_by_name(a, f->device);
		if (!re)
			continue;

		fn = agent_get_ap_by_ifname(a, f->name);
		if (!fn) {
			fn = netif_alloc_ap(a, f->name);
			if (fn) {
				/* subscribe following frames
				* WIFI_FRAME_ASSOC_REQ,
				* WIFI_FRAME_REASSOC_REQ,
				* WIFI_FRAME_ACTION
				*/
				// TODO: call unsubscribe_frame method on cleanup.

				list_add(&fn->list, &re->aplist);
				fn->cfg = f;
				strncpy(fn->radio_name, re->name, IFNAMSIZ-1);
				fn->sent_assocnotif = false;
			} else
				continue;
		}
		obj_id = fn->obj_id;
		fn->band = re->band;
		fn->torndown = false;

#if (EASYMESH_VERSION >= 6)
		if (f->mld_id) {
			mld = agent_get_apmld_by_id(a, f->mld_id);
			if (mld) {
				snprintf(objname, 31, "wifi.apmld.%s", mld->ifname);
				status_cb = apmld_parse;
				stats_cb = parse_apmld_stats;
				band = band_to_int(re->band);
				obj_id = mld->obj_id;
			}
		}
#endif

		ret = ubus_call_object(a->ubus_ctx, obj_id, "status", status_cb, fn);
		if (ret) {
			err("Failed to get '%s' (ret = %d) name:%s\n", "status", ret, objname);
			timer_set(&a->init_ifaces_scheduler,
				IFACE_TIMEOUT * 1000);
		}

		bss = &fn->bss;
		if (memcmp(bss->bssid, fn->bssid, 6)) {
			memcpy(bss->bssid, fn->bssid, 6);
			/* New AP or order has changed - read STA restrictions from file */
			assoc_ctrl_sync_from_file(a, fn);
		} else {
			/* No change in AP list, only reapply restrictions in WiFi */
			assoc_ctrl_apply_restriction(a, fn);
		}
		strncpy(bss->ssid, fn->ssid, sizeof(bss->ssid) - 1);
		bss->enabled = fn->enabled;

		for (k = 0; k < num_subfr; k++)
			wifi_subscribe_frame(f->name, subfr[k].type, subfr[k].stype);

		blob_buf_init(&bb, 0);
#if (EASYMESH_VERSION >= 6)
		if (f->mld_id)
			blobmsg_add_u32(&bb, "band", band);
#endif
		ubus_call_object_args(a->ubus_ctx, obj_id, &bb, "stats", stats_cb, bss);
		ubus_call_object_args(a->ubus_ctx, obj_id, &bb, "dump_beacon", parse_beacon_params, bss);
		blob_buf_free(&bb);

		if (fn->enabled)
			timer_set(&fn->bss_timer, BSS_REFRESH_INTERVAL);
	}

	/** if AP is gone from config free it altogether
	 *  if AP is disabled, cancel all timers and clear data
	 */
	re = NULL;
	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL, *tmp;

		list_for_each_entry_safe(ap, tmp, &re->aplist, list) {
			if (ap->torndown)
				netif_free(ap);
			else if (!ap->enabled)
				netif_reset(ap);
		}

	}

	list_for_each_entry(b, &cfg->bklist, list) {
		void (*bk_parser)(struct ubus_request *, int, struct blob_attr *) = parse_bk;
		struct wifi_radio_element *re;
		struct netif_bk *bn;
		int ret;

		re = agent_get_radio_by_name(a, b->device);
		if (re) {
			bn = &re->bk;
			re->has_bsta = true;
		}
#if (EASYMESH_VERSION >= 6)
		else if (b->is_mld_netdev) {

			if (!a->bstamld) {
				struct wifi_radio_element *re;

				a->bstamld = agent_alloc_bstamld(a, b->name);
				if (!a->bstamld) {
					agnt_err(LOG_MLO, "%s: failed to alloc bSTA bstamld ifname:%s\n", __func__, b->name);
					continue;
				}

				re = agent_get_radio_with_ifname(a, b->name);
				if (!re) {
					agnt_warn(LOG_MLO, "%s: could not find radio element\n", __func__);
					continue;
				}

				a->bstamld->str_enabled = re->wifi7_caps.bsta_str_support;
				a->bstamld->nstr_enabled = re->wifi7_caps.bsta_nstr_support;
				a->bstamld->emlsr_enabled = re->wifi7_caps.bsta_emlsr_support;
				a->bstamld->emlmr_enabled = re->wifi7_caps.bsta_emlmr_support;
			}
			bn = &a->bstamld->bk;
		}
#endif
		else
			continue;

		/* TODO: some condition to avoid redundant re-init? */
		netif_init_bsta(a, bn, b);

#if (EASYMESH_VERSION >= 6)
		if (b->mld_id)
			bk_parser = bstamld_parse;
#endif
		memset(bn->macaddr, 0, 6);
		ret = ubus_call_object(a->ubus_ctx, bn->obj_id, "status", bk_parser, bn);
		if (ret) {
			dbg("Failed to get '%s' (ret = %d) ifname:%s\n", "status", ret, bn->ifname);
			timer_set(&a->init_ifaces_scheduler,
					IFACE_TIMEOUT * 1000);
		}

		if (bn->connected)
			a->connected = true;
	}

#if (EASYMESH_VERSION >= 6)
	/* if an MLD is gone from config free it */
	mld = NULL;
	list_for_each_entry_safe(mld, tmp, &a->apmldlist, list) {
		if (!agent_get_mld_credential_by_ifname(&a->cfg, mld->ifname))
			apmld_clean(a, mld);
	}

	if (a->bstamld) {
		/* if bsta config has gone away free bstamld */
		if (!agent_get_bsta_mld_credential(&a->cfg))
			agent_clear_bsta_mld(a);
	}
#endif

	/* TODO: move this to backhaul.c and post_actions */
	if (!agent_has_active_backhaul(a)
	    && timer_remaining_ms(&a->bh_lost_timer) == -1
	    && !list_empty(&cfg->bklist)) {
		if (a->cfg.ap_follow_sta_dfs) {
			struct dyn_bh_cfg *dbcfg = &a->cfg.dbhcfg;
			struct wifi_radio_element *re = NULL;
			struct netif_bk *sta = NULL;

			list_for_each_entry(re, &a->radiolist, list) {
				if (!re->has_bsta)
					continue;

				sta = &re->bk;
				dbg("%s: [%s] connect %s bsta\n", __func__,
				    sta->ifname,
				    sta->cac_time ? "disable" : "enable");
				if (sta->cac_time)
					config_disable_bsta(sta->cfg);
				else
					config_enable_bsta(sta->cfg);
			}

			dynbh_bsta_scan_on_enabled(a);
			if (dbcfg->bh_miss_tmo)
				/* Trigger bh link loss timer */
				timer_set(&a->bh_lost_timer, dbcfg->bh_miss_tmo * 1000);

			timestamp_update(&a->disconnect_t);
		} else {
			struct dyn_bh_cfg *dbcfg = &a->cfg.dbhcfg;

			dynbh_bsta_enable_all(a);
			if (dbcfg) {
				timer_set(&a->bh_reconf_timer,
					  dbcfg->bh_reconf_tmo * 1000);
				timestamp_update(&a->disconnect_t);
			}
		}
	} else if (!list_empty(&cfg->bklist) && !a->dynbh_upgrade_ongoing) {
		timer_set(&a->dynbh_scheduler, 30 * 1000);
	}

	agent_config_reload(a);

	agent_init_interfaces_post_actions(a);
	if (cfg && cfg->pcfg && is_vid_valid(cfg->pcfg->pvid)) {
		a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_SETUP;
		timer_set(&a->reload_scheduler, 5 * 1000);
	}

	return 0;
}

static void agent_enable_fhs_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);

	struct agent *a = container_of(t, struct agent, enable_fhs_scheduler);

	agent_enable_fronthauls(a);
}

static void agent_boot_scan_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);

	struct agent *a = container_of(t, struct agent, boot_scan_scheduler);
	struct wifi_radio_element *re = NULL;
	int num_radio_disabled = 0;

	if (!a)
		return; /* err */

	dbg("%s: boot scan %s\n", __func__,
	    a->cfg.scan_on_boot_only ? "enabled" : "disabled");

	if (!a->cfg.scan_on_boot_only) {
		/* On boot scan disabled */
		return;
	}

	list_for_each_entry(re, &a->radiolist, list) {
		int ret;

		if (!re->enabled) {
			num_radio_disabled++;
			continue;
		}

		if (re->scan_state == SCAN_REQUESTED || re->scan_state == SCAN_DONE) {
			/* scan already requested or finished */
			continue;
		}

		ret = scan_radio(a, re->name, NULL, 0, NULL, 0, NULL);
		if (!ret)
			re->scan_state = SCAN_REQUESTED;
	}

	if (num_radio_disabled && a->boot_scan_tries++ < BOOT_UP_SCAN_MAX_TRY)
		/* one or more radio not available yet, re-try again later */
		timer_set(&a->boot_scan_scheduler, BOOT_UP_SCAN_ITV * 1000);
}

static void agent_fetch_ap_stats(struct agent *a)
{
	struct wifi_radio_element *re = NULL;
	struct wifi_bss_element *bss;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			char objname[32] = {0};
			struct blob_buf bb = {0};
#if (EASYMESH_VERSION >= 6)
			int band = 0;
#endif

			bss = &ap->bss;

			snprintf(objname, sizeof(objname), "wifi.ap.%s", ap->ifname);
#if (EASYMESH_VERSION >= 6)
			if (ap->cfg && ap->cfg->mld_id) {
				struct mld_credential *mldcred;

				mldcred = agent_get_mld_credential_by_id(&a->cfg,
									 ap->cfg->mld_id);
				if (mldcred) {
					snprintf(objname, sizeof(objname), "wifi.apmld.%s", mldcred->ifname);
					band = band_to_int(re->band);
				}
			}
#endif
			blob_buf_init(&bb, 0);

#if (EASYMESH_VERSION >= 6)
			if (band)
				blobmsg_add_u32(&bb, "band", band);
#endif
			ubus_call_object_args(a->ubus_ctx, ap->obj_id, &bb, "stats", parse_ap_stats, bss);
			blob_buf_free(&bb);
		}
	}
}

#define OPER_CHAN_REP_INTERVAL 60
static void agent_periodic_run(atimer_t *t)
{
	struct agent *a = container_of(t, struct agent, agent_periodic_run);
	sigset_t waiting_mask;

	a->uptime += 1;

	sigpending(&waiting_mask);

	if (sigismember(&waiting_mask, SIGHUP)) {
		uint32_t diff;
		dbg("%s: Received SIGHUP, reload config\n", __func__);
		signal(SIGHUP, SIG_IGN);

		diff = agent_config_reload(a);

		if ((diff & CONFIG_DIFF_DEBUG) && (a->lopts.level != a->cfg.debug_level)) {
			/* debug level changed in config and differs from runtime level */
			a->lopts.level = a->cfg.debug_level;
			restart_logging(&a->lopts);
			fprintf(stderr, "%s: log.level changed to %d\n",
					__func__, a->lopts.level);
		}
	}

	trace("%s: periodic time (elapsed:%"PRIu64")\n", __func__,
	      a->uptime);

	if ((a->uptime % OPER_CHAN_REP_INTERVAL) == 0)
		send_oper_channel_report(a, NULL);

	if (!a->subscribed) {
		dbg("%s: not subscribed for 1905 CMDUs, attempting to resubscribe\n", __func__);
		agent_subscribe_for_cmdus(a);
	}

	agent_fetch_ap_stats(a);

	timer_set(&a->agent_periodic_run, 1 * 1000);
}


static void agent_radio_stats_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);

	struct agent *a = container_of(t, struct agent, radio_stats_scheduler);
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		char r_objname[32] = {0};

		/* mark outdated scanresults */
		wifi_scanresults_mark_expired(&re->scanresults);

		/* get radio status and update stats */
		snprintf(r_objname, 31, "wifi.radio.%s", re->name);
		ubus_call_object_async(a, a->ubus_ctx, r_objname, re->obj_id,
				       "stats", async_parse_radio_stats, NULL,
				       NULL, re);
	}

	timer_set(&a->radio_stats_scheduler, RADIO_STATS_TIMER);
}

static void agent_init_ifaces_cb(atimer_t *t)
{
	struct agent *a = container_of(t, struct agent, init_ifaces_scheduler);

	if (!a)
		return;

	agent_init_wifi_radios(a);
}


#define RELOAD_TIMEOUT 10

static void agent_reload_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);
	struct agent *a = container_of(t, struct agent, reload_scheduler);

	if (a->cfg.eth_onboards_wifi_bhs) {
		/* re-sync any credentials passed with AP-Autoconfig */
		agent_exec_platform_scripts("bsta_to_wireless");
	}

	agent_exec_platform_scripts("sync_credentials");

	dbg("%s: reconfig_reason = %d\n", __func__, a->reconfig_reason);

	if (!!(a->reconfig_reason & AGENT_RECONFIG_REASON_VLAN_TEARDOWN)) {
		dbg("%s: ts teardown\n", __func__);
		agent_clear_traffic_sep(a);
	}

	if (!!(a->reconfig_reason & AGENT_RECONFIG_REASON_VLAN_SETUP)) {
		dbg("%s: ts setup\n", __func__);
		agent_apply_traffic_separation(a);
	}

	if (!!(a->reconfig_reason & AGENT_RECONFIG_REASON_AP_CRED)) {
		struct wifi_radio_element *re = NULL;

		dbg("%s: apconf reload APs credentials\n", __func__);
		list_for_each_entry(re, &a->radiolist, list) {
			struct netif_ap *ap = NULL;

			list_for_each_entry(ap, &re->aplist, list) {
				char cmd[512] = {};

				if (!ap->cfg)
					continue;

				if (ap->reconfig_reason & AGENT_AP_RECONFIG_SSID) {
					dbg("%s apconf reconfig ssid %s\n", ap->ifname, ap->cfg->ssid);
					snprintf(cmd, sizeof(cmd), "set_ap_ssid %s %s", ap->ifname, ap->cfg->ssid);
					agent_exec_platform_scripts(cmd);
				}

				if (ap->reconfig_reason & AGENT_AP_RECONFIG_PSK) {
					dbg("%s apconf reconfig psk %s\n", ap->ifname, ap->cfg->key);
					snprintf(cmd, sizeof(cmd), "set_ap_psk %s %s", ap->ifname, ap->cfg->key);
					agent_exec_platform_scripts(cmd);
				}

				ap->reconfig_reason = 0;
			}
		}
	}

#if (EASYMESH_VERSION >= 6)
	if (!!(a->reconfig_reason & AGENT_RECONFIG_REASON_AP_AUTOCONF) ||
	    !!(a->reconfig_reason & AGENT_RECONFIG_REASON_PUNCT_BITMAP) ||
	    !!(a->reconfig_reason & AGENT_RECONFIG_REASON_MLD_ID)) {
		dbg("%s: reload wireless\n", __func__);
#else
	if (!!(a->reconfig_reason & AGENT_RECONFIG_REASON_AP_AUTOCONF)) {
		dbg("%s: apconf reload wireless\n", __func__);
#endif

		/*
		 * Allow to change order in config/wireless.
		 * Some platforms require this before wifi
		 * reload/restart.
		 */
		agent_exec_platform_scripts("config_reorder");

		uci_reload_services("wireless");
	}

	a->reconfig_reason = 0;
}

/* send the steering compled message this function also resets the value of
 * steering opportunity
 */
static void agent_steering_opp_timeout(atimer_t *t)
{
	trace("agent: %s: --->\n", __func__);
	struct agent *a = container_of(t, struct agent, sta_steer_req_timer);
	uint8_t origin[6] = { 0 };


	memcpy(origin, a->cntlr_almac, 6);
	send_sta_steer_complete((void *)a, origin);
}

#define max_val(a,b) \
	({ typeof(a) _a = (a); \
	typeof(b) _b = (b); \
	_a > _b ? _a : _b; })

static void agent_start_controller_conditionally(struct agent *a)
{
	uint8_t max_retries = a->cntlr_select.retry_int;
	uint16_t probe_int = a->cntlr_select.probe_int;
	int try = a->cntlr_miss_count + 1;
	int max_misstime = max_val((max_retries * probe_int),
				HEARTBEAT_AUTOCFG_INTERVAL + 5);
	bool start_controller = false;

	trace("%s: Entry max_misstime %d\n", __func__, max_misstime);

	if (!timestamp_expired(&a->observed_time, (try * probe_int) * 1000)
			|| is_local_cntlr_running()) {
		dbg("%s: Active controller available\n",
			__func__);
		a->cntlr_miss_count = 0;
		a->active_cntlr = true;
		return;
	}

	if (!timestamp_expired(&a->observed_time, max_misstime * 1000)) {
		a->cntlr_miss_count++;
		return;
	}

	a->active_cntlr = false;

	memset(a->cntlr_almac, 0, sizeof(a->cntlr_almac));

	dbg("%s: No controller observed for over %d seconds\n",
	    __func__, max_misstime);

#ifdef AGENT_DYNAMIC_CNTRL
	if (a->cntlr_select.autostart)
		start_controller = true;
#endif

	if (start_controller) {
		if (is_local_cntlr_available()) {
			time_t now;
			/* Schedule start of local Controller after
			 * random time chosen from a 10s time window
			 * to reduce chance of multiple MAP-Agents
			 * starting own controller at the same time.
			 */
			srand(time(&now));
			agent_schedule_cntlr_start(a, rand() % 10);
		} else {
			warn("Local cntlr unavailable: skip start\n");
		}
	} else if (a->cntlr_miss_count) {
		/* Report controller absence to the user first time
		 * it goes missing
		 */
		wifiagent_log_cntlrinfo(a, NULL);
	}

	a->cntlr_miss_count = 0;
}


static void agent_dispatch_autoconfig(atimer_t *t)
{
	struct agent *a = container_of(t, struct agent, autocfg_dispatcher);
	struct wifi_radio_element *re = NULL;

	agnt_trace(LOG_APCFG, "%s: Triggering AP-Autoconfig Search\n", __func__);

	if (a->multiple_cntlr &&
	    timestamp_expired(&a->secondary_observed_time,
			      (HEARTBEAT_AUTOCFG_INTERVAL + 10) * 1000)) {
		a->multiple_cntlr = false;
		wifiagent_log_cntlrinfo(a, NULL);
	}

	agent_start_controller_conditionally(a);
	a->autocfg_interval = a->cntlr_select.probe_int;

	agent_check_bsta_connections(a);

	list_for_each_entry(re, &a->radiolist, list) {
		struct cmdu_buff *cmdu;
		uint8_t ieee1905band;

		if (!a->active_cntlr) {
			agnt_trace(LOG_APCFG, "%s: No active Controller present!\n", __func__);
			re->state = AUTOCFG_ACTIVE;
		}

		ieee1905band = wifi_band_to_ieee1905band(re->band);
		cmdu = agent_gen_ap_autoconfig_search(a, ieee1905band, 0x02);
		if (!cmdu)
			continue;

		agnt_trace(LOG_APCFG, "%s: Sending Autoconfig Search for radio %s(%sGHz), ieee1905band:%d\n",
				__func__, re->name,
				band_to_str(re->band), ieee1905band);
		re->mid = agent_send_cmdu(a, cmdu);

		cmdu_free(cmdu);
	}

	agnt_dbg(LOG_APCFG, "%s: Scheduling next autoconfig search in %u seconds\n",
				__func__, a->autocfg_interval);
	timer_set(&a->autocfg_dispatcher, a->autocfg_interval * 1000);
}

static void agent_metric_report_timer_cb(atimer_t *t)
{
	struct agent_config *cfg =
		container_of(t, struct agent_config, metric_report_timer);
	struct agent *a = container_of(cfg, struct agent, cfg);
	struct cmdu_buff *cmdu;
	struct node *n;

	n = agent_find_node(a, a->almac);
	if (!n)
		goto refresh_interval;

	cmdu = prepare_ap_metrics_query((void *)a, NULL);
	if (!cmdu)
		goto refresh_interval;

	send_ap_metrics_response(a, cmdu, n);
	cmdu_free(cmdu);
refresh_interval:
	timer_set(&cfg->metric_report_timer,
			cfg->pcfg->report_interval * 1000);
}

/* remove passed interface from mapagent and wireless config, if present */
static int wifi_clean_iface(const char *ifname)
{
	char fmt[128] = {0};

	snprintf(fmt, sizeof(fmt), "clean_stale_ifaces %s", ifname);
	agent_exec_platform_scripts(fmt);

	dbg("%s: Cleaned interface %s\n", __func__, ifname);
	return 0;
}

/* remove all torndown interfaces on radio from config/memory */
int wifi_clean_torndown_ifaces_on_radio(struct agent *a, struct wifi_radio_element *re)
{
	struct netif_ap *ap = NULL, *ap_tmp;

	list_for_each_entry_safe(ap, ap_tmp, &re->aplist, list) {
		if (!ap->torndown)
			continue;

		wifi_clean_iface(ap->ifname);
		clean_ap(ap->cfg);
		netif_free(ap);
	}

	return 0;
}

/* remove all torndown interfaces on radio from config/memory */
int wifi_clean_torndown_ifaces(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL, *ap_tmp;

		list_for_each_entry_safe(ap, ap_tmp, &re->aplist, list) {
			if (!ap->torndown)
				continue;
			wifi_clean_iface(ap->ifname);
			clean_ap(ap->cfg);
			netif_free(ap);
		}
	}

	return 0;
}

void clear_sta(struct sta *s)
{
	dbg("Delete STA " MACFMT "\n", MAC2STR(s->macaddr));
	timer_del(&s->sta_steer_timer);
	timer_del(&s->sta_bcn_req_timer);
	timer_del(&s->sta_timer);
	timer_del(&s->sta_steer_deauth_timer);
	list_del(&s->list);
	list_flush(&s->pref_nbrlist, struct pref_neighbor, list);
	list_flush(&s->sta_nbrlist, struct sta_neighbor, list);
	if (s->assoc_frame) {
		free(s->assoc_frame->frame);
		free(s->assoc_frame);
	}
#if (EASYMESH_VERSION >= 6)
	if (s->is_affiliated_sta)
		mld_del_sta(s->agent, s);
#endif
	free(s);
}

void clear_stalist(struct netif_ap *p)
{
	struct sta *s = NULL, *tmp;

	list_for_each_entry_safe(s, tmp, &p->stalist, list) {
		if (p->num_sta > 0)
			p->num_sta--;
		clear_sta(s);
	}
	agent_modify_assoc_status(p, true);
}

void clear_nbrlist(struct netif_ap *p)
{
	struct neighbor *n = NULL, *tmp;

	list_for_each_entry_safe(n, tmp, &p->nbrlist, list) {
		dbg("Delete NBR " MACFMT "\n", MAC2STR(n->nbr.bssid));
		list_del(&n->list);
		free(n);
	}
}

void clear_restrict_stalist(struct netif_ap *p)
{
	struct restrict_sta_entry *rs = NULL, *tmp;

	list_for_each_entry_safe(rs, tmp, &p->restrict_stalist, list) {
		timer_del(&rs->restrict_timer);
		list_del(&rs->list);
		free(rs);
	}
}

static void netif_reset(struct netif_ap *ap)
{
	/* clear stalist */
	clear_stalist(ap);

	/* clear nbrlist */
	clear_nbrlist(ap);

	/* cancel timers */
	timer_del(&ap->nbr_timer);
	timer_del(&ap->bss_timer);
	timer_del(&ap->util_threshold_timer);

#if (EASYMESH_VERSION >= 6)
	if (ap->is_affiliated_ap)
		mld_del_ap(ap->agent, ap);
#endif
}

static void netif_free(struct netif_ap *ap)
{
	netif_reset(ap);
	/* clear restricted sta list */
	clear_restrict_stalist(ap);
	list_del(&ap->list);
	free(ap);
}

void clear_aplist_in_radio(struct wifi_radio_element *re)
{
	struct netif_ap *ap = NULL, *tmp;

	list_for_each_entry_safe(ap, tmp, &re->aplist, list) {
		netif_free(ap);
	}
}

void clear_aplist(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list)
		clear_aplist_in_radio(re);
}

void clear_bk(struct agent *a, struct netif_bk *bk)
{
#if (EASYMESH_VERSION >= 6)
	if (bk->is_affiliated_sta)
		mld_del_bsta(a, bk);
#endif
	memset(bk, 0, sizeof(*bk));
}

void clear_bklist(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (!re->has_bsta)
			continue;
		clear_bk(a, &re->bk);
		re->has_bsta = false;
	}
}

void clear_wdslist(struct agent *a)
{
	struct netif_wds *p = NULL, *tmp;

	list_for_each_entry_safe(p, tmp,  &a->wdslist, list) {
		netif_free_wds(p);
	}
}

int agent_map_sub_cb(void *bus, void *priv, void *data)
{
	struct blob_attr *msg = (struct blob_attr *)data;
	char *str;


	str = blobmsg_format_json(msg, true);
	trace("Received notification '%s'\n", str);
	free(str);

	ieee1905_cmdu_event_handler(priv, msg);

	return 0;
}

int agent_map_del_cb(void *bus, void *priv, void *data)
{
	struct agent *a = (struct agent *)priv;
	uint32_t *obj = (uint32_t *)data;

	a->subscribed = false;
	map_unsubscribe(bus, a->subscriber);
	a->subscriber = NULL;
	fprintf(stdout, "Object 0x%x no longer present\n", *obj);

	return 0;
}

int agent_subscribe_for_cmdus(struct agent *a)
{
	mapmodule_cmdu_mask_t cmdu_mask = {0};
	uint32_t map_id;
	int ret;


	map_prepare_cmdu_mask(cmdu_mask,
			CMDU_TYPE_TOPOLOGY_DISCOVERY,
			CMDU_TYPE_TOPOLOGY_NOTIFICATION,
			CMDU_TYPE_TOPOLOGY_QUERY,
			CMDU_TYPE_TOPOLOGY_RESPONSE,
			CMDU_TYPE_VENDOR_SPECIFIC,
			CMDU_TYPE_LINK_METRIC_QUERY,
			CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH,
			CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE,
			CMDU_TYPE_AP_AUTOCONFIGURATION_WSC,
			CMDU_TYPE_AP_AUTOCONFIGURATION_RENEW,
			CMDU_TYPE_HIGHER_LAYER_QUERY,
			CMDU_1905_ACK,
			CMDU_BEACON_METRICS_QUERY,
			CMDU_BACKHAUL_STEER_REQUEST,
			CMDU_AP_METRICS_QUERY,
			CMDU_ASSOC_STA_LINK_METRICS_QUERY,
			CMDU_UNASSOC_STA_LINK_METRIC_QUERY,
			CMDU_CHANNEL_SCAN_REQUEST,
			CMDU_CHANNEL_SCAN_REPORT,
			CMDU_POLICY_CONFIG_REQ,
			CMDU_BACKHAUL_STA_CAPABILITY_QUERY,
			CMDU_BACKHAUL_STA_CAPABILITY_REPORT,
			CMDU_CHANNEL_PREFERENCE_QUERY,
			CMDU_CHANNEL_SELECTION_REQ,
#ifdef OPER_CHAN_CHANGE_RELAY_MCAST
			CMDU_OPERATING_CHANNEL_REPORT,
#endif
			CMDU_CLIENT_STEERING_REQUEST,
			CMDU_CLIENT_ASSOC_CONTROL_REQUEST,
			CMDU_AP_CAPABILITY_QUERY,
			CMDU_AP_CAPABILITY_REPORT,
			CMDU_HIGHER_LAYER_DATA,
			CMDU_CLIENT_CAPABILITY_QUERY,
			CMDU_CAC_REQUEST,
			CMDU_CAC_TERMINATION,
#if (EASYMESH_VERSION > 2)
			CMDU_BSS_CONFIG_RESPONSE,
			CMDU_DPP_CCE_INDICATION,
			CMDU_PROXIED_ENCAP_DPP,
			CMDU_DPP_BOOTSTRAPING_URI,
			CMDU_DIRECT_ENCAP_DPP,
			CMDU_AGENT_LIST,
			CMDU_SERVICE_PRIORITIZATION_REQUEST,
#endif
#if (EASYMESH_VERSION >= 6)
			CMDU_AP_MLD_CONFIG_REQUEST,
			CMDU_BSTA_MLD_CONFIG_REQUEST,
#endif
			-1);

	memcpy(a->cmdu_mask, cmdu_mask, sizeof(a->cmdu_mask));

	trace("<----------------------------------- %s\n", __func__);
	for (;;) {
		ret = ubus_lookup_id(a->ubus_ctx, map_plugin, &map_id);
		if (!ret)
			break;

		trace("ieee1905.map not up yet, sleeping for 2s!\n");
		sleep(2);
	}

	a->map_oid = map_id;

	ret = map_subscribe(a->ubus_ctx,
			    &a->map_oid,
			    "mapagent", &cmdu_mask, a,
			    agent_map_sub_cb,
			    agent_map_del_cb,
			    &a->subscriber);
	if (!ret) {
		a->subscribed = true;
	} else {
		trace("mapagent: Failed to 'register' with %s (err = %s)\n",
		      map_plugin, ubus_strerror(ret));
	}

	return ret;
}

int agent_init_defaults(struct agent *a)
{
	INIT_LIST_HEAD(&a->radiolist);
	INIT_LIST_HEAD(&a->framelist);
	INIT_LIST_HEAD(&a->nodelist);
	INIT_LIST_HEAD(&a->wdslist);
	INIT_LIST_HEAD(&a->pending_asynclist);
#if (EASYMESH_VERSION > 2)
	INIT_LIST_HEAD(&a->qos_rules);
#endif
#if (EASYMESH_VERSION >= 6)
	INIT_LIST_HEAD(&a->apmldlist);
#endif

	a->cntlr_select.local = CONTROLLER_SELECT_LOCAL;
	a->cntlr_select.probe_int = CONTROLLER_SELECT_PROBE_INT;
	a->cntlr_select.retry_int = CONTROLLER_SELECT_RETRY_INT;
#ifdef AGENT_DYNAMIC_CNTRL
	a->cntlr_select.autostart = CONTROLLER_SELECT_AUTOSTART;
#endif

	/* don't try to start cntlr right away */
	timestamp_update(&a->observed_time);
	timestamp_update(&a->last_unassoc);

	a->cntlr_miss_count = 0;
	a->active_cntlr = false;
	a->multiple_cntlr = false;
	a->autocfg_interval = CONTROLLER_SELECT_PROBE_INT;


	a->is_sta_steer_start = 0;
	a->sta_steerlist_count = 0;
	timer_init(&a->sta_steer_req_timer, agent_steering_opp_timeout);

	return 0;
}

static int agent_ackq_timeout_cb(struct cmdu_ackq *q, struct cmdu_ackq_entry *e)
{
	struct agent *a = container_of(q, struct agent, cmdu_ack_q);
	struct cmdu_buff *cmdu = (struct cmdu_buff *) e->cookie;
	int ret;
	uint16_t msgid;

	trace("%s: ---> cmdu = %04x to "MACFMT" \n", __func__,
		cmdu_get_type(cmdu), MAC2STR(cmdu->origin));

	if (e->resend_cnt-- > 0) {
		ret = ieee1905_ubus_send_cmdu(a->ubus_ctx, a->i1905obj_id,
					      cmdu, &msgid, a->pvid);
		if (ret)
			err("%s fail to send cmdu\n", __func__);
		dbg("%s CMDU sent, msgid = %d\n", __func__, msgid);
		return CMDU_ACKQ_TMO_REARM;
	}

	return CMDU_ACKQ_TMO_DELETE;
}

static void agent_ackq_delete_cb(struct cmdu_ackq *q, struct cmdu_ackq_entry *e)
{
	struct cmdu_buff *cmdu = (struct cmdu_buff *) e->cookie;

	trace("%s: ---> cmdu = %04x to "MACFMT" \n", __func__,
		cmdu_get_type(cmdu), MAC2STR(cmdu->origin));

	cmdu_free(cmdu);
}

void run_agent(void *opts)
{
	struct log_options *lopts = (struct log_options *)opts;
	struct agent *w;
	struct ubus_context *ctx;
	sigset_t base_mask;
	bool loglvl_set = true;

	sigemptyset(&base_mask);
	sigaddset(&base_mask, SIGHUP);

	sigprocmask(SIG_SETMASK, &base_mask, NULL);
	set_sighandler(SIGPIPE, SIG_IGN);

	w = calloc(1, sizeof(*w));
	if (!w)
		return;
	this_agent = w;

	fprintf(stderr, "Starting Multi_AP Agent ... (&agent = %p)\n", w);

	memcpy(&w->lopts, lopts, sizeof(*lopts));
	if (w->lopts.level == LOGLEVEL_UNSET) {
		/* not set explicitly with -v, set default for start */
		loglvl_set = false;
		w->lopts.level = DEFAULT_LOGLEVEL;
	}
	restart_logging(&w->lopts);
	fprintf(stderr, "%s: log.level set to %d\n",
			__func__, w->lopts.level);

	agent_init_defaults(w);

	cmdu_ackq_init(&w->cmdu_ack_q);
	w->cmdu_ack_q.timeout_cb = agent_ackq_timeout_cb;
	w->cmdu_ack_q.delete_cb = agent_ackq_delete_cb;

	uloop_init();
	ctx = ubus_connect(NULL);
	if (!ctx) {
		err("%s: Failed to connect to ubus\n", __func__);
		free(w);
		stop_logging();
		return;
	}

	w->ubus_ctx = ctx;
	w->evh.cb = agent_event_handler;

	agent_init_ieee1905id(w);

#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	timer_init(&w->dpp_legacy_timeout, dpp_legacy_timeout_cb);
	INIT_LIST_HEAD(&w->conf_resplist);
	if (dpp_init(&w->dpp, 0, NULL)) {
		err("%s: Failed to init dpp context\n", __func__);
		goto out_ubus;
	}

	allmac_init_table(&w->peer_table);

	dpp_set_ctx_private_data(w->dpp, w);
	dpp_register_cb(w->dpp, dpp_frame_handler);
	w->own_peer = dpp_create_responder_instance(w->dpp);
	if (!w->own_peer) {
		err("%s: Failed to create enrollee instance\n", __func__);
		goto out_ubus;
	}

	strcpy(w->own_peer->e_req.tech, "map");
	strcpy(w->own_peer->e_req.dpp_name, "iopsys_map_dpp");
	w->own_peer->e_req.name_len = strlen(w->own_peer->e_req.dpp_name);
	w->own_peer->e_req.netrole = DPP_NETROLE_MAPAGENT;

	dpp_print_bootstrap_info(w->own_peer->own_bi);

	int ret;
	uint8_t bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

	ret = dpp_build_presence_announcement(w->dpp, bcast, &w->pa_frame, &w->pa_framelen);
	if (ret) {
		err("%s: Failed to build presence announcement frame!\n", __func__);
		goto out_ubus;
	}

#ifdef DEBUG
	dump(w->pa_frame, w->pa_framelen, "Presence Annoucement");
#endif
	timer_init(&w->dpp_periodic_pa, dpp_send_chirp);
	timer_set(&w->dpp_periodic_pa, 10 * 1000);

#ifdef ZEROTOUCH_DPP
	if (!is_local_cntlr_running() && !libztdpp_open(&w->zt))
		zt_init(&w->zt, w->own_peer->own_bi->key, w->own_peer->own_bi->uri);
#endif
#endif
#endif

	agent_config_init(w, &w->cfg);
	if (!loglvl_set && w->lopts.level != w->cfg.debug_level) {
		/* verbosity wasn't set with -v option, use config */
		w->lopts.level = w->cfg.debug_level;
		restart_logging(&w->lopts);
		info("%s: log.level changed to %d\n", __func__, w->lopts.level);
	}

#ifdef VENDOR_EXTENSION
	agent_load_extensions(w);
#endif

	w->ts.nl_main_sk = nl_init_main_sock(w);
	if (!w->ts.nl_main_sk)
		goto out_ubus;

	w->ts.nl_sk.cb = nl_event_uloop_cb;
	w->ts.nl_sk.fd = nl_socket_get_fd(w->ts.nl_main_sk);
	uloop_fd_add(&w->ts.nl_sk, ULOOP_READ);
	if (w->pvid)
		agent_apply_traffic_separation(w);
#if (EASYMESH_VERSION > 2)
#if 0
	if (w->cfg.persist_uri) {
		/* attempt to read ec private key */
		dpp_get_keys(w);

		/* if no found, generate one and write to the file */
		if (!w->dpp_ctx.ec_key)
			dpp_gen_keypair(w);
	}
#endif
#endif

	ubus_add_uloop(ctx);

	/* used by some ts script handling */
	setenv("AL_BRIDGE", w->cfg.al_bridge, 1);

	agent_init_wsc_attributes(w);

	//memcpy(w->cntlr_almac, w->cfg.cntlr_almac, 6);

	//agent_config_dump(&w->cfg);

	get_registered_steer_rules();	/* TODO: return rule list and improve */

	//agent_switch_according_to_pref(w); /*switch to the channel according to the prefrence*/

	ubus_register_event_handler(ctx, &w->evh, "wifi.*");
	ubus_register_event_handler(ctx, &w->evh, "wps_credentials");
	ubus_register_event_handler(ctx, &w->evh, "ubus.object.*");

	timer_init(&w->autocfg_dispatcher, agent_dispatch_autoconfig);
	timer_init(&w->reload_scheduler, agent_reload_cb);
	timer_init(&w->cntlr_scheduler, agent_enable_local_cntlr);
	timer_init(&w->onboarding_scheduler, agent_trigger_bsta_sync);
	timer_init(&w->bh_lost_timer, dynbh_bh_lost_cb);
	timer_init(&w->bh_reconf_timer, dynbh_bh_reconf_cb);
#ifdef AGENT_ISLAND_PREVENTION
	timer_init(&w->sta_disconnect_timer, agent_sta_disconnnect_cb);
	timer_init(&w->fh_disable_timer, agent_fh_disable_cb);
#endif /* AGENT_ISLAND_PREVENTION */
	timer_init(&w->disable_unconnected_bstas_scheduler, dynbh_disable_unconnected_bsta_cb);
	timer_init(&w->dynbh_scheduler, dynbh_upgrade_backhaul_cb);
	timer_init(&w->init_ifaces_scheduler, agent_init_ifaces_cb);
	timer_init(&w->enable_fhs_scheduler, agent_enable_fhs_cb);
	timer_init(&w->radio_stats_scheduler, agent_radio_stats_cb);
	timer_init(&w->boot_scan_scheduler, agent_boot_scan_cb);
	timer_init(&w->agent_periodic_run, agent_periodic_run);

	timer_init(&w->cfg.metric_report_timer, agent_metric_report_timer_cb);

	agent_init_wifi_radios(w);
	agent_init_ifaces_assoc_mode(w);
	/* switch to the channel according to the prefrence */
	//agent_switch_according_to_pref(w);

	agent_get_system_info(w);

#if (EASYMESH_VERSION >= 6)
	autoconfig_ap_mld_hash_from_file(w->ap_mld_cfg_sha256);
	autoconfig_bsta_mld_hash_from_file(w->bsta_mld_cfg_sha256);
#endif

	/* w->cfg.enabled */
	agent_publish_object(w, MAPAGENT_OBJECT);
	agent_publish_dbg_object(w, MAPAGENT_DBG_OBJECT);

	//run_agent(w);

	timer_set(&w->autocfg_dispatcher, 0 * 1000);
	timer_set(&w->radio_stats_scheduler, RADIO_STATS_TIMER);
	w->boot_scan_tries = 0;
	timer_set(&w->boot_scan_scheduler, BOOT_UP_SCAN_TIME * 1000);
	timer_set(&w->agent_periodic_run, 1 * 1000);

	agent_subscribe_for_cmdus(w);
	nl_wds_start_all(w);

	timestamp_update(&w->start_t);
#ifdef DYNBH
	if (w->cfg.dynbh)
		dynbh_init(w);
#endif
	uloop_run();

/* out_and_exit: */
	if (w->subscribed)
		map_unsubscribe(w->ubus_ctx, w->subscriber);
	agent_free_wifi_assoc_frames(w);
	agent_remove_object(w, &w->obj);
	agent_remove_object(w, &w->obj_dbg);
	agent_free_cntlr_sync(w);
	agent_free_pending_async(w);
	cmdu_ackq_free(&w->cmdu_ack_q);
	agent_free_radios(w);
	clear_aplist(w);
	clear_unassocstalist(w);
	clear_bklist(w);
	clear_wdslist(w);
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	allmac_clean_table(&w->peer_table);
	dpp_free_peer(w->dpp, w->own_peer);
	timer_del(&w->dpp_periodic_pa);
	free(w->pa_frame);
	free(w->dpp);
#endif
	qos_rule_free_all(&w->qos_rules);
#endif
#if (EASYMESH_VERSION >= 6)
	if (w->bstamld)
		agent_clear_bsta_mld(w);
	apmld_free_all(w);
#endif
	ubus_unregister_event_handler(ctx, &w->evh);
#ifdef VENDOR_EXTENSION
	agent_unload_extensions(w);
#endif
#ifdef DYNBH
	if (w->cfg.dynbh)
		dynbh_free(w);
#endif
	uloop_done();
	nl_free_main_sock(w->ts.nl_main_sk);
	agent_free_nodes(w);
	agent_config_clean(&w->cfg);

out_ubus:
	ubus_free(ctx);
	free(w);
	stop_logging();
}

struct wsc_data *agent_free_wsc_data(struct wsc_data *wsc)
{
	if (wsc->m1_frame)
		free(wsc->m1_frame);
	if (wsc->key) {
		free(wsc->key->pub);
		free(wsc->key->key);
		free(wsc->key);
	}

	return NULL;
}

static void agent_free_nodes(struct agent *a)
{
	struct node *n = NULL, *tmp;

	list_for_each_entry_safe(n, tmp, &a->nodelist, list) {
		list_del(&n->list);
		free(n);
	}
}

static void agent_free_wifi_assoc_frames(struct agent *a)
{
	struct wifi_assoc_frame *n = NULL, *tmp;

	list_for_each_entry_safe(n, tmp, &a->framelist, list) {
		list_del(&n->list);
		free(n->frame);
		free(n);
	}
}

static void agent_reset_radio(struct wifi_radio_element *re)
{
	scan_free_scanlist(re);
	clear_aplist_in_radio(re);
	agent_free_wsc_data(&re->autconfig);
	memset(&re->autconfig, 0, sizeof(re->autconfig));
}

void agent_free_radios(struct agent *a)
{
	struct wifi_radio_element *re = NULL, *tmp;

	trace("%s: a->num_radios to free = %d\n", __func__, a->num_radios);
	list_for_each_entry_safe(re, tmp, &a->radiolist, list) {
		agent_reset_radio(re);
		timer_del(&re->preference_report_timer);
		timer_del(&re->unassoc_sta_meas_timer);
		timer_del(&re->available_scan_timer);
		timer_del(&re->bk.steer_timeout);
		list_del(&re->list);
		free(re);
	}
	a->num_radios = 0;
}

void agent_free_cntlr_sync(struct agent *a)
{
	if (a->sync_config_req)
		free(a->sync_config_req);

	if (a->privkey) {
		struct wsc_key *k = (struct wsc_key *) a->privkey;

		free(k->pub);
		free(k->key);
		free(k);
	}
}

static void agent_free_pending_async(struct agent *a)
{
	struct agent_async_request *async_req, *tmp;

	list_for_each_entry_safe(async_req, tmp, &a->pending_asynclist, list) {
		dbg("%s: freeing: %s\n", __func__, async_req->name);

		ubus_abort_request(async_req->ubus_req.ctx, &async_req->ubus_req);
		timer_del(&async_req->timeout);
		list_del(&async_req->list);
		free(async_req);
	}
}


/* following implements 'map.agent' ubus methods */
int agent_assoc_control_sta(uint8_t *bssid, uint8_t mode, int tmo,
		int num_sta, uint8_t stalist[][6])
{
	trace("%s: --->\n", __func__);

	struct agent *a = this_agent;
	struct netif_ap *ap;
	int ret = 0;

	if (!a) {
		dbg("this wifiagent is NULL\n");
		return -1;
	}

	if (num_sta == 0 || !stalist)
		return -1;

	ap = agent_get_ap(a, bssid);

	if (!ap || !ap->cfg)
		return -1;

	/* Start / stop validity timer or update config */
	if (mode == ASSOC_CTRL_UNBLOCK)
		ret = assoc_ctrl_unblock_sta(a, ap, num_sta, stalist);
	else
		ret = assoc_ctrl_block_sta(a, ap, tmo, mode,
					num_sta, stalist);

	return ret;
}

int wifiagent_steer_sta(struct ubus_context *ctx,
			char *ifname,
			unsigned char *sta,
			int prefcnt, struct pref_neighbor *pref, int optime)
{
	struct agent *a = this_agent;
	struct sta *s;
	struct nbr *wifi_nbr_list = NULL;
	int i;
	int ret = 0;

	UNUSED(ctx);

	if (!a) {
		dbg("this wifiagent is NULL\n");
		return -1;
	}

	s = agent_get_sta(a, sta);
	if (!s) {
		dbg("STA not found!\n");
		return -1;
	}

	if (optime) {
		s->steer_policy |= STA_STEER_OPPORTUNITY;
		if (!timer_pending(&s->sta_steer_timer)) {
			rebuild_cntlr_preflist(a, s, prefcnt, pref);
			/* (Re)start steer opportunity timer.
			 * In the opportunity time window constantly check
			 * every second for this sta's steering opportinity.
			 */
			s->steer_opportunity_tmo = optime * 1000;
			clock_gettime(CLOCK_MONOTONIC, &s->steer_opportunity);
			timer_set(&s->sta_steer_timer, 1000);
			return 0;
		}
	}

	/* steer mandate */
	s->steer_policy &= ~STA_STEER_OPPORTUNITY;
	if (/* !(s->caps & STA_CAP_11V_BSS_TRANS) || */   /* TODO */
			!(s->steer_policy & STEER_BTM)) {
		return -1;
	}

	/* Translate internal 'pref_neighbor' to wifi's 'nbr' struct */
	wifi_nbr_list = calloc(prefcnt, sizeof(struct nbr));
	if (WARN_ON(!wifi_nbr_list))
		return -ENOMEM;

	for (i = 0; i < prefcnt; i++) {
		agent_pref_neighbor_to_nbr(&pref[i], &wifi_nbr_list[i]);
	}

	ret = agent_req_btm_steer(a, ifname, s->macaddr, prefcnt, wifi_nbr_list, 0);

	if (ret)
		dbg("Failed to send BTM request to " MACFMT "\n",
		    MAC2STR(s->macaddr));

	free(wifi_nbr_list);

	return ret;
}

int wifiagent_get_status(struct ubus_context *ctx,
		struct ubus_request_data *req)
{
	struct agent *agent = this_agent;
	struct wifi_radio_element *re = NULL;
	struct blob_buf bb;
	void *a, *b, *c, *d, *t;

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

	a = blobmsg_open_array(&bb, "radios");
	list_for_each_entry(re, &agent->radiolist, list) {
		struct netif_ap *ap = NULL;

		t = blobmsg_open_table(&bb, "");
		blobmsg_add_string(&bb, "name", re->name);
		blobmsg_add_macaddr(&bb, "macaddr", re->macaddr);
		blobmsg_add_u32(&bb, "channel", re->current_channel);
		blobmsg_add_u32(&bb, "bandwidth", re->current_bandwidth);
		blobmsg_add_u32(&bb, "opclass", re->current_opclass);
		blobmsg_add_u32(&bb, "anpi", re->anpi);
		b = blobmsg_open_table(&bb, "autoconfig");
		blobmsg_add_string(&bb, "state", (re->state ? "ACTIVE":"HEARTBEAT"));
		c = blobmsg_open_table(&bb, "message_identifier");
		blobmsg_add_u32(&bb, "search", re->mid);
		blobmsg_add_u32(&bb, "wsc", re->wsc_mid);
		blobmsg_add_u32(&bb, "renew", re->renew_mid);
		blobmsg_close_table(&bb, c);
		blobmsg_close_table(&bb, b);

		d = blobmsg_open_array(&bb, "fronthaul");
		list_for_each_entry(ap, &re->aplist, list) {
			struct neighbor *n = NULL;
			struct sta *s = NULL;
			void *tt;

			if (!ap->cfg)
				continue;

			tt = blobmsg_open_table(&bb, "");
			blobmsg_add_string(&bb, "name", ap->ifname);
	#if (EASYMESH_VERSION >= 6)
			blobmsg_add_u8(&bb, "is_affiliated_ap", ap->is_affiliated_ap);
			if (ap->is_affiliated_ap) {
				struct netif_mld *apmld;

				apmld = agent_get_apmld(agent, ap->mld_macaddr);
				if (apmld) {
					blobmsg_add_string(&bb, "mld_ifname", apmld->ifname);
					blobmsg_add_u32(&bb, "mlo_link_id", ap->mlo_link_id);
				}
			}
	#endif
			if (ap->band == BAND_2)
				blobmsg_add_string(&bb, "band", "2.4GHz");
			else if (ap->band == BAND_5)
				blobmsg_add_string(&bb, "band", "5GHz");
			else if (ap->band == BAND_6)
				blobmsg_add_string(&bb, "band", "6GHz");
			else
				blobmsg_add_string(&bb, "band", "Unknown");

			blobmsg_add_u8(&bb, "enabled", ap->enabled);
			blobmsg_add_macaddr(&bb, "bssid", ap->bssid);
			blobmsg_add_string(&bb, "ssid", ap->ssid);
			blobmsg_add_u32(&bb, "channel", ap->channel);
			blobmsg_add_u32(&bb, "load", ap->bssload);
			blobmsg_add_u32(&bb, "num_sta", ap->num_sta);

			b = blobmsg_open_array(&bb, "neighbor");
			list_for_each_entry(n, &ap->nbrlist, list) {
				void *ttt;

				ttt = blobmsg_open_table(&bb, "");
				blobmsg_add_macaddr(&bb, "bssid", n->nbr.bssid);
				blobmsg_add_u32(&bb, "channel", n->nbr.channel);
				blobmsg_close_table(&bb, ttt);
			}
			blobmsg_close_array(&bb, b);

			b = blobmsg_open_array(&bb, "stations");
			list_for_each_entry(s, &ap->stalist, list) {
				void *aa, *ttt, *tttt;
				struct pref_neighbor *pn = NULL;

				ttt = blobmsg_open_table(&bb, "");
				blobmsg_add_macaddr(&bb, "macaddr", s->macaddr);
	#if (EASYMESH_VERSION >= 6)
				blobmsg_add_u8(&bb, "is_affiliated_sta", s->is_affiliated_sta);
				if (s->is_affiliated_sta) {
					struct sta_mld *smld;

					smld = agent_get_stamld(agent, s->mld_macaddr);
					if (smld) {
						blobmsg_add_macaddr(&bb, "mld_macaddr", smld->macaddr);
						blobmsg_add_macaddr(&bb, "mld_bssid", smld->bssid);
						blobmsg_add_u32(&bb, "mlo_linkid", s->mlo_link_id);
					}
				}
	#endif
				/* blobmsg_add_u32(&bb, "conn_time", s->connected_ms / 1000); */
				blobmsg_add_u32(&bb, "rssi", s->rssi_avg[0]);
				blobmsg_add_u32(&bb, "bcnreport_capable",
						s->supports_bcnreport);
				blobmsg_add_u32(&bb, "btmreq_steers",
						s->steer_btm_cnt);
				aa = blobmsg_open_array(&bb, "neighbor");
				list_for_each_entry(pn, &s->pref_nbrlist, list) {
					char pn_bssidstr[18] = {0};

					hwaddr_ntoa(pn->bssid, pn_bssidstr);
					tttt = blobmsg_open_table(&bb, "");
					blobmsg_add_string(&bb, "bssid", pn_bssidstr);
					blobmsg_add_u32(&bb, "rssi", pn->rssi);
					blobmsg_close_table(&bb, tttt);
				}
				blobmsg_close_array(&bb, aa);
				blobmsg_close_table(&bb, ttt);
			}
			blobmsg_close_array(&bb, b);
			blobmsg_close_table(&bb, tt);
		}
		blobmsg_close_array(&bb, d);
		d = blobmsg_open_table(&bb, "backhaul");
		if (re->has_bsta && re->bk.cfg) {
			struct netif_bk *bk = &re->bk;
			void *ttt;

			ttt = blobmsg_open_table(&bb, "");
			blobmsg_add_string(&bb, "name", bk->ifname);
			blobmsg_add_u8(&bb, "enabled", bk->enabled);
			blobmsg_add_u8(&bb, "connected", bk->connected);
			blobmsg_add_macaddr(&bb, "macaddr", bk->macaddr);
			blobmsg_add_macaddr(&bb, "bssid", bk->bssid);
			blobmsg_add_string(&bb, "ssid", bk->ssid);
			blobmsg_add_u32(&bb, "channel", bk->channel);
#if (EASYMESH_VERSION >= 6)
			blobmsg_add_u8(&bb, "is_affiliated_bsta", bk->is_affiliated_sta);
			if (bk->is_affiliated_sta) {
				blobmsg_add_string(&bb, "mld_ifname", bk->mld_ifname);
				blobmsg_add_u32(&bb, "mlo_link_id", bk->mlo_link_id);
				blobmsg_add_macaddr(&bb, "mld_macaddr", bk->mld_macaddr);
			}
#endif
			b = blobmsg_open_table(&bb, "cfg");
			blobmsg_add_string(&bb, "name", bk->cfg->name);
			blobmsg_add_u8(&bb, "enabled", bk->cfg->enabled);
			blobmsg_add_u32(&bb, "band", bk->cfg->band);
			blobmsg_add_string(&bb, "device", bk->cfg->device);
			blobmsg_add_string(&bb, "ssid", bk->cfg->ssid);
			blobmsg_add_string(&bb, "key", bk->cfg->key);
			blobmsg_add_string(&bb, "encryption", bk->cfg->encryption);
			blobmsg_add_u8(&bb, "onboarded", bk->cfg->onboarded);
			blobmsg_add_macaddr(&bb, "bssid", bk->cfg->bssid);
			blobmsg_add_u16(&bb, "priority", (uint16_t) bk->cfg->priority);
			blobmsg_close_table(&bb, b);
			blobmsg_close_table(&bb, ttt);
		}
		blobmsg_close_table(&bb, d);
		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);

#if (EASYMESH_VERSION >= 6)
	struct netif_mld *mld = NULL;

	a = blobmsg_open_array(&bb, "ap_mld");
	list_for_each_entry(mld, &agent->apmldlist, list) {
		void *aa, *aaa;
		int i;

		t = blobmsg_open_table(&bb, "");

		blobmsg_add_string(&bb, "ifname", mld->ifname);
		blobmsg_add_macaddr(&bb, "macaddr", mld->macaddr);
		blobmsg_add_string(&bb, "ssid", mld->ssid);

		aa = blobmsg_open_array(&bb, "affiliated_ap");
		for (i = 0; i < mld->num_affiliated_ap; i++) {
			struct netif_ap *ap;
			void *tt;

			ap = agent_get_ap(agent, mld->affiliated_ap[i]);
			if (!ap)
				continue;

			tt = blobmsg_open_table(&bb, "");
			blobmsg_add_string(&bb, "ifname", ap->ifname);
			blobmsg_close_table(&bb, tt);
		}
		blobmsg_close_array(&bb, aa);

		struct sta_mld *smld = NULL;

		aaa = blobmsg_open_array(&bb, "mld_stations");
		list_for_each_entry(smld, &mld->stamldlist, list) {
			void *ttt, *tttt;

			ttt = blobmsg_open_table(&bb, "");
			blobmsg_add_macaddr(&bb, "mld_macaddr", smld->macaddr);
			blobmsg_add_macaddr(&bb, "mld_bssid", smld->bssid);
			blobmsg_add_u32(&bb, "num_affiliated_sta", smld->num_affiliated_sta);
			tttt = blobmsg_open_array(&bb, "stations");
			for (i = 0; i < smld->num_affiliated_sta; i++) {
				void *ttttt;
				struct sta *s;

				ttttt = blobmsg_open_table(&bb, "");
				s = agent_get_sta(agent, smld->affiliated_sta[i]);
				if (!s)
					continue;

				blobmsg_add_macaddr(&bb, "macaddr", s->macaddr);
				blobmsg_add_macaddr(&bb, "bssid", s->bssid);
				blobmsg_add_u32(&bb, "linkid", s->mlo_link_id);
				blobmsg_close_table(&bb, ttttt);
			}
			blobmsg_close_array(&bb, tttt);
			blobmsg_close_table(&bb, ttt);
		}
		blobmsg_close_array(&bb, aaa);

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

	a = blobmsg_open_table(&bb, "bsta_mld");
	if (agent->bstamld) {
		void *aa;
		int i;

		t = blobmsg_open_table(&bb, "");

		blobmsg_add_string(&bb, "ifname", agent->bstamld->bk.ifname);
		blobmsg_add_macaddr(&bb, "macaddr", agent->bstamld->bk.macaddr);
		blobmsg_add_macaddr(&bb, "bssid", agent->bstamld->bk.bssid);
		blobmsg_add_u32(&bb, "num_affiliated_bsta", agent->bstamld->num_affiliated_bsta);

		if (agent->bstamld->setup_link) {
			blobmsg_add_string(&bb, "setup_link_ifname", agent->bstamld->setup_link->ifname);
			blobmsg_add_macaddr(&bb, "setup_link_macaddr", agent->bstamld->setup_link->macaddr);
			blobmsg_add_u8(&bb, "setup_link_connected", agent->bstamld->setup_link->connected);
		} else {
			blobmsg_add_string(&bb, "setup_link_ifname", "");
		}

		aa = blobmsg_open_array(&bb, "affiliated_sta");
		for (i = 0; i < agent->bstamld->num_affiliated_bsta; i++) {
			struct netif_bk *bk;
			void *tt;

			bk = agent_get_bsta(agent, agent->bstamld->affiliated_bsta[i]);
			if (!bk)
				continue;

			tt = blobmsg_open_table(&bb, "");
			blobmsg_add_string(&bb, "ifname", bk->ifname);
			blobmsg_add_macaddr(&bb, "macaddr", bk->macaddr);
			blobmsg_add_macaddr(&bb, "bssid", bk->bssid);
			blobmsg_add_u8(&bb, "is_setup_link", (bk == agent->bstamld->setup_link));
			blobmsg_close_table(&bb, tt);
		}
		blobmsg_close_array(&bb, aa);

		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, a);
#endif
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int wifiagent_get_nodes(struct ubus_context *ctx,
		struct ubus_request_data *req)
{
	struct agent *agent = this_agent;
	struct node *n = NULL;
	struct blob_buf bb;
	void *a;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_u32(&bb, "num_nodes", agent->num_nodes);

	a = blobmsg_open_array(&bb, "nodes");
	list_for_each_entry(n, &agent->nodelist, list) {
		char aladdrstr[18] = {0};
		void *t;

		hwaddr_ntoa(n->alid, aladdrstr);
		t = blobmsg_open_table(&bb, "");

		blobmsg_add_string(&bb, "almac", aladdrstr);
		blobmsg_add_u16(&bb, "profile", n->map_profile);
		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 wifiagent_get_info(struct ubus_context *ctx,
		struct ubus_request_data *req)
{
	char almac_str[18] = {};
	struct blob_buf bb;
	struct agent *a = this_agent;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_u32(&bb, "active_cntlr", a->active_cntlr);
	hwaddr_ntoa(a->cntlr_almac, almac_str);
	blobmsg_add_string(&bb, "cntlr_almac", almac_str);
	blobmsg_add_u32(&bb, "local_cntlr", is_local_cntlr_running());
	blobmsg_add_u32(&bb, "multiple_cntlr_found", a->multiple_cntlr);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

int wifiagent_get_bk_info(struct ubus_context *ctx,
		struct ubus_request_data *req)
{
	struct blob_buf bkfile = { 0 }, bb = { 0 };
	struct netif_bk *bk = NULL;
	struct agent *a = this_agent;
	struct blob_attr *tb[5];
	static const struct blobmsg_policy bk_attr[5] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "backhaul_device_id", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "backhaul_macaddr", .type = BLOBMSG_TYPE_STRING },
	};
	int ret;
	void *t, *tt;
	uint32_t wifi_bk_uptime = a->wifi_backhaul_time;
	struct wifi_radio_element *re = NULL;

	blob_buf_init(&bkfile, 0);

	if (!blobmsg_add_json_from_file(&bkfile, MAP_UPLINK_PATH)) {
		dbg("%s: Failed to parse %s\n", __func__, MAP_UPLINK_PATH);
		goto out;
	}

	ret = blobmsg_parse(bk_attr, 5, tb, blob_data(bkfile.head), blob_len(bkfile.head));
	if (ret)
		goto out;

	blob_buf_init(&bb, 0);
	t = blobmsg_open_table(&bb, "active_backhaul");
	if (tb[0])
		blobmsg_add_blob(&bb, tb[0]);
	if (tb[1])
		blobmsg_add_blob(&bb, tb[1]);
	if (tb[2])
		blobmsg_add_blob(&bb, tb[2]);
	if (tb[3])
		blobmsg_add_blob(&bb, tb[3]);
	if (tb[4])
		blobmsg_add_blob(&bb, tb[4]);
	blobmsg_close_table(&bb, t);

	bk = dynbh_get_best_connected_bsta(a);
	if (bk)
		wifi_bk_uptime += timestamp_elapsed_sec(&bk->connect_t);

	blobmsg_add_u32(&bb, "agent_uptime", timestamp_elapsed_sec(&a->start_t));
	blobmsg_add_u32(&bb, "wifi_backhaul_uptime", wifi_bk_uptime);
	tt = blobmsg_open_array(&bb, "backhauls");

	list_for_each_entry(re, &a->radiolist, list) {
		void *ttt;
		uint32_t tot_uptime;

		if (!re->has_bsta)
			continue;

		bk = &re->bk;
		tot_uptime = bk->total_connect_time;

		if (!bk->cfg)
			continue;

		ttt = blobmsg_open_table(&bb, "");
		blobmsg_add_string(&bb, "name", bk->ifname);
		blobmsg_add_u8(&bb, "connected", bk->connected);
		blobmsg_add_macaddr(&bb, "macaddr", bk->macaddr);
		blobmsg_add_macaddr(&bb, "bssid", bk->bssid);
		if (bk->connected)
			tot_uptime += timestamp_elapsed_sec(&bk->connect_t);
		blobmsg_add_u32(&bb, "total_uptime", tot_uptime);
		blobmsg_close_table(&bb, ttt);
	}

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

	return UBUS_STATUS_OK;
out:
	blob_buf_free(&bkfile);
	return UBUS_STATUS_UNKNOWN_ERROR;
}

int wifiagent_initiate_wpspbc(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req)
{
	trace("%s: --->\n", __func__);
	struct agent *a = container_of(obj, struct agent, obj);
	struct wifi_radio_element *re = NULL;
	int triggered = 0;

	bool active_backhaul = agent_has_active_backhaul(a);
	bool backhaul_onboarded = agent_is_bsta_onboarded(a);

	if (!active_backhaul && !backhaul_onboarded) {
		list_for_each_entry(re, &a->radiolist, list) {
			struct netif_bk *bk;

			if (!re->has_bsta)
				continue;

			bk = &re->bk;
			if (bk->cfg && bk->cfg->enabled) {
				trace("%s: Triggering WPS on bSTA: %s\n",
						__func__, bk->ifname);
				wifi_initiate_wpspbc(bk->ifname, true);
				bk->wps_active = true;
				triggered++;
			}
		}
	}

	re = NULL;
	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			if (ap->cfg && ap->cfg->enabled && ap->cfg->multi_ap == 2) {
				trace("%s: Triggering WPS on AP: %s\n",
						__func__, ap->ifname);
				wifi_initiate_wpspbc(ap->ifname, false);
				triggered++;
			}
		}
	}

	if (triggered == 0) {
		trace("%s: No valid interfaces to trigger WPS\n", __func__);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

int agent_switch_according_to_pref(struct agent *a)
{
	uint32_t channel = 0;
	uint32_t opclass = 0;
	int ret = 0;
	struct wifi_radio_element *re;

	trace("agent: %s: --->\n", __func__);

	list_for_each_entry(re, &a->radiolist, list) {
		ret = agent_get_highest_preference(re, re->current_opclass, &channel,
			&opclass);

		/* The operating class channel preference has been set
		 * now we want to switch the channel to the max preference */
		trace("%s: opclass is %d channel is %d--->\n",
				__func__, opclass, channel);

		if ((re->current_opclass == opclass) && (re->current_channel == channel))
			continue;

		if (channel != 0 && opclass != 0) {
			ret = agent_channel_switch(a, re->macaddr, channel, opclass);
			if (ret == -1) {
				/* Here we need to set the default preference for all the
				 * channels in that operating class also set the response
				 * code as rejected*/
				agent_set_channel_preference_to_default(re);
			}
		}
	}
	return 0;
}

void agent_set_post_scan_action_pref(struct agent *a, struct wifi_radio_element *re, bool opclass_preferences)
{
	re->post_scan_action.opclass_preferences = opclass_preferences;
}

int agent_reset_stack(struct agent *a)
{
	int ret;

	if (agent_is_ts_enabled(a))
		ts_configure_isolation(a, false);

	ret = agent_exec_platform_scripts("reset > /dev/null 2>&1 &");
	if (ret != 0) {
		trace("%s: reset command failed with code %d\n", __func__, ret);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

int wifiagent_ubus_reset_stack(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req)
{
	trace("%s: --->\n", __func__);
	struct agent *a = container_of(obj, struct agent, obj);

	return agent_reset_stack(a);
}
