/*
 * autoconfig.c - implements autoconfig logic and util functions
 *
 * Copyright (C) 2025 IOPSYS Software Solutions AB. All rights reserved.
 *
 */
#include <json-c/json.h>
#include <libubox/blobmsg_json.h>

#include <stdlib.h>
#include <string.h>

#include <1905_tlvs.h>
#include <i1905_wsc.h>
#include <cmdu.h>
#include <easymesh.h>
#include <easy/bufutil.h>
#include <openssl/evp.h>

#include "agent.h"
#include "agent_map.h"
#include "autoconfig.h"
#include "config.h"

#include "utils/debug.h"

void autoconfig_clean_wsc_hash(struct wsc_data *data)
{
	memset(data->sha256, 0, SHA256_LENGTH);
}

void autoconfig_update_wsc_hash(struct wsc_data *data, uint8_t *new_sha256)
{
	memcpy(&data->sha256, new_sha256, SHA256_LENGTH);
}

int autoconfig_wsc_m2_diff(struct wifi_radio_element *re,
			  struct tlv *tlvs[][TLV_MAXNUM],
			  size_t tlvs_size,
			  uint8_t *wsc_m2_sha256)
{
	enum {
		AP_RADIO_ID = 0,
		WSC = 1,
		DEFAULT_8021Q_SETTINGS = 2,
		TRAFFIC_SEPARATION_POLICY = 3,
#if (EASYMESH_VERSION >= 6)
		AP_MLD_CONFIG = 4,
		BACKHAUL_STA_MLD_CONFIG = 5,
		BSS_ADVANCED_CONFIG = 6,
		MAX_TLV_TYPES = 7
#else
		MAX_TLV_TYPES = 4
#endif
	};

	uint8_t new_sha256[SHA256_LENGTH];
	uint8_t bssid[6];
	EVP_MD_CTX *ctx;
	int i;
	int diff = DIFF_NONE;
	struct wsc_data *data = &re->autconfig;

	if (tlvs_size != MAX_TLV_TYPES) {
		agnt_dbg(LOG_APCFG, "%s: Unsupported version of CMDU.\n", __func__);
		return DIFF_ERR;
	}

	memcpy(bssid, tlvs[AP_RADIO_ID][0]->data, 6);

	/* Calculate SHA-256 hash from all TLVs data */
	ctx = EVP_MD_CTX_new();
	if (!ctx)
		return DIFF_ERR;

	if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL))
		goto error_cleanup;

	if (!EVP_DigestUpdate(ctx, tlvs[AP_RADIO_ID][0]->data, tlv_length(tlvs[AP_RADIO_ID][0])))
		goto error_cleanup;

	/* Use decrypted data of WSC TLVs */
	i = 0;
	while (i < TLV_MAXNUM && tlvs[WSC][i]) {
		struct wps_credential wps_out = { 0 };
		uint8_t *ext_out = NULL;
		uint16_t ext_len = 0;
		uint16_t m2_len = tlv_length(tlvs[WSC][i]);
		/* It's workaround as wsc_process_m2 modifies m2 buffer */
		uint8_t *m2_tmp = malloc(m2_len);
		int ret;

		if (!m2_tmp)
			goto error_cleanup;

		memcpy(m2_tmp, tlvs[WSC][i]->data, m2_len);

		ret = wsc_process_m2(data->m1_frame,
				     data->m1_size,
				     data->key, m2_tmp, m2_len,
				     &wps_out, &ext_out, &ext_len);

		free(m2_tmp);

		if (ret)
			goto error_cleanup;

		if (!EVP_DigestUpdate(ctx, &wps_out, offsetof(struct wps_credential, uuid))) {
			free(ext_out);
			goto error_cleanup;
		}

		if (ext_out && !EVP_DigestUpdate(ctx, ext_out, ext_len)) {
			free(ext_out);
			goto error_cleanup;
		}

		free(ext_out);

		++i;
	}

	if (tlvs[DEFAULT_8021Q_SETTINGS][0] &&
	    !EVP_DigestUpdate(ctx, tlvs[DEFAULT_8021Q_SETTINGS][0]->data,
			      tlv_length(tlvs[DEFAULT_8021Q_SETTINGS][0])))
		goto error_cleanup;

	if (tlvs[TRAFFIC_SEPARATION_POLICY][0] &&
	    !EVP_DigestUpdate(ctx, tlvs[TRAFFIC_SEPARATION_POLICY][0]->data,
			      tlv_length(tlvs[TRAFFIC_SEPARATION_POLICY][0])))
		goto error_cleanup;

	if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL))
		goto error_cleanup;

	EVP_MD_CTX_free(ctx);

	agnt_dbg(LOG_APCFG, "%s: current WSC M2 SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__,
		 data->sha256[0], data->sha256[1],
		 data->sha256[2], data->sha256[3],
		 data->sha256[4], data->sha256[5]);

	agnt_dbg(LOG_APCFG, "%s: new WSC M2 SHA256:     %02x%02x%02x%02x%02x%02x...\n", __func__,
		 new_sha256[0], new_sha256[1],
		 new_sha256[2], new_sha256[3],
		 new_sha256[4], new_sha256[5]);

	memcpy(wsc_m2_sha256, new_sha256, SHA256_LENGTH);
	if (memcmp(data->sha256, new_sha256, SHA256_LENGTH))
		diff |= DIFF_WSC_M2;

