/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * i1905_cmdresp.c - implements CLI commands.
 *
 * Copyright (C) 2023-2024 IOPSYS Software Solutions AB. All rights reserved.
 * Copyright (C) 2025 Genexis AB.
 *
 * Author: anjan.chanda@iopsys.eu
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <net/if.h>
#include <arpa/inet.h>

#include <json-c/json.h>
#include <libubox/blobmsg.h>
#include <libubox/blobmsg_json.h>
#include <libubox/utils.h>

#include <easy/easy.h>
#include <wifiutils.h>

#include "debug.h"
#include "util.h"
#include "timer.h"
#include "config.h"
#include "cmdu.h"
#include "cmdu_ackq.h"
#include "cmdufrag.h"
#include "1905_tlvs.h"
#include "i1905_dm.h"
#include "i1905.h"
#include "neigh.h"
#include "i1905_extension.h"

#include "i1905_wifi.h"


static const char *media_type2str(enum i1905_mediatype m)
{
#define C2S(x)	case I1905_ ## x: return "IEEE " #x;

	switch (m) {
	C2S(802_3U_FAST_ETHERNET)
	C2S(802_3AB_GIGABIT_ETHERNET)
#ifdef ETH_MEDIA_EXTENSION
	C2S(802_3BZ_2_5G_ETHERNET)
	C2S(802_3BZ_5G_ETHERNET)
	C2S(802_3AE_10G_ETHERNET)
#endif
	C2S(802_11B_2_4_GHZ)
	C2S(802_11G_2_4_GHZ)
	C2S(802_11A_5_GHZ)
	C2S(802_11N_2_4_GHZ)
	C2S(802_11N_5_GHZ)
	C2S(802_11AC_5_GHZ)
	C2S(802_11AD_60_GHZ)
	C2S(802_11AF_GHZ)
#ifdef WIFI_EASYMESH
	C2S(802_11AX)
	C2S(802_11BE)
#endif
	C2S(1901_WAVELET)
	C2S(1901_FFT)
	C2S(MOCA_V1_1)
	case I1905_MEDIA_UNKNOWN:
		break;
	}

	return "Unknown";

#undef C2S
}

static const char *ipv4_type2str(enum ip4addr_type t)
{
#define C2S(x)	case IP4_TYPE_ ## x: return #x;

	switch (t) {
	C2S(DHCP)
	C2S(STATIC)
	C2S(AUTOIP)
	case IP4_TYPE_UNKNOWN:
		break;
	}

	return "Unknown";

#undef C2S
}

static const char *ipv6_type2str(enum ip6addr_type t)
{
#define C2S(x)	case IP6_TYPE_ ## x: return #x;

	switch (t) {
	C2S(LINKLOCAL)
	C2S(DHCP)
	C2S(STATIC)
	C2S(SLAAC)
	case IP6_TYPE_UNKNOWN:
		break;
	}

	return "Unknown";

#undef C2S
}

static const char *role_type2str(uint8_t r)
{
	if (r == IEEE80211_ROLE_AP)
		return "ap";
	else if (r == IEEE80211_ROLE_STA)
		return "sta";
	else if (r == IEEE80211_ROLE_P2P_CLIENT)
		return "p2p_client";
	else if (r == IEEE80211_ROLE_P2P_GO)
		return "p2p_go";
	else if (r == IEEE80211_ROLE_AD_PCP)
		return "pcp";

	return "Unknown";
}

int i1905_dump_neighbors(struct i1905_private *priv, void *out)
{
	struct i1905_selfdevice *self = &priv->dm.self;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct i1905_device *nbr;
	void *a;


	a = blobmsg_open_array(bb, "neighbors");
	list_for_each_entry(nbr, &self->topology.devlist, list) {
		char nbr_almacstr[18] = {0};
		void *aa;

		aa = blobmsg_open_table(bb, "");
		hwaddr_ntoa(nbr->aladdr, nbr_almacstr);
		blobmsg_add_string(bb, "ieee1905id", nbr_almacstr);
		blobmsg_add_u8(bb, "immediate", nbr->is_immediate_neighbor ? true : false);
		blobmsg_add_u32(bb, "ageout", timer_remaining_ms(&nbr->agetimer));
		blobmsg_add_u32(bb, "ageout_immediate", timer_remaining_ms(&nbr->immediate_nbr_agetimer));
		blobmsg_close_table(bb, aa);
	}
	blobmsg_close_array(bb, a);

	return 0;
}

int i1905_dump_links(struct i1905_private *priv, void *out)
{
	struct i1905_selfdevice *self = &priv->dm.self;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct i1905_neighbor_interface *link = NULL;
	struct i1905_interface *iface;
	void *a;


	a = blobmsg_open_array(bb, "interfaces");
	list_for_each_entry(iface, &self->iflist, list) {
		char ifstr[18] = {0};
		char alstr[18] = {0};
		void *aa;
		void *t;

		t = blobmsg_open_table(bb, "");
		hwaddr_ntoa(iface->aladdr, alstr);
		hwaddr_ntoa(iface->macaddr, ifstr);
		blobmsg_add_string(bb, "ifname", iface->ifname);
		blobmsg_add_u8(bb, "upstream", iface->upstream ? true : false);
		blobmsg_add_string(bb, "ieee1905id", alstr);
		blobmsg_add_string(bb, "macaddress", ifstr);

		aa = blobmsg_open_array(bb, "links");
		list_for_each_entry(link, &iface->nbriflist, list) {
			void *tt;

			tt = blobmsg_open_table(bb, "");
			blobmsg_add_macaddr(bb, "nbr_ieee1905id", link->aladdr);
			blobmsg_add_macaddr(bb, "nbr_macaddress", link->r_macaddr);
			blobmsg_add_u8(bb, "direct", link->direct ? true : false);
			blobmsg_add_u32(bb, "ageout", timer_remaining_ms(&link->staletimer));
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aa);
		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, a);

	return 0;
}

int i1905_dump_non1905neighbors(struct i1905_private *priv, void *out)
{
	struct i1905_selfdevice *self = &priv->dm.self;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct i1905_interface *lif = NULL;
	void *a, *aa, *aaa;


	a = blobmsg_open_array(bb, "non1905_neighbors");
	list_for_each_entry(lif, &self->iflist, list) {
		struct i1905_non1905_neighbor *nnbr;
		char xnbr[18] = {0};

		if (lif->invalid)
			continue;

		aa = blobmsg_open_table(bb, "");
		blobmsg_add_string(bb, "ifname", lif->ifname);
		blobmsg_add_u8(bb, "upstream", lif->upstream ? true : false);

		aaa = blobmsg_open_array(bb, lif->ifname);
		list_for_each_entry(nnbr, &lif->non1905_nbrlist, list) {
			hwaddr_ntoa(nnbr->macaddr, xnbr);
			blobmsg_add_string(bb, "", xnbr);
		}
		blobmsg_close_array(bb, aaa);
		blobmsg_close_table(bb, aa);
	}
	blobmsg_close_array(bb, a);

	return 0;
}

