Partially finished client

This commit is contained in:
2025-04-09 17:21:49 -05:00
parent 6c71debd05
commit 1b336ff559
19 changed files with 664 additions and 47 deletions

View File

@ -24,6 +24,7 @@ target_sources(${DUSK_TARGET_NAME}
# Subdirs # Subdirs
add_subdirectory(assert) add_subdirectory(assert)
add_subdirectory(client)
add_subdirectory(console) add_subdirectory(console)
add_subdirectory(error) add_subdirectory(error)
add_subdirectory(server) add_subdirectory(server)

View File

@ -10,3 +10,4 @@ target_sources(${DUSK_TARGET_NAME}
) )
# Subdirs # Subdirs
add_subdirectory(networked)

View File

@ -0,0 +1,82 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "client.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
#include "server/server.h"
client_t CLIENT;
void cmdJoin(const consolecmdexec_t *exec) {
clientconnect_t connect;
if(exec->argc > 0) {
connect.type = CLIENT_TYPE_NETWORKED;
connect.networked.port = SERVER_DEFAULT_PORT;
} else {
connect.type = CLIENT_TYPE_SINGLE_PLAYER;
}
errorret_t ret = clientConnect(connect);
if(ret == ERROR_OK) {
consolePrint("Connected to server");
} else {
consolePrint("Failed to connect to server: %s", errorString());
}
errorFlush();
}
void cmdLeave(const consolecmdexec_t *exec) {
clientDisconnect();
}
void clientInit() {
memoryZero(&CLIENT, sizeof(client_t));
consoleRegCmd("join", cmdJoin);
consoleRegCmd("leave", cmdLeave);
}
errorret_t clientConnect(const clientconnect_t connect) {
errorret_t ret;
if(CLIENT.state != CLIENT_STATE_DISCONNECTED) {
return error("Client is already connected");
}
CLIENT.type = connect.type;
switch(connect.type) {
case CLIENT_TYPE_NETWORKED:
ret = networkedClientConnect(&CLIENT, connect.networked);
break;
default:
assertUnreachable("Invalid client type");
}
if(ret != ERROR_OK) CLIENT.state = CLIENT_STATE_DISCONNECTED;
return ret;
}
void clientDisconnect() {
if(CLIENT.state == CLIENT_STATE_DISCONNECTED) return;
switch(CLIENT.type) {
case CLIENT_TYPE_NETWORKED:
networkedClientDisconnect(&CLIENT);
break;
default:
assertUnreachable("Invalid client type");
}
}
void clientDispose() {
clientDisconnect();
}

View File

@ -6,3 +6,59 @@
*/ */
#pragma once #pragma once
#include "client/networked/networkedclient.h"
typedef enum {
CLIENT_TYPE_NETWORKED,
CLIENT_TYPE_SINGLE_PLAYER,
} clienttype_t;
typedef enum {
CLIENT_STATE_DISCONNECTED,
CLIENT_STATE_CONNECTING,
CLIENT_STATE_CONNECTED,
CLIENT_STATE_DISCONNECTING,
} clientstate_t;
typedef struct clientconnect_s {
clienttype_t type;
union {
networkedclientconnect_t networked;
};
} clientconnect_t;
typedef struct client_s {
clientstate_t state;
clienttype_t type;
union {
networkedclient_t networked;
};
} client_t;
extern client_t CLIENT;
/**
* Initializes the client.
*
* @return Error code indicating success or failure.
*/
void clientInit();
/**
* Connects to a server.
*
* @param connect Connection information.
* @return Error code indicating success or failure.
*/
errorret_t clientConnect(const clientconnect_t connect);
/**
* Disconnects the client from the server.
*/
void clientDisconnect();
/**
* Cleans up the client resources.
*/
void clientDispose();

View File

@ -0,0 +1,10 @@
# 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
networkedclient.c
)

View File

