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

/* send some msgs, receive using lc_socket_recvmmsg() */

#include "testnet.h"
#include <librecast/net.h>
#include <pthread.h>
#include <semaphore.h>

#define WAITS 2
#define VLEN 42 /* number of msgs to send/recv */

static char channel_name[] = "0000-0072";
static sem_t sem;
static volatile ssize_t bytes;
static char recvbuf[VLEN][BUFSIZ];

static void *listen_thread(void *arg)
{
	lc_ctx_t * lctx;
	lc_socket_t * sock;
	lc_channel_t * chan;
	struct iovec iov[VLEN];
	struct mmsghdr msgs[VLEN];
	int rc;

	lctx = lc_ctx_new();
	sock = lc_socket_new(lctx);
	chan = lc_channel_new(lctx, channel_name);

	memset(msgs, 0, sizeof msgs);
	for (int i = 0; i < VLEN; i++) {
		iov[i].iov_base         = recvbuf[i];
		iov[i].iov_len          = BUFSIZ;
		msgs[i].msg_hdr.msg_iov    = &iov[i];
		msgs[i].msg_hdr.msg_iovlen = 1;
	}

	lc_channel_bind(sock, chan);
	lc_channel_join(chan);

	sem_post(&sem); /* tell send thread we're ready */
	test_log("receiving on fd %i\n", lc_socket_raw(sock));
	rc = lc_socket_recvmmsg(sock, msgs, VLEN, 0, NULL);
	if (rc == -1) {
		perror("lc_socket_recvmmsg");
	}
	else {
		test_assert(rc == VLEN, "received %i/%i msgs", rc, VLEN);
		for (int i = 0; i < rc; i++) {
			bytes += msgs[i].msg_len;
			test_log("reporting %u bytes\n", msgs[i].msg_len);
		}
	}
	sem_post(&sem); /* tell send thread we're done */

	lc_ctx_free(lctx);
	return arg;
}

int main(void)
{
	lc_ctx_t * lctx;
	lc_socket_t * sock;
	lc_channel_t * chan;
	pthread_t thread;
	struct timespec ts;
	char buf[] = "liberté";
	struct iovec iov;
	struct msghdr msg = {0};
	ssize_t rc;

	test_name("lc_channel_sendmsg() / lc_socket_recvmmsg() - multiple message recv");
	test_require_net(TEST_NET_BASIC);

	sem_init(&sem, 0, 0);
	memset(recvbuf, 0, sizeof recvbuf);

	/* set up Librecast objects */
	lctx = lc_ctx_new();
	sock = lc_socket_new(lctx);
	chan = lc_channel_new(lctx, channel_name);
	lc_socket_loop(sock, 1);
	lc_channel_bind(sock, chan);

	/* start listen thread */
	pthread_create(&thread, NULL, &listen_thread, NULL);
	sem_wait(&sem); /* listen thread is ready */

	/* send some messages */
	iov.iov_base = buf;
	iov.iov_len = strlen(buf) + 1;
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	test_log("sending on fd %i\n", lc_socket_raw(sock));
	for (int i = 0; i < VLEN; i++) {
		rc = lc_channel_sendmsg(chan, &msg, 0);
		if (!test_assert(rc != -1, "lc_channel_sendmsg()")) {
			perror("lc_channel_sendmsg");
			break;
		}
		test_assert(rc == sizeof buf, "lc_channel_sendmsg %zi bytes sent", rc);
	}

	/* wait for listen thread */
	test_assert(!clock_gettime(CLOCK_REALTIME, &ts), "clock_gettime()");
	ts.tv_sec += WAITS;
	if (!test_assert(sem_timedwait(&sem, &ts) == 0, "timeout")) {
		pthread_cancel(thread);
	}
	pthread_join(thread, NULL);
	sem_destroy(&sem);
	lc_ctx_free(lctx);

	size_t received = bytes;
	size_t expected = sizeof buf * VLEN;
	test_assert(received == expected,
			"received %zi bytes, expected %zu", expected, received);
	for (int i = 0; i < VLEN; i++) {
		test_expect(buf, recvbuf[i]);
	}

	return test_status;
}
