/*
 * assoc_ctrl.c - implements association control logic and util functions
 *
 * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved.
 *
 */
#include <json-c/json.h>
#include <libubox/blobmsg_json.h>

#include <easymesh.h>

#include "agent.h"
#include "agent_map.h"
#include "agent_cmdu.h"
#include "agent_tlv.h"
#include "assoc_ctrl.h"
#include "config.h"
#include "timeutils.h"
#include "wifi.h"

#include "utils/debug.h"
#include "utils/utils.h"

static void assoc_ctrl_remove_client(struct json_object *arr, uint8_t *macaddr)
{
	int len, i;

	len = json_object_array_length(arr);
	for (i = 0; i < len; i++) {
		struct json_object *t;
		const char *p;
		uint8_t mac[6];

		t = json_object_array_get_idx(arr, i);
		if (!t) {
			agnt_warn(LOG_STEER, "%s: couldn't get entry:%d from restriction array\n",
			     __func__, i);
			continue;
		}

		p = json_get_string(t, "macaddr");
		if (!p) {
			agnt_warn(LOG_STEER, "%s: couldn't get macaddr from entry:%d\n",
			     __func__, i);
			continue;
		}

		if (!hwaddr_aton(p, mac)) {
			agnt_warn(LOG_STEER, "%s: couldn't convert macaddr from entry:%d\n",
			     __func__, i);
			continue;
		}

		if (!memcmp(mac, macaddr, 6)) {
			if (json_object_array_del_idx(arr, i, 1) < 0) {
				agnt_warn(LOG_STEER, "%s: couldn't remove entry:%d from restriction array\n",
					 __func__, i);
				return;
			}
			break;
		}
	}
}

static void assoc_ctrl_write_client(struct json_object *arr, uint8_t *macaddr,
			uint16_t validity, uint8_t mode)
{
	struct json_object *val, *new;
	char macstr[18] = {0};
	char time_str[64] = {0};

	if (!arr)
		return;

	if (!json_object_is_type(arr, json_type_array)) {
		agnt_warn(LOG_STEER, "%s: clients is not an array\n", __func__);
		return;
	}

	/* remove client if it already exists */
	assoc_ctrl_remove_client(arr, macaddr);

	if (mode == ASSOC_CTRL_UNBLOCK) {
		/* client is unblocked, no need to add */
		return;
	}

	new = json_object_new_object();
	if (!new)
		return;

	/* macaddr */
	if (!hwaddr_ntoa(macaddr, macstr)) {
		agnt_warn(LOG_STEER, "%s: couldn't convert macstr\n", __func__);
		return;
	}

	snprintf(macstr, sizeof(macstr), MACFMT, MAC2STR(macaddr)); /* Flawfinder: ignore */
	val = json_object_new_string(macstr);
	if (!val)
		return;
	json_object_object_add(new, "macaddr", val);

	/* start_time */
	time_to_timestamp(NULL, time_str);
	val = json_object_new_string(time_str);
	if (!val)
		return;
	json_object_object_add(new, "start_time", val);

	/* duration */
	val = json_object_new_int(validity);
	if (!val)
		return;
	json_object_object_add(new, "duration", val);

	/* mode */
	val = json_object_new_int(mode);
	if (!val)
		return;
	json_object_object_add(new, "mode", val);

	json_object_array_add(arr, new);
}

