Core/Movement: ported time synchronization (commits: 975f1e364a and 50d32fe493)

This commit is contained in:
Ovahlord
2019-04-26 16:48:09 +02:00
parent 433bd5a1bb
commit ee69943717
10 changed files with 212 additions and 127 deletions

View File

@@ -22,13 +22,20 @@
#include "Define.h"
#include <chrono>
inline uint32 getMSTime()
inline std::chrono::steady_clock::time_point GetApplicationStartTime()
{
using namespace std::chrono;
static const steady_clock::time_point ApplicationStartTime = steady_clock::now();
return uint32(duration_cast<milliseconds>(steady_clock::now() - ApplicationStartTime).count());
return ApplicationStartTime;
}
inline uint32 getMSTime()
{
using namespace std::chrono;
return uint32(duration_cast<milliseconds>(steady_clock::now() - GetApplicationStartTime()).count());
}
inline uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
@@ -40,6 +47,15 @@ inline uint32 getMSTimeDiff(uint32 oldMSTime, uint32 newMSTime)
return newMSTime - oldMSTime;
}
inline uint32 getMSTimeDiff(uint32 oldMSTime, std::chrono::steady_clock::time_point newTime)
{
using namespace std::chrono;
uint32 newMSTime = uint32(duration_cast<milliseconds>(newTime - GetApplicationStartTime()).count());
return getMSTimeDiff(oldMSTime, newMSTime);
}
inline uint32 GetMSTimeDiffToNow(uint32 oldMSTime)
{
return getMSTimeDiff(oldMSTime, getMSTime());

View File

@@ -381,10 +381,6 @@ Player::Player(WorldSession* session): Unit(true)
m_ChampioningFaction = 0;
m_timeSyncTimer = 0;
m_timeSyncClient = 0;
m_timeSyncServer = 0;
for (uint8 i = 0; i < MAX_POWERS_PER_CLASS; ++i)
m_powerFraction[i] = 0;
@@ -1260,14 +1256,6 @@ void Player::Update(uint32 p_time)
m_zoneUpdateTimer -= p_time;
}
if (m_timeSyncTimer > 0)
{
if (p_time >= m_timeSyncTimer)
SendTimeSync();
else
m_timeSyncTimer -= p_time;
}
if (IsAlive())
{
m_regenTimer += p_time;
@@ -23850,8 +23838,8 @@ void Player::SendInitialPacketsAfterAddToMap()
GetZoneAndAreaId(newzone, newarea);
UpdateZone(newzone, newarea); // also call SendInitWorldStates();
ResetTimeSync();
SendTimeSync();
GetSession()->ResetTimeSync();
GetSession()->SendTimeSync();
GetSession()->SendLoadCUFProfiles();
@@ -27417,29 +27405,6 @@ void Player::LoadActions(PreparedQueryResult result)
SendActionButtons(1);
}
void Player::ResetTimeSync()
{
m_timeSyncTimer = 0;
m_timeSyncClient = 0;
m_timeSyncServer = GameTime::GetGameTimeMS();
}
void Player::SendTimeSync()
{
m_timeSyncQueue.push(m_movementCounter++);
WorldPacket data(SMSG_TIME_SYNC_REQ, 4);
data << uint32(m_timeSyncQueue.back());
SendDirectMessage(&data);
// Schedule next sync in 10 sec
m_timeSyncTimer = 10000;
m_timeSyncServer = GameTime::GetGameTimeMS();
if (m_timeSyncQueue.size() > 3)
TC_LOG_ERROR("network", "Not received CMSG_TIME_SYNC_RESP for over 30 seconds from player %u (%s), possible cheater", GetGUID().GetCounter(), GetName().c_str());
}
void Player::SetReputation(uint32 factionentry, uint32 value)
{
GetReputationMgr().SetReputation(sFactionStore.LookupEntry(factionentry), value);

View File

@@ -2597,9 +2597,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
ItemDurationList m_itemDuration;
GuidUnorderedSet m_itemSoulboundTradeable;
void ResetTimeSync();
void SendTimeSync();
ResurrectionData* _resurrectionData;
WorldSession* m_session;
@@ -2743,11 +2740,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player>
uint32 m_ChampioningFaction;
std::queue<uint32> m_timeSyncQueue;
uint32 m_timeSyncTimer;
uint32 m_timeSyncClient;
uint32 m_timeSyncServer;
InstanceTimeMap _instanceResetTimes;
uint32 _pendingBindId;
uint32 _pendingBindTimer;

View File

@@ -1322,27 +1322,6 @@ void WorldSession::HandleSetTitleOpcode(WorldPacket& recvData)
GetPlayer()->SetUInt32Value(PLAYER_CHOSEN_TITLE, title);
}
void WorldSession::HandleTimeSyncResp(WorldPacket& recvData)
{
TC_LOG_DEBUG("network", "CMSG_TIME_SYNC_RESP");
uint32 counter, clientTicks;
recvData >> counter >> clientTicks;
if (counter != _player->m_timeSyncQueue.front())
TC_LOG_ERROR("network", "Wrong time sync counter from player %s (cheater?)", _player->GetName().c_str());
TC_LOG_DEBUG("network", "Time sync received: counter %u, client ticks %u, time since last sync %u", counter, clientTicks, clientTicks - _player->m_timeSyncClient);
uint32 ourTicks = clientTicks + (GameTime::GetGameTimeMS() - _player->m_timeSyncServer);
// diff should be small
TC_LOG_DEBUG("network", "Our ticks: %u, diff %u, latency %u", ourTicks, ourTicks - clientTicks, GetLatency());
_player->m_timeSyncClient = clientTicks;
_player->m_timeSyncQueue.pop();
}
void WorldSession::HandleResetInstancesOpcode(WorldPacket& /*recvData*/)
{
TC_LOG_DEBUG("network", "WORLD: CMSG_RESET_INSTANCES");

View File

@@ -34,8 +34,9 @@
#include "Vehicle.h"
#include "GameTime.h"
#include "SpellAuraEffects.h"
#define MOVEMENT_PACKET_TIME_DELAY 0
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics.hpp>
void WorldSession::HandleMoveWorldportAckOpcode(WorldPacket & /*recvData*/)
{
@@ -387,13 +388,17 @@ void WorldSession::HandleMovementOpcodes(WorldPacket& recvPacket)
plrMover->SetInWater(!plrMover->IsInWater() || plrMover->GetMap()->IsUnderWater(plrMover->GetPhaseShift(), movementInfo.pos.GetPositionX(), movementInfo.pos.GetPositionY(), movementInfo.pos.GetPositionZ()));
}
uint32 mstime = GameTime::GetGameTimeMS();
/*----------------------*/
if (m_clientTimeDelay == 0)
m_clientTimeDelay = mstime - movementInfo.time;
/* process position-change */
movementInfo.time = movementInfo.time + m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
int64 movementTime = (int64)movementInfo.time + _timeSyncClockDelta;
if (_timeSyncClockDelta == 0 || movementTime < 0 || movementTime > 0xFFFFFFFF)
{
TC_LOG_WARN("misc", "The computed movement time using clockDelta is erronous. Using fallback instead");
movementInfo.time = GameTime::GetGameTimeMS();
}
else
{
movementInfo.time = (uint32)movementTime;
}
movementInfo.guid = mover->GetGUID();
mover->m_movementInfo = movementInfo;
@@ -701,3 +706,76 @@ void WorldSession::HandleMoveTimeSkippedOpcode(WorldPacket& recvData)
data << time;
GetPlayer()->SendMessageToSet(&data, false);
}
void WorldSession::HandleTimeSyncResp(WorldPacket& recvData)
{
TC_LOG_DEBUG("network", "CMSG_TIME_SYNC_RESP");
uint32 counter, clientTimestamp;
recvData >> counter >> clientTimestamp;
if (_pendingTimeSyncRequests.count(counter) == 0)
return;
uint32 serverTimeAtSent = _pendingTimeSyncRequests.at(counter);
_pendingTimeSyncRequests.erase(counter);
// time it took for the request to travel to the client, for the client to process it and reply and for response to travel back to the server.
// we are going to make 2 assumptions:
// 1) we assume that the request processing time equals 0.
// 2) we assume that the packet took as much time to travel from server to client than it took to travel from client to server.
uint32 roundTripDuration = getMSTimeDiff(serverTimeAtSent, recvData.GetReceivedTime());
uint32 lagDelay = roundTripDuration / 2;
/*
clockDelta = serverTime - clientTime
where
serverTime: time that was displayed on the clock of the SERVER at the moment when the client processed the SMSG_TIME_SYNC_REQUEST packet.
clientTime: time that was displayed on the clock of the CLIENT at the moment when the client processed the SMSG_TIME_SYNC_REQUEST packet.
Once clockDelta has been computed, we can compute the time of an event on server clock when we know the time of that same event on the client clock,
using the following relation:
serverTime = clockDelta + clientTime
*/
int64 clockDelta = (int64)(serverTimeAtSent + lagDelay) - (int64)clientTimestamp;
_timeSyncClockDeltaQueue.push_back(std::pair<int64, uint32>(clockDelta, roundTripDuration));
ComputeNewClockDelta();
}
void WorldSession::ComputeNewClockDelta()
{
// implementation of the technique described here: https://web.archive.org/web/20180430214420/http://www.mine-control.com/zack/timesync/timesync.html
// to reduce the skew induced by dropped TCP packets that get resent.
using namespace boost::accumulators;
accumulator_set<uint32, features<tag::mean, tag::median, tag::variance(lazy)> > latencyAccumulator;
for (auto pair : _timeSyncClockDeltaQueue)
latencyAccumulator(pair.second);
uint32 latencyMedian = static_cast<uint32>(std::round(median(latencyAccumulator)));
uint32 latencyStandardDeviation = static_cast<uint32>(std::round(sqrt(variance(latencyAccumulator))));
accumulator_set<int64, features<tag::mean> > clockDeltasAfterFiltering;
uint32 sampleSizeAfterFiltering = 0;
for (auto pair : _timeSyncClockDeltaQueue)
{
if (pair.second < latencyStandardDeviation + latencyMedian) {
clockDeltasAfterFiltering(pair.first);
sampleSizeAfterFiltering++;
}
}
if (sampleSizeAfterFiltering != 0)
{
int64 meanClockDelta = static_cast<int64>(std::round(mean(clockDeltasAfterFiltering)));
if (std::abs(meanClockDelta - _timeSyncClockDelta) > 25)
_timeSyncClockDelta = meanClockDelta;
}
else if (_timeSyncClockDelta == 0)
{
std::pair<int64, uint32> back = _timeSyncClockDeltaQueue.back();
_timeSyncClockDelta = back.first;
}
}

