/*  Copyright (C) CZ.NIC, z.s.p.o. and contributors
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  For more information, see <https://www.knot-dns.cz/>
 */

#include <assert.h>

#include "contrib/base32hex.h"
#include "knot/dnssec/nsec-chain.h"
#include "knot/dnssec/rrset-sign.h"
#include "knot/dnssec/zone-nsec.h"
#include "knot/dnssec/zone-sign.h"
#include "knot/zone/adjust.h"

void bitmap_add_node_rrsets(dnssec_nsec_bitmap_t *bitmap, const zone_node_t *node,
                            bool exact)
{
	bool deleg = node->flags & NODE_FLAGS_DELEG;
	for (int i = 0; i < node->rrset_count; i++) {
		knot_rrset_t rr = node_rrset_at(node, i);
		if (deleg && (rr.type != KNOT_RRTYPE_NS && rr.type != KNOT_RRTYPE_DS &&
		              rr.type != KNOT_RRTYPE_NSEC)) {
			if (rr.type != KNOT_RRTYPE_RRSIG) {
				continue;
			}
			if (!rrsig_covers_type(&rr, KNOT_RRTYPE_DS) &&
			    !rrsig_covers_type(&rr, KNOT_RRTYPE_NSEC)) {
				continue;
			}
		}
		if (!exact && (rr.type == KNOT_RRTYPE_NSEC || rr.type == KNOT_RRTYPE_RRSIG)) {
			continue;
		}

		dnssec_nsec_bitmap_add(bitmap, rr.type);
	}
}

/* - NSEC chain construction ------------------------------------------------ */

static int create_nsec_base(knot_rrset_t *rrset, knot_dname_t *from_owner,
                            const knot_dname_t *to_owner, uint32_t ttl,
                            size_t bitmap_size, uint8_t **bitmap_writeto)
{
	knot_rrset_init(rrset, from_owner, KNOT_RRTYPE_NSEC, KNOT_CLASS_IN, ttl);

	size_t next_owner_size = knot_dname_size(to_owner);
	size_t rdsize = next_owner_size + bitmap_size;
	uint8_t rdata[rdsize];
	memcpy(rdata, to_owner, next_owner_size);

	int ret = knot_rrset_add_rdata(rrset, rdata, rdsize, NULL);

	assert(ret != KNOT_EOK || rrset->rrs.rdata->len == rdsize);
	*bitmap_writeto = rrset->rrs.rdata->data + next_owner_size;

	return ret;
}

/*!
 * \brief Create NSEC RR set.
 *
 * \param rrset      RRSet to be initialized.
 * \param from       Node that should contain the new RRSet.
 * \param to         Node that should be pointed to from 'from'.
 * \param ttl        Record TTL (SOA's minimum TTL).
 *
 * \return Error code, KNOT_EOK if successful.
 */
static int create_nsec_rrset(knot_rrset_t *rrset, const zone_node_t *from,
                             const knot_dname_t *to, uint32_t ttl)
{
	assert(from);
	assert(to);

	dnssec_nsec_bitmap_t *rr_types = dnssec_nsec_bitmap_new();
	if (!rr_types) {
		return KNOT_ENOMEM;
	}

	bitmap_add_node_rrsets(rr_types, from, false);
	dnssec_nsec_bitmap_add(rr_types, KNOT_RRTYPE_NSEC);
	dnssec_nsec_bitmap_add(rr_types, KNOT_RRTYPE_RRSIG);

	uint8_t *bitmap_write;
	int ret = create_nsec_base(rrset, from->owner, to, ttl,
	                           dnssec_nsec_bitmap_size(rr_types), &bitmap_write);
	if (ret == KNOT_EOK) {
		dnssec_nsec_bitmap_write(rr_types, bitmap_write);
	}
	dnssec_nsec_bitmap_free(rr_types);

	return ret;
}

