/*
 * decollector_ubus.c
 * WiFi Data Elements ubus methods
 *
 * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 * See LICENSE file for license related information.
 *
 */

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

#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubus.h>
#include <libubox/utils.h>

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

#include "wifi_dataelements.h"
#include "decollector_i.h"
#include "decollector.h"
#include "debug.h"

#define UBUS_TIMEOUT		1000

extern int decollector_handle_map_event(struct decollector_private *p,
				struct cmdu_buff *cmdu);

int decollector_dump(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct decollector_private *p = container_of(obj, struct decollector_private, obj);
	struct blob_buf bb = {0};
	int ret;

	blob_buf_init(&bb, 0);

	ret = decollector_dump_all(p, &bb);
	if (ret) {
		dbg("DM is not ready; do a refresh!\n");
		goto out;
	}

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

	return 0;
}

int decollector_dump2(struct ubus_context *ctx, struct ubus_object *obj,
	       struct ubus_request_data *req, const char *method,
	       struct blob_attr *msg)
{
	struct decollector_private *p = container_of(obj, struct decollector_private, obj);
	struct blob_buf bb;
	int ret = -1;

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

	ret = decollector_dump2_all(p, &bb);
	if (ret) {
		dbg("DM is not ready; do a refresh!\n");
		goto out;
	}

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

	return 0;
}

int decollector_refresh(struct ubus_context *ctx, struct ubus_object *obj,
			struct ubus_request_data *req, const char *method,
			struct blob_attr *msg)
{
	struct decollector_private *p =
		container_of(obj, struct decollector_private, obj);

	decollector_refresh_all(p);
	return 0;
}

enum {
	EVENT_SET_MAXNUM,
	EVENT_SET_MAXAGE,
	NUM_EVENT_POLICY,
};

static const struct blobmsg_policy event_policy[NUM_EVENT_POLICY] = {
	[EVENT_SET_MAXNUM] = { .name = "maxnum", .type = BLOBMSG_TYPE_INT32 },
	[EVENT_SET_MAXAGE] = { .name = "maxage", .type = BLOBMSG_TYPE_INT32 },
};

int decollector_event(struct ubus_context *ctx, struct ubus_object *obj,
		      struct ubus_request_data *req, const char *method,
		      struct blob_attr *msg)
{
	struct decollector_private *p = container_of(obj, struct decollector_private, obj);
	struct blob_attr *tb[NUM_EVENT_POLICY];

	if (msg && blob_len(msg)) {
		blobmsg_parse(event_policy, NUM_EVENT_POLICY, tb,
			      blob_data(msg), blob_len(msg));

		if (tb[EVENT_SET_MAXNUM]) {
			uint32_t maxnum = blobmsg_get_u32(tb[EVENT_SET_MAXNUM]);

			if (!maxnum)
				return UBUS_STATUS_INVALID_ARGUMENT;

			p->event_maxnum = maxnum;
		}

		if (tb[EVENT_SET_MAXAGE]) {
			uint32_t maxage = blobmsg_get_u32(tb[EVENT_SET_MAXAGE]);

			p->event_maxage = maxage;  /* if 0, don't ageout */
		}
	} else {
		struct blob_buf bb = {0};

		/* dump events */
		blob_buf_init(&bb, 0);
		decollector_event_dump(p, &bb);
		ubus_send_reply(ctx, req, bb.head);
		blob_buf_free(&bb);
	}
	return 0;
}

