
#include "cntlr_commands_impl.h"

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <easymesh.h>
#include <easy/easy.h>
#include <wifidefs.h>
#include <cmdu.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
#include <dpp_api.h>
#endif
#endif

#include "utils/debug.h"
#include "cntlr.h"
#include "cntlr_apis.h"
#include "acs.h"
#include "cntlr_cmdu.h"
#include "cntlr_commands.h"
#include "cntlr_map.h"
#include "cntlr_tlv.h"
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
#include "dpp.h"
#endif
#endif
#include "config.h"
#include "utils/utils.h"
#include "timer.h"
#include "sta.h"
#include "wifi_opclass.h"
#include "wifi_dataelements.h"
#include "cntlr_extension.h"
#include "cntlr_plugin.h"
#include "steer_module.h"
#include "steer.h"

static const char *blobmsg_type2str(unsigned int type)
{
	switch (type) {
	case BLOBMSG_TYPE_STRING: return "String";
	case BLOBMSG_TYPE_INT8: return "Boolean";
	case BLOBMSG_TYPE_INT32: return "Integer";
	case BLOBMSG_TYPE_ARRAY: return "Array";
	case BLOBMSG_TYPE_TABLE: return "Table";
	default:
		break;
	}

	return "(unknown)";
}

int COMMAND(help)(void *priv, void *args, void *out)
{
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_HELP];
	struct controller_command *cmd;
	const char *command = NULL;
	void *a, *t;
	int ret;


	ret = controller_command_parse("help", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[HELP_ATTR_COMMAND_NAME])
		command = blobmsg_data(tb[HELP_ATTR_COMMAND_NAME]);

	if (!command || !strncmp(command, "help", 4)) {
		void *aa;

		aa = blobmsg_open_array(bb, "commands");
		controller_for_each_command(cmd) {
			void *tt;

			tt = blobmsg_open_table(bb, "");
			blobmsg_add_string(bb, "command", cmd->command);
			blobmsg_add_string(bb, "help", cmd->help);

			a = blobmsg_open_array(bb, "args");
			for (int i = 0; i < cmd->num_attrs; i++)  {
				t = blobmsg_open_table(bb, "");
				blobmsg_add_string(bb, "attr", cmd->attrs[i].pol.name);
				blobmsg_add_string(bb, "type", blobmsg_type2str(cmd->attrs[i].pol.type));
				blobmsg_add_u8(bb, "optional", cmd->attrs[i].optional ? true : false);
				blobmsg_add_string(bb, "help", cmd->attrs[i].help);
				blobmsg_close_table(bb, t);
			}
			blobmsg_close_array(bb, a);
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aa);
		return 0;
	}


	cmd = controller_command_lookup(command);
	if (!cmd)
		return -EINVAL;

	blobmsg_add_string(bb, "command", cmd->command);
	blobmsg_add_string(bb, "help", cmd->help);

	a = blobmsg_open_array(bb, "args");
	for (int i = 0; i < cmd->num_attrs; i++)  {
		t = blobmsg_open_table(bb, "");
		blobmsg_add_string(bb, "attr", cmd->attrs[i].pol.name);
		blobmsg_add_string(bb, "type", blobmsg_type2str(cmd->attrs[i].pol.type));
		blobmsg_add_u8(bb, "optional", cmd->attrs[i].optional ? true : false);
		blobmsg_add_string(bb, "help", cmd->attrs[i].help);
		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, a);

	return 0;
}

int COMMAND(log)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_LOG];
	int ret = 0;
	void *a;
	bool restart = false;


	ret = controller_command_parse("log", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	/* return current log settings */
	if (!tb[LOG_ATTR_FEATURE] && !tb[LOG_ATTR_LEVEL]) {
		void *tt;

		tt = blobmsg_open_table(bb, "");
		blobmsg_add_u32(bb, "loglevel", c->log.level);
		a = blobmsg_open_array(bb, "feature");
		for (int i = 0; i <= LOG_DEFAULT; i++)  {
			if (!(c->log.features & BIT(i)))
				continue;

			blobmsg_add_string(bb, "", logfeature_to_string(i));
		}
		blobmsg_close_array(bb, a);
		blobmsg_close_table(bb, tt);
		return 0;
	}

	if (tb[LOG_ATTR_LEVEL]) {
		uint32_t loglevel = blobmsg_get_u32(tb[LOG_ATTR_LEVEL]);

		if (loglevel > LOGLEVEL_MAX)
			return -EINVAL;

		if (loglevel != c->log.level) {
			c->log.level = loglevel;
			restart = true;
		}
	}

	if (tb[LOG_ATTR_FEATURE]) {
		const char *fstr = blobmsg_data(tb[LOG_ATTR_FEATURE]);
		uint32_t features = c->log.features;
		char *str, *s, *tmp;

		str = strdup(fstr);
		foreach_token_r(s, str, tmp, ", ") {
			if (s[0] == '+') {
				features |= logfeature_to_enum(s+1);
			} else if (s[0] == '-') {
				features &= ~logfeature_to_enum(s+1);
			} else if (s[0] == '*' || !strncmp(s, "all", 3)) {
				features |= LOG_FEATURE_ALL;
			} else {
				ret = -1;
				break;
			}
		}

		free(str);

		if (!ret && features != c->log.features) {
			c->log.features = features;
			restart = true;
		}
	}

	if (restart)
		restart_logging(&c->log);

	blobmsg_add_string(bb, "status", !ret ? "ok" : "fail");

	return 0;
}

