/*
 * FRR filter northbound implementation.
 *
 * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
 *                    Rafael Zalamena
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

#include "zebra.h"

#include "lib/northbound.h"
#include "lib/prefix.h"
#include "lib/printfrr.h"

#include "lib/filter.h"
#include "lib/plist.h"
#include "lib/plist_int.h"
#include "lib/routemap.h"

/* Helper function. */
static void acl_notify_route_map(struct access_list *acl, int route_map_event)
{
	switch (route_map_event) {
	case RMAP_EVENT_FILTER_ADDED:
		if (acl->master->add_hook)
			(*acl->master->add_hook)(acl);
		break;
	case RMAP_EVENT_FILTER_DELETED:
		if (acl->master->delete_hook)
			(*acl->master->delete_hook)(acl);
		break;
	}

	route_map_notify_dependencies(acl->name, route_map_event);
}

static enum nb_error prefix_list_length_validate(struct nb_cb_modify_args *args)
{
	int type = yang_dnode_get_enum(args->dnode, "../../type");
	const char *xpath_le = NULL, *xpath_ge = NULL;
	struct prefix p;
	uint8_t le, ge;

	if (type == YPLT_IPV4) {
		yang_dnode_get_prefix(&p, args->dnode, "../ipv4-prefix");
		xpath_le = "../ipv4-prefix-length-lesser-or-equal";
		xpath_ge = "../ipv4-prefix-length-greater-or-equal";
	} else {
		yang_dnode_get_prefix(&p, args->dnode, "../ipv6-prefix");
		xpath_le = "../ipv6-prefix-length-lesser-or-equal";
		xpath_ge = "../ipv6-prefix-length-greater-or-equal";
	}

	/*
	 * Check rule:
	 * prefix length <= le.
	 */
	if (yang_dnode_exists(args->dnode, xpath_le)) {
		le = yang_dnode_get_uint8(args->dnode, xpath_le);
		if (p.prefixlen > le)
			goto log_and_fail;
	}

	/*
	 * Check rule:
	 * prefix length <= ge.
	 */
	if (yang_dnode_exists(args->dnode, xpath_ge)) {
		ge = yang_dnode_get_uint8(args->dnode, xpath_ge);
		if (p.prefixlen > ge)
			goto log_and_fail;
	}

	/*
	 * Check rule:
	 * ge <= le.
	 */
	if (yang_dnode_exists(args->dnode, xpath_le)
	    && yang_dnode_exists(args->dnode, xpath_ge)) {
		le = yang_dnode_get_uint8(args->dnode, xpath_le);
		ge = yang_dnode_get_uint8(args->dnode, xpath_ge);
		if (ge > le)
			goto log_and_fail;
	}

	return NB_OK;

log_and_fail:
	snprintfrr(
		args->errmsg, args->errmsg_len,
		"Invalid prefix range for %pFX: Make sure that mask length <= ge <= le",
		&p);
	return NB_ERR_VALIDATION;
}

/**
 * Sets prefix list entry to blank value.
 *
 * \param[out] ple prefix list entry to modify.
 */
static void prefix_list_entry_set_empty(struct prefix_list_entry *ple)
{
	ple->any = false;
	memset(&ple->prefix, 0, sizeof(ple->prefix));
	ple->ge = 0;
	ple->le = 0;
}

/**
 * Unsets the cisco style rule for addresses so it becomes disabled (the
 * equivalent of setting: `0.0.0.0/32`).
 *
 * \param addr address part.
 * \param mask mask part.
 */
static void cisco_unset_addr_mask(struct in_addr *addr, struct in_addr *mask)
{
	addr->s_addr = INADDR_ANY;
	mask->s_addr = CISCO_BIN_HOST_WILDCARD_MASK;
}

/*
 * XPath: /frr-filter:lib/access-list
 */