int decollector_status(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 decollector_private *p =
			container_of(obj, struct decollector_private, obj);
	struct wifi_data_element *dm = p->dm;
	struct wifi_network_device *dev = NULL;
	void *arr, *t;
	time_t now;


	memset(&bb, 0, sizeof(bb));
	blob_buf_init(&bb, 0);
	blobmsg_add_string(&bb, "state", timer_pending(&p->ct) ? "collecting" : "idle");
	blobmsg_add_u64(&bb, "refresh_count", p->refresh_cnt);
	blobmsg_add_u64(&bb, "refresh_interval", p->opts.refresh_int);

	time(&now);
	blobmsg_add_u64(&bb, "refresh_next", p->opts.refresh_int - difftime(now, p->refresh_time));

	if (!dm)
		goto out;

	arr = blobmsg_open_array(&bb, "DeviceList");
	list_for_each_entry(dev, &dm->network.devicelist, list) {
		struct getter_context *gt = dev->priv;
		char macstr[18] = {0};
		void *arr2, *tt;
		int i;

		t = blobmsg_open_table(&bb, "");
		hwaddr_ntoa(dev->macaddr, macstr);
		blobmsg_add_string(&bb, "macaddr", macstr);
		blobmsg_add_string(&bb, "status", getter_expect_pending_any(gt) ?
				   "incomplete" : "complete");
		arr2 = blobmsg_open_array(&bb, "cmdu");
		for (i = 0; i < NUM_TXN; i++) {
			char tbuf[64] = {0};
			struct tm *tmtsp;

			tt = blobmsg_open_table(&bb, "");
			blobmsg_add_string(&bb, "type", map_cmdu_type2str(gt->txn[i].expect));
			blobmsg_add_string(&bb, "applicability",
					   applicability_type2str(gt->txn[i].applicability));

			tmtsp = gmtime(&gt->txn[i].tsp);
			strftime(tbuf, sizeof(tbuf), "%Y-%m-%dT%H:%M:%SZ", tmtsp);
			blobmsg_add_string(&bb, "last_rxtime", tbuf);
			blobmsg_add_u32(&bb, "last_rxcnt", gt->txn[i].cnt);
			blobmsg_add_u32(&bb, "total_rxcnt", gt->rxcnt[i]);
			blobmsg_add_u32(&bb, "total_error", gt->error[i]);
			blobmsg_close_table(&bb, tt);
		}
		blobmsg_close_array(&bb, arr2);
		blobmsg_close_table(&bb, t);
	}
	blobmsg_close_array(&bb, arr);

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

	return 0;
}

int decollector_publish_object(struct decollector_private *p, const char *objname)
{
	struct ubus_object *obj;
	struct ubus_object_type *obj_type;
	struct ubus_method *obj_methods;
	int ret;
	struct ubus_method m[5] = {
		UBUS_METHOD_NOARG("dump", decollector_dump2),
		UBUS_METHOD_NOARG("dump2", decollector_dump2),
		UBUS_METHOD_NOARG("refresh", decollector_refresh),
		UBUS_METHOD("event", decollector_event, event_policy),
		UBUS_METHOD_NOARG("status", decollector_status),
	};
	int num_methods = ARRAY_SIZE(m);

	if (!p->ctx) {
		p->ctx = ubus_connect(NULL);
		if (!p->ctx) {
			dbg("Failed to connect to ubus\n");
			return -1;
		}
		ubus_add_uloop(p->ctx);
	}

	obj = &p->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(p->ctx, obj);
	if (ret) {
		dbg("Failed to add 'obj' err = %s\n",
			ubus_strerror(ret));
		free(obj_methods);
		free(obj_type);

		return ret;
	}

	dbg("ADDED object '%s' id %08x type id %08x\n", objname, obj->id, obj->type->id);

	return 0;
}

void decollector_ieee1905_cmdu_handler(struct decollector_private *priv,
		struct blob_attr *msg)
{
	uint16_t cmdu_type;
	uint16_t mid;
	char in_ifname[16] = {0};
	char src[18] = {0};
	uint8_t src_mac[6] = {0};
	uint8_t origin[6] = {0};
	int len = 0;
	char *tlvstr;
	uint8_t *tlv = NULL;
	struct cmdu_buff *cmdu;
	struct blob_attr *tb[6];
	static const struct blobmsg_policy cmdu_attr[6] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_INT16 },
		[1] = { .name = "mid", .type = BLOBMSG_TYPE_INT16 },
		[2] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "src", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "origin", .type = BLOBMSG_TYPE_STRING },
		[5] = { .name = "cmdu", .type = BLOBMSG_TYPE_STRING },
	};

	blobmsg_parse(cmdu_attr, 6, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1])
		return;

	if (tb[0]) {
		cmdu_type = (uint16_t)blobmsg_get_u16(tb[0]);
		if (cmdu_type > MAP_CMDU_TYPE_MAX)
			return;
	}

	if (tb[1])
		mid = (uint16_t)blobmsg_get_u16(tb[1]);

	if (tb[2])
		strncpy(in_ifname, blobmsg_data(tb[2]), sizeof(in_ifname) - 1);

	if (tb[3]) {
		strncpy(src, blobmsg_data(tb[3]), sizeof(src) - 1);
		hwaddr_aton(src, src_mac);
	}

	if (tb[4]) {
		memset(src, 0, sizeof(src));
		strncpy(src, blobmsg_data(tb[4]), sizeof(src) - 1);
		hwaddr_aton(src, origin);
	}

	if (tb[5]) {
		len = blobmsg_data_len(tb[5]) - 16;
		tlvstr = calloc(1, (len + 1) * sizeof(char));
		if (!tlvstr)
			return;

		strncpy(tlvstr, blobmsg_data(tb[5]) + 16, len);
		len = (len - 1) / 2;
		tlv = calloc(1, len * sizeof(uint8_t));
		if (!tlv) {
			free(tlvstr);
			return;
		}

		if (!strtob(tlvstr, len, tlv)) {
			free(tlvstr);
			free(tlv);
			return;
		}

		free(tlvstr);
	}

	cmdu = cmdu_alloc_custom(cmdu_type, &mid, in_ifname, src_mac, tlv, len);
	if (!cmdu) {
		dbg("%s: cmdu_alloc_custom failed!\n", __func__);
	} else {
		/* dbg("%s: cmdu type: %04x, mid: %hu, source: %s\n",
			__func__, cmdu_get_type(cmdu), cmdu_get_mid(cmdu), src);
		*/
		memcpy(cmdu->origin, origin, 6);
		decollector_handle_map_event(priv, cmdu);
		cmdu_free(cmdu);
	}

	if (tlv)
		free(tlv);
}

