/* SPDX-License-Identifier: BSD-3-Clause */
/*
 * i1905_dm.c - IEEE-1905 datamodel related functions.
 *
 * 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 <unistd.h>
#include <fcntl.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if_arp.h>
#include <linux/if_packet.h>
#include <net/ethernet.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/uloop.h>
#include <libubox/ustream.h>
#include <libubox/utils.h>

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


#define I1905_IFLINK_AGEOUT                     65000	/* msecs */
#define I1905_NEIGHBOR_AGEOUT                   70000	/* msecs */
#define I1905_IMMEDIATE_NEIGHBOR_AGEOUT         65000	/* msecs */


#define prlink(m,a,b,c,d)	\
	i1905_dbg(LOG_DM, "%s {" MACFMT ", " MACFMT " ---> " MACFMT ", " MACFMT "}\n", \
		  m, MAC2STR(a), MAC2STR(b), MAC2STR(c), MAC2STR(d));


static void i1905_dm_interface_peer_staletimer_cb(atimer_t *t)
{
	struct i1905_neighbor_interface *ifpeer =
		container_of(t, struct i1905_neighbor_interface, staletimer);
	struct i1905_interface *iface = ifpeer->iface;


	if (!iface) {
		err("link ageout: iface = NULL!\n");
		return;
	}

	dbg("%s: Deleting link " MACFMT " <---> " MACFMT"\n", __func__,
	    MAC2STR(iface->macaddr), MAC2STR(ifpeer->macaddr));

	/* only interfaces belonging to selfdevice have non-null priv */
	if (iface->priv && iface->device) {
		struct i1905_private *priv = i1905_selfdevice_to_context(iface->device);

		i1905_extmodules_notify(priv, IEEE1905_LINK_REMOVED,
					iface->aladdr,
					iface->macaddr,
					ifpeer->aladdr,
					ifpeer->r_macaddr,
					1);
	}

	list_del(&ifpeer->list);
	iface->num_links--;
	free(ifpeer);
}

struct i1905_bridge_tuple *i1905_dm_neighbor_brtuple_create(int num_macaddr)
{
	struct i1905_bridge_tuple *br;

	br = calloc(1, sizeof(*br) + 6 * num_macaddr);
	if (!br) {
		err("-ENOMEM\n");
		return NULL;
	}

	br->macaddrs = (uint8_t *)(br + 1);

	return br;
}

struct i1905_net_non1905_neighbor *i1905_dm_neighbor_non1905_nbr_create(void)
{
	struct i1905_net_non1905_neighbor *xnbr;

	xnbr = calloc(1, sizeof(*xnbr));
	if (!xnbr) {
		err("-ENOMEM\n");
		return NULL;
	}

	return xnbr;
}

int i1905_dm_neighbor_get_non1905_neighbors(struct i1905_device *rdev, uint8_t *macaddrs)
{
	struct i1905_interface *rif;
	int i = 0;

	list_for_each_entry(rif, &rdev->iflist, list) {
		struct i1905_net_non1905_neighbor *non;

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

		list_for_each_entry(non, &rif->non1905_nbrlist, list) {
			memcpy(&macaddrs[i*6], non->macaddr, 6);
			i++;
		}
	}

	return 0;
}

void i1905_neighbor_reset_ageout(struct i1905_device *rdev)
{
	if (rdev)
		timer_set(&rdev->agetimer, I1905_NEIGHBOR_AGEOUT);
}

struct i1905_device *i1905_dm_neighbor_lookup(struct i1905_interface *iface,
					      uint8_t *aladdr)
{
	struct i1905_selfdevice *self = (struct i1905_selfdevice *)iface->device;
	struct i1905_device *rdev = NULL;


	list_for_each_entry(rdev, &self->topology.devlist, list) {
		if (hwaddr_equal(rdev->aladdr, aladdr)) {
			return rdev;
		}
	}

	return NULL;
}

struct i1905_neighbor_interface *i1905_link_create(struct i1905_interface *iface)
{
	struct i1905_neighbor_interface *ifpeer;

	ifpeer = calloc(1, sizeof(*ifpeer));
	if (!ifpeer) {
		err("-ENOMEM\n");
		return NULL;
	}

	ifpeer->iface = iface;
	memcpy(ifpeer->macaddr, iface->macaddr, 6);
	ifpeer->tsp = time(NULL);
	timer_init(&ifpeer->staletimer, i1905_dm_interface_peer_staletimer_cb);
	timer_set(&ifpeer->staletimer, I1905_IFLINK_AGEOUT);
	ifpeer->media = I1905_MEDIA_UNKNOWN;
	ifpeer->has_bridge = iface->is_brif ? true : false;
	ifpeer->invalid = false;

	return ifpeer;
}

static void i1905_remote_link_ageout_cb(atimer_t *t)
{
	struct i1905_neighbor_interface *rlink =
		container_of(t, struct i1905_neighbor_interface, staletimer);

	warn("NOP: Remote link " MACFMT " ageout\n", MAC2STR(rlink->macaddr));
}

struct i1905_neighbor_interface *i1905_remote_link_create(struct i1905_interface *rif)
{
	struct i1905_neighbor_interface *link;

	link = calloc(1, sizeof(*link));
	if (!link) {
		dbg("-ENOMEM\n");
		return NULL;
	}

	link->iface = rif;
	memcpy(link->macaddr, rif->macaddr, 6);
	link->tsp = time(NULL);
	timer_init(&link->staletimer, i1905_remote_link_ageout_cb);
	link->media = I1905_MEDIA_UNKNOWN;
	link->has_bridge = false;
	link->invalid = false;

	return link;
}

/* lookup link {l_macaddr} --> {?, r_aladdr} */
struct i1905_neighbor_interface *i1905_remote_link_lookup(struct i1905_interface *rif,
							  uint8_t *l_macaddr, uint8_t *r_aladdr)
{
	struct i1905_neighbor_interface *rlink = NULL;

	list_for_each_entry(rlink, &rif->nbriflist, list) {
		prlink("link", rlink->macaddr, rif->aladdr, rlink->r_macaddr, rlink->aladdr);

		if (hwaddr_equal(rlink->macaddr, l_macaddr) &&
		    hwaddr_equal(rlink->aladdr, r_aladdr)) {
			rlink->tsp = time(NULL);
			return rlink;
		}
	}

	return NULL;
}

/* same as i1905_remote_link_lookup() but returns first matching invalid link */
struct i1905_neighbor_interface *i1905_remote_invalid_link_lookup(struct i1905_interface *rif,
								  uint8_t *l_macaddr,
								  uint8_t *r_aladdr)
{
	struct i1905_neighbor_interface *rlink = NULL;

	list_for_each_entry(rlink, &rif->nbriflist, list) {
		prlink("link", rlink->macaddr, rif->aladdr, rlink->r_macaddr, rlink->aladdr);

		if (rlink->invalid &&
		    hwaddr_equal(rlink->macaddr, l_macaddr) &&
		    hwaddr_equal(rlink->aladdr, r_aladdr)) {
			rlink->tsp = time(NULL);
			return rlink;
		}
	}

	return NULL;
}

