// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 * Copyright 2009-2016, LabN Consulting, L.L.C.
 *
 */

#include "lib/zebra.h"
#include "lib/prefix.h"
#include "lib/agg_table.h"
#include "lib/vty.h"
#include "lib/memory.h"
#include "lib/routemap.h"
#include "lib/log.h"
#include "lib/linklist.h"
#include "lib/command.h"
#include "lib/stream.h"

#include "bgpd/bgpd.h"
#include "bgpd/bgp_ecommunity.h"
#include "bgpd/bgp_attr.h"

#include "bgpd/rfapi/bgp_rfapi_cfg.h"
#include "bgpd/rfapi/rfapi.h"
#include "bgpd/rfapi/rfapi_backend.h"

#include "bgpd/bgp_route.h"
#include "bgpd/bgp_mplsvpn.h"
#include "bgpd/bgp_aspath.h"
#include "bgpd/bgp_advertise.h"

#include "bgpd/rfapi/rfapi_import.h"
#include "bgpd/rfapi/rfapi_private.h"
#include "bgpd/rfapi/rfapi_monitor.h"
#include "bgpd/rfapi/rfapi_vty.h"
#include "bgpd/rfapi/vnc_export_bgp.h"
#include "bgpd/rfapi/vnc_export_bgp_p.h"
#include "bgpd/rfapi/vnc_zebra.h"
#include "bgpd/rfapi/vnc_import_bgp.h"
#include "bgpd/rfapi/rfapi_rib.h"

#include "bgpd/rfapi/rfapi_ap.h"
#include "bgpd/rfapi/vnc_debug.h"

/*
 * Per-NVE Advertised prefixes
 *
 * We maintain a list of prefixes advertised by each NVE.
 * There are two indices: by prefix and by lifetime.
 *
 * BY-PREFIX skiplist
 *
 *  key:	ptr to struct prefix (when storing, point to prefix that
 *		is part of rfapi_adb).
 *
 *  value:	ptr to struct rfapi_adb
 *
 * BY-LIFETIME skiplist
 *
 *  key:	ptr to struct rfapi_adb
 *  value:	ptr to struct rfapi_adb
 *
 */

/*
 * Skiplist sort function that sorts first according to lifetime
 * and then according to adb pointer value. The adb pointer
 * is used to spread out the sort for adbs with the same lifetime
 * and thereby make the skip list operations more efficient.
 */
static int sl_adb_lifetime_cmp(const void *adb1, const void *adb2)
{
	const struct rfapi_adb *a1 = adb1;
	const struct rfapi_adb *a2 = adb2;

	if (a1->lifetime < a2->lifetime)
		return -1;
	if (a1->lifetime > a2->lifetime)
		return 1;

	if (a1 < a2)
		return -1;
	if (a1 > a2)
		return 1;

	return 0;
}

void rfapiApInit(struct rfapi_advertised_prefixes *ap)
{
	ap->ipN_by_prefix = skiplist_new(0, rfapi_rib_key_cmp, NULL);
	ap->ip0_by_ether = skiplist_new(0, rfapi_rib_key_cmp, NULL);
	ap->by_lifetime = skiplist_new(0, sl_adb_lifetime_cmp, NULL);
}

void rfapiApRelease(struct rfapi_advertised_prefixes *ap)
{
	struct rfapi_adb *adb;

	/* Free ADBs and lifetime items */
	while (0 == skiplist_first(ap->by_lifetime, NULL, (void **)&adb)) {
		rfapiAdbFree(adb);
		skiplist_delete_first(ap->by_lifetime);
	}

	while (0 == skiplist_delete_first(ap->ipN_by_prefix))
		;
	while (0 == skiplist_delete_first(ap->ip0_by_ether))
		;

	/* Free lists */
	skiplist_free(ap->ipN_by_prefix);
	skiplist_free(ap->ip0_by_ether);
	skiplist_free(ap->by_lifetime);

	ap->ipN_by_prefix = NULL;
	ap->ip0_by_ether = NULL;
	ap->by_lifetime = NULL;
}

int rfapiApCount(struct rfapi_descriptor *rfd)
{
	if (!rfd->advertised.by_lifetime)
		return 0;

	return skiplist_count(rfd->advertised.by_lifetime);
}

