/**
 *
 * \file util-internal.c
 * @brief private library routines
 *
 * These routines are not intended to be used by applications calling into
 * the library.
 *
 */

/*
 * Copyright (c) 2013, NLnet Labs, Verisign, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 * * Neither the names of the copyright holders nor the
 *   names of its contributors may be used to endorse or promote products
 *   derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Verisign, Inc. BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdint.h>
#include <stdlib.h>
#include <unbound.h>
#include "getdns/getdns.h"
#include "dict.h"
#include "list.h"
#include "util-internal.h"
#include "types-internal.h"
#include "rr-dict.h"
#if defined(WIRE_DEBUG) && WIRE_DEBUG
#include "gldns/wire2str.h"
#endif
#include "gldns/str2wire.h"
#include "gldns/gbuffer.h"
#include "gldns/pkthdr.h"

/**
  * this is a comprehensive list of extensions and their data types
  * used by validate_extensions()
  * The list has to be in sorted order for bsearch lookup in function
  * validate_extensions.
  */
static getdns_extension_format extformats[] = {
	{"add_opt_parameters", t_dict},
	{"add_warning_for_bad_dns", t_int},
	{"dnssec_ok_checking_disabled", t_int},
	{"dnssec_return_only_secure", t_int},
	{"dnssec_return_status", t_int},
	{"dnssec_return_validation_chain", t_int},
#ifdef EDNS_COOKIES
	{"edns_cookies", t_int},
#endif
	{"return_api_information", t_int},
	{"return_both_v4_and_v6", t_int},
	{"return_call_debugging", t_int},
	{"specify_class", t_int},
};

static struct getdns_bindata IPv4_str_bindata = { 5, (void *)"IPv4" };
static struct getdns_bindata IPv6_str_bindata = { 5, (void *)"IPv6" };

getdns_return_t
getdns_dict_util_set_string(struct getdns_dict * dict, char *name, const char *value)
{
	/* account for the null term */
	if (value == NULL) {
		return GETDNS_RETURN_WRONG_TYPE_REQUESTED;
	}
	struct getdns_bindata type_bin = { strlen(value) + 1, (uint8_t *) value };
	return getdns_dict_set_bindata(dict, name, &type_bin);
}

getdns_return_t
getdns_dict_util_get_string(struct getdns_dict * dict, char *name, char **result)
{
	struct getdns_bindata *bindata = NULL;
	if (!result) {
		return GETDNS_RETURN_GENERIC_ERROR;
	}
	*result = NULL;
	getdns_dict_get_bindata(dict, name, &bindata);
	if (!bindata) {
		return GETDNS_RETURN_GENERIC_ERROR;
	}
	*result = (char *) bindata->data;
	return GETDNS_RETURN_GOOD;
}

getdns_return_t
dict_to_sockaddr(struct getdns_dict * ns, struct sockaddr_storage * output)
{
	char *address_type = NULL;
	struct getdns_bindata *address_data = NULL;
	uint32_t port = 53;
	memset(output, 0, sizeof(struct sockaddr_storage));
	output->ss_family = AF_UNSPEC;

	uint32_t prt = 0;
	if (getdns_dict_get_int(ns, GETDNS_STR_PORT,
		&prt) == GETDNS_RETURN_GOOD) {
		port = prt;
	}

	getdns_dict_util_get_string(ns, GETDNS_STR_ADDRESS_TYPE,
	    &address_type);
	getdns_dict_get_bindata(ns, GETDNS_STR_ADDRESS_DATA, &address_data);
	if (!address_type || !address_data) {
		return GETDNS_RETURN_GENERIC_ERROR;
	}
	if (strncmp(GETDNS_STR_IPV4, address_type,
		strlen(GETDNS_STR_IPV4)) == 0) {
		/* data is an in_addr_t */
		struct sockaddr_in *addr = (struct sockaddr_in *) output;
		addr->sin_family = AF_INET;
		addr->sin_port = htons((uint16_t) port);
		memcpy(&(addr->sin_addr), address_data->data,
		    address_data->size);
	} else {
		/* data is a v6 addr in host order */
		struct sockaddr_in6 *addr = (struct sockaddr_in6 *) output;
		addr->sin6_family = AF_INET6;
		addr->sin6_port = htons((uint16_t) port);
		memcpy(&(addr->sin6_addr), address_data->data,
		    address_data->size);
	}
	return GETDNS_RETURN_GOOD;
}

