/*
 * cntlr.c - Multi-AP controller
 *
 * Copyright (C) 2020-2022 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for source code license information.
 *
 */
#include "cntlr.h"


#include <1905_tlvs.h>
#include <cmdu.h>
#include <easymesh.h>
#include <errno.h>
#include <easy/easy.h>
#include <inttypes.h>
#include <json-c/json.h>
#include <libubox/blob.h>
#include <libubox/blobmsg_json.h>
#include <libubox/uloop.h>
#include <libubus.h>
#include <cmdu_ackq.h>
#include <map_module.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wifidefs.h>
#include "timer.h"
#include "timeutils.h"
#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
#include <dpp_api.h>
#endif
#endif

#include "acs.h"
#include "cntlr_cmdu.h"
#include "cntlr_map.h"
#include "cntlr_ubus.h"
#include "config.h"
#if (EASYMESH_VERSION >= 3)
#include "dpp.h"
#endif
#include "libubox/blobmsg.h"
#include "libubox/list.h"
#include "mactable.h"
#include "sta.h"
#include "stdbool.h"
#include "timer_impl.h"
#include "topology.h"
#include "utils/debug.h"
#include "utils/utils.h"
#include "wifi_dataelements.h"
#include "wifi_opclass.h"
#include "steer.h"
#include "son.h"
#include "scan.h"

#define map_plugin	"ieee1905.map"


extern bool waitext;

struct netif_iface *cntlr_find_iface(struct controller *c, uint8_t *macaddr)
{
	struct macaddr_entry *entry = NULL;

	entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_FBSS);
	if (WARN_ON(!entry))
		return NULL;

	return (struct netif_iface *)entry->data;
}

struct netif_iface *cntlr_find_iface_type(struct controller *c, uint8_t *macaddr,
					  uint32_t type)
{
	struct macaddr_entry *entry = NULL;

	entry = mactable_lookup(c->mac_table, macaddr, type);
	if (WARN_ON(!entry))
		return NULL;

	return (struct netif_iface *)entry->data;
}

struct netif_iface *cntlr_find_bss(struct controller *c, uint8_t *macaddr)
{
	return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_FBSS | MAC_ENTRY_BBSS);
}

struct netif_iface *cntlr_find_fbss(struct controller *c, uint8_t *macaddr)
{
	//return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_FBSS);	//FIXME

	struct netif_iface *iface;

	iface = cntlr_find_bss(c, macaddr);
	if (iface && iface->bss->is_fbss)
		return iface;

	return NULL;
}

struct netif_iface *cntlr_find_bbss(struct controller *c, uint8_t *macaddr)
{
	// return cntlr_find_iface_type(c, macaddr, MAC_ENTRY_BBSS);	//FIXME

	struct netif_iface *iface;

	iface = cntlr_find_bss(c, macaddr);
	if (iface && iface->bss->is_bbss)
		return iface;

	return NULL;
}

int cntlr_set_iface_type(struct controller *c, uint8_t *macaddr, uint32_t type)
{
	return mactable_set_entry_type(c->mac_table, macaddr, type);
}

int cntlr_get_iface_type(struct controller *c, uint8_t *macaddr, uint32_t *type)
{
	return mactable_get_entry_type(c->mac_table, macaddr, type);
}


struct netif_radio *cntlr_find_radio(struct controller *c, uint8_t *macaddr)
{
	struct macaddr_entry *entry = NULL;

	entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_RADIO);
	if (WARN_ON(!entry))
		return NULL;

	return (struct netif_radio *)entry->data;
}

struct node *cntlr_find_node(struct controller *c, uint8_t *macaddr)
{
	struct macaddr_entry *entry = NULL;

	entry = mactable_lookup(c->mac_table, macaddr, MAC_ENTRY_ALID);
	if (WARN_ON(!entry))
		return NULL;

	return (struct node *)entry->data;
}

/* finds radio struct from interface macaddr */
struct netif_radio *cntlr_find_radio_with_bssid(struct controller *c, uint8_t *bssid)
{
	struct node *n = NULL;
	struct netif_radio *r = NULL;
	struct netif_iface *p = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		list_for_each_entry(r, &n->radiolist, list) {
			list_for_each_entry(p, &r->iflist, list) {
				if (!memcmp(p->bss->bssid, bssid, 6))
					return r;
			}
		}
	}

	return NULL;
}

struct netif_radio *cntlr_find_radio_in_node_by_band(struct controller *c, struct node *n,
				       enum wifi_band band)
{
	struct netif_radio *r = NULL;

	list_for_each_entry(r, &n->radiolist, list) {
		if (r->radio_el->band == band)
			return r;
	}

	return NULL;
}

/* TODO: untested */
int cntlr_get_node_assoclist(struct controller *c, uint8_t *node_alid,
			     int *num_sta, uint8_t *sta_macaddrs)
{
	struct sta *s = NULL;
	struct node *n;
	int limit = *num_sta;
	int ret = 0;
	int i = 0;

	*num_sta = 0;

	n = cntlr_find_node(c, node_alid);
	if (!n)
		return -1;

	list_for_each_entry(s, &n->stalist, list) {
		if (i < limit)
			memcpy(&sta_macaddrs[i*6], s->macaddr, 6);
		else
			ret = -E2BIG;

		i++;
	}

	*num_sta = i;
	return ret;
}

struct bcnreq *cntlr_find_bcnreq(struct controller *c, uint8_t *sta, uint8_t *almacaddr)
{
	trace("%s: --->\n", __func__);

	struct bcnreq *br = NULL;

	list_for_each_entry(br, &c->bcnreqlist, list) {
		if (!memcmp(br->sta_mac, sta, 6) && !memcmp(br->agent_mac, almacaddr, 6))
			return br;
	}

	return NULL;
}


/* find node based on bssid */
struct node *cntlr_find_node_with_bssid(struct controller *c, uint8_t *bssid)
{
	struct node *n = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		struct netif_radio *r = NULL;

		list_for_each_entry(r, &n->radiolist, list) {
			struct netif_iface *p = NULL;

			list_for_each_entry(p, &r->iflist, list) {
				if (!memcmp(p->bss->bssid, bssid, 6))
					return n;
			}
		}
	}

	return NULL;
}

/* returns first found node with given STA MAC on its stalist */
struct node *cntlr_find_node_with_sta(struct controller *c, uint8_t *stamacaddr)
{
	struct node *n = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		struct sta *e = NULL;

		list_for_each_entry(e, &n->stalist, list) {
			if (!memcmp(e->macaddr, stamacaddr, 6))
				return n;
		}
	}

	return NULL;
}

/* returns topology json string */
char *cntlr_get_topology(struct controller *c)
{
	struct json_object *jobj = json_object_new_object();
	struct bh_topology_dev *n = NULL;
	uint8_t root_aladdr[6] = {0};
	const char *json_str = NULL;
	char macbuf_if[32] = {0};
	char macbuf[32] = {0};
	int num_links = 0;
	char *out;


	list_for_each_entry(n, &c->topology.dev_list, list) {
		if (n->bh.depth == 0)
			memcpy(root_aladdr, n->al_macaddr, 6);

		if (n->bh.parent)
			num_links++;
	}

	if (hwaddr_is_zero(root_aladdr)) {
		errno = EAGAIN;
		return NULL;
	}

	json_object_object_add(jobj, "num_nodes", json_object_new_int(c->topology.num_devs));
	json_object_object_add(jobj, "num_links", json_object_new_int(num_links));

	hwaddr_ntoa(root_aladdr, macbuf);
	json_object_object_add(jobj, "root", json_object_new_string(macbuf));

	/* nodes */
	struct json_object *jnodes = json_object_new_array();
	list_for_each_entry(n, &c->topology.dev_list, list) {
		hwaddr_ntoa(n->al_macaddr, macbuf);
		json_object_array_add(jnodes, json_object_new_string(macbuf));
	}
	json_object_object_add(jobj, "nodes", jnodes);

	/* links */
	struct json_object *jlinks = json_object_new_array();
	list_for_each_entry(n, &c->topology.dev_list, list) {
		if (n->bh.parent) {

			struct json_object *jlink = json_object_new_object();

			hwaddr_ntoa(n->bh.parent->al_macaddr, macbuf);
			hwaddr_ntoa(n->bh.parent_iface->macaddr, macbuf_if);	/* is bBSS for wifi link */
			json_object_object_add(jlink, "u", json_object_new_string(macbuf));
			json_object_object_add(jlink, "u_iface", json_object_new_string(macbuf_if));

			hwaddr_ntoa(n->al_macaddr, macbuf);
			hwaddr_ntoa(n->bh.own_iface->macaddr, macbuf_if);	/* is bSTA for wifi link */
			json_object_object_add(jlink, "v", json_object_new_string(macbuf));
			json_object_object_add(jlink, "v_iface", json_object_new_string(macbuf_if));

			if (!!(n->bh.own_iface->media_type & 0x0100)) {
				int max_thput;
				int w = 0;

				json_object_object_add(jlink, "f", json_object_new_int(0));
				max_thput = cntlr_estimate_max_throughput_for_node(c, n->al_macaddr);
				w = 100 - (float)max_thput * (float)(100) / (float)(10000);	//FIXME

				json_object_object_add(jlink, "w", json_object_new_int(w));
			} else {
				json_object_object_add(jlink, "w", json_object_new_int(0));
				json_object_object_add(jlink, "f", json_object_new_int(1));
			}

			json_object_array_add(jlinks, jlink);
		}
	}
	json_object_object_add(jobj, "links", jlinks);

	json_str = json_object_to_json_string_ext(jobj, JSON_C_TO_STRING_PRETTY);
	out = strdup(json_str);
	json_object_put(jobj);

	cntlr_info(LOG_SON, "%s", out);
	return out;
}

