/*
 * IS-IS Rout(e)ing protocol - isis_te.c
 *
 * This is an implementation of RFC5305 & RFC 7810
 *
 * Author: Olivier Dugeon <olivier.dugeon@orange.com>
 *
 * Copyright (C) 2014 - 2019 Orange Labs http://www.orange.com
 *
 * This file is part of GNU Zebra.
 *
 * GNU Zebra is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2, or (at your option) any
 * later version.
 *
 * GNU Zebra 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 <math.h>

#include "linklist.h"
#include "thread.h"
#include "vty.h"
#include "stream.h"
#include "memory.h"
#include "log.h"
#include "prefix.h"
#include "command.h"
#include "hash.h"
#include "if.h"
#include "vrf.h"
#include "checksum.h"
#include "md5.h"
#include "sockunion.h"
#include "network.h"
#include "sbuf.h"
#include "link_state.h"
#include "lib/json.h"

#include "isisd/isis_constants.h"
#include "isisd/isis_common.h"
#include "isisd/isis_flags.h"
#include "isisd/isis_circuit.h"
#include "isisd/isis_adjacency.h"
#include "isisd/isisd.h"
#include "isisd/isis_lsp.h"
#include "isisd/isis_pdu.h"
#include "isisd/isis_dynhn.h"
#include "isisd/isis_misc.h"
#include "isisd/isis_csm.h"
#include "isisd/isis_adjacency.h"
#include "isisd/isis_spf.h"
#include "isisd/isis_tlvs.h"
#include "isisd/isis_mt.h"
#include "isisd/isis_te.h"
#include "isisd/isis_zebra.h"

DEFINE_MTYPE_STATIC(ISISD, ISIS_MPLS_TE,    "ISIS MPLS_TE parameters");

/*------------------------------------------------------------------------*
 * Following are control functions for MPLS-TE parameters management.
 *------------------------------------------------------------------------*/

/**
 * Create MPLS Traffic Engineering structure which belongs to given area.
 *
 * @param area	IS-IS Area
 */
void isis_mpls_te_create(struct isis_area *area)
{
	struct listnode *node;
	struct isis_circuit *circuit;

	if (!area)
		return;

	if (area->mta == NULL) {

		struct mpls_te_area *new;

		zlog_debug("ISIS-TE(%s): Initialize MPLS Traffic Engineering",
			   area->area_tag);

		new = XCALLOC(MTYPE_ISIS_MPLS_TE, sizeof(struct mpls_te_area));

		/* Initialize MPLS_TE structure */
		new->status = enable;
		new->level = 0;
		new->inter_as = off;
		new->interas_areaid.s_addr = 0;
		new->router_id.s_addr = 0;
		new->ted = ls_ted_new(1, "ISIS", 0);
		if (!new->ted)
			zlog_warn("Unable to create Link State Data Base");

		area->mta = new;
	} else {
		area->mta->status = enable;
	}

	/* Initialize Link State Database */
	if (area->mta->ted)
		isis_te_init_ted(area);

	/* Update Extended TLVs according to Interface link parameters */
	for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit))
		isis_link_params_update(circuit, circuit->interface);
}

/**
 * Disable MPLS Traffic Engineering structure which belongs to given area.
 *
 * @param area	IS-IS Area
 */
void isis_mpls_te_disable(struct isis_area *area)
{
	struct listnode *node;
	struct isis_circuit *circuit;

	if (!area->mta)
		return;

	area->mta->status = disable;

	/* Remove Link State Database */
	ls_ted_del_all(&area->mta->ted);

	/* Disable Extended SubTLVs on all circuit */
	for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
		if (!IS_EXT_TE(circuit->ext))
			continue;

		/* disable MPLS_TE Circuit keeping SR one's */
		if (IS_SUBTLV(circuit->ext, EXT_ADJ_SID))
			circuit->ext->status = EXT_ADJ_SID;
		else if (IS_SUBTLV(circuit->ext, EXT_LAN_ADJ_SID))
			circuit->ext->status = EXT_LAN_ADJ_SID;
		else
			circuit->ext->status = 0;
	}
}

void isis_mpls_te_term(struct isis_area *area)
{
	struct listnode *node;
	struct isis_circuit *circuit;

	if (!area->mta)
		return;

	zlog_info("TE(%s): Terminate MPLS TE", __func__);
	/* Remove Link State Database */
	ls_ted_del_all(&area->mta->ted);

	/* Remove Extended SubTLVs */
	zlog_info(" |- Remove Extended SubTLVS for all circuit");
	for (ALL_LIST_ELEMENTS_RO(area->circuit_list, node, circuit)) {
		zlog_info("   |- Call isis_del_ext_subtlvs()");
		isis_del_ext_subtlvs(circuit->ext);
		circuit->ext = NULL;
	}

	zlog_info(" |- Free MTA structure at %p", area->mta);
	XFREE(MTYPE_ISIS_MPLS_TE, area->mta);
}

/* Main initialization / update function of the MPLS TE Circuit context */
/* Call when interface TE Link parameters are modified */
void isis_link_params_update(struct isis_circuit *circuit,
			     struct interface *ifp)
{
	int i;
	struct prefix_ipv4 *addr;
	struct prefix_ipv6 *addr6;
	struct isis_ext_subtlvs *ext;

	/* Check if TE is enable or not */
	if (!circuit->area || !IS_MPLS_TE(circuit->area->mta))
		return;

	/* Sanity Check */
	if ((ifp == NULL) || (circuit->state != C_STATE_UP))
		return;

	te_debug("ISIS-TE(%s): Update circuit parameters for interface %s",
		 circuit->area->area_tag, ifp->name);

	/* Check if MPLS TE Circuit context has not been already created */
	if (circuit->ext == NULL) {
		circuit->ext = isis_alloc_ext_subtlvs();
		te_debug("  |- Allocated new Ext-subTLVs for interface %s",
			 ifp->name);
	}

	ext = circuit->ext;

