/*
 * bsteer.c - Controller initiated bSTA steering based on est-rate achievable from target-BSS.
 *
 * Copyright (C) 2025 Genexis Sweden AB.
 *
 * Author: anjan.chanda@genexis.eu
 *
 * See LICENSE file for license related information.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/time.h>
#include <time.h>
#include <libubox/list.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <json-c/json.h>
#include <uci.h>
#include <easy/easy.h>
#include <easy/timestamp.h>
#include <wifiutils.h>

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

#include <map-controller/wifi_dataelements.h>
#include <map-controller/wifi_opclass.h>
#include <map-controller/cntlr_commands.h>
#include <map-controller/cntlr_apis.h>
#include <map-controller/steer_module.h>
#include <map-controller/timer.h>
#include <map-controller/timer_impl.h>
#include <map-controller/utils/debug.h>

#ifndef MAC_ADDR_HASH
#define MAC_ADDR_HASH(a)	(a[0] ^ a[1] ^ a[2] ^ a[3] ^ a[4] ^ a[5])
#endif

#ifndef blobmsg_add_macaddr
#define blobmsg_add_macaddr(b, f, v)	\
do {					\
	char _vstr[18] = {0};		\
	hwaddr_ntoa(v, _vstr);		\
	blobmsg_add_string(b, f, _vstr);\
} while (0)
#endif

#ifndef cntlr_dbg
#define cntlr_dbg(feat, fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)  /* Flawfinder: ignore */
#endif

#define BSTA_MAX_NUM		32
#define sta_table_hash(m)	MAC_ADDR_HASH(m) & (BSTA_MAX_NUM - 1)
#define BSTA_INTERVAL_MAX	1000000

enum {
	BSTA_TIMER_NOP,
	BSTA_TIMER_ADD,
	BSTA_TIMER_DEL,
};

#define BSTEER_PLUGIN_NAME        "bsteer"

#define DEFAULT_RATE_BOOST           30        /* in MBps */

#define USTA_METRICS_AGEOUT          10        /* in secs */
#define USTA_RESPONSE_TIMEOUT        20        /* in secs */

#define MAX_SNR                      70        /* arbitrary high value */
#define MIN_ANPI                     40        /* = -90dBm */

struct scanres {
	uint8_t bssid[6];
	char ssid[33];
	uint8_t rcpi;
	int opclass;
	int channel;
	int bandwidth;
	time_t time;
};

enum {
	SCANRES_REQ_NONE,
	SCANRES_REQ_CACHED,
	SCANRES_REQ_FRESH,
};

enum bsteer_sta_state {
	BSTA_CONNECTED,
	BSTA_STEERING,
	BSTA_USTA_CHECK,
	BSTA_IDLE,
	BSTA_DISCONNECTED,
};

/* per-bSTA steer context data */
struct bsteer_sta_data {
	uint8_t macaddr[6];
	uint8_t bssid[6];
	uint8_t agent[6];	/* agent AL-macaddr this bSTA belongs to */
	uint8_t boost;

	enum bsteer_sta_state state;

	uint32_t maxrate;	/* from caps */
	enum wifi_bw bw;
	uint8_t nss;

#define DIFFRCPI_MAX_CNT	3
	int diffrcpi_cnt;
	uint8_t rcpi;           /* with current BSS */
	uint32_t est_rate;

	int waiting;

	time_t usta_query_time;
	bool usta_query_sent;
	bool usta_metrics_processed;

	int retry_scanresults;
	int scanreq_cached_sent;        /* request cached scanresults */
	int scanreq_fresh_sent;         /* trigger fresh scan */
	bool scanres_processed;
	struct scanres *scanres;
	size_t num_scanres;

	int scanres_reqtype;

	uint8_t connected;
	bool cleanup;

	uint32_t interval;	/* in ms */
	struct timeval tm;

	/* num steering attempts in a connection session */
	uint32_t num_steer_attempt;

	bool steer_attempt_pending;

	struct hlist_node hlist;
	void *priv;	/* struct bsteer_steer_control */
	void *sta;	/* struct steer_sta */
};

/* bsteer plugin private config options.
 *
 * config steer 'bsteer'
 *	option enabled '1              # bsta-rate plugin is enabled or not
 *	option boost '20'              # target-bBSS should be better by this amount (in Mbps)
 *	option bandsteer '0            # steer to different band
 */

/* bsteer private context */
struct bsteer_steer_control {
	bool enabled;
	uint8_t boost;
	bool bandsteer;
	int max_steer_attempt;
	uint32_t sta_count;
	struct hlist_head sta_table[BSTA_MAX_NUM];

	atimer_t t;
	struct timeval next_tmo;
	void *self;	/* our steer_control struct */
};

static int bsteer_steer_init(void **priv);
static int bsteer_steer_configure(void *priv, void *config_section);
static int bsteer_steer_exit(void *priv);
static int bsteer_steer_check(void *priv, struct steer_sta *s, uint16_t rxcmdu_type);
static int bsteer_get_scanresults(struct bsteer_steer_control *sc, struct bsteer_sta_data *st);

static void bsteer_reset_sta_data(struct bsteer_sta_data *st);

int wifi_band_to_int(enum wifi_band band)
{
	switch (band) {
	case BAND_2:
		return 2;
	case BAND_5:
		return 5;
	case BAND_6:
		return 6;
	default:
		break;
	}

	return 0;
}

enum wifi_band int_to_wifi_band(int band)
{
	switch (band) {
	case 2:
		return BAND_2;
	case 5:
		return BAND_5;
	case 6:
		return BAND_6;
	default:
		break;
	}

	return BAND_UNKNOWN;
}

//TODO: remove; get from libwifiutils.so
enum wifi_bw wifi_bw_to_enum_bw(uint16_t bw)
{
	switch (bw) {
	case 40:
		return BW40;
	case 80:
		return BW80;
	case 160:
		return BW160;
	case 8080:
		return BW8080;
	case 320:
		return BW320;
	default:
		break;
	}

	return BW20;
}

static const char *bsteer_sta_state_string(enum bsteer_sta_state s)
{
	switch (s) {
	case BSTA_CONNECTED:
		return "Connected";
	case BSTA_STEERING:
		return "Steering";
	case BSTA_IDLE:
		return "Idle";
	case BSTA_DISCONNECTED:
		return "Disconnected";
	default:
		break;
	}

	return "Unknown";
}

static int timeradd_msecs(struct timeval *a, unsigned long msecs,
			  struct timeval *res)
{
	if (res) {
		struct timeval t = { 0 };

		if (msecs > 1000) {
			t.tv_sec += msecs / 1000;
			t.tv_usec = (msecs % 1000) * 1000;
		} else {
			t.tv_usec = msecs * 1000;
		}

		timeradd(a, &t, res);
		return 0;
	}

	return -1;
}

