/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2019 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 int
lws_get_idlest_tsi(struct lws_context *context)
{
	unsigned int lowest = ~0u;
	int n = 0, hit = -1;

	for (; n < context->count_threads; n++) {
		lwsl_cx_debug(context, "%d %d\n", context->pt[n].fds_count,
				context->fd_limit_per_thread - 1);
		if ((unsigned int)context->pt[n].fds_count !=
		    context->fd_limit_per_thread - 1 &&
		    (unsigned int)context->pt[n].fds_count < lowest) {
			lowest = context->pt[n].fds_count;
			hit = n;
		}
	}

	return hit;
}

struct lws *
lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi, int group,
			  const char *desc)
{
	struct lws *new_wsi;
	int n = fixed_tsi;

	if (n < 0)
		n = lws_get_idlest_tsi(vhost->context);

	if (n < 0) {
		lwsl_vhost_err(vhost, "no space for new conn");
		return NULL;
	}

	lws_context_lock(vhost->context, __func__);
	new_wsi = __lws_wsi_create_with_role(vhost->context, n, NULL,
					     vhost->lc.log_cx);
	lws_context_unlock(vhost->context);
	if (new_wsi == NULL) {
		lwsl_vhost_err(vhost, "OOM");
		return NULL;
	}

	lws_wsi_fault_timedclose(new_wsi);

	__lws_lc_tag(vhost->context, &vhost->context->lcg[group],
			&new_wsi->lc, "%s|%s", vhost->name, desc);

	new_wsi->wsistate |= LWSIFR_SERVER;
	new_wsi->tsi = (char)n;
	lwsl_wsi_debug(new_wsi, "joining vh %s, tsi %d",
			vhost->name, new_wsi->tsi);

	lws_vhost_bind_wsi(vhost, new_wsi);
	new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
	new_wsi->retry_policy = vhost->retry_policy;

	/* initialize the instance struct */

	lwsi_set_state(new_wsi, LRS_UNCONNECTED);
	new_wsi->hdr_parsing_completed = 0;

#ifdef LWS_WITH_TLS
	new_wsi->tls.use_ssl = LWS_SSL_ENABLED(vhost);
#endif

	/*
	 * these can only be set once the protocol is known
	 * we set an un-established connection's protocol pointer
	 * to the start of the supported list, so it can look
	 * for matching ones during the handshake
	 */
	new_wsi->a.protocol = vhost->protocols;
	new_wsi->user_space = NULL;

	/*
	 * outermost create notification for wsi
	 * no user_space because no protocol selection
	 */
	vhost->protocols[0].callback(new_wsi, LWS_CALLBACK_WSI_CREATE, NULL,
				     NULL, 0);

	return new_wsi;
}


/* if not a socket, it's a raw, non-ssl file descriptor
 * req cx lock, acq pt lock, acq vh lock
 */

static struct lws *
__lws_adopt_descriptor_vhost1(struct lws_vhost *vh, lws_adoption_type type,
			    const char *vh_prot_name, struct lws *parent,
			    void *opaque, const char *fi_wsi_name)
{
	struct lws_context *context;
	struct lws_context_per_thread *pt;
	struct lws *new_wsi;
	int n;

	/*
	 * Notice that in SMP case, the wsi may be being created on an
	 * entirely different pt / tsi for load balancing.  In that case as
	 * we initialize it, it may become "live" concurrently unexpectedly...
	 */

	if (!vh)
		return NULL;

	context = vh->context;

	lws_context_assert_lock_held(vh->context);

	n = -1;
	if (parent)
		n = parent->tsi;
	new_wsi = lws_create_new_server_wsi(vh, n, LWSLCG_WSI_SERVER, fi_wsi_name);
	if (!new_wsi)
		return NULL;

	/* bring in specific fault injection rules early */
	lws_fi_inherit_copy(&new_wsi->fic, &context->fic, "wsi", fi_wsi_name);

	if (lws_fi(&new_wsi->fic, "createfail")) {
		lws_fi_destroy(&new_wsi->fic);

		return NULL;
	}

	new_wsi->a.opaque_user_data = opaque;

	pt = &context->pt[(int)new_wsi->tsi];
	lws_pt_lock(pt, __func__);

	if (parent) {
		new_wsi->parent = parent;
		new_wsi->sibling_list = parent->child_list;
		parent->child_list = new_wsi;
	}

	if (vh_prot_name) {
		new_wsi->a.protocol = lws_vhost_name_to_protocol(new_wsi->a.vhost,
							       vh_prot_name);
		if (!new_wsi->a.protocol) {
			if (!strcmp(vh_prot_name, "quic")) {
				new_wsi->a.protocol = &new_wsi->a.vhost->protocols[0];
			} else {
				lwsl_vhost_err(new_wsi->a.vhost, "Protocol %s not enabled",
							      vh_prot_name);
				goto bail;
			}
		}
		if (lws_ensure_user_space(new_wsi)) {
			lwsl_wsi_notice(new_wsi, "OOM");
			goto bail;
		}
	}

	if (!LWS_SSL_ENABLED(new_wsi->a.vhost) ||
	    !(type & LWS_ADOPT_SOCKET))
		type &= (unsigned int)~LWS_ADOPT_ALLOW_SSL;

	if (lws_role_call_adoption_bind(new_wsi, (int)type, vh_prot_name)) {
		lwsl_wsi_err(new_wsi, "no role for desc type 0x%x", type);
		goto bail;
	}

#if defined(LWS_WITH_SERVER)
	if (new_wsi->role_ops) {
		lws_metrics_tag_wsi_add(new_wsi, "role", new_wsi->role_ops->name);
	}
#endif

	lws_pt_unlock(pt);

	/*
	 * We can lose him from the context pre_natal "last resort" bind now,
	 * because we will list him on a specific vhost
	 */

	lws_dll2_remove(&new_wsi->pre_natal);

	/*
	 * he's an allocated wsi, but he's not on any fds list or child list,
	 * join him to the vhost's list of these kinds of incomplete wsi until
	 * he gets another identity (he may do async dns now...)
	 */
	lws_vhost_lock(new_wsi->a.vhost);

	lws_dll2_add_head(&new_wsi->vh_awaiting_socket,
			  &new_wsi->a.vhost->vh_awaiting_socket_owner);
	lws_vhost_unlock(new_wsi->a.vhost);

	return new_wsi;

bail:
        lws_pt_lock(pt, __func__); /* -------------- pt { */
        lws_dll2_remove(&new_wsi->pre_natal);
        lws_pt_unlock(pt); /* } pt --------------- */

	lwsl_wsi_notice(new_wsi, "exiting on bail");
	if (parent)
		parent->child_list = new_wsi->sibling_list;
	if (new_wsi->user_space)
		lws_free(new_wsi->user_space);

	lws_fi_destroy(&new_wsi->fic);

	lws_pt_unlock(pt);
	__lws_vhost_unbind_wsi(new_wsi); /* req cx, acq vh lock */

	lws_free(new_wsi);

	return NULL;
}

