diff --git a/Telegram/SourceFiles/base/qthelp_url.cpp b/Telegram/SourceFiles/base/qthelp_url.cpp index 387af2ef0..44f4d50ce 100644 --- a/Telegram/SourceFiles/base/qthelp_url.cpp +++ b/Telegram/SourceFiles/base/qthelp_url.cpp @@ -38,4 +38,10 @@ QMap url_parse_params( return result; } +bool is_ipv6(const QString &ip) { + //static const auto regexp = QRegularExpression("^[a-fA-F0-9:]+$"); + //return regexp.match(ip).hasMatch(); + return ip.indexOf(':') >= 0; +} + } // namespace qthelp diff --git a/Telegram/SourceFiles/base/qthelp_url.h b/Telegram/SourceFiles/base/qthelp_url.h index 322766814..81b31f67e 100644 --- a/Telegram/SourceFiles/base/qthelp_url.h +++ b/Telegram/SourceFiles/base/qthelp_url.h @@ -24,4 +24,6 @@ enum class UrlParamNameTransform { // Parses a string like "p1=v1&p2=v2&..&pn=vn" to a map. QMap url_parse_params(const QString ¶ms, UrlParamNameTransform transform = UrlParamNameTransform::NoTransform); +bool is_ipv6(const QString &ip); + } // namespace qthelp diff --git a/Telegram/SourceFiles/base/timer.cpp b/Telegram/SourceFiles/base/timer.cpp index e96b3bc94..a5f8b8169 100644 --- a/Telegram/SourceFiles/base/timer.cpp +++ b/Telegram/SourceFiles/base/timer.cpp @@ -17,12 +17,26 @@ QObject *TimersAdjuster() { } // namespace -Timer::Timer(base::lambda callback) : QObject(nullptr) +Timer::Timer( + not_null thread, + base::lambda callback) +: Timer(std::move(callback)) { + moveToThread(thread); +} + + +Timer::Timer(base::lambda callback) +: QObject(nullptr) , _callback(std::move(callback)) , _type(Qt::PreciseTimer) , _adjusted(false) { setRepeat(Repeat::Interval); - connect(TimersAdjuster(), &QObject::destroyed, this, [this] { adjust(); }, Qt::QueuedConnection); + connect( + TimersAdjuster(), + &QObject::destroyed, + this, + [this] { adjust(); }, + Qt::QueuedConnection); } void Timer::start(TimeMs timeout, Qt::TimerType type, Repeat repeat) { @@ -56,7 +70,11 @@ TimeMs Timer::remainingTime() const { void Timer::Adjust() { QObject emitter; - connect(&emitter, &QObject::destroyed, TimersAdjuster(), &QObject::destroyed); + connect( + &emitter, + &QObject::destroyed, + TimersAdjuster(), + &QObject::destroyed); } void Timer::adjust() { @@ -70,6 +88,7 @@ void Timer::adjust() { void Timer::setTimeout(TimeMs timeout) { Expects(timeout >= 0 && timeout <= std::numeric_limits::max()); + _timeout = static_cast(timeout); } @@ -93,8 +112,12 @@ void Timer::timerEvent(QTimerEvent *e) { } } -int DelayedCallTimer::call(TimeMs timeout, lambda_once callback, Qt::TimerType type) { +int DelayedCallTimer::call( + TimeMs timeout, + lambda_once callback, + Qt::TimerType type) { Expects(timeout >= 0); + if (!callback) { return 0; } @@ -108,7 +131,7 @@ int DelayedCallTimer::call(TimeMs timeout, lambda_once callback, Qt::Tim void DelayedCallTimer::cancel(int callId) { if (callId) { killTimer(callId); - _callbacks.erase(callId); + _callbacks.remove(callId); } } diff --git a/Telegram/SourceFiles/base/timer.h b/Telegram/SourceFiles/base/timer.h index f0e8bdf4c..cc04b4f0f 100644 --- a/Telegram/SourceFiles/base/timer.h +++ b/Telegram/SourceFiles/base/timer.h @@ -14,7 +14,10 @@ namespace base { class Timer final : private QObject { public: - Timer(base::lambda callback = base::lambda()); + explicit Timer( + not_null thread, + base::lambda callback = nullptr); + explicit Timer(base::lambda callback = nullptr); static Qt::TimerType DefaultType(TimeMs timeout) { constexpr auto kThreshold = TimeMs(1000); @@ -85,17 +88,23 @@ private: class DelayedCallTimer final : private QObject { public: int call(TimeMs timeout, lambda_once callback) { - return call(timeout, std::move(callback), Timer::DefaultType(timeout)); + return call( + timeout, + std::move(callback), + Timer::DefaultType(timeout)); } - int call(TimeMs timeout, lambda_once callback, Qt::TimerType type); + int call( + TimeMs timeout, + lambda_once callback, + Qt::TimerType type); void cancel(int callId); protected: void timerEvent(QTimerEvent *e) override; private: - std::map> _callbacks; // Better to use flatmap. + base::flat_map> _callbacks; }; diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index eba0c2a98..870df6155 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -87,7 +87,7 @@ bool ConnectionBox::badProxyValue() const { void ConnectionBox::updateControlsVisibility() { auto newHeight = st::boxOptionListPadding.top() + _autoRadio->heightNoMargins() + st::boxOptionListSkip + _httpProxyRadio->heightNoMargins() + st::boxOptionListSkip + _tcpProxyRadio->heightNoMargins() + st::boxOptionListSkip + st::connectionIPv6Skip + _tryIPv6->heightNoMargins() + st::defaultCheckbox.margin.bottom() + st::boxOptionListPadding.bottom() + st::boxPadding.bottom(); - if (_typeGroup->value() == dbictAuto && badProxyValue()) { + if (!proxyFieldsVisible()) { _hostInput->hide(); _portInput->hide(); _userInput->hide(); @@ -104,6 +104,13 @@ void ConnectionBox::updateControlsVisibility() { updateControlsPosition(); } +bool ConnectionBox::proxyFieldsVisible() const { + return (_typeGroup->value() != dbictAuto) + || (!badProxyValue() + && (_currentProxyType == ProxyData::Type::Http + || _currentProxyType == ProxyData::Type::Socks5)); +} + void ConnectionBox::setInnerFocus() { if (_typeGroup->value() == dbictAuto) { setFocus(); @@ -124,7 +131,7 @@ void ConnectionBox::updateControlsPosition() { _httpProxyRadio->moveToLeft(st::boxPadding.left() + st::boxOptionListPadding.left(), _autoRadio->bottomNoMargins() + st::boxOptionListSkip); auto inputy = 0; - auto fieldsVisible = (type != dbictAuto) || (!badProxyValue() && _currentProxyType != ProxyData::Type::None); + auto fieldsVisible = proxyFieldsVisible(); auto fieldsBelowHttp = fieldsVisible && (type == dbictHttpProxy || (type == dbictAuto && _currentProxyType == ProxyData::Type::Http)); auto fieldsBelowTcp = fieldsVisible && (type == dbictTcpProxy || (type == dbictAuto && _currentProxyType == ProxyData::Type::Socks5)); if (fieldsBelowHttp) { diff --git a/Telegram/SourceFiles/boxes/connection_box.h b/Telegram/SourceFiles/boxes/connection_box.h index 0aa325010..c7d089f0c 100644 --- a/Telegram/SourceFiles/boxes/connection_box.h +++ b/Telegram/SourceFiles/boxes/connection_box.h @@ -44,6 +44,7 @@ private: void updateControlsVisibility(); void updateControlsPosition(); bool badProxyValue() const; + bool proxyFieldsVisible() const; object_ptr _hostInput; object_ptr _portInput; diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 6384d44c6..d07c18c69 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -25,13 +25,6 @@ enum { MTPAckSendWaiting = 10000, // how much time to wait for some more requests, when sending msg acks MTPResendThreshold = 1, // how much ints should message contain for us not to resend, but to check it's state MTPContainerLives = 600, // container lives 10 minutes in haveSent map - MTPMinReceiveDelay = 4000, // 4 seconds - MTPMaxReceiveDelay = 64000, // 64 seconds - MTPMinConnectDelay = 1000, // tcp connect should take less then 1 second - MTPMaxConnectDelay = 8000, // tcp connect should take 8 seconds max - MTPConnectionOldTimeout = 192000, // 192 seconds - MTPTcpConnectionWaitTimeout = 2000, // 2 seconds waiting for tcp, until we accept http - MTPIPv4ConnectionWaitTimeout = 1000, // 1 seconds waiting for ipv4, until we accept ipv6 MTPKillFileSessionTimeout = 5000, // how much time without upload / download causes additional session kill @@ -39,10 +32,6 @@ enum { MaxUsersPerInvite = 100, // max users in one super group invite request - MTPPingDelayDisconnect = 60, // 1 min - MTPPingSendAfterAuto = 30, // send new ping starting from 30 seconds (add to existing container) - MTPPingSendAfter = 45, // send new ping after 45 seconds without ping - MTPChannelGetDifferenceLimit = 100, MaxSelectedItems = 100, diff --git a/Telegram/SourceFiles/mtproto/connection.cpp b/Telegram/SourceFiles/mtproto/connection.cpp index 93a2a0a12..a901de64e 100644 --- a/Telegram/SourceFiles/mtproto/connection.cpp +++ b/Telegram/SourceFiles/mtproto/connection.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "zlib.h" #include "lang/lang_keys.h" #include "base/openssl_help.h" +#include "base/qthelp_url.h" #include #include #include @@ -29,6 +30,15 @@ namespace { constexpr auto kRecreateKeyId = AuthKey::KeyId(0xFFFFFFFFFFFFFFFFULL); constexpr auto kIntSize = static_cast(sizeof(mtpPrime)); constexpr auto kMaxModExpSize = 256; +constexpr auto kWaitForBetterTimeout = TimeMs(2000); +constexpr auto kMinConnectedTimeout = TimeMs(1000); +constexpr auto kMaxConnectedTimeout = TimeMs(8000); +constexpr auto kMinReceiveTimeout = TimeMs(4000); +constexpr auto kMaxReceiveTimeout = TimeMs(64000); +constexpr auto kMarkConnectionOldTimeout = TimeMs(192000); +constexpr auto kPingDelayDisconnect = 60; +constexpr auto kPingSendAfter = TimeMs(30000); +constexpr auto kPingSendAfterForce = TimeMs(45000); // If we can't connect for this time we will ask _instance to update config. constexpr auto kRequestConfigTimeout = TimeMs(8000); @@ -45,6 +55,35 @@ QString LogIdsVector(const QVector &ids) { return idsStr + "]"; } +bytes::vector ProtocolSecretFromPassword(const QString &password) { + const auto size = password.size(); + if (size % 2) { + return {}; + } + const auto length = size / 2; + const auto fromHex = [](QChar ch) -> int { + const auto code = int(ch.unicode()); + if (code >= '0' && code <= '9') { + return (code - '0'); + } else if (code >= 'A' && code <= 'F') { + return 10 + (code - 'A'); + } else if (ch >= 'a' && ch <= 'f') { + return 10 + (code - 'a'); + } + return -1; + }; + auto result = bytes::vector(length); + for (auto i = 0; i != length; ++i) { + const auto high = fromHex(password[2 * i]); + const auto low = fromHex(password[2 * i + 1]); + if (high < 0 || low < 0) { + return {}; + } + result[i] = static_cast(high * 16 + low); + } + return result; +} + bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime) { auto diff = prime - modexp; if (modexp.failed() || prime.failed() || diff.failed()) { @@ -260,141 +299,140 @@ bool parsePQ(const QByteArray &pqStr, QByteArray &pStr, QByteArray &qStr) { } // namespace -Connection::Connection(Instance *instance) : _instance(instance) { +Connection::Connection(not_null instance) : _instance(instance) { } void Connection::start(SessionData *sessionData, ShiftedDcId shiftedDcId) { - Expects(thread == nullptr && data == nullptr); + Expects(_thread == nullptr && _private == nullptr); - thread = std::make_unique(); - auto newData = std::make_unique(_instance, thread.get(), this, sessionData, shiftedDcId); + _thread = std::make_unique(); + auto newData = std::make_unique( + _instance, + _thread.get(), + this, + sessionData, + shiftedDcId); // will be deleted in the thread::finished signal - data = newData.release(); - thread->start(); + _private = newData.release(); + _thread->start(); } void Connection::kill() { - Expects(data != nullptr && thread != nullptr); - data->stop(); - data = nullptr; - thread->quit(); + Expects(_private != nullptr && _thread != nullptr); + + _private->stop(); + _private = nullptr; + _thread->quit(); } void Connection::waitTillFinish() { - Expects(data == nullptr && thread != nullptr); + Expects(_private == nullptr && _thread != nullptr); DEBUG_LOG(("Waiting for connectionThread to finish")); - thread->wait(); - thread.reset(); + _thread->wait(); + _thread.reset(); } int32 Connection::state() const { - Expects(data != nullptr && thread != nullptr); + Expects(_private != nullptr && _thread != nullptr); - return data->getState(); + return _private->getState(); } QString Connection::transport() const { - Expects(data != nullptr && thread != nullptr); + Expects(_private != nullptr && _thread != nullptr); - return data->transport(); + return _private->transport(); } Connection::~Connection() { - Expects(data == nullptr); + Expects(_private == nullptr); - if (thread) { + if (_thread) { waitTillFinish(); } } -void ConnectionPrivate::createConn(bool createIPv4, bool createIPv6) { - destroyAllConnections(); - if (createIPv4) { - QWriteLocker lock(&stateConnMutex); - _conn4 = AbstractConnection::create( - *_connectionOptions, - _shiftedDcId, - _dcType, - thread()); - connect(_conn4, SIGNAL(error(qint32)), this, SLOT(onError4(qint32))); - connect(_conn4, SIGNAL(receivedSome()), this, SLOT(onReceivedSome())); - } - if (createIPv6) { - QWriteLocker lock(&stateConnMutex); - _conn6 = AbstractConnection::create( - *_connectionOptions, - _shiftedDcId, - _dcType, - thread()); - connect(_conn6, SIGNAL(error(qint32)), this, SLOT(onError6(qint32))); - connect(_conn6, SIGNAL(receivedSome()), this, SLOT(onReceivedSome())); - } +void ConnectionPrivate::appendTestConnection( + DcOptions::Variants::Protocol protocol, + const QString &ip, + int port, + const bytes::vector &protocolSecret) { + QWriteLocker lock(&stateConnMutex); + + const auto priority = (qthelp::is_ipv6(ip) ? 0 : 1) + + (protocol == DcOptions::Variants::Tcp ? 1 : 0); + _testConnections.push_back({ + AbstractConnection::create(protocol, thread()), + priority + }); + auto weak = _testConnections.back().data.get(); + connect(weak, &AbstractConnection::error, [=](int errorCode) { + onError(weak, errorCode); + }); + connect(weak, &AbstractConnection::receivedSome, [=] { + onReceivedSome(); + }); firstSentAt = 0; - if (oldConnection) { - oldConnection = false; + if (_oldConnection) { + _oldConnection = false; DEBUG_LOG(("This connection marked as not old!")); } - oldConnectionTimer.start(MTPConnectionOldTimeout); + _oldConnectionTimer.callOnce(kMarkConnectionOldTimeout); + connect(weak, &AbstractConnection::connected, [=] { + onConnected(weak); + }); + connect(weak, &AbstractConnection::disconnected, [=] { + onDisconnected(weak); + }); + + const auto protocolDcId = (_dcType == DcType::MediaDownload) + ? -MTP::bareDcId(_shiftedDcId) + : MTP::bareDcId(_shiftedDcId); + InvokeQueued(_testConnections.back().data, [=] { + weak->connectToServer(ip, port, protocolSecret, protocolDcId); + }); } void ConnectionPrivate::destroyAllConnections() { - destroyConnection(_conn4); - destroyConnection(_conn6); - _conn = nullptr; + _waitForBetterTimer.cancel(); + _waitForReceivedTimer.cancel(); + _waitForConnectedTimer.cancel(); + _testConnections.clear(); + _connection = nullptr; } -void ConnectionPrivate::destroyConnection(AbstractConnection *&connection) { - const auto taken = [&] { - QWriteLocker lock(&stateConnMutex); - if (connection) { - disconnect(connection, SIGNAL(connected()), nullptr, nullptr); - disconnect(connection, SIGNAL(disconnected()), nullptr, nullptr); - disconnect(connection, SIGNAL(error(qint32)), nullptr, nullptr); - disconnect(connection, SIGNAL(receivedData()), nullptr, nullptr); - disconnect(connection, SIGNAL(receivedSome()), nullptr, nullptr); - } - return base::take(connection); - }(); - if (taken) { - taken->disconnectFromServer(); - taken->deleteLater(); - } -} - -ConnectionPrivate::ConnectionPrivate(Instance *instance, QThread *thread, Connection *owner, SessionData *data, ShiftedDcId shiftedDcId) : QObject() +ConnectionPrivate::ConnectionPrivate( + not_null instance, + not_null thread, + not_null owner, + not_null data, + ShiftedDcId shiftedDcId) +: QObject(nullptr) , _instance(instance) , _state(DisconnectedState) , _shiftedDcId(shiftedDcId) , _owner(owner) , _configWasFineAt(getms(true)) -, _waitForReceived(MTPMinReceiveDelay) -, _waitForConnected(MTPMinConnectDelay) -//, sessionDataMutex(QReadWriteLock::Recursive) +, _retryTimer(thread, [=] { retryByTimer(); }) +, _oldConnectionTimer(thread, [=] { markConnectionOld(); }) +, _waitForConnectedTimer(thread, [=] { waitConnectedFailed(); }) +, _waitForReceivedTimer(thread, [=] { waitReceivedFailed(); }) +, _waitForBetterTimer(thread, [=] { waitBetterFailed(); }) +, _waitForReceived(kMinReceiveTimeout) +, _waitForConnected(kMinConnectedTimeout) +, _pingSender(thread, [=] { sendPingByTimer(); }) , sessionData(data) { - oldConnectionTimer.moveToThread(thread); - _waitForConnectedTimer.moveToThread(thread); - _waitForReceivedTimer.moveToThread(thread); - _waitForIPv4Timer.moveToThread(thread); - _pingSender.moveToThread(thread); - retryTimer.moveToThread(thread); - moveToThread(thread); - Expects(_shiftedDcId != 0); - connect(thread, &QThread::started, this, [this] { connectToServer(); }); - connect(thread, &QThread::finished, this, [this] { finishAndDestroy(); }); + moveToThread(thread); + + connect(thread, &QThread::started, this, [=] { connectToServer(); }); + connect(thread, &QThread::finished, this, [=] { finishAndDestroy(); }); connect(this, SIGNAL(finished(internal::Connection*)), _instance, SLOT(connectionFinished(internal::Connection*)), Qt::QueuedConnection); - connect(&retryTimer, SIGNAL(timeout()), this, SLOT(retryByTimer())); - connect(&_waitForConnectedTimer, SIGNAL(timeout()), this, SLOT(onWaitConnectedFailed())); - connect(&_waitForReceivedTimer, SIGNAL(timeout()), this, SLOT(onWaitReceivedFailed())); - connect(&_waitForIPv4Timer, SIGNAL(timeout()), this, SLOT(onWaitIPv4Failed())); - connect(&oldConnectionTimer, SIGNAL(timeout()), this, SLOT(onOldConnection())); - connect(&_pingSender, SIGNAL(timeout()), this, SLOT(onPingSender())); connect(sessionData->owner(), SIGNAL(authKeyCreated()), this, SLOT(updateAuthKey()), Qt::QueuedConnection); - connect(sessionData->owner(), SIGNAL(needToRestart()), this, SLOT(restartNow()), Qt::QueuedConnection); connect(this, SIGNAL(needToReceive()), sessionData->owner(), SLOT(tryToReceive()), Qt::QueuedConnection); connect(this, SIGNAL(stateChanged(qint32)), sessionData->owner(), SLOT(onConnectionStateChange(qint32)), Qt::QueuedConnection); @@ -434,8 +472,8 @@ int32 ConnectionPrivate::getState() const { QReadLocker lock(&stateConnMutex); int32 result = _state; if (_state < 0) { - if (retryTimer.isActive()) { - result = int32(getms(true) - retryWillFinish); + if (_retryTimer.isActive()) { + result = int32(getms(true) - _retryWillFinish); if (result >= 0) { result = -1; } @@ -446,16 +484,12 @@ int32 ConnectionPrivate::getState() const { QString ConnectionPrivate::transport() const { QReadLocker lock(&stateConnMutex); - if ((!_conn4 && !_conn6) || (_conn4 && _conn6) || (_state < 0)) { + if (!_connection || (_state < 0)) { return QString(); } Assert(_connectionOptions != nullptr); - auto result = (_conn4 ? _conn4 : _conn6)->transport(); - if (!result.isEmpty() && _connectionOptions->useIPv6) { - result += (_conn4 ? "/IPv4" : "/IPv6"); - } - return result; + return _connection->transport(); } bool ConnectionPrivate::setState(int32 state, int32 ifState) { @@ -467,9 +501,9 @@ bool ConnectionPrivate::setState(int32 state, int32 ifState) { if (_state == state) return false; _state = state; if (state < 0) { - retryTimeout = -state; - retryTimer.start(retryTimeout); - retryWillFinish = getms(true) + retryTimeout; + _retryTimeout = -state; + _retryTimer.callOnce(_retryTimeout); + _retryWillFinish = getms(true) + _retryTimeout; } emit stateChanged(state); return true; @@ -690,7 +724,7 @@ mtpMsgId ConnectionPrivate::placeToContainer(mtpRequest &toSendRequest, mtpMsgId void ConnectionPrivate::tryToSend() { QReadLocker lockFinished(&sessionDataMutex); - if (!sessionData || !_conn) { + if (!sessionData || !_connection) { return; } @@ -711,7 +745,7 @@ void ConnectionPrivate::tryToSend() { ping.write(*pingRequest); DEBUG_LOG(("MTP Info: sending ping, ping_id: %1").arg(_pingIdToSend)); } else { - MTPPing_delay_disconnect ping(MTP_long(_pingIdToSend), MTP_int(MTPPingDelayDisconnect)); + MTPPing_delay_disconnect ping(MTP_long(_pingIdToSend), MTP_int(kPingDelayDisconnect)); uint32 pingSize = ping.innerLength() >> 2; // copy from Session::send pingRequest = mtpRequestData::prepare(pingSize); ping.write(*pingRequest); @@ -719,11 +753,11 @@ void ConnectionPrivate::tryToSend() { } pingRequest->msDate = getms(true); // > 0 - can send without container - _pingSendAt = pingRequest->msDate + (MTPPingSendAfterAuto * 1000LL); + _pingSendAt = pingRequest->msDate + kPingSendAfter; pingRequest->requestId = 0; // dont add to haveSent / wereAcked maps if (_shiftedDcId == bareDcId(_shiftedDcId) && !prependOnly) { // main session - _pingSender.start(MTPPingSendAfter * 1000); + _pingSender.callOnce(kPingSendAfterForce); } _pingId = _pingIdToSend; @@ -782,7 +816,7 @@ void ConnectionPrivate::tryToSend() { stateRequest->msDate = getms(true); // > 0 - can send without container stateRequest->requestId = reqid();// add to haveSent / wereAcked maps, but don't add to requestMap } - if (_conn->usingHttpWait()) { + if (_connection->usingHttpWait()) { MTPHttpWait req(MTP_http_wait(MTP_int(100), MTP_int(30), MTP_int(25000))); httpWaitRequest = mtpRequestData::prepare(req.innerLength() >> 2); @@ -983,12 +1017,12 @@ void ConnectionPrivate::retryByTimer() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; - if (retryTimeout < 3) { - ++retryTimeout; - } else if (retryTimeout == 3) { - retryTimeout = 1000; - } else if (retryTimeout < 64000) { - retryTimeout *= 2; + if (_retryTimeout < 3) { + ++_retryTimeout; + } else if (_retryTimeout == 3) { + _retryTimeout = 1000; + } else if (_retryTimeout < 64000) { + _retryTimeout *= 2; } if (keyId == kRecreateKeyId) { if (sessionData->getKey()) { @@ -1003,8 +1037,8 @@ void ConnectionPrivate::retryByTimer() { } void ConnectionPrivate::restartNow() { - retryTimeout = 1; - retryTimer.stop(); + _retryTimeout = 1; + _retryTimer.cancel(); restart(); } @@ -1039,31 +1073,67 @@ void ConnectionPrivate::connectToServer(bool afterConfig) { } } - using Variants = DcOptions::Variants; - const auto kIPv4 = Variants::IPv4; - const auto kIPv6 = Variants::IPv6; - const auto kTcp = Variants::Tcp; - const auto kHttp = Variants::Http; - const auto variants = _instance->dcOptions()->lookup(bareDc, _dcType); - const auto useIPv4 = (_dcType == DcType::Temporary) ? true : _connectionOptions->useIPv4; - const auto useIPv6 = (_dcType == DcType::Temporary) ? false : _connectionOptions->useIPv6; - const auto useTcp = (_dcType == DcType::Temporary) ? true : _connectionOptions->useTcp; - const auto useHttp = (_dcType == DcType::Temporary) ? false : _connectionOptions->useHttp; - auto noIPv4 = (_dcType == DcType::Temporary) ? (variants.data[kIPv4][kTcp].port == 0) : (!useIPv4 || (variants.data[kIPv4][kHttp].port == 0)); - auto noIPv6 = (_dcType == DcType::Temporary) ? true : (!useIPv6 || (variants.data[kIPv6][kHttp].port == 0)); - if (noIPv4 && noIPv6) { + if (afterConfig && (!_testConnections.empty() || _connection)) { + return; + } + + destroyAllConnections(); + if (_connectionOptions->proxy.type == ProxyData::Type::Mtproto + && _dcType != DcType::Cdn) { + appendTestConnection( + DcOptions::Variants::Tcp, + _connectionOptions->proxy.host, + _connectionOptions->proxy.port, + ProtocolSecretFromPassword(_connectionOptions->proxy.password)); + } else { + using Variants = DcOptions::Variants; + const auto special = (_dcType == DcType::Temporary); + const auto variants = _instance->dcOptions()->lookup( + bareDc, + _dcType, + _connectionOptions->proxy.type != ProxyData::Type::None); + const auto useIPv4 = special ? true : _connectionOptions->useIPv4; + const auto useIPv6 = special ? false : _connectionOptions->useIPv6; + const auto useTcp = special ? true : _connectionOptions->useTcp; + const auto useHttp = special ? false : _connectionOptions->useHttp; + const auto skipAddress = !useIPv4 + ? Variants::IPv4 + : !useIPv6 + ? Variants::IPv6 + : Variants::AddressTypeCount; + const auto skipProtocol = !useTcp + ? Variants::Tcp + : !useHttp + ? Variants::Http + : Variants::ProtocolCount; + for (auto address = 0; address != Variants::AddressTypeCount; ++address) { + if (address == skipAddress) { + continue; + } + for (auto protocol = 0; protocol != Variants::ProtocolCount; ++protocol) { + if (protocol == skipProtocol) { + continue; + } + for (const auto &endpoint : variants.data[address][protocol]) { + appendTestConnection( + static_cast(protocol), + QString::fromStdString(endpoint.ip), + endpoint.port, + endpoint.protocolSecret); + } + } + } + } + if (_testConnections.empty()) { if (_instance->isKeysDestroyer()) { - LOG(("MTP Error: DC %1 options for IPv4 over HTTP not found for auth key destruction!").arg(_shiftedDcId)); - if (useIPv6 && noIPv6) LOG(("MTP Error: DC %1 options for IPv6 over HTTP not found for auth key destruction!").arg(_shiftedDcId)); + LOG(("MTP Error: DC %1 options for not found for auth key destruction!").arg(_shiftedDcId)); emit _instance->keyDestroyed(_shiftedDcId); return; } else if (afterConfig) { - LOG(("MTP Error: DC %1 options for IPv4 over HTTP not found right after config load!").arg(_shiftedDcId)); - if (useIPv6 && noIPv6) LOG(("MTP Error: DC %1 options for IPv6 over HTTP not found right after config load!").arg(_shiftedDcId)); + LOG(("MTP Error: DC %1 options for not found right after config load!").arg(_shiftedDcId)); return restart(); } - DEBUG_LOG(("MTP Info: DC %1 options for IPv4 over HTTP not found, waiting for config").arg(_shiftedDcId)); - if (useIPv6 && noIPv6) DEBUG_LOG(("MTP Info: DC %1 options for IPv6 over HTTP not found, waiting for config").arg(_shiftedDcId)); + DEBUG_LOG(("MTP Info: DC %1 options not found, waiting for config").arg(_shiftedDcId)); connect(_instance, SIGNAL(configLoaded()), this, SLOT(onConfigLoaded()), Qt::UniqueConnection); InvokeQueued(_instance, [instance = _instance] { instance->requestConfig(); @@ -1071,48 +1141,20 @@ void ConnectionPrivate::connectToServer(bool afterConfig) { return; } - if (afterConfig) { - if (_conn4 || _conn6) { - return; - } - } else if (getms(true) - _configWasFineAt > kRequestConfigTimeout) { + if (getms(true) - _configWasFineAt > kRequestConfigTimeout) { InvokeQueued(_instance, [instance = _instance] { instance->requestConfigIfOld(); }); } - createConn(!noIPv4, !noIPv6); - retryTimer.stop(); - _waitForConnectedTimer.stop(); + _retryTimer.cancel(); + _waitForConnectedTimer.cancel(); setState(ConnectingState); _pingId = _pingMsgId = _pingIdToSend = _pingSendAt = 0; - _pingSender.stop(); + _pingSender.cancel(); - if (!noIPv4) DEBUG_LOG(("MTP Info: creating IPv4 connection to %1:%2 (tcp) and %3:%4 (http)...").arg(variants.data[kIPv4][kTcp].ip.c_str()).arg(variants.data[kIPv4][kTcp].port).arg(variants.data[kIPv4][kHttp].ip.c_str()).arg(variants.data[kIPv4][kHttp].port)); - if (!noIPv6) DEBUG_LOG(("MTP Info: creating IPv6 connection to [%1]:%2 (tcp) and [%3]:%4 (http)...").arg(variants.data[kIPv6][kTcp].ip.c_str()).arg(variants.data[kIPv6][kTcp].port).arg(variants.data[kIPv4][kHttp].ip.c_str()).arg(variants.data[kIPv4][kHttp].port)); - - _waitForConnectedTimer.start(_waitForConnected); - if (auto conn = _conn4) { - auto endpoint = DcOptions::Endpoint(); - if (_connectionOptions->proxy.type == ProxyData::Type::Mtproto) { - endpoint.ip = _connectionOptions->proxy.host.toStdString(); - endpoint.port = _connectionOptions->proxy.port; - endpoint.flags = MTPDdcOption::Flag::f_tcpo_only; - } else { - endpoint = variants.data[kIPv4][kTcp]; - } - connect(conn, SIGNAL(connected()), this, SLOT(onConnected4())); - connect(conn, SIGNAL(disconnected()), this, SLOT(onDisconnected4())); - conn->connectTcp(endpoint); - conn->connectHttp(variants.data[kIPv4][kHttp]); - } - if (auto conn = _conn6) { - connect(conn, SIGNAL(connected()), this, SLOT(onConnected6())); - connect(conn, SIGNAL(disconnected()), this, SLOT(onDisconnected6())); - conn->connectTcp(variants.data[kIPv6][kTcp]); - conn->connectHttp(variants.data[kIPv6][kHttp]); - } + _waitForConnectedTimer.callOnce(_waitForConnected); } void ConnectionPrivate::restart() { @@ -1121,8 +1163,8 @@ void ConnectionPrivate::restart() { DEBUG_LOG(("MTP Info: restarting Connection")); - _waitForReceivedTimer.stop(); - _waitForConnectedTimer.stop(); + _waitForReceivedTimer.cancel(); + _waitForConnectedTimer.cancel(); auto key = sessionData->getKey(); if (key) { @@ -1148,18 +1190,19 @@ void ConnectionPrivate::restart() { resetSession(); } restarted = true; - if (retryTimer.isActive()) return; + if (_retryTimer.isActive()) return; - DEBUG_LOG(("MTP Info: restart timeout: %1ms").arg(retryTimeout)); - setState(-retryTimeout); + DEBUG_LOG(("MTP Info: restart timeout: %1ms").arg(_retryTimeout)); + setState(-_retryTimeout); } void ConnectionPrivate::onSentSome(uint64 size) { if (!_waitForReceivedTimer.isActive()) { auto remain = static_cast(_waitForReceived); - if (!oldConnection) { - auto remainBySize = size * _waitForReceived / 8192; // 8kb / sec, so 512 kb give 64 sec - remain = snap(remainBySize, remain, uint64(MTPMaxReceiveDelay)); + if (!_oldConnection) { + // 8kb / sec, so 512 kb give 64 sec + auto remainBySize = size * _waitForReceived / 8192; + remain = snap(remainBySize, remain, uint64(kMaxReceiveTimeout)); if (remain != _waitForReceived) { DEBUG_LOG(("Checking connect for request with size %1 bytes, delay will be %2").arg(size).arg(remain)); } @@ -1169,40 +1212,42 @@ void ConnectionPrivate::onSentSome(uint64 size) { } else if (isDownloadDcId(_shiftedDcId)) { remain *= kDownloadSessionsCount; } - _waitForReceivedTimer.start(remain); + _waitForReceivedTimer.callOnce(remain); } if (!firstSentAt) firstSentAt = getms(true); } void ConnectionPrivate::onReceivedSome() { - if (oldConnection) { - oldConnection = false; + if (_oldConnection) { + _oldConnection = false; DEBUG_LOG(("This connection marked as not old!")); } - oldConnectionTimer.start(MTPConnectionOldTimeout); - _waitForReceivedTimer.stop(); + _oldConnectionTimer.callOnce(kMarkConnectionOldTimeout); + _waitForReceivedTimer.cancel(); if (firstSentAt > 0) { int32 ms = getms(true) - firstSentAt; DEBUG_LOG(("MTP Info: response in %1ms, _waitForReceived: %2ms").arg(ms).arg(_waitForReceived)); - if (ms > 0 && ms * 2 < int32(_waitForReceived)) _waitForReceived = qMax(ms * 2, int32(MTPMinReceiveDelay)); + if (ms > 0 && ms * 2 < int32(_waitForReceived)) { + _waitForReceived = qMax(ms * 2, int32(kMinReceiveTimeout)); + } firstSentAt = -1; } } -void ConnectionPrivate::onOldConnection() { - oldConnection = true; - _waitForReceived = MTPMinReceiveDelay; +void ConnectionPrivate::markConnectionOld() { + _oldConnection = true; + _waitForReceived = kMinReceiveTimeout; DEBUG_LOG(("This connection marked as old! _waitForReceived now %1ms").arg(_waitForReceived)); } -void ConnectionPrivate::onPingSender() { +void ConnectionPrivate::sendPingByTimer() { if (_pingId) { - if (_pingSendAt + (MTPPingSendAfter - MTPPingSendAfterAuto - 1) * 1000LL < getms(true)) { - LOG(("Could not send ping for MTPPingSendAfter seconds, restarting...")); + if (_pingSendAt + kPingSendAfterForce - kPingSendAfter - TimeMs(1000) < getms(true)) { + LOG(("Could not send ping for some seconds, restarting...")); return restart(); } else { - _pingSender.start(_pingSendAt + (MTPPingSendAfter - MTPPingSendAfterAuto) * 1000LL - getms(true)); + _pingSender.callOnce(_pingSendAt + kPingSendAfterForce - kPingSendAfter - getms(true)); } } else { emit needToSendAsync(); @@ -1217,7 +1262,7 @@ void ConnectionPrivate::onPingSendForce() { } } -void ConnectionPrivate::onWaitReceivedFailed() { +void ConnectionPrivate::waitReceivedFailed() { Expects(_connectionOptions != nullptr); if (!_connectionOptions->useTcp) { @@ -1225,20 +1270,24 @@ void ConnectionPrivate::onWaitReceivedFailed() { } DEBUG_LOG(("MTP Info: bad connection, _waitForReceived: %1ms").arg(_waitForReceived)); - if (_waitForReceived < MTPMaxReceiveDelay) { + if (_waitForReceived < kMaxReceiveTimeout) { _waitForReceived *= 2; } doDisconnect(); restarted = true; - if (retryTimer.isActive()) return; + if (_retryTimer.isActive()) { + return; + } DEBUG_LOG(("MTP Info: immediate restart!")); InvokeQueued(this, [this] { connectToServer(); }); } -void ConnectionPrivate::onWaitConnectedFailed() { +void ConnectionPrivate::waitConnectedFailed() { DEBUG_LOG(("MTP Info: can't connect in %1ms").arg(_waitForConnected)); - if (_waitForConnected < MTPMaxConnectDelay) _waitForConnected *= 2; + if (_waitForConnected < kMaxConnectedTimeout) { + _waitForConnected *= 2; + } doDisconnect(); restarted = true; @@ -1247,17 +1296,8 @@ void ConnectionPrivate::onWaitConnectedFailed() { InvokeQueued(this, [this] { connectToServer(); }); } -void ConnectionPrivate::onWaitIPv4Failed() { - _conn = _conn6; - destroyConnection(_conn4); - - if (_conn) { - DEBUG_LOG(("MTP Info: can't connect through IPv4, using IPv6 connection.")); - - updateAuthKey(); - } else { - restart(); - } +void ConnectionPrivate::waitBetterFailed() { + confirmBestConnection(); } void ConnectionPrivate::doDisconnect() { @@ -1314,9 +1354,9 @@ void ConnectionPrivate::handleReceived() { return restartOnError(); } - while (!_conn->received().empty()) { - auto intsBuffer = std::move(_conn->received().front()); - _conn->received().pop_front(); + while (!_connection->received().empty()) { + auto intsBuffer = std::move(_connection->received().front()); + _connection->received().pop_front(); constexpr auto kExternalHeaderIntsCount = 6U; // 2 auth_key_id, 4 msg_key constexpr auto kEncryptedHeaderIntsCount = 8U; // 2 salt, 2 session, 2 msg_id, 1 seq_no, 1 length @@ -1502,7 +1542,7 @@ void ConnectionPrivate::handleReceived() { return restartOnError(); } - retryTimeout = 1; // reset restart() timer + _retryTimeout = 1; // reset restart() timer if (!sessionData->isCheckedKey()) { DEBUG_LOG(("MTP Info: marked auth key as checked")); @@ -1515,7 +1555,7 @@ void ConnectionPrivate::handleReceived() { } } } - if (_conn->needHttpWait()) { + if (_connection->needHttpWait()) { emit sendHttpWaitAsync(); } } @@ -2322,77 +2362,98 @@ void ConnectionPrivate::resendMany(QVector msgIds, qint64 msCanWait, bo emit resendManyAsync(msgIds, msCanWait, forceContainer, sendMsgStateInfo); } -void ConnectionPrivate::onConnected4() { - _waitForConnected = MTPMinConnectDelay; - _waitForConnectedTimer.stop(); - - _waitForIPv4Timer.stop(); - +void ConnectionPrivate::onConnected( + not_null connection) { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; - disconnect(_conn4, SIGNAL(connected()), this, SLOT(onConnected4())); - if (!_conn4->isConnected()) { - LOG(("Connection Error: not connected in onConnected4(), state: %1").arg(_conn4->debugState())); + disconnect(connection, &AbstractConnection::connected, nullptr, nullptr); + if (!connection->isConnected()) { + LOG(("Connection Error: not connected in onConnected(), " + "state: %1").arg(connection->debugState())); lockFinished.unlock(); return restart(); } - _conn = _conn4; - destroyConnection(_conn6); + _waitForConnected = kMinConnectedTimeout; + _waitForConnectedTimer.cancel(); - DEBUG_LOG(("MTP Info: connection through IPv4 succeed.")); + const auto i = ranges::find( + _testConnections, + connection.get(), + [](const TestConnection &test) { return test.data.get(); }); + Assert(i != end(_testConnections)); + const auto my = i->priority; + const auto j = ranges::find_if( + _testConnections, + [&](const TestConnection &test) { return test.priority > my; }); + if (j != end(_testConnections)) { + DEBUG_LOG(("MTP Info: connection %1 succeed, " + "waiting for %2.").arg(i->data->tag()).arg(j->data->tag())); + _waitForBetterTimer.callOnce(kWaitForBetterTimeout); + } else { + DEBUG_LOG(("MTP Info: connection through IPv4 succeed.")); + _waitForBetterTimer.cancel(); + _connection = std::move(i->data); + _testConnections.clear(); + + lockFinished.unlock(); + updateAuthKey(); + } +} + +void ConnectionPrivate::onDisconnected( + not_null connection) { + removeTestConnection(connection); + + if (_testConnections.empty()) { + if (!_connection || _connection == connection.get()) { + destroyAllConnections(); + restart(); + } + } else { + confirmBestConnection(); + } +} + +void ConnectionPrivate::confirmBestConnection() { + if (_waitForBetterTimer.isActive()) { + return; + } + const auto i = ranges::max_element( + _testConnections, + std::less<>(), + [](const TestConnection &test) { + return test.data->isConnected() ? test.priority : -1; + }); + Assert(i != end(_testConnections)); + if (!i->data->isConnected()) { + return; + } + + DEBUG_LOG(("MTP Info: can't connect through better, using %1." + ).arg(i->data->tag())); + + _connection = std::move(i->data); + _testConnections.clear(); - lockFinished.unlock(); updateAuthKey(); } -void ConnectionPrivate::onConnected6() { - _waitForConnected = MTPMinConnectDelay; - _waitForConnectedTimer.stop(); - - QReadLocker lockFinished(&sessionDataMutex); - if (!sessionData) return; - - disconnect(_conn6, SIGNAL(connected()), this, SLOT(onConnected6())); - if (!_conn6->isConnected()) { - LOG(("Connection Error: not connected in onConnected(), state: %1").arg(_conn6->debugState())); - - lockFinished.unlock(); - return restart(); - } - - DEBUG_LOG(("MTP Info: connection through IPv6 succeed, waiting IPv4 for %1ms.").arg(MTPIPv4ConnectionWaitTimeout)); - - _waitForIPv4Timer.start(MTPIPv4ConnectionWaitTimeout); -} - -void ConnectionPrivate::onDisconnected4() { - if (_conn && _conn == _conn6) return; // disconnected the unused - - if (_conn || !_conn6) { - destroyAllConnections(); - restart(); - } else { - destroyConnection(_conn4); - } -} - -void ConnectionPrivate::onDisconnected6() { - if (_conn && _conn == _conn4) return; // disconnected the unused - - if (_conn || !_conn4) { - destroyAllConnections(); - restart(); - } else { - destroyConnection(_conn6); - } +void ConnectionPrivate::removeTestConnection( + not_null connection) { + _testConnections.erase( + ranges::remove( + _testConnections, + connection.get(), + [](const TestConnection &test) { return test.data.get(); }), + end(_testConnections)); } void ConnectionPrivate::updateAuthKey() { QReadLocker lockFinished(&sessionDataMutex); - if (!sessionData || !_conn) return; + if (!sessionData || !_connection) return; _configWasFineAt = getms(true); @@ -2442,7 +2503,9 @@ void ConnectionPrivate::updateAuthKey() { MTPReq_pq_multi req_pq; req_pq.vnonce = _authKeyData->nonce; - connect(_conn, SIGNAL(receivedData()), this, SLOT(pqAnswered())); + connect(_connection, &AbstractConnection::receivedData, [=] { + pqAnswered(); + }); DEBUG_LOG(("AuthKey Info: sending Req_pq...")); lockFinished.unlock(); @@ -2450,13 +2513,13 @@ void ConnectionPrivate::updateAuthKey() { } void ConnectionPrivate::clearMessages() { - if (keyId && keyId != kRecreateKeyId && _conn) { - _conn->received().clear(); + if (keyId && keyId != kRecreateKeyId && _connection) { + _connection->received().clear(); } } void ConnectionPrivate::pqAnswered() { - disconnect(_conn, SIGNAL(receivedData()), this, SLOT(pqAnswered())); + disconnect(_connection, &AbstractConnection::receivedData, nullptr, nullptr); DEBUG_LOG(("AuthKey Info: receiving Req_pq answer...")); MTPReq_pq::ResponseType res_pq; @@ -2501,7 +2564,9 @@ void ConnectionPrivate::pqAnswered() { return restart(); } - connect(_conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered())); + connect(_connection, &AbstractConnection::receivedData, [=] { + dhParamsAnswered(); + }); DEBUG_LOG(("AuthKey Info: sending Req_DH_params...")); @@ -2545,7 +2610,7 @@ base::byte_vector ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data & } void ConnectionPrivate::dhParamsAnswered() { - disconnect(_conn, SIGNAL(receivedData()), this, SLOT(dhParamsAnswered())); + disconnect(_connection, &AbstractConnection::receivedData, nullptr, nullptr); DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer...")); MTPReq_DH_params::ResponseType res_DH_params; @@ -2688,7 +2753,9 @@ void ConnectionPrivate::dhClientParamsSend() { auto sdhEncString = encryptClientDHInner(client_dh_inner); - connect(_conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered())); + connect(_connection, &AbstractConnection::receivedData, [=] { + dhClientParamsAnswered(); + }); MTPSet_client_DH_params req_client_DH_params; req_client_DH_params.vnonce = _authKeyData->nonce; @@ -2729,7 +2796,7 @@ void ConnectionPrivate::dhClientParamsAnswered() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; - disconnect(_conn, SIGNAL(receivedData()), this, SLOT(dhClientParamsAnswered())); + disconnect(_connection, &AbstractConnection::receivedData, nullptr, nullptr); DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer...")); MTPSet_client_DH_params::ResponseType res_client_DH_params; @@ -2846,7 +2913,9 @@ void ConnectionPrivate::dhClientParamsAnswered() { void ConnectionPrivate::authKeyCreated() { clearAuthKeyData(); - connect(_conn, SIGNAL(receivedData()), this, SLOT(handleReceived())); + connect(_connection, &AbstractConnection::receivedData, [=] { + handleReceived(); + }); if (sessionData->getSalt()) { // else receive salt in bad_server_salt first, then try to send all the requests setState(ConnectedState); @@ -2888,35 +2957,30 @@ void ConnectionPrivate::clearAuthKeyData() { } } -void ConnectionPrivate::onError4(qint32 errorCode) { - if (_conn && _conn == _conn6) return; // error in the unused +void ConnectionPrivate::onError( + not_null connection, + qint32 errorCode) { + if (_connection) { + return; + } if (errorCode == -429) { LOG(("Protocol Error: -429 flood code returned!")); } - if (_conn || !_conn6) { - handleError(errorCode); - } else { - destroyConnection(_conn4); - } -} + removeTestConnection(connection); -void ConnectionPrivate::onError6(qint32 errorCode) { - if (_conn && _conn == _conn4) return; // error in the unused - - if (errorCode == -429) { - LOG(("Protocol Error: -429 flood code returned!")); - } - if (_conn || !_conn4) { - handleError(errorCode); + if (_testConnections.empty()) { + if (!_connection || _connection == connection.get()) { + handleError(errorCode); + } } else { - destroyConnection(_conn6); + confirmBestConnection(); } } void ConnectionPrivate::handleError(int errorCode) { destroyAllConnections(); - _waitForConnectedTimer.stop(); + _waitForConnectedTimer.cancel(); if (errorCode == -404) { if (_instance->isKeysDestroyer()) { @@ -2958,7 +3022,7 @@ void ConnectionPrivate::sendRequestNotSecure(const TRequest &request) { DEBUG_LOG(("AuthKey Info: sending request, size: %1, num: %2, time: %3").arg(requestSize).arg(_authKeyData->req_num).arg(buffer[5])); - _conn->sendData(buffer); + _connection->sendData(buffer); onSentSome(buffer.size() * sizeof(mtpPrime)); @@ -2972,13 +3036,13 @@ bool ConnectionPrivate::readResponseNotSecure(TResponse &response) { onReceivedSome(); try { - if (_conn->received().empty()) { + if (_connection->received().empty()) { LOG(("AuthKey Error: trying to read response from empty received list")); return false; } - auto buffer = std::move(_conn->received().front()); - _conn->received().pop_front(); + auto buffer = std::move(_connection->received().front()); + _connection->received().pop_front(); auto answer = buffer.constData(); auto len = buffer.size(); @@ -3073,8 +3137,8 @@ bool ConnectionPrivate::sendRequest(mtpRequest &request, bool needAnyResponse, Q DEBUG_LOG(("MTP Info: sending request, size: %1, num: %2, time: %3").arg(fullSize + 6).arg((*request)[4]).arg((*request)[5])); - _conn->setSentEncrypted(); - _conn->sendData(result); + _connection->setSentEncrypted(); + _connection->sendData(result); if (needAnyResponse) { onSentSome(result.size() * sizeof(mtpPrime)); @@ -3121,7 +3185,7 @@ void ConnectionPrivate::unlockKey() { ConnectionPrivate::~ConnectionPrivate() { clearAuthKeyData(); - Assert(_finished && _conn == nullptr && _conn4 == nullptr && _conn6 == nullptr); + Assert(_finished && _connection == nullptr && _testConnections.empty()); } void ConnectionPrivate::stop() { diff --git a/Telegram/SourceFiles/mtproto/connection.h b/Telegram/SourceFiles/mtproto/connection.h index 8542d2cca..a5d03082e 100644 --- a/Telegram/SourceFiles/mtproto/connection.h +++ b/Telegram/SourceFiles/mtproto/connection.h @@ -9,7 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/auth_key.h" #include "mtproto/dc_options.h" -#include "core/single_timer.h" +#include "mtproto/connection_abstract.h" +#include "base/timer.h" namespace MTP { @@ -57,7 +58,7 @@ public: HttpConnection }; - Connection(Instance *instance); + Connection(not_null instance); void start(SessionData *data, ShiftedDcId shiftedDcId); @@ -71,9 +72,9 @@ public: QString transport() const; private: - Instance *_instance = nullptr; - std::unique_ptr thread; - ConnectionPrivate *data = nullptr; + not_null _instance; + std::unique_ptr _thread; + ConnectionPrivate *_private = nullptr; }; @@ -81,7 +82,12 @@ class ConnectionPrivate : public QObject { Q_OBJECT public: - ConnectionPrivate(Instance *instance, QThread *thread, Connection *owner, SessionData *data, ShiftedDcId shiftedDcId); + ConnectionPrivate( + not_null instance, + not_null thread, + not_null owner, + not_null data, + ShiftedDcId shiftedDcId); ~ConnectionPrivate(); void stop(); @@ -109,29 +115,15 @@ signals: void finished(internal::Connection *connection); public slots: - void retryByTimer(); void restartNow(); - void onPingSender(); void onPingSendForce(); - void onWaitConnectedFailed(); - void onWaitReceivedFailed(); - void onWaitIPv4Failed(); - - void onOldConnection(); void onSentSome(uint64 size); void onReceivedSome(); void onReadyData(); - void onConnected4(); - void onConnected6(); - void onDisconnected4(); - void onDisconnected6(); - void onError4(qint32 errorCode); - void onError6(qint32 errorCode); - // Auth key creation packet receive slots void pqAnswered(); void dhParamsAnswered(); @@ -149,16 +141,32 @@ public slots: void onCDNConfigLoaded(); private: + struct TestConnection { + ConnectionPointer data; + int priority = 0; + }; void connectToServer(bool afterConfig = false); void doDisconnect(); void restart(); void finishAndDestroy(); void requestCDNConfig(); void handleError(int errorCode); + void onError( + not_null connection, + qint32 errorCode); + void onConnected(not_null connection); + void onDisconnected(not_null connection); + + void retryByTimer(); + void waitConnectedFailed(); + void waitReceivedFailed(); + void waitBetterFailed(); + void markConnectionOld(); + void sendPingByTimer(); - void createConn(bool createIPv4, bool createIPv6); void destroyAllConnections(); - void destroyConnection(AbstractConnection *&connection); + void confirmBestConnection(); + void removeTestConnection(not_null connection); mtpMsgId placeToContainer(mtpRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, mtpRequest &req); mtpMsgId prepareToSend(mtpRequest &request, mtpMsgId currentLastId); @@ -183,6 +191,26 @@ private: base::byte_vector encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key); std::string encryptClientDHInner(const MTPClient_DH_Inner_Data &data); + void appendTestConnection( + DcOptions::Variants::Protocol protocol, + const QString &ip, + int port, + const bytes::vector &protocolSecret); + + // if badTime received - search for ids in sessionData->haveSent and sessionData->wereAcked and sync time/salt, return true if found + bool requestsFixTimeSalt(const QVector &ids, int32 serverTime, uint64 serverSalt); + + // remove msgs with such ids from sessionData->haveSent, add to sessionData->wereAcked + void requestsAcked(const QVector &ids, bool byResponse = false); + + void resend(quint64 msgId, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false); + void resendMany(QVector msgIds, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false); + + template + void sendRequestNotSecure(const TRequest &request); + + template + bool readResponseNotSecure(TResponse &response); Instance *_instance = nullptr; DcType _dcType = DcType::Regular; @@ -194,45 +222,32 @@ private: void resetSession(); ShiftedDcId _shiftedDcId = 0; - Connection *_owner = nullptr; - AbstractConnection *_conn = nullptr; - AbstractConnection *_conn4 = nullptr; - AbstractConnection *_conn6 = nullptr; + not_null _owner; + ConnectionPointer _connection; + std::vector _testConnections; TimeMs _configWasFineAt = 0; - SingleTimer retryTimer; // exp retry timer - int retryTimeout = 1; - qint64 retryWillFinish; + base::Timer _retryTimer; // exp retry timer + int _retryTimeout = 1; + qint64 _retryWillFinish = 0; - SingleTimer oldConnectionTimer; - bool oldConnection = true; + base::Timer _oldConnectionTimer; + bool _oldConnection = true; - SingleTimer _waitForConnectedTimer, _waitForReceivedTimer, _waitForIPv4Timer; - uint32 _waitForReceived, _waitForConnected; + base::Timer _waitForConnectedTimer; + base::Timer _waitForReceivedTimer; + base::Timer _waitForBetterTimer; + uint32 _waitForReceived = 0; + uint32 _waitForConnected = 0; TimeMs firstSentAt = -1; QVector ackRequestData, resendRequestData; - // if badTime received - search for ids in sessionData->haveSent and sessionData->wereAcked and sync time/salt, return true if found - bool requestsFixTimeSalt(const QVector &ids, int32 serverTime, uint64 serverSalt); - - // remove msgs with such ids from sessionData->haveSent, add to sessionData->wereAcked - void requestsAcked(const QVector &ids, bool byResponse = false); - mtpPingId _pingId = 0; mtpPingId _pingIdToSend = 0; TimeMs _pingSendAt = 0; mtpMsgId _pingMsgId = 0; - SingleTimer _pingSender; - - void resend(quint64 msgId, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false); - void resendMany(QVector msgIds, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false); - - template - void sendRequestNotSecure(const TRequest &request); - - template - bool readResponseNotSecure(TResponse &response); + base::Timer _pingSender; bool restarted = false; bool _finished = false; diff --git a/Telegram/SourceFiles/mtproto/connection_abstract.cpp b/Telegram/SourceFiles/mtproto/connection_abstract.cpp index 36cff0919..649415e02 100644 --- a/Telegram/SourceFiles/mtproto/connection_abstract.cpp +++ b/Telegram/SourceFiles/mtproto/connection_abstract.cpp @@ -9,12 +9,71 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mtproto/connection_tcp.h" #include "mtproto/connection_http.h" -#include "mtproto/connection_auto.h" #include "mtproto/session.h" namespace MTP { namespace internal { +ConnectionPointer::ConnectionPointer() = default; + +ConnectionPointer::ConnectionPointer(std::nullptr_t) { +} + +ConnectionPointer::ConnectionPointer(AbstractConnection *value) +: _value(value) { +} + +ConnectionPointer::ConnectionPointer(ConnectionPointer &&other) +: _value(base::take(other._value)) { +} + +ConnectionPointer &ConnectionPointer::operator=(ConnectionPointer &&other) { + reset(base::take(other._value)); + return *this; +} + +AbstractConnection *ConnectionPointer::get() const { + return _value; +} + +void ConnectionPointer::reset(AbstractConnection *value) { + if (_value == value) { + return; + } else if (const auto old = base::take(_value)) { + const auto disconnect = [&](auto signal) { + old->disconnect(old, signal, nullptr, nullptr); + }; + disconnect(&AbstractConnection::receivedData); + disconnect(&AbstractConnection::receivedSome); + disconnect(&AbstractConnection::error); + disconnect(&AbstractConnection::connected); + disconnect(&AbstractConnection::disconnected); + old->disconnectFromServer(); + old->deleteLater(); + } + _value = value; +} + +ConnectionPointer::operator AbstractConnection*() const { + return get(); +} + +AbstractConnection *ConnectionPointer::operator->() const { + return get(); +} + +AbstractConnection &ConnectionPointer::operator*() const { + return *get(); +} + +ConnectionPointer::operator bool() const { + return get() != nullptr; +} + +ConnectionPointer::~ConnectionPointer() { + reset(); +} + AbstractConnection::~AbstractConnection() { } @@ -63,20 +122,14 @@ MTPResPQ AbstractConnection::readPQFakeReply(const mtpBuffer &buffer) { return response; } -AbstractConnection *AbstractConnection::create( - const ConnectionOptions &options, - ShiftedDcId shiftedDcId, - DcType type, +ConnectionPointer AbstractConnection::create( + DcOptions::Variants::Protocol protocol, QThread *thread) { - const auto protocolDcId = (type == DcType::MediaDownload) - ? -MTP::bareDcId(shiftedDcId) - : MTP::bareDcId(shiftedDcId); - if ((type == DcType::Temporary) || (!options.useHttp)) { - return new TCPConnection(thread, protocolDcId); - } else if (!options.useTcp) { - return new HTTPConnection(thread); + if (protocol == DcOptions::Variants::Tcp) { + return ConnectionPointer(new TCPConnection(thread)); + } else { + return ConnectionPointer(new HTTPConnection(thread)); } - return new AutoConnection(thread, protocolDcId); } } // namespace internal diff --git a/Telegram/SourceFiles/mtproto/connection_abstract.h b/Telegram/SourceFiles/mtproto/connection_abstract.h index e1082e076..ff5e18892 100644 --- a/Telegram/SourceFiles/mtproto/connection_abstract.h +++ b/Telegram/SourceFiles/mtproto/connection_abstract.h @@ -8,12 +8,37 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #include "mtproto/dc_options.h" +#include "base/bytes.h" namespace MTP { namespace internal { struct ConnectionOptions; +class AbstractConnection; + +class ConnectionPointer { +public: + ConnectionPointer(); + ConnectionPointer(std::nullptr_t); + explicit ConnectionPointer(AbstractConnection *value); + ConnectionPointer(ConnectionPointer &&other); + ConnectionPointer &operator=(ConnectionPointer &&other); + + AbstractConnection *get() const; + void reset(AbstractConnection *value = nullptr); + operator AbstractConnection*() const; + AbstractConnection *operator->() const; + AbstractConnection &operator*() const; + explicit operator bool() const; + + ~ConnectionPointer(); + +private: + AbstractConnection *_value = nullptr; + +}; + class AbstractConnection : public QObject { Q_OBJECT @@ -26,10 +51,8 @@ public: virtual ~AbstractConnection() = 0; // virtual constructor - static AbstractConnection *create( - const ConnectionOptions &options, - ShiftedDcId shiftedDcId, - DcType type, + static ConnectionPointer create( + DcOptions::Variants::Protocol protocol, QThread *thread); void setSentEncrypted() { @@ -38,8 +61,11 @@ public: virtual void sendData(mtpBuffer &buffer) = 0; // has size + 3, buffer[0] = len, buffer[1] = packetnum, buffer[last] = crc32 virtual void disconnectFromServer() = 0; - virtual void connectTcp(const DcOptions::Endpoint &endpoint) = 0; - virtual void connectHttp(const DcOptions::Endpoint &endpoint) = 0; + virtual void connectToServer( + const QString &ip, + int port, + const bytes::vector &protocolSecret, + int16 protocolDcId) = 0; virtual bool isConnected() const = 0; virtual bool usingHttpWait() { return false; @@ -51,6 +77,7 @@ public: virtual int32 debugState() const = 0; virtual QString transport() const = 0; + virtual QString tag() const = 0; using BuffersQueue = std::deque; BuffersQueue &received() { diff --git a/Telegram/SourceFiles/mtproto/connection_auto.cpp b/Telegram/SourceFiles/mtproto/connection_auto.cpp deleted file mode 100644 index 87f895c3d..000000000 --- a/Telegram/SourceFiles/mtproto/connection_auto.cpp +++ /dev/null @@ -1,323 +0,0 @@ -/* -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 "mtproto/connection_auto.h" - -#include "mtproto/connection_http.h" - -namespace MTP { -namespace internal { - -AutoConnection::AutoConnection(QThread *thread, int16 protocolDcId) -: AbstractTCPConnection(thread, protocolDcId) -, status(WaitingBoth) -, tcpNonce(rand_value()) -, httpNonce(rand_value()) -, _flagsTcp(0) -, _flagsHttp(0) -, _tcpTimeout(MTPMinReceiveDelay) { - manager.moveToThread(thread); - - httpStartTimer.moveToThread(thread); - httpStartTimer.setSingleShot(true); - connect(&httpStartTimer, SIGNAL(timeout()), this, SLOT(onHttpStart())); - - tcpTimeoutTimer.moveToThread(thread); - tcpTimeoutTimer.setSingleShot(true); - connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer())); - - sock.moveToThread(thread); - connect(&sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError))); - connect(&sock, SIGNAL(connected()), this, SLOT(onSocketConnected())); - connect(&sock, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected())); -} - -void AutoConnection::onHttpStart() { - if (status == HttpReady) { - DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by timer").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - status = UsingHttp; - sock.disconnectFromHost(); - emit connected(); - } -} - -void AutoConnection::onSocketConnected() { - if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) { - mtpBuffer buffer(preparePQFake(tcpNonce)); - - DEBUG_LOG(("Connection Info: sending fake req_pq through TCP/%1 transport").arg((_flagsTcp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - - if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout; - tcpTimeoutTimer.start(_tcpTimeout); - - tcpSend(buffer); - } else if (status == WaitingHttp || status == UsingHttp) { - sock.disconnectFromHost(); - } -} - -void AutoConnection::onTcpTimeoutTimer() { - if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) { - if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2; - _tcpTimeout = -_tcpTimeout; - - QAbstractSocket::SocketState state = sock.state(); - if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) { - sock.disconnectFromHost(); - } else if (state != QAbstractSocket::ClosingState) { - sock.connectToHost(QHostAddress(_addrTcp), _portTcp); - } - } -} - -void AutoConnection::onSocketDisconnected() { - if (_tcpTimeout < 0) { - _tcpTimeout = -_tcpTimeout; - if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) { - sock.connectToHost(QHostAddress(_addrTcp), _portTcp); - return; - } - } - if (status == WaitingBoth) { - status = WaitingHttp; - } else if (status == WaitingTcp || status == UsingTcp) { - emit disconnected(); - } else if (status == HttpReady) { - DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by socket disconnect").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - status = UsingHttp; - emit connected(); - } -} - -void AutoConnection::sendData(mtpBuffer &buffer) { - if (status == FinishedWork) return; - - if (buffer.size() < 3) { - LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime))); - TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str())); - emit error(kErrorCodeOther); - return; - } - - if (status == UsingTcp) { - tcpSend(buffer); - } else { - httpSend(buffer); - } -} - -void AutoConnection::httpSend(mtpBuffer &buffer) { - int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime); - - QNetworkRequest request(address); - request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize)); - request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded"))); - - TCP_LOG(("HTTP Info: sending %1 len request").arg(requestSize)); - requests.insert(manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize))); -} - -void AutoConnection::disconnectFromServer() { - if (status == FinishedWork) return; - status = FinishedWork; - - Requests copy = requests; - requests.clear(); - for (Requests::const_iterator i = copy.cbegin(), e = copy.cend(); i != e; ++i) { - (*i)->abort(); - (*i)->deleteLater(); - } - - disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); - - address = QUrl(); - - disconnect(&sock, SIGNAL(readyRead()), 0, 0); - sock.close(); - - httpStartTimer.stop(); -} - -void AutoConnection::connectTcp(const DcOptions::Endpoint &endpoint) { - _addrTcp = QString::fromStdString(endpoint.ip); - _portTcp = endpoint.port; - _flagsTcp = endpoint.flags; - - connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead())); - sock.connectToHost(QHostAddress(_addrTcp), _portTcp); -} - -void AutoConnection::connectHttp(const DcOptions::Endpoint &endpoint) { - _addrHttp = QString::fromStdString(endpoint.ip); - _portHttp = endpoint.port; - _flagsHttp = endpoint.flags; - - // not endpoint.port - always 80 port for http transport - address = QUrl(((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(_addrHttp).arg(80)); - TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString())); - connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); - - mtpBuffer buffer(preparePQFake(httpNonce)); - - DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP/%1 transport").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - - httpSend(buffer); -} - -bool AutoConnection::isConnected() const { - return (status == UsingTcp) || (status == UsingHttp); -} - -void AutoConnection::requestFinished(QNetworkReply *reply) { - if (status == FinishedWork) return; - - reply->deleteLater(); - if (reply->error() == QNetworkReply::NoError) { - requests.remove(reply); - - mtpBuffer data = HTTPConnection::handleResponse(reply); - if (data.size() == 1) { - if (status == WaitingBoth) { - status = WaitingTcp; - } else { - emit error(data[0]); - } - } else if (!data.isEmpty()) { - if (status == UsingHttp) { - _receivedQueue.push_back(data); - emit receivedData(); - } else if (status == WaitingBoth || status == WaitingHttp) { - try { - auto res_pq = readPQFakeReply(data); - const auto &res_pq_data(res_pq.c_resPQ()); - if (res_pq_data.vnonce == httpNonce) { - if (status == WaitingBoth) { - status = HttpReady; - httpStartTimer.start(MTPTcpConnectionWaitTimeout); - } else { - DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by pq-response, awaited").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - status = UsingHttp; - sock.disconnectFromHost(); - emit connected(); - } - } - } catch (Exception &e) { - DEBUG_LOG(("Connection Error: exception in parsing HTTP fake pq-responce, %1").arg(e.what())); - if (status == WaitingBoth) { - status = WaitingTcp; - } else { - emit error(kErrorCodeOther); - } - } - } else if (status == UsingTcp) { - DEBUG_LOG(("Connection Info: already using tcp, ignoring http response")); - } - } - } else { - if (!requests.remove(reply)) { - return; - } - - if (status == WaitingBoth) { - status = WaitingTcp; - } else if (status == WaitingHttp || status == UsingHttp) { - emit error(HTTPConnection::handleError(reply)); - } else { - LOG(("Strange Http Error: status %1").arg(status)); - } - } -} - -void AutoConnection::socketPacket(const char *packet, uint32 length) { - if (status == FinishedWork) return; - - mtpBuffer data = AbstractTCPConnection::handleResponse(packet, length); - if (data.size() == 1) { - if (status == WaitingBoth) { - status = WaitingHttp; - sock.disconnectFromHost(); - } else if (status == HttpReady) { - DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by bad tcp response, ready").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - status = UsingHttp; - sock.disconnectFromHost(); - emit connected(); - } else if (status == WaitingTcp || status == UsingTcp) { - emit error(data[0]); - } else { - LOG(("Strange Tcp Error; status %1").arg(status)); - } - } else if (status == UsingTcp) { - _receivedQueue.push_back(data); - emit receivedData(); - } else if (status == WaitingBoth || status == WaitingTcp || status == HttpReady) { - tcpTimeoutTimer.stop(); - try { - auto res_pq = readPQFakeReply(data); - const auto &res_pq_data(res_pq.c_resPQ()); - if (res_pq_data.vnonce == tcpNonce) { - DEBUG_LOG(("Connection Info: TCP/%1-transport chosen by pq-response").arg((_flagsTcp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - status = UsingTcp; - emit connected(); - } - } catch (Exception &e) { - DEBUG_LOG(("Connection Error: exception in parsing TCP fake pq-responce, %1").arg(e.what())); - if (status == WaitingBoth) { - status = WaitingHttp; - sock.disconnectFromHost(); - } else if (status == HttpReady) { - DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by bad tcp response, awaited").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - status = UsingHttp; - sock.disconnectFromHost(); - emit connected(); - } else { - emit error(kErrorCodeOther); - } - } - } -} - -bool AutoConnection::usingHttpWait() { - return (status == UsingHttp); -} - -bool AutoConnection::needHttpWait() { - return (status == UsingHttp) ? requests.isEmpty() : false; -} - -int32 AutoConnection::debugState() const { - return (status == UsingHttp) ? -1 : ((status == UsingTcp) ? sock.state() : -777); -} - -QString AutoConnection::transport() const { - if (status == UsingTcp) { - return qsl("TCP"); - } else if (status == UsingHttp) { - return qsl("HTTP"); - } else { - return QString(); - } -} - -void AutoConnection::socketError(QAbstractSocket::SocketError e) { - if (status == FinishedWork) return; - - AbstractTCPConnection::handleError(e, sock); - if (status == WaitingBoth) { - status = WaitingHttp; - } else if (status == HttpReady) { - DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by tcp error, ready").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); - status = UsingHttp; - emit connected(); - } else if (status == WaitingTcp || status == UsingTcp) { - emit error(kErrorCodeOther); - } else { - LOG(("Strange Tcp Error: status %1").arg(status)); - } -} - -} // namespace internal -} // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/connection_auto.h b/Telegram/SourceFiles/mtproto/connection_auto.h deleted file mode 100644 index b43afe9c4..000000000 --- a/Telegram/SourceFiles/mtproto/connection_auto.h +++ /dev/null @@ -1,76 +0,0 @@ -/* -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 -*/ -#pragma once - -#include "mtproto/connection_tcp.h" - -namespace MTP { -namespace internal { - -class AutoConnection : public AbstractTCPConnection { - Q_OBJECT - -public: - AutoConnection(QThread *thread, int16 protocolDcId); - - void sendData(mtpBuffer &buffer) override; - void disconnectFromServer() override; - void connectTcp(const DcOptions::Endpoint &endpoint) override; - void connectHttp(const DcOptions::Endpoint &endpoint) override; - bool isConnected() const override; - bool usingHttpWait() override; - bool needHttpWait() override; - - int32 debugState() const override; - - QString transport() const override; - -public slots: - void socketError(QAbstractSocket::SocketError e); - void requestFinished(QNetworkReply *reply); - - void onSocketConnected(); - void onSocketDisconnected(); - void onHttpStart(); - - void onTcpTimeoutTimer(); - -protected: - void socketPacket(const char *packet, uint32 length) override; - -private: - void httpSend(mtpBuffer &buffer); - enum Status { - WaitingBoth = 0, - WaitingHttp, - WaitingTcp, - HttpReady, - UsingHttp, - UsingTcp, - FinishedWork - }; - Status status; - MTPint128 tcpNonce, httpNonce; - QTimer httpStartTimer; - - QNetworkAccessManager manager; - QUrl address; - - typedef QSet Requests; - Requests requests; - - QString _addrTcp, _addrHttp; - int32 _portTcp, _portHttp; - MTPDdcOption::Flags _flagsTcp, _flagsHttp; - int32 _tcpTimeout; - QTimer tcpTimeoutTimer; - -}; - -} // namespace internal -} // namespace MTP diff --git a/Telegram/SourceFiles/mtproto/connection_http.cpp b/Telegram/SourceFiles/mtproto/connection_http.cpp index 87337046a..e7fc0c156 100644 --- a/Telegram/SourceFiles/mtproto/connection_http.cpp +++ b/Telegram/SourceFiles/mtproto/connection_http.cpp @@ -7,8 +7,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "mtproto/connection_http.h" +#include "base/qthelp_url.h" + namespace MTP { namespace internal { +namespace { + +constexpr auto kForceHttpPort = 80; + +} // namespace mtpBuffer HTTPConnection::handleResponse(QNetworkReply *reply) { QByteArray response = reply->readAll(); @@ -76,8 +83,7 @@ qint32 HTTPConnection::handleError(QNetworkReply *reply) { // returnes "maybe ba HTTPConnection::HTTPConnection(QThread *thread) : AbstractConnection(thread) , status(WaitingHttp) -, httpNonce(rand_value()) -, _flags(0) { +, httpNonce(rand_value()) { manager.moveToThread(thread); } @@ -93,7 +99,7 @@ void HTTPConnection::sendData(mtpBuffer &buffer) { int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime); - QNetworkRequest request(address); + QNetworkRequest request(url()); request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize)); request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded"))); @@ -113,22 +119,20 @@ void HTTPConnection::disconnectFromServer() { } disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); - - address = QUrl(); } -void HTTPConnection::connectHttp(const DcOptions::Endpoint &endpoint) { - _flags = endpoint.flags; - auto addr = QString::fromStdString(endpoint.ip); - - // not endpoint.port - always 80 port for http transport - address = QUrl(((_flags & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80)); - TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString())); +void HTTPConnection::connectToServer( + const QString &ip, + int port, + const bytes::vector &protocolSecret, + int16 protocolDcId) { + _address = ip; + TCP_LOG(("HTTP Info: address is %1").arg(url().toDisplayString())); connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*))); mtpBuffer buffer(preparePQFake(httpNonce)); - DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP/%1 transport").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); + DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP transport to %1").arg(ip)); sendData(buffer); } @@ -156,7 +160,7 @@ void HTTPConnection::requestFinished(QNetworkReply *reply) { auto res_pq = readPQFakeReply(data); const auto &res_pq_data(res_pq.c_resPQ()); if (res_pq_data.vnonce == httpNonce) { - DEBUG_LOG(("Connection Info: HTTP/%1-transport connected by pq-response").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); + DEBUG_LOG(("Connection Info: HTTP-transport to %1 connected by pq-response").arg(_address)); status = UsingHttp; emit connected(); } @@ -188,11 +192,33 @@ int32 HTTPConnection::debugState() const { } QString HTTPConnection::transport() const { - if (status == UsingHttp) { - return qsl("HTTP"); - } else { + if (!isConnected()) { return QString(); } + auto result = qsl("HTTP"); + if (qthelp::is_ipv6(_address)) { + result += qsl("/IPv6"); + } + return result; +} + +QString HTTPConnection::tag() const { + auto result = qsl("HTTP"); + if (qthelp::is_ipv6(_address)) { + result += qsl("/IPv6"); + } else { + result += qsl("/IPv4"); + } + return result; +} + +QUrl HTTPConnection::url() const { + const auto pattern = qthelp::is_ipv6(_address) + ? qsl("http://[%1]:%2/api") + : qsl("http://%1:%2/api"); + + // Not endpoint.port - always 80 port for http transport. + return QUrl(pattern.arg(_address).arg(kForceHttpPort)); } } // namespace internal diff --git a/Telegram/SourceFiles/mtproto/connection_http.h b/Telegram/SourceFiles/mtproto/connection_http.h index 8f6adc7aa..6fc53e23a 100644 --- a/Telegram/SourceFiles/mtproto/connection_http.h +++ b/Telegram/SourceFiles/mtproto/connection_http.h @@ -20,9 +20,11 @@ public: void sendData(mtpBuffer &buffer) override; void disconnectFromServer() override; - void connectTcp(const DcOptions::Endpoint &endpoint) override { // not supported - } - void connectHttp(const DcOptions::Endpoint &endpoint) override; + void connectToServer( + const QString &ip, + int port, + const bytes::vector &protocolSecret, + int16 protocolDcId) override; bool isConnected() const override; bool usingHttpWait() override; bool needHttpWait() override; @@ -30,6 +32,7 @@ public: int32 debugState() const override; QString transport() const override; + QString tag() const override; static mtpBuffer handleResponse(QNetworkReply *reply); static qint32 handleError(QNetworkReply *reply); // returnes error code @@ -38,6 +41,8 @@ public slots: void requestFinished(QNetworkReply *reply); private: + QUrl url() const; + enum Status { WaitingHttp = 0, UsingHttp, @@ -45,10 +50,9 @@ private: }; Status status; MTPint128 httpNonce; - MTPDdcOption::Flags _flags; QNetworkAccessManager manager; - QUrl address; + QString _address; typedef QSet Requests; Requests requests; diff --git a/Telegram/SourceFiles/mtproto/connection_tcp.cpp b/Telegram/SourceFiles/mtproto/connection_tcp.cpp index 255c5f646..98791ca26 100644 --- a/Telegram/SourceFiles/mtproto/connection_tcp.cpp +++ b/Telegram/SourceFiles/mtproto/connection_tcp.cpp @@ -9,13 +9,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/bytes.h" #include "base/openssl_help.h" +#include "base/qthelp_url.h" #include namespace MTP { namespace internal { - namespace { +constexpr auto kMinReceiveTimeout = TimeMs(2000); +constexpr auto kMaxReceiveTimeout = TimeMs(8000); + uint32 tcpPacketSize(const char *packet) { // must have at least 4 bytes readable uint32 result = (packet[0] > 0) ? packet[0] : 0; if (result == 0x7f) { @@ -29,14 +32,8 @@ uint32 tcpPacketSize(const char *packet) { // must have at least 4 bytes readabl } // namespace AbstractTCPConnection::AbstractTCPConnection( - QThread *thread, - int16 protocolDcId) + QThread *thread) : AbstractConnection(thread) -, _protocolDcId(protocolDcId) -, packetNum(0) -, packetRead(0) -, packetLeft(0) -, readingToShort(true) , currentPos((char*)shortBuffer) { } @@ -197,12 +194,11 @@ void AbstractTCPConnection::handleError(QAbstractSocket::SocketError e, QTcpSock TCP_LOG(("TCP Error %1, restarting! - %2").arg(e).arg(sock.errorString())); } -TCPConnection::TCPConnection(QThread *thread, int16 protocolDcId) -: AbstractTCPConnection(thread, protocolDcId) +TCPConnection::TCPConnection(QThread *thread) +: AbstractTCPConnection(thread) , status(WaitingTcp) , tcpNonce(rand_value()) -, _tcpTimeout(MTPMinReceiveDelay) -, _flags(0) { +, _tcpTimeout(kMinReceiveTimeout) { tcpTimeoutTimer.moveToThread(thread); tcpTimeoutTimer.setSingleShot(true); connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer())); @@ -217,7 +213,7 @@ void TCPConnection::onSocketConnected() { if (status == WaitingTcp) { mtpBuffer buffer(preparePQFake(tcpNonce)); - DEBUG_LOG(("Connection Info: sending fake req_pq through TCP/%1 transport").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); + DEBUG_LOG(("Connection Info: sending fake req_pq through TCP transport to %1").arg(_address)); if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout; tcpTimeoutTimer.start(_tcpTimeout); @@ -228,14 +224,16 @@ void TCPConnection::onSocketConnected() { void TCPConnection::onTcpTimeoutTimer() { if (status == WaitingTcp) { - if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2; + if (_tcpTimeout < kMaxReceiveTimeout) { + _tcpTimeout *= 2; + } _tcpTimeout = -_tcpTimeout; QAbstractSocket::SocketState state = sock.state(); if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) { sock.disconnectFromHost(); } else if (state != QAbstractSocket::ClosingState) { - sock.connectToHost(QHostAddress(_addr), _port); + sock.connectToHost(QHostAddress(_address), _port); } } } @@ -244,7 +242,7 @@ void TCPConnection::onSocketDisconnected() { if (_tcpTimeout < 0) { _tcpTimeout = -_tcpTimeout; if (status == WaitingTcp) { - sock.connectToHost(QHostAddress(_addr), _port); + sock.connectToHost(QHostAddress(_address), _port); return; } } @@ -291,8 +289,19 @@ void AbstractTCPConnection::writeConnectionStart() { || *first == reserved15 || *second == reserved21); + const auto prepareKey = [&](bytes::span key, bytes::const_span from) { + if (_protocolSecret.size() == 16) { + const auto payload = bytes::concatenate(from, _protocolSecret); + bytes::copy(key, openssl::Sha256(payload)); + } else if (_protocolSecret.empty()) { + bytes::copy(key, from); + } else { + bytes::set_with_const(key, gsl::byte{}); + } + }; + // prepare encryption key/iv - bytes::copy( + prepareKey( bytes::make_span(_sendKey), nonce.subspan(8, CTRState::KeySize)); bytes::copy( @@ -304,7 +313,7 @@ void AbstractTCPConnection::writeConnectionStart() { const auto reversed = bytes::make_span(reversedBytes); bytes::copy(reversed, nonce.subspan(8, reversed.size())); std::reverse(reversed.begin(), reversed.end()); - bytes::copy( + prepareKey( bytes::make_span(_receiveKey), reversed.subspan(0, CTRState::KeySize)); bytes::copy( @@ -356,14 +365,18 @@ void TCPConnection::disconnectFromServer() { sock.close(); } -void TCPConnection::connectTcp(const DcOptions::Endpoint &endpoint) { - _addr = QString::fromStdString(endpoint.ip); - _port = endpoint.port; - _flags = endpoint.flags; +void TCPConnection::connectToServer( + const QString &ip, + int port, + const bytes::vector &protocolSecret, + int16 protocolDcId) { + _address = ip; + _port = port; + _protocolSecret = protocolSecret; + _protocolDcId = protocolDcId; connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead())); - sock.connectToHost(QHostAddress(_addr), _port); - auto proxy = sock.proxy(); + sock.connectToHost(QHostAddress(_address), _port); } void TCPConnection::socketPacket(const char *packet, uint32 length) { @@ -381,7 +394,7 @@ void TCPConnection::socketPacket(const char *packet, uint32 length) { auto res_pq = readPQFakeReply(data); const auto &res_pq_data(res_pq.c_resPQ()); if (res_pq_data.vnonce == tcpNonce) { - DEBUG_LOG(("Connection Info: TCP/%1-transport chosen by pq-response").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4")); + DEBUG_LOG(("Connection Info: TCP-transport to %1 chosen by pq-response").arg(_address)); status = UsingTcp; emit connected(); } @@ -401,7 +414,24 @@ int32 TCPConnection::debugState() const { } QString TCPConnection::transport() const { - return isConnected() ? qsl("TCP") : QString(); + if (!isConnected()) { + return QString(); + } + auto result = qsl("TCP"); + if (qthelp::is_ipv6(_address)) { + result += qsl("/IPv6"); + } + return result; +} + +QString TCPConnection::tag() const { + auto result = qsl("TCP"); + if (qthelp::is_ipv6(_address)) { + result += qsl("/IPv6"); + } else { + result += qsl("/IPv4"); + } + return result; } void TCPConnection::socketError(QAbstractSocket::SocketError e) { diff --git a/Telegram/SourceFiles/mtproto/connection_tcp.h b/Telegram/SourceFiles/mtproto/connection_tcp.h index 9470ee0e2..7b619a085 100644 --- a/Telegram/SourceFiles/mtproto/connection_tcp.h +++ b/Telegram/SourceFiles/mtproto/connection_tcp.h @@ -17,7 +17,7 @@ class AbstractTCPConnection : public AbstractConnection { Q_OBJECT public: - AbstractTCPConnection(QThread *thread, int16 protocolDcId); + AbstractTCPConnection(QThread *thread); virtual ~AbstractTCPConnection() = 0; public slots: @@ -27,10 +27,11 @@ protected: void writeConnectionStart(); QTcpSocket sock; - uint32 packetNum; // sent packet number + uint32 packetNum = 0; // sent packet number - uint32 packetRead, packetLeft; // reading from socket - bool readingToShort; + uint32 packetRead = 0; + uint32 packetLeft = 0; // reading from socket + bool readingToShort = true; char *currentPos; mtpBuffer longBuffer; mtpPrime shortBuffer[MTPShortBufferSize]; @@ -49,6 +50,7 @@ protected: uchar _receiveKey[CTRState::KeySize]; CTRState _receiveState; int16 _protocolDcId = 0; + bytes::vector _protocolSecret; }; @@ -56,21 +58,23 @@ class TCPConnection : public AbstractTCPConnection { Q_OBJECT public: - TCPConnection(QThread *thread, int16 protocolDcId); + TCPConnection(QThread *thread); void sendData(mtpBuffer &buffer) override; void disconnectFromServer() override; - void connectTcp(const DcOptions::Endpoint &endpoint) override; - void connectHttp(const DcOptions::Endpoint &endpoint) override { // not supported - } + void connectToServer( + const QString &ip, + int port, + const bytes::vector &protocolSecret, + int16 protocolDcId) override; bool isConnected() const override; int32 debugState() const override; QString transport() const override; + QString tag() const override; public slots: - void socketError(QAbstractSocket::SocketError e); void onSocketConnected(); @@ -79,11 +83,9 @@ public slots: void onTcpTimeoutTimer(); protected: - void socketPacket(const char *packet, uint32 length) override; private: - enum Status { WaitingTcp = 0, UsingTcp, @@ -92,9 +94,8 @@ private: Status status; MTPint128 tcpNonce; - QString _addr; + QString _address; int32 _port, _tcpTimeout; - MTPDdcOption::Flags _flags; QTimer tcpTimeoutTimer; }; diff --git a/Telegram/SourceFiles/mtproto/dc_options.cpp b/Telegram/SourceFiles/mtproto/dc_options.cpp index 0a4eac121..238d721db 100644 --- a/Telegram/SourceFiles/mtproto/dc_options.cpp +++ b/Telegram/SourceFiles/mtproto/dc_options.cpp @@ -105,22 +105,36 @@ void DcOptions::constructFromBuiltIn() { auto bdcs = builtInDcs(); for (auto i = 0, l = builtInDcsCount(); i != l; ++i) { - auto flags = MTPDdcOption::Flags(0); - auto idWithShift = MTP::shiftDcId(bdcs[i].id, flags); - _data.emplace(idWithShift, Option(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port)); - DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port)); + const auto flags = MTPDdcOption::Flags(0); + const auto bdc = bdcs[i]; + const auto idWithShift = MTP::shiftDcId(bdc.id, flags); + _data.emplace( + idWithShift, + std::vector