/*  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 "libknot/dynarray.h"
#include "libdnssec/keystore.h"
#include "libdnssec/sign.h"
#include "knot/dnssec/kasp/kasp_zone.h"
#include "knot/dnssec/kasp/policy.h"
#include "knot/dnssec/context.h"

/*!
 * \brief Zone key context used during signing.
 */
typedef struct {
	const char *id;
	dnssec_key_t *key;

	dnssec_binary_t precomputed_ds;
	dnssec_key_digest_t precomputed_digesttype;

	knot_time_t next_event;

	bool is_ksk;
	bool is_zsk;
	bool is_active;
	bool is_public;
	bool is_ready;
	bool is_zsk_active_plus;
	bool is_ksk_active_plus;
	bool is_pub_only;
	bool is_revoked;
} zone_key_t;

knot_dynarray_declare(keyptr, zone_key_t *, DYNARRAY_VISIBILITY_NORMAL, 1)

typedef struct {
	size_t count;
	zone_key_t *keys;
} zone_keyset_t;

/*!
 * \brief Signing context used for single signing thread.
 */
typedef struct {
	size_t count;                     // number of keys in keyset
	zone_key_t *keys;                 // keys in keyset
	dnssec_sign_ctx_t **sign_ctxs;    // signing buffers for keys in keyset
	const kdnssec_ctx_t *dnssec_ctx;  // dnssec context
} zone_sign_ctx_t;

/*!
 * \brief Flags determining key type
 */
enum {
	DNSKEY_FLAGS_ZSK     = KNOT_DNSKEY_FLAG_ZONE,
	DNSKEY_FLAGS_KSK     = KNOT_DNSKEY_FLAG_ZONE | KNOT_DNSKEY_FLAG_SEP,
	DNSKEY_FLAGS_REVOKED = KNOT_DNSKEY_FLAG_ZONE | KNOT_DNSKEY_FLAG_SEP | KNOT_DNSKEY_FLAG_REVOKE,
};

inline static uint16_t dnskey_flags(bool is_ksk)
{
	return is_ksk ? DNSKEY_FLAGS_KSK : DNSKEY_FLAGS_ZSK;
}

typedef enum {
	DNSKEY_GENERATE_KSK       = (1 << 0), // KSK flag in metadata
	DNSKEY_GENERATE_ZSK       = (1 << 1), // ZSK flag in metadata
	DNSKEY_GENERATE_SEP_SPEC  = (1 << 2), // not (SEP bit set iff KSK)
	DNSKEY_GENERATE_SEP_ON    = (1 << 3), // SEP bit set on
	DNSKEY_GENERATE_FOR_LATER = (1 << 4), // Pregenerated key for later automatic use.
} kdnssec_generate_flags_t;

void normalize_generate_flags(kdnssec_generate_flags_t *flags);

/*!
 * \brief Generate new key, store all details in new kasp key structure.
 *
 * \param ctx           kasp context
 * \param flags         determine if to use the key as KSK and/or ZSK and SEP flag
 * \param key_ptr       output if KNOT_EOK: new pointer to generated key
 *
 * \return KNOT_E*
 */
int kdnssec_generate_key(kdnssec_ctx_t *ctx, kdnssec_generate_flags_t flags,
                         knot_kasp_key_t **key_ptr);

/*!
 * \brief Take a key from another zone (copying info, sharing privkey).
 *
 * \param ctx           kasp context
 * \param from_zone     name of the zone to take from
 * \param key_id        ID of the key to take
 *
 * \return KNOT_E*
 */
int kdnssec_share_key(kdnssec_ctx_t *ctx, const knot_dname_t *from_zone, const char *key_id);

/*!
 * \brief Remove key from zone.
 *
 * Deletes the key in keystore, unlinks the key from the zone in KASP db,
 * moreover if no more zones use this key in KASP db, deletes it completely there
 * and deletes it also from key storage (PKCS8dir/PKCS11).
 *
 * \param ctx           kasp context (zone, keystore, kaspdb) to be modified
 * \param key_ptr       pointer to key to be removed, must be inside keystore structure, NOT a copy of it!
 *
 * \return KNOT_E*
 */
