/*
 * cntlr_map.c - implements MAP2 CMDUs handling
 *
 * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */


#include "cntlr_map.h"

#include <1905_tlvs.h>
#include <cmdu.h>
#ifdef CONTROLLER_SYNC_DYNAMIC_CNTLR_CONFIG
#include <cntlrsync.h>
#endif
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <json-c/json.h>
#include <libubus.h>
#include <uci.h>
#include <easy/easy.h>
#include <i1905_wsc.h>
#include <easymesh.h>
#include <map_module.h>
#include <wifidefs.h>
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
#include <dpp_api.h>
#include <dpputils.h>
#endif
#endif

#include "utils/utils.h"
#include "utils/debug.h"
#include "config.h"
#include "timer.h"
#include "cmdu_ackq.h"
#include "cntlr.h"
#include "cntlr_cmdu.h"
#include "cntlr_ubus.h"
#include "cntlr_tlv.h"
#include "acs.h"
#include "mactable.h"
#include "scan.h"
#include "sta.h"
#include "steer.h"
#include "steer_module.h"
#include "topology.h"
#include "wifi_dataelements.h"
#include "wifi_opclass.h"
#include "cntlr_extension.h"

/* Length of data in CMDU after which we consider that CMDU might might be fragmentized */
#define FRAG_DATA_SIZE         (1400)

#define TIMESTAMP_MAX_LEN 256

#ifndef IEEE80211_FREQUENCY_BAND_6_GHZ
#define IEEE80211_FREQUENCY_BAND_6_GHZ	(0x03)
#endif


typedef int (*map_cmdu_handler_t)(void *cntlr, struct cmdu_buff *cmdu,
				  struct node *n);

struct map_cmdu_calltable_t {
	map_cmdu_handler_t handle;
};

struct tlv *map_cmdu_get_tlv(struct cmdu_buff *cmdu, uint8_t type)
{
	struct tlv *t;

	if (!cmdu || !cmdu->cdata) {
		map_error = MAP_STATUS_ERR_CMDU_MALFORMED;
		return NULL;
	}

	t = cmdu_peek_tlv(cmdu, type);
	if (!t) {
		map_error = MAP_STATUS_ERR_CMDU_MALFORMED;
		return NULL;
	}

/*
	if (tlv_length(t) < tlv_minsize(type)) {
		map_error = MAP_STATUS_ERR_TLV_MALFORMED;
		return NULL;
	}
*/

	return t;
}

#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
int send_dpp_cce_indication(struct controller *c, uint8_t *agent,
			     bool advertise)
{
	int ret = -1;

	if (agent) {
		struct cmdu_buff *cmdu;

		cmdu = cntlr_gen_dpp_cce_indication(c, agent, advertise);
		if (cmdu) {
			ret = send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
	} else {
		struct node *n = NULL;

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

			cmdu = cntlr_gen_dpp_cce_indication(c, n->almacaddr, advertise);
			if (cmdu) {
				ret |= send_cmdu(c, cmdu);
				cmdu_free(cmdu);
			}
		}
	}

	return ret;
}

int send_dpp_cce_indication_all(struct controller *c, bool advertise)
{
	return send_dpp_cce_indication(c, NULL, advertise);
}
#endif /* USE_LIBDPP */
#endif

int handle_topology_discovery(void *cntlr, struct cmdu_buff *cmdu,
				  struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	uint8_t almacaddr[6] = {0};
	struct tlv_aladdr *aladdr;
	struct tlv *t;

	t = map_cmdu_get_tlv(cmdu, TLV_TYPE_AL_MAC_ADDRESS_TYPE);
	if (!t) {
		warn("%s: Ignore malformed Topology Discovery\n", __func__);
		return -1;
	}

	aladdr = (struct tlv_aladdr *)t->data;
	memcpy(almacaddr, aladdr->macaddr, 6);
	if (hwaddr_is_zero(almacaddr)) {
		warn("%s: Discard Topology Discovery from aladdr = 0\n", __func__);
		return -1;
	}

	n = cntlr_add_node(c, almacaddr);
	if (!n) {
		err("%s: node allocation for "MACFMT" failed!\n", __func__,
		    MAC2STR(almacaddr));
		return -1;
	}

	return 0;
}

int handle_topology_notification(void *cntlr, struct cmdu_buff *cmdu,
				  struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *)cntlr;
	struct tlv *tv[2][TLV_MAXNUM] = {0};
	uint8_t almacaddr[6] = {0};
	struct tlv_aladdr *aladdr;
	struct sta *s = NULL;
	struct tlv *t;

	t = map_cmdu_get_tlv(cmdu, TLV_TYPE_AL_MAC_ADDRESS_TYPE);
	if (!t) {
		warn("%s: Ignore malformed Topology Notification\n", __func__);
		return -1;
	}

	aladdr = (struct tlv_aladdr *)t->data;
	memcpy(almacaddr, aladdr->macaddr, 6);
	if (hwaddr_is_zero(almacaddr)) {
		dbg("%s: Discard Topology Notification from aladdr = 0\n", __func__);
		return -1;
	}

	n = cntlr_add_node(c, almacaddr);
	if (!n) {
		err("%s: node allocation for "MACFMT" failed!\n", __func__,
		    MAC2STR(almacaddr));
		return -1;
	}

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	if (tv[1][0]) {
		struct tlv_client_assoc_event *ev;
		struct netif_iface *fh, *bh;
		bool associated;

		ev = (struct tlv_client_assoc_event *)tv[1][0]->data;
		associated = !!(ev->event & BIT(7)) ? true : false;

		fh = cntlr_find_fbss(c, ev->bssid);
		bh = cntlr_find_bbss(c, ev->bssid);
		if (!fh && !bh) {
			cntlr_warn(LOG_STA,
				  "%s: Ignore unknown STA " MACFMT " %sassociation event on BSSID " MACFMT "\n",
				  __func__,
				  MAC2STR(ev->macaddr),
				  associated ? "" : "dis",
				  MAC2STR(ev->bssid));
			return 0;
		}

		s = cntlr_find_sta(c->sta_table, ev->macaddr);
		if (s) {
			if (associated) {
				struct cmdu_buff *txcmdu;

				time(&s->assoc_time);
				memcpy(s->bssid, ev->bssid, 6);
				s->state = STA_CONNECTED;

				if (memcmp(s->agent_almacaddr, n->almacaddr, 6)) {
					/* associated to a new node - remove from the old one */
					struct node *old_n = NULL;

					cntlr_dbg(LOG_STA, "%s: STA " MACFMT " moved to new node (old="
						  MACFMT " new=" MACFMT ")\n", __func__, MAC2STR(s->macaddr),
						  MAC2STR(s->agent_almacaddr), MAC2STR(n->almacaddr));

					old_n = cntlr_find_node(c, s->agent_almacaddr);
					if (old_n)
						node_del_sta(old_n, s);
				}

				memcpy(s->de_sta->bssid, ev->bssid, 6);
				node_add_sta(n, s);

				txcmdu = cntlr_gen_client_caps_query(c, c->almacaddr, s->macaddr, s->bssid);
				if (txcmdu) {
					send_cmdu(c, txcmdu);
					cmdu_free(txcmdu);
				}
			} else {
				/* handle disconnect *iff* it is for current AP */
				if (!memcmp(s->bssid, ev->bssid, 6)) {
					node_del_sta(n, s);

					if (!memcmp(s->agent_almacaddr, n->almacaddr, 6)) {
						/* disassociated from current node */
						time(&s->disassoc_time);
						memset(s->bssid, 0, sizeof(s->bssid));
						memset(s->agent_almacaddr, 0, sizeof(s->agent_almacaddr));
						s->state = STA_DISCONNECTED;
					}
				}
			}
		} else { /* unknown sta */
			if (associated) {
				struct cmdu_buff *txcmdu;

				s = cntlr_add_sta(c, c->sta_table, ev->macaddr);
				if (!s) {
					cntlr_warn(LOG_STA, "%s: failed to add STA " MACFMT "\n",
						  __func__, MAC2STR(ev->macaddr));
					return -1;
				}

				time(&s->assoc_time);
				memcpy(s->bssid, ev->bssid, 6);
				s->state = STA_CONNECTED;

				memcpy(s->de_sta->bssid, ev->bssid, 6);
				node_add_sta(n, s);

				txcmdu = cntlr_gen_client_caps_query(c, c->almacaddr, s->macaddr, s->bssid);
				if (txcmdu) {
					send_cmdu(c, txcmdu);
					cmdu_free(txcmdu);
				}
			} else {
				cntlr_warn(LOG_STA,
					  "Ignore unknown STA " MACFMT " disassoc event\n",
					  MAC2STR(ev->macaddr));
				return 0;
			}
		}

		s->is_bsta = bh ? true : false;

		cntlr_info(LOG_STA, "%s: %sSTA " MACFMT " %s BSSID " MACFMT" on Node "MACFMT"\n",
			   __func__, s->is_bsta ? "b" : "", MAC2STR(s->macaddr),
			   associated ? "connected to" : "disconnected from",
			   MAC2STR(ev->bssid), MAC2STR(n->almacaddr));

		cntlr_update_sta_steer_data(c, s);
	}

	if (s) {
		/* Inform steering plugins */
		c->inform_cmdu_type = CMDU_TYPE_TOPOLOGY_NOTIFICATION;
		c->inform_sta_num = 1;
		memset(c->inform_stalist, 0, sizeof(c->inform_stalist));
		memcpy(c->inform_stalist, s->macaddr, 6);
		timer_set(&c->steer_sched_timer, 0);
	}

	return 0;
}

int handle_topology_query(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);
	return 0;
}


int handle_topology_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: from node " MACFMT " --->\n", __func__, MAC2STR(n->almacaddr));

	struct controller *c = (struct controller *) cntlr;
	struct tlv *tv[TOPOLOGY_RESPONSE_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct bh_topology_dev *bh_topo_dev = NULL;
	const struct tlv_1905neighbor *neighbor_tlvs[TLV_MAXNUM] = { NULL };
	uint8_t neigh_tlv_cnt = 0;
	uint16_t tlv_lengths[TLV_MAXNUM] = { 0 };

	cntlr_set_link_profile(c, n, cmdu);

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	/* Device Information Type TLV */
	if (tv[TOPOLOGY_RESPONSE_DEVICE_INFORMATION_TYPE_IDX][0]) {
		const struct tlv_device_info *tlv_dev_info = (struct tlv_device_info *)
			tv[TOPOLOGY_RESPONSE_DEVICE_INFORMATION_TYPE_IDX][0]->data;
		bool new_bh_topo_dev = false;

		dbg("%s: Device Info TLV from " MACFMT "\n", __func__, MAC2STR(cmdu->origin));

		bh_topo_dev = topology_find_device(&c->topology, tlv_dev_info->aladdr);

		if (!bh_topo_dev) {
			dbg("%s: New bh_topology_device\n", __func__);
			bh_topo_dev = topology_add_device(&c->topology, tlv_dev_info->aladdr);

			if (!bh_topo_dev) {
				err("%s: Error in memory alloc\n", __func__);
				return -1;
			}

			new_bh_topo_dev = true;
		}

		set_bh_toplogy_response_timestamp(bh_topo_dev);

		if (new_bh_topo_dev ||
			has_interface_info_changed(tlv_dev_info, bh_topo_dev)) {

			dbg("%s: New interface info for bh_topology_device\n", __func__);

			/* Copy new info and invalidate the model. */
			copy_interface_info_from_tlv(&c->topology, tlv_dev_info, bh_topo_dev);
		}
	}

	if (!bh_topo_dev) {
		err("%s: 1905 dev.info is missing. Logical error in\n", __func__);
		return -1;
	}

	/* 1905.1 neighbor device TLVs */
	neigh_tlv_cnt = 0;
	while (tv[TOPOLOGY_RESPONSE_NEIGHBOR_DEVICE_LIST_IDX][neigh_tlv_cnt]
			&& (neigh_tlv_cnt < TLV_MAXNUM)) {
		neighbor_tlvs[neigh_tlv_cnt] = (struct tlv_1905neighbor *)
			tv[TOPOLOGY_RESPONSE_NEIGHBOR_DEVICE_LIST_IDX][neigh_tlv_cnt]->data;

		tlv_lengths[neigh_tlv_cnt] =
			tlv_length(tv[TOPOLOGY_RESPONSE_NEIGHBOR_DEVICE_LIST_IDX][neigh_tlv_cnt]);
		++neigh_tlv_cnt;
	}

	if (has_neighbor_info_changed(neighbor_tlvs, tlv_lengths,
					neigh_tlv_cnt, bh_topo_dev)) {
		dbg("%s: New neighbor info for bh_topology_device\n", __func__);

		/* Copy new info and invalidate the model. */
		copy_neighbor_info_from_tlvs(&c->topology, neighbor_tlvs, tlv_lengths,
					     neigh_tlv_cnt, bh_topo_dev);
	}

	if (tv[TOPOLOGY_RESPONSE_AP_OPERATIONAL_BSS_IDX][0]) {
		struct tlv_ap_oper_bss *tlv;
		uint8_t *tv_data;
		int i, offset = 0;

		tlv = (struct tlv_ap_oper_bss *)tv[TOPOLOGY_RESPONSE_AP_OPERATIONAL_BSS_IDX][0]->data;
		if (!tlv)
			return -1;

		tv_data = (uint8_t *)tlv;

		offset += 1; /* num_radio */

		for (i = 0; i < tlv->num_radio; i++) {
			uint8_t num_bss = 0;
			struct netif_radio *r = NULL;
			struct netif_iface *p = NULL;
			int j;

			r = cntlr_node_add_radio(c, n, &tv_data[offset]);
			if (!r)
				return -1; /* FIXME: continue + proper offset shift */

			offset += 6; /* macaddr */

			/* disable all prior stored fh/bh interfaces */
			list_for_each_entry(p, &r->iflist, list) {
				p->bss->enabled = false;
				p->bss->is_fbss = true;
				p->bss->is_bbss = false;
			}

			memcpy(&num_bss, &tv_data[offset], 1);

			offset += 1; /* num_bss */

			for (j = 0; j < num_bss; j++) {
				uint8_t *macaddr = &tv_data[offset];
				struct netif_iface *fh;
				uint8_t ssidlen = 0;

				offset += 6; /* macaddr */
				ssidlen = tv_data[offset];
				if (ssidlen > 32) {
					warn("%s: Discard Topology Response. Invalid ssidlen %d \
					     in AP Operational BSS TLV\n", __func__, ssidlen);
					return -1;
				}
				offset += 1; /* ssidlen */

				fh = cntlr_radio_add_iface(c, r, macaddr);
				if (!fh)
					return -1; /* FIXME: continue + proper offset shift */

				memset(fh->bss->ssid, 0, sizeof(fh->bss->ssid));
				memcpy(fh->bss->ssid, &tv_data[offset], ssidlen);
				fh->bss->ssidlen = ssidlen;
				offset += ssidlen; /* ssid */

				/* Update measurement time */
				time(&fh->bss->tsp);
			}
		}
	}

	/* map profile info stored for each node */
	if (tv[TOPOLOGY_RESPONSE_MULTIAP_PROFILE_IDX][0])
		dbg("\t agent profile %d\n", n->map_profile);

#if (EASYMESH_VERSION >= 3)
	if (tv[TOPOLOGY_RESPONSE_BSS_CONFIGURATION_REPORT_IDX][0]
			&& n->map_profile > MULTIAP_PROFILE_2) {
		struct tlv_bss_configuration_report *tlv;
		uint8_t *tv_data;
		int i, offset = 0;

		tlv = (struct tlv_bss_configuration_report *)
			tv[TOPOLOGY_RESPONSE_BSS_CONFIGURATION_REPORT_IDX][0]->data;
		if (!tlv)
			return -1;

		tv_data = (uint8_t *)tlv;

		offset += 1; /* num_radio */

		dbg("\tradios_nr: %d\n", tlv->num_radio);
		for (i = 0; i < tlv->num_radio; i++) {
			uint8_t num_bss = 0;
			struct netif_radio *r = NULL;
			struct netif_iface *p = NULL;
			int j;
			uint8_t *radio_macaddr = &tv_data[offset];

			dbg("\t\tradio_id: " MACFMT "\n", MAC2STR(radio_macaddr));
			r = cntlr_node_add_radio(c, n, &tv_data[offset]);
			if (!r)
				return -1; /* FIXME: continue + proper offset shift */

			offset += 6; /* macaddr */

			/* disable all prior stored fh/bh interfaces */
			list_for_each_entry(p, &r->iflist, list) {
				p->bss->is_fbss = false;
				p->bss->is_bbss = false;
			}
			memcpy(&num_bss, &tv_data[offset], 1);

			offset += 1; /* num_bss */

			dbg("\t\tbss_nr: %d\n", num_bss);
			for (j = 0; j < num_bss; j++) {
				struct netif_iface *fh;
				uint8_t ssidlen = 0;
				uint8_t report = 0;
				//uint32_t bss_type = MAC_ENTRY_UNKNOWN;

				dbg("\t\t\tbssid: " MACFMT "\n",
					MAC2STR(&tv_data[offset]));

				fh = cntlr_radio_add_iface(c, r, &tv_data[offset]);
				if (!fh)
					return -1; /* FIXME: continue + proper offset shift */

				offset += 6; /* bssid macaddr */

				/*Here we need to mask bitwise to get the report*/
				memcpy(&report, &tv_data[offset], 1);
				dbg("\t\t\treport: 0x%02x\n", report);
				dbg("\t\t\treport: %d\n", report);
				fh->bss->is_bbss = (report & BSS_CONFIG_BBSS) ? 1 : 0 ;
				fh->bss->is_fbss = (report & BSS_CONFIG_FBSS) ? 1 : 0 ;

				/* TODO
				if (fh->bss->is_bbss)
					bss_type |= MAC_ENTRY_BBSS;

				if (fh->bss->is_fbss)
					bss_type |= MAC_ENTRY_FBSS;

				cntlr_set_iface_type(c, fh->bss->bssid, bss_type);
				*/

				fh->bss->r1_disallowed = (report & BSS_CONFIG_R1_DISALLOWED) ? 1 : 0 ;
				fh->bss->r2_disallowed = (report & BSS_CONFIG_R2_DISALLOWED) ? 1 : 0 ;
				fh->bss->multi_bssid = (report & BSS_CONFIG_MBSSID) ? 1 : 0 ;
				fh->bss->transmitted_bssid = (report & BSS_CONFIG_TX_MBSSID) ? 1 : 0 ;
				offset += 1; /*report*/
				offset += 1; /* reserved byte*/

				ssidlen = tv_data[offset];
				if (ssidlen > 32)
					return -1;

				offset += 1; /* ssidlen */
				memset(fh->bss->ssid, 0, sizeof(fh->bss->ssid));
				fh->bss->ssidlen = ssidlen;
				memcpy(fh->bss->ssid, &tv_data[offset], fh->bss->ssidlen);
				offset += ssidlen; /* ssid */

				/* Update measurement time */
				time(&fh->bss->tsp);
			}
		}
	}
#endif

	if (tv[TOPOLOGY_RESPONSE_ASSOCIATED_CLIENTS_IDX][0]) {
		struct tlv_assoc_client *tlv = (struct tlv_assoc_client *)
				tv[TOPOLOGY_RESPONSE_ASSOCIATED_CLIENTS_IDX][0]->data;
		uint8_t *tv_data = (uint8_t *)tlv;
		int i, offset = 0;
		uint8_t stas[256 * 6];
		int max_stas = sizeof(stas) / 6;
		memset(stas, 0, sizeof(stas));
		int idx = 0;

		if (!tlv)
			return -1;

		offset += 1; /* num_bss */
		for (i = 0; i < tlv->num_bss; i++) {
			struct netif_iface *bss_iface;
			uint8_t bssid[6] = {0};
			uint16_t num_client = 0;
			int j;

			memcpy(bssid, &tv_data[offset], 6);
			offset += 6; /* bss_id */
			num_client = BUF_GET_BE16(tv_data[offset]);
			offset += 2; /* num_client */

			bss_iface = cntlr_find_bss(c, bssid);
			if (!bss_iface) {
#if (EASYMESH_VERSION >= 6)
				cntlr_dbg(LOG_STA, "%s: BSSID " MACFMT " not found as BSS" \
					  "interface (likely AP MLD MAC, %u clients)\n",
					  __func__, MAC2STR(bssid), num_client);
#else
				/* num_client * (macaddr + conntime) */
				offset += (num_client * 8);
				continue;
#endif
			}

			if (!num_client)
				continue;

			for (j = 0; j < num_client; j++) {
				uint8_t macaddr[6] = {0};
				uint16_t conntime;
				struct sta *s;

				memcpy(macaddr, &tv_data[offset], 6);
				if (idx < max_stas)
					memcpy(&stas[6*idx++], &tv_data[offset], 6);
				offset += 6; /* macaddr */
				conntime = BUF_GET_BE16(tv_data[offset]);
				offset += 2; /* conntime */

				s = cntlr_find_sta(c->sta_table, macaddr);
				if (!s) {
					struct cmdu_buff *txcmdu;

					s = cntlr_add_sta(c, c->sta_table, macaddr);
					if (!s) {
						cntlr_warn(LOG_STA, "%s: failed to add STA " MACFMT "\n",
							  __func__, MAC2STR(macaddr));
						continue;
					}

					s->state = STA_CONNECTED;
					memcpy(s->bssid, bssid, 6);
					memset(s->ssid, 0, sizeof(s->ssid));
					/* TODO: WORKAROUND: For MLO clients, the reported BSSID may be the
					 * AP MLD MAC address which won't match any BSS interface.
					 * Track the STA without SSID info until full MLO support
					 * with proper affiliated AP handling is implemented.
					 */
					if (bss_iface) {
						memcpy(s->ssid, bss_iface->bss->ssid, bss_iface->bss->ssidlen);
						s->ssidlen = bss_iface->bss->ssidlen;
					} else {
						s->ssidlen = 0;
					}
					s->de_sta->conn_time = conntime;
					s->assoc_time = time(NULL) - conntime;

					memcpy(s->de_sta->bssid, bssid, 6);
					node_add_sta(n, s);
					cntlr_info(LOG_STA, "%s: STA " MACFMT " connected to BSSID "
						   MACFMT " on Node " MACFMT "\n", __func__,
						   MAC2STR(s->macaddr), MAC2STR(bssid),
						   MAC2STR(n->almacaddr));

					txcmdu = cntlr_gen_client_caps_query(c,
								c->almacaddr,
								s->macaddr,
								s->bssid);
					if (txcmdu) {
						send_cmdu(c, txcmdu);
						cmdu_free(txcmdu);
					}
				} else {
					/* ignore conflicting STA-association from multiple Agents */
					if (memcmp(s->agent_almacaddr, n->almacaddr, 6) ||
						memcmp(s->bssid, bssid, 6)) {
						continue;
					}

					s->de_sta->conn_time = conntime;
				}

				if (bss_iface && bss_iface->bss && bss_iface->bss->is_bbss)
					s->is_bsta = true;
				else
					s->is_bsta = false;

				cntlr_update_sta_steer_data(c, s);
			}
		}
		node_update_stalist(n, stas, (idx <= max_stas ? idx : max_stas));
	}

#if 1	//debug
	{
		struct netif_radio *r = NULL;
		struct netif_iface *p = NULL;

		dbg("Node: " MACFMT "\n", MAC2STR(n->almacaddr));
		list_for_each_entry(r, &n->radiolist, list) {
			list_for_each_entry(p, &r->iflist, list) {
				dbg("BSS: " MACFMT" is_bbss = %d, is_fbss = %d\n",
				    MAC2STR(p->bss->bssid),
				    p->bss->is_bbss, p->bss->is_fbss);
			}
		}
	}
#endif	//debug

	/* Check opclass preferency age */
	if (cntlr_node_pref_opclass_expired(n, 300)) {
		cntlr_dbg(LOG_CHANNEL, "node " MACFMT " pref opclass expired\n", MAC2STR(n->almacaddr));
		cntlr_send_channel_preference_query(c, n->almacaddr);
	}

	/* When all topology responses collected, build topology tree. */
	if (!is_topology_valid(&c->topology) && c->num_nodes == topology_num_devs(&c->topology)) {

		topology_build_tree(c, &c->topology, c->almacaddr);

		/* Send BH policy only when max_node_bh_hops is configured. */
		if (c->cfg.max_node_bh_hops != 0) {
			/* Requires valid bBSS netif_iface info */
			cntlr_send_max_wifi_bh_hops_policy(c);
		}
	}

	return 0;
}