	/* Fulfill Extended subTLVs from interface link parameters */
	if (HAS_LINK_PARAMS(ifp)) {
		/* STD_TE metrics */
		if (IS_PARAM_SET(ifp->link_params, LP_ADM_GRP)) {
			ext->adm_group = ifp->link_params->admin_grp;
			SET_SUBTLV(ext, EXT_ADM_GRP);
		} else
			UNSET_SUBTLV(ext, EXT_ADM_GRP);

		/* If known, register local IPv4 addr from ip_addr list */
		if (listcount(circuit->ip_addrs) != 0) {
			addr = (struct prefix_ipv4 *)listgetdata(
				(struct listnode *)listhead(circuit->ip_addrs));
			IPV4_ADDR_COPY(&ext->local_addr, &addr->prefix);
			SET_SUBTLV(ext, EXT_LOCAL_ADDR);
		} else
			UNSET_SUBTLV(ext, EXT_LOCAL_ADDR);

		/* If known, register local IPv6 addr from ip_addr list */
		if (listcount(circuit->ipv6_non_link) != 0) {
			addr6 = (struct prefix_ipv6 *)listgetdata(
				(struct listnode *)listhead(
					circuit->ipv6_non_link));
			IPV6_ADDR_COPY(&ext->local_addr6, &addr6->prefix);
			SET_SUBTLV(ext, EXT_LOCAL_ADDR6);
		} else
			UNSET_SUBTLV(ext, EXT_LOCAL_ADDR6);

		/*
		 * Remote IPv4 and IPv6 addresses are now added in
		 * isis_mpls_te_adj_ip_enabled() to get the right IP address
		 * in particular for IPv6 to get the global IPv6 address and
		 * not the link-local IPv6 address.
		 */

		if (IS_PARAM_SET(ifp->link_params, LP_MAX_BW)) {
			ext->max_bw = ifp->link_params->max_bw;
			SET_SUBTLV(ext, EXT_MAX_BW);
		} else
			UNSET_SUBTLV(ext, EXT_MAX_BW);

		if (IS_PARAM_SET(ifp->link_params, LP_MAX_RSV_BW)) {
			ext->max_rsv_bw = ifp->link_params->max_rsv_bw;
			SET_SUBTLV(ext, EXT_MAX_RSV_BW);
		} else
			UNSET_SUBTLV(ext, EXT_MAX_RSV_BW);

		if (IS_PARAM_SET(ifp->link_params, LP_UNRSV_BW)) {
			for (i = 0; i < MAX_CLASS_TYPE; i++)
				ext->unrsv_bw[i] =
					ifp->link_params->unrsv_bw[i];
			SET_SUBTLV(ext, EXT_UNRSV_BW);
		} else
			UNSET_SUBTLV(ext, EXT_UNRSV_BW);

		if (IS_PARAM_SET(ifp->link_params, LP_TE_METRIC)) {
			ext->te_metric = ifp->link_params->te_metric;
			SET_SUBTLV(ext, EXT_TE_METRIC);
		} else
			UNSET_SUBTLV(ext, EXT_TE_METRIC);

		/* TE metric extensions */
		if (IS_PARAM_SET(ifp->link_params, LP_DELAY)) {
			ext->delay = ifp->link_params->av_delay;
			SET_SUBTLV(ext, EXT_DELAY);
		} else
			UNSET_SUBTLV(ext, EXT_DELAY);

		if (IS_PARAM_SET(ifp->link_params, LP_MM_DELAY)) {
			ext->min_delay = ifp->link_params->min_delay;
			ext->max_delay = ifp->link_params->max_delay;
			SET_SUBTLV(ext, EXT_MM_DELAY);
		} else
			UNSET_SUBTLV(ext, EXT_MM_DELAY);

		if (IS_PARAM_SET(ifp->link_params, LP_DELAY_VAR)) {
			ext->delay_var = ifp->link_params->delay_var;
			SET_SUBTLV(ext, EXT_DELAY_VAR);
		} else
			UNSET_SUBTLV(ext, EXT_DELAY_VAR);

		if (IS_PARAM_SET(ifp->link_params, LP_PKT_LOSS)) {
			ext->pkt_loss = ifp->link_params->pkt_loss;
			SET_SUBTLV(ext, EXT_PKT_LOSS);
		} else
			UNSET_SUBTLV(ext, EXT_PKT_LOSS);

		if (IS_PARAM_SET(ifp->link_params, LP_RES_BW)) {
			ext->res_bw = ifp->link_params->res_bw;
			SET_SUBTLV(ext, EXT_RES_BW);
		} else
			UNSET_SUBTLV(ext, EXT_RES_BW);

		if (IS_PARAM_SET(ifp->link_params, LP_AVA_BW)) {
			ext->ava_bw = ifp->link_params->ava_bw;
			SET_SUBTLV(ext, EXT_AVA_BW);
		} else
			UNSET_SUBTLV(ext, EXT_AVA_BW);

		if (IS_PARAM_SET(ifp->link_params, LP_USE_BW)) {
			ext->use_bw = ifp->link_params->use_bw;
			SET_SUBTLV(ext, EXT_USE_BW);
		} else
			UNSET_SUBTLV(ext, EXT_USE_BW);

		/* INTER_AS */
		if (IS_PARAM_SET(ifp->link_params, LP_RMT_AS)) {
			ext->remote_as = ifp->link_params->rmt_as;
			ext->remote_ip = ifp->link_params->rmt_ip;
			SET_SUBTLV(ext, EXT_RMT_AS);
			SET_SUBTLV(ext, EXT_RMT_IP);
		} else {
			/* reset inter-as TE params */
			UNSET_SUBTLV(ext, EXT_RMT_AS);
			UNSET_SUBTLV(ext, EXT_RMT_IP);
		}
		te_debug("  |- New MPLS-TE link parameters status 0x%x",
			 ext->status);
	} else {
		te_debug("  |- Reset Extended subTLVs status 0x%x",
			 ext->status);
		/* Reset TE subTLVs keeping SR one's */
		if (IS_SUBTLV(ext, EXT_ADJ_SID))
			ext->status = EXT_ADJ_SID;
		else if (IS_SUBTLV(ext, EXT_LAN_ADJ_SID))
			ext->status = EXT_LAN_ADJ_SID;
		else
			ext->status = 0;
	}

	return;
}

static int isis_mpls_te_adj_ip_enabled(struct isis_adjacency *adj, int family,
				       bool global)
{
	struct isis_circuit *circuit;
	struct isis_ext_subtlvs *ext;

	/* Sanity Check */
	if (!adj || !adj->circuit)
		return 0;

	circuit = adj->circuit;

	/* Check that MPLS TE is enabled */
	if (!IS_MPLS_TE(circuit->area->mta) || !circuit->ext)
		return 0;

	ext = circuit->ext;

	/* Determine nexthop IP address */
	switch (family) {
	case AF_INET:
		if (!circuit->ip_router || !adj->ipv4_address_count)
			UNSET_SUBTLV(ext, EXT_NEIGH_ADDR);
		else {
			IPV4_ADDR_COPY(&ext->neigh_addr,
				       &adj->ipv4_addresses[0]);
			SET_SUBTLV(ext, EXT_NEIGH_ADDR);
		}
		break;
	case AF_INET6:
		if (!global)
			return 0;

		if (!circuit->ipv6_router || !adj->global_ipv6_count)
			UNSET_SUBTLV(ext, EXT_NEIGH_ADDR6);
		else {
			IPV6_ADDR_COPY(&ext->neigh_addr6,
				       &adj->global_ipv6_addrs[0]);
			SET_SUBTLV(ext, EXT_NEIGH_ADDR6);
		}
		break;
	default:
		return 0;
	}

	/* Update LSP */
	lsp_regenerate_schedule(circuit->area, circuit->is_type, 0);

	return 0;
}

static int isis_mpls_te_adj_ip_disabled(struct isis_adjacency *adj, int family,
					bool global)
{
	struct isis_circuit *circuit;
	struct isis_ext_subtlvs *ext;

	/* Sanity Check */
	if (!adj || !adj->circuit || !adj->circuit->ext)
		return 0;

	circuit = adj->circuit;

	/* Check that MPLS TE is enabled */
	if (!IS_MPLS_TE(circuit->area->mta) || !circuit->ext)
		return 0;

	ext = circuit->ext;

	/* Update MPLS TE IP address parameters if possible */
	if (!IS_MPLS_TE(circuit->area->mta) || !IS_EXT_TE(ext))
		return 0;

	/* Determine nexthop IP address */
	switch (family) {
	case AF_INET:
		UNSET_SUBTLV(ext, EXT_NEIGH_ADDR);
		break;
	case AF_INET6:
		if (global)
			UNSET_SUBTLV(ext, EXT_NEIGH_ADDR6);
		break;
	default:
		return 0;
	}

	/* Update LSP */
	lsp_regenerate_schedule(circuit->area, circuit->is_type, 0);

	return 0;
}

int isis_mpls_te_update(struct interface *ifp)
{
	struct isis_circuit *circuit;
	uint8_t rc = 1;

	/* Sanity Check */
	if (ifp == NULL)
		return rc;

	/* Get circuit context from interface */
	circuit = circuit_scan_by_ifp(ifp);
	if (circuit == NULL)
		return rc;

	/* Update TE TLVs ... */
	isis_link_params_update(circuit, ifp);

	/* ... and LSP */
	if (circuit->area && IS_MPLS_TE(circuit->area->mta))
		lsp_regenerate_schedule(circuit->area, circuit->is_type, 0);

	rc = 0;
	return rc;
}


/**
 * Export Link State information to consumer daemon through ZAPI Link State
 * Opaque Message.
 *
 * @param type		Type of Link State Element i.e. Vertex, Edge or Subnet
 * @param link_state	Pointer to Link State Vertex, Edge or Subnet
 *
 * @return		0 if success, -1 otherwise
 */
static int isis_te_export(uint8_t type, void *link_state)
{
	struct ls_message msg = {};
	int rc = 0;

	switch (type) {
	case LS_MSG_TYPE_NODE:
		ls_vertex2msg(&msg, (struct ls_vertex *)link_state);
		rc = ls_send_msg(zclient, &msg, NULL);
		break;
	case LS_MSG_TYPE_ATTRIBUTES:
		ls_edge2msg(&msg, (struct ls_edge *)link_state);
		rc = ls_send_msg(zclient, &msg, NULL);
		break;
	case LS_MSG_TYPE_PREFIX:
		ls_subnet2msg(&msg, (struct ls_subnet *)link_state);
		rc = ls_send_msg(zclient, &msg, NULL);
		break;
	default:
		rc = -1;
		break;
	}

	return rc;
}

/**
 * Parse LSP and build corresponding vertex. If vertex doesn't exist in the
 * Link State Database it is created otherwise updated.
 *
 * @param ted	Traffic Engineering Link State Database
 * @param lsp	IS-IS Link State PDU
 *
 * @return	Link State Vertex or NULL in case of error
 */
static struct ls_vertex *lsp_to_vertex(struct ls_ted *ted, struct isis_lsp *lsp)
{
	struct ls_vertex *vertex = NULL;
	struct ls_node *old, lnode = {};
	struct isis_tlvs *tlvs;
	const struct in_addr inaddr_any = {.s_addr = INADDR_ANY};

	/* Sanity check */
	if (!ted || !lsp)
		return NULL;

