/*
 * zerotouch.c - Full zero-touch DPP bootstrapping plugin.
 *
 * Copyright (C) 2025 Genexis Sweden AB.
 *
 * Author: anjan.chanda@genexis.eu
 *
 * See LICENSE file for license related information.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/time.h>
#include <dlfcn.h>
#include <time.h>
#include <libubox/list.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <json-c/json.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/evp.h>
#include <uci.h>
#include <easy/easy.h>
#include <wifiutils.h>

#include <cmdu.h>
#include <1905_tlvs.h>
#include <easymesh.h>

#include <map-controller/wifi_dataelements.h>
#include <map-controller/wifi_opclass.h>
#include <map-controller/cntlr_commands.h>
#include <map-controller/cntlr_apis.h>
#include <map-controller/cntlr_plugin.h>
#include <map-controller/utils/debug.h>

/* needed library */
#define LIBZTDPP_SOFILE		"/usr/lib/libztdpp.so"
#define PRIVKEY_FILE		"/root/priv.pem"
#ifndef MAC_ADDR_HASH
#define MAC_ADDR_HASH(a)	(a[0] ^ a[1] ^ a[2] ^ a[3] ^ a[4] ^ a[5])
#endif

#ifndef blobmsg_add_macaddr
#define blobmsg_add_macaddr(b, f, v)	\
do {					\
	char _vstr[18] = {0};		\
	hwaddr_ntoa(v, _vstr);		\
	blobmsg_add_string(b, f, _vstr);\
} while (0)
#endif

#ifndef cntlr_dbg
#define cntlr_dbg(feat, fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)  /* Flawfinder: ignore */
#endif

#define ZT_PLUGIN_NAME        "zerotouch"

#if 0 /* unused */
/* per-bSTA bootstrap data */
struct zt_enrollee_data {
	uint8_t macaddr[6];
	uint8_t bssid[6];
	uint8_t agent[6];	/* agent AL-macaddr this bSTA belongs to */
	struct hlist_node hlist;
	void *priv;	/* struct zt_bootstrap_control */
};
#endif

struct zt_enrollee {
#define MAX_ALIAS_LEN		64
#define MAX_PASSPHRASE_LEN	64
	char alias[MAX_ALIAS_LEN];
	char passphrase[MAX_PASSPHRASE_LEN];
};

/* private context */
struct zt_bootstrap_control {
	bool enabled;

#define MAX_NUM_SHKEYS		16
	int num_enrollee;
	struct zt_enrollee enrollee[MAX_NUM_SHKEYS];

	/* libztdpp functions */
	void *privkey;
	void *libztdpp;
	int (*dpp_process_pa_vsie_frame)(void *, uint8_t *, size_t, char **);
	int (*dpp_process_pa_vsie_frame2)(char *, uint8_t *, size_t, char **);

	void *self;	/* our plugin struct */
};

static int zt_bootstrap_init(void **priv);
static int zt_bootstrap_configure(void *priv, void *config_section);
static int zt_bootstrap_exit(void *priv);


static int libztdpp_open(void *priv, const char *sofile)
{
	struct zt_bootstrap_control *zt = priv;
	int ret;

	if (!zt || !sofile)
		return -EINVAL;

	if (zt->libztdpp || zt->dpp_process_pa_vsie_frame)
		return 0;

	zt->libztdpp = dlopen(sofile, RTLD_NOW | RTLD_GLOBAL);
	if (!zt->libztdpp)
		return errno;

	zt->dpp_process_pa_vsie_frame = dlsym(zt->libztdpp, "dpp_process_pa_vsie_frame");
	if (!zt->dpp_process_pa_vsie_frame) {
		ret = -1;
		goto out_error;
	}

	zt->dpp_process_pa_vsie_frame2 = dlsym(zt->libztdpp, "dpp_process_pa_vsie_frame2");
	if (!zt->dpp_process_pa_vsie_frame2) {
		ret = -1;
		goto out_error;
	}

	return 0;

out_error:
	dlclose(zt->libztdpp);
	zt->libztdpp = NULL;
	return ret;
}

static void libztdpp_close(void *priv)
{
	struct zt_bootstrap_control *zt = priv;
	if (zt && zt->dpp_process_pa_vsie_frame) {
		zt->dpp_process_pa_vsie_frame = NULL;
		zt->dpp_process_pa_vsie_frame2 = NULL;
		dlclose(zt->libztdpp);
		zt->libztdpp = NULL;
	}
}

int zt_set_dpp_enrollee_uri(void *priv, char *uri, char *alias)
{
	struct zt_bootstrap_control *zt = priv;
	void *cntlr = ((struct cntlr_plugin *)(zt->self))->controller;
	struct blob_buf bi = { 0 };
	struct blob_buf bo = { 0 };
	int ret = 0;

	dbg("%s: Enter\n", __func__);
	blob_buf_init(&bi, 0);
	blob_buf_init(&bo, 0);
	blobmsg_add_string(&bi, "uri", uri);
	if (alias)
		blobmsg_add_string(&bi, "alias", alias);

	ret = COMMAND(dpp_enrollee_uri)(cntlr, bi.head, &bo);
	dbg("%s: set dpp_enrollee_uri %s\n", __func__, !ret ? "SUCCESS": "FAILED");
	blob_buf_free(&bi);
	blob_buf_free(&bo);

	cntlr_dbg(LOG_BSTEER, "%s: Exit\n", __func__);
	return ret;
}

