/*
 *  Unix SMB/CIFS implementation.
 *
 *  Simple Named Pipe Echo service
 *
 *  Copyright (C) Stefan Metzmacher 2026
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

#include "includes.h"
#include "lib/global_contexts.h"
#include "lib/tsocket/tsocket.h"
#include "lib/util/tevent_unix.h"
#include "rpc_server/rpc_worker_nps.h"

static const struct nps_interface npsd_echo_msg8_iface = {
	.pipe_name		= "nps_echo_msg8",
	.file_type		= FILE_TYPE_MESSAGE_MODE_PIPE,
	.device_state		= 0xff | 0x0400 | 0x0100,
	.allocation_size	= UINT8_MAX,
};

static const struct nps_interface npsd_echo_msg16_iface = {
	.pipe_name		= "nps_echo_msg16",
	.file_type		= FILE_TYPE_MESSAGE_MODE_PIPE,
	.device_state		= 0xff | 0x0400 | 0x0100,
	.allocation_size	= UINT16_MAX,
};

static size_t npsd_echo_get_interfaces(const struct nps_interface ***pifaces,
				       void *private_data)
{
	static const struct nps_interface *ifaces[] = {
		&npsd_echo_msg8_iface,
		&npsd_echo_msg16_iface,
	};

	*pifaces = ifaces;
	return ARRAY_SIZE(ifaces);
}

static NTSTATUS npsd_echo_setup_servers(struct rpc_worker *worker,
					void *private_data)
{
	DBG_NOTICE("Started (%d)\n", getpid());
	return NT_STATUS_OK;
}

struct npsd_echo_connection {
	struct tevent_context *ev;
	struct rpc_worker_connection *worker_conn;
	struct auth_session_info *session_info;
	struct tstream_context *tstream;
	struct tsocket_address *remote_client_addr;
	struct tsocket_address *local_server_addr;
	size_t max_size;

	struct tevent_req *error_subreq;
	struct tevent_req *in_subreq;
	DATA_BLOB blob;
	struct iovec out_vec;
	struct tevent_req *out_subreq;
};

static int npsd_echo_connection_destructor(
		struct npsd_echo_connection *echo_conn)
{
	/*
	 * First cleanup all subreqs
	 */
	TALLOC_FREE(echo_conn->error_subreq);
	TALLOC_FREE(echo_conn->in_subreq);
	TALLOC_FREE(echo_conn->out_subreq);

	/*
	 * Disconnect the connection
	 */
	TALLOC_FREE(echo_conn->tstream);

	/*
	 * This lets the rpc_worker_connection_destructor
	 * to call rpc_worker_report_status()...
	 */
	TALLOC_FREE(echo_conn->worker_conn);
	return 0;
}

static void npsd_echo_connection_error(struct tevent_req *subreq);
static int npsd_echo_connection_next_vector(struct tstream_context *stream,
					     void *private_data,
					     TALLOC_CTX *mem_ctx,
					     struct iovec **_vector,
					     size_t *_count);
static void npsd_echo_connection_in_done(struct tevent_req *subreq);
static void npsd_echo_connection_out_done(struct tevent_req *subreq);

static NTSTATUS npsd_echo_accept_client(
		struct rpc_worker *worker,
		void *private_data,
		struct rpc_worker_connection *worker_conn,
		struct auth_session_info **transport_session_info,
		const char *pipe_name,
		struct tstream_context **tstream,
		struct tsocket_address **remote_client_addr,
		struct tsocket_address **local_server_addr)
{
	struct tevent_context *ev_ctx = global_event_context();
	struct npsd_echo_connection *echo_conn = NULL;
	struct tevent_req *subreq = NULL;
	size_t max_size;

	if (strcmp(npsd_echo_msg8_iface.pipe_name, pipe_name) == 0) {
		max_size = npsd_echo_msg8_iface.allocation_size;
	} else if (strcmp(npsd_echo_msg16_iface.pipe_name, pipe_name) == 0) {
		max_size = npsd_echo_msg16_iface.allocation_size;
	} else {
		return NT_STATUS_REQUEST_NOT_ACCEPTED;
	}

	echo_conn = talloc_zero(worker_conn, struct npsd_echo_connection);
	if (echo_conn == NULL) {
		return NT_STATUS_NO_MEMORY;
	}

	echo_conn->ev = ev_ctx;
	echo_conn->worker_conn = worker_conn;
	echo_conn->session_info = talloc_move(echo_conn, transport_session_info);
	echo_conn->tstream = talloc_move(echo_conn, tstream);
	echo_conn->remote_client_addr = talloc_move(echo_conn, remote_client_addr);
	echo_conn->local_server_addr = talloc_move(echo_conn, local_server_addr);
	echo_conn->max_size = max_size;

	DBG_NOTICE("starting echo server loop max_size=%zu\n", max_size);

