#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include "backhaul.h"

#include <easy/utils.h>
#include <easy/if_utils.h>
#include <easy/timestamp.h>
#include <easy/utils.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/list.h>
#include <net/if.h>
#include <stdio.h>
#include <string.h>
#include <timer_impl.h>
#include <uci.h>

#include "agent.h"
#include "agent_ubus.h"
#include "config.h"
#if (EASYMESH_VERSION >= 6)
#include "mld.h"
#endif
#include "timer.h"
#include "timeutils.h"
#include "utils/debug.h"
#include "utils/utils.h"
#include "wifi.h"

struct ubus_request;



void dynbh_update_agent_connect_t(struct agent *a)
{
	timestamp_update(&a->connect_t);
}

void dynbh_update_agent_disconnect_t(struct agent *a)
{
	timestamp_update(&a->disconnect_t);
}

void dynbh_update_bk_connect_t(struct agent *a, struct netif_bk *bk)
{
	if (timestamp_greater_than(&bk->connect_t, &bk->disconnect_t)) {
		bk->total_connect_time += timestamp_elapsed_sec(&bk->connect_t);
		a->wifi_backhaul_time += timestamp_elapsed_sec(&bk->connect_t);
	}

	timestamp_update(&a->connect_t);
	timestamp_update(&bk->connect_t);
}

void dynbh_update_bk_disconnect_t(struct agent *a, struct netif_bk *bk)
{
	if (timestamp_greater_than(&bk->connect_t, &bk->disconnect_t)) {
		bk->total_connect_time += timestamp_elapsed_sec(&bk->connect_t);
		a->wifi_backhaul_time += timestamp_elapsed_sec(&bk->connect_t);
	}

	timestamp_update(&bk->disconnect_t);
}

/* enable all bstas and start scanning
*/
void dynbh_bsta_enable_all(struct agent *a)
{
	if (a->cfg.backhaul_override || agent_bsta_steer_is_active_all(a))
		return;

	agent_exec_platform_scripts("bsta_enable_all");
}

/* disconnect and disable all links except passed ifname */
void dynbh_bsta_use_link(struct agent *a, char *ifname)
{
	char fmt[64] = {0};

	UNUSED(a);

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld && !a->cfg.physical_mld)
		return;
#endif

	snprintf(fmt, sizeof(fmt), "bsta_use_link %s", ifname);
	agent_exec_platform_scripts(fmt);
}

void dynbh_bsta_disable_lower_priority(struct agent *a, char *ifname)
{
	char fmt[64] = {0};

	if (a->cfg.backhaul_override || agent_bsta_steer_is_active_all(a))
		return;

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld && !a->cfg.physical_mld)
		return;
#endif
	dbg("%s: disabling lower links than bsta:%s override:%d\n",
	    __func__, ifname, a->cfg.backhaul_override);

	snprintf(fmt, sizeof(fmt), "bsta_disable_lower_priority %s", ifname);
	agent_exec_platform_scripts(fmt);
}

void dynbh_bsta_scan_on_enabled(struct agent *a)
{
	UNUSED(a);

	if (a->cfg.backhaul_override || agent_bsta_steer_is_active_all(a))
		return;

	agent_exec_platform_scripts("bsta_scan_on_enabled");
}

void dynbh_bsta_clear_bssid(struct agent *a, char *ifname)
{
	char fmt[64] = {0};

	UNUSED(a);

	strncpy(fmt, "bsta_clear_bssid", sizeof(fmt) - 1);

	if (ifname) {
		snprintf(fmt + strlen(fmt),
			 sizeof(fmt) - strlen(fmt), " %s",
			 ifname);
	}

	agent_exec_platform_scripts(fmt);
}

void dynbh_bsta_clear_all_bssid(struct agent *a)
{
	dynbh_bsta_clear_bssid(a, NULL);
}

struct netif_bk *dynbh_get_best_connected_bsta(struct agent *a)
{
	struct wifi_radio_element *re = NULL;
	struct netif_bk *best = NULL;

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

		if (!re->has_bsta)
			continue;

		dbg("%s: bsta %s enabled %d connected %d priority %u\n",
		    __func__, bk->ifname, bk->cfg->enabled,
		    bk->connected, bk->cfg->priority);

