/* SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include "compiler.h"
#include "range.h"
#include "libmctp-log.h"
#include "libmctp-i2c.h"
#include "libmctp-sizes.h"
#include "libmctp-alloc.h"

/* For access to mctp_bninding_i2c internals */
#include "i2c-internal.h"

#ifdef NDEBUG
#undef NDEBUG
#endif

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

struct mctp_binding_serial_pipe {
	int ingress;
	int egress;

	struct mctp_binding_serial *serial;
};

// Sized to test fragmentation and >8 bit length
#define TEST_MSG_LEN 300
static uint8_t mctp_msg_src[TEST_MSG_LEN];

struct i2c_test {
	struct mctp_binding_i2c *i2c;
	struct mctp *mctp;

	uint8_t rx_msg[TEST_MSG_LEN];
	size_t rx_len;

	/* Physical addresses. These get set regardless of whether the packet
	 * is dropped by the stack (no match etc) */
	uint8_t last_rx_i2c_src;
	uint8_t last_tx_i2c_dst;
};

static const uint8_t I2C_ADDR_A = 0x20;
static const uint8_t I2C_ADDR_B = 0x21;
static const uint8_t EID_A = 50;
static const uint8_t EID_B = 51;

static int test_i2c_tx(const void *buf, size_t len, void *ctx)
{
	struct i2c_test *test_pair = ctx;
	struct i2c_test *tx_test = &test_pair[0];
	struct i2c_test *rx_test = &test_pair[1];

	mctp_prdebug("test_i2c_tx len %zu", len);

	const struct mctp_i2c_hdr *hdr = buf;
	tx_test->last_tx_i2c_dst = hdr->dest >> 1;
	rx_test->last_rx_i2c_src = hdr->source >> 1;

	mctp_i2c_rx(rx_test->i2c, buf, len);
	return 0;
}

static void test_i2c_rxmsg(uint8_t src_eid, bool tag_owner, uint8_t msg_tag,
			   void *ctx, void *msg, size_t len)
{
	struct i2c_test *test_pair = ctx;
	// struct i2c_test *tx_test = &test_pair[0];
	struct i2c_test *rx_test = &test_pair[1];

	mctp_prdebug("test_i2c_rx src %d len %zu tag %d owner %d", src_eid, len,
		     msg_tag, tag_owner);

	// Must be cleared by previous test runs
	assert(rx_test->rx_len == 0);
	memcpy(rx_test->rx_msg, msg, len);
	rx_test->rx_len = len;
}

/* Transmits a MCTP message and checks the received message matches */
static void run_tx_test(struct i2c_test *tx_test, uint8_t dest_eid,
			size_t tx_len, struct i2c_test *rx_test)
{
	int rc;
	const uint8_t msg_tag = 2;
	const bool tag_owner = false;

	assert(tx_len <= sizeof(mctp_msg_src));
	rc = mctp_message_tx(tx_test->mctp, dest_eid, tag_owner, msg_tag,
			     mctp_msg_src, tx_len);
	assert(rc == 0);

	while (!mctp_is_tx_ready(tx_test->mctp, dest_eid)) {
		mctp_i2c_tx_poll(tx_test->i2c);
	}

	assert(rx_test->rx_len == tx_len);
	assert(memcmp(rx_test->rx_msg, mctp_msg_src, tx_len) == 0);

	rx_test->rx_len = 0;
}

static void test_neigh_expiry(struct i2c_test *tx_test,
			      struct i2c_test *rx_test)
{
	const uint8_t msg_tag = 2;
	const bool tag_owner = true;
	const size_t msg_len = 5;
	int rc;

	(void)rx_test;

	/* Clear the tx neighbour table */
	memset(tx_test->i2c->neigh, 0x0, sizeof(tx_test->i2c->neigh));

	/* Check that all EIDs fail */
	rx_test->rx_len = 0;
	for (size_t eid = 8; eid < 254; eid++) {
		mctp_message_tx(tx_test->mctp, eid, tag_owner, msg_tag,
				mctp_msg_src, msg_len);
		/* Not received */
		assert(rx_test->rx_len == 0);
	}

