/*  Copyright (C) 2022 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 <https://www.gnu.org/licenses/>.
 */

/*!
 * \file
 *
 * \brief Knot DNS module interface.
 *
 * \addtogroup module
 * @{
 */

#pragma once

#include <stdarg.h>
#include <stdint.h>
#include <syslog.h>
#include <sys/socket.h>

#include <libknot/libknot.h>
#include <libknot/yparser/ypschema.h>

/*** Query module API. ***/

/*! Current module ABI version. */
#define KNOTD_MOD_ABI_VERSION	400
/*! Module configuration name prefix. */
#define KNOTD_MOD_NAME_PREFIX	"mod-"

/*! Configuration check function context. */
typedef struct {
	const yp_item_t *item; /*!< Current item descriptor. */
	const uint8_t *id;     /*!< Current section identifier. */
	size_t id_len;         /*!< Current section identifier length. */
	const uint8_t *data;   /*!< Current item data. */
	size_t data_len;       /*!< Current item data length. */
	const char *err_str;   /*!< Output error message. */
	struct knotd_conf_check_extra *extra; /*!< Private items (conf/tools.h). */
} knotd_conf_check_args_t;

/*! Module context. */
typedef struct knotd_mod knotd_mod_t;

/*!
 * Module load callback.
 *
 * Responsibilities:
 *  - Query processing hooks registration
 *  - Optional module specific context initialization
 *  - Module configuration processing
 *  - Query statistics counters registration
 *
 * \param[in] mod  Module context.
 *
 * \return Error code, KNOT_EOK if success.
 */
typedef int (*knotd_mod_load_f)(knotd_mod_t *mod);

/*!
 * Module unload callback.
 *
 * Responsibilities:
 *  - Optional module specific context deinitialization
 *
 * \param[in] mod  Module context.
 */
typedef void (*knotd_mod_unload_f)(knotd_mod_t *mod);

/*!
 * Module configuration section check callback.
 *
 * Responsibilities:
 *  - Optional module configuration section items checks.
 *
 * \note Set args.err_str to proper error message if error.
 *
 * \param[in] args  Configuration check arguments.
 *
 * \return Error code, KNOT_EOK if success.
 */
typedef int (*knotd_conf_check_f)(knotd_conf_check_args_t *args);

/*! Module flags. */
typedef enum {
	KNOTD_MOD_FLAG_NONE         = 0,      /*!< Unspecified. */
	KNOTD_MOD_FLAG_OPT_CONF     = 1 << 0, /*!< Optional module configuration. */
	KNOTD_MOD_FLAG_SCOPE_GLOBAL = 1 << 1, /*!< Can be specified as global module. */
	KNOTD_MOD_FLAG_SCOPE_ZONE   = 1 << 2, /*!< Can be specified as zone module. */
	KNOTD_MOD_FLAG_SCOPE_ANY    = KNOTD_MOD_FLAG_SCOPE_GLOBAL |
	                              KNOTD_MOD_FLAG_SCOPE_ZONE,
} knotd_mod_flag_t;

/*! Module API. */
typedef struct {
	uint32_t version;                /*!< Embedded version of the module ABI. */
	const char *name;                /*!< Module name. */
	knotd_mod_flag_t flags;          /*!< Module flags. */
	knotd_mod_load_f load;           /*!< Module load callback. */
	knotd_mod_unload_f unload;       /*!< Module unload callback. */
	const yp_item_t *config;         /*!< Module configuration schema. */
	knotd_conf_check_f config_check; /*!< Module configuration check callback. */
} knotd_mod_api_t;

/*! Static module API symbol must have a unique name. */
#ifdef KNOTD_MOD_STATIC
 #define KNOTD_MOD_API_NAME(mod_name) knotd_mod_api_##mod_name
#else
 #define KNOTD_MOD_API_NAME(mod_name) knotd_mod_api
#endif

/*! Module API instance initialization helper macro. */
#define KNOTD_MOD_API(mod_name, mod_flags, mod_load, mod_unload, mod_conf, mod_conf_check) \
	__attribute__((visibility("default"))) \
	const knotd_mod_api_t KNOTD_MOD_API_NAME(mod_name) = { \
		.version = KNOTD_MOD_ABI_VERSION, \
		.name = KNOTD_MOD_NAME_PREFIX #mod_name, \
		.flags = mod_flags, \
		.load = mod_load, \
		.unload = mod_unload, \
		.config = mod_conf, \
		.config_check = mod_conf_check, \
	}

