/**
 * @file mock/dnssrv.c Mock DNS server
 *
 * Copyright (C) 2010 - 2016 Alfred E. Heggestad
 */
#include <string.h>
#include <re.h>
#include "../test.h"


#define DEBUG_MODULE "mock/dnssrv"
#define DEBUG_LEVEL 5
#include <re_dbg.h>


#define LOCAL_PORT 0


static void dns_server_match(struct dns_server *srv, struct list *rrl,
			     const char *name, uint16_t type)
{
	struct dnsrr *rr0 = NULL;
	struct le *le;

	le = srv->rrl.head;
	while (le) {

		struct dnsrr *rr = le->data;
		le = le->next;

		if (type == rr->type && 0==str_casecmp(name, rr->name)) {

			if (!rr0)
				rr0 = rr;
			list_append(rrl, &rr->le_priv, rr);
		}
	}

	/* If rotation is enabled, then rotate multiple entries
	   in a deterministic way (no randomness please) */
	if (srv->rotate && rr0) {
		list_unlink(&rr0->le);
		list_append(&srv->rrl, &rr0->le, rr0);
	}
}


static void decode_dns_query(struct dns_server *srv,
			     const struct sa *src, struct mbuf *mb)
{
	struct list rrl = LIST_INIT;
	struct dnshdr hdr;
	struct le *le;
	char *qname = NULL;
	size_t start, end;
	uint16_t type, dnsclass;
	int err = 0;

	start = mb->pos;
	end   = mb->end;

	if (dns_hdr_decode(mb, &hdr) || hdr.qr || hdr.nq != 1) {
		DEBUG_WARNING("unable to decode query header\n");
		return;
	}

	err = dns_dname_decode(mb, &qname, start);
	if (err) {
		DEBUG_WARNING("unable to decode query name\n");
		goto out;
	}

	if (mbuf_get_left(mb) < 4) {
		DEBUG_WARNING("unable to decode query type/class\n");
		goto out;
	}

	type     = ntohs(mbuf_read_u16(mb));
	dnsclass = ntohs(mbuf_read_u16(mb));

	DEBUG_INFO("dnssrv: type=%s query-name='%s'\n",
		   dns_rr_typename(type), qname);

	if (dnsclass == DNS_CLASS_IN) {
		dns_server_match(srv, &rrl, qname, type);
	}

	hdr.qr    = true;
	hdr.tc    = false;
	hdr.rcode = DNS_RCODE_OK;
	hdr.nq    = 1;
	hdr.nans  = list_count(&rrl);

	mb->pos = start;

	err = dns_hdr_encode(mb, &hdr);
	if (err)
		goto out;

	mb->pos = end;

	DEBUG_INFO("dnssrv: @@ found %u answers for %s\n",
		   list_count(&rrl), qname);

	for (le = rrl.head; le; le = le->next) {
		struct dnsrr *rr = le->data;

		err = dns_rr_encode(mb, rr, 0, NULL, start);
		if (err)
			goto out;
	}

	mb->pos = start;

	(void)udp_send(srv->us, src, mb);

 out:
	list_clear(&rrl);
	mem_deref(qname);
}


static void udp_recv(const struct sa *src, struct mbuf *mb, void *arg)
{
	struct dns_server *srv = arg;

	decode_dns_query(srv, src, mb);
}


static void destructor(void *arg)
{
	struct dns_server *srv = arg;

	list_flush(&srv->rrl);
	mem_deref(srv->us);
}


int dns_server_alloc(struct dns_server **srvp, bool rotate)
{
	struct dns_server *srv;
	int err;

	if (!srvp)
		return EINVAL;

	srv = mem_zalloc(sizeof(*srv), destructor);
	if (!srv)
		return ENOMEM;

	err = sa_set_str(&srv->addr, "127.0.0.1", LOCAL_PORT);
	if (err)
		goto out;

	err = udp_listen(&srv->us, &srv->addr, udp_recv, srv);
	if (err)
		goto out;

	err = udp_local_get(srv->us, &srv->addr);
	if (err)
		goto out;

	srv->rotate = rotate;

 out:
	if (err)
		mem_deref(srv);
	else
		*srvp = srv;

	return err;
}


int dns_server_add_a(struct dns_server *srv, const char *name, uint32_t addr)
{
	struct dnsrr *rr;
	int err;

	if (!srv || !name)
		return EINVAL;

	rr = dns_rr_alloc();
	if (!rr)
		return ENOMEM;

	err = str_dup(&rr->name, name);
	if (err)
		goto out;

	rr->type = DNS_TYPE_A;
	rr->dnsclass = DNS_CLASS_IN;
	rr->ttl = 3600;
	rr->rdlen = 0;

	rr->rdata.a.addr = addr;

	list_append(&srv->rrl, &rr->le, rr);

 out:
	if (err)
		mem_deref(rr);

	return err;
}


int dns_server_add_aaaa(struct dns_server *srv, const char *name,
			const uint8_t *addr)
{
	struct dnsrr *rr;
	int err;

	if (!srv || !name)
		return EINVAL;

	rr = dns_rr_alloc();
	if (!rr)
		return ENOMEM;

	err = str_dup(&rr->name, name);
	if (err)
		goto out;

	rr->type = DNS_TYPE_AAAA;
	rr->dnsclass = DNS_CLASS_IN;
	rr->ttl = 3600;
	rr->rdlen = 0;

	memcpy(rr->rdata.aaaa.addr, addr, 16);

	list_append(&srv->rrl, &rr->le, rr);

 out:
	if (err)
		mem_deref(rr);

	return err;
}


int dns_server_add_srv(struct dns_server *srv, const char *name,
		       uint16_t pri, uint16_t weight, uint16_t port,
		       const char *target)
{
	struct dnsrr *rr;
	int err;

	if (!srv || !name || !port || !target)
		return EINVAL;

	rr = dns_rr_alloc();
	if (!rr)
		return ENOMEM;

	err = str_dup(&rr->name, name);
	if (err)
		goto out;

	rr->type = DNS_TYPE_SRV;
	rr->dnsclass = DNS_CLASS_IN;
	rr->ttl = 3600;
	rr->rdlen = 0;

	rr->rdata.srv.pri = pri;
	rr->rdata.srv.weight = weight;
	rr->rdata.srv.port = port;
	str_dup(&rr->rdata.srv.target, target);

	list_append(&srv->rrl, &rr->le, rr);

 out:
	if (err)
		mem_deref(rr);

	return err;
}
