/*
 * dm.c
 * WiFi DE-Collector datamodel functions
 *
 * Copyright (C) 2020-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 <libubox/list.h>

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


void free_scanresults(struct wifi_scanres_element *scanres)
{
	struct wifi_scanres_opclass_element *op = NULL, *op_tmp;

	list_for_each_entry_safe(op, op_tmp, &scanres->opclass_scanlist, list) {
		struct wifi_scanres_channel_element *ch = NULL, *ch_tmp;

		list_for_each_entry_safe(ch, ch_tmp, &op->channel_scanlist, list) {
			struct wifi_scanres_neighbor_element *nbr = NULL, *nbr_tmp;

			list_for_each_entry_safe(nbr, nbr_tmp, &ch->nbrlist, list) {
				list_del(&nbr->list);
				free(nbr);
			}
			list_del(&ch->list);
			free(ch);
		}
		list_del(&op->list);
		free(op);
	}
	free(scanres);
}

void free_scanres(struct wifi_radio_element *radio)
{
	struct wifi_scanres_element *scanres = NULL, *tmp;

	list_for_each_entry_safe(scanres, tmp, &radio->scanlist, list) {
		list_del(&scanres->list);
		free_scanresults(scanres);
	}

	radio->num_scanresult = 0;
}

void clear_supported_opclasses(struct wifi_radio_element *radio)
{
	memset(&radio->supp_opclass, 0, sizeof(radio->supp_opclass));
}

void clear_current_opclasses(struct wifi_radio_element *radio)
{
	memset(&radio->cur_opclass, 0, sizeof(radio->cur_opclass));
}

void clear_preference_opclasses(struct wifi_radio_element *radio)
{
	memset(&radio->pref_opclass, 0, sizeof(radio->pref_opclass));
}

void free_sta_reassoc_frame(struct wifi_sta_element *sta)
{
	free(sta->reassoc_frame);
	sta->reassoc_frame = NULL;
	sta->reassoc_framelen = 0;
}

void free_wifi_sta_meas_report(struct wifi_sta_meas_report *report)
{
	free(report->element_data);
	free(report);
}

void free_sta_meas_reportlist(struct wifi_sta_element *sta)
{
	if (sta->num_meas_reports > 0 && !list_empty(&sta->meas_reportlist)) {
		struct wifi_sta_meas_report *report, *report_tmp;

		list_for_each_entry_safe(report, report_tmp, &sta->meas_reportlist, list) {
			list_del(&report->list);
			free_wifi_sta_meas_report(report); /* cppcheck-suppress uninitvar */
		}

		sta->num_meas_reports = 0;
	}
}

void free_sta(struct wifi_sta_element *sta)
{
	if (!sta)
		return;

	sta_ratings_free(sta->sta_ratings);
	free_sta_meas_reportlist(sta);
	free_sta_reassoc_frame(sta);
	free(sta);
}

void free_stalist(struct wifi_bss_element *bss)
{
	struct wifi_sta_element *s, *tmp;

	list_for_each_entry_safe(s, tmp, &bss->stalist, list) {
		list_del(&s->list);
		free_sta(s);  /* cppcheck-suppress uninitvar */
	}

	bss->num_stations = 0;
}

void free_bss(struct wifi_bss_element *bss)
{
	if (!bss)
		return;

	if (bss->num_stations > 0)
		free_stalist(bss);

	free(bss);
}

void free_bsslist(struct wifi_radio_element *radio)
{
	struct wifi_bss_element *b, *tmp;

	list_for_each_entry_safe(b, tmp, &radio->bsslist, list) {
		list_del(&b->list);
		free_bss(b);  /* cppcheck-suppress uninitvar */
	}

	radio->num_bss = 0;
}

void free_unassoc_stalist(struct wifi_radio_element *radio)
{
	struct wifi_unassoc_sta_element *usta, *tmp;

	list_for_each_entry_safe(usta, tmp, &radio->unassoc_stalist, list) {
		list_del(&usta->list);
		free(usta);  /* cppcheck-suppress uninitvar */
	}

	radio->num_unassoc_sta = 0;
}

void free_cac_capabilities(struct wifi_radio_element *radio)
{
	struct wifi_radio_cac_capabilities *cac_cap, *cac_cap_tmp;

	list_for_each_entry_safe(cac_cap, cac_cap_tmp, &radio->cac_capslist, list) {
		list_del(&cac_cap->list);
		free(cac_cap); /* cppcheck-suppress uninitvar */
	}
}