#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS)

/*
 * If the incoming wsi is bound to a vhost that is a ss server, this creates
 * an accepted ss bound to the wsi.
 *
 * For h1 or raw, we can do the binding here, but for muxed protocols like h2
 * or mqtt we have to do it not on the nwsi but on the stream.  And for h2 we
 * start off bound to h1 role, since we don't know if we will upgrade to h2
 * until we meet the server.
 *
 * 1) No tls is assumed to mean no muxed protocol so can do it at adopt.
 *
 * 2) After alpn if not muxed we can do it.
 *
 * 3) For muxed, do it at the nwsi migration and on new stream
 */

int
lws_adopt_ss_server_accept(struct lws *new_wsi)
{
	struct lws_context_per_thread *pt =
			&new_wsi->a.context->pt[(int)new_wsi->tsi];
	lws_ss_handle_t *h;
	void *pv, **ppv;

	if (!new_wsi->a.vhost->ss_handle)
		return 0;

	pv = (char *)&new_wsi->a.vhost->ss_handle[1];

	/*
	 * Yes... the vhost is pointing to its secure stream representing the
	 * server... we want to create an accepted SS and bind it to new_wsi,
	 * the info/ssi from the server SS (so the SS callbacks defined there),
	 * the opaque_user_data of the server object and the policy of it.
	 */

	ppv = (void **)((char *)pv +
	      new_wsi->a.vhost->ss_handle->info.opaque_user_data_offset);

	/*
	 * indicate we are an accepted connection referencing the
	 * server object
	 */

	new_wsi->a.vhost->ss_handle->info.flags |= LWSSSINFLAGS_SERVER;

	if (lws_ss_create(new_wsi->a.context, new_wsi->tsi,
			  &new_wsi->a.vhost->ss_handle->info,
			  *ppv, &h, NULL, NULL)) {
		lwsl_wsi_err(new_wsi, "accept ss creation failed");
		goto fail1;
	}

	/*
	 * We made a fresh accepted SS conn from the server pieces,
	 * now bind the wsi... the problem is, this is the nwsi if it's
	 * h2.
	 */

	h->wsi = new_wsi;
	new_wsi->a.opaque_user_data = h;
	h->info.flags |= LWSSSINFLAGS_ACCEPTED;
	/* indicate wsi should invalidate any ss link to it on close */
	new_wsi->for_ss = 1;

	// lwsl_wsi_notice(new_wsi, "%s: opaq %p, role %s",
	//			     new_wsi->a.opaque_user_data,
	//			     new_wsi->role_ops->name);

	h->policy = new_wsi->a.vhost->ss_handle->policy;

	/* apply requested socket options */
	if (lws_plat_set_socket_options_ip(new_wsi->desc.sockfd,
					   h->policy->priority,
		      (LCCSCF_IP_LOW_LATENCY *
		       !!(h->policy->flags & LWSSSPOLF_ATTR_LOW_LATENCY)) |
		      (LCCSCF_IP_HIGH_THROUGHPUT *
		       !!(h->policy->flags & LWSSSPOLF_ATTR_HIGH_THROUGHPUT)) |
		      (LCCSCF_IP_HIGH_RELIABILITY *
		       !!(h->policy->flags & LWSSSPOLF_ATTR_HIGH_RELIABILITY)) |
		      (LCCSCF_IP_LOW_COST *
		       !!(h->policy->flags & LWSSSPOLF_ATTR_LOW_COST))))
		lwsl_wsi_warn(new_wsi, "unable to set ip options");

	/*
	 * add us to the list of clients that came in from the server
	 */

	lws_pt_lock(pt, __func__);
	lws_dll2_add_tail(&h->cli_list, &new_wsi->a.vhost->ss_handle->src_list);
	lws_pt_unlock(pt);

	/*
	 * Let's give it appropriate state notifications
	 */

	if (lws_ss_event_helper(h, LWSSSCS_CREATING))
		goto fail;
	if (lws_ss_event_helper(h, LWSSSCS_CONNECTING))
		goto fail;

	/* defer CONNECTED until we see if he is upgrading */

//	if (lws_ss_event_helper(h, LWSSSCS_CONNECTED))
//		goto fail;

	// lwsl_notice("%s: accepted ss complete, pcol %s\n", __func__,
	//		new_wsi->a.protocol->name);

	return 0;

fail:
	lws_ss_destroy(&h);
fail1:
	return 1;
}