static int getcurrtime(struct timeval *out)
{
	struct timespec nowts = { 0 };
	struct timeval now = { 0 };
	int ret;

	ret = clock_gettime(CLOCK_MONOTONIC_RAW, &nowts);
	if (!ret) {
		now.tv_sec = nowts.tv_sec;
		now.tv_usec = nowts.tv_nsec / 1000;
	} else {
		ret = gettimeofday(&now, NULL);
	}

	now.tv_usec = (now.tv_usec / 1000) * 1000;
	out->tv_sec = now.tv_sec;
	out->tv_usec = now.tv_usec;

	return ret;
}

int bsteer_table_entry_process(struct bsteer_steer_control *sc,
			       struct bsteer_sta_data *st)
{
	struct wifi_sta_element *sta;
	int action = BSTA_TIMER_NOP;
	struct steer_sta *s;
	void *cntlr;

	cntlr = ((struct steer_control *)(sc->self))->controller;
	sta = cntlr_get_sta_element(cntlr, st->macaddr);
	if (!sta)
		return BSTA_TIMER_NOP;

	/* if action = BSTA_TIMER_ADD, set 'st->interval' as desired.
	 * Otherwise, the existing 'st->interval' will be used.
	 */

	s = cntlr_get_sta_steer_data(cntlr, st->macaddr);
	if (!s || s != st->sta) {
		cntlr_dbg(LOG_STEER, "%s: "MACFMT" updating steer_sta = %p\n",
				__func__, MAC2STR(st->macaddr), s);
		st->sta = s;
	}

	if (st->cleanup) {
		cntlr_dbg(LOG_BSTEER, "bSTA " MACFMT ": Reset steer data\n",
			  MAC2STR(st->macaddr));

		bsteer_reset_sta_data(st);
		return BSTA_TIMER_NOP;
	}

	switch (st->state) {
	case BSTA_CONNECTED:
		st->scanres_reqtype = SCANRES_REQ_NONE;
		st->waiting = 0;
		break;
	case BSTA_STEERING:
		st->state = BSTA_CONNECTED;
		st->waiting = 0;
		st->steer_attempt_pending = 0;
		break;
	case BSTA_IDLE:
		st->state = BSTA_CONNECTED;
		st->waiting = 0;
		st->retry_scanresults = 0;
		break;
	default:
		break;
	}

	return action;
}

void bsteer_sta_table_flush(struct hlist_head *table)
{
	struct bsteer_sta_data *e = NULL;
	struct hlist_node *tmp;
	int i;

	for (i = 0; i < BSTA_MAX_NUM; i++) {
		if (hlist_empty(&table[i]))
			continue;

		hlist_for_each_entry_safe(e, tmp, &table[i], hlist) {
			hlist_del(&e->hlist, &table[i]);
			e->priv = NULL;
			e->sta = NULL;
			free(e);
		}
	}
}

void bsteer_timer_set(struct bsteer_steer_control *sc,
		      struct bsteer_sta_data *st,
		      uint32_t interval)
{
	struct timeval now = { 0 };

	getcurrtime(&now);
	st->interval = interval;
	timeradd_msecs(&now, st->interval, &st->tm);
	st->tm.tv_usec = (st->tm.tv_usec / 1000) * 1000;

	if (timer_pending(&sc->t)) {
		if (timercmp(&sc->next_tmo, &st->tm, >)) {
			sc->next_tmo.tv_sec = st->tm.tv_sec;
			sc->next_tmo.tv_usec = st->tm.tv_usec;
			timer_set(&sc->t, st->interval);
		}
	} else {
		sc->next_tmo.tv_sec = st->tm.tv_sec;
		sc->next_tmo.tv_usec = st->tm.tv_usec;
		timer_set(&sc->t, st->interval);
	}
}

void bsteer_timer_reset(struct bsteer_steer_control *sc,
			struct bsteer_sta_data *st)
{
	bsteer_timer_set(sc, st, BSTA_INTERVAL_MAX * 1000);
}

static void bsteer_timer_process(struct bsteer_steer_control *priv,
				 struct hlist_head *head,
				 struct timeval *min_next_tmo)
{
	struct bsteer_sta_data *e = NULL;
	struct timeval now = { 0 };
	struct hlist_node *tmp;


	getcurrtime(&now);
	hlist_for_each_entry_safe(e, tmp, head, hlist) {
		int action = BSTA_TIMER_ADD;
		struct timeval new_next_tmo = { 0 };

		if (!timercmp(&e->tm, &now, >)) {
			/* cntlr_dbg(LOG_BSTEER,
				  "%s: process timer for bSTA " MACFMT "\n",
				  __func__, MAC2STR(e->macaddr)); */

			action = bsteer_table_entry_process(priv, e);
			if (action == BSTA_TIMER_ADD)
				timeradd_msecs(&now, e->interval, &e->tm);
		}

		switch (action) {
		case BSTA_TIMER_DEL:
			cntlr_dbg(LOG_BSTEER, "Remove timer for " MACFMT "\n", MAC2STR(e->macaddr));
			//bsteer_timer_reset(priv, e);	//TODO: remove
			break;
		case BSTA_TIMER_ADD:
			timersub(&e->tm, &now, &new_next_tmo);
			if (!timercmp(min_next_tmo, &new_next_tmo, <)) {
				min_next_tmo->tv_sec = new_next_tmo.tv_sec;
				min_next_tmo->tv_usec = new_next_tmo.tv_usec;
				/* cntlr_dbg(LOG_BSTEER,
					  "Adjusted next-tmo = (%jd.%jd)\n",
					  (uintmax_t) min_next_tmo->tv_sec,
					  (uintmax_t) min_next_tmo->tv_usec); */
			}
			break;
		}
	}
}

void bsteer_timer_cb(atimer_t *t)
{
	struct bsteer_steer_control *p = container_of(t, struct bsteer_steer_control, t);
	struct timeval min_next_tmo = { .tv_sec = BSTA_INTERVAL_MAX };
	struct timeval now;
	int remain_cnt = 0;
	int i;

	getcurrtime(&now);
	for (i = 0; i < BSTA_MAX_NUM; i++) {
		if (hlist_empty(&p->sta_table[i]))
			continue;

		bsteer_timer_process(p, &p->sta_table[i], &min_next_tmo);
	}

	remain_cnt = p->sta_count;
	timeradd(&now, &min_next_tmo, &p->next_tmo);

	/*
	cntlr_dbg(LOG_BSTEER,
		  "Next timer: when = %jd.%jd, after = %jd.%jd, sta-cnt = %d\n",
		  (uintmax_t) p->next_tmo.tv_sec, (uintmax_t) p->next_tmo.tv_usec,
		  (uintmax_t) min_next_tmo.tv_sec, (uintmax_t) min_next_tmo.tv_usec,
		  remain_cnt);
	*/

	if (remain_cnt) {
		uint32_t tmo_msecs =
			min_next_tmo.tv_sec * 1000 + min_next_tmo.tv_usec / 1000;

		if (tmo_msecs > 0) {
			/*
			cntlr_dbg(LOG_BSTEER,
				  "Next timer: set after %u ms, sta-cnt = %d\n",
				  tmo_msecs, remain_cnt);
			*/

			timer_set(&p->t, tmo_msecs);
		}
	}
}