	/* Compute Link State Node ID from IS-IS sysID ... */
	if (lsp->level == ISIS_LEVEL1)
		lnode.adv.origin = ISIS_L1;
	else
		lnode.adv.origin = ISIS_L2;
	memcpy(&lnode.adv.id.iso.sys_id, &lsp->hdr.lsp_id, ISIS_SYS_ID_LEN);
	lnode.adv.id.iso.level = lsp->level;
	/* ... and search the corresponding vertex */
	vertex = ls_find_vertex_by_id(ted, lnode.adv);
	/* Create a new one if not found */
	if (!vertex) {
		old = ls_node_new(lnode.adv, inaddr_any, in6addr_any);
		old->type = STANDARD;
		vertex = ls_vertex_add(ted, old);
	}
	old = vertex->node;
	te_debug("  |- %s Vertex (%" PRIu64 ") for node %s",
		 vertex->status == NEW ? "Create" : "Found", vertex->key,
		 print_sys_hostname(old->adv.id.iso.sys_id));

	/* Fulfill Link State Node information */
	tlvs = lsp->tlvs;
	if (tlvs) {
		if (tlvs->te_router_id) {
			IPV4_ADDR_COPY(&lnode.router_id, tlvs->te_router_id);
			SET_FLAG(lnode.flags, LS_NODE_ROUTER_ID);
		}
		if (tlvs->te_router_id_ipv6) {
			IPV6_ADDR_COPY(&lnode.router_id6,
				       tlvs->te_router_id_ipv6);
			SET_FLAG(lnode.flags, LS_NODE_ROUTER_ID6);
		}
		if (tlvs->hostname) {
			strlcpy(lnode.name, tlvs->hostname, MAX_NAME_LENGTH);
			SET_FLAG(lnode.flags, LS_NODE_NAME);
		}
		if (tlvs->router_cap) {
			struct isis_router_cap *cap = tlvs->router_cap;

			if (cap->srgb.lower_bound != 0
			    && cap->srgb.range_size != 0) {
				SET_FLAG(lnode.flags, LS_NODE_SR);
				lnode.srgb.flag = cap->srgb.flags;
				lnode.srgb.lower_bound = cap->srgb.lower_bound;
				lnode.srgb.range_size = cap->srgb.range_size;
				for (int i = 0; i < SR_ALGORITHM_COUNT; i++)
					lnode.algo[i] = cap->algo[i];
			}

			if (cap->srlb.lower_bound != 0
			    && cap->srlb.range_size != 0) {
				lnode.srlb.lower_bound = cap->srlb.lower_bound;
				lnode.srlb.range_size = cap->srlb.range_size;
				SET_FLAG(lnode.flags, LS_NODE_SRLB);
			}
			if (cap->msd != 0) {
				lnode.msd = cap->msd;
				SET_FLAG(lnode.flags, LS_NODE_MSD);
			}
		}
	}

	/* Update Link State Node information */
	if (!ls_node_same(old, &lnode)) {
		te_debug("    |- Update Link State Node information");
		memcpy(old, &lnode, sizeof(struct ls_node));
		if (vertex->status != NEW)
			vertex->status = UPDATE;
	}

	/* Set self TED vertex if LSP corresponds to the own router */
	if (lsp->own_lsp)
		ted->self = vertex;

	return vertex;
}

/**
 * Get Link State Edge from Link State Attributes in TE Database.
 * Edge structure is dynamically allocated and fulfill with Link State
 * Attributes if not found.
 *
 * @param ted	Link State Database
 * @param attr	Link State Attributes
 *
 * @return	New Link State Edge if success, NULL otherwise
 */
static struct ls_edge *get_edge(struct ls_ted *ted, struct ls_attributes *attr)
{
	struct ls_edge *edge;
	struct ls_standard *std;
	uint64_t key = 0;

	/* Check parameters */
	if (!ted || !attr)
		return NULL;

	std = &attr->standard;

	/* Compute keys in function of local address (IPv4/v6) or identifier */
	if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR))
		key = ((uint64_t)ntohl(std->local.s_addr)) & 0xffffffff;
	else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ADDR6))
		key = ((uint64_t)ntohl(std->local6.s6_addr32[2]) << 32
		       | (uint64_t)ntohl(std->local6.s6_addr32[3]));
	else if (CHECK_FLAG(attr->flags, LS_ATTR_LOCAL_ID))
		key = ((uint64_t)std->remote_id << 32)
		       | (((uint64_t)std->local_id) & 0xffffffff);
	else
		key = 0;

	/* Stop here if we don't got a valid key */
	if (key == 0)
		return NULL;

	/* Get corresponding Edge by key from Link State Data Base */
	edge = ls_find_edge_by_key(ted, key);

	/* and create new one if not exist */
	if (!edge) {
		edge = ls_edge_add(ted, attr);
		/*
		 * Edge could be Null if no local ID is found in Attributes.
		 * Stop the processing as without any local ID it is not
		 * possible to store Edge in the TED.
		 */
		if (!edge)
			return NULL;
	}

	if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR))
		te_debug("    |- %s Edge (%" PRIu64
			 ") from Extended Reach. %pI4",
			 edge->status == NEW ? "Create" : "Found", edge->key,
			 &attr->standard.local);
	else if (CHECK_FLAG(edge->attributes->flags, LS_ATTR_LOCAL_ADDR6))
		te_debug("    |- %s Edge (%" PRIu64
			 ") from Extended Reach. %pI6",
			 edge->status == NEW ? "Create" : "Found", edge->key,
			 &attr->standard.local6);
	else
		te_debug("    |- %s Edge (%" PRIu64 ")",
			 edge->status == NEW ? "Create" : "Found", edge->key);

	return edge;
}

/**
 * Get Link State Attributes from IS-IS Sub-TLVs. Structure is dynamically
 * allocated and should be free once not use anymore.
 *
 * @param adv	Link State Node ID
 * @param tlvs	IS-IS Sub TLVs
 *
 * @return	New Link State attributes if success, NULL otherwise
 */
static struct ls_attributes *get_attributes(struct ls_node_id adv,
					    struct isis_ext_subtlvs *tlvs)
{
	struct ls_attributes *attr;
	struct in_addr local = {.s_addr = INADDR_ANY};
	struct in6_addr local6 = in6addr_any;
	uint32_t local_id = 0;

	/* Got Local identifier */
	if (CHECK_FLAG(tlvs->status, EXT_LOCAL_ADDR))
		local.s_addr = tlvs->local_addr.s_addr;

	if (CHECK_FLAG(tlvs->status, EXT_LOCAL_ADDR6))
		memcpy(&local6, &tlvs->local_addr6, IPV6_MAX_BYTELEN);

	if (CHECK_FLAG(tlvs->status, EXT_LLRI))
		local_id = tlvs->local_llri;

	/* Create LS Attributes */
	attr = ls_attributes_new(adv, local, local6, local_id);
	if (!attr)
		return NULL;