/* lookup link {rif->macaddr} --- {r_macaddr, r_aladdr} */
struct i1905_neighbor_interface *i1905_remote_link_lookup_unique(struct i1905_interface *rif,
								 uint8_t *r_macaddr,
								 uint8_t *r_aladdr)
{
	struct i1905_neighbor_interface *rlink = NULL;

	list_for_each_entry(rlink, &rif->nbriflist, list) {
		prlink("link", rlink->macaddr, rif->aladdr, rlink->r_macaddr, rlink->aladdr);

		if (hwaddr_equal(rlink->macaddr, rif->macaddr) &&
		    hwaddr_equal(rlink->r_macaddr, r_macaddr) &&
		    hwaddr_equal(rlink->aladdr, r_aladdr)) {
			rlink->tsp = time(NULL);
			return rlink;
		}
	}

	return NULL;
}

struct i1905_ipv4 *i1905_dm_ipv4_create(void)
{
	struct i1905_ipv4 *ip;

	ip = calloc(1, sizeof(*ip));
	if (!ip) {
		err("-ENOMEM\n");
		return NULL;
	}

	return ip;
}

struct i1905_ipv6 *i1905_dm_ipv6_create(void)
{
	struct i1905_ipv6 *ip;

	ip = calloc(1, sizeof(*ip));
	if (!ip) {
		err("-ENOMEM\n");
		return NULL;
	}

	return ip;
}

struct i1905_neighbor_interface *i1905_find_neighbor_link(struct i1905_selfdevice *self,
							  uint8_t *aladdr)
{
	struct i1905_neighbor_interface *link = NULL;
	struct i1905_interface *iface = NULL;

	list_for_each_entry(iface, &self->iflist, list) {
		list_for_each_entry(link, &iface->nbriflist, list) {
			if (hwaddr_equal(link->aladdr, aladdr))
				return link;
		}
	}

	return NULL;
}

struct i1905_neighbor_interface *i1905_link_lookup(struct i1905_interface *iface,
						   uint8_t *l_macaddr,
						   uint8_t *r_aladdr)
{
	struct i1905_neighbor_interface *link = NULL;

	list_for_each_entry(link, &iface->nbriflist, list) {
		prlink("link ", link->macaddr, iface->aladdr, link->r_macaddr, link->aladdr);

		if (hwaddr_equal(link->macaddr, l_macaddr) &&
		    hwaddr_equal(link->aladdr, r_aladdr)) {
			link->tsp = time(NULL);
			timer_set(&link->staletimer, I1905_IFLINK_AGEOUT);
			return link;
		}
	}

	return NULL;
}

struct i1905_neighbor_interface *i1905_link_lookup_partial(struct i1905_interface *iface,
							   uint8_t *r_macaddr,
							   uint8_t *r_aladdr)
{
	struct i1905_neighbor_interface *ifpeer = NULL;

	list_for_each_entry(ifpeer, &iface->nbriflist, list) {
		prlink("Lookup link partial ", ifpeer->macaddr, iface->aladdr, ifpeer->r_macaddr, ifpeer->aladdr);

		if (hwaddr_equal(ifpeer->macaddr, iface->macaddr) &&
		    (hwaddr_equal(ifpeer->aladdr, r_aladdr) || hwaddr_equal(ifpeer->r_macaddr, r_macaddr))) {
			ifpeer->tsp = time(NULL);
			timer_set(&ifpeer->staletimer, I1905_IFLINK_AGEOUT);
			return ifpeer;
		}
	}

	return NULL;
}

struct i1905_interface *i1905_dm_neighbor_interface_create(void)
{
	struct i1905_interface *rif;

	rif = calloc(1, sizeof(*rif));
	if (!rif) {
		err("-ENOMEM\n");
		return NULL;
	}

	INIT_LIST_HEAD(&rif->vendorlist);
	INIT_LIST_HEAD(&rif->nbriflist);
	INIT_LIST_HEAD(&rif->non1905_nbrlist);
	//INIT_LIST_HEAD(&rif->iflinklist);

	return rif;
}

void i1905_free_all_non1905_nbrs_of_neighbor(struct i1905_interface *iface, uint8_t *aladdr)
{
	struct i1905_device *dev;

	dev = i1905_dm_neighbor_lookup(iface, aladdr);
	if (dev) {
		struct i1905_interface *rif = NULL;

		list_for_each_entry(rif, &dev->iflist, list) {
			list_flush(&rif->non1905_nbrlist, struct i1905_net_non1905_neighbor, list);
			dev->num_neighbor_non1905 -= rif->num_neighbor_non1905;
			rif->num_neighbor_non1905 = 0;
		}
	}
}

void i1905_free_interface_links(struct i1905_interface *iface)
{
	struct i1905_neighbor_interface *ifpeer = NULL, *tmp;


	/* stop link timers and free links */
	list_for_each_entry_safe(ifpeer, tmp, &iface->nbriflist, list) {
		if (timer_pending(&ifpeer->staletimer))
			timer_del(&ifpeer->staletimer);

		/* notify link event only for links belonging to selfdevice */
		if (iface->priv && iface->device) {
			struct i1905_private *priv = i1905_selfdevice_to_context(iface->device);

			i1905_extmodules_notify(priv, IEEE1905_LINK_REMOVED,
						iface->aladdr,
						iface->macaddr,
						ifpeer->aladdr,
						ifpeer->r_macaddr,
						0);
		}

		list_del(&ifpeer->list);
		free(ifpeer);
	}

	iface->num_links = 0;
}


void i1905_invalidate_links(struct i1905_interface *iface)
{
	struct i1905_neighbor_interface *link = NULL;

	list_for_each_entry(link, &iface->nbriflist, list) {
		link->invalid = true;
		prlink("Invalidate link", link->macaddr, iface->aladdr, link->r_macaddr, link->aladdr);
	}
}

void i1905_remote_link_invalidate_all(struct i1905_interface *iface, uint8_t *aladdr)
{
	struct i1905_device *dev;

	dev = i1905_dm_neighbor_lookup(iface, aladdr);
	if (dev) {
		struct i1905_interface *rif = NULL;

		list_for_each_entry(rif, &dev->iflist, list) {
			i1905_invalidate_links(rif);
		}
	}
}

void i1905_remote_link_free_invalid(struct i1905_interface *rif)
{
	struct i1905_neighbor_interface *link = NULL, *tmp;


	list_for_each_entry_safe(link, tmp, &rif->nbriflist, list) {
		if (!link->invalid)
			continue;

		if (timer_pending(&link->staletimer))
			timer_del(&link->staletimer);

		prlink("Free invalid link", link->macaddr, rif->aladdr, link->r_macaddr, link->aladdr);
		list_del(&link->list);
		rif->num_links--;
		free(link);
	}
}

void i1905_remote_link_free_all_invalid(struct i1905_interface *iface, uint8_t *aladdr)
{
	struct i1905_device *dev;

	dev = i1905_dm_neighbor_lookup(iface, aladdr);
	if (dev) {
		struct i1905_interface *rif = NULL;
		int i = 0;

		list_for_each_entry(rif, &dev->iflist, list) {
			i1905_dbg(LOG_DM, "Interface " MACFMT ", num-links = %d\n",
				  MAC2STR(rif->macaddr), rif->num_links);
			i1905_remote_link_free_invalid(rif);
			i += rif->num_links;
		}
		dev->num_neighbor_1905 = i;
		i1905_dbg(LOG_DM, "%s: 1905-Device "MACFMT" has %d links\n",
			  __func__, MAC2STR(aladdr), dev->num_neighbor_1905);
	} else {
		i1905_warn(LOG_DM, "%s: 1905-Device "MACFMT" not found in topology!\n",
			   __func__, MAC2STR(aladdr));
	}
}

