Basic protocol structure on the server side

This commit is contained in:
2025-09-28 11:09:26 +02:00
parent 0ade2938f3
commit b69cabfab8
9 changed files with 412 additions and 75 deletions
+50
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <common/PlayerConnection.h>
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);
+39
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <gio/gio.h>
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[], ...);
+27
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
typedef struct {
void* data;
} ServerResponse;
+21
View File
@@ -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;
}
+56
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <glib.h>
#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);
+27
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#pragma once
typedef struct {
} PlayerInfo;
+38
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#include <common/ClientRequest.h>
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;
}
+97
View File
@@ -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 <https://www.gnu.org/licenses/>.
*/
#include <common/PlayerConnection.h>
#include <common/messaging.h>
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);
}
+57 -75
View File
@@ -3,6 +3,8 @@
#include <glib.h>
#include <gio/gio.h>
#include <common/PlayerConnection.h>
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");