/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "private-lib-core.h"
#include "private-lib-async-dns.h"

static const uint32_t botable[] = { 500, 1000, 1250, 5000
				/* in case everything just dog slow */ };
static const lws_retry_bo_t retry_policy = {
	botable, LWS_ARRAY_SIZE(botable), LWS_ARRAY_SIZE(botable),
	/* don't conceal after the last table entry */ 0, 0, 20 };

void
lws_adns_q_destroy(lws_adns_q_t *q)
{
	lws_dll2_remove(&q->sul.list);
	lws_dll2_remove(&q->list);
	lws_free(q);
}

lws_adns_q_t *
lws_adns_get_query(lws_async_dns_t *dns, adns_query_type_t qtype,
		   lws_dll2_owner_t *owner, uint16_t tid, const char *name)
{
	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(owner)) {
		lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);

		if (!name && (tid & 0xfffe) == (q->tid & 0xfffe))
			return q;

		if (name && q->qtype == ((tid & 1) ? LWS_ADNS_RECORD_AAAA :
						     LWS_ADNS_RECORD_A) &&
		    !strcasecmp(name, (const char *)&q[1])) {
			if (owner == &dns->cached) {
				/* Keep sorted by LRU: move to the head */
				lws_dll2_remove(&q->list);
				lws_dll2_add_head(&q->list, &dns->cached);
			}

			return q;
		}
	} lws_end_foreach_dll_safe(d, d1);

	return NULL;
}

void
lws_async_dns_drop_server(struct lws_context *context)
{
	context->async_dns.dns_server_set = 0;
	lws_set_timeout(context->async_dns.wsi, 1, LWS_TO_KILL_ASYNC);
	context->async_dns.wsi = NULL;
}

int
lws_async_dns_complete(lws_adns_q_t *q, lws_adns_cache_t *c)
{

	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&q->wsi_adns)) {
		struct lws *w = lws_container_of(d, struct lws, adns);

		lws_dll2_remove(d);
		if (c && c->results) {
			lwsl_debug("%s: q: %p, c: %p, refcount %d -> %d\n",
				    __func__, q, c, c->refcount, c->refcount + 1);
			c->refcount++;
		}
		lws_set_timeout(w, NO_PENDING_TIMEOUT, 0);
		/*
		 * This may decide to close / delete w
		 */
		if (w->adns_cb(w, (const char *)&q[1], c ? c->results : NULL, 0,
				q->opaque) == NULL)
			lwsl_notice("%s: failed\n", __func__);
	//		lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS,
	//				   "adopt udp2 fail");

	} lws_end_foreach_dll_safe(d, d1);

	if (q->standalone_cb) {
		if (c && c->results) {
			lwsl_debug("%s: q: %p, c: %p, refcount %d -> %d\n",
				    __func__, q, c, c->refcount, c->refcount + 1);
			c->refcount++;
		}

		q->standalone_cb(NULL, (const char *)&q[1],
				 c ? c->results : NULL, 0, q->opaque);
	}

	return 0;
}

static void
lws_async_dns_sul_cb_retry(struct lws_sorted_usec_list *sul)
{
	lws_adns_q_t *q = lws_container_of(sul, lws_adns_q_t, sul);

	// lwsl_notice("%s\n", __func__);

	lws_callback_on_writable(q->dns->wsi);
}

