/*
 * IS-IS Rout(e)ing protocol                  - isis_spf.c
 *                                              The SPT algorithm
 *
 * Copyright (C) 2001,2002   Sampo Saaristo
 *                           Tampere University of Technology
 *                           Institute of Communications Engineering
 * Copyright (C) 2017        Christian Franke <chris@opensourcerouting.org>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public Licenseas published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; see the file COPYING; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <zebra.h>

#include "thread.h"
#include "linklist.h"
#include "vty.h"
#include "log.h"
#include "command.h"
#include "termtable.h"
#include "memory.h"
#include "prefix.h"
#include "if.h"
#include "table.h"
#include "spf_backoff.h"
#include "srcdest_table.h"
#include "vrf.h"

#include "isis_constants.h"
#include "isis_common.h"
#include "isis_flags.h"
#include "isisd.h"
#include "isis_misc.h"
#include "isis_adjacency.h"
#include "isis_circuit.h"
#include "isis_pdu.h"
#include "isis_lsp.h"
#include "isis_dynhn.h"
#include "isis_spf.h"
#include "isis_route.h"
#include "isis_csm.h"
#include "isis_mt.h"
#include "isis_tlvs.h"
#include "fabricd.h"
#include "isis_spf_private.h"

DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_RUN, "ISIS SPF Run Info");
DEFINE_MTYPE_STATIC(ISISD, ISIS_SPF_ADJ, "ISIS SPF Adjacency");
DEFINE_MTYPE_STATIC(ISISD, ISIS_VERTEX_ADJ, "ISIS SPF Vertex Adjacency");

static void spf_adj_list_parse_lsp(struct isis_spftree *spftree,
				   struct list *adj_list, struct isis_lsp *lsp,
				   const uint8_t *pseudo_nodeid,
				   uint32_t pseudo_metric);

/*
 *  supports the given af ?
 */
static bool speaks(uint8_t *protocols, uint8_t count, int family)
{
	for (uint8_t i = 0; i < count; i++) {
		if (family == AF_INET && protocols[i] == NLPID_IP)
			return true;
		if (family == AF_INET6 && protocols[i] == NLPID_IPV6)
			return true;
	}
	return false;
}

struct isis_spf_run {
	struct isis_area *area;
	int level;
};

/* 7.2.7 */
static void remove_excess_adjs(struct list *adjs)
{
	struct listnode *node, *excess = NULL;
	struct isis_vertex_adj *vadj, *candidate = NULL;
	int comp;

	for (ALL_LIST_ELEMENTS_RO(adjs, node, vadj)) {
		struct isis_adjacency *adj, *candidate_adj;

		adj = vadj->sadj->adj;
		assert(adj);

		if (excess == NULL)
			excess = node;
		candidate = listgetdata(excess);
		candidate_adj = candidate->sadj->adj;

		if (candidate_adj->sys_type < adj->sys_type) {
			excess = node;
			continue;
		}
		if (candidate_adj->sys_type > adj->sys_type)
			continue;

		comp = memcmp(candidate_adj->sysid, adj->sysid,
			      ISIS_SYS_ID_LEN);
		if (comp > 0) {
			excess = node;
			continue;
		}
		if (comp < 0)
			continue;

		if (candidate_adj->circuit->idx > adj->circuit->idx) {
			excess = node;
			continue;
		}

		if (candidate_adj->circuit->idx < adj->circuit->idx)
			continue;

		comp = memcmp(candidate_adj->snpa, adj->snpa, ETH_ALEN);
		if (comp > 0) {
			excess = node;
			continue;
		}
	}

	list_delete_node(adjs, excess);

	return;
}

static const char *vtype2string(enum vertextype vtype)
{
	switch (vtype) {
	case VTYPE_PSEUDO_IS:
		return "pseudo_IS";
	case VTYPE_PSEUDO_TE_IS:
		return "pseudo_TE-IS";
	case VTYPE_NONPSEUDO_IS:
		return "IS";
	case VTYPE_NONPSEUDO_TE_IS:
		return "TE-IS";
	case VTYPE_ES:
		return "ES";
	case VTYPE_IPREACH_INTERNAL:
		return "IP internal";
	case VTYPE_IPREACH_EXTERNAL:
		return "IP external";
	case VTYPE_IPREACH_TE:
		return "IP TE";
	case VTYPE_IP6REACH_INTERNAL:
		return "IP6 internal";
	case VTYPE_IP6REACH_EXTERNAL:
		return "IP6 external";
	default:
		return "UNKNOWN";
	}
	return NULL; /* Not reached */
}

const char *vid2string(struct isis_vertex *vertex, char *buff, int size)
{
	if (VTYPE_IS(vertex->type) || VTYPE_ES(vertex->type)) {
		const char *hostname = print_sys_hostname(vertex->N.id);
		strlcpy(buff, hostname, size);
		return buff;
	}

	if (VTYPE_IP(vertex->type)) {
		srcdest2str(&vertex->N.ip.dest,
			    &vertex->N.ip.src,
			    buff, size);
		return buff;
	}

	return "UNKNOWN";
}

static void isis_vertex_adj_free(void *arg)
{
	struct isis_vertex_adj *vadj = arg;

	XFREE(MTYPE_ISIS_VERTEX_ADJ, vadj);
}

static struct isis_vertex *isis_vertex_new(struct isis_spftree *spftree,
					   void *id,
					   enum vertextype vtype)
{
	struct isis_vertex *vertex;

	vertex = XCALLOC(MTYPE_ISIS_VERTEX, sizeof(struct isis_vertex));

	isis_vertex_id_init(vertex, id, vtype);

	vertex->Adj_N = list_new();
	vertex->Adj_N->del = isis_vertex_adj_free;
	vertex->parents = list_new();

	if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)) {
		vertex->firsthops = hash_create(isis_vertex_queue_hash_key,
						isis_vertex_queue_hash_cmp,
						NULL);
	}

	return vertex;
}

static struct isis_vertex_adj *isis_vertex_adj_add(struct isis_vertex *vertex,
						   struct isis_spf_adj *sadj)
{
	struct isis_vertex_adj *vadj;

	vadj = XCALLOC(MTYPE_ISIS_VERTEX_ADJ, sizeof(*vadj));
	vadj->sadj = sadj;
	listnode_add(vertex->Adj_N, vadj);

	return vadj;
}

static void isis_vertex_adj_del(struct isis_vertex *vertex,
				struct isis_adjacency *adj)
{
	struct isis_vertex_adj *vadj;
	struct listnode *node, *nextnode;

	if (!vertex)
		return;

	for (ALL_LIST_ELEMENTS(vertex->Adj_N, node, nextnode, vadj)) {
		if (vadj->sadj->adj == adj) {
			listnode_delete(vertex->Adj_N, vadj);
			isis_vertex_adj_free(vadj);
		}
	}
	return;
}

bool isis_vertex_adj_exists(const struct isis_spftree *spftree,
			    const struct isis_vertex *vertex,
			    const struct isis_spf_adj *sadj)
{
	struct isis_vertex_adj *tmp;
	struct listnode *node;

	for (ALL_LIST_ELEMENTS_RO(vertex->Adj_N, node, tmp)) {
		if (CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)) {
			if (memcmp(sadj->id, tmp->sadj->id, sizeof(sadj->id))
			    == 0)
				return true;
		} else {
			if (sadj->adj == tmp->sadj->adj)
				return true;
		}
	}

	return false;
}

static void isis_spf_adj_free(void *arg)
{
	struct isis_spf_adj *sadj = arg;

	XFREE(MTYPE_ISIS_SPF_ADJ, sadj);
}

struct isis_spftree *isis_spftree_new(struct isis_area *area,
				      struct lspdb_head *lspdb,
				      const uint8_t *sysid, int level,
				      enum spf_tree_id tree_id,
				      enum spf_type type, uint8_t flags)
{
	struct isis_spftree *tree;

	tree = XCALLOC(MTYPE_ISIS_SPFTREE, sizeof(struct isis_spftree));

