/*
 * Copyright (C) 2023 IOPSYS Software Solutions AB. All rights reserved.
 */
#if (EASYMESH_VERSION > 2)
#ifdef USE_LIBDPP

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "dpp.h"

#include <cmdu.h>
#include <dpp_api.h>
#include <dpputils.h>
#include <libubox/blob.h>
#include <libubox/blobmsg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <timer.h>

#include "agent.h"
#include "agent_cmdu.h"
#include "agent_map.h"
#include "agent_ubus.h"
#include "backhaul.h"
#include "config.h"
#include "dpphandler.h"
#include "extension.h"
#include "timer_impl.h"
#include "utils/debug.h"
#include "utils/utils.h"
#include "wifi_ubus.h"


uint32_t channel_to_freq(enum wifi_band band, uint32_t channel)
{
	uint32_t freq = 0;

	if (band == BAND_2)
		freq = 2412 + (5 * (channel - 1));
	else if (band == BAND_5)
		freq = 5180 + (5 * (channel - 36));
	//else if (band == BAND_6)
		/* TODO */

	return freq;
}

const char *dpp_frame_type2str(uint8_t type)
{
	if (type == DPP_PA_AUTHENTICATION_REQ)
		return "Authentication Request";
	else if (type == DPP_PA_AUTHENTICATION_RESP)
		return "Authentication Response";
	else if (type == DPP_PA_AUTHENTICATION_CONF)
		return "Authentication Confirm";
	else if (type == DPP_PA_PEER_DISCOVERY_REQ)
		return "Peer Discovery Request";
	else if (type == DPP_PA_PEER_DISCOVERY_RESP)
		return "Peer Discovery Response";
	else if (type == DPP_PA_PKEX_V1_EXCHANGE_REQ)
		return "PKEX Version 1 Exchange Request";
	else if (type == DPP_PA_PKEX_EXCHANGE_RESP)
		return "PKEX Exchange Response";
	else if (type == DPP_PA_PKEX_COMMIT_REVEAL_REQ)
		return "PKEX Commit-Reveal Request";
	else if (type == DPP_PA_PKEX_COMMIT_REVEAL_RESP)
		return "PKEX Commit-Reveal Response";
	else if (type == DPP_PA_CONFIGURATION_RESULT)
		return "Configuration Result";
	else if (type == DPP_PA_CONNECTION_STATUS_RESULT)
		return "Connection Status Result";
	else if (type == DPP_PA_PRESENCE_ANNOUNCEMENT)
		return "Presence Announcement";
	else if (type == DPP_PA_RECONFIG_ANNOUNCEMENT)
		return "Reconfiguration Announcement";
	else if (type == DPP_PA_RECONFIG_AUTH_REQ)
		return "Reconfiguration Authentication Request";
	else if (type == DPP_PA_RECONFIG_AUTH_RESP)
		return "Reconfiguration Authentication Response";
	else if (type == DPP_PA_RECONFIG_AUTH_CONF)
		return "Reconfiguration Authentication Confirm";
	else if (type == DPP_PA_PKEX_EXCHANGE_REQ)
		return "PKEX Exchange Request";
	else if (type == 0x13)
		return "Push Button Presence Announcement";
	else if (type == 0x14)
		return "Push Button Presence Announcement Response";
	else if (type == 0x15)
		return "Private Peer Introduction Query";
	else if (type == 0x16)
		return "Private Peer Introduction Notify";
	else if (type == 0x17)
		return "Private Peer Introduction Update";
	return "Unknown";
}


struct dpp_frame *dpp_get_config_resp(struct agent *a, uint8_t dialog_token, uint8_t *src)
{
	struct dpp_frame *f = NULL;

	list_for_each_entry(f, &a->conf_resplist, list) {
		if (dialog_token == f->id && !memcmp(f->src, src, 6))
			return f;
	}

	return NULL;
}

