summaryrefslogtreecommitdiffstats
path: root/src/knot/zone/contents.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/knot/zone/contents.c')
-rw-r--r--src/knot/zone/contents.c1197
1 files changed, 1197 insertions, 0 deletions
diff --git a/src/knot/zone/contents.c b/src/knot/zone/contents.c
new file mode 100644
index 0000000..e99cb48
--- /dev/null
+++ b/src/knot/zone/contents.c
@@ -0,0 +1,1197 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+
+#include "libdnssec/error.h"
+#include "knot/zone/contents.h"
+#include "knot/common/log.h"
+#include "knot/dnssec/zone-nsec.h"
+#include "libknot/libknot.h"
+#include "contrib/qp-trie/trie.h"
+#include "contrib/macros.h"
+
+typedef struct {
+ zone_contents_apply_cb_t func;
+ void *data;
+} zone_tree_func_t;
+
+typedef struct {
+ zone_node_t *first_node;
+ zone_contents_t *zone;
+ zone_node_t *previous_node;
+} zone_adjust_arg_t;
+
+static int tree_apply_cb(zone_node_t **node, void *data)
+{
+ if (node == NULL || data == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ zone_tree_func_t *f = (zone_tree_func_t *)data;
+ return f->func(*node, f->data);
+}
+
+/*!
+ * \brief Checks if the given node can be inserted into the given zone.
+ *
+ * Checks if both the arguments are non-NULL and if the owner of the node
+ * belongs to the zone (i.e. is a subdomain of the zone apex).
+ *
+ * \param zone Zone to which the node is going to be inserted.
+ * \param node Node to check.
+ *
+ * \retval KNOT_EOK if both arguments are non-NULL and the node belongs to the
+ * zone.
+ * \retval KNOT_EINVAL if either of the arguments is NULL.
+ * \retval KNOT_EOUTOFZONE if the node does not belong to the zone.
+ */
+static int check_node(const zone_contents_t *contents, const zone_node_t *node)
+{
+ assert(contents);
+ assert(contents->apex != NULL);
+ assert(node);
+
+ if (knot_dname_in_bailiwick(node->owner, contents->apex->owner) <= 0) {
+ return KNOT_EOUTOFZONE;
+ }
+
+ return KNOT_EOK;
+}
+
+/*!
+ * \brief Destroys all RRSets in a node.
+ *
+ * This function is designed to be used in the tree-iterating functions.
+ *
+ * \param node Node to destroy RRSets from.
+ * \param data Unused parameter.
+ */
+static int destroy_node_rrsets_from_tree(zone_node_t **node, void *data)
+{
+ assert(node);
+ UNUSED(data);
+
+ if (*node != NULL) {
+ node_free_rrsets(*node, NULL);
+ node_free(*node, NULL);
+ }
+
+ return KNOT_EOK;
+}
+
+static int create_nsec3_name(uint8_t *out, size_t out_size,
+ const zone_contents_t *zone,
+ const knot_dname_t *name)
+{
+ assert(out);
+ assert(zone);
+ assert(name);
+
+ if (!knot_is_nsec3_enabled(zone)) {
+ return KNOT_ENSEC3PAR;
+ }
+
+ return knot_create_nsec3_owner(out, out_size, name, zone->apex->owner,
+ &zone->nsec3_params);
+}
+
+/*! \brief Link pointers to additional nodes for this RRSet. */
+static int discover_additionals(const knot_dname_t *owner, struct rr_data *rr_data,
+ zone_contents_t *zone)
+{
+ assert(rr_data != NULL);
+
+ /* Drop possible previous additional nodes. */
+ additional_clear(rr_data->additional);
+ rr_data->additional = NULL;
+
+ const knot_rdataset_t *rrs = &rr_data->rrs;
+ uint16_t rdcount = rrs->count;
+
+ uint16_t mandatory_count = 0;
+ uint16_t others_count = 0;
+ glue_t mandatory[rdcount];
+ glue_t others[rdcount];
+
+ /* Scan new additional nodes. */
+ for (uint16_t i = 0; i < rdcount; i++) {
+ knot_rdata_t *rdata = knot_rdataset_at(rrs, i);
+ const knot_dname_t *dname = knot_rdata_name(rdata, rr_data->type);
+ const zone_node_t *node = NULL, *encloser = NULL, *prev = NULL;
+
+ /* Try to find node for the dname in the RDATA. */
+ zone_contents_find_dname(zone, dname, &node, &encloser, &prev);
+ if (node == NULL && encloser != NULL
+ && (encloser->flags & NODE_FLAGS_WILDCARD_CHILD)) {
+ /* Find wildcard child in the zone. */
+ node = zone_contents_find_wildcard_child(zone, encloser);
+ assert(node != NULL);
+ }
+
+ if (node == NULL) {
+ continue;
+ }
+
+ glue_t *glue;
+ if ((node->flags & (NODE_FLAGS_DELEG | NODE_FLAGS_NONAUTH)) &&
+ rr_data->type == KNOT_RRTYPE_NS &&
+ knot_dname_in_bailiwick(node->owner, owner) >= 0) {
+ glue = &mandatory[mandatory_count++];
+ glue->optional = false;
+ } else {
+ glue = &others[others_count++];
+ glue->optional = true;
+ }
+ glue->node = node;
+ glue->ns_pos = i;
+ }
+
+ /* Store sorted additionals by the type, mandatory first. */
+ size_t total_count = mandatory_count + others_count;
+ if (total_count > 0) {
+ rr_data->additional = malloc(sizeof(additional_t));
+ if (rr_data->additional == NULL) {
+ return KNOT_ENOMEM;
+ }
+ rr_data->additional->count = total_count;
+
+ size_t size = total_count * sizeof(glue_t);
+ rr_data->additional->glues = malloc(size);
+ if (rr_data->additional->glues == NULL) {
+ free(rr_data->additional);
+ return KNOT_ENOMEM;
+ }
+
+ size_t mandatory_size = mandatory_count * sizeof(glue_t);
+ memcpy(rr_data->additional->glues, mandatory, mandatory_size);
+ memcpy(rr_data->additional->glues + mandatory_count, others,
+ size - mandatory_size);
+ }
+
+ return KNOT_EOK;
+}
+
+static int adjust_pointers(zone_node_t **tnode, void *data)
+{
+ assert(tnode != NULL);
+ assert(data != NULL);
+
+ zone_adjust_arg_t *args = (zone_adjust_arg_t *)data;
+ zone_node_t *node = *tnode;
+
+ // remember first node
+ if (args->first_node == NULL) {
+ args->first_node = node;
+ }
+
+ // check if this node is not a wildcard child of its parent
+ if (knot_dname_is_wildcard(node->owner)) {
+ assert(node->parent != NULL);
+ node->parent->flags |= NODE_FLAGS_WILDCARD_CHILD;
+ }
+
+ // set flags (delegation point, non-authoritative)
+ if (node->parent &&
+ (node->parent->flags & NODE_FLAGS_DELEG ||
+ node->parent->flags & NODE_FLAGS_NONAUTH)) {
+ node->flags |= NODE_FLAGS_NONAUTH;
+ } else if (node_rrtype_exists(node, KNOT_RRTYPE_NS) && node != args->zone->apex) {
+ node->flags |= NODE_FLAGS_DELEG;
+ } else {
+ // Default.
+ node->flags = NODE_FLAGS_AUTH;
+ }
+
+ // set pointer to previous node
+ node->prev = args->previous_node;
+
+ // update remembered previous pointer only if authoritative
+ if (!(node->flags & NODE_FLAGS_NONAUTH) && node->rrset_count > 0) {
+ args->previous_node = node;
+ }
+
+ return KNOT_EOK;
+}
+
+static int adjust_nsec3_pointers(zone_node_t **tnode, void *data)
+{
+ assert(data != NULL);
+ assert(tnode != NULL);
+
+ zone_adjust_arg_t *args = (zone_adjust_arg_t *)data;
+ zone_node_t *node = *tnode;
+ const zone_node_t *ignored;
+
+ // Connect to NSEC3 node (only if NSEC3 tree is not empty)
+ node->nsec3_wildcard_prev = NULL;
+ uint8_t nsec3_name[KNOT_DNAME_MAXLEN];
+ int ret = create_nsec3_name(nsec3_name, sizeof(nsec3_name), args->zone,
+ node->owner);
+ if (ret == KNOT_EOK) {
+ node->nsec3_node = zone_tree_get(args->zone->nsec3_nodes, nsec3_name);
+
+ // Connect to NSEC3 node proving nonexistence of wildcard.
+ size_t wildcard_size = knot_dname_size(node->owner) + 2;
+ if (wildcard_size <= KNOT_DNAME_MAXLEN) {
+ assert(wildcard_size > 2);
+ knot_dname_t wildcard[wildcard_size];
+ memcpy(wildcard, "\x01""*", 2);
+ memcpy(wildcard + 2, node->owner, wildcard_size - 2);
+ ret = zone_contents_find_nsec3_for_name(args->zone, wildcard, &ignored,
+ (const zone_node_t **)&node->nsec3_wildcard_prev);
+ if (ret == ZONE_NAME_FOUND) {
+ node->nsec3_wildcard_prev = NULL;
+ ret = KNOT_EOK;
+ }
+ }
+ } else if (ret == KNOT_ENSEC3PAR) {
+ node->nsec3_node = NULL;
+ ret = KNOT_EOK;
+ }
+
+ return ret;
+}
+
+static int measure_size(zone_node_t *node, void *data){
+
+ size_t *size = data;
+ int rrset_count = node->rrset_count;
+ for (int i = 0; i < rrset_count; i++) {
+ knot_rrset_t rrset = node_rrset_at(node, i);
+ *size += knot_rrset_size(&rrset);
+ }
+ return KNOT_EOK;
+}
+
+static int measure_max_ttl(zone_node_t *node, void *data){
+
+ uint32_t *max = data;
+ int rrset_count = node->rrset_count;
+ for (int i = 0; i < rrset_count; i++) {
+ *max = MAX(*max, node->rrs[i].ttl);
+ }
+ return KNOT_EOK;
+}
+
+static bool nsec3_params_match(const knot_rdataset_t *rrs,
+ const dnssec_nsec3_params_t *params,
+ size_t rdata_pos)
+{
+ assert(rrs != NULL);
+ assert(params != NULL);
+
+ knot_rdata_t *rdata = knot_rdataset_at(rrs, rdata_pos);
+
+ return (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);
+}
+
+/*!
+ * \brief Adjust normal (non NSEC3) node.
+ *
+ * Set:
+ * - pointer to wildcard childs in parent nodes if applicable
+ * - flags (delegation point, non-authoritative)
+ * - pointer to previous node
+ * - parent pointers
+ *
+ * \param tnode Zone node to adjust.
+ * \param data Adjusting parameters (zone_adjust_arg_t *).
+ */
+static int adjust_normal_node(zone_node_t **tnode, void *data)
+{
+ assert(tnode != NULL && *tnode);
+ assert(data != NULL);
+
+ // Do cheap operations first
+ int ret = adjust_pointers(tnode, data);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ zone_adjust_arg_t *arg = data;
+ measure_size(*tnode, &arg->zone->size);
+ measure_max_ttl(*tnode, &arg->zone->max_ttl);
+
+ // Connect nodes to their NSEC3 nodes
+ return adjust_nsec3_pointers(tnode, data);
+}
+
+/*!
+ * \brief Adjust NSEC3 node.
+ *
+ * Set:
+ * - pointer to previous node
+ * - pointer to node stored in owner dname
+ *
+ * \param tnode Zone node to adjust.
+ * \param data Adjusting parameters (zone_adjust_arg_t *).
+ */
+static int adjust_nsec3_node(zone_node_t **tnode, void *data)
+{
+ assert(data != NULL);
+ assert(tnode != NULL);
+
+ zone_adjust_arg_t *args = (zone_adjust_arg_t *)data;
+ zone_node_t *node = *tnode;
+
+ // remember first node
+ if (args->first_node == NULL) {
+ args->first_node = node;
+ }
+
+ // set previous node
+ node->prev = args->previous_node;
+ args->previous_node = node;
+
+ measure_size(*tnode, &args->zone->size);
+ measure_max_ttl(*tnode, &args->zone->max_ttl);
+
+ // check if this node belongs to correct chain
+ const knot_rdataset_t *nsec3_rrs = node_rdataset(node, KNOT_RRTYPE_NSEC3);
+ for (uint16_t i = 0; nsec3_rrs != NULL && i < nsec3_rrs->count; i++) {
+ if (nsec3_params_match(nsec3_rrs, &args->zone->nsec3_params, i)) {
+ node->flags |= NODE_FLAGS_IN_NSEC3_CHAIN;
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+/*! \brief Discover additional records for affected nodes. */
+static int adjust_additional(zone_node_t **tnode, void *data)
+{
+ assert(data != NULL);
+ assert(tnode != NULL);
+
+ zone_adjust_arg_t *args = (zone_adjust_arg_t *)data;
+ zone_node_t *node = *tnode;
+
+ /* Lookup additional records for specific nodes. */
+ for(uint16_t i = 0; i < node->rrset_count; ++i) {
+ struct rr_data *rr_data = &node->rrs[i];
+ if (knot_rrtype_additional_needed(rr_data->type)) {
+ int ret = discover_additionals(node->owner, rr_data, args->zone);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+ }
+ }
+
+ return KNOT_EOK;
+}
+
+/*!
+ * \brief Tries to find the given domain name in the zone tree.
+ *
+ * \param zone Zone to search in.
+ * \param name Domain name to find.
+ * \param node Found node.
+ * \param previous Previous node in canonical order (i.e. the one directly
+ * preceding \a name in canonical order, regardless if the name
+ * is in the zone or not).
+ *
+ * \retval true if the domain name was found. In such case \a node holds the
+ * zone node with \a name as its owner. \a previous is set
+ * properly.
+ * \retval false if the domain name was not found. \a node may hold any (or none)
+ * node. \a previous is set properly.
+ */
+static bool find_in_tree(zone_tree_t *tree, const knot_dname_t *name,
+ zone_node_t **node, zone_node_t **previous)
+{
+ assert(tree != NULL);
+ assert(name != NULL);
+ assert(node != NULL);
+ assert(previous != NULL);
+
+ zone_node_t *found = NULL, *prev = NULL;
+
+ int match = zone_tree_get_less_or_equal(tree, name, &found, &prev);
+ if (match < 0) {
+ assert(0);
+ return false;
+ }
+
+ *node = found;
+ *previous = prev;
+
+ return match > 0;
+}
+
+zone_contents_t *zone_contents_new(const knot_dname_t *apex_name)
+{
+ if (apex_name == NULL) {
+ return NULL;
+ }
+
+ zone_contents_t *contents = malloc(sizeof(zone_contents_t));
+ if (contents == NULL) {
+ return NULL;
+ }
+
+ memset(contents, 0, sizeof(zone_contents_t));
+ contents->apex = node_new(apex_name, NULL);
+ if (contents->apex == NULL) {
+ goto cleanup;
+ }
+
+ contents->nodes = zone_tree_create();
+ if (contents->nodes == NULL) {
+ goto cleanup;
+ }
+
+ if (zone_tree_insert(contents->nodes, contents->apex) != KNOT_EOK) {
+ goto cleanup;
+ }
+
+ return contents;
+
+cleanup:
+ free(contents->nodes);
+ free(contents->nsec3_nodes);
+ free(contents);
+ return NULL;
+}
+
+static zone_node_t *get_node(const zone_contents_t *zone, const knot_dname_t *name)
+{
+ assert(zone);
+ assert(name);
+
+ return zone_tree_get(zone->nodes, name);
+}
+
+static int add_node(zone_contents_t *zone, zone_node_t *node, bool create_parents)
+{
+ if (zone == NULL || node == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = check_node(zone, node);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ ret = zone_tree_insert(zone->nodes, node);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (!create_parents) {
+ return KNOT_EOK;
+ }
+
+ /* No parents for root domain. */
+ if (*node->owner == '\0') {
+ return KNOT_EOK;
+ }
+
+ zone_node_t *next_node = NULL;
+ const uint8_t *parent = knot_wire_next_label(node->owner, NULL);
+
+ if (knot_dname_is_equal(zone->apex->owner, parent)) {
+ node_set_parent(node, zone->apex);
+
+ // check if the node is not wildcard child of the parent
+ if (knot_dname_is_wildcard(node->owner)) {
+ zone->apex->flags |= NODE_FLAGS_WILDCARD_CHILD;
+ }
+ } else {
+ while (parent != NULL && !(next_node = get_node(zone, parent))) {
+
+ /* Create a new node. */
+ next_node = node_new(parent, NULL);
+ if (next_node == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ /* Insert node to a tree. */
+ ret = zone_tree_insert(zone->nodes, next_node);
+ if (ret != KNOT_EOK) {
+ node_free(next_node, NULL);
+ return ret;
+ }
+
+ /* Update node pointers. */
+ node_set_parent(node, next_node);
+ if (knot_dname_is_wildcard(node->owner)) {
+ next_node->flags |= NODE_FLAGS_WILDCARD_CHILD;
+ }
+
+ node = next_node;
+ parent = knot_wire_next_label(parent, NULL);
+ }
+
+ // set the found parent (in the zone) as the parent of the last
+ // inserted node
+ assert(node->parent == NULL);
+ node_set_parent(node, next_node);
+ }
+
+ return KNOT_EOK;
+}
+
+static int add_nsec3_node(zone_contents_t *zone, zone_node_t *node)
+{
+ if (zone == NULL || node == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = check_node(zone, node);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ /* Create NSEC3 tree if not exists. */
+ if (zone->nsec3_nodes == NULL) {
+ zone->nsec3_nodes = zone_tree_create();
+ if (zone->nsec3_nodes == NULL) {
+ return KNOT_ENOMEM;
+ }
+ }
+
+ // how to know if this is successful??
+ ret = zone_tree_insert(zone->nsec3_nodes, node);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ // no parents to be created, the only parent is the zone apex
+ // set the apex as the parent of the node
+ node_set_parent(node, zone->apex);
+
+ // cannot be wildcard child, so nothing to be done
+
+ return KNOT_EOK;
+}
+
+static zone_node_t *get_nsec3_node(const zone_contents_t *zone,
+ const knot_dname_t *name)
+{
+ assert(zone);
+ assert(name);
+
+ return zone_tree_get(zone->nsec3_nodes, name);
+}
+
+static int insert_rr(zone_contents_t *z, const knot_rrset_t *rr,
+ zone_node_t **n, bool nsec3)
+{
+ if (knot_rrset_empty(rr)) {
+ return KNOT_EINVAL;
+ }
+
+ // check if the RRSet belongs to the zone
+ if (knot_dname_in_bailiwick(rr->owner, z->apex->owner) < 0) {
+ return KNOT_EOUTOFZONE;
+ }
+
+ if (*n == NULL) {
+ *n = nsec3 ? get_nsec3_node(z, rr->owner) : get_node(z, rr->owner);
+ if (*n == NULL) {
+ // Create new, insert
+ *n = node_new(rr->owner, NULL);
+ if (*n == NULL) {
+ return KNOT_ENOMEM;
+ }
+ int ret = nsec3 ? add_nsec3_node(z, *n) : add_node(z, *n, true);
+ if (ret != KNOT_EOK) {
+ node_free(*n, NULL);
+ *n = NULL;
+ }
+ }
+ }
+
+ return node_add_rrset(*n, rr, NULL);
+}
+
+static int remove_rr(zone_contents_t *z, const knot_rrset_t *rr,
+ zone_node_t **n, bool nsec3)
+{
+ if (knot_rrset_empty(rr)) {
+ return KNOT_EINVAL;
+ }
+
+ // check if the RRSet belongs to the zone
+ if (knot_dname_in_bailiwick(rr->owner, z->apex->owner) < 0) {
+ return KNOT_EOUTOFZONE;
+ }
+
+ zone_node_t *node;
+ if (*n == NULL) {
+ node = nsec3 ? get_nsec3_node(z, rr->owner) : get_node(z, rr->owner);
+ if (node == NULL) {
+ return KNOT_ENONODE;
+ }
+ } else {
+ node = *n;
+ }
+
+ knot_rdataset_t *node_rrs = node_rdataset(node, rr->type);
+ // Subtract changeset RRS from node RRS.
+ int ret = knot_rdataset_subtract(node_rrs, &rr->rrs, NULL);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ if (node_rrs->count == 0) {
+ // RRSet is empty now, remove it from node, all data freed.
+ node_remove_rdataset(node, rr->type);
+ // If node is empty now, delete it from zone tree.
+ if (node->rrset_count == 0 && node != z->apex) {
+ zone_tree_delete_empty(nsec3 ? z->nsec3_nodes : z->nodes, node);
+ }
+ }
+
+ *n = node;
+ return KNOT_EOK;
+}
+
+static int recreate_normal_tree(const zone_contents_t *z, zone_contents_t *out)
+{
+ out->nodes = trie_create(NULL);
+ if (out->nodes == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Insert APEX first.
+ zone_node_t *apex_cpy = node_shallow_copy(z->apex, NULL);
+ if (apex_cpy == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ // Normal additions need apex ... so we need to insert directly.
+ int ret = zone_tree_insert(out->nodes, apex_cpy);
+ if (ret != KNOT_EOK) {
+ node_free(apex_cpy, NULL);
+ return ret;
+ }
+
+ out->apex = apex_cpy;
+
+ trie_it_t *itt = trie_it_begin(z->nodes);
+ if (itt == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ while (!trie_it_finished(itt)) {
+ const zone_node_t *to_cpy = (zone_node_t *)*trie_it_val(itt);
+ if (to_cpy == z->apex) {
+ // Inserted already.
+ trie_it_next(itt);
+ continue;
+ }
+ zone_node_t *to_add = node_shallow_copy(to_cpy, NULL);
+ if (to_add == NULL) {
+ trie_it_free(itt);
+ return KNOT_ENOMEM;
+ }
+
+ int ret = add_node(out, to_add, true);
+ if (ret != KNOT_EOK) {
+ node_free(to_add, NULL);
+ trie_it_free(itt);
+ return ret;
+ }
+ trie_it_next(itt);
+ }
+
+ trie_it_free(itt);
+
+ return KNOT_EOK;
+}
+
+static int recreate_nsec3_tree(const zone_contents_t *z, zone_contents_t *out)
+{
+ out->nsec3_nodes = trie_create(NULL);
+ if (out->nsec3_nodes == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ trie_it_t *itt = trie_it_begin(z->nsec3_nodes);
+ if (itt == NULL) {
+ return KNOT_ENOMEM;
+ }
+ while (!trie_it_finished(itt)) {
+ const zone_node_t *to_cpy = (zone_node_t *)*trie_it_val(itt);
+ zone_node_t *to_add = node_shallow_copy(to_cpy, NULL);
+ if (to_add == NULL) {
+ trie_it_free(itt);
+ return KNOT_ENOMEM;
+ }
+
+ int ret = add_nsec3_node(out, to_add);
+ if (ret != KNOT_EOK) {
+ trie_it_free(itt);
+ node_free(to_add, NULL);
+ return ret;
+ }
+
+ trie_it_next(itt);
+ }
+
+ trie_it_free(itt);
+
+ return KNOT_EOK;
+}
+
+// Public API
+
+int zone_contents_add_rr(zone_contents_t *z, const knot_rrset_t *rr,
+ zone_node_t **n)
+{
+ if (z == NULL || rr == NULL || n == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ return insert_rr(z, rr, n, knot_rrset_is_nsec3rel(rr));
+}
+
+int zone_contents_remove_rr(zone_contents_t *z, const knot_rrset_t *rr,
+ zone_node_t **n)
+{
+ if (z == NULL || rr == NULL || n == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ return remove_rr(z, rr, n, knot_rrset_is_nsec3rel(rr));
+}
+
+zone_node_t *zone_contents_get_node_for_rr(zone_contents_t *zone, const knot_rrset_t *rrset)
+{
+ if (zone == NULL || rrset == NULL) {
+ return NULL;
+ }
+
+ const bool nsec3 = knot_rrset_is_nsec3rel(rrset);
+ zone_node_t *node = nsec3 ? get_nsec3_node(zone, rrset->owner) :
+ get_node(zone, rrset->owner);
+ if (node == NULL) {
+ node = node_new(rrset->owner, NULL);
+ int ret = nsec3 ? add_nsec3_node(zone, node) : add_node(zone, node, true);
+ if (ret != KNOT_EOK) {
+ node_free(node, NULL);
+ return NULL;
+ }
+
+ return node;
+ } else {
+ return node;
+ }
+}
+
+const zone_node_t *zone_contents_find_node(const zone_contents_t *zone, const knot_dname_t *name)
+{
+ if (zone == NULL || name == NULL) {
+ return NULL;
+ }
+
+ return get_node(zone, name);
+}
+
+zone_node_t *zone_contents_find_node_for_rr(zone_contents_t *contents, const knot_rrset_t *rrset)
+{
+ if (contents == NULL || rrset == NULL) {
+ return NULL;
+ }
+
+ const bool nsec3 = knot_rrset_is_nsec3rel(rrset);
+ return nsec3 ? get_nsec3_node(contents, rrset->owner) :
+ get_node(contents, rrset->owner);
+}
+
+int zone_contents_find_dname(const zone_contents_t *zone,
+ const knot_dname_t *name,
+ const zone_node_t **match,
+ const zone_node_t **closest,
+ const zone_node_t **previous)
+{
+ if (!zone || !name || !match || !closest || !previous) {
+ return KNOT_EINVAL;
+ }
+
+ if (knot_dname_in_bailiwick(name, zone->apex->owner) < 0) {
+ return KNOT_EOUTOFZONE;
+ }
+
+ zone_node_t *node = NULL;
+ zone_node_t *prev = NULL;
+
+ int found = zone_tree_get_less_or_equal(zone->nodes, name, &node, &prev);
+ if (found < 0) {
+ // error
+ return found;
+ } else if (found == 1) {
+ // exact match
+
+ assert(node && prev);
+
+ *match = node;
+ *closest = node;
+ *previous = prev;
+
+ return ZONE_NAME_FOUND;
+ } else {
+ // closest match
+
+ assert(!node && prev);
+
+ node = prev;
+ size_t matched_labels = knot_dname_matched_labels(node->owner, name);
+ while (matched_labels < knot_dname_labels(node->owner, NULL)) {
+ node = node->parent;
+ assert(node);
+ }
+
+ *match = NULL;
+ *closest = node;
+ *previous = prev;
+
+ return ZONE_NAME_NOT_FOUND;
+ }
+}
+
+const zone_node_t *zone_contents_find_nsec3_node(const zone_contents_t *zone,
+ const knot_dname_t *name)
+{
+ if (zone == NULL || name == NULL) {
+ return NULL;
+ }
+
+ return get_nsec3_node(zone, name);
+}
+
+int zone_contents_find_nsec3_for_name(const zone_contents_t *zone,
+ const knot_dname_t *name,
+ const zone_node_t **nsec3_node,
+ const zone_node_t **nsec3_previous)
+{
+ if (zone == NULL || name == NULL || nsec3_node == NULL ||
+ nsec3_previous == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ // check if the NSEC3 tree is not empty
+ if (zone_tree_is_empty(zone->nsec3_nodes)) {
+ return KNOT_ENSEC3CHAIN;
+ }
+
+ uint8_t nsec3_name[KNOT_DNAME_MAXLEN];
+ int ret = create_nsec3_name(nsec3_name, sizeof(nsec3_name), zone, name);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ zone_node_t *found = NULL, *prev = NULL;
+ bool match = find_in_tree(zone->nsec3_nodes, nsec3_name, &found, &prev);
+
+ *nsec3_node = found;
+
+ if (prev == NULL) {
+ // either the returned node is the root of the tree, or it is
+ // the leftmost node in the tree; in both cases node was found
+ // set the previous node of the found node
+ assert(match);
+ assert(*nsec3_node != NULL);
+ *nsec3_previous = (*nsec3_node)->prev;
+ } else {
+ *nsec3_previous = prev;
+ }
+
+ // The previous may be from wrong NSEC3 chain. Search for previous from the right chain.
+ const zone_node_t *original_prev = *nsec3_previous;
+ while (!((*nsec3_previous)->flags & NODE_FLAGS_IN_NSEC3_CHAIN)) {
+ *nsec3_previous = (*nsec3_previous)->prev;
+ if (*nsec3_previous == original_prev || *nsec3_previous == NULL) {
+ // cycle
+ *nsec3_previous = NULL;
+ break;
+ }
+ }
+
+ return (match ? ZONE_NAME_FOUND : ZONE_NAME_NOT_FOUND);
+}
+
+const zone_node_t *zone_contents_find_wildcard_child(const zone_contents_t *contents,
+ const zone_node_t *parent)
+{
+ if (contents == NULL || parent == NULL || parent->owner == NULL) {
+ return NULL;
+ }
+
+ knot_dname_t wildcard[KNOT_DNAME_MAXLEN] = { 0x01, '*' };
+ knot_dname_to_wire(wildcard + 2, parent->owner, KNOT_DNAME_MAXLEN - 2);
+
+ return zone_contents_find_node(contents, wildcard);
+}
+
+static int adjust_nodes(zone_tree_t *nodes, zone_adjust_arg_t *adjust_arg,
+ zone_tree_apply_cb_t callback)
+{
+ assert(adjust_arg);
+ assert(callback);
+
+ if (zone_tree_is_empty(nodes)) {
+ return KNOT_EOK;
+ }
+
+ adjust_arg->first_node = NULL;
+ adjust_arg->previous_node = NULL;
+
+ int ret = zone_tree_apply(nodes, callback, adjust_arg);
+
+ if (adjust_arg->first_node) {
+ adjust_arg->first_node->prev = adjust_arg->previous_node;
+ }
+
+ return ret;
+}
+
+static int load_nsec3param(zone_contents_t *contents)
+{
+ assert(contents);
+ assert(contents->apex);
+
+ const knot_rdataset_t *rrs = NULL;
+ rrs = node_rdataset(contents->apex, KNOT_RRTYPE_NSEC3PARAM);
+ if (rrs == NULL) {
+ dnssec_nsec3_params_free(&contents->nsec3_params);
+ return KNOT_EOK;
+ }
+
+ if (rrs->count < 1) {
+ return KNOT_EINVAL;
+ }
+
+ dnssec_binary_t rdata = {
+ .size = rrs->rdata->len,
+ .data = rrs->rdata->data,
+ };
+
+ dnssec_nsec3_params_t new_params = { 0 };
+ int r = dnssec_nsec3_params_from_rdata(&new_params, &rdata);
+ if (r != DNSSEC_EOK) {
+ return KNOT_EMALF;
+ }
+
+ dnssec_nsec3_params_free(&contents->nsec3_params);
+ contents->nsec3_params = new_params;
+ return KNOT_EOK;
+}
+
+static int contents_adjust(zone_contents_t *contents, bool normal)
+{
+ if (contents == NULL || contents->apex == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ int ret = load_nsec3param(contents);
+ if (ret != KNOT_EOK) {
+ log_zone_error(contents->apex->owner,
+ "failed to load NSEC3 parameters (%s)",
+ knot_strerror(ret));
+ return ret;
+ }
+
+ zone_adjust_arg_t arg = {
+ .zone = contents
+ };
+
+ contents->size = 0;
+ contents->dnssec = node_rrtype_is_signed(contents->apex, KNOT_RRTYPE_SOA);
+
+ // NSEC3 nodes must be adjusted first, because we already need the NSEC3 chain
+ // to be closed before we adjust NSEC3 pointers in adjust_normal_node
+ ret = adjust_nodes(contents->nsec3_nodes, &arg, adjust_nsec3_node);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ ret = adjust_nodes(contents->nodes, &arg,
+ normal ? adjust_normal_node : adjust_pointers);
+ if (ret != KNOT_EOK) {
+ return ret;
+ }
+
+ return adjust_nodes(contents->nodes, &arg, adjust_additional);
+}
+
+int zone_contents_adjust_pointers(zone_contents_t *contents)
+{
+ return contents_adjust(contents, false);
+}
+
+int zone_contents_adjust_full(zone_contents_t *contents)
+{
+ return contents_adjust(contents, true);
+}
+
+int zone_contents_apply(zone_contents_t *contents,
+ zone_contents_apply_cb_t function, void *data)
+{
+ if (contents == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ zone_tree_func_t f = {
+ .func = function,
+ .data = data
+ };
+
+ return zone_tree_apply(contents->nodes, tree_apply_cb, &f);
+}
+
+int zone_contents_nsec3_apply(zone_contents_t *contents,
+ zone_contents_apply_cb_t function, void *data)
+{
+ if (contents == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ zone_tree_func_t f = {
+ .func = function,
+ .data = data
+ };
+
+ return zone_tree_apply(contents->nsec3_nodes, tree_apply_cb, &f);
+}
+
+int zone_contents_shallow_copy(const zone_contents_t *from, zone_contents_t **to)
+{
+ if (from == NULL || to == NULL) {
+ return KNOT_EINVAL;
+ }
+
+ /* Copy to same destination as source. */
+ if (from == *to) {
+ return KNOT_EINVAL;
+ }
+
+ zone_contents_t *contents = calloc(1, sizeof(zone_contents_t));
+ if (contents == NULL) {
+ return KNOT_ENOMEM;
+ }
+
+ int ret = recreate_normal_tree(from, contents);
+ if (ret != KNOT_EOK) {
+ zone_tree_free(&contents->nodes);
+ free(contents);
+ return ret;
+ }
+
+ if (from->nsec3_nodes) {
+ ret = recreate_nsec3_tree(from, contents);
+ if (ret != KNOT_EOK) {
+ zone_tree_free(&contents->nodes);
+ zone_tree_free(&contents->nsec3_nodes);
+ free(contents);
+ return ret;
+ }
+ } else {
+ contents->nsec3_nodes = NULL;
+ }
+
+ *to = contents;
+ return KNOT_EOK;
+}
+
+void zone_contents_free(zone_contents_t *contents)
+{
+ if (contents == NULL) {
+ return;
+ }
+
+ // free the zone tree, but only the structure
+ zone_tree_free(&contents->nodes);
+ zone_tree_free(&contents->nsec3_nodes);
+
+ dnssec_nsec3_params_free(&contents->nsec3_params);
+
+ free(contents);
+}
+
+void zone_contents_deep_free(zone_contents_t *contents)
+{
+ if (contents == NULL) {
+ return;
+ }
+
+ if (contents != NULL) {
+ // Delete NSEC3 tree.
+ (void)zone_tree_apply(contents->nsec3_nodes,
+ destroy_node_rrsets_from_tree, NULL);
+
+ // Delete the normal tree.
+ (void)zone_tree_apply(contents->nodes,
+ destroy_node_rrsets_from_tree, NULL);
+ }
+
+ zone_contents_free(contents);
+}
+
+uint32_t zone_contents_serial(const zone_contents_t *zone)
+{
+ if (zone == NULL) {
+ return 0;
+ }
+
+ const knot_rdataset_t *soa = node_rdataset(zone->apex, KNOT_RRTYPE_SOA);
+ if (soa == NULL) {
+ return 0;
+ }
+
+ return knot_soa_serial(soa->rdata);
+}
+
+void zone_contents_set_soa_serial(zone_contents_t *zone, uint32_t new_serial)
+{
+ knot_rdataset_t *soa;
+ if (zone != NULL && (soa = node_rdataset(zone->apex, KNOT_RRTYPE_SOA)) != NULL) {
+ knot_soa_serial_set(soa->rdata, new_serial);
+ }
+}
+
+bool zone_contents_is_empty(const zone_contents_t *zone)
+{
+ if (zone == NULL) {
+ return true;
+ }
+
+ bool apex_empty = (zone->apex == NULL || zone->apex->rrset_count == 0);
+ bool no_non_apex = (zone_tree_count(zone->nodes) <= (zone->apex != NULL ? 1 : 0));
+ bool no_nsec3 = zone_tree_is_empty(zone->nsec3_nodes);
+
+ return (apex_empty && no_non_apex && no_nsec3);
+}
+
+size_t zone_contents_measure_size(zone_contents_t *zone)
+{
+ zone->size = 0;
+ zone_contents_apply(zone, measure_size, &zone->size);
+ return zone->size;
+}
+
+uint32_t zone_contents_max_ttl(zone_contents_t *zone)
+{
+ zone->max_ttl = 0;
+ zone_contents_apply(zone, measure_max_ttl, &zone->size);
+ return zone->max_ttl;
+}