/*
 * cmdu.c
 * File contains CMDUs handling needed for WiFi Data Elements.
 *
 * Copyright (C) 2020-2022 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: saurabh.verma@iopsys.eu
 *	   anjan.chanda@iopsys.eu
 *
 * See LICENSE file for license related information.
 */

#include "backhaul_topology.h"
#include "debug.h"
#include "decollector_i.h"
#include "decollector.h"
#include "timer.h"
#include "tlv.h"
#include "utils.h"
#include "wifi_dataelements.h"
#include "sta_ratings.h"

#include <easy/easy.h>
#include <wifidefs.h>
#include <wifiutils.h>

#include <cmdu.h>
#include <i1905_wsc.h>
#include <1905_tlvs.h>
#include <easymesh.h>
#include <map_module.h>

#include <libubox/utils.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct wifi_network_device *decollector_get_origin_dev(struct decollector_private *p,
						       struct wifi_data_element *dm,
						       uint8_t *origin)
{
	struct wifi_network_device *dev = NULL;

	list_for_each_entry(dev, &dm->network.devicelist, list) {
		if (!hwaddr_is_zero(origin) && hwaddr_equal(dev->macaddr, origin))
			return dev;
	}

	//TODO: check interface macaddrs belonging to dev

	return NULL;
}

struct wifi_apmld_element *find_apmld(const struct wifi_network_device *dev,
				      const uint8_t *macaddr)
{
	struct wifi_apmld_element *a = NULL;

	list_for_each_entry(a, &dev->apmldlist, list) {
		if (!memcmp(a->mld_macaddr, macaddr, 6))
			return a;
	}

	return NULL;
}


struct wifi_apmld_element *find_bssid_stamld(struct wifi_network_device *dev, uint8_t *macaddr)
{
	struct wifi_stamld_element *stamld = NULL;
	struct wifi_apmld_element *a = NULL;

	list_for_each_entry(a, &dev->apmldlist, list) {
		list_for_each_entry(stamld, &a->stamldlist, list) {
			if (!memcmp(stamld->mld_macaddr, macaddr, 6))
				return a;
		}
	}

	return NULL;
}


struct wifi_affiliated_ap_element *find_affiliated_ap_in_apmld(const struct wifi_apmld_element *apmld,
						      const uint8_t *bssid)
{
	struct wifi_affiliated_ap_element *affap = NULL;

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

	return NULL;
}

struct wifi_linkopcmap_element *find_linkopcmap_in_apmld(const struct wifi_apmld_element *apmld,
						uint8_t linkid)
{
	struct wifi_linkopcmap_element *linkopcmap = NULL;

	list_for_each_entry(linkopcmap, &apmld->linkopcmaplist, list) {
		if (linkopcmap->linkid == linkid)
			return linkopcmap;
	}

	return NULL;
}