/*** Configuration, statistics, logging,... API. ***/

/*!
 * Checks reference item (YP_TREF) value if the destination exists.
 *
 * \note This function is intended to be used in module schema.
 *
 * \param[in] args  Configuration check arguments.
 *
 * \return Error code, KNOT_EOK if success.
 */
int knotd_conf_check_ref(knotd_conf_check_args_t *args);

/*!
 * Gets optional module context.
 *
 * \param[in] mod  Module context.
 *
 * \return Pointer to optional module context.
 */
void *knotd_mod_ctx(knotd_mod_t *mod);

/*!
 * Sets optional module context.
 *
 * \param[in] mod  Module context.
 * \param[in] ctx  Optional module context.
 */
void knotd_mod_ctx_set(knotd_mod_t *mod, void *ctx);

/*!
 * Gets the zone name the module is configured for.
 *
 * \param[in] mod  Module context.
 *
 * \return Zone name.
 */
const knot_dname_t *knotd_mod_zone(knotd_mod_t *mod);

/*!
 * Emits a module specific log message.
 *
 * \param[in] mod       Module context.
 * \param[in] priority  Message priority (LOG_DEBUG...LOG_CRIT).
 * \param[in] fmt       Content of the message.
 */
void knotd_mod_log(knotd_mod_t *mod, int priority, const char *fmt, ...);

/*!
 * Emits a module specific log message (va_list variant).
 *
 * \param[in] mod       Module context.
 * \param[in] priority  Message priority (LOG_DEBUG...LOG_CRIT).
 * \param[in] fmt       Content of the message.
 * \param[in] args      Variable argument list.
 */
void knotd_mod_vlog(knotd_mod_t *mod, int priority, const char *fmt, va_list args);

/*!
 * Statistics multi-counter index to name transformation callback.
 *
 * \param[in] idx        Multi-counter index.
 * \param[in] idx_count  Number of subcounters.
 *
 * \return Index name string.
 */
typedef char* (*knotd_mod_idx_to_str_f)(uint32_t idx, uint32_t idx_count);

/*!
 * Registers a statistics counter.
 *
 * \param[in] mod         Module context.
 * \param[in] ctr_name    Counter name
 * \param[in] idx_count   Number of subcounters (set 1 for single-counter).
 * \param[in] idx_to_str  Subcounter index to name transformation callback
 *                        (set NULL for single-counter).
 *
 * \return Error code, KNOT_EOK if success.
 */
int knotd_mod_stats_add(knotd_mod_t *mod, const char *ctr_name, uint32_t idx_count,
                        knotd_mod_idx_to_str_f idx_to_str);

/*!
 * Increments a statistics counter.
 *
 * \param[in] mod     Module context.
 * \param[in] thr_id  Index of worker thread.
 * \param[in] ctr_id  Counter id (counted in the order the counters were registered).
 * \param[in] idx     Subcounter index (set 0 for single-counter).
 * \param[in] val     Value increment.
 */
void knotd_mod_stats_incr(knotd_mod_t *mod, unsigned thr_id, uint32_t ctr_id,
                          uint32_t idx, uint64_t val);

/*!
 * Decrements a statistics counter.
 *
 * \param[in] mod     Module context.
 * \param[in] thr_id  Index of worker thread.
 * \param[in] ctr_id  Counter id (counted in the order the counters were registered).
 * \param[in] idx     Subcounter index (set 0 for single-counter).
 * \param[in] val     Value decrement.
 */
void knotd_mod_stats_decr(knotd_mod_t *mod, unsigned thr_id, uint32_t ctr_id,
                          uint32_t idx, uint64_t val);

/*!
 * Sets a statistics counter value.
 *
 * \param[in] mod     Module context.
 * \param[in] thr_id  Index of worker thread.
 * \param[in] ctr_id  Counter id (counted in the order the counters were registered).
 * \param[in] idx     Subcounter index (set 0 for single-counter).
 * \param[in] val     Value.
 */
void knotd_mod_stats_store(knotd_mod_t *mod, unsigned thr_id, uint32_t ctr_id,
                           uint32_t idx, uint64_t val);