#if (EASYMESH_VERSION >= 6)
	if (tlvs[AP_MLD_CONFIG][0]) {
		bool ap_mld_updated = false;

		ap_mld_updated = update_sha256_hash(tlvs[AP_MLD_CONFIG][0]->data,
				tlv_length(tlvs[AP_MLD_CONFIG][0]),
				re->agent->ap_mld_cfg_sha256);

		if (ap_mld_updated) {
			agnt_dbg(LOG_APCFG, "%s: AP MLD SHA256 has changed\n", __func__);
			diff |= DIFF_AP_MLD;
		} else
			agnt_dbg(LOG_APCFG, "%s: no change of AP MLD SHA256\n", __func__);
	}

	if (tlvs[BACKHAUL_STA_MLD_CONFIG][0]) {
		bool bsta_mld_updated = false;

		bsta_mld_updated = update_sha256_hash(tlvs[BACKHAUL_STA_MLD_CONFIG][0]->data,
				tlv_length(tlvs[BACKHAUL_STA_MLD_CONFIG][0]),
				re->agent->bsta_mld_cfg_sha256);
		if (bsta_mld_updated) {
			agnt_dbg(LOG_APCFG, "%s: BSTA MLD SHA256 has changed\n", __func__);
			diff |= DIFF_BSTA_MLD;
		} else
			agnt_dbg(LOG_APCFG, "%s: no change of BSTA MLD SHA256\n", __func__);
	}

	if (tlvs[BSS_ADVANCED_CONFIG][0]) {
		const uint8_t *p = tlvs[BSS_ADVANCED_CONFIG][0]->data;
		const uint8_t *end = p + tlv_length(tlvs[BSS_ADVANCED_CONFIG][0]);
		bool changed = false;

		if (p < end) {
			uint8_t num_radio;
			int r;

			num_radio = *p++;
			for (r = 0; r < num_radio && !changed; r++) {
				struct wifi_radio_element *r_el;
				uint8_t ruid[6];
				uint8_t num_bss;
				int b;

				if (p + 7 > end)
					break;
				memcpy(ruid, p, 6);
				p += 6;
				num_bss = *p++;

				r_el = agent_get_radio(re->agent, ruid);
				if (!r_el) {
					p += num_bss * sizeof(struct bss_adv_config_bss);
					continue;
				}

				for (b = 0; b < num_bss && !changed; b++) {
					const struct bss_adv_config_bss *entry;
					struct netif_ap *ap;

					if (p + sizeof(*entry) > end)
						break;
					entry = (const struct bss_adv_config_bss *)p;
					p += sizeof(*entry);

					ap = agent_get_ap_by_bss_index(r_el, entry->bss_index);
					if (!ap)
						continue;

					if (ap->cfg->hidden !=
					    !(entry->flags & BSS_ADV_CONFIG_SSID_ADVERTISEMENT))
						changed = true;
				}
			}
		}
		if (changed)
			diff |= DIFF_BSS_ADV_CONFIG;
	}
#endif
	return diff;

error_cleanup:
	EVP_MD_CTX_free(ctx);

	return DIFF_ERR;
}

static const char *json_get_string(struct json_object *object, const char *key)
{
	json_bool ret;
	struct json_object *value;

	if (!object || !json_object_is_type(object, json_type_object))
		return NULL;

	ret = json_object_object_get_ex(object, key, &value);
	if (!ret || !value || !json_object_is_type(value, json_type_string))
		return NULL;

	return json_object_get_string(value);
}