void decollector_ubus_object_event_handler(struct ubus_context *ctx,
				struct ubus_event_handler *ev,
				const char *type,
				struct blob_attr *msg)
{
	char path[32] = { 0 };
	bool add, del;
	struct blob_attr *tb[2];
	static const struct blobmsg_policy ev_attr[2] = {
		[0] = { .name = "id", .type = BLOBMSG_TYPE_INT32 },
		[1] = { .name = "path", .type = BLOBMSG_TYPE_STRING }
	};
	struct decollector_private *p =
		container_of(ev, struct decollector_private, ubus_obj_ev);

	blobmsg_parse(ev_attr, 2, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1])
		return;

	strncpy(path, blobmsg_data(tb[1]), sizeof(path) - 1);

	if (strncmp(path, MAP_PLUGIN_OBJECT, strlen(MAP_PLUGIN_OBJECT)))
		return;

	add = !strcmp(type, "ubus.object.add");
	del = !strcmp(type, "ubus.object.remove");

	if (add) {
		/* update the i1905 & i1905.map object */
		p->i1905 = decollector_lookup_object(p, IEEE1905_OBJECT);
		if (p->i1905 == OBJECT_INVALID) {
			dbg("%s: Failed to get '%s' object!\n",
					__func__, IEEE1905_OBJECT);
			return;
		}

		p->i1905_map = decollector_lookup_object(p, MAP_PLUGIN_OBJECT);
		if (p->i1905_map == OBJECT_INVALID) {
			dbg("%s: Failed to get '%s' object!\n",
					__func__, MAP_PLUGIN_OBJECT);
			return;
		}

		dbg("re-subscribing CMDUs..!!\n");
		decollector_subscribe_for_cmdus(p);
	} else if (del) {
		dbg("unsubscribing previous CMDUs..!!\n");
		map_unsubscribe(p->ctx, p->subscriber);
		p->subscriber = NULL;
	}
}

int decollector_register_events(struct decollector_private *p)
{
	int ret;

	p->ubus_obj_ev.cb = decollector_ubus_object_event_handler;
	ret = ubus_register_event_handler(p->ctx, &p->ubus_obj_ev, "ubus.object.*");
	if (ret) {
		dbg("%s: failed %s\n for ubus_object_add",
			__func__, ubus_strerror(ret));
	}

	return ret;
}

uint32_t decollector_lookup_object(struct decollector_private *p, const char *objname)
{
	uint32_t id;
	int status;

	status = ubus_lookup_id(p->ctx, objname, &id);
	if (status != UBUS_STATUS_OK) {
		dbg("object '%s' not present!\n", objname);
		return OBJECT_INVALID;
	}

	return id;
}

static void ieee1905_info_response_cb(struct ubus_request *req,
					int mtype, struct blob_attr *msg)
{
	struct json_object *jobj = NULL;
	struct json_object *mac;
	struct decollector_private *p;
	char *addr = NULL;
	char *str;

	if (!msg || !req->priv) {
		dbg("%s:Message recieved is NULL\n", __func__);
		return;
	}

	p = (struct decollector_private *)req->priv;

	str = (char *)blobmsg_format_json_indent(msg, true, -1);
	if (str) {
		jobj = json_tokener_parse(str);
		free(str);
	}

	if (jobj == NULL)
		return;

	/* Store self device mac addr */
	if (json_object_object_get_ex(jobj, "ieee1905id", &mac)) {
		addr = strdup(json_object_get_string(mac));
		hwaddr_aton(addr, p->ieee1905_macaddr);
		free(addr);
	}

	json_object_put(jobj);
}

