/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * SPDX-License-Identifier: MPL-2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

/*! \file */

#include <stddef.h>
#include <string.h>

#include <isc/util.h>

#include <dns/name.h>
#include <dns/nsec3.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>

static dns_rdatasetmethods_t methods = {
	.disassociate = dns_rdatalist_disassociate,
	.first = dns__rdatalist_first,
	.next = dns__rdatalist_next,
	.current = dns__rdatalist_current,
	.clone = dns__rdatalist_clone,
	.count = dns__rdatalist_count,
	.addnoqname = dns__rdatalist_addnoqname,
	.getnoqname = dns__rdatalist_getnoqname,
	.addclosest = dns__rdatalist_addclosest,
	.getclosest = dns__rdatalist_getclosest,
	.setownercase = dns__rdatalist_setownercase,
	.getownercase = dns__rdatalist_getownercase,
};

void
dns_rdatalist_init(dns_rdatalist_t *rdatalist) {
	REQUIRE(rdatalist != NULL);

	/*
	 * Initialize rdatalist.
	 */
	*rdatalist = (dns_rdatalist_t){
		.rdata = ISC_LIST_INITIALIZER,
		.link = ISC_LINK_INITIALIZER,
	};
	memset(rdatalist->upper, 0xeb, sizeof(rdatalist->upper));

	/*
	 * Clear upper set bit.
	 */
	rdatalist->upper[0] &= ~0x01;
}

void
dns_rdatalist_tordataset(dns_rdatalist_t *rdatalist, dns_rdataset_t *rdataset) {
	/*
	 * Make 'rdataset' refer to the rdata in 'rdatalist'.
	 */

	REQUIRE(rdatalist != NULL);
	REQUIRE(DNS_RDATASET_VALID(rdataset));
	REQUIRE(!dns_rdataset_isassociated(rdataset));

	/* Check if dns_rdatalist_init has was called. */
	REQUIRE(rdatalist->upper[0] == 0xea);

	*rdataset = (dns_rdataset_t){
		.methods = &methods,
		.rdclass = rdatalist->rdclass,
		.type = rdatalist->type,
		.covers = rdatalist->covers,
		.ttl = rdatalist->ttl,
		.rdlist.list = rdatalist,

		.link = rdataset->link,
		.attributes = rdataset->attributes,
		.magic = rdataset->magic,
	};
}

void
dns_rdatalist_fromrdataset(dns_rdataset_t *rdataset,
			   dns_rdatalist_t **rdatalist) {
	REQUIRE(rdatalist != NULL && rdataset != NULL);
	REQUIRE(rdataset->methods == &methods);

	*rdatalist = rdataset->rdlist.list;
}

void
dns_rdatalist_disassociate(dns_rdataset_t *rdataset DNS__DB_FLARG) {
	UNUSED(rdataset);
}

isc_result_t
dns__rdatalist_first(dns_rdataset_t *rdataset) {
	dns_rdatalist_t *rdatalist = NULL;

	rdatalist = rdataset->rdlist.list;
	rdataset->rdlist.iter = ISC_LIST_HEAD(rdatalist->rdata);

	if (rdataset->rdlist.iter == NULL) {
		return ISC_R_NOMORE;
	}

	return ISC_R_SUCCESS;
}

isc_result_t
dns__rdatalist_next(dns_rdataset_t *rdataset) {
	dns_rdata_t *rdata;

	rdata = rdataset->rdlist.iter;
	if (rdata == NULL) {
		return ISC_R_NOMORE;
	}

	rdataset->rdlist.iter = ISC_LIST_NEXT(rdata, link);

	if (rdataset->rdlist.iter == NULL) {
		return ISC_R_NOMORE;
	}

	return ISC_R_SUCCESS;
}

void
dns__rdatalist_current(dns_rdataset_t *rdataset, dns_rdata_t *rdata) {
	dns_rdata_t *list_rdata;

	list_rdata = rdataset->rdlist.iter;
	INSIST(list_rdata != NULL);

	dns_rdata_clone(list_rdata, rdata);
}