		if (!bk->cfg->enabled || !bk->connected)
			continue;

#if (EASYMESH_VERSION >= 6)
		if (best && best->is_affiliated_sta && bk->is_affiliated_sta)
			continue;
#endif

		if (!best || bk->cfg->priority < best->cfg->priority)
			best = bk;
	}

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld) {
		struct netif_bk *bk = &a->bstamld->bk;

		if (!a->cfg.physical_mld && a->bstamld->setup_link)
			return a->bstamld->setup_link;

		if (!bk->cfg->enabled || !bk->connected)
			return best;

		if (!best || (bk->cfg->priority <= best->cfg->priority))
			best = bk;
	}
#endif
	return best;
}

uint8_t dynbh_get_best_bsta_priority(struct agent *a)
{
	struct wifi_radio_element *re = NULL;
	uint8_t best = 255;

	list_for_each_entry(re, &a->radiolist, list) {
		if (re->has_bsta && re->bk.cfg && re->bk.cfg->priority < best)
			best = re->bk.cfg->priority;
	}

	return best;
}

#define DYNBH_WINDOW (15 * 1000)
static void dynbh_send_start_event(struct agent *a, struct netif_bk *curr)
{
	char ev[256] = {0};


	snprintf(ev, sizeof(ev), "{"\
		"\"event\":\"dynbh\","\
		"\"data\": {"\
			"\"action\":\"%s\","\
			"\"upgrade_window\":%d,"\
			"\"current_backhaul\":\"%s\","\
			"\"current_priority\":%d,"\
		"}"\
		"}",	"start", DYNBH_WINDOW / 1000, (curr ? curr->ifname : "none"),
			(curr ? curr->cfg->priority : -1)
		);
	agent_notify_event(a, "map.agent", ev);
}

static void dynbh_send_end_event(struct agent *a, struct netif_bk *curr,
				 int best_prio, int timeout, bool success)
{
	char ev[256] = {0};
	bool curr_is_best = false;

	if (curr && best_prio == curr->cfg->priority)
		curr_is_best = true;

	snprintf(ev, sizeof(ev), "{"\
		"\"event\":\"dynbh\","\
		"\"data\": {"\
			"\"status\":\"%s\","\
			"\"reason\":\"%s\","\
			"\"ifname\":\"%s\","\
			"\"priority\":%d,"\
			"\"best\":%s,"\
			"\"time_to_next_attempt\": %d,"\
		"}"\
		"}",	(success ? "successful" : "unsuccessful"),
			"completed", (curr ? curr->ifname : "none"),
			(curr ? curr->cfg->priority : -1),
			(curr_is_best ? "true":"false"),
			timeout
		);

	agent_notify_event(a, "map.agent", ev);
}


void dynbh_upgrade_backhaul_cb(atimer_t *t)
{
	struct agent *a = container_of(t, struct agent, dynbh_scheduler);
	int timeout = 30; /* 30s default */
	uint8_t best_prio = dynbh_get_best_bsta_priority(a);
	struct netif_bk *best;


	/* Check CAC */
	wifi_bsta_check_cac_done(a);

	if (a->dynbh_attempts == 1)
		timeout = 60 * 4; /* 4 minutes */
	else if (a->dynbh_attempts == 2)
		timeout = 60 * 6; /* 6 minutes */
	else if (a->dynbh_attempts >= 3)
		timeout = 60 * 30; /* 30 minutes */

	best = dynbh_get_best_connected_bsta(a);
	if (!best && a->dynbh_upgrade_ongoing) {
		/* no connected bSTA, consider upgrade a failure */
		dynbh_send_end_event(a, NULL, best_prio, timeout, false);
		a->dynbh_upgrade_ongoing = false;
		timer_set(&a->dynbh_scheduler, timeout * 1000);
		timestamp_update(&a->dynbh_last_end);
		return;
	}

	/* exceptions for which triggering dynbh is not relevant */
	if ((agent_has_downstream(a) && !a->dynbh_upgrade_ongoing) ||
	    (best && (best->cfg->priority == best_prio) && !a->dynbh_upgrade_ongoing) ||
	    agent_is_backhaul_type_eth() || a->cfg.backhaul_override ||
	    !agent_is_bsta_onboarded(a)) {
		timer_set(&a->dynbh_scheduler, timeout * 1000);
		return;
	}

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld) {
		timer_set(&a->dynbh_scheduler, timeout * 1000);
		return;
	}