int kdnssec_delete_key(kdnssec_ctx_t *ctx, knot_kasp_key_t *key_ptr);

/*!
 * \brief Load private key for given ID by searching all configured keystores.
 *
 * \param keystores     Array of keystores.
 * \param id            Key ID to search.
 * \param key           Libdnssec key structure to be filled with private material.
 * \param name          Optional: name of the keystore in the configuration.
 * \param backend       Optional: backend of the keystore where found.
 *
 * \return DNSSEC_E*
 */
int kdnssec_load_private(knot_kasp_keystore_t *keystores, const char *id,
                         dnssec_key_t *key, const char **name, unsigned *backend);

/*!
 * \brief Find configured keystore for newly generated key.
 *
 * \param keystores    Array of keystores.
 * \param ksk          If the generated key is a KSK or CSK.
 *
 * \return Suitable keystore or NULL.
 */
knot_kasp_keystore_t *knot_store_for_key(knot_kasp_keystore_t *keystores, bool ksk);

/*!
 * \brief Load zone keys and init cryptographic context.
 *
 * \param ctx            Zone signing context.
 * \param keyset_ptr     Resulting zone keyset.
 * \param verbose        Print key summary into log.
 *
 * \return Error code, KNOT_EOK if successful.
 */
int load_zone_keys(kdnssec_ctx_t *ctx, zone_keyset_t *keyset_ptr, bool verbose);

/*!
 * \brief Free structure with zone keys and associated DNSSEC contexts.
 *
 * \param keyset  Zone keys.
 */
void free_zone_keys(zone_keyset_t *keyset);

/*!
 * \brief Get timestamp of next key event.
 *
 * \param keyset  Zone keys.
 *
 * \return Timestamp of next key event.
 */
knot_time_t knot_get_next_zone_key_event(const zone_keyset_t *keyset);

/*!
 * \brief Returns DS record rdata for given key.
 *
 * This function caches the results, so calling again with the same key returns immediately.
 *
 * \param for_key         The key to compute DS for.
 * \param digesttype      DS digest algorithm.
 * \param out_donotfree   Output: the DS record rdata. Do not call dnssec_binary_free() on this ever.
 *
 * \return Error code, KNOT_EOK if successful.
 */
int zone_key_calculate_ds(zone_key_t *for_key, dnssec_key_digest_t digesttype,
                          dnssec_binary_t *out_donotfree);

/*!
 * \brief Initialize local signing context.
 *
 * \param keyset      Key set.
 * \param dnssec_ctx  DNSSEC context.
 *
 * \return New local signing context or NULL.
 */
zone_sign_ctx_t *zone_sign_ctx(const zone_keyset_t *keyset, const kdnssec_ctx_t *dnssec_ctx);

/*!
 * \brief Initialize local validating context.
 * \param dnssec_ctx  DNSSEC context.
 * \return New local validating context or NULL.
 */
zone_sign_ctx_t *zone_validation_ctx(const kdnssec_ctx_t *dnssec_ctx);

/*!
 * \brief Free local signing context.
 *
 * \note This doesn't free the underlying keyset.
 *
 * \param ctx  Local context to be freed.
 */
void zone_sign_ctx_free(zone_sign_ctx_t *ctx);

/*!
 * \brief Create key signing structure from DNSKEY zone record.
 *
 * \param key     Dnssec key to be allocated.
 * \param owner   Zone name.
 * \param rdata   DNSKEY rdata.
 * \param rdlen   DNSKEY rdata length.
 *
 * \return KNOT_E*
 */
int dnssec_key_from_rdata(dnssec_key_t **key, const knot_dname_t *owner,
                          const uint8_t *rdata, size_t rdlen);

/*!
 * \brief Tell if apex SOA is signed by all active ZSKs.
 *
 * \param keyset        Zone key set.
 * \param apex_rrsig    Apex RRSIG RRSet.
 */
int is_soa_signed_by_all_zsks(const zone_keyset_t *keyset,
                              const knot_rdataset_t *apex_rrsig);