/* generate a restricted client entry if it does not exist */
static int assoc_ctrl_update_restrict_file(uint8_t *bssid, uint8_t *macaddr,
			uint16_t validity, uint8_t mode)
{
	struct json_object *restr_json;
	struct json_object *bssids, *bssid_obj = NULL;
	struct json_object *clients;
	json_bool ret;
	int len, i;

	agnt_dbg(LOG_STEER, "%s: file:%s\n", __func__, ASSOC_CTRL_FILE);

	/* Get the json object from the file */
	restr_json = json_object_from_file(ASSOC_CTRL_FILE);
	if (!restr_json) {
		agnt_dbg(LOG_STEER, "%s: failed to read json:%s, error:%s. "\
		     "Try to generate new\n", __func__,
		     ASSOC_CTRL_FILE, json_util_get_last_err());

		restr_json = json_object_new_object();
		if (!restr_json) {
			agnt_warn(LOG_STEER, "%s: failed to create json obj, error:%s\n",
			     __func__, json_util_get_last_err());
			goto out;
		}
	}

	/* Get the bssids array */
	ret = json_object_object_get_ex(restr_json, "bssids", &bssids);
	if (!ret) {
		/* Create bssids array if not found */
		bssids = json_object_new_array();
		if (!bssids) {
			agnt_warn(LOG_STEER, "%s: failed to add bssid array, error:%s\n",
			     __func__, json_util_get_last_err());
			goto out_bssid;
		}
		json_object_object_add(restr_json, "bssids", bssids);
	} else if (!json_object_is_type(bssids, json_type_array)) {
		agnt_warn(LOG_STEER, "%s: file: %s has wrong format\n",
		     __func__, ASSOC_CTRL_FILE);
		goto out_bssid;
	}

	/* Check if bssid already exists in bssid array */
	len = json_object_array_length(bssids);
	for (i = 0; i < len; i++) {
		struct json_object *t;
		uint8_t t_bssid[6];
		const char *p;

		t = json_object_array_get_idx(bssids, i);
		if (!t) {
			agnt_warn(LOG_STEER, "%s: couldn't get entry:%d from bssid array\n", __func__, i);
			continue;
		}

		p = json_get_string(t, "bssid");
		if (!p) {
			agnt_warn(LOG_STEER, "%s: couldn't get bssid from entry:%d\n", __func__, i);
			continue;
		}

		if (!hwaddr_aton(p, t_bssid)) {
			agnt_warn(LOG_STEER, "%s: couldn't convert bssid from entry:%d\n", __func__, i);
			continue;
		}

		if (!memcmp(bssid, t_bssid, ETH_ALEN)) {
			/* bssid already exists in file */
			bssid_obj = t;
			break;
		}
	}

	/* Create new bssid object if not found */
	if (!bssid_obj) {
		char bssidstr[18] = {0};
		struct json_object *val;

		bssid_obj = json_object_new_object();
		if (WARN_ON(!bssid_obj))
			goto out_bssid;

		/* bssid */
		hwaddr_ntoa(bssid, bssidstr);
		val = json_object_new_string(bssidstr);
		if (!val) {
			json_object_put(bssid_obj);
			goto out_bssid;
		}
		json_object_object_add(bssid_obj, "bssid", val);

		/* Add bssid object to bssids array */
		json_object_array_add(bssids, bssid_obj);
	}

	/* Get the clients array */
	ret = json_object_object_get_ex(bssid_obj, "clients", &clients);
	if (!ret) {
		/* Create client array if not found */
		clients = json_object_new_array();
		if (!clients) {
			json_object_put(bssid_obj);
			goto out_bssid;
		}
		json_object_object_add(bssid_obj, "clients", clients);
	} else {
		if (!json_object_is_type(clients, json_type_array)) {
			agnt_warn(LOG_STEER, "%s: file:%s is not expected format\n", __func__, ASSOC_CTRL_FILE);
			json_object_put(bssid_obj);
			goto out_bssid;
		}
	}

	/* Add (or remove) the client entry */
	assoc_ctrl_write_client(clients, macaddr, validity, mode);

	/* Update restricted clients file */
	json_object_to_file(ASSOC_CTRL_FILE, restr_json);

out_bssid:
	json_object_put(restr_json);
out:
	return 0;
}

struct rsta {
	uint8_t macaddr[6];
	uint8_t mode;
	uint16_t validity;
};

static int assoc_ctrl_read_restrictions(struct agent *a, uint8_t *bssid,
			struct rsta *fr, int *num_fr)
{
	struct blob_buf bssids = { 0 };
	struct blob_attr *b;
	static const struct blobmsg_policy attr[] = {
		[0] = { .name = "bssids", .type = BLOBMSG_TYPE_ARRAY },
	};
	struct blob_attr *tb[ARRAY_SIZE(attr)];
	int rem;
	int ret = 0;

	blob_buf_init(&bssids, 0);