static void
lws_async_dns_writeable(struct lws *wsi, lws_adns_q_t *q)
{
	uint8_t pkt[LWS_PRE + DNS_PACKET_LEN], *e = &pkt[sizeof(pkt)], *p, *pl;
	int m, n, which;
	const char *name;

	// lwsl_notice("%s: %p\n", __func__, q);

	/*
	 * UDP is not reliable, it can be locally dropped, or dropped
	 * by any intermediary or the remote peer.  So even though we
	 * will do the write in a moment, we schedule another request
	 * for rewrite according to the wsi retry policy.
	 *
	 * If the result came before, we'll cancel it as part of the
	 * wsi close.
	 *
	 * If we have already reached the end of our concealed retries
	 * in the policy, just close without another write.
	 */
	if (lws_dll2_is_detached(&q->sul.list) &&
	    lws_retry_sul_schedule_retry_wsi(wsi, &q->sul,
				       lws_async_dns_sul_cb_retry, &q->retry)) {
		/* we have reached the end of our concealed retries */
		lwsl_notice("%s: failing query\n", __func__);
		/*
		 * our policy is to force reloading the dns server info
		 * if our connection ever timed out, in case it or the
		 * routing state changed
		 */

		lws_async_dns_drop_server(q->context);
		goto qfail;
	}

	name = (const char *)&q[1];

	p = &pkt[LWS_PRE];
	memset(p, 0, DHO_SIZEOF);

#if defined(LWS_WITH_IPV6)
	if (!q->responded) {
		/* must pick between ipv6 and ipv4 */
		which = q->sent[0] >= q->sent[1];
		q->sent[which]++;
		q->asked = 3; /* want results for 4 & 6 before done */
	} else
		which = q->responded & 1;
#else
	which = 0;
	q->asked = 1;
#endif

	/* we hack b0 of the tid to be 0 = A, 1 = AAAA */

	lws_ser_wu16be(&p[DHO_TID],
#if defined(LWS_WITH_IPV6)
			which ? q->tid | 1 :
#endif
			q->tid);
	lws_ser_wu16be(&p[DHO_FLAGS], (1 << 8));
	lws_ser_wu16be(&p[DHO_NQUERIES], 1);

	p += DHO_SIZEOF;

	/* start of label-formatted qname */

	pl = p++;

	do {
		if (*name == '.' || !*name) {
			*pl = lws_ptr_diff(p, pl + 1);
			pl = p;
			*p++ = 0; /* also serves as terminal length */
			if (!*name++)
				break;
		} else
			*p++ = *name++;
	} while (p + 6 < e);

	if (p + 6 >= e) {
		assert(0);
		lwsl_err("%s: name too big\n", __func__);
		goto qfail;
	}

	lws_ser_wu16be(p, which ? LWS_ADNS_RECORD_AAAA :
				     LWS_ADNS_RECORD_A);
	p += 2;

	lws_ser_wu16be(p, 1); /* IN class */
	p += 2;

	assert(p < pkt + sizeof(pkt) - LWS_PRE);
	n = lws_ptr_diff(p, pkt + LWS_PRE);
	m = lws_write(wsi, pkt + LWS_PRE, n, 0);
	if (m != n) {
		lwsl_notice("%s: dns write failed %d %d errno %d\n", __func__,
			    m, n, errno);
		goto qfail;
	}

#if defined(LWS_WITH_IPV6)
	if (!q->responded && q->sent[0] != q->sent[1])
		lws_callback_on_writable(wsi);
#endif

	/* if we did anything, check one more time */
	lws_callback_on_writable(wsi);

	return;

qfail:
	lwsl_warn("%s: failing query doing NULL completion\n", __func__);
	/*
	 * in ipv6 case, we made a cache entry for the first response but
	 * evidently the second response didn't come in time, purge the
	 * incomplete cache entry
	 */
	if (q->firstcache)
		lws_adns_cache_destroy(q->firstcache);
	lws_async_dns_complete(q, NULL);
	lws_adns_q_destroy(q);
}

static int
callback_async_dns(struct lws *wsi, enum lws_callback_reasons reason,
		   void *user, void *in, size_t len)
{
	struct lws_async_dns *dns = &(lws_get_context(wsi)->async_dns);

	switch (reason) {

	/* callbacks related to raw socket descriptor */

        case LWS_CALLBACK_RAW_ADOPT:
		// lwsl_user("LWS_CALLBACK_RAW_ADOPT\n");
                break;

	case LWS_CALLBACK_RAW_CLOSE:
		// lwsl_user("LWS_CALLBACK_RAW_CLOSE\n");
		break;

	case LWS_CALLBACK_RAW_RX:
		// lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len);
		// lwsl_hexdump_level(LLL_NOTICE, in, len);
		lws_adns_parse_udp(dns, in, len);
		break;

	case LWS_CALLBACK_RAW_WRITEABLE:
		// lwsl_notice("%s: WRITABLE\n", __func__);

		lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
					   dns->waiting.head) {
			lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t,
							   list);

			if (lws_dll2_is_detached(&q->sul.list) &&
			    (!q->asked || q->responded != q->asked))
				lws_async_dns_writeable(wsi, q);
		} lws_end_foreach_dll_safe(d, d1);
		break;

	default:
		break;
	}

	return 0;
}

struct lws_protocols lws_async_dns_protocol = {
	"lws-async-dns", callback_async_dns, 0, 0
};

