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

/* 4-port router test of port packet counters and traffic forwarding
 *
 *   +--------------+
 *   |  router (r)  |
 *   |  0  1  2  3  | ports (r->port[])
 *   +--------------+
 *      0  2  4  6
 *      |  |  |  | (sockets (s[])
 *      1  3  5  7
 *
 * There are 8 sockets (4 socketpairs) and 4 router ports.
 */

#include "test.h"
#include <librecast_pvt.h>
#include <librecast/router.h>

#define PORTS 4
#define USEC 100000

static void create_socketpairs_and_connect(lc_ctx_t *lctx, lc_router_t *r, lc_socket_t *s[])
{
	test_log("%s()\n", __func__);
	int rc;
	for (int i = 0; i < PORTS * 2; i += 2) {
		rc = lc_socketpair(lctx, &s[i]);
		test_assert(rc == 0, "lc_socketpair()[%i]", i);
		rc = lc_router_socket_add(r, s[i + 1]);
		test_assert(rc == 0, "lc_router_socket_add()[%i]", i);
	}
}

static void check_all_ports(lc_router_t *r, int status, char msg[])
{
	test_log("%s()\n", __func__);
	for (int i = 0; i < PORTS; i++) {
		test_assert((r->port[i]->status & status) == status, msg, i);
	}
}

static void check_byt_and_pkt_counters(lc_router_t *r,
		size_t byt_in[], unsigned int pkt_in[], size_t byt_out[], unsigned int pkt_out[])
{
	test_log("%s()\n", __func__);
	for (int i = 0; i < PORTS; i++) {
		unsigned int pktin = aload(&r->port[i]->pkt_in);
		unsigned int pktout = aload(&r->port[i]->pkt_out);
		unsigned int bytin = aload(&r->port[i]->byt_in);
		unsigned int bytout = aload(&r->port[i]->byt_out);
		test_assert(pktin == pkt_in[i],
				"port[%i] pkt recv = %u/%u", i, pktin, pkt_in[i]);
		test_assert(bytin == byt_in[i],
				"port[%i] byt recv = %zu/%zu", i, bytin, byt_in[i]);
		test_assert(pktout == pkt_out[i],
				"port[%i] pkt sent = %u/%u", i, pktout, pkt_out[i]);
		test_assert(bytout == byt_out[i],
				"port[%i] byt sent = %zu/%zu", i, bytout, byt_out[i]);
	}
}

static void send_pkt_on_port(int port, lc_socket_t *s[],
		size_t byt_in[], unsigned int pkt_in[],
		size_t byt_out[], unsigned int pkt_out[],
		int flags)
{
	test_log("%s() [%i]\n", __func__, port);
	char msg[] = "hello there";
	ssize_t byt;
	byt = send(s[port]->sock, msg, sizeof msg, 0);
	if (byt > 0) {
		byt_in[port] += byt;
		pkt_in[port]++;
	}
	if ((flags & LC_PORT_FLOOD) == LC_PORT_FLOOD) {
		for (int i = 0; i < (int)s[1]->router->ports; i++) {
			if (i != port) {
				if (byt > 0) {
					byt_out[i] += byt;
					pkt_out[i]++;
				}
			}
		}
	}
}

int main(void)
{
	lc_ctx_t *lctx;
	lc_router_t *r;
	lc_socket_t *s[PORTS * 2];
	size_t byt_in[PORTS] = {0};
	size_t byt_out[PORTS] = {0};
	unsigned int pkt_in[PORTS] = {0};
	unsigned int pkt_out[PORTS] = {0};
	int rc;

	test_name("router port packet counters");

	/* create context and router */
	lctx = lc_ctx_new();
	if (!test_assert(lctx != NULL, "lc_ctx_new()")) return test_status;
	r = lc_router_new(lctx, PORTS, LC_ROUTER_FLAG_FIXED);
	if (!test_assert(r != NULL, "lc_router_new() - %i-port router created", PORTS))
		goto err_ctx_free;

	/* create socketpairs and connect to router ports */
	create_socketpairs_and_connect(lctx, r, s);

	/* ensure packet counters are zeroed */
	check_byt_and_pkt_counters(r, byt_in, pkt_in, byt_out, pkt_out);

	/* bring up router ports, enable forwarding and start router */
	lc_router_port_up(r, -1);
	lc_router_port_set(r, -1, LC_PORT_FWD);
	check_all_ports(r, LC_PORT_UP|LC_PORT_FWD, "port[%i] status UP + FWD");
	rc = lc_router_start(r);
	if (!test_assert(rc == 0, "lc_router_start()")) goto err_ctx_free;

	usleep(USEC); /* give router a moment to wake up */

	/* send a packet on port 0 */
	send_pkt_on_port(0, s, byt_in, pkt_in, byt_out, pkt_out, 0);
	usleep(USEC); /* wait for router to process packets */
	check_byt_and_pkt_counters(r, byt_in, pkt_in, byt_out, pkt_out);

	/* turn on port flooding */
	test_log("turn on port flooding (LC_PORT_FLOOD)\n");
	lc_router_port_set(r, -1, LC_PORT_FLOOD);
	check_all_ports(r, LC_PORT_UP|LC_PORT_FWD|LC_PORT_FLOOD, "port[%i] status UP + FWD + FLOOD");

	send_pkt_on_port(0, s, byt_in, pkt_in, byt_out, pkt_out, LC_PORT_FLOOD);
	usleep(USEC); /* wait for router to process packets */
	check_byt_and_pkt_counters(r, byt_in, pkt_in, byt_out, pkt_out);

err_ctx_free:
	lc_ctx_free(lctx);
	return test_status;
}
