195 lines
5.7 KiB
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);
|
|
}
|