/*
 * config.c - controller configuration handling
 *
 * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */

#include "config.h"

#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
#include <dpp_api.h>
#include <dpputils.h>
#include "dpp.h"
#endif
#endif
#include <libubox/list.h>
#include <easy/easy.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/random.h>
#include <uci.h>
#include <wifidefs.h>

#include "cntlr.h"
#include "config_helpers.h"
#include "easymesh.h"
#include "i1905_wsc.h"
#include "stdbool.h"
#include "steer_module.h"
#include "utils/debug.h"
#include "utils/utils.h"
#include "cntlr_extension.h"
#include "cntlr_plugin.h"
#include "cntlr_cmdu.h"
#include "acs.h"
#include "wifi_opclass.h"
#include "assoc_ctrl.h"

static int clean_agentlist(struct node_policy *p)
{
	return 0;
}

struct node_policy *cntlr_get_node_policy(struct controller_config *cfg, uint8_t *agent)
{
	struct node_policy *a = NULL;

	list_for_each_entry(a, &cfg->nodelist, list) {
		if (!memcmp(agent, a->agent_id, 6))
			return a;
	}

	return NULL;
}

struct radio_policy *cntlr_get_radio_policy(struct controller_config *cfg, uint8_t *radio_mac)
{
	struct node_policy *a = NULL;

	list_for_each_entry(a, &cfg->nodelist, list) {
		struct radio_policy *r = NULL;

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


	return NULL;
}

#if (EASYMESH_VERSION > 2)
struct dpp_controller_cfg *get_dpp_controller_by_band(struct controller_config *c,
		enum wifi_band band)
{
	struct dpp_controller_cfg *p = NULL;

	list_for_each_entry(p, &c->dpp_cntlrlist, list) {
		if (band == p->band)
			return p;
	}

	return NULL;
}

/* create ap config and initialize with default values */
struct dpp_controller_cfg *create_dpp_controller_config(struct controller_config *cfg,
							enum wifi_band band)
{
	struct dpp_controller_cfg *new;

	if (!cfg)
		return NULL;

	new = calloc(1, sizeof(struct dpp_controller_cfg));
	if (!new) {
		err("%s: OOM! config\n", __func__);
		return NULL;
	}
	new->band = band;

	if (new->band == BAND_2)
		new->port = 8902;
	else if (new->band == BAND_5)
		new->port = 8905;
#if 0 /* TOOD: BAND_6 not yet supported */
	else if (new->band == BAND_6)
		new->port = 8906;
#endif
	else
		new->port = 8910; /* default */

	dbg("%s: %s dpp_controller->cfg = %p\n", __func__, new->ifname, new);

	list_add(&new->list, &cfg->dpp_cntlrlist);
	return new;
}
#endif

static int clean_steer_btm_excl(struct node_policy *p)
{
	struct stax *n = NULL, *tmp;

	list_for_each_entry_safe(n, tmp, &p->btmsteer_exlist, list) {
		list_del(&n->list);
		free(n);
	}

	return 0;
}

static int clean_btm_exclude_list(struct controller_config *cfg)
{
	struct stax *btex = NULL, *tmp;

	list_for_each_entry_safe(btex, tmp, &cfg->btmsteer_exlist, list) {
		list_del(&btex->list);
		free(btex);
	}

	cfg->num_btmsteer_excl_stas = 0;

	return 0;
}

static struct stax *stax_add_entry(struct list_head *h, char *sta_macstr)
{
	struct stax *n;
	uint8_t macaddr[6];

	if (!hwaddr_aton(sta_macstr, macaddr)) {
		cntlr_err(LOG_MISC, "%s: Invalid MAC address format: %s\n",
			  __func__, sta_macstr);
		return NULL;
	}

	n = calloc(1, sizeof(struct stax));
	if (n) {
		memcpy(n->macaddr, macaddr, 6);
		list_add(&n->list, h);
	}
	return n;
}

/* Add missing global BTM exclusions to agent's local list */
static bool sync_agent_btm_exclusions(struct controller_config *cfg,
				       struct node_policy *agent_policy)
{
	struct stax *btex = NULL, *new_btex;
	bool changed = false;

	/* Add all current global exclusions to agent's local list if not already present */
	list_for_each_entry(btex, &cfg->btmsteer_exlist, list) {
		struct stax *agent_btex = NULL;
		bool found = false;
		char macstr[18];

		/* Check if agent already has this global exclusion locally */
		list_for_each_entry(agent_btex, &agent_policy->btmsteer_exlist, list) {
			if (memcmp(btex->macaddr, agent_btex->macaddr, 6) == 0) {
				found = true;
				break;
			}
		}

		if (found)
			continue;

		/* Add global exclusion to agent's local list */
		hwaddr_ntoa(btex->macaddr, macstr);
		new_btex = stax_add_entry(&agent_policy->btmsteer_exlist, macstr);
		if (!new_btex)
			continue;

		agent_policy->num_btmsteer_stas++;
		changed = true;
		cntlr_dbg(LOG_STEER,
			  "%s: Added global BTM exclusion %s to agent policy\n",
			  __func__, macstr);
	}

	/* Note: We never remove local exclusions */
	return changed;
}

static int clean_steer_excl(struct node_policy *p)
{
	struct stax *n = NULL, *tmp;

	list_for_each_entry_safe(n, tmp, &p->steer_exlist, list) {
		list_del(&n->list);
		free(n);
	}

	return 0;
}

/* Check if STA MAC address is in controller BTM exclusion list */
bool is_sta_in_controller_btm_exclude(struct controller *c, uint8_t *sta_mac)
{
	struct stax *btex = NULL;

	if (!c || !sta_mac)
		return false;

	list_for_each_entry(btex, &c->cfg.btmsteer_exlist, list) {
		if (memcmp(btex->macaddr, sta_mac, 6) == 0) {
			return true;
		}
	}
	return false;
}


int clean_radio_list(struct list_head *radiolist)
{
	struct radio_policy *p = NULL, *tmp;

	list_for_each_entry_safe(p, tmp, radiolist, list) {
		list_del(&p->list);
		free(p);
	}

	return 0;
}

#if (EASYMESH_VERSION >= 6)
int clean_mld_list(struct list_head *mldlist)
{
	struct mld_credential *p = NULL, *tmp;

	list_for_each_entry_safe(p, tmp, mldlist, list) {
		list_del(&p->list);
		free(p);
	}

	return 0;
}

struct mld_credential *cntlr_config_get_mld_by_id(struct controller_config *cfg, uint8_t id)
{
	struct mld_credential *p = NULL;

	list_for_each_entry(p, &cfg->mldlist, list) {
		if (p->id == id)
			return p;
	}

	return NULL;
}

struct mld_credential *cntlr_config_get_mld_by_ssid(struct controller_config *cfg, char *ssid)
{
	struct mld_credential *p = NULL;

	list_for_each_entry(p, &cfg->mldlist, list) {
		if (!memcmp(p->cred.ssid, ssid, sizeof(p->cred.ssid)))
			return p;
	}

	return NULL;
}
#endif

int clean_agent_policies(struct controller_config *cfg)
{
	struct node_policy *p = NULL, *tmp;

	list_for_each_entry_safe(p, tmp, &cfg->nodelist, list) {
		clean_steer_btm_excl(p);
		clean_steer_excl(p);
		clean_agentlist(p);
		clean_radio_list(&p->radiolist);
		list_del(&p->list);
		free(p);
	}

	return 0;
}

int clean_vendor_ie(struct wsc_vendor_ie *ext)
{
	free(ext->payload);
	return 0;
}

int clean_vendor_ies(struct iface_credential *iface_cred)
{
	int i;

	for (i = 0; i < iface_cred->num_ven_ies; i++)
		clean_vendor_ie(&iface_cred->ven_ies[i]);

	iface_cred->num_ven_ies = 0;

	return 0;
}

int clean_cred_list(struct controller_config *cfg)
{
	struct iface_credential *p = NULL, *tmp;

	list_for_each_entry_safe(p, tmp, &cfg->aplist, list) {
		clean_vendor_ies(p);
		list_del(&p->list);
		free(p);
	}

	return 0;
}

int clean_plugins(struct controller_config *cfg)
{
	struct cntlr_plugin_name *p = NULL, *tmp;

	list_for_each_entry_safe(p, tmp, &cfg->steerplugins, list) {
		list_del(&p->list);
		free(p);
	}

	list_for_each_entry_safe(p, tmp, &cfg->plugins, list) {
		list_del(&p->list);
		free(p);
	}

	return 0;
}


struct uci_package *uci_load_pkg(struct uci_context **ctx, const char *config)
{
	struct uci_package *pkg;

	if (!*ctx) {
		*ctx = uci_alloc_context();
		if (!*ctx)
			return NULL;
	}

	if (uci_load(*ctx, config, &pkg) != UCI_OK) {
		uci_free_context(*ctx);
		*ctx = NULL;
		return NULL;
	}

	return pkg;
}

int set_value(struct uci_context *ctx, struct uci_package *pkg,
		struct uci_section *section, const char *key,
		const char *value, enum uci_option_type type)
{
	struct uci_ptr ptr = {0};

	ptr.p = pkg;
	ptr.s = section;
	ptr.option = key;
	ptr.value = value;

	if (type == UCI_TYPE_STRING)
		return uci_set(ctx, &ptr);

	if (type == UCI_TYPE_LIST)
		return uci_add_list(ctx, &ptr);

	return -1;
}


int set_value_by_string(const char *package, const char *section,
		const char *key, const char *value, enum uci_option_type type)
{
	struct uci_ptr ptr = {0};
	struct uci_context *ctx;
	int rv = -1;

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

	ptr.package = package;
	ptr.section = section;
	ptr.option = key;
	ptr.value = value;

	if (type == UCI_TYPE_STRING)
		rv = uci_set(ctx, &ptr);

	if (type == UCI_TYPE_LIST)
		rv = uci_add_list(ctx, &ptr);

	uci_commit(ctx, &ptr.p, false);

	uci_free_context(ctx);
	return rv;
}

bool uci_set_option(char *package_name, char *section_type,
		char *search_key, char *search_val,
		char *option, char *value)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;

	if (!package_name || !search_val || !option || !value)
		return false;

	ctx = uci_alloc_context();
	if (!ctx)
		return false;

	if (uci_load(ctx, package_name, &pkg)) {
		uci_free_context(ctx);
		return false;
	}

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

		if (!strcmp(s->type, section_type)) {
			struct uci_option *opt = uci_lookup_option(ctx, s,
					search_key);

			if (!opt || opt->type != UCI_TYPE_STRING)
				continue;
			if (strcmp(opt->v.string, search_val) == 0) {
				struct uci_ptr ptr = {0};

				ptr.value = value;
				ptr.package = package_name;
				ptr.section = s->e.name;
				ptr.option = option;
				ptr.target = UCI_TYPE_OPTION;
				if (uci_lookup_ptr(ctx, &ptr, NULL, false) ||
						!UCI_LOOKUP_COMPLETE)
					break;
				if (uci_set(ctx, &ptr) == UCI_OK)
					uci_save(ctx, ptr.p);
				break;
			}
		}
	}
	uci_commit(ctx, &pkg, false);
	uci_unload(ctx, pkg);
	uci_free_context(ctx);
	return false;
}

struct uci_section *config_get_section(struct uci_context *ctx,
		struct uci_package *pkg, const char *type, const char *key,
		const char *value)
{
	struct uci_element *e;
	struct uci_section *section;

	/* get the wet iface section */
	uci_foreach_element(&pkg->sections, e) {
		const char *c_value;

		section = uci_to_section(e);
		if (strcmp(section->type, type))
			continue;

		c_value = uci_lookup_option_string(ctx, section, key);
		if (c_value && !strcmp(c_value, value))
			return section;
	}

	return NULL;
}

int cntlr_config_del_node(uint8_t *almacaddr)
{
	struct uci_context *ctx = NULL;
	struct uci_package *pkg;
	struct uci_section *section;
	struct uci_ptr ptr = {0};
	int ret = -1;
	char mac_str[18] = {0};

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

	pkg = uci_load_pkg(&ctx, "mapcontroller");
	if (!pkg)
		return ret;

	section = config_get_section(ctx, pkg, "node", "agent_id", mac_str);
	if (!section)
		goto out_pkg;

	ptr.p = pkg;
	ptr.s = section;

	uci_delete(ctx, &ptr);
	uci_commit(ctx, &pkg, false);
out_pkg:
	uci_unload(ctx, pkg);
	uci_free_context(ctx);
	return 0;
}

int cntlr_config_del_radio(uint8_t *almacaddr)
{
	struct uci_context *ctx = NULL;
	struct uci_package *pkg;
	struct uci_section *section;
	struct uci_ptr ptr = {0};
	int ret = -1;
	char mac_str[18] = {0};

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

	pkg = uci_load_pkg(&ctx, "mapcontroller");
	if (!pkg)
		return ret;

	section = config_get_section(ctx, pkg, "radio", "agent_id", mac_str);
	if (!section)
		goto out_pkg;

	ptr.p = pkg;
	ptr.s = section;

	uci_delete(ctx, &ptr);
	uci_commit(ctx, &pkg, false);
out_pkg:
	uci_unload(ctx, pkg);
	uci_free_context(ctx);
	return 0;
}

int cntlr_config_add_node(struct controller_config *c, char *al_mac)
{
	struct uci_context *ctx = NULL;
	struct uci_package *pkg;
	struct uci_section *section;
	struct uci_ptr ptr = {0};
	char name[32] = { 0 };
	static const char s[2] = ":";
	char *token;
	char mac[18] = { 0 };
	int ret = -1;

	pkg = uci_load_pkg(&ctx, "mapcontroller");
	if (!pkg)
		return ret;

	section = config_get_section(ctx, pkg, "node", "agent_id", al_mac);
	if (section)
		goto out_pkg;

	ret = uci_add_section(ctx, pkg, "node", &section);
	if (ret)
		goto out_pkg;

	strncpy(mac, al_mac, sizeof(mac) - 1);
	strncpy(name, "node_", sizeof(name) - 1);

	token = strtok(mac, s);
	while (token != NULL) {
		snprintf(name + strlen(name),
			 (sizeof(name) - strlen(name)),
			 "%s", token);
		token = strtok(NULL, s);
	}

	ptr.p = pkg;
	ptr.s = section;
	ptr.value = name;
	uci_rename(ctx, &ptr);

	ret = uci_save(ctx, pkg);
	if (ret)
		goto out_pkg;

	ret = set_value(ctx, pkg, section, "agent_id", al_mac, UCI_TYPE_STRING);

	uci_commit(ctx, &pkg, false);

out_pkg:
	uci_unload(ctx, pkg);
	uci_free_context(ctx);
	return ret;
}

int cntlr_config_add_node_radio(struct controller_config *c, uint8_t *alid,
		uint8_t *ruid, enum wifi_band band)
{
	struct uci_context *ctx = NULL;
	struct uci_section *section;
	struct uci_ptr ptr = {0};
	struct uci_package *pkg;
	static const char s[2] = ":";
	char radiostr[18] = {0};
	char alstr[18] = {0};
	char name[32] = {0};
	char mac[18] = {0};
	char *token;
	int ret = -1;

	if (!hwaddr_ntoa(ruid, radiostr))
		return ret;

	if (!hwaddr_ntoa(alid, alstr))
		return ret;

	pkg = uci_load_pkg(&ctx, "mapcontroller");
	if (!pkg)
		return ret;

	section = config_get_section(ctx, pkg, "radio", "macaddr", radiostr);
	if (section)
		goto out_pkg;

	/* create section */
	ret = uci_add_section(ctx, pkg, "radio", &section);
	if (ret)
		goto out_pkg;

	strncpy(mac, radiostr, 18);
	strncpy(name, "radio_", sizeof(name) - 1);

	token = strtok(mac, s);
	while (token != NULL) {
		snprintf(name + strlen(name),
			(sizeof(name) - strlen(name)),
			"%s", token);
		token = strtok(NULL, s);
	}

	ptr.p = pkg;
	ptr.s = section;
	ptr.value = name;
	uci_rename(ctx, &ptr);

	/* add default values */
	ret = set_value(ctx, pkg, section, "agent_id", alstr, UCI_TYPE_STRING);
	if (ret)
		goto out_pkg;

	ret = set_value(ctx, pkg, section, "macaddr", radiostr, UCI_TYPE_STRING);
	if (ret)
		goto out_pkg;

	ret = set_value(ctx, pkg, section, "band", wifi_freqband_to_str(band), UCI_TYPE_STRING);
	if (ret)
		goto out_pkg;

	ret = uci_save(ctx, pkg);
	if (ret)
		goto out_pkg;

	uci_commit(ctx, &pkg, false);

out_pkg:
	uci_unload(ctx, pkg);
	uci_free_context(ctx);
	return ret;
}



