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

#include "agent_map.h"

#include <1905_tlvs.h>
#include <cmdu.h>
#include <timer.h>
#include <cmdu_ackq.h>
#include <wifidefs.h>
#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
#include <dpp_api.h>
#include <dpputils.h>
#endif /* USE_LIBDPP */
#include <openssl/types.h>
#endif
#include <easy/bufutil.h>
#include <easy/timestamp.h>
#include <easy/utils.h>
#include <easymesh.h>
#include <i1905_wsc.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/list.h>
#include <map_module.h>
#include <net/if.h>
#include <openssl/evp.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <uci.h>
#ifdef AGENT_SYNC_DYNAMIC_CNTLR_CONFIG
#include <cntlrsync.h>
#endif

#include "agent.h"
#include "agent_cmdu.h"
#include "agent_tlv.h"
#include "agent_ubus.h"
#include "assoc_ctrl.h"
#include "autoconfig.h"
#include "backhaul.h"
#include "backhaul_blacklist.h"
#include "config.h"
#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
#include "dpp.h"
#include "dppfrag.h"
#endif /* USE_LIBDPP */
#include "qos.h"
#endif
#include "extension.h"
#include "scan.h"
#if (EASYMESH_VERSION >= 6)
#include "mld.h"
#endif
#include "timer_impl.h"
#include "unasta.h"

#include "utils/allmac.h"
#include "utils/debug.h"
#include "utils/1905_ubus.h"
#include "utils/libcrypto.h"
#include "utils/utils.h"

#include "wifi.h"
#include "wifi_opclass.h"
#include "wifi_scanresults.h"
#include "wifi_ubus.h"
#include "vendor.h"

struct timespec;

#define BSTA_STEER_TIMEOUT 20

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif


#define FRAG_DATA_SIZE         (1400)
#define UBUS_TIMEOUT            1000

/* TODO/FIXME: hardcoded 5 sec */
#define UTIL_THRESHOLD_TIMER	(5 * 1000)

#define MAX_RADIO 20

struct channel_response {
	uint8_t radio_id[6];
	uint8_t response;
};

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

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;
}

