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

#include "dpp.h"

#include <libubox/list.h>
#include <easy/easy.h>
#include <wifidefs.h>
#include <dpp_api.h>
#include <dpputils.h>
#include <string.h>
#include <json-c/json.h>
#include <libubox/blobmsg_json.h>

#include "cmdu.h"
#include "cntlr.h"
#include "cntlr_cmdu.h"
#include "cntlr_map.h"
#include "cntlr_plugin.h"
#include "utils/utils.h"
#include "utils/debug.h"

#define MAX_ALIAS_LEN		64
#if ZEROTOUCH_DPP
#define MAX_PASSPHRASE_LEN	64
#endif
#define MAX_NUM_SHKEYS		16

char *dppfile_gen_alias(char *alias_buf, size_t buf_len)
{
	struct json_object *uris = NULL;
	struct json_object *arr = NULL;
	int ret, i;

	strncpy(alias_buf, "cpe-1", buf_len - 1);

	uris = json_object_from_file(DPP_URI_FILE);
	if (!uris)
		return alias_buf;

	if (!json_object_is_type(uris, json_type_object)) {
		cntlr_dbg(LOG_DPP, "%s: %s is not a JSON object (type:%d)\n", __func__,
		    DPP_URI_FILE, json_object_get_type(uris));
		goto out;
	}

	ret = json_object_object_get_ex(uris, "enrollees", &arr);
	if (!ret || !arr || !json_object_is_type(arr, json_type_array))
		goto out;

	for (i = 1; i <= MAX_ALIAS_LEN; i++) {
		int used = 0;
		int j, len;

		snprintf(alias_buf, buf_len, "cpe-%d", i);
		len = json_object_array_length(arr);
		for (j = 0; j < len; j++) {
			struct json_object *entry = json_object_array_get_idx(arr, j);
			struct json_object *j_alias;
			const char *p_alias;

			if (!entry || !json_object_is_type(entry, json_type_object))
				continue;

			if (!json_object_object_get_ex(entry, "alias", &j_alias))
				continue;

			p_alias = json_object_get_string(j_alias);
			if (p_alias && !strcmp(p_alias, alias_buf)) {
				used = 1;
				break;
			}
		}

		if (!used) {
			cntlr_dbg(LOG_DPP, "%s: alias '%s' is available\n", __func__, alias_buf);
			goto out;
		}
	}

	cntlr_info(LOG_DPP, "%s: no available alias found (max %d)\n", __func__, MAX_ALIAS_LEN);
	return NULL;

out:
	if (uris)
		json_object_put(uris);

	return alias_buf;
}

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";
}


#ifdef ZEROTOUCH_DPP
static void dpp_notify_zerotouch_plugin(struct controller *c,
			     const char *alias, const char *key,
			     bool add)
{
	struct cntlr_plugin *plugin;
	struct {
		struct enrollee {
			char alias[64];
			char key[64];
		} e;
		bool add;
	} zt_priv = {
		.add = add
	};

	if (alias)
		strncpy(zt_priv.e.alias, alias, sizeof(zt_priv.e.alias) - 1);
	if (key)
		strncpy(zt_priv.e.key, key, sizeof(zt_priv.e.key) - 1);

	plugin = cntlr_lookup_plugin(c, "zerotouch");
	if (plugin)
		plugin->process(plugin->priv, &zt_priv, 0);
}
#endif

static bool dpp_entry_match(const char *alias, const char *key, const char *uri,
			const char *p_alias, const char *p_key, const char *p_uri)
{
	if (alias && p_alias &&
	    !strncmp(p_alias, alias, MAX_ALIAS_LEN - 1))
		return true;

#if ZEROTOUCH_DPP
	if (key && p_key &&
	    !strncmp(p_key, key, MAX_PASSPHRASE_LEN - 1))
		return true;
#endif

	if (uri && p_uri &&
	    !strncmp(p_uri, uri, strlen(p_uri) + 1))
		return true;

	return false;
}

