/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * policy.c - CMDU policy for Easymesh TLVs
 *
 * Copyright (C) 2022 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for license related information.
 *
 */


#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>

#include "util.h"
#include "1905_tlvs.h"
#include "i1905_wsc.h"
#include "cmdu.h"
#include "easymesh.h"
#include "map_module.h"

#define CMDU_TYPE_MAP_START	CMDU_1905_ACK
#define CMDU_TYPE_MAP_END	MAP_CMDU_TYPE_MAX

struct cmdu_tlv_policy {
	size_t num;
	struct tlv_policy *pol;
};

#include "r1.c"
#include "r2.c"
#include "r3.c"
#include "r4.c"
#include "r6.c"


int map_cmdu_parse_tlvs(struct cmdu_buff *cmdu, struct tlv *tv[][TLV_MAXNUM],
			int num_tv, int profile)
{
	uint16_t type;
	int idx = 0;
	int revision = 4; /* default revision */
	int last_error;
	uint8_t wsc_mtype = 0;

	if (!cmdu || !cmdu->cdata) {
		map_error =  MAP_STATUS_ERR_CMDU_MALFORMED;
		return -1;
	}

	type = cmdu_get_type(cmdu);

	if (type >= CMDU_TYPE_1905_START && type <= CMDU_TYPE_1905_END) {
		idx = type;
	} else if (type >= CMDU_TYPE_MAP_START && type <= CMDU_TYPE_MAP_END) {
		idx = type - CMDU_TYPE_MAP_START + CMDU_TYPE_1905_END + 1;
	} else {
		map_error = MAP_STATUS_ERR_CMDU_TYPE_NOT_SUPPORTED;
		return -1;
	}

	if (type == CMDU_TYPE_AP_AUTOCONFIGURATION_WSC) {
		struct tlv *t = cmdu_peek_tlv(cmdu, TLV_TYPE_WSC);

		if (!t) {
			map_error = MAP_STATUS_ERR_TLV_NUM_LESS;
			return -1;
		}

		wsc_mtype = wsc_get_message_type(t->data, tlv_length(t));
	}

	last_error = MAP_STATUS_OK;
#ifdef EASYMESH_VERSION
	revision = EASYMESH_VERSION;
#endif
	for (; revision >= 1; --revision) {
		struct cmdu_tlv_policy *pol = NULL;
		int i;

		last_error = MAP_STATUS_OK;
		switch (revision) {
		case 1:
			if (idx > ARRAY_SIZE(easymesh_policy_r1))
				continue;

			pol = &easymesh_policy_r1[idx];

			/* WSC-M2 has different policy than WSC (M1) message */
			if (wsc_mtype == WPS_M2)
				pol = &easymesh_policy_r1_wsc_m2;

			break;
#if (EASYMESH_VERSION >= 2)
		case 2:
			if (idx > ARRAY_SIZE(easymesh_policy_r2)) {
				last_error = MAP_STATUS_ERR_MAP_POLICY_NOT_FOUND;
				continue;
			}

			pol = &easymesh_policy_r2[idx];

			if (wsc_mtype == WPS_M2)
				pol = &easymesh_policy_r2_wsc_m2;

			break;
#endif
#if (EASYMESH_VERSION >= 3)
		case 3:
			if (idx > ARRAY_SIZE(easymesh_policy_r3))
				continue;

			pol = &easymesh_policy_r3[idx];

			if (wsc_mtype == WPS_M2)
				pol = &easymesh_policy_r3_wsc_m2;

			break;
#endif
#if (EASYMESH_VERSION >= 4)
		case 4:
			if (idx > ARRAY_SIZE(easymesh_policy_r4)) {
				last_error = MAP_STATUS_ERR_MAP_POLICY_NOT_FOUND;
				continue;
			}

			pol = &easymesh_policy_r4[idx];

			if (wsc_mtype == WPS_M2)
				pol = &easymesh_policy_r4_wsc_m2;

			break;
#endif
#if (EASYMESH_VERSION >= 6)
		case 6:
			if (idx > ARRAY_SIZE(easymesh_policy_r6)) {
				last_error = MAP_STATUS_ERR_MAP_POLICY_NOT_FOUND;
				continue;
			}

			pol = &easymesh_policy_r6[idx];

			if (wsc_mtype == WPS_M2)
				pol = &easymesh_policy_r6_wsc_m2;

			break;
#endif
		default:
			/* shouldn't happen but silence compiler */
			break;
		}

		if (pol == NULL) {
			continue;
		}

		if (pol->pol == NULL) {
			last_error = MAP_STATUS_ERR_MAP_POLICY_NOT_FOUND;
			continue;
		}

		if (pol->num == 0)
			continue;

		if (num_tv < pol->num) {
			fprintf(stderr, "%s: minimum %zu tv needed!\n", __func__, pol->num);
			last_error = MAP_STATUS_ERR_TLVS_OUTPUT_ARRAY_INSUFFICIENT;
			break;
		}

		/* explicitly zero out the passed tlvs array before using */
		for (i = 0; i < num_tv; i++)
			memset(tv[i], 0, TLV_MAXNUM * sizeof(struct tlv *));

		if (!cmdu_parse_tlvs(cmdu, tv, pol->pol, pol->num)) {
			return 0;
		}

		last_error = ieee1905_error;
	}

	map_error = last_error;
	return -1;
}
