/*
 * agent_ubus_events.c - event handling
 *
 * Copyright (C) 2022 IOPSYS Software Solutions AB. All rights reserved.
 *
 */

#include "agent.h"
#include "agent_cmdu.h"
#include "agent_map.h"
#include "agent_ubus.h"
#include "agent_tlv.h"
#include "backhaul.h"
#include "backhaul_blacklist.h"
#include "dpp.h"
#if (EASYMESH_VERSION >= 3)
#ifdef USE_LIBDPP
#include "dpp.h"
#endif /* USE_LIBDPP */
#include "qos.h"
#endif
#include "extension.h"
#include "utils/1905_ubus.h"
#include "utils/utils.h"
#ifdef EASYMESH_VENDOR_EXT
#include "vendor.h"
#endif
#if (EASYMESH_VERSION >= 6)
#include "mld.h"
#endif

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

#include <wifiutils.h>

#if (EASYMESH_VERSION >= 6)
static void agent_update_radio_channels(struct agent *a)
{
	struct wifi_radio_element *r = NULL;

	list_for_each_entry(r, &a->radiolist, list) {
		struct wifi_radio_status radio_status = {};

		if (wifi_radio_status(r->name, &radio_status))
			continue;

		r->current_channel = radio_status.channel;
		r->current_bandwidth = radio_status.bandwidth;
	}

	send_oper_channel_report(a, NULL);
}
#endif

static void wifi_chan_change_event_handler(void *c, struct blob_attr *msg)
{
	struct agent *a = c;
	struct cmdu_buff *cmdu;
	static const struct blobmsg_policy ev_attr[] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};
	struct blob_attr *tb[ARRAY_SIZE(ev_attr)];
	struct wifi_radio_element *radio;
	char *ifname, *event;
	uint32_t chan = 0;
	uint32_t bw = 0;

	blobmsg_parse(ev_attr, ARRAY_SIZE(ev_attr), tb, blob_data(msg), blob_len(msg));
	if (!tb[0] || !tb[1] || !tb[2])
		return;

	ifname = blobmsg_data(tb[0]);
	event = blobmsg_data(tb[1]);

	radio = agent_get_radio_with_ifname(a, ifname);
	if (!radio) {
#if (EASYMESH_VERSION >= 6)
		if (!strcmp(event, "ap-chan-change") ||
		    !strcmp(event, "csa-finished") ||
		    !strcmp(event, "ap-csa-finished"))
			/*
			 * If we get event on mld netdev about channel
			 * changes update all radios.
			 */
			if (agent_get_apmld_by_ifname(a, ifname))
				agent_update_radio_channels(a);
#endif
		return;
	}

	if (!strcmp(event, "ap-chan-change")) {
		static const struct blobmsg_policy data_attr[] = {
			[0] = { .name = "target-channel", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "target-width", .type = BLOBMSG_TYPE_STRING },
			[2] = { .name = "reason", .type = BLOBMSG_TYPE_STRING },
		};
		struct blob_attr *data[ARRAY_SIZE(data_attr)];

		blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data, blobmsg_data(tb[2]),
			      blobmsg_data_len(tb[2]));

		if (!data[0] || !data[1] || !data[2]) {
			WARN_ON(1);
			return;
		}

		chan = strtol(blobmsg_data(data[0]), NULL, 10);
		bw = strtol(blobmsg_data(data[1]), NULL, 10);

		if (!strcmp(blobmsg_data(data[2]), "radar") ||
		    !strcmp(blobmsg_data(data[2]), "move-start"))
			timer_set(&radio->preference_report_timer, 1000);

	} else if (!strcmp(event, "csa-finished")) {
		static const struct blobmsg_policy data_attr[] = {
			[0] = { .name = "channel", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "bandwidth", .type = BLOBMSG_TYPE_STRING },
			[2] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
		};
		struct blob_attr *data[ARRAY_SIZE(data_attr)];

		blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data, blobmsg_data(tb[2]),
			      blobmsg_data_len(tb[2]));

		if (!data[0] || !data[1] || !data[2]) {
			WARN_ON(1);
			return;
		}

		chan = strtol(blobmsg_data(data[0]), NULL, 10);
		bw = strtol(blobmsg_data(data[1]), NULL, 10);
	} else if (!strcmp(event, "ap-csa-finished")) {
		static const struct blobmsg_policy data_attr[] = {
			[0] = { .name = "freq", .type = BLOBMSG_TYPE_INT32 },
			[1] = { .name = "bandwidth", .type = BLOBMSG_TYPE_INT32 },
		};
		struct blob_attr *data[ARRAY_SIZE(data_attr)];
		uint32_t freq;

		blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data, blobmsg_data(tb[2]),
			      blobmsg_data_len(tb[2]));

		if (!data[0] || !data[1]) {
			WARN_ON(1);
			return;
		}

		freq = blobmsg_get_u32(data[0]);
		bw = blobmsg_get_u32(data[1]);
		chan = f2c(freq);
	} else {
		return;
	}

	if (!chan || !bw) {
		struct wifi_radio_status radio_status = {};

		if (!wifi_radio_status(radio->name, &radio_status)) {
			chan = radio_status.channel;
			bw = radio_status.bandwidth;
		}
	}

	if (!chan || !bw)
		return;

	if (radio->reported_channel == chan &&
	    radio->reported_bandwidth == bw)
		return;

	radio->current_channel = chan;
	radio->current_bandwidth = bw;

	radio->current_opclass = wifi_opclass_find_id_from_channel(&radio->opclass,
			radio->current_channel, radio->current_bandwidth);

	/* Finally send this to controller */
	agnt_dbg(LOG_CHANNEL, "[%s] oper_channel_response chan %d bw %d opclass %d\n",
	    ifname, radio->current_channel, radio->current_bandwidth,
	    radio->current_opclass);

	cmdu = agent_gen_oper_channel_response(a, radio, radio->current_channel,
					       radio->current_bandwidth, 0);
	if (WARN_ON(!cmdu))
		return;

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

	radio->reported_channel = radio->current_channel;
	radio->reported_bandwidth = radio->current_bandwidth;
}

static void wifi_cac_event_handler(void *c, struct blob_attr *msg)
{
	struct agent *a = c;
	static const struct blobmsg_policy ev_attr[] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};
	struct blob_attr *tb[ARRAY_SIZE(ev_attr)];
	struct wifi_radio_element *radio;
	char *ifname, *event;

	blobmsg_parse(ev_attr, ARRAY_SIZE(ev_attr), tb, blob_data(msg), blob_len(msg));
	if (!tb[0] || !tb[1] || !tb[2])
		return;

	ifname = blobmsg_data(tb[0]);
	event = blobmsg_data(tb[1]);

	radio = agent_get_radio_with_ifname(a, ifname);
	if (!radio) {
#if (EASYMESH_VERSION >= 6)
		/* If event reported on mld netdev */
		if (agent_get_apmld_by_ifname(a, ifname) &&
		    (!strcmp(event, "cac-start") || !strcmp(event, "cac-end"))) {
			radio = agent_get_radio_by_band(a, BAND_5);
			if (!radio)
				return;
		} else
#endif
			return;
	}

	/* Update preferences - send CMDU when changed */
	if (!strcmp(event, "cac-start") || !strcmp(event, "cac-end"))
		wifi_radio_update_opclass_preferences(a, radio, 1);

	if (!a->cfg.ap_follow_sta_dfs)
		return;

	if (!strcmp(event, "cac-end")) {
		static const struct blobmsg_policy data_attr[] = {
			[0] = { .name = "success", .type = BLOBMSG_TYPE_STRING },
		};
		struct blob_attr *data[ARRAY_SIZE(data_attr)];

		blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data, blobmsg_data(tb[2]),
			      blobmsg_data_len(tb[2]));

		if (!data[0])
			return;

		if (strcmp(blobmsg_data(data[0]), "1"))
			return;

		timer_set(&a->dynbh_scheduler, 1 * 1000);
	}
}

