/*
 * rcpi.c - RCPI based STA steering.
 *
 * Copyright (C) 2022 IOPSYS Software Solutions AB. All rights reserved.
 *
 * 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 <uci.h>
#include <easy/easy.h>
#include <wifidefs.h>
#include <cmdu.h>
#include <1905_tlvs.h>
#include <easymesh.h>

#include "wifi_dataelements.h"
#include "wifi_opclass.h"
#include "cntlr_commands.h"
#include "cntlr_apis.h"
#include "steer_module.h"
#include "timer.h"
#include "timer_impl.h"
#include "utils/debug.h"
#include "utils/utils.h"

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

#define STA_MAX_NUM		128
#define sta_table_hash(m)	MAC_ADDR_HASH(m) & (STA_MAX_NUM - 1)
#define STA_INTERVAL_MAX	1000000

enum {
	STA_TIMER_NOP,
	STA_TIMER_ADD,
	STA_TIMER_DEL,
};

#define RCPI_PLUGIN_NAME             "rcpi"

#define DEFAULT_RCPI_THRESHOLD       86	/* = -67 dBm */
#define DEFAULT_RCPI_HYSTERESIS      10	/* = 5dBm */
#define DEFAULT_RCPI_DIFFSNR         16	/* = 8dB */

#define DEFAULT_RCPI_LOW_CNT         3
#define DEFAULT_RCPI_HIGH_CNT        4

#define DEFAULT_STEER_RETRY_INT      30        /* 30s */
#define DEFAULT_STEER_INT            180       /* 180s */
#define DEFAULT_STEER_DISABLE_INT    600       /* 600s */

#define NUM_RCPI_SAMPLES             15

#define BEACON_METRICS_AGEOUT        10        /* 10s */
#define USTA_METRICS_AGEOUT          10        /* 10s */

#define USTA_RESPONSE_TIMEOUT        5         /* in seconds */
#define BEACON_RESPONSE_TIMEOUT      10        /* in seconds */


//#define SKIP_OFFCHANNEL_BEACON_REQUEST	1

enum rcpi_trigger_type {
	RCPI_TRIGGER_NONE = 0,
	RCPI_TRIGGER_LOW = -1,
	RCPI_TRIGGER_HIGH = 1,
};

enum rcpi_sta_state {
	RCPI_STA_CONNECTED,
	RCPI_STA_USTA_CHECK,
	RCPI_STA_BCN_CHECK,
	RCPI_STA_NOP,
	RCPI_STA_NOSTEER,
	RCPI_STA_DISCONNECTED,
};

/* per-sta steer context data */
struct rcpi_steer_sta_data {
	uint8_t macaddr[6];
	time_t t0, tprev;
	struct {
		uint8_t sample;
		uint8_t dt;
	} rcpi[NUM_RCPI_SAMPLES];
	uint8_t t;
	uint8_t rcpi_hysteresis;
	uint8_t rcpi_low_trigger_cnt;
	uint8_t rcpi_high_trigger_cnt;
	uint8_t diffsnr;

	enum rcpi_sta_state state;

	time_t lowrcpi_tstart;
	time_t lowrcpi_tend;
	bool lowrcpi_epoch;

	time_t nop_tstart;

	time_t bcn_query_time;
	time_t usta_query_time;

	uint8_t connected;
	bool bcn_metrics_processed;
	bool bcn_query_sent;
	bool check_bcn_report;

	bool usta_query_sent;
	bool usta_metrics_processed;

	bool cleanup;

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

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