int handle_vendor_specific(void *cntlr, struct cmdu_buff *rx_cmdu,
			   struct node *n)
{
	trace("%s: --->\n", __func__);
#ifdef EASYMESH_VENDOR_EXT
	return handle_vendor_extension(cntlr, rx_cmdu, n);
#endif
	return -1;
}

static inline uint16_t c_cmdu_expect_response(uint16_t req_type)
{
	switch (req_type) {
		case CMDU_AP_CAPABILITY_QUERY:
			return CMDU_AP_CAPABILITY_REPORT;
		case CMDU_POLICY_CONFIG_REQ:
			return CMDU_1905_ACK;
		case CMDU_CHANNEL_PREFERENCE_QUERY:
			return CMDU_CHANNEL_PREFERENCE_REPORT;
		case CMDU_CHANNEL_SELECTION_REQ:
			return CMDU_CHANNEL_SELECTION_RESPONSE;
		case CMDU_OPERATING_CHANNEL_REPORT:
			return CMDU_1905_ACK;
		case CMDU_CLIENT_CAPABILITY_QUERY:
			return CMDU_CLIENT_CAPABILITY_REPORT;
		case CMDU_AP_METRICS_QUERY:
			return CMDU_AP_METRICS_RESPONSE;
		case CMDU_ASSOC_STA_LINK_METRICS_QUERY:
			return CMDU_ASSOC_STA_LINK_METRICS_RESPONSE;
		case CMDU_UNASSOC_STA_LINK_METRIC_QUERY:
			return CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE;
		case CMDU_BEACON_METRICS_QUERY:
			return CMDU_BEACON_METRICS_RESPONSE;
		case CMDU_COMBINED_INFRA_METRICS:
			return CMDU_1905_ACK;
		case CMDU_CLIENT_STEERING_REQUEST:
			// FIX THIS: we need ACK ?
			return CMDU_CLIENT_STEERING_BTM_REPORT;
		case CMDU_CLIENT_ASSOC_CONTROL_REQUEST:
			return CMDU_1905_ACK;
		case CMDU_STEERING_COMPLETED:
			return CMDU_1905_ACK;
		case CMDU_HIGHER_LAYER_DATA:
			return CMDU_1905_ACK;
		case CMDU_BACKHAUL_STEER_REQUEST:
			return CMDU_BACKHAUL_STEER_RESPONSE;
		case CMDU_CHANNEL_SCAN_REQUEST:
			return CMDU_CHANNEL_SCAN_REPORT;
		case CMDU_CAC_REQUEST:
			return CMDU_TYPE_NONE;
		case CMDU_CAC_TERMINATION:
			return CMDU_TYPE_NONE;
		case CMDU_CLIENT_DISASSOCIATION_STATS:
			return CMDU_TYPE_NONE;
		case CMDU_ERROR_RESPONSE:
			return CMDU_TYPE_NONE;
		case CMDU_ASSOCIATION_STATUS_NOTIFICATION:
			return CMDU_TYPE_NONE;
		case CMDU_BACKHAUL_STA_CAPABILITY_QUERY:
			return CMDU_BACKHAUL_STA_CAPABILITY_REPORT;
		case CMDU_FAILED_CONNECTION:
			return CMDU_TYPE_NONE;
#if (EASYMESH_VERSION > 2)
		case CMDU_BSS_CONFIG_RESPONSE:
			return CMDU_BSS_CONFIG_RESULT;
#endif
		default:
			break;
	}

	return CMDU_TYPE_NONE;
}

static uint16_t cntlr_cmdu_expect_response(struct controller *c, uint16_t req_type)
{
	uint16_t resp_type = c_cmdu_expect_response(req_type);

	if (resp_type == CMDU_TYPE_NONE)
		return CMDU_TYPE_NONE;

	if (map_cmdu_mask_isset(c->cmdu_mask, resp_type))
		return resp_type;
	else
		return CMDU_TYPE_NONE;
}

void send_cmdu_cb(struct ubus_request *req, int type, struct blob_attr *msg)
{
	struct json_object *jobj = NULL;
	struct json_object *tmp;
	uint16_t *mid;
	char *str;

	if (!msg || !req->priv) {
		err("%s:Message received is NULL\n", __func__);
		return;
	}

	mid = (uint16_t *)req->priv;

	str = (char *)blobmsg_format_json_indent(msg, true, -1);
	if (str) {
		jobj = json_tokener_parse(str);
		free(str);
	}

	if (jobj == NULL)
		return;

	if (json_object_object_get_ex(jobj, "mid", &tmp)) {
		*mid = json_object_get_int(tmp);
		dbg("%s: cntlr map-mid:%d\n", __func__, *mid); // typo ||
	}

	json_object_put(jobj);
}

int send_cmdu_ubus(struct controller *c, struct cmdu_buff *cmdu)
{
	struct blob_buf b = { 0 };
	char dst_addr[18] = { 0 };
	uint16_t msgid = 0;
	int ret = 0;
	uint32_t id;

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

	memset(&b, 0, sizeof(struct blob_buf));
	blob_buf_init(&b, 0);

	blobmsg_add_u32(&b, "type", cmdu_get_type(cmdu));

	hwaddr_ntoa(cmdu->origin, dst_addr);
	blobmsg_add_string(&b, "dst", dst_addr);

	blobmsg_add_u32(&b, "mid", (uint32_t)cmdu_get_mid(cmdu));

	if (c->cfg.enable_ts && is_vid_valid(c->cfg.primary_vid))
		blobmsg_add_u32(&b, "vid", (uint32_t)c->cfg.primary_vid);


	trace("|%s:%d|cmdu:0x%04x|dst:%s|mid:%u|fid:%u|vid:%u|\n", __func__, __LINE__,
			cmdu_get_type(cmdu), dst_addr, cmdu_get_mid(cmdu),
			cmdu_get_fid(cmdu), c->cfg.primary_vid);

	if (cmdu->datalen) {
		char *tlv_str = NULL;
		uint16_t len = 0;

		len = (cmdu->datalen * 2) + 1;
		tlv_str = (char *)calloc(len, sizeof(char));
		if (!tlv_str)
			goto out;

		btostr(cmdu->data, cmdu->datalen, tlv_str);
		tlv_str[len-1] = '\0';
		blobmsg_add_string(&b, "data", tlv_str);
		trace("|%s:%d|data:%s|\n", __func__, __LINE__, tlv_str);
		free(tlv_str);
	}

	if (ubus_lookup_id(c->ubus_ctx, "ieee1905", &id)) {
		dbg("%s: not present ieee1905", __func__);
		goto out;
	}

	ret = ubus_invoke(c->ubus_ctx, id, "cmdu",
				b.head, send_cmdu_cb,
				&msgid,
				1000);
	if (ret) {
		dbg("%s: ubus call failed for |ieee1905 send|", __func__);
		goto out;
	}

	trace("|%s:%d| msgid = %d\n", __func__, __LINE__, msgid);
	blob_buf_free(&b);
	return msgid;

out:
	blob_buf_free(&b);
	return -1;
}

int send_frag_scheme_ubus(struct controller *c, uint8_t *origin, int frag_scheme)
{
	struct blob_buf b = { 0 };
	char dst_addr[18] = { 0 };
	uint16_t msgid = 0;
	int ret = 0;
	uint32_t id;

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

	memset(&b, 0, sizeof(struct blob_buf));
	blob_buf_init(&b, 0);

	hwaddr_ntoa(origin, dst_addr);
	blobmsg_add_string(&b, "dst", dst_addr);
	blobmsg_add_u32(&b, "mode", (uint32_t)frag_scheme);

	if (ubus_lookup_id(c->ubus_ctx, "ieee1905", &id)) {
		dbg("%s: not present ieee1905", __func__);
		goto out;
	}

	ret = ubus_invoke(c->ubus_ctx, id, "frag_scheme",
	                  b.head, NULL,
	                  &msgid,
	                  1000);
	if (ret) {
		warn("%s: ubus call failed for |ieee1905 send|", __func__);
		goto out;
	}

	trace("|%s:%d| msgid = %d\n", __func__, __LINE__, msgid);
	blob_buf_free(&b);
	return msgid;

out:
	blob_buf_free(&b);
	return -1;
}

uint16_t send_cmdu(struct controller *a, struct cmdu_buff *cmdu)
{
	uint16_t resp_type;
	int ret;
	void *cookie = NULL;
	const int resend_num = a->cfg.resend_num;
	uint16_t msgid, old_mid;

	if (hwaddr_is_ucast(cmdu->origin)) {
		resp_type = cntlr_cmdu_expect_response(a, cmdu_get_type(cmdu));
		if (resp_type != CMDU_TYPE_NONE)
			cookie = cmdu_clone(cmdu);
	}

	if (cmdu->datalen >= FRAG_DATA_SIZE) {
		struct node *n = NULL;
		int frag_scheme = CMDU_FRAG_SCHEME_BOUNDARY_TLV;

		n = cntlr_find_node(a, cmdu->origin);
		if (n != NULL) {
			switch (n->map_profile) {
				case 3:
				case 4:
				case 5:
				case 6:
					frag_scheme = CMDU_FRAG_SCHEME_BOUNDARY_OCTET;
					break;
				case 0:
				case 1:
				case 2:
				default:
					frag_scheme = CMDU_FRAG_SCHEME_BOUNDARY_TLV;
					break;
			}
		}

		send_frag_scheme_ubus(a, cmdu->origin, frag_scheme);
	}

	ret = send_cmdu_ubus(a, cmdu);
	if (ret < 0) {
		dbg("fail to send cmdu %04x over ubus\n", cmdu_get_type(cmdu));
		goto error;
	}

	msgid = ret;

	old_mid = cmdu_get_mid(cmdu);
	if (old_mid == 0)
		cmdu_set_mid(cmdu, msgid);
	else if (old_mid != msgid)
		dbg("msgid differs %d %d for cmdu %04x\n", old_mid, msgid,
		    cmdu_get_type(cmdu));

	if (cookie) {
		ret = cmdu_ackq_enqueue(&a->cmdu_ack_q, resp_type,
					cmdu_get_mid(cmdu), cmdu->origin,
					CMDU_DEFAULT_TIMEOUT, resend_num,
					cookie);
		if (ret < 0) {
			err("cmdu_ackq enqueue failed\n");
			goto error;
		}
	}

	return msgid;

error:
	cmdu_free((struct cmdu_buff *) cookie);
	return 0xffff;
}


static int handle_supported_service(struct controller *c, uint8_t *almacaddr, struct tlv *t)
{
	struct tlv_supported_service *ss;
	int i;

	if (!t)
		return -1;

	ss = (struct tlv_supported_service *) t->data;

	trace("%s: Node " MACFMT " supports %d service(s)\n",
		  __func__, MAC2STR(almacaddr), ss->num_services);

	/* if supports agent, add to config */
	for (i = 0; i < ss->num_services; i++) {
		trace("%s: Supported Service (%d): 0x%02x\n",
		      __func__, (i+1), ss->services[i]);
		if (ss->services[i] == SUPPORTED_SERVICE_MULTIAP_AGENT) {
			if (c->state == CNTLR_INIT) {
				dbg("%s: Discard ap-autoconfig search from" \
				    " agent during INIT phase\n", __func__);
				return -1;
			}
		} else if (ss->services[i] == SUPPORTED_SERVICE_MULTIAP_CONTROLLER) {
			if (!memcmp(almacaddr, c->almacaddr, 6)) {
				dbg("%s: Discard ap-autoconfig search from self\n", __func__);
				return -1;
			}

			if (c->state == CNTLR_INIT) {
				uint32_t uci_obj;
				int res;

				trace("Disable and exit\n");
				/* disable using UCI to avoid init.d reported issues */
				/* TODO: remove UCI commit invokes */
				set_value_by_string("mapcontroller",
						"controller",
						"enabled", "0",
						UCI_TYPE_STRING);

				res = ubus_lookup_id(c->ubus_ctx, "uci", &uci_obj);
				if (!res) {
					struct blob_buf bb = {0};

					/* trigger commit in order to reload mapcontroller
					* and not cause crash reports by procd
					*/

					blob_buf_init(&bb, 0);
					blobmsg_add_string(&bb, "config",
							"mapcontroller");
					res = ubus_invoke(c->ubus_ctx, uci_obj,
							"commit", bb.head,
							NULL, NULL,
							2 * 1000);
					if (res)
						err("%s: Failed to get 'commit' (ret = %d), exit anyway\n",
						    __func__, res);
					blob_buf_free(&bb);
				}

				exit(0);
			} else {
				char data[128] = {0};

				snprintf(data, sizeof(data), "{\"remote_almac\":\""MACFMT"\"}", MAC2STR(almacaddr));
				cntlr_notify_event(c, CNTLR_EVENT_MULTIPLE_CNTLR, data);
			}
		} else {
			dbg("%s: Invalid Supported Service, return!\n", __func__);
			return -1;
		}
	}

	return 0;
}

