/* 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.h" #include "mtproto/session.h" #include "mtproto/rsa_public_key.h" #include "mtproto/rpc_sender.h" #include "mtproto/dc_options.h" #include "mtproto/connection_abstract.h" #include "zlib.h" #include "core/application.h" #include "core/launcher.h" #include "lang/lang_keys.h" #include "base/openssl_help.h" #include "base/qthelp_url.h" extern "C" { #include #include #include #include #include #include } // extern "C" #ifdef small #undef small #endif // small namespace MTP { namespace internal { namespace { constexpr auto kRecreateKeyId = AuthKey::KeyId(0xFFFFFFFFFFFFFFFFULL); constexpr auto kIntSize = static_cast(sizeof(mtpPrime)); constexpr auto kMaxModExpSize = 256; constexpr auto kWaitForBetterTimeout = crl::time(2000); constexpr auto kMinConnectedTimeout = crl::time(1000); constexpr auto kMaxConnectedTimeout = crl::time(8000); constexpr auto kMinReceiveTimeout = crl::time(4000); constexpr auto kMaxReceiveTimeout = crl::time(64000); constexpr auto kMarkConnectionOldTimeout = crl::time(192000); constexpr auto kPingDelayDisconnect = 60; constexpr auto kPingSendAfter = crl::time(30000); constexpr auto kPingSendAfterForce = crl::time(45000); constexpr auto kTestModeDcIdShift = 10000; // If we can't connect for this time we will ask _instance to update config. constexpr auto kRequestConfigTimeout = crl::time(8000); // Don't try to handle messages larger than this size. constexpr auto kMaxMessageLength = 16 * 1024 * 1024; QString LogIdsVector(const QVector &ids) { if (!ids.size()) return "[]"; auto idsStr = QString("[%1").arg(ids.cbegin()->v); for (const auto &id : ids) { idsStr += QString(", %2").arg(id.v); } return idsStr + "]"; } bool IsGoodModExpFirst( const openssl::BigNum &modexp, const openssl::BigNum &prime) { const auto diff = openssl::BigNum::Sub(prime, modexp); if (modexp.failed() || prime.failed() || diff.failed()) { return false; } constexpr auto kMinDiffBitsCount = 2048 - 64; if (diff.isNegative() || diff.bitsSize() < kMinDiffBitsCount || modexp.bitsSize() < kMinDiffBitsCount || modexp.bytesSize() > kMaxModExpSize) { return false; } return true; } bool IsPrimeAndGoodCheck(const openssl::BigNum &prime, int g) { constexpr auto kGoodPrimeBitsCount = 2048; if (prime.failed() || prime.isNegative() || prime.bitsSize() != kGoodPrimeBitsCount) { LOG(("MTP Error: Bad prime bits count %1, expected %2.").arg(prime.bitsSize()).arg(kGoodPrimeBitsCount)); return false; } openssl::Context context; if (!prime.isPrime(context)) { LOG(("MTP Error: Bad prime.")); return false; } switch (g) { case 2: { auto mod8 = prime.modWord(8); if (mod8 != 7) { LOG(("BigNum PT Error: bad g value: %1, mod8: %2").arg(g).arg(mod8)); return false; } } break; case 3: { auto mod3 = prime.modWord(3); if (mod3 != 2) { LOG(("BigNum PT Error: bad g value: %1, mod3: %2").arg(g).arg(mod3)); return false; } } break; case 4: break; case 5: { auto mod5 = prime.modWord(5); if (mod5 != 1 && mod5 != 4) { LOG(("BigNum PT Error: bad g value: %1, mod5: %2").arg(g).arg(mod5)); return false; } } break; case 6: { auto mod24 = prime.modWord(24); if (mod24 != 19 && mod24 != 23) { LOG(("BigNum PT Error: bad g value: %1, mod24: %2").arg(g).arg(mod24)); return false; } } break; case 7: { auto mod7 = prime.modWord(7); if (mod7 != 3 && mod7 != 5 && mod7 != 6) { LOG(("BigNum PT Error: bad g value: %1, mod7: %2").arg(g).arg(mod7)); return false; } } break; default: { LOG(("BigNum PT Error: bad g value: %1").arg(g)); return false; } break; } auto primeSubOneDivTwo = prime; primeSubOneDivTwo.setSubWord(1); primeSubOneDivTwo.setDivWord(2); if (!primeSubOneDivTwo.isPrime(context)) { LOG(("MTP Error: Bad (prime - 1) / 2.")); return false; } return true; } bool IsPrimeAndGood(bytes::const_span primeBytes, int g) { static constexpr unsigned char GoodPrime[] = { 0xC7, 0x1C, 0xAE, 0xB9, 0xC6, 0xB1, 0xC9, 0x04, 0x8E, 0x6C, 0x52, 0x2F, 0x70, 0xF1, 0x3F, 0x73, 0x98, 0x0D, 0x40, 0x23, 0x8E, 0x3E, 0x21, 0xC1, 0x49, 0x34, 0xD0, 0x37, 0x56, 0x3D, 0x93, 0x0F, 0x48, 0x19, 0x8A, 0x0A, 0xA7, 0xC1, 0x40, 0x58, 0x22, 0x94, 0x93, 0xD2, 0x25, 0x30, 0xF4, 0xDB, 0xFA, 0x33, 0x6F, 0x6E, 0x0A, 0xC9, 0x25, 0x13, 0x95, 0x43, 0xAE, 0xD4, 0x4C, 0xCE, 0x7C, 0x37, 0x20, 0xFD, 0x51, 0xF6, 0x94, 0x58, 0x70, 0x5A, 0xC6, 0x8C, 0xD4, 0xFE, 0x6B, 0x6B, 0x13, 0xAB, 0xDC, 0x97, 0x46, 0x51, 0x29, 0x69, 0x32, 0x84, 0x54, 0xF1, 0x8F, 0xAF, 0x8C, 0x59, 0x5F, 0x64, 0x24, 0x77, 0xFE, 0x96, 0xBB, 0x2A, 0x94, 0x1D, 0x5B, 0xCD, 0x1D, 0x4A, 0xC8, 0xCC, 0x49, 0x88, 0x07, 0x08, 0xFA, 0x9B, 0x37, 0x8E, 0x3C, 0x4F, 0x3A, 0x90, 0x60, 0xBE, 0xE6, 0x7C, 0xF9, 0xA4, 0xA4, 0xA6, 0x95, 0x81, 0x10, 0x51, 0x90, 0x7E, 0x16, 0x27, 0x53, 0xB5, 0x6B, 0x0F, 0x6B, 0x41, 0x0D, 0xBA, 0x74, 0xD8, 0xA8, 0x4B, 0x2A, 0x14, 0xB3, 0x14, 0x4E, 0x0E, 0xF1, 0x28, 0x47, 0x54, 0xFD, 0x17, 0xED, 0x95, 0x0D, 0x59, 0x65, 0xB4, 0xB9, 0xDD, 0x46, 0x58, 0x2D, 0xB1, 0x17, 0x8D, 0x16, 0x9C, 0x6B, 0xC4, 0x65, 0xB0, 0xD6, 0xFF, 0x9C, 0xA3, 0x92, 0x8F, 0xEF, 0x5B, 0x9A, 0xE4, 0xE4, 0x18, 0xFC, 0x15, 0xE8, 0x3E, 0xBE, 0xA0, 0xF8, 0x7F, 0xA9, 0xFF, 0x5E, 0xED, 0x70, 0x05, 0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F, 0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF, 0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B }; if (!bytes::compare(bytes::make_span(GoodPrime), primeBytes)) { if (g == 3 || g == 4 || g == 5 || g == 7) { return true; } } return IsPrimeAndGoodCheck(openssl::BigNum(primeBytes), g); } bytes::vector CreateAuthKey( bytes::const_span firstBytes, bytes::const_span randomBytes, bytes::const_span primeBytes) { using openssl::BigNum; BigNum first(firstBytes); BigNum prime(primeBytes); if (!IsGoodModExpFirst(first, prime)) { LOG(("AuthKey Error: Bad first prime in CreateAuthKey().")); return {}; } return BigNum::ModExp(first, BigNum(randomBytes), prime).getBytes(); } ModExpFirst CreateModExp( int g, bytes::const_span primeBytes, bytes::const_span randomSeed) { Expects(randomSeed.size() == ModExpFirst::kRandomPowerSize); using namespace openssl; BigNum prime(primeBytes); auto result = ModExpFirst(); result.randomPower.resize(ModExpFirst::kRandomPowerSize); while (true) { bytes::set_random(result.randomPower); for (auto i = 0; i != ModExpFirst::kRandomPowerSize; ++i) { result.randomPower[i] ^= randomSeed[i]; } const auto modexp = BigNum::ModExp( BigNum(g), BigNum(result.randomPower), prime); if (IsGoodModExpFirst(modexp, prime)) { result.modexp = modexp.getBytes(); return result; } } } void wrapInvokeAfter(SecureRequest &to, const SecureRequest &from, const RequestMap &haveSent, int32 skipBeforeRequest = 0) { const auto afterId = *(mtpMsgId*)(from->after->data() + 4); const auto i = afterId ? haveSent.constFind(afterId) : haveSent.cend(); int32 size = to->size(), lenInInts = (from.innerLength() >> 2), headlen = 4, fulllen = headlen + lenInInts; if (i == haveSent.constEnd()) { // no invoke after or such msg was not sent or was completed recently to->resize(size + fulllen + skipBeforeRequest); if (skipBeforeRequest) { memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime)); memcpy(to->data() + size + headlen + skipBeforeRequest, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime)); } else { memcpy(to->data() + size, from->constData() + 4, fulllen * sizeof(mtpPrime)); } } else { to->resize(size + fulllen + skipBeforeRequest + 3); memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime)); (*to)[size + 3] += 3 * sizeof(mtpPrime); *((mtpTypeId*)&((*to)[size + headlen + skipBeforeRequest])) = mtpc_invokeAfterMsg; memcpy(to->data() + size + headlen + skipBeforeRequest + 1, &afterId, 2 * sizeof(mtpPrime)); memcpy(to->data() + size + headlen + skipBeforeRequest + 3, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime)); if (size + 3 != 7) (*to)[7] += 3 * sizeof(mtpPrime); } } bool parsePQ(const QByteArray &pqStr, QByteArray &pStr, QByteArray &qStr) { if (pqStr.length() > 8) return false; // more than 64 bit pq uint64 pq = 0, p, q; const uchar *pqChars = (const uchar*)pqStr.constData(); for (uint32 i = 0, l = pqStr.length(); i < l; ++i) { pq <<= 8; pq |= (uint64)pqChars[i]; } uint64 pqSqrt = (uint64)sqrtl((long double)pq), ySqr, y; while (pqSqrt * pqSqrt > pq) --pqSqrt; while (pqSqrt * pqSqrt < pq) ++pqSqrt; for (ySqr = pqSqrt * pqSqrt - pq; ; ++pqSqrt, ySqr = pqSqrt * pqSqrt - pq) { y = (uint64)sqrtl((long double)ySqr); while (y * y > ySqr) --y; while (y * y < ySqr) ++y; if (!ySqr || y + pqSqrt >= pq) return false; if (y * y == ySqr) { p = pqSqrt + y; q = (pqSqrt > y) ? (pqSqrt - y) : (y - pqSqrt); break; } } if (p > q) std::swap(p, q); pStr.resize(4); uchar *pChars = (uchar*)pStr.data(); for (uint32 i = 0; i < 4; ++i) { *(pChars + 3 - i) = (uchar)(p & 0xFF); p >>= 8; } qStr.resize(4); uchar *qChars = (uchar*)qStr.data(); for (uint32 i = 0; i < 4; ++i) { *(qChars + 3 - i) = (uchar)(q & 0xFF); q >>= 8; } return true; } } // namespace Connection::Connection(not_null instance) : _instance(instance) { } void Connection::start(SessionData *sessionData, ShiftedDcId shiftedDcId) { Expects(_thread == nullptr && _private == nullptr); _thread = std::make_unique(); auto newData = std::make_unique( _instance, _thread.get(), this, sessionData, shiftedDcId); // will be deleted in the thread::finished signal _private = newData.release(); _thread->start(); } void Connection::kill() { Expects(_private != nullptr && _thread != nullptr); _private->stop(); _private = nullptr; _thread->quit(); } void Connection::waitTillFinish() { Expects(_private == nullptr && _thread != nullptr); DEBUG_LOG(("Waiting for connectionThread to finish")); _thread->wait(); _thread.reset(); } int32 Connection::state() const { Expects(_private != nullptr && _thread != nullptr); return _private->getState(); } QString Connection::transport() const { Expects(_private != nullptr && _thread != nullptr); return _private->transport(); } Connection::~Connection() { Expects(_private == nullptr); if (_thread) { waitTillFinish(); } } 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) + (protocolSecret.empty() ? 0 : 1); _testConnections.push_back({ AbstractConnection::Create( _instance, protocol, thread(), _connectionOptions->proxy), 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; DEBUG_LOG(("This connection marked as not old!")); } _oldConnectionTimer.callOnce(kMarkConnectionOldTimeout); connect(weak, &AbstractConnection::connected, [=] { onConnected(weak); }); connect(weak, &AbstractConnection::disconnected, [=] { onDisconnected(weak); }); InvokeQueued(_testConnections.back().data, [=] { weak->connectToServer(ip, port, protocolSecret, getProtocolDcId()); }); } int16 ConnectionPrivate::getProtocolDcId() const { const auto dcId = BareDcId(_shiftedDcId); const auto simpleDcId = isTemporaryDcId(dcId) ? getRealIdFromTemporaryDcId(dcId) : dcId; const auto testedDcId = cTestMode() ? (kTestModeDcIdShift + simpleDcId) : simpleDcId; return (_dcType == DcType::MediaDownload) ? -testedDcId : testedDcId; } void ConnectionPrivate::destroyAllConnections() { _waitForBetterTimer.cancel(); _waitForReceivedTimer.cancel(); _waitForConnectedTimer.cancel(); _testConnections.clear(); _connection = nullptr; } 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) , _retryTimer(thread, [=] { retryByTimer(); }) , _oldConnectionTimer(thread, [=] { markConnectionOld(); }) , _waitForConnectedTimer(thread, [=] { waitConnectedFailed(); }) , _waitForReceivedTimer(thread, [=] { waitReceivedFailed(); }) , _waitForBetterTimer(thread, [=] { waitBetterFailed(); }) , _waitForReceived(kMinReceiveTimeout) , _waitForConnected(kMinConnectedTimeout) , _pingSender(thread, [=] { sendPingByTimer(); }) , sessionData(data) { Expects(_shiftedDcId != 0); 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(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); connect(sessionData->owner(), SIGNAL(needToSend()), this, SLOT(tryToSend()), Qt::QueuedConnection); connect(sessionData->owner(), SIGNAL(needToPing()), this, SLOT(onPingSendForce()), Qt::QueuedConnection); connect(this, SIGNAL(sessionResetDone()), sessionData->owner(), SLOT(onResetDone()), Qt::QueuedConnection); static bool _registered = false; if (!_registered) { _registered = true; qRegisterMetaType >("QVector"); } connect(this, SIGNAL(needToSendAsync()), sessionData->owner(), SLOT(needToResumeAndSend()), Qt::QueuedConnection); connect(this, SIGNAL(sendAnythingAsync(qint64)), sessionData->owner(), SLOT(sendAnything(qint64)), Qt::QueuedConnection); connect(this, SIGNAL(sendHttpWaitAsync()), sessionData->owner(), SLOT(sendAnything()), Qt::QueuedConnection); connect(this, SIGNAL(sendPongAsync(quint64,quint64)), sessionData->owner(), SLOT(sendPong(quint64,quint64)), Qt::QueuedConnection); connect(this, SIGNAL(sendMsgsStateInfoAsync(quint64, QByteArray)), sessionData->owner(), SLOT(sendMsgsStateInfo(quint64,QByteArray)), Qt::QueuedConnection); connect(this, SIGNAL(resendAsync(quint64,qint64,bool,bool)), sessionData->owner(), SLOT(resend(quint64,qint64,bool,bool)), Qt::QueuedConnection); connect(this, SIGNAL(resendManyAsync(QVector,qint64,bool,bool)), sessionData->owner(), SLOT(resendMany(QVector,qint64,bool,bool)), Qt::QueuedConnection); connect(this, SIGNAL(resendAllAsync()), sessionData->owner(), SLOT(resendAll()), Qt::QueuedConnection); } void ConnectionPrivate::onConfigLoaded() { connectToServer(true); } void ConnectionPrivate::onCDNConfigLoaded() { restart(); } int32 ConnectionPrivate::getShiftedDcId() const { return _shiftedDcId; } int32 ConnectionPrivate::getState() const { QReadLocker lock(&stateConnMutex); int32 result = _state; if (_state < 0) { if (_retryTimer.isActive()) { result = int32(crl::now() - _retryWillFinish); if (result >= 0) { result = -1; } } } return result; } QString ConnectionPrivate::transport() const { QReadLocker lock(&stateConnMutex); if (!_connection || (_state < 0)) { return QString(); } Assert(_connectionOptions != nullptr); return _connection->transport(); } bool ConnectionPrivate::setState(int32 state, int32 ifState) { if (ifState != Connection::UpdateAlways) { QReadLocker lock(&stateConnMutex); if (_state != ifState) return false; } QWriteLocker lock(&stateConnMutex); if (_state == state) return false; _state = state; if (state < 0) { _retryTimeout = -state; _retryTimer.callOnce(_retryTimeout); _retryWillFinish = crl::now() + _retryTimeout; } emit stateChanged(state); return true; } void ConnectionPrivate::resetSession() { // recreate all msg_id and msg_seqno _needSessionReset = false; QWriteLocker locker1(sessionData->haveSentMutex()); QWriteLocker locker2(sessionData->toResendMutex()); QWriteLocker locker3(sessionData->toSendMutex()); QWriteLocker locker4(sessionData->wereAckedMutex()); auto &haveSent = sessionData->haveSentMap(); auto &toResend = sessionData->toResendMap(); auto &toSend = sessionData->toSendMap(); auto &wereAcked = sessionData->wereAckedMap(); auto newId = msgid(); auto setSeqNumbers = RequestMap(); auto replaces = QMap(); for (auto i = haveSent.cbegin(), e = haveSent.cend(); i != e; ++i) { if (!i.value().isSentContainer()) { if (!*(mtpMsgId*)(i.value()->constData() + 4)) continue; mtpMsgId id = i.key(); if (id > newId) { while (true) { if (toResend.constFind(newId) == toResend.cend() && wereAcked.constFind(newId) == wereAcked.cend() && haveSent.constFind(newId) == haveSent.cend()) { break; } mtpMsgId m = msgid(); if (m <= newId) break; // wtf newId = m; } MTP_LOG(_shiftedDcId, ("Replacing msgId %1 to %2!").arg(id).arg(newId)); replaces.insert(id, newId); id = newId; *(mtpMsgId*)(i.value()->data() + 4) = id; } setSeqNumbers.insert(id, i.value()); } } for (auto i = toResend.cbegin(), e = toResend.cend(); i != e; ++i) { // collect all non-container requests const auto j = toSend.constFind(i.value()); if (j == toSend.cend()) continue; if (!j.value().isSentContainer()) { if (!*(mtpMsgId*)(j.value()->constData() + 4)) continue; mtpMsgId id = i.key(); if (id > newId) { while (true) { if (toResend.constFind(newId) == toResend.cend() && wereAcked.constFind(newId) == wereAcked.cend() && haveSent.constFind(newId) == haveSent.cend()) { break; } mtpMsgId m = msgid(); if (m <= newId) break; // wtf newId = m; } MTP_LOG(_shiftedDcId, ("Replacing msgId %1 to %2!").arg(id).arg(newId)); replaces.insert(id, newId); id = newId; *(mtpMsgId*)(j.value()->data() + 4) = id; } setSeqNumbers.insert(id, j.value()); } } uint64 session = rand_value(); DEBUG_LOG(("MTP Info: creating new session after bad_msg_notification, setting random server_session %1").arg(session)); sessionData->setSession(session); for (auto i = setSeqNumbers.cbegin(), e = setSeqNumbers.cend(); i != e; ++i) { // generate new seq_numbers bool wasNeedAck = (*(i.value()->data() + 6) & 1); *(i.value()->data() + 6) = sessionData->nextRequestSeqNumber(wasNeedAck); } if (!replaces.isEmpty()) { for (auto i = replaces.cbegin(), e = replaces.cend(); i != e; ++i) { // replace msgIds keys in all data structs const auto j = haveSent.find(i.key()); if (j != haveSent.cend()) { const auto req = j.value(); haveSent.erase(j); haveSent.insert(i.value(), req); } const auto k = toResend.find(i.key()); if (k != toResend.cend()) { const auto req = k.value(); toResend.erase(k); toResend.insert(i.value(), req); } const auto l = wereAcked.find(i.key()); if (l != wereAcked.cend()) { const auto req = l.value(); wereAcked.erase(l); wereAcked.insert(i.value(), req); } } for (auto i = haveSent.cbegin(), e = haveSent.cend(); i != e; ++i) { // replace msgIds in saved containers if (i.value().isSentContainer()) { mtpMsgId *ids = (mtpMsgId*)(i.value()->data() + 8); for (uint32 j = 0, l = (i.value()->size() - 8) >> 1; j < l; ++j) { const auto k = replaces.constFind(ids[j]); if (k != replaces.cend()) { ids[j] = k.value(); } } } } } ackRequestData.clear(); resendRequestData.clear(); { QWriteLocker locker5(sessionData->stateRequestMutex()); sessionData->stateRequestMap().clear(); } emit sessionResetDone(); } mtpMsgId ConnectionPrivate::prepareToSend(SecureRequest &request, mtpMsgId currentLastId) { if (request->size() < 9) return 0; mtpMsgId msgId = *(mtpMsgId*)(request->constData() + 4); if (msgId) { // resending this request QWriteLocker locker(sessionData->toResendMutex()); auto &toResend = sessionData->toResendMap(); const auto i = toResend.find(msgId); if (i != toResend.cend()) { toResend.erase(i); } } else { msgId = *(mtpMsgId*)(request->data() + 4) = currentLastId; *(request->data() + 6) = sessionData->nextRequestSeqNumber(request.needAck()); } return msgId; } mtpMsgId ConnectionPrivate::replaceMsgId(SecureRequest &request, mtpMsgId newId) { if (request->size() < 9) return 0; mtpMsgId oldMsgId = *(mtpMsgId*)(request->constData() + 4); if (oldMsgId != newId) { if (oldMsgId) { QWriteLocker locker(sessionData->toResendMutex()); // haveSentMutex() and wereAckedMutex() were locked in tryToSend() auto &toResend = sessionData->toResendMap(); auto &wereAcked = sessionData->wereAckedMap(); auto &haveSent = sessionData->haveSentMap(); while (true) { if (toResend.constFind(newId) == toResend.cend() && wereAcked.constFind(newId) == wereAcked.cend() && haveSent.constFind(newId) == haveSent.cend()) { break; } const auto m = msgid(); if (m <= newId) break; // wtf newId = m; } const auto i = toResend.find(oldMsgId); if (i != toResend.cend()) { const auto req = i.value(); toResend.erase(i); toResend.insert(newId, req); } const auto j = wereAcked.find(oldMsgId); if (j != wereAcked.cend()) { const auto req = j.value(); wereAcked.erase(j); wereAcked.insert(newId, req); } const auto k = haveSent.find(oldMsgId); if (k != haveSent.cend()) { const auto req = k.value(); haveSent.erase(k); haveSent.insert(newId, req); } for (auto l = haveSent.begin(); l != haveSent.cend(); ++l) { const auto req = l.value(); if (req.isSentContainer()) { const auto ids = (mtpMsgId *)(req->data() + 8); for (uint32 i = 0, l = (req->size() - 8) >> 1; i < l; ++i) { if (ids[i] == oldMsgId) { ids[i] = newId; } } } } } else { *(request->data() + 6) = sessionData->nextRequestSeqNumber(request.needAck()); } *(mtpMsgId*)(request->data() + 4) = newId; } return newId; } mtpMsgId ConnectionPrivate::placeToContainer(SecureRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, SecureRequest &req) { mtpMsgId msgId = prepareToSend(req, bigMsgId); if (msgId > bigMsgId) msgId = replaceMsgId(req, bigMsgId); if (msgId >= bigMsgId) bigMsgId = msgid(); *(haveSentArr++) = msgId; uint32 from = toSendRequest->size(), len = req.messageSize(); toSendRequest->resize(from + len); memcpy(toSendRequest->data() + from, req->constData() + 4, len * sizeof(mtpPrime)); return msgId; } void ConnectionPrivate::tryToSend() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData || !_connection) { return; } auto needsLayer = !_connectionOptions->inited; auto state = getState(); auto prependOnly = (state != ConnectedState); auto pingRequest = SecureRequest(); if (_shiftedDcId == BareDcId(_shiftedDcId)) { // main session if (!prependOnly && !_pingIdToSend && !_pingId && _pingSendAt <= crl::now()) { _pingIdToSend = rand_value(); } } if (_pingIdToSend) { if (prependOnly || _shiftedDcId != BareDcId(_shiftedDcId)) { pingRequest = SecureRequest::Serialize(MTPPing( MTP_long(_pingIdToSend) )); DEBUG_LOG(("MTP Info: sending ping, ping_id: %1" ).arg(_pingIdToSend)); } else { pingRequest = SecureRequest::Serialize(MTPPing_delay_disconnect( MTP_long(_pingIdToSend), MTP_int(kPingDelayDisconnect))); DEBUG_LOG(("MTP Info: sending ping_delay_disconnect, " "ping_id: %1").arg(_pingIdToSend)); } pingRequest->msDate = crl::now(); // > 0 - can send without container _pingSendAt = pingRequest->msDate + kPingSendAfter; pingRequest->requestId = 0; // dont add to haveSent / wereAcked maps if (_shiftedDcId == BareDcId(_shiftedDcId) && !prependOnly) { // main session _pingSender.callOnce(kPingSendAfterForce); } _pingId = _pingIdToSend; _pingIdToSend = 0; } else { if (prependOnly) { DEBUG_LOG(("MTP Info: dc %1 not sending, waiting for Connected state, state: %2").arg(_shiftedDcId).arg(state)); return; // just do nothing, if is not connected yet } else { DEBUG_LOG(("MTP Info: dc %1 trying to send after ping, state: %2").arg(_shiftedDcId).arg(state)); } } SecureRequest ackRequest, resendRequest, stateRequest, httpWaitRequest; if (!prependOnly && !ackRequestData.isEmpty()) { ackRequest = SecureRequest::Serialize(MTPMsgsAck( MTP_msgs_ack(MTP_vector(ackRequestData)))); ackRequest->msDate = crl::now(); // > 0 - can send without container ackRequest->requestId = 0; // dont add to haveSent / wereAcked maps ackRequestData.clear(); } if (!prependOnly && !resendRequestData.isEmpty()) { resendRequest = SecureRequest::Serialize(MTPMsgResendReq( MTP_msg_resend_req(MTP_vector(resendRequestData)))); resendRequest->msDate = crl::now(); // > 0 - can send without container resendRequest->requestId = 0; // dont add to haveSent / wereAcked maps resendRequestData.clear(); } if (!prependOnly) { QVector stateReq; { QWriteLocker locker(sessionData->stateRequestMutex()); auto &ids = sessionData->stateRequestMap(); if (!ids.isEmpty()) { stateReq.reserve(ids.size()); for (auto i = ids.cbegin(), e = ids.cend(); i != e; ++i) { stateReq.push_back(MTP_long(i.key())); } } ids.clear(); } if (!stateReq.isEmpty()) { stateRequest = SecureRequest::Serialize(MTPMsgsStateReq( MTP_msgs_state_req(MTP_vector(stateReq)))); stateRequest->msDate = crl::now(); // > 0 - can send without container stateRequest->requestId = GetNextRequestId();// add to haveSent / wereAcked maps, but don't add to requestMap } if (_connection->usingHttpWait()) { httpWaitRequest = SecureRequest::Serialize(MTPHttpWait( MTP_http_wait(MTP_int(100), MTP_int(30), MTP_int(25000)))); httpWaitRequest->msDate = crl::now(); // > 0 - can send without container httpWaitRequest->requestId = 0; // dont add to haveSent / wereAcked maps } } MTPInitConnection initWrapper; int32 initSize = 0, initSizeInInts = 0; if (needsLayer) { Assert(_connectionOptions != nullptr); const auto systemLangCode = _connectionOptions->systemLangCode; const auto cloudLangCode = _connectionOptions->cloudLangCode; const auto langPackName = _connectionOptions->langPackName; const auto deviceModel = (_dcType == DcType::Cdn) ? "n/a" : _instance->deviceModel(); const auto systemVersion = (_dcType == DcType::Cdn) ? "n/a" : _instance->systemVersion(); #if defined OS_MAC_STORE || defined OS_WIN_STORE const auto appVersion = QString::fromLatin1(AppVersionStr) + " store"; #else // OS_MAC_STORE || OS_WIN_STORE const auto appVersion = QString::fromLatin1(AppVersionStr); #endif // OS_MAC_STORE || OS_WIN_STORE const auto proxyType = _connectionOptions->proxy.type; const auto mtprotoProxy = (proxyType == ProxyData::Type::Mtproto); const auto clientProxyFields = mtprotoProxy ? MTP_inputClientProxy( MTP_string(_connectionOptions->proxy.host), MTP_int(_connectionOptions->proxy.port)) : MTPInputClientProxy(); using Flag = MTPInitConnection::Flag; initWrapper = MTPInitConnection( MTP_flags(mtprotoProxy ? Flag::f_proxy : Flag(0)), MTP_int(ApiId), MTP_string(deviceModel), MTP_string(systemVersion), MTP_string(appVersion), MTP_string(systemLangCode), MTP_string(langPackName), MTP_string(cloudLangCode), clientProxyFields, SecureRequest()); initSizeInInts = (initWrapper.innerLength() >> 2) + 2; initSize = initSizeInInts * sizeof(mtpPrime); } bool needAnyResponse = false; SecureRequest toSendRequest; { QWriteLocker locker1(sessionData->toSendMutex()); auto toSendDummy = PreRequestMap(); auto &toSend = prependOnly ? toSendDummy : sessionData->toSendMap(); if (prependOnly) locker1.unlock(); uint32 toSendCount = toSend.size(); if (pingRequest) ++toSendCount; if (ackRequest) ++toSendCount; if (resendRequest) ++toSendCount; if (stateRequest) ++toSendCount; if (httpWaitRequest) ++toSendCount; if (!toSendCount) return; // nothing to send auto first = pingRequest ? pingRequest : (ackRequest ? ackRequest : (resendRequest ? resendRequest : (stateRequest ? stateRequest : (httpWaitRequest ? httpWaitRequest : toSend.cbegin().value())))); if (toSendCount == 1 && first->msDate > 0) { // if can send without container toSendRequest = first; if (!prependOnly) { toSend.clear(); locker1.unlock(); } mtpMsgId msgId = prepareToSend(toSendRequest, msgid()); if (pingRequest) { _pingMsgId = msgId; needAnyResponse = true; } else if (resendRequest || stateRequest) { needAnyResponse = true; } if (toSendRequest->requestId) { if (toSendRequest.needAck()) { toSendRequest->msDate = toSendRequest.isStateRequest() ? 0 : crl::now(); QWriteLocker locker2(sessionData->haveSentMutex()); auto &haveSent = sessionData->haveSentMap(); haveSent.insert(msgId, toSendRequest); if (needsLayer && !toSendRequest->needsLayer) needsLayer = false; if (toSendRequest->after) { const auto toSendSize = toSendRequest.innerLength() >> 2; auto wrappedRequest = SecureRequest::Prepare( toSendSize, toSendSize + 3); wrappedRequest->resize(4); memcpy(wrappedRequest->data(), toSendRequest->constData(), 4 * sizeof(mtpPrime)); wrapInvokeAfter(wrappedRequest, toSendRequest, haveSent); toSendRequest = std::move(wrappedRequest); } if (needsLayer) { const auto noWrapSize = (toSendRequest.innerLength() >> 2); const auto toSendSize = noWrapSize + initSizeInInts; auto wrappedRequest = SecureRequest::Prepare(toSendSize); memcpy(wrappedRequest->data(), toSendRequest->constData(), 7 * sizeof(mtpPrime)); // all except length wrappedRequest->push_back(mtpc_invokeWithLayer); wrappedRequest->push_back(internal::CurrentLayer); initWrapper.write(*wrappedRequest); wrappedRequest->resize(wrappedRequest->size() + noWrapSize); memcpy(wrappedRequest->data() + wrappedRequest->size() - noWrapSize, toSendRequest->constData() + 8, noWrapSize * sizeof(mtpPrime)); toSendRequest = std::move(wrappedRequest); } needAnyResponse = true; } else { QWriteLocker locker3(sessionData->wereAckedMutex()); sessionData->wereAckedMap().insert(msgId, toSendRequest->requestId); } } } else { // send in container bool willNeedInit = false; uint32 containerSize = 1 + 1, idsWrapSize = (toSendCount << 1); // cons + vector size, idsWrapSize - size of "request-like" wrap for msgId vector if (pingRequest) containerSize += pingRequest.messageSize(); if (ackRequest) containerSize += ackRequest.messageSize(); if (resendRequest) containerSize += resendRequest.messageSize(); if (stateRequest) containerSize += stateRequest.messageSize(); if (httpWaitRequest) containerSize += httpWaitRequest.messageSize(); for (auto i = toSend.begin(), e = toSend.end(); i != e; ++i) { containerSize += i.value().messageSize(); if (needsLayer && i.value()->needsLayer) { containerSize += initSizeInInts; willNeedInit = true; } } mtpBuffer initSerialized; if (willNeedInit) { initSerialized.reserve(initSizeInInts); initSerialized.push_back(mtpc_invokeWithLayer); initSerialized.push_back(internal::CurrentLayer); initWrapper.write(initSerialized); } // prepare container + each in invoke after toSendRequest = SecureRequest::Prepare( containerSize, containerSize + 3 * toSend.size()); toSendRequest->push_back(mtpc_msg_container); toSendRequest->push_back(toSendCount); mtpMsgId bigMsgId = msgid(); // check for a valid container // the fact of this lock is used in replaceMsgId() QWriteLocker locker2(sessionData->haveSentMutex()); auto &haveSent = sessionData->haveSentMap(); // the fact of this lock is used in replaceMsgId() QWriteLocker locker3(sessionData->wereAckedMutex()); auto &wereAcked = sessionData->wereAckedMap(); // prepare "request-like" wrap for msgId vector auto haveSentIdsWrap = SecureRequest::Prepare(idsWrapSize); haveSentIdsWrap->requestId = 0; haveSentIdsWrap->resize(haveSentIdsWrap->size() + idsWrapSize); auto haveSentArr = (mtpMsgId*)(haveSentIdsWrap->data() + 8); if (pingRequest) { _pingMsgId = placeToContainer(toSendRequest, bigMsgId, haveSentArr, pingRequest); needAnyResponse = true; } else if (resendRequest || stateRequest) { needAnyResponse = true; } for (auto i = toSend.begin(), e = toSend.end(); i != e; ++i) { auto &req = i.value(); auto msgId = prepareToSend(req, bigMsgId); if (msgId > bigMsgId) msgId = replaceMsgId(req, bigMsgId); if (msgId >= bigMsgId) bigMsgId = msgid(); *(haveSentArr++) = msgId; bool added = false; if (req->requestId) { if (req.needAck()) { req->msDate = req.isStateRequest() ? 0 : crl::now(); int32 reqNeedsLayer = (needsLayer && req->needsLayer) ? toSendRequest->size() : 0; if (req->after) { wrapInvokeAfter(toSendRequest, req, haveSent, reqNeedsLayer ? initSizeInInts : 0); if (reqNeedsLayer) { memcpy(toSendRequest->data() + reqNeedsLayer + 4, initSerialized.constData(), initSize); *(toSendRequest->data() + reqNeedsLayer + 3) += initSize; } added = true; } else if (reqNeedsLayer) { toSendRequest->resize(reqNeedsLayer + initSizeInInts + req.messageSize()); memcpy(toSendRequest->data() + reqNeedsLayer, req->constData() + 4, 4 * sizeof(mtpPrime)); memcpy(toSendRequest->data() + reqNeedsLayer + 4, initSerialized.constData(), initSize); memcpy(toSendRequest->data() + reqNeedsLayer + 4 + initSizeInInts, req->constData() + 8, req.innerLength()); *(toSendRequest->data() + reqNeedsLayer + 3) += initSize; added = true; } haveSent.insert(msgId, req); needAnyResponse = true; } else { wereAcked.insert(msgId, req->requestId); } } if (!added) { uint32 from = toSendRequest->size(), len = req.messageSize(); toSendRequest->resize(from + len); memcpy(toSendRequest->data() + from, req->constData() + 4, len * sizeof(mtpPrime)); } } if (stateRequest) { mtpMsgId msgId = placeToContainer(toSendRequest, bigMsgId, haveSentArr, stateRequest); stateRequest->msDate = 0; // 0 for state request, do not request state of it haveSent.insert(msgId, stateRequest); } if (resendRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, resendRequest); if (ackRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, ackRequest); if (httpWaitRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, httpWaitRequest); mtpMsgId contMsgId = prepareToSend(toSendRequest, bigMsgId); *(mtpMsgId*)(haveSentIdsWrap->data() + 4) = contMsgId; (*haveSentIdsWrap)[6] = 0; // for container, msDate = 0, seqNo = 0 haveSent.insert(contMsgId, haveSentIdsWrap); toSend.clear(); } } sendSecureRequest( std::move(toSendRequest), needAnyResponse, lockFinished); } 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 (keyId == kRecreateKeyId) { if (sessionData->getKey()) { unlockKey(); QWriteLocker lock(sessionData->keyMutex()); sessionData->owner()->destroyKey(); } keyId = 0; } connectToServer(); } void ConnectionPrivate::restartNow() { _retryTimeout = 1; _retryTimer.cancel(); restart(); } void ConnectionPrivate::connectToServer(bool afterConfig) { if (_finished) { DEBUG_LOG(("MTP Error: " "connectToServer() called for finished connection!")); return; } auto hasKey = true; { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) { DEBUG_LOG(("MTP Error: " "connectToServer() called for stopped connection!")); return; } _connectionOptions = std::make_unique( sessionData->connectionOptions()); hasKey = (sessionData->getKey() != nullptr); } auto bareDc = BareDcId(_shiftedDcId); _dcType = _instance->dcOptions()->dcType(_shiftedDcId); // Use media_only addresses only if key for this dc is already created. if (_dcType == DcType::MediaDownload && !hasKey) { _dcType = DcType::Regular; } else if (_dcType == DcType::Cdn && !_instance->isKeysDestroyer()) { if (!_instance->dcOptions()->hasCDNKeysForDc(bareDc)) { requestCDNConfig(); return; } } if (afterConfig && (!_testConnections.empty() || _connection)) { return; } destroyAllConnections(); if (_connectionOptions->proxy.type == ProxyData::Type::Mtproto) { // host, port, secret for mtproto proxy are taken from proxy. appendTestConnection(DcOptions::Variants::Tcp, {}, 0, {}); } 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.secret); } } } } if (_testConnections.empty()) { if (_instance->isKeysDestroyer()) { 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 not found right after config load!").arg(_shiftedDcId)); return restart(); } 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(); }); return; } DEBUG_LOG(("Connection Info: Connecting to %1 with %2 test connections." ).arg(_shiftedDcId ).arg(_testConnections.size())); if (!_startedConnectingAt) { _startedConnectingAt = crl::now(); } else if (crl::now() - _startedConnectingAt > kRequestConfigTimeout) { InvokeQueued(_instance, [instance = _instance] { instance->requestConfigIfOld(); }); } _retryTimer.cancel(); _waitForConnectedTimer.cancel(); setState(ConnectingState); _pingId = _pingMsgId = _pingIdToSend = _pingSendAt = 0; _pingSender.cancel(); _waitForConnectedTimer.callOnce(_waitForConnected); } void ConnectionPrivate::restart() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; DEBUG_LOG(("MTP Info: restarting Connection")); _waitForReceivedTimer.cancel(); _waitForConnectedTimer.cancel(); auto key = sessionData->getKey(); if (key) { if (!sessionData->isCheckedKey()) { // No destroying in case of an error. // //if (mayBeBadKey) { // clearMessages(); // keyId = kRecreateKeyId; // retryTimeout = 1; // no ddos please // LOG(("MTP Info: key may be bad and was not checked - but won't be destroyed, no log outs because of bad server right now...")); //} } else { sessionData->setCheckedKey(false); } } lockFinished.unlock(); doDisconnect(); lockFinished.relock(); if (sessionData && _needSessionReset) { resetSession(); } restarted = true; if (_retryTimer.isActive()) return; 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) { // 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)); } } if (isUploadDcId(_shiftedDcId)) { remain *= kUploadSessionsCount; } else if (isDownloadDcId(_shiftedDcId)) { remain *= kDownloadSessionsCount; } _waitForReceivedTimer.callOnce(remain); } if (!firstSentAt) firstSentAt = crl::now(); } void ConnectionPrivate::onReceivedSome() { if (_oldConnection) { _oldConnection = false; DEBUG_LOG(("This connection marked as not old!")); } _oldConnectionTimer.callOnce(kMarkConnectionOldTimeout); _waitForReceivedTimer.cancel(); if (firstSentAt > 0) { const auto ms = crl::now() - firstSentAt; DEBUG_LOG(("MTP Info: response in %1ms, _waitForReceived: %2ms").arg(ms).arg(_waitForReceived)); if (ms > 0 && ms * 2 < _waitForReceived) { _waitForReceived = qMax(ms * 2, kMinReceiveTimeout); } firstSentAt = -1; } } void ConnectionPrivate::markConnectionOld() { _oldConnection = true; _waitForReceived = kMinReceiveTimeout; DEBUG_LOG(("This connection marked as old! _waitForReceived now %1ms").arg(_waitForReceived)); } void ConnectionPrivate::sendPingByTimer() { if (_pingId) { // _pingSendAt: when to send next ping (lastPingAt + kPingSendAfter) // could be equal to zero. const auto now = crl::now(); const auto mustSendTill = _pingSendAt + kPingSendAfterForce - kPingSendAfter; if (mustSendTill < now + 1000) { LOG(("Could not send ping for some seconds, restarting...")); return restart(); } else { _pingSender.callOnce(mustSendTill - now); } } else { emit needToSendAsync(); } } void ConnectionPrivate::onPingSendForce() { if (!_pingId) { _pingSendAt = 0; DEBUG_LOG(("Will send ping!")); tryToSend(); } } void ConnectionPrivate::waitReceivedFailed() { Expects(_connectionOptions != nullptr); if (!_connectionOptions->useTcp) { return; } DEBUG_LOG(("MTP Info: bad connection, _waitForReceived: %1ms").arg(_waitForReceived)); if (_waitForReceived < kMaxReceiveTimeout) { _waitForReceived *= 2; } doDisconnect(); restarted = true; if (_retryTimer.isActive()) { return; } DEBUG_LOG(("MTP Info: immediate restart!")); InvokeQueued(this, [=] { connectToServer(); }); } void ConnectionPrivate::waitConnectedFailed() { DEBUG_LOG(("MTP Info: can't connect in %1ms").arg(_waitForConnected)); auto maxTimeout = kMaxConnectedTimeout; for (const auto &connection : _testConnections) { accumulate_max(maxTimeout, connection.data->fullConnectTimeout()); } if (_waitForConnected < maxTimeout) { _waitForConnected = std::min(maxTimeout, 2 * _waitForConnected); } doDisconnect(); restarted = true; DEBUG_LOG(("MTP Info: immediate restart!")); InvokeQueued(this, [=] { connectToServer(); }); } void ConnectionPrivate::waitBetterFailed() { confirmBestConnection(); } void ConnectionPrivate::doDisconnect() { destroyAllConnections(); { QReadLocker lockFinished(&sessionDataMutex); if (sessionData) { unlockKey(); } } clearAuthKeyData(); setState(DisconnectedState); restarted = false; } void ConnectionPrivate::finishAndDestroy() { doDisconnect(); _finished = true; emit finished(_owner); deleteLater(); } void ConnectionPrivate::requestCDNConfig() { connect( _instance, SIGNAL(cdnConfigLoaded()), this, SLOT(onCDNConfigLoaded()), Qt::UniqueConnection); InvokeQueued(_instance, [instance = _instance] { instance->requestCDNConfig(); }); } void ConnectionPrivate::handleReceived() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; onReceivedSome(); auto restartOnError = [this, &lockFinished] { lockFinished.unlock(); restart(); }; ReadLockerAttempt lock(sessionData->keyMutex()); if (!lock) { DEBUG_LOG(("MTP Error: auth_key for dc %1 busy, cant lock").arg(_shiftedDcId)); clearMessages(); keyId = 0; return restartOnError(); } auto key = sessionData->getKey(); if (!key || key->keyId() != keyId) { DEBUG_LOG(("MTP Error: auth_key id for dc %1 changed").arg(_shiftedDcId)); return restartOnError(); } 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 constexpr auto kMinimalEncryptedIntsCount = kEncryptedHeaderIntsCount + 4U; // + 1 data + 3 padding constexpr auto kMinimalIntsCount = kExternalHeaderIntsCount + kMinimalEncryptedIntsCount; auto intsCount = uint32(intsBuffer.size()); auto ints = intsBuffer.constData(); if ((intsCount < kMinimalIntsCount) || (intsCount > kMaxMessageLength / kIntSize)) { LOG(("TCP Error: bad message received, len %1").arg(intsCount * kIntSize)); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(ints, intsCount * kIntSize).str())); return restartOnError(); } if (keyId != *(uint64*)ints) { LOG(("TCP Error: bad auth_key_id %1 instead of %2 received").arg(keyId).arg(*(uint64*)ints)); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(ints, intsCount * kIntSize).str())); return restartOnError(); } auto encryptedInts = ints + kExternalHeaderIntsCount; auto encryptedIntsCount = (intsCount - kExternalHeaderIntsCount) & ~0x03U; auto encryptedBytesCount = encryptedIntsCount * kIntSize; auto decryptedBuffer = QByteArray(encryptedBytesCount, Qt::Uninitialized); auto msgKey = *(MTPint128*)(ints + 2); #ifdef TDESKTOP_MTPROTO_OLD aesIgeDecrypt_oldmtp(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, key, msgKey); #else // TDESKTOP_MTPROTO_OLD aesIgeDecrypt(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, key, msgKey); #endif // TDESKTOP_MTPROTO_OLD auto decryptedInts = reinterpret_cast(decryptedBuffer.constData()); auto serverSalt = *(uint64*)&decryptedInts[0]; auto session = *(uint64*)&decryptedInts[2]; auto msgId = *(uint64*)&decryptedInts[4]; auto seqNo = *(uint32*)&decryptedInts[6]; auto needAck = ((seqNo & 0x01) != 0); auto messageLength = *(uint32*)&decryptedInts[7]; if (messageLength > kMaxMessageLength) { LOG(("TCP Error: bad messageLength %1").arg(messageLength)); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(ints, intsCount * kIntSize).str())); return restartOnError(); } auto fullDataLength = kEncryptedHeaderIntsCount * kIntSize + messageLength; // Without padding. // Can underflow, but it is an unsigned type, so we just check the range later. auto paddingSize = static_cast(encryptedBytesCount) - static_cast(fullDataLength); #ifdef TDESKTOP_MTPROTO_OLD constexpr auto kMinPaddingSize_oldmtp = 0U; constexpr auto kMaxPaddingSize_oldmtp = 15U; auto badMessageLength = (/*paddingSize < kMinPaddingSize_oldmtp || */paddingSize > kMaxPaddingSize_oldmtp); auto hashedDataLength = badMessageLength ? encryptedBytesCount : fullDataLength; auto sha1ForMsgKeyCheck = hashSha1(decryptedInts, hashedDataLength); constexpr auto kMsgKeyShift_oldmtp = 4U; if (memcmp(&msgKey, sha1ForMsgKeyCheck.data() + kMsgKeyShift_oldmtp, sizeof(msgKey)) != 0) { LOG(("TCP Error: bad SHA1 hash after aesDecrypt in message.")); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str())); return restartOnError(); } #else // TDESKTOP_MTPROTO_OLD constexpr auto kMinPaddingSize = 12U; constexpr auto kMaxPaddingSize = 1024U; auto badMessageLength = (paddingSize < kMinPaddingSize || paddingSize > kMaxPaddingSize); std::array sha256Buffer = { { 0 } }; SHA256_CTX msgKeyLargeContext; SHA256_Init(&msgKeyLargeContext); SHA256_Update(&msgKeyLargeContext, key->partForMsgKey(false), 32); SHA256_Update(&msgKeyLargeContext, decryptedInts, encryptedBytesCount); SHA256_Final(sha256Buffer.data(), &msgKeyLargeContext); constexpr auto kMsgKeyShift = 8U; if (memcmp(&msgKey, sha256Buffer.data() + kMsgKeyShift, sizeof(msgKey)) != 0) { LOG(("TCP Error: bad SHA256 hash after aesDecrypt in message")); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str())); return restartOnError(); } #endif // TDESKTOP_MTPROTO_OLD if (badMessageLength || (messageLength & 0x03)) { LOG(("TCP Error: bad msg_len received %1, data size: %2").arg(messageLength).arg(encryptedBytesCount)); TCP_LOG(("TCP Error: bad message %1").arg(Logs::mb(encryptedInts, encryptedBytesCount).str())); return restartOnError(); } TCP_LOG(("TCP Info: decrypted message %1,%2,%3 is %4 len").arg(msgId).arg(seqNo).arg(Logs::b(needAck)).arg(fullDataLength)); uint64 serverSession = sessionData->getSession(); if (session != serverSession) { LOG(("MTP Error: bad server session received")); TCP_LOG(("MTP Error: bad server session %1 instead of %2 in message received").arg(session).arg(serverSession)); return restartOnError(); } int32 serverTime((int32)(msgId >> 32)), clientTime(unixtime()); bool isReply = ((msgId & 0x03) == 1); if (!isReply && ((msgId & 0x03) != 3)) { LOG(("MTP Error: bad msg_id %1 in message received").arg(msgId)); return restartOnError(); } bool badTime = false; uint64 mySalt = sessionData->getSalt(); if (serverTime > clientTime + 60 || serverTime + 300 < clientTime) { DEBUG_LOG(("MTP Info: bad server time from msg_id: %1, my time: %2").arg(serverTime).arg(clientTime)); badTime = true; } bool wasConnected = (getState() == ConnectedState); if (serverSalt != mySalt) { if (!badTime) { DEBUG_LOG(("MTP Info: other salt received... received: %1, my salt: %2, updating...").arg(serverSalt).arg(mySalt)); sessionData->setSalt(serverSalt); if (setState(ConnectedState, ConnectingState)) { // only connected if (restarted) { emit resendAllAsync(); restarted = false; } } } else { DEBUG_LOG(("MTP Info: other salt received... received: %1, my salt: %2").arg(serverSalt).arg(mySalt)); } } else { serverSalt = 0; // dont pass to handle method, so not to lock in setSalt() } if (needAck) ackRequestData.push_back(MTP_long(msgId)); auto res = HandleResult::Success; // if no need to handle, then succeed auto from = decryptedInts + kEncryptedHeaderIntsCount; auto end = from + (messageLength / kIntSize); auto sfrom = decryptedInts + 4U; // msg_id + seq_no + length + message MTP_LOG(_shiftedDcId, ("Recv: ") + mtpTextSerialize(sfrom, end)); bool needToHandle = false; { QWriteLocker lock(sessionData->receivedIdsMutex()); needToHandle = sessionData->receivedIdsSet().registerMsgId(msgId, needAck); } if (needToHandle) { res = handleOneReceived(from, end, msgId, serverTime, serverSalt, badTime); } { QWriteLocker lock(sessionData->receivedIdsMutex()); sessionData->receivedIdsSet().shrink(); } // send acks uint32 toAckSize = ackRequestData.size(); if (toAckSize) { DEBUG_LOG(("MTP Info: will send %1 acks, ids: %2").arg(toAckSize).arg(LogIdsVector(ackRequestData))); emit sendAnythingAsync(kAckSendWaiting); } bool emitSignal = false; { QReadLocker locker(sessionData->haveReceivedMutex()); emitSignal = !sessionData->haveReceivedResponses().isEmpty() || !sessionData->haveReceivedUpdates().isEmpty(); if (emitSignal) { DEBUG_LOG(("MTP Info: emitting needToReceive() - need to parse in another thread, %1 responses, %2 updates.").arg(sessionData->haveReceivedResponses().size()).arg(sessionData->haveReceivedUpdates().size())); } } if (emitSignal) { emit needToReceive(); } if (res != HandleResult::Success && res != HandleResult::Ignored) { _needSessionReset = (res == HandleResult::ResetSession); return restartOnError(); } _retryTimeout = 1; // reset restart() timer if (!sessionData->isCheckedKey()) { DEBUG_LOG(("MTP Info: marked auth key as checked")); sessionData->setCheckedKey(true); } _startedConnectingAt = crl::time(0); if (!wasConnected) { if (getState() == ConnectedState) { emit needToSendAsync(); } } } if (_connection->needHttpWait()) { emit sendHttpWaitAsync(); } } ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPrime *from, const mtpPrime *end, uint64 msgId, int32 serverTime, uint64 serverSalt, bool badTime) { mtpTypeId cons = *from; try { switch (cons) { case mtpc_gzip_packed: { DEBUG_LOG(("Message Info: gzip container")); mtpBuffer response = ungzip(++from, end); if (response.empty()) { return HandleResult::RestartConnection; } return handleOneReceived(response.data(), response.data() + response.size(), msgId, serverTime, serverSalt, badTime); } case mtpc_msg_container: { if (++from >= end) throw mtpErrorInsufficient(); const mtpPrime *otherEnd; uint32 msgsCount = (uint32)*(from++); DEBUG_LOG(("Message Info: container received, count: %1").arg(msgsCount)); for (uint32 i = 0; i < msgsCount; ++i) { if (from + 4 >= end) throw mtpErrorInsufficient(); otherEnd = from + 4; MTPlong inMsgId; inMsgId.read(from, otherEnd); bool isReply = ((inMsgId.v & 0x03) == 1); if (!isReply && ((inMsgId.v & 0x03) != 3)) { LOG(("Message Error: bad msg_id %1 in contained message received").arg(inMsgId.v)); return HandleResult::RestartConnection; } MTPint inSeqNo; inSeqNo.read(from, otherEnd); MTPint bytes; bytes.read(from, otherEnd); if ((bytes.v & 0x03) || bytes.v < 4) { LOG(("Message Error: bad length %1 of contained message received").arg(bytes.v)); return HandleResult::RestartConnection; } bool needAck = (inSeqNo.v & 0x01); if (needAck) ackRequestData.push_back(inMsgId); DEBUG_LOG(("Message Info: message from container, msg_id: %1, needAck: %2").arg(inMsgId.v).arg(Logs::b(needAck))); otherEnd = from + (bytes.v >> 2); if (otherEnd > end) throw mtpErrorInsufficient(); bool needToHandle = false; { QWriteLocker lock(sessionData->receivedIdsMutex()); needToHandle = sessionData->receivedIdsSet().registerMsgId(inMsgId.v, needAck); } auto res = HandleResult::Success; // if no need to handle, then succeed if (needToHandle) { res = handleOneReceived(from, otherEnd, inMsgId.v, serverTime, serverSalt, badTime); badTime = false; } if (res != HandleResult::Success) { return res; } from = otherEnd; } } return HandleResult::Success; case mtpc_msgs_ack: { MTPMsgsAck msg; msg.read(from, end); auto &ids = msg.c_msgs_ack().vmsg_ids.v; uint32 idsCount = ids.size(); DEBUG_LOG(("Message Info: acks received, ids: %1").arg(LogIdsVector(ids))); if (!idsCount) return (badTime ? HandleResult::Ignored : HandleResult::Success); if (badTime) { if (requestsFixTimeSalt(ids, serverTime, serverSalt)) { badTime = false; } else { return HandleResult::Ignored; } } requestsAcked(ids); } return HandleResult::Success; case mtpc_bad_msg_notification: { MTPBadMsgNotification msg; msg.read(from, end); const auto &data(msg.c_bad_msg_notification()); LOG(("Message Info: bad message notification received (error_code %3) for msg_id = %1, seq_no = %2").arg(data.vbad_msg_id.v).arg(data.vbad_msg_seqno.v).arg(data.verror_code.v)); mtpMsgId resendId = data.vbad_msg_id.v; if (resendId == _pingMsgId) { _pingId = 0; } int32 errorCode = data.verror_code.v; if (false || errorCode == 16 || errorCode == 17 || errorCode == 32 || errorCode == 33 || errorCode == 64) { // can handle const auto needResend = false || (errorCode == 16) // bad msg_id || (errorCode == 17) // bad msg_id || (errorCode == 64); // bad container if (errorCode == 64) { // bad container! if (Logs::DebugEnabled()) { SecureRequest request; { QWriteLocker locker(sessionData->haveSentMutex()); auto &haveSent = sessionData->haveSentMap(); const auto i = haveSent.constFind(resendId); if (i == haveSent.cend()) { LOG(("Message Error: Container not found!")); } else { request = i.value(); } } if (request) { if (request.isSentContainer()) { QStringList lst; const auto ids = (const mtpMsgId*)(request->constData() + 8); for (uint32 i = 0, l = (request->size() - 8) >> 1; i < l; ++i) { lst.push_back(QString::number(ids[i])); } LOG(("Message Info: bad container received! messages: %1").arg(lst.join(','))); } else { LOG(("Message Error: bad container received, but request is not a container!")); } } } } if (!wasSent(resendId)) { DEBUG_LOG(("Message Error: " "such message was not sent recently %1").arg(resendId)); return badTime ? HandleResult::Ignored : HandleResult::Success; } if (needResend) { // bad msg_id or bad container if (serverSalt) sessionData->setSalt(serverSalt); unixtimeSet(serverTime, true); DEBUG_LOG(("Message Info: unixtime updated, now %1, resending in container...").arg(serverTime)); resend(resendId, 0, true); } else { // must create new session, because msg_id and msg_seqno are inconsistent if (badTime) { if (serverSalt) sessionData->setSalt(serverSalt); unixtimeSet(serverTime, true); badTime = false; } LOG(("Message Info: bad message notification received, msgId %1, error_code %2").arg(data.vbad_msg_id.v).arg(errorCode)); return HandleResult::ResetSession; } } else { // fatal (except 48, but it must not get here) const auto badMsgId = mtpMsgId(data.vbad_msg_id.v); const auto requestId = wasSent(resendId); if (requestId) { LOG(("Message Error: " "bad message notification received, " "msgId %1, error_code %2, fatal: clearing callbacks" ).arg(badMsgId ).arg(errorCode )); _instance->clearCallbacksDelayed({ 1, RPCCallbackClear( requestId, -errorCode) }); } else { DEBUG_LOG(("Message Error: " "such message was not sent recently %1").arg(badMsgId)); } return badTime ? HandleResult::Ignored : HandleResult::Success; } } return HandleResult::Success; case mtpc_bad_server_salt: { MTPBadMsgNotification msg; msg.read(from, end); const auto &data(msg.c_bad_server_salt()); DEBUG_LOG(("Message Info: bad server salt received (error_code %4) for msg_id = %1, seq_no = %2, new salt: %3").arg(data.vbad_msg_id.v).arg(data.vbad_msg_seqno.v).arg(data.vnew_server_salt.v).arg(data.verror_code.v)); mtpMsgId resendId = data.vbad_msg_id.v; if (resendId == _pingMsgId) { _pingId = 0; } else if (!wasSent(resendId)) { DEBUG_LOG(("Message Error: such message was not sent recently %1").arg(resendId)); return (badTime ? HandleResult::Ignored : HandleResult::Success); } uint64 serverSalt = data.vnew_server_salt.v; sessionData->setSalt(serverSalt); unixtimeSet(serverTime); if (setState(ConnectedState, ConnectingState)) { // maybe only connected if (restarted) { emit resendAllAsync(); restarted = false; } } badTime = false; DEBUG_LOG(("Message Info: unixtime updated, now %1, server_salt updated, now %2, resending...").arg(serverTime).arg(serverSalt)); resend(resendId); } return HandleResult::Success; case mtpc_msgs_state_req: { if (badTime) { DEBUG_LOG(("Message Info: skipping with bad time...")); return HandleResult::Ignored; } MTPMsgsStateReq msg; msg.read(from, end); auto &ids = msg.c_msgs_state_req().vmsg_ids.v; auto idsCount = ids.size(); DEBUG_LOG(("Message Info: msgs_state_req received, ids: %1").arg(LogIdsVector(ids))); if (!idsCount) return HandleResult::Success; QByteArray info(idsCount, Qt::Uninitialized); { QReadLocker lock(sessionData->receivedIdsMutex()); auto &receivedIds = sessionData->receivedIdsSet(); auto minRecv = receivedIds.min(); auto maxRecv = receivedIds.max(); QReadLocker locker(sessionData->wereAckedMutex()); const auto &wereAcked = sessionData->wereAckedMap(); const auto wereAckedEnd = wereAcked.cend(); for (uint32 i = 0, l = idsCount; i < l; ++i) { char state = 0; uint64 reqMsgId = ids[i].v; if (reqMsgId < minRecv) { state |= 0x01; } else if (reqMsgId > maxRecv) { state |= 0x03; } else { auto msgIdState = receivedIds.lookup(reqMsgId); if (msgIdState == ReceivedMsgIds::State::NotFound) { state |= 0x02; } else { state |= 0x04; if (wereAcked.constFind(reqMsgId) != wereAckedEnd) { state |= 0x80; // we know, that server knows, that we received request } if (msgIdState == ReceivedMsgIds::State::NeedsAck) { // need ack, so we sent ack state |= 0x08; } else { state |= 0x10; } } } info[i] = state; } } emit sendMsgsStateInfoAsync(msgId, info); } return HandleResult::Success; case mtpc_msgs_state_info: { MTPMsgsStateInfo msg; msg.read(from, end); auto &data = msg.c_msgs_state_info(); auto reqMsgId = data.vreq_msg_id.v; auto &states = data.vinfo.v; DEBUG_LOG(("Message Info: msg state received, msgId %1, reqMsgId: %2, HEX states %3").arg(msgId).arg(reqMsgId).arg(Logs::mb(states.data(), states.length()).str())); SecureRequest requestBuffer; { // find this request in session-shared sent requests map QReadLocker locker(sessionData->haveSentMutex()); const auto &haveSent = sessionData->haveSentMap(); const auto replyTo = haveSent.constFind(reqMsgId); if (replyTo == haveSent.cend()) { // do not look in toResend, because we do not resend msgs_state_req requests DEBUG_LOG(("Message Error: such message was not sent recently %1").arg(reqMsgId)); return (badTime ? HandleResult::Ignored : HandleResult::Success); } if (badTime) { if (serverSalt) sessionData->setSalt(serverSalt); // requestsFixTimeSalt with no lookup unixtimeSet(serverTime, true); DEBUG_LOG(("Message Info: unixtime updated from mtpc_msgs_state_info, now %1").arg(serverTime)); badTime = false; } requestBuffer = replyTo.value(); } QVector toAckReq(1, MTP_long(reqMsgId)), toAck; requestsAcked(toAck, true); if (requestBuffer->size() < 9) { LOG(("Message Error: bad request %1 found in requestMap, size: %2").arg(reqMsgId).arg(requestBuffer->size())); return HandleResult::RestartConnection; } try { const mtpPrime *rFrom = requestBuffer->constData() + 8, *rEnd = requestBuffer->constData() + requestBuffer->size(); if (mtpTypeId(*rFrom) == mtpc_msgs_state_req) { MTPMsgsStateReq request; request.read(rFrom, rEnd); handleMsgsStates(request.c_msgs_state_req().vmsg_ids.v, states, toAck); } else { MTPMsgResendReq request; request.read(rFrom, rEnd); handleMsgsStates(request.c_msg_resend_req().vmsg_ids.v, states, toAck); } } catch(Exception &) { LOG(("Message Error: could not parse sent msgs_state_req")); throw; } requestsAcked(toAck); } return HandleResult::Success; case mtpc_msgs_all_info: { if (badTime) { DEBUG_LOG(("Message Info: skipping with bad time...")); return HandleResult::Ignored; } MTPMsgsAllInfo msg; msg.read(from, end); auto &data = msg.c_msgs_all_info(); auto &ids = data.vmsg_ids.v; auto &states = data.vinfo.v; QVector toAck; DEBUG_LOG(("Message Info: msgs all info received, msgId %1, reqMsgIds: %2, states %3").arg(msgId).arg(LogIdsVector(ids)).arg(Logs::mb(states.data(), states.length()).str())); handleMsgsStates(ids, states, toAck); requestsAcked(toAck); } return HandleResult::Success; case mtpc_msg_detailed_info: { MTPMsgDetailedInfo msg; msg.read(from, end); const auto &data(msg.c_msg_detailed_info()); DEBUG_LOG(("Message Info: msg detailed info, sent msgId %1, answerId %2, status %3, bytes %4").arg(data.vmsg_id.v).arg(data.vanswer_msg_id.v).arg(data.vstatus.v).arg(data.vbytes.v)); QVector ids(1, data.vmsg_id); if (badTime) { if (requestsFixTimeSalt(ids, serverTime, serverSalt)) { badTime = false; } else { DEBUG_LOG(("Message Info: error, such message was not sent recently %1").arg(data.vmsg_id.v)); return HandleResult::Ignored; } } requestsAcked(ids); bool received = false; MTPlong resMsgId = data.vanswer_msg_id; { QReadLocker lock(sessionData->receivedIdsMutex()); received = (sessionData->receivedIdsSet().lookup(resMsgId.v) != ReceivedMsgIds::State::NotFound); } if (received) { ackRequestData.push_back(resMsgId); } else { DEBUG_LOG(("Message Info: answer message %1 was not received, requesting...").arg(resMsgId.v)); resendRequestData.push_back(resMsgId); } } return HandleResult::Success; case mtpc_msg_new_detailed_info: { if (badTime) { DEBUG_LOG(("Message Info: skipping msg_new_detailed_info with bad time...")); return HandleResult::Ignored; } MTPMsgDetailedInfo msg; msg.read(from, end); const auto &data(msg.c_msg_new_detailed_info()); DEBUG_LOG(("Message Info: msg new detailed info, answerId %2, status %3, bytes %4").arg(data.vanswer_msg_id.v).arg(data.vstatus.v).arg(data.vbytes.v)); bool received = false; MTPlong resMsgId = data.vanswer_msg_id; { QReadLocker lock(sessionData->receivedIdsMutex()); received = (sessionData->receivedIdsSet().lookup(resMsgId.v) != ReceivedMsgIds::State::NotFound); } if (received) { ackRequestData.push_back(resMsgId); } else { DEBUG_LOG(("Message Info: answer message %1 was not received, requesting...").arg(resMsgId.v)); resendRequestData.push_back(resMsgId); } } return HandleResult::Success; case mtpc_msg_resend_req: { MTPMsgResendReq msg; msg.read(from, end); auto &ids = msg.c_msg_resend_req().vmsg_ids.v; auto idsCount = ids.size(); DEBUG_LOG(("Message Info: resend of msgs requested, ids: %1").arg(LogIdsVector(ids))); if (!idsCount) return (badTime ? HandleResult::Ignored : HandleResult::Success); QVector toResend(ids.size()); for (int32 i = 0, l = ids.size(); i < l; ++i) { toResend[i] = ids.at(i).v; } resendMany(toResend, 0, false, true); } return HandleResult::Success; case mtpc_rpc_result: { if (from + 3 > end) throw mtpErrorInsufficient(); auto response = SerializedMessage(); MTPlong reqMsgId; reqMsgId.read(++from, end); mtpTypeId typeId = from[0]; DEBUG_LOG(("RPC Info: response received for %1, queueing...").arg(reqMsgId.v)); QVector ids(1, reqMsgId); if (badTime) { if (requestsFixTimeSalt(ids, serverTime, serverSalt)) { badTime = false; } else { DEBUG_LOG(("Message Info: error, such message was not sent recently %1").arg(reqMsgId.v)); return HandleResult::Ignored; } } requestsAcked(ids, true); if (typeId == mtpc_gzip_packed) { DEBUG_LOG(("RPC Info: gzip container")); response = ungzip(++from, end); if (!response.size()) { return HandleResult::RestartConnection; } typeId = response[0]; } else { response.resize(end - from); memcpy(response.data(), from, (end - from) * sizeof(mtpPrime)); } if (typeId != mtpc_rpc_error) { // An error could be some RPC_CALL_FAIL or other error inside // the initConnection, so we're not sure yet that it was inited. // Wait till a good response is received. if (!_connectionOptions->inited) { _connectionOptions->inited = true; sessionData->notifyConnectionInited(*_connectionOptions); } } auto requestId = wasSent(reqMsgId.v); if (requestId && requestId != mtpRequestId(0xFFFFFFFF)) { // Save rpc_result for processing in the main thread. QWriteLocker locker(sessionData->haveReceivedMutex()); sessionData->haveReceivedResponses().insert(requestId, response); } else { DEBUG_LOG(("RPC Info: requestId not found for msgId %1").arg(reqMsgId.v)); } } return HandleResult::Success; case mtpc_new_session_created: { const mtpPrime *start = from; MTPNewSession msg; msg.read(from, end); const auto &data(msg.c_new_session_created()); if (badTime) { if (requestsFixTimeSalt(QVector(1, data.vfirst_msg_id), serverTime, serverSalt)) { badTime = false; } else { DEBUG_LOG(("Message Info: error, such message was not sent recently %1").arg(data.vfirst_msg_id.v)); return HandleResult::Ignored; } } DEBUG_LOG(("Message Info: new server session created, unique_id %1, first_msg_id %2, server_salt %3").arg(data.vunique_id.v).arg(data.vfirst_msg_id.v).arg(data.vserver_salt.v)); sessionData->setSalt(data.vserver_salt.v); mtpMsgId firstMsgId = data.vfirst_msg_id.v; QVector toResend; { QReadLocker locker(sessionData->haveSentMutex()); const auto &haveSent = sessionData->haveSentMap(); toResend.reserve(haveSent.size()); for (auto i = haveSent.cbegin(), e = haveSent.cend(); i != e; ++i) { if (i.key() >= firstMsgId) break; if (i.value()->requestId) toResend.push_back(i.key()); } } resendMany(toResend, 10, true); mtpBuffer update(from - start); if (from > start) memcpy(update.data(), start, (from - start) * sizeof(mtpPrime)); // Notify main process about new session - need to get difference. QWriteLocker locker(sessionData->haveReceivedMutex()); sessionData->haveReceivedUpdates().push_back(SerializedMessage(update)); } return HandleResult::Success; case mtpc_ping: { if (badTime) return HandleResult::Ignored; MTPPing msg; msg.read(from, end); DEBUG_LOG(("Message Info: ping received, ping_id: %1, sending pong...").arg(msg.vping_id.v)); emit sendPongAsync(msgId, msg.vping_id.v); } return HandleResult::Success; case mtpc_pong: { MTPPong msg; msg.read(from, end); const auto &data(msg.c_pong()); DEBUG_LOG(("Message Info: pong received, msg_id: %1, ping_id: %2").arg(data.vmsg_id.v).arg(data.vping_id.v)); if (!wasSent(data.vmsg_id.v)) { DEBUG_LOG(("Message Error: such msg_id %1 ping_id %2 was not sent recently").arg(data.vmsg_id.v).arg(data.vping_id.v)); return HandleResult::Ignored; } if (data.vping_id.v == _pingId) { _pingId = 0; } else { DEBUG_LOG(("Message Info: just pong...")); } QVector ids(1, data.vmsg_id); if (badTime) { if (requestsFixTimeSalt(ids, serverTime, serverSalt)) { badTime = false; } else { return HandleResult::Ignored; } } requestsAcked(ids, true); } return HandleResult::Success; } } catch (Exception &) { return HandleResult::RestartConnection; } if (badTime) { DEBUG_LOG(("Message Error: bad time in updates cons, must create new session")); return HandleResult::ResetSession; } if (_dcType == DcType::Regular) { mtpBuffer update(end - from); if (end > from) memcpy(update.data(), from, (end - from) * sizeof(mtpPrime)); // Notify main process about the new updates. QWriteLocker locker(sessionData->haveReceivedMutex()); sessionData->haveReceivedUpdates().push_back(SerializedMessage(update)); if (cons != mtpc_updatesTooLong && cons != mtpc_updateShortMessage && cons != mtpc_updateShortChatMessage && cons != mtpc_updateShortSentMessage && cons != mtpc_updateShort && cons != mtpc_updatesCombined && cons != mtpc_updates) { // Maybe some new unknown update? LOG(("Message Error: unknown constructor %1").arg(cons)); } } else { LOG(("Message Error: unexpected updates in dcType: %1").arg(static_cast(_dcType))); } return HandleResult::Success; } mtpBuffer ConnectionPrivate::ungzip(const mtpPrime *from, const mtpPrime *end) const { MTPstring packed; packed.read(from, end); // read packed string as serialized mtp string type uint32 packedLen = packed.v.size(), unpackedChunk = packedLen, unpackedLen = 0; mtpBuffer result; // * 4 because of mtpPrime type result.resize(0); z_stream stream; stream.zalloc = 0; stream.zfree = 0; stream.opaque = 0; stream.avail_in = 0; stream.next_in = 0; int res = inflateInit2(&stream, 16 + MAX_WBITS); if (res != Z_OK) { LOG(("RPC Error: could not init zlib stream, code: %1").arg(res)); return result; } stream.avail_in = packedLen; stream.next_in = reinterpret_cast(packed.v.data()); stream.avail_out = 0; while (!stream.avail_out) { result.resize(result.size() + unpackedChunk); stream.avail_out = unpackedChunk * sizeof(mtpPrime); stream.next_out = (Bytef*)&result[result.size() - unpackedChunk]; int res = inflate(&stream, Z_NO_FLUSH); if (res != Z_OK && res != Z_STREAM_END) { inflateEnd(&stream); LOG(("RPC Error: could not unpack gziped data, code: %1").arg(res)); DEBUG_LOG(("RPC Error: bad gzip: %1").arg(Logs::mb(packed.v.constData(), packedLen).str())); return mtpBuffer(); } } if (stream.avail_out & 0x03) { uint32 badSize = result.size() * sizeof(mtpPrime) - stream.avail_out; LOG(("RPC Error: bad length of unpacked data %1").arg(badSize)); DEBUG_LOG(("RPC Error: bad unpacked data %1").arg(Logs::mb(result.data(), badSize).str())); return mtpBuffer(); } result.resize(result.size() - (stream.avail_out >> 2)); inflateEnd(&stream); if (!result.size()) { LOG(("RPC Error: bad length of unpacked data 0")); } return result; } bool ConnectionPrivate::requestsFixTimeSalt(const QVector &ids, int32 serverTime, uint64 serverSalt) { uint32 idsCount = ids.size(); for (uint32 i = 0; i < idsCount; ++i) { if (wasSent(ids[i].v)) {// found such msg_id in recent acked requests or in recent sent requests if (serverSalt) sessionData->setSalt(serverSalt); unixtimeSet(serverTime, true); return true; } } return false; } void ConnectionPrivate::requestsAcked(const QVector &ids, bool byResponse) { uint32 idsCount = ids.size(); DEBUG_LOG(("Message Info: requests acked, ids %1").arg(LogIdsVector(ids))); auto clearedBecauseTooOld = std::vector(); QVector toAckMore; { QWriteLocker locker1(sessionData->wereAckedMutex()); auto &wereAcked = sessionData->wereAckedMap(); { QWriteLocker locker2(sessionData->haveSentMutex()); auto &haveSent = sessionData->haveSentMap(); for (uint32 i = 0; i < idsCount; ++i) { mtpMsgId msgId = ids[i].v; const auto req = haveSent.find(msgId); if (req != haveSent.cend()) { if (!req.value()->msDate) { DEBUG_LOG(("Message Info: container ack received, msgId %1").arg(ids[i].v)); uint32 inContCount = ((*req)->size() - 8) / 2; const mtpMsgId *inContId = (const mtpMsgId *)(req.value()->constData() + 8); toAckMore.reserve(toAckMore.size() + inContCount); for (uint32 j = 0; j < inContCount; ++j) { toAckMore.push_back(MTP_long(*(inContId++))); } haveSent.erase(req); } else { mtpRequestId reqId = req.value()->requestId; bool moveToAcked = byResponse; if (!moveToAcked) { // ignore ACK, if we need a response (if we have a handler) moveToAcked = !_instance->hasCallbacks(reqId); } if (moveToAcked) { wereAcked.insert(msgId, reqId); haveSent.erase(req); } else { DEBUG_LOG(("Message Info: ignoring ACK for msgId %1 because request %2 requires a response").arg(msgId).arg(reqId)); } } } else { DEBUG_LOG(("Message Info: msgId %1 was not found in recent sent, while acking requests, searching in resend...").arg(msgId)); QWriteLocker locker3(sessionData->toResendMutex()); auto &toResend = sessionData->toResendMap(); const auto reqIt = toResend.find(msgId); if (reqIt != toResend.cend()) { const auto reqId = reqIt.value(); bool moveToAcked = byResponse; if (!moveToAcked) { // ignore ACK, if we need a response (if we have a handler) moveToAcked = !_instance->hasCallbacks(reqId); } if (moveToAcked) { QWriteLocker locker4(sessionData->toSendMutex()); auto &toSend = sessionData->toSendMap(); const auto req = toSend.find(reqId); if (req != toSend.cend()) { wereAcked.insert(msgId, req.value()->requestId); if (req.value()->requestId != reqId) { DEBUG_LOG(("Message Error: for msgId %1 found resent request, requestId %2, contains requestId %3").arg(msgId).arg(reqId).arg(req.value()->requestId)); } else { DEBUG_LOG(("Message Info: acked msgId %1 that was prepared to resend, requestId %2").arg(msgId).arg(reqId)); } toSend.erase(req); } else { DEBUG_LOG(("Message Info: msgId %1 was found in recent resent, requestId %2 was not found in prepared to send").arg(msgId)); } toResend.erase(reqIt); } else { DEBUG_LOG(("Message Info: ignoring ACK for msgId %1 because request %2 requires a response").arg(msgId).arg(reqId)); } } else { DEBUG_LOG(("Message Info: msgId %1 was not found in recent resent either").arg(msgId)); } } } } uint32 ackedCount = wereAcked.size(); if (ackedCount > kIdsBufferSize) { DEBUG_LOG(("Message Info: removing some old acked sent msgIds %1").arg(ackedCount - kIdsBufferSize)); clearedBecauseTooOld.reserve(ackedCount - kIdsBufferSize); while (ackedCount-- > kIdsBufferSize) { auto i = wereAcked.begin(); clearedBecauseTooOld.push_back(RPCCallbackClear( i.value(), RPCError::TimeoutError)); wereAcked.erase(i); } } } if (!clearedBecauseTooOld.empty()) { _instance->clearCallbacksDelayed(std::move(clearedBecauseTooOld)); } if (toAckMore.size()) { requestsAcked(toAckMore); } } void ConnectionPrivate::handleMsgsStates(const QVector &ids, const QByteArray &states, QVector &acked) { uint32 idsCount = ids.size(); if (!idsCount) { DEBUG_LOG(("Message Info: void ids vector in handleMsgsStates()")); return; } if (states.size() < idsCount) { LOG(("Message Error: got less states than required ids count.")); return; } acked.reserve(acked.size() + idsCount); for (uint32 i = 0, count = idsCount; i < count; ++i) { char state = states[i]; uint64 requestMsgId = ids[i].v; { QReadLocker locker(sessionData->haveSentMutex()); const auto &haveSent = sessionData->haveSentMap(); const auto haveSentEnd = haveSent.cend(); if (haveSent.find(requestMsgId) == haveSentEnd) { DEBUG_LOG(("Message Info: state was received for msgId %1, but request is not found, looking in resent requests...").arg(requestMsgId)); QWriteLocker locker2(sessionData->toResendMutex()); auto &toResend = sessionData->toResendMap(); const auto reqIt = toResend.find(requestMsgId); if (reqIt != toResend.cend()) { if ((state & 0x07) != 0x04) { // was received DEBUG_LOG(("Message Info: state was received for msgId %1, state %2, already resending in container").arg(requestMsgId).arg((int32)state)); } else { DEBUG_LOG(("Message Info: state was received for msgId %1, state %2, ack, cancelling resend").arg(requestMsgId).arg((int32)state)); acked.push_back(MTP_long(requestMsgId)); // will remove from resend in requestsAcked } } else { DEBUG_LOG(("Message Info: msgId %1 was not found in recent resent either").arg(requestMsgId)); } continue; } } if ((state & 0x07) != 0x04) { // was received DEBUG_LOG(("Message Info: state was received for msgId %1, state %2, resending in container").arg(requestMsgId).arg((int32)state)); resend(requestMsgId, 10, true); } else { DEBUG_LOG(("Message Info: state was received for msgId %1, state %2, ack").arg(requestMsgId).arg((int32)state)); acked.push_back(MTP_long(requestMsgId)); } } } void ConnectionPrivate::resend(quint64 msgId, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo) { if (msgId == _pingMsgId) return; emit resendAsync(msgId, msCanWait, forceContainer, sendMsgStateInfo); } void ConnectionPrivate::resendMany(QVector msgIds, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo) { for (int32 i = 0, l = msgIds.size(); i < l; ++i) { if (msgIds.at(i) == _pingMsgId) { msgIds.remove(i); --l; } } emit resendManyAsync(msgIds, msCanWait, forceContainer, sendMsgStateInfo); } void ConnectionPrivate::onConnected( not_null connection) { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; 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(); } _waitForConnected = kMinConnectedTimeout; _waitForConnectedTimer.cancel(); 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()) { 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(); updateAuthKey(); } 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 || !_connection) return; DEBUG_LOG(("AuthKey Info: Connection updating key from Session, dc %1").arg(_shiftedDcId)); uint64 newKeyId = 0; { ReadLockerAttempt lock(sessionData->keyMutex()); if (!lock) { DEBUG_LOG(("MTP Info: could not lock auth_key for read, waiting signal emit")); clearMessages(); keyId = newKeyId; return; // some other connection is getting key } auto key = sessionData->getKey(); newKeyId = key ? key->keyId() : 0; } if (keyId != newKeyId) { clearMessages(); keyId = newKeyId; } DEBUG_LOG(("AuthKey Info: Connection update key from Session, dc %1 result: %2").arg(_shiftedDcId).arg(Logs::mb(&keyId, sizeof(keyId)).str())); if (keyId) { return authKeyCreated(); } DEBUG_LOG(("AuthKey Info: No key in updateAuthKey(), will be creating auth_key")); lockKey(); auto &key = sessionData->getKey(); if (key) { if (keyId != key->keyId()) clearMessages(); keyId = key->keyId(); unlockKey(); return authKeyCreated(); } else if (_instance->isKeysDestroyer()) { // We are here to destroy an old key, so we're done. LOG(("MTP Error: No key %1 in updateAuthKey() for destroying.").arg(_shiftedDcId)); emit _instance->keyDestroyed(_shiftedDcId); return; } _authKeyData = std::make_unique(); _authKeyStrings = std::make_unique(); _authKeyData->nonce = rand_value(); MTPReq_pq_multi req_pq; req_pq.vnonce = _authKeyData->nonce; connect(_connection, &AbstractConnection::receivedData, [=] { pqAnswered(); }); DEBUG_LOG(("AuthKey Info: sending Req_pq...")); lockFinished.unlock(); sendNotSecureRequest(req_pq); } void ConnectionPrivate::clearMessages() { if (keyId && keyId != kRecreateKeyId && _connection) { _connection->received().clear(); } } void ConnectionPrivate::pqAnswered() { disconnect(_connection, &AbstractConnection::receivedData, nullptr, nullptr); DEBUG_LOG(("AuthKey Info: receiving Req_pq answer...")); MTPReq_pq::ResponseType res_pq; if (!readNotSecureResponse(res_pq)) { return restart(); } auto &res_pq_data = res_pq.c_resPQ(); if (res_pq_data.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in res_pq)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&res_pq_data.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); return restart(); } auto rsaKey = internal::RSAPublicKey(); if (!_instance->dcOptions()->getDcRSAKey(BareDcId(_shiftedDcId), res_pq.c_resPQ().vserver_public_key_fingerprints.v, &rsaKey)) { if (_dcType == DcType::Cdn) { LOG(("Warning: CDN public RSA key not found")); requestCDNConfig(); return; } LOG(("AuthKey Error: could not choose public RSA key")); return restart(); } Assert(rsaKey.isValid()); _authKeyData->server_nonce = res_pq_data.vserver_nonce; _authKeyData->new_nonce = rand_value(); auto &pq = res_pq_data.vpq.v; auto p = QByteArray(); auto q = QByteArray(); if (!internal::parsePQ(pq, p, q)) { LOG(("AuthKey Error: could not factor pq!")); DEBUG_LOG(("AuthKey Error: problematic pq: %1").arg(Logs::mb(pq.constData(), pq.length()).str())); return restart(); } auto p_q_inner = MTP_p_q_inner_data_dc( res_pq_data.vpq, MTP_bytes(std::move(p)), MTP_bytes(std::move(q)), _authKeyData->nonce, _authKeyData->server_nonce, _authKeyData->new_nonce, MTP_int(getProtocolDcId())); auto dhEncString = encryptPQInnerRSA(p_q_inner, rsaKey); if (dhEncString.empty()) { return restart(); } connect(_connection, &AbstractConnection::receivedData, [=] { dhParamsAnswered(); }); DEBUG_LOG(("AuthKey Info: sending Req_DH_params...")); MTPReq_DH_params req_DH_params; req_DH_params.vnonce = _authKeyData->nonce; req_DH_params.vserver_nonce = _authKeyData->server_nonce; req_DH_params.vpublic_key_fingerprint = MTP_long(rsaKey.getFingerPrint()); req_DH_params.vp = p_q_inner.c_p_q_inner_data_dc().vp; req_DH_params.vq = p_q_inner.c_p_q_inner_data_dc().vq; req_DH_params.vencrypted_data = MTP_bytes(dhEncString); sendNotSecureRequest(req_DH_params); } bytes::vector ConnectionPrivate::encryptPQInnerRSA( const MTPP_Q_inner_data &data, const internal::RSAPublicKey &key) { auto p_q_inner_size = data.innerLength(); auto encSize = (p_q_inner_size >> 2) + 6; if (encSize >= 65) { auto tmp = mtpBuffer(); tmp.reserve(encSize); data.write(tmp); LOG(("AuthKey Error: too large data for RSA encrypt, size %1").arg(encSize * sizeof(mtpPrime))); DEBUG_LOG(("AuthKey Error: bad data for RSA encrypt %1").arg(Logs::mb(&tmp[0], tmp.size() * 4).str())); return {}; // can't be 255-byte string } auto encBuffer = mtpBuffer(); encBuffer.reserve(65); // 260 bytes encBuffer.resize(6); encBuffer[0] = 0; data.write(encBuffer); hashSha1(&encBuffer[6], p_q_inner_size, &encBuffer[1]); if (encSize < 65) { encBuffer.resize(65); memset_rand(&encBuffer[encSize], (65 - encSize) * sizeof(mtpPrime)); } auto bytes = bytes::make_span(encBuffer); auto bytesToEncrypt = bytes.subspan(3, 256); return key.encrypt(bytesToEncrypt); } void ConnectionPrivate::dhParamsAnswered() { disconnect(_connection, &AbstractConnection::receivedData, nullptr, nullptr); DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer...")); MTPReq_DH_params::ResponseType res_DH_params; if (!readNotSecureResponse(res_DH_params)) { return restart(); } switch (res_DH_params.type()) { case mtpc_server_DH_params_ok: { const auto &encDH(res_DH_params.c_server_DH_params_ok()); if (encDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_ok)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); return restart(); } if (encDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_ok)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&encDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); return restart(); } auto &encDHStr = encDH.vencrypted_answer.v; uint32 encDHLen = encDHStr.length(), encDHBufLen = encDHLen >> 2; if ((encDHLen & 0x03) || encDHBufLen < 6) { LOG(("AuthKey Error: bad encrypted data length %1 (in server_DH_params_ok)!").arg(encDHLen)); DEBUG_LOG(("AuthKey Error: received encrypted data %1").arg(Logs::mb(encDHStr.constData(), encDHLen).str())); return restart(); } uint32 nlen = _authKeyData->new_nonce.innerLength(), slen = _authKeyData->server_nonce.innerLength(); uchar tmp_aes[1024], sha1ns[20], sha1sn[20], sha1nn[20]; memcpy(tmp_aes, &_authKeyData->new_nonce, nlen); memcpy(tmp_aes + nlen, &_authKeyData->server_nonce, slen); memcpy(tmp_aes + nlen + slen, &_authKeyData->new_nonce, nlen); memcpy(tmp_aes + nlen + slen + nlen, &_authKeyData->new_nonce, nlen); hashSha1(tmp_aes, nlen + slen, sha1ns); hashSha1(tmp_aes + nlen, nlen + slen, sha1sn); hashSha1(tmp_aes + nlen + slen, nlen + nlen, sha1nn); mtpBuffer decBuffer; decBuffer.resize(encDHBufLen); memcpy(_authKeyData->aesKey, sha1ns, 20); memcpy(_authKeyData->aesKey + 20, sha1sn, 12); memcpy(_authKeyData->aesIV, sha1sn + 12, 8); memcpy(_authKeyData->aesIV + 8, sha1nn, 20); memcpy(_authKeyData->aesIV + 28, &_authKeyData->new_nonce, 4); aesIgeDecryptRaw(encDHStr.constData(), &decBuffer[0], encDHLen, _authKeyData->aesKey, _authKeyData->aesIV); const mtpPrime *from(&decBuffer[5]), *to(from), *end(from + (encDHBufLen - 5)); MTPServer_DH_inner_data dh_inner; dh_inner.read(to, end); const auto &dh_inner_data(dh_inner.c_server_DH_inner_data()); if (dh_inner_data.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_inner_data)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&dh_inner_data.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); return restart(); } if (dh_inner_data.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_inner_data)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&dh_inner_data.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); return restart(); } uchar sha1Buffer[20]; if (memcmp(&decBuffer[0], hashSha1(&decBuffer[5], (to - from) * sizeof(mtpPrime), sha1Buffer), 20)) { LOG(("AuthKey Error: sha1 hash of encrypted part did not match!")); DEBUG_LOG(("AuthKey Error: sha1 did not match, server_nonce: %1, new_nonce %2, encrypted data %3").arg(Logs::mb(&_authKeyData->server_nonce, 16).str()).arg(Logs::mb(&_authKeyData->new_nonce, 16).str()).arg(Logs::mb(encDHStr.constData(), encDHLen).str())); return restart(); } unixtimeSet(dh_inner_data.vserver_time.v); // check that dhPrime and (dhPrime - 1) / 2 are really prime if (!IsPrimeAndGood(bytes::make_span(dh_inner_data.vdh_prime.v), dh_inner_data.vg.v)) { LOG(("AuthKey Error: bad dh_prime primality!")); return restart(); } _authKeyStrings->dh_prime = bytes::make_vector( dh_inner_data.vdh_prime.v); _authKeyData->g = dh_inner_data.vg.v; _authKeyStrings->g_a = bytes::make_vector(dh_inner_data.vg_a.v); _authKeyData->retry_id = MTP_long(0); _authKeyData->retries = 0; } return dhClientParamsSend(); case mtpc_server_DH_params_fail: { const auto &encDH(res_DH_params.c_server_DH_params_fail()); if (encDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_fail)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); return restart(); } if (encDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_fail)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&encDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); return restart(); } uchar sha1Buffer[20]; if (encDH.vnew_nonce_hash != *(MTPint128*)(hashSha1(&_authKeyData->new_nonce, 32, sha1Buffer) + 1)) { LOG(("AuthKey Error: received new_nonce_hash did not match!")); DEBUG_LOG(("AuthKey Error: received new_nonce_hash: %1, new_nonce: %2").arg(Logs::mb(&encDH.vnew_nonce_hash, 16).str()).arg(Logs::mb(&_authKeyData->new_nonce, 32).str())); return restart(); } LOG(("AuthKey Error: server_DH_params_fail received!")); } return restart(); } LOG(("AuthKey Error: unknown server_DH_params received, typeId = %1").arg(res_DH_params.type())); return restart(); } void ConnectionPrivate::dhClientParamsSend() { if (++_authKeyData->retries > 5) { LOG(("AuthKey Error: could not create auth_key for %1 retries").arg(_authKeyData->retries - 1)); return restart(); } // gen rand 'b' auto randomSeed = bytes::vector(ModExpFirst::kRandomPowerSize); bytes::set_random(randomSeed); auto g_b_data = CreateModExp(_authKeyData->g, _authKeyStrings->dh_prime, randomSeed); if (g_b_data.modexp.empty()) { LOG(("AuthKey Error: could not generate good g_b.")); return restart(); } auto computedAuthKey = CreateAuthKey(_authKeyStrings->g_a, g_b_data.randomPower, _authKeyStrings->dh_prime); if (computedAuthKey.empty()) { LOG(("AuthKey Error: could not generate auth_key.")); return restart(); } AuthKey::FillData(_authKeyStrings->auth_key, computedAuthKey); // count auth_key hashes - parts of sha1(auth_key) auto auth_key_sha = hashSha1(_authKeyStrings->auth_key.data(), _authKeyStrings->auth_key.size()); memcpy(&_authKeyData->auth_key_aux_hash, auth_key_sha.data(), 8); memcpy(&_authKeyData->auth_key_hash, auth_key_sha.data() + 12, 8); auto client_dh_inner = MTP_client_DH_inner_data(_authKeyData->nonce, _authKeyData->server_nonce, _authKeyData->retry_id, MTP_bytes(g_b_data.modexp)); auto sdhEncString = encryptClientDHInner(client_dh_inner); connect(_connection, &AbstractConnection::receivedData, [=] { dhClientParamsAnswered(); }); MTPSet_client_DH_params req_client_DH_params; req_client_DH_params.vnonce = _authKeyData->nonce; req_client_DH_params.vserver_nonce = _authKeyData->server_nonce; req_client_DH_params.vencrypted_data = MTP_string(std::move(sdhEncString)); DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params...")); sendNotSecureRequest(req_client_DH_params); } std::string ConnectionPrivate::encryptClientDHInner(const MTPClient_DH_Inner_Data &data) { auto client_dh_inner_size = data.innerLength(); auto encSize = (client_dh_inner_size >> 2) + 5; auto encFullSize = encSize; if (encSize & 0x03) { encFullSize += 4 - (encSize & 0x03); } auto encBuffer = mtpBuffer(); encBuffer.reserve(encFullSize); encBuffer.resize(5); data.write(encBuffer); hashSha1(&encBuffer[5], client_dh_inner_size, &encBuffer[0]); if (encSize < encFullSize) { encBuffer.resize(encFullSize); memset_rand(&encBuffer[encSize], (encFullSize - encSize) * sizeof(mtpPrime)); } auto sdhEncString = std::string(encFullSize * 4, ' '); aesIgeEncryptRaw(&encBuffer[0], &sdhEncString[0], encFullSize * sizeof(mtpPrime), _authKeyData->aesKey, _authKeyData->aesIV); return sdhEncString; } void ConnectionPrivate::dhClientParamsAnswered() { QReadLocker lockFinished(&sessionDataMutex); if (!sessionData) return; 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; if (!readNotSecureResponse(res_client_DH_params)) { lockFinished.unlock(); return restart(); } switch (res_client_DH_params.type()) { case mtpc_dh_gen_ok: { const auto &resDH(res_client_DH_params.c_dh_gen_ok()); if (resDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_ok)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); lockFinished.unlock(); return restart(); } if (resDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_ok)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); lockFinished.unlock(); return restart(); } _authKeyData->new_nonce_buf[32] = 1; uchar sha1Buffer[20]; if (resDH.vnew_nonce_hash1 != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) { LOG(("AuthKey Error: received new_nonce_hash1 did not match!")); DEBUG_LOG(("AuthKey Error: received new_nonce_hash1: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash1, 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str())); lockFinished.unlock(); return restart(); } uint64 salt1 = _authKeyData->new_nonce.l.l, salt2 = _authKeyData->server_nonce.l, serverSalt = salt1 ^ salt2; sessionData->setSalt(serverSalt); auto authKey = std::make_shared(AuthKey::Type::Generated, BareDcId(_shiftedDcId), _authKeyStrings->auth_key); DEBUG_LOG(("AuthKey Info: auth key gen succeed, id: %1, server salt: %2").arg(authKey->keyId()).arg(serverSalt)); sessionData->owner()->notifyKeyCreated(std::move(authKey)); // slot will call authKeyCreated() sessionData->clear(_instance); unlockKey(); } return; case mtpc_dh_gen_retry: { const auto &resDH(res_client_DH_params.c_dh_gen_retry()); if (resDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_retry)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); lockFinished.unlock(); return restart(); } if (resDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_retry)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); lockFinished.unlock(); return restart(); } _authKeyData->new_nonce_buf[32] = 2; uchar sha1Buffer[20]; if (resDH.vnew_nonce_hash2 != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) { LOG(("AuthKey Error: received new_nonce_hash2 did not match!")); DEBUG_LOG(("AuthKey Error: received new_nonce_hash2: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash2, 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str())); lockFinished.unlock(); return restart(); } _authKeyData->retry_id = _authKeyData->auth_key_aux_hash; } return dhClientParamsSend(); case mtpc_dh_gen_fail: { const auto &resDH(res_client_DH_params.c_dh_gen_fail()); if (resDH.vnonce != _authKeyData->nonce) { LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_fail)!")); DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce, 16).str()).arg(Logs::mb(&_authKeyData->nonce, 16).str())); lockFinished.unlock(); return restart(); } if (resDH.vserver_nonce != _authKeyData->server_nonce) { LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_fail)!")); DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce, 16).str()).arg(Logs::mb(&_authKeyData->server_nonce, 16).str())); lockFinished.unlock(); return restart(); } _authKeyData->new_nonce_buf[32] = 3; uchar sha1Buffer[20]; if (resDH.vnew_nonce_hash3 != *(MTPint128*)(hashSha1(_authKeyData->new_nonce_buf, 41, sha1Buffer) + 1)) { LOG(("AuthKey Error: received new_nonce_hash3 did not match!")); DEBUG_LOG(("AuthKey Error: received new_nonce_hash3: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash3, 16).str()).arg(Logs::mb(_authKeyData->new_nonce_buf, 41).str())); lockFinished.unlock(); return restart(); } LOG(("AuthKey Error: dh_gen_fail received!")); } lockFinished.unlock(); return restart(); } LOG(("AuthKey Error: unknown set_client_DH_params_answer received, typeId = %1").arg(res_client_DH_params.type())); lockFinished.unlock(); return restart(); } void ConnectionPrivate::authKeyCreated() { clearAuthKeyData(); 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); if (restarted) { emit resendAllAsync(); restarted = false; } } _pingIdToSend = rand_value(); // get server_salt emit needToSendAsync(); } void ConnectionPrivate::clearAuthKeyData() { auto zeroMemory = [](bytes::span bytes) { #ifdef Q_OS_WIN2 SecureZeroMemory(bytes.data(), bytes.size()); #else // Q_OS_WIN auto end = reinterpret_cast(bytes.data()) + bytes.size(); for (volatile auto p = reinterpret_cast(bytes.data()); p != end; ++p) { *p = 0; } #endif // Q_OS_WIN }; if (_authKeyData) { zeroMemory(gsl::make_span(reinterpret_cast(_authKeyData.get()), sizeof(AuthKeyCreateData))); _authKeyData.reset(); } if (_authKeyStrings) { if (!_authKeyStrings->dh_prime.empty()) { zeroMemory(_authKeyStrings->dh_prime); } if (!_authKeyStrings->g_a.empty()) { zeroMemory(_authKeyStrings->g_a); } zeroMemory(_authKeyStrings->auth_key); _authKeyStrings.reset(); } } void ConnectionPrivate::onError( not_null connection, qint32 errorCode) { if (errorCode == -429) { LOG(("Protocol Error: -429 flood code returned!")); } else if (errorCode == -444) { LOG(("Protocol Error: -444 bad dc_id code returned!")); InvokeQueued(_instance, [instance = _instance] { instance->badConfigurationError(); }); } removeTestConnection(connection); if (_testConnections.empty()) { handleError(errorCode); } else { confirmBestConnection(); } } void ConnectionPrivate::handleError(int errorCode) { destroyAllConnections(); _waitForConnectedTimer.cancel(); if (errorCode == -404) { if (_instance->isKeysDestroyer()) { LOG(("MTP Info: -404 error received on destroying key %1, assuming it is destroyed.").arg(_shiftedDcId)); emit _instance->keyDestroyed(_shiftedDcId); return; } else if (_dcType == DcType::Cdn) { LOG(("MTP Info: -404 error received in CDN dc %1, assuming it was destroyed, recreating.").arg(_shiftedDcId)); clearMessages(); keyId = kRecreateKeyId; return restart(); } } MTP_LOG(_shiftedDcId, ("Restarting after error in connection, error code: %1...").arg(errorCode)); return restart(); } void ConnectionPrivate::onReadyData() { } template void ConnectionPrivate::sendNotSecureRequest(const Request &request) { auto packet = _connection->prepareNotSecurePacket(request); DEBUG_LOG(("AuthKey Info: sending request, size: %1, time: %3" ).arg(packet.size() - 8 ).arg(packet[5])); const auto bytesSize = packet.size() * sizeof(mtpPrime); _connection->sendData(std::move(packet)); onSentSome(bytesSize); } template bool ConnectionPrivate::readNotSecureResponse(Response &response) { onReceivedSome(); if (_connection->received().empty()) { LOG(("AuthKey Error: " "trying to read response from empty received list")); return false; } const auto buffer = std::move(_connection->received().front()); _connection->received().pop_front(); const auto answer = _connection->parseNotSecureResponse(buffer); if (answer.empty()) { return false; } try { auto from = answer.data(); response.read(from, from + answer.size()); } catch (Exception &) { return false; } return true; } bool ConnectionPrivate::sendSecureRequest( SecureRequest &&request, bool needAnyResponse, QReadLocker &lockFinished) { request.addPadding(_connection->requiresExtendedPadding()); uint32 fullSize = request->size(); if (fullSize < 9) { return false; } auto messageSize = request.messageSize(); if (messageSize < 5 || fullSize < messageSize + 4) { return false; } auto lock = ReadLockerAttempt(sessionData->keyMutex()); if (!lock) { DEBUG_LOG(("MTP Info: could not lock key for read in sendBuffer(), dc %1, restarting...").arg(_shiftedDcId)); lockFinished.unlock(); restart(); return false; } auto key = sessionData->getKey(); if (!key || key->keyId() != keyId) { DEBUG_LOG(("MTP Error: auth_key id for dc %1 changed").arg(_shiftedDcId)); lockFinished.unlock(); restart(); return false; } auto session = sessionData->getSession(); auto salt = sessionData->getSalt(); memcpy(request->data() + 0, &salt, 2 * sizeof(mtpPrime)); memcpy(request->data() + 2, &session, 2 * sizeof(mtpPrime)); auto from = request->constData() + 4; MTP_LOG(_shiftedDcId, ("Send: ") + mtpTextSerialize(from, from + messageSize)); #ifdef TDESKTOP_MTPROTO_OLD uint32 padding = fullSize - 4 - messageSize; uchar encryptedSHA[20]; MTPint128 &msgKey(*(MTPint128*)(encryptedSHA + 4)); hashSha1( request->constData(), (fullSize - padding) * sizeof(mtpPrime), encryptedSHA); auto packet = _connection->prepareSecurePacket(keyId, msgKey, fullSize); const auto prefix = packet.size(); packet.resize(prefix + fullSize); aesIgeEncrypt_oldmtp( request->constData(), &packet[prefix], fullSize * sizeof(mtpPrime), key, msgKey); #else // TDESKTOP_MTPROTO_OLD uchar encryptedSHA256[32]; MTPint128 &msgKey(*(MTPint128*)(encryptedSHA256 + 8)); SHA256_CTX msgKeyLargeContext; SHA256_Init(&msgKeyLargeContext); SHA256_Update(&msgKeyLargeContext, key->partForMsgKey(true), 32); SHA256_Update(&msgKeyLargeContext, request->constData(), fullSize * sizeof(mtpPrime)); SHA256_Final(encryptedSHA256, &msgKeyLargeContext); auto packet = _connection->prepareSecurePacket(keyId, msgKey, fullSize); const auto prefix = packet.size(); packet.resize(prefix + fullSize); aesIgeEncrypt( request->constData(), &packet[prefix], fullSize * sizeof(mtpPrime), key, msgKey); #endif // TDESKTOP_MTPROTO_OLD DEBUG_LOG(("MTP Info: sending request, size: %1, num: %2, time: %3").arg(fullSize + 6).arg((*request)[4]).arg((*request)[5])); _connection->setSentEncrypted(); _connection->sendData(std::move(packet)); if (needAnyResponse) { onSentSome((prefix + fullSize) * sizeof(mtpPrime)); } return true; } mtpRequestId ConnectionPrivate::wasSent(mtpMsgId msgId) const { if (msgId == _pingMsgId) return mtpRequestId(0xFFFFFFFF); { QReadLocker locker(sessionData->haveSentMutex()); const auto &haveSent = sessionData->haveSentMap(); const auto i = haveSent.constFind(msgId); if (i != haveSent.cend()) { return i.value()->requestId ? i.value()->requestId : mtpRequestId(0xFFFFFFFF); } } { QReadLocker locker(sessionData->toResendMutex()); const auto &toResend = sessionData->toResendMap(); const auto i = toResend.constFind(msgId); if (i != toResend.cend()) return i.value(); } { QReadLocker locker(sessionData->wereAckedMutex()); const auto &wereAcked = sessionData->wereAckedMap(); const auto i = wereAcked.constFind(msgId); if (i != wereAcked.cend()) return i.value(); } return 0; } void ConnectionPrivate::lockKey() { unlockKey(); sessionData->keyMutex()->lockForWrite(); myKeyLock = true; } void ConnectionPrivate::unlockKey() { if (myKeyLock) { myKeyLock = false; sessionData->keyMutex()->unlock(); } } ConnectionPrivate::~ConnectionPrivate() { clearAuthKeyData(); Assert(_finished && _connection == nullptr && _testConnections.empty()); } void ConnectionPrivate::stop() { QWriteLocker lockFinished(&sessionDataMutex); if (sessionData) { if (myKeyLock) { sessionData->owner()->notifyKeyCreated(AuthKeyPtr()); // release key lock, let someone else create it sessionData->keyMutex()->unlock(); myKeyLock = false; } sessionData = nullptr; } } } // namespace internal bool IsPrimeAndGood(bytes::const_span primeBytes, int g) { return internal::IsPrimeAndGood(primeBytes, g); } bool IsGoodModExpFirst(const openssl::BigNum &modexp, const openssl::BigNum &prime) { return internal::IsGoodModExpFirst(modexp, prime); } ModExpFirst CreateModExp(int g, bytes::const_span primeBytes, bytes::const_span randomSeed) { return internal::CreateModExp(g, primeBytes, randomSeed); } bytes::vector CreateAuthKey(bytes::const_span firstBytes, bytes::const_span randomBytes, bytes::const_span primeBytes) { return internal::CreateAuthKey(firstBytes, randomBytes, primeBytes); } } // namespace MTP