	/* Browse sub-TLV and fulfill Link State Attributes */
	if (CHECK_FLAG(tlvs->status, EXT_ADM_GRP)) {
		attr->standard.admin_group = tlvs->adm_group;
		SET_FLAG(attr->flags, LS_ATTR_ADM_GRP);
	}
	if (CHECK_FLAG(tlvs->status, EXT_LLRI)) {
		attr->standard.local_id = tlvs->local_llri;
		attr->standard.remote_id = tlvs->remote_llri;
		SET_FLAG(attr->flags, LS_ATTR_LOCAL_ID);
		SET_FLAG(attr->flags, LS_ATTR_NEIGH_ID);
	}
	if (CHECK_FLAG(tlvs->status, EXT_NEIGH_ADDR)) {
		attr->standard.remote.s_addr = tlvs->neigh_addr.s_addr;
		SET_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR);
	}
	if (CHECK_FLAG(tlvs->status, EXT_NEIGH_ADDR6)) {
		memcpy(&attr->standard.remote6, &tlvs->neigh_addr6,
		       IPV6_MAX_BYTELEN);
		SET_FLAG(attr->flags, LS_ATTR_NEIGH_ADDR6);
	}
	if (CHECK_FLAG(tlvs->status, EXT_MAX_BW)) {
		attr->standard.max_bw = tlvs->max_bw;
		SET_FLAG(attr->flags, LS_ATTR_MAX_BW);
	}
	if (CHECK_FLAG(tlvs->status, EXT_MAX_RSV_BW)) {
		attr->standard.max_rsv_bw = tlvs->max_rsv_bw;
		SET_FLAG(attr->flags, LS_ATTR_MAX_RSV_BW);
	}
	if (CHECK_FLAG(tlvs->status, EXT_UNRSV_BW)) {
		memcpy(&attr->standard.unrsv_bw, tlvs->unrsv_bw,
		       ISIS_SUBTLV_UNRSV_BW_SIZE);
		SET_FLAG(attr->flags, LS_ATTR_UNRSV_BW);
	}
	if (CHECK_FLAG(tlvs->status, EXT_TE_METRIC)) {
		attr->standard.te_metric = tlvs->te_metric;
		SET_FLAG(attr->flags, LS_ATTR_TE_METRIC);
	}
	if (CHECK_FLAG(tlvs->status, EXT_RMT_AS)) {
		attr->standard.remote_as = tlvs->remote_as;
		SET_FLAG(attr->flags, LS_ATTR_REMOTE_AS);
	}
	if (CHECK_FLAG(tlvs->status, EXT_RMT_IP)) {
		attr->standard.remote_addr = tlvs->remote_ip;
		SET_FLAG(attr->flags, LS_ATTR_REMOTE_ADDR);
	}
	if (CHECK_FLAG(tlvs->status, EXT_DELAY)) {
		attr->extended.delay = tlvs->delay;
		SET_FLAG(attr->flags, LS_ATTR_DELAY);
	}
	if (CHECK_FLAG(tlvs->status, EXT_MM_DELAY)) {
		attr->extended.min_delay = tlvs->min_delay;
		attr->extended.max_delay = tlvs->max_delay;
		SET_FLAG(attr->flags, LS_ATTR_MIN_MAX_DELAY);
	}
	if (CHECK_FLAG(tlvs->status, EXT_DELAY_VAR)) {
		attr->extended.jitter = tlvs->delay_var;
		SET_FLAG(attr->flags, LS_ATTR_JITTER);
	}
	if (CHECK_FLAG(tlvs->status, EXT_PKT_LOSS)) {
		attr->extended.pkt_loss = tlvs->pkt_loss;
		SET_FLAG(attr->flags, LS_ATTR_PACKET_LOSS);
	}
	if (CHECK_FLAG(tlvs->status, EXT_AVA_BW)) {
		attr->extended.ava_bw = tlvs->ava_bw;
		SET_FLAG(attr->flags, LS_ATTR_AVA_BW);
	}
	if (CHECK_FLAG(tlvs->status, EXT_RES_BW)) {
		attr->extended.rsv_bw = tlvs->res_bw;
		SET_FLAG(attr->flags, LS_ATTR_RSV_BW);
	}
	if (CHECK_FLAG(tlvs->status, EXT_USE_BW)) {
		attr->extended.used_bw = tlvs->use_bw;
		SET_FLAG(attr->flags, LS_ATTR_USE_BW);
	}
	if (CHECK_FLAG(tlvs->status, EXT_ADJ_SID)) {
		struct isis_adj_sid *adj =
			(struct isis_adj_sid *)tlvs->adj_sid.head;
		int i;
		for (; adj; adj = adj->next) {
			i = adj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG ? 1 : 0;
			i += adj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG ? 2 : 0;
			attr->adj_sid[i].flags = adj->flags;
			attr->adj_sid[i].weight = adj->weight;
			attr->adj_sid[i].sid = adj->sid;
			switch (i) {
			case ADJ_PRI_IPV4:
				SET_FLAG(attr->flags, LS_ATTR_ADJ_SID);
				break;
			case ADJ_BCK_IPV4:
				SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID);
				break;
			case ADJ_PRI_IPV6:
				SET_FLAG(attr->flags, LS_ATTR_ADJ_SID6);
				break;
			case ADJ_BCK_IPV6:
				SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6);
				break;
			}
		}
	}
	if (CHECK_FLAG(tlvs->status, EXT_LAN_ADJ_SID)) {
		struct isis_lan_adj_sid *ladj =
			(struct isis_lan_adj_sid *)tlvs->lan_sid.head;
		int i;
		for (; ladj; ladj = ladj->next) {
			i = ladj->flags & EXT_SUBTLV_LINK_ADJ_SID_BFLG ? 1 : 0;
			i += ladj->flags & EXT_SUBTLV_LINK_ADJ_SID_FFLG ? 2 : 0;
			attr->adj_sid[i].flags = ladj->flags;
			attr->adj_sid[i].weight = ladj->weight;
			attr->adj_sid[i].sid = ladj->sid;
			memcpy(&attr->adj_sid[i].neighbor.sysid,
			       &ladj->neighbor_id, ISIS_SYS_ID_LEN);
			switch (i) {
			case ADJ_PRI_IPV4:
				SET_FLAG(attr->flags, LS_ATTR_ADJ_SID);
				break;
			case ADJ_BCK_IPV4:
				SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID);
				break;
			case ADJ_PRI_IPV6:
				SET_FLAG(attr->flags, LS_ATTR_ADJ_SID6);
				break;
			case ADJ_BCK_IPV6:
				SET_FLAG(attr->flags, LS_ATTR_BCK_ADJ_SID6);
				break;
			}
		}
	}

	return attr;
}

/**
 * Parse Extended Reachability TLVs and create or update the corresponding
 * Link State Edge and Attributes. Vertex connections are also updated if
 * needed based on the remote IP address of the Edge and existing reverse Edge.
 *
 * @param id		ID of Extended IS
 * @param metric	Metric of the link
 * @param old_metric	Boolean that indicate if it is an old metric (no TE)
 * @param tlvs		SubTlvs that contains TE information
 * @param arg		IS-IS TE argument (TED, Vertex, and export indication)
 *
 * @return		0 if success, -1 otherwise
 */
static int lsp_to_edge_cb(const uint8_t *id, uint32_t metric, bool old_metric,
			  struct isis_ext_subtlvs *tlvs, void *arg)
{
	struct isis_te_args *args = (struct isis_te_args *)arg;
	struct ls_vertex *vertex;
	struct ls_edge *edge, *dst;
	struct ls_attributes *attr;

	te_debug("  |- Process Extended IS for %s", sysid_print(id));

	/* Check parameters */
	if (old_metric || !args || !tlvs)
		return LSP_ITER_CONTINUE;

	/* Initialize Link State Attributes */
	vertex = args->vertex;
	attr = get_attributes(vertex->node->adv, tlvs);
	/*
	 * Attributes may be Null if no local ID has been found in the LSP.
	 * Stop processing here as without any local ID it is not possible to
	 * create corresponding Edge in the TED.
	 */
	if (!attr)
		return LSP_ITER_CONTINUE;

	attr->metric = metric;
	SET_FLAG(attr->flags, LS_ATTR_METRIC);

	/* Get corresponding Edge from Link State Data Base */
	edge = get_edge(args->ted, attr);
	/*
	 * Edge could be Null if no local ID has been found in Attributes.
	 * Stop processing here as without any local ID it is not possible to
	 * create corresponding Edge in the TED.
	 */
	if (!edge) {
		ls_attributes_del(attr);
		return LSP_ITER_CONTINUE;
	}

	/* Update Attribute fields if there are different */
	if (edge->status != NEW) {
		if (!ls_attributes_same(edge->attributes, attr)) {
			te_debug("    |- Update Edge Attributes information");
			ls_attributes_del(edge->attributes);
			edge->attributes = attr;
			edge->status = UPDATE;
		} else {
			if (edge->attributes != attr)
				ls_attributes_del(attr);
			edge->status = SYNC;
		}
	}

	/* Try to update remote Link from remote address or reachability ID */
	te_debug("    |- Link Edge (%" PRIu64 ") to destination vertex (%s)",
		 edge->key, print_sys_hostname(id));
	dst = ls_find_edge_by_destination(args->ted, edge->attributes);
	if (dst) {
		/* Attach remote link if not set */
		if (edge->source && dst->destination == NULL) {
			vertex = edge->source;
			if (vertex->incoming_edges)
				listnode_add_sort_nodup(vertex->incoming_edges,
							dst);
			dst->destination = vertex;
		}
		/* and destination vertex to this edge if not set */
		if (dst->source && edge->destination == NULL) {
			vertex = dst->source;
			if (vertex->incoming_edges)
				listnode_add_sort_nodup(vertex->incoming_edges,
							edge);
			edge->destination = vertex;
		}
	} else {
		/* Search dst. Vertex by Extended Reach. ID if not found */
		if (edge->destination == NULL) {
			vertex = ls_find_vertex_by_key(args->ted,
						       sysid_to_key(id));
			if (vertex && vertex->incoming_edges)
				listnode_add_sort_nodup(vertex->incoming_edges,
							edge);
			edge->destination = vertex;
		}
	}

	/* Update status and Export Link State Edge if needed */
	if (edge->status != SYNC) {
		if (args->export)
			isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge);
		edge->status = SYNC;
	}

	return LSP_ITER_CONTINUE;
}