	isis_vertex_queue_init(&tree->tents, "IS-IS SPF tents", true);
	isis_vertex_queue_init(&tree->paths, "IS-IS SPF paths", false);
	tree->route_table = srcdest_table_init();
	tree->area = area;
	tree->lspdb = lspdb;
	tree->sadj_list = list_new();
	tree->sadj_list->del = isis_spf_adj_free;
	tree->last_run_timestamp = 0;
	tree->last_run_monotime = 0;
	tree->last_run_duration = 0;
	tree->runcount = 0;
	tree->type = type;
	memcpy(tree->sysid, sysid, ISIS_SYS_ID_LEN);
	tree->level = level;
	tree->tree_id = tree_id;
	tree->family = (tree->tree_id == SPFTREE_IPV4) ? AF_INET : AF_INET6;
	tree->flags = flags;

	return tree;
}

void isis_spftree_del(struct isis_spftree *spftree)
{
	list_delete(&spftree->sadj_list);
	isis_vertex_queue_free(&spftree->tents);
	isis_vertex_queue_free(&spftree->paths);
	route_table_finish(spftree->route_table);
	spftree->route_table = NULL;

	XFREE(MTYPE_ISIS_SPFTREE, spftree);
	return;
}

static void isis_spftree_adj_del(struct isis_spftree *spftree,
				 struct isis_adjacency *adj)
{
	struct listnode *node;
	struct isis_vertex *v;
	if (!adj)
		return;
	assert(!isis_vertex_queue_count(&spftree->tents));
	for (ALL_QUEUE_ELEMENTS_RO(&spftree->paths, node, v))
		isis_vertex_adj_del(v, adj);
	return;
}

void spftree_area_init(struct isis_area *area)
{
	for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
		for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
			if (!(area->is_type & level))
				continue;
			if (area->spftree[tree][level - 1])
				continue;

			area->spftree[tree][level - 1] =
				isis_spftree_new(area, &area->lspdb[level - 1],
						 area->isis->sysid, level, tree,
						 SPF_TYPE_FORWARD, 0);
		}
	}
}

void spftree_area_del(struct isis_area *area)
{
	for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
		for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
			if (!(area->is_type & level))
				continue;
			if (!area->spftree[tree][level - 1])
				continue;

			isis_spftree_del(area->spftree[tree][level - 1]);
		}
	}
}

static int spf_adj_state_change(struct isis_adjacency *adj)
{
	struct isis_area *area = adj->circuit->area;

	if (adj->adj_state == ISIS_ADJ_UP)
		return 0;

	/* Remove adjacency from all SPF trees. */
	for (int tree = SPFTREE_IPV4; tree < SPFTREE_COUNT; tree++) {
		for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) {
			if (!(area->is_type & level))
				continue;
			if (!area->spftree[tree][level - 1])
				continue;
			isis_spftree_adj_del(area->spftree[tree][level - 1],
					     adj);
		}
	}

	if (fabricd_spftree(area) != NULL)
		isis_spftree_adj_del(fabricd_spftree(area), adj);

	return 0;
}

/*
 * Find the system LSP: returns the LSP in our LSP database
 * associated with the given system ID.
 */
static struct isis_lsp *isis_root_system_lsp(struct lspdb_head *lspdb,
					     uint8_t *sysid)
{
	struct isis_lsp *lsp;
	uint8_t lspid[ISIS_SYS_ID_LEN + 2];

	memcpy(lspid, sysid, ISIS_SYS_ID_LEN);
	LSP_PSEUDO_ID(lspid) = 0;
	LSP_FRAGMENT(lspid) = 0;
	lsp = lsp_search(lspdb, lspid);
	if (lsp && lsp->hdr.rem_lifetime != 0)
		return lsp;
	return NULL;
}

/*
 * Add this IS to the root of SPT
 */
static struct isis_vertex *isis_spf_add_root(struct isis_spftree *spftree)
{
	struct isis_vertex *vertex;
#ifdef EXTREME_DEBUG
	char buff[VID2STR_BUFFER];
#endif /* EXTREME_DEBUG */

	vertex = isis_vertex_new(spftree, spftree->sysid,
				 spftree->area->oldmetric
					 ? VTYPE_NONPSEUDO_IS
					 : VTYPE_NONPSEUDO_TE_IS);
	isis_vertex_queue_append(&spftree->paths, vertex);

#ifdef EXTREME_DEBUG
	zlog_debug("ISIS-Spf: added this IS %s %s depth %d dist %d to PATHS",
		   vtype2string(vertex->type),
		   vid2string(vertex, buff, sizeof(buff)), vertex->depth,
		   vertex->d_N);
#endif /* EXTREME_DEBUG */

	return vertex;
}

static void vertex_add_parent_firsthop(struct hash_bucket *bucket, void *arg)
{
	struct isis_vertex *vertex = arg;
	struct isis_vertex *hop = bucket->data;

	hash_get(vertex->firsthops, hop, hash_alloc_intern);
}

static void vertex_update_firsthops(struct isis_vertex *vertex,
				    struct isis_vertex *parent)
{
	if (vertex->d_N <= 2)
		hash_get(vertex->firsthops, vertex, hash_alloc_intern);

	if (vertex->d_N < 2 || !parent)
		return;

	hash_iterate(parent->firsthops, vertex_add_parent_firsthop, vertex);
}

/*
 * Add a vertex to TENT sorted by cost and by vertextype on tie break situation
 */
static struct isis_vertex *isis_spf_add2tent(struct isis_spftree *spftree,
					     enum vertextype vtype, void *id,
					     uint32_t cost, int depth,
					     struct isis_spf_adj *sadj,
					     struct isis_vertex *parent)
{
	struct isis_vertex *vertex;
	struct listnode *node;
	char buff[VID2STR_BUFFER];

	vertex = isis_find_vertex(&spftree->paths, id, vtype);
	if (vertex != NULL) {
		zlog_err(
			"%s: vertex %s of type %s already in PATH; check for sysId collisions with established neighbors",
			__func__, vid2string(vertex, buff, sizeof(buff)),
			vtype2string(vertex->type));
		return NULL;
	}
	vertex = isis_find_vertex(&spftree->tents, id, vtype);
	if (vertex != NULL) {
		zlog_err(
			"%s: vertex %s of type %s already in TENT; check for sysId collisions with established neighbors",
			__func__, vid2string(vertex, buff, sizeof(buff)),
			vtype2string(vertex->type));
		return NULL;
	}

	vertex = isis_vertex_new(spftree, id, vtype);
	vertex->d_N = cost;
	vertex->depth = depth;

	if (parent) {
		listnode_add(vertex->parents, parent);
	}

	if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC))
		vertex_update_firsthops(vertex, parent);

	if (parent && parent->Adj_N && listcount(parent->Adj_N) > 0) {
		struct isis_vertex_adj *parent_vadj;

		for (ALL_LIST_ELEMENTS_RO(parent->Adj_N, node, parent_vadj))
			isis_vertex_adj_add(vertex, parent_vadj->sadj);
	} else if (sadj) {
		isis_vertex_adj_add(vertex, sadj);
	}

#ifdef EXTREME_DEBUG
	zlog_debug(
		"ISIS-Spf: add to TENT %s %s %s depth %d dist %d adjcount %d",
		print_sys_hostname(vertex->N.id), vtype2string(vertex->type),
		vid2string(vertex, buff, sizeof(buff)), vertex->depth,
		vertex->d_N, listcount(vertex->Adj_N));
#endif /* EXTREME_DEBUG */

	isis_vertex_queue_insert(&spftree->tents, vertex);
	return vertex;
}

static void isis_spf_add_local(struct isis_spftree *spftree,
			       enum vertextype vtype, void *id,
			       struct isis_spf_adj *sadj, uint32_t cost,
			       struct isis_vertex *parent)
{
	struct isis_vertex *vertex;

	vertex = isis_find_vertex(&spftree->tents, id, vtype);