getdns_return_t
sockaddr_to_dict(struct getdns_context *context, struct sockaddr_storage *address,
    struct getdns_dict ** output)
{
	if (!output || !address) {
		return GETDNS_RETURN_GENERIC_ERROR;
	}
	struct getdns_bindata addr_data;
	*output = NULL;
	struct getdns_dict *result = getdns_dict_create_with_context(context);
	if (address->ss_family == AF_INET) {
		struct sockaddr_in *addr = (struct sockaddr_in *) address;
		getdns_dict_util_set_string(result, GETDNS_STR_ADDRESS_TYPE,
		    GETDNS_STR_IPV4);
		addr_data.size = sizeof(addr->sin_addr);
		addr_data.data = (uint8_t *) & (addr->sin_addr);
		getdns_dict_set_bindata(result, GETDNS_STR_ADDRESS_DATA,
		    &addr_data);
	} else if (address->ss_family == AF_INET6) {
		struct sockaddr_in6 *addr = (struct sockaddr_in6 *) address;
		getdns_dict_util_set_string(result, GETDNS_STR_ADDRESS_TYPE,
		    GETDNS_STR_IPV6);
		addr_data.size = sizeof(addr->sin6_addr);
		addr_data.data = (uint8_t *) & (addr->sin6_addr);
		getdns_dict_set_bindata(result, GETDNS_STR_ADDRESS_DATA,
		    &addr_data);
	} else {
		// invalid
		getdns_dict_destroy(result);
		return GETDNS_RETURN_GENERIC_ERROR;
	}
	*output = result;
	return GETDNS_RETURN_GOOD;
}

