/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "base/unixtime.h" #include "logs.h" #include #include #ifdef Q_OS_WIN #include #elif defined Q_OS_MAC #include #else #include #endif namespace base { namespace unixtime { namespace { std::atomic ValueUpdated/* = false*/; std::atomic ValueShift/* = 0*/; std::atomic HttpValueValid/* = false*/; std::atomic HttpValueShift/* = 0*/; class MsgIdManager { public: MsgIdManager(); void update(); [[nodiscard]] uint64 next(); private: void initialize(); QReadWriteLock _lock; uint64 _startId = 0; std::atomic _incrementedPart = 0; uint64 _startCounter = 0; uint64 _randomPart = 0; float64 _multiplier = 0.; }; MsgIdManager GlobalMsgIdManager; [[nodiscard]] float64 GetMultiplier() { // 0xFFFF0000 instead of 0x100000000 to make msgId grow slightly slower, // than unixtime and we had time to reconfigure. #ifdef Q_OS_WIN LARGE_INTEGER li; QueryPerformanceFrequency(&li); return float64(0xFFFF0000L) / float64(li.QuadPart); #elif defined Q_OS_MAC // Q_OS_WIN mach_timebase_info_data_t tb = { 0, 0 }; mach_timebase_info(&tb); const auto frequency = (float64(tb.numer) / tb.denom) / 1000000.; return frequency * (float64(0xFFFF0000L) / 1000.); #else // Q_OS_MAC || Q_OS_WIN return float64(0xFFFF0000L) / 1000000000.; #endif // Q_OS_MAC || Q_OS_WIN } [[nodiscard]] uint64 GetCounter() { #ifdef Q_OS_WIN LARGE_INTEGER li; QueryPerformanceCounter(&li); return li.QuadPart; #elif defined Q_OS_MAC // Q_OS_WIN return mach_absolute_time(); #else // Q_OS_MAC || Q_OS_WIN timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return 1000000000 * uint64(ts.tv_sec) + uint64(ts.tv_nsec); #endif // Q_OS_MAC || Q_OS_WIN } MsgIdManager::MsgIdManager() { auto generator = std::mt19937(std::random_device()()); auto distribution = std::uniform_int_distribution(); _randomPart = distribution(generator); _multiplier = GetMultiplier(); initialize(); srand(uint32(_startCounter & 0xFFFFFFFFUL)); } void MsgIdManager::update() { QWriteLocker lock(&_lock); initialize(); } void MsgIdManager::initialize() { _startCounter = GetCounter(); _startId = ((uint64(uint32(now()))) << 32) | _randomPart; } uint64 MsgIdManager::next() { const auto counter = GetCounter(); QReadLocker lock(&_lock); const auto delta = (counter - _startCounter); const auto result = _startId + (uint64)floor(delta * _multiplier); lock.unlock(); return (result & ~0x03L) + (_incrementedPart += 4); } TimeId local() { return (TimeId)time(nullptr); } } // namespace TimeId now() { return local() + ValueShift.load(); } void update(TimeId now, bool force) { if (force) { DEBUG_LOG(("MTP Info: forcing client unixtime to %1" ).arg(now)); ValueUpdated = true; } else { auto expected = false; if (!ValueUpdated.compare_exchange_strong(expected, true)) { return; } DEBUG_LOG(("MTP Info: setting client unixtime to %1").arg(now)); } const auto shift = now + 1 - local(); ValueShift = shift; DEBUG_LOG(("MTP Info: now unixtimeDelta is %1").arg(shift)); HttpValueShift = 0; HttpValueValid = false; GlobalMsgIdManager.update(); } QDateTime parse(TimeId value) { return (value > 0) ? QDateTime::fromTime_t(value - ValueShift) : QDateTime(); } TimeId serialize(const QDateTime &date) { return date.isNull() ? TimeId(0) : date.toTime_t() + ValueShift; } bool http_valid() { return HttpValueValid; } TimeId http_now() { return now() + HttpValueShift; } void http_update(TimeId now) { HttpValueShift = now - base::unixtime::now(); HttpValueValid = true; } void http_invalidate() { HttpValueValid = false; } uint64 mtproto_msg_id() { return GlobalMsgIdManager.next(); } } // namespace unixtime } // namespace base