int i1905_dump_info(struct i1905_private *priv, void *out)
{
	struct i1905_selfdevice *self = &priv->dm.self;
	size_t lsz = 16 * self->num_interface + self->num_interface;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct i1905_config *cfg = &priv->cfg;
	struct i1905_selfdevice_interface *ifl;
	struct i1905_master_interface *mif;
	int num_immediate_neighbors = 0;
	struct i1905_interface *iface;
	struct i1905_vendor_info *ven;
	struct i1905_bridge_tuple *br;
	struct i1905_interface *rif;
	int num_local_interface = 0;
	void *a, *aa, *aaa, *aaaa;
	struct i1905_device *nbr;
	char almacstr[18] = {0};
	char local_ifnames[lsz];


	blobmsg_add_string(bb, "version", self->version == I1905_VERSION_DOT_1 ?
			   "1905.1" : "1905.1a");

	hwaddr_ntoa(self->aladdr, almacstr);
	blobmsg_add_string(bb, "ieee1905id", almacstr);
	blobmsg_add_string(bb, "status", "enabled");

	blobmsg_add_string(bb, "name", self->name);
	blobmsg_add_string(bb, "manufacturer", self->manufacturer);
	blobmsg_add_string(bb, "model", self->model);
	blobmsg_add_string(bb, "url", self->url ? self->url : "");


	if (cfg->registrar) {
		blobmsg_add_u8(bb, "registrar", true);
		a = blobmsg_open_array(bb, "registrar_band");
		if (!!(cfg->registrar & I1905_REGISTRAR_5G))
			blobmsg_add_string(bb, "", "802.11 5 GHz");

		if (!!(cfg->registrar & I1905_REGISTRAR_2G))
			blobmsg_add_string(bb, "", "802.11 2.4 GHz");

		if (!!(cfg->registrar & I1905_REGISTRAR_60G))
			blobmsg_add_string(bb, "", "802.11 60 GHz");

		blobmsg_close_array(bb, a);
	} else {
		blobmsg_add_u8(bb, "registrar", false);
	}

	memset(local_ifnames, 0, lsz);
	list_for_each_entry(iface, &self->iflist, list) {
		if (iface->invalid || iface->lo)
			continue;

		snprintf(local_ifnames + strlen(local_ifnames), lsz - strlen(local_ifnames), ",%s", iface->ifname);
		num_local_interface++;
	}

	list_for_each_entry(ifl, &self->local_iflist, list) {
		if (ifl->exclude || ifl->is_bridge || strstr_exact(local_ifnames, ifl->ifname))
			continue;

		num_local_interface++;
	}

	blobmsg_add_u32(bb, "num_interfaces", num_local_interface);
	aa = blobmsg_open_array(bb, "interface");
	list_for_each_entry(iface, &self->iflist, list) {
		char ifmacstr[18] = {0};
		struct i1905_vendor_info *v;
		struct i1905_neighbor_interface *link;
		struct i1905_non1905_neighbor *nnbr;
		char ouistring[7] = {0};
		char parent[16] = {0};
		int iface_num_ipv4 = 0;
		int iface_num_ipv6 = 0;
		int j = 0;


		if (iface->invalid || iface->lo)
			continue;

		aaa = blobmsg_open_table(bb, "");
		blobmsg_add_string(bb, "ifname", iface->ifname);
		if (!platform_wifi_get_4addr_parent(iface->ifname, parent))
			blobmsg_add_string(bb, "parent_ifname", parent);
		else
			blobmsg_add_string(bb, "parent_ifname", "");

		hwaddr_ntoa(iface->macaddr, ifmacstr);
		blobmsg_add_string(bb, "macaddress", ifmacstr);
		/* blobmsg_add_u32(bb, "ifindex", iface->ifindex); */
		blobmsg_add_string(bb, "status", !!(iface->ifstatus & IFF_UP) ?
				   "up" : "down");

		blobmsg_add_u32(bb, "num_mediainfo", iface->num_mediainfo);
		if (iface->mediatype == IF_MEDIA_WIFI) {
			if (iface->num_mediainfo> 0) {
				a = blobmsg_open_array(bb, "mediainfo");
				for (int i = 0; i < iface->num_mediainfo; i++) {
					struct i1905_wifi_mediainfo *w;
					void *t;

					w = (struct i1905_wifi_mediainfo *)iface->mediainfo + i;

					t = blobmsg_open_table(bb, "");
					blobmsg_add_string(bb, "media", media_type2str(w->media));
					blobmsg_add_u32(bb, "band", w->band);
					blobmsg_add_macaddr(bb, "bssid", w->info.bssid);
					blobmsg_add_string(bb, "role", role_type2str(w->info.role));
					blobmsg_add_u32(bb, "bandwidth", wifi_bw_enum2MHz(w->info.ap_bandwidth));
					blobmsg_add_u32(bb, "freq_seg0_idx", w->info.ap_channel_seg0_idx);
					blobmsg_add_u32(bb, "freq_seg1_idx", w->info.ap_channel_seg1_idx);
					blobmsg_close_table(bb, t);
				}
				blobmsg_close_array(bb, a);
			}
		} else {
			blobmsg_add_string(bb, "media", media_type2str(iface->media));
			blobmsg_add_u32(bb, "band", 0);
		}

		sprintf(ouistring, "%02x%02x%02x", iface->genphy.oui[0],
			iface->genphy.oui[1], iface->genphy.oui[2]);
		blobmsg_add_string(bb, "genphy_oui", ouistring);
		if (iface->genphy.variant) {
			char varstr[3] = {0};

			sprintf(varstr, "%02x", iface->genphy.variant);
			blobmsg_add_string(bb, "genphy_variant", varstr);
		} else {
			blobmsg_add_string(bb, "genphy_variant", "");
		}

		blobmsg_add_string(bb, "genphy_url", iface->genphy.url ?
				   iface->genphy.url : "");

		blobmsg_add_string(bb, "power", !!(iface->ifstatus & IFF_UP) ?
				   "on" : "off");	//FIXME

		blobmsg_add_u32(bb, "num_vendor_properties", iface->num_vendor);
		a = blobmsg_open_array(bb, "properties");
		list_for_each_entry(v, &iface->vendorlist, list) {
			void *t;

			t = blobmsg_open_table(bb, "");
			blobmsg_add_string(bb, "oui", "");
			blobmsg_add_string(bb, "data", "");
			blobmsg_close_table(bb, t);
		}
		blobmsg_close_array(bb, a);

		for (j = 0; j < iface->num_ipaddrs; j++) {
			if (iface->ipaddrs[j].family == AF_INET)
				iface_num_ipv4++;
			else if (iface->ipaddrs[j].family == AF_INET6)
				iface_num_ipv6++;
		}

		blobmsg_add_u32(bb, "num_ipv4", iface_num_ipv4);
		a = blobmsg_open_array(bb, "ipv4_address");
		for (j = 0; j < iface->num_ipaddrs; j++) {
			char ipbuf[256] = {0};
			void *t;

			if (iface->ipaddrs[j].family != AF_INET)
				continue;

			t = blobmsg_open_table(bb, "");
			inet_ntop(AF_INET, &iface->ipaddrs[j].addr.ip4, ipbuf, sizeof(ipbuf));
			blobmsg_add_string(bb, "ip", ipbuf);
			blobmsg_add_string(bb, "type", ipv4_type2str(IPV4_TYPE_UNKNOWN));
			blobmsg_add_string(bb, "dhcpserver", "0.0.0.0");
			blobmsg_close_table(bb, t);
		}
		blobmsg_close_array(bb, a);

		blobmsg_add_u32(bb, "num_ipv6", iface_num_ipv6);
		a = blobmsg_open_array(bb, "ipv6_address");
		for (j = 0; j < iface->num_ipaddrs; j++) {
			char ipbuf[256] = {0};
			void *t;

			if (iface->ipaddrs[j].family != AF_INET6)
				continue;

			t = blobmsg_open_table(bb, "");
			inet_ntop(AF_INET6, &iface->ipaddrs[j].addr.ip6, ipbuf, sizeof(ipbuf));
			blobmsg_add_string(bb, "ip", ipbuf);
			blobmsg_add_string(bb, "type", ipv6_type2str(IPV6_TYPE_UNKNOWN));
			blobmsg_add_string(bb, "dhcpserver", "::");
			blobmsg_close_table(bb, t);
		}
		blobmsg_close_array(bb, a);

		blobmsg_add_u32(bb, "num_neighbor_non1905", iface->num_neighbor_non1905);
		a = blobmsg_open_array(bb, "non1905_neighbors");
		list_for_each_entry(nnbr, &iface->non1905_nbrlist, list) {
			char xnbr[18] = {0};

			if (iface->upstream)
				continue;

			hwaddr_ntoa(nnbr->macaddr, xnbr);
			blobmsg_add_string(bb, "", xnbr);
		}
		blobmsg_close_array(bb, a);


		blobmsg_add_u32(bb, "num_links", iface->num_links);
		a = blobmsg_open_array(bb, "links");
		list_for_each_entry(link, &iface->nbriflist, list) {
			struct i1905_device *rdev;
			void *t, *tt;

			t = blobmsg_open_table(bb, "");
			blobmsg_add_macaddr(bb, "macaddress", link->r_macaddr);
			blobmsg_add_macaddr(bb, "ieee1905id", link->aladdr);
			blobmsg_add_u8(bb, "direct", link->direct ? true : false);
			blobmsg_add_string(bb, "media", media_type2str(link->media));

			rdev = i1905_dm_neighbor_lookup(iface, link->aladdr);
			if (rdev && rdev->version == I1905_VERSION_DOT_1A) {
				blobmsg_add_string(bb, "genphy_oui", "");
				blobmsg_add_string(bb, "genphy_variant", "");
				blobmsg_add_string(bb, "genphy_url", "");
			}

			tt = blobmsg_open_table(bb, "metric");
			blobmsg_add_u8(bb, "has_bridge",
				       link->metric.br_present ? true : false);

			blobmsg_add_u32(bb, "tx_errors", link->metric.tx_errors);
			blobmsg_add_u32(bb, "rx_errors", link->metric.rx_errors);
			blobmsg_add_u32(bb, "tx_packets", link->metric.tx_packets);
			blobmsg_add_u32(bb, "rx_packets", link->metric.rx_packets);

			blobmsg_add_u32(bb, "max_macrate", link->metric.max_rate);
			blobmsg_add_u32(bb, "max_phyrate", link->metric.max_phyrate);
			blobmsg_add_u32(bb, "rssi", (int)link->metric.rssi);
			blobmsg_close_table(bb, tt);

			blobmsg_close_table(bb, t);
		}
		blobmsg_close_array(bb, a);



		blobmsg_close_table(bb, aaa);
	}


	/* Also dump local 'unmanaged' interfaces. This is mostly to give
	 * a consistent view of the local interfaces to neighbor devices.
	 */
	list_for_each_entry(ifl, &self->local_iflist, list) {
		char ifmacstr[18] = {0};
		char parent[16] = {0};

		if (ifl->exclude || ifl->is_bridge ||
		    hwaddr_is_zero(ifl->macaddr) ||
		    strstr_exact(local_ifnames, ifl->ifname))
			continue;

		aaa = blobmsg_open_table(bb, "");
		blobmsg_add_string(bb, "ifname", ifl->ifname);
		if (!platform_wifi_get_4addr_parent(ifl->ifname, parent))
			blobmsg_add_string(bb, "parent_ifname", parent);
		else
			blobmsg_add_string(bb, "parent_ifname", "");

		hwaddr_ntoa(ifl->macaddr, ifmacstr);
		blobmsg_add_string(bb, "macaddress", ifmacstr);
		blobmsg_add_string(bb, "status", !!(ifl->ifstatus & IFF_UP) ? "up" : "down");
		if (IS_MEDIA_WIFI(ifl->mediatype)) {
			blobmsg_add_u32(bb, "num_mediainfo", 1);
			a = blobmsg_open_array(bb, "mediainfo");
				struct i1905_wifi_mediainfo *w;
				void *t;

				w = (struct i1905_wifi_mediainfo *)ifl->mediainfo;

				t = blobmsg_open_table(bb, "");
				blobmsg_add_string(bb, "media", media_type2str(w->media));
				blobmsg_add_u32(bb, "band", w->band);
				blobmsg_add_macaddr(bb, "bssid", w->info.bssid);
				blobmsg_add_string(bb, "role", role_type2str(w->info.role));
				blobmsg_add_u32(bb, "bandwidth", wifi_bw_enum2MHz(w->info.ap_bandwidth));
				blobmsg_add_u32(bb, "freq_seg0_idx", w->info.ap_channel_seg0_idx);
				blobmsg_add_u32(bb, "freq_seg1_idx", w->info.ap_channel_seg1_idx);
				blobmsg_close_table(bb, t);
			blobmsg_close_array(bb, a);
		} else {
			blobmsg_add_string(bb, "media", media_type2str(ifl->mediatype));
			blobmsg_add_u32(bb, "band", 0);
		}

		blobmsg_add_string(bb, "genphy_oui", "000000");
		blobmsg_add_string(bb, "genphy_variant", "");
		blobmsg_add_string(bb, "genphy_url", "");
		blobmsg_add_string(bb, "power", "on");
		blobmsg_add_u32(bb, "num_vendor_properties", 0);
		a = blobmsg_open_array(bb, "properties");
		blobmsg_close_array(bb, a);

		blobmsg_add_u32(bb, "num_ipv4", 0);
		a = blobmsg_open_array(bb, "ipv4_address");
		blobmsg_close_array(bb, a);

		blobmsg_add_u32(bb, "num_ipv6", 0);
		a = blobmsg_open_array(bb, "ipv6_address");
		blobmsg_close_array(bb, a);

		blobmsg_add_u32(bb, "num_neighbor_non1905", 0);
		a = blobmsg_open_array(bb, "non1905_neighbors");
		blobmsg_close_array(bb, a);

		blobmsg_add_u32(bb, "num_links", 0);
		a = blobmsg_open_array(bb, "links");
		blobmsg_close_array(bb, a);

		blobmsg_close_table(bb, aaa);
	}
	blobmsg_close_array(bb, aa);

	/* show brtuples for selfdevice */
	a = blobmsg_open_array(bb, "bridge_tuples");
	list_for_each_entry(mif, &self->miflist, list) {
		char brifs[32][16] = {0};
		int n = 32;
		int ret;
		void *tt;
		int i;

		ret = br_get_iflist(mif->ifname, &n, brifs);
		if (ret)
			break;

		if (n == 0)
			continue;

		tt = blobmsg_open_table(bb, "");
		aaaa = blobmsg_open_array(bb, "tuple");
		for (i = 0; i < n; i++) {
			char macstr[18] = {0};
			uint8_t macaddr[6] = {0};

			if_gethwaddr(brifs[i], macaddr);
			hwaddr_ntoa(macaddr, macstr);
			blobmsg_add_string(bb, "", macstr);
		}
		blobmsg_close_array(bb, aaaa);
		blobmsg_close_table(bb, tt);
	}
	blobmsg_close_array(bb, a);

	a = blobmsg_open_table(bb, "topology");
	blobmsg_add_u8(bb, "enabled", self->topology.enable ? true : false);
	blobmsg_add_string(bb, "status", "available");
	blobmsg_add_u32(bb, "max_changelog", 100);
	blobmsg_add_u32(bb, "num_changelog", 0);
	blobmsg_add_string(bb, "last_change", "");
	//TODO: changelog table

	/* skip showing non-immediate 1905 neighbors from topology */
	list_for_each_entry(nbr, &self->topology.devlist, list) {
		if (nbr->is_immediate_neighbor)
			num_immediate_neighbors++;
	}

	blobmsg_add_u32(bb, "num_device", num_immediate_neighbors);
	aa = blobmsg_open_array(bb, "device");
	list_for_each_entry(nbr, &self->topology.devlist, list) {
		struct i1905_net_non1905_neighbor *non;
		char nbr_almacstr[18] = {0};
		struct i1905_ipv4 *ipv4 = NULL;
		struct i1905_ipv6 *ipv6 = NULL;
		void *t, *tt;


		if (!nbr->is_immediate_neighbor)
			continue;

		t = blobmsg_open_table(bb, "");
		hwaddr_ntoa(nbr->aladdr, nbr_almacstr);
		blobmsg_add_string(bb, "ieee1905id", nbr_almacstr);
		blobmsg_add_string(bb, "version", nbr->version == I1905_VERSION_DOT_1 ?
				   "1905.1" : "1905.1a");


		blobmsg_add_string(bb, "name", nbr->name);
		blobmsg_add_string(bb, "manufacturer", nbr->manufacturer);
		blobmsg_add_string(bb, "model", nbr->model);
		blobmsg_add_string(bb, "url", nbr->url ? nbr->url : "");

		blobmsg_add_u32(bb, "num_vendor_properties", nbr->num_vendor);
		blobmsg_add_u32(bb, "num_ipv4", nbr->num_ipv4);
		blobmsg_add_u32(bb, "num_ipv6", nbr->num_ipv6);
		blobmsg_add_u32(bb, "num_interface", nbr->num_interface);
		blobmsg_add_u32(bb, "num_neighbor_non1905", nbr->num_neighbor_non1905);
		blobmsg_add_u32(bb, "num_neighbor_1905", nbr->num_neighbor_1905);
		blobmsg_add_u32(bb, "num_neighbor_l2", nbr->num_neighbor_l2);
		blobmsg_add_u32(bb, "num_bridge_tuple", nbr->num_brtuple);

		aaa = blobmsg_open_array(bb, "ipv4_address");
		list_for_each_entry(ipv4, &nbr->ipv4list, list) {
			char ipv4_ifmacstr[18] = {0};
			char ipbuf[256] = {0};
			char dhcpsbuf[256] = {0};

			tt = blobmsg_open_table(bb, "");
			hwaddr_ntoa(ipv4->macaddr, ipv4_ifmacstr);
			inet_ntop(AF_INET, &ipv4->addr, ipbuf, sizeof(ipbuf));
			inet_ntop(AF_INET, &ipv4->dhcpserver, dhcpsbuf, sizeof(dhcpsbuf));
			blobmsg_add_string(bb, "macaddress", ipv4_ifmacstr);
			blobmsg_add_string(bb, "ip", ipbuf);
			blobmsg_add_string(bb, "type", ipv4_type2str(ipv4->type));
			blobmsg_add_string(bb, "dhcpserver", dhcpsbuf);
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aaa);

		aaa = blobmsg_open_array(bb, "ipv6_address");
		list_for_each_entry(ipv6, &nbr->ipv6list, list) {
			char ipv6_ifmacstr[18] = {0};
			char ipbuf[256] = {0};
			char dhcpsbuf[256] = {0};

			tt = blobmsg_open_table(bb, "");
			hwaddr_ntoa(ipv6->macaddr, ipv6_ifmacstr);
			inet_ntop(AF_INET6, &ipv6->addr, ipbuf, sizeof(ipbuf));
			inet_ntop(AF_INET6, &ipv6->origin, dhcpsbuf, sizeof(dhcpsbuf));
			blobmsg_add_string(bb, "macaddress", ipv6_ifmacstr);
			blobmsg_add_string(bb, "ip", ipbuf);
			blobmsg_add_string(bb, "type", ipv6_type2str(ipv6->type));
			blobmsg_add_string(bb, "dhcpserver", dhcpsbuf);
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aaa);

		aaa = blobmsg_open_array(bb, "vendor_properties");
		list_for_each_entry(ven, &nbr->vendorlist, list) {
			tt = blobmsg_open_table(bb, "");
			blobmsg_add_string(bb, "oui", "");
			blobmsg_add_string(bb, "data", "");
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aaa);


		aaa = blobmsg_open_array(bb, "interface");
		list_for_each_entry(rif, &nbr->iflist, list) {
			char rifmacstr[18] = {0};

			tt = blobmsg_open_table(bb, "");
			hwaddr_ntoa(rif->macaddr, rifmacstr);
			blobmsg_add_string(bb, "macaddress", rifmacstr);
			dbg("%s: rif = %s\n", __func__, rifmacstr);
			blobmsg_add_string(bb, "media", media_type2str(rif->media));
			blobmsg_add_string(bb, "power", "on");	//TODO
			if (nbr->version == I1905_VERSION_DOT_1A) {
				blobmsg_add_string(bb, "genphy_oui", "");
				blobmsg_add_string(bb, "genphy_variant", "");
				blobmsg_add_string(bb, "genphy_url", "");
			}

			if (IS_MEDIA_WIFI(rif->media)) {
				struct ieee80211_info *winfo;

				winfo = (struct ieee80211_info *)rif->mediainfo;
				if (winfo) {
					char rif_bssidstr[18] = {0};

					hwaddr_ntoa(winfo->bssid, rif_bssidstr);
					blobmsg_add_string(bb, "bssid", rif_bssidstr);
					blobmsg_add_string(bb, "role", role_type2str(winfo->role));
					blobmsg_add_u32(bb, "bandwidth", wifi_bw_enum2MHz(winfo->ap_bandwidth));
					blobmsg_add_u32(bb, "freq_seg0_idx", winfo->ap_channel_seg0_idx);
					blobmsg_add_u32(bb, "freq_seg1_idx", winfo->ap_channel_seg1_idx);
				} else {
					dbg("%s: WiFi rif missing mediainfo!\n", __func__);
				}
			}
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aaa);


		aaa = blobmsg_open_array(bb, "non1905_neighbors");
		list_for_each_entry(rif, &nbr->iflist, list) {
			char rif_macstr[18] = {0};

			aaaa = blobmsg_open_table(bb, "");
			hwaddr_ntoa(rif->macaddr, rif_macstr);
			blobmsg_add_string(bb, "interface_macaddress", rif_macstr);

			tt = blobmsg_open_array(bb, "neighbors");
			list_for_each_entry(non, &rif->non1905_nbrlist, list) {
				char non_macstr[18] = {0};

				hwaddr_ntoa(non->macaddr, non_macstr);
				blobmsg_add_string(bb, "macaddress", non_macstr);
			}
			blobmsg_close_array(bb, tt);
			blobmsg_close_table(bb, aaaa);
		}
		blobmsg_close_array(bb, aaa);

		/*
		aaa = blobmsg_open_array(bb, "l2_neighbors");
		list_for_each_entry(l2, &nbr->l2_nbrlist, list) {

			tt = blobmsg_open_table(bb, "");
			blobmsg_add_string(bb, "macaddress", "TODO");
			blobmsg_add_string(bb, "behind_macs", "TODO");
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aaa);
		*/


		aaa = blobmsg_open_array(bb, "ieee1905_neighbors");
		list_for_each_entry(rif, &nbr->iflist, list) {
			struct i1905_neighbor_interface *nnlink = NULL;

			list_for_each_entry(nnlink, &rif->nbriflist, list) {
				void *ttt;


				tt = blobmsg_open_table(bb, "");
				blobmsg_add_macaddr(bb, "macaddress", rif->macaddr);
				blobmsg_add_macaddr(bb, "neighbor_device_id", nnlink->aladdr);
				blobmsg_add_u32(bb, "num_metrics", rif->num_links);

				aaaa = blobmsg_open_array(bb, "metric");
				ttt = blobmsg_open_table(bb, "");
				blobmsg_add_macaddr(bb, "neighbor_macaddress", nnlink->r_macaddr);
				blobmsg_add_u8(bb, "has_bridge", nnlink->metric.br_present ? true : false);
				blobmsg_add_u32(bb, "tx_errors", nnlink->metric.tx_errors);
				blobmsg_add_u32(bb, "rx_errors", nnlink->metric.rx_errors);
				blobmsg_add_u32(bb, "tx_packets", nnlink->metric.tx_packets);
				blobmsg_add_u32(bb, "rx_packets", nnlink->metric.rx_packets);
				blobmsg_add_u32(bb, "max_macrate", nnlink->metric.max_rate);
				blobmsg_add_u32(bb, "max_phyrate", nnlink->metric.max_phyrate);
				blobmsg_add_u32(bb, "link_available", nnlink->metric.available);
				blobmsg_add_u32(bb, "rssi", (int)nnlink->metric.rssi);
				blobmsg_close_table(bb, ttt);
				blobmsg_close_array(bb, aaaa);
				blobmsg_close_table(bb, tt);
			}
		}
		blobmsg_close_array(bb, aaa);

		aaa = blobmsg_open_array(bb, "bridge_tuples");
		list_for_each_entry(br, &nbr->brlist, list) {
			int i;

			tt = blobmsg_open_table(bb, "");
			aaaa = blobmsg_open_array(bb, "tuple");
			for (i = 0; i < br->num_macs; i++) {
				char macstr[18] = {0};

				hwaddr_ntoa(&br->macaddrs[i*6], macstr);
				blobmsg_add_string(bb, "", macstr);
			}
			blobmsg_close_array(bb, aaaa);
			blobmsg_close_table(bb, tt);
		}
		blobmsg_close_array(bb, aaa);

		blobmsg_close_table(bb, t);
	}
	blobmsg_close_array(bb, aa);
	blobmsg_close_table(bb, a);


	a = blobmsg_open_table(bb, "network_registrars");
	{
		char macstr[18] = {0};

		hwaddr_ntoa(self->netregistrar[IEEE80211_FREQUENCY_BAND_2_4_GHZ], macstr);
		blobmsg_add_string(bb, "registrar_2", macstr);

		hwaddr_ntoa(self->netregistrar[IEEE80211_FREQUENCY_BAND_5_GHZ], macstr);
		blobmsg_add_string(bb, "registrar_5", macstr);

		hwaddr_ntoa(self->netregistrar[IEEE80211_FREQUENCY_BAND_60_GHZ], macstr);
		blobmsg_add_string(bb, "registrar_60", macstr);
#ifdef WIFI_EASYMESH
		hwaddr_ntoa(self->netregistrar[IEEE80211_FREQUENCY_BAND_6_GHZ], macstr);
		blobmsg_add_string(bb, "registrar_6", macstr);
#endif
	}
	blobmsg_close_table(bb, a);

	return 0;
}