int COMMAND(send_topology_query)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_TOPOLOGY_QUERY];
	uint8_t agent_mac[6] = { 0 };
	char agent[18] = { 0 };
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("send_topology_query", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent, blobmsg_data(tb[TOPOLOGY_QUERY_ATTR_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, agent_mac))
		return -EINVAL;

	cmdu = cntlr_gen_topology_query(c, agent_mac);
	if (!cmdu)
		return -1;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(scan)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_SCAN];
	struct scan_req_data sreq = {0};
	bool wildcard_scan = false;
	char agent_macstr[18] = {0};
	char radiostr[18] = {0};
	uint8_t agent[6] = {0};
	uint8_t radio[6] = {0};
	struct cmdu_buff *cmdu;
	bool fresh_scan = true;
	uint16_t mid = 0xffff;
	struct node *n = NULL;
	int num_channel = 0;
	int num_opclass = 0;
	int ret;


	ret = controller_command_parse("scan", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent_macstr, blobmsg_data(tb[SCAN_ATTR_AGENT]), sizeof(agent_macstr) - 1);
	if (!hwaddr_aton(agent_macstr, agent))
		return -EINVAL;

	n = cntlr_find_node(c, agent);
	if (!n)
		return -EINVAL;

	if (tb[SCAN_ATTR_FRESH_SCAN])
		fresh_scan = blobmsg_get_bool(tb[SCAN_ATTR_FRESH_SCAN]);

	sreq.is_fresh_scan = fresh_scan;

	if (!tb[SCAN_ATTR_BAND] && !tb[SCAN_ATTR_RADIO] &&
	    !tb[SCAN_ATTR_OPCLASS] && !tb[SCAN_ATTR_CHANNEL]) {
		struct netif_radio *r = NULL;
		int ri = 0;

		wildcard_scan = true;

		/* Include all radios in the scan request */

		list_for_each_entry(r, &n->radiolist, list) {
			memcpy(sreq.radios[ri++].radio_mac, r->radio_el->macaddr, 6);
			sreq.num_radio++;
		}
	}

	if (!wildcard_scan && fresh_scan && !tb[SCAN_ATTR_BAND] && !tb[SCAN_ATTR_RADIO]) {
		dbg("%s: To trigger fresh scanning, provide either 'band' or 'radio' in Agent\n", __func__);
		return -EINVAL;
	}

	if (tb[SCAN_ATTR_BAND]) {
		int band = blobmsg_get_u32(tb[SCAN_ATTR_BAND]);
		struct netif_radio *r = NULL;
		bool found = false;

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

		list_for_each_entry(r, &n->radiolist, list) {
			dbg("%s: r->radio_el->macaddr = " MACFMT ", band = %d\n", __func__,
			    MAC2STR(r->radio_el->macaddr), r->radio_el->band);

			if (band == 2 && r->radio_el->band == BAND_2) {
				memcpy(sreq.radios[0].radio_mac, r->radio_el->macaddr, 6);
				found = true;
			} else if (band == 5 && r->radio_el->band == BAND_5) {
				memcpy(sreq.radios[0].radio_mac, r->radio_el->macaddr, 6);
				found = true;
			} else if (band == 6 && r->radio_el->band == BAND_6) {
				memcpy(sreq.radios[0].radio_mac, r->radio_el->macaddr, 6);
				found = true;
			}
		}

		if (!found) {
			dbg("%s: radio for band %d not found in Agent\n", __func__, band);
				return -EINVAL;
		}

		sreq.num_radio = 1;

	} else if (tb[SCAN_ATTR_RADIO]) {
		struct netif_radio *r = NULL;
		bool found = false;

		strncpy(radiostr, blobmsg_data(tb[SCAN_ATTR_RADIO]), sizeof(radiostr) - 1);
		if (!hwaddr_aton(radiostr, radio))
			return -EINVAL;

		list_for_each_entry(r, &n->radiolist, list) {
			if (!memcmp(r->radio_el->macaddr, radio, 6)) {
				memcpy(sreq.radios[0].radio_mac, radio, 6);
				found = true;
			}
		}

		if (!found) {
			dbg("%s: Radio " MACFMT " not found in Agent\n", __func__, MAC2STR(radio));
			return -EINVAL;
		}
		sreq.num_radio = 1;
	}

	if (tb[SCAN_ATTR_OPCLASS])
		num_opclass = blobmsg_check_array(tb[SCAN_ATTR_OPCLASS], BLOBMSG_TYPE_INT32);

	if (tb[SCAN_ATTR_CHANNEL])
		num_channel = blobmsg_check_array(tb[SCAN_ATTR_CHANNEL], BLOBMSG_TYPE_INT32);


	if (num_opclass > 0 && num_channel > 0) {
		dbg("%s: Either opclass or channel list is allowed\n", __func__);
		return -EINVAL;
	}

	if (num_opclass == 0 && num_channel == 0) {
		int i;

		/* Include all opclasses for requested radios */

		for (i = 0; i < sreq.num_radio; i++) {
			struct wifi_radio_opclass *supp_opclass = NULL;
			struct netif_radio *r = NULL;
			int j;

			r = cntlr_find_radio(c, sreq.radios[i].radio_mac);
			if (!r || !r->radio_el)
				continue;

			supp_opclass = &r->radio_el->supp_opclass;

			for (j = 0; j < supp_opclass->num_opclass; j++) {
				struct wifi_radio_opclass_entry *e = NULL;
				int io = 0;

				e = &supp_opclass->opclass[j];
				if (wifi_opclass_id_supported(supp_opclass, e->id)
						&& wifi_opclass_get_bw(e->id) == 20) {

					io = sreq.radios[i].num_opclass;
					if (io >= SCAN_REQ_MAX_NUM_OPCLASS) {
						dbg("%s: skip skan of opclass %d - max number exceeded\n", __func__, e->id);
						break;
					}
					sreq.radios[i].opclasses[io].classid = e->id;
					sreq.radios[i].num_opclass++;
				}
			}
		}
	}

	if (num_opclass > 0) {
		if (!fresh_scan) {
			dbg("%s: opclass entries ignored for non-fresh-scan\n", __func__);
			sreq.radios[0].num_opclass = 0;
		} else {
			struct blob_attr *cur;
			int rem;
			int i;

			blobmsg_for_each_attr(cur, tb[SCAN_ATTR_OPCLASS], rem) {
				uint8_t opclass = (uint8_t)blobmsg_get_u32(cur);
				struct netif_radio *r = NULL;
				struct wifi_radio_opclass_entry *e = NULL;
				bool supported = false;
				int io;

				/* Verify opclass is in band/radio and corresponds to 20MHz. */

				for (i = 0; i < sreq.num_radio; i++) {
					r = cntlr_find_radio(c, sreq.radios[i].radio_mac);
					if (!r || !r->radio_el)
						continue;
					supported = wifi_opclass_id_supported(&r->radio_el->supp_opclass, opclass);
					if (supported)
						/* Opclass number can correspond to one radio only
							i - contains index of radio in sreq for given opclass
							r - contains pointer to radio for given opclass
						 */
						break;
				}

				if (!supported) {
					dbg("%s: skip skan of opclass %d - unsuported\n", __func__, opclass);
					continue;
				}

				if (sreq.radios[i].num_opclass >= SCAN_REQ_MAX_NUM_OPCLASS) {
					dbg("%s: skip skan of opclass %d - maximum number exceeded\n", __func__, opclass);
					continue;
				}

				if (wifi_opclass_get_bw(opclass) != 20) {
					dbg("%s: skip skan of opclass %d - not a 20MHz one\n", __func__, opclass);
					continue;
				}

				e = wifi_opclass_find_entry(&r->radio_el->supp_opclass, opclass);
				if (!e)
					continue;

				io = sreq.radios[i].num_opclass;
				sreq.radios[i].opclasses[io].classid = opclass;
				sreq.radios[i].num_opclass++;
			}
		}
	}

	if (num_channel > 0) {
		if (!fresh_scan) {
			dbg("%s: channel entries ignored for non-fresh-scan\n", __func__);
			sreq.radios[0].opclasses[0].num_channel = 0;
		} else {
			struct blob_attr *cur;
			int rem;

			blobmsg_for_each_attr(cur, tb[SCAN_ATTR_CHANNEL], rem) {
				uint8_t channel = (uint8_t)blobmsg_get_u32(cur);
				uint8_t opclass = 0;
				bool opclass_found = false;
				int i, j;
				int io = 0, ic = 0;

				for (i = 0; i < sreq.num_radio; i++) {
					struct netif_radio *r = NULL;

					r = cntlr_find_radio(c, sreq.radios[i].radio_mac);
					if (!r || !r->radio_el)
						continue;

					/* Find 20MHz opclass for channel provided in any band/radio */
					opclass = wifi_opclass_get_id(&r->radio_el->supp_opclass, channel, 20);
					if (opclass > 0)
						break; /* no need to check other radios */
				}

				if (!opclass) {
					dbg("%s: skip skan of channel %d - no matching 20MHz opclass\n", __func__, channel);
					continue;
				}

				/* Lookup opclass for given channel in current request */

				for (j = 0; j < sreq.radios[i].num_opclass; j++) {
					if (sreq.radios[i].opclasses[j].classid == opclass) {
						opclass_found = true;
						io = j;
						break;
					}
				}

				if (!opclass_found) {
					/* Must add a new 20MHz opclass to the request */
					if (sreq.radios[i].num_opclass >= SCAN_REQ_MAX_NUM_OPCLASS) {
						dbg("%s: skip skan - max num opclass exceeded\n", __func__);
						continue;
					}

					io = sreq.radios[i].num_opclass;
					sreq.radios[i].opclasses[io].classid = opclass;
					sreq.radios[i].num_opclass++;
				}

				ic = sreq.radios[i].opclasses[io].num_channel;
				if (ic >= SCAN_REQ_MAX_NUM_CHAN) {
					dbg("%s: skip skan - max num channel exceeded\n", __func__);
					break;
				}
				sreq.radios[i].opclasses[io].channels[ic] = channel;
				sreq.radios[i].opclasses[io].num_channel++;
			}
		}
	}

	cmdu = cntlr_gen_channel_scan_request(c, agent, &sreq);
	if (!cmdu)
		return -1;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(scanresults)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_SCANRESULTS];
	uint8_t radios[MAX_NUM_RADIO][6] = {0};
	char agentstr[18] = {0};
	uint8_t agent[6] = {0};
	struct netif_radio *r = NULL;
	struct node *n = NULL;
	int num_radio = 0;
	void *b;
	int ret;


	ret = controller_command_parse("scanresults", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[SCANRESULTS_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;

	n = cntlr_find_node(c, agent);
	if (!n)
		return -EINVAL;

	if (tb[SCANRESULTS_ATTR_BAND]) {
		int band = blobmsg_get_u32(tb[SCANRESULTS_ATTR_BAND]);

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

		r = cntlr_find_radio_in_node_by_band(c, n, wifi_freqband_to_band(band));
		if (!r || !r->radio_el) {
			dbg("%s: radio for band %d not found in Agent\n", __func__, band);
			return -EINVAL;
		}
		memcpy(radios[0], r->radio_el->macaddr, 6);
		num_radio = 1;
	} else if (tb[SCANRESULTS_ATTR_RADIO]) {
		char radiostr[18] = {0};
		uint8_t radio[6] = {0};

		strncpy(radiostr, blobmsg_get_string(tb[SCANRESULTS_ATTR_RADIO]), sizeof(radiostr) - 1);

		if (!hwaddr_aton(radiostr, radio)) {
			dbg("%s: RUID %s malformed\n", __func__, radiostr);
			return -EINVAL;
		}

		r = cntlr_find_radio(c, radio);
		if (!r || !r->radio_el) {
			dbg("%s: Radio %s not found in Agent\n", __func__, radiostr);
			return -EINVAL;
		}

		memcpy(radios[0], r->radio_el->macaddr, 6);
		num_radio = 1;
	} else {
		int ri = 0;

		/* Include all radios */
		list_for_each_entry(r, &n->radiolist, list) {
			memcpy(radios[ri++], r->radio_el->macaddr, 6);
		}
		num_radio = ri;
	}

	/* dump scanresults */
	b = blobmsg_open_array(bb, "radios");
	list_for_each_entry(n, &c->nodelist, list) {
		struct netif_radio *p = NULL;

		list_for_each_entry(p, &n->radiolist, list) {
			struct wifi_scanres_element *el = NULL;
			char macaddrstr[18] = {0};
			void *t1, *t2;
			int i;

			for (i = 0; i < num_radio; i++) {
				if (!memcmp(p->radio_el->macaddr, radios[i], 6))
					break;
			}

			if (num_radio > 0 && i == num_radio)
				continue;	/* not queried */

			t1 = blobmsg_open_table(bb, "");
			hwaddr_ntoa(p->radio_el->macaddr, macaddrstr);
			blobmsg_add_string(bb, "radio", macaddrstr);
			blobmsg_add_u32(bb, "num_scanresult", p->radio_el->num_scanresult);

			t2 = blobmsg_open_array(bb, "scanlist");
			list_for_each_entry(el, &p->radio_el->scanlist, list) {
				struct wifi_scanres_opclass_element *op = NULL;
				void *tt1, *tt2;

				tt1 = blobmsg_open_table(bb, "");
				blobmsg_add_string(bb, "tsp", el->tsp);
				blobmsg_add_u32(bb, "num_opclass_scanned", el->num_opclass_scanned);

				tt2 = blobmsg_open_array(bb, "opclasses");
				list_for_each_entry(op, &el->opclass_scanlist, list) {
					struct wifi_scanres_channel_element *ch = NULL;
					void *ttt1, *ttt2;

					ttt1 = blobmsg_open_table(bb, "");
					blobmsg_add_u32(bb, "opclass", op->opclass);

					ttt2 = blobmsg_open_array(bb, "channels");
					list_for_each_entry(ch, &op->channel_scanlist, list) {
						struct wifi_scanres_neighbor_element *nbr = NULL;
						void *tttt1, *tttt2;
						char bssstr[18] = {0};

						tttt1 = blobmsg_open_table(bb, "");
						blobmsg_add_string(bb, "tsp", ch->tsp);
						blobmsg_add_u32(bb, "channel", ch->channel);
						blobmsg_add_u32(bb, "utilization", ch->utilization);
						blobmsg_add_u32(bb, "anpi", ch->anpi);
						blobmsg_add_u32(bb, "num_neighbors", ch->num_neighbors);

						tttt2 = blobmsg_open_array(bb, "nbrlist");
						list_for_each_entry(nbr, &ch->nbrlist, list) {
							void *ttttt;

							if (!ch->num_neighbors)
								break;

							ttttt = blobmsg_open_table(bb, "");

							hwaddr_ntoa(nbr->bssid, bssstr);
							blobmsg_add_string(bb, "bssid", bssstr);
							blobmsg_add_string(bb, "ssid", nbr->ssid);
							blobmsg_add_u32(bb, "rssi", rcpi_to_rssi(nbr->rssi));
							blobmsg_add_u32(bb, "rcpi", nbr->rssi);
							blobmsg_add_u32(bb, "bw", nbr->bw);
							blobmsg_add_u32(bb, "utilization", nbr->utilization);
							blobmsg_add_u32(bb, "num_stations", nbr->num_stations);

							blobmsg_close_table(bb, ttttt);
						}
						blobmsg_close_table(bb, tttt2);
						blobmsg_close_table(bb, tttt1);
					}
					blobmsg_close_table(bb, ttt2);
					blobmsg_close_table(bb, ttt1);
				}
				blobmsg_close_table(bb, tt2);
				blobmsg_close_table(bb, tt1);
			}
			blobmsg_close_array(bb, t2);
			blobmsg_close_table(bb, t1);
		}
	}
	blobmsg_close_array(bb, b);

	return 0;
}

int COMMAND(cac_start)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_CAC_REQUEST];
	struct cac_data *cac_data = NULL;
	char agent_macstr[18] = {0};
	char radiostr[18] = {0};
	uint8_t agent[6] = {0};
	uint8_t radio[6] = {0};
	uint8_t opclass = 0;
	uint8_t channel = 0;
	uint8_t method = 0xff;
	uint8_t action = 0xff;
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int num_data = 0;
	void *tmp = NULL;
	int idx;
	int ret;

	ret = controller_command_parse("cac_start", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent_macstr, blobmsg_data(tb[CAC_START_ATTR_AGENT]), sizeof(agent_macstr) - 1);
	if (!hwaddr_aton(agent_macstr, agent))
		return -EINVAL;

	if (tb[CAC_START_ATTR_RADIO]) {
		strncpy(radiostr, blobmsg_data(tb[CAC_START_ATTR_RADIO]), sizeof(radiostr) - 1);
		if (!hwaddr_aton(radiostr, radio))
			return -EINVAL;
	} else
		err("%s: radio is not provided\n", __func__);

	if (tb[CAC_START_ATTR_OPCLASS])
		opclass = (uint8_t)blobmsg_get_u32(tb[CAC_START_ATTR_OPCLASS]);

	if (tb[CAC_START_ATTR_CHANNEL])
		channel = (uint8_t)blobmsg_get_u32(tb[CAC_START_ATTR_CHANNEL]);

	if (tb[CAC_START_ATTR_METHOD]) {
		/* TODO: if method is not provided then lookup CAC capability
		 * of the passed agent node as exchange via AP Capability
		 */
		method  = (uint8_t)blobmsg_get_u32(tb[CAC_START_ATTR_METHOD]);
		if (method > 3)
			return -EINVAL;
	}

	if (tb[CAC_START_ATTR_ACTION]) {
		action = (uint8_t)blobmsg_get_u32(tb[CAC_START_ATTR_ACTION]);
		if (action > 1)
			return -EINVAL;
	}

	/* TODO:
	 * for opclass bandwidth > 20MHz, if passed channel != cntlr-channel
	 * but the center-channel, then build CAC request for all 20MHz
	 * ctrl-channels within the opclass.
	 */
	do {
		tmp = realloc((void *)cac_data, (num_data + 1) * sizeof(struct cac_data));
		if (!tmp) {
			err("%s:%d -ENOMEM\n", __func__, __LINE__);
			if (cac_data)
				free(cac_data);

			return -ENOMEM;
		}

		cac_data = tmp;
		idx = num_data;
		memcpy(cac_data[idx].radio, radio, 6);
		cac_data[idx].opclass = opclass;
		cac_data[idx].channel = channel;
		cac_data[idx].cac_method = method;
		cac_data[idx].cac_action = action;
		num_data++;
	} while (0);

	cmdu = cntlr_gen_cac_req(c, agent, num_data, cac_data);
	if (!cmdu) {
		free(cac_data);
		return -1;
	}

	mid = send_cmdu(c, cmdu);
	cmdu_free(cmdu);
	free(cac_data);

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(cac_stop)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_CAC_TERMINATE];
	struct cac_data *cac_data = NULL;
	char agent_macstr[18] = {0};
	char radiostr[18] = {0};
	uint8_t agent[6] = {0};
	uint8_t radio[6] = {0};
	uint8_t opclass = 0;
	uint8_t channel = 0;
	uint8_t method = 0xff;
	uint8_t action = 0xff;
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int num_data = 0;
	void *tmp = NULL;
	int idx;
	int ret;

	ret = controller_command_parse("cac_stop", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent_macstr, blobmsg_data(tb[CAC_TERMINATE_ATTR_AGENT]), sizeof(agent_macstr) - 1);
	if (!hwaddr_aton(agent_macstr, agent))
		return -EINVAL;

	if (tb[CAC_TERMINATE_ATTR_RADIO]) {
		strncpy(radiostr, blobmsg_data(tb[CAC_TERMINATE_ATTR_RADIO]), sizeof(radiostr) - 1);
		if (!hwaddr_aton(radiostr, radio))
			return -EINVAL;
	}

	if (tb[CAC_TERMINATE_ATTR_OPCLASS]) {
		opclass = (uint8_t)blobmsg_get_u32(tb[CAC_TERMINATE_ATTR_OPCLASS]);
		dbg("%s: opclass = %d\n", __func__, opclass);
	}

	if (tb[CAC_TERMINATE_ATTR_CHANNEL]) {
		channel = (uint8_t)blobmsg_get_u32(tb[CAC_TERMINATE_ATTR_CHANNEL]);
		dbg("%s: channel = %d\n", __func__, channel);
	}

	/* TODO:
	 * for opclass bandwidth > 20MHz, if passed channel != cntlr-channel
	 * but the center-channel, then build CAC terminate request for all 20MHz
	 * ctrl-channels within the opclass.
	 */
	do {
		tmp = realloc((void *)cac_data, (num_data + 1) * sizeof(*cac_data));
		if (!tmp) {
			err("%s:%d -ENOMEM\n", __func__, __LINE__);
			if (cac_data)
				free(cac_data);

			return -ENOMEM;
		}

		cac_data = tmp;
		idx = num_data;
		memcpy(cac_data[idx].radio, radio, 6);
		cac_data[idx].opclass = opclass;
		cac_data[idx].channel = channel;
		cac_data[idx].cac_method = method;
		cac_data[idx].cac_action = action;
		num_data++;
	} while (0);

	cmdu = cntlr_gen_cac_term(c, agent, num_data, cac_data);
	if (!cmdu) {
		free(cac_data);
		return -1;
	}

	mid = send_cmdu(c, cmdu);
	cmdu_free(cmdu);
	free(cac_data);

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(send_combined_metrics)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_COMBINED_METRICS];
	uint8_t agent_mac[6] = { 0 };
	uint8_t bssid[6] = { 0 };
	char agent[18] = { 0 };
	char bssid_str[18] = { 0 };
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("send_combined_metrics", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent, blobmsg_data(tb[COMBINED_METRICS_ATTR_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, agent_mac))
		return -EINVAL;

	if (tb[COMBINED_METRICS_ATTR_BSSID]) {
		strncpy(bssid_str, blobmsg_data(tb[COMBINED_METRICS_ATTR_BSSID]), sizeof(bssid_str) - 1);
		if (!hwaddr_aton(bssid_str, bssid))
			return -EINVAL;
	} else {
		/* TODO: send for all BSSs */
	}

	cmdu = cntlr_gen_comb_infra_metrics_query(c, agent_mac, bssid);
	if (!cmdu)
		return -1;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(send_hld)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_HLD];
	uint8_t agent_mac[6] = { 0 };
	char agent[18] = { 0 };
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	uint8_t *data = NULL;
	char *datastr = NULL;
	uint8_t proto = 0;
	int len = 0;
	int tmp;
	int ret;

	ret = controller_command_parse("send_hld", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent, blobmsg_data(tb[HLD_ATTR_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, agent_mac))
		return -EINVAL;

	tmp = blobmsg_get_u32(tb[HLD_ATTR_PROTOCOL]);
	if (tmp < 0 || tmp > 255) {
		dbg("%s(): HLD protocol invalid; not in 0-255 range.\n", __func__);
		return -EINVAL;
	}
	proto = (uint8_t)tmp;

	if (tb[HLD_ATTR_DATA]) {
		datastr = blobmsg_get_string(tb[HLD_ATTR_DATA]);
		len = blobmsg_data_len(tb[HLD_ATTR_DATA]);
		if (len > 0 && len % 2 != 1) {
			/* expect n*2 hex digits + '\0' termination character  */
			dbg("%s(): HLD payload length (%d) invalid.\n", __func__, len);
			return -EINVAL;
		}

		if (len > 0) {
			len = len / 2;
			data = calloc(len, sizeof(uint8_t));
			if (!data) {
				err("%s(): alloc failure!\n", __func__);
				return -ENOMEM;
			}

			if (strtob(datastr, len, data) == NULL) {
				dbg("%s(): HLD payload data is invalid\n", __func__);
				free(data);
				return -EINVAL;
			}
		}
	} else {
		dbg("%s(): HLD payload empty.\n", __func__);
	}

	cmdu = cntlr_gen_higher_layer_data(c, agent_mac, proto, data, len);
	if (!cmdu) {
		if (data)
			free(data);

		return -EINVAL;
	}

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(send_channel_sel)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_CHANNEL_SEL];
	struct wifi_radio_opclass opclass = {};
	uint8_t agent_mac[6] = { 0 };
	char agent[18] = { 0 };
	uint8_t radio_mac[6] = { 0 };
	char radio[18] = { 0 };
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	uint32_t band, channel, bandwidth;
	struct netif_radio *r;
	int ret;

	ret = controller_command_parse("send_channel_sel", args, tb);
	if (ret) {
		err("%s: parse error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent, blobmsg_data(tb[CHANNEL_SEL_ATTR_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, agent_mac)) {
		err("%s: agent_mac error\n", __func__);
		return -EINVAL;
	}

	strncpy(radio, blobmsg_data(tb[CHANNEL_SEL_ATTR_RADIO]), sizeof(radio) - 1);
	if (!hwaddr_aton(radio, radio_mac)) {
		err("%s: radio_mac error\n", __func__);
		return -EINVAL;
	}

	r = cntlr_find_radio(c, radio_mac);
	if (!r) {
		err("%s: find radio (%s) error\n", __func__, radio);
		return -EINVAL;
	}

	channel = blobmsg_get_u32(tb[CHANNEL_SEL_ATTR_CHANNEL]);

	if (tb[CHANNEL_SEL_ATTR_BANDWIDTH])
		bandwidth = blobmsg_get_u32(tb[CHANNEL_SEL_ATTR_BANDWIDTH]);
	else
		bandwidth = 0;

	/* Build opclass we would like to send */
	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) {
		err("%s unknown band %u\n", __func__, r->radio_el->band);
		return -EINVAL;
	}

	if (wifi_radio_opclass_update_channel(&opclass, band, channel, bandwidth, 15)) {
		err("%s: opclass_update_channel(%u, %u, %u) error\n", __func__, band, channel, bandwidth);
		return -EINVAL;
	}

	wifi_opclass_dump_ex(&opclass, "chan_selection", radio_mac, false);

	cmdu = cntlr_gen_channel_sel_request(c, agent_mac, radio_mac, &opclass);
	if (!cmdu) {
		err("%s: gen_channel_sel_request() error\n", __func__);
		return -1;
	}

	cmdu_put_eom(cmdu);
	mid = send_cmdu(c, cmdu);
	cmdu_free(cmdu);

	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(send_channel_pref)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_CHANNEL_PREF_REQ];
	struct blob_attr *opclass_arr, *channel_arr, *pref_arr;
	struct wifi_radio_opclass_entry *op_entry;
	struct wifi_radio_opclass opclass = {0};
	uint8_t agent_mac[6] = {0}, radio_mac[6] = {0};
	char agent[18] = {0}, radio[18] = {0};
	struct netif_radio *r = NULL;
	struct node *n = NULL;
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int opclass_count, channel_count, pref_count;
	int idx = 0, band, ret, i;

	ret = controller_command_parse("send_channel_pref", args, tb);
	if (ret) {
		err("%s: parse error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (!tb[CHANNEL_PREF_REQ_ATTR_RADIO] && !tb[CHANNEL_PREF_REQ_ATTR_BAND]) {
		dbg("%s: either radioid or band must be specified\n", __func__);
		return -EINVAL;
	}

	strncpy(agent, blobmsg_data(tb[CHANNEL_PREF_REQ_ATTR_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, agent_mac)) {
		err("%s: invalid agent MAC\n", __func__);
		return -EINVAL;
	}

	n = cntlr_find_node(c, agent_mac);
	if (!n) {
		err("%s: find node (%s) error\n", __func__, agent);
		return -EINVAL;
	}

	if (tb[CHANNEL_PREF_REQ_ATTR_RADIO]) {
		strncpy(radio, blobmsg_data(tb[CHANNEL_PREF_REQ_ATTR_RADIO]), sizeof(radio) - 1);
		if (!hwaddr_aton(radio, radio_mac)) {
			err("%s: invalid radio MAC\n", __func__);
			return -EINVAL;
		}

		r = cntlr_find_radio(c, radio_mac);
		if (!r) {
			err("%s: find radio (%s) error\n", __func__, radio);
			return -EINVAL;
		}
	} else if (tb[CHANNEL_PREF_REQ_ATTR_BAND]) {
		band = blobmsg_get_u32(tb[CHANNEL_PREF_REQ_ATTR_BAND]);
		if (band != 2 && band != 5 && band != 6) {
			err("%s: invalid radio band (%d)\n", __func__, band);
			return -EINVAL;
		}

		r = cntlr_find_radio_in_node_by_band(c, n, wifi_freqband_to_band(band));
		if (!r || !r->radio_el) {
			err("%s: find radio error\n", __func__);
			return -EINVAL;
		}

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

	opclass_arr = tb[CHANNEL_PREF_REQ_ATTR_OPCLASS];
	channel_arr = tb[CHANNEL_PREF_REQ_ATTR_CHANNEL];
	pref_arr = tb[CHANNEL_PREF_REQ_ATTR_PREF];

	if ((!opclass_arr && !channel_arr) || !pref_arr) {
		err("%s: either opclass or channel must be specified, and pref must be specified\n", __func__);
		return -EINVAL;
	}

	opclass_count = opclass_arr ? blobmsg_check_array(opclass_arr, BLOBMSG_TYPE_INT32) : 0;
	channel_count = channel_arr ? blobmsg_check_array(channel_arr, BLOBMSG_TYPE_ARRAY) : 0;
	pref_count = blobmsg_check_array(pref_arr, BLOBMSG_TYPE_INT32);
	if ((opclass_arr && opclass_count != pref_count) ||
		(channel_arr && channel_count != pref_count)) {
		err("%s: inconsistent array lengths\n", __func__);
		return -EINVAL;
	}

	/* Copy supported opclasses and clear all preferences */
	memcpy(&opclass, &r->radio_el->supp_opclass, sizeof(opclass));
	wifi_opclass_set_preferences(&opclass, 0x0);

	if (opclass_arr && !channel_arr) { /* Case 1: opclass only (no channel array) */
		for (idx = 0; idx < pref_count; idx++) {
			struct blob_attr *cur_op, *cur_pref;
			uint8_t in_op, in_pref;

			/* Get nested opclass array by idx */
			cur_op = blobmsg_array_at(opclass_arr, idx);
			in_op = blobmsg_get_u32(cur_op);

			/* Get nested preference by idx */
			cur_pref = blobmsg_array_at(pref_arr, idx);
			in_pref = blobmsg_get_u32(cur_pref);

			/* Find matching opclass in our copy */
			op_entry = wifi_opclass_find_entry(&opclass, in_op);
			if (!op_entry) {
				err("%s: opclass (%u) not supported\n", __func__, in_op);
				return -EINVAL;
			}

			/* Apply same preference to all channels in this opclass */
			for (i = 0; i < op_entry->num_channel; i++)
				op_entry->channel[i].preference = in_pref << 4;
		}
	} else if (!opclass_arr && channel_arr) { /* Case 2: channel only (no opclass array) */
		for (idx = 0; idx < pref_count; idx++) {
			struct blob_attr *cur_pref, *cur_chs, *ch_attr;
			uint8_t in_ch, in_pref;
			size_t ch_rem;

			/* Get nested channel array by idx */
			cur_chs = blobmsg_array_at(channel_arr, idx);

			/* Get nested preference by idx */
			cur_pref = blobmsg_array_at(pref_arr, idx);
			in_pref = blobmsg_get_u32(cur_pref);

			/* Iterate each channel */
			blobmsg_for_each_attr(ch_attr, cur_chs, ch_rem) {
				struct wifi_radio_opclass_channel *channel_entry;
				bool ch_found = false;

				if (blobmsg_type(ch_attr) != BLOBMSG_TYPE_INT32) {
					err("%s: invalid channel type\n", __func__);
					return -EINVAL;
				}

				in_ch = blobmsg_get_u32(ch_attr);

				/* Apply to every opclass that supports this channel */
				for (i = 0; i < opclass.num_opclass; i++) {
					/* Find matching channel in opclass */
					channel_entry = wifi_opclass_find_channel(&opclass.opclass[i], in_ch);
					if (channel_entry) {
						channel_entry->preference = in_pref << 4;
						ch_found = true;
					}
				}

				if (!ch_found) {
					err("%s: channel (%u) not found in any opclass\n", __func__, in_ch);
					return -EINVAL;
				}
			}
		}
	} else { /* Case 3: both opclass and channel arrays present */
		for (idx = 0; idx < pref_count; idx++) {
			struct blob_attr *cur_op, *cur_pref, *cur_chs, *ch_attr;
			uint8_t in_op, in_ch, in_pref;
			size_t ch_rem;

			/* Get nested opclass array by idx */
			cur_op = blobmsg_array_at(opclass_arr, idx);
			in_op = blobmsg_get_u32(cur_op);

			/* Get nested channel array for this opclass */
			cur_chs = blobmsg_array_at(channel_arr, idx);

			/* Get nested preference by idx */
			cur_pref = blobmsg_array_at(pref_arr, idx);
			in_pref = blobmsg_get_u32(cur_pref);

			/* Find matching opclass in our copy */
			op_entry = wifi_opclass_find_entry(&opclass, in_op);
			if (!op_entry) {
				err("%s: opclass (%u) not supported \n", __func__, in_op);
				return -EINVAL;
			}

			/* Iterate each channel for this opclass */
			blobmsg_for_each_attr(ch_attr, cur_chs, ch_rem) {
				struct wifi_radio_opclass_channel *channel_entry;

				if (blobmsg_type(ch_attr) != BLOBMSG_TYPE_INT32) {
					err("%s: invalid channel type for opclass (%u) \n", __func__, in_op);
					return -EINVAL;
				}

				in_ch = blobmsg_get_u32(ch_attr);

				/* Find matching channel in opclass */
				channel_entry = wifi_opclass_find_channel(op_entry, in_ch);
				if (!channel_entry) {
					err("%s: channel (%u) not supported by opclass (%u) \n", __func__, in_ch, in_op);
					return -EINVAL;
				}

				channel_entry->preference = in_pref << 4;
			}
		}
	}

	wifi_opclass_dump_ex(&opclass, "send_channel_pref", radio_mac, false);

	cmdu = cntlr_gen_channel_sel_request(c, agent_mac, radio_mac, &opclass);
	if (!cmdu) {
		err("%s: gen_channel_sel_request() error\n", __func__);
		return -1;
	}

	cmdu_put_eom(cmdu);
	mid = send_cmdu(c, cmdu);
	cmdu_free(cmdu);

	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(trigger_channel_clearing)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_CHANNEL_CLEARING];
	uint8_t agent_mac[6] = { 0 };
	char agent[18] = { 0 };
	struct node *node = NULL;
	int ret;

	ret = controller_command_parse("trigger_channel_clearing", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[CHANNEL_CLEARING_ATTR_AGENT]) {
		strncpy(agent, blobmsg_data(tb[CHANNEL_CLEARING_ATTR_AGENT]), sizeof(agent) - 1);
		if (!hwaddr_aton(agent, agent_mac))
			return -EINVAL;
	}

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

		if (!hwaddr_is_zero(agent_mac) && memcmp(agent_mac, node->almacaddr, 6))
			continue;

		list_for_each_entry(radio, &node->radiolist, list) {
			if (radio->radio_el->band != BAND_5)
				continue;

			/* Action here */
			cntlr_dfs_radio_cleanup(node, radio);
			cntlr_dfs_radio_cleanup_info(node, radio, bb);
		}
	}

	return 0;
}