@ -0,0 +1,261 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "client/client.h"
#include "assert/assert.h"
#include "console/console.h"
errorret_t networkedClientConnect(
client_t *client,
const networkedclientconnect_t connInfo
) {
int32_t ret;
packet_t packet;
errorret_t err;
char_t *ip = "127.0.0.1";
assertNotNull(client, "Client is NULL");
assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked");
assertIsMainThread("Client connect must be on main thread");
client->state = CLIENT_STATE_CONNECTING;
consolePrint("Connecting to server %s:%d", ip, connInfo.port);
// Create a socket
client->networked.socket = socket(AF_INET, SOCK_STREAM, 0);
if(client->networked.socket < 0) {
return error("Failed to create socket %s", strerror(errno));
}
// Set ip address and port
client->networked.address.sin_family = AF_INET;
client->networked.address.sin_port = htons(connInfo.port);
client->networked.address.sin_addr.s_addr = inet_addr(ip);
ret = inet_pton(AF_INET, ip, &client->networked.address.sin_addr);
if(ret <= 0) {
close(client->networked.socket);
return error("Invalid or bad IP address %s: %s", ip, strerror(errno));
}
// Connect to the server
ret = connect(
client->networked.socket,
(struct sockaddr *)&client->networked.address,
sizeof(client->networked.address)
);
if(ret < 0) {
close(client->networked.socket);
switch(errno) {
case ECONNREFUSED:
return error("Failed to connect: Connection refused");
case ETIMEDOUT:
return error("Failed to connect: Connection timed out");
case ENETUNREACH:
return error("Failed to connect: Network unreachable");
default:
return error("Failed to connect: Unknown error");
}
}
// Send the version
{
const char_t *message = "DUSK|"DUSK_VERSION;
ssize_t sent = send(
client->networked.socket,
message,
strlen(message),
0
);
}
// We should now receive a welcome packet
err = networkedClientReadPacket(client, &packet);
if(err) return err;
switch(packet.type) {
case PACKET_TYPE_DISCONNECT:
err = packetDisconnectClient(&packet);
if(err) return err;
break;
case PACKET_TYPE_WELCOME:
err = packetWelcomeClient(&packet);
if(err) return err;
break;
default:
return error("Server did not send welcome message.");
}
// Connection was established, hand off to thread
ret = pthread_create(
&client->networked.thread,
NULL,
clientThread,
client
);
if(ret != 0) {
close(client->networked.socket);
return error("Failed to create client thread %s", strerror(errno));
}
// Wait for the thread to start
while(client->state == CLIENT_STATE_CONNECTING) {
usleep(1000);
}
return ERROR_OK;
}
void networkedClientDisconnect(client_t *client) {
assertNotNull(client, "Client is NULL");
assertIsMainThread("Client disconnect must be on main thread");
assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked");
assertTrue(client->state == CLIENT_STATE_CONNECTED, "Client not connected");
client->state = CLIENT_STATE_DISCONNECTING;
int32_t maxAttempts = 0;
while(client->state == CLIENT_STATE_DISCONNECTING) {
usleep(1000);
maxAttempts++;
if(maxAttempts > 15) {
consolePrint("Client disconnect timed out, force closing");
break;
}
}
client->state = CLIENT_STATE_DISCONNECTED;
if(client->networked.thread) {
pthread_join(client->networked.thread, NULL);
client->networked.thread = 0;
}
if(client->networked.socket) {
shutdown(client->networked.socket, SHUT_RDWR);
close(client->networked.socket);
client->networked.socket = 0;
}
consolePrint("Client disconnected");
}
errorret_t networkedClientWrite(
const client_t *client,
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");
assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked");
if(client->state == CLIENT_STATE_DISCONNECTED) {
return error("Client is disconnected");
}
ssize_t sent = send(client->networked.socket, data, len, 0);
if(sent < 0) return error("Failed to send data");
return ERROR_OK;
}
errorret_t networkedClientWritePacket(
const client_t *client,
const packet_t *packet
) {
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 networkedClientWrite(client, (const uint8_t *)packet, fullSize);
}
errorret_t networkedClientReadPacket(
const client_t *client,
packet_t *packet
) {
uint8_t buffer[sizeof(packet_t)];
assertNotNull(client, "Client is NULL");
assertNotNull(packet, "Packet is NULL");
assertTrue(client->type == CLIENT_TYPE_NETWORKED, "Client is not networked");
if(client->state == CLIENT_STATE_DISCONNECTED) {
return error("Client is disconnected");
}
// Read the packet header
ssize_t read = recv(
client->networked.socket,
buffer,
sizeof(packettype_t),
0
);
if(read != sizeof(packettype_t)) {
return error("Failed to read packet header %s", strerror(errno));
}
packet->type = *(packettype_t *)buffer;
// Read the packet length
read = recv(
client->networked.socket,
buffer,
sizeof(uint32_t),
0
);
if(read != sizeof(uint32_t)) {
return error("Failed to read packet length %s", strerror(errno));
}
if(read > sizeof(packetdata_t)) {
return error("Packet length is too large");
}
packet->length = *(uint32_t *)buffer;
// Now, read the packet data
read = recv(
client->networked.socket,
(uint8_t *)&packet->data,
packet->length,
0
);
if(read != packet->length) {
return error("Failed to read packet data %s", strerror(errno));
}
return ERROR_OK;
}
void * clientThread(void *arg) {
assertNotNull(arg, "Client thread argument is NULL");
assertNotMainThread("Client thread must not be on main thread");
client_t *client = (client_t *)arg;
assertTrue(
client->type == CLIENT_TYPE_NETWORKED,
"Client thread argument is not networked"
);
assertTrue(
client->state == CLIENT_STATE_CONNECTING,
"Client thread argument is not connecting"
);
client->state = CLIENT_STATE_CONNECTED;
while(client->state == CLIENT_STATE_CONNECTED) {
usleep(1000 * 1000);
}
printf("Client thread exiting\n");
}