int send_topology_notification(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_topology_query(void *agent, uint8_t *origin)
{
	struct agent *a = (struct agent *) agent;
	struct cmdu_buff *cmdu;
	int ret;

	cmdu = agent_gen_topology_query(a, origin);
	if (!cmdu)
		return -1;

	ret = CMDU_HOOK(agent, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(agent, cmdu);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	return 0;
}

int send_topology_response(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_ap_autoconfig_search(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_ap_autoconfig_response(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_ap_autoconfig_wsc(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_1905_ack(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_ap_caps_report(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_channel_pref_report(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_oper_channel_report(void *agent, struct cmdu_buff *rx_cmdu)
{
	struct agent *a = (struct agent *) agent;
	struct cmdu_buff *cmdu;
	int ret;

	cmdu = agent_gen_oper_channel_response(a, NULL, 0, 0, 1);
	if (!cmdu)
		return -1;

	ret = CMDU_HOOK(agent, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(agent, cmdu);
	} else {
		agnt_warn(LOG_CHANNEL, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	return 0;
}

int send_sta_steer_complete(void *agent, uint8_t *origin)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	struct agent *a = (struct agent *) agent;
	struct cmdu_buff *cmdu;
	uint16_t mid = 0;
	int ret;

	cmdu = cmdu_alloc_simple(CMDU_STEERING_COMPLETED, &mid);
	if (!cmdu) {
		agnt_dbg(LOG_STEER, "%s: -ENOMEM\n", __func__);
		return -1;
	}

	memcpy(cmdu->origin, origin, 6);

	if (a->is_sta_steer_start) {
		/**
		 * Here we are sending the steering completed message
		 * so we need to reset all the values of the
		 * steering opportunity
		 */
		a->is_sta_steer_start = 0;
		a->sta_steerlist_count = 0;
		memset(a->sta_steer_list, 0, sizeof(a->sta_steer_list));
		/* stop the timer if it is running */
		timer_del(&a->sta_steer_req_timer);
	}

	cmdu_put_eom(cmdu);

	ret = CMDU_HOOK(agent, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		agnt_warn(LOG_STEER, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	return 0;
}

int send_steer_btm_report(void *agent, uint8_t *origin,
		uint8_t *target_bssid, uint8_t *src_bssid,
		uint8_t *sta, uint8_t status_code)
{

	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	int ret = 0, all_complete = 1;
	struct cmdu_buff *cmdu;
	uint16_t mid = 0;

	cmdu = cmdu_alloc_simple(CMDU_CLIENT_STEERING_BTM_REPORT, &mid);
	if (!cmdu) {
		agnt_dbg(LOG_STEER, "%s: -ENOMEM\n", __func__);
		return -1;
	}

	memcpy(cmdu->origin, origin, 6);

	/* Clent Steering BTM Report TLV 17.2.30 */
	ret = agent_gen_steer_btm_report(a, cmdu, target_bssid,
			src_bssid, sta, status_code);
	if (ret) {
		cmdu_free(cmdu);
		return -1;
	}

	cmdu_put_eom(cmdu);

	ret = CMDU_HOOK(agent, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(agent, cmdu);
	} else {
		agnt_warn(LOG_STEER, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	agnt_trace(LOG_STEER, "is_steer is %d steer count %d\n",
			a->is_sta_steer_start, a->sta_steerlist_count);

	/**
	 * Check that the report is sent for a steering opportunity.
	 * Here we store the status in the sta list and check
	 * if the steering completed message can be sent
	 */
	if (a->is_sta_steer_start) {
		int i;

		/* iterate list of clients attempted to be steered */
		for (i = 0; i < a->sta_steerlist_count; i++) {

			/* mark all steered clients as completed */
			ret = memcmp(sta, a->sta_steer_list[i].sta_mac, 6);
			if (ret == 0)
				a->sta_steer_list[i].complete = 1;
		}

		/**
		 * Now we need to check if the steering completed
		 * message can be sent
		 */
		for (i = 0; i < a->sta_steerlist_count; i++) {
			if (a->sta_steer_list[i].complete != 1) {
				all_complete = 0;
				break;
			}
		}

		if (all_complete) {
			/* Here we need to send the steering completed CMDU */
			send_sta_steer_complete(agent, origin);
		}
	}

	return ret;
}

int send_sta_caps_report(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_ap_metrics_response(void *agent, struct cmdu_buff *rx_cmdu,
			     struct node *n)
{
	trace("%s: --->\n", __func__);
	struct agent *a = (struct agent *)agent;
	struct cmdu_buff *cmdu;
	int ret;

	cmdu = agent_gen_ap_metrics_response(a, rx_cmdu, n);
	if (!cmdu)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	return 0;
}

int send_sta_link_metrics_response(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_unassoc_sta_link_metrics_response(void *agent, int num_metrics,
			struct wifi_unassoc_sta_element *metrics, uint8_t opclass)
{
	struct cmdu_buff *cmdu;
	struct agent *a = (struct agent *)agent;
	uint16_t mid = 0;
	int ret = -1;

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

	cmdu = cmdu_alloc_simple(CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE, &mid);
	if (!cmdu) {
		dbg("%s: -ENOMEM\n", __func__);
		return -1;
	}

	memcpy(cmdu->origin, a->cntlr_almac, 6);

	ret = agent_gen_tlv_unassoc_sta_lm_report(a, cmdu, num_metrics, metrics, opclass);
	if (ret) {
		cmdu_free(cmdu);
		return -1;
	}

	cmdu_put_eom(cmdu);

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP)
		ret = agent_send_cmdu(a, cmdu);
	else
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));

	cmdu_free(cmdu);
	return ret;
}

int send_beacon_metrics_response(void *agent, uint8_t *sta_addr,
		uint8_t report_elems_nr, uint8_t *report_elem,
		uint16_t elem_len)
{
	struct cmdu_buff *resp;
	struct agent *a = (struct agent *) agent;
	int ret = 0;

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

	resp = agent_gen_cmdu_beacon_metrics_resp(a, sta_addr,
						  report_elems_nr, report_elem,
						  elem_len);
	if (!resp)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
			resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(a, resp);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);

	return ret;
}

int send_backhaul_sta_steer_response(void *agent, struct netif_bk *bk,
				     char *ul_ifname)
{
	struct agent *a = (struct agent *) agent;
	struct cmdu_buff *cmdu = NULL;
	int ret = -1;

	cmdu = agent_gen_cmdu_backhaul_steer_resp(a,
						  bk->bsta_steer.target_bssid,
						  bk->macaddr,
						  bk->bsta_steer.reason,
						  bk->bsta_steer.mid);
	if (!cmdu)
		return ret;

	memcpy(cmdu->origin, a->cntlr_almac, 6);
	if (ul_ifname)
		strncpy(cmdu->dev_ifname, ul_ifname, IFNAMSIZ - 1);

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(a, cmdu);
	} else {
		agnt_warn(LOG_BSTEER, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);
	return ret;
}

#if 0
int send_channel_scan_report(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}
#endif

int send_sta_disassoc_stats(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_assoc_status_notification(struct agent *a, int num_data, void *data)
{
	struct node *n = NULL;
	struct cmdu_buff *cmdu;

	/* TODO: ensure 1905 sends msg as reliable multicast */

	/* cntlr may not have its own node allocated */
	cmdu = agent_gen_association_status_notify(a, num_data, data);
	if (cmdu) {
		memcpy(cmdu->origin, a->cfg.cntlr_almac, ETH_ALEN);
		agent_send_cmdu(a, cmdu);
		cmdu_free(cmdu);
	}

	list_for_each_entry(n, &a->nodelist, list) {
		if (!memcmp(n->alid, a->cfg.cntlr_almac, ETH_ALEN))
			/* skip cntlr - already sent */
			continue;

		cmdu = agent_gen_association_status_notify(a, num_data, data);
		if (!cmdu)
			continue;

		memcpy(cmdu->origin, n->alid, 6);
		agent_send_cmdu(a, cmdu);
		cmdu_free(cmdu);
	}

	return 0;
}

int send_tunneled_message(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_backhaul_sta_caps_report(void *agent, struct cmdu_buff *cmdu)
{
	return 0;
}

int send_failed_connection_msg(void *agent, uint8_t *sta, int status_code, int reason_code)
{
	struct cmdu_buff *cmdu = NULL;
	struct agent *a = (struct agent *)agent;
	int ret;

	cmdu = agent_gen_failed_connection(a, sta, status_code, reason_code);
	if (!cmdu)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	return 0;
}

#if (EASYMESH_VERSION > 2)
int send_bss_configuration_request(struct agent *agent)
{
	struct cmdu_buff *req_cmdu;
	int ret;

	req_cmdu = agent_gen_bss_configuration_request(agent);
	if (!req_cmdu) {
		dbg("%s: agent_gen_bss_configuration_request failed.\n", __func__);
		return -1;
	}

	memcpy(req_cmdu->origin, agent->cntlr_almac, sizeof(req_cmdu->origin));

	ret = CMDU_HOOK(agent, cmdu_get_type(req_cmdu), cmdu_get_mid(req_cmdu),
			req_cmdu->origin, req_cmdu->cdata, cmdu_size(req_cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(agent, req_cmdu);
		if (ret == 0xffff) {
			ret = -1;
			dbg("%s: agent_send_cmdu failed.\n", __func__);
		} else {
			ret = 0;
			dbg("%s: bss configuration request sent.\n", __func__);
		}
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(req_cmdu));
	}

	cmdu_free(req_cmdu);
	return ret;
}

int send_bss_configuration_result(struct agent *agent)
{
	struct cmdu_buff *resp;
	int ret;

	resp = agent_gen_bss_configuration_result(agent);
	if (!resp) {
		dbg("%s: agent_gen_bss_configuration_result failed.\n", __func__);
		return -1;
	}

	memcpy(resp->origin, agent->cntlr_almac, sizeof(resp->origin));

	ret = CMDU_HOOK(agent, cmdu_get_type(resp), cmdu_get_mid(resp),
			resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(agent, resp);
		if (ret == 0xffff) {
			ret = -1;
			dbg("%s: agent_send_cmdu failed.\n", __func__);
		} else {
			ret = 0;
			dbg("%s: bss configuration result sent.\n", __func__);
		}
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);
	return ret;
}
#endif //EASYMESH_VERSION > 2

#if 0
static const map_cmdu_sendfunc_t i1905txftable[] = {
	[0x01] = send_topology_notification,
	//[0x02] = send_topology_query,
	[0x03] = send_topology_response,
	[0x07] = send_ap_autoconfig_search,
	[0x08] = send_ap_autoconfig_response,
	[0x09] = send_ap_autoconfig_wsc,
};
#endif

#if 0
static const map_cmdu_sendfunc_t agent_maptxftable[] = {
	[0x00] = send_1905_ack,
	[0x02] = send_ap_caps_report,
	[0x05] = send_channel_pref_report,
	//[0x07] = send_channel_sel_response,
	[0x08] = send_oper_channel_report,
	[0x0a] = send_sta_caps_report,
	[0x0c] = send_ap_metrics_response,
	[0x0e] = send_sta_link_metrics_response,
	[0x10] = send_unassoc_sta_link_metrics_response,
	//[0x12] = send_beacon_metrics_response,
	//[0x15] = send_steer_btm_report,
	//[0x17] = send_sta_steer_complete,
	[0x1a] = send_backhaul_sta_steer_response,
	[0x1c] = send_channel_scan_report,
	[0x22] = send_sta_disassoc_stats,
	[0x25] = send_assoc_status_notification,
	[0x26] = send_tunneled_message,
	[0x28] = send_backhaul_sta_caps_report,
	[0x33] = send_failed_connection_msg,
};
#endif


int handle_topology_discovery(void *agent, struct cmdu_buff *cmdu,
			     struct node *n)
{
	trace("%s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	static const struct blobmsg_policy bk_attr[3] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "backhaul_macaddr", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "backhaul_device_id", .type = BLOBMSG_TYPE_STRING },
	};
	struct tlv *tv[TOPOLOGY_DISCOVERY_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	char ul_ifname[IFNAMSIZ] = {0};
	struct blob_buf bk = {0};
	struct blob_attr *tb[3];
	uint8_t almac[6] = {0};
	uint8_t hwaddr[6] = {0};
	struct tlv_aladdr *aladdr;
	struct tlv *t;

	t = map_cmdu_get_tlv(cmdu, TLV_TYPE_AL_MAC_ADDRESS_TYPE);
	if (!t) {
		dbg("%s: Malformed topology notification!\n", __func__);
		return -1;
	}

	aladdr = (struct tlv_aladdr *) t->data;

	memcpy(almac, aladdr->macaddr, 6);

	if (hwaddr_is_zero(almac)) {
		trace("%s: Discard topology notification from aladdr = 0!\n",
			__func__);

		return -1;
	}

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

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

	memcpy(hwaddr, tv[TOPOLOGY_DISCOVERY_MAC_ADDR_TYPE_IDX][0]->data, 6);

	blob_buf_init(&bk, 0);

	if (!blobmsg_add_json_from_file(&bk, MAP_UPLINK_PATH)) {
		dbg("%s: Failed to parse %s\n", __func__,
		    MAP_UPLINK_PATH);
		goto out;
	}

	blobmsg_parse(bk_attr, 3, tb, blob_data(bk.head), blob_len(bk.head));
	if (!tb[0])
		goto out;

	strncpy(ul_ifname, blobmsg_data(tb[0]), IFNAMSIZ - 1);

	if (!strncmp(ul_ifname, cmdu->dev_ifname, IFNAMSIZ)) {
		memcpy(a->ul_dev.ul_almac, almac, 6);
		memcpy(a->ul_dev.ul_hwaddr, hwaddr, 6);

		if (!tb[1] || !tb[2]) {
			runCmd("/lib/wifi/multiap set_uplink_backhaul_info "
					MACFMT " " MACFMT,
					MAC2STR(a->ul_dev.ul_almac),
					MAC2STR(a->ul_dev.ul_hwaddr));
		}
	}

out:
	blob_buf_free(&bk);
	return 0;
}

int handle_topology_notification(void *agent, struct cmdu_buff *cmdu,
				 struct node *n)
{
	struct agent *a = (struct agent *) agent;
	uint8_t almac[6] = {0};
	struct tlv_aladdr *aladdr;
	struct tlv *t;
	struct tlv *tv[TOPOLOGY_NOTIFICATION_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

	trace("%s: --->\n", __func__);
	t = map_cmdu_get_tlv(cmdu, TLV_TYPE_AL_MAC_ADDRESS_TYPE);
	if (!t) {
		dbg("%s: Malformed topology notification!\n", __func__);
		return -1;
	}

	aladdr = (struct tlv_aladdr *) t->data;

	memcpy(almac, aladdr->macaddr, 6);

	if (hwaddr_is_zero(almac)) {
		trace("%s: Discard topology notification from aladdr = 0!\n",
			__func__);

		return -1;
	}

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

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

	if (tv[TOPOLOGY_NOTIFICATION_CLIENT_ASSOCIATION_EVENT_IDX][0]) {
		struct tlv_client_assoc_event *p = (struct tlv_client_assoc_event *)
			tv[TOPOLOGY_NOTIFICATION_CLIENT_ASSOCIATION_EVENT_IDX][0]->data;
		struct sta *s;
#ifdef UNASSOC_STA_CONT_MONITOR
		struct wifi_radio_element *re = NULL;
#endif

		dbg("%s: got event %d ifname %s for client_addr "MACFMT" bssid "MACFMT"\n",
		     __func__, p->event, cmdu->dev_ifname, MAC2STR(p->macaddr), MAC2STR(p->bssid));

		if (!(p->event & CLIENT_EVENT_MASK)) {
			/* Client has left the BSS */
			return 0;
		}

		if (!memcmp(cmdu->origin, a->almac, 6)) {
			/* Client has joined BSS on this node */
			return 0;
		}

		/* Client has joined BSS on another node */
		s = agent_get_sta(a, p->macaddr);
		if (s) {
			struct netif_ap *ap;

			ap = agent_get_ap(a, s->bssid);
			if (ap) {
				/* Associated to another node, disconnect from self */
				dbg("%s: disconnecting sta "MACFMT" from %s\n", __func__,
				    MAC2STR(p->macaddr), ap->ifname);
				wifi_disconnect_sta(ap->ifname, p->macaddr,
					WIFI_REASON_BSS_TRANSITION_DISASSOC);
			}
		}

#ifdef UNASSOC_STA_CONT_MONITOR
		/* Add to unassociated clients list and start monitoring */
		/* Note: start on all radios, as no request for specific opclass */
		list_for_each_entry(re, &a->radiolist, list) {
			bool rem;

			radio_monitor_unassoc_sta(re, p->macaddr,
					re->current_opclass, re->current_channel, &rem);
		}
#endif

	}
	return 0;
}

int handle_topology_query(void *agent, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);
	struct cmdu_buff *resp;
	struct agent *a = (struct agent *) agent;
	int ret;

#if 0 // Disable due to ieee1905 topology plugin sending without profile
	agent_set_link_profile(a, n, cmdu);
#endif
	resp = agent_gen_topology_response(a, cmdu->origin, cmdu_get_mid(cmdu), cmdu->dev_ifname);
	if (!resp)
		return -1;

	if (hwaddr_is_zero(resp->origin)) {
		memcpy(resp->origin, cmdu->dev_macaddr, 6);
		strncpy(resp->dev_ifname, cmdu->dev_ifname, IFNAMSIZ - 1);
	}

	ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
		        cmdu->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, resp);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);

	return 0;
}

/* 9.4.2.37 - Neighbor Report El.: BSID Information field */
static uint32_t get_basic_bssid_info(struct netif_ap *ap)
{
	trace("%s: --->\n", __func__);

	uint32_t bssid_info = 0; /* IEEE 802.11-2016 9.4.2.37 */

	/* AP Reachability: 3 - Reachable */
	agent_bssid_info_set(bssid_info, BSSID_INFO_REACHABILITY_B0);
	agent_bssid_info_set(bssid_info, BSSID_INFO_REACHABILITY_B1);

	/* Security: 1 - AP supports same provisioning */
	agent_bssid_info_set(bssid_info, BSSID_INFO_SECURITY);

	/* Key Scope: 1 - AP has the same authenticator */
	agent_bssid_info_set(bssid_info, BSSID_INFO_KEY_SCOPE);

	/* Capabilities: Spectrum Management: 0 */
	// agent_bssid_info_set(bssid_info, BSSID_INFO_CAP_SPECTRUM_MGMT);

	/* Capabilities: QoS */
	agent_bssid_info_set(bssid_info, BSSID_INFO_CAP_WMM);

	/* Capabilities: APSD: 0 */
	// agent_bssid_info_set(bssid_info, BSSID_INFO_CAP_APSD);

	/* Capabilities: Radio Measurement: 1 */
	agent_bssid_info_set(bssid_info, BSSID_INFO_CAP_RADIO_MEAS);

	/* Capabilities: Delayed Block Ack: 0 */
	// agent_bssid_info_set(bssid_info, BSSID_INFO_CAP_DELAYED_BA);

	/* Capabilities: Immediate Block Ack: 0 */
	// agent_bssid_info_set(bssid_info, BSSID_INFO_CAP_IMMEDIATE_BA);

	/* Mobility Domain: 0 */
	// agent_bssid_info_set(bssid_info, BSSID_INFO_MOBILITY_DOMAIN);

	/* HT & VHT will be updated based on AP capabilities exchange */

	/* Fine Timing Measurement Responder field: 0 */
	// agent_bssid_info_set(bssid_info, BSSID_INFO_FMT);

	return bssid_info;
}

static int neighbor_list_add(struct netif_ap *ap, struct nbr *nbr,
				uint8_t *radio_mac)
{
	struct neighbor *new;

	/* add new neighbor node */
	new = malloc(sizeof(struct neighbor));
	if (!new) {
		warn("OOM: neighbor entry malloc failed!\n");
		return -1;
	}
	memset(new, 0, sizeof(struct neighbor));
	memcpy(new->nbr.bssid, nbr->bssid, 6);
	new->nbr.bssid_info = nbr->bssid_info;
	new->nbr.reg = nbr->reg;
	new->nbr.channel = nbr->channel;
	new->nbr.phy = nbr->phy;
	timestamp_update(&new->tsp);
	new->flags &= ~NBR_FLAG_DRV_UPDATED;
	if (radio_mac)
		memcpy(new->radio_mac, radio_mac, 6);
	list_add(&new->list, &ap->nbrlist);
	/* keep number of neighbors up to date */
	ap->nbr_nr++;

	return 0;
}

static int maybe_update_neighbor(struct agent *a,
		struct netif_ap *ap, uint8_t *bssid)
{
	trace("%s: --->\n", __func__);

	struct neighbor *n = NULL;

	list_for_each_entry(n, &ap->nbrlist, list) {
		if (!memcmp(n->nbr.bssid, bssid, 6)) {
			/* bss found on the nbrlist */
			struct wifi_radio_element *r;
			struct wifi_scanresults_entry *e = NULL;

			/* last observed time (topology) */
			timestamp_update(&n->tsp);

			r = agent_get_radio_by_name(a, ap->radio_name);
			if (r)
				e = wifi_scanresults_get_entry(&r->scanresults, bssid);

			if (e && (n->nbr.reg != e->opclass
						|| n->nbr.channel != e->bss.channel)) {

				/* update channel and/or opclass */
				n->nbr.reg = e->opclass;
				n->nbr.channel = e->bss.channel;
				/* override with actual scan tsp */
				n->tsp = e->tsp;

				/* synchronize nbr data in driver */
				n->flags &= ~NBR_FLAG_DRV_UPDATED;
				reschedule_nbrlist_update(ap);
			}

			return 0; /* neighbor updated */
		}
	}

	return -1;
}

static int add_neighbor(struct agent *a,
		struct netif_ap *ap, uint8_t *bssid, uint8_t *radio_id)
{
	struct nbr nbr = { 0 };
	struct wifi_radio_element *r;

	dbg("%s: adding neighbor " MACFMT " for %s\n",
	    __func__, MAC2STR(bssid), ap->ifname);

	/* add new neighbor to the nbrlist & fill in basic info */
	memcpy(nbr.bssid, bssid, 6);

	/* basic bssid info - will get updated later from AP caps, etc */
	nbr.bssid_info = get_basic_bssid_info(ap);

	/* Opclass & channel */
	r = agent_get_radio(a, radio_id);
	if (r) {
		/* own radio */
		nbr.reg = r->current_opclass;
		nbr.channel = r->current_channel;
	} else {
		/* radio of another node */
		struct wifi_scanresults_entry *e = NULL;

		r = agent_get_radio_by_name(a, ap->radio_name);
		if (r)
			e = wifi_scanresults_get_entry(&r->scanresults, bssid);

		if (e) {
			nbr.reg = e->opclass;
			nbr.channel = e->bss.channel;
		}
	}

	/* Assume OFDM, will update to HT/VHT/HE based on AP caps */
	nbr.phy = PHY_OFDM;

	/* Add to the nbrlist */
	if (neighbor_list_add(ap, &nbr, radio_id))
		return -1;

	/* synchronize nbr list in driver */
	reschedule_nbrlist_update(ap);

	return 0;
}

static int add_or_update_neighbor(struct agent *a, uint8_t *radio_id,
		uint8_t *bssid, char *ssid, uint8_t len)
{
	struct wifi_radio_element *re = NULL;
	int num_added = 0;

	if (len > 32)
		return -1;

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

		list_for_each_entry(ap, &re->aplist, list) {
			if (!strncmp(ssid, ap->ssid, len)) {
				if (!maybe_update_neighbor(a, ap, bssid))
					dbg("%s: updated neighbor " MACFMT "\n",
					__func__, MAC2STR(bssid));
				else if (!add_neighbor(a, ap, bssid, radio_id)) {
					dbg("%s: added neighbor " MACFMT "\n",
					__func__, MAC2STR(bssid));
					num_added++;
				}
			}
		}
	}
	return num_added;
}



static int agent_send_ap_caps_query(struct agent *a, uint8_t *origin)
{
	struct cmdu_buff *resp;
	int ret;

	resp = agent_gen_ap_caps_query(a, origin);
	if (!resp)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
		        resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, resp);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
			__func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);

	return 0;
}

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

	struct agent *a = (struct agent *) agent;
	struct tlv *tv[TOPOLOGY_RESPONSE_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

	agent_set_link_profile(a, n, cmdu);

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

	if (tv[TOPOLOGY_RESPONSE_AP_OPERATIONAL_BSS_IDX][0]) {
		struct tlv_ap_oper_bss *tlv;
		uint8_t *tv_data;
		uint16_t i, offset = 0;
		int added = 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;
			uint8_t radio_id[6];
			int j;

			memcpy(radio_id, &tv_data[offset], 6);

			/* TODO: revisit (BSSes from own radio) */

			offset += 6; /* hw macaddr */

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

			offset += 1; /* num_bss */
			for (j = 0; j < num_bss; j++) {
				uint8_t ssidlen = 0;
				uint8_t bssid[6];
				char ssid[33] = {0};

				memcpy(bssid, &tv_data[offset], 6);

				offset += 6; /* bssid */

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

				offset += 1; /* ssidlen */

				memset(ssid, 0, sizeof(ssid));
				memcpy(ssid, &tv_data[offset], ssidlen);

				offset += ssidlen; /* ssid */

				added += add_or_update_neighbor(a, radio_id,
								bssid, ssid, ssidlen);

				backhaul_mod_blacklist(a, cmdu->dev_ifname, ssid, ssidlen, bssid);
				/* TODO: add bh neighbors for bsta steering */
			}
		}

		if (added) {
			/* At least one neighbor added for current ap */

			/* Query sender for AP capability to update bssid_info */
			agent_send_ap_caps_query(a, cmdu->origin);

			/* TODO: query for phy, channel and reg domain here */
		}
	}

	return 0;
}

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

int handle_link_metric_query(void *agent, struct cmdu_buff *rx_cmdu,
			   struct node *n)
{
	trace("%s: --->\n", __func__);
	struct cmdu_buff *resp;
	struct agent *a = (struct agent *) agent;
	struct tlv *tv[LINK_METRIC_QUERY_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_linkmetric_query *lq;
	uint8_t nbr[6] = {0};
	uint8_t query_type = LINKMETRIC_QUERY_TYPE_BOTH;
	int ret;

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

	if (!tv[LINK_METRIC_QUERY_TLV_IDX][0]) {
		warn("%s: Missing Link Metric Query TLV\n", __func__);
		return -1;
	}

	lq = (struct tlv_linkmetric_query *)tv[LINK_METRIC_QUERY_TLV_IDX][0]->data;
	query_type = lq->query_type;

	/* Extract neighbor MAC if specific neighbor requested */
	if (lq->nbr_type == LINKMETRIC_QUERY_NEIGHBOR_SPECIFIC)
		memcpy(nbr, lq->nbr_macaddr, 6);

	dbg("%s: gen response for " MACFMT "\n", __func__, MAC2STR(nbr));

	resp = agent_gen_link_metric_response(a, rx_cmdu->origin,
					      cmdu_get_mid(rx_cmdu),
					      nbr, query_type);
	if (!resp) {
		warn("%s: Failed to generate Link Metric Response\n", __func__);
		return -1;
	}

	ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
		        rx_cmdu->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);

	if (ret != CMDU_DROP) {
		dbg("%s: Sending Link Metric Response to " MACFMT "\n",
		    __func__, MAC2STR(rx_cmdu->origin));
		agent_send_cmdu(a, resp);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);
	return 0;
}

#define CTRL_MAC_ERROR		-1
#define CTRL_MAC_OLD		0
#define CTRL_MAC_NEW		1

static bool should_accept_controller(struct agent *a, uint8_t *origin)
{
	return !a->cntlr_select.local || !memcmp(origin, a->almac, 6);
}

static bool should_disable_local_controller(struct agent *a, uint8_t *origin)
{
	return is_local_cntlr_available() &&
	       is_local_cntlr_running() &&
	       memcmp(origin, a->almac, 6) &&
	       should_accept_controller(a, origin);
}

static int agent_update_controller_data(struct agent *a, struct cmdu_buff *cmdu)
{
	int ret = CTRL_MAC_OLD;
	char mac_str[18] = {0};
	bool current;

	dbg("%s: cntlr_almac " MACFMT " origin " MACFMT " self " MACFMT "\n", __func__,
	    MAC2STR(a->cntlr_almac), MAC2STR(cmdu->origin), MAC2STR(a->almac));

	a->active_cntlr = true;

	/* Determine if this message is from the current primary controller */
	current = !hwaddr_is_zero(a->cntlr_almac) &&
		  !memcmp(a->cntlr_almac, cmdu->origin, 6);

	if (current)
		timestamp_update(&a->observed_time);
	else if (!hwaddr_is_zero(a->cntlr_almac))
		timestamp_update(&a->secondary_observed_time);

	if (hwaddr_is_zero(a->cntlr_almac) ||
			timestamp_expired(&a->observed_time,
			(HEARTBEAT_AUTOCFG_INTERVAL + 10) * 1000)) {

		/* No active primary - clear dual controller state */
		a->multiple_cntlr = false;

		if (should_accept_controller(a, cmdu->origin)) {
			if (!hwaddr_ntoa(cmdu->origin, mac_str))
				return CTRL_MAC_ERROR;

			if (memcmp(a->cntlr_almac, cmdu->origin, 6)) {
				memcpy(a->cntlr_almac, cmdu->origin, 6);
				set_value_by_string("mapagent",
						    "agent",
						    "controller_macaddr",
						    mac_str, UCI_TYPE_STRING);
			}

			timestamp_update(&a->observed_time);
			wifiagent_log_cntlrinfo(a, NULL);
			ret = CTRL_MAC_NEW;
		}
	} else {
		/* Primary is active */
		if (current) {
			/* Message from current primary - check if secondary timed out */
			if (a->multiple_cntlr &&
			    timestamp_expired(&a->secondary_observed_time,
					      (HEARTBEAT_AUTOCFG_INTERVAL + 10) * 1000)) {
				a->multiple_cntlr = false;
				wifiagent_log_cntlrinfo(a, NULL);
			}
		} else {
			/* Message from different controller while primary is active */
			if (should_disable_local_controller(a, cmdu->origin)) {
				agent_disable_local_cntlr(a);
			} else {
				/* Dual controller detected - block configurations */
				if (!a->multiple_cntlr) {
					a->multiple_cntlr = true;
					wifiagent_log_cntlrinfo(a, cmdu->origin);
				}
			}
		}
	}

	return ret;
}

#define MAX_IMMEDIATE_AUTOCFG_DELAY 3
static void agent_trigger_autocfg(struct agent *a)
{
	int remaining = timer_remaining_ms(&a->autocfg_dispatcher);

	if (remaining > (MAX_IMMEDIATE_AUTOCFG_DELAY * 1000) || remaining < 0) { /* ms */
		/* slight delay to allow local controller to have time to teardown */
		agnt_dbg(LOG_APCFG, "%s: Scheduling AP-Autoconfig search "\
			 "in 1 second\n", __func__);
		timer_set(&a->autocfg_dispatcher, 1 * 1000);
	}
}

int handle_ap_autoconfig_search(void *agent, struct cmdu_buff *rx_cmdu,
				struct node *n)
{
	agnt_trace(LOG_APCFG, "agent: %s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	bool cntlr = false;
	int i;
	uint8_t almac[6] = {0};
	struct tlv *tv[AP_AUTOCONFIGURATION_SEARCH_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_aladdr *aladdr;
	struct wifi_radio_element *re = NULL;
	struct tlv *t;

	t = map_cmdu_get_tlv(rx_cmdu, TLV_TYPE_AL_MAC_ADDRESS_TYPE);
	if (!t) {
		agnt_dbg(LOG_APCFG, "%s: Malformed topology notification!\n", __func__);
		return -1;
	}

	aladdr = (struct tlv_aladdr *) t->data;

	memcpy(almac, aladdr->macaddr, 6);

	if (hwaddr_is_zero(almac)) {
		agnt_trace(LOG_APCFG, "%s: Discard topology notification from aladdr = 0!\n",
			__func__);

		return -1;
	}

	n = agent_add_node(a, almac);
	if (!n) {
		agnt_err(LOG_APCFG, "%s: node allocation for "MACFMT" failed!\n",
		      __func__, MAC2STR(almac));
		return -1;
	}

	agent_set_link_profile(a, n, rx_cmdu);

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

	/* Discard autoconfig search in case it's been sent by ourself */
	list_for_each_entry(re, &a->radiolist, list) {
		if (re->mid == cmdu_get_mid(rx_cmdu)) {
			agnt_trace(LOG_APCFG, "%s: skip handling autoconfig sent by self\n",
					__func__);
			return -1;
		}
	}

	if (tv[AP_AUTOCONFIGURATION_SEARCH_SUPPORTED_SERVICE_IDX][0]) {
		for (i = 1; i <= tv[AP_AUTOCONFIGURATION_SEARCH_SUPPORTED_SERVICE_IDX][0]->data[0];
				i++) {
			if (tv[AP_AUTOCONFIGURATION_SEARCH_SUPPORTED_SERVICE_IDX][0]->data[i] ==
					SUPPORTED_SERVICE_MULTIAP_CONTROLLER)
				cntlr = true;
			else if (tv[AP_AUTOCONFIGURATION_SEARCH_SUPPORTED_SERVICE_IDX][0]->data[i] ==
					SUPPORTED_SERVICE_MULTIAP_AGENT)
				/* send topology query to the agent to get oper bss */
				send_topology_query(a, almac);
		}
	}

	if (cntlr) {
		if (agent_update_controller_data(a, rx_cmdu) == CTRL_MAC_NEW) {
			agnt_dbg(LOG_APCFG, "%s: new controller found!"\
				 " Activate autoconfig configuration"\
				 " for all radios\n",
				 __func__);

			re = NULL;
			list_for_each_entry(re, &a->radiolist, list)
				re->state = AUTOCFG_ACTIVE;

			/* Report status of the new map controller to user */
			wifiagent_log_cntlrinfo(a, NULL);


			agnt_dbg(LOG_APCFG, "|%s:%d| Triggering immediate "\
				 "autoconfig search\n", __func__, __LINE__);
			agent_trigger_autocfg(a);
		} else {
			re = NULL;
			list_for_each_entry(re, &a->radiolist, list) {
				if (re->state == AUTOCFG_ACTIVE) {
					agnt_dbg(LOG_APCFG, "%s: radio:%s was "\
						 "not yet autoconfigured, "\
						 "trigger new search\n",
						__func__, re->name);
					agent_trigger_autocfg(a);
					break;
				}
			}
		}
	}

	return 0;
}

static void agent_reschedule_heartbeat_autocfg(struct agent *a, uint16_t interval)
{
	uint16_t remaining, elapsed = 0;

	/* don't modify interval if there is an active radio */
	if (a->autocfg_interval < interval) {
		struct wifi_radio_element *re = NULL;

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

	if (a->autocfg_interval == interval)
		return;

	remaining = timer_remaining_ms(&a->autocfg_dispatcher);
	remaining /= 1000; /* seconds */

	if (a->autocfg_interval > remaining)
		elapsed = a->autocfg_interval - remaining;

	a->autocfg_interval = interval;

	agnt_dbg(LOG_APCFG, "%s: Rescheduling autoconfig in %u seconds\n",
			__func__, a->autocfg_interval - elapsed);

	timer_set(&a->autocfg_dispatcher,
			(a->autocfg_interval - elapsed) * 1000);
}

int handle_ap_autoconfig_response(void *agent, struct cmdu_buff *rx_cmdu,
				  struct node *n)
{
	agnt_trace(LOG_APCFG, "agent: %s: --->\n", __func__);

	struct agent *a = (struct agent *) agent;
	struct wifi_radio_element *re = NULL;
	struct tlv *tv[AP_AUTOCONFIGURATION_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct cmdu_buff *resp;
	uint8_t band;
	bool found = false;
	int ret;
#if (EASYMESH_VERSION > 2)
	bool hash_validity;
	uint8_t hashlen;
	uint8_t *hash;
#endif

	agent_set_link_profile(a, n, rx_cmdu);

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

#ifdef EASYMESH_R2_CERT
	resp = agent_gen_topology_discovery(a);
	if (resp) {
		agent_send_cmdu(a, resp);
		cmdu_free(resp);
	}
#endif

	if (hwaddr_is_zero(rx_cmdu->origin)) {
		agnt_dbg(LOG_APCFG, "%s: origin is zeroed out, drop response\n", __func__);
		return -1;
	}

	/* If MID is not the one we sent, discard response */
	list_for_each_entry(re, &a->radiolist, list) {
		agnt_trace(LOG_APCFG, "radio %s has mid %d\n", re->name, re->mid);
		if (re->mid != cmdu_get_mid(rx_cmdu))
			continue;
		found = true;
		break;
	}
	if (!found) {
		agnt_dbg(LOG_APCFG, "autoconfig response mid did not match!\n");
		return -1;
	}

	if (!agent_tlv_supported_service_is_controller(tv[AP_AUTOCONFIGURATION_RESP_SUPPORTED_SERVICE_IDX][0])) {
		agnt_dbg(LOG_APCFG, "autoconfig response does not support controller!\n");
		return -1;
	}

	agnt_dbg(LOG_APCFG, "cntlr_almac " MACFMT " origin " MACFMT "\n",
			MAC2STR(a->cntlr_almac), MAC2STR(rx_cmdu->origin));

#if (EASYMESH_VERSION >= 6) /* supported already in 3 */
	if (tv[AP_AUTOCONFIGURATION_RESP_CONTROLLER_CAPS_IDX][0]) {
		struct tlv_controller_cap *cntlr_cap = (struct tlv_controller_cap *)
			tv[AP_AUTOCONFIGURATION_RESP_CONTROLLER_CAPS_IDX][0]->data;

		if (cntlr_cap->flag & CONTROLLER_EARLY_AP_CAP_SUPPORTED) {
			struct cmdu_buff *cmdu;

			cmdu = agent_gen_early_ap_cap_report(a, rx_cmdu->origin,
							cmdu_get_mid(rx_cmdu));
			if (cmdu) {
				agent_send_cmdu(a, cmdu);
				cmdu_free(cmdu);
			}
		}

	}
#endif
	if (agent_update_controller_data(a, rx_cmdu) == CTRL_MAC_NEW) {
		struct wifi_radio_element *r = NULL;

		agnt_dbg(LOG_APCFG, "%s: new controller found! Activate autoconfiguration"\
				" for all radios\n", __func__);

		list_for_each_entry(r, &a->radiolist, list)
			r->state = AUTOCFG_ACTIVE;

		/* report status of the new map controller to user */
		wifiagent_log_cntlrinfo(a, NULL);
	}

	if (re->state == AUTOCFG_ACTIVE) {
		struct tlv_supported_band *data;
		uint16_t mid;

		if (a->cntlr_select.local == true &&
				memcmp(rx_cmdu->origin, a->almac, 6)) {
			agnt_dbg(LOG_APCFG, "%s: local set in controller_select - don't"\
					"trigger WSC with "\
					"non-local controller\n",
					__func__);
			/* local cntlr enforced: don't WSC with non-local one */
			return -1;
		}

#if 0
		if (re->dedicated_backhaul) {
			agnt_dbg(LOG_APCFG, "%s: don't trigger WSC for dedicated backhaul,"\
					"setting re to heartbeat\n",
					__func__);
			return -1;
		}
#endif

		data = (struct tlv_supported_band *)
			tv[AP_AUTOCONFIGURATION_RESP_SUPPORTED_FREQ_BAND][0]->data;

		band = wifi_band_to_ieee1905band(re->band);
		if (band != data->band)
			return -1;

		agnt_dbg(LOG_APCFG, "%s: generate wsc for radio %s\n", __func__,
				re->name);


#if (EASYMESH_VERSION >= 3)
		if (tv[AP_AUTOCONFIGURATION_RESP_DPP_CHIRP_VALUE_IDX][0] && a->cfg.map_profile > 2) {
			int offset = 0;
			uint8_t flag = 0;
			bool mac_present;
			uint8_t *mac;
#ifdef USE_LIBDPP
			uint8_t *bi_hash;
			uint16_t bi_hashlen;
#endif
			flag = tv[AP_AUTOCONFIGURATION_RESP_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_RESP_DPP_CHIRP_VALUE_IDX][0]->data[offset];
				UNUSED(mac);
				offset += 6;
			}

			hashlen = tv[AP_AUTOCONFIGURATION_RESP_DPP_CHIRP_VALUE_IDX][0]->data[offset++];
			hash = &tv[AP_AUTOCONFIGURATION_RESP_DPP_CHIRP_VALUE_IDX][0]->data[offset];
			UNUSED(hash);
			UNUSED(hashlen);
#ifdef USE_LIBDPP
			bi_hash = dpp_get_bootstrap_pubkey_hash(a->own_peer->own_bi,
								&bi_hashlen);
			if (bi_hash && bi_hashlen &&
			    bi_hashlen == hashlen &&
			    !memcmp(bi_hash, hash, hashlen)) {
				// do not send autoconfig YET
				timer_set(&a->dpp_legacy_timeout, 10000);
				agent_reschedule_heartbeat_autocfg(a, a->cntlr_select.probe_int);
				return 0;
			}
#endif
		}
#endif

		resp = agent_gen_ap_autoconfig_wsc(a, rx_cmdu->origin, re);
		if (!resp)
			return -1;

		ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
				resp->origin, resp->cdata, cmdu_size(resp),
				CMDU_PRE_TX);
		if (ret != CMDU_DROP) {
			mid = agent_send_cmdu(a, resp);
			if (mid) {
				re->wsc_mid = mid;
				agnt_trace(LOG_APCFG, "assigned radio mid %d %d\n", re->wsc_mid, mid);
			}
		} else {
			agnt_warn(LOG_APCFG, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
				__func__, cmdu_get_type(resp));
		}

		cmdu_free(resp);
		agent_reschedule_heartbeat_autocfg(a, a->cntlr_select.probe_int);
	} else if (re->state == AUTOCFG_HEARTBEAT) {
		/* heartbeat from controller - no activity */
		agnt_dbg(LOG_APCFG, "%s: Received autoconfig search response from" \
				" controller during heartbeat\n", __func__);
		/* Autoconfig answered - increase search interval if needed */
		agent_reschedule_heartbeat_autocfg(a, HEARTBEAT_AUTOCFG_INTERVAL);
	}

	return 0;
}

int wifi_teardown_iface(const char *ifname)
{
	char fmt[128] = {0};

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

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

int wifi_teardown_map_ifaces_by_radio(struct agent *a, char *device)
{
	struct netif_apcfg *apcfg = NULL;

	/* teardown configs */
	list_for_each_entry(apcfg, &a->cfg.aplist, list) {
		struct netif_ap *ap;

		if (strncmp(apcfg->device, device, sizeof(apcfg->device) - 1))
			continue;

		wifi_teardown_iface(apcfg->name);
		ap = agent_get_ap_by_ifname(a, apcfg->name);
		if (ap)
			ap->torndown = true;
	}

	agent_config_reload(a);
	return 0;
}

/* return true if valid ifname is available */
int check_wireless_ifname(struct agent *a, const char *device,
		const char *ifname)
{
	enum {
		W_IFNAME,
		W_DEV,
		NUM_POLICIES
	};
	const struct uci_parse_option opts[] = {
		{ .name = "ifname", .type = UCI_TYPE_STRING },
		{ .name = "device", .type = UCI_TYPE_STRING }
	};
	struct uci_option *tb[NUM_POLICIES];
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;
	struct wifi_radio_element *re = agent_get_radio_by_name(a, device);
	bool rv = false;
	int num_ifs = 0;

	if (!re)
		return -1;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, "wireless", &pkg)) {
		uci_free_context(ctx);
		return -1;
	}

	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);

		if (strncmp(s->type, "wifi-iface", strlen("wifi-iface")))
			continue;

		uci_parse_section(s, opts, NUM_POLICIES, tb);

		if (tb[W_DEV]) {
			const char *cfg_dev;

			cfg_dev = tb[W_DEV]->v.string;
			if (!strncmp(cfg_dev, device, IFNAMSIZ))
				num_ifs++;
		}

		if (num_ifs >= re->max_bss) {
			rv = true;
			break;
		}

		if (tb[W_IFNAME]) {
			char *cfg_ifname;

			cfg_ifname = tb[W_IFNAME]->v.string;
			if (!strncmp(cfg_ifname, ifname, IFNAMSIZ)) {
				rv = true;
				break;
			}
		}
	}

	uci_free_context(ctx);
	return rv;
}

/* return ifname buffer */
char *wifi_gen_first_ifname(struct agent *a, char *device, char *ifname)
{
	struct wifi_radio_element *re = agent_get_radio_by_name(a, device);
	int i;

	if (!re)
		return NULL;

	for (i = 0; i <= re->max_bss; i++) {

		if (config_calc_ifname(&a->cfg, device, i, ifname))
			return NULL;

		if (!check_wireless_ifname(a, device, ifname))
			return ifname;
	}

	return NULL;
}

/* best effort return ifname buffer */
struct netif_ap *wifi_get_available_ifname_with_ssid(struct agent *a, struct wifi_radio_element *re,
					char *ssid, char *ifname)
{
	struct netif_ap *ap = NULL;

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

		if (!ap->cfg)
			continue;

		if (strncmp(ap->cfg->ssid, ssid, 32))
			continue;

		strncpy(ifname, ap->ifname, IFNAMSIZ - 1);
		return ap;
	}

	return NULL;
}

/* return ifname buffer */
struct netif_ap *wifi_get_first_available_ifname(struct agent *a, struct wifi_radio_element *re, char *ifname)
{
	struct netif_ap *ap = NULL;

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

		strncpy(ifname, ap->ifname, IFNAMSIZ - 1);
		return ap;
	}

	return NULL;
}

int agent_free_wsc_vendor_ies(struct wsc_ext *exts)
{
	struct wsc_vendor_ie *ext;
	int i;

	for (i = 0; i < exts->num_ven_ies; i++) {
		ext = &exts->ven_ies[i];
		if (ext->len)
			free(ext->payload);
	}

	exts->num_ven_ies = 0;

	return 0;
}

int agent_add_wsc_exts(struct wsc_ext *exts, uint8_t *oui, uint8_t len,
		       uint8_t *payload)
{
	struct wsc_vendor_ie *ext;

	if (exts->num_ven_ies >= VEN_IES_MAX)
		return -1;

	ext = &exts->ven_ies[exts->num_ven_ies];

	if (len && payload) {
		ext->payload = calloc(1, len);
		if (!ext->payload)
			return -1;

		memcpy(ext->payload, payload, len);
		ext->len = len;
	}

	memcpy(ext->oui, oui, 3);
	exts->num_ven_ies++;
	return 0;
}

int wsc_get_exts(uint8_t *msg, uint16_t msglen, struct wsc_ext *exts)
{
	uint8_t *p;
	uint8_t *msg_end;
#ifdef EASYMESH_VENDOR_EXT
#define ATTR_ENABLED (0x4C) /* provisioning of disabled APs */
#define ATTR_BRIDGE (0x4D) /* provisioning of custom bridge */
	const uint8_t vendor_oui[4] = {0};

#ifdef EASYMESH_VENDOR_EXT_OUI
	BUF_PUT_BE24(vendor_oui, EASYMESH_VENDOR_EXT_OUI);
#else
	BUF_PUT_BE24(vendor_oui, EASYMESH_VENDOR_EXT_OUI_DEFAULT);
#endif
#endif /* EASYMESH_VENDOR_EXT */
	if (!msg || msglen == 0 || !exts)
		return 0;

	p = msg;
	msg_end = msg + msglen;

	while (labs(p - msg) < msglen - 4) {
		uint16_t attr_type;
		uint16_t attr_len;

		attr_type = buf_get_be16(p);
		p += 2;
		attr_len = buf_get_be16(p);
		p += 2;

		if (p + attr_len > msg_end)
			return -1;

		if (attr_type == ATTR_VENDOR_EXTENSION) {
			uint8_t id[3] = {0};
			uint8_t *end_of_ext;

			/* May be one or more subelements (Section 12 of WSC spec) */
			end_of_ext = p + attr_len;

			while (p < end_of_ext) {
				memcpy(id, p, sizeof(id));
				p += 3;
				attr_len -= 3;

				agent_add_wsc_exts(exts, (uint8_t *)id, attr_len, p);
#ifdef EASYMESH_VENDOR_EXT
				if (!memcmp(id, vendor_oui, 3)) {
					uint8_t subelem;
					uint8_t len;

					memcpy(&subelem, p, 1);
					p += 1;
					attr_len -= 1;

					memcpy(&len, p, 1);
					p += 1;
					attr_len -= 1;

					switch (subelem) {
					case ATTR_ENABLED:
						memcpy(&exts->enabled, p, len);
						break;
					case ATTR_BRIDGE:
						memcpy(exts->bridge, p, len);
						break;
					default:
						trace("%s: unknown subelem:%u\n", __func__, subelem);
						break;
					}

					p += len;
					attr_len -= len;
				} else
#endif /*EASYMESH_VENDOR_EXT*/
					break;
			}
		}

		p += attr_len;
	}

#ifdef EASYMESH_VENDOR_EXT
#undef ATTR_ENABLED
#undef ATTR_BRIDGE
#endif
	return 0;
}

void agent_autoconfig_event(struct agent *a, char *radio, char *status,
		char *reason)
{
	char data[128] = { 0 };

	snprintf(data, sizeof(data), "{"\
			"\"ifname\":\"%s\","\
			"\"event\":\"ap-autoconfiguration\","\
			"\"data\": {"\
				"\"status\":\"%s\","\
				"\"reason\":\"%s\""\
			"}"\
		"}", radio, status, reason);

	agent_notify_event(a, "wifi.radio", data);

	snprintf(data, sizeof(data), "{"\
			"\"radio\":\"%s\","\
			"\"event\":\"ap-autoconfiguration\","\
			"\"data\": {"\
				"\"status\":\"%s\","\
				"\"reason\":\"%s\""\
			"}"\
		"}", radio, status, reason);
	agent_notify_event(a, "map.agent", data);
}


/* Set up Traffic Separation rules */
int agent_apply_traffic_separation(struct agent *a)
{
	struct agent_config *cfg;
	struct policy_cfg *c;

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

	if (!a)
		return -1;

	cfg = &a->cfg;
	if (!cfg) {
		err("%s: missing configuration!\n", __func__);
		return -1;
	}

	c = cfg->pcfg;
	if (!c) {
		err("%s: missing policy configuration!\n", __func__);
		return -1;
	}

	if (c->pvid == 0)
		return 0;

	if (!is_vid_valid(c->pvid)) {
		warn("Invalid primary vlan id %u\n", c->pvid);
		return -1;
	}

	nl_check_vlan(a, true);
	ts_configure_isolation(a, true);

	return 0;
}

/* Check if Traffic Separation is enabled in Policy */
bool agent_is_ts_enabled(struct agent *a)
{
	struct agent_config *cfg;
	struct policy_cfg *c;

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

	if (!a)
		return false;

	cfg = &a->cfg;
	if (!cfg) {
		err("%s: missing configuration!\n", __func__);
		return false;
	}

	c = cfg->pcfg;
	if (!c) {
		err("%s: missing policy configuration!\n", __func__);
		return false;
	}

	if (c->pvid == 0)
		return false;

	if (!is_vid_valid(c->pvid)) {
		warn("Invalid primary vlan id %u\n", c->pvid);
		return false;
	}

	return true;
}

#define RELOAD_TIMEOUT 5
#if (EASYMESH_VERSION >= 6)
static int mlo_process_ap_mld_config(struct agent *a, uint8_t *tlv_data)
{
	trace("%s: --->\n", __func__);

	struct tlv_ap_mld_config *tlv = (struct tlv_ap_mld_config *) tlv_data;
	uint8_t num_mlds;
	int offset = 0;
	struct netif_apcfg *apcfg = NULL;
	int i;

	num_mlds = tlv->num_mlds;
	offset++;

	/* 1: mark affiliated APs as non-MLD in both configs */
	list_for_each_entry(apcfg, &a->cfg.aplist, list) {
		if (apcfg->mld_id) {
			struct netif_ap *ap;

			mld_set_config_id(apcfg->name, 0);
			apcfg->mld_id = 0;
			ap = agent_get_ap_by_ifname(a, apcfg->name);
			if (ap)
				ap->torndown = true;
		}
	}

	/* 2: re-affiliate APs to appropriate MLDs based on rcvd config */
	for (i = 0; i < num_mlds; i++) {
		uint8_t num_affiliated_aps;
		bool mld_complete = false;
		bool mac_present = false;
		uint8_t ssidlen = 0;
		uint8_t ssid[33] = {0};
		uint8_t caps;
		uint8_t id;
		int j;

		UNUSED(mac_present);

		mac_present = (tlv_data[offset++] & AP_MLD_CONF_AP_MLD_ADDR_VALID);
		ssidlen = tlv_data[offset++];
		if (ssidlen > 32) {
			dbg("%s: Unexpected ssidlen:%d in mld config\n",
				__func__, ssidlen);
			break;
		}

		memcpy(ssid, &tlv_data[offset], ssidlen);
		offset += ssidlen;
		offset += 6; /* skip AP_MLD_MAC_Addr */
		caps = tlv_data[offset++];
		UNUSED(caps); /* TODO: compare against our local caps */

		offset += 20; /* step past reserved bytes */

		num_affiliated_aps = tlv_data[offset++];

		for (j = 0; j < num_affiliated_aps; j++) {
			struct mld_credential *mld;
			struct wifi_radio_element *re;
			uint8_t ruid[6] = {0};
			enum mld_type type;
			char *key = NULL;
			struct netif_ap *ap;

			mac_present = (tlv_data[offset++] & AP_MLD_CONF_AP_MLD_ADDR_VALID);
			memcpy(ruid, &tlv_data[offset], 6);
			offset += 6;

			offset += 6; /* skip Affiliated_AP_MAC_Addr */
			//id = tlv_data[offset++];
			offset++; /* ID */
			offset += 18; /* step past reserved bytes */

			re = agent_get_radio(a, ruid);
			if (!re) {
				warn("%s: no radio with ruid:"MACFMT"\n",
					__func__, MAC2STR(ruid));
				continue;
			}

			/* re-use existing configuration if present */
			apcfg = agent_config_get_ap_by_ssid_on_band(&a->cfg, re->band,
							(char *)ssid);
			if (!apcfg) {
				struct wps_credential wps = { 0	};
				struct wsc_ext exts = {
					.enabled = true
				};
				char ifname[IFNAMSIZ] = {0};

				/* Affiliated AP was not a part of the MLD,
				 * (re-)create the Affiliated AP configuration
				 * based on an AP already a part of the MLD
				 **/

				/* get MLD credentials from existing ap section */
				apcfg = agent_config_get_ap_by_ssid(&a->cfg, (char *)ssid);
				if (!apcfg) {
					warn("%s: no matching AP found for ssid:%s\n",
					__func__, (char *)ssid);
					continue;
				}

				/* try to re-use available config section */
				ap = wifi_get_available_ifname_with_ssid(a, re, (char *)ssid, ifname);
				if (!ap)
					ap = wifi_get_first_available_ifname(a, re, ifname);

				if (!ap) {
					/* try to generate new ap with first available ifname */
					ap = agent_gen_netif_ap(a, re);
					if (!ap) {
						warn("%s: failed to generate new net_ap\n", __func__);
						continue;
					}
				}

				/* create wps_credential struct based on affiliated ap */
				/* TODO: store credentials as struct wps_credentials in struct netif_ap */
				memcpy(wps.ssid, ssid, ssidlen);
				wps.ssidlen = ssidlen;
				memcpy(wps.key, apcfg->key, strlen(apcfg->key));
				wps.keylen = strlen(apcfg->key);
				wps.auth_type = get_encryption(apcfg->encryption);
				wps.enc_type = WPS_ENCR_AES;
				wps.band = re->band;
				wps.mapie = 0;
				if (apcfg->multi_ap & 0x01)
					wps.mapie |= BIT(6);
				if (apcfg->multi_ap & 0x02)
					wps.mapie |= BIT(5);

				apcfg = uci_apply_wps_credentials(&a->cfg, ap->ifname, re->name, &wps, &exts);
				if (!apcfg) {
					warn("%s: Failed to (re-)apply MLD credentials!\n", __func__);
					continue;
				}
			} else {
				ap = agent_get_ap_by_ifname(a, apcfg->name);
				if (!ap)
					continue;
			}

			ap->torndown = false;

			mld = agent_get_ap_mld_credential_by_ssid(&a->cfg, (char *)ssid);
			if (mld)
				id = mld->id;
			else {
				id = agent_get_first_available_mld_id(&a->cfg);
				if (id == 255) {
					warn("%s: No more MLD ID's available, skip ssid:%s\n", __func__, ssid);
					return -1;
				}
			}

			/* Add or update mld/wifi-mld sections in both config files */
			if (mld_complete == false) {
				key = apcfg->key;
				if (apcfg->multi_ap == 1)
					type = MLD_TYPE_BACKHAUL_BSS;
				else if (apcfg->multi_ap == 3)
					type = MLD_TYPE_COMBINED_BSS;
				else
					type = MLD_TYPE_FRONTHAUL_BSS;

				uci_apply_agent_mld_config(&a->cfg, id,
							   (char *)ssid,
							   key, type, 0, 0);
				uci_apply_wireless_mld_config(&a->cfg, id,
							      (char *)ssid,
							      key, "ap");
			}

			if (!mld_complete && strlen((char *)ssid) && key)
				mld_complete = true;

			/* Set mld_id/mld in ap/wifi-iface sections of cfg files */
			mld_set_config_id(apcfg->name, id);
			apcfg->mld_id = id;
		}
	}

	wifi_clean_torndown_ifaces(a);
	agent_exec_platform_scripts("write_credentials");
	return 0;
}

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

	//struct tlv_bsta_mld_config *tlv = (struct tlv_bsta_mld_config *) tlv_data;
	struct wifi_radio_element *re = NULL;
	bool bsta_mac_present = false;
	bool ap_mac_present = false;
	uint8_t num_affiliated_bstas;
	int offset = 0;
	uint8_t caps;
	int i;

	UNUSED(ap_mac_present);
	UNUSED(bsta_mac_present);

	bsta_mac_present = (tlv_data[offset] & BSTA_MLD_CONF_BSTA_MLD_ADDR_VALID);
	ap_mac_present = (tlv_data[offset] & BSTA_MLD_CONF_AP_MLD_ADDR_VALID);
	offset++;

	offset += 6; /* skip bSTA_MLD_MAC_Addr */
	offset += 6; /* skip AP_MLD_MAC_Addr */

	caps = tlv_data[offset++];

	/* TODO: compare against our local caps */
	if ((caps & BSTA_MLD_CONFIG_STR) == 0 &&
	    (caps & BSTA_MLD_CONFIG_NSTR) == 0 &&
	    (caps & BSTA_MLD_CONFIG_EMLSR) == 0 &&
	    (caps & BSTA_MLD_CONFIG_EMLMR) == 0) {
		dbg("%s: BSTA MLD config with emtpy caps, teardown\n",
		    __func__);
		return -1;
	}

	offset += 17; /* step past reserved bytes */

	/* configs: reset mld_id on all base bstas & remove all bands from netdev bsta */
	agent_config_mld_rem_bsta_all(&a->cfg);
	/* structs: remove all affiliated bstas from bsta mld */
	list_for_each_entry(re, &a->radiolist, list) {
		if (!re->has_bsta)
			continue;

		if (!re->bk.cfg->is_mld_netdev && re->bk.is_affiliated_sta)
			mld_del_bsta(a, &re->bk);
	}

	num_affiliated_bstas = tlv_data[offset++];
	for (i = 0; i < num_affiliated_bstas; i++) {
		bool affiliated_mac_present = false;
		struct mld_credential *mld = NULL;
		char *ssid = NULL, *key = NULL;
		struct wifi_radio_element *re;
		struct netif_bkcfg *bkcfg;
		uint8_t ruid[6] = {0};
		char mldname[16] = {0};
		char idstr[4] = {0};
		uint8_t id;

		UNUSED(affiliated_mac_present);

		affiliated_mac_present = (tlv_data[offset++] & BSTA_MLD_CONF_AFFILIATED_BSTA_MLD_ADDR_VALID);

		memcpy(ruid, &tlv_data[offset], 6);
		offset += 6;

		offset += 6; /* skip Affilated_bSTA_MAC_Addr */
		offset += 19; /* step past reserved bytes */

		re = agent_get_radio(a, ruid);
		if (!re) {
			warn("%s: no radio with ruid:"MACFMT"\n",
				__func__, MAC2STR(ruid));
			continue;
		}

		/* Base bsta section available only if band supported by default */
		bkcfg = agent_config_bsta_by_band(&a->cfg, re->band);
		if (!bkcfg) {
			warn("%s: no bsta with band:%d\n",
				__func__, re->band);
			continue;
		}

		mld = agent_get_bsta_mld_credential(&a->cfg);
		if (mld) {
			ssid = (char *)mld->ssid;
			key = (char *)mld->key;
			id = mld->id;
		} else if (bkcfg->onboarded) {
			ssid = bkcfg->ssid;
			key = bkcfg->key;
			id = agent_get_first_available_mld_id(&a->cfg);
			if (id == 255) {
				warn("%s: No more MLD ID's available, skip ssid:%s\n", __func__, ssid);
				return -1;
			}
		} else {
			warn("%s: bsta:%s is not onboarded and mld was not found, skipping\n",
			     __func__, bkcfg->name);
			continue;
		}

		/* Update mapagent config: mld & bsta sections */
		uci_apply_agent_mld_config(&a->cfg, id, ssid, key, MLD_TYPE_BACKHAUL_STA,
					   0, re->band);
		uci_apply_agent_bsta_config(&a->cfg, id, ssid, key, re->band);
		snprintf(idstr, sizeof(idstr), "%d", id);
		uci_set_wireless_interface_option("mapagent", "bsta", "ifname",
						  bkcfg->name, "mld_id", idstr);

		/* Update wireless config: wifi-mld & wifi-iface sections */
		uci_apply_wireless_mld_config(&a->cfg, id, ssid, key, "sta");
		snprintf(mldname, sizeof(mldname), "mld%d", id);
		uci_set_wireless_interface_option("wireless", "wifi-iface",
				"ifname", bkcfg->name, "mld", mldname);

	}
	return 0;
}
#endif

#if (EASYMESH_VERSION >= 6)
static void agent_process_bss_advanced_config(struct agent *a,
		const uint8_t *data, uint16_t len)
{
	const uint8_t *p = data;
	const uint8_t *end = data + len;
	uint8_t num_radio;
	int r;

	if (p >= end)
		return;

	num_radio = *p++;

	for (r = 0; r < num_radio; r++) {
		struct wifi_radio_element *re;
		uint8_t ruid[6];
		uint8_t num_bss;
		int b;

		if (p + 7 > end)
			break;

		memcpy(ruid, p, 6);
		p += 6;
		num_bss = *p++;

		re = agent_get_radio(a, ruid);
		if (!re) {
			p += num_bss * sizeof(struct bss_adv_config_bss);
			continue;
		}

		for (b = 0; b < num_bss; b++) {
			const struct bss_adv_config_bss *bss_entry;
			struct netif_ap *ap;

			if (p + sizeof(*bss_entry) > end)
				break;

			bss_entry = (const struct bss_adv_config_bss *)p;
			p += sizeof(*bss_entry);

			ap = agent_get_ap_by_bss_index(re, bss_entry->bss_index);
			if (!ap) {
				agnt_warn(LOG_APCFG,
					  "%s: no AP with bss_index %u on radio " MACFMT "\n",
					  __func__, bss_entry->bss_index,
					  MAC2STR(ruid));
				continue;
			}

			ap->cfg->hidden =
				!(bss_entry->flags & BSS_ADV_CONFIG_SSID_ADVERTISEMENT);

			uci_set_wireless_interface_option("wireless",
					"wifi-iface", "ifname", ap->ifname,
					"hidden", ap->cfg->hidden ? "1" : "");

			uci_set_wireless_interface_option("mapagent",
					"ap", "ifname", ap->ifname,
					"hidden", ap->cfg->hidden ? "1" : "");

			agnt_dbg(LOG_APCFG, "%s: BSS " MACFMT " hidden=%d\n",
				 __func__, MAC2STR(ap->bssid),
				 ap->cfg->hidden);
		}
	}
}

static void agent_process_rsn_parameters_config(struct agent *a,
						const uint8_t *data,
						uint16_t len)
{
	const uint8_t *p = data;
	const uint8_t *end = data + len;
	uint8_t num_radio;
	int r;

	if (p >= end)
		return;

	num_radio = *p++;

	for (r = 0; r < num_radio; r++) {
		struct wifi_radio_element *re;
		uint8_t ruid[6];
		uint8_t num_bss;
		int b;

		if (p + 7 > end)
			break;

		memcpy(ruid, p, 6);
		p += 6;
		num_bss = *p++;

		re = agent_get_radio(a, ruid);

		for (b = 0; b < num_bss; b++) {
			uint8_t bssid[6] = {0};
			struct netif_ap *ap;
			uint8_t bss_index;
			uint16_t ielen;

			if (p + 9 > end)	/* bssid(6) + bss_index(1) + ielen(2) */
				break;

			memcpy(bssid, p, 6);
			p += 6;
			bss_index = *p++;
			ielen = buf_get_be16(p);
			p += 2;

			if (p + ielen + 16 > end)
				break;

			if (!re) {
				p += ielen + 16;
				continue;
			}

			ap = agent_get_ap_by_bss_index(re, bss_index);
			if (ap && ap->ifname[0]) {
				char encstr[64] = {};

				if (!wifi_get_section_option("wireless", "wifi-iface",
							     "ifname", ap->ifname,
							     "encryption", encstr,
							     sizeof(encstr))) {
					if (!strncmp(encstr, "invalid", 7)) {
						/* find+replace encryption "invalid" -> "owe" */
						uci_set_wireless_interface_option("wireless",
										  "wifi-iface", "ifname", ap->ifname,
										  "encryption", "owe");

						uci_set_wireless_interface_option("wireless",
										  "wifi-iface", "ifname", ap->ifname,
										  "ieee80211w", "2");

						uci_set_wireless_interface_option("mapagent",
										  "ap", "ifname", ap->ifname,
										  "encryption", "owe");

						agnt_dbg(LOG_APCFG,
							 "%s: BSS index %u on radio " MACFMT
							 " set encryption=owe (RSN IE len=%u)\n",
							 __func__, bss_index, MAC2STR(ruid), ielen);
					}
				}
			}

			p += ielen + 16;	/* ie[] + rsvd1[16] */
		}
	}
}
#endif

int handle_ap_autoconfig_wsc(void *agent, struct cmdu_buff *rx_cmdu,
			     struct node *n)
{
	struct tlv *tv[AP_AUTOCONFIGURATION_WSC_M2_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct agent *a = (struct agent *) agent;
	uint8_t new_sha256[SHA256_LENGTH] = {0};
	struct wifi_radio_element *radio;
	uint8_t wsc_mtype = 0;
	int ret = 0, num = 0;
	uint8_t bssid[6];
#ifdef CHECK_PARTIAL_WIFI_RELOAD
	bool full_reconf_required = false;
#endif /* CHECK_PARTIAL_WIFI_RELOAD */
	int diff = DIFF_NONE;

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

	if (a->multiple_cntlr) {
		dbg("%s: Dual controllers detected - rejecting WSC M2 from " MACFMT "\n",
		    __func__, MAC2STR(rx_cmdu->origin));
		return -1;
	}

	if (memcmp(rx_cmdu->origin, a->cntlr_almac, 6)) {
		agnt_dbg(LOG_APCFG, "%s: response not from an active controller!\n",
				__func__);
		return -1;
	}

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

	wsc_mtype = wsc_get_message_type(tv[AP_AUTOCONFIGURATION_WSC_M2_WSC_IDX][0]->data,
					 tlv_length(tv[AP_AUTOCONFIGURATION_WSC_M2_WSC_IDX][0]));
	if (wsc_mtype != WPS_M2) {
		agnt_dbg(LOG_APCFG, "%s: Recvd WSC msg (type %d) not M2, drop\n", __func__, wsc_mtype);
                return -1;
        }

	memcpy(bssid, tv[AP_AUTOCONFIGURATION_WSC_M2_AP_RADIO_IDENTIFIER_IDX][0]->data, 6);

	radio = agent_get_radio(a, bssid);
	if (!radio)
		return -1;

	agnt_dbg(LOG_APCFG, "%s: found radio = %s\n", __func__, radio->name);

	if (!memcmp(new_sha256, radio->autconfig.sha256, SHA256_LENGTH))
		autoconfig_gen_radio_hash_from_uci(radio);

	diff = autoconfig_wsc_m2_diff(radio, tv,
				AP_AUTOCONFIGURATION_WSC_M2_NUM_OF_TLV_TYPES,
				new_sha256);

	if (diff == DIFF_NONE || diff == DIFF_ERR) {
		agnt_info(LOG_APCFG, "%s: The same config received, skip radio configuration for: " MACFMT "\n",
		     __func__, MAC2STR(radio->macaddr));

		radio->state = AUTOCFG_HEARTBEAT;
		goto teardown;
	}

	agnt_dbg(LOG_APCFG, "%s: New config received for radio: " MACFMT"\n",
		     __func__, MAC2STR(radio->macaddr));

	if (tv[AP_AUTOCONFIGURATION_WSC_M2_DEFAULT_8021Q_SETTINGS_IDX][0]) {
		struct tlv_default_8021q_settings *tlv = (struct tlv_default_8021q_settings *)
			tv[AP_AUTOCONFIGURATION_WSC_M2_DEFAULT_8021Q_SETTINGS_IDX][0]->data;
		//uci_apply_default_8021q_settings(tlv);
		agent_fill_8021q_setting_from_tlv(a, tlv);
	}

	if (radio->dedicated_backhaul) {
		agnt_dbg(LOG_APCFG, "%s: %s is dedicated backhaul"\
				" radio found - discard WSC and set to"\
				" HEARTBEAT\n", __func__,
				radio->name);
		radio->state = AUTOCFG_HEARTBEAT;
		goto exit;
	}

	wifi_teardown_map_ifaces_by_radio(a, radio->name);

	while (num < TLV_MAXNUM && tv[AP_AUTOCONFIGURATION_WSC_M2_WSC_IDX][num]) {
		struct wps_credential out = {0};
		char ifname[IFNAMSIZ] = {0};
		uint8_t *ext = NULL;
		uint16_t extlen = 0;
		struct wsc_ext exts = {
			.enabled = true
		};
		struct netif_ap *ap = NULL;
		struct netif_apcfg *apcfg;
		char ssid[33] = {0};

		ret = wsc_process_m2(radio->autconfig.m1_frame,
				radio->autconfig.m1_size,
				radio->autconfig.key, tv[AP_AUTOCONFIGURATION_WSC_M2_WSC_IDX][num]->data,
				tlv_length(tv[AP_AUTOCONFIGURATION_WSC_M2_WSC_IDX][num]),
				&out, &ext, &extlen);
		if (ret) {
			agnt_err(LOG_APCFG, "Failed to process M2 target for interface "\
					MACFMT "!\n", MAC2STR(bssid));

			wifi_teardown_map_ifaces_by_radio(a, radio->name);
			/* Return rather than freeing because it may belong to
			 * an updated frame
			 */
			agent_autoconfig_event(a, radio->name, "teardown",
					"M2 process failure");
			autoconfig_clean_wsc_hash(&radio->autconfig);
			return -1;
		}

		memcpy(ssid, out.ssid, out.ssidlen);
		ret = wsc_get_exts(ext, extlen, &exts);
		if (ret) {
			agnt_err(LOG_APCFG, "Failed to process IOPSYS vendor ext for interface "\
					MACFMT "!\n", MAC2STR(bssid));

			wifi_teardown_map_ifaces_by_radio(a, radio->name);
			/* Return rather than freeing because it may belong to
			 * an updated frame
			 */
			agent_autoconfig_event(a, radio->name, "teardown",
					"IOPSYS extension process error");
			if (ext)
				free(ext);
			autoconfig_clean_wsc_hash(&radio->autconfig);
			return -1;
		}

		if (BIT1(4, out.mapie)) {
			agnt_err(LOG_APCFG, "MAP Extension had teardown bit set, tearing down "\
					"all MAP interfaces for bssid "	MACFMT \
					"\n", MAC2STR(bssid));
			wifi_teardown_map_ifaces_by_radio(a, radio->name);
			agent_autoconfig_event(a, radio->name, "teardown",
					"teardown bit set");
			if (ext)
				free(ext);
			agent_free_wsc_vendor_ies(&exts);
			radio->state = AUTOCFG_HEARTBEAT;
			goto exit;
		}


		/* try to find good fit for interface*/
		ap = wifi_get_available_ifname_with_ssid(a, radio, ssid, ifname);
		if (!ap)
			ap = wifi_get_first_available_ifname(a, radio, ifname);

		/* no available interface found, try to generate*/
		if (!ap)
			ap = agent_gen_netif_ap(a, radio);

		if (!ap)
			break;

		apcfg = uci_apply_wps_credentials(&a->cfg, ap->ifname, radio->name, &out, &exts);
		if (!apcfg) {
			agnt_err(LOG_APCFG, "Failure to apply M2, tearing down all MAP "\
					" interfaces for bssid " MACFMT "\n",
					MAC2STR(bssid));
			wifi_teardown_map_ifaces_by_radio(a, radio->name);
			agent_autoconfig_event(a, radio->name, "teardown",
					"M2 apply failure");
			if (ext)
				free(ext);
			agent_free_wsc_vendor_ies(&exts);
			goto exit;
		}
		ap->cfg = apcfg;
#ifdef CHECK_PARTIAL_WIFI_RELOAD
		if (a->cfg.partial_wifi_reload) {
			ap->reconfig_reason = 0;

			if (!ap->cfg) {
				agnt_dbg(LOG_APCFG, "[%s, %s] apconf !apcfg - do full reconf\n", radio->name, ifname);
				full_reconf_required = true;
			} else {
				char enc[20];
				int mfp;

				if (strcmp(ap->cfg->ssid, (char *) out.ssid)) {
					agnt_dbg(LOG_APCFG, "[%s, %s] apconf ssid different %s vs %s - RECONFIG_SSID\n", radio->name, ifname,
						(char *) out.ssid, ap->cfg->ssid);
					ap->reconfig_reason |= AGENT_AP_RECONFIG_SSID;
				}

				if (strcmp(ap->cfg->key, (char *) out.key)) {
					agnt_dbg(LOG_APCFG, "[%s, %s] apconf key different %s vs %s - RECONFIG_PSK\n", radio->name, ifname,
						(char *) out.key, ap->cfg->key);
					ap->reconfig_reason |= AGENT_AP_RECONFIG_PSK;
				}

				memset(enc, 0, sizeof(enc));
				if (!get_encryption_value(out.auth_type, out.enc_type, enc, sizeof(enc), &mfp)) {
					agnt_dbg(LOG_APCFG, "[%s, %s] apconf !encryption - do full reconf\n", radio->name, ifname);
					full_reconf_required = true;
				} else {
					if (strcmp(enc, ap->cfg->encryption)) {
						agnt_dbg(LOG_APCFG, "[%s, %s] apconf encryption changed %s vs %s\n", radio->name, ifname, enc, ap->cfg->encryption);
						full_reconf_required = true;
					}
				}
			}

			if (!strlen(ap->ssid)) {
				agnt_dbg(LOG_APCFG, "[%s, %s] apconf no AP ssid configured do issue full reconf\n", radio->name, ifname);
				full_reconf_required = true;
			}

			if (!ap->enabled) {
				agnt_dbg(LOG_APCFG, "[%s, %s] apconf AP not enabled do issue full reconf\n", radio->name, ifname);
				full_reconf_required = true;
			}
		}

		/* Do default/old wifi reconf */
		if (!a->cfg.partial_wifi_reload) {
			agnt_dbg(LOG_APCFG, "[%s, %s] apconf issue full reconf - partial_wifi_reload not set\n", radio->name, ifname);
			full_reconf_required = true;
		}
#endif
		ap->torndown = false;
#if (EASYMESH_VERSION >= 6)
		if (ap->cfg) {
			char idx_str[4];

			ap->cfg->bss_index = num;
			snprintf(idx_str, sizeof(idx_str), "%u", num);
			uci_set_wireless_interface_option("mapagent",
					"ap", "ifname", ap->ifname,
					"bss_index", idx_str);
		}
#endif
		if (ext)
			free(ext);
		agent_free_wsc_vendor_ies(&exts);
		num++;
	}

	if (tv[AP_AUTOCONFIGURATION_WSC_M2_TRAFFIC_SEPARATION_POLICY_IDX][0]) {
		struct tlv_traffic_sep_policy *tlv = (struct tlv_traffic_sep_policy *)
			tv[AP_AUTOCONFIGURATION_WSC_M2_TRAFFIC_SEPARATION_POLICY_IDX][0]->data;

		agnt_dbg(LOG_APCFG, "%s: TS policy received, num_ssid = %u\n", __func__, tlv->num_ssid);

		if (tlv->num_ssid > 0) {
			a->reconfig_reason &= ~AGENT_RECONFIG_REASON_VLAN_TEARDOWN;
			a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_SETUP;
			agent_fill_traffic_sep_policy(a, tlv);
		} else if (agent_is_ts_enabled(a)) {
			a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_TEARDOWN;
		}

	} else if (agent_is_ts_enabled(a))
		a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_TEARDOWN;

	wifi_clean_torndown_ifaces_on_radio(a, radio);
	agent_exec_platform_scripts("write_credentials");
	radio->state = AUTOCFG_HEARTBEAT;

#if (EASYMESH_VERSION >= 6)
	if (tv[AP_AUTOCONFIGURATION_WSC_M2_AP_MLD_CONFIG_IDX][0]) {
		mlo_process_ap_mld_config(a,
				tv[AP_AUTOCONFIGURATION_WSC_M2_AP_MLD_CONFIG_IDX][0]->data);
		a->reconfig_reason |= AGENT_RECONFIG_REASON_MLD_ID;
		timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);

		if (diff & DIFF_AP_MLD)
			autoconfig_ap_mld_hash_to_file(a->ap_mld_cfg_sha256);
	}

	if (tv[AP_AUTOCONFIGURATION_WSC_M2_BACKHAUL_STA_MLD_CONFIG_IDX][0]) {
		int ret = 0;

		ret = mlo_process_bsta_mld_config(a,
				tv[AP_AUTOCONFIGURATION_WSC_M2_BACKHAUL_STA_MLD_CONFIG_IDX][0]->data);
		if (ret)
			bstamld_teardown_band(a, radio->band);
		a->reconfig_reason |= AGENT_RECONFIG_REASON_MLD_ID;
		timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);

		if (diff & DIFF_BSTA_MLD)
			autoconfig_bsta_mld_hash_to_file(a->bsta_mld_cfg_sha256);
	} else {
		agnt_dbg(LOG_APCFG, "%s: Missing BSTA MLD config TLV, teardown\n",
		    __func__);
		bstamld_teardown_band(a, radio->band);
	}

	if (tv[AP_AUTOCONFIGURATION_WSC_M2_BSS_ADVANCED_CONFIG_IDX][0])
		agent_process_bss_advanced_config(a,
				tv[AP_AUTOCONFIGURATION_WSC_M2_BSS_ADVANCED_CONFIG_IDX][0]->data,
				tlv_length(tv[AP_AUTOCONFIGURATION_WSC_M2_BSS_ADVANCED_CONFIG_IDX][0]));

	if (tv[AP_AUTOCONFIGURATION_WSC_M2_RSN_PARAMETERS_CONFIG_IDX][0]) {
		agnt_dbg(LOG_APCFG, "%s: Recvd RSN Params config TLV\n", __func__);
		agent_process_rsn_parameters_config(a,
						    tv[AP_AUTOCONFIGURATION_WSC_M2_RSN_PARAMETERS_CONFIG_IDX][0]->data,
						    tlv_length(tv[AP_AUTOCONFIGURATION_WSC_M2_RSN_PARAMETERS_CONFIG_IDX][0]));
	}
#endif

	agnt_dbg(LOG_APCFG, "%s: radio (%s) was configured! Apply heartbeat for this radio\n",
				__func__, radio->name);

	agent_autoconfig_event(a, radio->name, "success", "completed");

	/* Save successfully processed autoconfig wsc SHA256 */
	autoconfig_update_wsc_hash(&radio->autconfig, new_sha256);
	autoconfig_radio_hash_to_file(radio->macaddr, new_sha256);

	agnt_dbg(LOG_APCFG, "%s: Applied autoconfig SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__,
	    new_sha256[0], new_sha256[1], new_sha256[2], new_sha256[3], new_sha256[4], new_sha256[5]);

#ifdef CHECK_PARTIAL_WIFI_RELOAD
	if (!full_reconf_required) {
		agnt_dbg(LOG_APCFG, "apconf %s skip full reconf\n", radio->name);
		a->reconfig_reason |= AGENT_RECONFIG_REASON_AP_CRED;
		timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);
		goto teardown;
	}
#endif /* CHECK_PARTIAL_WIFI_RELOAD */

exit:
	a->reconfig_reason |= AGENT_RECONFIG_REASON_AP_AUTOCONF; /* ap autoconfig bit */
	timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);
teardown:
	agent_free_wsc_data(&radio->autconfig);
	radio->autconfig.key = NULL;
	radio->autconfig.m1_frame = NULL;
	return 0;
}

int handle_ap_autoconfig_renew(void *agent, struct cmdu_buff *rx_cmdu,
			       struct node *n)
{
	trace("agent: %s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	struct tlv_supported_role *supp_role;
	struct tlv *tv[AP_AUTOCONFIGURATION_RENEW_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_aladdr *aladdr;
	int ret;

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

	aladdr = (struct tlv_aladdr *)
		tv[AP_AUTOCONFIGURATION_RENEW_AL_MAC_ADDRESS_TYPE_IDX][0]->data;
	supp_role = (struct tlv_supported_role *)
		tv[AP_AUTOCONFIGURATION_RENEW_SUPPORTED_ROLE_IDX][0]->data;

	/* local cntlr enforced: accept renew from self only */
	if (a->cntlr_select.local == true &&
			memcmp(aladdr->macaddr, a->almac, 6)) {
		dbg("%s: local set in controller_select - don't"\
				"trigger WSC with "\
				"non-local controller\n",
				__func__);
		/* local cntlr enforced: don't WSC with non-local one */
		return -1;
	}

	if (memcmp(aladdr->macaddr, a->cntlr_almac, 6))
		return -1;

	if (supp_role->role != IEEE80211_ROLE_REGISTRAR)
		return -1;

	struct wifi_radio_element *radio = NULL;
	list_for_each_entry(radio, &a->radiolist, list) {
		struct cmdu_buff *resp;
		uint16_t mid = 0;

		radio->renew_mid = cmdu_get_mid(rx_cmdu);
		radio->state = AUTOCFG_ACTIVE;
		resp = agent_gen_ap_autoconfig_wsc(a, rx_cmdu->origin, radio);
		if (!resp)
			continue;

		ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
				resp->origin, resp->cdata, cmdu_size(resp),
				CMDU_PRE_TX);
		if (ret != CMDU_DROP) {
			mid = agent_send_cmdu(a, resp);
			if (mid) {
				radio->wsc_mid = mid;
				trace("assigned radio mid %d %d\n", radio->wsc_mid, mid);
			}
		} else {
			warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
			     __func__, cmdu_get_type(resp));
		}

		cmdu_free(resp);
	}

	return 0;
}

int handle_higher_layer_query(void *agent, struct cmdu_buff *rx_cmdu,
			       struct node *n)
{
	trace("%s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	struct cmdu_buff *resp;
	int ret;

	/* Generate cmdu */
	resp = agent_gen_higher_layer_response(a, rx_cmdu);
	if (!resp)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
			resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(a, resp);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);
	return ret;
}


/* ap: prepare the query for that specific interface;
 * ap: NULL, include all the interface.
 */
struct cmdu_buff *prepare_ap_metrics_query(void *agent, struct netif_ap *ap)
{
	struct agent *a = (struct agent *)agent;
	struct wifi_radio_element *re = NULL;
	struct wifi_bss_element *bss;
	struct cmdu_buff *cmdu = NULL;
	uint8_t *bsslist_orig = NULL;
	uint8_t *bsslist = NULL;
	int total_bss = 0;
	uint16_t mid = 0;
	int ret;

	cmdu = cmdu_alloc_simple(CMDU_AP_METRICS_QUERY, &mid);
	if (!cmdu) {
		dbg("%s: -ENOMEM\n", __func__);
		return NULL;
	}

	memcpy(cmdu->origin, a->cntlr_almac, 6);

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

			/* Radio Identifier TLV */
			ret = agent_gen_ap_radio_identifier(a,
					cmdu, re->macaddr);
			if (ret)
				goto error;

			list_for_each_entry(fn, &re->aplist, list) {
				bss = &fn->bss;

				bsslist_orig = bsslist;
				bsslist = (uint8_t *)realloc(bsslist,
						(total_bss + 1) * 6 * sizeof(uint8_t));
				if (!bsslist)
					goto error;
				bsslist_orig = NULL;
				memcpy(&bsslist[total_bss * 6], bss->bssid, 6);
				total_bss++;
			}
		}
	} else {
		re = agent_get_radio_by_band(a, ap->band);
		if (!re)
			goto error;

//#ifdef PROFILE2
		/* Radio Identifier TLV */
		ret = agent_gen_ap_radio_identifier(a,
				cmdu, re->macaddr);
		if (ret)
			goto error;
//#endif

		total_bss = 1;
		bsslist = calloc(total_bss, 6 * sizeof(uint8_t));
		if (!bsslist)
			goto error;

		bss = &ap->bss;
		memcpy(&bsslist[0], bss->bssid, 6);
	}

	if (total_bss <= 0) {
		dbg("%s: total_bss(%d) indicates no BSS to query\n",
		    __func__, total_bss);
		goto error;
	}

	/* AP Metrics TLV */
	ret = agent_gen_ap_metric_query(a, cmdu, total_bss, bsslist);
	if (ret)
		goto error;

	if (bsslist)
		free(bsslist);

	return cmdu;

error:
	if (bsslist)
		free(bsslist);

	if (bsslist_orig)
		free(bsslist_orig);

	cmdu_free(cmdu);

	return NULL;
}

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

