/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * i1905_extension.c - for extending core IEEE-1905 CMDUs.
 *
 * Copyright (C) 2021-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 <string.h>
#include <stdarg.h>
#include <dlfcn.h>
#include <sys/time.h>

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

#include "timer.h"
#include "debug.h"
#include "util.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 "i1905_extension.h"

#define I1905_EXTMODULE_PATH	"/usr/lib/"IEEE1905_OBJECT

static int extmodule_load(const char *path, const char *name, void **handle)
{
	void *h;
	char abspath[256] = {0};
	int flags = 0;

	if (!handle || !name || !path)
		return -1;

	flags |= RTLD_NOW | RTLD_GLOBAL;

	snprintf(abspath, sizeof(abspath) - 1, "%s/%s", path, name);

	h = dlopen(abspath, flags);
	if (!h) {
		i1905_err(LOG_MAIN, "%s: Error: %s\n", __func__, dlerror());
		return -1;
	}

	*handle = h;
	return 0;
}

static int extmodule_unload(void *handle)
{
	if (!handle)
		return -1;

	return dlclose(handle);
}

int i1905_unload_extmodule(struct i1905_extmodule *mod)
{
	if (mod) {
		int ret;

		if (mod->exit)
			mod->exit(mod->priv);

		ret = extmodule_unload(mod->handle);
		list_del(&mod->list);
		i1905_info(LOG_MAIN, "Unloaded extension\n");

		return ret;
	}

	return -1;
}

struct i1905_extmodule *i1905_load_extmodule(struct i1905_private *priv,
					     const char *name)
{
	struct i1905_extmodule *p;
	char extmodule_file[128] = {0};
	struct i1905_extmodule *pp = NULL;
	void *handle;
	int ret;
	struct i1905_context i1905ctx = {
		.bus = priv->ctx,
		.context = priv,
	};

	snprintf(extmodule_file, 127, "%s.so", name);
	ret = extmodule_load(I1905_EXTMODULE_PATH, extmodule_file, &handle);
	if (ret)
		return NULL;

	pp = dlsym(handle, name);
	if (!pp) {
		i1905_err(LOG_MAIN, "Symbol '%s' not found\n", name);
		return NULL;
	}

	p = calloc(1, sizeof(struct i1905_extmodule));
	if (!p) {
		extmodule_unload(handle);
		return NULL;
	}

	memcpy(p, pp, sizeof(struct i1905_extmodule));
	p->handle = handle;

	if (p->init)
		p->init(&p->priv, &i1905ctx);

	i1905_dbg(LOG_MAIN, "Loaded extension %s (priv = 0x%p)\n", name, p->priv);

	return p;
}

int extmodules_load(int argc, char *argv[], struct list_head *extensions)
{
	struct i1905_extmodule *p;
	int i;

	for (i = 0; i < argc && argv[i]; i++) {
		char extmodule_file[128] = {0};
		void *handle;
		struct i1905_extmodule *pp = NULL;
		int ret;

		snprintf(extmodule_file, 127, "%s.so", argv[i]);
		ret = extmodule_load(I1905_EXTMODULE_PATH, extmodule_file, &handle);
		if (ret)
			continue;

		pp = dlsym(handle, argv[i]);
		if (!pp) {
			i1905_dbg(LOG_MAIN, "Symbol '%s' not found\n", argv[i]);
			continue;
		}

		p = calloc(1, sizeof(struct i1905_extmodule));
		if (!p) {
			ret = extmodule_unload(handle);
			continue;
		}

		memcpy(p, pp, sizeof(struct i1905_extmodule));
		list_add_tail(&p->list, extensions);
		if (p->init)
			p->init(&p->priv, NULL);
	}

	return 0;
}

int extmodules_unload(struct list_head *extensions)
{
	struct i1905_extmodule *p = NULL, *tmp;
	int ret = 0;

	list_for_each_entry_safe(p, tmp, extensions, list) {
		if (p->exit)
			p->exit(p->priv);

		list_del(&p->list);
		ret |= extmodule_unload(p->handle);
		free(p);
	}

	return ret;
}

int extmodule_maybe_process_cmdu(struct list_head *extensions,
				 struct cmdu_buff *frm)
{
	struct i1905_extmodule *e = NULL;
	int ret = CMDU_NOP;
	uint16_t type;
	int i;

	type = buf_get_be16((uint8_t *)&frm->cdata->hdr.type);

