/*
 * neigh.c - neighbor hosts management.
 *
 * Copyright (C) 2023 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for license related information.
 *
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>

#include <libubox/list.h>
#include <libubus.h>
#include <easy/easy.h>

#include "debug.h"
#include "util.h"
#include "timer.h"
#include "neigh.h"
#include "wifi_api.h"

#include "hostmngr.h"
#include "lookup3_hash.h"

#include "arping.h"

uint32_t ipaddr_hash(struct ip_address *ip)
{
	uint32_t initval = 0xdeadbeef;
	uint32_t key;

	if (ip->family == AF_INET) {
		uint32_t arr[1] = {ip->addr.ip4.s_addr};

		key = hashword(arr, 1, initval);
	} else {
		uint32_t arr[4] = {*(uint32_t *)ip->addr.ip6.s6_addr};

		key = hashword(arr, 4, initval);
	}

	return key & (NEIGH_IP_ENTRIES_MAX - 1);
}

//TODO: move to util.c
static int getcurrtime(struct timeval *out)
{
	struct timespec nowts = { 0 };
	struct timeval now = { 0 };
	int ret;

	ret = clock_gettime(CLOCK_MONOTONIC_RAW, &nowts);
	if (!ret) {
		now.tv_sec = nowts.tv_sec;
		now.tv_usec = nowts.tv_nsec / 1000;
	} else {
		ret = gettimeofday(&now, NULL);
	}

	now.tv_usec = (now.tv_usec / 1000) * 1000;
	out->tv_sec = now.tv_sec;
	out->tv_usec = now.tv_usec;

	return ret;
}

struct neigh_history_entry *neigh_history_lookup(void *nq, uint8_t *macaddr)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct neigh_history_entry *e = NULL;
	int key = neigh_hash(macaddr);


	hlist_for_each_entry(e, &q->history[key], hlist) {
		if (!memcmp(e->macaddr, macaddr, 6)) {
			return e;
		}
	}

	return NULL;
}

static void neigh_history_entry_delete(struct neigh_history_entry *e)
{
	if (e) {
		struct hostmngr_private *priv = e->priv;
		int idx;

		dbg("Removing history entry " MACFMT"\n", MAC2STR(e->macaddr));
		idx = neigh_hash(e->macaddr);
		hlist_del(&e->hlist, &priv->neigh_q.history[idx]);
		priv->neigh_q.num_history--;
		timer_del(&e->delete_timer);

		hostlist_del_entry(priv, e->macaddr);
		free(e);
	}
}

static void neigh_history_entry_delete_timer_cb(atimer_t *t)
{
	struct neigh_history_entry *e =
		container_of(t, struct neigh_history_entry, delete_timer);

	neigh_history_entry_delete(e);
}

struct neigh_history_entry *neigh_history_entry_create(void *priv,
						       uint8_t *macaddr,
						       const char *hostname,
						       enum neigh_type type,
						       time_t firstseen,
						       time_t lastseen,
						       uint32_t timeout)
{
	struct neigh_history_entry *e;

	e = calloc(1, sizeof(*e));
	if (!e) {
		err("%s: -ENOMEM\n", __func__);
		return NULL;
	}

	memcpy(e->macaddr, macaddr, 6);
	if (type != NEIGH_TYPE_UNKNOWN)
		e->type = type;

	if (hostname && hostname[0] != '\0')
		strncpy(e->hostname, hostname, 255);

	INIT_LIST_HEAD(&e->iplist);
	e->timeout = timeout;
	e->priv = priv;
	timer_init(&e->delete_timer, neigh_history_entry_delete_timer_cb);

	if (!firstseen)
		time(&e->createtime);
	else
		e->createtime = firstseen;

	if (!lastseen)
		time(&e->lastseen);
	else
		e->lastseen = lastseen;

	if (e->timeout)
		timer_set(&e->delete_timer, e->timeout * 1000);

	dbg("CREATE history entry: " MACFMT "\n", MAC2STR(macaddr));

	return e;
}

#if 0
int neigh_history_entry_update_lastseen(void *priv, uint8_t *macaddr)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_history_entry *e = NULL;

	e = neigh_history_lookup(&p->neigh_q, macaddr);
	if (e)
		time(&e->lastseen);

	return 0;
}
#endif

int neigh_history_enqueue(void *priv, struct neigh_entry *n, uint32_t timeout)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_history_entry *he = NULL;


	he = neigh_history_lookup(&p->neigh_q, n->macaddr);
	if (he) {
		/* stop ageing history as neigh is live */
		he->timeout = timeout;
		timer_del(&he->delete_timer);
		he->alive = true;

		/* set neigh type iff wifi type determined */
		if (n->type == NEIGH_TYPE_WIFI)
			he->type = n->type;

		if (n->ipv4.family == AF_INET || n->ipv4.family == AF_INET6)
			memcpy(&he->ip, &n->ipv4, sizeof(struct ip_address));

		if (n->hostname && n->hostname[0] != '\0')
			strncpy(he->hostname, n->hostname, 255);

		if (n->ifname[0] != '\0') {
			memset(he->ifname, 0, 16);
			strncpy(he->ifname, n->ifname, 16);
		}

		he->is1905 = n->is1905;
		he->is1905_slave = n->is1905_slave;
		he->is1905_link = n->is1905_link;
		memcpy(he->aladdr, n->aladdr, 6);
		he->lastchange = n->lastchange;

		dbg("Update history stats: +ul-pkts = %ju, +dl-pkts = %ju\n",
		    (uintmax_t)n->ws.ul_packets, (uintmax_t)n->ws.dl_packets);

		he->ws.ul_packets += n->ws.ul_packets;
		he->ws.ul_bytes += n->ws.ul_bytes;
		he->ws.dl_packets += n->ws.dl_packets;
		he->ws.dl_bytes += n->ws.dl_bytes;
		return 0;
	}

	dbg("No history cache for " MACFMT " found! Add new..\n", MAC2STR(n->macaddr));

	he = neigh_history_entry_create(priv, n->macaddr, n->hostname, n->type,
					0, 0, timeout);
	if (he) {
		int idx = neigh_hash(n->macaddr);

		if (n->ipv4.family == AF_INET || n->ipv4.family == AF_INET6)
			memcpy(&he->ip, &n->ipv4, sizeof(struct ip_address));

		if (n->ifname[0] != '\0') {
			memset(he->ifname, 0, 16);
			strncpy(he->ifname, n->ifname, 16);
		}

		he->is1905 = n->is1905;
		he->is1905_slave = n->is1905_slave;
		he->is1905_link = n->is1905_link;
		memcpy(he->aladdr, n->aladdr, 6);
		he->lastchange = n->lastchange;

		/* stop ageing history as neigh is live */
		he->timeout = timeout;
		timer_del(&he->delete_timer);
		he->alive = true;
		he->ws.ul_packets = n->ws.ul_packets;
		he->ws.ul_bytes = n->ws.ul_bytes;
		he->ws.dl_packets = n->ws.dl_packets;
		he->ws.dl_bytes = n->ws.dl_bytes;

		hlist_add_head(&he->hlist, &p->neigh_q.history[idx]);
		p->neigh_q.num_history++;
		return 1;
	}

	return -1;
}