View File

@ -0,0 +1,85 @@
/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "error/error.h"
#include <arpa/inet.h>
#include "server/packet/packet.h"
typedef struct client_s client_t;
typedef struct clientconnect_s clientconnect_t;
typedef struct {
uint16_t port;
} networkedclientconnect_t;
typedef struct {
int32_t socket;
struct sockaddr_in address;
pthread_t thread;
} networkedclient_t;
/**
* Connects to a networked server.
*
* @param client Pointer to the client structure.
* @param connect Connection information.
*/
errorret_t networkedClientConnect(
client_t *client,
const networkedclientconnect_t connect
);
/**
* Closes the connection to a networked server.
*
* @param client Pointer to the client structure.
*/
void networkedClientDisconnect(client_t *client);
/**
* Writes data to the networked server.
*
* @param client Pointer to the client structure.
* @param data Data to write.
* @param len Length of the data.
* @return Error code.
*/
errorret_t networkedClientWrite(
const client_t *client,
const uint8_t *data,
const size_t len
);
/**
* Writes a packet to the networked server.
*
* @param client Pointer to the client structure.
* @param packet Pointer to the packet structure.
* @return Error code.
*/
errorret_t networkedClientWritePacket(
const client_t *client,
const packet_t *packet
);
/**
* Reads a packet from the networked server.
*
* @param client Pointer to the client structure.
* @param packet Pointer to the packet structure to read into.
* @return Error code.
*/
errorret_t networkedClientReadPacket(const client_t *client, packet_t *packet);
/**
* Thread function for handling networked client connections.
*
* @param arg Pointer to the client structure.
* @return NULL.
*/
void * networkedClientThread(void *arg);

View File

@ -6,6 +6,7 @@
*/ */
#include "console/console.h" #include "console/console.h"
#include "client/client.h"
#include "server/server.h" #include "server/server.h"
#include "util/string.h" #include "util/string.h"
#include "assert/assert.h" #include "assert/assert.h"
@ -16,41 +17,13 @@ void cmdExit(const consolecmdexec_t *exec) {
exitRequested = true; exitRequested = true;
} }
void cmdServe(const consolecmdexec_t *exec) {
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((serverstart_t){
.type = SERVER_TYPE_NETWORKED,
.networked = {
.port = 3030
}
});
if(ret != ERROR_OK) {
consolePrint("Failed to start server: %s", errorString());
errorFlush();
return;
}
}
void cmdClose(const consolecmdexec_t *exec) {
serverStop();
}
int main(void) { int main(void) {
assertInit(); assertInit();
consoleInit(); consoleInit();
clientInit();
serverInit(); serverInit();
consoleRegCmd("exit", cmdExit); consoleRegCmd("exit", cmdExit);
consoleRegCmd("serve", cmdServe);
consoleRegCmd("close", cmdClose);
InitWindow(1280, 720, DUSK_NAME); InitWindow(1280, 720, DUSK_NAME);
@ -68,7 +41,9 @@ int main(void) {
} }
CloseWindow(); CloseWindow();
serverDispose(); serverDispose();
clientDispose();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }

View File

@ -10,6 +10,10 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
typedef struct {
uint16_t port;
} networkedserverstart_t;
typedef struct { typedef struct {
int socket; int socket;
struct sockaddr_in address; struct sockaddr_in address;

View File

@ -207,6 +207,7 @@ void * networkedServerClientThread(void *arg) {
return NULL; return NULL;
} }
buffer[read] = '\0'; // Null-terminate the string buffer[read] = '\0'; // Null-terminate the string
if(strncmp(buffer, expecting, strlen(expecting)) != 0) { if(strncmp(buffer, expecting, strlen(expecting)) != 0) {
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION); packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
@ -238,17 +239,16 @@ void * networkedServerClientThread(void *arg) {
if(SERVER.state != SERVER_STATE_RUNNING) { if(SERVER.state != SERVER_STATE_RUNNING) {
networkedServerClientCloseOnThread(client, "Server is shutting down");
break;
}
}
packetDisconnectCreate( packetDisconnectCreate(
&packet, &packet,
PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN
); );
networkedServerClientWritePacket(client, &packet); networkedServerClientWritePacket(client, &packet);
if(errorCheck()) errorPrint(); if(errorCheck()) errorPrint();
networkedServerClientCloseOnThread(client, "Server is shutting down");
break;
}
}
client->state = SERVER_CLIENT_STATE_DISCONNECTED; client->state = SERVER_CLIENT_STATE_DISCONNECTED;
return NULL; return NULL;

View File

@ -7,6 +7,7 @@
#include "packet.h" #include "packet.h"
#include "util/memory.h" #include "util/memory.h"
#include "assert/assert.h"
void packetDisconnectCreate( void packetDisconnectCreate(
packet_t *packet, packet_t *packet,
@ -15,3 +16,31 @@ void packetDisconnectCreate(
packetInit(packet, PACKET_TYPE_DISCONNECT, sizeof(packetdisconnect_t)); packetInit(packet, PACKET_TYPE_DISCONNECT, sizeof(packetdisconnect_t));
packet->data.disconnect.reason = reason; packet->data.disconnect.reason = reason;
} }
errorret_t packetDisconnectClient(packet_t *packet) {
assertNotNull(packet, "Packet is NULL");
assertTrue(
packet->type == PACKET_TYPE_DISCONNECT,
"Packet type is not DISCONNECT"
);
if(packet->length != sizeof(packetdisconnect_t)) {
return error("Disconnect packet length is not correct");
}
packetdisconnect_t *data = (packetdisconnect_t *)&packet->data;
switch(data->reason) {
case PACKET_DISCONNECT_REASON_UNKNOWN:
return error("Server disconnected: Unknown reason");
case PACKET_DISCONNECT_REASON_INVALID_VERSION:
return error("Server disconnected: Invalid version");
case PACKET_DISCONNECT_REASON_MALFORMED_PACKET:
return error("Server disconnected: Malformed packet");
case PACKET_DISCONNECT_REASON_SERVER_FULL:
return error("Server disconnected: Server full");
case PACKET_DISCONNECT_REASON_SERVER_SHUTDOWN:
return error("Server disconnected: Server shutdown");
default:
return error("Server disconnected: Unknown reason");
}
}

View File

@ -6,7 +6,7 @@
*/ */
#pragma once #pragma once
#include "dusk.h" #include "error/error.h"
typedef struct packet_s packet_t; typedef struct packet_s packet_t;
@ -32,3 +32,11 @@ void packetDisconnectCreate(
packet_t *packet, packet_t *packet,
const packetdisconnectreason_t reason const packetdisconnectreason_t reason
); );
/**
* Handles a disconnect packet received FROM a server INTO a client.
*
* @param packet Pointer to the packet structure to handle.
* @return ERROR_OK on success, or an error code on failure.
*/
errorret_t packetDisconnectClient(packet_t *packet);

View File

@ -7,6 +7,7 @@
#include "packet.h" #include "packet.h"
#include "util/memory.h" #include "util/memory.h"
#include "assert/assert.h"
void packetWelcomeCreate(packet_t *packet) { void packetWelcomeCreate(packet_t *packet) {
packetInit(packet, PACKET_TYPE_WELCOME, PACKET_WELCOME_SIZE); packetInit(packet, PACKET_TYPE_WELCOME, PACKET_WELCOME_SIZE);
@ -14,3 +15,22 @@ void packetWelcomeCreate(packet_t *packet) {
packet->data.welcome.dusk, PACKET_WELCOME_STRING, PACKET_WELCOME_SIZE packet->data.welcome.dusk, PACKET_WELCOME_STRING, PACKET_WELCOME_SIZE
); );
} }
errorret_t packetWelcomeClient(packet_t *packet) {
assertNotNull(packet, "Packet is NULL");
assertTrue(packet->type == PACKET_TYPE_WELCOME, "Packet type is not WELCOME");
if(packet->length != PACKET_WELCOME_SIZE) {
return error("Welcome packet length is not %d", PACKET_WELCOME_SIZE);
}
if(
memoryCompare(
packet->data.welcome.dusk, PACKET_WELCOME_STRING, PACKET_WELCOME_SIZE
) != 0
) {
return error("Welcome packet data is not %s", PACKET_WELCOME_STRING);
}
return ERROR_OK;
}

