/*
 * decollector_tlv.c
 * File contains TLVs handling needed for WiFi Data Elements.
 *
 * Copyright (C) 2020 IOPSYS Software Solutions AB. All rights reserved.
 *
 * Author: saurabh.verma@iopsys.eu
 *	   anjan.chanda@iopsys.eu
 *
 */


#include <stdio.h>
#include <string.h>

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

#include "debug.h"
#include "wifi_dataelements.h"
#include "decollector.h"

size_t sizeof_cac_cap_opclass(const struct cac_cap_opclass *o)
{
	return sizeof(struct cac_cap_opclass) +
	       o->num_channel * sizeof(o->channel[0]);
}

size_t sizeof_cac_cap_cac(const struct cac_cap_cac *cap)
{
	int i;
	size_t sz = 0;

	for (i = 0; i < cap->num_opclass; ++i) {
		const struct cac_cap_opclass *o =
			(struct cac_cap_opclass *)(((uint8_t *)cap->opclass) + sz);

		sz += sizeof_cac_cap_opclass(o);
	}

	return sizeof(struct cac_cap_cac) + sz;
}

size_t sizeof_cac_cap_radio(const struct cac_cap_radio *cap_radio)
{
	int i;
	size_t sz = 0;

	for (i = 0; i < cap_radio->num_cac; ++i) {
		const struct cac_cap_cac *cap =
			(struct cac_cap_cac *)(((uint8_t *)cap_radio->cac) + sz);

		sz += sizeof_cac_cap_cac(cap);
	}

	return sizeof(struct cac_cap_radio) + sz;
}

size_t sizeof_channel_scan_capability_opclass(const struct channel_scan_capability_opclass *o)
{
	return sizeof(struct channel_scan_capability_opclass) +
	       o->num_channel * sizeof(o->channel[0]);
}

size_t sizeof_channel_scan_capability_radio(const struct channel_scan_capability_radio *cap_radio)
{
	int i;
	size_t sz = 0;

	for (i = 0; i < cap_radio->num_opclass; ++i) {
		const struct channel_scan_capability_opclass *o =
			(struct channel_scan_capability_opclass *)(((uint8_t *)cap_radio->opclass) + sz);

		sz += sizeof_channel_scan_capability_opclass(o);
	}

	return sizeof(struct channel_scan_capability_radio) + sz;
}