	bool steer_attempt_pending;

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

/* rcpi plugin private config options.
 *
 * config sta-steer 'rcpi'
 *	option enabled '1              # rcpi plugin is enabled or not
 *	option steer_retry_int '30'    # steer retry interval in seconds
 *	option steer_int '180'         # steer interval in seconds
 *	option steer_disable_int '600' # steer disable interval in seconds
 *	option diffsnr '8'             # target-AP should be better by this SNR (in dB)
 *	option bandsteer '0            # steer to different band
 *	option use_bcn_metrics '1'     # use Beacon Report in steering decision
 *	option use_usta_metrics '1'    # use unassociated sta metrics from neighboring APs
 *	option max_steer_attempt '-1'  # max steer attempts in a connection session
 *
 */

/* rcpi private context */
struct rcpi_steer_control {
	bool enabled;
	uint8_t rcpi_hysteresis;
	uint8_t rcpi_threshold;
	uint8_t report_rcpi_threshold;
	uint8_t diffsnr;
	bool bandsteer;
	int max_btm_attempt;
	uint32_t steer_int;
	uint32_t steer_retry_int;
	uint32_t steer_disable_int;
	bool use_bcn_metrics;
	bool use_usta_metrics;
	uint32_t sta_count;
	struct hlist_head sta_table[STA_MAX_NUM];

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

static int rcpi_steer_init(void **priv);
static int rcpi_steer_configure(void *priv, void *config_section);
static int rcpi_steer_exit(void *priv);

static void rcpi_reset_steer_sta_data(struct rcpi_steer_sta_data *st);

static int rcpi_query_sta_metrics(struct rcpi_steer_control *sc, struct steer_sta *s);

static const char *rcpi_sta_state_string(enum rcpi_sta_state s)
{
	switch (s) {
	case RCPI_STA_CONNECTED:
		return "Connected";
	case RCPI_STA_DISCONNECTED:
		return "Disconnected";
	case RCPI_STA_USTA_CHECK:
		return "Unassoc-STA metrics check";
	case RCPI_STA_BCN_CHECK:
		return "Beacon metrics check";
	case RCPI_STA_NOP:
		return "Idle";
	case RCPI_STA_NOSTEER:
		return "No Steer";
	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 rcpi_sta_table_entry_process(struct rcpi_steer_control *sc,
				 struct rcpi_steer_sta_data *st)
{
	struct wifi_sta_element *sta;
	int action = STA_TIMER_NOP;
	time_t now = time(NULL);
	struct steer_sta *s;
	void *cntlr;

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


	/* if action = STA_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_STEER, "STA " MACFMT ": Reset steer data\n",
			  MAC2STR(st->macaddr));

		rcpi_reset_steer_sta_data(st);
		return STA_TIMER_NOP;
	}

	switch (st->state) {
	case RCPI_STA_USTA_CHECK:
		if (st->usta_query_sent && difftime(now, st->usta_query_time) >= USTA_RESPONSE_TIMEOUT) {
			st->state = RCPI_STA_BCN_CHECK;
			st->check_bcn_report = 1;

			cntlr_dbg(LOG_STEER,
				  "rcpi_sta_timer_cb: STA " MACFMT ": Unassoc-STA meas not available yet\n",
				  MAC2STR(st->macaddr));

			/* send sta-metrics-query to know updated metrics */
			rcpi_query_sta_metrics(sc, s);
		}
		break;
	case RCPI_STA_BCN_CHECK:
		if (st->bcn_query_sent && difftime(now, st->bcn_query_time) >= BEACON_RESPONSE_TIMEOUT) {
			st->state = RCPI_STA_NOP;
			cntlr_dbg(LOG_STEER,
				  "rcpi_sta_timer_cb: STA " MACFMT ": Beacon Report not available yet\n",
				  MAC2STR(st->macaddr));

			/* enter 'steer_int' nop interval */
			time(&st->nop_tstart);
			st->interval = sc->steer_int * 1000;
			action = STA_TIMER_ADD;
		}
		break;
	case RCPI_STA_NOP:
		if (difftime(now, st->nop_tstart) >= sc->steer_int) {
			/* exit nop state */
			st->state = RCPI_STA_CONNECTED;
			st->nop_tstart = 0;
			st->usta_query_sent = 0;
			st->bcn_query_sent = 0;
			st->check_bcn_report = 0;

			rcpi_query_sta_metrics(sc, s);
		}
		break;
	default:
		break;
	}

	return action;
}

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

	for (i = 0; i < STA_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 rcpi_sta_timer_set(struct rcpi_steer_control *sc,
			struct rcpi_steer_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, >)) { /* cppcheck-suppress syntaxError */
			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 rcpi_sta_timer_reset(struct rcpi_steer_control *sc,
			  struct rcpi_steer_sta_data *st)
{
	rcpi_sta_timer_set(sc, st, STA_INTERVAL_MAX * 1000);
}

#if 0
void rcpi_sta_timer_del(struct rcpi_steer_control *sc,
			struct rcpi_steer_sta_data *st)
{
	struct timeval now = { 0 };

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

static void rcpi_sta_timer_cb(struct rcpi_steer_control *priv,
			      struct hlist_head *head,
			      struct timeval *min_next_tmo)
{
	struct rcpi_steer_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 = STA_TIMER_ADD;
		struct timeval new_next_tmo = { 0 };

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

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