View File

@ -6,7 +6,7 @@
*/ */
#pragma once #pragma once
#include "dusk.h" #include "error/error.h"
typedef struct packet_s packet_t; typedef struct packet_s packet_t;
@ -23,3 +23,11 @@ typedef struct {
* @param packet Pointer to the packet structure to initialize. * @param packet Pointer to the packet structure to initialize.
*/ */
void packetWelcomeCreate(packet_t *packet); void packetWelcomeCreate(packet_t *packet);
/**
* Handles a welcome packet received FROM a server INTO a client.
*
* @param packet Pointer to the packet structure to handle.
* @return ERROR_OK on success, or an error code on failure.
*/
errorret_t packetWelcomeClient(packet_t *packet);

View File

@ -9,15 +9,61 @@
#include "util/memory.h" #include "util/memory.h"
#include "assert/assert.h" #include "assert/assert.h"
#include "console/console.h" #include "console/console.h"
#include "util/string.h"
server_t SERVER; server_t SERVER;
void cmdStart(const consolecmdexec_t *exec) {
serverstart_t start;
if(exec->argc > 0) {
if(stringCompare(exec->argv[1], "0") == 0) {
start.type = SERVER_TYPE_SINGLE_PLAYER;
} else if(stringCompare(exec->argv[0], "1") == 0) {
start.type = SERVER_TYPE_NETWORKED;
} else {
consolePrint("Invalid server type: %s", exec->argv[0]);
return;
}
} else {
start.type = SERVER_TYPE_SINGLE_PLAYER;
}
if(exec->argc > 1) {
if(start.type == SERVER_TYPE_NETWORKED) {
if(!stringToU16(exec->argv[1], &start.networked.port)) {
consolePrint("Invalid port number: %s", exec->argv[0]);
return;
}
}
} else {
if(start.type == SERVER_TYPE_NETWORKED) {
start.networked.port = SERVER_DEFAULT_PORT;
}
}
errorret_t ret = serverStart(start);
if(ret != ERROR_OK) {
consolePrint("Failed to start server: %s", errorString());
errorFlush();
return;
}
}
void cmdClose(const consolecmdexec_t *exec) {
serverStop();
}
void serverInit() { void serverInit() {
memoryZero(&SERVER, sizeof(server_t)); memoryZero(&SERVER, sizeof(server_t));
SERVER.state = SERVER_STATE_STOPPED; SERVER.state = SERVER_STATE_STOPPED;
consoleRegCmd("start", cmdStart);
consoleRegCmd("close", cmdClose);
} }
errorret_t serverStart(const serverstart_t start) { errorret_t serverStart(const serverstart_t start) {
errorret_t ret;
assertIsMainThread("Server start must be on main thread"); assertIsMainThread("Server start must be on main thread");
// Do not start a running server. // Do not start a running server.
@ -30,12 +76,15 @@ errorret_t serverStart(const serverstart_t start) {
// Hand off to relevant server type to start. // Hand off to relevant server type to start.
switch(start.type) { switch(start.type) {
case SERVER_TYPE_NETWORKED: case SERVER_TYPE_NETWORKED:
return networkedServerStart(&SERVER, start); ret = networkedServerStart(&SERVER, start);
break; break;
default: default:
assertUnreachable("Invalid server type"); assertUnreachable("Invalid server type");
} }
if(ret != ERROR_OK) SERVER.state = SERVER_STATE_STOPPED;
return ret;
} }
uint8_t serverGetClientCount() { uint8_t serverGetClientCount() {

View File

@ -10,6 +10,7 @@
#include "server/networked/networkedserver.h" #include "server/networked/networkedserver.h"
#define SERVER_MAX_CLIENTS 32 #define SERVER_MAX_CLIENTS 32
#define SERVER_DEFAULT_PORT 3030
typedef enum { typedef enum {
SERVER_STATE_STOPPED, SERVER_STATE_STOPPED,
@ -26,9 +27,7 @@ typedef enum {
typedef struct serverstart_s { typedef struct serverstart_s {
servertype_t type; servertype_t type;
union { union {
struct { networkedserverstart_t networked;
uint16_t port;
} networked;
}; };
} serverstart_t; } serverstart_t;

View File

@ -14,6 +14,7 @@ errorret_t serverClientAccept(
serverclient_t *client, serverclient_t *client,
const serverclientaccept_t accept const serverclientaccept_t accept
) { ) {
errorret_t ret;
memoryZero(client, sizeof(serverclient_t)); memoryZero(client, sizeof(serverclient_t));
assertNotNull(accept.server, "Server is NULL"); assertNotNull(accept.server, "Server is NULL");
assertNotMainThread("Server client accept must not be main thread"); assertNotMainThread("Server client accept must not be main thread");
@ -22,11 +23,14 @@ errorret_t serverClientAccept(
switch(accept.server->type) { switch(accept.server->type) {
case SERVER_TYPE_NETWORKED: case SERVER_TYPE_NETWORKED:
return networkedServerClientAccept(client, accept); ret = networkedServerClientAccept(client, accept);
default: default:
assertUnreachable("Unknown server type"); assertUnreachable("Unknown server type");
} }
if(ret != ERROR_OK) memoryZero(client, sizeof(serverclient_t));
return ret;
} }
void serverClientClose(serverclient_t *client) { void serverClientClose(serverclient_t *client) {

View File

@ -58,3 +58,14 @@ void memoryMove(void *dest, const void *src, const size_t size) {
assertTrue(dest != src, "Cannot move memory to itself."); assertTrue(dest != src, "Cannot move memory to itself.");
memmove(dest, src, size); memmove(dest, src, size);
} }
ssize_t memoryCompare(
const void *a,
const void *b,
const size_t size
) {
assertNotNull(a, "Cannot compare NULL memory.");
assertNotNull(b, "Cannot compare NULL memory.");
assertTrue(size > 0, "Cannot compare 0 bytes of memory.");
return memcmp(a, b, size);
}

View File

@ -74,3 +74,17 @@ void memoryCopyRangeSafe(
* @param size The size of the memory to move. * @param size The size of the memory to move.
*/ */
void memoryMove(void *dest, const void *src, const size_t size); void memoryMove(void *dest, const void *src, const size_t size);
/**
* Compares memory.
*
* @param a The first memory to compare.
* @param b The second memory to compare.
* @param size The size of the memory to compare.
* @return 0 if the memory is equal, < 0 if a < b, > 0 if a > b.
*/
ssize_t memoryCompare(
const void *a,
const void *b,
const size_t size
);