int rfapiApCountAll(struct bgp *bgp)
{
	struct rfapi *h;
	struct listnode *node;
	struct rfapi_descriptor *rfd;
	int total = 0;

	h = bgp->rfapi;
	if (h) {
		for (ALL_LIST_ELEMENTS_RO(&h->descriptors, node, rfd)) {
			total += rfapiApCount(rfd);
		}
	}
	return total;
}


void rfapiApReadvertiseAll(struct bgp *bgp, struct rfapi_descriptor *rfd)
{
	struct rfapi_adb *adb;
	void *cursor = NULL;
	int rc;

	for (rc = skiplist_next(rfd->advertised.by_lifetime, NULL,
				(void **)&adb, &cursor);
	     rc == 0; rc = skiplist_next(rfd->advertised.by_lifetime, NULL,
					 (void **)&adb, &cursor)) {

		struct prefix_rd prd;
		uint32_t local_pref = rfp_cost_to_localpref(adb->cost);

		prd = rfd->rd;
		prd.family = AF_UNSPEC;
		prd.prefixlen = 64;

		/*
		 * TBD this is not quite right. When pfx_ip is 0/32 or 0/128,
		 * we need to substitute the VN address as the prefix
		 */
		add_vnc_route(rfd, bgp, SAFI_MPLS_VPN, &adb->u.s.prefix_ip,
			      &prd,	  /* RD to use (0 for ENCAP) */
			      &rfd->vn_addr, /* nexthop */
			      &local_pref, &adb->lifetime, NULL,
			      NULL, /* struct rfapi_un_option */
			      NULL, /* struct rfapi_vn_option */
			      rfd->rt_export_list, NULL, /* med */
			      NULL, ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, 0);
	}
}

void rfapiApWithdrawAll(struct bgp *bgp, struct rfapi_descriptor *rfd)
{
	struct rfapi_adb *adb;
	void *cursor;
	int rc;


	cursor = NULL;
	for (rc = skiplist_next(rfd->advertised.by_lifetime, NULL,
				(void **)&adb, &cursor);
	     rc == 0; rc = skiplist_next(rfd->advertised.by_lifetime, NULL,
					 (void **)&adb, &cursor)) {

		struct prefix pfx_vn_buf;
		struct prefix *pfx_ip;

		if (!(RFAPI_0_PREFIX(&adb->u.s.prefix_ip)
		      && RFAPI_HOST_PREFIX(&adb->u.s.prefix_ip))) {

			pfx_ip = &adb->u.s.prefix_ip;

		} else {

			pfx_ip = NULL;

			/*
			 * 0/32 or 0/128 => mac advertisement
			 */
			if (rfapiRaddr2Qprefix(&rfd->vn_addr, &pfx_vn_buf)) {
				/*
				 * Bad: it means we can't delete the route
				 */
				vnc_zlog_debug_verbose(
					"%s: BAD: handle has bad vn_addr: skipping",
					__func__);
				continue;
			}
		}

		del_vnc_route(rfd, rfd->peer, bgp, SAFI_MPLS_VPN,
			      pfx_ip ? pfx_ip : &pfx_vn_buf,
			      &adb->u.s.prd, /* RD to use (0 for ENCAP) */
			      ZEBRA_ROUTE_BGP, BGP_ROUTE_RFP, NULL, 0);
	}
}

/*
 * returns nonzero if tunnel readvertisement is needed, 0 otherwise
 */