	if (vertex) {
		/* C.2.5   c) */
		if (vertex->d_N == cost) {
			if (sadj)
				isis_vertex_adj_add(vertex, sadj);
			/*       d) */
			if (!CHECK_FLAG(spftree->flags,
					F_SPFTREE_NO_ADJACENCIES)
			    && listcount(vertex->Adj_N) > ISIS_MAX_PATH_SPLITS)
				remove_excess_adjs(vertex->Adj_N);
			if (parent && (listnode_lookup(vertex->parents, parent)
				       == NULL))
				listnode_add(vertex->parents, parent);
			return;
		} else if (vertex->d_N < cost) {
			/*       e) do nothing */
			return;
		} else { /* vertex->d_N > cost */
			/*         f) */
			isis_vertex_queue_delete(&spftree->tents, vertex);
			isis_vertex_del(vertex);
		}
	}

	isis_spf_add2tent(spftree, vtype, id, cost, 1, sadj, parent);
	return;
}

static void process_N(struct isis_spftree *spftree, enum vertextype vtype,
		      void *id, uint32_t dist, uint16_t depth,
		      struct isis_vertex *parent)
{
	struct isis_vertex *vertex;
#ifdef EXTREME_DEBUG
	char buff[VID2STR_BUFFER];
#endif

	assert(spftree && parent);

	if (CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)
	    && !VTYPE_IS(vtype))
		return;

	struct prefix_pair p;
	if (vtype >= VTYPE_IPREACH_INTERNAL) {
		memcpy(&p, id, sizeof(p));
		apply_mask(&p.dest);
		apply_mask((struct prefix *)&p.src);
		id = &p;
	}

	/* RFC3787 section 5.1 */
	if (spftree->area->newmetric == 1) {
		if (dist > MAX_WIDE_PATH_METRIC)
			return;
	}
	/* C.2.6 b)    */
	else if (spftree->area->oldmetric == 1) {
		if (dist > MAX_NARROW_PATH_METRIC)
			return;
	}

	/*       c)    */
	vertex = isis_find_vertex(&spftree->paths, id, vtype);
	if (vertex) {
#ifdef EXTREME_DEBUG
		zlog_debug(
			"ISIS-Spf: process_N %s %s %s dist %d already found from PATH",
			print_sys_hostname(vertex->N.id), vtype2string(vtype),
			vid2string(vertex, buff, sizeof(buff)), dist);
#endif /* EXTREME_DEBUG */
		assert(dist >= vertex->d_N);
		return;
	}

	vertex = isis_find_vertex(&spftree->tents, id, vtype);
	/*       d)    */
	if (vertex) {
/*        1) */
#ifdef EXTREME_DEBUG
		zlog_debug(
			"ISIS-Spf: process_N %s %s %s dist %d parent %s adjcount %d",
			print_sys_hostname(vertex->N.id), vtype2string(vtype),
			vid2string(vertex, buff, sizeof(buff)), dist,
			(parent ? print_sys_hostname(parent->N.id) : "null"),
			(parent ? listcount(parent->Adj_N) : 0));
#endif /* EXTREME_DEBUG */
		if (vertex->d_N == dist) {
			struct listnode *node;
			struct isis_vertex_adj *parent_vadj;
			for (ALL_LIST_ELEMENTS_RO(parent->Adj_N, node,
						  parent_vadj))
				if (!isis_vertex_adj_exists(spftree, vertex,
							    parent_vadj->sadj))
					isis_vertex_adj_add(vertex,
							    parent_vadj->sadj);
			if (CHECK_FLAG(spftree->flags,
				       F_SPFTREE_HOPCOUNT_METRIC))
				vertex_update_firsthops(vertex, parent);
			/*      2) */
			if (!CHECK_FLAG(spftree->flags,
					F_SPFTREE_NO_ADJACENCIES)
			    && listcount(vertex->Adj_N) > ISIS_MAX_PATH_SPLITS)
				remove_excess_adjs(vertex->Adj_N);
			if (listnode_lookup(vertex->parents, parent) == NULL)
				listnode_add(vertex->parents, parent);
			return;
		} else if (vertex->d_N < dist) {
			return;
			/*      4) */
		} else {
			isis_vertex_queue_delete(&spftree->tents, vertex);
			isis_vertex_del(vertex);
		}
	}

#ifdef EXTREME_DEBUG
	zlog_debug("ISIS-Spf: process_N add2tent %s %s dist %d parent %s",
		   print_sys_hostname(id), vtype2string(vtype), dist,
		   (parent ? print_sys_hostname(parent->N.id) : "null"));
#endif /* EXTREME_DEBUG */

	isis_spf_add2tent(spftree, vtype, id, dist, depth, NULL, parent);
	return;
}

/*
 * C.2.6 Step 1
 */
static int isis_spf_process_lsp(struct isis_spftree *spftree,
				struct isis_lsp *lsp, uint32_t cost,
				uint16_t depth, uint8_t *root_sysid,
				struct isis_vertex *parent)
{
	bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id);
	struct listnode *fragnode = NULL;
	uint32_t dist;
	enum vertextype vtype;
	static const uint8_t null_sysid[ISIS_SYS_ID_LEN];
	struct isis_mt_router_info *mt_router_info = NULL;
	struct prefix_pair ip_info;

	if (!lsp->tlvs)
		return ISIS_OK;

	if (spftree->mtid != ISIS_MT_IPV4_UNICAST)
		mt_router_info = isis_tlvs_lookup_mt_router_info(lsp->tlvs,
								 spftree->mtid);

	if (!pseudo_lsp && (spftree->mtid == ISIS_MT_IPV4_UNICAST
			    && !speaks(lsp->tlvs->protocols_supported.protocols,
				       lsp->tlvs->protocols_supported.count,
				       spftree->family))
	    && !mt_router_info)
		return ISIS_OK;

	/* RFC3787 section 4 SHOULD ignore overload bit in pseudo LSPs */
	bool no_overload = (pseudo_lsp
			    || (spftree->mtid == ISIS_MT_IPV4_UNICAST
				&& !ISIS_MASK_LSP_OL_BIT(lsp->hdr.lsp_bits))
			    || (mt_router_info && !mt_router_info->overload));

lspfragloop:
	if (lsp->hdr.seqno == 0) {
		zlog_warn(
			"isis_spf_process_lsp(): lsp with 0 seq_num - ignore");
		return ISIS_WARNING;
	}

#ifdef EXTREME_DEBUG
	zlog_debug("ISIS-Spf: process_lsp %s",
		   print_sys_hostname(lsp->hdr.lsp_id));