bool is_bandsteer_allowed(void *priv)
{
	struct bsteer_steer_control *sc = (struct bsteer_steer_control *)priv;

	cntlr_dbg(LOG_BSTEER, "%s: bandsteer is %sallowed\n", __func__,
		  sc->bandsteer ? "" : "not ");

	return sc->bandsteer ? true : false;
}

struct bsteer_sta_data *bsteer_lookup_sta(struct bsteer_steer_control *sc,
					  uint8_t *macaddr)
{
	struct bsteer_sta_data *st = NULL;
	int idx = sta_table_hash(macaddr);

	hlist_for_each_entry(st, &sc->sta_table[idx], hlist) {
		if (!memcmp(st->macaddr, macaddr, 6)) {
			return st;
		}
	}

	return NULL;
}

struct steer_sta_target_bss *nbrlist_lookup_bssid(struct steer_sta *s, uint8_t *bssid)
{
	for (int i = 0; i < s->num_nbr; i++) {
		if (!memcmp(s->nbrlist[i].bssid, bssid, 6))
			return &s->nbrlist[i];
	}

	return NULL;
}

void print_nbrlist(struct steer_sta *s)
{
	for (int i = 0; i < s->num_nbr; i++) {
		cntlr_dbg(LOG_BSTEER, "NBR " MACFMT ": Channel = %3d Opclass = %3d\n",
			  MAC2STR(s->nbrlist[i].bssid),
			  s->nbrlist[i].channel,
			  s->nbrlist[i].opclass);
	}
}

void print_scanresults(struct bsteer_sta_data *st)
{
	for (int i = 0; i < st->num_scanres; i++) {
		cntlr_dbg(LOG_BSTEER, "SCANRES " MACFMT ": Channel = %3d Opclass = %3d rcpi = %3d\n",
			  MAC2STR(st->scanres[i].bssid),
			  st->scanres[i].channel,
			  st->scanres[i].opclass,
			  st->scanres[i].rcpi);
	}
}

int bsteer_request_scanresults(struct bsteer_steer_control *sc, struct bsteer_sta_data *st,
			       bool cached)
{
	void *cntlr = ((struct steer_control *)(sc->self))->controller;
	struct steer_sta *s = ((struct steer_sta *)st->sta);
	enum wifi_band band = BAND_ANY;
	struct blob_buf bi = { 0 };
	struct blob_buf bo = { 0 };
	int ret = 0;

	cntlr_dbg(LOG_BSTEER, "%s: Enter\n", __func__);

	if (!sc->bandsteer)
		band = wifi_opclass_get_band(s->bss.opclass);

	blob_buf_init(&bi, 0);
	blob_buf_init(&bo, 0);

	blobmsg_add_macaddr(&bi, "agent", st->agent);
	if (band != BAND_ANY)
		blobmsg_add_u32(&bi, "band", wifi_band_to_int(band));

	blobmsg_add_u8(&bi, "fresh_scan", !cached);

	cntlr_dbg(LOG_BSTEER,
		  "%s: bSTA " MACFMT ": request %s scanresults from Agent " MACFMT ", for band %u\n",
		  __func__, MAC2STR(s->macaddr), !cached ? "fresh" : "cached",
		  MAC2STR(st->agent), wifi_band_to_int(band));

	ret = COMMAND(scan)(cntlr, bi.head, &bo);
	if (!ret) {
		struct blob_attr *tb[2];
		const struct blobmsg_policy rattr[2] = {
			[0] = { .name = "mid", .type = BLOBMSG_TYPE_INT32 },
			[1] = { .name = "status", .type = BLOBMSG_TYPE_STRING }
		};

		blobmsg_parse(rattr, 2, tb, blob_data(bo.head), blob_len(bo.head));

		if (tb[0]) {
			uint16_t req_mid = blobmsg_get_u32(tb[0]);

			cntlr_dbg(LOG_BSTEER,
				  "%s: Scan request cmdu mid = %hu\n", __func__, req_mid);

			if (cached)
				st->scanreq_cached_sent++;
			else
				st->scanreq_fresh_sent++;
		}
	} else {
		cntlr_dbg(LOG_BSTEER,
			  "%s: failed to request scanresults\n", __func__);
	}

	blob_buf_free(&bi);
	blob_buf_free(&bo);

	cntlr_dbg(LOG_BSTEER, "%s: Exit\n", __func__);
	return ret;
}

int bsteer_update_scanresults(struct bsteer_sta_data *st,
			      struct json_object *scanres_jobj,
			      struct scanres **results,
			      size_t *count)
{
	struct steer_sta *ss = ((struct steer_sta *)st->sta);
	struct json_object *radios;
	size_t total = 0;
	size_t radios_n;


	cntlr_dbg(LOG_BSTEER, "%s: filter bSTA ssid = '%s' (len = %d) --->\n", __func__, ss->bss.ssid, ss->bss.ssidlen);
	if (!scanres_jobj || !results || !count)
		return -1;

	*results = NULL;
	*count = 0;

	if (!json_object_object_get_ex(scanres_jobj, "radios", &radios) ||
	    !json_object_is_type(radios, json_type_array)) {
		cntlr_dbg(LOG_BSTEER, "%s: missing radios[]\n", __func__);
		return -1;
	}

	radios_n = json_object_array_length(radios);

	for (size_t r = 0; r < radios_n; r++) {
		struct json_object *radio = json_object_array_get_idx(radios, r);
		struct json_object *scanlist;

		if (!json_object_object_get_ex(radio, "scanlist", &scanlist) ||
		    !json_object_is_type(scanlist, json_type_array))
			continue;

		size_t scan_n = json_object_array_length(scanlist);
		for (size_t s = 0; s < scan_n; s++) {
			struct json_object *scan = json_object_array_get_idx(scanlist, s);
			struct json_object *opclasses;

			if (!json_object_object_get_ex(scan, "opclasses", &opclasses) ||
			    !json_object_is_type(opclasses, json_type_array))
				continue;

			size_t op_n = json_object_array_length(opclasses);
			for (size_t o = 0; o < op_n; o++) {
				struct json_object *opclass_entry = json_object_array_get_idx(opclasses, o);
				struct json_object *channels;
				struct json_object *jop;
				int opclass = 0;
				size_t ch_n;

				if (json_object_object_get_ex(opclass_entry, "opclass", &jop))
					opclass = json_object_get_int(jop);

				if (!json_object_object_get_ex(opclass_entry, "channels", &channels) ||
				    !json_object_is_type(channels, json_type_array))
					continue;

				ch_n = json_object_array_length(channels);
				for (size_t c = 0; c < ch_n; c++) {
					struct json_object *ch_entry = json_object_array_get_idx(channels, c);
					struct json_object *nbrlist;
					struct json_object *jtsp, *jch;
					char tspstr[64] = {0};
					int channel = 0;
					time_t tsp = 0;
					size_t nbr_n;


					if (json_object_object_get_ex(ch_entry, "tsp", &jtsp)) {
						strncpy(tspstr, json_object_get_string(jtsp), sizeof(tspstr) - 1);
						tsp = timestamp_to_time(tspstr);
					}

					if (json_object_object_get_ex(ch_entry, "channel", &jch))
						channel = json_object_get_int(jch);

					if (!json_object_object_get_ex(ch_entry, "nbrlist", &nbrlist) ||
					    !json_object_is_type(nbrlist, json_type_array))
						continue;

					nbr_n = json_object_array_length(nbrlist);
					for (size_t n = 0; n < nbr_n; n++) {
						struct json_object *nbr = json_object_array_get_idx(nbrlist, n);
						struct json_object *val;
						struct scanres *res;
						char ssid[33] = {0};
						uint8_t bssid[6] = {0};

						if (json_object_object_get_ex(nbr, "ssid", &val)) {
							strncpy(ssid, json_object_get_string(val), sizeof(ssid) - 1);
							if (ss->bss.ssidlen > 0 && strncmp(ssid, (const char *)ss->bss.ssid, ss->bss.ssidlen)) {
								cntlr_dbg(LOG_BSTEER, "%s: skip scanres ssid '%s'\n", __func__, ssid);
								continue;
							}
						}

						if (json_object_object_get_ex(nbr, "bssid", &val)) {
							char sbssid[32] = {0};

							strncpy(sbssid, json_object_get_string(val), sizeof(sbssid) - 1);
							hwaddr_aton(sbssid, bssid);
						}

						if (!nbrlist_lookup_bssid(ss, bssid))
							continue;

						*results = realloc(*results, (total + 1) * sizeof(**results));
						res = &(*results)[total];
						memset(res, 0, sizeof(*res));
						memcpy(res->ssid, ssid, sizeof(res->ssid) - 1);
						memcpy(res->bssid, bssid, 6);

						if (json_object_object_get_ex(nbr, "rcpi", &val))
							res->rcpi = json_object_get_int(val);

						res->time = tsp;
						res->opclass = opclass;
						res->channel = channel;

						if (json_object_object_get_ex(nbr, "bw", &val))
							res->bandwidth = json_object_get_int(val);

						total++;
					}
				}
			}
		}
	}

	*count = total;
	cntlr_dbg(LOG_BSTEER, "%s: num-scanresults = %zd\n", __func__, *count);
	return 0;
}