	subreq = tstream_monitor_send(echo_conn,
				      echo_conn->ev,
				      echo_conn->tstream);
	if (subreq == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	echo_conn->error_subreq = subreq;
	tevent_req_set_callback(echo_conn->error_subreq,
				npsd_echo_connection_error,
				echo_conn);

	subreq = tstream_readv_pdu_send(echo_conn,
					echo_conn->ev,
					echo_conn->tstream,
					npsd_echo_connection_next_vector,
					echo_conn);
	if (subreq == NULL) {
		return NT_STATUS_NO_MEMORY;
	}
	echo_conn->in_subreq = subreq;
	tevent_req_set_callback(echo_conn->in_subreq,
				npsd_echo_connection_in_done,
				echo_conn);

	talloc_set_destructor(echo_conn, npsd_echo_connection_destructor);

	return NT_STATUS_OK;
}

static void npsd_echo_connection_error(struct tevent_req *subreq)
{
	struct npsd_echo_connection *echo_conn =
		tevent_req_callback_data(subreq,
		struct npsd_echo_connection);
	int ret;
	int err;

	SMB_ASSERT(echo_conn->error_subreq == subreq);
	echo_conn->error_subreq = NULL;

	ret = tstream_monitor_recv(subreq, &err);
	TALLOC_FREE(subreq);

	DBG_NOTICE("Error: ret=%d err=%d (%s)\n", ret, err, strerror(err));
	TALLOC_FREE(echo_conn);
}

static int npsd_echo_connection_next_vector(struct tstream_context *stream,
					    void *private_data,
					    TALLOC_CTX *mem_ctx,
					    struct iovec **_vector,
					    size_t *_count)
{
	struct npsd_echo_connection *echo_conn =
		talloc_get_type_abort(private_data,
		struct npsd_echo_connection);
	struct iovec *vector = NULL;
	size_t ofs = echo_conn->blob.length;
	ssize_t pending;

	pending = tstream_pending_bytes(stream);
	if (pending < 0) {
		return pending;
	}

	if (pending == 0) {
		if (ofs != 0) {
			return 0;
		}

		echo_conn->blob = data_blob_talloc(echo_conn,
						   NULL,
						   1);
		if (echo_conn->blob.length == 0) {
			return -ENOMEM;
		}
	} else {
		size_t max_size = MIN(UINT16_MAX, echo_conn->max_size);
		size_t full_length;
		bool ok;

		if (pending > max_size) {
			return -EPROTO;
		}
		if (ofs > max_size) {
			return -EPROTO;
		}
		full_length = ofs + pending;
		if (full_length > max_size) {
			return -EPROTO;
		}

		ok = data_blob_realloc(echo_conn,
				       &echo_conn->blob,
				       full_length);
		if (!ok) {
			return -ENOMEM;
		}
	}

	vector = talloc(mem_ctx, struct iovec);
	if (vector == NULL) {
		return -ENOMEM;
	}
	vector->iov_base = echo_conn->blob.data + ofs;
	vector->iov_len = echo_conn->blob.length - ofs;

	*_vector = vector;
	*_count = 1;
	return 0;
}

static void npsd_echo_connection_in_done(struct tevent_req *subreq)
{
	struct npsd_echo_connection *echo_conn =
		tevent_req_callback_data(subreq,
		struct npsd_echo_connection);
	int ret;
	int err;

	SMB_ASSERT(echo_conn->in_subreq == subreq);
	echo_conn->in_subreq = NULL;

	ret = tstream_readv_pdu_recv(subreq, &err);
	TALLOC_FREE(subreq);
	if (ret < 0) {
		DBG_ERR("tstream_readv_pdu_recv() ret=%d err=%d (%s)\n",
			ret, err, strerror(err));
		TALLOC_FREE(echo_conn);
		return;
	}

	echo_conn->out_vec.iov_base = echo_conn->blob.data;
	echo_conn->out_vec.iov_len = echo_conn->blob.length;

	subreq = tstream_writev_send(echo_conn,
				     echo_conn->ev,
				     echo_conn->tstream,
				     &echo_conn->out_vec,
				     1);
	if (subreq == NULL) {
		DBG_ERR("tstream_writev_send() failed\n");
		TALLOC_FREE(echo_conn);
		return;
	}
	echo_conn->out_subreq = subreq;
	tevent_req_set_callback(echo_conn->out_subreq,
				npsd_echo_connection_out_done,
				echo_conn);
	return;
}

static void npsd_echo_connection_out_done(struct tevent_req *subreq)
{
	struct npsd_echo_connection *echo_conn =
		tevent_req_callback_data(subreq,
		struct npsd_echo_connection);
	int ret;
	int err;

	SMB_ASSERT(echo_conn->out_subreq == subreq);
	echo_conn->out_subreq = NULL;

	echo_conn->out_vec = (struct iovec) { .iov_len = 0, };
	data_blob_free(&echo_conn->blob);

	ret = tstream_writev_recv(subreq, &err);
	TALLOC_FREE(subreq);
	if (ret < 0) {
		DBG_ERR("tstream_writev_recv() ret=%d err=%d (%s)\n",
			ret, err, strerror(err));
		TALLOC_FREE(echo_conn);
		return;
	}

	subreq = tstream_readv_pdu_send(echo_conn,
					echo_conn->ev,
					echo_conn->tstream,
					npsd_echo_connection_next_vector,
					echo_conn);
	if (subreq == NULL) {
		DBG_ERR("tstream_readv_pdu_send() failed\n");
		TALLOC_FREE(echo_conn);
		return;
	}
	echo_conn->in_subreq = subreq;
	tevent_req_set_callback(echo_conn->in_subreq,
				npsd_echo_connection_in_done,
				echo_conn);
	return;
}

static NTSTATUS npsd_echo_shutdown_servers(struct rpc_worker *worker,
					   void *private_data)
{
	DBG_DEBUG("server exiting\n");
	return NT_STATUS_OK;
}

int main(int argc, const char *argv[])
{
	return nps_worker_main(
		argc,
		argv,
		"npsd_echo_server",
		5,
		60,
		npsd_echo_get_interfaces,
		npsd_echo_setup_servers,
		npsd_echo_accept_client,
		npsd_echo_shutdown_servers,
		NULL);
}