#endif


static struct lws *
lws_adopt_descriptor_vhost2(struct lws *new_wsi, lws_adoption_type type,
			    lws_sock_file_fd_type fd)
{
	struct lws_context_per_thread *pt =
			&new_wsi->a.context->pt[(int)new_wsi->tsi];
	int n;

	/* enforce that every fd is nonblocking */

	if (type & LWS_ADOPT_SOCKET) {
		if (lws_plat_set_nonblocking(fd.sockfd)) {
			lwsl_wsi_err(new_wsi, "unable to set sockfd %d nonblocking",
				     fd.sockfd);
			goto fail;
		}
	}
#if !defined(WIN32)
	else
		if (lws_plat_set_nonblocking(fd.filefd)) {
			lwsl_wsi_err(new_wsi, "unable to set filefd nonblocking");
			goto fail;
		}
#endif

	new_wsi->desc = fd;

	if (!LWS_SSL_ENABLED(new_wsi->a.vhost) ||
	    !(type & LWS_ADOPT_SOCKET))
		type &= (unsigned int)~LWS_ADOPT_ALLOW_SSL;

	/*
	 * A new connection was accepted. Give the user a chance to
	 * set properties of the newly created wsi. There's no protocol
	 * selected yet so we issue this to the vhosts's default protocol,
	 * itself by default protocols[0]
	 */
	new_wsi->wsistate |= LWSIFR_SERVER;
	n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED;
	if (new_wsi->role_ops->adoption_cb[lwsi_role_server(new_wsi)])
		n = new_wsi->role_ops->adoption_cb[lwsi_role_server(new_wsi)];

	if (new_wsi->a.context->event_loop_ops->sock_accept)
		if (new_wsi->a.context->event_loop_ops->sock_accept(new_wsi))
			goto fail;

	{
        	struct lws *nwsi = lws_get_network_wsi(new_wsi);
		char ta[64];

        	if (nwsi->sa46_peer.sa4.sin_family)
        	        lws_sa46_write_numeric_address(&nwsi->sa46_peer, ta, sizeof(ta));
        	else
                	strncpy(ta, "unknown", sizeof(ta));
		__lws_lc_tag_append(&new_wsi->lc, ta);
	}

#if LWS_MAX_SMP > 1
	/*
	 * Caution: after this point the wsi is live on its service thread
	 * which may be concurrent to this.  We mark the wsi as still undergoing
	 * init in another pt so the assigned pt leaves it alone.
	 */
	new_wsi->undergoing_init_from_other_pt = 1;
#endif

	if (!(type & LWS_ADOPT_ALLOW_SSL)) {
		lws_pt_lock(pt, __func__);
		if (__insert_wsi_socket_into_fds(new_wsi->a.context, new_wsi)) {
			lws_pt_unlock(pt);
			lwsl_wsi_err(new_wsi, "fail inserting socket");
			goto fail;
		}
		lws_pt_unlock(pt);
	}
#if defined(LWS_WITH_SERVER)
	 else {
#if defined(LWS_WITH_LATENCY)
		lws_usec_t _adptssl_start = lws_now_usecs();
#endif
		if (lws_server_socket_service_ssl(new_wsi, fd.sockfd, 0)) {
			lwsl_wsi_info(new_wsi, "fail ssl negotiation");

			goto fail;
		}
#if defined(LWS_WITH_LATENCY)
		{
			unsigned int ms = (unsigned int)((lws_now_usecs() - _adptssl_start) / 1000);
			if (ms > 2)
				lws_latency_note(pt, _adptssl_start, 2000, "adptssl:%dms", ms);
		}
#endif
	}
#endif

	lws_vhost_lock(new_wsi->a.vhost);
	/* he has fds visibility now, remove from vhost orphan list */
	lws_dll2_remove(&new_wsi->vh_awaiting_socket);
	lws_vhost_unlock(new_wsi->a.vhost);

	/*
	 *  by deferring callback to this point, after insertion to fds,
	 * lws_callback_on_writable() can work from the callback
	 */
	if ((new_wsi->a.protocol->callback)(new_wsi, (enum lws_callback_reasons)n, new_wsi->user_space,
					  NULL, 0))
		goto fail;

	/* role may need to do something after all adoption completed */

	lws_role_call_adoption_bind(new_wsi, (int)type | _LWS_ADOPT_FINISH,
				    new_wsi->a.protocol->name);

#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS)
	/*
	 * Did we come from an accepted client connection to a ss server?
	 *
	 * !!! For mux protocols, this will cause an additional inactive ss
	 * representing the nwsi.  Doing that allows us to support both h1
	 * (here) and h2 (at __lws_wsi_server_new())
	 */

	lwsl_wsi_info(new_wsi, "vhost %s", new_wsi->a.vhost->lc.gutag);

	if (lws_adopt_ss_server_accept(new_wsi))
		goto fail;
#endif

#if LWS_MAX_SMP > 1
	/* its actual pt can service it now */

	new_wsi->undergoing_init_from_other_pt = 0;
#endif

	lws_cancel_service_pt(new_wsi);

	return new_wsi;

fail:
	if (type & LWS_ADOPT_SOCKET)
		lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS,
				   "adopt skt fail");

	return NULL;
}