#if (EASYMESH_VERSION >= 6)
bool cntlr_radio_support_wifi7(struct wifi_wifi7_capabilities *caps)
{
	if (caps->str || caps->nstr || caps->emlsr || caps->emlmr)
		return true;

	return false;
}

bool cntlr_node_support_ap_wifi7(struct node *n)
{
	struct netif_radio *r = NULL;

	list_for_each_entry(r, &n->radiolist, list) {
		if (cntlr_radio_support_wifi7(&r->radio_el->wifi7caps_ap))
			return true;
	}
	return false;
}

#endif

static uint8_t *find_backhaul_upstream(
	struct controller *c,
	uint8_t *aladdr,
	uint8_t *local_macaddr,
	uint8_t *neighbor_macaddr,
	uint16_t mediatype)
{
	struct netif_iface *local_iface, *neighbor_iface;

	/* Try topology first - works for both WiFi and Ethernet backhaul */
	if (is_topology_valid(&c->topology)) {
		struct bh_topology_dev *topo_dev;

		topo_dev = topology_find_device(&c->topology, aladdr);
		if (topo_dev && topo_dev->bh.own_iface && topo_dev->bh.parent_iface) {
			if (hwaddr_equal(local_macaddr, topo_dev->bh.own_iface->macaddr) &&
			    hwaddr_equal(neighbor_macaddr, topo_dev->bh.parent_iface->macaddr)) {
				dbg("%s: Found backhaul link via topology (media: %s)\n",
				    __func__, i1905_media_type_to_str(mediatype));
				return neighbor_macaddr;
			}
		}
	}

	/* Fall back to bBSS checking if topology not valid (WiFi backhaul only) */
	local_iface = cntlr_find_iface(c, local_macaddr);
	if (local_iface && cntlr_find_bbss(c, local_macaddr)) {
		dbg("%s: Found WiFi backhaul link via bBSS (topology not valid)\n", __func__);
		return local_macaddr;
	}

	neighbor_iface = cntlr_find_iface(c, neighbor_macaddr);
	if (neighbor_iface && cntlr_find_bbss(c, neighbor_macaddr)) {
		dbg("%s: Found WiFi backhaul link via bBSS (topology not valid)\n", __func__);
		return neighbor_macaddr;
	}

	dbg("%s: No backhaul link found for " MACFMT " <-> " MACFMT "\n",
	    __func__, MAC2STR(local_macaddr), MAC2STR(neighbor_macaddr));

	return NULL;
}

int cntlr_update_txlink_metric_data(struct controller *c, struct tlv_tx_linkmetric *txl, int len)
{
	int i = 0;
	int size = 0;
	struct tx_link_info *txlinfo;
	struct node *n;

	n = cntlr_find_node(c, txl->aladdr);
	if (!n) {
		dbg("%s: Node " MACFMT " not found\n", __func__, MAC2STR(txl->aladdr));
		return -1;
	}

	/* For each tx link in the mesg */
	size = len - (sizeof(struct tlv_tx_linkmetric));
	size = size / (sizeof(struct tx_link_info));
	for (i = 0; i < size; i++) {
		uint8_t *upstream_mac;

		txlinfo = (struct tx_link_info *)&txl->link[i];

		upstream_mac = find_backhaul_upstream(c, txl->aladdr, txlinfo->local_macaddr,
						     txlinfo->neighbor_macaddr, BUF_GET_BE16(txlinfo->mediatype));
		if (!upstream_mac)
			continue;

		if (hwaddr_equal(txlinfo->local_macaddr, upstream_mac)) {
			dbg("%s: Ignoring upstream (bBSS) TX metrics\n", __func__);
			continue;
		}

		/* bSTA reports TX metrics (bSTA -> bBSS) */
		memcpy(n->tx_metrics.local_macaddr, txlinfo->local_macaddr, 6);
		memcpy(n->tx_metrics.neighbor_macaddr, txlinfo->neighbor_macaddr, 6);
		n->tx_metrics.mediatype = BUF_GET_BE16(txlinfo->mediatype);
		n->tx_metrics.has_bridge = txlinfo->has_bridge;
		n->tx_metrics.errors = BUF_GET_BE32(txlinfo->errors);
		n->tx_metrics.packets = BUF_GET_BE32(txlinfo->packets);
		n->tx_metrics.max_throughput = BUF_GET_BE16(txlinfo->max_throughput);
		n->tx_metrics.availability = BUF_GET_BE16(txlinfo->availability);
		n->tx_metrics.phyrate = BUF_GET_BE16(txlinfo->phyrate);

		memcpy(n->upstream_bssid, upstream_mac, 6);

		dbg("%s: bSTA " MACFMT " TX metrics to upstream " MACFMT ": max_throughput=%u Mbps\n",
		    __func__, MAC2STR(txl->aladdr), MAC2STR(upstream_mac), n->tx_metrics.max_throughput);
	}
	c->num_tx_links += size;
	return 0;
}

int cntlr_update_rxlink_metric_data(struct controller *c, struct tlv_rx_linkmetric *rxl, int len)
{
	int i = 0;
	int size = 0;
	struct rx_link_info *rxlinfo;
	struct node *n;

	n = cntlr_find_node(c, rxl->aladdr);
	if (!n) {
		dbg("%s: Node " MACFMT " not found\n", __func__, MAC2STR(rxl->aladdr));
		return -1;
	}

	/* For each rx link in the mesg */
	size = len - (sizeof(struct tlv_rx_linkmetric));
	size = size / (sizeof(struct rx_link_info));
	for (i = 0; i < size; i++) {
		uint8_t *upstream_mac;

		rxlinfo = (struct rx_link_info *)&rxl->link[i];

		upstream_mac = find_backhaul_upstream(c, rxl->aladdr, rxlinfo->local_macaddr,
						     rxlinfo->neighbor_macaddr, BUF_GET_BE16(rxlinfo->mediatype));
		if (!upstream_mac)
			continue;

		/* Only handle downstream (bSTA) reports */
		if (hwaddr_equal(rxlinfo->local_macaddr, upstream_mac)) {
			dbg("%s: Ignoring upstream (bBSS) RX metrics\n", __func__);
			continue;
		}

		memcpy(n->rx_metrics.local_macaddr, rxlinfo->local_macaddr, 6);
		memcpy(n->rx_metrics.neighbor_macaddr, rxlinfo->neighbor_macaddr, 6);
		n->rx_metrics.mediatype = BUF_GET_BE16(rxlinfo->mediatype);
		n->rx_metrics.errors = BUF_GET_BE32(rxlinfo->errors);
		n->rx_metrics.packets = BUF_GET_BE32(rxlinfo->packets);
		n->rx_metrics.rssi = rxlinfo->rssi;  /* Beacon RSSI from bBSS */

		memcpy(n->upstream_bssid, upstream_mac, 6);

		dbg("%s: bSTA " MACFMT " RX metrics from upstream " MACFMT ": rssi=%d dBm\n",
		    __func__, MAC2STR(rxl->aladdr), MAC2STR(upstream_mac), n->rx_metrics.rssi);
	}
	c->num_rx_links += size;
	return 0;
}


struct node *cntlr_add_node(struct controller *c, uint8_t *almacaddr)
{
	struct cmdu_buff *cmdu;
	char mac_str[18] = {0};
	struct node *n;
	int ret;

	if (hwaddr_is_zero(almacaddr)) {
		warn("%s: almacaddr = zero is not valid\n", __func__);
		return NULL;
	}

	if (!hwaddr_ntoa(almacaddr, mac_str))
		return NULL;

	n = cntlr_find_node(c, almacaddr);
	if (!n) {
		n = cntlr_alloc_node(c, almacaddr);
		if (!n) {
			err("%s: failed to allocate node "MACFMT"\n",
			    __func__, MAC2STR(almacaddr));
			return NULL;
		}

		timer_set(&c->son_timer, 0);
	} else {
		return n;
	}

