/*
 * sai-builder - resproxy
 *
 * Copyright (C) 2019 - 2021 Andy Green <andy@warmcat.com>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation:
 *  version 2.1 of the License.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *  MA  02110-1301  USA
 *
 * We create a listening UDS socket for each server that we accept jobs from.
 * sai-resource can later connect to it (the path is passed in by env var)
 * and do blocking requests for resource leases, proxied by us to the server
 * we're already connected to.
 *
 * This code handles the accepted UDS connections that connected to one of these
 * resource proxy UDS listeners, and performs the proxy action to the associated
 * server using the existing link the builder already has to it.
 *
 * The client sends us a well-formed JSON we just forward to the server.  We
 * know there's a cookie in there we stash a copy of in the pss, otherwise we
 * don't understand the JSON.
 *
 * The server responds with a JSON (or the client times out waiting) that again
 * we don't understand except the cookie, to find out which pss it belongs to.
 *
 * If we can't match the cookie, or the client hangs up, we send a JSON with
 * just the schema and the cookie to explicitly relinquish the remaining lease,
 * that stops us always waiting out the whole lease period when we just used
 * a small part of it.
 */

#include <libwebsockets.h>
#include <string.h>
#include <signal.h>
#include "b-private.h"

struct rppss {
	struct lws		*wsi;
	lws_dll2_t		list; /* builder's list of resource pss' */

	char			cookie[65];

	char			*response;
	unsigned int		response_len;

	uint8_t			accepted:1;
};

static struct rppss *
resproxy_find_by_cookie(struct sai_plat_server *spm, const char *c, size_t clen)
{
	lws_start_foreach_dll(lws_dll2_t *, p, spm->resource_pss_list.head) {
		struct rppss *pss = lws_container_of(p, struct rppss, list);

		if (!strncmp(pss->cookie, c, clen))
			return pss;

	} lws_end_foreach_dll(p);

	return NULL;
}

static int
saib_queue_yield_message(struct sai_plat_server *spm, const char *c, size_t len)
{
	char msg[LWS_PRE + 256];
	size_t jl;

	/*
	 * We just send the cookie to relinquish the leased resources
	 */
	jl = (size_t)lws_snprintf(msg + LWS_PRE, sizeof(msg) - LWS_PRE,
			      "{\"schema\":\"com-warmcat-sai-resource\","
			      "\"cookie\":\"%.*s\"}", (int)len, c);

	return saib_srv_queue_tx(spm->ss, msg + LWS_PRE, jl, LWSSS_FLAG_SOM | LWSSS_FLAG_EOM);
}

int
saib_handle_resource_result(struct sai_plat_server *spm, const char *in, size_t len)
{
	struct rppss *pss;
	const char *p;
	size_t al;

	p = lws_json_simple_find((const char *)in, len, "\"cookie\":", &al);
	if (!p) {
		/* seems malformed */
		lwsl_warn("%s: server sent JSON without a cookie\n", __func__);
		return -1;
	}

	pss = resproxy_find_by_cookie(spm, p, al);
	if (!pss) {
		// lwsl_warn("%s: the requestor left before the response\n",
		//		__func__);
		/*
		 * Explicit yield, in case the acceptance raced the client
		 * closing... if it was telling us we can't have it, the server
		 * will ignore the yield since no lease with this cookie on
		 * record there
		 */
		saib_queue_yield_message(spm, p, al);

		return 1; /* the requestor has gone away */
	}

	/*
	 * We want to proxy back the server's response to the client that
	 * we identified owns the cookie, we don't need to understand the
	 * response any further ourselves
	 */

	pss->response_len = (unsigned int)len;
	pss->response = malloc(len + LWS_PRE);
	if (!pss->response)
		return 0;

	memcpy(pss->response + LWS_PRE, in, len);

	lwsl_notice("%s: queuing server response %.*s\n", __func__, (int)len, in);

	/*
	 * Mark us as having been handled, so we will have to yield it
	 * when we close.  If this gets lost somewhere the lease period expiring
	 * will autorecover it at the server side, so no worries...
	 */
	pss->accepted = 1;
	lws_callback_on_writable(pss->wsi);

	return 0; /* we will try to pass it on */
}

static int
callback_resproxy(struct lws *wsi, enum lws_callback_reasons reason,
		  void *user, void *in, size_t len)
{
	struct sai_plat_server *spm = lws_vhost_user(lws_get_vhost(wsi));
	struct rppss *pss = (struct rppss *)user;
	const char *p;
	size_t al;

	switch (reason) {

	case LWS_CALLBACK_RAW_CLOSE:

		lwsl_notice("%s: CLOSE\n", __func__);
		/*
		 * We can close at any time regardless of what the server's
		 * doing... delist and destroy our request
		 */

		/* stop tracking the pss for this, we're closing */
		lws_dll2_remove(&pss->list);

		if (pss->response) {
			free(pss->response);
			pss->response = NULL;
		}

		if (pss->accepted)
			saib_queue_yield_message(spm, pss->cookie,
						 strlen(pss->cookie));
		break;

	case LWS_CALLBACK_RAW_RX:

		if (pss->wsi)
			/* only request once */
			return 0;

		pss->wsi = wsi;

		/*
		 * We get sent something like this
		 *
		 * {
		 *  "schema":"com-warmcat-sai-resource",
		 *  "resname":"warmcat_conns",
		 *  "cookie":"xxx",
		 *  "amount":8,
		 *  "lease":20
		 * }
		 *
		 * We keep a copy of the cookie in the pss, and add the pss to
		 * an owner in the server until it is closed.
		 */

		p = lws_json_simple_find((const char *)in, len,
					 "\"cookie\":", &al);
		if (!p)
			return -1;

		lws_strnncpy(pss->cookie, p, al, sizeof(pss->cookie));
		lws_dll2_add_tail(&pss->list, &spm->resource_pss_list);

		{
			int n;
			char *buf = malloc(LWS_PRE + len);
			if (!buf)
				return -1;
			memcpy(buf + LWS_PRE, in, len);
			n = saib_srv_queue_tx(spm->ss, buf + LWS_PRE, len, LWSSS_FLAG_SOM | LWSSS_FLAG_EOM);
			free(buf);
			return n;
		}

	case LWS_CALLBACK_RAW_WRITEABLE:
		if (pss->response) {
			lwsl_notice("%s: WRITABLE issue response %.*s\n",
				    __func__, (int)pss->response_len,
				    pss->response + LWS_PRE);
			if (lws_write(wsi, (uint8_t *)pss->response + LWS_PRE,
				      (unsigned int)pss->response_len,
				      LWS_WRITE_RAW) != (int)pss->response_len)
				return -1;

			free(pss->response);
			pss->response = NULL;

			/*
			 * We stay up until the client goes away, triggering
			 * explicitly giving up the lease
			 */

			break;
		}
		break;

	default:
		break;
	}

	return 0;
}

const struct lws_protocols protocol_resproxy = {
	.name			= "protocol-resproxy",
	.callback		= callback_resproxy,
	.per_session_data_size	= sizeof(struct rppss),
	.rx_buffer_size		= 2048,
	.tx_packet_size		= 2048,
};