		switch (action) {
		case STA_TIMER_DEL:
			cntlr_dbg(LOG_TIMER, "Remove timer for " MACFMT "\n", MAC2STR(e->macaddr));
			//rcpi_sta_timer_reset(priv, e);	//TODO: remove
			break;
		case STA_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_STEER,
					  "Adjusted next-tmo = (%jd.%jd)\n",
					  (uintmax_t) min_next_tmo->tv_sec,
					  (uintmax_t) min_next_tmo->tv_usec); */
			}
			break;
		}
	}
}

void rcpi_timer_cb(atimer_t *t)
{
	struct rcpi_steer_control *p = container_of(t, struct rcpi_steer_control, t);
	struct timeval min_next_tmo = { .tv_sec = STA_INTERVAL_MAX };
	struct timeval now;
	int remain_cnt = 0;
	int i;

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

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

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

	/*
	cntlr_dbg(LOG_STEER,
		  "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_STEER,
				  "Next timer: set after %u ms, sta-cnt = %d\n",
				  tmo_msecs, remain_cnt);
			*/

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

static int is_meas_old(time_t now, time_t t, uint32_t age)
{
	return (difftime(now, t) > age) ? 1 : 0;
}

static int is_bcnreport_usable(struct rcpi_steer_sta_data *st,
			       struct wifi_sta_meas_report *meas)
{
	int latest, idx;
	int dtsum = 0;
	int dt_rpt;
	int num, i;

	if (st->t == 0) {
		/* report's timestamp is withn 3s of start; treat as valid */
		dt_rpt = (int)difftime(time(NULL), meas->rpt_time);

		return dt_rpt >= 0 && dt_rpt <= 3 ? 1 : 0;
	}

	dt_rpt = (int)difftime(st->tprev, meas->rpt_time);
	if (dt_rpt < 0)
		return 0;

	if (dt_rpt >= 0 && dt_rpt <= 3)
		return 1;

	num = st->t < NUM_RCPI_SAMPLES ? st->t : NUM_RCPI_SAMPLES;
	latest = (st->t - 1) % NUM_RCPI_SAMPLES;
	idx = latest;

	for (i = 0; i < num; i++) {
		dtsum += st->rcpi[idx].dt;
		if (dtsum >= dt_rpt)
			break;

		idx = (idx - 1 + NUM_RCPI_SAMPLES) % NUM_RCPI_SAMPLES;
	}

	if (dtsum < dt_rpt)
		return 0;	/* meas too old */

	/* if STA's ul-RCPI at that instanst is roughly equal to latest, then
	 * it is fair to assume that dl-RCPI measured then will be still valid
	 * and usable.
	 */
	if (abs(st->rcpi[idx].sample - st->rcpi[latest].sample) <= 6) {
		cntlr_dbg(LOG_STEER,
			  "%s: STA " MACFMT " BCN-Rpt of %us old is usable (ul-rcpi[%d] = %u, ul-rcpi[%d] = %u)\n",
			  __func__, MAC2STR(st->macaddr),
			  dt_rpt,
			  idx, st->rcpi[idx].sample,
			  latest, st->rcpi[latest].sample);
		return 1;
	}

	return 0;
}

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

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

	return sc->bandsteer ? true : false;
}

struct rcpi_steer_sta_data *rcpi_lookup_sta(struct rcpi_steer_control *ctl,
					    uint8_t *macaddr)
{
	struct rcpi_steer_sta_data *st = NULL;
	int idx = sta_table_hash(macaddr);

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

	return NULL;
}

int rcpi_query_sta_metrics(struct rcpi_steer_control *sc, struct steer_sta *s)
{
	cntlr_trace(LOG_STEER, "%s: Enter\n", __func__);

	void *cntlr = ((struct steer_control *)(sc->self))->controller;
	struct blob_buf bi = { 0 };
	struct blob_buf bo = { 0 };
	char stamac[18] = {0};
	char agent[18] = {0};
	int ret = 0;

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

	hwaddr_ntoa(s->bss.agent, agent);
	blobmsg_add_string(&bi, "agent", agent);

	hwaddr_ntoa(s->macaddr, stamac);
	blobmsg_add_string(&bi, "sta", stamac);

	ret = COMMAND(query_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_STEER,
				  "%s: STA " MACFMT ": Request STA-Metrics cmdu mid = %hu\n",
				  __func__, MAC2STR(s->macaddr), req_mid);
		}
	} else {
		cntlr_warn(LOG_STEER, "%s: failed to Request STA-Metrics\n", __func__);
	}

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

	cntlr_trace(LOG_STEER, "%s: Exit\n", __func__);
	return ret;
}

int rcpi_query_unassoc_sta_metrics(struct rcpi_steer_control *sc, struct rcpi_steer_sta_data *st)
{
	cntlr_trace(LOG_STEER, "%s: Enter\n", __func__);

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

	if (s->num_nbr == 0)
		return -1;

	/* Request unassoc-sta-metrics reports from all neighbor fBSSs */
	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 STA's current Agent as it will return error */
		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);
		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);

		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_STEER,
					  "%s: STA " MACFMT ": Request Unassoc-STA 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_warn(LOG_STEER,
				  "%s: failed to request Unassoc-STA metrics\n", __func__);
		}

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

	cntlr_trace(LOG_STEER, "%s: Exit\n", __func__);
	return ret;
}

int rcpi_query_beacon_metrics(struct rcpi_steer_control *sc, struct rcpi_steer_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_STEER, "%s: Enter (num-nbr = %d)\n", __func__, s->num_nbr);

	/* controller has not passed nbr fBSS list yet */
	if (s->num_nbr == 0)
		return -1;

	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 };

		cntlr_dbg(LOG_STEER, "%s: Nbr-AP " MACFMT ", opclass = %d, channel = %d\n",
			  __func__, MAC2STR(nbr->bssid), nbr->opclass, nbr->channel);

