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

/*
 * testing lc_socketpair() - create a pair of connected sockets
 * then test with various API calls that use sockets
 */

#include "test.h"
#include <librecast_pvt.h>
#include <librecast/net.h>

#define CHANNELS 4

int main(void)
{
	lc_ctx_t *lctx;
	lc_socket_t *sock[2];
	lc_channel_t *chan[CHANNELS];

	test_name("lc_socketpair()");

	lctx = lc_ctx_new();
	if (!test_assert(lctx != NULL, "lc_ctx_new()")) return test_status;

	if (!test_assert(lc_socketpair(lctx, sock) == 0, "lc_socketpair()"))
		goto err_free_lctx;
	for (int i = 0; i < 2; i++) {
		test_assert(sock[i]->type == LC_SOCK_PAIR, "type[%i] == LC_SOCK_PAIR", i);
		test_assert(lc_socket_raw(sock[i]) != -1, "lc_socket_raw() socket != -1");
	}

	char msg[] = "hello";
	char buf[sizeof msg];
	ssize_t rc;
	rc = send(sock[0]->sock, msg, sizeof msg, 0);
	test_assert(rc == sizeof msg, "%zi bytes sent", rc);
	rc = recv(sock[1]->sock, buf, sizeof buf, 0);
	test_assert(rc == sizeof msg, "%zi bytes received", rc);

	for (int i = 0; i < CHANNELS; i++) {
		chan[i] = lc_channel_random(lctx);
		if (!chan[i] || lc_channel_bind(sock[0], chan[i])) goto err_free_lctx;
		test_assert(lc_channel_socket(chan[i]) == sock[0], "socket %i bound", i);
	}
	/* join channels (except last), adding them to the OIL filter */
	/* NB: with a socketpair, the OIL for sock->pair is updated,
	 * not the sock itself */
	test_assert(sock[1]->oil == NULL, "OIL filter is NULL");
	for (int i = 0; i < CHANNELS - 1; i++) {
		if (!test_assert(lc_channel_join(chan[i]) == 0, "lc_channel_join()[%i]", i))
			goto err_free_lctx;
	}
	test_assert(sock[1]->oil != NULL, "OIL filter is allocated");

	for (int i = 0; i < CHANNELS - 1; i++) {
		test_assert(lc_socket_oil_cmp(sock[1], chan[i]->hash) == 0,
				"lc_socket_oil_cmp()[%i]", i);
	}
	/* this last group was not added, ensure not found */
	test_assert(lc_socket_oil_cmp(sock[1], chan[CHANNELS - 1]->hash) == -1,
			"lc_socket_oil_cmp() - check for hash not in filter");
	test_assert(errno == ENOENT, "ENOENT");

	test_assert(lc_socket_ttl(sock[0], 42) == 0, "lc_socket_ttl() returns -1 for socketpair");
	test_assert(sock[0]->ttl == 42, "TTL set");

	errno = 0;
	test_assert(lc_socket_loop(sock[0], 1) == -1, "lc_socket_loop() returns -1 for socketpair");
	test_assert(errno == ENOTSUP, "lc_socket_loop() => errno == ENOTSUP");

	socklen_t optlen;
	int opt = 1;
	errno = 0;
	test_assert(lc_socket_getopt(sock[0], IPV6_MULTICAST_LOOP, &opt, &optlen) == -1,
			"lc_socket_getopt() returns -1 for socketpair");
	test_assert(errno == ENOTSUP, "lc_socket_getopt() => errno == ENOTSUP");

	errno = 0;
	test_assert(lc_socket_setopt(sock[0], IPV6_MULTICAST_LOOP, &opt, sizeof opt) == -1,
			"lc_socket_setopt() returns -1 for socketpair");
	test_assert(errno == ENOTSUP, "lc_socket_setopt() => errno == ENOTSUP");

	/* send without joining channel, 0 bytes sent */
	rc = lc_channel_send(chan[0], msg, sizeof msg, 0);
	test_assert(rc == 0, "%zi bytes sent (should be zero)", rc);

	/* now bind and join the channel and send/recv
	 * NB: chan[0] is bound to sock[0], so a JOIN will get added to the
	 * OIL of sock[1], NOT sock[0]
	 * The easiest way to deal with this for the test is just call
	 * lc_socket_oil_add() */
	lc_socket_oil_add(sock[0], chan[0]->hash);

	size_t overhead = sizeof(lc_tlv_hash_t);
	rc = lc_channel_send(chan[0], msg, sizeof msg, 0);
	if (!test_assert((size_t)rc == sizeof msg + overhead, "%zi bytes sent", rc))
		goto err_free_lctx;

	rc = lc_socket_recv(sock[1], buf, sizeof buf, 0);
	test_assert(rc == sizeof msg, "%zi/%zu bytes received", rc, sizeof msg);

err_free_lctx:
	lc_ctx_free(lctx);
	return test_status;
}