int COMMAND(trigger_acs)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_CHANNEL_ACS];
	uint8_t agent_mac[6] = { 0 };
	enum wifi_band band = BAND_ANY;
	struct node *node = NULL;
	char agent[18] = { 0 };
	bool skip_dfs = c->cfg.acs_skip_dfs;
	bool prevent_cac = c->cfg.acs_prevent_cac;
	bool highest_bandwidth = c->cfg.acs_highest_bandwidth;
	uint32_t bandwidth = 0;
	uint8_t opclass = 0;
	int ret;

	ret = controller_command_parse("trigger_acs", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[CHANNEL_ACS_ATTR_SKIP_DFS])
		skip_dfs = blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_SKIP_DFS]);

	if (tb[CHANNEL_ACS_ATTR_PREVENT_CAC])
		prevent_cac = blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_PREVENT_CAC]);

	if (tb[CHANNEL_ACS_ATTR_AGENT]) {
		strncpy(agent, blobmsg_data(tb[CHANNEL_ACS_ATTR_AGENT]), sizeof(agent) - 1);
		if (!hwaddr_aton(agent, agent_mac))
			return -EINVAL;
	}

	if (tb[CHANNEL_ACS_ATTR_BAND]) {
		switch (blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_BAND])) {
		case 2:
			band = BAND_2;
			break;
		case 5:
			band = BAND_5;
			break;
		case 6:
			band = BAND_6;
			break;
		default:
			break;
		}
	}

	if (tb[CHANNEL_ACS_ATTR_OPCLASS])
		opclass = blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_OPCLASS]);

	if (tb[CHANNEL_ACS_ATTR_BANDWIDTH]) {
		bandwidth = blobmsg_get_u32(tb[CHANNEL_ACS_ATTR_BANDWIDTH]);
		if (!bandwidth)
			highest_bandwidth = true;
	}

	list_for_each_entry(node, &c->nodelist, list) {
		if (!hwaddr_is_zero(agent_mac) && memcmp(agent_mac, node->almacaddr, 6))
			continue;

		/* Action here */
		cntlr_acs_node_channel_recalc(node, band, opclass, bandwidth, skip_dfs, prevent_cac, highest_bandwidth);

		/* Show information about recalc */
		cntlr_acs_node_info(node, band, bb);
	}

	return 0;
}