#endif /* EXTREME_DEBUG */

	if (no_overload) {
		if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) {
			struct isis_oldstyle_reach *r;
			for (r = (struct isis_oldstyle_reach *)
					 lsp->tlvs->oldstyle_reach.head;
			     r; r = r->next) {
				if (fabricd)
					continue;

				/* C.2.6 a) */
				/* Two way connectivity */
				if (!LSP_PSEUDO_ID(r->id)
				    && !memcmp(r->id, root_sysid,
					       ISIS_SYS_ID_LEN))
					continue;
				if (!pseudo_lsp
				    && !memcmp(r->id, null_sysid,
					       ISIS_SYS_ID_LEN))
					continue;
				dist = cost + r->metric;
				process_N(spftree,
					  LSP_PSEUDO_ID(r->id)
						  ? VTYPE_PSEUDO_IS
						  : VTYPE_NONPSEUDO_IS,
					  (void *)r->id, dist, depth + 1,
					  parent);
			}
		}

		struct isis_item_list *te_neighs = NULL;
		if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST)
			te_neighs = &lsp->tlvs->extended_reach;
		else
			te_neighs = isis_lookup_mt_items(&lsp->tlvs->mt_reach,
							 spftree->mtid);

		struct isis_extended_reach *er;
		for (er = te_neighs
				  ? (struct isis_extended_reach *)
					    te_neighs->head
				  : NULL;
		     er; er = er->next) {
			/* C.2.6 a) */
			/* Two way connectivity */
			if (!LSP_PSEUDO_ID(er->id)
			    && !memcmp(er->id, root_sysid, ISIS_SYS_ID_LEN))
				continue;
			if (!pseudo_lsp
			    && !memcmp(er->id, null_sysid, ISIS_SYS_ID_LEN))
				continue;
			dist = cost
			       + (CHECK_FLAG(spftree->flags,
					     F_SPFTREE_HOPCOUNT_METRIC)
					  ? 1
					  : er->metric);
			process_N(spftree,
				  LSP_PSEUDO_ID(er->id) ? VTYPE_PSEUDO_TE_IS
							: VTYPE_NONPSEUDO_TE_IS,
				  (void *)er->id, dist, depth + 1, parent);
		}
	}

	if (!fabricd && !pseudo_lsp && spftree->family == AF_INET
	    && spftree->mtid == ISIS_MT_IPV4_UNICAST) {
		struct isis_item_list *reachs[] = {
			&lsp->tlvs->oldstyle_ip_reach,
			&lsp->tlvs->oldstyle_ip_reach_ext};

		for (unsigned int i = 0; i < array_size(reachs); i++) {
			vtype = i ? VTYPE_IPREACH_EXTERNAL
				  : VTYPE_IPREACH_INTERNAL;

			memset(&ip_info, 0, sizeof(ip_info));
			ip_info.dest.family = AF_INET;

			struct isis_oldstyle_ip_reach *r;
			for (r = (struct isis_oldstyle_ip_reach *)reachs[i]
					 ->head;
			     r; r = r->next) {
				dist = cost + r->metric;
				ip_info.dest.u.prefix4 = r->prefix.prefix;
				ip_info.dest.prefixlen = r->prefix.prefixlen;
				process_N(spftree, vtype, &ip_info,
					  dist, depth + 1, parent);
			}
		}
	}

	if (!pseudo_lsp && spftree->family == AF_INET) {
		struct isis_item_list *ipv4_reachs;
		if (spftree->mtid == ISIS_MT_IPV4_UNICAST)
			ipv4_reachs = &lsp->tlvs->extended_ip_reach;
		else
			ipv4_reachs = isis_lookup_mt_items(
				&lsp->tlvs->mt_ip_reach, spftree->mtid);

		memset(&ip_info, 0, sizeof(ip_info));
		ip_info.dest.family = AF_INET;

		struct isis_extended_ip_reach *r;
		for (r = ipv4_reachs
				 ? (struct isis_extended_ip_reach *)
					   ipv4_reachs->head
				 : NULL;
		     r; r = r->next) {
			dist = cost + r->metric;
			ip_info.dest.u.prefix4 = r->prefix.prefix;
			ip_info.dest.prefixlen = r->prefix.prefixlen;
			process_N(spftree, VTYPE_IPREACH_TE, &ip_info,
				  dist, depth + 1, parent);
		}
	}

	if (!pseudo_lsp && spftree->family == AF_INET6) {
		struct isis_item_list *ipv6_reachs;
		if (spftree->mtid == ISIS_MT_IPV4_UNICAST)
			ipv6_reachs = &lsp->tlvs->ipv6_reach;
		else
			ipv6_reachs = isis_lookup_mt_items(
				&lsp->tlvs->mt_ipv6_reach, spftree->mtid);

		struct isis_ipv6_reach *r;
		for (r = ipv6_reachs
				 ? (struct isis_ipv6_reach *)ipv6_reachs->head
				 : NULL;
		     r; r = r->next) {
			dist = cost + r->metric;
			vtype = r->external ? VTYPE_IP6REACH_EXTERNAL
					    : VTYPE_IP6REACH_INTERNAL;
			memset(&ip_info, 0, sizeof(ip_info));
			ip_info.dest.family = AF_INET6;
			ip_info.dest.u.prefix6 = r->prefix.prefix;
			ip_info.dest.prefixlen = r->prefix.prefixlen;

			if (r->subtlvs
			    && r->subtlvs->source_prefix
			    && r->subtlvs->source_prefix->prefixlen) {
				if (spftree->tree_id != SPFTREE_DSTSRC) {
					char buff[VID2STR_BUFFER];
					zlog_warn("Ignoring dest-src route %s in non dest-src topology",
						srcdest2str(
							&ip_info.dest,
							r->subtlvs->source_prefix,
							buff, sizeof(buff)
						)
					);
					continue;
				}
				ip_info.src = *r->subtlvs->source_prefix;
			}
			process_N(spftree, vtype, &ip_info, dist,
				  depth + 1, parent);
		}
	}

	if (fragnode == NULL)
		fragnode = listhead(lsp->lspu.frags);
	else
		fragnode = listnextnode(fragnode);

	if (fragnode) {
		lsp = listgetdata(fragnode);
		goto lspfragloop;
	}

	return ISIS_OK;
}

static struct isis_adjacency *adj_find(struct list *adj_list, const uint8_t *id,
				       int level, uint16_t mtid, int family)
{
	struct isis_adjacency *adj;
	struct listnode *node;

	for (ALL_LIST_ELEMENTS_RO(adj_list, node, adj)) {
		if (!(adj->level & level))
			continue;
		if (memcmp(adj->sysid, id, ISIS_SYS_ID_LEN) != 0)
			continue;
		if (adj->adj_state != ISIS_ADJ_UP)
			continue;
		if (!adj_has_mt(adj, mtid))
			continue;
		if (mtid == ISIS_MT_IPV4_UNICAST
		    && !speaks(adj->nlpids.nlpids, adj->nlpids.count, family))
			continue;
		return adj;
	}

	return NULL;
}

struct spf_preload_tent_ip_reach_args {
	struct isis_spftree *spftree;
	struct isis_vertex *parent;
};

static int isis_spf_preload_tent_ip_reach_cb(const struct prefix *prefix,
					     uint32_t metric, bool external,
					     struct isis_subtlvs *subtlvs,
					     void *arg)
{
	struct spf_preload_tent_ip_reach_args *args = arg;
	struct isis_spftree *spftree = args->spftree;
	struct isis_vertex *parent = args->parent;
	struct prefix_pair ip_info;
	enum vertextype vtype;

	if (external)
		return LSP_ITER_CONTINUE;

	assert(spftree->family == prefix->family);
	memset(&ip_info, 0, sizeof(ip_info));
	prefix_copy(&ip_info.dest, prefix);
	apply_mask(&ip_info.dest);

	if (prefix->family == AF_INET)
		vtype = VTYPE_IPREACH_INTERNAL;
	else
		vtype = VTYPE_IP6REACH_INTERNAL;

	isis_spf_add_local(spftree, vtype, &ip_info, NULL, 0, parent);

	return LSP_ITER_CONTINUE;
}

static void isis_spf_preload_tent(struct isis_spftree *spftree,
				  uint8_t *root_sysid,
				  struct isis_lsp *root_lsp,
				  struct isis_vertex *parent)
{
	struct spf_preload_tent_ip_reach_args ip_reach_args;
	struct isis_spf_adj *sadj;
	struct listnode *node;

	if (!CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)) {
		ip_reach_args.spftree = spftree;
		ip_reach_args.parent = parent;
		isis_lsp_iterate_ip_reach(
			root_lsp, spftree->family, spftree->mtid,
			isis_spf_preload_tent_ip_reach_cb, &ip_reach_args);
	}

	/* Iterate over adjacencies. */
	for (ALL_LIST_ELEMENTS_RO(spftree->sadj_list, node, sadj)) {
		uint32_t metric;

		metric = CHECK_FLAG(spftree->flags, F_SPFTREE_HOPCOUNT_METRIC)
				 ? 1
				 : sadj->metric;
		if (!LSP_PSEUDO_ID(sadj->id)) {
			isis_spf_add_local(spftree,
					   CHECK_FLAG(sadj->flags,
						      F_ISIS_SPF_ADJ_OLDMETRIC)
						   ? VTYPE_NONPSEUDO_IS
						   : VTYPE_NONPSEUDO_TE_IS,
					   sadj->id, sadj, metric, parent);
		} else if (sadj->lan.lsp_pseudo) {
			isis_spf_process_lsp(spftree, sadj->lan.lsp_pseudo,
					     metric, 0, spftree->sysid, parent);
		}
	}
}

struct spf_adj_find_reverse_metric_args {
	const uint8_t *id_self;
	uint32_t reverse_metric;
};

