/*-
 *   BSD LICENSE
 *
 *   Copyright (c) Intel Corporation.
 *   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 name of Intel Corporation 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 THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS 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 "jsonrpc_internal.h"

struct spdk_jsonrpc_server *
spdk_jsonrpc_server_listen(struct sockaddr *listen_addr, socklen_t addrlen,
			   spdk_jsonrpc_handle_request_fn handle_request)
{
	struct spdk_jsonrpc_server *server;
	int rc, val;

	server = calloc(1, sizeof(struct spdk_jsonrpc_server));
	if (server == NULL) {
		return NULL;
	}

	server->handle_request = handle_request;

	server->sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (server->sockfd < 0) {
		SPDK_ERRLOG("socket() failed\n");
		free(server);
		return NULL;
	}

	val = 1;
	setsockopt(server->sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
	setsockopt(server->sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val));

	val = 1;
	rc = ioctl(server->sockfd, FIONBIO, &val);
	if (rc != 0) {
		SPDK_ERRLOG("ioctl(FIONBIO) failed\n");
		close(server->sockfd);
		free(server);
		return NULL;
	}

	rc = bind(server->sockfd, listen_addr, addrlen);
	if (rc != 0) {
		SPDK_ERRLOG("could not bind JSON-RPC server: %s\n", strerror(errno));
		close(server->sockfd);
		free(server);
		return NULL;
	}

	rc = listen(server->sockfd, 512);
	if (rc != 0) {
		SPDK_ERRLOG("listen() failed, errno = %d\n", errno);
		close(server->sockfd);
		free(server);
		return NULL;
	}

	/* Put listen socket in pollfds[0] */
	server->pollfds[0].fd = server->sockfd;
	server->pollfds[0].events = POLLIN;

	return server;
}

void
spdk_jsonrpc_server_shutdown(struct spdk_jsonrpc_server *server)
{
	int i;

	close(server->sockfd);

	for (i = 0; i < server->num_conns; i++) {
		close(server->conns[i].sockfd);
	}

	free(server);
}

static void
spdk_jsonrpc_server_conn_remove(struct spdk_jsonrpc_server_conn *conn)
{
	struct spdk_jsonrpc_server *server = conn->server;
	int conn_idx = conn - server->conns;

	close(conn->sockfd);

	/* Swap conn with the last entry in conns */
	server->conns[conn_idx] = server->conns[server->num_conns - 1];
	server->num_conns--;
}

static int
spdk_jsonrpc_server_accept(struct spdk_jsonrpc_server *server)
{
	struct spdk_jsonrpc_server_conn *conn;
	struct pollfd *pfd;
	int rc, conn_idx, nonblock;

	rc = accept(server->sockfd, NULL, NULL);
	if (rc >= 0) {
		assert(server->num_conns < SPDK_JSONRPC_MAX_CONNS);
		conn_idx = server->num_conns;
		conn = &server->conns[conn_idx];
		conn->server = server;
		conn->sockfd = rc;
		conn->recv_len = 0;
		conn->send_len = 0;
		conn->json_writer = 0;

		nonblock = 1;
		rc = ioctl(conn->sockfd, FIONBIO, &nonblock);
		if (rc != 0) {
			SPDK_ERRLOG("ioctl(FIONBIO) failed\n");
			close(conn->sockfd);
			return -1;
		}

		/* Add connection to pollfds */
		pfd = &server->pollfds[conn_idx + 1];
		pfd->fd = conn->sockfd;
		pfd->events = POLLIN | POLLOUT;

		server->num_conns++;

		return 0;
	}

	if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
		return 0;
	}

	return -1;
}

int
spdk_jsonrpc_server_write_cb(void *cb_ctx, const void *data, size_t size)
{
	struct spdk_jsonrpc_server_conn *conn = cb_ctx;

	if (SPDK_JSONRPC_SEND_BUF_SIZE - conn->send_len < size) {
		SPDK_ERRLOG("Not enough space in send buf\n");
		return -1;
	}

	memcpy(conn->send_buf + conn->send_len, data, size);
	conn->send_len += size;

	return 0;
}

void
spdk_jsonrpc_server_handle_request(struct spdk_jsonrpc_server_conn *conn,
				   const struct spdk_json_val *method, const struct spdk_json_val *params,
				   const struct spdk_json_val *id)
{
	conn->server->handle_request(conn, method, params, id);
}