int decollector_get_ieee1905id(struct decollector_private *p)
{
	struct blob_buf bb = { 0 };
	int ret = 0;

	blob_buf_init(&bb, 0);
	ret = ubus_invoke(p->ctx, p->i1905, "info", bb.head,
				ieee1905_info_response_cb, p, 1000);
	if (ret)
		dbg("Failed to get i1905 info (err = %s)\n",
						ubus_strerror(ret));

	blob_buf_free(&bb);

	return ret;
}

static void steer_stats_response_cb(struct ubus_request *req, int mtype,
				    struct blob_attr *msg)
{
	enum {
		FAIL_NO_CANDIDATE,
		ASSOC_CNTRL_ATTEMPTS,
		ASSOC_CNTRL_SUCCESS,
		ASSOC_CNTRL_FAIL,
		BTM_ATTEMPTS,
		BTM_SUCCESS,
		BTM_FAIL,
		BTM_QUERY_RESP,
		BTM_TIME_SINCE_STEER_ATTEMPT,

		STEER_RESP_NUM_PARAMS
	};

	static const struct blobmsg_policy resp_policy[STEER_RESP_NUM_PARAMS] = {
		[FAIL_NO_CANDIDATE] = { .name = "fail_no_candidate", .type = BLOBMSG_TYPE_INT64 },
		[ASSOC_CNTRL_ATTEMPTS] = { .name = "assoc_cntlr_attempts", .type = BLOBMSG_TYPE_INT64 },
		[ASSOC_CNTRL_SUCCESS] = { .name = "assoc_cntlr_success", .type = BLOBMSG_TYPE_INT64 },
		[ASSOC_CNTRL_FAIL] = { .name = "assoc_cntlr_fail", .type = BLOBMSG_TYPE_INT64 },
		[BTM_ATTEMPTS] = { .name = "btm_attempts", .type = BLOBMSG_TYPE_INT64 },
		[BTM_SUCCESS] = { .name = "btm_success", .type = BLOBMSG_TYPE_INT64 },
		[BTM_FAIL] = { .name = "btm_fail", .type = BLOBMSG_TYPE_INT64 },
		[BTM_QUERY_RESP] = { .name = "btm_query_resp", .type = BLOBMSG_TYPE_INT64 },
		[BTM_TIME_SINCE_STEER_ATTEMPT] =
			{ .name = "time_since_steer_attempt", .type = BLOBMSG_TYPE_INT32 },
	};

	struct wifi_steer_summary *dm_steer_stats;
	struct blob_attr *tb[STEER_RESP_NUM_PARAMS] = { NULL };

	if (!msg || !req->priv) {
		dbg("%s:Message recieved is NULL\n", __func__);
		return;
	}

	dbg("%s: collecting steer_stats...\n", __func__);

	dm_steer_stats = (struct wifi_steer_summary *)req->priv;
	*dm_steer_stats = (struct wifi_steer_summary){ 0 };

	blobmsg_parse(resp_policy, STEER_RESP_NUM_PARAMS, tb, blob_data(msg), blob_len(msg));

	if (tb[FAIL_NO_CANDIDATE]) {
		dm_steer_stats->no_candidate_cnt =
			blobmsg_get_u64(tb[FAIL_NO_CANDIDATE]);
	}

	if (tb[ASSOC_CNTRL_ATTEMPTS]) {
		dm_steer_stats->blacklist_attempt_cnt =
			blobmsg_get_u64(tb[ASSOC_CNTRL_ATTEMPTS]);
	}

	if (tb[ASSOC_CNTRL_SUCCESS]) {
		dm_steer_stats->blacklist_success_cnt =
			blobmsg_get_u64(tb[ASSOC_CNTRL_SUCCESS]);
	}

	if (tb[ASSOC_CNTRL_FAIL]) {
		dm_steer_stats->blacklist_failure_cnt =
			blobmsg_get_u64(tb[ASSOC_CNTRL_FAIL]);
	}

	if (tb[BTM_ATTEMPTS]) {
		dm_steer_stats->btm_attempt_cnt =
			blobmsg_get_u64(tb[BTM_ATTEMPTS]);
	}

	if (tb[BTM_SUCCESS]) {
		dm_steer_stats->btm_success_cnt =
			blobmsg_get_u64(tb[BTM_SUCCESS]);
	}

	if (tb[BTM_FAIL]) {
		dm_steer_stats->btm_failure_cnt =
			blobmsg_get_u64(tb[BTM_FAIL]);
	}