	ret = cntlr_config_add_node(&c->cfg, mac_str);
	if (!ret) {
		dbg("|%s:%d| resync config\n", __func__, __LINE__);
		cntlr_resync_config(c, true);
	}

	/* update the timestamp of the node */
	if (n)
		timestamp_update(&n->last_tsp_seen);

#ifdef CONTROLLER_SYNC_DYNAMIC_CNTLR_CONFIG
	if (!hwaddr_equal(c->almacaddr, almacaddr))
		cntlr_sync_dyn_controller_config(c, almacaddr);
#endif

#if (EASYMESH_VERSION > 2)
	if (c->cfg.qos.enabled)
		cntlr_qos_sync_node(c, &c->cfg.qos, n->almacaddr, true);
#endif

	cmdu = cntlr_gen_bk_caps_query(c, n->almacaddr);
	if (!cmdu)
		return n;

	send_cmdu(c, cmdu);
	cmdu_free(cmdu);

	cmdu = cntlr_gen_ap_capability_query(c, n->almacaddr);
	if (!cmdu)
		return n;

	send_cmdu(c, cmdu);
	cmdu_free(cmdu);
	return n;
}

static void cntlr_log_nodes(struct controller *c)
{
	struct node *n = NULL;
	int i = 0;

	list_for_each_entry(n, &c->nodelist, list) {
		cntlr_dbg(LOG_MISC,
			  "  %d | agent = %p,  hwaddr = '"MACFMT"')\n",
			  i++, n, MAC2STR(n->almacaddr));
	}
}

void cntlr_send_max_wifi_bh_hops_policy(struct controller *c)
{
	struct node *node = NULL;

	if (c->cfg.max_node_bh_hops == 0)
		return;

	trace("%s: max_node_bh_hops %d\n",
		  __func__, c->cfg.max_node_bh_hops);

	list_for_each_entry(node, &c->nodelist, list) {
		enum bh_control {
			BLOCK = 0x00,
			UNBLOCK = 0x01
		} bh_control = UNBLOCK;
		struct bh_topology_dev *topo_dev = topology_find_device(&c->topology, node->almacaddr);
		struct netif_radio *radio = NULL;

		if (!topo_dev) {
			err("%s: device not found in bh topo model, logical error.\n", __func__);
			continue;
		}

		if (topo_dev->bh.depth == UNKNOWN_TREE_LEVEL) {
			warn("%s: Level in BH treee unknown for: " MACFMT "\n", __func__, MAC2STR(node->almacaddr));
			continue;
		}

		if (c->cfg.max_node_bh_hops <= topo_dev->bh.wifi_hops_from_root)
			bh_control = BLOCK;

		list_for_each_entry(radio, &node->radiolist, list) {
			struct netif_iface *iface = NULL;

			list_for_each_entry(iface, &radio->iflist, list) {
				if (iface->bss->is_bbss) {
					const uint8_t NO_VALIDITY_PERIOD = 0;
					uint8_t ALL_BSTAS[6] = {
						0xff, 0xff, 0xff, 0xff, 0xff, 0xff
					};
					uint16_t mid;

					trace("%s: sending BH assoc_mode %s, node al_mac: " MACFMT
					      ", wifi_hops_from_root: %d, bssid: " MACFMT"\n",
					      __func__,
					      bh_control ? "UNBLOCK" : "BLOCK",
					      MAC2STR(node->almacaddr),
					      topo_dev->bh.wifi_hops_from_root,
					      MAC2STR(iface->bss->bssid));

					cntlr_send_client_assoc_ctrl_request(
						c, node->almacaddr, iface->bss->bssid,
						bh_control, NO_VALIDITY_PERIOD, 1,
						ALL_BSTAS, &mid);
				}
			}
		}
	}
}

void cntlr_bcn_metrics_timer_cb(atimer_t *t)
{
	struct sta *s = container_of(t, struct sta, bcn_metrics_timer);
	struct controller *c = s->cntlr;


	cntlr_dbg(LOG_STA, "%s: No Beacon Report from STA " MACFMT" yet.\n",
		  __func__, MAC2STR(s->macaddr));

	if (!s->bcn_response_tmo) {
		s->bcn_response_tmo = true;
		timer_set(&s->bcn_metrics_timer, 30 * 1000);
		cntlr_dbg(LOG_STA, "%s: Wait for 30s more.\n", __func__);
	} else {
		cntlr_del_bcnreq(c, s->macaddr, s->agent_almacaddr);
		s->bcn_response_tmo = false;
	}
}

static void cntlr_freeze_sta(struct controller *c, struct sta *s)
{
	timer_del(&s->bcn_metrics_timer);
	timer_del(&s->btm_req_timer);
	timer_del(&s->ageout_timer);

	sta_free_assoc_frame(s);
	sta_free_bcn_metrics(s);
	cntlr_clean_bcnreqlist_sta(c, s);
}

static void cntlr_remove_sta(struct controller *c, struct sta *s)
{
	struct node *n = NULL;

	do {
		n = cntlr_find_node_with_sta(c, s->macaddr);
		if (n)
			node_del_sta(n, s);
	} while (n);

	cntlr_freeze_sta(c, s);
	cntlr_del_sta(c->sta_table, s);
}

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

	struct sta *s = container_of(t, struct sta, ageout_timer);
	struct controller *c = s->cntlr;
	time_t now, elapsed;

	if (!c)
		return;

	time(&now);
	elapsed = now - s->disassoc_time;

	if (c->cfg.stale_sta_timeout > ULOOP_MAX_TIMEOUT) {
		/* For long timeouts, check if actual timeout period has passed */
		if (elapsed < c->cfg.stale_sta_timeout) {
			/* Not yet time to remove, restart timer with remaining time */
			int remaining_timeout = c->cfg.stale_sta_timeout - elapsed;
			int timeout_ms;

			if (remaining_timeout > ULOOP_MAX_TIMEOUT) {
				timeout_ms = ULOOP_MAX_TIMEOUT * 1000;
			} else {
				timeout_ms = remaining_timeout * 1000;
			}
			timer_set(&s->ageout_timer, timeout_ms);
			return;
		}
	}

	cntlr_dbg(LOG_STA, "%s: Delete STA " MACFMT " after %ds of disassociation\n",
		  __func__, MAC2STR(s->macaddr), (int)elapsed);
	cntlr_remove_sta(c, s);
}

int node_add_sta(struct node *n, struct sta *s)
{
	char ev_data[256] = {0};

	if (WARN_ON(node_find_sta(n, s->macaddr))) {
		cntlr_dbg(LOG_STA,
			  "%s: Warn! STA " MACFMT " already in node " MACFMT"\n",
			  __func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
		return -1;
	}

	memcpy(s->agent_almacaddr, n->almacaddr, 6);
	list_add(&s->list, &n->stalist);
	n->sta_count++;
	sta_inc_ref(s);

	info("%s: Client " MACFMT " attached to node " MACFMT "\n",
	     __func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));

	snprintf(ev_data, sizeof(ev_data), "{\"sta\": \"" MACFMT "\", \"agent\": \"" MACFMT "\", "
			"\"bssid\":\"" MACFMT "\"}", MAC2STR(s->macaddr), MAC2STR(n->almacaddr), MAC2STR(s->bssid));
	cntlr_notify_event(n->cntlr, CNTLR_EVENT_CLIENT_CONNECT, ev_data);
	return 0;
}

int node_del_sta(struct node *n, struct sta *s)
{
	char ev_data[256] = {0};

	if (WARN_ON(!node_find_sta(n, s->macaddr))) {
		cntlr_dbg(LOG_STA,
			  "%s: Warn! STA " MACFMT " not in node " MACFMT"\n",
			  __func__, MAC2STR(s->macaddr), MAC2STR(n->almacaddr));
		return -1;
	}

	snprintf(ev_data, sizeof(ev_data), "{\"sta\": \"" MACFMT "\", \"agent\": \"" MACFMT "\", "
			"\"bssid\":\"" MACFMT "\"}", MAC2STR(s->macaddr), MAC2STR(n->almacaddr), MAC2STR(s->bssid));
	cntlr_notify_event(n->cntlr, CNTLR_EVENT_CLIENT_DISCONNECT, ev_data);

	list_del(&s->list);
	n->sta_count--;
	sta_dec_ref(s);

	return 0;
}

struct sta *node_find_sta(struct node *n, uint8_t *macaddr)
{
	struct sta *s = NULL;

	list_for_each_entry(s, &n->stalist, list) {
		if (!memcmp(s->macaddr, macaddr, 6))
			return s;
	}

	return NULL;
}

void node_update_stalist(struct node *n, uint8_t *stalist, int num)
{
	struct sta *s = NULL, *tmp;

	list_for_each_entry_safe(s, tmp, &n->stalist, list) {
		bool found = false;

		for (int i = 0; i < num; i++) {
			if (!memcmp(s->macaddr, &stalist[i*6], 6)) {
				found = true;
				break;
			}
		}

		if (!found) {
			list_del(&s->list);
			n->sta_count--;
			sta_dec_ref(s);
		}
	}
}