#endif


	if (!a->dynbh_upgrade_ongoing) {
		dynbh_bsta_scan_on_enabled(a);
		a->dynbh_upgrade_ongoing = true;
		a->dynbh_attempts++;

		/* clean BSSIDs of better backhauls to get new connection */
		if (a->dynbh_attempts > 3 && !a->cfg.backhaul_override &&
		    /* do not clear BSSIDs when trying to
		     * reconnect over existing backhaul */
		    timer_remaining_ms(&a->bh_lost_timer) == -1 &&
		    timer_remaining_ms(&a->bh_reconf_timer) == -1) {
			struct wifi_radio_element *re = NULL;

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

				if (!re->has_bsta)
					continue;

				if (!bk->cfg->enabled || bk->connected)
					continue;

				dynbh_bsta_clear_bssid(a, bk->ifname);
			}
		}

		dynbh_send_start_event(a, best);

		timer_set(&a->dynbh_scheduler, DYNBH_WINDOW);
		timestamp_update(&a->dynbh_last_start);
	} else { /* a->dynbh_upgrade_ongoing == true */
		bool success = false;

		/* ensure latest configs and states are loaded prior to finding
		 * best bsta
		 */
		agent_check_bsta_connections(a);

		/* best will be set otherwise early exit*/
		dynbh_bsta_use_link(a, best->ifname);

		if (timer_remaining_ms(&a->disable_unconnected_bstas_scheduler) > 0)
			timer_del(&a->disable_unconnected_bstas_scheduler);

		success = (timestamp_elapsed_sec(&best->connect_t) < (DYNBH_WINDOW / 1000));
		if (success) {
			timeout = 30;
			a->dynbh_attempts = 0;
		}

		dynbh_send_end_event(a, best, best_prio, timeout, success);

		a->dynbh_upgrade_ongoing = false;

		timer_set(&a->dynbh_scheduler, timeout * 1000);
		timestamp_update(&a->dynbh_last_end);
	}
}


void dynbh_bh_reconf_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);

	struct agent *a = container_of(t, struct agent, bh_reconf_timer);
	struct agent_config *cfg;
	struct dyn_bh_cfg *c;

	if (!a)
		return;

	cfg = &a->cfg;
	if (!cfg) {
		err("%s: missing configuration!\n", __func__);
		return;
	}

	c = &cfg->dbhcfg;
	if (!c) {
		err("%s: missing dynamic backhaul configuration!\n",
		    __func__);
		return;
	}

	dbg("%s: backhaul fallback link loss timeout: %d sec\n",
		  __func__, c->bh_reconf_tmo);

	if (agent_is_backhaul_type_eth() && c->bh_reconf_tmo) {
		/* additional time */
		timer_set(&a->bh_reconf_timer, c->bh_reconf_tmo * 1000);
		return;
	}

	dynbh_bsta_clear_all_bssid(a);
	agent_config_reload(a);
}

static int dynbh_bh_lost(struct agent *a)
{
	trace("%s: --->\n", __func__);

	struct agent_config *cfg;
	struct dyn_bh_cfg *c;

	if (!a)
		return -1;

	cfg = &a->cfg;
	if (!cfg) {
		err("%s: missing configuration!\n", __func__);
		return -1;
	}

	c = &cfg->dbhcfg;
	if (!c) {
		err("%s: missing dynamic backhaul configuration!\n",
		    __func__);
		return -1;
	}

	dbg("%s: backhaul link loss timeout: %d sec\n",
		  __func__, c->bh_miss_tmo);

	if (agent_is_backhaul_type_eth() && c->bh_miss_tmo) {
		/* additional time */
		timer_set(&a->bh_lost_timer, c->bh_miss_tmo * 1000);
		return -1;
	}

#ifdef AGENT_ISLAND_PREVENTION
	if (a->cfg.island_prevention)
		/* Stop beaconing, ignore connect attempts, disassociate STAs */
		agent_schedule_fh_disable(a);
#endif /* AGENT_ISLAND_PREVENTION */

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

	dynbh_bsta_enable_all(a);

	a->dynbh_attempts = 0;
	if (!a->dynbh_upgrade_ongoing)
		timer_set(&a->dynbh_scheduler, 60 * 1000);

	agent_config_reload(a);
	if (timer_remaining_ms(&a->bh_reconf_timer) == -1)
		timer_set(&a->bh_reconf_timer,
				  (c->bh_reconf_tmo - c->bh_miss_tmo) * 1000);

	return 0;
}