static uint16_t wifi_process_pvid_assoc_frame(struct agent *a, uint8_t *frame,
					    int len)
{
	int i;
	uint16_t pvid = 0;

	for (i = 0; i < len; i++) {
		uint8_t *p = frame + i;
		uint8_t elem_len = 0;
		uint8_t wfa_oui[4] = {0x50, 0x6f, 0x9a, 0x1b};

		if (*p++ != 0xdd) /* IEEE 802.1 vendor specific element */
			continue;

		elem_len = *p;

		if (elem_len < 14) { /* At least 14 bytes long */
			i += elem_len;
			continue;
		}

		if (elem_len > len - (i + 2)) {
			agnt_dbg(LOG_TS, "%s: WFA Element len exceeds raw frame len!\n", __func__);
			break;
		}


		p++; /* len */
		if (memcmp(p, wfa_oui, 4)) {
			i += elem_len;
			continue;
		}

		p += 4; /* wfa oui */

		if (*p++ != 0x06)  { /* multi-ap ext */
			i += elem_len;
			continue;
		}

		p += *p; /* subelem len */
		p++; /* len */


		if (*p++ != 0x07) { /* multi-ap profile */
			i += elem_len;
			continue;
		}

		p += *p; /* subelem len */
		p++; /* len */

		if (*p++ != 0x08) { /* multi-ap def 8021q */
			i += elem_len;
			continue;
		}

		p++; /* len */
		memcpy(&pvid, p, 2);
		agnt_dbg(LOG_TS, "%s: Found 8021q PVID %d\n", __func__, pvid);
		break;
	}

	return pvid;
}

static void check_protocol(const char *framestr, uint8_t *protocol)
{
	uint8_t *frame;
	uint8_t category;
	uint8_t action;
	int offset = 0;
	int len;

	len = strlen(framestr);
	len = len / 2;
	frame = calloc(len, sizeof(uint8_t));
	if (!frame)
		return;

	if (!strtob((char *)framestr, len, frame)) /* cppcheck-suppress cert-EXP05-C */
		goto out;

	if (len < 26)
		goto out;

	/* 2: frame control,
	 * 2: duration,
	 * 6: destination addr,
	 * 6: source addr,
	 * 6: bssid,
	 * 2: seq control
	 */
	offset = 2 + 2 + 6 + 6 + 6 + 2;
	memcpy(&category, frame + offset, 1);
	memcpy(&action, frame + offset + 1, 1);

	if (category == 0x0a) {
		if (action == 0x06)
			*protocol = 0x02;
		else if (action == 0x1a)
			*protocol = 0x03;
	}

	/* TODO:check the action frame category &
	 * type for ANQP.
	 */

out:
	free(frame);
}

static int get_frame_type(struct agent *a, struct json_object *frameobj)
{
	const char *framestr;
	int frame_type = -1;

	framestr = json_object_to_json_string(frameobj);
	if (!framestr)
		return -1;

	if (strstr(framestr, "reassoc"))
		frame_type = WIFI_FRAME_REASSOC_REQ;
	else if (strstr(framestr, "assoc"))
		frame_type = WIFI_FRAME_ASSOC_REQ;
	else if (strstr(framestr, "action"))
		frame_type = WIFI_FRAME_ACTION;

	return frame_type;
}

static int wifi_parse_all_frame(struct agent *a, const char *ifname,
		struct json_object *frameobj)
{
	trace("%s\n", __func__);
	const char *framestr;
	int stype;
	uint8_t protocol = 0xff;

	stype = get_frame_type(a, frameobj);
	if (stype == -1)
		return -1;

	switch (stype) {
	case WIFI_FRAME_REASSOC_REQ:
		protocol = 0x01;
		framestr = json_get_string(frameobj, "reassoc");
		break;
	case WIFI_FRAME_ASSOC_REQ:
		protocol = 0x00;
		framestr = json_get_string(frameobj, "assoc");
		break;
	case WIFI_FRAME_ACTION:
		framestr = json_get_string(frameobj, "action");
		check_protocol(framestr, &protocol);
		break;
	default:
		framestr = NULL;
		break;
	}

	if (!framestr)
		return -1;

	prepare_tunneled_message((void *)a, ifname, protocol, framestr);

	return 0;
}

static int wifi_send_sta_report(struct agent *a, const char *ifname,
		uint8_t *macaddr, uint32_t status, uint8_t *bssid)
{
	struct netif_ap *ap;
	uint8_t ret = 0;

	ap = agent_get_ap_by_ifname(a, ifname);
	if (!ap)
		return -1;


	/* Here we get need to send the steering report */
	ret = send_steer_btm_report(a, a->cntlr_almac, bssid,
			ap->bssid, macaddr, status);
	return ret;
}

static int wifi_parse_assoc_frame(struct agent *a, struct json_object *frameobj)
{
	struct wifi_assoc_frame *new, *old;
	struct sta *s;
	const char *framestr, *macaddr;
	int len;
	uint8_t *frame;

	trace("%s: parsing wifi frame\n", __func__);

	macaddr = json_get_string(frameobj, "macaddr");
	if (!macaddr)
		return -1;

	framestr = json_get_string(frameobj, "raw");
	if (!framestr)
		return -1;

	len = strlen(framestr);
	len = len / 2;
	frame = calloc(len, sizeof(uint8_t));
	if (!frame)
		return -1;

	if (!strtob((char *)framestr, len, frame)) /* cppcheck-suppress cert-EXP05-C */
		goto out_frame;

	new = calloc(1, sizeof(*new));
	if (!new)
		goto out_frame;

	new->frame = frame;
	hwaddr_aton(macaddr, new->macaddr);
	new->len = len;

	/* if frame for client exists in list, replace it */
	old = wifi_get_frame(a, new->macaddr);
	if (old) {
		list_del(&old->list);
		free(old->frame);
		free(old);
	}

	/* if sta exists attach to sta directly and exit*/
	s = agent_get_sta(a, new->macaddr);
	if (s) {
		if (s->assoc_frame) {
			free(s->assoc_frame->frame);
			free(s->assoc_frame);
		}
		s->assoc_frame = new;
		return 0;
	}

	list_add(&new->list, &a->framelist);

	return 0;
out_frame:
	free(frame);
	return -1;
}

struct netif_bk *agent_get_netif_bk_with_channel(struct agent *a,
						 uint8_t opclass,
						 uint8_t channel)
{
	struct wifi_radio_element *re = NULL;
	struct netif_bk *best = NULL;
	uint8_t priority = 255;

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_bk *bk = &re->bk;

		if (!re->has_bsta)
			continue;

		if (!is_channel_supported_by_radio(re, opclass, channel))
			continue;

		if (best && bk->cfg->priority < priority)
			continue;

		best = bk;
		priority = bk->cfg->priority;
	}

	return best;
}

static void process_sta_con_dis_evt(struct agent *a,
		bool add, char *name, struct blob_attr *frm_data)
{
	/*
	 * { "wifi.sta": {"ifname":"wlan0","event":"connected","data":{
	 *   "macaddr":"16:bc:09:81:ec:48","mlo_macaddr":"90:09:df:e9:82:e3",
	 *   "mlo_bssid":"00:03:7f:12:69:69","bssid":"00:03:7f:12:6a:6a",
	 *   "mlo_link_id":1}} }
	 */
	static const struct blobmsg_policy data_attr[] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
#if (EASYMESH_VERSION >= 6)
		[2] = { .name = "mlo_bssid", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "mlo_macaddr", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "mlo_link_id", .type = BLOBMSG_TYPE_INT32 },
#endif
	};
	struct blob_attr *data[ARRAY_SIZE(data_attr)];
	struct netif_ap *ap = NULL;
	uint8_t macaddr[6] = {0};
	uint8_t bssid[6] = {0};
	struct sta *s;
#if (EASYMESH_VERSION >= 6)
	struct sta_mld *stamld = NULL;
	uint8_t mlo_macaddr[6] = {0};
#endif

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data, blobmsg_data(frm_data),
			blobmsg_data_len(frm_data));

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

	if (!hwaddr_aton(blobmsg_data(data[0]), macaddr))
		return;

	if (!hwaddr_aton(blobmsg_data(data[1]), bssid))
		return;

	ap = agent_get_ap(a, bssid);
	if (!ap)
		return;

#ifndef UNAUTHORIZED_STA_IN_ASSOCLIST
	if (add)
		s = wifi_add_sta(a, ap->ifname, macaddr);
	else
		s = agent_get_sta(a, macaddr);
#else
	s = agent_get_sta(a, macaddr);
#endif
	if (!s) {
		dbg("%s: Failed to add STA or STA has already disconnected\n", __func__);
		return;
	}