getdns_dict *
priv_getdns_rr_iter2rr_dict(getdns_context *context, priv_getdns_rr_iter *i)
{
	getdns_dict *rr_dict, *rdata_dict;
	getdns_bindata bindata;
	uint32_t int_val = 0;
	getdns_data_type val_type;
	priv_getdns_rdf_iter rdf_storage, *rdf;
	getdns_list *repeat_list = NULL;
	getdns_dict *repeat_dict = NULL;
	uint8_t ff_bytes[256];
	uint16_t rr_type;

	assert(i);
	if (!(rr_dict = getdns_dict_create_with_context(context)))
		return NULL;

	bindata.data = priv_getdns_owner_if_or_as_decompressed(
	    i, ff_bytes, &bindata.size);

	/* question */
	if (priv_getdns_rr_iter_section(i) == GLDNS_SECTION_QUESTION) {

		if (getdns_dict_set_int(rr_dict, "qtype",
		    (uint32_t) gldns_read_uint16(i->rr_type)) ||

		    getdns_dict_set_int(rr_dict, "qclass",
		    (uint32_t) gldns_read_uint16(i->rr_type + 2)) ||

		    getdns_dict_set_bindata(rr_dict, "qname", &bindata)) {

			goto error;
		}
		return rr_dict;
	}
	if (getdns_dict_set_int(rr_dict, "type",
	    (uint32_t)(rr_type = gldns_read_uint16(i->rr_type)))) {

		goto error;
	}
	if (rr_type == GETDNS_RRTYPE_OPT) {
		int_val = gldns_read_uint16(i->rr_type + 6);

		if (getdns_dict_set_int(rr_dict, "udp_payload_size",
		    (uint32_t) gldns_read_uint16(i->rr_type + 2)) ||

		    getdns_dict_set_int(rr_dict, "extended_rcode",
		    (uint32_t) *(i->rr_type + 4)) ||

		    getdns_dict_set_int(rr_dict, "version",
		    (uint32_t) *(i->rr_type + 5)) ||
		    
		    getdns_dict_set_int(rr_dict, "do",
		    (uint32_t) ((int_val & 0x8000) >> 15)) ||

		    getdns_dict_set_int(rr_dict, "z",
		    (uint32_t) (int_val & 0x7FF))) {

			goto error;
		}
	} else if (getdns_dict_set_int(rr_dict, "class",
	    (uint32_t) gldns_read_uint16(i->rr_type + 2)) ||

	    getdns_dict_set_int(rr_dict, "ttl",
	    (uint32_t) gldns_read_uint32(i->rr_type + 4)) ||

	    getdns_dict_set_bindata(rr_dict, "name", &bindata)) {

		goto error;
	}
	if (!(rdata_dict = getdns_dict_create_with_context(context)))
		goto error;

	if (i->rr_type + 10 <= i->nxt) {
		bindata.size = i->nxt - (i->rr_type + 10);
		bindata.data = i->rr_type + 10;
		if (getdns_dict_set_bindata(rdata_dict, "rdata_raw", &bindata))
			goto rdata_error;
	}
	for ( rdf = priv_getdns_rdf_iter_init(&rdf_storage, i)
	    ; rdf; rdf = priv_getdns_rdf_iter_next(rdf)) {
		if (rdf->rdd_pos->type & GETDNS_RDF_INTEGER) {
			val_type = t_int;
			switch (rdf->rdd_pos->type & GETDNS_RDF_FIXEDSZ) {
			case 1:	int_val = *rdf->pos;
				break;
			case 2:	int_val = gldns_read_uint16(rdf->pos);
				break; 
			case 4:	int_val = gldns_read_uint32(rdf->pos);
				break;
			default:
				goto rdata_error;
			}
		} else if ((rdf->rdd_pos->type & GETDNS_RDF_DNAME) ==
		    GETDNS_RDF_DNAME) {
			val_type = t_bindata;

			bindata.data = priv_getdns_rdf_if_or_as_decompressed(
			    rdf, ff_bytes, &bindata.size);

		} else if (rdf->rdd_pos->type & GETDNS_RDF_BINDATA) {
			val_type = t_bindata;
			if (rdf->rdd_pos->type & GETDNS_RDF_FIXEDSZ) {
				bindata.size = rdf->rdd_pos->type
				             & GETDNS_RDF_FIXEDSZ;
				bindata.data = rdf->pos;

			} else switch(rdf->rdd_pos->type & GETDNS_RDF_LEN_VAL){
			case 0x100:
				bindata.size = *rdf->pos;
				bindata.data = rdf->pos + 1;
				break;
			case 0x200:
				bindata.size = gldns_read_uint16(rdf->pos);
				bindata.data = rdf->pos + 2;
				break;
			default:
				bindata.size = rdf->nxt - rdf->pos;
				bindata.data = rdf->pos;
				break;
			}
		} else if (rdf->rdd_pos->type == GETDNS_RDF_SPECIAL)
			/* Abuse t_dict for special values */
			val_type = t_dict;
		else
			assert(0);

		if (! rdf->rdd_repeat) {
			switch (val_type) {
			case t_int:
				if (getdns_dict_set_int(rdata_dict,
				    rdf->rdd_pos->name, int_val))
					goto rdata_error;
				break;
			case t_bindata:
				if (getdns_dict_set_bindata(rdata_dict,
				    rdf->rdd_pos->name, &bindata))
					goto rdata_error;
				break;
			case t_dict:
				if (rdf->rdd_pos->special->dict_set_value(
				    rdata_dict, rdf->pos))
					goto rdata_error;
			default:
				break;
			}
			continue;
		}
		if (rdf->rdd_pos == rdf->rdd_repeat) {
			/* list with rdf values */

			if (! repeat_list && !(repeat_list =
			    getdns_list_create_with_context(context)))
				goto rdata_error;
			
			switch (val_type) {
			case t_int:
				if (getdns_list_append_int(repeat_list,
				    int_val))
					goto rdata_error;
				break;
			case t_bindata:
				if (getdns_list_append_bindata(repeat_list,
				    &bindata))
					goto rdata_error;
				break;
			case t_dict:
				if (rdf->rdd_pos->special->list_append_value(
				    repeat_list, rdf->pos))
					goto rdata_error;
			default:
				break;
			}
			continue;
		}
		if (rdf->rdd_pos == rdf->rdd_repeat + 1) {

			if (repeat_dict) {
				if (! repeat_list && !(repeat_list =
				    getdns_list_create_with_context(context)))
					goto rdata_error;
	
				if (getdns_list_append_dict(
				    repeat_list, repeat_dict))
					goto rdata_error;

				getdns_dict_destroy(repeat_dict);
				repeat_dict = NULL;
			}
			if (!(repeat_dict =
			    getdns_dict_create_with_context(context)))
				goto rdata_error;
		}
		assert(repeat_dict);
		switch (val_type) {
		case t_int:
			if (getdns_dict_set_int(repeat_dict,
			    rdf->rdd_pos->name, int_val))
				goto rdata_error;
			break;
		case t_bindata:
			if (getdns_dict_set_bindata(repeat_dict,
			    rdf->rdd_pos->name, &bindata))
				goto rdata_error;
			break;
		case t_dict:
			if (rdf->rdd_pos->special->dict_set_value(
			    repeat_dict, rdf->pos))
				goto rdata_error;
		default:
			break;
		}
	}
	if (repeat_dict) {
		if (!repeat_list && !(repeat_list =
		    getdns_list_create_with_context(context)))
			goto rdata_error;
		if (getdns_list_append_dict(repeat_list, repeat_dict))
			goto rdata_error;
		getdns_dict_destroy(repeat_dict);
		repeat_dict = NULL;
	}
	if (repeat_list) {
		if (getdns_dict_set_list(rdata_dict,
		    rdf_storage.rdd_repeat->name, repeat_list))
			goto rdata_error;
		getdns_list_destroy(repeat_list);
		repeat_list = NULL;
	}
	if (getdns_dict_set_dict(rr_dict, "rdata", rdata_dict))
		goto rdata_error;

	getdns_dict_destroy(rdata_dict);
	return rr_dict;

rdata_error:
	getdns_list_destroy(repeat_list);
	getdns_dict_destroy(repeat_dict);
	getdns_dict_destroy(rdata_dict);
error:
	getdns_dict_destroy(rr_dict);
	return NULL;
}