struct unassoc_sta_metrics *cntlr_find_usta_metric(struct controller *c,
                                                   struct sta *s,
                                                   uint8_t *agent_macaddr)
{
	struct unassoc_sta_metrics *u = NULL;

	list_for_each_entry(u, &s->de_sta->umetriclist, list) {
		if (!memcmp(u->agent_macaddr, agent_macaddr, 6))
			return u;
	}

	return NULL;
}

struct cmdu_buff *cntlr_query_sta_metric(struct controller *c, struct sta *s)
{
	if (!c || !s)
		return NULL;

	if (hwaddr_is_zero(s->agent_almacaddr)) {
		cntlr_warn(LOG_STA, "%s: Agent macaddr is zero\n", __func__);
		return NULL;
	}

	return cntlr_gen_sta_metric_query(c, s->agent_almacaddr, s->macaddr);
}

static void cntlr_get_all_sta_metrics(struct controller *c)
{
	struct node *n = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		struct sta *s = NULL;

		list_for_each_entry(s, &n->stalist, list) {
			struct cmdu_buff *cmdu;

			cmdu = cntlr_query_sta_metric(c, s);
			if (!cmdu)
				continue;

			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
	}
}

struct wifi_bss_element *cntlr_alloc_wifi_bss(struct controller *c,
					      uint8_t *macaddr)
{
	struct wifi_bss_element *bss = NULL;

	bss = calloc(1, sizeof(struct wifi_bss_element));
	if (!bss)
		return NULL;

	memcpy(bss->bssid, macaddr, 6);
	bss->sta_assoc_allowed = true;

	return bss;
}

struct netif_iface *cntlr_radio_add_iface(struct controller *c,
					      struct netif_radio *r,
					      uint8_t *macaddr)
{
	struct netif_iface *n;

	if (hwaddr_is_zero(macaddr)) {
		warn("%s: macaddr = zero is not valid\n", __func__);
		return NULL;
	}

	n = cntlr_find_iface(c, macaddr);
	if (n) {
		n->bss->enabled = true;
		n->radio = r;
		return n;
	}

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

	n->bss = cntlr_alloc_wifi_bss(c, macaddr);
	if (!n->bss) {
		free(n);
		return NULL;
	}

	n->band = wifi_opclass_get_band(r->radio_el->cur_opclass.opclass[0].id);
	if (n->band == BAND_UNKNOWN) {
		int ret;

		n->band = r->radio_el->band;
		if (n->band != BAND_UNKNOWN) {
			ret = cntlr_config_add_node_radio(&c->cfg, r->agent->almacaddr,
					r->radio_el->macaddr, n->band);
			if (!ret)
				cntlr_resync_config(c, true);
		} else {
			warn("%s: iface " MACFMT" has unknown band %d\n",
			     __func__, MAC2STR(macaddr), n->band);
		}
	}
	n->bss->is_fbss = true;
	n->bss->is_bbss = false;
	n->bss->enabled = true;
	list_add(&n->list, &r->iflist);
	n->agent = r->agent;
	n->radio = r;

#if (EASYMESH_VERSION >= 6)
	/* send channel selection request to propagate puncture bitmap */
	cntlr_send_channel_selection(c, r->agent->almacaddr, r->radio_el->macaddr, NULL);
#endif
	mactable_add_entry(c->mac_table, macaddr, MAC_ENTRY_FBSS, (void *)n);

	return n;
}

int cntlr_radio_add_bsta(struct controller *c, struct netif_radio *r, uint8_t *macaddr)
{
	memcpy(r->radio_el->bsta.macaddr, macaddr, 6);
	return 0;
}

static struct wifi_radio_element *cntlr_alloc_wifi_radio(struct controller *c, struct node *n,
							 struct netif_radio *r)
{
	struct wifi_radio_element *radio_el = NULL;

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

	INIT_LIST_HEAD(&radio_el->scanlist);
	radio_el->acs = cntlr_radio_acs_alloc(c, n, r);

	return radio_el;
}

struct netif_radio *cntlr_node_add_radio(struct controller *c, struct node *n,
		uint8_t *macaddr)
{
	struct netif_radio *r;
	struct radio_policy *p;

	if (hwaddr_is_zero(macaddr)) {
		warn("%s: macaddr = zero is not valid radio (node="MACFMT")\n",
		     __func__, MAC2STR(macaddr));
		return NULL;
	}

	r = cntlr_find_radio(c, macaddr);
	if (r)
		return r;

	r = calloc(1, sizeof(*r));
	if (!r)
		return NULL;

	r->radio_el = cntlr_alloc_wifi_radio(c, n, r);
	if (!r->radio_el) {
		free(r);
		return NULL;
	}

	INIT_LIST_HEAD(&r->iflist);
	list_add(&r->list, &n->radiolist);
	r->agent = n;

	memcpy(r->radio_el->macaddr, macaddr, 6);

	mactable_add_entry(c->mac_table, macaddr, MAC_ENTRY_RADIO, (void *)r);

	/* get cached results for this radio from agent */
	cntlr_request_scan_cache(c, n, r);

	cntlr_send_channel_selection(c, n->almacaddr, macaddr, NULL);

	p = cntlr_get_radio_policy(&c->cfg, macaddr);
	if (p)
		r->radio_el->band = p->band;
	return r;
}

int cntlr_radio_get_beacon_channel(struct wifi_radio_element *radio,
				   uint8_t *opclass, uint8_t *channel)
{
	struct wifi_radio_opclass *curr = &radio->cur_opclass;

	*channel = 0;
	*opclass = 0;

	for (int i = 0; i < curr->num_opclass; i++) {
		struct wifi_radio_opclass_entry *e = &curr->opclass[i];

		if (e->bandwidth == 20) {
			*opclass = e->id;
			*channel = e->channel[0].channel;
			return 0;
		}
	}

	return -1;
}

struct node *cntlr_alloc_node(struct controller *c, uint8_t *almacaddr)
{
	struct node *n;
	char ev_data[256] = {0};

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

	n->cntlr = c;
	n->depth = -1;
	n->scan_supported = true;
	n->np = NULL; //c->cfg.apolicy;
	memcpy(n->almacaddr, almacaddr, 6);
	n->map_profile = MULTIAP_PROFILE_1;

	INIT_LIST_HEAD(&n->stalist);
	INIT_LIST_HEAD(&n->radiolist);
	n->sta_count = 0;
	list_add(&n->list, &c->nodelist);
	c->num_nodes++;

	mactable_add_entry(c->mac_table, almacaddr, MAC_ENTRY_ALID, (void *)n);

	info("%s: New node " MACFMT " added\n", __func__, MAC2STR(almacaddr));
	snprintf(ev_data, sizeof(ev_data), "{ \"agent\": \"" MACFMT "\"}",
			MAC2STR(almacaddr));
	cntlr_notify_event(c, CNTLR_EVENT_AGENT_JOINED, ev_data);

	return n;
}

int cntlr_estimate_max_throughput_for_node(struct controller *c, uint8_t *node_almacaddr)
{
	const struct backhaul_info *b = NULL;
	const struct bh_topology_dev *dev;
	uint32_t est_thput = UINT32_MAX;

	dev = topology_find_device(&c->topology, node_almacaddr);
	if (!dev)
		return -1;

	if (dev->bh.depth == 0 || !dev->bh.parent) {
		struct node *n = cntlr_find_node(c, node_almacaddr);
		if (!n)
			return -1;

		/* Assume an arbitrarily large 'phyrate' for the root-node's uplink.
		 * The root node's uplink is not managed by 1905. Therefore, Tx-Link
		 * metrics is not available for the uplink.
		 */
		return n->tx_metrics.phyrate != 0 && n->tx_metrics.phyrate < UINT16_MAX ?
				n->tx_metrics.phyrate : 64000;
	}

	while (dev && dev->bh.parent) {
		b = &dev->bh;
		if (b->own_iface) {
			bool is_wifi = !!(b->own_iface->media_type & 0x0100);
			struct node *n;

			n = cntlr_find_node(c, (uint8_t *)dev->al_macaddr);
			if (!n)
				return -1;

			if (!is_wifi) {
				if (n->tx_metrics.phyrate < est_thput)
					est_thput = n->tx_metrics.phyrate;
			} else {
				struct sta *s = cntlr_find_sta(c->sta_table, (uint8_t *)b->own_iface->macaddr);

				if (!s || !s->is_bsta || !s->de_sta)
					return -1;

				if (s->de_sta->dl_est_thput / 2 < est_thput)
					est_thput = s->de_sta->dl_est_thput / 2;
			}
		}

		dev = b->parent;
	}

	return (int)est_thput;
}