int dpp_mod_enrollee(struct controller *c, const char *key,
		     const char *alias, const char *uri,
		     bool add)
{
	struct json_object *root = NULL, *arr = NULL, *entry = NULL;
	int rc = ZT_PASSPHRASE_MOD_ERROR;
	int rv, len, i;
	bool del = !add;

	/* addition requires alias as unique identifier */
	if (add && (!alias || !strlen(alias)))
		goto out;

	root = json_object_from_file(DPP_URI_FILE);
	if (!root) {
		if (del)
			goto out;

		cntlr_dbg(LOG_DPP, "%s: failed to read json:%s error:%s. Generating new JSON\n",
			__func__, DPP_URI_FILE, json_util_get_last_err());

		root = json_object_new_object();
		if (!root) {
			cntlr_warn(LOG_DPP, "%s: failed to create root JSON object\n", __func__);
			goto out;
		}

		arr = json_object_new_array();
		if (!arr) {
			cntlr_warn(LOG_DPP, "%s: failed to create enrollees array\n", __func__);
			goto out_put_json;
		}

		json_object_object_add(root, "enrollees", arr);
	} else {
		rv = json_object_object_get_ex(root, "enrollees", &arr);
		if (!rv || !arr || !json_object_is_type(arr, json_type_array)) {
			cntlr_err(LOG_DPP, "%s: malformed or missing 'enrollees' array in %s\n",
				__func__, DPP_URI_FILE);
			goto out_put_json;
		}
	}

	len = json_object_array_length(arr);
	for (i = 0; i < len; i++) {
		struct json_object *j_alias = NULL, *j_key = NULL, *j_uri = NULL, *t = NULL;
		const char *p_alias = NULL, *p_key = NULL, *p_uri = NULL;

		t = json_object_array_get_idx(arr, i);
		if (!t || !json_object_is_type(t, json_type_object))
			continue;

		json_object_object_get_ex(t, "alias", &j_alias);
		json_object_object_get_ex(t, "key", &j_key);
		json_object_object_get_ex(t, "uri", &j_uri);

		if (j_alias)
			p_alias = json_object_get_string(j_alias);
		if (j_key)
			p_key = json_object_get_string(j_key);
		if (j_uri)
			p_uri = json_object_get_string(j_uri);

		if (del) {
			struct dpp_bootstrap_info *bi;

			if (!dpp_entry_match(alias, key, uri,
					    p_alias, p_key, p_uri))
				continue;

			bi = dpp_get_bootstrap_info_from_uri(c->dpp, (char *)p_uri);
			if (bi) {
				dpp_bootstrap_free(bi);
				list_del(&bi->list);
			}

#ifdef ZEROTOUCH_DPP
			if (p_alias || p_key)
				dpp_notify_zerotouch_plugin(c, p_alias, p_key, false);
#endif
			json_object_array_del_idx(arr, i, 1);

			rc = ZT_PASSPHRASE_MOD_SUCCESS;
			goto write_and_exit;
		} else if (add) {
#if ZEROTOUCH_DPP
			if (key && p_key && !strncmp(p_key, key, MAX_PASSPHRASE_LEN - 1)) {
				cntlr_err(LOG_DPP,
					  "%s: key '%s' already exists\n",
					  __func__, key);
				rc = ZT_PASSPHRASE_MOD_EXISTS;
				goto out;
			}
#endif

			if (p_alias && !strncmp(p_alias, alias, MAX_ALIAS_LEN - 1)) {
				entry = t;
				if (key)
					json_object_object_del(t, "key");
			}
		}
	}

	/* entry not found, nothing to remove */
	if (del) {
		rc = ZT_PASSPHRASE_MOD_SUCCESS;
		goto write_and_exit;
	} else if (!entry) {
		entry = json_object_new_object();
		if (!entry) {
			cntlr_warn(LOG_DPP, "%s: failed to create entry object\n", __func__);
			goto out_put_json;
		}

		json_object_array_add(arr, entry);
		json_object_object_add(entry, "alias", json_object_new_string(alias));
	}

	if (key)
		json_object_object_add(entry, "key", json_object_new_string(key));

	cntlr_info(LOG_DPP, "%s: added new alias '%s'\n", __func__, alias);
	rc = ZT_PASSPHRASE_MOD_SUCCESS;

#ifdef ZEROTOUCH_DPP
	dpp_notify_zerotouch_plugin(c, alias, key, add);
#endif

write_and_exit:
	if (json_object_to_file_ext(DPP_URI_FILE, root, JSON_C_TO_STRING_PRETTY) != 0)
		cntlr_dbg(LOG_DPP, "%s: failed to write file %s\n", __func__, DPP_URI_FILE);

out_put_json:
	if (root)
		json_object_put(root);
out:
	return rc;
}