void
dns__rdatalist_clone(const dns_rdataset_t *source,
		     dns_rdataset_t *target DNS__DB_FLARG) {
	REQUIRE(source != NULL);
	REQUIRE(target != NULL);

	*target = *source;

	target->rdlist.iter = NULL;
}

unsigned int
dns__rdatalist_count(dns_rdataset_t *rdataset) {
	dns_rdatalist_t *rdatalist = NULL;
	unsigned int count;

	REQUIRE(rdataset != NULL);

	rdatalist = rdataset->rdlist.list;

	count = 0;
	ISC_LIST_FOREACH(rdatalist->rdata, rdata, link) {
		count++;
	}

	return count;
}

isc_result_t
dns__rdatalist_addnoqname(dns_rdataset_t *rdataset, dns_name_t *name) {
	dns_rdataset_t *neg = NULL;
	dns_rdataset_t *negsig = NULL;
	dns_ttl_t ttl;

	REQUIRE(rdataset != NULL);

	ISC_LIST_FOREACH(name->list, rdset, link) {
		if (rdset->rdclass != rdataset->rdclass) {
			continue;
		}
		if (dns_rdatatype_isnsec(rdset->type)) {
			neg = rdset;
		}
	}
	if (neg == NULL) {
		return ISC_R_NOTFOUND;
	}

	ISC_LIST_FOREACH(name->list, rdset, link) {
		if (rdset->type == dns_rdatatype_rrsig &&
		    rdset->covers == neg->type)
		{
			negsig = rdset;
		}
	}

	if (negsig == NULL) {
		return ISC_R_NOTFOUND;
	}

	/*
	 * Minimise ttl.
	 */
	ttl = rdataset->ttl;
	if (neg->ttl < ttl) {
		ttl = neg->ttl;
	}
	if (negsig->ttl < ttl) {
		ttl = negsig->ttl;
	}
	rdataset->ttl = neg->ttl = negsig->ttl = ttl;
	rdataset->attributes.noqname = true;
	rdataset->rdlist.noqname = name;
	return ISC_R_SUCCESS;
}

isc_result_t
dns__rdatalist_getnoqname(dns_rdataset_t *rdataset, dns_name_t *name,
			  dns_rdataset_t *neg,
			  dns_rdataset_t *negsig DNS__DB_FLARG) {
	dns_rdataclass_t rdclass;
	dns_rdataset_t *tneg = NULL;
	dns_rdataset_t *tnegsig = NULL;
	dns_name_t *noqname = NULL;

	REQUIRE(rdataset != NULL);
	REQUIRE(rdataset->attributes.noqname);

	rdclass = rdataset->rdclass;
	noqname = rdataset->rdlist.noqname;

	(void)dns_name_dynamic(noqname); /* Sanity Check. */

	ISC_LIST_FOREACH(noqname->list, rdset, link) {
		if (rdset->rdclass != rdclass) {
			continue;
		}
		if (dns_rdatatype_isnsec(rdset->type)) {
			tneg = rdset;
		}
	}
	if (tneg == NULL) {
		return ISC_R_NOTFOUND;
	}

	ISC_LIST_FOREACH(noqname->list, rdset, link) {
		if (rdset->type == dns_rdatatype_rrsig &&
		    rdset->covers == tneg->type)
		{
			tnegsig = rdset;
		}
	}
	if (tnegsig == NULL) {
		return ISC_R_NOTFOUND;
	}

	dns_name_clone(noqname, name);
	dns_rdataset_clone(tneg, neg);
	dns_rdataset_clone(tnegsig, negsig);
	return ISC_R_SUCCESS;
}