bool bsteer_scanresults_is_valid(struct bsteer_steer_control *sc, struct bsteer_sta_data *st)
{
	struct steer_sta *s = ((struct steer_sta *)st->sta);

	for (int i = 0; i < st->num_scanres; i++) {
		if (!memcmp(s->bss.bssid, st->scanres[i].bssid, 6)) {
			if (abs(st->scanres[i].rcpi - st->rcpi) > 20) {
				st->diffrcpi_cnt++;
				if (st->diffrcpi_cnt > DIFFRCPI_MAX_CNT) {
					st->diffrcpi_cnt = 0;
					cntlr_dbg(LOG_BSTEER, "%s: mark Invalid. diffrcpi > 20\n", __func__);
					return false;
				}
			}
			return true;
		}
	}

	cntlr_dbg(LOG_BSTEER, "%s: mark Invalid. Does not contain own bssid " MACFMT"\n", __func__, MAC2STR(s->bss.bssid));
	return false;
}

int bsteer_get_scanresults(struct bsteer_steer_control *sc, struct bsteer_sta_data *st)
{
	void *cntlr = ((struct steer_control *)(sc->self))->controller;
	struct steer_sta *s = ((struct steer_sta *)st->sta);
	enum wifi_band band = BAND_ANY;
	struct blob_buf bi = { 0 };
	struct blob_buf bo = { 0 };
	int ret = 0;

	cntlr_dbg(LOG_BSTEER, "%s: Enter\n", __func__);
	if (s->num_nbr == 0) {
		cntlr_dbg(LOG_BSTEER, "%s: num-nbr = %d\n", __func__, s->num_nbr);
		goto ret_out;
	}

	if (!sc->bandsteer)
		band = wifi_opclass_get_band(s->bss.opclass);

	blob_buf_init(&bi, 0);
	blob_buf_init(&bo, 0);

	blobmsg_add_macaddr(&bi, "agent", st->agent);
	if (band != BAND_ANY)
		blobmsg_add_u32(&bi, "", wifi_band_to_int(band));

	cntlr_dbg(LOG_BSTEER, "%s: bSTA " MACFMT ": get scanresults from Agent " MACFMT " for band %u\n",
		  __func__, MAC2STR(s->macaddr), MAC2STR(st->agent), wifi_band_to_int(band));

	ret = COMMAND(scanresults)(cntlr, bi.head, &bo);
	if (!ret) {
		char *jstr = blobmsg_format_json(bo.head, true);
		struct json_object *jobj;

		if (!jstr)
			goto out;

		jobj = json_tokener_parse(jstr);
		if (!jobj)
			cntlr_dbg(LOG_BSTEER, "%s: jobj = NULL\n", __func__);

		if (st->num_scanres && st->scanres) {
			free(st->scanres);
			st->num_scanres = 0;
		}

		ret = bsteer_update_scanresults(st, jobj, &st->scanres, &st->num_scanres);
		if (!ret) {
			cntlr_dbg(LOG_BSTEER,
				  "%s: got %zd scanresults from " MACFMT"\n",
				  __func__, st->num_scanres, MAC2STR(st->agent));

			print_scanresults(st);
		}

		json_object_put(jobj);
		free(jstr);
	} else {
		cntlr_dbg(LOG_BSTEER,
			  "%s: failed to get scanresults from " MACFMT"\n",
			  __func__, MAC2STR(st->agent));
	}

out:
	blob_buf_free(&bi);
	blob_buf_free(&bo);

ret_out:
	cntlr_dbg(LOG_BSTEER, "%s: Exit\n", __func__);
	return ret;
}

