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

#include "control_input.h"
#include "event.h"
#include "icmp6.h"
#include "iface.h"
#include "ip6.h"
#include "ip6_datapath.h"
#include "l3.h"
#include "log.h"
#include "mempool.h"
#include "module.h"
#include "vec.h"

#include <gr_ip6.h>

#include <event2/event.h>
#include <rte_ether.h>
#include <rte_ip6.h>
#include <rte_mbuf.h>

LOG_TYPE("ra");

#define RA_DEFAULT_INTERVAL 600
#define RA_DEFAULT_LIFETIME 1800

struct ra_ctx {
	struct rte_mempool *mp;
};

static struct ra_ctx ra_ctx;
static control_input_t ra_output;
static struct event_base *ev_base;

struct ra_iface_conf {
	struct event *timer;
	bool enabled;
	uint16_t interval;
	uint16_t lifetime;
};

static struct ra_iface_conf ra_conf[GR_MAX_IFACES];

static struct api_out iface_ra_set(const void *request, struct api_ctx *) {
	const struct gr_ip6_ra_set_req *req = request;

	if (iface_from_id(req->iface_id) == NULL)
		return api_out(errno, 0, NULL);

	if (req->set_interval)
		ra_conf[req->iface_id].interval = req->interval;
	if (req->set_lifetime)
		ra_conf[req->iface_id].lifetime = req->lifetime;

	event_add(
		ra_conf[req->iface_id].timer,
		&(struct timeval) {.tv_sec = ra_conf[req->iface_id].interval}
	);
	event_active(ra_conf[req->iface_id].timer, 0, 0);
	return api_out(0, 0, NULL);
}

static struct api_out iface_ra_clear(const void *request, struct api_ctx *) {
	const struct gr_ip6_ra_clear_req *req = request;

	if (iface_from_id(req->iface_id) == NULL)
		return api_out(errno, 0, NULL);

	event_del(ra_conf[req->iface_id].timer);
	ra_conf[req->iface_id].interval = RA_DEFAULT_INTERVAL;
	ra_conf[req->iface_id].lifetime = RA_DEFAULT_LIFETIME;

	return api_out(0, 0, NULL);
}

static struct api_out iface_ra_show(const void *request, struct api_ctx *ctx) {
	const struct gr_ip6_ra_show_req *req = request;
	uint16_t iface_id;
	struct hoplist *addrs;
	bool show_all = false;

	if (req->iface_id == 0)
		show_all = true;
	else if (iface_from_id(req->iface_id) == NULL)
		return api_out(errno, 0, NULL);

	for (iface_id = 0; iface_id < GR_MAX_IFACES; iface_id++) {
		addrs = addr6_get_all(iface_id);
		if (addrs == NULL || vec_len(addrs->nh) == 0)
			continue;
		if (show_all == false && iface_id != req->iface_id)
			continue;

		struct gr_ip6_ra_conf ra_conf_item = {
			.iface_id = iface_id,
			.enabled = event_pending(
				ra_conf[iface_id].timer,
				EV_TIMEOUT | EV_READ | EV_WRITE | EV_SIGNAL,
				0
			),
			.interval = ra_conf[iface_id].interval,
			.lifetime = ra_conf[iface_id].lifetime,
		};
		api_send(ctx, sizeof(ra_conf_item), &ra_conf_item);
	}

	return api_out(0, 0, NULL);
}

void ndp_router_sollicit_input_cb(void *mbuf, uintptr_t, const struct control_queue_drain *drain) {
	const struct iface *iface = mbuf_data(mbuf)->iface;
	rte_pktmbuf_free(mbuf);
	// Check if packet references deleted interface.
	if (drain != NULL && drain->event == GR_EVENT_IFACE_REMOVE && iface == drain->obj)
		return;
	event_active(ra_conf[iface->id].timer, 0, 0);
}