int handle_ap_autoconfig_search(void *cntlr, struct cmdu_buff *rx_cmdu,
				struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	struct tlv_autoconfig_band *freq;
	struct tlv_aladdr *aladdr;
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	void *event = NULL;
	bool hash_known = false;
	uint8_t hashlen;
	uint8_t *hash;
#endif
	bool hash_validity;
#endif
	struct tlv *tv[7][TLV_MAXNUM] = {0};
	uint8_t almacaddr[6] = {0};
	char freq_band[8] = {0};
	struct cmdu_buff *cmdu;
	struct tlv *t;
	int ret = 0;

	if (cmdu_get_mid(rx_cmdu) == c->mid_5g ||
	    cmdu_get_mid(rx_cmdu) == c->mid_2g) {
		cntlr_dbg(LOG_APCFG, "%s: Ignore AP-autoconfig search from self\n",
			  __func__);
		return -1;
	}

	t = map_cmdu_get_tlv(rx_cmdu, TLV_TYPE_AL_MAC_ADDRESS_TYPE);
	if (!t) {
		cntlr_dbg(LOG_APCFG,
			  "%s: Ignore AP-autoconfig search missing AL-macaddress TLV\n",
			  __func__);
		return -1;
	}

	aladdr = (struct tlv_aladdr *) t->data;
	memcpy(almacaddr, aladdr->macaddr, 6);
	if (hwaddr_is_zero(almacaddr)) {
		cntlr_dbg(LOG_APCFG,
			  "%s: Ignore AP-autoconfig search with AL-macaddress = 0\n",
			  __func__);
		return -1;
	}

	n = cntlr_add_node(c, almacaddr);
	if (!n) {
		cntlr_err(LOG_APCFG, "%s: node allocation for "MACFMT" failed!\n",
			  __func__, MAC2STR(almacaddr));
		return -1;
	}

	cntlr_set_link_profile(c, n, rx_cmdu);

	if (!map_cmdu_validate_parse(rx_cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	if (tv[AP_AUTOCONFIGURATION_SEARCH_SEARCHED_ROLE_IDX][0]->data[0]
			!= IEEE80211_ROLE_REGISTRAR) {
		cntlr_dbg(LOG_APCFG,
			  "%s: Ignore AP-autoconfig search for role != registrar\n",
			  __func__);
		return -1;
	}

	freq = (struct tlv_autoconfig_band *)
		tv[AP_AUTOCONFIGURATION_SEARCH_AUTOCONFIG_FREQ_BAND_IDX][0]->data;
	if (freq->band != IEEE80211_FREQUENCY_BAND_2_4_GHZ &&
	    freq->band != IEEE80211_FREQUENCY_BAND_5_GHZ &&
	    freq->band != IEEE80211_FREQUENCY_BAND_6_GHZ) {
		cntlr_dbg(LOG_APCFG,
			  "%s: Ignore AP-autoconfig search for unsupported WiFi band %d\n",
			  __func__, freq->band);
		return -1;
	}

	if (freq->band == IEEE80211_FREQUENCY_BAND_2_4_GHZ) {
		if (!c->cfg.has_registrar_2g)
			return -1;
	} else if (freq->band == IEEE80211_FREQUENCY_BAND_5_GHZ) {
		if (!c->cfg.has_registrar_5g)
			return -1;
	} else if (freq->band == IEEE80211_FREQUENCY_BAND_6_GHZ) {
		if (!c->cfg.has_registrar_6g)
			return -1;
	} else
		return -1;

	switch (freq->band) {
	case IEEE80211_FREQUENCY_BAND_2_4_GHZ:
		sprintf(freq_band, "%d", 2);
		break;
	case IEEE80211_FREQUENCY_BAND_5_GHZ:
		sprintf(freq_band, "%d", 5);
		break;
	case IEEE80211_FREQUENCY_BAND_6_GHZ:
		sprintf(freq_band, "%d", 6);
		break;
	default:
		return -1;
	}

	/* SupportedService TLV */
	if (tv[AP_AUTOCONFIGURATION_SEARCH_SUPPORTED_SERVICE_IDX][0]) {
		handle_supported_service(c, almacaddr,
				tv[AP_AUTOCONFIGURATION_SEARCH_SUPPORTED_SERVICE_IDX][0]);
	}

#if (EASYMESH_VERSION >= 3)
	if (tv[AP_AUTOCONFIGURATION_SEARCH_DPP_CHIRP_VALUE_IDX][0]
			&& c->cfg.map_profile >= 3) {
		int offset = 0;
		uint8_t flag = 0;
		bool mac_present;
		uint8_t *mac;

		flag = tv[AP_AUTOCONFIGURATION_SEARCH_DPP_CHIRP_VALUE_IDX][0]->data[offset++];

		mac_present = (flag & DPP_CHIRP_ENROLLEE_MAC_PRESENT);
		hash_validity = (flag & DPP_CHIRP_HASH_VALIDITY);
		UNUSED(hash_validity);

		if (mac_present) {
			mac = &tv[AP_AUTOCONFIGURATION_SEARCH_DPP_CHIRP_VALUE_IDX][0]->data[offset];
			UNUSED(mac);
			offset += 6;
		}

#ifdef USE_LIBDPP
		hashlen = tv[AP_AUTOCONFIGURATION_SEARCH_DPP_CHIRP_VALUE_IDX][0]->data[offset++];
		hash = &tv[AP_AUTOCONFIGURATION_SEARCH_DPP_CHIRP_VALUE_IDX][0]->data[offset];
		hash_known = dpp_is_peer_bootstrap_hash_known(c->dpp, hash, NULL);

#ifdef DEBUG
		dump(hash, hashlen, "AP-autoconfig search chirp hash");
#endif /* DEBUG */

		if (hash_known) {
			cntlr_dbg(LOG_DPP, "%s: Hash of DPP peer is known!\n", __func__);
			ret = dpp_process_presence_announcement(c->dpp,
								rx_cmdu->origin,
								hash, hashlen, NULL);
			if (!ret) {
				event = dpp_sm_create_event(c->dpp, rx_cmdu->origin,
								DPP_EVENT_RX_FRAME,
								0, NULL);
			} else {
				cntlr_warn(LOG_DPP,
					  "%s: Failed to process virt presence announcement!\n",
					  __func__);
			}
		}
#endif
	}
#endif

	cntlr_dbg(LOG_APCFG, "%s: sending AP-autoconfig response for band = %d to node %p\n",
		  __func__, freq->band, n);

	cmdu = cntlr_gen_ap_autoconfig_response(cntlr,
#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
			(hash_known ? hash_validity : 0),
			(hash_known ? hash : NULL),
			(hash_known ? hashlen : 0),
#endif
#endif
			almacaddr,
			freq->band,
			cmdu_get_mid(rx_cmdu));
	if (!cmdu)
		return -1;

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

#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
	if (event) {
		cntlr_dbg(LOG_DPP, "%s: Trigger DPP event\n", __func__);
		dpp_trigger(c->dpp, rx_cmdu->origin, event);
	}
#endif
#endif

	return !!ret;
}

/* disable and quit on controller response */
int handle_ap_autoconfig_response(void *cntlr, struct cmdu_buff *rx_cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);

	struct tlv *tv[AP_AUTOCONFIGURATION_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct controller *c = (struct controller *)cntlr;
	bool has_cntlr = false;
	int self_response;


	self_response = !memcmp(rx_cmdu->origin, c->almacaddr, 6);

	cntlr_set_link_profile(c, n, rx_cmdu);

	if (!map_cmdu_validate_parse(rx_cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	if (tv[AP_AUTOCONFIGURATION_RESP_SUPPORTED_SERVICE_IDX][0]) {
		int i;

		for (i = 0; i < tv[AP_AUTOCONFIGURATION_RESP_SUPPORTED_SERVICE_IDX][0]->data[0]; i++) {
			if (tv[AP_AUTOCONFIGURATION_RESP_SUPPORTED_SERVICE_IDX][0]->data[(i+1)] ==
					SUPPORTED_SERVICE_MULTIAP_CONTROLLER) {
				has_cntlr = true;
				break;
			}
		}
	}

	/* if does not support controller - return */
	if (!has_cntlr) {
		cntlr_dbg(LOG_APCFG, "Ignore AP-autoconfig response from non-controller device\n");
		return -1;
	}


	/* discard self response */
	if (self_response)
		return 0;

	cntlr_dbg(LOG_APCFG, "%s: Received AP-autoconfig response from remote supporting EM-profile %d\n",
		  __func__, n->map_profile);

	if (c->state == CNTLR_INIT) {
		uint32_t uci_obj;
		int res;

		cntlr_info(LOG_APCFG, "%s: Disable and exit\n", __func__);
		set_value_by_string("mapcontroller", "controller", "enabled", "0",
				UCI_TYPE_STRING);
		res = ubus_lookup_id(c->ubus_ctx, "uci", &uci_obj);
		if (!res) {
			struct blob_buf bb = {0};

			/* trigger commit in order to reload mapcontroller
			 * and not cause crash reports by procd
			 */

			blob_buf_init(&bb, 0);
			blobmsg_add_string(&bb, "config",
					"mapcontroller");
			res = ubus_invoke(c->ubus_ctx, uci_obj,
					"commit", bb.head,
					NULL, NULL,
					2 * 1000);
			if (res) {
				cntlr_err(LOG_APCFG,
					  "%s: Failed to 'ubus uci commit {mapcontroller}' (ret = %d), "
					  "exit anyway\n", __func__, res);
			}
			blob_buf_free(&bb);
		}

		exit(0);
	} else {
		char data[128] = {0};

		snprintf(data, sizeof(data), "{\"remote_almac\":\""MACFMT"\"}", MAC2STR(rx_cmdu->origin));
		cntlr_notify_event(c, CNTLR_EVENT_MULTIPLE_CNTLR, data);
	}

	return 0;
}

int handle_ap_autoconfig_wsc(void *cntlr, struct cmdu_buff *rx_cmdu,
			     struct node *n)
{
	trace("%s: --->\n", __func__);

	struct tlv *tv[AP_AUTOCONFIGURATION_WSC_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	uint8_t wildcard[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	struct controller *c = (struct controller *)cntlr;
	struct tlv_ap_radio_basic_cap *ap_caps;
	struct node_policy *np;
	struct cmdu_buff *cmdu;
	struct netif_radio *r;
	uint8_t wsc_mtype = 0;
	char ev_data[256] = {0};
	struct tlv *wsc_tlv;
	uint8_t band = 0;

	/* Peek at WSC TLV to check message type before parsing */
	wsc_tlv = cmdu_peek_tlv(rx_cmdu, TLV_TYPE_WSC);
	if (!wsc_tlv) {
		cntlr_dbg(LOG_APCFG, "%s: No WSC TLV found\n", __func__);
		return -1;
	}

	wsc_mtype = wsc_get_message_type(wsc_tlv->data, tlv_length(wsc_tlv));
	if (wsc_mtype != WPS_M1) {
		cntlr_dbg(LOG_APCFG, "%s: Ignore WSC msg not M1\n", __func__);
		return -1;
	}

	if (!map_cmdu_validate_parse(rx_cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	ap_caps = (struct tlv_ap_radio_basic_cap *)
		tv[AP_AUTOCONFIGURATION_WSC_AP_RADIO_BASIC_CAPS_IDX][0]->data;

	cntlr_dbg(LOG_APCFG, "%s: prepare AP-autoconfig WSC response\n", __func__);
	cmdu = cntlr_gen_ap_autoconfig_wsc(cntlr, n, rx_cmdu, ap_caps->radio,
					   tv[AP_AUTOCONFIGURATION_WSC_WSC_IDX][0],
					   cmdu_get_mid(rx_cmdu));
	if (!cmdu)
		return -1;

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

	r = cntlr_find_radio(c, ap_caps->radio);
	if (r)
		band = wifi_band_to_freqband(r->radio_el->band);
	snprintf(ev_data, sizeof(ev_data), "{ \"agent\": \"" MACFMT "\", "
			"\"radio\":\"" MACFMT "\", " "\"band\": %u}",
			MAC2STR(n->almacaddr), MAC2STR(ap_caps->radio), band);
	cntlr_notify_event(c, CNTLR_EVENT_AUTOCONFIGURED, ev_data);

	n->last_config = c->cfg.last_change;

	np = cntlr_get_node_policy(&c->cfg, rx_cmdu->origin);
	if (!np) {
		cntlr_warn(LOG_APCFG, "%s: missing policy for node " MACFMT
			  "not sending policy config request\n", __func__,
			  MAC2STR(rx_cmdu->origin));
		return -1;
	}

	cntlr_dbg(LOG_APCFG, "%s: sending policy config request\n", __func__);
	cmdu = cntlr_gen_policy_config_req(cntlr, rx_cmdu->origin, np, 1,
			ap_caps->radio, 1, wildcard);
	if (!cmdu)
		return -1;

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

	return 0;
}

#define REASON_STA_ASSOC_BSS 0x01
int handle_1905_ack(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	struct tlv *tv[ACK_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	uint16_t mid = cmdu_get_mid(cmdu);
	int idx;

	trace("parsing 1905 ack |" MACFMT "|\n", MAC2STR(cmdu->origin));

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	idx = 0;
	while (idx < TLV_MAXNUM && tv[ACK_ERROR_CODE_IDX][idx]) {
		struct tlv_error_code *data;
		struct tlv *t = (struct tlv *)tv[ACK_ERROR_CODE_IDX][idx++];
		struct sta *s;


		data = (struct tlv_error_code *)t->data;

		/* Update failed steer attempts in case one tries to
		 * assoc control STA that has been associated.
		 */
		if (data->reason == REASON_STA_ASSOC_BSS) {
			s = cntlr_find_sta(c->sta_table, data->macaddr);
			if (!s)
				continue;

			dbg("%s: cmdu->cdata->hdr.mid %u\n", __func__, mid);

			if (s->latest_assoc_cntrl_mid == mid) {
				//s->de_sta->mapsta.failed_steer_attempts++;
				cntlr_notify_client_steer_result(c, s->de_sta->macaddr,
						STEER_RESULT_FAIL_ASOC_CTRL);
			}
		}
	}

#if (EASYMESH_VERSION >= 6)
	if (mid == n->apmldconf_mid)
		timestamp_update(&n->last_apmld_ack);
	else if (mid == n->bstamldconf_mid)
		timestamp_update(&n->last_bstamld_ack);
#endif

	return 0;
}


static uint32_t get_duration_data(const uint8_t *buf)
{
	uint32_t val = 0;

	val = buf[2];
	val |= (buf[1] << 8);
	val |= (buf[0] << 16);

	return val;
}

static uint32_t get_supp_methods(uint8_t method)
{
	switch (method) {
	case CAC_METHOD_CONTINUOUS_CAC:
		return 1 << WIFI_CAC_CONTINUOUS;
	case CAC_METHOD_DEDICATED_RADIO:
		return 1 << WIFI_CAC_DEDICATED;
	case CAC_METHOD_MIMO_DIM_REDUCED:
		return 1 << WIFI_CAC_MIMO_REDUCED;
	case CAC_METHOD_TIME_SLICED:
		return 1 << WIFI_CAC_TIME_SLICED;
	default:
		break;
	}

	return 0;
}

static int cntlr_parse_radio_cac_caps(struct controller *c, struct node *n, struct tlv *t)
{
	struct tlv_cac_cap *p = (struct tlv_cac_cap *) t->data;
	struct wifi_radio_opclass *opclass;
	struct wifi_radio_element *radio_el;
	struct netif_radio *nr;
	int i, j, h, k;
	int offset;

	offset = sizeof(*p);
	for (h = 0; h < p->num_radio; h++) {
		struct cac_cap_radio *r =
				(struct cac_cap_radio *)&t->data[offset];
		offset += sizeof(*r);

		nr = cntlr_find_radio(c, r->radio);
		if (!nr)
			continue;

		radio_el = nr->radio_el;
		opclass = &radio_el->supp_opclass;

		for (i = 0; i < r->num_cac; i++) {
			struct cac_cap_cac *cac =
				(struct cac_cap_cac *)&t->data[offset];
			offset += sizeof(*cac);
			for (j = 0; j < cac->num_opclass; j++) {
				struct cac_cap_opclass *o =
					(struct cac_cap_opclass *)&t->data[offset];
				offset += 2 + o->num_channel;

				for (k = 0; k < o->num_channel; k++) {
					struct wifi_radio_opclass_channel *chan;

					chan = wifi_opclass_get_channel(opclass, o->classid, o->channel[k]);
					if (WARN_ON(!chan))
						continue;

					chan->cac_methods |= get_supp_methods(cac->supp_method);
					chan->cac_time = get_duration_data(cac->duration);

					if (chan->cac_methods && chan->cac_methods != (1 << WIFI_CAC_CONTINUOUS))
						radio_el->bgcac_supported =  true;
				}
			}
		}
	}

	return 0;
}

int handle_ap_caps_report(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);

	int index = 0;
	int i = 0;
	int offset = 0;
	struct controller *c = (struct controller *) cntlr;
	struct tlv *tv[AP_CAPABILITY_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

	trace("%s: parsing AP capabilities of |" MACFMT "|\n",
	      __func__, MAC2STR(cmdu->origin));

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}
	/*
	 * todo: handle tlv[9] .. [12]
	 * 	MAP_TLV_AP_WIFI6_CAPS, MAP_TLV_1905_SECURITY_CAPS, MAP_TLV_DEVICE_INVENTORY,
	 * 	and MAP_TLV_AP_RADIO_ADV_CAPABILITY
	 *
	 *  debug_ap_caps_report() does this.
	 */

	/* AP Capability TLV */
	if (tv[AP_CAPABILITY_REPORT_AP_CAPABILITY_IDX][0]) {
		struct tlv_ap_cap *p =
			(struct tlv_ap_cap *)tv[AP_CAPABILITY_REPORT_AP_CAPABILITY_IDX][0]->data;

		dbg("%s %d AP capability is 0x%02x\n", __func__, __LINE__, p->cap);
		n->ap_cap = p->cap;
	}

	index = 0;
	/* Parse AP Radio Basic Capabilities TLV */
	while (index < TLV_MAXNUM && tv[AP_CAPABILITY_REPORT_AP_RADIO_BASIC_CAPS_IDX][index]) {
		struct wifi_radio_opclass e4 = {};
		struct wifi_radio_opclass *opclass;
		struct netif_radio *r;
		struct wifi_radio_element *re;
		uint8_t *tv_data =
			(uint8_t *)tv[AP_CAPABILITY_REPORT_AP_RADIO_BASIC_CAPS_IDX][index++]->data;
		struct tlv_ap_radio_basic_cap *p =
			(struct tlv_ap_radio_basic_cap *)tv_data;
		int j, k;

		r = cntlr_find_radio(c, p->radio);
		if (!r)
			continue;

		re = r->radio_el;
		opclass = &re->supp_opclass;

		wifi_opclass_e4(&e4);
		wifi_opclass_reset(opclass);

		offset = sizeof(*p);

		/* k */
		for (i = 0; i < p->num_opclass; i++) {
			struct ap_radio_basic_cap_opclass *op =
				(struct ap_radio_basic_cap_opclass *)&tv_data[offset];
			struct wifi_radio_opclass_entry *e4_entry;
			struct wifi_radio_opclass_entry *entry;
			struct wifi_radio_opclass_channel *channel;

			e4_entry = wifi_opclass_find_entry(&e4, op->classid);
			if (!e4_entry)
				continue;

			/* m  == 0 - all channels supported */
			if (op->num_nonop_channel == 0) {
				wifi_opclass_add_entry(opclass, e4_entry);
				wifi_opclass_id_set_preferences(opclass, e4_entry->id, WIFI_RADIO_OPCLASS_MOST_PREFERRED);
				offset += sizeof(*op);
				continue;
			}

			/* Create new entry */
			entry = wifi_opclass_new_entry(opclass);
			if (!entry)
				continue;

			entry->id = e4_entry->id;
			entry->bandwidth = e4_entry->bandwidth;
			entry->band = e4_entry->band;
			entry->max_txpower = op->max_txpower;

			for (j = 0; j < e4_entry->num_channel; j++) {
				channel = &e4_entry->channel[j];
				for (k = 0; k < op->num_nonop_channel; k++) {
					if (channel->channel == op->nonop_channel[k])
						break;
				}

				if (k != op->num_nonop_channel)
					channel->preference = WIFI_RADIO_OPCLASS_NON_OPERABLE;
				else
					channel->preference = WIFI_RADIO_OPCLASS_MOST_PREFERRED;

				wifi_opclass_add_channel(entry, channel);
			}

			offset += sizeof(*op) + op->num_nonop_channel;
		}

		wifi_opclass_dump(opclass, "dev_supp_opclass", re->macaddr);
		cntlr_acs_dev_supp_opclass(n, r);
	}


	index = 0;
	/* AP HT Capabilities TLV */
	while (index < TLV_MAXNUM && tv[AP_CAPABILITY_REPORT_AP_HT_CAPS_IDX][index]) {
		struct netif_radio *r;
		uint8_t *tv_data = (uint8_t *)tv[AP_CAPABILITY_REPORT_AP_HT_CAPS_IDX][index++]->data;
		struct tlv_ap_ht_cap *ht_caps =
			(struct tlv_ap_ht_cap *)tv_data;

		r = cntlr_find_radio(c, ht_caps->radio);
		if (!r)
			continue;

		r->radio_el->caps.ht = ht_caps->cap;
	}

	index = 0;
	/* AP VHT Capabilities TLV */
	while (index < TLV_MAXNUM && tv[AP_CAPABILITY_REPORT_AP_VHT_CAPS_IDX][index]) {
		struct netif_radio *r;
		uint8_t *tv_data = (uint8_t *)tv[AP_CAPABILITY_REPORT_AP_VHT_CAPS_IDX][index++]->data;
		struct tlv_ap_vht_cap *vht_caps =
			(struct tlv_ap_vht_cap *)tv_data;

		r = cntlr_find_radio(c, vht_caps->radio);
		if (!r)
			continue;

		/* TODO: update when caps.vht is defined as separate fields */
		memcpy(r->radio_el->caps.vht, tv_data + sizeof(vht_caps->radio),
		       sizeof(r->radio_el->caps.vht));
	}

	offset = 0;
	index = 0;
	/* AP HE Capabilities TLV */
	while (index < TLV_MAXNUM && tv[AP_CAPABILITY_REPORT_AP_HE_CAPS_IDX][index]) {
		struct netif_radio *r;
		uint8_t *tv_data =
			(uint8_t *)tv[AP_CAPABILITY_REPORT_AP_HE_CAPS_IDX][index++]->data;
		struct tlv_ap_he_cap *he_caps =
			(struct tlv_ap_he_cap *)tv_data;

		r = cntlr_find_radio(c, he_caps->radio);
		if (!r)
			continue;

		offset += sizeof(he_caps->radio);

		/* TODO: update when caps.he is defined as separate fields */
		r->radio_el->caps.he[0] = he_caps->hemcs.len;

		offset += sizeof(he_caps->hemcs.len);

		memcpy(&(r->radio_el->caps.he[1]), tv_data + offset, he_caps->hemcs.len);

		offset += he_caps->hemcs.len;

		memcpy(&(r->radio_el->caps.he[1 + he_caps->hemcs.len]), tv_data + offset, 2);
	}

	/* Channel scan capabilities TLV */
	if (tv[AP_CAPABILITY_REPORT_CHANNEL_SCAN_CAPABILITY_IDX][0])
		cntlr_parse_radio_scan_caps(c, n,
				tv[AP_CAPABILITY_REPORT_CHANNEL_SCAN_CAPABILITY_IDX][0]);

	/* CAC capabilities */
	if (tv[AP_CAPABILITY_REPORT_CAC_CAPABILITY_IDX][0])
		cntlr_parse_radio_cac_caps(c, n, tv[AP_CAPABILITY_REPORT_CAC_CAPABILITY_IDX][0]);

	return 0;
}

int handle_channel_pref_report(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);

	int idx, offset = 0;
	int i, j;
	struct tlv *tv[CHANNEL_PREF_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	struct netif_radio *r;

	if(!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n", __func__,
		     map_error, map_strerror(map_error));
		return -1;
	}

	idx = 0;
	while (idx < TLV_MAXNUM && tv[CHANNEL_PREF_REPORT_CHANNEL_PREFERENCE_IDX][idx]) {
		struct tlv *t = (struct tlv *)tv[CHANNEL_PREF_REPORT_CHANNEL_PREFERENCE_IDX][idx++];
		uint8_t mac[6] = { 0 };
		int num_opclass;

		offset = 0;
		memcpy(mac, &t->data[offset], 6);
		offset += 6;
		num_opclass = t->data[offset++];

		r = cntlr_find_radio(cntlr, mac);
		if (!r)
			continue;

		if (!cntlr_radio_pref_opclass_reset(r->radio_el))
			continue;

		for (i = 0; i < num_opclass; i++) {
			uint8_t opclass;
			uint8_t num_channel;
			uint8_t preference;
			uint8_t channel;

			opclass = t->data[offset++];
			num_channel = t->data[offset++];		/* k */
			preference = t->data[offset + num_channel];

			if (num_channel == 0) {
				/* k == 0 */
				cntlr_radio_pref_opclass_set_pref(r->radio_el, opclass, preference);
			} else {
				for (j = 0; j < num_channel; j++) {
					channel = t->data[offset++];
					if (cntlr_radio_pref_opclass_add(r->radio_el, opclass, channel, preference))
						warn("opclass_add %u %u %u failed\n", opclass, channel, preference);
				}
			}

			/* last preference - simple move offset */
			offset++;

		}

		cntlr_radio_pref_opclass_dump(r->radio_el);

		/* Kick ACS code - for 5GHz we will send from CAC status */
		if (r->radio_el->band != BAND_5)
			cntlr_acs_channel_pref_report(n, r);
	}

	idx = 0;
	while (tv[CHANNEL_PREF_REPORT_CAC_COMPLETION_REPORT_IDX][idx]) {
		struct tlv *t = (struct tlv *)tv[CHANNEL_PREF_REPORT_CAC_COMPLETION_REPORT_IDX][idx++];
		uint8_t num_radio;
		uint8_t num_pairs;

		offset = 0;
		num_radio = t->data[offset++];

		for (i = 0; i < num_radio; i++) {
			uint8_t mac[6] = { 0 };
			uint8_t opclass, channel, status;

			memcpy(mac, &t->data[offset], 6);
			offset += 6;

			opclass = t->data[offset++];
			channel = t->data[offset++];
			status = t->data[offset++];

			num_pairs = t->data[offset++];
			for (j = 0; j < num_pairs; j++)
				offset += 2;

			r = cntlr_find_radio(cntlr, mac);
			if (!r)
				continue;

			/* Kick ACS code */
			cntlr_acs_cac_completion(r, opclass, channel, status);
		}

		break;
	}

	idx = 0;
	while (tv[CHANNEL_PREF_REPORT_CAC_STATUS_REPORT_IDX][idx]) {
		struct tlv *t = (struct tlv *)tv[CHANNEL_PREF_REPORT_CAC_STATUS_REPORT_IDX][idx++];
		uint8_t channel, opclassid;
		uint16_t time;
		uint8_t num;

		r = cntlr_find_radio_in_node_by_band(n->cntlr, n, BAND_5);
		if (!r)
			break;

		offset = 0;

		/* CAC completed */
		num = t->data[offset++];
		for (i = 0; i < num; i++) {
			opclassid = t->data[offset++];
			channel = t->data[offset++];
			time = BUF_GET_BE16(t->data[offset]);
			offset += 2;

			cntlr_radio_pref_opclass_set_dfs_status(r->radio_el, opclassid, channel,
							time ? WIFI_RADIO_OPCLASS_CHANNEL_DFS_AVAILABLE :
							       WIFI_RADIO_OPCLASS_CHANNEL_DFS_NONE);
		}

		/* NOP */
		num = t->data[offset++];
		for (i = 0; i < num; i++) {
			opclassid = t->data[offset++];
			channel = t->data[offset++];
			time = BUF_GET_BE16(t->data[offset]);
			offset += 2;

			cntlr_radio_pref_opclass_set_dfs_status(r->radio_el, opclassid, channel,
							WIFI_RADIO_OPCLASS_CHANNEL_DFS_NOP);
		}

		/* CAC ongoing */
		num = t->data[offset++];
		for (i = 0; i < num; i++) {
			opclassid = t->data[offset++];
			channel = t->data[offset++];
			time = BUF_GET_BE24(t->data[offset]);
			offset += 3;

			cntlr_radio_pref_opclass_set_dfs_status(r->radio_el, opclassid, channel,
							WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC);
		}

		/* Kick ACS code */
		cntlr_acs_channel_pref_report(n, r);

		/* Only one allowed */
		break;
	}

	return 0;
}

int handle_channel_sel_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	int idx = 0;
	struct controller *c = (struct controller *) cntlr;
	struct tlv *tv[CHANNEL_SELECTION_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	uint16_t mid;

	cntlr_trace(LOG_CHANNEL, "%s called\n", __func__);

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse ( ..,EMP=%d) failed,  err = (%d) '%s'\n", __func__,
		     n->map_profile, map_error, map_strerror(map_error));
		return -1;
	}

	mid = cmdu_get_mid(cmdu);
	while (idx < TLV_MAXNUM && tv[CHANNEL_SELECTION_RESP_CHANNEL_SELECTION_IDX][idx]) {
		struct netif_radio *r;
		struct tlv_channel_selection_resp *p;

		p = (struct tlv_channel_selection_resp *)
			tv[CHANNEL_SELECTION_RESP_CHANNEL_SELECTION_IDX][idx++]->data;

		cntlr_dbg(LOG_CHANNEL, "\tmid: %d\n", mid);
		cntlr_dbg(LOG_CHANNEL, "\tradio_id: " MACFMT "\n", MAC2STR(p->radio));
		cntlr_dbg(LOG_CHANNEL, "\tresponse_code: %d\n", p->response);

		r = cntlr_find_radio(c, p->radio);
		if (!r)
			continue;

		/* kick acs code */
		cntlr_acs_channel_sel_response(r, mid, p->response);
       }

       return 0;
}

int handle_oper_channel_report(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);

	int idx = 0;
	struct controller *c = (struct controller *) cntlr;
	struct tlv *tv[OPER_CHANNEL_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	/*
	 * [0] MAP_TLV_OPERATING_CHANNEL_REPORT
	 * todo:
	 * [1] MAP_TLV_SPATIAL_REUSE_REPORT
	 */

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse ( ..,EMP=%d) failed,  err = (%d) '%s'\n", __func__,
		     n->map_profile, map_error, map_strerror(map_error));
		return -1;
	}

	while (idx < TLV_MAXNUM && tv[OPER_CHANNEL_REPORT_OPERATING_CHANNEL_IDX][idx]) {
		int i;
		int offset = 0;
		uint8_t mac[6] = {0};
		uint8_t *p = (uint8_t *)tv[OPER_CHANNEL_REPORT_OPERATING_CHANNEL_IDX][idx++]->data;
		struct netif_radio *r;
		struct netif_iface *fh = NULL;
		enum wifi_band band = BAND_UNKNOWN;
		uint8_t channel;
		uint8_t opclass;
		uint8_t txpower;
		int num_opclass;
		int ret;
		char ev_data[256] = {0};
		uint8_t prev_opclass = 0;
		uint8_t prev_channel = 0;
		uint8_t new_opclass = 0;
		uint8_t new_channel = 0;

		memcpy(mac, &p[offset], 6);
		offset += 6;

		r = cntlr_find_radio(c, mac);
		if (!r) {
			r = cntlr_node_add_radio(c, n, mac);
			if (!r)
				continue;
		}

		wifi_get_widest_opclass_and_channel(r->radio_el, &prev_opclass, &prev_channel);

		/* Reset current settings */
		cntlr_radio_cur_opclass_reset(r->radio_el);

		num_opclass = p[offset++];
		txpower = p[offset + 2 * num_opclass];
		for (i = 0; i < num_opclass; i++) {
			opclass = p[offset++];
			channel = p[offset++];
			if (WARN_ON(cntlr_radio_cur_opclass_add(r->radio_el, opclass, channel, txpower)))
				continue;
		}
		offset++;

		/* setup band */
		band = wifi_opclass_get_band(r->radio_el->cur_opclass.opclass[0].id);
		r->radio_el->band = band;
		ret = cntlr_config_add_node_radio(&c->cfg, n->almacaddr, r->radio_el->macaddr, band); /* add radio policy if missing */
		if (!ret)
			cntlr_resync_config(c, true);

		list_for_each_entry(fh, &r->iflist, list) {
			fh->band = band;
		}

		cntlr_radio_cur_opclass_dump(r->radio_el);

		/* kick acs code */
		cntlr_acs_oper_channel_report(r);

		wifi_get_widest_opclass_and_channel(r->radio_el, &new_opclass, &new_channel);

		if (new_opclass && new_channel &&
				(new_opclass != prev_opclass || new_channel != prev_channel)) {
			snprintf(ev_data, sizeof(ev_data), "{ \"agent\":\"" MACFMT "\", \"radio\":\""
					MACFMT "\", \"band\":%u, \"channel\":%u, \"opclass\":%u }",
					MAC2STR(n->almacaddr), MAC2STR(r->radio_el->macaddr),
					wifi_band_to_freqband(r->radio_el->band),
					new_channel, new_opclass);
			cntlr_notify_event(c, CNTLR_EVENT_CHANNEL_CHANGE, ev_data);
		}
	}

	return 0;
}

int handle_client_cap_report(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *)cntlr;
	struct tlv *tv[3][TLV_MAXNUM] = {0};
	struct tlv_client_info *p;
	struct sta *s;
	struct tlv_client_cap_report *r;
	uint16_t frame_len = 0;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* One Client Info TLV */
	p = (struct tlv_client_info *)tv[0][0]->data;

	s = cntlr_find_sta(c->sta_table, p->macaddr);
	if (!s) {
		dbg("%s: client capability for alien sta " MACFMT "\n",
		    __func__, MAC2STR(p->macaddr));
		return -1;
	}

	/* One Client Capability Report TLV */
	r = (struct tlv_client_cap_report *)tv[1][0]->data;

	if (r->result != 0x00) {
		/* Zero or one Error Code TLV */
		if (tv[2][0]) {
			struct tlv_error_code *e =
				(struct tlv_error_code *)tv[2][0]->data;

			dbg("%s: failed getting client capability for " MACFMT ": %s\n",
			    __func__, MAC2STR(e->macaddr), e->reason == 0x02 ?
				"STA not_associated" : "unspecified reason" /* 0x03 */);

			return 0;
		}

		dbg("%s: failed getting client capability for " MACFMT ": missing error code\n",
		    __func__, MAC2STR(p->macaddr));

		return -1;
	}

	/* Success. Store the frame in the sta */
	frame_len = tlv_length(tv[1][0]) - 1; /* result code */

	if(s->de_sta->reassoc_frame)
		sta_free_assoc_frame(s);

	s->de_sta->reassoc_frame = calloc(frame_len, sizeof(uint8_t));
	if (!s->de_sta->reassoc_frame) {
		err("%s: calloc of frame failed\n", __func__);
		return -1;
	}
	s->de_sta->reassoc_framelen = frame_len;
	memcpy(s->de_sta->reassoc_frame, r->frame, frame_len);

	/* Parse and cache WiFi capabilities */
	sta_update_capabilities(s);

	return 0;
}

int handle_ap_metrics_response(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);

	struct tlv *tv[AP_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	struct controller *c = (struct controller *)cntlr;
	int idx;

	trace("AP Metrics Response from Node " MACFMT "\n", MAC2STR(cmdu->origin));

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	/* AP Metrics TLV */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[0][idx]) {
		struct tlv_ap_metrics *p = (struct tlv_ap_metrics *)tv[0][idx++]->data;
		struct netif_iface *bss;

		/* ESP_AC_BE is mandatory */
		if (!(p->esp_ac & ESP_AC_BE))
			continue;

		bss = cntlr_find_bss(c, p->bssid);
		if (!bss)
			continue;

		bss->bss->ch_util = p->channel_utilization;
		bss->bss->num_stations = p->num_station;
		bss->bss->esp_ac = p->esp_ac;

		memcpy(bss->bss->est_wmm_be, p->esp_be, 3);
		if (p->esp_ac & ESP_AC_BK)
			memcpy(bss->bss->est_wmm_bk, &p->esp[0], 3);

		if (p->esp_ac & ESP_AC_VO)
			memcpy(bss->bss->est_wmm_vo, &p->esp[3], 3);

		if (p->esp_ac & ESP_AC_VI)
			memcpy(bss->bss->est_wmm_vi, &p->esp[6], 3);
	}

	/* Associated STA Traffic Stats TLV */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[1][idx]) {
		struct tlv_assoc_sta_traffic_stats *p =
			(struct tlv_assoc_sta_traffic_stats *)tv[1][idx++]->data;
		struct sta *s;

		s = cntlr_find_sta(c->sta_table, p->macaddr);
		if (s) {
			s->de_sta->tx_bytes = BUF_GET_BE32(p->tx_bytes);
			s->de_sta->rx_bytes = BUF_GET_BE32(p->rx_bytes);
			s->de_sta->tx_pkts = BUF_GET_BE32(p->tx_packets);
			s->de_sta->rx_pkts = BUF_GET_BE32(p->rx_packets);
			s->de_sta->tx_errors = BUF_GET_BE32(p->tx_err_packets);
			s->de_sta->rx_errors = BUF_GET_BE32(p->rx_err_packets);
			s->de_sta->rtx_pkts = BUF_GET_BE32(p->rtx_packets);
		}
	}

	/* Associated STA Link Metrics TLV */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[2][idx]) {
		uint8_t *tv_data = (uint8_t *)tv[2][idx++]->data;
		struct tlv_assoc_sta_link_metrics *p =
				(struct tlv_assoc_sta_link_metrics *)tv_data;
		struct netif_iface *bss = NULL;
		uint32_t dl_est_thput = 0;
		uint32_t ul_est_thput = 0;
		uint8_t rcpi = 0;
		struct sta *s;
		int offset;
		int i;

		offset = sizeof(*p);
		for (i = 0; i < p->num_bss; i++) {
			struct assoc_sta_link_metrics_bss *b =
				(struct assoc_sta_link_metrics_bss *)&tv_data[offset];

			bss = cntlr_find_bss(c, b->bssid);
			if (!bss)
				break;

			dl_est_thput = BUF_GET_BE32(b->dl_thput);
			ul_est_thput = BUF_GET_BE32(b->ul_thput);
			rcpi = b->ul_rcpi;

			offset += sizeof(*b);
		}

		/* skip the STA's link metrics when bss is unknown */
		if (!bss)
			continue;

		s = cntlr_find_sta(c->sta_table, p->macaddr);
		if (s) {
			s->de_sta->dl_est_thput = dl_est_thput;
			s->de_sta->ul_est_thput = ul_est_thput;
			s->de_sta->rcpi = rcpi;

			if (s->is_bsta) {
				int est_thput = cntlr_estimate_max_throughput_for_node(c, n->almacaddr);

				cntlr_dbg(LOG_DEFAULT, "Node = " MACFMT ": est-throughput = %d\n",
					   MAC2STR(n->almacaddr), est_thput);
				n->est_thput_dl = est_thput;
			}
		}
	}

	/* AP Extended Metrics TLV */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[3][idx]) {
		struct tlv_ap_ext_metrics *p =
			(struct tlv_ap_ext_metrics *)tv[3][idx++]->data;
		struct netif_iface *bss;

		bss = cntlr_find_bss(c, p->bssid);
		if (bss) {
			bss->bss->tx_ucast_bytes = BUF_GET_BE32(p->tx_bytes_ucast);
			bss->bss->rx_ucast_bytes = BUF_GET_BE32(p->rx_bytes_ucast);
			bss->bss->tx_mcast_bytes = BUF_GET_BE32(p->tx_bytes_mcast);
			bss->bss->rx_mcast_bytes = BUF_GET_BE32(p->rx_bytes_mcast);
			bss->bss->tx_bcast_bytes = BUF_GET_BE32(p->tx_bytes_bcast);
			bss->bss->rx_bcast_bytes = BUF_GET_BE32(p->rx_bytes_bcast);
		}
	}

	/* Radio Metrics TLV */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[4][idx]) {
		struct tlv_radio_metrics *p = (struct tlv_radio_metrics *)tv[4][idx++]->data;
		struct netif_radio *radio;

		radio = cntlr_find_radio(c, p->radio);
		if (radio) {
			/* Kick ACS code */
			cntlr_acs_radio_metrics(n, radio, p->noise, p->receive_other,
						p->transmit, p->receive_self);

			radio->radio_el->anpi = p->noise;
			radio->radio_el->tx_utilization = p->transmit;
			radio->radio_el->rx_utilization = p->receive_self;
			radio->radio_el->other_utilization = p->receive_other;
		}
	}

	/* Associated STA Extended Link Metrics TLV */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[5][idx]) {
		uint8_t *tv_data = (uint8_t *)tv[5][idx++]->data;
		struct tlv_sta_ext_link_metric *p = (struct tlv_sta_ext_link_metric *)tv_data;
		int offset = sizeof(*p);
		struct sta *s;
		int i;

		for (i = 0; i < p->num_bss; i++) {
			struct sta_ext_link_metric_bss *b =
				(struct sta_ext_link_metric_bss *)&tv_data[offset];

			s = cntlr_find_sta(c->sta_table, p->macaddr);
			if (s && !memcmp(s->bssid, b->bssid, 6)) {
				s->de_sta->dl_rate = BUF_GET_BE32(b->dl_rate);
				s->de_sta->ul_rate = BUF_GET_BE32(b->ul_rate);
				s->de_sta->dl_utilization = BUF_GET_BE32(b->rx_util);
				s->de_sta->ul_utilization = BUF_GET_BE32(b->tx_util);
			}

			offset += sizeof(*b);
		}
	}

	return 0;
}