/*!
 * \brief Connect two nodes by adding a NSEC RR into the first node.
 *
 * Callback function, signature chain_iterate_cb.
 *
 * \param a     First node.
 * \param b     Second node (immediate follower of a).
 * \param data  Pointer to nsec_chain_iterate_data_t holding parameters
 *              including changeset.
 *
 * \return Error code, KNOT_EOK if successful.
 */
static int connect_nsec_nodes(zone_node_t *a, zone_node_t *b,
                              nsec_chain_iterate_data_t *data)
{
	assert(a);
	assert(b);
	assert(data);

	int ret = KNOT_EOK;

	/*!
	 * If the node has no other RRSets than NSEC (and possibly RRSIGs),
	 * or the node is not authoritative,
	 * just remove the NSEC and its RRSIG, they are redundant
	 */
	if (node_rrtype_exists(b, KNOT_RRTYPE_NSEC) &&
	    (b->flags & NODE_FLAGS_NONAUTH || knot_nsec_empty_nsec_and_rrsigs_in_node(b))) {
		ret = knot_nsec_changeset_remove(b, data->update);
		if (ret != KNOT_EOK) {
			return ret;
		}
		// Skip the 'b' node
		return NSEC_NODE_SKIP;
	}

	if (b->rrset_count == 0 || b->flags & NODE_FLAGS_NONAUTH) {
		return NSEC_NODE_SKIP;
	}

	// create new NSEC
	knot_rrset_t new_nsec;
	ret = create_nsec_rrset(&new_nsec, a, b->owner, data->ttl);
	if (ret != KNOT_EOK) {
		return ret;
	}

	knot_rrset_t old_nsec = node_rrset(a, KNOT_RRTYPE_NSEC);

	if (!knot_rrset_empty(&old_nsec)) {
		/* Convert old NSEC to lowercase, just in case it's not. */
		knot_rrset_t *old_nsec_lc = knot_rrset_copy(&old_nsec, NULL);
		ret = knot_rrset_rr_to_canonical(old_nsec_lc);
		if (ret != KNOT_EOK) {
			knot_rrset_free(old_nsec_lc, NULL);
			return ret;
		}

		bool equal = knot_rrset_equal(&new_nsec, old_nsec_lc, true);
		knot_rrset_free(old_nsec_lc, NULL);

		if (equal) {
			// current NSEC is valid, do nothing
			knot_rdataset_clear(&new_nsec.rrs, NULL);
			return KNOT_EOK;
		}

		ret = knot_nsec_changeset_remove(a, data->update);
		if (ret != KNOT_EOK) {
			knot_rdataset_clear(&new_nsec.rrs, NULL);
			return ret;
		}
	}

	// Add new NSEC to the changeset (no matter if old was removed)
	ret = zone_update_add(data->update, &new_nsec);
	knot_rdataset_clear(&new_nsec.rrs, NULL);
	return ret;
}

/*!
 * \brief Replace b's NSEC "next" field with a's, keeping the NSEC bitmap.
 *
 * \param a      Node to take the NSEC "next" field from.
 * \param b      Node to update the NSEC "next" field in.
 * \param data   Contains changeset to be updated.
 *
 * \return KNOT_E*
 */
static int reconnect_nsec_nodes(zone_node_t *a, zone_node_t *b,
                                nsec_chain_iterate_data_t *data)
{
	assert(a);
	assert(b);
	assert(data);

	knot_rrset_t an = node_rrset(a, KNOT_RRTYPE_NSEC);
	assert(!knot_rrset_empty(&an));

	knot_rrset_t bnorig = node_rrset(b, KNOT_RRTYPE_NSEC);
	assert(!knot_rrset_empty(&bnorig));

	size_t b_bitmap_len = knot_nsec_bitmap_len(bnorig.rrs.rdata);

	knot_rrset_t bnnew;
	uint8_t *bitmap_write;
	int ret = create_nsec_base(&bnnew, bnorig.owner, knot_nsec_next(an.rrs.rdata),
	                           bnorig.ttl, b_bitmap_len, &bitmap_write);
	if (ret == KNOT_EOK) {
		memcpy(bitmap_write, knot_nsec_bitmap(bnorig.rrs.rdata), b_bitmap_len);
	}

	ret = zone_update_remove(data->update, &bnorig);
	if (ret == KNOT_EOK) {
		ret = zone_update_add(data->update, &bnnew);
	}

	knot_rdataset_clear(&bnnew.rrs, NULL);
	return ret;
}

