/*
 * This is an implementation of RFC4970 Router Information
 * with support of RFC5088 PCE Capabilites announcement
 *
 * Module name: Router Information
 * Author: Olivier Dugeon <olivier.dugeon@orange.com>
 * Copyright (C) 2012 - 2017 Orange Labs http://www.orange.com/
 *
 * This file is part of GNU Quagga.
 *
 * 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 Quagga 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 "prefix.h"
#include "if.h"
#include "table.h"
#include "memory.h"
#include "command.h"
#include "vty.h"
#include "stream.h"
#include "log.h"
#include "thread.h"
#include "hash.h"
#include "sockunion.h" /* for inet_aton() */
#include "mpls.h"

#include "ospfd/ospfd.h"
#include "ospfd/ospf_interface.h"
#include "ospfd/ospf_ism.h"
#include "ospfd/ospf_asbr.h"
#include "ospfd/ospf_lsa.h"
#include "ospfd/ospf_lsdb.h"
#include "ospfd/ospf_neighbor.h"
#include "ospfd/ospf_nsm.h"
#include "ospfd/ospf_flood.h"
#include "ospfd/ospf_packet.h"
#include "ospfd/ospf_spf.h"
#include "ospfd/ospf_dump.h"
#include "ospfd/ospf_route.h"
#include "ospfd/ospf_ase.h"
#include "ospfd/ospf_zebra.h"
#include "ospfd/ospf_sr.h"
#include "ospfd/ospf_ri.h"
#include "ospfd/ospf_errors.h"

/*
 * Global variable to manage Opaque-LSA/Router Information on this node.
 * Note that all parameter values are stored in network byte order.
 */
static struct ospf_router_info OspfRI;

/*------------------------------------------------------------------------------*
 * Followings are initialize/terminate functions for Router Information
 *handling.
 *------------------------------------------------------------------------------*/

static void ospf_router_info_ism_change(struct ospf_interface *oi,
					int old_status);
static void ospf_router_info_config_write_router(struct vty *vty);
static void ospf_router_info_show_info(struct vty *vty, struct ospf_lsa *lsa);
static int ospf_router_info_lsa_originate(void *arg);
static struct ospf_lsa *ospf_router_info_lsa_refresh(struct ospf_lsa *lsa);
static void ospf_router_info_lsa_schedule(struct ospf_ri_area_info *ai,
					  enum lsa_opcode opcode);
static void ospf_router_info_register_vty(void);
static int ospf_router_info_lsa_update(struct ospf_lsa *lsa);
static void del_area_info(void *val);
static void del_pce_info(void *val);

int ospf_router_info_init(void)
{

	zlog_info("RI (%s): Initialize Router Information", __func__);

	memset(&OspfRI, 0, sizeof(struct ospf_router_info));
	OspfRI.enabled = false;
	OspfRI.registered = 0;
	OspfRI.scope = OSPF_OPAQUE_AS_LSA;
	OspfRI.as_flags = RIFLG_LSA_INACTIVE;
	OspfRI.area_info = list_new();
	OspfRI.area_info->del = del_area_info;

	/* Initialize pce domain and neighbor list */
	OspfRI.pce_info.enabled = false;
	OspfRI.pce_info.pce_domain = list_new();
	OspfRI.pce_info.pce_domain->del = del_pce_info;
	OspfRI.pce_info.pce_neighbor = list_new();
	OspfRI.pce_info.pce_neighbor->del = del_pce_info;

	/* Initialize Segment Routing information structure */
	OspfRI.sr_info.enabled = false;

	ospf_router_info_register_vty();

	return 0;
}

static int ospf_router_info_register(uint8_t scope)
{
	int rc = 0;

	if (OspfRI.registered)
		return rc;

	zlog_info("RI (%s): Register Router Information with scope %s(%d)",
		  __func__,
		  scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS", scope);
	rc = ospf_register_opaque_functab(
		scope, OPAQUE_TYPE_ROUTER_INFORMATION_LSA,
		NULL, /* new interface */
		NULL, /* del interface */
		ospf_router_info_ism_change,
		NULL, /* NSM change */
		ospf_router_info_config_write_router,
		NULL, /* Config. write interface */
		NULL, /* Config. write debug */
		ospf_router_info_show_info, ospf_router_info_lsa_originate,
		ospf_router_info_lsa_refresh, ospf_router_info_lsa_update,
		NULL); /* del_lsa_hook */

	if (rc != 0) {
		flog_warn(
			EC_OSPF_OPAQUE_REGISTRATION,
			"RI (%s): Failed to register functions", __func__);
		return rc;
	}

	OspfRI.registered = 1;
	OspfRI.scope = scope;
	return rc;
}

static int ospf_router_info_unregister(void)
{

	if ((OspfRI.scope != OSPF_OPAQUE_AS_LSA)
	    && (OspfRI.scope != OSPF_OPAQUE_AREA_LSA)) {
		assert("Unable to unregister Router Info functions: Wrong scope!"
		       == NULL);
		return -1;
	}

	ospf_delete_opaque_functab(OspfRI.scope,
				   OPAQUE_TYPE_ROUTER_INFORMATION_LSA);

	OspfRI.registered = 0;
	return 0;
}

void ospf_router_info_term(void)
{

	list_delete(&OspfRI.pce_info.pce_domain);
	list_delete(&OspfRI.pce_info.pce_neighbor);

	OspfRI.enabled = false;

	ospf_router_info_unregister();

	return;
}

void ospf_router_info_finish(void)
{
	struct listnode *node, *nnode;
	struct ospf_ri_area_info *ai;

	/* Flush Router Info LSA */
	for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai))
		if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED))
			ospf_router_info_lsa_schedule(ai, FLUSH_THIS_LSA);

	list_delete_all_node(OspfRI.pce_info.pce_domain);
	list_delete_all_node(OspfRI.pce_info.pce_neighbor);

	OspfRI.enabled = false;
}

static void del_area_info(void *val)
{
	XFREE(MTYPE_OSPF_ROUTER_INFO, val);
}

static void del_pce_info(void *val)
{
	XFREE(MTYPE_OSPF_PCE_PARAMS, val);
}

/* Catch RI LSA flooding Scope for ospf_ext.[h,c] code */
struct scope_info ospf_router_info_get_flooding_scope(void)
{
	struct scope_info flooding_scope;

	if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) {
		flooding_scope.scope = OSPF_OPAQUE_AS_LSA;
		flooding_scope.areas = NULL;
		return flooding_scope;
	}
	flooding_scope.scope = OSPF_OPAQUE_AREA_LSA;
	flooding_scope.areas = OspfRI.area_info;
	return flooding_scope;
}

static struct ospf_ri_area_info *lookup_by_area(struct ospf_area *area)
{
	struct listnode *node, *nnode;
	struct ospf_ri_area_info *ai;

	for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai))
		if (ai->area == area)
			return ai;

	return NULL;
}

/*------------------------------------------------------------------------*
 * Followings are control functions for ROUTER INFORMATION parameters
 *management.
 *------------------------------------------------------------------------*/

static void set_router_info_capabilities(struct ri_tlv_router_cap *ric,
					 uint32_t cap)
{
	ric->header.type = htons(RI_TLV_CAPABILITIES);
	ric->header.length = htons(RI_TLV_LENGTH);
	ric->value = htonl(cap);
	return;
}

static int set_pce_header(struct ospf_pce_info *pce)
{
	uint16_t length = 0;
	struct listnode *node;
	struct ri_pce_subtlv_domain *domain;
	struct ri_pce_subtlv_neighbor *neighbor;

	/* PCE Address */
	if (ntohs(pce->pce_address.header.type) != 0)
		length += TLV_SIZE(&pce->pce_address.header);

	/* PCE Path Scope */
	if (ntohs(pce->pce_scope.header.type) != 0)
		length += TLV_SIZE(&pce->pce_scope.header);

	/* PCE Domain */
	for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) {
		if (ntohs(domain->header.type) != 0)
			length += TLV_SIZE(&domain->header);
	}

	/* PCE Neighbor */
	for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) {
		if (ntohs(neighbor->header.type) != 0)
			length += TLV_SIZE(&neighbor->header);
	}

	/* PCE Capabilities */
	if (ntohs(pce->pce_cap_flag.header.type) != 0)
		length += TLV_SIZE(&pce->pce_cap_flag.header);

	if (length != 0) {
		pce->pce_header.header.type = htons(RI_TLV_PCE);
		pce->pce_header.header.length = htons(length);
		pce->enabled = true;
	} else {
		pce->pce_header.header.type = 0;
		pce->pce_header.header.length = 0;
		pce->enabled = false;
	}

	return length;
}

