/*
 * Copyright (C) 2000, 2001  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM
 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
 * INTERNET SOFTWARE CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* $Id: opt.c,v 1.14 2001/05/06 02:24:08 mayer Exp $ */

/* DNS_OPT_NEWCODES_KEEP */

#include <config.h>

#include <stdlib.h>
#include <string.h>

#include <isc/buffer.h>
#include <isc/util.h>

#include <dns/compress.h>
#include <dns/fixedname.h>
#include <dns/message.h>
#include <dns/name.h>
#include <dns/ncache.h>
#include <dns/opt.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/result.h>

#define ADD_STRING(b, s, g)	{if (strlen(s) >= \
				   isc_buffer_availablelength(b)) \
				       { result = ISC_R_NOSPACE; \
					 goto g; } else \
				       isc_buffer_putstr(b, s);}
static isc_result_t
optget(dns_optlist_t *optlist, dns_rdataset_t *optset,
       isc_uint16_t code, isc_boolean_t getall)
{
	isc_result_t result;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	unsigned int location;
	isc_region_t rdataregion;
	isc_buffer_t rdatabuf;
	isc_uint16_t thiscode, thislength;

	REQUIRE(DNS_RDATASET_VALID(optset));
	REQUIRE(optset->type == dns_rdatatype_opt);

	result = dns_rdataset_first(optset);
	if (result != ISC_R_SUCCESS)
		return(result);
	dns_rdataset_current(optset, &rdata);

	dns_rdata_toregion(&rdata, &rdataregion);
	isc_buffer_init(&rdatabuf, rdataregion.base, rdataregion.length);
	isc_buffer_add(&rdatabuf, rdataregion.length);

	optlist->used = 0;
	location = 0;
	/*
	 * We don't do the test in the while loop, since I want to
	 * actually keep searching the list for more data until I reach
	 * the first one I *can't* fit in.  This way, I can correctly
	 * decide between MOREDATA and SUCCESS.
	 */
	while (1) {
		if (isc_buffer_remaininglength(&rdatabuf) == 0) {
			optlist->next = 0;
			return (ISC_R_SUCCESS);
		}
		if (isc_buffer_remaininglength(&rdatabuf) < 4)
			return (ISC_R_UNEXPECTEDEND);
		thiscode = isc_buffer_getuint16(&rdatabuf);
		thislength = isc_buffer_getuint16(&rdatabuf);
		if (isc_buffer_remaininglength(&rdatabuf) < thislength)
			return (ISC_R_UNEXPECTEDEND);
		if ((thiscode == code || getall) &&
		    (location >= optlist->next)) {
			if (optlist->used >= optlist->size) {
				optlist->next = location;
				return (DNS_R_MOREDATA);
			}
			optlist->attrs[optlist->used].code = thiscode;
			optlist->attrs[optlist->used].value.base = 
				isc_buffer_current(&rdatabuf);
			optlist->attrs[optlist->used].value.length =
				thislength;
			optlist->used++;
		}
		isc_buffer_forward(&rdatabuf, thislength);
		location++;
	}
	/*NOTREACHED*/
}

isc_result_t
dns_opt_decode(dns_optlist_t *optlist, dns_rdataset_t *optset,
	       isc_uint16_t code)
{
	return (optget(optlist, optset, code, ISC_FALSE));
}

isc_result_t
dns_opt_decodeall(dns_optlist_t *optlist, dns_rdataset_t *optset) {
	return (optget(optlist, optset, 0, ISC_TRUE));
}

isc_result_t
dns_opt_add(dns_rdata_t *rdata, dns_optlist_t *optlist,
	    isc_buffer_t *target)
{
	unsigned char *base;
	unsigned int i;

	REQUIRE(rdata->length == 0);

	base = isc_buffer_current(target);
	if (optlist != NULL) {
		for (i = 0; i < optlist->used; i++) {
			rdata->length += optlist->attrs[i].value.length;
			rdata->length += 4;
		}
		if (isc_buffer_availablelength(target) < rdata->length) {
			rdata->length = 0;
			return (ISC_R_NOSPACE);
		}
		for (i = 0; i < optlist->used; i++) {
			isc_buffer_putuint16(target, optlist->attrs[i].code);
			isc_buffer_putuint16(target,
					     (isc_uint16_t) optlist->attrs[i].value.length);
			isc_buffer_putmem(target,
					  optlist->attrs[i].value.base,
					  optlist->attrs[i].value.length);
		}
		rdata->data = base;
	}
	return (ISC_R_SUCCESS);
}

isc_result_t
dns_opt_attrtotext(dns_optattr_t *attr, isc_buffer_t *target,
		   dns_messagetextflag_t flags) {
	isc_result_t result = ISC_R_SUCCESS;
	char store[sizeof("012345678")];
#ifdef DNS_OPT_NEWCODES
	dns_decompress_t dctx;
	dns_fixedname_t fname;
	isc_buffer_t source;

#else /* DNS_OPT_NEWCODES */
	UNUSED (flags);
#endif /* DNS_OPT_NEWCODES */

	switch (attr->code) {
#ifdef DNS_OPT_NEWCODES
	case DNS_OPTCODE_ZONE:
		ADD_STRING(target, "; ZONE attribute: ", zonefail0);
		dns_fixedname_init(&fname);
		dns_decompress_init(&dctx, 0, DNS_DECOMPRESS_NONE);
		isc_buffer_init(&source, attr->value.base, attr->value.length);
		isc_buffer_add(&source, attr->value.length);
		isc_buffer_setactive(&source, attr->value.length);
		result = dns_name_fromwire(&fname.name, &source, &dctx,
					   ISC_FALSE, NULL);
		if (result != ISC_R_SUCCESS)
			goto zonefail1;
		result = dns_name_totext(&fname.name, ISC_FALSE,
					 target);
		ADD_STRING(target, "\n", zonefail1);
	zonefail1:
		dns_decompress_invalidate(&dctx);
		dns_fixedname_invalidate(&fname);
	zonefail0:
		return (result);
	case DNS_OPTCODE_VIEW:
		ADD_STRING(target, "; VIEW attribute: ", viewfail0);
		if (attr->value.length >= isc_buffer_availablelength(target))
			return(ISC_R_NOSPACE);
		else
			isc_buffer_putmem(target, attr->value.base,
					  attr->value.length);
		ADD_STRING(target, "\n", viewfail0);
	viewfail0:
		return (result);
#endif /* DNS_OPT_NEWCODES */
	/*
	 * This routine is a placekeeper, awaiting assignment of
	 * OPT attribute values from IANA.
	 */
	default:
		ADD_STRING(target, "; Unknown EDNS attribute ", deffail);
		sprintf(store,"%d",attr->code);
		ADD_STRING(target, store, deffail);
		ADD_STRING(target, "\n", deffail);
		result = DNS_R_UNKNOWNOPT;
	deffail:
		return (result);
	}
}

isc_result_t
dns_opt_totext(dns_rdataset_t *opt, isc_buffer_t *target, 
	       dns_messagetextflag_t flags) {
	isc_result_t result, iresult;
	char buf[sizeof("1234567890")];
	dns_optattr_t attr;
	dns_optlist_t list;

	REQUIRE(DNS_RDATASET_VALID(opt));
	REQUIRE(target != NULL);

	if ((flags & DNS_MESSAGETEXTFLAG_NOCOMMENTS) == 0)
		ADD_STRING(target, ";; OPT PSEUDOSECTION:\n", fail);
	ADD_STRING(target, "; EDNS: version: ", fail);
	sprintf(buf, "%4u",
		(unsigned int)((opt->ttl &
				0x00ff0000 >> 16)));
	ADD_STRING(target, buf, fail);
	ADD_STRING(target, ", udp=", fail);
	sprintf(buf, "%7u",
		(unsigned int)opt->rdclass);
	ADD_STRING(target, buf, fail);
	ADD_STRING(target, ", flags:", fail);
	if ((DNS_OPT_FLAGS(opt) & DNS_MESSAGEEXTFLAG_DO) != 0)
		ADD_STRING(target, " do", fail);
	ADD_STRING(target, "\n", fail);

	list.attrs = &attr;
	list.size = 1;
	list.used = 0;
	list.next = 0;
	do {
		result = dns_opt_decodeall(&list, opt);
		if ((result == ISC_R_SUCCESS || result == DNS_R_MOREDATA)
		    && list.used != 0) {
			iresult = dns_opt_attrtotext(list.attrs, target,
						    flags);
			if (iresult != ISC_R_SUCCESS &&
			    iresult != DNS_R_UNKNOWNOPT)
				result = iresult;
		}
	} while (result == DNS_R_MOREDATA);
 fail:
	return (result);
}
