/*
 * agent_ubus.c - provides 'agent' object
 *
 * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */

#include "agent_ubus.h"

#include <cmdu.h>
#ifdef AGENT_SYNC_DYNAMIC_CNTLR_CONFIG
#include <cntlrsync.h>
#endif
#include <wifidefs.h>
#ifdef USE_LIBDPP
#include <dpp_api.h>
#endif
#include <easy/timestamp.h>
#include <easy/utils.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/list.h>
#include <libubus.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <timer.h>
#include <ubusmsg.h>
#include <uci.h>

#include "agent.h"
#include "agent_cmdu.h"
#include "agent_map.h"
#include "assoc_ctrl.h"
#include "backhaul.h"
#include "config.h"
#ifdef DYNBH
#include "dynbh/dynbh_ubus.h"
#endif
#include "utils/debug.h"
#include "utils/utils.h"
#include "easymesh.h"

#define OBJECT_INVALID	(0xFFFFFFFF)

#define MULTICAST_ADDR_STR "01:80:c2:00:00:13"

/* steer policy */
enum {
	STEER_POLICY_IFNAME,
	STEER_POLICY_RULE,     /* "rssi","load","capacity","user","custom" */
	_STEER_POLICY_MAX,
};

static const struct blobmsg_policy steer_policy_params[_STEER_POLICY_MAX] = {
	[STEER_POLICY_IFNAME] = { .name = "vif", .type = BLOBMSG_TYPE_STRING },
	[STEER_POLICY_RULE] = { .name = "rule", .type = BLOBMSG_TYPE_STRING },
};

/* steer */
enum {
	STEER_IFNAME,
	STEER_TYPE,     /* opportunity or  mandate */
	STEER_STA,      /* STA to be steered */
	STEER_TO_BSS,   /* array of desired target BSSes */
	STEER_OPTIME,   /* steer opportunity time window (in seconds) */
	_STEER_MAX,
};