int handle_ap_caps_query(void *agent, struct cmdu_buff *rx_cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	struct cmdu_buff *resp;
	int ret;

	/* Generate cmdu */
	resp = agent_gen_ap_caps_response(a, rx_cmdu);
	if (!resp)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
			resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(a, resp);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);
	return ret;
}

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

	struct agent *a = (struct agent *)agent;
	struct tlv *tv[AP_CAPABILITY_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int idx;

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

	/* Zero or more AP HT capabilities (per radio) */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[AP_CAPABILITY_REPORT_AP_HT_CAPS_IDX][idx]) {
		struct tlv_ap_ht_cap *tlv;
		struct wifi_radio_element *re = NULL;

		tlv = (struct tlv_ap_ht_cap *)tv[AP_CAPABILITY_REPORT_AP_HT_CAPS_IDX][idx]->data;
		if (tlv == NULL)
			return -1;

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

			list_for_each_entry(ap, &re->aplist, list) {
				struct neighbor *nbr = NULL;
				bool found = false;

				list_for_each_entry(nbr, &ap->nbrlist, list) {
					if (memcmp(nbr->radio_mac, tlv->radio, 6))
						continue;

					found = true;
					/* Set HT bit in bssid_info */
					agent_bssid_info_set(nbr->nbr.bssid_info,
							BSSID_INFO_HT);

					/* Update phy type */
					nbr->nbr.phy = PHY_HT;

					nbr->flags &= ~NBR_FLAG_DRV_UPDATED;
				}

				if (found)
					/* Sync nbrlist in ap & driver */
					reschedule_nbrlist_update(ap);
			}
		}
		idx++;
	}

	/* Zero or more AP VTH capabilities (per radio) */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[AP_CAPABILITY_REPORT_AP_VHT_CAPS_IDX][idx]) {
		struct wifi_radio_element *re = NULL;
		struct tlv_ap_vht_cap *tlv;

		tlv = (struct tlv_ap_vht_cap *)tv[AP_CAPABILITY_REPORT_AP_VHT_CAPS_IDX][idx]->data;
		if (tlv == NULL)
			return -1;

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

			list_for_each_entry(ap, &re->aplist, list) {
				struct neighbor *nbr = NULL;
				bool found = false;

				list_for_each_entry(nbr, &ap->nbrlist, list) {
					if (memcmp(nbr->radio_mac, tlv->radio, 6))
						continue;

					found = true;

					/* Set VHT bit in bssid_info */
					agent_bssid_info_set(nbr->nbr.bssid_info,
							BSSID_INFO_VHT);

					/* Update phy type */
					nbr->nbr.phy = PHY_VHT;

					nbr->flags &= ~NBR_FLAG_DRV_UPDATED;
				}

				if (found)
					/* Sync nbrlist in ap & driver */
					reschedule_nbrlist_update(ap);
			}
		}

		idx++;
	}

	/* Zero or more AP HE capabilities (per radio) */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[AP_CAPABILITY_REPORT_AP_HE_CAPS_IDX][idx]) {
		struct wifi_radio_element *re = NULL;
		struct tlv_ap_he_cap *tlv;

		tlv = (struct tlv_ap_he_cap *)tv[AP_CAPABILITY_REPORT_AP_HE_CAPS_IDX][idx]->data;
		if (tlv == NULL)
			return -1;

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

			list_for_each_entry(ap, &re->aplist, list) {
				struct neighbor *nbr = NULL;
				bool found = false;

				list_for_each_entry(nbr, &ap->nbrlist, list) {
					if (memcmp(nbr->radio_mac, tlv->radio, 6))
						continue;

					found = true;

					/* Update phy type */
					nbr->nbr.phy = PHY_HE;

					nbr->flags &= ~NBR_FLAG_DRV_UPDATED;
				}

				if (found)
					/* Sync nbrlist in ap & driver */
					reschedule_nbrlist_update(ap);
			}
		}

		idx++;
	}

	return 0;
}