int bsteer_query_unassoc_sta_metrics(struct bsteer_steer_control *sc, struct bsteer_sta_data *st)
{
	void *cntlr = ((struct steer_control *)(sc->self))->controller;
	struct steer_sta *s = ((struct steer_sta *)st->sta);
	int ret = 0;

	cntlr_dbg(LOG_BSTEER, "%s: Enter\n", __func__);

	if (s->num_nbr == 0) {
		cntlr_dbg(LOG_BSTEER, "%s: num-nbr = 0. Skip\n", __func__);
		goto ret_out;
	}

	/* Request unassoc-sta-metrics reports from all neighbor bBSSs */
	for (int i = 0; i < s->num_nbr; i++) {
		struct steer_sta_target_bss *nbr = &s->nbrlist[i];
		struct blob_buf bi = { 0 };
		struct blob_buf bo = { 0 };
		void *arr;


		/* Skip querying bSTA's current Agent */
		if (!memcmp(s->bss.agent, nbr->agent, 6))
			continue;

		/* also skip querying for different band if bandsteer disabled */
		if (!sc->bandsteer &&
		    wifi_opclass_get_band(nbr->opclass) != wifi_opclass_get_band(s->bss.opclass)) {
			continue;
		}

		blob_buf_init(&bi, 0);
		blob_buf_init(&bo, 0);

		blobmsg_add_macaddr(&bi, "agent", nbr->agent);

		//if (wifi_opclass_get_bw(nbr->opclass) < wifi_opclass_get_bw(s->bss->opclass))

		blobmsg_add_u32(&bi, "opclass", s->bss.opclass);
		blobmsg_add_u32(&bi, "channel", s->bss.channel);
		arr = blobmsg_open_array(&bi, "stalist");
		blobmsg_add_macaddr(&bi, "", s->macaddr);
		blobmsg_close_array(&bi, arr);

		cntlr_dbg(LOG_BSTEER, "%s: bSTA " MACFMT ": Query Unassoc bSTA metrics - Opclass = %u, Ch = %u ========>\n",
			  __func__, MAC2STR(s->macaddr), s->bss.opclass, s->bss.channel);

		ret = COMMAND(query_unassoc_sta_metrics)(cntlr, bi.head, &bo);
		if (!ret) {
			struct blob_attr *tb[2];
			const struct blobmsg_policy rattr[2] = {
				[0] = { .name = "mid", .type = BLOBMSG_TYPE_INT32 },
				[1] = { .name = "status", .type = BLOBMSG_TYPE_STRING }
			};

			blobmsg_parse(rattr, 2, tb, blob_data(bo.head), blob_len(bo.head));

			if (tb[0]) {
				uint16_t req_mid = blobmsg_get_u32(tb[0]);

				cntlr_dbg(LOG_BSTEER,
					  "%s: bSTA " MACFMT ": Request Unassoc-bSTA metrics from " MACFMT " cmdu mid = %hu\n",
					  __func__, MAC2STR(s->macaddr),
					  MAC2STR(nbr->agent), req_mid);

				time(&st->usta_query_time);
				st->usta_query_sent = true;
				st->usta_metrics_processed = 0;
			}
		} else {
			cntlr_dbg(LOG_BSTEER,
				  "%s: failed to request Unassoc-bSTA metrics\n", __func__);
		}

		blob_buf_free(&bi);
		blob_buf_free(&bo);
	}

ret_out:
	cntlr_dbg(LOG_BSTEER, "%s: Exit\n", __func__);
	return ret;
}

void bsteer_reset_sta_data(struct bsteer_sta_data *st)
{
	struct steer_sta *sta = (struct steer_sta *)st->sta;

	if (sta)
		st->connected = sta->bss.connected;

	st->state = st->connected ? BSTA_CONNECTED : BSTA_DISCONNECTED;
	st->maxrate = 0;
	st->est_rate = 0;
	st->nss = 0;
	st->bw = 0;
	st->scanreq_cached_sent = 0;
	st->scanreq_fresh_sent = 0;
	st->scanres_processed = false;
	st->usta_query_sent = false;
	st->usta_metrics_processed = false;
	st->cleanup = false;
	st->steer_attempt_pending = false;
	st->num_steer_attempt = 0;
	st->waiting = 0;
}

void bsteer_print_sta_data(struct bsteer_steer_control *priv, struct bsteer_sta_data *st)
{
	struct bsteer_steer_control *sc = (struct bsteer_steer_control *)priv;

#define NUM_USTA_METRICS	16
	cntlr_dbg(LOG_BSTEER, "bSTA = " MACFMT "\n", MAC2STR(st->macaddr));
	cntlr_dbg(LOG_BSTEER, "bSTA Agent = " MACFMT "\n", MAC2STR(st->agent));
	cntlr_dbg(LOG_BSTEER, "BSSID = " MACFMT "\n", MAC2STR(((struct steer_sta *)st->sta)->bss.bssid));
	cntlr_dbg(LOG_BSTEER, "state = %s\n", bsteer_sta_state_string(st->state));
	cntlr_dbg(LOG_BSTEER, "num_steer_attempt = %u\n", st->num_steer_attempt);
	cntlr_dbg(LOG_BSTEER, "max_steer_attempt = %u\n", sc->max_steer_attempt);
	cntlr_dbg(LOG_BSTEER, "max-rate = %d\n", st->maxrate);
	cntlr_dbg(LOG_BSTEER, "est-current-rate = %d\n", st->est_rate);
	cntlr_dbg(LOG_BSTEER, "scanreq-cached-sent = %d\n", st->scanreq_cached_sent);
	cntlr_dbg(LOG_BSTEER, "scanreq-fresh-sent = %d\n", st->scanreq_fresh_sent);
	cntlr_dbg(LOG_BSTEER, "scanresults-processed = %d\n", st->scanres_processed);
	cntlr_dbg(LOG_BSTEER, "usta-query-sent = %d\n", st->usta_query_sent);
	cntlr_dbg(LOG_BSTEER, "usta-metrics-processed = %d\n", st->usta_metrics_processed);
	cntlr_dbg(LOG_BSTEER, "config: rate-boost = %u Mbps\n", st->boost);

	if (list_empty(((struct steer_sta *)st->sta)->unassoc_metriclist)) {
		cntlr_dbg(LOG_BSTEER, "usta-metrics-list = []\n");
	} else {
		char ubuf[25 * NUM_USTA_METRICS] = {0};
		struct unassoc_sta_metrics *u = NULL;
		char buf[1024] = {0};
		int comma = 1;
		int cnt = 0;

		snprintf(buf, sizeof(buf) - 1, "usta-metrics-list = [");
		list_for_each_entry(u, ((struct steer_sta *)st->sta)->unassoc_metriclist, list) {
			snprintf(ubuf + strlen(ubuf),
				 sizeof(ubuf) - 1 - strlen(ubuf), "("MACFMT",%u),",
				 MAC2STR(u->bssid), u->ul_rcpi);

			cnt++;
			if (cnt >= NUM_USTA_METRICS)
				break;
		}
		snprintf(buf + strlen(buf), sizeof(buf) - 1 - strlen(buf), "%s", ubuf);
		snprintf(buf + strlen(buf) - comma , sizeof(buf) - 1 - strlen(buf), "%s", "]");
		cntlr_dbg(LOG_BSTEER, "%s\n", buf);
	}
}

void bsteer_dump_sta_table(void *priv, uint8_t *macaddr)
{
	struct bsteer_steer_control *sc = (struct bsteer_steer_control *)priv;
	struct bsteer_sta_data *st = NULL;
	int i;

	cntlr_dbg(LOG_BSTEER, "-------- bSTA Table ---------\n");
	for (i = 0; i < BSTA_MAX_NUM; i++) {
		if (hlist_empty(&sc->sta_table[i]))
			continue;

		hlist_for_each_entry(st, &sc->sta_table[i], hlist) {
			if (!macaddr || !memcmp(macaddr, st->macaddr, 6)) {
				bsteer_print_sta_data(priv, st);
			}
		}
	}
	cntlr_dbg(LOG_BSTEER, "----------------------------\n");
}

