Files
DarkflameServer/tests/dCommonTests/Sd0Tests.cpp
Aaron Kimbrell ceec638e52 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>
2026-09-28 22:31:13 -05:00

68 lines
2.1 KiB
C++

#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());
}