static void set_pce_address(struct in_addr ipv4, struct ospf_pce_info *pce)
{

	/* Enable PCE Info */
	pce->pce_header.header.type = htons(RI_TLV_PCE);
	/* Set PCE Address */
	pce->pce_address.header.type = htons(RI_PCE_SUBTLV_ADDRESS);
	pce->pce_address.header.length = htons(PCE_ADDRESS_LENGTH_IPV4);
	pce->pce_address.address.type = htons(PCE_ADDRESS_TYPE_IPV4);
	pce->pce_address.address.value = ipv4;

	return;
}

static void set_pce_path_scope(uint32_t scope, struct ospf_pce_info *pce)
{

	/* Set PCE Scope */
	pce->pce_scope.header.type = htons(RI_PCE_SUBTLV_PATH_SCOPE);
	pce->pce_scope.header.length = htons(RI_TLV_LENGTH);
	pce->pce_scope.value = htonl(scope);

	return;
}

static void set_pce_domain(uint16_t type, uint32_t domain,
			   struct ospf_pce_info *pce)
{

	struct ri_pce_subtlv_domain *new;

	/* Create new domain info */
	new = XCALLOC(MTYPE_OSPF_PCE_PARAMS,
		      sizeof(struct ri_pce_subtlv_domain));

	new->header.type = htons(RI_PCE_SUBTLV_DOMAIN);
	new->header.length = htons(PCE_ADDRESS_LENGTH_IPV4);
	new->type = htons(type);
	new->value = htonl(domain);

	/* Add new domain to the list */
	listnode_add(pce->pce_domain, new);

	return;
}

static void unset_pce_domain(uint16_t type, uint32_t domain,
			     struct ospf_pce_info *pce)
{
	struct listnode *node;
	struct ri_pce_subtlv_domain *old = NULL;
	int found = 0;

	/* Search the corresponding node */
	for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, old)) {
		if ((old->type == htons(type))
		    && (old->value == htonl(domain))) {
			found = 1;
			break;
		}
	}

	/* if found remove it */
	if (found) {
		listnode_delete(pce->pce_domain, old);

		/* Finally free the old domain */
		XFREE(MTYPE_OSPF_PCE_PARAMS, old);
	}
}

static void set_pce_neighbor(uint16_t type, uint32_t domain,
			     struct ospf_pce_info *pce)
{

	struct ri_pce_subtlv_neighbor *new;

	/* Create new neighbor info */
	new = XCALLOC(MTYPE_OSPF_PCE_PARAMS,
		      sizeof(struct ri_pce_subtlv_neighbor));

	new->header.type = htons(RI_PCE_SUBTLV_NEIGHBOR);
	new->header.length = htons(PCE_ADDRESS_LENGTH_IPV4);
	new->type = htons(type);
	new->value = htonl(domain);

	/* Add new domain to the list */
	listnode_add(pce->pce_neighbor, new);

	return;
}

static void unset_pce_neighbor(uint16_t type, uint32_t domain,
			       struct ospf_pce_info *pce)
{
	struct listnode *node;
	struct ri_pce_subtlv_neighbor *old = NULL;
	int found = 0;

	/* Search the corresponding node */
	for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, old)) {
		if ((old->type == htons(type))
		    && (old->value == htonl(domain))) {
			found = 1;
			break;
		}
	}

	/* if found remove it */
	if (found) {
		listnode_delete(pce->pce_neighbor, old);

		/* Finally free the old domain */
		XFREE(MTYPE_OSPF_PCE_PARAMS, old);
	}
}

static void set_pce_cap_flag(uint32_t cap, struct ospf_pce_info *pce)
{

	/* Set PCE Capabilities flag */
	pce->pce_cap_flag.header.type = htons(RI_PCE_SUBTLV_CAP_FLAG);
	pce->pce_cap_flag.header.length = htons(RI_TLV_LENGTH);
	pce->pce_cap_flag.value = htonl(cap);

	return;
}

/* Segment Routing TLV setter */

/* Algorithm SubTLV - section 3.1 */
static void set_sr_algorithm(uint8_t algo)
{

	OspfRI.sr_info.algo.value[0] = algo;
	for (int i = 1; i < ALGORITHM_COUNT; i++)
		OspfRI.sr_info.algo.value[i] = SR_ALGORITHM_UNSET;

	/* Set TLV type and length == only 1 Algorithm */
	TLV_TYPE(OspfRI.sr_info.algo) = htons(RI_SR_TLV_SR_ALGORITHM);
	TLV_LEN(OspfRI.sr_info.algo) = htons(sizeof(uint8_t));
}

/* unset Aglogithm SubTLV */
static void unset_sr_algorithm(uint8_t algo)
{

	for (int i = 0; i < ALGORITHM_COUNT; i++)
		OspfRI.sr_info.algo.value[i] = SR_ALGORITHM_UNSET;

	/* Unset TLV type and length */
	TLV_TYPE(OspfRI.sr_info.algo) = htons(0);
	TLV_LEN(OspfRI.sr_info.algo) = htons(0);
}

/* Set Segment Routing Global Block SubTLV - section 3.2 */
static void set_sr_global_label_range(struct sr_block srgb)
{
	/* Set Header */
	TLV_TYPE(OspfRI.sr_info.srgb) = htons(RI_SR_TLV_SRGB_LABEL_RANGE);
	TLV_LEN(OspfRI.sr_info.srgb) =
		htons(SUBTLV_SID_LABEL_SIZE + sizeof(uint32_t));
	/* Set Range Size */
	OspfRI.sr_info.srgb.size = htonl(SET_RANGE_SIZE(srgb.range_size));
	/* Set Lower bound label SubTLV */
	TLV_TYPE(OspfRI.sr_info.srgb.lower) = htons(SUBTLV_SID_LABEL);
	TLV_LEN(OspfRI.sr_info.srgb.lower) = htons(SID_RANGE_LABEL_LENGTH);
	OspfRI.sr_info.srgb.lower.value = htonl(SET_LABEL(srgb.lower_bound));
}

/* Unset Segment Routing Global Block SubTLV */
static void unset_sr_global_label_range(void)
{
	TLV_TYPE(OspfRI.sr_info.srgb) = htons(0);
	TLV_LEN(OspfRI.sr_info.srgb) = htons(0);
	TLV_TYPE(OspfRI.sr_info.srgb.lower) = htons(0);
	TLV_LEN(OspfRI.sr_info.srgb.lower) = htons(0);
}

/* Set Segment Routing Local Block SubTLV - section 3.2 */
static void set_sr_local_label_range(struct sr_block srlb)
{
	/* Set Header */
	TLV_TYPE(OspfRI.sr_info.srlb) = htons(RI_SR_TLV_SRLB_LABEL_RANGE);
	TLV_LEN(OspfRI.sr_info.srlb) =
		htons(SUBTLV_SID_LABEL_SIZE + sizeof(uint32_t));
	/* Set Range Size */
	OspfRI.sr_info.srlb.size = htonl(SET_RANGE_SIZE(srlb.range_size));
	/* Set Lower bound label SubTLV */
	TLV_TYPE(OspfRI.sr_info.srlb.lower) = htons(SUBTLV_SID_LABEL);
	TLV_LEN(OspfRI.sr_info.srlb.lower) = htons(SID_RANGE_LABEL_LENGTH);
	OspfRI.sr_info.srlb.lower.value = htonl(SET_LABEL(srlb.lower_bound));
}

/* Unset Segment Routing Local Block SubTLV */
static void unset_sr_local_label_range(void)
{
	TLV_TYPE(OspfRI.sr_info.srlb) = htons(0);
	TLV_LEN(OspfRI.sr_info.srlb) = htons(0);
	TLV_TYPE(OspfRI.sr_info.srlb.lower) = htons(0);
	TLV_LEN(OspfRI.sr_info.srlb.lower) = htons(0);
}

/* Set Maximum Stack Depth for this router */
static void set_sr_node_msd(uint8_t msd)
{
	TLV_TYPE(OspfRI.sr_info.msd) = htons(RI_SR_TLV_NODE_MSD);
	TLV_LEN(OspfRI.sr_info.msd) = htons(sizeof(uint32_t));
	OspfRI.sr_info.msd.value = msd;
}

/* Unset this router MSD */
static void unset_sr_node_msd(void)
{
	TLV_TYPE(OspfRI.sr_info.msd) = htons(0);
	TLV_LEN(OspfRI.sr_info.msd) = htons(0);
}

static void unset_param(void *tlv_buffer)
{
	struct tlv_header *tlv = (struct tlv_header *)tlv_buffer;

	tlv->type = 0;
	/* Fill the Value to 0 */
	memset(TLV_DATA(tlv_buffer), 0, TLV_BODY_SIZE(tlv));
	tlv->length = 0;

	return;
}

