/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * cmdu.c - IEEE1905 CMDU and TLV handling
 *
 * Copyright (C) 2021-2024 IOPSYS Software Solutions AB. All rights reserved.
 * Copyright (C) 2025-2024 Genexis AB.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */

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

#include "util.h"
#include "1905_tlvs.h"
#include "cmdu.h"


const char *cmdu_type2str(uint16_t type)
{

#define T2STR(t)	case CMDU_TYPE_ ## t: return #t;

	switch (type) {
	T2STR(TOPOLOGY_DISCOVERY)
	T2STR(TOPOLOGY_NOTIFICATION)
	T2STR(TOPOLOGY_QUERY)
	T2STR(TOPOLOGY_RESPONSE)
	T2STR(VENDOR_SPECIFIC)
	T2STR(LINK_METRIC_QUERY)
	T2STR(LINK_METRIC_RESPONSE)
	T2STR(AP_AUTOCONFIGURATION_SEARCH)
	T2STR(AP_AUTOCONFIGURATION_RESPONSE)
	T2STR(AP_AUTOCONFIGURATION_WSC)
	T2STR(AP_AUTOCONFIGURATION_RENEW)
	T2STR(PUSH_BUTTON_EVENT_NOTIFICATION)
	T2STR(PUSH_BUTTON_JOIN_NOTIFICATION)
	T2STR(HIGHER_LAYER_QUERY)
	T2STR(HIGHER_LAYER_RESPONSE)
	T2STR(INTERFACE_POWER_CHANGE_REQUEST)
	T2STR(INTERFACE_POWER_CHANGE_RESPONSE)
	T2STR(GENERIC_PHY_QUERY)
	T2STR(GENERIC_PHY_RESPONSE)
	}

	return "UNKNOWN";

#undef T2STR
}

const char *tlv_type2str(uint8_t type)
{

#define T2STR(t)	case TLV_TYPE_ ## t: return #t;

	switch (type) {
	T2STR(END_OF_MESSAGE)
	T2STR(AL_MAC_ADDRESS_TYPE)
	T2STR(DEVICE_INFORMATION_TYPE)
	T2STR(DEVICE_BRIDGING_CAPABILITIES)
	T2STR(NON_1905_NEIGHBOR_DEVICE_LIST)
	T2STR(NEIGHBOR_DEVICE_LIST)
	T2STR(TRANSMITTER_LINK_METRIC)
	T2STR(RECEIVER_LINK_METRIC)
	T2STR(SEARCHED_ROLE)
	T2STR(AUTOCONFIG_FREQ_BAND)
	T2STR(SUPPORTED_ROLE)
	T2STR(SUPPORTED_FREQ_BAND)
	T2STR(WSC)
	}

	return "UNKNOWN";

#undef T2STR
}

struct tlv *tlv_alloc(uint16_t datalen)
{
	struct tlv *n = calloc(1, sizeof(*n) + datalen);

	return n;
}

void tlv_zero(struct tlv *t)
{
	if (t)
		memset(t, 0, t->len + sizeof(*t));
}

void tlv_free_linear(struct tlv *t)
{
	if (t)
		free(t);
}

int tlv_ok(struct tlv *t, int rem)
{
	uint16_t l;

	if (rem < sizeof(struct tlv))
		return 0;

	l = buf_get_be16(&((uint8_t *)t)[1]);
	if (l + 3 > rem)
		return 0;

	return 1;
}

struct tlv *tlv_next(struct tlv *t, int *rem)
{
	uint16_t l = buf_get_be16(&((uint8_t *)t)[1]);

	*rem -= (l + 3);
	return (struct tlv *)((uint8_t *)t + l + 3);
}

uint16_t tlv_length(struct tlv *t)
{
	return buf_get_be16(&((uint8_t *)t)[1]);
}

uint16_t tlv_total_length(struct tlv *t)
{
	return tlv_length(t) + 3;
}