static bool node_no_nsec(zone_node_t *node)
{
	return ((node->flags & NODE_FLAGS_DELETED) ||
	        (node->flags & NODE_FLAGS_NONAUTH) ||
	        node->rrset_count == 0);
}

/*!
 * \brief Create or fix the node's NSEC record with correct bitmap.
 *
 * \param node          Node to fix the NSEC bitmap in.
 * \param data_voidp    NSEC creation data.
 *
 * \return KNOT_E*
 */
static int nsec_update_bitmap(zone_node_t *node,
			      nsec_chain_iterate_data_t *data)
{
	if (node_no_nsec(node) || knot_nsec_empty_nsec_and_rrsigs_in_node(node)) {
		return knot_nsec_changeset_remove(node, data->update);
	}

	knot_rrset_t old_nsec = node_rrset(node, KNOT_RRTYPE_NSEC);
	const knot_dname_t *next = knot_rrset_empty(&old_nsec) ?
	                           (const knot_dname_t *)"" :
	                           knot_nsec_next(old_nsec.rrs.rdata);
	knot_rrset_t new_nsec;
	int ret = create_nsec_rrset(&new_nsec, node, next, data->ttl);

	if (ret == KNOT_EOK && !knot_rrset_empty(&old_nsec)) {
		ret = zone_update_remove(data->update, &old_nsec);
	}
	if (ret == KNOT_EOK) {
		ret = zone_update_add(data->update, &new_nsec);
	}
	knot_rdataset_clear(&new_nsec.rrs, NULL);
	return ret;
}

static int nsec_update_bitmaps(zone_tree_t *node_ptrs,
                               nsec_chain_iterate_data_t *data)
{
	zone_tree_delsafe_it_t it = { 0 };
	int ret = zone_tree_delsafe_it_begin(node_ptrs, &it, false);
	if (ret != KNOT_EOK) {
		return ret;
	}
	while (!zone_tree_delsafe_it_finished(&it) && ret == KNOT_EOK) {
		ret = nsec_update_bitmap(zone_tree_delsafe_it_val(&it), data);
		zone_tree_delsafe_it_next(&it);
	}
	zone_tree_delsafe_it_free(&it);
	return ret;
}

static bool node_nsec3_unmatching(const zone_node_t *node, const dnssec_nsec3_params_t *params)
{
	knot_rdataset_t *nsec3 = node_rdataset(node, KNOT_RRTYPE_NSEC3);
	if (nsec3 == NULL || nsec3->count < 1 || params == NULL) {
		return false;
	}
	knot_rdata_t *rdata = nsec3->rdata;
	for (int i = 0; i < nsec3->count; i++) {
		if (knot_nsec3_alg(rdata) == params->algorithm &&
		    knot_nsec3_iters(rdata) == params->iterations &&
		    knot_nsec3_salt_len(rdata) == params->salt.size &&
		    memcmp(knot_nsec3_salt(rdata), params->salt.data, params->salt.size) == 0) {
			return false;
		}
		rdata = knot_rdataset_next(rdata);
	}
	return true;
}