static uint8_t calculate_radio_util(struct agent *a, struct netif_ap *ap)
{
	struct wifi_radio_element *radio;

	radio = agent_get_radio_by_name(a, ap->radio_name);
	if (!radio)
		return 0;

	return radio->total_utilization;
}

static void agent_util_threshold_timer_cb(atimer_t *t)
{
	struct netif_ap *p = container_of(t, struct netif_ap,
			util_threshold_timer);
	struct agent *a = p->agent;
	struct cmdu_buff *cmdu;
	uint8_t curr_util = 0;
	uint8_t prev_util;
	struct agent_config_radio *rcfg;
	struct node *n;

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

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

	curr_util = calculate_radio_util(a, p);
	prev_util = p->prev_util;
	if (((prev_util > rcfg->util_threshold) &&
				(curr_util > rcfg->util_threshold)) ||
			((prev_util < rcfg->util_threshold) &&
			 (curr_util < rcfg->util_threshold)))
		goto refresh_interval;

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

	send_ap_metrics_response(a, cmdu, n);
	cmdu_free(cmdu);

refresh_interval:
	p->prev_util = curr_util;
	timer_set(&p->util_threshold_timer, UTIL_THRESHOLD_TIMER);
}

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

	struct agent_config *cfg = &a->cfg;
	struct policy_cfg *c = cfg->pcfg;
	struct wifi_radio_element *re = NULL;

	/* timer cleanup */
	if (timer_pending(&cfg->metric_report_timer))
		timer_del(&cfg->metric_report_timer);

	if (c && (c->report_interval > 0))
		timer_set(&cfg->metric_report_timer, c->report_interval * 1000);

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

		ap = wifi_radio_to_ap(a, re);
		if (!ap)
			continue;

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

		/* timers cleanup ap specific */
		if (timer_pending(&ap->util_threshold_timer))
			timer_del(&ap->util_threshold_timer);

		if (rcfg->util_threshold > 0) {
			timer_init(&ap->util_threshold_timer, agent_util_threshold_timer_cb);
			timer_set(&ap->util_threshold_timer, UTIL_THRESHOLD_TIMER);
		}
	}

	return 0;
}

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

	int idx = 0;
	struct agent *a = (struct agent *)agent;
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct tlv *tv[MAP_POLICY_CONFIG_REQ_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

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

	/* send the 1905 ack message to controller */
	send_1905_acknowledge(a, cmdu->origin, cmdu_get_mid(cmdu), NULL, 0);

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, "mapagent", &pkg)) {
		uci_free_context(ctx);
		return -1;
	}

	if (tv[MAP_POLICY_CONFIG_REQ_STEERING_POLICY_IDX][0]) {
		struct tlv_steering_policy *p =
			(struct tlv_steering_policy *)tv[MAP_POLICY_CONFIG_REQ_STEERING_POLICY_IDX][0]->data;

		agent_fill_steering_policy(a, p, ctx, pkg);
	}

	if (tv[MAP_POLICY_CONFIG_REQ_METRIC_REPORTING_POLICY_IDX][0]) {
		struct tlv_metric_report_policy *p = (struct tlv_metric_report_policy *)
			tv[MAP_POLICY_CONFIG_REQ_METRIC_REPORTING_POLICY_IDX][0]->data;

		agent_fill_metric_report_policy(a, p, ctx, pkg);
	}

	if (tv[MAP_POLICY_CONFIG_REQ_DEFAULT_8021Q_SETTINGS_IDX][0]) {
		struct tlv_default_8021q_settings *p = (struct tlv_default_8021q_settings *)
			tv[MAP_POLICY_CONFIG_REQ_DEFAULT_8021Q_SETTINGS_IDX][0]->data;

		agent_fill_8021q_setting_from_tlv(a, p);
	}

	if (tv[MAP_POLICY_CONFIG_REQ_TRAFFIC_SEPARATION_POLICY_IDX][0]) {
		struct tlv_traffic_sep_policy *p = (struct tlv_traffic_sep_policy *)
			tv[MAP_POLICY_CONFIG_REQ_TRAFFIC_SEPARATION_POLICY_IDX][0]->data;

		dbg("%s: TS policy received, num_ssid = %u\n", __func__, p->num_ssid);

		if (p->num_ssid > 0) {
			a->reconfig_reason &= ~AGENT_RECONFIG_REASON_VLAN_TEARDOWN;
			a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_SETUP;
			agent_fill_traffic_sep_policy(a, p);
		} else if (agent_is_ts_enabled(a)) {
			a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_TEARDOWN;
		}
	} else if (agent_is_ts_enabled(a))
		a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_TEARDOWN;

	/* set reload timer */
	timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);

	if (tv[MAP_POLICY_CONFIG_REQ_CHANNEL_SCAN_REPORTING_POLICY_IDX][0]) {
		struct tlv_channel_scan_report_policy *p = (struct tlv_channel_scan_report_policy *)
			tv[MAP_POLICY_CONFIG_REQ_CHANNEL_SCAN_REPORTING_POLICY_IDX][0]->data;

		agent_fill_ch_scan_rep_policy(a, p, ctx, pkg);
	}

	if (tv[MAP_POLICY_CONFIG_REQ_UNSUCCESS_ASSOCIATION_POLICY_IDX][0]) {
		struct tlv_unsuccess_assoc_policy *p = (struct tlv_unsuccess_assoc_policy *)
			tv[MAP_POLICY_CONFIG_REQ_UNSUCCESS_ASSOCIATION_POLICY_IDX][0]->data;

		agent_fill_unsuccess_assoc_policy(a, p, ctx, pkg);
	}

	idx = 0;
	while (idx < TLV_MAXNUM && tv[MAP_POLICY_CONFIG_REQ_BACKHAUL_BSS_CONFIG_IDX][idx]) {
		uint8_t generic_id[6] = {0xff, 0x0ff, 0xff, 0xff, 0xff, 0xff};
		struct tlv_bbss_config *p = (struct tlv_bbss_config *)
			tv[MAP_POLICY_CONFIG_REQ_BACKHAUL_BSS_CONFIG_IDX][idx++]->data;

		if (!memcmp(p->bssid, generic_id, 6)) {
			agent_fill_backhaul_bss_config_all(a, p, ctx, pkg);
		} else
			agent_fill_backhaul_bss_config(a, p, ctx, pkg);
	}

	/* update agent config file */
	uci_commit(ctx, &pkg, false);
	uci_unload(ctx, pkg);
	uci_free_context(ctx);

	/* Reload agent config */
	agent_config_reload(a);

	agent_process_policy_config(a);
	return 0;
}

int handle_channel_pref_query(void *agent, struct cmdu_buff *rx_cmdu,
			      struct node *n)
{
	agnt_trace(LOG_CHANNEL, "%s: --->\n", __func__);
	struct agent *a = (struct agent *)agent;
	struct cmdu_buff *cmdu;
	uint16_t mid;
	int ret;