static int
dname_equal(uint8_t *s1, uint8_t *s2)
{
	uint8_t i;
	for (;;) {
		if (*s1 != *s2)
			return 0;
		else if (!*s1)
			return 1;
		for (i = *s1++, s2++; i > 0; i--, s1++, s2++)
			if ((*s1 & 0xDF) != (*s2 & 0xDF))
				return 0;
	}
}

inline static getdns_dict *
set_dict(getdns_dict **var, getdns_dict *value)
{
	if (*var)
		getdns_dict_destroy(*var);
	return *var = value;
}

#define SET_WIRE_INT(X,Y) if (getdns_dict_set_int(header, #X , (int) \
                              GLDNS_ ## Y ## _WIRE(req->response))) goto error
#define SET_WIRE_BIT(X,Y) if (getdns_dict_set_int(header, #X , \
                              GLDNS_ ## Y ## _WIRE(req->response) ? 1 : 0)) goto error
#define SET_WIRE_CNT(X,Y) if (getdns_dict_set_int(header, #X , (int) \
                              GLDNS_ ## Y (req->response))) goto error 

getdns_dict *
priv_getdns_create_reply_dict(getdns_context *context, getdns_network_req *req,
    getdns_list *just_addrs, int *rrsigs_in_answer)
{
	/* turn a packet into this glorious structure
	 *
	 * {     # This is the first reply
	 * "header": { "id": 23456, "qr": 1, "opcode": 0, ... },
	 * "question": { "qname": <bindata for "www.example.com">, "qtype": 1, "qclass": 1 },
	 * "answer":
	 * [
	 * {
	 * "name": <bindata for "www.example.com">,
	 * "type": 1,
	 * "class": 1,
	 * "ttl": 33000,
	 * "rdata":
	 * {
	 * "ipv4_address": <bindata of 0x0a0b0c01>
	 * "rdata_raw": <bindata of 0x0a0b0c01>
	 * }
	 * }
	 * ],
	 * "authority":
	 * [
	 * {
	 * "name": <bindata for "ns1.example.com">,
	 * "type": 1,
	 * "class": 1,
	 * "ttl": 600,
	 * "rdata":
	 * {
	 * "ipv4_address": <bindata of 0x65439876>
	 * "rdata_raw": <bindata of 0x65439876>
	 * }
	 * }
	 * ]
	 * "additional": [],
	 * "canonical_name": <bindata for "www.example.com">,
	 * "answer_type": GETDNS_NAMETYPE_DNS
	 * }
	 *
	 */
	getdns_return_t r = GETDNS_RETURN_GOOD;
	getdns_dict *result = getdns_dict_create_with_context(context);
	getdns_dict *question = NULL;
	getdns_list *sections[4] = { NULL
	                           , getdns_list_create_with_context(context)
	                           , getdns_list_create_with_context(context)
	                           , getdns_list_create_with_context(context)
	                           };
	getdns_dict *rr_dict = NULL;
	priv_getdns_rr_iter rr_iter_storage, *rr_iter;
	priv_getdns_rdf_iter rdf_iter_storage, *rdf_iter;
	getdns_bindata bindata;
	gldns_pkt_section section;
	uint8_t canonical_name_space[256],
	       *canonical_name = canonical_name_space;
	uint8_t owner_name_space[256], *owner_name;
	size_t canonical_name_len = 256, owner_name_len;
	int new_canonical = 0;
	uint16_t rr_type;
	getdns_dict *header = NULL;

	if (!result)
		goto error;

	if (!(header = getdns_dict_create_with_context(context)))
		goto error;

	SET_WIRE_INT(id, ID);
	SET_WIRE_BIT(qr, QR);
	SET_WIRE_BIT(aa, AA);
	SET_WIRE_BIT(tc, TC);
	SET_WIRE_BIT(rd, RD);
	SET_WIRE_BIT(cd, CD);
	SET_WIRE_BIT(ra, RA);
	SET_WIRE_BIT(ad, AD);
	SET_WIRE_INT(opcode, OPCODE);
	SET_WIRE_INT(rcode, RCODE);
	SET_WIRE_BIT(z, Z);

	SET_WIRE_CNT(qdcount, QDCOUNT);
	SET_WIRE_CNT(ancount, ANCOUNT);
	SET_WIRE_CNT(nscount, NSCOUNT);
	SET_WIRE_CNT(arcount, ARCOUNT);

	/* header */
    	if ((r = getdns_dict_set_dict(result, "header", header)))
		goto error;

	(void) gldns_str2wire_dname_buf(
	    req->owner->name, canonical_name_space, &canonical_name_len);

	for ( rr_iter = priv_getdns_rr_iter_init(&rr_iter_storage
	                                        , req->response
	                                        , req->response_len)
	    ; rr_iter
	    ; rr_iter = priv_getdns_rr_iter_next(rr_iter)) {

		if (!set_dict(&rr_dict,
		    priv_getdns_rr_iter2rr_dict(context, rr_iter)))
			continue;

		section = priv_getdns_rr_iter_section(rr_iter);
		if (section == GLDNS_SECTION_QUESTION) {

			if (getdns_dict_set_dict(result, "question", rr_dict))
				goto error;

			continue;
		}
		if (getdns_list_append_dict(sections[section], rr_dict))
			goto error;


		rr_type = gldns_read_uint16(rr_iter->rr_type);
		if (section > GLDNS_SECTION_QUESTION &&
		    rr_type == GETDNS_RRTYPE_RRSIG && rrsigs_in_answer)
			*rrsigs_in_answer = 1;

		if (section != GLDNS_SECTION_ANSWER)
			continue;

		if (rr_type == GETDNS_RRTYPE_CNAME) {

			owner_name = priv_getdns_owner_if_or_as_decompressed(
			    rr_iter, owner_name_space, &owner_name_len);
			if (!dname_equal(canonical_name, owner_name))
				continue;

			if (!(rdf_iter = priv_getdns_rdf_iter_init(
			     &rdf_iter_storage, rr_iter)))
				continue;

			new_canonical = 1;
			canonical_name = priv_getdns_rdf_if_or_as_decompressed(
			    rdf_iter,canonical_name_space,&canonical_name_len);
			continue;
		}

		if (rr_type != GETDNS_RRTYPE_A && rr_type != GETDNS_RRTYPE_AAAA)
			continue;

		if (!(rdf_iter = priv_getdns_rdf_iter_init(
		     &rdf_iter_storage, rr_iter)))
			continue;

		bindata.size = rdf_iter->nxt - rdf_iter->pos;
		bindata.data = rdf_iter->pos;
		if (!set_dict(&rr_dict, getdns_dict_create_with_context(context)) ||

		    getdns_dict_set_bindata(rr_dict, "address_type",
		    rr_type == GETDNS_RRTYPE_A ?
		    &IPv4_str_bindata : &IPv6_str_bindata) ||

		    getdns_dict_set_bindata(rr_dict,"address_data",&bindata) ||

		    (just_addrs && getdns_list_append_dict(just_addrs, rr_dict))) {

			goto error;
		}
	}
	if (getdns_dict_set_list(result, "answer",
	    sections[GLDNS_SECTION_ANSWER]) ||

	    getdns_dict_set_list(result, "authority",
	    sections[GLDNS_SECTION_AUTHORITY]) ||

	    getdns_dict_set_list(result, "additional",
	    sections[GLDNS_SECTION_ADDITIONAL])) {

		goto error;
	}

	/* other stuff
	 * Note that spec doesn't explicitely mention these.
	 * They are only showcased in the response dict example */
	if (getdns_dict_set_int(result, "answer_type", GETDNS_NAMETYPE_DNS))
		goto error;
	
	while (new_canonical) {
		new_canonical = 0;

		for ( rr_iter = priv_getdns_rr_iter_init(&rr_iter_storage
							, req->response
							, req->response_len)
		    ; rr_iter && priv_getdns_rr_iter_section(rr_iter)
		              <= GLDNS_SECTION_ANSWER
		    ; rr_iter = priv_getdns_rr_iter_next(rr_iter)) {

			if (priv_getdns_rr_iter_section(rr_iter) !=
			    GLDNS_SECTION_ANSWER)
				continue;

			if (gldns_read_uint16(rr_iter->rr_type) !=
			    GETDNS_RRTYPE_CNAME)
				continue;

			owner_name = priv_getdns_owner_if_or_as_decompressed(
			    rr_iter, owner_name_space, &owner_name_len);
			if (!dname_equal(canonical_name, owner_name))
				continue;

			if (!(rdf_iter = priv_getdns_rdf_iter_init(
			     &rdf_iter_storage, rr_iter)))
				continue;

			canonical_name = priv_getdns_rdf_if_or_as_decompressed(
			    rdf_iter,canonical_name_space,&canonical_name_len);
			new_canonical = 1;
		}
	}
	bindata.data = canonical_name;
	bindata.size = canonical_name_len;
	if (getdns_dict_set_bindata(result, "canonical_name", &bindata))
		goto error;

	goto success;
error:
	getdns_dict_destroy(result);
	result = NULL;
success:
	getdns_dict_destroy(header);
	getdns_dict_destroy(rr_dict);
	getdns_list_destroy(sections[GLDNS_SECTION_ADDITIONAL]);
	getdns_list_destroy(sections[GLDNS_SECTION_AUTHORITY]);
	getdns_list_destroy(sections[GLDNS_SECTION_ANSWER]);
	getdns_dict_destroy(question);
	return result;
}