int nsec_check_connect_nodes(zone_node_t *a, zone_node_t *b,
                             nsec_chain_iterate_data_t *data)
{
	if (node_no_nsec(b) || node_nsec3_unmatching(b, data->nsec3_params)) {
		return NSEC_NODE_SKIP;
	}
	knot_rdataset_t *nsec = node_rdataset(a, data->nsec_type);
	if (nsec == NULL || nsec->count != 1) {
		data->update->validation_hint.node = a->owner;
		data->update->validation_hint.rrtype = KNOT_RRTYPE_ANY;
		return KNOT_DNSSEC_ENSEC_CHAIN;
	}
	if (data->nsec_type == KNOT_RRTYPE_NSEC) {
		const knot_dname_t *a_next = knot_nsec_next(nsec->rdata);
		if (!knot_dname_is_case_equal(a_next, b->owner)) {
			data->update->validation_hint.node = a->owner;
			data->update->validation_hint.rrtype = data->nsec_type;
			return KNOT_DNSSEC_ENSEC_CHAIN;
		}
	} else {
		uint8_t next_len = knot_nsec3_next_len(nsec->rdata);
		uint8_t bdecoded[next_len];
		int len = knot_base32hex_decode(b->owner + 1, b->owner[0], bdecoded, next_len);
		if (len != next_len ||
		    memcmp(knot_nsec3_next(nsec->rdata), bdecoded, len) != 0) {
			data->update->validation_hint.node = a->owner;
			data->update->validation_hint.rrtype = data->nsec_type;
			return KNOT_DNSSEC_ENSEC_CHAIN;
		}
	}
	return KNOT_EOK;
}

static zone_node_t *nsec_prev(zone_node_t *node, const dnssec_nsec3_params_t *matching_params)
{
	zone_node_t *res = node;
	do {
		res = node_prev(res);
	} while (res != NULL && ((res->flags & NODE_FLAGS_NONAUTH) ||
	                         res->rrset_count == 0 ||
	                         node_nsec3_unmatching(res, matching_params)));
	assert(res == NULL || !knot_nsec_empty_nsec_and_rrsigs_in_node(res));
	return res;
}

static int nsec_check_prev_next(zone_node_t *node, void *ctx)
{
	if (node_no_nsec(node)) {
		return KNOT_EOK;
	}

	nsec_chain_iterate_data_t *data = ctx;
	int ret = nsec_check_connect_nodes(nsec_prev(node, data->nsec3_params), node, data);
	if (ret == NSEC_NODE_SKIP) {
		return KNOT_EOK;
	}
	if (ret != KNOT_EOK) {
		return ret;
	}

	dnssec_validation_hint_t *hint = &data->update->validation_hint;
	knot_rdataset_t *nsec = node_rdataset(node, data->nsec_type);
	if (nsec == NULL || nsec->count != 1) {
		hint->node = node->owner;
		hint->rrtype = KNOT_RRTYPE_ANY;
		return KNOT_DNSSEC_ENSEC_CHAIN;
	}

	const zone_node_t *nn;
	if (data->nsec_type == KNOT_RRTYPE_NSEC) {
		if (knot_dname_store(hint->next, knot_nsec_next(nsec->rdata)) == 0) {
			return KNOT_EINVAL;
		}
		knot_dname_to_lower(hint->next);
		nn = zone_contents_find_node(data->update->new_cont, hint->next);
	} else {
		ret = knot_nsec3_hash_to_dname(hint->next, sizeof(hint->next),
		                               knot_nsec3_next(nsec->rdata),
		                               knot_nsec3_next_len(nsec->rdata),
		                               data->update->new_cont->apex->owner);
		if (ret != KNOT_EOK) {
			return ret;
		}
		nn = zone_contents_find_nsec3_node(data->update->new_cont, hint->next);
	}
	if (nn == NULL) {
		hint->node = hint->next;
		hint->rrtype = KNOT_RRTYPE_ANY;
		return KNOT_DNSSEC_ENSEC_CHAIN;
	}
	if (nsec_prev((zone_node_t *)nn, data->nsec3_params) != node) {
		hint->node = node->owner;
		hint->rrtype = data->nsec_type;
		return KNOT_DNSSEC_ENSEC_CHAIN;
	}
	return KNOT_EOK;
}

int nsec_check_new_connects(zone_tree_t *tree, nsec_chain_iterate_data_t *data)
{
	return zone_tree_apply(tree, nsec_check_prev_next, data);
}

