/*  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/>
 */

#pragma once

#include "contrib/macros.h"
#include "contrib/mempattern.h"
#include "libknot/descriptor.h"
#include "libknot/dname.h"
#include "libknot/rrset.h"
#include "libknot/rdataset.h"

struct rr_data;

/*!
 * \brief Structure representing one node in a domain name tree, i.e. one domain
 *        name in a zone.
 */
typedef struct zone_node {
	knot_dname_t *owner; /*!< Domain name being the owner of this node. */
	struct zone_node *parent; /*!< Parent node in the name hierarchy. */

	/*! \brief Array with data of RRSets belonging to this node. */
	struct rr_data *rrs;

	/*!
	 * \brief Previous node in canonical order. Only authoritative
	 *        nodes or delegation points are referenced by this.
	 */
	struct zone_node *prev;
	union {
		knot_dname_t *nsec3_hash; /*! Name of the NSEC3 corresponding to this node. */
		struct zone_node *nsec3_node; /*! NSEC3 node corresponding to this node.
		\warning This always points to first part of that bi-node!
		assert(!(node->nsec3_node & NODE_FLAGS_SECOND)); */
	};
	knot_dname_t *nsec3_wildcard_name; /*! Name of NSEC3 node proving wildcard nonexistence. */
	uint32_t children; /*!< Count of children nodes in DNS hierarchy. */
	uint16_t rrset_count; /*!< Number of RRSets stored in the node. */
	uint16_t flags; /*!< \ref node_flags enum. */
} zone_node_t;

/*!< \brief Glue node context. */
typedef struct {
	const zone_node_t *node; /*!< Glue node. */
	uint16_t ns_pos; /*!< Corresponding NS record position (for compression). */
	bool optional; /*!< Optional glue indicator. */
} glue_t;

/*!< \brief Additional data. */
typedef struct {
	glue_t *glues; /*!< Glue data. */
	uint16_t count; /*!< Number of glue nodes. */
} additional_t;

/*!< \brief Structure storing RR data. */
struct rr_data {
	uint32_t ttl; /*!< RRSet TTL. */
	uint16_t type; /*!< RR type of data. */
	knot_rdataset_t rrs; /*!< Data of given type. */
	additional_t *additional; /*!< Additional nodes with glues. */
};

/*! \brief Flags used to mark nodes with some property. */
enum node_flags {
	/*! \brief Node is authoritative, default. */
	NODE_FLAGS_AUTH =            0 << 0,
	/*! \brief Node is a delegation point (i.e. marking a zone cut). */
	NODE_FLAGS_DELEG =           1 << 0,
	/*! \brief Node is not authoritative (i.e. below a zone cut). */
	NODE_FLAGS_NONAUTH =         1 << 1,
	/*! \brief RRSIGs in node have been cryptographically validated by Knot. */
	NODE_FLAGS_RRSIGS_VALID =    1 << 2,
	/*! \brief Node is empty and will be deleted after update. */
	NODE_FLAGS_EMPTY =           1 << 3,
	/*! \brief Node has a wildcard child. */
	NODE_FLAGS_WILDCARD_CHILD =  1 << 4,
	/*! \brief Is this NSEC3 node compatible with zone's NSEC3PARAMS ? */
	NODE_FLAGS_IN_NSEC3_CHAIN =  1 << 5,
	/*! \brief Node is the zone Apex. */
	NODE_FLAGS_APEX =            1 << 6,
	/*! \brief The nsec3_node pointer is valid and and nsec3_hash pointer invalid. */
	NODE_FLAGS_NSEC3_NODE =      1 << 7,
	/*! \brief Is this i bi-node? */
	NODE_FLAGS_BINODE =          1 << 8, // this value shall be fixed
	/*! \brief Is this the second half of bi-node? */
	NODE_FLAGS_SECOND =          1 << 9, // this value shall be fixed
	/*! \brief The node shall be deleted. It's just not because it's a bi-node and the counterpart still exists. */
	NODE_FLAGS_DELETED =         1 << 10,
	/*! \brief The node or some node in subtree has some authoritative data in it (possibly also DS at deleg). */
	NODE_FLAGS_SUBTREE_AUTH =    1 << 11,
	/*! \brief The node or some node in subtree has any data in it, possibly just insec deleg. */
	NODE_FLAGS_SUBTREE_DATA =    1 << 12,
	/*! \brief Node owner name contains \0 byte in some label. */
	NODE_FLAGS_NULLBYTE =        1 << 13,
};

typedef void (*node_addrem_cb)(zone_node_t *, void *);
typedef zone_node_t *(*node_new_cb)(const knot_dname_t *, void *);

/*!
 * \brief Clears additional structure.
 *
 * \param additional  Additional to clear.
 */
void additional_clear(additional_t *additional);