#if 0
void cntlr_dump_node_policy(struct node_policy *np)
{
	dbg("Dump node policy for agent "MACFMT"\n", MAC2STR(np->agent_id));
	dbg("agent_id "MACFMT"\n", MAC2STR(np->agent_id));
	dbg("bk_ul_mac "MACFMT"\n", MAC2STR(np->bk_ul_mac));
	dbg("bk_dl_mac "MACFMT"\n", MAC2STR(np->bk_dl_mac));
	dbg("type %d\n", np->type);
	dbg("pvid %u\n", np->pvid);
	dbg("pcp %u\n", np->pcp);
	dbg("report_scan %d\n", np->report_scan);
	dbg("report_sta_assocfails %d\n", np->report_sta_assocfails);
	dbg("report_sta_assocfails_rate %d\n", np->report_sta_assocfails_rate);
	dbg("report_metric_periodic %u\n", np->report_metric_periodic);
	dbg("steer_disallow %d\n", np->steer_disallow);
	dbg("coordinated_cac %d\n", np->coordinated_cac);
	dbg("traffic_separation %d\n", np->traffic_separation);
	dbg("sta_steer %d\n", np->sta_steer);
	dbg("num_steer_stas %d\n", np->num_steer_stas);
	dbg("num_btmsteer_stas %d\n", np->num_btmsteer_stas);
}


void cntlr_config_dump(struct controller_config *c)
{
	struct iface_credential *cred;

	dbg("Controller config ---------\n");
	dbg("Enabled: %d\n", c->enabled);
	dbg("Registrar @5Ghz: %d\n", c->has_registrar_5g);
	dbg("Registrar @2Ghz: %d\n", c->has_registrar_2g);
	dbg("Enable STA steer: %d\n", c->enable_sta_steer);
	dbg("Enable BSTA steer: %d\n", c->enable_bsta_steer);
	dbg("Use bcn metrics to steer: %d\n", c->use_bcn_metrics);
	dbg("Use uSTA metrics to steer: %d\n", c->use_usta_metrics);

	dbg("Credentials\n");
	list_for_each_entry(cred, &c->aplist, list) {
		dbg("  Band    : %d\n", cred->band);
		dbg("  Security: 0x%x\n", cred->sec);
		dbg("  Key     :\n");
		dbg("  ssid    : %s\n", cred->ssid);
		dbg("  vlan    : %d\n\n", cred->vlanid);
	}

	dbg("Agents policy: Default\n");
	//dbg("  Id                    : " MACFMT "\n", MAC2STR(c->apolicy.agent_id));
	dbg("  Steer-policy          : %d\n", c->apolicy->policy);
	dbg("  Util-threshold        : %d\n", c->apolicy->util_threshold);
	dbg("  RCPI-threshold        : %d\n", c->apolicy->rcpi_threshold);
	dbg("  Report scan           : %d\n", c->apolicy->report_scan);
	dbg("  Report assocfails     : %d\n", c->apolicy->report_sta_assocfails);
	dbg("  Report assocfails rate: %d\n", c->apolicy->report_sta_assocfails_rate);
	dbg("  Report metric         : %d\n", c->apolicy->report_metric_periodic);
	dbg("  Report RCPI-thresh    : %d\n", c->apolicy->report_rcpi_threshold);
	dbg("  Report Util-thresh    : %d\n", c->apolicy->report_util_threshold);
	dbg("  RCPI hysteresis margin: %d\n", c->apolicy->rcpi_hysteresis_margin);
	dbg("  Include STA stats     : %d\n", c->apolicy->include_sta_stats);
	dbg("  Include STA metric    : %d\n", c->apolicy->include_sta_metric);
	dbg("  Disallow bSTA P1      : %d\n", c->apolicy->disallow_bsta_p1);
	dbg("  Disallow bSTA P2      : %d\n", c->apolicy->disallow_bsta_p2);
	dbg("  Is policy diff        : %d\n", c->apolicy->is_policy_diff);

#if 0
	// INIT_LIST_HEAD(&c->apolicy.steer_exlist); // TODO: remove INIT_LIST_HEAD
	// INIT_LIST_HEAD(&c->apolicy.btmsteer_exlist);
	list_for_each_entry(x, &c->apolicy.steer_exlist, list) {
		char macstr[18];

		hwaddr_ntoa(x->macaddr, macstr);
		dbg("  Disallowed STA        : %s\n", macstr);
	}
	list_for_each_entry(x, &c->apolicy.btmsteer_exlist, list) {
		char macstr[18];

		hwaddr_ntoa(x->macaddr, macstr);
		dbg("  Disallowed BTM STA    : %s\n", macstr);
	}
#endif

	dbg("---------------------------\n");
}
#endif

int cntlr_config_defaults(struct controller *cntlr, struct controller_config *cfg)
{
	memset(cfg, 0, sizeof(*cfg));
	INIT_LIST_HEAD(&cfg->radiolist);
	INIT_LIST_HEAD(&cfg->nodelist);
	INIT_LIST_HEAD(&cfg->aplist);
	INIT_LIST_HEAD(&cfg->btmsteer_exlist);

	/* steer policy defaults */
	cfg->steer.enable_sta_steer = true;
	cfg->steer.enable_bsta_steer = true;
	cfg->steer.rcpi_threshold_2g = CONFIG_DEFAULT_RCPI_TH_2G;
	cfg->steer.rcpi_threshold_5g = CONFIG_DEFAULT_RCPI_TH_5G;
	cfg->steer.rcpi_threshold_6g = CONFIG_DEFAULT_RCPI_TH_6G;
	cfg->steer.report_rcpi_threshold_2g = CONFIG_DEFAULT_REPORT_RCPI_TH_2G;
	cfg->steer.report_rcpi_threshold_5g = CONFIG_DEFAULT_REPORT_RCPI_TH_5G;
	cfg->steer.report_rcpi_threshold_6g = CONFIG_DEFAULT_REPORT_RCPI_TH_6G;

	cfg->steer.plugin_enabled = true;
	cfg->steer.plugin_policy = STEER_PLUGIN_POLICY_OR;

	cfg->debug_level = LOGLEVEL_UNSET;
	cfg->log_features = 0;

#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	strncpy(cfg->dpp_uri_file, CONFIG_DEFAULT_DPP_URI_FILE,
		sizeof(cfg->dpp_uri_file) - 1);
#endif
#endif
	strncpy(cfg->assoc_ctrl_file, CONFIG_DEFAULT_ASSOC_CTRL_FILE,
		sizeof(cfg->assoc_ctrl_file) - 1);

	INIT_LIST_HEAD(&cfg->steerplugins);
	INIT_LIST_HEAD(&cfg->plugins);
#if (EASYMESH_VERSION > 2)
	INIT_LIST_HEAD(&cfg->qos.rule);
	INIT_LIST_HEAD(&cfg->qos.policy.mscs_disallowed);
	INIT_LIST_HEAD(&cfg->qos.policy.scs_disallowed);
#endif
#if (EASYMESH_VERSION >= 6)
	INIT_LIST_HEAD(&cfg->mldlist);
#endif

	return 0;
}

static int cntlr_config_get_base(struct controller *cntlr,
				 struct controller_config *c,
				 struct uci_section *s)
{
	enum {
		CNTLR_ENABLED,
		CNTLR_ID,		/* 1905 AL-macaddress of the controller */
		CNTLR_NETWORK_ID,	/* UUID of the Multi-AP network */
		CNTLR_REGISTRAR,
		CNTLR_DEBUG,
		CNTLR_LOG_FEATURE,
		CNTLR_RESEND_NUM,
		CNTLR_BCN_METRICS_MAX_NUM,
		CNTLR_STALE_STA_TIMEOUT,
		CNTLR_PRIMARY_VID,
		CNTLR_DEFAULT_PCP,
		CNTLR_ENABLE_TS,
		CNTLR_PROFILE,
		CNTLR_MAX_NODE_BH_HOPS,
		CNTLR_PLUGIN,
		CNTLR_DPP_URI_FILE,
		CNTLR_ASSOC_CTRL_FILE,

		NUM_CNTLR_ATTRS
	};
	const struct uci_parse_option opts[NUM_CNTLR_ATTRS] = {
		[CNTLR_ENABLED] = { .name = "enabled", .type = UCI_TYPE_STRING },
		[CNTLR_ID] = { .name = "id", .type = UCI_TYPE_STRING },
		[CNTLR_NETWORK_ID] = { .name = "network_id", .type = UCI_TYPE_STRING },
		[CNTLR_REGISTRAR] = { .name = "registrar", .type = UCI_TYPE_STRING },
		[CNTLR_DEBUG] = { .name = "debug", .type = UCI_TYPE_STRING },
		[CNTLR_LOG_FEATURE] = { .name = "log_feature", .type = UCI_TYPE_LIST },
		[CNTLR_RESEND_NUM] = { .name = "resend_num", .type = UCI_TYPE_STRING },
		[CNTLR_BCN_METRICS_MAX_NUM] = { .name = "bcn_metrics_max_num", .type = UCI_TYPE_STRING },
		[CNTLR_STALE_STA_TIMEOUT] = { .name = "stale_sta_timeout", .type = UCI_TYPE_STRING },
		[CNTLR_PRIMARY_VID] = { .name = "primary_vid", .type = UCI_TYPE_STRING },
		[CNTLR_DEFAULT_PCP] = { .name = "default_pcp", .type = UCI_TYPE_STRING },
		[CNTLR_ENABLE_TS] = { .name = "enable_ts", .type = UCI_TYPE_STRING },
		[CNTLR_PROFILE] = { .name = "profile", .type = UCI_TYPE_STRING },
		[CNTLR_MAX_NODE_BH_HOPS] = { .name = "max_node_bh_hops", .type = UCI_TYPE_STRING },
		[CNTLR_PLUGIN] = { .name = "plugin", .type = UCI_TYPE_LIST },
		[CNTLR_DPP_URI_FILE] = { .name = "dpp_uri_file", .type = UCI_TYPE_STRING },
		[CNTLR_ASSOC_CTRL_FILE] = { .name = "assoc_ctrl_file", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_CNTLR_ATTRS];
	char uuidstr[37] = {0};
	char *endptr = NULL;

	uci_parse_section(s, opts, NUM_CNTLR_ATTRS, tb);

	if (tb[CNTLR_PROFILE]) {
		errno = 0;
		c->map_profile = strtol(tb[CNTLR_PROFILE]->v.string, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s Error parsing profile value: %s\n",
				__func__, tb[CNTLR_PROFILE]->v.string);
			return -1;
		}
	} else {
		c->map_profile = DEFAULT_MULTIAP_PROFILE;
	}

	if (tb[CNTLR_ENABLED]) {
		const char *chval = tb[CNTLR_ENABLED]->v.string;
		int enbl;

		errno = 0;
		enbl = strtol(chval, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing enabled value: %s\n",
				__func__, chval);
			return -1;
		}

		c->enabled = (enbl == 1) ? true : false;
	}

	if (tb[CNTLR_ID]) {
		const char *val = tb[CNTLR_ID]->v.string;

		if (strlen(val) != 17)
			return -1;

		hwaddr_aton(val, c->id);
	}

	if (tb[CNTLR_NETWORK_ID]) {
		char *val = tb[CNTLR_NETWORK_ID]->v.string;

		if (strlen(val) == 36) {
			strncpy(uuidstr, val, sizeof(uuidstr) - 1);
			uuid_strtob(uuidstr, c->network_id);
		}
	} else {
		c->update_config = true;
		chrCmd(uuidstr, sizeof(uuidstr), "uuidgen -t 2>/dev/null");
		if (uuidstr[0] != '\0' && strlen(uuidstr) == 36)
			uuid_strtob(uuidstr, c->network_id);
		else
			getrandom(c->network_id, 16, GRND_NONBLOCK);
	}

	if (tb[CNTLR_REGISTRAR]) {
		const char *val = tb[CNTLR_REGISTRAR]->v.string;

		c->has_registrar_6g = !strstr(val, "6") ? false : true;
		c->has_registrar_5g = !strstr(val, "5") ? false : true;
		c->has_registrar_2g = !strstr(val, "2") ? false : true;
	}

	if (tb[CNTLR_DEBUG]) {
		int lvl = strtol(tb[CNTLR_DEBUG]->v.string, NULL, 10);

		lvl = ((lvl <= LOGLEVEL_MAX) ? lvl : LOGLEVEL_MAX);
		if (lvl >= 0)
			c->debug_level = lvl;
	} else {
		/* debug option missing in config */
		c->debug_level = DEFAULT_LOGLEVEL;
	}

	if (tb[CNTLR_LOG_FEATURE]) {
		struct uci_element *e;
		uint32_t features = 0;

		uci_foreach_element(&tb[CNTLR_LOG_FEATURE]->v.list, e) {
			if (!strcmp(e->name, "none"))
				features = 0;
			else if (!strcmp(e->name, "all"))
				features = LOG_FEATURE_ALL;
			else
				features |= logfeature_to_enum(e->name);
		}
		c->log_features = features;
	} else {
		/* log_feature option missing in config - default to all */
		c->log_features = LOG_FEATURE_ALL;
	}

	if (tb[CNTLR_RESEND_NUM]) {
		const char *chval = tb[CNTLR_RESEND_NUM]->v.string;

		errno = 0;
		c->resend_num = strtol(chval, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing resend_num value: %s\n",
			    __func__, chval);
			return -1;
		}
	}

	if (tb[CNTLR_BCN_METRICS_MAX_NUM]) {
		const char *val = tb[CNTLR_BCN_METRICS_MAX_NUM]->v.string;

		errno = 0;
		c->bcn_metrics_max_num = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s Error parsing bcn_metrics_max_num value: %s\n",
			    __func__, val);
			return -1;
		}
	} else
		c->bcn_metrics_max_num = BCN_METRICS_MAX_NUM;


	c->stale_sta_timeout = MAX_STALE_STA_TIMEOUT;
	if (tb[CNTLR_STALE_STA_TIMEOUT]) {
		const char *val = tb[CNTLR_STALE_STA_TIMEOUT]->v.string;
		int tm = time_str_to_sec(val);

		if (tm > 0 && tm <= MAX_STALE_STA_TIMEOUT)
			c->stale_sta_timeout = tm;
	}
	cntlr_dbg(LOG_MISC, "%s: stale_sta_timeout set to %d seconds\n",
		  __func__, c->stale_sta_timeout);

	if (tb[CNTLR_PRIMARY_VID]) {
		const char *val = tb[CNTLR_PRIMARY_VID]->v.string;

		errno = 0;
		c->primary_vid = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing primary_vid value: %s\n",
			    __func__, val);
			return -1;
		}
		if (c->primary_vid > 0xfff)
			c->primary_vid = 0;
	}

	if (tb[CNTLR_ENABLE_TS]) {
		const char *chval = tb[CNTLR_ENABLE_TS]->v.string;
		int enbl;

		errno = 0;
		enbl = strtol(chval, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing enable_ts value: %s\n",
					__func__, chval);
			return -1;
		}

		c->enable_ts = !!enbl;
	}

	if (tb[CNTLR_MAX_NODE_BH_HOPS]) {
		const char *val = tb[CNTLR_MAX_NODE_BH_HOPS]->v.string;
		int max_hops;

		errno = 0;
		max_hops = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing max_node_bh_hops value: %s\n",
					__func__, val);
			return -1;
		}

		c->max_node_bh_hops = (max_hops > 0) ? max_hops : 0;
	}

	if (tb[CNTLR_PLUGIN]) {
		struct uci_element *e;

		list_flush(&c->plugins, struct cntlr_plugin_name, list);

		uci_foreach_element(&tb[CNTLR_PLUGIN]->v.list, e) {
			struct cntlr_plugin_name *n;

			n = calloc(1, sizeof(*n));
			if (n) {
				strncpy(n->name, e->name, 32);
				list_add_tail(&n->list, &c->plugins);
			}
		}
	}

	cntlr_load_plugins(cntlr);

