/* SPDX-License-Identifier: BSD-3-Clause
 * Copyright(c) 2010-2014 Intel Corporation
 */

#include <string.h>
#include <rte_pipeline.h>
#include <rte_log.h>
#include <inttypes.h>
#include <rte_hexdump.h>
#include "test_table.h"
#include "test_table_pipeline.h"

#if 0

static rte_pipeline_port_out_action_handler port_action_0x00
	(struct rte_mbuf **pkts, uint32_t n, uint64_t *pkts_mask, void *arg);
static rte_pipeline_port_out_action_handler port_action_0xFF
	(struct rte_mbuf **pkts, uint32_t n, uint64_t *pkts_mask, void *arg);
static rte_pipeline_port_out_action_handler port_action_stub
	(struct rte_mbuf **pkts, uint32_t n, uint64_t *pkts_mask, void *arg);


rte_pipeline_port_out_action_handler port_action_0x00(struct rte_mbuf **pkts,
	uint32_t n,
	uint64_t *pkts_mask,
	void *arg)
{
	RTE_SET_USED(pkts);
	RTE_SET_USED(n);
	RTE_SET_USED(arg);
	printf("Port Action 0x00\n");
	*pkts_mask = 0x00;
	return 0;
}

rte_pipeline_port_out_action_handler port_action_0xFF(struct rte_mbuf **pkts,
	uint32_t n,
	uint64_t *pkts_mask,
	void *arg)
{
	RTE_SET_USED(pkts);
	RTE_SET_USED(n);
	RTE_SET_USED(arg);
	printf("Port Action 0xFF\n");
	*pkts_mask = 0xFF;
	return 0;
}

rte_pipeline_port_out_action_handler port_action_stub(struct rte_mbuf **pkts,
	uint32_t n,
	uint64_t *pkts_mask,
	void *arg)
{
	RTE_SET_USED(pkts);
	RTE_SET_USED(n);
	RTE_SET_USED(pkts_mask);
	RTE_SET_USED(arg);
	printf("Port Action stub\n");
	return 0;
}

#endif

rte_pipeline_table_action_handler_hit
table_action_0x00(struct rte_pipeline *p, struct rte_mbuf **pkts,
	uint64_t pkts_mask, struct rte_pipeline_table_entry **entry, void *arg);

rte_pipeline_table_action_handler_hit
table_action_stub_hit(struct rte_pipeline *p, struct rte_mbuf **pkts,
	uint64_t pkts_mask, struct rte_pipeline_table_entry **entry, void *arg);

static int
table_action_stub_miss(struct rte_pipeline *p, struct rte_mbuf **pkts,
	uint64_t pkts_mask, struct rte_pipeline_table_entry *entry, void *arg);

rte_pipeline_table_action_handler_hit
table_action_0x00(__attribute__((unused)) struct rte_pipeline *p,
	__attribute__((unused)) struct rte_mbuf **pkts,
	uint64_t pkts_mask,
	__attribute__((unused)) struct rte_pipeline_table_entry **entry,
	__attribute__((unused)) void *arg)
{
	printf("Table Action, setting pkts_mask to 0x00\n");
	pkts_mask = ~0x00;
	rte_pipeline_ah_packet_drop(p, pkts_mask);
	return 0;
}

rte_pipeline_table_action_handler_hit
table_action_stub_hit(__attribute__((unused)) struct rte_pipeline *p,
	__attribute__((unused)) struct rte_mbuf **pkts,
	uint64_t pkts_mask,
	__attribute__((unused)) struct rte_pipeline_table_entry **entry,
	__attribute__((unused)) void *arg)
{
	printf("STUB Table Action Hit - doing nothing\n");
	printf("STUB Table Action Hit - setting mask to 0x%"PRIx64"\n",
		override_hit_mask);
	pkts_mask = (~override_hit_mask) & 0x3;
	rte_pipeline_ah_packet_drop(p, pkts_mask);
	return 0;
}

static int
table_action_stub_miss(struct rte_pipeline *p,
	__attribute__((unused)) struct rte_mbuf **pkts,
	uint64_t pkts_mask,
	__attribute__((unused)) struct rte_pipeline_table_entry *entry,
	__attribute__((unused)) void *arg)
{
	printf("STUB Table Action Miss - setting mask to 0x%"PRIx64"\n",
		override_miss_mask);
	pkts_mask = (~override_miss_mask) & 0x3;
	rte_pipeline_ah_packet_drop(p, pkts_mask);
	return 0;
}