/**
 * Parse Extended IP Reachability or MT IPv6 Reachability TLVs and create or
 * update the corresponding Link State Subnet and Prefix.
 *
 * @param prefix	Prefix associated to this subnet
 * @param metric	Metric of this prefix
 * @param external	Boolean to indicate if the prefix is external
 * @param subtlvs	Subtlvs if any (mostly Segment Routing ID)
 * @param arg		IS-IS TE argument (TED, Vertex, and export indication)
 *
 * @return		0 if success, -1 otherwise
 */
static int lsp_to_subnet_cb(const struct prefix *prefix, uint32_t metric,
			    bool external, struct isis_subtlvs *subtlvs,
			    void *arg)
{
	struct isis_te_args *args = (struct isis_te_args *)arg;
	struct ls_vertex *vertex;
	struct ls_subnet *subnet;
	struct ls_prefix *ls_pref;
	struct listnode *node;
	struct ls_edge *edge;
	struct ls_standard *std = NULL;
	struct prefix p;

	/* Sanity Check */
	if (!args || !prefix)
		return LSP_ITER_CONTINUE;

	te_debug("  |- Process Extended %s Reachability %pFX",
		 prefix->family == AF_INET ? "IP" : "IPv6", prefix);

	vertex = args->vertex;

	/*
	 * Prefix with mask different from /32 or /128 are advertised by at
	 * least 2 nodes. To avoid subnet attached to undetermined vertex, and
	 * gives the possibility to send the information to client e.g. BGP for
	 * Link State advertisement, we adjust the prefix with the corresponding
	 * IP address of the belonging interface when it is available. Other
	 * prefixes are kept unchanged.
	 */
	if (prefix->family == AF_INET && prefix->prefixlen < IPV4_MAX_BITLEN) {
		std = NULL;
		for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) {
			if (!CHECK_FLAG(edge->attributes->flags,
					LS_ATTR_LOCAL_ADDR))
				continue;

			p.u.prefix4 = edge->attributes->standard.local;
			p.family = AF_INET;
			p.prefixlen = prefix->prefixlen;
			apply_mask_ipv4((struct prefix_ipv4 *)&p);
			if (IPV4_ADDR_SAME(&p.u.prefix4, &prefix->u.prefix4)) {
				std = &edge->attributes->standard;
				break;
			}
		}
		if (std)
			p.u.prefix4 = std->local;

	} else if (prefix->family == AF_INET6
		   && prefix->prefixlen < IPV6_MAX_BITLEN) {
		std = NULL;
		for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge)) {
			if (!CHECK_FLAG(edge->attributes->flags,
					LS_ATTR_LOCAL_ADDR6))
				continue;

			p.u.prefix6 = edge->attributes->standard.local6;
			p.family = AF_INET6;
			p.prefixlen = prefix->prefixlen;
			apply_mask_ipv6((struct prefix_ipv6 *)&p);
			if (IPV6_ADDR_SAME(&p.u.prefix6, &prefix->u.prefix6)) {
				std = &edge->attributes->standard;
				break;
			}
		}
		if (std)
			p.u.prefix6 = std->local6;
	}
	if (!std)
		prefix_copy(&p, prefix);
	else
		te_debug("   |- Adjust prefix %pFX with local address to: %pFX",
			 prefix, &p);

	/* Search existing Subnet in TED ... */
	subnet = ls_find_subnet(args->ted, p);
	/* ... and create a new Subnet if not found */
	if (!subnet) {
		ls_pref = ls_prefix_new(vertex->node->adv, p);
		subnet = ls_subnet_add(args->ted, ls_pref);
		if (!subnet)
			return LSP_ITER_CONTINUE;
	}
	ls_pref = subnet->ls_pref;

	te_debug("   |- %s Subnet from prefix %pFX",
		 subnet->status == NEW ? "Create" : "Found", &p);

	/* Update Metric */
	if (!CHECK_FLAG(ls_pref->flags, LS_PREF_METRIC)
	    || (ls_pref->metric != metric)) {
		ls_pref->metric = metric;
		SET_FLAG(ls_pref->flags, LS_PREF_METRIC);
		if (subnet->status != NEW)
			subnet->status = UPDATE;
	} else {
		if (subnet->status == ORPHAN)
			subnet->status = SYNC;
	}

	/* Update Prefix SID if any */
	if (subtlvs && subtlvs->prefix_sids.count != 0) {
		struct isis_prefix_sid *psid;
		struct ls_sid sr = {};

		psid = (struct isis_prefix_sid *)subtlvs->prefix_sids.head;
		sr.algo = psid->algorithm;
		sr.sid_flag = psid->flags;
		sr.sid = psid->value;

		if (!CHECK_FLAG(ls_pref->flags, LS_PREF_SR)
		    || !memcmp(&ls_pref->sr, &sr, sizeof(struct ls_sid))) {
			memcpy(&ls_pref->sr, &sr, sizeof(struct ls_sid));
			SET_FLAG(ls_pref->flags, LS_PREF_SR);
			if (subnet->status != NEW)
				subnet->status = UPDATE;
		} else {
			if (subnet->status == ORPHAN)
				subnet->status = SYNC;
		}
	} else {
		if (CHECK_FLAG(ls_pref->flags, LS_PREF_SR)) {
			UNSET_FLAG(ls_pref->flags, LS_PREF_SR);
			if (subnet->status != NEW)
				subnet->status = UPDATE;
		} else {
			if (subnet->status == ORPHAN)
				subnet->status = SYNC;
		}
	}

	/* Update status and Export Link State Edge if needed */
	if (subnet->status != SYNC) {
		if (args->export)
			isis_te_export(LS_MSG_TYPE_PREFIX, subnet);
		subnet->status = SYNC;
	}

	return LSP_ITER_CONTINUE;
}

/**
 * Parse ISIS LSP to fulfill the Link State Database
 *
 * @param ted	Link State Database
 * @param lsp	ISIS Link State PDU
 */
static void isis_te_parse_lsp(struct mpls_te_area *mta, struct isis_lsp *lsp)
{
	struct ls_ted *ted;
	struct ls_vertex *vertex;
	struct ls_edge *edge;
	struct ls_subnet *subnet;
	struct listnode *node;
	struct isis_te_args args;

	/* Sanity Check */
	if (!IS_MPLS_TE(mta) || !mta->ted || !lsp)
		return;

	ted = mta->ted;

	te_debug("ISIS-TE(%s): Parse LSP %s", lsp->area->area_tag,
		 sysid_print(lsp->hdr.lsp_id));

	/* First parse LSP to obtain the corresponding Vertex */
	vertex = lsp_to_vertex(ted, lsp);
	if (!vertex) {
		zlog_warn("Unable to build Vertex from LSP %s. Abort!",
			  sysid_print(lsp->hdr.lsp_id));
		return;
	}

	/* Check if Vertex has been modified */
	if (vertex->status != SYNC) {
		/* Vertex is out of sync: export it if requested */
		if (IS_EXPORT_TE(mta))
			isis_te_export(LS_MSG_TYPE_NODE, vertex);
		vertex->status = SYNC;
	}

	/* Mark outgoing Edges and Subnets as ORPHAN to detect deletion */
	for (ALL_LIST_ELEMENTS_RO(vertex->outgoing_edges, node, edge))
		edge->status = ORPHAN;

	for (ALL_LIST_ELEMENTS_RO(vertex->prefixes, node, subnet))
		subnet->status = ORPHAN;

	/* Process all Extended Reachability in LSP (all fragments) */
	args.ted = ted;
	args.vertex = vertex;
	args.export = mta->export;
	isis_lsp_iterate_is_reach(lsp, ISIS_MT_IPV4_UNICAST, lsp_to_edge_cb,
				  &args);

	isis_lsp_iterate_is_reach(lsp, ISIS_MT_IPV6_UNICAST, lsp_to_edge_cb,
				  &args);

	/* Process all Extended IP (v4 & v6) in LSP (all fragments) */
	isis_lsp_iterate_ip_reach(lsp, AF_INET, ISIS_MT_IPV4_UNICAST,
				  lsp_to_subnet_cb, &args);
	isis_lsp_iterate_ip_reach(lsp, AF_INET6, ISIS_MT_IPV6_UNICAST,
				  lsp_to_subnet_cb, &args);
	isis_lsp_iterate_ip_reach(lsp, AF_INET6, ISIS_MT_IPV4_UNICAST,
				  lsp_to_subnet_cb, &args);

	/* Clean remaining Orphan Edges or Subnets */
	if (IS_EXPORT_TE(mta))
		ls_vertex_clean(ted, vertex, zclient);
	else
		ls_vertex_clean(ted, vertex, NULL);
}