/*!
 * \brief Compares additional structures on equivalency.
 */
bool additional_equal(additional_t *a, additional_t *b);

/*!
 * \brief Creates and initializes new node structure.
 *
 * \param owner  Node's owner, will be duplicated.
 * \param binode Create bi-node.
 * \param second The second part of the bi-node shall be used now.
 * \param mm     Memory context to use.
 *
 * \return Newly created node or NULL if an error occurred.
 */
zone_node_t *node_new(const knot_dname_t *owner, bool binode, bool second, knot_mm_t *mm);

/*!
 * \brief Synchronize contents of both binode's nodes.
 *
 * \param node           Pointer to either of nodes in a binode.
 * \param free_deleted   When the unified node has DELETED flag, free it afterwards.
 * \param mm             Memory context.
 */
void binode_unify(zone_node_t *node, bool free_deleted, knot_mm_t *mm);

/*!
 * \brief This must be called before any change to either of the bi-node's node's rdatasets.
 */
int binode_prepare_change(zone_node_t *node, knot_mm_t *mm);

/*!
 * \brief Get the correct node of a binode.
 *
 * \param node     Pointer to either of nodes in a binode.
 * \param second   Get the second node (first otherwise).
 *
 * \return Pointer to correct node.
 */
inline static zone_node_t *binode_node(zone_node_t *node, bool second)
{
	if (unlikely(node == NULL || !(node->flags & NODE_FLAGS_BINODE))) {
		assert(node == NULL || !(node->flags & NODE_FLAGS_SECOND));
		return node;
	}
	return node + (second - (int)((node->flags & NODE_FLAGS_SECOND) >> 9));
}

inline static zone_node_t *binode_first(zone_node_t *node)
{
	return binode_node(node, false);
}

inline static zone_node_t *binode_node_as(zone_node_t *node, const zone_node_t *as)
{
	assert(node == NULL || (as->flags & NODE_FLAGS_BINODE) == (node->flags & NODE_FLAGS_BINODE));
	return binode_node(node, (as->flags & NODE_FLAGS_SECOND));
}

/*!
 * \brief Return the other node from a bi-node.
 *
 * \param node   A node in a bi-node.
 *
 * \return The counterpart node in the same bi-node.
 */
zone_node_t *binode_counterpart(zone_node_t *node);

/*!
 * \brief Return true if the rdataset of specified type is shared (shallow-copied) among both parts of bi-node.
 */
bool binode_rdata_shared(zone_node_t *node, uint16_t type);

/*!
 * \brief Return true if the additionals to rdataset of specified type are shared among both parts of bi-node.
 */
bool binode_additional_shared(zone_node_t *node, uint16_t type);

/*!
 * \brief Return true if the additionals are unchanged between two nodes (usually a bi-node).
 */
bool binode_additionals_unchanged(zone_node_t *node, zone_node_t *counterpart);

/*!
 * \brief Destroys allocated data within the node
 *        structure, but not the node itself.
 *
 * \param node  Node that contains data to be destroyed.
 * \param mm    Memory context to use.
 */
void node_free_rrsets(zone_node_t *node, knot_mm_t *mm);

/*!
 * \brief Destroys the node structure.
 *
 * Does not destroy the data within the node.
 *
 * \param node  Node to be destroyed.
 * \param mm    Memory context to use.
 */
void node_free(zone_node_t *node, knot_mm_t *mm);

/*!
 * \brief Adds an RRSet to the node. All data are copied. Owner and class are
 *        not used at all.
 *
 * \param node     Node to add the RRSet to.
 * \param rrset    RRSet to add.
 * \param mm       Memory context to use.
 *
 * \return KNOT_E*
 * \retval KNOT_ETTL  RRSet TTL was updated.
 */
int node_add_rrset(zone_node_t *node, const knot_rrset_t *rrset, knot_mm_t *mm);

/*!
 * \brief Removes data for given RR type from node.
 *
 * \param node  Node we want to delete from.
 * \param type  RR type to delete.
 */
void node_remove_rdataset(zone_node_t *node, uint16_t type);

/*!
 * \brief Remove all RRs from RRSet from the node.
 *
 * \param node    Node to remove from.
 * \param rrset   RRSet with RRs to be removed.
 * \param mm      Memory context.
 *
 * \return KNOT_E*
 */
int node_remove_rrset(zone_node_t *node, const knot_rrset_t *rrset, knot_mm_t *mm);

/*!
 * \brief Returns the RRSet of the given type from the node. RRSet is allocated.
 *
 * \param node  Node to get the RRSet from.
 * \param type  RR type of the RRSet to retrieve.
 *
 * \return RRSet from node \a node having type \a type, or NULL if no such
 *         RRSet exists in this node.
 */