static void initialize_params(struct ospf_router_info *ori)
{
	uint32_t cap = 0;
	struct ospf *top;
	struct listnode *node, *nnode;
	struct ospf_area *area;
	struct ospf_ri_area_info *new;

	/*
	 * Initialize default Router Information Capabilities.
	 */
	cap = RI_TE_SUPPORT;

	set_router_info_capabilities(&ori->router_cap, cap);

	/* If Area address is not null and exist, retrieve corresponding
	 * structure */
	top = ospf_lookup_by_vrf_id(VRF_DEFAULT);
	zlog_info("RI (%s): Initialize Router Info for %s scope", __func__,
		  OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS");

	/* Try to get available Area's context from ospf at this step.
	 * Do it latter if not available */
	if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) {
		if (!list_isempty(OspfRI.area_info))
			list_delete_all_node(OspfRI.area_info);
		for (ALL_LIST_ELEMENTS(top->areas, node, nnode, area)) {
			zlog_debug("RI (%s): Add area %s to Router Information",
				__func__, inet_ntoa(area->area_id));
			new = XCALLOC(MTYPE_OSPF_ROUTER_INFO,
				sizeof(struct ospf_ri_area_info));
			new->area = area;
			new->flags = RIFLG_LSA_INACTIVE;
			listnode_add(OspfRI.area_info, new);
		}
	}

	/*
	 * Initialize default PCE Information values
	 */
	/* PCE address == OSPF Router ID */
	set_pce_address(top->router_id, &ori->pce_info);

	/* PCE scope */
	cap = 7; /* Set L, R and Rd bits to one = intra & inter-area path
		    computation */
	set_pce_path_scope(cap, &ori->pce_info);

	/* PCE Capabilities */
	cap = PCE_CAP_BIDIRECTIONAL | PCE_CAP_DIVERSE_PATH | PCE_CAP_OBJECTIVES
	      | PCE_CAP_ADDITIVE | PCE_CAP_MULTIPLE_REQ;
	set_pce_cap_flag(cap, &ori->pce_info);

	return;
}

static int is_mandated_params_set(struct ospf_router_info *ori)
{
	int rc = 0;

	if (ori == NULL)
		return rc;

	if (ntohs(ori->router_cap.header.type) == 0)
		return rc;

	if ((ntohs(ori->pce_info.pce_header.header.type) == RI_TLV_PCE)
	    && (ntohs(ori->pce_info.pce_address.header.type) == 0)
	    && (ntohs(ori->pce_info.pce_cap_flag.header.type) == 0))
		return rc;

	if ((ori->sr_info.enabled) && (ntohs(TLV_TYPE(ori->sr_info.algo)) == 0)
	    && (ntohs(TLV_TYPE(ori->sr_info.srgb)) == 0))
		return rc;

	rc = 1;

	return rc;
}

/*
 * Used by Segment Routing to set new TLVs and Sub-TLVs values
 *
 * @param enable To activate or not Segment Routing router Information flooding
 * @param srn    Self Segment Routing node
 *
 * @return none
 */
void ospf_router_info_update_sr(bool enable, struct sr_node *srn)
{
	struct listnode *node, *nnode;
	struct ospf_ri_area_info *ai;

	/* First, check if Router Information is registered or not */
	if (!OspfRI.registered)
		ospf_router_info_register(OSPF_OPAQUE_AREA_LSA);

	/* Verify that scope is AREA */
	if (OspfRI.scope != OSPF_OPAQUE_AREA_LSA) {
		zlog_err(
			"RI (%s): Router Info is %s flooding: Change scope to Area flooding for Segment Routing",
			__func__,
			OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area" : "AS");
		return;
	}

	/* Then, activate and initialize Router Information if necessary */
	if (!OspfRI.enabled) {
		OspfRI.enabled = true;
		initialize_params(&OspfRI);
	}

	/* Check that SR node is valid */
	if (srn == NULL)
		return;

	if (IS_DEBUG_OSPF_SR)
		zlog_debug("RI (%s): %s Routing Information for Segment Routing",
			   __func__, enable ? "Enable" : "Disable");

	/* Unset or Set SR parameters */
	if (!enable) {
		unset_sr_algorithm(SR_ALGORITHM_SPF);
		unset_sr_global_label_range();
		unset_sr_local_label_range();
		unset_sr_node_msd();
		OspfRI.sr_info.enabled = false;
	} else {
		// Only SR_ALGORITHM_SPF is supported
		set_sr_algorithm(SR_ALGORITHM_SPF);
		set_sr_global_label_range(srn->srgb);
		set_sr_local_label_range(srn->srlb);
		if (srn->msd != 0)
			set_sr_node_msd(srn->msd);
		else
			unset_sr_node_msd();
		OspfRI.sr_info.enabled = true;
	}

	/* Refresh if already engaged or originate RI LSA */
	for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) {
		if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED))
			ospf_router_info_lsa_schedule(ai, REFRESH_THIS_LSA);
		else
			ospf_router_info_lsa_schedule(ai,
				REORIGINATE_THIS_LSA);

	}
}

/*------------------------------------------------------------------------*
 * Followings are callback functions against generic Opaque-LSAs handling.
 *------------------------------------------------------------------------*/
static void ospf_router_info_ism_change(struct ospf_interface *oi,
					int old_state)
{

	struct ospf_ri_area_info *ai;

	/* Collect area information */
	ai = lookup_by_area(oi->area);

	/* Check if area is not yet registered */
	if (ai != NULL)
		return;

	/* Add this new area to the list */
	ai = XCALLOC(MTYPE_OSPF_ROUTER_INFO, sizeof(struct ospf_ri_area_info));
	ai->area = oi->area;
	ai->flags = RIFLG_LSA_INACTIVE;
	listnode_add(OspfRI.area_info, ai);

	return;
}

/*------------------------------------------------------------------------*
 * Followings are OSPF protocol processing functions for ROUTER INFORMATION
 *------------------------------------------------------------------------*/

static void build_tlv_header(struct stream *s, struct tlv_header *tlvh)
{

	stream_put(s, tlvh, sizeof(struct tlv_header));
	return;
}

static void build_tlv(struct stream *s, struct tlv_header *tlvh)
{

	if (ntohs(tlvh->type) != 0) {
		build_tlv_header(s, tlvh);
		stream_put(s, TLV_DATA(tlvh), TLV_BODY_SIZE(tlvh));
	}
	return;
}

static void ospf_router_info_lsa_body_set(struct stream *s)
{

	struct listnode *node;
	struct ri_pce_subtlv_domain *domain;
	struct ri_pce_subtlv_neighbor *neighbor;

	/* Build Router Information TLV */
	build_tlv(s, &OspfRI.router_cap.header);

	/* Build Segment Routing TLVs if enabled */
	if (OspfRI.sr_info.enabled) {
		/* Build Algorithm TLV */
		build_tlv(s, &TLV_HDR(OspfRI.sr_info.algo));
		/* Build SRGB TLV */
		build_tlv(s, &TLV_HDR(OspfRI.sr_info.srgb));
		/* Build SRLB TLV */
		build_tlv(s, &TLV_HDR(OspfRI.sr_info.srlb));
		/* Build MSD TLV */
		build_tlv(s, &TLV_HDR(OspfRI.sr_info.msd));
	}

	/* Add RI PCE TLV if it is set */
	if (OspfRI.pce_info.enabled) {

		/* Compute PCE Info header first */
		set_pce_header(&OspfRI.pce_info);

		/* Build PCE TLV */
		build_tlv_header(s, &OspfRI.pce_info.pce_header.header);

		/* Build PCE address sub-tlv */
		build_tlv(s, &OspfRI.pce_info.pce_address.header);

		/* Build PCE path scope sub-tlv */
		build_tlv(s, &OspfRI.pce_info.pce_scope.header);

		/* Build PCE domain sub-tlv */
		for (ALL_LIST_ELEMENTS_RO(OspfRI.pce_info.pce_domain, node,
					  domain))
			build_tlv(s, &domain->header);

		/* Build PCE neighbor sub-tlv */
		for (ALL_LIST_ELEMENTS_RO(OspfRI.pce_info.pce_neighbor, node,
					  neighbor))
			build_tlv(s, &neighbor->header);

		/* Build PCE cap flag sub-tlv */
		build_tlv(s, &OspfRI.pce_info.pce_cap_flag.header);
	}

	return;
}

/* Create new opaque-LSA. */
static struct ospf_lsa *ospf_router_info_lsa_new(struct ospf_area *area)
{
	struct ospf *top;
	struct stream *s;
	struct lsa_header *lsah;
	struct ospf_lsa *new = NULL;
	uint8_t options, lsa_type;
	struct in_addr lsa_id;
	uint32_t tmp;
	uint16_t length;