	list_for_each_entry(e, extensions, list) {
		//if (!e->active)
		//	continue;

		if ((e->num_ext == -1 || (e->from_newtype <= type && type <= e->to_newtype))
		    && e->process_cmdu) {
			ret = e->process_cmdu(e->priv, frm);
			if (ret == CMDU_OK)
				continue;

			if (ret == CMDU_DONE || ret == CMDU_DROP)
				return ret;
		}

		if (type <= CMDU_TYPE_MAX) {
			for (i = 0; i < e->num_ext; i++) {
				if (e->ext[i].type == type) {
					ret = e->process_cmdu(e->priv, frm);
					if (ret == CMDU_OK)
						break;

					if (ret == CMDU_DONE || ret == CMDU_DROP)
						return ret;
				}
			}
		}
	}

	return ret;
}

static int i1905_extmodules_notify_event(struct i1905_private *priv, char *msg,
					 size_t len)
{
	struct i1905_extmodule *m = NULL;

	if (list_empty(&priv->extlist))
		return 0;

	list_for_each_entry(m, &priv->extlist, list) {
		if (m->event_cb)
			m->event_cb(m->priv, msg, len);
	}

	return 0;
}

int i1905_extmodules_notify(struct i1905_private *priv, uint32_t event, ...)
{
	char evbuf[512] = {0};
	va_list ap;


	va_start(ap, event);

	switch (event) {
	case IEEE1905_NBR_REMOVED:
		{
			uint8_t *nbr_aladdr = (uint8_t *)va_arg(ap, void *);

			snprintf(evbuf, sizeof(evbuf) - 1,
				 "ieee1905.neighbor.del '{\"nbr_ieee1905id\":\""MACFMT
				 "\", \"is1905\":true }'",
				 MAC2STR(nbr_aladdr));
		}
		break;
	case IEEE1905_NBR_ADDED:
		{
			uint8_t *nbr_aladdr = (uint8_t *)va_arg(ap, void *);
			uint8_t *nbr_ifmacaddr = (uint8_t *)va_arg(ap, void *);
			uint8_t *rx_ifmacaddr = (uint8_t *)va_arg(ap, void *);

			snprintf(evbuf, sizeof(evbuf) - 1,
				 "ieee1905.neighbor.add '{\"nbr_ieee1905id\":\"" MACFMT
				 "\", \"nbr_macaddress\":\"" MACFMT
				 "\", \"macaddress\":\"" MACFMT
				 "\", \"is1905\":true }'",
				 MAC2STR(nbr_aladdr),
				 MAC2STR(nbr_ifmacaddr),
				 MAC2STR(rx_ifmacaddr));
		}
		break;
	case IEEE1905_LINK_ADDED:
		{
			uint8_t *aladdr = (uint8_t *)va_arg(ap, void *);
			uint8_t *ifmacaddr = (uint8_t *)va_arg(ap, void *);
			uint8_t *nbr_aladdr = (uint8_t *)va_arg(ap, void *);
			uint8_t *nbr_ifmacaddr = (uint8_t *)va_arg(ap, void *);
			bool direct = (bool)va_arg(ap, void *);

			snprintf(evbuf, sizeof(evbuf) - 1,
				 "ieee1905.link.add '{\"ieee1905id\":\"" MACFMT
				 "\", \"macaddress\":\"" MACFMT
				 "\", \"nbr_ieee1905id\":\"" MACFMT
				 "\", \"nbr_macaddress\":\"" MACFMT
				 "\", \"direct\":\"%s\" }'",
				 MAC2STR(aladdr),
				 MAC2STR(ifmacaddr),
				 MAC2STR(nbr_aladdr),
				 MAC2STR(nbr_ifmacaddr),
				 direct ? "true" : "false");
		}
		break;
	case IEEE1905_LINK_REMOVED:
		{
			uint8_t *aladdr = (uint8_t *)va_arg(ap, void *);
			uint8_t *ifmacaddr = (uint8_t *)va_arg(ap, void *);
			uint8_t *nbr_aladdr = (uint8_t *)va_arg(ap, void *);
			uint8_t *nbr_ifmacaddr = (uint8_t *)va_arg(ap, void *);
			int timeout = (int)va_arg(ap, int);

			snprintf(evbuf, sizeof(evbuf) - 1,
				 "ieee1905.link.del '{\"ieee1905id\":\"" MACFMT
				 "\", \"macaddress\":\"" MACFMT
				 "\", \"nbr_ieee1905id\":\"" MACFMT
				 "\", \"nbr_macaddress\":\"" MACFMT
				 "\", \"reason\":\"%s\" }'",
				 MAC2STR(aladdr),
				 MAC2STR(ifmacaddr),
				 MAC2STR(nbr_aladdr),
				 MAC2STR(nbr_ifmacaddr),
				 timeout ? "timeout" : "invalid");
		}
		break;
	case IEEE1905_TOPOLOGY_CHANGED:
		{
			uint8_t *ifname = (uint8_t *)va_arg(ap, void *);
			bool newnbr = (bool)va_arg(ap, void *);
			bool is1905 = (bool)va_arg(ap, void *);
			uint8_t *macaddr = (uint8_t *)va_arg(ap, void *);

			snprintf(evbuf, sizeof(evbuf) - 1,
				 "ieee1905.topology '{\"ifname\":\"%s"
				 "\", \"event\":\"%s"
				 "\", \"data\": {"
				 "\"is1905\": %s"
				 ", \"macaddr\":\"" MACFMT
				 "\"}}'",
				 ifname,
				 newnbr ? "new_neighbor" : "lost_neighbor",
				 is1905 ? "true" : "false",
				 MAC2STR(macaddr));
		}
		break;
	default:
		va_end(ap);
		return 0;
	}

	va_end(ap);
	i1905_extmodules_notify_event(priv, evbuf, strlen(evbuf));

	return 0;
}