	if (tb[BTM_QUERY_RESP]) {
		dm_steer_stats->btm_query_resp_cnt =
			blobmsg_get_u64(tb[BTM_QUERY_RESP]);
	}

	if (tb[BTM_TIME_SINCE_STEER_ATTEMPT]) {
		dm_steer_stats->last_tsp.tv_sec =
			blobmsg_get_u32(tb[BTM_TIME_SINCE_STEER_ATTEMPT]);
	}
}

static int decollector_get_steer_stats_impl(struct decollector_private *p,
					    struct wifi_steer_summary *dm_steer_stats,
					    const char *sta_macaddr_str)
{
	const int ONE_SEC_TIMEOUT = 1000;
	uint32_t id;
	int ret;
	struct blob_buf msg = { 0 };

	id = decollector_lookup_object(p, MAP_CONTROLLER_OBJECT);
	if (id == OBJECT_INVALID)
		return -1;

	blob_buf_init(&msg, 0);

	if (sta_macaddr_str)
		blobmsg_add_string(&msg, "sta", sta_macaddr_str);

	ret = ubus_invoke(p->ctx, id, "dump_steer_summary", msg.head,
			  steer_stats_response_cb, dm_steer_stats, ONE_SEC_TIMEOUT);

	if (ret) {
		dbg("Failed to get steer_stats for %s (err = %s)\n",
			sta_macaddr_str ? sta_macaddr_str : "network",
			ubus_strerror(ret));
	}

	blob_buf_free(&msg);

	return ret;
}

int decollector_get_network_steer_stats(struct decollector_private *p)
{
	return decollector_get_steer_stats_impl(p,
						&p->dm->network.steer_summary,
						NULL);
}

int decollector_get_sta_steer_stats(struct decollector_private *p)
{
	struct wifi_network *net = &p->dm->network;
	struct wifi_network_device *device = NULL;
	struct wifi_radio_element *radio = NULL;
	struct wifi_bss_element *bss = NULL;
	struct wifi_sta_element *sta = NULL;
	int res = 0;

	list_for_each_entry(device, &net->devicelist, list) {
		list_for_each_entry(radio, &device->radiolist, list) {
			list_for_each_entry(bss, &radio->bsslist, list) {
				list_for_each_entry(sta, &bss->stalist, list) {
					char sta_macaddr_str[18] = { 0 };
					struct wifi_steer_summary *out =
						&sta->mapsta.stats;

					if (!hwaddr_ntoa(sta->macaddr,
							 sta_macaddr_str)) {
						dbg("%s: sta_macaddr is not valid)\n", __func__);
						continue;
					}

					res |= decollector_get_steer_stats_impl(
						p, out, sta_macaddr_str);
				}
			}
		}
	}

	return res;
}

static uint8_t steer_policy_str_to_int(const char *policy)
{
	if (!strcmp(policy, "AGENT_STEER_DISALLOW"))
		return STEER_DISALLOW;

	if (!strcmp(policy, "AGENT_STEER_RCPI_MANDATE"))
		return STEER_MANDATE;

	if (!strcmp(policy, "AGENT_STEER_RCPI_ALLOW"))
		return STEER_ALLOW;

	return 255;
}

static void dump_policy_response_cb(struct ubus_request *req, int mtype,
				    struct blob_attr *msg)
{
	static const struct blobmsg_policy dump_attr[] = {
		[0] = { .name = "node", .type = BLOBMSG_TYPE_ARRAY },
	};
	struct blob_attr *node_tb[ARRAY_SIZE(dump_attr)];
	struct wifi_data_element *dm;

	if (!msg || !req->priv) {
		dbg("%s:Message recieved is NULL\n", __func__);
		return;
	}
	dm = (struct wifi_data_element *)req->priv;

	blobmsg_parse(dump_attr, ARRAY_SIZE(dump_attr), node_tb, blob_data(msg), blob_len(msg));