void free_bss_akmlist(struct wifi_radio_element *radio)
{
	struct wifi_akm_suite *bss_akm, *tmp;

	list_for_each_entry_safe(bss_akm, tmp, &radio->fbss_akmlist, list) {
		list_del(&bss_akm->list);
		free(bss_akm);  /* cppcheck-suppress uninitvar */
	}

	list_for_each_entry_safe(bss_akm, tmp, &radio->bbss_akmlist, list) {
		list_del(&bss_akm->list);
		free(bss_akm);  /* cppcheck-suppress uninitvar */
	}

	radio->num_fbss_akm = 0;
	radio->num_bbss_akm = 0;
}

void free_radio(struct wifi_radio_element *r)
{
	if (!r)
		return;

	clear_supported_opclasses(r);
	clear_current_opclasses(r);
	clear_preference_opclasses(r);
	free_unassoc_stalist(r);
	free_bsslist(r);
	free_scanres(r);
	free_cac_capabilities(r);
	free_bss_akmlist(r);
	free(r);
}

void free_radiolist(struct wifi_network_device *dev)
{
	struct wifi_radio_element *r, *tmp;

	list_for_each_entry_safe(r, tmp, &dev->radiolist, list) {
		list_del(&r->list);
		free_radio(r);  /* cppcheck-suppress uninitvar */
	}

	dev->num_radios = 0;
}

void free_eventlist(struct wifi_network_device *dev)
{
	struct wifi_sta_events *ev = &dev->ev;


	list_flush(&ev->assoclist, struct wifi_assoc_event, list);  /* cppcheck-suppress uninitvar */
	ev->num_assoc = 0;

	list_flush(&ev->disassoclist, struct wifi_disassoc_event, list);  /* cppcheck-suppress uninitvar */
	ev->num_disassoc = 0;
}

void free_wifi_cac_status(struct wifi_cac_status *cac_status)
{
	if (!cac_status)
		return;

	list_flush(&cac_status->available_chlist, struct wifi_cac_available_channel, list); /* cppcheck-suppress uninitvar */
	list_flush(&cac_status->nop_chlist, struct wifi_cac_nop_channel, list);             /* cppcheck-suppress uninitvar */
	list_flush(&cac_status->cac_chlist, struct wifi_cac_active_channel, list);          /* cppcheck-suppress uninitvar */

	free(cac_status);
}

void free_cac_statuslist(struct wifi_network_device *dev)
{
	struct wifi_cac_status *cac_entry, *cac_entry_tmp;

	list_for_each_entry_safe(cac_entry, cac_entry_tmp, &dev->cac_statuslist, list) {
		list_del(&cac_entry->list);
		free_wifi_cac_status(cac_entry); /* cppcheck-suppress uninitvar */
	}
}

void free_sp_rulelist(struct wifi_network_device *dev)
{
	struct wifi_sp_rule *rule_entry, *rule_entry_tmp;

	list_for_each_entry_safe(rule_entry, rule_entry_tmp, &dev->sp_rulelist, list) {
		list_del(&rule_entry->list);
		free(rule_entry); /* cppcheck-suppress uninitvar */
	}
}

void free_anticipated_ch_usagelist(struct wifi_network_device *dev)
{
	struct anticipated_ch_usage *ch_usage, *ch_usage_tmp;

	list_for_each_entry_safe(ch_usage, ch_usage_tmp, &dev->anticipated_channel_usagelist, list) {
		list_del(&ch_usage->list);
		list_flush(&ch_usage->entry_list, struct anticipated_ch_usage_entry, list); /* cppcheck-suppress uninitvar */

		free(ch_usage); /* cppcheck-suppress uninitvar */
	}

	dev->num_anticipated_channel_usage = 0;
}

void free_stamld(struct wifi_stamld_element *stamld)
{
	list_flush(&stamld->stalist, struct wifi_affiliated_sta_element, list);	/* cppcheck-suppress uninitvar */
	list_flush(&stamld->ttlmlist, struct wifi_ttlm_element, list);		/* cppcheck-suppress uninitvar */

	sta_ratings_free(stamld->sta_ratings);
	free(stamld);
}

void free_bstamld(struct wifi_network_device *dev)
{
	memset(&dev->bstamld.mld_macaddr, 0, 6);
	memset(&dev->bstamld.bssid, 0, 6);
	list_flush(&dev->bstamld.affbstalist, struct wifi_affiliated_sta_element, list);	/* cppcheck-suppress uninitvar */

	dev->bstamld.num_aff_bsta = 0;
}

void free_apmld(struct wifi_apmld_element *apmld)
{
	struct wifi_stamld_element *stamld = 0, *tmp;

	list_flush(&apmld->aplist, struct wifi_affiliated_ap_element, list);	/* cppcheck-suppress uninitvar */
	list_flush(&apmld->ttlmlist, struct wifi_ttlm_element, list);		/* cppcheck-suppress uninitvar */
	list_flush(&apmld->linkopcmaplist, struct wifi_linkopcmap_element, list);	/* cppcheck-suppress uninitvar */

	list_for_each_entry_safe(stamld, tmp, &apmld->stamldlist, list) {
		list_del(&stamld->list);
		apmld->num_sta--;
		free_stamld(stamld);
	}

	free(apmld);
}

