diff --git a/src/common/ClientRequest.h b/src/common/ClientRequest.h
new file mode 100644
index 0000000..fca24a2
--- /dev/null
+++ b/src/common/ClientRequest.h
@@ -0,0 +1,50 @@
+/*
+
+ LibreFront --- an OpenFront clone
+ Copyright (C) 2025 Adrien Jaguenet
+ Copyright (C) 2025 Emil Kosz
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+*/
+
+#pragma once
+
+#include
+
+typedef struct {
+ char name[32];
+ //TODO: clan tag, flag, territory pattern under a PlayerInfo struct
+ int gameID;
+} JoinRequest;
+
+typedef struct {
+ int playerID;
+ union {
+ JoinRequest join;
+ } req;
+} ClientRequest;
+
+/* These prototypes are shared between client and server, but their implementation will be different
+ * and will be found in client/ClientRequest.c and server/ClientRequest.c */
+
+/*
+ * @param playerConn: player connection info
+ * @param req: the request to be sent (client) or received (server)
+ * @return: on success, the playerID, -1 on failure
+ */
+int requestJoinGame(PlayerConnection* playerConn, JoinRequest* req);
+
+
+
diff --git a/src/common/PlayerConnection.h b/src/common/PlayerConnection.h
new file mode 100644
index 0000000..502a613
--- /dev/null
+++ b/src/common/PlayerConnection.h
@@ -0,0 +1,39 @@
+/*
+
+ LibreFront --- an OpenFront clone
+ Copyright (C) 2025 Adrien Jaguenet
+ Copyright (C) 2025 Emil Kosz
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+*/
+
+#pragma once
+
+#include
+
+typedef struct {
+ GSocket* socket;
+ GSocketConnection* connection;
+
+ /* Obviously, the client's istream is the server's ostream, and vice versa*/
+ GInputStream* istream;
+ GOutputStream* ostream;
+} PlayerConnection;
+
+PlayerConnection* PlayerConnection_accept(GSocket* socket);
+void PlayerConnection_close(PlayerConnection** self);
+
+void PlayerConnection_sendErrorResponse(PlayerConnection* self, const char fmt[], ...);
+
diff --git a/src/common/ServerResponse.h b/src/common/ServerResponse.h
new file mode 100644
index 0000000..50ae7b9
--- /dev/null
+++ b/src/common/ServerResponse.h
@@ -0,0 +1,27 @@
+/*
+
+ LibreFront --- an OpenFront clone
+ Copyright (C) 2025 Adrien Jaguenet
+ Copyright (C) 2025 Emil Kosz
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+*/
+
+#pragma once
+
+typedef struct {
+ void* data;
+} ServerResponse;
+
diff --git a/src/common/messaging.c b/src/common/messaging.c
new file mode 100644
index 0000000..9ee935f
--- /dev/null
+++ b/src/common/messaging.c
@@ -0,0 +1,21 @@
+#include "messaging.h"
+
+int Message_read(GInputStream* stream, Message* message) {
+ GError *error;
+ gssize n =
+ g_input_stream_read (stream, &(message->header), sizeof(message->header), NULL,
+ &error);
+ if (n < 0) {
+ g_error("Could not g_input_stream_read() %s", error->message);
+ g_clear_error(&error);
+ return -1;
+ } else {
+ g_info("Received message with version=%d, data_size=%d, type=%x", message->header.version, message->header.data_size, message->header.type);
+ }
+
+ if (message->header.version != PROTOCOL_VERSION) {
+ g_error("Wrong protocol (expects %d, received %d)", PROTOCOL_VERSION, message->header.version);
+ return -1;
+ }
+ return 0;
+}
diff --git a/src/common/messaging.h b/src/common/messaging.h
new file mode 100644
index 0000000..33c84b7
--- /dev/null
+++ b/src/common/messaging.h
@@ -0,0 +1,56 @@
+/*
+
+ LibreFront --- an OpenFront clone
+ Copyright (C) 2025 Adrien Jaguenet
+ Copyright (C) 2025 Emil Kosz
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+*/
+
+#pragma once
+
+#include
+
+#include "ClientRequest.h"
+#include "ServerResponse.h"
+
+#define PROTOCOL_VERSION 0x1
+
+typedef enum {
+ RESPONSE_ERROR = 0xffffffff,
+} ResponseType;
+
+typedef enum {
+ REQUEST_HELLO = 0x00000001,
+ REQUEST_JOINGAME = 0x00000002,
+} RequestType;
+
+typedef struct {
+ guint32 version;
+ guint32 type;
+ guint32 data_size;
+} MessageHeader;
+
+typedef struct {
+ MessageHeader header;
+ void* data;
+} Message;
+
+/* @param stream: input stream (from a socket ideally)
+ * @param message: message structure that will be read into
+ * @return -1 on error
+ * */
+int Message_read(GInputStream* stream, Message* message);
+
diff --git a/src/common/player.h b/src/common/player.h
new file mode 100644
index 0000000..d4c2e80
--- /dev/null
+++ b/src/common/player.h
@@ -0,0 +1,27 @@
+/*
+
+ LibreFront --- an OpenFront clone
+ Copyright (C) 2025 Adrien Jaguenet
+ Copyright (C) 2025 Emil Kosz
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+*/
+
+#pragma once
+
+typedef struct {
+
+} PlayerInfo;
+
diff --git a/src/server/ClientRequest.c b/src/server/ClientRequest.c
new file mode 100644
index 0000000..6620f27
--- /dev/null
+++ b/src/server/ClientRequest.c
@@ -0,0 +1,38 @@
+/*
+
+ LibreFront --- an OpenFront clone
+ Copyright (C) 2025 Adrien Jaguenet
+ Copyright (C) 2025 Emil Kosz
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+*/
+
+#include
+
+int requestJoinGame(PlayerConnection* playerConn, JoinRequest* req) {
+ if (1 /*TODO game ID is not correct */) {
+ PlayerConnection_sendErrorResponse(playerConn, "Incorrect game ID %d", req->gameID);
+ }
+ if (1 /*TODO: username is not valid */ ) {
+ //WARNING: zero-terminated string?
+ PlayerConnection_sendErrorResponse(playerConn, "Invalid username '%s'", req->name);
+ }
+ if (1 /*TODO: Check that the game is not full*/) {
+ PlayerConnection_sendErrorResponse(playerConn, "Game with ID %d is full", req->gameID);
+ }
+ int playerID = -1 /*TODO: Select a pseudo-random unique positive integer to serve as the player's "secret" ID*/;
+ return playerID;
+}
+
diff --git a/src/server/PlayerConnection.c b/src/server/PlayerConnection.c
new file mode 100644
index 0000000..53b3fa6
--- /dev/null
+++ b/src/server/PlayerConnection.c
@@ -0,0 +1,97 @@
+/*
+
+ LibreFront --- an OpenFront clone
+ Copyright (C) 2025 Adrien Jaguenet
+ Copyright (C) 2025 Emil Kosz
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero 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 Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+*/
+
+#include
+#include
+
+PlayerConnection* PlayerConnection_accept(GSocket* socket) {
+ GError* error = NULL;
+ PlayerConnection *pconn = g_new0(PlayerConnection, 1);
+
+ pconn->socket = g_socket_accept (socket, NULL, &error);
+ pconn->connection =
+ g_socket_connection_factory_create_connection (pconn->socket);
+
+
+ if (!pconn->connection)
+ {
+ g_printerr ("accept() failed: %s\n", error->message);
+ goto error_flow;
+ }
+ else
+ {
+ g_info ("New connection accepted");
+ }
+
+ pconn->istream = g_io_stream_get_input_stream (G_IO_STREAM (pconn->connection));
+ if (!pconn->istream) {
+ g_printerr("Could not get input stream: %s", error->message);
+ goto error_flow;
+ }
+ else
+ {
+ g_info ("Got input stream.");
+ }
+
+ pconn->ostream = g_io_stream_get_output_stream (G_IO_STREAM (pconn->connection));
+ if (!pconn->ostream) {
+ g_printerr("Could not get output stream: %s", error->message);
+ goto error_flow;
+ }
+ else
+ {
+ g_info ("Got output stream.");
+ }
+
+ return pconn;
+
+error_flow:
+ g_clear_error (&error);
+ PlayerConnection_close(&pconn);
+ return NULL;
+}
+
+void PlayerConnection_close(PlayerConnection** self) {
+ g_io_stream_close (G_IO_STREAM (*self), NULL, NULL);
+ g_object_unref ((*self)->connection);
+ g_object_unref ((*self)->socket);
+ g_free(*self);
+ *self = NULL;
+}
+
+void PlayerConnection_sendErrorResponse(PlayerConnection* self, const char fmt[], ...) {
+ //WARNING: is this safe against non-null-terminated strings?
+ GError *error = NULL;
+
+ va_list args;
+ va_start(args);
+
+ gsize bytes_written = 0;
+ Message msg;
+ //TODO: create a proper ErrorResponse object
+ msg.header.version = PROTOCOL_VERSION;
+ msg.header.type = RESPONSE_ERROR;
+ msg.header.data_size = 0;
+ g_output_stream_write(self->ostream, &msg, sizeof(msg.header), NULL, &error);
+ g_output_stream_vprintf (self->ostream, &bytes_written, NULL, &error, fmt, args);
+ g_output_stream_flush (self->ostream, NULL, &error);
+ va_end(args);
+}
diff --git a/src/server/server.c b/src/server/server.c
index dffec4e..e8dd960 100644
--- a/src/server/server.c
+++ b/src/server/server.c
@@ -3,6 +3,8 @@
#include
#include
+#include
+
int
main (int argc, char *argv[])
{
@@ -13,100 +15,80 @@ main (int argc, char *argv[])
GError *error = NULL;
int PORT = SERVER_DEFAULT_PORT;
if (argc > 1)
- {
- PORT = atoi (argv[1]);
- g_info ("Changed the listening port from default=%d to custom=%d",
- SERVER_DEFAULT_PORT, PORT);
- }
+ {
+ PORT = atoi (argv[1]);
+ g_info ("Changed the listening port from default=%d to custom=%d",
+ SERVER_DEFAULT_PORT, PORT);
+ }
GSocket *socket = g_socket_new (G_SOCKET_FAMILY_IPV4, G_SOCKET_TYPE_STREAM,
- G_SOCKET_PROTOCOL_TCP, &error);
+ G_SOCKET_PROTOCOL_TCP, &error);
if (!socket)
- {
- g_printerr ("Socket creation failed: %s\n", error->message);
- g_clear_error (&error);
- return -1;
- }
+ {
+ g_printerr ("Socket creation failed: %s\n", error->message);
+ g_clear_error (&error);
+ return -1;
+ }
else
- {
- g_info ("Created TCP socket.");
- }
+{
+ g_info ("Created TCP socket.");
+ }
GInetAddress *addr = g_inet_address_new_any (G_SOCKET_FAMILY_IPV4);
GSocketAddress *sockaddr = g_inet_socket_address_new (addr, PORT);
if (!g_socket_bind (socket, sockaddr, TRUE, &error))
- {
- g_printerr ("Bind failed: %s\n", error->message);
- g_clear_error (&error);
- g_object_unref (socket);
- }
+ {
+ g_printerr ("Bind failed: %s\n", error->message);
+ g_clear_error (&error);
+ g_object_unref (socket);
+ }
else
- {
- g_info ("Bound TCP socket to port %d", PORT);
- }
+{
+ g_info ("Bound TCP socket to port %d", PORT);
+ }
g_object_unref (addr);
g_object_unref (sockaddr);
if (!g_socket_listen (socket, &error))
- {
- g_printerr ("Listen failed: %s\n", error->message);
- g_clear_error (&error);
- g_object_unref (socket);
- return -1;
- }
+ {
+ g_printerr ("Listen failed: %s\n", error->message);
+ g_clear_error (&error);
+ g_object_unref (socket);
+ return -1;
+ }
else
- {
- g_info ("TCP socket is now listening");
- }
+{
+ g_info ("TCP socket is now listening");
+ }
g_info ("Listening...");
while (true)
- {
- GSocket *socket2 = g_socket_accept (socket, NULL, &error);
- GSocketConnection *connection =
- g_socket_connection_factory_create_connection (socket2);
- if (!connection)
- {
- g_printerr ("accept() failed: %s\n", error->message);
- g_clear_error (&error);
- continue;
- }
- else
- {
- g_info ("New connection accepted");
- }
-
- GInputStream *istream =
- g_io_stream_get_input_stream (G_IO_STREAM (connection));
- gchar buffer[1024];
- gssize n =
- g_input_stream_read (istream, buffer, sizeof (buffer) - 1, NULL,
- &error);
-
- if (n > 0)
- {
- buffer[n] = '\0';
- g_print ("Received from client: %s\n", buffer);
- }
- else
- {
- g_printerr ("Read error: %s\n", error->message);
- g_clear_error (&error);
- }
-
- GOutputStream *ostream =
- g_io_stream_get_output_stream (G_IO_STREAM (connection));
- const gchar *response =
- "Hello from the librefront server. Not implemented!!";
- g_output_stream_write (ostream, response, strlen (response), NULL,
- &error);
- g_output_stream_flush (ostream, NULL, &error);
-
- g_io_stream_close (G_IO_STREAM (connection), NULL, NULL);
- g_object_unref (connection);
- g_object_unref (socket2);
+ {
+ PlayerConnection *playerConnection = PlayerConnection_accept(socket);
+ if (! playerConnection) {
+ g_printerr("Error creating a player connection");
}
+ gchar buffer[1024];
+ gssize n =
+ g_input_stream_read (playerConnection->istream, buffer, sizeof (buffer) - 1, NULL,
+ &error);
+
+ if (n > 0)
+ {
+ buffer[n] = '\0';
+ g_print ("Received from client: %s\n", buffer);
+ }
+ else
+ {
+ g_printerr ("Read error: %s\n", error->message);
+ g_clear_error (&error);
+ }
+
+ PlayerConnection_sendErrorResponse(playerConnection, "This is an error response");
+
+ PlayerConnection_close(&playerConnection);
+ }
g_object_unref (socket);
fprintf (stderr, "Server not implemented!\n");