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

#include "test.h"
#include "testdata.h"
#include "testnet.h"
#include <librecast/mtree.h>
#include <librecast/net.h>
#include <librecast/sync.h>
#include <errno.h>
#include <pthread.h>

#if HAVE_RQ_OTI
#define TEST_SIZE MTREE_CHUNKSIZE * 100 + 5
/* nice powers of two hide bugs - ensure test size is odd */
_Static_assert(TEST_SIZE % 2, "TEST_SIZE must be an odd number");

#define TIMEOUT_SECONDS 60

enum {
	TID_SEND,
	TID_RECV
};

static sem_t sem_recv;

struct pkg_s {
	void *data;
	size_t len;
	uint8_t *hash;
};

void *thread_recv(void *arg)
{
	struct pkg_s *pkg = (struct pkg_s *)arg;
	lc_ctx_t *lctx = lc_ctx_new();
	pthread_cleanup_push((void (*)(void *))lc_ctx_free, lctx);
	lc_recvchunk(lctx, pkg->hash, pkg->data, pkg->len, NULL, NULL, 0);
	pthread_cleanup_pop(1); /* lc_ctx_free */
	sem_post(&sem_recv);
	return NULL;
}

void *thread_send(void *arg)
{
	struct pkg_s *pkg = (struct pkg_s *)arg;
	lc_ctx_t *lctx = lc_ctx_new();
	pthread_cleanup_push((void (*)(void *))lc_ctx_free, lctx);
	lc_sendchunk(lctx, pkg->hash, pkg->data, pkg->len, NULL, NULL, NET_LOOPBACK);
	pthread_cleanup_pop(1);
	return NULL;
}
#endif /* HAVE_RQ_OTI */

int main(void)
{
	char name[] = "lc_sendchunk()/lc_recvchunk()";
#if HAVE_RQ_OTI
	uint8_t *src, *dst;
	struct pkg_s pkg_send = {0}, pkg_recv = {0};
	pthread_t tid[2];
	struct timespec timeout = {0};
	int rc;

	test_name(name);
	test_require_net(TEST_NET_BASIC);

	/* allocate some memory to work with */
	src = malloc(TEST_SIZE);
	test_assert(src != NULL, "allocate source");
	if (!src) return test_status;
	dst = malloc(TEST_SIZE);
	test_assert(dst != NULL, "allocate destination");
	if (!dst) goto err_free_src;
	memset(dst, 0, TEST_SIZE);

	test_assert(!test_random_bytes(src, TEST_SIZE), "randomize src");
	test_assert(memcmp(dst, src, TEST_SIZE), "dst and src differ");
	if (test_status != TEST_OK) goto err_free_dst;

	/* find hash of source */
	mtree_t stree = {0};
	rc = mtree_init(&stree, TEST_SIZE);
	if (!test_assert(rc == 0, "mtree_init returned %i", rc)) goto err_free_dst;
	mtree_build(&stree, src, NULL);

	/* sync */

	rc = sem_init(&sem_recv, 0, 0);
	if (!test_assert(rc == 0, "sem_init returned %i", rc)) goto err_free_stree;

	/* start send thread */
	pkg_send.data = src;
	pkg_send.len = TEST_SIZE;
	pkg_send.hash = stree.tree;
	rc = pthread_create(&tid[TID_SEND], NULL, thread_send, &pkg_send);
	if (!test_assert(rc == 0, "pthread_create(TID_SEND) returned %i", rc)) goto err_sem_recv_destroy;

	/* start recv thread */
	pkg_recv.data = dst;
	pkg_recv.len = TEST_SIZE;
	pkg_recv.hash = stree.tree;
	rc = pthread_create(&tid[TID_RECV], NULL, thread_recv, &pkg_recv);
	if (!test_assert(rc == 0, "pthread_create(TID_RECV) returned %i", rc)) goto err_cancel_tid_send;

	/* handle timeout */
	rc = clock_gettime(CLOCK_REALTIME, &timeout);
	if (!test_assert(rc == 0, "clock_gettime returned %i", rc)) goto err_cancel_tid_recv;
	timeout.tv_sec += TIMEOUT_SECONDS;
	if ((rc = sem_timedwait(&sem_recv, &timeout)) == -1 && errno == ETIMEDOUT) {
		test_assert(0, "timeout waiting for recv thread");
		goto err_cancel_tid_recv;
	}

	/* check what we received */
	test_assert(!memcmp(dst, src, TEST_SIZE), "dst matches src");

	/* clean up */
err_cancel_tid_recv:
	pthread_cancel(tid[TID_RECV]);
	pthread_join(tid[TID_RECV], NULL);
err_cancel_tid_send:
	pthread_cancel(tid[TID_SEND]);
	pthread_join(tid[TID_SEND], NULL);
err_sem_recv_destroy:
	sem_destroy(&sem_recv);
err_free_stree:
	mtree_free(&stree);
err_free_dst:
	free(dst);
err_free_src:
	free(src);
	return test_status;
#else
	return test_skip(name);
#endif
}