void free_apmldlist(struct wifi_network_device *dev)
{
	struct wifi_apmld_element *apmld = NULL, *tmp;

	list_for_each_entry_safe(apmld, tmp, &dev->apmldlist, list) {
		list_del(&apmld->list);
		dev->num_apmld--;
		free_apmld(apmld);
	}
}

void free_bh_down_list(struct wifi_network_device *dev)
{
	struct wifi_bh_down *bh_down, *bh_down_tmp;

	list_for_each_entry_safe(bh_down, bh_down_tmp, &dev->bh_downlist, list) {
		list_del(&bh_down->list);

		free(bh_down); /* cppcheck-suppress uninitvar */
	}

	dev->num_bh_down = 0;
}

void free_network_device(struct wifi_network_device *dev)
{
	if (!dev)
		return;

	free_eventlist(dev);
	free_radiolist(dev);
	free_cac_statuslist(dev);
	free_sp_rulelist(dev);
	free_apmldlist(dev);
	free_bstamld(dev);

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

	free_anticipated_ch_usagelist(dev);
	free_bh_down_list(dev);

	free(dev);
}

void decollector_free_dm(struct wifi_data_element *dm)
{
	struct wifi_provisioned_dpp *dpp = NULL, *tmp3;
	struct wifi_network_device *dev = NULL, *tmp1;
	struct wifi_network_ssid *ssid = NULL, *tmp;
	struct wifi_network_mld *mld = NULL, *tmp2;
	struct wifi_sta_block *blk = NULL, *tmp4;
	struct wifi_network *net;

	if (!dm)
		return;

	dbg("Freeing DM..\n");
	net = &dm->network;

	list_for_each_entry_safe(dev, tmp1, &net->devicelist, list) {
		list_del(&dev->list);
		decollector_free_getter(dev);
		free_network_device(dev);
		net->num_devices--;
	}

	/* free the ssid-list of network */
	list_for_each_entry_safe(ssid, tmp, &net->ssidlist, list) {
		list_del(&ssid->list);
		free(ssid);
	}

	/* free the mldlist of network */
	list_for_each_entry_safe(mld, tmp2, &net->mldlist, list) {
		list_del(&mld->list);
		free(mld);
	}

	/* free the dpplist of network */
	list_for_each_entry_safe(dpp, tmp3, &net->dpplist, list) {
		list_del(&dpp->list);
		free(dpp);
	}

	/* free the sta_blocklist of network */
	list_for_each_entry_safe(blk, tmp4, &net->sta_blocklist, list) {
		list_del(&blk->list);
		free(blk);
	}

	free(dm);
}

struct wifi_sta_element *alloc_sta(uint8_t *macaddr)
{
	struct wifi_sta_element *n = calloc(1, sizeof(*n));

	if (n) {
		memcpy(n->macaddr, macaddr, 6);
		INIT_LIST_HEAD(&n->meas_reportlist);
		INIT_LIST_HEAD(&n->tid_qsizelist);
		sta_ratings_init(&n->sta_ratings, macaddr);
	}

	return n;
}

struct wifi_stamld_element *alloc_stamld(uint8_t *macaddr)
{
	struct wifi_stamld_element *n = calloc(1, sizeof(*n));

	if (n) {
		memcpy(n->mld_macaddr, macaddr, 6);
		INIT_LIST_HEAD(&n->stalist);
		INIT_LIST_HEAD(&n->ttlmlist);
		n->is_bsta = false;
		sta_ratings_init(&n->sta_ratings, macaddr);
	}

	return n;
}

struct wifi_bss_element *alloc_bss(uint8_t *bssid)
{
	struct wifi_bss_element *n = calloc(1, sizeof(*n));

	if (n) {
		memcpy(n->bssid, bssid, 6);
		INIT_LIST_HEAD(&n->stalist);
	}

	return n;
}

struct wifi_radio_element *alloc_radio(uint8_t *ruid)
{
	struct wifi_radio_element *n = calloc(1, sizeof(*n));

	if (n) {
		memcpy(n->macaddr, ruid, 6);
		INIT_LIST_HEAD(&n->bsslist);
		INIT_LIST_HEAD(&n->unassoc_stalist);
		INIT_LIST_HEAD(&n->scanlist);
		INIT_LIST_HEAD(&n->cac_capslist);
		INIT_LIST_HEAD(&n->fbss_akmlist);
		INIT_LIST_HEAD(&n->bbss_akmlist);
	}

	return n;
}