#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	if (tb[CNTLR_DPP_URI_FILE]) {
		const char *val = tb[CNTLR_DPP_URI_FILE]->v.string;

		if (val && *val)
			strncpy(c->dpp_uri_file, val, sizeof(c->dpp_uri_file) - 1);
	}
#endif
#endif

	if (tb[CNTLR_ASSOC_CTRL_FILE]) {
		const char *val = tb[CNTLR_ASSOC_CTRL_FILE]->v.string;

		if (val && *val)
			strncpy(c->assoc_ctrl_file, val, sizeof(c->assoc_ctrl_file) - 1);
	}

	return 0;
}

static int cntlr_config_get_wsc_attributes(struct controller_config *cfg)
{
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;

	memset(&cfg->wscdev, 0, sizeof(cfg->wscdev));

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

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

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

		if (!strcmp(s->type, "ieee1905")) {
			uint8_t default_dev_type[8] = { 0x00, 0x06, 0x00, 0x50, 0xf2, 0x04, 0x00, 0x01 }; /* default WPS oui */
			const char *manufacturer, *model_name, *device_name;
			const char *model_number, *serial_number, *device_type;

			manufacturer = uci_lookup_option_string(ctx, s, "manufacturer");
			if (manufacturer)
				strncpy(cfg->wscdev.manufacturer, manufacturer, 63);

			model_name = uci_lookup_option_string(ctx, s, "model_name");
			if (model_name)
				strncpy(cfg->wscdev.model_name, model_name, 32);

			device_name = uci_lookup_option_string(ctx, s, "device_name");
			if (device_name)
				strncpy(cfg->wscdev.device_name, device_name, 32);

			model_number = uci_lookup_option_string(ctx, s, "model_number");
			if (model_number)
				strncpy(cfg->wscdev.model_number, model_number, 32);

			serial_number = uci_lookup_option_string(ctx, s, "serial_number");
			if (serial_number)
				strncpy(cfg->wscdev.serial_number, serial_number, 32);

			memcpy(cfg->wscdev.device_type, default_dev_type, 8);
			device_type = uci_lookup_option_string(ctx, s, "device_type");
			if (device_type) {
				int ret;
				uint8_t oui[4] = {0};
				uint16_t category = 0, sub_category = 0;

				ret = sscanf(device_type, "%02hu-%02hhx%02hhx%02hhx%02hhx-%02hu",
					     &category,
					     &oui[0], &oui[1], &oui[2], &oui[3],
					     &sub_category);
				if (ret == 6) {
					buf_put_be16(&cfg->wscdev.device_type[0], category);
					memcpy(&cfg->wscdev.device_type[2], oui, 4);
					buf_put_be16(&cfg->wscdev.device_type[6], sub_category);
				}
			}
			break;
		}
	}

	uci_unload(ctx, pkg);
	uci_free_context(ctx);
	return 0;
}

static int cntlr_config_get_steer_policy(struct controller_config *cc,
					 struct uci_section *s)
{
	enum {
		STEER_STA_ENABLE,
		STEER_BSTA_ENABLE,
		STEER_RCPI_TH_2G,
		STEER_RCPI_TH_5G,
		STEER_RCPI_TH_6G,
		STEER_RPT_RCPI_TH_2G,
		STEER_RPT_RCPI_TH_5G,
		STEER_RPT_RCPI_TH_6G,
		STEER_PLUGIN_ENABLED,
		STEER_PLUGIN_POLICY,
		STEER_PLUGINS,
		STEER_STEER_EXCLUDE,
		NUM_STEER_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[STEER_STA_ENABLE] = { .name = "enable_sta_steer", .type = UCI_TYPE_STRING },
		[STEER_BSTA_ENABLE] = { .name = "enable_bsta_steer", .type = UCI_TYPE_STRING },
		[STEER_RCPI_TH_2G] = { .name = "rcpi_threshold_2g", .type = UCI_TYPE_STRING },
		[STEER_RCPI_TH_5G] = { .name = "rcpi_threshold_5g", .type = UCI_TYPE_STRING },
		[STEER_RCPI_TH_6G] = { .name = "rcpi_threshold_6g", .type = UCI_TYPE_STRING },
		[STEER_RPT_RCPI_TH_2G] = { .name = "report_rcpi_threshold_2g", .type = UCI_TYPE_STRING },
		[STEER_RPT_RCPI_TH_5G] = { .name = "report_rcpi_threshold_5g", .type = UCI_TYPE_STRING },
		[STEER_RPT_RCPI_TH_6G] = { .name = "report_rcpi_threshold_6g", .type = UCI_TYPE_STRING },
		[STEER_PLUGIN_ENABLED] = { .name = "plugins_enabled", .type = UCI_TYPE_STRING },
		[STEER_PLUGIN_POLICY] = { .name = "plugins_policy", .type = UCI_TYPE_STRING },
		[STEER_PLUGINS] = { .name = "plugins", .type = UCI_TYPE_LIST },
		[STEER_STEER_EXCLUDE] = { .name = "steer_exclude", .type = UCI_TYPE_LIST },
	};
	struct uci_option *tb[NUM_STEER_ATTRS];
	struct steer_policy *sc = &cc->steer;
	char *endptr = NULL;

	uci_parse_section(s, opts, NUM_STEER_ATTRS, tb);

	if (tb[STEER_PLUGIN_ENABLED]) {
		const char *val = tb[STEER_PLUGIN_ENABLED]->v.string;
		int enbl;

		errno = 0;
		enbl = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0')
			sc->plugin_enabled = (enbl == 1) ? true : false;
	}

	if (tb[STEER_PLUGIN_POLICY]) {
		const char *val = tb[STEER_PLUGIN_POLICY]->v.string;

		if (!strncasecmp(val, "any", 3))
			sc->plugin_policy = STEER_PLUGIN_POLICY_OR;
		else if (!strncasecmp(val, "all", 3))
			sc->plugin_policy = STEER_PLUGIN_POLICY_AND;
	}

	if (tb[STEER_PLUGINS]) {
		struct uci_element *e;

		list_flush(&cc->steerplugins, struct cntlr_plugin_name, list);

		uci_foreach_element(&tb[STEER_PLUGINS]->v.list, e) {
			struct cntlr_plugin_name *n;

			n = calloc(1, sizeof(*n));
			if (n) {
				strncpy(n->name, e->name, 32);
				list_add_tail(&n->list, &cc->steerplugins);
			}
		}
	}

	if (tb[STEER_STA_ENABLE]) {
		const char *val = tb[STEER_STA_ENABLE]->v.string;
		int enbl;

		errno = 0;
		enbl = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0')
			sc->enable_sta_steer = (enbl == 1) ? true : false;
	}

	if (tb[STEER_BSTA_ENABLE]) {
		const char *val = tb[STEER_BSTA_ENABLE]->v.string;
		int enbl;

		errno = 0;
		enbl = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0')
			sc->enable_bsta_steer = (enbl == 1) ? true : false;
	}


	if (tb[STEER_RCPI_TH_2G]) {
		const char *val = tb[STEER_RCPI_TH_2G]->v.string;
		int rcpi;

		errno = 0;
		rcpi = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0' && rcpi > 0 && rcpi <= 220)
			sc->rcpi_threshold_2g = rcpi;
	}

	if (tb[STEER_RCPI_TH_5G]) {
		const char *val = tb[STEER_RCPI_TH_5G]->v.string;
		int rcpi;

		errno = 0;
		rcpi = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0' && rcpi > 0 && rcpi <= 220)
			sc->rcpi_threshold_5g = rcpi;
	}

	if (tb[STEER_RCPI_TH_6G]) {
		const char *val = tb[STEER_RCPI_TH_6G]->v.string;
		int rcpi;

		errno = 0;
		rcpi = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0' && rcpi > 0 && rcpi <= 220)
			sc->rcpi_threshold_6g = rcpi;
	}

	if (tb[STEER_RPT_RCPI_TH_2G]) {
		const char *val = tb[STEER_RPT_RCPI_TH_2G]->v.string;
		int rcpi;

		errno = 0;
		rcpi = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0' && rcpi > 0 && rcpi <= 220)
			sc->report_rcpi_threshold_2g = rcpi;
	}

	if (tb[STEER_RPT_RCPI_TH_5G]) {
		const char *val = tb[STEER_RPT_RCPI_TH_5G]->v.string;
		int rcpi;

		errno = 0;
		rcpi = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0' && rcpi > 0 && rcpi <= 220)
			sc->report_rcpi_threshold_5g = rcpi;
	}

	if (tb[STEER_RPT_RCPI_TH_6G]) {
		const char *val = tb[STEER_RPT_RCPI_TH_6G]->v.string;
		int rcpi;

		errno = 0;
		rcpi = strtol(val, &endptr, 10);
		if (!errno && *endptr == '\0' && rcpi > 0 && rcpi <= 220)
			sc->report_rcpi_threshold_6g = rcpi;
	}

	/* Load controller-level BTM exclusion list */
	if (tb[STEER_STEER_EXCLUDE]) {
		struct uci_element *x;
		struct stax *btex;

		/* Clear existing list in controller_config */
		clean_btm_exclude_list(cc);

		/* Add entries from config to controller_config */
		uci_foreach_element(&tb[STEER_STEER_EXCLUDE]->v.list, x) {
			btex = stax_add_entry(&cc->btmsteer_exlist, x->name);
			if (!btex)
				continue;
			cc->num_btmsteer_excl_stas++;
		}
	}

	return 0;
}

static int cntlr_config_get_channel_plan(struct controller_config *c,
					 struct uci_section *s)
{
	enum {
		CNTLR_CHANNEL_PLAN_ACS,
		CNTLR_CHANNEL_PLAN_ACS_RECALC_TIMEOUT,
		CNTLR_CHANNEL_PLAN_ACS_SKIP_DFS,
		CNTLR_CHANNEL_PLAN_ACS_PREVENT_CAC,
		CNTLR_CHANNEL_PLAN_ACS_HIGHEST_BANDWIDTH,
		CNTLR_CHANNEL_PLAN_ACS_SCAN_BEFORE_RECALC,
		CNTLR_CHANNEL_PLAN_BGCAC,
		NUM_CNTLR_CHANNEL_PLAN_ATTRS
	};
	const struct uci_parse_option opts[NUM_CNTLR_CHANNEL_PLAN_ATTRS] = {
		[CNTLR_CHANNEL_PLAN_ACS] = { .name = "acs", .type = UCI_TYPE_STRING },
		[CNTLR_CHANNEL_PLAN_ACS_RECALC_TIMEOUT] = { .name = "acs_timeout", .type = UCI_TYPE_STRING },
		[CNTLR_CHANNEL_PLAN_ACS_SKIP_DFS] = { .name = "acs_skip_dfs", .type = UCI_TYPE_STRING },
		[CNTLR_CHANNEL_PLAN_ACS_PREVENT_CAC] = { .name = "acs_prevent_cac", .type = UCI_TYPE_STRING },
		[CNTLR_CHANNEL_PLAN_ACS_HIGHEST_BANDWIDTH] = { .name = "acs_highest_bandwidth", .type = UCI_TYPE_STRING },
		[CNTLR_CHANNEL_PLAN_ACS_SCAN_BEFORE_RECALC] = { .name = "acs_scan_before_recalc", .type = UCI_TYPE_STRING },
		[CNTLR_CHANNEL_PLAN_BGCAC] = { .name = "preclear_dfs", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_CNTLR_CHANNEL_PLAN_ATTRS];
	char *endptr = NULL;

	uci_parse_section(s, opts, NUM_CNTLR_CHANNEL_PLAN_ATTRS, tb);

	if (tb[CNTLR_CHANNEL_PLAN_ACS]) {
		const char *val = tb[CNTLR_CHANNEL_PLAN_ACS]->v.string;

		errno = 0;
		c->acs = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s:Error parsing channel_plan acs value: %s\n",
			    __func__, val);
			return -1;
		}
	} else {
		c->acs = false;
	}

	if (tb[CNTLR_CHANNEL_PLAN_ACS_RECALC_TIMEOUT]) {
		const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_RECALC_TIMEOUT]->v.string;

		c->acs_timeout = time_str_to_sec(val);
		if (c->acs_timeout == -1) {
			err("%s: Error parsing channel_plan acs_timeout value: %s\n",
			    __func__, val);
			return -1;
		}
	} else {
		c->acs_timeout = 0;
	}

	if (tb[CNTLR_CHANNEL_PLAN_ACS_SKIP_DFS]) {
		const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_SKIP_DFS]->v.string;

		errno = 0;
		c->acs_skip_dfs = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing channel_plan skip_dfs value: %s\n",
			    __func__, val);
			return -1;
		}
	} else {
		c->acs_skip_dfs = false;
	}

	if (tb[CNTLR_CHANNEL_PLAN_ACS_PREVENT_CAC]) {
		const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_PREVENT_CAC]->v.string;

		errno = 0;
		c->acs_prevent_cac = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing channel_plan prevent_cac value: %s\n",
			    __func__, val);
			return -1;
		}
	} else {
		c->acs_prevent_cac = true;
	}

	if (tb[CNTLR_CHANNEL_PLAN_ACS_HIGHEST_BANDWIDTH]) {
		const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_HIGHEST_BANDWIDTH]->v.string;

		errno = 0;
		c->acs_highest_bandwidth = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing channel_plan highest_bandwidth value: %s\n",
			    __func__, val);
			return -1;
		}
	} else {
		/* Use configured bandwidth */
		c->acs_highest_bandwidth = false;
	}

	if (tb[CNTLR_CHANNEL_PLAN_ACS_SCAN_BEFORE_RECALC]) {
		const char *val = tb[CNTLR_CHANNEL_PLAN_ACS_SCAN_BEFORE_RECALC]->v.string;

		errno = 0;
		c->acs_scan_before_recalc = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing channel_plan scan_before_recalc value: %s\n",
			    __func__, val);
			return -1;
		}
	} else {
		c->acs_scan_before_recalc = true;
	}

	if (tb[CNTLR_CHANNEL_PLAN_BGCAC]) {
		const char *val = tb[CNTLR_CHANNEL_PLAN_BGCAC]->v.string;

		errno = 0;
		c->dfs_cleanup = !!strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing preclear_dfs value: %s\n",
			    __func__, val);
			return -1;
		}
	} else {
		c->dfs_cleanup = false;
	}

	return 0;
}

static int cntlr_config_get_credentials(struct controller_config *c,
						struct uci_section *s)
{
	enum {
		CRED_BAND,
		CRED_SSID,
		CRED_SEC,
		CRED_KEY,
		CRED_VLAN,
		CRED_TYPE,
		CRED_D_BSTA,
		CRED_ENABLED,
		CRED_VENDOR_IE,
#ifdef EASYMESH_VENDOR_EXT
#ifdef PROVISION_BRIDGE
		CRED_BRIDGE,
#endif
#endif
#if (EASYMESH_VERSION >= 6)
		CRED_MLD_ID,
		CRED_HIDDEN,
#endif

