// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * EIGRP Neighbor Handling.
 * Copyright (C) 2013-2016
 * Authors:
 *   Donnie Savage
 *   Jan Janovic
 *   Matej Perina
 *   Peter Orsag
 *   Peter Paluch
 *   Frantisek Gazo
 *   Tomas Hvorkovy
 *   Martin Kontsek
 *   Lukas Koribsky
 */

#include <zebra.h>

#include "linklist.h"
#include "prefix.h"
#include "memory.h"
#include "command.h"
#include "frrevent.h"
#include "stream.h"
#include "table.h"
#include "log.h"
#include "keychain.h"
#include "vty.h"

#include "eigrpd/eigrp_structs.h"
#include "eigrpd/eigrpd.h"
#include "eigrpd/eigrp_interface.h"
#include "eigrpd/eigrp_neighbor.h"
#include "eigrpd/eigrp_dump.h"
#include "eigrpd/eigrp_packet.h"
#include "eigrpd/eigrp_zebra.h"
#include "eigrpd/eigrp_vty.h"
#include "eigrpd/eigrp_network.h"
#include "eigrpd/eigrp_topology.h"
#include "eigrpd/eigrp_errors.h"

DEFINE_MTYPE_STATIC(EIGRPD, EIGRP_NEIGHBOR, "EIGRP neighbor");

struct eigrp_neighbor *eigrp_nbr_new(struct eigrp_interface *ei)
{
	struct eigrp_neighbor *nbr;

	/* Allcate new neighbor. */
	nbr = XCALLOC(MTYPE_EIGRP_NEIGHBOR, sizeof(struct eigrp_neighbor));

	/* Relate neighbor to the interface. */
	nbr->ei = ei;

	/* Set default values. */
	eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN);

	return nbr;
}

/**
 *@fn void dissect_eigrp_sw_version (tvbuff_t *tvb, proto_tree *tree,
 *                                   proto_item *ti)
 *
 * @par
 * Create a new neighbor structure and initalize it.
 */
static struct eigrp_neighbor *eigrp_nbr_add(struct eigrp_interface *ei,
					    struct eigrp_header *eigrph,
					    struct ip *iph)
{
	struct eigrp_neighbor *nbr;

	nbr = eigrp_nbr_new(ei);
	nbr->src = iph->ip_src;

	return nbr;
}

struct eigrp_neighbor *eigrp_nbr_get(struct eigrp_interface *ei,
				     struct eigrp_header *eigrph,
				     struct ip *iph)
{
	struct eigrp_neighbor *nbr;
	struct listnode *node, *nnode;

	for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) {
		if (iph->ip_src.s_addr == nbr->src.s_addr) {
			return nbr;
		}
	}

	nbr = eigrp_nbr_add(ei, eigrph, iph);
	listnode_add(ei->nbrs, nbr);

	return nbr;
}

/**
 * @fn eigrp_nbr_lookup_by_addr
 *
 * @param[in]		ei			EIGRP interface
 * @param[in]		nbr_addr 	Address of neighbor
 *
 * @return void
 *
 * @par
 * Function is used for neighbor lookup by address
 * in specified interface.
 */
struct eigrp_neighbor *eigrp_nbr_lookup_by_addr(struct eigrp_interface *ei,
						struct in_addr *addr)
{
	struct eigrp_neighbor *nbr;
	struct listnode *node, *nnode;

	for (ALL_LIST_ELEMENTS(ei->nbrs, node, nnode, nbr)) {
		if (addr->s_addr == nbr->src.s_addr) {
			return nbr;
		}
	}

	return NULL;
}

/**
 * @fn eigrp_nbr_lookup_by_addr_process
 *
 * @param[in]    eigrp          EIGRP process
 * @param[in]    nbr_addr       Address of neighbor
 *
 * @return void
 *
 * @par
 * Function is used for neighbor lookup by address
 * in whole EIGRP process.
 */
struct eigrp_neighbor *eigrp_nbr_lookup_by_addr_process(struct eigrp *eigrp,
							struct in_addr nbr_addr)
{
	struct eigrp_interface *ei;
	struct listnode *node, *node2, *nnode2;
	struct eigrp_neighbor *nbr;

	/* iterate over all eigrp interfaces */
	for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, ei)) {
		/* iterate over all neighbors on eigrp interface */
		for (ALL_LIST_ELEMENTS(ei->nbrs, node2, nnode2, nbr)) {
			/* compare if neighbor address is same as arg address */
			if (nbr->src.s_addr == nbr_addr.s_addr) {
				return nbr;
			}
		}
	}

	return NULL;
}


/* Delete specified EIGRP neighbor from interface. */
void eigrp_nbr_delete(struct eigrp_neighbor *nbr)
{
	eigrp_nbr_state_set(nbr, EIGRP_NEIGHBOR_DOWN);
	if (nbr->ei)
		eigrp_topology_neighbor_down(nbr->ei->eigrp, nbr);

	/* Cancel all events. */ /* Thread lookup cost would be negligible. */
	event_cancel_event(master, nbr);
	eigrp_fifo_free(nbr->multicast_queue);
	eigrp_fifo_free(nbr->retrans_queue);
	EVENT_OFF(nbr->t_holddown);

	if (nbr->ei)
		listnode_delete(nbr->ei->nbrs, nbr);
	XFREE(MTYPE_EIGRP_NEIGHBOR, nbr);
}