static void build_ra_packet(struct rte_mbuf *m, const struct rte_ipv6_addr *src) {
	struct rte_ipv6_addr dst = RTE_IPV6_ADDR_ALLNODES_LINK_LOCAL;
	struct icmp6_opt_lladdr *lladdr;
	struct icmp6_router_advert *ra;
	struct rte_ether_addr mac;
	struct rte_ipv6_hdr *ip;
	struct icmp6_opt *opt;
	uint16_t payload_len;
	struct icmp6 *icmp6;
	uint16_t iface_id;
	uint16_t vrf_id;

	vrf_id = mbuf_data(m)->iface->vrf_id;
	iface_id = mbuf_data(m)->iface->id;
	l3_mbuf_data(m)->nh = nh6_lookup(vrf_id, iface_id, &dst);
	ip = (struct rte_ipv6_hdr *)rte_pktmbuf_append(m, sizeof(*ip));
	icmp6 = (struct icmp6 *)rte_pktmbuf_append(m, sizeof(*icmp6));
	icmp6->type = ICMP6_TYPE_ROUTER_ADVERT;
	icmp6->code = 0;
	ra = (struct icmp6_router_advert *)rte_pktmbuf_append(m, sizeof(*ra));
	ra->cur_hoplim = IP6_DEFAULT_HOP_LIMIT; // Default TTL for this network
	ra->flags = 0;
	ra->lifetime = rte_cpu_to_be_16(ra_conf[iface_id].lifetime);
	ra->reachable_time = RTE_BE16(0);
	ra->retrans_timer = RTE_BE16(0);

	payload_len = sizeof(*icmp6) + sizeof(*ra);

	if (iface_get_eth_addr(mbuf_data(m)->iface, &mac) == 0) {
		opt = (struct icmp6_opt *)rte_pktmbuf_append(m, sizeof(*opt));
		opt->type = ICMP6_OPT_SRC_LLADDR;
		opt->len = ICMP6_OPT_LEN(sizeof(*opt) + sizeof(*lladdr));
		lladdr = (struct icmp6_opt_lladdr *)rte_pktmbuf_append(m, sizeof(*lladdr));
		lladdr->mac = mac;
		payload_len += sizeof(*opt) + sizeof(*lladdr);
	}

	ip6_set_fields(ip, payload_len, IPPROTO_ICMPV6, src, &dst);
	icmp6->cksum = 0;
	icmp6->cksum = rte_ipv6_udptcp_cksum(ip, icmp6);
}

static void send_ra_cb(evutil_socket_t, short /*what*/, void *priv) {
	const struct nexthop_info_l3 *l3;
	struct iface *iface = priv;
	struct hoplist *hl;
	struct nexthop *nh;
	struct rte_mbuf *m;

	if ((hl = addr6_get_all(iface->id)) == NULL)
		return;

	vec_foreach (nh, hl->nh) {
		if (nh->type != GR_NH_T_L3)
			continue;
		l3 = nexthop_info_l3(nh);
		if (l3->af != GR_AF_IP6)
			continue;
		if (!rte_ipv6_addr_is_linklocal(&l3->ipv6))
			continue;
		if ((m = rte_pktmbuf_alloc(ra_ctx.mp)) == NULL) {
			LOG(ERR, "rte_pktmbuf_alloc");
			return;
		}
		mbuf_data(m)->iface = iface;
		build_ra_packet(m, &l3->ipv6);
		if (post_to_stack(ra_output, m) < 0) {
			rte_pktmbuf_free(m);
			LOG(ERR, "post_to_stack: %s", strerror(errno));
		}
	}
}

static void ra_init(struct event_base *base) {
	ev_base = base;
	ra_output = gr_control_input_register_handler("ip6_output", true);
	ra_ctx.mp = gr_pktmbuf_pool_get(SOCKET_ID_ANY, 512);
	if (ra_ctx.mp == NULL) {
		ABORT("gr_pktmbuf_pool_get ENOMEM");
	}
}

static void ra_fini(struct event_base * /*ev_base*/) {
	gr_pktmbuf_pool_release(ra_ctx.mp, 512);
}

static struct module ra_module = {
	.name = "ip6_router_advert",
	.depends_on = "graph",
	.init = ra_init,
	.fini = ra_fini,
};

static void iface_event_handler(uint32_t event, const void *obj) {
	const struct iface *iface = obj;

	switch (event) {
	case GR_EVENT_IFACE_POST_ADD:
		ra_conf[iface->id].interval = RA_DEFAULT_INTERVAL;
		ra_conf[iface->id].lifetime = RA_DEFAULT_LIFETIME;
		ra_conf[iface->id].timer = event_new(
			ev_base, -1, EV_PERSIST, send_ra_cb, (void *)iface
		);
		break;
	case GR_EVENT_IFACE_REMOVE:
		event_free(ra_conf[iface->id].timer);
		ra_conf[iface->id].timer = NULL;
		break;
	}
}

RTE_INIT(router_advertisement_init) {
	module_register(&ra_module);
	api_handler(GR_IP6_IFACE_RA_SET, iface_ra_set);
	api_handler(GR_IP6_IFACE_RA_CLEAR, iface_ra_clear);
	api_handler(GR_IP6_IFACE_RA_SHOW, iface_ra_show);
	event_subscribe(GR_EVENT_IFACE_POST_ADD, iface_event_handler);
	event_subscribe(GR_EVENT_IFACE_REMOVE, iface_event_handler);
}