static int check_subtree_optout(zone_node_t *node, void *ctx)
{
	bool *res = ctx;
	if ((node->flags & NODE_FLAGS_NONAUTH) || !*res) {
		return KNOT_EOK;
	}
	if (node_nsec3_get(node) != NULL &&
	    node_rdataset(node_nsec3_get(node), KNOT_RRTYPE_NSEC3) != NULL) {
		*res = false;
	}
	return KNOT_EOK;
}

static int check_nsec_bitmap(zone_node_t *node, void *ctx)
{
	nsec_chain_iterate_data_t *data = ctx;
	assert((bool)(data->nsec_type == KNOT_RRTYPE_NSEC3) == (bool)(data->nsec3_params != NULL));
	const zone_node_t *nsec_node = node;
	bool shall_no_nsec = node_no_nsec(node);
	if (data->nsec3_params != NULL) {
		if ((node->flags & NODE_FLAGS_DELETED) ||
		    node_rrtype_exists(node, KNOT_RRTYPE_NSEC3)) {
			// this can happen when checking nodes from adjust_ptrs
			return KNOT_EOK;
		}
		nsec_node = node_nsec3_get(node);
		shall_no_nsec = (node->flags & NODE_FLAGS_DELETED) ||
		                (node->flags & NODE_FLAGS_NONAUTH);
	}
	bool may_no_nsec = (data->nsec3_params != NULL && !(node->flags & NODE_FLAGS_SUBTREE_AUTH));
	knot_rdataset_t *nsec = node_rdataset(nsec_node, data->nsec_type);
	if (may_no_nsec && nsec == NULL) {
		int ret = zone_tree_sub_apply(data->update->new_cont->nodes, node->owner,
		                              true, check_subtree_optout, &may_no_nsec);
		if (ret != KNOT_EOK) {
			return ret;
		}
	}
	if ((nsec == NULL || nsec->count != 1) && !shall_no_nsec && !may_no_nsec) {
		data->update->validation_hint.node = (nsec_node == NULL ? node->owner : nsec_node->owner);
		data->update->validation_hint.rrtype = KNOT_RRTYPE_ANY;
		return KNOT_DNSSEC_ENONSEC;
	}
	if (shall_no_nsec && nsec != NULL && nsec->count > 0) {
		data->update->validation_hint.node = nsec_node->owner;
		data->update->validation_hint.rrtype = data->nsec_type;
		return KNOT_DNSSEC_EXTRA_NSEC;
	}
	if (shall_no_nsec) {
		return KNOT_EOK;
	}
	if (may_no_nsec && nsec == NULL) {
		assert(data->nsec_type == KNOT_RRTYPE_NSEC3);
		const zone_node_t *found_nsec3 = NULL, *prev_nsec3 = NULL;
		if (node->nsec3_hash == NULL ||
		    zone_contents_find_nsec3(data->update->new_cont, node->nsec3_hash, &found_nsec3, &prev_nsec3) != ZONE_NAME_NOT_FOUND ||
		    found_nsec3 != NULL) {
			return KNOT_ERROR;
		}
		if (prev_nsec3 == NULL) {
			data->update->validation_hint.node = (nsec_node == NULL ? node->owner : nsec_node->owner);
			data->update->validation_hint.rrtype = KNOT_RRTYPE_ANY;
			return KNOT_DNSSEC_ENONSEC;
		}
		knot_rdataset_t *nsec3 = node_rdataset(prev_nsec3, KNOT_RRTYPE_NSEC3);
		if (nsec3 == NULL) {
			return KNOT_ERROR;
		}
		if (nsec3->count != 1 || !(knot_nsec3param_flags(nsec3->rdata) & KNOT_NSEC3_FLAG_OPT_OUT)) {
			data->update->validation_hint.node = prev_nsec3->owner;
			data->update->validation_hint.rrtype = data->nsec_type;
			return KNOT_DNSSEC_ENSEC3_OPTOUT;
		}
		return KNOT_EOK;
	}

	dnssec_nsec_bitmap_t *rr_types = dnssec_nsec_bitmap_new();
	if (rr_types == NULL) {
		return KNOT_ENOMEM;
	}
	bitmap_add_node_rrsets(rr_types, node, true);

	uint16_t node_wire_size = dnssec_nsec_bitmap_size(rr_types);
	uint8_t *node_wire = malloc(node_wire_size);
	if (node_wire == NULL) {
		dnssec_nsec_bitmap_free(rr_types);
		return KNOT_ENOMEM;
	}
	dnssec_nsec_bitmap_write(rr_types, node_wire);
	dnssec_nsec_bitmap_free(rr_types);

	const uint8_t *nsec_wire = NULL;
	uint16_t nsec_wire_size = 0;
	if (data->nsec3_params == NULL) {
		nsec_wire = knot_nsec_bitmap(nsec->rdata);
		nsec_wire_size = knot_nsec_bitmap_len(nsec->rdata);
	} else {
		nsec_wire = knot_nsec3_bitmap(nsec->rdata);
		nsec_wire_size = knot_nsec3_bitmap_len(nsec->rdata);
	}

	if (node_wire_size != nsec_wire_size ||
	    memcmp(node_wire, nsec_wire, node_wire_size) != 0) {
		free(node_wire);
		data->update->validation_hint.node = node->owner;
		data->update->validation_hint.rrtype = data->nsec_type;
		return KNOT_DNSSEC_ENSEC_BITMAP;
	}
	free(node_wire);
	return KNOT_EOK;
}