void
spdk_jsonrpc_server_handle_error(struct spdk_jsonrpc_server_conn *conn, int error,
				 const struct spdk_json_val *method, const struct spdk_json_val *params,
				 const struct spdk_json_val *id)
{
	const char *msg;

	switch (error) {
	case SPDK_JSONRPC_ERROR_PARSE_ERROR:
		msg = "Parse error";
		break;

	case SPDK_JSONRPC_ERROR_INVALID_REQUEST:
		msg = "Invalid request";
		break;

	case SPDK_JSONRPC_ERROR_METHOD_NOT_FOUND:
		msg = "Method not found";
		break;

	case SPDK_JSONRPC_ERROR_INVALID_PARAMS:
		msg = "Invalid parameters";
		break;

	case SPDK_JSONRPC_ERROR_INTERNAL_ERROR:
		msg = "Internal error";
		break;

	default:
		msg = "Error";
		break;
	}

	spdk_jsonrpc_send_error_response(conn, id, error, msg);
}

static int
spdk_jsonrpc_server_conn_recv(struct spdk_jsonrpc_server_conn *conn)
{
	ssize_t rc;
	size_t recv_avail = SPDK_JSONRPC_RECV_BUF_SIZE - conn->recv_len;

	rc = recv(conn->sockfd, conn->recv_buf + conn->recv_len, recv_avail, 0);
	if (rc == -1) {
		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
			return 0;
		}

		SPDK_TRACELOG(SPDK_TRACE_RPC, "recv() failed: %s\n", strerror(errno));
		return -1;
	}

	if (rc == 0) {
		SPDK_TRACELOG(SPDK_TRACE_RPC, "remote closed connection\n");
		return -1;
	}

	conn->recv_len += rc;

	rc = spdk_jsonrpc_parse_request(conn, conn->recv_buf, conn->recv_len);
	if (rc < 0) {
		SPDK_ERRLOG("jsonrpc parse request failed\n");
		return -1;
	}

	if (rc > 0) {
		/*
		 * Successfully parsed a request - move any data past the end of the
		 * parsed request down to the beginning.
		 */
		assert((size_t)rc <= conn->recv_len);
		memmove(conn->recv_buf, conn->recv_buf + rc, conn->recv_len - rc);
		conn->recv_len -= rc;
	}

	return 0;
}

static int
spdk_jsonrpc_server_conn_send(struct spdk_jsonrpc_server_conn *conn)
{
	ssize_t rc;

	rc = send(conn->sockfd, conn->send_buf, conn->send_len, 0);
	if (rc < 0) {
		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) {
			return 0;
		}

		SPDK_TRACELOG(SPDK_TRACE_RPC, "send() failed: %s\n", strerror(errno));
		return -1;
	}

	if (rc == 0) {
		SPDK_TRACELOG(SPDK_TRACE_RPC, "remote closed connection\n");
		return -1;
	}

	conn->send_len -= rc;

	return 0;
}

int
spdk_jsonrpc_server_poll(struct spdk_jsonrpc_server *server)
{
	int rc, i;
	struct pollfd *pfd;
	struct spdk_jsonrpc_server_conn *conn;

	rc = poll(server->pollfds, server->num_conns + 1, 0);

	if (rc < 0) {
		if (errno == EINTR) {
			return 0;
		}

		SPDK_ERRLOG("jsonrpc poll() failed\n");
		return -1;
	}

	if (rc == 0) {
		/* No sockets are ready */
		return 0;
	}

	/* Check listen socket */
	if (server->num_conns < SPDK_JSONRPC_MAX_CONNS) {
		pfd = &server->pollfds[0];
		if (pfd->revents) {
			spdk_jsonrpc_server_accept(server);
		}
		pfd->revents = 0;
	}

	for (i = 0; i < server->num_conns; i++) {
		pfd = &server->pollfds[i + 1];
		conn = &server->conns[i];
		if (conn->send_len) {
			/*
			 * If there is any data to send, keep sending it until the send buffer
			 *  is empty.  Each response should be allowed the full send buffer, so
			 *  don't accept any new requests until the previous response is sent out.
			 */
			if (pfd->revents & POLLOUT) {
				rc = spdk_jsonrpc_server_conn_send(conn);
				if (rc != 0) {
					SPDK_TRACELOG(SPDK_TRACE_RPC, "closing conn due to send failure\n");
					spdk_jsonrpc_server_conn_remove(conn);
				}
			}
		} else {
			/*
			 * No data to send - we can receive a new request.
			 */
			if (pfd->revents & POLLIN) {
				rc = spdk_jsonrpc_server_conn_recv(conn);
				if (rc != 0) {
					SPDK_TRACELOG(SPDK_TRACE_RPC, "closing conn due to recv failure\n");
					spdk_jsonrpc_server_conn_remove(conn);
				}
			}
		}
		pfd->revents = 0;
	}

	return 0;
}