static int spf_adj_find_reverse_metric_cb(const uint8_t *id, uint32_t metric,
					  bool oldmetric,
					  struct isis_ext_subtlvs *subtlvs,
					  void *arg)
{
	struct spf_adj_find_reverse_metric_args *args = arg;

	if (memcmp(id, args->id_self, ISIS_SYS_ID_LEN))
		return LSP_ITER_CONTINUE;

	args->reverse_metric = metric;

	return LSP_ITER_STOP;
}

/*
 * Change all SPF adjacencies to use the link cost in the direction from the
 * next hop back towards root in place of the link cost in the direction away
 * from root towards the next hop.
 */
static void spf_adj_get_reverse_metrics(struct isis_spftree *spftree)
{
	struct isis_spf_adj *sadj;
	struct listnode *node, *nnode;

	for (ALL_LIST_ELEMENTS(spftree->sadj_list, node, nnode, sadj)) {
		uint8_t lspid[ISIS_SYS_ID_LEN + 2];
		struct isis_lsp *lsp_adj;
		const uint8_t *id_self;
		struct spf_adj_find_reverse_metric_args args;

		/* Skip pseudonodes. */
		if (LSP_PSEUDO_ID(sadj->id))
			continue;

		/* Find LSP of the corresponding adjacency. */
		memcpy(lspid, sadj->id, ISIS_SYS_ID_LEN);
		LSP_PSEUDO_ID(lspid) = 0;
		LSP_FRAGMENT(lspid) = 0;
		lsp_adj = lsp_search(spftree->lspdb, lspid);
		if (lsp_adj == NULL || lsp_adj->hdr.rem_lifetime == 0) {
			/* Delete one-way adjacency. */
			listnode_delete(spftree->sadj_list, sadj);
			continue;
		}

		/* Find root node in the LSP of the adjacent router. */
		if (CHECK_FLAG(sadj->flags, F_ISIS_SPF_ADJ_BROADCAST))
			id_self = sadj->lan.desig_is_id;
		else
			id_self = spftree->sysid;
		args.id_self = id_self;
		args.reverse_metric = UINT32_MAX;
		isis_lsp_iterate_is_reach(lsp_adj, spftree->mtid,
					  spf_adj_find_reverse_metric_cb,
					  &args);
		if (args.reverse_metric == UINT32_MAX) {
			/* Delete one-way adjacency. */
			listnode_delete(spftree->sadj_list, sadj);
			continue;
		}
		sadj->metric = args.reverse_metric;
	}
}

static void spf_adj_list_parse_tlv(struct isis_spftree *spftree,
				   struct list *adj_list, const uint8_t *id,
				   const uint8_t *desig_is_id,
				   uint32_t pseudo_metric, uint32_t metric,
				   bool oldmetric,
				   struct isis_ext_subtlvs *subtlvs)
{
	struct isis_spf_adj *sadj;
	uint8_t flags = 0;

	/* Skip self in the pseudonode. */
	if (desig_is_id && !memcmp(id, spftree->sysid, ISIS_SYS_ID_LEN))
		return;

	sadj = XCALLOC(MTYPE_ISIS_SPF_ADJ, sizeof(*sadj));
	memcpy(sadj->id, id, sizeof(sadj->id));
	if (desig_is_id) {
		memcpy(sadj->lan.desig_is_id, desig_is_id,
		       sizeof(sadj->lan.desig_is_id));
		SET_FLAG(flags, F_ISIS_SPF_ADJ_BROADCAST);
		sadj->metric = pseudo_metric;
	} else
		sadj->metric = metric;
	if (oldmetric)
		SET_FLAG(flags, F_ISIS_SPF_ADJ_OLDMETRIC);
	sadj->subtlvs = subtlvs;
	sadj->flags = flags;

	/* Set real adjacency. */
	if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES)
	    && !LSP_PSEUDO_ID(id)) {
		struct isis_adjacency *adj;

		adj = adj_find(adj_list, id, spftree->level, spftree->mtid,
			       spftree->family);
		if (!adj) {
			XFREE(MTYPE_ISIS_SPF_ADJ, sadj);
			return;
		}

		listnode_delete(adj_list, adj);
		sadj->adj = adj;
	}

	/* Add adjacency to the list. */
	listnode_add(spftree->sadj_list, sadj);

	/* Parse pseudonode LSP too. */
	if (LSP_PSEUDO_ID(id)) {
		uint8_t lspid[ISIS_SYS_ID_LEN + 2];
		struct isis_lsp *lsp_pseudo;

		memcpy(lspid, id, ISIS_SYS_ID_LEN + 1);
		LSP_FRAGMENT(lspid) = 0;
		lsp_pseudo = lsp_search(spftree->lspdb, lspid);
		if (lsp_pseudo == NULL || lsp_pseudo->hdr.rem_lifetime == 0) {
			zlog_warn(
				"ISIS-Spf: No LSP found from root to L%d DR %s",
				spftree->level, rawlspid_print(id));
			return;
		}

		sadj->lan.lsp_pseudo = lsp_pseudo;
		spf_adj_list_parse_lsp(spftree, adj_list, lsp_pseudo, id,
				       metric);
	}
}

static void spf_adj_list_parse_lsp(struct isis_spftree *spftree,
				   struct list *adj_list, struct isis_lsp *lsp,
				   const uint8_t *pseudo_nodeid,
				   uint32_t pseudo_metric)
{
	bool pseudo_lsp = LSP_PSEUDO_ID(lsp->hdr.lsp_id);
	struct isis_lsp *frag;
	struct listnode *node;
	struct isis_item *head;
	struct isis_item_list *te_neighs;

	if (lsp->hdr.seqno == 0 || lsp->hdr.rem_lifetime == 0)
		return;

	/* Parse main LSP. */
	if (lsp->tlvs) {
		if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST) {
			head = lsp->tlvs->oldstyle_reach.head;
			for (struct isis_oldstyle_reach *reach =
				     (struct isis_oldstyle_reach *)head;
			     reach; reach = reach->next) {
				spf_adj_list_parse_tlv(
					spftree, adj_list, reach->id,
					pseudo_nodeid, pseudo_metric,
					reach->metric, true, NULL);
			}
		}

		if (pseudo_lsp || spftree->mtid == ISIS_MT_IPV4_UNICAST)
			te_neighs = &lsp->tlvs->extended_reach;
		else
			te_neighs = isis_get_mt_items(&lsp->tlvs->mt_reach,
						      spftree->mtid);
		if (te_neighs) {
			head = te_neighs->head;
			for (struct isis_extended_reach *reach =
				     (struct isis_extended_reach *)head;
			     reach; reach = reach->next) {
				spf_adj_list_parse_tlv(
					spftree, adj_list, reach->id,
					pseudo_nodeid, pseudo_metric,
					reach->metric, false, reach->subtlvs);
			}
		}
	}

	/* Parse LSP fragments. */
	for (ALL_LIST_ELEMENTS_RO(lsp->lspu.frags, node, frag)) {
		if (!frag->tlvs)
			continue;

		spf_adj_list_parse_lsp(spftree, adj_list, frag, pseudo_nodeid,
				       pseudo_metric);
	}
}

static void isis_spf_build_adj_list(struct isis_spftree *spftree,
				    struct isis_lsp *lsp)
{
	struct list *adj_list = NULL;

	if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES))
		adj_list = list_dup(spftree->area->adjacency_list);

	spf_adj_list_parse_lsp(spftree, adj_list, lsp, NULL, 0);

	if (!CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ADJACENCIES))
		list_delete(&adj_list);

	if (spftree->type == SPF_TYPE_REVERSE)
		spf_adj_get_reverse_metrics(spftree);
}

/*
 * The parent(s) for vertex is set when added to TENT list
 * now we just put the child pointer(s) in place
 */
static void add_to_paths(struct isis_spftree *spftree,
			 struct isis_vertex *vertex)
{
	char buff[VID2STR_BUFFER];