void holddown_timer_expired(struct event *thread)
{
	struct eigrp_neighbor *nbr = EVENT_ARG(thread);
	struct eigrp *eigrp = nbr->ei->eigrp;

	zlog_info("Neighbor %pI4 (%s) is down: holding time expired", &nbr->src,
		  ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id));
	nbr->state = EIGRP_NEIGHBOR_DOWN;
	eigrp_nbr_delete(nbr);
}

uint8_t eigrp_nbr_state_get(struct eigrp_neighbor *nbr)
{
	return (nbr->state);
}

void eigrp_nbr_state_set(struct eigrp_neighbor *nbr, uint8_t state)
{
	nbr->state = state;

	if (eigrp_nbr_state_get(nbr) == EIGRP_NEIGHBOR_DOWN) {
		// reset all the seq/ack counters
		nbr->recv_sequence_number = 0;
		nbr->init_sequence_number = 0;
		nbr->retrans_counter = 0;

		// Kvalues
		nbr->K1 = EIGRP_K1_DEFAULT;
		nbr->K2 = EIGRP_K2_DEFAULT;
		nbr->K3 = EIGRP_K3_DEFAULT;
		nbr->K4 = EIGRP_K4_DEFAULT;
		nbr->K5 = EIGRP_K5_DEFAULT;
		nbr->K6 = EIGRP_K6_DEFAULT;

		// hold time..
		nbr->v_holddown = EIGRP_HOLD_INTERVAL_DEFAULT;
		EVENT_OFF(nbr->t_holddown);

		/* out with the old */
		if (nbr->multicast_queue)
			eigrp_fifo_free(nbr->multicast_queue);
		if (nbr->retrans_queue)
			eigrp_fifo_free(nbr->retrans_queue);

		/* in with the new */
		nbr->retrans_queue = eigrp_fifo_new();
		nbr->multicast_queue = eigrp_fifo_new();

		nbr->crypt_seqnum = 0;
	}
}

const char *eigrp_nbr_state_str(struct eigrp_neighbor *nbr)
{
	const char *state;
	switch (nbr->state) {
	case EIGRP_NEIGHBOR_DOWN:
		state = "Down";
		break;
	case EIGRP_NEIGHBOR_PENDING:
		state = "Waiting for Init";
		break;
	case EIGRP_NEIGHBOR_UP:
		state = "Up";
		break;
	default:
		state = "Unknown";
		break;
	}

	return (state);
}

void eigrp_nbr_state_update(struct eigrp_neighbor *nbr)
{
	switch (nbr->state) {
	case EIGRP_NEIGHBOR_DOWN: {
		/*Start Hold Down Timer for neighbor*/
		//     EVENT_OFF(nbr->t_holddown);
		//     EVENT_TIMER_ON(master, nbr->t_holddown,
		//     holddown_timer_expired,
		//     nbr, nbr->v_holddown);
		break;
	}
	case EIGRP_NEIGHBOR_PENDING: {
		/*Reset Hold Down Timer for neighbor*/
		EVENT_OFF(nbr->t_holddown);
		event_add_timer(master, holddown_timer_expired, nbr,
				nbr->v_holddown, &nbr->t_holddown);
		break;
	}
	case EIGRP_NEIGHBOR_UP: {
		/*Reset Hold Down Timer for neighbor*/
		EVENT_OFF(nbr->t_holddown);
		event_add_timer(master, holddown_timer_expired, nbr,
				nbr->v_holddown, &nbr->t_holddown);
		break;
	}
	}
}

int eigrp_nbr_count_get(struct eigrp *eigrp)
{
	struct eigrp_interface *iface;
	struct listnode *node, *node2, *nnode2;
	struct eigrp_neighbor *nbr;
	uint32_t counter;

	counter = 0;
	for (ALL_LIST_ELEMENTS_RO(eigrp->eiflist, node, iface)) {
		for (ALL_LIST_ELEMENTS(iface->nbrs, node2, nnode2, nbr)) {
			if (nbr->state == EIGRP_NEIGHBOR_UP) {
				counter++;
			}
		}
	}
	return counter;
}

/**
 * @fn eigrp_nbr_hard_restart
 *
 * @param[in]		nbr	Neighbor who would receive hard restart
 * @param[in]		vty Virtual terminal for log output
 * @return void
 *
 * @par
 * Function used for executing hard restart for neighbor:
 * Send Hello packet with Peer Termination TLV with
 * neighbor's address, set it's state to DOWN and delete the neighbor
 */
void eigrp_nbr_hard_restart(struct eigrp_neighbor *nbr, struct vty *vty)
{
	struct eigrp *eigrp = nbr->ei->eigrp;

	zlog_debug("Neighbor %pI4 (%s) is down: manually cleared", &nbr->src,
		   ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id));
	if (vty != NULL) {
		vty_time_print(vty, 0);
		vty_out(vty, "Neighbor %pI4 (%s) is down: manually cleared\n",
			&nbr->src,
			ifindex2ifname(nbr->ei->ifp->ifindex, eigrp->vrf_id));
	}

	/* send Hello with Peer Termination TLV */
	eigrp_hello_send(nbr->ei, EIGRP_HELLO_GRACEFUL_SHUTDOWN_NBR,
			 &(nbr->src));
	/* set neighbor to DOWN */
	nbr->state = EIGRP_NEIGHBOR_DOWN;
	/* delete neighbor */
	eigrp_nbr_delete(nbr);
}

int eigrp_nbr_split_horizon_check(struct eigrp_route_descriptor *ne,
				  struct eigrp_interface *ei)
{
	if (ne->distance == EIGRP_MAX_METRIC)
		return 0;

	return (ne->ei == ei);
}