void i1905_remote_link_update(struct i1905_topology *topo, struct i1905_neighbor_interface *link)
{
	struct i1905_interface *rif = NULL;
	struct i1905_device *dev = NULL;

	list_for_each_entry(dev, &topo->devlist, list) {
		struct i1905_neighbor_interface *rlink;

		if (hwaddr_equal(dev->aladdr, link->iface->aladdr))
			continue;

		list_for_each_entry(rif, &dev->iflist, list) {
			if (rif->num_links == 0)
				continue;

			list_for_each_entry(rlink, &rif->nbriflist, list) {
				if (hwaddr_equal(rlink->aladdr, link->iface->aladdr)) {
					memcpy(link->r_macaddr, rlink->macaddr, 6);
					return;
				}
			}
		}
	}
}

void i1905_dm_neighbor_interface_free(struct i1905_interface *iface)
{
	if (!iface)
		return;

	if (iface->mediainfo)
		free(iface->mediainfo);

	i1905_free_interface_links(iface);
	list_flush(&iface->vendorlist, struct i1905_vendor_info, list);
	list_flush(&iface->non1905_nbrlist, struct i1905_net_non1905_neighbor, list);

	free(iface);
}

void i1905_dm_neighbor_free(struct i1905_device *rdev)
{
	struct i1905_interface *iface = NULL, *tmp;
	struct i1905_private *priv;

	if (!rdev)
		return;

	priv = i1905_selfdevice_to_context(rdev->dev);
	i1905_extmodules_notify(priv, IEEE1905_NBR_REMOVED, rdev->aladdr);

	list_flush(&rdev->ipv4list, struct i1905_ipv4, list);
	list_flush(&rdev->ipv6list, struct i1905_ipv6, list);
	list_flush(&rdev->vendorlist, struct i1905_vendor_info, list);
	//list_flush(&rdev->l2_nbrlist, struct i1905_l2_neighbor, list);
	list_flush(&rdev->brlist, struct i1905_bridge_tuple, list);

	list_for_each_entry_safe(iface, tmp, &rdev->iflist, list) {
		list_del(&iface->list);
		rdev->num_interface--;
		i1905_dm_neighbor_interface_free(iface);
	}

	free(rdev);
}

void i1905_dm_neighbor_remove(struct i1905_device *rdev)
{
	struct i1905_selfdevice *self = rdev->dev;
	int i;

	if (timer_pending(&rdev->immediate_nbr_agetimer))
		timer_del(&rdev->immediate_nbr_agetimer);

	if (timer_pending(&rdev->agetimer))
		timer_del(&rdev->agetimer);

	list_del(&rdev->list);
	self->topology.num_devices--;

	/* clear netregistrar */
	for (i = 0; i < IEEE80211_FREQUENCY_BAND_NUM; i++) {
		if (hwaddr_equal(self->netregistrar[i], rdev->aladdr))
			memset(self->netregistrar[i], 0, 6);
	}

	dbg("free neighbor device " MACFMT "\n", MAC2STR(rdev->aladdr));
	i1905_dm_neighbor_free(rdev);
}

static void i1905_dm_neighbor_agetimer_cb(atimer_t *t)
{
	struct i1905_device *rdev = container_of(t, struct i1905_device, agetimer);
	struct i1905_selfdevice *self = rdev->dev;
	struct i1905_neighbor_interface *link;
	struct i1905_private *priv;
	uint8_t aladdr[6] = {0};

	priv = i1905_selfdevice_to_context(self);
	memcpy(aladdr, rdev->aladdr, 6);
	link = i1905_find_neighbor_link(self, aladdr);
	i1905_dm_neighbor_remove(rdev);

	if (link && link->iface) {
		i1905_send_topology_notification(priv, link->iface->ifname);
		i1905_extmodules_notify(priv, IEEE1905_TOPOLOGY_CHANGED,
					link->iface->ifname, 0, 1, aladdr);
	}
}

void i1905_free_interface_neighbors(struct i1905_interface *iface)
{
	struct i1905_selfdevice *self = iface->device;
	struct i1905_neighbor_interface *link = NULL;
	struct i1905_private *priv;

	priv = i1905_selfdevice_to_context(self);
	list_for_each_entry(link, &iface->nbriflist, list) {
		struct i1905_device *rdev = NULL;

		rdev = i1905_dm_neighbor_lookup(iface, link->aladdr);
		if (rdev) {
			uint8_t aladdr[6] = {0};

			memcpy(aladdr, rdev->aladdr, 6);
			i1905_dm_neighbor_remove(rdev);
			i1905_send_topology_notification(priv, iface->ifname);

			i1905_extmodules_notify(priv, IEEE1905_TOPOLOGY_CHANGED,
						iface->ifname, 0, 1, aladdr);
		}
	}
}

static void i1905_dm_immediate_neighbor_agetimer_cb(atimer_t *t)
{
	struct i1905_device *rdev = container_of(t, struct i1905_device, immediate_nbr_agetimer);
	struct i1905_neighbor_interface *link = NULL;
	struct i1905_selfdevice *self = rdev->dev;
	struct i1905_interface *iface = NULL;

	dbg("invalidate immediate neighbor " MACFMT "\n", MAC2STR(rdev->aladdr));
	rdev->is_immediate_neighbor = 0;

	list_for_each_entry(iface, &self->iflist, list) {
		list_for_each_entry(link, &iface->nbriflist, list) {
			if (hwaddr_equal(link->aladdr, rdev->aladdr)) {
				if (link->direct)
					link->direct = false;
			}
		}
	}
}

struct i1905_device *i1905_dm_neighbor_create(void)
{
	struct i1905_device *rdev;

	rdev = calloc(1, sizeof(*rdev));
	if (!rdev) {
		err("-ENOMEM\n");
		return NULL;
	}

	INIT_LIST_HEAD(&rdev->ipv4list);
	INIT_LIST_HEAD(&rdev->ipv6list);
	INIT_LIST_HEAD(&rdev->vendorlist);
	INIT_LIST_HEAD(&rdev->iflist);
	//INIT_LIST_HEAD(&rdev->non1905_nbrlist);
	INIT_LIST_HEAD(&rdev->l2_nbrlist);
	INIT_LIST_HEAD(&rdev->brlist);
	//INIT_LIST_HEAD(&rdev->reglist);

	timer_init(&rdev->agetimer, i1905_dm_neighbor_agetimer_cb);
	timer_set(&rdev->agetimer, I1905_NEIGHBOR_AGEOUT);
	timer_init(&rdev->immediate_nbr_agetimer, i1905_dm_immediate_neighbor_agetimer_cb);

	return rdev;
}

#if 0
struct i1905_neighbor_interface *i1905_dm_link_lookup(struct i1905_interface *rif,
							      uint8_t *nbr_nbr_aladdr)
{
	struct i1905_neighbor_interface *nif = NULL;


	list_for_each_entry(nif, &rif->nbriflist, list) {
		if (hwaddr_equal(nif->aladdr, nbr_nbr_aladdr))
			return nif;
	}

	return NULL;
}
#endif