#if (EASYMESH_VERSION >= 6)
enum autocfg_mld {
	AP_MLD,
	BSTA_MLD,
};

static int autoconfig_mld_hash_to_file(uint8_t *sha256, int type)
{
	struct json_object *wsc_json;
	struct json_object *sha256_obj;
	char type_str[24] = {};
	char *sha256_str;
	json_bool found_in_file;

	agnt_dbg(LOG_APCFG, "%s: file:%s\n", __func__, AUTOCFG_FILE);

	strncpy(type_str,
		(type == AP_MLD) ? "ap_mld_sha256" : "bsta_mld_sha256",
		sizeof(type_str) - 1);

	/* Get the json object from the file */
	wsc_json = json_object_from_file(AUTOCFG_FILE);
	if (!wsc_json) {
		agnt_dbg(LOG_APCFG, "%s: failed to read json:%s, error:%s. "\
			 "Try to generate new\n", __func__,
			 AUTOCFG_FILE, json_util_get_last_err());

		wsc_json = json_object_new_object();
		if (!wsc_json) {
			agnt_warn(LOG_APCFG, "%s: failed to create json obj, error:%s. ",
				  __func__, json_util_get_last_err());
			return -1;
		}
	}

	found_in_file = json_object_object_get_ex(wsc_json, type_str, &sha256_obj);
	if (found_in_file) {
		/* Remove old SHA first */
		json_object_object_del(wsc_json, type_str);
	}

	/* Create sha256 string */
	sha256_str = calloc(SHA256_LENGTH * 2 + 1, sizeof(char));
	if (!sha256_str)
		goto out;
	btostr(sha256, SHA256_LENGTH, sha256_str);

	sha256_obj = json_object_new_string(sha256_str);
	if (WARN_ON(!sha256_obj)) {
		agnt_warn(LOG_APCFG,
				"%s: failed to add %s, error:%s. ",
			  __func__, type_str, json_util_get_last_err());
		goto free;
	}

	json_object_object_add(wsc_json, type_str, sha256_obj);

	/* Update sha256 file */
	json_object_to_file(AUTOCFG_FILE, wsc_json);

	agnt_dbg(LOG_APCFG, "%s: Wrote %s SHA256: %02x%02x%02x%02x%02x%02x... to file %s\n",
		 __func__,
		 (type == AP_MLD) ? "AP MLD" : "BSTA MLD",
		 sha256[0], sha256[1],
		 sha256[2], sha256[3],
		 sha256[4], sha256[5],
		 AUTOCFG_FILE);

free:
	free(sha256_str);
out:
	json_object_put(wsc_json);

	return 0;
}

static int get_mld_hash_from_file(uint8_t *sha256_out, int type, const struct blobmsg_policy *attr)
{
	struct blob_attr *tb[1];
	struct blob_buf wsc_m2_file = {0};
	char *sha256_str;
	int blen;
	int ret = 0;

	blob_buf_init(&wsc_m2_file, 0);

	if (!blobmsg_add_json_from_file(&wsc_m2_file, AUTOCFG_FILE)) {
		agnt_dbg(LOG_APCFG, "Failed to parse %s\n", AUTOCFG_FILE);
		ret = -1;
		goto free;
	}

	blobmsg_parse(attr, 1, tb, blob_data(wsc_m2_file.head), blob_len(wsc_m2_file.head));

	if (!tb[0]) {
		agnt_dbg(LOG_APCFG, "%s: %s SHA256 key not found in %s\n", __func__,
			 (type == AP_MLD) ? "AP MLD" : "BSTA MLD",
			 AUTOCFG_FILE);
		ret = -1;
		goto free;
	}

	sha256_str = blobmsg_get_string(tb[0]);
	if (!sha256_str) {
		agnt_err(LOG_APCFG, "%s: No valid %s SHA256\n", __func__,
			 (type == AP_MLD) ? "AP MLD" : "BSTA MLD");
		ret = -1;
		goto free;
	}

	blen = strlen(sha256_str) / 2;
	if (blen > SHA256_LENGTH) {
		agnt_err(LOG_APCFG, "%s: %s SHA256 too long\n", __func__,
			 (type == AP_MLD) ? "AP MLD" : "BSTA MLD");
		ret = -1;
		goto free;
	}

	strtob(sha256_str, blen, sha256_out);

	agnt_dbg(LOG_APCFG, "%s: Read %s SHA256: %02x%02x%02x%02x%02x%02x...\n", __func__,
	    (type == AP_MLD) ? "AP MLD" : "BSTA MLD",
	    sha256_out[0], sha256_out[1],
	    sha256_out[2], sha256_out[3],
	    sha256_out[4], sha256_out[5]);

free:
	blob_buf_free(&wsc_m2_file);

	return ret;
}

