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

/* The Butterfly Network
 *
 *    s0      s1
 *    |       |
 *   (r0)   (r1)
 *    |\     /|
 *    | \   / |
 *    |  (r2) |
 *    |   |   |
 *    |   |   |
 *    |  (r3) |
 *    | /   \ |
 *    |/     \|
 *   (r4)   (r5)
 *    |       |
 *    s2      s3
 *
 *    r[0-5] - routers
 *    s[0]   - socket on which A is sent
 *    s[1]   - socket on which B is sent
 *    s[2-3] - sockets on which A and B MUST be received
 */

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

#define ROUTERS 6 /* total routers */
#define SOCKS 4   /* socket pairs */

int main(void)
{
	lc_ctx_t *lctx;
	lc_router_t *r[ROUTERS];
	lc_socket_t *sv[SOCKS * 2] = {0};
	lc_socket_t *s[4];
	int rc;

	test_name("lc_router_net() - LC_TOPO_BUTTERFLY");

	lctx = lc_ctx_new();
	if (!test_assert(lctx != NULL, "lc_ctx_new()")) return test_status;

	/* first, try failure modes */
	errno = 0;
	rc = lc_router_net(lctx, r, 7, LC_TOPO_BUTTERFLY, 0, LC_ROUTER_FLAG_FIXED);
	test_assert(errno == EINVAL, "lc_router_net() errno = EINVAL when n != 6");
	test_assert(rc == -1, "lc_router_net() return -1 when n != 6");

	/* create a butterfly topology */
	rc = lc_router_net(lctx, r, ROUTERS, LC_TOPO_BUTTERFLY, 0, LC_ROUTER_FLAG_FIXED);
	if (rc) perror("lc_router_net");
	if (!test_assert(rc == 0, "lc_router_net()")) goto err_ctx_free;
	for (int i = 0; i < ROUTERS; i++) {
		test_log("====== ROUTER %i\n", i);

		/* check router parameters */
		if (!test_assert(r[i] != NULL, "r[%i] allocated", i))
			goto err_ctx_free;
		if (!test_assert(r[i]->ctx == lctx, "context set"))
			goto err_ctx_free;
		if (!test_assert(r[i]->flags == LC_ROUTER_FLAG_FIXED, "flags set"))
			goto err_ctx_free;
		if (!test_assert(r[i]->ports == 3, "ports == %i", r[i]->ports))
			goto err_ctx_free;
	}

	/* create socketpairs (these aren't part of the router topology itself) */
	for (int i = 0; i < SOCKS * 2; i += 2) {
		rc = lc_socketpair(lctx, &sv[i]);
		test_assert(rc == 0, "lc_socketpair()[%i]", i);
	}
	s[0] = sv[1];
	s[1] = sv[3];
	s[2] = sv[5];
	s[3] = sv[7];
	rc = lc_router_socket_add(r[0], s[0]->pair);
	test_assert(rc == 0, "lc_router_socket_add() R[0], S[0]");
	rc = lc_router_socket_add(r[1], s[1]->pair);
	test_assert(rc == 0, "lc_router_socket_add() R[1], S[1]");
	rc = lc_router_socket_add(r[4], s[2]->pair);
	test_assert(rc == 0, "lc_router_socket_add() R[4], S[2]");
	rc = lc_router_socket_add(r[5], s[3]->pair);
	test_assert(rc == 0, "lc_router_socket_add() R[5], S[3]");

	/* now check ports are connected correctly */
	for (int i = 0; i < SOCKS; i++) {
		if (!test_assert(s[i] != NULL, "socket s[%i] allocated", i))
			goto err_ctx_free;
		test_assert(s[i]->type == LC_SOCK_PAIR, "s[%i] is socketpair");
		test_assert(s[i]->type == LC_SOCK_PAIR, "s[%i] is socketpair");
	}
	for (int i = 0; i < ROUTERS; i++) {
		/* all routers have all 3 ports connected */
		for (int port = 0; port < 3; port++) {
			if (!(test_assert(r[i]->port[port] != NULL, "R[%i]P[%i] connected", i, port)))
				goto err_ctx_free;
		}
	}
	test_assert(r[0]->port[0]->pair == r[2]->port[0], "R[0]P[0] <--> R[2]P[0]");
	test_assert(r[0]->port[1]->pair == r[4]->port[0], "R[0]P[1] <--> R[4]P[0]");
	test_assert(r[1]->port[0]->pair == r[2]->port[1], "R[1]P[0] <--> R[2]P[1]");
	test_assert(r[1]->port[1]->pair == r[5]->port[0], "R[1]P[1] <--> R[5]P[0]");
	test_assert(r[2]->port[2]->pair == r[3]->port[0], "R[2]P[2] <--> R[3]P[0]");
	test_assert(r[3]->port[1]->pair == r[4]->port[1], "R[3]P[1] <--> R[4]P[1]");
	test_assert(r[3]->port[2]->pair == r[5]->port[1], "R[3]P[2] <--> R[5]P[1]");
	test_assert(r[0]->port[2]->pair == s[0], "R[0]P[2] <--> S[0]");
	test_assert(r[1]->port[2]->pair == s[1], "R[1]P[2] <--> S[1]");
	test_assert(r[4]->port[2]->pair == s[2], "R[4]P[2] <--> S[2]");
	test_assert(r[5]->port[2]->pair == s[3], "R[5]P[2] <--> S[3]");

	/* bring up all ports on all routers */
	for (int i = 0; i < ROUTERS; i++) lc_router_port_up(r[i], -1);
	test_assert(lc_router_net_hasloop(r, ROUTERS), "routing loop expected");
err_ctx_free:
	lc_ctx_free(lctx);
	return test_status;
}
