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

/* Ensure multicast packets are sent by default on ALL capable interfaces */

#include "test.h"
#include "testnet.h"
#include <ifaddrs.h>
#include <librecast/net.h>
#include <net/if.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>

#define TIMEOUT_SECONDS 2

static sem_t sem;
static lc_ctx_t *lctx;
static char channel_name[] = "0000-0063";

void *thread_listen(void *arg)
{
	unsigned int ifx = *(unsigned int *)arg;
	char buf[1024];
	lc_socket_t *sock;
	lc_channel_t *chan;
	ssize_t byt;
	int rc;

	sock = lc_socket_new(lctx);
	if (!test_assert(sock != NULL, "lc_socket_new")) return NULL;
	chan = lc_channel_new(lctx, channel_name);
	if (!test_assert(chan != NULL, "lc_channel_new")) return NULL;
	rc = lc_socket_bind(sock, ifx);
	if (!test_assert(rc == 0, "lc_socket_bind")) return NULL;
	/* bind socket to ifx so we only get packets on that interface */
	rc = lc_channel_bind(sock, chan);
	if (!test_assert(rc == 0, "lc_channel_bind")) return NULL;
	rc = lc_channel_join(chan);
	if (!test_assert(rc == 0, "lc_channel_join")) return NULL;
	test_log("listening on ifx = %u\n", ifx);
	sem_post(&sem); /* tell sender we're ready */
	byt = lc_socket_recv(sock, buf, sizeof buf, 0);
	test_assert(byt > 0, "%zi bytes received on ifx %u", byt, ifx);
	sem_post(&sem);

	return NULL;
}

int push_ifx(unsigned int ifxa[], int ifcount, char *ifname)
{
	unsigned int ifx = if_nametoindex(ifname);
	if (ifx) for (int i = 0; i < ifcount; i++) {
		if (ifxa[i] == ifx) return 0;
	}
	ifxa[ifcount] = ifx;
	return 1;
}

int main(void)
{
	char name[] = "lc_channel_send - send on ALL interfaces by default";
	lc_socket_t *sock;
	lc_channel_t *chan;
	unsigned int ifxa[16];
	struct ifaddrs *ifap;
	pthread_t tid[16];
	ssize_t byt;
	int ifcount = 0;
	int rc;

	test_name(name);
	test_require_net(TEST_NET_BASIC);

	/* Set up listeners on all multicast capable interfaces */
	rc = getifaddrs(&ifap);
	test_assert(rc == 0, "getifaddrs(3)");

	/* find interfaces with multicast enabled */
	for (struct ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr->sa_family != AF_INET6) continue; /* IPv6 */
		if (!(ifa->ifa_flags & IFF_MULTICAST)) continue;    /* multicast */
		if (ifa->ifa_addr == NULL) continue;
		ifcount += push_ifx(ifxa, ifcount, ifa->ifa_name);
		if (ifcount == sizeof ifxa / sizeof ifxa[0]) break;
	}
	freeifaddrs(ifap);
	test_log("%i interfaces\n", ifcount);

	/* create socket and channel for sending */
	lctx = lc_ctx_new();
	if (!test_assert(lctx != NULL, "lc_ctx_new()")) return test_status;
	sock = lc_socket_new(lctx);
	if (!test_assert(sock != NULL, "lc_socket_new() (send thread)")) goto err_lc_ctx_free;
	chan = lc_channel_new(lctx, channel_name);
	if (!test_assert(chan != NULL, "lc_channel_new() (send thread)")) goto err_lc_ctx_free;
	rc = lc_channel_bind(sock, chan);
	if (!test_assert(rc == 0, "lc_channel_bind()")) goto err_lc_ctx_free;
	rc = lc_socket_loop(sock, 1);
	if (!test_assert(rc == 0, "lc_socket_loop()")) goto err_lc_ctx_free;

	/* start listening threads */
	sem_init(&sem, 0, 0);
	for (int i = 0; i < ifcount; i++) {
		pthread_create(&tid[i], NULL, &thread_listen, &ifxa[i]);
	}

	/* wait for receiver threads to be ready */
	for (int i = 0; i < ifcount; i++) sem_wait(&sem);
	test_log("all receivers ready\n");

	/* Send one packet (with loopback set) */
	byt = lc_channel_send(chan, name, sizeof name, 0);
	test_assert(byt == sizeof name, "%zi bytes sent", byt);

	/* Ensure packet is received by all interfaces */
	struct timespec ts;
	clock_gettime(CLOCK_REALTIME, &ts);
	ts.tv_sec += TIMEOUT_SECONDS;
	for (int i = 0; i < ifcount; i++) {
		if (!test_assert(sem_timedwait(&sem, &ts) == 0, "timeout")) {
			break;
		}
	}
	for (int i = 0; i < ifcount; i++) {
		pthread_cancel(tid[i]);
		pthread_join(tid[i], NULL);
	}
	sem_destroy(&sem);
err_lc_ctx_free:
	lc_ctx_free(lctx);

	return test_status;
}