struct wifi_affiliated_ap_element *find_affiliated_ap(const struct wifi_network_device *dev,
				      const uint8_t *bssid)
{
	struct wifi_apmld_element *a = NULL;

	list_for_each_entry(a, &dev->apmldlist, list) {
		struct wifi_affiliated_ap_element *affap = NULL;

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

	return NULL;
}

struct wifi_affiliated_sta_element *find_affiliated_sta(const struct wifi_network_device *dev,
				      const uint8_t *macaddr)
{
	struct wifi_apmld_element *a = NULL;

	list_for_each_entry(a, &dev->apmldlist, list) {
		struct wifi_stamld_element *s = NULL;

		list_for_each_entry(s, &a->stamldlist, list) {
			struct wifi_affiliated_sta_element *affsta = NULL;

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

	return NULL;
}

struct wifi_affiliated_sta_element *find_affiliated_sta_in_bss(const struct wifi_network_device *dev,
				      const uint8_t *macaddr, uint8_t *bssid)
{
	struct wifi_apmld_element *a = NULL;

	list_for_each_entry(a, &dev->apmldlist, list) {
		struct wifi_stamld_element *s = NULL;

		list_for_each_entry(s, &a->stamldlist, list) {
			struct wifi_affiliated_sta_element *affsta = NULL;

			list_for_each_entry(affsta, &s->stalist, list) {
				if (memcmp(affsta->bssid, bssid, 6))
					continue;

				if (!memcmp(affsta->macaddr, macaddr, 6))
					return affsta;
			}
		}
	}

	return NULL;
}


struct wifi_bss_element *find_bss_and_radio(struct wifi_network_device *dev,
					    uint8_t *bssid,
					    struct wifi_radio_element **radio)
{
	struct wifi_radio_element *r;
	struct wifi_bss_element *b;

	list_for_each_entry(r, &dev->radiolist, list) {
		list_for_each_entry(b, &r->bsslist, list) {
			if (!memcmp(b->bssid, bssid, 6)) {
				*radio = r;
				return b;
			}
		}
	}

	return NULL;
}

struct wifi_bss_element *find_bss(struct wifi_network_device *dev, uint8_t *bssid)
{
	struct wifi_radio_element *r;
	struct wifi_bss_element *b;


	list_for_each_entry(r, &dev->radiolist, list) {
		list_for_each_entry(b, &r->bsslist, list) {
			if (!memcmp(b->bssid, bssid, 6)) {
				return b;
			}
		}
	}

	return NULL;
}

struct wifi_sta_element *find_sta(struct wifi_network_device *dev, uint8_t *macaddr)
{
	struct wifi_radio_element *r;
	struct wifi_bss_element *b;
	struct wifi_sta_element *s;


	list_for_each_entry(r, &dev->radiolist, list) {
		list_for_each_entry(b, &r->bsslist, list) {
			list_for_each_entry(s, &b->stalist, list) {
				if (!memcmp(s->macaddr, macaddr, 6)) {
					return s;
				}
			}
		}
	}

	return NULL;
}

struct wifi_stamld_element *find_stamld(const struct wifi_network_device *dev,
					const uint8_t *macaddr)
{
	struct wifi_apmld_element *a = NULL;
	struct wifi_stamld_element *s = NULL;

	list_for_each_entry(a, &dev->apmldlist, list) {
		list_for_each_entry(s, &a->stamldlist, list) {
			if (!memcmp(s->mld_macaddr, macaddr, 6))
				return s;
		}
	}

	return NULL;
}

bool network_device_has_stations(struct wifi_network_device *dev)
{
	struct wifi_radio_element *r;
	struct wifi_bss_element *b;

	list_for_each_entry(r, &dev->radiolist, list) {
		list_for_each_entry(b, &r->bsslist, list) {
			if (b->num_stations > 0)
				return true;
		}
	}

	return false;
}

bool network_device_has_bss(struct wifi_network_device *dev)
{
	struct wifi_radio_element *r;

	list_for_each_entry(r, &dev->radiolist, list) {
		if (r->num_bss)
			return true;
	}

	return false;
}

struct wifi_bss_element *find_bssid_sta(struct wifi_network_device *dev, uint8_t *macaddr)
{
	struct wifi_radio_element *r;
	struct wifi_bss_element *b;
	struct wifi_sta_element *s;


	list_for_each_entry(r, &dev->radiolist, list) {
		list_for_each_entry(b, &r->bsslist, list) {
			list_for_each_entry(s, &b->stalist, list) {
				if (!memcmp(s->macaddr, macaddr, 6))
					return b;
			}
		}
	}

	return NULL;
}

struct wifi_sta_element *find_sta_in_bss(struct wifi_network_device *dev, uint8_t *macaddr,
					 uint8_t *bssid)
{
	struct wifi_radio_element *r;
	struct wifi_bss_element *b;
	struct wifi_sta_element *s;


	list_for_each_entry(r, &dev->radiolist, list) {
		list_for_each_entry(b, &r->bsslist, list) {
			if (memcmp(b->bssid, bssid, 6))
				continue;

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

	return NULL;
}

struct wifi_sp_rule *find_sp_rule(const struct wifi_network_device *dev, uint32_t rule_id)
{
	struct wifi_sp_rule *rule = NULL;

	list_for_each_entry(rule, &dev->sp_rulelist, list) {
		if (rule->id == rule_id)
			return rule;
	}

	return NULL;
}

struct wifi_radio_element* get_radio_from_opclass(struct wifi_network_device *dev,
						  uint8_t opclass)
{
	//struct wifi_opclass_current_element *cur_opclass = NULL;
	struct wifi_radio_opclass_entry *cur_opclass = NULL;
	struct wifi_radio_element *r= NULL;
	int j;


	list_for_each_entry(r, &dev->radiolist, list) {
		for (j = 0; j < r->cur_opclass.num_opclass; j++) {
			cur_opclass = &r->cur_opclass.opclass[j];
			if (cur_opclass->id == opclass)
				return r;
		}
	}

	return NULL;
}

uint8_t wifi_get_widest_opclass(struct wifi_radio_element *radio)
{
	struct wifi_radio_opclass_entry *cur_opclass;
	uint8_t opclass_id = 0;
	uint32_t bw_max = 0;
	uint32_t bw_val;
	enum wifi_bw bw;
	int i, ret;

	for (i = 0; i < radio->cur_opclass.num_opclass; i++) {
		cur_opclass = &radio->cur_opclass.opclass[i];

		ret = wifi_opclass_bandwidth(cur_opclass->id, &bw);
		if (ret || bw == BW_UNKNOWN)
			continue;

		bw_val = wifi_bw_enum2MHz(bw);
		if (bw_val > bw_max) {
			bw_max = bw_val;
			opclass_id = cur_opclass->id;
		}
	}

	return opclass_id;
}

void wifi_refresh_linkopcmap_from_radio(struct wifi_network_device *dev,
					struct wifi_radio_element *radio)
{
	struct wifi_apmld_element *apmld;
	struct wifi_linkopcmap_element *linkopcmap;
	uint8_t opclass;

	opclass = wifi_get_widest_opclass(radio);

	list_for_each_entry(apmld, &dev->apmldlist, list) {
		list_for_each_entry(linkopcmap, &apmld->linkopcmaplist, list) {
			if (memcmp(linkopcmap->ruid, radio->macaddr, 6))
				continue;

			linkopcmap->opclass = opclass;
		}
	}
}

void sta_ratings_refresh(struct wifi_network_device *dev)
{
	struct wifi_apmld_element *apmld = NULL;
	struct wifi_radio_element *r = NULL;

	list_for_each_entry(r, &dev->radiolist, list) {
		struct wifi_bss_element *b = NULL;

		list_for_each_entry(b, &r->bsslist, list) {
			struct wifi_sta_element *s = NULL;

			list_for_each_entry(s, &b->stalist, list) {
				s->rating = sta_ratings_calculate(s->sta_ratings, dev, s->macaddr);
			}
		}
	}

	list_for_each_entry(apmld, &dev->apmldlist, list) {
		struct wifi_stamld_element *s = NULL;

		list_for_each_entry(s, &apmld->stamldlist, list) {
			s->rating = sta_ratings_calculate(s->sta_ratings, dev, s->mld_macaddr);
		}
	}
}

static enum network_link_type media_type_to_tr181_link_type(uint16_t media_type)
{
	const uint16_t media = media_type >> 8;

	switch (media) {
	case 0: return LINK_TYPE_ETH;
	case 1: return LINK_TYPE_WIFI;
	case 2: return LINK_TYPE_HOME;
	case 3: return LINK_TYPE_MOCA;

	default: return LINK_TYPE_NONE;
	}
}

static struct wifi_bh_down *find_bh_down(const struct wifi_network_device *dev,
				      const uint8_t *macaddr)
{
	struct wifi_bh_down *a = NULL;

	list_for_each_entry(a, &dev->bh_downlist, list) {
		if (!memcmp(a->al_macaddr, macaddr, 6))
			return a;
	}

	return NULL;
}

static void fill_dm_backhaul_curr_opclass(struct wifi_network_device *dev)
{
	struct wifi_radio_element *radio = NULL;
	struct wifi_network_device_backhaul *dm_bh =
		&dev->multi_ap_device.backhaul;

	list_for_each_entry(radio, &dev->radiolist, list) {
		struct wifi_backhaul_element *bsta = &radio->bsta;

		if (!memcmp(bsta->macaddr, dm_bh->bsta_macaddr, 6)) {
			dm_bh->cur_opclass = radio->cur_opclass;
			break;
		}
	}
}

static void fill_dm_backhaul_stats(struct decollector_private *p,
		struct wifi_network_device_backhaul *bh)
{
	struct wifi_network_device *dm_net_dev;

	list_for_each_entry(dm_net_dev, &p->dm->network.devicelist, list) {
		struct wifi_sta_element *sta;

		sta = find_sta(dm_net_dev, bh->bsta_macaddr);
		if (sta) {
			bh->stats.tx_bytes = sta->tx_bytes;
			bh->stats.rx_bytes = sta->rx_bytes;
			bh->stats.dl_rate = sta->dl_rate;
			break;
		}
	}
}

static void fill_dm_backhaul_down(struct wifi_network_device *dm_net_dev,
		const struct bh_topology_dev *bh_topo_dev, const uint8_t *cntlr_id)
{
	struct wifi_bh_down *bh_down, *tbh_down;
	int i, j;

	if (!dm_net_dev || !bh_topo_dev)
		return;

	dbg("%s: filling backhaul-down list for device " MACFMT "\n",
		__func__, MAC2STR(dm_net_dev->macaddr));

	/* Mark all backhaul-down entries for possible invalidation */
	if (!list_empty(&dm_net_dev->bh_downlist)) {
		list_for_each_entry(bh_down, &dm_net_dev->bh_downlist, list) {
			bh_down->invalidate = 1;
		}
	}

	for (i = 0; i < bh_topo_dev->number_of_interfaces; i++) {
		const struct local_iface *iface = &bh_topo_dev->ifaces[i];

		for (j = 0; j < iface->number_of_neighbors; j++) {
			/* Controller device has no parent,
			 * so this check applies only to non-controller devices in the network
			 */
			if (memcmp(cntlr_id, bh_topo_dev->al_macaddr, 6)) {
				if (!bh_topo_dev->bh_info.parent_in_tree) {
					dbg("%s: skip neighbor " MACFMT " (no parent_in_tree)\n",
						__func__, MAC2STR(iface->neighbors_al_macs[j]));
					continue;
				}

				if (hwaddr_equal(bh_topo_dev->bh_info.parent_in_tree->al_macaddr,
							iface->neighbors_al_macs[j])) {
					dbg("%s: skip neighbor " MACFMT " (same as parent)\n",
						__func__, MAC2STR(iface->neighbors_al_macs[j]));
					continue;
				}
			}

			bh_down = find_bh_down(dm_net_dev, iface->neighbors_al_macs[j]);
			if (bh_down) {
				bh_down->invalidate = 0;

				if (!hwaddr_is_zero(iface->neighbors_link_macs[j])) {
					memcpy(bh_down->bss_macaddr,
							iface->neighbors_link_macs[j], 6);
					dbg("%s: updated existing backhaul-down entry for " MACFMT "\n",
						__func__, MAC2STR(bh_down->al_macaddr));
				}

				continue;
			}

			if (hwaddr_is_zero(iface->neighbors_link_macs[j])) {
				dbg("%s: skip neighbor " MACFMT " (no link available)\n",
					__func__, MAC2STR(iface->neighbors_al_macs[j]));
				continue;
			}

			bh_down = calloc(1, sizeof(*bh_down));
			if (!bh_down) {
				err("%s: -ENOMEM\n", __func__);
				continue;
			}

			list_add_tail(&bh_down->list,
					&dm_net_dev->bh_downlist);

			memcpy(bh_down->al_macaddr,
					iface->neighbors_al_macs[j], 6);
			memcpy(bh_down->bss_macaddr,
					iface->neighbors_link_macs[j], 6);

			dm_net_dev->num_bh_down++;

			dbg("%s: added new backhaul-down entry for " MACFMT "\n",
				__func__, MAC2STR(bh_down->al_macaddr));
		}
	}

	/* Delete invalid backhaul-down entries */
	list_for_each_entry_safe(bh_down, tbh_down, &dm_net_dev->bh_downlist, list) {
		if (bh_down->invalidate) {
			dbg("%s: deleting stale backhaul-down entry for " MACFMT "\n",
				__func__, MAC2STR(bh_down->al_macaddr));
			list_del(&bh_down->list);
			dm_net_dev->num_bh_down -= 1;
			free(bh_down);
		}
	}
}

static void populate_dm_with_backhaul_topology(struct decollector_private *p)
{
	struct wifi_network_device *dm_net_dev;

	list_for_each_entry(dm_net_dev, &p->dm->network.devicelist, list) {
		const struct bh_topology_dev *bh_topo_dev =
			find_bh_topology_device(dm_net_dev->macaddr);

		if (bh_topo_dev &&
		    bh_topo_dev->bh_info.parent_iface) {
			const struct backhaul_info *bh_info =
				&bh_topo_dev->bh_info;
			struct wifi_network_device_backhaul *dm_bh =
				&dm_net_dev->multi_ap_device.backhaul;

			dm_bh->linktype = media_type_to_tr181_link_type(
				bh_info->parent_iface->media_type);
			if (bh_info->own_iface) {
				dm_bh->media_type = bh_info->own_iface->media_type;
				memcpy(dm_bh->bsta_macaddr,
				       bh_info->own_iface->macaddr, 6);

				fill_dm_backhaul_curr_opclass(dm_net_dev);
				fill_dm_backhaul_stats(p, dm_bh);
			}

			memcpy(dm_bh->upstream_bbss_macaddr,
			       bh_info->parent_iface->macaddr, 6);
			memcpy(dm_bh->upstream_device_macaddr,
			       bh_info->parent_in_tree->al_macaddr, 6);
		}

		fill_dm_backhaul_down(dm_net_dev, bh_topo_dev, p->dm->network.cntlr_id);
	}
}

static void build_bh_topology(struct decollector_private *p)
{
	int available_nodes = p->dm->network.num_devices;
	struct wifi_network_device *dev;

	if (is_bh_topology_valid()) {
		dbg("%s: BH toplogy tree is updated already.\n", __func__);
		return;
	}

	list_for_each_entry(dev, &p->dm->network.devicelist, list) {
		if (!is_network_device_alive(dev))
			--available_nodes;
	}

	dbg("%s: available nodes: %d, topo devs num: %d, cntrl: " MACFMT "\n",
		__func__, available_nodes, bh_topology_devs_number(), MAC2STR(p->dm->network.cntlr_id));

	/* When all topology responses collected and cntrl known, build topology tree. */
	if (available_nodes == bh_topology_devs_number() &&
	    !hwaddr_is_zero(p->dm->network.cntlr_id)) {

		build_bh_topology_tree(p->dm->network.cntlr_id);
		populate_dm_with_backhaul_topology(p);
	}
}


static int decollector_set_device_em_profile(struct decollector_private *p,
					     struct wifi_network_device *dev,
					     struct cmdu_buff *cmdu)
{
	int node_profile = map_cmdu_get_multiap_profile(cmdu);

	if (node_profile < MULTIAP_PROFILE_1)
		node_profile = MULTIAP_PROFILE_1;

	/* Use highest possible profile supported by both sides */
	dev->map_profile = min(node_profile, p->opts.em_profile);
	dbg("%s: " MACFMT ": multiap-profile = %d\n", __func__,
	    MAC2STR(dev->macaddr), dev->map_profile);

	return dev->map_profile;
}

int fill_ap_autoconfig_response_from_tlv(struct decollector_private *p,
					 struct cmdu_buff *resp,
					 struct wifi_network_device *dev)
{
	struct tlv *tlvs[AP_AUTOCONFIGURATION_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	char mac_addr_str[18] = { 0 };

	decollector_set_device_em_profile(p, dev, resp);


	dbg("%s: Setting map_profile=%d for device %s\n",
		__func__, dev->map_profile, hwaddr_ntoa(dev->macaddr, mac_addr_str));

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* SupportedService TLV */
	if (tlvs[AP_AUTOCONFIGURATION_RESP_SUPPORTED_SERVICE_IDX][0]) {
		const struct tlv_supported_service *services = (struct tlv_supported_service *)
			tlvs[AP_AUTOCONFIGURATION_RESP_SUPPORTED_SERVICE_IDX][0]->data;
		int i;

		for (i = 0; i < services->num_services; ++i) {

			if (services->services[i] == SUPPORTED_SERVICE_MULTIAP_CONTROLLER) {
				dbg("%s: controller id: " MACFMT "\n",
					__func__, MAC2STR(resp->origin));
				memcpy(p->dm->network.cntlr_id, resp->origin, 6);
				dev->multi_ap_device.controller_opmode = RUNNING;
				break;
			}
		}
	}

	return 0;
}

static bool is_topology_resp_from_easy_mesh_device(struct cmdu_buff *topology_resp)
{
	struct tlv *tlv = cmdu_peek_tlv(topology_resp, MAP_TLV_SUPPORTED_SERVICE);

	if (tlv && tlv_length(tlv)) {
		return ((struct tlv_supported_service *)tlv->data)->num_services > 0;
	}

	return false;
}

static int fill_topology_resp_from_tlv(struct decollector_private *priv,
				       struct cmdu_buff *resp,
				       struct wifi_network_device *dev)
{
	struct wifi_radio_element *radio, *tmp;
	struct wifi_bss_element *bss, *tbss;
	struct tlv *tlvs[TOPOLOGY_RESPONSE_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int offset = 0;
	int i, j;
	char mac_addr_str[18] = { 0 };
	struct bh_topology_dev *bh_topo_dev = NULL;
	const struct tlv_1905neighbor *neighbor_tlvs[TLV_MAXNUM] = { NULL };
	uint8_t neigh_tlv_cnt = 0;
	uint16_t tlv_lengths[TLV_MAXNUM] = { 0 };

#if (EASYMESH_VERSION >= 6)
	struct wifi_apmld_element *apmld = NULL, *t_apmld;
	int index = 0;
#endif

	/* Multi-AP Profile TLV */
	decollector_set_device_em_profile(priv, dev, resp);

	dbg("%s: Setting map_profile=%d for device %s\n",
	     __func__, dev->map_profile, hwaddr_ntoa(dev->macaddr, mac_addr_str));

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

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

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

		bh_topo_dev =
			find_bh_topology_device(tlv_dev_info->aladdr);

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

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

			new_bh_topo_dev = true;
		}

		if (new_bh_topo_dev ||
		    has_interface_info_changed(tlv_dev_info, bh_topo_dev)) {
			dbg("%s: New interface info for bh_topology_device\n", __func__);

			/* Copy new info and invalidate the model. */
			copy_interface_info_from_tlv(tlv_dev_info, bh_topo_dev);
		}
	}

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


	/* 1905.1 neighbor device TLVs */
	neigh_tlv_cnt = 0;
	while (neigh_tlv_cnt < TLV_MAXNUM && tlvs[3][neigh_tlv_cnt]) {
		neighbor_tlvs[neigh_tlv_cnt] =
			(struct tlv_1905neighbor *)tlvs[3][neigh_tlv_cnt]->data;
		tlv_lengths[neigh_tlv_cnt] = tlv_length(tlvs[3][neigh_tlv_cnt]);
		++neigh_tlv_cnt;
	}

	if (has_neighbor_info_changed(neighbor_tlvs, tlv_lengths, neigh_tlv_cnt, bh_topo_dev)) {
		/* Copy new info and invalidate the model. */
		copy_neighbor_info_from_tlvs(neighbor_tlvs, tlv_lengths,
					     neigh_tlv_cnt, bh_topo_dev);
	}

	/* SupportedService TLV */
	if (tlvs[TOPOLOGY_RESPONSE_SUPPORTED_SERVICE_IDX][0]) {
		const struct tlv_supported_service *services = (struct tlv_supported_service *)
			tlvs[TOPOLOGY_RESPONSE_SUPPORTED_SERVICE_IDX][0]->data;
		int i;

		for (i = 0; i < services->num_services; ++i) {
			const uint8_t service = services->services[i];
			struct wifi_multi_ap_device *map_dev =
				&dev->multi_ap_device;

			/* Don't change to SUPPORTED if detected as RUNNING already */
			if (service == SUPPORTED_SERVICE_MULTIAP_CONTROLLER &&
			    map_dev->controller_opmode == NOT_SUPPORTED) {

				map_dev->controller_opmode = SUPPORTED;

			} else if (service == SUPPORTED_SERVICE_MULTIAP_AGENT &&
				   map_dev->agent_opmode == NOT_SUPPORTED) {

				map_dev->agent_opmode = SUPPORTED;
			}
		}
	}

	/* AP Operational BSS TLV */
	if (tlvs[TOPOLOGY_RESPONSE_AP_OPERATIONAL_BSS_IDX][0]) {
		uint8_t *tv_data = (uint8_t *)tlvs[TOPOLOGY_RESPONSE_AP_OPERATIONAL_BSS_IDX][0]->data;
		struct tlv_ap_oper_bss *p = (struct tlv_ap_oper_bss *)tv_data;


		offset = sizeof(*p);

		/* mark all radios as updating */
		if (!list_empty(&dev->radiolist)) {
			list_for_each_entry(radio, &dev->radiolist, list) {
				radio->invalidate = 1;
			}
		}

		for (i = 0; i < p->num_radio; i++) {
			struct ap_oper_bss_radiolist *r = (struct ap_oper_bss_radiolist *)&tv_data[offset];
			int found = 0;


			list_for_each_entry(radio, &dev->radiolist, list) {
				if (!memcmp(radio->macaddr, r->radio, 6)) {
					found = 1;
					radio->invalidate = 0;
					break;
				}
			}

			if (!found) {
				/* create new radio entry */
				struct wifi_radio_element *n = alloc_radio(r->radio);

				if (n) {
					list_add_tail(&n->list, &dev->radiolist);
					dev->num_radios += 1;
					radio = n;
				}
			}

			radio->enabled = true;
			offset += sizeof(*r);

			/* mark all bss for this radio for possible invalidation */
			if (!list_empty(&radio->bsslist)) {
				list_for_each_entry(bss, &radio->bsslist, list) {
					bss->invalidate = 1;
				}
			}

			for (j = 0; j < r->num_bss; j++) {
				struct ap_oper_bss_bss *b = (struct ap_oper_bss_bss *)&tv_data[offset];
				int found_bss = 0;

				list_for_each_entry(bss, &radio->bsslist, list) {
					if (!memcmp(bss->bssid, b->bssid, 6)) {
						found_bss = 1;
						bss->invalidate = 0;
						break;
					}
				}

				if (!found_bss) {
					/* create new bss entry */
					struct wifi_bss_element *n = alloc_bss(b->bssid);

					if (n) {
						list_add_tail(&n->list, &radio->bsslist);
						radio->num_bss += 1;
						bss = n;
					}
				}

				memcpy(bss->ssid, b->ssid, b->ssidlen);
				bss->ssid[b->ssidlen] = '\0';
				dbg("%s: %d === ssid = %s\n", __func__, j, bss->ssid);
				bss->enabled = true;
				get_timestamp(NULL, bss->tsp, sizeof(bss->tsp));
				offset += sizeof(*b) + b->ssidlen;
			}

			/* delete invalid bss entries under this radio */
			list_for_each_entry_safe(bss, tbss, &radio->bsslist, list) {
				if (bss->invalidate) {
					list_del(&bss->list);
					radio->num_bss -= 1;
					free_bss(bss);
				}
			}
		}

		/* delete invalid radio entries */
		list_for_each_entry_safe(radio, tmp, &dev->radiolist, list) {
			if (radio->invalidate) {
				list_del(&radio->list);
				dev->num_radios -= 1;
				free_radio(radio);
			}
		}
	}

	/* Parse Associated Clients TLV */
	if (tlvs[TOPOLOGY_RESPONSE_ASSOCIATED_CLIENTS_IDX][0]) {
		uint8_t *tv_data = (uint8_t *)tlvs[TOPOLOGY_RESPONSE_ASSOCIATED_CLIENTS_IDX][0]->data;
		struct tlv_assoc_client *p = (struct tlv_assoc_client *)tv_data;

		offset = sizeof(*p);
		for (i = 0; i < p->num_bss; i++) {
			struct assoc_client_bss *b = (struct assoc_client_bss *)&tv_data[offset];
			struct wifi_sta_element *sta = NULL, *tsta;
			struct wifi_apmld_element *apmld = NULL;
			uint16_t num_sta = 0;

			offset += sizeof(*b);
			num_sta = BUF_GET_BE16(b->num_client);
			bss = find_bss(dev, b->bssid);
#if (EASYMESH_VERSION >= 6)
			if (!bss) {
				apmld = find_apmld(dev, b->bssid);
				if (apmld) {
					dbg("%s: Processing AP-MLD entry " MACFMT "\n",
					    __func__, MAC2STR(b->bssid));
				}
			}
#endif

			if (!bss && !apmld) {
					dbg("%s: skip unknown bssid " MACFMT "\n",
					    __func__, MAC2STR(b->bssid));
					offset += num_sta * sizeof(struct assoc_client_sta);
					continue;
			}

			/* Mark existing STAs for invalidation */
			if (bss && !list_empty(&bss->stalist)) {
				list_for_each_entry(sta, &bss->stalist, list) {
					sta->invalidate = 1;
				}
			}
#if (EASYMESH_VERSION >= 6)
			if (apmld && !list_empty(&apmld->stamldlist)) {
				struct wifi_stamld_element *stamld;

				list_for_each_entry(stamld, &apmld->stamldlist, list) {
					stamld->invalidate = 1;
				}
			}
#endif

			for (j = 0; j < num_sta; j++) {
				struct assoc_client_sta *s = (struct assoc_client_sta *)&tv_data[offset];
				bool found_sta = false;

				if (bss) {
					sta = NULL;
					list_for_each_entry(sta, &bss->stalist, list) {
						if (!memcmp(sta->macaddr, s->macaddr, 6)) {
							sta->invalidate = 0;
							sta->conn_time = BUF_GET_BE16(s->conntime);
							get_timestamp(NULL, sta->tsp, sizeof(sta->tsp));
							time(&sta->last_updated);
							found_sta = true;
							break;
						}
					}
				}

				/* Try to find as STAMLD (MLD entries) or add to BSS list */
				if (!found_sta) {
#if (EASYMESH_VERSION >= 6)
					if (apmld) {
						struct wifi_stamld_element *stamld;
						uint16_t conntime_val = BUF_GET_BE16(s->conntime);

						stamld = find_stamld(dev, s->macaddr);
						if (stamld) {
							stamld->conn_time = conntime_val;
							stamld->invalidate = 0;
							time(&stamld->last_updated);
							found_sta = true;
							dbg("%s: Updated STAMLD " MACFMT " conn_time=%u\n",
								__func__, MAC2STR(s->macaddr), conntime_val);
						} else {
							dbg("%s: STAMLD " MACFMT " not found (conntime=%u)\n",
								__func__, MAC2STR(s->macaddr), conntime_val);
						}
					}
#endif
					if (bss) {
						/* Create STA list entry for non-STAMLD MACs */
						struct wifi_sta_element *n;

						n = alloc_sta(s->macaddr);
						if (!n) {
							dbg("%s: -ENOMEM\n", __func__);
							return -1;
						}

						n->conn_time = BUF_GET_BE16(s->conntime);
						get_timestamp(NULL, n->tsp, sizeof(n->tsp));
						time(&n->last_updated);
						list_add_tail(&n->list, &bss->stalist);
						bss->num_stations += 1;
					}
				}

				offset += sizeof(*s);
			}

			/* Remove invalidated STAs */
			if (bss && !list_empty(&bss->stalist)) {
				sta = NULL;
				list_for_each_entry_safe(sta, tsta, &bss->stalist, list) {
					if (sta->invalidate) {
						list_del(&sta->list);
						bss->num_stations -= 1;
						free_sta(sta);
					}
				}
			}

#if (EASYMESH_VERSION >= 6)
			if (apmld && !list_empty(&apmld->stamldlist)) {
				struct wifi_stamld_element *stamld, *tstamld;

				list_for_each_entry_safe(stamld, tstamld, &apmld->stamldlist, list) {
					if (stamld->invalidate) {
						list_del(&stamld->list);
						apmld->num_sta -= 1;
						free_stamld(stamld);
					}
				}
			}
#endif
		}
	}

#if (EASYMESH_VERSION > 2)
	if (tlvs[TOPOLOGY_RESPONSE_BSS_CONFIGURATION_REPORT_IDX][0]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[TOPOLOGY_RESPONSE_BSS_CONFIGURATION_REPORT_IDX][0]->data;
		struct tlv_bss_configuration_report *p =
				(struct tlv_bss_configuration_report *)tv_data;

		offset = sizeof(*p);

		/* mark all radios as updating */
		if (!list_empty(&dev->radiolist)) {
			list_for_each_entry(radio, &dev->radiolist, list) {
				radio->invalidate = 1;
			}
		}

		for (i = 0; i < p->num_radio; i++) {
			struct bss_configuration_report_radio *r =
				(struct bss_configuration_report_radio *)&tv_data[offset];

			radio = find_radio(dev, r->ruid);

			if (radio)
				radio->invalidate = 0;
			else {
				/* create new radio entry */
				struct wifi_radio_element *n = alloc_radio(r->ruid);

				if (n) {
					list_add_tail(&n->list, &dev->radiolist);
					dev->num_radios += 1;
					radio = n;
				} else {
					dbg("%s: -ENOMEM\n", __func__);
					return -1;
				}
			}

			radio->enabled = true;
			offset += sizeof(*r);

			/* mark all bss for this radio for possible invalidation */
			if (!list_empty(&radio->bsslist)) {
				list_for_each_entry(bss, &radio->bsslist, list) {
					bss->invalidate = 1;
				}
			}

			for (j = 0; j < r->num_bss; j++) {
				struct bss_configuration_report_bss *b =
					(struct bss_configuration_report_bss *)&tv_data[offset];
				int found_bss = 0;
				uint8_t report = 0;

				list_for_each_entry(bss, &radio->bsslist, list) {
					if (!memcmp(bss->bssid, b->bssid, 6)) {
						found_bss = 1;
						bss->invalidate = 0;
						break;
					}
				}

				if (!found_bss) {
					/* create new bss entry */
					struct wifi_bss_element *n = alloc_bss(b->bssid);

					if (n) {
						list_add_tail(&n->list, &radio->bsslist);
						radio->num_bss += 1;
						bss = n;
					} else {
						dbg("%s: -ENOMEM\n", __func__);
						return -1;
					}
				}

				memcpy(bss->ssid, b->ssid, b->ssidlen);
				dbg("%s: %d === ssid = %s\n", __func__, j, bss->ssid);
				bss->enabled = true;
				memcpy(&report, &b->flag, 1);
				bss->is_bbss = (report & BSS_CONFIG_BBSS) ? 1 : 0;
				bss->is_fbss = (report & BSS_CONFIG_FBSS) ? 1 : 0;
				bss->r1_disallowed = (report & BSS_CONFIG_R1_DISALLOWED) ? 1 : 0;
				bss->r2_disallowed = (report & BSS_CONFIG_R2_DISALLOWED) ? 1 : 0;
				bss->multi_bssid = (report & BSS_CONFIG_MBSSID) ? 1 : 0;
				bss->transmitted_bssid = (report & BSS_CONFIG_TX_MBSSID) ? 1 : 0;
				get_timestamp(NULL, bss->tsp, sizeof(bss->tsp));
				offset += sizeof(*b) + b->ssidlen;
			}

			/* delete invalid bss entries under this radio */
			list_for_each_entry_safe(bss, tbss, &radio->bsslist, list) {
				if (bss->invalidate) {
					list_del(&bss->list);
					radio->num_bss -= 1;
					free_bss(bss);
				}
			}
		}

		/* delete invalid radio entries */
		list_for_each_entry_safe(radio, tmp, &dev->radiolist, list) {
			if (radio->invalidate) {
				list_del(&radio->list);
				dev->num_radios -= 1;
				free_radio(radio);
			}
		}
	}
#endif /* (EASYMESH_VERSION > 2) */

#if (EASYMESH_VERSION >= 6)
	/* MAP_TLV_AP_MLD_CONFIG */

	if (tlvs[TOPOLOGY_RESPONSE_AP_MLD_CONFIG_IDX][0]) {
		uint8_t *tv_data = tlvs[TOPOLOGY_RESPONSE_AP_MLD_CONFIG_IDX][0]->data;
		struct tlv_ap_mld_config *mldcfg = (struct tlv_ap_mld_config *)tv_data;

		/* mark all apmlds invalid for updating */
		if (!list_empty(&dev->apmldlist)) {
			list_for_each_entry(apmld, &dev->apmldlist, list) {
				apmld->invalidate = 1;
			}
		}

		dbg("%s: AP-MLD TLV: type = 0x%02X, len = %hu, num-apmld = %d\n",
		    __func__, tlvs[12][0]->type, tlv_length(tlvs[12][0]),
		    mldcfg->num_mlds);

		offset = 1;
		for (i = 0; i < mldcfg->num_mlds; i++) {
			struct wifi_affiliated_ap_element *affap = NULL, *t_affap = NULL;
			struct wifi_linkopcmap_element *linkopcmap = NULL, *t_linkopcmap = NULL;
			uint8_t num_affiliated_aps;
			uint8_t apmld_macaddr[6] = {0};
			bool mac_present = false;
			uint8_t ssid[33] = {0};
			uint8_t ssidlen = 0;
			uint8_t flag2;
			int j;

			apmld = NULL;
			mac_present = (tv_data[offset++] & AP_MLD_CONF_AP_MLD_ADDR_VALID);
			ssidlen = tv_data[offset++];
			memcpy(ssid, &tv_data[offset], ssidlen);
			offset += ssidlen;

			if (mac_present) {
				memcpy(apmld_macaddr, &tv_data[offset], 6);
				dbg("%s: AP-MLD-macaddr = " MACFMT "\n",
				    __func__, MAC2STR(apmld_macaddr));
			}
			offset += 6; /* sizeof(AP_MLD_MAC_Addr) */

			if (!hwaddr_is_zero(apmld_macaddr)) {
				apmld = find_apmld(dev, apmld_macaddr);
				if (apmld) {
					apmld->invalidate = 0;
				} else {
					/* create new apmld */
					struct wifi_apmld_element *n = calloc(1, sizeof(*n));

					if (!n) {
						dbg("%s: -ENOMEM\n", __func__);
						return -1;
					}

					memcpy(n->mld_macaddr, apmld_macaddr, 6);
					INIT_LIST_HEAD(&n->aplist);
					INIT_LIST_HEAD(&n->stamldlist);
					INIT_LIST_HEAD(&n->ttlmlist);
					INIT_LIST_HEAD(&n->linkopcmaplist);
					list_add_tail(&n->list, &dev->apmldlist);
					dev->num_apmld += 1;
					apmld = n;
					dbg("%s: create new AP-MLD " MACFMT"\n",
					    __func__, MAC2STR(apmld->mld_macaddr));
				}

				apmld->enabled = true;
				memset(apmld->ssid, 0, sizeof(apmld->ssid));
				memcpy(apmld->ssid, ssid, ssidlen);
				apmld->ssidlen = ssidlen;
			}

			//TODO: get apmld from ssid when !mac_present
			if (!apmld) {
				dbg("%s: TODO: get apmld from ssid\n", __func__);
				return 0; // FIXME: omited other APs from frame!
			}

			flag2 = tv_data[offset++];
			apmld->str_enabled = !!(flag2 & AP_MLD_CONFIG_STR);
			apmld->nstr_enabled = !!(flag2 & AP_MLD_CONFIG_NSTR);
			apmld->emlsr_enabled = !!(flag2 & AP_MLD_CONFIG_EMLSR);
			apmld->emlmr_enabled = !!(flag2 & AP_MLD_CONFIG_EMLMR);

			dbg("%s: str = %d, nstr = %d, emlsr = %d, emlmr = %d\n",
			    __func__, apmld->str_enabled, apmld->nstr_enabled,
			    apmld->emlsr_enabled, apmld->emlmr_enabled);

			offset += 20; /* rsvd 20 bytes */
			num_affiliated_aps = tv_data[offset++];
			dbg("%s: AP-MLD Config TLV: num-affiliated-ap = %d\n",
			    __func__, num_affiliated_aps);

			/* mark affiliated-aps invalid for updating */
			if (!list_empty(&apmld->aplist)) {
				list_for_each_entry(affap, &apmld->aplist, list) {
					affap->invalidate = 1;
				}
			}

			/* mark linkid-to-opclass-map invalid for updating */
			if (!list_empty(&apmld->linkopcmaplist)) {
				list_for_each_entry(linkopcmap, &apmld->linkopcmaplist, list) {
					linkopcmap->invalidate = 1;
				}
			}

			for (j = 0; j < num_affiliated_aps; j++) {
				uint8_t ruid[6] = {0};
				uint8_t link_bssid[6] = {0};
				uint8_t linkid;

				affap = NULL;
				mac_present = (tv_data[offset++] & AP_MLD_CONF_AFF_AP_ADDR_VALID);
				memcpy(ruid, &tv_data[offset], 6);
				offset += 6;

				if (mac_present)
					memcpy(link_bssid, &tv_data[offset], 6);
				offset += 6; /* sizeof(Affiliated_AP_MAC_Addr) */

				if (!hwaddr_is_zero(link_bssid)) {
					affap = find_affiliated_ap_in_apmld(apmld, link_bssid);
					if (affap) {
						affap->invalidate = 0;
					} else {
						/* create new affiliated ap */
						struct wifi_affiliated_ap_element *n = calloc(1, sizeof(*n));

						if (!n) {
							dbg("%s: -ENOMEM\n", __func__);
							return -1;
						}

						memcpy(n->bssid, link_bssid, 6);
						list_add_tail(&n->list, &apmld->aplist);
						apmld->num_ap += 1;
						affap = n;
					}
				}

				//TODO: affap from ruid/linkid when !mac_present
				if (!affap) {
					dbg("%s: TODO: get affiliated_ap from ruid/linkid\n", __func__);
					return 0; // FIXME: omited other APs from frame!
				}

				memcpy(affap->ruid, ruid, 6);
				linkid = tv_data[offset++];
				affap->linkid = linkid;
				offset += 18; /* rsvd 18 bytes */

				radio = find_radio(dev, affap->ruid);
				if (!radio)
					continue;

				linkopcmap = find_linkopcmap_in_apmld(apmld, affap->linkid);
				if (linkopcmap) {
					linkopcmap->invalidate = 0;
					linkopcmap->opclass = wifi_get_widest_opclass(radio);
				} else {
					/* create new linkid-to-opclass-map */
					struct wifi_linkopcmap_element *n = calloc(1, sizeof(*n));

					if (!n) {
						dbg("%s: -ENOMEM\n", __func__);
						return -1;
					}

					memcpy(n->ruid, ruid, 6);
					n->linkid = affap->linkid;
					n->opclass = wifi_get_widest_opclass(radio);
					list_add_tail(&n->list, &apmld->linkopcmaplist);
					apmld->num_linkopcmap += 1;
					linkopcmap = n;
				}
			}

			/* delete affiliated-ap entries no longer valid */
			if (!list_empty(&apmld->aplist)) {
				list_for_each_entry_safe(affap, t_affap, &apmld->aplist, list) {
					if (affap->invalidate) {
						list_del(&affap->list);
						apmld->num_ap -= 1;
						free(affap);
					}
				}
			}

			/* delete linkid-to-opclass-map entries no longer valid */
			if (!list_empty(&apmld->linkopcmaplist)) {
				list_for_each_entry_safe(linkopcmap, t_linkopcmap, &apmld->linkopcmaplist, list) {
					if (linkopcmap->invalidate) {
						list_del(&linkopcmap->list);
						apmld->num_linkopcmap -= 1;
						free(linkopcmap);
					}
				}
			}
		}

		/* delete apmld entries no longer valid */
		list_for_each_entry_safe(apmld, t_apmld, &dev->apmldlist, list) {
			if (apmld->invalidate) {
				list_del(&apmld->list);
				dev->num_apmld -= 1;
				free_apmld(apmld);
			}
		}
	}

	/* MAP_TLV_BACKHAUL_STA_MLD_CONFIG */
	free_bstamld(dev);	/* reset old BSTA MLD record */
	if (tlvs[TOPOLOGY_RESPONSE_BACKHAUL_STA_MLD_CONFIG_IDX][0]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[TOPOLOGY_RESPONSE_BACKHAUL_STA_MLD_CONFIG_IDX][0]->data;
		struct tlv_bsta_mld_config *p =
				(struct tlv_bsta_mld_config *)tv_data;

		dbg("%s: AP_MLD_MAC_Addr " MACFMT " (valid=%s)\n",
		    __func__,
		    MAC2STR(p->ap_mld_macaddr),
		    p->flag & BSTA_MLD_CONF_AP_MLD_ADDR_VALID ? "true" : "false");

		dbg("%s: bSTA_MLD_MAC_Addr " MACFMT " (valid=%s)\n",
		    __func__,
		    MAC2STR(p->bsta_mld_macaddr),
		    p->flag & BSTA_MLD_CONF_BSTA_MLD_ADDR_VALID ? "true" : "false");

		if (!!(p->flag & BSTA_MLD_CONF_AP_MLD_ADDR_VALID))
			memcpy(dev->bstamld.bssid, p->ap_mld_macaddr, 6);

		if (!!(p->flag & BSTA_MLD_CONF_BSTA_MLD_ADDR_VALID)) {
			struct wifi_network_device *anydev = NULL;

			memcpy(dev->bstamld.mld_macaddr, p->bsta_mld_macaddr, 6);

			list_for_each_entry(anydev, &priv->dm->network.devicelist, list) {
				struct wifi_stamld_element *stamld = NULL;

				stamld = find_stamld(anydev, p->bsta_mld_macaddr);
				if (stamld)
					stamld->is_bsta = true;
			}
		}

		dev->bstamld.str_enabled = !!(p->flag2 & BSTA_MLD_CONFIG_STR);
		dev->bstamld.nstr_enabled = !!(p->flag2 & BSTA_MLD_CONFIG_NSTR);
		dev->bstamld.emlsr_enabled = !!(p->flag2 & BSTA_MLD_CONFIG_EMLSR);
		dev->bstamld.emlmr_enabled = !!(p->flag2 & BSTA_MLD_CONFIG_EMLMR);
		/* TODO bstamld->ttlm_enabled source: TID-to-Link Mapping Policy TLV */

		offset = offsetof(struct tlv_bsta_mld_config, num_bsta);
		if (p->num_bsta)
			offset += 1;
		dbg("%s: Num_AffiliatedbSTA = %d\n", __func__, p->num_bsta);
		for (i = 0; i < p->num_bsta; i++) {
			struct _bsta_mld_config_affiliated_bsta *aff =
				(struct _bsta_mld_config_affiliated_bsta *)(tv_data + offset);

			if (!!(aff->flag3 & BSTA_MLD_CONF_AFFILIATED_BSTA_MLD_ADDR_VALID)) {
				struct wifi_affiliated_sta_element *affbsta;

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

				dbg("%s: Affiliated bSTA RUID " MACFMT ", macaddr " MACFMT "\n",
				    __func__, MAC2STR(aff->ruid), MAC2STR(aff->macaddr));
				memcpy(affbsta->ruid, aff->ruid , 6);
				memcpy(affbsta->macaddr, aff->macaddr, 6);
				list_add_tail(&affbsta->list, &dev->bstamld.affbstalist);
				dev->bstamld.num_aff_bsta++;
			}

			offset += (1 + 6 + 6 + 19);		// flag, ruid, macaddr, rsvd(19bytes)
		}
	}

	/* MAP_TLV_STA_MLD_CONFIG */
	/* mark all stamlds invalid for updating */
	if (!list_empty(&dev->apmldlist)) {
		list_for_each_entry(apmld, &dev->apmldlist, list) {
			struct wifi_stamld_element *stamld = NULL;

			list_for_each_entry(stamld, &apmld->stamldlist, list) {
				stamld->invalidate = 1;
			}
		}
	}
	while (index < TLV_MAXNUM && tlvs[TOPOLOGY_RESPONSE_STA_MLD_CONFIG_IDX][index]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[TOPOLOGY_RESPONSE_STA_MLD_CONFIG_IDX][index++]->data;
		struct tlv_associated_sta_mld_config *p =
				(struct tlv_associated_sta_mld_config *)tv_data;
		struct wifi_stamld_element *stamld = NULL;
		struct wifi_sta_element *sta;

		apmld = find_apmld(dev, p->ap_mld_macaddr);
		if (!apmld) {
			dbg("%s: AP-MLD " MACFMT " not found\n",
			     __func__, MAC2STR(p->ap_mld_macaddr));
			continue;
		}

		stamld = find_stamld(dev, p->sta_mld_macaddr);
		if (!stamld) {
			/* create new stamld */
			struct wifi_stamld_element *n = alloc_stamld(p->sta_mld_macaddr);

			if (!n) {
				dbg("%s: -ENOMEM\n", __func__);
				return -1;
			}

			list_add_tail(&n->list, &apmld->stamldlist);
			apmld->num_sta += 1;
			stamld = n;
			dbg("%s: create new STA-MLD " MACFMT"\n",
			    __func__, MAC2STR(stamld->mld_macaddr));
		}

		sta = find_sta(dev, p->sta_mld_macaddr);
		if (sta) {
			/* delete old SLO sta entry  */
			list_del(&sta->list);
			bss = find_bss(dev, p->ap_mld_macaddr);
			if (bss)
				bss->num_stations -= 1;
			stamld->conn_time = sta->conn_time;
			free_sta(sta);
		}

		stamld->invalidate = 0;
		stamld->str_enabled = !!(p->flag & STA_MLD_CONFIG_STR);
		stamld->nstr_enabled = !!(p->flag & STA_MLD_CONFIG_NSTR);
		stamld->emlsr_enabled = !!(p->flag & STA_MLD_CONFIG_EMLSR);
		stamld->emlmr_enabled = !!(p->flag & STA_MLD_CONFIG_EMLMR);

		/* TODO: read from assoc req */
		stamld->str_supported = !!(p->flag & STA_MLD_CONFIG_STR);
		stamld->nstr_supported = !!(p->flag & STA_MLD_CONFIG_NSTR);
		stamld->emlsr_supported = !!(p->flag & STA_MLD_CONFIG_EMLSR);
		stamld->emlmr_supported = !!(p->flag & STA_MLD_CONFIG_EMLMR);

		/* flush old affiliated-stalist for this STA-MLD */
		list_flush(&stamld->stalist, struct wifi_affiliated_sta_element, list);
		stamld->num_sta = 0;

		offset = offsetof(struct tlv_associated_sta_mld_config, num_affiliated_sta);
		if (p->num_affiliated_sta)
			offset += 1;

		for (i = 0; i < p->num_affiliated_sta; i++) {
			struct wifi_affiliated_sta_element *affsta = NULL;
			uint8_t *pos = &tv_data[offset];

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

			memcpy(affsta->bssid, pos, 6);
			memcpy(affsta->macaddr, pos + 6, 6);
			list_add_tail(&affsta->list, &stamld->stalist);
			stamld->num_sta++;
			offset += (6 + 6 + 19);		/* rsvd2 = 19 bytes */
		}
	}

	/* delete all stamld entries no longer valid */
	list_for_each_entry(apmld, &dev->apmldlist, list) {
		struct wifi_stamld_element *stamld = NULL, *t_stamld;

		list_for_each_entry_safe(stamld, t_stamld, &apmld->stamldlist, list) {
			if (stamld->invalidate) {
				list_del(&stamld->list);
				apmld->num_sta -= 1;
				free_stamld(stamld);
			}
		}
	}
#endif /* (EASYMESH_VERSION >= 6) */

	sta_ratings_refresh(dev);

	return 0;
}

static int fill_ap_capability_from_tlv(struct decollector_private *priv,
				       struct cmdu_buff *resp,
				       struct wifi_network_device *dev)
{
	struct wifi_radio_element *radio;
	struct tlv *tlvs[AP_CAPABILITY_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int offset = 0;
	int index = 0;
	int i;

	dbg("%s: %d\n", __func__, __LINE__);

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* Device replies with valid AP cap., map-agent is running */
	dev->multi_ap_device.agent_opmode = RUNNING;
	/* Use first 3 bytes of AL MAC as manufacturer's OUI */
	memcpy(dev->multi_ap_device.oui, dev->macaddr, ARRAY_SIZE(dev->multi_ap_device.oui));

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

		dev->multiap_caps = p->cap;
		if (!(p->cap & UNASSOC_STA_REPORTING_ONCHAN) &&
		    !(p->cap & UNASSOC_STA_REPORTING_OFFCHAN)) {
			/* if both unsupported, don't request unassoc-sta metric */
			list_for_each_entry(radio, &dev->radiolist, list) {
				radio->num_unassoc_sta = 0;
			}
		}
	}

	index = 0;
	/* AP Radio Basic Capabilities TLV */
	while (index < TLV_MAXNUM &&
			tlvs[AP_CAPABILITY_REPORT_AP_RADIO_BASIC_CAPS_IDX][index]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[AP_CAPABILITY_REPORT_AP_RADIO_BASIC_CAPS_IDX][index++]->data;
		struct tlv_ap_radio_basic_cap *p = (struct tlv_ap_radio_basic_cap *)tv_data;

		offset = sizeof(*p);
		radio = find_radio(dev, p->radio);
		if (!radio) {
			dbg("%s: radio in ap-radio-basic-cap not found!\n", __func__);
			continue;
		}

		radio->max_bssnum = p->max_bssnum;

		/* flush existing supp-opclass entries of this radio */
		if (radio->supp_opclass.num_opclass > 0)
			clear_supported_opclasses(radio);

		radio->supp_opclass.num_opclass = p->num_opclass;


		for (i = 0; i < p->num_opclass && i < ARRAY_SIZE(radio->supp_opclass.opclass); i++) {
			const struct ap_radio_basic_cap_opclass *cap_op =
				(struct ap_radio_basic_cap_opclass *)&tv_data[offset];
			struct wifi_radio_opclass_entry *radio_op =
				radio->supp_opclass.opclass;
			int j;

			radio_op[i].id = cap_op->classid;
			radio_op[i].max_txpower = cap_op->max_txpower;
			radio_op[i].num_channel = cap_op->num_nonop_channel;

			for (j = 0; j < cap_op->num_nonop_channel; j++) {
				radio_op[i].channel[j].channel = cap_op->nonop_channel[j];
				/*TODO: perhaps not need, as all channels in this structure are always non operable ones. */
				radio_op[i].channel[j].preference = WIFI_RADIO_OPCLASS_NON_OPERABLE;
			}

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

	}

	index = 0;
	/* AP HT Capabilities TLV */
	while (index < TLV_MAXNUM && tlvs[AP_CAPABILITY_REPORT_AP_HT_CAPS_IDX][index]) {
		struct tlv_ap_ht_cap *p =
			(struct tlv_ap_ht_cap *)tlvs[AP_CAPABILITY_REPORT_AP_HT_CAPS_IDX][index++]->data;

		radio = find_radio(dev, p->radio);
		if (!radio) {
			dbg("%s: radio in ap-ht-caps not found!\n", __func__);
			continue;
		}

		radio->caps.ht = p->cap;
		radio->caps.valid |= HT_CAP_VALID;
	}

	index = 0;
	/* AP VHT Capabilities TLV */
	while (index < TLV_MAXNUM && tlvs[AP_CAPABILITY_REPORT_AP_VHT_CAPS_IDX][index]) {
		struct tlv_ap_vht_cap *p =
			(struct tlv_ap_vht_cap *)tlvs[AP_CAPABILITY_REPORT_AP_VHT_CAPS_IDX][index++]->data;
		uint8_t vht[6];

		radio = find_radio(dev, p->radio);
		if (!radio) {
			dbg("%s: radio in ap-vht-caps not found!\n", __func__);
			continue;
		}

		memset(vht, 0, sizeof(vht));
		BUF_PUT_BE16(vht[0], p->tx_mcs_supported);
		BUF_PUT_BE16(vht[2], p->rx_mcs_supported);
		memcpy(&vht[4], p->cap, 2);

		memcpy(radio->caps.vht, vht, sizeof(vht));
		radio->caps.valid |= VHT_CAP_VALID;
		dbg("%s: %d: address = %p    VHT-cap = %02x %02x %02x %02x %02x %02x\n",
			__func__, __LINE__, radio->caps.vht,
			radio->caps.vht[0], radio->caps.vht[1],
			radio->caps.vht[2], radio->caps.vht[3],
			radio->caps.vht[4], radio->caps.vht[5]);
	}

	index = 0;
	/* Parse AP HE Capabilities TLV */
	while (index < TLV_MAXNUM && tlvs[AP_CAPABILITY_REPORT_AP_HE_CAPS_IDX][index]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[AP_CAPABILITY_REPORT_AP_HE_CAPS_IDX][index++]->data;
		struct tlv_ap_he_cap *p = (struct tlv_ap_he_cap *)tv_data;
		uint8_t he[15] = {0};
		int c_index = 0;


		offset = 0;
		radio = find_radio(dev, p->radio);
		if (!radio) {
			dbg("%s: radio not found for ap-he-caps!\n", __func__);
			continue;
		}

		if (p->hemcs.len > 12) {
			dbg("%s: radio HE-cap mcslen > 12!\n", __func__);
			continue;
		}

		offset = 1 + 6;
		he[0] = p->hemcs.len;

		if (p->hemcs.len > 0)
			memcpy(&he[1], &tv_data[offset], p->hemcs.len);

		c_index = 1 + he[0];
		if (c_index >= sizeof(he) - 2) {
			dbg("%s: radio HE-cap invalid!\n", __func__);
			continue;
		}

		offset += he[0];
		memcpy(&he[c_index], &tv_data[offset], 2);
		memcpy(radio->caps.he, he, sizeof(he));
		radio->caps.valid |= HE_CAP_VALID;
		dbg("%s: %d: HE-cap is valid\n", __func__, __LINE__);
		dbg("%s: %d: VHT-cap print again = %02x %02x %02x %02x %02x %02x\n", __func__, __LINE__,
			radio->caps.vht[0],
			radio->caps.vht[1],
			radio->caps.vht[2],
			radio->caps.vht[3],
			radio->caps.vht[4],
			radio->caps.vht[5]);
	}

	/* Channel Scan Capabilities TLV */
	if (tlvs[AP_CAPABILITY_REPORT_CHANNEL_SCAN_CAPABILITY_IDX][0]) {
		struct tlv_channel_scan_capability *tlv = (struct tlv_channel_scan_capability *)
			tlvs[AP_CAPABILITY_REPORT_CHANNEL_SCAN_CAPABILITY_IDX][0]->data;
		const uint8_t *tlv_data =
			tlvs[AP_CAPABILITY_REPORT_CHANNEL_SCAN_CAPABILITY_IDX][0]->data;
		size_t offset = 0;
		int i, j, k;

		offset += sizeof(tlv->num_radio);
		for (i = 0; i < tlv->num_radio; ++i) {
			const struct channel_scan_capability_radio
				*current_tlv_radio = (struct channel_scan_capability_radio *)(tlv_data + offset);

			radio = find_radio(dev, current_tlv_radio->radio);
			if (!radio) {
				offset += sizeof_channel_scan_capability_radio(current_tlv_radio);
				dbg("%s: radio not found for channel-scan-caps!\n", __func__);
				continue;
			}

			radio->scan_caps.boot_only = current_tlv_radio->cap & SCAN_CAP_ON_BOOT_ONLY;
			radio->scan_caps.impact = (((current_tlv_radio->cap & SCAN_CAP_IMPACT) >> 5) + 1);
			radio->scan_caps.interval =
				BUF_GET_BE32(current_tlv_radio->min_scan_interval);

			radio->scan_caps.opclass.num_opclass = current_tlv_radio->num_opclass;

			offset += sizeof(struct channel_scan_capability_radio);

			/* Operating classes and channel numbers in each operating class */
			/* that the radio is capable of scanning. */
			for (j = 0; j < current_tlv_radio->num_opclass; ++j) {
				const struct channel_scan_capability_opclass *current_tlv_oplass =
					(struct channel_scan_capability_opclass *)(tlv_data + offset);

				radio->scan_caps.opclass.opclass[j].id = current_tlv_oplass->classid;
				radio->scan_caps.opclass.opclass[j].num_channel = current_tlv_oplass->num_channel;

				offset += sizeof(struct channel_scan_capability_opclass);

				if (current_tlv_oplass->num_channel) {
					for (k = 0; k < current_tlv_oplass->num_channel; k++) {
						radio->scan_caps.opclass.opclass[j].channel[k].channel =
							current_tlv_oplass->channel[k];
					}

					offset += current_tlv_oplass->num_channel *
						  sizeof(current_tlv_oplass->channel[0]);
				} else {
					uint32_t opc_ch[64] = {0};
					int num_opc_ch = ARRAY_SIZE(opc_ch);

					/* full list of channels in given opclass */
					if (wifi_opclass_to_channels(current_tlv_oplass->classid,
							&num_opc_ch, opc_ch))
						continue;

					radio->scan_caps.opclass.opclass[j].num_channel = num_opc_ch;
					for (k = 0; k < num_opc_ch; k++) {
						radio->scan_caps.opclass.opclass[j].channel[k].channel =
								opc_ch[k];
					}
				}
			}
		}
	}

	/* CAC Capabilities TLV */
	if (tlvs[AP_CAPABILITY_REPORT_CAC_CAPABILITY_IDX][0]) {
		const struct tlv_cac_cap *tlv =
			(struct tlv_cac_cap *)tlvs[AP_CAPABILITY_REPORT_CAC_CAPABILITY_IDX][0]->data;
		const uint8_t *tlv_data = tlvs[AP_CAPABILITY_REPORT_CAC_CAPABILITY_IDX][0]->data;
		size_t offset = 0;
		int i, j, k, l;

		dev->country_code[0] = tlv->country[0];
		dev->country_code[1] = tlv->country[1];
		dev->country_code[2] = '\0';

		offset += sizeof(tlv->country) + sizeof(tlv->num_radio);

		/* DFS enabled if at least one radio supports CAC */
		dev->dfs_enabled = (tlv->num_radio > 0);

		for (i = 0; i < tlv->num_radio; ++i) {
			const struct cac_cap_radio *current_tlv_radio =
					 (struct cac_cap_radio *)(tlv_data + offset);

			radio = find_radio(dev, current_tlv_radio->radio);
			if (!radio) {
				offset += sizeof_cac_cap_radio(current_tlv_radio);
				dbg("%s: radio not found for CAC-caps!\n", __func__);
				continue;
			}

			list_flush(&radio->cac_capslist,
				   struct wifi_radio_cac_capabilities,
				   list);

			/* Move offset over static part of radio structure */
			offset += sizeof(struct cac_cap_radio);

			for (j = 0; j < current_tlv_radio->num_cac; ++j) {
				const struct cac_cap_cac *current_tlv_cap =
					(struct cac_cap_cac *)(tlv_data + offset);

				struct wifi_radio_cac_capabilities *cac_cap =
					calloc(1, sizeof(*cac_cap));

				if (!cac_cap)
					return -1;

				list_add(&cac_cap->list, &radio->cac_capslist);

				cac_cap->method = (enum wifi_cac_method)current_tlv_cap->supp_method;

				/* 24-bits big endian to host conversion */
				cac_cap->num_seconds =
					(current_tlv_cap->duration[0] << 16) |
					(current_tlv_cap->duration[1] << 8) |
					(current_tlv_cap->duration[2]);

				cac_cap->opclasses.num_opclass =  current_tlv_cap->num_opclass;

				offset += sizeof(struct cac_cap_cac);

				for (k = 0; k < current_tlv_cap->num_opclass; ++k) {
					const struct cac_cap_opclass *current_tlv_opclass =
						(struct cac_cap_opclass *)(tlv_data + offset);

					cac_cap->opclasses.opclass[k].id = current_tlv_opclass->classid;
					cac_cap->opclasses.opclass[k].num_channel = current_tlv_opclass->num_channel;

					offset += sizeof(struct cac_cap_opclass);

					for (l = 0; l < current_tlv_opclass->num_channel; ++l) {
						cac_cap->opclasses.opclass[k].channel[l].channel =
							current_tlv_opclass->channel[l];
					}

					offset +=
						current_tlv_opclass->num_channel *
						sizeof(current_tlv_opclass->channel[0]);
				}

			}
		}
	} else {
		dev->dfs_enabled = false;
	}

#if (EASYMESH_VERSION > 2)
	/* Profile-2 AP Capability TLV */
	if (tlvs[AP_CAPABILITY_REPORT_PROFILE2_AP_CAPABILITY_IDX][0]) {
		struct tlv_profile2_ap_cap *tlv = (struct tlv_profile2_ap_cap *)
			tlvs[AP_CAPABILITY_REPORT_PROFILE2_AP_CAPABILITY_IDX][0]->data;

		dev->max_prules = tlv->max_prio_rules;
		dev->max_vids = tlv->max_vids;

		dev->byte_counter_unit = ((tlv->caps | STATS_UNIT_MASK) >> 6);

		dev->support_sp =
			tlv->caps & PRIORITIZATION_SUPPORTED;
		dev->support_dpp =
			tlv->caps & DPP_ONBOARDING_SUPPORTED;
		dev->support_ts =
			tlv->caps & TRAFFIC_SEPARATION_SUPPORTED;

	}
#endif /* (EASYMESH_VERSION > 2) */

	/* Metric Collection Interval TLV */
	if (tlvs[AP_CAPABILITY_REPORT_METRIC_COLLECTION_INTERVAL_IDX][0]) {
		struct tlv_metric_collection_int *p;

		p = (struct tlv_metric_collection_int *)
			tlvs[AP_CAPABILITY_REPORT_METRIC_COLLECTION_INTERVAL_IDX][0]->data;
		dev->collect_int = BUF_GET_BE32(p->interval);
	}

#if (EASYMESH_VERSION > 2)
	/* AP Wi-Fi 6 Capabilities TLV */
	index = 0;
	while (index < TLV_MAXNUM && tlvs[AP_CAPABILITY_REPORT_AP_WIFI6_CAPS_IDX][index]) {
		const struct tlv_ap_wifi6_caps *tlv = (struct tlv_ap_wifi6_caps *)
			tlvs[AP_CAPABILITY_REPORT_AP_WIFI6_CAPS_IDX][index++]->data;
		const uint8_t *tlv_data = (uint8_t *)tlv;
		size_t offset = 0;
		int i;

		radio = find_radio(dev, tlv->ruid);
		if (!radio) {
			dbg("%s: radio in Wi-Fi 6 capabilities not found!\n", __func__);
			continue;
		}

		offset += sizeof(tlv->ruid) + sizeof(tlv->num_roles);

		for (i = 0; i < tlv->num_roles; ++i) {
			const struct wifi6_agent_role *tlv_role =
				(struct wifi6_agent_role *)(tlv_data + offset);
			const int role_type = (tlv_role->caps & AGENT_ROLE_MASK) >> 6;
			const int mcs_nss_len = tlv_role->caps & MCS_NSS_LEN_MASK;
			const struct wifi6_agent_role_other_caps *tlv_role_other =
				(struct wifi6_agent_role_other_caps *)
					(tlv_data + offset + sizeof(tlv_role->caps) + mcs_nss_len);
			struct wifi_wifi6_capabilities *wifi6_caps = NULL;

			/* Move offset to next role in tlv_ap_wifi6_caps */
			offset += sizeof(tlv_role->caps) + mcs_nss_len +
				  sizeof(struct wifi6_agent_role_other_caps);

			/* Select proper destination structure */
			if (role_type == AGENT_ROLE_AP) {
				wifi6_caps = &radio->wifi6caps_ap;
				radio->caps.valid |= WIFI6_AP_CAP_VALID;
			} else if (role_type == AGENT_ROLE_BH_STA) {
				wifi6_caps = &radio->wifi6caps_bsta;
				radio->caps.valid |= WIFI6_BSTA_CAP_VALID;
			} else {
				dbg("%s: Unsupported role in Wi-Fi 6 capabilities!\n", __func__);
				continue;
			}

			wifi6_caps->he160 = tlv_role->caps & HE160_SUPPORTED;
			wifi6_caps->he8080 = tlv_role->caps & HE8080_SUPPORTED;

			wifi6_caps->mcs_nss_len = mcs_nss_len;
			memcpy(wifi6_caps->mcs_nss_12, tlv_role->mcs_nss_12, mcs_nss_len);

			wifi6_caps->su_beamformer = tlv_role_other->beamform_caps & SU_BEAMFORMER_SUPPORTED;
			wifi6_caps->su_beamformee = tlv_role_other->beamform_caps & SU_BEAMFORMEE_SUPPORTED;
			wifi6_caps->mu_beamformer = tlv_role_other->beamform_caps & MU_B_FORMER_STATUS_SUPPORTED;
			wifi6_caps->beamformee_le80 = tlv_role_other->beamform_caps & B_FORMEE_STS_LE_80_SUPPORTED;
			wifi6_caps->beamformee_gt80 = tlv_role_other->beamform_caps & B_FORMEE_STS_GT_80_SUPPORTED;
			wifi6_caps->ul_mumimo = tlv_role_other->beamform_caps & UL_MU_MIMO_SUPPORTED;
			wifi6_caps->ul_ofdma = tlv_role_other->beamform_caps & UL_OFDMA_SUPPORTED;
			wifi6_caps->dl_ofdma = tlv_role_other->beamform_caps & DL_OFDMA_SUPPORTED;

			wifi6_caps->max_dl_mumimo =
				(tlv_role_other->max_mu_mimo_users & MAX_NUM_USRS_DL_MU_MIMO_MASK) >> 4;
			wifi6_caps->max_ul_mumimo =
				(tlv_role_other->max_mu_mimo_users & MAX_NUM_USRS_UL_MU_MIMO_MASK);
			wifi6_caps->max_dl_ofdma = tlv_role_other->max_dl_ofdma_users;
			wifi6_caps->max_ul_ofdma = tlv_role_other->max_ul_ofdma_users;

			wifi6_caps->rts = tlv_role_other->other_caps & RTS_SUPPORTED;
			wifi6_caps->mu_rts = tlv_role_other->other_caps & MU_RTS_SUPPORTED;
			wifi6_caps->multi_bssid = tlv_role_other->other_caps & MULTI_BSSID_SUPPORTED;
			wifi6_caps->mu_edca = tlv_role_other->other_caps & MU_EDCA_SUPPORTED;
			wifi6_caps->twt_requester = tlv_role_other->other_caps & TWT_REQUESTER_SUPPORTED;
			wifi6_caps->twt_responder = tlv_role_other->other_caps & TWT_RESPONDER_SUPPORTED;
			wifi6_caps->spatial_reuse = tlv_role_other->other_caps & SPATIAL_REUSE_SUPPORTED;
			wifi6_caps->anticipated_ch_usage = tlv_role_other->other_caps & ACU_SUPPORTED;

		}
	}

	/* Device 1905 Layer Security Capability TLV */
	if (tlvs[AP_CAPABILITY_REPORT_1905_SECURITY_CAPS_IDX][0]) {
		struct tlv_1905_security_cap *sec_cap = (struct tlv_1905_security_cap *)
			tlvs[AP_CAPABILITY_REPORT_1905_SECURITY_CAPS_IDX][0]->data;
		dev->i1905_seccap.caps_valid = true;

		dev->i1905_seccap.onboarding_protocol = sec_cap->protocol;
		dev->i1905_seccap.integrity = sec_cap->mic;
		dev->i1905_seccap.encryption = sec_cap->enc;
	}

	/* Device Inventory TLV */
	if (tlvs[AP_CAPABILITY_REPORT_DEVICE_INVENTORY_IDX][0]) {
		uint8_t *tlv_data = tlvs[AP_CAPABILITY_REPORT_DEVICE_INVENTORY_IDX][0]->data;
		int offset = 0;
		int max_str_len = 0;
		int i;
		uint8_t num_radio;
		struct device_inventory_sn *sn;
		struct device_inventory_sv *sv;
		struct device_inventory_ee *ee;

		/* Serial Number */
		sn = (struct device_inventory_sn *)(tlv_data + offset);

		max_str_len = min((sizeof(dev->serial) - 1), sn->lsn);
		memcpy(dev->serial, sn->sn, max_str_len);
		dev->serial[max_str_len] = '\0';
		offset += sizeof(sn->lsn) + sn->lsn;

		/* Software Version */
		sv = (struct device_inventory_sv *)(tlv_data + offset);

		max_str_len = min((sizeof(dev->swversion) - 1), sv->lsv);
		memcpy(dev->swversion, sv->sv, max_str_len);
		dev->swversion[max_str_len] = '\0';
		offset += sizeof(sv->lsv) + sv->lsv;

		/* Execution Env */
		ee = (struct device_inventory_ee *)(tlv_data + offset);

		max_str_len = min((sizeof(dev->execenv) - 1), ee->lee);
		memcpy(dev->execenv, ee->ee, max_str_len);
		dev->execenv[max_str_len] = '\0';
		offset += sizeof(ee->lee) + ee->lee;


		/* Wi-Fi Chipset(s) Vendor(s) */
		num_radio = *(uint8_t *)(tlv_data + offset);
		offset += sizeof(num_radio);
		for (i = 0; i < num_radio; ++i) {
			uint8_t *ruid = (uint8_t *)(tlv_data + offset);
			uint8_t chipset_vendor_len;
			const char *chipset_vendor;

			offset += 6;
			chipset_vendor_len = *(uint8_t *)(tlv_data + offset);

			offset += sizeof(chipset_vendor_len);
			chipset_vendor = (const char *)(tlv_data + offset);

			offset += chipset_vendor_len;

			radio = find_radio(dev, ruid);
			if (!radio) {
				dbg("%s: radio in ap-radio-advanced-cap not found!\n", __func__);
				continue;
			}

			max_str_len = min((sizeof(radio->vendor) - 1), chipset_vendor_len);
			memcpy(radio->vendor, chipset_vendor, max_str_len);
			radio->vendor[max_str_len] = '\0';
		}
	}


	index = 0;
	/* AP Radio Advanced Capabilities TLV */
	while (index < TLV_MAXNUM &&
			tlvs[AP_CAPABILITY_REPORT_AP_RADIO_ADV_CAPABILITY_IDX][index]) {
		struct tlv_ap_radio_adv_cap *p = (struct tlv_ap_radio_adv_cap *)
			tlvs[AP_CAPABILITY_REPORT_AP_RADIO_ADV_CAPABILITY_IDX][index]->data;

		radio = find_radio(dev, p->radio);
		if (!radio) {
			dbg("%s: radio in ap-radio-advanced-cap not found!\n", __func__);
			continue;
		}

		radio->ts_combined_fronthaul = p->cap & RADIO_CAP_COMBINED_FHBK;
		radio->ts_combined_backhaul = p->cap & RADIO_CAP_COMBINED_P1P2;
		// todo:
		// radio->mscs_and_em_support = p->cap & RADIO_CAP_MSCS_AND_EM;
		// radio->scs_and_em_support = p->cap & RADIO_CAP_SCS_AND_EM;
		// radio->dscp_to_up_map_support = p->cap & RADIO_CAP_DSCP_TO_UP_MAPPING;
		// radio->dscp_policy_support = p->cap & RADIO_CAP_DSCP_POLICY;

		++index;
	}

#endif /* (EASYMESH_VERSION > 2) */

#if (EASYMESH_VERSION >= 6)
	/* AKM Suite Capabilities TLV */
	if (tlvs[AP_CAPABILITY_REPORT_AKM_SUITE_CAPS_IDX][0]) {
		uint8_t *data_p = tlvs[AP_CAPABILITY_REPORT_AKM_SUITE_CAPS_IDX][0]->data;

		list_for_each_entry(radio, &dev->radiolist, list) {
			struct wifi_akm_suite *bss_akm = NULL;
			uint8_t num_bbss, num_fbss;
			int idx, offset = 0;

			list_flush(&radio->bbss_akmlist, struct wifi_akm_suite, list);
			list_flush(&radio->fbss_akmlist, struct wifi_akm_suite, list);

			radio->num_fbss_akm = 0;
			radio->num_bbss_akm = 0;

			num_bbss = data_p[offset];
			offset += 1;

			for (idx = 0; idx < num_bbss; idx++) {
				bss_akm = calloc(1, sizeof(*bss_akm));
				if (!bss_akm)
					break;

				memcpy(bss_akm->oui, &data_p[offset], 3);
				offset += 3;

				bss_akm->type = data_p[offset];
				offset += 1;

				list_add_tail(&bss_akm->list, &radio->bbss_akmlist);
				radio->num_bbss_akm++;
			}

			num_fbss = data_p[offset];
			offset += 1;

			for (idx = 0; idx < num_fbss; idx++) {
				bss_akm = calloc(1, sizeof(*bss_akm));
				if (!bss_akm)
					break;

				memcpy(bss_akm->oui, &data_p[offset], 3);
				offset += 3;

				bss_akm->type = data_p[offset];
				offset += 1;

				list_add_tail(&bss_akm->list, &radio->fbss_akmlist);
				radio->num_fbss_akm++;
			}
		}
	}
#endif

	return 0;
}

static int fill_ap_metrics_from_tlv(struct decollector_private *priv,
				    struct cmdu_buff *resp,
				    struct wifi_network_device *dev)
{
	struct wifi_radio_element *radio;
	struct wifi_bss_element *bss;
	struct wifi_sta_element *sta;
	struct tlv *tlvs[AP_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int index, offset = 0;
	int i;

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	index = 0;
	/* Parse AP Metrics TLV */
	while (index < TLV_MAXNUM && tlvs[AP_METRICS_RESP_AP_METRICS_IDX][index]) {
		int copy_index;
		struct tlv_ap_metrics *p =
			(struct tlv_ap_metrics *)tlvs[AP_METRICS_RESP_AP_METRICS_IDX][index++]->data;

		bss = find_bss_and_radio(dev, p->bssid, &radio);
		if (!bss) {
			dbg("%s: bss of ap-stats-tlv not found!\n", __func__);
			continue;
		}

		/* TODO/revisit:
		 * channel utilization & totoal STAs
		 */
		radio->total_utilization = p->channel_utilization;
		copy_index = 0;
		if (p->esp_ac & ESP_AC_BE)
			memcpy(bss->est_wmm_be, p->esp_be, 3);

		if (p->esp_ac & ESP_AC_BK) {
			memcpy(bss->est_wmm_bk, p->esp + copy_index, 3);
			copy_index += 3;
		}

		if (p->esp_ac & ESP_AC_VO) {
			memcpy(bss->est_wmm_vo, p->esp + copy_index, 3);
			copy_index += 3;
		}

		if (p->esp_ac & ESP_AC_VI)
			memcpy(bss->est_wmm_vi, p->esp + copy_index, 3);
	}

	index = 0;
	/* Associated STA Traffic Stats TLV */
	while (index < TLV_MAXNUM &&
			tlvs[AP_METRICS_RESP_ASSOCIATED_STA_TRAFFIC_STATS_IDX][index]) {
		struct tlv_assoc_sta_traffic_stats *p = (struct tlv_assoc_sta_traffic_stats *)
			tlvs[AP_METRICS_RESP_ASSOCIATED_STA_TRAFFIC_STATS_IDX][index++]->data;

		sta = find_sta(dev, p->macaddr);
		if (sta) {
			sta->tx_bytes = BUF_GET_BE32(p->tx_bytes);
			sta->rx_bytes = BUF_GET_BE32(p->rx_bytes);
			sta->tx_pkts = BUF_GET_BE32(p->tx_packets);
			sta->rx_pkts = BUF_GET_BE32(p->rx_packets);
			sta->tx_errors = BUF_GET_BE32(p->tx_err_packets);
			sta->rx_errors = BUF_GET_BE32(p->rx_err_packets);
			sta->rtx_pkts = BUF_GET_BE32(p->rtx_packets);
			get_timestamp(NULL, sta->tsp, sizeof(sta->tsp));
			time(&sta->last_updated);
		} else {
			struct wifi_stamld_element *stamld = NULL;

			stamld = find_stamld(dev, p->macaddr);
			if (stamld) {
				stamld->tx_bytes = BUF_GET_BE32(p->tx_bytes);
				stamld->rx_bytes = BUF_GET_BE32(p->rx_bytes);
				stamld->tx_packets = BUF_GET_BE32(p->tx_packets);
				stamld->rx_packets = BUF_GET_BE32(p->rx_packets);
				stamld->tx_errors = BUF_GET_BE32(p->tx_err_packets);
				stamld->rx_errors = BUF_GET_BE32(p->rx_err_packets);
				stamld->rtx_packets = BUF_GET_BE32(p->rtx_packets);
				time(&stamld->last_updated);
			} else {
				dbg("%s: sta of assoc-sta-stats-tlv not found!\n",
					__func__);
				continue;
			}
		}
	}

	index = 0;
	/* Associated STA Link Metrics TLV */
	while (index < TLV_MAXNUM &&
			tlvs[AP_METRICS_RESP_ASSOCIATED_STA_LINK_METRICS_IDX][index]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[AP_METRICS_RESP_ASSOCIATED_STA_LINK_METRICS_IDX][index++]->data;
		struct tlv_assoc_sta_link_metrics *p =
			(struct tlv_assoc_sta_link_metrics *)tv_data;
		uint32_t dl_est_thput = 0;
		uint32_t ul_est_thput = 0;
		uint32_t time_delta = 0;
		uint32_t rcpi = 0;

		offset = sizeof(*p);

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

			bss = find_bss(dev, b->bssid);
			if (!bss) {
				break;
			}

			offset += sizeof(*b);

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

		sta = find_sta(dev, p->macaddr);
		if (sta) {
			sta->time_delta = time_delta;
			sta->dl_est_thput = dl_est_thput;
			sta->ul_est_thput = ul_est_thput;
			sta->rcpi = rcpi;
			get_timestamp(NULL, sta->tsp, sizeof(sta->tsp));
			time(&sta->last_updated);
		} else {
			struct wifi_affiliated_sta_element *affsta = NULL;

			affsta = find_affiliated_sta(dev, p->macaddr);
			if (affsta) {
				affsta->time_delta = time_delta;
				affsta->dl_est_thput = dl_est_thput;
				affsta->ul_est_thput = ul_est_thput;
				affsta->rcpi = rcpi;
			} else {
				dbg("AP Metrics Response: has sta-link-metrics-tlv for UNKNOWN STA " MACFMT"\n", MAC2STR(p->macaddr));
			}
		}
	}

	index = 0;
	/* AP Extended Metrics TLV */
	while (index < TLV_MAXNUM && tlvs[AP_METRICS_RESP_AP_EXTENDED_METRICS_IDX][index]) {
		struct tlv_ap_ext_metrics *p = (struct tlv_ap_ext_metrics *)
			tlvs[AP_METRICS_RESP_AP_EXTENDED_METRICS_IDX][index++]->data;

		bss = find_bss(dev, p->bssid);
		if (!bss) {
			continue;
		}

		bss->tx_ucast_bytes = BUF_GET_BE32(p->tx_bytes_ucast);
		bss->rx_ucast_bytes = BUF_GET_BE32(p->rx_bytes_ucast);
		bss->tx_mcast_bytes = BUF_GET_BE32(p->tx_bytes_mcast);
		bss->rx_mcast_bytes = BUF_GET_BE32(p->rx_bytes_mcast);
		bss->tx_bcast_bytes = BUF_GET_BE32(p->tx_bytes_bcast);
		bss->rx_bcast_bytes = BUF_GET_BE32(p->rx_bytes_bcast);

		if (dev->byte_counter_unit == COUNTER_UNIT_MB)
			bss->unit = COUNTER_UNIT_MB;
		else if (dev->byte_counter_unit == COUNTER_UNIT_KB)
			bss->unit = COUNTER_UNIT_KB;
		else
			bss->unit = 0;
	}

	index = 0;
	/* Parse Radio Metrics TLV */
	while (index < TLV_MAXNUM && tlvs[AP_METRICS_RESP_RADIO_METRICS_IDX][index]) {
		struct tlv_radio_metrics *p =
			(struct tlv_radio_metrics *)tlvs[AP_METRICS_RESP_RADIO_METRICS_IDX][index++]->data;

		radio = find_radio(dev, p->radio);
		if (!radio)
			continue;

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

	index = 0;
	/* Associated STA Extended Link Metrics TLV */
	while (index < TLV_MAXNUM &&
			tlvs[AP_METRICS_RESP_ASSOCIATED_STA_EXT_LINK_METRICS_IDX][index]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[AP_METRICS_RESP_ASSOCIATED_STA_EXT_LINK_METRICS_IDX][index++]->data;
		struct tlv_sta_ext_link_metric *p =
			(struct tlv_sta_ext_link_metric *)tv_data;

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

			offset += sizeof(*b);
			sta = find_sta_in_bss(dev, p->macaddr, b->bssid);
			if (sta) {
				sta->dl_rate = BUF_GET_BE32(b->dl_rate);
				sta->ul_rate = BUF_GET_BE32(b->ul_rate);
				sta->dl_utilization = BUF_GET_BE32(b->rx_util);
				sta->ul_utilization = BUF_GET_BE32(b->tx_util);
				get_timestamp(NULL, sta->tsp, sizeof(sta->tsp));
				time(&sta->last_updated);
			} else {
				struct wifi_affiliated_sta_element *affsta = NULL;

				affsta = find_affiliated_sta_in_bss(dev, p->macaddr, b->bssid);
				if (!affsta)
					continue;

				affsta->dl_rate = BUF_GET_BE32(b->dl_rate);
				affsta->ul_rate = BUF_GET_BE32(b->ul_rate);
				affsta->dl_utilization = BUF_GET_BE32(b->rx_util);
				affsta->ul_utilization = BUF_GET_BE32(b->tx_util);
			}

		}
	}

#if (EASYMESH_VERSION >= 6)
	/* Parse Affiliated AP Metrics TLV */
	index = 0;
	while (index < TLV_MAXNUM && tlvs[AP_METRICS_RESP_AFFILIATED_AP_METRICS_IDX][index]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[AP_METRICS_RESP_AFFILIATED_AP_METRICS_IDX][index++]->data;
		struct tlv_affiliated_ap_metrics *p =
			(struct tlv_affiliated_ap_metrics *)tv_data;
		struct wifi_affiliated_ap_element *affap = NULL;

		dbg("%s: Affiliated AP MAC " MACFMT "\n",
		    __func__, MAC2STR(p->bssid));

		affap = find_affiliated_ap(dev, p->bssid);
		if (!affap)
			continue;

		affap->tx_packets = BUF_GET_BE32(p->tx_packets);
		affap->rx_packets = BUF_GET_BE32(p->rx_packets);
		affap->tx_errors = BUF_GET_BE32(p->tx_err_packets);
		affap->tx_ucast_bytes = BUF_GET_BE32(p->tx_bytes_ucast);
		affap->rx_ucast_bytes = BUF_GET_BE32(p->rx_bytes_ucast);
		affap->tx_mcast_bytes = BUF_GET_BE32(p->tx_bytes_mcast);
		affap->rx_mcast_bytes = BUF_GET_BE32(p->rx_bytes_mcast);
		affap->tx_bcast_bytes = BUF_GET_BE32(p->tx_bytes_bcast);
		affap->rx_bcast_bytes = BUF_GET_BE32(p->rx_bytes_bcast);
	}

	/* Parse Affiliated STA Metrics TLV */
	index = 0;
	while (index < TLV_MAXNUM &&
			tlvs[AP_METRICS_RESP_AFFILIATED_STA_METRICS_IDX][index]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[AP_METRICS_RESP_AFFILIATED_STA_METRICS_IDX][index++]->data;
		struct tlv_affiliated_sta_metrics *p =
			(struct tlv_affiliated_sta_metrics *)tv_data;
		struct wifi_affiliated_sta_element *affsta = NULL;

		dbg("%s: Affiliated STA MAC " MACFMT "\n",
		    __func__, MAC2STR(p->macaddr));

		affsta = find_affiliated_sta(dev, p->macaddr);
		if (!affsta)
			continue;

		affsta->tx_bytes = BUF_GET_BE32(p->tx_bytes);
		affsta->rx_bytes = BUF_GET_BE32(p->rx_bytes);
		affsta->tx_packets = BUF_GET_BE32(p->tx_packets);
		affsta->rx_packets = BUF_GET_BE32(p->rx_packets);
		affsta->tx_errors = BUF_GET_BE32(p->tx_err_packets);
	}
#endif

	sta_ratings_refresh(dev);
	return 0;
}

static int fill_bsta_from_tlv(struct decollector_private *priv,
			       struct cmdu_buff *resp,
			       struct wifi_network_device *dev)
{
	struct wifi_radio_element *radio;
	struct tlv *tlvs[BH_STA_CAPS_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int index;

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	index = 0;
	/* Backhaul STA Radio Capabilities TLV */
	while (index < TLV_MAXNUM &&
			tlvs[BH_STA_CAPS_REPORT_BACKHAUL_STA_RADIO_CAPABILITY_IDX][index]) {
		struct tlv_bsta_radio_cap *p = (struct tlv_bsta_radio_cap *)
			tlvs[BH_STA_CAPS_REPORT_BACKHAUL_STA_RADIO_CAPABILITY_IDX][index++]->data;

		radio = find_radio(dev, p->radio);
		if (!radio) {
			continue;
		}

		if (!!(p->macaddr_included & BSTA_MACADDRESS_INCLUDED))
			memcpy(radio->bsta.macaddr, p->macaddr[0], 6);
	}

	return 0;
}

static int fill_bh_link_metric_from_tlv(struct decollector_private *priv,
				     struct cmdu_buff *resp,
				     struct wifi_network_device *dev)
{
	struct tlv *tlvs[LINK_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct wifi_network_device_backhaul *bh_info =
		&dev->multi_ap_device.backhaul;

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* TLV_TYPE_TRANSMITTER_LINK_METRIC */
	if (tlvs[LINK_METRICS_RESP_TRANSMITTER_LINK_METRICS_IDX][0]) {
		const struct tlv_tx_linkmetric *tx = (struct tlv_tx_linkmetric *)
			tlvs[LINK_METRICS_RESP_TRANSMITTER_LINK_METRICS_IDX][0]->data;
		const struct tx_link_info *tx_link = &tx->link[0];

		if (tlv_length(tlvs[LINK_METRICS_RESP_TRANSMITTER_LINK_METRICS_IDX][0])
				!= (sizeof(struct tlv_tx_linkmetric) + sizeof(struct tx_link_info))) {

			dbg("%s: TX LINK_METRIC without tx_link_info\n", __func__);
			return -1;
		}

		if (!hwaddr_equal(bh_info->upstream_device_macaddr, tx->neighbor_aladdr) ||
		    !hwaddr_equal(bh_info->upstream_bbss_macaddr, tx_link->neighbor_macaddr) ||
		    !hwaddr_equal(bh_info->bsta_macaddr, tx_link->local_macaddr)) {

			dbg("%s: TX LINK_METRIC is not for known BH link\n", __func__);
			return -1;
		}


		bh_info->stats.tx_pkts = BUF_GET_BE32(tx_link->packets);
		bh_info->stats.tx_errors = BUF_GET_BE32(tx_link->errors);
		bh_info->stats.phyrate = BUF_GET_BE16(tx_link->phyrate);
		bh_info->stats.ul_rate = BUF_GET_BE16(tx_link->phyrate) * 1024u; /* from Mbps to kbps */

		get_timestamp(NULL, bh_info->stats.tsp, sizeof(bh_info->stats.tsp));
	}

	/* TLV_TYPE_RECEIVER_LINK_METRIC */
	if (tlvs[LINK_METRICS_RESP_RECEIVER_LINK_METRICS_IDX][0]) {
		const struct tlv_rx_linkmetric *rx = (struct tlv_rx_linkmetric *)
			tlvs[LINK_METRICS_RESP_RECEIVER_LINK_METRICS_IDX][0]->data;
		const struct rx_link_info *rx_link = &rx->link[0];

		if (tlv_length(tlvs[LINK_METRICS_RESP_RECEIVER_LINK_METRICS_IDX][0]) !=
				(sizeof(struct tlv_rx_linkmetric) + sizeof(struct rx_link_info))) {

			dbg("%s: RX LINK_METRIC without rx_link_info\n", __func__);
			return -1;
		}

		if (!hwaddr_equal(bh_info->upstream_device_macaddr, rx->neighbor_aladdr) ||
		    !hwaddr_equal(bh_info->upstream_bbss_macaddr, rx_link->neighbor_macaddr) ||
		    !hwaddr_equal(bh_info->bsta_macaddr, rx_link->local_macaddr)) {

			dbg("%s: RX LINK_METRIC is not for known BH link\n", __func__);
			return -1;
		}

		bh_info->stats.rx_pkts = BUF_GET_BE32(rx_link->packets);
		bh_info->stats.rx_errors = BUF_GET_BE32(rx_link->errors);
		bh_info->stats.rcpi = rssi_to_rcpi(rx_link->rssi);
	}

	return 0;
}


static int fill_unassocsta_from_tlv(struct decollector_private *priv,
				    struct cmdu_buff *resp,
				    struct wifi_network_device *dev)
{
	struct tlv *tlvs[UNASTA_LINK_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* Unassociated STA Link Metrics response TLV */
	if (tlvs[UNASTA_LINK_METRICS_RESP_UNASSOCIATED_STA_LINK_METRICS_IDX][0]) {
		struct tlv_unassoc_sta_link_metrics_resp *p = (struct tlv_unassoc_sta_link_metrics_resp *)
			tlvs[UNASTA_LINK_METRICS_RESP_UNASSOCIATED_STA_LINK_METRICS_IDX][0]->data;
		struct wifi_radio_element *radio = NULL;
		int i;


		radio = get_radio_from_opclass(dev, p->opclass);
		if (radio == NULL)
			return -1;

		/* flush older list */
		if (radio->num_unassoc_sta > 0) {
			list_flush(&radio->unassoc_stalist, struct wifi_unassoc_sta_element, list);
			radio->num_unassoc_sta = 0;
		}

		for (i = 0; i < p->num_sta; i++) {
			struct wifi_unassoc_sta_element *usta = NULL;

			usta = calloc(1, sizeof(*usta));
			if (!usta)
				break;

			memcpy(usta->macaddr, p->sta[i].macaddr, 6);
			usta->rcpi = p->sta[i].ul_rcpi;
			list_add_tail(&usta->list, &radio->unassoc_stalist);
			radio->num_unassoc_sta++;
		}
	}

	return 0;
}

#define WIFI_SCANRESULT_MAX_NUM       16
#define WIFI_SCANRESULT_MAX_AGE       300000	/* 5 mins */

static void decollector_scanlist_limit(struct wifi_network_device *dev)
{
	struct wifi_scanres_element *sres, *tmp;
	struct wifi_radio_element *radio;
	struct timeval now;


	list_for_each_entry(radio, &dev->radiolist, list) {
		/* to keep the scanresults number in a limit, delete oldest */
		if (radio->num_scanresult > WIFI_SCANRESULT_MAX_NUM) {
			sres = list_first_entry(&radio->scanlist, struct wifi_scanres_element, list);
			list_del(&sres->list);
			radio->num_scanresult--;
			free_scanresults(sres);
		}

		/* also delete very old entries */
		getcurrtime(&now);
		list_for_each_entry_safe(sres, tmp, &radio->scanlist, list) {
			struct timeval tmo;

			timeradd_msecs(&sres->tv, WIFI_SCANRESULT_MAX_AGE, &tmo);
			if (!timercmp(&tmo, &now, >)) { /* cppcheck-suppress syntaxError */
				list_del(&sres->list);
				radio->num_scanresult--;
				free_scanresults(sres);
			}
		}
	}
}

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

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

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

	return 0;
}

static int fill_scanres_channel_from_tlv(struct decollector_private *priv,
					 struct cmdu_buff *resp,
					 struct wifi_network_device *dev)
{
	struct tlv *tv[CHAN_SCAN_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv *tv_scan[256] = {0};
	timestamp_t tsp = {0};
	int num_res = 256;
	int i = 0, j = 0;
	struct radio_scanres {
		uint8_t radio[6];
		struct wifi_scanres_element *sres;
		bool newresults;
	} rs[dev->num_radios];
	int num_rs = 0;
	struct wifi_radio_element *r;
	int k;

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

		return -1;
	}

	if (get_tv_scan_from_cmdu(resp, tv_scan, &num_res))
		return -1;

	if (num_res <= 0) {
		dbg("Could not get scanresults\n");
		return -1;
	} else if (num_res > 256) {
		dbg("Num scanresult-tlvs = %d\n", num_res);
		for (i = 0; i < resp->datalen; i++) {
			dbg("%02x", resp->data[i] & 0xff);
		}
		dbg("\n------------------------------\n");
		return 0;
	}

	/* Timestamp TLV */
	if (tv[CHAN_SCAN_REPORT_TIMESTAMP_IDX][0]) {
		struct tlv_timestamp *p =
			(struct tlv_timestamp *)tv[CHAN_SCAN_REPORT_TIMESTAMP_IDX][0]->data;

		if (!p->len)
			return 0;

		strncpy(tsp, (char *)p->timestamp, min(p->len, sizeof(tsp) - 1));
	}

	list_for_each_entry(r, &dev->radiolist, list) {
		rs[num_rs].sres = calloc(1, sizeof(struct wifi_scanres_element));
		if (!rs[num_rs].sres) {
			dbg("%s: -ENOMEM\n", __func__);
			return -1;
		}

		memcpy(rs[num_rs].radio, r->macaddr, 6);
		INIT_LIST_HEAD(&rs[num_rs].sres->opclass_scanlist);
		getcurrtime(&rs[num_rs].sres->tv);
		strncpy(rs[num_rs].sres->tsp, tsp, sizeof(rs[i].sres->tsp));
		rs[num_rs].newresults = false;
		num_rs++;
	}


	/* Channel Scan Result TLV */
	for (i = 0; i < num_res; i++) {
		uint8_t *tv_data = (uint8_t *)tv_scan[i]->data;
		struct tlv_channel_scan_result *p = (struct tlv_channel_scan_result *)tv_data;
		struct wifi_scanres_opclass_element *op = NULL;
		struct wifi_scanres_channel_element *ch = NULL;
		struct scan_result_timestamp *stsp;
		struct scan_result_neighbor *snbr;
		struct wifi_radio_element *radio;
		struct scan_result_detail *sd;
		int found_op = 0;
		int found_ch = 0;
		int offset = 0;


		offset = sizeof(*p);

		/* do not store scan-results with status != success */
		if (p->status != CH_SCAN_STATUS_SUCCESS) {
			dbg("Ignore ScanResults: radio = " MACFMT ", channel = %d, status = %02x\n",
				MAC2STR(p->radio), p->channel, p->status);
			continue;
		}

		radio = find_radio(dev, p->radio);
		if (!radio) {
			goto error;
		}

		for (j = 0; j < dev->num_radios; j++) {
			if (!memcmp(rs[j].radio, p->radio, 6))
				break;
		}

		if (j == dev->num_radios) {
			dbg("%s: (unlikely) radio not found!\n", __func__);
			continue;
		}

		if (rs[j].sres->num_opclass_scanned > 0) {
			list_for_each_entry(op, &rs[j].sres->opclass_scanlist, list) {
				if (op->opclass == p->opclass) {
					found_op = 1;
					break;
				}
			}
		}

		if (!found_op) {
			op = calloc(1, sizeof(*op));
			if (!op) {
				dbg("%s: -ENOMEM\n", __func__);
				goto error;
			}

			INIT_LIST_HEAD(&op->channel_scanlist);
			op->opclass = p->opclass;

			rs[j].sres->num_opclass_scanned++;
			list_add_tail(&op->list, &rs[j].sres->opclass_scanlist);
		}


		if (op->num_channels_scanned > 0) {
			list_for_each_entry(ch, &op->channel_scanlist, list) {
				if (ch->channel == p->channel) {
					found_ch = 1;
					break;
				}
			}
		}

		if (!found_ch) {
			ch = calloc(1, sizeof(*ch));
			if (!ch) {
				dbg("%s: -ENOMEM\n", __func__);
				goto error;
			}

			INIT_LIST_HEAD(&ch->nbrlist);
			ch->channel = p->channel;

			op->num_channels_scanned++;
			list_add_tail(&ch->list, &op->channel_scanlist);
		}

		/* parse scanresult detail */
		sd = (struct scan_result_detail *)&tv_data[offset];
		stsp = (struct scan_result_timestamp *)&sd->tsp;

		if (stsp->len > 0)
			memcpy(ch->tsp, stsp->timestamp, stsp->len);

		offset += sizeof(*stsp) + stsp->len;
		ch->utilization = tv_data[offset++];
		ch->anpi = tv_data[offset++];
		snbr = (struct scan_result_neighbor *)&tv_data[offset];

		ch->num_neighbors = BUF_GET_BE16(snbr->num_neighbor);
		offset += 2;

		for (k = 0; k < ch->num_neighbors; k++) {
			struct wifi_scanres_neighbor_element *nbr;

			nbr = calloc(1, sizeof(*nbr));
			if (nbr) {
				struct scan_result_ssid *ss;
				struct scan_result_bandwidth *sbw;
				struct scan_result_bssload *bssload;

				memcpy(nbr->bssid, &tv_data[offset], 6);
				offset += 6;
				ss = (struct scan_result_ssid *)&tv_data[offset];

				if (ss->len > 0 && ss->len < 33)
					memcpy(nbr->ssid, ss->ssid, ss->len);

				offset += (1 + ss->len);
				nbr->rssi = tv_data[offset++];

				sbw = (struct scan_result_bandwidth *)&tv_data[offset];
				if (sbw->len > 0 && sbw->len < 8) {
					char bwstr[16] = {0};

					strncpy(bwstr, sbw->bwstr, min(sbw->len, sizeof(bwstr) - 1));
					if (!strncmp(bwstr, "80+80", 5))
						nbr->bw = 8080;
					else if (!strncmp(bwstr, "160", 3))
						nbr->bw = 160;
					else if (!strncmp(bwstr, "80", 2))
						nbr->bw = 80;
					else if (!strncmp(bwstr, "40", 2))
						nbr->bw = 40;
					else if (!strncmp(bwstr, "20", 2))
						nbr->bw = 20;
					else {
						dbg("decollector: Invalid bandwidth '%s' in scanresult\n", bwstr);
						goto error;
					}
				}
				offset += (1 + sbw->len);

				bssload = (struct scan_result_bssload *)&tv_data[offset++];
				if (!!(bssload->info & CH_SCAN_RESULT_BSSLOAD_PRESENT)) {
					struct scan_result_bssload_data *bldata;

					bldata = (struct scan_result_bssload_data *)&tv_data[offset];
					nbr->utilization = bldata->ch_util;
					nbr->num_stations = BUF_GET_BE16(bldata->sta_count);
					offset += sizeof(struct scan_result_bssload_data);
				}

				list_add_tail(&nbr->list, &ch->nbrlist);
			}
		}
		rs[j].newresults = true;
	}

	/* keep this scanresults if atleast one opclass has been scanned
	 * successfully.
	 */
	i = 0;
	list_for_each_entry(r, &dev->radiolist, list) {
		if (rs[i].sres->num_opclass_scanned > 0) {
			list_add_tail(&rs[i].sres->list, &r->scanlist);
			r->num_scanresult++;
		} else {
			free(rs[i].sres);
		}
		i++;
	}

	decollector_scanlist_limit(dev);
	return 0;

error:
	dbg("%s: error\n", __func__);
	if (num_rs > 0 && rs[j].sres) {
		free_scanresults(rs[j].sres);
	}
	return -1;
}

static int fill_channel_pref_from_tlv(struct decollector_private *priv,
				      struct cmdu_buff *resp,
				      struct wifi_network_device *dev)
{
	struct tlv *tlvs[CHANNEL_PREF_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int idx = 0;

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->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 Channel Preference TLVs */
	while (idx < TLV_MAXNUM && tlvs[CHANNEL_PREF_REPORT_CHANNEL_PREFERENCE_IDX][idx]) {
		const uint8_t *tlv_data =
			(uint8_t *)tlvs[CHANNEL_PREF_REPORT_CHANNEL_PREFERENCE_IDX][idx++]->data;
		const struct tlv_channel_pref *channel_pref = (struct tlv_channel_pref *)tlv_data;
		struct wifi_radio_element *radio;
		int i, offset = 0;

		radio = find_radio(dev, channel_pref->radio);
		if (!radio) {
			warn("%s: unknown radio " MACFMT " in Channel Preference TLV\n",
				__func__, MAC2STR(channel_pref->radio));
			continue;
		}

		/* Reset previous preferences on this radio */
		clear_preference_opclasses(radio);

		radio->pref_opclass.num_opclass = channel_pref->num_opclass;
		dbg("%s: radio " MACFMT " -> processing Channel Preference TLV: opclasses=%u\n",
			__func__, MAC2STR(channel_pref->radio), radio->pref_opclass.num_opclass);

		offset += sizeof(channel_pref->radio) +
				sizeof(channel_pref->num_opclass);

		for (i = 0; i < radio->pref_opclass.num_opclass &&
				i < ARRAY_SIZE(radio->pref_opclass.opclass); i++) {
			const struct channel_preflist *chan_pref;
			const struct channel_pref_opclass *pref_op =
					(struct channel_pref_opclass *)(tlv_data + offset);
			struct wifi_radio_opclass_entry *radio_op = radio->pref_opclass.opclass;
			int j;

			radio_op[i].id = pref_op->classid;

			offset += sizeof(pref_op->classid);
			chan_pref = (struct channel_preflist *)(tlv_data + offset);

			radio_op[i].num_channel = chan_pref->num_channel;
			for (j = 0; j < chan_pref->num_channel &&
					j < ARRAY_SIZE(radio_op[i].channel); j++) {
				radio_op[i].channel[j].channel = chan_pref->channel[j];
			}

			offset += sizeof(chan_pref->num_channel) +
					chan_pref->num_channel * sizeof(chan_pref->channel[0]);

			radio_op[i].channel[0].preference = tlv_data[offset];

			offset += sizeof(pref_op->preference);

			dbg("%s: radio " MACFMT " opclass[%d]: id=%u channels=%u pref=%u\n",
				__func__, MAC2STR(channel_pref->radio), i,
				radio_op[i].id, radio_op[i].num_channel,
				radio_op[i].channel[0].preference);
		}
	}

	/* One CAC Status Report TLV */
	if (tlvs[CHANNEL_PREF_REPORT_CAC_STATUS_REPORT_IDX][0]) {
		const uint8_t *tlv_data =
			(uint8_t *)tlvs[CHANNEL_PREF_REPORT_CAC_STATUS_REPORT_IDX][0]->data;
		const struct cac_status_available *avail_channels =
			(struct cac_status_available *)tlv_data;
		const struct cac_status_noop *noop_channels = NULL;
		const struct cac_status_cac *active_channels = NULL;
		struct wifi_cac_status *cac_status, *cac_status_tmp;
		int cac_stat_entry_cnt = 0;

		/* Not more than last MAX_CAC_STATUS_HISTORY reports are stored */
		list_for_each_entry_safe(cac_status, cac_status_tmp, &dev->cac_statuslist, list) {
			++cac_stat_entry_cnt;
			if (cac_stat_entry_cnt >= MAX_CAC_STATUS_HISTORY) {
				list_del(&cac_status->list);
				free_wifi_cac_status(cac_status);
			}
		}

		cac_status = calloc(1, sizeof(*cac_status));
		if (cac_status) {
			int i;
			int offset = 0;

			INIT_LIST_HEAD(&cac_status->available_chlist);
			INIT_LIST_HEAD(&cac_status->nop_chlist);
			INIT_LIST_HEAD(&cac_status->cac_chlist);

			get_timestamp(NULL, cac_status->tsp, sizeof(cac_status->tsp));

			list_add(&cac_status->list, &dev->cac_statuslist);

			for (i = 0; i < avail_channels->num_channel; ++i) {
				struct wifi_cac_available_channel *dm_avail_channels =
					calloc(1, sizeof(*dm_avail_channels));

				if (dm_avail_channels) {
					dm_avail_channels->opclass = avail_channels->ch[i].opclass;
					dm_avail_channels->channel = avail_channels->ch[i].channel;
					dm_avail_channels->cleared = BUF_GET_BE16(avail_channels->ch[i].since);

					list_add(&dm_avail_channels->list, &cac_status->available_chlist);
				}
			}

			offset += sizeof(avail_channels->num_channel) +
				  avail_channels->num_channel * sizeof(avail_channels->ch[0]);
			noop_channels = (struct cac_status_noop *)(tlv_data + offset);

			for (i = 0; i < noop_channels->num_channel; ++i) {
				struct wifi_cac_nop_channel *dm_nop_channels =
					calloc(1, sizeof(*dm_nop_channels));

				if (dm_nop_channels) {
					dm_nop_channels->opclass = noop_channels->ch[i].opclass;
					dm_nop_channels->channel = noop_channels->ch[i].channel;
					dm_nop_channels->remaining = BUF_GET_BE16(noop_channels->ch[i].remain);

					list_add(&dm_nop_channels->list, &cac_status->nop_chlist);
				}
			}

			offset += sizeof(noop_channels->num_channel) +
				noop_channels->num_channel * sizeof(noop_channels->ch[0]);
			active_channels = (struct cac_status_cac *)(tlv_data + offset);

			for (i = 0; i < active_channels->num_channel; ++i) {
				struct wifi_cac_active_channel *dm_active_channels =
					calloc(1, sizeof(*dm_active_channels));

				if (dm_active_channels) {
					dm_active_channels->opclass = active_channels->ch[i].opclass;
					dm_active_channels->channel = active_channels->ch[i].channel;
					dm_active_channels->remaining = BUF_GET_BE16(active_channels->ch[i].remain);

					list_add(&dm_active_channels->list,  &cac_status->cac_chlist);
				}
			}
		}
	}

	return 0;
}

static int fill_operating_channel_report_from_tlv(struct decollector_private *priv,
				      struct cmdu_buff *resp,
				      struct wifi_network_device *dev)
{
	int index, offset = 0;
	struct tlv *tlvs[OPER_CHANNEL_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int i;

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	index = 0;
	/* Parse Operating Channel Report TLV */
	while (index < TLV_MAXNUM &&
			tlvs[OPER_CHANNEL_REPORT_OPERATING_CHANNEL_IDX][index]) {
		uint8_t *tv_data =
			(uint8_t *)tlvs[OPER_CHANNEL_REPORT_OPERATING_CHANNEL_IDX][index++]->data;
		struct tlv_oper_channel_report *p =
			(struct tlv_oper_channel_report *)tv_data;
		struct wifi_radio_element *radio = NULL;
		uint8_t txpower;
		uint8_t num_opclass;

		radio = find_radio(dev, p->radio);
		if (!radio)
			continue;

		clear_current_opclasses(radio);

		clock_gettime(CLOCK_REALTIME, &radio->cur_opclass.entry_time);

		num_opclass = p->report.num_opclass;
		radio->cur_opclass.num_opclass = num_opclass;

		offset = sizeof(p->radio) + sizeof(p->report.num_opclass);

		txpower = tv_data[offset +
				  sizeof(p->report.opclass[0]) * num_opclass];

		for (i = 0; i < num_opclass; i++) {
			struct wifi_radio_opclass_entry *radio_op =
				radio->cur_opclass.opclass;

			radio_op[i].id = tv_data[offset++];
			radio_op[i].num_channel = 1;
			radio_op[i].channel[0].channel = tv_data[offset++];
			/* Set current tx power */
			radio_op[i].max_txpower = txpower;
		}

		wifi_refresh_linkopcmap_from_radio(dev, radio);
	}

#if (EASYMESH_VERSION >= 4)
	index = 0;
	while (index < TLV_MAXNUM &&
			tlvs[OPER_CHANNEL_REPORT_SPATIAL_REUSE_IDX][index]) {
		const struct tlv_sr_report *spatial_reuse_tlv = (struct tlv_sr_report *)
			tlvs[OPER_CHANNEL_REPORT_SPATIAL_REUSE_IDX][index++]->data;
		struct wifi_radio_element *radio =
			find_radio(dev, spatial_reuse_tlv->ruid);
		struct wifi_radio_spatial_reuse_report *out_report;

		if (!radio)
			continue;

		out_report = &radio->spatial_reuse_report;

		out_report->partial_bss_color =
			SR_PBSSCOLOR & spatial_reuse_tlv->bsscolor ? 1u : 0u;
		out_report->bss_color =
			SR_BSSCOLOR_MASK & spatial_reuse_tlv->bsscolor;
		out_report->hesiga_val15_allowed =
			SR_FLAG_HESIGA_VALUE15_ALLOWED & spatial_reuse_tlv->flag;
		out_report->srg_info_valid =
			SR_FLAG_SRG_VALID & spatial_reuse_tlv->flag;
		out_report->non_srg_offset_valid =
			SR_FLAG_NON_SRG_OFFSET_VALID & spatial_reuse_tlv->flag;
		out_report->psr_disallowed =
			SR_FLAG_PSR_DISALLOWED & spatial_reuse_tlv->flag;
		out_report->non_srg_obsspd_max_offset =
			spatial_reuse_tlv->non_srg_obss_pd_max_offset;
		out_report->srg_obsspd_min_offset =
			spatial_reuse_tlv->srg_obss_pd_min_offset;
		out_report->srg_obsspd_max_offset =
			spatial_reuse_tlv->srg_obss_pd_max_offset;
		memcpy(out_report->srg_bss_color_bitmap,
		       spatial_reuse_tlv->srg_bsscolor_bmp,
		       sizeof(out_report->srg_bss_color_bitmap));
		memcpy(out_report->srg_partial_bssid_bitmap,
		       spatial_reuse_tlv->srg_pbssid_bmp,
		       sizeof(out_report->srg_partial_bssid_bitmap));
		memcpy(out_report->neighbor_bss_color_in_use_bitmap,
		       spatial_reuse_tlv->nbr_bsscolor_bmp,
		       sizeof(out_report->neighbor_bss_color_in_use_bitmap));
	}
#endif /* (EASYMESH_VERSION >= 4) */

	return 0;
}

static int prepare_client_cap_query(struct decollector_private *priv, uint8_t *sta_mac,
					uint8_t *bssid, uint8_t *dst)
{
	struct cmdu_buff *req;
	uint16_t mid = 0;
	int ret;


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

	/* Client Info TLV */
	ret = decollector_gen_client_info(priv, req, sta_mac, bssid);
	if (ret)
		goto error;

	cmdu_put_eom(req);
	ret = decollector_send_cmdu_request(priv, req, dst);
	cmdu_free(req);

	return 0;

error:
	cmdu_free(req);

	return -1;
}

static const uint8_t *find_ie(const uint8_t *ies, size_t len, uint8_t eid)
{
	const uint8_t *end;

	if (!ies || len < 2)
		return NULL;

	end = ies + len;
	while (end - ies > 1) {
		if (ies + ies[1] + 2 > end)
			return NULL;

		if (ies[0] == eid)
			return ies;

		ies += ies[1] + 2;
	}

	return NULL;
}

static const uint8_t *find_ie_ext(const uint8_t *ies, size_t len, uint8_t ext_id)
{
	const uint8_t *end;

        if (!ies || len < 2)
                return NULL;

        end = ies + len;
        while (end - ies > 1) {
                if (ies + ies[1] + 2 > end)
                        return NULL;

                if (ies[0] == 0xff && ies[2] == ext_id)
                        return ies;

                ies += ies[1] + 2;
        }

        return NULL;
}

static void parse_iecap(struct wifi_caps_element *caps, const uint8_t *ie, uint8_t len, uint8_t valid_ie)
{
	int i;

	if (valid_ie == HT_CAP_VALID) {
		int rx_nss = 0, tx_nss = 0;

		/* HT cap info (2) + A-MPDU param (1) */
		int offset = 2 + 1;
		const uint8_t *supp_mcs = ie + offset;

		caps->valid |= HT_CAP_VALID;

		for (i = 0; i < 4; i++) {
			if (supp_mcs[i] == 0)
				break;

			rx_nss++;
		}

		if (!(supp_mcs[12] & BIT(0)) ||
				!(supp_mcs[12] & BIT(1)))
			tx_nss = rx_nss; /* Tx/Rx equal */
		else
			tx_nss = ((supp_mcs[12] >> 2) & 0x3) + 1; /* Tx differs, use Tx Max SS */

		if (rx_nss)
			rx_nss--;
		if (tx_nss)
			tx_nss--;

		caps->ht |= (tx_nss & 0x03) << 6;
		caps->ht |= (rx_nss & 0x03) << 4;
		caps->ht |= (!!(ie[0] & BIT(5)) ? 1 : 0) << 3;
		caps->ht |= (!!(ie[0] & BIT(6)) ? 1 : 0) << 2;
		caps->ht |= (!!(ie[0] & BIT(1)) ? 1 : 0) << 1;
	}

	if (valid_ie == VHT_CAP_VALID) {
		int offset = 0;
		int vht_chw = 0;
		int vht_nss = 0;
		bool ext_nss_bw_capable = false;
		bool has_atleast_2ss = false;

		caps->valid |= VHT_CAP_VALID;

		vht_chw = ((ie[0] >> 2) & 0x3);

		ext_nss_bw_capable = !!(ie[offset + 4 + 7] & BIT(5));
		vht_nss = ext_nss_bw_capable ? ((ie[3] >> 6) & 0x3) : 0;

		caps->vht[4] |= (!!(ie[0] & BIT(5)) ? 1 : 0) << 1;
		caps->vht[4] |= (!!(ie[0] & BIT(6)) ? 1 : 0) << 0;

		caps->vht[5] |= (!!(ie[1] & BIT(3)) ? 1 : 0) << 5;
		caps->vht[5] |= (!!(ie[2] & BIT(3)) ? 1 : 0) << 4;

		/* VHT caps info */
		offset += 4;
		for (i = 0; i < 2; i++) {
			const uint8_t *mcs_map = &ie[offset + i * 4];
			int octet = 0;
			int nss = 0;
			int l;

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

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

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

				nss++;
			}

			if (i == 1)
				has_atleast_2ss = nss >= 2;

			nss = nss ? (nss - 1) : nss;
			if (i == 0) {
				memcpy(&caps->vht[2], mcs_map, 2);
				caps->vht[4] |= (nss & 0x07) << 2;
			} else {
				memcpy(&caps->vht[0], mcs_map, 2);
				caps->vht[4] |= (nss & 0x07) << 5;
			}
		}

		/* 80+80 MHz */
		if (vht_chw >= 2 ||
				(vht_nss >= 1 && has_atleast_2ss))
			caps->vht[5] |= 1 << 7;

		/* 160 MHz */
		if (vht_chw >= 1 ||
				(vht_nss >= 1 && has_atleast_2ss))
			caps->vht[5] |= 1 << 6;
	}

	if (valid_ie == HE_CAP_VALID) {
		int offset = 0;
		bool b2, b3;
		int mcs_maplen = 4;
		int nss = 0;
		int index = 0;

		caps->valid |= HE_CAP_VALID;
		/* byte_mac (6) */
		offset += 6;

		b2 = !!(ie[offset] & BIT(3));
		b3 = !!(ie[offset] & BIT(4));

		/* get max MCS and NSS */
		if (b2)
			mcs_maplen += 4;
		if (b3)
			mcs_maplen += 4;

		/* byte_phy (11) */
		offset += 11;

		{
			int octet = 0;
			int l;
			const uint8_t *mcs_map = &ie[offset];

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

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

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

				nss++;
			}
		}

		nss = nss ? (nss - 1) : nss;
		caps->he[0] = mcs_maplen;

		memcpy(&caps->he[1], &ie[offset], mcs_maplen);
		index = mcs_maplen + 1;

		caps->he[index] |= (nss & 0x07) << 5;
		caps->he[index] |= (nss & 0x07) << 2;
		caps->he[index] |= (b3 ? 1 : 0) << 1;
		caps->he[index] |= (b2 ? 1 : 0) << 0;
		index++;

		caps->he[index] |= (!!(ie[9] & BIT(7)) ? 1 : 0) << 7; /* SU Beamformer */
		caps->he[index] |= (!!(ie[10] & BIT(1)) ? 1 : 0) << 6; /* MU Beamformer */
		caps->he[index] |= (!!(ie[8] & BIT(6)) ? 1 : 0) << 5;  /* UL MU-MIMO */
		caps->he[index] |= (!!(ie[8] & BIT(7)) ? 1 : 0) << 4;  /* UL MU-MIMO + OFDMA */
		caps->he[index] |= (!!(ie[12] & BIT(6)) ? 1 : 0) << 3;  /* DL MU-MIMO + OFDMA */
		caps->he[index] |= (!!(ie[12] & BIT(3)) ? 1 : 0) << 2;  /* UL OFDMA */
		caps->he[index] |= (!!(ie[12] & BIT(2)) ? 1 : 0) << 1;  /* DL OFDMA */
	}
}

static void fill_wifi6_caps(const uint8_t *re_assoc_req_frame, size_t frame_len,
			    struct wifi_wifi6_capabilities *caps)
{
	struct assoc_req_body *assoc_req;
	const uint8_t *he_mac_caps = NULL;
	const uint8_t *he_phy_caps = NULL;
	const uint8_t *mcs_nss = NULL;
	const uint8_t *ie;
	int offset = 0;
	size_t ies_len;

	assoc_req = (struct assoc_req_body *)re_assoc_req_frame;

	/* Only the variable part (IEs) after capab_info + listen_interval */
	ies_len = frame_len - offsetof(struct assoc_req_body, variable);

	ie = find_ie_ext(assoc_req->variable, ies_len, IE_EXT_HE_CAP);

	if (!ie || !caps || ie[0] != IE_EXT || ie[2] != IE_EXT_HE_CAP)
		return;

	/* elem. id (1b), length (1b), elem. ext. id (1b) */
	offset += 3;

	he_mac_caps = ie + offset;
	caps->twt_requester = he_mac_caps[0] & BIT(1);
	caps->twt_responder = he_mac_caps[0] & BIT(2);

	/* HE MAC caps. (6b) */
	offset += 6;

	he_phy_caps = ie + offset;
	caps->he160 = he_phy_caps[0] & BIT(3);
	caps->he8080 = he_phy_caps[0] & BIT(4);
	caps->ul_mumimo = he_phy_caps[2] & BIT(6);
	caps->ul_ofdma = he_phy_caps[6] & BIT(3);
	caps->dl_ofdma = he_phy_caps[6] & BIT(2);
	caps->su_beamformer = he_phy_caps[3] & BIT(7);
	caps->su_beamformee = he_phy_caps[4] & BIT(0);
	caps->mu_beamformer = he_phy_caps[4] & BIT(1);

	if (caps->su_beamformee) {
		caps->beamformee_le80 = (he_phy_caps[4] & BIT(2)) ||
					(he_phy_caps[4] & BIT(3)) ||
					(he_phy_caps[4] & BIT(4));

		caps->beamformee_gt80 = (he_phy_caps[4] & BIT(5)) ||
					(he_phy_caps[4] & BIT(6)) ||
					(he_phy_caps[4] & BIT(7));
	}

	/* HE PHY caps. (11b) */
	offset += 11;

	mcs_nss = ie + offset;
	caps->mcs_nss_len = 4;
	if (caps->he160)
		caps->mcs_nss_len += 4;
	if (caps->he8080)
		caps->mcs_nss_len += 4;

	/* copy 4, 8 or 12 bytes */
	memcpy(caps->mcs_nss_12, mcs_nss, caps->mcs_nss_len);
}

static void fill_reassoc_frame(const uint8_t *re_assoc_req_frame, size_t frame_len,
			       struct wifi_sta_element *sta)
{
	free(sta->reassoc_frame);
	sta->reassoc_framelen = 0;

	sta->reassoc_frame = calloc(1, frame_len);
	if (sta->reassoc_frame) {
		memcpy(sta->reassoc_frame, re_assoc_req_frame, frame_len);
		sta->reassoc_framelen = frame_len;
	}
}

static void fill_assoc_sta_caps(const uint8_t *re_assoc_req_frame, size_t frame_len,
				struct wifi_caps_element *caps)
{
	struct assoc_req_body *assoc_req;
	const uint8_t *ht_ie, *vht_ie, *he_ie;
	uint8_t ie_len = 0;
	size_t ies_len;

	assoc_req = (struct assoc_req_body *)re_assoc_req_frame;

	/* Only the variable part (IEs) after capab_info + listen_interval */
	ies_len = frame_len - offsetof(struct assoc_req_body, variable);

	ht_ie = find_ie(assoc_req->variable, ies_len, IE_HT_CAP);
	vht_ie = find_ie(assoc_req->variable, ies_len, IE_VHT_CAP);
	he_ie = find_ie_ext(assoc_req->variable, ies_len, IE_EXT_HE_CAP);

	if (ht_ie) {
		dbg("HT Caps..\n");
		ie_len = ht_ie[1];
		parse_iecap(caps, &ht_ie[2], ie_len, HT_CAP_VALID);
	}

	if (vht_ie) {
		dbg("VHT Caps..\n");
		ie_len = vht_ie[1];
		parse_iecap(caps, &vht_ie[2], ie_len, VHT_CAP_VALID);
	}

	if (he_ie) {
		dbg("HE caps...\n");
		ie_len = he_ie[1];
		parse_iecap(caps, &he_ie[3], ie_len - 1, HE_CAP_VALID);
	}
}

static int fill_client_capability_from_tlv(struct decollector_private *priv,
					   struct wifi_network_device *dev,
					   struct cmdu_buff *resp)
{
	struct wifi_sta_element *sta = NULL;
	struct wifi_assoc_event *event = NULL;
	struct tlv *tlvs[CLIENT_CAPABILITY_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* Client info TLV */
	if (tlvs[CLIENT_CAPABILITY_REPORT_CLIENT_INFO_IDX][0]) {
		struct tlv_client_info *cl_info =
			(struct tlv_client_info *)tlvs[CLIENT_CAPABILITY_REPORT_CLIENT_INFO_IDX][0]->data;

		/* check if the sta's last assoc-event is pending for this message */
		event = find_assoc_event(&dev->ev.assoclist, cl_info->macaddr, cl_info->bssid);
		sta = find_sta_in_bss(dev, cl_info->macaddr, cl_info->bssid);
	}

	/* Client Capability Report TLV */
	if (tlvs[CLIENT_CAPABILITY_REPORT_CLIENT_CAPABILITY_IDX][0]) {
		enum result_code { SUCCESS = 0x00, FAILURE = 0x01 };
		struct tlv *t = (struct tlv *)tlvs[CLIENT_CAPABILITY_REPORT_CLIENT_CAPABILITY_IDX][0];
		struct tlv_client_cap_report *report =
			(struct tlv_client_cap_report *)t->data;
		size_t frame_len = BUF_GET_BE16(t->len) - 1;

		if (report->result == SUCCESS) {
			if (event) {
				fill_assoc_sta_caps(report->frame, frame_len, &event->caps);
				if (!!(event->pending & WIFI_ASSOC_EVENT_PENDING_CAPS)) {
					event->pending &= ~WIFI_ASSOC_EVENT_PENDING_CAPS;
					dev->ev.pending |= WIFI_STA_EV_ASSOC;
				}
			}

			if (sta) {
				fill_assoc_sta_caps(report->frame, frame_len, &sta->caps);
				fill_wifi6_caps(report->frame, frame_len, &sta->wifi6caps);
				fill_reassoc_frame(report->frame, frame_len, sta);
			}
		} else if (report->result == FAILURE) {
			/* Error Code TLV */
			if (tlvs[CLIENT_CAPABILITY_REPORT_ERROR_CODE_IDX][0]) {
				struct tlv_error_code *err =
					(struct tlv_error_code *)tlvs[CLIENT_CAPABILITY_REPORT_ERROR_CODE_IDX][0]->data;

				if (err->reason == 0x02) {
					warn("%s: STA " MACFMT " not associated anymore with the bss\n",
					     __func__, MAC2STR(err->macaddr));
					if (sta) {
						//TODO: remove sta from this bss?
					}
				} else {
					err("%s: Client capability report unspecified failure: 0x%02x\n",
					    __func__, err->reason);
				}
			}

		} else {
			err("%s: Invalid CLIENT CAPABILITY REPORT result: 0x%02x\n",
			    __func__, report->result);
		}
	}

	return 0;
}

static int decollector_handle_topology_notification(struct decollector_private *priv,
						    struct cmdu_buff *resp)
{
	struct tlv *tlv;
	struct tlv_aladdr *tlv_aladdr;
	struct wifi_network_device *dev;
	struct tlv *tlvs[TOPOLOGY_NOTIFICATION_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int ret;


	/* AL-macaddr of origin TLV */
	tlv = cmdu_peek_tlv(resp, TLV_TYPE_AL_MAC_ADDRESS_TYPE);
	if (!tlv || tlv_length(tlv) != sizeof(struct tlv_aladdr))
		return -1;

	tlv_aladdr = (struct tlv_aladdr *)tlv->data;
	dev = find_network_device(priv->dm, tlv_aladdr->macaddr);
	if (!dev) {
		dbg("%s: TOPOLOGY-NOTIFICATION from new/unknown " MACFMT "\n",
			__func__, MAC2STR(tlv_aladdr->macaddr));
		//TODO: add new wifi_network_device
		return 0;
	}

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* CLIENT ASSOCIATION EVENT TLV */
	if (tlvs[TOPOLOGY_NOTIFICATION_CLIENT_ASSOCIATION_EVENT_IDX][0]) {
		struct tlv_client_assoc_event *p = (struct tlv_client_assoc_event *)
			tlvs[TOPOLOGY_NOTIFICATION_CLIENT_ASSOCIATION_EVENT_IDX][0]->data;
		struct wifi_apmld_element *apmld = NULL;
		struct wifi_bss_element *bss = NULL;
		struct wifi_sta_element *sta = NULL;

		bss = find_bss(dev, p->bssid);
		if (!bss) {
			apmld = find_apmld(dev, p->bssid);
			if (!apmld) {
				dbg("Ignore sta-assoc-event with unknown BSS\n");
				return 0;
			}
		}

		if (!!(p->event & CLIENT_EVENT_MASK)) {
			/* Client has joined the BSS */
			sta = find_sta(dev, p->macaddr);
			if (!sta && bss) {
				/* create new sta entry in bss */
				struct wifi_sta_element *n = alloc_sta(p->macaddr);

				if (!n) {
					dbg("%s: error -ENOMEM\n", __func__);
					return -1;
				}

				list_add_tail(&n->list, &bss->stalist);
				bss->num_stations += 1;
				get_timestamp(NULL, n->tsp, sizeof(n->tsp));
				time(&n->last_updated);
				sta = n;
			} else {
				struct wifi_stamld_element *stamld = NULL;

				stamld = find_stamld(dev, p->macaddr);
				if (!stamld && apmld) {
					/* create new stamld */
					struct wifi_stamld_element *n = calloc(1, sizeof(*n));

					if (!n) {
						dbg("%s: -ENOMEM\n", __func__);
						return -1;
					}

					memcpy(n->mld_macaddr, p->macaddr, 6);
					n->is_bsta = false;
					INIT_LIST_HEAD(&n->stalist);
					INIT_LIST_HEAD(&n->ttlmlist);
					list_add_tail(&n->list, &apmld->stamldlist);
					apmld->num_sta += 1;
					stamld = n;
					dbg("%s: create new STA-MLD " MACFMT"\n",
					    __func__, MAC2STR(stamld->mld_macaddr));
				}
			}

			/* add to sta eventlist */
			struct wifi_assoc_event *e = calloc(1, sizeof(*e));
			if (!e) {
				dbg("%s: error -ENOMEM\n", __func__);
				return -1;
			}

			get_timestamp(NULL, e->tsp, sizeof(e->tsp));
			time(&e->tm);
			memcpy(e->macaddr, p->macaddr, 6);
			memcpy(e->bssid, p->bssid, 6);
			list_add_tail(&e->list, &dev->ev.assoclist);
			dev->ev.num_assoc++;
			/* notify assoc event after knowing sta capability from following query */
			e->pending |= WIFI_ASSOC_EVENT_PENDING_CAPS;

			/* Send client capability query */
			ret = prepare_client_cap_query(priv, p->macaddr, p->bssid, resp->origin);
			if (ret) {
				dbg("Error in sending client capability query\n");
				return -1;
			}
		} else {
			struct wifi_disassoc_event *e = NULL;

			/* Client has left the BSS */
			sta = find_sta(dev, p->macaddr);
			if (sta) {
				list_del(&sta->list);
				if (bss)
					bss->num_stations -= 1;
				free_sta(sta);
			} else {
				struct wifi_stamld_element *stamld;

				stamld = find_stamld(dev, p->macaddr);
				if (stamld) {
					list_del(&stamld->list);
					if (apmld)
						apmld->num_sta -= 1;
					free_stamld(stamld);
				}
			}

			/* add to sta eventlist */
			e = calloc(1, sizeof(*e));
			if (!e) {
				dbg("%s: error -ENOMEM\n", __func__);
				return -1;
			}

			get_timestamp(NULL, e->tsp, sizeof(e->tsp));
			time(&e->tm);
			memcpy(e->macaddr, p->macaddr, 6);
			memcpy(e->bssid, p->bssid, 6);
			list_add_tail(&e->list, &dev->ev.disassoclist);
			dev->ev.num_disassoc++;
			/* notify disassoc event after getting the related
			 * Client Disassociation Stats message.
			 */
			e->pending |= WIFI_DISASSOC_EVENT_PENDING_STATS;
		}
	}

	return 0;
}

struct wifi_assoc_event *find_assoc_event(struct list_head *evlist, uint8_t *sta_macaddr, uint8_t *bssid)
{
	struct wifi_assoc_event *e = NULL;

	list_for_each_entry_reverse(e, evlist, list) {
		if (hwaddr_equal(e->macaddr, sta_macaddr) &&
		    hwaddr_equal(e->bssid, bssid)) {
			return e;
		}
	}

	return NULL;
}

struct wifi_disassoc_event *find_dissassoc_event(struct list_head *evlist, uint8_t *sta_macaddr, uint8_t *bssid)
{
	struct wifi_disassoc_event *e = NULL;

	list_for_each_entry_reverse(e, evlist, list) {
		if (hwaddr_equal(e->macaddr, sta_macaddr) &&
		    hwaddr_equal(e->bssid, bssid)) {
			return e;
		}
	}

	return NULL;
}

static int fill_disassoc_stats(struct decollector_private *priv,
			       struct wifi_network_device *dev,
			       struct cmdu_buff *resp)
{
	struct tlv *tlvs[STA_DISASSOC_STATS_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct wifi_apmld_element *apmld = NULL;
	struct tlv_assoc_sta_traffic_stats *t;
	struct wifi_disassoc_event *e = NULL;
	struct wifi_bss_element *bss = NULL;
	struct wifi_sta_element *sta = NULL;
	struct tlv_reason_code *r;
	struct tlv_sta_mac *p;
	uint8_t *bssid = NULL;
#if (EASYMESH_VERSION >= 6)
	int index = 0;
#endif /* (EASYMESH_VERSION >= 6) */

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	p = (struct tlv_sta_mac *)tlvs[STA_DISASSOC_STATS_STA_MAC_ADDR_IDX][0]->data;
	r = (struct tlv_reason_code *)tlvs[STA_DISASSOC_STATS_REASON_CODE_IDX][0]->data;
	t = (struct tlv_assoc_sta_traffic_stats *)
		tlvs[STA_DISASSOC_STATS_ASSOCIATED_STA_TRAFFIC_STATS_IDX][0]->data;

	bss = find_bssid_sta(dev, p->macaddr);
	if (bss) {
		bssid = bss->bssid;
	} else {
		apmld = find_bssid_stamld(dev, p->macaddr);
		if (!apmld) {
			dbg("%s: No bssid found\n", __func__);
			return -1;
		}
		bssid = apmld->mld_macaddr;
	}

	/* add/update event in disassoc eventlist */
	e = find_dissassoc_event(&dev->ev.disassoclist, p->macaddr, bssid);
	if (!e) {
		e = calloc(1, sizeof(*e));
		if (!e) {
			dbg("%s: -ENOMEM\n", __func__);
			return -1;
		}

		time(&e->tm);
		get_timestamp(NULL, e->tsp, sizeof(e->tsp));
		memcpy(e->macaddr, p->macaddr, 6);
		memcpy(e->sta.macaddr, p->macaddr, 6);
		memcpy(e->bssid, bssid, 6);
		e->pending |= WIFI_DISASSOC_EVENT_PENDING_STATS;

		list_add_tail(&e->list, &dev->ev.disassoclist);
		dev->ev.num_disassoc++;
	}

	e->reason_code = r->code;
	e->sta.tx_bytes = BUF_GET_BE32(t->tx_bytes);
	e->sta.rx_bytes = BUF_GET_BE32(t->rx_bytes);
	e->sta.tx_pkts = BUF_GET_BE32(t->tx_packets);
	e->sta.rx_pkts = BUF_GET_BE32(t->rx_packets);
	e->sta.tx_errors = BUF_GET_BE32(t->tx_err_packets);
	e->sta.rx_errors = BUF_GET_BE32(t->rx_err_packets);
	e->sta.rtx_pkts = BUF_GET_BE32(t->rtx_packets);

#if (EASYMESH_VERSION >= 6)
	while (index < TLV_MAXNUM &&
	       tlvs[STA_DISASSOC_STATS_AFFILIATED_STA_METRICS_IDX][index]) {
		struct tlv_affiliated_sta_metrics *v;

		v = (struct tlv_affiliated_sta_metrics *)
			tlvs[STA_DISASSOC_STATS_AFFILIATED_STA_METRICS_IDX][index++]->data;
		memcpy(e->affsta[index].macaddr, v->macaddr, 6);
		e->affsta[index].tx_bytes = v->tx_bytes;
		e->affsta[index].rx_bytes = v->rx_bytes;
		e->affsta[index].tx_packets = v->tx_packets;
		e->affsta[index].rx_packets = v->rx_packets;
		e->affsta[index].tx_errors = v->tx_err_packets;
	}
	e->num_affiliated_sta = index;
#endif /* (EASYMESH_VERSION >= 6) */

	if (!!(e->pending & WIFI_DISASSOC_EVENT_PENDING_STATS)) {
		e->pending &= ~WIFI_DISASSOC_EVENT_PENDING_STATS;
		dev->ev.pending |= WIFI_STA_EV_DISASSOC;
	}

	/* remove sta */
	sta = find_sta(dev, p->macaddr);
	if (sta) {
		/* delete sta entry in bss */
		list_del(&sta->list);
		if (bss)
			bss->num_stations -= 1;
		free_sta(sta);
	} else {
		struct wifi_stamld_element *stamld;

		stamld = find_stamld(dev, p->macaddr);
		if (stamld) {
			list_del(&stamld->list);
			if (apmld)
				apmld->num_sta -= 1;
			free_stamld(stamld);
		}
	}

	return 0;
}

#if (EASYMESH_VERSION >= 4)
static int fill_anticipated_channel_usage_from_tlv(struct decollector_private *priv,
						   struct wifi_network_device *dev,
						   struct cmdu_buff *resp)
{
	struct tlv *tlvs[ANTICIPATED_CHANNEL_USAGE_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int i;

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* Remove previous entries */
	free_anticipated_ch_usagelist(dev);

	i = 0;
	while (i < TLV_MAXNUM &&
			tlvs[ANTICIPATED_CHANNEL_USAGE_ANTICIPATED_CHANNEL_USAGE_IDX][i]) {
		const uint8_t *tlv_data =
			(uint8_t *)tlvs[ANTICIPATED_CHANNEL_USAGE_ANTICIPATED_CHANNEL_USAGE_IDX][i]->data;
		const struct tlv_anticipated_channel_usage *ch_usage =
			(struct tlv_anticipated_channel_usage *)tlv_data;
		struct anticipated_ch_usage *out_ch_usage =
			calloc(1, sizeof(*out_ch_usage));

		if (out_ch_usage) {
			int j;
			uint32_t offset =
				sizeof(ch_usage->opclass) +
				sizeof(ch_usage->channel) +
				sizeof(ch_usage->bssid) +
				sizeof(ch_usage->num_usage);

			list_add_tail(&out_ch_usage->list,
				      &dev->anticipated_channel_usagelist);

			out_ch_usage->op_class = ch_usage->opclass;
			out_ch_usage->channel = ch_usage->channel;
			memcpy(out_ch_usage->ref_bssid, ch_usage->bssid, 6);
			out_ch_usage->num_of_usage_entries  = ch_usage->num_usage;
			INIT_LIST_HEAD(&out_ch_usage->entry_list);

			for (j = 0; j < ch_usage->num_usage; ++j) {

				const struct anticipated_channel_usage_entry *entry =
					(struct anticipated_channel_usage_entry *)(tlv_data + offset);
				struct anticipated_ch_usage_entry *out_entry =
					calloc(1, sizeof(*out_entry));

				if (out_entry) {
					list_add_tail(&out_entry->list,
						      &out_ch_usage->entry_list);

					out_entry->burst_start_time = BUF_GET_BE32(entry->burst_start_time);
					out_entry->burst_length = BUF_GET_BE32(entry->burst_len);
					out_entry->repetitions = BUF_GET_BE32(entry->burst_rep);
					out_entry->burst_interval = BUF_GET_BE32(entry->burst_int);
					out_entry->ru_bitmask_length = entry->bitmask.len;
					memcpy(out_entry->ru_bitmask, entry->bitmask.mask, entry->bitmask.len);
					memcpy(out_entry->transmitter_id, entry->txid, 6);
					out_entry->power_level = entry->pwrlevel;
					out_entry->ch_usage_reason = entry->reason;
				}

				offset += sizeof(entry->burst_start_time) +
					sizeof(entry->burst_len) +
					sizeof(entry->burst_rep) +
					sizeof(entry->burst_int) +
					sizeof(entry->bitmask.len) + entry->bitmask.len +
					sizeof(entry->txid) +
					sizeof(entry->pwrlevel) +
					sizeof(entry->reason) +
					sizeof(entry->rsvd);
			}
		}

		dev->num_anticipated_channel_usage = ++i;
	}

	return 0;
}
#endif /* (EASYMESH_VERSION >= 4) */

static int fill_beacon_metrics_from_tlv(struct decollector_private *priv,
					struct wifi_network_device *dev,
					struct cmdu_buff *resp)
{
	struct tlv *tlvs[BEACON_METRICS_RESP_NUM_OF_TLV_TYPES][TLV_MAXNUM] = { 0 };

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
		    __func__, map_error, map_strerror(map_error));

		return -1;
	}

	/* Beacon Metrics Response TLV*/
	if (tlvs[BEACON_METRICS_RESP_BEACON_METRICS_IDX][0]) {
		struct tlv_beacon_metrics_resp *tlv_metrics = (struct tlv_beacon_metrics_resp *)
			tlvs[BEACON_METRICS_RESP_BEACON_METRICS_IDX][0]->data;
		struct wifi_sta_element *src_sta =
			find_sta(dev, tlv_metrics->sta_macaddr);
		int i;
		int offset;

		if (!src_sta) {
			dbg("%s: Beacon Metrics from unknown STA: " MACFMT "\n",
			    __func__, MAC2STR(tlv_metrics->sta_macaddr));
			return -2;
		}

		/* Remove old entries if any. */
		free_sta_meas_reportlist(src_sta);

		src_sta->num_meas_reports = tlv_metrics->num_element;

		for (i = 0, offset = 0; i < tlv_metrics->num_element; ++i) {
			const uint8_t *tlv_elem = tlv_metrics->element + offset;
			const uint8_t tlv_elem_len = tlv_elem[1] + 2;
			struct wifi_sta_meas_report *meas_rep =
				calloc(1, sizeof(*meas_rep));

			if (!meas_rep)
				return -3;

			/* Only Beacon Measurement Element IDs*/
			if (tlv_elem[0] == 0x27) {
				meas_rep->element_data = calloc(1, tlv_elem_len);
				if (!meas_rep->element_data) {
					free(meas_rep);
					return -4;
				}

				/* For dump purposes measurement report element parsing is not needed. */
				/* It's stored as received from STA. */
				memcpy(meas_rep->element_data, tlv_elem, tlv_elem_len);
				meas_rep->element_len = tlv_elem_len;

				list_add_tail(&meas_rep->list, &src_sta->meas_reportlist);
			}

			offset += tlv_elem_len;
		}
	}

	return 0;
}

#if (EASYMESH_VERSION >= 6)
static int fill_early_ap_capability_from_tlv(struct decollector_private *priv,
					     struct wifi_network_device *dev,
					     struct cmdu_buff *cmdu)
{
	struct tlv *tv[EARLY_AP_CAP_REPORT_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};

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

		return -1;
	}

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

		dev->max_mlds = agnt_caps->max_mldnum;
		dev->max_apmld_links = ((agnt_caps->max_linknum & WIFI7_AP_MAX_LINKNUM_MASK) >> 4);
		dev->max_bstamld_links = agnt_caps->max_linknum & WIFI7_BSTA_MAX_LINKNUM_MASK;
		dev->ttlm_cap = agnt_caps->flag;
		offset += 17;

		for (i = 0; i < agnt_caps->num_radio; i++) {
			struct wifi7_agent_radio_caps *rcaps = (struct wifi7_agent_radio_caps *)
				&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
			struct wifi_radio_element *radio;
			struct wifi7_radio_cap *cap;
			int j;

			radio = find_radio(dev, rcaps->ruid);
			if (radio) {
				radio->invalidate = 0;

				/* reset wifi7 ap/bsta caps */
				memset(&radio->wifi7caps_ap, 0, sizeof(struct wifi_wifi7_capabilities));
				memset(&radio->wifi7caps_bsta, 0, sizeof(struct wifi_wifi7_capabilities));
				radio->wifi7caps_ap_support_nmlo_sta = 0;
			} else {
				/* create new radio entry */
				struct wifi_radio_element *n = alloc_radio(rcaps->ruid);

				if (n) {
					list_add_tail(&n->list, &dev->radiolist);
					dev->num_radios += 1;
					radio = n;
				} else {
					dbg("%s: Error in memory alloc\n", __func__);
					return -1;
				}
			}

			radio->caps.valid |= EHT_CAP_VALID;
			//radio->wifi7caps_ap_support_nmlo_sta = rcaps->sta_non_mlo & WIFI7_AP_STA_NON_MLO_SUPPORTED;	/* removed from EasyMesh-R6 */
			radio->wifi7caps_ap.str = rcaps->ap_mlo & WIFI7_AP_MLO_STR_SUPPORTED;
			radio->wifi7caps_ap.nstr = rcaps->ap_mlo & WIFI7_AP_MLO_NSTR_SUPPORTED;
			radio->wifi7caps_ap.emlsr = rcaps->ap_mlo & WIFI7_AP_MLO_EMLSR_SUPPORTED;
			radio->wifi7caps_ap.emlmr = rcaps->ap_mlo & WIFI7_AP_MLO_EMLMR_SUPPORTED;
			radio->wifi7caps_bsta.str = rcaps->bsta_mlo & WIFI7_BSTA_MLO_STR_SUPPORTED;
			radio->wifi7caps_bsta.nstr = rcaps->bsta_mlo & WIFI7_BSTA_MLO_NSTR_SUPPORTED;
			radio->wifi7caps_bsta.emlsr = rcaps->bsta_mlo & WIFI7_BSTA_MLO_EMLSR_SUPPORTED;
			radio->wifi7caps_bsta.emlmr = rcaps->bsta_mlo & WIFI7_BSTA_MLO_EMLMR_SUPPORTED;
			offset += 32;

			/* AP STR mode freq separation restrictions */
			cap = (struct wifi7_radio_cap *)
				&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
			radio->wifi7caps_ap.num_freqsep_str = cap->num;
			offset++;
			for (j = 0; j < cap->num; j++) {
				struct wifi7_radio_cap_record *record = (struct wifi7_radio_cap_record *)
					&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];

				memcpy(radio->wifi7caps_ap.str_freqsep[j].ruid, record->ruid, 6);
				radio->wifi7caps_ap.str_freqsep[j].sep = (record->freqsep & WIFI7_RADIO_FREQSEP_MASK);
				offset += 7;
			}

			/* AP EMLSR mode freq separation restrictions */
			cap = (struct wifi7_radio_cap *)
				&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
			radio->wifi7caps_ap.num_freqsep_emlsr = cap->num;
			offset++;
			for (j = 0; j < cap->num; j++) {
				struct wifi7_radio_cap_record *record = (struct wifi7_radio_cap_record *)
					&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];

				memcpy(radio->wifi7caps_ap.emlsr_freqsep[j].ruid, record->ruid, 6);
				radio->wifi7caps_ap.emlsr_freqsep[j].sep = (record->freqsep & WIFI7_RADIO_FREQSEP_MASK);
				offset += 7;
			}

			/* AP EMLMR mode freq separation restrictions */
			cap = (struct wifi7_radio_cap *)
				&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
			radio->wifi7caps_ap.num_freqsep_emlmr = cap->num;
			offset++;
			for (j = 0; j < cap->num; j++) {
				struct wifi7_radio_cap_record *record = (struct wifi7_radio_cap_record *)
					&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];

				memcpy(radio->wifi7caps_ap.emlmr_freqsep[j].ruid, record->ruid, 6);
				radio->wifi7caps_ap.emlmr_freqsep[j].sep = (record->freqsep & WIFI7_RADIO_FREQSEP_MASK);
				offset += 7;
			}

			/* bSTA STR mode freq separation restrictions */
			cap = (struct wifi7_radio_cap *)
				&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
			radio->wifi7caps_bsta.num_freqsep_str = cap->num;
			offset++;
			for (j = 0; j < cap->num; j++) {
				struct wifi7_radio_cap_record *record = (struct wifi7_radio_cap_record *)
					&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];

				memcpy(radio->wifi7caps_bsta.str_freqsep[j].ruid, record->ruid, 6);
				radio->wifi7caps_bsta.str_freqsep[j].sep = (record->freqsep & WIFI7_RADIO_FREQSEP_MASK);
				offset += 7;
			}

			/* bSTA EMLSR mode freq separation restrictions */
			cap = (struct wifi7_radio_cap *)
				&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
			radio->wifi7caps_bsta.num_freqsep_emlsr = cap->num;
			offset++;
			for (j = 0; j < cap->num; j++) {
				struct wifi7_radio_cap_record *record = (struct wifi7_radio_cap_record *)
					&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];

				memcpy(radio->wifi7caps_bsta.emlsr_freqsep[j].ruid, record->ruid, 6);
				radio->wifi7caps_bsta.emlsr_freqsep[j].sep = (record->freqsep & WIFI7_RADIO_FREQSEP_MASK);
				offset += 7;
			}

			/* bSTA EMLMR mode freq separation restrictions */
			cap = (struct wifi7_radio_cap *)
				&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];
			radio->wifi7caps_bsta.num_freqsep_emlmr = cap->num;
			offset++;
			for (j = 0; j < cap->num; j++) {
				struct wifi7_radio_cap_record *record = (struct wifi7_radio_cap_record *)
					&tv[EARLY_AP_CAP_REPORT_WIFI7_AGENT_CAPS_IDX][0]->data[offset];

				memcpy(radio->wifi7caps_bsta.emlmr_freqsep[j].ruid, record->ruid, 6);
				radio->wifi7caps_bsta.emlmr_freqsep[j].sep = (record->freqsep & WIFI7_RADIO_FREQSEP_MASK);
				offset += 7;
			}
		}
	}

	return 0;
}
#endif /* (EASYMESH_VERSION >= 6) */

static int fill_ap_autoconfiguration_wsc_from_tlv(struct decollector_private *priv,
						   struct wifi_network_device *dev,
						   struct cmdu_buff *resp)
{
	struct tlv *tlvs[AP_AUTOCONFIGURATION_WSC_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	struct tlv *wsc_tlv;
	uint8_t wsc_mtype = 0;

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

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

	if (!map_cmdu_validate_parse(resp, tlvs, ARRAY_SIZE(tlvs), dev->map_profile)) {
		dbg("%s: map_cmdu_validate_parse failed, err = (%d) '%s'\n",
				__func__, map_error, map_strerror(map_error));

		return -1;
	}

	if (tlvs[AP_AUTOCONFIGURATION_WSC_WSC_IDX][0]) {
		uint8_t *data = tlvs[AP_AUTOCONFIGURATION_WSC_WSC_IDX][0]->data;
		uint16_t data_len = tlv_length(tlvs[AP_AUTOCONFIGURATION_WSC_WSC_IDX][0]);
		uint8_t attr_buf[256] = {0};
		uint16_t attr_len = 0;
		int ret;

		/* Manufacturer */
		ret = wsc_msg_get_attr(data, data_len, ATTR_MANUFACTURER, attr_buf, &attr_len);
		if (!ret && attr_len <= sizeof(dev->manufacturer) - 1) {
			memcpy(dev->manufacturer, attr_buf, attr_len);
			dev->manufacturer[attr_len] = '\0';
		} else {
			dbg("Error getting Manufacturer from WSC M1\n");
		}

		/* Model Name */
		ret = wsc_msg_get_attr(data, data_len, ATTR_MODEL_NAME, attr_buf, &attr_len);
		if (!ret && attr_len <= sizeof(dev->model) - 1) {
			memcpy(dev->model, attr_buf, attr_len);
			dev->model[attr_len] = '\0';
		} else {
			dbg("Error getting Model Name from WSC M1\n");
		}
	}

	return 0;
}

#if (EASYMESH_VERSION >= 3)
static int fill_service_prioritization_request_from_tlv(struct decollector_private *priv,
					     struct wifi_network_device *dev,
					     struct cmdu_buff *cmdu)
{
	struct tlv *tlvs[SERVICE_PRIORITIZATION_REQUEST_NUM_OF_TLV_TYPES][TLV_MAXNUM] = {0};
	int index = 0;

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

		return -1;
	}

	/* Service Prioritization Rule TLV */
	while (index < TLV_MAXNUM &&
			tlvs[SERVICE_PRIORITIZATION_REQUEST_SERVICE_PRIORITIZATION_RULE_IDX][index]) {
		struct tlv_spr *spr =
				(struct tlv_spr *)tlvs[SERVICE_PRIORITIZATION_REQUEST_SERVICE_PRIORITIZATION_RULE_IDX][index++]->data;
		struct wifi_sp_rule *sp_rule;

		spr->rule_id = BUF_GET_BE32(spr->rule_id);

		if (spr->rule_flags & SPR_FLAG_ADD_REMOVE) { /* Add rule */
			dbg("%s: adding DSCP rule...\n", __func__);
			sp_rule = find_sp_rule(dev, spr->rule_id);
			if (sp_rule) {
				dbg("%s: DSCP rule exists\n", __func__);
				continue;
			}

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

			list_add(&sp_rule->list, &dev->sp_rulelist);
			sp_rule->id = spr->rule_id;
			sp_rule->priority = spr->precedence;
			sp_rule->output = spr->output;
			sp_rule->always_match = !!(spr->match_flags & SPR_FLAG_ALWAYS_MATCH);
			dev->num_sprules++;
		} else { /* Remove rule */
			dbg("%s: removing DSCP rule...\n", __func__);
			sp_rule = find_sp_rule(dev, spr->rule_id);
			if (!sp_rule) {
				dbg("%s: rule is not found\n", __func__);
				continue;
			}

			if (sp_rule->output == 0x08) {
				// Reset DSCP Mapping
				memset(&dev->dscp_mapping, 0, sizeof(dev->dscp_mapping));
			}

			list_del(&sp_rule->list);
			free(sp_rule);
			dev->num_sprules--;
		}
	}

	/* DSCP Mapping Table TLV */
	if (tlvs[SERVICE_PRIORITIZATION_REQUEST_DSCP_MAPPING_TABLE_IDX][0]) {
		struct tlv_dscp_pcp *dscp_pcp =
				(struct tlv_dscp_pcp *)tlvs[SERVICE_PRIORITIZATION_REQUEST_DSCP_MAPPING_TABLE_IDX][0]->data;

		memcpy(&dev->dscp_mapping, dscp_pcp, sizeof(struct tlv_dscp_pcp));
	}

	return 0;
}
#endif /* (EASYMESH_VERSION >= 3) */

void remove_newline(char *buf)
{
	int len = strlen(buf) - 1;

	if (buf[len] == '\n')
		buf[len] = 0;
}

void decollector_read_dhcpv4_lease_table(struct decollector_private *p,
		void **clients, int *num_clients)
{
	struct dhcp_clients {
		uint8_t macaddr[6];
		char ipv4[24];
		char hostname[256];
	} *hosts = NULL, *tmp = NULL, *ptr = NULL;
	FILE *lease = NULL;
	char line[256];
	int cnt = 0;

	lease = fopen("/var/dhcp.leases", "r");
	if (!lease)
		return;

	while (fgets(line, sizeof(line), lease) != NULL) {
		long leasetime;
		char macaddr[18] = {0};
		char ipaddr[24] = {0};
		char hostname[256] = {0};
		char clid[256] = {0};

		remove_newline(line);
		if (sscanf(line, "%ld %17s %23s %255s %255s", &leasetime,
					macaddr, ipaddr, hostname, clid) == 5) {

			ptr = realloc(hosts, (cnt + 1) *
					sizeof(struct dhcp_clients));
			if (!ptr) {
				if (hosts)
					free(hosts);
				*num_clients = 0;
				goto out;
			}

			hosts = ptr;
			tmp = hosts + cnt;
			memset(tmp, 0, sizeof(struct dhcp_clients));
			hwaddr_aton(macaddr, tmp->macaddr);
			strncpy(tmp->ipv4, ipaddr, sizeof(ipaddr)-1);
			strncpy(tmp->hostname, hostname, sizeof(hostname)-1);
			cnt += 1;
		}
	}

	*clients = hosts;
	*num_clients = cnt;
out:
	fclose(lease);
}

void decollector_get_hostname_ipv4_address(struct wifi_sta_element *sta,
		void *clients, int num_clients)
{
	int i;
	struct dhcp_clients {
		uint8_t macaddr[6];
		char ipv4[24];
		char hostname[256];
	} *hosts = NULL, *tmp = NULL;

	if (!num_clients || !clients)
		return;

	hosts = (struct dhcp_clients *)clients;
	for (i = 0; i < num_clients; i++) {
		tmp = hosts + i;

		if (hwaddr_equal(tmp->macaddr, sta->macaddr)) {
			strncpy(sta->hostname, tmp->hostname,
					sizeof(sta->hostname) - 1);
			if (inet_pton(AF_INET, tmp->ipv4, &sta->ipv4_addr.addr.ip4) == 1)
                                sta->ipv4_addr.family = AF_INET;
			return;
		}
	}
}

/* Check the assoclist on device,
 * send the client capability query for each STA.
 */
int decollector_send_client_cap_query(struct decollector_private *priv,
				       struct wifi_network_device *dev)
{
	struct wifi_radio_element *radio;
	struct wifi_bss_element *bss;
	struct wifi_sta_element *sta;
	int ret = 0;
	void *clients = NULL;
	int num_clients = 0;

	decollector_read_dhcpv4_lease_table(priv, &clients, &num_clients);

	list_for_each_entry(radio, &dev->radiolist, list) {
		list_for_each_entry(bss, &radio->bsslist, list) {
			list_for_each_entry(sta, &bss->stalist, list) {
				dbg("send client capability query to: " MACFMT
				    "\n\tsta: " MACFMT "\n\tbssid: " MACFMT "\n",
				    MAC2STR(dev->macaddr),
				    MAC2STR(sta->macaddr),
				    MAC2STR(bss->bssid));

				/* query sta capability */
				ret = prepare_client_cap_query(priv,
								sta->macaddr,
								bss->bssid,
								dev->macaddr);

				/* fill sta hostname */
				decollector_get_hostname_ipv4_address(sta, clients, num_clients);
			}
		}
	}

	if (clients)
		free(clients);

	clients = NULL;

	return ret;
}

int prepare_1905_query(struct decollector_private *priv, uint16_t cmdu_type,
		       struct wifi_network_device *dev, uint8_t *dst)
{
	//uint32_t resp_timeout = 2; /* in seconds */
	//struct getter_context *gt = dev->priv;
	struct wifi_radio_element *radio;
	struct wifi_bss_element *bss;
	struct cmdu_buff *req;
	uint16_t mid = 0;
	int ret = 0;


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

	dbg("Send %s (0x%04x) ---> " MACFMT "\n", map_cmdu_type2str(cmdu_type),
	    cmdu_type, MAC2STR(dst));

	switch (cmdu_type) {
	case CMDU_TYPE_TOPOLOGY_QUERY:
		{
			/* append Mutli-AP Profile TLV */
			ret = decollector_gen_map_profile(priv, req,
							  priv->opts.em_profile);
			if (ret)
				goto error;

			break;
		}

	case CMDU_AP_CAPABILITY_QUERY:
			break;

	case CMDU_AP_METRICS_QUERY:
		{
			uint8_t *bsslist = NULL;
			int total_bss = 0;
			int c_index = 0;

			list_for_each_entry(radio, &dev->radiolist, list) {
				if (dev->map_profile > MULTIAP_PROFILE_2) {
					/* Radio Identifier TLV */
					ret = decollector_gen_ap_radio_identifier(priv, req, radio->macaddr);
					if (ret)
						goto error;
				}

				total_bss += radio->num_bss;
			}

			if (total_bss <= 0)
				break;

			bsslist = calloc(total_bss, 6 * sizeof(uint8_t));
			if (!bsslist) {
				dbg("%s:%d -ENOMEM\n", __func__, __LINE__);
				goto error;
			}

			list_for_each_entry(radio, &dev->radiolist, list) {
				list_for_each_entry(bss, &radio->bsslist, list) {
					memcpy(&bsslist[c_index * 6], bss->bssid, 6);
					c_index++;
				}
			}

			/* AP Metrics TLV */
			ret = decollector_gen_ap_metric_query(priv, req, total_bss, bsslist);
			free(bsslist);
			if (ret) {
				dbg("%s:%d error gen_ap_metric query\n", __func__, __LINE__);
				goto error;
			}

			break;
		}

	case CMDU_BACKHAUL_STA_CAPABILITY_QUERY:
		{
			//const uint8_t *parent_al_mac =
			//	dev->multi_ap_device.backhaul.upstream_device_macaddr;

			break;
		}
	case CMDU_TYPE_LINK_METRIC_QUERY:
		{
			const uint8_t *parent_al_mac =
				dev->multi_ap_device.backhaul.upstream_device_macaddr;

			if (!hwaddr_is_zero(parent_al_mac)) {
				ret = decollector_gen_link_metric_query(req, parent_al_mac);
			} /* else {
				dbg("%s:%d error bh link metric query\n", __func__, __LINE__);
				ret = -1;
				decollector_sched_getter(priv, dev, 200);
				goto error;
			} */

			break;
		}

	case CMDU_CLIENT_CAPABILITY_QUERY:
		ret = decollector_send_client_cap_query(priv, dev);
		//resp_timeout = 3;
		break;

	case CMDU_UNASSOC_STA_LINK_METRIC_QUERY:
		if (!!(dev->multiap_caps & UNASSOC_STA_REPORTING_ONCHAN) ||
		    !!(dev->multiap_caps & UNASSOC_STA_REPORTING_OFFCHAN)) {

			// TODO: create query
		}
		break;
	default:
		dbg("%s: Unhandled cmdu 0x%04x\n", __func__, cmdu_type);
		goto error;
	}

	cmdu_put_eom(req);
	ret = decollector_send_cmdu_request(priv, req, dst);

	/* Caller does this
	if (!ret) {
		gt->retry = 0;
		gt->request = decollector_next_request(gt);
		gt->tmo = resp_timeout * 1000;
		//gt->last_sent_mid = mid;
		timer_set(&gt->t, gt->tmo);
	}
	*/

error:
	cmdu_free(req);
	return ret;
}

void decollector_update_dm_timestamps(struct wifi_data_element *dm,
				      struct wifi_network_device *dev)
{
	get_timestamp(NULL, dm->network.tsp, sizeof(dm->network.tsp));

	strncpy(&dev->multi_ap_device.last_contacttime[0], &dm->network.tsp[0],
		sizeof(timestamp_t));
}

int decollector_handle_map_response(struct decollector_private *p, struct cmdu_buff *resp)
{
	uint16_t type = cmdu_get_type(resp) & 0xffff;
	struct wifi_network_device *dev = NULL;
	struct getter_context *gt;
	int ret = 0;

	dev = decollector_get_origin_dev(p, p->dm, resp->origin);
	if (!dev) {
		dbg("Ignore %s from unknown src " MACFMT "\n",
		    map_cmdu_type2str(type), MAC2STR(resp->origin));
		return -1;
	}

	dbg("%s: dev = %p\n", __func__, dev);
	gt = dev->priv;

	if (!gt) {
		warn("%s: Unexpectedly getter context is NULL!\n",
		     __func__);
		return -1;
	}

	switch (type) {
	case CMDU_TYPE_TOPOLOGY_RESPONSE:
		if (is_topology_resp_from_easy_mesh_device(resp)) {
			ret = fill_topology_resp_from_tlv(p, resp, dev);
			if (!ret) {
				getter_clear_expect(gt, type);

				/* update colocated agent id */
				if (dev->multi_ap_device.agent_opmode == SUPPORTED &&
				    (!memcmp(dev->macaddr, p->dm->network.cntlr_id, 6) ||
				     !strncmp(resp->dev_ifname, "lo", 2))) {
					memcpy(p->dm->network.coagent_id, dev->macaddr, 6);
				}

				if (network_device_has_bss(dev))
					getter_set_applicability(gt, CMDU_AP_METRICS_RESPONSE, MANDATORY);

				if (network_device_has_stations(dev))
					getter_set_applicability(gt, CMDU_CLIENT_CAPABILITY_REPORT, MANDATORY);

				if (dev->multi_ap_device.controller_opmode == NOT_SUPPORTED)
					getter_set_applicability(gt, CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE, OPTIONAL);

				build_bh_topology(p);
			}
		} else {
			list_del(&dev->list);
			decollector_free_getter(dev);
			free_network_device(dev);
			p->dm->network.num_devices--;
			goto out;
		}
		break;

	case CMDU_AP_CAPABILITY_REPORT:
		ret = fill_ap_capability_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_AP_METRICS_RESPONSE:
		ret = fill_ap_metrics_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_BACKHAUL_STA_CAPABILITY_REPORT:
		ret = fill_bsta_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_TYPE_LINK_METRIC_RESPONSE:
		ret = fill_bh_link_metric_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE:
		ret = fill_unassocsta_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE:
		ret = fill_ap_autoconfig_response_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_OPERATING_CHANNEL_REPORT:
		ret = fill_operating_channel_report_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_CHANNEL_SCAN_REPORT:
		ret = fill_scanres_channel_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_CHANNEL_PREFERENCE_REPORT:
		ret = fill_channel_pref_from_tlv(p, resp, dev);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

	case CMDU_CLIENT_CAPABILITY_REPORT:
		ret = fill_client_capability_from_tlv(p, dev, resp);
		if (!ret) {
			getter_clear_expect(gt, type);
			decollector_notify_events(p, dev);
		}
		break;

	case CMDU_CLIENT_DISASSOCIATION_STATS:
		ret = fill_disassoc_stats(p, dev, resp);
		if (!ret)
			decollector_notify_events(p, dev);
		break;

	case CMDU_TYPE_TOPOLOGY_NOTIFICATION:
		ret = decollector_handle_topology_notification(p, resp);
		if (!ret)
			decollector_notify_events(p, dev);	//FIXME
		break;

#if (EASYMESH_VERSION >= 4)
	case CMDU_ANTICIPATED_CHANNEL_USAGE:
		ret = fill_anticipated_channel_usage_from_tlv(p, dev, resp);
		if (!ret)
			getter_clear_expect(gt, type);
		break;
#endif

	case CMDU_BEACON_METRICS_RESPONSE:
		ret = fill_beacon_metrics_from_tlv(p, dev, resp);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

#if (EASYMESH_VERSION >= 6)
	case CMDU_EARLY_AP_CAPABILITY_REPORT:
		ret = fill_early_ap_capability_from_tlv(p, dev, resp);
		if (!ret)
			getter_clear_expect(gt, type);
		break;
#endif

	case CMDU_TYPE_AP_AUTOCONFIGURATION_WSC:
		ret = fill_ap_autoconfiguration_wsc_from_tlv(p, dev, resp);
		if (!ret)
			getter_clear_expect(gt, type);
		break;

#if (EASYMESH_VERSION >= 3)
	case CMDU_SERVICE_PRIORITIZATION_REQUEST:
		ret = fill_service_prioritization_request_from_tlv(p, dev, resp);
		if (!ret)
			getter_clear_expect(gt, type);
		break;
#endif

	default:
		dbg("Unhandled CMDU: %s\n", map_cmdu_type2str(type));
		goto out;
	}

	if (!ret) {
		decollector_update_dm_timestamps(p->dm, dev);
		/* Move to the next request if the expected cmdu response for
		 * the last request is received. Else, allow the getter timer
		 * to expire naturally.
		 */
		if (gt->txn[gt->txni].cnt > 0) {
			decollector_sched_getter(p, dev, 200);
		}
	} else {
		getter_update_error(gt, type);
	}

out:
	return ret;
}

int prepare_autoconfig_search(struct decollector_private *priv, uint8_t band)
{
	struct cmdu_buff *req;
	uint16_t mid = 0;
	int ret;

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

	CMDU_SET_RELAY_MCAST(req->cdata);

	/* 1905.1 AL MAC address type TLV */
	ret = decollector_gen_aladdr(priv, req);
	if (ret)
		goto error;

	/* SearchedRole TLV */
	ret = decollector_gen_searched_role(priv, req,
			IEEE80211_ROLE_REGISTRAR);
	if (ret)
		goto error;

	/* AutoconfigFreqBand TLV */
	ret = decollector_gen_autoconfig_band(priv, req, band);
	if (ret)
		goto error;

	/* SearchedService TLV */
	ret = decollector_gen_searched_service(priv, req,
			SEARCHED_SERVICE_MULTIAP_CONTROLLER);
	if (ret)
		goto error;

	/* Multi-AP Profile TLV */
	ret = decollector_gen_map_profile(priv, req, priv->opts.em_profile);
	if (ret)
		goto error;

	decollector_send_cmdu_request(priv, req, MCAST_1905);
	cmdu_free(req);

	return 0;
error:
	cmdu_free(req);

	return -1;
}

/* Entry point for collecting a node's wifi dataelements */
int decollector_collect_node(struct decollector_private *p,
			     struct wifi_network_device *dev)
{
	/* reset dev states */
	decollector_reset_getter(p, dev);

	return decollector_sched_getter(p, dev, 100);
}

int decollector_handle_map_event(struct decollector_private *p, struct cmdu_buff *cmdu)
{
	uint16_t type = cmdu_get_type(cmdu) & 0xffff;
	int ret = 0;

	info(MACFMT ": Recv %s\n", MAC2STR(cmdu->origin), map_cmdu_type2str(type));

	switch (type) {
	case CMDU_TYPE_TOPOLOGY_RESPONSE:
	case CMDU_AP_CAPABILITY_REPORT:
	case CMDU_CHANNEL_PREFERENCE_REPORT:
	case CMDU_AP_METRICS_RESPONSE:
	case CMDU_BACKHAUL_STA_CAPABILITY_REPORT:
	case CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE:
	case CMDU_TYPE_LINK_METRIC_RESPONSE:
	case CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE:
	case CMDU_OPERATING_CHANNEL_REPORT:
	case CMDU_CHANNEL_SCAN_REPORT:
	case CMDU_TYPE_TOPOLOGY_NOTIFICATION:
	case CMDU_CLIENT_DISASSOCIATION_STATS:
	case CMDU_CLIENT_CAPABILITY_REPORT:
#if (EASYMESH_VERSION >= 4)
	case CMDU_ANTICIPATED_CHANNEL_USAGE:
#endif
	case CMDU_BEACON_METRICS_RESPONSE:
#if (EASYMESH_VERSION >= 6)
	case CMDU_EARLY_AP_CAPABILITY_REPORT:
#endif
	case CMDU_TYPE_AP_AUTOCONFIGURATION_WSC:
#if (EASYMESH_VERSION >= 3)
	case CMDU_SERVICE_PRIORITIZATION_REQUEST:
#endif
		ret = decollector_handle_map_response(p, cmdu);
		break;

	default:
		break;
	}

	return ret;
}