int COMMAND(steer)(void *priv, void *args, void *out)
{
#define MAXNUM_STA_IN_STEER_REQ		18
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_STEER];
	bool btm_disassoc_imminent = false;
	struct cmdu_buff *cmdu = NULL;
	uint32_t btm_disassoc_tmo = 0;
	uint16_t steer_op_window = 0;
	uint8_t target_agent[6] = {0};
	uint8_t target_bssid[6] = {0};
	bool btm_abridged = false;
	char tbssidstr[18] = {0};
	char tagentstr[18] = {0};
	char agentstr[18] = {0};
	uint8_t agent[6] = {0};
	//uint8_t bssid[6] = {0};
	uint8_t mbo_reason = 0;
	char stastr[18] = {0};
	uint16_t mid = 0xffff;
	uint8_t sta[6] = {0};
	int ret;

	ret = controller_command_parse("steer", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	/* Either target-bssid or target-agent has to be specified */
	if (!tb[STEER_ATTR_TARGET_BSSID] && !tb[STEER_ATTR_TARGET_AGENT])
		return -EINVAL;

	if (tb[STEER_ATTR_STA]) {
		strncpy(stastr, blobmsg_data(tb[STEER_ATTR_STA]), sizeof(stastr) - 1);
		if (!hwaddr_aton(stastr, sta))
			return -EINVAL;
	}

	if (!tb[STEER_ATTR_AGENT]) {
		if (!hwaddr_is_zero(sta)) {
			struct node *n;

			n = cntlr_find_node_with_sta(c, sta);
			if (!n)
				return -EINVAL;

			memcpy(agent, n->almacaddr, 6);
		} else if (tb[STEER_ATTR_FROM_BSSID]) {
			char src_bssidstr[18] = {0};
			uint8_t from_bssid[6] = {0};
			struct netif_iface *bss;

			strncpy(src_bssidstr, blobmsg_data(tb[STEER_ATTR_FROM_BSSID]), sizeof(src_bssidstr) - 1);
			if (!hwaddr_aton(src_bssidstr, from_bssid))
				return -EINVAL;

			bss = cntlr_find_bss(c, from_bssid);
			if (!bss)
				return -EINVAL;

			memcpy(agent, bss->agent->almacaddr, 6);
		} else {
			return -EINVAL;
		}
	} else {
		strncpy(agentstr, blobmsg_data(tb[STEER_ATTR_AGENT]), sizeof(agentstr) - 1);
		if (!hwaddr_aton(agentstr, agent))
			return -EINVAL;
	}

	if (tb[STEER_ATTR_TARGET_AGENT]) {
		strncpy(tagentstr, blobmsg_data(tb[STEER_ATTR_TARGET_AGENT]), sizeof(tagentstr) - 1);
		if (!hwaddr_aton(tagentstr, target_agent))
			return -EINVAL;
	}

	if (tb[STEER_ATTR_TARGET_BSSID]) {
		strncpy(tbssidstr, blobmsg_data(tb[STEER_ATTR_TARGET_BSSID]), sizeof(tbssidstr) - 1);
		if (!hwaddr_aton(tbssidstr, target_bssid))
			return -EINVAL;
	} else if (tb[STEER_ATTR_TARGET_AGENT]) {
		/* Auto-determine target_bssid from target_agent + band */
		struct node *target_node;
		struct netif_radio *r;
		struct netif_iface *iface;
		enum wifi_band target_band;
		bool found = false;

		if (tb[STEER_ATTR_TARGET_BAND]) {
			uint8_t band_val = (uint8_t)blobmsg_get_u32(tb[STEER_ATTR_TARGET_BAND]);

			target_band = wifi_freqband_to_band(band_val);
			if (target_band == 0) {
				err("%s: Invalid band %u (must be 2, 5, or 6)\n", __func__, band_val);
				return -EINVAL;
			}
		} else if (!hwaddr_is_zero(sta)) {
			/* Infer band from STA's current connection */
			struct sta *s = cntlr_find_sta(c->sta_table, sta);

			if (!s) {
				err("%s: STA " MACFMT " not found\n", __func__, MAC2STR(sta));
				return -EINVAL;
			}

			target_band = cntlr_get_bss_band(c, s->bssid);
			if (target_band == BAND_UNKNOWN) {
				err("%s: Cannot determine STA's current band\n", __func__);
				return -EINVAL;
			}
		} else {
			err("%s: target_agent requires either target_band or sta parameter\n", __func__);
			return -EINVAL;
		}

		target_node = cntlr_find_node(c, target_agent);
		if (!target_node) {
			err("%s: Target agent " MACFMT " not found\n", __func__, MAC2STR(target_agent));
			return -EINVAL;
		}

		/* Find a fronthaul BSS on target agent in the specified band with matching SSID */
		if (!hwaddr_is_zero(sta)) {
			struct sta *s = cntlr_find_sta(c->sta_table, sta);

			if (!s) {
				err("%s: STA " MACFMT " not found\n", __func__, MAC2STR(sta));
				return -EINVAL;
			}

			list_for_each_entry(r, &target_node->radiolist, list) {
				if (r->radio_el->band != target_band)
					continue;

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

					/* Match SSID if STA is connected */
					if (s->ssidlen > 0 &&
					    (iface->bss->ssidlen != s->ssidlen ||
					     memcmp(iface->bss->ssid, s->ssid, s->ssidlen)))
						continue;

					/* Found a matching fronthaul BSS */
					memcpy(target_bssid, iface->bss->bssid, 6);
					found = true;
					break;
				}
				if (found)
					break;
			}
		}

		if (!found) {
			err("%s: No suitable target BSSID found on agent " MACFMT " band %d\n",
			    __func__, MAC2STR(target_agent), target_band);
			return -EINVAL;
		}

		dbg("%s: Auto-determined target_bssid " MACFMT " from agent " MACFMT " band %d\n",
		    __func__, MAC2STR(target_bssid), MAC2STR(target_agent), target_band);
	}

	if (tb[STEER_ATTR_BTM_ABRIDGED])
		btm_abridged = !!blobmsg_get_u32(tb[STEER_ATTR_BTM_ABRIDGED]);

	if (tb[STEER_ATTR_DISASSOC_TIMEOUT]) {
		btm_disassoc_tmo = (int)blobmsg_get_u32(tb[STEER_ATTR_DISASSOC_TIMEOUT]);
		if (btm_disassoc_tmo > 0)
			btm_disassoc_imminent = true;
	}

	if (tb[STEER_ATTR_MBO_REASON])
		mbo_reason = (uint8_t)blobmsg_get_u32(tb[STEER_ATTR_MBO_REASON]);

	if (!hwaddr_is_zero(target_bssid)) {
		if (!hwaddr_is_zero(sta)) {
			struct sta *s = cntlr_find_sta(c->sta_table, sta);

			if (!s)
				return -EINVAL;

			/* prepare Client Steer Request */
			cmdu = cntlr_gen_client_steer_request(c, agent,
							      s->bssid,
							      steer_op_window,
							      1, (uint8_t (*)[6])sta,
							      1, (uint8_t (*)[6])target_bssid,
							      btm_abridged,
							      btm_disassoc_imminent,
							      btm_disassoc_tmo,
							      mbo_reason,
							      true);
			if (!cmdu) {
				warn("%s: Error generating steer CMDU\n", __func__);
				return -1;
			}

			// TODO: unify steer handling
			cntlr_update_sta_steer_counters(c, s->macaddr, s->bssid,
							target_bssid, STEER_MODE_BTM_REQ,
							STEER_REASON_UNDEFINED,
							255);

		} else {
			/* TODO: select all STAs of Agent in the same band as target_bssid */
			err("%s: Steering without specific STA not yet supported\n", __func__);
			return -EINVAL;
		}
	} else {
		/* Should not reach here due to validation at beginning */
		err("%s: Invalid steer parameters\n", __func__);
		return -EINVAL;
	}

	if (cmdu) {
		mid = send_cmdu(c, cmdu);
		cmdu_free(cmdu);
	}

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(steer_op)(void *priv, void *args, void *out)
{
#define MAXNUM_STA_IN_STEER_REQ		18
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_STEER_OP];
	bool btm_disassoc_imminent = false;
	uint32_t btm_disassoc_tmo = 0;
	uint16_t steer_op_window = 0;
	struct cmdu_buff *cmdu = NULL;
	bool btm_abridged = false;
	char bssidstr[18] = {0};
	char agentstr[18] = {0};
	uint8_t mbo_reason = 0;
	uint8_t agent[6] = {0};
	uint8_t bssid[6] = {0};
	char stastr[18] = {0};
	uint16_t mid = 0xffff;
	uint8_t sta[6] = {0};
	int ret;

	ret = controller_command_parse("steer_op", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[STEER_OP_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;

	steer_op_window = (uint16_t)blobmsg_get_u32(tb[STEER_OP_ATTR_WINDOW]);

	if (tb[STEER_OP_ATTR_FROM_BSSID]) {
		strncpy(bssidstr, blobmsg_data(tb[STEER_OP_ATTR_FROM_BSSID]), sizeof(bssidstr) - 1);
		if (!hwaddr_aton(bssidstr, bssid))
			return -EINVAL;
	}

	if (tb[STEER_OP_ATTR_STA]) {
		strncpy(stastr, blobmsg_data(tb[STEER_OP_ATTR_STA]), sizeof(stastr) - 1);
		if (!hwaddr_aton(stastr, sta))
			return -EINVAL;
	}

	if (tb[STEER_OP_ATTR_BTM_ABRIDGED])
		btm_abridged = !!blobmsg_get_u32(tb[STEER_OP_ATTR_BTM_ABRIDGED]);

	if (tb[STEER_OP_ATTR_DISASSOC_TIMEOUT]) {
		btm_disassoc_tmo = (int)blobmsg_get_u32(tb[STEER_OP_ATTR_DISASSOC_TIMEOUT]);
		if (btm_disassoc_tmo > 0)
			btm_disassoc_imminent = true;
	}

	if (tb[STEER_OP_ATTR_MBO_REASON])
		mbo_reason = (uint8_t)blobmsg_get_u32(tb[STEER_OP_ATTR_MBO_REASON]);

	if (!hwaddr_is_zero(sta)) {
		struct sta *s = cntlr_find_sta(c->sta_table, sta);

		if (!s)
			return -EINVAL;

		if (s->is_bsta) {
			dbg("|%s:%d| STA "MACFMT", is a backhaul STA, skip!\n",
			    __func__, __LINE__, MAC2STR(s->de_sta->macaddr));
			return -EINVAL;
		}

		/* prepare Client Steer Request */
		cmdu = cntlr_gen_client_steer_request(c, agent,
						      hwaddr_is_zero(bssid) ? s->bssid : bssid,
						      steer_op_window,
						      1, (uint8_t (*)[6])sta,
						      0, NULL,
						      btm_abridged,
						      btm_disassoc_imminent,
						      btm_disassoc_tmo,
						      mbo_reason,
						      false);
		if (!cmdu) {
			warn("%s: Error generating steer CMDU\n", __func__);
			return -1;
		}
	} else if (!hwaddr_is_zero(bssid)) {
		/* select all STAs of this bssid */
		//TODO
	} else {
		/* wildcard from-bssid + wildcard sta */
		return -EINVAL;
	}

	if (cmdu) {
		mid = send_cmdu(c, cmdu);
		cmdu_free(cmdu);
	}

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(assoc_control)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_ASSOC_CONTROL];
	struct cmdu_buff *cmdu = NULL;
	uint32_t validity_period = 0;
	uint8_t *stalist = NULL;
	char bssidstr[18] = {0};
	char agentstr[18] = {0};
	uint8_t agent[6] = {0};
	uint8_t bssid[6] = {0};
	uint16_t mid = 0xffff;
	int num_sta = 0;
	int mode = 0;
	int ret;


	ret = controller_command_parse("assoc_control", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[ASSOC_CONTROL_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;

	if (tb[ASSOC_CONTROL_ATTR_BSSID]) {
		strncpy(bssidstr, blobmsg_data(tb[ASSOC_CONTROL_ATTR_BSSID]), sizeof(bssidstr) - 1);
		if (!hwaddr_aton(bssidstr, bssid))
			return -EINVAL;
	}

	if (tb[ASSOC_CONTROL_ATTR_MODE]) {
		mode = (int)blobmsg_get_u32(tb[ASSOC_CONTROL_ATTR_MODE]);
		if (mode < 0 || mode > 3)
			return -EINVAL;
	}

	if (tb[ASSOC_CONTROL_ATTR_VALIDITY_TIME])
		validity_period = (int)blobmsg_get_u32(tb[ASSOC_CONTROL_ATTR_VALIDITY_TIME]);


	if (tb[ASSOC_CONTROL_ATTR_STA]) {
		char stastr[18] = {0};
		struct blob_attr *cur;
		int rem = 0, l = 0;

		num_sta = blobmsg_check_array(tb[ASSOC_CONTROL_ATTR_STA], BLOBMSG_TYPE_STRING);
		if (num_sta > 0) {
			stalist = calloc(num_sta, 6 * sizeof(uint8_t));
			if (!stalist)
				return -ENOMEM;
		}

		blobmsg_for_each_attr(cur, tb[ASSOC_CONTROL_ATTR_STA], rem) {
			strncpy(stastr, blobmsg_get_string(cur), sizeof(stastr) - 1);
			hwaddr_aton(stastr, &stalist[l * 6]);
			l++;
		}

		/* no valid sta data */
		if (l == 0)
			goto error;
	}

	if (!hwaddr_is_zero(bssid)) {
		/* prepare Assoc Control Request */
		cmdu = cntlr_gen_client_assoc_ctrl_request(c, agent,
							   bssid,
							   mode,
							   validity_period,
							   num_sta, stalist);
		if (!cmdu) {
			warn("%s: Error generating CMDU\n", __func__);
			ret = -EINVAL;
			goto error;
		}
	} else {
		/* apply stalist for all BSSs in Agent */
		//TODO
	}

	if (cmdu) {
		mid = send_cmdu(c, cmdu);
		cmdu_free(cmdu);
	}

error:
	free(stalist);

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(steer_backhaul)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_BSTA_STEER];
	struct netif_iface *tbss = NULL;
	struct cmdu_buff *cmdu = NULL;
	uint32_t timeout = 0;
	char tbssidstr[18] = {0};
	char agentstr[18] = {0};
	char bstastr[18] = {0};
	uint8_t tbssid[6] = {0};
	uint8_t agent[6] = {0};
	uint8_t bsta[6] = {0};
	uint16_t mid = 0xffff;
	int ret;
	enum wifi_band band = BAND_UNKNOWN;

	UNUSED(timeout);

	ret = controller_command_parse("steer_backhaul", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[BSTA_STEER_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;

	if (tb[BSTA_STEER_ATTR_TARGET_BAND]) {
		int b;

		b = (int)blobmsg_get_u32(tb[BSTA_STEER_ATTR_TARGET_BAND]);
		if (b == 2)
			band = BAND_2;
		else if (b == 5)
			band = BAND_5;
		else if (b == 6)
			band = BAND_6;
		else
			return -EINVAL;
	}

	if (tb[BSTA_STEER_ATTR_TARGET_BSSID]) {
		strncpy(tbssidstr, blobmsg_data(tb[BSTA_STEER_ATTR_TARGET_BSSID]), sizeof(tbssidstr) - 1);
		if (!hwaddr_aton(tbssidstr, tbssid))
			return -EINVAL;

		tbss = cntlr_find_iface(c, tbssid);
		if (!tbss && !hwaddr_is_zero(tbssid) && !hwaddr_is_bcast(tbssid))
			return -EINVAL;
	} else if (band != BAND_UNKNOWN && tb[BSTA_STEER_ATTR_TARGET_AGENT]) {
		struct node *n;
		struct netif_radio *r;
		uint8_t tagent[6] = {0};

		strncpy(agentstr, blobmsg_data(tb[BSTA_STEER_ATTR_TARGET_AGENT]), sizeof(agentstr) - 1);
		if (!hwaddr_aton(agentstr, tagent))
			return -EINVAL;

		n = cntlr_find_node(c, tagent);
		if (!n)
			return -EINVAL;

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

		list_for_each_entry(tbss, &r->iflist, list) {
			if (tbss->bss->is_bbss) {
				memcpy(tbssid, tbss->bss->bssid, 6);
				break;
			}
		}

		if (hwaddr_is_zero(tbssid))
			return -EINVAL;
	} else {
		dbg("%s: Error must provide either target BSSID or target agent AND band\n", __func__);
		return -EINVAL;
	}

	if (tb[BSTA_STEER_ATTR_BSTA]) {
		strncpy(bstastr, blobmsg_data(tb[BSTA_STEER_ATTR_BSTA]), sizeof(bstastr) - 1);
		if (!hwaddr_aton(bstastr, bsta))
			return -EINVAL;
	} else if (band != BAND_UNKNOWN) {
		struct node *n;
		struct netif_radio *r;

		n = cntlr_find_node(c, agent);
		if (!n)
			return -EINVAL;

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

		/* TODO: bSTA in node 'n' band 'band'
		list_for_each_entry(tbss, &r->iflist, list) {
			if (!tbss->bss->is_bbss && !tbss->bss->is_fbss) {
				memcpy(bsta, tbss->bss->bssid, 6);
				break;
			}
		}
		*/
	} else {
		dbg("%s: Error must provide either bSTA or band\n", __func__);
		return -EINVAL;
	}

	if (tb[BSTA_STEER_ATTR_TIMEOUT])
		timeout = (int)blobmsg_get_u32(tb[BSTA_STEER_ATTR_TIMEOUT]);

	if (!hwaddr_is_zero(tbssid) && !hwaddr_is_zero(bsta)) {
		uint8_t opclass = 0;	//TODO
		uint8_t channel = 0;	//TODO
		/* TODO: find opclass and channel from target bssid. */

		/* prepare Backhaul-Steer request */
		cmdu = cntlr_gen_backhaul_steer_request(c, agent, bsta, tbssid,
							opclass, channel);
		if (!cmdu) {
			dbg("%s: Error generating CMDU\n", __func__);
			return -EINVAL;
		}
	}

	if (cmdu) {
		mid = send_cmdu(c, cmdu);
		cmdu_free(cmdu);
	}

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(query_ap_caps)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_AP_CAPS];
	uint8_t agent_alid[6] = {0};
	char agent_alstr[18] = {0};
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("query_ap_caps", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent_alstr, blobmsg_data(tb[AP_CAPS_ATTR_AGENT]), sizeof(agent_alstr) - 1);
	if (!hwaddr_aton(agent_alstr, agent_alid))
		return -EINVAL;

	cmdu = cntlr_gen_ap_capability_query(c, agent_alid);
	if (!cmdu)
		return -EINVAL;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(query_sta_caps)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_STA_CAPS];
	struct cmdu_buff *cmdu;
	char agentstr[18] = {0};
	char bssidstr[18] = {0};
	uint8_t agent[6] = {0};
	uint8_t bssid[6] = {0};
	uint16_t mid = 0xffff;
	char stastr[18] = {0};
	uint8_t sta[6] = {0};
	int ret;

	ret = controller_command_parse("query_sta_caps", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[STA_CAPS_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;

	strncpy(stastr, blobmsg_data(tb[STA_CAPS_ATTR_STA]), sizeof(stastr) - 1);
	if (!hwaddr_aton(stastr, sta))
		return -EINVAL;

	if (hwaddr_is_zero(sta))
		return -EINVAL;

	if (tb[STA_CAPS_ATTR_BSSID]) {
		strncpy(bssidstr, blobmsg_data(tb[STA_CAPS_ATTR_BSSID]), sizeof(bssidstr) - 1);
		if (!hwaddr_aton(bssidstr, bssid))
			return -EINVAL;
	} else {
		struct sta *s = NULL;

		s = cntlr_find_sta(c->sta_table, sta);
		if (!s) {
			dbg("%s: STA " MACFMT " not found\n",
			    __func__, MAC2STR(sta));
			return -EINVAL;
		}

		if (s->is_bsta) {
			dbg("|%s:%d| STA "MACFMT", is a backhaul STA, skip!\n",
			    __func__, __LINE__, MAC2STR(s->de_sta->macaddr));
			return -EINVAL;
		}

		memcpy(bssid, s->bssid, 6);
	}

	cmdu = cntlr_gen_client_caps_query(c, agent, sta, bssid);
	if (!cmdu)
		return -1;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(query_bsta_caps)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_BSTA_CAPS];
	uint8_t agent_mac[6] = { 0 };
	char agent[18] = { 0 };
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("query_bsta_caps", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	memset(agent, 0, sizeof(agent));
	strncpy(agent, blobmsg_data(tb[BSTA_CAPS_ATTR_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, agent_mac))
		return -EINVAL;

	cmdu = cntlr_gen_bk_caps_query(c, agent_mac);
	if (!cmdu)
		return -1;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(query_ap_metrics)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_AP_METRICS];
	uint8_t radiolist[6 * MAX_NUM_RADIO] = {0};
	uint8_t bsslist[6* MAX_NUM_BSSID] = {0};
	int num_bss = 0, num_radio = 0;
	struct blob_attr *attr = NULL;
	char agentstr[18] = { 0 };
	uint8_t agent[6] = { 0 };
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("query_ap_metrics", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[AP_METRICS_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;

	/* fetch bsslist */
	if (tb[AP_METRICS_ATTR_BSSIDS]) {
		int rem, num;

		num = blobmsg_check_array(tb[AP_METRICS_ATTR_BSSIDS], BLOBMSG_TYPE_STRING);

		blobmsg_for_each_attr(attr, tb[AP_METRICS_ATTR_BSSIDS], rem) {
			uint8_t bssid[6] = {0};
			char bss[18] = {0};

			if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
				continue;

			strncpy(bss, blobmsg_data(attr), sizeof(bss) - 1);
			if (!hwaddr_aton(bss, bssid))
				return -EINVAL;

			memcpy(&bsslist[num_bss * 6], bssid, 6);
			num_bss++;
		}

		if (num != num_bss)
			return -EINVAL;

	} else {
		struct netif_iface *iface = NULL;
		struct netif_radio *r = NULL;
		struct node *n;

		n = cntlr_find_node(c, agent);
		if (!n)
			return -EINVAL;

		/* include all BSSs of node in request */
		list_for_each_entry(r, &n->radiolist, list) {
			list_for_each_entry(iface, &r->iflist, list) {
				memcpy(&bsslist[num_bss * 6], iface->bss->bssid, 6);
				num_bss++;
			}
		}
	}

	if (num_bss == 0)
		return -EINVAL;

	/* fetch radio id's */
	if (tb[AP_METRICS_ATTR_RADIOS]) {
		int rem, num;

		num = blobmsg_check_array(tb[AP_METRICS_ATTR_RADIOS], BLOBMSG_TYPE_STRING);
		blobmsg_for_each_attr(attr, tb[AP_METRICS_ATTR_RADIOS], rem) {
			uint8_t radioid[6] = {0};
			char radio[18] = {0};

			if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
				continue;

			strncpy(radio, blobmsg_data(attr), sizeof(radio)-1);
			if (!hwaddr_aton(radio, radioid))
				return -EINVAL;

			memcpy(&radiolist[num_radio * 6], radioid, 6);
			num_radio++;
		}

		if (num != num_radio)
			return -EINVAL;

	} else {
		struct netif_radio *r = NULL;
		struct node *n;

		n = cntlr_find_node(c, agent);
		if (!n)
			return -EINVAL;

		/* include all Radio-IDs of node in the request */
		list_for_each_entry(r, &n->radiolist, list) {
			memcpy(&radiolist[num_radio * 6], r->radio_el->macaddr, 6);
			num_radio++;
		}
	}

	cmdu = cntlr_gen_ap_metrics_query(c, agent, num_bss, bsslist,
					  num_radio, radiolist);
	if (!cmdu)
		return -1;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(query_sta_metrics)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_STA_METRICS];
	uint8_t agent_mac[6] = { 0 };
	uint8_t sta[6] = { 0 };
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	char mac_str[18];
	int ret;

	ret = controller_command_parse("query_sta_metrics", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	memset(mac_str, 0, sizeof(mac_str));
	strncpy(mac_str, blobmsg_data(tb[STA_METRICS_ATTR_AGENT]), sizeof(mac_str) - 1);
	if (!hwaddr_aton(mac_str, agent_mac))
		return -EINVAL;

	memset(mac_str, 0, sizeof(mac_str));
	strncpy(mac_str, blobmsg_data(tb[STA_METRICS_ATTR_STA]), sizeof(mac_str) - 1);
	if (!hwaddr_aton(mac_str, sta))
		return -EINVAL;

	cmdu = cntlr_gen_sta_metric_query(c, agent_mac, sta);
	if (!cmdu)
		return -1;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

static const char *steer_method2str(enum steer_method type)
{
	switch (type) {
	case STEER_METHOD_ASSOC_CTL:
		return "assoc_ctl";
	case STEER_METHOD_BTM_REQ:
		return "btm";
	case STEER_METHOD_ASYNC_BTM:
		return "async_btm";
	default:
		return "unknown";
	}
}

static const char *steer_trigger2str(enum steer_trigger type)
{
	switch (type) {
	case STEER_TRIGGER_UNKNOWN:
		return "unknown";
	case STEER_TRIGGER_UTIL:
		return "channel_util";
	case STEER_TRIGGER_LINK_QUALITY:
		return "link_quality";
	case STEER_TRIGGER_THPUT:
		return "phyrate";
	case STEER_TRIGGER_BK_UTIL:
		return "bk_link_util";
	}
	return "unknown";
}

int COMMAND(dump_steer_summary)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_STEER_SUMMARY];
	char sta_macstr[18] = {0};
	uint8_t sta[6] = {0};
	int ret;

	ret = controller_command_parse("dump_steer_summary", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[STEER_SUMMARY_ATTR_STA]) {
		strncpy(sta_macstr, blobmsg_data(tb[STEER_SUMMARY_ATTR_STA]), sizeof(sta_macstr) - 1);
		if (!hwaddr_aton(sta_macstr, sta))
			return -EINVAL;
	}

	if (!hwaddr_is_zero(sta)) {
		struct sta *s = NULL;

		s = cntlr_find_sta(c->sta_table, sta);
		if (!s) {
			dbg("%s: STA " MACFMT " not found\n", __func__, MAC2STR(sta));
			return -EINVAL;
		}
		if (s->is_bsta) {
			dbg("|%s:%d| STA "MACFMT", is a backhaul STA, skip!\n",
			    __func__, __LINE__, MAC2STR(s->de_sta->macaddr));
			return -EINVAL;
		}

		blobmsg_add_string(bb, "macaddr", sta_macstr);
		blobmsg_add_u64(bb, "fail_no_candidate",
				s->de_sta->mapsta.steer_summary.no_candidate_cnt);
		blobmsg_add_u64(bb, "assoc_cntlr_attempts",
				s->de_sta->mapsta.steer_summary.blacklist_attempt_cnt);
		blobmsg_add_u64(bb, "assoc_cntlr_success",
				s->de_sta->mapsta.steer_summary.blacklist_success_cnt);
		blobmsg_add_u64(bb, "assoc_cntlr_fail",
				s->de_sta->mapsta.steer_summary.blacklist_failure_cnt);
		blobmsg_add_u64(bb, "btm_attempts",
				s->de_sta->mapsta.steer_summary.btm_attempt_cnt);
		blobmsg_add_u64(bb, "btm_success",
				s->de_sta->mapsta.steer_summary.btm_success_cnt);
		blobmsg_add_u64(bb, "btm_fail",
				s->de_sta->mapsta.steer_summary.btm_failure_cnt);
		blobmsg_add_u64(bb, "btm_query_resp",
				s->de_sta->mapsta.steer_summary.btm_query_resp_cnt);
		blobmsg_add_u32(bb, "time_since_steer_attempt",
				timestamp_elapsed_sec(&s->de_sta->mapsta.steer_summary.last_attempt_tsp));
		blobmsg_add_u32(bb, "time_since_steer",
				timestamp_elapsed_sec(&s->de_sta->mapsta.steer_summary.last_steer_tsp));
	} else {
		/* Combined steer summary of all STAs in the network */
		blobmsg_add_u64(bb, "fail_no_candidate",
				c->dlem.network.steer_summary.no_candidate_cnt);
		blobmsg_add_u64(bb, "assoc_cntlr_attempts",
				c->dlem.network.steer_summary.blacklist_attempt_cnt);
		blobmsg_add_u64(bb, "assoc_cntlr_success",
				c->dlem.network.steer_summary.blacklist_success_cnt);
		blobmsg_add_u64(bb, "assoc_cntlr_fail",
				c->dlem.network.steer_summary.blacklist_failure_cnt);
		blobmsg_add_u64(bb, "btm_attempts",
				c->dlem.network.steer_summary.btm_attempt_cnt);
		blobmsg_add_u64(bb, "btm_success",
				c->dlem.network.steer_summary.btm_success_cnt);
		blobmsg_add_u64(bb, "btm_fail",
				c->dlem.network.steer_summary.btm_failure_cnt);
		blobmsg_add_u64(bb, "btm_query_resp",
				c->dlem.network.steer_summary.btm_query_resp_cnt);
	}

	return 0;
}


static void add_array_steer_sta(struct blob_buf *bb, struct sta *s)
{
	void *ttt, *tttt, *t;
	char macstr[18] = {0};
	char str_tm[32] = {0};
	uint8_t num_attempts;
	struct tm *info;
	time_t tmp_t;
	int i;

	t = blobmsg_open_table(bb, "");
	hwaddr_ntoa(s->de_sta->macaddr, macstr);
	blobmsg_add_string(bb, "macaddr", macstr);
	ttt = blobmsg_open_array(bb, "history");
	num_attempts = s->de_sta->mapsta.num_steer_hist;
	for (i = 0; i < num_attempts; i++) {
		int idx = (s->de_sta->mapsta.first + i) % MAX_STEER_HISTORY;
		struct wifi_steer_history *attempt =
				&s->de_sta->mapsta.steer_history[idx];

		tttt = blobmsg_open_table(bb, "");

		tmp_t = attempt->steer_time;
		info = localtime(&tmp_t);
		strftime(str_tm, sizeof(str_tm), "%Y-%m-%dT%H:%M:%SZ", info);
		blobmsg_add_string(bb, "time", str_tm);
		hwaddr_ntoa(attempt->src_bssid, macstr);
		blobmsg_add_string(bb, "ap", macstr);
		blobmsg_add_string(bb, "trigger", steer_trigger2str(attempt->trigger));
			if (attempt->trigger == STEER_TRIGGER_LINK_QUALITY) {
				blobmsg_add_u32(bb, "source_rcpi", attempt->src_rcpi);
				blobmsg_add_u32(bb, "target_rcpi", attempt->dst_rcpi);
			}
		blobmsg_add_string(bb, "method", steer_method2str(attempt->method));
		hwaddr_ntoa(attempt->dst_bssid, macstr);
		blobmsg_add_string(bb, "target_ap", macstr);
		blobmsg_add_u32(bb, "duration", attempt->duration);
		blobmsg_close_table(bb, tttt);
	}
	blobmsg_close_array(bb, ttt);
	blobmsg_close_table(bb, t);
}

int COMMAND(dump_steer_history)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_STEER_HISTORY];
	char sta_macstr[18] = {0};
	uint8_t stamac[6] = {0};
	struct node *n = NULL;
	void *arr;
	int ret;

	ret = controller_command_parse("dump_steer_history", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[STEER_HISTORY_ATTR_STA]) {
		strncpy(sta_macstr, blobmsg_data(tb[STEER_HISTORY_ATTR_STA]), sizeof(sta_macstr) - 1);
		if (!hwaddr_aton(sta_macstr, stamac))
			return -EINVAL;
	}

	arr = blobmsg_open_array(bb, "sta");

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

		list_for_each_entry(s, &n->stalist, list) {
			if (!hwaddr_is_zero(stamac))
				/* If MAC is provided... */
				if (memcmp(s->de_sta->macaddr, stamac, 6))
					/* ...skip non-matching STAs. */
					continue;
			add_array_steer_sta(bb, s);
		}
	}

	blobmsg_close_array(bb, arr);

	return 0;
}

static void add_array_unastas(struct blob_buf *bb, struct sta *s)
{
	struct unassoc_sta_metrics *usm = NULL;
	char alid_str[18] = {0};
	char stastr[18] = {0};
	void *t, *tt;

	hwaddr_ntoa(s->de_sta->macaddr, stastr);

	t = blobmsg_open_table(bb, "");
	blobmsg_add_string(bb, "macaddr", stastr);
	tt = blobmsg_open_array(bb, "unassociated_metrics");
	list_for_each_entry(usm, &s->de_sta->umetriclist, list) {
		void *ttt;

		ttt = blobmsg_open_table(bb, "");
		hwaddr_ntoa(usm->agent_macaddr, alid_str);
		blobmsg_add_string(bb, "alid", alid_str);
		blobmsg_add_u16(bb, "channel", usm->channel);
		blobmsg_add_u16(bb, "ul_rcpi", usm->ul_rcpi);
		blobmsg_add_u64(bb, "time_delta", usm->time_delta);
		blobmsg_close_table(bb, ttt);
	}
	blobmsg_close_array(bb, tt);
	blobmsg_close_table(bb, t);
}

int COMMAND(dump_unassoc_sta_metrics)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_UNASSOC_STA_METRICS_DUMP];
	char sta_macstr[18] = {0};
	uint8_t stamac[6] = {0};
	struct node *n = NULL;
	void *arr;
	int ret;

	ret = controller_command_parse("dump_unassoc_sta_metrics", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[UNASSOC_STA_METRICS_DUMP_ATTR_STA]) {
		strncpy(sta_macstr, blobmsg_data(tb[UNASSOC_STA_METRICS_DUMP_ATTR_STA]), sizeof(sta_macstr) - 1);
		if (!hwaddr_aton(sta_macstr, stamac))
			return -EINVAL;
	}

	arr = blobmsg_open_array(bb, "stations");

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

		list_for_each_entry(s, &n->stalist, list) {
			if (!hwaddr_is_zero(stamac))
				/* If MAC is provided... */
				if (memcmp(s->de_sta->macaddr, stamac, 6))
					/* ...skip non-matching STAs. */
					continue;
			add_array_unastas(bb, s);
		}
	}

	blobmsg_close_array(bb, arr);
	return 0;
}

