summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoroy <tom_adams@web.de>2023-06-16 20:51:37 +0200
committerGitHub <noreply@github.com>2023-06-16 20:51:37 +0200
commit64cdbc1a3d2d5934f7142459917fb5d6bd387c2a (patch)
treef24f43559f9c3c763a900169582f4b566a3226f7
parent73c84cadc9cca00092017caba9f7d6e59926bc8b (diff)
parentf0d18f165a45b2f933b4a590f0f39c4c8fc32c7e (diff)
Merge pull request #3110 from Robyt3/engine-packer-compression
Add more checks for packers/unpackers, add tests, refactoring
-rw-r--r--CMakeLists.txt1
-rw-r--r--src/engine/client/client.cpp114
-rw-r--r--src/engine/message.h35
-rw-r--r--src/engine/server/server.cpp33
-rw-r--r--src/engine/shared/compression.cpp54
-rw-r--r--src/engine/shared/compression.h4
-rw-r--r--src/engine/shared/packer.cpp137
-rw-r--r--src/engine/shared/packer.h34
-rw-r--r--src/engine/shared/snapshot.cpp2
-rw-r--r--src/test/compression.cpp26
-rw-r--r--src/test/packer.cpp173
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());
+}