/*
 * 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 "dpphandler.h"

#include <cmdu.h>
#include <easy/utils.h>
#include <easy/if_utils.h>
#include <wifidefs.h>
#include <dpp_api.h>
#include <dpputils.h>
#include <easy/bufutil.h>
#include <easy/utils.h>
#include <libubox/list.h>
#include <stdbool.h>
#include <stdlib.h>
#include <timer.h>

#include "agent.h"
#include "agent_cmdu.h"
#include "agent_map.h"
#include "allmac.h"
#include "config.h"
#include "dpp.h"
#include "dppfrag.h"
#include "utils/debug.h"



void dpp_relay_rx_handler(struct agent *a, char *ifname, uint8_t *src,
			  uint16_t frametype, uint16_t framelen, uint8_t *frame)
{
	uint16_t hashlen = 0;
	uint8_t *hash = NULL;
	struct cmdu_buff *cmdu;

	if (FRAME_IS_DPP_PUB_AF(frame + 1)) {
		uint8_t *ptr;
		struct netif_ap *ap;

		ap = agent_get_ap_by_ifname(a, ifname);
		if (!ap || !ap->cfg)
			return;

		if (ap->cfg->multi_ap == 0x01)
			return;

		allmac_insert(&a->peer_table, src,
				MAC_ENTRY_DPP_ENROLLEE,
				(void *)ap);
		switch (frametype) {
		case DPP_PA_PRESENCE_ANNOUNCEMENT:
#ifndef DPP_RELAY_CHRIP_PROXIED_ENCAP
		{

			ptr = frame;
			ptr++;

			ptr += 6; /* PA Type + oui + oui type + crypto suite */
			ptr++; /* frame type */
			hash = dpp_get_attr(ptr, framelen - labs(ptr - frame),
					DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
					&hashlen);

			cmdu = agent_gen_chirp_notification(a, src,
							    hash, hashlen);
			if (cmdu) {
				agent_send_cmdu(a, cmdu);
				cmdu_free(cmdu);
			}
			break;
		}
#endif
		case DPP_PA_AUTHENTICATION_RESP:
		case DPP_PA_CONFIGURATION_RESULT:
		case DPP_PA_PEER_DISCOVERY_REQ:
		case DPP_PA_CONNECTION_STATUS_RESULT: {
			ptr = frame;
			ptr++;

			ptr += 6; /* PA Type + oui + oui type + crypto suite */
			ptr++; /* frame type */

			hash = dpp_get_attr(ptr, framelen - labs(ptr - frame),
					DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
					&hashlen);

			cmdu = agent_gen_proxied_encap_dpp(a, src,
							frametype,
							frame,
							framelen,
							hash, hashlen);
			if (cmdu) {
				agent_send_cmdu(a, cmdu);
				cmdu_free(cmdu);
			}
			break;
		}
		}
		err("|%s:%d|\n", __func__, __LINE__);
	} else if (FRAME_IS_DPP_GAS_FRAME(frame + 1)) {
		uint8_t *ptr;

		ptr = frame;
		ptr++;

		ptr += 6; /* PA Type + oui + oui type + crypto suite */
		ptr++; /* frame type */

		hash = dpp_get_attr(ptr, framelen - labs(ptr - frame),
				DPP_ATTR_R_BOOTSTRAP_KEY_HASH,
				&hashlen);

		cmdu = agent_gen_proxied_encap_dpp(a, src,
						frametype,
						frame, framelen,
						hash, hashlen);
		if (cmdu) {
			agent_send_cmdu(a, cmdu);
			cmdu_free(cmdu);
		}
	}
}

static uint8_t *dpp_frags_to_gas_response(struct agent *a, uint8_t dialog_token,
				   uint8_t *src, uint16_t *outlen)
{
	uint8_t *payload, *conf_resp;
	uint16_t payload_len, conf_resp_len;

	dbg("|%s:%d|\n", __func__, __LINE__);

	payload = dppfrag_defrag_payload(a, DPP_GAS_INITIAL_RESP_HEADER_LEN,
		dialog_token, src, &payload_len);
	if (!payload) {
		err("%s: failed to concatenate payload\n", __func__);
		return NULL;
	}

	conf_resp = dpp_build_gas_initial_response(payload_len,
			payload, WLAN_STATUS_SUCCESS,
			dialog_token, false, &conf_resp_len);
	free(payload);
	if (!conf_resp) {
		err("%s: failed to compile gas initial response\n", __func__);
		return NULL;
	}

#ifdef DEBUG
	dump(conf_resp, conf_resp_len, "reconstituted frame");
#endif
	*outlen = conf_resp_len;
	return conf_resp;
}