	if (node_tb[0]) {
		struct blob_attr *node = NULL;
		int rem_node = 0;

		blobmsg_for_each_attr(node, node_tb[0], rem_node) {
			static const struct blobmsg_policy node_attr[] = {
				[0] = { .name = "agent_id", .type = BLOBMSG_TYPE_STRING },
				[1] = { .name = "radio", .type = BLOBMSG_TYPE_ARRAY },
			};
			struct blob_attr *radio_tb[ARRAY_SIZE(node_attr)];
			char agent_id[18] = {0};
			uint8_t agent_id_mac[6] = {0};

			blobmsg_parse(node_attr, ARRAY_SIZE(node_attr), radio_tb,
					blobmsg_data(node), blobmsg_data_len(node));
			strncpy(agent_id, blobmsg_data(radio_tb[0]), sizeof(agent_id) - 1);
			hwaddr_aton(agent_id, agent_id_mac);

			dbg("%s: Parsing policy for agent_id " MACFMT " \n",
			    __func__, MAC2STR(agent_id_mac));

			if (radio_tb[1]) {
				struct blob_attr *radio;
				int rem_radio = 0;

				blobmsg_for_each_attr(radio, radio_tb[1], rem_radio) {
					static const struct blobmsg_policy radio_attr[] = {
						[0] = { .name = "macaddr",
								.type = BLOBMSG_TYPE_STRING },
						[1] = { .name = "agent_id",
								.type = BLOBMSG_TYPE_STRING },
						[2] = { .name = "band",
								.type = BLOBMSG_TYPE_STRING },
						[3] = { .name = "policy",
								.type = BLOBMSG_TYPE_STRING },
						[4] = { .name = "util_threshold",
								.type = BLOBMSG_TYPE_INT32 },
						[5] = { .name = "rcpi_threshold",
								.type = BLOBMSG_TYPE_INT32 },
						[6] = { .name = "report_rcpi_threshold",
								.type = BLOBMSG_TYPE_INT32 },
						[7] = { .name = "report_util_threshold",
								.type = BLOBMSG_TYPE_INT32 },
						[8] = { .name = "report_rcpi_hysteresis_margin",
								.type = BLOBMSG_TYPE_INT32 },
						[9] = { .name = "include_sta_stats",
								.type = BLOBMSG_TYPE_BOOL },
						[10] = { .name = "include_sta_metric",
								 .type = BLOBMSG_TYPE_BOOL },
						[11] = { .name = "include_wifi6_sta_status",
								 .type = BLOBMSG_TYPE_BOOL },
					};
					struct blob_attr *tb[ARRAY_SIZE(radio_attr)];
					char alid[18] = {0}, ruid[18] = {0};
					uint8_t alid_mac[6] = {0}, ruid_mac[6] = {0};
					struct wifi_network_device *dev = NULL;
					struct wifi_radio_element *radio_el = NULL;

					blobmsg_parse(radio_attr, ARRAY_SIZE(radio_attr),
							tb, blobmsg_data(radio), blobmsg_data_len(radio));

					if (!tb[0] || !tb[1]) {
						dbg("|%s:%d| no radio info found in given node\n",
						    __func__, __LINE__);
						continue;
					}

					/* ALID */
					strncpy(alid, blobmsg_data(tb[1]), sizeof(alid) - 1);
					hwaddr_aton(alid, alid_mac);
					dbg("%s: Checking policies for alid " MACFMT "\n",
					    __func__, MAC2STR(alid_mac));
					dev = find_network_device(dm, alid_mac);
					if (!dev) {
						dbg("%s: No matching alid found\n", __func__);
						continue;
					}

					/* RUID */
					strncpy(ruid, blobmsg_data(tb[0]), sizeof(ruid) - 1);
					hwaddr_aton(ruid, ruid_mac);
					dbg("%s: Got policy for radio " MACFMT "\n",
					    __func__, MAC2STR(ruid_mac));
					radio_el = find_radio(dev, ruid_mac);
					if (!radio_el) {
						dbg("%s: No matching radio found\n", __func__);
						continue;
					}

					if (tb[3]) {
						char policy[64] = {0};

						strncpy(policy, blobmsg_data(tb[3]), sizeof(policy) - 1);
						radio_el->steer_policy = steer_policy_str_to_int(policy);
					}

					if (tb[4])
						radio_el->channel_util_threshold = blobmsg_get_u32(tb[4]);
					if (tb[5])
						radio_el->rcpi_steer_threshold = blobmsg_get_u32(tb[5]);

					if (tb[6])
						radio_el->report.sta_rcpi_threshold = blobmsg_get_u32(tb[6]);
					if (tb[7])
						radio_el->report.channel_util_threshold = blobmsg_get_u32(tb[7]);
					if (tb[8])
						radio_el->report.sta_rcpi_margin_override = blobmsg_get_u32(tb[8]);

					if (tb[9])
						radio_el->report.include_sta_stats = blobmsg_get_bool(tb[9]);
					if (tb[10])
						radio_el->report.include_sta_metrics = blobmsg_get_bool(tb[10]);
					if (tb[11])
						radio_el->report.include_wifi6_metrics = blobmsg_get_bool(tb[11]);
				}
			} else {
				dbg("|%s:%d| no radios to parse in current node!\n",
				    __func__, __LINE__);
				continue;
			}
		}
	} else {
		dbg("|%s:%d| missing nodes to parse!\n",
		    __func__, __LINE__);
		return;
	}
}