#ifdef SKIP_OFFCHANNEL_BEACON_REQUEST
		/* skip off-channel request */
		if (s->bss.channel != nbr->channel)
			continue;
#endif

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

		blobmsg_add_macaddr(&bi, "agent", s->bss.agent);
		blobmsg_add_macaddr(&bi, "sta", s->macaddr);
		blobmsg_add_string(&bi, "ssid", (char *)s->bss.ssid);

		blobmsg_add_macaddr(&bi, "bssid", nbr->bssid);
		blobmsg_add_u32(&bi, "opclass", nbr->opclass);
		blobmsg_add_u32(&bi, "channel", nbr->channel);

		ret = COMMAND(query_beacon_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_STEER,
					  "%s: req-bcn-metrics cmdu mid = %hu\n",
					  __func__, req_mid);

				time(&st->bcn_query_time);
				st->bcn_query_sent = true;
			}
		} else {
			cntlr_warn(LOG_STEER,
				  "%s: failed to send request-beacon-metrics\n", __func__);
		}

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

	cntlr_trace(LOG_STEER, "%s: Exit\n", __func__);
	return ret;
}

void rcpi_reset_steer_sta_data(struct rcpi_steer_sta_data *st)
{
	struct steer_sta *sta = (struct steer_sta *)st->sta;

	st->t = 0;
	memset(st->rcpi, 0, sizeof(st->rcpi));
	st->tprev = 0;

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

	st->state = st->connected ? RCPI_STA_CONNECTED : RCPI_STA_DISCONNECTED;
	st->bcn_metrics_processed = false;
	st->bcn_query_sent = false;
	st->check_bcn_report = false;
	st->usta_query_sent = false;
	st->usta_metrics_processed = false;
	st->lowrcpi_epoch = 0;
	st->cleanup = false;
	st->steer_attempt_pending = false;
	st->num_steer_attempt = 0;
}