/* This function is only for loading history cache from file */
int neigh_history_entry_add(void *priv, struct neigh_history_entry *he)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_history_entry *e = NULL;
	int key = neigh_hash(he->macaddr);


	hlist_for_each_entry(e, &p->neigh_q.history[key], hlist) {
		if (!memcmp(e->macaddr, he->macaddr, 6)) {
			/* history entry already present */
			return -1;
		}
	}

	dbg("Adding history for " MACFMT "\n", MAC2STR(he->macaddr));
	e = neigh_history_entry_create(priv, he->macaddr, he->hostname,
				       he->type, he->createtime, he->lastseen,
				       he->timeout);
	if (!e)
		return -1;

	e->alive = false;	/* because no neigh entry yet for this */


	if (he->ip.family == AF_INET || he->ip.family == AF_INET6)
		memcpy(&e->ip, &he->ip, sizeof(struct ip_address));

	strncpy(e->ifname, he->ifname, 16);
	e->lastchange = he->lastchange;
	e->is1905 = he->is1905;
	e->is1905_slave = he->is1905_slave;
	e->is1905_link = he->is1905_link;
	memcpy(e->aladdr, he->aladdr, 6);

	e->ws.ul_packets = he->ws.ul_packets;
	e->ws.ul_bytes = he->ws.ul_bytes;
	e->ws.dl_packets = he->ws.dl_packets;
	e->ws.dl_bytes = he->ws.dl_bytes;

	hlist_add_head(&e->hlist, &p->neigh_q.history[key]);
	p->neigh_q.num_history++;

	return 0;
}

int neigh_history_dequeue(void *priv, uint8_t *macaddr)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_history_entry *e = NULL;


	e = neigh_history_lookup(&p->neigh_q, macaddr);
	if (!e) {
		dbg("Unexpected neigh history dequeue " MACFMT"\n", MAC2STR(macaddr));
		return -1;
	}

	neigh_history_entry_delete(e);

	return 0;
}

void neigh_history_flush(void *nq)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct neigh_history_entry *e = NULL;
	struct hlist_node *tmp;
	int idx = 0;

	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&q->history[idx]))
			continue;

		hlist_for_each_entry_safe(e, tmp, &q->history[idx], hlist)
			neigh_history_entry_delete(e);

		q->history[idx].first = NULL;
	}

	q->num_history = 0;
}

void neigh_history_free(void *nq)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;

	neigh_history_flush(q);
}

int neigh_history_entry_set_1905(void *nq, uint8_t *macaddr, uint8_t val)
{
	struct neigh_history_entry *he = NULL;


	he = neigh_history_lookup(nq, macaddr);
	if (!he)
		return -1;

	memcpy(he->aladdr, macaddr, 6);
	he->is1905 = !!val;
	dbg("%s: set " MACFMT " is1905 = %d\n", __func__,
	    MAC2STR(he->macaddr), he->is1905);

	return 0;
}

int neigh_history_entry_set_1905_slave(void *nq, uint8_t *macaddr,
				       uint8_t *aladdr, uint8_t val)
{
	struct neigh_history_entry *he = NULL;


	he = neigh_history_lookup(nq, macaddr);
	if (!he)
		return -1;

	if (aladdr)
		memcpy(he->aladdr, aladdr, 6);

	he->is1905_slave = !!val;
	dbg("%s: set " MACFMT " is1905_slave = %d\n", __func__,
	    MAC2STR(he->macaddr), he->is1905_slave);

	return 0;
}

int neigh_history_entry_set_1905_link(void *nq, uint8_t *macaddr,
				      uint8_t *aladdr, uint8_t val)
{
	struct neigh_history_entry *he = NULL;


	he = neigh_history_lookup(nq, macaddr);
	if (!he)
		return -1;

	if (aladdr)
		memcpy(he->aladdr, aladdr, 6);
	he->is1905_link = !!val;
	dbg("%s: set " MACFMT " is1905_link = %d\n", __func__,
	    MAC2STR(he->macaddr), he->is1905_link);

	return 0;
}