/* keep the AP MLD md5sum in non-persistent storage */
int autoconfig_ap_mld_hash_to_file(uint8_t *sha256)
{
	return autoconfig_mld_hash_to_file(sha256, AP_MLD);
}

int autoconfig_ap_mld_hash_from_file(uint8_t *sha256_out)
{
	static const struct blobmsg_policy attr_ap[] = {
		[0] = { .name = "ap_mld_sha256", .type = BLOBMSG_TYPE_STRING },
	};

	return get_mld_hash_from_file(sha256_out, AP_MLD, attr_ap);
}

/* keep the BSTA MLD md5sum in non-persistent storage */
int autoconfig_bsta_mld_hash_to_file(uint8_t *sha256)
{
	return autoconfig_mld_hash_to_file(sha256, BSTA_MLD);
}

int autoconfig_bsta_mld_hash_from_file(uint8_t *sha256_out)
{
	static const struct blobmsg_policy attr_bsta[] = {
		[0] = { .name = "bsta_mld_sha256", .type = BLOBMSG_TYPE_STRING },
	};

	return get_mld_hash_from_file(sha256_out, BSTA_MLD, attr_bsta);
}
#endif /* EASYMESH_VERSION >= 6 */

/* keep the WSC M2 md5sum in non-persistent storage */
int autoconfig_radio_hash_to_file(uint8_t *macaddr, uint8_t *sha256)
{
	struct json_object *wsc_json;
	struct json_object *radios, *radio_obj = NULL;
	struct json_object *sha256_obj;
	char *sha256_str;
	json_bool ret;
	int len, i;

	agnt_dbg(LOG_APCFG, "%s: file:%s\n", __func__, AUTOCFG_FILE);

	/* Get the json object from the file */
	wsc_json = json_object_from_file(AUTOCFG_FILE);
	if (!wsc_json) {
		agnt_dbg(LOG_APCFG, "%s: failed to read json:%s, error:%s. "\
			 "Try to generate new\n", __func__,
			 AUTOCFG_FILE, json_util_get_last_err());

		wsc_json = json_object_new_object();
		if (!wsc_json) {
			agnt_warn(LOG_APCFG, "%s: failed to create json obj, error:%s. ",
				  __func__, json_util_get_last_err());
			return -1;
		}
	}

	/* Get the radios array */
	ret = json_object_object_get_ex(wsc_json, "radios", &radios);
	if (!ret) {
		/* Create radios array if not found */
		radios = json_object_new_array();
		if (!radios) {
			agnt_warn(LOG_APCFG, "%s: failed to add radio array, error:%s. ",
				  __func__, json_util_get_last_err());
			goto out_radio;
		}
		json_object_object_add(wsc_json, "radios", radios);
	} else if (!json_object_is_type(radios, json_type_array)) {
		agnt_warn(LOG_APCFG, "%s: file: %s has wrong format\n",
			  __func__, AUTOCFG_FILE);
		goto out_radio;
	}

	/* Check if radio already exists in radio array */
	len = json_object_array_length(radios);
	for (i = 0; i < len; i++) {
		struct json_object *t;
		uint8_t radio_mac[6];
		const char *p;

		t = json_object_array_get_idx(radios, i);
		if (!t) {
			agnt_dbg(LOG_APCFG, "%s: couldn't get entry:%d from radio array", __func__, i);
			continue;
		}

		p = json_get_string(t, "macaddr");
		if (!p) {
			agnt_dbg(LOG_APCFG, "%s: couldn't get macaddr from entry:%d", __func__, i);
			continue;
		}

		if (!hwaddr_aton(p, radio_mac)) {
			agnt_dbg(LOG_APCFG, "%s: couldn't convert macaddr from entry:%d", __func__, i);
			continue;
		}

		if (!memcmp(macaddr, radio_mac, 6)) {
			/* radio entry already present in file */
			radio_obj = t;
			break;
		}
	}

	/* Create new radio object if not found */
	if (!radio_obj) {
		char radiostr[18] = {0};
		struct json_object *val;

		radio_obj = json_object_new_object();
		if (WARN_ON(!radio_obj))
			goto out_radio;

		/* radio mac */
		hwaddr_ntoa(macaddr, radiostr);
		val = json_object_new_string(radiostr);
		if (!val) {
			json_object_put(radio_obj);
			goto out_radio;
		}
		json_object_object_add(radio_obj, "macaddr", val);

		/* Add radio object to radios array */
		json_object_array_add(radios, radio_obj);
	}

	/* Get the sha256 string */
	ret = json_object_object_get_ex(radio_obj, "m2_sha256", &sha256_obj);
	if (ret) {
		/* Remove old SHA first */
		json_object_object_del(radio_obj, "m2_sha256");
	}

	/* Create sha256 string */
	sha256_str = calloc(SHA256_LENGTH * 2 + 1, sizeof(char));
	if (!sha256_str)
		goto out_radio;

	btostr(sha256, SHA256_LENGTH, sha256_str);

	sha256_obj = json_object_new_string(sha256_str);
	if (!sha256_obj) {
		json_object_put(radio_obj);
		goto free;
	}
	json_object_object_add(radio_obj, "m2_sha256", sha256_obj);

	/* Update sha256 file */
	json_object_to_file(AUTOCFG_FILE, wsc_json);

	agnt_dbg(LOG_APCFG, "%s: Wrote autoconfig SHA256: %02x%02x%02x%02x%02x%02x... to file %s\n",
		 __func__,
		 sha256[0], sha256[1],
		 sha256[2], sha256[3],
		 sha256[4], sha256[5],
		 AUTOCFG_FILE);

free:
	free(sha256_str);
out_radio:
	json_object_put(wsc_json);

	return 0;
}