int ieee1905_get_non1905_neighbors(void *ieee1905, void *buf, size_t *sz)
{
	struct i1905_non1905_ifneighbor *ent;
	struct i1905_selfdevice *self;
	struct i1905_context *i1905;
	struct i1905_interface *lif = NULL;
	struct i1905_private *p;
	int pos = 0;
	int rsz = 0;


	if (!ieee1905) {
		errno = -EINVAL;
		return -1;
	}

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context) {
		errno = -EINVAL;
		return -1;
	}

	p = i1905->context;
	self = &p->dm.self;

	list_for_each_entry(lif, &self->iflist, list) {
		if (list_empty(&lif->non1905_nbrlist))
			continue;

		if (lif->lo || hwaddr_is_zero(lif->macaddr))
			continue;

		rsz += sizeof(struct i1905_non1905_ifneighbor) + 6 * lif->num_neighbor_non1905;
	}

	if (*sz < rsz) {
		*sz = rsz;
		errno = -E2BIG;
		return -1;
	}

	memset(buf, 0, *sz);
	*sz = 0;
	pos = 0;
	ent = (struct i1905_non1905_ifneighbor *)buf;

	list_for_each_entry(lif, &self->iflist, list) {
		struct i1905_non1905_neighbor *nnbr = NULL;
		int i = 0;

		if (list_empty(&lif->non1905_nbrlist))
			continue;

		if (lif->lo || hwaddr_is_zero(lif->macaddr))
			continue;

		ent = (struct i1905_non1905_ifneighbor *)((uint8_t *)buf + pos);
		memcpy(ent->if_macaddr, lif->macaddr, 6);
		pos += 6;
		list_for_each_entry(nnbr, &lif->non1905_nbrlist, list) {
			memcpy(&ent->non1905_macaddr[i*6], nnbr->macaddr, 6);
			pos += 6;
			i++;
		}

		ent->num_non1905 = i;
		pos += sizeof(ent->num_non1905);
	}

	*sz = pos;
	return 0;
}