int
lws_async_dns_init(struct lws_context *context)
{
	lws_async_dns_t *dns = &context->async_dns;
	char ads[48];
	int n;

	if (!context->vhost_list) { /* coverity... system vhost always present */
		lwsl_err("%s: no system vhost\n", __func__);
		return 1;
	}

	memset(&dns->sa46, 0, sizeof(dns->sa46));

#if defined(LWS_WITH_SYS_DHCP_CLIENT)
	if (lws_dhcpc_status(context, &dns->sa46))
		goto ok;
#endif

	n = lws_plat_asyncdns_init(context, &dns->sa46);
	if (n < 0) {
		lwsl_warn("%s: no valid dns server, retry\n", __func__);

		return 1;
	}

	if (n != LADNS_CONF_SERVER_CHANGED)
		return 0;

#if defined(LWS_WITH_SYS_DHCP_CLIENT)
ok:
#endif
	dns->sa46.sa4.sin_port = htons(53);
	lws_write_numeric_address((uint8_t *)&dns->sa46.sa4.sin_addr.s_addr, 4,
				  ads, sizeof(ads));

	context->async_dns.wsi = lws_create_adopt_udp(context->vhost_list, ads,
				      53, 0, lws_async_dns_protocol.name, NULL,
				      NULL, NULL, &retry_policy);
	if (!dns->wsi) {
		lwsl_err("%s: foreign socket adoption failed\n", __func__);
		return 1;
	}

	dns->dns_server_set = 1;

	return 0;
}

lws_adns_cache_t *
lws_adns_get_cache(lws_async_dns_t *dns, const char *name)
{
	lws_adns_cache_t *c;
	const char *cn;

	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&dns->cached)) {
		c = lws_container_of(d, lws_adns_cache_t, list);
		cn = (const char *)&c[1];

		if (name && !c->incomplete && !strcasecmp(name, cn)) {
			/* Keep sorted by LRU: move to the head */
			lws_dll2_remove(&c->list);
			lws_dll2_add_head(&c->list, &dns->cached);

			return c;
		}
	} lws_end_foreach_dll_safe(d, d1);

	return NULL;
}

void
lws_adns_cache_destroy(lws_adns_cache_t *c)
{
	lws_dll2_remove(&c->sul.list);
	lws_dll2_remove(&c->list);
	if (c->chain)
		lws_free(c->chain);
	lws_free(c);
}

static int
cache_clean(struct lws_dll2 *d, void *user)
{
	lws_adns_cache_destroy(lws_container_of(d, lws_adns_cache_t, list));

	return 0;
}

void
sul_cb_expire(struct lws_sorted_usec_list *sul)
{
	lws_adns_cache_t *c = lws_container_of(sul, lws_adns_cache_t, sul);

	lws_adns_cache_destroy(c);
}

void
lws_async_dns_freeaddrinfo(const struct addrinfo **pai)
{
	lws_adns_cache_t *c;

	if (!*pai)
		return;

	/*
	 * First query may have been empty... if second has something, we
	 * fixed up the first result to point to second... but it means
	 * looking backwards from ai, which is c->result, which is the second
	 * packet's results, doesn't get us to the firstcache pointer.
	 *
	 * Adjust c to the firstcache in this case.
	 */

	c = &((lws_adns_cache_t *)(*pai))[-1];
	if (c->firstcache)
		c = c->firstcache;

	lwsl_debug("%s: c %p, %s, refcount %d -> %d\n", __func__, c,
		   (c->results && c->results->ai_canonname) ?
				c->results->ai_canonname : "none",
						c->refcount, c->refcount - 1);

	assert(c->refcount > 0);
	c->refcount--;
	*pai = NULL;
}

void
lws_async_dns_trim_cache(lws_async_dns_t *dns)
{
	lws_adns_cache_t *c1;

	if (dns->cached.count + 1< MAX_CACHE_ENTRIES)
		return;

	c1 = lws_container_of(lws_dll2_get_tail(&dns->cached),
						lws_adns_cache_t, list);
	if (c1->refcount)
		lwsl_notice("%s: wsi %p: refcount %d on purge\n",
				__func__, c1, c1->refcount);
	else
		lws_adns_cache_destroy(c1);
}


static int
clean(struct lws_dll2 *d, void *user)
{
	lws_adns_q_destroy(lws_container_of(d, lws_adns_q_t, list));

	return 0;
}

void
lws_async_dns_deinit(lws_async_dns_t *dns)
{
	lws_dll2_foreach_safe(&dns->waiting, NULL, clean);
	lws_dll2_foreach_safe(&dns->cached, NULL, cache_clean);
}


static int
cancel(struct lws_dll2 *d, void *user)
{
	lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);

	lws_start_foreach_dll_safe(struct lws_dll2 *, d3, d4,
				   lws_dll2_get_head(&q->wsi_adns)) {
		struct lws *w = lws_container_of(d3, struct lws, adns);

		if (user == w) {
			lws_dll2_remove(d3);
			if (!q->wsi_adns.count)
				lws_adns_q_destroy(q);
			return 1;
		}
	} lws_end_foreach_dll_safe(d3, d4);

	return 0;
}