void rcpi_print_steer_sta_data(struct rcpi_steer_sta_data *st)
{
#define NUM_USTA_METRICS	16
#define NUM_BCN_METRICS		16
	cntlr_dbg(LOG_STEER, "STA = " MACFMT "\n", MAC2STR(st->macaddr));
	cntlr_dbg(LOG_STEER, "BSSID = " MACFMT "\n", MAC2STR(((struct steer_sta *)st->sta)->bss.bssid));
	cntlr_dbg(LOG_STEER, "state = %s\n", rcpi_sta_state_string(st->state));
	cntlr_dbg(LOG_STEER, "lowrcpi = %d\n", st->lowrcpi_epoch);
	cntlr_dbg(LOG_STEER, "usta-query-sent = %d\n", st->usta_query_sent);
	cntlr_dbg(LOG_STEER, "bcn-query-sent = %d\n", st->bcn_query_sent);
	cntlr_dbg(LOG_STEER, "usta-metrics-processed = %d\n", st->usta_metrics_processed);
	cntlr_dbg(LOG_STEER, "bcn-metrics-processed = %d\n", st->bcn_metrics_processed);
	cntlr_dbg(LOG_STEER, "config: rcpi-report-threshold = %u\n",
		  ((struct steer_sta *)st->sta)->bss.report_rcpi_threshold);
	cntlr_dbg(LOG_STEER, "config: rcpi-steering-threshold = %u\n",
		  ((struct steer_sta *)st->sta)->bss.rcpi_threshold);
	cntlr_dbg(LOG_STEER, "config: rcpi-hysteresis = %u\n", st->rcpi_hysteresis);
	cntlr_dbg(LOG_STEER, "config: rcpi-low-trigger-cnt = %u\n", st->rcpi_low_trigger_cnt);
	cntlr_dbg(LOG_STEER, "config: rcpi-high-trigger-cnt = %u\n", st->rcpi_high_trigger_cnt);
	cntlr_dbg(LOG_STEER, "config: diffsnr = %u\n", st->diffsnr);

	if (st->t != 0) {
		char rbuf[NUM_RCPI_SAMPLES * 10 + 64] = {0};
		char buf[1024] = {0};
		int comma = 0;
		int start = 0;
		int end = 0;

		end = st->t < NUM_RCPI_SAMPLES ? st->t : st->t % NUM_RCPI_SAMPLES;
		if (st->t >= NUM_RCPI_SAMPLES)
			start = st->t % NUM_RCPI_SAMPLES;

		snprintf(buf, sizeof(buf) - 1, "rcpi = [");

		do {
			snprintf(rbuf + strlen(rbuf),
				 sizeof(rbuf) - 1 - strlen(rbuf), "(%u,%u),",
				 st->rcpi[start].dt, st->rcpi[start].sample);

			start = (start + 1) % NUM_RCPI_SAMPLES;
			comma = 1;
		} while (start != end);

		snprintf(buf + strlen(buf), sizeof(buf) - 1 - strlen(buf), "%s", rbuf);
		snprintf(buf + strlen(buf) -comma , sizeof(buf) - 1 - strlen(buf), "%s", "]");

		cntlr_dbg(LOG_STEER, "%s\n", buf);
	}

	if (list_empty(((struct steer_sta *)st->sta)->unassoc_metriclist)) {
		cntlr_dbg(LOG_STEER, "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_STEER, "%s\n", buf);
	}

	if (list_empty(((struct steer_sta *)st->sta)->meas_reportlist)) {
		cntlr_dbg(LOG_STEER, "beacon-metrics-list = []\n");
	} else {
		char bbuf[25 * NUM_BCN_METRICS] = {0};
		struct wifi_sta_meas_report *b = NULL;
		char buf[1024] = {0};
		int comma = 1;
		int cnt = 0;

		snprintf(buf, sizeof(buf) - 1, "beacon-metrics-list = [");
		list_for_each_entry(b, ((struct steer_sta *)st->sta)->meas_reportlist, list) {
			snprintf(bbuf + strlen(bbuf),
				 sizeof(bbuf) - 1 - strlen(bbuf), "("MACFMT",%u),",
				 MAC2STR(b->bssid), b->rcpi);

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

void rcpi_dump_sta_table(void *priv, uint8_t *macaddr)
{
	struct rcpi_steer_control *ctl = (struct rcpi_steer_control *)priv;
	struct rcpi_steer_sta_data *st = NULL;
	int i;

	cntlr_dbg(LOG_STEER, "-------- STA Table ---------\n");
	for (i = 0; i < STA_MAX_NUM; i++) {
		if (hlist_empty(&ctl->sta_table[i]))
			continue;

		hlist_for_each_entry(st, &ctl->sta_table[i], hlist) {
			if (!macaddr || !memcmp(macaddr, st->macaddr, 6)) {
				rcpi_print_steer_sta_data(st);
			}
		}
	}
	cntlr_dbg(LOG_STEER, "----------------------------\n");
}

/* Function returns
 * -1 when rcpi < report_rcpi_threshold for consecutive rcpi_low_trigger_cnt,
 * +1 when rcpi >= report_rcpi_threshold for consecutive rcpi_high_trigger_cnt,
 * 0 otherwise.
 */
int rcpi_sta_trigger_check(struct rcpi_steer_sta_data *st)
{
	uint8_t report_rcpi_threshold =
		((struct steer_sta *)st->sta)->bss.report_rcpi_threshold;
	int highrcpi_cnt = 0;
	int lowrcpi_cnt = 0;
	int start = 0;
	int end = 0;

	if (st->t < st->rcpi_low_trigger_cnt)
		return RCPI_TRIGGER_NONE;

	end = st->t < NUM_RCPI_SAMPLES ? st->t : st->t % NUM_RCPI_SAMPLES;
	if (st->t >= NUM_RCPI_SAMPLES)
		start = st->t % NUM_RCPI_SAMPLES;

	cntlr_trace(LOG_STEER, "Check rcpi-trigger type\n");
	do {
		end--;
		if (end < 0)
			end = NUM_RCPI_SAMPLES - 1;

		cntlr_trace(LOG_STEER, "[%d] = (%u, %u)\n",
			    end, st->rcpi[end].dt, st->rcpi[end].sample);

		if (st->rcpi[end].sample < report_rcpi_threshold) {
			highrcpi_cnt = 0;
			lowrcpi_cnt++;
			if (lowrcpi_cnt >= st->rcpi_low_trigger_cnt) {
				if (!st->lowrcpi_epoch) {
					time(&st->lowrcpi_tstart);
					st->lowrcpi_epoch = 1;
					return RCPI_TRIGGER_LOW;
				}

				return RCPI_TRIGGER_NONE;
			}
		} else {
			lowrcpi_cnt = 0;
			highrcpi_cnt++;
			if (highrcpi_cnt >= st->rcpi_high_trigger_cnt) {
				if (st->lowrcpi_epoch) {
					time(&st->lowrcpi_tend);
					st->lowrcpi_epoch = 0;
					return RCPI_TRIGGER_HIGH;
				}

				return RCPI_TRIGGER_NONE;
			}
		}
	} while (end != start);

	return RCPI_TRIGGER_NONE;
}

int rcpi_steer_ok(struct rcpi_steer_sta_data *st)
{
	struct steer_sta *s = ((struct steer_sta *)st->sta);

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

	cntlr_info(LOG_STEER,
		  "rcpi_steer: decision is steer STA " MACFMT " to new AP " MACFMT " with %s-RCPI %u\n",
		  MAC2STR(s->macaddr),
		  MAC2STR(s->target.bssid),
		  s->target.ul_rcpi ? "UL" : "DL",
		  s->target.ul_rcpi ? s->target.ul_rcpi : s->target.dl_rcpi);

	return 0;
}

int rcpi_steer(void *priv, struct steer_sta *s, uint16_t rxcmdu_type)
{
	struct rcpi_steer_control *sc = (struct rcpi_steer_control *)priv;
	struct rcpi_steer_sta_data *st = NULL;
	int rcpi_trigger = RCPI_TRIGGER_NONE;
	struct wifi_sta_element *sta;
	bool best_found = false;
	time_t now = time(NULL);
	uint8_t best_channel;
	uint8_t *best_agent;
	uint8_t *best_bssid;
	uint8_t best_rcpi;
	void *cntlr;


	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_STEER, "%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_STEER, "%s: Incomplete/invalid STA data. Exit\n", __func__);

		return 0;
	}

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

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

	cntlr_dbg(LOG_STEER, "%s: STA " MACFMT ", rcpi = %u --------->\n",
		  __func__, MAC2STR(s->macaddr), sta->rcpi);

	best_rcpi = sta->rcpi;
	best_channel = s->bss.channel;
	best_agent = s->bss.agent;
	best_bssid = s->bss.bssid;

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

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

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

		st->priv = sc;
		st->sta = s;
		memcpy(st->macaddr, sta->macaddr, 6);
		st->t = 0;
		time(&st->t0);
		st->diffsnr = sc->diffsnr;
		st->rcpi_hysteresis = sc->rcpi_hysteresis;
		st->rcpi_low_trigger_cnt = DEFAULT_RCPI_LOW_CNT;
		st->rcpi_high_trigger_cnt = DEFAULT_RCPI_HIGH_CNT;

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

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

		rcpi_sta_timer_reset(sc, st);
	}

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

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

			rcpi_reset_steer_sta_data(st);
			rcpi_sta_timer_reset(sc, st);
			return 0;
		}
	}

	/* update STA's rcpi samples */
	if (rxcmdu_type == CMDU_ASSOC_STA_LINK_METRICS_RESPONSE ||
	    rxcmdu_type == CMDU_AP_METRICS_RESPONSE) {
		st->rcpi[st->t % NUM_RCPI_SAMPLES].sample = sta->rcpi;
		st->rcpi[st->t % NUM_RCPI_SAMPLES].dt = st->tprev ? difftime(now, st->tprev) : 0;
		st->t++;
		st->tprev = now;

		rcpi_dump_sta_table(sc, sta->macaddr);

		rcpi_trigger = rcpi_sta_trigger_check(st);
		cntlr_dbg(LOG_STEER, "%s: rcpi-trigger = %s\n", __func__,
			  rcpi_trigger == RCPI_TRIGGER_LOW ? "LOW" :
			  rcpi_trigger == RCPI_TRIGGER_HIGH ? "HIGH" : "NONE");
	}

	if (rxcmdu_type == CMDU_CLIENT_STEERING_BTM_REPORT) {
		if (st->steer_attempt_pending)
			st->steer_attempt_pending = 0;

		return 0;
	}

	cntlr_dbg(LOG_STEER,
		  "%s: STA " MACFMT " connected to " MACFMT " on channel %d has RCPI = %u\n",
		  __func__, MAC2STR(sta->macaddr), MAC2STR(s->bss.bssid),
		  s->bss.channel, sta->rcpi);


	if (sc->max_btm_attempt != DEFAULT_MAX_STEER_ATTEMPT &&
	    st->num_steer_attempt > sc->max_btm_attempt) {
		cntlr_dbg(LOG_STEER,
			  "%s: Skip STA " MACFMT " steer check. "
			  "Num-attempts (%d) > max-attempts (%d)\n",
			  __func__, MAC2STR(sta->macaddr),
			  st->num_steer_attempt, sc->max_btm_attempt);
		return 0;
	}

	if (st->lowrcpi_epoch) {
		int ret;

		/* return if the last steer attempt is pending */
		if (st->steer_attempt_pending)
			return 0;

		if (st->usta_query_sent) {
			struct unassoc_sta_metrics *u = NULL;
			uint8_t diffsnr = st->diffsnr;

			list_for_each_entry(u, s->unassoc_metriclist, list) {
				time_t meas_time;

				st->usta_metrics_processed = 1;

				meas_time = u->rpt_time - (u->time_delta / 1000);
				if (is_meas_old(now, meas_time, USTA_METRICS_AGEOUT))
					continue;

				if (!sc->bandsteer && u->channel != s->bss.channel)
					continue;

				if (!memcmp(u->bssid, s->bss.bssid, 6))
					continue;

				if (u->ul_rcpi >= best_rcpi + diffsnr) {
					best_found = true;
					diffsnr = 0;
					best_rcpi = u->ul_rcpi;
					best_agent = u->agent_macaddr;
					best_bssid = u->bssid;
					best_channel = u->channel;
				}
			}
		}

		if (best_found) {
			/* better AP found using unassoc-sta metrics */
			if (sta->rcpi <= s->bss.rcpi_threshold) {
				memcpy(s->target.bssid, best_bssid, 6);
				memcpy(s->target.agent, best_agent, 6);
				s->target.channel = best_channel;
				s->target.ul_rcpi = best_rcpi;
				s->target.dl_rcpi = 0;

				return rcpi_steer_ok(st);
			}

			return 0;
		}

		if (st->bcn_query_sent) {
			struct wifi_sta_meas_report *own_report = NULL;
			struct wifi_sta_meas_report *b = NULL;
			uint8_t diffsnr = st->diffsnr;

			/* 1. find bcn-report for STA's current BSS */
			list_for_each_entry(b, s->meas_reportlist, list) {
				/* most recent report(s) are at list-head */
				cntlr_dbg(LOG_STEER,
					  "%s: STA " MACFMT " BCN-Rpt: bss = " MACFMT", rcpi = %u, ch = %d, opclass = %d\n",
					  __func__, MAC2STR(sta->macaddr),
					  MAC2STR(b->bssid), b->rcpi, b->channel, b->opclass);

				if (!is_bcnreport_usable(st, b)) {
					cntlr_dbg(LOG_STEER, "%s: Ignore %ds old bcn-report\n",
						  __func__, (int)difftime(now, b->rpt_time));
					continue;
				}

				if (!sc->bandsteer &&
				    wifi_opclass_get_band(b->opclass) != wifi_opclass_get_band(s->bss.opclass)) {
					continue;
				}

				st->bcn_metrics_processed = 1;

				if (!memcmp(b->bssid, s->bss.bssid, 6)) {
					own_report = b;
					break;
				}
			}

			if (!own_report)
				return 0;

			/* 2. compare own bcn-report with others' */
			best_rcpi = own_report->rcpi;
			best_bssid = own_report->bssid;
			best_channel = own_report->channel;
			best_agent = own_report->agent_macaddr;

			list_for_each_entry(b, s->meas_reportlist, list) {
				if (!memcmp(b->bssid, s->bss.bssid, 6))
					continue;

				if (!sc->bandsteer &&
				    wifi_opclass_get_band(b->opclass) != wifi_opclass_get_band(s->bss.opclass)) {
					continue;
				}

				if (!is_bcnreport_usable(st, b)) {
					cntlr_dbg(LOG_STEER, "%s: Ignore %ds old bcn-report\n",
						  __func__, (int)difftime(now, b->rpt_time));
					continue;
				}

				if (b->rcpi >= best_rcpi + diffsnr) {
					best_found = true;
					diffsnr = 0;
					best_rcpi = b->rcpi;
					best_bssid = b->bssid;
					best_channel = b->channel;
					best_agent = b->agent_macaddr;
				}
			}
		}

		if (best_found) {
			/* better AP found using beacon-report */
			if (sta->rcpi <= s->bss.rcpi_threshold) {
				memcpy(s->target.bssid, best_bssid, 6);
				memcpy(s->target.agent, best_agent, 6);
				s->target.channel = best_channel;
				s->target.dl_rcpi = best_rcpi;
				s->target.ul_rcpi = 0;

				return rcpi_steer_ok(st);
			}

			return 0;
		}

		if (!st->usta_query_sent) {
			ret = rcpi_query_unassoc_sta_metrics(sc, st);
			if (!ret && st->usta_query_sent) {
				st->state = RCPI_STA_USTA_CHECK;
				st->usta_metrics_processed = 0;
				rcpi_sta_timer_set(sc, st, USTA_RESPONSE_TIMEOUT * 1000);
			}
		} else if (st->check_bcn_report && !st->bcn_query_sent) {
			ret = rcpi_query_beacon_metrics(sc, st);
			if (!ret && st->bcn_query_sent) {
				st->state = RCPI_STA_BCN_CHECK;
				st->bcn_metrics_processed = 0;
				rcpi_sta_timer_set(sc, st, BEACON_RESPONSE_TIMEOUT * 1000);
			}
		}

		return 0;
	}

	/* curr-rcpi >= report_rcpi_threshold */
	if (rcpi_trigger == RCPI_TRIGGER_HIGH) {
		rcpi_reset_steer_sta_data(st);
	}

	return 0;
}

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

	//TODO

	return 0;
}