int nsec_check_bitmaps(zone_tree_t *nsec_ptrs, nsec_chain_iterate_data_t *data)
{
	return zone_tree_apply(nsec_ptrs, check_nsec_bitmap, data);
}

/*! \brief Return the one from those nodes which has
 * closest lower (lexicographically) owner name to ref. */
static zone_node_t *node_nearer(zone_node_t *a, zone_node_t *b, zone_node_t *ref)
{
	if (a == NULL || a == b) {
		return b;
	} else if (b == NULL) {
		return a;
	} else {
		int abigger = knot_dname_cmp(a->owner, ref->owner) >= 0 ? 1 : 0;
		int bbigger = knot_dname_cmp(b->owner, ref->owner) >= 0 ? 1 : 0;
		int cmp = knot_dname_cmp(a->owner, b->owner);
		if (abigger != bbigger) {
			cmp = -cmp;
		}
		return cmp < 0 ? b : a;
	}
}

/* - API - iterations ------------------------------------------------------- */

/*!
 * \brief Call a function for each piece of the chain formed by sorted nodes.
 */
int knot_nsec_chain_iterate_create(zone_tree_t *nodes,
                                   chain_iterate_create_cb callback,
                                   nsec_chain_iterate_data_t *data)
{
	assert(nodes);
	assert(callback);

	zone_tree_delsafe_it_t it = { 0 };
	int result = zone_tree_delsafe_it_begin(nodes, &it, false);
	if (result != KNOT_EOK) {
		return result;
	}

	if (zone_tree_delsafe_it_finished(&it)) {
		zone_tree_delsafe_it_free(&it);
		return KNOT_EINVAL;
	}

	zone_node_t *first = zone_tree_delsafe_it_val(&it);
	zone_node_t *previous = first;
	zone_node_t *current = first;

	zone_tree_delsafe_it_next(&it);

	while (!zone_tree_delsafe_it_finished(&it)) {
		current = zone_tree_delsafe_it_val(&it);

		result = callback(previous, current, data);
		if (result == NSEC_NODE_SKIP) {
			// No NSEC should be created for 'current' node, skip
			;
		} else if (result == KNOT_EOK) {
			previous = current;
		} else {
			zone_tree_delsafe_it_free(&it);
			return result;
		}
		zone_tree_delsafe_it_next(&it);
	}

	zone_tree_delsafe_it_free(&it);

	return result == NSEC_NODE_SKIP ? callback(previous, first, data) :
	                 callback(current, first, data);
}