#ifdef ZEROTOUCH_DPP
int zerotouch_add_enrollee_key(struct controller *c, const char *key, const char *alias)
{
	return dpp_mod_enrollee(c, key, alias, NULL, true);
}

int zerotouch_del_enrollee_key(struct controller *c, const char *key, const char *alias)
{
	return dpp_mod_enrollee(c, key, alias, NULL, false);
}
#endif

int dpp_append_enrollee_uri(struct controller *c, const char *uri, const char *alias)
{
	struct json_object *root = NULL, *arr = NULL, *entry = NULL;
	int rc = ZT_PASSPHRASE_MOD_ERROR;
	int rv, len, i;

	if (!alias || !strlen(alias))
		goto out;

	/* Load JSON file or create new */
	root = json_object_from_file(DPP_URI_FILE);
	if (!root) {
		cntlr_dbg(LOG_DPP, "%s: failed to read json:%s error:%s. Generating new JSON\n",
			__func__, DPP_URI_FILE, json_util_get_last_err());

		root = json_object_new_object();
		if (!root) {
			cntlr_warn(LOG_DPP, "%s: failed to create root JSON object\n", __func__);
			goto out;
		}

		arr = json_object_new_array();
		if (!arr) {
			cntlr_warn(LOG_DPP, "%s: failed to create enrollees array\n", __func__);
			goto out_put_json;
		}

		json_object_object_add(root, "enrollees", arr);
	}


	rv = json_object_object_get_ex(root, "enrollees", &arr);
	if (!rv || !arr || !json_object_is_type(arr, json_type_array)) {
		cntlr_warn(LOG_DPP, "%s: malformed or missing 'enrollees' array in %s\n",
			__func__, DPP_URI_FILE);
		goto out_put_json;
	}

	len = json_object_array_length(arr);
	for (i = 0; i < len; i++) {
		struct json_object *j_alias = NULL, *j_uri = NULL, *t = NULL;
		const char *p_alias = NULL, *p_uri = NULL;

		t = json_object_array_get_idx(arr, i);
		if (!t || !json_object_is_type(t, json_type_object))
			continue;

		json_object_object_get_ex(t, "alias", &j_alias);
		json_object_object_get_ex(t, "uri", &j_uri);

		if (j_alias)
			p_alias = json_object_get_string(j_alias);
		if (j_uri)
			p_uri = json_object_get_string(j_uri);

		if (uri && p_uri && !strcmp(p_uri, uri)) {
			cntlr_dbg(LOG_DPP, "%s: uri already exists (alias '%s'), rejecting\n",
			    __func__, p_alias ? p_alias : "unknown");
			rc = ZT_PASSPHRASE_MOD_EXISTS;
			goto out_put_json;
		}

		if (p_alias && !strcmp(p_alias, alias)) {
			cntlr_dbg(LOG_DPP, "%s: found existing alias '%s', updating\n", __func__, alias);

			if (uri)
				json_object_object_del(t, "uri");
			entry = t;
		}
	}

	if (!entry) {
		entry = json_object_new_object();
		if (!entry) {
			cntlr_warn(LOG_DPP, "%s: failed to create entry object\n", __func__);
			goto out_put_json;
		}
		json_object_array_add(arr, entry);
		json_object_object_add(entry, "alias", json_object_new_string(alias));
	}

	if (uri)
		json_object_object_add(entry, "uri", json_object_new_string(uri));

	if (json_object_to_file_ext(DPP_URI_FILE, root, JSON_C_TO_STRING_PRETTY) != 0)
		cntlr_dbg(LOG_DPP, "%s: failed to write file %s\n", __func__, DPP_URI_FILE);

	rc = ZT_PASSPHRASE_MOD_SUCCESS;
out_put_json:
	if (root)
		json_object_put(root);
out:
	return rc;
}