/**
 * Delete Link State Database Vertex, Edge & Prefix that correspond to this
 * ISIS Link State PDU
 *
 * @param ted	Link State Database
 * @param lsp	ISIS Link State PDU
 */
static void isis_te_delete_lsp(struct mpls_te_area *mta, struct isis_lsp *lsp)
{
	struct ls_ted *ted;
	struct ls_vertex *vertex = NULL;
	struct ls_node lnode = {};
	struct ls_edge *edge;
	struct ls_subnet *subnet;
	struct listnode *nnode, *node;

	/* Sanity Check */
	if (!IS_MPLS_TE(mta) || !mta->ted || !lsp)
		return;

	te_debug("ISIS-TE(%s): Delete Link State TED objects from LSP %s",
		 lsp->area->area_tag, sysid_print(lsp->hdr.lsp_id));

	/* Compute Link State Node ID from IS-IS sysID ... */
	if (lsp->level == ISIS_LEVEL1)
		lnode.adv.origin = ISIS_L1;
	else
		lnode.adv.origin = ISIS_L2;
	memcpy(&lnode.adv.id.iso.sys_id, &lsp->hdr.lsp_id, ISIS_SYS_ID_LEN);
	lnode.adv.id.iso.level = lsp->level;
	ted = mta->ted;
	/* ... and search the corresponding vertex */
	vertex = ls_find_vertex_by_id(ted, lnode.adv);
	if (!vertex)
		return;

	te_debug("  |- Delete Vertex %s", vertex->node->name);

	/*
	 * We can't use the ls_vertex_del_all() function if export TE is set,
	 * as we must first advertise the client daemons of each removal.
	 */
	/* Remove outgoing Edges */
	for (ALL_LIST_ELEMENTS(vertex->outgoing_edges, node, nnode, edge)) {
		if (IS_EXPORT_TE(mta)) {
			edge->status = DELETE;
			isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge);
		}
		ls_edge_del_all(ted, edge);
	}

	/* Disconnect incoming Edges */
	for (ALL_LIST_ELEMENTS(vertex->incoming_edges, node, nnode, edge)) {
		ls_disconnect(vertex, edge, false);
		if (edge->source == NULL) {
			if (IS_EXPORT_TE(mta)) {
				edge->status = DELETE;
				isis_te_export(LS_MSG_TYPE_ATTRIBUTES, edge);
			}
			ls_edge_del_all(ted, edge);
		}
	}

	/* Remove subnets */
	for (ALL_LIST_ELEMENTS(vertex->prefixes, node, nnode, subnet)) {
		if (IS_EXPORT_TE(mta)) {
			subnet->status = DELETE;
			isis_te_export(LS_MSG_TYPE_PREFIX, subnet);
		}
		ls_subnet_del_all(ted, subnet);
	}

	/* Then remove Link State Node */
	if (IS_EXPORT_TE(mta)) {
		vertex->status = DELETE;
		isis_te_export(LS_MSG_TYPE_NODE, vertex);
	}
	ls_node_del(vertex->node);

	/* Finally, remove Vertex */
	ls_vertex_del(ted, vertex);
}

/**
 * Process ISIS LSP according to the event to add, update or remove
 * corresponding vertex, edge and prefix in the Link State database.
 * Since LSP could be fragmented, the function starts by searching the root LSP
 * to retrieve the complete LSP, including eventual fragment before processing
 * all of them.
 *
 * @param lsp		ISIS Link State PDU
 * @param event		LSP event: ADD, UPD, INC & DEL (TICK are ignored)
 *
 */
void isis_te_lsp_event(struct isis_lsp *lsp, enum lsp_event event)
{
	struct isis_area *area;
	struct isis_lsp *lsp0;

	/* Sanity check */
	if (!lsp || !lsp->area)
		return;

	area = lsp->area;
	if (!IS_MPLS_TE(area->mta))
		return;

	/* Adjust LSP0 in case of fragment */
	if (LSP_FRAGMENT(lsp->hdr.lsp_id))
		lsp0 = lsp->lspu.zero_lsp;
	else
		lsp0 = lsp;

	/* Then process event */
	switch (event) {
	case LSP_ADD:
	case LSP_UPD:
	case LSP_INC:
		isis_te_parse_lsp(area->mta, lsp0);
		break;
	case LSP_DEL:
		isis_te_delete_lsp(area->mta, lsp0);
		break;
	default:
		break;
	}
}

/**
 * Send the whole Link State Traffic Engineering Database to the consumer that
 * request it through a ZAPI Link State Synchronous Opaque Message.
 *
 * @param info	ZAPI Opaque message
 *
 * @return	0 if success, -1 otherwise
 */
int isis_te_sync_ted(struct zapi_opaque_reg_info dst)
{
	struct listnode *node, *inode;
	struct isis *isis;
	struct isis_area *area;
	struct mpls_te_area *mta;
	int rc = -1;

	te_debug("ISIS-TE(%s): Received TED synchro from client %d", __func__,
		 dst.proto);
	/*  For each area, send TED if TE distribution is enabled */
	for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
		for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
			mta = area->mta;
			if (IS_MPLS_TE(mta) && IS_EXPORT_TE(mta)) {
				te_debug("  |- Export TED from area %s",
					 area->area_tag);
				rc = ls_sync_ted(mta->ted, zclient, &dst);
				if (rc != 0)
					return rc;
			}
		}
	}

	return rc;
}

/**
 * Initialize the Link State database from the LSP already stored for this area
 *
 * @param area	ISIS area
 */
void isis_te_init_ted(struct isis_area *area)
{
	struct isis_lsp *lsp;

	/* Iterate over all lsp. */
	for (int level = ISIS_LEVEL1; level <= ISIS_LEVELS; level++)
		frr_each (lspdb, &area->lspdb[level - 1], lsp)
			isis_te_parse_lsp(area->mta, lsp);
}

/* Following are vty command functions */
#ifndef FABRICD

static void show_router_id(struct vty *vty, struct isis_area *area)
{
	bool no_match = true;

	vty_out(vty, "Area %s:\n", area->area_tag);
	if (area->mta->router_id.s_addr != 0) {
		vty_out(vty, "  MPLS-TE IPv4 Router-Address: %pI4\n",
			&area->mta->router_id);
		no_match = false;
	}
	if (!IN6_IS_ADDR_UNSPECIFIED(&area->mta->router_id_ipv6)) {
		vty_out(vty, "  MPLS-TE IPv6 Router-Address: %pI6\n",
			&area->mta->router_id_ipv6);
		no_match = false;
	}
	if (no_match)
		vty_out(vty, "  N/A\n");
}

DEFUN(show_isis_mpls_te_router,
      show_isis_mpls_te_router_cmd,
      "show " PROTO_NAME " [vrf <NAME|all>] mpls-te router",
      SHOW_STR
      PROTO_HELP
      VRF_CMD_HELP_STR "All VRFs\n"
      MPLS_TE_STR "Router information\n")
{

	struct listnode *anode, *inode;
	struct isis_area *area;
	struct isis *isis = NULL;
	const char *vrf_name = VRF_DEFAULT_NAME;
	bool all_vrf = false;
	int idx_vrf = 0;

	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, inode, isis)) {
				for (ALL_LIST_ELEMENTS_RO(isis->area_list,
							  anode, area)) {
					if (!IS_MPLS_TE(area->mta))
						continue;

					show_router_id(vty, area);
				}
			}
			return 0;
		}
		isis = isis_lookup_by_vrfname(vrf_name);
		if (isis != NULL) {
			for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode,
						  area)) {

				if (!IS_MPLS_TE(area->mta))
					continue;

				show_router_id(vty, area);
			}
		}
	}

	return CMD_SUCCESS;
}

static void show_ext_sub(struct vty *vty, char *name,
			 struct isis_ext_subtlvs *ext)
{
	struct sbuf buf;
	char ibuf[PREFIX2STR_BUFFER];

	sbuf_init(&buf, NULL, 0);

	if (!ext || ext->status == EXT_DISABLE)
		return;

	vty_out(vty, "-- MPLS-TE link parameters for %s --\n", name);

	sbuf_reset(&buf);