void dynbh_bh_lost_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);

	struct agent *a = container_of(t, struct agent, bh_lost_timer);

	if (!a)
		return;

	dynbh_bh_lost(a);
}

int dynbh_handle_bh_lost(struct agent *a, struct netif_bk *bk, bool immediate)
{
	trace("%s: detected backhaul link loss bk:%s!\n",
		  __func__, bk->ifname);

	struct agent_config *cfg;
	struct dyn_bh_cfg *c;

	if (!a)
		return -1;

	if (!agent_is_backhaul_type_eth() && !agent_bsta_steer_is_active_all(a)) {
		char fmt[128] = {0};

		snprintf(fmt, sizeof(fmt), "bsta_scan %s", bk->ifname);
		agent_exec_platform_scripts(fmt);
	}

	cfg = &a->cfg;
	if (!cfg) {
		err("%s: missing configuration!\n",
		    __func__);
		return -1;
	}

	c = &cfg->dbhcfg;
	if (!c) {
		err("%s: missing dynamic backhaul configuration!\n",
		    __func__);
		return -1;
	}

	dbg("%s: backhaul link loss timeout: %d sec\n",
	    __func__, c->bh_miss_tmo);

	/* Trigger bh link loss timer */
	if (immediate)
		dynbh_bh_lost(a);
	else if (c->bh_miss_tmo && timer_remaining_ms(&a->bh_lost_timer) == -1)
		timer_set(&a->bh_lost_timer, c->bh_miss_tmo * 1000);

	timestamp_update(&a->disconnect_t);

	return 0;
}

void dynbh_disable_unconnected_bsta_cb(atimer_t *t)
{
	struct agent *a = container_of(t, struct agent, disable_unconnected_bstas_scheduler);
	struct netif_bk *best = NULL;
	struct wifi_radio_element *re = NULL;

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

		if (!re->has_bsta)
			continue;

		dbg("%s: bsta %s enabled %d connected %d priority %u\n",
		    __func__, bk->ifname, bk->cfg->enabled,
		    bk->connected, bk->cfg->priority);
		if (!bk->cfg->enabled || !bk->connected)
			continue;

		if (!best || bk->cfg->priority < best->cfg->priority)
			best = bk;
#if (EASYMESH_VERSION >= 6)
		else if ((bk->cfg->priority <= best->cfg->priority) &&
			 (bk->cfg->is_mld_netdev && a->cfg.physical_mld))
			best = bk;
#endif
	}


	if (best) {
		dynbh_bsta_use_link(a, best->ifname);

		/* keep backing off until we settle on the preferred band */
		if (best->cfg->priority != dynbh_get_best_bsta_priority(a))
			return;

		if (!a->dynbh_upgrade_ongoing)
			timer_set(&a->dynbh_scheduler, 30 * 1000);
	}

	a->dynbh_attempts = 0;
}

void agent_check_bsta_connections(struct agent *a)
{
	bool connected = false;

	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		void (*cb)(struct ubus_request *, int, struct blob_attr *) = parse_bk;
		struct netif_bk *bk = &re->bk;

		if (!re->has_bsta)
			continue;

#if (EASYMESH_VERSION >= 6)
		if (bk->cfg->is_mld_netdev)
			continue;

		if (bk->is_affiliated_sta)
			cb = bstamld_parse;
#endif
		ubus_call_object(a->ubus_ctx, bk->obj_id, "status", cb, bk);
	}

	/* reloading is costly - only do it once after all parsing is done */
	agent_config_reload(a);

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

		if (!re->has_bsta)
			continue;