enum e_test_type {
	e_TEST_STUB = 0,
	e_TEST_LPM,
	e_TEST_LPM6,
	e_TEST_HASH_LRU_8,
	e_TEST_HASH_LRU_16,
	e_TEST_HASH_LRU_32,
	e_TEST_HASH_EXT_8,
	e_TEST_HASH_EXT_16,
	e_TEST_HASH_EXT_32
};

char pipeline_test_names[][64] = {
	"Stub",
	"LPM",
	"LPMv6",
	"8-bit LRU Hash",
	"16-bit LRU Hash",
	"32-bit LRU Hash",
	"16-bit Ext Hash",
	"8-bit Ext Hash",
	"32-bit Ext Hash",
	""
};


static int
cleanup_pipeline(void)
{

	rte_pipeline_free(p);

	return 0;
}


static int check_pipeline_invalid_params(void);

static int
check_pipeline_invalid_params(void)
{
	struct rte_pipeline_params pipeline_params_1 = {
		.name = NULL,
		.socket_id = 0,
	};
	struct rte_pipeline_params pipeline_params_2 = {
		.name = "PIPELINE",
		.socket_id = -1,
	};
	struct rte_pipeline_params pipeline_params_3 = {
		.name = "PIPELINE",
		.socket_id = 127,
	};

	p = rte_pipeline_create(NULL);
	if (p != NULL) {
		RTE_LOG(INFO, PIPELINE,
			"%s: configured pipeline with null params\n",
			__func__);
		goto fail;
	}
	p = rte_pipeline_create(&pipeline_params_1);
	if (p != NULL) {
		RTE_LOG(INFO, PIPELINE, "%s: Configure pipeline with NULL "
			"name\n", __func__);
		goto fail;
	}

	p = rte_pipeline_create(&pipeline_params_2);
	if (p != NULL) {
		RTE_LOG(INFO, PIPELINE, "%s: Configure pipeline with invalid "
			"socket\n", __func__);
		goto fail;
	}

	p = rte_pipeline_create(&pipeline_params_3);
	if (p != NULL) {
		RTE_LOG(INFO, PIPELINE, "%s: Configure pipeline with invalid "
			"socket\n", __func__);
		goto fail;
	}

	/* Check pipeline consistency */
	if (!rte_pipeline_check(p)) {
		rte_panic("Pipeline consistency reported as OK\n");
		goto fail;
	}


	return 0;
fail:
	return -1;
}