struct neigh_entry *neigh_entry_create(void *priv, uint8_t *macaddr, uint16_t state,
				       const char *ifname, enum neigh_type type,
				       uint32_t timeout, void *cookie)
{
	struct neigh_entry *e;
	struct timeval tsp = { 0 };


	e = calloc(1, sizeof(*e));
	if (!e) {
		err("%s: -ENOMEM\n", __func__);
		return NULL;
	}

	INIT_LIST_HEAD(&e->iplist);
	INIT_LIST_HEAD(&e->iflist);
	e->ipv4.family = AF_INET;
	e->state = state;
	strncpy(e->ifname, ifname, 15);
	memcpy(e->macaddr, macaddr, 6);
	if (type != NEIGH_TYPE_UNKNOWN)
		e->type = type;

	time(&e->lastchange);
	getcurrtime(&tsp);
	e->ageing_time = timeout;
	timeradd_msecs(&tsp, e->ageing_time, &e->ageing_tmo);
	e->ageing_tmo.tv_usec = (e->ageing_tmo.tv_usec / 1000) * 1000;
	e->cookie = cookie;
	e->priv = priv;
	dbg("CREATE entry: " MACFMT ", ifname = %s, state = 0x%04x timeout = { after %u msecs ( at %jd:%jd) } [priv = %p, e = %p]\n",
	    MAC2STR(macaddr), e->ifname, state, e->ageing_time,
	    (uintmax_t)e->ageing_tmo.tv_sec, (uintmax_t)e->ageing_tmo.tv_usec / 1000, e->priv, e);

	return e;
}

static void neigh_entry_delete(struct neigh_entry *e)
{
	if (e) {
		struct neigh_history_entry *he;
		struct hostmngr_private *priv = (struct hostmngr_private *)e->priv;

		dbg("Sync history for " MACFMT" before delete\n", MAC2STR(e->macaddr));
		he = neigh_history_lookup(&priv->neigh_q, e->macaddr);
		if (he) {
			dbg("update history: +ul-pkts = %ju, +dl-pkts = %ju\n",
			    (uintmax_t)e->ws.ul_packets, (uintmax_t)e->ws.dl_packets);
			he->alive = false;
			time(&he->lastseen);
			he->state = e->state;
			he->type = e->type;
			he->is1905 = e->is1905;
			he->is1905_slave = e->is1905_slave;
			he->is1905_link = e->is1905_link;
			memcpy(he->aladdr, e->aladdr, 6);
			he->lastchange = e->lastchange;
			memcpy(&he->ip, &e->ipv4, sizeof(struct ip_address));
			if (e->ifname[0] != '\0') {
				memset(he->ifname, 0, 16);
				strncpy(he->ifname, e->ifname, 16);
			}

			he->ws.ul_packets += e->ws.ul_packets;
			he->ws.ul_bytes += e->ws.ul_bytes;
			he->ws.dl_packets += e->ws.dl_packets;
			he->ws.dl_bytes += e->ws.dl_bytes;

			dbg("Ageout history entry " MACFMT" after %u secs\n",
			    MAC2STR(e->macaddr), he->timeout);
			/* (re)start history ageing timer */
			if (he->timeout)
				timer_set(&he->delete_timer, he->timeout * 1000);
		}

		neigh_flush_ip_entry_stats(e->priv, &e->ipv4);

		dbg("Removing entry " MACFMT"\n", MAC2STR(e->macaddr));
		timer_del(&e->probing_timer);
		timer_del(&e->delete_timer);
		if (e->cookie)
			free(e->cookie);

		list_flush(&e->iplist, struct ip_address_entry, list);
		list_flush(&e->iflist, struct node_interface, list);
		free(e);
	}
}

static void neigh_probing_timer_run(atimer_t *t)
{
	struct neigh_entry *e = container_of(t, struct neigh_entry, probing_timer);
	struct ip_address_entry *x;
	struct timeval now = {0};

	getcurrtime(&now);

	list_for_each_entry(x, &e->iplist, list) {
		char cmd[256] = {0};
		char ipbuf[46] = {0};

		if (x->ip.family != AF_INET)
			continue;

		inet_ntop(x->ip.family, &x->ip.addr, ipbuf, sizeof(ipbuf));
		arping_send_arp_request(e, ipbuf);
		dbg("[%jd.%jd]  %s\n", (uintmax_t)now.tv_sec, (uintmax_t)now.tv_usec, cmd);
	}
}

static void neigh_entry_ageout(struct neigh_queue *st, struct hlist_head *head,
			       struct timeval *min_next_tmo)
{
	struct neigh_entry *e;
	struct hlist_node *tmp;
	struct timeval now = { 0 };
	struct timeval new_next_tmo = { 0 };
	//struct hostmngr_private *priv = container_of(st, struct hostmngr_private, neigh_q);


	getcurrtime(&now);