static int estimate_bsta_thput(void *priv, struct steer_sta *s, bool curr)
{
	struct bsteer_steer_control *sc = (struct bsteer_steer_control *)priv;
	struct bsteer_sta_data *st = bsteer_lookup_sta(sc, s->macaddr);
	void *cntlr = ((struct steer_control *)(sc->self))->controller;
	struct wifi_radio_element *rel;
	struct wifi_sta_element *sta;
	struct wifi_caps caps = {0};
	enum wifi_bw bw = BW20;
	enum wifi_bw apbw;
	uint32_t est_rate;
	size_t ieslen;
	uint8_t *ies;
	uint8_t anpi;
	int snr;


	sta = cntlr_get_sta_element(cntlr, s->macaddr);
	if (!sta)
		return -1;

	if (sta->reassoc_framelen < 4 || !sta->reassoc_frame)
		return -1;

	ieslen = sta->reassoc_framelen - 4;
	ies = sta->reassoc_frame + 4;

	if (wifi_find_ie_ext(ies, ieslen, IE_EXT_EHT_CAP)) {
		caps.valid |= WIFI_CAP_EHT_VALID;
		bw = BW160;
	} else if (wifi_find_ie_ext(ies, ieslen, IE_EXT_HE_CAP)) {
		caps.valid |= WIFI_CAP_HE_VALID;
		bw = BW80;
	} else if (wifi_find_ie(ies, ieslen, IE_VHT_CAP)) {
		caps.valid |= WIFI_CAP_VHT_VALID;
		bw = BW40;
	} else if (wifi_find_ie(ies, ieslen, IE_HT_CAP)) {
		caps.valid |= WIFI_CAP_HT_VALID;
	}
	cntlr_dbg(LOG_BSTEER, "%s: From caps, BW = %d\n", __func__, wifi_bw_enum2MHz(bw));

	apbw = wifi_bw_to_enum_bw(wifi_opclass_get_bw(s->bss.opclass));
	bw = min(apbw, bw);

	cntlr_dbg(LOG_BSTEER, "%s: for %s AP_BW = %d, BW = %d\n", __func__,
		  curr ? "current" : "max", wifi_bw_enum2MHz(apbw), wifi_bw_enum2MHz(bw));

	rel = cntlr_get_radio_element_by_bssid(cntlr, s->bss.bssid);
	anpi =  rel ? rel->anpi : MIN_ANPI;
	if (anpi == 0)
		anpi = MIN_ANPI;

	snr = curr ? (sta->rcpi - anpi) / 2 : MAX_SNR;
	wifi_nss_from_ies(ies, ieslen, &st->nss);

	cntlr_dbg(LOG_BSTEER, "%s: NSS = %d, BW = %d, SNR = %d RCPI = %u\n", __func__,
		  st->nss, wifi_bw_enum2MHz(bw), snr, sta->rcpi);

	est_rate = wifi_get_estimated_throughput(snr, bw, &caps, st->nss, 0);
	if (est_rate > 0) {
		int node_maxrate = cntlr_get_max_thput_estimate_for_node(cntlr, s->bss.agent);
		if (node_maxrate < 0)
			return -1;

		return min(est_rate, (uint32_t)node_maxrate);
	}

	return -1;
}

static int estimate_sta_thput_with_nbr(void *priv, struct steer_sta *s,
				       struct steer_sta_target_bss *tbss,
				       uint8_t rcpi)
{
	struct bsteer_steer_control *sc = (struct bsteer_steer_control *)priv;
	struct bsteer_sta_data *st = bsteer_lookup_sta(sc, s->macaddr);
	void *cntlr = ((struct steer_control *)(sc->self))->controller;
	struct wifi_radio_element *rel;
	struct wifi_sta_element *sta;
	struct wifi_caps caps = {0};
	enum wifi_bw bw = BW20;
	enum wifi_bw apbw;
	//uint8_t ch_busy;
	size_t ieslen;
	uint8_t *ies;
	uint8_t anpi;
	int snr;


	//ch_busy = (tbss->ch_util / 255) * 100;	/* in %ge */

	sta = cntlr_get_sta_element(cntlr, s->macaddr);
	if (!sta)
		return -1;

	if (sta->reassoc_framelen < 4 || !sta->reassoc_frame)
		return -1;

	ieslen = sta->reassoc_framelen - 4;
	ies = sta->reassoc_frame + 4;

	if (wifi_find_ie_ext(ies, ieslen, IE_EXT_EHT_CAP)) {
		caps.valid |= WIFI_CAP_EHT_VALID;
		bw = BW160;
	} else if (wifi_find_ie_ext(ies, ieslen, IE_EXT_HE_CAP)) {
		caps.valid |= WIFI_CAP_HE_VALID;
		bw = BW80;
	} else if (wifi_find_ie(ies, ieslen, IE_VHT_CAP)) {
		caps.valid |= WIFI_CAP_VHT_VALID;
		bw = BW40;
	} else if (wifi_find_ie(ies, ieslen, IE_HT_CAP)) {
		caps.valid |= WIFI_CAP_HT_VALID;
	}

	apbw = wifi_bw_to_enum_bw(wifi_opclass_get_bw(tbss->opclass));
	bw = min(apbw, bw);

	rel = cntlr_get_radio_element_by_bssid(cntlr, tbss->bssid);
	anpi =  rel ? rel->anpi : MIN_ANPI;
	if (anpi == 0)
		anpi = MIN_ANPI;

	snr = (rcpi - anpi) / 2;
	cntlr_dbg(LOG_BSTEER, "%s: NBR " MACFMT": NSS = %d, BW = %d, SNR = %d, RCPI = %u\n", __func__,
		  MAC2STR(tbss->bssid), st->nss, wifi_bw_enum2MHz(bw), snr, rcpi);

	return wifi_get_estimated_throughput(snr, bw, &caps, st->nss, 0);
}

static int bsteer_steer_ok(struct bsteer_sta_data *st, uint32_t link_rate, uint32_t bh_rate)
{
	struct steer_sta *s = ((struct steer_sta *)st->sta);

	s->reason = STEER_REASON_LOW_THPUT;
	s->verdict = STEER_VERDICT_OK;
	st->num_steer_attempt++;
	st->steer_attempt_pending = true;

	cntlr_info(LOG_BSTEER,
		  "bsteer: decision is steer bSTA " MACFMT " to new bAP " MACFMT
		  " with est link-RATE %u Mbps , bh-RATE %u Mbps\n",
		  MAC2STR(s->macaddr),
		  MAC2STR(s->target.bssid),
		  link_rate, bh_rate);

	return 0;
}