void cntlr_clean_bcnreqlist(struct controller *c)
{
	trace("%s: --->\n", __func__);

	struct bcnreq *b = NULL, *tmp;

	list_for_each_entry_safe(b, tmp, &c->bcnreqlist, list) {
		list_del(&b->list);
		free(b);
	}
}

void cntlr_clean_bcnreqlist_sta(struct controller *c, struct sta *s)
{
	trace("%s: --->\n", __func__);

	struct bcnreq *b = NULL, *tmp;

	list_for_each_entry_safe(b, tmp, &c->bcnreqlist, list) {
		if (!memcmp(b->sta_mac, s->macaddr, 6)) {
			dbg("%s Remove Beacon Request for STA " MACFMT "\n",
			    __func__, MAC2STR(b->sta_mac));
			list_del(&b->list);
			free(b);
		}
	}
}

static void radio_clean_iflist(struct controller *c, struct netif_radio *r)
{
	struct netif_iface *ni = NULL, *tmp;

	list_for_each_entry_safe(ni, tmp, &r->iflist, list) {
		mactable_del_entry(c->mac_table, ni->bss->bssid, MAC_ENTRY_FBSS);
		free(ni->bss);
		list_del(&ni->list);
		free(ni);
	}
}

static void radio_clean_radio_el(struct netif_radio *r)
{
	struct wifi_scanres_element *b = NULL, *tmp;

	list_for_each_entry_safe(b, tmp, &r->radio_el->scanlist, list) {
		cntlr_radio_clean_scanlist_el(b);
	}

	cntlr_radio_acs_free(r->radio_el->acs);
	r->radio_el->acs = NULL;
	free(r->radio_el);
}

static void cntlr_clean_radiolist(struct controller *c, struct node *n)
{
	struct netif_radio *r = NULL, *tmp;

	list_for_each_entry_safe(r, tmp, &n->radiolist, list) {
		radio_clean_iflist(c, r);
		mactable_del_entry(c->mac_table, r->radio_el->macaddr, MAC_ENTRY_RADIO);
		radio_clean_radio_el(r);
		list_del(&r->list);
		free(r);
	}
}

static void node_clean_stalist(struct controller *c, struct node *n)
{
	struct sta *s = NULL, *tmp;

	if (!c || !n)
		return;

	list_for_each_entry_safe(s, tmp, &n->stalist, list) {
		node_del_sta(n, s);
	}

	if (WARN_ON(n->sta_count != 0)) {
		dbg("%s: Misalligned station counter (cnt=%d)!\n",
		     __func__, n->sta_count);
		n->sta_count = 0;
	}
}

static void cntlr_clean_mac_hashtable(struct controller *c)
{
	mactable_flush(c->mac_table);
}

static void cntlr_clean_all_sta(struct controller *c)
{
	struct hlist_node *tmp = NULL;
	int i;

	for (i = 0; i < MAC_HASHTABLE_SIZE; i++) {
		struct sta *s = NULL;

		if (hlist_empty(&c->sta_table[i]))
			continue;

		hlist_for_each_entry_safe(s, tmp, &c->sta_table[i], hlist) {
			hlist_del(&s->hlist, &c->sta_table[i]);
			cntlr_freeze_sta(c, s);
			cntlr_free_sta(s);
		}
	}

	c->num_sta = 0;
}

static void cntlr_clean_nodelist(struct controller *c)
{
	struct node *n = NULL, *tmp;

	list_for_each_entry_safe(n, tmp, &c->nodelist, list) {
		node_clean_stalist(c, n);
		cntlr_clean_radiolist(c, n);
		list_del(&n->list);
		free(n);
	}
}

static void cntlr_radar_exit(atimer_t *t)
{
	/*TODO: before change channel due to radar, save old chandef.
	 * Restore that chandef upon exit from radar nop.
	 */
}

static void cntlr_ieee1905_cmdu_event_handler(void *cntlr,
		struct blob_attr *msg)
{
	static const struct blobmsg_policy cmdu_attrs[6] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_INT16 },
		[1] = { .name = "mid", .type = BLOBMSG_TYPE_INT16 },
		[2] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "source", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "origin", .type = BLOBMSG_TYPE_STRING },
		[5] = { .name = "cmdu", .type = BLOBMSG_TYPE_STRING },
	};
	struct controller *c = (struct controller *)cntlr;
	char in_ifname[16] = {0};
	struct blob_attr *tb[6];
	char src[18] = { 0 }, src_origin[18] = { 0 };
	uint8_t *tlv = NULL;
	char *tlvstr = NULL;
	uint16_t type;
	uint8_t srcmac[6], origin[6];
	uint16_t mid = 0;
	int len = 0;
	sigset_t waiting_mask;

	sigpending(&waiting_mask);
	if (sigismember(&waiting_mask, SIGINT) ||
			sigismember(&waiting_mask, SIGTERM))
		return;

	blobmsg_parse(cmdu_attrs, 6, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1])
		return;

	if (tb[0]) {
		int t;

		t = blobmsg_get_u16(tb[0]);
		if (t < 0)
			return;

		type = (uint16_t)t;
		if (!is_cmdu_for_us(c, type))
			return;
	}

	if (tb[1])
		mid = (uint16_t)blobmsg_get_u16(tb[1]);


	if (tb[2])
		strncpy(in_ifname, blobmsg_data(tb[2]), 15);

	if (tb[3]) {
		strncpy(src, blobmsg_data(tb[3]), 17);
		hwaddr_aton(src, srcmac);
	}

	if (tb[4]) {
		strncpy(src_origin, blobmsg_data(tb[4]), 17);
		hwaddr_aton(src_origin, origin);
	}


	if (tb[5]) {
		len = blobmsg_data_len(tb[5]) - 16;

		tlvstr = calloc(1, len + 1);

		if (!tlvstr)
			return;

		strncpy(tlvstr, (blobmsg_data(tb[5]) + 16), len);
		len = (len - 1) / 2;
		tlv = calloc(1, len);
		if (!tlv) {
			free(tlvstr);
			return;
		}

		strtob(tlvstr, len, tlv);
		free(tlvstr);
	}
	cntlr_handle_map_event(c, type, mid, in_ifname, srcmac, origin, tlv, len);

	if (tlv)
		free(tlv);
}

static void cntlr_query_nodes(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, query_nodes);
	struct node *n = NULL;

	list_for_each_entry(n, &c->nodelist, list) {
		struct cmdu_buff *cmdu;

		cmdu = cntlr_gen_bk_caps_query(c, n->almacaddr);
		if (cmdu) {
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}

		cmdu = cntlr_gen_ap_capability_query(c, n->almacaddr);
		if (cmdu) {
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}

		cmdu = cntlr_gen_topology_query(c, n->almacaddr);
		if (cmdu) {
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
	}

	timer_set(&c->query_nodes, 60 * 1000);
}

bool cntlr_check_config_diff(struct controller *c, uint32_t diff)
{
	bool reloaded = false;

	if (diff & CONFIG_DIFF_CREDENTIALS || diff & CONFIG_DIFF_VLAN) {
		struct cmdu_buff *cmdu;
		uint8_t origin[6] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x13};

		if (diff & CONFIG_DIFF_CREDENTIALS)
			info("%s: credentials changed!\n", __func__);
		if (diff & CONFIG_DIFF_VLAN)
			info("%s: traffic separation toggled!\n", __func__);

		cmdu = cntlr_gen_ap_autoconfig_renew(c, origin);
		if (cmdu) {
			dbg("%s: Sending autoconfig renew to " MACFMT "\n",
			    __func__, MAC2STR(origin));
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
			reloaded = true;
		}
	} else if (diff & (CONFIG_DIFF_AGENT_POLICY | CONFIG_DIFF_AGENT_POLICY_CNT)) {
		struct node_policy *p = NULL;
		struct node *n = NULL;

		dbg("%s: agent policy config changed\n", __func__);

		if (diff & CONFIG_DIFF_BTM_EXCLUDE)
			dbg("%s: controller BTM exclusion list changed\n", __func__);

		/* send the policy config cmdu to the marked agent */
		list_for_each_entry(n, &c->nodelist, list) {
			struct cmdu_buff *cmdu;
			int num_bk = 0;
			uint8_t bk_id[16 * 6] = {0};
			uint8_t radio_id[MAX_NUM_RADIO * 6] = {0};
			struct netif_radio *r = NULL;
			int num_radio = 0;

			if ((diff & CONFIG_DIFF_AGENT_POLICY) && !n->np->is_policy_diff)
				continue;

			list_for_each_entry(r, &n->radiolist, list) {
				struct netif_iface *iface = NULL;

				memcpy(&radio_id[num_radio * 6],
				       r->radio_el->macaddr, 6);
				num_radio++;

				list_for_each_entry(iface, &r->iflist, list) {
					if (!iface->bss->is_bbss)
						continue;

					memcpy(&bk_id[num_bk * 6],
					       iface->bss->bssid, 6);
					num_bk++;
				}

			}

			cmdu = cntlr_gen_policy_config_req(c, n->almacaddr,
					n->np, num_radio, radio_id, num_bk,
					bk_id);
			if (cmdu) {
				send_cmdu(c, cmdu);
				cmdu_free(cmdu);
				reloaded = true;
			}
		}

		/* reset is_policy_diff to false; */
		list_for_each_entry(p, &c->cfg.nodelist, list) {
			p->is_policy_diff = false;
		}
	}