int knot_nsec_chain_iterate_fix(zone_tree_t *node_ptrs,
                                chain_iterate_create_cb callback,
                                chain_iterate_create_cb cb_reconn,
                                nsec_chain_iterate_data_t *data)
{
	zone_tree_delsafe_it_t it = { 0 };
	int ret = zone_tree_delsafe_it_begin(node_ptrs, &it, true);
	if (ret != KNOT_EOK) {
		return ret;
	}

	zone_node_t *prev_it = NULL;
	zone_node_t *started_with = NULL;
	while (ret == KNOT_EOK) {
		if (zone_tree_delsafe_it_finished(&it)) {
			assert(started_with != NULL);
			zone_tree_delsafe_it_restart(&it);
		}

		zone_node_t *curr_new = zone_tree_delsafe_it_val(&it);
		zone_node_t *curr_old = binode_counterpart(curr_new);
		bool del_new = node_no_nsec(curr_new);
		bool del_old = node_no_nsec(curr_old);

		if (started_with == curr_new) {
			assert(started_with != NULL);
			break;
		}
		if (!del_old && !del_new && started_with == NULL) {
			// Once this must happen since the NSEC(3) node belonging
			// to zone apex is always present.
			started_with = curr_new;
		}

		if (!del_old && del_new && started_with != NULL) {
			zone_node_t *prev_old = curr_old, *prev_new;
			do {
				prev_old = nsec_prev(prev_old, NULL);
				prev_new = binode_counterpart(prev_old);
			} while (node_no_nsec(prev_new));

			zone_node_t *prev_near = node_nearer(prev_new, prev_it, curr_old);
			ret = cb_reconn(curr_old, prev_near, data);
		}
		if (del_old && !del_new && started_with != NULL) {
			zone_node_t *prev_new = nsec_prev(curr_new, NULL);
			ret = cb_reconn(prev_new, curr_new, data);
			if (ret == KNOT_EOK) {
				ret = callback(prev_new, curr_new, data);
			}
			prev_it = curr_new;
		}

		zone_tree_delsafe_it_next(&it);
	}
	zone_tree_delsafe_it_free(&it);
	return ret;
}

/* - API - utility functions ------------------------------------------------ */

/*!
 * \brief Add entry for removed NSEC to the changeset.
 */
int knot_nsec_changeset_remove(const zone_node_t *n, zone_update_t *update)
{
	if (update == NULL) {
		return KNOT_EINVAL;
	}

	int result = KNOT_EOK;
	knot_rrset_t nsec_rem = node_rrset(n, KNOT_RRTYPE_NSEC);
	knot_rrset_t nsec3_rem = node_rrset(n, KNOT_RRTYPE_NSEC3);
	knot_rrset_t rrsigs = node_rrset(n, KNOT_RRTYPE_RRSIG);

	if (!knot_rrset_empty(&nsec_rem)) {
		result = zone_update_remove(update, &nsec_rem);
	}
	if (result == KNOT_EOK && !knot_rrset_empty(&nsec3_rem)) {
		result = zone_update_remove(update, &nsec3_rem);
	}
	if (!knot_rrset_empty(&rrsigs) && result == KNOT_EOK) {
		knot_rrset_t synth_rrsigs;
		knot_rrset_init(&synth_rrsigs, n->owner, KNOT_RRTYPE_RRSIG,
		                KNOT_CLASS_IN, rrsigs.ttl);
		result = knot_synth_rrsig(KNOT_RRTYPE_NSEC, &rrsigs.rrs,
		                          &synth_rrsigs.rrs, NULL);
		if (result == KNOT_ENOENT) {
			// Try removing NSEC3 RRSIGs
			result = knot_synth_rrsig(KNOT_RRTYPE_NSEC3, &rrsigs.rrs,
			                          &synth_rrsigs.rrs, NULL);
		}

		if (result != KNOT_EOK) {
			knot_rdataset_clear(&synth_rrsigs.rrs, NULL);
			if (result != KNOT_ENOENT) {
				return result;
			}
			return KNOT_EOK;
		}

		// store RRSIG
		result = zone_update_remove(update, &synth_rrsigs);
		knot_rdataset_clear(&synth_rrsigs.rrs, NULL);
	}

	return result;
}