isc_result_t
dns__rdatalist_addclosest(dns_rdataset_t *rdataset, dns_name_t *name) {
	dns_rdataset_t *neg = NULL;
	dns_rdataset_t *negsig = NULL;
	dns_ttl_t ttl;

	REQUIRE(rdataset != NULL);

	ISC_LIST_FOREACH(name->list, rdset, link) {
		if (rdset->rdclass != rdataset->rdclass) {
			continue;
		}
		if (dns_rdatatype_isnsec(rdset->type)) {
			neg = rdset;
		}
	}
	if (neg == NULL) {
		return ISC_R_NOTFOUND;
	}

	ISC_LIST_FOREACH(name->list, rdset, link) {
		if (rdset->type == dns_rdatatype_rrsig &&
		    rdset->covers == neg->type)
		{
			negsig = rdset;
		}
	}

	if (negsig == NULL) {
		return ISC_R_NOTFOUND;
	}
	/*
	 * Minimise ttl.
	 */
	ttl = rdataset->ttl;
	if (neg->ttl < ttl) {
		ttl = neg->ttl;
	}
	if (negsig->ttl < ttl) {
		ttl = negsig->ttl;
	}
	rdataset->ttl = neg->ttl = negsig->ttl = ttl;
	rdataset->attributes.closest = true;
	rdataset->rdlist.closest = name;
	return ISC_R_SUCCESS;
}

isc_result_t
dns__rdatalist_getclosest(dns_rdataset_t *rdataset, dns_name_t *name,
			  dns_rdataset_t *neg,
			  dns_rdataset_t *negsig DNS__DB_FLARG) {
	dns_rdataclass_t rdclass;
	dns_rdataset_t *tneg = NULL;
	dns_rdataset_t *tnegsig = NULL;
	dns_name_t *closest = NULL;

	REQUIRE(rdataset != NULL);
	REQUIRE(rdataset->attributes.closest);

	rdclass = rdataset->rdclass;
	closest = rdataset->rdlist.closest;

	(void)dns_name_dynamic(closest); /* Sanity Check. */

	ISC_LIST_FOREACH(closest->list, rdset, link) {
		if (rdset->rdclass != rdclass) {
			continue;
		}
		if (dns_rdatatype_isnsec(rdset->type)) {
			tneg = rdset;
		}
	}
	if (tneg == NULL) {
		return ISC_R_NOTFOUND;
	}

	ISC_LIST_FOREACH(closest->list, rdset, link) {
		if (rdset->type == dns_rdatatype_rrsig &&
		    rdset->covers == tneg->type)
		{
			tnegsig = rdset;
		}
	}
	if (tnegsig == NULL) {
		return ISC_R_NOTFOUND;
	}

	dns_name_clone(closest, name);
	dns_rdataset_clone(tneg, neg);
	dns_rdataset_clone(tnegsig, negsig);
	return ISC_R_SUCCESS;
}

void
dns__rdatalist_setownercase(dns_rdataset_t *rdataset, const dns_name_t *name) {
	dns_rdatalist_t *rdatalist;
	unsigned int i;

	/*
	 * We do not need to worry about label lengths as they are all
	 * less than or equal to 63.
	 */
	rdatalist = rdataset->rdlist.list;
	memset(rdatalist->upper, 0, sizeof(rdatalist->upper));
	for (i = 1; i < name->length; i++) {
		if (name->ndata[i] >= 0x41 && name->ndata[i] <= 0x5a) {
			rdatalist->upper[i / 8] |= 1 << (i % 8);
		}
	}
	/*
	 * Record that upper has been set.
	 */
	rdatalist->upper[0] |= 0x01;
}

void
dns__rdatalist_getownercase(const dns_rdataset_t *rdataset, dns_name_t *name) {
	dns_rdatalist_t *rdatalist;
	unsigned int i;

	rdatalist = rdataset->rdlist.list;
	if ((rdatalist->upper[0] & 0x01) == 0) {
		return;
	}
	for (i = 0; i < name->length; i++) {
		/*
		 * Set the case bit if it does not match the recorded bit.
		 */
		if (name->ndata[i] >= 0x61 && name->ndata[i] <= 0x7a &&
		    (rdatalist->upper[i / 8] & (1 << (i % 8))) != 0)
		{
			name->ndata[i] &= ~0x20; /* clear the lower case bit */
		} else if (name->ndata[i] >= 0x41 && name->ndata[i] <= 0x5a &&
			   (rdatalist->upper[i / 8] & (1 << (i % 8))) == 0)
		{
			name->ndata[i] |= 0x20; /* set the lower case bit */
		}
	}
}
