/*
 * Static NHT code.
 * Copyright (C) 2018 Cumulus Networks, Inc.
 *               Donald Sharp
 *
 * This program 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 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; see the file COPYING; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */
#include <zebra.h>

#include "prefix.h"
#include "table.h"
#include "vrf.h"
#include "nexthop.h"
#include "srcdest_table.h"

#include "static_vrf.h"
#include "static_routes.h"
#include "static_zebra.h"
#include "static_nht.h"

static void static_nht_update_path(struct route_node *rn,
				   struct static_path *pn, struct prefix *nhp,
				   uint32_t nh_num, vrf_id_t nh_vrf_id,
				   struct vrf *vrf, safi_t safi)
{
	struct static_nexthop *nh;

	frr_each(static_nexthop_list, &pn->nexthop_list, nh) {
		if (nh->nh_vrf_id != nh_vrf_id)
			continue;

		if (nh->type != STATIC_IPV4_GATEWAY
		    && nh->type != STATIC_IPV4_GATEWAY_IFNAME
		    && nh->type != STATIC_IPV6_GATEWAY
		    && nh->type != STATIC_IPV6_GATEWAY_IFNAME)
			continue;

		if (nhp->family == AF_INET
		    && nhp->u.prefix4.s_addr == nh->addr.ipv4.s_addr)
			nh->nh_valid = !!nh_num;

		if (nhp->family == AF_INET6
		    && memcmp(&nhp->u.prefix6, &nh->addr.ipv6, 16) == 0)
			nh->nh_valid = !!nh_num;

		if (nh->state == STATIC_START)
			static_zebra_route_add(rn, pn, safi, true);
	}
}

static void static_nht_update_safi(struct prefix *sp, struct prefix *nhp,
				   uint32_t nh_num, afi_t afi, safi_t safi,
				   struct vrf *vrf, vrf_id_t nh_vrf_id)
{
	struct route_table *stable;
	struct static_vrf *svrf;
	struct route_node *rn;
	struct static_path *pn;
	struct static_route_info *si;

	svrf = vrf->info;
	if (!svrf)
		return;

	stable = static_vrf_static_table(afi, safi, svrf);
	if (!stable)
		return;

	if (sp) {
		rn = srcdest_rnode_lookup(stable, sp, NULL);
		if (rn && rn->info) {
			si = static_route_info_from_rnode(rn);
			frr_each(static_path_list, &si->path_list, pn) {
				static_nht_update_path(rn, pn, nhp, nh_num,
						       nh_vrf_id, vrf, safi);
			}
			route_unlock_node(rn);
		}
		return;
	}

	for (rn = route_top(stable); rn; rn = route_next(rn)) {
		si = static_route_info_from_rnode(rn);
		if (!si)
			continue;
		frr_each(static_path_list, &si->path_list, pn) {
			static_nht_update_path(rn, pn, nhp, nh_num, nh_vrf_id,
					       vrf, safi);
		}
	}
}

void static_nht_update(struct prefix *sp, struct prefix *nhp,
		       uint32_t nh_num, afi_t afi, vrf_id_t nh_vrf_id)
{

	struct vrf *vrf;

	RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
		static_nht_update_safi(sp, nhp, nh_num, afi, SAFI_UNICAST,
				       vrf, nh_vrf_id);
		static_nht_update_safi(sp, nhp, nh_num, afi, SAFI_MULTICAST,
				       vrf, nh_vrf_id);
	}
}

static void static_nht_reset_start_safi(struct prefix *nhp, afi_t afi,
					safi_t safi, struct vrf *vrf,
					vrf_id_t nh_vrf_id)
{
	struct static_vrf *svrf;
	struct route_table *stable;
	struct static_nexthop *nh;
	struct static_path *pn;
	struct route_node *rn;
	struct static_route_info *si;

	svrf = vrf->info;
	if (!svrf)
		return;

	stable = static_vrf_static_table(afi, safi, svrf);
	if (!stable)
		return;

	for (rn = route_top(stable); rn; rn = route_next(rn)) {
		si = static_route_info_from_rnode(rn);
		if (!si)
			continue;
		frr_each(static_path_list, &si->path_list, pn) {
			frr_each(static_nexthop_list, &pn->nexthop_list, nh) {
				if (nh->nh_vrf_id != nh_vrf_id)
					continue;

				if (nhp->family == AF_INET
				    && nhp->u.prefix4.s_addr
					       != nh->addr.ipv4.s_addr)
					continue;

				if (nhp->family == AF_INET6
				    && memcmp(&nhp->u.prefix6, &nh->addr.ipv6,
					      16)
					       != 0)
					continue;

				/*
				 * We've been told that a nexthop we
				 * depend on has changed in some manner,
				 * so reset the state machine to allow
				 * us to start over.
				 */
				nh->state = STATIC_START;
			}
		}
	}
}

void static_nht_reset_start(struct prefix *nhp, afi_t afi, vrf_id_t nh_vrf_id)
{
	struct vrf *vrf;

	RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
		static_nht_reset_start_safi(nhp, afi, SAFI_UNICAST,
					    vrf, nh_vrf_id);
		static_nht_reset_start_safi(nhp, afi, SAFI_MULTICAST,
					    vrf, nh_vrf_id);
	}
}

static void static_nht_mark_state_safi(struct prefix *sp, afi_t afi,
				       safi_t safi, struct vrf *vrf,
				       enum static_install_states state)
{
	struct static_vrf *svrf;
	struct route_table *stable;
	struct route_node *rn;
	struct static_nexthop *nh;
	struct static_path *pn;
	struct static_route_info *si;

	svrf = vrf->info;
	if (!svrf)
		return;

	stable = static_vrf_static_table(afi, safi, svrf);
	if (!stable)
		return;

	rn = srcdest_rnode_lookup(stable, sp, NULL);
	if (!rn)
		return;
	si = rn->info;
	if (si) {
		frr_each(static_path_list, &si->path_list, pn) {
			frr_each(static_nexthop_list, &pn->nexthop_list, nh) {
				nh->state = state;
			}
		}
	}

	route_unlock_node(rn);
}

void static_nht_mark_state(struct prefix *sp, vrf_id_t vrf_id,
			   enum static_install_states state)
{
	struct vrf *vrf;

	afi_t afi = AFI_IP;

	if (sp->family == AF_INET6)
		afi = AFI_IP6;

	vrf = vrf_lookup_by_id(vrf_id);
	if (!vrf || !vrf->info)
		return;

	static_nht_mark_state_safi(sp, afi, SAFI_UNICAST, vrf, state);
	static_nht_mark_state_safi(sp, afi, SAFI_MULTICAST, vrf, state);
}