#if (EASYMESH_VERSION >= 6)
	/* Affiliated STA MAC in-case of MLO client*/
	if (add && data[2] && data[3] && data[4]) {
		struct netif_mld *apmld;
		uint8_t mlo_bssid[6] = {0};
		uint8_t mlo_link_id = 0;

		mlo_link_id = (uint8_t) blobmsg_get_u32(data[4]);

		if (!hwaddr_aton(blobmsg_data(data[3]), mlo_macaddr))
			return;

		if (!hwaddr_aton(blobmsg_data(data[2]), mlo_bssid))
			return;

		stamld = stamld_update(a, ap, mlo_macaddr, mlo_bssid,
				    mlo_link_id, s);
		if (!stamld)
			return;

		if (s->assoc_frame && s->assoc_frame->len > 4) {
			wifi_process_ml_ie(a, stamld,
					   s->assoc_frame->frame + 4,
					   s->assoc_frame->len - 4);
		}

		apmld = agent_get_apmld(a, mlo_bssid);
		if (apmld)
			apmld_refresh_stations(a, apmld);
	}
#endif

#ifndef UNAUTHORIZED_STA_IN_ASSOCLIST
	if (add)
		wifi_topology_notification(a, s, ap, 0x01 /* joined */,
					   0x00 /* success */);
	else {
		wifi_topology_notification(a, s, ap, 0x00 /* left */,
					   0x01 /* unspecified reason */);

#if (EASYMESH_VERSION >= 6)
		if (data[3] && hwaddr_aton(blobmsg_data(data[3]), mlo_macaddr)) {
			stamld = agent_get_stamld(a, mlo_macaddr);
			if (stamld) {
				stamld_free(a, stamld);
				return;
			}
		}
#endif
		wifi_del_sta(a, ap->ifname, macaddr);
	}
#endif
}

static void process_sta_connected_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	process_sta_con_dis_evt(a, true, ifname, frm_data);
}

static void process_sta_disconnected_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	process_sta_con_dis_evt(a, false, ifname, frm_data);
}

#define ASSOC_TIMEOUT	60
static void process_sta_deauth_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	static const struct blobmsg_policy data_attr[] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "reason", .type = BLOBMSG_TYPE_INT32 },
		[2] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },

	};
	struct blob_attr *data[ARRAY_SIZE(data_attr)];
	struct agent_config *cfg = &a->cfg;
	struct policy_cfg *pcfg;
	uint8_t macaddr[6] = {0};
	uint8_t bssid[6] = {0};
	struct netif_ap *ap;
	uint16_t reason;
	struct sta *s;

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data,
			blobmsg_data(frm_data), blobmsg_data_len(frm_data));

	if (!data[0] || !data[1] || !data[2] || !cfg)
		return;

	pcfg = cfg->pcfg;
	if (!pcfg)
		return;

	if (!hwaddr_aton(blobmsg_data(data[0]), macaddr))
		return;

	if (!hwaddr_aton(blobmsg_data(data[2]), bssid))
		return;

	ap = agent_get_ap(a, bssid);
	if (!ap)
		return;

	s = agent_get_sta(a, macaddr);
	if (!s)
		return;

	reason = blobmsg_get_u32(data[1]);

	/* Send failed connection message if the map-agent
	 * has sent fewer than the maximum number of Send Failed
	 * Connection messages (as specified in the
	 * Maximum Reporting Rate element of the Unsuccessful
	 * Association Policy TLV) in the preceding minute
	 */
	if (pcfg->report_sta_assocfails) {
		if (timestamp_expired(&a->last_unassoc,
					ASSOC_TIMEOUT * 1000)) {

			timestamp_update(&a->last_unassoc);
			a->unassoc_cnt = 1;
			send_failed_connection_msg(a, macaddr, 0, reason);
		} else {
			if (a->unassoc_cnt < pcfg->report_sta_assocfails_rate) {
				a->unassoc_cnt++;
				send_failed_connection_msg(a, macaddr, 0, reason);
			}
		}
	}
#ifdef UNAUTHORIZED_STA_IN_ASSOCLIST
	wifi_topology_notification(a, s, ap, 0, reason);
	wifi_del_sta(a, ifname, macaddr);
#endif
}

static void process_sta_btm_resp_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	struct blob_attr *data[3];
	static const struct blobmsg_policy data_attr[3] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "target_bssid", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "status", .type = BLOBMSG_TYPE_STRING },
	};
	char mac_str[18] = {0};
	uint8_t mac[6] = {0}, bssid[6] = {0};
	int status;
	char ev_data[512] = {0};

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data,
			blobmsg_data(frm_data), blobmsg_data_len(frm_data));

	if (!data[0] || !data[2])
		return;

	/* macaddr */
	strncpy(mac_str, blobmsg_data(data[0]), sizeof(mac_str) - 1);
	if (!hwaddr_aton(mac_str, mac))
		return;

	/* target bssid - may be empty in case of failure */
	if (data[1]) {
		char bssid_str[18] = {0};

		strncpy(bssid_str, blobmsg_data(data[1]),
				sizeof(bssid_str) - 1);
		if (!hwaddr_aton(bssid_str, bssid))
			return;
	}

	/* status */
	status = strtol(blobmsg_data(data[2]), NULL, 10);

	/* notify event received */
	snprintf(ev_data, sizeof(ev_data),
			"{\"macaddr\":\""MACFMT"\""
			",\"dst_bssid\":\""MACFMT"\""
			",\"status\":%d}",
			MAC2STR(mac),
			MAC2STR(bssid),
			status);

	agent_notify_iface_event(a, ifname, "btm-resp", ev_data);

	wifi_send_sta_report(a, ifname, mac, status, bssid);

	trace("%s: btm-resp for "MACFMT" status %d\n",
		  __func__, MAC2STR(mac), status);

	/* Generally after successful steering station is disassociated from source BSSID,
	 * but sometimes it happens that station is not disassociated from source BSSID
	 * and is associated to targed BSSID at the same time. To avoid that situation
	 * we start timer here to disconnect wifi station when it left on source BSSID.
	 * When driver removes station after successful steer then timer will never end
	 * because it will be deleted together with station, but when timer hits timeout
	 * then it means station was not disconnected and funtion wifi_sta_steered_deauth
	 * will do this.
	 */
	if (!status) {
		struct sta *s = NULL;
		struct netif_ap *ap = agent_get_ap_by_ifname(a, ifname);

		if (!ap) {
			warn("%s: Unable to find iface %s\n", __func__, ifname);
			return;
		}

		list_for_each_entry(s, &ap->stalist, list) {
			if (!memcmp(s->macaddr, mac, 6)) {
				timer_set(&s->sta_steer_deauth_timer, STA_STEERED_DEAUTH_INTERVAL * 1000);
				break;
			}
		}
	} else {
		/* TODO:
		 * update reject counter and retry steer later
		 */
	}
}

static void process_sta_bcn_report_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	struct blob_attr *data[4];
	static const struct blobmsg_policy data_attr[4] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "token", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "mode", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "nbr", .type = BLOBMSG_TYPE_STRING },
	};
	char mac_str[18] = {0};
	uint8_t sta_addr[6] = {0};
	struct sta *s;
	uint8_t mode = 0;

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data,
			blobmsg_data(frm_data), blobmsg_data_len(frm_data));

	if (!data[0] || !data[1] || !data[2] || !data[3])
		return;

	mode = strtol(blobmsg_data(data[2]), NULL, 10);

	if (mode) /* Measurement Report Mode should be 0x00 */
		return;

	strncpy(mac_str, blobmsg_data(data[0]), sizeof(mac_str) - 1);

	if (!hwaddr_aton(mac_str, sta_addr))
		return;

	s = agent_get_sta(a, sta_addr);
	if (!s)
		return;

	if (!s->supports_bcnreport)
		s->supports_bcnreport = true;
}

static void process_sta_wds_station_evt(struct agent *a,
		bool add, char *ifname, struct blob_attr *frm_data)
{
	struct blob_attr *data[1];
	static const struct blobmsg_policy data_attr[1] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	};
	char wdsname[16] = {0};
	struct ts_context *ts = &a->ts;
	struct wifi_radio_element *re = NULL;

	if (a->cfg.brcm_setup)
		return;

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data,
			blobmsg_data(frm_data), blobmsg_data_len(frm_data));

	if (!data[0])
		return;

	strncpy(wdsname, blobmsg_data(data[0]), sizeof(wdsname) - 1);

	if (add)
		netif_alloc_wds(a, wdsname);
	else
		netif_free_wds_by_ifname(a, wdsname);

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_ap *ap = NULL;

		list_for_each_entry(ap, &re->aplist, list) {
			uint16_t vid;

			if (!ap->cfg)
				continue;

			vid = ap->cfg->vid;
			if (!is_vid_valid(vid))
				vid = ts->primary_vid;

			if (a->cfg.guest_isolation && (vid != 1 && vid != ts->primary_vid)) {
				dbg("/lib/wifi/multiap ts isolate_wds %s %s %s\n", (add ? "add" : "del"), ap->ifname, wdsname);
				runCmd("/lib/wifi/multiap ts isolate_wds %s %s %s", (add ? "add" : "del"), ap->ifname, wdsname);
			}
		}
	}
}