struct i1905_interface *i1905_dm_neighbor_interface_lookup(struct i1905_device *rdev,
							   uint8_t *ifmacaddr)
{
	struct i1905_interface *rif = NULL;


	list_for_each_entry(rif, &rdev->iflist, list) {
		if (hwaddr_equal(rif->macaddr, ifmacaddr))
			return rif;
	}

	return NULL;
}

void i1905_dm_neighbor_invalidate_all_interface(struct i1905_device *rdev)
{
	struct i1905_interface *rif = NULL;

	list_for_each_entry(rif, &rdev->iflist, list) {
		rif->invalid = true;
	}
}

void i1905_dm_neighbor_free_invalid_interfaces(struct i1905_device *rdev)
{
	struct i1905_interface *rif = NULL, *tmp;

	if (!rdev)
		return;

	list_for_each_entry_safe(rif, tmp, &rdev->iflist, list) {
		if (rif->invalid) {
			list_del(&rif->list);
			rdev->num_interface--;
			i1905_dm_neighbor_interface_free(rif);
		}
	}
}

int i1905_dm_neighbor_update_non1905_neighbors(struct i1905_interface *iface,
					       uint8_t *aladdr)
{
	struct i1905_device *rdev = NULL;


	rdev = i1905_dm_neighbor_lookup(iface, aladdr);
	if (!rdev)
		return -1;

	if (!rdev->num_neighbor_non1905)
		return 0;

	if (rdev->non1905_macaddrs) {
		free(rdev->non1905_macaddrs);
		rdev->non1905_macaddrs = NULL;
	}

	rdev->non1905_macaddrs = calloc(rdev->num_neighbor_non1905,  6 * sizeof(uint8_t));
	if (!rdev->non1905_macaddrs)
		return -1;

	return i1905_dm_neighbor_get_non1905_neighbors(rdev, rdev->non1905_macaddrs);
}

int i1905_dm_neighbor_update_non1905_nbrlist(struct i1905_device *rdev, struct tlv *t)
{
	struct tlv_non1905_neighbor *non1905_nbr =
				(struct tlv_non1905_neighbor *)t->data;
	struct i1905_interface *rif;
	int num;
	int j;


	rif = i1905_dm_neighbor_interface_lookup(rdev, non1905_nbr->local_macaddr);
	if (!rif) {
		rif = i1905_dm_neighbor_interface_create();
		if (!rif) {
			err("-ENOMEM\n");
			return -1;
		}

		memcpy(rif->macaddr, non1905_nbr->local_macaddr, 6);
		memcpy(rif->aladdr, rdev->aladdr, 6);
		rif->device = rdev;
		rif->priv = NULL;
		list_add_tail(&rif->list, &rdev->iflist);
		rdev->num_interface++;
	}

	num = (tlv_length(t) - 6) / 6;

	for (j = 0; j < num; j++) {
		struct i1905_net_non1905_neighbor *rdev_xnbr = NULL;

		rdev_xnbr = i1905_dm_neighbor_non1905_nbr_create();
		if (rdev_xnbr) {
			memcpy(rdev_xnbr->macaddr, non1905_nbr->non1905_nbr[j].macaddr, 6);
			list_add_tail(&rdev_xnbr->list, &rif->non1905_nbrlist);
			rif->num_neighbor_non1905++;
			rdev->num_neighbor_non1905++;
		}
	}

	return 0;
}

int i1905_dm_neighbor_update_nbrlist(struct i1905_interface *iface,
				     struct i1905_device *rdev, struct tlv *t)
{
	struct i1905_selfdevice *self = (struct i1905_selfdevice *)iface->device;
	struct tlv_1905neighbor *nbr = (struct tlv_1905neighbor *)t->data;
	struct i1905_interface *rif;
	uint16_t remlen = tlv_length(t);
	int j;


	rif = i1905_dm_neighbor_interface_lookup(rdev, nbr->local_macaddr);
	if (!rif) {
		rif = i1905_dm_neighbor_interface_create();
		if (!rif) {
			err("-ENOMEM\n");
			return -1;
		}

		memcpy(rif->macaddr, nbr->local_macaddr, 6);
		memcpy(rif->aladdr, rdev->aladdr, 6);
		rif->device = rdev;
		rif->priv = NULL;
		list_add_tail(&rif->list, &rdev->iflist);
		rdev->num_interface++;
	}

	remlen -= 6;	/* local_macaddr */
	j = 0;

	while (remlen >= sizeof(struct i1905_neighbor)) {
		struct i1905_neighbor_interface *nif = NULL;


		nif = i1905_remote_invalid_link_lookup(rif, nbr->local_macaddr, nbr->nbr[j].aladdr);
		if (nif) {
			nif->invalid = false;
			prlink("Valid link ", nif->macaddr, rif->aladdr, nif->r_macaddr, nif->aladdr);
		} else {
			nif = i1905_remote_link_create(rif);
			if (!nif) {
				err("-ENOMEM\n");
				return -1;
			}
			memcpy(nif->aladdr, nbr->nbr[j].aladdr, 6);
			/* NOTE: 'r_macaddr' = 0.
			 * In i1905_remote_link_update(), r_macaddr may get set with
			 * valid remote interface macaddr.
			 */
			list_add_tail(&nif->list, &rif->nbriflist);
			rif->num_links++;
		}

		nif->has_bridge = !!(nbr->nbr[j].has_bridge & 0x80);

		/* update link's r_macaddr based on other 1905-devices' nbrlist */
		i1905_remote_link_update(&self->topology, nif);

		/* create neighbor-neighbor device if not known */
		if (!hwaddr_equal(nif->aladdr, self->aladdr)) {
			struct i1905_device *rrdev = NULL;

			rrdev = i1905_dm_neighbor_lookup(iface, nif->aladdr);
			if (!rrdev) {
				rrdev = i1905_dm_neighbor_create();
				if (rrdev) {
					memcpy(rrdev->aladdr, nif->aladdr, 6);
					rrdev->tsp = time(NULL);
					rrdev->dev = self;
					list_add_tail(&rrdev->list, &self->topology.devlist);
					self->topology.num_devices++;
				}
			}
		}

		remlen -= sizeof(struct i1905_neighbor);
		j++;
	}

	/* Do not delete invalid links yet, as subsequent nbrlist update for the
	 * same topology-response cmdu can come with the same interface macaddr
	 * as this one.
	 * Only after seeing the all the tlv_1905neighbor entries in a topology-
	 * response can we infer if a link has become invalid or not.
	 */
#if 0
	/* delete links that are no longer valid through 'rif' */
	i1905_free_invalid_links(rif);
#endif

	return 0;
}

int i1905_is_tlv_device_bridge_caps_valid(struct tlv *t)
{
	struct tlv_device_bridge_caps *brcaps = (struct tlv_device_bridge_caps *)t->data;
	int remlen = (int)tlv_length(t) - sizeof(struct tlv_device_bridge_caps);
	struct device_bridge_tuple *tuple = NULL;
	uint8_t *ptr = t->data;
	int offset = 0;
	int i;


	if (brcaps->num_tuples > 0 && remlen >= sizeof(struct device_bridge_tuple))
		ptr += sizeof(struct tlv_device_bridge_caps);

	for (i = 0; i < brcaps->num_tuples; i++) {
		if (remlen < sizeof(struct device_bridge_tuple))
			return 0;

		remlen -= sizeof(struct device_bridge_tuple);
		ptr += offset;
		tuple = (struct device_bridge_tuple *)ptr;
		if (remlen < tuple->num_macaddrs * sizeof(struct device_bridge_tuple_macaddr))
			return 0;

		remlen -= tuple->num_macaddrs * sizeof(struct device_bridge_tuple_macaddr);
		offset = sizeof(tuple->num_macaddrs) +
			tuple->num_macaddrs * sizeof(struct device_bridge_tuple_macaddr);
	}

	return remlen ? 0 : 1;
}