int bsteer_steer_check(void *priv, struct steer_sta *s, uint16_t rxcmdu_type)
{
	cntlr_dbg(LOG_BSTEER, "%s: >>>>>> STA = " MACFMT"\n", __func__, MAC2STR(s->macaddr));
	struct bsteer_steer_control *sc = (struct bsteer_steer_control *)priv;
	struct bsteer_sta_data *st = NULL;
	struct wifi_sta_element *sta;
	bool best_found = false;
	uint8_t best_channel;
	uint8_t best_opclass;
	uint8_t *best_agent;
	uint8_t *best_bssid;
	uint32_t best_rate;
	uint32_t best_bh_rate;
	int32_t est_rate;
	void *cntlr;
	int ret;


	if (!sc || !sc->enabled || !s)
		return 0;

	if (hwaddr_is_zero(s->bss.bssid) || hwaddr_is_zero(s->bss.agent) ||
	    s->bss.channel == 0 || s->bss.opclass == 0) {
		cntlr_dbg(LOG_BSTEER, "%s: s->bss.bssid = "MACFMT", s->bss.agent = " MACFMT
			  ", channel = %d, opclass = %d\n",
			  __func__, MAC2STR(s->bss.bssid), MAC2STR(s->bss.agent),
			  s->bss.channel, s->bss.opclass);
		cntlr_dbg(LOG_BSTEER, "%s: Incomplete/invalid bSTA data. Exit\n", __func__);

		return 0;
	}

	cntlr = ((struct steer_control *)(sc->self))->controller;
	if (!cntlr_is_sta_bsta(cntlr, s->macaddr)) {
		cntlr_dbg(LOG_BSTEER, "%s: " MACFMT " is not bSTA; Exit\n", __func__, MAC2STR(s->macaddr));
		return 0;
	}

	sta = cntlr_get_sta_element(cntlr, s->macaddr);
	if (!sta) {
		cntlr_dbg(LOG_BSTEER, "%s: cntlr_get_sta_element() ret error. Exit\n", __func__);
		return 0;
	}

	cntlr_dbg(LOG_BSTEER, "%s: bSTA " MACFMT ", %s rcpi = %u, rate (dl = %uMbps, ul = %uMbps) ----->\n",
		  __func__, MAC2STR(s->macaddr),
		  s->bss.connected ? "connected" : "disconnected",
		  sta->rcpi, sta->dl_rate / 1000, sta->ul_rate / 1000);

	print_nbrlist(s);
	best_channel = s->bss.channel;
	best_opclass = s->bss.opclass;
	best_agent = s->bss.agent;
	best_bssid = s->bss.bssid;
	s->verdict = STEER_VERDICT_UNDECIDED;
	s->reason = STEER_REASON_UNDEFINED;

	st = bsteer_lookup_sta(sc, s->macaddr);
	if (!st) {
		int idx = sta_table_hash(s->macaddr);

		/* initialize steer state data for the bSTA */
		st = calloc(1, sizeof(*st));
		if (!st) {
			cntlr_dbg(LOG_BSTEER, "%s: -ENOMEM\n", __func__);
			return -1;
		}

		st->priv = sc;
		st->sta = s;
		memcpy(st->macaddr, s->macaddr, 6);
		memcpy(st->bssid, s->bss.bssid, 6);
		cntlr_get_agent_id(cntlr, s->macaddr, st->agent);
		st->boost = sc->boost;

		sc->sta_count++;
		hlist_add_head(&st->hlist, &sc->sta_table[idx]);

		s->verdict = STEER_VERDICT_UNDECIDED;
		s->reason = STEER_REASON_UNDEFINED;

		bsteer_timer_reset(sc, st);
	}

	/* shouldn't happen */
	if (WARN_ON(st->sta != s))
		st->sta = s;

	/* temp fix to handle out of seq Topology Notification for state disconnected */
	if (st->state == BSTA_DISCONNECTED && s->bss.connected)
		st->state = BSTA_CONNECTED;

	st->rcpi = sta->rcpi;
	bsteer_print_sta_data(priv, st);

	if (rxcmdu_type == CMDU_TYPE_TOPOLOGY_NOTIFICATION) {
		if (s->bss.connected != st->connected) {
			cntlr_dbg(LOG_BSTEER,
				  "%s: bSTA " MACFMT " %s " MACFMT "\n", __func__,
				  MAC2STR(s->macaddr),
				  s->bss.connected == 0 ? "disconnected from" : "connected to",
				  MAC2STR(s->bss.bssid));

			bsteer_reset_sta_data(st);
			bsteer_timer_reset(sc, st);
			return 0;
		}
	}

	if (st->maxrate <= 0) {
		int  maxrate = estimate_bsta_thput(priv, s, 0);
		if (maxrate < 0) {
			cntlr_dbg(LOG_BSTEER, "%s: " MACFMT": max estimate_bsta_thput() ret = -1\n",
				  __func__, MAC2STR(s->macaddr));
			return 0;
		}

		st->maxrate = maxrate;
	}

	est_rate = estimate_bsta_thput(priv, s, 1);
	if (est_rate < 0) {
		cntlr_dbg(LOG_BSTEER, "%s: " MACFMT": curr estimate_bsta_thput() ret = -1\n",
			  __func__, MAC2STR(s->macaddr));
		return 0;
	}

	st->est_rate = est_rate;
	best_rate = st->est_rate;

	if (rxcmdu_type == CMDU_BACKHAUL_STEER_RESPONSE) {
		st->state = BSTA_CONNECTED;
		st->waiting = 0;
		st->steer_attempt_pending = 0;

		return 0;
	}

	if (st->num_steer_attempt > sc->max_steer_attempt) {
		cntlr_dbg(LOG_BSTEER,
			  "%s: Skip bSTA " MACFMT " steer check. "
			  "Num-attempts (%d) > max-attempts (%d)\n",
			  __func__, MAC2STR(s->macaddr),
			  st->num_steer_attempt, sc->max_steer_attempt);
		return 0;
	}

	/* return if the last steer attempt is pending */
	if (st->steer_attempt_pending) {
		cntlr_dbg(LOG_BSTEER,
			  "%s: last steer attempt is pending. Skip\n", __func__);
		return 0;
	}

	if (st->waiting) {
		cntlr_dbg(LOG_BSTEER, "%s: waiting in %s state... return\n",
			  __func__, bsteer_sta_state_string(st->state));
		goto out;
	}

	switch (st->state) {
	case BSTA_CONNECTED:
	{
		ret = bsteer_get_scanresults(sc, st);
		if (ret) {
			st->retry_scanresults++;
			if (st->retry_scanresults > 3) {
				st->state = BSTA_IDLE;
				st->waiting = 0;
				st->retry_scanresults = 0;
			} else {
				//st->waiting = 1;
				bsteer_timer_set(sc, st, 3 * 1000);
			}

			return 0;
		}

		if (st->num_scanres == 0) {
			st->waiting = 1;
			st->scanres_reqtype = SCANRES_REQ_CACHED;
			ret = bsteer_request_scanresults(sc, st, true);
			if (!ret)
				bsteer_timer_set(sc, st, 3 * 1000);
			else
				st->waiting = 0;

			return 0;
		}

		if ((st->num_scanres == 1 && !memcmp(st->scanres[0].bssid, s->bss.bssid, 6)) ||
		    !bsteer_scanresults_is_valid(sc, st)) {
			/* request fresh scan */
			st->waiting = 1;
			st->scanres_reqtype = SCANRES_REQ_FRESH;
			ret = bsteer_request_scanresults(sc, st, false);
			if (!ret)
				bsteer_timer_set(sc, st, 10 * 1000);
			else
				st->waiting = 0;

			return 0;
		}

		/* process scanresults */
		for (int i = 0; i < st->num_scanres; i++) {
			struct steer_sta_target_bss *tbss = NULL;
			int tbss_est_rate_dl;
			int est_rate_dl;
			int est_maxrate_dl;

			/* already connected to this */
			if (!memcmp(s->bss.bssid, st->scanres[i].bssid, 6)) {
				cntlr_dbg(LOG_BSTEER, "%s: skip own bssid from scanresults\n", __func__);
				continue;
			}

			if (!sc->bandsteer &&
			    wifi_opclass_get_band(st->scanres[i].opclass) != wifi_opclass_get_band(s->bss.opclass)) {

				cntlr_dbg(LOG_BSTEER,
					  "%s: Skip off-band scanresult entry " MACFMT"\n", __func__,
					  MAC2STR(st->scanres[i].bssid));
				continue;
			}

			tbss = nbrlist_lookup_bssid(s, st->scanres[i].bssid);
			if (!tbss)
				continue;

			//if (is_meas_old(now, st->scanres[i].time, SCANRES_AGEOUT))
			//	continue;

			cntlr_dbg(LOG_BSTEER, "%s: Found NBR " MACFMT" in scanresults\n", __func__,
				  MAC2STR(st->scanres[i].bssid));
			est_rate_dl = estimate_sta_thput_with_nbr(priv, s, tbss, st->scanres[i].rcpi);
			if (est_rate_dl < 0)
				continue;

			est_maxrate_dl = estimate_sta_thput_with_nbr(priv, s, tbss, 220);	//TODO: debug only
			tbss_est_rate_dl = cntlr_get_max_thput_estimate_for_node(cntlr, tbss->agent);
			if (tbss_est_rate_dl < 0)
				continue;

			st->scanres_processed = true;
			cntlr_dbg(LOG_BSTEER,
				  "%s: bSTA " MACFMT ": with NBR " MACFMT" estimated target fh-rate = %u (MAX fh-rate = %u), bh-rate = %d\n",
				  __func__, MAC2STR(s->macaddr), MAC2STR(tbss->bssid), est_rate_dl, est_maxrate_dl, tbss_est_rate_dl);

			est_rate_dl = min(est_rate_dl, (uint32_t)tbss_est_rate_dl);
			if (est_rate_dl >= best_rate + st->boost) {
				best_found = true;
				best_rate = est_rate_dl;
				best_bh_rate = (uint32_t)tbss_est_rate_dl;
				best_agent = tbss->agent;
				best_bssid = tbss->bssid;
				best_channel = tbss->channel;
				best_opclass = tbss->opclass;
			}
		}

		if (best_found) {
			memcpy(s->target.bssid, best_bssid, 6);
			memcpy(s->target.agent, best_agent, 6);
			s->target.channel = best_channel;
			s->target.opclass = best_opclass;

			st->waiting = 1;
			st->state = BSTA_STEERING;
			bsteer_timer_set(sc, st, 10 * 1000);
			return bsteer_steer_ok(st, best_rate, best_bh_rate);
		}

		if (st->scanres_processed) {
			st->state = BSTA_IDLE;
			st->waiting = 0;
			st->retry_scanresults = 0;

			goto out;
		}

		break;
	}
	case BSTA_IDLE:
		st->waiting = 1,
		bsteer_timer_set(sc, st, 30 * 1000);
		break;
	default:
		break;
	}

out:
	cntlr_dbg(LOG_BSTEER, "%s: <<<<<< return\n", __func__);
	return 0;
}