int dpp_cntlr_read_enrollees(struct controller *c)
{
	static const struct blobmsg_policy top_attr[] = {
		[0] = { .name = "enrollees", .type = BLOBMSG_TYPE_ARRAY },
	};
#if ZEROTOUCH_DPP
	struct cntlr_plugin *plugin = cntlr_lookup_plugin(c, "zerotouch");
#endif
	struct blob_attr *tb[ARRAY_SIZE(top_attr)];
	struct blob_buf uris = { 0 };
	struct blob_attr *cur;
	int rc = 0;
	int rem;

	blob_buf_init(&uris, 0);

	if (!blobmsg_add_json_from_file(&uris, DPP_URI_FILE)) {
		cntlr_dbg(LOG_DPP, "%s: failed to parse %s\n", __func__, DPP_URI_FILE);
		rc = -1;
		goto out;
	}

	blobmsg_parse(top_attr, ARRAY_SIZE(top_attr), tb, blob_data(uris.head), blob_len(uris.head));
	if (!tb[0]) {
		cntlr_dbg(LOG_DPP, "%s: no 'enrollees' array found in %s\n", __func__, DPP_URI_FILE);
		rc = -1;
		goto out;
	}

	blobmsg_for_each_attr(cur, tb[0], rem) {
		static const struct blobmsg_policy enrollee_attr[] = {
			[0] = { .name = "alias", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "uri",   .type = BLOBMSG_TYPE_STRING },
#if ZEROTOUCH_DPP
			[2] = { .name = "key",   .type = BLOBMSG_TYPE_STRING },
#endif
		};
		struct blob_attr *tb2[ARRAY_SIZE(enrollee_attr)];
		const char *alias;

		if (blobmsg_type(cur) != BLOBMSG_TYPE_TABLE)
			continue;

		blobmsg_parse(enrollee_attr, ARRAY_SIZE(enrollee_attr), tb2,
			      blobmsg_data(cur), blobmsg_data_len(cur));

		if (!tb2[0])
			continue;

		alias = blobmsg_get_string(tb2[0]);
		if (!alias || !strlen(alias))
			continue;

#if ZEROTOUCH_DPP
		if (tb2[2] && plugin) {
			const char *key;

			key = blobmsg_get_string(tb2[2]);
			if (key && strlen(key)) {
				cntlr_dbg(LOG_DPP, "%s: found key: %s\n", __func__, key);
				dpp_notify_zerotouch_plugin(c, alias, key, true);
			}
		}
#endif

		if (tb2[1]) {
			const char *uri;

			uri = blobmsg_get_string(tb2[1]);
			if (uri && strlen(uri)) {
				struct dpp_bootstrap_info *enrollee_bi;

				cntlr_dbg(LOG_DPP, "%s: found URI: %s\n", __func__, uri);

				enrollee_bi = calloc(1, sizeof(*enrollee_bi));
				if (!enrollee_bi) {
					cntlr_warn(LOG_DPP, "%s: failed to allocate enrollee_bi\n", __func__);
					rc = -1;
					goto out;
				}

				if (dpp_build_bootstrap_info_from_uri(uri, enrollee_bi)) {
					cntlr_warn(LOG_DPP, "%s: failed to build bootstrap from URI\n", __func__);
					free(enrollee_bi);
					continue;
				}

				if (dpp_bootstrap_add(c->dpp, enrollee_bi)) {
					cntlr_warn(LOG_DPP, "%s: failed to add bootstrap\n", __func__);
					free(enrollee_bi);
					continue;
				}
			}
		}
	}

out:
	blob_buf_free(&uris);
	return rc;
}