static void dpp_frame_tmo(atimer_t *t)
{
	struct dpp_frame *f = container_of(t, struct dpp_frame, ageout);
	int l = sizeof(struct list_head);
	uint8_t *h = (uint8_t *)&f->list;

	if (!(h[0] == 0 && !memcmp(h, h + 1, l - 1)))
		list_del(&f->list);
	if (f->frame)
		free(f->frame);
	free(f);
}

struct dpp_frame *dpp_frame_init(struct agent *a, uint8_t *frm,
				 uint16_t framelen, uint8_t id,
				 uint8_t *src, int tmo)
{
	struct dpp_frame *f;

	f = calloc(1, sizeof(*f));
	if (!f)
		return NULL;

	f->frame = calloc(1, framelen);
	if (!f->frame) {
		free(f);
		return NULL;
	}

	f->framelen = framelen;
	memcpy(f->frame, frm, framelen);
	f->id = id;
	memcpy(f->src, src, 6);
	if (tmo > 0) {
		timer_init(&f->ageout, dpp_frame_tmo);
		timer_set(&f->ageout, tmo * 1000);
	}

	return f;
}

void dpp_frame_free(struct dpp_frame *f)
{
	free(f->frame);
	free(f);
}

void dpp_sta_event_handler(void *agent, struct blob_attr *msg)
{
	struct agent *a = (struct agent *)agent;
	char ifname[16] = {0}, event[16] = {0};
	struct blob_attr *tb[3];
	static const struct blobmsg_policy ev_attr[3] = {
		[0] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
		[1] = { .name = "event", .type = BLOBMSG_TYPE_STRING },
		[2] = { .name = "data", .type = BLOBMSG_TYPE_TABLE },
	};

	blobmsg_parse(ev_attr, 3, tb, blob_data(msg), blob_len(msg));

	if (!tb[0] || !tb[1] || !tb[2])
		return;

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

	if (!strcmp(event, "action")) {
		struct blob_attr *data[2];
		static const struct blobmsg_policy data_attr[2] = {
			[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "raw", .type = BLOBMSG_TYPE_STRING },
		};
		char mac_str[18] = {0};
		uint8_t enrollee[6] = {0};
		char *framestr;
		uint8_t *frame;
		int framelen = 0;
		uint16_t frametype;

		blobmsg_parse(data_attr, 2, data, blobmsg_data(tb[2]),
				blobmsg_data_len(tb[2]));

		if (!data[0] || !data[1])
			return;

		strncpy(mac_str, blobmsg_data(data[0]), sizeof(mac_str) - 1);

		if (!hwaddr_aton(mac_str, enrollee))
			return;

		framestr = (char *)blobmsg_data(data[1]);

		framelen = strlen(framestr);
		framelen = framelen / 2;
		frame = calloc(framelen, sizeof(uint8_t));
		if (!frame)
			return;

		if (!strtob(framestr, framelen, frame))
			goto out;

		if (*frame != 0x04)
			goto out;

		frametype = dpp_get_frame_type(frame + 1, framelen - 1);
		if (frametype == 255)
			goto out;
		if (FRAME_IS_DPP_PUB_AF(frame + 1)) {
#if 0 /* TODO: for active exchange we do not want to overwrite AP... */
			if (!allmac_lookup(&a->peer_table) &&
				frametype == DPP_PA_PRESENCE_ANNOUNCEMENT)
				// insert only if does not exist
#endif

			dbg("|%s:%d| Wi-Fi Alliance frametype:%s\n", __func__, __LINE__, dpp_frame_type2str(frametype));
			switch (frametype) {
			case DPP_PA_AUTHENTICATION_REQ:
			case DPP_PA_AUTHENTICATION_CONF:
			case DPP_PA_PEER_DISCOVERY_RESP: {
				dpp_enrollee_rx_handler(a, ifname, enrollee,
							frametype, framelen,
							frame);
				break;
			}
			case DPP_PA_PRESENCE_ANNOUNCEMENT:
			case DPP_PA_AUTHENTICATION_RESP:
			case DPP_PA_CONFIGURATION_RESULT:
			case DPP_PA_PEER_DISCOVERY_REQ:
			case DPP_PA_CONNECTION_STATUS_RESULT: {
				dpp_relay_rx_handler(a, ifname, enrollee,
						     frametype, framelen,
						     frame);
				break;
			}
			default:
				break;
			}
		} else if (FRAME_IS_DPP_GAS_FRAME(frame + 1)) {
			dbg("|%s:%d| Wi-Fi GAS frametype:%d\n", __func__, __LINE__, frametype);
			switch (frametype) {
			case DPP_PUB_AF_GAS_COMEBACK_RESP:
			case DPP_PUB_AF_GAS_INITIAL_RESP: {
				dpp_enrollee_rx_handler(a, ifname, enrollee,
							frametype, framelen,
							frame);
				break;
			}
			case DPP_PUB_AF_GAS_INITIAL_REQ: {
				dpp_relay_rx_handler(a, ifname, enrollee,
							frametype, framelen,
							frame);
				break;
			}
			default:
				break;
			}
		} else {
			dbg("|%s:%d| Unknown Public Action frame with frametype:%d\n", __func__, __LINE__, frametype);
		}
out:
		free(frame);
	}
}

