1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Clean up and compress some pre-join packets (#15881)

This commit is contained in:
sfan5 2025-03-11 20:00:07 +01:00 committed by GitHub
parent 287880aa27
commit afb15978d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 260 additions and 102 deletions

View file

@ -129,6 +129,7 @@ core.protocol_versions = {
["5.9.1"] = 45, ["5.9.1"] = 45,
["5.10.0"] = 46, ["5.10.0"] = 46,
["5.11.0"] = 47, ["5.11.0"] = 47,
["5.12.0"] = 48,
} }
setmetatable(core.protocol_versions, {__newindex = function() setmetatable(core.protocol_versions, {__newindex = function()

View file

@ -35,6 +35,6 @@ unittests.register("test_protocol_version", function(player)
-- The protocol version the client and server agreed on must exist in the table. -- The protocol version the client and server agreed on must exist in the table.
local match = table.key_value_swap(core.protocol_versions)[info.protocol_version] local match = table.key_value_swap(core.protocol_versions)[info.protocol_version]
assert(match ~= nil)
print(string.format("client proto matched: %s sent: %s", match, info.version_string)) print(string.format("client proto matched: %s sent: %s", match, info.version_string))
assert(match ~= nil)
end, {player = true}) end, {player = true})

View file

@ -902,7 +902,6 @@ void Client::request_media(const std::vector<std::string> &file_requests)
FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests"); FATAL_ERROR_IF(file_requests_size > 0xFFFF, "Unsupported number of file requests");
// Packet dynamicly resized
NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0); NetworkPacket pkt(TOSERVER_REQUEST_MEDIA, 2 + 0);
pkt << (u16) (file_requests_size & 0xFFFF); pkt << (u16) (file_requests_size & 0xFFFF);

View file

@ -606,10 +606,6 @@ void Client::handleCommand_DeathScreenLegacy(NetworkPacket* pkt)
void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt) void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt)
{ {
u16 num_files;
*pkt >> num_files;
infostream << "Client: Received media announcement: packet size: " infostream << "Client: Received media announcement: packet size: "
<< pkt->getSize() << std::endl; << pkt->getSize() << std::endl;
@ -619,9 +615,7 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt)
"we already saw another announcement" : "we already saw another announcement" :
"all media has been received already"; "all media has been received already";
errorstream << "Client: Received media announcement but " errorstream << "Client: Received media announcement but "
<< problem << "! " << problem << "!" << std::endl;
<< " files=" << num_files
<< " size=" << pkt->getSize() << std::endl;
return; return;
} }
@ -629,16 +623,36 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt)
// updating content definitions // updating content definitions
sanity_check(!m_mesh_update_manager->isRunning()); sanity_check(!m_mesh_update_manager->isRunning());
for (u16 i = 0; i < num_files; i++) { if (m_proto_ver >= 48) {
// compressed table of media names
std::vector<std::string> names;
{
std::istringstream iss(pkt->readLongString(), std::ios::binary);
std::stringstream ss(std::ios::in | std::ios::out | std::ios::binary);
decompressZstd(iss, ss);
names = deserializeString16Array(ss);
}
// raw hash for each media file
for (auto &name : names) {
auto sha1_raw = pkt->readRawString(20);
m_media_downloader->addFile(name, sha1_raw);
}
} else {
u16 num_files;
*pkt >> num_files;
std::string name, sha1_base64; std::string name, sha1_base64;
for (u16 i = 0; i < num_files; i++) {
*pkt >> name >> sha1_base64;
*pkt >> name >> sha1_base64; std::string sha1_raw = base64_decode(sha1_base64);
m_media_downloader->addFile(name, sha1_raw);
std::string sha1_raw = base64_decode(sha1_base64); }
m_media_downloader->addFile(name, sha1_raw);
} }
{ {
// Remote media servers
std::string str; std::string str;
*pkt >> str; *pkt >> str;
@ -657,18 +671,6 @@ void Client::handleCommand_AnnounceMedia(NetworkPacket* pkt)
void Client::handleCommand_Media(NetworkPacket* pkt) void Client::handleCommand_Media(NetworkPacket* pkt)
{ {
/*
u16 command
u16 total number of file bunches
u16 index of this bunch
u32 number of files in this bunch
for each file {
u16 length of name
string name
u32 length of data
data
}
*/
u16 num_bunches; u16 num_bunches;
u16 bunch_i; u16 bunch_i;
u32 num_files; u32 num_files;
@ -695,6 +697,12 @@ void Client::handleCommand_Media(NetworkPacket* pkt)
*pkt >> name; *pkt >> name;
data = pkt->readLongString(); data = pkt->readLongString();
if (m_proto_ver >= 48) {
std::istringstream iss(data, std::ios::binary);
std::ostringstream oss(std::ios::binary);
decompressZstd(iss, oss);
data = oss.str();
}
bool ok = false; bool ok = false;
if (init_phase) { if (init_phase) {
@ -729,7 +737,10 @@ void Client::handleCommand_NodeDef(NetworkPacket* pkt)
// Decompress node definitions // Decompress node definitions
std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out);
decompressZlib(tmp_is, tmp_os); if (m_proto_ver >= 48)
decompressZstd(tmp_is, tmp_os);
else
decompressZlib(tmp_is, tmp_os);
// Deserialize node definitions // Deserialize node definitions
m_nodedef->deSerialize(tmp_os, m_proto_ver); m_nodedef->deSerialize(tmp_os, m_proto_ver);
@ -748,7 +759,10 @@ void Client::handleCommand_ItemDef(NetworkPacket* pkt)
// Decompress item definitions // Decompress item definitions
std::istringstream tmp_is(pkt->readLongString(), std::ios::binary); std::istringstream tmp_is(pkt->readLongString(), std::ios::binary);
std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out); std::stringstream tmp_os(std::ios::binary | std::ios::in | std::ios::out);
decompressZlib(tmp_is, tmp_os); if (m_proto_ver >= 48)
decompressZstd(tmp_is, tmp_os);
else
decompressZlib(tmp_is, tmp_os);
// Deserialize node definitions // Deserialize node definitions
m_itemdef->deSerialize(tmp_os, m_proto_ver); m_itemdef->deSerialize(tmp_os, m_proto_ver);

View file

@ -69,6 +69,18 @@ void NetworkPacket::putRawString(const char* src, u32 len)
m_read_offset += len; m_read_offset += len;
} }
void NetworkPacket::readRawString(char *dst, u32 len)
{
checkReadOffset(m_read_offset, len);
if (len == 0)
return;
memcpy(dst, &m_data[m_read_offset], len);
m_read_offset += len;
}
NetworkPacket& NetworkPacket::operator>>(std::string& dst) NetworkPacket& NetworkPacket::operator>>(std::string& dst)
{ {
checkReadOffset(m_read_offset, 2); checkReadOffset(m_read_offset, 2);

View file

@ -51,6 +51,16 @@ public:
putRawString(src.data(), src.size()); putRawString(src.data(), src.size());
} }
// Reads bytes from packet into string buffer
void readRawString(char *dst, u32 len);
std::string readRawString(u32 len)
{
std::string s;
s.resize(len);
readRawString(&s[0], len);
return s;
}
NetworkPacket &operator>>(std::string &dst); NetworkPacket &operator>>(std::string &dst);
NetworkPacket &operator<<(std::string_view src); NetworkPacket &operator<<(std::string_view src);

View file

@ -62,10 +62,13 @@
PROTOCOL VERSION 47 PROTOCOL VERSION 47
Add particle blend mode "clip" Add particle blend mode "clip"
[scheduled bump for 5.11.0] [scheduled bump for 5.11.0]
PROTOCOL VERSION 48
Add compression to some existing packets
[scheduled bump for 5.12.0]
*/ */
// Note: Also update core.protocol_versions in builtin when bumping // Note: Also update core.protocol_versions in builtin when bumping
const u16 LATEST_PROTOCOL_VERSION = 47; const u16 LATEST_PROTOCOL_VERSION = 48;
// See also formspec [Version History] in doc/lua_api.md // See also formspec [Version History] in doc/lua_api.md
const u16 FORMSPEC_API_VERSION = 8; const u16 FORMSPEC_API_VERSION = 8;

View file

@ -33,24 +33,28 @@ enum ToClientCommand : u16
u32 supported auth methods u32 supported auth methods
std::string unused (used to be username) std::string unused (used to be username)
*/ */
TOCLIENT_AUTH_ACCEPT = 0x03, TOCLIENT_AUTH_ACCEPT = 0x03,
/* /*
Message from server to accept auth. Message from server to accept auth.
v3s16 player's position + v3f(0,BS/2,0) floatToInt'd v3f unused
u64 map seed u64 map seed
f1000 recommended send interval f1000 recommended send interval
u32 : supported auth methods for sudo mode u32 : supported auth methods for sudo mode
(where the user can change their password) (where the user can change their password)
*/ */
TOCLIENT_ACCEPT_SUDO_MODE = 0x04, TOCLIENT_ACCEPT_SUDO_MODE = 0x04,
/* /*
Sent to client to show it is in sudo mode now. Sent to client to show it is in sudo mode now.
*/ */
TOCLIENT_DENY_SUDO_MODE = 0x05, TOCLIENT_DENY_SUDO_MODE = 0x05,
/* /*
Signals client that sudo mode auth failed. Signals client that sudo mode auth failed.
*/ */
TOCLIENT_ACCESS_DENIED = 0x0A, TOCLIENT_ACCESS_DENIED = 0x0A,
/* /*
u8 reason u8 reason
@ -59,18 +63,26 @@ enum ToClientCommand : u16
*/ */
TOCLIENT_BLOCKDATA = 0x20, TOCLIENT_BLOCKDATA = 0x20,
/*
v3s16 position
serialized MapBlock
*/
TOCLIENT_ADDNODE = 0x21, TOCLIENT_ADDNODE = 0x21,
/* /*
v3s16 position v3s16 position
serialized mapnode serialized mapnode
u8 keep_metadata // Added in protocol version 22 u8 keep_metadata
*/ */
TOCLIENT_REMOVENODE = 0x22, TOCLIENT_REMOVENODE = 0x22,
/*
v3s16 position
*/
TOCLIENT_INVENTORY = 0x27, TOCLIENT_INVENTORY = 0x27,
/* /*
[0] u16 command serialized inventory
[2] serialized inventory
*/ */
TOCLIENT_TIME_OF_DAY = 0x29, TOCLIENT_TIME_OF_DAY = 0x29,
@ -167,40 +179,38 @@ enum ToClientCommand : u16
TOCLIENT_MEDIA = 0x38, TOCLIENT_MEDIA = 0x38,
/* /*
u16 total number of texture bunches u16 total number of bunches
u16 index of this bunch u16 index of this bunch
u32 number of files in this bunch u32 number of files in this bunch
for each file { for each file {
u16 length of name u16 length of name
string name string name
u32 length of data u32 length of data
data data (zstd-compressed)
} }
u16 length of remote media server url (if applicable)
string url
*/ */
TOCLIENT_NODEDEF = 0x3a, TOCLIENT_NODEDEF = 0x3a,
/* /*
u32 length of the next item u32 length of buffer
serialized NodeDefManager serialized NodeDefManager (zstd-compressed)
*/ */
TOCLIENT_ANNOUNCE_MEDIA = 0x3c, TOCLIENT_ANNOUNCE_MEDIA = 0x3c,
/* /*
u32 number of files u32 length of compressed name array
for each texture { string16array names (zstd-compressed)
u16 length of name for each file {
string name char[20] sha1_digest
u16 length of sha1_digest
string sha1_digest
} }
u16 length of remote media server url
string url
*/ */
TOCLIENT_ITEMDEF = 0x3d, TOCLIENT_ITEMDEF = 0x3d,
/* /*
u32 length of next item u32 length of buffer
serialized ItemDefManager serialized ItemDefManager (zstd-compressed)
*/ */
TOCLIENT_PLAY_SOUND = 0x3f, TOCLIENT_PLAY_SOUND = 0x3f,
@ -721,18 +731,16 @@ enum ToServerCommand : u16
TOSERVER_PLAYERPOS = 0x23, TOSERVER_PLAYERPOS = 0x23,
/* /*
[0] u16 command v3s32 position*100
[2] v3s32 position*100 v3s32 speed*100
[2+12] v3s32 speed*100 s32 pitch*100
[2+12+12] s32 pitch*100 s32 yaw*100
[2+12+12+4] s32 yaw*100 u32 keyPressed
[2+12+12+4+4] u32 keyPressed u8 fov*80
[2+12+12+4+4+4] u8 fov*80 u8 ceil(wanted_range / MAP_BLOCKSIZE)
[2+12+12+4+4+4+1] u8 ceil(wanted_range / MAP_BLOCKSIZE) u8 camera_inverted (bool)
[2+12+12+4+4+4+1+1] u8 camera_inverted (bool) f32 movement_speed
[2+12+12+4+4+4+1+1+1] f32 movement_speed f32 movement_direction
[2+12+12+4+4+4+1+1+1+4] f32 movement_direction
*/ */
TOSERVER_GOTBLOCKS = 0x24, TOSERVER_GOTBLOCKS = 0x24,

View file

@ -1487,17 +1487,20 @@ void Server::SendAccessDenied(session_t peer_id, AccessDeniedCode reason,
void Server::SendItemDef(session_t peer_id, void Server::SendItemDef(session_t peer_id,
IItemDefManager *itemdef, u16 protocol_version) IItemDefManager *itemdef, u16 protocol_version)
{ {
auto *client = m_clients.getClientNoEx(peer_id, CS_Created);
assert(client);
NetworkPacket pkt(TOCLIENT_ITEMDEF, 0, peer_id); NetworkPacket pkt(TOCLIENT_ITEMDEF, 0, peer_id);
/*
u16 command
u32 length of the next item
zlib-compressed serialized ItemDefManager
*/
std::ostringstream tmp_os(std::ios::binary);
itemdef->serialize(tmp_os, protocol_version);
std::ostringstream tmp_os2(std::ios::binary); std::ostringstream tmp_os2(std::ios::binary);
compressZlib(tmp_os.str(), tmp_os2); {
std::ostringstream tmp_os(std::ios::binary);
itemdef->serialize(tmp_os, protocol_version);
if (client->net_proto_version >= 48)
compressZstd(tmp_os.str(), tmp_os2);
else
compressZlib(tmp_os.str(), tmp_os2);
}
pkt.putLongString(tmp_os2.str()); pkt.putLongString(tmp_os2.str());
// Make data buffer // Make data buffer
@ -1510,18 +1513,20 @@ void Server::SendItemDef(session_t peer_id,
void Server::SendNodeDef(session_t peer_id, void Server::SendNodeDef(session_t peer_id,
const NodeDefManager *nodedef, u16 protocol_version) const NodeDefManager *nodedef, u16 protocol_version)
{ {
auto *client = m_clients.getClientNoEx(peer_id, CS_Created);
assert(client);
NetworkPacket pkt(TOCLIENT_NODEDEF, 0, peer_id); NetworkPacket pkt(TOCLIENT_NODEDEF, 0, peer_id);
/*
u16 command
u32 length of the next item
zlib-compressed serialized NodeDefManager
*/
std::ostringstream tmp_os(std::ios::binary);
nodedef->serialize(tmp_os, protocol_version);
std::ostringstream tmp_os2(std::ios::binary); std::ostringstream tmp_os2(std::ios::binary);
compressZlib(tmp_os.str(), tmp_os2); {
std::ostringstream tmp_os(std::ios::binary);
nodedef->serialize(tmp_os, protocol_version);
if (client->net_proto_version >= 48)
compressZstd(tmp_os.str(), tmp_os2);
else
compressZlib(tmp_os.str(), tmp_os2);
}
pkt.putLongString(tmp_os2.str()); pkt.putLongString(tmp_os2.str());
// Make data buffer // Make data buffer
@ -2583,13 +2588,12 @@ bool Server::addMediaFile(const std::string &filename,
} }
std::string sha1 = hashing::sha1(filedata); std::string sha1 = hashing::sha1(filedata);
std::string sha1_base64 = base64_encode(sha1);
std::string sha1_hex = hex_encode(sha1); std::string sha1_hex = hex_encode(sha1);
if (digest_to) if (digest_to)
*digest_to = sha1; *digest_to = sha1;
// Put in list // Put in list
m_media[filename] = MediaInfo(filepath, sha1_base64); m_media[filename] = MediaInfo(filepath, sha1);
verbosestream << "Server: " << sha1_hex << " is " << filename verbosestream << "Server: " << sha1_hex << " is " << filename
<< std::endl; << std::endl;
@ -2651,20 +2655,48 @@ void Server::sendMediaAnnouncement(session_t peer_id, const std::string &lang_co
}; };
// Make packet // Make packet
auto *client = m_clients.getClientNoEx(peer_id, CS_Created);
assert(client);
NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id); NetworkPacket pkt(TOCLIENT_ANNOUNCE_MEDIA, 0, peer_id);
u16 media_sent = 0; size_t media_sent = 0;
for (const auto &i : m_media) { if (client->net_proto_version < 48) {
if (include(i.first, i.second)) for (const auto &i : m_media) {
media_sent++; if (include(i.first, i.second))
} media_sent++;
pkt << media_sent; }
assert(media_sent < U16_MAX);
pkt << static_cast<u16>(media_sent);
for (const auto &i : m_media) {
if (include(i.first, i.second))
pkt << i.first << base64_encode(i.second.sha1_digest);
}
} else {
std::vector<std::string> names;
for (const auto &i : m_media) {
if (include(i.first, i.second))
names.emplace_back(i.first);
}
media_sent = names.size();
for (const auto &i : m_media) { // compressed table of media names
if (include(i.first, i.second)) {
pkt << i.first << i.second.sha1_digest; std::ostringstream oss(std::ios::binary);
auto tmp = serializeString16Array(names);
compressZstd(tmp, oss);
pkt.putLongString(oss.str());
}
// then the raw hash for each file
for (const auto &i : m_media) {
if (include(i.first, i.second)) {
assert(i.second.sha1_digest.size() == 20);
pkt.putRawString(i.second.sha1_digest);
}
}
} }
// and the remote media server(s)
pkt << g_settings->get("remote_media"); pkt << g_settings->get("remote_media");
Send(&pkt); Send(&pkt);
@ -2694,8 +2726,11 @@ void Server::sendRequestedMedia(session_t peer_id,
auto *client = getClient(peer_id, CS_DefinitionsSent); auto *client = getClient(peer_id, CS_DefinitionsSent);
assert(client); assert(client);
const bool compress = client->net_proto_version >= 48;
infostream << "Server::sendRequestedMedia(): Sending " infostream << "Server::sendRequestedMedia(): Sending "
<< tosend.size() << " files to " << client->getName() << std::endl; << tosend.size() << " files to " << client->getName()
<< (compress ? " (compressed)" : "") << std::endl;
/* Read files and prepare bunches */ /* Read files and prepare bunches */
@ -2713,6 +2748,7 @@ void Server::sendRequestedMedia(session_t peer_id,
// the amount of bunches quite well (at the expense of overshooting). // the amount of bunches quite well (at the expense of overshooting).
u32 file_size_bunch_total = 0; u32 file_size_bunch_total = 0;
size_t bytes_compressed = 0, bytes_uncompressed = 0;
for (const std::string &name : tosend) { for (const std::string &name : tosend) {
auto it = m_media.find(name); auto it = m_media.find(name);
@ -2739,9 +2775,19 @@ void Server::sendRequestedMedia(session_t peer_id,
if (!fs::ReadFile(m.path, data, true)) { if (!fs::ReadFile(m.path, data, true)) {
continue; continue;
} }
file_size_bunch_total += data.size(); bytes_uncompressed += data.size();
if (compress) {
// Zstd is very fast and can handle non-compressible data efficiently
// so we can just throw it at every file. Still we don't want to
// spend too much here, so we use the lowest compression level.
std::ostringstream oss(std::ios::binary);
compressZstd(data, oss, 1);
data = oss.str();
}
bytes_compressed += data.size();
// Put in list // Put in list
file_size_bunch_total += data.size();
file_bunches.back().emplace_back(name, m.path, std::move(data)); file_bunches.back().emplace_back(name, m.path, std::move(data));
// Start next bunch if got enough data // Start next bunch if got enough data
@ -2756,17 +2802,6 @@ void Server::sendRequestedMedia(session_t peer_id,
const u16 num_bunches = file_bunches.size(); const u16 num_bunches = file_bunches.size();
for (u16 i = 0; i < num_bunches; i++) { for (u16 i = 0; i < num_bunches; i++) {
auto &bunch = file_bunches[i]; auto &bunch = file_bunches[i];
/*
u16 total number of media bunches
u16 index of this bunch
u32 number of files in this bunch
for each file {
u16 length of name
string name
u32 length of data
data
}
*/
NetworkPacket pkt(TOCLIENT_MEDIA, 4 + 0, peer_id); NetworkPacket pkt(TOCLIENT_MEDIA, 4 + 0, peer_id);
const u32 bunch_size = bunch.size(); const u32 bunch_size = bunch.size();
@ -2784,6 +2819,14 @@ void Server::sendRequestedMedia(session_t peer_id,
<< " size=" << pkt.getSize() << std::endl; << " size=" << pkt.getSize() << std::endl;
Send(&pkt); Send(&pkt);
} }
if (compress && bytes_uncompressed != 0) {
int percent = bytes_compressed / (float)bytes_uncompressed * 100;
int diff = (int)bytes_compressed - (int)bytes_uncompressed;
infostream << "Server::sendRequestedMedia(): size after compression "
<< percent << "% (" << (diff > 0 ? '+' : '-') << std::abs(diff)
<< " byte)" << std::endl;
}
} }
void Server::stepPendingDynMediaCallbacks(float dtime) void Server::stepPendingDynMediaCallbacks(float dtime)
@ -4210,7 +4253,7 @@ std::unordered_map<std::string, std::string> Server::getMediaList()
for (auto &it : m_media) { for (auto &it : m_media) {
if (it.second.no_announce) if (it.second.no_announce)
continue; continue;
ret.emplace(base64_decode(it.second.sha1_digest), it.second.path); ret.emplace(it.second.sha1_digest, it.second.path);
} }
return ret; return ret;
} }

View file

@ -90,7 +90,7 @@ enum ClientDeletionReason {
struct MediaInfo struct MediaInfo
{ {
std::string path; std::string path;
std::string sha1_digest; // base64-encoded std::string sha1_digest;
// true = not announced in TOCLIENT_ANNOUNCE_MEDIA (at player join) // true = not announced in TOCLIENT_ANNOUNCE_MEDIA (at player join)
bool no_announce; bool no_announce;
// does what it says. used by some cases of dynamic media. // does what it says. used by some cases of dynamic media.

View file

@ -105,6 +105,67 @@ std::string deSerializeString32(std::istream &is)
return s; return s;
} }
////
//// String Array
////
std::string serializeString16Array(const std::vector<std::string> &array)
{
std::string ret;
const auto &at = [&] (size_t index) {
return reinterpret_cast<u8*>(&ret[index]);
};
if (array.size() > U32_MAX)
throw SerializationError("serializeString16Array: too many strings");
ret.resize(4 + array.size() * 2);
writeU32(at(0), array.size());
// Serialize lengths next to each other
size_t total = 0;
for (u32 i = 0; i < array.size(); i++) {
auto &s = array[i];
if (s.size() > STRING_MAX_LEN)
throw SerializationError("serializeString16Array: string too long");
writeU16(at(4 + 2*i), s.size());
total += s.size();
}
// Now the contents
ret.reserve(ret.size() + total);
for (auto &s : array)
ret.append(s);
return ret;
}
std::vector<std::string> deserializeString16Array(std::istream &is)
{
std::vector<std::string> ret;
u32 count = readU32(is);
if (is.gcount() != 4)
throw SerializationError("deserializeString16Array: count not read");
ret.resize(count);
// prepare string buffers as we read the sizes
for (auto &sbuf : ret) {
u16 size = readU16(is);
if (is.gcount() != 2)
throw SerializationError("deserializeString16Array: size not read");
sbuf.resize(size);
}
// now extract the strings
for (auto &sbuf : ret) {
is.read(sbuf.data(), sbuf.size());
if (is.gcount() != (std::streamsize) sbuf.size())
throw SerializationError("deserializeString16Array: truncated");
}
return ret;
}
//// ////
//// JSON-like strings //// JSON-like strings
//// ////

View file

@ -469,3 +469,10 @@ std::string serializeJsonStringIfNeeded(std::string_view s);
// Parses a string serialized by serializeJsonStringIfNeeded. // Parses a string serialized by serializeJsonStringIfNeeded.
std::string deSerializeJsonStringIfNeeded(std::istream &is); std::string deSerializeJsonStringIfNeeded(std::istream &is);
// Serializes an array of strings (max 2^16 chars each)
// Output is well suited for compression :)
std::string serializeString16Array(const std::vector<std::string> &array);
// Deserializes a string array
std::vector<std::string> deserializeString16Array(std::istream &is);