	mid = cmdu_get_mid(rx_cmdu);
	cmdu = agent_gen_channel_preference_report(a, mid, rx_cmdu->origin, NULL);
	if (!cmdu)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		agnt_warn(LOG_CHANNEL, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	/* Send also latest/current channel report */
#ifndef STRICT_OPER_CHANNEL_REPORT
	send_oper_channel_report(a, NULL);
#endif

	return 0;
}

int send_channel_sel_response(void *agent, struct cmdu_buff *rx_cmdu,
		struct channel_response *channel_resp,
		uint32_t channel_response_nr)
{
	agnt_trace(LOG_CHANNEL, "agent: %s: --->\n", __func__);

	struct agent *a = (struct agent *) agent;
	uint32_t j, ret = 0;
	uint16_t mid = 0;
	struct cmdu_buff *cmdu;

	mid = cmdu_get_mid(rx_cmdu);
	cmdu = cmdu_alloc_simple(CMDU_CHANNEL_SELECTION_RESPONSE, &mid);
	if (!cmdu) {
		agnt_dbg(LOG_CHANNEL, "%s: -ENOMEM\n", __func__);
		return -1;
	}

	memcpy(cmdu->origin, rx_cmdu->origin, 6);

	/* Operating Channel Response TLV 17.2.16 */
	for (j = 0; j < channel_response_nr; j++) {
		/* Here we need to check that the radio
		 *response is for the radio for which we get request
		 */
		ret = agent_gen_channel_selection_resp(a, cmdu,
			channel_resp[j].radio_id, channel_resp[j].response);
		if (ret)
			goto error;
	}

	cmdu_put_eom(cmdu);

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		agnt_warn(LOG_CHANNEL, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	return ret;

error:
	cmdu_free(cmdu);
	return -1;
}

int agent_fill_radio_max_preference(void *agent,
		struct channel_response *channel_resp,
		uint32_t *channel_response_nr)
{
	agnt_trace(LOG_CHANNEL, "agent: %s: --->\n", __func__);
	struct agent *a = agent;
	struct wifi_radio_element *re = NULL;
	int i = 0;

	*channel_response_nr = a->num_radios;

	list_for_each_entry(re, &a->radiolist, list) {
		memcpy(channel_resp[i].radio_id, re->macaddr, 6);
		memcpy(&re->req_opclass, &re->opclass, sizeof(re->opclass));
		wifi_opclass_set_preferences(&re->req_opclass, 0x0f << 4);
		channel_resp[i].response = 0x00;
		i++;
	}

	return 0;
}

int get_op_class_sec(int op_class)
{
	switch (op_class) {
	case 83:
	case 116:
	case 119:
	case 122:
	case 126:
		return 1;
	case 84:
	case 117:
	case 120:
	case 123:
	case 127:
		return -1;
	default:
		break;
	}

	return 0;
}

enum wifi_band get_op_class_band(int op_class)
{
	switch (op_class) {
	case 81:
	case 82:
	case 83:
	case 84:
		return BAND_2;
	case 115:
	case 116:
	case 117:
	case 118:
	case 119:
	case 120:
	case 121:
	case 122:
	case 123:
	case 124:
	case 125:
	case 126:
	case 127:
	case 128:
	case 129:
	case 130:
		return BAND_5;
	case 131:
	case 132:
	case 133:
	case 134:
		return BAND_6;
	default:
		return BAND_UNKNOWN;
	}
}

int get_op_class_bw(int op_class)
{
	switch (op_class) {

	case 115:
	case 118:
	case 121:
	case 125:
	case 81:
	case 82:
	case 124:
	case 131:
		return 20;
	case 116:
	case 119:
	case 122:
	case 117:
	case 120:
	case 123:
	case 83:
	case 84:
	case 126:
	case 127:
	case 132:
		return 40;
	case 128:
	case 130:
	case 133:
	case 135:
		return 80;
	case 129:
	case 134:
		return 160;
	case 137:
		return 320;
	default:
		return 0;
	}
}

enum wifi_bw get_op_class_wifi_bw(int op_class)
{
	switch (op_class) {

	case 115:
	case 118:
	case 121:
	case 125:
	case 81:
	case 82:
	case 124:
		return BW20;
	case 116:
	case 119:
	case 122:
	case 117:
	case 120:
	case 123:
	case 83:
	case 84:
	case 126:
	case 127:
		return BW40;
	case 128:
	case 130:
		return BW80;
	case 129:
		return BW160;
	default:
		return BW20;
	}
}

int agent_set_channel_preference_to_default(struct wifi_radio_element *radio)
{
	agnt_trace(LOG_CHANNEL, "%s: --->\n", __func__);
	wifi_opclass_set_preferences(&radio->req_opclass, 0x0f << 4);
	return 0;
}

/*This function return the channel with the highest preference*/
int agent_get_highest_preference(struct wifi_radio_element *radio,
		uint32_t op_class_id, uint32_t *channel_to_move,
		uint32_t *opclass_to_move)
{
	agnt_trace(LOG_CHANNEL, "%s: --->\n", __func__);
	uint8_t opclass;
	uint8_t channel;

	if (wifi_opclass_get_higest_preference(&radio->opclass, radio->current_bandwidth, &opclass, &channel))
		return -1;

	*opclass_to_move = opclass;
	*channel_to_move = channel;
	agnt_trace(LOG_CHANNEL, "%s: channel switch to channel %d opclass %d\n", __func__, *channel_to_move, *opclass_to_move);
	return 0;
}

int agent_channel_switch(struct agent *a, uint8_t *radio_id, int channel, int opclass)
{
	struct wifi_radio_element *re = NULL;
	struct chan_switch_param param = {};
	struct netif_ap *ap = NULL;
	int found = 0;
	int ret;

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

	re = agent_get_radio(a, radio_id);
	if (!re)
		return -1;

	list_for_each_entry(ap, &re->aplist, list) {
		if (ap->cfg->multi_ap != 2)
			continue;
		else {
			found = 1;
			break;
		}
	}

	if (found != 1)
		return -1;

	param.bandwidth = get_op_class_bw(opclass);
	param.freq = c2f_band(channel, get_op_class_band(opclass));
	param.count = 5;
	param.sec_chan_offset = get_op_class_sec(opclass);

	if (re->current_channel == channel &&
	    re->current_bandwidth == param.bandwidth &&
	    !re->cac_required) {
		/* Just in case controller don't know our oper channel */
		re->report_oper_channel = true;
		return 0;
	}

	/* Check current mode/standard */
	if (strstr(ap->standard, "be"))
		param.eht = true;
	if (strstr(ap->standard, "ax"))
		param.he = true;
	if (strstr(ap->standard, "ac"))
		param.vht = true;
	if (strstr(ap->standard, "n"))
		param.ht = true;

	if (!strlen(ap->standard))
		param.auto_ht = true;

	ret = wifi_chan_switch(ap->ifname, &param);

	agnt_info(LOG_CHANNEL, "%s: channel %d bandwidth %d sec %d ht/vht/he/eht: %d/%d/%d/%d auto-ht %d ret %d\n",
		__func__, channel, param.bandwidth, param.sec_chan_offset,
		param.ht, param.vht, param.he, param.eht, param.auto_ht, ret);

	return ret;
}

int agent_config_channel_preference(struct agent *a,
		uint8_t *radio_id, uint32_t opclass_id)
{
	struct wifi_radio_opclass *opclass;
	struct wifi_radio_element *re;
	int ret = 0;
	int i;

	agnt_trace(LOG_CHANNEL, "%s: radio_id: " MACFMT "\n", __func__,
			MAC2STR(radio_id));

	/* Here we need to write the preferences in the config file
	 * only the channels in which the preferences are not
	 * highest preferences
	 */

	re = agent_get_radio(a, radio_id);
	if (!re)
		return -1;

	/* Save requested opclass */
	opclass = &re->req_opclass;
	for (i = 0; i < opclass->entry_num; i++) {
		if (opclass->entry[i].id == opclass_id) {
			uint8_t channel_list[20] = {0};
			int j = 0, k = 0;
			int pref = 0x0f;

			for (j = 0; j < opclass->entry[i].channel_num ; j++) {
				pref = opclass->entry[i].channel[j].preference;
				channel_list[k++] = opclass->entry[i].channel[j].channel;
			}

			ret = wifi_set_opclass_preference(re->name,
					opclass_id, pref, channel_list, k);
			if (ret != 0)
				agnt_err(LOG_CHANNEL, "cannot write preference in config\n");
		}
	}

	return ret;
}

static bool agent_radio_has_active_bsta(struct agent *a, struct wifi_radio_element *re)
{
	if (re->has_bsta && re->bk.connected)
		return true;

	return false;
}

int agent_process_channel_pref_tlv(void *agent, struct tlv_channel_pref *p,
		struct channel_response *channel_resp,
		uint32_t *channel_resp_nr)
{
	struct agent *a = (struct agent *) agent;
	struct wifi_radio_element *re;
	struct wifi_radio_opclass opclass = {};
	uint8_t target_opclass=0;
	uint8_t target_channel=0;
	uint8_t radio_id[6] = {0};
	uint8_t *data = (uint8_t *)p;
	uint8_t opclass_nr;
	uint8_t opclass_offset;
	int offset = 0;
	int ret = 0;
	int i, j;

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

	/* Check radio and active bsta */
	memcpy(radio_id, &data[offset], 6);
	offset += 6;
	agnt_trace(LOG_CHANNEL, "\tradio_id: " MACFMT "\n", MAC2STR(radio_id));
	re = agent_get_radio(a, radio_id);
	if (re) {
		if (!re->acs_enabled) {
			agnt_warn(LOG_CHANNEL, "%s radio %s has disabled local ACS\n", __func__, re->name);
			memcpy(channel_resp[*channel_resp_nr].radio_id,
				re->macaddr, 6);
			channel_resp[*channel_resp_nr].response = 0x02;
			*channel_resp_nr = *channel_resp_nr + 1;
			return 0;
		}

		ret = agent_radio_has_active_bsta(a, re);
		if (ret != 0) {
			agnt_err(LOG_CHANNEL, "radio has active bsta connection\n");
			memcpy(channel_resp[*channel_resp_nr].radio_id,
				re->macaddr, 6);
			channel_resp[*channel_resp_nr].response = 0x03;
			*channel_resp_nr = *channel_resp_nr + 1;
			return 0;
		}
		memcpy(channel_resp[*channel_resp_nr].radio_id,
			re->macaddr, 6);
		channel_resp[*channel_resp_nr].response = 0x00;
	} else {
		memcpy(channel_resp[*channel_resp_nr].radio_id,
			radio_id, 6);
		/* No radio found so report preferences changed */
		channel_resp[*channel_resp_nr].response = 0x01;
		*channel_resp_nr = *channel_resp_nr + 1;

		return ret;
	}

	opclass_offset= offset;

	opclass_nr = data[offset++];
	agnt_trace(LOG_CHANNEL, "[%s]: ch_preference_op_class_nr: %d\n", re->name, opclass_nr);

	/* Build E-4 table first */
	memcpy(&opclass, &re->opclass, sizeof(opclass));
	wifi_opclass_set_preferences(&opclass, 0x0 << 4);

	/* Check reported opclasses and set higest pref for all channels */
	for (i = 0; i < opclass_nr; i++) {
		struct wifi_radio_opclass_entry *entry;
		uint8_t id;
		uint8_t channel_num;

		id = data[offset++];
		channel_num = data[offset++];
		offset += channel_num;
		offset++;

		entry = wifi_opclass_find_entry(&opclass, id);
		if (WARN_ON(!entry))
			continue;

		wifi_opclass_id_set_preferences(&opclass, id, 0x0f << 4);
	}

	/* Finally check requested preferences */
	offset = opclass_offset + 1;
	for (i = 0; i < opclass_nr; i++) {
		struct wifi_radio_opclass_entry *entry;
		struct wifi_radio_opclass_channel channel = {};
		struct wifi_radio_opclass_channel *chan;
		uint8_t id;
		uint8_t channel_num;
		uint8_t preference;

		id = data[offset++];
		channel_num = data[offset++];
		preference = data[offset + channel_num];

		entry = wifi_opclass_find_entry(&opclass, id);
		if (WARN_ON(!entry))
			continue;

		agnt_trace(LOG_CHANNEL, "[%s] -- opclass %d channels %d pref 0x%02X\n", re->name, id, channel_num, preference);
		if (channel_num == 0) {
			/* Setup same preference for all channels inside opclass */
			wifi_opclass_id_set_preferences(&opclass, id, preference);
		} else {
			for (j = 0; j < channel_num; j++) {
				chan = wifi_opclass_find_channel(entry, data[offset]);
				if (chan)
					channel = *chan;
				channel.channel = data[offset++];
				channel.preference = preference;
				wifi_opclass_add_channel(entry, &channel);
				agnt_trace(LOG_CHANNEL, "[%s] \tchan %d\n", re->name, channel.channel);
			}
		}

		offset++;
	}

	/* Remove unsupported channels */
	wifi_opclass_mark_unsupported(&opclass, &re->opclass);

#ifdef CHANSWITCH_SKIP_DFS_UNAVAILABLE
	/* Check DFS ready also */
	wifi_opclass_mark_unavailable(&opclass, &re->opclass);
#endif

	wifi_opclass_dump(&opclass);

#ifdef LOCAL_ACS_SERVICE
	if (wifi_opclass_channels_operable(&opclass, 20) > 1) {
		memcpy(&re->req_opclass, &opclass, sizeof(opclass));
		agnt_trace(LOG_CHANNEL, "[%s] enable local ACS service, skip chan switch\n", re->name);
		re->report_oper_channel = true;
		re->local_acs_enabled = true;
		goto exit;
	} else {
		agnt_trace(LOG_CHANNEL, "[%s] disable local ACS service\n", re->name);
		re->local_acs_enabled = false;
	}
#endif

	/* Switch to best channel */
	ret = wifi_opclass_get_higest_preference(&opclass, 0,
						 &target_opclass,
						 &target_channel);

	agnt_trace(LOG_CHANNEL, "[%s]: higest pref opclass %u chan %u ret %d\n",
	      re->name, target_opclass, target_channel, ret);


	/* Save it in req_opclass */
	memcpy(&re->req_opclass, &opclass, sizeof(opclass));

	/* Don't fail if all opclasses/channels with pref=0 */
	if (!target_opclass || !target_channel) {
		/* If current channel is allowed, do nothing */
		if (!wifi_opclass_id_channel_disallowed(&re->req_opclass,
				re->current_opclass, re->current_channel)) {
			re->report_oper_channel = true;
			goto exit;
		}

		agnt_dbg(LOG_CHANNEL, "[%s] Current channel %u (opclass %u) is disallowed\n",
				re->name, re->current_channel, re->current_opclass);

		/* Update supported opclass/channels by excluding disallowed entries */
		wifi_opclass_mark_disallowed(&re->opclass, &opclass);

		/* Find best alternative based on supported opclasses */
		ret = wifi_opclass_get_higest_preference(&re->opclass, re->current_bandwidth,
							 &target_opclass,
							 &target_channel);

		if (!target_opclass || !target_channel) {
			agnt_err(LOG_CHANNEL, "[%s] No alternative channel available; staying on current channel\n", re->name);
			re->report_oper_channel = true;
			goto exit;
		}
	}

	if (ret) {
		memcpy(channel_resp[*channel_resp_nr].radio_id, radio_id, 6);
		channel_resp[*channel_resp_nr].response = 0x02;
		*channel_resp_nr = *channel_resp_nr + 1;
		return 0;
	}

	ret = agent_channel_switch(a, radio_id, target_channel, target_opclass);
	if (ret) {
		memcpy(channel_resp[*channel_resp_nr].radio_id, radio_id, 6);
		channel_resp[*channel_resp_nr].response = 0x01;
	}

exit:
	*channel_resp_nr = *channel_resp_nr + 1;
	return 0;
}


int agent_process_transmit_power_tlv(struct agent *a, struct tlv_txpower_limit *p)
{
	struct wifi_radio_element *re;

	agnt_trace(LOG_CHANNEL, "tlv radio_id: " MACFMT "\n",
		MAC2STR(p->radio));

	re = agent_get_radio(a, p->radio);
	if (!re)
		return -1;

	/* Here we set the
	 * transmit_power_limit
	 * of the radio this also needs to be set in
	 * the wireless file
	 */
	re->transmit_power_limit = p->limit;
	//wifi_set_transmit_power(radio->name, p->limit);
	agnt_trace(LOG_CHANNEL, "%s: radio name [%s] transmit power [%d]\n",
		__func__, re->name, p->limit);
	return 0;
}

#if (EASYMESH_VERSION >= 6)
int agent_process_eht_operations_tlv(struct agent *a,
				     struct tlv_eht_operations *p)
{
	uint8_t *ptr = (uint8_t *) p;
	int i;

	ptr += 32; /* rsvd */
	ptr++; /* num_radio */

	for (i = 0; i < p->num_radio; i++) {
		struct wifi_radio_element *re;
		uint8_t ruid[6] = {0};
		int num_bss = 0;
		int j;

		memcpy(ruid, ptr, 6);
		ptr += 6;

		re = agent_get_radio(a, ruid);
		if (!re)
			return -1;

		num_bss = *ptr;
		ptr++; /* num_bss */

		for (j = 0; j < num_bss; j++) {
			uint8_t bssid[6] = {0};
			struct netif_ap *ap;
			uint8_t flag = 0;

			memcpy(bssid, ptr, 6);
			ap = agent_get_ap(a, bssid);
			if (!ap)
				return -1;

			ptr += 6; /* bssid */
			flag = *ptr;
			ptr++; /* flag1 */
			ptr += 4 + 1 + 1 + 1; /* mcs_nss + control + ccfs0 + ccfs1 */

			if (flag & DISABLED_SUBCHANNEL_VALID) {
				uint8_t punct_bitmap[2] = {0};
				char bitmapstr[4] = {0};
				struct agent_config_radio *rcfg;

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

				memcpy(punct_bitmap, ptr, 2);
				if (memcmp(punct_bitmap, "\x00\x00", 2))
					btostr(punct_bitmap, 2, bitmapstr);

				if (memcmp(punct_bitmap, rcfg->punct_bitmap, 2)) {
					agnt_dbg(LOG_CHANNEL, "%s: radio:"MACFMT" puncture "\
					    "bitmap changed, reload wifi\n",
					    __func__, MAC2STR(re->macaddr));
					a->reconfig_reason |= AGENT_RECONFIG_REASON_PUNCT_BITMAP;
					timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);

					uci_set_wireless_interface_option("mapagent",
						"radio", "device", re->name,
						"punct_bitmap", bitmapstr);
					set_value_by_string("wireless", re->name,
						    "ru_punct_bitmap",
						    bitmapstr, UCI_TYPE_STRING);
				}
			}

			ptr += 2; /* disabled_subchannel */
			ptr += 16; /* rsvd */
		}

		ptr += 25; /* rsvd */
	}
	return 0;
}
#endif

static void send_operating_channel_report_if_required(struct agent *a)
{
	struct wifi_radio_element *re = NULL;
	struct cmdu_buff *cmdu;
	int ret;

	list_for_each_entry(re, &a->radiolist, list) {
		if (!re->report_oper_channel)
			continue;
		cmdu = agent_gen_oper_channel_response(a, re, re->current_channel,
						       re->current_bandwidth, 0);
		if (WARN_ON(!cmdu))
			continue;

		ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
				cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
				CMDU_PRE_TX);
		if (ret != CMDU_DROP) {
			agent_send_cmdu(a, cmdu);
		} else {
			agnt_warn(LOG_CHANNEL, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
			     __func__, cmdu_get_type(cmdu));
		}

		cmdu_free(cmdu);

		agnt_dbg(LOG_CHANNEL, "[%s] oper_channel_response (req) chan %d bw %d opclass %d\n",
		    re->name, re->current_channel, re->current_bandwidth,
		    re->current_opclass);
		re->report_oper_channel = false;
	}
}


#if (EASYMESH_VERSION >= 6)
#define CHANNEL_SELECTION_REQUEST_NUM_TLV 4
#else
#define CHANNEL_SELECTION_REQUEST_NUM_TLV 3
#endif
int handle_channel_sel_request(void *agent, struct cmdu_buff *cmdu,
			       struct node *n)
{
	agnt_trace(LOG_CHANNEL, "agent: %s: --->\n", __func__);

	struct tlv *tv[CHANNEL_SELECTION_REQUEST_NUM_TLV][TLV_MAXNUM] = {0};
	struct channel_response channel_resp[MAX_RADIO];
	struct agent *a = (struct agent *) agent;
	uint32_t pref_tlv_present = 0;
	uint32_t channel_resp_nr = 0;
	int ret = 0;
	int idx;

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

	/* Here we first need to update the channel preference values
	 * from the channel selection request
	 * then send the CMDU for channel selection response
	 */
	idx = 0;
	while (idx < TLV_MAXNUM && tv[0][idx]) {
		struct tlv_channel_pref *p =
			(struct tlv_channel_pref *)tv[0][idx++]->data;

		pref_tlv_present = 1;
		agent_process_channel_pref_tlv(agent, p, channel_resp, &channel_resp_nr);
	}

	idx = 0;
	while (idx < TLV_MAXNUM && tv[1][idx]) {
		struct tlv_txpower_limit *p =
			(struct tlv_txpower_limit *)tv[1][idx++]->data;

		agent_process_transmit_power_tlv(a, p);
	}

#if (EASYMESH_VERSION >= 6)
	if (tv[3][0]) {
		struct tlv_eht_operations *p =
			(struct tlv_eht_operations *)tv[3][0]->data;

		agent_process_eht_operations_tlv(a, p);
	}
#endif

	if (pref_tlv_present == 0) {
		/* Here the condition is that the
		 * channel selection request have no tlvs or only transmit power tlv
		 * so we need to set all the prefernce in all radios to max 15
		 */
		agent_fill_radio_max_preference(agent, channel_resp, &channel_resp_nr);
	}

	ret = send_channel_sel_response(agent, cmdu, channel_resp, channel_resp_nr);

	/* Check and send operating channel report */
	send_operating_channel_report_if_required(agent);

	return ret;
}

int handle_sta_caps_query(void *agent, struct cmdu_buff *rx_cmdu,
			  struct node *n)
{
	struct agent *a = (struct agent *)agent;
	struct cmdu_buff *cmdu;
	int ret;

	cmdu = agent_gen_sta_caps_response(a, rx_cmdu, n);
	if (!cmdu)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		agnt_warn(LOG_STA, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);
	return 0;
}

int handle_ap_metrics_query(void *agent, struct cmdu_buff *rx_cmdu,
			    struct node *n)
{
	trace("%s: --->\n", __func__);
	send_ap_metrics_response(agent, rx_cmdu, n);
	return 0;
}

int handle_sta_link_metrics_query(void *agent, struct cmdu_buff *rx_cmdu,
				  struct node *n)
{
	agnt_trace(LOG_STA, "%s: --->\n", __func__);
	struct cmdu_buff *cmdu;
	struct agent *a = (struct agent *)agent;
	int ret;

	cmdu = agent_gen_assoc_sta_metric_response(a, rx_cmdu, n);
	if (!cmdu)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		agnt_warn(LOG_STA, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	return 0;
}


#define MAX_UNASSOC_STAMACS 10
int handle_unassoc_sta_link_metrics_query(void *agent,
		struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	struct tlv *tv[UNASSOC_STA_LINK_METRICS_QUERY_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_unassoc_sta_link_metrics_query *query;
	struct sta_error_response sta_resp[MAX_UNASSOC_STAMACS];
	struct netif_ap *ap = NULL;
	int err_tlv_cnt = 0;
	int i;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile))
		return -1;

	query = (struct tlv_unassoc_sta_link_metrics_query *)
		tv[UNASSOC_STA_LINK_METRICS_QUERY][0]->data;

	for (i = 0; i < query->num_channel; i++) {
		int j;

		for (j = 0; j < query->ch[i].num_sta; j++) {
			uint8_t sta[6];

			memcpy(sta, query->ch[i].sta[j].macaddr, 6);
			ap = agent_get_ap_with_sta(a, sta);
			if (!ap)
				continue;

			/* Add Error Code TLV with reason 0x01 and MAC for each associated STA */
			sta_resp[err_tlv_cnt].response = ERR_REASON_STA_ASSOCIATED;
			memcpy(sta_resp[err_tlv_cnt].sta_mac, sta, 6);
			err_tlv_cnt++;

			if (err_tlv_cnt >= MAX_UNASSOC_STAMACS)
				break;
		}
		if (err_tlv_cnt >= MAX_UNASSOC_STAMACS)
			break;
	}


	/* If a Multi-AP Agent receives an Unasociated STA Link Metrics Query message
	 * then it shall respond within one second with a 1905 Ack message.
	 */
	send_1905_acknowledge(agent, cmdu->origin,
		 cmdu_get_mid(cmdu), sta_resp, err_tlv_cnt);

	if (err_tlv_cnt)
		dbg("%s: One or more STAs is associated with a BSS operated "\
		    "by the Multi-AP Agent!\n", __func__);

	return agent_process_unassoc_sta_lm_query_tlv(a, query, cmdu);
}

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

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

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

/* Add the rcpi based check according to section
 * 11.3
 */
bool agent_rcpi_steer(void)
{
	/**
	 * TODO: Implement proper logic to trigger steer
	 */
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);
	return false;
}

static int agent_set_sta_resp(struct agent *a, struct netif_ap *ap,
		bool steering_mandate, uint8_t num_sta, uint8_t *sta_list,
		struct sta_error_response  *sta_resp, uint32_t *cnt)
{
	uint32_t res = 0;
	bool found = false;
	uint32_t count = 0;
	int i = 0;

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

		list_for_each_entry(s, &ap->stalist, list) {
			agnt_dbg(LOG_STA, "%s: sta: " MACFMT "\n",
			    __func__, MAC2STR(s->macaddr));
			res = memcmp(s->macaddr, &sta_list[i * 6], 6);
			if (!res) {
				found = true;
				/**
				 * Add the associated station to the agent opportunity list only
				 * if opportunity steering was requested.
				 */
				/* TODO: move smwhr else */
				if (steering_mandate == false) {
					memcpy(a->sta_steer_list[a->sta_steerlist_count].sta_mac,
							&sta_list[i * 6], 6);
					a->sta_steerlist_count++;
				}
				break;
			}
		}
		if (!found) {
			agnt_dbg(LOG_STA, "%s: sta: " MACFMT " not found\n",
			    __func__, MAC2STR(s->macaddr));
			memcpy(sta_resp[count].sta_mac, &sta_list[i * 6], 6);
			sta_resp[count].response = ERR_REASON_STA_NOT_ASSOCIATED;
			count++;
		}
	}
	*cnt += count;

	if (num_sta == count)
		/* None of the stations from the list found on ap stalist */
		return -1;

	return 0;
}