int ieee1905_get_links(void *ieee1905, void *buf, size_t *sz)
{
	struct i1905_selfdevice *self;
	struct i1905_ifneighbor *ent;
	struct i1905_interface *lif = NULL;
	struct i1905_context *i1905;
	struct i1905_private *p;
	int pos = 0;
	int rsz = 0;


	if (!ieee1905) {
		errno = -EINVAL;
		return -1;
	}

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context) {
		errno = -EINVAL;
		return -1;
	}

	p = i1905->context;
	self = &p->dm.self;

	list_for_each_entry(lif, &self->iflist, list) {
		if (list_empty(&lif->nbriflist))
			continue;

		if (lif->lo || hwaddr_is_zero(lif->macaddr))
			continue;

		rsz += sizeof(struct i1905_ifneighbor) +
			lif->num_links * sizeof(struct i1905_ifneighbor_link);
	}

	if (*sz < rsz) {
		*sz = rsz;
		errno = -E2BIG;
		return -1;
	}

	memset(buf, 0, *sz);
	*sz = 0;
	pos = 0;

	list_for_each_entry(lif, &self->iflist, list) {
		struct i1905_neighbor_interface *link = NULL;
		int i = 0;

		if (list_empty(&lif->nbriflist))
			continue;

		if (lif->lo || hwaddr_is_zero(lif->macaddr))
			continue;

		ent = (struct i1905_ifneighbor *)((uint8_t *)buf + pos);
		memcpy(ent->if_macaddr, lif->macaddr, 6);
		pos += 6;
		list_for_each_entry(link, &lif->nbriflist, list) {
			struct i1905_ifneighbor_link *el = &ent->link[i];

			memcpy(el->macaddr, link->r_macaddr, 6);
			memcpy(el->aladdr, link->aladdr, 6);
			el->direct = link->direct;
			el->media = link->media;
			el->has_bridge = link->has_bridge;
			el->metric.tx_errors = link->metric.tx_errors;
			el->metric.rx_errors = link->metric.rx_errors;
			el->metric.tx_packets = link->metric.tx_packets;
			el->metric.rx_packets = link->metric.rx_packets;
			el->metric.available = link->metric.available;
			el->metric.max_rate = link->metric.max_rate;
			el->metric.max_phyrate = link->metric.max_phyrate;
			el->metric.rssi = link->metric.rssi;
			pos += sizeof(*el);
			i++;
		}

		ent->num_links = i;
		pos += sizeof(ent->num_links);
	}

	*sz = pos;
	return 0;
}

int ieee1905_send_cmdu(void *ieee1905, uint8_t *dst, uint8_t *src,
		       uint16_t type, uint16_t *mid, uint8_t *data, int len)
{
	struct i1905_interface_private *ifpriv;
	struct i1905_interface *iface = NULL;
	struct i1905_context *i1905;
	struct i1905_private *p;
	uint8_t dstmac[6] = {0};
	uint8_t srcmac[6] = {0};
	uint16_t msgid = 0;
	int ret = -1;


	if (!ieee1905 || !mid)
		return -1;

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context)
		return -1;

	p = i1905->context;

	if (!dst || hwaddr_is_zero(dst))
		memcpy(dstmac, p->dm.self.aladdr, 6);
	else
		memcpy(dstmac, dst, 6);

	if (src && !hwaddr_is_zero(src))
		memcpy(srcmac, src, 6);

	if (*mid != 0)
		msgid = *mid;


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

		if (!msgid)
			msgid = cmdu_get_next_mid();

		/* send out through all interfaces */
		list_for_each_entry(iface, &p->dm.self.iflist, list) {
			ifpriv = iface->priv;
			ret &= i1905_cmdu_tx(ifpriv, iface->vid, dstmac, srcmac,
					     type, &msgid, data, len, lo);
			lo = false;
		}
	} else if (hwaddr_equal(dstmac, p->dm.self.aladdr)) {
		if (list_empty(&p->dm.self.iflist))
			return -1;

		iface = i1905_lookup_interface(p, "lo");
		if (!iface)
			return -1;

		ifpriv = iface->priv;
		ret = i1905_cmdu_tx(ifpriv, iface->vid, dstmac, srcmac, type, &msgid,
				    data, len, true);
	} else {
		struct i1905_neighbor_interface *nif = NULL;
		bool sent = false;

		ret = -1;

		list_for_each_entry(iface, &p->dm.self.iflist, list) {
			if (list_empty(&iface->nbriflist))
				continue;

			list_for_each_entry(nif, &iface->nbriflist, list) {
				if (hwaddr_equal(nif->aladdr, dstmac)) {
					ret = i1905_cmdu_tx(iface->priv, iface->vid,
							    dstmac, srcmac, type,
							    &msgid, data, len, true);
					sent = true;
					break;
				}
			}

			if (sent)
				break;
		}
	}

	*mid = !ret ? msgid : 0xffff;
	return ret;
}