static size_t tlv_minsize(struct tlv *t)
{
	const size_t sizeof_tlv[] = {
		[TLV_TYPE_END_OF_MESSAGE] =                      sizeof(struct tlv_eom),
		[TLV_TYPE_AL_MAC_ADDRESS_TYPE] =                 sizeof(struct tlv_aladdr),
		[TLV_TYPE_MAC_ADDRESS_TYPE] =                    sizeof(struct tlv_macaddr),
		[TLV_TYPE_DEVICE_INFORMATION_TYPE] =             sizeof(struct tlv_device_info),
		[TLV_TYPE_DEVICE_BRIDGING_CAPABILITIES] =        sizeof(struct tlv_device_bridge_caps),
		[TLV_TYPE_NON_1905_NEIGHBOR_DEVICE_LIST] =       sizeof(struct tlv_non1905_neighbor),
		[TLV_TYPE_NEIGHBOR_DEVICE_LIST] =                sizeof(struct tlv_1905neighbor),
		[TLV_TYPE_LINK_METRIC_QUERY] =                   sizeof(struct tlv_linkmetric_query),
		[TLV_TYPE_TRANSMITTER_LINK_METRIC] =             sizeof(struct tlv_tx_linkmetric),
		[TLV_TYPE_RECEIVER_LINK_METRIC] =                sizeof(struct tlv_rx_linkmetric),
		[TLV_TYPE_VENDOR_SPECIFIC] =                     sizeof(struct tlv_vendor_specific),
		[TLV_TYPE_LINK_METRIC_RESULT_CODE] =             sizeof(struct tlv_linkmetric_result),
		[TLV_TYPE_SEARCHED_ROLE] =                       sizeof(struct tlv_searched_role),
		[TLV_TYPE_AUTOCONFIG_FREQ_BAND] =                sizeof(struct tlv_autoconfig_band),
		[TLV_TYPE_SUPPORTED_ROLE] =                      sizeof(struct tlv_supported_role),
		[TLV_TYPE_SUPPORTED_FREQ_BAND] =                 sizeof(struct tlv_supported_band),
		[TLV_TYPE_WSC] =                                 sizeof(struct tlv_wsc),
		[TLV_TYPE_PUSH_BUTTON_EVENT_NOTIFICATION] =      sizeof(struct tlv_pbc_notification),
		[TLV_TYPE_PUSH_BUTTON_JOIN_NOTIFICATION] =       sizeof(struct tlv_pbc_join_notification),
		[TLV_TYPE_GENERIC_PHY_DEVICE_INFORMATION] =      sizeof(struct tlv_generic_phy_devinfo),
		[TLV_TYPE_DEVICE_IDENTIFICATION] =               sizeof(struct tlv_device_identification),
		[TLV_TYPE_CONTROL_URL] =                         sizeof(struct tlv_control_url),
		[TLV_TYPE_IPV4] =                                sizeof(struct tlv_ipv4),
		[TLV_TYPE_IPV6] =                                sizeof(struct tlv_ipv6),
		[TLV_TYPE_GENERIC_PHY_EVENT_NOTIFICATION] =      sizeof(struct tlv_pbc_generic_phy_notification),
		[TLV_TYPE_1905_PROFILE_VERSION] =                sizeof(struct tlv_1905_profile),
		[TLV_TYPE_POWER_OFF_INTERFACE] =                 sizeof(struct tlv_power_off),
		[TLV_TYPE_INTERFACE_POWER_CHANGE_INFORMATION] =  sizeof(struct tlv_powerchange_request),
		[TLV_TYPE_INTERFACE_POWER_CHANGE_STATUS] =       sizeof(struct tlv_powerchange_status),
		[TLV_TYPE_L2_NEIGHBOR_DEVICE] =                  sizeof(struct tlv_l2_neighbor),
	};

	if (t->type <= TLV_TYPE_L2_NEIGHBOR_DEVICE)
		return sizeof_tlv[t->type];

	return 0;
}

__thread int ieee1905_errval;

int *ieee1905_get_errval(void)
{
	return &ieee1905_errval;
}