static int
setup_pipeline(int test_type)
{
	int ret;
	int i;
	struct rte_pipeline_params pipeline_params = {
		.name = "PIPELINE",
		.socket_id = 0,
	};

	RTE_LOG(INFO, PIPELINE, "%s: **** Setting up %s test\n",
		__func__, pipeline_test_names[test_type]);

	/* Pipeline configuration */
	p = rte_pipeline_create(&pipeline_params);
	if (p == NULL) {
		RTE_LOG(INFO, PIPELINE, "%s: Failed to configure pipeline\n",
			__func__);
		goto fail;
	}

	ret = rte_pipeline_free(p);
	if (ret != 0) {
		RTE_LOG(INFO, PIPELINE, "%s: Failed to free pipeline\n",
			__func__);
		goto fail;
	}

	/* Pipeline configuration */
	p = rte_pipeline_create(&pipeline_params);
	if (p == NULL) {
		RTE_LOG(INFO, PIPELINE, "%s: Failed to configure pipeline\n",
			__func__);
		goto fail;
	}


	/* Input port configuration */
	for (i = 0; i < N_PORTS; i++) {
		struct rte_port_ring_reader_params port_ring_params = {
			.ring = rings_rx[i],
		};

		struct rte_pipeline_port_in_params port_params = {
			.ops = &rte_port_ring_reader_ops,
			.arg_create = (void *) &port_ring_params,
			.f_action = NULL,
			.burst_size = BURST_SIZE,
		};

		/* Put in action for some ports */
		if (i)
			port_params.f_action = NULL;

		ret = rte_pipeline_port_in_create(p, &port_params,
			&port_in_id[i]);
		if (ret) {
			rte_panic("Unable to configure input port %d, ret:%d\n",
				i, ret);
			goto fail;
		}
	}

	/* output Port configuration */
	for (i = 0; i < N_PORTS; i++) {
		struct rte_port_ring_writer_params port_ring_params = {
			.ring = rings_tx[i],
			.tx_burst_sz = BURST_SIZE,
		};

		struct rte_pipeline_port_out_params port_params = {
			.ops = &rte_port_ring_writer_ops,
			.arg_create = (void *) &port_ring_params,
			.f_action = NULL,
			.arg_ah = NULL,
		};

		if (i)
			port_params.f_action = port_out_action;

		if (rte_pipeline_port_out_create(p, &port_params,
			&port_out_id[i])) {
			rte_panic("Unable to configure output port %d\n", i);
			goto fail;
		}
	}

	/* Table configuration  */
	for (i = 0; i < N_PORTS; i++) {
		struct rte_pipeline_table_params table_params = {
				.ops = &rte_table_stub_ops,
				.arg_create = NULL,
				.f_action_hit = action_handler_hit,
				.f_action_miss = action_handler_miss,
				.action_data_size = 0,
		};

		if (rte_pipeline_table_create(p, &table_params, &table_id[i])) {
			rte_panic("Unable to configure table %u\n", i);
			goto fail;
		}

		if (connect_miss_action_to_table)
			if (rte_pipeline_table_create(p, &table_params,
				&table_id[i+2])) {
				rte_panic("Unable to configure table %u\n", i);
				goto fail;
			}
	}

	for (i = 0; i < N_PORTS; i++)
		if (rte_pipeline_port_in_connect_to_table(p, port_in_id[i],
			table_id[i])) {
			rte_panic("Unable to connect input port %u to "
				"table %u\n", port_in_id[i],  table_id[i]);
			goto fail;
		}

	/* Add entries to tables */
	for (i = 0; i < N_PORTS; i++) {
		struct rte_pipeline_table_entry default_entry = {
			.action = (enum rte_pipeline_action)
				table_entry_default_action,
			{.port_id = port_out_id[i^1]},
		};
		struct rte_pipeline_table_entry *default_entry_ptr;

		if (connect_miss_action_to_table) {
			printf("Setting first table to output to next table\n");
			default_entry.action = RTE_PIPELINE_ACTION_TABLE;
			default_entry.table_id = table_id[i+2];
		}

		/* Add the default action for the table. */
		ret = rte_pipeline_table_default_entry_add(p, table_id[i],
			&default_entry, &default_entry_ptr);
		if (ret < 0) {
			rte_panic("Unable to add default entry to table %u "
				"code %d\n", table_id[i], ret);
			goto fail;
		} else
			printf("Added default entry to table id %d with "
				"action %x\n",
				table_id[i], default_entry.action);

		if (connect_miss_action_to_table) {
			/* We create a second table so the first can pass
			traffic into it */
			struct rte_pipeline_table_entry default_entry = {
				.action = RTE_PIPELINE_ACTION_PORT,
				{.port_id = port_out_id[i^1]},
			};
			printf("Setting secont table to output to port\n");

			/* Add the default action for the table. */
			ret = rte_pipeline_table_default_entry_add(p,
				table_id[i+2],
				&default_entry, &default_entry_ptr);
			if (ret < 0) {
				rte_panic("Unable to add default entry to "
					"table %u code %d\n",
					table_id[i], ret);
				goto fail;
			} else
				printf("Added default entry to table id %d "
					"with action %x\n",
					table_id[i], default_entry.action);
		}
	}

	/* Enable input ports */
	for (i = 0; i < N_PORTS ; i++)
		if (rte_pipeline_port_in_enable(p, port_in_id[i]))
			rte_panic("Unable to enable input port %u\n",
				port_in_id[i]);

	/* Check pipeline consistency */
	if (rte_pipeline_check(p) < 0) {
		rte_panic("Pipeline consistency check failed\n");
		goto fail;
	} else
		printf("Pipeline Consistency OK!\n");

	return 0;
fail:

	return -1;
}