static int decollector_get_policy_config_impl(struct decollector_private *p,
		struct wifi_data_element *dm)
{
	const int ONE_SEC_TIMEOUT = 1000;
	uint32_t id;
	int ret;
	struct blob_buf msg = { 0 };

	id = decollector_lookup_object(p, MAP_CONTROLLER_OBJECT);
	if (id == OBJECT_INVALID)
		return -1;

	blob_buf_init(&msg, 0);

	ret = ubus_invoke(p->ctx, id, "dump_policy", msg.head,
			  dump_policy_response_cb, dm, ONE_SEC_TIMEOUT);

	if (ret) {
		dbg("Failed to dump policy (err = %s)\n",
			ubus_strerror(ret));
	}

	blob_buf_free(&msg);

	return ret;

	return 0;
}

int decollector_get_policy_config(struct decollector_private *p)
{
	return decollector_get_policy_config_impl(p, p->dm);
}


static void decollector_send_cmdu_cb(struct ubus_request *req, int type,
				     struct blob_attr *msg)
{
	struct json_object *jobj = NULL;
	struct json_object *tmp;
	uint16_t *mid;
	char *str;

	if (!msg || !req->priv) {
		dbg("%s:Message recieved is NULL\n", __func__);
		return;
	}

	mid = (uint16_t *)req->priv;

	str = (char *)blobmsg_format_json_indent(msg, true, -1);
	if (str) {
		jobj = json_tokener_parse(str);
		free(str);
	}

	if (jobj == NULL)
		return;

	if (json_object_object_get_ex(jobj, "mid", &tmp))
		*mid = json_object_get_int(tmp);


	json_object_put(jobj);
}

int decollector_send_cmdu_request(struct decollector_private *priv,
				  struct cmdu_buff *req, uint8_t *dst)
{
	//struct wifi_network_device *dev = NULL;
	uint16_t type = cmdu_get_type(req);
	struct blob_buf b = { 0 };
	char dst_addr[18] = { 0 };
	uint16_t msgid = 0;
	int ret = 0;


	info(MACFMT ": Send %s\n", MAC2STR(dst), map_cmdu_type2str(type));

	memset(&b, 0, sizeof(struct blob_buf));
	blob_buf_init(&b, 0);
	blobmsg_add_u32(&b, "type", cmdu_get_type(req));
	blobmsg_add_u32(&b, "mid", cmdu_get_mid(req));
	hwaddr_ntoa(dst, dst_addr);
	blobmsg_add_string(&b, "dst", dst_addr);

	if (req->datalen) {
		char *tlv_str = NULL;
		uint16_t len = 0;

		len = (req->datalen * 2) + 1;

		tlv_str = calloc(len, sizeof(char));
		if (!tlv_str)
			goto out;

		btostr(req->data, req->datalen, tlv_str);
		tlv_str[len-1] = '\0';

		blobmsg_add_string(&b, "data", tlv_str);
		free(tlv_str);
	}

	ret = ubus_invoke(priv->ctx, priv->i1905, "cmdu",
			  b.head, decollector_send_cmdu_cb,
			  (void *)&msgid,
			  UBUS_TIMEOUT);
	if (!ret) {
		dbg("%s: mid = %hu\n", __func__, msgid);
	} else {
		dbg("%s: cmdu send failed (err = %s)\n",
			__func__, ubus_strerror(ret));

#if 0
		dev = decollector_get_origin_dev(priv, priv->dm, req->origin);
		if (dev)
			decollector_stop_getter(priv, dev);

		if (ret == UBUS_STATUS_NOT_FOUND) {
			/* refresh reference to ieee1905 object */
			priv->i1905 = decollector_lookup_object(priv, IEEE1905_OBJECT);
			if (priv->i1905 == OBJECT_INVALID)
				dbg("Failed to get '%s' object!\n", IEEE1905_OBJECT);
		}
#endif
	}

out:
	blob_buf_free(&b);
	return ret;
}

