/* * 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 . * */ #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); }