int COMMAND(query_unassoc_sta_metrics)(void *priv, void *args, void *out)
{
	struct blob_attr *tb[NUM_ATTRS_UNASSOC_STA_METRICS];
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct unassoc_sta_metric metrics = {0};
	char agentstr[18] = {0};
	uint8_t agent[6] = {0};
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	uint8_t opclass = 0;
	int ret;


	ret = controller_command_parse("query_unassoc_sta_metrics", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[UNASSOC_STA_METRICS_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;

	opclass = (int)blobmsg_get_u32(tb[UNASSOC_STA_METRICS_ATTR_OPCLASS]);
	if (!opclass) {
		dbg("%s: invalid opclass\n", __func__);
		return -EINVAL;
	}

	if (tb[UNASSOC_STA_METRICS_ATTR_CHANNEL]) {
		metrics.channel = (uint8_t)blobmsg_get_u32(tb[UNASSOC_STA_METRICS_ATTR_CHANNEL]);

		if (metrics.channel == 0)
			return -EINVAL;

		//TODO: verify channel in opclass
	}

	if (tb[UNASSOC_STA_METRICS_ATTR_STALIST]) {
		struct blob_attr *attr;
		int num_sta = 0;
		int rem, num;

		num = blobmsg_check_array(tb[UNASSOC_STA_METRICS_ATTR_STALIST], BLOBMSG_TYPE_STRING);
		blobmsg_for_each_attr(attr, tb[UNASSOC_STA_METRICS_ATTR_STALIST], rem) {
			char stastr[18] = {0};
			uint8_t sta[6] = {0};

			if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
				continue;

			strncpy(stastr, blobmsg_data(attr), sizeof(stastr) - 1);
			if (!hwaddr_aton(stastr, sta))
				return -EINVAL;

			memcpy(metrics.sta[num_sta].macaddr, sta, 6);
			num_sta++;

			if (num_sta == MAX_UNASSOC_STAMACS)
				break;
		}

		if (num != num_sta)
			return -EINVAL;

		metrics.num_sta = num_sta;
	}

	cmdu = cntlr_gen_unassoc_sta_metric_query(c, agent, opclass, 1, &metrics);
	if (!cmdu) {
		ret = -1;
		goto out;
	}

	mid = send_cmdu(c, cmdu);
	cmdu_free(cmdu);
out:
	/* reply with mid */
	blobmsg_add_string(bb, "status", ret == 0 ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(query_channel_pref)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_CHANNEL_PREF];
	uint8_t agent_mac[6] = { 0 };
	char agent[18] = { 0 };
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("query_channel_pref", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[CHANNEL_PREF_ATTR_AGENT]) {
		strncpy(agent, blobmsg_data(tb[CHANNEL_PREF_ATTR_AGENT]), sizeof(agent) - 1);
		if (!hwaddr_aton(agent, agent_mac))
			return -EINVAL;
	}

	if (hwaddr_is_zero(agent_mac)) {
		struct node *n = NULL;

		list_for_each_entry(n, &c->nodelist, list) {
			cmdu = cntlr_gen_channel_preference_query(c, n->almacaddr);
			if (!cmdu)
				continue;

			mid = send_cmdu(c, cmdu);
			cmdu_free(cmdu);
		}
	} else {
		cmdu = cntlr_gen_channel_preference_query(c, agent_mac);
		if (!cmdu)
			goto err_out;

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

err_out:
	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

static void add_array_meas_reportlist(struct blob_buf *bb, struct sta *s)
{
	struct wifi_sta_meas_report *bcn = NULL;
	char bssstr[18] = {0};
	void *t;

	t = blobmsg_open_array(bb, "meas_reportlist");
	list_for_each_entry(bcn, &s->de_sta->meas_reportlist, list) {
		char tmbuf[32] = {0};
		struct tm *tminfo;
		void *tt;

		tt = blobmsg_open_table(bb, "");
		blobmsg_add_u16(bb, "channel", bcn->channel);
		blobmsg_add_u16(bb, "opclass", bcn->opclass);
		blobmsg_add_u16(bb, "rcpi", bcn->rcpi);
		blobmsg_add_u16(bb, "rsni", bcn->rsni);
		hwaddr_ntoa(bcn->bssid, bssstr);
		blobmsg_add_string(bb, "bssid", bssstr);
		tminfo = localtime(&bcn->rpt_time);
		strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%dT%H:%M:%S%z", tminfo);
		blobmsg_add_string(bb, "report_time", tmbuf);
		blobmsg_add_u64(bb, "meas_start_time", bcn->meas_start_time);
		blobmsg_close_table(bb, tt);
	}

	blobmsg_close_array(bb, t);
}

int COMMAND(dump_beacon_metrics)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_BCN_METRICS_DUMP];
	char sta_macstr[18] = {0};
	uint8_t sta[6] = {0};
	struct node *n = NULL;
	void *arr;
	int ret;

	ret = controller_command_parse("dump_beacon_metrics", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[BCN_METRICS_DUMP_ATTR_STA]) {
		strncpy(sta_macstr, blobmsg_data(tb[BCN_METRICS_DUMP_ATTR_STA]), sizeof(sta_macstr) - 1);
		if (!hwaddr_aton(sta_macstr, sta))
			return -EINVAL;
	}

	arr = blobmsg_open_array(bb, "stations");
	list_for_each_entry(n, &c->nodelist, list) {
		struct sta *s = NULL;

		list_for_each_entry(s, &n->stalist, list) {
			char stastr[18] = {0};
			void *ttt;

			if (!hwaddr_is_zero(sta) && memcmp(s->de_sta->macaddr, sta, 6))
				continue;

			hwaddr_ntoa(s->de_sta->macaddr, stastr);

			ttt = blobmsg_open_table(bb, "");
			blobmsg_add_string(bb, "macaddr", stastr);
			add_array_meas_reportlist(bb, s);
			blobmsg_close_table(bb, ttt);
		}
	}
	blobmsg_close_array(bb, arr);

	return 0;
}

static int dump_radiopolicy_list(struct blob_buf *bb,
	struct list_head *radiolist, uint8_t *agent_id)
{
	struct radio_policy *rp = NULL;
	void *arr;

	arr = blobmsg_open_array(bb, "radio");
	list_for_each_entry(rp, radiolist, list) {
		void *t;
		const char *band;
		const char *policy;

		if (agent_id != NULL && memcmp(agent_id, rp->agent_id, 6) != 0)
			continue;

		t = blobmsg_open_table(bb, "");

		blobmsg_add_macaddr(bb, "macaddr", rp->macaddr);
		blobmsg_add_macaddr(bb, "agent_id", rp->agent_id);

		switch (rp->band) {
#define RP_MAP_BAND(_name) \
			case _name: \
				band = # _name; \
				break;
			RP_MAP_BAND(BAND_2)
			RP_MAP_BAND(BAND_5)
			RP_MAP_BAND(BAND_DUAL)
			RP_MAP_BAND(BAND_60)
			RP_MAP_BAND(BAND_6)
			RP_MAP_BAND(BAND_UNII_1)
			RP_MAP_BAND(BAND_UNII_2)
			RP_MAP_BAND(BAND_UNII_3)
			RP_MAP_BAND(BAND_UNII_4)
			RP_MAP_BAND(BAND_UNII_5)
			RP_MAP_BAND(BAND_UNII_6)
			RP_MAP_BAND(BAND_UNII_7)
			RP_MAP_BAND(BAND_UNII_8)

			RP_MAP_BAND(BAND_ANY)

			RP_MAP_BAND(BAND_UNKNOWN)
#undef RP_MAP_BAND
			default:
				band = "UNKNOWN";
				break;
		}

		switch (rp->policy) {
#define RP_MAP_POLICY(_name) \
			case _name: \
				policy = # _name; \
				break;
			RP_MAP_POLICY(AGENT_STEER_DISALLOW)
			RP_MAP_POLICY(AGENT_STEER_RCPI_MANDATE)
			RP_MAP_POLICY(AGENT_STEER_RCPI_ALLOW)
#undef RP_MAP_POLICY
			default:
				policy = "UNKNOWN";
				break;
		}

		blobmsg_add_string(bb, "band", band);
		blobmsg_add_string(bb, "policy", policy);

		blobmsg_add_u32(bb, "util_threshold", rp->util_threshold);
		blobmsg_add_u32(bb, "rcpi_threshold", rp->rcpi_threshold);
		blobmsg_add_u32(bb, "report_rcpi_threshold",
			rp->report_rcpi_threshold);
		blobmsg_add_u32(bb, "report_util_threshold",
			rp->report_util_threshold);
		blobmsg_add_u32(bb, "report_rcpi_hysteresis_margin",
			rp->report_rcpi_hysteresis_margin);
		blobmsg_add_u8(bb, "include_sta_stats",
			rp->include_sta_stats ? 1 : 0);
		blobmsg_add_u8(bb, "include_sta_metric",
			rp->include_sta_metric ? 1 : 0);
#if (EASYMESH_VERSION > 2)
		blobmsg_add_u8(bb, "include_wifi6_sta_status",
			rp->include_wifi6_sta_status ? 1 : 0);
#endif

		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, arr);
	return 0;
}

#if (EASYMESH_VERSION > 2)
static int dump_qosrule_list(struct blob_buf *bb, struct list_head *qoslist)
{
	struct qos_rule *rule = NULL;
	void *arr;

	arr = blobmsg_open_array(bb, "qosrule");
	list_for_each_entry(rule, qoslist, list) {
		void *t;

		t = blobmsg_open_table(bb, "");

		blobmsg_add_string(bb, "type", cntlr_qos_type2str(rule->type));
		blobmsg_add_u32(bb, "output", rule->output);
		blobmsg_add_u32(bb, "always_match", rule->always_match);

		if (rule->type == QOS_RULE_TYPE_DSCP_PCP) {
			int dscp_min;

			for (dscp_min = 0;  dscp_min < sizeof(rule->dscp_pcp.dscp_pcp); dscp_min++) {
				char fmt[64] = {0};
				int pcp, dscp_max;

				pcp = rule->dscp_pcp.dscp_pcp[dscp_min];
				if (pcp <= 0 || pcp > 7)
					continue;

				for (dscp_max = dscp_min;
				     dscp_max < sizeof(rule->dscp_pcp.dscp_pcp);
				     dscp_max++) {
					if (rule->dscp_pcp.dscp_pcp[dscp_max] != pcp)
						break;
				}
				if (dscp_max != (dscp_min + 1)) {
					snprintf(fmt, sizeof(fmt), "%d-%d,%d", dscp_min, dscp_max, pcp);
					dscp_min = dscp_max;
				} else
					snprintf(fmt, sizeof(fmt), "%d,%d", dscp_min, pcp);
				blobmsg_add_string(bb, "dscp_pcp", fmt);
			}

		}
		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, arr);

	return 0;
}
#endif

int COMMAND(dump_policy)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_DUMP_POLICY];
	struct node_policy *np = NULL;
	uint8_t agent_mac[6] = {0};
	char agent[18] = {0};
	void *arr;
#if (EASYMESH_VERSION > 2)
	void *obj;
#endif
	int ret;

	ret = controller_command_parse("dump_policy", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[DUMP_POLICY_AGENT]) {
		strncpy(agent, blobmsg_data(tb[DUMP_POLICY_AGENT]), sizeof(agent) - 1);
		if (!hwaddr_aton(agent, agent_mac))
			return -EINVAL;
	}

	arr = blobmsg_open_array(bb, "node");
	list_for_each_entry(np, &c->cfg.nodelist, list) {
		void *t;
		void *arr_steer;
		struct stax *steer_itm = NULL;

		if (tb[DUMP_POLICY_AGENT] && memcmp(np->agent_id, agent_mac, 6) != 0)
			continue;

		t = blobmsg_open_table(bb, "");

		blobmsg_add_macaddr(bb, "agent_id", np->agent_id);
		blobmsg_add_macaddr(bb, "bk_ul_mac", np->bk_ul_mac);
		blobmsg_add_macaddr(bb, "bk_dl_mac", np->bk_dl_mac);
		blobmsg_add_u32(bb, "primary_vid", np->pvid);
		blobmsg_add_u32(bb, "primary_pcp", np->pcp);
		blobmsg_add_u8(bb, "report_independent_scan", np->report_scan ? 1 : 0);
		blobmsg_add_u8(bb, "report_sta_assocfails",
			np->report_sta_assocfails ? 1 : 0);
		blobmsg_add_u32(bb, "report_sta_assocfails_rate",
			np->report_sta_assocfails_rate);
		blobmsg_add_u32(bb, "report_metric_periodic",
			np->report_metric_periodic);
		blobmsg_add_u8(bb, "steer_disallow", np->steer_disallow ? 1 : 0);
		blobmsg_add_u8(bb, "coordinated_cac", np->coordinated_cac ? 1 : 0);
		blobmsg_add_u8(bb, "traffic_separation",
			np->traffic_separation ? 1 : 0);
		blobmsg_add_u32(bb, "num_steer_stas", np->num_steer_stas);
		blobmsg_add_u32(bb, "num_btmsteer_stas", np->num_btmsteer_stas);
		blobmsg_add_u8(bb, "sta_steer", np->sta_steer ? 1 : 0);
		blobmsg_add_u8(bb, "is_policy_diff", np->is_policy_diff ? 1 : 0);

		dump_radiopolicy_list(bb, &np->radiolist,
			tb[DUMP_POLICY_AGENT] != NULL ? agent_mac : NULL);

		arr_steer = blobmsg_open_array(bb, "steer_exception");
		list_for_each_entry(steer_itm, &np->steer_exlist, list) {
			void *tt;
			char macstr[18];

			hwaddr_ntoa(steer_itm->macaddr, macstr);
			tt = blobmsg_open_table(bb, "");
			blobmsg_add_string(bb, "mac", macstr);
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, arr_steer);

		arr_steer = blobmsg_open_array(bb, "btmsteer_exception");
		list_for_each_entry(steer_itm, &np->btmsteer_exlist, list) {
			void *tt;
			char macstr[18];

			hwaddr_ntoa(steer_itm->macaddr, macstr);
			tt = blobmsg_open_table(bb, "");
			blobmsg_add_string(bb, "mac", macstr);
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, arr_steer);

		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, arr);

#if (EASYMESH_VERSION > 2)
	obj = blobmsg_open_table(bb, "qos");
	blobmsg_add_u8(bb, "enabled", c->cfg.qos.enabled);
	dump_qosrule_list(bb, &c->cfg.qos.rule);
	blobmsg_close_table(bb, obj);
#endif
	return 0;
}

