diff --git a/CMakeLists.txt b/CMakeLists.txt index 574018350..3341c804a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2276,6 +2276,7 @@ if(GTEST_FOUND OR DOWNLOAD_GTEST) bezier.cpp blocklist_driver.cpp color.cpp + compression.cpp csv.cpp datafile.cpp fs.cpp diff --git a/src/engine/shared/compression.cpp b/src/engine/shared/compression.cpp index 35e6b3052..b04a234af 100644 --- a/src/engine/shared/compression.cpp +++ b/src/engine/shared/compression.cpp @@ -8,17 +8,17 @@ unsigned char *CVariableInt::Pack(unsigned char *pDst, int i) { *pDst = (i >> 25) & 0x40; // set sign bit if i<0 - i = i ^ (i >> 31); // if(i<0) i = ~i + i ^= i >> 31; // if(i<0) i = ~i *pDst |= i & 0x3F; // pack 6bit into dst i >>= 6; // discard 6 bits if(i) { *pDst |= 0x80; // set extend bit - while(1) + while(true) { pDst++; - *pDst = i & (0x7F); // pack 7bit + *pDst = i & 0x7F; // pack 7bit i >>= 7; // discard 7 bits *pDst |= (i != 0) << 7; // set extend bit (may branch) if(!i) @@ -32,7 +32,7 @@ unsigned char *CVariableInt::Pack(unsigned char *pDst, int i) const unsigned char *CVariableInt::Unpack(const unsigned char *pSrc, int *pInOut) { - int Sign = (*pSrc >> 6) & 1; + const int Sign = (*pSrc >> 6) & 1; *pInOut = *pSrc & 0x3F; do @@ -40,35 +40,37 @@ const unsigned char *CVariableInt::Unpack(const unsigned char *pSrc, int *pInOut if(!(*pSrc & 0x80)) break; pSrc++; - *pInOut |= (*pSrc & (0x7F)) << (6); + *pInOut |= (*pSrc & 0x7F) << 6; if(!(*pSrc & 0x80)) break; pSrc++; - *pInOut |= (*pSrc & (0x7F)) << (6 + 7); + *pInOut |= (*pSrc & 0x7F) << (6 + 7); if(!(*pSrc & 0x80)) break; pSrc++; - *pInOut |= (*pSrc & (0x7F)) << (6 + 7 + 7); + *pInOut |= (*pSrc & 0x7F) << (6 + 7 + 7); if(!(*pSrc & 0x80)) break; pSrc++; - *pInOut |= (*pSrc & (0x1F)) << (6 + 7 + 7 + 7); - } while(0); + *pInOut |= (*pSrc & 0x0F) << (6 + 7 + 7 + 7); + } while(false); pSrc++; *pInOut ^= -Sign; // if(sign) *i = ~(*i) return pSrc; } -long CVariableInt::Decompress(const void *pSrc_, int Size, void *pDst_, int DstSize) +long CVariableInt::Decompress(const void *pSrc_, int SrcSize, void *pDst_, int DstSize) { + dbg_assert(DstSize % sizeof(int) == 0, "invalid bounds"); + const unsigned char *pSrc = (unsigned char *)pSrc_; - const unsigned char *pEnd = pSrc + Size; + const unsigned char *pEnd = pSrc + SrcSize; int *pDst = (int *)pDst_; - int *pDstEnd = pDst + DstSize / 4; + const int *pDstEnd = pDst + DstSize / sizeof(int); while(pSrc < pEnd) { if(pDst >= pDstEnd) @@ -76,22 +78,24 @@ long CVariableInt::Decompress(const void *pSrc_, int Size, void *pDst_, int DstS pSrc = CVariableInt::Unpack(pSrc, pDst); pDst++; } - return (unsigned char *)pDst - (unsigned char *)pDst_; + return (long)((unsigned char *)pDst - (unsigned char *)pDst_); } -long CVariableInt::Compress(const void *pSrc_, int Size, void *pDst_, int DstSize) +long CVariableInt::Compress(const void *pSrc_, int SrcSize, void *pDst_, int DstSize) { - int *pSrc = (int *)pSrc_; + dbg_assert(SrcSize % sizeof(int) == 0, "invalid bounds"); + + const int *pSrc = (int *)pSrc_; unsigned char *pDst = (unsigned char *)pDst_; - unsigned char *pDstEnd = pDst + DstSize; - Size /= 4; - while(Size) + const unsigned char *pDstEnd = pDst + DstSize; + SrcSize /= sizeof(int); + while(SrcSize) { - if(pDstEnd - pDst < 6) + if(pDstEnd - pDst <= MAX_BYTES_PACKED) return -1; pDst = CVariableInt::Pack(pDst, *pSrc); - Size--; + SrcSize--; pSrc++; } - return pDst - (unsigned char *)pDst_; + return (long)(pDst - (unsigned char *)pDst_); } diff --git a/src/engine/shared/compression.h b/src/engine/shared/compression.h index 93bff1246..f15615d6b 100644 --- a/src/engine/shared/compression.h +++ b/src/engine/shared/compression.h @@ -2,13 +2,21 @@ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #ifndef ENGINE_SHARED_COMPRESSION_H #define ENGINE_SHARED_COMPRESSION_H + // variable int packing class CVariableInt { public: + enum + { + 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 long Compress(const void *pSrc, int Size, void *pDst, int DstSize); - static long Decompress(const void *pSrc, int Size, void *pDst, int DstSize); + + static long Compress(const void *pSrc, int SrcSize, void *pDst, int DstSize); + static long Decompress(const void *pSrc, int SrcSize, void *pDst, int DstSize); }; + #endif diff --git a/src/test/compression.cpp b/src/test/compression.cpp new file mode 100644 index 000000000..f09e72feb --- /dev/null +++ b/src/test/compression.cpp @@ -0,0 +1,68 @@ +#include + +#include + +static const int DATA[] = {0, 1, -1, 32, 64, 256, -512, 12345, -123456, 1234567, 12345678, 123456789, 2147483647, (-2147483647 - 1)}; +static const int NUM = sizeof(DATA) / sizeof(int); +static const int SIZES[NUM] = {1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 5, 5}; + +TEST(CVariableInt, RoundtripPackUnpack) +{ + for(int i = 0; i < NUM; i++) + { + 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(Result, DATA[i]); + } +} + +TEST(CVariableInt, UnpackInvalid) +{ + unsigned char aPacked[CVariableInt::MAX_BYTES_PACKED]; + for(int i = 0; i < CVariableInt::MAX_BYTES_PACKED; i++) + aPacked[i] = 0xFF; + + int Result; + EXPECT_EQ(int(CVariableInt::Unpack(aPacked, &Result) - 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(Result, 2147483647); +} + +TEST(CVariableInt, RoundtripCompressDecompress) +{ + unsigned char aCompressed[NUM * CVariableInt::MAX_BYTES_PACKED]; + int aDecompressed[NUM]; + long ExpectedCompressedSize = 0; + for(int i = 0; i < NUM; i++) + ExpectedCompressedSize += SIZES[i]; + + long CompressedSize = CVariableInt::Compress(DATA, sizeof(DATA), aCompressed, sizeof(aCompressed)); + ASSERT_EQ(CompressedSize, ExpectedCompressedSize); + long DecompressedSize = CVariableInt::Decompress(aCompressed, ExpectedCompressedSize, aDecompressed, sizeof(aDecompressed)); + ASSERT_EQ(DecompressedSize, sizeof(DATA)); + for(int i = 0; i < NUM; i++) + { + EXPECT_EQ(DATA[i], aDecompressed[i]); + } +} + +TEST(CVariableInt, CompressBufferTooSmall) +{ + unsigned char aCompressed[NUM]; // too small + long CompressedSize = CVariableInt::Compress(DATA, sizeof(DATA), aCompressed, sizeof(aCompressed)); + ASSERT_EQ(CompressedSize, -1); +} + +TEST(CVariableInt, DecompressBufferTooSmall) +{ + unsigned char aCompressed[] = {0x00, 0x01, 0x40, 0x20, 0x80, 0x01, 0x80, 0x04, 0xFF, 0x07, 0xB9, 0xC0, 0x01}; + int aUncompressed[4]; // too small + long CompressedSize = CVariableInt::Decompress(aCompressed, sizeof(aCompressed), aUncompressed, sizeof(aUncompressed)); + ASSERT_EQ(CompressedSize, -1); +}