Basic protocol structure on the server side
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
|
||||
@@ -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[], ...);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user