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");