/* if not a socket, it's a raw, non-ssl file descriptor */

struct lws *
lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
			   lws_sock_file_fd_type fd, const char *vh_prot_name,
			   struct lws *parent)
{
	lws_adopt_desc_t info;

	memset(&info, 0, sizeof(info));

	info.vh = vh;
	info.type = type;
	info.fd = fd;
	info.vh_prot_name = vh_prot_name;
	info.parent = parent;

	return lws_adopt_descriptor_vhost_via_info(&info);
}

struct lws *
lws_adopt_descriptor_vhost_via_info(const lws_adopt_desc_t *info)
{
	socklen_t slen = sizeof(lws_sockaddr46);
	struct lws *new_wsi;

#if defined(LWS_WITH_PEER_LIMITS)
	struct lws_peer *peer = NULL;

	if (info->type & LWS_ADOPT_SOCKET) {
		peer = lws_get_or_create_peer(info->vh, info->fd.sockfd);

		if (peer && info->vh->context->ip_limit_wsi &&
		    peer->count_wsi >= info->vh->context->ip_limit_wsi) {
			lwsl_info("Peer reached wsi limit %d\n",
					info->vh->context->ip_limit_wsi);
			if (info->vh->context->pl_notify_cb)
				info->vh->context->pl_notify_cb(
							info->vh->context,
							info->fd.sockfd,
							&peer->sa46);
			compatible_close(info->fd.sockfd);
			return NULL;
		}
	}
#endif

#if defined(LWS_WITH_LATENCY)
	lws_usec_t _adpt1_start = lws_now_usecs();
#endif

	lws_context_lock(info->vh->context, __func__);

	new_wsi = __lws_adopt_descriptor_vhost1(info->vh, info->type,
					      info->vh_prot_name, info->parent,
					      info->opaque, info->fi_wsi_name);

#if defined(LWS_WITH_LATENCY)
	{
		unsigned int ms = (unsigned int)((lws_now_usecs() - _adpt1_start) / 1000);
		if (ms > 2)
			lws_latency_note(info->vh->context->pt, _adpt1_start, 2000, "adpt1:%dms", ms);
	}
#endif

	if (!new_wsi) {
		if (info->type & LWS_ADOPT_SOCKET)
			compatible_close(info->fd.sockfd);
		goto bail;
	}

#if defined(LWS_WITH_LATENCY)
	lws_usec_t _peer_start = lws_now_usecs();
#endif

	if (info->type & LWS_ADOPT_SOCKET &&
	    getpeername(info->fd.sockfd, (struct sockaddr *)&new_wsi->sa46_peer,
								    &slen) < 0)
		lwsl_info("%s: getpeername failed\n", __func__);

#if defined(LWS_WITH_LATENCY)
	{
		unsigned int ms = (unsigned int)((lws_now_usecs() - _peer_start) / 1000);
		if (ms > 2)
			lws_latency_note(info->vh->context->pt, _peer_start, 2000, "peer:%dms", ms);
	}
#endif

#if defined(LWS_WITH_PEER_LIMITS)
	if (peer)
		lws_peer_add_wsi(info->vh->context, peer, new_wsi);
#endif

#if defined(LWS_WITH_LATENCY)
	lws_usec_t _adpt2_start = lws_now_usecs();
#endif

	new_wsi = lws_adopt_descriptor_vhost2(new_wsi, info->type, info->fd);

#if defined(LWS_WITH_LATENCY)
	{
		unsigned int ms = (unsigned int)((lws_now_usecs() - _adpt2_start) / 1000);
		if (ms > 2)
			lws_latency_note(info->vh->context->pt, _adpt2_start, 2000, "adpt2:%dms", ms);
	}
#endif

bail:
	lws_context_unlock(info->vh->context);

	return new_wsi;
}

struct lws *
lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd)
{
	lws_sock_file_fd_type fd;

	fd.sockfd = accept_fd;
	return lws_adopt_descriptor_vhost(vh, LWS_ADOPT_SOCKET |
			LWS_ADOPT_HTTP | LWS_ADOPT_ALLOW_SSL, fd, NULL, NULL);
}

struct lws *
lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd)
{
	return lws_adopt_socket_vhost(context->vhost_list, accept_fd);
}