#define ieee1905_set_error(v)	(ieee1905_errval = (v))

static const char *ieee1905_errlist[IEEE1905_ERROR_MAXNUM] = {
	[CMDU_STATUS_OK] = "Ok",
	[CMDU_STATUS_ERR_TLV_MALFORMED] = "TLV is malformed",
	[CMDU_STATUS_ERR_TLV_NUM_LESS] = "Number of TLVs less than required",
	[CMDU_STATUS_ERR_TLV_NUM_MORE] = "Number of TLVs more than required",
	[CMDU_STATUS_ERR_TLV_NO_EOM] = "EOM TLV is not present",
	[CMDU_STATUS_ERR_TLV_RESIDUE_DATA] = "Stray non-zero bytes after EOM",
	[CMDU_STATUS_ERR_TLV_LEN_INSUFFICIENT] = "TLV length insufficient",
	[CMDU_STATUS_ERR_TLV_LEN_OVERFLOW] = "TLV length points to beyond CMDU",
	[CMDU_STATUS_ERR_CMDU_MALFORMED] = "CMDU input structure malformed",
	[CMDU_STATUS_ERR_MISC] = "Misc cmdu error",
};

const char *ieee1905_strerror(int err)
{
	int last_err = err;

	if (last_err >= 0 && last_err <= IEEE1905_ERROR_LAST && ieee1905_errlist[last_err])
		return ieee1905_errlist[last_err];

	return "";
}

void cmdu_set_type(struct cmdu_buff *c, uint16_t type)
{
	if (c && c->cdata)
		buf_put_be16((uint8_t *)&c->cdata->hdr.type, type);
}

uint16_t cmdu_get_type(struct cmdu_buff *c)
{
	return (c && c->cdata) ?
		buf_get_be16((uint8_t *)&c->cdata->hdr.type) : 0xffff;
}

void cmdu_set_mid(struct cmdu_buff *c, uint16_t mid)
{
	if (c && c->cdata)
		buf_put_be16((uint8_t *)&c->cdata->hdr.mid, mid);
}

uint16_t cmdu_get_mid(struct cmdu_buff *c)
{
	return (c && c->cdata) ?
		buf_get_be16((uint8_t *)&c->cdata->hdr.mid) : 0xffff;
}

void cmdu_set_fid(struct cmdu_buff *c, uint8_t fid)
{
	if (c && c->cdata)
		c->cdata->hdr.fid = fid;
}

uint8_t cmdu_get_fid(struct cmdu_buff *c)
{
	return (c && c->cdata) ? c->cdata->hdr.fid : 0xff;
}

uint8_t *cmdu_get_origin(struct cmdu_buff *c)
{
	return c ? c->origin : NULL;
}

int is_cmdu_type_valid(uint16_t type)
{
	return type <= CMDU_TYPE_MAX;
}

int is_cmdu_tlv_required(uint16_t type)
{
	return !(type == CMDU_TYPE_TOPOLOGY_QUERY ||
		type == CMDU_TYPE_HIGHER_LAYER_QUERY ||
		type == CMDU_TYPE_GENERIC_PHY_QUERY);
}

int cmdu_should_relay(uint16_t type)
{
	return (type == CMDU_TYPE_TOPOLOGY_NOTIFICATION ||
		type == CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH ||
		type == CMDU_TYPE_AP_AUTOCONFIGURATION_RENEW ||
		type == CMDU_TYPE_PUSH_BUTTON_EVENT_NOTIFICATION ||
		type == CMDU_TYPE_PUSH_BUTTON_JOIN_NOTIFICATION);
}

/* for unicast request types only */
int is_cmdu_type_response(uint16_t type)
{
	return (type == CMDU_TYPE_TOPOLOGY_RESPONSE ||
		type == CMDU_TYPE_LINK_METRIC_RESPONSE ||
		/* type == CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE || */
		type == CMDU_TYPE_HIGHER_LAYER_RESPONSE ||
		type == CMDU_TYPE_INTERFACE_POWER_CHANGE_RESPONSE);
}