void
lws_async_dns_cancel(struct lws *wsi)
{
	lws_async_dns_t *dns = &wsi->a.context->async_dns;

	lws_dll2_foreach_safe(&dns->waiting, wsi, cancel);
}


static int
check_tid(struct lws_dll2 *d, void *user)
{
	lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);

	return q->tid == (uint16_t)(intptr_t)user;
}

int
lws_async_dns_get_new_tid(struct lws_context *context, lws_adns_q_t *q)
{
	lws_async_dns_t *dns = &context->async_dns;
	int budget = 10;

	/*
	 * Make the TID unpredictable, but must be unique amongst ongoing ones
	 */
	do {
		uint16_t tid;

		if (lws_get_random(context, &tid, 2) != 2)
			return -1;

		if (lws_dll2_foreach_safe(&dns->waiting,
					  (void *)(intptr_t)tid, check_tid))
			continue;

		q->tid = tid;

		return 0;

	} while (budget--);

	lwsl_err("%s: unable to get unique tid\n", __func__);

	return -1;
}

struct temp_q {
	lws_adns_q_t tq;
	char name[48];
};

lws_async_dns_retcode_t
lws_async_dns_query(struct lws_context *context, int tsi, const char *name,
		    adns_query_type_t qtype, lws_async_dns_cb_t cb,
		    struct lws *wsi, void *opaque)
{
	lws_async_dns_t *dns = &context->async_dns;
	size_t nlen = strlen(name);
	lws_sockaddr46 *sa46;
	lws_adns_cache_t *c;
	struct addrinfo *ai;
	struct temp_q tmq;
	lws_adns_q_t *q;
	uint8_t ads[16];
	char *p;
	int m;

#if !defined(LWS_WITH_IPV6)
	if (qtype == LWS_ADNS_RECORD_AAAA) {
		lwsl_err("%s: ipv6 not enabled\n", __func__);
		goto failed;
	}
#endif

	if (nlen >= DNS_MAX - 1)
		goto failed;

	/*
	 * we magically know 'localhost' and 'localhost6' if IPv6, this is a
	 * sort of canned /etc/hosts
	 */

	if (!strcmp(name, "localhost"))
		name = "127.0.0.1";

#if defined(LWS_WITH_IPV6)
	if (!strcmp(name, "localhost6"))
		name = "::1";
#endif

	if (wsi) {
		if (!lws_dll2_is_detached(&wsi->adns)) {
			lwsl_err("%s: wsi %p already bound to query %p\n",
					__func__, wsi, wsi->adns.owner);
			goto failed;
		}
		wsi->adns_cb = cb;
	}

	/* there's a done, cached query we can just reuse? */

	c = lws_adns_get_cache(dns, name);
	if (c) {
		lwsl_err("%s: using cached, c->results %p\n", __func__, c->results);
		m = c->results ? LADNS_RET_FOUND : LADNS_RET_FAILED;
		if (c->results)
			c->refcount++;
		if (cb(wsi, name, c->results, m, opaque) == NULL)
			return LADNS_RET_FAILED_WSI_CLOSED;

		return m;
	}

	/*
	 * It's a 1.2.3.4 type IP address already?  We don't need a dns
	 * server set up to be able to create an addrinfo result for that.
	 *
	 * Create it as a cached object so it follows the refcount lifecycle
	 * of any other result
	 */

	m = lws_parse_numeric_address(name, ads, sizeof(ads));
	if (m == 4
#if defined(LWS_WITH_IPV6)
		|| m == 16
#endif
	) {
		lws_async_dns_trim_cache(dns);

		c = lws_zalloc(sizeof(lws_adns_cache_t) +
			       sizeof(struct addrinfo) +
			       sizeof(lws_sockaddr46) + nlen + 1, "adns-numip");
		if (!c)
			goto failed;

		ai = (struct addrinfo *)&c[1];
		sa46 = (lws_sockaddr46 *)&ai[1];

		ai->ai_socktype = SOCK_STREAM;
		memcpy(&sa46[1], name, nlen + 1);
		ai->ai_canonname = (char *)&sa46[1];

		c->results = ai;
		memset(&tmq.tq, 0, sizeof(tmq.tq));
		tmq.tq.opaque = opaque;
		if (wsi) {
			wsi->adns_cb = cb;
			lws_dll2_add_head(&wsi->adns, &tmq.tq.wsi_adns);
		} else
			tmq.tq.standalone_cb = cb;
		lws_strncpy(tmq.name, name, sizeof(tmq.name));

		lws_dll2_add_head(&c->list, &dns->cached);
		lws_sul_schedule(context, 0, &c->sul, sul_cb_expire,
				 lws_now_usecs() + (3600ll * LWS_US_PER_SEC));
	}

