

#include "decollector.h"
#include "wifi_dataelements.h"
#include "utils.h"

#include <easy/easy.h>
#include <libubox/utils.h>
#include <uci.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


static int cntlr_config_get_ssid(struct uci_section *s,
				 struct list_head *ssidlist,
				 uint32_t *num)
{
	enum {
		SSID_BAND,
		SSID_SSID,
		SSID_SEC,
		SSID_KEY,
		SSID_VLAN,
		SSID_NETWORK,
		SSID_TYPE,
		SSID_DISALLOW_BSTA,
		SSID_ENABLED,
		SSID_MLD_ID,
		NUM_SSID_ATTRS,
	};
	const struct uci_parse_option opts[] = {
		[SSID_BAND] = { .name = "band", .type = UCI_TYPE_STRING },
		[SSID_SSID] = { .name = "ssid", .type = UCI_TYPE_STRING },
		[SSID_SEC] = { .name = "encryption", .type = UCI_TYPE_STRING },
		[SSID_KEY] = { .name = "key", .type = UCI_TYPE_STRING },
		[SSID_VLAN] = { .name = "vid", .type = UCI_TYPE_STRING },
		[SSID_NETWORK] = { .name = "network", .type = UCI_TYPE_STRING },
		[SSID_TYPE] = { .name = "type", .type = UCI_TYPE_STRING },
		[SSID_DISALLOW_BSTA] = { .name = "disallow_bsta", .type = UCI_TYPE_LIST },
		[SSID_ENABLED] = { .name = "enabled", .type = UCI_TYPE_STRING },
		[SSID_MLD_ID] = { .name = "mld_id", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_SSID_ATTRS];
	struct wifi_network_ssid *ssid;


	ssid = calloc(1, sizeof(*ssid));
	if (!ssid)
		return -1;

	ssid->enabled = true;
	ssid->band = BAND_UNKNOWN;
	ssid->type = WIFI_BSSTYPE_FBSS;
	ssid->multi_ap = 2;
	ssid->mld_id = 255;

	uci_parse_section(s, opts, NUM_SSID_ATTRS, tb);


	if (tb[SSID_BAND]) {
		char *endptr = NULL;
		errno = 0;
		int band;

		band = strtol(tb[SSID_BAND]->v.string, &endptr, 10);
		if (errno || *endptr != '\0') {
			free(ssid);
			return -1;
		}

		if (band == 6)
			ssid->band = BAND_6;
		else if (band == 5)
			ssid->band = BAND_5;
		else if (band == 2)
			ssid->band = BAND_2;
		else {
			free(ssid);
			return -1;
		}
	}

	if (tb[SSID_SSID]) {
		strncpy(ssid->ssid, tb[SSID_SSID]->v.string, 32);
		ssid->ssidlen = strlen(ssid->ssid);
	}

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

		if (!strncmp(sec, "sae-mixed", 9)) {
			ssid->security |= BIT(WIFI_SECURITY_WPA3PSK);
			ssid->security |= BIT(WIFI_SECURITY_WPA3PSK_T);
		} else if (!strncmp(sec, "sae", 3)) {
			ssid->security |= BIT(WIFI_SECURITY_WPA3PSK);
		} else if (!strncmp(sec, "psk-mixed", 9)) {
			ssid->security |= BIT(WIFI_SECURITY_WPAPSK);
			ssid->security |= BIT(WIFI_SECURITY_WPA2PSK);
		} else if (!strncmp(sec, "psk2", 4)) {
			ssid->security |= BIT(WIFI_SECURITY_WPA2PSK);
		} else if (!strncmp(sec, "psk", 3)) {
			ssid->security |= BIT(WIFI_SECURITY_WPAPSK);
		} else if (!strncmp(sec, "wpa-mixed", 9)) {
			ssid->security |= BIT(WIFI_SECURITY_WPA);
			ssid->security |= BIT(WIFI_SECURITY_WPA2);
		} else if (!strncmp(sec, "wpa2", 4)) {
			ssid->security |= BIT(WIFI_SECURITY_WPA2);
		} else if (!strncmp(sec, "wpa", 3)) {
			ssid->security |= BIT(WIFI_SECURITY_WPA);
		} else if (!strncmp(sec, "none", 4)) {
			ssid->security |= BIT(WIFI_SECURITY_NONE);
		} else if (!strncmp(sec, "open", 4)) {
			ssid->security |= BIT(WIFI_SECURITY_NONE);
		} else {
			free(ssid);
			return -1;
		}
	}

	if (tb[SSID_VLAN]) {
		char *endptr = NULL;
		int vid;

		errno = 0;
		vid = strtol(tb[SSID_VLAN]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			ssid->vid = vid;
	}

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

		if (!strcmp(type, "backhaul")) {
			ssid->multi_ap = 1;
			ssid->type = WIFI_BSSTYPE_BBSS;
		} else if (!strcmp(type, "fronthaul")) {
			ssid->multi_ap = 2;
			ssid->type = WIFI_BSSTYPE_FBSS;
		} else if (!strcmp(type, "combined")) {
			ssid->multi_ap = 3;
			ssid->type = WIFI_BSSTYPE_COMBINED;
		} else {
			free(ssid);
			return -1;
		}
	}

	if (tb[SSID_ENABLED]) {
		char *endptr = NULL;
		int enabled;

		errno = 0;
		enabled = strtol(tb[SSID_ENABLED]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			ssid->enabled = enabled;
	}

	if (tb[SSID_MLD_ID]) {
		char *endptr = NULL;
		int mld_id;

		errno = 0;
		mld_id = strtol(tb[SSID_MLD_ID]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			ssid->mld_id = mld_id;
	}

	*num += 1;
	list_add_tail(&ssid->list, ssidlist);
	return 0;
}

static int cntlr_config_get_mld(struct uci_section *s,
				struct list_head *head,
				uint32_t *num)
{
	enum {
		MLD_ENABLED,
		MLD_ID,
		MLD_SSID,
		MLD_SEC,
		MLD_KEY,
		MLD_TYPE,
		NUM_MLD_ATTRS,
	};
	const struct uci_parse_option opts[] = {
		[MLD_ENABLED] = { .name = "enabled", .type = UCI_TYPE_STRING },
		[MLD_ID] = { .name = "id", .type = UCI_TYPE_STRING },
		[MLD_SSID] = { .name = "ssid", .type = UCI_TYPE_STRING },
		[MLD_SEC] = { .name = "encryption", .type = UCI_TYPE_STRING },
		[MLD_KEY] = { .name = "key", .type = UCI_TYPE_STRING },
		[MLD_TYPE] = { .name = "type", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_MLD_ATTRS];
	struct wifi_network_mld *mld;
	char *endptr = NULL;
	int mld_id;


	uci_parse_section(s, opts, NUM_MLD_ATTRS, tb);

	if (!tb[MLD_ID])
		return -1;

	mld = calloc(1, sizeof(*mld));
	if (!mld)
		return -1;

	mld->enabled = true;
	mld->type = WIFI_BSSTYPE_FBSS;
	mld->multi_ap = 2;

	errno = 0;
	mld_id = strtol(tb[MLD_ID]->v.string, &endptr, 10);
	if (!errno && *endptr == '\0')
		mld->id = mld_id;

	if (tb[MLD_ENABLED]) {
		int enabled;

		errno = 0;
		enabled = strtol(tb[MLD_ENABLED]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			mld->enabled = enabled;
	}

	if (tb[MLD_SSID]) {
		strncpy(mld->ssid, tb[MLD_SSID]->v.string, 32);
		mld->ssidlen = strlen(mld->ssid);
	}

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

		if (!strncmp(sec, "sae-mixed", 9)) {
			mld->security |= BIT(WIFI_SECURITY_WPA3PSK);
			mld->security |= BIT(WIFI_SECURITY_WPA3PSK_T);
		} else if (!strncmp(sec, "sae", 3)) {
			mld->security |= BIT(WIFI_SECURITY_WPA3PSK);
		} else if (!strncmp(sec, "psk-mixed", 9)) {
			mld->security |= BIT(WIFI_SECURITY_WPAPSK);
			mld->security |= BIT(WIFI_SECURITY_WPA2PSK);
		} else if (!strncmp(sec, "psk2", 4)) {
			mld->security |= BIT(WIFI_SECURITY_WPA2PSK);
		} else if (!strncmp(sec, "psk", 3)) {
			mld->security |= BIT(WIFI_SECURITY_WPAPSK);
		} else if (!strncmp(sec, "wpa-mixed", 9)) {
			mld->security |= BIT(WIFI_SECURITY_WPA);
			mld->security |= BIT(WIFI_SECURITY_WPA2);
		} else if (!strncmp(sec, "wpa2", 4)) {
			mld->security |= BIT(WIFI_SECURITY_WPA2);
		} else if (!strncmp(sec, "wpa", 3)) {
			mld->security |= BIT(WIFI_SECURITY_WPA);
		} else if (!strncmp(sec, "none", 4)) {
			mld->security |= BIT(WIFI_SECURITY_NONE);
		} else if (!strncmp(sec, "open", 4)) {
			mld->security |= BIT(WIFI_SECURITY_NONE);
		} else {
			free(mld);
			return -1;
		}
	}

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

		if (!strcmp(type, "backhaul")) {
			mld->multi_ap = 1;
			mld->type = WIFI_BSSTYPE_BBSS;
		} else if (!strcmp(type, "fronthaul")) {
			mld->multi_ap = 2;
			mld->type = WIFI_BSSTYPE_FBSS;
		} else if (!strcmp(type, "combined")) {
			mld->multi_ap = 3;
			mld->type = WIFI_BSSTYPE_COMBINED;
		} else {
			free(mld);
			return -1;
		}
	}

	*num += 1;
	list_add_tail(&mld->list, head);
	return 0;
}

static int cntlr_config_get_globals(struct uci_section *s,
				    struct wifi_network *net)
{
	enum {
		CNTLR_NETWORK_ID,
		CNTLR_NETWORK_PVID,
		CNTLR_NETWORK_PCP,
		NUM_CNTLR_ATTRS,
	};
	const struct uci_parse_option opts[] = {
		{ .name = "network_id", .type = UCI_TYPE_STRING },
		{ .name = "primary_vid", .type = UCI_TYPE_STRING },
		{ .name = "primary_pcp", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_CNTLR_ATTRS];

	uci_parse_section(s, opts, NUM_CNTLR_ATTRS, tb);

	if (tb[CNTLR_NETWORK_PVID]) {
		char *endptr = NULL;
		int pvid;

		errno = 0;
		pvid = strtol(tb[CNTLR_NETWORK_PVID]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			net->primary_vid = pvid;
	}

	if (tb[CNTLR_NETWORK_PCP]) {
		char *endptr = NULL;
		int pcp;

		errno = 0;
		pcp = strtol(tb[CNTLR_NETWORK_PCP]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			net->default_pcp = pcp;
	}

	if (tb[CNTLR_NETWORK_ID])
		uuid_strtob(tb[CNTLR_NETWORK_ID]->v.string, (uint8_t *)net->id);

	return 0;
}

static int cntlr_config_get_sta_steer(struct uci_section *s,
				    struct wifi_network *net)
{
	enum {
		STA_STEER_ENABLED,
		NUM_STA_STEER_ATTRS,
	};
	const struct uci_parse_option opts[] = {
		{ .name = "enable_sta_steer", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_STA_STEER_ATTRS];
	struct wifi_network_device *dev = NULL;
	bool enabled = false;

	uci_parse_section(s, opts, NUM_STA_STEER_ATTRS, tb);

	if (tb[STA_STEER_ENABLED]) {
		char *endptr = NULL;
		int enbl;

		errno = 0;
		enbl = strtol(tb[STA_STEER_ENABLED]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			enabled = (enbl == 1) ? true : false;
	}

	list_for_each_entry(dev, &net->devicelist, list) {
		dev->sta_steering_state = enabled;
	}

	return 0;
}

static int cntlr_config_get_agent_node(struct uci_section *s,
				       struct wifi_network *net)
{
	enum {
		NODE_AGENT_ID,
		NODE_RPT_ASSOC_FAILS,
		NODE_RPT_ASSOC_FAILS_RATE,
		NODE_RPT_METRIC_PERIODIC,
		NODE_RPT_SCAN,
		NODE_STEER_EXCLUDE,
		NODE_STEER_EXCLUDE_BTM,
		NODE_COORDINATED_CAC,
		NODE_TRAFFIC_SEPARATION,
		/* TODO: NODE_STA_STEER, */
		NUM_POLICIES,
	};
	const struct uci_parse_option opts[] = {
		[NODE_AGENT_ID] =
			{ .name = "agent_id", .type = UCI_TYPE_STRING },
		[NODE_RPT_ASSOC_FAILS] =
			{ .name = "report_sta_assocfails", .type = UCI_TYPE_STRING },
		[NODE_RPT_ASSOC_FAILS_RATE] =
			{ .name = "report_sta_assocfails_rate", .type = UCI_TYPE_STRING },
		[NODE_RPT_METRIC_PERIODIC] =
			{ .name = "report_metric_periodic", .type = UCI_TYPE_STRING },
		[NODE_RPT_SCAN] =
			{ .name = "report_scan", .type = UCI_TYPE_STRING },
		[NODE_STEER_EXCLUDE] =
			{ .name = "steer_exclude", .type = UCI_TYPE_LIST },
		[NODE_STEER_EXCLUDE_BTM] =
			{ .name = "steer_exclude_btm", .type = UCI_TYPE_LIST },
		[NODE_COORDINATED_CAC] =
			{ .name = "coordinated_cac", .type = UCI_TYPE_STRING },
		[NODE_TRAFFIC_SEPARATION] =
			{ .name = "traffic_separation", .type = UCI_TYPE_STRING },
		/* TODO: [NODE_STA_STEER] =
			{ .name = "sta_steer", .type = UCI_TYPE_STRING }, */
	};

	struct uci_option *tb[NUM_POLICIES];
	struct wifi_network_device *dev = NULL;
	bool device_found = false;

	uci_parse_section(s, opts, NUM_POLICIES, tb);

	if (tb[NODE_AGENT_ID]) {
		macaddr_t agent_id;

		hwaddr_aton(tb[NODE_AGENT_ID]->v.string, agent_id);

		list_for_each_entry(dev, &net->devicelist, list) {
			if (!memcmp(dev->macaddr, agent_id, 6)) {
				device_found = true;
				break;
			}
		}
	} else {
		return -1;
	}

	/* No device in model yet */
	if (!device_found)
		return 0;

	if (tb[NODE_RPT_ASSOC_FAILS]) {
		char *endptr = NULL;
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_RPT_ASSOC_FAILS]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			dev->report.sta_assocfails = ((enbl == 1) ? true : false);
	}

	if (tb[NODE_RPT_ASSOC_FAILS_RATE]) {
		char *endptr = NULL;
		int rate;

		errno = 0;
		rate = strtol(tb[NODE_RPT_ASSOC_FAILS_RATE]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			dev->report.sta_assocfails_rate = rate;
	}

	if (tb[NODE_RPT_SCAN]) {
		char *endptr = NULL;
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_RPT_SCAN]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			dev->report.independent_scans = ((enbl == 1) ? true : false);
	} else {
		dev->report.independent_scans = true;
	}

	if (tb[NODE_RPT_METRIC_PERIODIC]) {
		char *endptr = NULL;
		int metrics;

		errno = 0;
		metrics = strtol(tb[NODE_RPT_METRIC_PERIODIC]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			dev->report.ap_metrics_int = metrics;
	} else {
		dev->report.ap_metrics_int = 5;
	}

	if (tb[NODE_STEER_EXCLUDE]) {
		struct uci_element *excluded_sta;

		list_flush(&dev->sta_steer_disallowlist, struct mac_address_element, list);

		uci_foreach_element(&tb[NODE_STEER_EXCLUDE]->v.list, excluded_sta) {
			struct mac_address_element *mac =
				calloc(1, sizeof(*mac));

			if (mac && hwaddr_aton(excluded_sta->name, mac->macaddr))
				list_add_tail(&mac->list, &dev->sta_steer_disallowlist);
		}
	}

	if (tb[NODE_STEER_EXCLUDE_BTM]) {
		struct uci_element *excluded_sta;

		list_flush(&dev->sta_btmsteer_disallowlist, struct mac_address_element, list);

		uci_foreach_element(&tb[NODE_STEER_EXCLUDE_BTM]->v.list, excluded_sta) {
			struct mac_address_element *mac =
				calloc(1, sizeof(*mac));

			if (mac && hwaddr_aton(excluded_sta->name, mac->macaddr))
				list_add_tail(&mac->list, &dev->sta_btmsteer_disallowlist);
		}
	}

	if (tb[NODE_COORDINATED_CAC]) {
		char *endptr = NULL;
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_COORDINATED_CAC]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			dev->coordinated_cac_allowed = ((enbl == 1) ? true : false);
	}

	if (tb[NODE_TRAFFIC_SEPARATION]) {
		char *endptr = NULL;
		int enbl;

		errno = 0;
		enbl = strtol(tb[NODE_TRAFFIC_SEPARATION]->v.string, &endptr, 10);
		if (!errno && *endptr == '\0')
			dev->ts_allowed = ((enbl == 1) ? true : false);
	}

	/* TODO: use policy to update per-node steering state (enabled/disabled) */
	/*
	if (tb[NODE_STA_STEER]) {
		dev->sta_steering_state =
			atoi(tb[NODE_STA_STEER]->v.string) == 1 ? true : false;
	}
	*/

	return 0;
}

static int cntlr_config_get_agent_qos(struct uci_section *s,
				    struct wifi_network *net)
{
	enum {
		QOS_ENABLED,
		NUM_QOS_ATTRS,
	};
	const struct uci_parse_option opts[] = {
		{ .name = "enabled", .type = UCI_TYPE_STRING },
	};
	struct uci_option *tb[NUM_QOS_ATTRS];
	struct wifi_network_device *dev = NULL;
	bool enabled = false;

	uci_parse_section(s, opts, NUM_QOS_ATTRS, tb);

	if (tb[QOS_ENABLED]) {
		char *endptr = NULL;
		int enbl;

		errno = 0;
		enbl = strtol(tb[QOS_ENABLED]->v.string, &endptr, 10);
		if (!errno || *endptr == '\0')
			enabled = (enbl == 1) ? true : false;
	}

	list_for_each_entry(dev, &net->devicelist, list) {
		dev->sp_allowed = enabled;
	}

	return 0;
}

int collector_get_network_params(struct decollector_private *priv,
				 struct wifi_network *net)
{
	struct wifi_network_ssid *ssid = NULL, *tssid;
	struct wifi_network_mld *mld = NULL, *tmld;
	struct uci_context *ctx;
	struct uci_package *pkg;
	struct uci_element *e;

	ctx = uci_alloc_context();
	if (!ctx)
		return -1;

	if (uci_load(ctx, "mapcontroller", &pkg)) {
		uci_free_context(ctx);
		return -1;
	}

	net->num_ssid = 0;
	list_for_each_entry_safe(ssid, tssid, &net->ssidlist, list) {
		list_del(&ssid->list);
		free(ssid);
	}

	net->num_mld = 0;
	list_for_each_entry_safe(mld, tmld, &net->mldlist, list) {
		list_del(&mld->list);
		free(mld);
	}

	uci_foreach_element(&pkg->sections, e) {
		struct uci_section *s = uci_to_section(e);

		if (!strcmp(s->type, "ap"))
			cntlr_config_get_ssid(s, &net->ssidlist, &net->num_ssid);
		else if (!strcmp(s->type, "mld"))
			cntlr_config_get_mld(s, &net->mldlist, &net->num_mld);
		else if (!strcmp(s->type, "controller"))
			cntlr_config_get_globals(s, net);
		else if (!strcmp(s->type, "sta_steering"))
			cntlr_config_get_sta_steer(s, net);
		else if (!strcmp(s->type, "node"))
			cntlr_config_get_agent_node(s, net);
		else if (!strcmp(s->type, "qos"))
			cntlr_config_get_agent_qos(s, net);
	}

	/* set mld bands from corresponding affiliated interfaces. */
	list_for_each_entry(mld, &net->mldlist, list) {
		list_for_each_entry(ssid, &net->ssidlist, list) {
			if (ssid->mld_id == mld->id)
				mld->band |= ssid->band;
		}
	}

	uci_free_context(ctx);
	return 0;
}
