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

/* symmetric encryption test using key on context (msg functions) */

#include "testnet.h"
#include <librecast/crypto.h>
#include <librecast/net.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>

#define WAITS 1

#ifdef HAVE_LIBSODIUM
static sem_t sem;
static ssize_t byt_recv, byt_sent, ohead;
static char channame[] = "0000-0058";
static char data[] = "black lives matter";
static unsigned char key[crypto_secretbox_KEYBYTES];
static int encryption_on;

void *testthread(void *arg)
{
	lc_ctx_t *lctx;
	lc_socket_t *sock;
	lc_channel_t *chan;
	lc_message_t *msg = (lc_message_t *)arg;
	char buf[BUFSIZ];

	lc_msg_init(msg);
	msg->data = buf;
	msg->len = BUFSIZ;

	lctx = lc_ctx_new();
	test_assert(lctx != NULL, "lc_ctx_new()");

	if (encryption_on) {
		lc_ctx_set_sym_key(lctx, key, crypto_secretbox_KEYBYTES);
		lc_ctx_coding_set(lctx, LC_CODE_SYMM);
		ohead = crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES;
	}

	sock = lc_socket_new(lctx);
	test_assert(sock != NULL, "lc_socket_new()");
	chan = lc_channel_new(lctx, channame);
	test_assert(chan != NULL, "lc_channel_new()");

	test_assert(lc_channel_bind(sock, chan) == 0, "lc_channel_bind()");
	test_assert(lc_channel_join(chan) == 0, "lc_channel_join()");

	sem_post(&sem); /* tell send thread we're ready */
	byt_recv = lc_msg_recv(sock, msg);

	lc_ctx_free(lctx);

	sem_post(&sem); /* tell send thread we're done */

	return arg;
}
#endif

int main(void)
{
#ifndef HAVE_LIBSODIUM
	return test_skip("lc_msg_send() / lc_msg_recv() - symmetric encryption");
#else
	lc_ctx_t *lctx;
	lc_socket_t *sock;
	lc_channel_t *chan;
	lc_message_t msg_out, msg_in;
	pthread_t thread;
	struct timespec ts;
	unsigned op;

	test_name("lc_msg_send() / lc_msg_recv() - symmetric encryption (ctx key)");
	test_require_net(TEST_NET_BASIC);

	/* fire up test thread */
	sem_init(&sem, 0, 0);
	pthread_create(&thread, NULL, &testthread, &msg_in);
	sem_wait(&sem); /* recv thread is ready */

	/* Librecast Context, Socket + Channel */
	lctx = lc_ctx_new();
	test_assert(lctx != NULL, "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);

	/* create socket and channel with key from ctx */
	sock = lc_socket_new(lctx);
	test_assert(sock != NULL, "lc_socket_new()");
	chan = lc_channel_new(lctx, channame);
	test_assert(chan != NULL, "lc_channel_new()");
	lc_socket_loop(sock, 1); /* talking to ourselves, set loopback */
	lc_channel_bind(sock, chan);

	/* send msg with PING opcode */
	op = LC_OP_PING;
	lc_msg_init_data(&msg_out, data, strlen(data), NULL, NULL);
	test_log("msg (out): '%.*s'\n", (int)msg_out.len, msg_out.data);
	lc_msg_set(&msg_out, LC_ATTR_OPCODE, &op);
	byt_sent = lc_msg_send(chan, &msg_out);
	lc_msg_free(&msg_out); /* clear struct before recv */

	/* wait for recv thread */
	test_assert(!clock_gettime(CLOCK_REALTIME, &ts), "clock_gettime()");
	ts.tv_sec += WAITS;
	test_assert(!sem_timedwait(&sem, &ts), "timeout");
	pthread_cancel(thread);
	pthread_join(thread, NULL);

	test_assert(memcmp(data, msg_in.data, strlen(data)) != 0, "encrypted data doesn't match");
	lc_msg_free(&msg_in);

	/* run testthread again with encryption key set */
	encryption_on = 1;
	pthread_create(&thread, NULL, &testthread, &msg_in);
	sem_wait(&sem); /* recv thread is ready */

	op = LC_OP_PING;
	lc_msg_init_data(&msg_out, data, strlen(data), NULL, NULL);
	test_log("msg (out): '%.*s'\n", (int)msg_out.len, msg_out.data);
	lc_msg_set(&msg_out, LC_ATTR_OPCODE, &op);
	byt_sent = lc_msg_send(chan, &msg_out);
	lc_msg_free(&msg_out); /* clear struct before recv */

	/* wait for recv thread */
	test_assert(!clock_gettime(CLOCK_REALTIME, &ts), "clock_gettime()");
	ts.tv_sec += WAITS;
	test_assert(!sem_timedwait(&sem, &ts), "timeout");
	sem_destroy(&sem);

	test_assert(memcmp(data, msg_in.data, strlen(data)) == 0, "decrypted data matches");
	test_assert(msg_in.op == LC_OP_PING, "opcode matches");

	test_assert(byt_sent - ohead == byt_recv, "bytes sent (%zi) == bytes received (%zi)",
			byt_sent, byt_recv);

	/* clean up */
	pthread_cancel(thread);
	pthread_join(thread, NULL);
	lc_msg_free(&msg_out);
	lc_msg_free(&msg_in);
	lc_ctx_free(lctx);

	return test_status;
#endif
}