int cntlr_request_usta_metrics(struct controller *c, uint8_t *agent_almacaddr,
			       uint8_t *sta_macaddr, enum wifi_band band)
{
	cntlr_trace(LOG_STA, "%s: --->\n", __func__);

	struct unassoc_sta_metric metrics[1] = {};
	struct cmdu_buff *cmdu = NULL;
	struct netif_radio *r;
	struct node *n;
	int i;

	if (!c)
		return -1;

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

	cntlr_trace(LOG_STA, "%s: Node " MACFMT ": ap_cap = 0x%02x\n",
		    __func__, MAC2STR(n->almacaddr), n->ap_cap);

	if (!(n->ap_cap & UNASSOC_STA_REPORTING_ONCHAN)) {
		cntlr_dbg(LOG_STA, "%s: Unassoc STA metric not supported by " MACFMT "\n",
			  __func__, MAC2STR(n->almacaddr));

		return 0;
	}

	r = cntlr_find_radio_in_node_by_band(c, n, band);
	if (!r)
		return -1;

	for (i = 0; i < r->radio_el->cur_opclass.num_opclass; i++) {
		/* TODO: allow more than one channel */
		metrics[0].channel = r->radio_el->cur_opclass.opclass[0].channel[0].channel;
		metrics[0].num_sta = 1;
		memcpy(metrics[0].sta[0].macaddr, sta_macaddr, 6);

		cmdu = cntlr_gen_unassoc_sta_metric_query(c,
							  n->almacaddr,
							  r->radio_el->cur_opclass.opclass[i].id,
							  1, metrics);

		if (cmdu) {
			cntlr_dbg(LOG_STA, "%s: sending usta metric query: STA " MACFMT \
				  ", NODE " MACFMT " OPCLASS %d CHANNEL %d\n", __func__,
				  MAC2STR(sta_macaddr), MAC2STR(n->almacaddr),
				  r->radio_el->cur_opclass.opclass[i].id,
				  r->radio_el->cur_opclass.opclass[i].channel[0].channel);
			send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
	}

	return 0;
}

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

	struct cmdu_buff *bcn_cmdu;
	uint8_t wildcard_bssid[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

	bcn_cmdu = cntlr_gen_beacon_metrics_query(c, s->agent_almacaddr,
						  s->macaddr, 0, 0,
						  wildcard_bssid, 0,
						  ssid, 0, NULL, 0, NULL);

	if (bcn_cmdu) {
		send_cmdu(c, bcn_cmdu);
		cmdu_free(bcn_cmdu);
	}
}

#if 0	// Unused
static void _cntlr_create_sta_channel_reports(struct wifi_radio_element *radio,
		struct sta_channel_report *reports, uint8_t *num_report)
{
	struct wifi_radio_opclass *cur_opclass = &radio->cur_opclass;
	int i;

	for (i = 0; i < cur_opclass->num_opclass; i++) {
		struct wifi_radio_opclass_entry *oe;
		struct wifi_radio_opclass *pref_opclass = &radio->pref_opclass;
		uint8_t cl_id;
		int j, k;

		oe = &cur_opclass->opclass[i];
		if (!oe->num_channel)
			continue;

		/* Always use /20 classid for beacon metrics */
		cl_id = wifi_opclass_get_id(pref_opclass, oe->channel[0].channel, 20);

		if (WARN_ON(!cl_id))
			return;

		for (j = 0; j < *num_report; j++) {
			if (reports[j].opclass == cl_id)
				/* opclass already present in report */
				break;
		}

		if (j == *num_report) {

			if (*num_report >= STA_CHANNEL_REPORT_MAX_NUM) {
				warn("|%s:%d| maximum number of channel reports reached\n",
				     __func__, __LINE__);
				return;
			}

			/* add this opclass to the report */
			reports[(*num_report)++].opclass = cl_id;
		}

		for (k = 0; k < oe->num_channel; k++) {
			int l;

			for (l = 0; l < reports[j].num_channel; l++) {
				if (oe->channel[k].channel == reports[j].channel[l])
					/* channel already present in report */
					break;
			}

			if (l == reports[j].num_channel) {
				/* add this channel to the report */
				reports[j].channel[l] = oe->channel[k].channel;
				reports[j].num_channel++;
				dbg("|%s:%d| channel_report add: clid=%d|chan=%d\n",
				    __func__, __LINE__,
				    reports[j].opclass,
				    reports[j].channel[l]);
			}
		}
	}
}


static void cntlr_create_sta_channel_reports(struct controller *c, struct sta *s,
		struct sta_channel_report *reports, uint8_t *num_report, bool match_band)
{
	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;

			if (match_band && s->fh->band != r->radio_el->band) {
				dbg("%s: skip bcn req for out-of-band radio " MACFMT "\n",
				    __func__, MAC2STR(r->radio_el->macaddr));
				continue;
			}

			list_for_each_entry(p, &r->iflist, list) {
				if (!p->bss || !s->fh || !s->fh->bss)
					continue;

				if (!memcmp(p->bss->ssid, s->fh->bss->ssid, 33))
					_cntlr_create_sta_channel_reports(r->radio_el,
							reports, num_report);
			}
		}
	}
}