int autoconfig_radio_hash_from_file(uint8_t *macaddr, uint8_t *sha256_out)
{
	struct blob_buf radios = { 0 };
	struct blob_attr *b;
	static const struct blobmsg_policy attr[] = {
		[0] = { .name = "radios", .type = BLOBMSG_TYPE_ARRAY },
	};
	struct blob_attr *tb[ARRAY_SIZE(attr)];
	int rem;
	int ret = 0;

	blob_buf_init(&radios, 0);

	if (!blobmsg_add_json_from_file(&radios, AUTOCFG_FILE)) {
		agnt_dbg(LOG_APCFG, "Failed to parse %s\n", AUTOCFG_FILE);
		ret = -1;
		goto out;
	}

	blobmsg_parse(attr, ARRAY_SIZE(attr), tb, blob_data(radios.head), blob_len(radios.head));

	if (!tb[0]) {
		ret = -1;
		goto out;
	}

	blobmsg_for_each_attr(b, tb[0], rem) {
		static const struct blobmsg_policy radio_attr[2] = {
			[0] = { .name = "macaddr", .type = BLOBMSG_TYPE_STRING },
			[1] = { .name = "m2_sha256", .type = BLOBMSG_TYPE_STRING },
		};
		struct blob_attr *tb1[ARRAY_SIZE(radio_attr)];
		char radio_str[18] = {0};
		uint8_t radio_mac[6] = {0};
		char *sh256_str;
		int blen;

		blobmsg_parse(radio_attr, ARRAY_SIZE(radio_attr), tb1, blobmsg_data(b), blob_len(b));
		if (!tb1[0] || !tb1[1])
			continue;

		strncpy(radio_str, blobmsg_data(tb1[0]), sizeof(radio_str) - 1);
		if (!hwaddr_aton(radio_str, radio_mac)) {
			agnt_dbg(LOG_APCFG, "Failed to convert macaddr %s\n", radio_str);
			continue;
		}

		if (memcmp(macaddr, radio_mac, 6))
			continue;

		sh256_str = blobmsg_get_string(tb1[1]);
		if (!sh256_str) {
			agnt_err(LOG_APCFG, "%s: No valid sha256\n", __func__);
			ret = -1;
			break;
		}

		blen = strlen(sh256_str) / 2;
		if (blen > SHA256_LENGTH) {
			agnt_err(LOG_APCFG, "%s: SHA256 string too long\n", __func__);
			ret = -1;
			break;
		}

		strtob(sh256_str, blen, sha256_out);
	}

	agnt_dbg(LOG_APCFG, "%s: Read autoconfig SHA256 for radio " MACFMT ": %02x%02x%02x%02x%02x%02x...\n",
		__func__, MAC2STR(macaddr),
		sha256_out[0], sha256_out[1],
		sha256_out[2], sha256_out[3],
		sha256_out[4], sha256_out[5]);

out:
	blob_buf_free(&radios);

	return ret;
}