int COMMAND(query_beacon_metrics)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_BCN_METRICS];
	struct sta_channel_report *reports = NULL;
	uint8_t reporting_detail = 0;
	uint8_t agent_mac[6] = {0};
	uint8_t bssid_mac[6] = {0};
	uint8_t sta_mac[6] = {0};
	struct cmdu_buff *cmdu;
	uint8_t num_element = 0;
	uint8_t *element = NULL;
	uint8_t num_report = 0;
	uint16_t mid = 0xffff;
	char agent[18] = {0};
	char bssid[18] = {0};
	uint8_t opclass = 0;
	uint8_t channel = 0;
	char ssid[33] = {0};
	char sta[18] = {0};
	int ret;


	ret = controller_command_parse("query_beacon_metrics", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent, blobmsg_data(tb[BCN_METRICS_ATTR_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, agent_mac))
		return -EINVAL;

	strncpy(sta, blobmsg_data(tb[BCN_METRICS_ATTR_STA]), sizeof(sta) - 1);
	if (!hwaddr_aton(sta, sta_mac))
		return -EINVAL;

	if (tb[BCN_METRICS_ATTR_BSSID]) {
		strncpy(bssid, blobmsg_data(tb[BCN_METRICS_ATTR_BSSID]), sizeof(bssid) - 1);
		if (!hwaddr_aton(bssid, bssid_mac))
			return -EINVAL;
	}

	if (tb[BCN_METRICS_ATTR_OPCLASS])
		opclass = (int) blobmsg_get_u32(tb[BCN_METRICS_ATTR_OPCLASS]);

	if (tb[BCN_METRICS_ATTR_CHANNEL])
		channel = (int) blobmsg_get_u32(tb[BCN_METRICS_ATTR_CHANNEL]);

	if (tb[BCN_METRICS_ATTR_REPORTING_DETAIL])
		reporting_detail = (int) blobmsg_get_u32(tb[BCN_METRICS_ATTR_REPORTING_DETAIL]);

	if (tb[BCN_METRICS_ATTR_SSID])
		strncpy(ssid, blobmsg_data(tb[BCN_METRICS_ATTR_SSID]), sizeof(ssid) - 1);

	/* Example ubus call:
	 * ubus call map.controller query_beacon_metrics '{"agent":
	 * "44:d4:37:42:47:b9", "sta":"44:d4:37:4d:84:83",
	 * "bssid":"44:d4:37:42:47:bf", "ssid":"MAP-$BASEMAC-5GHz",
	 * "channel_report":[{"opclass":81,"channels": [1, 6, 13]},
	 * {"opclass":82, "channels": [1, 6, 13]}],
	 * "reporting_detail":1, "request_element": [7, 33]}'
	 */

	if (tb[BCN_METRICS_ATTR_CHAN_REPORT]) {
		static const struct blobmsg_policy supp_attrs[2] = {
				[0] = { .name = "opclass",
					.type = BLOBMSG_TYPE_INT32 },
				[1] = { .name = "channels",
					.type = BLOBMSG_TYPE_ARRAY },
		};
		struct blob_attr *cur;
		int rem, i = 0;

		num_report = blobmsg_check_array(tb[BCN_METRICS_ATTR_CHAN_REPORT],
						 BLOBMSG_TYPE_TABLE);

		reports = calloc(num_report, sizeof(struct sta_channel_report));
		if (!reports) {
			ret = -ENOMEM;
			goto out;
		}

		blobmsg_for_each_attr(cur, tb[BCN_METRICS_ATTR_CHAN_REPORT], rem) {
			struct blob_attr *data[2], *attr;
			int remm, j = 0;

			blobmsg_parse(supp_attrs, 2, data, blobmsg_data(cur), blobmsg_data_len(cur));

			if (!data[0] || !data[1])
				continue;

			reports[i].opclass = (uint8_t)blobmsg_get_u32(data[0]);
			reports[i].num_channel = blobmsg_check_array(data[1], BLOBMSG_TYPE_INT32);

			// Iterate through all channels of the opclass
			blobmsg_for_each_attr(attr, data[1], remm) {
				if (blobmsg_type(attr) != BLOBMSG_TYPE_INT32)
					continue;

				/* Channel List */
				reports[i].channel[j++] = (uint8_t) blobmsg_get_u32(attr);
			}

			if (reports[i].num_channel != j) {
				dbg("%s(): invalid channel!\n", __func__);
				ret = -EINVAL;
				goto out;
			}

			i++;
		}

		if (num_report != i) {
			dbg("%s(): invalid report!\n", __func__);
			ret = -EINVAL;
			goto out;
		}
	}

	/* TODO: consider overriding reporting_detail */
	if (tb[BCN_METRICS_ATTR_ELEMENT_IDS] && reporting_detail == 1) {
		struct blob_attr *attr_id;
		int rem_id, k = 0;

		num_element = blobmsg_check_array(tb[BCN_METRICS_ATTR_ELEMENT_IDS], BLOBMSG_TYPE_INT32);

		element = calloc(num_element, sizeof(uint8_t));
		if (!element) {
			ret = -ENOMEM;
			goto out;
		}

		blobmsg_for_each_attr(attr_id, tb[BCN_METRICS_ATTR_ELEMENT_IDS], rem_id) {
			if (blobmsg_type(attr_id) != BLOBMSG_TYPE_INT32)
				continue;

			element[k] = (uint8_t)blobmsg_get_u32(attr_id);
			k++;
		}

		if (k != num_element) {
			dbg("%s(): invalid element ID!\n", __func__);
			ret = -EINVAL;
			goto out;
		}
	}

	cmdu = cntlr_gen_beacon_metrics_query(c, agent_mac,
					      sta_mac, opclass, channel,
					      bssid_mac, reporting_detail,
					      ssid, num_report, reports,
					      num_element, element);
	if (!cmdu) {
		ret = -EINVAL;
		goto out;
	}

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

out:
	if (element)
		free(element);

	if (reports)
		free(reports);

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

static char *cntlr_status_channel_pref_reason(uint8_t reason,
				enum wifi_radio_opclass_dfs status)
{
	switch (reason) {
	case CHANNEL_PREF_REASON_UNSPEC:
		return "none";
	case CHANNEL_PREF_REASON_NON11_INTERFERENCE:
		return "non11-interference";
	case CHANNEL_PREF_REASON_INT_OBSS_INTERFERENCE:
		return "int-obss-interference";
	case CHANNEL_PREF_REASON_EXT_OBSS_INTERFERENCE:
		return "ext-obss-interference";
	case CHANNEL_PREF_REASON_REDUCED_COVERAGE:
		return "reduced-coverage";
	case CHANNEL_PREF_REASON_REDUCED_THROUGHPUT:
		return "reduced-throughput";
	case CHANNEL_PREF_REASON_IN_DEVICE_INTERFERENCE:
		return "in-device-interference";
	case CHANNEL_PREF_REASON_DFS_NOP:
		return "dfs-nop";
	case CHANNEL_PREF_REASON_SHARED_BHAUL_PREVENT:
		return "shared-bhaul-prevent";
	case CHANNEL_PREF_REASON_DFS_USABLE:
		/* Two options here, CAC required or CAC ongoing */
		if (status == WIFI_RADIO_OPCLASS_CHANNEL_DFS_CAC)
			return "dfs-cac";
		else
			return "dfs-usable";
	case CHANNEL_PREF_REASON_DFS_AVAILABLE:
		return "dfs-available";
	case CHANNEL_PREF_REASON_REG_DISALLOWED:
		return "reg-disallowed";
	default:
		break;
	}

	return "unknown";
}

void cntlr_status_add_opclass(struct blob_buf *bb, struct wifi_radio_opclass *opclass,
			      const char *name, int opclass_id)
{
	enum wifi_radio_opclass_dfs dfs_status;
	uint32_t cac_methods, cac_time;
	void *a, *aa, *t, *tt;
	uint8_t reas, pref;
	char age[64];
	int j, k;

	/* Add age */
	snprintf(age, sizeof(age), "%s_age", name);
	blobmsg_add_u32(bb, age, (uint32_t) timestamp_elapsed_sec(&opclass->entry_time));

	a = blobmsg_open_array(bb, name);
	for (j = 0; j < opclass->num_opclass; j++) {
		if (opclass_id && opclass->opclass[j].id != opclass_id)
			continue;
		t = blobmsg_open_table(bb, "");
		blobmsg_add_u32(bb, "opclass", opclass->opclass[j].id);
		blobmsg_add_u32(bb, "bandwidth", opclass->opclass[j].bandwidth);
		if (strstr(name, "cur"))
			blobmsg_add_u32(bb, "txpower", opclass->opclass[j].max_txpower);
		aa = blobmsg_open_array(bb, "channels");
		for (k = 0; k < opclass->opclass[j].num_channel; k++) {
			tt = blobmsg_open_table(bb, "");
			blobmsg_add_u32(bb, "channel", opclass->opclass[j].channel[k].channel);
			if (!strstr(name, "cur")) {
				pref = (opclass->opclass[j].channel[k].preference & CHANNEL_PREF_MASK) >> 4;
				reas = opclass->opclass[j].channel[k].preference & CHANNEL_PREF_REASON;
				dfs_status = opclass->opclass[j].channel[k].dfs;

				blobmsg_add_u32(bb, "preference", pref);
				blobmsg_add_string(bb, "reason", cntlr_status_channel_pref_reason(reas, dfs_status));

				if (opclass->opclass[j].channel[k].cac_methods) {
					cac_methods = opclass->opclass[j].channel[k].cac_methods;
					cac_time = opclass->opclass[j].channel[k].cac_time;
					blobmsg_add_u32(bb, "cac_methods", cac_methods);
					blobmsg_add_u32(bb, "cac_time", cac_time);
				}
			}
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aa);
		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, a);
}

static int _cntlr_status(struct controller *c, void *args, void *out, bool full)
{
	struct blob_buf *bb = (struct blob_buf *)out;
	struct node *n = NULL;
	uint8_t cur_opclass_id;
	void *a, *b;


	blobmsg_add_u32(bb, "num_nodes", c->num_nodes);
	//blobmsg_add_u32(bb, "num_nodes", list_num_entries(&c->nodelist));
	a = blobmsg_open_array(bb, "node");
	list_for_each_entry(n, &c->nodelist, list) {
		void *t, *tt;
		char hwaddrstr[18] = {0};
		struct netif_radio *p = NULL;

		hwaddr_ntoa(n->almacaddr, hwaddrstr);
		t = blobmsg_open_table(bb, "");
		blobmsg_add_string(bb, "ieee1905id", hwaddrstr);
		blobmsg_add_u32(bb, "profile", n->map_profile);


		b = blobmsg_open_array(bb, "radios");
		list_for_each_entry(p, &n->radiolist, list) {
			char macaddrstr[18] = {0};
			void *tttt, *ttttt;
			struct netif_iface *fh = NULL;

			hwaddr_ntoa(p->radio_el->macaddr, macaddrstr);
			tt = blobmsg_open_table(bb, "");
			blobmsg_add_string(bb, "macaddr", macaddrstr);

			/* Show current/prefered opclasses */
			cntlr_status_add_opclass(bb, &p->radio_el->cur_opclass, "cur_opclass", 0);
			cntlr_acs_radio_info(bb, p);

			if (p->radio_el->bgcac_supported)
				cntlr_acs_radio_cleanup_info(bb, p);

			/* Limit opclass output if possible */
			cur_opclass_id = ctrl_radio_cur_opclass_id(p->radio_el);

			if (full) {
				cur_opclass_id = 0;
				cntlr_status_add_opclass(bb, &p->radio_el->supp_opclass, "supp_opclass", cur_opclass_id);
			}

			cntlr_status_add_opclass(bb, &p->radio_el->pref_opclass, "pref_opclass", cur_opclass_id);

			tttt = blobmsg_open_array(bb, "interfaces");
			list_for_each_entry(fh, &p->iflist, list) {
				if (!fh->bss->enabled)
					continue;

				ttttt = blobmsg_open_table(bb, "");
				if (fh->bss->is_fbss) {
					blobmsg_add_macaddr(bb, "bssid", fh->bss->bssid);
					blobmsg_add_string(bb, "type", "fronthaul");
					blobmsg_add_string(bb, "ssid", fh->bss->ssid);
				} else if (fh->bss->is_bbss) {
					blobmsg_add_macaddr(bb, "bssid", fh->bss->bssid);
					blobmsg_add_string(bb, "type", "backhaul");
					blobmsg_add_string(bb, "ssid", fh->bss->ssid);
				}
				blobmsg_close_table(bb, ttttt);
			}

			if (!hwaddr_is_zero(p->radio_el->bsta.macaddr)) {
				struct sta *s = cntlr_find_sta(c->sta_table, p->radio_el->bsta.macaddr);

				if (s && s->is_bsta) {
					ttttt = blobmsg_open_table(bb, "");
					blobmsg_add_macaddr(bb, "macaddr", s->macaddr);
					blobmsg_add_string(bb, "type", "station");
					if (!hwaddr_is_zero(s->bssid))
						blobmsg_add_macaddr(bb, "bssid", s->bssid);

					blobmsg_close_table(bb, ttttt);
				}
			}

			blobmsg_close_array(bb, tttt);
			blobmsg_close_table(bb, tt);
		}

		blobmsg_close_array(bb, b);
		blobmsg_close_table(bb, t);
	}

	blobmsg_close_array(bb, a);

	a = blobmsg_open_array(bb, "stations");

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

		list_for_each_entry(s, &n->stalist, list) {
			void *ttt;
			char stastr[18] = {0};
			char bssstr[18] = {0};
			struct tm *timeinfo;
			char t_assoc[30] = {};

			hwaddr_ntoa(s->de_sta->macaddr, stastr);
			hwaddr_ntoa(s->bssid, bssstr);

			ttt = blobmsg_open_table(bb, "");

			blobmsg_add_string(bb, "macaddr", stastr);
			blobmsg_add_string(bb, "bssid", bssstr);
			blobmsg_add_macaddr(bb, "node", n->almacaddr);
			blobmsg_add_string(bb, "type", !s->is_bsta ? "NON_IEEE1905" : "IEEE1905");
			blobmsg_add_u8(bb, "connected", s->state == STA_CONNECTED ? 1 : 0);

			/* show association time in ISO8601 format */
			timeinfo = localtime(&s->assoc_time);
			strftime(t_assoc, sizeof(t_assoc), "%Y-%m-%dT%H:%M:%S%z", timeinfo);

			blobmsg_add_u32(bb, "conntime", s->de_sta->conn_time);
			blobmsg_add_string(bb, "assoc_time", t_assoc);
			blobmsg_add_u32(bb, "time_delta", s->de_sta->time_delta);
			blobmsg_add_u32(bb, "dl_rate", s->de_sta->dl_rate);
			blobmsg_add_u32(bb, "ul_rate", s->de_sta->ul_rate);
			blobmsg_add_u32(bb, "dl_utilization", s->de_sta->dl_utilization);
			blobmsg_add_u32(bb, "ul_utilization", s->de_sta->ul_utilization);
			blobmsg_add_u32(bb, "dl_est_thput", s->de_sta->dl_est_thput);
			blobmsg_add_u32(bb, "ul_est_thput", s->de_sta->ul_est_thput);
			blobmsg_add_u32(bb, "ul_rcpi", s->de_sta->rcpi);
			//blobmsg_add_u32(bb, "last_steer_time", s->stats.last_steer_time);
			//blobmsg_add_u32(bb, "failed_steer_attempts", s->de_sta->mapsta.failed_steer_attempts);

			if (s->de_sta->reassoc_framelen) {
				char frmstr[s->de_sta->reassoc_framelen * 2 + 1];

				memset(frmstr, 0, s->de_sta->reassoc_framelen * 2 + 1);
				btostr(s->de_sta->reassoc_frame, s->de_sta->reassoc_framelen, frmstr);
				blobmsg_add_string(bb, "reassoc_frame", frmstr);

				/* Ensure capabilities are parsed */
				if (!s->sta_caps.valid)
					sta_update_capabilities(s);

				blobmsg_add_u8(bb, "dot11k_nbr_report",
						!!(s->sta_caps.rm_caps & RM_CAP_NBR_REPORT) ? 1 : 0);
				blobmsg_add_u8(bb, "dot11k_bcn_active",
						!!(s->sta_caps.rm_caps & RM_CAP_BCN_ACTIVE) ? 1 : 0);
				blobmsg_add_u8(bb, "dot11k_bcn_passive",
						!!(s->sta_caps.rm_caps & RM_CAP_BCN_PASSIVE) ? 1 : 0);
				blobmsg_add_u8(bb, "dot11k_bcn_table",
						!!(s->sta_caps.rm_caps & RM_CAP_BCN_TABLE) ? 1 : 0);
				blobmsg_add_u8(bb, "dot11v_btm",
						s->sta_caps.btm_support ? 1 : 0);
				blobmsg_add_u8(bb, "multiband",
						s->sta_caps.multiband ? 1 : 0);
				blobmsg_add_u8(bb, "agile_multiband",
						s->sta_caps.agile_multiband ? 1 : 0);
			}

			add_array_meas_reportlist(bb, s);
			blobmsg_close_table(bb, ttt);
		}
	}

	blobmsg_close_array(bb, a);

	return 0;
}