int i1905_dm_neighbor_update_brlist(struct i1905_device *rdev, struct tlv *t)
{
	struct tlv_device_bridge_caps *brcaps =
				(struct tlv_device_bridge_caps *)t->data;
	//struct i1905_bridge_tuple *bp, *tmp;
	int i;

	if (!i1905_is_tlv_device_bridge_caps_valid(t)) {
		dbg("Invalid tlv_bridge_caps; discard CMDU\n");
		return -1;
	}

	/* replace old brlist with this one.
	 * tuple->macaddrs freed automatically because it is contiguous.
	 */
	list_flush(&rdev->brlist, struct i1905_bridge_tuple, list);
	rdev->num_brtuple = 0;

#if 0
	list_for_each_entry_safe(bp, tmp, &rdev->brlist, list) {
		list_del(&bp->list);
		if (bp->num_macs) {
			free(bp->macaddrs);
		}
		free(bp);
	}
#endif

	for (i = 0; i < brcaps->num_tuples; i++) {
		struct i1905_bridge_tuple *br = NULL;
		int num_macaddrs = brcaps->tuple[i].num_macaddrs;

		if (num_macaddrs == 0)
			continue;

		br = i1905_dm_neighbor_brtuple_create(num_macaddrs);
		if (!br) {
			err("-ENOMEM\n");
			return -1;
		}

		br->num_macs = num_macaddrs;
		memcpy(br->macaddrs, brcaps->tuple[i].addr, 6 * num_macaddrs);
		list_add_tail(&br->list, &rdev->brlist);
		rdev->num_brtuple++;
	}

	return 0;
}

int i1905_is_tlv_device_info_valid(struct tlv *t)
{
	struct tlv_device_info *devinfo =
				(struct tlv_device_info *)t->data;
	struct local_interface *tif = NULL;
	int remlen = (int)tlv_length(t) - sizeof(struct tlv_device_info);
	uint8_t *ptr = t->data;
	int offset = 0;
	int i;


	if (devinfo->num_interface > 0 && remlen >= sizeof(struct local_interface))
		ptr += sizeof(struct tlv_device_info);

	for (i = 0; i < devinfo->num_interface; i++) {
		if (remlen < sizeof(struct local_interface))
			return 0;

		remlen -= sizeof(struct local_interface);
		ptr += offset;
		tif = (struct local_interface *)ptr;
		if (remlen < tif->sizeof_mediainfo)
			return 0;

		if (tif->sizeof_mediainfo > 0) {
			uint16_t tif_media = buf_get_be16((uint8_t *)&tif->mediatype);

			if (tif_media == MEDIA_TYPE_IEEE_802_3AB_GIGABIT_ETHERNET ||
			    tif_media == MEDIA_TYPE_IEEE_802_3AB_GIGABIT_ETHERNET)
				return 0;
		}

		remlen -= tif->sizeof_mediainfo;
		offset = sizeof(struct local_interface) + tif->sizeof_mediainfo;
	}

	return remlen ? 0 : 1;
}

int i1905_dm_neighbor_update_devinfo(struct i1905_private *priv,
				     struct i1905_device *rdev, struct tlv *t)
{
	struct tlv_device_info *devinfo =
				(struct tlv_device_info *)t->data;
	int offset = sizeof(struct tlv_device_info);
	uint8_t *ptr = t->data;
	int i;


	dbg("%s: Node " MACFMT " reports %d interfaces in its latest Topology Response\n",
	    __func__, MAC2STR(rdev->aladdr), devinfo->num_interface);
	dbg("%s: Node " MACFMT " last reported %d interfaces\n",
	    __func__, MAC2STR(rdev->aladdr), rdev->num_interface);

	if (!i1905_is_tlv_device_info_valid(t)) {
		dbg("Invalid tlv_device_info; discard CMDU\n");
		return -1;
	}

	i1905_dm_neighbor_invalidate_all_interface(rdev);

	for (i = 0; i < devinfo->num_interface; i++) {
		struct local_interface *tif;
		struct i1905_interface *rif;

		ptr += offset;
		tif = (struct local_interface *)ptr;

		rif = i1905_dm_neighbor_interface_lookup(rdev, tif->macaddr);
		if (!rif) {
			rif = i1905_dm_neighbor_interface_create();
			if (!rif) {
				err("-ENOMEM\n");
				return -1;
			}

			rif->invalid = false;
			memcpy(rif->macaddr, tif->macaddr, 6);
			memcpy(rif->aladdr, rdev->aladdr, 6);
			rif->device = rdev;
			rif->priv = NULL;
			rif->media = buf_get_be16((uint8_t *)&tif->mediatype);
			if (tif->sizeof_mediainfo > 0) {
				rif->mediainfo = calloc(1, tif->sizeof_mediainfo);
				if (!rif->mediainfo) {
					err("-ENOMEM\n");
					return -1;
				}

				memcpy(rif->mediainfo, tif->mediainfo,
							tif->sizeof_mediainfo);
			}

			list_add_tail(&rif->list, &rdev->iflist);
			rdev->num_interface++;
		} else {
			rif->invalid = false;
			memcpy(rif->aladdr, rdev->aladdr, 6);
			rif->media = buf_get_be16((uint8_t *)&tif->mediatype);
			if (tif->sizeof_mediainfo > 0) {
				if (rif->mediainfo)
					free(rif->mediainfo);

				rif->mediainfo = calloc(1, tif->sizeof_mediainfo);
				if (!rif->mediainfo) {
					err("-ENOMEM\n");
					return -1;
				}

				memcpy(rif->mediainfo, tif->mediainfo,
							tif->sizeof_mediainfo);
			}
		}

		neigh_set_1905_slave(&priv->neigh_q, rif->macaddr);
		offset = sizeof(struct local_interface) + tif->sizeof_mediainfo;
	}

	dbg("%s: Num interfaces stored from Topo Response = %d\n",
	    __func__, rdev->num_interface);

	i1905_dm_neighbor_free_invalid_interfaces(rdev);

	return 0;
}