	if (!blobmsg_add_json_from_file(&bssids, ASSOC_CTRL_FILE)) {
		agnt_warn(LOG_STEER, "Failed to parse %s\n", ASSOC_CTRL_FILE);
		ret = -1;
		goto out;
	}

	blobmsg_parse(attr, ARRAY_SIZE(attr), tb, blob_data(bssids.head), blob_len(bssids.head));

	if (!tb[0])
		goto out;

	blobmsg_for_each_attr(b, tb[0], rem) {
		static const struct blobmsg_policy bssid_attr[2] = {
			[0] = { .name = "bssid", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "clients", .type = BLOBMSG_TYPE_ARRAY },
		};
		struct blob_attr *tb1[ARRAY_SIZE(bssid_attr)];
		char bssid_str[18] = {0};
		uint8_t bssid_mac[6] = {0};
		struct blob_attr *client;
		int rem1;

		blobmsg_parse(bssid_attr, ARRAY_SIZE(bssid_attr), tb1, blobmsg_data(b), blob_len(b));
		if (!tb1[0] || !tb1[1])
			continue;

		strncpy(bssid_str, blobmsg_data(tb1[0]), sizeof(bssid_str) - 1);
		if (!hwaddr_aton(bssid_str, bssid_mac)) {
			agnt_warn(LOG_STEER, "Failed to convert macaddr %s\n", bssid_str);
			continue;
		}

		if (memcmp(bssid, bssid_mac, ETH_ALEN))
			continue;

		blobmsg_for_each_attr(client, tb1[1], rem1) {
			static const struct blobmsg_policy client_attr[4] = {
				[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
				[1] = { .name = "start_time", .type = BLOBMSG_TYPE_STRING },
				[2] = { .name = "duration", .type = BLOBMSG_TYPE_INT32 },
				[3] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 },
			};
			struct blob_attr *tb2[ARRAY_SIZE(client_attr)];
			char mac_str[18] = {0};
			uint8_t macaddr[6] = {0};
			int32_t duration, elapsed;
			time_t start, now = time(NULL);

			blobmsg_parse(client_attr, ARRAY_SIZE(client_attr), tb2, blobmsg_data(client), blob_len(client));
			if (!tb2[0] || !tb2[1] || !tb2[2] || !tb2[3])
				continue;

			strncpy(mac_str, blobmsg_data(tb2[0]), sizeof(mac_str) - 1);
			if (!hwaddr_aton(mac_str, macaddr)) {
				agnt_warn(LOG_STEER, "Failed to convert macaddr %s\n", mac_str);
				continue;
			}

			memcpy(fr[*num_fr].macaddr, macaddr, ETH_ALEN);
			fr[*num_fr].mode = blobmsg_get_u32(tb2[3]);
			start = timestamp_to_time(blobmsg_get_string(tb2[1]));
			duration = blobmsg_get_u32(tb2[2]);
			elapsed = (int32_t)difftime(now, start);
			if (elapsed < 0 || elapsed > duration) {
				fr[*num_fr].validity = 0;
			} else {
				fr[*num_fr].validity = duration - elapsed;
			}

			(*num_fr)++;
		}
	}

out:
	blob_buf_free(&bssids);

	return ret;
}

static void assoc_ctrl_clean_expired(struct agent *a, uint8_t *bssid,
			struct rsta *fr, int *num_fr)
{
	int i;

	for (i = 0; i < *num_fr; i++) {
		if ((fr[i].mode == ASSOC_CTRL_TIMED_BLOCK || fr[i].mode == ASSOC_CTRL_BLOCK)
				&& fr[i].validity == 0) {
			int j;

			/* remove from file */
			assoc_ctrl_update_restrict_file(bssid, fr[i].macaddr,
					0, ASSOC_CTRL_UNBLOCK);
			/* remove fr[i] from fr */
			for (j = i; j < *num_fr - 1; j++)
				memcpy(&fr[j], &fr[j + 1], sizeof(struct rsta));
			memset(&fr[*num_fr - 1], 0, sizeof(struct rsta));
			/* decrease num_fr */
			(*num_fr)--;
			/* decrease i */
			i--;
		}
	}
}