int ieee1905_get_local_interfaces(void *ieee1905, void *buf, size_t *sz)
{
	struct i1905_selfdevice_interface *ifl = NULL;
	struct i1905_local_interface *ent;
	struct i1905_selfdevice *self;
	struct i1905_context *i1905;
	struct i1905_interface *lif = NULL;
	struct i1905_private *p;
	int rsz = 0;
	size_t lsz;
	int i = 0;


	if (!ieee1905) {
		errno = -EINVAL;
		return -1;
	}

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context) {
		errno = -EINVAL;
		return -1;
	}

	p = i1905->context;
	self = &p->dm.self;
	lsz = self->num_interface * 16 + self->num_interface;

	char local_ifnames[lsz];

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

		snprintf(local_ifnames + strlen(local_ifnames), lsz - strlen(local_ifnames), ",%s", lif->ifname);
		rsz += sizeof(struct i1905_local_interface);
	}

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

		rsz += sizeof(struct i1905_local_interface);
	}

	if (*sz < rsz) {
		*sz = rsz;
		errno = -E2BIG;
		return -1;
	}

	memset(buf, 0, *sz);
	list_for_each_entry(lif, &self->iflist, list) {
		if (lif->lo || hwaddr_is_zero(lif->macaddr))
			continue;

		ent = (struct i1905_local_interface *)buf + i;
		memcpy(ent->if_macaddr, lif->macaddr, 6);
		ent->mediatype = lif->media;
		if (lif->mediainfo) {
			int k;

			if (IS_MEDIA_WIFI(ent->mediatype)) {
				for (k = 0; k < MAX_NUM_MEDIAINFO && k < lif->num_mediainfo; k++) {
					struct i1905_wifi_mediainfo *wi =
						(struct i1905_wifi_mediainfo *)lif->mediainfo + k;

					memcpy(&ent->mediainfo[k * sizeof(struct ieee80211_info)],
					       &wi->info, sizeof(struct ieee80211_info));
				}
			} else if (IS_MEDIA_1901(ent->mediatype)) {
				for (k = 0; k < MAX_NUM_MEDIAINFO && k < lif->num_mediainfo; k++) {
					struct i1905_plc_mediainfo *pi =
						(struct i1905_plc_mediainfo *)lif->mediainfo + k;

					memcpy(&ent->mediainfo[k * sizeof(struct ieee1901_info)],
					       &pi->info, sizeof(struct ieee1901_info));
				}
			}
		}

		i++;
	}

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

		ent = (struct i1905_local_interface *)buf + i;
		memcpy(ent->if_macaddr, ifl->macaddr, 6);
		ent->mediatype = ifl->mediatype;
		if (IS_MEDIA_WIFI(ent->mediatype)) {
			memcpy(ent->mediainfo, ifl->mediainfo, sizeof(struct ieee80211_info));
		} else if (IS_MEDIA_1901(ent->mediatype)) {
			memcpy(ent->mediainfo, ifl->mediainfo, sizeof(struct ieee1901_info));
		}

		i++;
	}

	*sz = rsz;
	return 0;
}

int ieee1905_get_bridge_tuples(void *ieee1905, void *buf, size_t *sz)
{
	struct i1905_master_interface *mif = NULL;
	struct i1905_selfdevice *self;
	struct i1905_context *i1905;
	struct i1905_brtuple *ent;
	struct i1905_private *p;
	size_t offset = 0;
	int rsz = 0;
	int i = 0;


	if (!ieee1905) {
		errno = -EINVAL;
		return -1;
	}

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context) {
		errno = -EINVAL;
		return -1;
	}

	p = i1905->context;
	self = &p->dm.self;

	memset(buf, 0, *sz);
	list_for_each_entry(mif, &self->miflist, list) {
		ent = (struct i1905_brtuple *)((uint8_t *)buf + offset);
		char brifs[32][16] = {0};
		int n = 32;
		int ret;

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

		rsz += 1 + n * 6;	/* num + n * sizeof(macaddr) */
		if (*sz < rsz) {
			*sz = rsz;
			errno = -E2BIG;
			return -1;
		}

		ent->num = n;
		for (i = 0; i < n; i++) {
			uint8_t macaddr[6] = {0};

			if_gethwaddr(brifs[i], macaddr);
			memcpy(&ent->macaddrs[i * 6], macaddr, 6);
		}
		offset += sizeof(ent->num) + ent->num * 6;
		if (offset > *sz) {
			errno = -E2BIG;
			return -1;
		}
	}

	*sz = rsz;
	return 0;
}