#if (EASYMESH_VERSION > 2)
	if ((diff & CONFIG_DIFF_QOS) && c->cfg.qos.enabled) {
		struct node *n = NULL;

		cntlr_dbg(LOG_QOS, "%s: qos config changed, sending new rules\n", __func__);

		/* send the policy config cmdu to the marked agent */
		list_for_each_entry(n, &c->nodelist, list)
			cntlr_qos_sync_node(c, &c->cfg.qos, n->almacaddr, true);
	}
#endif
#if (EASYMESH_VERSION >= 6)
	if ((diff & CONFIG_DIFF_AP_MLD) || (diff & CONFIG_DIFF_PUNCT_BITMAP)) {
		struct node *n = NULL;

		dbg("%s: ap mld config changed\n", __func__);

		/* send the ap mld config cmdu to the agent */
		list_for_each_entry(n, &c->nodelist, list)
			cntlr_send_ap_mld_configuration_request(c, n);
	}

	if (diff & CONFIG_DIFF_BSTA_MLD) {
		struct node *n = NULL;

		trace("%s: bsta mld config changed\n", __func__);

		/* send the ap mld config cmdu to the agent */
		list_for_each_entry(n, &c->nodelist, list)
			cntlr_send_bsta_mld_configuration_request(c, n);
	}
#endif
	if ((diff & CONFIG_DIFF_DEBUG)
			&& (c->log.level != c->cfg.debug_level ||
			    c->log.features != c->cfg.log_features)) {
		/* verbosity or features changed at runtime */
		c->log.level = c->cfg.debug_level;
		c->log.features = c->cfg.log_features;
		restart_logging(&c->log);
		info("%s: log.level changed to %d\n", __func__, c->log.level);
	}

	return reloaded;
}

#ifdef CONTROLLER_SYNC_DYNAMIC_CNTLR_CONFIG
int cntlr_sync_dyn_controller_config(struct controller *c, uint8_t *agent)
{
	struct node *n = NULL;
	uint8_t proto = 0xab;
	struct cmdu_buff *cmdu;

	if (agent && !hwaddr_is_zero(agent)) {
		cmdu = cntlr_gen_higher_layer_data(c, agent, proto, NULL, 0);
		if (!cmdu)
			return -1;

		send_cmdu(c, cmdu);
		cmdu_free(cmdu);
	} else {
		list_for_each_entry(n, &c->nodelist, list) {
			if (hwaddr_equal(c->almacaddr, n->almacaddr))
				continue; /* skip locally running agent */

			cmdu = cntlr_gen_higher_layer_data(c, n->almacaddr, proto, NULL, 0);
			if (!cmdu)
				return -1;

			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
	}

	return 0;
}
#endif

bool cntlr_resync_config(struct controller *c, bool reload)
{
	uint32_t diff;
	struct node_policy *np = NULL;

	diff = cntlr_config_reload(&c->cfg);

	list_for_each_entry(np, &c->cfg.nodelist, list) {
		struct node *n;

		n = cntlr_find_node(c, np->agent_id);
		if (n)
			n->np = np;
	}

	if (reload)
		cntlr_check_config_diff(c, diff);

#ifdef CONTROLLER_SYNC_DYNAMIC_CNTLR_CONFIG
	/* in dyn-controller mode, sync controller's config in network */
	if (diff)
		cntlr_sync_dyn_controller_config(c, NULL);
#endif

	return !!diff;
}

static void cntlr_signal_periodic_run(atimer_t *t)
{
	cntlr_trace(LOG_TIMER, "%s --->\n", __func__);

	struct controller *c = container_of(t, struct controller, signal_handler);
	sigset_t waiting_mask;

	sigpending(&waiting_mask);

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

		cntlr_resync_config(c, true);
	}

	timer_set(&c->signal_handler, 1 * 1000);
}

static void cntlr_ageout_nodes(struct controller *c)
{
	trace("%s: --->\n", __func__);

	struct node *n = NULL, *tmp;
	struct netif_radio *r = NULL, *temp;
	bool son = false;
	char ev_data[256] = {0};

	/* Here we need to see that all nodes in nodelist */
	/* and check there timestamp */
	list_for_each_entry_safe(n, tmp, &c->nodelist, list) {
		if (timestamp_expired(&n->last_tsp_seen, NODE_EXPIRE_TIME * 1000)) {

			list_for_each_entry_safe(r, temp, &n->radiolist, list) {
				cntlr_config_del_radio(n->almacaddr);
			}

			cntlr_config_del_node(n->almacaddr);
			topology_remove_device(&c->topology, n->almacaddr);
			node_clean_stalist(c, n);
			cntlr_clean_radiolist(c, n);
			mactable_del_entry(c->mac_table, n->almacaddr, MAC_ENTRY_ALID);

			info("%s: Node " MACFMT " removed due to ageout\n",
			     __func__, MAC2STR(n->almacaddr));
			snprintf(ev_data, sizeof(ev_data), "{ \"agent\": \"" MACFMT "\"}",
					MAC2STR(n->almacaddr));
			cntlr_notify_event(c, CNTLR_EVENT_AGENT_AGEOUT, ev_data);

			list_del(&n->list);
			free(n);
			if (c->num_nodes > 0) {
				c->num_nodes--;
				son = true;
			}
		}
	}

	if (son /* && c->son_enabled */)
		timer_set(&c->son_timer, 0);
}

static void cntlr_renew_nodes_configuration(struct controller *c)
{
	trace("%s: --->\n", __func__);

	struct node *node = NULL;
	char ev_data[256] = {0};

	/* When at least one running node was configured with older config, */
	/* send autoconfig renew */
	list_for_each_entry(node, &c->nodelist, list) {
		if (!timestamp_invalid(&node->last_config) &&
		    timestamp_less_than(&node->last_config, &c->cfg.last_change) &&
		    timestamp_greater_than(&node->last_cmdu, &c->cfg.last_change)) {
			uint8_t multicast_addr[6] = {0x01, 0x80, 0xc2, 0x00, 0x00, 0x13};
			struct cmdu_buff *cmdu =
				cntlr_gen_ap_autoconfig_renew(c, multicast_addr);

			info("%s: Renewing configuration of " MACFMT "\n",
			    __func__, MAC2STR(node->almacaddr));

			if (cmdu) {
				dbg("%s: Sending autoconfig renew to " MACFMT "\n",
				    __func__, MAC2STR(multicast_addr));
				send_cmdu(c, cmdu);
				cmdu_free(cmdu);
				snprintf(ev_data, sizeof(ev_data), "{ \"agent\": \"" MACFMT "\"}",
						MAC2STR(node->almacaddr));
				cntlr_notify_event(c, CNTLR_EVENT_AUTOCFG_RENEW, ev_data);
			}

			break;
		}
#if (EASYMESH_VERSION >= 6)
		else if (timestamp_less_than(&node->last_apmld_ack, &c->cfg.last_apmld_change) &&
			 timestamp_greater_than(&node->last_cmdu, &c->cfg.last_apmld_change)) {

			dbg("Found possibly unconfigured AP MLD node: "MACFMT"\n", MAC2STR(node->almacaddr));

			cntlr_send_ap_mld_configuration_request(c, node);
		} else if (timestamp_less_than(&node->last_bstamld_ack, &c->cfg.last_bstamld_change) &&
			   timestamp_greater_than(&node->last_cmdu, &c->cfg.last_bstamld_change)) {

			dbg("Found possibly unconfigured BSTA MLD node: "MACFMT"\n", MAC2STR(node->almacaddr));

			cntlr_send_bsta_mld_configuration_request(c, node);
		}
#endif
	}

}