int bsteer_cmdu_notifier(void *priv, struct cmdu_buff *cmdu)
{
	cntlr_dbg(LOG_BSTEER, "%s: received CMDU 0x%02X\n", __func__, cmdu_get_type(cmdu));

	//TODO

	return 0;
}

uint16_t cmdu_notifylist[] = {
	CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE,
	CMDU_TYPE_TOPOLOGY_NOTIFICATION,
	/* CMDU_ASSOCIATION_STATUS_NOTIFICATION, */
	CMDU_BACKHAUL_STEER_RESPONSE,
};

struct steer_control bsteer = {
	.name = BSTEER_PLUGIN_NAME,
	.init = bsteer_steer_init,
	.config = bsteer_steer_configure,
	.exit = bsteer_steer_exit,
	.steer = bsteer_steer_check,
	.cmdu_notifier = bsteer_cmdu_notifier,
	.num_cmdu = ARRAY_SIZE(cmdu_notifylist),
	.cmdulist = cmdu_notifylist,
};

static int bsteer_steer_init(void **priv)
{
	struct bsteer_steer_control *p;

	p = calloc(1, sizeof(struct bsteer_steer_control));
	if (!p)
		return -1;

	*priv = p;
	p->self = &bsteer;
	p->enabled = true;
	p->boost = DEFAULT_RATE_BOOST;
	p->max_steer_attempt = 1;
	timer_init(&p->t, bsteer_timer_cb);

	cntlr_info(LOG_BSTEER, "%s", "Estimated-rate in target-BSS based bSTA steering initalized");
	return 0;
}

static int bsteer_steer_configure(void *priv, void *config_section)
{
	enum {
		BSTEER_ENABLED,
		BSTEER_ALLOW_BANDSTEER,
		BSTEER_BOOST,
		NUM_BSTEER_ATTRS,
	};
	const struct uci_parse_option opts[] = {
		[BSTEER_ENABLED] = { .name = "enabled", .type = UCI_TYPE_STRING },
		[BSTEER_ALLOW_BANDSTEER] = { .name = "bandsteer", .type = UCI_TYPE_STRING },
		[BSTEER_BOOST] = { .name = "boost", .type = UCI_TYPE_STRING },
	};
	struct bsteer_steer_control *p = (struct bsteer_steer_control *)priv;
	struct uci_section *s = (struct uci_section *)config_section;
	struct uci_option *tb[NUM_BSTEER_ATTRS] = {0};


	//cntlr_dbg(LOG_BSTEER, "%s: Enter\n", __func__);
	if (!p)
		return -1;

	uci_parse_section(s, opts, NUM_BSTEER_ATTRS, tb);

	if (strncmp(s->e.name, BSTEER_PLUGIN_NAME, strlen(s->e.name)))
		return 0;	/* not our config */

	if (tb[BSTEER_ENABLED]) {
		const char *val = tb[BSTEER_ENABLED]->v.string;

		p->enabled = atoi(val) == 1 ? true : false;
	}

	if (tb[BSTEER_ALLOW_BANDSTEER]) {
		const char *val = tb[BSTEER_ALLOW_BANDSTEER]->v.string;

		p->bandsteer = atoi(val) == 1 ? true : false;
	}

	if (tb[BSTEER_BOOST]) {
		const char *val = tb[BSTEER_BOOST]->v.string;
		int boost = atoi(val);

		if (boost > 0)
			p->boost = boost;
	}

	//cntlr_dbg(LOG_BSTEER, "%s: Exit\n", __func__);
	return 0;
}

static int bsteer_steer_exit(void *priv)
{
	struct bsteer_steer_control *p = (struct bsteer_steer_control *)priv;

	if (p) {
		timer_del(&p->t);
		bsteer_sta_table_flush(p->sta_table);
		free(p);
	}

	cntlr_dbg(LOG_BSTEER, "%s: Exit\n", __func__);
	return 0;
}

