abt to big refactor

This commit is contained in:
2025-04-08 20:24:00 -05:00
parent a779da6c72
commit 9208ebb685
17 changed files with 501 additions and 83 deletions

View File

@ -9,6 +9,6 @@ include(FetchContent)
FetchContent_Declare(
raylib
GIT_REPOSITORY https://github.com/raysan5/raylib
GIT_TAG 5.5
GIT_TAG 5aa3f0ccc374c1fbfc880402b37871b9c3bb7d8e
)
FetchContent_MakeAvailable(raylib)

View File

@ -21,6 +21,7 @@ errorret_t errorCode(const errorret_t code, const char_t *message, ...) {
"Multiple errors encountered."
);
errorPrint();
return code;
}
va_list args;

View File

@ -7,13 +7,29 @@
#include "console/console.h"
#include "server/server.h"
#include "util/string.h"
bool_t exitRequested = false;
void cmdExit(const consolecmdexec_t *exec) {
CloseWindow();
exitRequested = true;
}
void cmdServe(const consolecmdexec_t *exec) {
serverStart(3030);
uint16_t port = 3030;
if(exec->argc != 0) {
if(!stringToU16(exec->argv[0], &port)) {
consolePrint("Invalid port number: %s", exec->argv[0]);
return;
}
}
errorret_t ret = serverStart(port);
if(ret != ERROR_OK) {
consolePrint("Failed to start server: %s", errorString());
errorFlush();
return;
}
}
void cmdClose(const consolecmdexec_t *exec) {
@ -39,6 +55,8 @@ int main(void) {
consoleDraw();
EndDrawing();
if(exitRequested) break;
}
CloseWindow();

View File

@ -9,3 +9,6 @@ target_sources(${DUSK_TARGET_NAME}
server.c
serverclient.c
)
# Subdirs
add_subdirectory(packet)

View File

@ -0,0 +1,11 @@
# Copyright (c) 2025 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DUSK_TARGET_NAME}
PRIVATE
packet.c
packetwelcome.c
)

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "packet.h"
#include "assert/assert.h"
#include "util/memory.h"
void packetInit(
packet_t *packet,
const packettype_t type,
const uint32_t length
) {
assertNotNull(packet, "Packet is NULL");
memoryZero(packet, sizeof(packet_t));
assertTrue(length > 0, "Packet length is 0");
assertTrue(
length <= sizeof(packetdata_t),
"Packet length is too large"
);
packet->type = type;
packet->length = length;
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
#include "packetwelcome.h"
#include "packetdisconnect.h"
typedef enum {
PACKET_TYPE_INVALID = 0,
PACKET_TYPE_WELCOME = 1,
PACKET_TYPE_DISCONNECT = 2,
} packettype_t;
typedef union {
packetwelcome_t welcome;
packetdisconnect_t disconnect;
} packetdata_t;
typedef struct packet_s {
packettype_t type;
uint32_t length;
packetdata_t data;
} packet_t;
/**
* Initializes a packet with the given type. This is only to be used by sub
* initializers, such as packetWelcomeCreate for example.
*
* @param packet Pointer to the packet structure to initialize.
* @param type The type of the packet.
* @param length The length of the packet data.
*/
void packetInit(
packet_t *packet,
const packettype_t type,
const uint32_t length
);

View File

@ -0,0 +1,17 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "packet.h"
#include "util/memory.h"
void packetDisconnectCreate(
packet_t *packet,
const packetdisconnectreason_t reason
) {
packetInit(packet, PACKET_TYPE_DISCONNECT, sizeof(packetdisconnect_t));
packet->data.disconnect.reason = reason;
}

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct packet_s packet_t;
typedef enum {
PACKET_DISCONNECT_REASON_UNKNOWN = 0,
PACKET_DISCONNECT_REASON_INVALID_VERSION = 1,
PACKET_DISCONNECT_REASON_MALFORMED_PACKET = 2,
} packetdisconnectreason_t;
typedef struct {
packetdisconnectreason_t reason;
} packetdisconnect_t;
/**
* Creates a disconnect packet.
*
* @param packet Pointer to the packet structure to initialize.
* @param reason The reason for the disconnect.
*/
void packetDisconnectCreate(
packet_t *packet,
const packetdisconnectreason_t reason
);

View File

@ -0,0 +1,16 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "packet.h"
#include "util/memory.h"
void packetWelcomeCreate(packet_t *packet) {
packetInit(packet, PACKET_TYPE_WELCOME, PACKET_WELCOME_SIZE);
memoryCopy(
packet->data.welcome.dusk, PACKET_WELCOME_STRING, PACKET_WELCOME_SIZE
);
}

View File

@ -0,0 +1,25 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "dusk.h"
typedef struct packet_s packet_t;
#define PACKET_WELCOME_STRING "DUSK"
#define PACKET_WELCOME_SIZE 4
typedef struct {
char_t dusk[PACKET_WELCOME_SIZE];
} packetwelcome_t;
/**
* Creates a welcome packet.
*
* @param packet Pointer to the packet structure to initialize.
*/
void packetWelcomeCreate(packet_t *packet);

View File

@ -14,21 +14,20 @@ server_t SERVER;
void serverInit() {
memoryZero(&SERVER, sizeof(server_t));
pthread_mutex_init(&SERVER.startMutex, NULL);
pthread_cond_init(&SERVER.startCond, NULL);
SERVER.state = SERVER_STATE_STOPPED;
}
errorret_t serverStart(uint16_t port) {
assertFalse(SERVER.isRunning, "Server is already running?");
consolePrint("Starting server on port %d...\n", port);
if(SERVER.state != SERVER_STATE_STOPPED) {
return error("Server is already running");
}
pthread_mutex_lock(&SERVER.startMutex);
SERVER.threadReady = false;
consolePrint("Starting server on port %d...\n", port);
memoryZero(&SERVER, sizeof(server_t));
// Initialize the server socket
SERVER.serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if(SERVER.serverSocket < 0) {
pthread_mutex_unlock(&SERVER.startMutex);
return error("Failed to create socket");
}
SERVER.serverAddress.sin_family = AF_INET;
@ -41,35 +40,32 @@ errorret_t serverStart(uint16_t port) {
(struct sockaddr *)&SERVER.serverAddress,
sizeof(SERVER.serverAddress)
);
if(res < 0) {
if(res != 0) {
close(SERVER.serverSocket);
pthread_mutex_unlock(&SERVER.startMutex);
return error("Failed to bind socket");
}
// Set the socket to listen for incoming connections
res = listen(SERVER.serverSocket, SERVER_MAX_CLIENTS);
if(res < 0) {
if(res != 0) {
close(SERVER.serverSocket);
pthread_mutex_unlock(&SERVER.startMutex);
return error("Failed to listen on socket");
}
// Start the server thread
SERVER.isRunning = true;
if(pthread_create(&SERVER.thread, NULL, serverThread, NULL) != 0) {
// Start the server thread.
SERVER.state = SERVER_STATE_STARTING;
res = pthread_create(&SERVER.thread, NULL, serverThread, NULL);
if(res != 0) {
perror("Failed to create server thread");
serverStop();
pthread_mutex_unlock(&SERVER.startMutex);
return error("Failed to create server thread");
}
// Wait for the server thread to be ready
while(!SERVER.threadReady) {
pthread_cond_wait(&SERVER.startCond, &SERVER.startMutex);
// Wait
while(SERVER.state == SERVER_STATE_STARTING) {
usleep(1000);
}
pthread_mutex_unlock(&SERVER.startMutex);
consolePrint("Server started.");
return ERROR_OK;
}
@ -78,18 +74,17 @@ void * serverThread(void *arg) {
struct timeval timeout;
fd_set readfds;
pthread_mutex_lock(&SERVER.startMutex);
SERVER.threadReady = true;
pthread_cond_signal(&SERVER.startCond);
pthread_mutex_unlock(&SERVER.startMutex);
SERVER.state = SERVER_STATE_RUNNING;
while(SERVER.isRunning) {
usleep(SERVER_TIMEOUT * 1000);
while(SERVER.state == SERVER_STATE_RUNNING) {
usleep(1000);
FD_ZERO(&readfds);
FD_SET(SERVER.serverSocket, &readfds);
timeout.tv_usec = SERVER_TIMEOUT * 1000;
// Fix timeout struct
timeout.tv_sec = 1;
timeout.tv_usec = 0;
// Wait for
int32_t activity = select(
@ -119,7 +114,6 @@ void * serverThread(void *arg) {
continue;
}
// Initialize the client
serverclient_t *client = NULL;
uint8_t i = 0;
@ -147,14 +141,17 @@ void * serverThread(void *arg) {
errorFlush();
close(clientSocket);
}
usleep(1000);
}
printf("Server thread exiting.\n");
assertFalse(
SERVER.isRunning, "Server thread exiting while server is running?"
assertTrue(
SERVER.state != SERVER_STATE_RUNNING,
"Server thread exiting while server is running?"
);
SERVER.thread = 0;
SERVER.state = SERVER_STATE_STOPPED;
return NULL;
}
@ -168,9 +165,36 @@ uint8_t serverGetClientCount() {
}
void serverStop() {
consolePrint("Stopping server...");
if(SERVER.state == SERVER_STATE_STOPPED) return;
SERVER.isRunning = false;
assertTrue(
SERVER.state != SERVER_STATE_STARTING,
"Stopping a server that is starting?\n"
);
consolePrint("Stopping server...");
SERVER.state = SERVER_STATE_STOPPING;
// Wait for the server thread to finish
int32_t maxWaits = 0;
while(SERVER.state == SERVER_STATE_STOPPING) {
usleep(1000);
maxWaits++;
if(maxWaits < 1000) continue;
consolePrint("Server thread did not stop in time, forcing exit.");
SERVER.state = SERVER_STATE_STOPPED;
break;
}
// Kill the clients
uint8_t i = 0;
do {
serverclient_t *client = &SERVER.clients[i++];
serverClientClose(client);
} while(i < SERVER_MAX_CLIENTS);
// Stop the server
if(SERVER.serverSocket >= 0) {
close(SERVER.serverSocket);
SERVER.serverSocket = -1;
@ -181,9 +205,6 @@ void serverStop() {
SERVER.thread = 0;
}
pthread_mutex_destroy(&SERVER.startMutex);
pthread_cond_destroy(&SERVER.startCond);
consolePrint("Server stopped.");
}

View File

@ -12,16 +12,19 @@
#define SERVER_MAX_CLIENTS 32
#define SERVER_MAX_CHANNELS 2
#define SERVER_TIMEOUT 200
typedef enum {
SERVER_STATE_STOPPED,
SERVER_STATE_STARTING,
SERVER_STATE_RUNNING,
SERVER_STATE_STOPPING
} serverstate_t;
typedef struct {
bool_t isRunning;
serverstate_t state;
pthread_t thread;
int serverSocket;
struct sockaddr_in serverAddress;
pthread_mutex_t startMutex;
pthread_cond_t startCond;
bool_t threadReady;
serverclient_t clients[SERVER_MAX_CLIENTS];
} server_t;

View File

@ -6,6 +6,8 @@
*/
#include "serverclient.h"
#include "server.h"
#include "assert/assert.h"
#include "util/memory.h"
#include <sys/socket.h>
#include "console/console.h"
@ -18,8 +20,9 @@ errorret_t serverClientAccept(
client->socket = socket;
client->state = SERVER_CLIENT_STATE_ACCEPTING;
// Set timeout to 20 seconds
client->timeout.tv_usec = SERVER_CLIENT_TIMEOUT * 1000;
// Set timeout to 8 seconds
client->timeout.tv_sec = 8;
client->timeout.tv_usec = 0;
// Create a thread for the client
if(pthread_create(&client->thread, NULL, serverClientThread, client) != 0) {
@ -33,6 +36,10 @@ errorret_t serverClientSend(
const uint8_t *data,
const size_t len
) {
assertNotNull(client, "Client is NULL");
assertNotNull(data, "Data is NULL");
assertTrue(len > 0, "Data length is 0");
if(client->state != SERVER_CLIENT_STATE_ACCEPTING) {
return error("Client is not accepting");
}
@ -48,62 +55,156 @@ errorret_t serverClientSendString(
return serverClientSend(client, (const uint8_t *)data, strlen(data));
}
errorret_t serverClientSendPacket(
serverclient_t * client,
const packet_t *packet
) {
assertNotNull(client, "Client is NULL");
assertNotNull(packet, "Packet is NULL");
assertTrue(packet->type != PACKET_TYPE_INVALID, "Packet type is INVALID");
assertTrue(packet->length > 0, "Packet length is 0");
assertTrue(
packet->length <= sizeof(packetdata_t),
"Packet length is too large (1)"
);
size_t fullSize = sizeof(packet_t) - sizeof(packet->data) + packet->length;
assertTrue(fullSize <= sizeof(packet_t), "Packet size is too large (2)");
return serverClientSend(client, (const uint8_t *)packet, fullSize);
}
ssize_t serverClientReceive(
serverclient_t * client,
uint8_t *buffer,
const size_t len
) {
if(client->state != SERVER_CLIENT_STATE_ACCEPTING) return -1;
ssize_t received = recv(client->socket, buffer, len, 0);
if(received < 0) return received;
return received;
assertNotNull(client, "Client is NULL");
assertNotNull(buffer, "Buffer is NULL");
assertTrue(len > 0, "Buffer length is 0");
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) return -1;
if(client->state == SERVER_CLIENT_STATE_DISCONNECTING) return -1;
return recv(client->socket, buffer, len, 0);
}
void serverClientClose(serverclient_t *client) {
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
close(client->socket);
assertNotNull(client, "Client is NULL");
if(client->state == SERVER_CLIENT_STATE_DISCONNECTED) return;
// Mark client as disconnecting
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
int32_t maxWaits = 0;
while(client->state == SERVER_CLIENT_STATE_DISCONNECTING) {
// Wait for the thread to finish
usleep(1000);
maxWaits++;
if(maxWaits > 10) break;
}
pthread_join(client->thread, NULL);
client->thread = 0;
// Close the socket
if(client->socket != -1) {
close(client->socket);
client->socket = -1;
}
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
consolePrint("Client %d disconnected.", client->socket);
}
void serverClientCloseOnThread(serverclient_t *client, const char_t *reason) {
assertNotNull(client, "Client is NULL");
assertNotNull(reason, "Reason is NULL");
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
close(client->socket);
client->socket = -1;
consolePrint("Client disconnected.");
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
client->thread = 0;
consolePrint("Client %d disconnected: %s", client->socket, reason);
pthread_exit(NULL);
}
void *serverClientThread(void *arg) {
assertNotNull(arg, "Client is NULL");
serverclient_t *client = (serverclient_t *)arg;
char_t buffer[1024];
ssize_t read;
errorret_t err;
packet_t packet;
printf("Client thread started for socket %d.\n", client->socket);
consolePrint("Accepting client on socket %d.", client->socket);
// Set socket timeout
setsockopt(
if(setsockopt(
client->socket,
SOL_SOCKET,
SO_RCVTIMEO,
&client->timeout,
sizeof(client->timeout)
);
// Send welcome message
serverClientSendString(client, "DUSK|"DUSK_VERSION);
// Receive version from client
read = serverClientReceive(client, buffer, sizeof(buffer));
// First 5 bytes should be "DUSK|"
if(read < 5 || strncmp(buffer, "DUSK|", 5) != 0) {
serverClientSendString(client, "ERR|Invalid version (0)");
serverClientClose(client);
) < 0) {
serverClientCloseOnThread(client, "Failed to set socket timeout");
return NULL;
}
// Next (up to X bytes where X is the length of the version string)
// should be the version string
buffer[read] = '\0'; // Null-terminate the string
// First message from the client should always be "DUSK|VERSION" to match
// the server version.
{
const char_t *expecting = "DUSK|"DUSK_VERSION;
read = serverClientReceive(client, buffer, sizeof(buffer));
if(read <= 0) {
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
err = serverClientSendPacket(client, &packet);
serverClientCloseOnThread(client, "Failed to receive version");
return NULL;
}
buffer[read] = '\0'; // Null-terminate the string
if(strncmp(buffer, expecting, strlen(expecting)) != 0) {
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
err = serverClientSendPacket(client, &packet);
serverClientCloseOnThread(client, "Invalid version");
return NULL;
}
}
// Send DUSK back!
packetWelcomeCreate(&packet);
err = serverClientSendPacket(client, &packet);
if(err != ERROR_OK) {
serverClientCloseOnThread(client, "Failed to send welcome message");
return NULL;
}
client->state = SERVER_CLIENT_STATE_CONNECTED;
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
read = serverClientReceive(client, buffer, sizeof(buffer));
if(read <= 0) {
serverClientCloseOnThread(client, "Failed to receive data");
break;
}
buffer[read] = '\0'; // Null-terminate the string
consolePrint("Received: %s", buffer);
printf("Received version: %s\n", buffer);
if(SERVER.state != SERVER_STATE_RUNNING) {
serverClientCloseOnThread(client, "Server is shutting down");
break;
}
}
serverClientClose(client);
serverClientSendString(client, "Server is shutting down");
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
return NULL;
}

View File

@ -7,23 +7,28 @@
#pragma once
#include "error/error.h"
#define SERVER_CLIENT_TIMEOUT 20000 // 20 seconds
#include "server/packet/packet.h"
typedef enum {
SERVER_CLIENT_STATE_DISCONNECTED,
SERVER_CLIENT_STATE_ACCEPTING,
SERVER_CLIENT_STATE_CONNECTED,
SERVER_CLIENT_STATE_DISCONNECTING,
} serverclientstate_t;
typedef struct {
int32_t socket;
serverclientstate_t state;
pthread_t thread; // Add thread field
struct timeval timeout; // Add timeout field
pthread_t thread;
struct timeval timeout;
} serverclient_t;
/**
* Accepts an incoming connection from a server client.
*
* @param client Pointer to the server client structure.
* @param socket The socket file descriptor for the client.
* @return Error code indicating success or failure.
*/
errorret_t serverClientAccept(
serverclient_t *client,
@ -56,6 +61,18 @@ errorret_t serverClientSendString(
const char_t *data
);
/**
* Sends a packet to a server client.
*
* @param client Pointer to the server client structure.
* @param packet Pointer to the packet to send.
* @return Error code indicating success or failure.
*/
errorret_t serverClientSendPacket(
serverclient_t * client,
const packet_t *packet
);
/**
* Receives data from a server client.
*
@ -71,15 +88,28 @@ ssize_t serverClientReceive(
);
/**
* Closes the connection to a server client.
* Closes the connection to a server client. Waits for the thread to finish.
*
* @param client Pointer to the server client structure.
*/
void serverClientClose(
serverclient_t *client
void serverClientClose(serverclient_t *client);
/**
* Closes the connection to a server client without waiting for the thread to
* finish.
*
* @param client Pointer to the server client structure.
* @param reason Reason for closing the connection.
*/
void serverClientCloseOnThread(
serverclient_t *client,
const char_t *reason
);
/**
* Thread function for handling a server client.
*
* @param arg Pointer to the server client structure.
* @return NULL.
*/
void *serverClientThread(void *arg);

View File

@ -83,3 +83,45 @@ int32_t stringFormatVA(
assertTrue(ret < destSize, "Formatted string is too long.");
return ret;
}
bool_t stringToI32(const char_t *str, int32_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
char_t *endptr;
errno = 0;
long int result = strtol(str, &endptr, 10);
if (errno != 0 || *endptr != '\0') {
return false;
}
*out = (int32_t)result;
return true;
}
bool_t stringToI64(const char_t *str, int64_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
char_t *endptr;
errno = 0;
long long int result = strtoll(str, &endptr, 10);
if (errno != 0 || *endptr != '\0') {
return false;
}
*out = (int64_t)result;
return true;
}
bool_t stringToU16(const char_t *str, uint16_t *out) {
assertNotNull(str, "str must not be NULL");
assertNotNull(out, "out must not be NULL");
char_t *endptr;
errno = 0;
unsigned long int result = strtoul(str, &endptr, 10);
if (errno != 0 || *endptr != '\0' || result > UINT16_MAX) {
return false;
}
*out = (uint16_t)result;
return true;
}

View File

@ -80,3 +80,30 @@ int32_t stringFormatVA(
char_t *format,
va_list args
);
/**
* Converts a string to an integer.
*
* @param str The string to convert.
* @param out The output integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToI32(const char_t *str, int32_t *out);
/**
* Converts a string to a signed 16-bit integer.
*
* @param str The string to convert.
* @param out The output signed integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToI16(const char_t *str, int16_t *out);
/**
* Converts a string to an unsigned 16-bit integer.
*
* @param str The string to convert.
* @param out The output unsigned integer.
* @return TRUE if the conversion was successful, FALSE otherwise.
*/
bool_t stringToU16(const char_t *str, uint16_t *out);