#if (EASYMESH_VERSION >= 6)
		if (bk->is_affiliated_sta &&
		    (!bk->cfg->is_mld_netdev && a->cfg.physical_mld))
			continue;
#endif
		agent_manage_bsta(a, bk);

		if (bk->connected)
			connected = true;
	}

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld && a->cfg.physical_mld) {
		struct netif_bk *bk = &a->bstamld->bk;

		agent_manage_bsta(a, bk);

		if (bk->connected)
			connected = true;
	}
#endif

	a->connected = connected;
}

void dynbh_dhcp_renew(struct agent *a)
{
	char file[256] = {0};
	size_t buflen;
	char *newline;
	uint8_t *buf;
	uint32_t pid;
	int ret;

	snprintf(file, sizeof(file), "/var/run/udhcpc-%s.pid", a->cfg.al_bridge);
	ret = readfrom_configfile(file, &buf, &buflen);
	if (ret) {
		dbg("%s: udhcpc pidfile missing: %s\n", __func__, file);
		return;
	}

	if (buflen == 0) {
		dbg("%s: pidfile %s is empty empty\n", __func__, file);
		return;
	}

	newline = strchr((char *)buf, '\n');
	if (newline)
		*newline = '\0';

	pid = strtoul((char *)buf, NULL, 10);
	if (pid == 0) {
		warn("%s: error reading pid\n", __func__);
		return;
	}

	kill(pid, SIGUSR2);
	kill(pid, SIGUSR1);
	trace("%s: triggered SIGUSR2 and SIGUSR1 to udhcpc (pid:%u)\n", __func__, pid);
}

void agent_manage_bsta(struct agent *a, struct netif_bk *bk)
{
	char *ifname = bk->ifname;
	bool enabled = false;

	if (agent_is_backhaul_type_eth()) {
		if (bk->connected)
			wifi_bsta_disconnect(bk, 0);
		return;
	}

	wifi_get_4addr(bk, &enabled);

	trace("%s: %s has got 4addr mode %s (%d) connected:%d cfg->enabled:%d\n",
	      __func__, ifname, (enabled > 0 ? "enabled" : "disabled"), enabled,
	      bk->connected, bk->cfg->enabled);

	if (enabled > 0 && bk->connected && bk->cfg->enabled) {
		struct netif_bk *best;

		best = dynbh_get_best_connected_bsta(a);
		if (!best) /* may be hit in case of affiliated bsta */
			return;

#if (EASYMESH_VERSION >= 6)
		/* Non-physical MLD: Only setup_link (first connected affiliated BSTA) gets added to bridge.
		 * Other affiliated BSTAs that connect later should not modify bridge membership.
		 */
		if (bk->is_affiliated_sta && !a->cfg.physical_mld && a->bstamld) {
			if (!a->bstamld->setup_link) {
				/* assign first connection as setup link */
				a->bstamld->setup_link = bk;
			}

			if (bk != a->bstamld->setup_link) {
				dbg("%s: Affiliated BSTA %s connected, but "\
				    "setup_link is %s - not modifying bridge\n",
				    __func__, bk->ifname, a->bstamld->setup_link->ifname);
				return;
			}
		}
#endif
		if (!if_isbridge_interface(ifname) &&
		    (bk == best || agent_bsta_steer_is_active(bk))) {
			struct wifi_radio_element *re = NULL;

			trace("%s: Attempting to add interface (%s) "\
			      " to the bridge\n", __func__, ifname);

			list_for_each_entry(re, &a->radiolist, list) {
				if (!re->has_bsta)
					continue;

				if (if_isbridge_interface(re->bk.ifname))
					wifi_mod_bridge(a, &re->bk, false);
			}
			wifi_mod_bridge(a, bk, true);
			dynbh_dhcp_renew(a);
		}
	} else {
		if (if_isbridge_interface(ifname) > 0)
			wifi_mod_bridge(a, bk, false);

#if (EASYMESH_VERSION >= 6)
		if (a->bstamld && !a->cfg.physical_mld && a->bstamld->setup_link != bk)
			return;
#endif
		/* backhaul is only lost when no bSTA is connected */
		if (agent_is_bsta_connected(a))
			return;

		agent_handle_bh_lost(a, bk, false);
	}
}

