// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2024 Christophe Fontaine

#include "control_input.h"
#include "graph.h"
#include "ip4.h"
#include "ip4_datapath.h"
#include "mbuf.h"

#include <gr_clock.h>

#include <rte_icmp.h>

#include <netinet/in.h>

enum {
	OUTPUT = 0,
	EDGE_COUNT,
};

struct ctl_to_stack {
	ip4_addr_t dst;
	ip4_addr_t src;
	uint16_t vrf_id;
	uint16_t ident;
	uint16_t seq_num;
	uint8_t ttl;
};

static control_input_t ip4_icmp_request;

int icmp_local_send(
	uint16_t vrf_id,
	ip4_addr_t dst,
	const struct nexthop *gw,
	uint16_t ident,
	uint16_t seq_num,
	uint8_t ttl
) {
	const struct nexthop_info_l3 *l3;
	const struct nexthop *local;
	struct ctl_to_stack *msg;
	int ret;

	if (gw->type == GR_NH_T_GROUP) {
		struct nexthop_info_group *g = (struct nexthop_info_group *)gw->info;
		gw = nexthop_group_get_nh(g, ident);
		if (unlikely(gw == NULL))
			return errno_set(EHOSTUNREACH);
	}

	// FIXME
	if (gw->type != GR_NH_T_L3)
		return errno_set(ENONET);

	if ((msg = calloc(1, sizeof(struct ctl_to_stack))) == NULL)
		return errno_set(ENOMEM);

	msg->seq_num = seq_num;
	msg->vrf_id = vrf_id;
	msg->ident = ident;
	msg->ttl = ttl;
	msg->dst = dst;

	l3 = nexthop_info_l3(gw);

	if ((local = addr4_get_preferred(gw->iface_id, l3->ipv4)) == NULL) {
		free(msg);
		return -errno;
	}

	l3 = nexthop_info_l3(local);
	msg->src = l3->ipv4;

	if ((ret = post_to_stack(ip4_icmp_request, msg)) < 0) {
		free(msg);
		return ret;
	}

	return 0;
}

static uint16_t icmp_local_send_process(
	struct rte_graph *graph,
	struct rte_node *node,
	void **objs,
	uint16_t n_objs
) {
	struct ip_local_mbuf_data *data;
	struct rte_icmp_hdr *icmp;
	struct ctl_to_stack *msg;
	gr_clock_ns_t *payload;
	struct rte_mbuf *mbuf;
	rte_edge_t next;

	for (unsigned i = 0; i < n_objs; i++) {
		mbuf = objs[i];
		msg = control_input_mbuf_data(mbuf)->data;
		icmp = (struct rte_icmp_hdr *)rte_pktmbuf_append(
			mbuf, sizeof(*icmp) + sizeof(gr_clock_ns_t)
		);

		payload = rte_pktmbuf_mtod_offset(mbuf, gr_clock_ns_t *, sizeof(*icmp));
		*payload = gr_clock_ns();

		// Build ICMP packet
		icmp->icmp_type = RTE_ICMP_TYPE_ECHO_REQUEST;
		icmp->icmp_code = 0;
		icmp->icmp_seq_nb = rte_cpu_to_be_16(msg->seq_num);
		icmp->icmp_ident = rte_cpu_to_be_16(msg->ident);

		// Fake RSS to spread the traffic
		// for ECMP routes or active/active bonds.
		mbuf->hash.rss = msg->ident;
		mbuf->ol_flags |= RTE_MBUF_F_RX_RSS_HASH;

		data = ip_local_mbuf_data(mbuf);
		data->proto = IPPROTO_ICMP;
		data->len = sizeof(*icmp) + sizeof(gr_clock_ns_t);
		data->dst = msg->dst;
		data->src = msg->src;
		data->vrf_id = msg->vrf_id;
		data->ttl = msg->ttl;

		next = OUTPUT;

		if (gr_mbuf_is_traced(mbuf)) {
			struct rte_icmp_hdr *t = gr_mbuf_trace_add(mbuf, node, sizeof(*t));
			*t = *icmp;
		}
		rte_node_enqueue_x1(graph, node, next, mbuf);
		free(msg);
	}

	return n_objs;
}

static void icmp_local_send_register(void) {
	ip4_icmp_request = gr_control_input_register_handler("icmp_local_send", false);
}

static struct rte_node_register icmp_local_send_node = {
	.name = "icmp_local_send",
	.process = icmp_local_send_process,
	.nb_edges = EDGE_COUNT,
	.next_nodes = {
		[OUTPUT] = "icmp_output",
	},
};

static struct gr_node_info icmp_local_send_info = {
	.node = &icmp_local_send_node,
	.type = GR_NODE_T_CONTROL | GR_NODE_T_L4,
	.register_callback = icmp_local_send_register,
	.trace_format = (gr_trace_format_cb_t)trace_icmp_format,
};

GR_NODE_REGISTER(icmp_local_send_info);