int i1905_dm_neighbor_update_link_metric(struct i1905_device *rdev, struct tlv *t)
{
	struct tlv_tx_linkmetric *txl = NULL;
	struct tlv_rx_linkmetric *rxl = NULL;
	uint16_t remlen = tlv_length(t);
	struct tx_link_info *txlinfo;
	struct rx_link_info *rxlinfo;
	struct i1905_interface *rif;
	uint16_t remlen_min = 0;
	uint8_t *rif_macaddr = NULL;
	int j = 0;


	if (remlen < 12)
		return 0;

	remlen -= 12;	/* sender's 'aladdr' and its 'neighbor_aladdr' */
	if (remlen < 6)	/* i.e. sizeof(local_macaddr) */
		return 0;

	if (t->type == TLV_TYPE_TRANSMITTER_LINK_METRIC) {
		txl = (struct tlv_tx_linkmetric *)t->data;
		txlinfo = (struct tx_link_info *)&txl->link[0];
		rif_macaddr = txlinfo->local_macaddr;
		remlen_min = sizeof(*txlinfo);
	} else if (t->type == TLV_TYPE_RECEIVER_LINK_METRIC) {
		rxl = (struct tlv_rx_linkmetric *)t->data;
		rxlinfo = (struct rx_link_info *)&rxl->link[0];
		rif_macaddr = rxlinfo->local_macaddr;
		remlen_min = sizeof(*rxlinfo);
	} else
		return -1;


	rif = i1905_dm_neighbor_interface_lookup(rdev, rif_macaddr);
	if (!rif) {
		rif = i1905_dm_neighbor_interface_create();
		if (!rif) {
			err("-ENOMEM\n");
			return -1;
		}

		memcpy(rif->macaddr, rif_macaddr, 6);
		memcpy(rif->aladdr, rdev->aladdr, 6);
		rif->device = rdev;
		rif->priv = NULL;
		list_add_tail(&rif->list, &rdev->iflist);
		rdev->num_interface++;
	}

#if 0	// invalidate links only from topology resonse
	/* invalidate current links through 'rif' before updating below */
	i1905_invalidate_links(rif);
#endif

	while (remlen >= remlen_min) {
		struct i1905_neighbor_interface *nif;
		int newlink = 0;

		if (t->type == TLV_TYPE_TRANSMITTER_LINK_METRIC && txl) {
			txlinfo = (struct tx_link_info *)&txl->link[j];


			nif = i1905_remote_link_lookup(rif, txlinfo->local_macaddr, txl->neighbor_aladdr);
			if (nif) {
				memcpy(nif->r_macaddr, txlinfo->neighbor_macaddr, 6);
				nif->invalid = false;
			} else {
				nif = i1905_remote_link_create(rif);
				if (!nif) {
					err("-ENOMEM\n");
					return -1;
				}
				newlink = 1;
				memcpy(nif->r_macaddr, txlinfo->neighbor_macaddr, 6);
				memcpy(nif->aladdr, txl->neighbor_aladdr, 6);
				i1905_dbg(LOG_DM, "%s: Created new link from interface "MACFMT"\n",
					  __func__, MAC2STR(rif->macaddr));
			}

			BUF_PUT_BE16(nif->media, txlinfo->mediatype);
			nif->metric.br_present = !!(txlinfo->has_bridge == 0x01);
			BUF_PUT_BE32(nif->metric.tx_errors, txlinfo->errors);
			BUF_PUT_BE32(nif->metric.tx_packets, txlinfo->packets);
			BUF_PUT_BE16(nif->metric.available, txlinfo->availability);
			BUF_PUT_BE16(nif->metric.max_rate, txlinfo->max_throughput);
			BUF_PUT_BE16(nif->metric.max_phyrate, txlinfo->phyrate);

			remlen -= sizeof(struct tx_link_info);
		} else if (t->type == TLV_TYPE_RECEIVER_LINK_METRIC && rxl) {
			rxlinfo = (struct rx_link_info *)&rxl->link[j];


			nif = i1905_remote_link_lookup(rif, rxlinfo->local_macaddr, rxl->neighbor_aladdr);
			if (nif) {
				memcpy(nif->r_macaddr, rxlinfo->neighbor_macaddr, 6);
				nif->invalid = false;
			} else {
				nif = i1905_remote_link_create(rif);
				if (!nif) {
					err("-ENOMEM\n");
					return -1;
				}
				newlink = 1;
				memcpy(nif->r_macaddr, rxlinfo->neighbor_macaddr, 6);
				memcpy(nif->aladdr, rxl->neighbor_aladdr, 6);
				i1905_dbg(LOG_DM, "%s: Created new link from interface "MACFMT"\n",
					  __func__, MAC2STR(rif->macaddr));
			}

			BUF_PUT_BE16(nif->media, rxlinfo->mediatype);
			BUF_PUT_BE32(nif->metric.rx_errors, rxlinfo->errors);
			BUF_PUT_BE32(nif->metric.rx_packets, rxlinfo->packets);
			nif->metric.rssi = rxlinfo->rssi;

			remlen -= sizeof(struct rx_link_info);
		}

		if (newlink) {
			list_add_tail(&nif->list, &rif->nbriflist);
			rif->num_links++;
		}

		j++;
	}

#if 0
	/* delete links that are no longer valid through 'rif' */
	i1905_free_invalid_links(rif);
#endif

	return 0;
}

int i1905_dm_neighbor_update_device_ident(struct i1905_device *rdev, struct tlv *t)
{
	struct tlv_device_identification *ident =
				(struct tlv_device_identification *)t->data;


	memset(rdev->name, 0, sizeof(rdev->name));
	memcpy(rdev->name, ident->name, 64);

	memset(rdev->manufacturer, 0, sizeof(rdev->manufacturer));
	memcpy(rdev->manufacturer, ident->manufacturer, 64);

	memset(rdev->model, 0, sizeof(rdev->model));
	memcpy(rdev->model, ident->model, 64);

	return 0;
}

int i1905_dm_neighbor_update_profile(struct i1905_device *rdev, struct tlv *t)
{
	struct tlv_1905_profile *p =
				(struct tlv_1905_profile *)t->data;


	if (p->version == PROFILE_1905_1)
		rdev->version = I1905_VERSION_DOT_1;
	else if (p->version == PROFILE_1905_1A)
		rdev->version = I1905_VERSION_DOT_1A;

	return 0;
}

int i1905_dm_neighbor_update_control_url(struct i1905_device *rdev, struct tlv *t)
{
	struct tlv_control_url *ctrl =
				(struct tlv_control_url *)t->data;
	int url_len = tlv_length(t);
	char *url;

	if (!url_len)
		return 0;

	if (rdev->url)
		free(rdev->url);

	url = calloc(tlv_length(t) + 1, sizeof(char));
	if (url)
		memcpy(url, ctrl->url, url_len);

	rdev->url = url;

	return 0;
}

int i1905_dm_neighbor_update_ipv4(struct i1905_device *rdev, struct tlv *t)
{
	struct i1905_private *priv = i1905_selfdevice_to_context(rdev->dev);
	struct tlv_ipv4 *ip = (struct tlv_ipv4 *)t->data;
	int offset = sizeof(struct tlv_ipv4);
	uint8_t *ptr = t->data;
	struct i1905_ipv4 *ip4;
	int i, j;


	list_flush(&rdev->ipv4list, struct i1905_ipv4, list);
	rdev->num_ipv4 = 0;

	for (i = 0; i < ip->num_interfaces; i++) {
		struct ipv4_interface *tif;

		ptr += offset;
		tif = (struct ipv4_interface *)ptr;

		if (!hwaddr_equal(rdev->aladdr, tif->macaddr)) {
			struct i1905_interface *rif;

			rif = i1905_dm_neighbor_interface_lookup(rdev, tif->macaddr);
			if (!rif) {
				rif = i1905_dm_neighbor_interface_create();
				if (!rif) {
					err("-ENOMEM\n");
					return -1;
				}

				memcpy(rif->macaddr, tif->macaddr, 6);
				memcpy(rif->aladdr, rdev->aladdr, 6);
				rif->device = rdev;
				rif->priv = NULL;
				list_add_tail(&rif->list, &rdev->iflist);
				rdev->num_interface++;
			}
			//XXX: store ipaddress per interface?
		}

		neigh_set_1905_slave(&priv->neigh_q, tif->macaddr);
		offset = sizeof(struct ipv4_interface);

		for (j = 0; j < tif->num_ipv4; j++) {
			struct ipv4_entry *e;

			ptr += offset;
			e = (struct ipv4_entry *)ptr;

			ip4 = i1905_dm_ipv4_create();
			if (!ip4) {
				err("-ENOMEM\n");
				return -ENOMEM;
			}

			memcpy(ip4->macaddr, tif->macaddr, 6);
			memcpy(&ip4->addr, e->address, sizeof(struct in_addr));
			ip4->type = e->type;
			memcpy(&ip4->dhcpserver, e->dhcpserver, sizeof(struct in_addr));
			list_add_tail(&ip4->list, &rdev->ipv4list);
			rdev->num_ipv4++;

			offset = sizeof(struct ipv4_entry);
		}
	}

	return 0;
}