	/* Create a stream for LSA. */
	s = stream_new(OSPF_MAX_LSA_SIZE);

	lsah = (struct lsa_header *)STREAM_DATA(s);

	options = OSPF_OPTION_E;  /* Enable AS external as we flood RI with
				     Opaque Type 11 */
	options |= OSPF_OPTION_O; /* Don't forget this :-) */

	lsa_type = OspfRI.scope;
	/* LSA ID == 0 for Router Information see RFC 4970 */
	tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_ROUTER_INFORMATION_LSA, 0);
	lsa_id.s_addr = htonl(tmp);

	if (IS_DEBUG_OSPF(lsa, LSA_GENERATE))
		zlog_debug(
			"LSA[Type%d:%s]: Create an Opaque-LSA/ROUTER INFORMATION instance",
			lsa_type, inet_ntoa(lsa_id));

	top = ospf_lookup_by_vrf_id(VRF_DEFAULT);

	/* Set opaque-LSA header fields. */
	lsa_header_set(s, options, lsa_type, lsa_id, top->router_id);

	/* Set opaque-LSA body fields. */
	ospf_router_info_lsa_body_set(s);

	/* Set length. */
	length = stream_get_endp(s);
	lsah->length = htons(length);

	/* Now, create an OSPF LSA instance. */
	new = ospf_lsa_new_and_data(length);

	new->area = area;

	if (new->area && new->area->ospf)
		new->vrf_id = new->area->ospf->vrf_id;
	else
		new->vrf_id = VRF_DEFAULT;

	SET_FLAG(new->flags, OSPF_LSA_SELF);
	memcpy(new->data, lsah, length);
	stream_free(s);

	return new;
}

static int ospf_router_info_lsa_originate_as(void *arg)
{
	struct ospf_lsa *new;
	struct ospf *top;
	int rc = -1;
	vrf_id_t vrf_id = VRF_DEFAULT;

	/* Sanity Check */
	if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) {
		flog_warn(
			EC_OSPF_LSA_INSTALL_FAILURE,
			"RI (%s): wrong flooding scope AREA instead of AS ?",
			__func__);
		return rc;
	}

	/* Create new Opaque-LSA/ROUTER INFORMATION instance. */
	new = ospf_router_info_lsa_new(NULL);
	new->vrf_id = VRF_DEFAULT;
	top = (struct ospf *)arg;

	/* Check ospf info */
	if (top == NULL) {
		zlog_debug("RI (%s): ospf instance not found for vrf id %u",
			   __func__, vrf_id);
		ospf_lsa_unlock(&new);
		return rc;
	}

	/* Install this LSA into LSDB. */
	if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) {
		flog_warn(
			EC_OSPF_LSA_INSTALL_FAILURE,
			"RI (%s): ospf_lsa_install() ?", __func__);
		ospf_lsa_unlock(&new);
		return rc;
	}

	/* Update new LSA origination count. */
	top->lsa_originate_count++;

	/* Flood new LSA through AREA or AS. */
	SET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED);
	ospf_flood_through_as(top, NULL /*nbr */, new);

	if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) {
		zlog_debug(
			"LSA[Type%d:%s]: Originate Opaque-LSA/ROUTER INFORMATION",
			new->data->type, inet_ntoa(new->data->id));
		ospf_lsa_header_dump(new->data);
	}

	rc = 0;
	return rc;
}

static int ospf_router_info_lsa_originate_area(void *arg)
{
	struct ospf_lsa *new;
	struct ospf *top;
	struct ospf_ri_area_info *ai = NULL;
	int rc = -1;
	vrf_id_t vrf_id = VRF_DEFAULT;

	/* Sanity Check */
	if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) {
		flog_warn(
			EC_OSPF_LSA_INSTALL_FAILURE,
			"RI (%s): wrong flooding scope AS instead of AREA ?",
			__func__);
		return rc;
	}

	/* Create new Opaque-LSA/ROUTER INFORMATION instance. */
	ai = lookup_by_area((struct ospf_area *)arg);
	if (ai == NULL) {
		zlog_debug(
			"RI (%s): There is no context for this Router Information. Stop processing",
			__func__);
		return rc;
	}
	if (ai->area->ospf) {
		vrf_id = ai->area->ospf->vrf_id;
		top = ai->area->ospf;
	} else {
		top = ospf_lookup_by_vrf_id(vrf_id);
	}
	new = ospf_router_info_lsa_new(ai->area);
	new->vrf_id = vrf_id;

	/* Check ospf info */
	if (top == NULL) {
		zlog_debug("RI (%s): ospf instance not found for vrf id %u",
			   __func__, vrf_id);
		ospf_lsa_unlock(&new);
		return rc;
	}

	/* Install this LSA into LSDB. */
	if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) {
		flog_warn(
			EC_OSPF_LSA_INSTALL_FAILURE,
			"RI (%s): ospf_lsa_install() ?", __func__);
		ospf_lsa_unlock(&new);
		return rc;
	}

	/* Update new LSA origination count. */
	top->lsa_originate_count++;

	/* Flood new LSA through AREA or AS. */
	SET_FLAG(ai->flags, RIFLG_LSA_ENGAGED);
	ospf_flood_through_area(ai->area, NULL /*nbr */, new);

	if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) {
		zlog_debug(
			"LSA[Type%d:%s]: Originate Opaque-LSA/ROUTER INFORMATION",
			new->data->type, inet_ntoa(new->data->id));
		ospf_lsa_header_dump(new->data);
	}

	rc = 0;
	return rc;
}

static int ospf_router_info_lsa_originate(void *arg)
{

	struct ospf_ri_area_info *ai;
	int rc = -1;

	if (!OspfRI.enabled) {
		zlog_info("RI (%s): ROUTER INFORMATION is disabled now.",
			  __func__);
		rc = 0; /* This is not an error case. */
		return rc;
	}

	/* Check if Router Information LSA is already engaged */
	if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) {
		if ((CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED))
			&& (CHECK_FLAG(OspfRI.as_flags,
				RIFLG_LSA_FORCED_REFRESH))) {
			UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_FORCED_REFRESH);
			ospf_router_info_lsa_schedule(NULL, REFRESH_THIS_LSA);
			rc = 0;
			return rc;
		}
	} else {
		ai = lookup_by_area((struct ospf_area *)arg);
		if (ai == NULL) {
			flog_warn(
				EC_OSPF_LSA,
				"RI (%s): Missing area information", __func__);
			return rc;
		}
		if ((CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED))
			&& (CHECK_FLAG(ai->flags, RIFLG_LSA_FORCED_REFRESH))) {
			UNSET_FLAG(ai->flags, RIFLG_LSA_FORCED_REFRESH);
			ospf_router_info_lsa_schedule(ai, REFRESH_THIS_LSA);
			rc = 0;
			return rc;
		}
	}

	/* Router Information is not yet Engaged, check parameters */
	if (!is_mandated_params_set(&OspfRI))
		flog_warn(
			EC_OSPF_LSA,
			"RI (%s): lacks mandated ROUTER INFORMATION parameters",
			__func__);

	/* Ok, let's try to originate an LSA */
	if (OspfRI.scope == OSPF_OPAQUE_AS_LSA)
		rc = ospf_router_info_lsa_originate_as(arg);
	else
		rc = ospf_router_info_lsa_originate_area(arg);

	return rc;
}

static struct ospf_lsa *ospf_router_info_lsa_refresh(struct ospf_lsa *lsa)
{
	struct ospf_ri_area_info *ai = NULL;
	struct ospf_lsa *new = NULL;
	struct ospf *top;

	if (!OspfRI.enabled) {
		/*
		 * This LSA must have flushed before due to ROUTER INFORMATION
		 * status change.
		 * It seems a slip among routers in the routing domain.
		 */
		zlog_info("RI (%s): ROUTER INFORMATION is disabled now.",
			  __func__);
		lsa->data->ls_age =
			htons(OSPF_LSA_MAXAGE); /* Flush it anyway. */
	}

	/* Verify that the Router Information ID is supported */
	if (GET_OPAQUE_ID(ntohl(lsa->data->id.s_addr)) != 0) {
		flog_warn(
			EC_OSPF_LSA,
			"RI (%s): Unsupported Router Information ID",
			__func__);
		return NULL;
	}

