kpmpgsmkii/server/src/client.c

195 lines
5.7 KiB
C

/*
* Kangaroo Punch MultiPlayer Game Server Mark II
* Copyright (C) 2020-2021 Scott Duensing
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
#include "client.h"
#include "array.h"
#include "network.h"
#include "console.h"
#include "packet.h"
#include "server.h"
#include "client/login.h"
#include "client/signup.h"
#include "client/pong.h"
#include "client/version.h"
#include "client/shutdown.h"
#include "client/file.h"
typedef void (*clientApi)(ClientThreadT *client, PacketDecodeDataT *data);
// Also update enum in packets.h!
static clientApi _clientApiMethod[PACKET_TYPE_COUNT];
static uint8_t clientDequeuePacket(ClientThreadT *client);
static uint8_t clientDequeuePacket(ClientThreadT *client) {
uint16_t length = 0;
uint8_t *data = NULL;
PacketDecodeDataT decode = { 0 };
ClientRawPacketT *packet = NULL;
// Is there new data to process?
pthread_mutex_lock(&client->packetQueueMutex);
if (arrlenu(client->packetQueue) > 0) {
packet = client->packetQueue[0];
length = packet->length;
data = packet->data;
arrdel(client->packetQueue, 0);
}
pthread_mutex_unlock(&client->packetQueueMutex);
// New data or not, process anything in the queue.
if (packetDecode(client->packetThreadData, &decode, data, length)) {
if (_clientApiMethod[decode.packetType]) {
_clientApiMethod[decode.packetType](client, &decode);
} else {
consoleMessageQueue("%ld: Channel %d Unknown Packet %d\n", client->threadIndex, decode.channel, decode.packetType);
}
if (decode.data) DEL(decode.data);
if (packet) {
DEL(packet->data);
DEL(packet);
}
return 1;
}
// No packet.
return 0;
}
void clientQueuePacket(ClientThreadT *client, uint8_t *data, uint32_t length) {
ClientRawPacketT *packet = NULL;
packet = (ClientRawPacketT *)malloc(sizeof(ClientRawPacketT));
if (packet) {
packet->data = (uint8_t *)malloc(length);
if (packet->data) {
memcpy(packet->data, data, length);
packet->length = length;
}
pthread_mutex_lock(&client->packetQueueMutex);
arrput(client->packetQueue, packet);
pthread_mutex_unlock(&client->packetQueueMutex);
}
}
void clientShutdown(void) {
// Nothing yet.
}
void clientStartup(void) {
memset(_clientApiMethod, 0, sizeof(_clientApiMethod));
_clientApiMethod[PACKET_TYPE_CLIENT_SHUTDOWN] = clientApiClientShutdown;
_clientApiMethod[PACKET_TYPE_FILE_REQUEST] = clientApiFileRequest;
_clientApiMethod[PACKET_TYPE_LOGIN] = clientApiLogin;
_clientApiMethod[PACKET_TYPE_PONG] = clientApiPong;
_clientApiMethod[PACKET_TYPE_SIGNUP] = clientApiSignup;
_clientApiMethod[PACKET_TYPE_VERSION_BAD] = clientApiVersionBad;
_clientApiMethod[PACKET_TYPE_VERSION_OKAY] = clientApiVersionOkay;
}
void *clientThread(void *data) {
ENetPeer *peer = (ENetPeer *)data;
ClientThreadT *client = (ClientThreadT *)peer->data;
PacketEncodeDataT encoded = { 0 };
struct timespec remaining = { 0, 0 };
struct timespec sleepTime = { 0, 1000000000/100 }; // 1/100th second.
uint8_t versionSent = 0;
time_t ticks = { 0 };
time_t lastTicks = { 0 };
int8_t pingTimeout = 0;
uint8_t *packetData = NULL;
uint16_t length = 0;
// Process packets until we're done.
while (client->running) {
if (!clientDequeuePacket(client)) {
// Start communications with client as soon as encryption channel is ready.
if (!versionSent) {
if (packetEncryptionReady()) {
packetData = packetContentPack(&length, "i", PACKET_PROTOCOL_VERSION);
// Send required protocol version.
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_VERSION;
encoded.channel = 0;
encoded.encrypt = 0;
packetEncode(client->packetThreadData, &encoded, packetData, length);
packetSend(client->packetThreadData, &encoded);
DEL(packetData);
versionSent = 1;
consoleMessageQueue("PACKET_TYPE_VERSION sent.\n");
}
}
// Ping the client every 5 seconds.
ticks = time(NULL);
if (ticks != lastTicks) {
lastTicks = ticks;
pingTimeout++;
if (pingTimeout >= 5) {
pingTimeout = 0;
encoded.control = PACKET_CONTROL_DAT;
encoded.packetType = PACKET_TYPE_PING;
encoded.channel = 0;
encoded.encrypt = 0;
packetEncode(client->packetThreadData, &encoded, NULL, 0);
packetSend(client->packetThreadData, &encoded);
clock_gettime(CLOCK_MONOTONIC_RAW, &client->pingStart);
}
}
// Don't eat all the CPU.
nanosleep(&remaining, &sleepTime);
}
}
// ***TODO*** Write ping stats to database.
// Clean up client data on the way out.
// ***TODO*** valgrind says this isn't working.
while (arrlen(client->packetQueue) > 0) {
if (client->packetQueue[0]->data) DEL(client->packetQueue[0]->data);
arrdel(client->packetQueue, 0);
}
arrfree(client->packetQueue);
pthread_mutex_destroy(&client->packetQueueMutex);
packetThreadDataDestroy(&client->packetThreadData);
if (client->handle) fclose(client->handle);
peer = client->peer;
peer->data = NULL;
DEL(client);
pthread_exit(NULL);
}