/* Common read-buffer adoption for lws_adopt_*_readbuf */
static struct lws*
adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len)
{
	struct lws_context_per_thread *pt;
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
	struct lws_pollfd *pfd;
#endif
	int n;

	if (!wsi)
		return NULL;

	if (!readbuf || len == 0)
		return wsi;

	if (wsi->position_in_fds_table == LWS_NO_FDS_POS)
		return wsi;

	pt = &wsi->a.context->pt[(int)wsi->tsi];

	n = lws_buflist_append_segment(&wsi->buflist, (const uint8_t *)readbuf,
				       len);
	if (n < 0)
		goto bail;
	if (n)
		lws_dll2_add_head(&wsi->dll_buflist, &pt->dll_buflist_owner);

	/*
	 * we can't process the initial read data until we can attach an ah.
	 *
	 * if one is available, get it and place the data in his ah rxbuf...
	 * wsi with ah that have pending rxbuf get auto-POLLIN service.
	 *
	 * no autoservice because we didn't get a chance to attach the
	 * readbuf data to wsi or ah yet, and we will do it next if we get
	 * the ah.
	 */
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
	if (wsi->http.ah || !lws_header_table_attach(wsi, 0)) {

		lwsl_notice("%s: calling service on readbuf ah\n", __func__);

		/*
		 * unlike a normal connect, we have the headers already
		 * (or the first part of them anyway).
		 * libuv won't come back and service us without a network
		 * event, so we need to do the header service right here.
		 */
		pfd = &pt->fds[wsi->position_in_fds_table];
		pfd->revents |= LWS_POLLIN;
		lwsl_err("%s: calling service\n", __func__);
		if (lws_service_fd_tsi(wsi->a.context, pfd, wsi->tsi))
			/* service closed us */
			return NULL;

		return wsi;
	}
#endif
	lwsl_err("%s: deferring handling ah\n", __func__);

	return wsi;

bail:
	lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
			   "adopt skt readbuf fail");

	return NULL;
}

#if defined(LWS_WITH_UDP)

/*
 * This is the ASYNC_DNS callback target for udp client, it's analogous to
 * connect3()
 */