static int cntlr_request_bcn_metrics_sta(struct controller *c, struct sta *s, bool match_band)
{
	struct cmdu_buff *bcn_cmdu;
	uint8_t wildcard[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
	uint8_t opclass = 0;
	uint8_t channel = 255; /* use channel report by default */
	struct sta_channel_report reports[STA_CHANNEL_REPORT_MAX_NUM] = {0};
	uint8_t num_report = 0;

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

	cntlr_create_sta_channel_reports(c, s, reports, &num_report, match_band);

	if (num_report == 1 && reports[0].num_channel == 1) {
		/* one channel & opclass pair only - don't use channel report */
		opclass = reports[0].opclass;
		channel = reports[0].channel[0];
		num_report = 0;
	}

	dbg("|%s:%d| almacaddr " MACFMT " STA macaddr " MACFMT " num_report = %d\n",
	    __func__, __LINE__,
	    MAC2STR(s->fh->agent->almacaddr),
	    MAC2STR(s->de_sta->macaddr),
	    num_report);

	bcn_cmdu = cntlr_gen_beacon_metrics_query(c,
			s->fh->agent->almacaddr, s->de_sta->macaddr,
			opclass, channel,
			wildcard, 0, s->fh->bss->ssid,
			num_report, reports, 0, NULL);

	if (bcn_cmdu) {
		uint16_t mid = send_cmdu(c, bcn_cmdu);

		if (mid != 0xffff) {
			cntlr_add_bcnreq(c, s->de_sta->macaddr,
					 s->fh->agent->almacaddr,
					 num_report);
		}

		cmdu_free(bcn_cmdu);
	}

	/* returns expected number of requests in agents */
	return (num_report ? num_report : 1);
}
#endif // #if 0

int handle_link_metrics_response(void *cntlr, struct cmdu_buff *cmdu,
				 struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *)cntlr;
	struct tlv *tv[LINK_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int num = 0;
	int ret;

	if (cmdu == NULL || c == NULL)
		return -1;

	/* Reset number of links */
	c->num_tx_links = 0;
	c->num_rx_links = 0;

	/* Parsing 1905 Link Metrics TLV */
	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n", __func__,
		     map_error, map_strerror(map_error));
		return -1;
	}

	/* Storing 1905 Link Metrics TLV */
	while (tv[LINK_METRICS_RESP_TRANSMITTER_LINK_METRICS_IDX][num]) {
		struct tlv_tx_linkmetric *txl = (struct tlv_tx_linkmetric *)
			tv[LINK_METRICS_RESP_TRANSMITTER_LINK_METRICS_IDX][num]->data;

		uint16_t tlv_tx_len = BUF_GET_BE16(tv[LINK_METRICS_RESP_TRANSMITTER_LINK_METRICS_IDX][num]->len);

		if (hwaddr_is_zero(txl->aladdr) ||
			hwaddr_is_zero(txl->neighbor_aladdr)) {
			dbg("%s: Discard Tx-link response\n", __func__);
			return -1;
		}
		/* Storing tx 1905 Link Metric data */
		ret = cntlr_update_txlink_metric_data(c, txl, tlv_tx_len);
		if (ret) {
			dbg("|cntlr_update_txlink_metric_data| Link not found!\n");
			return -1;
		}
		num++;
	}

	num = 0;
	while (tv[LINK_METRICS_RESP_RECEIVER_LINK_METRICS_IDX][num]) {
		struct tlv_rx_linkmetric *rxl = (struct tlv_rx_linkmetric *)
			tv[LINK_METRICS_RESP_RECEIVER_LINK_METRICS_IDX][num]->data;

		uint16_t tlv_rx_len = BUF_GET_BE16(tv[LINK_METRICS_RESP_RECEIVER_LINK_METRICS_IDX][num]->len);

		if (hwaddr_is_zero(rxl->aladdr) ||
			hwaddr_is_zero(rxl->neighbor_aladdr)) {
			dbg("%s: Discard Tx-link response\n", __func__);
			return -1;
		}
		/* Storing rx 1905 Link Metric data */
		ret = cntlr_update_rxlink_metric_data(c, rxl, tlv_rx_len);
		if (ret) {
			dbg("|cntlr_update_rxlink_metric_data| Link not found!\n");
			return -1;
		}
		num++;
	}

	return 0;
}

int handle_sta_link_metrics_response(void *cntlr, struct cmdu_buff *cmdu,
				     struct node *n)
{
	cntlr_trace(LOG_STA, "%s: --->\n", __func__);

	uint8_t inform_stalist[INFORM_STA_MAXNUM * 6] = {0};
	struct controller *c = (struct controller *)cntlr;
	struct tlv *tv[ASSOC_STA_LINK_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	int offset = 0;
	int iidx = 0;
	int idx = 0;
	int i;

	cntlr_dbg(LOG_STA, "STA-Link Metrics Response from " MACFMT ", node %p\n",
		  MAC2STR(cmdu->origin), n);

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n", __func__,
		     map_error, map_strerror(map_error));
		return -1;
	}

	while (tv[ASSOC_STA_LINK_METRICS_RESP_ASSOCIATED_STA_LINK_METRICS_IDX][idx]) {
		uint8_t *tv_data =
			(uint8_t *)tv[ASSOC_STA_LINK_METRICS_RESP_ASSOCIATED_STA_LINK_METRICS_IDX][idx]->data;
		struct tlv_assoc_sta_link_metrics *p =
			(struct tlv_assoc_sta_link_metrics *)tv_data;
		struct sta *s;

		idx++;

		s = cntlr_find_sta(c->sta_table, p->macaddr);
		if (!s)
			continue;

		offset = sizeof(*p);
		for (i = 0; i < p->num_bss; i++) {
			struct assoc_sta_link_metrics_bss *b =
				(struct assoc_sta_link_metrics_bss *)&tv_data[offset];
			struct netif_iface *fh = NULL;

			fh = cntlr_find_iface(c, b->bssid);
			if (!fh) {
				offset += sizeof(*b);
				continue;
			}

			if (memcmp(s->bssid, b->bssid, 6)) {
				cntlr_dbg(LOG_STA,
					  "%s: Ignore STA " MACFMT " link-metrics with different bssid\n",
					  __func__, MAC2STR(s->macaddr));

				offset += sizeof(*b);
				continue;
			}

			s->de_sta->dl_est_thput = BUF_GET_BE32(b->dl_thput);
			s->de_sta->ul_est_thput = BUF_GET_BE32(b->ul_thput);
			s->de_sta->time_delta = BUF_GET_BE32(b->time_delta);
			/* record link-metrics-response receive time */
			time(&s->de_sta->tsp);
			s->de_sta->rcpi = b->ul_rcpi;

			offset += sizeof(*b);
		}

		if (iidx < INFORM_STA_MAXNUM - 1)
			memcpy(&inform_stalist[6 * iidx++], s->de_sta->macaddr, 6);
	}

	if (iidx == 0)
		cntlr_dbg(LOG_STA, "%s: No STA found in Assoc-Link-Metrics-Resp\n", __func__);

	idx = 0;
	while (tv[ASSOC_STA_LINK_METRICS_RESP_ASSOCIATED_STA_EXT_LINK_METRICS_IDX][idx]) {
		uint8_t *tv_data = (uint8_t *)
			tv[ASSOC_STA_LINK_METRICS_RESP_ASSOCIATED_STA_EXT_LINK_METRICS_IDX][idx]->data;
		struct tlv_sta_ext_link_metric *p =
			(struct tlv_sta_ext_link_metric *)tv_data;
		struct sta *s;

		s = cntlr_find_sta(c->sta_table, p->macaddr);
		if (!s)
			return -1;

		offset = sizeof(*p);
		/* XXX: if num_bss > 1, then sta's bssid, rate and utilization
		 * may get overridden with the last entry, which is undesirable.
		 */
		for (i = 0; i < p->num_bss; i++) {
			struct sta_ext_link_metric_bss *b =
				(struct sta_ext_link_metric_bss *)&tv_data[offset];
			struct netif_iface *fh;

			fh = cntlr_find_iface(c, b->bssid);
			if (!fh) {
				offset += sizeof(*b);
				continue;
			}

			if (memcmp(s->bssid, b->bssid, 6)) {
				cntlr_dbg(LOG_STA,
					  "%s: Ignore STA " MACFMT " link-metrics with different bssid\n",
					  __func__, MAC2STR(s->macaddr));

				offset += sizeof(*b);
				continue;
			}

			s->de_sta->dl_rate = BUF_GET_BE32(b->dl_rate);
			s->de_sta->ul_rate = BUF_GET_BE32(b->ul_rate);
			s->de_sta->dl_utilization = BUF_GET_BE32(b->rx_util);
			s->de_sta->ul_utilization = BUF_GET_BE32(b->tx_util);

			offset += sizeof(*b);
			//TODO: skip remaining if num_bss > 1; reason above
		}
		idx++;
	}

	/* A scalable approach is to notify the cmdu via cntlr_notify_cmdu(),
	 * and allow the plugin to take care of the rest -
	 *
	 * For now, inform steer plugins about the STAs.
	 */
	c->inform_cmdu_type = CMDU_ASSOC_STA_LINK_METRICS_RESPONSE;
	c->inform_sta_num = iidx;
	memset(c->inform_stalist, 0, sizeof(c->inform_stalist));
	memcpy(c->inform_stalist, inform_stalist, iidx * 6);

	timer_set(&c->steer_sched_timer, 0);

	return 0;
}

