diff options
author | oy <tom_adams@web.de> | 2023-06-16 20:51:37 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-06-16 20:51:37 +0200 |
commit | 64cdbc1a3d2d5934f7142459917fb5d6bd387c2a (patch) | |
tree | f24f43559f9c3c763a900169582f4b566a3226f7 | |
parent | 73c84cadc9cca00092017caba9f7d6e59926bc8b (diff) | |
parent | f0d18f165a45b2f933b4a590f0f39c4c8fc32c7e (diff) |
Merge pull request #3110 from Robyt3/engine-packer-compression
Add more checks for packers/unpackers, add tests, refactoring
-rw-r--r-- | CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/engine/client/client.cpp | 114 | ||||
-rw-r--r-- | src/engine/message.h | 35 | ||||
-rw-r--r-- | src/engine/server/server.cpp | 33 | ||||
-rw-r--r-- | src/engine/shared/compression.cpp | 54 | ||||
-rw-r--r-- | src/engine/shared/compression.h | 4 | ||||
-rw-r--r-- | src/engine/shared/packer.cpp | 137 | ||||
-rw-r--r-- | src/engine/shared/packer.h | 34 | ||||
-rw-r--r-- | src/engine/shared/snapshot.cpp | 2 | ||||
-rw-r--r-- | src/test/compression.cpp | 26 | ||||
-rw-r--r-- | src/test/packer.cpp | 173 |
11 files changed, 416 insertions, 197 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 612ba180a..348fae5ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1727,6 +1727,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST) hash.cpp io.cpp jsonwriter.cpp + packer.cpp sorted_array.cpp storage.cpp str.cpp diff --git a/src/engine/client/client.cpp b/src/engine/client/client.cpp index 09e63b1ae..04e6e127e 100644 --- a/src/engine/client/client.cpp +++ b/src/engine/client/client.cpp @@ -1122,21 +1122,14 @@ void CClient::ProcessConnlessPacket(CNetChunk *pPacket) void CClient::ProcessServerPacket(CNetChunk *pPacket) { - CUnpacker Unpacker; - Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize); - - // unpack msgid and system flag - int Msg = Unpacker.GetInt(); - int Sys = Msg&1; - Msg >>= 1; - + CMsgUnpacker Unpacker(pPacket->m_pData, pPacket->m_DataSize); if(Unpacker.Error()) return; - if(Sys) + if(Unpacker.System()) { // system message - if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_CHANGE) + if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_MAP_CHANGE) { const char *pMap = Unpacker.GetString(CUnpacker::SANITIZE_CC|CUnpacker::SKIP_START_WHITESPACES); int MapCrc = Unpacker.GetInt(); @@ -1209,12 +1202,14 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) } } } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAP_DATA) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_MAP_DATA) { if(!m_MapdownloadFileTemp) return; - int Size = minimum(m_MapDownloadChunkSize, m_MapdownloadTotalsize-m_MapdownloadAmount); + const int Size = minimum(m_MapDownloadChunkSize, m_MapdownloadTotalsize - m_MapdownloadAmount); + if(Size <= 0) + return; const unsigned char *pData = Unpacker.GetRaw(Size); if(Unpacker.Error()) return; @@ -1257,7 +1252,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) m_pConsole->Print(IConsole::OUTPUT_LEVEL_DEBUG, "client/network", "requested next chunk package"); } } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_SERVERINFO) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_SERVERINFO) { CServerInfo Info = {0}; net_addr_str(&pPacket->m_Address, Info.m_aAddress, sizeof(Info.m_aAddress), true); @@ -1268,16 +1263,16 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) m_CurrentServerInfo.m_NetAddr = m_ServerAddress; } } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_CON_READY) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_CON_READY) { GameClient()->OnConnected(); } - else if(Msg == NETMSG_PING) + else if(Unpacker.Type() == NETMSG_PING) { CMsgPacker Msg(NETMSG_PING_REPLY, true); SendMsg(&Msg, MSGFLAG_FLUSH); } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_ADD) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_RCON_CMD_ADD) { const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); const char *pHelp = Unpacker.GetString(CUnpacker::SANITIZE_CC); @@ -1285,30 +1280,30 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) if(Unpacker.Error() == 0) m_pConsole->RegisterTemp(pName, pParams, CFGFLAG_SERVER, pHelp); } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_CMD_REM) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_RCON_CMD_REM) { const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); if(Unpacker.Error() == 0) m_pConsole->DeregisterTemp(pName); } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAPLIST_ENTRY_ADD) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_MAPLIST_ENTRY_ADD) { const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); if(Unpacker.Error() == 0) m_pConsole->RegisterTempMap(pName); } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_MAPLIST_ENTRY_REM) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_MAPLIST_ENTRY_REM) { const char *pName = Unpacker.GetString(CUnpacker::SANITIZE_CC); if(Unpacker.Error() == 0) m_pConsole->DeregisterTempMap(pName); } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_ON) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_RCON_AUTH_ON) { m_RconAuthed = 1; m_UseTempRconCommands = 1; } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_AUTH_OFF) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_RCON_AUTH_OFF) { m_RconAuthed = 0; if(m_UseTempRconCommands) @@ -1316,19 +1311,19 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) m_UseTempRconCommands = 0; m_pConsole->DeregisterTempMapAll(); } - else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Msg == NETMSG_RCON_LINE) + else if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && Unpacker.Type() == NETMSG_RCON_LINE) { const char *pLine = Unpacker.GetString(); if(Unpacker.Error() == 0) GameClient()->OnRconLine(pLine); } - else if(Msg == NETMSG_PING_REPLY) + else if(Unpacker.Type() == NETMSG_PING_REPLY) { char aBuf[256]; str_format(aBuf, sizeof(aBuf), "latency %.2f", (time_get() - m_PingStartTime)*1000 / (float)time_freq()); m_pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "client/network", aBuf); } - else if(Msg == NETMSG_INPUTTIMING) + else if(Unpacker.Type() == NETMSG_INPUTTIMING) { int InputPredTick = Unpacker.GetInt(); int TimeLeft = Unpacker.GetInt(); @@ -1348,36 +1343,39 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) if(Target) m_PredictedTime.Update(&m_InputtimeMarginGraph, Target, TimeLeft, 1); } - else if(Msg == NETMSG_SNAP || Msg == NETMSG_SNAPSINGLE || Msg == NETMSG_SNAPEMPTY) + else if(Unpacker.Type() == NETMSG_SNAP || Unpacker.Type() == NETMSG_SNAPSINGLE || Unpacker.Type() == NETMSG_SNAPEMPTY) { - int NumParts = 1; - int Part = 0; - int GameTick = Unpacker.GetInt(); - int DeltaTick = GameTick-Unpacker.GetInt(); - int PartSize = 0; - int Crc = 0; - int CompleteSize = 0; - const char *pData = 0; - // we are not allowed to process snapshot yet if(State() < IClient::STATE_LOADING) return; - if(Msg == NETMSG_SNAP) + const int GameTick = Unpacker.GetInt(); + const int DeltaTick = GameTick - Unpacker.GetInt(); + + int NumParts = 1; + int Part = 0; + if(Unpacker.Type() == NETMSG_SNAP) { NumParts = Unpacker.GetInt(); Part = Unpacker.GetInt(); + if(NumParts < 1 || NumParts > CSnapshot::MAX_PARTS || Part < 0 || Part >= NumParts) + return; } - if(Msg != NETMSG_SNAPEMPTY) + int PartSize = 0; + int Crc = 0; + const char *pData = 0; + if(Unpacker.Type() != NETMSG_SNAPEMPTY) { Crc = Unpacker.GetInt(); PartSize = Unpacker.GetInt(); + if(PartSize < 0 || PartSize > MAX_SNAPSHOT_PACKSIZE) + return; + if(PartSize > 0) + pData = (const char *)Unpacker.GetRaw(PartSize); } - pData = (const char *)Unpacker.GetRaw(PartSize); - - if(Unpacker.Error() || NumParts < 1 || NumParts > CSnapshot::MAX_PARTS || Part < 0 || Part >= NumParts || PartSize < 0 || PartSize > MAX_SNAPSHOT_PACKSIZE) + if(Unpacker.Error()) return; if(GameTick >= m_CurrentRecvTick) @@ -1389,27 +1387,26 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) } // TODO: clean this up abit - mem_copy((char*)m_aSnapshotIncomingData + Part*MAX_SNAPSHOT_PACKSIZE, pData, PartSize); + if(pData) + mem_copy((char *)m_aSnapshotIncomingData + Part * MAX_SNAPSHOT_PACKSIZE, pData, PartSize); + m_SnapshotParts |= 1<<Part; if(m_SnapshotParts == (unsigned)((1<<NumParts)-1)) { - static CSnapshot Emptysnap; - CSnapshot *pDeltaShot = &Emptysnap; - int PurgeTick; - int DeltaSize; + static CSnapshot s_Emptysnap; + CSnapshot *pDeltaShot = &s_Emptysnap; unsigned char aTmpBuffer2[CSnapshot::MAX_SIZE]; unsigned char aTmpBuffer3[CSnapshot::MAX_SIZE]; CSnapshot *pTmpBuffer3 = (CSnapshot*)aTmpBuffer3; // Fix compiler warning for strict-aliasing - int SnapSize; - CompleteSize = (NumParts-1) * MAX_SNAPSHOT_PACKSIZE + PartSize; + int CompleteSize = (NumParts-1) * MAX_SNAPSHOT_PACKSIZE + PartSize; // reset snapshoting m_SnapshotParts = 0; // find snapshot that we should use as delta - Emptysnap.Clear(); + s_Emptysnap.Clear(); // find delta if(DeltaTick >= 0) @@ -1436,7 +1433,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) // decompress snapshot const void *pDeltaData = m_SnapshotDelta.EmptyDelta(); - DeltaSize = sizeof(int)*3; + int DeltaSize = sizeof(int) * 3; if(CompleteSize) { @@ -1450,7 +1447,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) } // unpack delta - SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize); + int SnapSize = m_SnapshotDelta.UnpackDelta(pDeltaShot, pTmpBuffer3, pDeltaData, DeltaSize); if(SnapSize < 0) { char aBuf[64]; @@ -1459,7 +1456,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) return; } - if(Msg != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc) + if(Unpacker.Type() != NETMSG_SNAPEMPTY && pTmpBuffer3->Crc() != Crc) { if(Config()->m_Debug) { @@ -1486,7 +1483,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) } // purge old snapshots - PurgeTick = DeltaTick; + int PurgeTick = DeltaTick; if(m_aSnapshots[SNAP_PREV] && m_aSnapshots[SNAP_PREV]->m_Tick < PurgeTick) PurgeTick = m_aSnapshots[SNAP_PREV]->m_Tick; if(m_aSnapshots[SNAP_CURRENT] && m_aSnapshots[SNAP_CURRENT]->m_Tick < PurgeTick) @@ -1545,7 +1542,7 @@ void CClient::ProcessServerPacket(CNetChunk *pPacket) if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0) { // game message - GameClient()->OnMessage(Msg, &Unpacker); + GameClient()->OnMessage(Unpacker.Type(), &Unpacker); if(m_RecordGameMessage && m_DemoRecorder.IsRecording()) m_DemoRecorder.RecordMessage(pPacket->m_pData, pPacket->m_DataSize); @@ -1617,19 +1614,12 @@ void CClient::OnDemoPlayerSnapshot(void *pData, int Size) void CClient::OnDemoPlayerMessage(void *pData, int Size) { - CUnpacker Unpacker; - Unpacker.Reset(pData, Size); - - // unpack msgid and system flag - int Msg = Unpacker.GetInt(); - int Sys = Msg&1; - Msg >>= 1; - + CMsgUnpacker Unpacker(pData, Size); if(Unpacker.Error()) return; - if(!Sys) - GameClient()->OnMessage(Msg, &Unpacker); + if(!Unpacker.System()) + GameClient()->OnMessage(Unpacker.Type(), &Unpacker); } void CClient::Update() diff --git a/src/engine/message.h b/src/engine/message.h index edfef24e1..77d8ed2b4 100644 --- a/src/engine/message.h +++ b/src/engine/message.h @@ -8,11 +8,42 @@ class CMsgPacker : public CPacker { public: - CMsgPacker(int Type, bool System=false) + CMsgPacker(int Type, bool System = false) { Reset(); - AddInt((Type<<1)|(System?1:0)); + if(Type < 0 || Type > 0x3FFFFFFF) + { + m_Error = true; + return; + } + AddInt((Type << 1) | (System ? 1 : 0)); } }; +class CMsgUnpacker : public CUnpacker +{ + int m_Type; + bool m_System; + +public: + CMsgUnpacker(const void *pData, int Size) + { + Reset(pData, Size); + const int Msg = GetInt(); + if(Msg < 0) + m_Error = true; + if(m_Error) + { + m_System = false; + m_Type = 0; + return; + } + m_System = Msg & 1; + m_Type = Msg >> 1; + } + + int Type() const { return m_Type; } + bool System() const { return m_System; } +}; + #endif diff --git a/src/engine/server/server.cpp b/src/engine/server/server.cpp index 18a796eb6..4aedd51c9 100644 --- a/src/engine/server/server.cpp +++ b/src/engine/server/server.cpp @@ -802,22 +802,15 @@ void CServer::UpdateClientMapListEntries() void CServer::ProcessClientPacket(CNetChunk *pPacket) { - int ClientID = pPacket->m_ClientID; - CUnpacker Unpacker; - Unpacker.Reset(pPacket->m_pData, pPacket->m_DataSize); - - // unpack msgid and system flag - int Msg = Unpacker.GetInt(); - int Sys = Msg&1; - Msg >>= 1; - + CMsgUnpacker Unpacker(pPacket->m_pData, pPacket->m_DataSize); if(Unpacker.Error()) return; - if(Sys) + const int ClientID = pPacket->m_ClientID; + if(Unpacker.System()) { // system message - if(Msg == NETMSG_INFO) + if(Unpacker.Type() == NETMSG_INFO) { if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_AUTH) { @@ -845,7 +838,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) SendMap(ClientID); } } - else if(Msg == NETMSG_REQUEST_MAP_DATA) + else if(Unpacker.Type() == NETMSG_REQUEST_MAP_DATA) { if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && (m_aClients[ClientID].m_State == CClient::STATE_CONNECTING || m_aClients[ClientID].m_State == CClient::STATE_CONNECTING_AS_SPEC)) { @@ -879,7 +872,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } } } - else if(Msg == NETMSG_READY) + else if(Unpacker.Type() == NETMSG_READY) { if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && (m_aClients[ClientID].m_State == CClient::STATE_CONNECTING || m_aClients[ClientID].m_State == CClient::STATE_CONNECTING_AS_SPEC)) { @@ -896,7 +889,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) SendConnectionReady(ClientID); } } - else if(Msg == NETMSG_ENTERGAME) + else if(Unpacker.Type() == NETMSG_ENTERGAME) { if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State == CClient::STATE_READY && GameServer()->IsClientReady(ClientID)) { @@ -911,7 +904,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) GameServer()->OnClientEnter(ClientID); } } - else if(Msg == NETMSG_INPUT) + else if(Unpacker.Type() == NETMSG_INPUT) { CClient::CInput *pInput; int64 TagTime; @@ -968,7 +961,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) if(m_aClients[ClientID].m_State == CClient::STATE_INGAME) GameServer()->OnClientDirectInput(ClientID, m_aClients[ClientID].m_LatestInput.m_aData); } - else if(Msg == NETMSG_RCON_CMD) + else if(Unpacker.Type() == NETMSG_RCON_CMD) { const char *pCmd = Unpacker.GetString(); @@ -986,7 +979,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) m_RconAuthLevel = AUTHED_ADMIN; } } - else if(Msg == NETMSG_RCON_AUTH) + else if(Unpacker.Type() == NETMSG_RCON_AUTH) { const char *pPw = Unpacker.GetString(CUnpacker::SANITIZE_CC); @@ -1053,7 +1046,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) } } } - else if(Msg == NETMSG_PING) + else if(Unpacker.Type() == NETMSG_PING) { CMsgPacker Msg(NETMSG_PING_REPLY, true); SendMsg(&Msg, MSGFLAG_FLUSH, ClientID); @@ -1063,7 +1056,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) if(Config()->m_Debug) { char aBuf[256]; - str_format(aBuf, sizeof(aBuf), "strange message ClientID=%d msg=%d data_size=%d", ClientID, Msg, pPacket->m_DataSize); + str_format(aBuf, sizeof(aBuf), "strange message ClientID=%d msg=%d data_size=%d", ClientID, Unpacker.Type(), pPacket->m_DataSize); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf); str_hex(aBuf, sizeof(aBuf), pPacket->m_pData, minimum(pPacket->m_DataSize, 32)); Console()->Print(IConsole::OUTPUT_LEVEL_DEBUG, "server", aBuf); @@ -1074,7 +1067,7 @@ void CServer::ProcessClientPacket(CNetChunk *pPacket) { // game message if((pPacket->m_Flags&NET_CHUNKFLAG_VITAL) != 0 && m_aClients[ClientID].m_State >= CClient::STATE_READY) - GameServer()->OnMessage(Msg, &Unpacker, ClientID); + GameServer()->OnMessage(Unpacker.Type(), &Unpacker, ClientID); } } diff --git a/src/engine/shared/compression.cpp b/src/engine/shared/compression.cpp index 44b11f14d..a4d24b83d 100644 --- a/src/engine/shared/compression.cpp +++ b/src/engine/shared/compression.cpp @@ -5,8 +5,12 @@ #include "compression.h" // Format: ESDDDDDD EDDDDDDD EDD... Extended, Data, Sign -unsigned char *CVariableInt::Pack(unsigned char *pDst, int i) +unsigned char *CVariableInt::Pack(unsigned char *pDst, int i, int DstSize) { + if(DstSize <= 0) + return 0; + + DstSize--; *pDst = 0; if(i < 0) { @@ -18,7 +22,10 @@ unsigned char *CVariableInt::Pack(unsigned char *pDst, int i) i >>= 6; // discard 6 bits while(i) { + if(DstSize <= 0) + return 0; *pDst |= 0x80; // set extend bit + DstSize--; pDst++; *pDst = i & 0x7F; // pack 7bit i >>= 7; // discard 7 bits @@ -28,33 +35,28 @@ unsigned char *CVariableInt::Pack(unsigned char *pDst, int i) return pDst; } -const unsigned char *CVariableInt::Unpack(const unsigned char *pSrc, int *pInOut) +const unsigned char *CVariableInt::Unpack(const unsigned char *pSrc, int *pInOut, int SrcSize) { + if(SrcSize <= 0) + return 0; + const int Sign = (*pSrc >> 6) & 1; *pInOut = *pSrc & 0x3F; + SrcSize--; - do - { - if(!(*pSrc & 0x80)) - break; - pSrc++; - *pInOut |= (*pSrc & 0x7F) << 6; - - if(!(*pSrc & 0x80)) - break; - pSrc++; - *pInOut |= (*pSrc & 0x7F) << (6 + 7); - - if(!(*pSrc & 0x80)) - break; - pSrc++; - *pInOut |= (*pSrc & 0x7F) << (6 + 7 + 7); + const static int s_aMasks[] = {0x7F, 0x7F, 0x7F, 0x0F}; + const static int s_aShifts[] = {6, 6 + 7, 6 + 7 + 7, 6 + 7 + 7 + 7}; + for(unsigned i = 0; i < sizeof(s_aMasks) / sizeof(int); i++) + { if(!(*pSrc & 0x80)) break; + if(SrcSize <= 0) + return 0; + SrcSize--; pSrc++; - *pInOut |= (*pSrc & 0x0F) << (6 + 7 + 7 + 7); - } while(false); + *pInOut |= (*pSrc & s_aMasks[i]) << s_aShifts[i]; + } pSrc++; *pInOut ^= -Sign; // if(sign) *i = ~(*i) @@ -66,14 +68,16 @@ long CVariableInt::Decompress(const void *pSrc_, int SrcSize, void *pDst_, int D dbg_assert(DstSize % sizeof(int) == 0, "invalid bounds"); const unsigned char *pSrc = (unsigned char *)pSrc_; - const unsigned char *pEnd = pSrc + SrcSize; + const unsigned char *pSrcEnd = pSrc + SrcSize; int *pDst = (int *)pDst_; const int *pDstEnd = pDst + DstSize / sizeof(int); - while(pSrc < pEnd) + while(pSrc < pSrcEnd) { if(pDst >= pDstEnd) return -1; - pSrc = CVariableInt::Unpack(pSrc, pDst); + pSrc = CVariableInt::Unpack(pSrc, pDst, pSrcEnd - pSrc); + if(!pSrc) + return -1; pDst++; } return (long)((unsigned char *)pDst - (unsigned char *)pDst_); @@ -89,9 +93,9 @@ long CVariableInt::Compress(const void *pSrc_, int SrcSize, void *pDst_, int Dst SrcSize /= sizeof(int); while(SrcSize) { - if(pDstEnd - pDst <= MAX_BYTES_PACKED) + pDst = CVariableInt::Pack(pDst, *pSrc, pDstEnd - pDst); + if(!pDst) return -1; - pDst = CVariableInt::Pack(pDst, *pSrc); SrcSize--; pSrc++; } diff --git a/src/engine/shared/compression.h b/src/engine/shared/compression.h index f15615d6b..ef490b0d8 100644 --- a/src/engine/shared/compression.h +++ b/src/engine/shared/compression.h @@ -12,8 +12,8 @@ public: MAX_BYTES_PACKED = 5, // maximum number of bytes in a packed int }; - static unsigned char *Pack(unsigned char *pDst, int i); - static const unsigned char *Unpack(const unsigned char *pSrc, int *pInOut); + static unsigned char *Pack(unsigned char *pDst, int i, int DstSize); + static const unsigned char *Unpack(const unsigned char *pSrc, int *pInOut, int SrcSize); static long Compress(const void *pSrc, int SrcSize, void *pDst, int DstSize); static long Decompress(const void *pSrc, int SrcSize, void *pDst, int DstSize); diff --git a/src/engine/shared/packer.cpp b/src/engine/shared/packer.cpp index e39cb2994..7fe721fd9 100644 --- a/src/engine/shared/packer.cpp +++ b/src/engine/shared/packer.cpp @@ -2,30 +2,28 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include <base/system.h> -#include "packer.h" #include "compression.h" -#include "config.h" +#include "packer.h" void CPacker::Reset() { - m_Error = 0; + m_Error = false; m_pCurrent = m_aBuffer; m_pEnd = m_pCurrent + PACKER_BUFFER_SIZE; } -void CPacker::AddInt(int i) +void CPacker::AddInt(int Integer) { if(m_Error) return; - // make sure that we have space enough - if(m_pEnd - m_pCurrent <= CVariableInt::MAX_BYTES_PACKED) + unsigned char *pNext = CVariableInt::Pack(m_pCurrent, Integer, RemainingSize()); + if(!pNext) { - dbg_break(); - m_Error = 1; + m_Error = true; + return; } - else - m_pCurrent = CVariableInt::Pack(m_pCurrent, i); + m_pCurrent = pNext; } void CPacker::AddString(const char *pStr, int Limit) @@ -33,36 +31,31 @@ void CPacker::AddString(const char *pStr, int Limit) if(m_Error) return; - // - if(Limit > 0) - { - while(*pStr && Limit != 0) - { - *m_pCurrent++ = *pStr++; - Limit--; - - if(m_pCurrent >= m_pEnd) - { - m_Error = 1; - break; - } - } - *m_pCurrent++ = 0; - } - else + if(Limit <= 0) + Limit = PACKER_BUFFER_SIZE; + + while(*pStr && Limit != 0) { - while(*pStr) - { - *m_pCurrent++ = *pStr++; + int Codepoint = str_utf8_decode(&pStr); + if(Codepoint == -1) + Codepoint = 0xfffd; // Unicode replacement character. + + char aEncoded[4]; + const int Length = str_utf8_encode(aEncoded, Codepoint); + if(Limit < Length) + break; - if(m_pCurrent >= m_pEnd) - { - m_Error = 1; - break; - } + // Ensure space for the null termination. + if(RemainingSize() < Length + 1) + { + m_Error = true; + break; } - *m_pCurrent++ = 0; + mem_copy(m_pCurrent, aEncoded, Length); + m_pCurrent += Length; + Limit -= Length; } + *m_pCurrent++ = 0; } void CPacker::AddRaw(const void *pData, int Size) @@ -70,24 +63,20 @@ void CPacker::AddRaw(const void *pData, int Size) if(m_Error) return; - if(m_pCurrent+Size >= m_pEnd) + if(Size <= 0 || Size >= RemainingSize()) { - m_Error = 1; + m_Error = true; return; } - const unsigned char *pSrc = (const unsigned char *)pData; - while(Size) - { - *m_pCurrent++ = *pSrc++; - Size--; - } + mem_copy(m_pCurrent, pData, Size); + m_pCurrent += Size; } void CUnpacker::Reset(const void *pData, int Size) { - m_Error = 0; + m_Error = false; m_pStart = (const unsigned char *)pData; m_pEnd = m_pStart + Size; m_pCurrent = m_pStart; @@ -98,74 +87,84 @@ int CUnpacker::GetInt() if(m_Error) return 0; - if(m_pCurrent >= m_pEnd) + if(RemainingSize() <= 0) { - m_Error = 1; + m_Error = true; return 0; } - int i; - m_pCurrent = CVariableInt::Unpack(m_pCurrent, &i); - if(m_pCurrent > m_pEnd) + int Integer; + const unsigned char *pNext = CVariableInt::Unpack(m_pCurrent, &Integer, RemainingSize()); + if(!pNext) { - m_Error = 1; + m_Error = true; return 0; } - return i; + m_pCurrent = pNext; + return Integer; } int CUnpacker::GetIntOrDefault(int Default) { if(m_Error) - { return 0; - } - if(m_pCurrent == m_pEnd) - { + + if(RemainingSize() == 0) return Default; - } + return GetInt(); } const char *CUnpacker::GetString(int SanitizeType) { - if(m_Error || m_pCurrent >= m_pEnd) + if(m_Error) + return ""; + + if(RemainingSize() <= 0) + { + m_Error = true; return ""; + } - char *pPtr = (char *)m_pCurrent; - while(*m_pCurrent) // skip the string + // Ensure string is null terminated. + char *pStr = (char *)m_pCurrent; + while(*m_pCurrent) { m_pCurrent++; if(m_pCurrent == m_pEnd) { - m_Error = 1;; + m_Error = true; return ""; } } m_pCurrent++; // sanitize all strings - if(SanitizeType&SANITIZE) - str_sanitize(pPtr); - else if(SanitizeType&SANITIZE_CC) - str_sanitize_cc(pPtr); - return SanitizeType&SKIP_START_WHITESPACES ? str_utf8_skip_whitespaces(pPtr) : pPtr; + if(SanitizeType & SANITIZE) + str_sanitize(pStr); + else if(SanitizeType & SANITIZE_CC) + str_sanitize_cc(pStr); + + if(SanitizeType & SKIP_START_WHITESPACES) + return str_utf8_skip_whitespaces(pStr); + + return pStr; } const unsigned char *CUnpacker::GetRaw(int Size) { - const unsigned char *pPtr = m_pCurrent; if(m_Error) return 0; // check for nasty sizes - if(Size < 0 || m_pCurrent+Size > m_pEnd) + if(Size <= 0 || Size > RemainingSize()) { - m_Error = 1; + m_Error = true; return 0; } // "unpack" the data + const unsigned char *pPtr = m_pCurrent; m_pCurrent += Size; return pPtr; } diff --git a/src/engine/shared/packer.h b/src/engine/shared/packer.h index 967c2e6d0..ad1fdd7bb 100644 --- a/src/engine/shared/packer.h +++ b/src/engine/shared/packer.h @@ -3,26 +3,30 @@ #ifndef ENGINE_SHARED_PACKER_H #define ENGINE_SHARED_PACKER_H - - class CPacker { +public: enum { - PACKER_BUFFER_SIZE=1024*2 + PACKER_BUFFER_SIZE = 1024 * 2 }; +private: unsigned char m_aBuffer[PACKER_BUFFER_SIZE]; unsigned char *m_pCurrent; unsigned char *m_pEnd; - int m_Error; + +protected: + bool m_Error; + public: void Reset(); - void AddInt(int i); - void AddString(const char *pStr, int Limit); + void AddInt(int Integer); + void AddString(const char *pStr, int Limit = 0); void AddRaw(const void *pData, int Size); - int Size() const { return (int)(m_pCurrent-m_aBuffer); } + int Size() const { return (int)(m_pCurrent - m_aBuffer); } + int RemainingSize() const { return (int)(m_pEnd - m_pCurrent); } const unsigned char *Data() const { return m_aBuffer; } bool Error() const { return m_Error; } }; @@ -32,13 +36,16 @@ class CUnpacker const unsigned char *m_pStart; const unsigned char *m_pCurrent; const unsigned char *m_pEnd; - int m_Error; + +protected: + bool m_Error; + public: enum { - SANITIZE=1, - SANITIZE_CC=2, - SKIP_START_WHITESPACES=4 + SANITIZE = 1, + SANITIZE_CC = 2, + SKIP_START_WHITESPACES = 4 }; void Reset(const void *pData, int Size); @@ -46,6 +53,11 @@ public: int GetIntOrDefault(int Default); const char *GetString(int SanitizeType = SANITIZE); const unsigned char *GetRaw(int Size); + + int Size() const { return m_pCurrent - m_pStart; } + int RemainingSize() const { return m_pEnd - m_pCurrent; } + int CompleteSize() const { return m_pEnd - m_pStart; } + const unsigned char *Data() const { return m_pStart; } bool Error() const { return m_Error; } }; diff --git a/src/engine/shared/snapshot.cpp b/src/engine/shared/snapshot.cpp index 35dfa0388..1ef1a929e 100644 --- a/src/engine/shared/snapshot.cpp +++ b/src/engine/shared/snapshot.cpp @@ -163,7 +163,7 @@ static void UndiffItem(const int *pPast, const int *pDiff, int *pOut, int Size, else { unsigned char aBuf[CVariableInt::MAX_BYTES_PACKED]; - unsigned char *pEnd = CVariableInt::Pack(aBuf, *pDiff); + unsigned char *pEnd = CVariableInt::Pack(aBuf, *pDiff, sizeof(aBuf)); *pDataRate += (int)(pEnd - (unsigned char*)aBuf) * 8; } diff --git a/src/test/compression.cpp b/src/test/compression.cpp index 89f5b46b8..664c29d97 100644 --- a/src/test/compression.cpp +++ b/src/test/compression.cpp @@ -14,8 +14,8 @@ TEST(CVariableInt, RoundtripPackUnpack) { unsigned char aPacked[CVariableInt::MAX_BYTES_PACKED]; int Result; - EXPECT_EQ(int(CVariableInt::Pack(aPacked, DATA[i]) - aPacked), SIZES[i]); - EXPECT_EQ(int(CVariableInt::Unpack(aPacked, &Result) - aPacked), SIZES[i]); + EXPECT_EQ(int(CVariableInt::Pack(aPacked, DATA[i], sizeof(aPacked)) - aPacked), SIZES[i]); + EXPECT_EQ(int(CVariableInt::Unpack(aPacked, &Result, sizeof(aPacked)) - aPacked), SIZES[i]); EXPECT_EQ(Result, DATA[i]); } } @@ -23,19 +23,35 @@ TEST(CVariableInt, RoundtripPackUnpack) TEST(CVariableInt, UnpackInvalid) { unsigned char aPacked[CVariableInt::MAX_BYTES_PACKED]; - for(int i = 0; i < CVariableInt::MAX_BYTES_PACKED; i++) + for(unsigned i = 0; i < sizeof(aPacked); i++) aPacked[i] = 0xFF; int Result; - EXPECT_EQ(int(CVariableInt::Unpack(aPacked, &Result) - aPacked), int(CVariableInt::MAX_BYTES_PACKED)); + EXPECT_EQ(int(CVariableInt::Unpack(aPacked, &Result, sizeof(aPacked)) - aPacked), int(CVariableInt::MAX_BYTES_PACKED)); EXPECT_EQ(Result, (-2147483647 - 1)); aPacked[0] &= ~0x40; // unset sign bit - EXPECT_EQ(int(CVariableInt::Unpack(aPacked, &Result) - aPacked), int(CVariableInt::MAX_BYTES_PACKED)); + EXPECT_EQ(int(CVariableInt::Unpack(aPacked, &Result, sizeof(aPacked)) - aPacked), int(CVariableInt::MAX_BYTES_PACKED)); EXPECT_EQ(Result, 2147483647); } +TEST(CVariableInt, PackBufferTooSmall) +{ + unsigned char aPacked[CVariableInt::MAX_BYTES_PACKED / 2]; // too small + EXPECT_EQ(CVariableInt::Pack(aPacked, 2147483647, sizeof(aPacked)), (const unsigned char *)0x0); +} + +TEST(CVariableInt, UnpackBufferTooSmall) +{ + unsigned char aPacked[CVariableInt::MAX_BYTES_PACKED / 2]; + for(unsigned i = 0; i < sizeof(aPacked); i++) + aPacked[i] = 0xFF; // extended bits are set, but buffer ends too early + + int UnusedResult; + EXPECT_EQ(CVariableInt::Unpack(aPacked, &UnusedResult, sizeof(aPacked)), (const unsigned char *)0x0); +} + TEST(CVariableInt, RoundtripCompressDecompress) { unsigned char aCompressed[NUM * CVariableInt::MAX_BYTES_PACKED]; diff --git a/src/test/packer.cpp b/src/test/packer.cpp new file mode 100644 index 000000000..593f787dc --- /dev/null +++ b/src/test/packer.cpp @@ -0,0 +1,173 @@ +#include "test.h" +#include <gtest/gtest.h> + +#include <base/system.h> +#include <engine/message.h> + +// pExpected is NULL if an error is expected +static void ExpectAddString5(const char *pString, int Limit, const char *pExpected) +{ + static char ZEROS[CPacker::PACKER_BUFFER_SIZE] = {0}; + static const int OFFSET = CPacker::PACKER_BUFFER_SIZE - 5; + CPacker Packer; + Packer.Reset(); + Packer.AddRaw(ZEROS, OFFSET); + Packer.AddString(pString, Limit); + + EXPECT_EQ(pExpected == 0, Packer.Error()); + if(pExpected) + { + // Include null termination. + int ExpectedLength = str_length(pExpected) + 1; + EXPECT_EQ(ExpectedLength, Packer.Size() - OFFSET); + if(ExpectedLength == Packer.Size() - OFFSET) + { + EXPECT_STREQ(pExpected, (const char *)Packer.Data() + OFFSET); + } + } +} + +TEST(Packer, AddString) +{ + ExpectAddString5("", 0, ""); + ExpectAddString5("a", 0, "a"); + ExpectAddString5("abcd", 0, "abcd"); + ExpectAddString5("abcde", 0, 0); +} + +TEST(Packer, AddStringLimit) +{ + ExpectAddString5("", 1, ""); + ExpectAddString5("a", 1, "a"); + ExpectAddString5("aa", 1, "a"); + ExpectAddString5("ä", 1, ""); + + ExpectAddString5("", 10, ""); + ExpectAddString5("a", 10, "a"); + ExpectAddString5("abcd", 10, "abcd"); + ExpectAddString5("abcde", 10, 0); + + ExpectAddString5("äöü", 5, "äö"); + ExpectAddString5("äöü", 6, 0); +} + +TEST(Packer, AddStringBroken) +{ + ExpectAddString5("\x80", 0, "�"); + ExpectAddString5("\x80\x80", 0, 0); + ExpectAddString5("a\x80", 0, "a�"); + ExpectAddString5("\x80""a", 0, "�a"); + ExpectAddString5("\x80", 1, ""); + ExpectAddString5("\x80\x80", 3, "�"); + ExpectAddString5("\x80\x80", 5, "�"); + ExpectAddString5("\x80\x80", 6, 0); +} + +TEST(Packer, EmptyPacker) +{ + CPacker Packer; + Packer.Reset(); + EXPECT_EQ(false, Packer.Error()); + EXPECT_EQ(0, Packer.Size()); + EXPECT_EQ(int(CPacker::PACKER_BUFFER_SIZE), Packer.RemainingSize()); +} + +TEST(Packer, PackerRawNoSize) +{ + CPacker Packer; + Packer.Reset(); + EXPECT_EQ(false, Packer.Error()); + Packer.AddRaw("a", 0); + EXPECT_EQ(true, Packer.Error()); +} + +TEST(Packer, MinimalUnpacker) +{ + unsigned char aBuf[1] = {0}; + CUnpacker Unpacker; + Unpacker.Reset(aBuf, sizeof(aBuf)); + EXPECT_EQ(1, Unpacker.RemainingSize()); + EXPECT_EQ(1, Unpacker.CompleteSize()); + EXPECT_EQ(0, Unpacker.Size()); + EXPECT_EQ(0, Unpacker.GetInt()); + EXPECT_EQ(false, Unpacker.Error()); + EXPECT_EQ(0, Unpacker.RemainingSize()); + EXPECT_EQ(1, Unpacker.CompleteSize()); + EXPECT_EQ(1, Unpacker.Size()); + EXPECT_EQ(123, Unpacker.GetIntOrDefault(123)); + EXPECT_EQ(false, Unpacker.Error()); + EXPECT_EQ(0, Unpacker.GetInt()); + EXPECT_EQ(true, Unpacker.Error()); +} + +TEST(Packer, UnpackerRawNoSize) +{ + unsigned char aBuf[1] = {0}; + CUnpacker Unpacker; + Unpacker.Reset(aBuf, sizeof(aBuf)); + EXPECT_EQ(false, Unpacker.Error()); + EXPECT_EQ((const unsigned char *)0x0, Unpacker.GetRaw(0)); + EXPECT_EQ(true, Unpacker.Error()); +} + +TEST(Packer, RoundtripPackerUnpacker) +{ + CPacker Packer; + Packer.Reset(); + Packer.AddInt(12345); + Packer.AddString("abcd"); + Packer.AddInt(-123); + Packer.AddRaw("efgh", 4); + EXPECT_EQ(false, Packer.Error()); + EXPECT_EQ(14, Packer.Size()); + + CUnpacker Unpacker; + Unpacker.Reset(Packer.Data(), Packer.Size()); + EXPECT_EQ(12345, Unpacker.GetInt()); + EXPECT_STREQ("abcd", Unpacker.GetString()); + EXPECT_EQ(-123, Unpacker.GetInt()); + EXPECT_EQ(0, mem_comp("efgh", Unpacker.GetRaw(4), 4)); + EXPECT_EQ(false, Unpacker.Error()); + EXPECT_EQ(0, Unpacker.RemainingSize()); +} + +static void TestMsgPackerUnpacker(int Type, bool System, bool ExpectError) +{ + CMsgPacker Packer(Type, System); + EXPECT_EQ(ExpectError, Packer.Error()); + if(Packer.Error()) + return; + + CMsgUnpacker Unpacker(Packer.Data(), Packer.Size()); + EXPECT_EQ(Type, Unpacker.Type()); + EXPECT_EQ(System, Unpacker.System()); + EXPECT_EQ(false, Unpacker.Error()); + EXPECT_EQ(0, Unpacker.RemainingSize()); +} + +TEST(Packer, RoundtripMsgPackerUnpacker) +{ + TestMsgPackerUnpacker(0, false, false); + TestMsgPackerUnpacker(12345, true, false); + TestMsgPackerUnpacker(0x3FFFFFFF, true, false); +} + +TEST(Packer, MsgPackerError) +{ + TestMsgPackerUnpacker(-1, false, true); + TestMsgPackerUnpacker(-12345, true, true); + TestMsgPackerUnpacker(0x7FFFFFFF, true, true); +} + +TEST(Packer, MsgUnpackerError) +{ + CPacker Packer; + Packer.Reset(); + Packer.AddInt(-12345); + EXPECT_EQ(false, Packer.Error()); + + CMsgUnpacker Unpacker(Packer.Data(), Packer.Size()); + EXPECT_EQ(0, Unpacker.Type()); + EXPECT_EQ(false, Unpacker.System()); + EXPECT_EQ(true, Unpacker.Error()); +} |