uint16_t cmdu_notifylist[] = {
	CMDU_ASSOC_STA_LINK_METRICS_RESPONSE,
	CMDU_BEACON_METRICS_RESPONSE,
	CMDU_UNASSOC_STA_LINK_METRIC_RESPONSE,
	CMDU_TYPE_TOPOLOGY_NOTIFICATION,
	CMDU_ASSOCIATION_STATUS_NOTIFICATION,
	CMDU_CLIENT_STEERING_BTM_REPORT,
	CMDU_STEERING_COMPLETED,
};

struct steer_control rcpi = {
	.name = RCPI_PLUGIN_NAME,
	.init = rcpi_steer_init,
	.config = rcpi_steer_configure,
	.exit = rcpi_steer_exit,
	.steer = rcpi_steer,
	.cmdu_notifier = rcpi_cmdu_notifier,
	.num_cmdu = ARRAY_SIZE(cmdu_notifylist),
	.cmdulist = cmdu_notifylist,
};

static int rcpi_steer_init(void **priv)
{
	struct rcpi_steer_control *p;

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

	*priv = p;
	p->self = &rcpi;
	p->enabled = true;
	p->rcpi_threshold = DEFAULT_RCPI_THRESHOLD;
	p->rcpi_hysteresis = DEFAULT_RCPI_HYSTERESIS;
	p->diffsnr = DEFAULT_RCPI_DIFFSNR;
	p->max_btm_attempt = DEFAULT_MAX_STEER_ATTEMPT;
	p->steer_int = DEFAULT_STEER_INT;
	p->steer_retry_int = DEFAULT_STEER_RETRY_INT;
	p->steer_disable_int = DEFAULT_STEER_DISABLE_INT;

	timer_init(&p->t, rcpi_timer_cb);

	cntlr_info(LOG_STEER, "%s", "RCPI based STA steering initalized");
	return 0;
}