static void process_sta_wds_station_add_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	process_sta_wds_station_evt(a, true, ifname, frm_data);
}

static void process_sta_wds_station_del_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	process_sta_wds_station_evt(a, false, ifname, frm_data);
}

static void process_sta_action_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	struct blob_attr *data[2];
	static const struct blobmsg_policy data_attr[2] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "raw", .type = BLOBMSG_TYPE_STRING },
	};
	char mac_str[18] = {0};
	uint8_t macaddr[6] = {0};
	char *framestr;
	uint8_t *frame;
	int framelen = 0;

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data,
			blobmsg_data(frm_data), blobmsg_data_len(frm_data));

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

	strncpy(mac_str, blobmsg_data(data[0]), sizeof(mac_str) - 1);

	if (!hwaddr_aton(mac_str, macaddr))
		return;

	framestr = (char *)blobmsg_data(data[1]);

	framelen = strlen(framestr);
	framelen = framelen / 2;
	frame = calloc(framelen, sizeof(uint8_t));
	if (!frame)
		return;

	if (!strtob(framestr, framelen, frame)) {
		free(frame);
		return;
	}

	if (frame && frame[0] == 0x4 && frame[1] == 0x9 &&
	    !memcmp(&frame[2], "\xb4\x56\xfa", 3)) {
		/* Public action frame with IOPSYS OUI */
		struct cmdu_buff *cmdu;
		uint8_t protocol = frame[5];
		int ret;

		cmdu = agent_gen_tunneled_msg(a, protocol, macaddr,
				framelen, frame);
		if (!cmdu)
			return;

		ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
				cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
				CMDU_PRE_TX);
		if (ret != CMDU_DROP) {
			agent_send_cmdu(a, cmdu);
		} else {
			warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
			__func__, cmdu_get_type(cmdu));
		}

		cmdu_free(cmdu);
	}

	free(frame);
}

static void process_sta_assoc_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	struct blob_attr *data[3];
	static const struct blobmsg_policy data_attr[3] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "raw", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
	};
#ifdef UNAUTHORIZED_STA_IN_ASSOCLIST
	uint8_t macaddr[6] = {0};
	uint8_t bssid[6] = {0};
	struct netif_ap *ap;
	struct sta *s;
#endif

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data,
			blobmsg_data(frm_data), blobmsg_data_len(frm_data));

	if (!data[0] || !data[1] || !data[2])
		return;

	if (agent_get_bsta_by_ifname(a, ifname)) {
		char *framestr;
		uint8_t *frame;
		uint16_t pvid;
		int len;

		framestr = (char *)blobmsg_data(data[1]);

		len = strlen(framestr) / 2;
		frame = calloc(len, sizeof(uint8_t));
		if (!frame)
			return;

		if (!strtob(framestr, len, frame)) {
			free(frame);
			return;
		}

		pvid = wifi_process_pvid_assoc_frame(a, frame, len);
		if (a->pvid != pvid && pvid > 0) {
			agent_fill_8021q_setting(a, pvid, 0);
			agent_apply_traffic_separation(a);
		} else if (a->pvid && pvid == 0) {
			/* traffic separation has been disabled
			 * unset if it was previously set
			 */
			agent_clear_traffic_sep(a);
		}

		free(frame);
	}

#ifdef UNAUTHORIZED_STA_IN_ASSOCLIST
	if (!hwaddr_aton(blobmsg_get_string(data[0]), macaddr))
		return;

	if (!hwaddr_aton(blobmsg_get_string(data[2]), bssid))
		return;

	ap = agent_get_ap(a, bssid);
	if (!ap)
		return;

	s = wifi_add_sta(a, ifname, macaddr);
	if (!s)
		return;

	wifi_topology_notification(a, s, ap,
				   0x01 /* client joined */,
				   0x00 /* success */);
#endif
}

static void process_sta_assoc_frm(struct agent *a,
		char *ifname, struct blob_attr *msg)
{
	trace("%s: ------------>\n", __func__);

	struct json_object *jmsg, *data;
	char *str;

	str = blobmsg_format_json(msg, true);
	if (!str)
		return;

	jmsg = json_tokener_parse(str);
	if (!jmsg)
		goto out_str;

	if (!json_object_is_type(jmsg, json_type_object))
		goto out_json;

	json_object_object_get_ex(jmsg, "data", &data);
	if (!data)
		goto out_json;

	wifi_parse_assoc_frame(a, data);

	/* parse (re-)assoc message */
	wifi_parse_all_frame(a, ifname, data);

out_json:
	json_object_put(jmsg);
out_str:
	free(str);
}


static void process_sta_assoc_failure_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	/* { "wifi.sta": {"ifname":"wl0","event":"assoc-failure","data":{"macaddr":"00:0a:52:06:2e:b9", "status":12}} } */
	struct blob_attr *bdata[2];
	static const struct blobmsg_policy data_attr[2] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "status", .type = BLOBMSG_TYPE_INT32 },
	};
	uint8_t macaddr[6] = {0};
	struct agent_config *cfg = &a->cfg;
	struct policy_cfg *pcfg;
	struct netif_ap *ap;
	struct sta *s;
	int status, status_code = 0;

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), bdata,
			blobmsg_data(frm_data), blobmsg_data_len(frm_data));

	if (!bdata[0] || !bdata[1] || !cfg)
		return;

	pcfg = cfg->pcfg;
	if (!pcfg)
		return;

	status = blobmsg_get_u32(bdata[1]);
	if (status == 12) /* mac list deny */
		status_code = 33; /* QoS AP lacks sufficient bandwidth */

	hwaddr_aton(blobmsg_get_string(bdata[0]), macaddr);

	s = agent_get_sta(a, macaddr);
	if (!s)
		return;

	if (pcfg->report_sta_assocfails) {
		if (timestamp_expired(&a->last_unassoc,
					  ASSOC_TIMEOUT * 1000)) {
			timestamp_update(&a->last_unassoc);
			a->unassoc_cnt = 1;
			send_failed_connection_msg(a, macaddr, status_code, 0);
		} else {
			if (a->unassoc_cnt < pcfg->report_sta_assocfails_rate) {
				a->unassoc_cnt++;
				send_failed_connection_msg(a, macaddr, status_code, 0);
			}
		}
	}

	ap = agent_get_ap_by_ifname(a, ifname);
	if (!ap)
		return;

	wifi_topology_notification(a, s, ap, 0, 0x01);
	wifi_del_sta(a, ifname, macaddr);
}

#ifdef EASYMESH_VENDOR_EXT
static void process_sta_probe_req_evt(struct agent *a,
		char *ifname, struct blob_attr *frm_data)
{
	struct netif_ap *iface = agent_get_ap_by_ifname(a, ifname);
	struct netif_bk *bk_iface = NULL;
	static const struct blobmsg_policy data_attr[2] = {
		[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "rssi", .type = BLOBMSG_TYPE_INT32 },
	};
	struct blob_attr *bdata[2];
	struct cmdu_buff *cmdu;
	char mac_str[18] = {0};
	uint8_t mac[6] = {0};

	if (iface == NULL) {
		bk_iface = agent_get_bsta_by_ifname(a, ifname);
		if (bk_iface == NULL)
			return;
	}

	blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), bdata,
			blobmsg_data(frm_data), blobmsg_data_len(frm_data));

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

	strncpy(mac_str, blobmsg_data(bdata[0]), sizeof(mac_str) - 1);
	hwaddr_aton(mac_str, mac);

	/* send vendor probe request to map controller */
	cmdu = agent_gen_vendor_specific_probe_req(a,
		mac, iface != NULL ? iface->bssid : bk_iface->macaddr,
		rssi_to_rcpi((int32_t)blobmsg_get_u32(bdata[1])));
	if (cmdu) {
		agent_send_cmdu(a, cmdu);
		cmdu_free(cmdu);
	}
}
#endif /* EASYMESH_VENDOR_EXT */

