mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
feat(common): Sd0::Compress, a default Sd0 and room for chunks that don't shrink
Sd0 gets a default constructor and Sd0::Compress(data), the raw sd0 bytes of some data (what the client reads for UGC files it downloads without 3D services). FromData compresses into a heap buffer big enough for a chunk that grows when deflated (it used a 256 KiB stack buffer, so random data failed and workers carried a large stack frame), and drops a half-written result. Tests: chunks as the 1.10.64 client inflates them, incompressible data, empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ set(DCOMMONTEST_SOURCES
|
||||
"PropertyReputationRulesTests.cpp"
|
||||
"BindAddressTests.cpp"
|
||||
"TrafficStatsTests.cpp"
|
||||
"Sd0Tests.cpp"
|
||||
)
|
||||
|
||||
add_subdirectory(dEnumsTests)
|
||||
|
||||
67
tests/dCommonTests/Sd0Tests.cpp
Normal file
67
tests/dCommonTests/Sd0Tests.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
#include "Sd0.h"
|
||||
#include "ZCompression.h"
|
||||
|
||||
namespace {
|
||||
// The chunks as the 1.10.64 client reads them (Sd0Decompress): the header, then a u32 size and zlib data per chunk,
|
||||
// each inflating to at most 256 KiB
|
||||
std::string InflateLikeTheClient(const std::string& sd0, size_t& chunks) {
|
||||
EXPECT_TRUE(sd0.starts_with(std::string(Sd0::SD0_HEADER, 5)));
|
||||
size_t offset = 5;
|
||||
std::string inflated;
|
||||
chunks = 0;
|
||||
while (offset < sd0.size()) {
|
||||
if (offset + 4 > sd0.size()) return {};
|
||||
uint32_t length{};
|
||||
std::memcpy(&length, sd0.data() + offset, 4);
|
||||
offset += 4;
|
||||
if (offset + length > sd0.size()) return {};
|
||||
std::string chunk(Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, '\0');
|
||||
int32_t error{};
|
||||
const auto size = ZCompression::Decompress(reinterpret_cast<const uint8_t*>(sd0.data() + offset), length,
|
||||
reinterpret_cast<uint8_t*>(chunk.data()), static_cast<uint32_t>(chunk.size()), error);
|
||||
if (size < 0) return {};
|
||||
inflated.append(chunk.data(), static_cast<size_t>(size));
|
||||
offset += length;
|
||||
chunks++;
|
||||
}
|
||||
return inflated;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Sd0, CompressesInChunksTheClientReads) {
|
||||
// 600 KiB: chunks of 256, 256 and 88 KiB
|
||||
std::string data(600 * 1024, '\0');
|
||||
for (size_t i = 0; i < data.size(); i++) data[i] = static_cast<char>((i * 7) % 251);
|
||||
const auto sd0 = Sd0::Compress(data);
|
||||
size_t chunks{};
|
||||
EXPECT_EQ(InflateLikeTheClient(sd0, chunks), data);
|
||||
EXPECT_EQ(chunks, 3u);
|
||||
|
||||
// And back through the reader
|
||||
std::istringstream stream(sd0);
|
||||
EXPECT_EQ(Sd0(stream).GetAsStringUncompressed(), data);
|
||||
}
|
||||
|
||||
TEST(Sd0, CompressesDataThatDoesNotShrink) {
|
||||
// Random bytes grow a little when deflated: a full chunk must still fit
|
||||
std::string data(Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE + 1000, '\0');
|
||||
std::mt19937 random(1234);
|
||||
for (auto& c : data) c = static_cast<char>(random());
|
||||
const auto sd0 = Sd0::Compress(data);
|
||||
size_t chunks{};
|
||||
EXPECT_EQ(InflateLikeTheClient(sd0, chunks), data);
|
||||
EXPECT_EQ(chunks, 2u);
|
||||
}
|
||||
|
||||
TEST(Sd0, EmptyDataIsTheHeaderOnly) {
|
||||
EXPECT_EQ(Sd0::Compress(""), std::string(Sd0::SD0_HEADER, 5));
|
||||
Sd0 empty;
|
||||
EXPECT_TRUE(empty.GetAsVector().empty());
|
||||
}
|
||||
Reference in New Issue
Block a user