	/* Process LSA depending of the flooding scope */
	if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) {
		/* Get context AREA context */
		ai = lookup_by_area(lsa->area);
		if (ai == NULL) {
			flog_warn(
				EC_OSPF_LSA,
				"RI (%s): No associated Area", __func__);
			return NULL;
		}
		/* Flush LSA, if the lsa's age reached to MaxAge. */
		if (IS_LSA_MAXAGE(lsa)) {
			UNSET_FLAG(ai->flags, RIFLG_LSA_ENGAGED);
			ospf_opaque_lsa_flush_schedule(lsa);
			return NULL;
		}
		/* Create new Opaque-LSA/ROUTER INFORMATION instance. */
		new = ospf_router_info_lsa_new(ai->area);
		new->data->ls_seqnum = lsa_seqnum_increment(lsa);
		new->vrf_id = lsa->vrf_id;
		/* Install this LSA into LSDB. */
		/* Given "lsa" will be freed in the next function. */
		top = ospf_lookup_by_vrf_id(lsa->vrf_id);
		if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) {
			flog_warn(EC_OSPF_LSA_INSTALL_FAILURE,
				  "RI (%s): ospf_lsa_install() ?", __func__);
			ospf_lsa_unlock(&new);
			return new;
		}
		/* Flood updated LSA through AREA */
		ospf_flood_through_area(ai->area, NULL /*nbr */, new);

	} else { /* AS Flooding scope */
		/* Flush LSA, if the lsa's age reached to MaxAge. */
		if (IS_LSA_MAXAGE(lsa)) {
			UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED);
			ospf_opaque_lsa_flush_schedule(lsa);
			return NULL;
		}
		/* Create new Opaque-LSA/ROUTER INFORMATION instance. */
		new = ospf_router_info_lsa_new(NULL);
		new->data->ls_seqnum = lsa_seqnum_increment(lsa);
		new->vrf_id = lsa->vrf_id;
		/* Install this LSA into LSDB. */
		/* Given "lsa" will be freed in the next function. */
		top = ospf_lookup_by_vrf_id(lsa->vrf_id);
		if (ospf_lsa_install(top, NULL /*oi */, new) == NULL) {
			flog_warn(EC_OSPF_LSA_INSTALL_FAILURE,
				  "RI (%s): ospf_lsa_install() ?", __func__);
			ospf_lsa_unlock(&new);
			return new;
		}
		/* Flood updated LSA through AS */
		ospf_flood_through_as(top, NULL /*nbr */, new);
	}

	/* Debug logging. */
	if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) {
		zlog_debug(
			"LSA[Type%d:%s]: Refresh Opaque-LSA/ROUTER INFORMATION",
			new->data->type, inet_ntoa(new->data->id));
		ospf_lsa_header_dump(new->data);
	}

	return new;
}

static void ospf_router_info_lsa_schedule(struct ospf_ri_area_info *ai,
					  enum lsa_opcode opcode)
{
	struct ospf_lsa lsa;
	struct lsa_header lsah;
	struct ospf *top;
	uint32_t tmp;

	memset(&lsa, 0, sizeof(lsa));
	memset(&lsah, 0, sizeof(lsah));

	zlog_debug("RI (%s): LSA schedule %s%s%s", __func__,
		   opcode == REORIGINATE_THIS_LSA ? "Re-Originate" : "",
		   opcode == REFRESH_THIS_LSA ? "Refresh" : "",
		   opcode == FLUSH_THIS_LSA ? "Flush" : "");

	/* Check LSA flags state coherence and collect area information */
	if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA) {
		if ((ai == NULL) || (ai->area == NULL)) {
			flog_warn(
				EC_OSPF_LSA,
				"RI (%s): Router Info is Area scope flooding but area is not set",
				__func__);
				return;
		}

		if (!CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)
		    && (opcode != REORIGINATE_THIS_LSA))
			return;

		if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED)
		    && (opcode == REORIGINATE_THIS_LSA))
			opcode = REFRESH_THIS_LSA;

		lsa.area = ai->area;
		top = ai->area->ospf;
	} else {
		if (!CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED)
		    && (opcode != REORIGINATE_THIS_LSA))
			return;

		if (CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED)
		    && (opcode == REORIGINATE_THIS_LSA))
			opcode = REFRESH_THIS_LSA;

		top = ospf_lookup_by_vrf_id(VRF_DEFAULT);
		lsa.area = NULL;
	}

	lsa.data = &lsah;
	lsah.type = OspfRI.scope;

	/* LSA ID is set to 0 for the Router Information. See RFC 4970 */
	tmp = SET_OPAQUE_LSID(OPAQUE_TYPE_ROUTER_INFORMATION_LSA, 0);
	lsah.id.s_addr = htonl(tmp);

	switch (opcode) {
	case REORIGINATE_THIS_LSA:
		if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA)
			ospf_opaque_lsa_reoriginate_schedule(
				(void *)ai->area, OSPF_OPAQUE_AREA_LSA,
				OPAQUE_TYPE_ROUTER_INFORMATION_LSA);
		else
			ospf_opaque_lsa_reoriginate_schedule(
				(void *)top, OSPF_OPAQUE_AS_LSA,
				OPAQUE_TYPE_ROUTER_INFORMATION_LSA);
		break;
	case REFRESH_THIS_LSA:
		ospf_opaque_lsa_refresh_schedule(&lsa);
		break;
	case FLUSH_THIS_LSA:
		if (OspfRI.scope == OSPF_OPAQUE_AREA_LSA)
			UNSET_FLAG(ai->flags, RIFLG_LSA_ENGAGED);
		else
			UNSET_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED);
		ospf_opaque_lsa_flush_schedule(&lsa);
		break;
	}

	return;
}

/* Callback to handle Segment Routing information */
static int ospf_router_info_lsa_update(struct ospf_lsa *lsa)
{

	/* Sanity Check */
	if (lsa == NULL) {
		flog_warn(EC_OSPF_LSA, "RI (%s): Abort! LSA is NULL",
			  __func__);
		return -1;
	}

	/* Process only Opaque LSA */
	if ((lsa->data->type != OSPF_OPAQUE_AREA_LSA)
	    && (lsa->data->type != OSPF_OPAQUE_AS_LSA))
		return 0;

	/* Process only Router Information LSA */
	if (GET_OPAQUE_TYPE(ntohl(lsa->data->id.s_addr))
	    != OPAQUE_TYPE_ROUTER_INFORMATION_LSA)
		return 0;

	/* Check if it is not my LSA */
	if (IS_LSA_SELF(lsa))
		return 0;

	/* Check if Router Info & Segment Routing are enable */
	if (!OspfRI.enabled || !OspfRI.sr_info.enabled)
		return 0;

	/* Call Segment Routing LSA update or deletion */
	if (!IS_LSA_MAXAGE(lsa))
		ospf_sr_ri_lsa_update(lsa);
	else
		ospf_sr_ri_lsa_delete(lsa);

	return 0;
}

/*------------------------------------------------------------------------*
 * Followings are vty session control functions.
 *------------------------------------------------------------------------*/

static uint16_t show_vty_router_cap(struct vty *vty, struct tlv_header *tlvh)
{
	struct ri_tlv_router_cap *top = (struct ri_tlv_router_cap *)tlvh;

	if (vty != NULL)
		vty_out(vty, "  Router Capabilities: 0x%x\n",
			ntohl(top->value));
	else
		zlog_debug("    Router Capabilities: 0x%x", ntohl(top->value));

	return TLV_SIZE(tlvh);
}

static uint16_t show_vty_pce_subtlv_address(struct vty *vty,
					    struct tlv_header *tlvh)
{
	struct ri_pce_subtlv_address *top =
		(struct ri_pce_subtlv_address *)tlvh;

	if (ntohs(top->address.type) == PCE_ADDRESS_TYPE_IPV4) {
		if (vty != NULL)
			vty_out(vty, "  PCE Address: %s\n",
				inet_ntoa(top->address.value));
		else
			zlog_debug("    PCE Address: %s",
				   inet_ntoa(top->address.value));
	} else {
		/* TODO: Add support to IPv6 with inet_ntop() */
		if (vty != NULL)
			vty_out(vty, "  PCE Address: 0x%x\n",
				ntohl(top->address.value.s_addr));
		else
			zlog_debug("    PCE Address: 0x%x",
				   ntohl(top->address.value.s_addr));
	}

	return TLV_SIZE(tlvh);
}

static uint16_t show_vty_pce_subtlv_path_scope(struct vty *vty,
					       struct tlv_header *tlvh)
{
	struct ri_pce_subtlv_path_scope *top =
		(struct ri_pce_subtlv_path_scope *)tlvh;

	if (vty != NULL)
		vty_out(vty, "  PCE Path Scope: 0x%x\n", ntohl(top->value));
	else
		zlog_debug("    PCE Path Scope: 0x%x", ntohl(top->value));

	return TLV_SIZE(tlvh);
}