static void wifi_sta_event_handler(void *c, struct blob_attr *msg)
{
	struct agent *a = (struct agent *)c;
	char ifname[16] = {0}, event[24] = {0};
	struct blob_attr *tb[3];
	static const struct blobmsg_policy ev_attr[3] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};

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

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

	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
	strncpy(event, blobmsg_data(tb[1]), sizeof(event) - 1);

	if (!strcmp(event, "connected"))
		process_sta_connected_evt(a, ifname, tb[2]);
	else if (!strcmp(event, "disconnected"))
		process_sta_disconnected_evt(a, ifname, tb[2]);
	else if (!strcmp(event, "deauth"))
		process_sta_deauth_evt(a, ifname, tb[2]);
	else if (!strcmp(event, "btm-resp"))
		process_sta_btm_resp_evt(a, ifname, tb[2]);
	else if (!strcmp(event, "bcn-report"))
		process_sta_bcn_report_evt(a, ifname, tb[2]);
	else if (!strcmp(event, "wds-station-added"))
		process_sta_wds_station_add_evt(a, ifname, tb[2]);
	else if (!strcmp(event, "wds-station-removed"))
		process_sta_wds_station_del_evt(a, ifname, tb[2]);
	else if (!strcmp(event, "action"))
		process_sta_action_evt(a, ifname, tb[2]);
	else if (!strcmp(event, "assoc") || !strcmp(event, "reassoc")) {
		process_sta_assoc_evt(a, ifname, tb[2]);
		process_sta_assoc_frm(a, ifname, msg);
	} else if (!strcmp(event, "assoc-failure"))
		process_sta_assoc_failure_evt(a, ifname, tb[2]);
#ifdef EASYMESH_VENDOR_EXT
	else if (!strcmp(event, "probe-req"))
		process_sta_probe_req_evt(a, ifname, tb[2]);
#endif
}

static void apply_iface_assocation_mode(struct agent *a, const char *ifname)
{
	struct netif_ap *ap = agent_get_ap_by_ifname(a, ifname);

	if (ap) {
		wifi_set_ap_mbo_association_mode(ifname,
						       ap->cfg->disallow_assoc);
	}
}

static int wifi_process_action_frame(struct agent *a, const char *framestr)
{
	trace("%s\n", __func__);

	int len;
	uint8_t *frame;

	if (!framestr)
		return -1;

	len = strlen(framestr);
	len = len / 2; /* octets */
	frame = calloc(len, sizeof(uint8_t));
	if (!frame)
		return -1;

	if (!strtob((char *)framestr, len, frame)) /* cppcheck-suppress cert-EXP05-C */
		goto out_frame;

	if (frame) {
		struct action_frame_container *frame_ptr;
		struct sta *s;

		frame_ptr = (struct action_frame_container *) frame;

		s = agent_get_sta(a, frame_ptr->src);
		if (!s)
			goto out_frame;

		trace("%s: Action Frame belongs to category %d\n",
				__func__, frame_ptr->category_code);

		if (frame_ptr->category_code == WIFI_ACTION_RADIO_MEASUREMENT) {
			if (frame_ptr->u.rrm.action_code == WIFI_RRM_RADIO_MEASUREMENT_REPORT) {
				uint16_t head_len = frame_ptr->u.rrm.tags - frame;
				uint16_t tags_len = len - head_len;
				uint16_t pos = 0;
				int count = 0;

				if (!s->supports_bcnreport)
					s->supports_bcnreport = true;

				/* Count action frame elements (Measurement Reports) */
				while (pos < tags_len) {
					if (frame_ptr->u.rrm.tags[pos] != 0x27) /* EID 39 - Meas Report */
						warn("%s: Measurement report frame contains unsupported" \
							 "Element ID\n", __func__);
					/* +2 for tag_no & tag_len */
					pos += (frame_ptr->u.rrm.tags[pos+1] + 2);
					count++;
				}

				trace("%s: sending beacon metrics response to the cntlr\n",
						__func__);

				send_beacon_metrics_response(a, frame_ptr->src,
						count, frame_ptr->u.rrm.tags, tags_len);

				free(frame);
			} else {
				trace("%s: Action Frame is not a Radio Measurement Response\n",
					__func__);
			}
		}
	}

	return 0;
out_frame:
	free(frame);
	return -1;
}

static int wifi_process_rx_frame(struct agent *a, struct json_object *frameobj)
{
	trace("%s\n", __func__);
	int stype;

	stype = get_frame_type(a, frameobj);
	if (stype == -1)
		return -1;

	if (stype == WIFI_FRAME_ACTION) {
		const char *framestr = json_get_string(frameobj, "action");

		if (framestr) {
			dbg("%s: processing action frame %s\n",
					__func__, framestr);
			return wifi_process_action_frame(a, framestr);
		}
	}

	return -1;
}

static void wifi_iface_event_handler(void *c, struct blob_attr *msg)
{
	struct agent *a = (struct agent *)c;
	const char *ifname, *event;
	struct json_object *jmsg, *data;
	char *str;

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

	str = blobmsg_format_json(msg, true);
	if (!str)
		return;

	jmsg = json_tokener_parse(str);
	if (!jmsg)
		goto out_str;

	if (!json_object_is_type(jmsg, json_type_object))
		goto out_json;

	json_object_object_get_ex(jmsg, "data", &data);
	if (!data)
		goto out_json;

	ifname = json_get_string(jmsg, "ifname");
	if (!ifname)
		goto out_json;

	event = json_get_string(jmsg, "event");
	if (!event)
		goto out_json;

	if (!strcmp(event, "assoc") || !strcmp(event, "reassoc")) {
		wifi_parse_assoc_frame(a, data);

		/* parse (re-)assoc message
		 */
		wifi_parse_all_frame(a, ifname, data);
	} else if (!strcmp(event, "frame-rx")) {
		wifi_process_rx_frame(a, data);
	} else if (!strcmp(event, "action")) {
		/* parse action frame
		 * BTM, WNM, ANQP
		 */
		wifi_parse_all_frame(a, ifname, data);
	} else if (!strcmp(event, "bss-up")) {
		apply_iface_assocation_mode(a, ifname);
	}

out_json:
	json_object_put(jmsg);
out_str:
	free(str);
}

static void wifi_radio_handle_survey_evt(struct wifi_radio_element *r,
		struct blob_attr *tb_data)
{
	dbg("%s --->\n", __func__);

	struct blob_attr *data[3];
	static const struct blobmsg_policy supp_attrs[3] = {
		[0] = { .name = "channel", .type = BLOBMSG_TYPE_INT32 },
		[1] = { .name = "noise", .type = BLOBMSG_TYPE_INT32 },
		[2] = { .name = "utilization", .type = BLOBMSG_TYPE_INT32 },
	};
	uint32_t cur_ch;
	int dBm;
	int i;

	if (!tb_data)
		return;

	if (!r) {
		dbg("%s: missing radio_element !\n", __func__);
		return;
	}

	if (!r->scanlist) {
		dbg("%s: missing scanlist!\n", __func__);
		return;
	}

	blobmsg_parse(supp_attrs, 3, data,
		blobmsg_data(tb_data), blobmsg_data_len(tb_data));

	if (!data[0] || !data[1] || !data[2]) {
		dbg("%s: Wifi event parse error.\n", __func__);
		return;
	}
	cur_ch = (uint8_t)blobmsg_get_u32(data[0]);

	for (i = 0; i < r->scanlist->num_opclass; i++) {
		struct wifi_scanres_opclass_element *op;
		int j;

		op = r->scanlist->opclass_scanlist + i;

		for (j = 0; j < op->num_channels; j++) {
			if (op->channel_scanlist[j].channel == cur_ch) {
				struct wifi_scanres_channel_element *ch;
				uint32_t utilization;

				ch = op->channel_scanlist + j;
				dBm = (int8_t)blobmsg_get_u32(data[1]);
				ch->anpi = noise_to_anpi(dBm);
				utilization = blobmsg_get_u32(data[2]);
				ch->utilization = (uint8_t)((utilization * 255) / 100);
				return;
			}
		}
	}
}