static const struct blobmsg_policy steer_params[_STEER_MAX] = {
	[STEER_IFNAME] = { .name = "vif", .type = BLOBMSG_TYPE_STRING },
	[STEER_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
	[STEER_STA] = { .name = "sta", .type = BLOBMSG_TYPE_STRING },
	[STEER_TO_BSS] = { .name = "to_bss", .type = BLOBMSG_TYPE_ARRAY },
	[STEER_OPTIME] = { .name = "optime", .type = BLOBMSG_TYPE_INT32 },
};

/* assoc_control (timed blocking of STA association) */
enum {
	ASSOC_CONTROL_BSSID, /* BSSID to apply association control. */
	ASSOC_CONTROL_STA,	 /* Array of STA MACs for association control. */
	ASSOC_CONTROL_MODE,  /* Mode: 0-block, 1-unblock, 2-timed, 3-indefinite */
	ASSOC_CONTROL_VALIDITY_TIME, /* Time in seconds association control applies. */
	_ASSOC_CONTROL_MAX,
};

static const struct blobmsg_policy assoc_control_params[_ASSOC_CONTROL_MAX] = {
	[ASSOC_CONTROL_BSSID] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
	[ASSOC_CONTROL_STA] = { .name = "stalist", .type = BLOBMSG_TYPE_ARRAY },
	[ASSOC_CONTROL_MODE] = { .name = "mode", .type = BLOBMSG_TYPE_INT32},
	[ASSOC_CONTROL_VALIDITY_TIME] = {
		.name = "validity_int",
		.type = BLOBMSG_TYPE_INT32
	},
};

/* fh_toggle */
enum {
	TOGGLE_FH_ENABLE,   /* enable or disable */
#ifdef AGENT_ISLAND_PREVENTION
	TOGGLE_FH_ISL_PREV, /* remote island formation prevention */
#endif
	TOGGLE_FH_IFNAME,
	_TOGGLE_FH_MAX,
};

static const struct blobmsg_policy toggle_fh_params[_TOGGLE_FH_MAX] = {
	[TOGGLE_FH_ENABLE] = { .name = "enable", .type = BLOBMSG_TYPE_BOOL },
#ifdef AGENT_ISLAND_PREVENTION
	[TOGGLE_FH_ISL_PREV] = { .name = "prevent_island", .type = BLOBMSG_TYPE_BOOL },
#endif
	[TOGGLE_FH_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
};

enum {
	UNASSOC_STA_LM_QUERY_AGENT,
	UNASSOC_STA_LM_QUERY_OPCLASS,
	UNASSOC_STA_LM_QUERY_METRICS,
	__UNASSOC_STA_LM_QUERY_MAX,
};

static const struct blobmsg_policy
		unassoc_sta_lm_query_params[__UNASSOC_STA_LM_QUERY_MAX] = {
	[UNASSOC_STA_LM_QUERY_AGENT] = { .name = "agent",
		.type = BLOBMSG_TYPE_STRING },
	[UNASSOC_STA_LM_QUERY_OPCLASS] = { .name = "opclass",
		.type = BLOBMSG_TYPE_INT32 },
	[UNASSOC_STA_LM_QUERY_METRICS] = { .name = "metrics",
		.type = BLOBMSG_TYPE_ARRAY },
};

/* topology query */
enum {
	TOPOLOGY_QUERY_AGENT,
	__TOPOLOGY_QUERY_MAX,
};

static const struct blobmsg_policy topology_query_params[__TOPOLOGY_QUERY_MAX] = {
	[TOPOLOGY_QUERY_AGENT] = { .name = "agent", .type = BLOBMSG_TYPE_STRING },
};


/* bcn_metrics_query */
enum {
	BCN_METRICS_AGENT,
	BCN_METRICS_STA,
	BCN_METRICS_OPCLASS,
	BCN_METRICS_CHANNEL,
	BCN_METRICS_BSSID,
	BCN_METRICS_REPORTING_DETAIL,
	BCN_METRICS_SSID,
	BCN_METRICS_CHAN_REPORT,
	BCN_METRICS_ELEMENT_IDS,
	__BCN_METRICS_QUERY_MAX,
};

static const struct blobmsg_policy
		bcn_metrics_query_params[__BCN_METRICS_QUERY_MAX] = {
	[BCN_METRICS_AGENT] = { .name = "agent", .type = BLOBMSG_TYPE_STRING },
	[BCN_METRICS_STA] = { .name = "sta", .type = BLOBMSG_TYPE_STRING },
	[BCN_METRICS_OPCLASS] = { .name = "opclass",
			.type = BLOBMSG_TYPE_INT32 },
	[BCN_METRICS_CHANNEL] = { .name = "channel",
			.type = BLOBMSG_TYPE_INT32 },
	[BCN_METRICS_BSSID] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
	[BCN_METRICS_REPORTING_DETAIL] = { .name = "reporting_detail",
			.type = BLOBMSG_TYPE_INT32 },
	[BCN_METRICS_SSID] = { .name = "ssid", .type = BLOBMSG_TYPE_STRING },
	[BCN_METRICS_CHAN_REPORT] = { .name = "channel_report",
			.type = BLOBMSG_TYPE_ARRAY },
	[BCN_METRICS_ELEMENT_IDS] = { .name = "request_element",
			.type = BLOBMSG_TYPE_ARRAY },
};

enum {
	HLD_AGENT,
	HLD_PROTOCOL,
	HLD_DATA,
	_HLD_MAX,
};

static const struct blobmsg_policy higher_layer_data_params[_HLD_MAX] = {
	[HLD_AGENT] = { .name = "agent", .type = BLOBMSG_TYPE_STRING },
	[HLD_PROTOCOL] = { .name = "protocol", .type = BLOBMSG_TYPE_INT32 },
	[HLD_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING },
};

#if 0
enum {
	CFG_POLICY_AGENT,
	CFG_POLICY_BSSID,
	__CFG_POLICY_MAX,
};

static const struct blobmsg_policy config_policy_params[__CFG_POLICY_MAX] = {
	[CFG_POLICY_AGENT] = { .name = "agent", .type = BLOBMSG_TYPE_STRING },
	[CFG_POLICY_BSSID] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
};
#endif

enum {
	SEARCH_POLICY_BAND,
	__SEARCH_POLICY_MAX,
};

static const struct blobmsg_policy search_policy_params[__SEARCH_POLICY_MAX] = {
	[SEARCH_POLICY_BAND] = { .name = "band", .type = BLOBMSG_TYPE_INT32 },
};

enum {
	SET_BH_BAND,
	SET_BH_TARGET_BSSID,
	SET_BH_FORCE_DISASSOC,
	__SET_BH_MAX,
};

static const struct blobmsg_policy set_bh_params[__SET_BH_MAX] = {
	[SET_BH_BAND] = { .name = "band", .type = BLOBMSG_TYPE_INT32 },
	[SET_BH_TARGET_BSSID] = { .name = "target_bssid", .type = BLOBMSG_TYPE_STRING },
	[SET_BH_FORCE_DISASSOC] = { .name = "force_disassoc", .type = BLOBMSG_TYPE_INT32 },
};

/* scanresults */
enum {
	SCANRESULTS_RADIO_NAME,
	SCANRESULTS_RADIO_MAC,
	SCANRESULTS_BAND,
	SCANRESULTS_CACHE,
	_SCANRESULTS_MAX
};

static const struct blobmsg_policy scanresults_params[_SCANRESULTS_MAX] = {
	[SCANRESULTS_RADIO_NAME] = {
		.name = "radio",
		.type = BLOBMSG_TYPE_STRING
	},
	[SCANRESULTS_RADIO_MAC] = {
		.name = "radio_mac",
		.type = BLOBMSG_TYPE_STRING
	},
	[SCANRESULTS_BAND] = {
		.name = "band",
		.type = BLOBMSG_TYPE_INT32
	},
	[SCANRESULTS_CACHE] = {
		.name = "cache",
		.type = BLOBMSG_TYPE_BOOL
	},
};

/* scan */
enum {
	SCAN_RADIO,
	SCAN_BAND,
	SCAN_OPCLASS,
	SCAN_CHANNEL,
	_SCAN_MAX
};

static const struct blobmsg_policy scan_params[_SCAN_MAX] = {
	[SCAN_RADIO] = {
		.name = "radio",
		.type = BLOBMSG_TYPE_STRING
	},
	[SCAN_BAND] = {
		.name = "band",
		.type = BLOBMSG_TYPE_INT32
	},
	[SCAN_OPCLASS] = {
		.name = "opclass",
		.type = BLOBMSG_TYPE_ARRAY
	},
	[SCAN_CHANNEL] = {
		.name = "channel",
		.type = BLOBMSG_TYPE_ARRAY
	},
};

enum {
	LOG_ATTR_FEATURE,
	LOG_ATTR_LEVEL,
	__LOG_ATTR_MAX,
};

static const struct blobmsg_policy
		log_cmd_params[__LOG_ATTR_MAX] = {
	[LOG_ATTR_FEATURE] = { .name = "feature",
		.type = BLOBMSG_TYPE_STRING },
	[LOG_ATTR_LEVEL] = { .name = "level",
		.type = BLOBMSG_TYPE_INT32 },
};

static int steer_policy(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[_STEER_POLICY_MAX];
	char ifname[16] = {0};


	blobmsg_parse(steer_policy_params, _STEER_POLICY_MAX, tb,
		blob_data(msg), blob_len(msg));

	if (!(tb[STEER_POLICY_IFNAME])) {
		dbg("%s(): ifname not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	memset(ifname, '\0', sizeof(ifname));
	strncpy(ifname, blobmsg_data(tb[STEER_POLICY_IFNAME]),
			sizeof(ifname) - 1);

	if (!(tb[STEER_POLICY_RULE])) {
		dbg("%s(): Steer rule not specified!\n", __func__);
		/* TODO:
		 * dump current rules
		 */
	} else {
		/* TODO:
		 * validate rules ...
		 */
	}

	/* TODO: steer rules */
	/* if (wifiagent_update_steer_policy(ifname, policy) != 0) {
	 *	return UBUS_STATUS_UNKNOWN_ERROR;
	 * }
	 */

	return UBUS_STATUS_OK;
}

static int assoc_control(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct blob_attr *tb[_ASSOC_CONTROL_MAX];
	char bssid[18] = {0};
	uint8_t bssid_mac[6] = {0};
	int time = 0;
	int mode = ASSOC_CTRL_BLOCK; /* Block by default */
	uint8_t sta_list[ASSOC_CTRL_MAX_STA][6];
	int num_sta = 0;

	blobmsg_parse(assoc_control_params, _ASSOC_CONTROL_MAX,
			tb, blob_data(msg), blob_len(msg));

	/* TODO: allow empty - apply for all BSSs in Agent */
	if (!(tb[ASSOC_CONTROL_BSSID])) {
		dbg("%s(): bssid not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	strncpy(bssid, blobmsg_data(tb[ASSOC_CONTROL_BSSID]),
				sizeof(bssid) - 1);

	if (!hwaddr_aton(bssid, bssid_mac))
		return UBUS_STATUS_UNKNOWN_ERROR;

	/* TODO: allow empty - apply for all STAs in BSS */
	if (!(tb[ASSOC_CONTROL_STA])) {
		dbg("%s(): STA not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

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

		num_sta = blobmsg_check_array(tb[ASSOC_CONTROL_STA],
					BLOBMSG_TYPE_STRING);
		if (num_sta > ASSOC_CTRL_MAX_STA)
			return UBUS_STATUS_INVALID_ARGUMENT;

		blobmsg_for_each_attr(cur, tb[ASSOC_CONTROL_STA], rem) {
			strncpy(stastr, blobmsg_get_string(cur), sizeof(stastr) - 1);
			if (!hwaddr_aton(stastr, sta_list[i])) {
				dbg("%s(): Invalid sta address. Use 00:10:22:..\n",
				    __func__);
				return UBUS_STATUS_INVALID_ARGUMENT;
			}
			i++;
		}

		/* no valid sta data */
		if (i == 0)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[ASSOC_CONTROL_MODE]) {
		mode = (int)blobmsg_get_u32(tb[ASSOC_CONTROL_MODE]);
		if (mode < ASSOC_CTRL_BLOCK || mode > ASSOC_CTRL_INDEF_BLOCK)
			return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[ASSOC_CONTROL_VALIDITY_TIME] && mode != ASSOC_CTRL_INDEF_BLOCK)
		time = (int)blobmsg_get_u32(tb[ASSOC_CONTROL_VALIDITY_TIME]);

	if (agent_assoc_control_sta(bssid_mac, mode,
			time, num_sta, sta_list)) {
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	return UBUS_STATUS_OK;
}

static int toggle_fh(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct blob_attr *tb[_TOGGLE_FH_MAX];
	char ifname[16] = {0};
	int enable = 0;
	int isl_prev = 0;

	blobmsg_parse(toggle_fh_params, _TOGGLE_FH_MAX,
			tb, blob_data(msg), blob_len(msg));

	if (!tb[TOGGLE_FH_ENABLE])
		return UBUS_STATUS_UNKNOWN_ERROR;

	enable = blobmsg_get_bool(tb[TOGGLE_FH_ENABLE]);

#ifdef AGENT_ISLAND_PREVENTION
	if (tb[TOGGLE_FH_ISL_PREV])
		isl_prev = blobmsg_get_bool(tb[TOGGLE_FH_ISL_PREV]);
#endif

	memset(ifname, '\0', sizeof(ifname));
	if (tb[TOGGLE_FH_IFNAME])
		strncpy(ifname, blobmsg_data(tb[TOGGLE_FH_IFNAME]),
				sizeof(ifname) - 1);
	else
		/* toggle all interfaces */
		strncpy(ifname, "all", sizeof(ifname) - 1);

	if (wifiagent_toggle_fh(a, isl_prev, ifname, enable) != 0)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

static int bcn_metrics_query(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct blob_attr *tb[__BCN_METRICS_QUERY_MAX];
	char agent[18] = {0};
	char sta[18] = {0};
	char bssid[18] = {0};
	uint8_t agent_mac[6] = {0};
	uint8_t sta_mac[6] = {0};
	uint8_t bssid_mac[6] = {0};
	uint8_t opclass = 0;
	uint8_t channel = 0;
	uint8_t reporting_detail = 0;
	char ssid[33] = {0};
	uint8_t num_report = 0;
	struct sta_channel_report *reports = NULL;
	uint8_t num_element = 0;
	uint8_t *element = NULL;
	int ret = UBUS_STATUS_OK;
	struct cmdu_buff *cmdu = NULL;

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

	blobmsg_parse(bcn_metrics_query_params, __BCN_METRICS_QUERY_MAX,
			tb, blob_data(msg), blob_len(msg));

	if (!tb[BCN_METRICS_AGENT] || !tb[BCN_METRICS_STA]) {
		fprintf(stderr, "Beacon metrics query:" \
				" provide agent & STA" \
				" in format 11:22:33...\n");
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	strncpy(agent, blobmsg_data(tb[BCN_METRICS_AGENT]),
			sizeof(agent) - 1);
	strncpy(sta, blobmsg_data(tb[BCN_METRICS_STA]),
			sizeof(sta) - 1);

	if (tb[BCN_METRICS_BSSID])
		strncpy(bssid, blobmsg_data(tb[BCN_METRICS_BSSID]),
				sizeof(bssid) - 1);
	else
		strcpy(bssid, "ff:ff:ff:ff:ff:ff");

	if (!hwaddr_aton(agent, agent_mac)
			|| !hwaddr_aton(sta, sta_mac)
			|| !hwaddr_aton(bssid, bssid_mac))
		return UBUS_STATUS_UNKNOWN_ERROR;

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

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

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

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

	/* Example ubus call:
	 * ubus call map.agent bcn_metrics_query '{"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":255, "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_CHAN_REPORT]) {
		struct blob_attr *cur;
		static const struct blobmsg_policy supp_attrs[2] = {
				[0] = { .name = "opclass",
						.type = BLOBMSG_TYPE_INT32 },
				[1] = { .name = "channels",
						.type = BLOBMSG_TYPE_ARRAY },
		};
		int rem, i = 0;

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

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

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

			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 = UBUS_STATUS_INVALID_ARGUMENT;
				goto out;
			}

			i++;
		}

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

	if (tb[BCN_METRICS_ELEMENT_IDS] && reporting_detail == 1) {
		struct blob_attr *attr_id;
		int rem_id, k = 0;

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

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

		blobmsg_for_each_attr(attr_id,
				tb[BCN_METRICS_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 = UBUS_STATUS_INVALID_ARGUMENT;
			goto out;
		}
	}

	cmdu = agent_gen_beacon_metrics_query(a, agent_mac,
				sta_mac, opclass, channel, bssid_mac,
				reporting_detail, ssid, num_report, reports,
				num_element, element);

	if (!cmdu) {
		ret = UBUS_STATUS_UNKNOWN_ERROR;
		goto out;
	}

	agent_send_cmdu(a, cmdu);
	cmdu_free(cmdu);

out:
	if (element)
		free(element);
	if (reports)
		free(reports);
	return ret;
}

static int unassoc_sta_lm_query(struct ubus_context *ctx,
		struct ubus_object *obj, struct ubus_request_data *req,
		const char *method, struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct cmdu_buff *cmdu;
	char mac_str[18];
	uint8_t agent_mac[6] = { 0 };
	struct blob_attr *tb[__UNASSOC_STA_LM_QUERY_MAX];
	uint8_t opclass = 0;
	int num_metrics = 0;
	struct unassoc_sta_metric *metrics = NULL;
	int ret = UBUS_STATUS_OK;

	blobmsg_parse(unassoc_sta_lm_query_params, __UNASSOC_STA_LM_QUERY_MAX, tb,
			blob_data(msg), blob_len(msg));

	if (!tb[UNASSOC_STA_LM_QUERY_AGENT]) {
		fprintf(stderr, "Unassociated STA link metric query: provide Agent" \
				"address in format aa:bb:cc:dd:ee:ff\n");
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

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

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

	if (!opclass) {
		fprintf(stderr, "%s(): missing opclass\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	/* Example ubus call:
	 * ubus call map.agent unassoc_sta_lm_query '{"agent":
	 * "44:d4:37:42:47:b9", "opclass":81, "metrics":
	 * [{"channel":11, "stamacs": ["44:d4:37:42:3a:c6", "44:d4:37:42:47:be"]}]}'
	 *
	 * ubus call map.agent unassoc_sta_lm_query '{"agent":
	 * "44:d4:37:42:47:b9", "opclass":128,
	 * "metrics":[{"channel":36,"stamacs: ["e0:d4:e8:79:c4:ef"]}]}'
	 */

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

		num_metrics = blobmsg_check_array(tb[UNASSOC_STA_LM_QUERY_METRICS],
				BLOBMSG_TYPE_TABLE);

		if (!num_metrics) {
			fprintf(stderr, "%s(): missing metrics\n", __func__);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		metrics = calloc(num_metrics, sizeof(struct unassoc_sta_metric));
		if (!metrics) {
			ret = UBUS_STATUS_UNKNOWN_ERROR;
			goto out;
		}

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

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

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

			metrics[i].channel = (uint8_t) blobmsg_get_u32(data[0]);
			metrics[i].num_sta = blobmsg_check_array(
					data[1], BLOBMSG_TYPE_STRING);

			if (!metrics[i].channel) {
				fprintf(stderr, "unassoc_sta_lm_query: missing channel \
						for metrics [%d]\n", i);
				ret = UBUS_STATUS_INVALID_ARGUMENT;
				goto out;
			}

			if (!metrics[i].num_sta) {
				fprintf(stderr, "unassoc_sta_lm_query: no stations for \
						channel %d\n", metrics[i].channel);
				ret = UBUS_STATUS_INVALID_ARGUMENT;
				goto out;
			}

			if (metrics[i].num_sta > MAX_UNASSOC_STAMACS) {
				fprintf(stderr, "unassoc_sta_lm_query: max 10 stations \
						allowed per channel!\n");
				ret = UBUS_STATUS_INVALID_ARGUMENT;
				goto out;
			}

			/* Iterate through all metrics of given channel */
			blobmsg_for_each_attr(attr, data[1], remm) {
				if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
					continue;

				/* STA list */
				strncpy(mac, blobmsg_get_string(attr), sizeof(mac) - 1);
				hwaddr_aton(mac, metrics[i].sta[j].macaddr);

				j++;
			}

			if (metrics[i].num_sta != j) {
				dbg("%s(): invalid metric [%d]!\n", __func__, i);
				ret = UBUS_STATUS_INVALID_ARGUMENT;
				goto out;
			}

			i++;
		}

		if (num_metrics != i) {
			dbg("%s(): invalid metrics!\n", __func__);
			ret = UBUS_STATUS_INVALID_ARGUMENT;
			goto out;
		}
	}


	cmdu = agent_gen_unassoc_sta_metric_query(a, agent_mac,
			opclass, num_metrics, metrics);

	if (!cmdu) {
		ret = UBUS_STATUS_UNKNOWN_ERROR;
		goto out;
	}

	agent_send_cmdu(a, cmdu);
	cmdu_free(cmdu);

out:
	if (metrics)
		free(metrics);

	return ret;
}

static int topology_query(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct blob_attr *tb[__TOPOLOGY_QUERY_MAX];
	char agent_macstr[19] = { 0 };
	uint8_t agent_mac[6] = { 0 };
	struct cmdu_buff *cmdu;

	blobmsg_parse(topology_query_params, __TOPOLOGY_QUERY_MAX, tb,
			blob_data(msg), blob_len(msg));

	if (tb[TOPOLOGY_QUERY_AGENT]) {
		strncpy(agent_macstr, blobmsg_data(tb[TOPOLOGY_QUERY_AGENT]),
				sizeof(agent_macstr) - 1);
	} else {
		strncpy(agent_macstr, MULTICAST_ADDR_STR, sizeof(agent_macstr) - 1);
	}
	if (!hwaddr_aton(agent_macstr, agent_mac))
		return UBUS_STATUS_UNKNOWN_ERROR;

	cmdu = agent_gen_topology_query(a, agent_mac);
	if (!cmdu)
		return UBUS_STATUS_UNKNOWN_ERROR;

	agent_send_cmdu(a, cmdu);
	cmdu_free(cmdu);

	return UBUS_STATUS_OK;
}

#define STEER_PREFLIST_MAX_NUM 32
static int steer(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	unsigned char sta_macaddr[6] = {0};
	struct pref_neighbor preflist[STEER_PREFLIST_MAX_NUM] = {0};
	struct blob_attr *tb[_STEER_MAX];
	char ifname[16] = {0};
	int steer_type = STA_STEER_MANDATE; /* default is mandate */
	unsigned int optime = 0;
	char sta[18] = {0};
	int prefcnt = 0;

	blobmsg_parse(steer_params, _STEER_MAX, tb, blob_data(msg),
			blob_len(msg));
	if (!(tb[STEER_IFNAME])) {
		dbg("%s(): ifname not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	memset(ifname, '\0', sizeof(ifname));
	strncpy(ifname, blobmsg_data(tb[STEER_IFNAME]), sizeof(ifname)-1);

	if (!(tb[STEER_STA])) {
		dbg("%s(): STA's macaddress not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	strncpy(sta, blobmsg_data(tb[STEER_STA]), sizeof(sta)-1);
	if (hwaddr_aton(sta, sta_macaddr) == NULL) {
		dbg("%s(): Invalid address. Use 00:10:22:..\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[STEER_TYPE]) {
		char type[12] = {0};

		snprintf(type, 12, "%s", (char *)blobmsg_data(tb[STEER_TYPE]));
		if (!strncasecmp(type, "opportunity", 11))
			steer_type = STA_STEER_OPPORTUNITY;
	}

	if (steer_type == STA_STEER_OPPORTUNITY) {
		if (!tb[STEER_OPTIME]) {
			dbg("%s(): 'optime' not specified for steer " \
					"opportunity\n", __func__);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}
		optime = blobmsg_get_u32(tb[STEER_OPTIME]);
	}

	if (tb[STEER_TO_BSS]) {
		struct blob_attr *attr;
		char bss[18] = {0};
		int rem;

		blobmsg_for_each_attr(attr, tb[STEER_TO_BSS], rem) {
			unsigned char bss_macaddr[6] = {0};

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

			strncpy(bss, blobmsg_data(attr), sizeof(bss)-1);
			if (hwaddr_aton(bss, bss_macaddr) == NULL) {
				dbg("%s(): Invalid Bss address. " \
						"Use 00:10:22:..\n", __func__);
				return UBUS_STATUS_INVALID_ARGUMENT;
			}
			memcpy(&preflist[prefcnt].bssid, bss_macaddr, 6);
			if (++prefcnt > STEER_PREFLIST_MAX_NUM)
				break;
		}
	}

	dbg("%s: bssid pref count = %d\n", __func__, prefcnt);

	if (wifiagent_steer_sta(ctx, ifname, sta_macaddr, prefcnt, preflist, optime))
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

static int higher_layer_data(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct cmdu_buff *cmdu;
	struct blob_attr *tb[_HLD_MAX];
	uint8_t agent_mac[6];
	uint8_t proto;
	int len, tmp;
	uint8_t *data;
	char *datastr;

	dbg("Agent: received '%s'\n", __func__);
	blobmsg_parse(higher_layer_data_params, _HLD_MAX, tb, blob_data(msg),
			blob_len(msg));

	if (!tb[HLD_AGENT]) {
		dbg("%s(): ADDR not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	len = blobmsg_data_len(tb[HLD_AGENT]);
	if (len < 17) {
		dbg("%s(): wrong ADDR length %d!\n", __func__, len);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	if (hwaddr_aton(blobmsg_data(tb[HLD_AGENT]), agent_mac) == NULL) {
		dbg("%s(): wrong ADDR!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (!tb[HLD_PROTOCOL]) {
		dbg("%s(): PROTOCOL not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	tmp = blobmsg_get_u32(tb[HLD_PROTOCOL]);
	if (tmp < 0 || tmp > 255) {
		dbg("%s(): PROTOCOL not withing the 0-255 range !\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	proto = (uint8_t) tmp;

	if (!tb[HLD_DATA]) {
		dbg("%s(): DATA not specified!\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}
	datastr = blobmsg_get_string(tb[HLD_DATA]);
	len = blobmsg_data_len(tb[HLD_DATA]);
	if (len % 2 != 1) {
		/* expect n*2 hex digits + '\0' termination character  */
		dbg("%s(): wrong DATA length %d!\n", __func__, len);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	len = len / 2;
	data = calloc(len, sizeof(uint8_t));
	if (!data) {
		dbg("%s(): alloc failure!\n", __func__);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	if (strtob(datastr, len, data) == NULL) {
		dbg("%s(): wrong DATA %d!\n", __func__, len);
		goto error;
	}

	cmdu = agent_gen_higher_layer_data(a, agent_mac, proto, data, len);
	if (!cmdu)
		goto error;

	free(data);

	agent_send_cmdu(a, cmdu);
	cmdu_free(cmdu);

	dbg("Agent: CMD returned OK\n");
	return UBUS_STATUS_OK;

error:
	free(data);
	return UBUS_STATUS_UNKNOWN_ERROR;
}

#if AGENT_SYNC_DYNAMIC_CNTLR_CONFIG
static int sync_dyn_controller_config(struct ubus_context *ctx, struct ubus_object *obj,
				      struct ubus_request_data *req, const char *method,
				      struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	uint16_t sync_config_reqsize = 0;
	uint8_t *sync_config_req;
	struct cmdu_buff *cmdu;
	uint8_t proto = 0xac;
	void *key;
	int ret;


	ret = build_sync_config_request(a->almac, &sync_config_req,
					&sync_config_reqsize, &key);
	if (ret) {
		err("Failed to build sync-dyn-controller-config request!\n");
		return ret;
	}

	agent_free_cntlr_sync(a);

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

	cmdu = agent_gen_higher_layer_data(a, a->cntlr_almac, proto,
					   sync_config_req, sync_config_reqsize);
	if (!cmdu)
		goto error;

	agent_send_cmdu(a, cmdu);
	cmdu_free(cmdu);

	return UBUS_STATUS_OK;

error:
	return UBUS_STATUS_UNKNOWN_ERROR;
}
#endif

static int agent_timers(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct blob_buf bb;
	struct agent *a = container_of(obj, struct agent, obj);
	struct wifi_radio_element *re = NULL;
	void *t;
	struct timespec now = {0};
	void *b;

	timestamp_update(&now);

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_u32(&bb, "autoconfig_trigger", timer_remaining_ms(&a->autocfg_dispatcher));
	t = blobmsg_open_table(&bb, "dynamic_backhaul");
	blobmsg_add_u32(&bb, "num_attempts", a->dynbh_attempts);
	blobmsg_add_u32(&bb, "upgrade_ongoing", a->dynbh_upgrade_ongoing);
	blobmsg_add_u32(&bb, "next_attempt", timer_remaining_ms(&a->dynbh_scheduler));
	blobmsg_add_u32(&bb, "last_attempt_start", timestamp_diff_ms(now, a->dynbh_last_start));
	blobmsg_add_u32(&bb, "last_attempt_end", timestamp_diff_ms(now, a->dynbh_last_end));
	blobmsg_close_table(&bb, t);
	blobmsg_add_u32(&bb, "bh_lost_timer", timer_remaining_ms(&a->bh_lost_timer));
	blobmsg_add_u32(&bb, "bh_reconf_timer", timer_remaining_ms(&a->bh_reconf_timer));
#ifdef AGENT_ISLAND_PREVENTION
	blobmsg_add_u32(&bb, "sta_disconnect_timer", timer_remaining_ms(&a->sta_disconnect_timer));
	blobmsg_add_u32(&bb, "fh_disable_timer", timer_remaining_ms(&a->fh_disable_timer));
#endif /* AGENT_ISLAND_PREVENTION */
	blobmsg_add_u32(&bb, "disable_unconnected_bstas_scheduler", timer_remaining_ms(&a->disable_unconnected_bstas_scheduler));
	blobmsg_add_u32(&bb, "onboarding_scheduler", timer_remaining_ms(&a->onboarding_scheduler));
	b = blobmsg_open_array(&bb, "scan");

	list_for_each_entry(re, &a->radiolist, list) {
		void *tt;

		tt = blobmsg_open_table(&bb, "");
		blobmsg_add_string(&bb, "name", re->name);
		blobmsg_add_u32(&bb, "timeout", timer_remaining_ms(&re->available_scan_timer));
		blobmsg_add_u32(&bb, "state", re->scan_state);
		blobmsg_close_table(&bb, tt);
	}

	blobmsg_close_array(&bb, b);
	blobmsg_close_table(&bb, t);
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

static int agent_backhaul_blacklist(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct blob_buf bb;
	struct agent *a = container_of(obj, struct agent, obj);
	struct netif_bk *bk;
	struct wifi_radio_element *re = NULL;
	void *t;

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	t = blobmsg_open_array(&bb, "backhauls");

	list_for_each_entry(re, &a->radiolist, list) {
		void *tt, *ttt;
		char mac[18] = {0};
		int i;

		if (!re->has_bsta)
			continue;

		bk = &re->bk;

		tt = blobmsg_open_table(&bb, "");
		blobmsg_add_string(&bb, "ifname", bk->ifname);
		hwaddr_ntoa(bk->macaddr, mac);
		blobmsg_add_string(&bb, "macaddr", mac);
		blobmsg_add_u32(&bb, "num_blacklist_bssids", bk->num_blacklist_bssids);
		ttt = blobmsg_open_array(&bb, "blacklist_bssid");
		for (i = 0; i < bk->num_blacklist_bssids; i++) {
			hwaddr_ntoa(bk->blacklist_bssid[i], mac);
			blobmsg_add_string(&bb, "", mac);
		}
		blobmsg_close_array(&bb, ttt);
		blobmsg_close_table(&bb, tt);
	}

	blobmsg_close_array(&bb, t);

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

static int agent_set_backhaul(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct netif_bk *bk;
	struct blob_attr *tb[__SET_BH_MAX];
	int ret;
	uint8_t b;
	enum wifi_band band;
	uint8_t target_bssid[6] = {0};
	bool force_disassoc = false;

	blobmsg_parse(set_bh_params, __SET_BH_MAX, tb, blob_data(msg),
			blob_len(msg));

	if (!tb[SET_BH_BAND]) {
		dbg("%s(): band not specified!\n", __func__);
		set_value_by_string("mapagent", "agent", "backhaul_override", "",
				    UCI_TYPE_STRING);
		a->cfg.backhaul_override = 0;
		dynbh_bsta_enable_all(a);
		a->dynbh_attempts = 0;
		if (!a->dynbh_upgrade_ongoing)
			timer_set(&a->dynbh_scheduler, 0 * 1000);

		agent_config_reload(a);
		return UBUS_STATUS_OK;
	}

	b = blobmsg_get_u32(tb[SET_BH_BAND]);
	if (b == 2)
		band = BAND_2;
	else if (b == 5)
		band = BAND_5;
	else if (b == 6)
		band = BAND_6;
	else
		return UBUS_STATUS_INVALID_ARGUMENT;

	if (tb[SET_BH_TARGET_BSSID]) {
		hwaddr_aton(blobmsg_get_string(tb[SET_BH_TARGET_BSSID]),
			    target_bssid);
	}

	bk = agent_get_bsta_by_band(a, band);
	if (!bk) {
		dbg("%s(): bSTA for band:%d not specified\n", __func__, b);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	if (tb[SET_BH_FORCE_DISASSOC]) {
		char ul_ifname[16] = {0};
		struct netif_bk *ul_bk;

		agent_get_backhaul_ifname(a, ul_ifname);

		ul_bk = agent_get_bsta_by_ifname(a, ul_ifname);
		if (ul_bk)
			config_disable_bsta(ul_bk->cfg);
		force_disassoc = !!blobmsg_get_u32(tb[SET_BH_BAND]);
	}

	ret = agent_bsta_steer(a, bk->ifname, target_bssid, force_disassoc);
	if (ret) {
		dbg("%s(): steer returned with error code\n", __func__);
		return UBUS_STATUS_UNKNOWN_ERROR;
	} else {
		config_enable_bsta(bk->cfg); /* enable bsta as candidate */
	}

	bk->bsta_steer.trigger = BK_STEER_LOCAL;
	bk->bsta_steer.expired = false;
	bk->bsta_steer.mid = 1;
	wifi_get_iface_bssid(bk->ifname, bk->bsta_steer.prev_bssid);
	memcpy(bk->bsta_steer.target_bssid, target_bssid, 6);

	agent_get_backhaul_ifname(a, bk->bsta_steer.ul_ifname);
	timer_set(&bk->steer_timeout, 20 * 1000);

	return UBUS_STATUS_OK;
}

static int agent_trigger_dynamic_upgrade(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);

	if (timer_remaining_ms(&a->dynbh_scheduler) > 0)
		timer_set(&a->dynbh_scheduler, 0);

	return UBUS_STATUS_OK;
}

static int agent_status(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	return wifiagent_get_status(ctx, req);
}

static int agent_nodes(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	return wifiagent_get_nodes(ctx, req);
}


static int agent_info(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	return wifiagent_get_info(ctx, req);
}

static int agent_bk_info(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	return wifiagent_get_bk_info(ctx, req);
}

static int agent_wpspbc(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	return wifiagent_initiate_wpspbc(ctx, obj, req);
}

static int agent_reset(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	return wifiagent_ubus_reset_stack(ctx, obj, req);
}

#if 0
static int agent_config_ap(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	return 0;

	struct agent *a = container_of(obj, struct agent, obj);
	struct blob_attr *tb[__CFG_POLICY_MAX];
	char agent[18] = {0}, bssidstr[18] = {0};
	struct wifi_radio_element *radio, *found = NULL;
	uint8_t hwaddr[6] = {0}, bssid[6] = {0};
	int i;

	blobmsg_parse(config_policy_params, __CFG_POLICY_MAX, tb,
			blob_data(msg), blob_len(msg));

	if (!tb[CFG_POLICY_AGENT] || !tb[CFG_POLICY_BSSID]) {
		fprintf(stderr, "STA Capability Query: provide BSSID " \
				"address in format 11:22:33...\n");
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	strncpy(agent, blobmsg_data(tb[CFG_POLICY_AGENT]), sizeof(agent) - 1);
	if (!hwaddr_aton(agent, hwaddr))
		return UBUS_STATUS_UNKNOWN_ERROR;

	strncpy(bssidstr, blobmsg_data(tb[CFG_POLICY_BSSID]),
			sizeof(bssidstr) - 1);
	if (!hwaddr_aton(bssidstr, bssid))
		return UBUS_STATUS_UNKNOWN_ERROR;

	for (i = 0; i < a->num_radios; i++) {
		radio = a->radios + i;

		if (memcmp(radio->macaddr, bssid, 6))
			continue;
		found = radio;
	}
	if (!found)
		return UBUS_STATUS_UNKNOWN_ERROR;

	build_ap_autoconfig_wsc(a, hwaddr, radio, i);

	return 0;
}
#endif

static int agent_ap_search(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	return 0;
//	struct agent *a = container_of(obj, struct agent, obj);
//	struct blob_attr *tb[__SEARCH_POLICY_MAX];
//	int i, band = 0;
//
//	blobmsg_parse(search_policy_params, __SEARCH_POLICY_MAX, tb,
//			blob_data(msg), blob_len(msg));
//
//	if (tb[SEARCH_POLICY_BAND]) {
//		band = blobmsg_get_u32(tb[SEARCH_POLICY_BAND]);
//
//		if (band == 2)
//			band = BAND_2;
//		else if (band == 5)
//			band = BAND_5;
//		else {
//			trace("|%s:%d| Please provide band as '2', '5' or N/A\n",
//					__func__, __LINE__);
//			return UBUS_STATUS_UNKNOWN_ERROR;
//		}
//	}
//
//	for (i = 0; i < a->num_radios; i++) {
//		struct cmdu_cstruct *cmdu;
//		struct wifi_radio_element *radio = &a->radios[i];
//
//		if (tb[SEARCH_POLICY_BAND])
//			if (band != radio->band)
//				continue;
//
//		cmdu = agent_gen_ap_autoconfig_search(a, radio, NULL, 0x02);
//		if (!cmdu)
//			continue;
//
//		trace("|%s:%d| Sending Autoconfig Search for radio %s(%s)\n",
//				__func__, __LINE__, radio->name,
//				(radio->band == BAND_2 ? "2.4GHz" : "5GHz"));
//		agent_send_cmdu(a, cmdu);
//		map_free_cmdu(cmdu);
//	}
//
//	return 0;
}

enum {
	ASSOC_NOTIFY_BSSARR,
	_ASSOC_NOTIFY_MAX
};

static const struct blobmsg_policy assoc_notify_params[_ASSOC_NOTIFY_MAX] = {
	[ASSOC_NOTIFY_BSSARR] = { .name = "bss_status_list", .type = BLOBMSG_TYPE_ARRAY }
};

/* i.e.
 * ubus call map.agent assoc_notify '{"bss_status_list":[{"bssid":"11:11:11:11:11:11","status":0},{"bssid":"12:12:12:12:12:12","status":1}]}'
 */
static int assoc_notify(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	int rem, num_data = 0;
	struct blob_attr *cur;
	struct bss_data {
		uint8_t bssid[6];
		uint8_t status;
	} *bss_data = NULL;

	void *tmp = NULL;
	struct blob_attr *tb[_ASSOC_NOTIFY_MAX];
	struct agent *a = container_of(obj, struct agent, obj);

	blobmsg_parse(assoc_notify_params, _ASSOC_NOTIFY_MAX, tb,
			blob_data(msg), blob_len(msg));

	if (!tb[ASSOC_NOTIFY_BSSARR])
		return UBUS_STATUS_UNKNOWN_ERROR;

	blobmsg_for_each_attr(cur, tb[ASSOC_NOTIFY_BSSARR], rem) {
		int status, idx;
		char bss[18] = {0};
		struct blob_attr *tb1[2];
		struct blobmsg_policy assoc_notify_data[] = {
			[0] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "status", .type = BLOBMSG_TYPE_INT32 }
		};

		blobmsg_parse(assoc_notify_data, 2,
				tb1, blobmsg_data(cur), blobmsg_data_len(cur));

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

		/* allowed status value should be
		 * '0x00' or '0x01'
		 */
		status = blobmsg_get_u32(tb1[1]);
		if (!((status == STA_ASSOC_NOT_ALLOWED) || (status == STA_ASSOC_ALLOWED)))
			continue;

		tmp = realloc(bss_data, (num_data + 1) * sizeof(*bss_data));
		if (!tmp) {
			dbg("%s: -ENOMEM\n", __func__);
			goto error;
		}

		bss_data = tmp;
		idx = num_data;
		memset(bss_data + idx, 0, sizeof(struct bss_data));
		strncpy(bss, blobmsg_data(tb1[0]),
				sizeof(bss) - 1);
		if (!hwaddr_aton(bss, bss_data[idx].bssid))
			goto error;

		bss_data[idx].status = (uint8_t) status;
		num_data++;
	}

	if (!num_data)
		return UBUS_STATUS_UNKNOWN_ERROR;

	send_assoc_status_notification(a, num_data, (void *)bss_data);

	free(bss_data);
	return UBUS_STATUS_OK;

error:
	free(bss_data);
	return UBUS_STATUS_UNKNOWN_ERROR;
}

#if (EASYMESH_VERSION > 2)
static int bss_config_request(struct ubus_context *ctx, struct ubus_object *obj,
			      struct ubus_request_data *req, const char *method,
			      struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);

	if (send_bss_configuration_request(a))
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

#if 0
static int print_ec_key(struct ubus_context *ctx, struct ubus_object *obj,
			      struct ubus_request_data *req, const char *method,
			      struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct dpp_context_wifi *dpp = &a->dpp_ctx;
	char *buf;
	struct blob_buf bb;

	buf = calloc(1, (dpp->ec_len * 2) + 1);
	if (!buf) {
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	btostr(dpp->ec_key, dpp->ec_len, buf);

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "ec_priv_key", buf);
	ubus_send_reply(ctx, req, bb.head);

	blob_buf_free(&bb);
	free(buf);
	return UBUS_STATUS_OK;
}
#endif
#ifdef USE_LIBDPP
static int dbg_dpp_enrollee_uri(struct ubus_context *ctx, struct ubus_object *obj,
				 struct ubus_request_data *req, const char *method,
				 struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct blob_buf bb = {};
	char *uri;

	uri = dpp_get_bootstrap_uri(a->own_peer->own_bi);

	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "uri", (uri ? uri : ""));
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}
#endif
#endif /*EASYMESH_VERSION > 2*/

static int log_cmd(struct ubus_context *ctx, struct ubus_object *obj,
		struct ubus_request_data *req, const char *method,
		struct blob_attr *msg)
{
	struct agent *a = container_of(obj, struct agent, obj);
	struct blob_attr *tb[__LOG_ATTR_MAX];
	struct blob_buf bb = {};
	bool restart = false;
	int ret = UBUS_STATUS_OK;

	blobmsg_parse(log_cmd_params, __LOG_ATTR_MAX, tb,
			blob_data(msg), blob_len(msg));

	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);

	/* show current log settings */
	if (!tb[LOG_ATTR_FEATURE] && !tb[LOG_ATTR_LEVEL]) {
		void *tt;
		void *arr;
		int i;

		tt = blobmsg_open_table(&bb, "");
		blobmsg_add_u32(&bb, "level", a->lopts.level);
		arr = blobmsg_open_array(&bb, "feature");

		for (i = 0; i < LOG_DEFAULT; i++) {
			if (!(a->lopts.features & BIT(i)))
				continue;
			blobmsg_add_string(&bb, "", logfeature_to_string(i));
		}

		blobmsg_close_array(&bb, arr);
		blobmsg_close_table(&bb, tt);

		goto send;
	}

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

		if (loglevel > LOGLEVEL_MAX)
			return UBUS_STATUS_INVALID_ARGUMENT;

		if (loglevel != a->lopts.level) {
			a->lopts.level = loglevel;
			restart = true;
		}
	}

	if (tb[LOG_ATTR_FEATURE]) {
		const char *fstr = blobmsg_data(tb[LOG_ATTR_FEATURE]);
		uint32_t features = a->lopts.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 = UBUS_STATUS_UNKNOWN_ERROR;
				break;
			}
		}

		free(str);

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

	if (restart) {
		restart_logging(&a->lopts);
		info("%s: log.level changed to %d\n", __func__, a->lopts.level);
	}

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

send:
	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return ret;
}

static void add_scanresults_entry(struct blob_buf *bb, struct wifi_bss *bss,
				   uint32_t opclass, uint32_t age, uint32_t expired,
				   bool include_age_expired)
{
	char bssidstr[18] = {};
	int bandwidth;
	void *b2;

	b2 = blobmsg_open_table(bb, "");

	hwaddr_ntoa(bss->bssid, bssidstr);
	blobmsg_add_string(bb, "bssid", bssidstr);

	blobmsg_add_u32(bb, "opclass", opclass);
	blobmsg_add_u32(bb, "channel", bss->channel);
	blobmsg_add_string(bb, "ssid", (char *) bss->ssid);
	bandwidth = wifi_bw_to_bw(bss->curr_bw);
	blobmsg_add_u32(bb, "bandwidth", bandwidth);
	blobmsg_add_u32(bb, "rssi", bss->rssi);
	blobmsg_add_u32(bb, "load_stas", bss->load.sta_count);
	blobmsg_add_u32(bb, "load_utilization", bss->load.utilization);

	if (include_age_expired) {
		blobmsg_add_u32(bb, "age", age);
		blobmsg_add_u32(bb, "expired", expired);
	}

	blobmsg_close_table(bb, b2);
}

static int agent_scanresults(struct ubus_context *ctx, struct ubus_object *obj,
			      struct ubus_request_data *req, const char *method,
			      struct blob_attr *msg)
{
	struct agent *agent = container_of(obj, struct agent, obj);
	struct wifi_radio_element *re = NULL, *c_re = NULL;
	struct blob_attr *tb[_SCANRESULTS_MAX];
	char mac_str[18] = {};
	uint8_t radio_mac[6] = {0};
	struct blob_buf bb = {};
	bool use_cache = false;
	void *a, *b;
	int i;

	blobmsg_parse(scanresults_params, _SCANRESULTS_MAX,
		      tb, blob_data(msg), blob_len(msg));

	if (tb[SCANRESULTS_CACHE])
		use_cache = blobmsg_get_bool(tb[SCANRESULTS_CACHE]);

	if (tb[SCANRESULTS_RADIO_NAME]) {
		char *radio;

		radio = blobmsg_get_string(tb[SCANRESULTS_RADIO_NAME]);
		re = agent_get_radio_by_name(agent, radio);
		if (!re) {
			dbg("failed to get radio by name\n");
			return UBUS_STATUS_UNKNOWN_ERROR;
		}
	} else if (tb[SCANRESULTS_RADIO_MAC]) {
		strncpy(mac_str,
				blobmsg_data(tb[SCANRESULTS_RADIO_MAC]),
							 sizeof(mac_str) - 1);

		if (!hwaddr_aton(mac_str, radio_mac)) {
			dbg("wrongly formated radio MAC\n");
			return UBUS_STATUS_UNKNOWN_ERROR;
		}

		re = agent_get_radio(agent, radio_mac);
		if (!re) {
			dbg("failed to get radio by MAC\n");
			return UBUS_STATUS_UNKNOWN_ERROR;
		}
	} else if (tb[SCANRESULTS_BAND]) {
		enum wifi_band band;
		int band_val;

		band_val = blobmsg_get_u32(tb[SCANRESULTS_BAND]);
		switch (band_val) {
		case 2:
			band = BAND_2;
			break;
		case 5:
			band = BAND_5;
			break;
		case 6:
			band = BAND_6;
			break;
		default:
			dbg("invalid band value %d (valid: 2, 5, 6)\n", band_val);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		re = agent_get_radio_by_band(agent, band);
		if (!re) {
			dbg("failed to get radio by band %d\n", band_val);
			return UBUS_STATUS_UNKNOWN_ERROR;
		}
	}

	blob_buf_init(&bb, 0);
	b = blobmsg_open_array(&bb, "radios");

	list_for_each_entry(c_re, &agent->radiolist, list) {
		char macaddrstr[18] = {0};
		void *b1;

		if (re && re != c_re)
			/* other radio requested */
			continue;

		b1 = blobmsg_open_table(&bb, "");

		hwaddr_ntoa(c_re->macaddr, macaddrstr);
		blobmsg_add_string(&bb, "macaddr", macaddrstr);

		a = blobmsg_open_array(&bb, "entries");

		if (use_cache) {
			/* Get results cached in WiFi */
			struct wifi_bss bss[128];
			int bss_num = ARRAY_SIZE(bss);
			int ret;

			ret = wifi_get_scan_results(c_re->name, bss, &bss_num, true);
			if (ret)
				dbg("Failed to fetch scan results cache for radio %s\n", c_re->name);
			else {
				for (i = 0; i < bss_num; i++) {
					uint8_t opclass;
					int bandwidth;

					/* Calculate opclass from channel and bandwidth */
					bandwidth = wifi_bw_to_bw(bss[i].curr_bw);
					opclass = wifi_opclass_get_id(&c_re->opclass, bss[i].channel, bandwidth);

					/* WiFi manager: no age/expired info */
					add_scanresults_entry(&bb, &bss[i], opclass, 0, 0, false);
				}
			}
		} else {
			/* Use agent's internal cache (default behavior) */
			for (i = 0; i < c_re->scanresults.entry_num; i++) {
				struct wifi_scanresults_entry *entry;

				entry = &c_re->scanresults.entry[i];
				add_scanresults_entry(&bb, &entry->bss, entry->opclass,
						       timestamp_elapsed_sec(&entry->tsp),
						       entry->expired, true);
			}
		}
		blobmsg_close_array(&bb, a); /* accesspoints */
		blobmsg_close_table(&bb, b1);
	}

	blobmsg_close_array(&bb, b); /* radios */

	ubus_send_reply(ctx, req, bb.head);
	blob_buf_free(&bb);

	return UBUS_STATUS_OK;
}

static int execute_radio_scan(struct agent *agent,
		      struct wifi_radio_element *re,
		      struct wifi_scan_request_radio *radio_req)
{
	int ret;

	/* Set radio MAC and status */
	memcpy(radio_req->radio, re->macaddr, 6);
	radio_req->status = 0;

	/* Execute the scan request */
	ret = issue_channel_scan(agent, re->name, &re->opclass, radio_req);
	if (ret)
		dbg("%s: issue_channel_scan failed with error %d for radio %s\n",
		    __func__, ret, re->name);

	return ret;
}

static int agent_scan(struct ubus_context *ctx, struct ubus_object *obj,
		       struct ubus_request_data *req, const char *method,
		       struct blob_attr *msg)
{
	struct agent *agent = container_of(obj, struct agent, obj);
	struct wifi_radio_element *re = NULL;
	struct blob_attr *tb[_SCAN_MAX];
	struct wifi_scan_request_radio radio_req = {0};
	struct wifi_scan_request_opclass *opclass_req;
	int ret;

	blobmsg_parse(scan_params, _SCAN_MAX, tb, blob_data(msg), blob_len(msg));

	/* Case 1: No parameters - scan all supported channels on all radios */
	if (!tb[SCAN_RADIO] && !tb[SCAN_BAND] && !tb[SCAN_OPCLASS] && !tb[SCAN_CHANNEL]) {
		struct wifi_radio_element *c_re;
		int scan_count = 0;

		list_for_each_entry(c_re, &agent->radiolist, list) {
			if (scan_radio_all_channels(c_re->name) == 0)
				scan_count++;
		}

		if (scan_count == 0) {
			dbg("%s: failed to start scan on any radio\n", __func__);
			return UBUS_STATUS_UNKNOWN_ERROR;
		}

		return UBUS_STATUS_OK;
	}

	/* Get radio - either by name or by band */
	if (tb[SCAN_RADIO]) {
		char *radio_name = blobmsg_get_string(tb[SCAN_RADIO]);

		re = agent_get_radio_by_name(agent, radio_name);
		if (!re) {
			dbg("%s: failed to get radio by name: %s\n", __func__, radio_name);
			return UBUS_STATUS_UNKNOWN_ERROR;
		}
	} else if (tb[SCAN_BAND]) {
		enum wifi_band band;
		int band_val = blobmsg_get_u32(tb[SCAN_BAND]);

		switch (band_val) {
		case 2:
			band = BAND_2;
			break;
		case 5:
			band = BAND_5;
			break;
		case 6:
			band = BAND_6;
			break;
		default:
			dbg("%s: invalid band value %d (valid: 2, 5, 6)\n",
			    __func__, band_val);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		re = agent_get_radio_by_band(agent, band);
		if (!re) {
			dbg("%s: failed to get radio by band %d\n",
			    __func__, band_val);
			return UBUS_STATUS_UNKNOWN_ERROR;
		}
	} else {
		dbg("%s: either radio or band parameter must be provided\n", __func__);
		return UBUS_STATUS_INVALID_ARGUMENT;
	}

	/* Case 2: Radio or band only - scan all supported channels on specified radio */
	if ((tb[SCAN_RADIO] || tb[SCAN_BAND]) && !tb[SCAN_OPCLASS] && !tb[SCAN_CHANNEL]) {
		ret = scan_radio_all_channels(re->name);
		if (ret)
			return UBUS_STATUS_UNKNOWN_ERROR;
		return UBUS_STATUS_OK;

	} else if (tb[SCAN_OPCLASS] && tb[SCAN_CHANNEL]) {
		/* Case 3: Both opclass and channel arrays - scan opclass/channel pairs */
		struct blob_attr *cur;
		int rem, opclass_idx = 0, channel_idx = 0;
		int num_opclass, num_channels;

		num_opclass = blobmsg_check_array(tb[SCAN_OPCLASS], BLOBMSG_TYPE_INT32);
		num_channels = blobmsg_check_array(tb[SCAN_CHANNEL], BLOBMSG_TYPE_INT32);

		if (num_opclass != num_channels) {
			dbg("%s, opclass and channel arrays must have same length\n", __func__);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		if (num_opclass > SCAN_MAX_OPCLASS) {
			dbg("%s: too many opclass entries (max %d)\n", __func__, SCAN_MAX_OPCLASS);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		radio_req.num_opclass = num_opclass;

		/* Parse opclass array */
		blobmsg_for_each_attr(cur, tb[SCAN_OPCLASS], rem) {
			if (blobmsg_type(cur) != BLOBMSG_TYPE_INT32 || opclass_idx >= SCAN_MAX_OPCLASS)
				continue;

			opclass_req = &radio_req.opclass[opclass_idx];
			opclass_req->classid = (uint8_t)blobmsg_get_u32(cur);
			opclass_req->num_channel = 1; /* One channel per opclass */
			opclass_idx++;
		}

		/* Parse channel array */
		blobmsg_for_each_attr(cur, tb[SCAN_CHANNEL], rem) {
			if (blobmsg_type(cur) != BLOBMSG_TYPE_INT32 || channel_idx >= SCAN_MAX_OPCLASS)
				continue;

			radio_req.opclass[channel_idx].channel[0] = (uint8_t)blobmsg_get_u32(cur);
			channel_idx++;
		}

	} else if (tb[SCAN_CHANNEL]) {
		/* Case 4: Channels only - auto-calculate opclass for given channels */
		struct blob_attr *cur;
		int rem;
		int num_channels;

		num_channels = blobmsg_check_array(tb[SCAN_CHANNEL], BLOBMSG_TYPE_INT32);
		if (num_channels > SCAN_MAX_OPCLASS) {
			dbg("%s: too many channel entries (max %d)\n", __func__, SCAN_MAX_OPCLASS);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		radio_req.num_opclass = 0;

		blobmsg_for_each_attr(cur, tb[SCAN_CHANNEL], rem) {
			uint8_t channel, opclass_id;
			int bandwidth = 20; /* Default to 20MHz for scanning */

			if (blobmsg_type(cur) != BLOBMSG_TYPE_INT32)
				continue;

			channel = (uint8_t)blobmsg_get_u32(cur);

			/* Calculate opclass from channel and bandwidth */
			opclass_id = wifi_opclass_get_id(&re->opclass, channel, bandwidth);
			if (opclass_id == 0) {
				dbg("%s: failed to determine opclass for channel %d\n", __func__, channel);
				continue;
			}

			/* Check if we already have this opclass */
			int existing_idx = -1;

			for (int i = 0; i < radio_req.num_opclass; i++) {
				if (radio_req.opclass[i].classid == opclass_id) {
					existing_idx = i;
					break;
				}
			}

			if (existing_idx >= 0) {
				/* Add channel to existing opclass */
				opclass_req = &radio_req.opclass[existing_idx];
				if (opclass_req->num_channel < SCAN_MAX_CHANNEL)
					opclass_req->channel[opclass_req->num_channel++] = channel;
			} else {
				/* Create new opclass entry */
				if (radio_req.num_opclass >= SCAN_MAX_OPCLASS)
					break;

				opclass_req = &radio_req.opclass[radio_req.num_opclass++];
				opclass_req->classid = opclass_id;
				opclass_req->num_channel = 1;
				opclass_req->channel[0] = channel;
			}
		}

	} else if (tb[SCAN_OPCLASS]) {
		/* Case 5: Opclass only - pass opclasses directly to wifi */
		struct blob_attr *cur;
		int rem, opclass_idx = 0;
		uint8_t opclasses[SCAN_MAX_OPCLASS];

		int num_opclasses = blobmsg_check_array(tb[SCAN_OPCLASS], BLOBMSG_TYPE_INT32);

		if (num_opclasses > SCAN_MAX_OPCLASS) {
			dbg("%s: too many opclass entries (max %d)\n", __func__, SCAN_MAX_OPCLASS);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		blobmsg_for_each_attr(cur, tb[SCAN_OPCLASS], rem) {
			if (blobmsg_type(cur) != BLOBMSG_TYPE_INT32 || opclass_idx >= SCAN_MAX_OPCLASS)
				continue;

			opclasses[opclass_idx] = (uint8_t)blobmsg_get_u32(cur);
			opclass_idx++;
		}

		if (opclass_idx == 0) {
			dbg("%s, no valid opclasses provided\n", __func__);
			return UBUS_STATUS_INVALID_ARGUMENT;
		}

		/* Use opclass-only scan - let wifi handle channel selection */
		ret = scan_radio_opclass(re->name, opclass_idx, opclasses);
		if (ret)
			return UBUS_STATUS_UNKNOWN_ERROR;
		return UBUS_STATUS_OK;

	} else
		return UBUS_STATUS_UNKNOWN_ERROR;

	if (radio_req.num_opclass == 0) {
		dbg("%s: no valid opclass/channel combinations for scan\n", __func__);
		return UBUS_STATUS_UNKNOWN_ERROR;
	}

	/* Execute the scan request */
	ret = execute_radio_scan(agent, re, &radio_req);
	if (ret)
		return UBUS_STATUS_UNKNOWN_ERROR;

	return UBUS_STATUS_OK;
}

int agent_publish_object(struct agent *a, const char *objname)
{
	struct ubus_method m[] = {
		UBUS_METHOD("apconfig", agent_ap_search, search_policy_params),
		//UBUS_METHOD("config_ap", agent_config_ap, config_policy_params),
		UBUS_METHOD("steer_policy", steer_policy, steer_policy_params),
		UBUS_METHOD("steer", steer, steer_params),
		UBUS_METHOD("assoc_control", assoc_control,
				assoc_control_params),
		UBUS_METHOD("toggle_fh", toggle_fh, toggle_fh_params),
		UBUS_METHOD("bcn_metrics_query", bcn_metrics_query,
				bcn_metrics_query_params),
		UBUS_METHOD("unassoc_sta_lm_query", unassoc_sta_lm_query,
				unassoc_sta_lm_query_params),
		UBUS_METHOD("topology_query", topology_query,
				topology_query_params),
		UBUS_METHOD("higher_layer_data", higher_layer_data, higher_layer_data_params),
		UBUS_METHOD_NOARG("backhaul_info", agent_bk_info),
		UBUS_METHOD_NOARG("status", agent_status),
		UBUS_METHOD_NOARG("nodes", agent_nodes),
		UBUS_METHOD_NOARG("info", agent_info),
		UBUS_METHOD("scan", agent_scan, scan_params),
		UBUS_METHOD("scanresults", agent_scanresults, scanresults_params),
		UBUS_METHOD("assoc_notify", assoc_notify,
				assoc_notify_params),
		UBUS_METHOD_NOARG("WPSPBC", agent_wpspbc),
		UBUS_METHOD_NOARG("reset", agent_reset),
#ifdef AGENT_SYNC_DYNAMIC_CNTLR_CONFIG
		UBUS_METHOD_NOARG("sync", sync_dyn_controller_config),
#endif
		UBUS_METHOD_NOARG("timers", agent_timers),
		UBUS_METHOD_NOARG("dynamic_backhaul_upgrade", agent_trigger_dynamic_upgrade),
		UBUS_METHOD_NOARG("backhaul_blacklist", agent_backhaul_blacklist),
		UBUS_METHOD("set_backhaul", agent_set_backhaul, set_bh_params),
#if (EASYMESH_VERSION > 2)
		UBUS_METHOD_NOARG("bss_config_request", bss_config_request),
#if 0
		UBUS_METHOD_NOARG("ec_key", print_ec_key),
#endif
#ifdef USE_LIBDPP
		UBUS_METHOD_NOARG("dpp_enrollee_uri", dbg_dpp_enrollee_uri),
#endif
#endif
#ifdef DYNBH
		UBUS_METHOD("backhaul", dynbhd_status, backhaul_status_params),
#endif
		UBUS_METHOD("log", log_cmd, log_cmd_params),
	};

	int num_methods = ARRAY_SIZE(m);
	struct ubus_object_type *obj_type;
	struct ubus_method *obj_methods;
	struct ubus_object *obj;
	int ret;


	obj = &a->obj;
	memset(obj, 0, sizeof(*obj));

	obj_type = calloc(1, sizeof(struct ubus_object_type));
	if (!obj_type)
		return -1;

	obj_methods = calloc(num_methods, sizeof(struct ubus_method));
	if (!obj_methods) {
		free(obj_type);
		return -1;
	}

	obj->name = objname;
	memcpy(obj_methods, m, num_methods * sizeof(struct ubus_method));
	obj->methods = obj_methods;
	obj->n_methods = num_methods;

	obj_type->name = obj->name;
	obj_type->n_methods = obj->n_methods;
	obj_type->methods = obj->methods;
	obj->type = obj_type;

	ret = ubus_add_object(a->ubus_ctx, obj);
	if (ret) {
		err("Failed to add '%s' err = %s\n",
				objname, ubus_strerror(ret));
		free(obj_methods);
		free(obj_type);
		return ret;
	}

	info("Published '%s' object\n", objname);

	return ret;
}

void agent_remove_object(struct agent *a, struct ubus_object *obj)
{

	if (a->ubus_ctx && obj->id != OBJECT_INVALID) {
		uint32_t obj_id = OBJECT_INVALID;

		obj_id = ubus_get_object(a->ubus_ctx, obj->name);
		if (WARN_ON(obj_id == OBJECT_INVALID || obj_id != obj->id))
			err("Unexpected object ID!\n");
		else {
			ubus_remove_object(a->ubus_ctx, obj);
			free(obj->type);
			free((void *) obj->methods);
		}
	}
}

void agent_notify_event(struct agent *a, const void *ev_type, void *ev_data)
{
	struct blob_buf b;

	memset(&b, 0, sizeof(struct blob_buf));
	blob_buf_init(&b, 0);
	if (ev_data)
		blobmsg_add_json_from_string(&b, (char *)ev_data);

	ubus_send_event(a->ubus_ctx, (const char *)ev_type, b.head);
	blob_buf_free(&b);
}

void agent_notify_iface_event(struct agent *a, void *ev_ifname,
		const char *ev_type, void *ev_data)
{
	struct blob_buf b;

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

	if (ev_ifname)
		blobmsg_add_string(&b, "ifname", ev_ifname);

	if (ev_type)
		blobmsg_add_string(&b, "event", ev_type);

	if (ev_data) {
		void *t;

		t = blobmsg_open_table(&b, "data");
		blobmsg_add_json_from_string(&b, (char *)ev_data);
		blobmsg_close_table(&b, t);
	}

	ubus_send_event(a->ubus_ctx, "map.agent", b.head);
	blob_buf_free(&b);
}

uint32_t ubus_get_object(struct ubus_context *ctx, const char *name)
{
	uint32_t id;
	int status;

	status = ubus_lookup_id(ctx, name, &id);
	if (status != UBUS_STATUS_OK) {
		warn("object '%s' not present!\n", name);
		return WIFI_OBJECT_INVALID;
	}

	return id;
}

int ubus_call_object(struct ubus_context *ctx, wifi_object_t wobj,
		const char *method,
		void (*response_cb)(struct ubus_request *, int, struct blob_attr *),
		void *priv)
{
	struct blob_buf bb = {};
	int ret;

	blob_buf_init(&bb, 0);
	ret = ubus_invoke(ctx, wobj, method, bb.head,
				response_cb, priv, 2 * 1000);
	if (ret) {
		warn("Failed to get '%s' (ret = %d)\n", method, ret);
		blob_buf_free(&bb);
		return -1;
	}

	blob_buf_free(&bb);
	return 0;
}

int ubus_call_object_args(struct ubus_context *ctx, wifi_object_t wobj,
			  struct blob_buf *bb, const char *method,
			  void (*response_cb)(struct ubus_request *, int, struct blob_attr *),
			  void *priv)
{
	int ret;

	ret = ubus_invoke(ctx, wobj, method, bb->head,
				response_cb, priv, 2 * 1000);
	if (ret) {
		warn("Failed to get '%s' (ret = %d)\n", method, ret);
		return -1;
	}

	return 0;
}

static void agent_async_timeout_cb(atimer_t *t)
{
	struct agent_async_request *async_req = container_of(t, struct agent_async_request, timeout);

	info("%s: async cb timed out: %s\n", __func__, async_req->name);
	ubus_abort_request(async_req->ubus_req.ctx, &async_req->ubus_req);
	list_del(&async_req->list);
	free(async_req);
}

static void agent_async_complete(struct ubus_request *req, int type)
{
	struct agent_async_request *async_req = container_of(req, struct agent_async_request, ubus_req);

	trace("%s: freeing: %s\n", __func__, async_req->name);
	timer_del(&async_req->timeout);
	list_del(&async_req->list);
	free(async_req);
}

int ubus_call_object_async(struct agent *a,
			   struct ubus_context *ctx, const char *objname,
			   uint32_t obj_id, const char *method,
			   void (*response_cb)(struct ubus_request *, int, struct blob_attr *),
			   void (*success_cb)(struct agent_async_request *),
			   void (*timeout_cb)(atimer_t *),
			   void *priv)
{
	struct agent_async_request *async_req;
	struct blob_buf bb = {};
	uint32_t id = obj_id;
	int ret;

	if (id == WIFI_OBJECT_INVALID && objname) {
		id = ubus_get_object(ctx, objname);
		if (id == WIFI_OBJECT_INVALID) {
			warn("%s: object %s not present!\n", __func__, objname);
			return -1;
		}
	}

	async_req = calloc(1, sizeof(*async_req));
	if (!async_req) {
		err("%s: failed to allocate async request (OOM)\n", __func__);
		return -1;
	}

	blob_buf_init(&bb, 0);
	ret = ubus_invoke_async(ctx, id, method, bb.head, &async_req->ubus_req);
	if (ret) {
		err("Failed to get '%s' (ret = %d)\n", method, ret);
		free(async_req);
		blob_buf_free(&bb);
		return -1;
	}

	snprintf(async_req->name, sizeof(async_req->name), "%s->%s\n", objname, method);

	async_req->ubus_req.priv = priv;
	async_req->ubus_req.data_cb = response_cb;

	if (timeout_cb)
		async_req->timeout_cb = timeout_cb;
	else
		async_req->timeout_cb = agent_async_timeout_cb;

	if (success_cb)
		async_req->success_cb = success_cb;

	async_req->ubus_req.complete_cb = agent_async_complete;
	timer_init(&async_req->timeout, async_req->timeout_cb);
	timer_set(&async_req->timeout, 2 * 1000);
	ubus_complete_request_async(ctx, &async_req->ubus_req);
	list_add(&async_req->list, &a->pending_asynclist);
	blob_buf_free(&bb);

	dbg("%s: sent request %s\n", __func__, async_req->name);
	return 0;
}