	hlist_for_each_entry_safe(e, tmp, head, hlist) {
		if (!timercmp(&e->ageing_tmo, &now, >)) {
			/* schedule a one-time probing for this entry after 1s.
			 * If it does not become reachable within 15s, then delete it.
			 */
			if (e->state != NEIGH_STATE_REACHABLE) {
				if (!e->probing) {
					dbg7("probing timer started for " MACFMT " probe count = %d \n",
					     MAC2STR(e->macaddr), e->probe_cnt);
					timer_init(&e->probing_timer, neigh_probing_timer_run);
					timer_set(&e->probing_timer, 1000);
					e->probe_cnt++;
					if (e->probe_cnt > MAX_PROBE_COUNT)
						e->probing = 1;

					/* give few secs for updating the entry if arp resp received */
					e->ageing_time = 1000 + NEIGH_AGEOUT_PROBE;
					timeradd_msecs(&now, e->ageing_time, &e->ageing_tmo);

					timersub(&e->ageing_tmo, &now, &new_next_tmo);
					if (!timercmp(min_next_tmo, &new_next_tmo, <)) {
						min_next_tmo->tv_sec = new_next_tmo.tv_sec;
						min_next_tmo->tv_usec = new_next_tmo.tv_usec;
					}

				} else {
					dbg("[%jd.%jd]: No response from probed entry " MACFMT". Delete soon\n",
					    (uintmax_t)now.tv_sec, (uintmax_t)now.tv_usec,
					    MAC2STR(e->macaddr));

					/* dbg(MACFMT " state = 0x%04x aged out!\n",
					       MAC2STR(e->macaddr), e->state); */
					neigh_dequeue(st, e->macaddr, NULL);
				}
			}
		} else {
			timersub(&e->ageing_tmo, &now, &new_next_tmo);
			if (!timercmp(min_next_tmo, &new_next_tmo, <)) {
				min_next_tmo->tv_sec = new_next_tmo.tv_sec;
				min_next_tmo->tv_usec = new_next_tmo.tv_usec;
			}
		}
	}
}

static void neigh_ageing_timer_run(atimer_t *t)
{
	struct neigh_queue *st = container_of(t, struct neigh_queue, ageing_timer);
	struct timeval min_next_tmo = { .tv_sec = 999999 };
	int remain_cnt = 0;
	struct timeval nu;
	int i;

	getcurrtime(&nu);

	for (i = 0; i < NEIGH_ENTRIES_MAX; i++) {
		if (hlist_empty(&st->table[i]))
			continue;

		neigh_entry_ageout(st, &st->table[i], &min_next_tmo);
	}

	remain_cnt = st->pending_cnt;
	timeradd(&nu, &min_next_tmo, &st->next_tmo);

	if (remain_cnt) {
		uint32_t tmo_msecs =
			min_next_tmo.tv_sec * 1000 + min_next_tmo.tv_usec / 1000;

		if (tmo_msecs > 0)
			timer_set(&st->ageing_timer, tmo_msecs);
	}
}

int neigh_queue_init(void *nq)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;

	memset(q, 0, sizeof(*q));
	timer_init(&q->ageing_timer, neigh_ageing_timer_run);

	return 0;
}

struct neigh_entry *neigh_lookup(void *nq, uint8_t *macaddr)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	int idx = neigh_hash(macaddr);
	struct neigh_entry *e = NULL;


	hlist_for_each_entry(e, &q->table[idx], hlist) {
		if (!memcmp(e->macaddr, macaddr, 6)) {
			return e;
		}
	}

	return NULL;
}

struct neigh_entry *neigh_lookup_by_ipaddr(void *priv, struct ip_address *ip)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	uint32_t key = ipaddr_hash(ip);
	struct neigh_ip_entry *e = NULL;


	/* dbg("%s: ip = 0x%x, idx = %u\n", __func__, ip->addr.ip4.s_addr, key); */
	hlist_for_each_entry(e, &p->nftable.iptable[key], iphlist) {
		if (ipaddr_equal(&e->ip, ip))
			return e->neigh;
	}

	return NULL;
}

struct neigh_entry *neigh_lookup_by_aladdr(void *priv, uint8_t *aladdr)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_entry *e = NULL;
	int idx;

	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&p->neigh_q.table[idx]))
			continue;

		hlist_for_each_entry(e, &p->neigh_q.table[idx], hlist) {
			if (!memcmp(e->aladdr, aladdr, 6)) {
				return e;
			}
		}
	}

	return NULL;
}

int neigh_update_ip_entry_stats(void *priv, struct ip_address *ip, struct neigh_entry *n)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_ip_entry *e = NULL;
	uint32_t key;

	if (!priv || !ip || !n)
		return -1;

	key = ipaddr_hash(ip);
	hlist_for_each_entry(e, &p->nftable.iptable[key], iphlist) {
		if (ipaddr_equal(&e->ip, ip)) {
			n->num_tcp = e->num_tcp;
			n->num_udp = e->num_udp;
			n->ws.ul_packets = e->ws.ul_packets;
			n->ws.ul_bytes = e->ws.ul_bytes;
			n->ws.dl_packets = e->ws.dl_packets;
			n->ws.dl_bytes = e->ws.dl_bytes;
			return 0;
		}
	}

	return -1;
}

int neigh_flush_ip_entry_stats(void *priv, struct ip_address *ip)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_ip_entry *e = NULL;
	uint32_t key;

	if (!priv || !ip)
		return -1;

	key = ipaddr_hash(ip);
	hlist_for_each_entry(e, &p->nftable.iptable[key], iphlist) {
		if (ipaddr_equal(&e->ip, ip)) {
			dbg("Flushing flow stats for neigh\n");
			e->num_tcp = 0;
			e->num_udp = 0;
			memset(&e->ws, 0, sizeof(struct neigh_wanstats));
			return 0;
		}
	}

	return -1;
}

struct neigh_entry *neigh_queue_print(void *nq)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct neigh_entry *e = NULL;
	int idx = 0;


	dbg("-------- neigh queue --------------\n");
	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&q->table[idx]))
			continue;

		hlist_for_each_entry(e, &q->table[idx], hlist) {
			struct ip_address_entry *t;
			char buf[1024] = {0};

			snprintf(buf, sizeof(buf) - 1,
				"[e = %p, priv = %p] Entry: " MACFMT " wifi = %d, probing = %d | ifname = %s (port = %hu)   state = %02x ipaddrs: ",
				e, e->priv,
				MAC2STR(e->macaddr),
				e->type == NEIGH_TYPE_WIFI ? 1 : 0,
				e->probing,
				e->ifname,
				e->brport,
				e->state);

			list_for_each_entry(t, &e->iplist, list) {
				char ipbuf[64] = {0};

				inet_ntop(t->ip.family, &t->ip.addr, ipbuf, sizeof(ipbuf));
				snprintf(buf + strlen(buf),
					 sizeof(buf) - strlen(buf),
					 "%s, ", ipbuf);
			}
			snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
				 "%s", "\n");
			dbg("%s", buf);
		}
	}
	dbg("-----------------------------------\n");

	return NULL;
}