static void ieee1905_neighbors_cb(struct ubus_request *req,
		int mtype, struct blob_attr *msg)
{
	int i, num;
	int total_nodes = 0;
	struct json_object *nbr_array;
	struct json_object *jobj = NULL;
	char *str = NULL;
	struct map_devices {
		int num;
		uint8_t *dev;
	} *n_array = NULL;


	if (!msg || !req->priv) {
		dbg("%s: msg = NULL\n", __func__);
		return;
	}

	n_array = (struct map_devices *)req->priv;

	str = (char *)blobmsg_format_json_indent(msg, true, -1);
	if (str) {
		jobj = json_tokener_parse(str);
		free(str);
	}

	if (jobj == NULL)
		return;

	/* for own 1905 macaddress */
	total_nodes += 1;

	if (json_object_object_get_ex(jobj, "neighbors", &nbr_array)) {
		num = json_object_array_length(nbr_array);
		if (!num)
			dbg("%s: nodes array is empty\n", __func__);

		total_nodes += num;

		n_array->dev = calloc(total_nodes, 6 * sizeof(uint8_t));
		if (!n_array->dev) {
			dbg("Failed to malloc nodes array\n");
			goto out_err;
		}

		n_array->num = total_nodes;

		for (i = 0; i < num; i++) {

			struct json_object *nbr_object
				= json_object_array_get_idx(nbr_array, i);
			struct json_object *mac_addr_object = NULL;

			if (json_object_object_get_ex(nbr_object, "ieee1905id", &mac_addr_object)) {
				const char *mac_addr_str =
					json_object_get_string(mac_addr_object);

				if (mac_addr_str) {
					uint8_t tmpmac[6] = { 0 };
					hwaddr_aton(mac_addr_str, tmpmac);
					memcpy(&n_array->dev[6 * (i + 1)], tmpmac, 6);
				}
			} else {
				dbg("%s: Failed to get ieee1905id object for device[%d]\n", __func__, i);
			}
		}
	}


out_err:
	json_object_put(jobj);
}

int decollector_get_deagents(struct decollector_private *p, int *num, uint8_t **macaddrs)
{
	struct blob_buf bb;
	int ret = 0;
	struct map_devices {
		int num;
		uint8_t *dev;
	} mapdevs = { 0 };


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

	ret = ubus_invoke(p->ctx, p->i1905, "neighbors", bb.head,
			  ieee1905_neighbors_cb, &mapdevs, 1000);

	if (!ret) {
		/* copy the self device macaddr */
		memcpy(&mapdevs.dev[0], p->ieee1905_macaddr, 6);

		*num = mapdevs.num;
		*macaddrs = mapdevs.dev;
		dbg("num ieee1905 nodes = %d\n", *num);
	} else {
		dbg("Failed to call ubus ieee1905 neighbors\n");
	}

	blob_buf_free(&bb);

	return ret;
}

int decollector_map_sub_cb(void *bus, void *priv, void *data)
{
	struct blob_attr *msg = (struct blob_attr *)data;

	decollector_ieee1905_cmdu_handler(priv, msg);
	return 0;
}

int decollector_map_del_cb(void *bus, void *priv, void *data)
{
	uint32_t *obj = (uint32_t *)data;

	dbg("Object 0x%x no longer present\n", *obj);
	return 0;
}

int decollector_subscribe_for_cmdus(struct decollector_private *priv)
{
	mapmodule_cmdu_mask_t cmdu_mask = {0};
	int ret;

	map_prepare_cmdu_mask(cmdu_mask,
			CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE,
			CMDU_TYPE_TOPOLOGY_RESPONSE,
			CMDU_AP_CAPABILITY_REPORT,
			CMDU_AP_METRICS_RESPONSE,
			CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE,
			CMDU_BACKHAUL_STA_CAPABILITY_REPORT,
			CMDU_OPERATING_CHANNEL_REPORT,
			CMDU_CHANNEL_SCAN_REPORT,
			CMDU_CHANNEL_PREFERENCE_REPORT,
			CMDU_TYPE_TOPOLOGY_NOTIFICATION,
			CMDU_CLIENT_DISASSOCIATION_STATS,
			CMDU_CLIENT_CAPABILITY_REPORT,
			CMDU_TYPE_LINK_METRIC_RESPONSE,
#if (EASYMESH_VERSION >= 4)
			CMDU_ANTICIPATED_CHANNEL_USAGE,
#endif
			CMDU_BEACON_METRICS_RESPONSE,
#if (EASYMESH_VERSION >= 6)
			CMDU_EARLY_AP_CAPABILITY_REPORT,
#endif
			CMDU_TYPE_AP_AUTOCONFIGURATION_WSC,
#if (EASYMESH_VERSION >= 3)
			CMDU_SERVICE_PRIORITIZATION_REQUEST,
#endif
			CMDU_TYPE_INVALID);

	ret = map_subscribe(priv->ctx,
			    &priv->i1905_map,
			    "decollector", &cmdu_mask, priv,
			    decollector_map_sub_cb,
			    decollector_map_del_cb,
			    &priv->subscriber);
	return ret;
}