	if (IS_SUBTLV(ext, EXT_ADM_GRP))
		sbuf_push(&buf, 4, "Administrative Group: 0x%x\n",
			ext->adm_group);
	if (IS_SUBTLV(ext, EXT_LLRI)) {
		sbuf_push(&buf, 4, "Link Local  ID: %u\n",
			  ext->local_llri);
		sbuf_push(&buf, 4, "Link Remote ID: %u\n",
			  ext->remote_llri);
	}
	if (IS_SUBTLV(ext, EXT_LOCAL_ADDR))
		sbuf_push(&buf, 4, "Local Interface IP Address(es): %pI4\n",
			  &ext->local_addr);
	if (IS_SUBTLV(ext, EXT_NEIGH_ADDR))
		sbuf_push(&buf, 4, "Remote Interface IP Address(es): %pI4\n",
			  &ext->neigh_addr);
	if (IS_SUBTLV(ext, EXT_LOCAL_ADDR6))
		sbuf_push(&buf, 4, "Local Interface IPv6 Address(es): %s\n",
			  inet_ntop(AF_INET6, &ext->local_addr6, ibuf,
				    PREFIX2STR_BUFFER));
	if (IS_SUBTLV(ext, EXT_NEIGH_ADDR6))
		sbuf_push(&buf, 4, "Remote Interface IPv6 Address(es): %s\n",
			  inet_ntop(AF_INET6, &ext->local_addr6, ibuf,
				    PREFIX2STR_BUFFER));
	if (IS_SUBTLV(ext, EXT_MAX_BW))
		sbuf_push(&buf, 4, "Maximum Bandwidth: %g (Bytes/sec)\n",
			  ext->max_bw);
	if (IS_SUBTLV(ext, EXT_MAX_RSV_BW))
		sbuf_push(&buf, 4,
			  "Maximum Reservable Bandwidth: %g (Bytes/sec)\n",
			  ext->max_rsv_bw);
	if (IS_SUBTLV(ext, EXT_UNRSV_BW)) {
		sbuf_push(&buf, 4, "Unreserved Bandwidth:\n");
		for (int j = 0; j < MAX_CLASS_TYPE; j += 2) {
			sbuf_push(&buf, 4 + 2,
				  "[%d]: %g (Bytes/sec),\t[%d]: %g (Bytes/sec)\n",
				  j, ext->unrsv_bw[j],
				  j + 1, ext->unrsv_bw[j + 1]);
		}
	}
	if (IS_SUBTLV(ext, EXT_TE_METRIC))
		sbuf_push(&buf, 4, "Traffic Engineering Metric: %u\n",
			  ext->te_metric);
	if (IS_SUBTLV(ext, EXT_RMT_AS))
		sbuf_push(&buf, 4,
			  "Inter-AS TE Remote AS number: %u\n",
			  ext->remote_as);
	if (IS_SUBTLV(ext, EXT_RMT_IP))
		sbuf_push(&buf, 4,
			  "Inter-AS TE Remote ASBR IP address: %pI4\n",
			  &ext->remote_ip);
	if (IS_SUBTLV(ext, EXT_DELAY))
		sbuf_push(&buf, 4,
			  "%s Average Link Delay: %u (micro-sec)\n",
			  IS_ANORMAL(ext->delay) ? "Anomalous" : "Normal",
			  ext->delay);
	if (IS_SUBTLV(ext, EXT_MM_DELAY)) {
		sbuf_push(&buf, 4, "%s Min/Max Link Delay: %u / %u (micro-sec)\n",
			  IS_ANORMAL(ext->min_delay) ? "Anomalous" : "Normal",
			  ext->min_delay & TE_EXT_MASK,
			  ext->max_delay & TE_EXT_MASK);
	}
	if (IS_SUBTLV(ext, EXT_DELAY_VAR))
		sbuf_push(&buf, 4,
			  "Delay Variation: %u (micro-sec)\n",
			  ext->delay_var & TE_EXT_MASK);
	if (IS_SUBTLV(ext, EXT_PKT_LOSS))
		sbuf_push(&buf, 4, "%s Link Packet Loss: %g (%%)\n",
			  IS_ANORMAL(ext->pkt_loss) ? "Anomalous" : "Normal",
			  (float)((ext->pkt_loss & TE_EXT_MASK)
				  * LOSS_PRECISION));
	if (IS_SUBTLV(ext, EXT_RES_BW))
		sbuf_push(&buf, 4,
			  "Unidirectional Residual Bandwidth: %g (Bytes/sec)\n",
			  ext->res_bw);
	if (IS_SUBTLV(ext, EXT_AVA_BW))
		sbuf_push(&buf, 4,
			  "Unidirectional Available Bandwidth: %g (Bytes/sec)\n",
			  ext->ava_bw);
	if (IS_SUBTLV(ext, EXT_USE_BW))
		sbuf_push(&buf, 4,
			  "Unidirectional Utilized Bandwidth: %g (Bytes/sec)\n",
			  ext->use_bw);

	vty_multiline(vty, "", "%s", sbuf_buf(&buf));
	vty_out(vty, "---------------\n\n");

	sbuf_free(&buf);
	return;
}

DEFUN (show_isis_mpls_te_interface,
       show_isis_mpls_te_interface_cmd,
       "show " PROTO_NAME " mpls-te interface [INTERFACE]",
       SHOW_STR
       PROTO_HELP
       MPLS_TE_STR
       "Interface information\n"
       "Interface name\n")
{
	struct listnode *anode, *cnode, *inode;
	struct isis_area *area;
	struct isis_circuit *circuit;
	struct interface *ifp;
	int idx_interface = 4;
	struct isis *isis = NULL;

	if (!im) {
		vty_out(vty, "IS-IS Routing Process not enabled\n");
		return CMD_SUCCESS;
	}

	if (argc == idx_interface) {
		/* Show All Interfaces. */
		for (ALL_LIST_ELEMENTS_RO(im->isis, inode, isis)) {
			for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode,
						  area)) {

				if (!IS_MPLS_TE(area->mta))
					continue;

				vty_out(vty, "Area %s:\n", area->area_tag);

				for (ALL_LIST_ELEMENTS_RO(area->circuit_list,
							  cnode, circuit))
					show_ext_sub(vty,
						     circuit->interface->name,
						     circuit->ext);
			}
		}
	} else {
		/* Interface name is specified. */
		ifp = if_lookup_by_name(argv[idx_interface]->arg, VRF_DEFAULT);
		if (ifp == NULL)
			vty_out(vty, "No such interface name\n");
		else {
			circuit = circuit_scan_by_ifp(ifp);
			if (!circuit)
				vty_out(vty,
					"ISIS is not enabled on circuit %s\n",
					ifp->name);
			else
				show_ext_sub(vty, ifp->name, circuit->ext);
		}
	}

	return CMD_SUCCESS;
}

/**
 * Search Vertex in TED that corresponds to the given string that represent
 * the ISO system ID in the forms <systemid/hostname>[.<pseudo-id>-<framenent>]
 *
 * @param ted	Link State Database
 * @param id	ISO System ID
 * @param isis	Main reference to the isis daemon
 *
 * @return	Vertex if found, NULL otherwise
 */
static struct ls_vertex *vertex_for_arg(struct ls_ted *ted, const char *id,
					struct isis *isis)
{
	char sysid[255] = {0};
	uint8_t number[3];
	const char *pos;
	uint8_t lspid[ISIS_SYS_ID_LEN + 2] = {0};
	struct isis_dynhn *dynhn;
	uint64_t key = 0;

	if (!id)
		return NULL;

	/*
	 * extract fragment and pseudo id from the string argv
	 * in the forms:
	 * (a) <systemid/hostname>.<pseudo-id>-<framenent> or
	 * (b) <systemid/hostname>.<pseudo-id> or
	 * (c) <systemid/hostname> or
	 * Where systemid is in the form:
	 * xxxx.xxxx.xxxx
	 */
	strlcpy(sysid, id, sizeof(sysid));
	if (strlen(id) > 3) {
		pos = id + strlen(id) - 3;
		if (strncmp(pos, "-", 1) == 0) {
			memcpy(number, ++pos, 2);
			lspid[ISIS_SYS_ID_LEN + 1] =
				(uint8_t)strtol((char *)number, NULL, 16);
			pos -= 4;
			if (strncmp(pos, ".", 1) != 0)
				return NULL;
		}
		if (strncmp(pos, ".", 1) == 0) {
			memcpy(number, ++pos, 2);
			lspid[ISIS_SYS_ID_LEN] =
				(uint8_t)strtol((char *)number, NULL, 16);
			sysid[pos - id - 1] = '\0';
		}
	}

	/*
	 * Try to find the lsp-id if the argv
	 * string is in
	 * the form
	 * hostname.<pseudo-id>-<fragment>
	 */
	if (sysid2buff(lspid, sysid)) {
		key = sysid_to_key(lspid);
	} else if ((dynhn = dynhn_find_by_name(isis, sysid))) {
		memcpy(lspid, dynhn->id, ISIS_SYS_ID_LEN);
		key = sysid_to_key(lspid);
	} else if (strncmp(cmd_hostname_get(), sysid, 15) == 0) {
		memcpy(lspid, isis->sysid, ISIS_SYS_ID_LEN);
		key = sysid_to_key(lspid);
	}