static void combined_link_metric_periodic_collection(struct controller *c)
{
	trace("%s: --->\n", __func__);

	uint8_t *radiolist = NULL, *bsslist = NULL;
	struct cmdu_buff *cmdu;
	struct node *n = NULL;

	/* AP metrics query for each agent */
	/* For each agent */
	list_for_each_entry(n, &c->nodelist, list) {
		uint8_t *new_radiolist;
		struct netif_radio *r = NULL;
		int num_bss = 0, num_radio = 0;
		uint8_t hwaddr[6];
		struct node_policy *np;

		num_radio = 0;
		num_bss = 0;
		memcpy(hwaddr, n->almacaddr, 6);

		np = cntlr_get_node_policy(&c->cfg, n->almacaddr);
		if (np && np->report_metric_periodic > 0) {
			dbg("%s: Skip AP metrics query for node " MACFMT " - reporting interval configured\n",
			    __func__, MAC2STR(n->almacaddr));
			continue; /* next node */
		}

		list_for_each_entry(r, &n->radiolist, list) {
			struct netif_iface *bss = NULL;
			int radio_index;
			struct radio_policy *rp;

			rp = cntlr_get_radio_policy(&c->cfg, r->radio_el->macaddr);
			if (rp && rp->report_util_threshold > 0) {
				dbg("%s: Skip AP metrics query for radio "
					MACFMT " - utilization threshold configured\n",
				    __func__, MAC2STR(r->radio_el->macaddr));
				continue;
			}

			/* Building a radiolist of all radios */
			new_radiolist = (uint8_t *)realloc(radiolist,
							6 * (num_radio + 1) * sizeof(uint8_t));

			if (!new_radiolist) {
				err("%s: realloc of radiolist failed\n", __func__);
				goto error;
			}

			radiolist = new_radiolist;
			num_radio++;
			radio_index = (num_radio - 1) * 6;
			memcpy(radiolist + radio_index, r->radio_el->macaddr, 6);

			/* For each bss in radio */
			list_for_each_entry(bss, &r->iflist, list) {
				int bss_index;
				uint8_t *new_bsslist;

				/* Building a bsslist of all BSS */
				new_bsslist = (uint8_t *)realloc(bsslist,
							6 * (num_bss + 1) * sizeof(uint8_t));

				if (!new_bsslist) {
					err("%s: realloc of bsslist failed\n", __func__);
					goto error;
				}

				bsslist = new_bsslist;
				num_bss++;
				bss_index = (num_bss - 1) * 6;
				memcpy(bsslist + bss_index, bss->bss->bssid, 6);
			}
		}

		if (num_bss > 0) {
			cmdu = cntlr_gen_ap_metrics_query(c, hwaddr, num_bss, bsslist, num_radio, radiolist);
			if (!cmdu) {
				warn("%s: cmdu cntlr_gen_ap_metrics_query failed!\n", __func__);
				goto error;
			}
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		} else {
			dbg("%s: Skip sending AP metrics query, no BSS to query\n", __func__);
		}

		cmdu = cntlr_gen_1905_link_metric_query(c, n->almacaddr);
		if (!cmdu) {
			warn("%s: cmdu cntlr_gen_1905_link_metric_query failed!\n", __func__);
			goto error;
		}

		send_cmdu(c, cmdu);
		cmdu_free(cmdu);
	}

error:
	if (radiolist)
		free(radiolist);
	if (bsslist)
		free(bsslist);
}

static void cntlr_metric_collection(struct controller *c)
{

	cntlr_log_nodes(c);

	//forall_node_get_fhinfo(c);   /* replaced from per-node refresh bss */

	cntlr_get_all_sta_metrics(c);

	/* Call AP metrics query and 1905 link metrics query for data collection */
	combined_link_metric_periodic_collection(c);

	cntlr_renew_nodes_configuration(c);
}


static void cntlr_acs_run(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, acs);

	/* Run ACS recalc here */
	dbg("acs timeout - run recalc\n");
	cntlr_acs_recalc(c);

	if (c->cfg.acs && c->cfg.acs_timeout)
		timer_set(&c->acs, c->cfg.acs_timeout * 1000);
}

static void cntlr_steer_sched_run(atimer_t *t)
{
	cntlr_trace(LOG_STEER, "%s: --->\n", __func__);

	struct controller *c = container_of(t, struct controller, steer_sched_timer);
	int i;

	for (i = 0; i < c->inform_sta_num; i++) {
		struct sta *s = cntlr_find_sta(c->sta_table, &c->inform_stalist[i * 6]);

		if (!s)
			continue;

		if (c->cfg.steer.enable_sta_steer || c->cfg.steer.enable_bsta_steer)
			cntlr_inform_steer_modules(c, s, c->inform_cmdu_type);
	}
}

static void cntlr_son_timer_cb(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, son_timer);
	int ret;

	cntlr_dbg(LOG_SON, "%s: --->\n", __func__);
	if (!c->son || !c->son_doit) {
		cntlr_dbg(LOG_SON, "%s: SON plugin not available\n", __func__);
		return;
	}

	if (c->topology.num_devs < 3)
		return;

	ret = c->son_doit(c, NULL, cntlr_son_response_cb);
	if (ret == -1 && errno == EAGAIN) {
		cntlr_dbg(LOG_SON, "%s: -- %d --\n", __func__, __LINE__);
		timer_set(&c->son_timer, 2000);
	}
}

static void cntlr_start(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, start_timer);

	if (c->state == CNTLR_INIT) {
		c->state = CNTLR_START;
		cntlr_publish_object(c, "map.controller");
		//cntlr_publish_dbg_object(c, "map.controller.dbg");	//TODO: open
	}
}

static void cntlr_discovery(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, discovery_timer);
	struct cmdu_buff *cmdu;

	cmdu = cntlr_gen_ap_autoconfig_search(c, 0x02, 0x00);
	if (!cmdu)
		return;

	c->mid_2g = send_cmdu(c, cmdu);
	cmdu_free(cmdu);

	cmdu = cntlr_gen_ap_autoconfig_search(c, 0x02, 0x01);
	if (!cmdu)
		return;

	c->mid_5g = send_cmdu(c, cmdu);
	cmdu_free(cmdu);

	timer_set(t, 180 * 1000);
}

int cntlr_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);

	cntlr_ieee1905_cmdu_event_handler(priv, msg);

	return 0;
}

int cntlr_map_del_cb(void *bus, void *priv, void *data)
{
	struct controller *c = (struct controller *)priv;
	uint32_t *obj = (uint32_t *)data;

	c->subscribed = false;
	map_unsubscribe(bus, c->subscriber);
	c->subscriber = NULL;

	dbg("%s: Object 0x%x no longer present\n", __func__, *obj);

	return 0;
}

static int cntlr_subscribe_for_cmdus(struct controller *c)
{
	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_LINK_METRIC_RESPONSE,
			CMDU_TYPE_VENDOR_SPECIFIC,
			CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH,
			CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE,
			CMDU_TYPE_AP_AUTOCONFIGURATION_WSC,
			CMDU_1905_ACK,
			CMDU_BEACON_METRICS_RESPONSE,
			CMDU_AP_METRICS_RESPONSE,
			CMDU_ASSOC_STA_LINK_METRICS_RESPONSE,
			CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE,
			CMDU_CHANNEL_SCAN_REQUEST,
			CMDU_CHANNEL_SCAN_REPORT,
			CMDU_CLIENT_DISASSOCIATION_STATS,
			CMDU_ASSOCIATION_STATUS_NOTIFICATION,
			CMDU_TUNNELED,
			CMDU_BACKHAUL_STA_CAPABILITY_QUERY,
			CMDU_BACKHAUL_STA_CAPABILITY_REPORT,
			CMDU_CHANNEL_PREFERENCE_REPORT,
			CMDU_CLIENT_STEERING_BTM_REPORT,
			CMDU_STEERING_COMPLETED,
			CMDU_CHANNEL_SELECTION_RESPONSE,
			CMDU_OPERATING_CHANNEL_REPORT,
			CMDU_AP_CAPABILITY_QUERY,
			CMDU_AP_CAPABILITY_REPORT,
			CMDU_CLIENT_CAPABILITY_REPORT,
			CMDU_HIGHER_LAYER_DATA,
			CMDU_BACKHAUL_STEER_RESPONSE,
#if (EASYMESH_VERSION > 2)
			CMDU_PROXIED_ENCAP_DPP,
			CMDU_DIRECT_ENCAP_DPP,
			CMDU_BSS_CONFIG_REQUEST,
			CMDU_BSS_CONFIG_RESULT,
			CMDU_CHIRP_NOTIFICATION,
			CMDU_DPP_BOOTSTRAPING_URI,
#endif
#if (EASYMESH_VERSION > 5)
			CMDU_EARLY_AP_CAPABILITY_REPORT,
#endif
			-1);
	memcpy(c->cmdu_mask, cmdu_mask, sizeof(c->cmdu_mask));

	dbg("%s: wait for map-plugin\n", __func__);
	cntlr_wait_for_object_timeout(c, map_plugin, -1, &map_id);
	c->map_oid = map_id;

	/* register as client to the map module */
	ret = map_subscribe(c->ubus_ctx,
			    &c->map_oid,
			    "mapcontroller", &cmdu_mask, c,
			    cntlr_map_sub_cb,
			    cntlr_map_del_cb,
			    &c->subscriber);
	if (!ret) {
		c->subscribed = true;
	} else {
		warn("Failed to 'register' with %s (err = %s)\n",
		      map_plugin, ubus_strerror(ret));
	}

	return ret;
}

