/**
 * ospf6_bfd.c: IPv6 OSPF BFD handling routines
 *
 * @copyright Copyright (C) 2015 Cumulus Networks, Inc.
 *
 * 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 "command.h"
#include "linklist.h"
#include "memory.h"
#include "prefix.h"
#include "thread.h"
#include "buffer.h"
#include "stream.h"
#include "zclient.h"
#include "vty.h"
#include "table.h"
#include "bfd.h"
#include "if.h"
#include "ospf6d.h"
#include "ospf6_message.h"
#include "ospf6_neighbor.h"
#include "ospf6_interface.h"
#include "ospf6_route.h"
#include "ospf6_zebra.h"
#include "ospf6_bfd.h"

extern struct zclient *zclient;

/*
 * ospf6_bfd_info_free - Free BFD info structure
 */
void ospf6_bfd_info_free(void **bfd_info)
{
	bfd_info_free((struct bfd_info **)bfd_info);
}

/*
 * ospf6_bfd_show_info - Show BFD info structure
 */
void ospf6_bfd_show_info(struct vty *vty, void *bfd_info, int param_only)
{
	if (param_only)
		bfd_show_param(vty, bfd_info, 1, 0, 0, NULL);
	else
		bfd_show_info(vty, bfd_info, 0, 1, 0, NULL);
}

/*
 * ospf6_bfd_reg_dereg_nbr - Register/Deregister a neighbor with BFD through
 *                           zebra for starting/stopping the monitoring of
 *                           the neighbor rechahability.
 */
void ospf6_bfd_reg_dereg_nbr(struct ospf6_neighbor *on, int command)
{
	struct ospf6_interface *oi = on->ospf6_if;
	struct interface *ifp = oi->interface;
	struct bfd_info *bfd_info;
	char src[64];
	int cbit;

	if (!oi->bfd_info || !on->bfd_info)
		return;
	bfd_info = (struct bfd_info *)oi->bfd_info;

	if (IS_OSPF6_DEBUG_ZEBRA(SEND)) {
		inet_ntop(AF_INET6, &on->linklocal_addr, src, sizeof(src));
		zlog_debug("%s nbr (%s) with BFD",
			   bfd_get_command_dbg_str(command), src);
	}

	cbit = CHECK_FLAG(bfd_info->flags, BFD_FLAG_BFD_CBIT_ON);

	bfd_peer_sendmsg(zclient, bfd_info, AF_INET6, &on->linklocal_addr,
			 on->ospf6_if->linklocal_addr, ifp->name, 0, 0, cbit,
			 command, 0, ifp->vrf_id);

	if (command == ZEBRA_BFD_DEST_DEREGISTER)
		bfd_info_free((struct bfd_info **)&on->bfd_info);
}

/*
 * ospf6_bfd_trigger_event - Neighbor is registered/deregistered with BFD when
 *                           neighbor state is changed to/from 2way.
 */
void ospf6_bfd_trigger_event(struct ospf6_neighbor *on, int old_state,
			     int state)
{
	if ((old_state < OSPF6_NEIGHBOR_TWOWAY)
	    && (state >= OSPF6_NEIGHBOR_TWOWAY))
		ospf6_bfd_reg_dereg_nbr(on, ZEBRA_BFD_DEST_REGISTER);
	else if ((old_state >= OSPF6_NEIGHBOR_TWOWAY)
		 && (state < OSPF6_NEIGHBOR_TWOWAY))
		ospf6_bfd_reg_dereg_nbr(on, ZEBRA_BFD_DEST_DEREGISTER);
}

/*
 * ospf6_bfd_reg_dereg_all_nbr - Register/Deregister all neighbors associated
 *                               with a interface with BFD through
 *                               zebra for starting/stopping the monitoring of
 *                               the neighbor rechahability.
 */
static void ospf6_bfd_reg_dereg_all_nbr(struct ospf6_interface *oi, int command)
{
	struct ospf6_neighbor *on;
	struct listnode *node;

	for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, on)) {
		if (command == ZEBRA_BFD_DEST_REGISTER)
			ospf6_bfd_info_nbr_create(oi, on);

		if (on->state < OSPF6_NEIGHBOR_TWOWAY) {
			if (command == ZEBRA_BFD_DEST_DEREGISTER)
				bfd_info_free(
					(struct bfd_info **)&on->bfd_info);
			continue;
		}

		ospf6_bfd_reg_dereg_nbr(on, command);
	}
}

/*
 * ospf6_bfd_nbr_replay - Replay all the neighbors that have BFD enabled
 *                        to zebra
 */