		NUM_CREDS,
	};
	const struct uci_parse_option opts[] = {
		[CRED_BAND] = { .name = "band", .type = UCI_TYPE_STRING },
		[CRED_SSID] = { .name = "ssid", .type = UCI_TYPE_STRING },
		[CRED_SEC] = { .name = "encryption", .type = UCI_TYPE_STRING },
		[CRED_KEY] = { .name = "key", .type = UCI_TYPE_STRING },
		[CRED_VLAN] = { .name = "vid", .type = UCI_TYPE_STRING },
		[CRED_TYPE] = { .name = "type", .type = UCI_TYPE_STRING },
		[CRED_D_BSTA] = { .name = "disallow_bsta_profile", .type = UCI_TYPE_LIST },
		[CRED_ENABLED] = { .name = "enabled", .type = UCI_TYPE_STRING },
		[CRED_VENDOR_IE] = { .name = "vendor_ie", .type = UCI_TYPE_LIST },
#ifdef EASYMESH_VENDOR_EXT
#ifdef PROVISION_BRIDGE
		[CRED_BRIDGE] = { .name = "bridge", .type = UCI_TYPE_STRING },
#endif
#endif
#if (EASYMESH_VERSION >= 6)
		[CRED_MLD_ID] = { .name = "mld_id", .type = UCI_TYPE_STRING },
		[CRED_HIDDEN] = { .name = "hidden", .type = UCI_TYPE_STRING },
#endif
	};
	struct uci_option *tb[NUM_CREDS];
	struct iface_credential *ap;
	bool use_default_security = false;
	char *endptr = NULL;

	if (c->num_bss >= 32)
		return -1;

	ap = calloc(1, sizeof(*ap));
	if (!ap)
		return -1;

	uci_parse_section(s, opts, NUM_CREDS, tb);

	ap->enabled = true;
	if (tb[CRED_ENABLED]) {
		errno = 0;
		ap->enabled = strtol(tb[CRED_ENABLED]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing enabled value: %s\n",
			    __func__, tb[CRED_ENABLED]->v.string);
	}
#ifndef PROVISION_DISABLED_AP
	if (!(ap->enabled)) {
		free(ap);
		return -1;
	}
#endif

	if (tb[CRED_BAND]) {
		int band;

		errno = 0;
		band = strtol(tb[CRED_BAND]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing band value: %s\n",
			    __func__, tb[CRED_BAND]->v.string);

		if (band == 5) {
			ap->band = ap->cred.band = (uint8_t) BAND_5;
		} else if (band == 2) {
			ap->band = ap->cred.band = (uint8_t) BAND_2;
		} else if (band == 6) {
			ap->band = ap->cred.band = (uint8_t) BAND_6;
		} else {
			ap->band = ap->cred.band = (uint8_t) BAND_UNKNOWN;
		}
	} else {
		ap->band = ap->cred.band = (uint8_t) BAND_UNKNOWN;
	}

	if (tb[CRED_SSID]) {
		strncpy((char *) ap->cred.ssid, tb[CRED_SSID]->v.string, 32);
		ap->cred.ssidlen = strlen((char *)ap->cred.ssid);
	}

	if (tb[CRED_SEC]) {
		const char *sec = tb[CRED_SEC]->v.string;

		if (ap->band == BAND_6 &&
		    strcmp(sec, "sae") && strcmp(sec, "dpp") &&
		    strcmp(sec, "dpp+sae") && strcmp(sec, "sae+dpp") &&
		    strcmp(sec, "owe")) {
			err("%s: cred:%s sec:%s is not valid\n",
			    __func__, ap->cred.ssid, sec);
			free(ap);
			return -1;
		}

		if (!strncmp(sec, "sae-mixed", 9)) {
			ap->sec |= BIT(WIFI_SECURITY_WPA3PSK);
			ap->sec |= BIT(WIFI_SECURITY_WPA3PSK_T);
		} else if (!strncmp(sec, "sae", 3)
#if (EASYMESH_VERSION > 2)
			   || !strncmp(sec, "dpp", 3) ||
			   !strncmp(sec, "dpp+sae", 3) ||
			   !strncmp(sec, "sae+dpp", 3)
#endif
			   ) {
			ap->sec |= BIT(WIFI_SECURITY_WPA3PSK); /* TODO: how do we properly transfer DPP encryption */
		} else if (!strncmp(sec, "psk-mixed", 9)) {
			ap->sec |= BIT(WIFI_SECURITY_WPAPSK);
			ap->sec |= BIT(WIFI_SECURITY_WPA2PSK);
		} else if (!strncmp(sec, "psk2", 4)) {
			ap->sec |= BIT(WIFI_SECURITY_WPA2PSK);
		} else if (!strncmp(sec, "psk", 3)) {
			ap->sec |= BIT(WIFI_SECURITY_WPAPSK);
		} else if (!strncmp(sec, "wpa-mixed", 9)) {
			ap->sec |= BIT(WIFI_SECURITY_WPA);
			ap->sec |= BIT(WIFI_SECURITY_WPA2);
		} else if (!strncmp(sec, "wpa2", 4)) {
			ap->sec |= BIT(WIFI_SECURITY_WPA2);
		} else if (!strncmp(sec, "wpa", 3)) {
			ap->sec |= BIT(WIFI_SECURITY_WPA);
		} else if (!strncmp(sec, "none", 4)) {
			ap->sec |= BIT(WIFI_SECURITY_NONE);
		} else if (!strncmp(sec, "open", 4)) {
			ap->sec |= BIT(WIFI_SECURITY_NONE);
		} else if (!strncmp(sec, "owe", 3)) {
			ap->sec |= BIT(WIFI_SECURITY_OWE);
		} else {
			free(ap);
			return -1;
		}

		//TODO: ciphers (if any)
	} else {
		use_default_security = true;
	}

	if (tb[CRED_KEY]) {
		strncpy((char *) ap->cred.key, tb[CRED_KEY]->v.string, 64);
		ap->cred.keylen = strlen((char *)ap->cred.key);
	}

	if (tb[CRED_VLAN]) {
		errno = 0;
		ap->vlanid = strtoul(tb[CRED_VLAN]->v.string, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing vid value: %s\n",
			    __func__, tb[CRED_VLAN]->v.string);
			free(ap);
			return -1;
		}
		if (!is_vid_valid(ap->vlanid)) {
			err("%s: ap:%s vid:%d is not valid\n",
			    __func__, ap->cred.ssid, ap->vlanid);
			free(ap);
			return -1;
		}
	} else {
		/* if missing option vid in ap section use 1 as default */
		ap->vlanid = 1;
	}

	if (tb[CRED_TYPE]) {
		const char *type = tb[CRED_TYPE]->v.string;

		if (!strcmp(type, "backhaul")) {
			ap->multi_ap = 1;
		} else if (!strcmp(type, "fronthaul")) {
			ap->multi_ap = 2;
		} else if (!strcmp(type, "combined")) {
			ap->multi_ap = 3;
		} else {
			free(ap);
			return -1;
		}
	} else {
		ap->multi_ap = 2; /* default to fhbss */
	}

	ap->cred.mapie = (ap->multi_ap & 0x01) << 6;
	ap->cred.mapie |= (ap->multi_ap & 0x02) << 4;
	ap->cred.mapie |= (ap->disallow_bsta << 2);

	if (use_default_security)
		ap->sec |= BIT(WIFI_SECURITY_NONE);

	ap->cred.auth_type = wifi_sec_to_auth_types(ap->sec);
	if (ap->cred.auth_type == WPS_AUTH_OPEN)
		ap->cred.enc_type = WPS_ENCR_NONE;
	else /* by default use encryption type AES */
		ap->cred.enc_type = WPS_ENCR_AES; /* TODO: TKIP? */

	if (tb[CRED_D_BSTA]) {
		struct uci_element *x;

		uci_foreach_element(&tb[CRED_D_BSTA]->v.list, x) {
			uint8_t profile;

			errno = 0;
			profile = strtol(x->name, &endptr, 10);
			if (errno || *endptr != '\0') {
				err("%s: Error parsing disallow_bsta_profile value: %s\n",
				    __func__, x->name);
				continue;
			}
			if (profile > 2)
				continue;

			ap->disallow_bsta |= profile;
		}
	}


#ifdef EASYMESH_VENDOR_EXT
#ifdef PROVISION_DISABLED_AP
	do {
		/* add iopsys vendor_ies for ap->enabled option */
		struct wsc_vendor_ie *ext;
		uint8_t offset = 0;
		uint8_t attr = 0x4c;
		uint8_t attr_len = 0x01;
		const uint8_t vendor_oui[4] = {0};
		uint32_t oui = 0;


		BUF_PUT_BE24(vendor_oui, EASYMESH_VENDOR_EXT_OUI_DEFAULT);
#ifdef EASYMESH_VENDOR_EXT_OUI
		oui = EASYMESH_VENDOR_EXT_OUI;
		BUF_PUT_BE24(vendor_oui, oui);
#endif
		ext = &ap->ven_ies[0];

		memcpy(ext->oui, vendor_oui, 3);
		ext->len = 3;
		ext->payload = calloc(1, ext->len);
		if (!ext->payload)
			break;

		/* uses same format as WFA WSC vendor extension:
		 * <attr:8><len:8><data>
		 */
		memcpy(ext->payload,  &attr, 1); /* IOP enabled attr */
		offset += 1;
		memcpy(ext->payload + offset, &attr_len, 1); /* len */
		offset += 1;
		memcpy(ext->payload + offset, &ap->enabled, 1); /* val */

		ap->num_ven_ies++;
	} while (0);
#endif /* PROVISION_DISABLED_AP */

#ifdef PROVISION_BRIDGE
	if (tb[CRED_BRIDGE]) {
		const char *bridge = tb[CRED_BRIDGE]->v.string;

		if (bridge[0] != '\0' && ap->num_ven_ies < VEN_IES_MAX) {
			const uint8_t vendor_oui[4] = {0};
			uint8_t attr_len = strlen(bridge);
			struct wsc_vendor_ie *ext;
			uint8_t offset = 0;
			uint8_t attr = 0x4d;
			uint32_t oui = 0;

			BUF_PUT_BE24(vendor_oui, EASYMESH_VENDOR_EXT_OUI_DEFAULT);
#ifdef EASYMESH_VENDOR_EXT_OUI
			oui = EASYMESH_VENDOR_EXT_OUI;
			BUF_PUT_BE24(vendor_oui, oui);
#endif
			ext = &ap->ven_ies[ap->num_ven_ies];

			memcpy(ext->oui, vendor_oui, 3);
			ext->len = 1 + 1 + attr_len;
			ext->payload = calloc(1, ext->len);
			if (ext->payload) {
				/* <attr:8><len:8><data> */
				memcpy(ext->payload,  &attr, 1); /* IOP enabled attr */
				offset += 1;
				memcpy(ext->payload + offset, &attr_len, 1); /* len */
				offset += 1;
				memcpy(ext->payload + offset, bridge, attr_len); /* val */

				ap->num_ven_ies++;
			}
		}
	}
#endif /* PROVISION_BRIDGE */
#endif /* EASYMESH_VENDOR_EXT */

	if (tb[CRED_VENDOR_IE]) {
		struct uci_element *x;

		uci_foreach_element(&tb[CRED_VENDOR_IE]->v.list, x) {
			char *vendor_ie = x->name;
			struct wsc_vendor_ie *ext;
			uint16_t len;
			uint8_t offset = 0;
			uint8_t *buf;

			if (ap->num_ven_ies >= VEN_IES_MAX) {
				dbg("at most %d vendor ies\n", VEN_IES_MAX);
				break;
			}

			len = strlen(vendor_ie);
			if (len % 2 != 0)
				continue;

			len = len / 2;
			if (len < 3) {
				dbg("payload len too short %d\n", len);
				continue;
			}

			buf = calloc(1, len);
			if (!buf)
				continue;

			strtob(vendor_ie, len, buf);

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

			memcpy(ext->oui, (uint8_t *) buf, 3);
			offset += 3;

			if (len > offset) {
				ext->len = len - offset;
				ext->payload = calloc(1, ext->len);
				if (!ext->payload) {
					free(buf);
					continue;
				}

				memcpy(ext->payload,
				       (uint8_t *) (buf + offset),
				       ext->len);
			}

			ap->num_ven_ies++;
			free(buf);
		}
	}

	c->num_bss++;
	list_add_tail(&ap->list, &c->aplist);

#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
	if (ap->multi_ap & 0x01) {
		struct controller *cntlr = container_of(c, struct controller, cfg);
		const char *sec;

		if (tb[CRED_SEC])
			sec =  tb[CRED_SEC]->v.string;
		else
			sec = "none";

		dpp_set_configuration(cntlr->dpp, DPP_NETROLE_MAP_BH_STA,
				      dpp_akm_from_str(sec),
				      (char *) ap->cred.ssid,
				      (char *) ap->cred.key,
				      ap->band);
	}
#endif
#endif

#if (EASYMESH_VERSION >= 6)
	if (tb[CRED_MLD_ID]) {
		errno = 0;
		ap->mld_id = strtol(tb[CRED_MLD_ID]->v.string, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing mld_id value: %s\n",
			    __func__, tb[CRED_MLD_ID]->v.string);
			return -1;
		}
	}
	if (tb[CRED_HIDDEN]) {
		errno = 0;
		ap->hidden = !!strtol(tb[CRED_HIDDEN]->v.string, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing hidden value: %s\n",
			    __func__, tb[CRED_HIDDEN]->v.string);
			return -1;
		}
	}
#endif
	return 0;
}

struct node_policy *cntlr_config_get_node_by_mac(struct controller_config *cfg,
							uint8_t *macaddr)
{
	struct node_policy *node = NULL;

	list_for_each_entry(node, &cfg->nodelist, list) {
		if (!memcmp(node->agent_id, macaddr, 6))
			return node;
	}

	return NULL;
}

