dusk/src/server/networked/networkedserverclient.c

339 lines
9.5 KiB
C

/**
* Copyright (c) 2025 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "server/server.h"
#include "assert/assert.h"
#include "util/memory.h"
#include "console/console.h"
#include <sys/socket.h>
errorret_t networkedServerClientAccept(
serverclient_t *client,
const serverclientaccept_t accept
) {
assertNotNull(client, "Client is NULL");
assertNotNull(accept.server, "Server is NULL");
assertTrue(
accept.server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
client->state = SERVER_CLIENT_STATE_ACCEPTING;
client->networked.socket = accept.networked.socket;
// Set timeout to 8 seconds
client->networked.timeout.tv_sec = 8;
client->networked.timeout.tv_usec = 0;
// Initialize mutexs
pthread_mutex_init(&client->networked.readLock, NULL);
pthread_mutex_init(&client->networked.writeLock, NULL);
// Create a read thread for the client
int32_t ret = pthread_create(
&client->networked.readThread,
NULL,
networkedServerClientReadThread,
client
);
if(ret != 0) {
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
return error("Failed to create client read thread");
}
// Set socket timeout
if(setsockopt(
client->networked.socket,
SOL_SOCKET,
SO_RCVTIMEO,
&client->networked.timeout,
sizeof(client->networked.timeout)
) < 0) {
networkedServerClientCloseOnThread(client, "Failed to set socket timeout");
return error("Failed to set socket timeout");
}
return ERROR_OK;
}
void networkedServerClientClose(serverclient_t *client) {
assertIsMainThread("Server client close must be on main thread.");
assertNotNull(client, "Client is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
// Mark client as disconnecting
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
// Wait for the read thread to finish
pthread_mutex_lock(&client->networked.readLock);
pthread_mutex_unlock(&client->networked.readLock);
pthread_join(client->networked.readThread, NULL);
client->networked.readThread = 0;
pthread_mutex_destroy(&client->networked.readLock);
// Signal and wait for the write thread to finish
pthread_mutex_lock(&client->networked.writeLock);
pthread_mutex_unlock(&client->networked.writeLock);
pthread_join(client->networked.writeThread, NULL);
client->networked.writeThread = 0;
pthread_mutex_destroy(&client->networked.writeLock);
// Close the socket
if(client->networked.socket != -1) {
close(client->networked.socket);
client->networked.socket = -1;
}
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
consolePrint("Client %d disconnected.", client->networked.socket);
}
void networkedServerClientCloseOnThread(
serverclient_t *client,
const char_t *reason
) {
assertNotNull(client, "Client is NULL");
assertNotNull(reason, "Reason is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertNotMainThread("Client close must not be main thread");
client->state = SERVER_CLIENT_STATE_DISCONNECTING;
// Terminate the socket
close(client->networked.socket);
client->networked.socket = -1;
client->networked.readThread = 0;
consolePrint("Client %d disconnected: %s", client->networked.socket, reason);
// Mark this client as disconnected so it can be used again.
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
pthread_exit(NULL);
}
ssize_t networkedServerClientRead(
const serverclient_t * client,
uint8_t *buffer,
const size_t len
) {
assertNotNull(client, "Client is NULL");
assertNotNull(buffer, "Buffer is NULL");
assertNotNull(client->server, "Server is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertTrue(len > 0, "Buffer length is 0");
assertTrue(
client->state == SERVER_CLIENT_STATE_CONNECTED ||
client->state == SERVER_CLIENT_STATE_ACCEPTING ||
client->state == SERVER_CLIENT_STATE_DISCONNECTING,
"Client is not connected, accepting or disconnecting"
);
return recv(client->networked.socket, buffer, len, 0);
}
errorret_t networkedServerClientReadPacket(
const serverclient_t * client,
packet_t *packet
) {
uint8_t buffer[sizeof(packet_t)];
ssize_t read;
assertNotNull(client, "Client is NULL");
assertNotNull(packet, "Packet is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
// Read packet ID
read = networkedServerClientRead(client, buffer, sizeof(packettype_t));
if(read != sizeof(packettype_t)) {
return error("Failed to read packet ID");
}
packet->type = *(packettype_t *)buffer;
if(packet->type == PACKET_TYPE_INVALID) {
return error("Invalid packet type");
}
// Read length
read = networkedServerClientRead(
client,
buffer,
sizeof(uint32_t)
);
if(read != sizeof(uint32_t)) {
return error("Failed to read packet length");
}
packet->length = *(uint32_t *)buffer;
if(packet->length > sizeof(packetdata_t)) {
return error("Packet length is too large");
}
// Read data
read = networkedServerClientRead(
client,
(uint8_t *)&packet->data,
packet->length
);
if(read != packet->length) {
return error("Failed to read packet data");
}
return ERROR_OK;
}
errorret_t networkedServerClientWrite(
serverclient_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");
assertNotNull(client->server, "Server is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
if(client->state == SERVER_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 networkedServerClientWritePacket(
serverclient_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 networkedServerClientWrite(client, (const uint8_t *)packet, fullSize);
}
void * networkedServerClientReadThread(void *arg) {
assertNotNull(arg, "Client is NULL");
assertNotMainThread("Client thread must not be main thread");
serverclient_t *client = (serverclient_t *)arg;
char_t buffer[1024];
ssize_t read;
errorret_t err;
packet_t packet;
assertNotNull(client->server, "Server is NULL");
assertTrue(
client->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertTrue(
client->state == SERVER_CLIENT_STATE_ACCEPTING,
"Client is not accepting?"
);
// First message from the client should always be "DUSK|VERSION" to match
// the server version.
{
const char_t *expecting = "DUSK|"DUSK_VERSION;
read = networkedServerClientRead(client, buffer, sizeof(buffer));
if(read <= 0) {
packetDisconnectCreate(&packet, PACKET_DISCONNECT_REASON_INVALID_VERSION);
err = networkedServerClientWritePacket(client, &packet);
networkedServerClientCloseOnThread(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 = networkedServerClientWritePacket(client, &packet);
networkedServerClientCloseOnThread(client, "Invalid version");
return NULL;
}
}
// Send DUSK back!
packetWelcomeCreate(&packet);
err = networkedServerClientWritePacket(client, &packet);
if(err != ERROR_OK) {
networkedServerClientCloseOnThread(client, "Failed to send welcome message");
return NULL;
}
// Client is connected.
client->state = SERVER_CLIENT_STATE_CONNECTED;
// Start the write thread after the handshake
int32_t ret = pthread_create(
&client->networked.writeThread,
NULL,
networkedServerClientWriteThread,
client
);
if(ret != 0) {
networkedServerClientCloseOnThread(client, "Failed to create write thread");
return NULL;
}
// Start listening for packets.
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
pthread_mutex_lock(&client->networked.readLock);
pthread_mutex_unlock(&client->networked.readLock);
}
pthread_mutex_lock(&client->networked.readLock);
client->state = SERVER_CLIENT_STATE_DISCONNECTED;
pthread_mutex_unlock(&client->networked.readLock);
return NULL;
}
void * networkedServerClientWriteThread(void *arg) {
assertNotNull(arg, "Client is NULL");
assertNotMainThread("Client thread must not be main thread");
assertTrue(
((serverclient_t *)arg)->server->type == SERVER_TYPE_NETWORKED,
"Server is not networked"
);
assertTrue(
((serverclient_t *)arg)->state == SERVER_CLIENT_STATE_CONNECTED,
"Client is not connected"
);
serverclient_t *client = (serverclient_t *)arg;
while(client->state == SERVER_CLIENT_STATE_CONNECTED) {
pthread_mutex_lock(&client->networked.writeLock);
pthread_mutex_unlock(&client->networked.writeLock);
}
return NULL;
}