static int ospf6_bfd_nbr_replay(ZAPI_CALLBACK_ARGS)
{
	struct vrf *vrf = vrf_lookup_by_id(vrf_id);
	struct listnode *node;
	struct interface *ifp;
	struct ospf6_interface *oi;
	struct ospf6_neighbor *on;
	char dst[64];

	if (IS_OSPF6_DEBUG_ZEBRA(RECV))
		zlog_debug("Zebra: BFD Dest replay request");

	/* Send the client registration */
	bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id);

	/* Replay the neighbor, if BFD is enabled on the interface*/
	FOR_ALL_INTERFACES (vrf, ifp) {
		oi = (struct ospf6_interface *)ifp->info;

		if (!oi || !oi->bfd_info)
			continue;

		for (ALL_LIST_ELEMENTS_RO(oi->neighbor_list, node, on)) {
			if (on->state < OSPF6_NEIGHBOR_TWOWAY)
				continue;

			if (IS_OSPF6_DEBUG_ZEBRA(SEND)) {
				inet_ntop(AF_INET6, &on->linklocal_addr, dst,
					  sizeof(dst));
				zlog_debug("Replaying nbr (%s) to BFD", dst);
			}

			ospf6_bfd_reg_dereg_nbr(on, ZEBRA_BFD_DEST_UPDATE);
		}
	}
	return 0;
}

/*
 * ospf6_bfd_interface_dest_update - Find the neighbor for which the BFD status
 *                                   has changed and bring down the neighbor
 *                                   connectivity if BFD down is received.
 */
static int ospf6_bfd_interface_dest_update(ZAPI_CALLBACK_ARGS)
{
	struct interface *ifp;
	struct ospf6_interface *oi;
	struct ospf6_neighbor *on;
	struct prefix dp;
	struct prefix sp;
	struct listnode *node, *nnode;
	char dst[64];
	int status;
	int old_status;
	struct bfd_info *bfd_info;
	struct timeval tv;

	ifp = bfd_get_peer_info(zclient->ibuf, &dp, &sp, &status,
				NULL, vrf_id);

	if ((ifp == NULL) || (dp.family != AF_INET6))
		return 0;

	if (IS_OSPF6_DEBUG_ZEBRA(RECV)) {
		char buf[PREFIX2STR_BUFFER];
		prefix2str(&dp, buf, sizeof(buf));
		zlog_debug("Zebra: interface %s bfd destination %s %s",
			   ifp->name, buf, bfd_get_status_str(status));
	}


	oi = (struct ospf6_interface *)ifp->info;
	if (!oi || !oi->bfd_info)
		return 0;

	for (ALL_LIST_ELEMENTS(oi->neighbor_list, node, nnode, on)) {
		if (memcmp(&(on->linklocal_addr), &dp.u.prefix6,
			   sizeof(struct in6_addr)))
			continue;

		if (IS_OSPF6_DEBUG_NEIGHBOR(EVENT)) {
			inet_ntop(AF_INET6, &on->linklocal_addr, dst,
				  sizeof(dst));
			zlog_debug("[%s:%s]: BFD %s", ifp->name, dst,
				   bfd_get_status_str(status));
		}

		if (!on->bfd_info)
			continue;

		bfd_info = (struct bfd_info *)on->bfd_info;
		if (bfd_info->status == status)
			continue;

		old_status = bfd_info->status;
		BFD_SET_CLIENT_STATUS(bfd_info->status, status);
		monotime(&tv);
		bfd_info->last_update = tv.tv_sec;

		if ((status == BFD_STATUS_DOWN)
		    && (old_status == BFD_STATUS_UP)) {
			THREAD_OFF(on->inactivity_timer);
			thread_add_event(master, inactivity_timer, on, 0, NULL);
		}
	}

	return 0;
}

/*
 * ospf6_bfd_info_nbr_create - Create/update BFD information for a neighbor.
 */
void ospf6_bfd_info_nbr_create(struct ospf6_interface *oi,
			       struct ospf6_neighbor *on)
{
	struct bfd_info *oi_bfd_info;
	struct bfd_info *on_bfd_info;

	if (!oi->bfd_info)
		return;

	oi_bfd_info = (struct bfd_info *)oi->bfd_info;

	if (!on->bfd_info)
		on->bfd_info = bfd_info_create();

	on_bfd_info = (struct bfd_info *)on->bfd_info;
	on_bfd_info->detect_mult = oi_bfd_info->detect_mult;
	on_bfd_info->desired_min_tx = oi_bfd_info->desired_min_tx;
	on_bfd_info->required_min_rx = oi_bfd_info->required_min_rx;
}

/*
 * ospf6_bfd_write_config - Write the interface BFD configuration.
 */
