/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2022-2023 Brett Sheffield <bacs@librecast.net> */

#include "test.h"
#include <errno.h>
#include <ifaddrs.h>
#ifdef __linux__
# include <librecast.h>
# include <librecast/if.h>
#endif
#include <limits.h>
#include <net/if.h>
#include <sys/types.h>

#ifndef AF_LINK
# ifdef AF_PACKET
#  define AF_LINK AF_PACKET
# endif
#endif

unsigned int get_invalid_ifx(void)
{
	char ifname[IF_NAMESIZE];
	for (unsigned int ifx = 1; ifx < UINT_MAX; ifx++) {
		if (!if_indextoname(ifx, ifname)) return ifx;
	}
	return 0;
}

/* find an interface that supports multicast */
unsigned get_multicast_if(void)
{
	unsigned ifidx = 0;
	/* create tap interface and bring it up */
	char ifname[IFNAMSIZ] = {0};
#ifdef __linux__
	lc_tuntap_create(ifname, IFF_TAP | IFF_NO_PI);
	lc_ctx_t *lctx = lc_ctx_new();
	lc_link_set(lctx, ifname, 1);
	lc_ctx_free(lctx);
	ifidx = if_nametoindex(ifname);
#endif
	if (!ifidx) {
		test_log("unable to create tap device: %s\n", strerror(errno));
		/* failed to create tap, find multicast capable interface */
		struct ifaddrs *ifa = NULL, *ifap = NULL;
		test_assert(getifaddrs(&ifa) != -1, "getifaddrs(): %s", strerror(errno));
		for (ifap = ifa; ifap; ifap = ifap->ifa_next) {
			if (!(ifap->ifa_flags & IFF_MULTICAST)) continue;
			/* we don't want the loopback interface */
			if (ifap->ifa_flags & IFF_LOOPBACK) continue;
			if (ifap->ifa_addr == NULL) continue;
			if (ifap->ifa_addr->sa_family != AF_INET6) continue;
			ifidx = if_nametoindex(ifap->ifa_name);
			test_log("found multicast interface %s [%u]\n", ifap->ifa_name, ifidx);
			break;
		}
		freeifaddrs(ifa);
	}
	else {
		test_log("tap %s created\n", ifname);
	}
	return ifidx;
}

int test_net_level(void)
{
	struct ifaddrs *ifaddr;
	int ifaces = 0;
	int ifup = 0;
	int mcast = 0;
	int addr6 = 0;
	int addr4 = 0;
	int level = 0;
	int mroutes = 0;
	char * have_multicast = getenv("HAVE_MULTICAST_ROUTES");

	test_init();
	test_log("Checking network connectivity...\n");
	if (getifaddrs(&ifaddr) == -1) return -1;

	for (struct ifaddrs *ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
		switch (ifa->ifa_addr->sa_family) {
			case AF_LINK:
				ifaces++;
				if (ifa->ifa_flags & IFF_UP) {
					ifup++;
					/* test MULTICAST flag, skip loopback interfaces */
					if (ifa->ifa_flags & IFF_LOOPBACK) break;
					if (ifa->ifa_flags & IFF_MULTICAST) mcast++;
				}
				break;
			case AF_INET6:
				if (ifa->ifa_flags & IFF_UP) addr6++;
				break;
			case AF_INET:
				if (ifa->ifa_flags & IFF_UP) addr4++;
				break;
		}
	}
	freeifaddrs(ifaddr);

	test_log("have_multicast: %s\n", have_multicast);

	if (have_multicast) mroutes = atoi(have_multicast);

	test_log(" %i network interfaces found\n", ifaces);
	test_log(" %i network interfaces up\n", ifup);
	test_log(" %i MULTICAST interfaces up\n", mcast);
	test_log(" %i IPv6 addresses\n", addr6);
	test_log(" %i IPv4 addresses\n", addr4);
	test_log(" %i multicast routes\n", mroutes);

	level |= (ifaces > 0);
	level |= (ifup > 0) << 1;
	level |= (addr4 > 0) << 2;
	level |= (addr6 > 0) << 3;
	level |= (mcast > 0) << 4;
	level |= (mroutes > 0) << 5;
	test_log("connectivity level = %i (%06b)\n", level, level);

	return level;
}


int test_require_net(int required)
{
	int lvl = test_net_level();
	if ((lvl & required) != required) {
		test_log("------------------------------------------------------\n");
		test_log("Required network level not met. Skipping test.        \n");
		test_log("(%06b/%06b)\n", lvl, required);
		test_log("------------------------------------------------------\n");
		exit(TEST_WARN);
	}
	return lvl;
}
