diff --git a/client/client.pro b/client/client.pro index 2b5def8..af2791f 100644 --- a/client/client.pro +++ b/client/client.pro @@ -63,6 +63,7 @@ HEADERS = \ $$SHARED/thirdparty/blowfish-api/blowfish.h \ $$SHARED/thirdparty/ini/src/ini.h \ $$SHARED/primes.h \ + $$SHARED/packet.h \ src/config.h \ $$SHARED/util.h \ src/gui/msgbox.h \ @@ -105,6 +106,7 @@ SOURCES = \ $$SHARED/thirdparty/memwatch/memwatch.c \ $$SHARED/thirdparty/blowfish-api/blowfish.c \ $$SHARED/thirdparty/ini/src/ini.c \ + $$SHARED/packet.c \ src/config.c \ $$SHARED/memory.c \ src/gui/msgbox.c \ diff --git a/shared/packet.c b/shared/packet.c new file mode 100644 index 0000000..bbce34a --- /dev/null +++ b/shared/packet.c @@ -0,0 +1,195 @@ +/* + * 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 "packet.h" + + +static uint8_t packetCRC(char *data, uint16_t length, uint8_t startAt); + + +static uint8_t packetCRC(char *data, uint16_t length, uint8_t startAt) { + uint16_t x = 0; + + for (x=0; x 0) { + for (x=0; xdecodeQueue[threadData->decodeQueueHead++] = input[x]; + if (threadData->decodeQueueHead >= PACKET_INPUT_QUEUE_SIZE) { + threadData->decodeQueueHead = 0; + } + } + } + + while (1) { + // Do we have data to process? + if (threadData->decodeQueueHead == threadData->decodeQueueTail) return 0; + + // Get next byte. + c = threadData->decodeQueue[threadData->decodeQueueTail++]; + if (threadData->decodeQueueTail >= PACKET_INPUT_QUEUE_SIZE) { + threadData->decodeQueueTail = 0; + } + + // New packet? + if (threadData->newPacket) { + threadData->newPacket = 0; + threadData->inEscape = 0; + data->length = 0; + } + + // Are we escaped? + if (threadData->inEscape) { + threadData->inEscape = 0; + // Is this the end of the packet? + if (c != PACKET_FRAME) { + threadData->newPacket = 1; + + // Check CRC. + if (threadData->decodeBuffer[data->length - 1] != packetCRC(threadData->decodeBuffer, data->length - 1, 0)) continue; + + // Fill decoded data fields. + data->packetType = threadData->decodeBuffer[1]; + data->channel = threadData->decodeBuffer[2]; + + // ***TODO*** Blowfish Decryption. + if (threadData->decodeBuffer[0] & 32) { + + } + + // Copy packet data to new buffer. + data->data = (char *)malloc(data->length - 4); // 4 for 3 byte header and 1 byte CRC. + if (!data) continue; + memcpy(data->data, &threadData->decodeBuffer[3], data->length - 4); // Skip header and CRC. + break; + } + } else { + // Are we escaping? + if (c == PACKET_FRAME) { + // Yes. Don't add this byte. + threadData->inEscape = 1; + continue; + } + // Did we overflow? + if (data->length >= PACKET_MAX) { + // Yup. Dump it. + threadData->newPacket = 1; + continue; + } + // Add byte to packet. + threadData->decodeBuffer[data->length++] = c; + } + } + + return 1; +} + + +uint8_t packetEncode(PacketThreadDataT *threadData, PacketEncodeDataT *data, char *input, uint16_t length) { + uint8_t crc = 0; + uint8_t control = 0; + + // Returns 1 on success, 0 on failure. + + // Packet too large? + if (length > PACKET_MAX) return 0; + + data->dataPointer = NULL; + data->length = 0; + data->sequence = threadData->sequence++; + + // Make needed header bytes. + control = (data->control << 6) + (data->encrypt << 5) + (data->sequence & 0x1f); + + // Calculate CRC over header bytes and payload. + crc = packetCRC((char *)&control, 1, crc); + crc = packetCRC((char *)&data->packetType, 1, crc); + crc = packetCRC((char *)&data->channel, 1, crc); + crc = packetCRC(input, length, crc); + + // Add header bytes. + threadData->encodeBuffer[data->length++] = control; + if (control == PACKET_FRAME) threadData->encodeBuffer[data->length++] = PACKET_FRAME; + threadData->encodeBuffer[data->length++] = data->packetType; + if (data->packetType == PACKET_FRAME) threadData->encodeBuffer[data->length++] = PACKET_FRAME; + threadData->encodeBuffer[data->length++] = data->channel; + if (data->channel == PACKET_FRAME) threadData->encodeBuffer[data->length++] = PACKET_FRAME; + + // ***TODO*** Blowfish Encryption. + if (data->encrypt) { + + } + + // Add payload. + while (length--) { + // Is this a frame character? If so, escape it. + if (*input == PACKET_FRAME) threadData->encodeBuffer[data->length++] = PACKET_FRAME; + // Add data. + threadData->encodeBuffer[data->length++] = *input++; + } + + // Add CRC. + threadData->encodeBuffer[data->length++] = crc; + if (crc == PACKET_FRAME) threadData->encodeBuffer[data->length++] = PACKET_FRAME; + + // Mark end of packet. + threadData->encodeBuffer[data->length++] = PACKET_FRAME; + threadData->encodeBuffer[data->length++] = 0; + + return 1; +} + + +PacketThreadDataT *packetThreadDataCreate(void) { + PacketThreadDataT *data = NULL; + + data = (PacketThreadDataT *)malloc(sizeof(PacketThreadDataT)); + if (data) { + data->sequence = 0; + data->decodeQueueHead = 0; + data->decodeQueueTail = 0; + data->newPacket = 1; + } + + return data; +} + + +void packetThreadDataDestroy(PacketThreadDataT **data) { + PacketThreadDataT *d = *data; + + free(d); + d = NULL; + *data = d; +} diff --git a/shared/packet.h b/shared/packet.h new file mode 100644 index 0000000..8d69a72 --- /dev/null +++ b/shared/packet.h @@ -0,0 +1,97 @@ +/* + * 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 . + * + */ + + +#ifndef PACKET_H +#define PACKET_H + + +#include "os.h" + + +/* + * Packet format (without framing): + * + * Byte 01: cces ssss - Two control bits, one encryption bit, five sequence bits. + * Byte 02: pppp pppp - Eight bits, packet type. + * Byte 03: cccc cccc - Eight bits, channel. + * Byte 04: dddd dddd \ + * Byte ..: dddd dddd > Packet payload. + * Byte X0: dddd dddd / + * Byte X1: cccc cccc - Checksum + * + */ + + +#define PACKET_MAX 1024 // Maximum number of bytes per packet +#define PACKET_SEQUENCE_MAX 32 // Five bits of data + +#define PACKET_INPUT_QUEUE_SIZE 4096 +#define PACKET_BUFFER_SIZE (PACKET_MAX * 2 + 2 + 8) // Worst case, every byte is a PACKET_FRAME. Add two for ending frame. Add 8 for worst case header and CRC. + +#define PACKET_FRAME 0x7e + +#define PACKET_CONTROL_DAT 0 // Regular data packet. +#define PACKET_CONTROL_RTX 1 // Retransmission packet. +#define PACKET_CONTROL_NAK 2 // Negative acknowledge. + +#define PACKET_TYPE_NONE 0 // No packet. + + +typedef struct PacketDecodeDataS { + // Output + uint8_t packetType; // One of PACKET_TYPE_* + char *data; // Buffer received. MUST FREE. + uint16_t length; // Length of buffer. + uint8_t channel; // Which data channel it arrived on. +} PacketDecodeDataT; + +typedef struct PacketEncodeDataS { + // Input + uint8_t control; // One of PACKET_CONTROL_* + uint8_t packetType; // One of PACKET_TYPE_* + uint8_t channel; // Which data channel to use. + uint8_t encrypt; // Do we want the packet encrypted? + // Input & Output + uint16_t sequence; // For NAK packets, input the sequence number we missed. Other packets returns the sequence of this packet. + // Output + char *dataPointer; // Buffer ready to send. DO NOT FREE. + uint16_t length; // Length of buffer. +} PacketEncodeDataT; + +typedef struct PacketThreadDataS { + // Internal state per thread for packet processing. + uint8_t sequence; + char decodeBuffer[PACKET_MAX]; + char encodeBuffer[PACKET_BUFFER_SIZE]; + char decodeQueue[PACKET_INPUT_QUEUE_SIZE]; + uint16_t decodeQueueHead; + uint16_t decodeQueueTail; + uint8_t inEscape; + uint8_t newPacket; +} PacketThreadDataT; + + +uint8_t packetDecode(PacketThreadDataT *threadData, PacketDecodeDataT *data, char *input, uint16_t length); +uint8_t packetEncode(PacketThreadDataT *threadData, PacketEncodeDataT *data, char *input, uint16_t length); +PacketThreadDataT *packetThreadDataCreate(void); +void packetThreadDataDestroy(PacketThreadDataT **data); + + +#endif // PACKET_H