tests: exercise mall open in smoke login
Some checks failed
build / Linux asan (push) Has been cancelled
build / Linux release (push) Has been cancelled
build / FreeBSD build (push) Has been cancelled

This commit is contained in:
server
2026-04-14 12:33:08 +02:00
parent 84ed35cbda
commit 6b274186c5

View File

@@ -27,6 +27,8 @@ constexpr size_t ACCOUNT_STATUS_MAX_LEN_LOCAL = 8;
constexpr size_t PLAYER_PER_ACCOUNT_LOCAL = 4;
constexpr size_t CHARACTER_NAME_MAX_LEN_LOCAL = 64;
constexpr size_t GUILD_NAME_MAX_LEN_LOCAL = 12;
constexpr size_t SAFEBOX_PASSWORD_MAX_LEN_LOCAL = 6;
constexpr uint8_t CHAT_TYPE_TALKING_LOCAL = 0;
#pragma pack(push, 1)
struct PacketGCPhase
@@ -77,6 +79,13 @@ struct PacketCGLogin2
uint32_t login_key;
};
struct PacketCGChat
{
uint16_t header;
uint16_t length;
uint8_t type;
};
struct SimplePlayerLocal
{
uint32_t id;
@@ -216,6 +225,19 @@ struct PacketGCChannel
uint16_t length;
uint8_t channel;
};
struct PacketGCSafeboxSize
{
uint16_t header;
uint16_t length;
uint8_t size;
};
struct PacketGCSafeboxWrongPassword
{
uint16_t header;
uint16_t length;
};
#pragma pack(pop)
struct SmokeOptions
@@ -225,6 +247,7 @@ struct SmokeOptions
std::string client_version = "1215955205";
std::string expect_auth_failure;
std::string expect_channel_failure;
std::string mall_password;
};
struct SmokeResult
@@ -240,6 +263,8 @@ struct SmokeResult
std::string character_name;
bool character_created = false;
int game_channel = -1;
bool mall_opened = false;
int mall_size = -1;
int64_t auth_handshake_ms = -1;
int64_t auth_login_ms = -1;
int64_t channel_handshake_ms = -1;
@@ -247,6 +272,7 @@ struct SmokeResult
int64_t character_create_ms = -1;
int64_t character_select_ms = -1;
int64_t entergame_ms = -1;
int64_t mall_open_ms = -1;
std::vector<std::string> events;
};
@@ -333,6 +359,8 @@ void PrintJson(const SmokeResult& result)
<< ",\"character_name\":\"" << JsonEscape(result.character_name) << "\""
<< ",\"character_created\":" << (result.character_created ? "true" : "false")
<< ",\"game_channel\":" << result.game_channel
<< ",\"mall_opened\":" << (result.mall_opened ? "true" : "false")
<< ",\"mall_size\":" << result.mall_size
<< ",\"timings_ms\":{"
<< "\"auth_handshake\":" << result.auth_handshake_ms
<< ",\"auth_login\":" << result.auth_login_ms
@@ -341,6 +369,7 @@ void PrintJson(const SmokeResult& result)
<< ",\"character_create\":" << result.character_create_ms
<< ",\"character_select\":" << result.character_select_ms
<< ",\"entergame\":" << result.entergame_ms
<< ",\"mall_open\":" << result.mall_open_ms
<< "},\"events\":[";
for (size_t i = 0; i < result.events.size(); ++i)
@@ -470,6 +499,14 @@ public:
WriteExact(m_fd, &encrypted, sizeof(encrypted), "write encrypted packet");
}
void SendEncryptedFrame(const void* data, size_t length)
{
std::vector<uint8_t> encrypted(length);
std::memcpy(encrypted.data(), data, length);
m_cipher.EncryptInPlace(encrypted.data(), encrypted.size());
WriteExact(m_fd, encrypted.data(), encrypted.size(), "write encrypted frame");
}
bool WaitForFrame(std::vector<uint8_t>& frame, int timeout_ms)
{
while (true)
@@ -763,6 +800,17 @@ void SendClientVersion(EncryptedClient& channel_client, std::string_view client_
EmitEvent(result, json, "client_version " + std::string(version_packet.timestamp));
}
void SendChatCommand(EncryptedClient& channel_client, std::string_view command)
{
std::vector<uint8_t> frame(sizeof(PacketCGChat) + command.size() + 1, 0);
auto* chat = reinterpret_cast<PacketCGChat*>(frame.data());
chat->header = CG::CHAT;
chat->length = static_cast<uint16_t>(frame.size());
chat->type = CHAT_TYPE_TALKING_LOCAL;
std::memcpy(frame.data() + sizeof(PacketCGChat), command.data(), command.size());
channel_client.SendEncryptedFrame(frame.data(), frame.size());
}
void EnterGame(EncryptedClient& channel_client, SmokeResult& result, bool json)
{
PacketCGEnterGame enter_game {};
@@ -822,6 +870,41 @@ void EnterGame(EncryptedClient& channel_client, SmokeResult& result, bool json)
return;
}
}
void OpenMall(EncryptedClient& channel_client, const std::string& password, SmokeResult& result, bool json)
{
Expect(!password.empty(), "mall password is empty");
Expect(password.size() <= SAFEBOX_PASSWORD_MAX_LEN_LOCAL, "mall password too long");
SendChatCommand(channel_client, "/mall_password " + password);
std::vector<uint8_t> frame;
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
while (true)
{
const int timeout_ms = RemainingTimeoutMs(deadline);
Expect(timeout_ms > 0, "timed out waiting for mall open response");
Expect(channel_client.WaitForFrame(frame, timeout_ms), "timed out waiting for mall open response");
const auto header = FrameHeader(frame);
if (header == GC::SAFEBOX_WRONG_PASSWORD)
{
Expect(frame.size() == sizeof(PacketGCSafeboxWrongPassword), "unexpected safebox wrong password packet size");
throw std::runtime_error("mall password rejected");
}
if (header == GC::MALL_OPEN)
{
Expect(frame.size() == sizeof(PacketGCSafeboxSize), "unexpected mall open packet size");
const auto* mall_open = reinterpret_cast<const PacketGCSafeboxSize*>(frame.data());
result.mall_opened = true;
result.mall_size = mall_open->size;
EmitEvent(result, json, "mall_open size=" + std::to_string(static_cast<int>(mall_open->size)));
return;
}
}
}
}
int main(int argc, char** argv)
@@ -835,7 +918,7 @@ int main(int argc, char** argv)
"usage: metin_login_smoke <host> <auth_port> <channel_port> <login> <password>\n"
" or: metin_login_smoke <host> <auth_port> <channel_port> <login> --password-env=ENV_NAME\n"
" optional: --create-character-name=NAME [--client-version=VERSION] [--json] "
"[--expect-auth-failure=STATUS] [--expect-channel-failure=STATUS]");
"[--expect-auth-failure=STATUS] [--expect-channel-failure=STATUS] [--mall-password=PASSWORD]");
const std::string host = argv[1];
const uint16_t auth_port = static_cast<uint16_t>(std::stoi(argv[2]));
@@ -863,6 +946,7 @@ int main(int argc, char** argv)
const std::string version_prefix = "--client-version=";
const std::string auth_failure_prefix = "--expect-auth-failure=";
const std::string channel_failure_prefix = "--expect-channel-failure=";
const std::string mall_password_prefix = "--mall-password=";
if (create_arg.rfind(create_prefix, 0) == 0)
{
options.create_character_name = create_arg.substr(create_prefix.size());
@@ -892,6 +976,14 @@ int main(int argc, char** argv)
continue;
}
if (create_arg.rfind(mall_password_prefix, 0) == 0)
{
options.mall_password = create_arg.substr(mall_password_prefix.size());
Expect(!options.mall_password.empty(), "mall password is empty");
Expect(options.mall_password.size() <= SAFEBOX_PASSWORD_MAX_LEN_LOCAL, "mall password too long");
continue;
}
if (create_arg == "--json")
{
options.json = true;
@@ -1008,6 +1100,14 @@ int main(int argc, char** argv)
EnterGame(channel_client, result, options.json);
result.entergame_ms = ElapsedMs(started_at);
if (!options.mall_password.empty())
{
result.stage = "mall_open";
started_at = std::chrono::steady_clock::now();
OpenMall(channel_client, options.mall_password, result, options.json);
result.mall_open_ms = ElapsedMs(started_at);
}
result.ok = true;
result.result = "success";
result.stage = "complete";