uint16_t cmdu_expect_response(uint16_t req_type)
{
	switch (req_type) {
		case CMDU_TYPE_TOPOLOGY_QUERY:
			return CMDU_TYPE_TOPOLOGY_RESPONSE;
		case CMDU_TYPE_LINK_METRIC_QUERY:
			return CMDU_TYPE_LINK_METRIC_RESPONSE;
		case CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH:
			return CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE;
		case CMDU_TYPE_AP_AUTOCONFIGURATION_WSC:
			return CMDU_TYPE_AP_AUTOCONFIGURATION_WSC;
		case CMDU_TYPE_AP_AUTOCONFIGURATION_RENEW:
			return CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE;
		case CMDU_TYPE_HIGHER_LAYER_QUERY:
			return CMDU_TYPE_HIGHER_LAYER_RESPONSE;
		case CMDU_TYPE_INTERFACE_POWER_CHANGE_REQUEST:
			return CMDU_TYPE_INTERFACE_POWER_CHANGE_RESPONSE;
		case CMDU_TYPE_GENERIC_PHY_QUERY:
			return CMDU_TYPE_GENERIC_PHY_RESPONSE;
		default:
			break;
	}

	return CMDU_TYPE_NONE;
}

struct cmdu_buff *cmdu_alloc(int size)
{
#define CMDU_RESERVE_HEADSPACE	32

	struct cmdu_buff *n;
	uint8_t *p;


	p = calloc(1, sizeof(*n) + size + CMDU_RESERVE_HEADSPACE);
	if (!p)
		return NULL;

	n = (struct cmdu_buff *)p;
	n->head = (uint8_t *)(n + 1) + CMDU_RESERVE_HEADSPACE;
	n->end = n->head + size;
	n->data = n->head;
	n->cdata = NULL;
	//n->cdata = (struct cmdu_linear *)(n->head + 18);
	//n->data = (uint8_t *)(n->cdata + 1);
	n->tail = n->data;
	n->num_frags = 0;
	n->datalen = 0;
	n->len = 0;
	n->head -= 18;
	INIT_LIST_HEAD(&n->fraglist);

	return (struct cmdu_buff *)p;
}

struct cmdu_buff *cmdu_alloc_frame(int size)
{
	struct cmdu_buff *f;

	f = cmdu_alloc(size + sizeof(struct cmdu_header));
	if (!f)
		return NULL;

	f->cdata = (struct cmdu_linear *)(f->head + 18);
	f->data = (uint8_t *)(f->cdata + 1);
	f->tail = f->data;

	return f;
}

struct cmdu_buff *cmdu_alloc_default(void)
{
#define ETH_FRAME_SZ	1500

	return cmdu_alloc(ETH_FRAME_SZ);
}

struct cmdu_buff *cmdu_alloc_nohdr(void)
{
	struct cmdu_buff *f;

	f = cmdu_alloc(ETH_FRAME_SZ);
	if (f) {
		f->cdata = NULL;
		f->data = (uint8_t *)(f->head + 18);
		f->tail = f->data;
	}

	return f;
}

struct cmdu_buff *cmdu_alloc_simple(uint16_t type, uint16_t *mid)
{
	struct cmdu_buff *f;


	f = cmdu_alloc_default();
	if (!f) {
		fprintf(stderr, "%s: -ENOMEM\n", __func__);
		return NULL;
	}

	f->cdata = (struct cmdu_linear *)(f->head + 18);
	f->data = (uint8_t *)(f->cdata + 1);
	f->tail = f->data;

	cmdu_set_type(f, type);

	if (*mid == 0)
		*mid = cmdu_get_next_mid();

	cmdu_set_mid(f, *mid);

	if (cmdu_should_relay(type))
		CMDU_SET_RELAY_MCAST(f->cdata);

	CMDU_SET_LAST_FRAGMENT(f->cdata);

	return f;
}

void cmdu_free(struct cmdu_buff *c)
{
	if (c) {
		list_flush(&c->fraglist, struct cmdu_frag, list);
		free(c);
	}
}

