//
// libsyd: Rust-based C library for syd interaction via /dev/syd
// lib/examples/echo_server.c: syd API C Library Echo Server Example
//
// Copyright (c) 2023 Ali Polatel <alip@chesswob.org>
//
// SPDX-License-Identifier: GPL-3.0

/*
 * Compile this with:
 * cc -fPIC -fPIE echo_server.c -oecho_server -static-pie -lsyd
 * Run with:
 * syd -pcontainer ./echo_server
 * This binds to port 127.0.0.1:65432
 */

#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <err.h>

#include <syd.h>

#define PORT 65432 // Port number for the server
#define BUFFER_SIZE 1024 // Size of the buffer for incoming messages

int main()
{
	int server_fd, new_socket;
	struct sockaddr_in address;
	int r, opt = 1;
	int addrlen = sizeof(address);
	char buffer[BUFFER_SIZE] = {0};
	bool should_quit = false; // Flag to indicate if the server should quit

	// Creating socket file descriptor
	if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
		err(1, "socket failed");
	}

	// Forcefully attaching socket to the port 65432
	if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt,
	               sizeof(opt))) {
		err(1, "setsockopt");
	}

	address.sin_family = AF_INET;
	address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
	address.sin_port = htons(PORT);

	if ((r = syd_check()) != 0) {
		errno = -r;
		err(1, "Not running under syd");
	}
	if ((r = syd_allow_net_bind_add("LOOPBACK!65432")) != 0) {
		errno = -r;
		err(1, "Failed to allow LOOPBACK!65432");
	}

	// Binding the socket to the port 65432
	if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
		err(1, "bind failed");
	}

	if ((r = syd_allow_net_bind_del("LOOPBACK!65432")) != 0) {
		errno = -r;
		err(1, "Failed to allow LOOPBACK!65432");
	}
	if ((r = syd_lock(LOCK_ON)) != 0) {
		errno = -r;
		err(1, "Failed to lock syd");
	}

	// Listening for incoming connections
	if (listen(server_fd, 3) < 0) {
		err(1, "listen");
	}

	for (;;) {
		printf("*** Waiting for a connection...\n");

		// Accepting an incoming connection
		if ((new_socket = accept(server_fd, (struct sockaddr *)&address,
		                         (socklen_t *)&addrlen)) < 0) {
			err(1, "accept");
		}

		printf("*** Connection established\n");

		// Read data from the client, echo it back
		ssize_t read_bytes;
		while ((read_bytes = read(new_socket, buffer, BUFFER_SIZE)) > 0) {
			if (strncmp(buffer, "QUIT", 4) == 0) {
				printf("*** Received 'QUIT', exiting.\n");
				should_quit = true; // Set the flag to break out of the outer loop
				break;
			}
			write(new_socket, buffer, read_bytes);
			memset(buffer, 0, BUFFER_SIZE); // Clear the buffer
		}

		if (read_bytes < 0) {
			err(1, "read");
		}

		close(new_socket);
		printf("*** Connection closed\n");

		if (should_quit) {
			break; // Exit the outer loop if "QUIT" was received
		}
	}

	printf("*** Shutting down server.\n");
	close(server_fd);

	return 0;
}