static int lib_access_list_create(struct nb_cb_create_args *args)
{
	struct access_list *acl = NULL;
	const char *acl_name;
	int type;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	type = yang_dnode_get_enum(args->dnode, "./type");
	acl_name = yang_dnode_get_string(args->dnode, "./name");

	switch (type) {
	case YALT_IPV4:
		acl = access_list_get(AFI_IP, acl_name);
		break;
	case YALT_IPV6:
		acl = access_list_get(AFI_IP6, acl_name);
		break;
	case YALT_MAC:
		acl = access_list_get(AFI_L2VPN, acl_name);
		break;
	}

	nb_running_set_entry(args->dnode, acl);

	return NB_OK;
}

static int lib_access_list_destroy(struct nb_cb_destroy_args *args)
{
	struct access_master *am;
	struct access_list *acl;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	acl = nb_running_unset_entry(args->dnode);
	am = acl->master;
	if (am->delete_hook)
		am->delete_hook(acl);

	access_list_delete(acl);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/remark
 */
static int lib_access_list_remark_modify(struct nb_cb_modify_args *args)
{
	struct access_list *acl;
	const char *remark;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	acl = nb_running_get_entry(args->dnode, NULL, true);
	if (acl->remark)
		XFREE(MTYPE_TMP, acl->remark);

	remark = yang_dnode_get_string(args->dnode, NULL);
	acl->remark = XSTRDUP(MTYPE_TMP, remark);

	return NB_OK;
}

static int
lib_access_list_remark_destroy(struct nb_cb_destroy_args *args)
{
	struct access_list *acl;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	acl = nb_running_get_entry(args->dnode, NULL, true);
	if (acl->remark)
		XFREE(MTYPE_TMP, acl->remark);

	return NB_OK;
}


/*
 * XPath: /frr-filter:lib/access-list/entry
 */
static int lib_access_list_entry_create(struct nb_cb_create_args *args)
{
	struct access_list *acl;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = filter_new();
	f->seq = yang_dnode_get_uint32(args->dnode, "./sequence");

	acl = nb_running_get_entry(args->dnode, NULL, true);
	f->acl = acl;
	access_list_filter_add(acl, f);
	nb_running_set_entry(args->dnode, f);

	return NB_OK;
}

static int lib_access_list_entry_destroy(struct nb_cb_destroy_args *args)
{
	struct access_list *acl;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_unset_entry(args->dnode);
	acl = f->acl;
	access_list_filter_delete(acl, f);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/action
 */
static int
lib_access_list_entry_action_modify(struct nb_cb_modify_args *args)
{
	const char *filter_type;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	filter_type = yang_dnode_get_string(args->dnode, NULL);
	if (strcmp(filter_type, "permit") == 0)
		f->type = FILTER_PERMIT;
	else
		f->type = FILTER_DENY;

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/ipv4-prefix
 */
static int
lib_access_list_entry_ipv4_prefix_modify(struct nb_cb_modify_args *args)
{
	struct filter_zebra *fz;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	f->cisco = 0;
	fz = &f->u.zfilter;
	yang_dnode_get_prefix(&fz->prefix, args->dnode, NULL);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

static int
lib_access_list_entry_ipv4_prefix_destroy(struct nb_cb_destroy_args *args)
{
	struct filter_zebra *fz;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fz = &f->u.zfilter;
	memset(&fz->prefix, 0, sizeof(fz->prefix));

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/ipv4-exact-match
 */
static int
lib_access_list_entry_ipv4_exact_match_modify(struct nb_cb_modify_args *args)
{
	struct filter_zebra *fz;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fz = &f->u.zfilter;
	fz->exact = yang_dnode_get_bool(args->dnode, NULL);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

static int
lib_access_list_entry_ipv4_exact_match_destroy(struct nb_cb_destroy_args *args)
{
	struct filter_zebra *fz;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fz = &f->u.zfilter;
	fz->exact = 0;

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/host
 */
static int
lib_access_list_entry_host_modify(struct nb_cb_modify_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	f->cisco = 1;
	fc = &f->u.cfilter;
	yang_dnode_get_ipv4(&fc->addr, args->dnode, NULL);
	fc->addr_mask.s_addr = CISCO_BIN_HOST_WILDCARD_MASK;

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

static int
lib_access_list_entry_host_destroy(struct nb_cb_destroy_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fc = &f->u.cfilter;
	cisco_unset_addr_mask(&fc->addr, &fc->addr_mask);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/network/address
 */
static int
lib_access_list_entry_network_address_modify(struct nb_cb_modify_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	f->cisco = 1;
	fc = &f->u.cfilter;
	yang_dnode_get_ipv4(&fc->addr, args->dnode, NULL);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/network/mask
 */
static int
lib_access_list_entry_network_mask_modify(struct nb_cb_modify_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	f->cisco = 1;
	fc = &f->u.cfilter;
	yang_dnode_get_ipv4(&fc->addr_mask, args->dnode, NULL);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/source-any
 */
static int
lib_access_list_entry_source_any_create(struct nb_cb_create_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	f->cisco = 1;
	fc = &f->u.cfilter;
	fc->addr.s_addr = INADDR_ANY;
	fc->addr_mask.s_addr = CISCO_BIN_ANY_WILDCARD_MASK;

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

static int
lib_access_list_entry_source_any_destroy(struct nb_cb_destroy_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fc = &f->u.cfilter;
	cisco_unset_addr_mask(&fc->addr, &fc->addr_mask);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/destination-host
 */
static int lib_access_list_entry_destination_host_modify(
	struct nb_cb_modify_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fc = &f->u.cfilter;
	fc->extended = 1;
	yang_dnode_get_ipv4(&fc->mask, args->dnode, NULL);
	fc->mask_mask.s_addr = CISCO_BIN_HOST_WILDCARD_MASK;

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

static int lib_access_list_entry_destination_host_destroy(
	struct nb_cb_destroy_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fc = &f->u.cfilter;
	fc->extended = 0;
	cisco_unset_addr_mask(&fc->mask, &fc->mask_mask);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/destination-network/address
 */
static int lib_access_list_entry_destination_network_address_modify(
	struct nb_cb_modify_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fc = &f->u.cfilter;
	fc->extended = 1;
	yang_dnode_get_ipv4(&fc->mask, args->dnode, NULL);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/destination-network/mask
 */
static int lib_access_list_entry_destination_network_mask_modify(
	struct nb_cb_modify_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fc = &f->u.cfilter;
	fc->extended = 1;
	yang_dnode_get_ipv4(&fc->mask_mask, args->dnode, NULL);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/destination-any
 */
static int lib_access_list_entry_destination_any_create(
	struct nb_cb_create_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fc = &f->u.cfilter;
	fc->extended = 1;
	fc->mask.s_addr = INADDR_ANY;
	fc->mask_mask.s_addr = CISCO_BIN_ANY_WILDCARD_MASK;

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

static int lib_access_list_entry_destination_any_destroy(
	struct nb_cb_destroy_args *args)
{
	struct filter_cisco *fc;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fc = &f->u.cfilter;
	fc->extended = 0;
	cisco_unset_addr_mask(&fc->mask, &fc->mask_mask);

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/access-list/entry/any
 */
static int lib_access_list_entry_any_create(struct nb_cb_create_args *args)
{
	struct filter_zebra *fz;
	struct filter *f;
	int type;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	f->cisco = 0;
	fz = &f->u.zfilter;
	memset(&fz->prefix, 0, sizeof(fz->prefix));

	type = yang_dnode_get_enum(args->dnode, "../../type");
	switch (type) {
	case YALT_IPV4:
		fz->prefix.family = AF_INET;
		break;
	case YALT_IPV6:
		fz->prefix.family = AF_INET6;
		break;
	case YALT_MAC:
		fz->prefix.family = AF_ETHERNET;
		break;
	}

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_ADDED);

	return NB_OK;
}

static int lib_access_list_entry_any_destroy(struct nb_cb_destroy_args *args)
{
	struct filter_zebra *fz;
	struct filter *f;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	f = nb_running_get_entry(args->dnode, NULL, true);
	fz = &f->u.zfilter;
	fz->prefix.family = 0;

	acl_notify_route_map(f->acl, RMAP_EVENT_FILTER_DELETED);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/prefix-list
 */
static int lib_prefix_list_create(struct nb_cb_create_args *args)
{
	struct prefix_list *pl = NULL;
	const char *name;
	int type;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	type = yang_dnode_get_enum(args->dnode, "./type");
	name = yang_dnode_get_string(args->dnode, "./name");
	switch (type) {
	case 0: /* ipv4 */
		pl = prefix_list_get(AFI_IP, 0, name);
		break;
	case 1: /* ipv6 */
		pl = prefix_list_get(AFI_IP6, 0, name);
		break;
	}

	nb_running_set_entry(args->dnode, pl);

	return NB_OK;
}

static int lib_prefix_list_destroy(struct nb_cb_destroy_args *args)
{
	struct prefix_list *pl;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	pl = nb_running_unset_entry(args->dnode);
	prefix_list_delete(pl);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/prefix-list/remark
 */
static int lib_prefix_list_remark_modify(struct nb_cb_modify_args *args)
{
	struct prefix_list *pl;
	const char *remark;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	pl = nb_running_get_entry(args->dnode, NULL, true);
	if (pl->desc)
		XFREE(MTYPE_TMP, pl->desc);

	remark = yang_dnode_get_string(args->dnode, NULL);
	pl->desc = XSTRDUP(MTYPE_TMP, remark);

	return NB_OK;
}

static int lib_prefix_list_remark_destroy(struct nb_cb_destroy_args *args)
{
	struct prefix_list *pl;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	pl = nb_running_get_entry(args->dnode, NULL, true);
	if (pl->desc)
		XFREE(MTYPE_TMP, pl->desc);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/prefix-list/entry
 */
static int lib_prefix_list_entry_create(struct nb_cb_create_args *args)
{
	struct prefix_list_entry *ple;
	struct prefix_list *pl;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	pl = nb_running_get_entry(args->dnode, NULL, true);
	ple = prefix_list_entry_new();
	ple->pl = pl;
	ple->seq = yang_dnode_get_uint32(args->dnode, "./sequence");
	prefix_list_entry_set_empty(ple);
	nb_running_set_entry(args->dnode, ple);

	return NB_OK;
}

static int lib_prefix_list_entry_destroy(struct nb_cb_destroy_args *args)
{
	struct prefix_list_entry *ple;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_unset_entry(args->dnode);
	if (ple->installed)
		prefix_list_entry_delete2(ple);
	else
		prefix_list_entry_free(ple);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/prefix-list/entry/action
 */
static int lib_prefix_list_entry_action_modify(struct nb_cb_modify_args *args)
{
	struct prefix_list_entry *ple;
	int action_type;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	action_type = yang_dnode_get_enum(args->dnode, NULL);
	if (action_type == YPLA_PERMIT)
		ple->type = PREFIX_PERMIT;
	else
		ple->type = PREFIX_DENY;

	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix
 */
static int
lib_prefix_list_entry_ipv4_prefix_modify(struct nb_cb_modify_args *args)
{
	struct prefix_list_entry *ple;
	struct prefix p;

	if (args->event == NB_EV_VALIDATE) {
		/*
		 * TODO: validate prefix_entry_dup_check() passes.
		 *
		 * This needs to be implemented using YANG lyd_node
		 * navigation, because the `priv` data structures are not
		 * available at `NB_EV_VALIDATE` phase. An easier
		 * alternative would be mark `ipvx-prefix` as unique
		 * (see RFC 7950, Section 7.8.3. The list "unique" Statement).
		 */
		return NB_OK;
	}

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	yang_dnode_get_prefix(&ple->prefix, args->dnode, NULL);

	/* Apply mask and correct original address if necessary. */
	prefix_copy(&p, &ple->prefix);
	apply_mask(&p);
	if (!prefix_same(&ple->prefix, &p)) {
		zlog_info("%s: bad network %pFX correcting it to %pFX",
			  __func__, &ple->prefix, &p);
		prefix_copy(&ple->prefix, &p);
	}


	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

static int
lib_prefix_list_entry_ipv4_prefix_destroy(struct nb_cb_destroy_args *args)
{
	struct prefix_list_entry *ple;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	memset(&ple->prefix, 0, sizeof(ple->prefix));

	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix-length-greater-or-equal
 */
static int lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_modify(
	struct nb_cb_modify_args *args)
{
	struct prefix_list_entry *ple;

	if (args->event == NB_EV_VALIDATE &&
	    prefix_list_length_validate(args) != NB_OK)
		return NB_ERR_VALIDATION;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	ple->ge = yang_dnode_get_uint8(args->dnode, NULL);

	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

static int lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_destroy(
	struct nb_cb_destroy_args *args)
{
	struct prefix_list_entry *ple;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	ple->ge = 0;

	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/prefix-list/entry/ipv4-prefix-length-lesser-or-equal
 */
static int lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_modify(
	struct nb_cb_modify_args *args)
{
	struct prefix_list_entry *ple;

	if (args->event == NB_EV_VALIDATE &&
	    prefix_list_length_validate(args) != NB_OK)
		return NB_ERR_VALIDATION;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	ple->le = yang_dnode_get_uint8(args->dnode, NULL);

	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

static int lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_destroy(
	struct nb_cb_destroy_args *args)
{
	struct prefix_list_entry *ple;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	ple->le = 0;

	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

/*
 * XPath: /frr-filter:lib/prefix-list/entry/any
 */
static int lib_prefix_list_entry_any_create(struct nb_cb_create_args *args)
{
	struct prefix_list_entry *ple;
	int type;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	ple->any = true;

	/* Fill prefix struct from scratch. */
	memset(&ple->prefix, 0, sizeof(ple->prefix));

	type = yang_dnode_get_enum(args->dnode, "../../type");
	switch (type) {
	case YPLT_IPV4:
		ple->prefix.family = AF_INET;
		ple->ge = 0;
		ple->le = IPV4_MAX_BITLEN;
		break;
	case YPLT_IPV6:
		ple->prefix.family = AF_INET6;
		ple->ge = 0;
		ple->le = IPV6_MAX_BITLEN;
		break;
	}

	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

static int lib_prefix_list_entry_any_destroy(struct nb_cb_destroy_args *args)
{
	struct prefix_list_entry *ple;

	if (args->event != NB_EV_APPLY)
		return NB_OK;

	ple = nb_running_get_entry(args->dnode, NULL, true);

	/* Start prefix entry update procedure. */
	prefix_list_entry_update_start(ple);

	prefix_list_entry_set_empty(ple);

	/* Finish prefix entry update procedure. */
	prefix_list_entry_update_finish(ple);

	return NB_OK;
}

/* clang-format off */
const struct frr_yang_module_info frr_filter_info = {
	.name = "frr-filter",
	.nodes = {
		{
			.xpath = "/frr-filter:lib/access-list",
			.cbs = {
				.create = lib_access_list_create,
				.destroy = lib_access_list_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/remark",
			.cbs = {
				.modify = lib_access_list_remark_modify,
				.destroy = lib_access_list_remark_destroy,
				.cli_show = access_list_remark_show,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry",
			.cbs = {
				.create = lib_access_list_entry_create,
				.destroy = lib_access_list_entry_destroy,
				.cli_show = access_list_show,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/action",
			.cbs = {
				.modify = lib_access_list_entry_action_modify,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/ipv4-prefix",
			.cbs = {
				.modify = lib_access_list_entry_ipv4_prefix_modify,
				.destroy = lib_access_list_entry_ipv4_prefix_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/ipv4-exact-match",
			.cbs = {
				.modify = lib_access_list_entry_ipv4_exact_match_modify,
				.destroy = lib_access_list_entry_ipv4_exact_match_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/host",
			.cbs = {
				.modify = lib_access_list_entry_host_modify,
				.destroy = lib_access_list_entry_host_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/network/address",
			.cbs = {
				.modify = lib_access_list_entry_network_address_modify,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/network/mask",
			.cbs = {
				.modify = lib_access_list_entry_network_mask_modify,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/source-any",
			.cbs = {
				.create = lib_access_list_entry_source_any_create,
				.destroy = lib_access_list_entry_source_any_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/destination-host",
			.cbs = {
				.modify = lib_access_list_entry_destination_host_modify,
				.destroy = lib_access_list_entry_destination_host_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/destination-network/address",
			.cbs = {
				.modify = lib_access_list_entry_destination_network_address_modify,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/destination-network/mask",
			.cbs = {
				.modify = lib_access_list_entry_destination_network_mask_modify,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/destination-any",
			.cbs = {
				.create = lib_access_list_entry_destination_any_create,
				.destroy = lib_access_list_entry_destination_any_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/ipv6-prefix",
			.cbs = {
				.modify = lib_access_list_entry_ipv4_prefix_modify,
				.destroy = lib_access_list_entry_ipv4_prefix_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/ipv6-exact-match",
			.cbs = {
				.modify = lib_access_list_entry_ipv4_exact_match_modify,
				.destroy = lib_access_list_entry_ipv4_exact_match_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/mac",
			.cbs = {
				.modify = lib_access_list_entry_ipv4_prefix_modify,
				.destroy = lib_access_list_entry_ipv4_prefix_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/access-list/entry/any",
			.cbs = {
				.create = lib_access_list_entry_any_create,
				.destroy = lib_access_list_entry_any_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list",
			.cbs = {
				.create = lib_prefix_list_create,
				.destroy = lib_prefix_list_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/remark",
			.cbs = {
				.modify = lib_prefix_list_remark_modify,
				.destroy = lib_prefix_list_remark_destroy,
				.cli_show = prefix_list_remark_show,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry",
			.cbs = {
				.create = lib_prefix_list_entry_create,
				.destroy = lib_prefix_list_entry_destroy,
				.cli_show = prefix_list_show,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry/action",
			.cbs = {
				.modify = lib_prefix_list_entry_action_modify,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix",
			.cbs = {
				.modify = lib_prefix_list_entry_ipv4_prefix_modify,
				.destroy = lib_prefix_list_entry_ipv4_prefix_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix-length-greater-or-equal",
			.cbs = {
				.modify = lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_modify,
				.destroy = lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry/ipv4-prefix-length-lesser-or-equal",
			.cbs = {
				.modify = lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_modify,
				.destroy = lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix",
			.cbs = {
				.modify = lib_prefix_list_entry_ipv4_prefix_modify,
				.destroy = lib_prefix_list_entry_ipv4_prefix_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix-length-greater-or-equal",
			.cbs = {
				.modify = lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_modify,
				.destroy = lib_prefix_list_entry_ipv4_prefix_length_greater_or_equal_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry/ipv6-prefix-length-lesser-or-equal",
			.cbs = {
				.modify = lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_modify,
				.destroy = lib_prefix_list_entry_ipv4_prefix_length_lesser_or_equal_destroy,
			}
		},
		{
			.xpath = "/frr-filter:lib/prefix-list/entry/any",
			.cbs = {
				.create = lib_prefix_list_entry_any_create,
				.destroy = lib_prefix_list_entry_any_destroy,
			}
		},
		{
			.xpath = NULL,
		},
	}
};