int autoconfig_gen_radio_hash_from_uci(struct wifi_radio_element *re)
{
	uint8_t new_sha256[SHA256_LENGTH];
	struct netif_ap *ap;
	EVP_MD_CTX *ctx;

	if (list_empty(&re->aplist))
		return -1;

	ctx = EVP_MD_CTX_new();
	if (!ctx)
		return -1;

	if (!EVP_DigestInit_ex(ctx, EVP_sha256(), NULL))
		goto error;

	if (!EVP_DigestUpdate(ctx, re->macaddr, 6))
		goto error;

	list_for_each_entry(ap, &re->aplist, list) {
		struct wps_credential wps_out = {0};
		uint8_t multi_ap;

		if (!ap->cfg)
			continue;

		strncpy((char *)wps_out.ssid, ap->cfg->ssid, sizeof(wps_out.ssid) - 1);
		wps_out.ssidlen = strlen(ap->cfg->ssid);

		strncpy((char *)wps_out.key, ap->cfg->key, sizeof(wps_out.key) - 1);
		wps_out.keylen = strlen(ap->cfg->key);

		wps_out.auth_type = get_encryption(ap->cfg->encryption);
		wps_out.enc_type = (wps_out.auth_type == WPS_AUTH_OPEN) ?
				WPS_ENCR_NONE : WPS_ENCR_AES;

		multi_ap = ap->cfg->multi_ap;
		wps_out.mapie = ((multi_ap & 2) ? 0x20 : 0) | ((multi_ap & 1) ? 0x40 : 0);

		wps_out.band = re->band;

		if (!EVP_DigestUpdate(ctx, &wps_out, offsetof(struct wps_credential, uuid)))
			goto error;
	}

	if (agent_is_ts_enabled(re->agent)) {
		struct agent *a = re->agent;
		struct tlv_default_8021q_settings ts_8021q;
		struct netif_apcfg *apcfg;
		uint8_t ts_buf[600];
		uint8_t *p = ts_buf + 1;
		uint8_t num_ssid = 0;

		BUF_PUT_BE16(ts_8021q.pvid, a->cfg.pcfg->pvid);
		ts_8021q.pcp = (a->cfg.pcfg->pcp_default << 5) & PCP_MASK;
		if (!EVP_DigestUpdate(ctx, &ts_8021q, sizeof(ts_8021q)))
			goto error;

		list_for_each_entry_reverse(apcfg, &a->cfg.aplist, list) {
			uint8_t len = (uint8_t)strlen(apcfg->ssid);
			uint8_t *scan = ts_buf + 1;
			bool dup = false;
			int i;

			for (i = 0; i < num_ssid; i++) {
				if (*scan == len && !memcmp(scan + 1, apcfg->ssid, len)) {
					dup = true;
					break;
				}
				scan += 1 + *scan + 2;
			}
			if (dup)
				continue;

			*p++ = len;
			memcpy(p, apcfg->ssid, len);
			p += len;
			buf_put_be16(p, apcfg->vid);
			p += 2;
			num_ssid++;
		}
		ts_buf[0] = num_ssid;
		if (!EVP_DigestUpdate(ctx, ts_buf, p - ts_buf))
			goto error;
	}

	if (!EVP_DigestFinal_ex(ctx, new_sha256, NULL))
		goto error;

	EVP_MD_CTX_free(ctx);

	agnt_dbg(LOG_APCFG, "%s: pre-cooked WSC M2 SHA256 for radio " MACFMT ": "
		  "%02x%02x%02x%02x%02x%02x...\n", __func__, MAC2STR(re->macaddr),
		  new_sha256[0], new_sha256[1], new_sha256[2],
		  new_sha256[3], new_sha256[4], new_sha256[5]);

	memcpy(re->autconfig.sha256, new_sha256, SHA256_LENGTH);
	autoconfig_radio_hash_to_file(re->macaddr, new_sha256);

	return 0;

error:
	EVP_MD_CTX_free(ctx);
	return -1;
}