void dpp_enrollee_rx_handler(struct agent *a, char *ifname, uint8_t *src,
			  uint16_t frametype, uint16_t framelen, uint8_t *frame)
{
	if (FRAME_IS_DPP_PUB_AF(frame + 1)) {
		struct netif_bk *bk;
		uint8_t bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
		void *ev;
		int ret;


		bk = agent_get_bsta_by_ifname(a, ifname);
		if (!bk)
			return;

		allmac_insert(&a->peer_table, src,
				MAC_ENTRY_DPP_ENROLLEE,
				(void *)bk);

		ret = dpp_set_peer_private_data(a->dpp, bcast, bk);
		if (ret)
			return;

		ev = dpp_sm_create_event(a->dpp,
						bcast,
						DPP_EVENT_RX_FRAME,
						framelen, frame);
		if (ev) {
			dpp_trigger(a->dpp, bcast, ev);
			dpp_sm_free_event(ev);
		}
	} else if (FRAME_IS_DPP_GAS_FRAME(frame + 1)) {
		switch (frametype) {
		case DPP_PUB_AF_GAS_COMEBACK_RESP: {
			uint8_t fragbyte;
			int ret;
			uint8_t dialog_token;

			fragbyte = *(frame + 5);
			dialog_token = *(frame + 2);
			ret = dppfrag_queue_enqueue(a, frametype, frame,
							framelen, fragbyte,
							dialog_token,
							src);
			if (ret == 1) {
				struct dpp_frame *f;
				uint8_t bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
				uint8_t *conf_resp;
				uint16_t conf_resp_len;
				void *ev;

				dbg("%s: ==> all fragments received (%u)\n",
				    __func__, dialog_token);

				f = dpp_get_config_resp(a, dialog_token,
						src);
				if (!f)
					break;

				conf_resp = dpp_frags_to_gas_response(a,
								dialog_token,
								src,
								&conf_resp_len);
				if (!conf_resp)
					break;

				ev = dpp_sm_create_event(a->dpp,
							bcast,
							DPP_EVENT_RX_FRAME,
							conf_resp_len,
							conf_resp);
				if (ev) {
					dpp_trigger(a->dpp, bcast, ev);
					dpp_sm_free_event(ev);
				}

				free(conf_resp);
			}
			break;
		}
		case DPP_PUB_AF_GAS_INITIAL_RESP: {
			uint8_t bcast[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
			uint8_t *ptr = (frame + 5);
			uint16_t paylen;
			uint8_t dialog_token;

			dialog_token = *(frame + 2);
			paylen = buf_get_le16(ptr);
			dbg("%s: handling frametype:%d (length:%u, dialog:%u, gas_comeback_delay:%u)\n",__func__, frametype, framelen, dialog_token, paylen);
			if (paylen > 0) {
				struct dpp_frame *init_resp;
				uint8_t *conf_resp;
				uint16_t conf_resp_len;
				void *ev;

				init_resp = dpp_frame_init(a, frame, framelen,
							   dialog_token, src, 5);
				if (!init_resp)
					break;

				list_add(&init_resp->list, &a->conf_resplist);

				if (!dppfrag_complete(a, dialog_token, src))
					break;

				dbg("%s: ==> all fragments received (%u)\n",
				    __func__, dialog_token);

				conf_resp = dpp_frags_to_gas_response(a,
							dialog_token,
							src,
							&conf_resp_len);
				if (!conf_resp)
					break;

				ev = dpp_sm_create_event(a->dpp,
							bcast,
							DPP_EVENT_RX_FRAME,
							conf_resp_len,
							conf_resp);
				if (ev) {
					dpp_trigger(a->dpp, bcast, ev);
					dpp_sm_free_event(ev);
				}

				free(conf_resp);
			} else {
				struct dpp_frame *f;
				void *ev;

				f = dpp_get_config_resp(a, dialog_token,
						src);
				if (f || framelen < 500) {
					dbg("%s: config_resp (dialog:%u) already cached\n", __func__, dialog_token);
					break;
				}

				ev = dpp_sm_create_event(a->dpp, bcast,
								DPP_EVENT_RX_FRAME,
								framelen,
								frame);

				if (ev) {
					dpp_trigger(a->dpp, bcast, ev);
					dpp_sm_free_event(ev);
				}
			}
			break;
		}
		default:
			break;
		}
	}
}

int dpp_frame_handler(void *dpp, uint8_t *smac, enum dpp_event ev,
		      uint8_t *frame, size_t framelen)
{
	int frametype = 0;
	struct agent *a;

	dbg("%s: ----->\n", __func__);

	a = dpp_get_ctx_private_data(dpp);
	if (!a) {
		dbg("%s: Error no private data!\n", __func__);
		return -1;
	}

	dbg("%s: DPP_EVENT: event = %d smac = " MACFMT", frame = %p, len = %zu\n",
		__func__, ev, MAC2STR(smac), frame, framelen);

	switch (ev) {
	case DPP_EVENT_TX_FRAME: {
		struct dpp_peer *peer = a->own_peer;
		struct netif_bk *bk;

		UNUSED(peer);

		if (!smac) {
			err("%s: Error: smac = NULL\n", __func__);
			return -1;
		}

		if (!frame) {
			err("%s: Error: frame = NULL\n", __func__);
			return -1;
		}

		if (!framelen) {
			err("%s: Err: framelen = 0\n", __func__);
			return -1;
		}

		frametype = dpp_get_frame_type(frame + 1, framelen - 1);
		if (frametype == 255) {
			err("%s: Invalid frame type!\n", __func__);
			return -1;
		}

		bk = dpp_get_peer_private_data(dpp, smac);
		if (bk) {
			uint32_t freq = 0;

			if (bk->dpp_cfg) {
				int i = bk->dpp_ch_chirp_idx;

				freq = channel_to_freq(bk->cfg->band,
						       bk->dpp_cfg->channel[i]);

			}
			timer_set(&a->dpp_periodic_pa, 5 * 1000);
			dpp_send_wifi_frame(a, bk->ifname, smac, frametype, frame,
					    freq, framelen);
		} else {
			struct cmdu_buff *cmdu;

			cmdu = agent_gen_direct_encap_dpp(a, a->cfg.cntlr_almac,
							  frame,
							  framelen);
			if (cmdu) {
				agent_send_cmdu(a, cmdu);
				cmdu_free(cmdu);
			}
		}
		break;
	}
	case DPP_EVENT_AUTHENTICATED: {
		if (!smac) {
			err("%s: Error: smac = NULL\n", __func__);
			return -1;
		}

		void *e = dpp_sm_create_event(dpp, smac, DPP_EVENT_TRIGGER, 0, NULL);

		if (e) {
			dpp_trigger(dpp, smac, e);
			dpp_sm_free_event(e);
		}
		break;
	}
	case DPP_EVENT_TIMEOUT:
		dpp_set_peer_private_data(dpp, smac, NULL);
		break;
	case DPP_EVENT_BOOTSTRAPPING:
		break;
	case DPP_EVENT_PROVISIONED: {
		struct dpp_peer *peer = a->own_peer;
		bool reload = false;
		int i;

		warn("DPP: PROVISIONED!\n");

		for (i = 0; i < peer->e_req.num_bsta; i++) {
			enum wifi_band band = dpp_chan_list_to_band(peer->e_req.blist[i].chan_list);
			int bandnum = 0;
			struct dpp_config_obj *conf;
			struct netif_bk *bk;

			if (band == BAND_2)
				bandnum = 2;
			if (band == BAND_5)
				bandnum = 5;
			if (band == BAND_6)
				bandnum = 6;

			dbg("DPP: Trying to find creds for band:%d\n", bandnum);

			conf = dpp_config_obj_get_netrole(peer, DPP_NETROLE_MAP_BH_STA, i);
			if (!conf) {
				err("DPP: Did not find conf obj for netrole %s\n",
					dpp_netrole_str(DPP_NETROLE_MAP_BH_STA));
				continue;
			}

			dpp_handle_config_obj(peer, conf);
			bk = agent_get_bsta_by_band(a, band);
			if (!bk)
				continue;

			wifi_apply_iface_cfg(bk->ifname, dpp_akm_str(conf->akm),
					     (char *)conf->ssid,
					     (char *)conf->passphrase);
			bk->cfg->onboarded = true;
			reload = true;
		}

		if (reload) {
			agent_exec_platform_scripts("bsta_to_wireless");
			uci_reload_services("wireless");

		}
		dpp_send_event(a);
		break;
	}
	default:
		break;
	}

	return 0;
}

#endif

#endif