getdns_dict *
create_getdns_response(getdns_dns_req *completed_request)
{
	getdns_dict *result;
	getdns_list *just_addrs = NULL;
	getdns_list *replies_full;
	getdns_list *replies_tree;
	getdns_network_req *netreq, **netreq_p;
	int rrsigs_in_answer = 0;
	getdns_dict *reply;
	getdns_bindata *canonical_name = NULL;
	int nreplies = 0, nanswers = 0, nsecure = 0, ninsecure = 0, nbogus = 0;
    	getdns_bindata full_data;

	/* info (bools) about dns_req */
	int dnssec_return_status;
	getdns_context *context;

	assert(completed_request);
	
	context = completed_request->context;
	if (!(result = getdns_dict_create_with_context(context)))
		return NULL;

	dnssec_return_status = completed_request->dnssec_return_status ||
	                       completed_request->dnssec_return_only_secure;

	if (completed_request->netreqs[0]->request_type == GETDNS_RRTYPE_A ||
	    completed_request->netreqs[0]->request_type == GETDNS_RRTYPE_AAAA)
		just_addrs = getdns_list_create_with_context(
		    completed_request->context);

	if (getdns_dict_set_int(result, GETDNS_STR_KEY_ANSWER_TYPE,
	    GETDNS_NAMETYPE_DNS))
		goto error_free_result;
	
	if (!(replies_full = getdns_list_create_with_context(context)))
		goto error_free_result;

	if (!(replies_tree = getdns_list_create_with_context(context)))
		goto error_free_replies_full;

	for ( netreq_p = completed_request->netreqs
	    ; (netreq = *netreq_p) ; netreq_p++) {

		if (! netreq->response_len)
			continue;

		nreplies++;
		if (netreq->secure)
			nsecure++;
		else if (! netreq->bogus)
			ninsecure++;
		if (dnssec_return_status && netreq->bogus)
			nbogus++;
		else if (GLDNS_RCODE_NOERROR ==
		    GLDNS_RCODE_WIRE(netreq->response))
			nanswers++;

		if (! completed_request->dnssec_return_validation_chain) {
			if (dnssec_return_status && netreq->bogus)
				continue;
			else if (completed_request->dnssec_return_only_secure
			    && ! netreq->secure)
				continue;
		}
    		if (!(reply = priv_getdns_create_reply_dict(context,
		    netreq, just_addrs, &rrsigs_in_answer)))
			goto error;

		if (!canonical_name) {
			if (getdns_dict_get_bindata(
			    reply, "canonical_name", &canonical_name))
				goto error;
			if (getdns_dict_set_bindata(
			    result, "canonical_name", canonical_name))
				goto error;
		}
		if (dnssec_return_status ||
		    completed_request->dnssec_return_validation_chain) {

			if (getdns_dict_set_int(reply, "dnssec_status",
			    ( netreq->secure   ? GETDNS_DNSSEC_SECURE
			    : netreq->bogus    ? GETDNS_DNSSEC_BOGUS
			    : rrsigs_in_answer &&
			      context->has_ta  ? GETDNS_DNSSEC_INDETERMINATE
					       : GETDNS_DNSSEC_INSECURE )))
				goto error;
		}

    		if (getdns_list_append_dict(replies_tree, reply)) {
    			getdns_dict_destroy(reply);
			goto error;
		}
    		getdns_dict_destroy(reply);

    		/* buffer */
		full_data.data = netreq->response;
		full_data.size = netreq->response_len;
		if (getdns_list_append_bindata(replies_full, &full_data))
			goto error;
    	}
    	if (getdns_dict_set_list(result, "replies_tree", replies_tree))
		goto error;
	getdns_list_destroy(replies_tree);

	if (getdns_dict_set_list(result, "replies_full", replies_full))
		goto error_free_replies_full;
	getdns_list_destroy(replies_full);

	if (just_addrs && getdns_dict_set_list(
	    result, GETDNS_STR_KEY_JUST_ADDRS, just_addrs))
		goto error_free_result;
	getdns_list_destroy(just_addrs);

	if (getdns_dict_set_int(result, GETDNS_STR_KEY_STATUS,
	    nreplies == 0   ? GETDNS_RESPSTATUS_ALL_TIMEOUT :
	    completed_request->dnssec_return_only_secure && nsecure == 0 && ninsecure > 0
	                    ? GETDNS_RESPSTATUS_NO_SECURE_ANSWERS :
	    completed_request->dnssec_return_only_secure && nsecure == 0 && nbogus > 0
	                    ? GETDNS_RESPSTATUS_ALL_BOGUS_ANSWERS :
	    nanswers == 0   ? GETDNS_RESPSTATUS_NO_NAME
	                    : GETDNS_RESPSTATUS_GOOD))
		goto error_free_result;

	return result;
error:
	/* cleanup */
	getdns_list_destroy(replies_tree);
error_free_replies_full:
	getdns_list_destroy(replies_full);
error_free_result:
	getdns_list_destroy(just_addrs);
	getdns_dict_destroy(result);
	return NULL;
}

static int
extformatcmp(const void *a, const void *b)
{
	return strcmp(((getdns_extension_format *) a)->extstring,
	    ((getdns_extension_format *) b)->extstring);
}

/*---------------------------------------- validate_extensions */
getdns_return_t
validate_extensions(struct getdns_dict * extensions)
{
	struct getdns_dict_item *item;
	getdns_extension_format *extformat;

	if (extensions)
		RBTREE_FOR(item, struct getdns_dict_item *,
		    &(extensions->root)) {

			getdns_extension_format key;
			key.extstring = (char *) item->node.key;
			extformat = bsearch(&key, extformats,
			    sizeof(extformats) /
			    sizeof(getdns_extension_format),
			    sizeof(getdns_extension_format), extformatcmp);
			if (!extformat)
				return GETDNS_RETURN_NO_SUCH_EXTENSION;

			if (item->dtype != extformat->exttype)
				return GETDNS_RETURN_EXTENSION_MISFORMAT;
		}
	return GETDNS_RETURN_GOOD;
}				/* validate_extensions */

getdns_return_t
getdns_apply_network_result(getdns_network_req* netreq,
    struct ub_result* ub_res)
{
	size_t dname_len;

	netreq->secure = ub_res->secure;
	netreq->bogus  = ub_res->bogus;

	if (ub_res == NULL) /* Timeout */
		return GETDNS_RETURN_GOOD;

	if (ub_res->answer_packet) {
		if (netreq->max_udp_payload_size < ub_res->answer_len)
			netreq->response = GETDNS_XMALLOC(
			    netreq->owner->context->mf,
			    uint8_t, ub_res->answer_len
			);
		(void) memcpy(netreq->response, ub_res->answer_packet,
		    (netreq->response_len = ub_res->answer_len));
		return GETDNS_RETURN_GOOD;
	}

    	if (ub_res->rcode == GETDNS_RCODE_SERVFAIL) {
		/* Likely to be caused by timeout from a synchronous
		 * lookup.  Don't forge a packet.
		 */
		return GETDNS_RETURN_GOOD;
	}
	/* Likely to be because libunbound refused the request
	 * so ub_res->answer_packet=NULL, ub_res->answer_len=0
	 * So we need to create an answer packet.
	 */
	gldns_write_uint16(netreq->response    , 0); /* query_id */
	gldns_write_uint16(netreq->response + 2, 0); /* reset all flags */
	gldns_write_uint16(netreq->response + GLDNS_QDCOUNT_OFF, 1);
	gldns_write_uint16(netreq->response + GLDNS_ANCOUNT_OFF, 0);
	gldns_write_uint16(netreq->response + GLDNS_NSCOUNT_OFF, 0);
	gldns_write_uint16(netreq->response + GLDNS_ARCOUNT_OFF, 0);

	GLDNS_OPCODE_SET(netreq->response, 3);
	GLDNS_QR_SET(netreq->response);
	GLDNS_RD_SET(netreq->response);
	GLDNS_RA_SET(netreq->response);
	GLDNS_RCODE_SET(netreq->response, ub_res->rcode);

	dname_len = netreq->max_udp_payload_size - GLDNS_HEADER_SIZE;
	if (gldns_str2wire_dname_buf(netreq->owner->name,
	    netreq->response + GLDNS_HEADER_SIZE, &dname_len))
		return GETDNS_RETURN_GENERIC_ERROR;

	gldns_write_uint16( netreq->response + GLDNS_HEADER_SIZE + dname_len
	                  , netreq->request_type);
	gldns_write_uint16( netreq->response + GLDNS_HEADER_SIZE + dname_len + 2
	                  , netreq->request_class);

	netreq->response_len = GLDNS_HEADER_SIZE + dname_len + 4;

	return GETDNS_RETURN_GOOD;
}


getdns_return_t
validate_dname(const char* dname) {
    int len;
    int label_len;
    const char* s;
    if (dname == NULL) {
        return GETDNS_RETURN_INVALID_PARAMETER;
    }
    len = strlen(dname);
    if (len > GETDNS_MAX_DNAME_LEN * 4 || len == 0) {
        return GETDNS_RETURN_BAD_DOMAIN_NAME;
    }
    if (len == 1 && dname[0] == '.') {
        /* root is ok */
        return GETDNS_RETURN_GOOD;
    }
	/* By specification [RFC1035] the total length of a DNS label is
	 * restricted to 63 octets and must be larger than 0 (except for the
	 * final root-label).  The total length of a domain name (i.e., label
	 * octets and label length octets) is restricted to 255 octets or less.
	 * With a fully qualified domain name this includes the last label
	 * length octet for the root label.  In a normalized representation the
	 * number of labels (including the root) plus the number of octets in
	 * each label may not be larger than 255.
	 */
    len = 0;
    label_len = 0;
    for (s = dname; *s; ++s) {
        switch (*s) {
            case '.':
                if (label_len > GETDNS_MAX_LABEL_LEN ||
                    label_len == 0) {
                    return GETDNS_RETURN_BAD_DOMAIN_NAME;
                }
                label_len = 0;
		len += 1;
                break;
	    case '\\':
		s += 1;
		if (isdigit(s[0])) {
			/* octet value */
			if (! isdigit(s[1]) && ! isdigit(s[2]))
				return GETDNS_RETURN_BAD_DOMAIN_NAME;

			if ((s[0] - '0') * 100 +
			    (s[1] - '0') * 10 + (s[2] - '0') > 255)
				return GETDNS_RETURN_BAD_DOMAIN_NAME;

			s += 2;
		}
		/* else literal char (1 octet) */
		label_len++;
		len += 1;
		break;
            default:
                label_len++;
		len += 1;
                break;
        }
    }
    if (len > GETDNS_MAX_DNAME_LEN || label_len > GETDNS_MAX_LABEL_LEN) {
        return GETDNS_RETURN_BAD_DOMAIN_NAME;
    }
    return GETDNS_RETURN_GOOD;
} /* validate_dname */


/* util-internal.c */