static void agent_disconnect_stas(struct agent *a, int num_sta,
		uint8_t sta_macs[][6], struct netif_ap *ap)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	int i;

	if (!sta_macs)
		return;

	for (i = 0; i < num_sta; i++) {
		struct netif_ap *s_ap = agent_get_ap_with_sta(a, sta_macs[i]);

		if (!s_ap || s_ap != ap)
			continue;

		agnt_dbg(LOG_STEER, "%s: Disconnect STA: " MACFMT " from %s\n",
		    __func__, MAC2STR(sta_macs[i]), ap->ifname);
		wifi_disconnect_sta(ap->ifname, sta_macs[i],
				WIFI_REASON_AP_INITIATED);
	}
}

static int assoc_ctrl_restrict_sta(struct agent *a, uint32_t num_sta,
		uint8_t sta_list[][6], struct netif_ap *ap, uint8_t mode)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	int ret = 0;
	int i;

	if (!sta_list || !num_sta)
		return -1;

	for (i = 0; i < num_sta; i++)
		ret |= WARN_ON(wifi_restrict_sta(ap, sta_list[i], mode != ASSOC_CTRL_UNBLOCK));

	if (mode == ASSOC_CTRL_TIMED_BLOCK || mode == ASSOC_CTRL_INDEF_BLOCK)
		/* Disassociate any associated STA(s) specified in the req */
		agent_disconnect_stas(a, num_sta, sta_list, ap);

	return ret;
}

int assoc_ctrl_reapply(struct netif_ap *ap)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	uint8_t client_sta[ASSOC_CTRL_MAX_STA][6] = {0};
	struct restrict_sta_entry *s = NULL;
	int ret = 0;
	int i, num_sta = 0;

	list_for_each_entry(s, &ap->restrict_stalist, list) {
		memcpy(client_sta[num_sta++], s->macaddr, 6);
	}

	for (i = 0; i < num_sta; i++)
		ret |= wifi_restrict_sta(ap, client_sta[i], s->mode != ASSOC_CTRL_UNBLOCK);

	return ret;
}

static void wifi_sta_restrict_timeout_cb(atimer_t *t)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	struct restrict_sta_entry *s =
		container_of(t, struct restrict_sta_entry, restrict_timer);
	struct netif_ap *ap;

	ap = agent_get_ap(s->agent, s->bssid);
	if (!ap) {
		agnt_trace(LOG_STEER, "%s: Error BSSID not present\n", __func__);
		return;
	}

	/* Unblock STA */
	wifi_restrict_sta(ap, s->macaddr, false);
	assoc_ctrl_update_restrict_file(ap->bssid, s->macaddr, 0, ASSOC_CTRL_UNBLOCK);
	list_del(&s->list);
	free(s);
}

static struct restrict_sta_entry *find_restricted_sta(struct netif_ap *ap, uint8_t *mac)
{
	struct restrict_sta_entry *s = NULL;

	list_for_each_entry(s, &ap->restrict_stalist, list) {
		if (!memcmp(s->macaddr, mac, 6))
			return s;
	}
	return NULL;
}

int assoc_ctrl_add_sta(struct agent *a, struct netif_ap *ap,
		uint32_t num_fr, struct rsta fr[],
		bool update_config)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	int i = 0;

	if (!fr)
		return -1;

	for (i = 0; i < num_fr; i++) {
		struct restrict_sta_entry *s;

		s = find_restricted_sta(ap, fr[i].macaddr);
		if (!s) {
			/* Add new entry to restricted list */
			s = calloc(1, sizeof(struct restrict_sta_entry));
			if (!s)
				return -1;

			memcpy(s->macaddr, fr[i].macaddr, 6);
			memcpy(s->bssid, ap->bssid, 6);
			s->agent = a;
			list_add_tail(&s->list, &ap->restrict_stalist);

			timer_init(&s->restrict_timer, wifi_sta_restrict_timeout_cb);
		}

		s->mode = fr[i].mode;

		/* Update validity timer */
		if ((s->mode == ASSOC_CTRL_TIMED_BLOCK || s->mode == ASSOC_CTRL_BLOCK)) {
			if (fr[i].validity > 0)
				timer_set(&s->restrict_timer, fr[i].validity * 1000);
			else {
				/* error */
				continue;
			}
		} else if (s->mode == ASSOC_CTRL_INDEF_BLOCK) {
			timer_del(&s->restrict_timer);
		}

		/* Update restriction file */
		if (update_config)
			assoc_ctrl_update_restrict_file(ap->bssid,
					fr[i].macaddr, fr[i].validity, fr[i].mode);
	}

	return 0;
}