	if (m == 4) {
		ai->ai_family = sa46->sa4.sin_family = AF_INET;
		ai->ai_addrlen = sizeof(sa46->sa4);
		ai->ai_addr = (struct sockaddr *)&sa46->sa4;
		memcpy(&sa46->sa4.sin_addr, ads, m);

		lws_async_dns_complete(&tmq.tq, c);

		return LADNS_RET_FOUND;
	}

#if defined(LWS_WITH_IPV6)
	if (m == 16) {
		ai->ai_family = sa46->sa6.sin6_family = AF_INET6;
		ai->ai_addrlen = sizeof(sa46->sa6);
		ai->ai_addr = (struct sockaddr *)&sa46->sa6;
		memcpy(&sa46->sa6.sin6_addr, ads, m);

		lws_async_dns_complete(&tmq.tq, c);

		return LADNS_RET_FOUND;
	}
#endif

	/*
	 * to try anything else we need a remote server configured...
	 */

	if (!context->async_dns.dns_server_set &&
	    lws_async_dns_init(context)) {
		lwsl_notice("%s: init failed\n", __func__);
		goto failed;
	}

	/* there's an ongoing query we can share the result of */

	q = lws_adns_get_query(dns, qtype, &dns->waiting, 0, name);
	if (q) {
		lwsl_debug("%s: dns piggybacking: %d:%s\n", __func__,
				qtype, name);
		if (wsi)
			lws_dll2_add_head(&wsi->adns, &q->wsi_adns);

		return LADNS_RET_CONTINUING;
	}

	/*
	 * Allocate new query / queries... this is a bit complicated because
	 * multiple queries in one packet are not supported peoperly in DNS
	 * itself, and there's no reliable other way to get both ipv6 and ipv4
	 * (AAAA and A) responses in one hit.
	 *
	 * If we don't support ipv6, it's simple, we just ask for A and that's
	 * it.  But if we do support ipv6, we need to ask twice, once for A
	 * and in a separate query, again for AAAA.
	 *
	 * For ipv6, A / ipv4 is routable over ipv6.  So we always ask for A
	 * first and then if ipv6, AAAA separately.
	 *
	 * Allocate for DNS_MAX, because we may recurse and alter what we're
	 * looking for.
	 *
	 * 0             sizeof(*q)                  sizeof(*q) + DNS_MAX
	 * [lws_adns_q_t][ name (DNS_MAX reserved) ] [ name \0 ]
	 */

	q = (lws_adns_q_t *)lws_malloc(sizeof(*q) + DNS_MAX + nlen + 1,
					__func__);
	if (!q)
		goto failed;
	memset(q, 0, sizeof(*q));

	if (wsi)
		lws_dll2_add_head(&wsi->adns, &q->wsi_adns);

	q->qtype = (uint16_t)qtype;

	if (lws_async_dns_get_new_tid(context, q))
		goto failed;

	q->tid &= 0xfffe;
	q->context = context;
	q->tsi = tsi;
	q->opaque = opaque;
	q->dns = dns;

	if (!wsi)
		q->standalone_cb = cb;

	/* schedule a retry according to the retry policy on the wsi */
	if (lws_retry_sul_schedule_retry_wsi(dns->wsi, &q->sul,
					 lws_async_dns_sul_cb_retry, &q->retry))
		goto failed;

	/*
	 * We may rewrite the copy at +sizeof(*q) for CNAME recursion.  Keep
	 * a second copy at + sizeof(*q) + DNS_MAX so we can create the cache
	 * entry for the original name, not the last CNAME we met.
	 */

	p = (char *)&q[1];
	while (nlen--) {
		*p++ = tolower(*name++);
		p[DNS_MAX - 1] = p[-1];
	}
	*p = '\0';
	p[DNS_MAX] = '\0';

	lws_callback_on_writable(dns->wsi);

	lws_dll2_add_head(&q->list, &dns->waiting);

	lwsl_debug("%s: created new query\n", __func__);

	return LADNS_RET_CONTINUING;

failed:
	cb(wsi, NULL, NULL, LADNS_RET_FAILED, opaque);

	return LADNS_RET_FAILED;
}