static uint16_t show_vty_pce_subtlv_domain(struct vty *vty,
					   struct tlv_header *tlvh)
{
	struct ri_pce_subtlv_domain *top = (struct ri_pce_subtlv_domain *)tlvh;
	struct in_addr tmp;

	if (ntohs(top->type) == PCE_DOMAIN_TYPE_AREA) {
		tmp.s_addr = top->value;
		if (vty != NULL)
			vty_out(vty, "  PCE domain Area: %s\n", inet_ntoa(tmp));
		else
			zlog_debug("    PCE domain Area: %s", inet_ntoa(tmp));
	} else {
		if (vty != NULL)
			vty_out(vty, "  PCE domain AS: %d\n",
				ntohl(top->value));
		else
			zlog_debug("    PCE domain AS: %d", ntohl(top->value));
	}
	return TLV_SIZE(tlvh);
}

static uint16_t show_vty_pce_subtlv_neighbor(struct vty *vty,
					     struct tlv_header *tlvh)
{

	struct ri_pce_subtlv_neighbor *top =
		(struct ri_pce_subtlv_neighbor *)tlvh;
	struct in_addr tmp;

	if (ntohs(top->type) == PCE_DOMAIN_TYPE_AREA) {
		tmp.s_addr = top->value;
		if (vty != NULL)
			vty_out(vty, "  PCE neighbor Area: %s\n",
				inet_ntoa(tmp));
		else
			zlog_debug("    PCE neighbor Area: %s", inet_ntoa(tmp));
	} else {
		if (vty != NULL)
			vty_out(vty, "  PCE neighbor AS: %d\n",
				ntohl(top->value));
		else
			zlog_debug("    PCE neighbor AS: %d",
				   ntohl(top->value));
	}
	return TLV_SIZE(tlvh);
}

static uint16_t show_vty_pce_subtlv_cap_flag(struct vty *vty,
					     struct tlv_header *tlvh)
{
	struct ri_pce_subtlv_cap_flag *top =
		(struct ri_pce_subtlv_cap_flag *)tlvh;

	if (vty != NULL)
		vty_out(vty, "  PCE Capabilities Flag: 0x%x\n",
			ntohl(top->value));
	else
		zlog_debug("    PCE Capabilities Flag: 0x%x",
			   ntohl(top->value));

	return TLV_SIZE(tlvh);
}

static uint16_t show_vty_unknown_tlv(struct vty *vty, struct tlv_header *tlvh)
{
	if (vty != NULL)
		vty_out(vty, "  Unknown TLV: [type(0x%x), length(0x%x)]\n",
			ntohs(tlvh->type), ntohs(tlvh->length));
	else
		zlog_debug("    Unknown TLV: [type(0x%x), length(0x%x)]",
			   ntohs(tlvh->type), ntohs(tlvh->length));

	return TLV_SIZE(tlvh);
}

static uint16_t show_vty_pce_info(struct vty *vty, struct tlv_header *ri,
				  uint32_t total)
{
	struct tlv_header *tlvh;
	uint16_t sum = 0;

	for (tlvh = ri; sum < total; tlvh = TLV_HDR_NEXT(tlvh)) {
		switch (ntohs(tlvh->type)) {
		case RI_PCE_SUBTLV_ADDRESS:
			sum += show_vty_pce_subtlv_address(vty, tlvh);
			break;
		case RI_PCE_SUBTLV_PATH_SCOPE:
			sum += show_vty_pce_subtlv_path_scope(vty, tlvh);
			break;
		case RI_PCE_SUBTLV_DOMAIN:
			sum += show_vty_pce_subtlv_domain(vty, tlvh);
			break;
		case RI_PCE_SUBTLV_NEIGHBOR:
			sum += show_vty_pce_subtlv_neighbor(vty, tlvh);
			break;
		case RI_PCE_SUBTLV_CAP_FLAG:
			sum += show_vty_pce_subtlv_cap_flag(vty, tlvh);
			break;
		default:
			sum += show_vty_unknown_tlv(vty, tlvh);
			break;
		}
	}
	return sum;
}

/* Display Segment Routing Algorithm TLV information */
static uint16_t show_vty_sr_algorithm(struct vty *vty, struct tlv_header *tlvh)
{
	struct ri_sr_tlv_sr_algorithm *algo =
		(struct ri_sr_tlv_sr_algorithm *)tlvh;
	int i;

	if (vty != NULL) {
		vty_out(vty, "  Segment Routing Algorithm TLV:\n");
		for (i = 0; i < ntohs(algo->header.length); i++) {
			switch (algo->value[i]) {
			case 0:
				vty_out(vty, "    Algorithm %d: SPF\n", i);
				break;
			case 1:
				vty_out(vty, "    Algorithm %d: Strict SPF\n",
					i);
				break;
			default:
				vty_out(vty,
					"  Algorithm %d: Unknown value %d\n", i,
					algo->value[i]);
				break;
			}
		}
	}

	else {
		zlog_debug("  Segment Routing Algorithm TLV:");
		for (i = 0; i < ntohs(algo->header.length); i++)
			switch (algo->value[i]) {
			case 0:
				zlog_debug("    Algorithm %d: SPF", i);
				break;
			case 1:
				zlog_debug("    Algorithm %d: Strict SPF", i);
				break;
			default:
				zlog_debug("    Algorithm %d: Unknown value %d",
					   i, algo->value[i]);
				break;
			}
	}

	return TLV_SIZE(tlvh);
}

/* Display Segment Routing SID/Label Range TLV information */
static uint16_t show_vty_sr_range(struct vty *vty, struct tlv_header *tlvh)
{
	struct ri_sr_tlv_sid_label_range *range =
		(struct ri_sr_tlv_sid_label_range *)tlvh;

	if (vty != NULL) {
		vty_out(vty,
			"  Segment Routing %s Range TLV:\n"
			"    Range Size = %d\n"
			"    SID Label = %d\n\n",
			ntohs(range->header.type) == RI_SR_TLV_SRGB_LABEL_RANGE
				? "Global"
				: "Local",
			GET_RANGE_SIZE(ntohl(range->size)),
			GET_LABEL(ntohl(range->lower.value)));
	} else {
		zlog_debug(
			"  Segment Routing %s Range TLV:\n"
			"    Range Size = %d\n"
			"    SID Label = %d\n\n",
			ntohs(range->header.type) == RI_SR_TLV_SRGB_LABEL_RANGE
				? "Global"
				: "Local",
			GET_RANGE_SIZE(ntohl(range->size)),
			GET_LABEL(ntohl(range->lower.value)));
	}

	return TLV_SIZE(tlvh);
}

/* Display Segment Routing Maximum Stack Depth TLV information */
static uint16_t show_vty_sr_msd(struct vty *vty, struct tlv_header *tlvh)
{
	struct ri_sr_tlv_node_msd *msd = (struct ri_sr_tlv_node_msd *)tlvh;

	if (vty != NULL) {
		vty_out(vty,
			"  Segment Routing MSD TLV:\n"
			"    Node Maximum Stack Depth = %d\n",
			msd->value);
	} else {
		zlog_debug(
			"  Segment Routing MSD TLV:\n"
			"    Node Maximum Stack Depth = %d\n",
			msd->value);
	}

	return TLV_SIZE(tlvh);
}

static void ospf_router_info_show_info(struct vty *vty, struct ospf_lsa *lsa)
{
	struct lsa_header *lsah = lsa->data;
	struct tlv_header *tlvh;
	uint16_t length = 0, sum = 0;

	/* Initialize TLV browsing */
	length = ntohs(lsah->length) - OSPF_LSA_HEADER_SIZE;

	for (tlvh = TLV_HDR_TOP(lsah); sum < length;
	     tlvh = TLV_HDR_NEXT(tlvh)) {
		switch (ntohs(tlvh->type)) {
		case RI_TLV_CAPABILITIES:
			sum += show_vty_router_cap(vty, tlvh);
			break;
		case RI_TLV_PCE:
			tlvh++;
			sum += TLV_HDR_SIZE;
			sum += show_vty_pce_info(vty, tlvh, length - sum);
			break;
		case RI_SR_TLV_SR_ALGORITHM:
			sum += show_vty_sr_algorithm(vty, tlvh);
			break;
		case RI_SR_TLV_SRGB_LABEL_RANGE:
		case RI_SR_TLV_SRLB_LABEL_RANGE:
			sum += show_vty_sr_range(vty, tlvh);
			break;
		case RI_SR_TLV_NODE_MSD:
			sum += show_vty_sr_msd(vty, tlvh);
			break;

		default:
			sum += show_vty_unknown_tlv(vty, tlvh);
			break;
		}
	}

	return;
}