int ieee1905_get_interface_ipaddrs(void *ieee1905, void *buf, size_t *sz)
{
	struct i1905_interface_ipaddress *ent;
	struct i1905_master_interface *mif = NULL;
	struct i1905_selfdevice *self;
	struct i1905_context *i1905;
	struct i1905_interface *lif = NULL;
	struct i1905_private *p;
	size_t offset = 0;
	int rsz = 0;
	int i = 0;


	if (!ieee1905) {
		errno = -EINVAL;
		return -1;
	}

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context) {
		errno = -EINVAL;
		return -1;
	}

	p = i1905->context;
	self = &p->dm.self;

	/* consider ipaddrs of interfaces and their master interfaces if any */
	list_for_each_entry(lif, &self->iflist, list) {
		if (lif->lo || hwaddr_is_zero(lif->macaddr))
			continue;

		rsz += lif->num_ipaddrs * sizeof(struct i1905_interface_ipaddress);
	}

	list_for_each_entry(mif, &self->miflist, list)
		rsz += mif->num_ipaddrs * sizeof(struct i1905_interface_ipaddress);

	if (*sz < rsz) {
		*sz = rsz;
		errno = -E2BIG;
		return -1;
	}

	memset(buf, 0, *sz);
	list_for_each_entry(mif, &self->miflist, list) {
		ent = (struct i1905_interface_ipaddress *)((uint8_t *)buf + offset);
		memcpy(ent->if_macaddr, mif->macaddr, 6);
		ent->num = mif->num_ipaddrs;
		for (i = 0; i < mif->num_ipaddrs; i++)
			memcpy(&ent->addr[i], mif->ipaddrs + i, sizeof(struct ip_address));

		offset += sizeof(struct i1905_interface_ipaddress) +
				ent->num * sizeof(struct ip_address);
	}

	list_for_each_entry(lif, &self->iflist, list) {
		if (lif->lo || hwaddr_is_zero(lif->macaddr))
			continue;

		ent = (struct i1905_interface_ipaddress *)((uint8_t *)buf + offset);
		memcpy(ent->if_macaddr, lif->macaddr, 6);
		ent->num = lif->num_ipaddrs;
		for (i = 0; i < lif->num_ipaddrs; i++)
			memcpy(&ent->addr[i], lif->ipaddrs + i, sizeof(struct ip_address));

		offset += sizeof(struct i1905_interface_ipaddress) +
				ent->num * sizeof(struct ip_address);
	}

	*sz = rsz;
	return 0;
}

int ieee1905_get_selfdevice_misc_info(void *ieee1905, void *buf, size_t *sz)
{
	struct i1905_device_ident *ident;
	struct i1905_selfdevice *self;
	struct i1905_context *i1905;
	struct i1905_private *p;
	int rsz = 0;

	if (!ieee1905) {
		errno = -EINVAL;
		return -1;
	}

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context) {
		errno = -EINVAL;
		return -1;
	}

	p = i1905->context;
	self = &p->dm.self;

	rsz = sizeof(struct i1905_device_ident);
	if (*sz < rsz) {
		*sz = rsz;
		errno = -E2BIG;
		return -1;
	}

	ident = (struct i1905_device_ident *)buf;
	memcpy(ident->aladdr, self->aladdr, 6);
	ident->regband = self->regband;
	ident->version = self->version;
	memcpy(ident->name, self->name, 64);
	memcpy(ident->manufacturer, self->manufacturer, 64);
	memcpy(ident->model, self->model, 64);
	if (self->url) {
		if (strlen(self->url) < sizeof(ident->url))
			memcpy(ident->url, self->url, strlen(self->url));
		else
			memcpy(ident->url, self->url, sizeof(ident->url) - 1);
	}

	*sz = rsz;
	return 0;
}

int ieee1905_get_alid(void *ieee1905, uint8_t *aladdr)
{
	struct i1905_context *i1905;
	struct i1905_private *p;

	if (!ieee1905 || !aladdr)
		return -1;

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context)
		return -1;

	p = i1905->context;
	memcpy(aladdr, p->dm.self.aladdr, 6);

	return 0;
}

