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

#define WAITS 1

#ifdef HAVE_LIBSODIUM
static char channel_name[] = "0000-0060";
static sem_t sem;
static ssize_t bytes = -1;
static unsigned char key[crypto_secretbox_KEYBYTES];
static int encryption_on = 0;
static char recvbuf[8];
static char recvbuf2[8];

static void *listen_thread(void *arg)
{
	lc_ctx_t * lctx;
	lc_socket_t * sock;
	lc_channel_t * chan;
	struct iovec iov[2];
	struct msghdr msg = {0};

	lctx = lc_ctx_new();

	if (encryption_on) {
		lc_ctx_set_sym_key(lctx, key, crypto_secretbox_KEYBYTES);
		lc_ctx_coding_set(lctx, LC_CODE_SYMM);
	}

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

	iov[0].iov_base = recvbuf;
	iov[0].iov_len = sizeof recvbuf;
	iov[1].iov_base = recvbuf2;
	iov[1].iov_len = sizeof recvbuf2;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;

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

	sem_post(&sem); /* tell send thread we're ready */
	bytes = lc_socket_recvmsg(sock, &msg, 0);
	sem_post(&sem); /* tell send thread we're done */

	lc_ctx_free(lctx);
	return arg;
}
#endif

int main(void)
{
#ifndef HAVE_LIBSODIUM
	return test_skip("lc_channel_sendmsg() / lc_socket_recvmsg() - symmetric encryption");
#else
	lc_ctx_t * lctx;
	lc_socket_t * sock;
	lc_channel_t * chan;
	pthread_t thread;
	struct timespec ts;
	char buf[8] = "liberte";
	char buf2[8] = "egalite";
	struct iovec iov[2];
	struct msghdr msg = {0};
	ssize_t sentbyt;

	test_name("lc_channel_sendmsg() / lc_socket_recvmsg() - symmetric encryption (enc)");
	test_require_net(TEST_NET_BASIC);

	sem_init(&sem, 0, 0);

	pthread_create(&thread, NULL, &listen_thread, &recvbuf);

	sem_wait(&sem); /* recv thread is ready */

	lctx = lc_ctx_new();

	/* generate and set symmetric key on sender */
	crypto_secretbox_keygen(key);
	lc_ctx_set_sym_key(lctx, key, crypto_secretbox_KEYBYTES);
	lc_ctx_coding_set(lctx, LC_CODE_SYMM);

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

	iov[0].iov_base = buf;
	iov[0].iov_len = strlen(buf) + 1;
	iov[1].iov_base = buf2;
	iov[1].iov_len = strlen(buf2) + 1;
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	sentbyt = 0;
	for (size_t z = 0; z < (size_t)msg.msg_iovlen; z++) sentbyt += iov[z].iov_len;

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

	lc_channel_sendmsg(chan, &msg, 0);

	test_assert(!clock_gettime(CLOCK_REALTIME, &ts), "clock_gettime()");
	ts.tv_sec += WAITS;
	test_assert(!sem_timedwait(&sem, &ts), "timeout");
	sem_timedwait(&sem, &ts);

	pthread_cancel(thread);
	pthread_join(thread, NULL);

	test_assert(bytes == sentbyt,
			"0: received %zi bytes, expected %zu",
			bytes, sentbyt);

	/* we can't really prove the data is encrypted, but we can at least
	 * check it isn't the same as what we sent */
	test_assert(memcmp(buf, recvbuf, sizeof buf - 1) != 0, "data doesn't match with encryption");

	/* now receive again with key */
	encryption_on = 1;
	pthread_create(&thread, NULL, &listen_thread, &recvbuf);
	sem_wait(&sem); /* recv thread is ready */

	iov[0].iov_base = buf;
	iov[0].iov_len = strlen(buf) + 1;
	test_log("iov[0].iov_len = %zu\n", iov[0].iov_len);
	iov[1].iov_base = buf2;
	iov[1].iov_len = strlen(buf2) + 1;
	test_log("iov[1].iov_len = %zu\n", iov[1].iov_len);
	msg.msg_iov = iov;
	msg.msg_iovlen = 2;
	sentbyt = 0;
	for (size_t z = 0; z < (size_t)msg.msg_iovlen; z++) sentbyt += iov[z].iov_len;
	lc_channel_sendmsg(chan, &msg, 0);

	test_assert(!clock_gettime(CLOCK_REALTIME, &ts), "clock_gettime()");
	ts.tv_sec += WAITS;
	test_assert(!sem_timedwait(&sem, &ts), "timeout");
	sem_timedwait(&sem, &ts);
	sem_destroy(&sem);

	pthread_cancel(thread);
	pthread_join(thread, NULL);

	test_assert(bytes == sentbyt,
			"1: received %zi bytes, expected %zu",
			bytes, sentbyt);

	test_assert(memcmp(buf, recvbuf, sizeof buf - 1) == 0, "data decrypts [0]");
	test_assert(memcmp(buf2, recvbuf2, sizeof buf2 - 1) == 0, "data decrypts [1]");

	lc_ctx_free(lctx);

	return test_status;
#endif
}