void neigh_queue_flush(void *nq)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct neigh_entry *e = NULL;
	struct hlist_node *tmp;
	int idx = 0;

	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&q->table[idx]))
			continue;

		hlist_for_each_entry_safe(e, tmp, &q->table[idx], hlist)
			neigh_entry_delete(e);

		q->table[idx].first = NULL;
	}

	q->pending_cnt = 0;
}

void neigh_queue_free(void *nq)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;

	neigh_queue_flush(q);
	timer_del(&q->ageing_timer);
}

void neigh_set_unreachable(void *nq, const char *ifname)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct hostmngr_private *priv = container_of(q, struct hostmngr_private, neigh_q);
	struct neigh_entry *e = NULL;
	int idx = 0;

	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&q->table[idx]))
			continue;

		hlist_for_each_entry(e, &q->table[idx], hlist) {
			dbg("%s: e->ifname = %s ifname = %s\n", __func__, e->ifname, ifname);
			if (!strncmp(e->ifname, ifname, sizeof(e->ifname))) {
				//struct neigh_history_entry *he = NULL;

				if (!e->unreachable) {
					e->unreachable = 1;
					time(&e->lastchange);
					err(MACFMT " marked unreachable\n",
					    MAC2STR(e->macaddr));

					hostmngr_host_event(priv, HOST_EVENT_DISCONNECT, e);
				}
				/*
				he = neigh_history_lookup(nq, e->macaddr);
				if (he) {
					dbg("Update history last-seen time\n");
					time(&he->lastseen);
					he->alive = false;
				}
				*/
			}
		}
	}
}

void neigh_mark_reachable(void *nq, uint8_t *macaddr, const char *ifname)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct hostmngr_private *priv = container_of(q, struct hostmngr_private, neigh_q);
	struct neigh_entry *e = NULL;
	int idx = 0;

	if (!macaddr || hwaddr_is_zero(macaddr) || !ifname)
		return;

	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&q->table[idx]))
			continue;

		hlist_for_each_entry(e, &q->table[idx], hlist) {
			if (hwaddr_equal(e->macaddr, macaddr)) {
				//struct neigh_history_entry *he = NULL;

				if (e->unreachable) {
					hostmngr_get_neigh_hostname(q, macaddr);
					e->unreachable = 0;
					time(&e->lastchange);
					err("Marking " MACFMT " reachable through %s\n",
					    MAC2STR(e->macaddr), ifname);

					memset(e->ifname, 0, sizeof(e->ifname));
					strncpy(e->ifname, ifname, strlen(ifname));
					if (e->ipv4_type_static || strlen(e->hostname))
						hostmngr_host_event(priv, HOST_EVENT_CONNECT, e);
					else
						e->event_pending = 1;
				}
				/*
				he = neigh_history_lookup(nq, e->macaddr);
				if (he) {
					dbg("Update history last-seen time\n");
					time(&he->lastseen);
					he->alive = false;
				}
				*/
			}
		}
	}
}

void neigh_probe_unreachable(void *nq, const char *ifname)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct neigh_entry *e = NULL;
	int idx = 0;

	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&q->table[idx]))
			continue;

		hlist_for_each_entry(e, &q->table[idx], hlist) {
			if (e->unreachable) {
				char mifname[16] = {0};
				char ipbuf[46] = {0};
				char cmd[256] = {0};
				int mifindex = 0;

				if (e->ipv4.addr.ip4.s_addr == INADDR_ANY)
					continue;

				inet_ntop(e->ipv4.family, &e->ipv4.addr, ipbuf, sizeof(ipbuf));
				dbg("arping %s ...\n", ipbuf);
				mifindex = if_isbridge_interface(ifname);
				if (mifindex > 0 && if_indextoname(mifindex, mifname))
					snprintf(cmd, 255, "arping -I %s -c 1 -w 1 -f %s &", mifname, ipbuf);
				else
					snprintf(cmd, 255, "arping -I %s -c 1 -w 1 -f %s &", ifname, ipbuf);

				runCmd(cmd); /* Flawfinder: ignore */
			}
		}
	}
}

int neigh_set_type(void *nq, uint8_t *macaddr, enum neigh_type type)
{
	struct neigh_entry *e = NULL;


	e = neigh_lookup(nq, macaddr);
	if (!e)
		return -1;

	e->type = type;

	return 0;
}

uint16_t neigh_get_brport(void *nq, uint8_t *macaddr)
{
	struct neigh_entry *e = NULL;
	uint16_t brport = 0xffff;


	e = neigh_lookup(nq, macaddr);
	if (!e)
		return 0xffff;

	brport = e->brport;

	return brport;
}

int neigh_set_1905(void *nq, uint8_t *macaddr, uint8_t val)
{
	struct neigh_entry *e = NULL;


	e = neigh_lookup(nq, macaddr);
	if (!e)
		return -1;

	memcpy(e->aladdr, macaddr, 6);
	e->is1905 = !!val;

	return 0;
}

int neigh_set_1905_remote(void *nq, uint8_t *aladdr, uint8_t val)
{
	int idx;
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct neigh_entry *e = NULL;

	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&q->table[idx]))
			continue;

		hlist_for_each_entry(e, &q->table[idx], hlist) {
			if (!memcmp(e->aladdr, aladdr, 6) && e->is1905_link)
				e->isremote = !!val;
		}
	}
}