static int rfapiApAdjustLifetimeStats(
	struct rfapi_descriptor *rfd,
	uint32_t *old_lifetime, /* set if removing/replacing */
	uint32_t *new_lifetime) /* set if replacing/adding */
{
	int advertise = 0;
	int find_max = 0;
	int find_min = 0;

	vnc_zlog_debug_verbose("%s: rfd=%p, pOldLife=%p, pNewLife=%p", __func__,
			       rfd, old_lifetime, new_lifetime);
	if (old_lifetime)
		vnc_zlog_debug_verbose("%s: OldLife=%d", __func__,
				       *old_lifetime);
	if (new_lifetime)
		vnc_zlog_debug_verbose("%s: NewLife=%d", __func__,
				       *new_lifetime);

	if (new_lifetime) {
		/*
		 * Adding new lifetime
		 */
		if (old_lifetime) {
			/*
			 * replacing existing lifetime
			 */


			/* old and new are same */
			if (*old_lifetime == *new_lifetime)
				return 0;

			if (*old_lifetime == rfd->min_prefix_lifetime) {
				find_min = 1;
			}
			if (*old_lifetime == rfd->max_prefix_lifetime) {
				find_max = 1;
			}

			/* no need to search if new value is at or equals
			 * min|max  */
			if (*new_lifetime <= rfd->min_prefix_lifetime) {
				rfd->min_prefix_lifetime = *new_lifetime;
				find_min = 0;
			}
			if (*new_lifetime >= rfd->max_prefix_lifetime) {
				rfd->max_prefix_lifetime = *new_lifetime;
				advertise = 1;
				find_max = 0;
			}

		} else {
			/*
			 * Just adding new lifetime
			 */
			if (*new_lifetime < rfd->min_prefix_lifetime) {
				rfd->min_prefix_lifetime = *new_lifetime;
			}
			if (*new_lifetime > rfd->max_prefix_lifetime) {
				advertise = 1;
				rfd->max_prefix_lifetime = *new_lifetime;
			}
		}
	} else {
		/*
		 * Deleting
		 */

		/*
		 * See if the max prefix lifetime for this NVE has decreased.
		 * The easy optimization: track min & max; walk the table only
		 * if they are different.
		 * The general optimization: index the advertised_prefixes
		 * table by lifetime.
		 *
		 * Note: for a given nve_descriptor, only one of the
		 * advertised_prefixes[] tables will be used: viz., the
		 * address family that matches the VN address.
		 *
		 */
		if (rfd->max_prefix_lifetime == rfd->min_prefix_lifetime) {

			/*
			 * Common case: all lifetimes are the same. Only
			 * thing we need to do here is check if there are
			 * no exported routes left. In that case, reinitialize
			 * the max and min values.
			 */
			if (!rfapiApCount(rfd)) {
				rfd->max_prefix_lifetime = 0;
				rfd->min_prefix_lifetime = UINT32_MAX;
			}


		} else {
			if (old_lifetime) {
				if (*old_lifetime == rfd->min_prefix_lifetime) {
					find_min = 1;
				}
				if (*old_lifetime == rfd->max_prefix_lifetime) {
					find_max = 1;
				}
			}
		}
	}

	if (find_min || find_max) {
		uint32_t min = UINT32_MAX;
		uint32_t max = 0;

		struct rfapi_adb *adb_min;
		struct rfapi_adb *adb_max;

		if (!skiplist_first(rfd->advertised.by_lifetime,
				    (void **)&adb_min, NULL)
		    && !skiplist_last(rfd->advertised.by_lifetime,
				      (void **)&adb_max, NULL)) {

			/*
			 * This should always work
			 */
			min = adb_min->lifetime;
			max = adb_max->lifetime;

		} else {

			void *cursor;
			struct rfapi_rib_key rk;
			struct rfapi_adb *adb;
			int rc;

			vnc_zlog_debug_verbose(
				"%s: walking to find new min/max", __func__);

			cursor = NULL;
			for (rc = skiplist_next(rfd->advertised.ipN_by_prefix,
						(void **)&rk, (void **)&adb,
						&cursor);
			     !rc;
			     rc = skiplist_next(rfd->advertised.ipN_by_prefix,
						(void **)&rk, (void **)&adb,
						&cursor)) {

				uint32_t lt = adb->lifetime;

				if (lt > max)
					max = lt;
				if (lt < min)
					min = lt;
			}
			cursor = NULL;
			for (rc = skiplist_next(rfd->advertised.ip0_by_ether,
						(void **)&rk, (void **)&adb,
						&cursor);
			     !rc;
			     rc = skiplist_next(rfd->advertised.ip0_by_ether,
						(void **)&rk, (void **)&adb,
						&cursor)) {

				uint32_t lt = adb->lifetime;

				if (lt > max)
					max = lt;
				if (lt < min)
					min = lt;
			}
		}

		/*
		 * trigger tunnel route update
		 * but only if we found a VPN route and it had
		 * a lifetime greater than 0
		 */
		if (max && rfd->max_prefix_lifetime != max)
			advertise = 1;
		rfd->max_prefix_lifetime = max;
		rfd->min_prefix_lifetime = min;
	}

	vnc_zlog_debug_verbose("%s: returning advertise=%d, min=%d, max=%d",
			       __func__, advertise, rfd->min_prefix_lifetime,
			       rfd->max_prefix_lifetime);

	return (advertise != 0);
}