#if defined(LWS_WITH_CLIENT)
static struct lws *
lws_create_adopt_udp2(struct lws *wsi, const char *ads,
		      const struct addrinfo *r, int n, void *opaque)
{
	lws_sock_file_fd_type sock;
	int bc = 1, m;

	assert(wsi);

	if (ads && (n < 0 || !r)) {
		/*
		 * DNS lookup failed: there are no usable results.  Fail the
		 * overall connection request.
		 */
		lwsl_notice("%s: bad: n %d, r %p\n", __func__, n, r);

		goto bail;
	}

	if (r) {
		m = lws_sort_dns(wsi, r);
#if defined(LWS_WITH_SYS_ASYNC_DNS)
		lws_async_dns_freeaddrinfo(&r);
#else
		freeaddrinfo((struct addrinfo *)r);
#endif
		if (m)
			goto bail;
	} else {
		/*
		 * If we get here with r == NULL, it's because ads == NULL and
		 * we're using ASYNC_DNS, taking the fast path because no lookup
		 * is needed for INADDR_ANY. Synthesize a result.
		 */
		lws_dns_sort_t *s = lws_zalloc(sizeof(*s), __func__);
		if (!s)
			goto bail;

#if defined(LWS_WITH_IPV6) && defined(LWS_WITH_IPV4)
                if (!lws_check_opt(wsi->a.context->options,
                                   LWS_SERVER_OPTION_DISABLE_IPV6)) {
                        s->dest.sa6.sin6_family = AF_INET6;
                        s->af = AF_INET6;
                } else {
                        s->dest.sa4.sin_family = AF_INET;
                        s->dest.sa4.sin_addr.s_addr = INADDR_ANY;
                        s->af = AF_INET;
                }
#elif defined(LWS_WITH_IPV6)
                {
                        s->dest.sa6.sin6_family = AF_INET6;
                        s->af = AF_INET6;
                }
#elif defined(LWS_WITH_IPV4)
                {
                        s->dest.sa4.sin_family = AF_INET;
                        s->dest.sa4.sin_addr.s_addr = INADDR_ANY;
                        s->af = AF_INET;
                }
#endif
		lws_dll2_add_tail(&s->list, &wsi->dns_sorted_list);
	}

	while (lws_dll2_get_head(&wsi->dns_sorted_list)) {
		lws_dns_sort_t *s = lws_container_of(
				lws_dll2_get_head(&wsi->dns_sorted_list),
				lws_dns_sort_t, list);

		/*
		 * Remove it from the head, but don't free it yet... we are
		 * taking responsibility to free it
		 */
		lws_dll2_remove(&s->list);

		/*
		 * We have done the dns lookup, identify the result we want
		 * if any, and then complete the adoption by binding wsi to
		 * socket opened on it.
		 *
		 * Ignore the weak assumptions about protocol driven by port
		 * number and force to DGRAM / UDP since that's what this
		 * function is for.
		 */

#if !defined(__linux__)
		sock.sockfd = socket(s->dest.sa4.sin_family,
				     SOCK_DGRAM, IPPROTO_UDP);
#else
		/* PF_PACKET is linux-only */
		sock.sockfd = socket(wsi->pf_packet ? PF_PACKET :
						s->dest.sa4.sin_family,
				     SOCK_DGRAM, wsi->pf_packet ?
					htons(0x800) : IPPROTO_UDP);
#endif
		if (sock.sockfd == LWS_SOCK_INVALID)
			goto resume;

		{
			int opt = 4 * 1024 * 1024;
			if (setsockopt(sock.sockfd, SOL_SOCKET, SO_RCVBUF, (const char *)&opt, sizeof(opt)) < 0) {
				lwsl_wsi_warn(wsi, "Failed to set SO_RCVBUF");
			}
			if (setsockopt(sock.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&opt, sizeof(opt)) < 0) {
				lwsl_wsi_warn(wsi, "Failed to set SO_SNDBUF");
			}
		}

		if (lws_plat_apply_FD_CLOEXEC((int)sock.sockfd) ||
		    lws_plat_set_nonblocking(sock.sockfd)) {
			compatible_close(sock.sockfd);
			sock.sockfd = LWS_SOCK_INVALID;
			goto resume;
		}

#if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY)
		/*
		 * For IPv6 UDP sockets we own the IPV6_V6ONLY state rather
		 * than inheriting the OS default (which is 0 = dual-stack on
		 * Linux/macOS but 1 = v6-only on Windows):
		 *
		 *   - by default (!MODIFY) we force v6-only, matching the TCP
		 *     listen path.  Callers that want both families bind two
		 *     sockets (:: and 0.0.0.0) and rely on this -- e.g. dht,
		 *     auth_dns, extip.
		 *   - MODIFY && !VALUE explicitly requests dual-stack, so a
		 *     lone :: listener also receives IPv4-mapped packets
		 *     (the QUIC adoption path).  Force V6ONLY=0 here, since
		 *     the OS default cannot be relied on (Windows = 1).
		 *   - MODIFY | VALUE explicitly requests v6-only.
		 */
		if (s->dest.sa4.sin_family == AF_INET6 &&
		    !lws_sa46_is_ipv4_mapped(&s->dest)) {
			int opt = !(wsi->a.vhost->options &
				    LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) ||
				  !!(wsi->a.vhost->options &
				     LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
			if (setsockopt(sock.sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
				       (const void *)&opt, sizeof(opt)) < 0) {
				lwsl_vhost_notice(wsi->a.vhost, "set IPV6_V6ONLY fail");
			}
		}
#endif

		/* ipv6 udp!!! */

#if defined(LWS_WITH_IPV4)
		if (s->dest.sa4.sin_family == AF_INET)
			s->dest.sa4.sin_port = htons(wsi->c_port);
#if defined(LWS_WITH_IPV6)
		else
#endif
#endif
#if defined(LWS_WITH_IPV6)
			s->dest.sa6.sin6_port = htons(wsi->c_port);
#endif

		if (setsockopt(sock.sockfd, SOL_SOCKET, SO_REUSEADDR,
			       (const char *)&bc, sizeof(bc)) < 0)
			lwsl_err("%s: failed to set reuse\n", __func__);

		if (wsi->do_broadcast &&
		    setsockopt(sock.sockfd, SOL_SOCKET, SO_BROADCAST,
			       (const char *)&bc, sizeof(bc)) < 0)
			lwsl_err("%s: failed to set broadcast\n", __func__);

		/* Bind the udp socket to a particular network interface */

		if (opaque &&
		    lws_plat_BINDTODEVICE(sock.sockfd, (const char *)opaque)) {
			compatible_close(sock.sockfd);
			sock.sockfd = LWS_SOCK_INVALID;
			goto resume;
		}

		if (wsi->do_bind &&
		    bind(sock.sockfd, sa46_sockaddr(&s->dest),
#if defined(_WIN32)
			 (int)
#endif
			 sa46_socklen(&s->dest)
		) == -1) {
			lwsl_err("%s: bind failed\n", __func__);
			compatible_close(sock.sockfd);
			sock.sockfd = LWS_SOCK_INVALID;
			goto resume;
		}

		if (!wsi->do_bind && !wsi->pf_packet) {
#if !defined(__APPLE__)
			if (connect(sock.sockfd, sa46_sockaddr(&s->dest),
				    sa46_socklen(&s->dest)) == -1 &&
			    errno != EADDRNOTAVAIL /* openbsd */ ) {
				lwsl_err("%s: conn fd %d fam %d %s:%u failed "
					 "errno %d\n", __func__, sock.sockfd,
					 s->dest.sa4.sin_family,
					 ads ? ads : "null", wsi->c_port,
					 LWS_ERRNO);
				compatible_close(sock.sockfd);
				goto resume;
			}
#endif
		}

		if (wsi->udp)
			wsi->udp->sa46 = s->dest;
		wsi->sa46_peer = s->dest;

		/* we connected: complete the udp socket adoption flow */

#if defined(LWS_WITH_SYS_ASYNC_DNS)
		{
			lws_async_dns_server_t *asds =
					__lws_async_dns_server_find_wsi(
						&wsi->a.context->async_dns, wsi);
			if (asds)
				asds->dns_server_connected = 1;
		}
#endif

		lws_free(s);
		lws_addrinfo_clean(wsi);
		return lws_adopt_descriptor_vhost2(wsi,
						LWS_ADOPT_RAW_SOCKET_UDP, sock);

resume:
		lws_free(s);
	}

	lwsl_err("%s: unable to create INET socket %d\n", __func__, LWS_ERRNO);
	lws_addrinfo_clean(wsi);

#if defined(LWS_WITH_SYS_ASYNC_DNS)
	{
		lws_async_dns_server_t *asds = __lws_async_dns_server_find_wsi(
					&wsi->a.context->async_dns, wsi);
		if (asds)
			lws_async_dns_drop_server(asds);
	}
#endif

bail:
	lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "adopt udp2 fail");

	return NULL;
}
#else
static struct lws *
lws_create_adopt_udp2(struct lws *wsi, const char *ads,
		      const struct addrinfo *r, int n, void *opaque)
{
	lws_sock_file_fd_type sock;
	int bc = 1;
	lws_sockaddr46 dest;

	assert(wsi);

	if (ads && (n < 0 || !r)) {
		lwsl_notice("%s: bad: n %d, r %p\n", __func__, n, r);
		goto bail;
	}

	memset(&dest, 0, sizeof(dest));

	if (r) {
#if defined(LWS_WITH_IPV4)
		if (r->ai_family == AF_INET) {
			dest.sa4.sin_family = AF_INET;
			memcpy(&dest.sa4.sin_addr, &((struct sockaddr_in *)r->ai_addr)->sin_addr, sizeof(struct in_addr));
		}
#if defined(LWS_WITH_IPV6)
		else
#endif
#endif
#if defined(LWS_WITH_IPV6)
		if (r->ai_family == AF_INET6) {
			dest.sa6.sin6_family = AF_INET6;
			memcpy(&dest.sa6.sin6_addr, &((struct sockaddr_in6 *)r->ai_addr)->sin6_addr, sizeof(struct in6_addr));
		}
#endif
	} else {
#if defined(LWS_WITH_IPV6)
		if (!lws_check_opt(wsi->a.context->options,
				   LWS_SERVER_OPTION_DISABLE_IPV6)) {
			dest.sa6.sin6_family = AF_INET6;
		} else
#endif
#if defined(LWS_WITH_IPV4)
		{
			dest.sa4.sin_family = AF_INET;
			dest.sa4.sin_addr.s_addr = INADDR_ANY;
		}
#endif
	}

#if !defined(__linux__)
	sock.sockfd = socket(dest.sa4.sin_family, SOCK_DGRAM, IPPROTO_UDP);
#else
	sock.sockfd = socket(wsi->pf_packet ? PF_PACKET : dest.sa4.sin_family,
			     SOCK_DGRAM, wsi->pf_packet ? htons(0x800) : IPPROTO_UDP);
#endif
	if (sock.sockfd == LWS_SOCK_INVALID)
		goto bail;

	{
		int opt = 4 * 1024 * 1024;
		if (setsockopt(sock.sockfd, SOL_SOCKET, SO_RCVBUF, (const char *)&opt, sizeof(opt)) < 0) {
			lwsl_wsi_warn(wsi, "Failed to set SO_RCVBUF");
		}
		if (setsockopt(sock.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&opt, sizeof(opt)) < 0) {
			lwsl_wsi_warn(wsi, "Failed to set SO_SNDBUF");
		}
	}

	if (lws_plat_apply_FD_CLOEXEC((int)sock.sockfd) ||
	    lws_plat_set_nonblocking(sock.sockfd)) {
		compatible_close(sock.sockfd);
		sock.sockfd = LWS_SOCK_INVALID;
		goto resume;
	}

#if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY)
	if (dest.sa4.sin_family == AF_INET6 &&
	    !lws_sa46_is_ipv4_mapped(&dest)) {
		int opt = !(wsi->a.vhost->options &
			    LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) ||
			  !!(wsi->a.vhost->options &
			     LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
		if (setsockopt(sock.sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
			       (const void *)&opt, sizeof(opt)) < 0) {
			lwsl_vhost_notice(wsi->a.vhost, "set IPV6_V6ONLY fail");
		}
	}
#endif

#if defined(LWS_WITH_IPV4)
	if (dest.sa4.sin_family == AF_INET)
		dest.sa4.sin_port = htons(wsi->c_port);
#if defined(LWS_WITH_IPV6)
	else
#endif
#endif
#if defined(LWS_WITH_IPV6)
		dest.sa6.sin6_port = htons(wsi->c_port);
#endif

	if (setsockopt(sock.sockfd, SOL_SOCKET, SO_REUSEADDR,
		       (const char *)&bc, sizeof(bc)) < 0)
		lwsl_err("%s: failed to set reuse\n", __func__);

	if (wsi->do_broadcast &&
	    setsockopt(sock.sockfd, SOL_SOCKET, SO_BROADCAST,
		       (const char *)&bc, sizeof(bc)) < 0)
		lwsl_err("%s: failed to set broadcast\n", __func__);

	if (opaque && lws_plat_BINDTODEVICE(sock.sockfd, (const char *)opaque))
		goto resume;

	if (wsi->do_bind &&
	    bind(sock.sockfd, sa46_sockaddr(&dest),
#if defined(_WIN32)
		 (int)
#endif
		 sa46_socklen(&dest)) == -1) {
		lwsl_err("%s: bind failed\n", __func__);
		goto resume;
	}

	if (!wsi->do_bind && !wsi->pf_packet) {
#if !defined(__APPLE__)
		if (connect(sock.sockfd, sa46_sockaddr(&dest),
			    sa46_socklen(&dest)) == -1 &&
		    errno != EADDRNOTAVAIL) {
			lwsl_err("%s: conn failed\n", __func__);
			goto resume;
		}
#endif
	}

	if (wsi->udp)
		wsi->udp->sa46 = dest;
	wsi->sa46_peer = dest;

#if defined(LWS_WITH_SYS_ASYNC_DNS)
	{
		lws_async_dns_server_t *asds =
				__lws_async_dns_server_find_wsi(
					&wsi->a.context->async_dns, wsi);
		if (asds)
			asds->dns_server_connected = 1;
	}
#endif

	return lws_adopt_descriptor_vhost2(wsi, LWS_ADOPT_RAW_SOCKET_UDP, sock);

resume:
	compatible_close(sock.sockfd);
bail:
	lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "adopt udp2 fail");

	return NULL;
}
#endif

