// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2024 Robin Jarry

#include "control_input.h"
#include "graph.h"
#include "icmp6.h"
#include "ip6_datapath.h"
#include "mbuf.h"
#include "trace.h"

#include <rte_ip6.h>

enum {
	OUTPUT = 0,
	EDGE_COUNT,
};

struct advertise_context {
	const struct nexthop *local;
	const struct nexthop *remote;
	const struct iface *iface;
};

static control_input_t na_output;

int nh6_advertise(const struct nexthop *local, const struct nexthop *remote) {
	assert(local != NULL);
	assert(local->type == GR_NH_T_L3);
	struct advertise_context *ctx = malloc(sizeof(*ctx));
	if (ctx == NULL)
		return errno_set(ENOMEM);
	ctx->local = local;
	ctx->remote = remote;
	ctx->iface = iface_from_id(local->iface_id);
	assert(ctx->iface != NULL);
	int ret = post_to_stack(na_output, ctx);
	if (ret < 0)
		free(ctx);
	return ret;
}

static uint16_t ndp_na_output_process(
	struct rte_graph *graph,
	struct rte_node *node,
	void **objs,
	uint16_t nb_objs
) {
	const struct nexthop *local, *remote;
	struct advertise_context *ctx;
	struct ip6_local_mbuf_data *d;
	struct icmp6_neigh_advert *na;
	struct icmp6_opt_lladdr *ll;
	struct nexthop_info_l3 *l3;
	const struct iface *iface;
	struct icmp6_opt *opt;
	struct rte_mbuf *mbuf;
	uint16_t payload_len;
	struct icmp6 *icmp6;

	for (uint16_t i = 0; i < nb_objs; i++) {
		mbuf = objs[i];
		ctx = control_input_mbuf_data(mbuf)->data;
		local = ctx->local;
		remote = ctx->remote;
		iface = ctx->iface;
		free(ctx);
		l3 = nexthop_info_l3(local);

		rte_pktmbuf_trim(mbuf, rte_pktmbuf_pkt_len(mbuf));

		// Fill ICMP6 layer.
		payload_len = sizeof(*icmp6) + sizeof(*na) + sizeof(*opt) + sizeof(*ll);
		icmp6 = (struct icmp6 *)rte_pktmbuf_append(mbuf, payload_len);
		icmp6->type = ICMP6_TYPE_NEIGH_ADVERT;
		icmp6->code = 0;
		na = PAYLOAD(icmp6);
		na->flags = ICMP6_NA_F_OVERRIDE | ICMP6_NA_F_ROUTER;
		if (remote != NULL)
			na->flags |= ICMP6_NA_F_SOLICITED;
		na->target = l3->ipv6;
		opt = PAYLOAD(na);
		opt->type = ICMP6_OPT_TARGET_LLADDR;
		opt->len = ICMP6_OPT_LEN(sizeof(*opt) + sizeof(*ll));
		ll = PAYLOAD(opt);
		ll->mac = l3->mac;

		// Fill in IP local data
		d = ip6_local_mbuf_data(mbuf);
		d->iface = iface;
		d->src = l3->ipv6;
		if (remote == NULL) {
			// If the source of the solicitation is the unspecified address, the
			// node MUST set the Solicited flag to zero and multicast the
			// advertisement to the all-nodes address.
			d->dst = (struct rte_ipv6_addr)RTE_IPV6_ADDR_ALLNODES_LINK_LOCAL;
		} else {
			d->dst = nexthop_info_l3(remote)->ipv6;
		}
		d->len = payload_len;
		d->hop_limit = IP6_DEFAULT_HOP_LIMIT;
		d->proto = IPPROTO_ICMPV6;

		if (gr_mbuf_is_traced(mbuf)) {
			uint8_t trace_len = RTE_MIN(payload_len, GR_TRACE_ITEM_MAX_LEN);
			struct icmp6 *t = gr_mbuf_trace_add(mbuf, node, trace_len);
			memcpy(t, icmp6, trace_len);
		}
		rte_node_enqueue_x1(graph, node, OUTPUT, mbuf);
	}

	return nb_objs;
}

static void ndp_na_output_register(void) {
	na_output = gr_control_input_register_handler("ndp_na_output", false);
}

static struct rte_node_register node = {
	.name = "ndp_na_output",

	.process = ndp_na_output_process,

	.nb_edges = EDGE_COUNT,
	.next_nodes = {
		[OUTPUT] = "icmp6_output",
	},
};

static struct gr_node_info info = {
	.node = &node,
	.type = GR_NODE_T_CONTROL | GR_NODE_T_L4,
	.register_callback = ndp_na_output_register,
	.trace_format = (gr_trace_format_cb_t)trace_icmp6_format,
};

GR_NODE_REGISTER(info);