/*
 * Return Value
 *
 *	0	No need to advertise tunnel route
 *	non-0	advertise tunnel route
 */
int rfapiApAdd(struct bgp *bgp, struct rfapi_descriptor *rfd,
	       struct prefix *pfx_ip, struct prefix *pfx_eth,
	       struct prefix_rd *prd, uint32_t lifetime, uint8_t cost,
	       struct rfapi_l2address_option *l2o) /* other options TBD */
{
	int rc;
	struct rfapi_adb *adb;
	uint32_t old_lifetime = 0;
	int use_ip0 = 0;
	struct rfapi_rib_key rk;

	rfapi_rib_key_init(pfx_ip, prd, pfx_eth, &rk);
	if (RFAPI_0_PREFIX(pfx_ip) && RFAPI_HOST_PREFIX(pfx_ip)) {
		use_ip0 = 1;
		assert(pfx_eth);
		rc = skiplist_search(rfd->advertised.ip0_by_ether, &rk,
				     (void **)&adb);

	} else {

		/* find prefix in advertised prefixes list */
		rc = skiplist_search(rfd->advertised.ipN_by_prefix, &rk,
				     (void **)&adb);
	}


	if (rc) {
		/* Not found */
		adb = XCALLOC(MTYPE_RFAPI_ADB, sizeof(struct rfapi_adb));
		adb->lifetime = lifetime;
		adb->u.key = rk;

		if (use_ip0) {
			assert(pfx_eth);
			skiplist_insert(rfd->advertised.ip0_by_ether,
					&adb->u.key, adb);
		} else {
			skiplist_insert(rfd->advertised.ipN_by_prefix,
					&adb->u.key, adb);
		}

		skiplist_insert(rfd->advertised.by_lifetime, adb, adb);
	} else {
		old_lifetime = adb->lifetime;
		if (old_lifetime != lifetime) {
			assert(!skiplist_delete(rfd->advertised.by_lifetime,
						adb, NULL));
			adb->lifetime = lifetime;
			assert(!skiplist_insert(rfd->advertised.by_lifetime,
						adb, adb));
		}
	}
	adb->cost = cost;
	if (l2o)
		adb->l2o = *l2o;
	else
		memset(&adb->l2o, 0, sizeof(struct rfapi_l2address_option));

	if (rfapiApAdjustLifetimeStats(rfd, (rc ? NULL : &old_lifetime),
				       &lifetime))
		return 1;

	return 0;
}

/*
 * After this function returns successfully, caller should call
 * rfapiAdjustLifetimeStats() and possibly rfapiTunnelRouteAnnounce()
 */
int rfapiApDelete(struct bgp *bgp, struct rfapi_descriptor *rfd,
		  struct prefix *pfx_ip, struct prefix *pfx_eth,
		  struct prefix_rd *prd, int *advertise_tunnel) /* out */
{
	int rc;
	struct rfapi_adb *adb;
	uint32_t old_lifetime;
	int use_ip0 = 0;
	struct rfapi_rib_key rk;

	if (advertise_tunnel)
		*advertise_tunnel = 0;

	rfapi_rib_key_init(pfx_ip, prd, pfx_eth, &rk);
	/* find prefix in advertised prefixes list */
	if (RFAPI_0_PREFIX(pfx_ip) && RFAPI_HOST_PREFIX(pfx_ip)) {
		use_ip0 = 1;
		assert(pfx_eth);

		rc = skiplist_search(rfd->advertised.ip0_by_ether, &rk,
				     (void **)&adb);

	} else {

		/* find prefix in advertised prefixes list */
		rc = skiplist_search(rfd->advertised.ipN_by_prefix, &rk,
				     (void **)&adb);
	}

	if (rc) {
		return ENOENT;
	}

	old_lifetime = adb->lifetime;

	if (use_ip0) {
		rc = skiplist_delete(rfd->advertised.ip0_by_ether, &rk, NULL);
	} else {
		rc = skiplist_delete(rfd->advertised.ipN_by_prefix, &rk, NULL);
	}
	assert(!rc);

	rc = skiplist_delete(rfd->advertised.by_lifetime, adb, NULL);
	assert(!rc);

	rfapiAdbFree(adb);

	if (rfapiApAdjustLifetimeStats(rfd, &old_lifetime, NULL)) {
		if (advertise_tunnel)
			*advertise_tunnel = 1;
	}

	return 0;
}
