Skip to content
Snippets Groups Projects
cmdu.c 18.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • /* 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
     *
     */
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    #include <stdio.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <stdbool.h>
    #include <string.h>
    
    #include <pthread.h>
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    #include "util.h"
    
    #include "1905_tlvs.h"
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    #include "cmdu.h"
    
    
    
    Jakob Olsson's avatar
    Jakob Olsson committed
    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
    }
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    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;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    }
    
    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;
    }
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    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),
    	};
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	if (t->type <= TLV_TYPE_L2_NEIGHBOR_DEVICE)
    		return sizeof_tlv[t->type];
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    __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] = {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	[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",
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	[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])
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		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;
    }
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    int is_cmdu_type_valid(uint16_t type)
    {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	return type <= CMDU_TYPE_MAX;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    }
    
    
    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)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    	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;
    }
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    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);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	//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);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    
    	return (struct cmdu_buff *)p;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    }
    
    
    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;
    }
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    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;
    }
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    void cmdu_free(struct cmdu_buff *c)
    {
    	if (c) {
    
    		list_flush(&c->fraglist, struct cmdu_frag, list);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		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;
    	}
    
    
    
    	if (*mid == 0)
    		*mid = cmdu_get_next_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)
    {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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;
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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);
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    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);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	c->tail += tlen + sizeof(*t);
    	c->datalen += tlen + sizeof(*t);
    
    	return 0;
    }
    
    int cmdu_put(struct cmdu_buff *c, uint8_t *bytes, int len)
    {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		return -1;
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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)
    
    		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)
    
    	/* malformed cmdu if data remaining */
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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;
    
    
    int cmdu_parse_tlvs(struct cmdu_buff *c, struct tlv *tv[][TLV_MAXNUM],
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		    struct tlv_policy *policy, int policy_len)
    {
    
    	int idx[policy_len];
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	struct tlv *t;
    	int len;
    	int i;
    
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	ieee1905_set_error(CMDU_STATUS_OK);
    
    	if (!c || !c->data || !c->datalen) {
    		ieee1905_set_error(CMDU_STATUS_ERR_CMDU_MALFORMED);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		return -1;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    
    	for (i = 0; i < policy_len; i++) {
    
    		memset(tv[i], 0, TLV_MAXNUM * sizeof(struct tlv *));
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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;
    
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			if (policy[i].len && tlv_length(t) != policy[i].len) {
    				ieee1905_set_error(CMDU_STATUS_ERR_TLV_MALFORMED);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			if (policy[i].minlen > 0 &&
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			    tlv_length(t) < policy[i].minlen) {
    				ieee1905_set_error(CMDU_STATUS_ERR_TLV_LEN_INSUFFICIENT);
    				return -1;
    			}
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    			if (policy[i].maxlen > 0 &&
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			    tlv_length(t) > policy[i].maxlen) {
    				ieee1905_set_error(CMDU_STATUS_ERR_TLV_LEN_OVERFLOW);
    				return -1;
    			}
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    
    			if (tlv_length(t) < tlv_minsize(t))
    				continue;
    
    
    				if (policy[i].present == TLV_PRESENT_ONE ||
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    				    policy[i].present == TLV_PRESENT_OPTIONAL_ONE) {
    					ieee1905_set_error(CMDU_STATUS_ERR_TLV_NUM_MORE);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    
    			tv[i][idx[i]++] = t;
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	/* malformed cmdu if data remaining; only allow zero padding */
    	if (len) {
    		int k = 0;
    
    		while (k < len) {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    			if (c->data[c->datalen - len + k++] != 0) {
    				ieee1905_set_error(CMDU_STATUS_ERR_TLV_RESIDUE_DATA);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    				return -1;
    
    
    	/* strictly check against tlv policies */
    	for (i = 0; i < policy_len; i++) {
    
    		if ((policy[i].present == TLV_PRESENT_ONE ||
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    		    policy[i].present == TLV_PRESENT_MORE) && !tv[i][0]) {
    			ieee1905_set_error(CMDU_STATUS_ERR_TLV_NUM_LESS);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	return 0;
    }
    
    
    /* Extracts the first matching tlv from tlv-stream.
     * This function is destructive, i.e. it modifies the passed cmdu buffer.
    
    Anjan Chanda's avatar
    Anjan Chanda committed
     * Use cmdu_peek_tlv() for the non-destructive version.
     */
    
    struct tlv *cmdu_extract_tlv(struct cmdu_buff *c, uint8_t tlv_type)
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    {
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    	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);
    
    Anjan Chanda's avatar
    Anjan Chanda committed
    
    		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;
    }