int cmdu_size(struct cmdu_buff *c)
{
	return c ? c->datalen + sizeof(struct cmdu_header) : 0;
}

#if 0
int cmdu_reserve(struct cmdu_buff *c, size_t s)
{
	if (!c)
		return -1;

	if (c->head - (uint8_t *)(c + 1) < s)
		return -1;

	c->head -= s;

	return 0;
}
#endif


int cmdu_copy_tlvs_linear(struct cmdu_buff *c, uint8_t *tlvs, uint32_t tlvslen)
{
	if (c->end - c->tail < tlvslen)
		return -1;

	memcpy(c->tail, tlvs, tlvslen);
	c->tail += tlvslen;
	c->datalen += tlvslen;

	return 0;
}

struct cmdu_buff *cmdu_alloc_custom(uint16_t type, uint16_t *mid, char *ifname,
				    uint8_t *origin, uint8_t *tlvs,
				    uint32_t tlvslen)
{
	struct cmdu_buff *f;
	int ret;


	f = cmdu_alloc_frame(tlvslen + 128);
	if (!f) {
		fprintf(stderr, "%s: -ENOMEM\n", __func__);
		return NULL;
	}

	cmdu_set_type(f, type);

	if (*mid == 0)
		*mid = cmdu_get_next_mid();

	cmdu_set_mid(f, *mid);

	ret = cmdu_copy_tlvs_linear(f, tlvs, tlvslen);
	if (ret) {
		fprintf(stderr, "%s: tlv-length > max cmdu size!\n", __func__);
		cmdu_free(f);
		return NULL;
	}

	memcpy(f->dev_macaddr, origin, 6);
	if (ifname)
		strncpy(f->dev_ifname, ifname, 15);

	return f;
}

struct cmdu_buff *cmdu_clone(struct cmdu_buff *frm)
{
	struct cmdu_buff *f;
	int len;


	if (!frm || !frm->cdata) {
		fprintf(stderr, "%s: cmdu for cloning is invalid!\n", __func__);
		return NULL;
	}

	len = cmdu_size(frm);
	f = cmdu_alloc(len);
	if (!f) {
		fprintf(stderr, "%s: -ENOMEM\n", __func__);
		return NULL;
	}

	f->cdata = (struct cmdu_linear *)(f->head + 18);
	f->data = (uint8_t *)(f->cdata + 1);
	f->datalen = frm->datalen;
	f->tail = f->data + f->datalen;
	memcpy(f->cdata, frm->cdata, len);

	memcpy(f->dev_macaddr, frm->dev_macaddr, 6);
	strncpy(f->dev_ifname, frm->dev_ifname, 15);
	memcpy(f->origin, frm->origin, 6);

	return f;
}

struct cmdu_buff *cmdu_realloc(struct cmdu_buff *c, size_t size)
{
	ptrdiff_t head_off, data_off, cdata_off;
	struct cmdu_buff *f;
	ptrdiff_t origsize;
	uint8_t *n;


	if (!c)
		return NULL;

	origsize = (c->end - (uint8_t *)c);
	if (size < origsize)
		return c;

	head_off = c->head - (uint8_t *)c;
	data_off = c->data - (uint8_t *)c;
	cdata_off = c->cdata ? (uint8_t *)c->cdata - (uint8_t *)c : 0;

	n = realloc((uint8_t *)c, sizeof(*f) + size + CMDU_RESERVE_HEADSPACE);
	if (!n)
		return NULL;

	f = (struct cmdu_buff *)n;
	f->head = n + head_off;
	f->data = n + data_off;
	f->tail = f->data + f->datalen;
	f->cdata = cdata_off ? (struct cmdu_linear *)(n + cdata_off) : NULL;
	f->end = f->head + size;
	INIT_LIST_HEAD(&f->fraglist);

	return f;
}

