mirror of
https://github.com/mangosone/server
synced 2026-08-18 22:26:15 -04:00
Decouple world networking and harden ACE removal (#150)
* fix: harden ACE removal networking and OpenSSL runtime Constrain builds and runtime loading to OpenSSL 3.x, update the merged realmd pointer, and add regression coverage. Harden backend shutdown, session lifetime, async authentication, and startup failure handling across the ACE replacement network stack. * refactor: separate wire opcodes from game dispatch * refactor: add isolated world protocol primitives * refactor: isolate world client connections * refactor: decouple world networking from game policy * test: cover the world protocol boundary * fix: make mailbox ownership transfer explicit * fix: contain gateway exceptions per connection * fix: close session publication races * fix: declare pet item prototype dependency * fix: preserve auth ordering and session synchronization
This commit is contained in:
parent
6cbf136a47
commit
5155841ae0
66 changed files with 3857 additions and 1457 deletions
59
.github/workflows/core_windows_build.yml
vendored
59
.github/workflows/core_windows_build.yml
vendored
|
|
@ -3,6 +3,10 @@ name: Windows Build (MSVC)
|
|||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
OPENSSL_VERSION: 3.6.3
|
||||
OPENSSL_SHA512: 120A2E9A3E8B961484CB94D52F85D48DC2C8AF777B5B3B9E26BF49B4408797281F7C0FB2D543FD7796B3C3232ADFAA0F675E7122C0E5C4225B3B02E767197AC6
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master, devel]
|
||||
|
|
@ -30,18 +34,48 @@ jobs:
|
|||
id: cache-openssl
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
C:\Program Files\OpenSSL
|
||||
C:\Program Files\OpenSSL-Win64
|
||||
key: ${{ runner.os }}-openssl-v1
|
||||
path: C:\Program Files\OpenSSL-Win64
|
||||
key: ${{ runner.os }}-openssl-${{ env.OPENSSL_VERSION }}-v1
|
||||
|
||||
- name: Install OpenSSL (developer)
|
||||
- name: Install full OpenSSL (developer)
|
||||
if: steps.cache-openssl.outputs.cache-hit != 'true'
|
||||
shell: powershell
|
||||
run: |
|
||||
choco upgrade openssl -y --no-progress
|
||||
$ver = & openssl version
|
||||
Write-Host "Installed OpenSSL version: $ver"
|
||||
$versionSlug = $env:OPENSSL_VERSION.Replace('.', '_')
|
||||
$installer = Join-Path $env:RUNNER_TEMP "Win64OpenSSL-$versionSlug.exe"
|
||||
$url = "https://slproweb.com/download/Win64OpenSSL-$versionSlug.exe"
|
||||
|
||||
Write-Host "Downloading OpenSSL $env:OPENSSL_VERSION for development..."
|
||||
curl.exe --fail --location --retry 3 --output $installer $url
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "OpenSSL download failed with exit code $LASTEXITCODE"
|
||||
}
|
||||
|
||||
$actualHash = (Get-FileHash -Algorithm SHA512 $installer).Hash
|
||||
if ($actualHash -ne $env:OPENSSL_SHA512) {
|
||||
throw "OpenSSL installer checksum mismatch: $actualHash"
|
||||
}
|
||||
|
||||
$installArgs = '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- /DIR="C:\Program Files\OpenSSL-Win64"'
|
||||
$process = Start-Process -FilePath $installer -ArgumentList $installArgs -Wait -PassThru
|
||||
if ($process.ExitCode -ne 0) {
|
||||
throw "OpenSSL installer failed with exit code $($process.ExitCode)"
|
||||
}
|
||||
|
||||
- name: Verify OpenSSL major version
|
||||
shell: powershell
|
||||
run: |
|
||||
$openssl = "C:\Program Files\OpenSSL-Win64\bin\openssl.exe"
|
||||
|
||||
if (-not (Test-Path $openssl)) {
|
||||
throw "OpenSSL executable not found at $openssl"
|
||||
}
|
||||
|
||||
$version = & $openssl version
|
||||
Write-Host "Installed OpenSSL version: $version"
|
||||
if ($version -notmatch '^OpenSSL 3\.') {
|
||||
throw "Windows CI requires OpenSSL 3.x, found: $version"
|
||||
}
|
||||
|
||||
- name: Setup Windows 10 SDK
|
||||
uses: GuillaumeFalourd/setup-windows10-sdk-action@v2
|
||||
|
|
@ -51,18 +85,13 @@ jobs:
|
|||
- name: Configure OpenSSL environment
|
||||
shell: bash
|
||||
run: |
|
||||
if [ -d "C:/Program Files/OpenSSL/lib/VC" ]; then
|
||||
echo "OPENSSL_ROOT_DIR=C:/Program Files/OpenSSL" >> $GITHUB_ENV
|
||||
echo "OPENSSL_INCLUDE_DIR=C:/Program Files/OpenSSL/include" >> $GITHUB_ENV
|
||||
echo "OPENSSL_CRYPTO_LIBRARY=C:/Program Files/OpenSSL/lib/VC/libcrypto64MT.lib" >> $GITHUB_ENV
|
||||
echo "OPENSSL_SSL_LIBRARY=C:/Program Files/OpenSSL/lib/VC/libssl64MT.lib" >> $GITHUB_ENV
|
||||
elif [ -d "C:/Program Files/OpenSSL-Win64/lib/VC" ]; then
|
||||
if [ -d "C:/Program Files/OpenSSL-Win64/lib/VC" ]; then
|
||||
echo "OPENSSL_ROOT_DIR=C:/Program Files/OpenSSL-Win64" >> $GITHUB_ENV
|
||||
echo "OPENSSL_INCLUDE_DIR=C:/Program Files/OpenSSL-Win64/include" >> $GITHUB_ENV
|
||||
echo "OPENSSL_CRYPTO_LIBRARY=C:/Program Files/OpenSSL-Win64/lib/VC/libcrypto64MT.lib" >> $GITHUB_ENV
|
||||
echo "OPENSSL_SSL_LIBRARY=C:/Program Files/OpenSSL-Win64/lib/VC/libssl64MT.lib" >> $GITHUB_ENV
|
||||
else
|
||||
echo "::error::OpenSSL not found in either expected location"
|
||||
echo "::error::OpenSSL developer libraries not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ option(BUILD_MANGOSD "Build the main server" ON)
|
|||
option(BUILD_REALMD "Build the login server" ON)
|
||||
option(BUILD_TOOLS "Build the assets extractor (baker)" ON)
|
||||
option(BUILD_TESTS "Build collision-related utilities" OFF)
|
||||
option(BUILD_REGRESSION_TESTS "Build focused regression tests" OFF)
|
||||
option(USE_STORMLIB "Use StormLib for reading MPQs" ON)
|
||||
option(SCRIPT_LIB_ELUNA "Compile with support for Eluna scripts" ON)
|
||||
option(SCRIPT_LIB_SD3 "Compile with support for ScriptDev3 scripts" ON)
|
||||
|
|
@ -147,6 +148,11 @@ endif()
|
|||
|
||||
find_package(Threads REQUIRED)
|
||||
find_package(OpenSSL 3.0 REQUIRED)
|
||||
# OpenSSL 4.x support is intentionally deferred until the OpenSSL 4.2 LTS migration.
|
||||
if(OPENSSL_VERSION VERSION_GREATER_EQUAL "4.0.0")
|
||||
message(FATAL_ERROR
|
||||
"Unsupported OpenSSL ${OPENSSL_VERSION}: MaNGOS currently requires >=3.0.0 and <4.0.0")
|
||||
endif()
|
||||
find_package(MySQL REQUIRED)
|
||||
|
||||
# =============================================================================
|
||||
|
|
@ -245,6 +251,11 @@ else()
|
|||
message(WARNING "Source directory 'src' not found. Nothing to build.")
|
||||
endif()
|
||||
|
||||
if(BUILD_REGRESSION_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# =============================================================================
|
||||
# Final info and summary
|
||||
# =============================================================================
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@
|
|||
# Build the mangos shared library
|
||||
add_subdirectory(shared)
|
||||
|
||||
add_subdirectory(proto)
|
||||
|
||||
add_subdirectory(genrev)
|
||||
|
||||
# Needs to link against mangos_world.lib
|
||||
|
|
|
|||
|
|
@ -273,6 +273,7 @@ target_compile_definitions(game
|
|||
|
||||
target_link_libraries(game
|
||||
PUBLIC
|
||||
proto
|
||||
shared
|
||||
RecastNavigation::Detour
|
||||
ZLIB::ZLIB
|
||||
|
|
@ -302,4 +303,4 @@ endif()
|
|||
if(PLAYERBOTS)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/aiplayerbot.conf.dist
|
||||
DESTINATION ${CONF_INSTALL_DIR})
|
||||
endif()
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@
|
|||
#include "DBCStores.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "Player.h"
|
||||
#include "Opcodes.h"
|
||||
#include "OpcodeTable.h"
|
||||
#include "Chat.h"
|
||||
#include "Log.h"
|
||||
#include "Unit.h"
|
||||
|
|
@ -293,7 +293,7 @@ bool ChatHandler::HandleDebugRecvOpcodeCommand(char* /*args*/)
|
|||
}
|
||||
stream.close();
|
||||
|
||||
DEBUG_LOG("Queued opcode %u, %s", data->GetOpcode(), data->GetOpcodeName());
|
||||
DEBUG_LOG("Queued opcode %u, %s", data->GetOpcode(), LookupOpcodeName(data->GetOpcode()));
|
||||
|
||||
m_session->QueuePacket(data);
|
||||
|
||||
|
|
@ -406,7 +406,7 @@ bool ChatHandler::HandleDebugSendOpcodeCommand(char* /*args*/)
|
|||
}
|
||||
stream.close();
|
||||
|
||||
DEBUG_LOG("Sending opcode %u, %s", data.GetOpcode(), data.GetOpcodeName());
|
||||
DEBUG_LOG("Sending opcode %u, %s", data.GetOpcode(), LookupOpcodeName(data.GetOpcode()));
|
||||
|
||||
data.hexlike();
|
||||
unit->ToPlayer()->SendDirectMessage(&data);
|
||||
|
|
|
|||
|
|
@ -194,6 +194,7 @@ extern const uint32 LevelStartLoyalty[6];
|
|||
#define PET_FOLLOW_ANGLE (M_PI_F/2.0f)
|
||||
|
||||
class Player;
|
||||
struct ItemPrototype;
|
||||
|
||||
class Pet : public Creature
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* @file Opcodes.cpp
|
||||
* @file OpcodeTable.cpp
|
||||
* @brief Network opcode handler registration
|
||||
*
|
||||
* This file registers all network packet handlers for the world server.
|
||||
|
|
@ -39,12 +39,11 @@
|
|||
* - STATUS_LOGGEDIN: Require player to be logged in
|
||||
* - STATUS_UNHANDLED: No handler assigned
|
||||
*
|
||||
* @see Opcodes.h for opcode definitions
|
||||
* @see OpcodeTable.h for handler metadata
|
||||
* @see WorldSession for packet handler implementations
|
||||
*/
|
||||
|
||||
#include "Opcodes.h"
|
||||
#include "WorldSession.h"
|
||||
#include "OpcodeTable.h"
|
||||
|
||||
/**
|
||||
* @brief Define opcode handler
|
||||
58
src/game/Server/OpcodeTable.h
Normal file
58
src/game/Server/OpcodeTable.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* MaNGOS is a full featured server for World of Warcraft, supporting
|
||||
* multiple client versions.
|
||||
*
|
||||
* Copyright (C) 2005-2026 MaNGOS <https://www.getmangos.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef MANGOS_H_OPCODETABLE
|
||||
#define MANGOS_H_OPCODETABLE
|
||||
|
||||
#include "Opcodes.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
extern void InitializeOpcodes();
|
||||
|
||||
enum SessionStatus
|
||||
{
|
||||
STATUS_AUTHED = 0,
|
||||
STATUS_LOGGEDIN,
|
||||
STATUS_TRANSFER,
|
||||
STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT,
|
||||
STATUS_NEVER,
|
||||
STATUS_UNHANDLED
|
||||
};
|
||||
|
||||
enum PacketProcessing
|
||||
{
|
||||
PROCESS_INPLACE = 0,
|
||||
PROCESS_THREADUNSAFE,
|
||||
PROCESS_THREADSAFE
|
||||
};
|
||||
|
||||
struct OpcodeHandler
|
||||
{
|
||||
char const* name;
|
||||
SessionStatus status;
|
||||
PacketProcessing packetProcessing;
|
||||
void (WorldSession::*handler)(WorldPacket& recvPacket);
|
||||
};
|
||||
|
||||
extern OpcodeHandler opcodeTable[NUM_MSG_TYPES];
|
||||
|
||||
inline const char* LookupOpcodeName(uint16 id)
|
||||
{
|
||||
if (id >= NUM_MSG_TYPES)
|
||||
{
|
||||
return "Received unknown opcode, it's more than max!";
|
||||
}
|
||||
|
||||
return opcodeTable[id].name ? opcodeTable[id].name : "UNKNOWN";
|
||||
}
|
||||
|
||||
#endif
|
||||
40
src/game/Server/SessionMailbox.cpp
Normal file
40
src/game/Server/SessionMailbox.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#include "SessionMailbox.h"
|
||||
|
||||
SessionMailbox::~SessionMailbox()
|
||||
{
|
||||
Close();
|
||||
WorldPacket* packet = nullptr;
|
||||
while (m_packets.next(packet))
|
||||
delete packet;
|
||||
}
|
||||
|
||||
bool SessionMailbox::Enqueue(std::unique_ptr<WorldPacket> packet)
|
||||
{
|
||||
if (!packet)
|
||||
return false;
|
||||
|
||||
std::lock_guard<std::mutex> guard(m_stateLock);
|
||||
if (m_closed)
|
||||
return false;
|
||||
|
||||
WorldPacket* const accepted = packet.get();
|
||||
m_packets.add(accepted);
|
||||
return packet.release() == accepted;
|
||||
}
|
||||
|
||||
bool SessionMailbox::Next(WorldPacket*& packet)
|
||||
{
|
||||
return m_packets.next(packet);
|
||||
}
|
||||
|
||||
void SessionMailbox::Close()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_stateLock);
|
||||
m_closed = true;
|
||||
}
|
||||
|
||||
bool SessionMailbox::IsClosed() const
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_stateLock);
|
||||
return m_closed;
|
||||
}
|
||||
34
src/game/Server/SessionMailbox.h
Normal file
34
src/game/Server/SessionMailbox.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#ifndef MANGOS_H_SESSIONMAILBOX
|
||||
#define MANGOS_H_SESSIONMAILBOX
|
||||
|
||||
#include "LockedQueue/LockedQueue.h"
|
||||
#include "Utilities/WorldPacket.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
||||
class SessionMailbox
|
||||
{
|
||||
public:
|
||||
SessionMailbox() = default;
|
||||
~SessionMailbox();
|
||||
|
||||
bool Enqueue(std::unique_ptr<WorldPacket> packet);
|
||||
bool Next(WorldPacket*& packet);
|
||||
|
||||
template<class Checker>
|
||||
bool Next(WorldPacket*& packet, Checker& checker)
|
||||
{
|
||||
return m_packets.next(packet, checker);
|
||||
}
|
||||
|
||||
void Close();
|
||||
bool IsClosed() const;
|
||||
|
||||
private:
|
||||
mutable std::mutex m_stateLock;
|
||||
bool m_closed = false;
|
||||
MaNGOS::LockedQueue<WorldPacket*> m_packets;
|
||||
};
|
||||
|
||||
#endif
|
||||
250
src/game/Server/WorldGateway.cpp
Normal file
250
src/game/Server/WorldGateway.cpp
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
#include "WorldGateway.h"
|
||||
|
||||
#include "AddonHandler.h"
|
||||
#include "Auth/BigNumber.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
#include "DBCStores.h"
|
||||
#include "IClientLink.h"
|
||||
#include "Log.h"
|
||||
#include "OpcodeTable.h"
|
||||
#include "SessionMailbox.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "World.h"
|
||||
#include "WorldSession.h"
|
||||
|
||||
#ifdef ENABLE_ELUNA
|
||||
#include "LuaEngine.h"
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <openssl/crypto.h>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct AccountRow final : proto::AuthContext
|
||||
{
|
||||
uint32 id = 0;
|
||||
AccountTypes security = SEC_PLAYER;
|
||||
uint8 expansion = 0;
|
||||
time_t muteTime = 0;
|
||||
LocaleConstant locale = LOCALE_enUS;
|
||||
std::string os;
|
||||
BigNumber sessionKey;
|
||||
};
|
||||
|
||||
void EnsureDbThreadRegistered()
|
||||
{
|
||||
static thread_local DbThreadGuard guard(&LoginDatabase);
|
||||
(void)guard;
|
||||
}
|
||||
|
||||
proto::AuthLookup Rejected(proto::AuthStatus status)
|
||||
{
|
||||
proto::AuthLookup lookup;
|
||||
lookup.status = status;
|
||||
return lookup;
|
||||
}
|
||||
}
|
||||
|
||||
bool WorldGateway::FilterAuthPacket(WorldPacket& packet)
|
||||
{
|
||||
#ifdef ENABLE_ELUNA
|
||||
if (Eluna* eluna = sWorld.GetEluna())
|
||||
return eluna->OnPacketReceive(nullptr, packet);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void WorldGateway::TracePacket(const WorldPacket& packet, bool incoming)
|
||||
{
|
||||
if (sLog.IsPacketLoggingEnabled())
|
||||
{
|
||||
sLog.outWorldPacketDump(0, packet.GetOpcode(),
|
||||
LookupOpcodeName(packet.GetOpcode()), &packet, incoming);
|
||||
}
|
||||
}
|
||||
|
||||
proto::AuthLookup WorldGateway::LookupAccount(const proto::AuthRequest& request)
|
||||
{
|
||||
EnsureDbThreadRegistered();
|
||||
|
||||
if (!IsAcceptableClientBuild(request.build))
|
||||
return Rejected(proto::AuthStatus::VersionMismatch);
|
||||
|
||||
std::string safeAccount = request.account;
|
||||
LoginDatabase.escape_string(safeAccount);
|
||||
std::string safeAddress = request.peerAddress;
|
||||
LoginDatabase.escape_string(safeAddress);
|
||||
|
||||
std::unique_ptr<QueryResult> result(LoginDatabase.PQuery(
|
||||
"SELECT "
|
||||
"`a`.`id`, "
|
||||
"`a`.`gmlevel`, "
|
||||
"`a`.`sessionkey`, "
|
||||
"`a`.`last_ip`, "
|
||||
"`a`.`locked`, "
|
||||
"`a`.`v`, "
|
||||
"`a`.`s`, "
|
||||
"`a`.`expansion`, "
|
||||
"`a`.`mutetime`, "
|
||||
"`a`.`locale`, "
|
||||
"`a`.`os`, "
|
||||
"(SELECT 1 FROM `account_banned` WHERE `id` = `a`.`id` AND `active` = 1 "
|
||||
"AND (`unbandate` > UNIX_TIMESTAMP() OR `unbandate` = `bandate`) LIMIT 1), "
|
||||
"(SELECT 1 FROM `ip_banned` WHERE (`unbandate` = `bandate` OR `unbandate` > UNIX_TIMESTAMP()) "
|
||||
"AND `ip` = '%s' LIMIT 1) "
|
||||
"FROM `account` AS `a` WHERE `a`.`username` = '%s'",
|
||||
safeAddress.c_str(), safeAccount.c_str()));
|
||||
|
||||
if (!result)
|
||||
return Rejected(proto::AuthStatus::UnknownAccount);
|
||||
|
||||
Field const* fields = result->Fetch();
|
||||
if (fields[11].GetUInt32() || fields[12].GetUInt32())
|
||||
return Rejected(proto::AuthStatus::Banned);
|
||||
|
||||
if (fields[4].GetBool()
|
||||
&& std::strcmp(fields[3].GetString(), request.peerAddress.c_str()) != 0)
|
||||
{
|
||||
return Rejected(proto::AuthStatus::Failed);
|
||||
}
|
||||
|
||||
uint32 security = fields[1].GetUInt16();
|
||||
if (security > SEC_ADMINISTRATOR)
|
||||
security = SEC_ADMINISTRATOR;
|
||||
|
||||
AccountTypes const allowedAccountType = sWorld.GetPlayerSecurityLimit();
|
||||
if (allowedAccountType > SEC_PLAYER && AccountTypes(security) < allowedAccountType)
|
||||
return Rejected(proto::AuthStatus::Unavailable);
|
||||
|
||||
std::string const os = fields[10].GetString();
|
||||
bool const wardenActive = sWorld.getConfig(CONFIG_BOOL_WARDEN_WIN_ENABLED)
|
||||
|| sWorld.getConfig(CONFIG_BOOL_WARDEN_OSX_ENABLED);
|
||||
if (wardenActive && os != "Win" && os != "OSX")
|
||||
return Rejected(proto::AuthStatus::Reject);
|
||||
|
||||
auto row = std::make_shared<AccountRow>();
|
||||
row->id = fields[0].GetUInt32();
|
||||
row->security = AccountTypes(security);
|
||||
row->expansion = sWorld.getConfig(CONFIG_UINT32_EXPANSION) > fields[7].GetUInt8()
|
||||
? fields[7].GetUInt8() : uint8(sWorld.getConfig(CONFIG_UINT32_EXPANSION));
|
||||
row->muteTime = time_t(fields[8].GetUInt64());
|
||||
uint8 const locale = fields[9].GetUInt8();
|
||||
row->locale = locale >= MAX_LOCALE ? LOCALE_enUS : LocaleConstant(locale);
|
||||
row->os = os;
|
||||
row->sessionKey.SetHexStr(fields[2].GetString());
|
||||
|
||||
BigNumber verifier;
|
||||
BigNumber salt;
|
||||
verifier.SetHexStr(fields[5].GetString());
|
||||
salt.SetHexStr(fields[6].GetString());
|
||||
char const* saltHex = salt.AsHexStr();
|
||||
char const* verifierHex = verifier.AsHexStr();
|
||||
DEBUG_LOG("WorldGateway::LookupAccount: (s,v) check s: %s v: %s",
|
||||
saltHex, verifierHex);
|
||||
OPENSSL_free(const_cast<char*>(saltHex));
|
||||
OPENSSL_free(const_cast<char*>(verifierHex));
|
||||
|
||||
proto::AuthLookup lookup;
|
||||
lookup.status = proto::AuthStatus::Ok;
|
||||
lookup.sessionKey = row->sessionKey;
|
||||
lookup.context = row;
|
||||
return lookup;
|
||||
}
|
||||
|
||||
proto::SessionId WorldGateway::Attach(const proto::AuthRequest& request,
|
||||
const std::shared_ptr<proto::IClientLink>& link,
|
||||
const std::shared_ptr<proto::AuthContext>& context)
|
||||
{
|
||||
EnsureDbThreadRegistered();
|
||||
std::shared_ptr<AccountRow> const account =
|
||||
std::dynamic_pointer_cast<AccountRow>(context);
|
||||
if (!link || !account)
|
||||
return proto::INVALID_SESSION_ID;
|
||||
|
||||
std::string safeAddress = request.peerAddress;
|
||||
LoginDatabase.escape_string(safeAddress);
|
||||
LoginDatabase.PExecute("UPDATE `account` SET `last_ip` = '%s' WHERE `id` = '%u'",
|
||||
safeAddress.c_str(), account->id);
|
||||
|
||||
auto mailbox = std::make_shared<SessionMailbox>();
|
||||
auto session = std::make_unique<WorldSession>(account->id, link, mailbox,
|
||||
account->security, account->expansion, account->muteTime, account->locale);
|
||||
session->LoadTutorialsData();
|
||||
|
||||
WorldPacket addonSource(CMSG_AUTH_SESSION, request.addonData.size());
|
||||
if (!request.addonData.empty())
|
||||
addonSource.append(request.addonData.data(), request.addonData.size());
|
||||
|
||||
bool const wardenActive = sWorld.getConfig(CONFIG_BOOL_WARDEN_WIN_ENABLED)
|
||||
|| sWorld.getConfig(CONFIG_BOOL_WARDEN_OSX_ENABLED);
|
||||
if (wardenActive)
|
||||
session->InitWarden(uint16(request.build), &account->sessionKey, account->os);
|
||||
|
||||
WorldPacket addonResponse;
|
||||
if (sAddOnHandler.BuildAddonPacket(&addonSource, &addonResponse))
|
||||
{
|
||||
session->SetPendingAddonInfo(
|
||||
std::make_unique<WorldPacket>(std::move(addonResponse)));
|
||||
}
|
||||
if (link->IsClosed())
|
||||
return proto::INVALID_SESSION_ID;
|
||||
|
||||
proto::SessionId sessionId;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_lock);
|
||||
do
|
||||
{
|
||||
sessionId = ++m_nextSessionId;
|
||||
if (sessionId == proto::INVALID_SESSION_ID)
|
||||
sessionId = ++m_nextSessionId;
|
||||
}
|
||||
while (m_routes.find(sessionId) != m_routes.end());
|
||||
m_routes.emplace(sessionId, mailbox);
|
||||
}
|
||||
|
||||
WorldSession* const publishedSession = session.release();
|
||||
try
|
||||
{
|
||||
sWorld.AddSession(publishedSession);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
session.reset(publishedSession);
|
||||
Detach(sessionId);
|
||||
throw;
|
||||
}
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
void WorldGateway::Deliver(proto::SessionId session, WorldPacket&& packet)
|
||||
{
|
||||
std::shared_ptr<SessionMailbox> mailbox;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_lock);
|
||||
auto const route = m_routes.find(session);
|
||||
if (route == m_routes.end())
|
||||
return;
|
||||
mailbox = route->second;
|
||||
}
|
||||
|
||||
mailbox->Enqueue(std::make_unique<WorldPacket>(std::move(packet)));
|
||||
}
|
||||
|
||||
void WorldGateway::Detach(proto::SessionId session)
|
||||
{
|
||||
std::shared_ptr<SessionMailbox> mailbox;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_lock);
|
||||
auto const route = m_routes.find(session);
|
||||
if (route == m_routes.end())
|
||||
return;
|
||||
mailbox = route->second;
|
||||
m_routes.erase(route);
|
||||
}
|
||||
|
||||
mailbox->Close();
|
||||
}
|
||||
30
src/game/Server/WorldGateway.h
Normal file
30
src/game/Server/WorldGateway.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef MANGOS_H_WORLDGATEWAY
|
||||
#define MANGOS_H_WORLDGATEWAY
|
||||
|
||||
#include "IWorldGateway.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
class SessionMailbox;
|
||||
|
||||
class WorldGateway final : public proto::IWorldGateway
|
||||
{
|
||||
public:
|
||||
bool FilterAuthPacket(WorldPacket& packet) override;
|
||||
void TracePacket(const WorldPacket& packet, bool incoming) override;
|
||||
proto::AuthLookup LookupAccount(const proto::AuthRequest& request) override;
|
||||
proto::SessionId Attach(const proto::AuthRequest& request,
|
||||
const std::shared_ptr<proto::IClientLink>& link,
|
||||
const std::shared_ptr<proto::AuthContext>& context) override;
|
||||
void Deliver(proto::SessionId session, WorldPacket&& packet) override;
|
||||
void Detach(proto::SessionId session) override;
|
||||
|
||||
private:
|
||||
std::mutex m_lock;
|
||||
proto::SessionId m_nextSessionId = proto::INVALID_SESSION_ID;
|
||||
std::unordered_map<proto::SessionId, std::shared_ptr<SessionMailbox>> m_routes;
|
||||
};
|
||||
|
||||
#endif
|
||||
40
src/game/Server/WorldNetwork.cpp
Normal file
40
src/game/Server/WorldNetwork.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#include "WorldNetwork.h"
|
||||
|
||||
#include "Log.h"
|
||||
#include "OpcodeTable.h"
|
||||
|
||||
WorldNetwork::WorldNetwork()
|
||||
: m_listener(m_gateway)
|
||||
{
|
||||
}
|
||||
|
||||
WorldNetwork::~WorldNetwork()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool WorldNetwork::Start(uint16 port, const std::string& bindIp)
|
||||
{
|
||||
if (m_started)
|
||||
return false;
|
||||
|
||||
InitializeOpcodes();
|
||||
if (!m_listener.Start(port, bindIp))
|
||||
{
|
||||
sLog.outError("WorldNetwork::Start: failed to listen on %s:%u",
|
||||
bindIp.empty() ? "0.0.0.0" : bindIp.c_str(), unsigned(port));
|
||||
return false;
|
||||
}
|
||||
|
||||
m_started = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WorldNetwork::Stop()
|
||||
{
|
||||
if (!m_started)
|
||||
return;
|
||||
|
||||
m_listener.Stop();
|
||||
m_started = false;
|
||||
}
|
||||
29
src/game/Server/WorldNetwork.h
Normal file
29
src/game/Server/WorldNetwork.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef MANGOS_H_WORLDNETWORK
|
||||
#define MANGOS_H_WORLDNETWORK
|
||||
|
||||
#include "Listener.h"
|
||||
#include "Policies/Singleton.h"
|
||||
#include "WorldGateway.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
class WorldNetwork : public MaNGOS::Singleton<WorldNetwork>
|
||||
{
|
||||
friend class MaNGOS::Singleton<WorldNetwork>;
|
||||
|
||||
public:
|
||||
bool Start(uint16 port, const std::string& bindIp);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
WorldNetwork();
|
||||
~WorldNetwork();
|
||||
|
||||
WorldGateway m_gateway;
|
||||
proto::Listener m_listener;
|
||||
bool m_started = false;
|
||||
};
|
||||
|
||||
#define sWorldNetwork MaNGOS::Singleton<WorldNetwork>::Instance()
|
||||
|
||||
#endif
|
||||
|
|
@ -41,15 +41,16 @@
|
|||
* - World::UpdateSessions() context: Process all packets
|
||||
*
|
||||
* @see WorldSession for the session class
|
||||
* @see WorldSocket for the network socket
|
||||
* @see Opcodes.cpp for opcode registration
|
||||
* @see SessionMailbox for incoming protocol delivery
|
||||
* @see OpcodeTable.cpp for opcode registration
|
||||
*/
|
||||
|
||||
#include "WorldSocket.h"
|
||||
#include "Common.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
#include "IClientLink.h"
|
||||
#include "Log.h"
|
||||
#include "Opcodes.h"
|
||||
#include "OpcodeTable.h"
|
||||
#include "SessionMailbox.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
#include "Player.h"
|
||||
|
|
@ -152,35 +153,41 @@ bool WorldSessionFilter::Process(WorldPacket* packet)
|
|||
}
|
||||
|
||||
/// WorldSession constructor
|
||||
WorldSession::WorldSession(uint32 id, std::shared_ptr<WorldSocket> sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale) :
|
||||
WorldSession::WorldSession(uint32 id, std::shared_ptr<proto::IClientLink> link,
|
||||
std::shared_ptr<SessionMailbox> mailbox, AccountTypes sec,
|
||||
uint8 expansion, time_t mute_time, LocaleConstant locale) :
|
||||
LookingForGroup_auto_join(false), LookingForGroup_auto_add(false), m_muteTime(mute_time),
|
||||
_player(NULL), m_Socket(std::move(sock)), _security(sec), _accountId(id), m_expansion(expansion), _warden(NULL), _build(0), _logoutTime(0),
|
||||
_player(NULL), m_link(std::move(link)),
|
||||
m_mailbox(mailbox ? std::move(mailbox) : std::make_shared<SessionMailbox>()),
|
||||
_security(sec), _accountId(id), m_expansion(expansion), _warden(NULL), _build(0), _logoutTime(0),
|
||||
m_inQueue(false), m_playerLoading(false), m_playerLogout(false), m_playerRecentlyLogout(false), m_playerSave(false),
|
||||
m_sessionDbcLocale(sWorld.GetAvailableDbcLocale(locale)), m_sessionDbLocaleIndex(sObjectMgr.GetIndexForLocale(locale)),
|
||||
m_latency(0), m_tutorialState(TUTORIALDATA_UNCHANGED), m_clientTimeDelay(0), m_npcWatchLastGuid(),
|
||||
m_lastPingTime(0), m_overSpeedPings(0)
|
||||
{
|
||||
if (sock)
|
||||
{
|
||||
m_Address = sock->GetRemoteAddress();
|
||||
|
||||
}
|
||||
if (m_link)
|
||||
m_Address = m_link->GetRemoteAddress();
|
||||
}
|
||||
|
||||
/// WorldSession destructor
|
||||
WorldSession::~WorldSession()
|
||||
{
|
||||
m_mailbox->Close();
|
||||
WorldPacket* packet = NULL;
|
||||
while (m_mailbox->Next(packet))
|
||||
delete packet;
|
||||
|
||||
///- unload player if not unloaded
|
||||
if (_player)
|
||||
{
|
||||
LogoutPlayer(true);
|
||||
}
|
||||
|
||||
/// - If have unclosed socket, close it
|
||||
if (m_Socket)
|
||||
/// - If the client link remains live, close it
|
||||
if (m_link)
|
||||
{
|
||||
m_Socket->CloseSocket();
|
||||
m_Socket.reset();
|
||||
m_link->Close();
|
||||
m_link.reset();
|
||||
}
|
||||
|
||||
// Warden
|
||||
|
|
@ -189,12 +196,6 @@ WorldSession::~WorldSession()
|
|||
delete _warden;
|
||||
}
|
||||
|
||||
///- empty incoming packet queue
|
||||
WorldPacket* packet = NULL;
|
||||
while (_recvQueue.next(packet))
|
||||
{
|
||||
delete packet;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -206,7 +207,7 @@ WorldSession::~WorldSession()
|
|||
void WorldSession::SizeError(WorldPacket const& packet, uint32 size) const
|
||||
{
|
||||
sLog.outError("Client (account %u) send packet %s (%u) with size %zu but expected %u (attempt crash server?), skipped",
|
||||
GetAccountId(), packet.GetOpcodeName(), packet.GetOpcode(), packet.size(), size);
|
||||
GetAccountId(), LookupOpcodeName(packet.GetOpcode()), packet.GetOpcode(), packet.size(), size);
|
||||
}
|
||||
|
||||
/// Get the player name
|
||||
|
|
@ -232,7 +233,7 @@ void WorldSession::SendPacket(WorldPacket const* packet)
|
|||
}
|
||||
#endif
|
||||
|
||||
if (!m_Socket)
|
||||
if (!m_link)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -279,23 +280,34 @@ void WorldSession::SendPacket(WorldPacket const* packet)
|
|||
|
||||
#endif // !MANGOS_DEBUG
|
||||
|
||||
if (m_Socket->SendPacket(*packet) == -1)
|
||||
{
|
||||
m_Socket->CloseSocket();
|
||||
}
|
||||
m_link->SendPacket(*packet);
|
||||
}
|
||||
|
||||
void WorldSession::SetPendingAddonInfo(std::unique_ptr<WorldPacket> packet)
|
||||
{
|
||||
m_pendingAddonInfo = std::move(packet);
|
||||
}
|
||||
|
||||
void WorldSession::SendPendingAddonInfo()
|
||||
{
|
||||
if (!m_pendingAddonInfo)
|
||||
return;
|
||||
|
||||
SendPacket(m_pendingAddonInfo.get());
|
||||
m_pendingAddonInfo.reset();
|
||||
}
|
||||
|
||||
/// Add an incoming packet to the queue
|
||||
void WorldSession::QueuePacket(WorldPacket* new_packet)
|
||||
{
|
||||
_recvQueue.add(new_packet);
|
||||
m_mailbox->Enqueue(std::unique_ptr<WorldPacket>(new_packet));
|
||||
}
|
||||
|
||||
/// Logging helper for unexpected opcodes
|
||||
void WorldSession::LogUnexpectedOpcode(WorldPacket* packet, const char* reason)
|
||||
{
|
||||
sLog.outError("SESSION: received unexpected opcode %s (0x%.4X) %s",
|
||||
packet->GetOpcodeName(),
|
||||
LookupOpcodeName(packet->GetOpcode()),
|
||||
packet->GetOpcode(),
|
||||
reason);
|
||||
}
|
||||
|
|
@ -304,7 +316,7 @@ void WorldSession::LogUnexpectedOpcode(WorldPacket* packet, const char* reason)
|
|||
void WorldSession::LogUnprocessedTail(WorldPacket* packet)
|
||||
{
|
||||
sLog.outError("SESSION: opcode %s (0x%.4X) have unprocessed tail data (read stop at %zu from %zu)",
|
||||
packet->GetOpcodeName(),
|
||||
LookupOpcodeName(packet->GetOpcode()),
|
||||
packet->GetOpcode(),
|
||||
packet->rpos(), packet->wpos());
|
||||
}
|
||||
|
|
@ -313,13 +325,13 @@ void WorldSession::LogUnprocessedTail(WorldPacket* packet)
|
|||
bool WorldSession::Update(PacketFilter& updater)
|
||||
{
|
||||
///- Retrieve packets from the receive queue and call the appropriate handlers
|
||||
/// not process packets if socket already closed
|
||||
/// not process packets if the client link already closed
|
||||
WorldPacket* packet = NULL;
|
||||
while (m_Socket && !m_Socket->IsClosed() && _recvQueue.next(packet, updater))
|
||||
while (m_link && !m_link->IsClosed() && m_mailbox->Next(packet, updater))
|
||||
{
|
||||
/*#if 1
|
||||
sLog.outError( "MOEP: %s (0x%.4X)",
|
||||
packet->GetOpcodeName(),
|
||||
LookupOpcodeName(packet->GetOpcode()),
|
||||
packet->GetOpcode());
|
||||
#endif*/
|
||||
|
||||
|
|
@ -398,17 +410,17 @@ bool WorldSession::Update(PacketFilter& updater)
|
|||
break;
|
||||
case STATUS_NEVER:
|
||||
sLog.outError("SESSION: received not allowed opcode %s (0x%.4X)",
|
||||
packet->GetOpcodeName(),
|
||||
LookupOpcodeName(packet->GetOpcode()),
|
||||
packet->GetOpcode());
|
||||
break;
|
||||
case STATUS_UNHANDLED:
|
||||
DEBUG_LOG("SESSION: received not handled opcode %s (0x%.4X)",
|
||||
packet->GetOpcodeName(),
|
||||
LookupOpcodeName(packet->GetOpcode()),
|
||||
packet->GetOpcode());
|
||||
break;
|
||||
default:
|
||||
sLog.outError("SESSION: received wrong-status-req opcode %s (0x%.4X)",
|
||||
packet->GetOpcodeName(),
|
||||
LookupOpcodeName(packet->GetOpcode()),
|
||||
packet->GetOpcode());
|
||||
break;
|
||||
}
|
||||
|
|
@ -442,14 +454,14 @@ bool WorldSession::Update(PacketFilter& updater)
|
|||
}
|
||||
#endif
|
||||
|
||||
///- Cleanup socket pointer if need
|
||||
if (m_Socket && m_Socket->IsClosed())
|
||||
///- Cleanup client link if needed
|
||||
if (m_link && m_link->IsClosed())
|
||||
{
|
||||
m_Socket.reset();
|
||||
m_link.reset();
|
||||
}
|
||||
|
||||
// Warden
|
||||
if (m_Socket && !m_Socket->IsClosed() && _warden)
|
||||
if (m_link && !m_link->IsClosed() && _warden)
|
||||
{
|
||||
_warden->Update();
|
||||
}
|
||||
|
|
@ -460,18 +472,18 @@ bool WorldSession::Update(PacketFilter& updater)
|
|||
{
|
||||
///- If necessary, log the player out
|
||||
time_t currTime = time(NULL);
|
||||
if (!m_Socket || (ShouldLogOut(currTime) && !m_playerLoading))
|
||||
if (!m_link || (ShouldLogOut(currTime) && !m_playerLoading))
|
||||
{
|
||||
LogoutPlayer(true);
|
||||
}
|
||||
|
||||
// Warden
|
||||
if (m_Socket && GetPlayer() && _warden)
|
||||
if (m_link && GetPlayer() && _warden)
|
||||
{
|
||||
_warden->Update();
|
||||
}
|
||||
|
||||
if (!m_Socket)
|
||||
if (!m_link)
|
||||
{
|
||||
return false; // Will remove this session from the world session map
|
||||
}
|
||||
|
|
@ -488,7 +500,7 @@ bool WorldSession::Update(PacketFilter& updater)
|
|||
void WorldSession::HandleBotPackets()
|
||||
{
|
||||
WorldPacket* packet;
|
||||
while (_recvQueue.next(packet))
|
||||
while (m_mailbox->Next(packet))
|
||||
{
|
||||
OpcodeHandler const& opHandle = opcodeTable[packet->GetOpcode()];
|
||||
(this->*opHandle.handler)(*packet);
|
||||
|
|
@ -682,7 +694,7 @@ void WorldSession::LogoutPlayer(bool Save)
|
|||
|
||||
// remove player from the group if he is:
|
||||
// a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected)
|
||||
if (_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && m_Socket)
|
||||
if (_player->GetGroup() && !_player->GetGroup()->isRaidGroup() && m_link)
|
||||
{
|
||||
_player->RemoveFromGroup();
|
||||
}
|
||||
|
|
@ -754,17 +766,16 @@ void WorldSession::LogoutPlayer(bool Save)
|
|||
/// Kick a player out of the World
|
||||
void WorldSession::KickPlayer()
|
||||
{
|
||||
if (m_Socket)
|
||||
if (m_link)
|
||||
{
|
||||
m_Socket->CloseSocket();
|
||||
m_link->Close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles a client ping and replies with a pong.
|
||||
*
|
||||
* Formerly handled in-place on the network thread by WorldSocket; now runs
|
||||
* here, on the world/map thread, like every other opcode.
|
||||
* Runs here, on the world/map thread, like every other opcode.
|
||||
*/
|
||||
void WorldSession::HandlePingOpcode(WorldPacket& recv_data)
|
||||
{
|
||||
|
|
@ -815,19 +826,12 @@ void WorldSession::HandlePingOpcode(WorldPacket& recv_data)
|
|||
/**
|
||||
* @brief Handles a client keep-alive.
|
||||
*
|
||||
* Formerly handled in-place on the network thread by WorldSocket (including
|
||||
* the Eluna hook below); now runs here, on the world/map thread.
|
||||
* Runs here, on the world/map thread. ExecuteOpcode() invokes the Eluna packet hook
|
||||
* before dispatch, so this handler must not invoke it a second time.
|
||||
*/
|
||||
void WorldSession::HandleKeepAliveOpcode(WorldPacket& recv_data)
|
||||
{
|
||||
DEBUG_LOG("CMSG_KEEP_ALIVE ,size: %zu ", recv_data.size());
|
||||
|
||||
#ifdef ENABLE_ELUNA
|
||||
if (Eluna* e = sWorld.GetEluna())
|
||||
{
|
||||
e->OnPacketReceive(this, recv_data);
|
||||
}
|
||||
#endif /* ENABLE_ELUNA */
|
||||
}
|
||||
|
||||
/// Cancel channeling handler
|
||||
|
|
@ -913,7 +917,7 @@ const char* WorldSession::GetMangosString(int32 entry) const
|
|||
void WorldSession::Handle_NULL(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("SESSION: received unimplemented opcode %s (0x%.4X)",
|
||||
recvPacket.GetOpcodeName(),
|
||||
LookupOpcodeName(recvPacket.GetOpcode()),
|
||||
recvPacket.GetOpcode());
|
||||
}
|
||||
|
||||
|
|
@ -924,8 +928,8 @@ void WorldSession::Handle_NULL(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::Handle_EarlyProccess(WorldPacket& recvPacket)
|
||||
{
|
||||
sLog.outError("SESSION: received opcode %s (0x%.4X) that must be processed in WorldSocket::OnRead",
|
||||
recvPacket.GetOpcodeName(),
|
||||
sLog.outError("SESSION: received opcode %s (0x%.4X) that must be processed by the protocol layer",
|
||||
LookupOpcodeName(recvPacket.GetOpcode()),
|
||||
recvPacket.GetOpcode());
|
||||
}
|
||||
|
||||
|
|
@ -937,7 +941,7 @@ void WorldSession::Handle_EarlyProccess(WorldPacket& recvPacket)
|
|||
void WorldSession::Handle_ServerSide(WorldPacket& recvPacket)
|
||||
{
|
||||
sLog.outError("SESSION: received server-side opcode %s (0x%.4X)",
|
||||
recvPacket.GetOpcodeName(),
|
||||
LookupOpcodeName(recvPacket.GetOpcode()),
|
||||
recvPacket.GetOpcode());
|
||||
}
|
||||
|
||||
|
|
@ -949,7 +953,7 @@ void WorldSession::Handle_ServerSide(WorldPacket& recvPacket)
|
|||
void WorldSession::Handle_Deprecated(WorldPacket& recvPacket)
|
||||
{
|
||||
sLog.outError("SESSION: received deprecated opcode %s (0x%.4X)",
|
||||
recvPacket.GetOpcodeName(),
|
||||
LookupOpcodeName(recvPacket.GetOpcode()),
|
||||
recvPacket.GetOpcode());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class Player;
|
|||
class Unit;
|
||||
class Warden;
|
||||
class WorldPacket;
|
||||
class WorldSocket;
|
||||
class SessionMailbox;
|
||||
class QueryResult;
|
||||
class LoginQueryHolder;
|
||||
class CharacterHandler;
|
||||
|
|
@ -65,6 +65,11 @@ class WorldSession;
|
|||
|
||||
struct OpcodeHandler;
|
||||
|
||||
namespace proto
|
||||
{
|
||||
class IClientLink;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Party operation enumeration
|
||||
*/
|
||||
|
|
@ -249,12 +254,15 @@ class WorldSession
|
|||
/**
|
||||
* @brief Constructor
|
||||
* @param id Session ID
|
||||
* @param sock World socket
|
||||
* @param link Client protocol link
|
||||
* @param mailbox Incoming packet mailbox
|
||||
* @param sec Account security level
|
||||
* @param mute_time Mute time
|
||||
* @param locale Locale
|
||||
*/
|
||||
WorldSession(uint32 id, std::shared_ptr<WorldSocket> sock, AccountTypes sec, uint8 expansion, time_t mute_time, LocaleConstant locale);
|
||||
WorldSession(uint32 id, std::shared_ptr<proto::IClientLink> link,
|
||||
std::shared_ptr<SessionMailbox> mailbox, AccountTypes sec,
|
||||
uint8 expansion, time_t mute_time, LocaleConstant locale);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
|
|
@ -291,6 +299,8 @@ class WorldSession
|
|||
void SizeError(WorldPacket const& packet, uint32 size) const;
|
||||
|
||||
void SendPacket(WorldPacket const* packet);
|
||||
void SetPendingAddonInfo(std::unique_ptr<WorldPacket> packet);
|
||||
void SendPendingAddonInfo();
|
||||
void SendNotification(const char* format, ...) ATTR_PRINTF(2, 3);
|
||||
void SendNotification(int32 string_id, ...);
|
||||
void SendPetNameInvalid(uint32 error, const std::string& name, DeclinedName* declinedName);
|
||||
|
|
@ -501,7 +511,7 @@ class WorldSession
|
|||
|
||||
// opcodes handlers
|
||||
void Handle_NULL(WorldPacket& recvPacket); // not used
|
||||
void Handle_EarlyProccess(WorldPacket& recvPacket); // just mark packets processed in WorldSocket::OnRead
|
||||
void Handle_EarlyProccess(WorldPacket& recvPacket); // marks packets handled by the protocol layer
|
||||
void Handle_ServerSide(WorldPacket& recvPacket); // sever side only, can't be accepted from client
|
||||
void Handle_Deprecated(WorldPacket& recvPacket); // never used anymore by client
|
||||
|
||||
|
|
@ -958,9 +968,11 @@ class WorldSession
|
|||
void LogUnprocessedTail(WorldPacket* packet);
|
||||
|
||||
Player* _player;
|
||||
/// Shared with the transport: the socket may outlive its connection, which is
|
||||
/// what lets a tick in progress finish safely (its sends simply become no-ops).
|
||||
std::shared_ptr<WorldSocket> m_Socket;
|
||||
/// Thread-safe protocol link retained while the client is connected.
|
||||
std::shared_ptr<proto::IClientLink> m_link;
|
||||
/// Shared queue used by the network gateway without exposing this session.
|
||||
std::shared_ptr<SessionMailbox> m_mailbox;
|
||||
std::unique_ptr<WorldPacket> m_pendingAddonInfo;
|
||||
std::string m_Address;
|
||||
|
||||
AccountTypes _security;
|
||||
|
|
@ -985,12 +997,11 @@ class WorldSession
|
|||
int32 m_clientTimeDelay;
|
||||
ObjectGuid m_npcWatchLastGuid;
|
||||
|
||||
// Ping flood tracking, formerly on WorldSocket (network thread); now
|
||||
// Ping flood tracking now lives exclusively on the world thread and is
|
||||
// only ever touched from HandlePingOpcode() on the world/map thread.
|
||||
time_t m_lastPingTime;
|
||||
uint32 m_overSpeedPings;
|
||||
|
||||
MaNGOS::LockedQueue<WorldPacket*> _recvQueue;
|
||||
};
|
||||
#endif
|
||||
/// @}
|
||||
|
|
|
|||
|
|
@ -1,746 +0,0 @@
|
|||
/**
|
||||
* MaNGOS is a full featured server for World of Warcraft, supporting
|
||||
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
|
||||
*
|
||||
* Copyright (C) 2005-2025 MaNGOS <https://www.getmangos.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
|
||||
* and lore are copyrighted by Blizzard Entertainment, Inc.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file WorldSocket.cpp
|
||||
* @brief World server network socket implementation
|
||||
*
|
||||
* This file implements WorldSocket which handles individual client
|
||||
* connections to the world server. It manages:
|
||||
*
|
||||
* - TCP socket communication using ACE
|
||||
* - Packet encryption/decryption (SRP6)
|
||||
* - Packet fragmentation and reassembly
|
||||
* - Session creation (account lookup is asynchronous; see
|
||||
* HandleAuthSession()/HandleAuthSessionCallback())
|
||||
*
|
||||
* The socket uses the SRP6 authentication protocol for secure
|
||||
* client-server communication. Ping/pong and keep-alive are handled by
|
||||
* WorldSession (see WorldSession::HandlePingOpcode()/HandleKeepAliveOpcode()),
|
||||
* not here, so nothing on this network-thread-owned class runs game logic.
|
||||
*
|
||||
* @see WorldSocket for the socket class
|
||||
* @see WorldSession for the player session
|
||||
* @see WorldSocketMgr for the socket manager
|
||||
*/
|
||||
#include "WorldSocket.h"
|
||||
#include "Common.h"
|
||||
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "ByteBuffer.h"
|
||||
#include "AddonHandler.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Database/DatabaseEnv.h"
|
||||
#include "Auth/BigNumber.h"
|
||||
#include "Auth/Sha1.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldSocketMgr.h"
|
||||
#include "Log.h"
|
||||
#include "DBCStores.h"
|
||||
#ifdef ENABLE_ELUNA
|
||||
#include "LuaEngine.h"
|
||||
#endif /* ENABLE_ELUNA */
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if defined( __GNUC__ )
|
||||
#pragma pack(1)
|
||||
#else
|
||||
#pragma pack(push,1)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Server packet header structure
|
||||
*
|
||||
* Header for packets sent from server to client.
|
||||
*/
|
||||
struct ServerPktHeader
|
||||
{
|
||||
uint16 size; ///< Packet size
|
||||
uint16 cmd; ///< Opcode
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Client packet header structure
|
||||
*
|
||||
* Header for packets sent from client to server.
|
||||
*/
|
||||
struct ClientPktHeader
|
||||
{
|
||||
uint16 size; ///< Packet size
|
||||
uint32 cmd; ///< Opcode
|
||||
};
|
||||
|
||||
#if defined( __GNUC__ )
|
||||
#pragma pack()
|
||||
#else
|
||||
#pragma pack(pop)
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief WorldSocket constructor
|
||||
*
|
||||
* Initializes a new client socket with default values:
|
||||
* - Last ping time: zero
|
||||
* - Overspeed pings: 0
|
||||
* - Session: NULL
|
||||
* - Output buffer size: 64KB
|
||||
* - Random seed for encryption
|
||||
*/
|
||||
WorldSocket::WorldSocket() :
|
||||
m_Session(0),
|
||||
m_closed(false),
|
||||
m_headerPending(false),
|
||||
m_recvOpcode(0),
|
||||
m_recvSize(0),
|
||||
m_Seed(static_cast<uint32>(rand32())),
|
||||
m_AuthPending(false),
|
||||
m_AuthBuildNumber(0),
|
||||
m_AuthClientSeed(0),
|
||||
m_AuthDigest{},
|
||||
m_AuthAddonData()
|
||||
{
|
||||
}
|
||||
|
||||
WorldSocket::~WorldSocket()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mark the connection dead and ask the transport to close it.
|
||||
*
|
||||
* Idempotent, and safe from any thread: the Closer is disarmed by the transport at
|
||||
* teardown, so a late call from the world thread is a no-op rather than a use-after-free.
|
||||
*/
|
||||
void WorldSocket::CloseSocket()
|
||||
{
|
||||
if (m_closed.exchange(true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Detach the session first, so the network thread can never route a packet into a
|
||||
// session that is being torn down.
|
||||
SetSession(NULL);
|
||||
|
||||
if (m_closer)
|
||||
{
|
||||
m_closer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Serialise a packet into an encrypted-header frame ready for the wire.
|
||||
*
|
||||
* Wire format is a 4-byte ServerPktHeader -- big-endian size (payload + 2), little-endian
|
||||
* opcode -- with the whole header encrypted, followed by the raw payload.
|
||||
*/
|
||||
std::vector<uint8_t> WorldSocket::EncodePacket(const WorldPacket& pct)
|
||||
{
|
||||
ServerPktHeader header;
|
||||
header.cmd = pct.GetOpcode();
|
||||
header.size = static_cast<uint16>(pct.size() + 2);
|
||||
|
||||
EndianConvertReverse(header.size);
|
||||
EndianConvert(header.cmd);
|
||||
|
||||
std::vector<uint8_t> frame;
|
||||
frame.reserve(sizeof(header) + pct.size());
|
||||
|
||||
{
|
||||
// EncryptSend mutates the cipher state, and SendPacket is reachable from both
|
||||
// the world thread and the network thread, so the encrypt-and-append must be
|
||||
// atomic: two packets encrypted out of order would decrypt to garbage.
|
||||
std::lock_guard<std::mutex> guard(m_CryptSendLock);
|
||||
|
||||
m_Crypt.EncryptSend(reinterpret_cast<uint8*>(&header), sizeof(header));
|
||||
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(&header);
|
||||
frame.insert(frame.end(), raw, raw + sizeof(header));
|
||||
|
||||
if (!pct.empty())
|
||||
{
|
||||
frame.insert(frame.end(), pct.contents(), pct.contents() + pct.size());
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send a packet. Reentrant, and callable from any thread.
|
||||
*
|
||||
* The bytes are handed straight to the transport, which appends them to this
|
||||
* connection's outbound buffer. That buffer coalesces everything queued between two
|
||||
* writes into one send, so the per-packet output buffer and overflow queue this class
|
||||
* used to carry are no longer needed.
|
||||
*
|
||||
* @return -1 if the connection is gone.
|
||||
*/
|
||||
int WorldSocket::SendPacket(const WorldPacket& pct)
|
||||
{
|
||||
if (m_closed.load() || !m_sender)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (sLog.IsPacketLoggingEnabled())
|
||||
{
|
||||
sLog.outWorldPacketDump(0, pct.GetOpcode(), pct.GetOpcodeName(), &pct, false);
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> frame = EncodePacket(pct);
|
||||
m_sender(frame.data(), frame.size());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Greet the client with SMSG_AUTH_CHALLENGE, carrying our seed (network thread).
|
||||
std::vector<uint8_t> WorldSocket::onConnect()
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_CHALLENGE, 4);
|
||||
packet << m_Seed;
|
||||
|
||||
return EncodePacket(packet);
|
||||
}
|
||||
|
||||
/// The transport is tearing the connection down (network thread).
|
||||
void WorldSocket::onClose()
|
||||
{
|
||||
m_closed.store(true);
|
||||
|
||||
// Drop the session link so a world tick still holding this socket stops routing
|
||||
// through it. The WorldSession itself outlives us; it notices via IsClosed().
|
||||
SetSession(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reassemble the TCP stream into packets (network thread).
|
||||
*
|
||||
* A recv may deliver half a header, several whole packets, or anything between, so bytes
|
||||
* accumulate in m_recvBuf until a complete packet can be cut out. A header is decrypted
|
||||
* exactly once -- m_headerPending remembers that we already did it -- because decryption
|
||||
* advances the cipher and doing it twice would corrupt every packet after it.
|
||||
*/
|
||||
std::vector<uint8_t> WorldSocket::onData(const uint8_t* data, size_t len)
|
||||
{
|
||||
if (m_closed.load())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
m_recvBuf.insert(m_recvBuf.end(), data, data + len);
|
||||
|
||||
size_t pos = 0;
|
||||
for (;;)
|
||||
{
|
||||
if (!m_headerPending)
|
||||
{
|
||||
if (m_recvBuf.size() - pos < sizeof(ClientPktHeader))
|
||||
{
|
||||
break; // not even a full header yet
|
||||
}
|
||||
|
||||
ClientPktHeader header;
|
||||
memcpy(&header, m_recvBuf.data() + pos, sizeof(header));
|
||||
pos += sizeof(header);
|
||||
|
||||
m_Crypt.DecryptRecv(reinterpret_cast<uint8*>(&header), sizeof(header));
|
||||
|
||||
EndianConvertReverse(header.size);
|
||||
EndianConvert(header.cmd);
|
||||
|
||||
if ((header.size < 4) || (header.size > 10240) || (header.cmd > 10240))
|
||||
{
|
||||
sLog.outError("WorldSocket::onData: client sent malformed packet size = %d , cmd = %d",
|
||||
header.size, header.cmd);
|
||||
CloseSocket();
|
||||
return {};
|
||||
}
|
||||
|
||||
m_recvOpcode = static_cast<uint16>(header.cmd);
|
||||
m_recvSize = header.size - 4u; // the opcode's own 4 bytes are counted in
|
||||
m_headerPending = true;
|
||||
}
|
||||
|
||||
if (m_recvBuf.size() - pos < m_recvSize)
|
||||
{
|
||||
break; // header is in hand, payload still incomplete
|
||||
}
|
||||
|
||||
WorldPacket* pct = new WorldPacket(OpcodesList(m_recvOpcode), m_recvSize);
|
||||
if (m_recvSize > 0)
|
||||
{
|
||||
pct->resize(m_recvSize);
|
||||
memcpy(const_cast<uint8*>(pct->contents()), m_recvBuf.data() + pos, m_recvSize);
|
||||
pos += m_recvSize;
|
||||
}
|
||||
m_headerPending = false;
|
||||
|
||||
if (ProcessIncoming(pct) == -1)
|
||||
{
|
||||
CloseSocket();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (m_closed.load())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Drop what we consumed; whatever is left is the start of the next packet.
|
||||
if (pos > 0)
|
||||
{
|
||||
m_recvBuf.erase(m_recvBuf.begin(), m_recvBuf.begin() + pos);
|
||||
}
|
||||
|
||||
// Nothing is ever answered synchronously: packets are queued to the session and
|
||||
// handled on the world thread, and replies go back out through the Sender.
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Dispatches a fully assembled incoming packet.
|
||||
*
|
||||
* @param new_pct The packet to process.
|
||||
* @return int Zero on success; otherwise -1.
|
||||
*/
|
||||
int WorldSocket::ProcessIncoming(WorldPacket* new_pct)
|
||||
{
|
||||
MANGOS_ASSERT(new_pct);
|
||||
|
||||
// manage memory ;)
|
||||
std::unique_ptr<WorldPacket> aptr(new_pct);
|
||||
|
||||
const uint16 opcode = new_pct->GetOpcode();
|
||||
|
||||
if (opcode >= NUM_MSG_TYPES)
|
||||
{
|
||||
sLog.outError("SESSION: received nonexistent opcode 0x%.4X", opcode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (m_closed.load())
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Dump received packet (opt-in via PacketLoggingEnabled; off by default).
|
||||
if (sLog.IsPacketLoggingEnabled())
|
||||
{
|
||||
sLog.outWorldPacketDump(0, new_pct->GetOpcode(), new_pct->GetOpcodeName(), new_pct, true);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
switch (opcode)
|
||||
{
|
||||
case CMSG_AUTH_SESSION:
|
||||
if (GetSession() || m_AuthPending)
|
||||
{
|
||||
sLog.outError("WorldSocket::ProcessIncoming: Player send CMSG_AUTH_SESSION again");
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_ELUNA
|
||||
if (Eluna* e = sWorld.GetEluna())
|
||||
{
|
||||
// No session exists yet at this point, so pass NULL to the hook.
|
||||
if (!e->OnPacketReceive(NULL, *new_pct))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif /* ENABLE_ELUNA */
|
||||
return HandleAuthSession(*new_pct);
|
||||
// CMSG_PING and CMSG_KEEP_ALIVE are intentionally not special-cased
|
||||
// here: they fall through to the default case below like every
|
||||
// other opcode, so they are queued to the session and handled by
|
||||
// WorldSession::Update() on the world/map thread instead of
|
||||
// in-place on the network thread.
|
||||
default:
|
||||
{
|
||||
// Hold the session lock across QueuePacket so the session cannot be
|
||||
// cleared (and later destroyed) while we hand the packet to it.
|
||||
// QueuePacket only touches its own queue lock, so no lock order is at
|
||||
// risk here.
|
||||
std::lock_guard<std::mutex> Guard(m_SessionLock);
|
||||
|
||||
if (m_Session != NULL)
|
||||
{
|
||||
// OK ,give the packet to WorldSession
|
||||
aptr.release();
|
||||
m_Session->QueuePacket(new_pct);
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
sLog.outError("WorldSocket::ProcessIncoming: Client not authed opcode = %u", uint32(opcode));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ByteBufferException&)
|
||||
{
|
||||
WorldSession* session = GetSession();
|
||||
sLog.outError("WorldSocket::ProcessIncoming ByteBufferException occured while parsing an instant handled packet (opcode: %u) from client %s, accountid=%i.",
|
||||
opcode, GetRemoteAddress().c_str(), session ? session->GetAccountId() : -1);
|
||||
if (sLog.HasLogLevelOrHigher(LOG_LVL_DEBUG))
|
||||
{
|
||||
DEBUG_LOG("Dumping error-causing packet:");
|
||||
new_pct->hexlike();
|
||||
}
|
||||
|
||||
if (sWorld.getConfig(CONFIG_BOOL_KICK_PLAYER_ON_BAD_PACKET))
|
||||
{
|
||||
DETAIL_LOG("Disconnecting session [account id %i / address %s] for badly formatted packet.",
|
||||
session ? session->GetAccountId() : -1, GetRemoteAddress().c_str());
|
||||
|
||||
return -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Validates a CMSG_AUTH_SESSION packet synchronously, then issues an
|
||||
* async account lookup so the network thread never blocks on a DB round-trip.
|
||||
*
|
||||
* @param recvPacket The authentication packet.
|
||||
* @return int Zero on success; otherwise -1.
|
||||
*/
|
||||
int WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
|
||||
{
|
||||
uint32 clientSeed;
|
||||
uint32 unk2;
|
||||
uint32 BuiltNumberClient;
|
||||
uint8 digest[SHA_DIGEST_LENGTH];
|
||||
std::string account;
|
||||
|
||||
// Read the content of the packet
|
||||
recvPacket >> BuiltNumberClient;
|
||||
recvPacket >> unk2;
|
||||
recvPacket >> account;
|
||||
recvPacket >> clientSeed;
|
||||
recvPacket.read(digest, SHA_DIGEST_LENGTH);
|
||||
|
||||
DEBUG_LOG("WorldSocket::HandleAuthSession: client %u, unk2 %u, account %s, clientseed %u",
|
||||
BuiltNumberClient,
|
||||
unk2,
|
||||
account.c_str(),
|
||||
clientSeed);
|
||||
|
||||
// Check the version of client trying to connect
|
||||
if (!IsAcceptableClientBuild(BuiltNumberClient))
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
|
||||
packet << uint8(AUTH_VERSION_MISMATCH);
|
||||
SendPacket(packet);
|
||||
|
||||
sLog.outError("WorldSocket::HandleAuthSession: Sent Auth Response (version mismatch).");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get the account information from the realmd database
|
||||
std::string safe_account = account; // Duplicate, else will screw the SHA hash verification below
|
||||
LoginDatabase.escape_string(safe_account);
|
||||
// No SQL injection, username escaped.
|
||||
|
||||
// Stash everything HandleAuthSessionCallback() will need once the async
|
||||
// account lookup below completes, since recvPacket does not outlive this
|
||||
// function and the socket is otherwise single-threaded during a login.
|
||||
m_AuthPending = true;
|
||||
m_AuthBuildNumber = BuiltNumberClient;
|
||||
m_AuthAccountName = account;
|
||||
m_AuthClientSeed = clientSeed;
|
||||
memcpy(m_AuthDigest, digest, SHA_DIGEST_LENGTH);
|
||||
m_AuthAddonData = recvPacket;
|
||||
|
||||
// Keep the socket alive until HandleAuthSessionCallback() runs. Capturing a
|
||||
// shared_ptr is what the transport's shared ownership is for: the connection may be
|
||||
// torn down while the query is still in flight, and the callback must still find a
|
||||
// live object (it will simply observe closed() and bail).
|
||||
auto self = std::static_pointer_cast<WorldSocket>(shared_from_this());
|
||||
|
||||
// Account lookup and ban check in a single round-trip: the account_banned
|
||||
// check needs the account id, which this same query produces, so it is
|
||||
// expressed as a correlated subquery instead of a second chained query.
|
||||
LoginDatabase.AsyncPQuery([self](QueryResult* result)
|
||||
{
|
||||
self->HandleAuthSessionCallback(result);
|
||||
},
|
||||
"SELECT "
|
||||
"`a`.`id`, " // 0
|
||||
"`a`.`gmlevel`, " // 1
|
||||
"`a`.`sessionkey`, " // 2
|
||||
"`a`.`last_ip`, " // 3
|
||||
"`a`.`locked`, " // 4
|
||||
"`a`.`v`, " // 5
|
||||
"`a`.`s`, " // 6
|
||||
"`a`.`expansion`, " // 7
|
||||
"`a`.`mutetime`, " // 8
|
||||
"`a`.`locale`, " // 9
|
||||
"`a`.`os`, " // 10
|
||||
"(SELECT 1 FROM `account_banned` WHERE `id` = `a`.`id` AND `active` = 1 "
|
||||
"AND (`unbandate` > UNIX_TIMESTAMP() OR `unbandate` = `bandate`) LIMIT 1), " // 11
|
||||
"(SELECT 1 FROM `ip_banned` WHERE (`unbandate` = `bandate` OR `unbandate` > UNIX_TIMESTAMP()) "
|
||||
"AND `ip` = '%s' LIMIT 1) " // 12
|
||||
"FROM `account` AS `a` "
|
||||
"WHERE `a`.`username` = '%s'",
|
||||
GetRemoteAddress().c_str(), safe_account.c_str());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Async callback for the account lookup started by HandleAuthSession().
|
||||
*
|
||||
* Invoked from Database::ProcessResultQueue() (called each tick from
|
||||
* World::Update()), so this runs on the world thread, never on the network
|
||||
* thread. Finishes what HandleAuthSession() used to do synchronously:
|
||||
* ban/lock checks, SHA verification, WorldSession creation and registration,
|
||||
* Warden init, and sending the addon packet.
|
||||
*
|
||||
* @param result The query result; this function takes ownership of it.
|
||||
*/
|
||||
void WorldSocket::HandleAuthSessionCallback(QueryResult* result)
|
||||
{
|
||||
std::unique_ptr<QueryResult> resultGuard(result);
|
||||
|
||||
m_AuthPending = false;
|
||||
|
||||
if (m_closed.load())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop if the account is not found
|
||||
if (!result)
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
|
||||
packet << uint8(AUTH_UNKNOWN_ACCOUNT);
|
||||
SendPacket(packet);
|
||||
|
||||
sLog.outError("WorldSocket::HandleAuthSessionCallback: Sent Auth Response (unknown account).");
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
const bool wardenActive = (sWorld.getConfig(CONFIG_BOOL_WARDEN_WIN_ENABLED) || sWorld.getConfig(CONFIG_BOOL_WARDEN_OSX_ENABLED));
|
||||
BigNumber v, s, g, N, K;
|
||||
|
||||
const Field* fields = result->Fetch();
|
||||
|
||||
// Account/IP ban check (evaluated as correlated subqueries in the lookup).
|
||||
if (fields[11].GetUInt32() || fields[12].GetUInt32())
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
|
||||
packet << uint8(AUTH_BANNED);
|
||||
SendPacket(packet);
|
||||
|
||||
sLog.outError("WorldSocket::HandleAuthSessionCallback: Sent Auth Response (Account banned).");
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8 expansion = ((sWorld.getConfig(CONFIG_UINT32_EXPANSION) > fields[7].GetUInt8()) ? fields[7].GetUInt8() : sWorld.getConfig(CONFIG_UINT32_EXPANSION));
|
||||
|
||||
N.SetHexStr("894B645E89E1535BBDAD5B8B290650530801B18EBFBF5E8FAB3C82872A3E9BB7");
|
||||
g.SetDword(7);
|
||||
|
||||
v.SetHexStr(fields[5].GetString());
|
||||
s.SetHexStr(fields[6].GetString());
|
||||
|
||||
const char* sStr = s.AsHexStr(); // Must be freed by OPENSSL_free()
|
||||
const char* vStr = v.AsHexStr(); // Must be freed by OPENSSL_free()
|
||||
|
||||
DEBUG_LOG("WorldSocket::HandleAuthSessionCallback: (s,v) check s: %s v: %s",
|
||||
sStr,
|
||||
vStr);
|
||||
|
||||
OPENSSL_free((void*) sStr);
|
||||
OPENSSL_free((void*) vStr);
|
||||
|
||||
///- Re-check ip locking (same check as in realmd).
|
||||
if (fields[4].GetBool())
|
||||
{
|
||||
if (strcmp(fields[3].GetString(), GetRemoteAddress().c_str()))
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
|
||||
packet << uint8(AUTH_FAILED);
|
||||
SendPacket(packet);
|
||||
|
||||
BASIC_LOG("WorldSocket::HandleAuthSessionCallback: Sent Auth Response (Account IP differs).");
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 id = fields[0].GetUInt32();
|
||||
uint32 security = fields[1].GetUInt16();
|
||||
if (security > SEC_ADMINISTRATOR) // prevent invalid security settings in DB
|
||||
{
|
||||
security = SEC_ADMINISTRATOR;
|
||||
}
|
||||
|
||||
K.SetHexStr(fields[2].GetString());
|
||||
|
||||
time_t mutetime = time_t (fields[8].GetUInt64());
|
||||
|
||||
uint8 tmpLoc = fields[9].GetUInt8();
|
||||
LocaleConstant locale = tmpLoc >= MAX_LOCALE ? LOCALE_enUS : LocaleConstant(tmpLoc);
|
||||
|
||||
std::string os = fields[10].GetString();
|
||||
|
||||
// Check locked state for server
|
||||
AccountTypes allowedAccountType = sWorld.GetPlayerSecurityLimit();
|
||||
|
||||
if (allowedAccountType > SEC_PLAYER && AccountTypes(security) < allowedAccountType)
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
|
||||
packet << uint8(AUTH_UNAVAILABLE);
|
||||
SendPacket(packet);
|
||||
|
||||
BASIC_LOG("WorldSocket::HandleAuthSessionCallback: User tries to login but his security level is not enough");
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Warden: Must be done before WorldSession is created
|
||||
if (wardenActive && os != "Win" && os != "OSX")
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
|
||||
packet << uint8(AUTH_REJECT);
|
||||
SendPacket(packet);
|
||||
|
||||
BASIC_LOG("WorldSocket::HandleAuthSessionCallback: Client %s attempted to log in using invalid client OS (%s).", GetRemoteAddress().c_str(), os.c_str());
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that Key and account name are the same on client and server
|
||||
uint8 t[4]{ 0 };
|
||||
uint32 seed = m_Seed;
|
||||
|
||||
Sha1Hash sha;
|
||||
sha.UpdateData(m_AuthAccountName);
|
||||
sha.UpdateData((uint8*) & t, 4);
|
||||
sha.UpdateData((uint8*) & m_AuthClientSeed, 4);
|
||||
sha.UpdateData((uint8*) & seed, 4);
|
||||
sha.UpdateBigNumbers(&K, nullptr);
|
||||
sha.Finalize();
|
||||
|
||||
if (memcmp(sha.GetDigest(), m_AuthDigest, SHA_DIGEST_LENGTH))
|
||||
{
|
||||
WorldPacket packet(SMSG_AUTH_RESPONSE, 1);
|
||||
packet << uint8(AUTH_FAILED);
|
||||
SendPacket(packet);
|
||||
|
||||
sLog.outError("WorldSocket::HandleAuthSessionCallback: Sent Auth Response (authentification failed).");
|
||||
CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
std::string address = GetRemoteAddress();
|
||||
|
||||
DEBUG_LOG("WorldSocket::HandleAuthSessionCallback: Client '%s' authenticated successfully from %s.",
|
||||
m_AuthAccountName.c_str(),
|
||||
address.c_str());
|
||||
|
||||
// Update the last_ip in the database
|
||||
// No SQL injection, username escaped.
|
||||
static SqlStatementID updAccount;
|
||||
|
||||
SqlStatement stmt = LoginDatabase.CreateStatement(updAccount, "UPDATE `account` SET `last_ip` = ? WHERE `username` = ?");
|
||||
stmt.PExecute(address.c_str(), m_AuthAccountName.c_str());
|
||||
|
||||
WorldSession* session = new WorldSession(id, std::static_pointer_cast<WorldSocket>(shared_from_this()),
|
||||
AccountTypes(security), expansion, mutetime, locale);
|
||||
|
||||
// Publish the session under the lock so the network thread routes incoming
|
||||
// packets to it consistently.
|
||||
SetSession(session);
|
||||
|
||||
m_Crypt.Init(&K);
|
||||
|
||||
session->LoadTutorialsData();
|
||||
|
||||
// Warden: Initialize Warden system only if it is enabled by config
|
||||
if (wardenActive)
|
||||
{
|
||||
session->InitWarden(uint16(m_AuthBuildNumber), &K, os);
|
||||
}
|
||||
|
||||
sWorld.AddSession(session);
|
||||
|
||||
// Create and send the Addon packet
|
||||
WorldPacket SendAddonPacked;
|
||||
if (sAddOnHandler.BuildAddonPacket(&m_AuthAddonData, &SendAddonPacked))
|
||||
{
|
||||
SendPacket(SendAddonPacked);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Returns the current session pointer under m_SessionLock.
|
||||
*
|
||||
* The returned pointer is either a live session or NULL, never a dangling
|
||||
* pointer, because the session is always cleared under the same lock before it
|
||||
* is destroyed.
|
||||
*
|
||||
* @return WorldSession* The current session, or NULL if none/closed.
|
||||
*/
|
||||
WorldSession* WorldSocket::GetSession()
|
||||
{
|
||||
std::lock_guard<std::mutex> Guard(m_SessionLock);
|
||||
return m_Session;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Stores the session pointer under m_SessionLock.
|
||||
*
|
||||
* @param session The session to associate with this socket, or NULL to clear.
|
||||
*/
|
||||
void WorldSocket::SetSession(WorldSession* session)
|
||||
{
|
||||
std::lock_guard<std::mutex> Guard(m_SessionLock);
|
||||
m_Session = session;
|
||||
}
|
||||
|
|
@ -1,195 +0,0 @@
|
|||
/**
|
||||
* MaNGOS is a full featured server for World of Warcraft, supporting
|
||||
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
|
||||
*
|
||||
* Copyright (C) 2005-2026 MaNGOS <https://www.getmangos.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
|
||||
* and lore are copyrighted by Blizzard Entertainment, Inc.
|
||||
*/
|
||||
|
||||
/** \addtogroup u2w User to World Communication
|
||||
* @{
|
||||
* \file WorldSocket.h
|
||||
* \author Derex <derex101@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef MANGOS_H_WORLDSOCKET
|
||||
#define MANGOS_H_WORLDSOCKET
|
||||
|
||||
#include "Common.h"
|
||||
#include "Auth/AuthCrypt.h"
|
||||
#include "Auth/Sha1.h"
|
||||
#include "WorldPacket.h"
|
||||
|
||||
#include "net/ISession.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
class WorldSession;
|
||||
class QueryResult;
|
||||
|
||||
/**
|
||||
* @brief The world protocol spoken over one client connection.
|
||||
*
|
||||
* A pure protocol object: the shared networking engine (net::Server) owns the socket
|
||||
* and the byte plumbing and hands the bytes here. Inbound, onData() reassembles the TCP
|
||||
* stream into WorldPackets and queues them for the world thread. Outbound, SendPacket()
|
||||
* encrypts a header and pushes the bytes through the Sender, which may be called from
|
||||
* any thread.
|
||||
*
|
||||
* @note There is no output buffer, packet overflow queue or write cork here any more.
|
||||
* The transport's net::SendQueue already coalesces everything queued between two writes
|
||||
* into a single contiguous send — which is exactly what the old 64 KB m_OutBuffer plus
|
||||
* its 10 ms cork existed to achieve, and it does so without a heap allocation per packet.
|
||||
*
|
||||
* @note Lifetime: the transport holds this by shared_ptr, and so does the WorldSession
|
||||
* once one is attached. The socket may therefore outlive its connection — deliberately,
|
||||
* and safely, because the transport disarms the Sender on teardown, so a world thread
|
||||
* still ticking a dying session merely sends into a no-op.
|
||||
*/
|
||||
class WorldSocket : public net::ISession
|
||||
{
|
||||
public:
|
||||
|
||||
WorldSocket();
|
||||
~WorldSocket() override;
|
||||
|
||||
// ── net::ISession ────────────────────────────────────────────────────────
|
||||
|
||||
/// Peer address, handed over by the transport right after accept.
|
||||
void setPeerAddress(const std::string& address) override { m_Address = address; }
|
||||
|
||||
void setSender(net::Sender sender) override { m_sender = std::move(sender); }
|
||||
void setCloser(net::Closer closer) override { m_closer = std::move(closer); }
|
||||
|
||||
/// Greets the client with SMSG_AUTH_CHALLENGE, carrying our seed.
|
||||
std::vector<uint8_t> onConnect() override;
|
||||
|
||||
/// Feed received bytes into the reassembler (network thread).
|
||||
std::vector<uint8_t> onData(const uint8_t* data, size_t len) override;
|
||||
|
||||
/// The transport is tearing the connection down (network thread).
|
||||
void onClose() override;
|
||||
|
||||
bool closed() const override { return m_closed.load(); }
|
||||
|
||||
// ── Used by WorldSession ─────────────────────────────────────────────────
|
||||
|
||||
/// Check if the socket is closed.
|
||||
bool IsClosed() const { return m_closed.load(); }
|
||||
|
||||
/// Mark the connection dead and ask the transport to close it.
|
||||
void CloseSocket();
|
||||
|
||||
/// Address of the connected peer.
|
||||
const std::string& GetRemoteAddress() const { return m_Address; }
|
||||
|
||||
/// Send a packet on the socket. Reentrant; callable from any thread.
|
||||
/// @return -1 on failure.
|
||||
int SendPacket(const WorldPacket& pct);
|
||||
|
||||
private:
|
||||
|
||||
/// Serialise one packet into an encrypted-header frame ready for the wire.
|
||||
std::vector<uint8_t> EncodePacket(const WorldPacket& pct);
|
||||
|
||||
/// Process one fully assembled incoming packet. Takes ownership of @p new_pct.
|
||||
int ProcessIncoming(WorldPacket* new_pct);
|
||||
|
||||
/// Called by ProcessIncoming() on CMSG_AUTH_SESSION. Validates what it can
|
||||
/// synchronously, then issues an async account lookup so the network thread
|
||||
/// never blocks on a DB round-trip.
|
||||
int HandleAuthSession(WorldPacket& recvPacket);
|
||||
|
||||
/// Async callback for the account lookup started by HandleAuthSession(). Runs on
|
||||
/// the world thread (via Database::ProcessResultQueue), not the network thread.
|
||||
/// Takes ownership of (and must delete) @p result.
|
||||
void HandleAuthSessionCallback(QueryResult* result);
|
||||
|
||||
/// Current session, read under m_SessionLock. Either live or NULL, never
|
||||
/// dangling: it is cleared under the same lock before the session is destroyed.
|
||||
WorldSession* GetSession();
|
||||
void SetSession(WorldSession* session);
|
||||
|
||||
private:
|
||||
|
||||
/// Address of the remote peer
|
||||
std::string m_Address;
|
||||
|
||||
/// Manages encryption of the packet headers
|
||||
AuthCrypt m_Crypt;
|
||||
|
||||
/// Serialises EncryptSend, which mutates the cipher state and is reached from
|
||||
/// both the world thread (SendPacket) and the network thread (onConnect).
|
||||
std::mutex m_CryptSendLock;
|
||||
|
||||
/// Protects m_Session. Set from the network thread on authentication and cleared
|
||||
/// on close, while the network thread also reads it to route incoming packets.
|
||||
std::mutex m_SessionLock;
|
||||
WorldSession* m_Session;
|
||||
|
||||
/// Set once the connection is finished with; the transport polls closed().
|
||||
std::atomic<bool> m_closed;
|
||||
|
||||
/// Outbound channel and teardown request, armed by the transport before
|
||||
/// onConnect(). Both are lifetime-safe: after teardown they become no-ops.
|
||||
net::Sender m_sender;
|
||||
net::Closer m_closer;
|
||||
|
||||
// ── Inbound reassembly (network thread only) ─────────────────────────────
|
||||
//
|
||||
// TCP is a stream, so one recv may carry half a header, several whole packets, or
|
||||
// anything in between. Bytes accumulate in m_recvBuf until a whole packet can be
|
||||
// cut out of them. m_headerPending records that a header has already been
|
||||
// decrypted: decryption mutates the cipher, so a header must never be decrypted
|
||||
// twice while we wait for its payload to arrive.
|
||||
std::vector<uint8_t> m_recvBuf;
|
||||
bool m_headerPending;
|
||||
uint16 m_recvOpcode;
|
||||
uint32 m_recvSize; ///< Payload bytes still expected
|
||||
|
||||
const uint32 m_Seed;
|
||||
|
||||
/// Set once HandleAuthSession() issues the async account lookup, so a second
|
||||
/// CMSG_AUTH_SESSION arriving before the first one's callback runs is rejected
|
||||
/// instead of clobbering the fields below.
|
||||
bool m_AuthPending;
|
||||
|
||||
/// Captured from CMSG_AUTH_SESSION and read back once the async account lookup
|
||||
/// completes. Written once on the network thread before the query is issued, then
|
||||
/// read once on the world thread when the callback runs, so it needs no locking.
|
||||
uint32 m_AuthBuildNumber;
|
||||
std::string m_AuthAccountName;
|
||||
uint32 m_AuthClientSeed;
|
||||
uint8 m_AuthDigest[SHA_DIGEST_LENGTH];
|
||||
|
||||
/// Remainder of CMSG_AUTH_SESSION (the addon block), copied because the original
|
||||
/// packet is freed once HandleAuthSession() returns but the addon list is only
|
||||
/// consumed once the account lookup completes.
|
||||
WorldPacket m_AuthAddonData;
|
||||
};
|
||||
|
||||
#endif /* MANGOS_H_WORLDSOCKET */
|
||||
|
||||
/// @}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/**
|
||||
* MaNGOS is a full featured server for World of Warcraft, supporting
|
||||
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
|
||||
*
|
||||
* Copyright (C) 2005-2026 MaNGOS <https://www.getmangos.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
|
||||
* and lore are copyrighted by Blizzard Entertainment, Inc.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file WorldSocketMgr.cpp
|
||||
* @brief Owner of the world server's listening socket.
|
||||
*
|
||||
* @see WorldSocketMgr for the manager class
|
||||
* @see WorldSocket for the protocol spoken on each connection
|
||||
*/
|
||||
|
||||
#include "Common.h"
|
||||
#include "Log.h"
|
||||
#include "Config/Config.h"
|
||||
#include "WorldSocket.h"
|
||||
#include "WorldSocketMgr.h"
|
||||
#include "Opcodes.h"
|
||||
|
||||
#include <memory>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
WorldSocketMgr::WorldSocketMgr()
|
||||
: m_started(false)
|
||||
{
|
||||
}
|
||||
|
||||
WorldSocketMgr::~WorldSocketMgr()
|
||||
{
|
||||
StopNetwork();
|
||||
}
|
||||
|
||||
int WorldSocketMgr::StartNetwork(uint16_t port, const std::string& bindIp)
|
||||
{
|
||||
if (m_started)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
// One WorldSocket per accepted connection; the engine owns it (and shares it with
|
||||
// the WorldSession once the client authenticates).
|
||||
net::SessionFactory factory = []() -> std::shared_ptr<net::ISession>
|
||||
{
|
||||
return std::make_shared<WorldSocket>();
|
||||
};
|
||||
|
||||
if (!m_server.start(port, std::move(factory), bindIp))
|
||||
{
|
||||
sLog.outError("WorldSocketMgr::StartNetwork: failed to listen on %s:%u",
|
||||
(bindIp.empty() ? "0.0.0.0" : bindIp.c_str()), unsigned(port));
|
||||
return -1;
|
||||
}
|
||||
|
||||
m_started = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WorldSocketMgr::StopNetwork()
|
||||
{
|
||||
if (!m_started)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_server.stop();
|
||||
m_started = false;
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
/**
|
||||
* MaNGOS is a full featured server for World of Warcraft, supporting
|
||||
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
|
||||
*
|
||||
* Copyright (C) 2005-2026 MaNGOS <https://www.getmangos.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* World of Warcraft, and all World of Warcraft or Warcraft art, images,
|
||||
* and lore are copyrighted by Blizzard Entertainment, Inc.
|
||||
*/
|
||||
|
||||
/** \addtogroup u2w User to World Communication
|
||||
* @{
|
||||
* \file WorldSocketMgr.h
|
||||
* \author Derex <derex101@gmail.com>
|
||||
*/
|
||||
|
||||
#ifndef MANGOS_H_WORLDSOCKETMGR
|
||||
#define MANGOS_H_WORLDSOCKETMGR
|
||||
|
||||
#include "Common.h"
|
||||
#include "Policies/Singleton.h"
|
||||
|
||||
#include "net/Server.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
* @brief Owns the world server's listening socket.
|
||||
*
|
||||
* A thin lid on the shared networking engine: it starts a net::Server on the world port
|
||||
* and gives it a factory that mints one WorldSocket per accepted connection. The engine
|
||||
* owns the threads, the sockets and the byte plumbing; everything protocol-shaped lives
|
||||
* in WorldSocket.
|
||||
*
|
||||
* The ACE reactor, its thread pool, the acceptor and the per-socket buffer tuning that
|
||||
* used to live here are gone — the engine does that job for every protocol now, and
|
||||
* realmd's AuthServer is the same shape.
|
||||
*/
|
||||
class WorldSocketMgr : public MaNGOS::Singleton<WorldSocketMgr>
|
||||
{
|
||||
friend class MaNGOS::Singleton<WorldSocketMgr>;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Bind and start accepting world connections.
|
||||
*
|
||||
* @param port TCP port to listen on.
|
||||
* @param bindIp Interface to bind to; empty (or "0.0.0.0") listens on all.
|
||||
* @return 0 on success, -1 on failure.
|
||||
*/
|
||||
int StartNetwork(uint16_t port, const std::string& bindIp);
|
||||
|
||||
/// Stop accepting, and tear down every live connection.
|
||||
void StopNetwork();
|
||||
|
||||
private:
|
||||
|
||||
WorldSocketMgr();
|
||||
~WorldSocketMgr();
|
||||
|
||||
net::Server m_server;
|
||||
bool m_started;
|
||||
};
|
||||
|
||||
#define sWorldSocketMgr MaNGOS::Singleton<WorldSocketMgr>::Instance()
|
||||
|
||||
#endif
|
||||
/// @}
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
|
||||
#include "ObjectMgr.h" // for normalizePlayerName
|
||||
#include "ChannelMgr.h"
|
||||
#include "OpcodeTable.h"
|
||||
#include <string>
|
||||
|
||||
/**
|
||||
|
|
@ -52,7 +53,7 @@
|
|||
*/
|
||||
void WorldSession::HandleJoinChannelOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
|
||||
uint32 channel_id;
|
||||
uint8 unknown1, unknown2;
|
||||
|
|
@ -82,7 +83,7 @@ void WorldSession::HandleJoinChannelOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleLeaveChannelOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
uint32 unk;
|
||||
std::string channelname;
|
||||
|
|
@ -111,7 +112,7 @@ void WorldSession::HandleLeaveChannelOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelListOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -130,7 +131,7 @@ void WorldSession::HandleChannelListOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelPasswordOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname, pass;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -151,7 +152,7 @@ void WorldSession::HandleChannelPasswordOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelSetOwnerOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
|
||||
std::string channelname, newp;
|
||||
|
|
@ -178,7 +179,7 @@ void WorldSession::HandleChannelSetOwnerOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelOwnerOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -196,7 +197,7 @@ void WorldSession::HandleChannelOwnerOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelModeratorOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname, otp;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -222,7 +223,7 @@ void WorldSession::HandleChannelModeratorOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelUnmoderatorOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname, otp;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -248,7 +249,7 @@ void WorldSession::HandleChannelUnmoderatorOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelMuteOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname, otp;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -274,7 +275,7 @@ void WorldSession::HandleChannelMuteOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelUnmuteOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
|
||||
std::string channelname, otp;
|
||||
|
|
@ -301,7 +302,7 @@ void WorldSession::HandleChannelUnmuteOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelInviteOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname, otp;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -327,7 +328,7 @@ void WorldSession::HandleChannelInviteOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelKickOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname, otp;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -352,7 +353,7 @@ void WorldSession::HandleChannelKickOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelBanOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname, otp;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -378,7 +379,7 @@ void WorldSession::HandleChannelBanOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelUnbanOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
|
||||
std::string channelname, otp;
|
||||
|
|
@ -405,7 +406,7 @@ void WorldSession::HandleChannelUnbanOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelAnnouncementsOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -423,7 +424,7 @@ void WorldSession::HandleChannelAnnouncementsOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelModerateOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -441,7 +442,7 @@ void WorldSession::HandleChannelModerateOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleChannelDisplayListQueryOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -459,7 +460,7 @@ void WorldSession::HandleChannelDisplayListQueryOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleGetChannelMemberCountOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname;
|
||||
recvPacket >> channelname;
|
||||
|
|
@ -483,7 +484,7 @@ void WorldSession::HandleGetChannelMemberCountOpcode(WorldPacket& recvPacket)
|
|||
*/
|
||||
void WorldSession::HandleSetChannelWatchOpcode(WorldPacket& recvPacket)
|
||||
{
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", recvPacket.GetOpcodeName(), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
DEBUG_LOG("WORLD: Received opcode %s (%u, 0x%X)", LookupOpcodeName(recvPacket.GetOpcode()), recvPacket.GetOpcode(), recvPacket.GetOpcode());
|
||||
// recvPacket.hexlike();
|
||||
std::string channelname;
|
||||
recvPacket >> channelname;
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ class CharacterHandler
|
|||
// The bot's WorldSession is owned by the bot's Player object
|
||||
// The bot's WorldSession is deleted by PlayerbotMgr::LogoutPlayerBot
|
||||
uint32 botAccountId = lqh->GetAccountId();
|
||||
WorldSession *botSession = new WorldSession(botAccountId, NULL, SEC_PLAYER, 1,0, LOCALE_enUS);
|
||||
WorldSession *botSession = new WorldSession(botAccountId, nullptr, nullptr, SEC_PLAYER, 1, 0, LOCALE_enUS);
|
||||
botSession->m_Address = "bot";
|
||||
botSession->HandlePlayerLogin(lqh); // will delete lqh
|
||||
Player* bot = botSession->GetPlayer();
|
||||
|
|
@ -1216,4 +1216,3 @@ void WorldSession::HandleShowingCloakOpcode(WorldPacket & /*recv_data*/)
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
#include "Common.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "WorldSession.h"
|
||||
#include "Opcodes.h"
|
||||
#include "OpcodeTable.h"
|
||||
#include "Log.h"
|
||||
#include "Player.h"
|
||||
#include "MapManager.h"
|
||||
|
|
@ -436,7 +436,7 @@ void WorldSession::HandleMovementOpcodes(WorldPacket& recv_data)
|
|||
void WorldSession::HandleForceSpeedChangeAckOpcodes(WorldPacket& recv_data)
|
||||
{
|
||||
uint16 opcode = recv_data.GetOpcode();
|
||||
DEBUG_LOG("WORLD: Received %s (%u, 0x%X) opcode", recv_data.GetOpcodeName(), opcode, opcode);
|
||||
DEBUG_LOG("WORLD: Received %s (%u, 0x%X) opcode", LookupOpcodeName(recv_data.GetOpcode()), opcode, opcode);
|
||||
|
||||
/* extract packet */
|
||||
ObjectGuid guid;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@
|
|||
#include "Platform/Define.h"
|
||||
#include "SystemConfig.h"
|
||||
#include "Log.h"
|
||||
#include "Opcodes.h"
|
||||
#include "OpcodeTable.h"
|
||||
#include "WorldSession.h"
|
||||
#include "WorldPacket.h"
|
||||
#include "Player.h"
|
||||
|
|
@ -299,7 +299,7 @@ World::AddSession_(WorldSession* s)
|
|||
{
|
||||
MANGOS_ASSERT(s);
|
||||
|
||||
// NOTE - Still there is race condition in WorldSession* being used in the Sockets
|
||||
// New sessions arrive through a locked queue and become world-thread-owned here.
|
||||
|
||||
///- kick already loaded player with same account (if any) and remove session
|
||||
///- if player is in loading and want to load again, return
|
||||
|
|
@ -346,6 +346,7 @@ World::AddSession_(WorldSession* s)
|
|||
if (pLimit > 0 && Sessions >= pLimit && s->GetSecurity() == SEC_PLAYER)
|
||||
{
|
||||
AddQueuedSession(s);
|
||||
s->SendPendingAddonInfo();
|
||||
UpdateMaxSessionCounters();
|
||||
DETAIL_LOG("PlayerQueue: Account id %u is in Queue Position (%u).", s->GetAccountId(), ++QueueSize);
|
||||
return;
|
||||
|
|
@ -358,6 +359,7 @@ World::AddSession_(WorldSession* s)
|
|||
packet << uint32(0); // BillingTimeRested
|
||||
packet << uint8(s->Expansion()); // 0 - normal, 1 - TBC. Must be set in database manually for each account.
|
||||
s->SendPacket(&packet);
|
||||
s->SendPendingAddonInfo();
|
||||
|
||||
UpdateMaxSessionCounters();
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ class WorldSession;
|
|||
class Player;
|
||||
class SqlResultQueue;
|
||||
class QueryResult;
|
||||
class WorldSocket;
|
||||
|
||||
// ServerMessages.dbc
|
||||
enum ServerMessageType
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
#include "Timer.h"
|
||||
#include "Util.h"
|
||||
#include "World.h"
|
||||
#include "WorldSocketMgr.h"
|
||||
#include "WorldNetwork.h"
|
||||
|
||||
#ifdef ENABLE_SOAP
|
||||
#include "SOAP/SoapThread.h"
|
||||
|
|
@ -283,7 +283,7 @@ void Master::WorldLoop()
|
|||
sLog.outString("[shutdown] final UpdateSessions done");
|
||||
|
||||
sLog.outString("[shutdown] StopNetwork: closing listener + joining network threads...");
|
||||
sWorldSocketMgr.StopNetwork();
|
||||
sWorldNetwork.Stop();
|
||||
sLog.outString("[shutdown] StopNetwork done");
|
||||
|
||||
sLog.outString("[shutdown] UnloadAll: unloading maps + MapUpdater teardown...");
|
||||
|
|
@ -689,7 +689,7 @@ int Master::Run()
|
|||
const std::string bindIp = sConfig.GetStringDefault("BindIP", "0.0.0.0");
|
||||
const uint16 worldPort = uint16(sWorld.getConfig(CONFIG_UINT32_PORT_WORLD));
|
||||
|
||||
if (sWorldSocketMgr.StartNetwork(worldPort, bindIp) == -1)
|
||||
if (!sWorldNetwork.Start(worldPort, bindIp))
|
||||
{
|
||||
sLog.outError("Failed to start network");
|
||||
World::StopNow(ERROR_EXIT_CODE);
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ int main(int argc, char** argv)
|
|||
if (!providerManager.IsInitialized())
|
||||
{
|
||||
Log::WaitBeforeContinueIfNeed();
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
///- Set progress bars show mode
|
||||
|
|
|
|||
|
|
@ -651,7 +651,7 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/aiplayerbot.conf.dist DESTINATION ${CO
|
|||
# This used to read ADD_CXX_PCH(Bots ${bots_PCH}) -- and bots_PCH was never set anywhere, so it
|
||||
# collapsed to ADD_CXX_PCH(Bots) and precompiled nothing at all, silently. Meanwhile 275 of
|
||||
# these 277 sources open botpch.h by hand on their first line, so every one of them was parsing
|
||||
# WorldSocket.h, ObjectMgr.h and playerbot.h from scratch. Name the header.
|
||||
# ObjectMgr.h and playerbot.h from scratch. Name the header.
|
||||
if(PCH)
|
||||
ADD_CXX_PCH(Bots botpch.h)
|
||||
endif()
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
//add here most rarely modified headers to speed up debug build compilation
|
||||
#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it
|
||||
#include "Common.h"
|
||||
|
||||
#include "MapManager.h"
|
||||
|
|
@ -7,10 +6,10 @@
|
|||
#include "ObjectAccessor.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "SQLStorages.h"
|
||||
#include "Opcodes.h"
|
||||
#include "OpcodeTable.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
|
||||
#include "playerbot.h"
|
||||
#include "playerbot.h"
|
||||
|
|
|
|||
|
|
@ -139,9 +139,9 @@ bool RandomPlayerbotFactory::CreateRandomBot(uint8 cls)
|
|||
uint8 outfitId = 0;
|
||||
|
||||
#if !defined(CLASSIC)
|
||||
WorldSession* session = new WorldSession(accountId, NULL, SEC_PLAYER, MAX_EXPANSION, 0, LOCALE_enUS);
|
||||
WorldSession* session = new WorldSession(accountId, nullptr, nullptr, SEC_PLAYER, MAX_EXPANSION, 0, LOCALE_enUS);
|
||||
#else
|
||||
WorldSession* session = new WorldSession(accountId, NULL, SEC_PLAYER, 0, LOCALE_enUS);
|
||||
WorldSession* session = new WorldSession(accountId, nullptr, nullptr, SEC_PLAYER, 0, 0, LOCALE_enUS);
|
||||
#endif
|
||||
if (!session)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
//add here most rarely modified headers to speed up debug build compilation
|
||||
#include "WorldSocket.h" // must be first to make ACE happy with ACE includes in it
|
||||
#include "Common.h"
|
||||
|
||||
#include "MapManager.h"
|
||||
|
|
@ -7,10 +6,10 @@
|
|||
#include "ObjectAccessor.h"
|
||||
#include "ObjectGuid.h"
|
||||
#include "SQLStorages.h"
|
||||
#include "Opcodes.h"
|
||||
#include "OpcodeTable.h"
|
||||
#include "SharedDefines.h"
|
||||
#include "GuildMgr.h"
|
||||
#include "ObjectMgr.h"
|
||||
#include "ScriptMgr.h"
|
||||
|
||||
#include "immersive.h"
|
||||
#include "immersive.h"
|
||||
|
|
|
|||
7
src/proto/CMakeLists.txt
Normal file
7
src/proto/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
add_library(proto STATIC
|
||||
ClientConnection.cpp ClientConnection.h
|
||||
Listener.cpp Listener.h
|
||||
PacketCodec.cpp PacketCodec.h
|
||||
IClientLink.h IWorldGateway.h Opcodes.h)
|
||||
target_include_directories(proto PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
target_link_libraries(proto PUBLIC shared PRIVATE mangos_openssl_strict)
|
||||
235
src/proto/ClientConnection.cpp
Normal file
235
src/proto/ClientConnection.cpp
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
#include "ClientConnection.h"
|
||||
|
||||
#include "Auth/Sha1.h"
|
||||
#include "Opcodes.h"
|
||||
#include "Utilities/Util.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
namespace proto
|
||||
{
|
||||
ClientConnection::ClientConnection(IWorldGateway& gateway)
|
||||
: m_gateway(gateway), m_seed(rand32())
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ClientConnection::onConnect()
|
||||
{
|
||||
if (m_closed.load())
|
||||
return {};
|
||||
|
||||
try
|
||||
{
|
||||
WorldPacket challenge(SMSG_AUTH_CHALLENGE, 4);
|
||||
challenge << m_seed;
|
||||
m_gateway.TracePacket(challenge, false);
|
||||
return EncodePacket(challenge);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Close();
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> ClientConnection::onData(const uint8_t* data, std::size_t len)
|
||||
{
|
||||
if (m_closed.load())
|
||||
return {};
|
||||
try
|
||||
{
|
||||
if (!data && len != 0)
|
||||
{
|
||||
Close();
|
||||
return {};
|
||||
}
|
||||
|
||||
std::size_t offset = 0;
|
||||
std::vector<WorldPacket> packets;
|
||||
while (offset < len && !m_closed.load())
|
||||
{
|
||||
packets.clear();
|
||||
std::size_t consumed = 0;
|
||||
DecodeStatus const status = m_codec.FeedOne(data + offset, len - offset, consumed, packets);
|
||||
offset += consumed;
|
||||
|
||||
if (status == DecodeStatus::Malformed)
|
||||
{
|
||||
Close();
|
||||
break;
|
||||
}
|
||||
if (status == DecodeStatus::NeedMore)
|
||||
break;
|
||||
|
||||
WorldPacket& packet = packets.front();
|
||||
m_gateway.TracePacket(packet, true);
|
||||
if (!HandlePacket(packet))
|
||||
Close();
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ClientConnection::onClose()
|
||||
{
|
||||
m_closed.store(true);
|
||||
SessionId session = INVALID_SESSION_ID;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_sessionLock);
|
||||
session = m_session;
|
||||
m_session = INVALID_SESSION_ID;
|
||||
}
|
||||
if (session != INVALID_SESSION_ID)
|
||||
{
|
||||
try
|
||||
{
|
||||
m_gateway.Detach(session);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ClientConnection::SendPacket(const WorldPacket& packet)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_cryptSendLock);
|
||||
if (m_closed.load() || !m_sender)
|
||||
return;
|
||||
|
||||
m_gateway.TracePacket(packet, false);
|
||||
std::vector<uint8> const frame = PacketCodec::Encode(packet,
|
||||
[this](uint8* header, std::size_t len) { m_crypt.EncryptSend(header, len); });
|
||||
m_sender(frame.data(), frame.size());
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
void ClientConnection::Close()
|
||||
{
|
||||
if (m_closed.exchange(true))
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (m_closer)
|
||||
m_closer();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
bool ClientConnection::HandlePacket(WorldPacket& packet)
|
||||
{
|
||||
if (packet.GetOpcode() == CMSG_AUTH_SESSION)
|
||||
return HandleAuthSession(packet);
|
||||
if (packet.GetOpcode() >= NUM_MSG_TYPES)
|
||||
return false;
|
||||
SessionId const session = CurrentSession();
|
||||
if (session == INVALID_SESSION_ID)
|
||||
return false;
|
||||
|
||||
m_gateway.Deliver(session, std::move(packet));
|
||||
return true;
|
||||
}
|
||||
|
||||
SessionId ClientConnection::CurrentSession()
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_sessionLock);
|
||||
return m_session;
|
||||
}
|
||||
|
||||
bool ClientConnection::HandleAuthSession(WorldPacket& packet)
|
||||
{
|
||||
if (m_authStarted)
|
||||
return false;
|
||||
if (!m_gateway.FilterAuthPacket(packet))
|
||||
return true;
|
||||
|
||||
m_authStarted = true;
|
||||
AuthRequest request;
|
||||
packet >> request.build;
|
||||
packet >> request.unknown;
|
||||
packet >> request.account;
|
||||
packet >> request.clientSeed;
|
||||
packet.read(request.digest, sizeof(request.digest));
|
||||
request.addonData.assign(packet.contents() + packet.rpos(),
|
||||
packet.contents() + packet.size());
|
||||
request.peerAddress = m_address;
|
||||
|
||||
AuthLookup lookup = m_gateway.LookupAccount(request);
|
||||
if (lookup.status != AuthStatus::Ok)
|
||||
{
|
||||
SendAuthResponse(lookup.status);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8 const zero[4] = {0, 0, 0, 0};
|
||||
Sha1Hash sha;
|
||||
sha.UpdateData(request.account);
|
||||
sha.UpdateData(zero, sizeof(zero));
|
||||
sha.UpdateData(reinterpret_cast<const uint8*>(&request.clientSeed), sizeof(request.clientSeed));
|
||||
sha.UpdateData(reinterpret_cast<const uint8*>(&m_seed), sizeof(m_seed));
|
||||
sha.UpdateBigNumbers(&lookup.sessionKey, nullptr);
|
||||
sha.Finalize();
|
||||
|
||||
if (std::memcmp(sha.GetDigest(), request.digest, sizeof(request.digest)) != 0)
|
||||
{
|
||||
SendAuthResponse(AuthStatus::Failed);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_crypt.Init(&lookup.sessionKey);
|
||||
m_codec.SetHeaderDecryptor(
|
||||
[this](uint8* header, std::size_t len) { m_crypt.DecryptRecv(header, len); });
|
||||
|
||||
std::shared_ptr<ClientConnection> const self =
|
||||
std::static_pointer_cast<ClientConnection>(shared_from_this());
|
||||
std::shared_ptr<IClientLink> const link = self;
|
||||
SessionId const session = m_gateway.Attach(request, link, lookup.context);
|
||||
if (session == INVALID_SESSION_ID)
|
||||
{
|
||||
SendAuthResponse(AuthStatus::SystemError);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool closedDuringAttach = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_sessionLock);
|
||||
closedDuringAttach = m_closed.load();
|
||||
if (!closedDuringAttach)
|
||||
m_session = session;
|
||||
}
|
||||
if (closedDuringAttach)
|
||||
{
|
||||
m_gateway.Detach(session);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientConnection::SendAuthResponse(AuthStatus status)
|
||||
{
|
||||
WorldPacket response(SMSG_AUTH_RESPONSE, 1);
|
||||
response << uint8(status);
|
||||
SendPacket(response);
|
||||
}
|
||||
|
||||
std::vector<uint8> ClientConnection::EncodePacket(const WorldPacket& packet)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(m_cryptSendLock);
|
||||
return PacketCodec::Encode(packet,
|
||||
[this](uint8* header, std::size_t len) { m_crypt.EncryptSend(header, len); });
|
||||
}
|
||||
}
|
||||
57
src/proto/ClientConnection.h
Normal file
57
src/proto/ClientConnection.h
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#ifndef MANGOS_PROTO_CLIENTCONNECTION_H
|
||||
#define MANGOS_PROTO_CLIENTCONNECTION_H
|
||||
|
||||
#include "Auth/AuthCrypt.h"
|
||||
#include "IClientLink.h"
|
||||
#include "IWorldGateway.h"
|
||||
#include "PacketCodec.h"
|
||||
#include "net/ISession.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace proto
|
||||
{
|
||||
class ClientConnection final : public net::ISession, public IClientLink
|
||||
{
|
||||
public:
|
||||
explicit ClientConnection(IWorldGateway& gateway);
|
||||
|
||||
void setPeerAddress(const std::string& address) override { m_address = address; }
|
||||
void setSender(net::Sender sender) override { m_sender = std::move(sender); }
|
||||
void setCloser(net::Closer closer) override { m_closer = std::move(closer); }
|
||||
std::vector<uint8_t> onConnect() override;
|
||||
std::vector<uint8_t> onData(const uint8_t* data, std::size_t len) override;
|
||||
void onClose() override;
|
||||
bool closed() const override { return m_closed.load(); }
|
||||
|
||||
void SendPacket(const WorldPacket& packet) override;
|
||||
void Close() override;
|
||||
const std::string& GetRemoteAddress() const override { return m_address; }
|
||||
bool IsClosed() const override { return m_closed.load(); }
|
||||
|
||||
private:
|
||||
bool HandlePacket(WorldPacket& packet);
|
||||
bool HandleAuthSession(WorldPacket& packet);
|
||||
SessionId CurrentSession();
|
||||
void SendAuthResponse(AuthStatus status);
|
||||
std::vector<uint8> EncodePacket(const WorldPacket& packet);
|
||||
|
||||
IWorldGateway& m_gateway;
|
||||
std::string m_address;
|
||||
PacketCodec m_codec;
|
||||
AuthCrypt m_crypt;
|
||||
std::mutex m_cryptSendLock;
|
||||
std::mutex m_sessionLock;
|
||||
uint32 m_seed;
|
||||
SessionId m_session = INVALID_SESSION_ID;
|
||||
bool m_authStarted = false;
|
||||
std::atomic<bool> m_closed{false};
|
||||
net::Sender m_sender;
|
||||
net::Closer m_closer;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
22
src/proto/IClientLink.h
Normal file
22
src/proto/IClientLink.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef MANGOS_PROTO_ICLIENTLINK_H
|
||||
#define MANGOS_PROTO_ICLIENTLINK_H
|
||||
|
||||
#include <string>
|
||||
|
||||
class WorldPacket;
|
||||
|
||||
namespace proto
|
||||
{
|
||||
class IClientLink
|
||||
{
|
||||
public:
|
||||
virtual ~IClientLink() = default;
|
||||
|
||||
virtual void SendPacket(const WorldPacket& packet) = 0;
|
||||
virtual void Close() = 0;
|
||||
virtual const std::string& GetRemoteAddress() const = 0;
|
||||
virtual bool IsClosed() const = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
85
src/proto/IWorldGateway.h
Normal file
85
src/proto/IWorldGateway.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef MANGOS_PROTO_IWORLDGATEWAY_H
|
||||
#define MANGOS_PROTO_IWORLDGATEWAY_H
|
||||
|
||||
#include "Auth/BigNumber.h"
|
||||
#include "Platform/Define.h"
|
||||
#include "Utilities/WorldPacket.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace proto
|
||||
{
|
||||
using SessionId = uint32;
|
||||
constexpr SessionId INVALID_SESSION_ID = 0;
|
||||
|
||||
enum class AuthStatus : uint8
|
||||
{
|
||||
Ok = 0x0C,
|
||||
Failed = 0x0D,
|
||||
Reject = 0x0E,
|
||||
BadServerProof = 0x0F,
|
||||
Unavailable = 0x10,
|
||||
SystemError = 0x11,
|
||||
BillingError = 0x12,
|
||||
BillingExpired = 0x13,
|
||||
VersionMismatch = 0x14,
|
||||
UnknownAccount = 0x15,
|
||||
IncorrectPassword = 0x16,
|
||||
SessionExpired = 0x17,
|
||||
ServerShuttingDown = 0x18,
|
||||
AlreadyLoggingIn = 0x19,
|
||||
LoginServerNotFound = 0x1A,
|
||||
WaitQueue = 0x1B,
|
||||
Banned = 0x1C,
|
||||
AlreadyOnline = 0x1D,
|
||||
NoTime = 0x1E,
|
||||
DatabaseBusy = 0x1F,
|
||||
Suspended = 0x20,
|
||||
ParentalControl = 0x21,
|
||||
LockedEnforced = 0x22
|
||||
};
|
||||
|
||||
struct AuthRequest
|
||||
{
|
||||
uint32 build = 0;
|
||||
uint32 unknown = 0;
|
||||
std::string account;
|
||||
uint32 clientSeed = 0;
|
||||
uint8 digest[20]{};
|
||||
std::vector<uint8> addonData;
|
||||
std::string peerAddress;
|
||||
};
|
||||
|
||||
struct AuthContext
|
||||
{
|
||||
virtual ~AuthContext() = default;
|
||||
};
|
||||
|
||||
struct AuthLookup
|
||||
{
|
||||
AuthStatus status = AuthStatus::UnknownAccount;
|
||||
BigNumber sessionKey;
|
||||
std::shared_ptr<AuthContext> context;
|
||||
};
|
||||
|
||||
class IClientLink;
|
||||
|
||||
class IWorldGateway
|
||||
{
|
||||
public:
|
||||
virtual ~IWorldGateway() = default;
|
||||
|
||||
virtual bool FilterAuthPacket(WorldPacket& packet) = 0;
|
||||
virtual void TracePacket(const WorldPacket& packet, bool incoming) = 0;
|
||||
virtual AuthLookup LookupAccount(const AuthRequest& request) = 0;
|
||||
virtual SessionId Attach(const AuthRequest& request,
|
||||
const std::shared_ptr<IClientLink>& link,
|
||||
const std::shared_ptr<AuthContext>& context) = 0;
|
||||
virtual void Deliver(SessionId session, WorldPacket&& packet) = 0;
|
||||
virtual void Detach(SessionId session) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
27
src/proto/Listener.cpp
Normal file
27
src/proto/Listener.cpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#include "Listener.h"
|
||||
|
||||
#include "ClientConnection.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace proto
|
||||
{
|
||||
Listener::Listener(IWorldGateway& gateway)
|
||||
: m_gateway(gateway)
|
||||
{
|
||||
}
|
||||
|
||||
bool Listener::Start(uint16 port, const std::string& bindIp)
|
||||
{
|
||||
return m_server.start(port,
|
||||
[this]() -> std::shared_ptr<net::ISession>
|
||||
{
|
||||
return std::make_shared<ClientConnection>(m_gateway);
|
||||
}, bindIp);
|
||||
}
|
||||
|
||||
void Listener::Stop()
|
||||
{
|
||||
m_server.stop();
|
||||
}
|
||||
}
|
||||
25
src/proto/Listener.h
Normal file
25
src/proto/Listener.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#ifndef MANGOS_PROTO_LISTENER_H
|
||||
#define MANGOS_PROTO_LISTENER_H
|
||||
|
||||
#include "IWorldGateway.h"
|
||||
#include "net/Server.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace proto
|
||||
{
|
||||
class Listener
|
||||
{
|
||||
public:
|
||||
explicit Listener(IWorldGateway& gateway);
|
||||
|
||||
bool Start(uint16 port, const std::string& bindIp);
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
IWorldGateway& m_gateway;
|
||||
net::Server m_server;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -26,17 +26,10 @@
|
|||
/// @{
|
||||
/// \file
|
||||
|
||||
#ifndef MANGOS_H_OPCODES
|
||||
#define MANGOS_H_OPCODES
|
||||
#ifndef MANGOS_PROTO_OPCODES_H
|
||||
#define MANGOS_PROTO_OPCODES_H
|
||||
|
||||
#include "Common.h"
|
||||
#include "Policies/Singleton.h"
|
||||
|
||||
// Note: this include need for be sure have full definition of class WorldSession
|
||||
// if this class definition not complite then VS for x64 release use different size for
|
||||
// struct OpcodeHandler in this header and Opcode.cpp and get totally wrong data from
|
||||
// table opcodeTable in source when Opcode.h included but WorldSession.h not included
|
||||
#include "WorldSession.h"
|
||||
#include "Platform/Define.h"
|
||||
|
||||
/**
|
||||
* This is a list of Opcodes that are known for the client/server communication, it is used
|
||||
|
|
@ -1114,71 +1107,8 @@ enum OpcodesList
|
|||
SMSG_SUMMON_CANCEL = 0x423
|
||||
};
|
||||
|
||||
// Don't forget to change this value and add opcode name to Opcodes.cpp when you add new opcode!
|
||||
// Don't forget to change this value when you add a new opcode.
|
||||
#define NUM_MSG_TYPES 0x424
|
||||
|
||||
/**
|
||||
* Initializes opcode handler metadata tables.
|
||||
*/
|
||||
extern void InitializeOpcodes();
|
||||
|
||||
/// Player state
|
||||
enum SessionStatus
|
||||
{
|
||||
STATUS_AUTHED = 0, ///< Player authenticated (_player==NULL, m_playerRecentlyLogout = false or will be reset before handler call)
|
||||
STATUS_LOGGEDIN, ///< Player in game (_player!=NULL, inWorld())
|
||||
STATUS_TRANSFER, ///< Player transferring to another map (_player!=NULL, !inWorld())
|
||||
STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT, ///< _player!= NULL or _player==NULL && m_playerRecentlyLogout)
|
||||
STATUS_NEVER, ///< Opcode not accepted from client (deprecated or server side only)
|
||||
STATUS_UNHANDLED ///< We don' handle this opcode yet
|
||||
};
|
||||
|
||||
/**
|
||||
* This determines how a \ref WorldPacket is handled by MaNGOS. This can be either in the
|
||||
* same function as we received it in, this is unusual, or it can be in:
|
||||
* - \ref World::UpdateSessions if it's not thread safe
|
||||
* - \ref Map::Update if it is thread safe
|
||||
*/
|
||||
enum PacketProcessing
|
||||
{
|
||||
PROCESS_INPLACE = 0, ///< process packet whenever we receive it - mostly for non-handled or non-implemented packets
|
||||
PROCESS_THREADUNSAFE, ///< packet is not thread-safe - process it in \ref World::UpdateSessions
|
||||
PROCESS_THREADSAFE ///< packet is thread-safe - process it in \ref Map::Update
|
||||
};
|
||||
|
||||
class WorldPacket;
|
||||
|
||||
/**
|
||||
* A structure containing some of the necessary info to handle a \ref WorldPacket when it comes in.
|
||||
* The most interesting thing in here is the \ref OpcodeHandler::handler that actually does
|
||||
* something with one of the opcodes (see \ref Opcodes) that came in.
|
||||
*/
|
||||
struct OpcodeHandler
|
||||
{
|
||||
///A string representation of the name of this opcode (see \ref Opcodes)
|
||||
char const* name;
|
||||
///The status for this handler, it tells whether or not we will handle the packet at all and
|
||||
///when we will handle it.
|
||||
SessionStatus status;
|
||||
///This tells where the packet should be processed, ie: is it thread un/safe, which in turn
|
||||
///determines where it will be processed
|
||||
PacketProcessing packetProcessing;
|
||||
///The callback called for this opcode which will work some magic
|
||||
void (WorldSession::*handler)(WorldPacket& recvPacket);
|
||||
};
|
||||
|
||||
extern OpcodeHandler opcodeTable[NUM_MSG_TYPES];
|
||||
|
||||
/// Lookup opcode name for human understandable logging
|
||||
inline const char* LookupOpcodeName(uint16 id)
|
||||
{
|
||||
if (id >= NUM_MSG_TYPES)
|
||||
{
|
||||
return "Received unknown opcode, it's more than max!";
|
||||
}
|
||||
// An entry that was never registered has a NULL name; never hand that to a "%s"
|
||||
return opcodeTable[id].name ? opcodeTable[id].name : "UNKNOWN";
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
136
src/proto/PacketCodec.cpp
Normal file
136
src/proto/PacketCodec.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
#include "PacketCodec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace proto
|
||||
{
|
||||
PacketCodec::PacketCodec(HeaderDecryptor decryptor)
|
||||
: m_decryptor(std::move(decryptor))
|
||||
{
|
||||
}
|
||||
|
||||
DecodeStatus PacketCodec::Feed(const uint8* data, std::size_t len,
|
||||
std::vector<WorldPacket>& out)
|
||||
{
|
||||
if (!data && len != 0)
|
||||
{
|
||||
return DecodeStatus::Malformed;
|
||||
}
|
||||
|
||||
std::size_t offset = 0;
|
||||
bool producedPacket = false;
|
||||
|
||||
while (offset < len)
|
||||
{
|
||||
std::size_t consumed = 0;
|
||||
DecodeStatus const status = FeedOne(data + offset, len - offset, consumed, out);
|
||||
offset += consumed;
|
||||
|
||||
if (status == DecodeStatus::Malformed)
|
||||
return status;
|
||||
if (status == DecodeStatus::Ready)
|
||||
producedPacket = true;
|
||||
if (consumed == 0 || status == DecodeStatus::NeedMore)
|
||||
break;
|
||||
}
|
||||
|
||||
return producedPacket ? DecodeStatus::Ready : DecodeStatus::NeedMore;
|
||||
}
|
||||
|
||||
DecodeStatus PacketCodec::FeedOne(const uint8* data, std::size_t len,
|
||||
std::size_t& consumed, std::vector<WorldPacket>& out)
|
||||
{
|
||||
consumed = 0;
|
||||
if (!data && len != 0)
|
||||
return DecodeStatus::Malformed;
|
||||
|
||||
while (consumed < len)
|
||||
{
|
||||
if (!m_haveHeader)
|
||||
{
|
||||
std::size_t const wanted = CLIENT_HEADER_SIZE - m_headerFill;
|
||||
std::size_t const taken = std::min(wanted, len - consumed);
|
||||
std::memcpy(m_header + m_headerFill, data + consumed, taken);
|
||||
m_headerFill += taken;
|
||||
consumed += taken;
|
||||
|
||||
if (m_headerFill < CLIENT_HEADER_SIZE)
|
||||
return DecodeStatus::NeedMore;
|
||||
|
||||
if (m_decryptor)
|
||||
{
|
||||
m_decryptor(m_header, CLIENT_HEADER_SIZE);
|
||||
}
|
||||
|
||||
uint32 const wireSize = (uint32(m_header[0]) << 8) | uint32(m_header[1]);
|
||||
uint32 const opcode = uint32(m_header[2])
|
||||
| (uint32(m_header[3]) << 8)
|
||||
| (uint32(m_header[4]) << 16)
|
||||
| (uint32(m_header[5]) << 24);
|
||||
|
||||
if (wireSize < 4 || wireSize > MAX_CLIENT_PACKET_SIZE
|
||||
|| opcode > MAX_CLIENT_PACKET_SIZE)
|
||||
{
|
||||
return DecodeStatus::Malformed;
|
||||
}
|
||||
|
||||
m_opcode = uint16(opcode);
|
||||
m_payloadNeeded = wireSize - 4;
|
||||
m_haveHeader = true;
|
||||
m_payload.clear();
|
||||
m_payload.reserve(m_payloadNeeded);
|
||||
}
|
||||
|
||||
if (m_payloadNeeded != 0)
|
||||
{
|
||||
std::size_t const taken = std::min<std::size_t>(m_payloadNeeded, len - consumed);
|
||||
m_payload.insert(m_payload.end(), data + consumed, data + consumed + taken);
|
||||
consumed += taken;
|
||||
m_payloadNeeded -= uint32(taken);
|
||||
|
||||
if (m_payloadNeeded != 0)
|
||||
return DecodeStatus::NeedMore;
|
||||
}
|
||||
|
||||
WorldPacket packet(m_opcode, m_payload.size());
|
||||
if (!m_payload.empty())
|
||||
{
|
||||
packet.append(m_payload.data(), m_payload.size());
|
||||
}
|
||||
out.push_back(packet);
|
||||
|
||||
m_haveHeader = false;
|
||||
m_headerFill = 0;
|
||||
m_payload.clear();
|
||||
return DecodeStatus::Ready;
|
||||
}
|
||||
|
||||
return DecodeStatus::NeedMore;
|
||||
}
|
||||
|
||||
std::vector<uint8> PacketCodec::Encode(const WorldPacket& packet,
|
||||
const HeaderEncryptor& encryptor)
|
||||
{
|
||||
uint16 const wireSize = uint16(packet.size() + 2);
|
||||
uint16 const opcode = packet.GetOpcode();
|
||||
uint8 header[SERVER_HEADER_SIZE] = {
|
||||
uint8(wireSize >> 8), uint8(wireSize),
|
||||
uint8(opcode), uint8(opcode >> 8)
|
||||
};
|
||||
|
||||
if (encryptor)
|
||||
{
|
||||
encryptor(header, SERVER_HEADER_SIZE);
|
||||
}
|
||||
|
||||
std::vector<uint8> wire;
|
||||
wire.reserve(SERVER_HEADER_SIZE + packet.size());
|
||||
wire.insert(wire.end(), header, header + SERVER_HEADER_SIZE);
|
||||
if (!packet.empty())
|
||||
{
|
||||
wire.insert(wire.end(), packet.contents(), packet.contents() + packet.size());
|
||||
}
|
||||
return wire;
|
||||
}
|
||||
}
|
||||
58
src/proto/PacketCodec.h
Normal file
58
src/proto/PacketCodec.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
#ifndef MANGOS_PROTO_PACKETCODEC_H
|
||||
#define MANGOS_PROTO_PACKETCODEC_H
|
||||
|
||||
#include "Platform/Define.h"
|
||||
#include "Utilities/WorldPacket.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace proto
|
||||
{
|
||||
constexpr std::size_t CLIENT_HEADER_SIZE = 6;
|
||||
constexpr std::size_t SERVER_HEADER_SIZE = 4;
|
||||
constexpr uint32 MAX_CLIENT_PACKET_SIZE = 10240;
|
||||
|
||||
enum class DecodeStatus
|
||||
{
|
||||
NeedMore,
|
||||
Ready,
|
||||
Malformed
|
||||
};
|
||||
|
||||
class PacketCodec
|
||||
{
|
||||
public:
|
||||
using HeaderDecryptor = std::function<void(uint8* header, std::size_t len)>;
|
||||
using HeaderEncryptor = std::function<void(uint8* header, std::size_t len)>;
|
||||
|
||||
explicit PacketCodec(HeaderDecryptor decryptor = HeaderDecryptor());
|
||||
|
||||
DecodeStatus Feed(const uint8* data, std::size_t len,
|
||||
std::vector<WorldPacket>& out);
|
||||
|
||||
DecodeStatus FeedOne(const uint8* data, std::size_t len,
|
||||
std::size_t& consumed, std::vector<WorldPacket>& out);
|
||||
|
||||
static std::vector<uint8> Encode(const WorldPacket& packet,
|
||||
const HeaderEncryptor& encryptor = HeaderEncryptor());
|
||||
|
||||
void SetHeaderDecryptor(HeaderDecryptor decryptor)
|
||||
{
|
||||
m_decryptor = std::move(decryptor);
|
||||
}
|
||||
|
||||
private:
|
||||
HeaderDecryptor m_decryptor;
|
||||
uint8 m_header[CLIENT_HEADER_SIZE]{};
|
||||
std::size_t m_headerFill = 0;
|
||||
bool m_haveHeader = false;
|
||||
uint16 m_opcode = 0;
|
||||
uint32 m_payloadNeeded = 0;
|
||||
std::vector<uint8> m_payload;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 6890fdc7f0c2c953fc776e23f3043988c0a5c720
|
||||
Subproject commit 39b78467d708263de9570fb9376fc9278912e01e
|
||||
|
|
@ -25,7 +25,6 @@
|
|||
#include "Auth/BigNumber.h"
|
||||
#include <openssl/bn.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
BigNumber::BigNumber()
|
||||
{
|
||||
|
|
@ -184,13 +183,9 @@ uint8* BigNumber::AsByteArray(int minSize)
|
|||
delete[] _array;
|
||||
_array = new uint8[length];
|
||||
|
||||
// If we need more bytes than length of BigNumber set the rest to 0
|
||||
if (length > GetNumBytes())
|
||||
{
|
||||
memset((void*)_array, 0, length);
|
||||
}
|
||||
|
||||
BN_bn2bin(_bn, (unsigned char*)_array);
|
||||
// Zero padding must be added as leading zeroes, not trailing ones -
|
||||
// BN_bn2binpad right-aligns the value in the buffer for us.
|
||||
BN_bn2binpad(_bn, (unsigned char*)_array, length);
|
||||
|
||||
std::reverse(_array, _array + length);
|
||||
|
||||
|
|
@ -208,13 +203,9 @@ uint8 *BigNumber::AsByteArray(int minSize, bool reverse)
|
|||
}
|
||||
_array = new uint8[length];
|
||||
|
||||
// If we need more bytes than length of BigNumber set the rest to 0
|
||||
if (length > GetNumBytes())
|
||||
{
|
||||
memset((void*)_array, 0, length);
|
||||
}
|
||||
|
||||
BN_bn2bin(_bn, (unsigned char *)_array);
|
||||
// Zero padding must be added as leading zeroes, not trailing ones -
|
||||
// BN_bn2binpad right-aligns the value in the buffer for us.
|
||||
BN_bn2binpad(_bn, (unsigned char *)_array, length);
|
||||
|
||||
if (reverse)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -29,8 +29,27 @@
|
|||
|
||||
#include "OpenSSLProvider.h"
|
||||
#include "Log/Log.h"
|
||||
#include <charconv>
|
||||
#include <cstdlib>
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/params.h>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
bool ParseProviderMajor(std::string const& version, unsigned& major)
|
||||
{
|
||||
std::size_t const separator = version.find('.');
|
||||
if (separator == std::string::npos || separator == 0)
|
||||
return false;
|
||||
|
||||
char const* const begin = version.data();
|
||||
char const* const end = begin + separator;
|
||||
std::from_chars_result const parsed = std::from_chars(begin, end, major);
|
||||
return parsed.ec == std::errc{} && parsed.ptr == end;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new OpenSSL cipher context wrapper.
|
||||
*/
|
||||
|
|
@ -134,19 +153,30 @@ OpenSSLProvider& OpenSSLProvider::operator=(OpenSSLProvider&& other) noexcept
|
|||
return *this;
|
||||
}
|
||||
|
||||
std::string OpenSSLProvider::Version() const
|
||||
{
|
||||
if (!m_provider)
|
||||
return {};
|
||||
|
||||
char* version = nullptr;
|
||||
OSSL_PARAM params[] = {
|
||||
OSSL_PARAM_construct_utf8_ptr(OSSL_PROV_PARAM_VERSION, &version, 0),
|
||||
OSSL_PARAM_construct_end()
|
||||
};
|
||||
|
||||
if (OSSL_PROVIDER_get_params(m_provider, params) != 1 || !version)
|
||||
return {};
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the OpenSSL provider manager and loads required providers.
|
||||
*/
|
||||
OpenSSLProviderManager::OpenSSLProviderManager()
|
||||
: m_legacyProvider("legacy"), m_defaultProvider("default"), m_initialized(false)
|
||||
{
|
||||
// Check if both providers loaded successfully
|
||||
if (m_legacyProvider.IsLoaded() && m_defaultProvider.IsLoaded())
|
||||
{
|
||||
m_initialized = true;
|
||||
sLog.outString("OpenSSL 3.x providers loaded successfully: legacy, default");
|
||||
}
|
||||
else
|
||||
if (!m_legacyProvider.IsLoaded() || !m_defaultProvider.IsLoaded())
|
||||
{
|
||||
sLog.outError("Failed to load OpenSSL 3.x providers");
|
||||
|
||||
|
|
@ -164,7 +194,45 @@ OpenSSLProviderManager::OpenSSLProviderManager()
|
|||
{
|
||||
sLog.outError(" - Default provider failed to load");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::string const legacyProviderVersion = m_legacyProvider.Version();
|
||||
std::string const defaultProviderVersion = m_defaultProvider.Version();
|
||||
unsigned legacyProviderMajor = 0;
|
||||
unsigned defaultProviderMajor = 0;
|
||||
bool const parsedLegacyProviderMajor =
|
||||
ParseProviderMajor(legacyProviderVersion, legacyProviderMajor);
|
||||
bool const parsedDefaultProviderMajor =
|
||||
ParseProviderMajor(defaultProviderVersion, defaultProviderMajor);
|
||||
|
||||
unsigned long const runtimeVersionNumber = OpenSSL_version_num();
|
||||
unsigned const runtimeMajor = static_cast<unsigned>((runtimeVersionNumber >> 28) & 0x0f);
|
||||
if (runtimeMajor != 3 ||
|
||||
!parsedLegacyProviderMajor || legacyProviderMajor != runtimeMajor ||
|
||||
!parsedDefaultProviderMajor || defaultProviderMajor != runtimeMajor)
|
||||
{
|
||||
char const* const modules = std::getenv("OPENSSL_MODULES");
|
||||
sLog.outError("OpenSSL 3.x provider/runtime validation failed: runtime='%s', legacy provider='%s', default provider='%s', OPENSSL_MODULES='%s'",
|
||||
OpenSSL_version(OPENSSL_VERSION),
|
||||
legacyProviderVersion.empty() ? "<unavailable>" : legacyProviderVersion.c_str(),
|
||||
defaultProviderVersion.empty() ? "<unavailable>" : defaultProviderVersion.c_str(),
|
||||
modules ? modules : "<unset>");
|
||||
return;
|
||||
}
|
||||
|
||||
EVP_CIPHER* const rc4 = EVP_CIPHER_fetch(nullptr, "RC4", nullptr);
|
||||
if (!rc4)
|
||||
{
|
||||
sLog.outError("OpenSSL legacy provider is loaded but RC4 is unavailable");
|
||||
return;
|
||||
}
|
||||
EVP_CIPHER_free(rc4);
|
||||
|
||||
m_initialized = true;
|
||||
sLog.outString("OpenSSL 3.x providers loaded successfully: legacy %s, default %s",
|
||||
legacyProviderVersion.c_str(), defaultProviderVersion.c_str());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -123,6 +123,12 @@ class OpenSSLProvider
|
|||
*/
|
||||
OSSL_PROVIDER* Get() const { return m_provider; }
|
||||
|
||||
/**
|
||||
* @brief Get the provider-reported version without taking ownership
|
||||
* @return Provider version string, or empty when unavailable
|
||||
*/
|
||||
std::string Version() const;
|
||||
|
||||
/**
|
||||
* @brief Move constructor
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ source_group("Auth" FILES ${SRC_GRP_AUTH})
|
|||
set(SRC_GRP_COMMON
|
||||
Common/Common.cpp
|
||||
Common/Common.h
|
||||
Common/Locales.h
|
||||
Common/ServerDefines.h
|
||||
Common/TimeConstants.h
|
||||
Common/GitRevision.cpp
|
||||
Common/GitRevision.h
|
||||
)
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@
|
|||
#include <signal.h>
|
||||
#include <assert.h>
|
||||
#include "ServerDefines.h"
|
||||
#include "Locales.h"
|
||||
#include "TimeConstants.h"
|
||||
|
||||
// Containers and utilities that used to reach translation units transitively
|
||||
// through the ACE headers. ACE is gone, so Common.h names them explicitly:
|
||||
|
|
@ -130,65 +132,6 @@ inline float finiteAlways(float f) { return std::isfinite(f) ? f : 0.0f; }
|
|||
#define PAIR32_HIPART(x) (uint16)((uint32(x) >> 16) & 0x0000FFFF)
|
||||
#define PAIR32_LOPART(x) (uint16)(uint32(x) & 0x0000FFFF)
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
enum TimeConstants
|
||||
{
|
||||
MINUTE = 60,
|
||||
HOUR = MINUTE * 60,
|
||||
DAY = HOUR * 24,
|
||||
WEEK = DAY * 7,
|
||||
MONTH = DAY * 30,
|
||||
YEAR = MONTH * 12,
|
||||
IN_MILLISECONDS = 1000
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
enum LocaleConstant
|
||||
{
|
||||
LOCALE_enUS = 0, // also enGB
|
||||
LOCALE_koKR = 1,
|
||||
LOCALE_frFR = 2,
|
||||
LOCALE_deDE = 3,
|
||||
LOCALE_zhCN = 4,
|
||||
LOCALE_zhTW = 5,
|
||||
LOCALE_esES = 6,
|
||||
LOCALE_esMX = 7,
|
||||
LOCALE_ruRU = 8
|
||||
};
|
||||
|
||||
#define MAX_LOCALE 9
|
||||
#define DEFAULT_LOCALE LOCALE_enUS
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
* @param name
|
||||
* @return LocaleConstant
|
||||
*/
|
||||
LocaleConstant GetLocaleByName(const std::string& name);
|
||||
|
||||
typedef std::vector<std::string> StringVector;
|
||||
|
||||
extern char const* localeNames[MAX_LOCALE]; /**< TODO */
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
struct LocaleNameStr
|
||||
{
|
||||
char const* name; /**< TODO */
|
||||
LocaleConstant locale; /**< TODO */
|
||||
};
|
||||
|
||||
extern LocaleNameStr const fullLocaleNameList[]; /**< used for iterate all names including alternative */
|
||||
|
||||
/**
|
||||
* @brief operator new[] based version of strdup() function! Release memory by using operator delete[] !
|
||||
*
|
||||
|
|
|
|||
50
src/shared/Common/Locales.h
Normal file
50
src/shared/Common/Locales.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* MaNGOS is a full featured server for World of Warcraft, supporting
|
||||
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
|
||||
*
|
||||
* Copyright (C) 2005-2025 MaNGOS <https://www.getmangos.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef MANGOS_LOCALES_H
|
||||
#define MANGOS_LOCALES_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/** Client locales, in the order the client itself numbers them. */
|
||||
enum LocaleConstant
|
||||
{
|
||||
LOCALE_enUS = 0, ///< also enGB
|
||||
LOCALE_koKR = 1,
|
||||
LOCALE_frFR = 2,
|
||||
LOCALE_deDE = 3,
|
||||
LOCALE_zhCN = 4,
|
||||
LOCALE_zhTW = 5,
|
||||
LOCALE_esES = 6,
|
||||
LOCALE_esMX = 7,
|
||||
LOCALE_ruRU = 8
|
||||
};
|
||||
|
||||
#define MAX_LOCALE 9
|
||||
#define DEFAULT_LOCALE LOCALE_enUS
|
||||
|
||||
extern char const* localeNames[MAX_LOCALE];
|
||||
|
||||
struct LocaleNameStr
|
||||
{
|
||||
char const* name;
|
||||
LocaleConstant locale;
|
||||
};
|
||||
|
||||
extern LocaleNameStr const fullLocaleNameList[];
|
||||
|
||||
LocaleConstant GetLocaleByName(const std::string& name);
|
||||
|
||||
typedef std::vector<std::string> StringVector;
|
||||
|
||||
#endif
|
||||
28
src/shared/Common/TimeConstants.h
Normal file
28
src/shared/Common/TimeConstants.h
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* MaNGOS is a full featured server for World of Warcraft, supporting
|
||||
* the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8
|
||||
*
|
||||
* Copyright (C) 2005-2025 MaNGOS <https://www.getmangos.eu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#ifndef MANGOS_TIMECONSTANTS_H
|
||||
#define MANGOS_TIMECONSTANTS_H
|
||||
|
||||
/** Durations in seconds, plus the milliseconds-per-second factor. */
|
||||
enum TimeConstants
|
||||
{
|
||||
MINUTE = 60,
|
||||
HOUR = MINUTE * 60,
|
||||
DAY = HOUR * 24,
|
||||
WEEK = DAY * 7,
|
||||
MONTH = DAY * 30,
|
||||
YEAR = MONTH * 12,
|
||||
IN_MILLISECONDS = 1000
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -266,7 +266,8 @@ void Database::escape_string(std::string& str)
|
|||
|
||||
char* buf = new char[str.size() * 2 + 1];
|
||||
// we don't care what connection to use - escape string will be the same
|
||||
m_pQueryConnections[0]->escape_string(buf, str.c_str(), str.size());
|
||||
SqlConnection::Lock guard(m_pQueryConnections[0]);
|
||||
guard->escape_string(buf, str.c_str(), str.size());
|
||||
str = buf;
|
||||
delete[] buf;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -697,4 +697,26 @@ class Database
|
|||
std::string m_logsDir; /**< TODO */
|
||||
uint32 m_pingIntervallms; /**< TODO */
|
||||
};
|
||||
|
||||
class DbThreadGuard
|
||||
{
|
||||
public:
|
||||
explicit DbThreadGuard(Database* database) : m_database(database)
|
||||
{
|
||||
if (m_database)
|
||||
m_database->ThreadStart();
|
||||
}
|
||||
|
||||
~DbThreadGuard()
|
||||
{
|
||||
if (m_database)
|
||||
m_database->ThreadEnd();
|
||||
}
|
||||
|
||||
DbThreadGuard(const DbThreadGuard&) = delete;
|
||||
DbThreadGuard& operator=(const DbThreadGuard&) = delete;
|
||||
|
||||
private:
|
||||
Database* m_database;
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@
|
|||
|
||||
#include "Common.h"
|
||||
#include "ByteBuffer.h"
|
||||
#include "Opcodes.h"
|
||||
|
||||
// Note: m_opcode and size stored in platfom dependent format
|
||||
// ignore endianess until send, and converted at receive
|
||||
|
|
@ -42,7 +41,7 @@ class WorldPacket : public ByteBuffer
|
|||
* @brief just container for later use
|
||||
*
|
||||
*/
|
||||
WorldPacket() : ByteBuffer(0), m_opcode(MSG_NULL_ACTION)
|
||||
WorldPacket() : ByteBuffer(0), m_opcode(0)
|
||||
{
|
||||
}
|
||||
/**
|
||||
|
|
@ -91,7 +90,6 @@ class WorldPacket : public ByteBuffer
|
|||
*
|
||||
* @return const char
|
||||
*/
|
||||
inline const char* GetOpcodeName() const { return LookupOpcodeName(m_opcode); }
|
||||
|
||||
protected:
|
||||
uint16 m_opcode; /**< TODO */
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
// COALESCING. A world tick emits a great many small packets — movement, chat, spell
|
||||
// updates — often to the same client. A queue-of-buffers would make that one heap
|
||||
// allocation and one syscall per packet, which is exactly the cost the ACE-era
|
||||
// WorldSocket built a 64 KB output buffer (and a 10 ms cork) to avoid. Here,
|
||||
// The former world protocol path built a 64 KB output buffer (and a 10 ms cork) to avoid. Here,
|
||||
// producers append into `m_pending` while the socket drains `m_inflight`; when the
|
||||
// in-flight span is fully written the two are swapped. So every packet queued during
|
||||
// one write completes in the *next* single write, and both vectors keep their
|
||||
|
|
|
|||
|
|
@ -42,18 +42,23 @@ namespace net {
|
|||
|
||||
void SendChannel::post(const uint8_t* data, size_t len) {
|
||||
std::lock_guard<std::mutex> lock(mu);
|
||||
if (ctx)
|
||||
if (ctx && !closeRequested)
|
||||
ctx->enqueue(data, len);
|
||||
}
|
||||
|
||||
void SendChannel::requestClose() {
|
||||
std::lock_guard<std::mutex> lock(mu);
|
||||
if (ctx)
|
||||
ctx->close(); // closing the socket makes pending I/O complete -> markDead
|
||||
if (!ctx || closeRequested)
|
||||
return;
|
||||
|
||||
closeRequested = true;
|
||||
if (!ctx->owner->postControl(ctx))
|
||||
ctx->close();
|
||||
}
|
||||
|
||||
void SendChannel::disarm() {
|
||||
std::lock_guard<std::mutex> lock(mu);
|
||||
closeRequested = true;
|
||||
ctx = nullptr;
|
||||
out.close(); // release any bulk producer parked on backpressure
|
||||
}
|
||||
|
|
@ -61,6 +66,10 @@ void SendChannel::disarm() {
|
|||
// ── ConnCtx ───────────────────────────────────────────────────────────────────
|
||||
|
||||
bool ConnCtx::postSend(const uint8_t* data, size_t len) {
|
||||
IocpServer* server = owner;
|
||||
if (!server || !server->m_operations.tryBegin())
|
||||
return false;
|
||||
|
||||
ZeroMemory(&sendOv.ov, sizeof(OVERLAPPED));
|
||||
// Safe to hand the kernel a pointer into the SendQueue's in-flight buffer: only
|
||||
// the pending buffer is ever appended to, so this storage cannot move or be
|
||||
|
|
@ -71,6 +80,7 @@ bool ConnCtx::postSend(const uint8_t* data, size_t len) {
|
|||
int rc = WSASend(sock, &sendOv.wsabuf, 1, nullptr, 0, &sendOv.ov, nullptr);
|
||||
if (rc == SOCKET_ERROR && WSAGetLastError() != WSA_IO_PENDING) {
|
||||
release(); // no completion will arrive for a synchronous failure
|
||||
server->m_operations.complete();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -118,6 +128,9 @@ IocpServer::~IocpServer() { stop(); }
|
|||
|
||||
bool IocpServer::start(uint16_t port, SessionFactory factory,
|
||||
const std::string& bindIp) {
|
||||
if (m_running.load() || !m_operations.startSubmissions())
|
||||
return false;
|
||||
|
||||
m_factory = std::move(factory);
|
||||
|
||||
// Own one Winsock reference for this listener's lifetime. realmd (and every
|
||||
|
|
@ -126,7 +139,7 @@ bool IocpServer::start(uint16_t port, SessionFactory factory,
|
|||
if (!m_wsaStarted) {
|
||||
WSADATA wsa{};
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
|
||||
sLog.outError("WorldSocket: WSAStartup failed");
|
||||
sLog.outError("World network: WSAStartup failed");
|
||||
return false;
|
||||
}
|
||||
m_wsaStarted = true;
|
||||
|
|
@ -184,35 +197,56 @@ bool IocpServer::start(uint16_t port, SessionFactory factory,
|
|||
for (int i = 0; i < PENDING_ACCEPTS; ++i)
|
||||
postAccept();
|
||||
|
||||
sLog.outString("WorldSocket: listening on %s:%u with %u worker threads (IOCP)",
|
||||
sLog.outString("World network: listening on %s:%u with %u worker threads (IOCP)",
|
||||
(bindIp.empty() ? "0.0.0.0" : bindIp.c_str()), (unsigned)port, (unsigned)nThreads);
|
||||
return true;
|
||||
}
|
||||
|
||||
void IocpServer::stop() {
|
||||
if (m_running.exchange(false)) {
|
||||
// Wake all workers
|
||||
for (size_t i = 0; i < m_workers.size(); ++i)
|
||||
PostQueuedCompletionStatus(m_iocp, 0, SHUTDOWN_KEY, nullptr);
|
||||
if (m_running.load()) {
|
||||
// Close the submission gate while workers are still alive. Every operation
|
||||
// accepted before this point is counted and must produce one completion.
|
||||
m_operations.stopSubmissions();
|
||||
|
||||
for (auto& t : m_workers) t.join();
|
||||
m_workers.clear();
|
||||
|
||||
// Close all still-live connections. The workers are joined, so no completion can
|
||||
// race us here; force-free regardless of refcount (any kernel-pending ops are
|
||||
// cancelled by the closesocket / CloseHandle below).
|
||||
{
|
||||
std::lock_guard lock(m_connsMu);
|
||||
for (auto* c : m_conns) {
|
||||
if (c->channel) c->channel->disarm();
|
||||
c->close();
|
||||
delete c;
|
||||
if (m_running.exchange(false)) {
|
||||
// Cancels the outstanding AcceptEx operations. Their completions are
|
||||
// drained by the workers just like ordinary accepts.
|
||||
if (m_listen != INVALID_SOCKET) {
|
||||
closesocket(m_listen);
|
||||
m_listen = INVALID_SOCKET;
|
||||
}
|
||||
m_conns.clear();
|
||||
}
|
||||
|
||||
if (m_listen != INVALID_SOCKET) { closesocket(m_listen); m_listen = INVALID_SOCKET; }
|
||||
if (m_iocp) { CloseHandle(m_iocp); m_iocp = nullptr; }
|
||||
// Hold a temporary reference while taking each connection through the
|
||||
// normal idempotent teardown. closesocket then completes pending I/O.
|
||||
std::vector<ConnCtx*> connections;
|
||||
{
|
||||
std::lock_guard lock(m_connsMu);
|
||||
connections.reserve(m_conns.size());
|
||||
for (auto* ctx : m_conns) {
|
||||
ctx->addRef();
|
||||
connections.push_back(ctx);
|
||||
}
|
||||
}
|
||||
for (auto* ctx : connections) {
|
||||
markDead(ctx);
|
||||
ctx->release();
|
||||
}
|
||||
|
||||
// Do not stop the workers or close the completion port until all kernel
|
||||
// operations have completed and released their ConnCtx references.
|
||||
m_operations.waitForZero();
|
||||
|
||||
for (size_t i = 0; i < m_workers.size(); ++i)
|
||||
PostQueuedCompletionStatus(m_iocp, 0, SHUTDOWN_KEY, nullptr);
|
||||
for (auto& t : m_workers)
|
||||
t.join();
|
||||
m_workers.clear();
|
||||
|
||||
if (m_iocp) {
|
||||
CloseHandle(m_iocp);
|
||||
m_iocp = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Balance the WSAStartup from start(). Guarded so a failed start (which may
|
||||
|
|
@ -221,19 +255,32 @@ void IocpServer::stop() {
|
|||
}
|
||||
|
||||
void IocpServer::postAccept() {
|
||||
if (!m_operations.tryBegin())
|
||||
return;
|
||||
|
||||
auto* aov = new AcceptOv{};
|
||||
aov->clientSock = WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP,
|
||||
nullptr, 0, WSA_FLAG_OVERLAPPED);
|
||||
if (aov->clientSock == INVALID_SOCKET) { delete aov; return; }
|
||||
if (aov->clientSock == INVALID_SOCKET) {
|
||||
delete aov;
|
||||
m_operations.complete();
|
||||
return;
|
||||
}
|
||||
|
||||
DWORD recvd = 0;
|
||||
m_fnAcceptEx(m_listen, aov->clientSock,
|
||||
aov->addrbuf, 0,
|
||||
sizeof(SOCKADDR_IN) + 16,
|
||||
sizeof(SOCKADDR_IN) + 16,
|
||||
&recvd, &aov->ov);
|
||||
// Errors here are normal (e.g. WSAEWOULDBLOCK = pending) — the completion
|
||||
// will arrive via IOCP when a client connects.
|
||||
BOOL accepted = m_fnAcceptEx(m_listen, aov->clientSock,
|
||||
aov->addrbuf, 0,
|
||||
sizeof(SOCKADDR_IN) + 16,
|
||||
sizeof(SOCKADDR_IN) + 16,
|
||||
&recvd, &aov->ov);
|
||||
if (!accepted && WSAGetLastError() != ERROR_IO_PENDING) {
|
||||
closesocket(aov->clientSock);
|
||||
delete aov;
|
||||
m_operations.complete();
|
||||
return;
|
||||
}
|
||||
// A successfully submitted accept completes through IOCP when a client
|
||||
// connects or when shutdown cancels the listener.
|
||||
}
|
||||
|
||||
void IocpServer::workerThread() {
|
||||
|
|
@ -248,6 +295,16 @@ void IocpServer::workerThread() {
|
|||
|
||||
if (!ov) continue; // timeout or spurious
|
||||
|
||||
// Control completions use the connection's dedicated OVERLAPPED. Identify
|
||||
// them by address before decoding the type tag used by kernel I/O operations.
|
||||
auto* ctx = key == 0 ? nullptr : reinterpret_cast<ConnCtx*>(key);
|
||||
if (ctx && ov == &ctx->closeOv) {
|
||||
handleControl(ctx);
|
||||
ctx->release();
|
||||
m_operations.complete();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine operation type from the IoType field embedded right after OVERLAPPED
|
||||
auto* base = reinterpret_cast<IoType*>(
|
||||
reinterpret_cast<char*>(ov) + sizeof(OVERLAPPED));
|
||||
|
|
@ -255,11 +312,12 @@ void IocpServer::workerThread() {
|
|||
|
||||
if (opType == IoType::Accept) {
|
||||
auto* aov = reinterpret_cast<AcceptOv*>(ov);
|
||||
if (ok)
|
||||
if (ok && m_running.load())
|
||||
handleAccept(aov, bytesXfr);
|
||||
else
|
||||
closesocket(aov->clientSock);
|
||||
delete aov;
|
||||
m_operations.complete();
|
||||
if (m_running) postAccept(); // always maintain PENDING_ACCEPTS
|
||||
continue;
|
||||
}
|
||||
|
|
@ -267,8 +325,6 @@ void IocpServer::workerThread() {
|
|||
// Data operation: key == ConnCtx*. Exactly one completion per posted op, so
|
||||
// we release() once here no matter which branch runs — that balances the
|
||||
// addRef() the post did and is what eventually frees the ctx.
|
||||
auto* ctx = reinterpret_cast<ConnCtx*>(key);
|
||||
|
||||
if (!ok || bytesXfr == 0)
|
||||
markDead(ctx); // closed or error: tear down (idempotent)
|
||||
else if (opType == IoType::Recv)
|
||||
|
|
@ -277,6 +333,7 @@ void IocpServer::workerThread() {
|
|||
handleSend(ctx, bytesXfr);
|
||||
|
||||
ctx->release();
|
||||
m_operations.complete();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +342,7 @@ void IocpServer::handleAccept(AcceptOv* aov, DWORD /*bytes*/) {
|
|||
setsockopt(aov->clientSock, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
|
||||
reinterpret_cast<char*>(&m_listen), sizeof(m_listen));
|
||||
|
||||
auto* ctx = new ConnCtx(m_factory);
|
||||
auto* ctx = new ConnCtx(m_factory, this);
|
||||
ctx->sock = aov->clientSock;
|
||||
|
||||
// Associate new socket with IOCP; key = ctx pointer
|
||||
|
|
@ -329,10 +386,8 @@ void IocpServer::handleAccept(AcceptOv* aov, DWORD /*bytes*/) {
|
|||
if (!greeting.empty())
|
||||
ctx->enqueue(greeting.data(), greeting.size());
|
||||
|
||||
if (ctx->session->closed() && ctx->channel->out.empty()) {
|
||||
markDead(ctx);
|
||||
if (closeIfDrained(ctx))
|
||||
return;
|
||||
}
|
||||
|
||||
// Post initial recv.
|
||||
if (!postRecv(ctx))
|
||||
|
|
@ -340,6 +395,9 @@ void IocpServer::handleAccept(AcceptOv* aov, DWORD /*bytes*/) {
|
|||
}
|
||||
|
||||
bool IocpServer::postRecv(ConnCtx* ctx) {
|
||||
if (!m_operations.tryBegin())
|
||||
return false;
|
||||
|
||||
ZeroMemory(&ctx->recvOv.ov, sizeof(OVERLAPPED));
|
||||
ctx->recvOv.wsabuf.buf = ctx->recvOv.buf;
|
||||
ctx->recvOv.wsabuf.len = sizeof(ctx->recvOv.buf);
|
||||
|
|
@ -349,12 +407,35 @@ bool IocpServer::postRecv(ConnCtx* ctx) {
|
|||
nullptr, &ctx->recvOv.flags, &ctx->recvOv.ov, nullptr) == SOCKET_ERROR) {
|
||||
if (WSAGetLastError() != WSA_IO_PENDING) {
|
||||
ctx->release(); // synchronous failure: no completion will arrive
|
||||
m_operations.complete();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IocpServer::postControl(ConnCtx* ctx) {
|
||||
bool expected = false;
|
||||
if (!ctx->controlPending.compare_exchange_strong(expected, true))
|
||||
return true;
|
||||
|
||||
if (!m_operations.tryBegin()) {
|
||||
ctx->controlPending.store(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
ZeroMemory(&ctx->closeOv, sizeof(OVERLAPPED));
|
||||
ctx->addRef();
|
||||
if (!PostQueuedCompletionStatus(m_iocp, 0,
|
||||
reinterpret_cast<ULONG_PTR>(ctx), &ctx->closeOv)) {
|
||||
ctx->controlPending.store(false);
|
||||
ctx->release();
|
||||
m_operations.complete();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void IocpServer::handleRecv(ConnCtx* ctx, DWORD bytes) {
|
||||
auto response = ctx->session->onData(
|
||||
reinterpret_cast<const uint8_t*>(ctx->recvOv.buf), bytes);
|
||||
|
|
@ -366,10 +447,8 @@ void IocpServer::handleRecv(ConnCtx* ctx, DWORD bytes) {
|
|||
// rejection, say) must still get them out, so only tear down once the outbound
|
||||
// buffer has actually drained. Otherwise keep a recv posted: it guarantees a
|
||||
// completion will arrive to carry the teardown even if the peer goes quiet.
|
||||
if (ctx->session->closed() && ctx->channel->out.empty()) {
|
||||
markDead(ctx);
|
||||
if (closeIfDrained(ctx))
|
||||
return;
|
||||
}
|
||||
|
||||
if (!postRecv(ctx))
|
||||
markDead(ctx);
|
||||
|
|
@ -380,8 +459,36 @@ void IocpServer::handleSend(ConnCtx* ctx, DWORD bytes) {
|
|||
// Only tear down once the session's remaining output has actually drained —
|
||||
// otherwise a session that closes right after queueing its last packet (e.g. an
|
||||
// auth rejection followed by a disconnect) loses those bytes.
|
||||
if (ctx->session->closed() && ctx->channel->out.empty())
|
||||
closeIfDrained(ctx);
|
||||
}
|
||||
|
||||
void IocpServer::handleControl(ConnCtx* ctx) {
|
||||
ctx->controlPending.store(false);
|
||||
closeIfDrained(ctx);
|
||||
}
|
||||
|
||||
bool IocpServer::closeIfDrained(ConnCtx* ctx) {
|
||||
bool const sessionClosed = ctx->session && ctx->session->closed();
|
||||
bool shouldClose = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ctx->channel->mu);
|
||||
if (sessionClosed)
|
||||
ctx->channel->closeRequested = true;
|
||||
shouldClose = ctx->channel->closeRequested && !ctx->channel->sendShutdown &&
|
||||
ctx->channel->out.empty();
|
||||
if (shouldClose)
|
||||
ctx->channel->sendShutdown = true;
|
||||
}
|
||||
|
||||
if (shouldClose)
|
||||
{
|
||||
// On Winsock, closing an overlapped socket directly can reset the peer even
|
||||
// after the final WSASend completion. Queue the FIN first, then perform the
|
||||
// normal idempotent teardown so clients receive all bytes followed by EOF.
|
||||
shutdown(ctx->sock, SD_SEND);
|
||||
markDead(ctx);
|
||||
}
|
||||
return shouldClose;
|
||||
}
|
||||
|
||||
void IocpServer::markDead(ConnCtx* ctx) {
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@
|
|||
#include "net/ISession.hpp"
|
||||
#include "net/SendQueue.hpp"
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
|
|
@ -51,6 +53,68 @@
|
|||
|
||||
namespace net {
|
||||
|
||||
class OutstandingOperations {
|
||||
public:
|
||||
bool startSubmissions()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (m_count != 0)
|
||||
return false;
|
||||
m_acceptingSubmissions = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool tryBegin()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
if (!m_acceptingSubmissions)
|
||||
return false;
|
||||
++m_count;
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopSubmissions()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_acceptingSubmissions = false;
|
||||
if (m_count == 0)
|
||||
m_zero.notify_all();
|
||||
}
|
||||
|
||||
void complete()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
assert(m_count != 0);
|
||||
--m_count;
|
||||
if (m_count == 0)
|
||||
m_zero.notify_all();
|
||||
}
|
||||
|
||||
void waitForZero()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
m_zero.wait(lock, [&] { return m_count == 0; });
|
||||
}
|
||||
|
||||
std::size_t count() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_count;
|
||||
}
|
||||
|
||||
bool acceptingSubmissions() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_acceptingSubmissions;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_zero;
|
||||
std::size_t m_count = 0;
|
||||
bool m_acceptingSubmissions = true;
|
||||
};
|
||||
|
||||
// ── Per-operation type tag ────────────────────────────────────────────────────
|
||||
enum class IoType : uint8_t { Accept, Recv, Send };
|
||||
|
||||
|
|
@ -82,6 +146,7 @@ struct SendOv {
|
|||
};
|
||||
|
||||
// ── Per-connection context ────────────────────────────────────────────────────
|
||||
class IocpServer;
|
||||
struct ConnCtx;
|
||||
|
||||
// Lifetime-safe handle the session uses to send from any thread (e.g. the world
|
||||
|
|
@ -96,6 +161,8 @@ struct ConnCtx;
|
|||
struct SendChannel {
|
||||
std::mutex mu;
|
||||
ConnCtx* ctx = nullptr;
|
||||
bool closeRequested = false;
|
||||
bool sendShutdown = false;
|
||||
SendQueue out; // coalescing buffer + byte backpressure
|
||||
|
||||
void post(const uint8_t* data, size_t len); // append + kick a write while armed
|
||||
|
|
@ -107,8 +174,10 @@ struct ConnCtx {
|
|||
SOCKET sock{INVALID_SOCKET};
|
||||
RecvOv recvOv;
|
||||
SendOv sendOv;
|
||||
OVERLAPPED closeOv{};
|
||||
std::shared_ptr<ISession> session;
|
||||
std::shared_ptr<SendChannel> channel;
|
||||
IocpServer* owner = nullptr;
|
||||
|
||||
// Lifetime: the ConnCtx must outlive every overlapped op posted on it, because
|
||||
// their completions arrive (keyed by this pointer) on an IOCP worker possibly
|
||||
|
|
@ -118,8 +187,10 @@ struct ConnCtx {
|
|||
// teardown idempotent across the recv/send/close paths that can all race to it.
|
||||
std::atomic<long> refs{1};
|
||||
std::atomic<bool> dead{false};
|
||||
std::atomic<bool> controlPending{false};
|
||||
|
||||
explicit ConnCtx(const SessionFactory& factory) : session(factory()) {}
|
||||
ConnCtx(const SessionFactory& factory, IocpServer* server)
|
||||
: session(factory()), owner(server) {}
|
||||
|
||||
void addRef() { refs.fetch_add(1, std::memory_order_relaxed); }
|
||||
void release() { if (refs.fetch_sub(1, std::memory_order_acq_rel) == 1) delete this; }
|
||||
|
|
@ -155,6 +226,9 @@ public:
|
|||
void stop();
|
||||
|
||||
private:
|
||||
friend struct SendChannel;
|
||||
friend struct ConnCtx;
|
||||
|
||||
HANDLE m_iocp{nullptr};
|
||||
SOCKET m_listen{INVALID_SOCKET};
|
||||
SessionFactory m_factory;
|
||||
|
|
@ -164,6 +238,7 @@ private:
|
|||
|
||||
std::vector<std::thread> m_workers;
|
||||
std::atomic<bool> m_running{false};
|
||||
OutstandingOperations m_operations;
|
||||
bool m_wsaStarted{false}; // owns one WSAStartup ref
|
||||
|
||||
static constexpr int PENDING_ACCEPTS = 4;
|
||||
|
|
@ -176,9 +251,12 @@ private:
|
|||
void workerThread();
|
||||
void postAccept();
|
||||
bool postRecv (ConnCtx* ctx); // refs++ on success
|
||||
bool postControl(ConnCtx* ctx); // refs++ on success
|
||||
void handleAccept(AcceptOv* aov, DWORD bytes);
|
||||
void handleRecv (ConnCtx* ctx, DWORD bytes);
|
||||
void handleSend (ConnCtx* ctx, DWORD bytes);// `bytes` MUST be honoured (short writes)
|
||||
void handleControl(ConnCtx* ctx);
|
||||
bool closeIfDrained(ConnCtx* ctx);
|
||||
void markDead (ConnCtx* ctx); // idempotent teardown; releases alive ref
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ bool ReactorServer::start(uint16_t port, SessionFactory factory,
|
|||
|
||||
m_acceptThread = std::thread([this] { acceptLoop(); });
|
||||
|
||||
sLog.outString("WorldSocket: listening on %s:%u with %u worker threads (%s)",
|
||||
sLog.outString("World network: listening on %s:%u with %u worker threads (%s)",
|
||||
(bindIp.empty() ? "0.0.0.0" : bindIp.c_str()), (unsigned)port,
|
||||
(unsigned)nWorkers, m_acceptPoller->name());
|
||||
return true;
|
||||
|
|
@ -149,6 +149,7 @@ void ReactorServer::stop() {
|
|||
for (auto& w : m_workers) {
|
||||
for (auto* c : w->incoming) {
|
||||
if (c->channel) c->channel->disarm(); // wake any parked producer
|
||||
if (c->session) c->session->onClose();
|
||||
::close(c->fd);
|
||||
delete c;
|
||||
}
|
||||
|
|
@ -283,6 +284,10 @@ void ReactorServer::drainIncoming(Worker& w) {
|
|||
}
|
||||
for (auto* conn : pending) {
|
||||
if (!w.poller->add(conn->fd, EvRead, conn)) {
|
||||
if (conn->channel)
|
||||
conn->channel->disarm();
|
||||
if (conn->session)
|
||||
conn->session->onClose();
|
||||
::close(conn->fd);
|
||||
delete conn;
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ bool UringServer::start(uint16_t port, SessionFactory factory,
|
|||
|
||||
m_acceptThread = std::thread([this] { acceptLoop(); });
|
||||
|
||||
sLog.outString("WorldSocket: listening on %s:%u with %u worker threads (io_uring)",
|
||||
sLog.outString("World network: listening on %s:%u with %u worker threads (io_uring)",
|
||||
(bindIp.empty() ? "0.0.0.0" : bindIp.c_str()), (unsigned)port, (unsigned)nWorkers);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -152,16 +152,28 @@ void UringServer::stop() {
|
|||
|
||||
void UringServer::acceptLoop() {
|
||||
while (true) {
|
||||
int cfd = ::accept4(m_listen, nullptr, nullptr, SOCK_CLOEXEC);
|
||||
sockaddr_in peer{};
|
||||
socklen_t peerLen = sizeof(peer);
|
||||
int cfd = ::accept4(m_listen, reinterpret_cast<sockaddr*>(&peer),
|
||||
&peerLen, SOCK_CLOEXEC);
|
||||
if (cfd < 0) {
|
||||
if (errno == EINTR) continue;
|
||||
break; // listen socket closed on shutdown
|
||||
}
|
||||
|
||||
char peerIp[INET_ADDRSTRLEN] = {};
|
||||
if (peer.sin_family != AF_INET ||
|
||||
!inet_ntop(AF_INET, &peer.sin_addr, peerIp, sizeof(peerIp))) {
|
||||
::close(cfd);
|
||||
continue;
|
||||
}
|
||||
|
||||
int one = 1;
|
||||
::setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
|
||||
|
||||
auto* conn = new UringConn(m_factory);
|
||||
conn->fd = cfd;
|
||||
conn->session->setPeerAddress(peerIp);
|
||||
|
||||
// Pick the owning worker up front so the channel can target its eventfd
|
||||
// before the session (in onConnect) registers with the world loop.
|
||||
|
|
|
|||
76
tests/AuthCryptoTests.cpp
Normal file
76
tests/AuthCryptoTests.cpp
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#include "TestSupport.hpp"
|
||||
|
||||
#include "Auth/ARC4.h"
|
||||
#include "Auth/BigNumber.h"
|
||||
#include "Auth/HMACSHA1.h"
|
||||
#include "Auth/OpenSSLProvider.h"
|
||||
#include "Auth/Sha1.h"
|
||||
|
||||
#include <array>
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
BigNumber number;
|
||||
number.SetHexStr("1234");
|
||||
CHECK_BYTES(number.AsByteArray(4, false), 4, {0x00, 0x00, 0x12, 0x34});
|
||||
CHECK_BYTES(number.AsByteArray(4, true), 4, {0x34, 0x12, 0x00, 0x00});
|
||||
CHECK_BYTES(number.AsByteArray(2, false), 2, {0x12, 0x34});
|
||||
CHECK_BYTES(number.AsByteArray(2, true), 2, {0x34, 0x12});
|
||||
|
||||
BigNumber zero;
|
||||
CHECK_BYTES(zero.AsByteArray(4, false), 4, {0x00, 0x00, 0x00, 0x00});
|
||||
CHECK_BYTES(zero.AsByteArray(4, true), 4, {0x00, 0x00, 0x00, 0x00});
|
||||
|
||||
Sha1Hash sha1;
|
||||
sha1.UpdateData(std::string("abc"));
|
||||
sha1.Finalize();
|
||||
CHECK_HEX(sha1.GetDigest(), sha1.GetLength(),
|
||||
"a9993e364706816aba3e25717850c26c9cd0d89d");
|
||||
|
||||
std::array<uint8, 20> hmacKey{};
|
||||
hmacKey.fill(0x0b);
|
||||
HMACSHA1 hmac(static_cast<uint32>(hmacKey.size()), hmacKey.data());
|
||||
hmac.UpdateData(std::string("Hi There"));
|
||||
hmac.Finalize();
|
||||
CHECK_HEX(hmac.GetDigest(), hmac.GetLength(),
|
||||
"b617318655057264e28bc0b6fb378c8ef146be00");
|
||||
|
||||
OpenSSLProviderManager providerManager;
|
||||
CHECK(providerManager.IsInitialized());
|
||||
|
||||
unsigned const runtimeMajor = static_cast<unsigned>((OpenSSL_version_num() >> 28) & 0x0f);
|
||||
CHECK(runtimeMajor == 3);
|
||||
auto checkProviderMajor = [runtimeMajor](OpenSSLProvider const& provider)
|
||||
{
|
||||
std::string const providerVersion = provider.Version();
|
||||
unsigned providerMajor = 0;
|
||||
CHECK(!providerVersion.empty());
|
||||
std::size_t const delimiter = providerVersion.find('.');
|
||||
CHECK(delimiter != std::string::npos);
|
||||
if (delimiter == std::string::npos)
|
||||
return;
|
||||
|
||||
char const* const providerBegin = providerVersion.data();
|
||||
char const* const providerEnd = providerBegin + delimiter;
|
||||
std::from_chars_result const parsed =
|
||||
std::from_chars(providerBegin, providerEnd, providerMajor);
|
||||
CHECK(parsed.ec == std::errc{});
|
||||
CHECK(parsed.ptr == providerEnd);
|
||||
CHECK(providerMajor == runtimeMajor);
|
||||
};
|
||||
checkProviderMajor(providerManager.GetLegacyProvider());
|
||||
checkProviderMajor(providerManager.GetDefaultProvider());
|
||||
|
||||
uint8 rc4Key[] = {'K', 'e', 'y'};
|
||||
uint8 rc4Data[] = {'P', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't'};
|
||||
ARC4 rc4(rc4Key, static_cast<uint8>(sizeof(rc4Key)));
|
||||
rc4.UpdateData(sizeof(rc4Data), rc4Data);
|
||||
CHECK_HEX(rc4Data, sizeof(rc4Data), "bbf316e8d940af0ad3");
|
||||
|
||||
return mangos::test::failures == 0 ? 0 : 1;
|
||||
}
|
||||
46
tests/CMakeLists.txt
Normal file
46
tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
add_executable(auth_crypto_tests AuthCryptoTests.cpp)
|
||||
target_link_libraries(auth_crypto_tests PRIVATE shared mangos_openssl_strict)
|
||||
add_test(NAME auth_crypto_tests COMMAND auth_crypto_tests)
|
||||
if(WIN32)
|
||||
find_file(MANGOS_TEST_OPENSSL_CRYPTO_DLL
|
||||
NAMES libcrypto-3-x64.dll libcrypto-3.dll
|
||||
HINTS "${OPENSSL_ROOT_DIR}/bin"
|
||||
NO_DEFAULT_PATH)
|
||||
if(NOT MANGOS_TEST_OPENSSL_CRYPTO_DLL)
|
||||
message(FATAL_ERROR "Could not find the OpenSSL 3.x crypto runtime DLL under ${OPENSSL_ROOT_DIR}/bin")
|
||||
endif()
|
||||
add_custom_command(TARGET auth_crypto_tests POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${MANGOS_TEST_OPENSSL_CRYPTO_DLL}" "$<TARGET_FILE_DIR:auth_crypto_tests>"
|
||||
COMMENT "auth_crypto_tests: co-locating the OpenSSL 3.x crypto runtime")
|
||||
endif()
|
||||
|
||||
add_executable(network_regression_tests NetworkRegressionTests.cpp)
|
||||
target_link_libraries(network_regression_tests PRIVATE shared Threads::Threads)
|
||||
add_test(NAME network_regression_tests COMMAND network_regression_tests)
|
||||
set_tests_properties(network_regression_tests PROPERTIES TIMEOUT 60)
|
||||
|
||||
add_test(NAME proto_boundary
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DPROTO_DIR=${PROJECT_SOURCE_DIR}/src/proto
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/CheckProtoBoundary.cmake)
|
||||
|
||||
add_test(NAME world_network_boundary
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_ROOT=${PROJECT_SOURCE_DIR}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/CheckWorldNetworkBoundary.cmake)
|
||||
|
||||
add_executable(protocol_tests ProtocolTests.cpp)
|
||||
target_link_libraries(protocol_tests PRIVATE proto shared Threads::Threads mangos_openssl_strict)
|
||||
add_test(NAME protocol_tests COMMAND protocol_tests)
|
||||
|
||||
add_executable(database_concurrency_tests DatabaseConcurrencyTests.cpp)
|
||||
target_link_libraries(database_concurrency_tests PRIVATE shared Threads::Threads)
|
||||
add_test(NAME database_concurrency_tests COMMAND database_concurrency_tests)
|
||||
|
||||
add_executable(session_mailbox_tests
|
||||
SessionMailboxTests.cpp
|
||||
${PROJECT_SOURCE_DIR}/src/game/Server/SessionMailbox.cpp)
|
||||
target_include_directories(session_mailbox_tests PRIVATE ${PROJECT_SOURCE_DIR}/src/game/Server)
|
||||
target_link_libraries(session_mailbox_tests PRIVATE shared Threads::Threads)
|
||||
add_test(NAME session_mailbox_tests COMMAND session_mailbox_tests)
|
||||
20
tests/CheckProtoBoundary.cmake
Normal file
20
tests/CheckProtoBoundary.cmake
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
if(NOT IS_DIRECTORY "${PROTO_DIR}")
|
||||
message(FATAL_ERROR "Protocol boundary missing: ${PROTO_DIR}")
|
||||
endif()
|
||||
|
||||
file(GLOB PROTO_SOURCES
|
||||
"${PROTO_DIR}/*.h" "${PROTO_DIR}/*.hpp"
|
||||
"${PROTO_DIR}/*.cpp" "${PROTO_DIR}/*.cc")
|
||||
|
||||
set(FORBIDDEN_PATTERNS
|
||||
"#[ \t]*include[ \t]*[\"<](Database/|World\\.h|WorldSession\\.h|AddonHandler\\.h|LuaEngine\\.h|Warden)"
|
||||
"(^|[^A-Za-z0-9_])(WorldSession|sWorld|LoginDatabase|CharacterDatabase|WorldDatabase|sAddOnHandler|LuaEngine|Warden)([^A-Za-z0-9_]|$)")
|
||||
|
||||
foreach(FILE_PATH IN LISTS PROTO_SOURCES)
|
||||
file(READ "${FILE_PATH}" CONTENTS)
|
||||
foreach(PATTERN IN LISTS FORBIDDEN_PATTERNS)
|
||||
if(CONTENTS MATCHES "${PATTERN}")
|
||||
message(FATAL_ERROR "Forbidden protocol dependency in ${FILE_PATH}: ${PATTERN}")
|
||||
endif()
|
||||
endforeach()
|
||||
endforeach()
|
||||
69
tests/CheckWorldNetworkBoundary.cmake
Normal file
69
tests/CheckWorldNetworkBoundary.cmake
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
set(REQUIRED_FILES
|
||||
"${SOURCE_ROOT}/src/game/Server/WorldGateway.h"
|
||||
"${SOURCE_ROOT}/src/game/Server/WorldGateway.cpp"
|
||||
"${SOURCE_ROOT}/src/game/Server/SessionMailbox.h"
|
||||
"${SOURCE_ROOT}/src/game/Server/SessionMailbox.cpp"
|
||||
"${SOURCE_ROOT}/src/game/Server/WorldNetwork.h"
|
||||
"${SOURCE_ROOT}/src/game/Server/WorldNetwork.cpp")
|
||||
|
||||
string(CONCAT OLD_SOCKET_NAME "World" "Socket")
|
||||
string(CONCAT OLD_SOCKET_MANAGER_NAME "World" "Socket" "Mgr")
|
||||
string(CONCAT OLD_LEASE_NAME "Leased" "Ptr")
|
||||
set(REMOVED_FILES
|
||||
"${SOURCE_ROOT}/src/game/Server/${OLD_SOCKET_NAME}.h"
|
||||
"${SOURCE_ROOT}/src/game/Server/${OLD_SOCKET_NAME}.cpp"
|
||||
"${SOURCE_ROOT}/src/game/Server/${OLD_SOCKET_MANAGER_NAME}.h"
|
||||
"${SOURCE_ROOT}/src/game/Server/${OLD_SOCKET_MANAGER_NAME}.cpp"
|
||||
"${SOURCE_ROOT}/src/shared/Threading/${OLD_LEASE_NAME}.h")
|
||||
|
||||
foreach(FILE_PATH IN LISTS REQUIRED_FILES)
|
||||
if(NOT EXISTS "${FILE_PATH}")
|
||||
message(FATAL_ERROR "Required decoupling file is missing: ${FILE_PATH}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
foreach(FILE_PATH IN LISTS REMOVED_FILES)
|
||||
if(EXISTS "${FILE_PATH}")
|
||||
message(FATAL_ERROR "Obsolete coupled file still exists: ${FILE_PATH}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
file(READ "${SOURCE_ROOT}/src/game/Server/WorldGateway.cpp" WORLD_GATEWAY_SOURCE)
|
||||
foreach(REQUIRED_TRANSACTION_STEP IN ITEMS
|
||||
"WorldSession* const publishedSession = session.release()"
|
||||
"session.reset(publishedSession)"
|
||||
"Detach(sessionId)")
|
||||
string(FIND "${WORLD_GATEWAY_SOURCE}" "${REQUIRED_TRANSACTION_STEP}" STEP_POSITION)
|
||||
if(STEP_POSITION EQUAL -1)
|
||||
message(FATAL_ERROR
|
||||
"WorldGateway session publication is missing rollback step: ${REQUIRED_TRANSACTION_STEP}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
string(FIND "${WORLD_GATEWAY_SOURCE}" "link->SendPacket(addonResponse)" EARLY_ADDON_SEND)
|
||||
if(NOT EARLY_ADDON_SEND EQUAL -1)
|
||||
message(FATAL_ERROR "WorldGateway sends addon info before the world-thread auth response")
|
||||
endif()
|
||||
|
||||
file(READ "${SOURCE_ROOT}/src/game/WorldHandlers/World.cpp" WORLD_SOURCE)
|
||||
foreach(REQUIRED_AUTH_ORDER IN ITEMS
|
||||
"AddQueuedSession(s);\n s->SendPendingAddonInfo();"
|
||||
"s->SendPacket(&packet);\n s->SendPendingAddonInfo();")
|
||||
string(FIND "${WORLD_SOURCE}" "${REQUIRED_AUTH_ORDER}" AUTH_ORDER_POSITION)
|
||||
if(AUTH_ORDER_POSITION EQUAL -1)
|
||||
message(FATAL_ERROR
|
||||
"World-thread auth/addon ordering is missing: ${REQUIRED_AUTH_ORDER}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
file(READ "${SOURCE_ROOT}/src/proto/ClientConnection.cpp" CLIENT_CONNECTION_SOURCE)
|
||||
foreach(REQUIRED_SESSION_SNAPSHOT_STEP IN ITEMS
|
||||
"SessionId ClientConnection::CurrentSession()"
|
||||
"SessionId const session = CurrentSession()"
|
||||
"m_gateway.Deliver(session, std::move(packet))")
|
||||
string(FIND "${CLIENT_CONNECTION_SOURCE}" "${REQUIRED_SESSION_SNAPSHOT_STEP}" SNAPSHOT_POSITION)
|
||||
if(SNAPSHOT_POSITION EQUAL -1)
|
||||
message(FATAL_ERROR
|
||||
"ClientConnection delivery is missing a locked session snapshot: ${REQUIRED_SESSION_SNAPSHOT_STEP}")
|
||||
endif()
|
||||
endforeach()
|
||||
137
tests/DatabaseConcurrencyTests.cpp
Normal file
137
tests/DatabaseConcurrencyTests.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#include "TestSupport.hpp"
|
||||
|
||||
#include "Database/Database.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
class FakeConnection final : public SqlConnection
|
||||
{
|
||||
public:
|
||||
explicit FakeConnection(Database& database)
|
||||
: SqlConnection(database)
|
||||
{
|
||||
}
|
||||
|
||||
bool Initialize(const char*) override { return true; }
|
||||
|
||||
QueryResult* Query(const char*) override
|
||||
{
|
||||
Enter();
|
||||
queryEntered.store(true);
|
||||
if (coordinateEscape)
|
||||
{
|
||||
while (!escapeAttempting.load())
|
||||
std::this_thread::yield();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
Leave();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QueryNamedResult* QueryNamed(const char*) override { return nullptr; }
|
||||
bool Execute(const char*) override { return true; }
|
||||
|
||||
unsigned long escape_string(char* to, const char* from, unsigned long length) override
|
||||
{
|
||||
Enter();
|
||||
for (unsigned long i = 0; i < length; ++i)
|
||||
to[i] = from[i];
|
||||
to[length] = '\0';
|
||||
Leave();
|
||||
return length;
|
||||
}
|
||||
|
||||
void Enter()
|
||||
{
|
||||
if (active.fetch_add(1) != 0)
|
||||
overlap.store(true);
|
||||
}
|
||||
|
||||
void Leave()
|
||||
{
|
||||
active.fetch_sub(1);
|
||||
}
|
||||
|
||||
std::atomic<int> active{0};
|
||||
std::atomic<bool> overlap{false};
|
||||
std::atomic<bool> queryEntered{false};
|
||||
std::atomic<bool> escapeAttempting{false};
|
||||
bool coordinateEscape = false;
|
||||
};
|
||||
|
||||
class FakeDatabase final : public Database
|
||||
{
|
||||
public:
|
||||
FakeDatabase()
|
||||
{
|
||||
m_connection = new FakeConnection(*this);
|
||||
m_pQueryConnections.push_back(m_connection);
|
||||
m_nQueryConnPoolSize = 1;
|
||||
}
|
||||
|
||||
FakeConnection& Connection() { return *m_connection; }
|
||||
|
||||
protected:
|
||||
SqlConnection* CreateConnection() override { return new FakeConnection(*this); }
|
||||
|
||||
private:
|
||||
FakeConnection* m_connection;
|
||||
};
|
||||
|
||||
void concurrentQueriesUseTheConnectionLock()
|
||||
{
|
||||
FakeDatabase database;
|
||||
std::vector<std::thread> threads;
|
||||
for (unsigned i = 0; i < 8; ++i)
|
||||
{
|
||||
threads.emplace_back([&database]()
|
||||
{
|
||||
for (unsigned query = 0; query < 3; ++query)
|
||||
database.PQuery("SELECT %u", query);
|
||||
});
|
||||
}
|
||||
for (std::thread& thread : threads)
|
||||
thread.join();
|
||||
|
||||
CHECK(!database.Connection().overlap.load());
|
||||
}
|
||||
|
||||
void escapingSharesTheQueryConnectionLock()
|
||||
{
|
||||
FakeDatabase database;
|
||||
FakeConnection& connection = database.Connection();
|
||||
connection.coordinateEscape = true;
|
||||
|
||||
std::thread query([&database]() { database.PQuery("SELECT 1"); });
|
||||
std::thread escape([&database, &connection]()
|
||||
{
|
||||
while (!connection.queryEntered.load())
|
||||
std::this_thread::yield();
|
||||
connection.escapeAttempting.store(true);
|
||||
std::string value = "account'name";
|
||||
database.escape_string(value);
|
||||
});
|
||||
|
||||
query.join();
|
||||
escape.join();
|
||||
|
||||
CHECK(!connection.overlap.load());
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
concurrentQueriesUseTheConnectionLock();
|
||||
escapingSharesTheQueryConnectionLock();
|
||||
return mangos::test::failures == 0 ? 0 : 1;
|
||||
}
|
||||
752
tests/NetworkRegressionTests.cpp
Normal file
752
tests/NetworkRegressionTests.cpp
Normal file
|
|
@ -0,0 +1,752 @@
|
|||
#include "TestSupport.hpp"
|
||||
|
||||
#include "net/Server.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class SocketHandle
|
||||
{
|
||||
public:
|
||||
explicit SocketHandle(SOCKET socket = INVALID_SOCKET) : m_socket(socket) {}
|
||||
~SocketHandle()
|
||||
{
|
||||
if (m_socket != INVALID_SOCKET)
|
||||
closesocket(m_socket);
|
||||
}
|
||||
|
||||
SocketHandle(SocketHandle const&) = delete;
|
||||
SocketHandle& operator=(SocketHandle const&) = delete;
|
||||
|
||||
SocketHandle(SocketHandle&& other) noexcept : m_socket(other.m_socket)
|
||||
{
|
||||
other.m_socket = INVALID_SOCKET;
|
||||
}
|
||||
|
||||
SocketHandle& operator=(SocketHandle&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (m_socket != INVALID_SOCKET)
|
||||
closesocket(m_socket);
|
||||
m_socket = other.m_socket;
|
||||
other.m_socket = INVALID_SOCKET;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
SOCKET get() const { return m_socket; }
|
||||
|
||||
private:
|
||||
SOCKET m_socket;
|
||||
};
|
||||
|
||||
class SessionRegistry
|
||||
{
|
||||
public:
|
||||
void publish(std::shared_ptr<class LoopbackSession> session)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_sessions.push_back(std::move(session));
|
||||
}
|
||||
m_ready.notify_one();
|
||||
}
|
||||
|
||||
std::shared_ptr<class LoopbackSession> take()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
if (!m_ready.wait_for(lock, 5s, [&] { return !m_sessions.empty(); }))
|
||||
return {};
|
||||
|
||||
auto session = std::move(m_sessions.front());
|
||||
m_sessions.pop_front();
|
||||
return session;
|
||||
}
|
||||
|
||||
private:
|
||||
std::mutex m_mutex;
|
||||
std::condition_variable m_ready;
|
||||
std::deque<std::shared_ptr<class LoopbackSession>> m_sessions;
|
||||
};
|
||||
|
||||
class LoopbackSession final : public net::ISession
|
||||
{
|
||||
public:
|
||||
enum class Mode { FinalResponse, ContractClose, ExternalClose };
|
||||
|
||||
LoopbackSession(Mode mode, std::vector<uint8_t> finalResponse,
|
||||
SessionRegistry* registry = nullptr)
|
||||
: m_mode(mode), m_finalResponse(std::move(finalResponse)), m_registry(registry)
|
||||
{
|
||||
}
|
||||
|
||||
void setSender(net::Sender sender) override { m_sender = std::move(sender); }
|
||||
void setCloser(net::Closer closer) override { m_closer = std::move(closer); }
|
||||
|
||||
std::vector<uint8_t> onConnect() override
|
||||
{
|
||||
if (m_registry)
|
||||
m_registry->publish(std::static_pointer_cast<LoopbackSession>(shared_from_this()));
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> onData(uint8_t const*, std::size_t) override
|
||||
{
|
||||
if (m_mode == Mode::FinalResponse)
|
||||
{
|
||||
m_sender(m_finalResponse.data(), m_finalResponse.size());
|
||||
requestClose();
|
||||
}
|
||||
else if (m_mode == Mode::ContractClose)
|
||||
{
|
||||
m_closed.store(true, std::memory_order_release);
|
||||
return m_finalResponse;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool closed() const override { return m_closed.load(std::memory_order_acquire); }
|
||||
|
||||
void send(std::vector<uint8_t> const& bytes) { m_sender(bytes.data(), bytes.size()); }
|
||||
|
||||
void requestClose()
|
||||
{
|
||||
m_closed.store(true, std::memory_order_release);
|
||||
m_closer();
|
||||
}
|
||||
|
||||
private:
|
||||
Mode m_mode;
|
||||
std::vector<uint8_t> m_finalResponse;
|
||||
SessionRegistry* m_registry;
|
||||
net::Sender m_sender;
|
||||
net::Closer m_closer;
|
||||
std::atomic<bool> m_closed{false};
|
||||
};
|
||||
|
||||
uint16_t reserveLoopbackPort()
|
||||
{
|
||||
SocketHandle socket(::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
|
||||
if (socket.get() == INVALID_SOCKET)
|
||||
return 0;
|
||||
|
||||
SOCKADDR_IN address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
address.sin_port = 0;
|
||||
if (bind(socket.get(), reinterpret_cast<SOCKADDR*>(&address), sizeof(address)) == SOCKET_ERROR)
|
||||
return 0;
|
||||
|
||||
int length = sizeof(address);
|
||||
if (getsockname(socket.get(), reinterpret_cast<SOCKADDR*>(&address), &length) == SOCKET_ERROR)
|
||||
return 0;
|
||||
return ntohs(address.sin_port);
|
||||
}
|
||||
|
||||
bool startServer(net::Server& server, net::SessionFactory factory, uint16_t& port)
|
||||
{
|
||||
for (int attempt = 0; attempt < 5; ++attempt)
|
||||
{
|
||||
port = reserveLoopbackPort();
|
||||
if (port != 0 && server.start(port, factory, "127.0.0.1"))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SocketHandle connectClient(uint16_t port)
|
||||
{
|
||||
SOCKET socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (socket == INVALID_SOCKET)
|
||||
return SocketHandle{};
|
||||
|
||||
DWORD timeout = 5000;
|
||||
int receiveBuffer = 4096;
|
||||
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO,
|
||||
reinterpret_cast<char const*>(&timeout), sizeof(timeout));
|
||||
setsockopt(socket, SOL_SOCKET, SO_RCVBUF,
|
||||
reinterpret_cast<char const*>(&receiveBuffer), sizeof(receiveBuffer));
|
||||
|
||||
SOCKADDR_IN address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
address.sin_port = htons(port);
|
||||
if (connect(socket, reinterpret_cast<SOCKADDR*>(&address), sizeof(address)) == SOCKET_ERROR)
|
||||
{
|
||||
closesocket(socket);
|
||||
return SocketHandle{};
|
||||
}
|
||||
return SocketHandle(socket);
|
||||
}
|
||||
|
||||
bool readToEof(SOCKET socket, std::vector<uint8_t>& bytes, int& socketError)
|
||||
{
|
||||
std::array<uint8_t, 8192> buffer{};
|
||||
while (true)
|
||||
{
|
||||
int const received = recv(socket, reinterpret_cast<char*>(buffer.data()),
|
||||
static_cast<int>(buffer.size()), 0);
|
||||
if (received == 0)
|
||||
return true;
|
||||
if (received == SOCKET_ERROR)
|
||||
{
|
||||
socketError = WSAGetLastError();
|
||||
return false;
|
||||
}
|
||||
bytes.insert(bytes.end(), buffer.begin(), buffer.begin() + received);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<uint8_t> makeFinalPayload()
|
||||
{
|
||||
std::vector<uint8_t> payload(1024 * 1024);
|
||||
for (std::size_t i = 0; i < payload.size(); ++i)
|
||||
payload[i] = static_cast<uint8_t>(i % 251);
|
||||
return payload;
|
||||
}
|
||||
|
||||
bool finalResponseDrainsBeforeEof()
|
||||
{
|
||||
std::vector<uint8_t> const payload = makeFinalPayload();
|
||||
net::Server server;
|
||||
uint16_t port = 0;
|
||||
if (!startServer(server,
|
||||
[payload] {
|
||||
return std::make_shared<LoopbackSession>(
|
||||
LoopbackSession::Mode::FinalResponse, payload);
|
||||
}, port))
|
||||
return false;
|
||||
|
||||
bool passed = true;
|
||||
for (int iteration = 0; iteration < 100 && passed; ++iteration)
|
||||
{
|
||||
SocketHandle client = connectClient(port);
|
||||
if (client.get() == INVALID_SOCKET)
|
||||
{
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t trigger = 1;
|
||||
if (send(client.get(), reinterpret_cast<char const*>(&trigger), 1, 0) != 1)
|
||||
{
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> received;
|
||||
int socketError = 0;
|
||||
bool const reachedEof = readToEof(client.get(), received, socketError);
|
||||
passed = reachedEof && received == payload;
|
||||
if (!passed)
|
||||
std::cerr << "final-response iteration=" << iteration
|
||||
<< " eof=" << reachedEof << " socketError=" << socketError
|
||||
<< " expectedBytes=" << payload.size()
|
||||
<< " actualBytes=" << received.size() << '\n';
|
||||
}
|
||||
|
||||
server.stop();
|
||||
return passed;
|
||||
}
|
||||
|
||||
bool closeTransitionRejectsLaterSends()
|
||||
{
|
||||
SessionRegistry registry;
|
||||
net::Server server;
|
||||
uint16_t port = 0;
|
||||
if (!startServer(server,
|
||||
[®istry] {
|
||||
return std::make_shared<LoopbackSession>(
|
||||
LoopbackSession::Mode::ExternalClose, std::vector<uint8_t>{}, ®istry);
|
||||
}, port))
|
||||
return false;
|
||||
|
||||
std::vector<uint8_t> const pre = {'P', 'R', 'E'};
|
||||
std::vector<uint8_t> const post = {'P', 'O', 'S', 'T'};
|
||||
bool passed = true;
|
||||
for (int iteration = 0; iteration < 100 && passed; ++iteration)
|
||||
{
|
||||
SocketHandle client = connectClient(port);
|
||||
std::shared_ptr<LoopbackSession> session = registry.take();
|
||||
if (client.get() == INVALID_SOCKET || !session)
|
||||
{
|
||||
passed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
std::promise<void> preReturned;
|
||||
std::promise<void> closeReturned;
|
||||
std::shared_future<void> closeSignal = closeReturned.get_future().share();
|
||||
std::thread producer([&] {
|
||||
session->send(pre);
|
||||
preReturned.set_value();
|
||||
closeSignal.wait();
|
||||
session->send(post);
|
||||
});
|
||||
|
||||
preReturned.get_future().wait();
|
||||
session->requestClose();
|
||||
closeReturned.set_value();
|
||||
producer.join();
|
||||
|
||||
std::vector<uint8_t> received;
|
||||
int socketError = 0;
|
||||
bool const reachedEof = readToEof(client.get(), received, socketError);
|
||||
passed = reachedEof && received == pre;
|
||||
if (!passed)
|
||||
{
|
||||
std::cerr << "close-race iteration=" << iteration
|
||||
<< " eof=" << reachedEof << " socketError=" << socketError
|
||||
<< " actualBytes=" << received.size() << " data=";
|
||||
for (uint8_t byte : received)
|
||||
std::cerr << static_cast<char>(byte);
|
||||
std::cerr << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
server.stop();
|
||||
return passed;
|
||||
}
|
||||
|
||||
bool closedContractDrainsBeforeEof()
|
||||
{
|
||||
std::vector<uint8_t> const payload = makeFinalPayload();
|
||||
net::Server server;
|
||||
uint16_t port = 0;
|
||||
if (!startServer(server,
|
||||
[payload] {
|
||||
return std::make_shared<LoopbackSession>(
|
||||
LoopbackSession::Mode::ContractClose, payload);
|
||||
}, port))
|
||||
return false;
|
||||
|
||||
SocketHandle client = connectClient(port);
|
||||
uint8_t trigger = 1;
|
||||
bool passed = client.get() != INVALID_SOCKET &&
|
||||
send(client.get(), reinterpret_cast<char const*>(&trigger), 1, 0) == 1;
|
||||
if (passed)
|
||||
{
|
||||
std::vector<uint8_t> received;
|
||||
int socketError = 0;
|
||||
passed = readToEof(client.get(), received, socketError) && received == payload;
|
||||
}
|
||||
|
||||
server.stop();
|
||||
return passed;
|
||||
}
|
||||
|
||||
bool restartAcceptsConnections()
|
||||
{
|
||||
SessionRegistry registry;
|
||||
net::Server server;
|
||||
auto factory = [®istry] {
|
||||
return std::make_shared<LoopbackSession>(
|
||||
LoopbackSession::Mode::ExternalClose, std::vector<uint8_t>{}, ®istry);
|
||||
};
|
||||
|
||||
uint16_t firstPort = 0;
|
||||
if (!startServer(server, factory, firstPort))
|
||||
return false;
|
||||
SocketHandle firstClient = connectClient(firstPort);
|
||||
bool const firstAccepted = firstClient.get() != INVALID_SOCKET && registry.take() != nullptr;
|
||||
server.stop();
|
||||
if (!firstAccepted)
|
||||
return false;
|
||||
|
||||
uint16_t secondPort = 0;
|
||||
if (!startServer(server, factory, secondPort))
|
||||
return false;
|
||||
SocketHandle secondClient = connectClient(secondPort);
|
||||
bool const secondAccepted = secondClient.get() != INVALID_SOCKET && registry.take() != nullptr;
|
||||
server.stop();
|
||||
return secondAccepted;
|
||||
}
|
||||
|
||||
bool outstandingOperationAccountingIsClosedByShutdown()
|
||||
{
|
||||
net::OutstandingOperations operations;
|
||||
if (!operations.tryBegin() || operations.count() != 1)
|
||||
return false;
|
||||
operations.complete();
|
||||
if (operations.count() != 0)
|
||||
return false;
|
||||
|
||||
if (!operations.tryBegin())
|
||||
return false;
|
||||
auto completion = std::async(std::launch::async, [&] { operations.complete(); });
|
||||
operations.waitForZero();
|
||||
completion.get();
|
||||
if (operations.count() != 0)
|
||||
return false;
|
||||
|
||||
operations.stopSubmissions();
|
||||
return !operations.acceptingSubmissions() && !operations.tryBegin() &&
|
||||
operations.count() == 0;
|
||||
}
|
||||
|
||||
bool shutdownDrainsPendingOperations()
|
||||
{
|
||||
for (int iteration = 0; iteration < 50; ++iteration)
|
||||
{
|
||||
net::Server server;
|
||||
uint16_t port = 0;
|
||||
if (!startServer(server,
|
||||
[] {
|
||||
return std::make_shared<LoopbackSession>(
|
||||
LoopbackSession::Mode::ExternalClose, std::vector<uint8_t>{});
|
||||
}, port))
|
||||
return false;
|
||||
|
||||
std::array<SocketHandle, 4> clients;
|
||||
for (SocketHandle& client : clients)
|
||||
{
|
||||
client = connectClient(port);
|
||||
if (client.get() == INVALID_SOCKET)
|
||||
return false;
|
||||
}
|
||||
|
||||
auto stopped = std::async(std::launch::async, [&] { server.stop(); });
|
||||
if (stopped.wait_for(5s) != std::future_status::ready)
|
||||
{
|
||||
std::cerr << "shutdown iteration=" << iteration << " exceeded five seconds\n";
|
||||
return false;
|
||||
}
|
||||
stopped.get();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
|
||||
#include "net/reactor/ReactorServer.hpp"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class PosixSocketHandle
|
||||
{
|
||||
public:
|
||||
explicit PosixSocketHandle(int fd = -1) : m_fd(fd) {}
|
||||
~PosixSocketHandle() { if (m_fd >= 0) ::close(m_fd); }
|
||||
PosixSocketHandle(PosixSocketHandle const&) = delete;
|
||||
PosixSocketHandle& operator=(PosixSocketHandle const&) = delete;
|
||||
PosixSocketHandle(PosixSocketHandle&& other) noexcept : m_fd(other.m_fd)
|
||||
{
|
||||
other.m_fd = -1;
|
||||
}
|
||||
PosixSocketHandle& operator=(PosixSocketHandle&& other) noexcept
|
||||
{
|
||||
if (this != &other)
|
||||
{
|
||||
if (m_fd >= 0)
|
||||
::close(m_fd);
|
||||
m_fd = other.m_fd;
|
||||
other.m_fd = -1;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
int get() const { return m_fd; }
|
||||
|
||||
private:
|
||||
int m_fd;
|
||||
};
|
||||
|
||||
uint16_t reservePosixLoopbackPort()
|
||||
{
|
||||
PosixSocketHandle socket(::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
|
||||
if (socket.get() < 0)
|
||||
return 0;
|
||||
|
||||
sockaddr_in address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
address.sin_port = 0;
|
||||
if (::bind(socket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0)
|
||||
return 0;
|
||||
|
||||
socklen_t length = sizeof(address);
|
||||
if (::getsockname(socket.get(), reinterpret_cast<sockaddr*>(&address), &length) < 0)
|
||||
return 0;
|
||||
return ntohs(address.sin_port);
|
||||
}
|
||||
|
||||
PosixSocketHandle connectPosixClient(uint16_t port)
|
||||
{
|
||||
int fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (fd < 0)
|
||||
return PosixSocketHandle{};
|
||||
|
||||
sockaddr_in address{};
|
||||
address.sin_family = AF_INET;
|
||||
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||
address.sin_port = htons(port);
|
||||
if (::connect(fd, reinterpret_cast<sockaddr*>(&address), sizeof(address)) < 0)
|
||||
{
|
||||
::close(fd);
|
||||
return PosixSocketHandle{};
|
||||
}
|
||||
return PosixSocketHandle(fd);
|
||||
}
|
||||
|
||||
class CallbackRecorderSession final : public net::ISession
|
||||
{
|
||||
public:
|
||||
void setPeerAddress(std::string const& address) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_events.push_back("peer:" + address);
|
||||
}
|
||||
|
||||
void setSender(net::Sender sender) override
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_sender = std::move(sender);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> onConnect() override
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_events.push_back("connect");
|
||||
}
|
||||
m_changed.notify_all();
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<uint8_t> onData(uint8_t const*, std::size_t) override { return {}; }
|
||||
|
||||
void onClose() override
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
m_events.push_back("close");
|
||||
++m_closeCount;
|
||||
m_closed.store(true, std::memory_order_release);
|
||||
}
|
||||
m_changed.notify_all();
|
||||
}
|
||||
|
||||
bool closed() const override { return m_closed.load(std::memory_order_acquire); }
|
||||
|
||||
bool waitForEventCount(std::size_t count)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_mutex);
|
||||
return m_changed.wait_for(lock, 5s, [&] { return m_events.size() >= count; });
|
||||
}
|
||||
|
||||
std::vector<std::string> events() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_events;
|
||||
}
|
||||
|
||||
int closeCount() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
return m_closeCount;
|
||||
}
|
||||
|
||||
void sendOneByte()
|
||||
{
|
||||
net::Sender sender;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mutex);
|
||||
sender = m_sender;
|
||||
}
|
||||
uint8_t byte = 1;
|
||||
sender(&byte, 1);
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::mutex m_mutex;
|
||||
std::condition_variable m_changed;
|
||||
std::vector<std::string> m_events;
|
||||
net::Sender m_sender;
|
||||
std::atomic<bool> m_closed{false};
|
||||
int m_closeCount = 0;
|
||||
};
|
||||
|
||||
struct RejectingPollerState
|
||||
{
|
||||
void signalAccept()
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
acceptReady = true;
|
||||
++epoch;
|
||||
}
|
||||
changed.notify_all();
|
||||
}
|
||||
|
||||
std::mutex mutex;
|
||||
std::condition_variable changed;
|
||||
uint64_t epoch = 0;
|
||||
bool acceptReady = false;
|
||||
std::atomic<unsigned> created{0};
|
||||
std::atomic<unsigned> workerWakeCalls{0};
|
||||
};
|
||||
|
||||
class RejectingPoller final : public net::Poller
|
||||
{
|
||||
public:
|
||||
RejectingPoller(std::shared_ptr<RejectingPollerState> state, bool acceptPoller)
|
||||
: m_state(std::move(state)), m_acceptPoller(acceptPoller) {}
|
||||
|
||||
bool init() override { return true; }
|
||||
|
||||
bool add(int, uint32_t, void* udata) override
|
||||
{
|
||||
if (!m_acceptPoller)
|
||||
return false;
|
||||
m_acceptUdata = udata;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mod(int, uint32_t, void*) override { return true; }
|
||||
bool del(int) override { return true; }
|
||||
|
||||
int wait(net::PollerEvent* out, int maxEvents) override
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_state->mutex);
|
||||
m_state->changed.wait(lock, [&] {
|
||||
return m_state->epoch != m_seenEpoch ||
|
||||
(m_acceptPoller && m_state->acceptReady);
|
||||
});
|
||||
m_seenEpoch = m_state->epoch;
|
||||
if (m_acceptPoller && m_state->acceptReady && maxEvents > 0)
|
||||
{
|
||||
m_state->acceptReady = false;
|
||||
out[0] = {m_acceptUdata, net::EvRead, false};
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wake() override
|
||||
{
|
||||
if (!m_acceptPoller)
|
||||
m_state->workerWakeCalls.fetch_add(1, std::memory_order_relaxed);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_state->mutex);
|
||||
++m_state->epoch;
|
||||
}
|
||||
m_state->changed.notify_all();
|
||||
}
|
||||
|
||||
void shutdown() override { wake(); }
|
||||
char const* name() const override { return "rejecting-test-poller"; }
|
||||
|
||||
private:
|
||||
std::shared_ptr<RejectingPollerState> m_state;
|
||||
bool m_acceptPoller;
|
||||
void* m_acceptUdata = nullptr;
|
||||
uint64_t m_seenEpoch = 0;
|
||||
};
|
||||
|
||||
bool reactorRejectedRegistrationRunsTeardown()
|
||||
{
|
||||
auto state = std::make_shared<RejectingPollerState>();
|
||||
net::ReactorServer server([state] {
|
||||
bool const acceptPoller = state->created.fetch_add(1) == 0;
|
||||
return std::make_unique<RejectingPoller>(state, acceptPoller);
|
||||
});
|
||||
auto session = std::make_shared<CallbackRecorderSession>();
|
||||
|
||||
uint16_t port = reservePosixLoopbackPort();
|
||||
if (port == 0 || !server.start(port, [session] { return session; }, "127.0.0.1"))
|
||||
return false;
|
||||
|
||||
PosixSocketHandle client = connectPosixClient(port);
|
||||
if (client.get() < 0)
|
||||
return false;
|
||||
state->signalAccept();
|
||||
|
||||
bool passed = session->waitForEventCount(3) &&
|
||||
session->events() == std::vector<std::string>{"peer:127.0.0.1", "connect", "close"} &&
|
||||
session->closeCount() == 1;
|
||||
if (passed)
|
||||
{
|
||||
unsigned const wakesBeforeSend = state->workerWakeCalls.load(std::memory_order_relaxed);
|
||||
session->sendOneByte();
|
||||
std::this_thread::sleep_for(20ms);
|
||||
passed = state->workerWakeCalls.load(std::memory_order_relaxed) == wakesBeforeSend;
|
||||
}
|
||||
|
||||
server.stop();
|
||||
return passed && session->closeCount() == 1;
|
||||
}
|
||||
|
||||
#ifdef MANGOS_USE_IO_URING
|
||||
bool uringPublishesPeerBeforeConnect()
|
||||
{
|
||||
net::Server server;
|
||||
auto session = std::make_shared<CallbackRecorderSession>();
|
||||
uint16_t port = reservePosixLoopbackPort();
|
||||
if (port == 0 || !server.start(port, [session] { return session; }, "127.0.0.1"))
|
||||
return false;
|
||||
|
||||
PosixSocketHandle client = connectPosixClient(port);
|
||||
bool const connected = client.get() >= 0 && session->waitForEventCount(2);
|
||||
std::vector<std::string> const events = session->events();
|
||||
server.stop();
|
||||
return connected && events.size() >= 2 &&
|
||||
events[0] == "peer:127.0.0.1" && events[1] == "connect";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa{};
|
||||
CHECK(WSAStartup(MAKEWORD(2, 2), &wsa) == 0);
|
||||
if (mangos::test::failures == 0)
|
||||
{
|
||||
CHECK(outstandingOperationAccountingIsClosedByShutdown());
|
||||
CHECK(finalResponseDrainsBeforeEof());
|
||||
CHECK(closeTransitionRejectsLaterSends());
|
||||
CHECK(closedContractDrainsBeforeEof());
|
||||
CHECK(restartAcceptsConnections());
|
||||
CHECK(shutdownDrainsPendingOperations());
|
||||
}
|
||||
WSACleanup();
|
||||
#else
|
||||
CHECK(reactorRejectedRegistrationRunsTeardown());
|
||||
#ifdef MANGOS_USE_IO_URING
|
||||
CHECK(uringPublishesPeerBeforeConnect());
|
||||
#else
|
||||
std::cout << "SKIP: io_uring peer-order regression requires MANGOS_USE_IO_URING\n";
|
||||
#endif
|
||||
#endif
|
||||
return mangos::test::failures == 0 ? 0 : 1;
|
||||
}
|
||||
795
tests/ProtocolTests.cpp
Normal file
795
tests/ProtocolTests.cpp
Normal file
|
|
@ -0,0 +1,795 @@
|
|||
#include "TestSupport.hpp"
|
||||
|
||||
#include "Auth/HMACSHA1.h"
|
||||
#include "Auth/Sha1.h"
|
||||
#include "ClientConnection.h"
|
||||
#include "IWorldGateway.h"
|
||||
#include "Opcodes.h"
|
||||
#include "PacketCodec.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
static_assert(uint8(proto::AuthStatus::Ok) == 0x0C);
|
||||
static_assert(uint8(proto::AuthStatus::Failed) == 0x0D);
|
||||
static_assert(uint8(proto::AuthStatus::Reject) == 0x0E);
|
||||
static_assert(uint8(proto::AuthStatus::BadServerProof) == 0x0F);
|
||||
static_assert(uint8(proto::AuthStatus::Unavailable) == 0x10);
|
||||
static_assert(uint8(proto::AuthStatus::SystemError) == 0x11);
|
||||
static_assert(uint8(proto::AuthStatus::BillingError) == 0x12);
|
||||
static_assert(uint8(proto::AuthStatus::BillingExpired) == 0x13);
|
||||
static_assert(uint8(proto::AuthStatus::VersionMismatch) == 0x14);
|
||||
static_assert(uint8(proto::AuthStatus::UnknownAccount) == 0x15);
|
||||
static_assert(uint8(proto::AuthStatus::IncorrectPassword) == 0x16);
|
||||
static_assert(uint8(proto::AuthStatus::SessionExpired) == 0x17);
|
||||
static_assert(uint8(proto::AuthStatus::ServerShuttingDown) == 0x18);
|
||||
static_assert(uint8(proto::AuthStatus::AlreadyLoggingIn) == 0x19);
|
||||
static_assert(uint8(proto::AuthStatus::LoginServerNotFound) == 0x1A);
|
||||
static_assert(uint8(proto::AuthStatus::WaitQueue) == 0x1B);
|
||||
static_assert(uint8(proto::AuthStatus::Banned) == 0x1C);
|
||||
static_assert(uint8(proto::AuthStatus::AlreadyOnline) == 0x1D);
|
||||
static_assert(uint8(proto::AuthStatus::NoTime) == 0x1E);
|
||||
static_assert(uint8(proto::AuthStatus::DatabaseBusy) == 0x1F);
|
||||
static_assert(uint8(proto::AuthStatus::Suspended) == 0x20);
|
||||
static_assert(uint8(proto::AuthStatus::ParentalControl) == 0x21);
|
||||
static_assert(uint8(proto::AuthStatus::LockedEnforced) == 0x22);
|
||||
|
||||
std::vector<uint8> ClientFrame(uint32 opcode, std::initializer_list<uint8> payload)
|
||||
{
|
||||
uint16 const size = uint16(4 + payload.size());
|
||||
std::vector<uint8> wire = {
|
||||
uint8(size >> 8), uint8(size),
|
||||
uint8(opcode), uint8(opcode >> 8), uint8(opcode >> 16), uint8(opcode >> 24)
|
||||
};
|
||||
wire.insert(wire.end(), payload.begin(), payload.end());
|
||||
return wire;
|
||||
}
|
||||
|
||||
std::vector<uint8> ClientFrame(uint32 opcode, const uint8* payload, std::size_t payloadSize)
|
||||
{
|
||||
uint16 const size = uint16(4 + payloadSize);
|
||||
std::vector<uint8> wire = {
|
||||
uint8(size >> 8), uint8(size),
|
||||
uint8(opcode), uint8(opcode >> 8), uint8(opcode >> 16), uint8(opcode >> 24)
|
||||
};
|
||||
wire.insert(wire.end(), payload, payload + payloadSize);
|
||||
return wire;
|
||||
}
|
||||
|
||||
class DummyAuthContext final : public proto::AuthContext
|
||||
{
|
||||
};
|
||||
|
||||
class FakeGateway final : public proto::IWorldGateway
|
||||
{
|
||||
public:
|
||||
bool filterResult = true;
|
||||
proto::AuthLookup lookup;
|
||||
proto::SessionId attachResult = 41;
|
||||
unsigned filterCalls = 0;
|
||||
unsigned lookupCalls = 0;
|
||||
unsigned attachCalls = 0;
|
||||
unsigned detachCalls = 0;
|
||||
std::vector<uint16> delivered;
|
||||
std::vector<std::pair<uint16, bool>> traced;
|
||||
proto::AuthRequest attachedRequest;
|
||||
std::shared_ptr<proto::IClientLink> retainedLink;
|
||||
bool sendDuringAttach = false;
|
||||
bool throwOnLookup = false;
|
||||
bool throwOnTrace = false;
|
||||
std::function<void()> duringAttach;
|
||||
|
||||
bool FilterAuthPacket(WorldPacket&) override
|
||||
{
|
||||
++filterCalls;
|
||||
return filterResult;
|
||||
}
|
||||
|
||||
void TracePacket(const WorldPacket& packet, bool incoming) override
|
||||
{
|
||||
if (throwOnTrace)
|
||||
throw std::runtime_error("simulated trace failure");
|
||||
traced.emplace_back(packet.GetOpcode(), incoming);
|
||||
}
|
||||
|
||||
proto::AuthLookup LookupAccount(const proto::AuthRequest&) override
|
||||
{
|
||||
++lookupCalls;
|
||||
if (throwOnLookup)
|
||||
throw std::runtime_error("simulated gateway failure");
|
||||
return lookup;
|
||||
}
|
||||
|
||||
proto::SessionId Attach(const proto::AuthRequest& request,
|
||||
const std::shared_ptr<proto::IClientLink>& link,
|
||||
const std::shared_ptr<proto::AuthContext>&) override
|
||||
{
|
||||
++attachCalls;
|
||||
if (duringAttach)
|
||||
duringAttach();
|
||||
attachedRequest = request;
|
||||
retainedLink = link;
|
||||
if (sendDuringAttach)
|
||||
{
|
||||
WorldPacket addon(SMSG_ADDON_INFO, 1);
|
||||
addon << uint8(0xA5);
|
||||
link->SendPacket(addon);
|
||||
}
|
||||
return attachResult;
|
||||
}
|
||||
|
||||
void Deliver(proto::SessionId id, WorldPacket&& packet) override
|
||||
{
|
||||
CHECK(id == attachResult);
|
||||
delivered.push_back(packet.GetOpcode());
|
||||
}
|
||||
|
||||
void Detach(proto::SessionId id) override
|
||||
{
|
||||
CHECK(id == attachResult);
|
||||
++detachCalls;
|
||||
}
|
||||
};
|
||||
|
||||
struct ConnectionHarness
|
||||
{
|
||||
FakeGateway gateway;
|
||||
std::shared_ptr<proto::ClientConnection> connection =
|
||||
std::make_shared<proto::ClientConnection>(gateway);
|
||||
std::vector<std::vector<uint8>> sent;
|
||||
unsigned closeCalls = 0;
|
||||
|
||||
ConnectionHarness()
|
||||
{
|
||||
connection->setPeerAddress("127.0.0.1");
|
||||
connection->setSender([this](const uint8* data, std::size_t len)
|
||||
{
|
||||
sent.emplace_back(data, data + len);
|
||||
});
|
||||
connection->setCloser([this]() { ++closeCalls; });
|
||||
}
|
||||
};
|
||||
|
||||
uint16 ServerOpcode(const std::vector<uint8>& frame)
|
||||
{
|
||||
CHECK(frame.size() >= proto::SERVER_HEADER_SIZE);
|
||||
return uint16(frame[2]) | (uint16(frame[3]) << 8);
|
||||
}
|
||||
|
||||
uint32 ServerSeed(const std::vector<uint8>& challenge)
|
||||
{
|
||||
CHECK(challenge.size() == proto::SERVER_HEADER_SIZE + 4);
|
||||
return uint32(challenge[4])
|
||||
| (uint32(challenge[5]) << 8)
|
||||
| (uint32(challenge[6]) << 16)
|
||||
| (uint32(challenge[7]) << 24);
|
||||
}
|
||||
|
||||
std::array<uint8, 20> MakeProof(const std::string& account, uint32 clientSeed,
|
||||
uint32 serverSeed, BigNumber& sessionKey)
|
||||
{
|
||||
uint8 const zero[4] = {0, 0, 0, 0};
|
||||
Sha1Hash sha;
|
||||
sha.UpdateData(account);
|
||||
sha.UpdateData(zero, sizeof(zero));
|
||||
sha.UpdateData(reinterpret_cast<const uint8*>(&clientSeed), sizeof(clientSeed));
|
||||
sha.UpdateData(reinterpret_cast<const uint8*>(&serverSeed), sizeof(serverSeed));
|
||||
sha.UpdateBigNumbers(&sessionKey, nullptr);
|
||||
sha.Finalize();
|
||||
|
||||
std::array<uint8, 20> digest{};
|
||||
std::copy(sha.GetDigest(), sha.GetDigest() + digest.size(), digest.begin());
|
||||
return digest;
|
||||
}
|
||||
|
||||
std::vector<uint8> AuthFrame(uint32 clientSeed, const std::array<uint8, 20>& digest,
|
||||
std::initializer_list<uint8> addonData = {})
|
||||
{
|
||||
WorldPacket packet(CMSG_AUTH_SESSION, 64);
|
||||
packet << uint32(8606);
|
||||
packet << uint32(0x12345678);
|
||||
packet << std::string("ACCOUNT");
|
||||
packet << clientSeed;
|
||||
packet.append(digest.data(), digest.size());
|
||||
if (addonData.size() != 0)
|
||||
packet.append(addonData.begin(), addonData.size());
|
||||
return ClientFrame(CMSG_AUTH_SESSION, packet.contents(), packet.size());
|
||||
}
|
||||
|
||||
class HeaderCipher
|
||||
{
|
||||
public:
|
||||
explicit HeaderCipher(BigNumber& sessionKey)
|
||||
{
|
||||
uint8 seed[SEED_KEY_SIZE] = {
|
||||
0x38, 0xA7, 0x83, 0x15, 0xF8, 0x92, 0x25, 0x30,
|
||||
0x71, 0x98, 0x67, 0xB1, 0x8C, 0x04, 0xE2, 0xAA
|
||||
};
|
||||
HMACSHA1 hash(SEED_KEY_SIZE, seed);
|
||||
hash.UpdateBigNumber(&sessionKey);
|
||||
hash.Finalize();
|
||||
m_key.assign(hash.GetDigest(), hash.GetDigest() + SHA_DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
void EncryptClientHeader(std::vector<uint8>& frame)
|
||||
{
|
||||
CHECK(frame.size() >= proto::CLIENT_HEADER_SIZE);
|
||||
for (std::size_t offset = 0; offset < proto::CLIENT_HEADER_SIZE; ++offset)
|
||||
{
|
||||
m_clientIndex %= m_key.size();
|
||||
uint8 const encrypted = uint8((frame[offset] ^ m_key[m_clientIndex]) + m_clientPrevious);
|
||||
++m_clientIndex;
|
||||
frame[offset] = m_clientPrevious = encrypted;
|
||||
}
|
||||
}
|
||||
|
||||
void DecryptServerHeader(std::vector<uint8>& frame)
|
||||
{
|
||||
CHECK(frame.size() >= proto::SERVER_HEADER_SIZE);
|
||||
for (std::size_t offset = 0; offset < proto::SERVER_HEADER_SIZE; ++offset)
|
||||
{
|
||||
m_serverIndex %= m_key.size();
|
||||
uint8 const encrypted = frame[offset];
|
||||
frame[offset] = uint8((encrypted - m_serverPrevious) ^ m_key[m_serverIndex]);
|
||||
++m_serverIndex;
|
||||
m_serverPrevious = encrypted;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<uint8> m_key;
|
||||
std::size_t m_clientIndex = 0;
|
||||
std::size_t m_serverIndex = 0;
|
||||
uint8 m_clientPrevious = 0;
|
||||
uint8 m_serverPrevious = 0;
|
||||
};
|
||||
|
||||
BigNumber SuccessfulLookup(FakeGateway& gateway)
|
||||
{
|
||||
BigNumber sessionKey;
|
||||
sessionKey.SetHexStr("0123456789ABCDEF0123456789ABCDEF01234567");
|
||||
gateway.lookup.status = proto::AuthStatus::Ok;
|
||||
gateway.lookup.sessionKey = sessionKey;
|
||||
gateway.lookup.context = std::make_shared<DummyAuthContext>();
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
BigNumber Authenticate(ConnectionHarness& harness, uint32 clientSeed = 0xA1B2C3D4)
|
||||
{
|
||||
BigNumber sessionKey = SuccessfulLookup(harness.gateway);
|
||||
std::vector<uint8> const challenge = harness.connection->onConnect();
|
||||
std::array<uint8, 20> const proof = MakeProof("ACCOUNT", clientSeed,
|
||||
ServerSeed(challenge), sessionKey);
|
||||
std::vector<uint8> const auth = AuthFrame(clientSeed, proof, {0xCA, 0xFE});
|
||||
harness.connection->onData(auth.data(), auth.size());
|
||||
CHECK(harness.gateway.attachCalls == 1);
|
||||
CHECK(!harness.connection->closed());
|
||||
return sessionKey;
|
||||
}
|
||||
|
||||
void fragmentedFrameDecodesOnce()
|
||||
{
|
||||
proto::PacketCodec codec;
|
||||
std::vector<WorldPacket> packets;
|
||||
std::vector<uint8> const wire = ClientFrame(CMSG_KEEP_ALIVE, {0x11, 0x22});
|
||||
|
||||
CHECK(codec.Feed(wire.data(), 3, packets) == proto::DecodeStatus::NeedMore);
|
||||
CHECK(packets.empty());
|
||||
CHECK(codec.Feed(wire.data() + 3, wire.size() - 3, packets) == proto::DecodeStatus::Ready);
|
||||
CHECK(packets.size() == 1);
|
||||
CHECK(packets[0].GetOpcode() == CMSG_KEEP_ALIVE);
|
||||
CHECK(packets[0].size() == 2);
|
||||
CHECK(packets[0][0] == 0x11);
|
||||
CHECK(packets[0][1] == 0x22);
|
||||
}
|
||||
|
||||
void combinedFramesPreserveOrder()
|
||||
{
|
||||
proto::PacketCodec codec;
|
||||
std::vector<WorldPacket> packets;
|
||||
std::vector<uint8> wire = ClientFrame(CMSG_PING, {0x01});
|
||||
std::vector<uint8> const second = ClientFrame(CMSG_KEEP_ALIVE, {});
|
||||
wire.insert(wire.end(), second.begin(), second.end());
|
||||
|
||||
CHECK(codec.Feed(wire.data(), wire.size(), packets) == proto::DecodeStatus::Ready);
|
||||
CHECK(packets.size() == 2);
|
||||
CHECK(packets[0].GetOpcode() == CMSG_PING);
|
||||
CHECK(packets[1].GetOpcode() == CMSG_KEEP_ALIVE);
|
||||
}
|
||||
|
||||
void splitHeadersDecryptExactlyOnce()
|
||||
{
|
||||
for (std::size_t split = 1; split < proto::CLIENT_HEADER_SIZE; ++split)
|
||||
{
|
||||
unsigned decryptCalls = 0;
|
||||
proto::PacketCodec codec([&decryptCalls](uint8*, std::size_t len)
|
||||
{
|
||||
++decryptCalls;
|
||||
CHECK(len == proto::CLIENT_HEADER_SIZE);
|
||||
});
|
||||
std::vector<WorldPacket> packets;
|
||||
std::vector<uint8> const wire = ClientFrame(CMSG_KEEP_ALIVE, {0x42});
|
||||
|
||||
CHECK(codec.Feed(wire.data(), split, packets) == proto::DecodeStatus::NeedMore);
|
||||
CHECK(decryptCalls == 0);
|
||||
CHECK(codec.Feed(wire.data() + split, wire.size() - split, packets) == proto::DecodeStatus::Ready);
|
||||
CHECK(decryptCalls == 1);
|
||||
CHECK(packets.size() == 1);
|
||||
}
|
||||
}
|
||||
|
||||
void malformedFramesAreRejected()
|
||||
{
|
||||
auto checkMalformed = [](std::vector<uint8> const& wire)
|
||||
{
|
||||
proto::PacketCodec codec;
|
||||
std::vector<WorldPacket> packets;
|
||||
CHECK(codec.Feed(wire.data(), wire.size(), packets) == proto::DecodeStatus::Malformed);
|
||||
CHECK(packets.empty());
|
||||
};
|
||||
|
||||
checkMalformed({0x00, 0x03, 0, 0, 0, 0});
|
||||
checkMalformed({0x28, 0x01, 0, 0, 0, 0});
|
||||
checkMalformed(ClientFrame(10241, {}));
|
||||
}
|
||||
|
||||
void serverFramesUseTheFixed243Header()
|
||||
{
|
||||
WorldPacket packet(SMSG_PONG, 2);
|
||||
packet << uint8(0xAA) << uint8(0xBB);
|
||||
|
||||
unsigned encryptCalls = 0;
|
||||
std::vector<uint8> const wire = proto::PacketCodec::Encode(
|
||||
packet, [&encryptCalls](uint8*, std::size_t len)
|
||||
{
|
||||
++encryptCalls;
|
||||
CHECK(len == proto::SERVER_HEADER_SIZE);
|
||||
});
|
||||
|
||||
CHECK(encryptCalls == 1);
|
||||
CHECK(wire.size() == proto::SERVER_HEADER_SIZE + 2);
|
||||
CHECK(wire[0] == 0x00);
|
||||
CHECK(wire[1] == 0x04);
|
||||
CHECK(wire[2] == uint8(uint16(SMSG_PONG) & 0xFF));
|
||||
CHECK(wire[3] == uint8(uint16(SMSG_PONG) >> 8));
|
||||
CHECK(wire[4] == 0xAA);
|
||||
CHECK(wire[5] == 0xBB);
|
||||
}
|
||||
|
||||
void connectionChallengeHasTheExpectedShape()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
std::vector<uint8> const challenge = harness.connection->onConnect();
|
||||
|
||||
CHECK(challenge.size() == proto::SERVER_HEADER_SIZE + 4);
|
||||
CHECK(challenge[0] == 0x00);
|
||||
CHECK(challenge[1] == 0x06);
|
||||
CHECK(ServerOpcode(challenge) == SMSG_AUTH_CHALLENGE);
|
||||
CHECK(harness.gateway.traced.size() == 1);
|
||||
CHECK(harness.gateway.traced[0] == std::make_pair(uint16(SMSG_AUTH_CHALLENGE), false));
|
||||
CHECK(harness.connection->GetRemoteAddress() == "127.0.0.1");
|
||||
}
|
||||
|
||||
void preAuthenticationWorldPacketsAreRejected()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
std::vector<uint8> const frame = ClientFrame(CMSG_KEEP_ALIVE, {});
|
||||
|
||||
harness.connection->onData(frame.data(), frame.size());
|
||||
|
||||
CHECK(harness.connection->closed());
|
||||
CHECK(harness.closeCalls == 1);
|
||||
CHECK(harness.gateway.delivered.empty());
|
||||
CHECK(harness.gateway.traced.size() == 1);
|
||||
CHECK(harness.gateway.traced[0] == std::make_pair(uint16(CMSG_KEEP_ALIVE), true));
|
||||
}
|
||||
|
||||
void authenticationFilterVetoSkipsLookupWithoutClosing()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
harness.gateway.filterResult = false;
|
||||
std::array<uint8, 20> const digest{};
|
||||
std::vector<uint8> const frame = AuthFrame(7, digest);
|
||||
|
||||
harness.connection->onData(frame.data(), frame.size());
|
||||
|
||||
CHECK(harness.gateway.filterCalls == 1);
|
||||
CHECK(harness.gateway.lookupCalls == 0);
|
||||
CHECK(!harness.connection->closed());
|
||||
}
|
||||
|
||||
void authenticationFilterVetoAllowsALaterAcceptedAttempt()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
BigNumber sessionKey = SuccessfulLookup(harness.gateway);
|
||||
uint32 const clientSeed = 0x55667788;
|
||||
std::vector<uint8> const challenge = harness.connection->onConnect();
|
||||
std::array<uint8, 20> const proof = MakeProof("ACCOUNT", clientSeed,
|
||||
ServerSeed(challenge), sessionKey);
|
||||
std::vector<uint8> const frame = AuthFrame(clientSeed, proof);
|
||||
|
||||
harness.gateway.filterResult = false;
|
||||
harness.connection->onData(frame.data(), frame.size());
|
||||
harness.gateway.filterResult = true;
|
||||
harness.connection->onData(frame.data(), frame.size());
|
||||
|
||||
CHECK(harness.gateway.filterCalls == 2);
|
||||
CHECK(harness.gateway.lookupCalls == 1);
|
||||
CHECK(harness.gateway.attachCalls == 1);
|
||||
CHECK(!harness.connection->closed());
|
||||
}
|
||||
|
||||
void lookupRejectionSendsStatusAndCloses()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
harness.gateway.lookup.status = proto::AuthStatus::UnknownAccount;
|
||||
std::vector<uint8> const challenge = harness.connection->onConnect();
|
||||
(void)challenge;
|
||||
std::array<uint8, 20> const digest{};
|
||||
std::vector<uint8> const frame = AuthFrame(7, digest);
|
||||
|
||||
harness.connection->onData(frame.data(), frame.size());
|
||||
|
||||
CHECK(harness.gateway.lookupCalls == 1);
|
||||
CHECK(harness.gateway.attachCalls == 0);
|
||||
CHECK(harness.sent.size() == 1);
|
||||
CHECK(ServerOpcode(harness.sent[0]) == SMSG_AUTH_RESPONSE);
|
||||
CHECK(harness.sent[0].size() == proto::SERVER_HEADER_SIZE + 1);
|
||||
CHECK(harness.sent[0][4] == uint8(proto::AuthStatus::UnknownAccount));
|
||||
CHECK(harness.connection->closed());
|
||||
CHECK(harness.closeCalls == 1);
|
||||
std::vector<std::pair<uint16, bool>> const expectedTraces = {
|
||||
{uint16(SMSG_AUTH_CHALLENGE), false},
|
||||
{uint16(CMSG_AUTH_SESSION), true},
|
||||
{uint16(SMSG_AUTH_RESPONSE), false}
|
||||
};
|
||||
CHECK(harness.gateway.traced == expectedTraces);
|
||||
}
|
||||
|
||||
void gatewayExceptionsCloseWithoutEscapingTheTransportBoundary()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
harness.gateway.throwOnLookup = true;
|
||||
harness.connection->onConnect();
|
||||
std::array<uint8, 20> const digest{};
|
||||
std::vector<uint8> const frame = AuthFrame(7, digest);
|
||||
|
||||
bool escaped = false;
|
||||
try
|
||||
{
|
||||
harness.connection->onData(frame.data(), frame.size());
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
escaped = true;
|
||||
}
|
||||
|
||||
CHECK(!escaped);
|
||||
CHECK(harness.gateway.lookupCalls == 1);
|
||||
CHECK(harness.connection->closed());
|
||||
CHECK(harness.closeCalls == 1);
|
||||
}
|
||||
|
||||
void traceExceptionsCloseWithoutEscapingTheTransportBoundary()
|
||||
{
|
||||
ConnectionHarness connectHarness;
|
||||
connectHarness.gateway.throwOnTrace = true;
|
||||
bool connectEscaped = false;
|
||||
try
|
||||
{
|
||||
connectHarness.connection->onConnect();
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
connectEscaped = true;
|
||||
}
|
||||
CHECK(!connectEscaped);
|
||||
CHECK(connectHarness.connection->closed());
|
||||
CHECK(connectHarness.closeCalls == 1);
|
||||
|
||||
ConnectionHarness dataHarness;
|
||||
dataHarness.connection->onConnect();
|
||||
dataHarness.gateway.throwOnTrace = true;
|
||||
std::vector<uint8> const frame = ClientFrame(CMSG_KEEP_ALIVE, {});
|
||||
bool dataEscaped = false;
|
||||
try
|
||||
{
|
||||
dataHarness.connection->onData(frame.data(), frame.size());
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
dataEscaped = true;
|
||||
}
|
||||
CHECK(!dataEscaped);
|
||||
CHECK(dataHarness.connection->closed());
|
||||
CHECK(dataHarness.closeCalls == 1);
|
||||
|
||||
ConnectionHarness sendHarness;
|
||||
sendHarness.connection->onConnect();
|
||||
sendHarness.gateway.throwOnTrace = true;
|
||||
WorldPacket packet(SMSG_PONG, 0);
|
||||
bool sendEscaped = false;
|
||||
try
|
||||
{
|
||||
sendHarness.connection->SendPacket(packet);
|
||||
}
|
||||
catch (std::runtime_error const&)
|
||||
{
|
||||
sendEscaped = true;
|
||||
}
|
||||
CHECK(!sendEscaped);
|
||||
CHECK(sendHarness.connection->closed());
|
||||
CHECK(sendHarness.closeCalls == 1);
|
||||
}
|
||||
|
||||
void closeDuringAttachDetachesThePublishedSession()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
BigNumber sessionKey = SuccessfulLookup(harness.gateway);
|
||||
uint32 const clientSeed = 0x1234ABCD;
|
||||
std::vector<uint8> const challenge = harness.connection->onConnect();
|
||||
std::array<uint8, 20> const proof = MakeProof("ACCOUNT", clientSeed,
|
||||
ServerSeed(challenge), sessionKey);
|
||||
std::vector<uint8> const auth = AuthFrame(clientSeed, proof);
|
||||
harness.gateway.duringAttach = [&harness]() { harness.connection->onClose(); };
|
||||
|
||||
harness.connection->onData(auth.data(), auth.size());
|
||||
|
||||
CHECK(harness.gateway.attachCalls == 1);
|
||||
CHECK(harness.gateway.detachCalls == 1);
|
||||
CHECK(harness.connection->closed());
|
||||
}
|
||||
|
||||
void invalidProofSendsFailureAndNeverAttaches()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
SuccessfulLookup(harness.gateway);
|
||||
harness.connection->onConnect();
|
||||
std::array<uint8, 20> const digest{};
|
||||
std::vector<uint8> const frame = AuthFrame(7, digest);
|
||||
|
||||
harness.connection->onData(frame.data(), frame.size());
|
||||
|
||||
CHECK(harness.gateway.lookupCalls == 1);
|
||||
CHECK(harness.gateway.attachCalls == 0);
|
||||
CHECK(harness.sent.size() == 1);
|
||||
CHECK(harness.sent[0][4] == uint8(proto::AuthStatus::Failed));
|
||||
CHECK(harness.connection->closed());
|
||||
}
|
||||
|
||||
void successfulAuthenticationInitializesCryptBeforeAttach()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
harness.gateway.sendDuringAttach = true;
|
||||
BigNumber sessionKey = Authenticate(harness);
|
||||
|
||||
CHECK(harness.gateway.attachedRequest.build == 8606);
|
||||
CHECK(harness.gateway.attachedRequest.unknown == 0x12345678);
|
||||
CHECK(harness.gateway.attachedRequest.account == "ACCOUNT");
|
||||
CHECK(harness.gateway.attachedRequest.peerAddress == "127.0.0.1");
|
||||
CHECK(harness.gateway.attachedRequest.addonData == std::vector<uint8>({0xCA, 0xFE}));
|
||||
CHECK(harness.sent.size() == 1);
|
||||
|
||||
HeaderCipher cipher(sessionKey);
|
||||
cipher.DecryptServerHeader(harness.sent[0]);
|
||||
CHECK(ServerOpcode(harness.sent[0]) == SMSG_ADDON_INFO);
|
||||
CHECK(harness.gateway.traced.back() == std::make_pair(uint16(SMSG_ADDON_INFO), false));
|
||||
}
|
||||
|
||||
void attachFailureSendsEncryptedSystemErrorWithoutPublishingASession()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
harness.gateway.attachResult = proto::INVALID_SESSION_ID;
|
||||
BigNumber sessionKey = SuccessfulLookup(harness.gateway);
|
||||
uint32 const clientSeed = 0x11223344;
|
||||
std::vector<uint8> const challenge = harness.connection->onConnect();
|
||||
std::array<uint8, 20> const proof = MakeProof("ACCOUNT", clientSeed,
|
||||
ServerSeed(challenge), sessionKey);
|
||||
std::vector<uint8> const auth = AuthFrame(clientSeed, proof);
|
||||
|
||||
harness.connection->onData(auth.data(), auth.size());
|
||||
|
||||
CHECK(harness.gateway.attachCalls == 1);
|
||||
CHECK(harness.gateway.detachCalls == 0);
|
||||
CHECK(harness.sent.size() == 1);
|
||||
if (harness.sent.size() == 1)
|
||||
{
|
||||
HeaderCipher cipher(sessionKey);
|
||||
cipher.DecryptServerHeader(harness.sent[0]);
|
||||
CHECK(ServerOpcode(harness.sent[0]) == SMSG_AUTH_RESPONSE);
|
||||
CHECK(harness.sent[0][4] == uint8(proto::AuthStatus::SystemError));
|
||||
}
|
||||
CHECK(harness.connection->closed());
|
||||
}
|
||||
|
||||
void authenticatedPacketsStayOpaqueToTheConnection()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
BigNumber sessionKey = Authenticate(harness);
|
||||
HeaderCipher cipher(sessionKey);
|
||||
|
||||
std::vector<uint8> ping = ClientFrame(CMSG_PING, {0x01, 0x02});
|
||||
cipher.EncryptClientHeader(ping);
|
||||
harness.connection->onData(ping.data(), ping.size());
|
||||
|
||||
std::vector<uint8> keepAlive = ClientFrame(CMSG_KEEP_ALIVE, {});
|
||||
cipher.EncryptClientHeader(keepAlive);
|
||||
harness.connection->onData(keepAlive.data(), keepAlive.size());
|
||||
|
||||
CHECK(harness.gateway.delivered.size() == 2);
|
||||
CHECK(harness.gateway.delivered[0] == CMSG_PING);
|
||||
CHECK(harness.gateway.delivered[1] == CMSG_KEEP_ALIVE);
|
||||
CHECK(!harness.connection->closed());
|
||||
std::vector<std::pair<uint16, bool>> const expectedTraces = {
|
||||
{uint16(SMSG_AUTH_CHALLENGE), false},
|
||||
{uint16(CMSG_AUTH_SESSION), true},
|
||||
{uint16(CMSG_PING), true},
|
||||
{uint16(CMSG_KEEP_ALIVE), true}
|
||||
};
|
||||
CHECK(harness.gateway.traced == expectedTraces);
|
||||
}
|
||||
|
||||
void coalescedAuthenticationActivatesCryptBeforeTheNextFrame()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
BigNumber sessionKey = SuccessfulLookup(harness.gateway);
|
||||
uint32 const clientSeed = 0x10203040;
|
||||
std::vector<uint8> const challenge = harness.connection->onConnect();
|
||||
std::array<uint8, 20> const proof = MakeProof("ACCOUNT", clientSeed,
|
||||
ServerSeed(challenge), sessionKey);
|
||||
std::vector<uint8> input = AuthFrame(clientSeed, proof);
|
||||
|
||||
HeaderCipher cipher(sessionKey);
|
||||
std::vector<uint8> keepAlive = ClientFrame(CMSG_KEEP_ALIVE, {});
|
||||
cipher.EncryptClientHeader(keepAlive);
|
||||
input.insert(input.end(), keepAlive.begin(), keepAlive.end());
|
||||
|
||||
harness.connection->onData(input.data(), input.size());
|
||||
|
||||
CHECK(harness.gateway.attachCalls == 1);
|
||||
CHECK(harness.gateway.delivered.size() == 1);
|
||||
CHECK(harness.gateway.delivered[0] == CMSG_KEEP_ALIVE);
|
||||
CHECK(!harness.connection->closed());
|
||||
}
|
||||
|
||||
void fragmentedEncryptedHeadersKeepCipherStateSynchronized()
|
||||
{
|
||||
for (std::size_t split = 1; split < proto::CLIENT_HEADER_SIZE; ++split)
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
BigNumber sessionKey = Authenticate(harness);
|
||||
HeaderCipher cipher(sessionKey);
|
||||
std::vector<uint8> keepAlive = ClientFrame(CMSG_KEEP_ALIVE, {0x42});
|
||||
cipher.EncryptClientHeader(keepAlive);
|
||||
|
||||
harness.connection->onData(keepAlive.data(), split);
|
||||
CHECK(harness.gateway.delivered.empty());
|
||||
CHECK(!harness.connection->closed());
|
||||
harness.connection->onData(keepAlive.data() + split, keepAlive.size() - split);
|
||||
|
||||
CHECK(harness.gateway.delivered.size() == 1);
|
||||
CHECK(harness.gateway.delivered[0] == CMSG_KEEP_ALIVE);
|
||||
CHECK(!harness.connection->closed());
|
||||
}
|
||||
}
|
||||
|
||||
void invalidPostAuthenticationOpcodeClosesInsteadOfDropping()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
BigNumber sessionKey = Authenticate(harness);
|
||||
HeaderCipher cipher(sessionKey);
|
||||
std::vector<uint8> invalid = ClientFrame(NUM_MSG_TYPES, {});
|
||||
cipher.EncryptClientHeader(invalid);
|
||||
|
||||
harness.connection->onData(invalid.data(), invalid.size());
|
||||
|
||||
CHECK(harness.gateway.delivered.empty());
|
||||
CHECK(harness.connection->closed());
|
||||
CHECK(harness.closeCalls == 1);
|
||||
}
|
||||
|
||||
void authenticationAddonTailReconstructsAtPositionZero()
|
||||
{
|
||||
std::array<uint8, 20> digest{};
|
||||
for (std::size_t i = 0; i < digest.size(); ++i)
|
||||
digest[i] = uint8(i);
|
||||
|
||||
WorldPacket original(CMSG_AUTH_SESSION, 64);
|
||||
original << uint32(8606) << uint32(7) << std::string("ACCOUNT") << uint32(9);
|
||||
original.append(digest.data(), digest.size());
|
||||
uint8 const addon[] = {0x04, 0x00, 0x00, 0x00, 0x78, 0x9C};
|
||||
original.append(addon, sizeof(addon));
|
||||
|
||||
uint32 build;
|
||||
uint32 unknown;
|
||||
uint32 clientSeed;
|
||||
std::string account;
|
||||
std::array<uint8, 20> parsedDigest{};
|
||||
original >> build >> unknown >> account >> clientSeed;
|
||||
original.read(parsedDigest.data(), parsedDigest.size());
|
||||
std::vector<uint8> const tail(original.contents() + original.rpos(),
|
||||
original.contents() + original.size());
|
||||
|
||||
WorldPacket reconstructed(CMSG_AUTH_SESSION, tail.size());
|
||||
reconstructed.append(tail.data(), tail.size());
|
||||
|
||||
CHECK(reconstructed.rpos() == 0);
|
||||
CHECK(reconstructed.size() == sizeof(addon));
|
||||
CHECK(std::equal(reconstructed.contents(),
|
||||
reconstructed.contents() + reconstructed.size(), addon));
|
||||
}
|
||||
|
||||
void repeatedAuthenticationClosesWithoutSecondLookup()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
BigNumber sessionKey = Authenticate(harness);
|
||||
HeaderCipher cipher(sessionKey);
|
||||
std::array<uint8, 20> const digest{};
|
||||
std::vector<uint8> repeated = AuthFrame(9, digest);
|
||||
cipher.EncryptClientHeader(repeated);
|
||||
|
||||
harness.connection->onData(repeated.data(), repeated.size());
|
||||
|
||||
CHECK(harness.gateway.lookupCalls == 1);
|
||||
CHECK(harness.connection->closed());
|
||||
CHECK(harness.closeCalls == 1);
|
||||
}
|
||||
|
||||
void closeDetachesOnceAndLateSendsAreIgnored()
|
||||
{
|
||||
ConnectionHarness harness;
|
||||
Authenticate(harness);
|
||||
CHECK(harness.gateway.retainedLink != nullptr);
|
||||
|
||||
harness.connection->onClose();
|
||||
harness.connection->onClose();
|
||||
std::size_t const tracesBeforeLateSend = harness.gateway.traced.size();
|
||||
std::size_t const sendsBeforeLateSend = harness.sent.size();
|
||||
WorldPacket late(SMSG_PONG, 0);
|
||||
harness.gateway.retainedLink->SendPacket(late);
|
||||
|
||||
CHECK(harness.gateway.detachCalls == 1);
|
||||
CHECK(harness.gateway.traced.size() == tracesBeforeLateSend);
|
||||
CHECK(harness.sent.size() == sendsBeforeLateSend);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
fragmentedFrameDecodesOnce();
|
||||
combinedFramesPreserveOrder();
|
||||
splitHeadersDecryptExactlyOnce();
|
||||
malformedFramesAreRejected();
|
||||
serverFramesUseTheFixed243Header();
|
||||
connectionChallengeHasTheExpectedShape();
|
||||
preAuthenticationWorldPacketsAreRejected();
|
||||
authenticationFilterVetoSkipsLookupWithoutClosing();
|
||||
authenticationFilterVetoAllowsALaterAcceptedAttempt();
|
||||
lookupRejectionSendsStatusAndCloses();
|
||||
gatewayExceptionsCloseWithoutEscapingTheTransportBoundary();
|
||||
traceExceptionsCloseWithoutEscapingTheTransportBoundary();
|
||||
closeDuringAttachDetachesThePublishedSession();
|
||||
invalidProofSendsFailureAndNeverAttaches();
|
||||
successfulAuthenticationInitializesCryptBeforeAttach();
|
||||
attachFailureSendsEncryptedSystemErrorWithoutPublishingASession();
|
||||
authenticatedPacketsStayOpaqueToTheConnection();
|
||||
coalescedAuthenticationActivatesCryptBeforeTheNextFrame();
|
||||
fragmentedEncryptedHeadersKeepCipherStateSynchronized();
|
||||
invalidPostAuthenticationOpcodeClosesInsteadOfDropping();
|
||||
authenticationAddonTailReconstructsAtPositionZero();
|
||||
repeatedAuthenticationClosesWithoutSecondLookup();
|
||||
closeDetachesOnceAndLateSendsAreIgnored();
|
||||
return mangos::test::failures == 0 ? 0 : 1;
|
||||
}
|
||||
110
tests/SessionMailboxTests.cpp
Normal file
110
tests/SessionMailboxTests.cpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#include "TestSupport.hpp"
|
||||
|
||||
#include "SessionMailbox.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::unique_ptr<WorldPacket> Packet(uint16 opcode, uint8 value)
|
||||
{
|
||||
auto packet = std::make_unique<WorldPacket>(opcode, 1);
|
||||
*packet << value;
|
||||
return packet;
|
||||
}
|
||||
|
||||
void mailboxTransfersPacketsInFifoOrder()
|
||||
{
|
||||
SessionMailbox mailbox;
|
||||
CHECK(mailbox.Enqueue(Packet(1, 0x11)));
|
||||
CHECK(mailbox.Enqueue(Packet(2, 0x22)));
|
||||
|
||||
WorldPacket* raw = nullptr;
|
||||
CHECK(mailbox.Next(raw));
|
||||
std::unique_ptr<WorldPacket> first(raw);
|
||||
CHECK(first->GetOpcode() == 1);
|
||||
CHECK((*first)[0] == 0x11);
|
||||
|
||||
CHECK(mailbox.Next(raw));
|
||||
std::unique_ptr<WorldPacket> second(raw);
|
||||
CHECK(second->GetOpcode() == 2);
|
||||
CHECK((*second)[0] == 0x22);
|
||||
CHECK(!mailbox.Next(raw));
|
||||
}
|
||||
|
||||
void closedMailboxRejectsNewOwnership()
|
||||
{
|
||||
SessionMailbox mailbox;
|
||||
mailbox.Close();
|
||||
|
||||
CHECK(mailbox.IsClosed());
|
||||
CHECK(!mailbox.Enqueue(Packet(3, 0x33)));
|
||||
WorldPacket* raw = nullptr;
|
||||
CHECK(!mailbox.Next(raw));
|
||||
}
|
||||
|
||||
void closeRacingProducersLeavesNoPostClosePackets()
|
||||
{
|
||||
SessionMailbox mailbox;
|
||||
std::atomic<bool> start{false};
|
||||
std::atomic<unsigned> accepted{0};
|
||||
std::vector<std::thread> producers;
|
||||
for (unsigned producer = 0; producer < 4; ++producer)
|
||||
{
|
||||
producers.emplace_back([&mailbox, &start, &accepted, producer]()
|
||||
{
|
||||
while (!start.load())
|
||||
std::this_thread::yield();
|
||||
for (unsigned packet = 0; packet < 200; ++packet)
|
||||
{
|
||||
if (mailbox.Enqueue(Packet(uint16(producer + 1), uint8(packet))))
|
||||
accepted.fetch_add(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
start.store(true);
|
||||
mailbox.Close();
|
||||
for (std::thread& producer : producers)
|
||||
producer.join();
|
||||
|
||||
unsigned drained = 0;
|
||||
WorldPacket* raw = nullptr;
|
||||
while (mailbox.Next(raw))
|
||||
{
|
||||
std::unique_ptr<WorldPacket> packet(raw);
|
||||
++drained;
|
||||
}
|
||||
CHECK(drained == accepted.load());
|
||||
CHECK(!mailbox.Enqueue(Packet(9, 0x99)));
|
||||
}
|
||||
|
||||
void detachedRegistryRouteCannotReachItsReplacement()
|
||||
{
|
||||
auto oldMailbox = std::make_shared<SessionMailbox>();
|
||||
std::shared_ptr<SessionMailbox> retainedDelivery = oldMailbox;
|
||||
oldMailbox->Close();
|
||||
|
||||
auto replacement = std::make_shared<SessionMailbox>();
|
||||
CHECK(!retainedDelivery->Enqueue(Packet(4, 0x44)));
|
||||
CHECK(replacement->Enqueue(Packet(5, 0x55)));
|
||||
|
||||
WorldPacket* raw = nullptr;
|
||||
CHECK(replacement->Next(raw));
|
||||
std::unique_ptr<WorldPacket> packet(raw);
|
||||
CHECK(packet->GetOpcode() == 5);
|
||||
CHECK(!replacement->Next(raw));
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
mailboxTransfersPacketsInFifoOrder();
|
||||
closedMailboxRejectsNewOwnership();
|
||||
closeRacingProducersLeavesNoPostClosePackets();
|
||||
detachedRegistryRouteCannotReachItsReplacement();
|
||||
return mangos::test::failures == 0 ? 0 : 1;
|
||||
}
|
||||
78
tests/TestSupport.hpp
Normal file
78
tests/TestSupport.hpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#ifndef MANGOS_TEST_SUPPORT_HPP
|
||||
#define MANGOS_TEST_SUPPORT_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <initializer_list>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace mangos::test
|
||||
{
|
||||
inline int failures = 0;
|
||||
|
||||
inline void check(bool condition, char const* expression, char const* file, int line)
|
||||
{
|
||||
if (condition)
|
||||
return;
|
||||
|
||||
++failures;
|
||||
std::cerr << file << ':' << line << ": CHECK failed: " << expression << '\n';
|
||||
}
|
||||
|
||||
inline std::string bytesToHex(uint8_t const* data, std::size_t length)
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << std::hex << std::setfill('0');
|
||||
for (std::size_t i = 0; i < length; ++i)
|
||||
stream << std::setw(2) << static_cast<unsigned>(data[i]);
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
inline void checkBytes(uint8_t const* actual, std::size_t actualLength,
|
||||
std::initializer_list<uint8_t> expected,
|
||||
char const* expression, char const* file, int line)
|
||||
{
|
||||
bool const equalLength = actualLength == expected.size();
|
||||
bool equalBytes = equalLength;
|
||||
std::size_t i = 0;
|
||||
for (uint8_t byte : expected)
|
||||
{
|
||||
if (!actual || actual[i++] != byte)
|
||||
equalBytes = false;
|
||||
}
|
||||
|
||||
if (equalBytes)
|
||||
return;
|
||||
|
||||
++failures;
|
||||
std::string const expectedHex = bytesToHex(expected.begin(), expected.size());
|
||||
std::string const actualHex = actual ? bytesToHex(actual, actualLength) : "<null>";
|
||||
std::cerr << file << ':' << line << ": CHECK_BYTES failed: " << expression
|
||||
<< " expected=" << expectedHex << " actual=" << actualHex << '\n';
|
||||
}
|
||||
|
||||
inline void checkHex(uint8_t const* actual, std::size_t actualLength,
|
||||
std::string const& expectedHex,
|
||||
char const* expression, char const* file, int line)
|
||||
{
|
||||
std::string const actualHex = actual ? bytesToHex(actual, actualLength) : "<null>";
|
||||
if (actualHex == expectedHex)
|
||||
return;
|
||||
|
||||
++failures;
|
||||
std::cerr << file << ':' << line << ": CHECK_HEX failed: " << expression
|
||||
<< " expected=" << expectedHex << " actual=" << actualHex << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
#define CHECK(expression) \
|
||||
::mangos::test::check(static_cast<bool>(expression), #expression, __FILE__, __LINE__)
|
||||
#define CHECK_BYTES(actual, length, ...) \
|
||||
::mangos::test::checkBytes((actual), (length), __VA_ARGS__, #actual, __FILE__, __LINE__)
|
||||
#define CHECK_HEX(actual, length, expected) \
|
||||
::mangos::test::checkHex((actual), (length), (expected), #actual, __FILE__, __LINE__)
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue