/*  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 <stdbool.h>
#include <stdint.h>

#include "libknot/attribute.h"
#include "libknot/errcode.h"
#include "libknot/rrset.h"
#include "libknot/rrtype/naptr.h"
#include "libknot/rrtype/rrsig.h"
#include "contrib/mempattern.h"

_public_
knot_rrset_t *knot_rrset_new(const knot_dname_t *owner, uint16_t type,
                             uint16_t rclass, uint32_t ttl, knot_mm_t *mm)
{
	knot_dname_t *owner_cpy = knot_dname_copy(owner, mm);
	if (owner_cpy == NULL) {
		return NULL;
	}

	knot_rrset_t *ret = mm_alloc(mm, sizeof(knot_rrset_t));
	if (ret == NULL) {
		knot_dname_free(owner_cpy, mm);
		return NULL;
	}

	knot_rrset_init(ret, owner_cpy, type, rclass, ttl);

	return ret;
}

_public_
knot_rrset_t *knot_rrset_copy(const knot_rrset_t *src, knot_mm_t *mm)
{
	if (src == NULL) {
		return NULL;
	}

	knot_rrset_t *rrset = knot_rrset_new(src->owner, src->type,
	                                     src->rclass, src->ttl, mm);
	if (rrset == NULL) {
		return NULL;
	}

	int ret = knot_rdataset_copy(&rrset->rrs, &src->rrs, mm);
	if (ret != KNOT_EOK) {
		knot_rrset_free(rrset, mm);
		return NULL;
	}

	return rrset;
}

_public_
void knot_rrset_free(knot_rrset_t *rrset, knot_mm_t *mm)
{
	if (rrset == NULL) {
		return;
	}

	knot_rrset_clear(rrset, mm);
	mm_free(mm, rrset);
}

_public_
void knot_rrset_clear(knot_rrset_t *rrset, knot_mm_t *mm)
{
	if (rrset == NULL) {
		return;
	}

	knot_rdataset_clear(&rrset->rrs, mm);
	knot_dname_free(rrset->owner, mm);
	rrset->owner = NULL;
}

_public_
int knot_rrset_add_rdata(knot_rrset_t *rrset, const uint8_t *data, uint16_t len,
                         knot_mm_t *mm)
{
	if (rrset == NULL || (data == NULL && len > 0)) {
		return KNOT_EINVAL;
	}

	uint8_t buf[knot_rdata_size(len)];
	knot_rdata_t *rdata = (knot_rdata_t *)buf;
	knot_rdata_init(rdata, len, data);

	return knot_rdataset_add(&rrset->rrs, rdata, mm);
}

_public_
bool knot_rrset_equal(const knot_rrset_t *r1,
                      const knot_rrset_t *r2,
                      bool incl_ttl)
{
	if (r1->type != r2->type ||
	    (incl_ttl && r1->ttl != r2->ttl)) {
		return false;
	}

	if ((r1->owner != NULL || r2->owner != NULL) &&
	    !knot_dname_is_equal(r1->owner, r2->owner)) {
		return false;
	}

	return knot_rdataset_eq(&r1->rrs, &r2->rrs);
}

_public_
bool knot_rrset_is_nsec3rel(const knot_rrset_t *rr)
{
	if (rr == NULL) {
		return false;
	}

	/* Is NSEC3 or non-empty RRSIG covering NSEC3. */
	return ((rr->type == KNOT_RRTYPE_NSEC3) ||
	        (rr->type == KNOT_RRTYPE_RRSIG
	         && knot_rrsig_type_covered(rr->rrs.rdata) == KNOT_RRTYPE_NSEC3));
}

_public_
int knot_rdata_to_canonical(knot_rdata_t *rdata, uint16_t type)
{
	if (rdata == NULL) {
		return KNOT_EINVAL;
	}

	/*
	 * NOTE - the non/zero rdata length MUST be guarded elsewhere.
	 * In case of incoming pkt this is rrset-wire.c:allow_zero_rdata()
	 *   (including the exceptions for DDNS)
	 * in case of zonefile, ctl or Redis it is scanner-body.rl:_hex_r_data
	 */
	const uint16_t rdlen = rdata->len;
	if (rdlen == 0) {
		return KNOT_EOK;
	}

	const knot_rdata_descriptor_t *desc = knot_get_rdata_descriptor(type);
	if (desc->type_name == NULL) {
		desc = knot_get_obsolete_rdata_descriptor(type);
	}

	uint8_t *pos = rdata->data;
	uint8_t *endpos = pos + rdlen;

	/* Check the RDATA items against the corresponding descriptor, lower case if needed. */
	for (int i = 0; desc->block_types[i] != KNOT_RDATA_WF_END; ++i) {
		int ret, block_type = desc->block_types[i];
		switch (block_type) {
		case KNOT_RDATA_WF_COMPRESSIBLE_DNAME:
		case KNOT_RDATA_WF_DECOMPRESSIBLE_DNAME:
		case KNOT_RDATA_WF_FIXED_DNAME:
			ret = knot_dname_wire_check(pos, endpos, NULL);
			if (ret <= 0) {
				return KNOT_EMALF;
			}
			/* Convert DNAME in RDATA if needed. */
			if (knot_rrtype_should_be_lowercased(type)) {
				knot_dname_to_lower(pos);
			}
			pos += ret;
			break;
		case KNOT_RDATA_WF_NAPTR_HEADER:
			ret = knot_naptr_header_size(pos, endpos);
			if (ret < 0) {
				return ret;
			}
			pos += ret;
			break;
		case KNOT_RDATA_WF_REMAINDER:
			if (pos >= endpos) {
				return KNOT_EMALF;
			}
			break;
		case KNOT_RDATA_WF_REMAINDER_MAYEMPTY:
			break;
		default:
			/* Fixed size block */
			assert(block_type > 0);
			pos += block_type;
			if (pos > endpos) {
				return KNOT_EMALF;
			}
		}
	}

	return KNOT_EOK;
}

_public_
int knot_rrset_rr_to_canonical(knot_rrset_t *rrset)
{
	if (rrset == NULL || rrset->rrs.count != 1) {
		return KNOT_EINVAL;
	}

	/* Convert owner for all RRSets. */
	knot_dname_to_lower(rrset->owner);

	return knot_rdata_to_canonical(rrset->rrs.rdata, rrset->type);
}

_public_
size_t knot_rrset_size(const knot_rrset_t *rrset)
{
	if (rrset == NULL) {
		return 0;
	}

	uint16_t rr_count = rrset->rrs.count;

	size_t total_size = knot_dname_size(rrset->owner) * rr_count;

	knot_rdata_t *rr = rrset->rrs.rdata;
	for (size_t i = 0; i < rr_count; ++i) {
		/* 10B = TYPE + CLASS + TTL + RDLENGTH */
		total_size += rr->len + 10;
		rr = knot_rdataset_next(rr);
	}

	return total_size;
}