//TODO:
int neigh_set_1905_linkaddr(void *nq, uint8_t *aladdr, uint8_t *linkaddr)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct neigh_entry *e = NULL;
	enum neigh_type linktype = 0;
	struct neigh_entry *le;
	int ret = -1;
	int idx;


	le = neigh_lookup(nq, linkaddr);
	if (le)
		linktype = le->type;

	for (idx = 0; idx < NEIGH_ENTRIES_MAX; idx++) {
		if (hlist_empty(&q->table[idx]))
			continue;

		hlist_for_each_entry(e, &q->table[idx], hlist) {
			if (!memcmp(e->aladdr, aladdr, 6) && e->is1905_link) {
				dbg("%s: matched neigh 1905-aladdr; update linkaddr\n", __func__);
				memcpy(e->linkaddr, linkaddr, 6);
				e->type = linktype;
				ret = 0;
			}
		}
	}

	return ret;
}

int neigh_set_1905_slave(void *nq, uint8_t *macaddr, uint8_t *aladdr, uint8_t val)
{
	struct neigh_entry *e = NULL;


	e = neigh_lookup(nq, macaddr);
	if (!e)
		return -1;

	if (aladdr)
		memcpy(e->aladdr, aladdr, 6);

	e->is1905_slave = !!val;

	return 0;
}

int neigh_set_1905_link(void *nq, uint8_t *macaddr, uint8_t *aladdr, uint8_t val)
{
	struct neigh_entry *e = NULL;


	e = neigh_lookup(nq, macaddr);
	if (!e)
		return -1;

	if (aladdr)
		memcpy(e->aladdr, aladdr, 6);
	e->is1905_link = !!val;

	return 0;
}

bool is_neigh_1905(void *nq, uint8_t *macaddr)
{
	struct neigh_entry *e = NULL;
	bool is1905;


	e = neigh_lookup(nq, macaddr);
	if (!e)
		return false;

	is1905 = e->is1905 == 1 ? true : false;

	return is1905;
}

bool is_neigh_1905_slave(void *nq, uint8_t *macaddr)
{
	struct neigh_entry *e = NULL;
	bool is1905_slave;


	e = neigh_lookup(nq, macaddr);
	if (!e)
		return false;

	is1905_slave = e->is1905_slave == 1 ? true : false;

	return is1905_slave;
}

bool neigh_is_ip_known(struct neigh_entry *e, struct ip_address *ip)
{
	struct ip_address_entry *ipaddr = NULL;

	list_for_each_entry(ipaddr, &e->iplist, list) {
		if (ipaddr_equal(&ipaddr->ip, ip))
			return true;
	}

	return false;
}

int neigh_is_wifi_type(void *priv, uint8_t *macaddr)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct local_interface *iface;


	if (!p || list_empty(&p->iflist))
		return -1;

	list_for_each_entry(iface, &p->iflist, list) {
		if (iface->is_ap) {
			char *ifname = iface->ifname;
			uint8_t stas[6*128] = {0};
			int num = 128;
			int ret;
			int i;

			if (iface->is_affiliated_ap && strlen(iface->mld_ifname))
				ifname = iface->mld_ifname;

			ret = ap_get_assoclist(ifname, stas, &num);
			if (ret)
				continue;

			for (i = 0; i < num; i++) {
				if (!memcmp(&stas[i*6], macaddr, 6)) {
					return 1;
				}
			}
		}
	}

	return 0;
}

void hostmngr_get_wifi_stations(struct hostmngr_private *priv,
				struct local_interface *iface)
{
	char *ifname = iface->ifname;
	uint8_t stas[6*128] = {0};
	int num = 128;
	int ret;
	int i;

	if (iface->is_affiliated_ap && strlen(iface->mld_ifname))
		ifname = iface->mld_ifname;

	ret = ap_get_assoclist(ifname, stas, &num);
	if (ret)
		return;

	for (i = 0; i < num; i++) {
		struct neigh_entry *new = NULL;
		uint8_t *macaddr = &stas[i*6];

		new = neigh_enqueue(&priv->neigh_q, macaddr,
			      NEIGH_STATE_REACHABLE,
			      iface->ifname,
			      NEIGH_TYPE_WIFI,
			      NULL,
			      NEIGH_AGEOUT_DEFAULT,
			      NULL);
		if (new) {
			hostmngr_get_neigh_hostname(&priv->neigh_q, macaddr);

			/* add/update history cache for this neigh */
			neigh_history_enqueue(priv, new,
					      priv->cfg.history_ageout);

			if (!ipaddr_is_zero(&new->ipv4) && (new->ipv4_type_static || strlen(new->hostname)))
				hostmngr_host_event(priv, HOST_EVENT_CONNECT, new);
			else
				new->event_pending = 1;
		}
	}
}

