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

#include "testnet.h"
#include <librecast.h>
#include <limits.h>
#include <mld_pvt.h>
#include <net/if.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <unistd.h>

int main(void)
{
	mld_t *mld;
	struct in6_addr *addr;
	struct timespec ts = {0};
	lc_ctx_t *lctx;
	lc_channel_t *chan;
	unsigned int ifx, ifx_invalid;
	int rc;

	test_cap_require(CAP_NET_RAW);
	test_name("mld_filter_grp_cmp() / mld_filter_grp_add()");
	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_start(mld)) {
		test_assert(0, "mld start failed");
		goto err_mld_free;
	}
	/* ensure all threads created */
	for (int i = 0; i < MLD_THREADS; i++) {
		assert(mld->q[i]);
	}

	ifx_invalid = get_invalid_ifx();
	test_assert(ifx_invalid, "get_invalid_ifx() - find invalid interface");

	errno = 0;
	rc = mld_filter_grp_cmp(mld, 0, NULL);
	test_assert(errno == EINVAL, "mld_filter_grp_cmp() - pass in NULL (EINVAL)");
	test_assert(rc == -1, "mld_filter_grp_cmp() - pass in NULL (return -1 error)");

	errno = 0;
	rc = mld_filter_grp_add(mld, ifx, NULL);
	test_assert(errno == EINVAL, "mld_filter_grp_add() - pass in NULL (EINVAL)");
	test_assert(rc == -1, "mld_filter_grp_add() - pass in NULL (return -1 error)");

	/* 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()");

	/* test addr on interface that doesn't exist */
	errno = 0;
	rc = mld_filter_grp_add(mld, ifx_invalid, addr);
	test_assert(errno == ENODEV, "mld_filter_grp_add() - invalid ifx (ENODEV)");
	test_assert(rc == -1, "attempt to add to invalid ifx[%u]", ifx_invalid);

	/* add grp */
	rc = mld_filter_grp_cmp(mld, ifx, addr);
	test_assert(rc == 0, "check for address before adding to interface[%u] filter", ifx);
	rc = mld_filter_grp_add(mld, ifx, addr);
	test_assert(rc == 0, "mld_filter_grp_add() - add address to interface[%u] filter", ifx);
	rc = mld_filter_grp_cmp(mld, ifx, addr);
	test_assert(rc == 1, "check address was added to interface[%u] filter", ifx);
	rc = mld_filter_grp_add(mld, ifx, addr);
	test_assert(rc == 1, "mld_filter_grp_add() - try to add duplicate address to filter ");

	/* test with ifx = 0 - MUST find grp regardless of interface */
	rc = mld_filter_grp_cmp(mld, 0, addr);
	test_assert(rc == 1, "check mld_filter_grp_cmp() returns 1 with ifx=0", ifx);

	/* delete grp */
	rc = mld_filter_grp_del(mld, ifx, addr);
	test_assert(rc == 0, "mld_filter_grp_del() - delete grp from filter");
	rc = mld_filter_grp_cmp(mld, ifx, addr);
	test_assert(rc == 0, "check for address after deleting from filter");

	/* add again, check timers */
	rc = mld_filter_grp_add(mld, ifx, addr);
	test_assert(rc == 0, "mld_filter_grp_add() - add address to interface[%u] filter", ifx);
	rc = mld_filter_grp_cmp(mld, ifx, addr);
	test_assert(rc == 1, "check address was added to interface[%u] filter", ifx);
	rc = mld_filter_timer_get(mld, ifx, addr, &ts);
	test_assert(rc == 1, "ensure timer set");
	test_assert(ts.tv_sec > 0, "ensure timer set");

	mld_filter_timer_set_s(mld, ifx, addr, 1); /* lower timeout to 1s */
	ts.tv_sec = 1;
	ts.tv_nsec = 100000000;
	nanosleep(&ts, NULL); /* sleep 1.1s */

	rc = mld_filter_grp_cmp(mld, ifx, addr);
	test_assert(rc == 0, "ensure address expired");
	test_assert(mld_get_grp(mld, ifx, addr) == NULL, "ensure grp deleted after expiry");

	lc_ctx_free(lctx);
	mld_stop(mld);
err_mld_free:
	mld_free(mld);
	return test_status;
}