/*! Configuration single-value abstraction. */
typedef union {
	int64_t integer;
	unsigned option;
	bool boolean;
	const char *string;
	const knot_dname_t *dname;
	struct {
		struct sockaddr_storage addr;
		struct sockaddr_storage addr_max;
		int addr_mask;
	};
	struct {
		const uint8_t *data;
		size_t data_len;
	};
} knotd_conf_val_t;

/*! Configuration value. */
typedef struct {
	knotd_conf_val_t single; /*!< Single-valued item data. */
	knotd_conf_val_t *multi; /*!< Multi-valued item data. */
	size_t count;            /*!< Number of items (0 if default single value). */
} knotd_conf_t;

/*! Environment items. */
typedef enum {
	KNOTD_CONF_ENV_VERSION     = 0, /*!< Software version. */
	KNOTD_CONF_ENV_HOSTNAME    = 1, /*!< Current hostname. */
	KNOTD_CONF_ENV_WORKERS_UDP = 2, /*!< Current number of UDP workers. */
	KNOTD_CONF_ENV_WORKERS_TCP = 3, /*!< Current number of TCP workers. */
	KNOTD_CONF_ENV_WORKERS_XDP = 4, /*!< Current number of UDP-over-XDP workers. */
} knotd_conf_env_t;

/*!
 * Gets general configuration value.
 *
 * \param[in] mod           Module context.
 * \param[in] section_name  Section name.
 * \param[in] item_name     Section item name.
 * \param[in] id            Section identifier (NULL for simple section).
 *
 * \return Configuration value.
 */
knotd_conf_t knotd_conf(knotd_mod_t *mod, const yp_name_t *section_name,
                        const yp_name_t *item_name, const knotd_conf_t *id);

/*!
 * Gets environment value.
 *
 * \param[in] mod  Module context.
 * \param[in] env  Environment item.
 *
 * \return Configuration value.
 */
knotd_conf_t knotd_conf_env(knotd_mod_t *mod, knotd_conf_env_t env);

/*!
 * Gets number of answering threads.
 *
 * \param[in] mod  Module context.
 *
 * \return Number of worker threads.
 */
unsigned knotd_mod_threads(knotd_mod_t *mod);

/*!
 * Gets module configuration value.
 *
 * \param[in] mod        Module context.
 * \param[in] item_name  Module section item name.
 *
 * \return Configuration value.
 */
knotd_conf_t knotd_conf_mod(knotd_mod_t *mod, const yp_name_t *item_name);

/*!
 * Gets zone configuration value.
 *
 * \param[in] mod        Module context.
 * \param[in] item_name  Zone section item name.
 * \param[in] zone       Zone name.
 *
 * \return Configuration value.
 */
knotd_conf_t knotd_conf_zone(knotd_mod_t *mod, const yp_name_t *item_name,
                             const knot_dname_t *zone);

/*!
 * Gets module configuration value during the checking phase.
 *
 * \note This function is intended to be used in 'knotd_conf_check_f' callbacks.
 *
 * \param[in] args
 * \param[in] item_name
 *
 * \return Configuration value.
 */
knotd_conf_t knotd_conf_check_item(knotd_conf_check_args_t *args,
                                   const yp_name_t *item_name);

/*!
 * \brief Checks if address is in at least one of given ranges.
 *
 * \param[in] range
 * \param[in] addr
 *
 * \return true if addr is in at least one range, false otherwise.
 */
bool knotd_conf_addr_range_match(const knotd_conf_t *range,
                                 const struct sockaddr_storage *addr);

/*!
 * Deallocates multi-valued configuration values.
 *
 * \param[in] conf  Configuration value.
 */
void knotd_conf_free(knotd_conf_t *conf);

/*** Query processing API. ***/

/*!
 * DNS query type.
 *
 * This type encompasses the different query types distinguished by both the
 * OPCODE and the QTYPE.
 */
typedef enum {
	KNOTD_QUERY_TYPE_INVALID, /*!< Invalid query. */
	KNOTD_QUERY_TYPE_NORMAL,  /*!< Normal query. */
	KNOTD_QUERY_TYPE_AXFR,    /*!< Request for AXFR transfer. */
	KNOTD_QUERY_TYPE_IXFR,    /*!< Request for IXFR transfer. */
	KNOTD_QUERY_TYPE_NOTIFY,  /*!< NOTIFY query. */
	KNOTD_QUERY_TYPE_UPDATE,  /*!< Dynamic update. */
} knotd_query_type_t;