int i1905_dm_neighbor_update_ipv6(struct i1905_device *rdev, struct tlv *t)
{
	struct i1905_private *priv = i1905_selfdevice_to_context(rdev->dev);
	struct tlv_ipv6 *ip = (struct tlv_ipv6 *)t->data;
	int offset = sizeof(struct tlv_ipv6);
	uint8_t *ptr = t->data;
	struct i1905_ipv6 *ip6;
	int i, j;


	list_flush(&rdev->ipv6list, struct i1905_ipv6, list);
	rdev->num_ipv6 = 0;

	for (i = 0; i < ip->num_interfaces; i++) {
		struct ipv6_interface *tif;

		ptr += offset;
		tif = (struct ipv6_interface *)ptr;

		if (!hwaddr_equal(rdev->aladdr, tif->macaddr)) {
			struct i1905_interface *rif;


			rif = i1905_dm_neighbor_interface_lookup(rdev, tif->macaddr);
			if (!rif) {
				rif = i1905_dm_neighbor_interface_create();
				if (!rif) {
					err("-ENOMEM\n");
					return -1;
				}

				memcpy(rif->macaddr, tif->macaddr, 6);
				memcpy(rif->aladdr, rdev->aladdr, 6);
				rif->device = rdev;
				rif->priv = NULL;
				list_add_tail(&rif->list, &rdev->iflist);
				rdev->num_interface++;
			}
			//XXX: store ipaddress per interface?
		}

		neigh_set_1905_slave(&priv->neigh_q, tif->macaddr);
		offset = sizeof(struct ipv6_interface);

		for (j = 0; j < tif->num_ipv6; j++) {
			struct ipv6_entry *e;

			ptr += offset;
			e = (struct ipv6_entry *)ptr;

			ip6 = i1905_dm_ipv6_create();
			if (!ip6) {
				err("-ENOMEM\n");
				return -ENOMEM;
			}

			memcpy(ip6->macaddr, tif->macaddr, 6);
			//TODO: linklocal address
			memcpy(&ip6->addr, e->address, sizeof(struct in6_addr));
			ip6->type = e->type;
			memcpy(&ip6->origin, e->origin, sizeof(struct in6_addr));
			list_add_tail(&ip6->list, &rdev->ipv6list);
			rdev->num_ipv6++;

			offset = sizeof(struct ipv6_entry);
		}
	}

	return 0;
}

int i1905_dm_neighbor_update(struct i1905_interface *iface, uint8_t *aladdr,
			     struct tlv *t)
{
	struct i1905_interface_private *ifpriv = iface->priv;
	struct i1905_private *priv = ifpriv ? ifpriv->i1905private : NULL;
	struct i1905_device *rdev = NULL;
	int ret = 0;


	if (!priv) {
		warn("%s: i1905 private context is NULL\n", __func__);
		return -1;
	}

	rdev = i1905_dm_neighbor_lookup(iface, aladdr);
	if (!rdev) {
		i1905_dbg(LOG_DM, "ALERT! received CMDU from unknown device\n");
		return -99;
	}

	rdev->upstream = iface->upstream ? true : false;
	i1905_neighbor_reset_ageout(rdev);

	switch (t->type) {
	case TLV_TYPE_DEVICE_INFORMATION_TYPE:
		ret = i1905_dm_neighbor_update_devinfo(priv, rdev, t);
		break;
	case TLV_TYPE_DEVICE_BRIDGING_CAPABILITIES:
		ret = i1905_dm_neighbor_update_brlist(rdev, t);
		break;
	case TLV_TYPE_NON_1905_NEIGHBOR_DEVICE_LIST:
		ret = i1905_dm_neighbor_update_non1905_nbrlist(rdev, t);
		break;
	case TLV_TYPE_NEIGHBOR_DEVICE_LIST:
		ret = i1905_dm_neighbor_update_nbrlist(iface, rdev, t);
		break;
	case TLV_TYPE_POWER_OFF_INTERFACE:
		break;
	case TLV_TYPE_L2_NEIGHBOR_DEVICE:
		break;
	case TLV_TYPE_TRANSMITTER_LINK_METRIC:
	case TLV_TYPE_RECEIVER_LINK_METRIC:
		ret = i1905_dm_neighbor_update_link_metric(rdev, t);
		break;
	case TLV_TYPE_DEVICE_IDENTIFICATION:
		ret = i1905_dm_neighbor_update_device_ident(rdev, t);
		break;
	case TLV_TYPE_1905_PROFILE_VERSION:
		ret = i1905_dm_neighbor_update_profile(rdev, t);
		break;
	case TLV_TYPE_CONTROL_URL:
		ret = i1905_dm_neighbor_update_control_url(rdev, t);
		break;
	case TLV_TYPE_IPV4:
		ret = i1905_dm_neighbor_update_ipv4(rdev, t);
		break;
	case TLV_TYPE_IPV6:
		ret = i1905_dm_neighbor_update_ipv6(rdev, t);
		break;
	default:
		i1905_dbg(LOG_CMDU, "%s: Unhandled TLV 0x%02X\n", __func__, t->type);
		break;
	}

	return ret;
}