static int agent_add_sta_bcn_req(struct sta *s, struct netif_ap *ap,
	uint8_t opclass, uint8_t channel, uint8_t *bssid,
	uint8_t reporting_detail, uint8_t ssidlen, char *ssid,
	uint8_t num_element, uint8_t *element)
{
	struct sta_bcn_req *breq;

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

	if (s->sta_bcn_req_nr >= 16)
		/* Max number of requests stashed */
		return -1;

	/* LIFO */
	breq = &s->bcn_req_queue[s->sta_bcn_req_nr];
	if (!breq)
		return -1;

	memset(breq, 0, sizeof(struct sta_bcn_req));

	breq->ap = ap;
	breq->opclass = opclass;
	breq->channel = channel;
	memcpy(breq->bssid, bssid, 6);
	breq->reporting_detail = reporting_detail;

	if (ssidlen > sizeof(breq->ssid) - 1)
		return -1;

	breq->ssid_len = ssidlen;
	memcpy(breq->ssid, ssid, ssidlen);

	breq->num_element = num_element;
	memcpy(breq->element, element, num_element);

	s->sta_bcn_req_nr++;

	return 0;
}

#define ERR_STA_NOT_ASSOCIATED -2
static int agent_request_beacon_metrics(struct agent *a,
	uint8_t *sta_addr, uint8_t opclass,
	uint8_t channel, uint8_t *bssid, uint8_t reporting_detail,
	uint8_t ssid_len, char *ssid, uint8_t num_report,
	uint8_t *report, uint8_t num_element, uint8_t *element)
{
	trace("agent: %s: --->\n", __func__);

	struct sta *s;
	struct netif_ap *ap;
	int num_channel_requested = 0;
	int remaining = 0;
	int i;

	dbg("%s: opclass = %u, channel = %u, num_report = %u\n",
	    __func__, opclass, channel, num_report);

	s = agent_get_sta(a, sta_addr);
	if (WARN_ON(!s))
		return ERR_STA_NOT_ASSOCIATED;

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

	/* TODO: return if client isn't RRM capable */

	switch (channel) {
	case 0:
		/* Iterative measurements for all channels in the operating class */
		struct wifi_radio_element *radio;

		radio = agent_get_radio_by_name(a, ap->radio_name);
		if (!radio) {
			dbg("%s: Did not find radio\n", __func__);
			return -1;
		}

		for (i = 0; i < radio->opclass.entry_num; i++) {
			struct wifi_radio_opclass_entry *entry;
			int j;

			entry = &radio->opclass.entry[i];
			if (entry->id != opclass)
				continue;

			for (j = 0; j < entry->channel_num; j++) {
				int ret = 0;

				dbg("%s: adding bcn req for opclass = %u, channel = %u\n",
				    __func__, opclass, entry->channel[j].channel);
				ret = agent_add_sta_bcn_req(s, ap,
						opclass, entry->channel[j].channel,
						bssid, reporting_detail,
						ssid_len, ssid,
						num_element, element);
				if (ret) {
					dbg("%s: Failed to add bcn request for channel %d!\n",
					    __func__, entry->channel[j].channel);
					continue;
				}
				num_channel_requested++;
			}
		}
		break;
	case 255:
		/* Iterative measurements for all channels listed in AP Channel Report */
		uint8_t *pos;

		if (!report) {
			dbg("%s: Missing AP Channel Report!\n", __func__);
			return -1;
		}

		pos = report;
		for (i = 0; i < num_report; i++) {
			struct ap_channel_report *rep = (struct ap_channel_report *)pos;
			int j, ret = 0;

			/* rep->len equals number_of_channels + 1 (for opclass) */
			for (j = 0; j < (rep->len - 1); j++) { /* for each channel */
				dbg("%s: adding bcn req for opclass = %u, channel = %u\n",
				    __func__, rep->opclass, rep->channel[j]);
				ret = agent_add_sta_bcn_req(s, ap,
						rep->opclass, rep->channel[j],
						bssid, reporting_detail,
						ssid_len, ssid,
						num_element, element);
				if (ret) {
					dbg("%s: Failed to add bcn request for channel %d!\n",
					    __func__, rep->channel[j]);
					continue;
				}
				num_channel_requested++;
			}

			pos += 1 + rep->len;
		}
		break;
	default:
		/* Single measurement for the channel number provided */
		int ret = 0;

		/* Single opclass/channel pair, add directly */
		dbg("%s: adding bcn req for opclass = %u, channel = %u\n",
		    __func__, opclass, channel);
		ret = agent_add_sta_bcn_req(s, ap, opclass, channel,
					    bssid, reporting_detail,
					    ssid_len, ssid, num_element, element);
		if (ret) {
			dbg("%s: Failed to add beacon request for channel %d!\n",
			    __func__, channel);
			return -1;
		}
		num_channel_requested++;
	}

	if (!num_channel_requested) {
		dbg("%s: No channels requested!\n", __func__);
		return -1;
	}

	/* Trigger sending beacon metrics request */
	remaining = timer_remaining_ms(&s->sta_bcn_req_timer);
	if (remaining == -1 && s->sta_bcn_req_nr > 0)
		timer_set(&s->sta_bcn_req_timer, 0 * 1000);

	return 0;
}

#define BTM_VALIDITY_TM_MSEC 10 * 1000
static int agent_request_btm(void *agent, uint8_t *client_sta, struct netif_ap *ap,
			struct nbr *pref, uint8_t steer_req_mode, uint32_t disassoc_tmo)
{
	struct wifi_btmreq req = {};
	struct agent *a = (struct agent *) agent;

	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);
	if (!client_sta || !pref)
		return -1;

	/* Preferred Candidate List Included */
	req.mode |= WIFI_BTMREQ_PREF_INC;

	/* Inform client of imminent disassociation */
	if (steer_req_mode & STEER_REQUEST_BTM_DISASSOC_IMM) {
		req.mode |= WIFI_BTMREQ_DISASSOC_IMM;
		req.disassoc_tmo = disassoc_tmo / WIFI_BEACON_PERIOD_MS;
	}

	/* Inform client of Abridged field */
	if (steer_req_mode & STEER_REQUEST_BTM_ABRIDGED)
		req.mode |= WIFI_BTMREQ_ABRIDGED;

	req.validity_int = BTM_VALIDITY_TM_MSEC / WIFI_BEACON_PERIOD_MS;
	req.dialog_token = agent_steer_next_dialog_token(a);
	return wifi_req_btm(ap->ifname, client_sta, 1, pref, &req);
}

/*TODO: 11.4 Multi-AP Agent determination of target BSS
 * Find best bssid based on link metrics, RCPI threshold
 * & channel utilization.
 */
int agent_find_best_bssid_for_sta(struct agent *a, uint8_t *sta, uint8_t *src_bssid,
		struct pref_neighbor *best_target)
{
	struct wifi_radio_element *re = NULL;
	int ret = 0;

	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);
	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			agnt_trace(LOG_STEER, "src_bssid = " MACFMT " pbssid = " MACFMT "\n",
				MAC2STR(src_bssid), MAC2STR(ap->bssid));

			/* FIXME: using first non-self ap bssid */
			ret = memcmp(src_bssid, ap->bssid, 6);
			if (ret != 0) {
				memcpy(best_target->bssid, ap->bssid, 6);
				return 0;
			}
		}
	}
	memcpy(best_target->bssid, src_bssid, 6);
	return 0;
}

int send_1905_acknowledge(void *agent, uint8_t *origin, uint16_t mid,
			  struct sta_error_response *sta_resp,
			  uint32_t sta_count)
{
	struct cmdu_buff *resp;
	struct agent *a = (struct agent *) agent;
	int ret;

	trace("agent: %s: --->\n", __func__);
	resp = agent_gen_cmdu_1905_ack(a, origin, mid, sta_resp, sta_count);
	if (!resp)
		return -1;

	ret = CMDU_HOOK(agent, cmdu_get_type(resp), cmdu_get_mid(resp),
			resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(agent, resp);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);

	return 0;
}

static bool is_wildcard_bssid(uint8_t *bssid)
{
	uint8_t wildcard[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

	return !memcmp(bssid, wildcard, 6);
}

static int agent_sta_disallowed(struct agent *a,
		uint8_t *sta_mac, int type)
{
	struct agent_config *cfg;
	struct policy_cfg *pcfg;
	struct stax *ex_sta = NULL;
	struct list_head *head = NULL;
	int disallowed = 0;

	if (!a)
		return -1;

	cfg = &a->cfg;
	if (!cfg) {
		err("%s: missing configuration!\n",
		    __func__);
		return -1;
	}

	pcfg = cfg->pcfg;
	if (!pcfg) {
		err("%s: missing policy configuration!\n",
		    __func__);
		return -1;
	}

	if (type == STA_DISALLOWED_BTM)
		head = &pcfg->steer_btm_excludelist;
	else if (type == STA_DISALLOWED_LOCAL)
		head = &pcfg->steer_excludelist;
	else
		return -1;

	list_for_each_entry(ex_sta, head, list) {
		if (!memcmp(sta_mac, ex_sta->macaddr, 6)) {
			dbg("STA " MACFMT " excluded from steering\n",
			    MAC2STR(sta_mac));
			disallowed = 1;
			break;
		}
	}

	return disallowed;
}

static int agent_try_steer_sta(struct agent *a, struct netif_ap *ap,
		uint8_t *src_bssid, uint8_t *sta_mac, struct pref_neighbor *pref_target,
		struct sta_error_response *sta_resp, uint32_t count,
		uint8_t steer_req_mode, uint32_t disassoc_tmo)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	struct pref_neighbor target = {};
	int ret = 0;
	int i;

	agnt_dbg(LOG_STEER, "steered STA mac: " MACFMT "\n", MAC2STR(sta_mac));
	if (pref_target)
		agnt_dbg(LOG_STEER, "target bssid: " MACFMT "\n", MAC2STR(pref_target->bssid));
	else
		agnt_dbg(LOG_STEER, "target bssid unspecified!\n");

	/* Check if STA is allowed for steering */
	ret = agent_sta_disallowed(a, sta_mac, STA_DISALLOWED_LOCAL);
	if (ret == 1)
		agnt_dbg(LOG_STEER, "Error steering: STA disallowed (local)\n");

	if (ret)
		return -1;


	/* Check if STA is associated with the src bssid */
	agnt_dbg(LOG_STEER, "num of error code stas %d\n", count);
	for (i = 0; i < count; i++) {
		agnt_dbg(LOG_STEER, "sta error mac: " MACFMT "\n",
		    MAC2STR(sta_resp[i].sta_mac));
		if (!memcmp(sta_mac, sta_resp[i].sta_mac, 6))
			return -1;
	}

	if (pref_target && !is_wildcard_bssid(pref_target->bssid))
		/* Target specified directly in Steering Request TLV */
		memcpy(&target, pref_target, sizeof(struct pref_neighbor));
	else {
		/* Best target BSSID for this STA has to be found */
		ret = agent_find_best_bssid_for_sta(a, sta_mac, src_bssid, &target);
		if (ret)
			return -1;
	}

	/* Check if STA is disallowed from BTM steering */
	ret = agent_sta_disallowed(a, sta_mac, STA_DISALLOWED_BTM);
	if (!ret) {
		struct nbr pref = {};

		agent_pref_neighbor_to_nbr(&target, &pref);

		ret = agent_request_btm(a, sta_mac, ap, &pref, steer_req_mode, disassoc_tmo);
		if (!ret) {
			char ev_data[512] = {0};

			snprintf(ev_data, sizeof(ev_data),
					"{\"macaddr\":\""MACFMT"\""
					",\"src_bssid\":\""MACFMT"\""
					",\"dst_bssid\":\""MACFMT"\"}",
					MAC2STR(sta_mac),
					MAC2STR(src_bssid),
					MAC2STR(target.bssid));

			agent_notify_iface_event(a, ap->ifname, "request_btm", ev_data);
		}
	} else if (ret == 1) {
		/* BTM disallowed */
		agnt_dbg(LOG_STEER, "Error steering: STA disallowed (BTM)\n");
		/* TODO: opportunity: try assoc control */
		//if (req_mode == 0x00) {
		//}
	}

	return ret;
}

int agent_process_steer_request_tlv(void *agent,
		struct tlv_steer_request *p, struct cmdu_buff *cmdu, bool is_profile2)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	struct agent *a = (struct agent *) agent;
	int ret = 0, offset = 0;
	int i;
	struct netif_ap *ap;
	struct sta_error_response sta_resp[MAX_STA];
	uint32_t count = 0;
	uint8_t mode = 0x00;
	bool steering_mandate;
	uint16_t op_window;
	uint16_t disassoc_tmo;
	uint8_t bssid[6] = { 0 };
	uint8_t num_sta;
	uint8_t *stalist = NULL;
	uint8_t num_bss;
	struct pref_neighbor *pref_arr = NULL;
	uint8_t *data = (uint8_t *)p;

	memcpy(bssid, &data[offset], 6);
	offset += 6;
	mode = data[offset++];
	op_window = BUF_GET_BE16(data[offset]);
	offset += 2;
	disassoc_tmo = BUF_GET_BE16(data[offset]);
	offset +=2;

	steering_mandate = !!((mode & STEER_REQUEST_MODE) >> 7);

	agnt_dbg(LOG_STEER, "%s: mode: %x\n", __func__ , mode);
	agnt_dbg(LOG_STEER, "%s: steer_mode: %s\n", __func__, steering_mandate ? "mandate" : "opportunity");
	agnt_dbg(LOG_STEER, "%s: steer_opp_window: %d\n", __func__, op_window);
	agnt_dbg(LOG_STEER, "%s: btm_disassoc_timer: %d\n", __func__, disassoc_tmo);

	if (steering_mandate == false) {
		/* Here we start the steer opportunity timer */
		if (a->is_sta_steer_start == 1) {
			agnt_err(LOG_STEER, "%s: Steering opportunity timer already running\n", __func__);
			return -1;
		}

		/**
		 * Here we need to check the three conditions that needs to be
		 * satisfied according to section 11.2
		 */
		if (op_window == 0) {
			agnt_err(LOG_STEER, "%s: Steering opportunity timer value is zero\n", __func__);
			return -1;
		}

		a->is_sta_steer_start = 1;
		a->sta_steerlist_count = 0;
		timer_set(&a->sta_steer_req_timer, op_window * 1000);
	}

	/* TODO check the rcpi based steering rule section 11.3*/
	/* FIXME: not here */
	agent_rcpi_steer();

	/**
	 * The src bssid is with which the STA is associated so
	 * Here we need to check that the STA is associated with the
	 * src_bssid according to section 11.1 of the steer mandate
	 */
	num_sta = data[offset++];
	agnt_dbg(LOG_STEER, "%s: sta_list_cnt: %d\n", __func__ , num_sta);
	if (num_sta > 0) {
		stalist = calloc(num_sta, 6 * sizeof(uint8_t));
		if (!stalist) {
			agnt_err(LOG_STEER, "%s: -ENOMEM\n", __func__);
			return -1;
		}
	}

	for (i = 0; i < num_sta; i++) {
		memcpy(&stalist[i * 6], &data[offset], 6);
		offset += 6;
		agnt_dbg(LOG_STEER, "%s: sta_mac: " MACFMT "\n", __func__ , MAC2STR(&stalist[i * 6]));
	}

	num_bss = data[offset++];
	agnt_dbg(LOG_STEER, "%s: target_bssid_list_cnt: %d\n", __func__, num_bss);

	if (num_bss == 0 && steering_mandate == true) {
		agnt_err(LOG_STEER, "%s: Error steerig mandate: target BSSID not present\n",__func__);
		ret = -1;
		goto out;
	}

	if (num_bss > 0) {
		pref_arr = calloc(num_bss, sizeof(struct pref_neighbor));
		if (!pref_arr) {
			agnt_err(LOG_STEER, "%s: -ENOMEM\n", __func__);
			ret = -1;
			goto out;
		}
	}

	for (i = 0; i < num_bss; i++) {
		memcpy(pref_arr[i].bssid, &data[offset], 6);
		offset += 6;
		/* Target BSS Operating Class */
		pref_arr[i].reg = data[offset++];
		/* Target BSS Channel Number for channel on which
		 * the Target BSS is transmitting Beacon frames.
		 */
		pref_arr[i].channel = data[offset++];
		/* MBO Transition Reason Code (Profile-2 only) */
		if (is_profile2)
			pref_arr[i].reason = data[offset++];
	}

	/* Check src bssid present */
	ap = agent_get_ap(a, bssid);
	if (!ap) {
		agnt_err(LOG_STEER, "%s: Error BSSID " MACFMT " not present",
			 __func__, MAC2STR(bssid));
		for (i = 0; i < num_sta; i++) {
			memcpy(sta_resp[count].sta_mac, &stalist[i * 6], 6);
			sta_resp[count++].response = ERR_REASON_STA_NOT_ASSOCIATED;
			goto send_ack;
		}
	}

	if (num_sta == 0) {
		/**
		 * No STA provided, Steering request applies to all associated STAs
		 * in the BSS, per policy setting.
		 */
		if (num_bss == 1) {
			struct sta *s = NULL;
			int num_steered = 0;

			list_for_each_entry(s, &ap->stalist, list) {
				/* Call for transition of sta */
				ret = agent_try_steer_sta(a, ap, bssid,
						s->macaddr, &pref_arr[0],
						sta_resp, 0, mode, disassoc_tmo);
				if (ret)
					agnt_trace(LOG_STEER, "%s: Couldn't steer " MACFMT "\n",
					    __func__, MAC2STR(s->macaddr));
				else
					num_steered++;
			}
			ret = num_steered ? -1 : 0;
			goto send_ack;
		/* Zero or more than one BSS */
		} else {
			agnt_err(LOG_STEER, "%s: Error condition\n", __func__);
			ret = -1;
			goto out;
		}
	}


	/**
	 * At least one station
	 *
	 * See section 11.1:
	 *   If a STA specified in the Client Steering Request message is not associated
	 *   with the source BSSID specified in the same message (an error scenario),
	 *   for each of those unassociated STAs, the Multi-AP Agent shall include an Error Code TLV
	 *   with the reason code field set to 0x02 and the STA MAC address field included per
	 *   section 17.2.36 in the 1905 Ack message.
	 */
	ret = agent_set_sta_resp(a, ap, steering_mandate, num_sta, stalist, sta_resp, &count);
	if (ret == -1)
		/* None of the stations from the stalist found attached to ap */
		goto send_ack;

	/* Number of STAs and BSSIDs are equal, map STA to BSSID */
	if ((num_sta == num_bss) && (steering_mandate == true)) {
		for (i = 0; i < num_sta; i++) {
			/* Call for transition of sta */
			ret = agent_try_steer_sta(a, ap, bssid,
					&stalist[i * 6], &pref_arr[i],
					sta_resp, count, mode, disassoc_tmo);
		}
	}
	/* Multiple STAs and single BSSID, send all STAs to same BSSID */
	else if ((num_bss == 1) && (steering_mandate == true)) {
		for (i = 0; i < num_sta; i++) {
			/* Call for transition of sta */
			ret = agent_try_steer_sta(a, ap, bssid,
					&stalist[i * 6], &pref_arr[0],
					sta_resp, count, mode, disassoc_tmo);
		}
	}
	/* No BSSID specified for the STAs */
	else if ((num_bss == 0) && (steering_mandate == false)) {
		agnt_dbg(LOG_STEER, "|%s| target BSSID not present\n", __func__);
		for (i = 0; i < num_sta; i++) {
			/* Call for transition of sta */
			ret = agent_try_steer_sta(a, ap, bssid,
					&stalist[i * 6], NULL,
					sta_resp, count, mode, disassoc_tmo);
		}
	/* Unspecified error condition */
	} else {
		agnt_err(LOG_STEER, "%s: Error condition\n", __func__);
		ret = -1;
		goto out;
	}

send_ack:
	send_1905_acknowledge(agent, cmdu->origin, cmdu_get_mid(cmdu), sta_resp, count);

out:
	if (stalist)
		free(stalist);

	if (pref_arr)
		free(pref_arr);

	return ret;
}