int COMMAND(status)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;

	return _cntlr_status(c, args, out, false);
}

int COMMAND(status_full)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;

	return _cntlr_status(c, args, out, true);
}

int COMMAND(metrics)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct node *n = NULL;
	void *a;

	a = blobmsg_open_array(bb, "backhaul_metrics");
	list_for_each_entry(n, &c->nodelist, list) {
		void *node_tbl;
		void *tx, *rx;

		/* Only output metrics for downstream nodes with upstream connection */
		if (hwaddr_is_zero(n->upstream_bssid))
			continue;

		if (hwaddr_is_zero(n->tx_metrics.local_macaddr))
			continue;

		node_tbl = blobmsg_open_table(bb, "");
		blobmsg_add_macaddr(bb, "node", n->almacaddr);
		blobmsg_add_macaddr(bb, "upstream_bssid", n->upstream_bssid);
		blobmsg_add_macaddr(bb, "downstream_mac", n->tx_metrics.local_macaddr);
		blobmsg_add_u32(bb, "type", n->tx_metrics.mediatype);

		/* TX metrics (node -> upstream bBSS) */
		tx = blobmsg_open_table(bb, "tx");
		blobmsg_add_u32(bb, "max_throughput", n->tx_metrics.max_throughput);
		blobmsg_add_u32(bb, "phyrate", n->tx_metrics.phyrate);
		blobmsg_add_u32(bb, "link_availability", n->tx_metrics.availability);
		blobmsg_add_u32(bb, "packets", n->tx_metrics.packets);
		blobmsg_add_u32(bb, "errors", n->tx_metrics.errors);
		blobmsg_close_table(bb, tx);

		/* RX metrics (node receives from upstream bBSS) */
		rx = blobmsg_open_table(bb, "rx");
		blobmsg_add_u32(bb, "packets", n->rx_metrics.packets);
		blobmsg_add_u32(bb, "errors", n->rx_metrics.errors);
		/* Only report RSSI for WiFi media types (0x01xx) */
		if (media_is_ieee80211(n->tx_metrics.mediatype))
			blobmsg_add_u32(bb, "rssi", n->rx_metrics.rssi);
		blobmsg_close_table(bb, rx);

		blobmsg_close_table(bb, node_tbl);
	}
	blobmsg_close_array(bb, a);

	return 0;
}

int COMMAND(timers)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	void *t;

	t = blobmsg_open_table(bb, "channel_planning");
	blobmsg_add_u32(bb, "acs_timeout", timer_remaining_ms(&c->acs));
	blobmsg_close_table(bb, t);

	return 0;
}

int COMMAND(reconf)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	bool changed;

	changed = cntlr_resync_config(c, true);

	blobmsg_add_u32(bb, "status", 0);
	blobmsg_add_string(bb, "message", changed ?
			"Configuration reloaded and changes applied" :
			"Configuration reloaded, no changes detected");

	return 0;
}

