feat(Core/Movement): time synchronisation to better interpret client timestamps (#5300)

This commit is contained in:
Chaouki Dhib
2021-04-23 15:53:09 +02:00
committed by GitHub
parent 970d371442
commit 2d21bfc915
11 changed files with 367 additions and 96 deletions

View File

@@ -929,11 +929,6 @@ Player::Player(WorldSession* session): Unit(true), m_mover(this)
m_ChampioningFaction = 0;
m_timeSyncCounter = 0;
m_timeSyncTimer = 0;
m_timeSyncClient = 0;
m_timeSyncServer = 0;
for (uint8 i = 0; i < MAX_POWERS; ++i)
m_powerFraction[i] = 0;
@@ -1833,14 +1828,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;
@@ -23804,8 +23791,8 @@ void Player::SendInitialPacketsAfterAddToMap()
{
UpdateVisibilityForPlayer(true);
ResetTimeSync();
SendTimeSync();
GetSession()->ResetTimeSync();
GetSession()->SendTimeSync();
CastSpell(this, 836, true); // LOGINEFFECT
@@ -27558,25 +27545,6 @@ uint8 Player::GetMostPointsTalentTree() const
return maxIndex;
}
void Player::ResetTimeSync()
{
m_timeSyncCounter = 0;
m_timeSyncTimer = 0;
m_timeSyncClient = 0;
m_timeSyncServer = World::GetGameTimeMS();
}
void Player::SendTimeSync()
{
WorldPacket data(SMSG_TIME_SYNC_REQ, 4);
data << uint32(m_timeSyncCounter++);
GetSession()->SendPacket(&data);
// Schedule next sync in 10 sec
m_timeSyncTimer = 10000;
m_timeSyncServer = World::GetGameTimeMS();
}
void Player::SetReputation(uint32 factionentry, uint32 value)
{
GetReputationMgr().SetReputation(sFactionStore.LookupEntry(factionentry), value);

View File

@@ -2823,9 +2823,6 @@ protected:
ItemDurationList m_itemSoulboundTradeable;
std::mutex m_soulboundTradableLock;
void ResetTimeSync();
void SendTimeSync();
uint64 m_resurrectGUID;
uint32 m_resurrectMap;
float m_resurrectX, m_resurrectY, m_resurrectZ;
@@ -2966,11 +2963,6 @@ private:
uint32 m_ChampioningFaction;
uint32 m_timeSyncCounter;
uint32 m_timeSyncTimer;
uint32 m_timeSyncClient;
uint32 m_timeSyncServer;
InstanceTimeMap _instanceResetTimes;
uint32 _pendingBindId;
uint32 _pendingBindTimer;

View File

@@ -1356,14 +1356,6 @@ void WorldSession::HandleSetTitleOpcode(WorldPacket& recv_data)
GetPlayer()->SetUInt32Value(PLAYER_CHOSEN_TITLE, title);
}
void WorldSession::HandleTimeSyncResp(WorldPacket& recv_data)
{
uint32 counter, clientTicks;
recv_data >> counter >> clientTicks;
//uint32 ourTicks = clientTicks + (World::GetGameTimeMS() - _player->m_timeSyncServer);
_player->m_timeSyncClient = clientTicks;
}
void WorldSession::HandleResetInstancesOpcode(WorldPacket& /*recv_data*/)
{
#if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS)

View File

@@ -14,6 +14,7 @@
#include "GameGraveyard.h"
#include "InstanceSaveMgr.h"
#include "Log.h"
#include "MathUtil.h"
#include "MapManager.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
@@ -419,11 +420,6 @@ void WorldSession::HandleMovementOpcodes(WorldPacket& recvData)
if (mover->GetGUID() != _player->GetGUID())
movementInfo.flags &= ~MOVEMENTFLAG_WALKING;
uint32 mstime = World::GetGameTimeMS();
/*----------------------*/
if(m_clientTimeDelay == 0)
m_clientTimeDelay = mstime > movementInfo.time ? std::min(mstime - movementInfo.time, (uint32)100) : 0;
// Xinef: do not allow to move with UNIT_FLAG_DISABLE_MOVE
if (mover->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_DISABLE_MOVE))
{
@@ -442,8 +438,16 @@ void WorldSession::HandleMovementOpcodes(WorldPacket& recvData)
/* process position-change */
WorldPacket data(opcode, recvData.size());
//movementInfo.time = movementInfo.time + m_clientTimeDelay + MOVEMENT_PACKET_TIME_DELAY;
movementInfo.time = mstime; // pussywizard: set to time of relocation (server time), constant addition may smoothen movement clientside, but client sees target on different position than the real serverside position
int64 movementTime = (int64)movementInfo.time + _timeSyncClockDelta;
if (_timeSyncClockDelta == 0 || movementTime < 0 || movementTime > 0xFFFFFFFF)
{
LOG_INFO("misc", "The computed movement time using clockDelta is erronous. Using fallback instead");
movementInfo.time = getMSTime();
}
else
{
movementInfo.time = (uint32) movementTime;
}
movementInfo.guid = mover->GetGUID();
WriteMovementInfo(&data, &movementInfo);
@@ -796,3 +800,76 @@ void WorldSession::HandleMoveTimeSkippedOpcode(WorldPacket& recvData)
data << timeSkipped;
GetPlayer()->SendMessageToSet(&data, false);
}
void WorldSession::HandleTimeSyncResp(WorldPacket& recvData)
{
#if defined(ENABLE_EXTRAS) && defined(ENABLE_EXTRA_LOGS)
LOG_DEBUG("network", "CMSG_TIME_SYNC_RESP");
#endif
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 + (int64)lagDelay - (int64)clientTimestamp;
_timeSyncClockDeltaQueue.put(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.
std::vector<uint32> latencies;
std::vector<int64> clockDeltasAfterFiltering;
for (auto pair : _timeSyncClockDeltaQueue.content())
latencies.push_back(pair.second);
uint32 latencyMedian = median(latencies);
uint32 latencyStandardDeviation = standard_deviation(latencies);
uint32 sampleSizeAfterFiltering = 0;
for (auto pair : _timeSyncClockDeltaQueue.content())
{
if (pair.second <= latencyMedian + latencyStandardDeviation) {
clockDeltasAfterFiltering.push_back(pair.first);
sampleSizeAfterFiltering++;
}
}
if (sampleSizeAfterFiltering != 0)
{
int64 meanClockDelta = static_cast<int64>(mean(clockDeltasAfterFiltering));
if (std::abs(meanClockDelta - _timeSyncClockDelta) > 25)
_timeSyncClockDelta = meanClockDelta;
}
else if (_timeSyncClockDelta == 0)
{
std::pair<int64, uint32> back = _timeSyncClockDeltaQueue.peak_back();
_timeSyncClockDelta = back.first;
}
}

View File

@@ -926,7 +926,7 @@ OpcodeHandler opcodeTable[NUM_MSG_TYPES] =
/*0x38D*/ { "CMSG_MOVE_CHNG_TRANSPORT", STATUS_LOGGEDIN, PROCESS_THREADSAFE, &WorldSession::HandleMovementOpcodes },
/*0x38E*/ { "MSG_PARTY_ASSIGNMENT", STATUS_LOGGEDIN, PROCESS_THREADUNSAFE, &WorldSession::HandlePartyAssignmentOpcode },
/*0x38F*/ { "SMSG_OFFER_PETITION_ERROR", STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide },
/*0x390*/ { "SMSG_TIME_SYNC_REQ", STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_ServerSide },
/*0x390*/ { "SMSG_TIME_SYNC_REQ", STATUS_NEVER, PROCESS_THREADSAFE, &WorldSession::Handle_ServerSide },
/*0x391*/ { "CMSG_TIME_SYNC_RESP", STATUS_LOGGEDIN, PROCESS_INPLACE, &WorldSession::HandleTimeSyncResp },
/*0x392*/ { "CMSG_SEND_LOCAL_EVENT", STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL },
/*0x393*/ { "CMSG_SEND_GENERAL_TRIGGER", STATUS_NEVER, PROCESS_INPLACE, &WorldSession::Handle_NULL },

View File

@@ -110,14 +110,16 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8
m_sessionDbcLocale(sWorld->GetDefaultDbcLocale()),
m_sessionDbLocaleIndex(locale),
m_latency(0),
m_clientTimeDelay(0),
m_TutorialsChanged(false),
recruiterId(recruiter),
isRecruiter(isARecruiter),
m_currentVendorEntry(0),
m_currentBankerGUID(0),
timeWhoCommandAllowed(0),
_calendarEventCreationCooldown(0)
_calendarEventCreationCooldown(0),
_timeSyncClockDeltaQueue(6),
_timeSyncClockDelta(0),
_pendingTimeSyncRequests()
{
memset(m_Tutorials, 0, sizeof(m_Tutorials));
@@ -126,6 +128,9 @@ WorldSession::WorldSession(uint32 id, WorldSocket* sock, AccountTypes sec, uint8
_kicked = false;
_shouldSetOfflineInDB = true;
_timeSyncNextCounter = 0;
_timeSyncTimer = 0;
if (sock)
{
m_Address = sock->GetRemoteAddress();
@@ -253,7 +258,7 @@ void WorldSession::QueuePacket(WorldPacket* new_packet)
/// Update the WorldSession (triggered by World update)
bool WorldSession::Update(uint32 diff, PacketFilter& updater)
{
if (updater.ProcessLogout())
if (updater.ProcessUnsafe())
{
UpdateTimeOutTime(diff);
@@ -263,7 +268,7 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
m_Socket->CloseSocket("Client didn't send anything for too long");
}
HandleTeleportTimeout(updater.ProcessLogout());
HandleTeleportTimeout(updater.ProcessUnsafe());
uint32 _startMSTime = getMSTime();
WorldPacket* packet = nullptr;
@@ -390,7 +395,7 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
if (m_Socket && !m_Socket->IsClosed())
ProcessQueryCallbacks();
if (updater.ProcessLogout())
if (updater.ProcessUnsafe())
{
if (m_Socket && !m_Socket->IsClosed() && _warden)
{
@@ -415,6 +420,22 @@ bool WorldSession::Update(uint32 diff, PacketFilter& updater)
}
}
if (!updater.ProcessUnsafe()) // <=> updater is of type MapSessionFilter
{
// Send time sync packet every 10s.
if (_timeSyncTimer > 0)
{
if (diff >= _timeSyncTimer)
{
SendTimeSync();
}
else
{
_timeSyncTimer -= diff;
}
}
}
return true;
}
@@ -1648,3 +1669,22 @@ uint32 WorldSession::DosProtection::GetMaxPacketCounterAllowed(uint16 opcode) co
return maxPacketCounterAllowed;
}
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

@@ -15,6 +15,7 @@
#include "AuthDefines.h"
#include "AddonMgr.h"
#include "BanManager.h"
#include "CircularBuffer.h"
#include "Common.h"
#include "DatabaseEnv.h"
#include "GossipDef.h"
@@ -23,6 +24,7 @@
#include "World.h"
#include "WorldPacket.h"
#include <utility>
#include <map>
class Creature;
class GameObject;
@@ -124,7 +126,7 @@ public:
virtual ~PacketFilter() = default;
virtual bool Process(WorldPacket* /*packet*/) { return true; }
[[nodiscard]] virtual bool ProcessLogout() const { return true; }
[[nodiscard]] virtual bool ProcessUnsafe() const { return true; }
protected:
WorldSession* const m_pSession;
@@ -138,7 +140,7 @@ public:
bool Process(WorldPacket* packet) override;
//in Map::Update() we do not process player logout!
[[nodiscard]] bool ProcessLogout() const override { return false; }
[[nodiscard]] bool ProcessUnsafe() const override { return false; }
};
//class used to filer only thread-unsafe packets from queue
@@ -357,7 +359,6 @@ public:
uint32 GetLatency() const { return m_latency; }
void SetLatency(uint32 latency) { m_latency = latency; }
void ResetClientTimeDelay() { m_clientTimeDelay = 0; }
std::atomic<time_t> m_timeOutTime;
void UpdateTimeOutTime(uint32 diff)
@@ -387,6 +388,9 @@ public:
time_t GetCalendarEventCreationCooldown() const { return _calendarEventCreationCooldown; }
void SetCalendarEventCreationCooldown(time_t cooldown) { _calendarEventCreationCooldown = cooldown; }
// Time Synchronisation
void ResetTimeSync();
void SendTimeSync();
public: // opcodes handlers
void Handle_NULL(WorldPacket& recvPacket); // not used
void Handle_EarlyProccess(WorldPacket& recvPacket); // just mark packets processed in WorldSocket::OnRead
@@ -1028,7 +1032,6 @@ private:
LocaleConstant m_sessionDbcLocale;
LocaleConstant m_sessionDbLocaleIndex;
uint32 m_latency;
uint32 m_clientTimeDelay;
AccountData m_accountData[NUM_ACCOUNT_DATA_TYPES];
uint32 m_Tutorials[MAX_ACCOUNT_TUTORIAL_VALUES];
bool m_TutorialsChanged;
@@ -1044,6 +1047,14 @@ private:
bool _shouldSetOfflineInDB;
// Packets cooldown
time_t _calendarEventCreationCooldown;
CircularBuffer<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;
};
#endif
/// @}

View File

@@ -669,15 +669,13 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct)
switch (opcode)
{
case CMSG_PING:
try
{
try
{
return HandlePing(*new_pct);
}
catch (ByteBufferPositionException const&) {}
LOG_ERROR("server", "WorldSocket::ReadDataHandler(): client sent malformed CMSG_PING");
return -1;
return HandlePing(*new_pct);
}
catch (ByteBufferPositionException const&) { }
LOG_ERROR("server", "WorldSocket::ReadDataHandler(): client sent malformed CMSG_PING");
return -1;
case CMSG_AUTH_SESSION:
if (m_Session)
{
@@ -689,27 +687,11 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct)
if (m_Session)
m_Session->ResetTimeOutTime(true);
return 0;
case CMSG_TIME_SYNC_RESP:
new_pct = new WorldPacket(std::move(*new_pct), std::chrono::steady_clock::now());
break;
default:
{
std::lock_guard<std::mutex> guard(m_SessionLock);
if (m_Session != nullptr)
{
// Our Idle timer will reset on any non PING opcodes.
// Catches people idling on the login screen and any lingering ingame connections.
m_Session->ResetTimeOutTime(false);
// OK, give the packet to WorldSession
aptr.release();
m_Session->QueuePacket (new_pct);
return 0;
}
else
{
LOG_ERROR("server", "WorldSocket::ProcessIncoming: Client not authed opcode = %u", uint32(opcode));
return -1;
}
}
break;
}
}
catch (ByteBufferException const&)
@@ -724,7 +706,25 @@ int WorldSocket::ProcessIncoming(WorldPacket* new_pct)
return -1;
}
ACE_NOTREACHED (return 0);
std::lock_guard<std::mutex> guard(m_SessionLock);
if (m_Session != nullptr)
{
// Our Idle timer will reset on any non PING or TIME_SYNC opcodes.
// Catches people idling on the login screen and any lingering ingame connections.
if (opcode != CMSG_PING && opcode != CMSG_TIME_SYNC_RESP)
{
m_Session->ResetTimeOutTime(false);
}
// OK, give the packet to WorldSession
aptr.release();
m_Session->QueuePacket(new_pct);
return 0;
}
LOG_ERROR("server", "WorldSocket::ProcessIncoming: Client not authed opcode = %u", uint32(opcode));
return -1;
}
int WorldSocket::HandleAuthSession(WorldPacket& recvPacket)
@@ -1074,7 +1074,6 @@ int WorldSocket::HandlePing(WorldPacket& recvPacket)
if (m_Session)
{
m_Session->SetLatency (latency);
m_Session->ResetClientTimeDelay();
}
else
{