static void ospf_router_info_config_write_router(struct vty *vty)
{
	struct ospf_pce_info *pce = &OspfRI.pce_info;
	struct listnode *node;
	struct ri_pce_subtlv_domain *domain;
	struct ri_pce_subtlv_neighbor *neighbor;
	struct in_addr tmp;

	if (!OspfRI.enabled)
		return;

	if (OspfRI.scope == OSPF_OPAQUE_AS_LSA)
		vty_out(vty, " router-info as\n");
	else
		vty_out(vty, " router-info area\n");

	if (OspfRI.pce_info.enabled) {

		if (pce->pce_address.header.type != 0)
			vty_out(vty, "  pce address %s\n",
				inet_ntoa(pce->pce_address.address.value));

		if (pce->pce_cap_flag.header.type != 0)
			vty_out(vty, "  pce flag 0x%x\n",
				ntohl(pce->pce_cap_flag.value));

		for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) {
			if (domain->header.type != 0) {
				if (domain->type == PCE_DOMAIN_TYPE_AREA) {
					tmp.s_addr = domain->value;
					vty_out(vty, "  pce domain area %s\n",
						inet_ntoa(tmp));
				} else {
					vty_out(vty, "  pce domain as %d\n",
						ntohl(domain->value));
				}
			}
		}

		for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) {
			if (neighbor->header.type != 0) {
				if (neighbor->type == PCE_DOMAIN_TYPE_AREA) {
					tmp.s_addr = neighbor->value;
					vty_out(vty, "  pce neighbor area %s\n",
						inet_ntoa(tmp));
				} else {
					vty_out(vty, "  pce neighbor as %d\n",
						ntohl(neighbor->value));
				}
			}
		}

		if (pce->pce_scope.header.type != 0)
			vty_out(vty, "  pce scope 0x%x\n",
				ntohl(OspfRI.pce_info.pce_scope.value));
	}
	return;
}

/*------------------------------------------------------------------------*
 * Followings are vty command functions.
 *------------------------------------------------------------------------*/
/* Simple wrapper schedule RI LSA action in function of the scope */
static void ospf_router_info_schedule(enum lsa_opcode opcode)
{
	struct listnode *node, *nnode;
	struct ospf_ri_area_info *ai;

	if (OspfRI.scope == OSPF_OPAQUE_AS_LSA) {
		if (CHECK_FLAG(OspfRI.as_flags, RIFLG_LSA_ENGAGED))
			ospf_router_info_lsa_schedule(NULL, opcode);
		else if (opcode == REORIGINATE_THIS_LSA)
			ospf_router_info_lsa_schedule(NULL, opcode);
	} else {
		for (ALL_LIST_ELEMENTS(OspfRI.area_info, node, nnode, ai)) {
			if (CHECK_FLAG(ai->flags, RIFLG_LSA_ENGAGED))
				ospf_router_info_lsa_schedule(ai, opcode);
		}
	}
}

DEFUN (router_info,
       router_info_area_cmd,
       "router-info <as|area [A.B.C.D]>",
       OSPF_RI_STR
       "Enable the Router Information functionality with AS flooding scope\n"
       "Enable the Router Information functionality with Area flooding scope\n"
       "OSPF area ID in IP format (deprecated)\n")
{
	int idx_mode = 1;
	uint8_t scope;

	if (OspfRI.enabled)
		return CMD_SUCCESS;

	/* Check and get Area value if present */
	if (strncmp(argv[idx_mode]->arg, "as", 2) == 0)
		scope = OSPF_OPAQUE_AS_LSA;
	else
		scope = OSPF_OPAQUE_AREA_LSA;

	/* First start to register Router Information callbacks */
	if (!OspfRI.registered && (ospf_router_info_register(scope)) != 0) {
		vty_out(vty,
			"%% Unable to register Router Information callbacks.");
		flog_err(
			EC_OSPF_INIT_FAIL,
			"RI (%s): Unable to register Router Information callbacks. Abort!",
			__func__);
		return CMD_WARNING_CONFIG_FAILED;
	}

	OspfRI.enabled = true;

	if (IS_DEBUG_OSPF_EVENT)
		zlog_debug("RI-> Router Information (%s flooding): OFF -> ON",
			   OspfRI.scope == OSPF_OPAQUE_AREA_LSA ? "Area"
								: "AS");

	/*
	 * Following code is intended to handle two cases;
	 *
	 * 1) Router Information was disabled at startup time, but now become
	 * enabled.
	 * 2) Router Information was once enabled then disabled, and now enabled
	 * again.
	 */

	initialize_params(&OspfRI);

	/* Originate or Refresh RI LSA if already engaged */
	ospf_router_info_schedule(REORIGINATE_THIS_LSA);
	return CMD_SUCCESS;
}


DEFUN (no_router_info,
       no_router_info_cmd,
       "no router-info",
       NO_STR
       "Disable the Router Information functionality\n")
{

	if (!OspfRI.enabled)
		return CMD_SUCCESS;

	if (IS_DEBUG_OSPF_EVENT)
		zlog_debug("RI-> Router Information: ON -> OFF");

	ospf_router_info_schedule(FLUSH_THIS_LSA);

	OspfRI.enabled = false;

	return CMD_SUCCESS;
}

static int ospf_ri_enabled(struct vty *vty)
{
	if (OspfRI.enabled)
		return 1;

	if (vty)
		vty_out(vty, "%% OSPF RI is not turned on\n");

	return 0;
}

DEFUN (pce_address,
       pce_address_cmd,
       "pce address A.B.C.D",
       PCE_STR
       "Stable IP address of the PCE\n"
       "PCE address in IPv4 address format\n")
{
	int idx_ipv4 = 2;
	struct in_addr value;
	struct ospf_pce_info *pi = &OspfRI.pce_info;

	if (!ospf_ri_enabled(vty))
		return CMD_WARNING_CONFIG_FAILED;

	if (!inet_aton(argv[idx_ipv4]->arg, &value)) {
		vty_out(vty, "Please specify PCE Address by A.B.C.D\n");
		return CMD_WARNING_CONFIG_FAILED;
	}

	if (ntohs(pi->pce_address.header.type) == 0
	    || ntohl(pi->pce_address.address.value.s_addr)
		       != ntohl(value.s_addr)) {

		set_pce_address(value, pi);

		/* Refresh RI LSA if already engaged */
		ospf_router_info_schedule(REFRESH_THIS_LSA);
	}

	return CMD_SUCCESS;
}

DEFUN (no_pce_address,
       no_pce_address_cmd,
       "no pce address [A.B.C.D]",
       NO_STR
       PCE_STR
       "Disable PCE address\n"
       "PCE address in IPv4 address format\n")
{

	unset_param(&OspfRI.pce_info.pce_address);

	/* Refresh RI LSA if already engaged */
	ospf_router_info_schedule(REFRESH_THIS_LSA);

	return CMD_SUCCESS;
}

DEFUN (pce_path_scope,
       pce_path_scope_cmd,
       "pce scope BITPATTERN",
       PCE_STR
       "Path scope visibilities of the PCE for path computation\n"
       "32-bit Hexadecimal value\n")
{
	int idx_bitpattern = 2;
	uint32_t scope;
	struct ospf_pce_info *pi = &OspfRI.pce_info;

	if (!ospf_ri_enabled(vty))
		return CMD_WARNING_CONFIG_FAILED;

	if (sscanf(argv[idx_bitpattern]->arg, "0x%x", &scope) != 1) {
		vty_out(vty, "pce_path_scope: fscanf: %s\n",
			safe_strerror(errno));
		return CMD_WARNING_CONFIG_FAILED;
	}

	if (ntohl(pi->pce_scope.header.type) == 0
	    || scope != pi->pce_scope.value) {
		set_pce_path_scope(scope, pi);

		/* Refresh RI LSA if already engaged */
		ospf_router_info_schedule(REFRESH_THIS_LSA);
	}

	return CMD_SUCCESS;
}

DEFUN (no_pce_path_scope,
       no_pce_path_scope_cmd,
       "no pce scope [BITPATTERN]",
       NO_STR
       PCE_STR
       "Disable PCE path scope\n"
       "32-bit Hexadecimal value\n")
{

	unset_param(&OspfRI.pce_info.pce_address);

	/* Refresh RI LSA if already engaged */
	ospf_router_info_schedule(REFRESH_THIS_LSA);

	return CMD_SUCCESS;
}