int cntlr_send_1905_acknowledge(void *cntlr,
	struct cmdu_buff *rx_cmdu,
	struct sta_error_response *sta_resp, uint32_t sta_count)
{
	trace("%s: --->\n", __func__);

	struct cmdu_buff *response;
	struct controller *c = (struct controller *) cntlr;

	response = cntlr_gen_cmdu_1905_ack(c, rx_cmdu, sta_resp, sta_count);
	if (!response)
		return -1;

	send_cmdu(c, response);
	cmdu_free(response);
	return 0;
}

int handle_unassoc_sta_link_metrics_response(void *cntlr,
					     struct cmdu_buff *cmdu,
					     struct node *n)
{
	trace("%s: --->\n", __func__);

	uint8_t inform_stalist[INFORM_STA_MAXNUM * 6] = {0};
	struct controller *c = (struct controller *) cntlr;
	struct tlv *tv[UNASTA_LINK_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int iidx = 0;
	int i = 0;

	if (!cntlr || !cmdu)
		return -1;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n", __func__,
		     map_error, map_strerror(map_error));
		return -1;
	}

	/* If a Multi-AP Controller receives an Unassociated STA
	 * Link Metrics Response message, then it shall respond
	 * within one second with a 1905 Ack message.
	 */
	cntlr_send_1905_acknowledge(cntlr, cmdu, NULL, 0);

	if (tv[UNASTA_LINK_METRICS_RESP_UNASSOCIATED_STA_LINK_METRICS_IDX][0]) {
		int offset = 0;
		uint8_t *tv_data = (uint8_t *)
			tv[UNASTA_LINK_METRICS_RESP_UNASSOCIATED_STA_LINK_METRICS_IDX][0]->data;
		struct tlv_unassoc_sta_link_metrics_resp *resp =
			(struct tlv_unassoc_sta_link_metrics_resp  *)tv_data;

		offset = sizeof(*resp);
		for (i = 0; i < resp->num_sta; i++) {
			struct unassoc_sta_link_metrics_sta *b =
				(struct unassoc_sta_link_metrics_sta *)&tv_data[offset];
			struct unassoc_sta_metrics *u;
			struct netif_iface *fh = NULL;
			struct netif_radio *r = NULL;
			struct sta *s;

			s = cntlr_find_sta(c->sta_table, b->macaddr);
			if (!s) {
				cntlr_dbg(LOG_STA,
					  "%s: Ignore unknown STA "MACFMT" in Unassoc STA Metrics Response\n",
					  __func__, MAC2STR(b->macaddr));
				continue;
			}

			if (!wifi_opclass_has_channel(resp->opclass, b->channel)) {
				cntlr_dbg(LOG_STA,
					  "%s: Skip Unassoc-STA Metrics Response with invalid opclass/channel {%u/%u}\n",
					  __func__, resp->opclass, b->channel);
				continue;
			}

			u = cntlr_find_usta_metric(c, s, cmdu->origin);
			if (!u) {
				u = calloc(1, sizeof(*u));
				if (!u)
					continue;

				cntlr_dbg(LOG_STA,
					  "%s: Adding Unassoc-metric for STA " MACFMT " add to list %p (%p)\n",
					  __func__, MAC2STR(s->de_sta->macaddr),
					  &s->de_sta->umetriclist,
					  s->steer_data->unassoc_metriclist);

				list_add(&u->list, &s->de_sta->umetriclist);
				memcpy(u->agent_macaddr, cmdu->origin, 6);
			}

			node_for_each_bss(fh, r, n) {
				if (wifi_opclass_get_band(resp->opclass) != r->radio_el->band)
					continue;

				if (fh->bss->is_fbss) {
					/* TODO: also check vlanid */
					memcpy(u->bssid, fh->bss->bssid, 6);
					break;
				}
			}

			u->channel = b->channel;
			u->time_delta = BUF_GET_BE32(b->time_delta);
			u->ul_rcpi = b->ul_rcpi;
			time(&u->rpt_time);

			cntlr_dbg(LOG_STA,
				  "%s: STA " MACFMT " Agent = " MACFMT ", Ch = %u, ul-rcpi = %u\n",
				  __func__,
				  MAC2STR(s->de_sta->macaddr),
				  MAC2STR(u->agent_macaddr),
				  u->channel,
				  u->ul_rcpi);

			if (iidx < INFORM_STA_MAXNUM - 1)
				memcpy(&inform_stalist[6 * iidx++], s->de_sta->macaddr, 6);

			offset += sizeof(*b);
		}
	}

	/* inform steer plugins of the responses */
	c->inform_cmdu_type = CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE;
	c->inform_sta_num = iidx;
	memset(c->inform_stalist, 0, sizeof(c->inform_stalist));
	memcpy(c->inform_stalist, inform_stalist, iidx * 6);

	timer_set(&c->steer_sched_timer, 0);

	return 0;
}

int handle_beacon_metrics_response(void *cntlr, struct cmdu_buff *cmdu,
				   struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	struct tlv_beacon_metrics_resp *resp;
	struct tlv *tv[BEACON_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	uint8_t *ppos;
	struct sta *s;
	int i = 0;

	if (!cntlr || !cmdu)
		return -1;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n", __func__,
		     map_error, map_strerror(map_error));
		return -1;
	}

	/* If a Multi-AP Controller receives a Beacon Metrics Response message,
	 * then it shall respond within one second with a 1905 Ack message.
	 */
	cntlr_send_1905_acknowledge(cntlr, cmdu, NULL, 0);

	resp = (struct tlv_beacon_metrics_resp *)
		tv[BEACON_METRICS_RESP_BEACON_METRICS_IDX][0]->data;

	s = cntlr_find_sta(c->sta_table, resp->sta_macaddr);
	if (!s) {
		cntlr_dbg(LOG_STA,
			  "%s: Ignore Beacon-Report for unknown STA "MACFMT"\n",
			  __func__, MAC2STR(resp->sta_macaddr));
		return -1;
	}

	timer_del(&s->bcn_metrics_timer);
	cntlr_del_bcnreq(c, s->macaddr, n->almacaddr);

	ppos = resp->element;
	for (i = 0; i < resp->num_element; i++) {
		struct bcn_meas_element *elem;
		struct wifi_sta_meas_report *b;

		elem = (struct bcn_meas_element *)ppos;
		if (elem->tag_number != 0x27) {
			cntlr_dbg(LOG_STA,
				  "%s: Skip meas-report element != 0x27\n", __func__);
			ppos = ppos + elem->tag_length + 2;
			continue;
		}

		if (hwaddr_is_zero(elem->bssid)) {
			ppos = ppos + elem->tag_length + 2;
			continue;
		}

		if (!cntlr_find_iface_type(c, elem->bssid, MAC_ENTRY_FBSS | MAC_ENTRY_BBSS)) {
			cntlr_dbg(LOG_STA, "%s: Skip Beacon-Report for alien AP " MACFMT"\n",
				  __func__, MAC2STR(elem->bssid));

			ppos = ppos + elem->tag_length + 2;
			continue;
		}

		if (!wifi_opclass_has_channel(elem->op_class, elem->channel)) {
			cntlr_dbg(LOG_STA, "%s: Skip Beacon-Report with invalid channel/opclass: {%u/%u}\n",
				  __func__, elem->channel, elem->op_class);

			ppos = ppos + elem->tag_length + 2;
			continue;
		}

		cntlr_dbg(LOG_STA, "Beacon-Report: {STA:"MACFMT", bssid:"
			  MACFMT", rcpi:%u, opclass:%u, channel:%u}\n",
			  MAC2STR(s->de_sta->macaddr), MAC2STR(elem->bssid),
			  elem->rcpi, elem->op_class, elem->channel);


		b = calloc(1, sizeof(*b));
		if (!b) {
			cntlr_err(LOG_STA, "%s: -ENOMEM\n", __func__);
			return -ENOMEM;
		}

		/* limit number of saved meas-reports per STA */
		if (s->de_sta->num_meas_reports >= c->cfg.bcn_metrics_max_num) {
			struct wifi_sta_meas_report *rep = NULL;

			cntlr_dbg(LOG_STA, "%s: dropping oldest Beacon-Report for STA" MACFMT "\n",
				  __func__, MAC2STR(s->de_sta->macaddr));

			rep = list_last_entry(&s->de_sta->meas_reportlist, struct wifi_sta_meas_report, list);
			if (rep) {
				list_del(&rep->list);
				free(rep);
				s->de_sta->num_meas_reports--;
			}
		}

		cntlr_dbg(LOG_STA, "%s: adding new Beacon-Report for STA " MACFMT "\n",
			  __func__, MAC2STR(s->de_sta->macaddr));

		memcpy(b->bssid, elem->bssid, 6);
		b->channel = elem->channel;
		b->opclass = elem->op_class;
		b->rcpi = elem->rcpi;
		b->rsni = elem->rsni;
		b->antena_id = elem->antena_id;
		b->parent_tsf = BUF_GET_LE32(elem->tsf);
		memcpy(&b->meas_start_time, &elem->start_time, 8);
		time(&b->rpt_time);
		memcpy(b->agent_macaddr, n->almacaddr, 6);

		list_add(&b->list, &s->de_sta->meas_reportlist);
		s->de_sta->num_meas_reports++;

		ppos = ppos + elem->tag_length + 2;
	}

	/* inform steer plugins of the responses */
	c->inform_cmdu_type = CMDU_BEACON_METRICS_RESPONSE;
	c->inform_sta_num = 1;
	memset(c->inform_stalist, 0, sizeof(c->inform_stalist));
	memcpy(c->inform_stalist, s->de_sta->macaddr, 6);

	timer_set(&c->steer_sched_timer, 0);
	return 0;
}

int handle_sta_steer_btm_report(void *cntlr, struct cmdu_buff *cmdu,
				struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	struct wifi_steer_history *attempt;
	struct tlv_steer_btm_report *resp;
	struct tlv *tv[STEERING_BTM_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	char ev_data[512] = {0};
	struct sta *s;

	if (!cntlr || !cmdu)
		return -1;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n", __func__,
		     map_error, map_strerror(map_error));
		return -1;
	}

	/* If a Multi-AP Controller receives a Client Steering BTM Report message,
	 * then it shall respond within one second with a 1905 Ack message.
	 */
	cntlr_send_1905_acknowledge(cntlr, cmdu, NULL, 0);

	resp = (struct tlv_steer_btm_report *)tv[STEERING_BTM_REPORT_BTM_REPORT_IDX][0]->data;

	s = cntlr_find_sta(c->sta_table, resp->sta_macaddr);
	if (!s) {
		cntlr_warn(LOG_STEER,
			   "%s: Received Steer BTM Report from unknown STA "MACFMT"\n",
			   __func__, MAC2STR(resp->sta_macaddr));
		return -1;
	}

	timer_del(&s->btm_req_timer);

	snprintf(ev_data, sizeof(ev_data),
			 "{\"bssid\":\""MACFMT"\""
			 ",\"sta_mac\":\""MACFMT"\""
			 ",\"target_bssid\":\""MACFMT"\""
			 ",\"status\":%u}",
			 MAC2STR(resp->bssid), MAC2STR(resp->sta_macaddr),
			 MAC2STR(resp->target_bssid[0]), resp->status);
	cntlr_notify_event(c, CNTLR_EVENT_CLIENT_STEER_REPORT, ev_data);


	attempt = sta_lookup_steer_attempt(s, resp->bssid,
					   resp->status == 0 ? resp->target_bssid[0] : NULL);

	if (!attempt) {
		cntlr_warn(LOG_STEER,
			  "%s: Steering attempt for " MACFMT " not found for this Steering BTM Report\n",
			  __func__, MAC2STR(resp->sta_macaddr));
		return 0;
	}

	if (resp->status != 0x00) { /* steer failed */
		s->de_sta->mapsta.steer_summary.btm_failure_cnt++;
		c->dlem.network.steer_summary.btm_failure_cnt++;
		attempt->complete = 1;

		cntlr_notify_client_steer_result(c, s->de_sta->macaddr,
						 STEER_RESULT_FAIL_RSP);

	} else {
		s->de_sta->mapsta.steer_summary.btm_success_cnt++;
		c->dlem.network.steer_summary.btm_success_cnt++;

		cntlr_notify_client_steer_result(c, s->de_sta->macaddr,
						 STEER_RESULT_SUCCESS);

		/* Record tsp for most recent steer success */
		timestamp_update(&s->de_sta->mapsta.steer_summary.last_steer_tsp);
		attempt->duration = timestamp_elapsed_sec(&attempt->time);
		memcpy(attempt->dst_bssid, resp->target_bssid[0], 6);
		attempt->complete = 1;
	}

	/* Inform steering plugins */
	c->inform_cmdu_type = CMDU_CLIENT_STEERING_BTM_REPORT;
	c->inform_sta_num = 1;
	memset(c->inform_stalist, 0, sizeof(c->inform_stalist));
	memcpy(c->inform_stalist, s->de_sta->macaddr, 6);

	timer_set(&c->steer_sched_timer, 0);

	return 0;
}

int handle_sta_steer_complete(void *cntlr, struct cmdu_buff *cmdu,
			      struct node *n)
{
	trace("%s: --->\n", __func__);

	/* TODO: close steering-opportunity window timer, if running. */

	cntlr_send_1905_acknowledge(cntlr, cmdu, NULL, 0);
	return 0;
}

static int cntlr_data_event(struct controller *c, const char *event_name,
		uint8_t proto, uint8_t *data, int data_len)
{
	const int len = data_len*2 + 128;
	char *str;
	int idx;

	str = calloc(len, sizeof(char));
	if (!str)
		return -1;

	idx = snprintf(str, len, "{\"protocol\":%d,\"data\":\"", proto);
	btostr(data, data_len, str + idx);
	idx += data_len*2;
	snprintf(str + idx, len - idx, "\"}");

	cntlr_notify_event(c, (void *)event_name, str);

	free(str);

	return 0;
}

static inline int cntlr_hld_event(struct controller *c, uint8_t proto,
		uint8_t *data, int data_len)
{
	return cntlr_data_event(c, (void *)CNTLR_EVENT_HLD, proto, data, data_len);
}