View File

@@ -539,7 +539,7 @@ void OpcodeTable::Initialize()
DEFINE_OPCODE_HANDLER(CMSG_TELEPORT_TO_UNIT, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
DEFINE_OPCODE_HANDLER(CMSG_TEXT_EMOTE, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTextEmoteOpcode );
DEFINE_OPCODE_HANDLER(CMSG_TIME_ADJUSTMENT_RESPONSE, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
DEFINE_OPCODE_HANDLER(CMSG_TIME_SYNC_RESP, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTimeSyncResp );
DEFINE_OPCODE_HANDLER(CMSG_TIME_SYNC_RESP, STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleTimeSyncResp );
DEFINE_OPCODE_HANDLER(CMSG_TIME_SYNC_RESP_FAILED, STATUS_UNHANDLED, PROCESS_INPLACE, &WorldSession::Handle_NULL );
DEFINE_OPCODE_HANDLER(CMSG_TOGGLE_PVP, STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandleTogglePvP );
DEFINE_OPCODE_HANDLER(CMSG_TOTEM_DESTROYED, STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTotemDestroyed );

View File

@@ -22,6 +22,7 @@
#include "Common.h"
#include "Opcodes.h"
#include "ByteBuffer.h"
#include <chrono>
struct z_stream_s;
@@ -39,6 +40,10 @@ class WorldPacket : public ByteBuffer
{
}
WorldPacket(WorldPacket&& packet, std::chrono::steady_clock::time_point receivedTime) : ByteBuffer(std::move(packet)), m_opcode(packet.m_opcode), m_receivedTime(receivedTime)
{
}
WorldPacket(WorldPacket const& right) : ByteBuffer(right), m_opcode(right.m_opcode)
{
}
@@ -69,10 +74,13 @@ class WorldPacket : public ByteBuffer
void Compress(z_stream_s* compressionStream);
void Compress(z_stream_s* compressionStream, WorldPacket const* source);
std::chrono::steady_clock::time_point GetReceivedTime() const { return m_receivedTime; }
protected:
Opcodes m_opcode;
void Compress(void* dst, uint32 *dst_size, const void* src, int src_size);
z_stream_s* _compressionStream;
std::chrono::steady_clock::time_point m_receivedTime; // only set for a specific set of opcodes, for performance reasons.
};
#endif

View File

@@ -124,7 +124,6 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 battlenetAccoun
m_sessionDbcLocale(sWorld->GetAvailableDbcLocale(locale)),
m_sessionDbLocaleIndex(locale),
m_latency(0),
m_clientTimeDelay(0),
m_TutorialsChanged(TUTORIALS_FLAG_NONE),
_filterAddonMessages(false),
recruiterId(recruiter),
@@ -132,10 +131,16 @@ WorldSession::WorldSession(uint32 id, std::string&& name, uint32 battlenetAccoun
_RBACData(nullptr),
expireTime(60000), // 1 min after socket loss, session is deleted
forceExit(false),
m_currentBankerGUID()
m_currentBankerGUID(),
_timeSyncClockDeltaQueue(6),
_timeSyncClockDelta(0),
_pendingTimeSyncRequests()
{
memset(m_Tutorials, 0, sizeof(m_Tutorials));
_timeSyncNextCounter = 0;
_timeSyncTimer = 0;
if (sock)
{
m_Address = sock->GetRemoteIpAddress().to_string();
@@ -431,11 +436,23 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
if (m_Socket && m_Socket->IsOpen() && _warden)
_warden->Update();
if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter
{
// Send time sync packet every 10s.
if (_timeSyncTimer > 0)
{
if (diff >= _timeSyncTimer)
SendTimeSync();
else
_timeSyncTimer -= diff;
}
}
ProcessQueryCallbacks();
//check if we are safe to proceed with logout
//logout procedure should happen only in World::UpdateSessions() method!!!
if (updater.ProcessLogout())
if (updater.ProcessUnsafe())
{
time_t currTime = time(nullptr);
///- If necessary, log the player out
@@ -1481,3 +1498,22 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co
WorldSession::DosProtection::DosProtection(WorldSession* s) : Session(s), _policy((Policy)sWorld->getIntConfig(CONFIG_PACKET_SPOOF_POLICY))
{
}
void WorldSession::ResetTimeSync()
{
_timeSyncNextCounter = 0;
_pendingTimeSyncRequests.clear();
}
void WorldSession::SendTimeSync()
{
WorldPacket data(SMSG_TIME_SYNC_REQ, 4);
data << uint32(_timeSyncNextCounter);
SendPacket(&data);
_pendingTimeSyncRequests[_timeSyncNextCounter] = getMSTime();
// Schedule next sync in 10 sec (except for the 2 first packets, which are spaced by only 5s)
_timeSyncTimer = _timeSyncNextCounter == 0 ? 5000 : 10000;
_timeSyncNextCounter++;
}

View File

@@ -29,7 +29,9 @@
#include "ObjectGuid.h"
#include "QueryCallbackProcessor.h"
#include "SharedDefines.h"
#include <map>
#include <unordered_map>
#include <boost/circular_buffer.hpp>
class BigNumber;
class Creature;
@@ -180,7 +182,7 @@ public:
virtual ~PacketFilter() { }
virtual bool Process(WorldPacket* /*packet*/) { return true; }
virtual bool ProcessLogout() const { return true; }
virtual bool ProcessUnsafe() const { return true; }
protected:
WorldSession* const m_pSession;
@@ -198,7 +200,7 @@ public:
virtual bool Process(WorldPacket* packet) override;
//in Map::Update() we do not process player logout!
virtual bool ProcessLogout() const override { return false; }
virtual bool ProcessUnsafe() const override { return false; }
};
//class used to filer only thread-unsafe packets from queue
@@ -444,7 +446,6 @@ class TC_GAME_API WorldSession
uint32 GetLatency() const { return m_latency; }
void SetLatency(uint32 latency) { m_latency = latency; }
void ResetClientTimeDelay() { m_clientTimeDelay = 0; }
std::atomic<int32> m_timeOutTime;
@@ -466,6 +467,10 @@ class TC_GAME_API WorldSession
z_stream_s* GetCompressionStream() { return _compressionStream; }
// Time Synchronisation
void ResetTimeSync();
void SendTimeSync();
public: // opcodes handlers
void Handle_NULL(WorldPacket& recvPacket); // not used
@@ -1163,7 +1168,6 @@ class TC_GAME_API WorldSession
LocaleConstant m_sessionDbcLocale;
LocaleConstant m_sessionDbLocaleIndex;
std::atomic<uint32> m_latency;
std::atomic<uint32> m_clientTimeDelay;
AccountData m_accountData[NUM_ACCOUNT_DATA_TYPES];
uint32 m_Tutorials[MAX_ACCOUNT_TUTORIAL_VALUES];
uint8 m_TutorialsChanged;
@@ -1179,6 +1183,14 @@ class TC_GAME_API WorldSession
bool forceExit;
ObjectGuid m_currentBankerGUID;
boost::circular_buffer<std::pair<int64, uint32>> _timeSyncClockDeltaQueue; // first member: clockDelta. Second member: latency of the packet exchange that was used to compute that clockDelta.
int64 _timeSyncClockDelta;
void ComputeNewClockDelta();
std::map<uint32, uint32> _pendingTimeSyncRequests; // key: counter. value: server time when packet with that counter was sent.
uint32 _timeSyncNextCounter;
uint32 _timeSyncTimer;
WorldSession(WorldSession const& right) = delete;
WorldSession& operator=(WorldSession const& right) = delete;
};

View File

@@ -339,6 +339,7 @@ WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler()
Opcodes opcode = Opcodes(header->cmd);
WorldPacket packet(opcode, std::move(_packetBuffer));
WorldPacket* packetToQueue;
if (sPacketLog->CanLogPacket())
sPacketLog->LogPacket(packet, CLIENT_TO_SERVER, GetRemoteIpAddress(), GetRemotePort());
@@ -382,58 +383,59 @@ WorldSocket::ReadDataHandlerResult WorldSocket::ReadDataHandler()
TC_LOG_ERROR("network", "WorldSocket::ReadDataHandler(): client %s sent malformed CMSG_AUTH_SESSION", GetRemoteIpAddress().to_string().c_str());
return ReadDataHandlerResult::Error;
}
case CMSG_KEEP_ALIVE:
LogOpcodeText(opcode, sessionGuard);
sScriptMgr->OnPacketReceive(_worldSession, packet);
break;
case CMSG_LOG_DISCONNECT:
packet.rfinish(); // contains uint32 disconnectReason;
TC_LOG_DEBUG("network", "%s", GetOpcodeNameForLogging(opcode).c_str());
sScriptMgr->OnPacketReceive(_worldSession, packet);
break;
return ReadDataHandlerResult::Ok;
case CMSG_ENABLE_NAGLE:
{
TC_LOG_DEBUG("network", "%s", GetOpcodeNameForLogging(opcode).c_str());
sScriptMgr->OnPacketReceive(_worldSession, packet);
if (_worldSession)
_worldSession->HandleEnableNagleAlgorithm();
break;
}
default:
{
sessionGuard.lock();
return ReadDataHandlerResult::Ok;
case CMSG_KEEP_ALIVE: // todo: handle this packet in the same way of CMSG_TIME_SYNC_RESP
LogOpcodeText(opcode, sessionGuard);
if (!_worldSession)
{
TC_LOG_ERROR("network.opcode", "ProcessIncoming: Client not authed opcode = %u", uint32(opcode));
return ReadDataHandlerResult::Error;
}
// prevent invalid memory access/crash with custom opcodes
if (opcode >= NUM_OPCODE_HANDLERS)
{
CloseSocket();
return ReadDataHandlerResult::Error;
}
OpcodeHandler const* handler = opcodeTable[opcode];
if (!handler)
{
TC_LOG_ERROR("network.opcode", "No defined handler for opcode %s sent by %s", GetOpcodeNameForLogging(packet.GetOpcode()).c_str(), _worldSession->GetPlayerInfo().c_str());
break;
}
// Our Idle timer will reset on any non PING opcodes.
// Catches people idling on the login screen and any lingering ingame connections.
_worldSession->ResetTimeOutTime();
// Copy the packet to the heap before enqueuing
_worldSession->QueuePacket(new WorldPacket(std::move(packet)));
sScriptMgr->OnPacketReceive(_worldSession, packet);
return ReadDataHandlerResult::Ok;
case CMSG_TIME_SYNC_RESP:
packetToQueue = new WorldPacket(std::move(packet), std::chrono::steady_clock::now());
break;
default:
packetToQueue = new WorldPacket(std::move(packet));
break;
}
}
sessionGuard.lock();
LogOpcodeText(opcode, sessionGuard);
if (!_worldSession)
{
TC_LOG_ERROR("network.opcode", "ProcessIncoming: Client not authed opcode = %u", uint32(opcode));
return ReadDataHandlerResult::Error;
}
// prevent invalid memory access/crash with custom opcodes
if (opcode >= NUM_OPCODE_HANDLERS)
{
CloseSocket();
return ReadDataHandlerResult::Error;
}
OpcodeHandler const* handler = opcodeTable[opcode];
if (!handler)
{
TC_LOG_ERROR("network.opcode", "No defined handler for opcode %s sent by %s", GetOpcodeNameForLogging(packet.GetOpcode()).c_str(), _worldSession->GetPlayerInfo().c_str());
return ReadDataHandlerResult::Error;
}
// Our Idle timer will reset on any non PING opcodes.
// Catches people idling on the login screen and any lingering ingame connections.
_worldSession->ResetTimeOutTime();
// Copy the packet to the heap before enqueuing
_worldSession->QueuePacket(packetToQueue);
}
else
{
@@ -764,10 +766,7 @@ bool WorldSocket::HandlePing(WorldPacket& recvPacket)
std::lock_guard<std::mutex> sessionGuard(_worldSessionLock);
if (_worldSession)
{
_worldSession->SetLatency(latency);
_worldSession->ResetClientTimeDelay();
}
else
{
TC_LOG_ERROR("network", "WorldSocket::HandlePing: peer sent CMSG_PING, but is not authenticated or got recently kicked, address = %s", GetRemoteIpAddress().to_string().c_str());