int zt_cmdu_notifier(void *priv, struct cmdu_buff *cmdu)
{
	struct zt_bootstrap_control *zt = priv;
	uint16_t type = cmdu_get_type(cmdu);
	int ret = 0;

	dbg("%s: received CMDU 0x%02X\n", __func__, type);
	if (!zt || !zt->dpp_process_pa_vsie_frame || !zt->dpp_process_pa_vsie_frame2)
		return 0;

	switch (type) {
	case CMDU_TUNNELED:
		struct tlv *t;
		struct tlv_tunnel_msg_type *ttype;
		struct tlv_tunneled *ttun;
		char *alias = NULL;
		char *uri = NULL;

		t = cmdu_peek_tlv(cmdu, MAP_TLV_TUNNELED_MSG_TYPE);
		if (!t) {
			dbg("%s: Ignore malformed Tunneled Msg (no type)\n", __func__);
			return -1;
		}

		ttype = (struct tlv_tunnel_msg_type *)t->data;
		if (ttype->type != 0x0a && ttype->type != 0x0b) {
			dbg("%s: Ignore Tunneled Msg type 0x%02x\n", __func__, ttype->type);
			return -1;
		}

		t = cmdu_peek_tlv(cmdu, MAP_TLV_TUNNELED);
		if (!t) {
			dbg("%s: Ignore malformed Tunneled Msg (no data)\n", __func__);
			return -1;
		}

		ttun = (struct tlv_tunneled *)t->data;
		if (ttype->type == 0x0a && zt->privkey) {
			ret = zt->dpp_process_pa_vsie_frame(zt->privkey, ttun->frame, tlv_length(t), &uri);
			if (ret) {
				dbg("%s: Error processing PA frame\n", __func__);
				return -1;
			}
		} else {
			for (int i = 0; i < zt->num_enrollee; i++) {
				ret = zt->dpp_process_pa_vsie_frame2(zt->enrollee[i].passphrase,
								     ttun->frame,
								     tlv_length(t),
								     &uri);
				if (!ret) {
					alias = zt->enrollee[i].alias;
					break;
				}
			}

			if (ret) {
				dbg("%s: Error processing PA frame2\n", __func__);
				return -1;
			}
		}
		if (uri) {
			ret = zt_set_dpp_enrollee_uri(priv, uri, alias);
		}
	default:
		break;
	}

	return ret;
}

static int zt_add_passphrase(struct zt_bootstrap_control *zt,
			     const char *passphrase, const char *alias)
{
	for (int i = 0; i < zt->num_enrollee; i++) {
		if (!strncmp(zt->enrollee[i].passphrase, passphrase, sizeof(zt->enrollee[i].passphrase)))
			return 0;
	}

	if (zt->num_enrollee < MAX_NUM_SHKEYS) {
		int num = zt->num_enrollee;

		snprintf(zt->enrollee[num].alias, MAX_ALIAS_LEN, "%s", alias);
		snprintf(zt->enrollee[num].passphrase, MAX_PASSPHRASE_LEN, "%s", passphrase);
		zt->num_enrollee++;
		return 0;
	}

	return -1;
}

int zt_process(void *priv, void *data, uint16_t rxcmdu_type)
{
	struct zt_enrollee *enrollee = (struct zt_enrollee *) data;
	struct zt_bootstrap_control *zt = priv;
	int ret = -1;

	UNUSED(rxcmdu_type);
	ret = zt_add_passphrase(zt, enrollee->passphrase, enrollee->alias);

	return ret;
}

uint16_t cmdu_notifylist[] = {
	CMDU_TUNNELED,
	0xffff
};

struct cntlr_plugin zerotouch = {
	.name = ZT_PLUGIN_NAME,
	.init = zt_bootstrap_init,
	.config = zt_bootstrap_configure,
	.exit = zt_bootstrap_exit,
	.cmdu_notifier = zt_cmdu_notifier,
	.num_cmdu = ARRAY_SIZE(cmdu_notifylist),
	.cmdulist = cmdu_notifylist,
	.process = zt_process,
};

void *zt_load_privatekey(void)
{
	EVP_PKEY *pkey;
	BIO *bio;

	bio = BIO_new_file(PRIVKEY_FILE, "r");
	if (!bio) {
		dbg("%s: Zero-touch provisioning private key missing (%s).\n",
		     __func__, PRIVKEY_FILE);
		return NULL;
	}

	pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL);
	BIO_free(bio);
	if (!pkey) {
		dbg("%s: Failed to read zero-touch private key.\n", __func__);
		return NULL;
	}

	return (void *) pkey;
}

static int zt_bootstrap_init(void **priv)
{
	struct zt_bootstrap_control *p;

	p = calloc(1, sizeof(struct zt_bootstrap_control));
	if (!p)
		return -1;

	p->privkey = zt_load_privatekey();
	if (p->privkey)
		info("Key based zero-touch bootstrapping initialized\n");

	*priv = p;
	p->self = &zerotouch;
	libztdpp_open(p, LIBZTDPP_SOFILE);

	info("Passphrase based zero-touch bootstrapping initialized\n");
	return 0;
}

static int zt_bootstrap_configure(void *priv, void *config_section)
{
	trace("%s: Enter\n", __func__);
	//TODO: if anything interesting

	trace("%s: Exit\n", __func__);
	return 0;
}

static int zt_bootstrap_exit(void *priv)
{
	struct zt_bootstrap_control *p = (struct zt_bootstrap_control *)priv;

	trace("%s: Enter\n", __func__);
	if (p) {
		libztdpp_close(p);
		free(p);
		if (p->privkey)
			EVP_PKEY_free(p->privkey);
	}

	trace("%s: Exit\n", __func__);
	return 0;
}