static int rcpi_steer_configure(void *priv, void *config_section)
{
	enum {
		RCPI_ENABLED,
		RCPI_ALLOW_BANDSTEER,
		RCPI_DIFFSNR,
		RCPI_STEER_RETRY_INT,
		RCPI_STEER_INT,
		RCPI_STEER_DISABLE_INT,
		RCPI_USE_BCN_METRICS,
		RCPI_USE_USTA_METRICS,
		NUM_RCPI_ATTRS,
	};
	const struct uci_parse_option opts[] = {
		[RCPI_ENABLED] = { .name = "enabled", .type = UCI_TYPE_STRING },
		[RCPI_ALLOW_BANDSTEER] = { .name = "bandsteer", .type = UCI_TYPE_STRING },
		[RCPI_DIFFSNR] = { .name = "diffsnr", .type = UCI_TYPE_STRING },
		[RCPI_STEER_RETRY_INT] = { .name = "steer_retry_int", .type = UCI_TYPE_STRING },
		[RCPI_STEER_INT] = { .name = "steer_int", .type = UCI_TYPE_STRING },
		[RCPI_STEER_DISABLE_INT] = { .name = "steer_disable_int", .type = UCI_TYPE_STRING },
		[RCPI_USE_BCN_METRICS] = { .name = "use_bcn_metrics", .type = UCI_TYPE_STRING },
		[RCPI_USE_USTA_METRICS] = { .name = "use_usta_metrics", .type = UCI_TYPE_STRING },
	};
	struct rcpi_steer_control *p = (struct rcpi_steer_control *)priv;
	struct uci_section *s = (struct uci_section *)config_section;
	struct uci_option *tb[NUM_RCPI_ATTRS] = {0};
	char *endptr = NULL;

	cntlr_trace(LOG_STEER, "%s: Enter\n", __func__);
	if (!p)
		return -1;

	uci_parse_section(s, opts, NUM_RCPI_ATTRS, tb);

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

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

		enbl = strtol(val, &endptr, 10);
		p->enabled = (enbl == 1) ? true : false;
	}

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

		enbl = strtol(val, &endptr, 10);
		p->bandsteer = (enbl == 1) ? true : false;
	}

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

		diffsnr = strtol(val, &endptr, 10);
		if (diffsnr > 0)
			p->diffsnr = diffsnr * 2;	/* store as SNI */
	}

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

		ival = strtol(val, &endptr, 10);
		if (ival > 0)
			p->steer_retry_int = ival;
	}

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

		ival = strtol(val, &endptr, 10);
		if (ival > 0)
			p->steer_int = ival;
	}

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

		ival = strtol(val, &endptr, 10);
		if (ival > 0)
			p->steer_disable_int = ival;
	}

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

		enbl = strtol(val, &endptr, 10);
		p->use_bcn_metrics = (enbl == 1) ? true : false;
	}

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

		enbl = strtol(val, &endptr, 10);
		p->use_usta_metrics = (enbl == 1) ? true : false;
	}

	cntlr_trace(LOG_STEER, "%s: Exit\n", __func__);
	return 0;
}

static int rcpi_steer_exit(void *priv)
{
	struct rcpi_steer_control *p = (struct rcpi_steer_control *)priv;

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

	cntlr_trace(LOG_STEER, "%s: Exit\n", __func__);
	return 0;
}