int assoc_ctrl_block_sta(struct agent *a, struct netif_ap *ap,
		uint16_t validity_period, uint8_t mode,
		uint32_t num_sta, uint8_t sta_list[][6])
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	struct rsta fr[ASSOC_CTRL_MAX_STA];
	int num_fr = 0;
	int i, ret = 0;

	if (num_sta > ASSOC_CTRL_MAX_STA) {
		agnt_err(LOG_STEER, "%s: Invalid number of STAs\n", __func__);
		return -1;
	}

	for (i = 0; i < num_sta; i++) {
		memcpy(fr[num_fr].macaddr, sta_list[i], ETH_ALEN);
		fr[num_fr].mode = mode;
		fr[num_fr].validity = validity_period;
		num_fr++;
	}

	/* Add STAs to runtime & file lists */
	ret = assoc_ctrl_add_sta(a, ap, num_fr, fr, true);
	if (!ret)
		/* Block STAs in WiFi */
		ret = assoc_ctrl_restrict_sta(a, num_sta, sta_list, ap, mode);

	return ret;
}

int assoc_ctrl_del_sta(struct agent *a, struct netif_ap *ap,
		uint32_t num_sta, uint8_t sta_list[][6], bool update_config)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	int i = 0;

	if (sta_list == NULL)
		return -1;

	for (i = 0; i < num_sta; i++) {
		struct restrict_sta_entry *s = NULL, *tmp;

		/* Stop validity timer and remove entry from runtime list */
		list_for_each_entry_safe(s, tmp, &ap->restrict_stalist, list) {
			if (!memcmp(s->macaddr, sta_list[i], 6)) {
				timer_del(&s->restrict_timer);
				list_del(&s->list);
				free(s);
			}
		}

		/* Remove STA from restriction file */
		if (update_config) {
			assoc_ctrl_update_restrict_file(ap->bssid,
					sta_list[i], 0, ASSOC_CTRL_UNBLOCK);
		}
	}

	return 0;
}

int assoc_ctrl_unblock_sta(struct agent *a, struct netif_ap *ap,
		uint32_t num_sta, uint8_t sta_list[][6])
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);
	int ret = 0;

	/* Remove STAs from runtime and file lists */
	ret = assoc_ctrl_del_sta(a, ap, num_sta, sta_list, true);
	if (!ret)
		/* Unblock STAs in WiFi */
		ret = assoc_ctrl_restrict_sta(a, num_sta, sta_list, ap, ASSOC_CTRL_UNBLOCK);

	return ret;
}

/* Sync runtime list from file list only - do not apply restriction */
int assoc_ctrl_sync_from_file(struct agent *a, struct netif_ap *ap)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	struct rsta fr[ASSOC_CTRL_MAX_STA];
	int num_fr = 0;
	struct restrict_sta_entry *s = NULL;
	int ret = 0;

	if (!ap || !ap->cfg) {
		agnt_trace(LOG_STEER, "%s: ap has NULL cfg\n", __func__);
		return -EINVAL;
	}

	/* Read STA restrictions from file list */
	ret = assoc_ctrl_read_restrictions(a, ap->bssid, fr, &num_fr);
	if (ret)
		return ret;

	/* Remove all outdated entries from file */
	assoc_ctrl_clean_expired(a, ap->bssid, fr, &num_fr);

	/* 1. Add/Update STAs: file list -> runtime list */
	if (num_fr > 0)
		ret |= assoc_ctrl_add_sta(a, ap, num_fr, fr, false);

	/* 2. Remove STAs from runtime if not present in file */
	list_for_each_entry(s, &ap->restrict_stalist, list) {
		bool found = false;
		int i = 0;

		for (i = 0; i < num_fr; i++) {
			if (!memcmp(fr[i].macaddr, s->macaddr, 6)) {
				found = true;
				break;
			}
		}

		if (!found) {
			uint8_t sta_list[1][6];

			/* Remove STA from runtime list & unlock in WiFi */
			memcpy(sta_list[0], s->macaddr, 6);
			ret |= assoc_ctrl_unblock_sta(a, ap, 1, sta_list);

		}
	}

	return ret;
}