/* cmdu fragmentation policy */
enum {
	CMDU_FRAG_DST,		/* macaddress of the receiving node */
	CMDU_FRAG_SCHEME,	/* scheme = 1 to fragment at octet-boundary */
	NUM_CMDU_FRAG_POLICY,
};

static const struct blobmsg_policy cmdu_frag_policy[NUM_CMDU_FRAG_POLICY] = {
	[CMDU_FRAG_DST] = { .name = "dst", .type = BLOBMSG_TYPE_STRING },
	[CMDU_FRAG_SCHEME] = { .name = "mode", .type = BLOBMSG_TYPE_INT32 },
};

int i1905_set_cmdu_fragment_scheme(struct i1905_private *priv, void *args, void *out)
{
	struct blob_attr *msg = (struct blob_attr *)args;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_CMDU_FRAG_POLICY];
	uint8_t dst[6] = {0};
	uint8_t scheme = 0;
	int ret;


	blobmsg_parse(cmdu_frag_policy, NUM_CMDU_FRAG_POLICY, tb, blob_data(msg),
		      blob_len(msg));

	/* destination macaddress is mandatory */
	if (!tb[CMDU_FRAG_DST])
		return -EINVAL;

	if (tb[CMDU_FRAG_DST]) {
		char dst_macstr[18] = {0};

		strncpy(dst_macstr, blobmsg_data(tb[CMDU_FRAG_DST]),
			sizeof(dst_macstr)-1);

		if (hwaddr_aton(dst_macstr, dst) == NULL)
			return -EINVAL;

		if (hwaddr_is_zero(dst) || hwaddr_is_mcast(dst) || hwaddr_is_bcast(dst))
			return -EINVAL;
	}

	if (tb[CMDU_FRAG_SCHEME])
		scheme = (uint8_t)blobmsg_get_u32(tb[CMDU_FRAG_SCHEME]);

	ret = i1905_set_fragment_scheme(priv, dst, scheme);

	blobmsg_add_string(bb, "status", ret == 0 ? "ok" : "fail");
	return 0;
}

