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

#include "testnet.h"
#include <librecast/crypto.h>
#include <librecast/net.h>
#include <assert.h>
#include <pthread.h>
#include <fcntl.h>
#include <stdio.h>
#include <semaphore.h>
#include <unistd.h>
#include <sys/param.h>

#undef TESTDEBUG
#ifdef TESTDEBUG
# define PKTS 10
# define PKTSZ 4
#else
# define PKTS 20
# define PKTSZ 1024
#endif

#define WAITS 20

#ifdef HAVE_LIBLCRQ
enum {
	TID_SEND,
	TID_RECV
};

static const size_t SZ = PKTS * PKTSZ + 5;
static char channel_name[] = "0000-0039";
static sem_t receiver_ready, timeout;

#ifdef TESTDEBUG
static void dumppkt(uint8_t *pkt, int haseq)
{
	uint16_t seq;
	uint8_t *dat = pkt;
	if (haseq) {
		dat += sizeof seq;
		seq = ntohs(*(uint16_t *)pkt);
		fprintf(stderr, "%u: ", seq);
	}
	else fprintf(stderr, "   ");
	for (size_t z = 0; z < PKTSZ * 8; z++) {
		if (isset(dat, z)) putc('1', stderr);
		else putc('0', stderr);
	}
	putc('\n', stderr);
}
static void dump_bufs(uint8_t buf[2][SZ])
{
	for (size_t p = 0; p < PKTS; p++) {
		fprintf(stderr, "%zu: ", p);
		for (int i = 0; i < 2; i++) {
			for (size_t z = 0; z < PKTSZ * 8; z++) {
				uint8_t *b = buf[i];
				if (isset(b, p * PKTSZ * 8 + z)) putc('1', stderr);
				else putc('0', stderr);
			}
			putc(' ', stderr);
		}
		putc('\n', stderr);
	}
	putc('\n', stderr);
}
#endif

static void generate_source_data(uint8_t *buf, size_t sz)
{
	ssize_t rc;
	int f;
	f = open("/dev/urandom", O_RDONLY);
	if (f == -1) return;
	rc = read(f, buf, sz);
	test_assert(rc == (ssize_t)sz, "%zi random bytes read", rc);
	close(f);
}

static void *recv_data_fec(void *arg)
{
	uint8_t *buf = (uint8_t *)arg;
	ssize_t rc;
	lc_ctx_t *lctx = lc_ctx_new();
	lc_socket_t * sock = lc_socket_new(lctx);
	lc_channel_t * chan = lc_channel_new(lctx, channel_name);

	memset(buf, 0, SZ);    /* clear receive buffer */

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

	sem_post(&receiver_ready);

	rc = lc_channel_recv(chan, buf, SZ, 0);
	test_log("lc_channel_recv returned %zi\n", rc);
	test_assert(rc >= (ssize_t)SZ, "data received %zi/%zu bytes", rc, SZ);

	lc_channel_part(chan);
	lc_ctx_free(lctx);
	sem_post(&timeout);
	return arg;
}

static void *send_data_fec(void *arg)
{
	uint8_t *buf = (uint8_t *)arg;
	lc_ctx_t *lctx = lc_ctx_new();
	lc_socket_t * sock = lc_socket_new(lctx);
	lc_channel_t * chan = lc_channel_new(lctx, channel_name);
	rq_t *rq;
	ssize_t rc;
	size_t byt = 0;

	lc_socket_loop(sock, 1);
	lc_channel_bind(sock, chan);
	lc_channel_coding_set(chan, LC_CODE_FEC_RQ);
	test_log("sending data with FEC\n");
	sem_wait(&receiver_ready);

	rc = lc_channel_send(chan, buf, SZ, 0);
	test_assert(rc > 0, "lc_channel_send encoded data, returned %zi", rc);
	if (rc == -1) goto err_ctx_free;
	byt = rc;
	rq = lc_channel_rq(chan);

	/* Send packets. Drop packet 3 */
	int pkts = rq_KP(rq) + RQ_OVERHEAD * 2;
	test_log("sending %u packets\n", pkts);
	for (int i = 1; i < pkts; i++) {
		int flags = (i == 3) ? MSG_DROP : 0;
		rc = lc_channel_send(chan, NULL, 0, flags);
		test_assert(rc > 0, "lc_channel_send encoded data, returned %zi", rc);
		if (rc > 0) byt += rc;
	}
	test_assert(byt >= SZ, "data sent %zi bytes (buffer = %zu)", byt, SZ);
err_ctx_free:
	lc_ctx_free(lctx);
	return arg;
}
#endif

int main(void)
{
#ifndef HAVE_LIBLCRQ
	return test_skip("lc_channel_send() / lc_channel_recv() - RaptorQ (RFC 6330)");
#else
	pthread_t tid[2];
	uint8_t *buf[2];
	struct timespec ts;
	int rc;

	test_name("lc_channel_send() / lc_channel_recv() - RaptorQ (RFC 6330)");
	test_require_net(TEST_NET_BASIC);

	buf[TID_SEND] = malloc(SZ); assert(buf[TID_SEND]);
	buf[TID_RECV] = malloc(SZ); assert(buf[TID_RECV]);
	memset(buf[0], 0, SZ);
	memset(buf[1], 0, SZ);
	sem_init(&timeout, 0, 0);
	sem_init(&receiver_ready, 0, 0);
	generate_source_data(buf[TID_SEND], SZ);
	test_assert(memcmp(buf[TID_RECV], buf[TID_SEND], SZ), "buffer differ before sync");
	pthread_create(&tid[TID_SEND], NULL, &send_data_fec, buf[TID_SEND]);
	pthread_create(&tid[TID_RECV], NULL, &recv_data_fec, buf[TID_RECV]);
	clock_gettime(CLOCK_REALTIME, &ts);
	ts.tv_sec += WAITS;
	rc = sem_timedwait(&timeout, &ts);
	test_assert(rc == 0, "timeout");
	pthread_cancel(tid[TID_RECV]);
	pthread_join(tid[TID_RECV], NULL);
	pthread_join(tid[TID_SEND], NULL);
	sem_destroy(&receiver_ready);
	sem_destroy(&timeout);
	if (!rc) test_assert(!memcmp(buf[TID_RECV], buf[TID_SEND], SZ),
			"send buffer matches received");
#ifdef TESTDEBUG
	dump_bufs(buf);
#endif
	free(buf[TID_SEND]); free(buf[TID_RECV]);
	return test_status;
#endif
}