int cmdu_copy_tlvs(struct cmdu_buff *c, struct tlv *tv[], int tv_arrsize)
{
	uint16_t tlvslen = 0;
	int i;


	for (i = 0; i < tv_arrsize; i++)
		tlvslen += tv[i]->len;


	if (c->end - c->tail < tlvslen)
		return -1;


	for (i = 0; i < tv_arrsize; i++) {
		uint16_t tlen = tv[i]->len;

		*c->tail = tv[i]->type;
		buf_put_be16(c->tail + 1, tlen);
		memcpy(c->tail + 3, tv[i]->data, tlen);
		c->tail += tlen + sizeof(struct tlv);
		c->datalen += tlen + sizeof(struct tlv);
	}

	return 0;
}

int cmdu_put_tlv(struct cmdu_buff *c, struct tlv *t)
{
	uint16_t tlen;


	if (!c || !t)
		return -1;


	tlen = t->len;

	if (c->end - c->tail < tlen) {
		fprintf(stderr, "%s: %d: c->end = %p c->tail = %p\n",
			__func__, __LINE__, c->end, c->tail);

		return -1;
	}

	if ((uint8_t *)t != c->tail) {
		fprintf(stderr, "%s: tlv outside cmdu buffer; use cmdu_copy_tlv() instead\n",
			__func__);
		return -1;
	}


	buf_put_be16(c->tail + 1, tlen);
	c->tail += tlen + sizeof(*t);
	c->datalen += tlen + sizeof(*t);

	return 0;
}

int cmdu_put(struct cmdu_buff *c, uint8_t *bytes, int len)
{
	if (!c)
		return -1;

	if (!bytes)
		return 0;

	if (c->end - c->tail < len) {
		fprintf(stderr, "%s: %d: c->end = %p c->tail = %p\n",
			__func__, __LINE__, c->end, c->tail);

		return -1;
	}

	memcpy(c->tail, bytes, len);
	c->tail += len;
	c->datalen += len;

	return 0;
}

int cmdu_put_eom(struct cmdu_buff *c)
{
	uint8_t eom[3] = {0};

	return cmdu_put(c, eom, sizeof(eom));
}

int cmdu_pull_eom(struct cmdu_buff *c)
{
	if (!c || c->datalen < 3)
		return -1;

	c->tail -= 3;
	c->datalen -= 3;
	return 0;
}

struct tlv *cmdu_reserve_tlv(struct cmdu_buff *c, uint16_t tlv_datalen)
{
	uint16_t len = tlv_datalen + TLV_HLEN;

	if (!c)
		return NULL;

	if (c->end - c->tail < len) {
		fprintf(stderr, "%s: Failed to reserve %hu! Allocate new cmdu fragment\n",
			__func__, tlv_datalen);

		return NULL;
	}

	return (struct tlv *)c->tail;
}

int cmdu_parse_tlv_single(struct cmdu_buff *c, struct tlv *tv[],
			  struct tlv_policy *policy, int *num)
{
	struct tlv *t;
	int i = 0;
	int len;


	if (!c || !c->data || !c->datalen)
		return -1;

	if (*num == 0)
		return 0;

	memset(tv, 0, *num * sizeof(struct tlv *));
	len = c->datalen;

	cmdu_for_each_tlv(t, c->data, len) {
		if (policy->type != t->type)
			continue;

		if (policy->len && tlv_length(t) != policy->len)
			return -1;

		if (policy->minlen > 0 && tlv_length(t) < policy->minlen)
			continue;

		if (policy->maxlen > 0 && tlv_length(t) > policy->maxlen)
			continue;

		if (tlv_length(t) < tlv_minsize(t))
			continue;

		if (tv[0] && policy->present == TLV_PRESENT_ONE)
			return -1;

		if (i >= *num)
			break;

		tv[i++] = t;
	}

	/* malformed cmdu if data remaining */
	if (len) {
		int k = 0;

		while (k < len) {
			if (c->data[c->datalen - len + k++] != 0)
				return -1;
		}
	}

	/* exactly one tlv must be present */
	if (policy->present == TLV_PRESENT_ONE && !tv[0])
		return -1;

	*num = i;
	return 0;
}