static void wifi_radio_event_handler(void *c, struct blob_attr *msg)
{
	dbg("%s --->\n", __func__);

	struct agent *a = (struct agent *) c;
	struct blob_attr *tb[3];
	static const struct blobmsg_policy ev_attr[6] = {
		[0] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};
	char ifname[18] = {0}, event[64] = {0};

	blobmsg_parse(ev_attr, 3, tb, blob_data(msg), blob_len(msg));
	if (!tb[0] || !tb[1])
		return;

	strncpy(ifname, blobmsg_data(tb[1]), sizeof(ifname) - 1);
	strncpy(event, blobmsg_data(tb[0]), sizeof(event) - 1);

	dbg("%s: Handling %s for %s\n", __func__, event, ifname);

	if (!strcmp(event, "ap-enabled")) {
		/* TODO: do this on event-basis, meaning don't teardown
		 * everything and rebuild everything
		 */
		/* completely re-init data */
		timer_set(&a->init_ifaces_scheduler,
				IFACE_TIMEOUT * 1000);
		if (agent_may_enable_fhs(a))
			/* Enable fronthauls */
			timer_set(&a->enable_fhs_scheduler,
					(IFACE_TIMEOUT + 1) * 1000);

	} else if (!strcmp(event, "ap-disabled")) {
		timer_set(&a->init_ifaces_scheduler,
				IFACE_TIMEOUT * 1000);
	} else if (!strcmp(event, "ap-updated")) {
		timer_set(&a->init_ifaces_scheduler,
				IFACE_TIMEOUT * 1000);
	} else if (!strcmp(event, "radar")) {
		struct wifi_radio_element *re;

		re = agent_get_radio_with_ifname(a, ifname);
		if (!re) {
#if (EASYMESH_VERSION >= 6)
			/* Fallback to 5GHz */
			re = agent_get_radio_by_band(a, BAND_5);
			if (!re)
#endif
				return;
		}

		/* Only handle radar events on 5G radios */
		if (re->band != BAND_5)
			return;

		/* Switch BH immediatelly */
		re->bk.connected = false;

		wifi_mod_bridge(a, &re->bk, false);

		agent_handle_bh_lost(a, &re->bk, true);
		a->connected = agent_is_bsta_connected(a);

		/* Send channel preference report (after BH switch) */
		timer_set(&re->preference_report_timer, 1000);
	} else if (!strcmp(event, "scan_finished")) {
		struct wifi_radio_element *re =
			agent_get_radio_with_ifname(a, ifname);

		if (WARN_ON(!re)) {
			dbg("%s: Current radio_element not found.\n", __func__);
			return;
		}
		re->scan_state = SCAN_DONE;

		handle_wifi_radio_scan_finished(a, re);
	} else if (!strcmp(event, "scan_aborted")) {
		struct wifi_radio_element *re =
			agent_get_radio_with_ifname(a, ifname);

		if (WARN_ON(!re)) {
			dbg("%s: Current radio_element not found.\n", __func__);
			return;
		}
		re->scan_state = SCAN_CANCELED;

		handle_wifi_radio_scan_finished(a, re);
	} else if (!strcmp(event, "survey")) {
		struct wifi_radio_element *re;

		if (!tb[2])
			return;

		re = agent_get_radio_with_ifname(a, ifname);
		if (WARN_ON(!re)) {
			dbg("%s: Current radio_element not found.\n", __func__);
			return;
		}

		wifi_radio_handle_survey_evt(re, tb[2]);
	} else
		dbg("%s: Unhandled wifi event %s!\n", __func__, event);

}

static void wifi_channel_event_handler(void *c, struct blob_attr *msg)
{
	/* struct agent *a = (struct agent *)c; */
	const char *ifdev, *res;
	int ch = 0;
	struct json_object *jmsg;
	char *str;

	agnt_trace(LOG_CHANNEL, "%s: ------------>\n", __func__);
	str = blobmsg_format_json(msg, true);
	if (!str)
		return;

	jmsg = json_tokener_parse(str);
	if (!jmsg)
		goto out_str;

	if (!json_object_is_type(jmsg, json_type_object))
		goto out_json;

	ifdev = json_get_string(jmsg, "radio");
	if (!ifdev)
		goto out_json;

	ch = json_get_int(jmsg, "channel");
	UNUSED(ch);

	res = json_get_string(jmsg, "reason");
	if (!res)
		goto out_json;

	if (!strcmp(res, "radar")) {
		fprintf(stderr, "TODO handle ch change due to radar!\n");
		/* wifi_handle_channel_event(a, ifdev, ch, res); */
	}

out_json:
	json_object_put(jmsg);
out_str:
	free(str);
}

static void netdev_event_handler(void *agent, struct blob_attr *msg)
{
	char ifname[16] = {0}, link[8] = {0};
	struct agent *a = (struct agent *) agent;
	struct blob_attr *tb[2];
	static const struct blobmsg_policy ev_attr[2] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "link", .type = BLOBMSG_TYPE_STRING },
	};
	bool is_ethport = false;
	bool up, down;
	int i;

	if (a->num_ethports <= 0)
		return;

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

	if (!tb[0] || !tb[1]) /* only need ifname and link status */
		return;

	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
	strncpy(link, blobmsg_data(tb[1]), sizeof(link) - 1);

	for (i = 0; i < a->num_ethports; i++) {
		if (!strncmp(ifname, a->ethports[i], sizeof(ifname))) {
			is_ethport = true;
			break;
		}
	}

	if (!is_ethport)
		return;


	up = !strcmp(link, "up");
	down = !strcmp(link, "down");

	UNUSED(down);

	if (up) {
		dbg("%s: Scheduling next ACS in 1 second\n", __func__);
		timer_set(&a->autocfg_dispatcher, 1 * 1000);
		timestamp_update(&a->eth_connect_t);
	}
}

#define map_plugin			IEEE1905_OBJECT_MULTIAP
static void uobj_add_event_handler(void *agent, struct blob_attr *msg)
{
	char path[32] = {0};
	uint32_t id = 0;
	struct agent *a = (struct agent *) agent;
	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 }
	};

	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);
	id = (uint32_t) blobmsg_get_u32(tb[0]);
	dbg("%s: path = [%s] id = [%d] [%u]\n", __func__, path, id, id);
	if (!strncmp(path, map_plugin, strlen(map_plugin)) && a->subscribed == false) {
		/* TODO: how to handle failure? */
		agent_subscribe_for_cmdus(a);
	} else if (!strncmp(path, "wifi.radio.", 11)) {
		char *name = path + 11;
		struct wifi_radio_element *re;

		re = agent_get_radio_by_name(a, name);
		if (!re)
			return;
		re->obj_id = id;
		dbg("%s: wifi.radio.%s object added is [%u]\n", __func__,
		    re->name, re->obj_id);
	} else if (!strncmp(path, "wifi.ap.", 8)) {
		char *ifname = path + 8;
		struct netif_ap *ap;

		ap = agent_get_ap_by_ifname(a, ifname);
		if (!ap)
			return;

		ap->obj_id = id;
		dbg("%s: wifi.ap.%s object added is [%u]\n", __func__,
		    ap->ifname, ap->obj_id);
	}
#if (EASYMESH_VERSION >= 6)
	else if (!strncmp(path, "wifi.apmld.", 11)) {
		char *ifname = path + 11;
		struct netif_mld *apmld;

		apmld = agent_get_apmld_by_ifname(a, ifname);
		if (!apmld)
			return;

		apmld->obj_id = id;
		dbg("%s: wifi.apmld.%s object added is [%u]\n", __func__,
		    apmld->ifname, apmld->obj_id);
	} else if (!strncmp(path, "wifi.bstamld.", 13)) {
		struct bsta_mld *bstamld = a->bstamld;
		char *ifname = path + 11;
		int i;

		if (!bstamld || strcmp(ifname, bstamld->ifname))
			return;


		for (i = 0; i < bstamld->num_affiliated_bsta; i++) {
			struct netif_bk *bsta;

			bsta = agent_get_bsta(a, bstamld->affiliated_bsta[i]);
			if (!bsta)
				continue;

			bsta->obj_id = id;
			dbg("%s: wifi.bstamld.%s update affiliated bsta [%s]"\
			    " object added is [%u]\n", __func__,
			    bstamld->bk.ifname, bsta->ifname, bsta->obj_id);
		}
		bstamld->bk.obj_id = id;
		dbg("%s: wifi.apmld.%s object added is [%u]\n", __func__,
		    bstamld->ifname, bstamld->bk.obj_id);
	}