/* Blocks all stas from the ap->restrict_stalist in WiFi */
int assoc_ctrl_apply_restriction(struct agent *a, struct netif_ap *ap)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	uint8_t wifi_res_stas[ASSOC_CTRL_MAX_STA * 6] = {0};
	int num_wifi_res_stas = ASSOC_CTRL_MAX_STA;
	struct restrict_sta_entry *s = NULL;
	int ret = 0;

	if (list_empty(&ap->restrict_stalist)) {
		agnt_dbg(LOG_STEER, "%s: restrict STA list empty for %s\n", __func__, ap->ifname);
		return 0;
	}

	/* Get the list of STAs currently blocked in WiFi */
	ret = wifi_get_restricted_stas(ap, wifi_res_stas, &num_wifi_res_stas);
	if (ret) {
		agnt_err(LOG_STEER, "%s: Failed to get restricted STAs\n", __func__);
		return -1;
	}

	/* Invoke block_sta for STAs on runtime list not blocked in Wi-Fi */
	list_for_each_entry(s, &ap->restrict_stalist, list) {
		bool blocked = false;
		int i;

		for (i = 0; i < num_wifi_res_stas; i++) {
			if (!memcmp(&wifi_res_stas[i * 6], s->macaddr, 6)) {
				/* STA blocked in WiFi */
				blocked = true;
				break;
			}
		}

		if (blocked) {
			/* STA already blocked in WiFi */
			agnt_dbg(LOG_STEER, "%s: STA already blocked in WiFi\n", __func__);
			continue;
		}

		if ((s->mode == ASSOC_CTRL_TIMED_BLOCK || s->mode == ASSOC_CTRL_BLOCK)
				&& !timer_pending(&s->restrict_timer)) {
			/* Timer expired: do not block in WiFi */
			agnt_dbg(LOG_STEER, "%s: Timer expired for " MACFMT " - won't block in WiFi\n",
			    __func__, MAC2STR(s->macaddr));
			continue;
		}

		agnt_dbg(LOG_STEER, "%s: Block STA: " MACFMT " in %s\n",
		    __func__, MAC2STR(s->macaddr), ap->ifname);
		assoc_ctrl_restrict_sta(a, 1, &s->macaddr, ap, s->mode);
	}

	/* Theoretically, it could be blocked for other reasons,
	 * i.e. network admin-issued block, manually or via webGUI.
	 * Uncomment following code if we want to unblock STAs anyway.
	 */
#if 0
	/* Unblock STAs in Wi-FI if not found on runtime list */
	for (i = 0; i < num_wifi_res_stas; i++) {
		bool found = false;

		list_for_each_entry(s, &ap->restrict_stalist, list) {
			if (!memcmp(&wifi_res_stas[i * 6], s->macaddr, 6)) {
				found = true;
				break;
			}
		}

		if (!found) {
			/* STA not in runtime list, unblock it */
			uint8_t mac[6];

			memcpy(mac, &wifi_res_stas[i * 6], 6);
			assoc_ctrl_restrict_sta(a, 1, &mac, ap, ASSOC_CTRL_UNBLOCK);
		}
	}
#endif

	return 0;
}

int assoc_ctrl_process_request(void *agent, uint8_t *p,
				 struct cmdu_buff *cmdu)
{
	agnt_trace(LOG_STEER, "agent: %s: --->\n", __func__);

	struct tlv_client_assoc_ctrl_request *data =
		(struct tlv_client_assoc_ctrl_request *)p;
	struct agent *a = (struct agent *) agent;
	int num_err = 0;
	struct netif_ap *ap;
	struct sta_error_response sta_resp[MAX_STA];
	int offset = 0;
	uint8_t sta_list[ASSOC_CTRL_MAX_STA][6];
	uint16_t validity_period = 0;
	int ret = 0;
	int i;