static int cntlr_config_get_agent_node(struct controller_config *c,
						struct uci_section *s)
{
	enum {
		NODE_AGENT_ID,
		NODE_BK_UL_MAC,
		NODE_BK_DL_MAC,
		NODE_BK_TYPE,
		NODE_PVID,
		NODE_PCP,
		NODE_RPT_ASSOC_FAILS,
		NODE_RPT_ASSOC_FAILS_RATE,
		NODE_RPT_METRIC_PERIODIC,
		NODE_RPT_SCAN,
		NODE_STEER_EXCLUDE,
		NODE_STEER_EXCLUDE_BTM,
		NODE_STEER_DISALLOW,
		NODE_C_CAC,
		NODE_TRAFFIC_SEPARATION,
		NODE_STA_STEER,
		NUM_POLICIES,
	};
	const struct uci_parse_option opts[] = {
		[NODE_AGENT_ID] = { .name = "agent_id", .type = UCI_TYPE_STRING },
		[NODE_BK_UL_MAC] = { .name = "backhaul_ul_macaddr", .type = UCI_TYPE_STRING },
		[NODE_BK_DL_MAC] = { .name = "backhaul_dl_macaddr", .type = UCI_TYPE_STRING },
		[NODE_BK_TYPE] = { .name = "backhaul_type", .type = UCI_TYPE_STRING },
		[NODE_PVID] = { .name = "primary_vid", .type = UCI_TYPE_STRING },
		[NODE_PCP] = { .name = "primary_pcp", .type = UCI_TYPE_STRING },
		[NODE_RPT_ASSOC_FAILS] = { .name = "report_sta_assocfails", .type = UCI_TYPE_STRING },
		[NODE_RPT_ASSOC_FAILS_RATE] = { .name = "report_sta_assocfails_rate", .type = UCI_TYPE_STRING },
		[NODE_RPT_METRIC_PERIODIC] = { .name = "report_metric_periodic", .type = UCI_TYPE_STRING },
		[NODE_RPT_SCAN] = { .name = "report_scan", .type = UCI_TYPE_STRING },
		[NODE_STEER_EXCLUDE] = { .name = "steer_exclude", .type = UCI_TYPE_LIST },
		[NODE_STEER_EXCLUDE_BTM] = { .name = "steer_exclude_btm", .type = UCI_TYPE_LIST },
		[NODE_STEER_DISALLOW] = { .name = "steer_disallow", .type = UCI_TYPE_STRING },
		[NODE_C_CAC] = { .name = "coordinated_cac", .type = UCI_TYPE_STRING },
		[NODE_TRAFFIC_SEPARATION] = { .name = "traffic_separation", .type = UCI_TYPE_STRING },
		[NODE_STA_STEER] = { .name = "sta_steer", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_POLICIES];
	struct node_policy *a;
	struct uci_element *x;
	char *endptr = NULL;

	uci_parse_section(s, opts, NUM_POLICIES, tb);

	if (tb[NODE_AGENT_ID]) {
		a = calloc(1, sizeof(*a));
		if (!a)
			return -1;

		list_add(&a->list, &c->nodelist);

		INIT_LIST_HEAD(&a->radiolist);
		hwaddr_aton(tb[NODE_AGENT_ID]->v.string, a->agent_id);
		INIT_LIST_HEAD(&a->steer_exlist);
		INIT_LIST_HEAD(&a->btmsteer_exlist);

		a->pvid = 0;
	} else
		return -1;

	if (tb[NODE_BK_UL_MAC])
		hwaddr_aton(tb[NODE_BK_UL_MAC]->v.string, a->bk_ul_mac);

	if (tb[NODE_BK_DL_MAC])
		hwaddr_aton(tb[NODE_BK_DL_MAC]->v.string, a->bk_dl_mac);

	if (tb[NODE_BK_TYPE]) {
		char *type = tb[NODE_BK_TYPE]->v.string;

		if (strcmp(type, "none"))
			a->type = BK_TYPE_NONE;
		else
			a->type = BK_TYPE_NONE;
	}

	if (tb[NODE_PVID]) {
		errno = 0;
		a->pvid = strtoul(tb[NODE_PVID]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			return -1;
	}

	if (tb[NODE_PCP]) {
		errno = 0;
		a->pcp = strtoul(tb[NODE_PCP]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			return -1;
	}


	if (tb[NODE_RPT_ASSOC_FAILS]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_RPT_ASSOC_FAILS]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			/* 1 = true, 0 = false */
			a->report_sta_assocfails = (enbl == 1) ? true : false;
	}

	if (tb[NODE_RPT_ASSOC_FAILS_RATE]) {
		errno = 0;
		a->report_sta_assocfails_rate =
			strtol(tb[NODE_RPT_ASSOC_FAILS_RATE]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			return -1;
	}

	if (tb[NODE_STEER_EXCLUDE]) {
		uci_foreach_element(&tb[NODE_STEER_EXCLUDE]->v.list, x) {
			if (!stax_add_entry(&a->steer_exlist, x->name))
				continue;
			a->num_steer_stas++;
		}
	}

	if (tb[NODE_STEER_EXCLUDE_BTM]) {
		uci_foreach_element(&tb[NODE_STEER_EXCLUDE_BTM]->v.list, x) {
			if (!stax_add_entry(&a->btmsteer_exlist, x->name))
				continue;
			a->num_btmsteer_stas++;
		}
	}

	if (tb[NODE_RPT_SCAN]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_RPT_SCAN]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			/* 1 = true, 0 = false */
			a->report_scan = (enbl == 1) ? true : false;
	} else {
		a->report_scan = true;
	}

	if (tb[NODE_RPT_METRIC_PERIODIC]) {
		errno = 0;
		a->report_metric_periodic =
			strtol(tb[NODE_RPT_METRIC_PERIODIC]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			a->report_metric_periodic = 5;
	} else
		a->report_metric_periodic = 5;

	if (tb[NODE_STEER_DISALLOW]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_STEER_DISALLOW]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			/* 1 = true, 0 = false */
			a->steer_disallow = (enbl == 1) ? true : false;
	}

	if (tb[NODE_C_CAC]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_C_CAC]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			/* 1 = true, 0 = false */
			a->coordinated_cac = (enbl == 1) ? true : false;
	}

	if (tb[NODE_TRAFFIC_SEPARATION]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_TRAFFIC_SEPARATION]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			/* 1 = true, 0 = false */
			a->traffic_separation = (enbl == 1) ? true : false;
	}

	if (tb[NODE_STA_STEER]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_STA_STEER]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			/* 1 = true, 0 = false */
			a->sta_steer = (enbl == 1) ? true : false;
	}

	return 0;
}

static int cntlr_config_get_agent_radio(struct controller_config *cc,
						struct uci_section *s)
{
	enum {
		RADIO_AGENT,
		RADIO_MAC,
		RADIO_BAND,
		RADIO_DIS_OPCLASS,
		RADIO_STEER_POLICY,
		RADIO_UTIL_TH,
		RADIO_RCPI_TH,
		RADIO_RPT_RCPI_TH,
		RADIO_RPT_UTIL_TH,
		RADIO_RPT_HYS_MARGIN,
		RADIO_INCL_STA_STATS,
		RADIO_INCL_STA_METRIC,
#if (EASYMESH_VERSION > 2)
		RADIO_INCL_WIFI6_STA_STATUS,
#endif
#if (EASYMESH_VERSION >= 6)
		RADIO_PUNCTURE_BITMAP,
#endif
		NUM_POLICIES,
	};
	const struct uci_parse_option opts[] = {
		[RADIO_AGENT] = { .name = "agent_id", .type = UCI_TYPE_STRING },
		[RADIO_MAC] = { .name = "macaddr", .type = UCI_TYPE_STRING },
		[RADIO_BAND] = { .name = "band", .type = UCI_TYPE_STRING },
		[RADIO_DIS_OPCLASS] = { .name = "disallow_opclass", .type = UCI_TYPE_LIST },
		[RADIO_STEER_POLICY] = { .name = "steer_policy", .type = UCI_TYPE_STRING },
		[RADIO_UTIL_TH] = { .name = "util_threshold", .type = UCI_TYPE_STRING },
		[RADIO_RCPI_TH] = { .name = "rcpi_threshold", .type = UCI_TYPE_STRING },
		[RADIO_RPT_RCPI_TH] = { .name = "report_rcpi_threshold", .type = UCI_TYPE_STRING },
		[RADIO_RPT_UTIL_TH] = { .name = "report_util_threshold", .type = UCI_TYPE_STRING },
		[RADIO_RPT_HYS_MARGIN] = { .name = "report_rcpi_hysteresis_margin", .type = UCI_TYPE_STRING },
		[RADIO_INCL_STA_STATS] = { .name = "include_sta_stats", .type = UCI_TYPE_STRING },
		[RADIO_INCL_STA_METRIC] = { .name = "include_sta_metric", .type = UCI_TYPE_STRING },
#if (EASYMESH_VERSION > 2)
		[RADIO_INCL_WIFI6_STA_STATUS] = { .name = "include_wifi6_sta_status", .type = UCI_TYPE_STRING },
#endif
#if (EASYMESH_VERSION >= 6)
		[RADIO_PUNCTURE_BITMAP] = { .name = "punct_bitmap", .type = UCI_TYPE_STRING },
#endif
	};
	struct uci_option *tb[NUM_POLICIES];
	struct steer_policy *sp = &cc->steer;
	struct radio_policy *rp;
	int band;
	char *endptr = NULL;

	uci_parse_section(s, opts, NUM_POLICIES, tb);

	if (!tb[RADIO_AGENT] || !tb[RADIO_MAC] || !tb[RADIO_BAND]) {
		warn("%s: invalid radio config! Must hold agent_id, macaddr and band", __func__);
		return -1;
	}

	errno = 0;
	band = strtol(tb[RADIO_BAND]->v.string, &endptr, 10);
	if (errno || *endptr != '\0') {
		err("%s: Error parsing band value: %s\n",
		    __func__, tb[RADIO_BAND]->v.string);
		return -1;
	}

	if (band != 2 && band != 5 && band != 6)
		return -1;

	rp = calloc(1, sizeof(*rp));
	if (!rp)
		return -1;

	hwaddr_aton(tb[RADIO_AGENT]->v.string, rp->agent_id);
	hwaddr_aton(tb[RADIO_MAC]->v.string, rp->macaddr);
	rp->band = band;

	switch (band) {
	case 5:
		rp->band = BAND_5;
		rp->rcpi_threshold = sp->rcpi_threshold_5g;
		rp->report_rcpi_threshold = sp->report_rcpi_threshold_5g;
		break;
	case 6:
		rp->band = BAND_6;
		rp->rcpi_threshold = sp->rcpi_threshold_6g;
		rp->report_rcpi_threshold = sp->report_rcpi_threshold_6g;
		break;
	case 2:
	default:
		rp->band = BAND_2;
		rp->rcpi_threshold = sp->rcpi_threshold_2g;
		rp->report_rcpi_threshold = sp->report_rcpi_threshold_2g;
		break;
	}

	rp->include_sta_stats = true;
	rp->include_sta_metric = true;
	rp->dis_opclass.num_opclass = 0;

	list_add(&rp->list, &cc->radiolist);

	if (tb[RADIO_DIS_OPCLASS]) {
		struct uci_element *x;

		uci_foreach_element(&tb[RADIO_DIS_OPCLASS]->v.list, x) {
			struct wifi_radio_opclass_entry *entry;
			const char *ptr = x->name;
			char *endptr = NULL;
			long val;

			if (rp->dis_opclass.num_opclass >= 64) {
				warn("Too many disallow opclasses (max 64)");
				break;
			}

			entry = &rp->dis_opclass.opclass[rp->dis_opclass.num_opclass];

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

			errno = 0;
			val = strtol(ptr, &endptr, 10);
			if (errno || endptr == ptr || val <= 0 || val > 255) {
				warn("Invalid opclass value: %s", x->name);
				continue;
			}

			entry->id = (uint8_t)val;
			ptr = endptr;

			if (*ptr == '\0') {
				entry->num_channel = 0;
				rp->dis_opclass.num_opclass++;
				continue;
			}

			if (*ptr != ':') {
				warn("Invalid disallow_opclass format: %s", x->name);
				continue;
			}

			ptr++; /* skip ':' */

			while (*ptr != '\0') {
				if (entry->num_channel >= 64) {
					warn("Too many channels for opclass %u", entry->id);
					break;
				}

				errno = 0;
				val = strtol(ptr, &endptr, 10);
				if (errno || endptr == ptr || val <= 0 || val > 255) {
					warn("Invalid channel in: %s", x->name);
					break;
				}

				entry->channel[entry->num_channel].channel = (uint8_t)val;
				entry->num_channel++;

				ptr = endptr;

				if (*ptr == ',')
					ptr++;      /* next channel */
			}

			rp->dis_opclass.num_opclass++;
		}
	}

	if (tb[RADIO_STEER_POLICY]) {
		errno = 0;
		rp->policy = strtol(tb[RADIO_STEER_POLICY]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing steer_policy value: %s\n",
			    __func__, tb[RADIO_STEER_POLICY]->v.string);
	}


	if (tb[RADIO_RCPI_TH]) {
		int val;

		errno = 0;
		val = strtol(tb[RADIO_RCPI_TH]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing rcpi_threshold value: %s\n",
			    __func__, tb[RADIO_RCPI_TH]->v.string);
		else if (val >= 0 && val <= 220)
			rp->rcpi_threshold = val;
	}

	if (tb[RADIO_RPT_RCPI_TH]) {
		int val;

		errno = 0;
		val = strtol(tb[RADIO_RPT_RCPI_TH]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing report_rcpi_threshold value: %s\n",
			    __func__, tb[RADIO_RPT_RCPI_TH]->v.string);
		else if (val >= 0 && val <= 220)
			rp->report_rcpi_threshold = val;
	}

	if (tb[RADIO_UTIL_TH]) {
		int val;

		errno = 0;
		val = strtol(tb[RADIO_UTIL_TH]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing util_threshold value: %s\n",
			    __func__, tb[RADIO_UTIL_TH]->v.string);
		else if (val > 0 && val <= 255)
			rp->util_threshold = val;
	}

	if (tb[RADIO_RPT_UTIL_TH]) {
		int val;

		errno = 0;
		val = strtol(tb[RADIO_RPT_UTIL_TH]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing report_util_threshold value: %s\n",
			    __func__, tb[RADIO_RPT_UTIL_TH]->v.string);
		else if (val > 0 && val <= 255)
			rp->report_util_threshold = val;
	}

	if (tb[RADIO_RPT_HYS_MARGIN]) {
		int val;

		errno = 0;
		val = strtol(tb[RADIO_RPT_HYS_MARGIN]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing report_rcpi_hysteresis_margin value: %s\n",
			    __func__, tb[RADIO_RPT_HYS_MARGIN]->v.string);
		else if (rp->report_rcpi_threshold - (2 * val) > 0 &&
		    rp->report_rcpi_threshold + (2 * val) <= 220) {
			rp->report_rcpi_hysteresis_margin = val;
		}
	}

	if (tb[RADIO_INCL_STA_STATS]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[RADIO_INCL_STA_STATS]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing include_sta_stats value: %s\n",
			    __func__, tb[RADIO_INCL_STA_STATS]->v.string);
		else
			/* 1 = true, 0 = false */
			rp->include_sta_stats = (enbl == 1) ? true : false;
	}

	if (tb[RADIO_INCL_STA_METRIC]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[RADIO_INCL_STA_METRIC]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing include_sta_metric value: %s\n",
			    __func__, tb[RADIO_INCL_STA_METRIC]->v.string);
		else
			/* 1 = true, 0 = false */
			rp->include_sta_metric = (enbl == 1) ? true : false;
	}

#if (EASYMESH_VERSION > 2)
	if (tb[RADIO_INCL_WIFI6_STA_STATUS]) {
		int enbl;

		errno = 0;
		enbl = strtol(tb[RADIO_INCL_WIFI6_STA_STATUS]->v.string, &endptr, 10);
		if (errno || *endptr != '\0')
			err("%s: Error parsing include_wifi6_sta_status value: %s\n",
			    __func__, tb[RADIO_INCL_WIFI6_STA_STATUS]->v.string);
		else
			/* 1 = true, 0 = false */
			rp->include_wifi6_sta_status = (enbl == 1) ? true : false;
	}
#endif

#if (EASYMESH_VERSION >= 6)
	if (tb[RADIO_PUNCTURE_BITMAP]) {
		const char *val = tb[RADIO_PUNCTURE_BITMAP]->v.string;
		int len = strlen(val);

		if (len <= 4) {
			const char *initialbuf = "0000";
			char buf[5];

			strncpy(buf, initialbuf, sizeof(buf) - 1);
			buf[sizeof(buf) - 1] = '\0';

			strncpy(buf + (4 - strlen(val)), val, strlen(val));
			strtob(buf, 2, (unsigned char *)&rp->punct_bitmap);
		}
	}
#endif
	return 0;
}

#if (EASYMESH_VERSION >= 6)
/*
config mld
	option id '1'
	option ssid 'unique-ssid1'
	option encryption 'sae'
	option key '1234567890'
#	option vid '1'
	option type 'fronthaul'
*/
int cntlr_config_get_mld(struct controller_config *c,
			 struct uci_section *s)
{
	enum {
		MLD_ENABLED,
		MLD_ID,
		MLD_SSID,
		MLD_KEY,
		MLD_SEC,
		MLD_VLAN,
		MLD_TYPE,
		NUM_MLD_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[MLD_ENABLED] = { .name = "enabled", .type = UCI_TYPE_STRING },
		[MLD_ID] = { .name = "id", .type = UCI_TYPE_STRING },
		[MLD_SSID] = { .name = "ssid", .type = UCI_TYPE_STRING },
		[MLD_KEY] = { .name = "key", .type = UCI_TYPE_STRING },
		[MLD_SEC] = { .name = "encryption", .type = UCI_TYPE_STRING },
		[MLD_VLAN] = { .name = "vid", .type = UCI_TYPE_STRING },
		[MLD_TYPE] = { .name = "type", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_MLD_ATTRS];
	struct mld_credential *mld;
	char *endptr = NULL;

	if (c->num_mlds >= 32)
		return -1;

	mld = calloc(1, sizeof(*mld));
	if (!mld)
		return -1;

	uci_parse_section(s, opts, NUM_MLD_ATTRS, tb);