	if (key == 0)
		return NULL;

	return ls_find_vertex_by_key(ted, key);
}

/**
 * Show Link State Traffic Engineering Database extracted from IS-IS LSP.
 *
 * @param vty	VTY output console
 * @param argv	Command line argument
 * @param argc	Number of command line argument
 * @param ted	Traffic Engineering Database
 * @param isis	isis Main reference to the isis daemon
 *
 * @return	Command Success if OK, Command Warning otherwise
 */
static int show_ted(struct vty *vty, struct cmd_token *argv[], int argc,
		    struct isis_area *area, struct isis *isis)
{
	int idx;
	char *id;
	struct in_addr ip_addr;
	struct in6_addr ip6_addr;
	struct prefix pref;
	struct ls_ted *ted;
	struct ls_vertex *vertex;
	struct ls_edge *edge;
	struct ls_subnet *subnet;
	uint64_t key;
	bool detail = false;
	bool uj = use_json(argc, argv);
	json_object *json = NULL;

	if (!IS_MPLS_TE(area->mta) || !area->mta->ted) {
		vty_out(vty, "MPLS-TE is disabled for Area %s\n",
			area->area_tag ? area->area_tag : "null");
		return CMD_SUCCESS;
	}

	ted = area->mta->ted;

	if (uj)
		json = json_object_new_object();
	else
		vty_out(vty, "Area %s:\n",
			area->area_tag ? area->area_tag : "null");

	if (argv[argc - 1]->arg && strmatch(argv[argc - 1]->text, "detail"))
		detail = true;

	idx = 4;
	if (argv_find(argv, argc, "vertex", &idx)) {
		/* Show Vertex */
		id = argv_find(argv, argc, "WORD", &idx) ? argv[idx]->arg
							 : NULL;
		if (!id)
			vertex = NULL;
		else if (!strncmp(id, "self", 4))
			vertex = ted->self;
		else {
			vertex = vertex_for_arg(ted, id, isis);
			if (!vertex) {
				vty_out(vty, "No vertex found for ID %s\n", id);
				return CMD_WARNING;
			}
		}

		if (vertex)
			ls_show_vertex(vertex, vty, json, detail);
		else
			ls_show_vertices(ted, vty, json, detail);

	} else if (argv_find(argv, argc, "edge", &idx)) {
		/* Show Edge */
		if (argv_find(argv, argc, "A.B.C.D", &idx)) {
			if (!inet_pton(AF_INET, argv[idx]->arg, &ip_addr)) {
				vty_out(vty,
					"Specified Edge ID %s is invalid\n",
					argv[idx]->arg);
				return CMD_WARNING_CONFIG_FAILED;
			}
			/* Get the Edge from the Link State Database */
			key = ((uint64_t)ntohl(ip_addr.s_addr)) & 0xffffffff;
			edge = ls_find_edge_by_key(ted, key);
			if (!edge) {
				vty_out(vty, "No edge found for ID %pI4\n",
					&ip_addr);
				return CMD_WARNING;
			}
		} else if (argv_find(argv, argc, "X:X::X:X", &idx)) {
			if (!inet_pton(AF_INET6, argv[idx]->arg, &ip6_addr)) {
				vty_out(vty,
					"Specified Edge ID %s is invalid\n",
					argv[idx]->arg);
				return CMD_WARNING_CONFIG_FAILED;
			}
			/* Get the Edge from the Link State Database */
			key = (uint64_t)ntohl(ip6_addr.s6_addr32[3])
			      | ((uint64_t)ntohl(ip6_addr.s6_addr32[2]) << 32);
			edge = ls_find_edge_by_key(ted, key);
			if (!edge) {
				vty_out(vty, "No edge found for ID %pI6\n",
					&ip6_addr);
				return CMD_WARNING;
			}
		} else
			edge = NULL;

		if (edge)
			ls_show_edge(edge, vty, json, detail);
		else
			ls_show_edges(ted, vty, json, detail);

	} else if (argv_find(argv, argc, "subnet", &idx)) {
		/* Show Subnet */
		if (argv_find(argv, argc, "A.B.C.D/M", &idx)) {
			if (!str2prefix(argv[idx]->arg, &pref)) {
				vty_out(vty, "Invalid prefix format %s\n",
					argv[idx]->arg);
				return CMD_WARNING_CONFIG_FAILED;
			}
			/* Get the Subnet from the Link State Database */
			subnet = ls_find_subnet(ted, pref);
			if (!subnet) {
				vty_out(vty, "No subnet found for ID %pFX\n",
					&pref);
				return CMD_WARNING;
			}
		} else if (argv_find(argv, argc, "X:X::X:X/M", &idx)) {
			if (!str2prefix(argv[idx]->arg, &pref)) {
				vty_out(vty, "Invalid prefix format %s\n",
					argv[idx]->arg);
				return CMD_WARNING_CONFIG_FAILED;
			}
			/* Get the Subnet from the Link State Database */
			subnet = ls_find_subnet(ted, pref);
			if (!subnet) {
				vty_out(vty, "No subnet found for ID %pFX\n",
					&pref);
				return CMD_WARNING;
			}
		} else
			subnet = NULL;

		if (subnet)
			ls_show_subnet(subnet, vty, json, detail);
		else
			ls_show_subnets(ted, vty, json, detail);

	} else {
		/* Show the complete TED */
		ls_show_ted(ted, vty, json, detail);
	}

	if (uj)
		vty_json(vty, json);

	return CMD_SUCCESS;
}

/**
 * Show ISIS Traffic Engineering Database
 *
 * @param vty	VTY output console
 * @param argv	Command line argument
 * @param argc	Number of command line argument
 * @param isis	isis Main reference to the isis daemon

 * @return	Command Success if OK, Command Warning otherwise
 */
static int show_isis_ted(struct vty *vty, struct cmd_token *argv[], int argc,
			 struct isis *isis)
{
	struct listnode *node;
	struct isis_area *area;
	int rc;

	for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area)) {
		rc = show_ted(vty, argv, argc, area, isis);
		if (rc != CMD_SUCCESS)
			return rc;
	}
	return CMD_SUCCESS;
}

DEFUN(show_isis_mpls_te_db,
      show_isis_mpls_te_db_cmd,
      "show " PROTO_NAME " [vrf <NAME|all>] mpls-te database [<vertex [WORD]|edge [A.B.C.D|X:X::X:X]|subnet [A.B.C.D/M|X:X::X:X/M]>] [detail|json]",
      SHOW_STR PROTO_HELP VRF_CMD_HELP_STR
      "All VRFs\n"
      MPLS_TE_STR
      "MPLS-TE database\n"
      "MPLS-TE Vertex\n"
      "MPLS-TE Vertex ID (as an ISO ID, hostname or \"self\")\n"
      "MPLS-TE Edge\n"
      "MPLS-TE Edge ID (as an IPv4 address)\n"
      "MPLS-TE Edge ID (as an IPv6 address)\n"
      "MPLS-TE Subnet\n"
      "MPLS-TE Subnet ID (as an IPv4 prefix)\n"
      "MPLS-TE Subnet ID (as an IPv6 prefix)\n"
      "Detailed information\n"
      JSON_STR)
{
	int idx_vrf = 0;
	const char *vrf_name = VRF_DEFAULT_NAME;
	bool all_vrf = false;
	struct listnode *node;
	struct isis *isis;
	int rc = CMD_WARNING;

	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)) {
				rc = show_isis_ted(vty, argv, argc, isis);
				if (rc != CMD_SUCCESS)
					return rc;
			}
			return CMD_SUCCESS;
		}
		isis = isis_lookup_by_vrfname(vrf_name);
		if (isis)
			rc = show_isis_ted(vty, argv, argc, isis);
	}

	return rc;
}

#endif /* #ifndef FRABRICD */

/* Initialize MPLS_TE */
void isis_mpls_te_init(void)
{

	/* Register Circuit and Adjacency hook */
	hook_register(isis_if_new_hook, isis_mpls_te_update);
	hook_register(isis_adj_ip_enabled_hook, isis_mpls_te_adj_ip_enabled);
	hook_register(isis_adj_ip_disabled_hook, isis_mpls_te_adj_ip_disabled);

#ifndef FABRICD
	/* Register new VTY commands */
	install_element(VIEW_NODE, &show_isis_mpls_te_router_cmd);
	install_element(VIEW_NODE, &show_isis_mpls_te_interface_cmd);
	install_element(VIEW_NODE, &show_isis_mpls_te_db_cmd);
#endif

	return;
}