#endif
	else if (!strncmp(path, "wifi.bsta.", 10)) {
		char *ifname = path + 10;
		struct netif_bk *bk;

		warn("%s: iface = [%s]\n", __func__, ifname);
		bk = agent_get_bsta_by_ifname(a, ifname);
		if (!bk)
			return;
		bk->obj_id = id;
		warn("%s: wifi.bsta.%s object added is [%d]\n", __func__,
		     bk->ifname, bk->obj_id);
	} else if (!strncmp(path, "ieee1905", 9)) {
		int ret;

		a->i1905obj_id = id;
		ret = ubus_lookup_id(a->ubus_ctx, "ieee1905", &id);
		if (ret)
			return;

		ubus_call_object(a->ubus_ctx, id, "info", parse_i1905_info, a);
	} else if (!strncmp(path, "wpa_supplicant.", 15)) {
		char *ifname = path + 15;

		/* re-init blacklist upon supplicant coming back up*/
		backhaul_blacklist_update_ifname(agent, ifname);
	}
#if (EASYMESH_VERSION > 2)
	else if (!strncmp(path, "hostapd.", 8)) {
		/*
		 * QoS rules are volatile and cannot be stored, so they have to be
		 * reapplied after hostapd restarts
		 */
		qos_apply_dscp_rules(agent);

#ifdef USE_LIBDPP
		{
			char *ifname = path + 8;
			struct netif_ap *ap;

			ap = agent_get_ap_by_ifname(a, ifname);
			if (ap && ap->cfg && ap->cfg->advertise_cce)
				dpp_advertise_cce(a, ifname, ap->cfg->advertise_cce);
		}
#endif

	}
#endif
}

static void wifi_bsta_event_scan_failed(struct agent *a, struct netif_bk *bk)
{
	struct wifi_radio_element *re;
	struct wifi_radio_opclass opclass = {};

	trace("[%s] %s called\n", bk->ifname, __func__);

	if (bk->connected)
		return;

	if (!bk->cfg->enabled)
		return;

	if (bk->cfg->band != BAND_5)
		return;

	re = agent_get_radio_with_ifname(a, bk->ifname);
	if (!re)
		return;

	if (!wifi_radio_to_ap(a, re)) {
		trace("[%s] %s !ap_found\n", bk->ifname, __func__);
		return;
	}

	/* Get fresh opclass preferences */
	if (WARN_ON(wifi_opclass_preferences(re->name, &opclass)))
		return;

	if (!wifi_opclass_cac_ongoing(&opclass)) {
		trace("[%s] no cac_ongoing\n", bk->ifname);
		return;
	}

	dbg("[%s] cac ongoing skip CAC %s\n", bk->ifname, re->name);

	WARN_ON(wifi_stop_cac(re->name, 36, BW80));
}

static void wifi_bsta_event_handler(void *agent, struct blob_attr *msg)
{
	char ifname[16] = {0}, event[16] = {0};
	struct agent *a = (struct agent *) agent;
	struct blob_attr *tb[3];
	static const struct blobmsg_policy ev_attr[3] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};
	struct netif_bk *bk;

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

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

	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
	strncpy(event, blobmsg_data(tb[1]), sizeof(event) - 1);

	bk = agent_get_bsta_by_ifname(a, ifname);
	if (!bk)
		return;

	/* if scan event and eth backhaul is used - disconnect to prevent loop */
	if ((!strcmp(event, "scan-started") || !strcmp(event, "scan-failed")
		 || !strcmp(event, "connected")) && agent_is_backhaul_type_eth())
		wifi_bsta_disconnect(bk, 0);

	/* if receive scan event from non-active link,
	 * while we are not trying to progress or backhaul steer
	 */
	if ((!strcmp(event, "scan-started") || !strcmp(event, "scan-failed"))
			&& ((!bk->connected && !a->dynbh_upgrade_ongoing && a->connected))
			&& !agent_bsta_steer_is_active(bk))
		wifi_bsta_disconnect(bk, 0);

	if (!strcmp(event, "scan-failed")) {
		wifi_bsta_event_scan_failed(a, bk);
		return;
	}

	if (!strcmp(event, "connected")) {
		uint8_t bssid[6] = {0};
		char bssid_str[18] = {0};
		static const struct blobmsg_policy data_attr[] = {
			[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "freq", .type = BLOBMSG_TYPE_INT32 },
		};
		struct blob_attr *data[ARRAY_SIZE(data_attr)];
		struct wifi_radio_element *re = NULL;
		uint32_t freq = 0;
		int remaining;

		if (!tb[2])
			return;

		/* don't handle event if bk is not enabled */
		if (!bk->cfg->enabled) {
			dbg("%s: bsta is not enabled - higher prio is "\
					"active, disconnect\n",
					__func__);
			wifi_bsta_disconnect(bk, 0);
			return;
		}


		blobmsg_parse(data_attr, ARRAY_SIZE(data_attr), data, blobmsg_data(tb[2]),
				blobmsg_data_len(tb[2]));
		if (!data[0])
			return;

		strncpy(bssid_str, blobmsg_data(data[0]),
				sizeof(bssid_str) - 1);

		hwaddr_aton(bssid_str, bssid);

		if (data[1])
			freq = blobmsg_get_u32(data[1]);

		wifi_bsta_connect(a, bk, bssid, freq);
		agent_manage_bsta(a, bk);

		timer_del(&a->bh_lost_timer);
		timer_del(&a->bh_reconf_timer);
#ifdef AGENT_ISLAND_PREVENTION
		if (a->cfg.island_prevention) {
			timer_del(&a->sta_disconnect_timer);
			timer_del(&a->fh_disable_timer);
		}
#endif /* AGENT_ISLAND_PREVENTION */

		/* send any pending failed backhaul steer responses */
		list_for_each_entry(re, &a->radiolist, list) {
			if (!re->has_bsta)
				continue;

			if (re->bk.bsta_steer.expired == false ||
			    re->bk.bsta_steer.mid == 0)
				continue;

			/* should always be true from here */
			if (bk->bsta_steer.trigger == BK_STEER_MANDATE) {
				re->bk.bsta_steer.reason = ERR_REASON_BH_STEERING_ASSOC_REJ;
				send_backhaul_sta_steer_response(a, &re->bk, bk->ifname);
			}
			agent_bsta_steer_clear(&re->bk);
		}

		if (agent_bsta_steer_is_active(bk)) {
			/* steer was successful */

			set_value_by_string("mapagent", "agent",
					    "backhaul_override", "1",
					    UCI_TYPE_STRING);
			a->cfg.backhaul_override = 1;

			dynbh_bsta_use_link(a, bk->ifname);
			re = NULL;
			list_for_each_entry(re, &a->radiolist, list) {
				if (!re->has_bsta)
					continue;

				if (strncmp(re->bk.ifname, bk->ifname, IFNAMSIZ) /* && !agent_bsta_steer_is_active(b) */)
					config_disable_bsta(re->bk.cfg);
			}
			agent_bsta_steer_handler(a, bk, bssid);
			return;
		}

		remaining = timer_remaining_ms(&a->onboarding_scheduler);
		if (remaining == -1 && bk->connected) {
			dynbh_bsta_disable_lower_priority(a, ifname);
			agent_config_reload(a);
			timer_set(&a->disable_unconnected_bstas_scheduler,
					  15 * 1000);
		}
	} else if (!strcmp(event, "disconnected")) {
		if (bk->connected) {
			dynbh_update_bk_disconnect_t(a, bk);
			bk->connected = false;

#if (EASYMESH_VERSION >= 6)
			if (bk->is_affiliated_sta && !a->cfg.physical_mld && a->bstamld) {
				if (a->bstamld->setup_link == bk) {
					agnt_trace(LOG_MLO, "%s: Setup_link %s disconnected - resetting setup_link\n",
					    	   __func__, bk->ifname);
					a->bstamld->setup_link = NULL;
				}
			}
#endif
			/* Handle link loss if this was supposed to be an active bsta */
			agent_handle_bh_lost(a, bk, false);
		}
		wifi_mod_bridge(a, bk, false);
		a->connected = agent_is_bsta_connected(a);
	} else if (!strcmp(event, "wps-pbc-active")) {
		bk->wps_active = true;
	} else if (!strcmp(event, "wps-timeout")) {
		bk->wps_active = false;
	} else if (!strcmp(event, "csa")) {
		struct wifi_bsta_status status = {};
		uint32_t cac_time;

		dbg("[%s] connect csa\n", ifname);

		if (a->cfg.ap_follow_sta_dfs) {
			struct wifi_radio_element *re = agent_get_radio_with_ifname(a, ifname);

			if (re) {
				/* Get channel/bandwidth from bsta */
				if (!wifi_bsta_status(ifname, &status)) {
					re->current_channel = status.channel;
					re->current_bandwidth = wifi_bw_to_bw(status.bandwidth);
					re->current_opclass =
						wifi_opclass_find_id_from_channel(
								&re->opclass,
								re->current_channel,
								re->current_bandwidth);
				}

				if (wifi_radio_cac_required(a, re, re->current_channel,
							    re->current_bandwidth, &cac_time)) {
					/* Simple disconnect, new connect will do required actions */
					wifi_bsta_disconnect(bk, 0);
					dynbh_bsta_scan_on_enabled(a);
				}
			}
		}
	}
}