static int cntlr_ackq_timeout_cb(struct cmdu_ackq *q, struct cmdu_ackq_entry *e)
{
	struct controller *a = container_of(q, struct controller, cmdu_ack_q);
	struct cmdu_buff *cmdu = (struct cmdu_buff *) e->cookie;
	int ret;

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

	if (e->resend_cnt-- > 0) {
		ret = send_cmdu_ubus(a, cmdu);
		if (ret < 0)
			err("%s: Failed to send cmdu\n", __func__);

		return CMDU_ACKQ_TMO_REARM;
	}

	return CMDU_ACKQ_TMO_DELETE;
}

static void cntlr_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);
}

static void uobj_add_event_handler(void *cntlr, struct blob_attr *msg)
{
	char path[32] = {0};
	uint32_t id = 0;
	struct controller *c = (struct controller *) cntlr;
	struct blob_attr *tb[2];
	static const struct blobmsg_policy ev_attr[2] = {
		[0] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
		[1] = { .name = "path", .type = BLOBMSG_TYPE_STRING }
	};

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

	if (!tb[0] || !tb[1])
		return;

	strncpy(path, blobmsg_data(tb[1]), sizeof(path) - 1);
	id = (uint32_t) blobmsg_get_u32(tb[0]);
	dbg("|%s:%d| path = [%s] id = [%d] [%u]\n", __func__, __LINE__, path,
			id, id);
	if (!strncmp(path, map_plugin, strlen(map_plugin))) {
		/* TODO: how to handle failure? */
		cntlr_subscribe_for_cmdus(c);
	}
}

static void cntlr_event_handler(struct ubus_context *ctx,
		struct ubus_event_handler *ev,
		const char *type, struct blob_attr *msg)
{
	int i;
	char *str;
	struct controller *c = container_of(ev, struct controller, evh);
	struct wifi_ev_handler {
		const char *ev_type;
		void (*handler)(void *ctx, struct blob_attr *ev_data);
	} evs[] = {
		{ "ubus.object.add", uobj_add_event_handler }
	};

	str = blobmsg_format_json(msg, true);
	if (!str)
		return;

	dbg("[ &controller = %p ] Received [event = %s]  [val = %s]\n",
			c, type, str);

	for (i = 0; i < ARRAY_SIZE(evs); i++) {
		if (!strcmp(type, evs[i].ev_type))
			evs[i].handler(c, msg);
	}

	free(str);
}

static void cntlr_periodic_run(atimer_t *t)
{
	struct controller *c = container_of(t, struct controller, heartbeat);

	c->uptime += 1;

	cntlr_trace(LOG_TIMER, "%s: periodic time (elapsed:%"PRIu64")\n",
		  __func__, c->uptime);


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

	if ((c->uptime % METRIC_REP_INTERVAL) == 0)
		cntlr_metric_collection(c);

#if 1	//TODO: move from here
	if (c->uptime % 7 == 0) {
		struct node *n = NULL;

		list_for_each_entry(n, &c->nodelist, list) {
			int est_thput = cntlr_estimate_max_throughput_for_node(c, n->almacaddr);

			dbg("** Node = " MACFMT ": est-throughput = %d\n",
			    MAC2STR(n->almacaddr), est_thput);
			n->est_thput_dl = est_thput;
		}
	}
#endif
	cntlr_ageout_nodes(c);
	timer_set(&c->heartbeat, 1 * 1000);
}

void run_controller(void *opts)
{
	struct log_options *lopts = (struct log_options *)opts;
	struct controller *c;
	struct ubus_context *ctx;
	sigset_t base_mask;
	int ret;

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

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

	c = calloc(1, sizeof(struct controller));
	if (!c)
		return;

	c->num_nodes = 0;

	INIT_LIST_HEAD(&c->nodelist);
	INIT_LIST_HEAD(&c->bcnreqlist);
	INIT_LIST_HEAD(&c->sclist);
	INIT_LIST_HEAD(&c->plugins);

#if (EASYMESH_VERSION > 2)
	{
#ifdef USE_LIBDPP
		char *argv[] = {"-I", "-C", "-V", "2"};
		int argc = 4;
		int ret;

		ret = dpp_init(&c->dpp, argc, argv);
		if (ret) {
			warn("Failed to init dpp context\n");
			free(c);
			return;
		}

		dpp_register_cb(c->dpp, dpp_frame_handler);
		dpp_set_ctx_private_data(c->dpp, c);
#endif
	}
#endif

	memcpy(&c->log, lopts, sizeof(*lopts));
	cntlr_config_defaults(c, &c->cfg);
	cntlr_resync_config(c, false);

	if (c->log.level == LOGLEVEL_UNSET &&
	    c->log.level != c->cfg.debug_level) {
		/* verbosity not explicitly set with -v
		 * use value from config (or default)
		 */
		c->log.level = c->cfg.debug_level;
	}

	/* Apply log features from config */
	c->log.features = c->cfg.log_features;
	restart_logging(&c->log);

#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	dpp_cntlr_read_enrollees(c);
#endif
#endif

	info("Starting Controller build %s %s, EasyMesh ver%d.\n",
	     __DATE__, __TIME__, EASYMESH_VERSION);
	dbg("%s: cntlr = %p\n", __func__, c);
	info("%s: log.level set to %d\n", __func__, c->log.level);

	init_topology(&c->topology);

	uloop_init();
	ctx = ubus_connect(ubus_socket);
	if (!ctx) {
		err("Failed to connect to ubus\n");
		goto out_exit;
	}
	c->ubus_ctx = ctx;

	ret = cntlr_get_ieee1905_almac(c, c->almacaddr);
	if (ret)
		goto out_exit;
	memcpy(c->cfg.id, c->almacaddr, 6);

	cmdu_ackq_init(&c->cmdu_ack_q);
	c->cmdu_ack_q.timeout_cb = cntlr_ackq_timeout_cb;
	c->cmdu_ack_q.delete_cb = cntlr_ackq_delete_cb;

	ubus_add_uloop(ctx);

	if (!c->cfg.enabled)
		goto out_exit;

	{
		/* diff always 1 after first round, will cause failures on
		 * first reload if not un-set
		 */
		struct node_policy *np = NULL;

		list_for_each_entry(np, &c->cfg.nodelist, list) {
			struct node *n;

			np->is_policy_diff = false;
			n = cntlr_add_node(c, np->agent_id);
			if (!n)
				goto out_exit;

			n->np = np;

		}
	}

	c->state = CNTLR_INIT;
	timer_init(&c->discovery_timer, cntlr_discovery);
	timer_init(&c->start_timer, cntlr_start);
	timer_init(&c->heartbeat, cntlr_periodic_run);
	timer_init(&c->radar_timer, cntlr_radar_exit);
	timer_init(&c->signal_handler, cntlr_signal_periodic_run);
	timer_init(&c->query_nodes, cntlr_query_nodes);
	timer_init(&c->acs, cntlr_acs_run);
	timer_init(&c->steer_sched_timer, cntlr_steer_sched_run);
	timer_init(&c->son_timer, cntlr_son_timer_cb);

	timer_set(&c->heartbeat, 1 * 1000);
	timer_set(&c->start_timer, waitext ? 5 * 1000 : 0);
	timer_set(&c->discovery_timer, 0);
	timer_set(&c->signal_handler, 5 * 1000);
	timer_set(&c->query_nodes, 60 * 1000);

	if (c->cfg.acs && c->cfg.acs_timeout)
		timer_set(&c->acs, c->cfg.acs_timeout * 1000);

	c->evh.cb = cntlr_event_handler;
	ubus_register_event_handler(ctx, &c->evh, "ubus.object.*");

	cntlr_subscribe_for_cmdus(c);

	/* The counters in MultiAPSteeringSummaryStats are all reset on reboot. */
	memset(&c->dlem.network.steer_summary, 0, sizeof(struct wifi_steer_summary));
	info("Configured Multi-AP Profile %d\n", c->cfg.map_profile);

	cntlr_son_init(c);

	uloop_run();

out_exit:
	info("exit!\n");
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	cntlr_info(LOG_DPP, "free dpp\n");
	dpp_free(c->dpp);
#endif
#endif
	cntlr_unload_steer_modules(c);
	cntlr_son_exit(c);
	if (c->subscribed)
		map_unsubscribe(c->ubus_ctx, c->subscriber);
	cntlr_clean_mac_hashtable(c);
	cntlr_clean_bcnreqlist(c);
	cntlr_clean_nodelist(c);
	cntlr_clean_all_sta(c);
	free_topology(&c->topology);
	ubus_unregister_event_handler(ctx, &c->evh);
	cntlr_remove_object(c);
	//cntlr_remove_dbg_object(c);	//TODO
	cmdu_ackq_free(&c->cmdu_ack_q);
	cntlr_config_clean(&c->cfg);
	uloop_done();
	stop_logging();
	free(c);
}