	if (tb[MLD_ENABLED]) {
		const char *val = tb[MLD_ENABLED]->v.string;
		int enbl;

		errno = 0;
		enbl = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			//warn("|%s:%d| Error parsing enabled value: %s\n",
			//		__func__, __LINE__, tb[MLD_ENABLED]->v.string);
			free(mld);
			return -1;
		}

		mld->enabled = !!enbl;
		if (mld->enabled == false) {
			free(mld);
			return -1;
		}
	} else {
		mld->enabled = true;
	}

	if (tb[MLD_ID]) {
		const char *val = tb[MLD_ID]->v.string;

		errno = 0;
		mld->id = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing id value: %s\n",
			    __func__, tb[MLD_ID]->v.string);
			free(mld);
			return -1;
		}
	} else {
		err("%s: MLD section MLD ID must be defined\n", __func__);
		free(mld);
		return -1;
	}

	if (tb[MLD_SSID]) {
		strncpy((char *) mld->cred.ssid, tb[MLD_SSID]->v.string, 32);
		mld->cred.ssidlen = strlen((char *)mld->cred.ssid);
	}

	if (tb[MLD_SEC]) {
		const char *sec = tb[MLD_SEC]->v.string;

		if (!strncmp(sec, "sae-mixed", 9)) {
			mld->sec |= BIT(WIFI_SECURITY_WPA3PSK);
			mld->sec |= BIT(WIFI_SECURITY_WPA3PSK_T);
		} else if (!strncmp(sec, "sae", 3)
#if (EASYMESH_VERSION > 2)
			   || !strncmp(sec, "dpp", 3) ||
			   !strncmp(sec, "dpp+sae", 3) ||
			   !strncmp(sec, "sae+dpp", 3)
#endif
			   ) {
			mld->sec |= BIT(WIFI_SECURITY_WPA3PSK); /* TODO: how do we properly transfer DPP encryption */
		} else if (!strncmp(sec, "psk-mixed", 9)) {
			mld->sec |= BIT(WIFI_SECURITY_WPAPSK);
			mld->sec |= BIT(WIFI_SECURITY_WPA2PSK);
		} else if (!strncmp(sec, "psk2", 4)) {
			mld->sec |= BIT(WIFI_SECURITY_WPA2PSK);
		} else if (!strncmp(sec, "psk", 3)) {
			mld->sec |= BIT(WIFI_SECURITY_WPAPSK);
		} else if (!strncmp(sec, "wpa-mixed", 9)) {
			mld->sec |= BIT(WIFI_SECURITY_WPA);
			mld->sec |= BIT(WIFI_SECURITY_WPA2);
		} else if (!strncmp(sec, "wpa2", 4)) {
			mld->sec |= BIT(WIFI_SECURITY_WPA2);
		} else if (!strncmp(sec, "wpa", 3)) {
			mld->sec |= BIT(WIFI_SECURITY_WPA);
		} else if (!strncmp(sec, "none", 4)) {
			mld->sec |= BIT(WIFI_SECURITY_NONE);
		} else if (!strncmp(sec, "open", 4)) {
			mld->sec |= BIT(WIFI_SECURITY_NONE);
		} else {
			free(mld);
			return -1;
		}

		//TODO: ciphers (if any)

		mld->cred.auth_type = wifi_sec_to_auth_types(mld->sec);
		if (mld->cred.auth_type == WPS_AUTH_OPEN)
			mld->cred.enc_type = WPS_ENCR_NONE;
		else /* by default use encryption type AES */
			mld->cred.enc_type = WPS_ENCR_AES; /* TODO: TKIP? */
	}
	/* if encryption is not provided fallback to APs encryption
	 * during wsc generation
	 */

	if (tb[MLD_KEY]) {
		strncpy((char *) mld->cred.key, tb[MLD_KEY]->v.string, 64);
		mld->cred.keylen = strlen((char *)mld->cred.key);
	}

	if (tb[MLD_VLAN]) {
		errno = 0;
		mld->vlanid = strtol(tb[MLD_VLAN]->v.string, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing vid value: %s\n",
			    __func__, tb[MLD_VLAN]->v.string);
			free(mld);
			return -1;
		}
		if (!is_vid_valid(mld->vlanid)) {
			err("%s: cred:%s vid:%d is not valid\n", __func__,
			    mld->cred.ssid, mld->vlanid);
			free(mld);
			return -1;
		}
	} else {
		/* if missing option vid in ap section use 1 as default */
		mld->vlanid = 1;
	}

	if (tb[MLD_TYPE]) {
		const char *type = tb[MLD_TYPE]->v.string;

		if (!strcmp(type, "backhaul")) {
			mld->multi_ap = 1;
		} else if (!strcmp(type, "fronthaul")) {
			mld->multi_ap = 2;
		} else if (!strcmp(type, "combined")) {
			mld->multi_ap = 3;
		} else {
			free(mld);
			return -1;
		}
	} else {
		mld->multi_ap = 2; /* default to fhbss */
	}

	mld->cred.mapie = (mld->multi_ap & 0x01) << 6;
	mld->cred.mapie |= (mld->multi_ap & 0x02) << 4;
	//mld->cred.mapie |= (mld->disallow_bsta << 2);



	if (cntlr_config_get_mld_by_id(c, mld->id) ||
	    cntlr_config_get_mld_by_ssid(c, (char *)mld->cred.ssid)) {
		warn("%s: MLD ssid:%s or id:%d is not unique, dropping\n",
		     __func__, mld->cred.ssid, mld->id);
		free(mld);
		return -1;
	}

	c->num_mlds++;
	list_add(&mld->list, &c->mldlist);

	return 0;
}

static void config_map_ap_to_mld(struct controller_config *cfg)
{
	struct iface_credential *cred = NULL, *tmp = NULL;

	list_for_each_entry_safe(cred, tmp, &cfg->aplist, list) {
		struct mld_credential *mld;

		if (cred->mld_id == 0) {
			if (cntlr_config_get_mld_by_ssid(cfg, (char *)cred->cred.ssid)) {
				/** AP must not have same SSID as an MLD without
				 *  being a part of it, discard entry
				 **/
				cfg->num_bss--;
				clean_vendor_ies(cred);
				list_del(&cred->list);
				free(cred);
			}

			continue;
		}

		mld = cntlr_config_get_mld_by_id(cfg, cred->mld_id);
		if (mld) {
			if (cred->band == BAND_6 && mld->sec != 0 &&
			    mld->sec != BIT(WIFI_SECURITY_WPA3PSK)) {
				warn("%s: MLD does not use SAE encryption,"\
				     " excluding 6GHz AP from mld:%u\n",
				     __func__, mld->id);
				continue;
			}
			mld->num_affiliated_aps++;
		}
	}
}


#endif

static void config_map_radios_to_node(struct controller_config *cfg)
{
	struct radio_policy *r = NULL, *tmp;

	list_for_each_entry_safe(r, tmp, &cfg->radiolist, list) {
		struct node_policy *n;

		n = cntlr_config_get_node_by_mac(cfg, r->agent_id);
		if (!n) {
			list_del(&r->list);
			free(r);
			continue;
		}

		list_del(&r->list);
		list_add(&r->list, &n->radiolist);
	}
}

uint32_t cntlr_policy_lists_diff(struct list_head *prev_policylist,
		struct list_head *curr_policylist)
{
	uint32_t diff = 0;
	struct node_policy *prev, *curr;

	list_for_multiple_entry(prev, curr, prev_policylist, curr_policylist, list, list) {
#if (EASYMESH_VERSION >= 6)
		struct radio_policy *prev_rp, *curr_rp;
#endif

		if (list_memcmp(&prev->steer_exlist, &curr->steer_exlist,
				struct stax, offsetof(struct stax, list))) {
			trace("steer_exlist differ\n");
			curr->is_policy_diff = 1;
			diff |= CONFIG_DIFF_AGENT_POLICY;
		} else if (list_memcmp(&prev->btmsteer_exlist, &curr->btmsteer_exlist,
					struct stax, offsetof(struct stax, list))) {
			trace("btmsteer_exlist differ\n");
			curr->is_policy_diff = 1;
			diff |= CONFIG_DIFF_AGENT_POLICY;
		}

#if (EASYMESH_VERSION >= 6)
		list_for_multiple_entry(prev_rp, curr_rp, &prev->radiolist, &curr->radiolist, list, list) {
			if (memcmp(prev_rp->punct_bitmap, curr_rp->punct_bitmap, sizeof(prev_rp->punct_bitmap))) {
				diff |= CONFIG_DIFF_PUNCT_BITMAP;
			}
		}
#endif
	}

	return diff;
}

uint32_t cntlr_vendor_ies_diff(struct controller_config *pcfg,
			      struct controller_config *ccfg)
{
	struct iface_credential *prev, *curr;
	uint32_t diff = 0;

	list_for_multiple_entry(prev, curr, &pcfg->aplist, &ccfg->aplist, list, list) {
		if (prev->num_ven_ies != curr->num_ven_ies) {
			dbg("num of vendor ies differ\n");
			diff |= CONFIG_DIFF_CREDENTIALS;
			break;
		} else {
			int i;

			for (i = 0; i < curr->num_ven_ies; i++) {
				struct wsc_vendor_ie *curr_ie, *prev_ie;

				curr_ie = &curr->ven_ies[i];
				prev_ie = &prev->ven_ies[i];

				if (prev_ie->len != curr_ie->len) {
					diff |= CONFIG_DIFF_CREDENTIALS;
					return diff;
				}

				if (memcmp(curr_ie->payload, prev_ie->payload, prev_ie->len)) {
					diff |= CONFIG_DIFF_CREDENTIALS;
					return diff;
				}
			}
		}
	}

	return diff;
}

uint32_t cntlr_creds_diff(struct controller_config *cfg,
		struct controller_config *prev)
{
	uint32_t diff = 0;


	/* credentials diff */
	if (prev->num_bss != cfg->num_bss) {
#if (EASYMESH_VERSION >= 6)
		int prev_num_affaps = 0;
		int curr_num_affaps = 0;
		struct mld_credential *mld;

		list_for_each_entry(mld, &prev->mldlist, list)
			prev_num_affaps += mld->num_affiliated_aps;
		list_for_each_entry(mld, &cfg->mldlist, list)
			curr_num_affaps += mld->num_affiliated_aps;

		/** if only affiliated APs are different, then handle via
		 *  AP/BSTA MLD Config Request messages
		 */
		if ((prev->num_bss - cfg->num_bss) != (prev_num_affaps - curr_num_affaps))
			diff |= CONFIG_DIFF_CREDENTIALS;
#else
		dbg("|%s:%d| number of credentials differed\n", __func__, __LINE__);
		diff |= CONFIG_DIFF_CREDENTIALS;
#endif
	} else if (list_memcmp(&prev->aplist, &cfg->aplist,
			       struct iface_credential,
			       offsetof(struct iface_credential, list)
			)) {
		dbg("|%s:%d| bss credentials have changed\n", __func__, __LINE__);
		diff |= CONFIG_DIFF_CREDENTIALS;
	}
#if (EASYMESH_VERSION >= 6)
	else if (list_memcmp(&prev->mldlist, &cfg->mldlist,
		   struct mld_credential,
		   offsetof(struct mld_credential, list))) {
		dbg("|%s:%d| MLD credentials have changed\n", __func__, __LINE__);
		diff |= CONFIG_DIFF_CREDENTIALS;
	}
#endif
	else {
		/* compare vendor ie list */
		diff |= cntlr_vendor_ies_diff(prev, cfg);
	}

	return diff;
}

#if (EASYMESH_VERSION > 2)
uint32_t cntlr_qos_diff(struct controller_config *cfg,
		struct controller_config *prev)
{
	uint32_t diff = 0;

	if (prev->qos.enabled != cfg->qos.enabled) {
		dbg("|%s:%d| qos enable state changed\n", __func__, __LINE__);
		diff |= CONFIG_DIFF_QOS;
	}

	return diff;
}

uint32_t cntlr_qos_rule_diff(struct controller_config *cfg,
		struct controller_config *prev)
{
	uint32_t diff = 0;

	if (list_memcmp(&prev->qos.rule, &cfg->qos.rule,
			struct qos_rule,
			offsetof(struct qos_rule, list))) {
		dbg("|%s:%d| qos rules have changed\n", __func__, __LINE__);
		diff |= CONFIG_DIFF_QOS;
	}

	return diff;
}

uint32_t cntlr_qos_mgmt_policy_diff(struct controller_config *cfg,
		struct controller_config *prev,
		bool mscs)
{
	uint32_t diff = 0;

	if (list_memcmp(mscs ? &prev->qos.policy.mscs_disallowed
			     : &prev->qos.policy.scs_disallowed,
			mscs ? &cfg->qos.policy.mscs_disallowed
			     : &cfg->qos.policy.scs_disallowed,
			struct qos_mgmt_policy_elem,
			offsetof(struct qos_mgmt_policy_elem, list))) {
		dbg("|%s:%d| qos mgmt policies have changed\n", __func__, __LINE__);
		diff |= CONFIG_DIFF_QOS;
	}

	return diff;
}

/* QoS-related sections */
static int cntlr_config_get_qos_rule_dscp_pcp(struct controller_config *cc,
	struct uci_section *s,
	struct tlv_dscp_pcp *dscp_pcp)
{
	enum {
		QOS_RULE_DSCP_PCP_DSCP_PCP,
		NUM_QOS_RULE_DSCP_PCP_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_RULE_DSCP_PCP_DSCP_PCP] = {
			.name = "dscp_pcp",
			.type = UCI_TYPE_LIST
		},
	};
	struct uci_option *tb[NUM_QOS_RULE_DSCP_PCP_ATTRS];

	memset(dscp_pcp, 0, sizeof(struct tlv_dscp_pcp));

	uci_parse_section(s, opts, NUM_QOS_RULE_DSCP_PCP_ATTRS, tb);


	if (tb[QOS_RULE_DSCP_PCP_DSCP_PCP]) {
		struct uci_element *x;

		uci_foreach_element(&tb[QOS_RULE_DSCP_PCP_DSCP_PCP]->v.list, x) {
			int dscp_min = 0;
			int dscp_max = 0;
			int pcp = 0;

			if (sscanf(x->name, "%d-%d,%d", &dscp_min, &dscp_max, &pcp) == 3) {
				if (pcp <= 7 && pcp >= 0 && dscp_min >= 0 && dscp_max >= 0 &&
				    dscp_min < dscp_max &&
				    dscp_min < (sizeof(dscp_pcp->dscp_pcp) /
				                sizeof(dscp_pcp->dscp_pcp[0])) &&
				    dscp_max < (sizeof(dscp_pcp->dscp_pcp) /
				                sizeof(dscp_pcp->dscp_pcp[0]))) {
					uint8_t dscp;

					for (dscp = dscp_min; dscp <= dscp_max; ++dscp) {
						dscp_pcp->dscp_pcp[dscp] = pcp;
					}
				}
			} else if (sscanf(x->name, "%d,%d", &dscp_min, &pcp) == 2) {
				if (pcp <= 7 && pcp >= 0 && dscp_min >= 0 &&
				    dscp_min < (sizeof(dscp_pcp->dscp_pcp) /
				                sizeof(dscp_pcp->dscp_pcp[0]))) {
					dscp_pcp->dscp_pcp[dscp_min] = pcp;
				}
			}
		}
	}

	return 0;
}

static int cntlr_config_get_qos_rule_mscs(struct controller_config *cc,
                                          struct uci_section *s,
                                          struct mscs_desc_usr *mscs)
{
	enum {
		QOS_RULE_MSCS_UP_BITMAP,
		QOS_RULE_MSCS_UP_LIMIT,
		QOS_RULE_MSCS_STREAM_TIMEOUT,
		NUM_QOS_RULE_MSCS_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_RULE_MSCS_UP_BITMAP] = {
			.name = "up_bitmap",
			.type = UCI_TYPE_STRING },
		[QOS_RULE_MSCS_UP_LIMIT] = {
			.name = "up_limit",
			.type = UCI_TYPE_STRING },
		[QOS_RULE_MSCS_STREAM_TIMEOUT] = {
			.name = "stream_timeout",
			.type = UCI_TYPE_STRING
		},
	};
	struct uci_option *tb[NUM_QOS_RULE_MSCS_ATTRS];

	memset(mscs, 0, sizeof(struct mscs_desc_usr));

	uci_parse_section(s, opts, NUM_QOS_RULE_MSCS_ATTRS, tb);