/*! Supported transport protocols. */
typedef enum {
	KNOTD_QUERY_PROTO_UDP  = KNOT_PROBE_PROTO_UDP,  /*!< Pure UDP. */
	KNOTD_QUERY_PROTO_TCP  = KNOT_PROBE_PROTO_TCP,  /*!< Pure TCP. */
	KNOTD_QUERY_PROTO_QUIC = KNOT_PROBE_PROTO_QUIC, /*!< QUIC/UDP. */
} knotd_query_proto_t;

/*! Query processing specific flags. */
typedef enum {
	KNOTD_QUERY_FLAG_COOKIE  = 1 << 0, /*!< Valid DNS Cookie indication. */
} knotd_query_flag_t;

/*! Query processing data context parameters. */
typedef struct {
	knotd_query_proto_t proto;             /*!< Transport protocol used. */
	knotd_query_flag_t flags;              /*!< Current query flags. */
	const struct sockaddr_storage *remote; /*!< Current remote address. */
	int socket;                            /*!< Current network socket. */
	unsigned thread_id;                    /*!< Current thread id. */
	void *server;                          /*!< Server object private item. */
	const struct knot_xdp_msg *xdp_msg;    /*!< Possible XDP message context. */
	uint32_t measured_rtt;                 /*!< Measured RTT in usecs: QUIC or TCP-XDP. */
} knotd_qdata_params_t;

/*! Query processing data context. */
typedef struct {
	knot_pkt_t *query;              /*!< Query to be solved. */
	knotd_query_type_t type;        /*!< Query packet type. */
	const knot_dname_t *name;       /*!< Currently processed name. */
	uint16_t rcode;                 /*!< Resulting RCODE (Whole extended RCODE). */
	uint16_t rcode_tsig;            /*!< Resulting TSIG RCODE. */
	int rcode_ede;                  /*!< Resulting Extended (EDE) RCODE. */
	knot_rrset_t opt_rr;            /*!< OPT record. */
	knot_sign_context_t sign;       /*!< Signing context. */
	knot_edns_client_subnet_t *ecs; /*!< EDNS Client Subnet option. */
	bool err_truncated;             /*!< Set TC and AA bits if an error reply. */

	/*! Persistent items on processing reset. */
	knot_mm_t *mm;                /*!< Memory context. */
	knotd_qdata_params_t *params; /*!< Low-level processing parameters. */

	struct knotd_qdata_extra *extra; /*!< Private items (process_query.h). */
} knotd_qdata_t;

/*!
 * Gets the local (destination) address of the query.
 *
 * \param[in] qdata  Query data.
 * \param[out] buff  Auxiliary buffer (not used for XDP).
 *
 * \return Local address or NULL if error.
 */
const struct sockaddr_storage *knotd_qdata_local_addr(knotd_qdata_t *qdata,
                                                      struct sockaddr_storage *buff);

/*!
 * Gets the remote (source) address of the query.
 *
 * \param[in] qdata  Query data.
 *
 * \return Remote address or NULL if error.
 */
const struct sockaddr_storage *knotd_qdata_remote_addr(knotd_qdata_t *qdata);

/*!
 * Gets the measured TCP round-trip-time.
 *
 * \param[in] qdata  Query data.
 *
 * \return RTT in microseconds or 0 if error or not available.
 */
uint32_t knotd_qdata_rtt(knotd_qdata_t *qdata);

/*!
 * Gets the current zone name.
 *
 * \param[in] qdata  Query data.
 *
 * \return Zone name.
 */
const knot_dname_t *knotd_qdata_zone_name(knotd_qdata_t *qdata);

/*!
 * Gets the current zone apex rrset of the given type.
 *
 * \param[in] qdata  Query data.
 * \param[in] type   Rrset type.
 *
 * \return A copy of the zone apex rrset.
 */
knot_rrset_t knotd_qdata_zone_apex_rrset(knotd_qdata_t *qdata, uint16_t type);

/*! General query processing states. */
typedef enum {
	KNOTD_STATE_NOOP  = 0, /*!< No response. */
	KNOTD_STATE_DONE  = 4, /*!< Finished. */
	KNOTD_STATE_FAIL  = 5, /*!< Error. */
	KNOTD_STATE_FINAL = 6, /*!< Finished and finalized (QNAME, EDNS, TSIG). */
} knotd_state_t;