#if (EASYMESH_VERSION >= 6)
static void wifi_mld_event_handler(void *agent, struct blob_attr *msg)
{
	char ifname[16] = {0}, event[16] = {0};
	struct agent *a = (struct agent *) agent;
	struct blob_attr *tb[3];
	static const struct blobmsg_policy ev_attr[3] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};
	bool add, del;

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

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

	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
	strncpy(event, blobmsg_data(tb[1]), sizeof(event) - 1);

	add = !strcmp(event, "mlo-link-added");
	del = !strcmp(event, "mlo-link-remove");

	if (add) {
		/* more actions */
		timer_set(&a->init_ifaces_scheduler,
			  IFACE_TIMEOUT * 1000);
	} else if (del) {
		/* more actions */
		timer_set(&a->init_ifaces_scheduler,
			  IFACE_TIMEOUT * 1000);
	}
}

#endif

#define ONBOARDING_TIMER 15
static void wifi_wps_creds_event_handler(void *agent, struct blob_attr *msg)
{
	char encryption[32] = {0}, ifname[16] = {0}, ssid[33] = {0},
			key[65] = {0};
	static const struct blobmsg_policy ap_attr[5] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "encryption", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "ssid", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "key", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "vlan_id", .type = BLOBMSG_TYPE_INT32 },
	};
	//uint16_t vlan = 0;
	struct agent *a = (struct agent *) agent;
	struct blob_attr *tb[5];
	struct wifi_radio_element *re = NULL;
	struct netif_bk *bk;
	int timeout = 0;

	blobmsg_parse(ap_attr, 5, tb, blob_data(msg), blob_len(msg));

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

	strncpy(ifname,	blobmsg_data(tb[0]), sizeof(ifname) - 1);
	strncpy(encryption, blobmsg_data(tb[1]), sizeof(encryption) - 1);
	strncpy(ssid, blobmsg_data(tb[2]), sizeof(ssid) - 1);
	strncpy(key, blobmsg_data(tb[3]), sizeof(key) - 1);
	bk = agent_get_bsta_by_ifname(a, ifname);
	if (!bk)
		return;

	bk->wps_active = false;

	/* don't wait for other bands if creds are synced via AP-Autoconfig */
	if (!a->cfg.eth_onboards_wifi_bhs) {
		list_for_each_entry(re, &a->radiolist, list) {
			if (!re->has_bsta)
				continue;

			if (re->bk.wps_active) {
				int remaining = timer_remaining_ms(&a->onboarding_scheduler);

				/* allow up to extra time for the other
				 * band(s) to complete
				 */
				timeout = ONBOARDING_TIMER - (remaining == -1 ? 0 : remaining);
			}
		}
	}

	wifi_apply_iface_cfg(bk->ifname, encryption, ssid, key);
	bk->cfg->onboarded = true;

	//uci_reload_services("wireless"); /* move to platform script? */

	re = agent_get_radio_with_ifname(a, ifname);
	if (!re)
		return;

	timer_set(&a->onboarding_scheduler, timeout * 1000);

#if 0 /* Not used at present */
	vlan = 0;
	if (tb[4])
		vlan = (uint16_t) blobmsg_get_u16(tb[4]);

	if (vlan && bk->agent) {
		if (!(a->cfg.pcfg)) {
			a->cfg.pcfg = (struct policy_cfg *)calloc(1, sizeof(struct policy_cfg));
			if (!(a->cfg.pcfg)) {
				err("%s: memory allocation failed\n",
				    __func__);
				return;
			}
		}
		bk->agent->cfg.pcfg->pvid = vlan;

		agent_apply_traffic_separation(bk->agent);
	}
#endif
}

void ieee1905_cmdu_event_handler(void *c, struct blob_attr *msg)
{
	char src[18] = { 0 }, src_origin[18] = { 0 };
	struct agent *a = (struct agent *)c;
	uint8_t srcmac[6], origin[6];
	char in_ifname[16] = {0};
	struct blob_attr *tb[6];
	char *cmdustr = NULL;
	uint8_t *cmdu = NULL;
	int cmdu_strlen = 0;
	uint16_t mid = 0;
	int cmdulen = 0;
	uint16_t type;
	int ret;
	static const struct blobmsg_policy cmdu_attrs[6] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_INT16 },
		[1] = { .name = "mid", .type = BLOBMSG_TYPE_INT16 },
		[2] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[3] = { .name = "source", .type = BLOBMSG_TYPE_STRING },
		[4] = { .name = "origin", .type = BLOBMSG_TYPE_STRING },
		[5] = { .name = "cmdu", .type = BLOBMSG_TYPE_STRING },
	};

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

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

	if (!tb[0] || !tb[1]) {
		trace("%s: no type or mid.\n", __func__);
		return;
	}

	type = (uint16_t)blobmsg_get_u16(tb[0]);
	if (!is_cmdu_for_us(a, type)) {
		trace("%s: type: 0x%x is_cmdu_for_us false.\n", __func__, type);
		return;
	}

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

	if (tb[2])
		strncpy(in_ifname, blobmsg_data(tb[2]), 15);

	if (tb[3]) {
		strncpy(src, blobmsg_data(tb[3]), 17);
		hwaddr_aton(src, srcmac);
	}

	if (tb[4]) {
		strncpy(src_origin, blobmsg_data(tb[4]), 17);
		hwaddr_aton(src_origin, origin);
	}

	if (tb[5]) {
		cmdu_strlen = blobmsg_data_len(tb[5]);
		cmdulen = (cmdu_strlen - 1) / 2;

		cmdustr = calloc(cmdu_strlen + 1, sizeof(char));
		if (!cmdustr)
			return;

		strncpy(cmdustr, blobmsg_data(tb[5]), cmdu_strlen);
		cmdu = calloc(cmdulen, sizeof(uint8_t));
		if (!cmdu) {
			free(cmdustr);
			return;
		}

		strtob(cmdustr, cmdulen, cmdu);
		free(cmdustr);
	}

	ret = CMDU_HOOK(a, type, mid,
			srcmac,
			cmdu, cmdulen,
			CMDU_RX);
	if (ret == CMDU_DROP) {
		warn("[%s]: cmdu 0x%04X dropped by extension at Rx\n", __func__, type);
		goto out;
	}

	agent_handle_map_event(a, type, mid, in_ifname, srcmac, origin, cmdu + 8, cmdulen - 8);
out:
	free(cmdu);
}

void agent_event_handler(struct ubus_context *ctx,
		struct ubus_event_handler *ev,
		const char *type, struct blob_attr *msg)
{
	int i;
	char *str;
	struct agent *a = container_of(ev, struct agent, evh);
	struct wifi_ev_handler {
		const char *ev_type;
		void (*handler)(void *ctx, struct blob_attr *ev_data);
	} evs[] = {
		{ "wifi.sta", wifi_sta_event_handler },
		{ "wifi.channel", wifi_channel_event_handler },
		{ "wifi.radio", wifi_radio_event_handler },
		{ "wifi.iface", wifi_iface_event_handler },
		{ "wps_credentials", wifi_wps_creds_event_handler },
		{ "wifi.bsta", wifi_bsta_event_handler },
		{ "network.device", netdev_event_handler },
		{ "ubus.object.add", uobj_add_event_handler },
		{ "wifi.radio", wifi_chan_change_event_handler },
		{ "wifi.radio", wifi_cac_event_handler},
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP
		{ "wifi.sta", dpp_sta_event_handler },
#endif
#endif
#if (EASYMESH_VERSION >= 6)
		{ "wifi.mld", wifi_mld_event_handler },
#endif
	};

	str = blobmsg_format_json(msg, true);
	if (!str)
		return;

	info("[ &agent = %p ] Received [event = %s]  [val = %s]\n",
			a, type, str);

	for (i = 0; i < ARRAY_SIZE(evs); i++) {
		if (!strcmp(type, evs[i].ev_type))
			evs[i].handler(a, msg);
	}

	free(str);
}
