// SPDX-License-Identifier: MIT
/*
Copyright (c) 2007, 2008 by Juliusz Chroboczek
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>

#include <zebra.h>
#include "if.h"

#include "babel_main.h"
#include "babeld.h"
#include "util.h"
#include "neighbour.h"
#include "resend.h"
#include "message.h"
#include "babel_interface.h"

struct timeval resend_time = { 0, 0 };
struct resend *to_resend = NULL;

static int resend_match(struct resend *resend, int kind, const unsigned char *prefix,
			unsigned char plen)
{
	return (resend->kind == kind && resend->plen == plen &&
		memcmp(resend->prefix, prefix, 16) == 0);
}

/* This is called by neigh.c when a neighbour is flushed */

void flush_resends(struct neighbour *neigh)
{
	/* Nothing for now */
}

static struct resend *find_resend(int kind, const unsigned char *prefix, unsigned char plen,
				  struct resend **previous_return)
{
	struct resend *current, *previous;

	previous = NULL;
	current = to_resend;
	while (current) {
		if (resend_match(current, kind, prefix, plen)) {
			if (previous_return)
				*previous_return = previous;
			return current;
		}
		previous = current;
		current = current->next;
	}

	return NULL;
}

struct resend *find_request(const unsigned char *prefix, unsigned char plen,
			    struct resend **previous_return)
{
	return find_resend(RESEND_REQUEST, prefix, plen, previous_return);
}

int record_resend(int kind, const unsigned char *prefix, unsigned char plen, unsigned short seqno,
		  const unsigned char *id, struct interface *ifp, int delay)
{
	struct resend *resend;
	unsigned int ifindex = ifp ? ifp->ifindex : 0;

	if ((kind == RESEND_REQUEST &&
	     input_filter(NULL, prefix, plen, NULL, ifindex) >= INFINITY) ||
	    (kind == RESEND_UPDATE && output_filter(NULL, prefix, plen, ifindex) >= INFINITY))
		return 0;

	if (delay >= 0xFFFF)
		delay = 0xFFFF;

	resend = find_resend(kind, prefix, plen, NULL);
	if (resend) {
		if (resend->delay && delay)
			resend->delay = MIN(resend->delay, delay);
		else if (delay)
			resend->delay = delay;
		resend->time = babel_now;
		resend->max = RESEND_MAX;
		if (id && memcmp(resend->id, id, 8) == 0 &&
		    seqno_compare(resend->seqno, seqno) > 0) {
			return 0;
		}
		if (id)
			memcpy(resend->id, id, 8);
		else
			memset(resend->id, 0, 8);
		resend->seqno = seqno;
		if (resend->ifp != ifp)
			resend->ifp = NULL;
	} else {
		resend = malloc(sizeof(struct resend));
		if (resend == NULL)
			return -1;
		resend->kind = kind;
		resend->max = RESEND_MAX;
		resend->delay = delay;
		memcpy(resend->prefix, prefix, 16);
		resend->plen = plen;
		resend->seqno = seqno;
		if (id)
			memcpy(resend->id, id, 8);
		else
			memset(resend->id, 0, 8);
		resend->ifp = ifp;
		resend->time = babel_now;
		resend->next = to_resend;
		to_resend = resend;
	}

	if (resend->delay) {
		struct timeval timeout;
		timeval_add_msec(&timeout, &resend->time, resend->delay);
		timeval_min(&resend_time, &timeout);
	}
	return 1;
}

static int resend_expired(struct resend *resend)
{
	switch (resend->kind) {
	case RESEND_REQUEST:
		return timeval_minus_msec(&babel_now, &resend->time) >= REQUEST_TIMEOUT;
	default:
		return resend->max <= 0;
	}
}

int unsatisfied_request(const unsigned char *prefix, unsigned char plen, unsigned short seqno,
			const unsigned char *id)
{
	struct resend *request;

	request = find_request(prefix, plen, NULL);
	if (request == NULL || resend_expired(request))
		return 0;

	if (memcmp(request->id, id, 8) != 0 || seqno_compare(request->seqno, seqno) <= 0)
		return 1;

	return 0;
}

/* Determine whether a given request should be forwarded. */
int request_redundant(struct interface *ifp, const unsigned char *prefix, unsigned char plen,
		      unsigned short seqno, const unsigned char *id)
{
	struct resend *request;

	request = find_request(prefix, plen, NULL);
	if (request == NULL || resend_expired(request))
		return 0;

	if (memcmp(request->id, id, 8) == 0 && seqno_compare(request->seqno, seqno) > 0)
		return 0;

	if (request->ifp != NULL && request->ifp != ifp)
		return 0;

	if (request->max > 0)
		/* Will be resent. */
		return 1;

	if (timeval_minus_msec(&babel_now, &request->time) <
	    (ifp ? MIN(babel_get_if_nfo(ifp)->hello_interval, 1000) : 1000))
		/* Fairly recent. */
		return 1;

	return 0;
}

int satisfy_request(const unsigned char *prefix, unsigned char plen, unsigned short seqno,
		    const unsigned char *id, struct interface *ifp)
{
	struct resend *request, *previous;

	request = find_request(prefix, plen, &previous);
	if (request == NULL)
		return 0;

	if (ifp != NULL && request->ifp != ifp)
		return 0;

	if (memcmp(request->id, id, 8) != 0 || seqno_compare(request->seqno, seqno) <= 0) {
		/* We cannot remove the request, as we may be walking the list right
           now.  Mark it as expired, so that expire_resend will remove it. */
		request->max = 0;
		request->time.tv_sec = 0;
		recompute_resend_time();
		return 1;
	}

	return 0;
}

void expire_resend(void)
{
	struct resend *current, *previous;
	int recompute = 0;

	previous = NULL;
	current = to_resend;
	while (current) {
		if (resend_expired(current)) {
			if (previous == NULL) {
				to_resend = current->next;
				free(current);
				current = to_resend;
			} else {
				previous->next = current->next;
				free(current);
				current = previous->next;
			}
			recompute = 1;
		} else {
			previous = current;
			current = current->next;
		}
	}
	if (recompute)
		recompute_resend_time();
}

void recompute_resend_time(void)
{
	struct resend *request;
	struct timeval resend = { 0, 0 };

	request = to_resend;
	while (request) {
		if (!resend_expired(request) && request->delay > 0 && request->max > 0) {
			struct timeval timeout;
			timeval_add_msec(&timeout, &request->time, request->delay);
			timeval_min(&resend, &timeout);
		}
		request = request->next;
	}

	resend_time = resend;
}

void do_resend(void)
{
	struct resend *resend;

	resend = to_resend;
	while (resend) {
		if (!resend_expired(resend) && resend->delay > 0 && resend->max > 0) {
			struct timeval timeout;
			timeval_add_msec(&timeout, &resend->time, resend->delay);
			if (timeval_compare(&babel_now, &timeout) >= 0) {
				switch (resend->kind) {
				case RESEND_REQUEST:
					send_multihop_request(resend->ifp, resend->prefix,
							      resend->plen, resend->seqno,
							      resend->id, 127);
					break;
				case RESEND_UPDATE:
					send_update(resend->ifp, 1, resend->prefix, resend->plen);
					break;
				default:
					abort();
				}
				resend->delay = MIN(0xFFFF, resend->delay * 2);
				resend->max--;
			}
		}
		resend = resend->next;
	}
	recompute_resend_time();
}