int ieee1905_get_neighbor_info(void *ieee1905, uint8_t *aladdr, void *buf, size_t *sz)
{
	struct i1905_neighbor_device *dev;
	struct i1905_interface *rif = NULL;
	struct i1905_device *rdev = NULL;
	struct i1905_selfdevice *self;
	struct i1905_context *i1905;
	struct i1905_private *p;
	struct i1905_ipv4 *ip4 = NULL;
	struct i1905_ipv6 *ip6 = NULL;
	int found = 0;
	uint8_t *ptr;
	int pos = 0;
	size_t rsz;
	int i = 0;


	if (!ieee1905 || !aladdr || !buf)
		return -1;

	i1905 = (struct i1905_context *)ieee1905;
	if (!i1905->context)
		return -1;

	p = i1905->context;
	self = &p->dm.self;

	list_for_each_entry(rdev, &self->topology.devlist, list) {
		if (hwaddr_equal(rdev->aladdr, aladdr)) {
			found = 1;
			break;
		}
	}

	if (!found) {
		errno = -EINVAL;
		return -1;
	}

	rsz = sizeof(struct i1905_neighbor_device) +
		rdev->num_ipv4 * sizeof(struct i1905_ipv4) +
		rdev->num_ipv6 * sizeof(struct i1905_ipv6) +
		rdev->num_interface * sizeof(struct i1905_local_interface);

	if (*sz < rsz) {
		*sz = rsz;
		errno = -E2BIG;
		return -1;
	}

	memset(buf, 0, *sz);
	dev = (struct i1905_neighbor_device *)buf;

	memcpy(dev->aladdr, aladdr, 6);
	dev->version = rdev->version;
	memcpy(dev->name, rdev->name, 64);
	memcpy(dev->manufacturer, rdev->manufacturer, 64);
	memcpy(dev->model, rdev->model, 64);
	if (strlen(rdev->url) < sizeof(dev->url))
		memcpy(dev->url, rdev->url, strlen(rdev->url));
	else
		memcpy(dev->url, rdev->url, sizeof(dev->url) - 1);

	dev->num_interface = rdev->num_interface;
	dev->num_ipv4 = rdev->num_ipv4;
	dev->num_ipv6 = rdev->num_ipv6;
	pos = offsetof(struct i1905_neighbor_device, data);

	list_for_each_entry(ip4, &rdev->ipv4list, list) {
		/* struct ipv4_entry * */ struct i1905_ipv4 *ipv4 = (struct i1905_ipv4*)&dev->data[pos];

		memcpy(ipv4->macaddr, ip4->macaddr, 6);
		memcpy(&ipv4->addr, &ip4->addr, sizeof(struct in_addr));
		ipv4->type = ip4->type;
		memcpy(&ipv4->dhcpserver, &ip4->dhcpserver, sizeof(struct in_addr));
		if (++i >= rdev->num_ipv4)
			break;

		pos += sizeof(struct i1905_ipv4);
	}

	i = 0;
	list_for_each_entry(ip6, &rdev->ipv6list, list) {
		struct i1905_ipv6 *ipv6 = (struct i1905_ipv6 *)&dev->data[pos];

		memcpy(ipv6->macaddr, ip6->macaddr, 6);
		memcpy(&ipv6->addr, &ip6->addr, sizeof(struct in6_addr));
		ipv6->type = ip6->type;
		memcpy(&ipv6->origin, &ip6->origin, sizeof(struct in6_addr));
		if (++i >= rdev->num_ipv6)
			break;

		pos += sizeof(struct i1905_ipv6);
	}

	i = 0;
	ptr = &dev->data[pos];
	list_for_each_entry(rif, &rdev->iflist, list) {
		struct i1905_local_interface *ent =
				(struct i1905_local_interface *)ptr + i;

		memcpy(ent->if_macaddr, rif->macaddr, 6);
		ent->mediatype = rif->media;
		if (rif->mediainfo) {
			if (IS_MEDIA_WIFI(ent->mediatype))
				memcpy(ent->mediainfo, rif->mediainfo, sizeof(struct ieee80211_info));
			else if (IS_MEDIA_1901(ent->mediatype))
				memcpy(ent->mediainfo, rif->mediainfo, sizeof(struct ieee1901_info));
		}

		if (++i > rdev->num_interface)
			break;
	}

	i1905_dbg(LOG_DM, "Nbr " MACFMT" data ---\n", MAC2STR(dev->aladdr));
	for (i = 0; i < rsz; i++) {
		i1905_dbg(LOG_DM, "%02x ", ((uint8_t *)buf)[i] & 0xff);
	}
	i1905_dbg(LOG_DM, "\n");

	return 0;
}