DEFUN (pce_domain,
       pce_domain_cmd,
       "pce domain as (0-65535)",
       PCE_STR
       "Configure PCE domain AS number\n"
       "AS number where the PCE as visibilities for path computation\n"
       "AS number in decimal <0-65535>\n")
{
	int idx_number = 3;

	uint32_t as;
	struct ospf_pce_info *pce = &OspfRI.pce_info;
	struct listnode *node;
	struct ri_pce_subtlv_domain *domain;

	if (!ospf_ri_enabled(vty))
		return CMD_WARNING_CONFIG_FAILED;

	if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) {
		vty_out(vty, "pce_domain: fscanf: %s\n", safe_strerror(errno));
		return CMD_WARNING_CONFIG_FAILED;
	}

	/* Check if the domain is not already in the domain list */
	for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) {
		if (ntohl(domain->header.type) == 0 && as == domain->value)
			return CMD_SUCCESS;
	}

	/* Create new domain if not found */
	set_pce_domain(PCE_DOMAIN_TYPE_AS, as, pce);

	/* Refresh RI LSA if already engaged */
	ospf_router_info_schedule(REFRESH_THIS_LSA);

	return CMD_SUCCESS;
}

DEFUN (no_pce_domain,
       no_pce_domain_cmd,
       "no pce domain as (0-65535)",
       NO_STR
       PCE_STR
       "Disable PCE domain AS number\n"
       "AS number where the PCE as visibilities for path computation\n"
       "AS number in decimal <0-65535>\n")
{
	int idx_number = 4;

	uint32_t as;
	struct ospf_pce_info *pce = &OspfRI.pce_info;

	if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) {
		vty_out(vty, "no_pce_domain: fscanf: %s\n",
			safe_strerror(errno));
		return CMD_WARNING_CONFIG_FAILED;
	}

	/* Unset corresponding PCE domain */
	unset_pce_domain(PCE_DOMAIN_TYPE_AS, as, pce);

	/* Refresh RI LSA if already engaged */
	ospf_router_info_schedule(REFRESH_THIS_LSA);

	return CMD_SUCCESS;
}

DEFUN (pce_neigbhor,
       pce_neighbor_cmd,
       "pce neighbor as (0-65535)",
       PCE_STR
       "Configure PCE neighbor domain AS number\n"
       "AS number of PCE neighbors\n"
       "AS number in decimal <0-65535>\n")
{
	int idx_number = 3;

	uint32_t as;
	struct ospf_pce_info *pce = &OspfRI.pce_info;
	struct listnode *node;
	struct ri_pce_subtlv_neighbor *neighbor;

	if (!ospf_ri_enabled(vty))
		return CMD_WARNING_CONFIG_FAILED;

	if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) {
		vty_out(vty, "pce_neighbor: fscanf: %s\n",
			safe_strerror(errno));
		return CMD_WARNING_CONFIG_FAILED;
	}

	/* Check if the domain is not already in the domain list */
	for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) {
		if (ntohl(neighbor->header.type) == 0 && as == neighbor->value)
			return CMD_SUCCESS;
	}

	/* Create new domain if not found */
	set_pce_neighbor(PCE_DOMAIN_TYPE_AS, as, pce);

	/* Refresh RI LSA if already engaged */
	ospf_router_info_schedule(REFRESH_THIS_LSA);

	return CMD_SUCCESS;
}

DEFUN (no_pce_neighbor,
       no_pce_neighbor_cmd,
       "no pce neighbor as (0-65535)",
       NO_STR
       PCE_STR
       "Disable PCE neighbor AS number\n"
       "AS number of PCE neighbor\n"
       "AS number in decimal <0-65535>\n")
{
	int idx_number = 4;

	uint32_t as;
	struct ospf_pce_info *pce = &OspfRI.pce_info;

	if (sscanf(argv[idx_number]->arg, "%" SCNu32, &as) != 1) {
		vty_out(vty, "no_pce_neighbor: fscanf: %s\n",
			safe_strerror(errno));
		return CMD_WARNING_CONFIG_FAILED;
	}

	/* Unset corresponding PCE domain */
	unset_pce_neighbor(PCE_DOMAIN_TYPE_AS, as, pce);

	/* Refresh RI LSA if already engaged */
	ospf_router_info_schedule(REFRESH_THIS_LSA);

	return CMD_SUCCESS;
}

DEFUN (pce_cap_flag,
       pce_cap_flag_cmd,
       "pce flag BITPATTERN",
       PCE_STR
       "Capabilities of the PCE for path computation\n"
       "32-bit Hexadecimal value\n")
{
	int idx_bitpattern = 2;

	uint32_t cap;
	struct ospf_pce_info *pce = &OspfRI.pce_info;

	if (!ospf_ri_enabled(vty))
		return CMD_WARNING_CONFIG_FAILED;

	if (sscanf(argv[idx_bitpattern]->arg, "0x%x", &cap) != 1) {
		vty_out(vty, "pce_cap_flag: fscanf: %s\n",
			safe_strerror(errno));
		return CMD_WARNING_CONFIG_FAILED;
	}

	if (ntohl(pce->pce_cap_flag.header.type) == 0
	    || cap != pce->pce_cap_flag.value) {
		set_pce_cap_flag(cap, pce);

		/* Refresh RI LSA if already engaged */
		ospf_router_info_schedule(REFRESH_THIS_LSA);
	}

	return CMD_SUCCESS;
}

DEFUN (no_pce_cap_flag,
       no_pce_cap_flag_cmd,
       "no pce flag",
       NO_STR
       PCE_STR
       "Disable PCE capabilities\n")
{

	unset_param(&OspfRI.pce_info.pce_cap_flag);

	/* Refresh RI LSA if already engaged */
	ospf_router_info_schedule(REFRESH_THIS_LSA);

	return CMD_SUCCESS;
}

DEFUN (show_ip_ospf_router_info,
       show_ip_ospf_router_info_cmd,
       "show ip ospf router-info",
       SHOW_STR
       IP_STR
       OSPF_STR
       "Router Information\n")
{

	if (OspfRI.enabled) {
		vty_out(vty, "--- Router Information parameters ---\n");
		show_vty_router_cap(vty, &OspfRI.router_cap.header);
	} else {
		if (vty != NULL)
			vty_out(vty,
				"  Router Information is disabled on this router\n");
	}
	return CMD_SUCCESS;
}

DEFUN (show_ip_opsf_router_info_pce,
       show_ip_ospf_router_info_pce_cmd,
       "show ip ospf router-info pce",
       SHOW_STR
       IP_STR
       OSPF_STR
       "Router Information\n"
       "PCE information\n")
{

	struct ospf_pce_info *pce = &OspfRI.pce_info;
	struct listnode *node;
	struct ri_pce_subtlv_domain *domain;
	struct ri_pce_subtlv_neighbor *neighbor;

	if ((OspfRI.enabled) && (OspfRI.pce_info.enabled)) {
		vty_out(vty, "--- PCE parameters ---\n");

		if (pce->pce_address.header.type != 0)
			show_vty_pce_subtlv_address(vty,
						    &pce->pce_address.header);

		if (pce->pce_scope.header.type != 0)
			show_vty_pce_subtlv_path_scope(vty,
						       &pce->pce_scope.header);

		for (ALL_LIST_ELEMENTS_RO(pce->pce_domain, node, domain)) {
			if (domain->header.type != 0)
				show_vty_pce_subtlv_domain(vty,
							   &domain->header);
		}

		for (ALL_LIST_ELEMENTS_RO(pce->pce_neighbor, node, neighbor)) {
			if (neighbor->header.type != 0)
				show_vty_pce_subtlv_neighbor(vty,
							     &neighbor->header);
		}

		if (pce->pce_cap_flag.header.type != 0)
			show_vty_pce_subtlv_cap_flag(vty,
						     &pce->pce_cap_flag.header);

	} else {
		vty_out(vty, "  PCE info is disabled on this router\n");
	}

	return CMD_SUCCESS;
}

/* Install new CLI commands */
static void ospf_router_info_register_vty(void)
{
	install_element(VIEW_NODE, &show_ip_ospf_router_info_cmd);
	install_element(VIEW_NODE, &show_ip_ospf_router_info_pce_cmd);

	install_element(OSPF_NODE, &router_info_area_cmd);
	install_element(OSPF_NODE, &no_router_info_cmd);
	install_element(OSPF_NODE, &pce_address_cmd);
	install_element(OSPF_NODE, &no_pce_address_cmd);
	install_element(OSPF_NODE, &pce_path_scope_cmd);
	install_element(OSPF_NODE, &no_pce_path_scope_cmd);
	install_element(OSPF_NODE, &pce_domain_cmd);
	install_element(OSPF_NODE, &no_pce_domain_cmd);
	install_element(OSPF_NODE, &pce_neighbor_cmd);
	install_element(OSPF_NODE, &no_pce_neighbor_cmd);
	install_element(OSPF_NODE, &pce_cap_flag_cmd);
	install_element(OSPF_NODE, &no_pce_cap_flag_cmd);

	return;
}