/*! brief Internet query processing states. */
typedef enum {
	KNOTD_IN_STATE_BEGIN,  /*!< Begin name resolution. */
	KNOTD_IN_STATE_NODATA, /*!< Positive result with NO data. */
	KNOTD_IN_STATE_HIT,    /*!< Positive result. */
	KNOTD_IN_STATE_MISS,   /*!< Negative result. */
	KNOTD_IN_STATE_DELEG,  /*!< Result is delegation. */
	KNOTD_IN_STATE_FOLLOW, /*!< Resolution not complete (CNAME/DNAME chain). */
	KNOTD_IN_STATE_TRUNC,  /*!< Finished, packet size limit encountered. */
	KNOTD_IN_STATE_ERROR,  /*!< Resolution failed. */
} knotd_in_state_t;

/*! Query module processing stages. */
typedef enum {
	KNOTD_STAGE_BEGIN = 0,  /*!< Before query processing. */
	KNOTD_STAGE_PREANSWER,  /*!< Before section processing. */
	KNOTD_STAGE_ANSWER,     /*!< Answer section processing. */
	KNOTD_STAGE_AUTHORITY,  /*!< Authority section processing. */
	KNOTD_STAGE_ADDITIONAL, /*!< Additional section processing. */
	KNOTD_STAGE_END,        /*!< After query processing. */
} knotd_stage_t;

/*!
 * General processing hook.
 *
 * \param[in] state    Current processing state.
 * \param[in,out] pkt  Response packet.
 * \param[in] qdata    Query data.
 * \param[in] mod      Module context.
 *
 * \return Next processing state.
 */
typedef knotd_state_t (*knotd_mod_hook_f)
	(knotd_state_t state, knot_pkt_t *pkt, knotd_qdata_t *qdata, knotd_mod_t *mod);

/*!
 * Internet class processing hook.
 *
 * \param[in] state    Current processing state.
 * \param[in,out] pkt  Response packet.
 * \param[in] qdata    Query data.
 * \param[in] mod      Module context.
 *
 * \return Next processing state.
 */
typedef knotd_in_state_t (*knotd_mod_in_hook_f)
	(knotd_in_state_t state, knot_pkt_t *pkt, knotd_qdata_t *qdata, knotd_mod_t *mod);

/*!
 * Registers general processing module hook.
 *
 * \param[in] mod    Module context.
 * \param[in] stage  Processing stage (KNOTD_STAGE_BEGIN or KNOTD_STAGE_END).
 * \param[in] hook   Module hook.
 *
 * \return Error code, KNOT_EOK if success.
 */
int knotd_mod_hook(knotd_mod_t *mod, knotd_stage_t stage, knotd_mod_hook_f hook);

/*!
 * Registers Internet class module hook.
 *
 * \param[in] mod    Module context.
 * \param[in] stage  Processing stage (KNOTD_STAGE_ANSWER..KNOTD_STAGE_ADDITIONAL).
 * \param[in] hook   Module hook.
 *
 * \return Error code, KNOT_EOK if success.
 */
int knotd_mod_in_hook(knotd_mod_t *mod, knotd_stage_t stage, knotd_mod_in_hook_f hook);

/*** DNSSEC API. ***/

/*!
 * Initializes DNSSEC signing context.
 *
 * \param[in] mod  Module context.
 *
 * \return Error code, KNOT_EOK if success.
 */
int knotd_mod_dnssec_init(knotd_mod_t *mod);

/*!
 * Loads available DNSSEC signing keys.
 *
 * \param[in] mod      Module context.
 * \param[in] verbose  Print key summary into log indication.
 *
 * \return Error code, KNOT_EOK if success.
 */
int knotd_mod_dnssec_load_keyset(knotd_mod_t *mod, bool verbose);

/*!
 * Frees up resources before re-loading DNSSEC signing keys.
 *
 * \param[in] mod  Module context.
 */
void knotd_mod_dnssec_unload_keyset(knotd_mod_t *mod);

/*!
 * Generates RRSIGs for given RRSet.
 *
 * \param[in] mod      Module context.
 * \param[out] rrsigs  Output RRSIG RRSet.
 * \param[in] rrset    Input RRSet to generate RRSIGs for.
 * \param[in] mm       Memory context.
 *
 * \return Error code, KNOT_EOK if success.
 */
int knotd_mod_dnssec_sign_rrset(knotd_mod_t *mod, knot_rrset_t *rrsigs,
                                const knot_rrset_t *rrset, knot_mm_t *mm);

/*! @} */
