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

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

#define SZ PKTS * PKTSZ + 5

#define WAITS 1

#ifdef HAVE_RQ_OTI
enum {
	TID_SEND,
	TID_RECV
};

struct oti_header {
	rq_oti_t oti;
	rq_scheme_t scheme;
};

static char channel_name[] = "0000-0045";
static sem_t receiver_ready, timeout;

static void *recv_oti(void *arg)
{
	struct oti_header *oti = (struct oti_header *)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);


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

	sem_post(&receiver_ready);

	lc_channel_oti_peek(chan, &oti->oti, &oti->scheme);

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

static void *send_oti(void *arg)
{
	struct oti_header *oti = (struct oti_header *)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);
	char buf[] = "Celebrating Pride Month June 2023: LGBTQ++";

	lc_socket_loop(sock, 1);
	lc_channel_bind(sock, chan);

	sem_wait(&receiver_ready);
	test_log("sending data with OTI header");
	lc_channel_coding_set(chan, LC_CODE_FEC_RQ | LC_CODE_FEC_OTI);
	lc_channel_send(chan, buf, sizeof buf, 0);
	rq_oti(lc_channel_rq(chan), &oti->oti, &oti->scheme);

	for (uint16_t i = 0; i < 100 + 5; i++) {
		lc_channel_send(chan, NULL, 0, 0);
	}

	lc_ctx_free(lctx);
	return arg;
}
#endif

int main(void)
{
	char name[] = "RaptorQ FEC Object Transmission Information";
#ifndef HAVE_RQ_OTI
	return test_skip(name);
#else
	pthread_t tid[2];
	struct oti_header oti[2];
	struct timespec ts;

	test_name(name);
	test_require_net(TEST_NET_BASIC);

	oti[0].oti = 1;
	oti[1].oti = 2;
	oti[0].scheme = 1;
	oti[1].scheme = 2;
	test_assert(oti[TID_SEND].oti != oti[TID_RECV].oti, "OTI (common) differ before test");
	test_assert(oti[TID_SEND].scheme != oti[TID_RECV].scheme, "OTI (scheme) differ before test");
	sem_init(&timeout, 0, 0);
	sem_init(&receiver_ready, 0, 0);
	pthread_create(&tid[TID_SEND], NULL, &send_oti, &oti[TID_SEND]);
	pthread_create(&tid[TID_RECV], NULL, &recv_oti, &oti[TID_RECV]);
	clock_gettime(CLOCK_REALTIME, &ts);
	ts.tv_sec += WAITS;
	test_assert(!sem_timedwait(&timeout, &ts), "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);

	test_assert(oti[TID_SEND].oti == oti[TID_RECV].oti, "OTI (common) matches");
	test_assert(oti[TID_SEND].scheme == oti[TID_RECV].scheme, "OTI (scheme) matches");

	return test_status;
#endif
}
