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

#include "graph.h"
#include "icmp6.h"
#include "ip6.h"
#include "ip6_datapath.h"
#include "l3.h"
#include "log.h"
#include "mbuf.h"

#include <rte_common.h>

GR_NODE_CTX_TYPE(ip6_error_ctx, {
	icmp6_type_t icmp_type;
	uint8_t icmp_code;
});

enum edges {
	ICMP_OUTPUT = 0,
	NO_HEADROOM,
	NO_IP,
	EDGE_COUNT,
};

static uint16_t
ip6_error_process(struct rte_graph *graph, struct rte_node *node, void **objs, uint16_t nb_objs) {
	const struct ip6_error_ctx *ctx = ip6_error_ctx(node);
	struct icmp6_err_dest_unreach *du;
	struct icmp6_err_ttl_exceeded *te;
	const struct nexthop_info_l3 *l3;
	struct ip6_local_mbuf_data *d;
	const struct iface *iface;
	const struct nexthop *nh;
	struct rte_ipv6_hdr *ip;
	struct rte_mbuf *mbuf;
	struct icmp6 *icmp6;
	rte_edge_t edge;

	for (uint16_t i = 0; i < nb_objs; i++) {
		mbuf = objs[i];

		if (gr_mbuf_is_traced(mbuf))
			gr_mbuf_trace_add(mbuf, node, 0);

		// Get the pointer to the start of the ipv6 header before
		// prepending any data
		ip = rte_pktmbuf_mtod(mbuf, struct rte_ipv6_hdr *);

		// https://www.rfc-editor.org/rfc/rfc4443.html#section-3
		// ICMPv6 error messages should contain "As much of invoking
		// packet as possible without the ICMPv6 packet exceeding the
		// minimum IPv6 MTU (1280)"
		if (rte_pktmbuf_pkt_len(mbuf) > RTE_IPV6_MIN_MTU)
			rte_pktmbuf_trim(mbuf, rte_pktmbuf_pkt_len(mbuf) - RTE_IPV6_MIN_MTU);

		switch (ctx->icmp_type) {
		case ICMP6_ERR_DEST_UNREACH:
			du = gr_mbuf_prepend(mbuf, du);
			if (unlikely(du == NULL)) {
				edge = NO_HEADROOM;
				goto next;
			}
			break;
		case ICMP6_ERR_TTL_EXCEEDED:
			te = gr_mbuf_prepend(mbuf, te);
			if (unlikely(te == NULL)) {
				edge = NO_HEADROOM;
				goto next;
			}
			break;
		default:
			ABORT("unexpected icmp_type value %hhu", ctx->icmp_type);
			break;
		}

		icmp6 = gr_mbuf_prepend(mbuf, icmp6);
		if (unlikely(icmp6 == NULL || ip == NULL)) {
			edge = NO_HEADROOM;
			goto next;
		}
		icmp6->type = ctx->icmp_type;
		icmp6->code = ctx->icmp_code;

		// Get the local router IP address from the input iface
		iface = l3_mbuf_data(mbuf)->iface;
		if (iface == NULL) {
			edge = NO_IP;
			goto next;
		}
		if ((nh = addr6_get_preferred(iface->id, &ip->src_addr)) == NULL) {
			edge = NO_IP;
			goto next;
		}
		l3 = nexthop_info_l3(nh);
		d = ip6_local_mbuf_data(mbuf);
		d->src = l3->ipv6;
		d->dst = ip->src_addr;
		d->len = rte_pktmbuf_pkt_len(mbuf);
		d->iface = iface;
		edge = ICMP_OUTPUT;
next:
		rte_node_enqueue_x1(graph, node, edge, mbuf);
	}

	return nb_objs;
}

static int ttl_exceeded_init(const struct rte_graph *, struct rte_node *node) {
	struct ip6_error_ctx *ctx = ip6_error_ctx(node);
	ctx->icmp_type = ICMP6_ERR_TTL_EXCEEDED;
	ctx->icmp_code = 0;
	return 0;
}

static int no_route_init(const struct rte_graph *, struct rte_node *node) {
	struct ip6_error_ctx *ctx = ip6_error_ctx(node);
	ctx->icmp_type = ICMP6_ERR_DEST_UNREACH;
	ctx->icmp_code = 0;
	return 0;
}

static struct rte_node_register dest_unreach_node = {
	.name = "ip6_error_dest_unreach",
	.process = ip6_error_process,
	.nb_edges = EDGE_COUNT,
	.next_nodes = {
		[ICMP_OUTPUT] = "icmp6_output",
		[NO_HEADROOM] = "error_no_headroom",
		[NO_IP] = "error_no_local_ip",
	},
	.init = no_route_init,
};

static struct rte_node_register ttl_exceeded_node = {
	.name = "ip6_error_ttl_exceeded",
	.process = ip6_error_process,
	.nb_edges = EDGE_COUNT,
	.next_nodes = {
		[ICMP_OUTPUT] = "icmp6_output",
		[NO_HEADROOM] = "error_no_headroom",
		[NO_IP] = "error_no_local_ip",
	},
	.init = ttl_exceeded_init,
};

static struct gr_node_info dest_unreach_info = {
	.node = &dest_unreach_node,
	.type = GR_NODE_T_L3,
};

static struct gr_node_info ttl_exceeded_info = {
	.node = &ttl_exceeded_node,
	.type = GR_NODE_T_L3,
};

GR_NODE_REGISTER(dest_unreach_info);
GR_NODE_REGISTER(ttl_exceeded_info);