/*!
 * \brief Checks whether the node is empty or eventually contains only NSEC and
 *        RRSIGs.
 */
bool knot_nsec_empty_nsec_and_rrsigs_in_node(const zone_node_t *n)
{
	assert(n);
	for (int i = 0; i < n->rrset_count; ++i) {
		knot_rrset_t rrset = node_rrset_at(n, i);
		if (rrset.type != KNOT_RRTYPE_NSEC &&
		    rrset.type != KNOT_RRTYPE_RRSIG) {
			return false;
		}
	}

	return true;
}

/* - API - Chain creation --------------------------------------------------- */

/*!
 * \brief Create new NSEC chain, add differences from current into a changeset.
 */
int knot_nsec_create_chain(zone_update_t *update, uint32_t ttl)
{
	assert(update);
	assert(update->new_cont->nodes);

	nsec_chain_iterate_data_t data = { ttl, update, KNOT_RRTYPE_NSEC };

	return knot_nsec_chain_iterate_create(update->new_cont->nodes,
	                                      connect_nsec_nodes, &data);
}

int knot_nsec_fix_chain(zone_update_t *update, uint32_t ttl)
{
	assert(update);
	assert(update->zone->contents->nodes);
	assert(update->new_cont->nodes);

	nsec_chain_iterate_data_t data = { ttl, update, KNOT_RRTYPE_NSEC };

	int ret = nsec_update_bitmaps(update->a_ctx->node_ptrs, &data);
	if (ret != KNOT_EOK) {
		return ret;
	}

	ret = zone_adjust_contents(update->new_cont, adjust_cb_void, NULL, false, true, 1, update->a_ctx->node_ptrs);
	if (ret != KNOT_EOK) {
		return ret;
	}

	// ensure that zone root is in list of changed nodes
	ret = zone_tree_insert(update->a_ctx->node_ptrs, &update->new_cont->apex);
	if (ret != KNOT_EOK) {
		return ret;
	}

	return knot_nsec_chain_iterate_fix(update->a_ctx->node_ptrs,
					   connect_nsec_nodes, reconnect_nsec_nodes, &data);
}

int knot_nsec_check_chain(zone_update_t *update)
{
	if (!zone_tree_is_empty(update->new_cont->nsec3_nodes)) {
		update->validation_hint.node = update->zone->name;
		update->validation_hint.rrtype = KNOT_RRTYPE_NSEC3;
		return KNOT_DNSSEC_EXTRA_NSEC;
	}

	nsec_chain_iterate_data_t data = { 0, update, KNOT_RRTYPE_NSEC };

	int ret = nsec_check_bitmaps(update->new_cont->nodes, &data);
	if (ret != KNOT_EOK) {
		return ret;
	}

	return knot_nsec_chain_iterate_create(update->new_cont->nodes,
					      nsec_check_connect_nodes, &data);
}

int knot_nsec_check_chain_fix(zone_update_t *update)
{
	if (!zone_tree_is_empty(update->new_cont->nsec3_nodes)) {
		update->validation_hint.node = update->zone->name;
		update->validation_hint.rrtype = KNOT_RRTYPE_NSEC3;
		return KNOT_DNSSEC_EXTRA_NSEC;
	}

	nsec_chain_iterate_data_t data = { 0, update, KNOT_RRTYPE_NSEC };

	int ret = nsec_check_bitmaps(update->a_ctx->node_ptrs, &data);
	if (ret != KNOT_EOK) {
		return ret;
	}

	return nsec_check_new_connects(update->a_ctx->node_ptrs, &data);
}