	if (isis_find_vertex(&spftree->paths, &vertex->N, vertex->type))
		return;
	isis_vertex_queue_append(&spftree->paths, vertex);

#ifdef EXTREME_DEBUG
	zlog_debug("ISIS-Spf: added %s %s %s depth %d dist %d to PATHS",
		   print_sys_hostname(vertex->N.id), vtype2string(vertex->type),
		   vid2string(vertex, buff, sizeof(buff)), vertex->depth,
		   vertex->d_N);
#endif /* EXTREME_DEBUG */

	if (VTYPE_IP(vertex->type)
	    && !CHECK_FLAG(spftree->flags, F_SPFTREE_NO_ROUTES)) {
		if (listcount(vertex->Adj_N) > 0)
			isis_route_create(&vertex->N.ip.dest, &vertex->N.ip.src,
					  vertex->d_N, vertex->depth,
					  vertex->Adj_N, spftree->area,
					  spftree->route_table);
		else if (IS_DEBUG_SPF_EVENTS)
			zlog_debug(
				"ISIS-Spf: no adjacencies do not install route for %s depth %d dist %d",
				vid2string(vertex, buff, sizeof(buff)),
				vertex->depth, vertex->d_N);
	}

	return;
}

static void init_spt(struct isis_spftree *spftree, int mtid)
{
	/* Clear data from previous run. */
	list_delete_all_node(spftree->sadj_list);
	isis_vertex_queue_clear(&spftree->tents);
	isis_vertex_queue_clear(&spftree->paths);

	spftree->mtid = mtid;
}

static void isis_spf_loop(struct isis_spftree *spftree,
			  uint8_t *root_sysid)
{
	struct isis_vertex *vertex;
	struct isis_lsp *lsp;

	while (isis_vertex_queue_count(&spftree->tents)) {
		vertex = isis_vertex_queue_pop(&spftree->tents);

#ifdef EXTREME_DEBUG
		zlog_debug(
			"ISIS-Spf: get TENT node %s %s depth %d dist %d to PATHS",
			print_sys_hostname(vertex->N.id),
			vtype2string(vertex->type), vertex->depth, vertex->d_N);
#endif /* EXTREME_DEBUG */

		add_to_paths(spftree, vertex);
		if (!VTYPE_IS(vertex->type))
			continue;

		lsp = lsp_for_vertex(spftree, vertex);
		if (!lsp) {
			zlog_warn("ISIS-Spf: No LSP found for %s",
				  isis_format_id(vertex->N.id,
						 sizeof(vertex->N.id)));
			continue;
		}

		isis_spf_process_lsp(spftree, lsp, vertex->d_N, vertex->depth,
				     root_sysid, vertex);
	}
}

struct isis_spftree *isis_run_hopcount_spf(struct isis_area *area,
					   uint8_t *sysid,
					   struct isis_spftree *spftree)
{
	if (!spftree)
		spftree = isis_spftree_new(area, &area->lspdb[IS_LEVEL_2 - 1],
					   sysid, ISIS_LEVEL2, SPFTREE_IPV4,
					   SPF_TYPE_FORWARD,
					   F_SPFTREE_HOPCOUNT_METRIC);

	init_spt(spftree, ISIS_MT_IPV4_UNICAST);
	if (!memcmp(sysid, area->isis->sysid, ISIS_SYS_ID_LEN)) {
		struct isis_lsp *root_lsp;
		struct isis_vertex *root_vertex;

		root_lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid);
		if (root_lsp) {
			/*
			 * If we are running locally, initialize with
			 * information from adjacencies
			 */
			root_vertex = isis_spf_add_root(spftree);

			isis_spf_preload_tent(spftree, sysid, root_lsp,
					      root_vertex);
		}
	} else {
		isis_vertex_queue_insert(
			&spftree->tents,
			isis_vertex_new(spftree, sysid, VTYPE_NONPSEUDO_TE_IS));
	}

	isis_spf_loop(spftree, sysid);

	return spftree;
}

void isis_run_spf(struct isis_spftree *spftree)
{
	struct isis_lsp *root_lsp;
	struct isis_vertex *root_vertex;
	struct timeval time_start;
	struct timeval time_end;
	struct isis_mt_router_info *mt_router_info;
	uint16_t mtid = 0;

	/* Get time that can't roll backwards. */
	monotime(&time_start);

	root_lsp = isis_root_system_lsp(spftree->lspdb, spftree->sysid);
	if (root_lsp == NULL) {
		zlog_err("ISIS-Spf: could not find own l%d LSP!",
			 spftree->level);
		return;
	}

	/* Get Multi-Topology ID. */
	switch (spftree->tree_id) {
	case SPFTREE_IPV4:
		mtid = ISIS_MT_IPV4_UNICAST;
		break;
	case SPFTREE_IPV6:
		mt_router_info = isis_tlvs_lookup_mt_router_info(
			root_lsp->tlvs, ISIS_MT_IPV6_UNICAST);
		if (mt_router_info)
			mtid = ISIS_MT_IPV6_UNICAST;
		else
			mtid = ISIS_MT_IPV4_UNICAST;
		break;
	case SPFTREE_DSTSRC:
		mtid = ISIS_MT_IPV6_DSTSRC;
		break;
	case SPFTREE_COUNT:
		zlog_err(
			"isis_run_spf should never be called with SPFTREE_COUNT as argument!");
		exit(1);
	}

	/*
	 * C.2.5 Step 0
	 */
	init_spt(spftree, mtid);
	/*              a) */
	root_vertex = isis_spf_add_root(spftree);
	/*              b) */
	isis_spf_build_adj_list(spftree, root_lsp);
	isis_spf_preload_tent(spftree, spftree->sysid, root_lsp, root_vertex);

	/*
	 * C.2.7 Step 2
	 */
	if (!isis_vertex_queue_count(&spftree->tents)
	    && (IS_DEBUG_SPF_EVENTS)) {
		zlog_warn("ISIS-Spf: TENT is empty SPF-root:%s",
			  print_sys_hostname(spftree->sysid));
	}

	isis_spf_loop(spftree, spftree->sysid);
	spftree->runcount++;
	spftree->last_run_timestamp = time(NULL);
	spftree->last_run_monotime = monotime(&time_end);
	spftree->last_run_duration =
		((time_end.tv_sec - time_start.tv_sec) * 1000000)
		+ (time_end.tv_usec - time_start.tv_usec);
}

void isis_spf_verify_routes(struct isis_area *area, struct isis_spftree **trees)
{
	if (area->is_type == IS_LEVEL_1) {
		isis_route_verify_table(area, trees[0]->route_table);
	} else if (area->is_type == IS_LEVEL_2) {
		isis_route_verify_table(area, trees[1]->route_table);
	} else {
		isis_route_verify_merge(area, trees[0]->route_table,
					trees[1]->route_table);
	}
}

void isis_spf_invalidate_routes(struct isis_spftree *tree)
{
	isis_route_invalidate_table(tree->area, tree->route_table);
}

static int isis_run_spf_cb(struct thread *thread)
{
	struct isis_spf_run *run = THREAD_ARG(thread);
	struct isis_area *area = run->area;
	struct isis_spftree *spftree;
	int level = run->level;

	XFREE(MTYPE_ISIS_SPF_RUN, run);
	area->spf_timer[level - 1] = NULL;

	if (!(area->is_type & level)) {
		if (IS_DEBUG_SPF_EVENTS)
			zlog_warn("ISIS-SPF (%s) area does not share level",
				  area->area_tag);
		return ISIS_WARNING;
	}

	isis_area_invalidate_routes(area, level);

	if (IS_DEBUG_SPF_EVENTS)
		zlog_debug("ISIS-Spf (%s) L%d SPF needed, periodic SPF",
			   area->area_tag, level);

	if (area->ip_circuits) {
		spftree = area->spftree[SPFTREE_IPV4][level - 1];
		memcpy(spftree->sysid, area->isis->sysid, ISIS_SYS_ID_LEN);
		isis_run_spf(spftree);
	}
	if (area->ipv6_circuits) {
		spftree = area->spftree[SPFTREE_IPV6][level - 1];
		memcpy(spftree->sysid, area->isis->sysid, ISIS_SYS_ID_LEN);
		isis_run_spf(spftree);
	}
	if (area->ipv6_circuits && isis_area_ipv6_dstsrc_enabled(area)) {
		spftree = area->spftree[SPFTREE_DSTSRC][level - 1];
		memcpy(spftree->sysid, area->isis->sysid, ISIS_SYS_ID_LEN);
		isis_run_spf(spftree);
	}

	isis_area_verify_routes(area);

	isis_area_verify_sr(area);

	/* walk all circuits and reset any spf specific flags */
	struct listnode *node;
	struct isis_circuit *circuit;
	for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
		UNSET_FLAG(circuit->flags, ISIS_CIRCUIT_FLAPPED_AFTER_SPF);

	fabricd_run_spf(area);

	return 0;
}

