diff options
| author | fusion32 <marcopuzziello@gmail.com> | 2025-06-19 03:13:48 -0300 |
|---|---|---|
| committer | fusion32 <marcopuzziello@gmail.com> | 2025-06-19 03:13:48 -0300 |
| commit | bd3a6a04601749cbde007eaabf803fb602ee2783 (patch) | |
| tree | 476b89932627aa73ad88de22d37e4617bbdb1795 /src/communication.cc | |
| parent | e5b8aadd493b8b5df4ed661f2491de33d634b001 (diff) | |
| download | game-bd3a6a04601749cbde007eaabf803fb602ee2783.tar.gz game-bd3a6a04601749cbde007eaabf803fb602ee2783.zip | |
beginning of `communication.cc`
Diffstat (limited to 'src/communication.cc')
| -rw-r--r-- | src/communication.cc | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/src/communication.cc b/src/communication.cc new file mode 100644 index 0000000..dc28566 --- /dev/null +++ b/src/communication.cc @@ -0,0 +1,335 @@ +#include "communication.hh" +#include "config.hh" +#include "connections.hh" +#include "containers.hh" +#include "crypto.hh" +#include "query.hh" +#include "threads.hh" + +#include "stubs.hh" + +#include <signal.h> + +#define MAX_COMMUNICATION_THREADS 1100 +#define THREAD_OWN_STACK_SIZE ((int)KB(64)) + +static int TERMINAL_VERSION[3] = {770, 770, 770}; +static int TCPSocket; +static ThreadHandle AcceptorThread; +static pid_t AcceptorThreadPID; +static int ActiveConnections; + +static Semaphore RSAMutex(1); +static TRSAPrivateKey PrivateKey; + +static TQueryManagerConnectionPool QueryManagerConnectionPool(10); +static int LoadHistory[360]; +static int LoadHistoryPointer; +static int TotalLoad; +static int TotalSend; +static int TotalRecv; +static uint32 LagEnd; +static uint32 EarliestFreeAccountAdmissionRound; +static store<TWaitinglistEntry, 100> Waitinglist; +static TWaitinglistEntry *WaitinglistHead; + +static Semaphore CommunicationThreadMutex(1); +static bool UseOwnStacks; +static uint8 CommunicationThreadStacks[MAX_COMMUNICATION_THREADS][THREAD_OWN_STACK_SIZE]; +static pid_t LastUsingCommunicationThread[MAX_COMMUNICATION_THREADS]; +static int FreeCommunicationThreadStacks[MAX_COMMUNICATION_THREADS]; +static int NumberOfFreeCommunicationThreadStacks; + +// Communication Thread Stacks +// ============================================================================= +void GetCommunicationThreadStack(int *StackNumber, void **Stack){ + *StackNumber = -1; + *Stack = NULL; + + if(!UseOwnStacks){ + error("GetCommunicationThreadStack: Bibliothek unterstützt keine eigenen Stacks.\n"); + return; + } + + CommunicationThreadMutex.down(); + for(int i = 0; i < NumberOfFreeCommunicationThreadStacks; i += 1){ + int FreeStack = FreeCommunicationThreadStacks[i]; + if(LastUsingCommunicationThread[FreeStack] == 0 + || kill(LastUsingCommunicationThread[FreeStack], 0) == -1){ + // NOTE(fusion): A little swap and pop action. + NumberOfFreeCommunicationThreadStacks -= 1; + FreeCommunicationThreadStacks[i] = FreeCommunicationThreadStacks[NumberOfFreeCommunicationThreadStacks]; + + *StackNumber = FreeStack; + *Stack = CommunicationThreadStacks[FreeStack]; + break; + } + } + CommunicationThreadMutex.up(); +} + +void AttachCommunicationThreadStack(int StackNumber){ + LastUsingCommunicationThread[StackNumber] = getpid(); +} + +void ReleaseCommunicationThreadStack(int StackNumber){ + CommunicationThreadMutex.down(); + FreeCommunicationThreadStacks[NumberOfFreeCommunicationThreadStacks] = StackNumber; + NumberOfFreeCommunicationThreadStacks += 1; + CommunicationThreadMutex.up(); +} + +void InitCommunicationThreadStacks(void){ + if(UseOwnStacks){ + memset(CommunicationThreadStacks, 0xAA, sizeof(CommunicationThreadStacks)); + for(int i = 0; i < MAX_COMMUNICATION_THREADS; i += 1){ + LastUsingCommunicationThread[i] = 0; + FreeCommunicationThreadStacks[i] = i; + } + NumberOfFreeCommunicationThreadStacks = MAX_COMMUNICATION_THREADS; + } +} + +void ExitCommunicationThreadStacks(void){ + if(UseOwnStacks){ + if(NumberOfFreeCommunicationThreadStacks != MAX_COMMUNICATION_THREADS){ + error("FreeCommunicationThreadStacks: Nicht alle Stacks freigegeben.\n"); + } + + int HighestStackAddress = -1; + int LowestStackAddress = THREAD_OWN_STACK_SIZE; + for(int i = 0; i < MAX_COMMUNICATION_THREADS; i += 1){ + for(int Addr = 0; Addr < THREAD_OWN_STACK_SIZE; Addr += 1){ + if(CommunicationThreadStacks[i][Addr] != 0xAA){ + if(Addr < LowestStackAddress){ + LowestStackAddress = Addr; + } + + if(Addr > HighestStackAddress){ + HighestStackAddress = Addr; + } + } + } + } + + // NOTE(fusion): It seems we want to track whether the stack size is + // too small but I'd argue the method is not very robust. + if((HighestStackAddress - LowestStackAddress) > (THREAD_OWN_STACK_SIZE / 2)){ + error("Maximale Stack-Ausdehnung: %d..%d\n", LowestStackAddress, HighestStackAddress); + } + } +} + +// Load History +// ============================================================================= +void InitLoadHistory(void){ + for(int i = 0; i < NARRAY(LoadHistory); i += 1){ + LoadHistory[i] = 0; + } + LoadHistoryPointer = 0; + TotalLoad = 0; + TotalSend = 0; + TotalRecv = 0; + LagEnd = 0; + EarliestFreeAccountAdmissionRound = 0; + InitLog("netload"); +} + +bool LagDetected(void){ + return RoundNr <= LagEnd; +} + +void NetLoad(int Amount, bool Send){ + CommunicationThreadMutex.down(); + if(Send){ + TotalSend += Amount; + }else{ + TotalRecv += Amount; + } + CommunicationThreadMutex.up(); +} + +void NetLoadSummary(void){ + CommunicationThreadMutex.down(); + Log("netload", "gesendet: %d Bytes.\n", TotalSend); + Log("netload", "empfangen: %d Bytes.\n", TotalRecv); + TotalSend = 0; + TotalRecv = 0; + CommunicationThreadMutex.up(); +} + +void NetLoadCheck(void){ + static int LastRecv; + + int DeltaRecv = TotalRecv - LastRecv; + LastRecv = TotalRecv; + if(DeltaRecv < 0){ + return; + } + + int DeltaRecvPerPlayer = 0; + int PlayersOnline = GetPlayersOnline(); + if(PlayersOnline > 0){ + DeltaRecvPerPlayer = DeltaRecv / PlayersOnline; + } + + TotalLoad -= LoadHistory[LoadHistoryPointer]; + TotalLoad += DeltaRecvPerPlayer; + LoadHistory[LoadHistoryPointer] = DeltaRecvPerPlayer; + LoadHistoryPointer += 1; + if(LoadHistoryPointer >= NARRAY(LoadHistory)){ + LoadHistoryPointer = 0; + } + + // NOTE(fusion): Running this lag check only makes sense if `LoadHistory` + // is filled up which won't be the case until this function executes at + // least as many times as there are entries in `LoadHistory`. + // Looking at `AdvanceGame`, we see that this function is called every + // 10 rounds, giving us the value of `EarliestLagCheckRound`. + constexpr uint32 EarliestLagCheckRound = 10 * NARRAY(LoadHistory); + if(RoundNr >= EarliestLagCheckRound && PlayersOnline >= 50){ + int AvgDeltaRecvPerPlayer = (TotalLoad / NARRAY(LoadHistory)); + if(DeltaRecvPerPlayer < (AvgDeltaRecvPerPlayer / 2)){ + Log("game", "Lag erkannt!\n"); + LagEnd = RoundNr + 30; + + // NOTE(fusion): This formula looks weird but when we take `MaxPlayers` + // and `PremiumPlayerBuffer` to be 950 and 150 (taken from the config + // file, although their values are now loaded from the database), we + // get a line with negative values when the number of players online + // is less than 650, and exactly 60 when it is 950. Since the delay + // is 60 when `PremiumPlayerBuffer` is zero, I don't think this is a + // coincidence. + int FreeAccountAdmissionDelay = 60; + if(PremiumPlayerBuffer != 0){ + FreeAccountAdmissionDelay = (PlayersOnline + PremiumPlayerBuffer * 2 - MaxPlayers); + FreeAccountAdmissionDelay = (FreeAccountAdmissionDelay * 30) / PremiumPlayerBuffer; + if(FreeAccountAdmissionDelay < 0){ + FreeAccountAdmissionDelay = 0; + } + } + + uint32 FreeAccountAdmissionRound = RoundNr + (uint32)FreeAccountAdmissionDelay; + if(EarliestFreeAccountAdmissionRound < FreeAccountAdmissionRound){ + EarliestFreeAccountAdmissionRound = FreeAccountAdmissionRound; + } + + TConnection *Connection = GetFirstConnection(); + while(Connection != NULL){ + if(Connection->Live()){ + Connection->EmergencyPing(); + } + Connection = GetNextConnection(); + } + } + } +} + +// Communication Handling +// ============================================================================= +bool WriteToSocket(TConnection *Connection, uint8 *Buffer, int Size){ + // TODO(fusion): I think `Size` refers to the payload but `Buffer` also has + // room for writing the packet's length at the beginning and enough room for + // padding (supposedly). + + while((Size % 8) != 0){ + Buffer[Size + 2] = rand_r(&Connection->RandomSeed); + Size += 1; + } + + for(int i = 0; i < Size; i += 8){ + Connection->SymmetricKey.encrypt(&Buffer[i + 2]); + } + + TWriteBuffer WriteBuffer(Buffer, 2); + WriteBuffer.writeWord((uint16)Size); + + int Attempts = 50; + int BytesToWrite = Size + 2; + uint8 *WritePtr = Buffer; + while(BytesToWrite > 0){ + int ret = (int)write(Connection->GetSocket(), WritePtr, BytesToWrite); + if(ret > 0){ + BytesToWrite -= ret; + WritePtr += ret; + }else if(ret == 0){ + // TODO(fusion): Can this even happen? + error("WriteToSocket: Fehler %d beim Senden an Socket %d.\n", + errno, Connection->GetSocket()); + return false; + }else{ + if(errno == EINTR){ + continue; + } + + if(errno != EAGAIN || Attempts <= 0){ + if(errno == ECONNRESET || errno == EPIPE || errno == EAGAIN){ + Log("game", "Verbindung an Socket %d zusammengebrochen.\n", + Connection->GetSocket()); + }else{ + error("WriteToSocket: Fehler %d beim Senden an Socket %d.\n", + errno, Connection->GetSocket()); + } + return false; + } + + DelayThread(0, 100000); + Attempts -= 1; + } + } + + // TODO(fusion): Do we add 50 extra bytes to account for TCP segment headers? + // This does make sense If we assume packets are split into ~2.5 segments on + // average, with each segment header being 20 bytes. + NetLoad(Size + 50, true); + return true; +} + +bool SendLoginMessage(TConnection *Connection, int Type, char *Message, int WaitingTime){ + // TODO(fusion): + // LOGIN_MESSAGE_ERROR = 20 + // LOGIN_MESSAGE_? = 21 + // LOGIN_MESSAGE_WAITING_LIST = 22 + if(Type != 20 && Type != 21 && Type != 22){ + error("SendLoginMessage: Ungültiger Meldungstyp %d.\n", Type); + return true; + } + + if(Message == NULL){ + error("SendLoginMessage: Message ist NULL.\n"); + return true; + } + + if(Type == 22 && (WaitingTime < 0 || WaitingTime > UINT8_MAX)){ + error("SendLoginMessage: Ungültige Wartezeit %d.\n", WaitingTime); + return true; + } + + if(strlen(Message) > 290){ + error("SendLoginMessage: Botschaft zu lang (%s).\n", Message); + return true; + } + + // TODO(fusion): Writing output messages should have more robust helpers + // to avoid all sorts of memory bugs but since we're only doing it in two + // places from what I've seen, I'm not actually gonna bother (for now at + // least). + + // NOTE(fusion): We make sure we leave two extra bytes at the beginning so + // `WriteToSocket` can write the packet size. We also make sure that the + // remainder of the buffer has a size that is multiple of 8 so `WriteToSocket` + // can add any necessary padding for XTEA encryption without overflowing it. + uint8 Data[302]; // 2 + 300 + TWriteBuffer WriteBuffer(Data + 2, sizeof(Data) - 2); + WriteBuffer.writeWord(0); + WriteBuffer.writeByte((uint8)Type); + WriteBuffer.writeString(Message); + if(Type == 22){ + WriteBuffer.writeByte(WaitingTime); + } + + int Size = WriteBuffer.Position; + WriteBuffer.Position = 0; + WriteBuffer.writeWord((uint16)(Size - 2)); + return WriteToSocket(Connection, Data, Size); +} |