	if (!data->num_sta || data->num_sta > ASSOC_CTRL_MAX_STA) {
		agnt_err(LOG_STEER, "%s: Invalid number of STAs\n", __func__);
		ret = -1;
		goto send_ack;
	}

	offset = sizeof(*data);
	for (i = 0; i < data->num_sta; i++) {
		memcpy(sta_list[i], &p[offset], 6);
		offset += 6;
	}

	ap = agent_get_ap(a, data->bssid);
	if (!ap) {
		agnt_err(LOG_STEER, "%s: BSSID not present\n", __func__);
		ret = -1;
		goto send_ack;
	}

	/* Special case for bBSS: If bssid is backhaul BSS and STA addr
	 * is ff:ff:ff:ff:ff:ff block/unblock associations for all macs.
	 */
	if ((ap->cfg && ap->cfg->multi_ap & 0x01)
		 && data->num_sta == 1
		 && hwaddr_is_bcast(data->sta[0].macaddr)) {
		bool disallow = false;

		if (data->control == ASSOC_CTRL_BLOCK)
			disallow = true;

		agnt_trace(LOG_STEER, "%s: %s assoc. for bBSS: " MACFMT " [%s]\n",
			  __func__, disallow ? "Disallow" : "Allow",
			  MAC2STR(data->bssid), ap->ifname);

		if (!wifi_set_ap_mbo_association_mode(ap->ifname, disallow)) {
			ap->cfg->disallow_assoc = disallow;
			uci_apply_bss_association_mode(ap->ifname, disallow);
		}

		goto send_ack;
	}

	switch (data->control) {
	case ASSOC_CTRL_BLOCK: /* Client Blocking */
		/* Block - check for an Error Scenario:
		 * Send an error TLV in case STA is already associated
		 * with the BSSID for which the 'Block' mode is being set.
		 */
		for (i = 0; i < data->num_sta; i++) {
			struct netif_ap *s_ap = agent_get_ap_with_sta(a, sta_list[i]);

			if (s_ap == ap) {
				agnt_dbg(LOG_STEER, "STA already associated with BSSID!\n");
				/* 11.6: "include an Error Code TLV with the
				 * reason set to 0x01 and the STA MAC address
				 * field included"
				 */
				memcpy(sta_resp[num_err].sta_mac, sta_list[i], 6);
				sta_resp[num_err].response = 0x01;
				num_err++;
			}
		}

		if (num_err == data->num_sta) {
			/* All STAs are already associated with the BSSID */
			agnt_warn(LOG_STEER, "All STAs already associated with given BSSID!\n");
			ret = -1;
			goto send_ack;
		}
		/* no break - checkpatch ignore */
	case ASSOC_CTRL_TIMED_BLOCK: /* Timed Block */
		/* Validity Period mandatory for Client Blocking and Timed Block */
		validity_period = BUF_GET_BE16(data->validity_period);
		if (!validity_period) {
			agnt_warn(LOG_STEER, "Validity period unset, while expected!\n");
			ret = -1;
			goto send_ack;
		}
		/* no break - checkpatch ignore */
	case ASSOC_CTRL_INDEF_BLOCK: /* Indefinite Block */
		/* Add STA to blocking list for block/timed block/indef block */
		WARN_ON(assoc_ctrl_block_sta(a, ap, validity_period, data->control,
					data->num_sta, sta_list));
		break;
	case ASSOC_CTRL_UNBLOCK: /* Client Unblocking */
		/* Rem STA from blocking list and stop the restrict timer */
		WARN_ON(assoc_ctrl_unblock_sta(a, ap, data->num_sta, sta_list));
		break;
	default:
		agnt_warn(LOG_STEER, "Reserved Association Control mode!\n");
		ret = -1;
		goto send_ack;
	}

send_ack:
	/* If a Multi-AP Agent receives a Client Association Control
	 * Request message, then it shall respond within one second
	 * with a 1905 ACK message.
	 */
	ret = send_1905_acknowledge(agent, cmdu->origin, cmdu_get_mid(cmdu),
							    sta_resp, num_err);
	return ret;
}