struct neigh_entry *neigh_enqueue(void *nq, uint8_t *macaddr, uint16_t state,
				  char *ifname, enum neigh_type type,
				  struct ip_address *ip, uint32_t timeout,
				  void *cookie)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct hostmngr_private *priv;
	struct neigh_entry *e = NULL;
	struct timeval tsp = { 0 };


	priv = container_of(q, struct hostmngr_private, neigh_q);
	dbg("%s: priv = %p\n", __func__, priv);
	getcurrtime(&tsp);

	//neigh_queue_print(q);


	e = neigh_lookup(nq, macaddr);
	if (e) {
		struct ip_address_entry *ipaddr;

		dbg("[%jd.%jd] Neigh " MACFMT " changed. ifname = %s, IP-family = %s, state 0x%04x -> 0x%04x\n",
		    (uintmax_t)tsp.tv_sec, (uintmax_t)tsp.tv_usec, MAC2STR(macaddr),
		    ifname ? ifname : "NULL",
		    ip ? ip->family == AF_INET ? "IP4" : "IP6" : "NULL",
		    e->state, state);


		/* update neigh's state only for its ipv4 entry change */
		if (ip && ip->family == AF_INET)
			e->state = state;

		if (type != NEIGH_TYPE_UNKNOWN)
			e->type = type;

		strncpy(e->ifname, ifname, 15);
		if (if_isbridge(ifname)) {
			dbg("%s: update brport for neigh\n", __func__);
			hostmngr_update_neigh_brport(priv, ifname);
		}

		if ((!ip || (ip && ip->family == AF_INET)) && e->state == NEIGH_STATE_REACHABLE) {
			if (timer_pending(&e->probing_timer)) {
				/* stop probing timer as state becomes reachable */
				timer_del(&e->probing_timer);
			}

			/* if neigh reappeared, stop delete timer if running */
			if (timer_pending(&e->delete_timer))
				timer_del(&e->delete_timer);

			e->delete_pending = 0;
			e->probing = 0;
			e->probe_cnt = 0;
			if (e->unreachable) {
				e->unreachable = 0;
				time(&e->lastchange);
			}

			/* reset ageing timer for entry */
			e->ageing_time = timeout;
			timeradd_msecs(&tsp, e->ageing_time, &e->ageing_tmo);
			e->ageing_tmo.tv_usec = (e->ageing_tmo.tv_usec / 1000) * 1000;

			/* update hostname of entry if empty.
			 * TODO: move to dhcp-lease table inotify cb.
			 */
			if (e->hostname[0] == '\0')
				hostmngr_get_neigh_hostname(q, e->macaddr);
		}

		if (ip) {
			if (!neigh_is_ip_known(e, ip)) {
				struct ip_address_entry *new;
				char newip[64] = {0};

				inet_ntop(ip->family, &ip->addr, newip, sizeof(newip));
				dbg("%s: Adding new ipaddress %s to entry " MACFMT"\n",
					__func__, newip, MAC2STR(e->macaddr));

				new = calloc(1, sizeof(*new));
				if (new) {
					memcpy(&new->ip, ip, sizeof(*ip));
					list_add_tail(&new->list, &e->iplist);
				}
			}

			/* can get overridden by dhcp lease entry */
			if (ip->family == AF_INET) {
				if (!ipaddr_equal(&e->ipv4, ip)) {
					memcpy(&e->ipv4, ip, sizeof(*ip));
					if (e->ipv4_type_static || strlen(e->hostname)) {
						e->event_pending = 0;
						hostmngr_host_event(priv, HOST_EVENT_CONNECT, e);
					} else
						e->event_pending = 1;
				}
			}
		}

		if (!timer_pending(&q->ageing_timer)) {
			q->next_tmo.tv_sec = e->ageing_tmo.tv_sec;
			q->next_tmo.tv_usec = e->ageing_tmo.tv_usec;
			timer_set(&q->ageing_timer, e->ageing_time);
		}

#if 0
		/* add/update history entry for this neigh */
		neigh_history_enqueue(priv, e, priv->cfg.history_ageout);
#endif
		return NULL;
	}

	/* skip creation of neigh entry for non-ipv4 entry change */
	if (ip && ip->family != AF_INET)
		return NULL;


	e = neigh_entry_create(priv, macaddr, state, ifname, type, timeout, cookie);
	if (e) {
		int idx = neigh_hash(macaddr);
		struct ip_address_entry *ipaddr;


		hlist_add_head(&e->hlist, &q->table[idx]);
		q->pending_cnt++;
		/* dbg("ENQ:   " MACFMT " state = 0x%04x  ifname = %s\n",
			MAC2STR(macaddr), state, ifname); */

		if (if_isbridge(ifname))
			hostmngr_update_neigh_brport(priv, ifname);

		hostlist_add_entry(priv, macaddr, false);

		/* reduce timeout of non-reachable entry for quick probing */
		if (state != NEIGH_STATE_REACHABLE) {
			dbg("Adjust timeout of " MACFMT " to 2s\n", MAC2STR(macaddr));
			    e->ageing_time = NEIGH_AGEOUT_QUICK;

			timeradd_msecs(&tsp, NEIGH_AGEOUT_QUICK, &e->ageing_tmo);
			e->ageing_tmo.tv_usec = (e->ageing_tmo.tv_usec / 1000) * 1000;
		}

		if (timer_pending(&q->ageing_timer)) {
			if (timercmp(&q->next_tmo, &e->ageing_tmo, >)) {
				q->next_tmo.tv_sec = e->ageing_tmo.tv_sec;
				q->next_tmo.tv_usec = e->ageing_tmo.tv_usec;
				timer_set(&q->ageing_timer, e->ageing_time);
			}
		} else {
			q->next_tmo.tv_sec = e->ageing_tmo.tv_sec;
			q->next_tmo.tv_usec = e->ageing_tmo.tv_usec;
			timer_set(&q->ageing_timer, e->ageing_time);
		}

		if (ip) {
			/* can get overridden by dhcp lease entry */
			if (ip->family == AF_INET)
				memcpy(&e->ipv4, ip, sizeof(*ip));

			if (!neigh_is_ip_known(e, ip)) {
				struct ip_address_entry *new;

				dbg("Adding new ipaddress to entry " MACFMT"\n",
				    MAC2STR(e->macaddr));
				new = calloc(1, sizeof(*new));
				if (new) {
					memcpy(&new->ip, ip, sizeof(*ip));
					list_add_tail(&new->list, &e->iplist);
				}
			}
		}


		return e;
	}

	return NULL;
}