int handle_hld_message(void *cntlr, struct cmdu_buff *rx_cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	struct tlv *t;
	uint8_t proto;
	uint8_t *data;
	int data_len;
	int ret;
	uint8_t origin[6] = {0};

	t = cmdu_peek_tlv(rx_cmdu, MAP_TLV_HIGHER_LAYER_DATA);
	if (!t) {
		warn("%s: higher layer data TLV not found\n", __func__);
		return -1;
	}

	data_len = tlv_length(t) - 1;
	proto = t->data[0];
	data = t->data + 1;

	dbg("%s TLV received proto %u data_len %u!!\n", __func__, proto, data_len);

	if (!hwaddr_is_zero(rx_cmdu->origin)) {
		memcpy(origin, rx_cmdu->origin, 6);

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

	dbg("Received HLD from " MACFMT "\n", MAC2STR(origin));

	ret = cntlr_hld_event(c, proto, data, data_len);
	if (ret == 0)
		cntlr_send_1905_acknowledge(c, rx_cmdu, NULL, 0);

#ifdef CONTROLLER_SYNC_DYNAMIC_CNTLR_CONFIG
	if (proto == 0xac) {
		struct sync_config cfg = {0};
		uint16_t sync_config_respsize = 0;
		uint8_t *sync_config_resp = NULL;
		struct cmdu_buff *cmdu;

		dbg("******** Handle dyn-cntlr-sync-config-request ******\n");
		/* if (n->sync_config_req)
			free(n->sync_config_req);

		n->sync_config_reqsize = 0;
		n->sync_config_req = calloc(data_len, sizeof(uint8_t));
		if (n->sync_config_req) {
			memcpy(n->sync_config_req, data, data_len);
			n->sync_config_reqsize = data_len;
			fprintf(stderr, "%s: -- %d --\n", __func__, __LINE__);
		}
		*/
		ret = readfrom_configfile("/etc/config/mapcontroller", &cfg.data, &cfg.len);
		if (ret) {
			err("error reading controller config file\n");
			return -1;
		}

		ret = build_sync_config_response(data, data_len,
						 &cfg,
						 &sync_config_resp,
						 &sync_config_respsize);

		free(cfg.data);

		dbg("dyn-cntlr-sync-config response size = %u\n", sync_config_respsize);

		/* ret = build_sync_config_response(c->sync_config_req,
						 c->sync_config_reqsize,
						 &cfg,
						 &sync_config_resp,
						 &sync_config_respsize); */
		if (ret) {
			dbg("Error building sync-config-response\n");
			return ret;
		}

		cmdu = cntlr_gen_higher_layer_data(c, origin, proto,
						   sync_config_resp,
						   sync_config_respsize);

		free(sync_config_resp);

		if (!cmdu)
			return -1;

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

	return ret;
}

int handle_backhaul_sta_steer_response(void *cntlr, struct cmdu_buff *cmdu,
				       struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	struct tlv_backhaul_steer_resp *resp;
	struct tlv_error_code *err;
	struct tlv *tv[BH_STA_STEER_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	char ev_data[512] = {0};

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n", __func__,
		     map_error, map_strerror(map_error));
		return -1;
	}

	/* If a Multi-AP Controller receives a Backhaul Steering Response message,
	 * then it shall respond within one second with a 1905 Ack message.
	 */
	cntlr_send_1905_acknowledge(cntlr, cmdu, NULL, 0);

	resp = (struct tlv_backhaul_steer_resp *)tv[BH_STA_STEER_RESP_BACKHAUL_STEERING_RESPONSE_IDX][0]->data;

	/* Error Code TLV */
	err = (tv[BH_STA_STEER_RESP_ERROR_CODE_IDX][0]) ?
			(struct tlv_error_code *)tv[BH_STA_STEER_RESP_ERROR_CODE_IDX][0]->data : NULL;

	cntlr_dbg(LOG_BSTEER,
		  "%s: Backhaul Steering Response: agent: " MACFMT " mac address: " MACFMT \
		  " target_bssid: " MACFMT " result: %u\n", __func__, MAC2STR(n->almacaddr),
		  MAC2STR(resp->macaddr), MAC2STR(resp->target_bssid),
		  err ? err->reason : resp->result);

	snprintf(ev_data, sizeof(ev_data),
			 "{\"agent\":\""MACFMT"\""
			 ",\"macaddr\":\""MACFMT"\""
			 ",\"target_bssid\":\""MACFMT"\""
			 ",\"result\":%u}",
			 MAC2STR(n->almacaddr), MAC2STR(resp->macaddr), MAC2STR(resp->target_bssid),
			 err ? err->reason : resp->result);

	cntlr_notify_event(c, CNTLR_EVENT_BSTA_STEER_RESULT, ev_data);

	/* Inform steering plugins */
	c->inform_cmdu_type = CMDU_BACKHAUL_STEER_RESPONSE;
	c->inform_sta_num = 1;
	memset(c->inform_stalist, 0, sizeof(c->inform_stalist));
	memcpy(c->inform_stalist, resp->macaddr, 6);

	timer_set(&c->steer_sched_timer, 0);

	return 0;
}

/* fill in tv_scan with results and return number of results received */
int get_tv_scan_from_cmdu(struct cmdu_buff *cmdu, struct tlv *tv_scan[], int *num_res)
{
	struct tlv_policy d_policy_scan[] = {
		[0] = {
			.type = MAP_TLV_CHANNEL_SCAN_RES,
			.present = TLV_PRESENT_MORE,
			.minlen = 9
		}
	};

	if (cmdu_parse_tlv_single(cmdu, tv_scan, d_policy_scan, num_res)) {
		warn("%s: cmdu_parse_tlv_single failed,  err = (%d) '%s'\n",
			 __func__, map_error, map_strerror(map_error));
		return -1;
	}

	if (!tv_scan[0])
		dbg("%s: No RESULT_TLV received!\n", __func__);

	return 0;
}

int handle_channel_scan_report(void *cntlr, struct cmdu_buff *cmdu,
			       struct node *n)
{
	trace("%s: --->\n", __func__);

	int num_result = 256;
	struct tlv *tv[CHAN_SCAN_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv *tv_scan[256] = {0};
	char timestamp[TIMESTAMP_MAX_LEN] = {0};
	struct tlv_timestamp *p = NULL;
	uint8_t *tv_data = NULL;
	struct netif_radio *r = NULL;
	uint16_t mid;
	int i;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	if (get_tv_scan_from_cmdu(cmdu, tv_scan, &num_result))
		return -1;

	tv_data = (uint8_t *)tv[CHAN_SCAN_REPORT_TIMESTAMP_IDX][0]->data;
	p = (struct tlv_timestamp *)tv_data;

	memcpy(timestamp, p->timestamp, p->len);

	dbg("%s: timestamp = %s\n", __func__, timestamp);

	/* Inform ACS about fresh scan and preference */
	mid = cmdu_get_mid(cmdu);
	list_for_each_entry(r, &n->radiolist, list) {
		for (i = 0; i < num_result; i++) {
			struct tlv_channel_scan_result *tlv =
				(struct tlv_channel_scan_result *)tv_scan[i]->data;

			if (!memcmp(r->radio_el->macaddr, tlv->radio, 6))
				break;
		}

		if (i == num_result)
			continue;

		cntlr_acs_scan_report(n, r, mid);
	}

	return cntlr_radio_update_scanlist(cntlr, timestamp, tv_scan, num_result);
}

int handle_sta_disassoc_stats(void *cntlr, struct cmdu_buff *cmdu,
			      struct node *n)
{
	trace("%s: --->\n", __func__);

	struct tlv *tv[STA_DISASSOC_STATS_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct controller *c = (struct controller *) cntlr;
	struct tlv_sta_mac *p;
	struct tlv_reason_code *r;
	struct tlv_assoc_sta_traffic_stats *stats;
	struct sta *s = NULL;
#if (EASYMESH_VERSION >= 6)
	int idx = 0;
#endif

	if (!cntlr || !cmdu)
		return -1;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n", __func__,
			 map_error, map_strerror(map_error));
		return -1;
	}

	/* One STA MAC Address TLV per STA */
	p = (struct tlv_sta_mac *)tv[STA_DISASSOC_STATS_STA_MAC_ADDR_IDX][0]->data;
	s = cntlr_find_sta(c->sta_table, p->macaddr);
	if (!s) {
		cntlr_dbg(LOG_STA, "%s: Unaccounted STA " MACFMT "!\n", __func__, MAC2STR(p->macaddr));
		return -1;
	}

	/* One Reason Code TLV per Associated STA */
	r = (struct tlv_reason_code *)tv[STA_DISASSOC_STATS_REASON_CODE_IDX][0]->data;
	s->disassoc_reason = BUF_GET_BE16(r->code);

	/* One Associated STA Traffic Stats TLV per STA */
	stats = (struct tlv_assoc_sta_traffic_stats *)
		tv[STA_DISASSOC_STATS_ASSOCIATED_STA_TRAFFIC_STATS_IDX][0]->data;
	if (!hwaddr_equal(p->macaddr, stats->macaddr)) {
		cntlr_warn(LOG_STA, "%s: MAC mismatch in STA Traffic Stats TLV\n", __func__);
		return -1;
	}

	/* Update STA stats */
	s->de_sta->tx_bytes = BUF_GET_BE32(stats->tx_bytes);
	s->de_sta->rx_bytes = BUF_GET_BE32(stats->rx_bytes);
	s->de_sta->tx_pkts = BUF_GET_BE32(stats->tx_packets);
	s->de_sta->rx_pkts = BUF_GET_BE32(stats->rx_packets);
	s->de_sta->tx_errors = BUF_GET_BE32(stats->tx_err_packets);
	s->de_sta->rx_errors = BUF_GET_BE32(stats->rx_err_packets);
	s->de_sta->rtx_pkts = BUF_GET_BE32(stats->rtx_packets);

#if (EASYMESH_VERSION >= 6)
	//TODO: Use wifi_stamld_element for MLD station, once supported

	/* Zero or more Affiliated STA Metrics TLV per STA */
	while (idx < TLV_MAXNUM &&
		   tv[STA_DISASSOC_STATS_AFFILIATED_STA_METRICS_IDX][idx]) {
		struct tlv_affiliated_sta_metrics *aff = (struct tlv_affiliated_sta_metrics *)
			tv[3][idx]->data;

		UNUSED(aff);
		idx++;
	}
#endif

	return 0;
}

int handle_assoc_status_notification(void *cntlr, struct cmdu_buff *cmdu,
				     struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	struct tlv *tv[ASSOC_STATUS_NOTIF_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_assoc_status_notif *notif;
	int i;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse( ..,EMP=%d) failed,  err = (%d) '%s'\n",
		     __func__, n->map_profile, map_error, map_strerror(map_error));
		return -1;
	}

	/* One Association Status TLV */
	notif = (struct tlv_assoc_status_notif *)
		tv[ASSOC_STATUS_NOTIF_ASSOCIATION_STATUS_IDX][0]->data;

	for (i = 0; i < notif->num_bss; i++) {
		struct netif_iface *ap = NULL;

		ap = cntlr_find_bss(c, notif->bss[i].bssid);
		if (!ap) {
			dbg("No BSS found for " MACFMT "\n",
			    MAC2STR(notif->bss[i].bssid));
			continue;
		}

		if (notif->bss[i].allowance & STA_ASSOC_ALLOWED)
			ap->bss->sta_assoc_allowed = true;
		else
			ap->bss->sta_assoc_allowed = false;
	}

	return 0;
}

static inline int cntlr_tunneled_msg_event(struct controller *c,
		uint8_t msg_type, uint8_t *data, int data_len)
{
	return cntlr_data_event(c, (void *)CNTLR_EVENT_TUNNELED_MSG, msg_type, data, data_len);
}

int handle_tunneled_message(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);
	struct tlv *tv[TUNNELED_MESSAGE_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_tunnel_msg_type *tuntype;
	struct tlv_tunneled *tunframe;
	int num = 0;
	struct controller *c = (struct controller *) cntlr;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		err("%s: map_cmdu_validate_parse( ..,EMP=%d) failed,  err = (%d) '%s'\n", __func__,
		     n->map_profile, map_error, map_strerror(map_error));
		return -1;
	}

	tuntype = (struct tlv_tunnel_msg_type *)tv[TUNNELED_MESSAGE_TUNNELED_MSG_TYPE_IDX][0];
	while (num < TLV_MAXNUM && tv[TUNNELED_MESSAGE_TUNNELED_MSG_IDX][num]) {
		struct tlv *tlv;
		uint8_t *frame = NULL;
		uint16_t framelen = 0;

		tlv = (struct tlv *)tv[TUNNELED_MESSAGE_TUNNELED_MSG_IDX][num];
		tunframe = (struct tlv_tunneled *)tlv->data;
		framelen = BUF_GET_BE16(tv[TUNNELED_MESSAGE_TUNNELED_MSG_IDX][num]->len);
		frame = tunframe->frame;

		cntlr_tunneled_msg_event(c, tuntype->type, frame, framelen);

		num++;
	}

	return 0;
}

int handle_backhaul_sta_caps_report(void *cntlr, struct cmdu_buff *cmdu,
				    struct node *n)
{
	struct tlv *tv[BH_STA_CAPS_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct controller *c = (struct controller *) cntlr;
	uint8_t *tv_data;
	int num = 0;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse( ..,EMP=%d) failed,  err = (%d) '%s'\n", __func__,
		     n->map_profile, map_error, map_strerror(map_error));
		return -1;
	}

	while (num < TLV_MAXNUM && tv[BH_STA_CAPS_REPORT_BACKHAUL_STA_RADIO_CAPABILITY_IDX][num]) {
		struct tlv_bsta_radio_cap *p;
		struct netif_radio *r;

		tv_data = (uint8_t *)tv[BH_STA_CAPS_REPORT_BACKHAUL_STA_RADIO_CAPABILITY_IDX][num]->data;
		p = (struct tlv_bsta_radio_cap *)tv_data;

		r = cntlr_find_radio(c, p->radio);
		if (!r) {
			r = cntlr_node_add_radio(c, n, p->radio);
			if (!r)
				continue;
		}

		if (p->macaddr_included) {
			struct sta *s = cntlr_find_sta(c->sta_table, (uint8_t *)p->macaddr);

			if (!s) {
				s = cntlr_add_sta(c, c->sta_table, (uint8_t *)p->macaddr);
				if (!s) {
					cntlr_warn(LOG_STA, "%s: failed to add bSTA " MACFMT "\n",
						   __func__, MAC2STR((uint8_t *)p->macaddr));
					return -1;
				}
			}

			s->is_bsta = true;
			memcpy(s->bsta_agent_almacaddr, n->almacaddr, 6);
			cntlr_radio_add_bsta(c, r, (uint8_t *)p->macaddr);
		}

		num++;
	}
	return 0;
}

#if (EASYMESH_VERSION >= 3)
int handle_proxied_encap_dpp(void *cntlr, struct cmdu_buff *cmdu,
				     struct node *n)
{
	trace("%s: --->\n", __func__);