#define TRANSFER_INT(__val_en, __field) \
	if (tb[(__val_en)]) {                               \
	    const char *val = tb[(__val_en)]->v.string;     \
		char *endptr = NULL;						            \
		mscs->__field = strtol(val, &endptr, 10);       \
	}

	TRANSFER_INT(QOS_RULE_MSCS_UP_BITMAP, up_bitmap);
	TRANSFER_INT(QOS_RULE_MSCS_UP_LIMIT, up_limit);
	TRANSFER_INT(QOS_RULE_MSCS_STREAM_TIMEOUT, stream_timeout);

#undef TRANSFER_INT

	return 0;
}

static int cntlr_config_get_qos_rule_scs(struct controller_config *cc,
                                         struct uci_section *s,
                                         struct scs_desc_usr *scs)
{
	enum {
		QOS_RULE_SCS_ID,
		QOS_RULE_SCS_INTRA_ACCESS_PRIORITY,
		QOS_RULE_SCS_PROCESSING,
		NUM_QOS_RULE_SCS_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_RULE_SCS_ID] = {
			.name = "id",
			.type = UCI_TYPE_STRING
		},
		[QOS_RULE_SCS_INTRA_ACCESS_PRIORITY] = {
			.name = "intra_access_priority",
			.type = UCI_TYPE_STRING
		},
		[QOS_RULE_SCS_PROCESSING] = {
			.name = "processing",
			.type = UCI_TYPE_STRING
		},
	};
	struct uci_option *tb[NUM_QOS_RULE_SCS_ATTRS];
	char *endptr = NULL;

	memset(scs, 0, sizeof(struct scs_desc_usr));

	uci_parse_section(s, opts, NUM_QOS_RULE_SCS_ATTRS, tb);

	if (tb[QOS_RULE_SCS_ID]) {
		errno = 0;
		scs->scsid = strtol(tb[QOS_RULE_SCS_ID]->v.string, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing scs id value: %s\n",
			    __func__, tb[QOS_RULE_SCS_ID]->v.string);
			return -1;
		}
	}

	if (tb[QOS_RULE_SCS_INTRA_ACCESS_PRIORITY]) {
		errno = 0;
		scs->intra_access_category_priority =
			strtol(tb[QOS_RULE_SCS_INTRA_ACCESS_PRIORITY]->v.string,
			       &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing intra_access_category_priority value: %s\n",
			    __func__, tb[QOS_RULE_SCS_INTRA_ACCESS_PRIORITY]->v.string);
			return -1;
		}
	}

	if (tb[QOS_RULE_SCS_PROCESSING]) {
		errno = 0;
		scs->processing = strtol(tb[QOS_RULE_SCS_PROCESSING]->v.string,
					 &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing processing value: %s\n",
			    __func__, tb[QOS_RULE_SCS_PROCESSING]->v.string);
			return -1;
		}
		scs->processing_present = true;
	}

	return 0;
}

static int cntlr_config_get_qos_rule_qm_dscp(struct controller_config *cc,
	struct uci_section *s,
	struct qos_mgmt_attr_usr *qm)
{
	enum {
		QOS_RULE_QM_DSCP_POLICY_ID,
		QOS_RULE_QM_DSCP_DSCP,
		NUM_QOS_RULE_QM_DSCP_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_RULE_QM_DSCP_POLICY_ID] = {
			.name = "policy_id",
			.type = UCI_TYPE_STRING },
		[QOS_RULE_QM_DSCP_DSCP] = {
			.name = "dscp",
			.type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_QOS_RULE_QM_DSCP_ATTRS];
	char *endptr = NULL;

	memset(&qm->dscp_policy, 0, sizeof(qm->dscp_policy));

	uci_parse_section(s, opts, NUM_QOS_RULE_QM_DSCP_ATTRS, tb);

	if (tb[QOS_RULE_QM_DSCP_POLICY_ID]) {
		const char *val = tb[QOS_RULE_QM_DSCP_POLICY_ID]->v.string;

		errno = 0;
		qm->dscp_policy.dscp_policy_id =
			strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing dscp_policy_id value: %s\n",
			    __func__, tb[QOS_RULE_QM_DSCP_POLICY_ID]->v.string);
			return -1;
		}
	}

	if (tb[QOS_RULE_QM_DSCP_DSCP]) {
		const char *val = tb[QOS_RULE_QM_DSCP_DSCP]->v.string;

		errno = 0;
		qm->dscp_policy.dscp =
			strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing dscp value: %s\n",
			    __func__, tb[QOS_RULE_QM_DSCP_DSCP]->v.string);
			return -1;
		}
	}

	return 0;
}

static int cntlr_config_get_qos_rule_qm(struct controller_config *cc,
                                        struct uci_section *s,
                                        struct qos_mgmt_attr_usr *qm)
{
	int ret = 0;
	enum {
		QOS_RULE_QM_TYPE,
		NUM_QOS_RULE_QM_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_RULE_QM_TYPE] = { .name = "policy_type", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_QOS_RULE_QM_ATTRS];

	memset(qm, 0, sizeof(struct qos_mgmt_attr_usr));

	uci_parse_section(s, opts, NUM_QOS_RULE_QM_ATTRS, tb);

	if (tb[QOS_RULE_QM_TYPE]) {
		const char *val = tb[QOS_RULE_QM_TYPE]->v.string;

		if (strcmp(val, "port_range") == 0)
			qm->attribute_id = QOS_MGMT_ATTR_ID_PORT_RANGE;
		else if (strcmp(val, "dscp_policy") == 0)
			qm->attribute_id = QOS_MGMT_ATTR_ID_DSCP_POLICY;
		else if (strcmp(val, "tclas") == 0)
			qm->attribute_id = QOS_MGMT_ATTR_ID_TCLAS;
		else if (strcmp(val, "domain_name") == 0)
			qm->attribute_id = QOS_MGMT_ATTR_ID_DOMAIN_NAME;
	}

	switch (qm->attribute_id)
	{
		case QOS_MGMT_ATTR_ID_DSCP_POLICY:
		{
			ret = cntlr_config_get_qos_rule_qm_dscp(cc, s, qm);
			break;
		}
		default:
		{
			ret = -1;
			break;
		}
	}

	return ret;
}

static int cntlr_config_get_qos_rule_mgmt(struct controller_config *cc,
                                          struct uci_section *s,
                                          struct qos_rule *rule)
{
	int ret = 0;
	enum {
		QOS_MGMT_RULE_QMID,
		QOS_MGMT_RULE_BSSID,
		QOS_MGMT_RULE_STA,
		NUM_QOS_MGMT_RULE_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_MGMT_RULE_QMID] = { .name = "qmid", .type = UCI_TYPE_STRING },
		[QOS_MGMT_RULE_BSSID] = { .name = "bssid", .type = UCI_TYPE_STRING },
		[QOS_MGMT_RULE_STA] = { .name = "sta", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_QOS_MGMT_RULE_ATTRS];

	uci_parse_section(s, opts, NUM_QOS_MGMT_RULE_ATTRS, tb);

	if (tb[QOS_MGMT_RULE_QMID]) {
		const char *val = tb[QOS_MGMT_RULE_QMID]->v.string;
		char *endptr = NULL;

		errno = 0;
		rule->mgmt.qmid = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing qmid value: %s\n",
			    __func__, tb[QOS_MGMT_RULE_QMID]->v.string);
			return -1;
		}
	}

	if (tb[QOS_MGMT_RULE_BSSID]) {
		hwaddr_aton(tb[QOS_MGMT_RULE_BSSID]->v.string, rule->mgmt.bssid);
	}

	if (tb[QOS_MGMT_RULE_STA]) {
		hwaddr_aton(tb[QOS_MGMT_RULE_STA]->v.string, rule->mgmt.sta);
	}

	return ret;
}

static int cntlr_config_get_qos_rule(struct controller_config *cc,
                                     struct uci_section *s)
{
	int ret = 0;
	enum {
		QOS_RULE_TYPE,
		QOS_RULE_ID,
		QOS_RULE_PRECEDENCE,
		QOS_RULE_OUTPUT,
		QOS_RULE_ALWAYS_MATCH,
		NUM_QOS_RULE_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_RULE_TYPE] = { .name = "type", .type = UCI_TYPE_STRING },
		[QOS_RULE_ID] = { .name = "id", .type = UCI_TYPE_STRING },
		[QOS_RULE_PRECEDENCE] = { .name = "precedence", .type = UCI_TYPE_STRING },
		[QOS_RULE_OUTPUT] = { .name = "output", .type = UCI_TYPE_STRING },
		[QOS_RULE_ALWAYS_MATCH] = { .name = "always_match", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_QOS_RULE_ATTRS];
	struct qos_rule *rule;
	char *endptr = NULL;
	uint32_t id = 0;

	uci_parse_section(s, opts, NUM_QOS_RULE_ATTRS, tb);

	if (tb[QOS_RULE_ID]) {
		const char *val = tb[QOS_RULE_ID]->v.string;

		errno = 0;
		id = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			warn("%s: Error parsing qos rule id value: %s\n",
			    __func__, tb[QOS_RULE_ID]->v.string);
			return -1;
		}

		if (cntlr_qos_get_rule(cc, id)) {
			warn("%s: Duplicate QoS rule ID %u – skipping rule\n",
				__func__, id);
			return 0;
		}
	} else {
		warn("%s: QoS rule ID is not defined – skipping rule\n",
			__func__);
		return 0;
	}

	rule = calloc(1, sizeof(struct qos_rule));
	if (rule == NULL)
		return -1;

	rule->id = id;

	if (tb[QOS_RULE_TYPE]) {
		const char *val = tb[QOS_RULE_TYPE]->v.string;

		if (!strcmp(val, "dscp_pcp"))
			rule->type = QOS_RULE_TYPE_DSCP_PCP;
		else if (!strcmp(val, "mscs"))
			rule->type = QOS_RULE_TYPE_MSCS;
		else if (!strcmp(val, "scs"))
			rule->type = QOS_RULE_TYPE_SCS;
		else if (!strcmp(val, "mgmt"))
			rule->type = QOS_RULE_TYPE_MGMT;
	}

	if (tb[QOS_RULE_PRECEDENCE]) {
		const char *val = tb[QOS_RULE_PRECEDENCE]->v.string;

		errno = 0;
		rule->precedence = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			warn("%s: Error parsing qos rule precedence value: %s\n",
			    __func__, tb[QOS_RULE_PRECEDENCE]->v.string);
			ret = -1;
			goto out;
		}
	} else {
		rule->precedence = 254;
	}

	if (tb[QOS_RULE_OUTPUT]) {
		const char *val = tb[QOS_RULE_OUTPUT]->v.string;

		errno = 0;
		rule->output = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			warn("%s: Error parsing qos rule output value: %s\n",
			    __func__, tb[QOS_RULE_OUTPUT]->v.string);
			ret = -1;
			goto out;
		}
	}

	if (tb[QOS_RULE_ALWAYS_MATCH]) {
		const char *val = tb[QOS_RULE_ALWAYS_MATCH]->v.string;

		errno = 0;
		rule->always_match = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			warn("%s: Error parsing qos rule always_match value: %s\n",
			    __func__, tb[QOS_RULE_ALWAYS_MATCH]->v.string);
			ret = -1;
			goto out;
		}
	}

	if (rule->type != QOS_RULE_TYPE_DSCP_PCP) {
		ret = cntlr_config_get_qos_rule_mgmt(cc, s, rule);
		if (ret != 0) {
			goto out;
		}
	}

	switch (rule->type)
	{
		case QOS_RULE_TYPE_DSCP_PCP:
		{
			ret = cntlr_config_get_qos_rule_dscp_pcp(cc, s, &rule->dscp_pcp);
			break;
		}
		case QOS_RULE_TYPE_MSCS:
		{
			ret = cntlr_config_get_qos_rule_mscs(cc, s, &rule->mgmt.mscs);
			break;
		}
		case QOS_RULE_TYPE_SCS:
		{
			ret = cntlr_config_get_qos_rule_scs(cc, s, &rule->mgmt.scs);
			break;
		}
		case QOS_RULE_TYPE_MGMT:
		{
			ret = cntlr_config_get_qos_rule_qm(cc, s, &rule->mgmt.qm);
			break;
		}
		default:
		{
			ret = -1;
			break;
		}
	}

out:
	if (ret == 0) {
		list_add_tail(&rule->list, &cc->qos.rule);
	} else {
		free(rule);
	}

	return ret;
}

static int cntlr_config_get_qos_policy(
	struct controller_config *cc,
	struct uci_section *s)
{
	enum {
		QOS_POLICY_MSCS_DISALLOWED,
		QOS_POLICY_SCS_DISALLOWED,
		NUM_QOS_POLICY_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_POLICY_MSCS_DISALLOWED] = { .name = "mscs_disallowed",
		                                 .type = UCI_TYPE_LIST },
		[QOS_POLICY_SCS_DISALLOWED] = { .name = "scs_disallowed",
		                                .type = UCI_TYPE_LIST },
	};
	struct uci_option *tb[NUM_QOS_POLICY_ATTRS];

	uci_parse_section(s, opts, NUM_QOS_POLICY_ATTRS, tb);

	if (tb[QOS_POLICY_MSCS_DISALLOWED]) {
		struct uci_element *x;

		uci_foreach_element(&tb[QOS_POLICY_MSCS_DISALLOWED]->v.list, x) {
			struct qos_mgmt_policy_elem *el = calloc(1,
				sizeof(struct qos_mgmt_policy_elem));

			if (el == NULL ||
				hwaddr_aton(x->name, el->mac) == NULL) {
				free(el);
				return -1;
			}

			list_add(&el->list, &cc->qos.policy.mscs_disallowed);
			cc->qos.policy.mscs_num++;
		}
	}

	if (tb[QOS_POLICY_SCS_DISALLOWED]) {
		struct uci_element *x;

		uci_foreach_element(&tb[QOS_POLICY_SCS_DISALLOWED]->v.list, x) {
			struct qos_mgmt_policy_elem *el = calloc(1,
				sizeof(struct qos_mgmt_policy_elem));

			if (el == NULL ||
				hwaddr_aton(x->name, el->mac) == NULL) {
				free(el);
				return -1;
			}

			list_add(&el->list, &cc->qos.policy.scs_disallowed);
			cc->qos.policy.scs_num++;
		}
	}

	return 0;
}

int cntlr_config_get_qos(struct controller_config *cc,
                         struct uci_section *s)
{
	int ret;
	enum {
		QOS_ENABLED,
		NUM_QOS_ATTRS
	};
	const struct uci_parse_option opts[] = {
		[QOS_ENABLED] = { .name = "enabled", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_QOS_ATTRS];
	struct uci_element *e;

	uci_parse_section(s, opts, NUM_QOS_ATTRS, tb);

	if (tb[QOS_ENABLED]) {
		const char *val = tb[QOS_ENABLED]->v.string;
		char *endptr = NULL;
		int enbl;

		errno = 0;
		enbl = strtol(val, &endptr, 10);
		if (errno || *endptr != '\0') {
			err("%s: Error parsing qos enabled value: %s\n",
			    __func__, tb[QOS_ENABLED]->v.string);
			return -1;
		}
		cc->qos.enabled = (enbl == 1) ? true : false;
	}

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

		if (!strcmp(sect1->type, "qos_rule")) {
			ret = cntlr_config_get_qos_rule(cc, sect1);
		} else if (!strcmp(sect1->type, "qos_policy")) {
			ret = cntlr_config_get_qos_policy(cc, sect1);
		} else {
			ret = 0;
		}
		if (ret != 0)
			return ret;
	}

	return 0;
}

#endif

