2688 lines
86 KiB
C++
2688 lines
86 KiB
C++
/*
|
|
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/details/mtproto_dc_key_creator.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"
|
|
#include "base/unixtime.h"
|
|
|
|
extern "C" {
|
|
#include <openssl/bn.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/aes.h>
|
|
#include <openssl/sha.h>
|
|
#include <openssl/md5.h>
|
|
#include <openssl/rand.h>
|
|
} // extern "C"
|
|
|
|
#ifdef small
|
|
#undef small
|
|
#endif // small
|
|
|
|
namespace MTP {
|
|
namespace internal {
|
|
namespace {
|
|
|
|
constexpr auto kRecreateKeyId = AuthKey::KeyId(0xFFFFFFFFFFFFFFFFULL);
|
|
constexpr auto kIntSize = static_cast<int>(sizeof(mtpPrime));
|
|
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;
|
|
|
|
using namespace details;
|
|
|
|
QString LogIdsVector(const QVector<MTPlong> &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 + "]";
|
|
}
|
|
|
|
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 = (tl::count_length(from) >> 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);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Connection::Connection(not_null<Instance*> instance) : _instance(instance) {
|
|
}
|
|
|
|
void Connection::start(SessionData *sessionData, ShiftedDcId shiftedDcId) {
|
|
Expects(_thread == nullptr && _private == nullptr);
|
|
|
|
_thread = std::make_unique<Thread>();
|
|
auto newData = std::make_unique<ConnectionPrivate>(
|
|
_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(),
|
|
protocolSecret,
|
|
_connectionOptions->proxy),
|
|
priority
|
|
});
|
|
const 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);
|
|
});
|
|
connect(weak, &AbstractConnection::syncTimeRequest, [=] {
|
|
InvokeQueued(_instance, [instance = _instance] {
|
|
instance->syncHttpUnixtime();
|
|
});
|
|
});
|
|
|
|
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();
|
|
_keyCreator = nullptr;
|
|
_connection = nullptr;
|
|
}
|
|
|
|
ConnectionPrivate::ConnectionPrivate(
|
|
not_null<Instance*> instance,
|
|
not_null<QThread*> thread,
|
|
not_null<Connection*> owner,
|
|
not_null<SessionData*> 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<quint64> >("QVector<quint64>");
|
|
}
|
|
|
|
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<quint64>,qint64,bool,bool)), sessionData->owner(), SLOT(resendMany(QVector<quint64>,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 = base::unixtime::mtproto_msg_id();
|
|
auto setSeqNumbers = RequestMap();
|
|
auto replaces = QMap<mtpMsgId, mtpMsgId>();
|
|
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;
|
|
}
|
|
const auto m = base::unixtime::mtproto_msg_id();
|
|
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());
|
|
}
|
|
}
|
|
// Collect all non-container requests.
|
|
for (auto i = toResend.cbegin(), e = toResend.cend(); i != e; ++i) {
|
|
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;
|
|
}
|
|
const auto m = base::unixtime::mtproto_msg_id();
|
|
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<uint64>();
|
|
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()) {
|
|
DEBUG_LOG(("MTP Info: Replaced %1 with %2 in wereAcked."
|
|
).arg(i.key()
|
|
).arg(i.value()));
|
|
|
|
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 = base::unixtime::mtproto_msg_id();
|
|
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) {
|
|
auto msgId = prepareToSend(req, bigMsgId);
|
|
if (msgId > bigMsgId) {
|
|
msgId = replaceMsgId(req, bigMsgId);
|
|
}
|
|
if (msgId >= bigMsgId) {
|
|
bigMsgId = base::unixtime::mtproto_msg_id();
|
|
}
|
|
*(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<mtpPingId>();
|
|
}
|
|
}
|
|
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<MTPlong>(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<MTPlong>(resendRequestData))));
|
|
resendRequest->msDate = crl::now(); // > 0 - can send without container
|
|
resendRequest->requestId = 0; // dont add to haveSent / wereAcked maps
|
|
|
|
resendRequestData.clear();
|
|
}
|
|
if (!prependOnly) {
|
|
QVector<MTPlong> 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<MTPlong>(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<SecureRequest> 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
|
|
const auto appVersion = QString::fromLatin1(AppVersionStr)
|
|
+ " mac store";
|
|
#elif defined OS_WIN_STORE // OS_MAC_STORE
|
|
const auto appVersion = QString::fromLatin1(AppVersionStr)
|
|
+ " win 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<SecureRequest>::Flag;
|
|
initWrapper = MTPInitConnection<SecureRequest>(
|
|
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 = (tl::count_length(initWrapper) >> 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();
|
|
}
|
|
|
|
const auto msgId = prepareToSend(
|
|
toSendRequest,
|
|
base::unixtime::mtproto_msg_id());
|
|
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 = tl::count_length(toSendRequest) >> 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 = (tl::count_length(toSendRequest) >> 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<mtpBuffer>(*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<mtpBuffer>(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);
|
|
|
|
// check for a valid container
|
|
auto bigMsgId = base::unixtime::mtproto_msg_id();
|
|
|
|
// 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 = base::unixtime::mtproto_msg_id();
|
|
}
|
|
*(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, tl::count_length(req));
|
|
*(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<ConnectionOptions>(
|
|
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<Variants::Protocol>(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));
|
|
_instance->checkIfKeyWasDestroyed(_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<uint64>(_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);
|
|
}
|
|
|
|
connectingTimedOut();
|
|
restarted = true;
|
|
|
|
DEBUG_LOG(("MTP Info: immediate restart!"));
|
|
InvokeQueued(this, [=] { connectToServer(); });
|
|
}
|
|
|
|
void ConnectionPrivate::waitBetterFailed() {
|
|
confirmBestConnection();
|
|
}
|
|
|
|
void ConnectionPrivate::connectingTimedOut() {
|
|
for (const auto &connection : _testConnections) {
|
|
connection.data->timedOut();
|
|
}
|
|
doDisconnect();
|
|
}
|
|
|
|
void ConnectionPrivate::doDisconnect() {
|
|
destroyAllConnections();
|
|
|
|
{
|
|
QReadLocker lockFinished(&sessionDataMutex);
|
|
if (sessionData) {
|
|
unlockKey();
|
|
}
|
|
}
|
|
|
|
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<const mtpPrime*>(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<uint32>(encryptedBytesCount) - static_cast<uint32>(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<uchar, 32> 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();
|
|
}
|
|
|
|
const auto serverTime = int32(msgId >> 32);
|
|
const auto clientTime = base::unixtime::now();
|
|
const auto 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) {
|
|
const auto cons = mtpTypeId(*from);
|
|
|
|
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) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
|
|
const mtpPrime *otherEnd;
|
|
const auto msgsCount = (uint32)*(from++);
|
|
DEBUG_LOG(("Message Info: container received, count: %1").arg(msgsCount));
|
|
for (uint32 i = 0; i < msgsCount; ++i) {
|
|
if (from + 4 >= end) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
otherEnd = from + 4;
|
|
|
|
MTPlong inMsgId;
|
|
if (!inMsgId.read(from, otherEnd)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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;
|
|
if (!inSeqNo.read(from, otherEnd)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
MTPint bytes;
|
|
if (!bytes.read(from, otherEnd)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
|
|
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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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);
|
|
base::unixtime::update(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);
|
|
base::unixtime::update(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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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);
|
|
base::unixtime::update(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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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
|
|
base::unixtime::update(serverTime, true);
|
|
|
|
DEBUG_LOG(("Message Info: unixtime updated from mtpc_msgs_state_info, now %1").arg(serverTime));
|
|
|
|
badTime = false;
|
|
}
|
|
requestBuffer = replyTo.value();
|
|
}
|
|
QVector<MTPlong> 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;
|
|
}
|
|
const mtpPrime *rFrom = requestBuffer->constData() + 8, *rEnd = requestBuffer->constData() + requestBuffer->size();
|
|
if (mtpTypeId(*rFrom) == mtpc_msgs_state_req) {
|
|
MTPMsgsStateReq request;
|
|
if (!request.read(rFrom, rEnd)) {
|
|
LOG(("Message Error: could not parse sent msgs_state_req"));
|
|
return HandleResult::ParseError;
|
|
}
|
|
handleMsgsStates(request.c_msgs_state_req().vmsg_ids().v, states, toAck);
|
|
} else {
|
|
MTPMsgResendReq request;
|
|
if (!request.read(rFrom, rEnd)) {
|
|
LOG(("Message Error: could not parse sent msgs_state_req"));
|
|
return HandleResult::ParseError;
|
|
}
|
|
handleMsgsStates(request.c_msg_resend_req().vmsg_ids().v, states, toAck);
|
|
}
|
|
|
|
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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
auto &data = msg.c_msgs_all_info();
|
|
auto &ids = data.vmsg_ids().v;
|
|
auto &states = data.vinfo().v;
|
|
|
|
QVector<MTPlong> 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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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<MTPlong> 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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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<quint64> 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) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
auto response = SerializedMessage();
|
|
|
|
MTPlong reqMsgId;
|
|
if (!reqMsgId.read(++from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
mtpTypeId typeId = from[0];
|
|
|
|
DEBUG_LOG(("RPC Info: response received for %1, queueing...").arg(reqMsgId.v));
|
|
|
|
QVector<MTPlong> 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.empty()) {
|
|
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;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
const auto &data(msg.c_new_session_created());
|
|
|
|
if (badTime) {
|
|
if (requestsFixTimeSalt(QVector<MTPlong>(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<quint64> 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_pong: {
|
|
MTPPong msg;
|
|
if (!msg.read(from, end)) {
|
|
return HandleResult::ParseError;
|
|
}
|
|
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<MTPlong> ids(1, data.vmsg_id());
|
|
if (badTime) {
|
|
if (requestsFixTimeSalt(ids, serverTime, serverSalt)) {
|
|
badTime = false;
|
|
} else {
|
|
return HandleResult::Ignored;
|
|
}
|
|
}
|
|
requestsAcked(ids, true);
|
|
} return HandleResult::Success;
|
|
|
|
}
|
|
|
|
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 0x%1").arg(cons, 0, 16));
|
|
}
|
|
} else {
|
|
LOG(("Message Error: unexpected updates in dcType: %1").arg(static_cast<int>(_dcType)));
|
|
}
|
|
|
|
return HandleResult::Success;
|
|
}
|
|
|
|
mtpBuffer ConnectionPrivate::ungzip(const mtpPrime *from, const mtpPrime *end) const {
|
|
mtpBuffer result; // * 4 because of mtpPrime type
|
|
result.resize(0);
|
|
|
|
MTPstring packed;
|
|
if (!packed.read(from, end)) { // read packed string as serialized mtp string type
|
|
LOG(("RPC Error: could not read gziped bytes."));
|
|
return result;
|
|
}
|
|
uint32 packedLen = packed.v.size(), unpackedChunk = packedLen, unpackedLen = 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<Bytef*>(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<MTPlong> &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);
|
|
base::unixtime::update(serverTime, true);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ConnectionPrivate::requestsAcked(const QVector<MTPlong> &ids, bool byResponse) {
|
|
uint32 idsCount = ids.size();
|
|
|
|
DEBUG_LOG(("Message Info: requests acked, ids %1").arg(LogIdsVector(ids)));
|
|
|
|
auto clearedBecauseTooOld = std::vector<RPCCallbackClear>();
|
|
QVector<MTPlong> 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<MTPlong> &ids, const QByteArray &states, QVector<MTPlong> &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<quint64> 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<AbstractConnection*> 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<AbstractConnection*> 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<AbstractConnection*> 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();
|
|
|
|
const 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));
|
|
_instance->checkIfKeyWasDestroyed(_shiftedDcId);
|
|
return;
|
|
}
|
|
lockFinished.unlock();
|
|
|
|
createDcKey();
|
|
}
|
|
|
|
void ConnectionPrivate::createDcKey() {
|
|
using Result = DcKeyCreator::Result;
|
|
using Error = DcKeyCreator::Error;
|
|
auto delegate = DcKeyCreator::Delegate();
|
|
delegate.done = [=](base::expected<Result, Error> result) {
|
|
_keyCreator = nullptr;
|
|
|
|
if (result) {
|
|
QReadLocker lockFinished(&sessionDataMutex);
|
|
if (!sessionData) return;
|
|
|
|
sessionData->setSalt(result->serverSalt);
|
|
|
|
auto authKey = std::move(result->key);
|
|
|
|
DEBUG_LOG(("AuthKey Info: auth key gen succeed, id: %1, server salt: %2").arg(authKey->keyId()).arg(result->serverSalt));
|
|
|
|
sessionData->owner()->notifyKeyCreated(std::move(authKey)); // slot will call authKeyCreated()
|
|
sessionData->clear(_instance);
|
|
unlockKey();
|
|
} else if (result.error() == Error::UnknownPublicKey) {
|
|
if (_dcType == DcType::Cdn) {
|
|
LOG(("Warning: CDN public RSA key not found"));
|
|
requestCDNConfig();
|
|
} else {
|
|
LOG(("AuthKey Error: could not choose public RSA key"));
|
|
restart();
|
|
}
|
|
} else {
|
|
restart();
|
|
}
|
|
};
|
|
_keyCreator = std::make_unique<DcKeyCreator>(
|
|
BareDcId(_shiftedDcId),
|
|
getProtocolDcId(),
|
|
_connection.get(),
|
|
_instance->dcOptions(),
|
|
std::move(delegate));
|
|
}
|
|
|
|
void ConnectionPrivate::clearMessages() {
|
|
if (keyId && keyId != kRecreateKeyId && _connection) {
|
|
_connection->received().clear();
|
|
}
|
|
}
|
|
|
|
void ConnectionPrivate::authKeyCreated() {
|
|
_keyCreator = nullptr;
|
|
|
|
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<uint64>(); // get server_salt
|
|
|
|
emit needToSendAsync();
|
|
}
|
|
|
|
void ConnectionPrivate::onError(
|
|
not_null<AbstractConnection*> 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 (_dcType == DcType::Cdn && !_instance->isKeysDestroyer()) {
|
|
LOG(("MTP Info: -404 error received in CDN dc %1, assuming it was destroyed, recreating.").arg(_shiftedDcId));
|
|
clearMessages();
|
|
keyId = kRecreateKeyId;
|
|
return restart();
|
|
} else {
|
|
LOG(("MTP Info: -404 error received, informing instance."));
|
|
_instance->checkIfKeyWasDestroyed(_shiftedDcId);
|
|
if (_instance->isKeysDestroyer()) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
MTP_LOG(_shiftedDcId, ("Restarting after error in connection, error code: %1...").arg(errorCode));
|
|
return restart();
|
|
}
|
|
|
|
void ConnectionPrivate::onReadyData() {
|
|
}
|
|
|
|
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() {
|
|
Expects(_finished);
|
|
Expects(!_connection);
|
|
Expects(_testConnections.empty());
|
|
Expects(!_keyCreator);
|
|
}
|
|
|
|
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
|
|
} // namespace MTP
|