int decollector_gen_aladdr(struct decollector_private *priv, struct cmdu_buff *cmdu)
{
	int ret;
	struct tlv *t;
	struct tlv_aladdr *data;

	t = cmdu_reserve_tlv(cmdu, 30);
	if (!t)
		return -1;

	t->type = TLV_TYPE_AL_MAC_ADDRESS_TYPE;
	t->len = sizeof(*data);
	data = (struct tlv_aladdr *)t->data;
	memcpy(data->macaddr, priv->ieee1905_macaddr, 6);

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

int decollector_gen_searched_role(struct decollector_private *priv,
		struct cmdu_buff *cmdu, uint8_t role)
{
	int ret;
	struct tlv *t;
	struct tlv_searched_role *data;

	t = cmdu_reserve_tlv(cmdu, 30);
	if (!t)
		return -1;

	t->type = TLV_TYPE_SEARCHED_ROLE;
	t->len = sizeof(*data);
	data = (struct tlv_searched_role *)t->data;
	data->role = role;

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

int decollector_gen_autoconfig_band(struct decollector_private *priv,
		struct cmdu_buff *cmdu, uint8_t band)
{
	int ret;
	struct tlv *t;
	struct tlv_autoconfig_band *data;

	t = cmdu_reserve_tlv(cmdu, 30);
	if (!t)
		return -1;

	t->type = TLV_TYPE_AUTOCONFIG_FREQ_BAND;
	t->len = sizeof(*data);
	data = (struct tlv_autoconfig_band *)t->data;
	data->band = band;

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

int decollector_gen_supported_service(struct decollector_private *priv,
		struct cmdu_buff *cmdu, uint8_t service)
{
	int i, ret;
	struct tlv *t;

	t = cmdu_reserve_tlv(cmdu, 30);
	if (!t)
		return -1;

	t->type = MAP_TLV_SUPPORTED_SERVICE;
	t->data[0] = 1;
	for (i = 0; i < t->data[0]; i++)
		t->data[1 + i] = service;

	t->len = 1 + t->data[0];

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

int decollector_gen_searched_service(struct decollector_private *priv,
		struct cmdu_buff *cmdu, uint8_t service)
{
	int i, ret;
	struct tlv *t;

	t = cmdu_reserve_tlv(cmdu, 30);
	if (!t)
		return -1;

	t->type = MAP_TLV_SEARCHED_SERVICE;
	t->data[0] = 1;
	for (i = 0; i < t->data[0]; i++)
		t->data[1 + i] = service;

	t->len = 1 + t->data[0];

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

int decollector_gen_map_profile(struct decollector_private *priv,
		struct cmdu_buff *cmdu, uint8_t profile)
{
	int ret;
	struct tlv *t;
	struct tlv_map_profile *data;

	t = cmdu_reserve_tlv(cmdu, 20);
	if (!t)
		return -1;

	t->type = MAP_TLV_MULTIAP_PROFILE;
	t->len = sizeof(*data);
	data = (struct tlv_map_profile *)t->data;
	data->profile = profile;

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

int decollector_gen_ap_radio_identifier(struct decollector_private *priv,
		struct cmdu_buff *cmdu, uint8_t *radio_id)
{
	int ret;
	struct tlv *t;
	struct tlv_ap_radio_identifier *data;

	t = cmdu_reserve_tlv(cmdu, 20);
	if (!t)
		return -1;

	t->type = MAP_TLV_AP_RADIO_IDENTIFIER;
	t->len = sizeof(*data);
	data = (struct tlv_ap_radio_identifier *)t->data;
	memcpy(data->radio, radio_id, 6);

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

int decollector_gen_ap_metric_query(struct decollector_private *priv,
		struct cmdu_buff *cmdu, int num_bss, uint8_t *bsslist)
{
	int i, ret;
	struct tlv *t;
	struct tlv_ap_metric_query *data;
	int reserve_len = 0;

	reserve_len = sizeof(*data) + (6 * num_bss);
	t = cmdu_reserve_tlv(cmdu, reserve_len + 4);
	if (!t)
		return -1;

	t->type = MAP_TLV_AP_METRIC_QUERY;
	t->len = reserve_len;
	data = (struct tlv_ap_metric_query *) t->data;

	data->num_bss = num_bss;
	for (i = 0; i < data->num_bss; i++)
		memcpy(data->bss[i].bssid, &bsslist[i * 6], 6);

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

#if 0	//TODO: unused
static inline void put_duration_data(uint8_t *buf, uint32_t val)
{
	/* ignore val MSB data */
	buf[0] = (val >> 16) & 0x0ff;
	buf[1] = (val >> 8) & 0xff;
	buf[2] = val & 0xff;
}
#endif

#define AP_COLLECTION_INTERVAL	(10 * 1000)

int decollector_gen_client_info(struct decollector_private *priv,
               struct cmdu_buff *cmdu, uint8_t *sta,
               uint8_t *bssid)
{
	int ret;
	struct tlv *t;
	struct tlv_client_info *data;

	t = cmdu_reserve_tlv(cmdu, 256);
	if (!t)
		return -1;

	t->type = MAP_TLV_CLIENT_INFO;
	t->len = sizeof(*data);
	data = (struct tlv_client_info *)t->data;
	memcpy(data->bssid, bssid, 6);
	memcpy(data->macaddr, sta, 6);

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

int decollector_gen_link_metric_query(struct cmdu_buff *cmdu,
				      const uint8_t *neighbor_al_mac)
{
	int ret;
	struct tlv *t;
	struct tlv_linkmetric_query *data;

	t = cmdu_reserve_tlv(cmdu, 512);
	if (!t)
		return -1;

	t->type = TLV_TYPE_LINK_METRIC_QUERY;
	t->len = sizeof(*data);
	data = (struct tlv_linkmetric_query *) t->data;
	data->nbr_type = LINKMETRIC_QUERY_NEIGHBOR_SPECIFIC;
	memcpy(data->nbr_macaddr, neighbor_al_mac, 6);
	data->query_type = LINKMETRIC_QUERY_TYPE_BOTH;

	ret = cmdu_put_tlv(cmdu, t);
	if (ret) {
		dbg("%s: error: cmdu_put_tlv()\n", __func__);
		return -1;
	}

	return 0;
}

