/*
 * mactable.c - macaddress table functions.
 *
 * Copyright (C) 2020-2024 IOPSYS Software Solutions AB. All rights reserved.
 *
 * See LICENSE file for source code license information.
 *
 */

#include "mactable.h"

#include <stdlib.h>
#include <string.h>
#include <easy/easy.h>

#include "utils/debug.h"



const char *macaddr_type2str(enum macaddr_type type)
{
	switch (type) {
	case MAC_ENTRY_FBSS:
		return "fbss";
	case MAC_ENTRY_BSTA:
		return "bsta";
	case MAC_ENTRY_ALID:
		return "alid";
	case MAC_ENTRY_RADIO:
		return "radio";
	case MAC_ENTRY_UNKNOWN:
	default:
		return "unknown";
	}
}

struct macaddr_entry *mactable_lookup(struct hlist_head *table, uint8_t *macaddr,
				      uint32_t type)
{
	struct macaddr_entry *e = NULL;
	int idx;

	if (hwaddr_is_zero(macaddr)) {
		dbg("%s: macaddr = zero\n", __func__);
		return NULL;
	}

	idx = mac_hash(macaddr);
	if (hlist_empty(&table[idx]))
		return NULL;

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

	return NULL;
}

int mactable_set_entry_type(struct hlist_head *table,
			    uint8_t *macaddr,
			    uint32_t type)
{
	struct macaddr_entry *e = NULL;
	int idx;

	if (hwaddr_is_zero(macaddr)) {
		dbg("%s: macaddr = zero\n", __func__);
		return -1;
	}

	idx = mac_hash(macaddr);
	if (hlist_empty(&table[idx]))
		return -1;

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

	return -1;
}

int mactable_get_entry_type(struct hlist_head *table,
			    uint8_t *macaddr,
			    uint32_t *type)
{
	struct macaddr_entry *e = NULL;
	int idx;

	if (hwaddr_is_zero(macaddr)) {
		dbg("%s: macaddr = zero\n", __func__);
		return -1;
	}

	idx = mac_hash(macaddr);
	if (hlist_empty(&table[idx]))
		return -1;

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

	return -1;
}

int mactable_del_entry(struct hlist_head *table, uint8_t *macaddr,
		       enum macaddr_type type)
{
	struct macaddr_entry *e = NULL;
	int idx;

	if (WARN_ON(hwaddr_is_zero(macaddr)))
		return -1;

	e = mactable_lookup(table, macaddr, type);
	if (WARN_ON(!e))
		return -1;

	idx = mac_hash(macaddr);
	hlist_del(&e->hlist, &table[idx]);
	free(e);
	dbg("Deleted MAC "MACFMT " from mac_table\n", MAC2STR(macaddr));

	return 0;
}

void mactable_flush(struct hlist_head *table)
{
	struct macaddr_entry *e = NULL;
	struct hlist_node *tmp = NULL;
	int i;

	for (i = 0; i < MAC_HASHTABLE_SIZE; i++) {
		if (hlist_empty(&table[i]))
			continue;

		hlist_for_each_entry_safe(e, tmp, &table[i], hlist) {
			hlist_del(&e->hlist, &table[i]);
			free(e);
		}
	}
}

struct macaddr_entry *mactable_add_entry(struct hlist_head *table,
					 uint8_t *macaddr,
					 enum macaddr_type type,
					 void *data)
{
	struct macaddr_entry *e = NULL;
	int idx;

	if (WARN_ON(hwaddr_is_zero(macaddr)))
		return NULL;

	e = mactable_lookup(table, macaddr, type);
	if (e) {
		WARN_ON(e->data != data);
		return e;
	}

	e = calloc(1, sizeof(struct macaddr_entry));
	if (!e) {
		err("%s: calloc failed\n", __func__);
		return NULL;
	}

	memcpy(e->macaddr, macaddr, 6);
	e->type = type;
	e->data = data;

	idx = mac_hash(macaddr);
	hlist_add_head(&e->hlist, &table[idx]);

	dbg("%s: Add MAC entry " MACFMT " of type %s\n", __func__,
	    MAC2STR(macaddr), macaddr_type2str(type));

	return e;
}

void mactable_print(struct hlist_head *table)
{
	struct macaddr_entry *e = NULL;
	int i;

	dbg("MAC-Table:\n");
	for (i = 0; i < MAC_HASHTABLE_SIZE; i++) {
		if (hlist_empty(&table[i]))
			continue;

		hlist_for_each_entry(e, &table[i], hlist) {
			dbg("[%d]: " MACFMT ", type = %s\n", i,
			    MAC2STR(e->macaddr), macaddr_type2str(e->type));
		}
	}
}