static int
test_pipeline_single_filter(int test_type, int expected_count)
{
	int i;
	int j;
	int ret;
	int tx_count;

	RTE_LOG(INFO, PIPELINE, "%s: **** Running %s test\n",
		__func__, pipeline_test_names[test_type]);
	/* Run pipeline once */
	for (i = 0; i < N_PORTS; i++)
		rte_pipeline_run(p);


	ret = rte_pipeline_flush(NULL);
	if (ret != -EINVAL) {
		RTE_LOG(INFO, PIPELINE,
			"%s: No pipeline flush error NULL pipeline (%d)\n",
			__func__, ret);
		goto fail;
	}

	/*
	 * Allocate a few mbufs and manually insert into the rings. */
	for (i = 0; i < N_PORTS; i++)
		for (j = 0; j < N_PORTS; j++) {
			struct rte_mbuf *m;
			uint8_t *key;
			uint32_t *k32;

			m = rte_pktmbuf_alloc(pool);
			if (m == NULL) {
				rte_panic("Failed to alloc mbuf from pool\n");
				return -1;
			}
			key = RTE_MBUF_METADATA_UINT8_PTR(m,
					APP_METADATA_OFFSET(32));

			k32 = (uint32_t *) key;
			k32[0] = 0xadadadad >> (j % 2);

			RTE_LOG(INFO, PIPELINE, "%s: Enqueue onto ring %d\n",
				__func__, i);
			rte_ring_enqueue(rings_rx[i], m);
		}

	/* Run pipeline once */
	for (i = 0; i < N_PORTS; i++)
		rte_pipeline_run(p);

   /*
	* need to flush the pipeline, as there may be less hits than the burst
	size and they will not have been flushed to the tx rings. */
	rte_pipeline_flush(p);

   /*
	* Now we'll see what we got back on the tx rings. We should see whatever
	* packets we had hits on that were destined for the output ports.
	*/
	tx_count = 0;

	for (i = 0; i < N_PORTS; i++) {
		void *objs[RING_TX_SIZE];
		struct rte_mbuf *mbuf;

		ret = rte_ring_sc_dequeue_burst(rings_tx[i], objs, 10, NULL);
		if (ret <= 0)
			printf("Got no objects from ring %d - error code %d\n",
				i, ret);
		else {
			printf("Got %d object(s) from ring %d!\n", ret, i);
			for (j = 0; j < ret; j++) {
				mbuf = objs[j];
				rte_hexdump(stdout, "Object:",
					rte_pktmbuf_mtod(mbuf, char *),
					mbuf->data_len);
				rte_pktmbuf_free(mbuf);
			}
			tx_count += ret;
		}
	}

	if (tx_count != expected_count) {
		RTE_LOG(INFO, PIPELINE,
			"%s: Unexpected packets out for %s test, expected %d, "
			"got %d\n", __func__, pipeline_test_names[test_type],
			expected_count, tx_count);
		goto fail;
	}

	cleanup_pipeline();

	return 0;
fail:
	return -1;

}

int
test_table_pipeline(void)
{
	/* TEST - All packets dropped */
	action_handler_hit = NULL;
	action_handler_miss = NULL;
	table_entry_default_action = RTE_PIPELINE_ACTION_DROP;
	setup_pipeline(e_TEST_STUB);
	if (test_pipeline_single_filter(e_TEST_STUB, 0) < 0)
		return -1;

	/* TEST - All packets passed through */
	table_entry_default_action = RTE_PIPELINE_ACTION_PORT;
	setup_pipeline(e_TEST_STUB);
	if (test_pipeline_single_filter(e_TEST_STUB, 4) < 0)
		return -1;

	/* TEST - one packet per port */
	action_handler_hit = NULL;
	action_handler_miss = table_action_stub_miss;
	table_entry_default_action = RTE_PIPELINE_ACTION_PORT;
	override_miss_mask = 0x01; /* one packet per port */
	setup_pipeline(e_TEST_STUB);
	if (test_pipeline_single_filter(e_TEST_STUB, 2) < 0)
		return -1;

	/* TEST - one packet per port */
	override_miss_mask = 0x02; /*all per port */
	setup_pipeline(e_TEST_STUB);
	if (test_pipeline_single_filter(e_TEST_STUB, 2) < 0)
		return -1;

	/* TEST - all packets per port */
	override_miss_mask = 0x03; /*all per port */
	setup_pipeline(e_TEST_STUB);
	if (test_pipeline_single_filter(e_TEST_STUB, 4) < 0)
		return -1;

   /*
	* This test will set up two tables in the pipeline. the first table
	* will forward to another table on miss, and the second table will
	* forward to port.
	*/
	connect_miss_action_to_table = 1;
	table_entry_default_action = RTE_PIPELINE_ACTION_TABLE;
	action_handler_hit = NULL;  /* not for stub, hitmask always zero */
	action_handler_miss = NULL;
	setup_pipeline(e_TEST_STUB);
	if (test_pipeline_single_filter(e_TEST_STUB, 4) < 0)
		return -1;
	connect_miss_action_to_table = 0;

	printf("TEST - two tables, hitmask override to 0x01\n");
	connect_miss_action_to_table = 1;
	action_handler_miss = table_action_stub_miss;
	override_miss_mask = 0x01;
	setup_pipeline(e_TEST_STUB);
	if (test_pipeline_single_filter(e_TEST_STUB, 2) < 0)
		return -1;
	connect_miss_action_to_table = 0;

	if (check_pipeline_invalid_params()) {
		RTE_LOG(INFO, PIPELINE, "%s: Check pipeline invalid params "
			"failed.\n", __func__);
		return -1;
	}

	return 0;
}
