Skip to content

Commit

Permalink
Encode high codepoints as surrogates to safely transport wchar_t over…
Browse files Browse the repository at this point in the history
… network

fixes #7643
  • Loading branch information
sfan5 committed Feb 2, 2021
1 parent c834d2a commit 674d67f
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 25 deletions.
49 changes: 40 additions & 9 deletions src/network/networkpacket.cpp
Expand Up @@ -50,7 +50,7 @@ void NetworkPacket::checkReadOffset(u32 from_offset, u32 field_size)
}
}

void NetworkPacket::putRawPacket(u8 *data, u32 datasize, session_t peer_id)
void NetworkPacket::putRawPacket(const u8 *data, u32 datasize, session_t peer_id)
{
// If a m_command is already set, we are rewriting on same packet
// This is not permitted
Expand Down Expand Up @@ -145,6 +145,8 @@ void NetworkPacket::putLongString(const std::string &src)
putRawString(src.c_str(), msgsize);
}

static constexpr bool NEED_SURROGATE_CODING = sizeof(wchar_t) > 2;

NetworkPacket& NetworkPacket::operator>>(std::wstring& dst)
{
checkReadOffset(m_read_offset, 2);
Expand All @@ -160,9 +162,16 @@ NetworkPacket& NetworkPacket::operator>>(std::wstring& dst)
checkReadOffset(m_read_offset, strLen * 2);

dst.reserve(strLen);
for(u16 i=0; i<strLen; i++) {
wchar_t c16 = readU16(&m_data[m_read_offset]);
dst.append(&c16, 1);
for (u16 i = 0; i < strLen; i++) {
wchar_t c = readU16(&m_data[m_read_offset]);
if (NEED_SURROGATE_CODING && c >= 0xD800 && c < 0xDC00 && i+1 < strLen) {
i++;
m_read_offset += sizeof(u16);

wchar_t c2 = readU16(&m_data[m_read_offset]);
c = 0x10000 + ( ((c & 0x3ff) << 10) | (c2 & 0x3ff) );
}
dst.push_back(c);
m_read_offset += sizeof(u16);
}

Expand All @@ -175,15 +184,37 @@ NetworkPacket& NetworkPacket::operator<<(const std::wstring &src)
throw PacketError("String too long");
}

u16 msgsize = src.size();
if (!NEED_SURROGATE_CODING || src.size() == 0) {
*this << static_cast<u16>(src.size());
for (u16 i = 0; i < src.size(); i++)
*this << static_cast<u16>(src[i]);

*this << msgsize;
return *this;
}

// Write string
for (u16 i=0; i<msgsize; i++) {
*this << (u16) src[i];
// write dummy value, to be overwritten later
const u32 len_offset = m_read_offset;
u32 written = 0;
*this << static_cast<u16>(0xfff0);

for (u16 i = 0; i < src.size(); i++) {
wchar_t c = src[i];
if (c > 0xffff) {
// Encode high code-points as surrogate pairs
u32 n = c - 0x10000;
*this << static_cast<u16>(0xD800 | (n >> 10))
<< static_cast<u16>(0xDC00 | (n & 0x3ff));
written += 2;
} else {
*this << static_cast<u16>(c);
written++;
}
}

if (written > WIDE_STRING_MAX_LEN)
throw PacketError("String too long");
writeU16(&m_data[len_offset], written);

return *this;
}

Expand Down
2 changes: 1 addition & 1 deletion src/network/networkpacket.h
Expand Up @@ -34,7 +34,7 @@ class NetworkPacket

~NetworkPacket();

void putRawPacket(u8 *data, u32 datasize, session_t peer_id);
void putRawPacket(const u8 *data, u32 datasize, session_t peer_id);
void clear();

// Getters
Expand Down
15 changes: 1 addition & 14 deletions src/network/serverpackethandler.cpp
Expand Up @@ -752,21 +752,8 @@ void Server::handleCommand_InventoryAction(NetworkPacket* pkt)

void Server::handleCommand_ChatMessage(NetworkPacket* pkt)
{
/*
u16 command
u16 length
wstring message
*/
u16 len;
*pkt >> len;

std::wstring message;
for (u16 i = 0; i < len; i++) {
u16 tmp_wchar;
*pkt >> tmp_wchar;

message += (wchar_t)tmp_wchar;
}
*pkt >> message;

session_t peer_id = pkt->getPeerId();
RemotePlayer *player = m_env->getPlayer(peer_id);
Expand Down
3 changes: 2 additions & 1 deletion src/server.cpp
Expand Up @@ -1482,7 +1482,8 @@ void Server::SendChatMessage(session_t peer_id, const ChatMessage &message)
NetworkPacket pkt(TOCLIENT_CHAT_MESSAGE, 0, peer_id);
u8 version = 1;
u8 type = message.type;
pkt << version << type << std::wstring(L"") << message.message << (u64)message.timestamp;
pkt << version << type << message.sender << message.message
<< static_cast<u64>(message.timestamp);

if (peer_id != PEER_ID_INEXISTENT) {
RemotePlayer *player = m_env->getPlayer(peer_id);
Expand Down
35 changes: 35 additions & 0 deletions src/unittest/test_connection.cpp
Expand Up @@ -39,6 +39,7 @@ class TestConnection : public TestBase {

void runTests(IGameDef *gamedef);

void testNetworkPacketSerialize();
void testHelpers();
void testConnectSendReceive();
};
Expand All @@ -47,6 +48,7 @@ static TestConnection g_test_instance;

void TestConnection::runTests(IGameDef *gamedef)
{
TEST(testNetworkPacketSerialize);
TEST(testHelpers);
TEST(testConnectSendReceive);
}
Expand Down Expand Up @@ -78,6 +80,39 @@ struct Handler : public con::PeerHandler
const char *name;
};

void TestConnection::testNetworkPacketSerialize()
{
const static u8 expected[] = {
0x00, 0x7b,
0x00, 0x02, 0xd8, 0x42, 0xdf, 0x9a
};

if (sizeof(wchar_t) == 2)
warningstream << __func__ << " may fail on this platform." << std::endl;

{
NetworkPacket pkt(123, 0);

// serializing wide strings should do surrogate encoding, we test that here
pkt << std::wstring(L"\U00020b9a");

SharedBuffer<u8> buf = pkt.oldForgePacket();
UASSERTEQ(int, buf.getSize(), sizeof(expected));
UASSERT(!memcmp(expected, &buf[0], buf.getSize()));
}

{
NetworkPacket pkt;
pkt.putRawPacket(expected, sizeof(expected), 0);

// same for decoding
std::wstring pkt_s;
pkt >> pkt_s;

UASSERT(pkt_s == L"\U00020b9a");
}
}

void TestConnection::testHelpers()
{
// Some constants for testing
Expand Down

0 comments on commit 674d67f

Please sign in to comment.