	/* Add one entry */
	rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B,
				    rx_test->i2c->own_addr);
	assert(rc == 0);
	rx_test->rx_len = 0;
	mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src,
			msg_len);
	assert(rx_test->rx_len == msg_len);
	assert(tx_test->last_tx_i2c_dst == rx_test->i2c->own_addr);

	/* Replace the entry */
	rx_test->i2c->own_addr++;
	rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B,
				    rx_test->i2c->own_addr);
	assert(rc == 0);
	rx_test->rx_len = 0;
	mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src,
			msg_len);
	assert(rc == 0);
	assert(rx_test->rx_len == msg_len);
	assert(tx_test->last_tx_i2c_dst == rx_test->i2c->own_addr);

	/* Check only one entry is set */
	size_t count = 0;
	for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) {
		struct mctp_i2c_neigh *n = &tx_test->i2c->neigh[i];
		if (n->used) {
			assert(n->eid == EID_B);
			count++;
		}
	}
	assert(count == 1);

	/* Ensure we can iterate without overflow.
	 * If MCTP_I2C_NEIGH_COUNT increases too large this test would need rethinking
	 * (and eviction may become impossible) */
	assert((int)EID_B + MCTP_I2C_NEIGH_COUNT < 254);
	assert((int)I2C_ADDR_B + MCTP_I2C_NEIGH_COUNT < 0x7f);

	/* Fill entries. -1 because one was already filled. */
	for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT - 1; i++) {
		/* Unused addresses */
		uint8_t addr = rx_test->i2c->own_addr + i + 1;
		uint8_t eid = EID_B + i + 1;
		rc = mctp_i2c_set_neighbour(tx_test->i2c, eid, addr);
		assert(rc == 0);
	}

	/* Check all are used */
	for (size_t i = 0; i < MCTP_I2C_NEIGH_COUNT; i++) {
		struct mctp_i2c_neigh *n = &tx_test->i2c->neigh[i];
		assert(n->used);
	}

	/* Test eviction */
	{
		uint8_t addr =
			rx_test->i2c->own_addr + MCTP_I2C_NEIGH_COUNT + 1;
		uint8_t eid = EID_B + MCTP_I2C_NEIGH_COUNT + 1;
		rc = mctp_i2c_set_neighbour(tx_test->i2c, eid, addr);
		assert(rc == 0);

		/* EID_B got evicted, send should fail */
		rx_test->rx_len = 0;
		mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag,
				mctp_msg_src, msg_len);
		/* Not received */
		assert(rx_test->rx_len == 0);
	}

	/* Add EID_B again */
	rc = mctp_i2c_set_neighbour(tx_test->i2c, EID_B,
				    rx_test->i2c->own_addr);
	assert(rc == 0);
	rx_test->rx_len = 0;
	mctp_message_tx(tx_test->mctp, EID_B, tag_owner, msg_tag, mctp_msg_src,
			msg_len);
	/* Is received */
	assert(rx_test->rx_len == msg_len);
}

int main(void)
{
	struct i2c_test scenario[2];
	struct i2c_test *tx_test = &scenario[0];
	struct i2c_test *rx_test = &scenario[1];

	mctp_set_log_stdio(MCTP_LOG_DEBUG);

	memset(scenario, 0x0, sizeof(scenario));

	/* Setup a source buffer */
	for (size_t i = 0; i < sizeof(mctp_msg_src); i++) {
		mctp_msg_src[i] = i & 0xff;
	}

	tx_test->mctp = mctp_init();
	assert(tx_test->mctp);
	tx_test->i2c = malloc(MCTP_SIZEOF_BINDING_I2C);
	assert(tx_test->i2c);
	rx_test->mctp = mctp_init();
	assert(rx_test->mctp);
	rx_test->i2c = malloc(MCTP_SIZEOF_BINDING_I2C);
	assert(rx_test->i2c);

	/* TX side */
	mctp_i2c_setup(tx_test->i2c, I2C_ADDR_A, test_i2c_tx, scenario);
	mctp_register_bus(tx_test->mctp, mctp_binding_i2c_core(tx_test->i2c),
			  EID_A);
	mctp_set_rx_all(tx_test->mctp, NULL, NULL);
	mctp_i2c_set_neighbour(tx_test->i2c, EID_B, I2C_ADDR_B);

	/* RX side */
	mctp_i2c_setup(rx_test->i2c, I2C_ADDR_B, NULL, NULL);
	mctp_register_bus(rx_test->mctp, mctp_binding_i2c_core(rx_test->i2c),
			  EID_B);
	mctp_set_rx_all(rx_test->mctp, test_i2c_rxmsg, scenario);
	// mctp_i2c_set_neighbour(rx_test->i2c, EID_A, I2C_ADDR_A);

	/* Try all message sizes */
	for (size_t i = 1; i < sizeof(mctp_msg_src); i++) {
		run_tx_test(tx_test, EID_B, i, rx_test);
	}

	test_neigh_expiry(tx_test, rx_test);

	free(tx_test->i2c);
	free(rx_test->i2c);
	mctp_destroy(tx_test->mctp);
	mctp_destroy(rx_test->mctp);

	return 0;
}