void dpp_send_event(struct agent *a)
{
	char data[128] = { 0 };

	snprintf(data, sizeof(data), "{"\
			"\"event\":\"dpp\","\
			"\"data\": {"\
				"\"status\":\"%s\","\
				"\"reason\":\"%s\","\
			"}"\
		"}", "success", "completed");

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

int dpp_send_wifi_frame(struct agent *a, char *ifname, uint8_t *dst,
			uint16_t frametype, uint8_t *frame, uint32_t freq,
			size_t framelen)
{
	bool hostapd;

	if (FRAME_IS_DPP_PUB_AF(frame + 1)) {
		switch(frametype) {
		case DPP_PA_AUTHENTICATION_REQ:
		case DPP_PA_AUTHENTICATION_CONF:
		case DPP_PA_PEER_DISCOVERY_RESP: {
			hostapd = true;
			break;
		}
		case DPP_PA_PRESENCE_ANNOUNCEMENT:
		case DPP_PA_AUTHENTICATION_RESP:
		case DPP_PA_CONFIGURATION_RESULT:
		case DPP_PA_PEER_DISCOVERY_REQ:
		case DPP_PA_CONNECTION_STATUS_RESULT: {
			hostapd = false;
			break;
		}
		default:
			return -1;
		}
	} else if (FRAME_IS_DPP_GAS_FRAME(frame + 1)) {
		switch(frametype) {
		case DPP_PUB_AF_GAS_INITIAL_RESP: {
			hostapd = true;
			break;
		}
		case DPP_PUB_AF_GAS_INITIAL_REQ: {
			hostapd = false;
			break;
		}
		default:
			return -1;
		}
	} else
		return -1;


	return wifi_ubus_ap_send_actframe(a->ubus_ctx, ifname, dst, frame,
					  framelen, freq, hostapd);
}

void dpp_legacy_timeout_cb(atimer_t *t)
{
	trace("%s: --->\n", __func__);
	struct agent *a = container_of(t, struct agent, dpp_legacy_timeout);
	struct wifi_radio_element *re;

	list_for_each_entry(re, &a->radiolist, list) {
		uint16_t mid;
		struct cmdu_buff *cmdu;
		int ret;

		if (re->state != AUTOCFG_ACTIVE)
			continue;

		cmdu = agent_gen_ap_autoconfig_wsc(a, a->cntlr_almac, re);
		if (!cmdu)
			continue;

		ret = CMDU_HOOK(a, cmdu_get_type(cmdu), cmdu_get_mid(cmdu),
				cmdu->origin, cmdu->cdata, cmdu_size(cmdu),
				CMDU_PRE_TX);
		if (ret != CMDU_DROP) {
			mid = agent_send_cmdu(a, cmdu);
			if (mid) {
				re->wsc_mid = mid;
				trace("assigned radio mid %d %d\n", re->wsc_mid, mid);
			}
		} else {
			warn("[%s]: cmdu 0x%04X dropped by extension before Tx\n",
				__func__, cmdu_get_type(cmdu));
		}

		cmdu_free(cmdu);
	}
}

void dpp_send_chirp(atimer_t *t)
{
	struct agent *a = container_of(t, struct agent, dpp_periodic_pa);
	uint8_t bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
	struct wifi_radio_element *re;
#define DPP_CHIRP_PER_CHANNEL 2

	if (agent_has_active_backhaul(a))
		goto out;

	dbg("%s: Here I want to send PA!\n", __func__);
#ifdef DEBUG
	dump(a->pa_frame, a->pa_framelen, "Presence Announcement");
#endif

	list_for_each_entry(re, &a->radiolist, list) {
		struct netif_bk *bk = &re->bk;
		int i, channel;
		uint32_t freq;

		if (!re->has_bsta)
			continue;

		if (!bk->cfg || !bk->dpp_cfg || bk->cfg->onboarded) {
			dbg("%s: do not chirp on bsta %s\n", __func__, bk->ifname);
			continue;
		}

		if (bk->wps_active) {
			dbg("%s: do not chirp on bsta %s while WPS is active\n", __func__, bk->ifname);
			continue;
		}

		if (bk->dpp_ch_chirp_cnt >= DPP_CHIRP_PER_CHANNEL) {
			bk->dpp_ch_chirp_cnt = 0;
			bk->dpp_ch_chirp_idx++;
			bk->dpp_ch_chirp_idx %= bk->dpp_cfg->num_chan;
		}

		i = bk->dpp_ch_chirp_idx;
		channel = bk->dpp_cfg->channel[i];

		freq = channel_to_freq(bk->cfg->band, channel);
		if (!freq) {
			err("%s: unknown freq for channel:%d on bsta:%s\n",
				__func__, channel, bk->ifname);
			continue;
		}

		if (bk->dpp_ch_chirp_cnt == 0) {
			wifi_ubus_dpp_stop_listen(a->ubus_ctx, bk->ifname, false);
			wifi_ubus_dpp_listen(a->ubus_ctx, bk->ifname, freq, false);
		}

		dbg("%s: Sending PA for bsta:%s freq:%u\n", __func__, bk->ifname,
				freq);
		wifi_ubus_ap_send_actframe(a->ubus_ctx, bk->ifname, bcast,
					   a->pa_frame, a->pa_framelen, freq,
					   false);
		bk->dpp_ch_chirp_cnt++;

#ifdef ZEROTOUCH_DPP
		if (a->zt.encoded_frame) {
#ifdef DEBUG
			dump(a->zt.encoded_frame, a->zt.encoded_framelen, "Zereo-touch EVP KEY Encoded frame");
#endif
			wifi_ubus_ap_send_actframe(a->ubus_ctx, bk->ifname,
						   bcast, a->zt.encoded_frame,
						   a->zt.encoded_framelen,
						   freq, false);
		}

		if (a->zt.encoded_frame2) {
#ifdef DEBUG
			dump(a->zt.encoded_frame2, a->zt.encoded_framelen2, "Zero-touch Passphrase Encoded frame");
#endif
			wifi_ubus_ap_send_actframe(a->ubus_ctx, bk->ifname,
						   bcast, a->zt.encoded_frame2,
						   a->zt.encoded_framelen2,
						   freq, false);
		}
#endif
	}

out:
	timer_set(&a->dpp_periodic_pa, 5 * 1000);
}

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

	snprintf(fmt, sizeof(fmt), "cce %s %d", ifname, enable);
	agent_exec_platform_scripts(fmt);
}

#endif

#endif