/* cmdu tx policy */
enum {
	CMDU_TX_DST,		/* dest macaddress */
	CMDU_TX_SRC,		/* optional; interface's macaddress */
	CMDU_TX_TYPE,		/* can be in hex '0xaaaa' or int */
	CMDU_TX_MID,		/* optional; otherwise autogenerated */
	CMDU_TX_VID,		/* optional; vlanid for tagging frames */
	CMDU_TX_DATA,		/* tlv data in hexstring format */
	CMDU_TX_IFNAME,		/* use as outgoing interface if provided */
	NUM_CMDU_TX_POLICY,
};

static const struct blobmsg_policy cmdu_tx_policy[NUM_CMDU_TX_POLICY] = {
	[CMDU_TX_DST] = { .name = "dst", .type = BLOBMSG_TYPE_STRING },
	[CMDU_TX_SRC] = { .name = "src", .type = BLOBMSG_TYPE_STRING },
	[CMDU_TX_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_TX_MID] = { .name = "mid", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_TX_VID] = { .name = "vid", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_TX_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING },
	[CMDU_TX_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
};

int i1905_do_cmdu_tx(struct i1905_private *priv, void *args, void *out)
{
	struct blob_attr *msg = (struct blob_attr *)args;
	struct blob_buf *bb = (struct blob_buf *)out;
	struct blob_attr *tb[NUM_CMDU_TX_POLICY];
	struct i1905_interface_private *ifpriv;
	struct i1905_interface *iface = NULL;
	char dst_macstr[18] = {0};
	char src_macstr[18] = {0};
	uint8_t dst[6] = {0};
	uint8_t src[6] = {0};
	uint8_t *data = NULL;
	uint16_t type = 0;
	uint16_t mid = 0;
	uint16_t vid = 0;
	int data_len = 0;
	int ret = 0;


	blobmsg_parse(cmdu_tx_policy, NUM_CMDU_TX_POLICY, tb, blob_data(msg),
		      blob_len(msg));

	/* cmdu type and destination macaddress are mandatory */
	if (!tb[CMDU_TX_DST] || !tb[CMDU_TX_TYPE])
		return -EINVAL;

	if (tb[CMDU_TX_DST]) {
		strncpy(dst_macstr, blobmsg_data(tb[CMDU_TX_DST]),
			sizeof(dst_macstr)-1);

		if (hwaddr_aton(dst_macstr, dst) == NULL)
			return -EINVAL;

		if (hwaddr_is_zero(dst))
			return -EINVAL;
	}

	if (tb[CMDU_TX_SRC]) {
		strncpy(src_macstr, blobmsg_data(tb[CMDU_TX_SRC]),
			sizeof(src_macstr)-1);

		if (hwaddr_aton(src_macstr, src) == NULL)
			return -EINVAL;
	}

	if (tb[CMDU_TX_TYPE])
		type = blobmsg_get_u32(tb[CMDU_TX_TYPE]);

	if (tb[CMDU_TX_MID])
		mid = blobmsg_get_u32(tb[CMDU_TX_MID]);

	if (tb[CMDU_TX_VID]) {
		vid = (uint16_t)blobmsg_get_u32(tb[CMDU_TX_VID]);
		if (vid > 4094)
			vid = 0;
	}

	if (tb[CMDU_TX_DATA]) {
		int data_strlen = 0;
		char *data_str = NULL;

		data_strlen = blobmsg_data_len(tb[CMDU_TX_DATA]);
		data_len = (data_strlen - 1) / 2;
		data_str = calloc(1, data_strlen * sizeof(char));
		data = calloc(1, data_len * sizeof(uint8_t));

		if (data_str && data) {
			strncpy(data_str, blobmsg_data(tb[CMDU_TX_DATA]), data_strlen);
			strtob(data_str, data_len, data);
		}

		if (data_str)
			free(data_str);
	}

	if (tb[CMDU_TX_IFNAME]) {
		char outifname[16] = {0};

		strncpy(outifname, blobmsg_data(tb[CMDU_TX_IFNAME]), sizeof(outifname) - 1);
		iface = i1905_ifname_to_interface(priv, outifname);
		if (!iface || iface->exclude) {
			i1905_dbg(LOG_TX, "%s: %s is not a 1905 interface\n", __func__, outifname);
			if (data)
				free(data);
			return -EINVAL;
		}
	}

	if (hwaddr_is_mcast(dst) || hwaddr_is_bcast(dst)) {
		bool lo = true;

		if (!mid)
			mid = cmdu_get_next_mid();

		/* send out through all interfaces */
		list_for_each_entry(iface, &priv->dm.self.iflist, list) {
			if (iface->exclude)
				continue;

			ifpriv = iface->priv;
			ret &= i1905_cmdu_tx(ifpriv, vid, dst, src, type, &mid,
					     data, data_len, lo);
			lo = false;
			/* if any ret = 0, return success */
		}
	} else if (hwaddr_equal(dst, priv->dm.self.aladdr)) {

		if (list_empty(&priv->dm.self.iflist)) {
			if (data)
				free(data);
			return -EINVAL;
		}

		iface = i1905_lookup_interface(priv, "lo");
		if (!iface) {
			if (data)
				free(data);

			return -EINVAL;
		}

		ifpriv = iface->priv;
		ret = i1905_cmdu_tx(ifpriv, vid, dst, src, type, &mid,
				    data, data_len, true);
	} else {
		struct i1905_txpath *txp = NULL;
		struct neigh_entry *ent = NULL;

		ret = -1;

		if (iface) {
			ret = i1905_cmdu_tx(iface->priv, vid, dst, src, type, &mid,
					    data, data_len, false);

			goto done;
		}

		/* first check neigh cache */
		ent = neigh_lookup(&priv->neigh_q, dst);
		if (ent && !ent->unreachable && ent->state == NEIGH_STATE_REACHABLE) {
			char *ifname = ent->ifname;

			if (if_isbridge(ifname))
				ifname = i1905_brport_to_ifname(priv, ent->brport);

			if (ifname) {
				iface = i1905_ifname_to_interface(priv, ifname);
				if (iface && !iface->exclude) {
					ret = i1905_cmdu_tx(iface->priv, vid, dst,
							    src, type, &mid,
							    data, data_len, true);
					goto done;
				}
			}
		}

		/* check txpaths to dst */
		list_for_each_entry(txp, &priv->dm.self.txpaths, list) {
			if (hwaddr_equal(txp->aladdr, dst)) {
				ret = i1905_cmdu_tx(txp->iface->priv, vid, dst,
						    src, type, &mid,
						    data, data_len, true);
				if (!ret)
					goto done;

				i1905_warn(LOG_TX, "%s: cmdu tx failed\n", __func__);
			}
		}
	}

done:
	if (data)
		free(data);


	/* reply with mid and status of cmdu tx */
	blobmsg_add_string(bb, "status", ret == 0 ? "ok" : "fail");
	if (!ret)
		blobmsg_add_u32(bb, "mid", mid);

	return 0;
}

/* cmdu rx policy */
enum {
	CMDU_RX_SRC,		/* sender's macaddress */
	CMDU_RX_IFNAME,		/* receiving interface's name */
	CMDU_RX_TYPE,		/* in hex "0xaaaa" or int */
	CMDU_RX_MID,		/* optional; default is 0 */
	CMDU_RX_DATA,		/* data in hexstring representing tlvs */
	NUM_CMDU_RX_POLICY,
};

static const struct blobmsg_policy cmdu_rx_policy[NUM_CMDU_RX_POLICY] = {
	[CMDU_RX_SRC] = { .name = "src", .type = BLOBMSG_TYPE_STRING },
	[CMDU_RX_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	[CMDU_RX_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_STRING },
	[CMDU_RX_MID] = { .name = "mid", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_RX_DATA] = { .name = "data", .type = BLOBMSG_TYPE_STRING },
};

int i1905_do_cmdu_rx(struct i1905_private *priv, void *args, void *out)
{
	struct blob_attr *msg = (struct blob_attr *)args;
	struct blob_attr *tb[NUM_CMDU_RX_POLICY];
	struct blob_buf *bb = (struct blob_buf *)out;
	struct i1905_interface *iface;
	struct cmdu_buff *rxf = NULL;
	char ifname[16] = {0};
	uint8_t src[6] = {0};
	char *data_str = NULL;
	uint8_t *data = NULL;
	int data_len = 0;
	int ret = 0;



	blobmsg_parse(cmdu_rx_policy, NUM_CMDU_RX_POLICY, tb,
		      blob_data(msg), blob_len(msg));

	if (!tb[CMDU_RX_IFNAME]) {
		i1905_dbg(LOG_CMD, "%s: interface name is mandatory\n", __func__);
		return -EINVAL;
	}

	strncpy(ifname, blobmsg_data(tb[CMDU_RX_IFNAME]), sizeof(ifname) - 1);
	iface = i1905_ifname_to_interface(priv, ifname);
	if (!iface) {
		i1905_dbg(LOG_CMD, "%s: %s is not a 1905 interface\n", __func__, ifname);
		return -EINVAL;
	}

	if (tb[CMDU_RX_SRC]) {
		char src_macstr[18] = {0};

		strncpy(src_macstr, blobmsg_data(tb[CMDU_RX_SRC]),
			sizeof(src_macstr)-1);

		if (hwaddr_aton(src_macstr, src) == NULL)
			return -EINVAL;

		if (hwaddr_is_zero(src))
			return -EINVAL;
	}

	if (tb[CMDU_RX_DATA]) {
		int data_strlen = 0;

		data_strlen = blobmsg_data_len(tb[CMDU_RX_DATA]);
		data_len = (data_strlen - 1) / 2;
		data_str = calloc(1, data_strlen * sizeof(char));
		if (!data_str) {
			dbg("%s: -ENOMEM\n", __func__);
			return -1;
		}

		data = calloc(1, data_len * sizeof(uint8_t));
		if (!data) {
			free (data_str);
			dbg("%s: -ENOMEM\n", __func__);
			return -1;
		}

		strncpy(data_str, blobmsg_data(tb[CMDU_RX_DATA]), data_strlen);
		strtob(data_str, data_len, data);
		free(data_str);
	}

	/* start building the cmdu */
	rxf = cmdu_alloc_frame(data_len);
	if (!rxf) {
		dbg("%s: -ENOMEM\n", __func__);
		if (data_len)
			free(data);

		return -1;
	}

	memcpy(rxf->dev_macaddr, iface->macaddr, 6);
	strncpy(rxf->dev_ifname, ifname, 15);
	memcpy(rxf->origin, src, 6);
	CMDU_SET_LAST_FRAGMENT(rxf->cdata);

	if (tb[CMDU_RX_TYPE]) {
		const char *typestr = blobmsg_data(tb[CMDU_RX_TYPE]);
		uint16_t type = 0;

		type = strtoul(typestr, NULL, 16);
		cmdu_set_type(rxf, type);
	}

	if (tb[CMDU_RX_MID]) {
		uint16_t mid = 0;

		mid = blobmsg_get_u32(tb[CMDU_RX_MID]);
		cmdu_set_mid(rxf, mid);
	}

	if (data_len) {
		cmdu_put(rxf, data, data_len);
		free(data);
	}

	ret = i1905_process_cmdu(priv, rxf);
	cmdu_free(rxf);

	/* reply with status */
	blobmsg_add_string(bb, "status", ret == 0 ? "ok" : "fail");

	return ret;
}

/* cmdu prepare policy */
enum {
	CMDU_PREP_TYPE,		/* cmdu type */
	CMDU_PREP_IFNAME,	/* interface name (optional) */
	CMDU_PREP_ARGS,		/* cmdu specific argument list */
	NUM_CMDU_PREP_POLICY,
};

static const struct blobmsg_policy cmdu_prep_policy[NUM_CMDU_PREP_POLICY] = {
	[CMDU_PREP_TYPE] = { .name = "type", .type = BLOBMSG_TYPE_INT32 },
	[CMDU_PREP_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	[CMDU_PREP_ARGS] = { .name = "args", .type = BLOBMSG_TYPE_ARRAY },
};

int i1905_do_cmdu_prepare(struct i1905_private *priv, void *args, void *out)
{
	struct blob_attr *msg = (struct blob_attr *)args;
	struct blob_attr *tb[NUM_CMDU_PREP_POLICY];
	struct blob_buf *bb = (struct blob_buf *)out;
	struct i1905_interface *iface = NULL;
	struct cmdu_buff *cmdu = NULL;
	struct blob_attr *attr;
	char ifname[16] = {0};
	char *datastr = NULL;
	uint16_t type = 0;
#define ARGS_MAX 8
	char *argv[ARGS_MAX] = {0};
	int argc = 0;
	int ret = 0;
	int i = 0;
	int rem;


	if (list_empty(&priv->dm.self.iflist))
		return -EINVAL;

	blobmsg_parse(cmdu_prep_policy, NUM_CMDU_PREP_POLICY, tb,
		      blob_data(msg), blob_len(msg));

	/* cmdu type is mandatory */
	if (!tb[CMDU_PREP_TYPE])
		return -EINVAL;


	type = blobmsg_get_u32(tb[CMDU_PREP_TYPE]);
	if (type > CMDU_TYPE_1905_END)
		return -EINVAL;

	if (tb[CMDU_PREP_IFNAME]) {
		strncpy(ifname, blobmsg_data(tb[CMDU_PREP_IFNAME]), 16);
		ifname[15] = '\0';

		iface = i1905_ifname_to_interface(priv, ifname);
	} else {
		if (list_empty(&priv->dm.self.iflist))
			return -EINVAL;

		/* assume first interface in the iflist */
		iface = list_first_entry(&priv->dm.self.iflist, struct i1905_interface, list);
	}

	if (!iface)
		return -EINVAL;

	if (tb[CMDU_PREP_ARGS]) {
		blobmsg_for_each_attr(attr, tb[CMDU_PREP_ARGS], rem) {
			int len = blobmsg_data_len(attr);

			if (blobmsg_type(attr) != BLOBMSG_TYPE_STRING)
				continue;

			if (i >= ARGS_MAX) {
				argc = ARGS_MAX;
				ret = -EINVAL;
				goto out;
			}

			argv[i] = calloc(1, len * sizeof(char));
			if (argv[i])
				strncpy(argv[i], blobmsg_data(attr), len - 1);

			i++;
		}
	}

	argc = i;

	if (!is_cmdu_tlv_required(type)) {
		blobmsg_add_u32(bb, "type", type);
		blobmsg_add_string(bb, "data", "");
		goto out;
	}

	switch (type) {
	case CMDU_TYPE_TOPOLOGY_DISCOVERY:
		cmdu = i1905_build_topology_discovery(iface);
		break;
	case CMDU_TYPE_TOPOLOGY_NOTIFICATION:
		cmdu = i1905_build_topology_notification(iface);
		break;
	case CMDU_TYPE_TOPOLOGY_RESPONSE:
		cmdu = i1905_build_topology_response(iface);
		break;
	case CMDU_TYPE_VENDOR_SPECIFIC:
		cmdu = i1905_build_vendor_specific(iface, argc, argv);
		break;
	case CMDU_TYPE_LINK_METRIC_QUERY:
		cmdu = i1905_build_link_metric_query(iface);
		break;
	case CMDU_TYPE_LINK_METRIC_RESPONSE:
		{
			uint8_t qtype = LINKMETRIC_QUERY_TYPE_BOTH;
			uint8_t nbrmac[6] = {0};

			if (argc > 2) {
				ret = -EINVAL;
				goto out;
			}

			if (argc >= 1 && argv[0]) {
				char *endptr = NULL;

				errno = 0;
				qtype = strtoul(argv[0], &endptr, 10);
				if (errno || *endptr != '\0') {
					ret = -EINVAL;
					goto out;
				}

				if (qtype != LINKMETRIC_QUERY_TYPE_TX &&
				    qtype != LINKMETRIC_QUERY_TYPE_RX &&
				    qtype != LINKMETRIC_QUERY_TYPE_BOTH) {
					ret = -EINVAL;
					goto out;
				}
			}

			if (argc == 2 && argv[1]) {
				if (!hwaddr_aton(argv[1], nbrmac)) {
					ret = -EINVAL;
					goto out;
				}
			}

			cmdu = i1905_build_link_metric_response(iface, nbrmac, qtype);
		}
		break;
	case CMDU_TYPE_HIGHER_LAYER_RESPONSE:
		cmdu = i1905_build_higher_layer_response(iface);
		break;
	case CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH:
	case CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE:
	case CMDU_TYPE_AP_AUTOCONFIGURATION_RENEW:
		{
			int band = 0;
			uint8_t freqband = IEEE80211_FREQUENCY_BAND_UNKNOWN;
			char *endptr = NULL;

			if (argc != 1 || !argv[0]) {
				ret = -EINVAL;
				goto out;
			}

			errno = 0;
			band = strtol(argv[0], &endptr, 10);
			if (errno || *endptr != '\0') {
				ret = -EINVAL;
				goto out;
			}

			switch (band) {
			case 2:
				freqband = IEEE80211_FREQUENCY_BAND_2_4_GHZ;
				break;
			case 5:
				freqband = IEEE80211_FREQUENCY_BAND_5_GHZ;
				break;
			case 60:
				freqband = IEEE80211_FREQUENCY_BAND_60_GHZ;
				break;
#ifdef WIFI_EASYMESH
			case 6:
				freqband = IEEE80211_FREQUENCY_BAND_6_GHZ;
				break;
#endif
			default:
				ret = -EINVAL;
				goto out;
			}

			if (type == CMDU_TYPE_AP_AUTOCONFIGURATION_SEARCH)
				cmdu = i1905_build_ap_autoconfig_search(iface, freqband);
			else if (type == CMDU_TYPE_AP_AUTOCONFIGURATION_RESPONSE)
				cmdu = i1905_build_ap_autoconfig_response(iface, freqband);
			else
				cmdu = i1905_build_ap_autoconfig_renew(iface, freqband);
		}
		break;
	case CMDU_TYPE_AP_AUTOCONFIGURATION_WSC:
	case CMDU_TYPE_PUSH_BUTTON_EVENT_NOTIFICATION:
	case CMDU_TYPE_PUSH_BUTTON_JOIN_NOTIFICATION:
	case CMDU_TYPE_INTERFACE_POWER_CHANGE_REQUEST:
	case CMDU_TYPE_INTERFACE_POWER_CHANGE_RESPONSE:
	case CMDU_TYPE_GENERIC_PHY_RESPONSE:
	default:
		ret = -ENOTSUP;
		goto out;
	}

	if (!cmdu) {
		ret = -1;
		goto out;
	}


	datastr = calloc(1, 2 * cmdu->datalen * sizeof(char) + 1);
	if (!datastr) {
		dbg("%s: -ENOMEM\n", __func__);
		cmdu_free(cmdu);
		ret = -1;
		goto out;
	}

	btostr(cmdu->data, cmdu->datalen, datastr);

	/* reply with type and tlv data */
	blobmsg_add_u32(bb, "type", type);
	blobmsg_add_string(bb, "data", datastr);

	free(datastr);
	cmdu_free(cmdu);

out:
	for (i = 0; i < argc; i++)
		free(argv[i]);

#undef ARGS_MAX
	return ret;
}

enum {
	I1905_APCONFIG_IFNAME,
	I1905_APCONFIG_BAND,
	I1905_APCONFIG_ACTION,
	NUM_I1905_APCONFIG_POLICY,
};

static const struct blobmsg_policy apconfig_policy[NUM_I1905_APCONFIG_POLICY] = {
	[I1905_APCONFIG_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
	[I1905_APCONFIG_BAND] = { .name = "band", .type = BLOBMSG_TYPE_INT32 },
	[I1905_APCONFIG_ACTION] = { .name = "action", .type = BLOBMSG_TYPE_STRING },
};

int i1905_do_apconfig(struct i1905_private *priv, void *args, void *out)
{
	struct blob_attr *msg = (struct blob_attr *)args;
	struct blob_attr *tb[NUM_I1905_APCONFIG_POLICY];
	struct blob_buf *bb = (struct blob_buf *)out;
	char action[64] = { 0 };
	uint8_t band = 0xff;
	int ret = 0;


	blobmsg_parse(apconfig_policy, NUM_I1905_APCONFIG_POLICY, tb,
		      blob_data(msg), blob_len(msg));


	if (tb[I1905_APCONFIG_IFNAME]) {
		char ifname[16] = { 0 };

		strncpy(ifname, blobmsg_data(tb[I1905_APCONFIG_IFNAME]), 16);
		ifname[15] = '\0';
	}

	if (tb[I1905_APCONFIG_BAND]) {
		band = blobmsg_get_u32(tb[I1905_APCONFIG_BAND]);
		if (band != 2 && band != 5 && band != 6)
			return -EINVAL;
	}

	if (tb[I1905_APCONFIG_ACTION]) {
		size_t len = blobmsg_data_len(tb[I1905_APCONFIG_ACTION]);

		strncpy(action, blobmsg_data(tb[I1905_APCONFIG_ACTION]), len);
		action[63] = '\0';
	}

	if (!strcmp(action, "renew"))
		ret = i1905_apconfig_renew(priv, band);
	else if (!strcmp(action, "search"))
		ret = i1905_apconfig_request(priv, band);
	else {
		priv->start_apconfig = 1;
		ret = i1905_apconfig_request(priv, band);
	}

	/* reply with status */
	blobmsg_add_string(bb, "status", ret == 0 ? "ok" : "fail");
	return 0;
}

/* add/del interface policy */
enum {
	I1905_INTERFACE_IFNAME,		/* interface name */
	NUM_I1905_INTERFACE_POLICY,
};

static const struct blobmsg_policy interface_policy[NUM_I1905_INTERFACE_POLICY] = {
	[I1905_INTERFACE_IFNAME] = { .name = "ifname", .type = BLOBMSG_TYPE_STRING },
};

int i1905_interface_add(struct i1905_private *priv, void *args, void *out)
{
	struct blob_attr *msg = (struct blob_attr *)args;
	struct blob_attr *tb[NUM_I1905_INTERFACE_POLICY];
	struct blob_buf *bb = (struct blob_buf *)out;
	struct i1905_interface *iface;
	char ifname[16] = {0};
	int ret = 0;


	blobmsg_parse(interface_policy, NUM_I1905_INTERFACE_POLICY, tb,
		      blob_data(msg), blob_len(msg));

	if (!tb[I1905_INTERFACE_IFNAME])
		return -EINVAL;

	strncpy(ifname, blobmsg_data(tb[I1905_INTERFACE_IFNAME]), 16);
	ifname[15] = '\0';

	iface = i1905_ifname_to_interface(priv, ifname);
	if (iface) {
		dbg("%s: %s is already a 1905 interface\n", __func__, ifname);
		return -EINVAL;
	}

	iface = i1905_setup_interface(priv, ifname);
	if (!iface) {
		dbg("%s: %s: error setup interface\n", __func__, ifname);
		ret = 1;
	}

	/* reply with status */
	blobmsg_add_string(bb, "status", ret == 0 ? "ok" : "fail");
	return 0;
}

int i1905_interface_del(struct i1905_private *priv, void *args, void *out)
{
	struct blob_attr *msg = (struct blob_attr *)args;
	struct blob_attr *tb[NUM_I1905_INTERFACE_POLICY];
	struct blob_buf *bb = (struct blob_buf *)out;
	struct i1905_interface *iface;
	char ifname[16] = {0};


	blobmsg_parse(interface_policy, NUM_I1905_INTERFACE_POLICY, tb,
		      blob_data(msg), blob_len(msg));

	if (!tb[I1905_INTERFACE_IFNAME])
		return -EINVAL;

	strncpy(ifname, blobmsg_data(tb[I1905_INTERFACE_IFNAME]), 16);
	ifname[15] = '\0';

	iface = i1905_ifname_to_interface(priv, ifname);
	if (!iface) {
		dbg("%s: %s is not a 1905 interface\n", __func__, ifname);
		return -EINVAL;
	}

	i1905_teardown_interface(priv, ifname);

	/* reply with status */
	blobmsg_add_string(bb, "status", "ok");
	return 0;
}