struct lws *
lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port,
		     int flags, const char *protocol_name, const char *ifname,
		     struct lws *parent_wsi, void *opaque,
		     const lws_retry_bo_t *retry_policy, const char *fi_wsi_name)
{
#if !defined(LWS_PLAT_OPTEE)
	struct lws *wsi;
	int n;

	lwsl_info("%s: %s:%u\n", __func__, ads ? ads : "null", port);

	/* create the logical wsi without any valid fd */

	lws_context_lock(vhost->context, __func__);

	wsi = __lws_adopt_descriptor_vhost1(vhost, LWS_ADOPT_SOCKET |
						 LWS_ADOPT_RAW_SOCKET_UDP,
					  protocol_name, parent_wsi, opaque,
					  fi_wsi_name);

	lws_context_unlock(vhost->context);
	if (!wsi) {
		lwsl_err("%s: udp wsi creation failed\n", __func__);
		goto bail;
	}

	// lwsl_notice("%s: role %s\n", __func__, wsi->role_ops->name);

	wsi->do_bind = !!(flags & LWS_CAUDP_BIND);
	wsi->do_broadcast = !!(flags & LWS_CAUDP_BROADCAST);
	wsi->pf_packet = !!(flags & LWS_CAUDP_PF_PACKET);
	wsi->c_port = (uint16_t)(unsigned int)port;
	if (retry_policy)
		wsi->retry_policy = retry_policy;
	else
		wsi->retry_policy = vhost->retry_policy;
	/*
	 * lws_sort_dns() filters out IPv6 results if wsi->ipv6 == 0, pick up
	 * the vhost / context ipv6 policy
	 */
	wsi->ipv6 = !!LWS_IPV6_ENABLED(vhost);

#if !defined(LWS_WITH_SYS_ASYNC_DNS)
	{
		struct addrinfo *r, h;
		char buf[16];

		memset(&h, 0, sizeof(h));
		h.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
		h.ai_socktype = SOCK_DGRAM;
		h.ai_protocol = IPPROTO_UDP;
#if defined(AI_PASSIVE)
		h.ai_flags = AI_PASSIVE;
#endif
#ifdef AI_ADDRCONFIG
		h.ai_flags |= AI_ADDRCONFIG;
#endif

		/* if the dns lookup is synchronous, do the whole thing now */
		lws_snprintf(buf, sizeof(buf), "%u", port);
		n = getaddrinfo(ads, buf, &h, &r);
		if (n) {
#if !defined(LWS_PLAT_FREERTOS)
			lwsl_cx_info(vhost->context, "getaddrinfo error: %d", n);
#else
#if (_LWS_ENABLED_LOGS & LLL_INFO)
			char t16[16];
			lwsl_cx_info(vhost->context, "getaddrinfo error: %s",
				lws_errno_describe(LWS_ERRNO, t16, sizeof(t16)));
#endif
#endif
			//freeaddrinfo(r);
			goto bail1;
		}
		/*
		 * With synchronous dns, complete it immediately after the
		 * blocking dns lookup finished... free r when connect either
		 * completed or failed
		 */
		wsi = lws_create_adopt_udp2(wsi, ads, r, 0, NULL);

		return wsi;
	}
#else
	if (ads) {
		/*
		 * with async dns, use the wsi as the point about which to do
		 * the dns lookup and have it call the second part when it's
		 * done.
		 *
		 * Keep a refcount on the results and free it when we connected
		 * or definitively failed.
		 *
		 * Notice wsi has no socket at this point (we don't know what
		 * kind to ask for until we get the dns back).  But it is bound
		 * to a vhost and can be cleaned up from that at vhost destroy.
		 */
		n = lws_async_dns_query(vhost->context, 0, ads,
					LWS_ADNS_RECORD_A,
					lws_create_adopt_udp2, wsi,
					(void *)ifname, NULL);
		// lwsl_notice("%s: dns query returned %d\n", __func__, n);
		if (n < LADNS_RET_FOUND) {
			/*
			 * Any terminal failure retcode (< 0), including
			 * LADNS_RET_FAILED_WSI_CLOSED which is what we get when
			 * the synchronous numeric-address path ran the adopt
			 * callback and it failed (eg, the resolved address had
			 * no usable route, so lws_sort_dns() produced no results
			 * and lws_create_adopt_udp2() closed the wsi).  The
			 * callback has already closed the wsi in that case; we
			 * must NOT return the now-dangling wsi pointer.
			 */
			lwsl_err("%s: async dns failed (%d)\n", __func__, n);
			wsi = NULL;
			goto bail;
		}
	} else {
		lwsl_debug("%s: udp adopt has no ads\n", __func__);
		wsi = lws_create_adopt_udp2(wsi, ads, NULL, 0, (void *)ifname);
	}

	/* dns lookup is happening asynchronously */

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

	return wsi;
#endif
#if !defined(LWS_WITH_SYS_ASYNC_DNS)
bail1:
	lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "adopt udp2 fail");
	wsi = NULL;
#endif
bail:
	return wsi;
#else
	return NULL;
#endif
}
#endif

struct lws *
lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd,
			 const char *readbuf, size_t len)
{
        return adopt_socket_readbuf(lws_adopt_socket(context, accept_fd),
				    readbuf, len);
}

struct lws *
lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost,
			       lws_sockfd_type accept_fd,
			       const char *readbuf, size_t len)
{
        return adopt_socket_readbuf(lws_adopt_socket_vhost(vhost, accept_fd),
				    readbuf, len);
}