int cmdu_parse_tlvs(struct cmdu_buff *c, struct tlv *tv[][TLV_MAXNUM],
		    struct tlv_policy *policy, int policy_len)
{
	int idx[policy_len];
	struct tlv *t;
	int len;
	int i;


	ieee1905_set_error(CMDU_STATUS_OK);
	if (!c || !c->data || !c->datalen) {
		ieee1905_set_error(CMDU_STATUS_ERR_CMDU_MALFORMED);
		return -1;
	}

	for (i = 0; i < policy_len; i++) {
		memset(tv[i], 0, TLV_MAXNUM * sizeof(struct tlv *));
		idx[i] = 0;
	}
	len = c->datalen;

	cmdu_for_each_tlv(t, c->data, len) {
		for (i = 0; i < policy_len; i++) {
			if (policy[i].type != t->type)
				continue;

			if (policy[i].len && tlv_length(t) != policy[i].len) {
				ieee1905_set_error(CMDU_STATUS_ERR_TLV_MALFORMED);
				return -1;
			}

			if (policy[i].minlen > 0 &&
			    tlv_length(t) < policy[i].minlen) {
				ieee1905_set_error(CMDU_STATUS_ERR_TLV_LEN_INSUFFICIENT);
				return -1;
			}

			if (policy[i].maxlen > 0 &&
			    tlv_length(t) > policy[i].maxlen) {
				ieee1905_set_error(CMDU_STATUS_ERR_TLV_LEN_OVERFLOW);
				return -1;
			}

			if (tlv_length(t) < tlv_minsize(t))
				continue;

			if (tv[i][0]) {
				if (policy[i].present == TLV_PRESENT_ONE ||
				    policy[i].present == TLV_PRESENT_OPTIONAL_ONE) {
					ieee1905_set_error(CMDU_STATUS_ERR_TLV_NUM_MORE);
					return -1;
				}
			}

			tv[i][idx[i]++] = t;
		}
	}

	/* malformed cmdu if data remaining; only allow zero padding */
	if (len) {
		int k = 0;

		while (k < len) {
			if (c->data[c->datalen - len + k++] != 0) {
				ieee1905_set_error(CMDU_STATUS_ERR_TLV_RESIDUE_DATA);
				return -1;
			}
		}
	}

	/* strictly check against tlv policies */
	for (i = 0; i < policy_len; i++) {
		if ((policy[i].present == TLV_PRESENT_ONE ||
		    policy[i].present == TLV_PRESENT_MORE) && !tv[i][0]) {
			ieee1905_set_error(CMDU_STATUS_ERR_TLV_NUM_LESS);
			return -1;
		}
	}

	return 0;
}

/* Extracts the first matching tlv from tlv-stream.
 * This function is destructive, i.e. it modifies the passed cmdu buffer.
 * Use cmdu_peek_tlv() for the non-destructive version.
 */
struct tlv *cmdu_extract_tlv(struct cmdu_buff *c, uint8_t tlv_type)
{
	struct tlv *t;
	int found = 0;
	int inlen;


	if (!c)
		return NULL;

	inlen = c->datalen;

	cmdu_for_each_tlv(t, c->data, inlen) {
		if (t->type == tlv_type) {
			found = 1;
			break;
		}
	}

	if (found) {
		uint16_t tlen = tlv_total_length(t);
		struct tlv *tmp;

		tmp = tlv_alloc(tlv_length(t));
		if (tmp)
			memcpy(tmp, t, tlen);

		inlen -= tlen;
		memmove(t, (uint8_t *)t + tlen, inlen);
		c->datalen -= tlen;
		return tmp;
	}

	return NULL;
}

struct tlv *cmdu_peek_tlv(struct cmdu_buff *c, uint8_t tlv_type)
{
	struct tlv *t;
	int len;

	if (!c)
		return NULL;

	len = c->datalen;

	cmdu_for_each_tlv(t, c->data, len) {
		if (t->type == tlv_type)
			return t;
	}

	return NULL;
}