int i1905_dm_neighbor_discovered(struct i1905_interface *iface, uint8_t *aladdr,
				 uint8_t *macaddr, uint16_t cmdu_type)
{
	struct i1905_selfdevice *self = (struct i1905_selfdevice *)iface->device;
	struct i1905_neighbor_interface *if_peer;
	struct i1905_interface *rif = NULL;
	struct i1905_device *rdev = NULL;
	bool newlink = false;
	bool newdev = false;
	bool newnbr = false;



	if (!macaddr || hwaddr_is_zero(macaddr))
		return -1;

	rdev = i1905_dm_neighbor_lookup(iface, aladdr);
	if (!rdev) {
		rdev = i1905_dm_neighbor_create();
		if (!rdev) {
			i1905_dbg(LOG_DM, "%s: failed to create nbr-device\n", __func__);
			return -1;
		}

		memcpy(rdev->aladdr, aladdr, 6);
		list_add_tail(&rdev->list, &self->topology.devlist);
		self->topology.num_devices++;
		rdev->dev = self;
		i1905_dbg(LOG_DM, "%s: New neighbor " MACFMT " discovered\n",
			  __func__, MAC2STR(aladdr));
		newdev = true;
	}

	rdev->tsp = time(NULL);
	i1905_neighbor_reset_ageout(rdev);

	if (hwaddr_equal(aladdr, macaddr)) {
		/* check if _any_ link to this nbr exists or not */
		if_peer = i1905_link_lookup_partial(iface, aladdr, aladdr);
		if (!if_peer) {
			if_peer = i1905_link_create(iface);
			if (if_peer) {
				newlink = true;
				//NOTE: r_macaddr = 0
				memcpy(if_peer->aladdr, aladdr, 6);
				list_add_tail(&if_peer->list, &iface->nbriflist);
				iface->num_links++;
				prlink("Created partial link", if_peer->macaddr, iface->aladdr,
				       if_peer->r_macaddr, if_peer->aladdr);
			}
		}
	} else {
		rif = i1905_dm_neighbor_interface_lookup(rdev, macaddr);
		if (rif) {
			/* lookup {l_macaddr --> r_aladdr} and complete r_macaddr if found */
			if_peer = i1905_link_lookup(iface, iface->macaddr, aladdr);
			if (if_peer) {
				memcpy(if_peer->r_macaddr, macaddr, 6);
			} else {
				if_peer = i1905_link_create(iface);
				if (if_peer) {
					newlink = true;
					memcpy(if_peer->r_macaddr, macaddr, 6);
					memcpy(if_peer->aladdr, aladdr, 6);
					list_add_tail(&if_peer->list, &iface->nbriflist);
					iface->num_links++;
					prlink("Created link", if_peer->macaddr, iface->aladdr,
					       if_peer->r_macaddr, if_peer->aladdr);
				}
			}
		} else {
			/* add this nbr interface */
			rif = i1905_dm_neighbor_interface_create();
			if (!rif) {
				i1905_err(LOG_DM, "%s: failed to create nbr-iface\n", __func__);
				return -1;
			}

			memcpy(rif->macaddr, macaddr, 6);
			memcpy(rif->aladdr, aladdr, 6);
			rif->device = rdev;
			rif->priv = NULL;
			list_add_tail(&rif->list, &rdev->iflist);
			rdev->num_interface++;

			/* create link towards it */
			if_peer = i1905_link_lookup(iface, iface->macaddr, aladdr);
			if (if_peer) {
				memcpy(if_peer->r_macaddr, macaddr, 6);
			} else {
				if_peer = i1905_link_create(iface);
				if (if_peer) {
					newlink = true;
					memcpy(if_peer->r_macaddr, macaddr, 6);
					memcpy(if_peer->aladdr, aladdr, 6);
					list_add_tail(&if_peer->list, &iface->nbriflist);
					iface->num_links++;
					prlink("Created link", if_peer->macaddr, iface->aladdr,
					       if_peer->r_macaddr, if_peer->aladdr);
				}
			}
		}
	}

	if (if_peer && cmdu_type == CMDU_TYPE_TOPOLOGY_DISCOVERY) {
		if_peer->direct = true;

		if (!rdev->is_immediate_neighbor)
			newnbr = true;

		rdev->is_immediate_neighbor = 1;
		timer_set(&rdev->immediate_nbr_agetimer, I1905_IMMEDIATE_NEIGHBOR_AGEOUT);
	}

	/* send this extra discovery to help neighbors find us quicker */
	if (newdev) {
		struct i1905_interface_private *ifpriv = iface->priv;
		struct i1905_private *priv = (struct i1905_private *)ifpriv->i1905private;

		i1905_send_topology_notification(priv, iface->ifname);
		i1905_send_topology_discovery(iface);

		if (newnbr) {
			i1905_extmodules_notify(priv, IEEE1905_NBR_ADDED,
						rdev->aladdr,
						macaddr,
						iface->macaddr);

			i1905_extmodules_notify(priv, IEEE1905_TOPOLOGY_CHANGED,
						iface->ifname, 1, 1, rdev->aladdr);
		}
	}

	if (newlink) {
		struct i1905_interface_private *ifpriv = iface->priv;
		struct i1905_private *priv = (struct i1905_private *)ifpriv->i1905private;

		if (hwaddr_is_zero(if_peer->r_macaddr)) {
			i1905_extmodules_notify(priv, IEEE1905_LINK_ADDED,
						iface->aladdr,
						iface->macaddr,
						if_peer->aladdr,
						if_peer->aladdr,
						if_peer->direct);
		} else {
			i1905_extmodules_notify(priv, IEEE1905_LINK_ADDED,
						iface->aladdr,
						iface->macaddr,
						if_peer->aladdr,
						if_peer->r_macaddr,
						if_peer->direct);
		}

		i1905_send_link_metric_query(iface, aladdr);
	}

	return 0;
}

int i1905_dm_neighbor_changed(struct i1905_interface *iface, uint8_t *aladdr)
{
	struct i1905_selfdevice *self = (struct i1905_selfdevice *)iface->device;
	struct i1905_device *rdev = NULL;



	rdev = i1905_dm_neighbor_lookup(iface, aladdr);
	if (!rdev) {
		rdev = i1905_dm_neighbor_create();
		if (!rdev) {
			i1905_err(LOG_DM, "%s: failed to create nbr-device\n", __func__);
			return -1;
		}

		memcpy(rdev->aladdr, aladdr, 6);
		list_add_tail(&rdev->list, &self->topology.devlist);
		self->topology.num_devices++;
		rdev->dev = self;

		i1905_dbg(LOG_DM, "%s: New neighbor " MACFMT " discovered.\n",
			  __func__, MAC2STR(aladdr));
	}

	rdev->tsp = time(NULL);
	i1905_neighbor_reset_ageout(rdev);

	return 0;
}

int i1905_dm_init(struct i1905_dm *dm, struct i1905_config *cfg)
{
	struct i1905_selfdevice *self = &dm->self;

	memset(self, 0, sizeof(*self));

	self->enabled = true;
	self->url = NULL;
	memcpy(self->aladdr, cfg->macaddr, 6);
	strncpy(self->manufacturer, cfg->manufacturer, strlen(cfg->manufacturer));
	strncpy(self->model, cfg->model_name, strlen(cfg->model_name));
	strncpy(self->name, cfg->device_name, strlen(cfg->device_name));
	if (cfg->url)
		self->url = strdup(cfg->url);

	self->version = I1905_VERSION_DOT_1A;
	self->regband = cfg->registrar;
	self->num_interface = 0;
	INIT_LIST_HEAD(&self->iflist);

	self->num_local_interface = 0;
	INIT_LIST_HEAD(&self->local_iflist);

	self->num_master_interface = 0;
	INIT_LIST_HEAD(&self->miflist);

	self->fwd.allow = true;
	INIT_LIST_HEAD(&self->fwd.rulelist);

	self->topology.enable = 1;
	self->topology.status = 0;
	self->topology.num_devices = 0;
	INIT_LIST_HEAD(&self->topology.devlist);

	self->security.method = I1905_SECURITY_PBC;
	self->frag_scheme = cfg->frag_scheme;

	return 0;
}

int i1905_dm_free(struct i1905_dm *dm)
{
	struct i1905_selfdevice *self = &dm->self;
	struct i1905_device *rdev = NULL, *tmp;

	if (self->url) {
		free(self->url);
		self->url = NULL;
	}

	if (self->topology.num_devices == 0)
		return 0;

	list_for_each_entry_safe(rdev, tmp, &self->topology.devlist, list) {
		i1905_dm_neighbor_remove(rdev);
	}

	return 0;
}