int handle_sta_steer_request(void *agent, struct cmdu_buff *cmdu,
			     struct node *n)
{
	agnt_trace(LOG_STEER, "%s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	int ret = 0;
	struct tlv *tv[CLIENT_STEERING_REQUEST_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

	UNUSED(a);

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

	if (tv[CLIENT_STEERING_REQUEST_STEERING_REQUEST_IDX][0]) {
		struct tlv_steer_request *p = (struct tlv_steer_request *)
			tv[CLIENT_STEERING_REQUEST_STEERING_REQUEST_IDX][0]->data;

		ret = agent_process_steer_request_tlv(agent, p, cmdu, false);
	}

//#ifdef PROFILE2
#if (EASYMESH_VERSION >= 2)
	if (tv[CLIENT_STEERING_REQUEST_PROFILE2_STEERING_REQ_IDX][0]) {
		struct tlv_profile2_steer_request *p = (struct tlv_profile2_steer_request *)
			tv[CLIENT_STEERING_REQUEST_PROFILE2_STEERING_REQ_IDX][0]->data;

		ret = agent_process_steer_request_tlv(agent, (struct tlv_steer_request *)p, cmdu, true);
	}
#endif
//#endif

	return ret;
}

static int agent_process_beacon_metrics_query_tlv(struct agent *a,
		struct tlv_beacon_metrics_query *query,
		struct cmdu_buff *cmdu)
{
	struct ssid_query *ssidq;
	char ssid[33] = {0};
	uint8_t *data;
	uint8_t num_report = 0;
	uint8_t *reports;
	uint8_t num_element = 0;
	uint8_t *elements = NULL;
	int i, offset = 0;
	int ret = 0;

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

	/* ssid */
	ssidq = (struct ssid_query *)&(query->ssid);
	if (ssidq->ssidlen > 32) {
		dbg("%s: Unexpected ssidlen:%d in bcn metrics query\n",
		    __func__, ssidq->ssidlen);
		return -1;
	}
	memcpy(ssid, ssidq->ssid, ssidq->ssidlen);

	/* Num_Report */
	data = (uint8_t *)query;
	offset += offsetof(struct tlv_beacon_metrics_query, ssid) + 1 + ssidq->ssidlen;
	num_report = data[offset++];

	if (query->channel && query->channel != 255 && num_report > 0) {
		dbg("%s: Both channel and num_report set!\n",
		    __func__);
		return -1;
	}

	if (query->channel == 255 && !num_report) {
		dbg("%s: Neither channel nor num_report set!\n",
		    __func__);
		return -1;
	}

	if (!query->opclass && query->channel != 255) {
		dbg("%s: Channel set but no opclass provided!\n", __func__);
		return -1;
	}

	/* Report */
	if (num_report > 0)
		reports = &data[offset];

	for (i = 0; i < num_report; i++) {
		struct ap_channel_report *rep =
				(struct ap_channel_report *)&data[offset];
		offset += 1 + rep->len;
	}

	/* num_element */
	num_element = data[offset++];

	if (num_element > 0) {
		/* element */
		elements = &data[offset];
	}

	dbg("%s: num_report = %d, num_element = %d\n",
	    __func__, num_report, num_element);

	if (is_wildcard_bssid(query->bssid))
		trace("%s: using wildcard bssid\n", __func__);

	/* Send an 802.11 Beacon request to STA */
	ret = agent_request_beacon_metrics(a, query->sta_macaddr,
			query->opclass, query->channel, query->bssid,
			query->reporting_detail, ssidq->ssidlen, ssid,
			num_report, reports, num_element, elements);

	return ret;
}

int handle_beacon_metrics_query(void *agent, struct cmdu_buff *rx_cmdu,
				struct node *n)
{
	struct agent *a = (struct agent *) agent;
	struct tlv *tv[BEACON_METRICS_QUERY_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_beacon_metrics_query *query;
	struct sta_error_response sta_resp[1] = {0};
	int sta_count = 0;
	int ret = 0;

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

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

	query = (struct tlv_beacon_metrics_query *)tv[BEACON_METRICS_QUERY][0]->data;

	ret = agent_process_beacon_metrics_query_tlv(a, query, rx_cmdu);
	if (ret == ERR_STA_NOT_ASSOCIATED) {
		/*
		 * If the specified STA in the Beacon Metrics Query message
		 * is not associated with any of the BSS operated by the
		 * Multi-AP Agent (an error scenario), the Multi-AP Agent
		 * shall include an Error Code TLV with the reason code field
		 * set to 0x02 and the STA MAC address field included per
		 * section 17.2.36 in the 1905 Ack message.
		 */
		dbg("Specified STA not associated with a BSS operated"\
				 " by the Multi-AP Agent!\n");

		sta_resp[0].response = ERR_REASON_STA_NOT_ASSOCIATED;
		memcpy(sta_resp[0].sta_mac, query->sta_macaddr, 6);

		sta_count = 1;
		ret = -1;
	}

	/* If a Multi-AP Agent receives a Beacon Metrics Query message,
	 * then it shall respond within 1s with a 1905 Ack message.
	 */
	send_1905_acknowledge(agent, rx_cmdu->origin, cmdu_get_mid(rx_cmdu),
			      sta_resp, sta_count);

	return ret;
}

int handle_sta_assoc_control_request(void *agent, struct cmdu_buff *cmdu,
				     struct node *n)
{
	agnt_trace(LOG_STEER, "%s: --->\n", __func__);

	int ret = -1, idx;
	struct tlv *tv[CLIENT_ASSOC_CONTROL_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

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

	idx = 0;
	while (idx < TLV_MAXNUM && tv[CLIENT_ASSOC_CONTROL_REQUEST_IDX][idx]) {
		uint8_t *p = (uint8_t *)tv[CLIENT_ASSOC_CONTROL_REQUEST_IDX][idx++]->data;

		ret = assoc_ctrl_process_request(agent, p, cmdu);
	}

	return ret;
}

int agent_hld_event(struct agent *a, 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, "\"}");

	agent_notify_event(a, "map.agent.higher_layer_data", str);

	free(str);

	return 0;
}

int handle_hld_message(void *agent, struct cmdu_buff *rx_cmdu, struct node *n)
{
	struct agent *a = (struct agent *) agent;
	struct tlv *tv[HIGHER_LAYER_DATA_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	uint8_t *usrdata;
	struct tlv *t;
	uint8_t proto;
	uint8_t *data;
#ifdef AGENT_SYNC_DYNAMIC_CNTLR_CONFIG
	bool cntlr_sync = (a->cfg.dyn_cntlr_sync & is_local_cntlr_available());
#endif
	int data_len;
	int tlen = 0;
	int ret;
	int idx;


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

	if (!tv[HIGHER_LAYER_DATA_HLD_IDX][0]) {
		err("%s: higher layer data TLV not found\n", __func__);
		return -1;
	}

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

	dbg("%s HLD received proto = %u (0x%01x)\n", __func__, proto, proto);
	usrdata = calloc(data_len, sizeof(uint8_t));
	if (!usrdata) {
		err("%s: calloc() failed\n", __func__);
		return -1;
	}

	memcpy(usrdata, data, data_len);
	idx++;
	while (idx < TLV_MAXNUM && tv[HIGHER_LAYER_DATA_HLD_IDX][idx]) {
		uint8_t *newdata = NULL;

		t = tv[HIGHER_LAYER_DATA_HLD_IDX][idx];
		data_len = tlv_length(t);

		newdata = realloc(usrdata, tlen + data_len);
		if (!newdata) {
			free(usrdata);
			err("%s: realloc() failed\n", __func__);
			return -1;
		}

		memcpy(newdata + tlen, t->data, data_len);
		tlen += data_len;
		idx++;
		usrdata = newdata;
		dbg("\nRealloc'd userdata tlen = %d\n", tlen);
	}

	ret = agent_hld_event(a, proto, usrdata, tlen);
	if (ret == 0)
		send_1905_acknowledge(a, rx_cmdu->origin, cmdu_get_mid(rx_cmdu), NULL, 0);

#ifdef AGENT_SYNC_DYNAMIC_CNTLR_CONFIG
	if (proto == 0xab && cntlr_sync) {
		struct cmdu_buff *cmdu;
		uint8_t res_proto = 0xac;
		uint16_t sync_config_reqsize = 0;
		uint8_t *sync_config_req;
		void *key;

		dbg("*** handle dyn-controller-config-sync-start ***\n");
		ret = build_sync_config_request(a->almac, &sync_config_req,
						&sync_config_reqsize, &key);
		if (ret) {
			err("Failed to build sync-config-req frame!\n");
			goto error;
		}

		/* free old data if any */
		agent_free_cntlr_sync(a);

		a->sync_config_reqsize = sync_config_reqsize;
		a->sync_config_req = sync_config_req;
		a->privkey = key;

		cmdu = agent_gen_higher_layer_data(a, a->cntlr_almac, res_proto,
						   sync_config_req, sync_config_reqsize);
		if (!cmdu) {
			ret = -1;
			goto error;
		}

		agent_send_cmdu(a, cmdu);
		cmdu_free(cmdu);
	} else if (proto == 0xac && cntlr_sync) {
		struct sync_config out = {0};
		char enabled[2] = {0};

		dbg("*** Process dyn-controller-config-sync response ***\n");
		/*
		if (a->sync_config_resp)
			free(a->sync_config_resp);

		a->sync_config_resp = 0;
		a->sync_config_resp = calloc(data_len, sizeof(uint8_t));
		if (a->sync_config_resp) {
			memcpy(a->sync_config_resp, data, data_len);
			a->sync_config_respsize = data_len;
		}
		*/

		ret = process_sync_config_response(a->sync_config_req,
						   a->sync_config_reqsize,
						   a->privkey,
						   usrdata, tlen,
						   &out);
		if (ret) {
			err("Error processing dyn-controller-config-sync response\n");
			goto error;
		}

		agent_get_controller_enabled(a, enabled);

		ret = writeto_configfile("/etc/config/mapcontroller", out.data, out.len);
		if (ret)
			fprintf(stderr, "failed to write file\n");
		set_value_by_string("mapcontroller", "controller", "enabled",
				enabled, UCI_TYPE_STRING);

		free(out.data);
	}

error:
#endif /* AGENT_SYNC_DYNAMIC_CNTLR_CONFIG */
	free(usrdata);

	return ret;
}

int handle_backhaul_sta_steer_request(void *agent, struct cmdu_buff *cmdu,
				      struct node *n)
{
	agnt_trace(LOG_BSTEER, "%s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	struct tlv_backhaul_steer_request *steer_req;
	struct tlv *tv[BACKHAUL_STEERING_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct netif_bk *bk;
	int timeout = BSTA_STEER_TIMEOUT;
	int ret;

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

	/* send the 1905 ack message to controller */
	send_1905_acknowledge(agent, cmdu->origin, cmdu_get_mid(cmdu), NULL, 0);

	steer_req =
		(struct tlv_backhaul_steer_request *) tv[BACKHAUL_STEERING_REQUEST_IDX][0]->data;
	if (!steer_req)
		return -1;

	bk = agent_get_bsta(a, steer_req->macaddr);
	if (!bk) {
		struct netif_bk fail_bk;

		memcpy(fail_bk.bsta_steer.target_bssid, steer_req->target_bssid, 6);
		memcpy(fail_bk.macaddr, steer_req->macaddr, 6);
		fail_bk.bsta_steer.mid = cmdu_get_mid(cmdu);
		fail_bk.bsta_steer.reason = ERR_REASON_BH_STEERING_NOT_FOUND;
		send_backhaul_sta_steer_response(a, &fail_bk, fail_bk.ifname);
		return -1;
	}

	bk->bsta_steer.expired = false;
	bk->bsta_steer.mid = cmdu_get_mid(cmdu);
	bk->bsta_steer.reason = 0x00;
	wifi_get_iface_bssid(bk->ifname, bk->bsta_steer.prev_bssid);
	memcpy(bk->bsta_steer.target_bssid, steer_req->target_bssid, 6);
	bk->bsta_steer.trigger = BK_STEER_MANDATE;

	ret = agent_bsta_steer(a, bk->ifname, bk->bsta_steer.target_bssid, false);
	if (ret) {
		timeout = 0;
		agnt_dbg(LOG_BSTEER, "%s: failed to exec fmt\n", __func__);
	} else {
		config_enable_bsta(bk->cfg); /* enable bsta as candidate */
	}

	agent_get_backhaul_ifname(a, bk->bsta_steer.ul_ifname);
	timer_set(&bk->steer_timeout, timeout * 1000);
	return 0;
}

int parse_channel_scan_req(struct agent *a, struct cmdu_buff *req_cmdu,
			struct wifi_scan_request *req, uint8_t map_profile)
{
	struct tlv *tv[CHAN_SCAN_REQ_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	uint8_t *t = NULL;
	int i, j, k;
	int offset = 0;
	uint8_t mode;
	uint16_t mid;
	uint8_t num_radio;

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

	t = (uint8_t *)tv[CHAN_SCAN_REQ_CHANNEL_SCAN_REQ_IDX][0]->data;

	/* store mid of the request */
	mid = cmdu_get_mid(req_cmdu);

	mode = t[offset++];

	req->mode = mode;

	num_radio = t[offset++];
	if (WARN_ON(num_radio > SCAN_MAX_RADIO))
		return -1;
	req->num_radio = num_radio;

	for (i = 0; i < num_radio; i++) {
		uint8_t radio_id[6];
		uint8_t num_opclass;

		/* radio id */
		memcpy(&radio_id, &t[offset], 6);
		memcpy(&req->radio[i].radio, &radio_id, 6);
		offset += 6;

		req->radio[i].mid = mid;

		/* num opclass */
		num_opclass = t[offset++];
		if (WARN_ON(num_opclass > SCAN_MAX_OPCLASS))
			return -1;

		req->radio[i].num_opclass = num_opclass;

		for (j = 0; j < num_opclass; j++) {
			uint8_t num_channel;

			/* class id */
			req->radio[i].opclass[j].classid = t[offset++];

			/* num channel */
			num_channel = t[offset++];
			if (WARN_ON(num_channel > SCAN_MAX_CHANNEL))
				return -1;

			req->radio[i].opclass[j].num_channel = num_channel;

			for (k = 0; k < num_channel; k++)
				req->radio[i].opclass[j].channel[k] = t[offset++];
		}
	}

	return 0;
}

int handle_channel_scan_request(void *agent, struct cmdu_buff *rx_cmdu,
				struct node *n)
{
	agnt_trace(LOG_SCAN, "%s: --->\n", __func__);

	struct agent *a = (struct agent *) agent;
	struct wifi_scan_request ch_scan_req = { 0 };
	int ret;

	/* If a Multi-AP Agent receives a Channel Scan Request message,
	 * it shall respond within one second with a 1905 Ack message.
	 */
	send_1905_acknowledge(a, rx_cmdu->origin, cmdu_get_mid(rx_cmdu), NULL, 0);

	ret = parse_channel_scan_req(a, rx_cmdu, &ch_scan_req, n->map_profile);
	if (ret)
		return -1;

	return scan_process_request(a, &ch_scan_req);
}

int wifi_radio_update_opclass_preferences(struct agent *a, struct wifi_radio_element *re, bool send_report)
{
	struct wifi_radio_opclass opclass = {};
	int send_pref_report = 0;
	struct cmdu_buff *cmdu;
	uint16_t mid = 0;
	int ret;

	if (WARN_ON(wifi_opclass_preferences(re->name, &opclass))) {
		return -1;
	}

	if (!opclass.entry_num) {
		return 0;
	}

	if (!re->acs_enabled)
		wifi_opclass_lower_preferences(&opclass,
					       re->current_opclass,
					       re->current_channel, 1);

	if (wifi_opclass_changed(&re->opclass, &opclass))
		send_pref_report = 1;

	/* Setup new results */
	agnt_trace(LOG_CHANNEL, "[%s] update opclass preferences %d\n", re->name, opclass.entry_num);
	memcpy(&re->opclass, &opclass, sizeof(opclass));

	/* Update supported opclass/channels by excluding disallowed entries */
	wifi_opclass_mark_disallowed(&re->opclass, &re->req_opclass);

	if (!send_report)
		return 0;

	agnt_trace(LOG_CHANNEL, "[%s] send channel_preference_report here %d\n", re->name, send_pref_report);
	if (!send_pref_report)
		return 0;

	cmdu = agent_gen_channel_preference_report(a, mid, a->cntlr_almac, re->macaddr);
	if (WARN_ON(!cmdu))
		return 0;

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		agnt_warn(LOG_CHANNEL, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);

	return 0;
}

void update_neighbor_params(struct agent *a, uint8_t *bssid,
		uint8_t classid, uint8_t channel, const struct timespec *tsp)
{
	dbg("%s: updating reg for neighbor " MACFMT "\n",
	    __func__, MAC2STR(bssid));
	struct wifi_radio_element *re = NULL;

	if (!channel || !classid)
		return;

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

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

			list_for_each_entry(n, &ap->nbrlist, list) {
				if (!memcmp(n->nbr.bssid, bssid, 6)) {
					n->nbr.reg = classid;
					n->nbr.channel = channel;
					/* refresh last seen */
					if (!tsp)
						timestamp_update(&n->tsp);
					else
						n->tsp = *tsp;

					n->flags &= ~NBR_FLAG_DRV_UPDATED;
					/* Synchronize nbr list in driver */
					reschedule_nbrlist_update(ap);
				}
			}
		}
	}
}

#ifdef OPER_CHAN_CHANGE_RELAY_MCAST
static void update_neighbor_params_for_radio(struct agent *a, uint8_t *radio_mac,
		uint8_t classid, uint8_t channel, const struct timespec *tsp)
{
	agnt_dbg(LOG_CHANNEL, "%s: updating reg/ch for neighbors on radio " MACFMT "\n",
	    __func__, MAC2STR(radio_mac));
	struct wifi_radio_element *re;
	struct netif_ap *ap = NULL;

	if (!channel || !classid)
		return;

	re = agent_get_radio(a, radio_mac);
	if (!re)
		return;

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

		list_for_each_entry(n, &ap->nbrlist, list) {
			n->nbr.reg = classid;
			n->nbr.channel = channel;
			/* refresh last seen */
			if (!tsp)
				timestamp_update(&n->tsp);
			else
				n->tsp = *tsp;

			n->flags &= ~NBR_FLAG_DRV_UPDATED;
			/* Synchronize nbr list in driver */
			reschedule_nbrlist_update(ap);
		}
	}
}
#endif

#ifdef OPER_CHAN_CHANGE_RELAY_MCAST
int handle_oper_channel_report(void *agent, struct cmdu_buff *cmdu, struct node *n)
{
	agnt_trace(LOG_CHANNEL, "%s: --->\n", __func__);

	struct agent *a = (struct agent *)agent;
	struct tlv *tv[OPER_CHANNEL_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int idx = 0;

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

	while (idx < TLV_MAXNUM &&
			tv[OPER_CHANNEL_REPORT_OPERATING_CHANNEL_IDX][idx]) {
		int offset = 0;
		uint8_t radio_mac[6] = {0};
		uint8_t *p =
			(uint8_t *)tv[OPER_CHANNEL_REPORT_OPERATING_CHANNEL_IDX][idx++]->data;
		uint8_t channel = 0, opclass = 0;
		int num_opclass;
		int i;

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

		num_opclass = p[offset++];

		for (i = 0; i < num_opclass; i++) {
			opclass = p[offset++];
			channel = p[offset++];
		}

		/* TODO handle 80+80 in the future */

		update_neighbor_params_for_radio(a, radio_mac, opclass, channel, NULL);

		offset++; /* txpower */
	}

	return 0;
}
#endif

int handle_cac_request(void *agent, struct cmdu_buff *cmdu, struct node *n)
{
	struct agent *a = agent;
	struct tlv *tv[CAC_REQUEST_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_cac_request *cac_request;
	struct wifi_radio_element *re;
	int ret = -1;
	int i;

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

	if (WARN_ON(!tv[CAC_REQUEST_CAC_REQ_IDX][0]))
		return ret;

	/* Cast to tlv */
	cac_request = (struct tlv_cac_request *) tv[CAC_REQUEST_CAC_REQ_IDX][0]->data;

	for (i = 0; i < cac_request->num_radio; i++) {
		enum wifi_cac_method method;
		int ctrl_channel;
		enum wifi_bw bw;
		uint32_t cac_time;
		int cac_method;
		int opclass;
		int channel;

		re = agent_get_radio(a, cac_request->radio[i].radio);
		if (WARN_ON(!re))
			continue;

		channel = cac_request->radio[i].channel;
		bw = get_op_class_wifi_bw(cac_request->radio[i].opclass);
		opclass = cac_request->radio[i].opclass;
		cac_method = (cac_request->radio[i].mode & CAC_REQUEST_METHOD) >> 5;
		switch (cac_method) {
		case 0:
			method = WIFI_CAC_CONTINUOUS;
			break;
		case 1:
			method = WIFI_CAC_DEDICATED;
			break;
		case 2:
			method = WIFI_CAC_MIMO_REDUCED;
			break;
		default:
			method = WIFI_CAC_MIMO_REDUCED;
			break;
		}

		/* Check if CAC ongoing */
		if (re->cac_request.channel &&
		    (re->cac_request.channel != channel ||
		     re->cac_request.opclass != opclass)) {

			/* Controller should first stop ongoing request */
			re->cac_request.report_failed = true;
			re->cac_request.report_failed_channel = channel;
			re->cac_request.report_failed_opclass = opclass;
			re->cac_request.report_failed_status = CAC_COMP_REPORT_STATUS_TOO_BUSY;
			timer_set(&re->preference_report_timer, 0);
			continue;
		}

		re->cac_request.channel = channel;
		re->cac_request.bw = bw;
		re->cac_request.opclass = opclass;
		re->cac_request.method = method;
		timestamp_update(&re->cac_request.time);

		/* Lower layer expect control channel */
		switch (opclass) {
		case 128:
		case 130:
			ctrl_channel = channel - 6;
			break;
		case 129:
			ctrl_channel = channel - 14;
			break;
		default:
			ctrl_channel = channel;
			break;
		}

		re->cac_request.ctrl_channel = ctrl_channel;

		/* Check if CAC required */
		if (!wifi_opclass_cac_required(&re->opclass, ctrl_channel, get_op_class_bw(opclass), &cac_time)) {
			re->cac_request.report_failed = true;
			re->cac_request.report_failed_channel = channel;
			re->cac_request.report_failed_opclass = opclass;
			re->cac_request.report_failed_status = CAC_COMP_REPORT_STATUS_SUCCESSFUL;
			re->cac_request.channel = 0;
			re->cac_request.opclass = 0;
			timer_set(&re->preference_report_timer, 0);
			continue;
		}

		/* Finally run CAC */
		ret = wifi_start_cac(re->name, ctrl_channel, bw, method);
		if (ret) {
			re->cac_request.report_failed = true;
			re->cac_request.report_failed_channel = channel;
			re->cac_request.report_failed_opclass = opclass;
			re->cac_request.report_failed_status = CAC_COMP_REPORT_STATUS_OTHER;
			re->cac_request.channel = 0;
			re->cac_request.opclass = 0;
			timer_set(&re->preference_report_timer, 0);
			continue;
		}

		/* Check cac time */
		cac_time += 10;
		timer_set(&re->preference_report_timer, cac_time * 1000);
	}

	return ret;
}

int handle_cac_stop(void *agent, struct cmdu_buff *cmdu, struct node *n)
{
	struct agent *a = agent;
	struct tlv *tv[CAC_TERMINATION_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_cac_termination *cac_term;
	struct wifi_radio_element *re;
	int ret = -1;
	int i;

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

	if (WARN_ON(!tv[CAC_TERMINATION_CAC_TERMINATION_IDX][0]))
		return ret;

	/* Cast to tlv */
	cac_term =
		(struct tlv_cac_termination *) tv[CAC_TERMINATION_CAC_TERMINATION_IDX][0]->data;

	for (i = 0; i < cac_term->num_radio; i++) {
		re = agent_get_radio(a, cac_term->radio[i].radio);
		if (WARN_ON(!re))
			continue;

		ret = wifi_stop_cac(re->name, 0, 0);
		if (!ret) {
			/* We are done with this request */
			memset(&re->cac_request, 0, sizeof(re->cac_request));
			timer_del(&re->preference_report_timer);
		}
	}

	if (WARN_ON(ret))
		ret = -1;

	return ret;
}

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

int prepare_tunneled_message(void *agent, const char *ifname,
		uint8_t protocol, const char *framestr)
{
	trace("%s: --->\n", __func__);
	struct agent *a = (struct agent *)agent;
	struct cmdu_buff *cmdu;
	struct netif_ap *ap;
	uint8_t *frame;
	uint8_t sta_mac[6] = { 0 };
	uint8_t bss_mac[6] = { 0 };
	int len;
	int index;
	int ret;

	if (!framestr)
		return -1;

	/* check protocol type;
	 * 0x00: association req
	 * 0x01: re-association req
	 * 0x02: BTM query
	 * 0x03: WNM req
	 * 0x04: ANQP req
	 */
	if (protocol > 0x04)
		return -1;

	len = strlen(framestr);
	len = len / 2;
	frame = calloc(len, sizeof(uint8_t));
	if (!frame)
		return -1;

	if (len < (2 + 2 + 6 + 6 + 6))
		goto error;

	if (!strtob((char *)framestr, len, frame))
		goto error;

	index = 2 + 2 + 6;	/* sta mac index */
	memcpy(sta_mac, frame + index, 6);

	index  = 2 + 2 + 6 + 6; /* bssid index */
	memcpy(bss_mac, frame + index, 6);

	cmdu = agent_gen_tunneled_msg(a, protocol, sta_mac,
			len, frame);
	if (!cmdu)
		goto error;

	ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
			cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, cmdu);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(cmdu));
	}

	cmdu_free(cmdu);
	free(frame);

	/* in case of BTM message type,
	 * generate BTM request to the STA
	 */
	if (protocol == 0x02) {
		ap = agent_get_ap_by_ifname(a, ifname);
		if (ap) {
			struct nbr pref;
			uint8_t steer_req_mode = 0;

			/* Inform client of imminent disassociation */
			steer_req_mode |= STEER_REQUEST_BTM_DISASSOC_IMM;
			/* Inform client of Abridged field */
			steer_req_mode |= STEER_REQUEST_BTM_ABRIDGED;

			memcpy(pref.bssid, bss_mac, 6);
			agent_request_btm(a, sta_mac, ap, &pref,
					  steer_req_mode, 0);
		}
	}

	return 0;

error:
	free(frame);

	return -1;
}

int handle_backhaul_sta_caps_query(void *agent, struct cmdu_buff *cmdu,
				   struct node *n)
{
	trace("%s: --->\n", __func__);
	struct agent *a = (struct agent *) agent;
	struct cmdu_buff *resp;
	int ret;

	/* Generate response cmdu */
	resp = agent_gen_bk_caps_response(a, cmdu);
	if (!resp)
		return -1;

	ret = CMDU_HOOK(agent, cmdu_get_type(resp), cmdu_get_mid(resp),
			resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		agent_send_cmdu(a, resp);
	} else {
		warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);
	return ret;
}

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

#if (EASYMESH_VERSION > 2)
int agent_process_bss_configuration_response_tlv(struct agent *agent, struct tlv *t)
{
	// todo:
	// Process One or more JSON encoded DPP Configuration Object attributes
	// {
	//	"wi-fi_tech":"infra",
	//	"discovery":
	//		{
	//		"ssid":"mywifi"
	//		},
	//	"cred":
	//		{
	//		"akm":"dpp",
	//		...
	//		}
	//	...
	// }

	(void)t->data;
	(void)t->len;

	return 0;
}

int handle_proxied_encap_dpp(void *agent, struct cmdu_buff *cmdu,
			      struct node *n)
{
	agnt_trace(LOG_DPP, "%s: --->\n", __func__);
#if USE_LIBDPP
	struct agent *a = (struct agent *)agent;
	struct tlv *t;
	struct tlv *tv[PROXIED_ENCAP_DPP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	struct tlv_1905_encap_dpp *encap;
	struct encap_dpp_frame *frm;
	bool mac_present;
	//bool is_dpp_frame;
	uint8_t *enrollee;
	uint16_t len;
	int offset = 0;

	if (!map_cmdu_validate_parse(cmdu, tv, ARRAY_SIZE(tv), n->map_profile)) {
		agnt_dbg(LOG_DPP, "%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 & (1 << 7));
	//is_dpp_frame = (encap->dst.flag & (1 << 5));

	offset += sizeof(encap->dst);

	/* Destination STA MAC Address */
	if (!mac_present) {
		agnt_err(LOG_DPP, "%s: no destination mac, drop proxied frame\n",
		    __func__);
		return -1;
	}

	offset += 6;
	enrollee = (uint8_t *) encap->dst.addr;

	frm = (struct encap_dpp_frame *) &t->data[offset];

	/* Encapsulated frame length */
	len = BUF_GET_BE16(frm->len);
#ifdef DEBUG
	dump(frm->frame, len, "PROXIED CMDU FRAME");
#endif

	switch (frm->type) {
	case 255:
	case DPP_PA_AUTHENTICATION_REQ:
	case DPP_PA_AUTHENTICATION_CONF: {
		struct map_macaddr_entry *e;
		struct netif_ap *ap;

		e = allmac_lookup(&a->peer_table, enrollee, MAC_ENTRY_DPP_ENROLLEE);
		if (!e) {
			agnt_warn(LOG_DPP, "%s: Enrollee ("MACFMT")not found in htable\n",
			     __func__, MAC2STR(enrollee));
			break;
		}

		ap = (struct netif_ap *) e->data;
		agnt_dbg(LOG_DPP, "%s: ===> frmlen:%d\n", __func__, len);
		if (len > DPP_FRAG_SIZE && frm->type == 255
		    && *(frm->frame + 1) == DPP_PUB_AF_GAS_INITIAL_RESP) {
			uint8_t *buf;
			uint16_t outlen = 0;
			uint8_t dialog_token;
			int ret;

			dialog_token = *(frm->frame + 2);

			buf = dpp_build_gas_initial_response(0, NULL,
							     WLAN_STATUS_SUCCESS,
							     dialog_token, true,
							     &outlen);
			if (!buf)
				break;

			wifi_ubus_ap_send_actframe(a->ubus_ctx, ap->ifname,
						   enrollee, buf, 19, 0, true);
			free(buf);

			ret = dpp_send_gas_comeback_responses(a, ap, enrollee,
							      /* skip header */
							      (frm->frame + 19),
							      len - 19,
							      dialog_token);
			if (ret)
				break;
		} else {
			wifi_ubus_ap_send_actframe(a->ubus_ctx, ap->ifname,
						   enrollee, frm->frame, len, 0,
						   true);
		}
		break;
	}
	default:
		break;
	}
#endif
	return 0;
}

int handle_direct_encap_dpp(void *agent, struct cmdu_buff *cmdu,
				     struct node *n)
{
	agnt_trace(LOG_DPP, "%s: --->\n", __func__);
#if USE_LIBDPP
	struct agent *a = (struct agent *)agent;
	struct tlv *tlv;
	struct tlv *tlvs[DIRECT_ENCAP_DPP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	struct tlv_dpp_message *dpp_msg;
	uint16_t framelen;
	uint8_t *frame;
	int frametype;
	uint8_t bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

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

	/* 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) {
		agnt_err(LOG_DPP, "%s: Frame type unknown!\n", __func__);
		return -1;
	}

#ifdef DEBUG
	/* Encapsulated frame length */
	dump(frame, framelen, "dpp direct encap CMDU");
#endif

	agnt_dbg(LOG_DPP, "%s: frametype:%s\n", __func__,
	dpp_frame_type2str(frametype));

	switch (frametype) {
	case DPP_PA_AUTHENTICATION_REQ:
	case DPP_PA_AUTHENTICATION_CONF:
	case DPP_PUB_AF_GAS_INITIAL_RESP:
	case DPP_PA_PEER_DISCOVERY_RESP: {
		void *ev = dpp_sm_create_event(a->dpp, bcast, DPP_EVENT_RX_FRAME, framelen, frame);

		timer_del(&a->dpp_legacy_timeout);

		if (ev) {
			dpp_trigger(a->dpp, bcast, ev);
			dpp_sm_free_event(ev);
		}
		break;
	}
	default:
		break;
	}
#endif
	return 0;
}

int handle_dpp_cce_indication(void *agent, struct cmdu_buff *cmdu,
			      struct node *n)
{
#ifdef USE_LIBDPP
	struct agent *a = (struct agent *)agent;
	struct tlv *tv[DPP_CCE_INDICATION_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv_dpp_cce *data;
	struct wifi_radio_element *re = NULL;

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

	if (!tv[DPP_CCE_INDICATION_IDX][0])
		return -1;

	data = (struct tlv_dpp_cce *)tv[DPP_CCE_INDICATION_IDX][0]->data;
	agnt_dbg(LOG_DPP, "%s: CCE Advertise flag: %d\n", __func__, data->enable);


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

		list_for_each_entry(ap, &re->aplist, list) {
			if (ap->cfg && !(ap->cfg->multi_ap & 0x02))
				continue;
			ap->cfg->advertise_cce = !!(data->enable);
			dpp_advertise_cce(a, ap->ifname, data->enable);
		}
	}
#endif
	return 0;
}

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

	int i;
	struct agent *a = (struct agent *)agent;
	struct tlv *tlv;
	struct tlv *tlvs[BSS_CFG_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };

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

	/* One or more BSS Configuration Response TLV */
	i = 0;
	while (i < TLV_MAXNUM && tlvs[BSS_CFG_RESP_BSS_CONFIG_RESPONSE_IDX][i]) {
		if (agent_process_bss_configuration_response_tlv(
			    a, tlvs[BSS_CFG_RESP_BSS_CONFIG_RESPONSE_IDX][i]))
			return -1;
		++i;
	}

	/* Zero or one Default 802.1Q Settings TLV */
	tlv = tlvs[BSS_CFG_RESP_DEFAULT_8021Q_SETTINGS_IDX][0];
	if (tlv)
		if (agent_fill_8021q_setting_from_tlv(a, (struct tlv_default_8021q_settings *)tlv->data))
			return -1;

	/* Zero or one Traffic Separation Policy TLV */
	tlv = tlvs[BSS_CFG_RESP_TRAFFIC_SEPARATION_POLICY_IDX][0];
	if (tlv) {
		struct tlv_traffic_sep_policy *ts_tlv = (struct tlv_traffic_sep_policy *) tlv->data;

		dbg("%s: TS policy received, num_ssid = %u\n", __func__, ts_tlv->num_ssid);

		if (ts_tlv->num_ssid > 0) {
			a->reconfig_reason &= ~AGENT_RECONFIG_REASON_VLAN_TEARDOWN;
			a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_SETUP;
			agent_fill_traffic_sep_policy(a, ts_tlv);
		} else if (agent_is_ts_enabled(a)) {
			a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_TEARDOWN;
		}
	} else if (agent_is_ts_enabled(a))
		a->reconfig_reason |= AGENT_RECONFIG_REASON_VLAN_TEARDOWN;

	timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);

	return send_bss_configuration_result(a);
}


int handle_dpp_bootstrap_uri(void *agent, struct cmdu_buff *cmdu,
				      struct node *n)
{
	agnt_trace(LOG_DPP, "%s: --->\n", __func__);

	struct agent *a = (struct agent *) agent;
	struct tlv *t;
	struct tlv *tlvs[DPP_BOOTSTRAP_URI_NOTIF_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };
	const struct tlv_dpp_uri_bootstrap *dpp_uri_notif;
	uint8_t *enrollee;
	int len;

	UNUSED(a);

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

	/* One DPP Bootstraping URI Notification TLV */
	t = tlvs[DPP_BOOTSTRAP_URI_NOTIF_IDX][0];

	agnt_trace(LOG_DPP, "\nTLV type: MAP_TLV_DPP_BOOTSTRAPING_URI_NOTIFICATION\n");

	dpp_uri_notif = (struct tlv_dpp_uri_bootstrap *)t->data;

	/* BSTA/macaddr */
	enrollee = (uint8_t *) dpp_uri_notif->bsta;
	agnt_trace(LOG_DPP, "\t\tbsta:" MACFMT "\n", MAC2STR(enrollee));

	/* URI length must be calculated based on TLV length */
	len = BUF_GET_BE16(t->len) - 18;

	/* URI length */
	agnt_trace(LOG_DPP, "\t\turi_len: %d\n", len);

	agnt_trace(LOG_DPP, "\t\tframe: %s\n\n", dpp_uri_notif->uri);
	agnt_trace(LOG_DPP, "\n");

	return 0;
}
#endif

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

	struct tlv *tv[AGENT_LIST_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

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

	if (tv[AGENT_LIST_AGENT_LIST_IDX][0]) {
		struct tlv_agent_list *tlv;
		uint16_t i;

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

		for (i = 0; i < tlv->num_agent; i++) {
			/* agent aladdr */
			dbg("\t\tagent_id: " MACFMT "\n", MAC2STR(tlv->agent[i].aladdr));
			/* profile */
			dbg("\t\tprofile: %d\n", tlv->agent[i].profile);
			/* security */
			dbg("\t\tsecurity: %d\n", tlv->agent[i].security);
		}
	}
	return 0;
}

int handle_service_prioritization_request(void *agent, struct cmdu_buff *cmdu,
                                          struct node *n)
{
	struct tlv_dscp_pcp *dscp_pcp;
	struct tlv_spr *spr;
	struct tlv *tv[SERVICE_PRIORITIZATION_REQUEST_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

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

	agnt_dbg(LOG_QOS, "%s: handle service prioritization request\n", __func__);

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

	if (tv[SERVICE_PRIORITIZATION_REQUEST_SERVICE_PRIORITIZATION_RULE_IDX][0]) {
		spr = (struct tlv_spr *)
			tv[SERVICE_PRIORITIZATION_REQUEST_SERVICE_PRIORITIZATION_RULE_IDX][0]->data;
	} else {
		agnt_err(LOG_QOS, "%s: no SPR TLV in SPR CMDU\n", __func__);
		map_error = MAP_STATUS_ERR_TLV_NUM_LESS;
		return -1;
	}

	spr->rule_id = BUF_GET_BE32(spr->rule_id);
	if (spr->rule_flags & SPR_FLAG_ADD_REMOVE) {
		if (tv[SERVICE_PRIORITIZATION_REQUEST_DSCP_MAPPING_TABLE_IDX][0]) {
			dscp_pcp = (struct tlv_dscp_pcp *)
				tv[SERVICE_PRIORITIZATION_REQUEST_DSCP_MAPPING_TABLE_IDX][0]->data;
		} else {
			agnt_dbg(LOG_QOS, "%s: no DSCP TLV in SPR CMDU\n", __func__);
			/* It might be correct if there is a different, but unsupported TLV */
			return 0;
		}

		if (qos_add_dscp_rule(agent, spr, dscp_pcp) != 0) {
			struct cmdu_buff *err_cmdu;

			agnt_err(LOG_QOS, "%s: failed to add rule\n", __func__);
			err_cmdu = agent_gen_profile2_error_code_cmdu(agent,
				cmdu->origin,
				TLV_PROFILE2_ERR_CODE_REASON_TOO_MANY_SPR,
				NULL,
				spr->rule_id,
				0);
			if (err_cmdu != NULL) {
				agent_send_cmdu(agent, err_cmdu);
				cmdu_free(err_cmdu);
			}
			return -1;
		}
	} else {
		if (qos_del_dscp_rule(agent, spr) != 0) {
			struct cmdu_buff *err_cmdu;

			agnt_err(LOG_QOS, "%s: failed to delete rule\n", __func__);
			err_cmdu = agent_gen_profile2_error_code_cmdu(agent,
				cmdu->origin,
				TLV_PROFILE2_ERR_CODE_REASON_SPR_NOT_FOUND,
				NULL,
				spr->rule_id,
				0);
			if (err_cmdu != NULL) {
				agent_send_cmdu(agent, err_cmdu);
				cmdu_free(err_cmdu);
			}
			return -1;
		}
	}

	qos_apply_dscp_rules(agent);

	return 0;
}
#endif

#if (EASYMESH_VERSION >= 6)
int handle_ap_mld_config_request(void *agent, struct cmdu_buff *cmdu, struct node *n)
{
	trace("%s:--->\n", __func__);
	struct agent *a = (struct agent *) agent;
	struct tlv *tv[AP_MLD_CONFIG_REQUEST_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct cmdu_buff *resp;
	int ret;

	if (a->multiple_cntlr) {
		dbg("%s: Multiple controllers detected - rejecting AP MLD Config " \
		    "Request from " MACFMT "\n", __func__, MAC2STR(cmdu->origin));
		return -1;
	}

	if (memcmp(cmdu->origin, a->cntlr_almac, 6)) {
		agnt_dbg(LOG_MLO, "%s: response not from an active controller!\n",
				__func__);
		return -1;
	}

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

	/* Send the 1905 ack message to Controller */
	send_1905_acknowledge(a, cmdu->origin, cmdu_get_mid(cmdu), NULL, 0);

	if (update_sha256_hash(tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]->data,
				tlv_length(tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]),
				a->ap_mld_cfg_sha256)) {
		/* Proces ReConfiguration */
		mlo_process_ap_mld_config(a, tv[AP_MLD_CONFIG_REQUEST_AP_MLD_CONFIG_IDX][0]->data);
		agnt_dbg(LOG_MLO, "%s: MLD configuration changed, schedule reconfig\n",
		    __func__);
		a->reconfig_reason |= AGENT_RECONFIG_REASON_MLD_ID;
		timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);
		autoconfig_ap_mld_hash_to_file(a->ap_mld_cfg_sha256);
	} else {
		agnt_dbg(LOG_MLO, "%s: Skip AP MLD configuration TLV, no change\n",
			    __func__);
	}

	if (tv[AP_MLD_CONFIG_REQUEST_EHT_OPERATIONS_IDX][0]) {
		struct tlv_eht_operations *p =
			(struct tlv_eht_operations *)tv[AP_MLD_CONFIG_REQUEST_EHT_OPERATIONS_IDX][0]->data;

		agent_process_eht_operations_tlv(a, p);
	}

	if (tv[AP_MLD_CONFIG_REQUEST_BSS_ADVANCED_CONFIG_IDX][0])
		agent_process_bss_advanced_config(a,
				tv[AP_MLD_CONFIG_REQUEST_BSS_ADVANCED_CONFIG_IDX][0]->data,
				tlv_length(tv[AP_MLD_CONFIG_REQUEST_BSS_ADVANCED_CONFIG_IDX][0]));

	/* Generate AP MLD Configuration Response */
	resp = agent_gen_ap_mld_configuration_response(a,
			cmdu->origin, cmdu_get_mid(cmdu));
	if (!resp)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
			resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(a, resp);
	} else {
		agnt_warn(LOG_MLO, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);

	return ret;
}

int handle_bsta_mld_config_request(void *agent, struct cmdu_buff *cmdu, struct node *n)
{
	agnt_trace(LOG_MLO, "%s:--->\n", __func__);
	struct agent *a = (struct agent *) agent;
	struct tlv *tv[BSTA_MLD_CONFIG_REQUEST_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct cmdu_buff *resp;
	int ret;

	if (a->multiple_cntlr) {
		dbg("%s: Dual controllers detected - rejecting BSTA MLD Config " \
		    "Request from " MACFMT "\n", __func__, MAC2STR(cmdu->origin));
		return -1;
	}

	if (memcmp(cmdu->origin, a->cntlr_almac, 6)) {
		agnt_dbg(LOG_MLO, "%s: response not from an active controller!\n",
				__func__);
		return -1;
	}

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

	/* Send the 1905 ack message to Controller */
	send_1905_acknowledge(a, cmdu->origin, cmdu_get_mid(cmdu), NULL, 0);

	/* Process TLV only if changed */
	if (update_sha256_hash(tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0]->data,
				tlv_length(tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0]),
				a->bsta_mld_cfg_sha256)) {
		int ret = 0;

		ret = mlo_process_bsta_mld_config(a,
				tv[BSTA_MLD_CONFIG_REQUEST_BSTA_MLD_CONFIG_IDX][0]->data);

		if (ret) {
			uint32_t band_all = BAND_2 | BAND_5 | BAND_6;

			bstamld_teardown_band(a, band_all);
		} else {
			/* re-do all MLD configuration in case TLV has changed */
			agnt_dbg(LOG_MLO, "%s: MLD configuration changed, schedule reconfig\n",
			    __func__);
			a->reconfig_reason |= AGENT_RECONFIG_REASON_MLD_ID;
			timer_set(&a->reload_scheduler, RELOAD_TIMEOUT * 1000);
		}
		autoconfig_bsta_mld_hash_to_file(a->bsta_mld_cfg_sha256);
	} else {
		agnt_dbg(LOG_MLO, "%s: Skip BSTA MLD configuration TLV, no change\n",
			    __func__);
	}

	/* Generate AP MLD Configuration Response */
	resp = agent_gen_bsta_mld_configuration_response(a,
			cmdu->origin, cmdu_get_mid(cmdu));
	if (!resp)
		return -1;

	ret = CMDU_HOOK(a, cmdu_get_type(resp), cmdu_get_mid(resp),
			resp->origin, resp->cdata, cmdu_size(resp),
			CMDU_PRE_TX);
	if (ret != CMDU_DROP) {
		ret = agent_send_cmdu(a, resp);
	} else {
		agnt_warn(LOG_MLO, "[%s]: cmdu 0x%04X dropped by extension before Tx\n",
		     __func__, cmdu_get_type(resp));
	}

	cmdu_free(resp);

	return ret;
}
#endif

/* Cmdu handlers commented out in the following two tables should be
 * handled in the controller.
 * Agent must implement the send cmdu functions corresponding to the
 * same.
 */

static const map_cmdu_handler_t i1905ftable[] = {
	[0x00] = handle_topology_discovery,
	[0x01] = handle_topology_notification,
	[0x02] = handle_topology_query,
	[0x03] = handle_topology_response,
	[0x04] = handle_vendor_specific,
	[0x05] = handle_link_metric_query,
	/* hole */
	[0x07] = handle_ap_autoconfig_search,
	[0x08] = handle_ap_autoconfig_response,
	[0x09] = handle_ap_autoconfig_wsc,
	[0x0a] = handle_ap_autoconfig_renew,
	[0x0d] = handle_higher_layer_query,
};


#define CMDU_TYPE_MAP_START	0x8000
#define CMDU_TYPE_MAP_END	MAP_CMDU_TYPE_MAX
#define CMDU_TYPE_MAP_NUM	(CMDU_TYPE_MAP_END - CMDU_TYPE_MAP_START + 1)

static const map_cmdu_handler_t agent_mapftable[CMDU_TYPE_MAP_NUM] = {
	[0x00] = handle_1905_ack,
	[0x01] = handle_ap_caps_query,
	[0x02] = handle_ap_caps_report,
	[0x03] = handle_map_policy_config,
	[0x04] = handle_channel_pref_query,
	/* [0x05] = handle_channel_pref_report, */
	[0x06] = handle_channel_sel_request,
	/* [0x07] = handle_channel_sel_response, */
#ifdef OPER_CHAN_CHANGE_RELAY_MCAST
	[0x08] = handle_oper_channel_report,
#endif
	[0x09] = handle_sta_caps_query,
	/* [0x0a] = handle_sta_caps_report, */
	[0x0b] = handle_ap_metrics_query,
	/* [0x0c] = handle_ap_metrics_response, */
	[0x0d] = handle_sta_link_metrics_query,
	/* [0x0e] = handle_sta_link_metrics_response, */
	[0x0f] = handle_unassoc_sta_link_metrics_query,
	[0x10] = handle_unassoc_sta_link_metrics_response,
	[0x11] = handle_beacon_metrics_query,
	/* [0x12] = handle_beacon_metrics_response, */
	[0x13] = handle_combined_infra_metrics,
	[0x14] = handle_sta_steer_request,
	/*[0x15] = handle_sta_steer_btm_report,*/
	[0x16] = handle_sta_assoc_control_request,
	/* [0x17] = handle_sta_steer_complete, */
	[0x18] = handle_hld_message,
	[0x19] = handle_backhaul_sta_steer_request,
	/* [0x1a] = handle_backhaul_sta_steer_response, */
	[0x1b] = handle_channel_scan_request,
	/* [0x1c] = handle_channel_scan_report, */
	/* hole */
	[0x20] = handle_cac_request,
	[0x21] = handle_cac_stop,
	/* [0x22] = handle_sta_disassoc_stats, */
#if (EASYMESH_VERSION >= 3)
	[0x23] = handle_service_prioritization_request,
#endif
	[0x24] = handle_error_response,
	/* [0x25] = handle_assoc_status_notification, */
	/* [0x26] = handle_tunneled_message, */
	[0x27] = handle_backhaul_sta_caps_query,
	[0x28] = handle_backhaul_sta_caps_report,
#if (EASYMESH_VERSION > 2)
	[0x29] = handle_proxied_encap_dpp,
	[0x2a] = handle_direct_encap_dpp,
	/* hole */
	[0x1d] = handle_dpp_cce_indication,
	[0x2d] = handle_bss_configuration_response,
	/* hole */
	[0x31] = handle_dpp_bootstrap_uri,
	/* [0x33] = handle_failed_connection_msg, */
	/* hole */
	[0x35] = handle_agent_list,
#if (EASYMESH_VERSION >= 4)
	/* hole */
#endif
#if (EASYMESH_VERSION >= 6)
	[0x44] = handle_ap_mld_config_request,
	[0x46] = handle_bsta_mld_config_request,
#endif
#endif
};


const char *tlv_friendlyname[] = {
	"supported_service",
	"searched_service",
	"ap_radio_id",
	"ap_oper_bss",
	"assoc_clients",
	"ap_caps",
	"ap_radio_basic_caps",
	"ap_caps_ht",
	"ap_caps_vht",
	"ap_caps_he",
	"steer_policy",
	"metric_report_policy",
	"channel_preference",
	"radio_oper_restriction",
	"tx_power_limit",
	"channel_sel_response",
	"oper_channel_report",
	"sta_info",
	"sta_caps_report",
	"sta_assoc_event",
	"ap_metric_query",
	"ap_metrics",
	"sta_macaddr",
	"sta_link_metrics",
	"unassoc_sta_link_metrics_query",
	"unassoc_sta_link_metrics_response",
	"beacon_metrics_query",
	"beacon_metrics_response",
	"steer_request",
	"steer_btm_report",
	"sta_assoc_control",
	"backhaul_steer_request",
	"backhaul_steer_response",
	"hld",
	"assoc_sta_stats",
	"error_code",
	"channel_scan_report_policy",
	"channel_scan_caps",
	"channel_scan_request",
	"channel_scan_result",
	"timestamp",
	"cac_request",
	"cac_stop",
	"cac_done_report",
	"cac_status_report",
	"cac_caps",
	"map_profile",
	"profile2_ap_caps",
	"default_8021q",
	"traffic_separation_policy",
	"profile2_error_code",
	"ap_radio_caps_advanced",
	"assoc_status_notification",
	"source_info",
	"tunneled_message",
	"tunneled",
	"profile2_steer_request",
	"unsuccessful_assoc_policy",
	"metric_collection_int",
	"radio_metrics",
	"ap_metrics_ext",
	"assoc_sta_link_metrics_ext",
	"status_code",
	"reason_code",
	"backhaul_sta_radio_caps",
	"backhaul_bss_config",
};


bool is_cmdu_for_us(struct agent *a, uint16_t type)
{
	/* Since map-plugin now sends cmdu events, agent must filter out cmdus
	 * that it is not supposed to handle.
	 * When map-plugin directly call's map-module's 'cmd', then the
	 * additonal cmdu type validation/filtering it does becomes useful. In
	 * the latter case, agent doesn't need to do additonal checks for valid
	 * cmdu types.
	 */

	/* until then, the following should be okay.. */

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

static inline uint16_t a_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_REQUEST:
			return CMDU_BSS_CONFIG_RESPONSE;
#endif
		default:
			break;
	}

	return CMDU_TYPE_NONE;
}

static uint16_t agent_cmdu_expect_response(struct agent *a, uint16_t req_type)
{
	uint16_t resp_type = a_cmdu_expect_response(req_type);

	if (resp_type == CMDU_TYPE_NONE)
		return CMDU_TYPE_NONE;

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

int agent_handle_map_event(struct agent *a, uint16_t cmdutype, uint16_t mid,
	char *rxif, uint8_t *src, uint8_t *origin, uint8_t *tlvs, int len)
{
	const map_cmdu_handler_t *f;
	struct cmdu_buff *cmdu = NULL;
	int ret = -1;
	int idx;
	uint16_t resp_type;
	struct cmdu_ackq_entry *entry;
	void *cookie;
	struct node *n;

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


	/* 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(a->almac, origin)) {
		resp_type = agent_cmdu_expect_response(a, cmdutype);
		entry = cmdu_ackq_lookup(&a->cmdu_ack_q, resp_type, mid, origin);
		if (entry) {
			dbg("%s: do not response for own cmdu %04x mid %u\n",
				__func__, cmdutype, mid);
			return 0;
		}
	}

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

	cmdu = cmdu_alloc_custom(cmdutype, &mid, rxif, src, tlvs, len);
	if (!cmdu) {
		dbg("agent: %s: cmdu_alloc_custom() failed!\n", __func__);
		return -1;
	}

	if (cmdutype >= CMDU_TYPE_MAP_START && cmdutype <= CMDU_TYPE_MAP_END) {
		idx = cmdutype - CMDU_TYPE_MAP_START;
		f = agent_mapftable;
		dbg("MSGID---> %d \n", idx);
	} else if (cmdutype <= CMDU_TYPE_1905_END) {
		idx = cmdutype;
		f = i1905ftable;
#ifdef DYNBH
		/* dynamic backhaul logic must always receive frame */
		if (dynbh_is_enabled(a) && dbh_ftable[idx])
			dbh_ftable[idx](a, cmdu);
#endif
	} else {
		cmdu_free(cmdu);
		return -1;
	}

	n = agent_find_node(a, origin);
	if (!n) {
		if (cmdutype != CMDU_TYPE_TOPOLOGY_DISCOVERY &&
		    cmdutype != CMDU_TYPE_TOPOLOGY_NOTIFICATION &&
		    cmdutype != CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH) {
			cmdu_free(cmdu);
			return -1;
		}
	}

	if (f[idx]) {
		memcpy(cmdu->origin, origin, 6);
		dbg("%s: cmdu_alloc_custom() succeeded! cmdu->cdata->hdr.mid %u\n", __func__, cmdu_get_mid(cmdu));
		ret = f[idx](a, cmdu, n);
	}

	cmdu_free(cmdu);

	return ret;
}

uint16_t agent_send_cmdu(struct agent *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;

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

	if (hwaddr_is_ucast(cmdu->origin)) {
		resp_type = agent_cmdu_expect_response(a, cmdu_get_type(cmdu));
		trace("%s: ---> resp_type %d\n", __func__, resp_type);
		if (resp_type != CMDU_TYPE_NONE)
			cookie = (void *) cmdu_clone(cmdu);
	}

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

		n = agent_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;
			}
		}

		ieee1905_ubus_send_frag_scheme(a->ubus_ctx, a->i1905obj_id,
					       cmdu->origin, frag_scheme);
	}

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

	ret = ieee1905_ubus_send_cmdu(a->ubus_ctx, a->i1905obj_id, cmdu, &msgid,
				      a->pvid);
	if (ret) {
		err("fail to send cmdu %04x over ubus\n", cmdu_get_type(cmdu));
		goto error;
	}

	old_mid = cmdu_get_mid(cmdu);
	if (old_mid == 0)
		cmdu_set_mid(cmdu, msgid);
	else if (old_mid != msgid)
		warn("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,
					msgid, 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;
}

int agent_set_link_profile(struct agent *a, struct node *n,
			   struct cmdu_buff *cmdu)
{
	int p = a->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;
}