bool agent_is_bsta_connected(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (!re->has_bsta)
			continue;

		if (re->bk.connected)
			return true;
	}

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld) {
		return a->bstamld->bk.connected;
	}
#endif

	return false;
}

bool agent_is_bsta_onboarded(struct agent *a)
{
	struct wifi_radio_element *re = NULL;

	list_for_each_entry(re, &a->radiolist, list) {
		if (!re->has_bsta)
			continue;

		if (re->bk.cfg && re->bk.cfg->onboarded)
			return true;
	}

#if (EASYMESH_VERSION >= 6)
	if (a->bstamld && a->bstamld->bk.cfg && a->bstamld->bk.cfg->onboarded) {
		return true;
	}
#endif
	return false;
}

bool agent_is_backhaul_type_eth(void)
{
	struct blob_buf bk = {0};
	char type[8] = {0};
	struct blob_attr *tb[1];
	static const struct blobmsg_policy bk_attr[1] = {
		[0] = { .name = "type", .type = BLOBMSG_TYPE_STRING }
	};

	blob_buf_init(&bk, 0);

	if (!blobmsg_add_json_from_file(&bk, MAP_UPLINK_PATH)) {
		dbg("%s: Failed to parse %s\n", __func__, MAP_UPLINK_PATH);
		goto out;
	}

	blobmsg_parse(bk_attr, 1, tb, blob_data(bk.head), blob_len(bk.head));

	if (!tb[0])
		goto out;

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

	blob_buf_free(&bk);
	return !strncmp(type, "eth", 4);
out:
	blob_buf_free(&bk);
	return false;
}

bool agent_has_active_backhaul(struct agent *a)
{
	return (agent_is_bsta_connected(a) || agent_is_backhaul_type_eth());
}

char *agent_get_backhaul_ifname(struct agent *a, char *ifname)
{
	struct blob_buf bk = { 0 };
	struct blob_attr *tb[2];
	static const struct blobmsg_policy bk_attr[2] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
	};

	blob_buf_init(&bk, 0);

	if (!blobmsg_add_json_from_file(&bk, MAP_UPLINK_PATH)) {
		dbg("%s: Failed to parse %s\n", __func__, MAP_UPLINK_PATH);
		goto out;
	}

	blobmsg_parse(bk_attr, 2, tb, blob_data(bk.head), blob_len(bk.head));

	if (!tb[0])
		goto out;

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

	if (tb[1]) {
		uint8_t macaddr[6] = {0};

		if (!hwaddr_aton(blobmsg_data(tb[1]), macaddr))
			goto out;

		/* if backhaul differs from last seen backhaul, move last seen to idx 1 */
		if (memcmp(macaddr, a->backhaul_macaddr[0], 6)) {
			memcpy(a->backhaul_macaddr[1], a->backhaul_macaddr[0], 6);
			timestamp_update(&a->backhaul_change_t);
		}

		memcpy(a->backhaul_macaddr[0], macaddr, 6);
	}

	blob_buf_free(&bk);
	return ifname;
out:
	blob_buf_free(&bk);
	return NULL;
}

void agent_trigger_bsta_sync(atimer_t *t)
{
	struct agent *a = container_of(t, struct agent, onboarding_scheduler);

	UNUSED(a);

	agent_exec_platform_scripts("bsta_to_wireless");
}

void agent_update_active_uplink(struct agent *a, struct netif_bk *bk)
{
	char ev[512] = {0};
	char *ifname = bk->ifname;
	uint8_t best_prio = 0;
	bool best = false;

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

	if (!a)
		return;

	best_prio = dynbh_get_best_bsta_priority(a);
	best = (best_prio == bk->cfg->priority);

	sprintf(ev, /* Flawfinder: ignore */
		"{\"action\":\"set_uplink\""
		",\"type\":\"wifi\""
		",\"ifname\":\"%s\""
		",\"best\":%s}",
		ifname, (best ? "true" : "false")
		);

	trace("cntlrinfo: %s\n", ev);
	agent_notify_event(a, "map.agent", ev);
	runCmd("/lib/wifi/multiap set_uplink wifi %s " MACFMT, ifname, MAC2STR(bk->macaddr));
}