#if (EASYMESH_VERSION >= 6)
uint32_t cntlr_mld_id_diff(struct controller_config *curr,
		struct controller_config *prev)
{
	struct iface_credential *prev_ap = NULL, *curr_ap = NULL;
	struct mld_credential *prev_mld = NULL, *curr_mld = NULL;
	uint8_t multi_ap = 0;
	uint32_t diff = 0;

	list_for_multiple_entry(prev_mld, curr_mld, &prev->mldlist, &curr->mldlist, list, list) { /* cppcheck-suppress nullPointer */
		if (prev_mld->multi_ap != curr_mld->multi_ap)
			continue;

		if (prev_mld->num_affiliated_aps != curr_mld->num_affiliated_aps) {
			if (curr_mld->num_affiliated_aps == 0 ||
			    prev_mld->num_affiliated_aps == 0) {
				diff |= CONFIG_DIFF_CREDENTIALS;
				/* AP-Autoconfiguration Renew covers all cases */
				return diff;
			} else if (curr_mld->multi_ap & 0x01) {
				diff |= CONFIG_DIFF_BSTA_MLD;
				diff |= CONFIG_DIFF_AP_MLD;
				multi_ap |= 0x01;
			} else if (curr_mld->multi_ap & 0x02) {
				diff |= CONFIG_DIFF_AP_MLD;
				multi_ap |= 0x02;
			}
		}
	}

	if (diff == 0) {
		/** TODO: if REMOVE an affiliated FBSS from MLD and MOVE
		 *  affiliated BBSS from one MLD to another, DIFF_BSTA_MLD *MAY*
		 *  not get set
		 **/
		list_for_multiple_entry(prev_ap, curr_ap, &prev->aplist, &curr->aplist, list, list) {
			/* skip mld type which had changed num affiliated APs */
			if (prev_ap->multi_ap & multi_ap)
				continue;

			if (prev_ap->mld_id != curr_ap->mld_id) {
				dbg("mld_id differ\n");
				if (curr_ap->multi_ap & 0x02)
					diff |= CONFIG_DIFF_AP_MLD;
				else if (curr_ap->multi_ap & 0x01) {
					diff |= CONFIG_DIFF_BSTA_MLD;
					diff |= CONFIG_DIFF_AP_MLD;
				}
				break;
			}
		}
	}

	return diff;
}

uint32_t cntlr_mld_diff(struct controller_config *cfg,
		struct controller_config *prev)
{
	uint32_t diff = 0;

	if (prev->num_mlds != cfg->num_mlds)
		diff |= CONFIG_DIFF_CREDENTIALS;
	else {
		/* Check ap sections for mld_id change */
		diff |= cntlr_mld_id_diff(cfg, prev);
	}

	return diff;
}
#endif

static uint32_t cntlr_btm_exclude_diff(struct controller_config *cfg,
		struct controller_config *prev)
{
	uint32_t diff = 0;

	/* Check if controller BTM exclusion list changed */
	if (list_memcmp(&prev->btmsteer_exlist, &cfg->btmsteer_exlist,
			struct stax, offsetof(struct stax, list))) {
		struct node_policy *np = NULL;
		int agents_changed = 0;

		diff |= CONFIG_DIFF_BTM_EXCLUDE;

		/* Sync each agent's local BTM exclusion list with global changes */
		list_for_each_entry(np, &cfg->nodelist, list) {
			if (sync_agent_btm_exclusions(cfg, np)) {
				/* This agent's local list changed - mark for policy update */
				np->is_policy_diff = true;
				diff |= CONFIG_DIFF_AGENT_POLICY;
				agents_changed++;
			}
		}
		cntlr_dbg(LOG_STEER,
			  "%s: Synchronized agent BTM exclusion lists - %d agents affected\n",
			  __func__, agents_changed);
	}

	return diff;
}

static uint32_t cntlr_disallowed_opc_diff(struct controller_config *cfg,
		struct controller_config *prev)
{
	struct node_policy *prev_n, *curr_n;
	struct radio_policy *prev_rp, *curr_rp;
	uint32_t diff = 0;

	list_for_multiple_entry(prev_n, curr_n, &prev->nodelist, &cfg->nodelist, list, list) {
		list_for_multiple_entry(prev_rp, curr_rp, &prev_n->radiolist, &curr_n->radiolist, list, list) {
			if (memcmp(&prev_rp->dis_opclass, &curr_rp->dis_opclass,
				   sizeof(struct wifi_radio_opclass)))
				diff |= CONFIG_DIFF_DISALLOWED_OPC;
		}
	}

	return diff;
}

uint32_t cntlr_config_diff(struct controller_config *cfg,
		struct controller_config *prev)
{
	uint32_t diff = 0;

	/* Check for debug level and log feature changes */
	if (prev->debug_level != cfg->debug_level ||
	    prev->log_features != cfg->log_features)
		diff |= CONFIG_DIFF_DEBUG;

	/* traffic separation change */
	if (prev->primary_vid != cfg->primary_vid ||
	    prev->default_pcp != cfg->default_pcp ||
	    prev->enable_ts != cfg->enable_ts) {
		diff |= CONFIG_DIFF_VLAN;
	}

	/* credentials diff */
	diff |= cntlr_creds_diff(cfg, prev);

	/* agent policy diff */
	if (prev->num_apolicy != cfg->num_apolicy) {
		dbg("|%s:%d| number of agent policy differed\n", __func__, __LINE__);
		diff |= CONFIG_DIFF_AGENT_POLICY_CNT;
	}

	if (list_policy_memcmp(&prev->nodelist, &cfg->nodelist,
				struct node_policy,
				(sizeof(struct node_policy) -
				offsetof(struct node_policy, is_policy_diff))
			)) {
		trace("|%s:%d| agent_policy section have changed\n", __func__, __LINE__);
		diff |= CONFIG_DIFF_AGENT_POLICY;
	} else {
		diff |= cntlr_policy_lists_diff(&prev->nodelist, &cfg->nodelist);
	}

	/* controller BTM exclusion list diff */
	diff |= cntlr_btm_exclude_diff(cfg, prev);

	/* disallow opclass diff */
	diff |= cntlr_disallowed_opc_diff(cfg, prev);

#if (EASYMESH_VERSION > 2)
	/* qos diff */
	diff |= cntlr_qos_diff(cfg, prev);

	/* qos rule diff */
	diff |= cntlr_qos_rule_diff(cfg, prev);

	/* qos management policies */
	diff |= cntlr_qos_mgmt_policy_diff(cfg, prev, false);
	diff |= cntlr_qos_mgmt_policy_diff(cfg, prev, true);
#endif

#if (EASYMESH_VERSION >= 6)
	diff |= cntlr_mld_diff(cfg, prev);
#endif
	return diff;
}

void config_copy_cntlr_config(struct controller_config *curr,
		struct controller_config *old)
{
	INIT_LIST_HEAD(&old->radiolist);
	INIT_LIST_HEAD(&old->nodelist);
	INIT_LIST_HEAD(&old->aplist);
	INIT_LIST_HEAD(&old->btmsteer_exlist);
#if (EASYMESH_VERSION > 2)
	INIT_LIST_HEAD(&old->qos.rule);
	INIT_LIST_HEAD(&old->qos.policy.mscs_disallowed);
	INIT_LIST_HEAD(&old->qos.policy.scs_disallowed);
#endif
#if (EASYMESH_VERSION >= 6)
	INIT_LIST_HEAD(&old->mldlist);
#endif
	memcpy(old, curr, offsetof(struct controller_config, nodelist));

	list_copy(&curr->radiolist, &old->radiolist, struct radio_policy);
	list_copy(&curr->nodelist, &old->nodelist, struct node_policy);
	list_copy(&curr->aplist, &old->aplist, struct iface_credential);
#if (EASYMESH_VERSION > 2)
	old->qos.enabled = curr->qos.enabled;
	list_copy(&curr->qos.rule,
			  &old->qos.rule,
			  struct qos_rule);
	list_copy(&curr->qos.policy.mscs_disallowed,
			  &old->qos.policy.mscs_disallowed,
			  struct qos_mgmt_policy_elem);
	list_copy(&curr->qos.policy.scs_disallowed,
			  &old->qos.policy.scs_disallowed,
			  struct qos_mgmt_policy_elem);
#endif
#if (EASYMESH_VERSION >= 6)
	list_copy(&curr->mldlist, &old->mldlist, struct mld_credential);
#endif
	list_copy(&curr->btmsteer_exlist, &old->btmsteer_exlist, struct stax);
	old->num_btmsteer_excl_stas = curr->num_btmsteer_excl_stas;
}

void cntlr_check_disallow_opclass(struct controller *c, struct controller_config *cfg,
		struct controller_config *prev)
{
	struct node_policy *n = NULL;

	list_for_each_entry(n, &cfg->nodelist, list) {
		struct wifi_radio_opclass opclass = {0};
		struct netif_radio *r;
		struct radio_policy *p = NULL;
		uint32_t band, channel, bandwidth;

		list_for_each_entry(p, &n->radiolist, list) {
			struct radio_policy *rp;

			rp = cntlr_get_radio_policy(prev, p->macaddr);
			if (!rp)
				continue;

			if (!memcmp(&p->dis_opclass, &rp->dis_opclass, sizeof(struct wifi_radio_opclass)))
				continue;


			r = cntlr_find_radio(c, p->macaddr);
			if (!r || !r->radio_el)
				continue;

			memcpy(&opclass, &r->radio_el->supp_opclass, sizeof(opclass));
			wifi_opclass_set_preferences(&opclass, 0x0);

			band = wifi_band_to_freqband(r->radio_el->band);
			if (!band)
				continue;

			channel = ctrl_radio_cur_opclass_ctrl_chan(r->radio_el);
			bandwidth = ctrl_radio_cur_opclass_max_bw(r->radio_el);

			if (wifi_radio_opclass_update_channel(&opclass, band, channel, bandwidth, 15))
				continue;

			cntlr_send_channel_selection(c, n->agent_id, p->macaddr, &opclass);
		}
	}
}

uint32_t cntlr_config_reload(struct controller_config *cfg)
{
	struct controller *c = container_of(cfg, struct controller, cfg);
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;
	struct controller_config old = {0};
	uint32_t diff = 0;

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

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

	config_copy_cntlr_config(cfg, &old);

	/* reset counters */
	cfg->num_bss = cfg->num_apolicy = 0;
#if (EASYMESH_VERSION > 2)
	cfg->qos.enabled = false;
	INIT_LIST_HEAD(&cfg->qos.rule);
	INIT_LIST_HEAD(&cfg->qos.policy.mscs_disallowed);
	INIT_LIST_HEAD(&cfg->qos.policy.scs_disallowed);
#endif
#if (EASYMESH_VERSION >= 6)
	cfg->num_mlds = 0;
#endif

	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);
		if (!strcmp(s->type, "controller")) {
			cntlr_config_get_base(c, cfg, s);
		} else if (!strcmp(s->type, "sta_steering")) {
			cntlr_config_get_steer_policy(cfg, s);
			cntlr_load_steer_modules(c);
			cntlr_reorder_steer_modules(c);
		} else if (!strcmp(s->type, "channel_plan")) {
			cntlr_config_get_channel_plan(cfg, s);
		} else if (!strcmp(s->type, "ap")) {
			cntlr_config_get_credentials(cfg, s);
		} else if (!strcmp(s->type, "node")) {
			cntlr_config_get_agent_node(cfg, s);
		} else if (!strcmp(s->type, "radio")) {
			cntlr_config_get_agent_radio(cfg, s);
		}
#if (EASYMESH_VERSION > 2)
		else if (!strcmp(s->type, "qos")) {
			cntlr_config_get_qos(cfg, s);
		}
#endif
#if (EASYMESH_VERSION >= 6)
		else if (!strcmp(s->type, "mld")) {
			cntlr_config_get_mld(cfg, s);
		}
#endif
	}

	/* (re)config plugins, if plugins specific config sections exist */
	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);

		if (!strcmp(s->type, "sta-steer")) {
			struct steer_control *sc = NULL;

			list_for_each_entry(sc, &c->sclist, list) {
				if (!strncmp(s->e.name, sc->name, strlen(s->e.name))) {
					cntlr_configure_steer_module(sc, s);
					break;
				}
			}
		}
	}

	cntlr_config_get_wsc_attributes(cfg);

#if (EASYMESH_VERSION >= 6)
	config_map_ap_to_mld(cfg);
#endif
	config_map_radios_to_node(cfg);

	/* get bitmap of what sections changed */
	diff = cntlr_config_diff(cfg, &old);

	if (diff & CONFIG_DIFF_DISALLOWED_OPC)
		cntlr_check_disallow_opclass(c, cfg, &old);

#if (EASYMESH_VERSION >= 6)
	if (diff & CONFIG_DIFF_AP_MLD) {
		bool nonwifi7_node = false;
		struct node *n = NULL;

		list_for_each_entry(n, &c->nodelist, list) {
			if (!cntlr_node_support_ap_wifi7(n)) {
				nonwifi7_node = true;
				break;
			}
		}

		if (!nonwifi7_node) {
			timestamp_update(&cfg->last_apmld_change);
		} else {
			/* unset DIFF_AP_MLD and instead use AP-Autoconfiguration Renew
			 * so update will reach non-wifi7 nodes as well
			 */
			diff &= ~CONFIG_DIFF_AP_MLD;
			diff |= CONFIG_DIFF_CREDENTIALS;
		}
	}
	if (diff & CONFIG_DIFF_PUNCT_BITMAP)
		timestamp_update(&cfg->last_apmld_change);
	if (diff & CONFIG_DIFF_BSTA_MLD)
		timestamp_update(&cfg->last_bstamld_change);
#endif
	if (diff & CONFIG_DIFF_CREDENTIALS || diff & CONFIG_DIFF_VLAN)
		timestamp_update(&cfg->last_change);


	/* clean old lists */
	clean_cred_list(&old);
	clean_agent_policies(&old); /* cleans nodelist */
	clean_radio_list(&old.radiolist);
	clean_btm_exclude_list(&old);
#if (EASYMESH_VERSION > 2)
	if ((diff & CONFIG_DIFF_QOS) && old.qos.enabled) {
		struct node *n = NULL;

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

		/* send the policy config cmdu to the marked agent */
		list_for_each_entry(n, &c->nodelist, list)
			cntlr_qos_sync_node(c, &old.qos, n->almacaddr, false);
	}

	list_flush(&old.qos.rule, struct qos_rule, list);
	list_flush(&old.qos.policy.mscs_disallowed, struct qos_mgmt_policy_elem, list);
	list_flush(&old.qos.policy.scs_disallowed, struct qos_mgmt_policy_elem, list);
#endif

	if (cfg->update_config) {
		struct uci_section *s;

		s = uci_lookup_section(ctx, pkg, "controller");
		if (s) {
			char network_id[37] = {0};
			int ret;

			uuid_btostr(cfg->network_id, network_id);
			ret = set_value(ctx, pkg, s, "network_id", network_id, UCI_TYPE_STRING);
			if (!ret) {
				uci_commit(ctx, &pkg, false);
				cfg->update_config = false;
			}
		}
	}


#if (EASYMESH_VERSION >= 6)
	clean_mld_list(&old.mldlist);
#endif
	uci_free_context(ctx);

	return diff;
}

int cntlr_config_clean(struct controller_config *cfg)
{
	clean_plugins(cfg);
	clean_cred_list(cfg);
	clean_agent_policies(cfg); /* cleans nodelist */
	clean_radio_list(&cfg->radiolist);
	clean_btm_exclude_list(cfg);
#if (EASYMESH_VERSION > 2)
	list_flush(&cfg->qos.rule, struct qos_rule, list);
	list_flush(&cfg->qos.policy.mscs_disallowed, struct qos_mgmt_policy_elem, list);
	list_flush(&cfg->qos.policy.scs_disallowed, struct qos_mgmt_policy_elem, list);
#endif
#if (EASYMESH_VERSION >= 6)
	clean_mld_list(&cfg->mldlist);
#endif
	return 0;
}