void ospf6_bfd_write_config(struct vty *vty, struct ospf6_interface *oi)
{
#if HAVE_BFDD == 0
	struct bfd_info *bfd_info;
#endif /* ! HAVE_BFDD */

	if (!oi->bfd_info)
		return;

#if HAVE_BFDD == 0
	bfd_info = (struct bfd_info *)oi->bfd_info;

	if (CHECK_FLAG(bfd_info->flags, BFD_FLAG_PARAM_CFG))
		vty_out(vty, " ipv6 ospf6 bfd %d %d %d\n",
			bfd_info->detect_mult, bfd_info->required_min_rx,
			bfd_info->desired_min_tx);
	else
#endif /* ! HAVE_BFDD */
		vty_out(vty, " ipv6 ospf6 bfd\n");
}

/*
 * ospf6_bfd_if_param_set - Set the configured BFD paramter values for
 *                            interface.
 */
static void ospf6_bfd_if_param_set(struct ospf6_interface *oi, uint32_t min_rx,
				   uint32_t min_tx, uint8_t detect_mult,
				   int defaults)
{
	int command = 0;

	bfd_set_param((struct bfd_info **)&(oi->bfd_info), min_rx, min_tx,
		      detect_mult, NULL, defaults, &command);
	if (command)
		ospf6_bfd_reg_dereg_all_nbr(oi, command);
}

DEFUN (ipv6_ospf6_bfd,
       ipv6_ospf6_bfd_cmd,
       "ipv6 ospf6 bfd",
       IP6_STR
       OSPF6_STR
       "Enables BFD support\n"
       )
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct ospf6_interface *oi;
	assert(ifp);

	oi = (struct ospf6_interface *)ifp->info;
	if (oi == NULL)
		oi = ospf6_interface_create(ifp);
	assert(oi);

	ospf6_bfd_if_param_set(oi, BFD_DEF_MIN_RX, BFD_DEF_MIN_TX,
			       BFD_DEF_DETECT_MULT, 1);
	return CMD_SUCCESS;
}

#if HAVE_BFDD > 0
DEFUN_HIDDEN(
#else
DEFUN(
#endif /* HAVE_BFDD */
       ipv6_ospf6_bfd_param,
       ipv6_ospf6_bfd_param_cmd,
       "ipv6 ospf6 bfd (2-255) (50-60000) (50-60000)",
       IP6_STR
       OSPF6_STR
       "Enables BFD support\n"
       "Detect Multiplier\n"
       "Required min receive interval\n"
       "Desired min transmit interval\n")
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	int idx_number = 3;
	int idx_number_2 = 4;
	int idx_number_3 = 5;
	struct ospf6_interface *oi;
	uint32_t rx_val;
	uint32_t tx_val;
	uint8_t dm_val;
	int ret;

	assert(ifp);

	oi = (struct ospf6_interface *)ifp->info;
	if (oi == NULL)
		oi = ospf6_interface_create(ifp);
	assert(oi);

	if ((ret = bfd_validate_param(
		     vty, argv[idx_number]->arg, argv[idx_number_2]->arg,
		     argv[idx_number_3]->arg, &dm_val, &rx_val, &tx_val))
	    != CMD_SUCCESS)
		return ret;

	ospf6_bfd_if_param_set(oi, rx_val, tx_val, dm_val, 0);

	return CMD_SUCCESS;
}

DEFUN (no_ipv6_ospf6_bfd,
       no_ipv6_ospf6_bfd_cmd,
       "no ipv6 ospf6 bfd",
       NO_STR
       IP6_STR
       OSPF6_STR
       "Disables BFD support\n"
       )
{
	VTY_DECLVAR_CONTEXT(interface, ifp);
	struct ospf6_interface *oi;
	assert(ifp);

	oi = (struct ospf6_interface *)ifp->info;
	if (oi == NULL)
		oi = ospf6_interface_create(ifp);
	assert(oi);

	if (oi->bfd_info) {
		ospf6_bfd_reg_dereg_all_nbr(oi, ZEBRA_BFD_DEST_DEREGISTER);
		bfd_info_free((struct bfd_info **)&(oi->bfd_info));
	}

	return CMD_SUCCESS;
}

void ospf6_bfd_init(void)
{
	bfd_gbl_init();

	/* Initialize BFD client functions */
	zclient->interface_bfd_dest_update = ospf6_bfd_interface_dest_update;
	zclient->bfd_dest_replay = ospf6_bfd_nbr_replay;

	/* Install BFD command */
	install_element(INTERFACE_NODE, &ipv6_ospf6_bfd_cmd);
	install_element(INTERFACE_NODE, &ipv6_ospf6_bfd_param_cmd);
	install_element(INTERFACE_NODE, &no_ipv6_ospf6_bfd_cmd);
}