static struct isis_spf_run *isis_run_spf_arg(struct isis_area *area, int level)
{
	struct isis_spf_run *run = XMALLOC(MTYPE_ISIS_SPF_RUN, sizeof(*run));

	run->area = area;
	run->level = level;

	return run;
}

int _isis_spf_schedule(struct isis_area *area, int level,
		       const char *func, const char *file, int line)
{
	struct isis_spftree *spftree = area->spftree[SPFTREE_IPV4][level - 1];
	time_t now = monotime(NULL);
	int diff = now - spftree->last_run_monotime;

	if (CHECK_FLAG(im->options, F_ISIS_UNIT_TEST))
		return 0;

	assert(diff >= 0);
	assert(area->is_type & level);

	if (IS_DEBUG_SPF_EVENTS) {
		zlog_debug(
			"ISIS-Spf (%s) L%d SPF schedule called, lastrun %d sec ago Caller: %s %s:%d",
			area->area_tag, level, diff, func, file, line);
	}

	if (area->spf_delay_ietf[level - 1]) {
		/* Need to call schedule function also if spf delay is running
		 * to
		 * restart holdoff timer - compare
		 * draft-ietf-rtgwg-backoff-algo-04 */
		long delay =
			spf_backoff_schedule(area->spf_delay_ietf[level - 1]);
		if (area->spf_timer[level - 1])
			return ISIS_OK;

		thread_add_timer_msec(master, isis_run_spf_cb,
				      isis_run_spf_arg(area, level), delay,
				      &area->spf_timer[level - 1]);
		return ISIS_OK;
	}

	if (area->spf_timer[level - 1])
		return ISIS_OK;

	/* wait configured min_spf_interval before doing the SPF */
	long timer;
	if (diff >= area->min_spf_interval[level - 1]
	    || area->bfd_force_spf_refresh) {
		/*
		 * Last run is more than min interval ago or BFD signalled a
		 * 'down' message, schedule immediate run
		 */
		timer = 0;

		if (area->bfd_force_spf_refresh) {
			zlog_debug(
				"ISIS-Spf (%s) L%d SPF scheduled immediately due to BFD 'down' message",
				area->area_tag, level);
			area->bfd_force_spf_refresh = false;
		}
	} else {
		timer = area->min_spf_interval[level - 1] - diff;
	}

	thread_add_timer(master, isis_run_spf_cb, isis_run_spf_arg(area, level),
			 timer, &area->spf_timer[level - 1]);

	if (IS_DEBUG_SPF_EVENTS)
		zlog_debug("ISIS-Spf (%s) L%d SPF scheduled %ld sec from now",
			   area->area_tag, level, timer);

	return ISIS_OK;
}

static void isis_print_paths(struct vty *vty, struct isis_vertex_queue *queue,
			     uint8_t *root_sysid)
{
	struct listnode *node;
	struct isis_vertex *vertex;
	char buff[VID2STR_BUFFER];

	vty_out(vty,
		"Vertex               Type         Metric Next-Hop             Interface Parent\n");

	for (ALL_QUEUE_ELEMENTS_RO(queue, node, vertex)) {
		if (memcmp(vertex->N.id, root_sysid, ISIS_SYS_ID_LEN) == 0) {
			vty_out(vty, "%-20s %-12s %-6s",
				print_sys_hostname(root_sysid), "", "");
			vty_out(vty, "%-30s\n", "");
			continue;
		}

		int rows = 0;
		struct listnode *anode = listhead(vertex->Adj_N);
		struct listnode *pnode = listhead(vertex->parents);
		struct isis_vertex_adj *vadj;
		struct isis_vertex *pvertex;

		vty_out(vty, "%-20s %-12s %-6u ",
			vid2string(vertex, buff, sizeof(buff)),
			vtype2string(vertex->type), vertex->d_N);
		for (unsigned int i = 0;
		     i < MAX(vertex->Adj_N ? listcount(vertex->Adj_N) : 0,
			     vertex->parents ? listcount(vertex->parents) : 0);
		     i++) {
			if (anode) {
				vadj = listgetdata(anode);
				anode = anode->next;
			} else {
				vadj = NULL;
			}

			if (pnode) {
				pvertex = listgetdata(pnode);
				pnode = pnode->next;
			} else {
				pvertex = NULL;
			}

			if (rows) {
				vty_out(vty, "\n");
				vty_out(vty, "%-20s %-12s %-6s ", "", "", "");
			}

			if (vadj) {
				struct isis_spf_adj *sadj = vadj->sadj;

				vty_out(vty, "%-20s %-9s ",
					print_sys_hostname(sadj->id),
					sadj->adj ? sadj->adj->circuit
							    ->interface->name
						  : "-");
			}

			if (pvertex) {
				if (!vadj)
					vty_out(vty, "%-20s %-9s ", "", "");

				vty_out(vty, "%s(%d)",
					vid2string(pvertex, buff, sizeof(buff)),
					pvertex->type);
			}

			++rows;
		}
		vty_out(vty, "\n");
	}
}

void isis_print_spftree(struct vty *vty, struct isis_spftree *spftree)
{
	const char *tree_id_text = NULL;

	if (!spftree || !isis_vertex_queue_count(&spftree->paths))
		return;

	switch (spftree->tree_id) {
	case SPFTREE_IPV4:
		tree_id_text = "that speak IP";
		break;
	case SPFTREE_IPV6:
		tree_id_text = "that speak IPv6";
		break;
	case SPFTREE_DSTSRC:
		tree_id_text = "that support IPv6 dst-src routing";
		break;
	case SPFTREE_COUNT:
		assert(!"isis_print_spftree shouldn't be called with SPFTREE_COUNT as type");
		return;
	}

	vty_out(vty, "IS-IS paths to level-%d routers %s\n", spftree->level,
		tree_id_text);
	isis_print_paths(vty, &spftree->paths, spftree->sysid);
	vty_out(vty, "\n");
}

static void show_isis_topology_common(struct vty *vty, int levels,
				      struct isis *isis)
{
	struct listnode *node;
	struct isis_area *area;

	if (!isis->area_list || isis->area_list->count == 0)
		return;

	for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
		vty_out(vty, "Area %s:\n",
			area->area_tag ? area->area_tag : "null");

		for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
			if ((level & levels) == 0)
				continue;

			if (area->ip_circuits > 0) {
				isis_print_spftree(
					vty,
					area->spftree[SPFTREE_IPV4][level - 1]);
			}
			if (area->ipv6_circuits > 0) {
				isis_print_spftree(
					vty,
					area->spftree[SPFTREE_IPV6][level - 1]);
			}
			if (isis_area_ipv6_dstsrc_enabled(area)) {
				isis_print_spftree(vty,
						   area->spftree[SPFTREE_DSTSRC]
								[level - 1]);
			}
		}

		if (fabricd_spftree(area)) {
			vty_out(vty,
				"IS-IS paths to level-2 routers with hop-by-hop metric\n");
			isis_print_paths(vty, &fabricd_spftree(area)->paths, isis->sysid);
			vty_out(vty, "\n");
		}

		vty_out(vty, "\n");
	}
}