	struct tlv *t;
	struct tlv *tv[PROXIED_ENCAP_DPP_MAX_NUMBER_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	struct tlv_1905_encap_dpp *encap;
	bool mac_present;
	//bool is_dpp_frame;
	int offset = 0;
#ifdef USE_LIBDPP
	struct encap_dpp_frame *frm;
	struct controller *c = (struct controller *)cntlr;
	uint8_t *enrollee;
	uint8_t *frame;
	uint16_t framelen;
#endif

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	/* One Encap 1905 DPP TLV */
	t = tv[PROXIED_ENCAP_1905_ENCAP_DPP_IDX][0];
	encap = (struct tlv_1905_encap_dpp *) t->data;

	/* Flags */
	mac_present = (encap->dst.flag & ENCAP_DPP_ENROLLEE_MAC_PRESENT);
	//is_dpp_frame = (encap->dst.flag & ENCAP_DPP_FRAME_INDICATOR);

	offset += sizeof(encap->dst);

	/* Destination STA MAC Address */
	if (!mac_present) {
		cntlr_err(LOG_DPP, "DPP: Proxied encap are expected to contain enrollee MAC\n");
		return -1;
	}

	offset += 6;

#ifdef USE_LIBDPP
	frm = (struct encap_dpp_frame *) &t->data[offset];
	enrollee = (uint8_t *) encap->dst.addr;
	/* Encapsulated frame length */
	frame = frm->frame;
	framelen = BUF_GET_BE16(frm->len);

	cntlr_dbg(LOG_DPP, "%s: Received frametype:%d from enrollee:"MACFMT"\n", __func__, frm->type, MAC2STR(enrollee));
#ifdef DEBUG
	dump(frame, framelen, "DPP Frame Received");
#endif /* DEBUG */
	switch (frm->type) {
#ifdef DPP_RELAY_CHRIP_PROXIED_ENCAP
	case DPP_PA_PRESENCE_ANNOUNCEMENT: {
		int ret;
		uint8_t *ptr;
		uint8_t *hash;
		uint16_t hashlen;

		ptr = frame;
		ptr++;

		ptr += 6; /* PA Type + oui + oui type + crypto suite */
		ptr++; /* frame type */
		hash = dpp_get_attr(ptr, framelen - labs(ptr - frame),
				    DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
				    &hashlen);

		ret = dpp_process_presence_announcement(c->dpp, enrollee,
							hash,
							hashlen, n);
		if (ret) {
			cntrl_err(LOG_DPP, "Failed to build presence announcement frame!\n");
			break;
		}
		break;
	}
#endif
	case 255: /* GAS */
	case DPP_PA_AUTHENTICATION_RESP:
	case DPP_PA_CONFIGURATION_RESULT:
	case DPP_PA_PEER_DISCOVERY_REQ:
	case DPP_PA_CONNECTION_STATUS_RESULT: {
		void *event = dpp_sm_create_event(c->dpp, enrollee,
					    DPP_EVENT_RX_FRAME, framelen,
					    frame);

		if (event) {
			dpp_trigger(c->dpp, enrollee, event);
			dpp_sm_free_event(event);
		}
		break;
	}
	default:
		dbg("%s: Unknown frame!\n", __func__);
		break;
	}
#endif

	return 0;
}

int handle_direct_encap_dpp(void *cntlr, struct cmdu_buff *cmdu,
				     struct node *n)
{
	trace("%s: --->\n", __func__);

#ifdef USE_LIBDPP
	struct controller *c = (struct controller *)cntlr;
	struct tlv *tlv;
	struct tlv *tlvs[DIRECT_ENCAP_DPP_MAX_NUMBER_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	struct tlv_dpp_message *dpp_msg;
	uint16_t framelen;
	uint8_t *frame;
	int frametype;

	UNUSED(c);

	if (!map_cmdu_validate_parse(cmdu, tlvs, ARRAY_SIZE(tlvs), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	/* POSSIBLY MID CHECK HERE? */

	/* One DPP Message TLV */
	tlv = tlvs[DIRECT_ENCAP_DPP_MESSAGE_IDX][0];
	dpp_msg = (struct tlv_dpp_message *) tlv->data;

	frame = dpp_msg->frame;
	framelen = BUF_GET_BE16(tlv->len);

	frametype = dpp_get_frame_type(frame + 1, framelen - 1);
	if (frametype == 255)
		return -1;

#ifdef DEBUG
	/* Encapsulated frame length */
	dump(frame, framelen, "DIRECT CMDU FRAME");
#endif /* DEBUG */

	switch (frametype) {
#if 0
	case DPP_PA_PRESENCE_ANNOUNCEMENT: {
		int ret;

		ret = dpp_process_presence_announcement(c->dpp, cmdu->origin,
							frame,
							framelen, NULL);
		if (ret) {
			dbg("Failed to build presence announcement frame!\n");
			break;
		}

		dbg("%s processing PA\n", (!ret ? "Succeeded" : "Failed"));
		/* FALLTHROUGH */
	}
#endif
	case DPP_PA_AUTHENTICATION_RESP:
	case DPP_PUB_AF_GAS_INITIAL_REQ:
	case DPP_PA_CONFIGURATION_RESULT:
	case DPP_PA_PEER_DISCOVERY_REQ:
	case DPP_PA_CONNECTION_STATUS_RESULT: {
		void *event =
			dpp_sm_create_event(c->dpp, cmdu->origin, DPP_EVENT_RX_FRAME, framelen, frame);

		if (event) {
			dpp_trigger(c->dpp, cmdu->origin, event);
			dpp_sm_free_event(event);
		}
		break;

	}
	default:
		warn("%s: Unknown frame!\n", __func__);
		break;
	}
#endif //USE_LIBDPPS
	return 0;
}

int handle_bss_configuration_request(void *cntlr, struct cmdu_buff *request_cmdu,
				     struct node *n)
{
	trace("%s: --->\n", __func__);

	int res;
	struct cmdu_buff *response_cmdu;
	struct controller *c = (struct controller *)cntlr;
	struct tlv *tlvs[BSS_CFG_REQ_MAX_NUMBER_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };

	cntlr_set_link_profile(c, n, request_cmdu);

	// section 17.1.53

	if (!map_cmdu_validate_parse(request_cmdu, tlvs, ARRAY_SIZE(tlvs), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	/* One SupportedService TLV */
	/* One AKM Suite Capabilities TLV */
	/* One or more AP Radio Basic Capabilities TLV */
	/* Zero or more Backhaul STA Radio Capabilities TLV */
	/* One Profile-2 AP Capability TLV */
	/* One or more AP Radio Advanced Capabilities TLV */
	/* One BSS Configuration Request TLV */


	response_cmdu = cntlr_gen_bss_configuration_response(c, request_cmdu);

	if (!response_cmdu)
		return -1;

	res = send_cmdu(c, response_cmdu);
	if (res == 0xffff) {
		res = -1;
		warn("%s: agent_send_cmdu failed.\n", __func__);
	} else {
		res = 0;
		dbg("%s: bss configuration response sent.\n", __func__);
	}

	cmdu_free(response_cmdu);

	return res;
}

int handle_bss_configuration_result(void *cntlr, struct cmdu_buff *cmdu,
				    struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *)cntlr;
	struct tlv *tlv;
	struct tlv *tlvs[BSS_CFG_RESULT_MAX_NUMBER_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };

	if (!map_cmdu_validate_parse(cmdu, tlvs, ARRAY_SIZE(tlvs), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	// todo: process tlvs
	/* One BSS Configuration Report TLV */
	tlv = tlvs[BSS_CFG_RESULT_BSS_CONFIG_REPORT_IDX][0];
	(void) tlv;
	(void) c;

	if (send_agent_list_to_all_nodes(c) != 0)
		warn("send_agent_list_to_all_nodes failed.\n");

	return 0;
}

#ifdef USE_LIBDPP
int handle_dpp_chirp_notification(void *cntlr, struct cmdu_buff *cmdu,
				    struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *c = (struct controller *)cntlr;
	struct tlv *tv[DPP_CHIPR_NOTIFICATION_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	int ret, num = 0;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	while (num < TLV_MAXNUM && tv[DPP_CHIPR_NOTIFICATION_DPP_CHIRP_VALUE_IDX][num]) {
		int offset = 0;
		uint8_t flag = 0;
		bool mac_present;
		bool hash_validity;
		uint8_t *mac = NULL;
		uint8_t *bi_hash = NULL;
		uint16_t bi_hashlen;

		flag = tv[DPP_CHIPR_NOTIFICATION_DPP_CHIRP_VALUE_IDX][num]->data[offset++];

		mac_present = (flag & DPP_CHIRP_ENROLLEE_MAC_PRESENT);
		if (!mac_present) {
			cntlr_warn(LOG_DPP, "%s: [%d] missing enrollee mac, skip\n", __func__, num);
			continue;
		}
		hash_validity = (flag & DPP_CHIRP_HASH_VALIDITY);
		if (!hash_validity) {
			cntlr_warn(LOG_DPP, "%s: [%d] hash validity not set, skip\n", __func__, num);
			continue;
		}

		mac = &tv[DPP_CHIPR_NOTIFICATION_DPP_CHIRP_VALUE_IDX][num]->data[offset];
		offset += 6;

		bi_hashlen = tv[DPP_CHIPR_NOTIFICATION_DPP_CHIRP_VALUE_IDX][num]->data[offset++];
		bi_hash = &tv[DPP_CHIPR_NOTIFICATION_DPP_CHIRP_VALUE_IDX][num]->data[offset];
		offset += bi_hashlen;

		ret = dpp_process_presence_announcement(c->dpp, mac,
							bi_hash,
							bi_hashlen, n);
		if (ret) {
			cntlr_err(LOG_DPP, "%s: Failed to process presence announcement frame!\n", __func__);
			break;
		}

		num++;
	}


	return 0;
}

int handle_dpp_bootstraping_uri_notificiation(void *cntlr, struct cmdu_buff *cmdu,
				    struct node *n)
{
	trace("%s: --->\n", __func__);

	struct controller *controller = (struct controller *)cntlr;
	struct tlv *tlv;
	struct tlv *tv[DPP_BOOTSTRAP_URI_NOTIF_MAX_NUMBER_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	/* DPP Chirp Value TLV */
	tlv = tv[DPP_BOOTSTRAP_URI_NOTIF_IDX][0];
	UNUSED(tlv);
	UNUSED(controller);

	return 0;
}
#endif /* USE_LIBDPP */
#endif /* EASYMESH_VERSION > 2 */

int handle_failed_connection_msg(void *cntlr, struct cmdu_buff *cmdu,
				 struct node *n)
{
	trace("%s: --->\n", __func__);
	return 0;
}

#if (EASYMESH_VERSION >= 6)
int handle_early_ap_cap_report(void *cntlr, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s:--->\n", __func__);

	struct controller *c = (struct controller *) cntlr;
	struct tlv *tv[EARLY_AP_CAP_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		warn("%s: map_cmdu_validate_parse failed,  err = (%d) '%s'\n",
		     __func__, map_error, map_strerror(map_error));
		return -1;
	}

	if (tv[EARLY_AP_CAP_REPORT_AP_CAPABILITY_IDX][0]) {
		struct tlv *t = tv[EARLY_AP_CAP_REPORT_AP_CAPABILITY_IDX][0];
		struct tlv_ap_cap *p = (struct tlv_ap_cap *)t->data;

		n->ap_cap = p->cap;
	}

	if (tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]) {
		struct tlv_wifi7_agent_caps *agnt_caps =
			(struct tlv_wifi7_agent_caps *)tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data;
		int offset = 0;
		int i;

		n->max_mld_num = agnt_caps->max_mldnum;
		n->max_aplink_num = agnt_caps->max_linknum & WIFI7_AP_MAX_LINKNUM_MASK;
		n->max_bstalink_num = agnt_caps->max_linknum & WIFI7_BSTA_MAX_LINKNUM_MASK;
		n->tid_link_caps = agnt_caps->flag;
		offset += 17;

		for (i = 0; i < agnt_caps->num_radio; i++) {
			struct wifi7_agent_radio_caps *agnt_rad_caps = (struct wifi7_agent_radio_caps *)
				&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
			struct wifi_wifi7_capabilities *ap7_caps, *bsta7_caps;
			struct wifi7_radio_cap *cap;
			struct netif_radio *r;
			uint8_t *mlo_freqsep = NULL;
			int cap_iter;
			int j;

			r = cntlr_node_add_radio(c, n, agnt_rad_caps->ruid);
			if (!r) {
				warn("%s: failed to add radio:"MACFMT"\n",
				     __func__, MAC2STR(agnt_rad_caps->ruid));
				return -1;
			}

			ap7_caps = &r->radio_el->wifi7caps_ap;
			bsta7_caps = &r->radio_el->wifi7caps_bsta;

			ap7_caps->str = agnt_rad_caps->ap_mlo & WIFI7_AP_MLO_STR_SUPPORTED;
			ap7_caps->nstr = agnt_rad_caps->ap_mlo & WIFI7_AP_MLO_NSTR_SUPPORTED;
			ap7_caps->emlsr = agnt_rad_caps->ap_mlo & WIFI7_AP_MLO_EMLSR_SUPPORTED;
			ap7_caps->emlmr = agnt_rad_caps->ap_mlo & WIFI7_AP_MLO_EMLMR_SUPPORTED;

			bsta7_caps->str = agnt_rad_caps->bsta_mlo & WIFI7_BSTA_MLO_STR_SUPPORTED;
			bsta7_caps->nstr = agnt_rad_caps->bsta_mlo & WIFI7_BSTA_MLO_NSTR_SUPPORTED;
			bsta7_caps->emlsr = agnt_rad_caps->bsta_mlo & WIFI7_BSTA_MLO_EMLSR_SUPPORTED;
			bsta7_caps->emlmr = agnt_rad_caps->bsta_mlo & WIFI7_BSTA_MLO_EMLMR_SUPPORTED;
			offset += 32;

#define NUM_MLO_MODES 6
			for (cap_iter = 0; cap_iter < NUM_MLO_MODES; cap_iter++) {
				cap = (struct wifi7_radio_cap *)
					&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
				offset++;

				for (j = 0; j < cap->num; j++) {
					struct wifi7_radio_cap_record *cap_record = (struct wifi7_radio_cap_record *)
						&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
					struct netif_radio *subr;

					subr = cntlr_find_radio(c, cap_record->ruid);
					if (!subr) {
						trace("|%s:%d| did not find subradio:"MACFMT"\n",
						      __func__, __LINE__, MAC2STR(cap_record->ruid));
						offset += 7;
						continue;
					}

					if (cap_iter == 0)
						mlo_freqsep = (uint8_t *)&subr->radio_el->wifi7caps_ap.str_freqsep;
					else if (cap_iter == 1)
						mlo_freqsep = (uint8_t *)&subr->radio_el->wifi7caps_ap.emlsr_freqsep;
					else if (cap_iter == 2)
						mlo_freqsep = (uint8_t *)&subr->radio_el->wifi7caps_ap.emlmr_freqsep;
					else if (cap_iter == 3)
						mlo_freqsep = (uint8_t *)&subr->radio_el->wifi7caps_bsta.str_freqsep;
					else if (cap_iter == 4)
						mlo_freqsep = (uint8_t *)&subr->radio_el->wifi7caps_bsta.emlsr_freqsep;
					else if (cap_iter == 5)
						mlo_freqsep = (uint8_t *)&subr->radio_el->wifi7caps_bsta.emlmr_freqsep;
					else {
						err("|%s:%d| cap_iter:%d out of bounds!!\n", __func__, __LINE__, cap_iter);
						return -1;
					}
					*mlo_freqsep = (cap_record->freqsep & WIFI7_RADIO_FREQSEP_MASK);
					offset += 7;
				}
			}
		}
	}
#undef NUM_MLO_MODES

	return 0;
}

int cntlr_send_ap_mld_configuration_request(struct controller *c, struct node *n)
{
	struct cmdu_buff *cmdu;
	struct netif_radio *r = NULL;
	bool has_wifi7 = false;

	if (!n)
		return -1;

	list_for_each_entry(r, &n->radiolist, list) {
		if (cntlr_radio_support_wifi7(&r->radio_el->wifi7caps_ap)) {
			has_wifi7 = true;
			break;
		}
	}

	if (!has_wifi7)
		return -1;

	cmdu = cntlr_gen_ap_mld_configuration_request(c, n->almacaddr, &r->radio_el->wifi7caps_ap);
	if (!cmdu)
		return -1;

	n->apmldconf_mid = send_cmdu(c, cmdu);
	cmdu_free(cmdu);

	return 0;
}

int cntlr_send_bsta_mld_configuration_request(struct controller *c, struct node *n)
{
	struct cmdu_buff *cmdu;
	struct netif_radio *r = NULL;
	bool has_wifi7 = false;

	if (!n)
		return -1;

	list_for_each_entry(r, &n->radiolist, list) {
		if (cntlr_radio_support_wifi7(&r->radio_el->wifi7caps_bsta)) {
			has_wifi7 = true;
			break;
		}
	}

	if (!has_wifi7)
		return -1;

	cmdu = cntlr_gen_bsta_mld_configuration_request(c, n->almacaddr, &r->radio_el->wifi7caps_bsta);
	if (!cmdu)
		return -1;

	n->bstamldconf_mid = send_cmdu(c, cmdu);
	cmdu_free(cmdu);

	return 0;
}

#endif

//#define CMDU_TYPE_1905_START	0x0001
//#define CMDU_TYPE_1905_END	0x0009


#define CMDU_TYPE_MAP_START	0x8000
#if (EASYMESH_VERSION >= 6)
#define CMDU_TYPE_MAP_END	0x8047
#elif (EASYMESH_VERSION >= 3)
#define CMDU_TYPE_MAP_END	0x8037
#else
#define CMDU_TYPE_MAP_END	0x8033
#endif

/* mind the holes in the following two tables */
static const struct map_cmdu_calltable_t i1905ftable[] = {
	[0x00] = {
		.handle = handle_topology_discovery,
	},
	[0x01] = {
		.handle = handle_topology_notification,
	},
	[0x02] = {
		.handle = handle_topology_query,
	},
	[0x03] = {
		.handle = handle_topology_response,
	},
	[0x04] = {
		.handle = handle_vendor_specific,
	},
	[0x06] = {
		.handle = handle_link_metrics_response,
	},
	[0x07] = {
		.handle = handle_ap_autoconfig_search,
	},
	[0x08] = {
		.handle = handle_ap_autoconfig_response,
	},
	[0x09] = {
		.handle = handle_ap_autoconfig_wsc,
	},
};


static const struct map_cmdu_calltable_t cntlr_mapftable[] = {
	[0x00] = {
		.handle = handle_1905_ack,
	},
	[0x02] = {
		.handle = handle_ap_caps_report,
	},
	[0x05] = {
		.handle = handle_channel_pref_report,
	},
	[0x07] = {
		.handle = handle_channel_sel_response,
	},
	[0x08] = {
		.handle = handle_oper_channel_report,
	},
	[0x0a] = {
		.handle = handle_client_cap_report,
	},
	[0x0c] = {
		.handle = handle_ap_metrics_response,
	},
	[0x0e] = {
		.handle = handle_sta_link_metrics_response,
	},
	[0x10] = {
		.handle = handle_unassoc_sta_link_metrics_response,
	},
	[0x12] = {
		.handle = handle_beacon_metrics_response,
	},
	[0x15] = {
		.handle = handle_sta_steer_btm_report,
	},
	[0x17] = {
		.handle = handle_sta_steer_complete,
	},
	[0x18] = {
		.handle = handle_hld_message,
	},
	[0x1a] = {
		.handle = handle_backhaul_sta_steer_response,
	},
	[0x1c] = {
		.handle = handle_channel_scan_report,
	},
	[0x22] = {
		.handle = handle_sta_disassoc_stats,
	},
	[0x25] = {
		.handle = handle_assoc_status_notification,
	},
	[0x26] = {
		.handle = handle_tunneled_message,
	},
	[0x28] = {
		.handle = handle_backhaul_sta_caps_report,
	},
#if (EASYMESH_VERSION > 2)
	[0x29] = {
		.handle = handle_proxied_encap_dpp,
	},
	[0x2a] = {
		.handle = handle_direct_encap_dpp,
	},
	[0x2c] = {
		.handle = handle_bss_configuration_request,
	},
	[0x2e] = {
		.handle = handle_bss_configuration_result,
	},
#ifdef USE_LIBDPP
	[0x2f] = {
		.handle = handle_dpp_chirp_notification,
	},
	[0x31] = {
		.handle = handle_dpp_bootstraping_uri_notificiation,
	},
#endif /* USE_LIBDPP */
#endif
	[0x33] = {
		.handle = handle_failed_connection_msg,
	},
#if (EASYMESH_VERSION > 5)
	[0x43] = {
		.handle = handle_early_ap_cap_report,
	}
#endif

};


bool is_cmdu_for_us(void *module, uint16_t type)
{
	struct controller *c = (struct controller *)module;

	/* TODO: handle cmdu types relevant for operating profile. */

	/* discard responses that are not to ACS during initilization */
	if (c->state == CNTLR_INIT &&
			(type != CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE &&
			 type != CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH)) {
		trace("Controller is initializing, waitfor ACS response or "\
				"start timer (%ds) to finish\n",
				timer_remaining_ms(&c->start_timer));
		return false;
	}

	if (type >= CMDU_TYPE_1905_START && type <= CMDU_TYPE_1905_END) {
		if (i1905ftable[type].handle)
			return true;
	} else if (type >= CMDU_TYPE_MAP_START && type <= CMDU_TYPE_MAP_END) {
		if (cntlr_mapftable[type - CMDU_TYPE_MAP_START].handle)
			return true;
	}

	return false;
}

int cntlr_handle_map_event(void *module, uint16_t cmdutype, uint16_t mid,
		char *rxif, uint8_t *src, uint8_t *origin, uint8_t *tlvs, int len)
{
	struct controller *c = (struct controller *)module;
	const struct map_cmdu_calltable_t *f;
	struct cmdu_buff *cmdu = NULL;
	int ret = 0;
	int idx;
	uint16_t resp_type;
	void *cookie;
	struct cmdu_ackq_entry *entry;
	struct node *n;

	trace("%s: ---> cmdu = %d (%04x), ifce \"%s\"\n", __func__, cmdutype, cmdutype,
			rxif);

	/* If request CMDU is from us, do not process is. This is for
	 * situation where controller and agent are on the same device,
	 * share the same MAC address and send CMDU's to each other. */
	if (hwaddr_equal(c->almacaddr, origin)) {
		// Do we expect response for this CMDU ?
		resp_type = cntlr_cmdu_expect_response(c, cmdutype);
		entry = cmdu_ackq_lookup(&c->cmdu_ack_q, resp_type, mid, origin);
		if (entry)
			return 0;
	}

	ret = cmdu_ackq_dequeue(&c->cmdu_ack_q, cmdutype, mid, origin, &cookie);
	if (ret == 0)
		cmdu_free((struct cmdu_buff *) cookie);

	if (cmdutype >= CMDU_TYPE_MAP_START) {
		idx = cmdutype - CMDU_TYPE_MAP_START;
		f = cntlr_mapftable;
	} else {
		idx = cmdutype;
		f = i1905ftable;
	}

	n = cntlr_find_node(c, origin);
	if (!n) {
		if (cmdutype != CMDU_TYPE_TOPOLOGY_DISCOVERY &&
		    cmdutype != CMDU_TYPE_TOPOLOGY_NOTIFICATION &&
		    cmdutype != CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH)
			return -1;
	}

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

	cmdu = cmdu_alloc_custom(cmdutype, &mid, rxif, src, tlvs, len);
	if (!cmdu) {
		warn("%s: cmdu_alloc_custom() failed!\n", __func__);
		return -1;
	}
	memcpy(cmdu->origin, origin, 6);
	dbg("%s: cmdu_alloc_custom() succeeded! cmdu->cdata->hdr.mid %u\n", __func__, cmdu_get_mid(cmdu));

	if (f[idx].handle) {
		ret = f[idx].handle(c, cmdu, n);
		cntlr_notify_plugins(c, cmdu);
	}

	if (!n)
		n = cntlr_find_node(c, origin);

	if (n)
		timestamp_update(&n->last_cmdu);

	cmdu_free(cmdu);
	return ret;
}

int cntlr_set_link_profile(struct controller *c, struct node *n,
			   struct cmdu_buff *cmdu)
{
	int p = c->cfg.map_profile;
	int np = map_cmdu_get_multiap_profile(cmdu);

	if (p <= MULTIAP_PROFILE_1) {
		n->map_profile = MULTIAP_PROFILE_1;
		return n->map_profile;
	}

	if (np > p) {
		n->map_profile = p;
		return p;
	}

	n->map_profile = np;
	return np;
}