knot_rrset_t *node_create_rrset(const zone_node_t *node, uint16_t type);

/*!
 * \brief Gets rdata set structure of given type from node.
 *
 * \param node  Node to get data from.
 * \param type  RR type of data to get.
 *
 * \return Pointer to data if found, NULL otherwise.
 */
knot_rdataset_t *node_rdataset(const zone_node_t *node, uint16_t type);

/*!
 * \brief Returns parent node (fixing bi-node issue) of given node.
 */
inline static zone_node_t *node_parent(const zone_node_t *node)
{
	return binode_node_as(node->parent, node);
}

/*!
 * \brief Returns previous (lexicographically in same zone tree) node (fixing bi-node issue) of given node.
 */
inline static zone_node_t *node_prev(const zone_node_t *node)
{
	return binode_node_as(node->prev, node);
}

/*!
 * \brief Return node referenced by a glue.
 *
 * \param glue                Glue in question.
 * \param another_zone_node   Another node from the same zone.
 *
 * \return Glue node.
 */
inline static const zone_node_t *glue_node(const glue_t *glue, const zone_node_t *another_zone_node)
{
	return binode_node_as((zone_node_t *)glue->node, another_zone_node);
}

/*!
 * \brief Add a flag to this node and all (grand-)parents until the flag is present.
 */
inline static void node_set_flag_hierarch(zone_node_t *node, uint16_t fl)
{
	for (zone_node_t *i = node; i != NULL && (i->flags & fl) != fl; i = node_parent(i)) {
		i->flags |= fl;
	}
}

/*!
 * \brief Checks whether node contains any RRSIG for given type.
 *
 * \param node  Node to check in.
 * \param type  Type to check for.
 *
 * \return True/False.
 */
bool node_rrtype_is_signed(const zone_node_t *node, uint16_t type);

/*!
 * \brief Checks whether node contains RRSet for given type.
 *
 * \param node  Node to check in.
 * \param type  Type to check for.
 *
 * \return True/False.
 */
inline static bool node_rrtype_exists(const zone_node_t *node, uint16_t type)
{
	return node_rdataset(node, type) != NULL;
}

/*!
 * \brief Checks whether node is empty. Node is empty when NULL or when no
 *        RRSets are in it.
 *
 * \param node  Node to check in.
 *
 * \return True/False.
 */
inline static bool node_empty(const zone_node_t *node)
{
	return node == NULL || node->rrset_count == 0;
}

/*!
 * \brief Check whether two nodes have equal set of rrtypes.
 *
 * \param a  A node.
 * \param b  Another node.
 *
 * \return True/False.
 */
bool node_bitmap_equal(const zone_node_t *a, const zone_node_t *b);

/*!
 * \brief Returns RRSet structure initialized with data from node.
 *
 * \param node   Node containing RRSet.
 * \param type   RRSet type we want to get.
 *
 * \return RRSet structure with wanted type, or empty RRSet.
 */
static inline knot_rrset_t node_rrset(const zone_node_t *node, uint16_t type)
{
	knot_rrset_t rrset;
	for (uint16_t i = 0; node && i < node->rrset_count; ++i) {
		if (node->rrs[i].type == type) {
			struct rr_data *rr_data = &node->rrs[i];
			knot_rrset_init(&rrset, node->owner, type, KNOT_CLASS_IN,
			                rr_data->ttl);
			rrset.rrs = rr_data->rrs;
			rrset.additional = rr_data->additional;
			return rrset;
		}
	}
	knot_rrset_init_empty(&rrset);
	return rrset;
}

/*!
 * \brief Returns RRSet structure initialized with data from node at position
 *        equal to \a pos.
 *
 * \param node  Node containing RRSet.
 * \param pos   RRSet position we want to get.
 *
 * \return RRSet structure with data from wanted position, or empty RRSet.
 */
static inline knot_rrset_t node_rrset_at(const zone_node_t *node, size_t pos)
{
	knot_rrset_t rrset;
	if (node == NULL || pos >= node->rrset_count) {
		knot_rrset_init_empty(&rrset);
		return rrset;
	}

	struct rr_data *rr_data = &node->rrs[pos];
	knot_rrset_init(&rrset, node->owner, rr_data->type, KNOT_CLASS_IN,
	                rr_data->ttl);
	rrset.rrs = rr_data->rrs;
	rrset.additional = rr_data->additional;
	return rrset;
}

/*!
 * \brief Return the relevant NSEC3 node (if specified by adjusting), or NULL.
 */
static inline zone_node_t *node_nsec3_get(const zone_node_t *node)
{
	if (!(node->flags & NODE_FLAGS_NSEC3_NODE) || node->nsec3_node == NULL) {
		return NULL;
	} else {
		return binode_node_as(node->nsec3_node, node);
	}
}
