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

#include "testnet.h"
#ifdef HAVE_LIBLIBRECAST
#include <librecast.h>
#endif
#include <mld.h>
#include <mld_pvt.h>
#include <unistd.h>

#ifdef HAVE_LIBLIBRECAST
static struct in6_addr *addr;
static unsigned int ifx;
static int watchcount;

void watch_callback(mld_watch_t *watch)
{
	test_assert(watch->ifx == ifx, "callback: ifx set");
	aadd(&watchcount, 1);
}
#endif

int main(void)
{
	char name[] = "mld_watch_*()";
#ifdef HAVE_LIBLIBRECAST
	mld_t *mld;
	mld_watch_t *watch;
	lc_ctx_t *lctx;
	lc_channel_t *chan;
	int rc;

	test_cap_require(CAP_NET_RAW);
	test_name(name);
	test_require_net(TEST_NET_BASIC);

	ifx = get_multicast_if();
	test_assert(ifx, "get_multicast_if() - find multicast capable interface");
	if (!ifx) return TEST_WARN;
	mld = mld_init(0);
	test_assert(mld != NULL, "mld_t allocated");
	if (!mld) return test_status;

	if (!mld_start(mld)) {
		test_assert(0, "mld_start() failed: %s", strerror(errno));
		goto err_mld_free;
	}
	/* ensure all threads created */
	for (int i = 0; i < MLD_THREADS; i++) assert(mld->q[i]);

	/* generate a random multicast address */
	lctx = lc_ctx_new();
	test_assert(lctx != NULL, "lc_ctx_new()");
	chan = lc_channel_random(lctx);
	test_assert(chan != NULL, "lc_channel_random()");
	addr = lc_channel_in6addr(chan);
	test_assert(addr != NULL, "lc_channel_in6addr()");

	void *arg = mld; /* any arg will do */
	int funwithflags = MLD_EVENT_JOIN;
	watch = mld_watch_add(mld, ifx, addr, watch_callback, arg, funwithflags);
	test_assert(watch != NULL, "watch set");
	test_assert(watch->mld == mld, "watch: mld set");
	test_assert(watch->ifx == ifx, "watch: ifx set");
	test_assert(!memcmp(&watch->grp, addr, sizeof(struct in6_addr)), "watch: grp set");
	test_assert(watch->arg == arg, "watch: arg set");
	test_assert(watch->flags == funwithflags, "watch: flags set");
	test_assert(watch->f == &watch_callback, "watch: callback function set");

	mld_watch_del(watch);
	test_assert(mld->watch == NULL, "mld->watch == NULL");

	/* add the watch again, we'll check with valgrind that the watch was
	 * freed on mld_free() */
	watch = mld_watch_add(mld, ifx, addr, watch_callback, arg, funwithflags);

	/* check for group before joining */
	rc = mld_filter_grp_cmp(mld, ifx, addr);
	test_assert(rc == 0, "check for group before joining");

	rc = mld_filter_grp_add(mld, ifx, addr);
	test_assert(rc == 0, "add group to filter");
	usleep(10000);
	rc = mld_filter_grp_cmp(mld, ifx, addr);
	test_assert(rc == 1, "check for group after joining");

	test_assert(aload(&watchcount) == 1, "ensure callback was triggered");

	lc_ctx_free(lctx);

	mld_stop(mld);
	mld_free(mld);
err_mld_free:
	return test_status;
#else
	return test_skip("%s - requires liblibrecast", name);
#endif

}
