mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-03 19:33:43 +00:00
Every MASTER service packet is now an LUBitStream struct (docs/PacketArchitecture.md, PR 14) and the master server's switch is a dispatch map (PacketDispatcher), as are the master handlers of the world, chat and dashboard servers. - dNet/MasterPackets.h: RequestZoneTransfer, RequestZoneTransferResponse, ServerInfo, RequestSessionKey, SetSessionKey, SessionKeyResponse, NewSessionAlert, PlayerAdded / PlayerRemoved, CreatePrivateZone, RequestPrivateZone (passwords still cut to 50 characters when read), WorldReady, WorldReadyInfo (WORLD_READY to the dashboard), PrepZone, Shutdown, ShutdownResponse, WorldShutDown (SHUTDOWN_RESPONSE to the dashboard), ShutdownUniverse, AffirmTransferRequest/Response, RequestServerList, ServerListResponse, DashboardShutdown, ConfigReload, InstanceShutdown. The Send* functions are gone; MasterPackets::SendToMaster(msg) and SendTo(sysAddr, msg) send a struct. - The dashboard and instance migration structs (PlayerAction, DataChanged, Dashboard messages, MessageCapture, InstanceMigration) are LUBitStreams of the MASTER service now and moved to dNet/master/, included by MasterPackets.h. Their payloads are unchanged; master forwards them by re-serializing the struct instead of copying raw bytes. - InstanceManager, ZoneInstanceManager, MigrationCoordinator, dServer (server info, zone transfer response), auth (SET_SESSION_KEY), the world (session keys, player added and removed, world ready, shutdown response, affirmations, prep zone, shutdown universe) and the dashboard (server list, instance shutdown, config reload, announcements, player actions, message capture) send and read structs. - The login stamps are a `stamps` field of RequestZoneTransfer and RequestZoneTransferResponse (read leniently as before: a message without them reads as empty); master adds its stamps in the REQUEST_ZONE_TRANSFER handler and when it answers, as it did. - InstanceManager::GetInstanceBySysAddr takes a const address. Verified: tests/dGameTests/dNetTests/Legacy/MasterPacketsLegacy.h is a verbatim copy of the old writers and readers; MasterPacketsTests requires identical bytes for a grid of inputs, checks the old readers read what the structs write, round trips and truncation, hand written golden packets, that zone transfers without stamps still read, that the dashboard/migration structs write what "header + Serialize" wrote, and that the dispatcher drops truncated packets. No wire bytes changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
473 lines
20 KiB
C++
473 lines
20 KiB
C++
#include "MasterPackets.h"
|
|
#include "master/DashboardMessages.h"
|
|
#include "master/DataChanged.h"
|
|
#include "master/InstanceMigration.h"
|
|
#include "master/MessageCapture.h"
|
|
#include "master/PlayerAction.h"
|
|
#include "PacketDispatcher.h"
|
|
#include "PacketTestUtils.h"
|
|
#include "Game.h"
|
|
#include "Logger.h"
|
|
#include "dGameMessagesTests/GameMessageTestUtils.h"
|
|
#include "Legacy/MasterPacketsLegacy.h"
|
|
|
|
#include <array>
|
|
#include <functional>
|
|
#include <limits>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
using namespace PacketTestUtils;
|
|
using GameMessageTestUtils::RoundTrip;
|
|
using GameMessageTestUtils::ExpectTruncatedFails;
|
|
|
|
namespace {
|
|
const std::array<std::string, 4> g_Strings = { "", "localhost", "a password that is longer than fifty characters, which master cuts", "10.0.0.255" };
|
|
const std::array<uint64_t, 3> g_RequestIds = { 0, 1, 0x0102030405060708ULL };
|
|
const std::array<uint32_t, 3> g_Numbers = { 0, 1100, 0xFFFFFFFF };
|
|
|
|
PacketBytes StructPacket(const LUBitStream& msg) {
|
|
RakNet::BitStream bitStream;
|
|
msg.WritePacket(bitStream);
|
|
return FromBitStream(bitStream);
|
|
}
|
|
|
|
PacketBytes Written(const std::function<void(RakNet::BitStream&)>& write) {
|
|
RakNet::BitStream bitStream;
|
|
write(bitStream);
|
|
return FromBitStream(bitStream);
|
|
}
|
|
|
|
// A stream positioned after the header of msg's packet
|
|
void LoadPayload(RakNet::BitStream& bitStream, const LUBitStream& msg) {
|
|
msg.WritePacket(bitStream);
|
|
bitStream.IgnoreBytes(8);
|
|
}
|
|
|
|
// The old way the dashboard and migration structs were sent: the header, then Serialize
|
|
template<typename T>
|
|
void ExpectHeaderThenSerialize(const T& msg, MessageType::Master id) {
|
|
RakNet::BitStream old;
|
|
BitStreamUtils::WriteHeader(old, ServiceType::MASTER, id);
|
|
msg.Serialize(old);
|
|
EXPECT_PACKET_EQ(FromBitStream(old), StructPacket(msg));
|
|
RoundTrip(msg);
|
|
}
|
|
}
|
|
|
|
TEST(MasterPacketsTests, ZoneTransferMatchesLegacy) {
|
|
Stamps someStamps;
|
|
someStamps.list.emplace_back(eStamps::PASSPORT_AUTH_START, 0, 1700000000);
|
|
someStamps.list.emplace_back(eStamps::PASSPORT_AUTH_WORLD_PACKET_RECEIVED, 1100, 1700000001);
|
|
for (const auto& stamps : { Stamps{}, someStamps })
|
|
for (const auto requestID : g_RequestIds) {
|
|
for (const bool mythranShift : { false, true }) {
|
|
for (const auto zone : g_Numbers) {
|
|
MasterPackets::RequestZoneTransfer request;
|
|
request.requestID = requestID;
|
|
request.mythranShift = mythranShift;
|
|
request.zoneID = zone;
|
|
request.cloneID = zone ^ 0x5555;
|
|
request.stamps = stamps;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendZoneTransferRequest(b, requestID, mythranShift, zone, zone ^ 0x5555, stamps); }), StructPacket(request));
|
|
|
|
RakNet::BitStream stream; LoadPayload(stream, request);
|
|
const auto legacy = LegacyMaster::ReadZoneTransferRequest(stream);
|
|
const auto copy = RoundTrip(request);
|
|
EXPECT_EQ(legacy.requestID, copy.requestID);
|
|
EXPECT_EQ(legacy.mythranShift, copy.mythranShift);
|
|
EXPECT_EQ(legacy.zoneID, copy.zoneID);
|
|
EXPECT_EQ(legacy.zoneClone, copy.cloneID);
|
|
EXPECT_EQ(legacy.stamps.size(), copy.stamps.size());
|
|
|
|
for (const auto& ip : g_Strings) {
|
|
for (const uint32_t port : { 0u, 2001u, 70000u }) {
|
|
MasterPackets::RequestZoneTransferResponse response;
|
|
response.requestID = requestID;
|
|
response.mythranShift = mythranShift;
|
|
response.zoneID = zone;
|
|
response.zoneInstance = zone / 2;
|
|
response.zoneClone = zone / 3;
|
|
response.serverPort = static_cast<uint16_t>(port);
|
|
response.serverIP = LUString(ip, 255);
|
|
response.stamps = stamps;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendZoneTransferResponse(b, requestID, mythranShift, zone, zone / 2, zone / 3, ip, port, stamps); }), StructPacket(response));
|
|
|
|
RakNet::BitStream responseStream; LoadPayload(responseStream, response);
|
|
const auto legacyResponse = LegacyMaster::ReadZoneTransferResponse(responseStream);
|
|
const auto responseCopy = RoundTrip(response);
|
|
EXPECT_EQ(legacyResponse.requestID, responseCopy.requestID);
|
|
EXPECT_EQ(legacyResponse.mythranShift, responseCopy.mythranShift > 0);
|
|
EXPECT_EQ(legacyResponse.zoneInstance, responseCopy.zoneInstance);
|
|
EXPECT_EQ(legacyResponse.serverPort, responseCopy.serverPort);
|
|
EXPECT_EQ(legacyResponse.serverIP, responseCopy.serverIP.string);
|
|
EXPECT_EQ(legacyResponse.stamps.size(), responseCopy.stamps.size());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Senders without the login stamps (older servers) still read, with no stamps; a cut off fixed part does not
|
|
TEST(MasterPacketsTests, ZoneTransferStampsAreOptional) {
|
|
RakNet::BitStream noStamps;
|
|
noStamps.Write<uint64_t>(5);
|
|
noStamps.Write<uint8_t>(1);
|
|
noStamps.Write<uint32_t>(1100);
|
|
noStamps.Write<uint32_t>(0);
|
|
MasterPackets::RequestZoneTransfer request;
|
|
ASSERT_TRUE(request.Deserialize(noStamps));
|
|
EXPECT_EQ(request.requestID, 5u);
|
|
EXPECT_TRUE(request.stamps.empty());
|
|
|
|
MasterPackets::RequestZoneTransfer full;
|
|
full.requestID = 5;
|
|
RakNet::BitStream all;
|
|
full.Serialize(all);
|
|
for (uint32_t bits = 0; bits < 8 * (8 + 1 + 4 + 4); bits++) {
|
|
RakNet::BitStream prefix;
|
|
prefix.WriteBits(all.GetData(), bits, false);
|
|
MasterPackets::RequestZoneTransfer copy;
|
|
EXPECT_FALSE(copy.Deserialize(prefix)) << bits;
|
|
}
|
|
}
|
|
|
|
TEST(MasterPacketsTests, ServerInfoMatchesLegacy) {
|
|
for (const auto type : { ServiceType::AUTH, ServiceType::CHAT, ServiceType::WORLD, ServiceType::DASHBOARD }) {
|
|
for (const auto& ip : g_Strings) {
|
|
if (ip.size() > 33) continue; // LUString(33)
|
|
for (const int instance : { 0, 7, -1 }) {
|
|
MasterPackets::ServerInfo info;
|
|
info.port = 2001;
|
|
info.zoneID = 1100;
|
|
info.instanceID = static_cast<uint32_t>(instance);
|
|
info.serverType = type;
|
|
info.ip = LUString(ip);
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendServerInfo(b, 2001, 1100u, instance, type, ip); }), StructPacket(info));
|
|
|
|
RakNet::BitStream stream; LoadPayload(stream, info);
|
|
const auto legacy = LegacyMaster::ReadServerInfo(stream);
|
|
const auto copy = RoundTrip(info);
|
|
EXPECT_EQ(legacy.theirPort, copy.port);
|
|
EXPECT_EQ(legacy.theirInstanceID, copy.instanceID);
|
|
EXPECT_EQ(legacy.theirServerType, copy.serverType);
|
|
EXPECT_EQ(legacy.theirIP, copy.ip.string);
|
|
ExpectTruncatedFails(info);
|
|
}
|
|
}
|
|
MasterPackets::ServerInfo offline;
|
|
offline.serverType = type;
|
|
offline.ip = LUString("offline");
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteServerOffline(b, type); }), StructPacket(offline));
|
|
}
|
|
}
|
|
|
|
TEST(MasterPacketsTests, SessionKeysMatchLegacy) {
|
|
for (const uint32_t key : { 0u, 12345u, 0xFFFFFFFFu }) {
|
|
for (const auto& name : g_Strings) {
|
|
if (name.size() > 33) continue;
|
|
MasterPackets::SetSessionKey set;
|
|
set.sessionKey = key;
|
|
set.username = LUString(name);
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteSessionKey(b, MessageType::Master::SET_SESSION_KEY, key, LUString(name)); }), StructPacket(set));
|
|
RoundTrip(set);
|
|
ExpectTruncatedFails(set);
|
|
|
|
MasterPackets::NewSessionAlert alert;
|
|
alert.sessionKey = key;
|
|
alert.username = LUString(name);
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteSessionKey(b, MessageType::Master::NEW_SESSION_ALERT, key, LUString(name)); }), StructPacket(alert));
|
|
RoundTrip(alert);
|
|
|
|
MasterPackets::SessionKeyResponse response;
|
|
response.sessionKey = key;
|
|
response.username = LUWString(name);
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteSessionKeyResponse(b, key, LUWString(name)); }), StructPacket(response));
|
|
RoundTrip(response);
|
|
ExpectTruncatedFails(response);
|
|
|
|
MasterPackets::RequestSessionKey request;
|
|
request.username = LUWString(name);
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteRequestSessionKey(b, LUWString(name)); }), StructPacket(request));
|
|
RoundTrip(request);
|
|
ExpectTruncatedFails(request);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(MasterPacketsTests, PrivateZonesMatchLegacy) {
|
|
for (const auto& password : g_Strings) {
|
|
MasterPackets::CreatePrivateZone create;
|
|
create.zoneID = 1150;
|
|
create.cloneID = 0xAABBCCDD;
|
|
create.password = password;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendZoneCreatePrivate(b, 1150, 0xAABBCCDD, password); }), StructPacket(create));
|
|
|
|
RakNet::BitStream stream; LoadPayload(stream, create);
|
|
const auto legacy = LegacyMaster::ReadCreatePrivateZone(stream);
|
|
RakNet::BitStream stream2; LoadPayload(stream2, create);
|
|
MasterPackets::CreatePrivateZone read;
|
|
ASSERT_TRUE(read.Deserialize(stream2));
|
|
EXPECT_EQ(legacy.first.first, read.zoneID);
|
|
EXPECT_EQ(legacy.first.second, read.cloneID);
|
|
EXPECT_EQ(legacy.second, read.password); // both cut at 50 characters
|
|
if (password.size() <= 50) RoundTrip(create);
|
|
|
|
for (const bool mythranShift : { false, true }) {
|
|
MasterPackets::RequestPrivateZone request;
|
|
request.requestID = 0x0102030405060708ULL;
|
|
request.mythranShift = mythranShift;
|
|
request.password = password;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendZoneRequestPrivate(b, 0x0102030405060708ULL, mythranShift, password); }), StructPacket(request));
|
|
|
|
RakNet::BitStream requestStream; LoadPayload(requestStream, request);
|
|
const auto legacyRequest = LegacyMaster::ReadRequestPrivateZone(requestStream);
|
|
RakNet::BitStream requestStream2; LoadPayload(requestStream2, request);
|
|
MasterPackets::RequestPrivateZone readRequest;
|
|
ASSERT_TRUE(readRequest.Deserialize(requestStream2));
|
|
EXPECT_EQ(legacyRequest.first.first, readRequest.requestID);
|
|
EXPECT_EQ(legacyRequest.first.second, readRequest.mythranShift);
|
|
EXPECT_EQ(legacyRequest.second, readRequest.password);
|
|
if (password.size() <= 50) RoundTrip(request);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST(MasterPacketsTests, WorldStateMatchesLegacy) {
|
|
for (const LWOMAPID zone : { static_cast<LWOMAPID>(0), static_cast<LWOMAPID>(1100), static_cast<LWOMAPID>(0xFFFF) }) {
|
|
for (const LWOINSTANCEID instance : { static_cast<LWOINSTANCEID>(0), static_cast<LWOINSTANCEID>(3), static_cast<LWOINSTANCEID>(0xFFFF) }) {
|
|
MasterPackets::WorldReady ready;
|
|
ready.zoneID = zone;
|
|
ready.instanceID = instance;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::SendWorldReady(b, zone, instance); }), StructPacket(ready));
|
|
RoundTrip(ready);
|
|
ExpectTruncatedFails(ready);
|
|
|
|
MasterPackets::PlayerAdded added;
|
|
added.zoneID = zone;
|
|
added.instanceID = instance;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WritePlayerCount(b, MessageType::Master::PLAYER_ADDED, zone, instance); }), StructPacket(added));
|
|
RoundTrip(added);
|
|
ExpectTruncatedFails(added);
|
|
|
|
MasterPackets::PlayerRemoved removed;
|
|
removed.zoneID = zone;
|
|
removed.instanceID = instance;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WritePlayerCount(b, MessageType::Master::PLAYER_REMOVED, zone, instance); }), StructPacket(removed));
|
|
RoundTrip(removed);
|
|
|
|
MasterPackets::WorldShutDown shutDown;
|
|
shutDown.zoneID = zone;
|
|
shutDown.instanceID = instance;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteWorldShutDown(b, zone, instance); }), StructPacket(shutDown));
|
|
RoundTrip(shutDown);
|
|
|
|
for (const bool isPrivate : { false, true }) {
|
|
MasterPackets::WorldReadyInfo info;
|
|
info.zoneID = zone;
|
|
info.instanceID = instance;
|
|
info.cloneID = 0xDEADBEEF;
|
|
info.ip = LUString("127.0.0.1");
|
|
info.port = 3000;
|
|
info.isPrivate = isPrivate ? 1 : 0;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteWorldReadyInfo(b, zone, instance, 0xDEADBEEF, "127.0.0.1", 3000, isPrivate); }), StructPacket(info));
|
|
RakNet::BitStream stream; LoadPayload(stream, info);
|
|
const auto legacy = LegacyMaster::ReadWorldReadyInfo(stream);
|
|
const auto copy = RoundTrip(info);
|
|
EXPECT_EQ(legacy.cloneID, copy.cloneID);
|
|
EXPECT_EQ(legacy.ip, copy.ip.string);
|
|
EXPECT_EQ(legacy.isPrivate, copy.isPrivate != 0);
|
|
ExpectTruncatedFails(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const int zone : { 0, 1100, -1 }) {
|
|
MasterPackets::PrepZone prep;
|
|
prep.zoneID = zone;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::TellMasterToPrepZone(b, zone); }), StructPacket(prep));
|
|
RoundTrip(prep);
|
|
ExpectTruncatedFails(prep);
|
|
}
|
|
|
|
for (const auto requestID : g_RequestIds) {
|
|
MasterPackets::AffirmTransferRequest request;
|
|
request.requestID = requestID;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteAffirmTransfer(b, MessageType::Master::AFFIRM_TRANSFER_REQUEST, requestID); }), StructPacket(request));
|
|
RoundTrip(request);
|
|
ExpectTruncatedFails(request);
|
|
MasterPackets::AffirmTransferResponse response;
|
|
response.requestID = requestID;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteAffirmTransfer(b, MessageType::Master::AFFIRM_TRANSFER_RESPONSE, requestID); }), StructPacket(response));
|
|
RoundTrip(response);
|
|
}
|
|
|
|
for (const auto zone : g_Numbers) {
|
|
MasterPackets::InstanceShutdown shutdown;
|
|
shutdown.zoneID = zone;
|
|
shutdown.instanceID = zone / 7;
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteInstanceShutdown(b, zone, zone / 7); }), StructPacket(shutdown));
|
|
RoundTrip(shutdown);
|
|
ExpectTruncatedFails(shutdown);
|
|
}
|
|
}
|
|
|
|
TEST(MasterPacketsTests, EmptyMessagesMatchLegacy) {
|
|
EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::SHUTDOWN); }), StructPacket(MasterPackets::Shutdown()));
|
|
EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::SHUTDOWN_RESPONSE); }), StructPacket(MasterPackets::ShutdownResponse()));
|
|
EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::SHUTDOWN_UNIVERSE); }), StructPacket(MasterPackets::ShutdownUniverse()));
|
|
EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::REQUEST_SERVER_LIST); }), StructPacket(MasterPackets::RequestServerList()));
|
|
EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::CONFIG_RELOAD); }), StructPacket(MasterPackets::ConfigReload()));
|
|
EXPECT_PACKET_EQ(Written([](RakNet::BitStream& b) { LegacyMaster::WriteEmpty(b, MessageType::Master::DASHBOARD_SHUTDOWN); }), StructPacket(MasterPackets::DashboardShutdown()));
|
|
}
|
|
|
|
TEST(MasterPacketsTests, Golden) {
|
|
MasterPackets::PlayerAdded added;
|
|
added.zoneID = 1100;
|
|
added.instanceID = 2;
|
|
// 53 | MASTER (6) | PLAYER_ADDED (7) | pad | u16 1100 | u16 2
|
|
EXPECT_PACKET_EQ(FromHex("53 06 00 07 00 00 00 00 4c 04 02 00"), StructPacket(added));
|
|
|
|
MasterPackets::AffirmTransferRequest affirm;
|
|
affirm.requestID = 0x0102030405060708ULL;
|
|
// AFFIRM_TRANSFER_REQUEST (17) | u64
|
|
EXPECT_PACKET_EQ(FromHex("53 06 00 11 00 00 00 00 08 07 06 05 04 03 02 01"), StructPacket(affirm));
|
|
}
|
|
|
|
TEST(MasterPacketsTests, ServerListMatchesLegacy) {
|
|
for (size_t count = 0; count <= 3; count++) {
|
|
std::vector<LegacyMaster::ServerListInstance> instances;
|
|
MasterPackets::ServerListResponse response;
|
|
response.authOnline = 1;
|
|
response.chatOnline = count % 2;
|
|
for (size_t i = 0; i < count; i++) {
|
|
LegacyMaster::ServerListInstance instance{ static_cast<LWOMAPID>(1000 + i), static_cast<LWOINSTANCEID>(i), static_cast<LWOCLONEID>(i * 7), static_cast<uint32_t>(i * 3), "10.0.0." + std::to_string(i), static_cast<uint32_t>(3000 + i), i == 2 };
|
|
instances.push_back(instance);
|
|
auto& entry = response.instances.emplace_back();
|
|
entry.mapID = instance.mapID;
|
|
entry.instanceID = instance.instanceID;
|
|
entry.cloneID = instance.cloneID;
|
|
entry.players = instance.players;
|
|
entry.ip = LUString(instance.ip);
|
|
entry.port = instance.port;
|
|
entry.isPrivate = instance.isPrivate ? 1 : 0;
|
|
}
|
|
EXPECT_PACKET_EQ(Written([&](RakNet::BitStream& b) { LegacyMaster::WriteServerList(b, true, count % 2, instances); }), StructPacket(response));
|
|
|
|
RakNet::BitStream stream; LoadPayload(stream, response);
|
|
const auto legacy = LegacyMaster::ReadServerList(stream);
|
|
const auto copy = RoundTrip(response);
|
|
ASSERT_EQ(legacy.instances.size(), copy.instances.size());
|
|
for (size_t i = 0; i < count; i++) {
|
|
EXPECT_EQ(legacy.instances[i].mapID, copy.instances[i].mapID);
|
|
EXPECT_EQ(legacy.instances[i].players, copy.instances[i].players);
|
|
EXPECT_EQ(legacy.instances[i].ip, copy.instances[i].ip.string);
|
|
EXPECT_EQ(legacy.instances[i].isPrivate, copy.instances[i].isPrivate != 0);
|
|
}
|
|
ExpectTruncatedFails(response);
|
|
}
|
|
}
|
|
|
|
// The dashboard and migration structs kept their payloads; they now carry their own header
|
|
TEST(MasterPacketsTests, DashboardAndMigrationStructsKeepTheirBytes) {
|
|
PlayerActionRequest action;
|
|
action.requestId = 7;
|
|
action.action = ePlayerAction::CHAT_MESSAGE;
|
|
action.text = "hello";
|
|
action.name = "[Discord] Bob";
|
|
ExpectHeaderThenSerialize(action, MessageType::Master::PLAYER_ACTION);
|
|
|
|
PlayerActionResult result;
|
|
result.requestId = 7;
|
|
result.affected = 3;
|
|
result.timedOut = true;
|
|
ExpectHeaderThenSerialize(result, MessageType::Master::PLAYER_ACTION_RESULT);
|
|
|
|
DataChanged changed;
|
|
changed.entries.push_back({ "characters", 42 });
|
|
ExpectHeaderThenSerialize(changed, MessageType::Master::DATA_CHANGED);
|
|
|
|
PlayerPositions positions;
|
|
positions.zoneId = 1100;
|
|
positions.players.push_back({ 42, 1.0f, 2.0f, 3.0f });
|
|
ExpectHeaderThenSerialize(positions, MessageType::Master::PLAYER_POSITIONS);
|
|
|
|
Announcement announcement;
|
|
announcement.title = "Title";
|
|
announcement.message = "Message";
|
|
announcement.zones = { 1100, 1200 };
|
|
ExpectHeaderThenSerialize(announcement, MessageType::Master::ANNOUNCE);
|
|
|
|
MessageCaptureControl control;
|
|
control.captureId = 3;
|
|
control.seconds = 60;
|
|
control.only = { 1, 2 };
|
|
ExpectHeaderThenSerialize(control, MessageType::Master::MESSAGE_CAPTURE_CONTROL);
|
|
|
|
MessageCaptureData data;
|
|
data.captureId = 3;
|
|
data.entries.resize(1);
|
|
data.entries[0].payload = "abc";
|
|
ExpectHeaderThenSerialize(data, MessageType::Master::MESSAGE_CAPTURE_DATA);
|
|
|
|
InstanceMigrationRequest migrate;
|
|
migrate.requestId = 9;
|
|
migrate.requestedBy = "GM";
|
|
ExpectHeaderThenSerialize(migrate, MessageType::Master::INSTANCE_MIGRATE);
|
|
|
|
MigratePlayersOrder order;
|
|
order.targetIp = "127.0.0.1";
|
|
ExpectHeaderThenSerialize(order, MessageType::Master::MIGRATE_PLAYERS);
|
|
|
|
MigrationStatus status;
|
|
status.message = "Moving players";
|
|
ExpectHeaderThenSerialize(status, MessageType::Master::MIGRATE_STATUS);
|
|
|
|
CarriedPlayerState state;
|
|
state.characterId = 42;
|
|
ExpectHeaderThenSerialize(state, MessageType::Master::MIGRATE_PLAYER_STATE);
|
|
}
|
|
|
|
TEST(MasterPacketsTests, DispatcherRoutesAndDropsBadPackets) {
|
|
// Dropped packets are logged
|
|
auto* const previousLogger = Game::logger;
|
|
Game::logger = new Logger("./testing.log", true, true);
|
|
PacketDispatcher<MessageType::Master> handlers;
|
|
int calls = 0;
|
|
MasterPackets::PlayerAdded seen;
|
|
handlers.On<MasterPackets::PlayerAdded>(MessageType::Master::PLAYER_ADDED, [&](const MasterPackets::PlayerAdded& msg, const SystemAddress&) { calls++; seen = msg; });
|
|
|
|
MasterPackets::PlayerAdded added;
|
|
added.zoneID = 1100;
|
|
added.instanceID = 4;
|
|
RakNet::BitStream good;
|
|
added.WritePacket(good);
|
|
Packet packet{};
|
|
packet.data = good.GetData();
|
|
packet.length = good.GetNumberOfBytesUsed();
|
|
EXPECT_TRUE(handlers.Dispatch(&packet, ServiceType::MASTER));
|
|
EXPECT_EQ(calls, 1);
|
|
EXPECT_EQ(seen.zoneID, 1100);
|
|
EXPECT_EQ(seen.instanceID, 4);
|
|
|
|
// Wrong service: not ours
|
|
EXPECT_FALSE(handlers.Dispatch(&packet, ServiceType::CHAT));
|
|
|
|
// Truncated: handled (dropped), the handler isn't called
|
|
packet.length -= 1;
|
|
EXPECT_TRUE(handlers.Dispatch(&packet, ServiceType::MASTER));
|
|
EXPECT_EQ(calls, 1);
|
|
|
|
// No handler for this ID
|
|
MasterPackets::PlayerRemoved removed;
|
|
RakNet::BitStream other;
|
|
removed.WritePacket(other);
|
|
packet.data = other.GetData();
|
|
packet.length = other.GetNumberOfBytesUsed();
|
|
EXPECT_FALSE(handlers.Dispatch(&packet, ServiceType::MASTER));
|
|
|
|
delete Game::logger;
|
|
Game::logger = previousLogger;
|
|
}
|