void neigh_unlink_flowtable(void *priv, struct ip_address *ip4)
{
	struct hostmngr_private *p = (struct hostmngr_private *)priv;
	struct neigh_ip_entry *n = NULL;
	struct neigh_flow_table *ft;
	struct hlist_node *tmp;
	uint32_t key;

	if (!priv) {
		dbg("%s: priv = NULL!\n", __func__);
		return;
	}

	ft = &p->nftable;
	key = ipaddr_hash(ip4);
	hlist_for_each_entry_safe(n, tmp, &ft->iptable[key], iphlist) {
		if (ipaddr_equal(&n->ip, ip4)) {
			dbg("unlink ip-entry at key %u\n", key);
			n->neigh = NULL;
			break;
		}
	}
}

static int neigh_delete(struct neigh_entry *e)
{
	struct hostmngr_private *priv = e->priv;
	struct neigh_entry *n = NULL;
	struct hlist_node *tmp;
	struct neigh_queue *q;
	int idx;


	if (!priv) {
		dbg("%s: priv = NULL!\n", __func__);
		return -1;
	}

	dbg("Deleting Neigh " MACFMT "\n", MAC2STR(e->macaddr));
	q = &priv->neigh_q;

	neigh_unlink_flowtable(priv, &e->ipv4);

	idx = neigh_hash(e->macaddr);
	hlist_for_each_entry_safe(n, tmp, &q->table[idx], hlist) {
		if (hwaddr_equal(n->macaddr, e->macaddr)) {
			hlist_del(&n->hlist, &q->table[idx]);
			break;
		}
	}

	q->pending_cnt--;
	dbg("DELETE: Neigh " MACFMT " (pending_cnt = %d)\n", MAC2STR(e->macaddr),
	    q->pending_cnt);

	neigh_entry_delete(e);

	return 0;
}

#if 0
static void neigh_delete_timer_cb(atimer_t *t)
{
	struct neigh_entry *e = container_of(t, struct neigh_entry, delete_timer);

	neigh_delete(e);
}
#endif

int neigh_dequeue(void *nq, uint8_t *macaddr, void **cookie)
{
	struct neigh_queue *q = (struct neigh_queue *)nq;
	struct hostmngr_private *priv =
			container_of(q, struct hostmngr_private, neigh_q);
	struct neigh_entry *e = NULL;

	//neigh_queue_print(nq);

	e = neigh_lookup(nq, macaddr);
	if (!e) {
		dbg("unexpected neigh dequeue " MACFMT"\n", MAC2STR(macaddr));
		return -1;
	}

#if 0
	if (!e->delete_pending) {
		/* return cookie back to user if there is one */
		if (cookie && *cookie)
			*cookie = e->cookie;

		e->cookie = NULL;
		e->delete_pending = 1;
		dbg("Neigh " MACFMT" marked for deletion\n", MAC2STR(macaddr));
		timer_init(&e->delete_timer, neigh_delete_timer_cb);
		timer_set(&e->delete_timer, 3000);
		return 0;
	}
#endif
	if (!(e->is1905 || (e->is1905_slave && !e->is1905_link)))
		hostmngr_host_event(priv, HOST_EVENT_DISCONNECT, e);

	neigh_delete(e);

	return 0;
}

int hostlist_add_entry(struct hostmngr_private *priv, uint8_t *macaddr, bool from_history)
{
	struct host_macaddr_entry *h;

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

	list_for_each_entry(h, &priv->hostlist, list) {
		if (hwaddr_equal(h->macaddr, macaddr) && !from_history) {
			h->reconnect_cnt++;
			return 0;
		}
	}

	h = calloc(1, sizeof(*h));
	if (!h)
		return -1;

	memcpy(h->macaddr, macaddr, 6);
	h->reconnect_cnt = from_history ? 0 : 1;
	list_add_tail(&h->list, &priv->hostlist);
	priv->num_hosts++;

	return 0;
}

int hostlist_del_entry(struct hostmngr_private *priv, uint8_t *macaddr)
{
	struct host_macaddr_entry *h, *tmp;

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

	list_for_each_entry_safe(h, tmp, &priv->hostlist, list) {
		if (hwaddr_equal(h->macaddr, macaddr)) {
			list_del(&h->list);
			priv->num_hosts--;
			free(h);
			return 0;
		}
	}

	return -1;
}

int hostlist_flush(struct hostmngr_private *priv)
{
	struct host_macaddr_entry *h, *tmp;

	if (!priv)
		return -1;

	list_for_each_entry_safe(h, tmp, &priv->hostlist, list) {
		list_del(&h->list);
		priv->num_hosts--;
		free(h);
	}

	return 0;
}

struct node_interface *neigh_interface_lookup(struct neigh_entry *e,
						   uint8_t *macaddr)
{
	struct node_interface *iface = NULL;

	if (list_empty(&e->iflist))
		return NULL;

	list_for_each_entry(iface, &e->iflist, list) {
		if (!memcmp(iface->macaddr, macaddr, 6))
			return iface;
	}

	return NULL;
}

struct node_interface *niegh_add_interface(struct neigh_entry *e,
					   uint8_t *macaddr)
{
        struct node_interface *iface = NULL;

	iface = neigh_interface_lookup(e, macaddr);
	if (iface)
		return iface;

	iface = calloc(1, sizeof(*iface));
	if (!iface) {
		err("%s: Error allocating node_interface "MACFMT"\n", __func__,
		    MAC2STR(macaddr));
		return NULL;
	}

	memcpy(iface->macaddr, macaddr, 6);
	list_add(&iface->list, &e->iflist);
	return iface;
}