#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
int COMMAND(dpp_add_uri)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_attr *tb[NUM_ATTRS_DPP_ADD_URI];
	struct blob_buf *bb = (struct blob_buf *)out;
	struct dpp_bootstrap_info *enrollee_bi;
	char alias[64];
	char *uri;
	int ret;

	ret = controller_command_parse("dpp_add_uri", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (!tb[DPP_ADD_URI_ATTR_URI]) {
		if (!blobmsg_add_json_from_file(bb, DPP_URI_FILE)) {
			void *a;

			dbg("%s: Failed to parse %s\n", __func__, DPP_URI_FILE);
			a = blobmsg_open_array(bb, "enrollees");
			blobmsg_close_array(bb, a);
		}
		return 0;
	}

	uri = blobmsg_data(tb[DPP_ADD_URI_ATTR_URI]);
	if (!uri)
		return UBUS_STATUS_UNKNOWN_ERROR;

	if (tb[DPP_ADD_URI_ATTR_ALIAS])
		strncpy(alias, blobmsg_get_string(tb[DPP_ADD_URI_ATTR_ALIAS]), sizeof(alias) - 1);
	else
		dppfile_gen_alias(alias, sizeof(alias));

	ret = dpp_append_enrollee_uri(c, uri, alias);
	if (ret) {
		blobmsg_add_string(bb, "status", "fail");
		blobmsg_add_string(bb, "reason", "error adding to /etc/multiap/dpp_uris.json");
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	enrollee_bi = calloc(1, sizeof(*enrollee_bi));
	if (!enrollee_bi) {
		fprintf(stderr, "Failed to allocate enrollee_bi\n");
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	ret = dpp_build_bootstrap_info_from_uri(uri, enrollee_bi);
	if (ret) {
		fprintf(stderr, "Failed to build bootstrap info\n");
		free(enrollee_bi);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	ret = dpp_bootstrap_add(c->dpp, enrollee_bi);
	if (ret) {
		fprintf(stderr, "Failed to add bootstrap\n");
		free(enrollee_bi);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	blobmsg_add_string(bb, "status", "ok");
	return 0;
}

int COMMAND(dpp_del_uri)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_attr *tb[NUM_ATTRS_DPP_DEL_URI];
	struct blob_buf *bb = (struct blob_buf *)out;
	char *uri = NULL, *alias = NULL;
	int ret;

	ret = controller_command_parse("dpp_del_uri", args, tb);
	if (ret) {
		warn("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (!tb[DPP_DEL_URI_ATTR_URI] && !tb[DPP_DEL_URI_ATTR_ALIAS]) {
		warn("%s: URI or alias must be provided\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[DPP_DEL_URI_ATTR_URI]) {
		uri = blobmsg_data(tb[DPP_DEL_URI_ATTR_URI]);
		if (!uri)
			return UBUS_STATUS_UNKNOWN_ERROR;
	}

	if (tb[DPP_DEL_URI_ATTR_ALIAS]) {
		alias = blobmsg_data(tb[DPP_DEL_URI_ATTR_ALIAS]);
		if (!alias)
			return UBUS_STATUS_UNKNOWN_ERROR;
	}

	ret = dpp_mod_enrollee(c, NULL, alias, uri, false);
	blobmsg_add_string(bb, "status", (ret ? "fail" : "ok"));

	return 0;
}

int COMMAND(dpp_advertise_cce)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_DPP_ADVERTISE_CCE];
	uint8_t agent_mac[6] = { 0 };
	char agent[18] = { 0 };
	bool enable = true;
	struct cmdu_buff *cmdu;
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("dpp_advertise_cce", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agent, blobmsg_data(tb[DPP_ADVERTISE_CCE_ATTR_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, agent_mac))
		return -EINVAL;

	if (tb[DPP_ADVERTISE_CCE_ATTR_ENABLE])
		enable = !!blobmsg_get_u32(tb[DPP_ADVERTISE_CCE_ATTR_ENABLE]);

	cmdu = cntlr_gen_dpp_cce_indication(c, agent_mac, enable);
	if (!cmdu)
		return -1;

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}
#ifdef ZEROTOUCH_DPP
int COMMAND(zerotouch_add_key)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_ZEROTOUCH_ADD_KEY];
	const char *key = NULL;
	char alias[64] = {0};
	int ret;

	ret = controller_command_parse("zerotouch_add_key", args, tb);
	if (ret) {
		warn("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	key = blobmsg_get_string(tb[ZEROTOUCH_ADD_KEY_ATTR_KEY]);
	if (tb[ZEROTOUCH_ADD_KEY_ATTR_ALIAS])
		strncpy(alias, blobmsg_get_string(tb[ZEROTOUCH_ADD_KEY_ATTR_ALIAS]), sizeof(alias) - 1);
	else
		dppfile_gen_alias(alias, sizeof(alias));

	ret = zerotouch_add_enrollee_key(c, key, alias);
	if (ret == ZT_PASSPHRASE_MOD_ERROR) {
		warn("%s: Failed to append key, Error\n", __func__);
		return -EINVAL;
	} else if (ret == ZT_PASSPHRASE_MOD_EXISTS) {
		blobmsg_add_string(bb, "status", "fail");
		blobmsg_add_string(bb, "reason", "key exists");
		ret = 0;
		goto out;
	}

	blobmsg_add_string(bb, "status", !ret ? "ok" : "fail");
out:
	return ret;
}

int COMMAND(zerotouch_del_key)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_ZEROTOUCH_DEL_KEY];
	const char *key = NULL, *alias = NULL;
	int ret;

	ret = controller_command_parse("zerotouch_del_key", args, tb);
	if (ret) {
		warn("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[ZEROTOUCH_DEL_KEY_ATTR_ALIAS]) {
		alias = blobmsg_get_string(tb[ZEROTOUCH_DEL_KEY_ATTR_ALIAS]);
	} else if (tb[ZEROTOUCH_DEL_KEY_ATTR_KEY]) {
		key = blobmsg_get_string(tb[ZEROTOUCH_DEL_KEY_ATTR_KEY]);
	} else {
		warn("%s: Must provide either key or alias\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	ret = zerotouch_del_enrollee_key(c, key, alias);
	if (ret == ZT_PASSPHRASE_MOD_ERROR) {
		warn("%s: Failed to append key, Error\n", __func__);
		return -EINVAL;
	} else if (ret == ZT_PASSPHRASE_MOD_EXISTS) {
		blobmsg_add_string(bb, "status", "fail");
		blobmsg_add_string(bb, "reason", "key exists");
		ret = 0;
		goto out;
	}

	blobmsg_add_string(bb, "status", !ret ? "ok" : "fail");
out:
	return ret;
}
#endif
#endif
#endif
#if (EASYMESH_VERSION >= 6)
int COMMAND(dump_mlo_caps)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_MLO_CAPS_DUMP];
	uint8_t agent_mac[6] = { 0 };
	char agent[18] = { 0 };
	struct node *n = NULL;
	void *a;
	int ret;

	ret = controller_command_parse("dump_mlo_caps", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	if (tb[MLO_CAPABILITIES_ATTR_AGENT]) {
		strncpy(agent, blobmsg_data(tb[MLO_CAPABILITIES_ATTR_AGENT]), sizeof(agent) - 1);
		if (!hwaddr_aton(agent, agent_mac))
			return -EINVAL;
	}

	a = blobmsg_open_array(bb, "node");

	list_for_each_entry(n, &c->nodelist, list) {
		char macaddrstr[18] = { 0 };
		struct netif_radio *r = NULL;
		void *ttt;
		void *tt;
		void *t;

		if (tb[MLO_CAPABILITIES_ATTR_AGENT] && memcmp(n->almacaddr, agent_mac, 6))
			continue;

		hwaddr_ntoa(n->almacaddr, macaddrstr);
		t = blobmsg_open_table(bb, "");

		blobmsg_add_string(bb, "alid", macaddrstr);
		tt = blobmsg_open_array(bb, "radio");

		list_for_each_entry(r, &n->radiolist, list) {
			void *tttt;

			ttt = blobmsg_open_table(bb, "");
			hwaddr_ntoa(r->radio_el->macaddr, macaddrstr);
			blobmsg_add_string(bb, "ruid", macaddrstr);

			tttt = blobmsg_open_table(bb, "ap_caps");
			blobmsg_add_u32(bb, "str_support", r->radio_el->wifi7caps_ap.str);
			blobmsg_add_u32(bb, "nstr_support", r->radio_el->wifi7caps_ap.nstr);
			blobmsg_add_u32(bb, "emlsr_support", r->radio_el->wifi7caps_ap.emlsr);
			blobmsg_add_u32(bb, "emlmr_support", r->radio_el->wifi7caps_ap.emlmr);
			blobmsg_add_u32(bb, "str_freqsep", r->radio_el->wifi7caps_ap.str_freqsep[0].sep);
			blobmsg_add_u32(bb, "emlsr_freqsep", r->radio_el->wifi7caps_ap.emlsr_freqsep[0].sep);
			blobmsg_add_u32(bb, "emlmr_freqsep", r->radio_el->wifi7caps_ap.emlmr_freqsep[0].sep);
			blobmsg_close_table(bb, tttt);
			tttt = blobmsg_open_table(bb, "bsta_caps");
			blobmsg_add_u32(bb, "str_support", r->radio_el->wifi7caps_bsta.str);
			blobmsg_add_u32(bb, "nstr_support", r->radio_el->wifi7caps_bsta.nstr);
			blobmsg_add_u32(bb, "emlsr_support", r->radio_el->wifi7caps_bsta.emlsr);
			blobmsg_add_u32(bb, "emlmr_support", r->radio_el->wifi7caps_bsta.emlmr);
			blobmsg_add_u32(bb, "str_freqsep", r->radio_el->wifi7caps_bsta.str_freqsep[0].sep);
			blobmsg_add_u32(bb, "emlsr_freqsep", r->radio_el->wifi7caps_bsta.emlsr_freqsep[0].sep);
			blobmsg_add_u32(bb, "emlmr_freqsep", r->radio_el->wifi7caps_bsta.emlmr_freqsep[0].sep);
			blobmsg_close_table(bb, tttt);
			blobmsg_close_table(bb, ttt);
		}
		blobmsg_close_array(bb, tt);
		blobmsg_close_table(bb, t);

	}
	blobmsg_close_array(bb, a);
	return 0;
}
#endif


#ifdef EASYMESH_VENDOR_EXT
int COMMAND(disassociate_sta)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_DISASSOCIATE_STA];
	struct cmdu_buff *cmdu;
	uint8_t agent[6] = {0};
	char agentstr[18] = {0};
	char sta_macstr[18] = {0};
	uint8_t sta[6] = {0};
	uint16_t reason = 0;
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("disassociate_sta", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[DISASSOCIATE_STA_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;


	strncpy(sta_macstr, blobmsg_data(tb[DISASSOCIATE_STA_ATTR_STA]), sizeof(sta_macstr) - 1);
	if (!hwaddr_aton(sta_macstr, sta))
		return -EINVAL;


	if (tb[DISASSOCIATE_STA_ATTR_REASON])
		reason = (uint16_t) blobmsg_get_u32(tb[DISASSOCIATE_STA_ATTR_REASON]);

	cmdu = cntlr_gen_vendor_specific_disassociate_sta(c, sta, reason);
	if (!cmdu)
		return -1;

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

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}

int COMMAND(reset_agent)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_ATTRS_RESET_AGENT];
	struct cmdu_buff *cmdu;
	uint8_t agent[6] = {0};
	char agentstr[18] = {0};
	uint16_t mid = 0xffff;
	int ret;

	ret = controller_command_parse("reset_agent", args, tb);
	if (ret) {
		err("%s: Error (ret = %d)\n", __func__, ret);
		return -EINVAL;
	}

	strncpy(agentstr, blobmsg_data(tb[RESET_AGENT_ATTR_AGENT]), sizeof(agentstr) - 1);
	if (!hwaddr_aton(agentstr, agent))
		return -EINVAL;

	cmdu = cntlr_gen_vendor_specific_reset_agent(c);
	if (!cmdu)
		return -1;

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

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

	/* reply with mid */
	blobmsg_add_string(bb, "status", mid != 0xffff ? "ok" : "fail");
	if (mid != 0xffff)
		blobmsg_add_u32(bb, "mid", mid);

	return mid != 0xffff ? 0 : -1;
}
#endif

int COMMAND(dump_topology)(void *priv, void *args, void *out)
{
	struct controller *c = (struct controller *)priv;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct bh_topology_dev *dev = NULL;
	void *t, *a;

	t = blobmsg_open_table(bb, "topology");
	blobmsg_add_u32(bb, "num_nodes", c->topology.num_devs);

	a = blobmsg_open_array(bb, "node");
	list_for_each_entry(dev, &c->topology.dev_list, list) {
		void *tt;

		tt = blobmsg_open_table(bb, "");
		blobmsg_add_macaddr(bb, "alid", dev->al_macaddr);
		if (dev->bh.parent) {
			blobmsg_add_macaddr(bb, "parent", dev->bh.parent->al_macaddr);
			if (dev->bh.parent_iface)
				blobmsg_add_macaddr(bb, "parent_iface", dev->bh.parent_iface->macaddr);

			if (dev->bh.own_iface) {
				blobmsg_add_macaddr(bb, "own_iface", dev->bh.own_iface->macaddr);
				blobmsg_add_string(bb, "media", i1905_media_type_to_str(dev->bh.own_iface->media_type));
			}
		}
		blobmsg_add_u32(bb, "depth", dev->bh.depth);
		blobmsg_add_u32(bb, "depth_wifi", dev->bh.wifi_hops_from_root);

		blobmsg_close_table(bb, tt);
	}
	blobmsg_close_array(bb, a);
	blobmsg_close_table(bb, t);

	return 0;
}

#if 0
int controller_init_apis()
{
	controller_register_api(help, "Show usage")
	controller_register_api(query_ap_caps, "Send AP-Capability Query to Agent(s)"),
	controller_register_api(query_sta_caps, "Send STA Capability Query to Agent(s)"),
	controller_register_api(query_bsta_caps, "Send BSTA Capability Query to Agent(s)"),
	controller_register_api(query_ap_metrics, "Send AP Metrics Query to Agent(s)"),
	controller_register_api(query_sta_metrics, "Send STA/bSTA Link Metrics Query to Agent"),
	controller_register_api(query_unassoc_sta_metrics, "Send Unassociated STA Link Metrics Query to Agent(s)"),
	controller_register_api(query_channel_pref, "Send Channel Preference Query to Agent(s)"),
	controller_register_api(query_beacon_metrics, "Send Beacon Metrics Query to Agent"),
	controller_register_api(send_channel_sel, "Send Channel Selection Request to Agent"),
	controller_register_api(trigger_channel_clearing, "Trigger CAC Request in Agent for clearing DFS channels"),
	controller_register_api(steer, "Send STA steering request to Agent"),
	controller_register_api(steer_op, "Send STA steering Opportunity request to Agent"),
	controller_register_api(assoc_control, "Send STA/bSTA association control request to Agent"),
	controller_register_api(steer_backhaul, "Send bSTA Steering Request to Agent"),
	//controller_register_api(set_policy, "Set Controller Policy in Agent(s)"),
	controller_register_api(dump_beacon_metrics, "Dump collected beacon metrices"),
	controller_register_api(scan, "Send Scanning Request to Agent"),
	controller_register_api(scanresults, "Dump collected scanresults from Agent(s)"),
	controller_register_api(cac_start, "Send CAC Request to Agent"),
	controller_register_api(cac_stop, "Send CAC Terminate Request to Agent"),
	controller_register_api(send_hld, "Send Higher Layer Data to Agent"),
	controller_register_api(send_combined_metrics, "Send Combined Infrastructure Metrics to Agent"),
	controller_register_api(send_topology_query, "Send Topology Query to Agent"),
	controller_register_api(dump_steer_summary, "Dump STA steering summary"),
	controller_register_api(dump_steer_history, "Dump STA steering history"),
	controller_register_api(dump_unassoc_sta_metrics, "Dump Unassociated STA link metrics"),
	controller_register_api(status, "Show status"),
	controller_register_api(status_full, "Show verbose status"),
	controller_register_api(timers, "Show timers (for debugging)"),
}
#endif