DEFUN(show_isis_topology, show_isis_topology_cmd,
      "show " PROTO_NAME
      " [vrf <NAME|all>] topology"
#ifndef FABRICD
      " [<level-1|level-2>]"
#endif
      ,
      SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
      "All VRFs\n"
      "IS-IS paths to Intermediate Systems\n"
#ifndef FABRICD
      "Paths to all level-1 routers in the area\n"
      "Paths to all level-2 routers in the domain\n"
#endif
)
{
	int levels = ISIS_LEVELS;
	struct listnode *node;
	struct isis *isis = NULL;
	int idx = 0;
	const char *vrf_name = VRF_DEFAULT_NAME;
	bool all_vrf = false;
	int idx_vrf = 0;

	if (argv_find(argv, argc, "topology", &idx)) {
		if (argc < idx + 2)
			levels = ISIS_LEVEL1 | ISIS_LEVEL2;
		else if (strmatch(argv[idx + 1]->arg, "level-1"))
			levels = ISIS_LEVEL1;
		else
			levels = ISIS_LEVEL2;
	}

	if (!im) {
		vty_out(vty, "IS-IS Routing Process not enabled\n");
		return CMD_SUCCESS;
	}
	ISIS_FIND_VRF_ARGS(argv, argc, idx_vrf, vrf_name, all_vrf);

	if (vrf_name) {
		if (all_vrf) {
			for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
				show_isis_topology_common(vty, levels, isis);
			return CMD_SUCCESS;
		}
		isis = isis_lookup_by_vrfname(vrf_name);
		if (isis != NULL)
			show_isis_topology_common(vty, levels, isis);
	}

	return CMD_SUCCESS;
}

void isis_print_routes(struct vty *vty, struct isis_spftree *spftree)
{
	struct ttable *tt;
	struct route_node *rn;
	const char *tree_id_text = NULL;

	if (!spftree)
		return;

	switch (spftree->tree_id) {
	case SPFTREE_IPV4:
		tree_id_text = "IPv4";
		break;
	case SPFTREE_IPV6:
		tree_id_text = "IPv6";
		break;
	case SPFTREE_DSTSRC:
		tree_id_text = "IPv6 (dst-src routing)";
		break;
	case SPFTREE_COUNT:
		assert(!"isis_print_routes shouldn't be called with SPFTREE_COUNT as type");
		return;
	}

	vty_out(vty, "IS-IS %s %s routing table:\n\n",
		circuit_t2string(spftree->level), tree_id_text);

	/* Prepare table. */
	tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]);
	ttable_add_row(tt, "Prefix|Metric|Interface|Nexthop|Label(s)");
	tt->style.cell.rpad = 2;
	tt->style.corner = '+';
	ttable_restyle(tt);
	ttable_rowseps(tt, 0, BOTTOM, true, '-');

	for (rn = route_top(spftree->route_table); rn; rn = route_next(rn)) {
		struct isis_route_info *rinfo;
		struct isis_nexthop *nexthop;
		struct listnode *node;
		bool first = true;
		char buf_prefix[BUFSIZ];

		rinfo = rn->info;
		if (!rinfo)
			continue;

		(void)prefix2str(&rn->p, buf_prefix, sizeof(buf_prefix));
		for (ALL_LIST_ELEMENTS_RO(rinfo->nexthops, node, nexthop)) {
			struct interface *ifp;
			char buf_iface[BUFSIZ];
			char buf_nhop[BUFSIZ];
			char buf_labels[BUFSIZ] = {};

			if (!CHECK_FLAG(spftree->flags,
					F_SPFTREE_NO_ADJACENCIES)) {
				inet_ntop(nexthop->family, &nexthop->ip,
					  buf_nhop, sizeof(buf_nhop));
				ifp = if_lookup_by_index(nexthop->ifindex,
							 VRF_DEFAULT);
				if (ifp)
					strlcpy(buf_iface, ifp->name,
						sizeof(buf_iface));
				else
					snprintf(buf_iface, sizeof(buf_iface),
						 "ifindex %u",
						 nexthop->ifindex);
			} else {
				strlcpy(buf_nhop,
					print_sys_hostname(nexthop->sysid),
					sizeof(buf_nhop));
				strlcpy(buf_iface, "-", sizeof(buf_iface));
			}

			if (nexthop->sr.label != MPLS_INVALID_LABEL)
				label2str(nexthop->sr.label, buf_labels,
					  sizeof(buf_labels));
			else
				strlcpy(buf_labels, "-", sizeof(buf_labels));

			if (first) {
				ttable_add_row(tt, "%s|%u|%s|%s|%s", buf_prefix,
					       rinfo->cost, buf_iface, buf_nhop,
					       buf_labels);
				first = false;
			} else
				ttable_add_row(tt, "||%s|%s|%s", buf_iface,
					       buf_nhop, buf_labels);
		}
	}

	/* Dump the generated table. */
	if (tt->nrows > 1) {
		char *table;

		table = ttable_dump(tt, "\n");
		vty_out(vty, "%s\n", table);
		XFREE(MTYPE_TMP, table);
	}
	ttable_del(tt);
}

static void show_isis_route_common(struct vty *vty, int levels,
				   struct isis *isis)
{
	struct listnode *node;
	struct isis_area *area;

	if (!isis->area_list || isis->area_list->count == 0)
		return;

	for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
		vty_out(vty, "Area %s:\n",
			area->area_tag ? area->area_tag : "null");

		for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++) {
			if ((level & levels) == 0)
				continue;

			if (area->ip_circuits > 0) {
				isis_print_routes(
					vty,
					area->spftree[SPFTREE_IPV4][level - 1]);
			}
			if (area->ipv6_circuits > 0) {
				isis_print_routes(
					vty,
					area->spftree[SPFTREE_IPV6][level - 1]);
			}
			if (isis_area_ipv6_dstsrc_enabled(area)) {
				isis_print_routes(vty,
						  area->spftree[SPFTREE_DSTSRC]
							       [level - 1]);
			}
		}
	}
}

DEFUN(show_isis_route, show_isis_route_cmd,
      "show " PROTO_NAME
      " [vrf <NAME|all>] route"
#ifndef FABRICD
      " [<level-1|level-2>]"
#endif
      ,
      SHOW_STR PROTO_HELP VRF_FULL_CMD_HELP_STR
      "IS-IS routing table\n"
#ifndef FABRICD
      "level-1 routes\n"
      "level-2 routes\n"
#endif
)
{
	int levels;
	struct isis *isis;
	struct listnode *node;
	const char *vrf_name = VRF_DEFAULT_NAME;
	bool all_vrf = false;
	int idx = 0;

	if (argv_find(argv, argc, "level-1", &idx))
		levels = ISIS_LEVEL1;
	else if (argv_find(argv, argc, "level-2", &idx))
		levels = ISIS_LEVEL2;
	else
		levels = ISIS_LEVEL1 | ISIS_LEVEL2;

	if (!im) {
		vty_out(vty, "IS-IS Routing Process not enabled\n");
		return CMD_SUCCESS;
	}
	ISIS_FIND_VRF_ARGS(argv, argc, idx, vrf_name, all_vrf);

	if (vrf_name) {
		if (all_vrf) {
			for (ALL_LIST_ELEMENTS_RO(im->isis, node, isis))
				show_isis_route_common(vty, levels, isis);
			return CMD_SUCCESS;
		}
		isis = isis_lookup_by_vrfname(vrf_name);
		if (isis != NULL)
			show_isis_route_common(vty, levels, isis);
	}

	return CMD_SUCCESS;
}

void isis_spf_init(void)
{
	install_element(VIEW_NODE, &show_isis_topology_cmd);
	install_element(VIEW_NODE, &show_isis_route_cmd);

	/* Register hook(s). */
	hook_register(isis_adj_state_change_hook, spf_adj_state_change);
}

void isis_spf_print(struct isis_spftree *spftree, struct vty *vty)
{
	vty_out(vty, "      last run elapsed  : ");
	vty_out_timestr(vty, spftree->last_run_timestamp);
	vty_out(vty, "\n");

	vty_out(vty, "      last run duration : %u usec\n",
		(uint32_t)spftree->last_run_duration);

	vty_out(vty, "      run count         : %u\n", spftree->runcount);
}
