ZaStoGram_desktop/Telegram/SourceFiles/mtproto/instance/mtp_instance.cpp
loop-uh 11a404930a
All checks were successful
Desktop source guards / guards (push) Successful in 10s
Вернуть в MTP::Instance hasCallback/processCallback для тестовой заглушки RPC
Новый test_rpc_fixture апстрима отвечает на запросы синтезированными ответами через эти методы; у нас они оставались только в Instance::Private.
2026-09-25 15:32:02 +03:00

2262 lines
62 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/instance/mtp_instance.h"
#include "mtproto/auth/mtproto_auth_key.h"
#include "mtproto/dc_id.h"
#include "mtproto/details/mtproto_dcenter.h"
#include "mtproto/session/pause_state.h"
#include "mtproto/details/mtproto_rsa_public_key.h"
#include "mtproto/proxy/diagnostics.h"
#include "mtproto/proxy/proxy_services.h"
#include "mtproto/proxy/status.h"
#include "mtproto/runtime/connection_status.h"
#include "mtproto/runtime/runtime_environment.h"
#include "mtproto/config/special_config_request.h"
#include "mtproto/session/session.h"
#include "mtproto/config/mtproto_config.h"
#include "mtproto/config/mtproto_dc_options.h"
#include "mtproto/config/config_loader.h"
#include "mtproto/instance/request_registry.h"
#include "mtproto/instance/rpc_error_handler.h"
#include "mtproto/instance/sender.h"
#include "mtproto/session/session_state.h"
#include "base/unixtime.h"
#include "base/call_delayed.h"
#include "base/timer.h"
#include "base/network_reachability.h"
#include "test/test_rpc_retry.h"
namespace MTP {
namespace {
constexpr auto kConfigBecomesOldIn = 2 * 60 * crl::time(1000);
constexpr auto kConfigBecomesOldForBlockedIn = 8 * crl::time(1000);
constexpr auto kFileTransferSlowRequestThreshold = 8 * crl::time(1000);
using namespace details;
[[nodiscard]] ProxyDiagnosticsDirection FileDiagnosticsDirection(
FileTransferDirection direction) {
switch (direction) {
case FileTransferDirection::Download:
return ProxyDiagnosticsDirection::Download;
case FileTransferDirection::Upload:
return ProxyDiagnosticsDirection::Upload;
}
Unexpected("File transfer direction.");
}
[[nodiscard]] ProxyConnectionUse FileDiagnosticsUse(
FileTransferDirection direction) {
switch (direction) {
case FileTransferDirection::Download:
return ProxyConnectionUse::Media;
case FileTransferDirection::Upload:
return ProxyConnectionUse::Upload;
}
Unexpected("File transfer direction.");
}
[[nodiscard]] ProxyDiagnosticsRpcKind FileDiagnosticsRpcKind(
FileTransferRpcKind kind) {
switch (kind) {
case FileTransferRpcKind::GetFile:
return ProxyDiagnosticsRpcKind::GetFile;
case FileTransferRpcKind::GetWebFile:
return ProxyDiagnosticsRpcKind::GetWebFile;
case FileTransferRpcKind::GetCdnFile:
return ProxyDiagnosticsRpcKind::GetCdnFile;
case FileTransferRpcKind::GetCdnFileHashes:
return ProxyDiagnosticsRpcKind::GetCdnFileHashes;
case FileTransferRpcKind::ReuploadCdnFile:
return ProxyDiagnosticsRpcKind::ReuploadCdnFile;
case FileTransferRpcKind::SaveFilePart:
return ProxyDiagnosticsRpcKind::SaveFilePart;
case FileTransferRpcKind::SaveBigFilePart:
return ProxyDiagnosticsRpcKind::SaveBigFilePart;
}
Unexpected("File transfer RPC kind.");
}
[[nodiscard]] ProxyDiagnosticsErrorClass FileDiagnosticsErrorClass(
const Error &error) {
if (IsFloodError(error) || error.code() == 420) {
return ProxyDiagnosticsErrorClass::Flood;
}
switch (error.code()) {
case 400: return ProxyDiagnosticsErrorClass::BadRequest;
case 401: return ProxyDiagnosticsErrorClass::Unauthorized;
case 403: return ProxyDiagnosticsErrorClass::Forbidden;
case 404: return ProxyDiagnosticsErrorClass::NotFound;
case 406: return ProxyDiagnosticsErrorClass::NotAcceptable;
case 0: return ProxyDiagnosticsErrorClass::Transport;
}
return (error.code() < 0 || error.code() >= 500)
? ProxyDiagnosticsErrorClass::Server
: ProxyDiagnosticsErrorClass::Unknown;
}
[[nodiscard]] ProxyDiagnosticsEvent FileDiagnosticsEvent(
const FileTransferRequestTag::Trace &trace,
ProxyDiagnosticsTransition transition) {
auto event = ProxyDiagnosticsEvent();
event.source = ProxyDiagnosticsSource::MTP;
event.phase = ProxyDiagnosticsPhase::FileRpc;
event.attempt.traceId = trace.traceOrdinal;
event.attempt.use = FileDiagnosticsUse(trace.direction);
event.transition = transition;
event.direction = FileDiagnosticsDirection(trace.direction);
event.rpcKind = FileDiagnosticsRpcKind(trace.rpcKind);
event.laneOrdinal = trace.laneOrdinal;
event.requestOrdinal = trace.requestOrdinal;
event.firstInLane = trace.firstInLane;
return event;
}
[[nodiscard]] crl::time FileDiagnosticsDuration(
const FileTransferRequestTag::Trace &trace,
crl::time terminalAt) {
const auto startedAt = trace.enqueuedAt
? trace.enqueuedAt
: trace.firstSentAt;
return (startedAt > 0 && terminalAt > startedAt)
? (terminalAt - startedAt)
: crl::time();
}
void ReportFileTransferQueued(
not_null<RuntimeEnvironment*> runtime,
const SerializedRequest &request,
crl::time now) {
if (!request || !request->fileTransferTag) {
return;
}
const auto trace = request->fileTransferTag->trace;
auto event = std::optional<ProxyDiagnosticsEvent>();
{
QMutexLocker lock(&trace->mutex);
if (!trace->enqueuedAt) {
trace->enqueuedAt = now;
}
if (!trace->firstInLane || trace->queueEventEmitted) {
return;
}
trace->queueEventEmitted = true;
event = FileDiagnosticsEvent(
*trace,
ProxyDiagnosticsTransition::Queued);
}
WriteProxyDiagnosticsLine(runtime, std::move(*event));
}
void ReportFileTransferResult(
not_null<RuntimeEnvironment*> runtime,
const SerializedRequest &request,
crl::time now) {
if (!request || !request->fileTransferTag) {
return;
}
const auto trace = request->fileTransferTag->trace;
auto event = std::optional<ProxyDiagnosticsEvent>();
{
QMutexLocker lock(&trace->mutex);
if (trace->terminalEventEmitted) {
return;
}
trace->terminalAt = now;
trace->terminalEventEmitted = true;
const auto duration = FileDiagnosticsDuration(*trace, now);
if (trace->firstInLane) {
event = FileDiagnosticsEvent(
*trace,
ProxyDiagnosticsTransition::Result);
} else if (duration >= kFileTransferSlowRequestThreshold
&& !trace->slowEventEmitted) {
trace->slowEventEmitted = true;
event = FileDiagnosticsEvent(
*trace,
ProxyDiagnosticsTransition::Slow);
}
if (event) {
event->sendCount = trace->successfulSendCount;
event->totalMs = duration;
event->isFinal = true;
}
}
if (event) {
WriteProxyDiagnosticsLine(runtime, std::move(*event));
}
}
void ReportFileTransferError(
not_null<RuntimeEnvironment*> runtime,
const SerializedRequest &request,
const Error &error,
bool terminal,
crl::time now) {
if (!request || !request->fileTransferTag) {
return;
}
const auto trace = request->fileTransferTag->trace;
auto event = ProxyDiagnosticsEvent();
{
QMutexLocker lock(&trace->mutex);
if (terminal) {
trace->terminalAt = now;
trace->terminalEventEmitted = true;
}
event = FileDiagnosticsEvent(
*trace,
ProxyDiagnosticsTransition::Error);
event.sendCount = trace->successfulSendCount;
event.errorClass = FileDiagnosticsErrorClass(error);
event.errorCode = error.code();
event.isFinal = terminal;
}
WriteProxyDiagnosticsLine(runtime, std::move(event));
}
void ReportFileTransferCancelled(
not_null<RuntimeEnvironment*> runtime,
const SerializedRequest &request,
crl::time now) {
if (!request || !request->fileTransferTag) {
return;
}
const auto trace = request->fileTransferTag->trace;
auto event = ProxyDiagnosticsEvent();
{
QMutexLocker lock(&trace->mutex);
if (trace->terminalEventEmitted) {
return;
}
trace->terminalAt = now;
trace->terminalEventEmitted = true;
event = FileDiagnosticsEvent(
*trace,
ProxyDiagnosticsTransition::Cancelled);
event.sendCount = trace->successfulSendCount;
event.totalMs = FileDiagnosticsDuration(*trace, now);
event.isFinal = true;
}
WriteProxyDiagnosticsLine(runtime, std::move(event));
}
std::atomic<int> GlobalAtomicRequestId = 0;
} // namespace
namespace details {
int GetNextRequestId() {
const auto result = ++GlobalAtomicRequestId;
if (result == std::numeric_limits<int>::max() / 2) {
GlobalAtomicRequestId = 0;
}
return result;
}
} // namespace details
class Instance::Private : private Sender, public details::SessionDelegate {
public:
Private(
not_null<Instance*> instance,
Instance::Mode mode,
Fields &&fields);
void start();
[[nodiscard]] Config &config() const;
[[nodiscard]] const ConfigFields &configValues() const;
[[nodiscard]] DcOptions &dcOptions() const;
[[nodiscard]] Environment environment() const;
[[nodiscard]] bool isTestMode() const;
void resolveProxyDomain(const QString &host);
void setGoodProxyDomain(const QString &host, const QString &ip);
void suggestMainDcId(DcId mainDcId);
void setMainDcId(DcId mainDcId);
[[nodiscard]] bool hasMainDcId() const;
[[nodiscard]] DcId mainDcId() const;
[[nodiscard]] rpl::producer<DcId> mainDcIdValue() const;
[[nodiscard]] QString systemLangCode() const;
[[nodiscard]] QString cloudLangCode() const;
[[nodiscard]] QString langPackName() const;
[[nodiscard]] rpl::producer<> writeKeysRequests() const;
void dcPersistentKeyChanged(DcId dcId, const AuthKeyPtr &persistentKey);
void dcTemporaryKeyChanged(DcId dcId);
[[nodiscard]] rpl::producer<DcId> dcTemporaryKeyChanged() const;
[[nodiscard]] AuthKeysList getKeysForWrite() const;
void addKeysForDestroy(AuthKeysList &&keys);
[[nodiscard]] rpl::producer<> allKeysDestroyed() const;
// Thread safe.
[[nodiscard]] RuntimeEnvironment &runtimeEnvironment() const;
[[nodiscard]] QString deviceModel() const;
[[nodiscard]] QString systemVersion() const;
// Main thread.
void requestConfig();
void requestConfigIfOld();
void requestCDNConfig();
void setUserPhone(const QString &phone);
void badConfigurationError();
void syncHttpUnixtime();
void restartedByTimeout(ShiftedDcId shiftedDcId);
[[nodiscard]] rpl::producer<ShiftedDcId> restartsByTimeout() const;
[[nodiscard]] auto nonPremiumDelayedRequests() const
-> rpl::producer<mtpRequestId>;
[[nodiscard]] rpl::producer<> frozenErrorReceived() const;
void restart();
void restart(ShiftedDcId shiftedDcId);
void migrateProxy(bool manual);
[[nodiscard]] int32 dcstate(ShiftedDcId shiftedDcId = 0);
[[nodiscard]] QString dctransport(ShiftedDcId shiftedDcId = 0);
[[nodiscard]] ConnectionStatus &connectionStatus() const;
void ping();
void cancel(mtpRequestId requestId);
[[nodiscard]] int32 state(mtpRequestId requestId); // < 0 means waiting for such count of ms
void killSession(ShiftedDcId shiftedDcId);
void stopSession(ShiftedDcId shiftedDcId);
void reInitConnection(DcId dcId);
void logout(Fn<void()> done);
not_null<Dcenter*> getDcById(ShiftedDcId shiftedDcId);
Dcenter *findDc(ShiftedDcId shiftedDcId);
not_null<Dcenter*> addDc(
ShiftedDcId shiftedDcId,
AuthKeyPtr &&key = nullptr);
void removeDc(ShiftedDcId shiftedDcId);
void sendRequest(
mtpRequestId requestId,
SerializedRequest &&request,
ResponseHandler &&callbacks,
ShiftedDcId shiftedDcId,
crl::time msCanWait,
bool needsLayer,
mtpRequestId afterRequestId);
[[nodiscard]] bool hasCallback(mtpRequestId requestId) const;
void processCallback(const Response &response);
void processUpdate(const Response &message);
void onStateChange(ShiftedDcId shiftedDcId, int32 state);
void onSessionReset(ShiftedDcId shiftedDcId);
// return true if need to clean request data
bool rpcErrorOccured(
const Response &response,
const FailHandler &onFail,
const Error &error);
inline bool rpcErrorOccured(
const Response &response,
const ResponseHandler &handler,
const Error &error) {
return rpcErrorOccured(response, handler.fail, error);
}
void setUpdatesHandler(Fn<void(const Response&)> handler);
void setGlobalFailHandler(
Fn<void(const Error&, const Response&)> handler);
void setStateChangedHandler(Fn<void(ShiftedDcId shiftedDcId, int32 state)> handler);
void setSessionResetHandler(Fn<void(ShiftedDcId shiftedDcId)> handler);
void clearGlobalHandlers();
[[nodiscard]] not_null<Session*> getSession(ShiftedDcId shiftedDcId);
bool isNormal() const {
return (_mode == Instance::Mode::Normal);
}
bool isKeysDestroyer() const {
return (_mode == Instance::Mode::KeysDestroyer);
}
void scheduleKeyDestroy(ShiftedDcId shiftedDcId);
void keyWasPossiblyDestroyed(ShiftedDcId shiftedDcId);
void performKeyDestroy(ShiftedDcId shiftedDcId);
void completedKeyDestroy(ShiftedDcId shiftedDcId);
void keyDestroyedOnServer(ShiftedDcId shiftedDcId, uint64 keyId);
void prepareToDestroy();
[[nodiscard]] rpl::lifetime &lifetime();
private:
void importDone(
const MTPauth_Authorization &result,
const Response &response);
bool importFail(const Error &error, const Response &response);
void exportDone(
const MTPauth_ExportedAuthorization &result,
const Response &response);
bool exportFail(const Error &error, const Response &response);
bool handleMigrationError(
mtpRequestId requestId,
DcId migrateDcId);
bool handleMsgWaitError(mtpRequestId requestId);
bool handleRetryError(
mtpRequestId requestId,
const details::DefaultRpcErrorRetry &retry);
bool handleUnauthorizedError(
const Error &error,
const Response &response,
bool badGuestDc);
bool handleConnectionInitError(mtpRequestId requestId);
bool onErrorDefault(const Error &error, const Response &response);
void unpaused();
Session *findSession(ShiftedDcId shiftedDcId);
not_null<Session*> startSession(
ShiftedDcId shiftedDcId,
SessionRole role);
void scheduleSessionDestroy(ShiftedDcId shiftedDcId);
[[nodiscard]] not_null<QThread*> getThreadForDc(ShiftedDcId shiftedDcId);
void applyDomainIps(
const QString &host,
const QStringList &ips,
crl::time expireAt);
void logoutGuestDcs();
bool logoutGuestDone(mtpRequestId requestId);
void requestConfigIfExpired();
void configLoadDone(const MTPConfig &result);
bool configLoadFail(const Error &error);
void checkDelayedRequests();
void resendDependentRequests(
std::vector<RequestRegistry::DependentRequest> &&requests);
const not_null<Instance*> _instance;
const Instance::Mode _mode = Instance::Mode::Normal;
const std::unique_ptr<Config> _config;
const std::shared_ptr<RuntimeEnvironment> _runtime;
const std::unique_ptr<ConnectionStatus> _connectionStatus;
const std::shared_ptr<base::NetworkReachability> _networkReachability;
std::unique_ptr<QThread> _mainSessionThread;
std::unique_ptr<QThread> _otherSessionsThread;
std::vector<std::unique_ptr<QThread>> _fileSessionThreads;
QString _deviceModelDefault;
QString _systemVersion;
mutable QMutex _deviceModelMutex;
QString _customDeviceModel;
rpl::variable<DcId> _mainDcId = Fields::kDefaultMainDc;
bool _mainDcIdForced = false;
base::flat_map<DcId, std::unique_ptr<Dcenter>> _dcenters;
std::vector<std::unique_ptr<Dcenter>> _dcentersToDestroy;
rpl::event_stream<DcId> _dcTemporaryKeyChanged;
Session *_mainSession = nullptr;
base::flat_map<ShiftedDcId, std::unique_ptr<Session>> _sessions;
std::vector<std::unique_ptr<Session>> _sessionsToDestroy;
rpl::event_stream<ShiftedDcId> _restartsByTimeout;
std::unique_ptr<ConfigLoader> _configLoader;
std::unique_ptr<DomainResolver> _domainResolver;
std::unique_ptr<SpecialConfigRequest> _httpUnixtimeLoader;
QString _userPhone;
mtpRequestId _cdnConfigLoadRequestId = 0;
crl::time _lastConfigLoadedTime = 0;
crl::time _configExpiresAt = 0;
base::flat_map<DcId, AuthKeyPtr> _keysForWrite;
base::flat_map<ShiftedDcId, mtpRequestId> _logoutGuestRequestIds;
rpl::event_stream<> _writeKeysRequests;
rpl::event_stream<> _allKeysDestroyed;
RequestRegistry _requests;
std::map<mtpRequestId, ShiftedDcId> _authExportRequests;
std::set<mtpRequestId> _badGuestDcRequests;
std::map<DcId, std::vector<mtpRequestId>> _authWaiters;
Fn<void(const Response&)> _updatesHandler;
Fn<void(const Error&, const Response&)> _globalFailHandler;
Fn<void(ShiftedDcId shiftedDcId, int32 state)> _stateChangedHandler;
Fn<void(ShiftedDcId shiftedDcId)> _sessionResetHandler;
rpl::event_stream<mtpRequestId> _nonPremiumDelayedRequests;
rpl::event_stream<> _frozenErrorReceived;
base::Timer _checkDelayedTimer;
uint64 _proxyGeneration = 1;
rpl::lifetime _lifetime;
};
Instance::Fields::Fields() = default;
Instance::Fields::Fields(Fields &&other) = default;
auto Instance::Fields::operator=(Fields &&other) -> Fields & = default;
Instance::Fields::~Fields() = default;
Instance::Private::Private(
not_null<Instance*> instance,
Instance::Mode mode,
Fields &&fields)
: Sender(instance)
, _instance(instance)
, _mode(mode)
, _config(std::move(fields.config))
, _runtime(fields.runtimeEnvironment
? std::move(fields.runtimeEnvironment)
: CreateRuntimeEnvironment())
, _connectionStatus(std::make_unique<ConnectionStatus>(
not_null{ _runtime.get() }))
, _networkReachability(base::NetworkReachability::Instance())
{
Expects(_config != nullptr);
Expects(_runtime != nullptr);
_runtime->bindInstance({
.connectionStatus = _connectionStatus.get(),
.mainDcId = [=] {
return hasMainDcId() ? mainDcId() : DcId();
},
.dcOptionsLookup = [=](
DcId dcId,
DcType type,
bool throughProxy) {
return dcOptions().lookup(dcId, type, throughProxy);
},
.proxyResolver = {
.resolveDomain = [=](QString host) {
resolveProxyDomain(std::move(host));
},
.setGoodDomain = [=](QString host, QString ip) {
setGoodProxyDomain(std::move(host), std::move(ip));
},
.domainResolved = [=](
QString host,
QStringList ips,
qint64 expireAt) {
_runtime->proxyServices().dnsResolver().resolved(
host,
ips,
expireAt);
_instance->proxyDomainResolved(
std::move(host),
std::move(ips),
expireAt);
},
},
.syncHttpUnixtime = [=] {
syncHttpUnixtime();
},
});
const auto idealThreadPoolSize = QThread::idealThreadCount();
_fileSessionThreads.resize(2 * std::max(idealThreadPoolSize / 2, 1));
details::unpaused(
) | rpl::on_next([=] {
unpaused();
}, _lifetime);
_networkReachability->availableChanges(
) | rpl::on_next([=](bool available) {
restart();
}, _lifetime);
_deviceModelDefault = std::move(fields.deviceModel);
_systemVersion = std::move(fields.systemVersion);
_customDeviceModel = _runtime->device().model
? _runtime->device().model()
: QString();
if (_runtime->device().watchModelChanges) {
_runtime->device().watchModelChanges([=](QString value) {
QMutexLocker lock(&_deviceModelMutex);
_customDeviceModel = value;
lock.unlock();
if (hasMainDcId()) {
reInitConnection(mainDcId());
}
}, _lifetime);
}
for (auto &key : fields.keys) {
auto dcId = key->dcId();
auto shiftedDcId = dcId;
if (isKeysDestroyer()) {
shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
// There could be several keys for one dc if we're destroying them.
// Place them all in separate shiftedDcId so that they won't conflict.
while (_keysForWrite.find(shiftedDcId) != _keysForWrite.cend()) {
shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
}
}
_keysForWrite[shiftedDcId] = key;
addDc(shiftedDcId, std::move(key));
}
if (fields.mainDcId != Fields::kNotSetMainDc) {
_mainDcId = fields.mainDcId;
_mainDcIdForced = true;
}
if (_runtime->proxy().watchConnectionTypeChanges) {
_runtime->proxy().watchConnectionTypeChanges([=] {
if (_configLoader) {
_configLoader->setProxyEnabled(
_runtime->proxy().enabled
? _runtime->proxy().enabled()
: false);
}
if (!_runtime->proxy().enabled || !_runtime->proxy().enabled()) {
_connectionStatus->resetProxyStatus();
}
_connectionStatus->resetNotices();
}, _lifetime);
}
}
void Instance::Private::start() {
if (isKeysDestroyer()) {
for (const auto &[shiftedDcId, dc] : _dcenters) {
startSession(shiftedDcId, SessionRole::Maintenance);
}
} else if (hasMainDcId()) {
_mainSession = startSession(
mainDcId(),
SessionRole::PrimaryMain);
}
_checkDelayedTimer.setCallback([this] { checkDelayedRequests(); });
Assert(!hasMainDcId() == isKeysDestroyer());
requestConfig();
}
void Instance::Private::resolveProxyDomain(const QString &host) {
if (!_domainResolver) {
_domainResolver = std::make_unique<DomainResolver>([=](
const QString &host,
const QStringList &ips,
crl::time expireAt) {
applyDomainIps(host, ips, expireAt);
});
}
_domainResolver->resolve(host);
}
void Instance::Private::applyDomainIps(
const QString &host,
const QStringList &ips,
crl::time expireAt) {
if (_runtime->proxy().applyDomainIps
&& _runtime->proxy().applyDomainIps(host, ips, expireAt)) {
for (const auto &[shiftedDcId, session] : _sessions) {
session->refreshOptions();
}
}
if (_runtime->proxyResolver().domainResolved) {
_runtime->proxyResolver().domainResolved(host, ips, expireAt);
}
}
void Instance::Private::setGoodProxyDomain(
const QString &host,
const QString &ip) {
if (_runtime->proxy().promoteDomainIp
&& _runtime->proxy().promoteDomainIp(host, ip)
&& _runtime->app().refreshGlobalProxy) {
_runtime->app().refreshGlobalProxy();
}
}
void Instance::Private::suggestMainDcId(DcId mainDcId) {
if (!_mainDcIdForced) {
setMainDcId(mainDcId);
}
}
void Instance::Private::setMainDcId(DcId mainDcId) {
if (!_mainSession) {
LOG(("MTP Error: attempting to change mainDcId in an MTP instance without main session."));
return;
}
_mainDcIdForced = true;
const auto oldMainDcId = _mainSession->getDcWithShift();
if (oldMainDcId != mainDcId) {
scheduleSessionDestroy(oldMainDcId);
scheduleSessionDestroy(mainDcId);
_mainSession = startSession(
mainDcId,
SessionRole::PrimaryMain);
_connectionStatus->resetPingTime();
}
_mainDcId = mainDcId;
_writeKeysRequests.fire({});
}
bool Instance::Private::hasMainDcId() const {
return (_mainDcId.current() != Fields::kNoneMainDc);
}
DcId Instance::Private::mainDcId() const {
Expects(hasMainDcId());
return _mainDcId.current();
}
rpl::producer<DcId> Instance::Private::mainDcIdValue() const {
Expects(_mainDcId.current() != Fields::kNoneMainDc);
return _mainDcId.value();
}
QString Instance::Private::systemLangCode() const {
return _runtime->language().systemCode
? _runtime->language().systemCode()
: QString();
}
QString Instance::Private::cloudLangCode() const {
return _runtime->language().cloudCode
? _runtime->language().cloudCode()
: QString();
}
QString Instance::Private::langPackName() const {
return _runtime->language().packName
? _runtime->language().packName()
: QString();
}
void Instance::Private::requestConfig() {
if (_configLoader || isKeysDestroyer()) {
return;
}
_configLoader = std::make_unique<ConfigLoader>(
_instance,
_userPhone,
[=](const MTPConfig &result) { configLoadDone(result); },
[=](const Error &error, const Response &) {
return configLoadFail(error);
},
_runtime->proxy().enabled ? _runtime->proxy().enabled() : false);
_configLoader->load();
}
void Instance::Private::setUserPhone(const QString &phone) {
if (_userPhone != phone) {
_userPhone = phone;
if (_configLoader) {
_configLoader->setPhone(_userPhone);
}
}
}
void Instance::Private::badConfigurationError() {
if (_mode == Mode::Normal && _runtime->app().badMtprotoConfigurationError) {
_runtime->app().badMtprotoConfigurationError();
}
}
void Instance::Private::syncHttpUnixtime() {
if (base::unixtime::http_valid() || _httpUnixtimeLoader) {
return;
}
_httpUnixtimeLoader = std::make_unique<SpecialConfigRequest>([=] {
InvokeQueued(_instance, [=] {
_httpUnixtimeLoader = nullptr;
});
}, isTestMode(), configValues().txtDomainString);
}
void Instance::Private::restartedByTimeout(ShiftedDcId shiftedDcId) {
_restartsByTimeout.fire_copy(shiftedDcId);
}
rpl::producer<ShiftedDcId> Instance::Private::restartsByTimeout() const {
return _restartsByTimeout.events();
}
auto Instance::Private::nonPremiumDelayedRequests() const
-> rpl::producer<mtpRequestId> {
return _nonPremiumDelayedRequests.events();
}
rpl::producer<> Instance::Private::frozenErrorReceived() const {
return _frozenErrorReceived.events();
}
void Instance::Private::requestConfigIfOld() {
const auto timeout = _config->values().blockedMode
? kConfigBecomesOldForBlockedIn
: kConfigBecomesOldIn;
if (crl::now() - _lastConfigLoadedTime >= timeout) {
requestConfig();
}
}
void Instance::Private::requestConfigIfExpired() {
const auto requestIn = (_configExpiresAt - crl::now());
if (requestIn > 0) {
base::call_delayed(
std::min(requestIn, 3600 * crl::time(1000)),
_instance,
[=] { requestConfigIfExpired(); });
} else {
requestConfig();
}
}
void Instance::Private::requestCDNConfig() {
if (_cdnConfigLoadRequestId || !hasMainDcId()) {
return;
}
_cdnConfigLoadRequestId = request(
MTPhelp_GetCdnConfig()
).done([this](const MTPCdnConfig &result) {
_cdnConfigLoadRequestId = 0;
result.match([&](const MTPDcdnConfig &data) {
dcOptions().setCDNConfig(data);
});
if (_runtime->storage().writeSettings) {
_runtime->storage().writeSettings();
}
}).send();
}
void Instance::Private::restart() {
for (const auto &[shiftedDcId, session] : _sessions) {
session->restart();
}
}
void Instance::Private::migrateProxy(bool manual) {
if (isKeysDestroyer()) {
return restart();
}
++_proxyGeneration;
const auto selected = (_runtime->proxy().enabled
&& _runtime->proxy().enabled()
&& _runtime->proxy().selected)
? _runtime->proxy().selected()
: ProxyData();
_connectionStatus->setProxyStatus({
.attempt = { .proxyGeneration = _proxyGeneration },
.proxy = selected,
});
for (const auto &[shiftedDcId, session] : _sessions) {
session->migrateProxy(_proxyGeneration);
}
}
void Instance::Private::restart(ShiftedDcId shiftedDcId) {
const auto dcId = BareDcId(shiftedDcId);
for (const auto &[shiftedDcId, session] : _sessions) {
if (BareDcId(shiftedDcId) == dcId) {
session->restart();
}
}
}
int32 Instance::Private::dcstate(ShiftedDcId shiftedDcId) {
if (!shiftedDcId) {
Assert(_mainSession != nullptr);
return _mainSession->getState();
}
if (!BareDcId(shiftedDcId)) {
Assert(_mainSession != nullptr);
shiftedDcId += BareDcId(_mainSession->getDcWithShift());
}
if (const auto session = findSession(shiftedDcId)) {
return session->getState();
}
return DisconnectedState;
}
QString Instance::Private::dctransport(ShiftedDcId shiftedDcId) {
if (!shiftedDcId) {
Assert(_mainSession != nullptr);
return _mainSession->transport();
}
if (!BareDcId(shiftedDcId)) {
Assert(_mainSession != nullptr);
shiftedDcId += BareDcId(_mainSession->getDcWithShift());
}
if (const auto session = findSession(shiftedDcId)) {
return session->transport();
}
return QString();
}
ConnectionStatus &Instance::Private::connectionStatus() const {
return *_connectionStatus;
}
void Instance::Private::ping() {
getSession(0)->ping();
}
void Instance::Private::cancel(mtpRequestId requestId) {
if (!requestId) return;
DEBUG_LOG(("MTP Info: Cancel request %1.").arg(requestId));
auto cancelled = _requests.cancel(requestId);
ReportFileTransferCancelled(
not_null{ _runtime.get() },
cancelled.request,
crl::now());
resendDependentRequests(std::move(cancelled.dependentRequests));
if (cancelled.dcWithShift) {
const auto session = getSession(std::abs(*cancelled.dcWithShift));
session->cancel(requestId, cancelled.msgId);
}
}
// result < 0 means waiting for such count of ms.
int32 Instance::Private::state(mtpRequestId requestId) {
if (requestId > 0) {
if (const auto shiftedDcId = _requests.queryDc(requestId)) {
const auto session = getSession(std::abs(*shiftedDcId));
return session->requestState(requestId);
}
return MTP::RequestSent;
}
const auto session = getSession(-requestId);
return session->requestState(0);
}
void Instance::Private::killSession(ShiftedDcId shiftedDcId) {
const auto i = _sessions.find(shiftedDcId);
if (i != _sessions.cend()) {
Assert(i->second.get() != _mainSession);
}
scheduleSessionDestroy(shiftedDcId);
}
void Instance::Private::stopSession(ShiftedDcId shiftedDcId) {
if (const auto session = findSession(shiftedDcId)) {
if (session != _mainSession) { // don't stop main session
session->stop();
}
}
}
void Instance::Private::reInitConnection(DcId dcId) {
for (const auto &[shiftedDcId, session] : _sessions) {
if (BareDcId(shiftedDcId) == dcId) {
session->reInitConnection();
}
}
}
void Instance::Private::logout(Fn<void()> done) {
_instance->send(MTPauth_LogOut(), [=](Response) {
done();
return true;
}, [=](const Error&, Response) {
done();
return true;
});
logoutGuestDcs();
}
void Instance::Private::logoutGuestDcs() {
Expects(!isKeysDestroyer());
auto dcIds = std::vector<DcId>();
dcIds.reserve(_keysForWrite.size());
for (const auto &key : _keysForWrite) {
dcIds.push_back(key.first);
}
for (const auto dcId : dcIds) {
if (dcId == mainDcId() || dcOptions().dcType(dcId) == DcType::Cdn) {
continue;
}
const auto shiftedDcId = MTP::logoutDcId(dcId);
const auto requestId = _instance->send(MTPauth_LogOut(), [=](
const Response &response) {
logoutGuestDone(response.requestId);
return true;
}, [=](const Error &, const Response &response) {
logoutGuestDone(response.requestId);
return true;
}, shiftedDcId);
_logoutGuestRequestIds.emplace(shiftedDcId, requestId);
}
}
bool Instance::Private::logoutGuestDone(mtpRequestId requestId) {
for (auto i = _logoutGuestRequestIds.begin(), e = _logoutGuestRequestIds.end(); i != e; ++i) {
if (i->second == requestId) {
killSession(i->first);
_logoutGuestRequestIds.erase(i);
return true;
}
}
return false;
}
Dcenter *Instance::Private::findDc(ShiftedDcId shiftedDcId) {
const auto i = _dcenters.find(shiftedDcId);
return (i != _dcenters.end()) ? i->second.get() : nullptr;
}
not_null<Dcenter*> Instance::Private::addDc(
ShiftedDcId shiftedDcId,
AuthKeyPtr &&key) {
const auto dcId = BareDcId(shiftedDcId);
return _dcenters.emplace(
shiftedDcId,
std::make_unique<Dcenter>(dcId, std::move(key))
).first->second.get();
}
void Instance::Private::removeDc(ShiftedDcId shiftedDcId) {
const auto i = _dcenters.find(shiftedDcId);
if (i != _dcenters.end()) {
_dcentersToDestroy.push_back(std::move(i->second));
_dcenters.erase(i);
}
}
not_null<Dcenter*> Instance::Private::getDcById(
ShiftedDcId shiftedDcId) {
if (const auto result = findDc(shiftedDcId)) {
return result;
}
const auto dcId = [&] {
const auto result = BareDcId(shiftedDcId);
if (isTemporaryDcId(result)) {
if (const auto realDcId = getRealIdFromTemporaryDcId(result)) {
return realDcId;
}
}
return result;
}();
if (dcId != shiftedDcId) {
if (const auto result = findDc(dcId)) {
return result;
}
}
return addDc(dcId);
}
void Instance::Private::dcPersistentKeyChanged(
DcId dcId,
const AuthKeyPtr &persistentKey) {
dcTemporaryKeyChanged(dcId);
if (isTemporaryDcId(dcId)) {
return;
}
const auto i = _keysForWrite.find(dcId);
if (i != _keysForWrite.end() && i->second == persistentKey) {
return;
} else if (i == _keysForWrite.end() && !persistentKey) {
return;
}
if (!persistentKey) {
_keysForWrite.erase(i);
} else if (i != _keysForWrite.end()) {
i->second = persistentKey;
} else {
_keysForWrite.emplace(dcId, persistentKey);
}
DEBUG_LOG(("AuthKey Info: writing auth keys, called by dc %1"
).arg(dcId));
_writeKeysRequests.fire({});
}
void Instance::Private::dcTemporaryKeyChanged(DcId dcId) {
_dcTemporaryKeyChanged.fire_copy(dcId);
}
rpl::producer<DcId> Instance::Private::dcTemporaryKeyChanged() const {
return _dcTemporaryKeyChanged.events();
}
AuthKeysList Instance::Private::getKeysForWrite() const {
auto result = AuthKeysList();
result.reserve(_keysForWrite.size());
for (const auto &key : _keysForWrite) {
result.push_back(key.second);
}
return result;
}
void Instance::Private::addKeysForDestroy(AuthKeysList &&keys) {
Expects(isKeysDestroyer());
for (auto &key : keys) {
const auto dcId = key->dcId();
auto shiftedDcId = MTP::destroyKeyNextDcId(dcId);
// There could be several keys for one dc if we're destroying them.
// Place them all in separate shiftedDcId so that they won't conflict.
while (_keysForWrite.find(shiftedDcId) != _keysForWrite.cend()) {
shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
}
_keysForWrite[shiftedDcId] = key;
addDc(shiftedDcId, std::move(key));
startSession(shiftedDcId, SessionRole::Maintenance);
}
}
rpl::producer<> Instance::Private::allKeysDestroyed() const {
return _allKeysDestroyed.events();
}
rpl::producer<> Instance::Private::writeKeysRequests() const {
return _writeKeysRequests.events();
}
Config &Instance::Private::config() const {
return *_config;
}
const ConfigFields &Instance::Private::configValues() const {
return _config->values();
}
DcOptions &Instance::Private::dcOptions() const {
return _config->dcOptions();
}
Environment Instance::Private::environment() const {
return _config->environment();
}
bool Instance::Private::isTestMode() const {
return _config->isTestMode();
}
RuntimeEnvironment &Instance::Private::runtimeEnvironment() const {
return *_runtime;
}
QString Instance::Private::deviceModel() const {
QMutexLocker lock(&_deviceModelMutex);
return _customDeviceModel.isEmpty()
? _deviceModelDefault
: _customDeviceModel;
}
QString Instance::Private::systemVersion() const {
return _systemVersion;
}
void Instance::Private::unpaused() {
for (const auto &[shiftedDcId, session] : _sessions) {
session->unpaused();
}
}
void Instance::Private::configLoadDone(const MTPConfig &result) {
Expects(result.type() == mtpc_config);
_configLoader.reset();
_lastConfigLoadedTime = crl::now();
const auto &data = result.c_config();
_config->apply(data);
const auto lang = qs(data.vsuggested_lang_code().value_or_empty());
if (_runtime->language().setSuggested) {
_runtime->language().setSuggested(lang);
}
if (_runtime->language().setCurrentVersions) {
_runtime->language().setCurrentVersions(
data.vlang_pack_version().value_or_empty(),
data.vbase_lang_pack_version().value_or_empty());
}
if (const auto prefix = data.vautoupdate_url_prefix()) {
if (_runtime->storage().writeAutoupdatePrefix) {
_runtime->storage().writeAutoupdatePrefix(qs(*prefix));
}
}
_configExpiresAt = crl::now()
+ (data.vexpires().v - base::unixtime::now()) * crl::time(1000);
requestConfigIfExpired();
}
bool Instance::Private::configLoadFail(const Error &error) {
if (IsDefaultHandledError(error)) return false;
// loadingConfig = false;
LOG(("MTP Error: failed to get config!"));
return false;
}
void Instance::Private::checkDelayedRequests() {
const auto now = crl::now();
for (const auto &delayed : _requests.takeReadyDelayed(now)) {
if (!delayed.dcWithShift) {
LOG(("MTP Error: could not find request dc for delayed resend, requestId %1").arg(delayed.requestId));
continue;
}
if (!delayed.request) {
DEBUG_LOG(("MTP Error: could not find request %1").arg(delayed.requestId));
continue;
}
const auto session = getSession(std::abs(*delayed.dcWithShift));
session->sendPrepared(delayed.request);
}
if (const auto next = _requests.nextDelayedAt()) {
_checkDelayedTimer.callOnce(*next - now);
}
}
void Instance::Private::sendRequest(
mtpRequestId requestId,
SerializedRequest &&request,
ResponseHandler &&callbacks,
ShiftedDcId shiftedDcId,
crl::time msCanWait,
bool needsLayer,
mtpRequestId afterRequestId) {
const auto session = getSession(shiftedDcId);
request->requestId = requestId;
_requests.storeRequest(requestId, request, std::move(callbacks));
const auto toMainDc = (shiftedDcId == 0);
const auto realShiftedDcId = session->getDcWithShift();
const auto signedDcId = toMainDc ? -realShiftedDcId : realShiftedDcId;
_requests.registerRequest(requestId, signedDcId);
ReportFileTransferQueued(
not_null{ _runtime.get() },
request,
crl::now());
request->lastSentTime = crl::now();
request->needsLayer = needsLayer;
if (afterRequestId
&& _requests.prepareDependency(
requestId,
request,
afterRequestId) == RequestRegistry::DependencyAction::Wait) {
return;
}
session->sendPrepared(request, msCanWait);
}
void Instance::Private::resendDependentRequests(
std::vector<RequestRegistry::DependentRequest> &&requests) {
for (const auto &resending : requests) {
if (!resending.request) {
LOG(("MTP Error: could not find dependent request %1").arg(resending.requestId));
return;
}
getSession(std::abs(resending.dcWithShift))->sendPrepared(resending.request);
}
}
bool Instance::Private::hasCallback(mtpRequestId requestId) const {
return _requests.hasCallback(requestId);
}
void Instance::Private::processCallback(const Response &response) {
const auto requestId = response.requestId;
auto handler = _requests.takeCallback(requestId);
const auto unregister = [&] {
DEBUG_LOG(("MTP Info: unregistering request %1.").arg(requestId));
return _requests.unregisterRequest(requestId);
};
if (handler.done || handler.fail) {
DEBUG_LOG(("RPC Info: found parser for request %1, trying to parse response...").arg(requestId));
const auto handleError = [&](const Error &error) {
const auto request = _requests.request(requestId);
DEBUG_LOG(("RPC Info: "
"error received, code %1, type %2, description: %3").arg(
QString::number(error.code()),
error.type(),
error.description()));
const auto guard = QPointer<Instance>(_instance);
if (rpcErrorOccured(response, handler, error) && guard) {
auto unregistered = unregister();
const auto terminal = bool(unregistered.request);
ReportFileTransferError(
not_null{ _runtime.get() },
terminal ? unregistered.request : request,
error,
terminal,
crl::now());
resendDependentRequests(
std::move(unregistered.dependentRequests));
} else if (guard) {
_requests.restoreCallback(requestId, std::move(handler));
ReportFileTransferError(
not_null{ _runtime.get() },
request,
error,
false,
crl::now());
}
};
auto from = response.reply.constData();
if (response.reply.isEmpty()) {
handleError(Error::Local(
"RESPONSE_PARSE_FAILED",
"Empty response."));
} else if (*from == mtpc_rpc_error) {
auto error = MTPRpcError();
handleError(
Error(error.read(from, from + response.reply.size())
? error
: Error::MTPLocal(
"RESPONSE_PARSE_FAILED",
"Error parse failed.")));
} else {
const auto guard = QPointer<Instance>(_instance);
if (handler.done && !handler.done(response) && guard) {
const auto request = _requests.request(requestId);
const auto body = request
? request.bodyPrimes()
: gsl::span<const mtpPrime>();
const auto methodId = body.empty() ? uint32() : uint32(body[0]);
const auto responseId = uint32(response.reply[0]);
const auto shiftedDcId = _requests.queryDc(requestId).value_or(0);
LOG(("RPC Parse Error: request %1 method 0x%2 response 0x%3 "
"words %4 dc %5").arg(
QString::number(requestId),
QString::number(methodId, 16).rightJustified(8, '0'),
QString::number(responseId, 16).rightJustified(8, '0'),
QString::number(response.reply.size()),
QString::number(shiftedDcId)));
handleError(Error::Local(
"RESPONSE_PARSE_FAILED",
"Response parse failed."));
}
if (guard) {
auto unregistered = unregister();
ReportFileTransferResult(
not_null{ _runtime.get() },
unregistered.request,
crl::now());
resendDependentRequests(
std::move(unregistered.dependentRequests));
}
}
} else {
DEBUG_LOG(("RPC Info: parser not found for %1").arg(requestId));
auto unregistered = unregister();
ReportFileTransferResult(
not_null{ _runtime.get() },
unregistered.request,
crl::now());
resendDependentRequests(
std::move(unregistered.dependentRequests));
}
}
void Instance::Private::processUpdate(const Response &message) {
if (_updatesHandler) {
_updatesHandler(message);
}
}
void Instance::Private::onStateChange(ShiftedDcId dcWithShift, int32 state) {
if (_stateChangedHandler) {
_stateChangedHandler(dcWithShift, state);
}
}
void Instance::Private::onSessionReset(ShiftedDcId dcWithShift) {
if (_sessionResetHandler) {
_sessionResetHandler(dcWithShift);
}
}
bool Instance::Private::rpcErrorOccured(
const Response &response,
const FailHandler &onFail,
const Error &error) { // return true if need to clean request data
if (IsDefaultHandledError(error)) {
const auto guard = QPointer<Instance>(_instance);
if (onFail && onFail(error, response)) {
return true;
} else if (!guard) {
return false;
}
}
if (onErrorDefault(error, response)) {
return false;
}
LOG(("RPC Error: request %1 got fail with code %2, error %3%4").arg(
QString::number(response.requestId),
QString::number(error.code()),
error.type(),
error.description().isEmpty()
? QString()
: QString(": %1").arg(error.description())));
if (onFail) {
const auto guard = QPointer<Instance>(_instance);
onFail(error, response);
if (!guard) {
return false;
}
}
return true;
}
void Instance::Private::importDone(
const MTPauth_Authorization &result,
const Response &response) {
const auto shiftedDcId = _requests.queryDc(response.requestId);
if (!shiftedDcId) {
LOG(("MTP Error: "
"auth import request not found in requestsByDC, requestId: %1"
).arg(response.requestId));
//
// Don't log out on export/import problems, perhaps this is a server side error.
//
//const auto error = Error::Local(
// "AUTH_IMPORT_FAIL",
// QString("did not find import request in requestsByDC, "
// "request %1").arg(requestId));
//if (_globalFailHandler && hasAuthorization()) {
// _globalFailHandler(error, response); // auth failed in main dc
//}
return;
}
auto newdc = BareDcId(*shiftedDcId);
DEBUG_LOG(("MTP Info: auth import to dc %1 succeeded").arg(newdc));
auto &waiters = _authWaiters[newdc];
if (waiters.size()) {
for (auto waitedRequestId : waiters) {
const auto request = _requests.request(waitedRequestId);
if (!request) {
LOG(("MTP Error: could not find request %1 for resending").arg(waitedRequestId));
continue;
}
const auto shiftedDcId = _requests.changeDc(waitedRequestId, newdc);
if (!shiftedDcId) {
LOG(("MTP Error: could not find request %1 by dc for resending").arg(waitedRequestId));
continue;
} else if (*shiftedDcId < 0) {
_instance->setMainDcId(newdc);
}
DEBUG_LOG(("MTP Info: resending request %1 to dc %2 after import auth").arg(waitedRequestId).arg(*shiftedDcId));
const auto session = getSession(*shiftedDcId);
session->sendPrepared(request);
}
waiters.clear();
}
}
bool Instance::Private::importFail(
const Error &error,
const Response &response) {
if (IsDefaultHandledError(error)) {
return false;
}
//
// Don't log out on export/import problems, perhaps this is a server side error.
//
//if (_globalFailHandler && hasAuthorization()) {
// _globalFailHandler(error, response); // auth import failed
//}
return true;
}
void Instance::Private::exportDone(
const MTPauth_ExportedAuthorization &result,
const Response &response) {
auto it = _authExportRequests.find(response.requestId);
if (it == _authExportRequests.cend()) {
LOG(("MTP Error: "
"auth export request target dcWithShift not found, requestId: %1"
).arg(response.requestId));
//
// Don't log out on export/import problems, perhaps this is a server side error.
//
//const auto error = Error::Local(
// "AUTH_IMPORT_FAIL",
// QString("did not find target dcWithShift, request %1"
// ).arg(requestId));
//if (_globalFailHandler && hasAuthorization()) {
// _globalFailHandler(error, response); // auth failed in main dc
//}
return;
}
const auto &data = result.c_auth_exportedAuthorization();
_instance->send(MTPauth_ImportAuthorization(
data.vid(),
data.vbytes()
), [this](const Response &response) {
auto result = MTPauth_Authorization();
auto from = response.reply.constData();
if (!result.read(from, from + response.reply.size())) {
return false;
}
importDone(result, response);
return true;
}, [this](const Error &error, const Response &response) {
return importFail(error, response);
}, it->second);
_authExportRequests.erase(response.requestId);
}
bool Instance::Private::exportFail(
const Error &error,
const Response &response) {
if (IsDefaultHandledError(error)) {
return false;
}
auto it = _authExportRequests.find(response.requestId);
if (it != _authExportRequests.cend()) {
_authWaiters[BareDcId(it->second)].clear();
}
//
// Don't log out on export/import problems, perhaps this is a server side error.
//
//if (_globalFailHandler && hasAuthorization()) {
// _globalFailHandler(error, response); // auth failed in main dc
//}
return true;
}
bool Instance::Private::handleMigrationError(
mtpRequestId requestId,
DcId migrateDcId) {
if (!requestId) {
return false;
}
auto dcWithShift = ShiftedDcId(0);
auto newdcWithShift = ShiftedDcId(migrateDcId);
if (const auto shiftedDcId = _requests.queryDc(requestId)) {
dcWithShift = *shiftedDcId;
} else {
LOG(("MTP Error: could not find request %1 for migrating to %2"
).arg(requestId).arg(newdcWithShift));
}
if (!dcWithShift || !newdcWithShift) {
return false;
}
DEBUG_LOG(("MTP Info: changing request %1 from dcWithShift%2 to dc%3"
).arg(requestId).arg(dcWithShift).arg(newdcWithShift));
if (dcWithShift < 0) {
_instance->setMainDcId(newdcWithShift);
} else {
newdcWithShift = ShiftDcId(
newdcWithShift,
GetDcIdShift(dcWithShift));
}
const auto request = _requests.request(requestId);
if (!request) {
LOG(("MTP Error: could not find request %1").arg(requestId));
return false;
}
const auto session = getSession(newdcWithShift);
_requests.registerRequest(
requestId,
(dcWithShift < 0) ? -newdcWithShift : newdcWithShift);
session->sendPrepared(request);
return true;
}
bool Instance::Private::handleMsgWaitError(mtpRequestId requestId) {
const auto request = _requests.request(requestId);
if (!request) {
LOG(("MTP Error: could not find MSG_WAIT_* request %1"
).arg(requestId));
return false;
}
if (!request->after) {
LOG(("MTP Error: MSG_WAIT_* for not dependent request %1"
).arg(requestId));
return false;
}
auto dcWithShift = ShiftedDcId(0);
if (const auto shiftedDcId = _requests.queryDc(requestId)) {
dcWithShift = *shiftedDcId;
if (const auto afterDcId = _requests.queryDc(
request->after->requestId)) {
if (*shiftedDcId != *afterDcId) {
request->after = SerializedRequest();
}
} else {
request->after = SerializedRequest();
}
} else {
LOG(("MTP Error: could not find MSG_WAIT_* request %1 by dc"
).arg(requestId));
}
if (!dcWithShift) {
return false;
}
if (!request->after) {
getSession(std::abs(dcWithShift))->sendPrepared(request);
} else {
_requests.addDependency(requestId, request->after->requestId);
}
return true;
}
bool Instance::Private::handleRetryError(
mtpRequestId requestId,
const details::DefaultRpcErrorRetry &retry) {
if (!requestId) {
return false;
}
auto secs = 1;
auto nonPremiumDelay = false;
if (retry.delay == details::DefaultRpcErrorRetry::Delay::Backoff) {
secs = _requests.nextBackoffSeconds(requestId);
} else if (retry.delay == details::DefaultRpcErrorRetry::Delay::Exact) {
secs = retry.seconds;
} else if (retry.delay == details::DefaultRpcErrorRetry::Delay::NonPremium) {
secs = retry.seconds;
nonPremiumDelay = true;
}
const auto sendAt = crl::now() + secs * 1000 + 10;
if (!_requests.scheduleDelayed(requestId, sendAt)) {
return true;
}
checkDelayedRequests();
if (nonPremiumDelay) {
_nonPremiumDelayedRequests.fire_copy(requestId);
}
return true;
}
bool Instance::Private::handleUnauthorizedError(
const Error &error,
const Response &response,
bool badGuestDc) {
const auto requestId = response.requestId;
auto dcWithShift = ShiftedDcId(0);
if (const auto shiftedDcId = _requests.queryDc(requestId)) {
dcWithShift = *shiftedDcId;
} else {
LOG(("MTP Error: unauthorized request without dc info, requestId %1"
).arg(requestId));
}
const auto newdc = BareDcId(std::abs(dcWithShift));
if (!newdc || !hasMainDcId() || newdc == mainDcId()) {
if (!badGuestDc && _globalFailHandler) {
_globalFailHandler(error, response);
}
return false;
}
DEBUG_LOG(("MTP Info: importing auth to dcWithShift %1"
).arg(dcWithShift));
auto &waiters = _authWaiters[newdc];
if (waiters.empty()) {
auto exportRequestId = _instance->send(MTPauth_ExportAuthorization(
MTP_int(newdc)
), [this](const Response &response) {
auto result = MTPauth_ExportedAuthorization();
auto from = response.reply.constData();
if (!result.read(from, from + response.reply.size())) {
return false;
}
exportDone(result, response);
return true;
}, [this](const Error &error, const Response &response) {
return exportFail(error, response);
});
_authExportRequests.emplace(exportRequestId, abs(dcWithShift));
}
waiters.push_back(requestId);
if (badGuestDc) {
_badGuestDcRequests.insert(requestId);
}
return true;
}
bool Instance::Private::handleConnectionInitError(mtpRequestId requestId) {
const auto request = _requests.request(requestId);
if (!request) {
LOG(("MTP Error: could not find request %1").arg(requestId));
return false;
}
auto dcWithShift = ShiftedDcId(0);
if (const auto shiftedDcId = _requests.queryDc(requestId)) {
dcWithShift = *shiftedDcId;
} else {
LOG(("MTP Error: could not find request %1 for resending with init connection"
).arg(requestId));
}
if (!dcWithShift) {
return false;
}
const auto session = getSession(std::abs(dcWithShift));
request->needsLayer = true;
session->setConnectionNotInited();
session->sendPrepared(request);
return true;
}
bool Instance::Private::onErrorDefault(
const Error &error,
const Response &response) {
const auto requestId = response.requestId;
const auto action = details::ClassifyDefaultRpcError(
error,
_badGuestDcRequests.find(requestId) != _badGuestDcRequests.cend());
using Type = details::DefaultRpcErrorAction::Type;
switch (action.type) {
case Type::Migrate:
return handleMigrationError(requestId, action.migrateDcId);
case Type::MsgWait:
return handleMsgWaitError(requestId);
case Type::Retry:
if (action.retry.delay
== details::DefaultRpcErrorRetry::Delay::Backoff) {
auto body = mtpTypeId(0);
const auto request = _requests.request(requestId);
if (request
&& (request->size()
> SerializedRequest::kMessageBodyPosition)) {
body = mtpTypeId(
(*request)[SerializedRequest::kMessageBodyPosition]);
}
Test::RecordRpcRetry(error.code(), error.type(), body);
}
return handleRetryError(requestId, action.retry);
case Type::Unauthorized:
return handleUnauthorizedError(error, response, action.badGuestDc);
case Type::ConnectionInit:
return handleConnectionInitError(requestId);
case Type::ResetLanguage:
if (_runtime->language().resetToDefault) {
_runtime->language().resetToDefault();
}
break;
case Type::Frozen:
_frozenErrorReceived.fire({});
break;
case Type::ClearBadGuestDc:
_badGuestDcRequests.erase(requestId);
break;
case Type::None:
break;
}
return false;
}
not_null<Session*> Instance::Private::getSession(
ShiftedDcId shiftedDcId) {
if (!shiftedDcId) {
Assert(_mainSession != nullptr);
return _mainSession;
} else if (!BareDcId(shiftedDcId)) {
Assert(_mainSession != nullptr);
shiftedDcId += BareDcId(_mainSession->getDcWithShift());
}
if (const auto session = findSession(shiftedDcId)) {
return session;
}
return startSession(shiftedDcId, SessionRole::Auxiliary);
}
rpl::lifetime &Instance::Private::lifetime() {
return _lifetime;
}
Session *Instance::Private::findSession(ShiftedDcId shiftedDcId) {
const auto i = _sessions.find(shiftedDcId);
return (i != _sessions.end()) ? i->second.get() : nullptr;
}
not_null<Session*> Instance::Private::startSession(
ShiftedDcId shiftedDcId,
SessionRole role) {
Expects(BareDcId(shiftedDcId) != 0);
const auto dc = getDcById(shiftedDcId);
const auto thread = getThreadForDc(shiftedDcId);
const auto result = _sessions.emplace(
shiftedDcId,
std::make_unique<Session>(
_instance,
this,
thread,
shiftedDcId,
dc,
role,
_proxyGeneration)
).first->second.get();
if (isKeysDestroyer()) {
scheduleKeyDestroy(shiftedDcId);
}
return result;
}
void Instance::Private::scheduleSessionDestroy(ShiftedDcId shiftedDcId) {
const auto i = _sessions.find(shiftedDcId);
if (i == _sessions.cend()) {
return;
}
i->second->kill();
_sessionsToDestroy.push_back(std::move(i->second));
_sessions.erase(i);
InvokeQueued(_instance, [=] {
_sessionsToDestroy.clear();
});
}
not_null<QThread*> Instance::Private::getThreadForDc(
ShiftedDcId shiftedDcId) {
static const auto EnsureStarted = [](
std::unique_ptr<QThread> &thread,
auto name) {
if (!thread) {
thread = std::make_unique<QThread>();
thread->setObjectName(name());
thread->start();
}
return thread.get();
};
static const auto FindOne = [](
std::vector<std::unique_ptr<QThread>> &threads,
const char *prefix,
int index,
bool shift) {
Expects(!threads.empty());
Expects(!(threads.size() % 2));
const auto count = int(threads.size());
index %= count;
if (index >= count / 2) {
index = (count - 1) - (index - count / 2);
}
if (shift) {
index = (index + count / 2) % count;
}
return EnsureStarted(threads[index], [=] {
return QString("MTP %1 Session (%2)").arg(prefix).arg(index);
});
};
if (shiftedDcId == BareDcId(shiftedDcId)) {
return EnsureStarted(_mainSessionThread, [] {
return QString("MTP Main Session");
});
} else if (isDownloadDcId(shiftedDcId)) {
const auto index = GetDcIdShift(shiftedDcId) - kBaseDownloadDcShift;
const auto composed = index + BareDcId(shiftedDcId);
return FindOne(_fileSessionThreads, "Download", composed, false);
} else if (isUploadDcId(shiftedDcId)) {
const auto index = GetDcIdShift(shiftedDcId) - kBaseUploadDcShift;
const auto composed = index + BareDcId(shiftedDcId);
return FindOne(_fileSessionThreads, "Upload", composed, true);
}
return EnsureStarted(_otherSessionsThread, [] {
return QString("MTP Other Session");
});
}
void Instance::Private::scheduleKeyDestroy(ShiftedDcId shiftedDcId) {
Expects(isKeysDestroyer());
if (dcOptions().dcType(shiftedDcId) == DcType::Cdn) {
performKeyDestroy(shiftedDcId);
} else {
_instance->send(MTPauth_LogOut(), [=](const Response &) {
performKeyDestroy(shiftedDcId);
return true;
}, [=](const Error &error, const Response &) {
if (IsDefaultHandledError(error)) {
return false;
}
performKeyDestroy(shiftedDcId);
return true;
}, shiftedDcId);
}
}
void Instance::Private::keyWasPossiblyDestroyed(ShiftedDcId shiftedDcId) {
Expects(isKeysDestroyer());
InvokeQueued(_instance, [=] {
LOG(("MTP Info: checkIfKeyWasDestroyed on destroying key %1, "
"assuming it is destroyed.").arg(shiftedDcId));
completedKeyDestroy(shiftedDcId);
});
}
void Instance::Private::performKeyDestroy(ShiftedDcId shiftedDcId) {
Expects(isKeysDestroyer());
_instance->send(MTPDestroy_auth_key(), [=](const Response &response) {
auto result = MTPDestroyAuthKeyRes();
auto from = response.reply.constData();
if (!result.read(from, from + response.reply.size())) {
return false;
}
result.match([&](const MTPDdestroy_auth_key_ok &) {
LOG(("MTP Info: key %1 destroyed.").arg(shiftedDcId));
}, [&](const MTPDdestroy_auth_key_fail &) {
LOG(("MTP Error: key %1 destruction fail, leave it for now."
).arg(shiftedDcId));
killSession(shiftedDcId);
}, [&](const MTPDdestroy_auth_key_none &) {
LOG(("MTP Info: key %1 already destroyed.").arg(shiftedDcId));
});
keyWasPossiblyDestroyed(shiftedDcId);
return true;
}, [=](const Error &error, const Response &response) {
LOG(("MTP Error: key %1 destruction resulted in error: %2"
).arg(shiftedDcId).arg(error.type()));
keyWasPossiblyDestroyed(shiftedDcId);
return true;
}, shiftedDcId);
}
void Instance::Private::completedKeyDestroy(ShiftedDcId shiftedDcId) {
Expects(isKeysDestroyer());
removeDc(shiftedDcId);
_keysForWrite.erase(shiftedDcId);
killSession(shiftedDcId);
if (_dcenters.empty()) {
_allKeysDestroyed.fire({});
}
}
void Instance::Private::keyDestroyedOnServer(
ShiftedDcId shiftedDcId,
uint64 keyId) {
LOG(("Destroying key for dc: %1").arg(shiftedDcId));
if (const auto dc = findDc(BareDcId(shiftedDcId))) {
if (dc->destroyConfirmedForgottenKey(keyId)) {
LOG(("Key destroyed!"));
dcPersistentKeyChanged(BareDcId(shiftedDcId), nullptr);
} else {
LOG(("Key already is different."));
}
}
restart(shiftedDcId);
}
void Instance::Private::setUpdatesHandler(
Fn<void(const Response&)> handler) {
_updatesHandler = std::move(handler);
}
void Instance::Private::setGlobalFailHandler(
Fn<void(const Error&, const Response&)> handler) {
_globalFailHandler = std::move(handler);
}
void Instance::Private::setStateChangedHandler(
Fn<void(ShiftedDcId shiftedDcId, int32 state)> handler) {
_stateChangedHandler = std::move(handler);
}
void Instance::Private::setSessionResetHandler(
Fn<void(ShiftedDcId shiftedDcId)> handler) {
_sessionResetHandler = std::move(handler);
}
void Instance::Private::clearGlobalHandlers() {
setUpdatesHandler(nullptr);
setGlobalFailHandler(nullptr);
setStateChangedHandler(nullptr);
setSessionResetHandler(nullptr);
}
void Instance::Private::prepareToDestroy() {
// It accesses Instance in destructor, so it should be destroyed first.
_configLoader.reset();
requestCancellingDiscard();
for (const auto &[shiftedDcId, session] : base::take(_sessions)) {
session->kill();
}
_mainSession = nullptr;
auto threads = std::vector<std::unique_ptr<QThread>>();
threads.push_back(base::take(_mainSessionThread));
threads.push_back(base::take(_otherSessionsThread));
for (auto &thread : base::take(_fileSessionThreads)) {
threads.push_back(std::move(thread));
}
for (const auto &thread : threads) {
if (thread) {
thread->quit();
}
}
for (const auto &thread : threads) {
if (thread) {
thread->wait();
}
}
_runtime->unbindInstance(_connectionStatus.get());
}
Instance::Instance(Mode mode, Fields &&fields)
: QObject()
, _private(std::make_unique<Private>(this, mode, std::move(fields))) {
_private->start();
}
void Instance::suggestMainDcId(DcId mainDcId) {
_private->suggestMainDcId(mainDcId);
}
void Instance::setMainDcId(DcId mainDcId) {
_private->setMainDcId(mainDcId);
}
DcId Instance::mainDcId() const {
return _private->mainDcId();
}
rpl::producer<DcId> Instance::mainDcIdValue() const {
return _private->mainDcIdValue();
}
rpl::producer<> Instance::writeKeysRequests() const {
return _private->writeKeysRequests();
}
rpl::producer<> Instance::allKeysDestroyed() const {
return _private->allKeysDestroyed();
}
void Instance::requestConfig() {
_private->requestConfig();
}
void Instance::setUserPhone(const QString &phone) {
_private->setUserPhone(phone);
}
void Instance::syncHttpUnixtime() {
_private->syncHttpUnixtime();
}
rpl::producer<ShiftedDcId> Instance::restartsByTimeout() const {
return _private->restartsByTimeout();
}
bool Instance::hasCallback(mtpRequestId requestId) const {
return _private->hasCallback(requestId);
}
void Instance::processCallback(const Response &response) {
_private->processCallback(response);
}
rpl::producer<mtpRequestId> Instance::nonPremiumDelayedRequests() const {
return _private->nonPremiumDelayedRequests();
}
rpl::producer<> Instance::frozenErrorReceived() const {
return _private->frozenErrorReceived();
}
void Instance::requestConfigIfOld() {
_private->requestConfigIfOld();
}
void Instance::requestCDNConfig() {
_private->requestCDNConfig();
}
void Instance::restart() {
_private->restart();
}
void Instance::restart(ShiftedDcId shiftedDcId) {
_private->restart(shiftedDcId);
}
void Instance::migrateProxy(bool manual) {
_private->migrateProxy(manual);
}
int32 Instance::dcstate(ShiftedDcId shiftedDcId) {
return _private->dcstate(shiftedDcId);
}
QString Instance::dctransport(ShiftedDcId shiftedDcId) {
return _private->dctransport(shiftedDcId);
}
ConnectionStatus &Instance::connectionStatus() const {
return _private->connectionStatus();
}
void Instance::ping() {
_private->ping();
}
void Instance::cancel(mtpRequestId requestId) {
_private->cancel(requestId);
}
int32 Instance::state(mtpRequestId requestId) { // < 0 means waiting for such count of ms
return _private->state(requestId);
}
void Instance::killSession(ShiftedDcId shiftedDcId) {
_private->killSession(shiftedDcId);
}
void Instance::stopSession(ShiftedDcId shiftedDcId) {
_private->stopSession(shiftedDcId);
}
void Instance::reInitConnection(DcId dcId) {
_private->reInitConnection(dcId);
}
void Instance::logout(Fn<void()> done) {
_private->logout(std::move(done));
}
AuthKeysList Instance::getKeysForWrite() const {
return _private->getKeysForWrite();
}
void Instance::addKeysForDestroy(AuthKeysList &&keys) {
_private->addKeysForDestroy(std::move(keys));
}
Config &Instance::config() const {
return _private->config();
}
const ConfigFields &Instance::configValues() const {
return _private->configValues();
}
DcOptions &Instance::dcOptions() const {
return _private->dcOptions();
}
Environment Instance::environment() const {
return _private->environment();
}
bool Instance::isTestMode() const {
return _private->isTestMode();
}
RuntimeEnvironment &Instance::runtimeEnvironment() const {
return _private->runtimeEnvironment();
}
QString Instance::deviceModel() const {
return _private->deviceModel();
}
QString Instance::systemVersion() const {
return _private->systemVersion();
}
void Instance::setUpdatesHandler(Fn<void(const Response&)> handler) {
_private->setUpdatesHandler(std::move(handler));
}
void Instance::setGlobalFailHandler(
Fn<void(const Error&, const Response&)> handler) {
_private->setGlobalFailHandler(std::move(handler));
}
void Instance::setStateChangedHandler(
Fn<void(ShiftedDcId shiftedDcId, int32 state)> handler) {
_private->setStateChangedHandler(std::move(handler));
}
void Instance::setSessionResetHandler(
Fn<void(ShiftedDcId shiftedDcId)> handler) {
_private->setSessionResetHandler(std::move(handler));
}
void Instance::clearGlobalHandlers() {
_private->clearGlobalHandlers();
}
bool Instance::isKeysDestroyer() const {
return _private->isKeysDestroyer();
}
void Instance::sendRequest(
mtpRequestId requestId,
SerializedRequest &&request,
ResponseHandler &&callbacks,
ShiftedDcId shiftedDcId,
crl::time msCanWait,
bool needsLayer,
mtpRequestId afterRequestId) {
return _private->sendRequest(
requestId,
std::move(request),
std::move(callbacks),
shiftedDcId,
msCanWait,
needsLayer,
afterRequestId);
}
void Instance::sendAnything(ShiftedDcId shiftedDcId, crl::time msCanWait) {
_private->getSession(shiftedDcId)->sendAnything(msCanWait);
}
rpl::lifetime &Instance::lifetime() {
return _private->lifetime();
}
Instance::~Instance() {
_private->prepareToDestroy();
}
} // namespace MTP