emberemu/tests/GruntProtocol.cpp

387 lines
No EOL
13 KiB
C++

/*
* Copyright (c) 2015 - 2026 Ember
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include "GruntPacketDumps.h"
#include <login/StreamTypes.h>
#include <login/grunt/Packets.h>
#include <login/grunt/Magic.h>
#include <shared/utility/HashDefines.h>
#include <boost/asio/ip/address.hpp>
#include <gtest/gtest.h>
#include <memory>
#include <string>
#include <tuple>
#include <cstdint>
/*
* These tests verify that the serialisation & deserialisation of the Grunt login protocol
* packets is working as expected. The process is roughly as follows:
*
* 1) Each packet as it appears on the wire is stored in a byte array. This byte array is then
* written into a buffer chain, to simulate reading a complete packet from a socket.
* 2) The buffer is then deserialised and each field is checked to ensure it matches the expected
* value.
* 3) The deserialised structure is serialised back into the buffer chain and the bytes are
* compared to ensure that the output of the serialisation exactly matches the input of the
* deserialisation.
*/
using namespace ember;
using namespace std::string_literals;
TEST(GruntProtocol, ClientLoginChallenge) {
const std::size_t packet_size = sizeof(client_login_challenge);
// write the packet bytes into chain
BufferType chain;
PacketStream in_stream(chain);
PacketStream out_stream(chain);
chain.write(client_login_challenge);
// deserialise the packet
grunt::client::LoginChallenge packet{};
packet.read_from_stream(in_stream);
// verify the deserialisation results
GameVersion version { 1, 12, 1, 5875 };
auto ip = boost::asio::ip::make_address_v4("10.0.0.5").to_uint();
ASSERT_EQ(0, chain.size()) << "Read length incorrect";
ASSERT_EQ(version, packet.version)
<< "field: client version";
ASSERT_EQ("CHAOSVEX"s, packet.username)
<< "field: username";
ASSERT_EQ(0, packet.timezone_bias)
<< "field: timezone bias";
ASSERT_EQ(grunt::Platform::x86, packet.platform)
<< "field: platform";
ASSERT_EQ(grunt::System::Win, packet.os)
<< "field: operating system";
ASSERT_EQ(grunt::Game::WoW, packet.game)
<< "field: magic";
ASSERT_EQ(grunt::Locale::enUS, packet.locale)
<< "field: locale";
ASSERT_EQ(ip, packet.ip)
<< "field: IP";
ASSERT_EQ(3, packet.protocol_ver)
<< "field: protocol_ver";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, client_login_challenge, packet_size))
<< "Serialisation failed (input != output)";
}
TEST(GruntProtocol, ClientLoginProof) {
const std::size_t packet_size = sizeof(client_login_proof);
// write the packet bytes into chain
BufferType chain;
PacketStream in_stream(chain);
PacketStream out_stream(chain);
chain.write(client_login_proof);
// deserialise the packet
grunt::client::LoginProof packet{};
packet.read_from_stream(in_stream);
// verify the deserialisation results
ASSERT_EQ(0, chain.size()) << "Read length incorrect";
ASSERT_EQ(false, packet.two_factor_auth)
<< "field: security";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, client_login_proof, packet_size))
<< "Serialisation failed (input != output)";
}
TEST(GruntProtocol, ClientReconnectProof) {
const std::size_t packet_size = sizeof(client_reconnect_proof);
// write the packet bytes into chain
BufferType chain;
PacketStream in_stream(chain);
PacketStream out_stream(chain);
chain.write(client_reconnect_proof);
// deserialise the packet
grunt::client::ReconnectProof packet{};
packet.read_from_stream(in_stream);
// verify the deserialisation results
std::array<std::uint8_t, 16> r1_expected = {
0x39, 0xb1, 0xd1, 0xe4, 0x49, 0x13, 0x80, 0x9d,
0x92, 0x17, 0x56, 0x8e, 0xa5, 0x7b, 0x24, 0x3d
};
std::array<std::uint8_t, hash_sizes::sha160> r2_expected {
0xc0, 0x4e, 0x35, 0xe6, 0xfe, 0xb2, 0x1a, 0x84,
0x98, 0xc4, 0x8e, 0x6c, 0x2d, 0x7f, 0x42, 0x56,
0xb6, 0x4c, 0x98, 0x24
};
std::array<std::uint8_t, hash_sizes::sha160> r3_expected {
0x0d, 0xbe, 0x94, 0xaf, 0x7f, 0x46, 0x94, 0xa2,
0x3f, 0xe7, 0xdf, 0xe5, 0x99, 0xbf, 0xe7, 0x88,
0x86, 0x02, 0x5f, 0x92
};
ASSERT_TRUE(chain.empty()) << "Read length incorrect";
ASSERT_EQ(r1_expected, packet.salt)
<< "field: salt";
ASSERT_EQ(r2_expected, packet.proof)
<< "field: proof";
ASSERT_EQ(r3_expected, packet.client_checksum)
<< "field: client_checksum";
ASSERT_EQ(0, packet.key_count) << "field: key count";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, client_reconnect_proof, packet_size))
<< "Serialisation failed (input != output)";
}
TEST(GruntProtocol, ClientRequestRealmList) {
const std::size_t packet_size = sizeof(request_realm_list);
// write the packet bytes into chain
BufferType chain;
PacketStream in_stream(chain);
PacketStream out_stream(chain);
chain.write(request_realm_list);
// deserialise the packet
grunt::client::RequestRealmList packet;
packet.read_from_stream(in_stream);
// verify the deserialisation results
ASSERT_EQ(0, chain.size()) << "Read length incorrect";
ASSERT_EQ(0, packet.unknown) << "field: unknown";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, request_realm_list, packet_size))
<< "Serialisation failed (input != output)";
}
TEST(GruntProtocol, ServerLoginChallenge) {
const std::size_t packet_size = sizeof(server_login_challenge);
// write the packet bytes into chain
BufferType chain;
chain.write(server_login_challenge);
// deserialise the packet
PacketStream in_stream(chain);
PacketStream out_stream(chain);
grunt::server::LoginChallenge packet{};
packet.read_from_stream(in_stream);
// verify the deserialisation results
ASSERT_EQ(0, chain.size()) << "Read length incorrect";
ASSERT_EQ(Botan::BigInt("0x153a794dba6475ef8b2cfdbb8fc88d40edc0effea638842829d4d2c4baba84f1"), packet.B)
<< "field: B [public ephemeral]";
ASSERT_EQ(7, packet.g) << "field: g [generator]";
ASSERT_EQ(1, packet.g_len) << "field: g length";
ASSERT_EQ(Botan::BigInt("0x894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7"), packet.N)
<< "field: N [safe prime]";
ASSERT_EQ(32, packet.n_len) << "field: N length";
ASSERT_EQ(grunt::Result::success, packet.result) << "field: result";
ASSERT_EQ(Botan::BigInt("0xF4C7DBCA7138DA48D9B7BE55C0C76B1145AF67340CF7A6718D452A563E12A19C"), packet.s)
<< "field: salt";
ASSERT_EQ(0, packet.protocol_ver) << "field: protocol_ver";
ASSERT_EQ(false, packet.two_factor_auth)
<< "field: security";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, server_login_challenge, packet_size))
<< "Serialisation failed (input != output)";
}
TEST(GruntProtocol, ServerLoginProof) {
const std::size_t packet_size = sizeof(server_login_proof);
// write the packet bytes into chain
BufferType chain;
PacketStream in_stream(chain);
PacketStream out_stream(chain);
chain.write(server_login_proof);
// deserialise the packet
grunt::server::LoginProof packet{};
packet.read_from_stream(in_stream);
// verify the deserialisation results
ASSERT_TRUE(chain.empty()) << "Read length incorrect";
ASSERT_EQ(0, packet.survey_id) << "field: survey ID";
ASSERT_EQ(Botan::BigInt("0xa3fbd672a092de7650fb0733419ebf59bcae644a"), packet.M2)
<< "field: M2";
ASSERT_EQ(grunt::Result::success, packet.result) << "field: result";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, server_login_proof, packet_size))
<< "Serialisation failed (input != output)";
}
TEST(GruntProtocol, ServerRealmList) {
const std::size_t packet_size = sizeof(realm_list);
// write the packet bytes into chain
BufferType chain;
PacketStream in_stream(chain);
PacketStream out_stream(chain);
chain.write(realm_list);
// deserialise the packet
grunt::server::RealmList packet;
packet.read_from_stream(in_stream);
// verify the deserialisation results
ASSERT_EQ(0, chain.size()) << "Read length incorrect";
ASSERT_EQ(2, packet.realms.size()) << "Invalid realm count";
{ // test first realm entry
grunt::server::RealmList::RealmListEntry& entry = packet.realms[0];
const auto& realm = entry.realm;
const auto& chars = entry.characters;
ASSERT_EQ(Realm::Flags::offline, realm.flags) << "Invalid realm flags";
ASSERT_EQ(Realm::Type::pvp, realm.type) << "Invalid realm type";
ASSERT_EQ(dbc::Cfg_Categories::Category::UNITED_STATES, realm.category) << "Invalid realm category";
ASSERT_EQ("127.0.0.1:1337"s, realm.address) << "Invalid realm IP";
ASSERT_EQ("Ember"s, realm.name) << "Invalid realm name";
ASSERT_FLOAT_EQ(0.0f, realm.population) << "Invalid realm population";
ASSERT_EQ(0, chars) << "Invalid character count";
}
{ // test second realm entry
grunt::server::RealmList::RealmListEntry& entry = packet.realms[1];
const auto& realm = entry.realm;
const auto& chars = entry.characters;
ASSERT_EQ(Realm::Flags::invalid, realm.flags) << "Invalid realm flags";
ASSERT_EQ(Realm::Type::pve, realm.type) << "Invalid realm type";
ASSERT_EQ(dbc::Cfg_Categories::Category::UNITED_STATES, realm.category) << "Invalid realm category";
ASSERT_EQ("127.0.0.1:8085"s, realm.address) << "Invalid realm IP";
ASSERT_EQ("Ember Test"s, realm.name) << "Invalid realm name";
ASSERT_FLOAT_EQ(1.4f, realm.population) << "Invalid realm population";
ASSERT_EQ(0, chars) << "Invalid character count";
}
// check the other fields
ASSERT_EQ(0, packet.unknown) << "field: unknown";
ASSERT_EQ(5, packet.unknown2) << "field: unknown";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, realm_list, packet_size))
<< "Serialisation failed (input != output)";
}
TEST(GruntProtocol, ServerReconnectChallenge) {
const std::size_t packet_size = sizeof(server_reconnect_challenge);
// write the packet bytes into chain
BufferType chain;
PacketStream in_stream(chain);
PacketStream out_stream(chain);
chain.write(server_reconnect_challenge);
// deserialise the packet
auto packet = grunt::server::ReconnectChallenge();
packet.read_from_stream(in_stream);
// verify the deserialisation results
std::array<std::uint8_t, 16> salt {
0xdd, 0x26, 0xe7, 0x5f, 0x44, 0x24, 0xcf, 0xdd,
0x51, 0x89, 0x41, 0xd2, 0x02, 0x78, 0xd1, 0x84
};
std::array<std::uint8_t, 16> expected_bytes {}; // all zero
ASSERT_EQ(0, chain.size()) << "Read length incorrect";
ASSERT_EQ(salt, packet.salt) << "field: salt";
ASSERT_EQ(grunt::Result::success, packet.result) << "field: result";
ASSERT_EQ(expected_bytes, packet.checksum_salt) << "field: checksum_salt";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, server_reconnect_challenge, packet_size))
<< "Serialisation failed (input != output)";
}
TEST(GruntProtocol, ServerReconnectProof) {
const std::size_t packet_size = sizeof(server_reconnect_proof);
// write the packet bytes into chain
BufferType chain;
PacketStream in_stream(chain);
PacketStream out_stream(chain);
chain.write(server_reconnect_proof);
// deserialise the packet
auto packet = grunt::server::ReconnectProof();
packet.read_from_stream(in_stream);
// verify the deserialisation results
ASSERT_EQ(0, chain.size()) << "Read length incorrect";
ASSERT_EQ(grunt::Result::success, packet.result) << "field: result";
// serialise back to the stream and verify that the output matches the original packet
packet.write_to_stream(out_stream);
ASSERT_EQ(packet_size, chain.size()) << "Write length incorrect";
char buffer[packet_size];
chain.read(buffer, chain.size());
ASSERT_EQ(0, memcmp(buffer, server_reconnect_proof, packet_size))
<< "Serialisation failed (input != output)";
}