int dpp_frame_handler(void *dpp, uint8_t *smac, enum dpp_event ev,
		      uint8_t *frame, size_t framelen)
{
	cntlr_trace(LOG_DPP, "%s: ----->\n", __func__);

	struct controller *c;
	int ret = 0;

	if (!smac) {
		cntlr_warn(LOG_DPP, "%s: Error: smac = NULL\n", __func__);
		ret = -1;
		goto out;
	}

	cntlr_info(LOG_DPP, "%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 cmdu_buff *resp;
		int frametype = 0;
		struct node *proxy = NULL;

		frametype = dpp_get_frame_type(frame + 1, framelen - 1);
		if (frametype == 255) {
			cntlr_warn(LOG_DPP, "Invalid frametype\n");
			ret = -1;
			goto out;
		}

		cntlr_dbg(LOG_DPP, "DPP frametype %s\n", dpp_frame_type2str(frametype));

		c = dpp_get_ctx_private_data(dpp);
		if (!c) {
			ret = -1;
			goto out;
		}
#ifdef DEBUG
		dump(frame, framelen, "DPP Frame Response");
#endif /* DEBUG */

		/* if peer has private data, it is the proxy node
		 * thus we can infer its a proxied encap DPP message
		 */
		proxy = dpp_get_peer_private_data(dpp, smac);
		if (proxy) {
			resp = cntlr_gen_proxied_encap_dpp(c, smac, frametype,
							   frame, framelen,
							   NULL, 0);
			if (!resp) {
				cntlr_warn(LOG_DPP, "%s: DPP: ----> Error generating proxy encap\n",
					   __func__);
				ret = -1;
				goto out;
			}
		} else {
			resp = cntlr_gen_direct_encap_dpp(c, smac, frame,
							  framelen);
			if (!resp) {
				cntlr_warn(LOG_DPP, "%s: DPP: ----> Error generating direct encap\n",
					  __func__);
				ret = -1;
				goto out;
			}
		}

		switch (frametype) {
		case DPP_PA_AUTHENTICATION_REQ: {
			if (proxy) {
				struct node *n = NULL;

				list_for_each_entry(n, &c->nodelist, list) {
					memcpy(resp->origin, n->almacaddr, 6);
					cntlr_dbg(LOG_DPP, "|%s:%d| send frame to "MACFMT"\n",
						  __func__, __LINE__, MAC2STR(n->almacaddr));
					send_cmdu(c, resp);
				}
				cmdu_free(resp);
				break;
			}
			/* FALLTHROUGH */
		}
		case DPP_PA_AUTHENTICATION_CONF:
		case DPP_PUB_AF_GAS_INITIAL_RESP:
		case DPP_PA_PEER_DISCOVERY_RESP:
			if (proxy)
				memcpy(resp->origin, proxy->almacaddr, 6);
			else
				memcpy(resp->origin, smac, 6);
			send_cmdu(c, resp);
			cmdu_free(resp);
			break;
		default:
			cntlr_warn(LOG_DPP, "%s: DPP: ----> Unknown frame\n",
				   __func__);
			break;
		}
		break;
	}
	case DPP_EVENT_BAD_AUTH:
		cntlr_warn(LOG_DPP, "%s: DPP: BAD AUTH!\n", __func__);
		/* FALLTHROUGH */
	case DPP_EVENT_TIMEOUT: {
		struct dpp_peer *peer = dpp_lookup_peer(dpp, smac);

		cntlr_info(LOG_DPP, "%s: DPP: EVENT TIMEOUT!\n", __func__);

		if (peer) {
			list_del(&peer->list);
			dpp_free_peer(dpp, peer);
		}
		break;
	}
	default:
		break;
	}

out:
	return ret;
}
#endif
#endif
