init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Close stale issues and PRs / stale (push) Successful in 13s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
This commit is contained in:
242
Telegram/SourceFiles/mtproto/config_loader.cpp
Normal file
242
Telegram/SourceFiles/mtproto/config_loader.cpp
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
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/config_loader.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "mtproto/special_config_request.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "mtproto/mtproto_config.h"
|
||||
#include "mtproto/mtp_instance.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
namespace {
|
||||
|
||||
constexpr auto kEnumerateDcTimeout = 8000; // 8 seconds timeout for help_getConfig to work (then move to other dc)
|
||||
constexpr auto kSpecialRequestTimeoutMs = 6000; // 4 seconds timeout for it to work in a specially requested dc.
|
||||
|
||||
} // namespace
|
||||
|
||||
ConfigLoader::ConfigLoader(
|
||||
not_null<Instance*> instance,
|
||||
const QString &phone,
|
||||
Fn<void(const MTPConfig &result)> onDone,
|
||||
FailHandler onFail,
|
||||
bool proxyEnabled)
|
||||
: _instance(instance)
|
||||
, _phone(phone)
|
||||
, _proxyEnabled(proxyEnabled)
|
||||
, _doneHandler(onDone)
|
||||
, _failHandler(onFail) {
|
||||
_enumDCTimer.setCallback([this] { enumerate(); });
|
||||
_specialEnumTimer.setCallback([this] { sendSpecialRequest(); });
|
||||
}
|
||||
|
||||
void ConfigLoader::load() {
|
||||
if (!_instance->isKeysDestroyer()) {
|
||||
sendRequest(_instance->mainDcId());
|
||||
_enumDCTimer.callOnce(kEnumerateDcTimeout);
|
||||
} else {
|
||||
auto ids = _instance->dcOptions().configEnumDcIds();
|
||||
Assert(!ids.empty());
|
||||
_enumCurrent = ids.front();
|
||||
enumerate();
|
||||
}
|
||||
}
|
||||
|
||||
mtpRequestId ConfigLoader::sendRequest(ShiftedDcId shiftedDcId) {
|
||||
auto done = [done = _doneHandler](const Response &response) {
|
||||
auto from = response.reply.constData();
|
||||
auto result = MTPConfig();
|
||||
if (!result.read(from, from + response.reply.size())) {
|
||||
return false;
|
||||
}
|
||||
done(result);
|
||||
return true;
|
||||
};
|
||||
return _instance->send(
|
||||
MTPhelp_GetConfig(),
|
||||
std::move(done),
|
||||
base::duplicate(_failHandler),
|
||||
shiftedDcId);
|
||||
}
|
||||
|
||||
DcId ConfigLoader::specialToRealDcId(DcId specialDcId) {
|
||||
return getTemporaryIdFromRealDcId(specialDcId);
|
||||
}
|
||||
|
||||
void ConfigLoader::terminateRequest() {
|
||||
if (_enumRequest) {
|
||||
_instance->cancel(base::take(_enumRequest));
|
||||
}
|
||||
if (_enumCurrent) {
|
||||
_instance->killSession(MTP::configDcId(_enumCurrent));
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigLoader::terminateSpecialRequest() {
|
||||
if (_specialEnumRequest) {
|
||||
_instance->cancel(base::take(_specialEnumRequest));
|
||||
}
|
||||
if (_specialEnumCurrent) {
|
||||
_instance->killSession(_specialEnumCurrent);
|
||||
}
|
||||
}
|
||||
|
||||
ConfigLoader::~ConfigLoader() {
|
||||
terminateRequest();
|
||||
terminateSpecialRequest();
|
||||
}
|
||||
|
||||
void ConfigLoader::enumerate() {
|
||||
terminateRequest();
|
||||
if (!_enumCurrent) {
|
||||
_enumCurrent = _instance->mainDcId();
|
||||
}
|
||||
auto ids = _instance->dcOptions().configEnumDcIds();
|
||||
Assert(!ids.empty());
|
||||
|
||||
auto i = std::find(ids.cbegin(), ids.cend(), _enumCurrent);
|
||||
if (i == ids.cend() || (++i) == ids.cend()) {
|
||||
_enumCurrent = ids.front();
|
||||
} else {
|
||||
_enumCurrent = *i;
|
||||
}
|
||||
_enumRequest = sendRequest(MTP::configDcId(_enumCurrent));
|
||||
|
||||
_enumDCTimer.callOnce(kEnumerateDcTimeout);
|
||||
|
||||
refreshSpecialLoader();
|
||||
}
|
||||
|
||||
void ConfigLoader::refreshSpecialLoader() {
|
||||
if (_proxyEnabled || _instance->isKeysDestroyer()) {
|
||||
_specialLoader.reset();
|
||||
return;
|
||||
}
|
||||
if (!_specialLoader
|
||||
|| (!_specialEnumRequest && _specialEndpoints.empty())) {
|
||||
createSpecialLoader();
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigLoader::setPhone(const QString &phone) {
|
||||
if (_phone != phone) {
|
||||
_phone = phone;
|
||||
if (_specialLoader) {
|
||||
createSpecialLoader();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigLoader::createSpecialLoader() {
|
||||
const auto testMode = _instance->isTestMode();
|
||||
_triedSpecialEndpoints.clear();
|
||||
_specialLoader = std::make_unique<SpecialConfigRequest>([=](
|
||||
DcId dcId,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret) {
|
||||
if (ip.empty()) {
|
||||
_specialLoader = nullptr;
|
||||
} else {
|
||||
addSpecialEndpoint(dcId, ip, port, secret);
|
||||
}
|
||||
}, testMode, _instance->configValues().txtDomainString, _phone);
|
||||
}
|
||||
|
||||
void ConfigLoader::addSpecialEndpoint(
|
||||
DcId dcId,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret) {
|
||||
const auto endpoint = SpecialEndpoint {
|
||||
dcId,
|
||||
ip,
|
||||
port,
|
||||
bytes::make_vector(secret)
|
||||
};
|
||||
if (base::contains(_specialEndpoints, endpoint)
|
||||
|| base::contains(_triedSpecialEndpoints, endpoint)) {
|
||||
return;
|
||||
}
|
||||
DEBUG_LOG(("MTP Info: Special endpoint received, '%1:%2'").arg(ip.c_str()).arg(port));
|
||||
_specialEndpoints.push_back(endpoint);
|
||||
|
||||
if (!_specialEnumTimer.isActive()) {
|
||||
_specialEnumTimer.callOnce(1);
|
||||
}
|
||||
}
|
||||
|
||||
void ConfigLoader::sendSpecialRequest() {
|
||||
terminateSpecialRequest();
|
||||
if (_proxyEnabled) {
|
||||
_specialLoader.reset();
|
||||
return;
|
||||
}
|
||||
if (_specialEndpoints.empty()) {
|
||||
refreshSpecialLoader();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto weak = base::make_weak(this);
|
||||
const auto index = base::RandomValue<uint32>() % _specialEndpoints.size();
|
||||
const auto endpoint = _specialEndpoints.begin() + index;
|
||||
_specialEnumCurrent = specialToRealDcId(endpoint->dcId);
|
||||
|
||||
using Flag = MTPDdcOption::Flag;
|
||||
const auto flags = Flag::f_tcpo_only
|
||||
| (endpoint->secret.empty() ? Flag(0) : Flag::f_secret);
|
||||
_instance->dcOptions().constructAddOne(
|
||||
_specialEnumCurrent,
|
||||
flags,
|
||||
endpoint->ip,
|
||||
endpoint->port,
|
||||
endpoint->secret);
|
||||
_specialEnumRequest = _instance->send(
|
||||
MTPhelp_GetConfig(),
|
||||
[weak](const Response &response) {
|
||||
auto result = MTPConfig();
|
||||
auto from = response.reply.constData();
|
||||
if (!result.read(from, from + response.reply.size())) {
|
||||
return false;
|
||||
}
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->specialConfigLoaded(result);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
base::duplicate(_failHandler),
|
||||
_specialEnumCurrent);
|
||||
_triedSpecialEndpoints.push_back(*endpoint);
|
||||
_specialEndpoints.erase(endpoint);
|
||||
|
||||
_specialEnumTimer.callOnce(kSpecialRequestTimeoutMs);
|
||||
}
|
||||
|
||||
void ConfigLoader::specialConfigLoaded(const MTPConfig &result) {
|
||||
Expects(result.type() == mtpc_config);
|
||||
|
||||
const auto &data = result.c_config();
|
||||
if (data.vdc_options().v.empty()) {
|
||||
LOG(("MTP Error: config with empty dc_options received!"));
|
||||
return;
|
||||
}
|
||||
|
||||
// We use special config only for dc options.
|
||||
// For everything else we wait for normal config from main dc.
|
||||
_instance->dcOptions().setFromList(data.vdc_options());
|
||||
}
|
||||
|
||||
void ConfigLoader::setProxyEnabled(bool value) {
|
||||
_proxyEnabled = value;
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
84
Telegram/SourceFiles/mtproto/config_loader.h
Normal file
84
Telegram/SourceFiles/mtproto/config_loader.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/bytes.h"
|
||||
#include "mtproto/mtproto_response.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class Instance;
|
||||
|
||||
namespace details {
|
||||
|
||||
class SpecialConfigRequest;
|
||||
|
||||
class ConfigLoader : public base::has_weak_ptr {
|
||||
public:
|
||||
ConfigLoader(
|
||||
not_null<Instance*> instance,
|
||||
const QString &phone,
|
||||
Fn<void(const MTPConfig &result)> onDone,
|
||||
FailHandler onFail,
|
||||
bool proxyEnabled);
|
||||
~ConfigLoader();
|
||||
|
||||
void load();
|
||||
void setPhone(const QString &phone);
|
||||
void setProxyEnabled(bool value);
|
||||
|
||||
private:
|
||||
mtpRequestId sendRequest(ShiftedDcId shiftedDcId);
|
||||
void addSpecialEndpoint(
|
||||
DcId dcId,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret);
|
||||
void sendSpecialRequest();
|
||||
void enumerate();
|
||||
void refreshSpecialLoader();
|
||||
void createSpecialLoader();
|
||||
DcId specialToRealDcId(DcId specialDcId);
|
||||
void specialConfigLoaded(const MTPConfig &result);
|
||||
void terminateRequest();
|
||||
void terminateSpecialRequest();
|
||||
|
||||
not_null<Instance*> _instance;
|
||||
base::Timer _enumDCTimer;
|
||||
DcId _enumCurrent = 0;
|
||||
mtpRequestId _enumRequest = 0;
|
||||
|
||||
struct SpecialEndpoint {
|
||||
DcId dcId;
|
||||
std::string ip;
|
||||
int port;
|
||||
bytes::vector secret;
|
||||
};
|
||||
friend bool operator==(const SpecialEndpoint &a, const SpecialEndpoint &b);
|
||||
std::unique_ptr<SpecialConfigRequest> _specialLoader;
|
||||
std::vector<SpecialEndpoint> _specialEndpoints;
|
||||
std::vector<SpecialEndpoint> _triedSpecialEndpoints;
|
||||
base::Timer _specialEnumTimer;
|
||||
DcId _specialEnumCurrent = 0;
|
||||
mtpRequestId _specialEnumRequest = 0;
|
||||
QString _phone;
|
||||
bool _proxyEnabled = false;
|
||||
|
||||
Fn<void(const MTPConfig &result)> _doneHandler;
|
||||
FailHandler _failHandler;
|
||||
|
||||
};
|
||||
|
||||
inline bool operator==(const ConfigLoader::SpecialEndpoint &a, const ConfigLoader::SpecialEndpoint &b) {
|
||||
return (a.dcId == b.dcId) && (a.ip == b.ip) && (a.port == b.port);
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
220
Telegram/SourceFiles/mtproto/connection_abstract.cpp
Normal file
220
Telegram/SourceFiles/mtproto/connection_abstract.cpp
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
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_abstract.h"
|
||||
|
||||
#include "mtproto/connection_tcp.h"
|
||||
#include "mtproto/connection_http.h"
|
||||
#include "mtproto/connection_resolving.h"
|
||||
#include "mtproto/session.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/random.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
namespace {
|
||||
|
||||
std::atomic<int> GlobalConnectionCounter/* = 0*/;
|
||||
|
||||
} // namespace
|
||||
|
||||
ConnectionPointer::ConnectionPointer() = default;
|
||||
|
||||
ConnectionPointer::ConnectionPointer(std::nullptr_t) {
|
||||
}
|
||||
|
||||
ConnectionPointer::ConnectionPointer(AbstractConnection *value)
|
||||
: _value(value) {
|
||||
}
|
||||
|
||||
ConnectionPointer::ConnectionPointer(ConnectionPointer &&other)
|
||||
: _value(base::take(other._value)) {
|
||||
}
|
||||
|
||||
ConnectionPointer &ConnectionPointer::operator=(ConnectionPointer &&other) {
|
||||
reset(base::take(other._value));
|
||||
return *this;
|
||||
}
|
||||
|
||||
AbstractConnection *ConnectionPointer::get() const {
|
||||
return _value;
|
||||
}
|
||||
|
||||
void ConnectionPointer::reset(AbstractConnection *value) {
|
||||
if (_value == value) {
|
||||
return;
|
||||
} else if (const auto old = base::take(_value)) {
|
||||
const auto disconnect = [&](auto signal) {
|
||||
old->disconnect(old, signal, nullptr, nullptr);
|
||||
};
|
||||
disconnect(&AbstractConnection::receivedData);
|
||||
disconnect(&AbstractConnection::receivedSome);
|
||||
disconnect(&AbstractConnection::error);
|
||||
disconnect(&AbstractConnection::connected);
|
||||
disconnect(&AbstractConnection::disconnected);
|
||||
old->disconnectFromServer();
|
||||
old->deleteLater();
|
||||
}
|
||||
_value = value;
|
||||
}
|
||||
|
||||
ConnectionPointer::operator AbstractConnection*() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
AbstractConnection *ConnectionPointer::operator->() const {
|
||||
return get();
|
||||
}
|
||||
|
||||
AbstractConnection &ConnectionPointer::operator*() const {
|
||||
return *get();
|
||||
}
|
||||
|
||||
ConnectionPointer::operator bool() const {
|
||||
return get() != nullptr;
|
||||
}
|
||||
|
||||
ConnectionPointer::~ConnectionPointer() {
|
||||
reset();
|
||||
}
|
||||
|
||||
mtpBuffer AbstractConnection::prepareSecurePacket(
|
||||
uint64 keyId,
|
||||
MTPint128 msgKey,
|
||||
uint32 size) const {
|
||||
auto result = mtpBuffer();
|
||||
constexpr auto kTcpPrefixInts = 2;
|
||||
constexpr auto kAuthKeyIdPosition = kTcpPrefixInts;
|
||||
constexpr auto kAuthKeyIdInts = 2;
|
||||
constexpr auto kMessageKeyPosition = kAuthKeyIdPosition
|
||||
+ kAuthKeyIdInts;
|
||||
constexpr auto kMessageKeyInts = 4;
|
||||
constexpr auto kPrefixInts = kTcpPrefixInts
|
||||
+ kAuthKeyIdInts
|
||||
+ kMessageKeyInts;
|
||||
constexpr auto kTcpPostfixInts = 4;
|
||||
result.reserve(kPrefixInts + size + kTcpPostfixInts);
|
||||
result.resize(kPrefixInts);
|
||||
*reinterpret_cast<uint64*>(&result[kAuthKeyIdPosition]) = keyId;
|
||||
*reinterpret_cast<MTPint128*>(&result[kMessageKeyPosition]) = msgKey;
|
||||
return result;
|
||||
}
|
||||
|
||||
gsl::span<const mtpPrime> AbstractConnection::parseNotSecureResponse(
|
||||
const mtpBuffer &buffer) const {
|
||||
const auto answer = buffer.data();
|
||||
const auto len = buffer.size();
|
||||
if (len < 6) {
|
||||
LOG(("Not Secure Error: bad request answer, len = %1"
|
||||
).arg(len * sizeof(mtpPrime)));
|
||||
DEBUG_LOG(("Not Secure Error: answer bytes %1"
|
||||
).arg(Logs::mb(answer, len * sizeof(mtpPrime)).str()));
|
||||
return {};
|
||||
}
|
||||
if (answer[0] != 0
|
||||
|| answer[1] != 0
|
||||
|| (((uint32)answer[2]) & 0x03) != 1
|
||||
//|| (base::unixtime::now() - answer[3] > 300) // We didn't sync time yet.
|
||||
//|| (answer[3] - base::unixtime::now() > 60)
|
||||
|| false) {
|
||||
LOG(("Not Secure Error: bad request answer start (%1 %2 %3)"
|
||||
).arg(answer[0]
|
||||
).arg(answer[1]
|
||||
).arg(answer[2]));
|
||||
DEBUG_LOG(("Not Secure Error: answer bytes %1"
|
||||
).arg(Logs::mb(answer, len * sizeof(mtpPrime)).str()));
|
||||
return {};
|
||||
}
|
||||
const auto answerLen = (uint32)answer[4];
|
||||
if (answerLen < 1 || answerLen > (len - 5) * sizeof(mtpPrime)) {
|
||||
LOG(("Not Secure Error: bad request answer 1 <= %1 <= %2"
|
||||
).arg(answerLen
|
||||
).arg((len - 5) * sizeof(mtpPrime)));
|
||||
DEBUG_LOG(("Not Secure Error: answer bytes %1"
|
||||
).arg(Logs::mb(answer, len * sizeof(mtpPrime)).str()));
|
||||
return {};
|
||||
}
|
||||
return gsl::make_span(answer + 5, answerLen);
|
||||
}
|
||||
|
||||
mtpBuffer AbstractConnection::preparePQFake(const MTPint128 &nonce) const {
|
||||
return prepareNotSecurePacket(
|
||||
MTPReq_pq(nonce),
|
||||
base::unixtime::mtproto_msg_id());
|
||||
}
|
||||
|
||||
std::optional<MTPResPQ> AbstractConnection::readPQFakeReply(
|
||||
const mtpBuffer &buffer) const {
|
||||
const auto answer = parseNotSecureResponse(buffer);
|
||||
if (answer.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto from = answer.data();
|
||||
MTPResPQ response;
|
||||
return response.read(from, from + answer.size())
|
||||
? std::make_optional(response)
|
||||
: std::nullopt;
|
||||
}
|
||||
|
||||
AbstractConnection::AbstractConnection(
|
||||
QThread *thread,
|
||||
const ProxyData &proxy)
|
||||
: _proxy(proxy)
|
||||
, _debugId(QString::number(++GlobalConnectionCounter)) {
|
||||
moveToThread(thread);
|
||||
}
|
||||
|
||||
ConnectionPointer AbstractConnection::Create(
|
||||
not_null<Instance*> instance,
|
||||
DcOptions::Variants::Protocol protocol,
|
||||
QThread *thread,
|
||||
const bytes::vector &secret,
|
||||
const ProxyData &proxy) {
|
||||
auto result = [&] {
|
||||
if (protocol == DcOptions::Variants::Tcp) {
|
||||
return ConnectionPointer::New<TcpConnection>(
|
||||
instance,
|
||||
thread,
|
||||
proxy);
|
||||
} else {
|
||||
return ConnectionPointer::New<HttpConnection>(thread, proxy);
|
||||
}
|
||||
}();
|
||||
if (proxy.tryCustomResolve()) {
|
||||
return ConnectionPointer::New<ResolvingConnection>(
|
||||
instance,
|
||||
thread,
|
||||
proxy,
|
||||
std::move(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString AbstractConnection::ProtocolDcDebugId(int16 protocolDcId) {
|
||||
const auto postfix = (protocolDcId < 0) ? "_media" : "";
|
||||
protocolDcId = (protocolDcId < 0) ? (-protocolDcId) : protocolDcId;
|
||||
const auto prefix = (protocolDcId > kTestModeDcIdShift) ? "test_" : "";
|
||||
protocolDcId = (protocolDcId > kTestModeDcIdShift)
|
||||
? (protocolDcId - kTestModeDcIdShift)
|
||||
: protocolDcId;
|
||||
return prefix + QString::number(protocolDcId) + postfix;
|
||||
}
|
||||
|
||||
void AbstractConnection::logInfo(const QString &message) {
|
||||
DEBUG_LOG(("Connection %1 Info: ").arg(_debugId) + message);
|
||||
}
|
||||
|
||||
void AbstractConnection::logError(const QString &message) {
|
||||
DEBUG_LOG(("Connection %1 Error: ").arg(_debugId) + message);
|
||||
}
|
||||
|
||||
uint32 AbstractConnection::extendedNotSecurePadding() const {
|
||||
return uint32(base::RandomValue<uchar>() & 0x3F);
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
213
Telegram/SourceFiles/mtproto/connection_abstract.h
Normal file
213
Telegram/SourceFiles/mtproto/connection_abstract.h
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "mtproto/mtproto_proxy_data.h"
|
||||
#include "base/bytes.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <deque>
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class Instance;
|
||||
|
||||
namespace details {
|
||||
|
||||
struct ConnectionOptions;
|
||||
|
||||
class AbstractConnection;
|
||||
|
||||
inline constexpr auto kTestModeDcIdShift = 10000;
|
||||
|
||||
class ConnectionPointer {
|
||||
public:
|
||||
ConnectionPointer();
|
||||
ConnectionPointer(std::nullptr_t);
|
||||
ConnectionPointer(ConnectionPointer &&other);
|
||||
ConnectionPointer &operator=(ConnectionPointer &&other);
|
||||
|
||||
template <typename ConnectionType, typename ...Args>
|
||||
static ConnectionPointer New(Args &&...args) {
|
||||
return ConnectionPointer(new ConnectionType(
|
||||
std::forward<Args>(args)...
|
||||
));
|
||||
}
|
||||
|
||||
AbstractConnection *get() const;
|
||||
void reset(AbstractConnection *value = nullptr);
|
||||
operator AbstractConnection*() const;
|
||||
AbstractConnection *operator->() const;
|
||||
AbstractConnection &operator*() const;
|
||||
explicit operator bool() const;
|
||||
|
||||
~ConnectionPointer();
|
||||
|
||||
private:
|
||||
explicit ConnectionPointer(AbstractConnection *value);
|
||||
|
||||
AbstractConnection *_value = nullptr;
|
||||
|
||||
};
|
||||
|
||||
class AbstractConnection : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AbstractConnection(QThread *thread, const ProxyData &proxy);
|
||||
AbstractConnection(const AbstractConnection &other) = delete;
|
||||
AbstractConnection &operator=(const AbstractConnection &other) = delete;
|
||||
virtual ~AbstractConnection() = default;
|
||||
|
||||
// virtual constructor
|
||||
[[nodiscard]] static ConnectionPointer Create(
|
||||
not_null<Instance*> instance,
|
||||
DcOptions::Variants::Protocol protocol,
|
||||
QThread *thread,
|
||||
const bytes::vector &secret,
|
||||
const ProxyData &proxy);
|
||||
|
||||
[[nodiscard]] virtual ConnectionPointer clone(const ProxyData &proxy) = 0;
|
||||
|
||||
[[nodiscard]] virtual crl::time pingTime() const = 0;
|
||||
[[nodiscard]] virtual crl::time fullConnectTimeout() const = 0;
|
||||
virtual void sendData(mtpBuffer &&buffer) = 0;
|
||||
virtual void disconnectFromServer() = 0;
|
||||
virtual void connectToServer(
|
||||
const QString &ip,
|
||||
int port,
|
||||
const bytes::vector &protocolSecret,
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) = 0;
|
||||
virtual void timedOut() {
|
||||
}
|
||||
[[nodiscard]] virtual bool isConnected() const = 0;
|
||||
[[nodiscard]] virtual bool usingHttpWait() {
|
||||
return false;
|
||||
}
|
||||
[[nodiscard]] virtual bool needHttpWait() {
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] virtual int32 debugState() const = 0;
|
||||
|
||||
[[nodiscard]] virtual QString transport() const = 0;
|
||||
[[nodiscard]] virtual QString tag() const = 0;
|
||||
|
||||
void setSentEncryptedWithKeyId(uint64 keyId) {
|
||||
_sentEncryptedWithKeyId = keyId;
|
||||
}
|
||||
[[nodiscard]] uint64 sentEncryptedWithKeyId() const {
|
||||
return _sentEncryptedWithKeyId;
|
||||
}
|
||||
|
||||
using BuffersQueue = std::deque<mtpBuffer>;
|
||||
[[nodiscard]] BuffersQueue &received() {
|
||||
return _receivedQueue;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
[[nodiscard]] mtpBuffer prepareNotSecurePacket(
|
||||
const Request &request,
|
||||
mtpMsgId newId) const;
|
||||
[[nodiscard]] mtpBuffer prepareSecurePacket(
|
||||
uint64 keyId,
|
||||
MTPint128 msgKey,
|
||||
uint32 size) const;
|
||||
|
||||
[[nodiscard]] gsl::span<const mtpPrime> parseNotSecureResponse(
|
||||
const mtpBuffer &buffer) const;
|
||||
|
||||
[[nodiscard]] static QString ProtocolDcDebugId(int16 protocolDcId);
|
||||
[[nodiscard]] QString debugId() const {
|
||||
return _debugId;
|
||||
}
|
||||
void logInfo(const QString &message);
|
||||
void logError(const QString &message);
|
||||
|
||||
// Used to emit error(...) with no real code from the server.
|
||||
static constexpr auto kErrorCodeOther = -499;
|
||||
|
||||
Q_SIGNALS:
|
||||
void receivedData();
|
||||
void receivedSome(); // to stop restart timer
|
||||
|
||||
void error(qint32 errorCodebool);
|
||||
|
||||
void connected();
|
||||
void disconnected();
|
||||
|
||||
void syncTimeRequest();
|
||||
|
||||
protected:
|
||||
BuffersQueue _receivedQueue; // list of received packets, not processed yet
|
||||
int _pingTime = 0;
|
||||
ProxyData _proxy;
|
||||
|
||||
QString _debugId;
|
||||
|
||||
// first we always send fake MTPReq_pq to see if connection works at all
|
||||
// we send them simultaneously through TCP/HTTP/IPv4/IPv6 to choose the working one
|
||||
[[nodiscard]] mtpBuffer preparePQFake(const MTPint128 &nonce) const;
|
||||
[[nodiscard]] std::optional<MTPResPQ> readPQFakeReply(
|
||||
const mtpBuffer &buffer) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] uint32 extendedNotSecurePadding() const;
|
||||
|
||||
uint64 _sentEncryptedWithKeyId = 0;
|
||||
|
||||
};
|
||||
|
||||
template <typename Request>
|
||||
mtpBuffer AbstractConnection::prepareNotSecurePacket(
|
||||
const Request &request,
|
||||
mtpMsgId newId) const {
|
||||
const auto intsSize = tl::count_length(request) >> 2;
|
||||
const auto intsPadding = extendedNotSecurePadding();
|
||||
|
||||
auto result = mtpBuffer();
|
||||
constexpr auto kTcpPrefixInts = 2;
|
||||
constexpr auto kAuthKeyIdInts = 2;
|
||||
constexpr auto kMessageIdInts = 2;
|
||||
constexpr auto kMessageLengthInts = 1;
|
||||
constexpr auto kPrefixInts = kTcpPrefixInts
|
||||
+ kAuthKeyIdInts
|
||||
+ kMessageIdInts
|
||||
+ kMessageLengthInts;
|
||||
constexpr auto kTcpPostfixInts = 4;
|
||||
|
||||
result.reserve(kPrefixInts + intsSize + intsPadding + kTcpPostfixInts);
|
||||
result.resize(kPrefixInts);
|
||||
|
||||
const auto messageId = &result[kTcpPrefixInts + kAuthKeyIdInts];
|
||||
*reinterpret_cast<mtpMsgId*>(messageId) = newId;
|
||||
|
||||
request.write(result);
|
||||
|
||||
const auto messageLength = messageId + kMessageIdInts;
|
||||
*messageLength = (result.size() - kPrefixInts + intsPadding) << 2;
|
||||
|
||||
if (intsPadding > 0) {
|
||||
const auto skipPrimes = result.size();
|
||||
result.resize(skipPrimes + intsPadding);
|
||||
const auto skipBytes = skipPrimes * sizeof(mtpPrime);
|
||||
bytes::set_random(bytes::make_span(result).subspan(skipBytes));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#define CONNECTION_LOG_INFO(x) if (Logs::DebugEnabled()) { logInfo(x); }
|
||||
#define CONNECTION_LOG_ERROR(x) if (Logs::DebugEnabled()) { logError(x); }
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
289
Telegram/SourceFiles/mtproto/connection_http.cpp
Normal file
289
Telegram/SourceFiles/mtproto/connection_http.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
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_http.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "base/qthelp_url.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
namespace {
|
||||
|
||||
constexpr auto kForceHttpPort = 80;
|
||||
constexpr auto kFullConnectionTimeout = crl::time(8000);
|
||||
|
||||
} // namespace
|
||||
|
||||
HttpConnection::HttpConnection(QThread *thread, const ProxyData &proxy)
|
||||
: AbstractConnection(thread, proxy)
|
||||
, _checkNonce(base::RandomValue<MTPint128>()) {
|
||||
_manager.moveToThread(thread);
|
||||
_manager.setProxy(ToNetworkProxy(proxy));
|
||||
}
|
||||
|
||||
ConnectionPointer HttpConnection::clone(const ProxyData &proxy) {
|
||||
return ConnectionPointer::New<HttpConnection>(thread(), proxy);
|
||||
}
|
||||
|
||||
void HttpConnection::sendData(mtpBuffer &&buffer) {
|
||||
Expects(buffer.size() > 2);
|
||||
|
||||
if (_status == Status::Finished) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32 requestSize = (buffer.size() - 2) * sizeof(mtpPrime);
|
||||
|
||||
QNetworkRequest request(url());
|
||||
request.setHeader(
|
||||
QNetworkRequest::ContentLengthHeader,
|
||||
QVariant(requestSize));
|
||||
request.setHeader(
|
||||
QNetworkRequest::ContentTypeHeader,
|
||||
QVariant(u"application/x-www-form-urlencoded"_q));
|
||||
|
||||
CONNECTION_LOG_INFO(u"Sending %1 len request."_q.arg(requestSize));
|
||||
_requests.insert(_manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize)));
|
||||
}
|
||||
|
||||
void HttpConnection::disconnectFromServer() {
|
||||
if (_status == Status::Finished) return;
|
||||
_status = Status::Finished;
|
||||
|
||||
const auto requests = base::take(_requests);
|
||||
for (const auto request : requests) {
|
||||
request->abort();
|
||||
request->deleteLater();
|
||||
}
|
||||
|
||||
disconnect(
|
||||
&_manager,
|
||||
&QNetworkAccessManager::finished,
|
||||
this,
|
||||
&HttpConnection::requestFinished);
|
||||
}
|
||||
|
||||
void HttpConnection::connectToServer(
|
||||
const QString &address,
|
||||
int port,
|
||||
const bytes::vector &protocolSecret,
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) {
|
||||
_address = address;
|
||||
connect(
|
||||
&_manager,
|
||||
&QNetworkAccessManager::finished,
|
||||
this,
|
||||
&HttpConnection::requestFinished);
|
||||
|
||||
auto buffer = preparePQFake(_checkNonce);
|
||||
|
||||
if (Logs::DebugEnabled()) {
|
||||
_debugId = u"%1(dc:%2,%3)"_q
|
||||
.arg(_debugId.toInt())
|
||||
.arg(ProtocolDcDebugId(protocolDcId), url().toDisplayString());
|
||||
}
|
||||
|
||||
_pingTime = crl::now();
|
||||
sendData(std::move(buffer));
|
||||
}
|
||||
|
||||
mtpBuffer HttpConnection::handleResponse(QNetworkReply *reply) {
|
||||
QByteArray response = reply->readAll();
|
||||
CONNECTION_LOG_INFO(u"Read %1 bytes."_q.arg(response.size()));
|
||||
|
||||
if (response.isEmpty()) return mtpBuffer();
|
||||
|
||||
if (response.size() & 0x03 || response.size() < 8) {
|
||||
CONNECTION_LOG_ERROR(u"Bad response size %1."_q.arg(response.size()));
|
||||
return mtpBuffer(1, -500);
|
||||
}
|
||||
|
||||
mtpBuffer data(response.size() >> 2);
|
||||
memcpy(data.data(), response.constData(), response.size());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
// Returns "maybe bad key".
|
||||
qint32 HttpConnection::handleError(QNetworkReply *reply) {
|
||||
auto result = qint32(kErrorCodeOther);
|
||||
|
||||
QVariant statusCode = reply->attribute(
|
||||
QNetworkRequest::HttpStatusCodeAttribute);
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
result = -status;
|
||||
}
|
||||
|
||||
switch (reply->error()) {
|
||||
case QNetworkReply::ConnectionRefusedError:
|
||||
CONNECTION_LOG_ERROR(u"Connection refused - %1."_q
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
case QNetworkReply::RemoteHostClosedError:
|
||||
CONNECTION_LOG_ERROR(u"Remote host closed - %1."_q
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
case QNetworkReply::HostNotFoundError:
|
||||
CONNECTION_LOG_ERROR(u"Host not found - %1."_q
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
case QNetworkReply::TimeoutError:
|
||||
CONNECTION_LOG_ERROR(u"Timeout - %1."_q
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
case QNetworkReply::OperationCanceledError:
|
||||
CONNECTION_LOG_ERROR(u"Cancelled - %1."_q
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
case QNetworkReply::TemporaryNetworkFailureError:
|
||||
case QNetworkReply::NetworkSessionFailedError:
|
||||
case QNetworkReply::BackgroundRequestNotAllowedError:
|
||||
case QNetworkReply::UnknownNetworkError:
|
||||
CONNECTION_LOG_ERROR(u"Network error %1 - %2."_q
|
||||
.arg(reply->error())
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
|
||||
// proxy errors (101-199):
|
||||
case QNetworkReply::ProxyConnectionRefusedError:
|
||||
case QNetworkReply::ProxyConnectionClosedError:
|
||||
case QNetworkReply::ProxyNotFoundError:
|
||||
case QNetworkReply::ProxyTimeoutError:
|
||||
case QNetworkReply::ProxyAuthenticationRequiredError:
|
||||
case QNetworkReply::UnknownProxyError:
|
||||
CONNECTION_LOG_ERROR(u"Proxy error %1 - %2."_q
|
||||
.arg(reply->error())
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
|
||||
// content errors (201-299):
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
case QNetworkReply::ContentReSendError:
|
||||
case QNetworkReply::UnknownContentError:
|
||||
CONNECTION_LOG_ERROR(u"Content error %1 - %2."_q
|
||||
.arg(reply->error())
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
|
||||
// protocol errors
|
||||
case QNetworkReply::ProtocolUnknownError:
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
case QNetworkReply::ProtocolFailure:
|
||||
CONNECTION_LOG_ERROR(u"Protocol error %1 - %2."_q
|
||||
.arg(reply->error())
|
||||
.arg(reply->errorString()));
|
||||
break;
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool HttpConnection::isConnected() const {
|
||||
return (_status == Status::Ready);
|
||||
}
|
||||
|
||||
void HttpConnection::requestFinished(QNetworkReply *reply) {
|
||||
if (_status == Status::Finished) return;
|
||||
|
||||
reply->deleteLater();
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
_requests.remove(reply);
|
||||
|
||||
mtpBuffer data = handleResponse(reply);
|
||||
if (data.size() == 1) {
|
||||
error(data[0]);
|
||||
} else if (!data.isEmpty()) {
|
||||
if (_status == Status::Ready) {
|
||||
_receivedQueue.push_back(data);
|
||||
receivedData();
|
||||
} else if (const auto res_pq = readPQFakeReply(data)) {
|
||||
const auto &data = res_pq->c_resPQ();
|
||||
if (data.vnonce() == _checkNonce) {
|
||||
CONNECTION_LOG_INFO(
|
||||
"HTTP-transport connected by pq-response.");
|
||||
_status = Status::Ready;
|
||||
_pingTime = crl::now() - _pingTime;
|
||||
connected();
|
||||
} else {
|
||||
CONNECTION_LOG_ERROR(
|
||||
"Wrong nonce in HTTP fake pq-response.");
|
||||
error(kErrorCodeOther);
|
||||
}
|
||||
} else {
|
||||
CONNECTION_LOG_ERROR(
|
||||
"Could not parse HTTP fake pq-response.");
|
||||
error(kErrorCodeOther);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!_requests.remove(reply)) {
|
||||
return;
|
||||
}
|
||||
|
||||
error(handleError(reply));
|
||||
}
|
||||
}
|
||||
|
||||
crl::time HttpConnection::pingTime() const {
|
||||
return isConnected() ? _pingTime : crl::time(0);
|
||||
}
|
||||
|
||||
crl::time HttpConnection::fullConnectTimeout() const {
|
||||
return kFullConnectionTimeout;
|
||||
}
|
||||
|
||||
bool HttpConnection::usingHttpWait() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HttpConnection::needHttpWait() {
|
||||
return _requests.isEmpty();
|
||||
}
|
||||
|
||||
int32 HttpConnection::debugState() const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString HttpConnection::transport() const {
|
||||
if (!isConnected()) {
|
||||
return QString();
|
||||
}
|
||||
auto result = u"HTTP"_q;
|
||||
if (qthelp::is_ipv6(_address)) {
|
||||
result += u"/IPv6"_q;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString HttpConnection::tag() const {
|
||||
auto result = u"HTTP"_q;
|
||||
if (qthelp::is_ipv6(_address)) {
|
||||
result += u"/IPv6"_q;
|
||||
} else {
|
||||
result += u"/IPv4"_q;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QUrl HttpConnection::url() const {
|
||||
const auto pattern = qthelp::is_ipv6(_address)
|
||||
? u"http://[%1]:%2/api"_q
|
||||
: u"http://%1:%2/api"_q;
|
||||
|
||||
// Not endpoint.port - always 80 port for http transport.
|
||||
return QUrl(pattern.arg(_address).arg(kForceHttpPort));
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
69
Telegram/SourceFiles/mtproto/connection_http.h
Normal file
69
Telegram/SourceFiles/mtproto/connection_http.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/connection_abstract.h"
|
||||
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
|
||||
class HttpConnection : public AbstractConnection {
|
||||
public:
|
||||
HttpConnection(QThread *thread, const ProxyData &proxy);
|
||||
|
||||
ConnectionPointer clone(const ProxyData &proxy) override;
|
||||
|
||||
crl::time pingTime() const override;
|
||||
crl::time fullConnectTimeout() const override;
|
||||
void sendData(mtpBuffer &&buffer) override;
|
||||
void disconnectFromServer() override;
|
||||
void connectToServer(
|
||||
const QString &address,
|
||||
int port,
|
||||
const bytes::vector &protocolSecret,
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) override;
|
||||
bool isConnected() const override;
|
||||
bool usingHttpWait() override;
|
||||
bool needHttpWait() override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
QString transport() const override;
|
||||
QString tag() const override;
|
||||
|
||||
mtpBuffer handleResponse(QNetworkReply *reply);
|
||||
qint32 handleError(QNetworkReply *reply); // Returns error code.
|
||||
|
||||
private:
|
||||
QUrl url() const;
|
||||
|
||||
void requestFinished(QNetworkReply *reply);
|
||||
|
||||
enum class Status {
|
||||
Waiting = 0,
|
||||
Ready,
|
||||
Finished,
|
||||
};
|
||||
Status _status = Status::Waiting;
|
||||
MTPint128 _checkNonce;
|
||||
|
||||
QNetworkAccessManager _manager;
|
||||
QString _address;
|
||||
|
||||
QSet<QNetworkReply*> _requests;
|
||||
|
||||
crl::time _pingTime = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
249
Telegram/SourceFiles/mtproto/connection_resolving.cpp
Normal file
249
Telegram/SourceFiles/mtproto/connection_resolving.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
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_resolving.h"
|
||||
|
||||
#include "mtproto/mtp_instance.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
namespace {
|
||||
|
||||
constexpr auto kOneConnectionTimeout = 4000;
|
||||
|
||||
} // namespace
|
||||
|
||||
ResolvingConnection::ResolvingConnection(
|
||||
not_null<Instance*> instance,
|
||||
QThread *thread,
|
||||
const ProxyData &proxy,
|
||||
ConnectionPointer &&child)
|
||||
: AbstractConnection(thread, proxy)
|
||||
, _instance(instance)
|
||||
, _timeoutTimer([=] { handleError(kErrorCodeOther); }) {
|
||||
setChild(std::move(child));
|
||||
if (proxy.resolvedExpireAt < crl::now()) {
|
||||
const auto host = proxy.host;
|
||||
connect(
|
||||
instance,
|
||||
&Instance::proxyDomainResolved,
|
||||
this,
|
||||
&ResolvingConnection::domainResolved,
|
||||
Qt::QueuedConnection);
|
||||
InvokeQueued(instance, [=] {
|
||||
instance->resolveProxyDomain(host);
|
||||
});
|
||||
}
|
||||
if (!proxy.resolvedIPs.empty()) {
|
||||
refreshChild();
|
||||
}
|
||||
}
|
||||
|
||||
ConnectionPointer ResolvingConnection::clone(const ProxyData &proxy) {
|
||||
Unexpected("ResolvingConnection::clone call.");
|
||||
}
|
||||
|
||||
void ResolvingConnection::setChild(ConnectionPointer &&child) {
|
||||
_child = std::move(child);
|
||||
connect(
|
||||
_child,
|
||||
&AbstractConnection::receivedData,
|
||||
this,
|
||||
&ResolvingConnection::handleReceivedData);
|
||||
connect(
|
||||
_child,
|
||||
&AbstractConnection::receivedSome,
|
||||
this,
|
||||
&ResolvingConnection::receivedSome);
|
||||
connect(
|
||||
_child,
|
||||
&AbstractConnection::error,
|
||||
this,
|
||||
&ResolvingConnection::handleError);
|
||||
connect(_child,
|
||||
&AbstractConnection::connected,
|
||||
this,
|
||||
&ResolvingConnection::handleConnected);
|
||||
connect(_child,
|
||||
&AbstractConnection::disconnected,
|
||||
this,
|
||||
&ResolvingConnection::handleDisconnected);
|
||||
if (_protocolDcId) {
|
||||
_child->connectToServer(
|
||||
_address,
|
||||
_port,
|
||||
_protocolSecret,
|
||||
_protocolDcId,
|
||||
_protocolForFiles);
|
||||
CONNECTION_LOG_INFO("Resolving connected a new child: "
|
||||
+ _child->debugId());
|
||||
}
|
||||
}
|
||||
|
||||
void ResolvingConnection::domainResolved(
|
||||
const QString &host,
|
||||
const QStringList &ips,
|
||||
qint64 expireAt) {
|
||||
if (host != _proxy.host || !_child) {
|
||||
return;
|
||||
}
|
||||
_proxy.resolvedExpireAt = expireAt;
|
||||
|
||||
auto index = 0;
|
||||
for (const auto &ip : ips) {
|
||||
if (index >= _proxy.resolvedIPs.size()) {
|
||||
_proxy.resolvedIPs.push_back(ip);
|
||||
} else if (_proxy.resolvedIPs[index] != ip) {
|
||||
_proxy.resolvedIPs[index] = ip;
|
||||
if (_ipIndex >= index) {
|
||||
_ipIndex = index - 1;
|
||||
refreshChild();
|
||||
}
|
||||
}
|
||||
++index;
|
||||
}
|
||||
if (index < _proxy.resolvedIPs.size()) {
|
||||
_proxy.resolvedIPs.resize(index);
|
||||
if (_ipIndex >= index) {
|
||||
emitError(kErrorCodeOther);
|
||||
}
|
||||
}
|
||||
if (_ipIndex < 0) {
|
||||
refreshChild();
|
||||
}
|
||||
}
|
||||
|
||||
bool ResolvingConnection::refreshChild() {
|
||||
if (!_child) {
|
||||
return true;
|
||||
} else if (++_ipIndex >= _proxy.resolvedIPs.size()) {
|
||||
return false;
|
||||
}
|
||||
setChild(_child->clone(ToDirectIpProxy(_proxy, _ipIndex)));
|
||||
_timeoutTimer.callOnce(kOneConnectionTimeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResolvingConnection::emitError(int errorCode) {
|
||||
_ipIndex = -1;
|
||||
_child = nullptr;
|
||||
error(errorCode);
|
||||
}
|
||||
|
||||
void ResolvingConnection::handleError(int errorCode) {
|
||||
if (_connected) {
|
||||
emitError(errorCode);
|
||||
} else if (!_proxy.resolvedIPs.empty()) {
|
||||
if (!refreshChild()) {
|
||||
emitError(errorCode);
|
||||
}
|
||||
} else {
|
||||
// Wait for the domain to be resolved.
|
||||
}
|
||||
}
|
||||
|
||||
void ResolvingConnection::handleDisconnected() {
|
||||
if (_connected) {
|
||||
disconnected();
|
||||
} else {
|
||||
handleError(kErrorCodeOther);
|
||||
}
|
||||
}
|
||||
|
||||
void ResolvingConnection::handleReceivedData() {
|
||||
auto &my = received();
|
||||
auto &his = _child->received();
|
||||
for (auto &item : his) {
|
||||
my.push_back(std::move(item));
|
||||
}
|
||||
his.clear();
|
||||
receivedData();
|
||||
}
|
||||
|
||||
void ResolvingConnection::handleConnected() {
|
||||
_connected = true;
|
||||
_timeoutTimer.cancel();
|
||||
if (_ipIndex >= 0) {
|
||||
const auto host = _proxy.host;
|
||||
const auto good = _proxy.resolvedIPs[_ipIndex];
|
||||
const auto instance = _instance;
|
||||
InvokeQueued(_instance, [=] {
|
||||
instance->setGoodProxyDomain(host, good);
|
||||
});
|
||||
}
|
||||
connected();
|
||||
}
|
||||
|
||||
crl::time ResolvingConnection::pingTime() const {
|
||||
Expects(_child != nullptr);
|
||||
|
||||
return _child->pingTime();
|
||||
}
|
||||
|
||||
crl::time ResolvingConnection::fullConnectTimeout() const {
|
||||
return kOneConnectionTimeout * qMax(int(_proxy.resolvedIPs.size()), 1);
|
||||
}
|
||||
|
||||
void ResolvingConnection::sendData(mtpBuffer &&buffer) {
|
||||
Expects(_child != nullptr);
|
||||
|
||||
_child->sendData(std::move(buffer));
|
||||
}
|
||||
|
||||
void ResolvingConnection::disconnectFromServer() {
|
||||
_address = QString();
|
||||
_port = 0;
|
||||
_protocolSecret = bytes::vector();
|
||||
_protocolDcId = 0;
|
||||
if (!_child) {
|
||||
return;
|
||||
}
|
||||
_child->disconnectFromServer();
|
||||
}
|
||||
|
||||
void ResolvingConnection::connectToServer(
|
||||
const QString &address,
|
||||
int port,
|
||||
const bytes::vector &protocolSecret,
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) {
|
||||
if (!_child) {
|
||||
InvokeQueued(this, [=] { emitError(kErrorCodeOther); });
|
||||
return;
|
||||
}
|
||||
_address = address;
|
||||
_port = port;
|
||||
_protocolSecret = protocolSecret;
|
||||
_protocolDcId = protocolDcId;
|
||||
_protocolForFiles = protocolForFiles;
|
||||
_child->connectToServer(
|
||||
address,
|
||||
port,
|
||||
protocolSecret,
|
||||
protocolDcId,
|
||||
protocolForFiles);
|
||||
CONNECTION_LOG_INFO("Resolving connected a child: " + _child->debugId());
|
||||
}
|
||||
|
||||
bool ResolvingConnection::isConnected() const {
|
||||
return _child ? _child->isConnected() : false;
|
||||
}
|
||||
|
||||
int32 ResolvingConnection::debugState() const {
|
||||
return _child ? _child->debugState() : -1;
|
||||
}
|
||||
|
||||
QString ResolvingConnection::transport() const {
|
||||
return _child ? _child->transport() : QString();
|
||||
}
|
||||
|
||||
QString ResolvingConnection::tag() const {
|
||||
return _child ? _child->tag() : QString();
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
72
Telegram/SourceFiles/mtproto/connection_resolving.h
Normal file
72
Telegram/SourceFiles/mtproto/connection_resolving.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
|
||||
class ResolvingConnection : public AbstractConnection {
|
||||
public:
|
||||
ResolvingConnection(
|
||||
not_null<Instance*> instance,
|
||||
QThread *thread,
|
||||
const ProxyData &proxy,
|
||||
ConnectionPointer &&child);
|
||||
|
||||
ConnectionPointer clone(const ProxyData &proxy) override;
|
||||
|
||||
crl::time pingTime() const override;
|
||||
crl::time fullConnectTimeout() const override;
|
||||
void sendData(mtpBuffer &&buffer) override;
|
||||
void disconnectFromServer() override;
|
||||
void connectToServer(
|
||||
const QString &address,
|
||||
int port,
|
||||
const bytes::vector &protocolSecret,
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) override;
|
||||
bool isConnected() const override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
QString transport() const override;
|
||||
QString tag() const override;
|
||||
|
||||
private:
|
||||
void setChild(ConnectionPointer &&child);
|
||||
bool refreshChild();
|
||||
void emitError(int errorCode);
|
||||
|
||||
void domainResolved(
|
||||
const QString &host,
|
||||
const QStringList &ips,
|
||||
qint64 expireAt);
|
||||
void handleError(int errorCode);
|
||||
void handleConnected();
|
||||
void handleDisconnected();
|
||||
void handleReceivedData();
|
||||
|
||||
not_null<Instance*> _instance;
|
||||
ConnectionPointer _child;
|
||||
bool _connected = false;
|
||||
int _ipIndex = -1;
|
||||
QString _address;
|
||||
int _port = 0;
|
||||
bytes::vector _protocolSecret;
|
||||
int16 _protocolDcId = 0;
|
||||
bool _protocolForFiles = false;
|
||||
base::Timer _timeoutTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
668
Telegram/SourceFiles/mtproto/connection_tcp.cpp
Normal file
668
Telegram/SourceFiles/mtproto/connection_tcp.cpp
Normal file
@@ -0,0 +1,668 @@
|
||||
/*
|
||||
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_tcp.h"
|
||||
|
||||
#include "mtproto/details/mtproto_abstract_socket.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "base/qthelp_url.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
namespace {
|
||||
|
||||
constexpr auto kPacketSizeMax = int(0x01000000 * sizeof(mtpPrime));
|
||||
constexpr auto kFullConnectionTimeout = 8 * crl::time(1000);
|
||||
constexpr auto kSmallBufferSize = 256 * 1024;
|
||||
constexpr auto kMinPacketBuffer = 256;
|
||||
constexpr auto kConnectionStartPrefixSize = 64;
|
||||
|
||||
} // namespace
|
||||
|
||||
class TcpConnection::Protocol {
|
||||
public:
|
||||
static std::unique_ptr<Protocol> Create(bytes::const_span secret);
|
||||
|
||||
virtual uint32 id() const = 0;
|
||||
virtual bool supportsArbitraryLength() const = 0;
|
||||
|
||||
virtual void prepareKey(bytes::span key, bytes::const_span source) = 0;
|
||||
virtual bytes::span finalizePacket(mtpBuffer &buffer) = 0;
|
||||
|
||||
static constexpr auto kUnknownSize = -1;
|
||||
static constexpr auto kInvalidSize = -2;
|
||||
virtual int readPacketLength(bytes::const_span bytes) const = 0;
|
||||
virtual bytes::const_span readPacket(bytes::const_span bytes) const = 0;
|
||||
|
||||
virtual QString debugPostfix() const = 0;
|
||||
|
||||
virtual ~Protocol() = default;
|
||||
|
||||
private:
|
||||
class Version0;
|
||||
class Version1;
|
||||
class VersionD;
|
||||
|
||||
};
|
||||
|
||||
class TcpConnection::Protocol::Version0 : public Protocol {
|
||||
public:
|
||||
uint32 id() const override;
|
||||
bool supportsArbitraryLength() const override;
|
||||
|
||||
void prepareKey(bytes::span key, bytes::const_span source) override;
|
||||
bytes::span finalizePacket(mtpBuffer &buffer) override;
|
||||
|
||||
int readPacketLength(bytes::const_span bytes) const override;
|
||||
bytes::const_span readPacket(bytes::const_span bytes) const override;
|
||||
|
||||
QString debugPostfix() const override;
|
||||
|
||||
};
|
||||
|
||||
uint32 TcpConnection::Protocol::Version0::id() const {
|
||||
return 0xEFEFEFEFU;
|
||||
}
|
||||
|
||||
bool TcpConnection::Protocol::Version0::supportsArbitraryLength() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void TcpConnection::Protocol::Version0::prepareKey(
|
||||
bytes::span key,
|
||||
bytes::const_span source) {
|
||||
bytes::copy(key, source);
|
||||
}
|
||||
|
||||
bytes::span TcpConnection::Protocol::Version0::finalizePacket(
|
||||
mtpBuffer &buffer) {
|
||||
Expects(buffer.size() > 2 && buffer.size() < 0x1000003U);
|
||||
|
||||
const auto intsSize = uint32(buffer.size() - 2);
|
||||
const auto bytesSize = intsSize * sizeof(mtpPrime);
|
||||
const auto data = reinterpret_cast<uchar*>(&buffer[0]);
|
||||
const auto added = [&] {
|
||||
if (intsSize < 0x7F) {
|
||||
data[7] = uchar(intsSize);
|
||||
return 1;
|
||||
}
|
||||
data[4] = uchar(0x7F);
|
||||
data[5] = uchar(intsSize & 0xFF);
|
||||
data[6] = uchar((intsSize >> 8) & 0xFF);
|
||||
data[7] = uchar((intsSize >> 16) & 0xFF);
|
||||
return 4;
|
||||
}();
|
||||
return bytes::make_span(buffer).subspan(8 - added, added + bytesSize);
|
||||
}
|
||||
|
||||
int TcpConnection::Protocol::Version0::readPacketLength(
|
||||
bytes::const_span bytes) const {
|
||||
if (bytes.empty()) {
|
||||
return kUnknownSize;
|
||||
}
|
||||
|
||||
const auto first = static_cast<char>(bytes[0]);
|
||||
if (first == 0x7F) {
|
||||
if (bytes.size() < 4) {
|
||||
return kUnknownSize;
|
||||
}
|
||||
const auto ints = static_cast<uint32>(bytes[1])
|
||||
| (static_cast<uint32>(bytes[2]) << 8)
|
||||
| (static_cast<uint32>(bytes[3]) << 16);
|
||||
return (ints >= 0x7F) ? (int(ints << 2) + 4) : kInvalidSize;
|
||||
} else if (first > 0 && first < 0x7F) {
|
||||
const auto ints = uint32(first);
|
||||
return int(ints << 2) + 1;
|
||||
}
|
||||
return kInvalidSize;
|
||||
}
|
||||
|
||||
bytes::const_span TcpConnection::Protocol::Version0::readPacket(
|
||||
bytes::const_span bytes) const {
|
||||
const auto size = readPacketLength(bytes);
|
||||
Assert(size != kUnknownSize
|
||||
&& size != kInvalidSize
|
||||
&& size <= bytes.size());
|
||||
const auto sizeLength = (static_cast<char>(bytes[0]) == 0x7F) ? 4 : 1;
|
||||
return bytes.subspan(sizeLength, size - sizeLength);
|
||||
}
|
||||
|
||||
QString TcpConnection::Protocol::Version0::debugPostfix() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
class TcpConnection::Protocol::Version1 : public Version0 {
|
||||
public:
|
||||
explicit Version1(bytes::vector &&secret);
|
||||
|
||||
void prepareKey(bytes::span key, bytes::const_span source) override;
|
||||
|
||||
QString debugPostfix() const override;
|
||||
|
||||
private:
|
||||
bytes::vector _secret;
|
||||
|
||||
};
|
||||
|
||||
TcpConnection::Protocol::Version1::Version1(bytes::vector &&secret)
|
||||
: _secret(std::move(secret)) {
|
||||
}
|
||||
|
||||
void TcpConnection::Protocol::Version1::prepareKey(
|
||||
bytes::span key,
|
||||
bytes::const_span source) {
|
||||
const auto payload = bytes::concatenate(source, _secret);
|
||||
bytes::copy(key, openssl::Sha256(payload));
|
||||
}
|
||||
|
||||
QString TcpConnection::Protocol::Version1::debugPostfix() const {
|
||||
return u"_obf"_q;
|
||||
}
|
||||
|
||||
class TcpConnection::Protocol::VersionD : public Version1 {
|
||||
public:
|
||||
using Version1::Version1;
|
||||
|
||||
uint32 id() const override;
|
||||
bool supportsArbitraryLength() const override;
|
||||
|
||||
bytes::span finalizePacket(mtpBuffer &buffer) override;
|
||||
|
||||
int readPacketLength(bytes::const_span bytes) const override;
|
||||
bytes::const_span readPacket(bytes::const_span bytes) const override;
|
||||
|
||||
QString debugPostfix() const override;
|
||||
|
||||
};
|
||||
|
||||
uint32 TcpConnection::Protocol::VersionD::id() const {
|
||||
return 0xDDDDDDDDU;
|
||||
}
|
||||
|
||||
bool TcpConnection::Protocol::VersionD::supportsArbitraryLength() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bytes::span TcpConnection::Protocol::VersionD::finalizePacket(
|
||||
mtpBuffer &buffer) {
|
||||
Expects(buffer.size() > 2 && buffer.size() < 0x1000003U);
|
||||
|
||||
const auto intsSize = uint32(buffer.size() - 2);
|
||||
const auto padding = base::RandomValue<uint32>() & 0x0F;
|
||||
const auto bytesSize = intsSize * sizeof(mtpPrime) + padding;
|
||||
buffer[1] = bytesSize;
|
||||
for (auto added = 0; added < padding; added += 4) {
|
||||
buffer.push_back(base::RandomValue<mtpPrime>());
|
||||
}
|
||||
|
||||
return bytes::make_span(buffer).subspan(4, 4 + bytesSize);
|
||||
}
|
||||
|
||||
int TcpConnection::Protocol::VersionD::readPacketLength(
|
||||
bytes::const_span bytes) const {
|
||||
if (bytes.size() < 4) {
|
||||
return kUnknownSize;
|
||||
}
|
||||
const auto value = *reinterpret_cast<const uint32*>(bytes.data()) + 4;
|
||||
return (value >= 8 && value < kPacketSizeMax)
|
||||
? int(value)
|
||||
: kInvalidSize;
|
||||
}
|
||||
|
||||
bytes::const_span TcpConnection::Protocol::VersionD::readPacket(
|
||||
bytes::const_span bytes) const {
|
||||
const auto size = readPacketLength(bytes);
|
||||
Assert(size != kUnknownSize
|
||||
&& size != kInvalidSize
|
||||
&& size <= bytes.size());
|
||||
const auto sizeLength = 4;
|
||||
return bytes.subspan(sizeLength, size - sizeLength);
|
||||
}
|
||||
|
||||
QString TcpConnection::Protocol::VersionD::debugPostfix() const {
|
||||
return u"_dd"_q;
|
||||
}
|
||||
|
||||
auto TcpConnection::Protocol::Create(bytes::const_span secret)
|
||||
-> std::unique_ptr<Protocol> {
|
||||
// See also DcOptions::ValidateSecret.
|
||||
if ((secret.size() >= 21 && secret[0] == bytes::type(0xEE))
|
||||
|| (secret.size() == 17 && secret[0] == bytes::type(0xDD))) {
|
||||
return std::make_unique<VersionD>(
|
||||
bytes::make_vector(secret.subspan(1, 16)));
|
||||
} else if (secret.size() == 16) {
|
||||
return std::make_unique<Version1>(bytes::make_vector(secret));
|
||||
} else if (secret.empty()) {
|
||||
return std::make_unique<Version0>();
|
||||
}
|
||||
Unexpected("Secret bytes in TcpConnection::Protocol::Create.");
|
||||
}
|
||||
|
||||
TcpConnection::TcpConnection(
|
||||
not_null<Instance*> instance,
|
||||
QThread *thread,
|
||||
const ProxyData &proxy)
|
||||
: AbstractConnection(thread, proxy)
|
||||
, _instance(instance)
|
||||
, _checkNonce(base::RandomValue<MTPint128>()) {
|
||||
}
|
||||
|
||||
ConnectionPointer TcpConnection::clone(const ProxyData &proxy) {
|
||||
return ConnectionPointer::New<TcpConnection>(_instance, thread(), proxy);
|
||||
}
|
||||
|
||||
void TcpConnection::ensureAvailableInBuffer(int amount) {
|
||||
auto &buffer = _usingLargeBuffer ? _largeBuffer : _smallBuffer;
|
||||
const auto full = bytes::make_span(buffer).subspan(
|
||||
_offsetBytes);
|
||||
if (full.size() >= amount) {
|
||||
return;
|
||||
}
|
||||
const auto read = full.subspan(0, _readBytes);
|
||||
if (amount <= _smallBuffer.size()) {
|
||||
if (_usingLargeBuffer) {
|
||||
bytes::copy(_smallBuffer, read);
|
||||
_usingLargeBuffer = false;
|
||||
_largeBuffer.clear();
|
||||
} else {
|
||||
bytes::move(_smallBuffer, read);
|
||||
}
|
||||
} else if (amount <= _largeBuffer.size()) {
|
||||
Assert(_usingLargeBuffer);
|
||||
bytes::move(_largeBuffer, read);
|
||||
} else {
|
||||
auto enough = bytes::vector(amount);
|
||||
bytes::copy(enough, read);
|
||||
_largeBuffer = std::move(enough);
|
||||
_usingLargeBuffer = true;
|
||||
}
|
||||
_offsetBytes = 0;
|
||||
}
|
||||
|
||||
void TcpConnection::socketRead() {
|
||||
Expects(_leftBytes > 0 || !_usingLargeBuffer);
|
||||
|
||||
if (!_socket || !_socket->isConnected()) {
|
||||
CONNECTION_LOG_ERROR("Socket not connected in socketRead()");
|
||||
error(kErrorCodeOther);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_smallBuffer.empty()) {
|
||||
_smallBuffer.resize(kSmallBufferSize);
|
||||
}
|
||||
do {
|
||||
const auto readLimit = (_leftBytes > 0)
|
||||
? _leftBytes
|
||||
: (kSmallBufferSize - _offsetBytes - _readBytes);
|
||||
Assert(readLimit > 0);
|
||||
|
||||
auto &buffer = _usingLargeBuffer ? _largeBuffer : _smallBuffer;
|
||||
const auto full = bytes::make_span(buffer).subspan(_offsetBytes);
|
||||
const auto free = full.subspan(_readBytes);
|
||||
const auto readCount = _socket->read(free.subspan(0, readLimit));
|
||||
if (readCount > 0) {
|
||||
const auto read = free.subspan(0, readCount);
|
||||
aesCtrEncrypt(read, _receiveKey, &_receiveState);
|
||||
CONNECTION_LOG_INFO(u"Read %1 bytes"_q.arg(readCount));
|
||||
|
||||
_readBytes += readCount;
|
||||
if (_leftBytes > 0) {
|
||||
Assert(readCount <= _leftBytes);
|
||||
_leftBytes -= readCount;
|
||||
if (!_leftBytes) {
|
||||
socketPacket(full.subspan(0, _readBytes));
|
||||
if (!_socket || !_socket->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
_usingLargeBuffer = false;
|
||||
_largeBuffer.clear();
|
||||
_offsetBytes = _readBytes = 0;
|
||||
} else {
|
||||
CONNECTION_LOG_INFO(
|
||||
u"Not enough %1 for packet! read %2"_q
|
||||
.arg(_leftBytes)
|
||||
.arg(_readBytes));
|
||||
receivedSome();
|
||||
}
|
||||
} else {
|
||||
auto available = full.subspan(0, _readBytes);
|
||||
while (_readBytes > 0) {
|
||||
const auto packetSize = _protocol->readPacketLength(
|
||||
available);
|
||||
if (packetSize == Protocol::kUnknownSize) {
|
||||
// Not enough bytes yet.
|
||||
break;
|
||||
} else if (packetSize <= 0) {
|
||||
CONNECTION_LOG_ERROR(
|
||||
u"Bad packet size in 4 bytes: %1"_q
|
||||
.arg(packetSize));
|
||||
error(kErrorCodeOther);
|
||||
return;
|
||||
} else if (available.size() >= packetSize) {
|
||||
socketPacket(available.subspan(0, packetSize));
|
||||
if (!_socket || !_socket->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
available = available.subspan(packetSize);
|
||||
_offsetBytes += packetSize;
|
||||
_readBytes -= packetSize;
|
||||
|
||||
// If we have too little space left in the buffer.
|
||||
ensureAvailableInBuffer(kMinPacketBuffer);
|
||||
} else {
|
||||
_leftBytes = packetSize - available.size();
|
||||
|
||||
// If the next packet won't fit in the buffer.
|
||||
ensureAvailableInBuffer(packetSize);
|
||||
|
||||
CONNECTION_LOG_INFO(u"Not enough %1 for packet! "
|
||||
"full size %2 read %3"_q
|
||||
.arg(_leftBytes)
|
||||
.arg(packetSize)
|
||||
.arg(available.size()));
|
||||
receivedSome();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (readCount < 0) {
|
||||
CONNECTION_LOG_ERROR(u"Socket read return %1."_q.arg(readCount));
|
||||
error(kErrorCodeOther);
|
||||
return;
|
||||
} else {
|
||||
CONNECTION_LOG_INFO(
|
||||
"No bytes read, but bytes available was true...");
|
||||
break;
|
||||
}
|
||||
} while (_socket
|
||||
&& _socket->isConnected()
|
||||
&& _socket->hasBytesAvailable());
|
||||
}
|
||||
|
||||
mtpBuffer TcpConnection::parsePacket(bytes::const_span bytes) {
|
||||
const auto packet = _protocol->readPacket(bytes);
|
||||
CONNECTION_LOG_INFO(u"Packet received, size = %1."_q.arg(packet.size()));
|
||||
const auto ints = gsl::make_span(
|
||||
reinterpret_cast<const mtpPrime*>(packet.data()),
|
||||
packet.size() / sizeof(mtpPrime));
|
||||
Assert(!ints.empty());
|
||||
if (ints.size() < 3) {
|
||||
// nop or error or new quickack, latter is not yet supported.
|
||||
if (ints[0] != 0) {
|
||||
CONNECTION_LOG_ERROR(u"Error packet received, code = %1"_q
|
||||
.arg(ints[0]));
|
||||
}
|
||||
return mtpBuffer(1, ints[0]);
|
||||
}
|
||||
auto result = mtpBuffer(ints.size());
|
||||
memcpy(result.data(), ints.data(), ints.size() * sizeof(mtpPrime));
|
||||
return result;
|
||||
}
|
||||
|
||||
void TcpConnection::socketConnected() {
|
||||
Expects(_status == Status::Waiting);
|
||||
|
||||
auto buffer = preparePQFake(_checkNonce);
|
||||
|
||||
CONNECTION_LOG_INFO("Sending fake req_pq.");
|
||||
|
||||
_pingTime = crl::now();
|
||||
sendData(std::move(buffer));
|
||||
}
|
||||
|
||||
void TcpConnection::socketDisconnected() {
|
||||
if (_status == Status::Waiting || _status == Status::Ready) {
|
||||
disconnected();
|
||||
}
|
||||
}
|
||||
|
||||
void TcpConnection::sendData(mtpBuffer &&buffer) {
|
||||
Expects(buffer.size() > 2);
|
||||
|
||||
if (!_socket) {
|
||||
return;
|
||||
}
|
||||
char connectionStartPrefixBytes[kConnectionStartPrefixSize];
|
||||
const auto connectionStartPrefix = prepareConnectionStartPrefix(
|
||||
bytes::make_span(connectionStartPrefixBytes));
|
||||
|
||||
// buffer: 2 available int-s + data + available int.
|
||||
const auto bytes = _protocol->finalizePacket(buffer);
|
||||
CONNECTION_LOG_INFO(u"TCP Info: write packet %1 bytes."_q
|
||||
.arg(bytes.size()));
|
||||
aesCtrEncrypt(bytes, _sendKey, &_sendState);
|
||||
_socket->write(connectionStartPrefix, bytes);
|
||||
}
|
||||
|
||||
bytes::const_span TcpConnection::prepareConnectionStartPrefix(
|
||||
bytes::span buffer) {
|
||||
Expects(_socket != nullptr);
|
||||
Expects(_protocol != nullptr);
|
||||
|
||||
if (_connectionStarted) {
|
||||
return {};
|
||||
}
|
||||
_connectionStarted = true;
|
||||
|
||||
// prepare random part
|
||||
char nonceBytes[64];
|
||||
const auto nonce = bytes::make_span(nonceBytes);
|
||||
do {
|
||||
bytes::set_random(nonce);
|
||||
} while (!_socket->isGoodStartNonce(nonce));
|
||||
|
||||
// prepare encryption key/iv
|
||||
_protocol->prepareKey(
|
||||
bytes::make_span(_sendKey),
|
||||
nonce.subspan(8, CTRState::KeySize));
|
||||
bytes::copy(
|
||||
bytes::make_span(_sendState.ivec),
|
||||
nonce.subspan(8 + CTRState::KeySize, CTRState::IvecSize));
|
||||
|
||||
// prepare decryption key/iv
|
||||
auto reversedBytes = bytes::vector(48);
|
||||
const auto reversed = bytes::make_span(reversedBytes);
|
||||
bytes::copy(reversed, nonce.subspan(8, reversed.size()));
|
||||
std::reverse(reversed.begin(), reversed.end());
|
||||
_protocol->prepareKey(
|
||||
bytes::make_span(_receiveKey),
|
||||
reversed.subspan(0, CTRState::KeySize));
|
||||
bytes::copy(
|
||||
bytes::make_span(_receiveState.ivec),
|
||||
reversed.subspan(CTRState::KeySize, CTRState::IvecSize));
|
||||
|
||||
// write protocol and dc ids
|
||||
const auto protocol = reinterpret_cast<uint32*>(nonce.data() + 56);
|
||||
*protocol = _protocol->id();
|
||||
const auto dcId = reinterpret_cast<int16*>(nonce.data() + 60);
|
||||
*dcId = _protocolDcId;
|
||||
|
||||
bytes::copy(buffer, nonce.subspan(0, 56));
|
||||
aesCtrEncrypt(nonce, _sendKey, &_sendState);
|
||||
bytes::copy(buffer.subspan(56), nonce.subspan(56));
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
void TcpConnection::disconnectFromServer() {
|
||||
if (_status == Status::Finished) {
|
||||
return;
|
||||
}
|
||||
_status = Status::Finished;
|
||||
_connectedLifetime.destroy();
|
||||
_lifetime.destroy();
|
||||
_socket = nullptr;
|
||||
}
|
||||
|
||||
void TcpConnection::connectToServer(
|
||||
const QString &address,
|
||||
int port,
|
||||
const bytes::vector &protocolSecret,
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) {
|
||||
Expects(_address.isEmpty());
|
||||
Expects(_port == 0);
|
||||
Expects(_protocol == nullptr);
|
||||
Expects(_protocolDcId == 0);
|
||||
|
||||
const auto secret = (_proxy.type == ProxyData::Type::Mtproto)
|
||||
? _proxy.secretFromMtprotoPassword()
|
||||
: protocolSecret;
|
||||
if (_proxy.type == ProxyData::Type::Mtproto) {
|
||||
_address = _proxy.host;
|
||||
_port = _proxy.port;
|
||||
_protocol = Protocol::Create(secret);
|
||||
} else {
|
||||
_address = address;
|
||||
_port = port;
|
||||
_protocol = Protocol::Create(secret);
|
||||
}
|
||||
_socket = AbstractSocket::Create(
|
||||
thread(),
|
||||
secret,
|
||||
ToNetworkProxy(_proxy),
|
||||
protocolForFiles);
|
||||
_protocolDcId = protocolDcId;
|
||||
|
||||
const auto postfix = _socket->debugPostfix();
|
||||
_debugId = u"%1(dc:%2,%3%4:%5%6)"_q
|
||||
.arg(_debugId.toInt())
|
||||
.arg(
|
||||
ProtocolDcDebugId(_protocolDcId),
|
||||
(_proxy.type == ProxyData::Type::Mtproto) ? "mtproxy " : "",
|
||||
_address)
|
||||
.arg(_port)
|
||||
.arg(postfix.isEmpty() ? _protocol->debugPostfix() : postfix);
|
||||
_socket->setDebugId(_debugId);
|
||||
|
||||
CONNECTION_LOG_INFO("Connecting...");
|
||||
|
||||
_socket->connected(
|
||||
) | rpl::on_next([=] {
|
||||
socketConnected();
|
||||
}, _connectedLifetime);
|
||||
|
||||
_socket->disconnected(
|
||||
) | rpl::on_next([=] {
|
||||
socketDisconnected();
|
||||
}, _lifetime);
|
||||
|
||||
_socket->readyRead(
|
||||
) | rpl::on_next([=] {
|
||||
socketRead();
|
||||
}, _lifetime);
|
||||
|
||||
_socket->error(
|
||||
) | rpl::on_next([=] {
|
||||
socketError();
|
||||
}, _lifetime);
|
||||
|
||||
_socket->syncTimeRequests(
|
||||
) | rpl::on_next([=] {
|
||||
syncTimeRequest();
|
||||
}, _lifetime);
|
||||
|
||||
_socket->connectToHost(_address, _port);
|
||||
}
|
||||
|
||||
crl::time TcpConnection::pingTime() const {
|
||||
return isConnected() ? _pingTime : crl::time(0);
|
||||
}
|
||||
|
||||
crl::time TcpConnection::fullConnectTimeout() const {
|
||||
return kFullConnectionTimeout;
|
||||
}
|
||||
|
||||
void TcpConnection::socketPacket(bytes::const_span bytes) {
|
||||
Expects(_socket != nullptr);
|
||||
|
||||
// old quickack?..
|
||||
const auto data = parsePacket(bytes);
|
||||
if (data.size() == 1) {
|
||||
if (data[0] != 0) {
|
||||
error(data[0]);
|
||||
} else {
|
||||
// nop
|
||||
}
|
||||
//} else if (data.size() == 2) {
|
||||
// new quickack?..
|
||||
} else if (_status == Status::Ready) {
|
||||
_receivedQueue.push_back(data);
|
||||
receivedData();
|
||||
} else if (_status == Status::Waiting) {
|
||||
if (const auto res_pq = readPQFakeReply(data)) {
|
||||
const auto &data = res_pq->c_resPQ();
|
||||
if (data.vnonce() == _checkNonce) {
|
||||
CONNECTION_LOG_INFO("Valid pq response by TCP.");
|
||||
_status = Status::Ready;
|
||||
_connectedLifetime.destroy();
|
||||
_pingTime = (crl::now() - _pingTime);
|
||||
connected();
|
||||
} else {
|
||||
CONNECTION_LOG_ERROR(
|
||||
"Wrong nonce received in TCP fake pq-responce");
|
||||
error(kErrorCodeOther);
|
||||
}
|
||||
} else {
|
||||
CONNECTION_LOG_ERROR("Could not parse TCP fake pq-responce");
|
||||
error(kErrorCodeOther);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TcpConnection::timedOut() {
|
||||
if (_socket) {
|
||||
_socket->timedOut();
|
||||
}
|
||||
}
|
||||
|
||||
bool TcpConnection::isConnected() const {
|
||||
return (_status == Status::Ready);
|
||||
}
|
||||
|
||||
int32 TcpConnection::debugState() const {
|
||||
return _socket ? _socket->debugState() : -1;
|
||||
}
|
||||
|
||||
QString TcpConnection::transport() const {
|
||||
if (!isConnected()) {
|
||||
return QString();
|
||||
}
|
||||
auto result = u"TCP"_q;
|
||||
if (qthelp::is_ipv6(_address)) {
|
||||
result += u"/IPv6"_q;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString TcpConnection::tag() const {
|
||||
auto result = u"TCP"_q;
|
||||
if (qthelp::is_ipv6(_address)) {
|
||||
result += u"/IPv6"_q;
|
||||
} else {
|
||||
result += u"/IPv4"_q;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void TcpConnection::socketError() {
|
||||
if (!_socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
error(kErrorCodeOther);
|
||||
}
|
||||
|
||||
TcpConnection::~TcpConnection() = default;
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
102
Telegram/SourceFiles/mtproto/connection_tcp.h
Normal file
102
Telegram/SourceFiles/mtproto/connection_tcp.h
Normal file
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/connection_abstract.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
|
||||
class AbstractSocket;
|
||||
|
||||
class TcpConnection : public AbstractConnection {
|
||||
public:
|
||||
TcpConnection(
|
||||
not_null<Instance*> instance,
|
||||
QThread *thread,
|
||||
const ProxyData &proxy);
|
||||
|
||||
ConnectionPointer clone(const ProxyData &proxy) override;
|
||||
|
||||
crl::time pingTime() const override;
|
||||
crl::time fullConnectTimeout() const override;
|
||||
void sendData(mtpBuffer &&buffer) override;
|
||||
void disconnectFromServer() override;
|
||||
void connectToServer(
|
||||
const QString &address,
|
||||
int port,
|
||||
const bytes::vector &protocolSecret,
|
||||
int16 protocolDcId,
|
||||
bool protocolForFiles) override;
|
||||
void timedOut() override;
|
||||
bool isConnected() const override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
QString transport() const override;
|
||||
QString tag() const override;
|
||||
|
||||
~TcpConnection();
|
||||
|
||||
private:
|
||||
enum class Status {
|
||||
Waiting = 0,
|
||||
Ready,
|
||||
Finished,
|
||||
};
|
||||
|
||||
void socketRead();
|
||||
bytes::const_span prepareConnectionStartPrefix(bytes::span buffer);
|
||||
|
||||
void socketPacket(bytes::const_span bytes);
|
||||
|
||||
void socketConnected();
|
||||
void socketDisconnected();
|
||||
void socketError();
|
||||
|
||||
mtpBuffer parsePacket(bytes::const_span bytes);
|
||||
void ensureAvailableInBuffer(int amount);
|
||||
static uint32 fourCharsToUInt(char ch1, char ch2, char ch3, char ch4) {
|
||||
char ch[4] = { ch1, ch2, ch3, ch4 };
|
||||
return *reinterpret_cast<uint32*>(ch);
|
||||
}
|
||||
|
||||
const not_null<Instance*> _instance;
|
||||
std::unique_ptr<AbstractSocket> _socket;
|
||||
bool _connectionStarted = false;
|
||||
|
||||
int _offsetBytes = 0;
|
||||
int _readBytes = 0;
|
||||
int _leftBytes = 0;
|
||||
bytes::vector _smallBuffer;
|
||||
bytes::vector _largeBuffer;
|
||||
bool _usingLargeBuffer = false;
|
||||
|
||||
uchar _sendKey[CTRState::KeySize];
|
||||
CTRState _sendState;
|
||||
uchar _receiveKey[CTRState::KeySize];
|
||||
CTRState _receiveState;
|
||||
class Protocol;
|
||||
std::unique_ptr<Protocol> _protocol;
|
||||
int16 _protocolDcId = 0;
|
||||
|
||||
Status _status = Status::Waiting;
|
||||
MTPint128 _checkNonce;
|
||||
|
||||
QString _address;
|
||||
int32 _port = 0;
|
||||
crl::time _pingTime = 0;
|
||||
|
||||
rpl::lifetime _connectedLifetime;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
317
Telegram/SourceFiles/mtproto/core_types.h
Normal file
317
Telegram/SourceFiles/mtproto/core_types.h
Normal file
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/basic_types.h"
|
||||
#include "base/match_method.h"
|
||||
#include "base/flags.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/algorithm.h"
|
||||
#include "base/assertion.h"
|
||||
#include "tl/tl_basic_types.h"
|
||||
#include "tl/tl_boxed.h"
|
||||
|
||||
#include <QtCore/QVector>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <range/v3/range/conversion.hpp>
|
||||
#include <gsl/gsl>
|
||||
|
||||
using mtpPrime = int32;
|
||||
using mtpRequestId = int32;
|
||||
using mtpMsgId = uint64;
|
||||
using mtpPingId = uint64;
|
||||
|
||||
using mtpBuffer = QVector<mtpPrime>;
|
||||
using mtpTypeId = uint32;
|
||||
|
||||
struct NotSingleDataTypePlaceholder {
|
||||
};
|
||||
|
||||
namespace MTP {
|
||||
|
||||
// type DcId represents actual data center id, while in most cases
|
||||
// we use some shifted ids, like DcId() + X * DCShift
|
||||
using DcId = int32;
|
||||
using ShiftedDcId = int32;
|
||||
|
||||
constexpr auto kDcShift = ShiftedDcId(10000);
|
||||
constexpr auto kConfigDcShift = 0x01;
|
||||
constexpr auto kLogoutDcShift = 0x02;
|
||||
constexpr auto kUpdaterDcShift = 0x03;
|
||||
constexpr auto kExportDcShift = 0x04;
|
||||
constexpr auto kExportMediaDcShift = 0x05;
|
||||
constexpr auto kGroupCallStreamDcShift = 0x06;
|
||||
constexpr auto kStatsDcShift = 0x07;
|
||||
constexpr auto kMaxMediaDcCount = 0x10;
|
||||
constexpr auto kBaseDownloadDcShift = 0x10;
|
||||
constexpr auto kBaseUploadDcShift = 0x20;
|
||||
constexpr auto kDestroyKeyStartDcShift = 0x100;
|
||||
|
||||
constexpr DcId BareDcId(ShiftedDcId shiftedDcId) {
|
||||
return (shiftedDcId % kDcShift);
|
||||
}
|
||||
|
||||
constexpr ShiftedDcId ShiftDcId(DcId dcId, int value) {
|
||||
return dcId + kDcShift * value;
|
||||
}
|
||||
|
||||
constexpr int GetDcIdShift(ShiftedDcId shiftedDcId) {
|
||||
return shiftedDcId / kDcShift;
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
|
||||
enum {
|
||||
// core types
|
||||
mtpc_int = tl::id_int,
|
||||
mtpc_long = tl::id_long,
|
||||
mtpc_int128 = tl::id_int128,
|
||||
mtpc_int256 = tl::id_int256,
|
||||
mtpc_double = tl::id_double,
|
||||
mtpc_string = tl::id_string,
|
||||
mtpc_vector = tl::id_vector,
|
||||
mtpc_bytes = tl::id_bytes,
|
||||
mtpc_flags = tl::id_flags,
|
||||
mtpc_flags64 = tl::id_flags64,
|
||||
|
||||
// layers
|
||||
mtpc_invokeWithLayer1 = 0x53835315,
|
||||
mtpc_invokeWithLayer2 = 0x289dd1f6,
|
||||
mtpc_invokeWithLayer3 = 0xb7475268,
|
||||
mtpc_invokeWithLayer4 = 0xdea0d430,
|
||||
mtpc_invokeWithLayer5 = 0x417a57ae,
|
||||
mtpc_invokeWithLayer6 = 0x3a64d54d,
|
||||
mtpc_invokeWithLayer7 = 0xa5be56d3,
|
||||
mtpc_invokeWithLayer8 = 0xe9abd9fd,
|
||||
mtpc_invokeWithLayer9 = 0x76715a63,
|
||||
mtpc_invokeWithLayer10 = 0x39620c41,
|
||||
mtpc_invokeWithLayer11 = 0xa6b88fdf,
|
||||
mtpc_invokeWithLayer12 = 0xdda60d3c,
|
||||
mtpc_invokeWithLayer13 = 0x427c8ea2,
|
||||
mtpc_invokeWithLayer14 = 0x2b9b08fa,
|
||||
mtpc_invokeWithLayer15 = 0xb4418b64,
|
||||
mtpc_invokeWithLayer16 = 0xcf5f0987,
|
||||
mtpc_invokeWithLayer17 = 0x50858a19,
|
||||
mtpc_invokeWithLayer18 = 0x1c900537,
|
||||
|
||||
// manually parsed
|
||||
mtpc_rpc_result = 0xf35c6d01,
|
||||
mtpc_msg_container = 0x73f1f8dc,
|
||||
// mtpc_msg_copy = 0xe06046b2,
|
||||
mtpc_gzip_packed = 0x3072cfa1
|
||||
};
|
||||
static const mtpTypeId mtpc_core_message = -1; // undefined type, but is used
|
||||
static const mtpTypeId mtpLayers[] = {
|
||||
mtpTypeId(mtpc_invokeWithLayer1),
|
||||
mtpTypeId(mtpc_invokeWithLayer2),
|
||||
mtpTypeId(mtpc_invokeWithLayer3),
|
||||
mtpTypeId(mtpc_invokeWithLayer4),
|
||||
mtpTypeId(mtpc_invokeWithLayer5),
|
||||
mtpTypeId(mtpc_invokeWithLayer6),
|
||||
mtpTypeId(mtpc_invokeWithLayer7),
|
||||
mtpTypeId(mtpc_invokeWithLayer8),
|
||||
mtpTypeId(mtpc_invokeWithLayer9),
|
||||
mtpTypeId(mtpc_invokeWithLayer10),
|
||||
mtpTypeId(mtpc_invokeWithLayer11),
|
||||
mtpTypeId(mtpc_invokeWithLayer12),
|
||||
mtpTypeId(mtpc_invokeWithLayer13),
|
||||
mtpTypeId(mtpc_invokeWithLayer14),
|
||||
mtpTypeId(mtpc_invokeWithLayer15),
|
||||
mtpTypeId(mtpc_invokeWithLayer16),
|
||||
mtpTypeId(mtpc_invokeWithLayer17),
|
||||
mtpTypeId(mtpc_invokeWithLayer18),
|
||||
};
|
||||
static const uint32 mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
|
||||
|
||||
using MTPint = tl::int_type;
|
||||
|
||||
inline MTPint MTP_int(int32 v) {
|
||||
return tl::make_int(v);
|
||||
}
|
||||
|
||||
template <typename Flags>
|
||||
using MTPflags = tl::flags_type<Flags>;
|
||||
|
||||
template <typename T>
|
||||
inline MTPflags<base::flags<T>> MTP_flags(base::flags<T> v) {
|
||||
return tl::make_flags(v);
|
||||
}
|
||||
|
||||
template <typename T, typename = std::enable_if_t<!std::is_same<T, int>::value>>
|
||||
inline MTPflags<base::flags<T>> MTP_flags(T v) {
|
||||
return tl::make_flags(v);
|
||||
}
|
||||
|
||||
inline tl::details::zero_flags_helper MTP_flags(void(tl::details::zero_flags_helper::*)()) {
|
||||
return tl::details::zero_flags_helper();
|
||||
}
|
||||
|
||||
using MTPlong = tl::long_type;
|
||||
|
||||
inline MTPlong MTP_long(uint64 v) {
|
||||
return tl::make_long(v);
|
||||
}
|
||||
|
||||
using MTPint128 = tl::int128_type;
|
||||
|
||||
inline MTPint128 MTP_int128(uint64 l, uint64 h) {
|
||||
return tl::make_int128(l, h);
|
||||
}
|
||||
|
||||
using MTPint256 = tl::int256_type;
|
||||
|
||||
inline MTPint256 MTP_int256(const MTPint128 &l, const MTPint128 &h) {
|
||||
return tl::make_int256(l, h);
|
||||
}
|
||||
|
||||
using MTPdouble = tl::double_type;
|
||||
|
||||
inline MTPdouble MTP_double(float64 v) {
|
||||
return tl::make_double(v);
|
||||
}
|
||||
|
||||
using MTPstring = tl::string_type;
|
||||
using MTPbytes = tl::bytes_type;
|
||||
|
||||
inline MTPstring MTP_string(const std::string &v) {
|
||||
return tl::make_string(v);
|
||||
}
|
||||
inline MTPstring MTP_string(const QString &v) {
|
||||
return tl::make_string(v);
|
||||
}
|
||||
inline MTPstring MTP_string(const char *v) {
|
||||
return tl::make_string(v);
|
||||
}
|
||||
inline MTPstring MTP_string() {
|
||||
return tl::make_string();
|
||||
}
|
||||
MTPstring MTP_string(const QByteArray &v) = delete;
|
||||
|
||||
inline MTPbytes MTP_bytes(const QByteArray &v) {
|
||||
return tl::make_bytes(v);
|
||||
}
|
||||
inline MTPbytes MTP_bytes(QByteArray &&v) {
|
||||
return tl::make_bytes(std::move(v));
|
||||
}
|
||||
inline MTPbytes MTP_bytes() {
|
||||
return tl::make_bytes();
|
||||
}
|
||||
inline MTPbytes MTP_bytes(bytes::const_span buffer) {
|
||||
return tl::make_bytes(buffer);
|
||||
}
|
||||
inline MTPbytes MTP_bytes(const bytes::vector &buffer) {
|
||||
return tl::make_bytes(buffer);
|
||||
}
|
||||
|
||||
inline QString qs(const MTPstring &v) {
|
||||
return tl::utf16(v);
|
||||
}
|
||||
|
||||
inline QString qs(const QByteArray &v) {
|
||||
return tl::utf16(v);
|
||||
}
|
||||
|
||||
inline QByteArray qba(const MTPstring &v) {
|
||||
return tl::utf8(v);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
using MTPvector = tl::vector_type<T>;
|
||||
|
||||
template <typename T>
|
||||
inline MTPvector<T> MTP_vector(uint32 count) {
|
||||
return tl::make_vector<T>(count);
|
||||
}
|
||||
template <typename T>
|
||||
inline MTPvector<T> MTP_vector(uint32 count, const T &value) {
|
||||
return tl::make_vector<T>(count, value);
|
||||
}
|
||||
template <typename T>
|
||||
inline MTPvector<T> MTP_vector(const QVector<T> &v) {
|
||||
return tl::make_vector<T>(v);
|
||||
}
|
||||
template <typename T>
|
||||
inline MTPvector<T> MTP_vector(QVector<T> &&v) {
|
||||
return tl::make_vector<T>(std::move(v));
|
||||
}
|
||||
template <typename T>
|
||||
inline MTPvector<T> MTP_vector() {
|
||||
return tl::make_vector<T>();
|
||||
}
|
||||
|
||||
// ranges::to<QVector> doesn't work with Qt 6 in Clang,
|
||||
// because QVector is a type alias for QList there.
|
||||
template <typename Rng>
|
||||
inline auto MTP_vector_from_range(Rng &&range) {
|
||||
using T = std::remove_cvref_t<decltype(*ranges::begin(range))>;
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0 ,0)
|
||||
return MTP_vector<T>(std::forward<Rng>(range) | ranges::to<QList>());
|
||||
#else // QT_VERSION >= 6.0
|
||||
return MTP_vector<T>(std::forward<Rng>(range) | ranges::to<QVector>());
|
||||
#endif // QT_VERSION < 6.0
|
||||
}
|
||||
|
||||
namespace tl {
|
||||
|
||||
template <typename Accumulator>
|
||||
struct Writer;
|
||||
|
||||
template <typename Prime>
|
||||
struct Reader;
|
||||
|
||||
template <>
|
||||
struct Writer<mtpBuffer> {
|
||||
static void PutBytes(mtpBuffer &to, const void *bytes, uint32 count) {
|
||||
constexpr auto kPrime = sizeof(uint32);
|
||||
const auto primes = (count / kPrime) + (count % kPrime ? 1 : 0);
|
||||
const auto size = to.size();
|
||||
to.resize(size + primes);
|
||||
memcpy(to.data() + size, bytes, count);
|
||||
}
|
||||
static void Put(mtpBuffer &to, uint32 value) {
|
||||
to.push_back(mtpPrime(value));
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Reader<mtpPrime> final {
|
||||
[[nodiscard]] static bool HasBytes(
|
||||
uint32 count,
|
||||
const mtpPrime *from,
|
||||
const mtpPrime *end) {
|
||||
constexpr auto kPrime = sizeof(uint32);
|
||||
const auto primes = (count / kPrime) + (count % kPrime ? 1 : 0);
|
||||
return (end - from) >= primes;
|
||||
}
|
||||
static void GetBytes(
|
||||
void *bytes,
|
||||
uint32 count,
|
||||
const mtpPrime *&from,
|
||||
const mtpPrime *end) {
|
||||
Expects(HasBytes(count, from, end));
|
||||
|
||||
constexpr auto kPrime = sizeof(uint32);
|
||||
const auto primes = (count / kPrime) + (count % kPrime ? 1 : 0);
|
||||
memcpy(bytes, from, count);
|
||||
from += primes;
|
||||
}
|
||||
[[nodiscard]] static bool Has(
|
||||
uint32 primes,
|
||||
const mtpPrime *from,
|
||||
const mtpPrime *end) {
|
||||
return (end - from) >= primes;
|
||||
}
|
||||
[[nodiscard]] static uint32 Get(const mtpPrime *&from, const mtpPrime *end) {
|
||||
Expects(from < end);
|
||||
|
||||
return uint32(*from++);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace tl
|
||||
471
Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp
Normal file
471
Telegram/SourceFiles/mtproto/dedicated_file_loader.cpp
Normal file
@@ -0,0 +1,471 @@
|
||||
/*
|
||||
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/dedicated_file_loader.h"
|
||||
|
||||
#include "mtproto/facade.h"
|
||||
#include "main/main_account.h" // Account::sessionChanges.
|
||||
#include "main/main_session.h" // Session::account.
|
||||
#include "core/application.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace {
|
||||
|
||||
std::optional<MTPInputChannel> ExtractChannel(
|
||||
const MTPcontacts_ResolvedPeer &result) {
|
||||
const auto &data = result.c_contacts_resolvedPeer();
|
||||
if (const auto peer = peerFromMTP(data.vpeer())) {
|
||||
for (const auto &chat : data.vchats().v) {
|
||||
if (chat.type() == mtpc_channel) {
|
||||
const auto &channel = chat.c_channel();
|
||||
if (peer == peerFromChannel(channel.vid())) {
|
||||
return MTP_inputChannel(
|
||||
channel.vid(),
|
||||
MTP_long(channel.vaccess_hash().value_or_empty()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<DedicatedLoader::File> ParseFile(
|
||||
const MTPmessages_Messages &result) {
|
||||
const auto message = GetMessagesElement(result);
|
||||
if (!message || message->type() != mtpc_message) {
|
||||
LOG(("Update Error: MTP file message not found."));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto &data = message->c_message();
|
||||
const auto media = data.vmedia();
|
||||
if (!media || media->type() != mtpc_messageMediaDocument) {
|
||||
LOG(("Update Error: MTP file media not found."));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto &inner = media->c_messageMediaDocument();
|
||||
const auto document = inner.vdocument();
|
||||
if (!document || document->type() != mtpc_document) {
|
||||
LOG(("Update Error: MTP file not found."));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto &fields = document->c_document();
|
||||
const auto name = [&] {
|
||||
for (const auto &attribute : fields.vattributes().v) {
|
||||
if (attribute.type() == mtpc_documentAttributeFilename) {
|
||||
const auto &data = attribute.c_documentAttributeFilename();
|
||||
return qs(data.vfile_name());
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}();
|
||||
if (name.isEmpty()) {
|
||||
LOG(("Update Error: MTP file name not found."));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto size = int64(fields.vsize().v);
|
||||
if (size <= 0) {
|
||||
LOG(("Update Error: MTP file size is invalid."));
|
||||
return std::nullopt;
|
||||
}
|
||||
const auto location = MTP_inputDocumentFileLocation(
|
||||
fields.vid(),
|
||||
fields.vaccess_hash(),
|
||||
fields.vfile_reference(),
|
||||
MTP_string());
|
||||
return DedicatedLoader::File{ name, size, fields.vdc_id().v, location };
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WeakInstance::WeakInstance(base::weak_ptr<Main::Session> session)
|
||||
: _session(session)
|
||||
, _instance(_session ? &_session->account().mtp() : nullptr) {
|
||||
if (!valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(_instance, &QObject::destroyed, this, [=] {
|
||||
_instance = nullptr;
|
||||
_session = nullptr;
|
||||
die();
|
||||
});
|
||||
_session->account().sessionChanges(
|
||||
) | rpl::filter([](Main::Session *session) {
|
||||
return !session;
|
||||
}) | rpl::on_next([=] {
|
||||
die();
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
base::weak_ptr<Main::Session> WeakInstance::session() const {
|
||||
return _session;
|
||||
}
|
||||
|
||||
bool WeakInstance::valid() const {
|
||||
return (_session != nullptr);
|
||||
}
|
||||
|
||||
Instance *WeakInstance::instance() const {
|
||||
return _instance;
|
||||
}
|
||||
|
||||
void WeakInstance::die() {
|
||||
for (const auto &[requestId, fail] : base::take(_requests)) {
|
||||
if (_instance) {
|
||||
_instance->cancel(requestId);
|
||||
}
|
||||
fail(Error::Local(
|
||||
"UNAVAILABLE",
|
||||
"MTP instance is not available."));
|
||||
}
|
||||
}
|
||||
|
||||
bool WeakInstance::removeRequest(mtpRequestId requestId) {
|
||||
if (const auto i = _requests.find(requestId); i != end(_requests)) {
|
||||
_requests.erase(i);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WeakInstance::reportUnavailable(
|
||||
Fn<void(const Error &error)> callback) {
|
||||
InvokeQueued(this, [=] {
|
||||
callback(Error::Local(
|
||||
"UNAVAILABLE",
|
||||
"MTP instance is not available."));
|
||||
});
|
||||
}
|
||||
|
||||
WeakInstance::~WeakInstance() {
|
||||
if (_instance) {
|
||||
for (const auto &[requestId, fail] : base::take(_requests)) {
|
||||
_instance->cancel(requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AbstractDedicatedLoader::AbstractDedicatedLoader(
|
||||
const QString &filepath,
|
||||
int chunkSize)
|
||||
: _filepath(filepath)
|
||||
, _chunkSize(chunkSize) {
|
||||
}
|
||||
|
||||
void AbstractDedicatedLoader::start() {
|
||||
if (!validateOutput()
|
||||
|| (!_output.isOpen() && !_output.open(QIODevice::Append))) {
|
||||
QFile(_filepath).remove();
|
||||
threadSafeFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(("Update Info: Starting loading '%1' from %2 offset."
|
||||
).arg(_filepath
|
||||
).arg(alreadySize()));
|
||||
startLoading();
|
||||
}
|
||||
|
||||
int64 AbstractDedicatedLoader::alreadySize() const {
|
||||
QMutexLocker lock(&_sizesMutex);
|
||||
return _alreadySize;
|
||||
}
|
||||
|
||||
int64 AbstractDedicatedLoader::totalSize() const {
|
||||
QMutexLocker lock(&_sizesMutex);
|
||||
return _totalSize;
|
||||
}
|
||||
|
||||
rpl::producer<QString> AbstractDedicatedLoader::ready() const {
|
||||
return _ready.events();
|
||||
}
|
||||
|
||||
auto AbstractDedicatedLoader::progress() const -> rpl::producer<Progress> {
|
||||
return _progress.events();
|
||||
}
|
||||
|
||||
rpl::producer<> AbstractDedicatedLoader::failed() const {
|
||||
return _failed.events();
|
||||
}
|
||||
|
||||
void AbstractDedicatedLoader::wipeFolder() {
|
||||
QFileInfo info(_filepath);
|
||||
const auto dir = info.dir();
|
||||
const auto all = dir.entryInfoList(QDir::Files);
|
||||
for (auto i = all.begin(), e = all.end(); i != e; ++i) {
|
||||
if (i->absoluteFilePath() != info.absoluteFilePath()) {
|
||||
QFile::remove(i->absoluteFilePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AbstractDedicatedLoader::validateOutput() {
|
||||
if (_filepath.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QFileInfo info(_filepath);
|
||||
const auto dir = info.dir();
|
||||
if (!dir.exists()) {
|
||||
dir.mkdir(dir.absolutePath());
|
||||
}
|
||||
_output.setFileName(_filepath);
|
||||
|
||||
if (!info.exists()) {
|
||||
return true;
|
||||
}
|
||||
const auto fullSize = info.size();
|
||||
if (fullSize < _chunkSize || fullSize > kMaxFileSize) {
|
||||
return _output.remove();
|
||||
}
|
||||
const auto goodSize = int64((fullSize % _chunkSize)
|
||||
? (fullSize - (fullSize % _chunkSize))
|
||||
: fullSize);
|
||||
if (_output.resize(goodSize)) {
|
||||
_alreadySize = goodSize;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AbstractDedicatedLoader::threadSafeProgress(Progress progress) {
|
||||
crl::on_main(this, [=] {
|
||||
_progress.fire_copy(progress);
|
||||
});
|
||||
}
|
||||
|
||||
void AbstractDedicatedLoader::threadSafeReady() {
|
||||
crl::on_main(this, [=] {
|
||||
_ready.fire_copy(_filepath);
|
||||
});
|
||||
}
|
||||
|
||||
void AbstractDedicatedLoader::threadSafeFailed() {
|
||||
crl::on_main(this, [=] {
|
||||
_failed.fire({});
|
||||
});
|
||||
}
|
||||
|
||||
void AbstractDedicatedLoader::writeChunk(bytes::const_span data, int totalSize) {
|
||||
const auto size = data.size();
|
||||
if (size > 0) {
|
||||
const auto written = _output.write(QByteArray::fromRawData(
|
||||
reinterpret_cast<const char*>(data.data()),
|
||||
size));
|
||||
if (written != size) {
|
||||
threadSafeFailed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const auto progress = [&] {
|
||||
QMutexLocker lock(&_sizesMutex);
|
||||
if (!_totalSize) {
|
||||
_totalSize = totalSize;
|
||||
}
|
||||
_alreadySize += size;
|
||||
return Progress { _alreadySize, _totalSize };
|
||||
}();
|
||||
|
||||
if (progress.size > 0 && progress.already >= progress.size) {
|
||||
_output.close();
|
||||
threadSafeReady();
|
||||
} else {
|
||||
threadSafeProgress(progress);
|
||||
}
|
||||
}
|
||||
|
||||
rpl::lifetime &AbstractDedicatedLoader::lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
||||
DedicatedLoader::DedicatedLoader(
|
||||
base::weak_ptr<Main::Session> session,
|
||||
const QString &folder,
|
||||
const File &file)
|
||||
: AbstractDedicatedLoader(folder + '/' + file.name, kChunkSize)
|
||||
, _size(file.size)
|
||||
, _dcId(file.dcId)
|
||||
, _location(file.location)
|
||||
, _mtp(session) {
|
||||
Expects(_size > 0);
|
||||
}
|
||||
|
||||
void DedicatedLoader::startLoading() {
|
||||
if (!_mtp.valid()) {
|
||||
LOG(("Update Error: MTP is unavailable."));
|
||||
threadSafeFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(("Update Info: Loading using MTP from '%1'.").arg(_dcId));
|
||||
_offset = alreadySize();
|
||||
writeChunk({}, _size);
|
||||
sendRequest();
|
||||
}
|
||||
|
||||
void DedicatedLoader::sendRequest() {
|
||||
if (_requests.size() >= kRequestsCount || _offset >= _size) {
|
||||
return;
|
||||
}
|
||||
const auto offset = _offset;
|
||||
_requests.push_back({ offset });
|
||||
_mtp.send(
|
||||
MTPupload_GetFile(
|
||||
MTP_flags(0),
|
||||
_location,
|
||||
MTP_long(offset),
|
||||
MTP_int(kChunkSize)),
|
||||
[=](const MTPupload_File &result) { gotPart(offset, result); },
|
||||
failHandler(),
|
||||
MTP::updaterDcId(_dcId));
|
||||
_offset += kChunkSize;
|
||||
|
||||
if (_requests.size() < kRequestsCount) {
|
||||
base::call_delayed(kNextRequestDelay, this, [=] { sendRequest(); });
|
||||
}
|
||||
}
|
||||
|
||||
void DedicatedLoader::gotPart(int offset, const MTPupload_File &result) {
|
||||
Expects(!_requests.empty());
|
||||
|
||||
if (result.type() == mtpc_upload_fileCdnRedirect) {
|
||||
LOG(("Update Error: MTP does not support cdn right now."));
|
||||
threadSafeFailed();
|
||||
return;
|
||||
}
|
||||
const auto &data = result.c_upload_file();
|
||||
if (data.vbytes().v.isEmpty()) {
|
||||
LOG(("Update Error: MTP empty part received."));
|
||||
threadSafeFailed();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto i = ranges::find(
|
||||
_requests,
|
||||
offset,
|
||||
[](const Request &request) { return request.offset; });
|
||||
Assert(i != end(_requests));
|
||||
|
||||
i->bytes = data.vbytes().v;
|
||||
while (!_requests.empty() && !_requests.front().bytes.isEmpty()) {
|
||||
writeChunk(bytes::make_span(_requests.front().bytes), _size);
|
||||
_requests.pop_front();
|
||||
}
|
||||
sendRequest();
|
||||
}
|
||||
|
||||
Fn<void(const Error &)> DedicatedLoader::failHandler() {
|
||||
return [=](const Error &error) {
|
||||
LOG(("Update Error: MTP load failed with '%1'"
|
||||
).arg(QString::number(error.code()) + ':' + error.type()));
|
||||
threadSafeFailed();
|
||||
};
|
||||
}
|
||||
|
||||
void ResolveChannel(
|
||||
not_null<MTP::WeakInstance*> mtp,
|
||||
const QString &username,
|
||||
Fn<void(const MTPInputChannel &channel)> done,
|
||||
Fn<void()> fail) {
|
||||
const auto failed = [&] {
|
||||
LOG(("Dedicated MTP Error: Channel '%1' resolve failed."
|
||||
).arg(username));
|
||||
fail();
|
||||
};
|
||||
const auto session = mtp->session();
|
||||
if (!mtp->valid()) {
|
||||
failed();
|
||||
return;
|
||||
}
|
||||
|
||||
struct ResolveResult {
|
||||
base::weak_ptr<Main::Session> session;
|
||||
MTPInputChannel channel;
|
||||
};
|
||||
static std::map<QString, ResolveResult> ResolveCache;
|
||||
|
||||
const auto i = ResolveCache.find(username);
|
||||
if (i != end(ResolveCache)) {
|
||||
if (i->second.session.get() == session.get()) {
|
||||
done(i->second.channel);
|
||||
return;
|
||||
}
|
||||
ResolveCache.erase(i);
|
||||
}
|
||||
|
||||
const auto doneHandler = [=](const MTPcontacts_ResolvedPeer &result) {
|
||||
Expects(result.type() == mtpc_contacts_resolvedPeer);
|
||||
|
||||
if (const auto channel = ExtractChannel(result)) {
|
||||
ResolveCache.emplace(
|
||||
username,
|
||||
ResolveResult { session, *channel });
|
||||
done(*channel);
|
||||
} else {
|
||||
failed();
|
||||
}
|
||||
};
|
||||
const auto failHandler = [=](const Error &error) {
|
||||
LOG(("Dedicated MTP Error: Resolve failed with '%1'"
|
||||
).arg(QString::number(error.code()) + ':' + error.type()));
|
||||
fail();
|
||||
};
|
||||
mtp->send(MTPcontacts_ResolveUsername(
|
||||
MTP_flags(0),
|
||||
MTP_string(username),
|
||||
MTP_string()
|
||||
), doneHandler, failHandler);
|
||||
}
|
||||
|
||||
std::optional<MTPMessage> GetMessagesElement(
|
||||
const MTPmessages_Messages &list) {
|
||||
return list.match([&](const MTPDmessages_messagesNotModified &) {
|
||||
return std::optional<MTPMessage>(std::nullopt);
|
||||
}, [&](const auto &data) {
|
||||
return data.vmessages().v.isEmpty()
|
||||
? std::nullopt
|
||||
: std::make_optional(data.vmessages().v[0]);
|
||||
});
|
||||
}
|
||||
|
||||
void StartDedicatedLoader(
|
||||
not_null<MTP::WeakInstance*> mtp,
|
||||
const DedicatedLoader::Location &location,
|
||||
const QString &folder,
|
||||
Fn<void(std::unique_ptr<DedicatedLoader>)> ready) {
|
||||
const auto doneHandler = [=](const MTPmessages_Messages &result) {
|
||||
const auto file = ParseFile(result);
|
||||
ready(file
|
||||
? std::make_unique<MTP::DedicatedLoader>(
|
||||
mtp->session(),
|
||||
folder,
|
||||
*file)
|
||||
: nullptr);
|
||||
};
|
||||
const auto failHandler = [=](const Error &error) {
|
||||
LOG(("Update Error: MTP check failed with '%1'"
|
||||
).arg(QString::number(error.code()) + ':' + error.type()));
|
||||
ready(nullptr);
|
||||
};
|
||||
|
||||
const auto &[username, postId] = location;
|
||||
ResolveChannel(mtp, username, [=, postId = postId](
|
||||
const MTPInputChannel &channel) {
|
||||
mtp->send(
|
||||
MTPchannels_GetMessages(
|
||||
channel,
|
||||
MTP_vector<MTPInputMessage>(
|
||||
1,
|
||||
MTP_inputMessageID(MTP_int(postId)))),
|
||||
doneHandler,
|
||||
failHandler);
|
||||
}, [=] { ready(nullptr); });
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
207
Telegram/SourceFiles/mtproto/dedicated_file_loader.h
Normal file
207
Telegram/SourceFiles/mtproto/dedicated_file_loader.h
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtp_instance.h"
|
||||
|
||||
namespace Main {
|
||||
class Session;
|
||||
} // namespace Main
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class WeakInstance final : private QObject {
|
||||
public:
|
||||
explicit WeakInstance(base::weak_ptr<Main::Session> session);
|
||||
|
||||
template <typename Request>
|
||||
void send(
|
||||
const Request &request,
|
||||
Fn<void(const typename Request::ResponseType &result)> done,
|
||||
Fn<void(const Error &error)> fail,
|
||||
ShiftedDcId dcId = 0);
|
||||
|
||||
[[nodiscard]] base::weak_ptr<Main::Session> session() const;
|
||||
[[nodiscard]] bool valid() const;
|
||||
[[nodiscard]] Instance *instance() const;
|
||||
|
||||
~WeakInstance();
|
||||
|
||||
private:
|
||||
void die();
|
||||
bool removeRequest(mtpRequestId requestId);
|
||||
void reportUnavailable(Fn<void(const Error &error)> callback);
|
||||
|
||||
base::weak_ptr<Main::Session> _session;
|
||||
Instance *_instance = nullptr;
|
||||
std::map<mtpRequestId, Fn<void(const Error &)>> _requests;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class AbstractDedicatedLoader : public base::has_weak_ptr {
|
||||
public:
|
||||
AbstractDedicatedLoader(const QString &filepath, int chunkSize);
|
||||
|
||||
static constexpr auto kChunkSize = 128 * 1024;
|
||||
static constexpr auto kMaxFileSize = 256 * 1024 * 1024;
|
||||
|
||||
struct Progress {
|
||||
int64 already = 0;
|
||||
int64 size = 0;
|
||||
|
||||
inline bool operator<(const Progress &other) const {
|
||||
return (already < other.already)
|
||||
|| (already == other.already && size < other.size);
|
||||
}
|
||||
inline bool operator==(const Progress &other) const {
|
||||
return (already == other.already) && (size == other.size);
|
||||
}
|
||||
};
|
||||
|
||||
void start();
|
||||
void wipeFolder();
|
||||
void wipeOutput();
|
||||
|
||||
int64 alreadySize() const;
|
||||
int64 totalSize() const;
|
||||
|
||||
rpl::producer<Progress> progress() const;
|
||||
rpl::producer<QString> ready() const;
|
||||
rpl::producer<> failed() const;
|
||||
|
||||
rpl::lifetime &lifetime();
|
||||
|
||||
virtual ~AbstractDedicatedLoader() = default;
|
||||
|
||||
protected:
|
||||
void threadSafeFailed();
|
||||
|
||||
// Single threaded.
|
||||
void writeChunk(bytes::const_span data, int totalSize);
|
||||
|
||||
private:
|
||||
virtual void startLoading() = 0;
|
||||
|
||||
bool validateOutput();
|
||||
void threadSafeProgress(Progress progress);
|
||||
void threadSafeReady();
|
||||
|
||||
QString _filepath;
|
||||
int _chunkSize = 0;
|
||||
|
||||
QFile _output;
|
||||
int64 _alreadySize = 0;
|
||||
int64 _totalSize = 0;
|
||||
mutable QMutex _sizesMutex;
|
||||
rpl::event_stream<Progress> _progress;
|
||||
rpl::event_stream<QString> _ready;
|
||||
rpl::event_stream<> _failed;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
class DedicatedLoader : public AbstractDedicatedLoader {
|
||||
public:
|
||||
struct Location {
|
||||
QString username;
|
||||
int32 postId = 0;
|
||||
};
|
||||
struct File {
|
||||
QString name;
|
||||
int64 size = 0;
|
||||
DcId dcId = 0;
|
||||
MTPInputFileLocation location;
|
||||
};
|
||||
|
||||
DedicatedLoader(
|
||||
base::weak_ptr<Main::Session> session,
|
||||
const QString &folder,
|
||||
const File &file);
|
||||
|
||||
private:
|
||||
struct Request {
|
||||
int64 offset = 0;
|
||||
QByteArray bytes;
|
||||
};
|
||||
void startLoading() override;
|
||||
void sendRequest();
|
||||
void gotPart(int offset, const MTPupload_File &result);
|
||||
Fn<void(const Error &)> failHandler();
|
||||
|
||||
static constexpr auto kRequestsCount = 2;
|
||||
static constexpr auto kNextRequestDelay = crl::time(20);
|
||||
|
||||
std::deque<Request> _requests;
|
||||
int64 _size = 0;
|
||||
int64 _offset = 0;
|
||||
DcId _dcId = 0;
|
||||
MTPInputFileLocation _location;
|
||||
WeakInstance _mtp;
|
||||
|
||||
};
|
||||
|
||||
void ResolveChannel(
|
||||
not_null<MTP::WeakInstance*> mtp,
|
||||
const QString &username,
|
||||
Fn<void(const MTPInputChannel &channel)> done,
|
||||
Fn<void()> fail);
|
||||
|
||||
std::optional<MTPMessage> GetMessagesElement(
|
||||
const MTPmessages_Messages &list);
|
||||
|
||||
void StartDedicatedLoader(
|
||||
not_null<MTP::WeakInstance*> mtp,
|
||||
const DedicatedLoader::Location &location,
|
||||
const QString &folder,
|
||||
Fn<void(std::unique_ptr<DedicatedLoader>)> ready);
|
||||
|
||||
template <typename Request>
|
||||
void WeakInstance::send(
|
||||
const Request &request,
|
||||
Fn<void(const typename Request::ResponseType &result)> done,
|
||||
Fn<void(const Error &error)> fail,
|
||||
MTP::ShiftedDcId dcId) {
|
||||
using Result = typename Request::ResponseType;
|
||||
if (!valid()) {
|
||||
reportUnavailable(fail);
|
||||
return;
|
||||
}
|
||||
const auto onDone = crl::guard((QObject*)this, [=](
|
||||
const Response &response) {
|
||||
auto result = Result();
|
||||
auto from = response.reply.constData();
|
||||
if (!result.read(from, from + response.reply.size())) {
|
||||
return false;
|
||||
}
|
||||
if (removeRequest(response.requestId)) {
|
||||
done(result);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const auto onFail = crl::guard((QObject*)this, [=](
|
||||
const Error &error,
|
||||
const Response &response) {
|
||||
if (MTP::IsDefaultHandledError(error)) {
|
||||
return false;
|
||||
}
|
||||
if (removeRequest(response.requestId)) {
|
||||
fail(error);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
const auto requestId = _instance->send(
|
||||
request,
|
||||
std::move(onDone),
|
||||
std::move(onFail),
|
||||
dcId);
|
||||
_requests.emplace(requestId, fail);
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
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/details/mtproto_abstract_socket.h"
|
||||
|
||||
#include "mtproto/details/mtproto_tcp_socket.h"
|
||||
#include "mtproto/details/mtproto_tls_socket.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
std::unique_ptr<AbstractSocket> AbstractSocket::Create(
|
||||
not_null<QThread*> thread,
|
||||
const bytes::vector &secret,
|
||||
const QNetworkProxy &proxy,
|
||||
bool protocolForFiles) {
|
||||
if (secret.size() >= 21 && secret[0] == bytes::type(0xEE)) {
|
||||
return std::make_unique<TlsSocket>(
|
||||
thread,
|
||||
secret,
|
||||
proxy,
|
||||
protocolForFiles);
|
||||
} else {
|
||||
return std::make_unique<TcpSocket>(thread, proxy, protocolForFiles);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSocket::logError(int errorCode, const QString &errorText) {
|
||||
const auto log = [&](const QString &message) {
|
||||
DEBUG_LOG(("Socket %1 Error: ").arg(_debugId) + message);
|
||||
};
|
||||
switch (errorCode) {
|
||||
case QAbstractSocket::ConnectionRefusedError:
|
||||
log(u"Socket connection refused - %1."_q.arg(errorText));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::RemoteHostClosedError:
|
||||
log(u"Remote host closed socket connection - %1."_q.arg(errorText));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::HostNotFoundError:
|
||||
log(u"Host not found - %1."_q.arg(errorText));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::SocketTimeoutError:
|
||||
log(u"Socket timeout - %1."_q.arg(errorText));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::NetworkError: {
|
||||
log(u"Network - %1."_q.arg(errorText));
|
||||
} break;
|
||||
|
||||
case QAbstractSocket::ProxyAuthenticationRequiredError:
|
||||
case QAbstractSocket::ProxyConnectionRefusedError:
|
||||
case QAbstractSocket::ProxyConnectionClosedError:
|
||||
case QAbstractSocket::ProxyConnectionTimeoutError:
|
||||
case QAbstractSocket::ProxyNotFoundError:
|
||||
case QAbstractSocket::ProxyProtocolError:
|
||||
log(u"Proxy (%1) - %2."_q.arg(errorCode).arg(errorText));
|
||||
break;
|
||||
|
||||
default:
|
||||
log(u"Other (%1) - %2."_q.arg(errorCode).arg(errorText));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/bytes.h"
|
||||
#include "base/basic_types.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
class AbstractSocket : protected QObject {
|
||||
public:
|
||||
static std::unique_ptr<AbstractSocket> Create(
|
||||
not_null<QThread*> thread,
|
||||
const bytes::vector &secret,
|
||||
const QNetworkProxy &proxy,
|
||||
bool protocolForFiles);
|
||||
|
||||
void setDebugId(const QString &id) {
|
||||
_debugId = id;
|
||||
}
|
||||
|
||||
explicit AbstractSocket(not_null<QThread*> thread) {
|
||||
moveToThread(thread);
|
||||
}
|
||||
virtual ~AbstractSocket() = default;
|
||||
|
||||
[[nodiscard]] rpl::producer<> connected() const {
|
||||
return _connected.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> disconnected() const {
|
||||
return _disconnected.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> readyRead() const {
|
||||
return _readyRead.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> error() const {
|
||||
return _error.events();
|
||||
}
|
||||
[[nodiscard]] rpl::producer<> syncTimeRequests() const {
|
||||
return _syncTimeRequests.events();
|
||||
}
|
||||
|
||||
virtual void connectToHost(const QString &address, int port) = 0;
|
||||
[[nodiscard]] virtual bool isGoodStartNonce(bytes::const_span nonce) = 0;
|
||||
virtual void timedOut() = 0;
|
||||
[[nodiscard]] virtual bool isConnected() = 0;
|
||||
[[nodiscard]] virtual bool hasBytesAvailable() = 0;
|
||||
[[nodiscard]] virtual int64 read(bytes::span buffer) = 0;
|
||||
virtual void write(
|
||||
bytes::const_span prefix,
|
||||
bytes::const_span buffer) = 0;
|
||||
|
||||
virtual int32 debugState() = 0;
|
||||
[[nodiscard]] virtual QString debugPostfix() const = 0;
|
||||
|
||||
protected:
|
||||
static const int kFilesSendBufferSize = 2 * 1024 * 1024;
|
||||
static const int kFilesReceiveBufferSize = 2 * 1024 * 1024;
|
||||
|
||||
void logError(int errorCode, const QString &errorText);
|
||||
|
||||
QString _debugId;
|
||||
rpl::event_stream<> _connected;
|
||||
rpl::event_stream<> _disconnected;
|
||||
rpl::event_stream<> _readyRead;
|
||||
rpl::event_stream<> _error;
|
||||
rpl::event_stream<> _syncTimeRequests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
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/details/mtproto_bound_key_creator.h"
|
||||
|
||||
#include "mtproto/details/mtproto_serialized_request.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
BoundKeyCreator::BoundKeyCreator(DcKeyRequest request, Delegate delegate)
|
||||
: _request(request)
|
||||
, _delegate(std::move(delegate)) {
|
||||
}
|
||||
|
||||
void BoundKeyCreator::start(
|
||||
DcId dcId,
|
||||
int16 protocolDcId,
|
||||
not_null<AbstractConnection*> connection,
|
||||
not_null<DcOptions*> dcOptions) {
|
||||
Expects(!_creator.has_value());
|
||||
|
||||
auto delegate = DcKeyCreator::Delegate();
|
||||
delegate.done = _delegate.unboundReady;
|
||||
delegate.sentSome = _delegate.sentSome;
|
||||
delegate.receivedSome = _delegate.receivedSome;
|
||||
|
||||
_creator.emplace(
|
||||
dcId,
|
||||
protocolDcId,
|
||||
connection,
|
||||
dcOptions,
|
||||
std::move(delegate),
|
||||
_request);
|
||||
}
|
||||
|
||||
void BoundKeyCreator::stop() {
|
||||
_creator = std::nullopt;
|
||||
}
|
||||
|
||||
void BoundKeyCreator::bind(AuthKeyPtr &&persistentKey) {
|
||||
stop();
|
||||
_binder.emplace(std::move(persistentKey));
|
||||
}
|
||||
|
||||
void BoundKeyCreator::restartBinder() {
|
||||
if (_binder) {
|
||||
_binder.emplace(_binder->persistentKey());
|
||||
}
|
||||
}
|
||||
|
||||
bool BoundKeyCreator::readyToBind() const {
|
||||
return _binder.has_value();
|
||||
}
|
||||
|
||||
SerializedRequest BoundKeyCreator::prepareBindRequest(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
uint64 sessionId) {
|
||||
Expects(_binder.has_value());
|
||||
|
||||
return _binder->prepareRequest(temporaryKey, sessionId);
|
||||
}
|
||||
|
||||
DcKeyBindState BoundKeyCreator::handleBindResponse(
|
||||
const mtpBuffer &response) {
|
||||
Expects(_binder.has_value());
|
||||
|
||||
return _binder->handleResponse(response);
|
||||
}
|
||||
|
||||
AuthKeyPtr BoundKeyCreator::bindPersistentKey() const {
|
||||
Expects(_binder.has_value());
|
||||
|
||||
return _binder->persistentKey();
|
||||
}
|
||||
|
||||
bool IsDestroyedTemporaryKeyError(const mtpBuffer &buffer) {
|
||||
auto from = buffer.data();
|
||||
auto error = MTPRpcError();
|
||||
if (!error.read(from, from + buffer.size())) {
|
||||
return false;
|
||||
}
|
||||
return error.match([&](const MTPDrpc_error &data) {
|
||||
return (data.verror_code().v == 401)
|
||||
&& (data.verror_message().v == "AUTH_KEY_PERM_EMPTY");
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/details/mtproto_dc_key_creator.h"
|
||||
#include "mtproto/details/mtproto_dc_key_binder.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
class SerializedRequest;
|
||||
|
||||
class BoundKeyCreator final {
|
||||
public:
|
||||
struct Delegate {
|
||||
Fn<void(base::expected<DcKeyResult, DcKeyError>)> unboundReady;
|
||||
Fn<void(uint64)> sentSome;
|
||||
Fn<void()> receivedSome;
|
||||
};
|
||||
|
||||
BoundKeyCreator(DcKeyRequest request, Delegate delegate);
|
||||
|
||||
void start(
|
||||
DcId dcId,
|
||||
int16 protocolDcId,
|
||||
not_null<AbstractConnection*> connection,
|
||||
not_null<DcOptions*> dcOptions);
|
||||
void stop();
|
||||
|
||||
void bind(AuthKeyPtr &&persistentKey);
|
||||
void restartBinder();
|
||||
[[nodiscard]] bool readyToBind() const;
|
||||
[[nodiscard]] SerializedRequest prepareBindRequest(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
uint64 sessionId);
|
||||
[[nodiscard]] DcKeyBindState handleBindResponse(
|
||||
const mtpBuffer &response);
|
||||
[[nodiscard]] AuthKeyPtr bindPersistentKey() const;
|
||||
|
||||
private:
|
||||
const DcKeyRequest _request;
|
||||
Delegate _delegate;
|
||||
|
||||
std::optional<DcKeyCreator> _creator;
|
||||
std::optional<DcKeyBinder> _binder;
|
||||
|
||||
};
|
||||
|
||||
|
||||
[[nodiscard]] bool IsDestroyedTemporaryKeyError(const mtpBuffer &buffer);
|
||||
|
||||
} // namespace MTP::details
|
||||
132
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.cpp
Normal file
132
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
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/details/mtproto_dc_key_binder.h"
|
||||
|
||||
#include "mtproto/details/mtproto_serialized_request.h"
|
||||
#include "mtproto/mtp_instance.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "scheme.h"
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] QByteArray EncryptBindAuthKeyInner(
|
||||
const AuthKeyPtr &persistentKey,
|
||||
mtpMsgId realMsgId,
|
||||
const MTPBindAuthKeyInner &data) {
|
||||
auto serialized = SerializedRequest::Serialize(data);
|
||||
serialized.setMsgId(realMsgId);
|
||||
serialized.setSeqNo(0);
|
||||
serialized.addPadding(true);
|
||||
|
||||
constexpr auto kMsgIdPosition = SerializedRequest::kMessageIdPosition;
|
||||
constexpr auto kMinMessageSize = 5;
|
||||
|
||||
const auto sizeInPrimes = serialized->size();
|
||||
const auto messageSize = serialized.messageSize();
|
||||
Assert(messageSize >= kMinMessageSize);
|
||||
Assert(sizeInPrimes >= kMsgIdPosition + messageSize);
|
||||
|
||||
const auto sizeInBytes = sizeInPrimes * sizeof(mtpPrime);
|
||||
const auto padding = sizeInBytes
|
||||
- (kMsgIdPosition + messageSize) * sizeof(mtpPrime);
|
||||
|
||||
// session_id, salt - just random here.
|
||||
bytes::set_random(bytes::make_span(*serialized).subspan(
|
||||
0,
|
||||
kMsgIdPosition * sizeof(mtpPrime)));
|
||||
|
||||
const auto hash = openssl::Sha1(bytes::make_span(*serialized).subspan(
|
||||
0,
|
||||
sizeInBytes - padding));
|
||||
auto msgKey = MTPint128();
|
||||
bytes::copy(
|
||||
bytes::object_as_span(&msgKey),
|
||||
bytes::make_span(hash).subspan(4));
|
||||
|
||||
constexpr auto kAuthKeyIdBytes = 2 * sizeof(mtpPrime);
|
||||
constexpr auto kMessageKeyPosition = kAuthKeyIdBytes;
|
||||
constexpr auto kMessageKeyBytes = 4 * sizeof(mtpPrime);
|
||||
constexpr auto kPrefix = (kAuthKeyIdBytes + kMessageKeyBytes);
|
||||
auto encrypted = QByteArray(kPrefix + sizeInBytes, Qt::Uninitialized);
|
||||
*reinterpret_cast<uint64*>(encrypted.data()) = persistentKey->keyId();
|
||||
*reinterpret_cast<MTPint128*>(encrypted.data() + kMessageKeyPosition)
|
||||
= msgKey;
|
||||
|
||||
aesIgeEncrypt_oldmtp(
|
||||
serialized->constData(),
|
||||
encrypted.data() + kPrefix,
|
||||
sizeInBytes,
|
||||
persistentKey,
|
||||
msgKey);
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DcKeyBinder::DcKeyBinder(AuthKeyPtr &&persistentKey)
|
||||
: _persistentKey(std::move(persistentKey)) {
|
||||
Expects(_persistentKey != nullptr);
|
||||
}
|
||||
|
||||
SerializedRequest DcKeyBinder::prepareRequest(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
uint64 sessionId) {
|
||||
Expects(temporaryKey != nullptr);
|
||||
Expects(temporaryKey->expiresAt() != 0);
|
||||
|
||||
const auto nonce = base::RandomValue<uint64>();
|
||||
const auto msgId = base::unixtime::mtproto_msg_id();
|
||||
auto result = SerializedRequest::Serialize(MTPauth_BindTempAuthKey(
|
||||
MTP_long(_persistentKey->keyId()),
|
||||
MTP_long(nonce),
|
||||
MTP_int(temporaryKey->expiresAt()),
|
||||
MTP_bytes(EncryptBindAuthKeyInner(
|
||||
_persistentKey,
|
||||
msgId,
|
||||
MTP_bind_auth_key_inner(
|
||||
MTP_long(nonce),
|
||||
MTP_long(temporaryKey->keyId()),
|
||||
MTP_long(_persistentKey->keyId()),
|
||||
MTP_long(sessionId),
|
||||
MTP_int(temporaryKey->expiresAt()))))));
|
||||
result.setMsgId(msgId);
|
||||
return result;
|
||||
}
|
||||
|
||||
DcKeyBindState DcKeyBinder::handleResponse(const mtpBuffer &response) {
|
||||
Expects(!response.isEmpty());
|
||||
|
||||
auto from = response.data();
|
||||
const auto end = from + response.size();
|
||||
auto error = MTPRpcError();
|
||||
if (response[0] == mtpc_boolTrue) {
|
||||
return DcKeyBindState::Success;
|
||||
} else if (response[0] == mtpc_rpc_error && error.read(from, end)) {
|
||||
const auto destroyed = error.match([&](const MTPDrpc_error &data) {
|
||||
return (data.verror_code().v == 400)
|
||||
&& (data.verror_message().v == "ENCRYPTED_MESSAGE_INVALID");
|
||||
});
|
||||
return destroyed
|
||||
? DcKeyBindState::DefinitelyDestroyed
|
||||
: DcKeyBindState::Failed;
|
||||
} else {
|
||||
return DcKeyBindState::Failed;
|
||||
}
|
||||
}
|
||||
|
||||
AuthKeyPtr DcKeyBinder::persistentKey() const {
|
||||
return _persistentKey;
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
42
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.h
Normal file
42
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
|
||||
namespace MTP {
|
||||
class Instance;
|
||||
} // namespace MTP
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
class SerializedRequest;
|
||||
|
||||
enum class DcKeyBindState {
|
||||
Success,
|
||||
Failed,
|
||||
DefinitelyDestroyed,
|
||||
};
|
||||
|
||||
class DcKeyBinder final {
|
||||
public:
|
||||
explicit DcKeyBinder(AuthKeyPtr &&persistentKey);
|
||||
|
||||
[[nodiscard]] SerializedRequest prepareRequest(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
uint64 sessionId);
|
||||
[[nodiscard]] DcKeyBindState handleResponse(const mtpBuffer &response);
|
||||
[[nodiscard]] AuthKeyPtr persistentKey() const;
|
||||
|
||||
private:
|
||||
AuthKeyPtr _persistentKey;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
855
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
Normal file
855
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
Normal file
@@ -0,0 +1,855 @@
|
||||
/*
|
||||
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/details/mtproto_dc_key_creator.h"
|
||||
|
||||
#include "mtproto/details/mtproto_rsa_public_key.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
#include "mtproto/mtproto_dh_utils.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "scheme.h"
|
||||
#include "logs.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
struct ParsedPQ {
|
||||
QByteArray p;
|
||||
QByteArray q;
|
||||
};
|
||||
|
||||
// Fast PQ factorization taken from TDLib:
|
||||
// https://github.com/tdlib/td/blob/v1.7.0/tdutils/td/utils/crypto.cpp
|
||||
[[nodiscard]] uint64 gcd(uint64 a, uint64 b) {
|
||||
if (a == 0) {
|
||||
return b;
|
||||
} else if (b == 0) {
|
||||
return a;
|
||||
}
|
||||
|
||||
int shift = 0;
|
||||
while ((a & 1) == 0 && (b & 1) == 0) {
|
||||
a >>= 1;
|
||||
b >>= 1;
|
||||
shift++;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
while ((a & 1) == 0) {
|
||||
a >>= 1;
|
||||
}
|
||||
while ((b & 1) == 0) {
|
||||
b >>= 1;
|
||||
}
|
||||
if (a > b) {
|
||||
a -= b;
|
||||
} else if (b > a) {
|
||||
b -= a;
|
||||
} else {
|
||||
return a << shift;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] uint64 FactorizeSmallPQ(uint64 pq) {
|
||||
if (pq < 2 || pq >(static_cast<uint64>(1) << 63)) {
|
||||
return 1;
|
||||
}
|
||||
uint64 g = 0;
|
||||
for (int i = 0, iter = 0; i < 3 || iter < 1000; i++) {
|
||||
uint64 q = (17 + base::RandomIndex(16)) % (pq - 1);
|
||||
uint64 x = base::RandomValue<uint64>() % (pq - 1) + 1;
|
||||
uint64 y = x;
|
||||
int lim = 1 << (std::min(5, i) + 18);
|
||||
for (int j = 1; j < lim; j++) {
|
||||
iter++;
|
||||
uint64 a = x;
|
||||
uint64 b = x;
|
||||
uint64 c = q;
|
||||
|
||||
// c += a * b
|
||||
while (b) {
|
||||
if (b & 1) {
|
||||
c += a;
|
||||
if (c >= pq) {
|
||||
c -= pq;
|
||||
}
|
||||
}
|
||||
a += a;
|
||||
if (a >= pq) {
|
||||
a -= pq;
|
||||
}
|
||||
b >>= 1;
|
||||
}
|
||||
|
||||
x = c;
|
||||
uint64 z = x < y ? pq + x - y : x - y;
|
||||
g = gcd(z, pq);
|
||||
if (g != 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!(j & (j - 1))) {
|
||||
y = x;
|
||||
}
|
||||
}
|
||||
if (g > 1 && g < pq) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (g != 0) {
|
||||
uint64 other = pq / g;
|
||||
if (other < g) {
|
||||
g = other;
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
ParsedPQ FactorizeBigPQ(const QByteArray &pqStr) {
|
||||
using namespace openssl;
|
||||
|
||||
Context context;
|
||||
BigNum a;
|
||||
BigNum b;
|
||||
BigNum p;
|
||||
BigNum q;
|
||||
auto one = BigNum(1);
|
||||
auto pq = BigNum(bytes::make_span(pqStr));
|
||||
|
||||
bool found = false;
|
||||
for (int i = 0, iter = 0; !found && (i < 3 || iter < 1000); i++) {
|
||||
int32 t = 17 + base::RandomIndex(16);
|
||||
a.setWord(base::RandomValue<uint32>());
|
||||
b = a;
|
||||
|
||||
int32 lim = 1 << (i + 23);
|
||||
for (int j = 1; j < lim; j++) {
|
||||
iter++;
|
||||
a.setModMul(a, a, pq, context);
|
||||
a.setAdd(a, BigNum(uint32(t)));
|
||||
if (BigNum::Compare(a, pq) >= 0) {
|
||||
a = BigNum::Sub(a, pq);
|
||||
}
|
||||
if (BigNum::Compare(a, b) > 0) {
|
||||
q.setSub(a, b);
|
||||
} else {
|
||||
q.setSub(b, a);
|
||||
}
|
||||
p.setGcd(q, pq, context);
|
||||
if (BigNum::Compare(p, one) != 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
if ((j & (j - 1)) == 0) {
|
||||
b = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return ParsedPQ();
|
||||
}
|
||||
BigNum::Div(&q, nullptr, pq, p, context);
|
||||
if (BigNum::Compare(p, q) > 0) {
|
||||
std::swap(p, q);
|
||||
}
|
||||
|
||||
const auto pb = p.getBytes();
|
||||
const auto qb = q.getBytes();
|
||||
|
||||
return {
|
||||
QByteArray(reinterpret_cast<const char*>(pb.data()), pb.size()),
|
||||
QByteArray(reinterpret_cast<const char*>(qb.data()), qb.size())
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] ParsedPQ FactorizePQ(const QByteArray &pqStr) {
|
||||
const auto size = pqStr.size();
|
||||
if (size > 8 || (size == 8 && (uchar(pqStr[0]) & 128) != 0)) {
|
||||
return FactorizeBigPQ(pqStr);
|
||||
}
|
||||
|
||||
auto ptr = reinterpret_cast<const uchar*>(pqStr.data());
|
||||
uint64 pq = 0;
|
||||
for (auto i = 0; i != size; ++i) {
|
||||
pq = (pq << 8) | ptr[i];
|
||||
}
|
||||
|
||||
auto p = FactorizeSmallPQ(pq);
|
||||
if (p == 0 || (pq % p) != 0) {
|
||||
return ParsedPQ();
|
||||
}
|
||||
auto q = pq / p;
|
||||
|
||||
auto pStr = QByteArray(4, Qt::Uninitialized);
|
||||
uchar *pChars = (uchar*)pStr.data();
|
||||
for (auto i = 0; i != 4; ++i) {
|
||||
*(pChars + 3 - i) = (uchar)(p & 0xFF);
|
||||
p >>= 8;
|
||||
}
|
||||
|
||||
auto qStr = QByteArray(4, Qt::Uninitialized);
|
||||
uchar *qChars = (uchar*)qStr.data();
|
||||
for (auto i = 0; i != 4; ++i) {
|
||||
*(qChars + 3 - i) = (uchar)(q & 0xFF);
|
||||
q >>= 8;
|
||||
}
|
||||
return { pStr, qStr };
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsGoodEncryptedInner(
|
||||
bytes::const_span keyAesEncrypted,
|
||||
const RSAPublicKey &key) {
|
||||
Expects(keyAesEncrypted.size() == 256);
|
||||
|
||||
const auto modulus = key.getN();
|
||||
const auto e = key.getE();
|
||||
const auto shift = (256 - int(modulus.size()));
|
||||
Assert(shift >= 0);
|
||||
for (auto i = 0; i != 256; ++i) {
|
||||
const auto a = keyAesEncrypted[i];
|
||||
const auto b = (i < shift)
|
||||
? bytes::type(0)
|
||||
: modulus[i - shift];
|
||||
if (a > b) {
|
||||
return false;
|
||||
} else if (a < b) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename PQInnerData>
|
||||
[[nodiscard]] bytes::vector EncryptPQInnerRSA(
|
||||
const PQInnerData &data,
|
||||
const RSAPublicKey &key) {
|
||||
DEBUG_LOG(("AuthKey Info: encrypting pq inner..."));
|
||||
|
||||
constexpr auto kPrime = sizeof(mtpPrime);
|
||||
constexpr auto kDataWithPaddingPrimes = 192 / kPrime;
|
||||
constexpr auto kMaxSizeInPrimes = 144 / kPrime;
|
||||
constexpr auto kDataHashPrimes = (SHA256_DIGEST_LENGTH / kPrime);
|
||||
constexpr auto kKeySize = 32;
|
||||
constexpr auto kIvSize = 32;
|
||||
|
||||
using BoxedPQInnerData = std::conditional_t<
|
||||
tl::is_boxed_v<PQInnerData>,
|
||||
PQInnerData,
|
||||
tl::boxed<PQInnerData>>;
|
||||
const auto boxed = BoxedPQInnerData(data);
|
||||
const auto p_q_inner_size = tl::count_length(boxed);
|
||||
const auto sizeInPrimes = (p_q_inner_size / kPrime);
|
||||
if (sizeInPrimes > kMaxSizeInPrimes) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto dataWithPadding = mtpBuffer();
|
||||
dataWithPadding.reserve(kDataWithPaddingPrimes);
|
||||
boxed.write(dataWithPadding);
|
||||
|
||||
// data_with_padding := data + random_padding_bytes;
|
||||
dataWithPadding.resize(kDataWithPaddingPrimes);
|
||||
const auto dataWithPaddingBytes = bytes::make_span(dataWithPadding);
|
||||
bytes::set_random(dataWithPaddingBytes.subspan(sizeInPrimes * kPrime));
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: starting key generation for pq inner..."));
|
||||
|
||||
while (true) {
|
||||
auto dataWithHash = mtpBuffer();
|
||||
dataWithHash.reserve(kDataWithPaddingPrimes + kDataHashPrimes);
|
||||
dataWithHash.append(dataWithPadding);
|
||||
|
||||
// data_pad_reversed := BYTE_REVERSE(data_with_padding);
|
||||
ranges::reverse(bytes::make_span(dataWithHash));
|
||||
|
||||
// data_with_hash := data_pad_reversed
|
||||
// + SHA256(temp_key + data_with_padding);
|
||||
const auto tempKey = base::RandomValue<bytes::array<kKeySize>>();
|
||||
dataWithHash.resize(kDataWithPaddingPrimes + kDataHashPrimes);
|
||||
const auto dataWithHashBytes = bytes::make_span(dataWithHash);
|
||||
bytes::copy(
|
||||
dataWithHashBytes.subspan(kDataWithPaddingPrimes * kPrime),
|
||||
openssl::Sha256(tempKey, bytes::make_span(dataWithPadding)));
|
||||
|
||||
auto aesEncrypted = mtpBuffer();
|
||||
auto keyAesEncrypted = mtpBuffer();
|
||||
aesEncrypted.resize(dataWithHash.size());
|
||||
const auto aesEncryptedBytes = bytes::make_span(aesEncrypted);
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: encrypting ige for pq inner..."));
|
||||
|
||||
// aes_encrypted := AES256_IGE(data_with_hash, temp_key, 0);
|
||||
const auto tempIv = bytes::array<kIvSize>{ { bytes::type(0) } };
|
||||
aesIgeEncryptRaw(
|
||||
dataWithHashBytes.data(),
|
||||
aesEncryptedBytes.data(),
|
||||
dataWithHashBytes.size(),
|
||||
tempKey.data(),
|
||||
tempIv.data());
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: counting hash for pq inner..."));
|
||||
|
||||
// temp_key_xor := temp_key XOR SHA256(aes_encrypted);
|
||||
const auto fullSize = (kKeySize / kPrime) + dataWithHash.size();
|
||||
keyAesEncrypted.resize(fullSize);
|
||||
const auto keyAesEncryptedBytes = bytes::make_span(keyAesEncrypted);
|
||||
const auto aesHash = openssl::Sha256(aesEncryptedBytes);
|
||||
for (auto i = 0; i != kKeySize; ++i) {
|
||||
keyAesEncryptedBytes[i] = tempKey[i] ^ aesHash[i];
|
||||
}
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: checking chosen key for pq inner..."));
|
||||
|
||||
// key_aes_encrypted := temp_key_xor + aes_encrypted;
|
||||
bytes::copy(
|
||||
keyAesEncryptedBytes.subspan(kKeySize),
|
||||
aesEncryptedBytes);
|
||||
if (IsGoodEncryptedInner(keyAesEncryptedBytes, key)) {
|
||||
DEBUG_LOG(("AuthKey Info: chosen key for pq inner is good."));
|
||||
return key.encrypt(keyAesEncryptedBytes);
|
||||
}
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: chosen key for pq inner is bad..."));
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string EncryptClientDHInner(
|
||||
const MTPClient_DH_Inner_Data &data,
|
||||
const void *aesKey,
|
||||
const void *aesIV) {
|
||||
constexpr auto kSkipPrimes = openssl::kSha1Size / sizeof(mtpPrime);
|
||||
|
||||
auto client_dh_inner_size = tl::count_length(data);
|
||||
auto encSize = (client_dh_inner_size >> 2) + kSkipPrimes;
|
||||
auto encFullSize = encSize;
|
||||
if (encSize & 0x03) {
|
||||
encFullSize += 4 - (encSize & 0x03);
|
||||
}
|
||||
|
||||
auto encBuffer = mtpBuffer();
|
||||
encBuffer.reserve(encFullSize);
|
||||
encBuffer.resize(kSkipPrimes);
|
||||
data.write(encBuffer);
|
||||
encBuffer.resize(encFullSize);
|
||||
|
||||
const auto bytes = bytes::make_span(encBuffer);
|
||||
|
||||
const auto hash = openssl::Sha1(bytes.subspan(
|
||||
kSkipPrimes * sizeof(mtpPrime),
|
||||
client_dh_inner_size));
|
||||
bytes::copy(bytes, hash);
|
||||
bytes::set_random(bytes.subspan(encSize * sizeof(mtpPrime)));
|
||||
|
||||
auto sdhEncString = std::string(encFullSize * 4, ' ');
|
||||
|
||||
aesIgeEncryptRaw(&encBuffer[0], &sdhEncString[0], encFullSize * sizeof(mtpPrime), aesKey, aesIV);
|
||||
|
||||
return sdhEncString;
|
||||
}
|
||||
|
||||
// 128 lower-order bits of SHA1.
|
||||
MTPint128 NonceDigest(bytes::const_span data) {
|
||||
const auto hash = openssl::Sha1(data);
|
||||
return *(MTPint128*)(hash.data() + 4);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
DcKeyCreator::Attempt::~Attempt() {
|
||||
const auto clearBytes = [](bytes::span bytes) {
|
||||
OPENSSL_cleanse(bytes.data(), bytes.size());
|
||||
};
|
||||
OPENSSL_cleanse(&data, sizeof(data));
|
||||
clearBytes(dhPrime);
|
||||
clearBytes(g_a);
|
||||
clearBytes(authKey);
|
||||
}
|
||||
|
||||
DcKeyCreator::DcKeyCreator(
|
||||
DcId dcId,
|
||||
int16 protocolDcId,
|
||||
not_null<AbstractConnection*> connection,
|
||||
not_null<DcOptions*> dcOptions,
|
||||
Delegate delegate,
|
||||
DcKeyRequest request)
|
||||
: _connection(connection)
|
||||
, _dcOptions(dcOptions)
|
||||
, _dcId(dcId)
|
||||
, _protocolDcId(protocolDcId)
|
||||
, _request(request)
|
||||
, _delegate(std::move(delegate)) {
|
||||
Expects(_request.temporaryExpiresIn > 0);
|
||||
Expects(_delegate.done != nullptr);
|
||||
|
||||
QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
|
||||
answered();
|
||||
});
|
||||
|
||||
if (_request.persistentNeeded) {
|
||||
pqSend(&_persistent, 0);
|
||||
} else {
|
||||
pqSend(&_temporary, _request.temporaryExpiresIn);
|
||||
}
|
||||
}
|
||||
|
||||
DcKeyCreator::~DcKeyCreator() {
|
||||
if (_delegate.done) {
|
||||
stopReceiving();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RequestType>
|
||||
void DcKeyCreator::sendNotSecureRequest(const RequestType &request) {
|
||||
auto packet = _connection->prepareNotSecurePacket(
|
||||
request,
|
||||
base::unixtime::mtproto_msg_id());
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: sending request, size: %1, time: %3"
|
||||
).arg(packet.size() - 8
|
||||
).arg(packet[5]));
|
||||
|
||||
const auto bytesSize = packet.size() * sizeof(mtpPrime);
|
||||
|
||||
_connection->sendData(std::move(packet));
|
||||
|
||||
if (_delegate.sentSome) {
|
||||
_delegate.sentSome(bytesSize);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename RequestType, typename Response>
|
||||
std::optional<Response> DcKeyCreator::readNotSecureResponse(
|
||||
gsl::span<const mtpPrime> answer) {
|
||||
auto from = answer.data();
|
||||
auto result = Response();
|
||||
if (result.read(from, from + answer.size())) {
|
||||
return result;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void DcKeyCreator::answered() {
|
||||
if (_delegate.receivedSome) {
|
||||
_delegate.receivedSome();
|
||||
}
|
||||
|
||||
if (_connection->received().empty()) {
|
||||
LOG(("AuthKey Error: "
|
||||
"trying to read response from empty received list"));
|
||||
return failed();
|
||||
}
|
||||
|
||||
const auto buffer = std::move(_connection->received().front());
|
||||
_connection->received().pop_front();
|
||||
|
||||
const auto answer = _connection->parseNotSecureResponse(buffer);
|
||||
if (answer.empty()) {
|
||||
return failed();
|
||||
}
|
||||
|
||||
handleAnswer(answer);
|
||||
}
|
||||
|
||||
DcKeyCreator::Attempt *DcKeyCreator::attemptByNonce(const MTPint128 &nonce) {
|
||||
if (_temporary.data.nonce == nonce) {
|
||||
DEBUG_LOG(("AuthKey Info: receiving answer for temporary..."));
|
||||
return &_temporary;
|
||||
} else if (_persistent.data.nonce == nonce) {
|
||||
DEBUG_LOG(("AuthKey Info: receiving answer for persistent..."));
|
||||
return &_persistent;
|
||||
}
|
||||
LOG(("AuthKey Error: attempt by nonce not found."));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DcKeyCreator::handleAnswer(gsl::span<const mtpPrime> answer) {
|
||||
if (const auto resPQ = readNotSecureResponse<MTPReq_pq>(answer)) {
|
||||
const auto nonce = resPQ->match([](const auto &data) {
|
||||
return data.vnonce();
|
||||
});
|
||||
if (const auto attempt = attemptByNonce(nonce)) {
|
||||
DEBUG_LOG(("AuthKey Info: receiving Req_pq answer..."));
|
||||
return pqAnswered(attempt, *resPQ);
|
||||
}
|
||||
} else if (const auto resDH = readNotSecureResponse<MTPReq_DH_params>(answer)) {
|
||||
const auto nonce = resDH->match([](const auto &data) {
|
||||
return data.vnonce();
|
||||
});
|
||||
if (const auto attempt = attemptByNonce(nonce)) {
|
||||
DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer..."));
|
||||
return dhParamsAnswered(attempt, *resDH);
|
||||
}
|
||||
} else if (const auto result = readNotSecureResponse<MTPSet_client_DH_params>(answer)) {
|
||||
const auto nonce = result->match([](const auto &data) {
|
||||
return data.vnonce();
|
||||
});
|
||||
if (const auto attempt = attemptByNonce(nonce)) {
|
||||
DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer..."));
|
||||
return dhClientParamsAnswered(attempt, *result);
|
||||
}
|
||||
}
|
||||
LOG(("AuthKey Error: Unknown answer received."));
|
||||
failed();
|
||||
}
|
||||
|
||||
void DcKeyCreator::pqSend(not_null<Attempt*> attempt, TimeId expiresIn) {
|
||||
DEBUG_LOG(("AuthKey Info: sending Req_pq for %1..."
|
||||
).arg(expiresIn ? "temporary" : "persistent"));
|
||||
attempt->stage = Stage::WaitingPQ;
|
||||
attempt->expiresIn = expiresIn;
|
||||
attempt->data.nonce = base::RandomValue<MTPint128>();
|
||||
sendNotSecureRequest(MTPReq_pq_multi(attempt->data.nonce));
|
||||
}
|
||||
|
||||
void DcKeyCreator::pqAnswered(
|
||||
not_null<Attempt*> attempt,
|
||||
const MTPresPQ &data) {
|
||||
data.match([&](const MTPDresPQ &data) {
|
||||
Expects(data.vnonce() == attempt->data.nonce);
|
||||
|
||||
if (attempt->stage != Stage::WaitingPQ) {
|
||||
LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
|
||||
return failed();
|
||||
}
|
||||
DEBUG_LOG(("AuthKey Info: getting dc RSA key..."));
|
||||
const auto rsaKey = _dcOptions->getDcRSAKey(
|
||||
_dcId,
|
||||
data.vserver_public_key_fingerprints().v);
|
||||
if (!rsaKey.valid()) {
|
||||
DEBUG_LOG(("AuthKey Error: unknown public key."));
|
||||
return failed(DcKeyError::UnknownPublicKey);
|
||||
}
|
||||
|
||||
attempt->data.server_nonce = data.vserver_nonce();
|
||||
attempt->data.new_nonce = base::RandomValue<MTPint256>();
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: parsing pq..."));
|
||||
const auto &pq = data.vpq().v;
|
||||
const auto parsed = FactorizePQ(data.vpq().v);
|
||||
if (parsed.p.isEmpty() || parsed.q.isEmpty()) {
|
||||
LOG(("AuthKey Error: could not factor pq!"));
|
||||
DEBUG_LOG(("AuthKey Error: problematic pq: %1").arg(Logs::mb(pq.constData(), pq.length()).str()));
|
||||
return failed();
|
||||
}
|
||||
DEBUG_LOG(("AuthKey Info: parse pq done."));
|
||||
|
||||
const auto dhEncString = [&] {
|
||||
return (attempt->expiresIn == 0)
|
||||
? EncryptPQInnerRSA(
|
||||
MTP_p_q_inner_data_dc(
|
||||
data.vpq(),
|
||||
MTP_bytes(parsed.p),
|
||||
MTP_bytes(parsed.q),
|
||||
attempt->data.nonce,
|
||||
attempt->data.server_nonce,
|
||||
attempt->data.new_nonce,
|
||||
MTP_int(_protocolDcId)),
|
||||
rsaKey)
|
||||
: EncryptPQInnerRSA(
|
||||
MTP_p_q_inner_data_temp_dc(
|
||||
data.vpq(),
|
||||
MTP_bytes(parsed.p),
|
||||
MTP_bytes(parsed.q),
|
||||
attempt->data.nonce,
|
||||
attempt->data.server_nonce,
|
||||
attempt->data.new_nonce,
|
||||
MTP_int(_protocolDcId),
|
||||
MTP_int(attempt->expiresIn)),
|
||||
rsaKey);
|
||||
}();
|
||||
if (dhEncString.empty()) {
|
||||
DEBUG_LOG(("AuthKey Error: could not encrypt pq inner."));
|
||||
return failed();
|
||||
}
|
||||
|
||||
attempt->stage = Stage::WaitingDH;
|
||||
DEBUG_LOG(("AuthKey Info: sending Req_DH_params..."));
|
||||
sendNotSecureRequest(MTPReq_DH_params(
|
||||
attempt->data.nonce,
|
||||
attempt->data.server_nonce,
|
||||
MTP_bytes(parsed.p),
|
||||
MTP_bytes(parsed.q),
|
||||
MTP_long(rsaKey.fingerprint()),
|
||||
MTP_bytes(dhEncString)));
|
||||
});
|
||||
}
|
||||
|
||||
void DcKeyCreator::dhParamsAnswered(
|
||||
not_null<Attempt*> attempt,
|
||||
const MTPserver_DH_Params &data) {
|
||||
if (attempt->stage != Stage::WaitingDH) {
|
||||
LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
|
||||
return failed();
|
||||
}
|
||||
data.match([&](const MTPDserver_DH_params_ok &data) {
|
||||
Expects(data.vnonce() == attempt->data.nonce);
|
||||
|
||||
if (data.vserver_nonce() != attempt->data.server_nonce) {
|
||||
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_ok)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
|
||||
auto &encDHStr = data.vencrypted_answer().v;
|
||||
uint32 encDHLen = encDHStr.length(), encDHBufLen = encDHLen >> 2;
|
||||
if ((encDHLen & 0x03) || encDHBufLen < 6) {
|
||||
LOG(("AuthKey Error: bad encrypted data length %1 (in server_DH_params_ok)!").arg(encDHLen));
|
||||
DEBUG_LOG(("AuthKey Error: received encrypted data %1").arg(Logs::mb(encDHStr.constData(), encDHLen).str()));
|
||||
return failed();
|
||||
}
|
||||
|
||||
const auto nlen = sizeof(attempt->data.new_nonce);
|
||||
const auto slen = sizeof(attempt->data.server_nonce);
|
||||
auto tmp_aes_buffer = bytes::array<1024>();
|
||||
const auto tmp_aes = bytes::make_span(tmp_aes_buffer);
|
||||
bytes::copy(tmp_aes, bytes::object_as_span(&attempt->data.new_nonce));
|
||||
bytes::copy(tmp_aes.subspan(nlen), bytes::object_as_span(&attempt->data.server_nonce));
|
||||
bytes::copy(tmp_aes.subspan(nlen + slen), bytes::object_as_span(&attempt->data.new_nonce));
|
||||
bytes::copy(tmp_aes.subspan(nlen + slen + nlen), bytes::object_as_span(&attempt->data.new_nonce));
|
||||
const auto sha1ns = openssl::Sha1(tmp_aes.subspan(0, nlen + slen));
|
||||
const auto sha1sn = openssl::Sha1(tmp_aes.subspan(nlen, nlen + slen));
|
||||
const auto sha1nn = openssl::Sha1(tmp_aes.subspan(nlen + slen, nlen + nlen));
|
||||
|
||||
mtpBuffer decBuffer;
|
||||
decBuffer.resize(encDHBufLen);
|
||||
|
||||
const auto aesKey = bytes::make_span(attempt->data.aesKey);
|
||||
const auto aesIV = bytes::make_span(attempt->data.aesIV);
|
||||
bytes::copy(aesKey, bytes::make_span(sha1ns).subspan(0, 20));
|
||||
bytes::copy(aesKey.subspan(20), bytes::make_span(sha1sn).subspan(0, 12));
|
||||
bytes::copy(aesIV, bytes::make_span(sha1sn).subspan(12, 8));
|
||||
bytes::copy(aesIV.subspan(8), bytes::make_span(sha1nn).subspan(0, 20));
|
||||
bytes::copy(aesIV.subspan(28), bytes::object_as_span(&attempt->data.new_nonce).subspan(0, 4));
|
||||
|
||||
aesIgeDecryptRaw(encDHStr.constData(), &decBuffer[0], encDHLen, aesKey.data(), aesIV.data());
|
||||
|
||||
const mtpPrime *from(&decBuffer[5]), *to(from), *end(from + (encDHBufLen - 5));
|
||||
MTPServer_DH_inner_data dh_inner;
|
||||
if (!dh_inner.read(to, end)) {
|
||||
LOG(("AuthKey Error: could not decrypt server_DH_inner_data!"));
|
||||
return failed();
|
||||
}
|
||||
const auto &dh_inner_data(dh_inner.c_server_DH_inner_data());
|
||||
if (dh_inner_data.vnonce() != attempt->data.nonce) {
|
||||
LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_inner_data)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&dh_inner_data.vnonce(), 16).str(), Logs::mb(&attempt->data.nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
if (dh_inner_data.vserver_nonce() != attempt->data.server_nonce) {
|
||||
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_inner_data)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&dh_inner_data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
const auto sha1Buffer = openssl::Sha1(
|
||||
bytes::make_span(decBuffer).subspan(
|
||||
5 * sizeof(mtpPrime),
|
||||
(to - from) * sizeof(mtpPrime)));
|
||||
const auto sha1Dec = bytes::make_span(decBuffer).subspan(
|
||||
0,
|
||||
openssl::kSha1Size);
|
||||
if (bytes::compare(sha1Dec, sha1Buffer)) {
|
||||
LOG(("AuthKey Error: sha1 hash of encrypted part did not match!"));
|
||||
DEBUG_LOG(("AuthKey Error: sha1 did not match, server_nonce: %1, new_nonce %2, encrypted data %3").arg(Logs::mb(&attempt->data.server_nonce, 16).str(), Logs::mb(&attempt->data.new_nonce, 16).str(), Logs::mb(encDHStr.constData(), encDHLen).str()));
|
||||
return failed();
|
||||
}
|
||||
base::unixtime::update(dh_inner_data.vserver_time().v);
|
||||
|
||||
// check that dhPrime and (dhPrime - 1) / 2 are really prime
|
||||
if (!IsPrimeAndGood(bytes::make_span(dh_inner_data.vdh_prime().v), dh_inner_data.vg().v)) {
|
||||
LOG(("AuthKey Error: bad dh_prime primality!"));
|
||||
return failed();
|
||||
}
|
||||
|
||||
attempt->dhPrime = bytes::make_vector(
|
||||
dh_inner_data.vdh_prime().v);
|
||||
attempt->data.g = dh_inner_data.vg().v;
|
||||
attempt->g_a = bytes::make_vector(dh_inner_data.vg_a().v);
|
||||
attempt->data.retry_id = MTP_long(0);
|
||||
attempt->retries = 0;
|
||||
dhClientParamsSend(attempt);
|
||||
}, [&](const MTPDserver_DH_params_fail &data) {
|
||||
Expects(data.vnonce() == attempt->data.nonce);
|
||||
|
||||
if (data.vserver_nonce() != attempt->data.server_nonce) {
|
||||
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_fail)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
if (data.vnew_nonce_hash() != NonceDigest(bytes::object_as_span(&attempt->data.new_nonce))) {
|
||||
LOG(("AuthKey Error: received new_nonce_hash did not match!"));
|
||||
DEBUG_LOG(("AuthKey Error: received new_nonce_hash: %1, new_nonce: %2").arg(Logs::mb(&data.vnew_nonce_hash(), 16).str(), Logs::mb(&attempt->data.new_nonce, 32).str()));
|
||||
return failed();
|
||||
}
|
||||
LOG(("AuthKey Error: server_DH_params_fail received!"));
|
||||
failed();
|
||||
});
|
||||
}
|
||||
|
||||
void DcKeyCreator::dhClientParamsSend(not_null<Attempt*> attempt) {
|
||||
if (++attempt->retries > 5) {
|
||||
LOG(("AuthKey Error: could not create auth_key for %1 retries").arg(attempt->retries - 1));
|
||||
return failed();
|
||||
}
|
||||
|
||||
// gen rand 'b'
|
||||
auto randomSeed = bytes::vector(ModExpFirst::kRandomPowerSize);
|
||||
bytes::set_random(randomSeed);
|
||||
auto g_b_data = CreateModExp(attempt->data.g, attempt->dhPrime, randomSeed);
|
||||
if (g_b_data.modexp.empty()) {
|
||||
LOG(("AuthKey Error: could not generate good g_b."));
|
||||
return failed();
|
||||
}
|
||||
|
||||
auto computedAuthKey = CreateAuthKey(attempt->g_a, g_b_data.randomPower, attempt->dhPrime);
|
||||
if (computedAuthKey.empty()) {
|
||||
LOG(("AuthKey Error: could not generate auth_key."));
|
||||
return failed();
|
||||
}
|
||||
AuthKey::FillData(attempt->authKey, computedAuthKey);
|
||||
|
||||
auto auth_key_sha = openssl::Sha1(attempt->authKey);
|
||||
memcpy(&attempt->data.auth_key_aux_hash.v, auth_key_sha.data(), 8);
|
||||
memcpy(&attempt->data.auth_key_hash.v, auth_key_sha.data() + 12, 8);
|
||||
|
||||
const auto client_dh_inner = MTP_client_DH_inner_data(
|
||||
attempt->data.nonce,
|
||||
attempt->data.server_nonce,
|
||||
attempt->data.retry_id,
|
||||
MTP_bytes(g_b_data.modexp));
|
||||
|
||||
auto sdhEncString = EncryptClientDHInner(
|
||||
client_dh_inner,
|
||||
attempt->data.aesKey.data(),
|
||||
attempt->data.aesIV.data());
|
||||
|
||||
attempt->stage = Stage::WaitingDone;
|
||||
DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params..."));
|
||||
sendNotSecureRequest(MTPSet_client_DH_params(
|
||||
attempt->data.nonce,
|
||||
attempt->data.server_nonce,
|
||||
MTP_string(std::move(sdhEncString))));
|
||||
}
|
||||
|
||||
void DcKeyCreator::dhClientParamsAnswered(
|
||||
not_null<Attempt*> attempt,
|
||||
const MTPset_client_DH_params_answer &data) {
|
||||
if (attempt->stage != Stage::WaitingDone) {
|
||||
LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
|
||||
return failed();
|
||||
}
|
||||
|
||||
data.match([&](const MTPDdh_gen_ok &data) {
|
||||
if (data.vnonce() != attempt->data.nonce) {
|
||||
LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_ok)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str(), Logs::mb(&attempt->data.nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
if (data.vserver_nonce() != attempt->data.server_nonce) {
|
||||
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_ok)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
attempt->data.new_nonce_buf[32] = bytes::type(1);
|
||||
if (data.vnew_nonce_hash1() != NonceDigest(attempt->data.new_nonce_buf)) {
|
||||
LOG(("AuthKey Error: received new_nonce_hash1 did not match!"));
|
||||
DEBUG_LOG(("AuthKey Error: received new_nonce_hash1: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash1(), 16).str(), Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
|
||||
return failed();
|
||||
}
|
||||
|
||||
uint64 salt1 = attempt->data.new_nonce.l.l, salt2 = attempt->data.server_nonce.l;
|
||||
attempt->data.doneSalt = salt1 ^ salt2;
|
||||
attempt->stage = Stage::Ready;
|
||||
done();
|
||||
}, [&](const MTPDdh_gen_retry &data) {
|
||||
if (data.vnonce() != attempt->data.nonce) {
|
||||
LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_retry)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str(), Logs::mb(&attempt->data.nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
if (data.vserver_nonce() != attempt->data.server_nonce) {
|
||||
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_retry)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
attempt->data.new_nonce_buf[32] = bytes::type(2);
|
||||
if (data.vnew_nonce_hash2() != NonceDigest(attempt->data.new_nonce_buf)) {
|
||||
LOG(("AuthKey Error: received new_nonce_hash2 did not match!"));
|
||||
DEBUG_LOG(("AuthKey Error: received new_nonce_hash2: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash2(), 16).str(), Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
|
||||
return failed();
|
||||
}
|
||||
attempt->data.retry_id = attempt->data.auth_key_aux_hash;
|
||||
dhClientParamsSend(attempt);
|
||||
}, [&](const MTPDdh_gen_fail &data) {
|
||||
if (data.vnonce() != attempt->data.nonce) {
|
||||
LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_fail)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str(), Logs::mb(&attempt->data.nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
if (data.vserver_nonce() != attempt->data.server_nonce) {
|
||||
LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_fail)!"));
|
||||
DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str(), Logs::mb(&attempt->data.server_nonce, 16).str()));
|
||||
return failed();
|
||||
}
|
||||
attempt->data.new_nonce_buf[32] = bytes::type(3);
|
||||
if (data.vnew_nonce_hash3() != NonceDigest(attempt->data.new_nonce_buf)) {
|
||||
LOG(("AuthKey Error: received new_nonce_hash3 did not match!"));
|
||||
DEBUG_LOG(("AuthKey Error: received new_nonce_hash3: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash3(), 16).str(), Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
|
||||
return failed();
|
||||
}
|
||||
LOG(("AuthKey Error: dh_gen_fail received!"));
|
||||
failed();
|
||||
});
|
||||
}
|
||||
|
||||
void DcKeyCreator::failed(DcKeyError error) {
|
||||
stopReceiving();
|
||||
auto onstack = base::take(_delegate.done);
|
||||
onstack(tl::unexpected(error));
|
||||
}
|
||||
|
||||
void DcKeyCreator::done() {
|
||||
if (_temporary.stage == Stage::None) {
|
||||
pqSend(&_temporary, _request.temporaryExpiresIn);
|
||||
return;
|
||||
}
|
||||
Assert(_temporary.stage == Stage::Ready);
|
||||
Assert(_persistent.stage == Stage::Ready || _persistent.stage == Stage::None);
|
||||
|
||||
auto result = DcKeyResult();
|
||||
result.temporaryKey = std::make_shared<AuthKey>(
|
||||
AuthKey::Type::Temporary,
|
||||
_dcId,
|
||||
_temporary.authKey);
|
||||
result.temporaryServerSalt = _temporary.data.doneSalt;
|
||||
if (_persistent.stage == Stage::Ready) {
|
||||
result.persistentKey = std::make_shared<AuthKey>(
|
||||
AuthKey::Type::Generated,
|
||||
_dcId,
|
||||
_persistent.authKey);
|
||||
result.persistentServerSalt = _persistent.data.doneSalt;
|
||||
}
|
||||
|
||||
stopReceiving();
|
||||
auto onstack = base::take(_delegate.done);
|
||||
onstack(std::move(result));
|
||||
}
|
||||
|
||||
void DcKeyCreator::stopReceiving() {
|
||||
QObject::disconnect(
|
||||
_connection,
|
||||
&AbstractConnection::receivedData,
|
||||
nullptr,
|
||||
nullptr);
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
139
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
Normal file
139
Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
#include "base/basic_types.h"
|
||||
#include "base/expected.h"
|
||||
|
||||
namespace MTP {
|
||||
class DcOptions;
|
||||
} // namespace MTP
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
struct DcKeyRequest {
|
||||
TimeId temporaryExpiresIn = 0;
|
||||
bool persistentNeeded = false;
|
||||
};
|
||||
|
||||
enum class DcKeyError {
|
||||
UnknownPublicKey,
|
||||
Other,
|
||||
};
|
||||
|
||||
struct DcKeyResult {
|
||||
AuthKeyPtr persistentKey;
|
||||
AuthKeyPtr temporaryKey;
|
||||
uint64 temporaryServerSalt = 0;
|
||||
uint64 persistentServerSalt = 0;
|
||||
};
|
||||
|
||||
class DcKeyCreator final {
|
||||
public:
|
||||
struct Delegate {
|
||||
Fn<void(base::expected<DcKeyResult, DcKeyError>)> done;
|
||||
Fn<void(uint64)> sentSome;
|
||||
Fn<void()> receivedSome;
|
||||
};
|
||||
|
||||
DcKeyCreator(
|
||||
DcId dcId,
|
||||
int16 protocolDcId,
|
||||
not_null<AbstractConnection*> connection,
|
||||
not_null<DcOptions*> dcOptions,
|
||||
Delegate delegate,
|
||||
DcKeyRequest request);
|
||||
~DcKeyCreator();
|
||||
|
||||
private:
|
||||
enum class Stage {
|
||||
None,
|
||||
WaitingPQ,
|
||||
WaitingDH,
|
||||
WaitingDone,
|
||||
Ready,
|
||||
};
|
||||
struct Data {
|
||||
Data()
|
||||
: new_nonce(*(MTPint256*)((uchar*)new_nonce_buf.data()))
|
||||
, auth_key_aux_hash(*(MTPlong*)((uchar*)new_nonce_buf.data() + 33)) {
|
||||
}
|
||||
MTPint128 nonce, server_nonce;
|
||||
|
||||
// 32 bytes new_nonce + 1 check byte + 8 bytes of auth_key_aux_hash.
|
||||
bytes::array<41> new_nonce_buf{};
|
||||
|
||||
MTPint256 &new_nonce;
|
||||
MTPlong &auth_key_aux_hash;
|
||||
|
||||
MTPlong retry_id;
|
||||
|
||||
int32 g = 0;
|
||||
|
||||
bytes::array<32> aesKey;
|
||||
bytes::array<32> aesIV;
|
||||
MTPlong auth_key_hash;
|
||||
uint64 doneSalt = 0;
|
||||
};
|
||||
struct Attempt {
|
||||
~Attempt();
|
||||
|
||||
Data data;
|
||||
bytes::vector dhPrime;
|
||||
bytes::vector g_a;
|
||||
AuthKey::Data authKey = { { gsl::byte{} } };
|
||||
TimeId expiresIn = 0;
|
||||
uint32 retries = 0;
|
||||
Stage stage = Stage::None;
|
||||
};
|
||||
|
||||
template <typename RequestType>
|
||||
void sendNotSecureRequest(const RequestType &request);
|
||||
|
||||
template <
|
||||
typename RequestType,
|
||||
typename Response = typename RequestType::ResponseType>
|
||||
[[nodiscard]] std::optional<Response> readNotSecureResponse(
|
||||
gsl::span<const mtpPrime> answer);
|
||||
|
||||
Attempt *attemptByNonce(const MTPint128 &nonce);
|
||||
|
||||
void answered();
|
||||
void handleAnswer(gsl::span<const mtpPrime> answer);
|
||||
void pqSend(not_null<Attempt*> attempt, TimeId expiresIn);
|
||||
void pqAnswered(
|
||||
not_null<Attempt*> attempt,
|
||||
const MTPresPQ &data);
|
||||
void dhParamsAnswered(
|
||||
not_null<Attempt*> attempt,
|
||||
const MTPserver_DH_Params &data);
|
||||
void dhClientParamsSend(not_null<Attempt*> attempt);
|
||||
void dhClientParamsAnswered(
|
||||
not_null<Attempt*> attempt,
|
||||
const MTPset_client_DH_params_answer &data);
|
||||
|
||||
void stopReceiving();
|
||||
void failed(DcKeyError error = DcKeyError::Other);
|
||||
void done();
|
||||
|
||||
const not_null<AbstractConnection*> _connection;
|
||||
const not_null<DcOptions*> _dcOptions;
|
||||
const DcId _dcId = 0;
|
||||
const int16 _protocolDcId = 0;
|
||||
const DcKeyRequest _request;
|
||||
Delegate _delegate;
|
||||
|
||||
Attempt _temporary;
|
||||
Attempt _persistent;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
166
Telegram/SourceFiles/mtproto/details/mtproto_dcenter.cpp
Normal file
166
Telegram/SourceFiles/mtproto/details/mtproto_dcenter.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
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/details/mtproto_dcenter.h"
|
||||
|
||||
#include "mtproto/facade.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "mtproto/mtp_instance.h"
|
||||
#include "mtproto/special_config_request.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
namespace {
|
||||
|
||||
int IndexByType(TemporaryKeyType type) {
|
||||
switch (type) {
|
||||
case TemporaryKeyType::Regular: return 0;
|
||||
case TemporaryKeyType::MediaCluster: return 1;
|
||||
}
|
||||
Unexpected("Type value in IndexByType.");
|
||||
}
|
||||
|
||||
int IndexByType(CreatingKeyType type) {
|
||||
switch (type) {
|
||||
case CreatingKeyType::Persistent:
|
||||
case CreatingKeyType::TemporaryRegular: return 0;
|
||||
case CreatingKeyType::TemporaryMediaCluster: return 1;
|
||||
}
|
||||
Unexpected("Creating type value in IndexByType.");
|
||||
}
|
||||
|
||||
const char *NameOfType(CreatingKeyType type) {
|
||||
switch (type) {
|
||||
case CreatingKeyType::Persistent: return "persistent";
|
||||
case CreatingKeyType::TemporaryRegular: return "regular";
|
||||
case CreatingKeyType::TemporaryMediaCluster: return "media";
|
||||
}
|
||||
Unexpected("Type value in NameOfType.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
TemporaryKeyType TemporaryKeyTypeByDcType(DcType type) {
|
||||
return (type == DcType::MediaCluster)
|
||||
? TemporaryKeyType::MediaCluster
|
||||
: TemporaryKeyType::Regular;
|
||||
}
|
||||
|
||||
Dcenter::Dcenter(DcId dcId, AuthKeyPtr &&key)
|
||||
: _id(dcId)
|
||||
, _persistentKey(std::move(key)) {
|
||||
}
|
||||
|
||||
DcId Dcenter::id() const {
|
||||
return _id;
|
||||
}
|
||||
|
||||
AuthKeyPtr Dcenter::getTemporaryKey(TemporaryKeyType type) const {
|
||||
QReadLocker lock(&_mutex);
|
||||
return _temporaryKeys[IndexByType(type)];
|
||||
}
|
||||
|
||||
AuthKeyPtr Dcenter::getPersistentKey() const {
|
||||
QReadLocker lock(&_mutex);
|
||||
return _persistentKey;
|
||||
}
|
||||
|
||||
bool Dcenter::destroyTemporaryKey(uint64 keyId) {
|
||||
QWriteLocker lock(&_mutex);
|
||||
for (auto &key : _temporaryKeys) {
|
||||
if (key && key->keyId() == keyId) {
|
||||
key = nullptr;
|
||||
_connectionInited = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Dcenter::destroyConfirmedForgottenKey(uint64 keyId) {
|
||||
QWriteLocker lock(&_mutex);
|
||||
if (!_persistentKey || _persistentKey->keyId() != keyId) {
|
||||
return false;
|
||||
}
|
||||
for (auto &key : _temporaryKeys) {
|
||||
key = nullptr;
|
||||
}
|
||||
_persistentKey = nullptr;
|
||||
_connectionInited = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Dcenter::connectionInited() const {
|
||||
QReadLocker lock(&_mutex);
|
||||
return _connectionInited;
|
||||
}
|
||||
|
||||
void Dcenter::setConnectionInited(bool connectionInited) {
|
||||
QWriteLocker lock(&_mutex);
|
||||
_connectionInited = connectionInited;
|
||||
}
|
||||
|
||||
CreatingKeyType Dcenter::acquireKeyCreation(DcType type) {
|
||||
QReadLocker lock(&_mutex);
|
||||
const auto keyType = TemporaryKeyTypeByDcType(type);
|
||||
const auto index = IndexByType(keyType);
|
||||
auto &key = _temporaryKeys[index];
|
||||
if (key != nullptr) {
|
||||
return CreatingKeyType::None;
|
||||
}
|
||||
auto expected = false;
|
||||
const auto regular = IndexByType(TemporaryKeyType::Regular);
|
||||
if (keyType == TemporaryKeyType::MediaCluster && _temporaryKeys[regular]) {
|
||||
return !_creatingKeys[index].compare_exchange_strong(expected, true)
|
||||
? CreatingKeyType::None
|
||||
: CreatingKeyType::TemporaryMediaCluster;
|
||||
}
|
||||
return !_creatingKeys[regular].compare_exchange_strong(expected, true)
|
||||
? CreatingKeyType::None
|
||||
: (type != DcType::Cdn && !_persistentKey)
|
||||
? CreatingKeyType::Persistent
|
||||
: CreatingKeyType::TemporaryRegular;
|
||||
}
|
||||
|
||||
bool Dcenter::releaseKeyCreationOnDone(
|
||||
CreatingKeyType type,
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
const AuthKeyPtr &persistentKeyUsedForBind) {
|
||||
Expects(_creatingKeys[IndexByType(type)]);
|
||||
Expects(_temporaryKeys[IndexByType(type)] == nullptr);
|
||||
Expects(temporaryKey != nullptr);
|
||||
|
||||
QWriteLocker lock(&_mutex);
|
||||
if (type == CreatingKeyType::Persistent) {
|
||||
_persistentKey = persistentKeyUsedForBind;
|
||||
} else if (_persistentKey != persistentKeyUsedForBind) {
|
||||
return false;
|
||||
}
|
||||
_temporaryKeys[IndexByType(type)] = temporaryKey;
|
||||
_creatingKeys[IndexByType(type)] = false;
|
||||
_connectionInited = false;
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: Dcenter::releaseKeyCreationOnDone(%1, %2, %3)."
|
||||
).arg(NameOfType(type)
|
||||
).arg(temporaryKey ? temporaryKey->keyId() : 0
|
||||
).arg(persistentKeyUsedForBind
|
||||
? persistentKeyUsedForBind->keyId()
|
||||
: 0));
|
||||
return true;
|
||||
}
|
||||
|
||||
void Dcenter::releaseKeyCreationOnFail(CreatingKeyType type) {
|
||||
Expects(_creatingKeys[IndexByType(type)]);
|
||||
Expects(_temporaryKeys[IndexByType(type)] == nullptr);
|
||||
|
||||
_creatingKeys[IndexByType(type)] = false;
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
70
Telegram/SourceFiles/mtproto/details/mtproto_dcenter.h
Normal file
70
Telegram/SourceFiles/mtproto/details/mtproto_dcenter.h
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QReadWriteLock>
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class Instance;
|
||||
class AuthKey;
|
||||
using AuthKeyPtr = std::shared_ptr<AuthKey>;
|
||||
enum class DcType;
|
||||
|
||||
namespace details {
|
||||
|
||||
enum class TemporaryKeyType {
|
||||
Regular,
|
||||
MediaCluster
|
||||
};
|
||||
|
||||
enum class CreatingKeyType {
|
||||
None,
|
||||
Persistent,
|
||||
TemporaryRegular,
|
||||
TemporaryMediaCluster
|
||||
};
|
||||
|
||||
[[nodiscard]] TemporaryKeyType TemporaryKeyTypeByDcType(DcType type);
|
||||
|
||||
class Dcenter : public QObject {
|
||||
public:
|
||||
// Main thread.
|
||||
Dcenter(DcId dcId, AuthKeyPtr &&key);
|
||||
|
||||
// Thread-safe.
|
||||
[[nodiscard]] DcId id() const;
|
||||
[[nodiscard]] AuthKeyPtr getPersistentKey() const;
|
||||
[[nodiscard]] AuthKeyPtr getTemporaryKey(TemporaryKeyType type) const;
|
||||
[[nodiscard]] CreatingKeyType acquireKeyCreation(DcType type);
|
||||
bool releaseKeyCreationOnDone(
|
||||
CreatingKeyType type,
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
const AuthKeyPtr &persistentKeyUsedForBind);
|
||||
void releaseKeyCreationOnFail(CreatingKeyType type);
|
||||
bool destroyTemporaryKey(uint64 keyId);
|
||||
bool destroyConfirmedForgottenKey(uint64 keyId);
|
||||
|
||||
[[nodiscard]] bool connectionInited() const;
|
||||
void setConnectionInited(bool connectionInited = true);
|
||||
|
||||
private:
|
||||
static constexpr auto kTemporaryKeysCount = 2;
|
||||
|
||||
const DcId _id = 0;
|
||||
mutable QReadWriteLock _mutex;
|
||||
|
||||
AuthKeyPtr _temporaryKeys[kTemporaryKeysCount];
|
||||
AuthKeyPtr _persistentKey;
|
||||
bool _connectionInited = false;
|
||||
std::atomic<bool> _creatingKeys[kTemporaryKeysCount] = { false };
|
||||
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
367
Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp
Normal file
367
Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.cpp
Normal file
@@ -0,0 +1,367 @@
|
||||
/*
|
||||
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/details/mtproto_domain_resolver.h"
|
||||
|
||||
#include "base/random.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <range/v3/algorithm/shuffle.hpp>
|
||||
#include <range/v3/algorithm/reverse.hpp>
|
||||
#include <range/v3/algorithm/remove.hpp>
|
||||
#include <random>
|
||||
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSendNextTimeout = crl::time(800);
|
||||
constexpr auto kMinTimeToLive = 10 * crl::time(1000);
|
||||
constexpr auto kMaxTimeToLive = 300 * crl::time(1000);
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<QString> &DnsDomains() {
|
||||
static const auto kResult = std::vector<QString>{
|
||||
"google.com",
|
||||
"www.google.com",
|
||||
"google.ru",
|
||||
"www.google.ru",
|
||||
};
|
||||
return kResult;
|
||||
}
|
||||
|
||||
QString GenerateDnsRandomPadding() {
|
||||
constexpr char kValid[] = "abcdefghijklmnopqrstuvwxyz"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
|
||||
auto result = QString();
|
||||
const auto count = [&] {
|
||||
constexpr auto kMinPadding = 13;
|
||||
constexpr auto kMaxPadding = 128;
|
||||
while (true) {
|
||||
const auto result = 1 + (base::RandomValue<uchar>() / 2);
|
||||
Assert(result <= kMaxPadding);
|
||||
if (result >= kMinPadding) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}();
|
||||
result.resize(count);
|
||||
for (auto &ch : result) {
|
||||
ch = kValid[base::RandomValue<uchar>() % (sizeof(kValid) - 1)];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray DnsUserAgent() {
|
||||
static const auto kResult = QByteArray(
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) "
|
||||
"Chrome/142.0.0.0 Safari/537.36");
|
||||
return kResult;
|
||||
}
|
||||
|
||||
std::vector<DnsEntry> ParseDnsResponse(
|
||||
const QByteArray &bytes,
|
||||
std::optional<int> typeRestriction) {
|
||||
if (bytes.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Read and store to "result" all the data bytes from the response:
|
||||
// { ..,
|
||||
// "Answer": [
|
||||
// { .., "data": "bytes1", "TTL": int, .. },
|
||||
// { .., "data": "bytes2", "TTL": int, .. }
|
||||
// ],
|
||||
// .. }
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(bytes, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG(("Config Error: Failed to parse dns response JSON, error: %1"
|
||||
).arg(error.errorString()));
|
||||
return {};
|
||||
} else if (!document.isObject()) {
|
||||
LOG(("Config Error: Not an object received in dns response JSON."));
|
||||
return {};
|
||||
}
|
||||
const auto response = document.object();
|
||||
const auto answerIt = response.find("Answer");
|
||||
if (answerIt == response.constEnd()) {
|
||||
LOG(("Config Error: Could not find Answer in dns response JSON."));
|
||||
return {};
|
||||
} else if (!(*answerIt).isArray()) {
|
||||
LOG(("Config Error: Not an array received "
|
||||
"in Answer in dns response JSON."));
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto array = (*answerIt).toArray();
|
||||
auto result = std::vector<DnsEntry>();
|
||||
for (const auto elem : array) {
|
||||
if (!elem.isObject()) {
|
||||
LOG(("Config Error: Not an object found "
|
||||
"in Answer array in dns response JSON."));
|
||||
continue;
|
||||
}
|
||||
const auto object = elem.toObject();
|
||||
if (typeRestriction) {
|
||||
const auto typeIt = object.find("type");
|
||||
const auto type = int(base::SafeRound((*typeIt).toDouble()));
|
||||
if (!(*typeIt).isDouble()) {
|
||||
LOG(("Config Error: Not a number in type field "
|
||||
"in Answer array in dns response JSON."));
|
||||
continue;
|
||||
} else if (type != *typeRestriction) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
const auto dataIt = object.find("data");
|
||||
if (dataIt == object.constEnd()) {
|
||||
LOG(("Config Error: Could not find data "
|
||||
"in Answer array entry in dns response JSON."));
|
||||
continue;
|
||||
} else if (!(*dataIt).isString()) {
|
||||
LOG(("Config Error: Not a string data found "
|
||||
"in Answer array entry in dns response JSON."));
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto ttlIt = object.find("TTL");
|
||||
const auto ttl = (ttlIt != object.constEnd())
|
||||
? crl::time(base::SafeRound((*ttlIt).toDouble()))
|
||||
: crl::time(0);
|
||||
result.push_back({ (*dataIt).toString(), ttl });
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ServiceWebRequest::ServiceWebRequest(not_null<QNetworkReply*> reply)
|
||||
: reply(reply.get()) {
|
||||
}
|
||||
|
||||
ServiceWebRequest::ServiceWebRequest(ServiceWebRequest &&other)
|
||||
: reply(base::take(other.reply)) {
|
||||
}
|
||||
|
||||
ServiceWebRequest &ServiceWebRequest::operator=(ServiceWebRequest &&other) {
|
||||
if (reply != other.reply) {
|
||||
destroy();
|
||||
reply = base::take(other.reply);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ServiceWebRequest::destroy() {
|
||||
if (const auto value = base::take(reply)) {
|
||||
value->disconnect(
|
||||
value,
|
||||
&QNetworkReply::finished,
|
||||
nullptr,
|
||||
nullptr);
|
||||
value->abort();
|
||||
value->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
ServiceWebRequest::~ServiceWebRequest() {
|
||||
if (reply) {
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
DomainResolver::DomainResolver(Fn<void(
|
||||
const QString &host,
|
||||
const QStringList &ips,
|
||||
crl::time expireAt)> callback)
|
||||
: _callback(std::move(callback)) {
|
||||
_manager.setProxy(QNetworkProxy::NoProxy);
|
||||
}
|
||||
|
||||
void DomainResolver::resolve(const QString &domain) {
|
||||
resolve({ domain, false });
|
||||
resolve({ domain, true });
|
||||
}
|
||||
|
||||
void DomainResolver::resolve(const AttemptKey &key) {
|
||||
if (_attempts.find(key) != end(_attempts)) {
|
||||
return;
|
||||
} else if (_requests.find(key) != end(_requests)) {
|
||||
return;
|
||||
}
|
||||
const auto i = _cache.find(key);
|
||||
_lastTimestamp = crl::now();
|
||||
if (i != end(_cache) && i->second.expireAt > _lastTimestamp) {
|
||||
checkExpireAndPushResult(key.domain);
|
||||
return;
|
||||
}
|
||||
|
||||
auto attempts = std::vector<Attempt>();
|
||||
auto domains = DnsDomains();
|
||||
std::random_device rd;
|
||||
ranges::shuffle(domains, std::mt19937(rd()));
|
||||
const auto takeDomain = [&] {
|
||||
const auto result = domains.back();
|
||||
domains.pop_back();
|
||||
return result;
|
||||
};
|
||||
const auto shuffle = [&](int from, int till) {
|
||||
Expects(till > from);
|
||||
|
||||
ranges::shuffle(
|
||||
begin(attempts) + from,
|
||||
begin(attempts) + till,
|
||||
std::mt19937(rd()));
|
||||
};
|
||||
|
||||
attempts.push_back({ Type::Google, "dns.google.com" });
|
||||
attempts.push_back({ Type::Google, takeDomain(), "dns" });
|
||||
attempts.push_back({ Type::Mozilla, "mozilla.cloudflare-dns.com" });
|
||||
while (!domains.empty()) {
|
||||
attempts.push_back({ Type::Google, takeDomain(), "dns" });
|
||||
}
|
||||
|
||||
shuffle(0, 2);
|
||||
|
||||
ranges::reverse(attempts); // We go from last to first.
|
||||
|
||||
_attempts.emplace(key, Attempts{ std::move(attempts) });
|
||||
sendNextRequest(key);
|
||||
}
|
||||
|
||||
void DomainResolver::checkExpireAndPushResult(const QString &domain) {
|
||||
const auto ipv4 = _cache.find({ domain, false });
|
||||
if (ipv4 == end(_cache) || ipv4->second.expireAt <= _lastTimestamp) {
|
||||
return;
|
||||
}
|
||||
auto result = ipv4->second;
|
||||
const auto ipv6 = _cache.find({ domain, true });
|
||||
if (ipv6 != end(_cache) && ipv6->second.expireAt > _lastTimestamp) {
|
||||
result.ips.append(ipv6->second.ips);
|
||||
accumulate_min(result.expireAt, ipv6->second.expireAt);
|
||||
}
|
||||
InvokeQueued(this, [=] {
|
||||
_callback(domain, result.ips, result.expireAt);
|
||||
});
|
||||
}
|
||||
|
||||
void DomainResolver::sendNextRequest(const AttemptKey &key) {
|
||||
auto i = _attempts.find(key);
|
||||
if (i == end(_attempts)) {
|
||||
return;
|
||||
}
|
||||
auto &attempts = i->second;
|
||||
auto &list = attempts.list;
|
||||
const auto attempt = list.back();
|
||||
list.pop_back();
|
||||
|
||||
if (!list.empty()) {
|
||||
base::call_delayed(kSendNextTimeout, &attempts.guard, [=] {
|
||||
sendNextRequest(key);
|
||||
});
|
||||
}
|
||||
performRequest(key, attempt);
|
||||
}
|
||||
|
||||
void DomainResolver::performRequest(
|
||||
const AttemptKey &key,
|
||||
const Attempt &attempt) {
|
||||
auto url = QUrl();
|
||||
url.setScheme("https");
|
||||
auto request = QNetworkRequest();
|
||||
switch (attempt.type) {
|
||||
case Type::Mozilla: {
|
||||
url.setHost(attempt.data);
|
||||
url.setPath("/dns-query");
|
||||
url.setQuery(QStringLiteral("name=%1&type=%2&random_padding=%3"
|
||||
).arg(key.domain
|
||||
).arg(key.ipv6 ? 28 : 1
|
||||
).arg(GenerateDnsRandomPadding()));
|
||||
request.setRawHeader("accept", "application/dns-json");
|
||||
} break;
|
||||
case Type::Google: {
|
||||
url.setHost(attempt.data);
|
||||
url.setPath("/resolve");
|
||||
url.setQuery(QStringLiteral("name=%1&type=%2&random_padding=%3"
|
||||
).arg(key.domain
|
||||
).arg(key.ipv6 ? 28 : 1
|
||||
).arg(GenerateDnsRandomPadding()));
|
||||
if (!attempt.host.isEmpty()) {
|
||||
const auto host = attempt.host + ".google.com";
|
||||
request.setRawHeader("Host", host.toLatin1());
|
||||
}
|
||||
} break;
|
||||
default: Unexpected("Type in DomainResolver::performRequest.");
|
||||
}
|
||||
request.setUrl(url);
|
||||
request.setRawHeader("User-Agent", DnsUserAgent());
|
||||
const auto i = _requests.emplace(
|
||||
key,
|
||||
std::vector<ServiceWebRequest>()).first;
|
||||
const auto reply = i->second.emplace_back(
|
||||
_manager.get(request)
|
||||
).reply;
|
||||
connect(reply, &QNetworkReply::finished, this, [=] {
|
||||
requestFinished(key, reply);
|
||||
});
|
||||
}
|
||||
|
||||
void DomainResolver::requestFinished(
|
||||
const AttemptKey &key,
|
||||
not_null<QNetworkReply*> reply) {
|
||||
const auto result = finalizeRequest(key, reply);
|
||||
const auto response = ParseDnsResponse(result);
|
||||
if (response.empty()) {
|
||||
return;
|
||||
}
|
||||
_requests.erase(key);
|
||||
_attempts.erase(key);
|
||||
|
||||
auto entry = CacheEntry();
|
||||
auto ttl = kMaxTimeToLive;
|
||||
for (const auto &item : response) {
|
||||
entry.ips.push_back(item.data);
|
||||
ttl = std::min(
|
||||
ttl,
|
||||
std::max(item.TTL * crl::time(1000), kMinTimeToLive));
|
||||
}
|
||||
_lastTimestamp = crl::now();
|
||||
entry.expireAt = _lastTimestamp + ttl;
|
||||
_cache[key] = std::move(entry);
|
||||
|
||||
checkExpireAndPushResult(key.domain);
|
||||
}
|
||||
|
||||
QByteArray DomainResolver::finalizeRequest(
|
||||
const AttemptKey &key,
|
||||
not_null<QNetworkReply*> reply) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
DEBUG_LOG(("Resolve Error: Failed to get response, error: %2 (%3)"
|
||||
).arg(reply->errorString()
|
||||
).arg(reply->error()));
|
||||
}
|
||||
const auto result = reply->readAll();
|
||||
const auto i = _requests.find(key);
|
||||
if (i != end(_requests)) {
|
||||
auto &requests = i->second;
|
||||
const auto from = ranges::remove(
|
||||
requests,
|
||||
reply,
|
||||
[](const ServiceWebRequest &request) { return request.reply; });
|
||||
requests.erase(from, end(requests));
|
||||
if (requests.empty()) {
|
||||
_requests.erase(i);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
106
Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.h
Normal file
106
Telegram/SourceFiles/mtproto/details/mtproto_domain_resolver.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <optional>
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
[[nodiscard]] const std::vector<QString> &DnsDomains();
|
||||
[[nodiscard]] QString GenerateDnsRandomPadding();
|
||||
[[nodiscard]] QByteArray DnsUserAgent();
|
||||
|
||||
struct DnsEntry {
|
||||
QString data;
|
||||
crl::time TTL = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::vector<DnsEntry> ParseDnsResponse(
|
||||
const QByteArray &bytes,
|
||||
std::optional<int> typeRestriction = std::nullopt);
|
||||
|
||||
struct ServiceWebRequest {
|
||||
ServiceWebRequest(not_null<QNetworkReply*> reply);
|
||||
ServiceWebRequest(ServiceWebRequest &&other);
|
||||
ServiceWebRequest &operator=(ServiceWebRequest &&other);
|
||||
~ServiceWebRequest();
|
||||
|
||||
void destroy();
|
||||
|
||||
QPointer<QNetworkReply> reply;
|
||||
};
|
||||
|
||||
class DomainResolver : public QObject {
|
||||
public:
|
||||
DomainResolver(Fn<void(
|
||||
const QString &domain,
|
||||
const QStringList &ips,
|
||||
crl::time expireAt)> callback);
|
||||
|
||||
void resolve(const QString &domain);
|
||||
|
||||
private:
|
||||
enum class Type {
|
||||
Mozilla,
|
||||
Google,
|
||||
};
|
||||
struct Attempt {
|
||||
Type type;
|
||||
QString data;
|
||||
QString host;
|
||||
};
|
||||
struct AttemptKey {
|
||||
QString domain;
|
||||
bool ipv6 = false;
|
||||
|
||||
inline bool operator<(const AttemptKey &other) const {
|
||||
return (domain < other.domain)
|
||||
|| (domain == other.domain && !ipv6 && other.ipv6);
|
||||
}
|
||||
inline bool operator==(const AttemptKey &other) const {
|
||||
return (domain == other.domain) && (ipv6 == other.ipv6);
|
||||
}
|
||||
};
|
||||
struct CacheEntry {
|
||||
QStringList ips;
|
||||
crl::time expireAt = 0;
|
||||
};
|
||||
struct Attempts {
|
||||
std::vector<Attempt> list;
|
||||
base::has_weak_ptr guard;
|
||||
};
|
||||
|
||||
void resolve(const AttemptKey &key);
|
||||
void sendNextRequest(const AttemptKey &key);
|
||||
void performRequest(const AttemptKey &key, const Attempt &attempt);
|
||||
void checkExpireAndPushResult(const QString &domain);
|
||||
void requestFinished(
|
||||
const AttemptKey &key,
|
||||
not_null<QNetworkReply*> reply);
|
||||
QByteArray finalizeRequest(
|
||||
const AttemptKey &key,
|
||||
not_null<QNetworkReply*> reply);
|
||||
|
||||
Fn<void(
|
||||
const QString &domain,
|
||||
const QStringList &ips,
|
||||
crl::time expireAt)> _callback;
|
||||
|
||||
QNetworkAccessManager _manager;
|
||||
std::map<AttemptKey, Attempts> _attempts;
|
||||
std::map<AttemptKey, std::vector<ServiceWebRequest>> _requests;
|
||||
std::map<AttemptKey, CacheEntry> _cache;
|
||||
crl::time _lastTimestamp = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
169
Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.cpp
Normal file
169
Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.cpp
Normal file
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
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/details/mtproto_dump_to_text.h"
|
||||
|
||||
#include "scheme-dump_to_text.h"
|
||||
#include "scheme.h"
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
bool DumpToTextCore(DumpToTextBuffer &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons) {
|
||||
switch (mtpTypeId(cons)) {
|
||||
case mtpc_int: {
|
||||
MTPint value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [INT]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_long: {
|
||||
MTPlong value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [LONG]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_int128: {
|
||||
MTPint128 value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.h)).add(" * 2^64 + ").add(QString::number(value.l)).add(" [INT128]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_int256: {
|
||||
MTPint256 value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.h.h)).add(" * 2^192 + ").add(QString::number(value.h.l)).add(" * 2^128 + ").add(QString::number(value.l.h)).add(" * 2 ^ 64 + ").add(QString::number(value.l.l)).add(" [INT256]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_double: {
|
||||
MTPdouble value;
|
||||
if (value.read(from, end, cons)) {
|
||||
to.add(QString::number(value.v)).add(" [DOUBLE]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_string: {
|
||||
MTPstring value;
|
||||
if (value.read(from, end, cons)) {
|
||||
auto strUtf8 = value.v;
|
||||
auto str = QString::fromUtf8(strUtf8);
|
||||
if (str.toUtf8() == strUtf8) {
|
||||
to.add("\"").add(str.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n")).add("\" [STRING]");
|
||||
} else if (strUtf8.size() < 64) {
|
||||
to.add(Logs::mb(strUtf8.constData(), strUtf8.size()).str()).add(" [").add(QString::number(strUtf8.size())).add(" BYTES]");
|
||||
} else {
|
||||
to.add(Logs::mb(strUtf8.constData(), 16).str()).add("... [").add(QString::number(strUtf8.size())).add(" BYTES]");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_vector: {
|
||||
if (from < end) {
|
||||
int32 cnt = *(from++);
|
||||
to.add("[ vector<0x").add(QString::number(vcons, 16)).add("> (").add(QString::number(cnt)).add(")");
|
||||
if (cnt) {
|
||||
to.add("\n").addSpaces(level);
|
||||
for (int32 i = 0; i < cnt; ++i) {
|
||||
to.add(" ");
|
||||
if (!DumpToTextType(to, from, end, vcons, level + 1)) {
|
||||
return false;
|
||||
}
|
||||
to.add(",\n").addSpaces(level);
|
||||
}
|
||||
} else {
|
||||
to.add(" ");
|
||||
}
|
||||
to.add("]");
|
||||
return true;
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_gzip_packed: {
|
||||
MTPstring packed;
|
||||
// read packed string as serialized mtp string type
|
||||
if (!packed.read(from, end)) {
|
||||
return false;
|
||||
}
|
||||
uint32 packedLen = packed.v.size(), unpackedChunk = packedLen;
|
||||
mtpBuffer result; // * 4 because of mtpPrime type
|
||||
result.resize(0);
|
||||
|
||||
z_stream stream;
|
||||
stream.zalloc = nullptr;
|
||||
stream.zfree = nullptr;
|
||||
stream.opaque = nullptr;
|
||||
stream.avail_in = 0;
|
||||
stream.next_in = nullptr;
|
||||
int res = inflateInit2(&stream, 16 + MAX_WBITS);
|
||||
if (res != Z_OK) {
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (stream.avail_out & 0x03) {
|
||||
return false;
|
||||
}
|
||||
result.resize(result.size() - (stream.avail_out >> 2));
|
||||
inflateEnd(&stream);
|
||||
|
||||
if (result.empty()) {
|
||||
return false;
|
||||
}
|
||||
const mtpPrime *newFrom = result.constData(), *newEnd = result.constData() + result.size();
|
||||
to.add("[GZIPPED] ");
|
||||
return DumpToTextType(to, newFrom, newEnd, 0, level);
|
||||
} break;
|
||||
|
||||
default: {
|
||||
for (uint32 i = 1; i < mtpLayerMaxSingle; ++i) {
|
||||
if (cons == mtpLayers[i]) {
|
||||
to.add("[LAYER").add(QString::number(i + 1)).add("] ");
|
||||
return DumpToTextType(to, from, end, 0, level);
|
||||
}
|
||||
}
|
||||
if (cons == mtpc_invokeWithLayer) {
|
||||
if (from >= end) {
|
||||
return false;
|
||||
}
|
||||
int32 layer = *(from++);
|
||||
to.add("[LAYER").add(QString::number(layer)).add("] ");
|
||||
return DumpToTextType(to, from, end, 0, level);
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString DumpToText(const mtpPrime *&from, const mtpPrime *end) {
|
||||
DumpToTextBuffer to;
|
||||
[[maybe_unused]] bool result = DumpToTextType(to, from, end, mtpc_core_message);
|
||||
return QString::fromUtf8(to.p, to.size);
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
82
Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.h
Normal file
82
Telegram/SourceFiles/mtproto/details/mtproto_dump_to_text.h
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/details/mtproto_serialized_request.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
// Human-readable text serialization
|
||||
QString DumpToText(const mtpPrime *&from, const mtpPrime *end);
|
||||
|
||||
struct DumpToTextBuffer {
|
||||
static constexpr auto kBufferSize = 1024 * 1024; // 1 mb start size
|
||||
|
||||
DumpToTextBuffer()
|
||||
: p(new char[kBufferSize])
|
||||
, alloced(kBufferSize) {
|
||||
}
|
||||
~DumpToTextBuffer() {
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
DumpToTextBuffer &add(const QString &data) {
|
||||
auto d = data.toUtf8();
|
||||
return add(d.constData(), d.size());
|
||||
}
|
||||
|
||||
DumpToTextBuffer &add(const char *data, int32 len = -1) {
|
||||
if (len < 0) len = strlen(data);
|
||||
if (!len) return (*this);
|
||||
|
||||
ensureLength(len);
|
||||
memcpy(p + size, data, len);
|
||||
size += len;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
DumpToTextBuffer &addSpaces(int32 level) {
|
||||
int32 len = level * 2;
|
||||
if (!len) return (*this);
|
||||
|
||||
ensureLength(len);
|
||||
for (char *ptr = p + size, *end = ptr + len; ptr != end; ++ptr) {
|
||||
*ptr = ' ';
|
||||
}
|
||||
size += len;
|
||||
return (*this);
|
||||
}
|
||||
|
||||
DumpToTextBuffer &error(const char *problem = "could not decode type") {
|
||||
return add("[ERROR] (").add(problem).add(")");
|
||||
}
|
||||
|
||||
void ensureLength(int32 add) {
|
||||
if (size + add <= alloced) return;
|
||||
|
||||
int32 newsize = size + add;
|
||||
if (newsize % kBufferSize) {
|
||||
newsize += kBufferSize - (newsize % kBufferSize);
|
||||
}
|
||||
char *b = new char[newsize];
|
||||
memcpy(b, p, size);
|
||||
alloced = newsize;
|
||||
delete[] p;
|
||||
p = b;
|
||||
}
|
||||
|
||||
char *p = nullptr;
|
||||
int size = 0;
|
||||
int alloced = 0;
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] bool DumpToTextCore(DumpToTextBuffer &to, const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons, uint32 level, mtpPrime vcons = 0);
|
||||
|
||||
} // namespace MTP::details
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
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/details/mtproto_received_ids_manager.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
ReceivedIdsManager::Result ReceivedIdsManager::registerMsgId(
|
||||
mtpMsgId msgId,
|
||||
bool needAck) {
|
||||
const auto i = _idsNeedAck.find(msgId);
|
||||
if (i != _idsNeedAck.end()) {
|
||||
MTP_LOG(-1, ("No need to handle - %1 already is in map").arg(msgId));
|
||||
return Result::Duplicate;
|
||||
} else if (_idsNeedAck.size() < kIdsBufferSize || msgId > min()) {
|
||||
_idsNeedAck.emplace(msgId, needAck);
|
||||
return Result::Success;
|
||||
}
|
||||
MTP_LOG(-1, ("Reset on too old - %1 < min = %2").arg(msgId).arg(min()));
|
||||
return Result::TooOld;
|
||||
}
|
||||
|
||||
mtpMsgId ReceivedIdsManager::min() const {
|
||||
return _idsNeedAck.empty() ? 0 : _idsNeedAck.begin()->first;
|
||||
}
|
||||
|
||||
mtpMsgId ReceivedIdsManager::max() const {
|
||||
auto end = _idsNeedAck.end();
|
||||
return _idsNeedAck.empty() ? 0 : (--end)->first;
|
||||
}
|
||||
|
||||
ReceivedIdsManager::State ReceivedIdsManager::lookup(mtpMsgId msgId) const {
|
||||
auto i = _idsNeedAck.find(msgId);
|
||||
if (i == _idsNeedAck.end()) {
|
||||
return State::NotFound;
|
||||
}
|
||||
return i->second ? State::NeedsAck : State::NoAckNeeded;
|
||||
}
|
||||
|
||||
void ReceivedIdsManager::shrink() {
|
||||
auto size = _idsNeedAck.size();
|
||||
while (size-- > kIdsBufferSize) {
|
||||
_idsNeedAck.erase(_idsNeedAck.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void ReceivedIdsManager::clear() {
|
||||
_idsNeedAck.clear();
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/flat_map.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
// Received msgIds and wereAcked msgIds count stored.
|
||||
inline constexpr auto kIdsBufferSize = 400;
|
||||
|
||||
class ReceivedIdsManager final {
|
||||
public:
|
||||
enum class State {
|
||||
NotFound,
|
||||
NeedsAck,
|
||||
NoAckNeeded,
|
||||
};
|
||||
enum class Result {
|
||||
Success,
|
||||
Duplicate,
|
||||
TooOld,
|
||||
};
|
||||
|
||||
[[nodiscard]] Result registerMsgId(mtpMsgId msgId, bool needAck);
|
||||
[[nodiscard]] mtpMsgId min() const;
|
||||
[[nodiscard]] mtpMsgId max() const;
|
||||
[[nodiscard]] State lookup(mtpMsgId msgId) const;
|
||||
|
||||
void shrink();
|
||||
void clear();
|
||||
|
||||
private:
|
||||
base::flat_map<mtpMsgId, bool> _idsNeedAck;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
266
Telegram/SourceFiles/mtproto/details/mtproto_rsa_public_key.cpp
Normal file
266
Telegram/SourceFiles/mtproto/details/mtproto_rsa_public_key.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
/*
|
||||
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/details/mtproto_rsa_public_key.h"
|
||||
|
||||
#include "base/openssl_help.h"
|
||||
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
enum class Format {
|
||||
RSAPublicKey,
|
||||
RSA_PUBKEY,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct BIODeleter {
|
||||
void operator()(BIO *value) {
|
||||
BIO_free(value);
|
||||
}
|
||||
};
|
||||
|
||||
Format GuessFormat(bytes::const_span key) {
|
||||
const auto array = QByteArray::fromRawData(
|
||||
reinterpret_cast<const char*>(key.data()),
|
||||
key.size());
|
||||
if (array.indexOf("BEGIN RSA PUBLIC KEY") >= 0) {
|
||||
return Format::RSAPublicKey;
|
||||
} else if (array.indexOf("BEGIN PUBLIC KEY") >= 0) {
|
||||
return Format::RSA_PUBKEY;
|
||||
}
|
||||
return Format::Unknown;
|
||||
}
|
||||
|
||||
RSA *CreateRaw(bytes::const_span key) {
|
||||
const auto format = GuessFormat(key);
|
||||
const auto bio = std::unique_ptr<BIO, BIODeleter>{
|
||||
BIO_new_mem_buf(
|
||||
const_cast<gsl::byte*>(key.data()),
|
||||
key.size()),
|
||||
};
|
||||
switch (format) {
|
||||
case Format::RSAPublicKey:
|
||||
return PEM_read_bio_RSAPublicKey(bio.get(), nullptr, nullptr, nullptr);
|
||||
case Format::RSA_PUBKEY:
|
||||
return PEM_read_bio_RSA_PUBKEY(bio.get(), nullptr, nullptr, nullptr);
|
||||
}
|
||||
Unexpected("format in RSAPublicKey::Private::Create.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class RSAPublicKey::Private {
|
||||
public:
|
||||
explicit Private(bytes::const_span key);
|
||||
Private(bytes::const_span nBytes, bytes::const_span eBytes);
|
||||
~Private();
|
||||
|
||||
[[nodiscard]] bool valid() const;
|
||||
[[nodiscard]] uint64 fingerprint() const;
|
||||
[[nodiscard]] bytes::vector getN() const;
|
||||
[[nodiscard]] bytes::vector getE() const;
|
||||
[[nodiscard]] bytes::vector encrypt(bytes::const_span data) const;
|
||||
[[nodiscard]] bytes::vector decrypt(bytes::const_span data) const;
|
||||
[[nodiscard]] bytes::vector encryptOAEPpadding(
|
||||
bytes::const_span data) const;
|
||||
|
||||
private:
|
||||
void computeFingerprint();
|
||||
[[nodiscard]] static bytes::vector ToBytes(const BIGNUM *number);
|
||||
|
||||
RSA *_rsa = nullptr;
|
||||
uint64 _fingerprint = 0;
|
||||
|
||||
};
|
||||
|
||||
RSAPublicKey::Private::Private(bytes::const_span key)
|
||||
: _rsa(CreateRaw(key)) {
|
||||
if (_rsa) {
|
||||
computeFingerprint();
|
||||
}
|
||||
}
|
||||
|
||||
RSAPublicKey::Private::Private(bytes::const_span nBytes, bytes::const_span eBytes)
|
||||
: _rsa(RSA_new()) {
|
||||
if (_rsa) {
|
||||
const auto n = openssl::BigNum(nBytes).takeRaw();
|
||||
const auto e = openssl::BigNum(eBytes).takeRaw();
|
||||
const auto valid = (n != nullptr) && (e != nullptr);
|
||||
// We still pass both values to RSA_set0_key() so that even
|
||||
// if only one of them is valid RSA would take ownership of it.
|
||||
if (!RSA_set0_key(_rsa, n, e, nullptr) || !valid) {
|
||||
RSA_free(base::take(_rsa));
|
||||
} else {
|
||||
computeFingerprint();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RSAPublicKey::Private::valid() const {
|
||||
return _rsa != nullptr;
|
||||
}
|
||||
|
||||
uint64 RSAPublicKey::Private::fingerprint() const {
|
||||
return _fingerprint;
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::Private::getN() const {
|
||||
Expects(valid());
|
||||
|
||||
const BIGNUM *n;
|
||||
RSA_get0_key(_rsa, &n, nullptr, nullptr);
|
||||
return ToBytes(n);
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::Private::getE() const {
|
||||
Expects(valid());
|
||||
|
||||
const BIGNUM *e;
|
||||
RSA_get0_key(_rsa, nullptr, &e, nullptr);
|
||||
return ToBytes(e);
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::Private::encrypt(bytes::const_span data) const {
|
||||
Expects(valid());
|
||||
|
||||
constexpr auto kEncryptSize = 256;
|
||||
auto result = bytes::vector(kEncryptSize, gsl::byte{});
|
||||
auto res = RSA_public_encrypt(kEncryptSize, reinterpret_cast<const unsigned char*>(data.data()), reinterpret_cast<unsigned char*>(result.data()), _rsa, RSA_NO_PADDING);
|
||||
if (res < 0 || res > kEncryptSize) {
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
|
||||
LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(fingerprint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0)));
|
||||
return {};
|
||||
} else if (auto zeroBytes = kEncryptSize - res) {
|
||||
auto resultBytes = gsl::make_span(result);
|
||||
bytes::move(resultBytes.subspan(zeroBytes, res), resultBytes.subspan(0, res));
|
||||
bytes::set_with_const(resultBytes.subspan(0, zeroBytes), gsl::byte{});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::Private::decrypt(bytes::const_span data) const {
|
||||
Expects(valid());
|
||||
|
||||
constexpr auto kDecryptSize = 256;
|
||||
auto result = bytes::vector(kDecryptSize, gsl::byte{});
|
||||
auto res = RSA_public_decrypt(kDecryptSize, reinterpret_cast<const unsigned char*>(data.data()), reinterpret_cast<unsigned char*>(result.data()), _rsa, RSA_NO_PADDING);
|
||||
if (res < 0 || res > kDecryptSize) {
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
|
||||
LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(fingerprint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0)));
|
||||
return {};
|
||||
} else if (auto zeroBytes = kDecryptSize - res) {
|
||||
auto resultBytes = gsl::make_span(result);
|
||||
bytes::move(resultBytes.subspan(zeroBytes - res, res), resultBytes.subspan(0, res));
|
||||
bytes::set_with_const(resultBytes.subspan(0, zeroBytes - res), gsl::byte{});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::Private::encryptOAEPpadding(bytes::const_span data) const {
|
||||
Expects(valid());
|
||||
|
||||
const auto resultSize = RSA_size(_rsa);
|
||||
auto result = bytes::vector(resultSize, gsl::byte{});
|
||||
const auto encryptedSize = RSA_public_encrypt(
|
||||
data.size(),
|
||||
reinterpret_cast<const unsigned char*>(data.data()),
|
||||
reinterpret_cast<unsigned char*>(result.data()),
|
||||
_rsa,
|
||||
RSA_PKCS1_OAEP_PADDING);
|
||||
if (encryptedSize != resultSize) {
|
||||
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, nullptr);
|
||||
LOG(("RSA Error: RSA_public_encrypt failed, "
|
||||
"key fp: %1, result: %2, error: %3"
|
||||
).arg(fingerprint()
|
||||
).arg(encryptedSize
|
||||
).arg(ERR_error_string(ERR_get_error(), 0)
|
||||
));
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
RSAPublicKey::Private::~Private() {
|
||||
RSA_free(_rsa);
|
||||
}
|
||||
|
||||
void RSAPublicKey::Private::computeFingerprint() {
|
||||
Expects(valid());
|
||||
|
||||
const BIGNUM *n, *e;
|
||||
mtpBuffer string;
|
||||
RSA_get0_key(_rsa, &n, &e, nullptr);
|
||||
MTP_bytes(ToBytes(n)).write(string);
|
||||
MTP_bytes(ToBytes(e)).write(string);
|
||||
|
||||
bytes::array<20> sha1Buffer;
|
||||
openssl::Sha1To(sha1Buffer, bytes::make_span(string));
|
||||
_fingerprint = *(uint64*)(sha1Buffer.data() + 12);
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::Private::ToBytes(const BIGNUM *number) {
|
||||
auto size = BN_num_bytes(number);
|
||||
auto result = bytes::vector(size, gsl::byte{});
|
||||
BN_bn2bin(number, reinterpret_cast<unsigned char*>(result.data()));
|
||||
return result;
|
||||
}
|
||||
|
||||
RSAPublicKey::RSAPublicKey(bytes::const_span key)
|
||||
: _private(std::make_shared<Private>(key)) {
|
||||
}
|
||||
|
||||
RSAPublicKey::RSAPublicKey(
|
||||
bytes::const_span nBytes,
|
||||
bytes::const_span eBytes)
|
||||
: _private(std::make_shared<Private>(nBytes, eBytes)) {
|
||||
}
|
||||
|
||||
bool RSAPublicKey::empty() const {
|
||||
return !_private;
|
||||
}
|
||||
|
||||
bool RSAPublicKey::valid() const {
|
||||
return !empty() && _private->valid();
|
||||
}
|
||||
|
||||
uint64 RSAPublicKey::fingerprint() const {
|
||||
Expects(valid());
|
||||
|
||||
return _private->fingerprint();
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::getN() const {
|
||||
Expects(valid());
|
||||
|
||||
return _private->getN();
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::getE() const {
|
||||
Expects(valid());
|
||||
|
||||
return _private->getE();
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::encrypt(bytes::const_span data) const {
|
||||
Expects(valid());
|
||||
|
||||
return _private->encrypt(data);
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::decrypt(bytes::const_span data) const {
|
||||
Expects(valid());
|
||||
|
||||
return _private->decrypt(data);
|
||||
}
|
||||
|
||||
bytes::vector RSAPublicKey::encryptOAEPpadding(
|
||||
bytes::const_span data) const {
|
||||
return _private->encryptOAEPpadding(data);
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/bytes.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
// this class holds an RSA public key and can encrypt fixed-size messages with it
|
||||
class RSAPublicKey final {
|
||||
public:
|
||||
RSAPublicKey() = default;
|
||||
RSAPublicKey(bytes::const_span nBytes, bytes::const_span eBytes);
|
||||
RSAPublicKey(RSAPublicKey &&other) = default;
|
||||
RSAPublicKey(const RSAPublicKey &other) = default;
|
||||
RSAPublicKey &operator=(RSAPublicKey &&other) = default;
|
||||
RSAPublicKey &operator=(const RSAPublicKey &other) = default;
|
||||
|
||||
// key in "-----BEGIN RSA PUBLIC KEY----- ..." format
|
||||
// or in "-----BEGIN PUBLIC KEY----- ..." format
|
||||
explicit RSAPublicKey(bytes::const_span key);
|
||||
|
||||
[[nodiscard]] bool empty() const;
|
||||
[[nodiscard]] bool valid() const;
|
||||
[[nodiscard]] uint64 fingerprint() const;
|
||||
[[nodiscard]] bytes::vector getN() const;
|
||||
[[nodiscard]] bytes::vector getE() const;
|
||||
|
||||
// data has exactly 256 chars to be encrypted
|
||||
[[nodiscard]] bytes::vector encrypt(bytes::const_span data) const;
|
||||
|
||||
// data has exactly 256 chars to be decrypted
|
||||
[[nodiscard]] bytes::vector decrypt(bytes::const_span data) const;
|
||||
|
||||
// data has lequal than 215 chars to be decrypted
|
||||
[[nodiscard]] bytes::vector encryptOAEPpadding(bytes::const_span data) const;
|
||||
|
||||
private:
|
||||
class Private;
|
||||
std::shared_ptr<Private> _private;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
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/details/mtproto_serialized_request.h"
|
||||
|
||||
#include "base/random.h"
|
||||
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
uint32 CountPaddingPrimesCount(
|
||||
uint32 requestSize,
|
||||
bool forAuthKeyInner) {
|
||||
if (forAuthKeyInner) {
|
||||
return ((8 + requestSize) & 0x03)
|
||||
? (4 - ((8 + requestSize) & 0x03))
|
||||
: 0;
|
||||
}
|
||||
auto result = ((8 + requestSize) & 0x03)
|
||||
? (4 - ((8 + requestSize) & 0x03))
|
||||
: 0;
|
||||
|
||||
// At least 12 bytes of random padding.
|
||||
if (result < 3) {
|
||||
result += 4;
|
||||
}
|
||||
|
||||
// Some more random padding.
|
||||
return result + ((base::RandomValue<uchar>() & 0x0F) << 2);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SerializedRequest::SerializedRequest(const RequestConstructHider::Tag &tag)
|
||||
: _data(std::make_shared<RequestData>(tag)) {
|
||||
}
|
||||
|
||||
SerializedRequest SerializedRequest::Prepare(
|
||||
uint32 size,
|
||||
uint32 reserveSize) {
|
||||
Expects(size > 0);
|
||||
|
||||
const auto finalSize = std::max(size, reserveSize);
|
||||
|
||||
auto result = SerializedRequest(RequestConstructHider::Tag{});
|
||||
result->reserve(kMessageBodyPosition + finalSize);
|
||||
result->resize(kMessageBodyPosition);
|
||||
result->back() = (size << 2);
|
||||
result->lastSentTime = crl::now();
|
||||
return result;
|
||||
}
|
||||
|
||||
RequestData *SerializedRequest::operator->() const {
|
||||
Expects(_data != nullptr);
|
||||
|
||||
return _data.get();
|
||||
}
|
||||
|
||||
RequestData &SerializedRequest::operator*() const {
|
||||
Expects(_data != nullptr);
|
||||
|
||||
return *_data;
|
||||
}
|
||||
|
||||
SerializedRequest::operator bool() const {
|
||||
return (_data != nullptr);
|
||||
}
|
||||
|
||||
void SerializedRequest::setMsgId(mtpMsgId msgId) {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
memcpy(_data->data() + kMessageIdPosition, &msgId, sizeof(mtpMsgId));
|
||||
}
|
||||
|
||||
mtpMsgId SerializedRequest::getMsgId() const {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
return *(mtpMsgId*)(_data->constData() + kMessageIdPosition);
|
||||
}
|
||||
|
||||
void SerializedRequest::setSeqNo(uint32 seqNo) {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
(*_data)[kSeqNoPosition] = mtpPrime(seqNo);
|
||||
}
|
||||
|
||||
uint32 SerializedRequest::getSeqNo() const {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
return uint32((*_data)[kSeqNoPosition]);
|
||||
}
|
||||
|
||||
void SerializedRequest::addPadding(bool forAuthKeyInner) {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
const auto requestSize = (tl::count_length(*this) >> 2);
|
||||
const auto padding = CountPaddingPrimesCount(
|
||||
requestSize,
|
||||
forAuthKeyInner);
|
||||
const auto fullSize = kMessageBodyPosition + requestSize + padding;
|
||||
if (uint32(_data->size()) != fullSize) {
|
||||
_data->resize(fullSize);
|
||||
if (padding > 0) {
|
||||
bytes::set_random(bytes::make_span(*_data).subspan(
|
||||
(fullSize - padding) * sizeof(mtpPrime)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32 SerializedRequest::messageSize() const {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
const auto ints = (tl::count_length(*this) >> 2);
|
||||
return kMessageIdInts + kSeqNoInts + kMessageLengthInts + ints;
|
||||
}
|
||||
|
||||
bool SerializedRequest::needAck() const {
|
||||
Expects(_data != nullptr);
|
||||
Expects(_data->size() > kMessageBodyPosition);
|
||||
|
||||
const auto type = mtpTypeId((*_data)[kMessageBodyPosition]);
|
||||
switch (type) {
|
||||
case mtpc_msg_container:
|
||||
case mtpc_msgs_ack:
|
||||
case mtpc_http_wait:
|
||||
case mtpc_bad_msg_notification:
|
||||
case mtpc_msgs_all_info:
|
||||
case mtpc_msgs_state_info:
|
||||
case mtpc_msg_detailed_info:
|
||||
case mtpc_msg_new_detailed_info:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t SerializedRequest::sizeInBytes() const {
|
||||
Expects(!_data || _data->size() > kMessageBodyPosition);
|
||||
return _data ? (*_data)[kMessageLengthPosition] : 0;
|
||||
}
|
||||
|
||||
const void *SerializedRequest::dataInBytes() const {
|
||||
Expects(!_data || _data->size() > kMessageBodyPosition);
|
||||
return _data ? (_data->constData() + kMessageBodyPosition) : nullptr;
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
|
||||
#include <crl/crl_time.h>
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
|
||||
class RequestData;
|
||||
class SerializedRequest;
|
||||
|
||||
class RequestConstructHider {
|
||||
struct Tag {};
|
||||
friend class RequestData;
|
||||
friend class SerializedRequest;
|
||||
};
|
||||
|
||||
class SerializedRequest {
|
||||
public:
|
||||
SerializedRequest() = default;
|
||||
|
||||
static constexpr auto kSaltInts = 2;
|
||||
static constexpr auto kSessionIdInts = 2;
|
||||
static constexpr auto kMessageIdPosition = kSaltInts + kSessionIdInts;
|
||||
static constexpr auto kMessageIdInts = 2;
|
||||
static constexpr auto kSeqNoPosition = kMessageIdPosition
|
||||
+ kMessageIdInts;
|
||||
static constexpr auto kSeqNoInts = 1;
|
||||
static constexpr auto kMessageLengthPosition = kSeqNoPosition
|
||||
+ kSeqNoInts;
|
||||
static constexpr auto kMessageLengthInts = 1;
|
||||
static constexpr auto kMessageBodyPosition = kMessageLengthPosition
|
||||
+ kMessageLengthInts;
|
||||
|
||||
static SerializedRequest Prepare(uint32 size, uint32 reserveSize = 0);
|
||||
|
||||
template <
|
||||
typename Request,
|
||||
typename = std::enable_if_t<tl::is_boxed_v<Request>>>
|
||||
static SerializedRequest Serialize(const Request &request);
|
||||
|
||||
// For template MTP requests and MTPBoxed instantiation.
|
||||
template <typename Accumulator>
|
||||
void write(Accumulator &to) const {
|
||||
if (const auto size = sizeInBytes()) {
|
||||
tl::Writer<Accumulator>::PutBytes(to, dataInBytes(), size);
|
||||
}
|
||||
}
|
||||
|
||||
RequestData *operator->() const;
|
||||
RequestData &operator*() const;
|
||||
explicit operator bool() const;
|
||||
|
||||
void setMsgId(mtpMsgId msgId);
|
||||
[[nodiscard]] mtpMsgId getMsgId() const;
|
||||
|
||||
void setSeqNo(uint32 seqNo);
|
||||
[[nodiscard]] uint32 getSeqNo() const;
|
||||
|
||||
void addPadding(bool forAuthKeyInner);
|
||||
[[nodiscard]] uint32 messageSize() const;
|
||||
|
||||
[[nodiscard]] bool needAck() const;
|
||||
|
||||
using ResponseType = void; // don't know real response type =(
|
||||
|
||||
private:
|
||||
explicit SerializedRequest(const RequestConstructHider::Tag &);
|
||||
|
||||
[[nodiscard]] size_t sizeInBytes() const;
|
||||
[[nodiscard]] const void *dataInBytes() const;
|
||||
|
||||
std::shared_ptr<RequestData> _data;
|
||||
|
||||
};
|
||||
|
||||
class RequestData : public mtpBuffer {
|
||||
public:
|
||||
explicit RequestData(const RequestConstructHider::Tag &) {
|
||||
}
|
||||
|
||||
SerializedRequest after;
|
||||
crl::time lastSentTime = 0;
|
||||
mtpRequestId requestId = 0;
|
||||
bool needsLayer = false;
|
||||
bool forceSendInContainer = false;
|
||||
|
||||
};
|
||||
|
||||
template <typename Request, typename>
|
||||
SerializedRequest SerializedRequest::Serialize(const Request &request) {
|
||||
const auto requestSize = tl::count_length(request) >> 2;
|
||||
auto serialized = Prepare(requestSize);
|
||||
request.template write<mtpBuffer>(*serialized);
|
||||
return serialized;
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
125
Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.cpp
Normal file
125
Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
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/details/mtproto_tcp_socket.h"
|
||||
|
||||
#include "base/invoke_queued.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
TcpSocket::TcpSocket(
|
||||
not_null<QThread*> thread,
|
||||
const QNetworkProxy &proxy,
|
||||
bool protocolForFiles)
|
||||
: AbstractSocket(thread) {
|
||||
_socket.moveToThread(thread);
|
||||
_socket.setProxy(proxy);
|
||||
if (protocolForFiles) {
|
||||
_socket.setSocketOption(
|
||||
QAbstractSocket::SendBufferSizeSocketOption,
|
||||
kFilesSendBufferSize);
|
||||
_socket.setSocketOption(
|
||||
QAbstractSocket::ReceiveBufferSizeSocketOption,
|
||||
kFilesReceiveBufferSize);
|
||||
}
|
||||
const auto wrap = [&](auto handler) {
|
||||
return [=](auto &&...args) {
|
||||
InvokeQueued(this, [=] { handler(args...); });
|
||||
};
|
||||
};
|
||||
using Error = QAbstractSocket::SocketError;
|
||||
connect(
|
||||
&_socket,
|
||||
&QTcpSocket::connected,
|
||||
wrap([=] { _connected.fire({}); }));
|
||||
connect(
|
||||
&_socket,
|
||||
&QTcpSocket::disconnected,
|
||||
wrap([=] { _disconnected.fire({}); }));
|
||||
connect(
|
||||
&_socket,
|
||||
&QTcpSocket::readyRead,
|
||||
wrap([=] { _readyRead.fire({}); }));
|
||||
connect(
|
||||
&_socket,
|
||||
&QAbstractSocket::errorOccurred,
|
||||
wrap([=](Error e) { handleError(e); }));
|
||||
}
|
||||
|
||||
void TcpSocket::connectToHost(const QString &address, int port) {
|
||||
_socket.connectToHost(address, port);
|
||||
}
|
||||
|
||||
bool TcpSocket::isGoodStartNonce(bytes::const_span nonce) {
|
||||
Expects(nonce.size() >= 2 * sizeof(uint32));
|
||||
|
||||
const auto bytes = nonce.data();
|
||||
const auto zero = *reinterpret_cast<const uchar*>(bytes);
|
||||
const auto first = *reinterpret_cast<const uint32*>(bytes);
|
||||
const auto second = *(reinterpret_cast<const uint32*>(bytes) + 1);
|
||||
const auto reserved01 = 0x000000EFU;
|
||||
const auto reserved11 = 0x44414548U;
|
||||
const auto reserved12 = 0x54534F50U;
|
||||
const auto reserved13 = 0x20544547U;
|
||||
const auto reserved14 = 0xEEEEEEEEU;
|
||||
const auto reserved15 = 0xDDDDDDDDU;
|
||||
const auto reserved16 = 0x02010316U;
|
||||
const auto reserved21 = 0x00000000U;
|
||||
return (zero != reserved01)
|
||||
&& (first != reserved11)
|
||||
&& (first != reserved12)
|
||||
&& (first != reserved13)
|
||||
&& (first != reserved14)
|
||||
&& (first != reserved15)
|
||||
&& (first != reserved16)
|
||||
&& (second != reserved21);
|
||||
}
|
||||
|
||||
void TcpSocket::timedOut() {
|
||||
}
|
||||
|
||||
bool TcpSocket::isConnected() {
|
||||
return (_socket.state() == QAbstractSocket::ConnectedState);
|
||||
}
|
||||
|
||||
bool TcpSocket::hasBytesAvailable() {
|
||||
return _socket.bytesAvailable() > 0;
|
||||
}
|
||||
|
||||
int64 TcpSocket::read(bytes::span buffer) {
|
||||
return _socket.read(
|
||||
reinterpret_cast<char*>(buffer.data()),
|
||||
buffer.size());
|
||||
}
|
||||
|
||||
void TcpSocket::write(bytes::const_span prefix, bytes::const_span buffer) {
|
||||
Expects(!buffer.empty());
|
||||
|
||||
if (!prefix.empty()) {
|
||||
_socket.write(
|
||||
reinterpret_cast<const char*>(prefix.data()),
|
||||
prefix.size());
|
||||
}
|
||||
_socket.write(
|
||||
reinterpret_cast<const char*>(buffer.data()),
|
||||
buffer.size());
|
||||
}
|
||||
|
||||
int32 TcpSocket::debugState() {
|
||||
return _socket.state();
|
||||
}
|
||||
|
||||
QString TcpSocket::debugPostfix() const {
|
||||
return QString();
|
||||
}
|
||||
|
||||
void TcpSocket::handleError(int errorCode) {
|
||||
logError(errorCode, _socket.errorString());
|
||||
_error.fire({});
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
39
Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.h
Normal file
39
Telegram/SourceFiles/mtproto/details/mtproto_tcp_socket.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/details/mtproto_abstract_socket.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
class TcpSocket final : public AbstractSocket {
|
||||
public:
|
||||
TcpSocket(
|
||||
not_null<QThread*> thread,
|
||||
const QNetworkProxy &proxy,
|
||||
bool protocolForFiles);
|
||||
|
||||
void connectToHost(const QString &address, int port) override;
|
||||
bool isGoodStartNonce(bytes::const_span nonce) override;
|
||||
void timedOut() override;
|
||||
bool isConnected() override;
|
||||
bool hasBytesAvailable() override;
|
||||
int64 read(bytes::span buffer) override;
|
||||
void write(bytes::const_span prefix, bytes::const_span buffer) override;
|
||||
|
||||
int32 debugState() override;
|
||||
QString debugPostfix() const override;
|
||||
|
||||
private:
|
||||
void handleError(int errorCode);
|
||||
|
||||
QTcpSocket _socket;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
944
Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp
Normal file
944
Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.cpp
Normal file
@@ -0,0 +1,944 @@
|
||||
/*
|
||||
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/details/mtproto_tls_socket.h"
|
||||
|
||||
#include "mtproto/details/mtproto_tcp_socket.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/invoke_queued.h"
|
||||
#include "base/random.h"
|
||||
#include "base/unixtime.h"
|
||||
|
||||
#include <QtCore/QtEndian>
|
||||
#include <range/v3/algorithm/reverse.hpp>
|
||||
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxGrease = 8;
|
||||
constexpr auto kClientHelloLimit = 2048;
|
||||
constexpr auto kHelloDigestLength = 32;
|
||||
constexpr auto kLengthSize = sizeof(uint16);
|
||||
const auto kServerHelloPart1 = qstr("\x16\x03\x03");
|
||||
const auto kServerHelloPart3 = qstr("\x14\x03\x03\x00\x01\x01\x17\x03\x03");
|
||||
constexpr auto kServerHelloDigestPosition = 11;
|
||||
const auto kServerHeader = qstr("\x17\x03\x03");
|
||||
constexpr auto kClientPartSize = 2878;
|
||||
const auto kClientPrefix = qstr("\x14\x03\x03\x00\x01\x01");
|
||||
const auto kClientHeader = qstr("\x17\x03\x03");
|
||||
|
||||
using BigNum = openssl::BigNum;
|
||||
using BigNumContext = openssl::Context;
|
||||
|
||||
[[nodiscard]] MTPTlsClientHello PrepareClientHelloRules() {
|
||||
using Scope = QVector<MTPTlsBlock>;
|
||||
using Permutation = std::vector<Scope>;
|
||||
using StackElement = std::variant<Scope, Permutation>;
|
||||
auto stack = std::vector<StackElement>();
|
||||
const auto pushToBack = [&](MTPTlsBlock &&block) {
|
||||
Expects(!stack.empty());
|
||||
|
||||
if (const auto scope = std::get_if<Scope>(&stack.back())) {
|
||||
scope->push_back(std::move(block));
|
||||
} else {
|
||||
auto &permutation = v::get<Permutation>(stack.back());
|
||||
Assert(!permutation.empty());
|
||||
permutation.back().push_back(std::move(block));
|
||||
}
|
||||
};
|
||||
const auto S = [&](QByteArray data) {
|
||||
pushToBack(MTP_tlsBlockString(MTP_bytes(data)));
|
||||
};
|
||||
const auto Z = [&](int length) {
|
||||
pushToBack(MTP_tlsBlockZero(MTP_int(length)));
|
||||
};
|
||||
const auto G = [&](int seed) {
|
||||
pushToBack(MTP_tlsBlockGrease(MTP_int(seed)));
|
||||
};
|
||||
const auto R = [&](int length) {
|
||||
pushToBack(MTP_tlsBlockRandom(MTP_int(length)));
|
||||
};
|
||||
const auto D = [&] {
|
||||
pushToBack(MTP_tlsBlockDomain());
|
||||
};
|
||||
const auto K = [&] {
|
||||
pushToBack(MTP_tlsBlockPublicKey());
|
||||
};
|
||||
const auto M = [&] {
|
||||
pushToBack(MTP_tlsBlockM());
|
||||
};
|
||||
const auto E = [&] {
|
||||
pushToBack(MTP_tlsBlockE());
|
||||
};
|
||||
const auto P = [&] {
|
||||
pushToBack(MTP_tlsBlockPadding());
|
||||
};
|
||||
const auto OpenScope = [&] {
|
||||
stack.emplace_back(Scope());
|
||||
};
|
||||
const auto CloseScope = [&] {
|
||||
Expects(stack.size() > 1);
|
||||
Expects(v::is<Scope>(stack.back()));
|
||||
|
||||
const auto blocks = std::move(v::get<Scope>(stack.back()));
|
||||
stack.pop_back();
|
||||
pushToBack(MTP_tlsBlockScope(MTP_vector<MTPTlsBlock>(blocks)));
|
||||
};
|
||||
const auto OpenPermutation = [&] {
|
||||
stack.emplace_back(Permutation());
|
||||
};
|
||||
const auto ClosePermutation = [&] {
|
||||
Expects(stack.size() > 1);
|
||||
Expects(v::is<Permutation>(stack.back()));
|
||||
|
||||
const auto list = std::move(v::get<Permutation>(stack.back()));
|
||||
stack.pop_back();
|
||||
|
||||
const auto wrapped = list | ranges::views::transform([](
|
||||
const QVector<MTPTlsBlock> &elements) {
|
||||
return MTP_vector<MTPTlsBlock>(elements);
|
||||
}) | ranges::to<QVector<MTPVector<MTPTlsBlock>>>();
|
||||
|
||||
pushToBack(MTP_tlsBlockPermutation(
|
||||
MTP_vector<MTPVector<MTPTlsBlock>>(wrapped)));
|
||||
};
|
||||
const auto StartPermutationElement = [&] {
|
||||
Expects(stack.size() > 1);
|
||||
Expects(v::is<Permutation>(stack.back()));
|
||||
|
||||
v::get<Permutation>(stack.back()).emplace_back();
|
||||
};
|
||||
const auto Finish = [&] {
|
||||
Expects(stack.size() == 1);
|
||||
Expects(v::is<Scope>(stack.back()));
|
||||
|
||||
return v::get<Scope>(stack.back());
|
||||
};
|
||||
|
||||
stack.emplace_back(Scope());
|
||||
|
||||
S("\x16\x03\x01"_q);
|
||||
OpenScope();
|
||||
S("\x01\x00"_q);
|
||||
OpenScope();
|
||||
S("\x03\x03"_q);
|
||||
Z(32);
|
||||
S("\x20"_q);
|
||||
R(32);
|
||||
S("\x00\x20"_q);
|
||||
G(0);
|
||||
S(""
|
||||
"\x13\x01\x13\x02\x13\x03\xc0\x2b\xc0\x2f\xc0\x2c\xc0\x30\xcc\xa9"
|
||||
"\xcc\xa8\xc0\x13\xc0\x14\x00\x9c\x00\x9d\x00\x2f\x00\x35\x01\x00"
|
||||
""_q);
|
||||
OpenScope();
|
||||
G(2);
|
||||
S("\x00\x00"_q);
|
||||
OpenPermutation(); {
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x00"_q);
|
||||
OpenScope();
|
||||
OpenScope();
|
||||
S("\x00"_q);
|
||||
OpenScope();
|
||||
D();
|
||||
CloseScope();
|
||||
CloseScope();
|
||||
CloseScope();
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x05\x00\x05\x01\x00\x00\x00\x00"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x0a\x00\x0c\x00\x0a"_q);
|
||||
G(4);
|
||||
S("\x11\xec\x00\x1d\x00\x17\x00\x18"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x0b\x00\x02\x01\x00"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S(""
|
||||
"\x00\x0d\x00\x12\x00\x10\x04\x03\x08\x04\x04\x01\x05\x03"
|
||||
"\x08\x05\x05\x01\x08\x06\x06\x01"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S(""
|
||||
"\x00\x10\x00\x0e\x00\x0c\x02\x68\x32\x08\x68\x74\x74\x70"
|
||||
"\x2f\x31\x2e\x31"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x12\x00\x00"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x17\x00\x00"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x1b\x00\x03\x02\x00\x02"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x23\x00\x00"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x2b\x00\x07\x06"_q);
|
||||
G(6);
|
||||
S("\x03\x04\x03\x03"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x2d\x00\x02\x01\x01"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x00\x33\x04\xef\x04\xed"_q);
|
||||
G(4);
|
||||
S("\x00\x01\x00\x11\xec\x04\xc0"_q);
|
||||
M();
|
||||
K();
|
||||
S("\x00\x1d\x00\x20"_q);
|
||||
K();
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\x44\xcd\x00\x05\x00\x03\x02\x68\x32"_q);
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\xfe\x02"_q);
|
||||
OpenScope();
|
||||
S("\x00\x00\x01\x00\x01"_q);
|
||||
R(1);
|
||||
S("\x00\x20"_q);
|
||||
R(20);
|
||||
OpenScope();
|
||||
E();
|
||||
CloseScope();
|
||||
CloseScope();
|
||||
}
|
||||
StartPermutationElement(); {
|
||||
S("\xff\x01\x00\x01\x00"_q);
|
||||
}
|
||||
} ClosePermutation();
|
||||
G(3);
|
||||
S("\x00\x01\x00"_q);
|
||||
P();
|
||||
CloseScope();
|
||||
CloseScope();
|
||||
CloseScope();
|
||||
|
||||
return MTP_tlsClientHello(MTP_vector<MTPTlsBlock>(Finish()));
|
||||
}
|
||||
|
||||
[[nodiscard]] bytes::vector PrepareGreases() {
|
||||
auto result = bytes::vector(kMaxGrease);
|
||||
bytes::set_random(result);
|
||||
for (auto &byte : result) {
|
||||
byte = bytes::type((uchar(byte) & 0xF0) + 0x0A);
|
||||
}
|
||||
static_assert(kMaxGrease % 2 == 0);
|
||||
for (auto i = 0; i != kMaxGrease; i += 2) {
|
||||
if (result[i] == result[i + 1]) {
|
||||
result[i + 1] = bytes::type(uchar(result[i + 1]) ^ 0x10);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] bytes::vector GeneratePublicKey() {
|
||||
const auto context = EVP_PKEY_CTX_new_id(NID_ED25519, nullptr);
|
||||
if (!context) {
|
||||
return {};
|
||||
}
|
||||
const auto guardContext = gsl::finally([&] {
|
||||
EVP_PKEY_CTX_free(context);
|
||||
});
|
||||
|
||||
if (EVP_PKEY_keygen_init(context) <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto key = (EVP_PKEY*)nullptr;
|
||||
if (EVP_PKEY_keygen(context, &key) <= 0) {
|
||||
return {};
|
||||
}
|
||||
const auto guardKey = gsl::finally([&] {
|
||||
EVP_PKEY_free(key);
|
||||
});
|
||||
|
||||
auto length = size_t(0);
|
||||
if (!EVP_PKEY_get_raw_public_key(key, nullptr, &length)) {
|
||||
return {};
|
||||
}
|
||||
Assert(length == 32);
|
||||
|
||||
auto result = bytes::vector(length);
|
||||
const auto code = EVP_PKEY_get_raw_public_key(
|
||||
key,
|
||||
reinterpret_cast<unsigned char *>(result.data()),
|
||||
&length);
|
||||
if (!code) {
|
||||
return {};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct ClientHello {
|
||||
QByteArray data;
|
||||
QByteArray digest;
|
||||
};
|
||||
|
||||
class Generator {
|
||||
public:
|
||||
Generator(
|
||||
const MTPTlsClientHello &rules,
|
||||
bytes::const_span domain,
|
||||
bytes::const_span key);
|
||||
[[nodiscard]] ClientHello take();
|
||||
|
||||
private:
|
||||
class Part final {
|
||||
public:
|
||||
explicit Part(
|
||||
bytes::const_span domain,
|
||||
const bytes::vector &greases);
|
||||
|
||||
[[nodiscard]] bytes::span grow(int size);
|
||||
void writeBlocks(const QVector<MTPTlsBlock> &blocks);
|
||||
void writeBlock(const MTPTlsBlock &data);
|
||||
void writeBlock(const MTPDtlsBlockString &data);
|
||||
void writeBlock(const MTPDtlsBlockZero &data);
|
||||
void writeBlock(const MTPDtlsBlockGrease &data);
|
||||
void writeBlock(const MTPDtlsBlockRandom &data);
|
||||
void writeBlock(const MTPDtlsBlockDomain &data);
|
||||
void writeBlock(const MTPDtlsBlockPublicKey &data);
|
||||
void writeBlock(const MTPDtlsBlockScope &data);
|
||||
void writeBlock(const MTPDtlsBlockPermutation &data);
|
||||
void writeBlock(const MTPDtlsBlockM &data);
|
||||
void writeBlock(const MTPDtlsBlockE &data);
|
||||
void writeBlock(const MTPDtlsBlockPadding &data);
|
||||
void finalize(bytes::const_span key);
|
||||
[[nodiscard]] QByteArray extractDigest() const;
|
||||
|
||||
[[nodiscard]] bool error() const;
|
||||
[[nodiscard]] QByteArray take();
|
||||
|
||||
private:
|
||||
void writeDigest(bytes::const_span key);
|
||||
void injectTimestamp();
|
||||
|
||||
bytes::const_span _domain;
|
||||
const bytes::vector &_greases;
|
||||
QByteArray _result;
|
||||
const char *_data = nullptr;
|
||||
int _digestPosition = -1;
|
||||
bool _error = false;
|
||||
|
||||
};
|
||||
|
||||
bytes::vector _greases;
|
||||
Part _result;
|
||||
QByteArray _digest;
|
||||
|
||||
};
|
||||
|
||||
Generator::Part::Part(
|
||||
bytes::const_span domain,
|
||||
const bytes::vector &greases)
|
||||
: _domain(domain)
|
||||
, _greases(greases) {
|
||||
_result.reserve(kClientHelloLimit);
|
||||
_data = _result.constData();
|
||||
}
|
||||
|
||||
bool Generator::Part::error() const {
|
||||
return _error;
|
||||
}
|
||||
|
||||
QByteArray Generator::Part::take() {
|
||||
Expects(_error || _result.constData() == _data);
|
||||
|
||||
return _error ? QByteArray() : std::move(_result);
|
||||
}
|
||||
|
||||
bytes::span Generator::Part::grow(int size) {
|
||||
if (_error
|
||||
|| size <= 0
|
||||
|| _result.size() + size > kClientHelloLimit) {
|
||||
_error = true;
|
||||
return bytes::span();
|
||||
}
|
||||
|
||||
const auto offset = _result.size();
|
||||
_result.resize(offset + size);
|
||||
return bytes::make_detached_span(_result).subspan(offset);
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlocks(const QVector<MTPTlsBlock> &blocks) {
|
||||
for (const auto &block : blocks) {
|
||||
writeBlock(block);
|
||||
}
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPTlsBlock &data) {
|
||||
data.match([&](const auto &data) {
|
||||
writeBlock(data);
|
||||
});
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockString &data) {
|
||||
const auto &bytes = data.vdata().v;
|
||||
const auto storage = grow(bytes.size());
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
bytes::copy(storage, bytes::make_span(bytes));
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockZero &data) {
|
||||
const auto length = data.vlength().v;
|
||||
const auto already = _result.size();
|
||||
const auto storage = grow(length);
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
if (length == kHelloDigestLength && _digestPosition < 0) {
|
||||
_digestPosition = already;
|
||||
}
|
||||
bytes::set_with_const(storage, bytes::type(0));
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockGrease &data) {
|
||||
const auto seed = data.vseed().v;
|
||||
if (seed < 0 || seed >= _greases.size()) {
|
||||
_error = true;
|
||||
return;
|
||||
}
|
||||
const auto storage = grow(2);
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
bytes::set_with_const(storage, _greases[seed]);
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockRandom &data) {
|
||||
const auto length = data.vlength().v;
|
||||
const auto storage = grow(length);
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
bytes::set_random(storage);
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockDomain &data) {
|
||||
const auto storage = grow(_domain.size());
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
bytes::copy(storage, _domain);
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockPublicKey &data) {
|
||||
const auto key = GeneratePublicKey();
|
||||
const auto storage = grow(key.size());
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
bytes::copy(storage, key);
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockScope &data) {
|
||||
const auto storage = grow(kLengthSize);
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto already = _result.size();
|
||||
writeBlocks(data.ventries().v);
|
||||
const auto length = qToBigEndian(uint16(_result.size() - already));
|
||||
bytes::copy(storage, bytes::object_as_span(&length));
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockPermutation &data) {
|
||||
auto list = std::vector<QByteArray>();
|
||||
list.reserve(data.ventries().v.size());
|
||||
for (const auto &inner : data.ventries().v) {
|
||||
auto part = Part(_domain, _greases);
|
||||
part.writeBlocks(inner.v);
|
||||
if (part.error()) {
|
||||
_error = true;
|
||||
return;
|
||||
}
|
||||
list.push_back(part.take());
|
||||
}
|
||||
ranges::shuffle(list);
|
||||
for (const auto &element : list) {
|
||||
const auto storage = grow(element.size());
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
bytes::copy(storage, bytes::make_span(element));
|
||||
}
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockM &data) {
|
||||
constexpr auto kElements = 384;
|
||||
constexpr auto kAdded = 32;
|
||||
|
||||
const auto storage = grow(kElements * 3 + kAdded);
|
||||
if (storage.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto random = bytes::vector(kElements * 8 + kAdded);
|
||||
bytes::set_random(random);
|
||||
|
||||
auto chars = reinterpret_cast<char*>(storage.data());
|
||||
const auto ints = reinterpret_cast<const uint32*>(random.data());
|
||||
for (auto i = 0; i < kElements; ++i) {
|
||||
const auto a = int(ints[i * 2] % 3329);
|
||||
const auto b = int(ints[i * 2 + 1] % 3329);
|
||||
*chars++ = (char)(a & 255);
|
||||
*chars++ = (char)((a >> 8) + ((b & 15) << 4));
|
||||
*chars++ = (char)(b >> 4);
|
||||
}
|
||||
bytes::set_random(storage.subspan(kElements * 3));
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockE &data) {
|
||||
const auto lengths = std::array{ 144, 176, 208, 240 };
|
||||
const auto length = lengths[base::RandomIndex(lengths.size())];
|
||||
writeBlock(MTP_tlsBlockRandom(MTP_int(length)));
|
||||
}
|
||||
|
||||
void Generator::Part::writeBlock(const MTPDtlsBlockPadding &data) {
|
||||
const auto length = int(_result.size());
|
||||
if (length < 513) {
|
||||
const auto zero = MTP_tlsBlockZero(MTP_int(513 - length));
|
||||
writeBlock(MTP_tlsBlockString(MTP_bytes("\x00\x15"_q)));
|
||||
writeBlock(MTP_tlsBlockScope(MTP_vector<MTPTlsBlock>(1, zero)));
|
||||
}
|
||||
}
|
||||
|
||||
void Generator::Part::finalize(bytes::const_span key) {
|
||||
if (_error) {
|
||||
return;
|
||||
} else if (_digestPosition < 0) {
|
||||
_error = true;
|
||||
return;
|
||||
}
|
||||
writeDigest(key);
|
||||
injectTimestamp();
|
||||
}
|
||||
|
||||
QByteArray Generator::Part::extractDigest() const {
|
||||
if (_digestPosition < 0) {
|
||||
return {};
|
||||
}
|
||||
return _result.mid(_digestPosition, kHelloDigestLength);
|
||||
}
|
||||
|
||||
void Generator::Part::writeDigest(bytes::const_span key) {
|
||||
Expects(_digestPosition >= 0);
|
||||
|
||||
bytes::copy(
|
||||
bytes::make_detached_span(_result).subspan(_digestPosition),
|
||||
openssl::HmacSha256(key, bytes::make_span(_result)));
|
||||
}
|
||||
|
||||
void Generator::Part::injectTimestamp() {
|
||||
Expects(_digestPosition >= 0);
|
||||
|
||||
const auto storage = bytes::make_detached_span(_result).subspan(
|
||||
_digestPosition + kHelloDigestLength - sizeof(int32),
|
||||
sizeof(int32));
|
||||
auto already = int32();
|
||||
bytes::copy(bytes::object_as_span(&already), storage);
|
||||
already ^= qToLittleEndian(int32(base::unixtime::http_now()));
|
||||
bytes::copy(storage, bytes::object_as_span(&already));
|
||||
}
|
||||
|
||||
Generator::Generator(
|
||||
const MTPTlsClientHello &rules,
|
||||
bytes::const_span domain,
|
||||
bytes::const_span key)
|
||||
: _greases(PrepareGreases())
|
||||
, _result(domain, _greases) {
|
||||
_result.writeBlocks(rules.data().vblocks().v);
|
||||
_result.finalize(key);
|
||||
}
|
||||
|
||||
ClientHello Generator::take() {
|
||||
auto digest = _result.extractDigest();
|
||||
return { _result.take(), std::move(digest) };
|
||||
}
|
||||
|
||||
[[nodiscard]] ClientHello PrepareClientHello(
|
||||
const MTPTlsClientHello &rules,
|
||||
bytes::const_span domain,
|
||||
bytes::const_span key) {
|
||||
return Generator(rules, domain, key).take();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CheckPart(bytes::const_span data, QLatin1String check) {
|
||||
if (data.size() < check.size()) {
|
||||
return false;
|
||||
}
|
||||
return !bytes::compare(
|
||||
data.subspan(0, check.size()),
|
||||
bytes::make_span(check.data(), check.size()));
|
||||
}
|
||||
|
||||
[[nodiscard]] int ReadPartLength(bytes::const_span data, int offset) {
|
||||
const auto storage = data.subspan(offset, kLengthSize);
|
||||
return qFromBigEndian(
|
||||
*reinterpret_cast<const uint16*>(storage.data()));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TlsSocket::TlsSocket(
|
||||
not_null<QThread*> thread,
|
||||
const bytes::vector &secret,
|
||||
const QNetworkProxy &proxy,
|
||||
bool protocolForFiles)
|
||||
: AbstractSocket(thread)
|
||||
, _secret(secret) {
|
||||
Expects(_secret.size() >= 21 && _secret[0] == bytes::type(0xEE));
|
||||
|
||||
_socket.moveToThread(thread);
|
||||
_socket.setProxy(proxy);
|
||||
if (protocolForFiles) {
|
||||
_socket.setSocketOption(
|
||||
QAbstractSocket::SendBufferSizeSocketOption,
|
||||
kFilesSendBufferSize);
|
||||
_socket.setSocketOption(
|
||||
QAbstractSocket::ReceiveBufferSizeSocketOption,
|
||||
kFilesReceiveBufferSize);
|
||||
}
|
||||
const auto wrap = [&](auto handler) {
|
||||
return [=](auto &&...args) {
|
||||
InvokeQueued(this, [=] { handler(args...); });
|
||||
};
|
||||
};
|
||||
using Error = QAbstractSocket::SocketError;
|
||||
connect(
|
||||
&_socket,
|
||||
&QTcpSocket::connected,
|
||||
wrap([=] { plainConnected(); }));
|
||||
connect(
|
||||
&_socket,
|
||||
&QTcpSocket::disconnected,
|
||||
wrap([=] { plainDisconnected(); }));
|
||||
connect(
|
||||
&_socket,
|
||||
&QTcpSocket::readyRead,
|
||||
wrap([=] { plainReadyRead(); }));
|
||||
connect(
|
||||
&_socket,
|
||||
&QAbstractSocket::errorOccurred,
|
||||
wrap([=](Error e) { handleError(e); }));
|
||||
}
|
||||
|
||||
bytes::const_span TlsSocket::domainFromSecret() const {
|
||||
return bytes::make_span(_secret).subspan(17);
|
||||
}
|
||||
|
||||
bytes::const_span TlsSocket::keyFromSecret() const {
|
||||
return bytes::make_span(_secret).subspan(1, 16);
|
||||
}
|
||||
|
||||
void TlsSocket::plainConnected() {
|
||||
if (_state != State::Connecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const auto kClientHelloRules = PrepareClientHelloRules();
|
||||
const auto hello = PrepareClientHello(
|
||||
kClientHelloRules,
|
||||
domainFromSecret(),
|
||||
keyFromSecret());
|
||||
if (hello.data.isEmpty()) {
|
||||
logError(888, "Could not generate Client Hello.");
|
||||
_state = State::Error;
|
||||
_error.fire({});
|
||||
} else {
|
||||
_state = State::WaitingHello;
|
||||
_incoming = hello.digest;
|
||||
_socket.write(hello.data);
|
||||
}
|
||||
}
|
||||
|
||||
void TlsSocket::plainDisconnected() {
|
||||
_state = State::NotConnected;
|
||||
_incoming = QByteArray();
|
||||
_serverHelloLength = 0;
|
||||
_incomingGoodDataOffset = 0;
|
||||
_incomingGoodDataLimit = 0;
|
||||
_disconnected.fire({});
|
||||
}
|
||||
|
||||
void TlsSocket::plainReadyRead() {
|
||||
switch (_state) {
|
||||
case State::WaitingHello: return readHello();
|
||||
case State::Connected: return readData();
|
||||
}
|
||||
}
|
||||
|
||||
bool TlsSocket::requiredHelloPartReady() const {
|
||||
return _incoming.size() >= kHelloDigestLength + _serverHelloLength;
|
||||
}
|
||||
|
||||
void TlsSocket::readHello() {
|
||||
const auto parts1Size = kServerHelloPart1.size() + kLengthSize;
|
||||
if (!_serverHelloLength) {
|
||||
_serverHelloLength = parts1Size;
|
||||
}
|
||||
while (!requiredHelloPartReady()) {
|
||||
if (!_socket.bytesAvailable()) {
|
||||
return;
|
||||
}
|
||||
_incoming.append(_socket.readAll());
|
||||
}
|
||||
checkHelloParts12(parts1Size);
|
||||
}
|
||||
|
||||
void TlsSocket::checkHelloParts12(int parts1Size) {
|
||||
const auto data = bytes::make_span(_incoming).subspan(
|
||||
kHelloDigestLength,
|
||||
parts1Size);
|
||||
const auto part2Size = ReadPartLength(data, parts1Size - kLengthSize);
|
||||
const auto parts123Size = parts1Size
|
||||
+ part2Size
|
||||
+ kServerHelloPart3.size()
|
||||
+ kLengthSize;
|
||||
if (_serverHelloLength == parts1Size) {
|
||||
const auto part1Offset = parts1Size
|
||||
- kLengthSize
|
||||
- kServerHelloPart1.size();
|
||||
if (!CheckPart(data.subspan(part1Offset), kServerHelloPart1)) {
|
||||
logError(888, "Bad Server Hello part1.");
|
||||
handleError();
|
||||
return;
|
||||
}
|
||||
_serverHelloLength = parts123Size;
|
||||
if (!requiredHelloPartReady()) {
|
||||
readHello();
|
||||
return;
|
||||
}
|
||||
}
|
||||
checkHelloParts34(parts123Size);
|
||||
}
|
||||
|
||||
void TlsSocket::checkHelloParts34(int parts123Size) {
|
||||
const auto data = bytes::make_span(_incoming).subspan(
|
||||
kHelloDigestLength,
|
||||
parts123Size);
|
||||
const auto part4Size = ReadPartLength(data, parts123Size - kLengthSize);
|
||||
const auto full = parts123Size + part4Size;
|
||||
if (_serverHelloLength == parts123Size) {
|
||||
const auto part3Offset = parts123Size
|
||||
- kLengthSize
|
||||
- kServerHelloPart3.size();
|
||||
if (!CheckPart(data.subspan(part3Offset), kServerHelloPart3)) {
|
||||
logError(888, "Bad Server Hello part.");
|
||||
handleError();
|
||||
return;
|
||||
}
|
||||
_serverHelloLength = full;
|
||||
if (!requiredHelloPartReady()) {
|
||||
readHello();
|
||||
return;
|
||||
}
|
||||
}
|
||||
checkHelloDigest();
|
||||
}
|
||||
|
||||
void TlsSocket::checkHelloDigest() {
|
||||
const auto fulldata = bytes::make_detached_span(_incoming).subspan(
|
||||
0,
|
||||
kHelloDigestLength + _serverHelloLength);
|
||||
const auto digest = fulldata.subspan(
|
||||
kHelloDigestLength + kServerHelloDigestPosition,
|
||||
kHelloDigestLength);
|
||||
const auto digestCopy = bytes::make_vector(digest);
|
||||
bytes::set_with_const(digest, bytes::type(0));
|
||||
const auto check = openssl::HmacSha256(keyFromSecret(), fulldata);
|
||||
if (bytes::compare(digestCopy, check) != 0) {
|
||||
logError(888, "Bad Server Hello digest.");
|
||||
handleError();
|
||||
return;
|
||||
}
|
||||
shiftIncomingBy(fulldata.size());
|
||||
if (!_incoming.isEmpty()) {
|
||||
InvokeQueued(this, [=] {
|
||||
if (!checkNextPacket()) {
|
||||
handleError();
|
||||
}
|
||||
});
|
||||
}
|
||||
_incomingGoodDataOffset = _incomingGoodDataLimit = 0;
|
||||
_state = State::Connected;
|
||||
_connected.fire({});
|
||||
}
|
||||
|
||||
void TlsSocket::readData() {
|
||||
if (!isConnected()) {
|
||||
return;
|
||||
}
|
||||
_incoming.append(_socket.readAll());
|
||||
if (!checkNextPacket()) {
|
||||
handleError();
|
||||
} else if (hasBytesAvailable()) {
|
||||
_readyRead.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
bool TlsSocket::checkNextPacket() {
|
||||
auto offset = 0;
|
||||
const auto incoming = bytes::make_span(_incoming);
|
||||
while (!_incomingGoodDataLimit) {
|
||||
const auto fullHeader = kServerHeader.size() + kLengthSize;
|
||||
if (incoming.size() <= offset + fullHeader) {
|
||||
return true;
|
||||
}
|
||||
if (!CheckPart(incoming.subspan(offset), kServerHeader)) {
|
||||
logError(888, "Bad packet header.");
|
||||
return false;
|
||||
}
|
||||
const auto length = ReadPartLength(
|
||||
incoming,
|
||||
offset + kServerHeader.size());
|
||||
if (length > 0) {
|
||||
if (offset > 0) {
|
||||
shiftIncomingBy(offset);
|
||||
}
|
||||
_incomingGoodDataOffset = fullHeader;
|
||||
_incomingGoodDataLimit = length;
|
||||
} else {
|
||||
offset += kServerHeader.size() + kLengthSize + length;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TlsSocket::shiftIncomingBy(int amount) {
|
||||
Expects(_incomingGoodDataOffset == 0);
|
||||
Expects(_incomingGoodDataLimit == 0);
|
||||
|
||||
const auto incoming = bytes::make_detached_span(_incoming);
|
||||
if (incoming.size() > amount) {
|
||||
bytes::move(incoming, incoming.subspan(amount));
|
||||
_incoming.chop(amount);
|
||||
} else {
|
||||
_incoming.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TlsSocket::connectToHost(const QString &address, int port) {
|
||||
Expects(_state == State::NotConnected);
|
||||
|
||||
_state = State::Connecting;
|
||||
_socket.connectToHost(address, port);
|
||||
}
|
||||
|
||||
bool TlsSocket::isGoodStartNonce(bytes::const_span nonce) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void TlsSocket::timedOut() {
|
||||
_syncTimeRequests.fire({});
|
||||
}
|
||||
|
||||
bool TlsSocket::isConnected() {
|
||||
return (_state == State::Connected);
|
||||
}
|
||||
|
||||
bool TlsSocket::hasBytesAvailable() {
|
||||
return (_incomingGoodDataLimit > 0)
|
||||
&& (_incomingGoodDataOffset < _incoming.size());
|
||||
}
|
||||
|
||||
int64 TlsSocket::read(bytes::span buffer) {
|
||||
auto written = int64(0);
|
||||
while (_incomingGoodDataLimit) {
|
||||
const auto available = std::min(
|
||||
_incomingGoodDataLimit,
|
||||
int(_incoming.size()) - _incomingGoodDataOffset);
|
||||
if (available <= 0) {
|
||||
return written;
|
||||
}
|
||||
const auto write = std::min(std::size_t(available), buffer.size());
|
||||
if (write <= 0) {
|
||||
return written;
|
||||
}
|
||||
bytes::copy(
|
||||
buffer,
|
||||
bytes::make_span(_incoming).subspan(
|
||||
_incomingGoodDataOffset,
|
||||
write));
|
||||
written += write;
|
||||
buffer = buffer.subspan(write);
|
||||
_incomingGoodDataLimit -= write;
|
||||
_incomingGoodDataOffset += write;
|
||||
if (_incomingGoodDataLimit) {
|
||||
return written;
|
||||
}
|
||||
shiftIncomingBy(base::take(_incomingGoodDataOffset));
|
||||
if (!checkNextPacket()) {
|
||||
_state = State::Error;
|
||||
InvokeQueued(this, [=] { handleError(); });
|
||||
return written;
|
||||
}
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
void TlsSocket::write(bytes::const_span prefix, bytes::const_span buffer) {
|
||||
Expects(!buffer.empty());
|
||||
|
||||
if (!isConnected()) {
|
||||
return;
|
||||
}
|
||||
if (!prefix.empty()) {
|
||||
_socket.write(kClientPrefix.data(), kClientPrefix.size());
|
||||
}
|
||||
while (!buffer.empty()) {
|
||||
const auto write = std::min(
|
||||
kClientPartSize - prefix.size(),
|
||||
buffer.size());
|
||||
_socket.write(kClientHeader.data(), kClientHeader.size());
|
||||
const auto size = qToBigEndian(uint16(prefix.size() + write));
|
||||
_socket.write(reinterpret_cast<const char*>(&size), sizeof(size));
|
||||
if (!prefix.empty()) {
|
||||
_socket.write(
|
||||
reinterpret_cast<const char*>(prefix.data()),
|
||||
prefix.size());
|
||||
prefix = bytes::const_span();
|
||||
}
|
||||
_socket.write(
|
||||
reinterpret_cast<const char*>(buffer.data()),
|
||||
write);
|
||||
buffer = buffer.subspan(write);
|
||||
}
|
||||
}
|
||||
|
||||
int32 TlsSocket::debugState() {
|
||||
return _socket.state();
|
||||
}
|
||||
|
||||
QString TlsSocket::debugPostfix() const {
|
||||
return u"_ee"_q;
|
||||
}
|
||||
|
||||
void TlsSocket::handleError(int errorCode) {
|
||||
if (_state != State::Connected) {
|
||||
_syncTimeRequests.fire({});
|
||||
}
|
||||
if (errorCode) {
|
||||
logError(errorCode, _socket.errorString());
|
||||
}
|
||||
_state = State::Error;
|
||||
_error.fire({});
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
68
Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.h
Normal file
68
Telegram/SourceFiles/mtproto/details/mtproto_tls_socket.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/details/mtproto_abstract_socket.h"
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
class TlsSocket final : public AbstractSocket {
|
||||
public:
|
||||
TlsSocket(
|
||||
not_null<QThread*> thread,
|
||||
const bytes::vector &secret,
|
||||
const QNetworkProxy &proxy,
|
||||
bool protocolForFiles);
|
||||
|
||||
void connectToHost(const QString &address, int port) override;
|
||||
bool isGoodStartNonce(bytes::const_span nonce) override;
|
||||
void timedOut() override;
|
||||
bool isConnected() override;
|
||||
bool hasBytesAvailable() override;
|
||||
int64 read(bytes::span buffer) override;
|
||||
void write(bytes::const_span prefix, bytes::const_span buffer) override;
|
||||
|
||||
int32 debugState() override;
|
||||
QString debugPostfix() const override;
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
NotConnected,
|
||||
Connecting,
|
||||
WaitingHello,
|
||||
Connected,
|
||||
Error,
|
||||
};
|
||||
|
||||
[[nodiscard]] bytes::const_span domainFromSecret() const;
|
||||
[[nodiscard]] bytes::const_span keyFromSecret() const;
|
||||
|
||||
void plainConnected();
|
||||
void plainDisconnected();
|
||||
void plainReadyRead();
|
||||
void handleError(int errorCode = 0);
|
||||
[[nodiscard]] bool requiredHelloPartReady() const;
|
||||
void readHello();
|
||||
void checkHelloParts12(int parts1Size);
|
||||
void checkHelloParts34(int parts123Size);
|
||||
void checkHelloDigest();
|
||||
void readData();
|
||||
[[nodiscard]] bool checkNextPacket();
|
||||
void shiftIncomingBy(int amount);
|
||||
|
||||
const bytes::vector _secret;
|
||||
QTcpSocket _socket;
|
||||
State _state = State::NotConnected;
|
||||
QByteArray _incoming;
|
||||
int _incomingGoodDataOffset = 0;
|
||||
int _incomingGoodDataLimit = 0;
|
||||
int16 _serverHelloLength = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
43
Telegram/SourceFiles/mtproto/facade.cpp
Normal file
43
Telegram/SourceFiles/mtproto/facade.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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/facade.h"
|
||||
|
||||
#include "storage/localstorage.h"
|
||||
#include "core/application.h"
|
||||
#include "main/main_account.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
namespace {
|
||||
|
||||
int PauseLevel = 0;
|
||||
rpl::event_stream<> Unpaused;
|
||||
|
||||
} // namespace
|
||||
|
||||
bool paused() {
|
||||
return PauseLevel > 0;
|
||||
}
|
||||
|
||||
void pause() {
|
||||
++PauseLevel;
|
||||
}
|
||||
|
||||
void unpause() {
|
||||
--PauseLevel;
|
||||
if (!PauseLevel) {
|
||||
Unpaused.fire({});
|
||||
}
|
||||
}
|
||||
|
||||
rpl::producer<> unpaused() {
|
||||
return Unpaused.events();
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
128
Telegram/SourceFiles/mtproto/facade.h
Normal file
128
Telegram/SourceFiles/mtproto/facade.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/type_utils.h"
|
||||
#include "mtproto/mtp_instance.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
|
||||
[[nodiscard]] bool paused();
|
||||
void pause();
|
||||
void unpause();
|
||||
[[nodiscard]] rpl::producer<> unpaused();
|
||||
|
||||
} // namespace details
|
||||
|
||||
// send(MTPhelp_GetConfig(), MTP::configDcId(dc)) - for dc enumeration
|
||||
constexpr ShiftedDcId configDcId(DcId dcId) {
|
||||
return ShiftDcId(dcId, kConfigDcShift);
|
||||
}
|
||||
|
||||
// send(MTPauth_LogOut(), MTP::logoutDcId(dc)) - for logout of guest dcs enumeration
|
||||
constexpr ShiftedDcId logoutDcId(DcId dcId) {
|
||||
return ShiftDcId(dcId, kLogoutDcShift);
|
||||
}
|
||||
|
||||
// send(MTPupload_GetFile(), MTP::updaterDcId(dc)) - for autoupdater
|
||||
constexpr ShiftedDcId updaterDcId(DcId dcId) {
|
||||
return ShiftDcId(dcId, kUpdaterDcShift);
|
||||
}
|
||||
|
||||
// send(MTPupload_GetFile(), MTP::groupCallStreamDcId(dc)) - for group call stream
|
||||
constexpr ShiftedDcId groupCallStreamDcId(DcId dcId) {
|
||||
return ShiftDcId(dcId, kGroupCallStreamDcShift);
|
||||
}
|
||||
|
||||
namespace details {
|
||||
|
||||
constexpr ShiftedDcId downloadDcId(DcId dcId, int index) {
|
||||
Expects(index < kMaxMediaDcCount);
|
||||
|
||||
return ShiftDcId(dcId, kBaseDownloadDcShift + index);
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
// send(req, callbacks, MTP::downloadDcId(dc, index)) - for download shifted dc id
|
||||
inline ShiftedDcId downloadDcId(DcId dcId, int index) {
|
||||
return details::downloadDcId(dcId, index);
|
||||
}
|
||||
|
||||
inline constexpr bool isDownloadDcId(ShiftedDcId shiftedDcId) {
|
||||
return (shiftedDcId >= details::downloadDcId(0, 0))
|
||||
&& (shiftedDcId < details::downloadDcId(0, kMaxMediaDcCount - 1) + kDcShift);
|
||||
}
|
||||
|
||||
inline constexpr bool isMediaClusterDcId(ShiftedDcId shiftedDcId) {
|
||||
const auto shift = GetDcIdShift(shiftedDcId);
|
||||
return isDownloadDcId(shiftedDcId)
|
||||
|| (shift == kGroupCallStreamDcShift)
|
||||
|| (shift == kExportMediaDcShift)
|
||||
|| (shift == kUpdaterDcShift);
|
||||
}
|
||||
|
||||
inline bool isCdnDc(MTPDdcOption::Flags flags) {
|
||||
return (flags & MTPDdcOption::Flag::f_cdn);
|
||||
}
|
||||
|
||||
inline bool isTemporaryDcId(ShiftedDcId shiftedDcId) {
|
||||
auto dcId = BareDcId(shiftedDcId);
|
||||
return (dcId >= Instance::Fields::kTemporaryMainDc);
|
||||
}
|
||||
|
||||
inline DcId getRealIdFromTemporaryDcId(ShiftedDcId shiftedDcId) {
|
||||
auto dcId = BareDcId(shiftedDcId);
|
||||
return (dcId >= Instance::Fields::kTemporaryMainDc) ? (dcId - Instance::Fields::kTemporaryMainDc) : 0;
|
||||
}
|
||||
|
||||
inline DcId getTemporaryIdFromRealDcId(ShiftedDcId shiftedDcId) {
|
||||
auto dcId = BareDcId(shiftedDcId);
|
||||
return (dcId < Instance::Fields::kTemporaryMainDc) ? (dcId + Instance::Fields::kTemporaryMainDc) : 0;
|
||||
}
|
||||
|
||||
namespace details {
|
||||
|
||||
constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
|
||||
return ShiftDcId(dcId, kBaseUploadDcShift + index);
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
|
||||
// send(req, callbacks, MTP::uploadDcId(index)) - for upload shifted dc id
|
||||
// uploading always to the main dc so BareDcId(result) == 0
|
||||
inline ShiftedDcId uploadDcId(int index) {
|
||||
Expects(index >= 0 && index < kMaxMediaDcCount);
|
||||
|
||||
return details::uploadDcId(0, index);
|
||||
};
|
||||
|
||||
constexpr bool isUploadDcId(ShiftedDcId shiftedDcId) {
|
||||
return (shiftedDcId >= details::uploadDcId(0, 0))
|
||||
&& (shiftedDcId < details::uploadDcId(0, kMaxMediaDcCount - 1) + kDcShift);
|
||||
}
|
||||
|
||||
inline ShiftedDcId destroyKeyNextDcId(ShiftedDcId shiftedDcId) {
|
||||
const auto shift = GetDcIdShift(shiftedDcId);
|
||||
return ShiftDcId(BareDcId(shiftedDcId), shift ? (shift + 1) : kDestroyKeyStartDcShift);
|
||||
}
|
||||
|
||||
enum {
|
||||
DisconnectedState = 0,
|
||||
ConnectingState = 1,
|
||||
ConnectedState = 2,
|
||||
};
|
||||
|
||||
enum {
|
||||
RequestSent = 0,
|
||||
RequestConnecting = 1,
|
||||
RequestSending = 2
|
||||
};
|
||||
|
||||
} // namespace MTP
|
||||
2129
Telegram/SourceFiles/mtproto/mtp_instance.cpp
Normal file
2129
Telegram/SourceFiles/mtproto/mtp_instance.cpp
Normal file
File diff suppressed because it is too large
Load Diff
247
Telegram/SourceFiles/mtproto/mtp_instance.h
Normal file
247
Telegram/SourceFiles/mtproto/mtp_instance.h
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/details/mtproto_serialized_request.h"
|
||||
#include "mtproto/mtproto_response.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
|
||||
class Dcenter;
|
||||
class Session;
|
||||
|
||||
[[nodiscard]] int GetNextRequestId();
|
||||
|
||||
} // namespace details
|
||||
|
||||
class DcOptions;
|
||||
class Config;
|
||||
struct ConfigFields;
|
||||
class AuthKey;
|
||||
using AuthKeyPtr = std::shared_ptr<AuthKey>;
|
||||
using AuthKeysList = std::vector<AuthKeyPtr>;
|
||||
enum class Environment : uchar;
|
||||
|
||||
class Instance : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct Fields {
|
||||
Fields();
|
||||
Fields(Fields &&other);
|
||||
Fields &operator=(Fields &&other);
|
||||
~Fields();
|
||||
|
||||
static constexpr auto kNoneMainDc = -1;
|
||||
static constexpr auto kNotSetMainDc = 0;
|
||||
static constexpr auto kDefaultMainDc = 2;
|
||||
static constexpr auto kTemporaryMainDc = 1000;
|
||||
|
||||
std::unique_ptr<Config> config;
|
||||
DcId mainDcId = kNotSetMainDc;
|
||||
AuthKeysList keys;
|
||||
QString deviceModel;
|
||||
QString systemVersion;
|
||||
};
|
||||
|
||||
enum class Mode {
|
||||
Normal,
|
||||
KeysDestroyer,
|
||||
};
|
||||
|
||||
Instance(Mode mode, Fields &&fields);
|
||||
Instance(const Instance &other) = delete;
|
||||
Instance &operator=(const Instance &other) = delete;
|
||||
~Instance();
|
||||
|
||||
void resolveProxyDomain(const QString &host);
|
||||
void setGoodProxyDomain(const QString &host, const QString &ip);
|
||||
void suggestMainDcId(DcId mainDcId);
|
||||
void setMainDcId(DcId mainDcId);
|
||||
[[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;
|
||||
[[nodiscard]] rpl::producer<> allKeysDestroyed() const;
|
||||
|
||||
// Thread-safe.
|
||||
[[nodiscard]] Config &config() const;
|
||||
[[nodiscard]] const ConfigFields &configValues() const;
|
||||
[[nodiscard]] DcOptions &dcOptions() const;
|
||||
[[nodiscard]] Environment environment() const;
|
||||
[[nodiscard]] bool isTestMode() const;
|
||||
[[nodiscard]] QString deviceModel() const;
|
||||
[[nodiscard]] QString systemVersion() const;
|
||||
|
||||
// Main thread.
|
||||
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);
|
||||
|
||||
void restart();
|
||||
void restart(ShiftedDcId shiftedDcId);
|
||||
int32 dcstate(ShiftedDcId shiftedDcId = 0);
|
||||
QString dctransport(ShiftedDcId shiftedDcId = 0);
|
||||
void ping();
|
||||
void cancel(mtpRequestId requestId);
|
||||
int32 state(mtpRequestId requestId); // < 0 means waiting for such count of ms
|
||||
|
||||
// Main thread.
|
||||
void killSession(ShiftedDcId shiftedDcId);
|
||||
void stopSession(ShiftedDcId shiftedDcId);
|
||||
void reInitConnection(DcId dcId);
|
||||
void logout(Fn<void()> done);
|
||||
|
||||
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();
|
||||
|
||||
void onStateChange(ShiftedDcId shiftedDcId, int32 state);
|
||||
void onSessionReset(ShiftedDcId shiftedDcId);
|
||||
|
||||
[[nodiscard]] bool hasCallback(mtpRequestId requestId) const;
|
||||
void processCallback(const Response &response);
|
||||
void processUpdate(const Response &message);
|
||||
|
||||
// return true if need to clean request data
|
||||
bool rpcErrorOccured(
|
||||
const Response &response,
|
||||
const FailHandler &onFail,
|
||||
const Error &err);
|
||||
|
||||
// Thread-safe.
|
||||
bool isKeysDestroyer() const;
|
||||
void keyWasPossiblyDestroyed(ShiftedDcId shiftedDcId);
|
||||
|
||||
// Main thread.
|
||||
void keyDestroyedOnServer(ShiftedDcId shiftedDcId, uint64 keyId);
|
||||
|
||||
void requestConfig();
|
||||
void requestConfigIfOld();
|
||||
void requestCDNConfig();
|
||||
void setUserPhone(const QString &phone);
|
||||
void badConfigurationError();
|
||||
|
||||
void restartedByTimeout(ShiftedDcId shiftedDcId);
|
||||
[[nodiscard]] rpl::producer<ShiftedDcId> restartsByTimeout() const;
|
||||
|
||||
[[nodiscard]] auto nonPremiumDelayedRequests() const
|
||||
-> rpl::producer<mtpRequestId>;
|
||||
[[nodiscard]] rpl::producer<> frozenErrorReceived() const;
|
||||
|
||||
void syncHttpUnixtime();
|
||||
|
||||
void sendAnything(ShiftedDcId shiftedDcId = 0, crl::time msCanWait = 0);
|
||||
|
||||
template <typename Request>
|
||||
mtpRequestId send(
|
||||
const Request &request,
|
||||
ResponseHandler &&callbacks = {},
|
||||
ShiftedDcId shiftedDcId = 0,
|
||||
crl::time msCanWait = 0,
|
||||
mtpRequestId afterRequestId = 0,
|
||||
mtpRequestId overrideRequestId = 0) {
|
||||
const auto requestId = overrideRequestId
|
||||
? overrideRequestId
|
||||
: details::GetNextRequestId();
|
||||
sendSerialized(
|
||||
requestId,
|
||||
details::SerializedRequest::Serialize(request),
|
||||
std::move(callbacks),
|
||||
shiftedDcId,
|
||||
msCanWait,
|
||||
afterRequestId);
|
||||
return requestId;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
mtpRequestId send(
|
||||
const Request &request,
|
||||
DoneHandler &&onDone,
|
||||
FailHandler &&onFail = nullptr,
|
||||
ShiftedDcId shiftedDcId = 0,
|
||||
crl::time msCanWait = 0,
|
||||
mtpRequestId afterRequestId = 0,
|
||||
mtpRequestId overrideRequestId = 0) {
|
||||
return send(
|
||||
request,
|
||||
ResponseHandler{ std::move(onDone), std::move(onFail) },
|
||||
shiftedDcId,
|
||||
msCanWait,
|
||||
afterRequestId,
|
||||
overrideRequestId);
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
mtpRequestId sendProtocolMessage(
|
||||
ShiftedDcId shiftedDcId,
|
||||
const Request &request) {
|
||||
const auto requestId = details::GetNextRequestId();
|
||||
sendRequest(
|
||||
requestId,
|
||||
details::SerializedRequest::Serialize(request),
|
||||
{},
|
||||
shiftedDcId,
|
||||
0,
|
||||
false,
|
||||
0);
|
||||
return requestId;
|
||||
}
|
||||
|
||||
void sendSerialized(
|
||||
mtpRequestId requestId,
|
||||
details::SerializedRequest &&request,
|
||||
ResponseHandler &&callbacks,
|
||||
ShiftedDcId shiftedDcId,
|
||||
crl::time msCanWait,
|
||||
mtpRequestId afterRequestId) {
|
||||
const auto needsLayer = true;
|
||||
sendRequest(
|
||||
requestId,
|
||||
std::move(request),
|
||||
std::move(callbacks),
|
||||
shiftedDcId,
|
||||
msCanWait,
|
||||
needsLayer,
|
||||
afterRequestId);
|
||||
}
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
Q_SIGNALS:
|
||||
void proxyDomainResolved(
|
||||
QString host,
|
||||
QStringList ips,
|
||||
qint64 expireAt);
|
||||
|
||||
private:
|
||||
void sendRequest(
|
||||
mtpRequestId requestId,
|
||||
details::SerializedRequest &&request,
|
||||
ResponseHandler &&callbacks,
|
||||
ShiftedDcId shiftedDcId,
|
||||
crl::time msCanWait,
|
||||
bool needsLayer,
|
||||
mtpRequestId afterRequestId);
|
||||
|
||||
class Private;
|
||||
const std::unique_ptr<Private> _private;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP
|
||||
189
Telegram/SourceFiles/mtproto/mtproto_auth_key.cpp
Normal file
189
Telegram/SourceFiles/mtproto/mtproto_auth_key.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
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/mtproto_auth_key.h"
|
||||
|
||||
#include "base/openssl_help.h"
|
||||
|
||||
#include <QtCore/QDataStream>
|
||||
|
||||
namespace MTP {
|
||||
|
||||
AuthKey::AuthKey(Type type, DcId dcId, const Data &data)
|
||||
: _type(type)
|
||||
, _dcId(dcId)
|
||||
, _key(data) {
|
||||
countKeyId();
|
||||
if (type == Type::Generated || type == Type::Temporary) {
|
||||
_creationTime = crl::now();
|
||||
}
|
||||
}
|
||||
|
||||
AuthKey::AuthKey(const Data &data) : _type(Type::Local), _key(data) {
|
||||
countKeyId();
|
||||
}
|
||||
|
||||
AuthKey::Type AuthKey::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
int AuthKey::dcId() const {
|
||||
return _dcId;
|
||||
}
|
||||
|
||||
AuthKey::KeyId AuthKey::keyId() const {
|
||||
return _keyId;
|
||||
}
|
||||
|
||||
void AuthKey::prepareAES_oldmtp(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send) const {
|
||||
uint32 x = send ? 0 : 8;
|
||||
|
||||
bytes::array<20> sha1_a, sha1_b, sha1_c, sha1_d;
|
||||
bytes::array<16 + 32> data_a;
|
||||
memcpy(data_a.data(), &msgKey, 16);
|
||||
memcpy(data_a.data() + 16, _key.data() + x, 32);
|
||||
openssl::Sha1To(sha1_a, data_a);
|
||||
|
||||
bytes::array<16 + 16 + 16> data_b;
|
||||
memcpy(data_b.data(), _key.data() + 32 + x, 16);
|
||||
memcpy(data_b.data() + 16, &msgKey, 16);
|
||||
memcpy(data_b.data() + 32, _key.data() + 48 + x, 16);
|
||||
openssl::Sha1To(sha1_b, data_b);
|
||||
|
||||
bytes::array<32 + 16> data_c;
|
||||
memcpy(data_c.data(), _key.data() + 64 + x, 32);
|
||||
memcpy(data_c.data() + 32, &msgKey, 16);
|
||||
openssl::Sha1To(sha1_c, data_c);
|
||||
|
||||
bytes::array<16 + 32> data_d;
|
||||
memcpy(data_d.data(), &msgKey, 16);
|
||||
memcpy(data_d.data() + 16, _key.data() + 96 + x, 32);
|
||||
openssl::Sha1To(sha1_d, data_d);
|
||||
|
||||
auto key = reinterpret_cast<bytes::type*>(&aesKey);
|
||||
auto iv = reinterpret_cast<bytes::type*>(&aesIV);
|
||||
memcpy(key, sha1_a.data(), 8);
|
||||
memcpy(key + 8, sha1_b.data() + 8, 12);
|
||||
memcpy(key + 8 + 12, sha1_c.data() + 4, 12);
|
||||
memcpy(iv, sha1_a.data() + 8, 12);
|
||||
memcpy(iv + 12, sha1_b.data(), 8);
|
||||
memcpy(iv + 12 + 8, sha1_c.data() + 16, 4);
|
||||
memcpy(iv + 12 + 8 + 4, sha1_d.data(), 8);
|
||||
}
|
||||
|
||||
void AuthKey::prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send) const {
|
||||
uint32 x = send ? 0 : 8;
|
||||
|
||||
bytes::array<32> sha256_a, sha256_b;
|
||||
bytes::array<16 + 36> data_a;
|
||||
memcpy(data_a.data(), &msgKey, 16);
|
||||
memcpy(data_a.data() + 16, _key.data() + x, 36);
|
||||
openssl::Sha256To(sha256_a, data_a);
|
||||
|
||||
bytes::array<36 + 16> data_b;
|
||||
memcpy(data_b.data(), _key.data() + 40 + x, 36);
|
||||
memcpy(data_b.data() + 36, &msgKey, 16);
|
||||
openssl::Sha256To(sha256_b, data_b);
|
||||
|
||||
auto key = reinterpret_cast<uchar*>(&aesKey);
|
||||
auto iv = reinterpret_cast<uchar*>(&aesIV);
|
||||
memcpy(key, sha256_a.data(), 8);
|
||||
memcpy(key + 8, sha256_b.data() + 8, 16);
|
||||
memcpy(key + 8 + 16, sha256_a.data() + 24, 8);
|
||||
memcpy(iv, sha256_b.data(), 8);
|
||||
memcpy(iv + 8, sha256_a.data() + 8, 16);
|
||||
memcpy(iv + 8 + 16, sha256_b.data() + 24, 8);
|
||||
}
|
||||
|
||||
const void *AuthKey::partForMsgKey(bool send) const {
|
||||
return _key.data() + 88 + (send ? 0 : 8);
|
||||
}
|
||||
|
||||
void AuthKey::write(QDataStream &to) const {
|
||||
to.writeRawData(reinterpret_cast<const char*>(_key.data()), _key.size());
|
||||
}
|
||||
|
||||
bytes::const_span AuthKey::data() const {
|
||||
return _key;
|
||||
}
|
||||
|
||||
bool AuthKey::equals(const std::shared_ptr<AuthKey> &other) const {
|
||||
return other ? (_key == other->_key) : false;
|
||||
}
|
||||
|
||||
crl::time AuthKey::creationTime() const {
|
||||
return _creationTime;
|
||||
}
|
||||
|
||||
TimeId AuthKey::expiresAt() const {
|
||||
return _expiresAt;
|
||||
}
|
||||
|
||||
void AuthKey::setExpiresAt(TimeId expiresAt) {
|
||||
Expects(_type == Type::Temporary);
|
||||
|
||||
_expiresAt = expiresAt;
|
||||
}
|
||||
|
||||
void AuthKey::FillData(Data &authKey, bytes::const_span computedAuthKey) {
|
||||
auto computedAuthKeySize = computedAuthKey.size();
|
||||
Assert(computedAuthKeySize <= kSize);
|
||||
auto authKeyBytes = gsl::make_span(authKey);
|
||||
if (computedAuthKeySize < kSize) {
|
||||
bytes::set_with_const(authKeyBytes.subspan(0, kSize - computedAuthKeySize), gsl::byte());
|
||||
bytes::copy(authKeyBytes.subspan(kSize - computedAuthKeySize), computedAuthKey);
|
||||
} else {
|
||||
bytes::copy(authKeyBytes, computedAuthKey);
|
||||
}
|
||||
}
|
||||
|
||||
void AuthKey::countKeyId() {
|
||||
const auto hash = openssl::Sha1(_key);
|
||||
|
||||
// Lower 64 bits = 8 bytes of 20 byte SHA1 hash.
|
||||
_keyId = *reinterpret_cast<const KeyId*>(hash.data() + 12);
|
||||
}
|
||||
|
||||
void aesIgeEncryptRaw(const void *src, void *dst, uint32 len, const void *key, const void *iv) {
|
||||
uchar aes_key[32], aes_iv[32];
|
||||
memcpy(aes_key, key, 32);
|
||||
memcpy(aes_iv, iv, 32);
|
||||
|
||||
AES_KEY aes;
|
||||
AES_set_encrypt_key(aes_key, 256, &aes);
|
||||
AES_ige_encrypt(static_cast<const uchar*>(src), static_cast<uchar*>(dst), len, &aes, aes_iv, AES_ENCRYPT);
|
||||
}
|
||||
|
||||
void aesIgeDecryptRaw(const void *src, void *dst, uint32 len, const void *key, const void *iv) {
|
||||
uchar aes_key[32], aes_iv[32];
|
||||
memcpy(aes_key, key, 32);
|
||||
memcpy(aes_iv, iv, 32);
|
||||
|
||||
AES_KEY aes;
|
||||
AES_set_decrypt_key(aes_key, 256, &aes);
|
||||
AES_ige_encrypt(static_cast<const uchar*>(src), static_cast<uchar*>(dst), len, &aes, aes_iv, AES_DECRYPT);
|
||||
}
|
||||
|
||||
void aesCtrEncrypt(bytes::span data, const void *key, CTRState *state) {
|
||||
AES_KEY aes;
|
||||
AES_set_encrypt_key(static_cast<const uchar*>(key), 256, &aes);
|
||||
|
||||
static_assert(CTRState::IvecSize == AES_BLOCK_SIZE, "Wrong size of ctr ivec!");
|
||||
static_assert(CTRState::EcountSize == AES_BLOCK_SIZE, "Wrong size of ctr ecount!");
|
||||
|
||||
CRYPTO_ctr128_encrypt(
|
||||
reinterpret_cast<const uchar*>(data.data()),
|
||||
reinterpret_cast<uchar*>(data.data()),
|
||||
data.size(),
|
||||
&aes,
|
||||
state->ivec,
|
||||
state->ecount,
|
||||
&state->num,
|
||||
(block128_f)AES_encrypt);
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
125
Telegram/SourceFiles/mtproto/mtproto_auth_key.h
Normal file
125
Telegram/SourceFiles/mtproto/mtproto_auth_key.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/bytes.h"
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class AuthKey {
|
||||
public:
|
||||
static constexpr auto kSize = 256; // 2048 bits.
|
||||
using Data = std::array<gsl::byte, kSize>;
|
||||
using KeyId = uint64;
|
||||
|
||||
enum class Type {
|
||||
Generated,
|
||||
Temporary,
|
||||
ReadFromFile,
|
||||
Local,
|
||||
};
|
||||
AuthKey(Type type, DcId dcId, const Data &data);
|
||||
explicit AuthKey(const Data &data);
|
||||
|
||||
AuthKey(const AuthKey &other) = delete;
|
||||
AuthKey &operator=(const AuthKey &other) = delete;
|
||||
|
||||
[[nodiscard]] Type type() const;
|
||||
[[nodiscard]] int dcId() const;
|
||||
[[nodiscard]] KeyId keyId() const;
|
||||
|
||||
void prepareAES_oldmtp(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send) const;
|
||||
void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send) const;
|
||||
|
||||
[[nodiscard]] const void *partForMsgKey(bool send) const;
|
||||
|
||||
void write(QDataStream &to) const;
|
||||
[[nodiscard]] bytes::const_span data() const;
|
||||
[[nodiscard]] bool equals(const std::shared_ptr<AuthKey> &other) const;
|
||||
|
||||
[[nodiscard]] crl::time creationTime() const; // > 0 if known.
|
||||
[[nodiscard]] TimeId expiresAt() const;
|
||||
void setExpiresAt(TimeId expiresAt);
|
||||
|
||||
static void FillData(Data &authKey, bytes::const_span computedAuthKey);
|
||||
|
||||
private:
|
||||
void countKeyId();
|
||||
|
||||
Type _type = Type::Generated;
|
||||
DcId _dcId = 0;
|
||||
Data _key = { { gsl::byte{} } };
|
||||
KeyId _keyId = 0;
|
||||
crl::time _creationTime = 0;
|
||||
TimeId _expiresAt = 0;
|
||||
|
||||
};
|
||||
|
||||
using AuthKeyPtr = std::shared_ptr<AuthKey>;
|
||||
using AuthKeysList = std::vector<AuthKeyPtr>;
|
||||
|
||||
void aesIgeEncryptRaw(const void *src, void *dst, uint32 len, const void *key, const void *iv);
|
||||
void aesIgeDecryptRaw(const void *src, void *dst, uint32 len, const void *key, const void *iv);
|
||||
|
||||
inline void aesIgeEncrypt_oldmtp(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES_oldmtp(msgKey, aesKey, aesIV, true);
|
||||
|
||||
return aesIgeEncryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
inline void aesIgeEncrypt(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES(msgKey, aesKey, aesIV, true);
|
||||
|
||||
return aesIgeEncryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const void *key128) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES_oldmtp(*(const MTPint128*)key128, aesKey, aesIV, false);
|
||||
|
||||
return aesIgeEncryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
inline void aesIgeDecrypt_oldmtp(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES_oldmtp(msgKey, aesKey, aesIV, false);
|
||||
|
||||
return aesIgeDecryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
inline void aesIgeDecrypt(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES(msgKey, aesKey, aesIV, false);
|
||||
|
||||
return aesIgeDecryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const void *key128) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES_oldmtp(*(const MTPint128*)key128, aesKey, aesIV, false);
|
||||
|
||||
return aesIgeDecryptRaw(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
// ctr used inplace, encrypt the data and leave it at the same place
|
||||
struct CTRState {
|
||||
static constexpr int KeySize = 32;
|
||||
static constexpr int IvecSize = 16;
|
||||
static constexpr int EcountSize = 16;
|
||||
|
||||
uchar ivec[IvecSize] = { 0 };
|
||||
uint32 num = 0;
|
||||
uchar ecount[EcountSize] = { 0 };
|
||||
};
|
||||
void aesCtrEncrypt(bytes::span data, const void *key, CTRState *state);
|
||||
|
||||
} // namespace MTP
|
||||
200
Telegram/SourceFiles/mtproto/mtproto_concurrent_sender.cpp
Normal file
200
Telegram/SourceFiles/mtproto/mtproto_concurrent_sender.cpp
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
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/mtproto_concurrent_sender.h"
|
||||
|
||||
#include "mtproto/mtp_instance.h"
|
||||
#include "mtproto/mtproto_response.h"
|
||||
#include "mtproto/facade.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class ConcurrentSender::HandlerMaker final {
|
||||
public:
|
||||
static DoneHandler MakeDone(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
Fn<void(FnMut<void()>)> runner);
|
||||
static FailHandler MakeFail(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
Fn<void(FnMut<void()>)> runner,
|
||||
FailSkipPolicy skipPolicy);
|
||||
};
|
||||
|
||||
DoneHandler ConcurrentSender::HandlerMaker::MakeDone(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
Fn<void(FnMut<void()>)> runner) {
|
||||
return [
|
||||
weak = base::make_weak(sender),
|
||||
runner = std::move(runner)
|
||||
](const Response &response) mutable {
|
||||
runner([=]() mutable {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->senderRequestDone(
|
||||
response.requestId,
|
||||
bytes::make_span(response.reply));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
FailHandler ConcurrentSender::HandlerMaker::MakeFail(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
Fn<void(FnMut<void()>)> runner,
|
||||
FailSkipPolicy skipPolicy) {
|
||||
return [
|
||||
weak = base::make_weak(sender),
|
||||
runner = std::move(runner),
|
||||
skipPolicy
|
||||
](const Error &error, const Response &response) mutable {
|
||||
if (skipPolicy == FailSkipPolicy::Simple) {
|
||||
if (IsDefaultHandledError(error)) {
|
||||
return false;
|
||||
}
|
||||
} else if (skipPolicy == FailSkipPolicy::HandleFlood) {
|
||||
if (IsDefaultHandledError(error) && !IsFloodError(error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
runner([=, requestId = response.requestId]() mutable {
|
||||
if (const auto strong = weak.get()) {
|
||||
strong->senderRequestFail(requestId, error);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Method>
|
||||
auto ConcurrentSender::with_instance(Method &&method)
|
||||
-> std::enable_if_t<is_callable_v<Method, not_null<Instance*>>> {
|
||||
crl::on_main([
|
||||
weak = _weak,
|
||||
method = std::forward<Method>(method)
|
||||
]() mutable {
|
||||
if (const auto instance = weak.get()) {
|
||||
std::move(method)(instance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ConcurrentSender::RequestBuilder::RequestBuilder(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
details::SerializedRequest &&serialized) noexcept
|
||||
: _sender(sender)
|
||||
, _serialized(std::move(serialized)) {
|
||||
}
|
||||
|
||||
void ConcurrentSender::RequestBuilder::setToDC(ShiftedDcId dcId) noexcept {
|
||||
_dcId = dcId;
|
||||
}
|
||||
|
||||
void ConcurrentSender::RequestBuilder::setCanWait(crl::time ms) noexcept {
|
||||
_canWait = ms;
|
||||
}
|
||||
|
||||
void ConcurrentSender::RequestBuilder::setFailSkipPolicy(
|
||||
FailSkipPolicy policy) noexcept {
|
||||
_failSkipPolicy = policy;
|
||||
}
|
||||
|
||||
void ConcurrentSender::RequestBuilder::setAfter(
|
||||
mtpRequestId requestId) noexcept {
|
||||
_afterRequestId = requestId;
|
||||
}
|
||||
|
||||
mtpRequestId ConcurrentSender::RequestBuilder::send() {
|
||||
const auto requestId = details::GetNextRequestId();
|
||||
const auto dcId = _dcId;
|
||||
const auto msCanWait = _canWait;
|
||||
const auto afterRequestId = _afterRequestId;
|
||||
|
||||
_sender->senderRequestRegister(requestId, std::move(_handlers));
|
||||
_sender->with_instance([
|
||||
=,
|
||||
request = std::move(_serialized),
|
||||
done = HandlerMaker::MakeDone(_sender, _sender->_runner),
|
||||
fail = HandlerMaker::MakeFail(
|
||||
_sender,
|
||||
_sender->_runner,
|
||||
_failSkipPolicy)
|
||||
](not_null<Instance*> instance) mutable {
|
||||
instance->sendSerialized(
|
||||
requestId,
|
||||
std::move(request),
|
||||
ResponseHandler{ std::move(done), std::move(fail) },
|
||||
dcId,
|
||||
msCanWait,
|
||||
afterRequestId);
|
||||
});
|
||||
|
||||
return requestId;
|
||||
}
|
||||
|
||||
ConcurrentSender::ConcurrentSender(
|
||||
base::weak_qptr<Instance> weak,
|
||||
Fn<void(FnMut<void()>)> runner)
|
||||
: _weak(weak)
|
||||
, _runner(runner) {
|
||||
}
|
||||
|
||||
ConcurrentSender::~ConcurrentSender() {
|
||||
senderRequestCancelAll();
|
||||
}
|
||||
|
||||
void ConcurrentSender::senderRequestRegister(
|
||||
mtpRequestId requestId,
|
||||
Handlers &&handlers) {
|
||||
_requests.emplace(requestId, std::move(handlers));
|
||||
}
|
||||
|
||||
void ConcurrentSender::senderRequestDone(
|
||||
mtpRequestId requestId,
|
||||
bytes::const_span result) {
|
||||
if (auto handlers = _requests.take(requestId)) {
|
||||
if (!handlers->done(requestId, result)) {
|
||||
handlers->fail(
|
||||
requestId,
|
||||
Error::Local(
|
||||
"RESPONSE_PARSE_FAILED",
|
||||
"ConcurrentSender::senderRequestDone"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ConcurrentSender::senderRequestFail(
|
||||
mtpRequestId requestId,
|
||||
const Error &error) {
|
||||
if (auto handlers = _requests.take(requestId)) {
|
||||
handlers->fail(requestId, error);
|
||||
}
|
||||
}
|
||||
|
||||
void ConcurrentSender::senderRequestCancel(mtpRequestId requestId) {
|
||||
senderRequestDetach(requestId);
|
||||
with_instance([=](not_null<Instance*> instance) {
|
||||
instance->cancel(requestId);
|
||||
});
|
||||
}
|
||||
|
||||
void ConcurrentSender::senderRequestCancelAll() {
|
||||
auto list = std::vector<mtpRequestId>(_requests.size());
|
||||
for (const auto &pair : base::take(_requests)) {
|
||||
list.push_back(pair.first);
|
||||
}
|
||||
with_instance([list = std::move(list)](not_null<Instance*> instance) {
|
||||
for (const auto requestId : list) {
|
||||
instance->cancel(requestId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ConcurrentSender::senderRequestDetach(mtpRequestId requestId) {
|
||||
_requests.erase(requestId);
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
433
Telegram/SourceFiles/mtproto/mtproto_concurrent_sender.h
Normal file
433
Telegram/SourceFiles/mtproto/mtproto_concurrent_sender.h
Normal file
@@ -0,0 +1,433 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/bytes.h"
|
||||
#include "base/weak_ptr.h"
|
||||
#include "base/weak_qptr.h"
|
||||
#include "base/flat_map.h"
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/details/mtproto_serialized_request.h"
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
#include <rpl/details/callable.h>
|
||||
|
||||
#ifndef _DEBUG
|
||||
#define MTP_SENDER_USE_GENERIC_HANDLERS
|
||||
#endif // !_DEBUG
|
||||
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class Error;
|
||||
class Instance;
|
||||
|
||||
class ConcurrentSender : public base::has_weak_ptr {
|
||||
template <typename ...Args>
|
||||
static constexpr bool is_callable_v
|
||||
= rpl::details::is_callable_v<Args...>;
|
||||
|
||||
template <typename Method>
|
||||
auto with_instance(Method &&method)
|
||||
-> std::enable_if_t<is_callable_v<Method, not_null<Instance*>>>;
|
||||
|
||||
struct Handlers {
|
||||
FnMut<bool(mtpRequestId requestId, bytes::const_span result)> done;
|
||||
FnMut<void(mtpRequestId requestId, const Error &error)> fail;
|
||||
};
|
||||
|
||||
enum class FailSkipPolicy {
|
||||
Simple,
|
||||
HandleFlood,
|
||||
HandleAll,
|
||||
};
|
||||
|
||||
class RequestBuilder {
|
||||
public:
|
||||
RequestBuilder(const RequestBuilder &other) = delete;
|
||||
RequestBuilder(RequestBuilder &&other) = default;
|
||||
RequestBuilder &operator=(const RequestBuilder &other) = delete;
|
||||
RequestBuilder &operator=(RequestBuilder &&other) = delete;
|
||||
|
||||
mtpRequestId send();
|
||||
|
||||
protected:
|
||||
RequestBuilder(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
details::SerializedRequest &&serialized) noexcept;
|
||||
|
||||
void setToDC(ShiftedDcId dcId) noexcept;
|
||||
void setCanWait(crl::time ms) noexcept;
|
||||
template <typename Response, typename InvokeFullDone>
|
||||
void setDoneHandler(InvokeFullDone &&invoke) noexcept;
|
||||
template <typename InvokeFullFail>
|
||||
void setFailHandler(InvokeFullFail &&invoke) noexcept;
|
||||
void setFailSkipPolicy(FailSkipPolicy policy) noexcept;
|
||||
void setAfter(mtpRequestId requestId) noexcept;
|
||||
|
||||
private:
|
||||
not_null<ConcurrentSender*> _sender;
|
||||
details::SerializedRequest _serialized;
|
||||
ShiftedDcId _dcId = 0;
|
||||
crl::time _canWait = 0;
|
||||
|
||||
Handlers _handlers;
|
||||
FailSkipPolicy _failSkipPolicy = FailSkipPolicy::Simple;
|
||||
mtpRequestId _afterRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
public:
|
||||
ConcurrentSender(
|
||||
base::weak_qptr<Instance> weak,
|
||||
Fn<void(FnMut<void()>)> runner);
|
||||
|
||||
template <typename Request>
|
||||
class SpecificRequestBuilder : public RequestBuilder {
|
||||
public:
|
||||
using Result = typename Request::ResponseType;
|
||||
|
||||
SpecificRequestBuilder(
|
||||
const SpecificRequestBuilder &other) = delete;
|
||||
SpecificRequestBuilder(
|
||||
SpecificRequestBuilder &&other) = default;
|
||||
SpecificRequestBuilder &operator=(
|
||||
const SpecificRequestBuilder &other) = delete;
|
||||
SpecificRequestBuilder &operator=(
|
||||
SpecificRequestBuilder &&other) = delete;
|
||||
|
||||
[[nodiscard]] SpecificRequestBuilder &toDC(
|
||||
ShiftedDcId dcId) noexcept;
|
||||
[[nodiscard]] SpecificRequestBuilder &afterDelay(
|
||||
crl::time ms) noexcept;
|
||||
|
||||
#ifndef MTP_SENDER_USE_GENERIC_HANDLERS
|
||||
// Allow code completion to show response type.
|
||||
[[nodiscard]] SpecificRequestBuilder &done(FnMut<void()> &&handler);
|
||||
[[nodiscard]] SpecificRequestBuilder &done(
|
||||
FnMut<void(mtpRequestId, Result &&)> &&handler);
|
||||
[[nodiscard]] SpecificRequestBuilder &done(
|
||||
FnMut<void(Result &&)> &&handler);
|
||||
[[nodiscard]] SpecificRequestBuilder &fail(Fn<void()> &&handler);
|
||||
[[nodiscard]] SpecificRequestBuilder &fail(
|
||||
Fn<void(mtpRequestId, const Error &)> &&handler);
|
||||
[[nodiscard]] SpecificRequestBuilder &fail(
|
||||
Fn<void(const Error &)> &&handler);
|
||||
#else // !MTP_SENDER_USE_GENERIC_HANDLERS
|
||||
template <typename Handler>
|
||||
[[nodiscard]] SpecificRequestBuilder &done(Handler &&handler);
|
||||
template <typename Handler>
|
||||
[[nodiscard]] SpecificRequestBuilder &fail(Handler &&handler);
|
||||
#endif // MTP_SENDER_USE_GENERIC_HANDLERS
|
||||
|
||||
[[nodiscard]] SpecificRequestBuilder &handleFloodErrors() noexcept;
|
||||
[[nodiscard]] SpecificRequestBuilder &handleAllErrors() noexcept;
|
||||
[[nodiscard]] SpecificRequestBuilder &afterRequest(
|
||||
mtpRequestId requestId) noexcept;
|
||||
|
||||
private:
|
||||
SpecificRequestBuilder(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
Request &&request) noexcept;
|
||||
|
||||
friend class ConcurrentSender;
|
||||
|
||||
};
|
||||
|
||||
class SentRequestWrap {
|
||||
public:
|
||||
void cancel();
|
||||
void detach();
|
||||
|
||||
private:
|
||||
friend class ConcurrentSender;
|
||||
SentRequestWrap(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
mtpRequestId requestId);
|
||||
|
||||
not_null<ConcurrentSender*> _sender;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
template <
|
||||
typename Request,
|
||||
typename = std::enable_if_t<!std::is_reference_v<Request>>,
|
||||
typename = typename Request::Unboxed>
|
||||
[[nodiscard]] SpecificRequestBuilder<Request> request(
|
||||
Request &&request) noexcept;
|
||||
|
||||
[[nodiscard]] SentRequestWrap request(mtpRequestId requestId) noexcept;
|
||||
|
||||
[[nodiscard]] auto requestCanceller() noexcept;
|
||||
|
||||
~ConcurrentSender();
|
||||
|
||||
private:
|
||||
class HandlerMaker;
|
||||
friend class HandlerMaker;
|
||||
friend class RequestBuilder;
|
||||
friend class SentRequestWrap;
|
||||
|
||||
void senderRequestRegister(mtpRequestId requestId, Handlers &&handlers);
|
||||
void senderRequestDone(
|
||||
mtpRequestId requestId,
|
||||
bytes::const_span result);
|
||||
void senderRequestFail(
|
||||
mtpRequestId requestId,
|
||||
const Error &error);
|
||||
void senderRequestCancel(mtpRequestId requestId);
|
||||
void senderRequestCancelAll();
|
||||
void senderRequestDetach(mtpRequestId requestId);
|
||||
|
||||
const base::weak_qptr<Instance> _weak;
|
||||
const Fn<void(FnMut<void()>)> _runner;
|
||||
base::flat_map<mtpRequestId, Handlers> _requests;
|
||||
|
||||
};
|
||||
|
||||
template <typename Result, typename InvokeFullDone>
|
||||
void ConcurrentSender::RequestBuilder::setDoneHandler(
|
||||
InvokeFullDone &&invoke
|
||||
) noexcept {
|
||||
_handlers.done = [handler = std::move(invoke)](
|
||||
mtpRequestId requestId,
|
||||
bytes::const_span result) mutable {
|
||||
auto from = reinterpret_cast<const mtpPrime*>(result.data());
|
||||
const auto end = from + result.size() / sizeof(mtpPrime);
|
||||
Result data;
|
||||
if (!data.read(from, end)) {
|
||||
return false;
|
||||
}
|
||||
handler(requestId, std::move(data));
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
template <typename InvokeFullFail>
|
||||
void ConcurrentSender::RequestBuilder::setFailHandler(
|
||||
InvokeFullFail &&invoke
|
||||
) noexcept {
|
||||
_handlers.fail = std::move(invoke);
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
ConcurrentSender::SpecificRequestBuilder<Request>::SpecificRequestBuilder(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
Request &&request) noexcept
|
||||
: RequestBuilder(sender, details::SerializedRequest::Serialize(request)) {
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::toDC(
|
||||
ShiftedDcId dcId
|
||||
) noexcept -> SpecificRequestBuilder & {
|
||||
setToDC(dcId);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::afterDelay(
|
||||
crl::time ms
|
||||
) noexcept -> SpecificRequestBuilder & {
|
||||
setCanWait(ms);
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifndef MTP_SENDER_USE_GENERIC_HANDLERS
|
||||
// Allow code completion to show response type.
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
|
||||
FnMut<void(Result &&)> &&handler
|
||||
) -> SpecificRequestBuilder & {
|
||||
setDoneHandler<Result>([handler = std::move(handler)](
|
||||
mtpRequestId requestId,
|
||||
Result &&result) mutable {
|
||||
handler(std::move(result));
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
|
||||
FnMut<void(mtpRequestId, Result &&)> &&handler
|
||||
) -> SpecificRequestBuilder & {
|
||||
setDoneHandler<Result>(std::move(handler));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
|
||||
FnMut<void()> &&handler
|
||||
) -> SpecificRequestBuilder & {
|
||||
setDoneHandler<Result>([handler = std::move(handler)](
|
||||
mtpRequestId requestId,
|
||||
Result &&result) mutable {
|
||||
std::move(handler)();
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
|
||||
Fn<void(const Error &)> &&handler
|
||||
) -> SpecificRequestBuilder & {
|
||||
setFailHandler([handler = std::move(handler)](
|
||||
mtpRequestId requestId,
|
||||
const Error &error) {
|
||||
handler(error);
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
|
||||
Fn<void(mtpRequestId, const Error &)> &&handler
|
||||
) -> SpecificRequestBuilder & {
|
||||
setFailHandler(std::move(handler));
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
|
||||
Fn<void()> &&handler
|
||||
) -> SpecificRequestBuilder & {
|
||||
setFailHandler([handler = std::move(handler)](
|
||||
mtpRequestId requestId,
|
||||
const Error &error) {
|
||||
handler();
|
||||
});
|
||||
return *this;
|
||||
}
|
||||
#else // !MTP_SENDER_USE_GENERIC_HANDLERS
|
||||
template <typename Request>
|
||||
template <typename Handler>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
|
||||
Handler &&handler
|
||||
) -> SpecificRequestBuilder & {
|
||||
using Result = typename Request::ResponseType;
|
||||
constexpr auto takesFull = rpl::details::is_callable_plain_v<
|
||||
Handler,
|
||||
mtpRequestId,
|
||||
Result>;
|
||||
[[maybe_unused]] constexpr auto takesResponse = rpl::details::is_callable_plain_v<
|
||||
Handler,
|
||||
Result>;
|
||||
[[maybe_unused]] constexpr auto takesNone = rpl::details::is_callable_plain_v<Handler>;
|
||||
|
||||
if constexpr (takesFull) {
|
||||
setDoneHandler<Result>(std::forward<Handler>(handler));
|
||||
} else if constexpr (takesResponse) {
|
||||
setDoneHandler<Result>([handler = std::forward<Handler>(handler)](
|
||||
mtpRequestId requestId,
|
||||
Result &&result) mutable {
|
||||
handler(std::move(result));
|
||||
});
|
||||
} else if constexpr (takesNone) {
|
||||
setDoneHandler<Result>([handler = std::forward<Handler>(handler)](
|
||||
mtpRequestId requestId,
|
||||
Result &&result) mutable {
|
||||
handler();
|
||||
});
|
||||
} else {
|
||||
static_assert(false_t(Handler{}), "Bad done handler.");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
template <typename Handler>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
|
||||
Handler &&handler
|
||||
) -> SpecificRequestBuilder & {
|
||||
constexpr auto takesFull = rpl::details::is_callable_plain_v<
|
||||
Handler,
|
||||
mtpRequestId,
|
||||
Error>;
|
||||
[[maybe_unused]] constexpr auto takesError = rpl::details::is_callable_plain_v<
|
||||
Handler,
|
||||
Error>;
|
||||
[[maybe_unused]] constexpr auto takesNone = rpl::details::is_callable_plain_v<Handler>;
|
||||
|
||||
if constexpr (takesFull) {
|
||||
setFailHandler(std::forward<Handler>(handler));
|
||||
} else if constexpr (takesError) {
|
||||
setFailHandler([handler = std::forward<Handler>(handler)](
|
||||
mtpRequestId requestId,
|
||||
const Error &error) {
|
||||
handler(error);
|
||||
});
|
||||
} else if constexpr (takesNone) {
|
||||
setFailHandler([handler = std::forward<Handler>(handler)](
|
||||
mtpRequestId requestId,
|
||||
const Error &error) {
|
||||
handler();
|
||||
});
|
||||
} else {
|
||||
static_assert(false_t(Handler{}), "Bad fail handler.");
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
#endif // MTP_SENDER_USE_GENERIC_HANDLERS
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::handleFloodErrors(
|
||||
) noexcept -> SpecificRequestBuilder & {
|
||||
setFailSkipPolicy(FailSkipPolicy::HandleFlood);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::handleAllErrors(
|
||||
) noexcept -> SpecificRequestBuilder & {
|
||||
setFailSkipPolicy(FailSkipPolicy::HandleAll);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
auto ConcurrentSender::SpecificRequestBuilder<Request>::afterRequest(
|
||||
mtpRequestId requestId
|
||||
) noexcept -> SpecificRequestBuilder & {
|
||||
setAfter(requestId);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline void ConcurrentSender::SentRequestWrap::cancel() {
|
||||
_sender->senderRequestCancel(_requestId);
|
||||
}
|
||||
|
||||
inline void ConcurrentSender::SentRequestWrap::detach() {
|
||||
_sender->senderRequestDetach(_requestId);
|
||||
}
|
||||
|
||||
inline ConcurrentSender::SentRequestWrap::SentRequestWrap(
|
||||
not_null<ConcurrentSender*> sender,
|
||||
mtpRequestId requestId
|
||||
) : _sender(sender)
|
||||
, _requestId(requestId) {
|
||||
}
|
||||
|
||||
template <typename Request, typename, typename>
|
||||
inline auto ConcurrentSender::request(Request &&request) noexcept
|
||||
-> SpecificRequestBuilder<Request> {
|
||||
return SpecificRequestBuilder<Request>(this, std::move(request));
|
||||
}
|
||||
|
||||
inline auto ConcurrentSender::requestCanceller() noexcept {
|
||||
return [=](mtpRequestId requestId) {
|
||||
request(requestId).cancel();
|
||||
};
|
||||
}
|
||||
|
||||
inline auto ConcurrentSender::request(mtpRequestId requestId) noexcept
|
||||
-> SentRequestWrap {
|
||||
return SentRequestWrap(this, requestId);
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
317
Telegram/SourceFiles/mtproto/mtproto_config.cpp
Normal file
317
Telegram/SourceFiles/mtproto/mtproto_config.cpp
Normal file
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
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/mtproto_config.h"
|
||||
|
||||
#include "storage/serialize_common.h"
|
||||
#include "mtproto/type_utils.h"
|
||||
#include "logs.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace {
|
||||
|
||||
constexpr auto kVersion = 1;
|
||||
|
||||
[[nodiscard]] QString ConfigDefaultReactionEmoji() {
|
||||
static const auto result = QString::fromUtf8("\xf0\x9f\x91\x8d");
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ConfigFields::ConfigFields(Environment environment)
|
||||
: webFileDcId(environment == Environment::Test ? 2 : 4)
|
||||
, txtDomainString(environment == Environment::Test
|
||||
? u"tapv3.stel.com"_q
|
||||
: u"apv3.stel.com"_q)
|
||||
, reactionDefaultEmoji(ConfigDefaultReactionEmoji())
|
||||
, gifSearchUsername(environment == Environment::Test
|
||||
? u"izgifbot"_q
|
||||
: u"gif"_q)
|
||||
, venueSearchUsername(environment == Environment::Test
|
||||
? u"foursquarebot"_q
|
||||
: u"foursquare"_q) {
|
||||
}
|
||||
|
||||
Config::Config(Environment environment)
|
||||
: _dcOptions(environment)
|
||||
, _fields(environment) {
|
||||
}
|
||||
|
||||
Config::Config(const Config &other)
|
||||
: _dcOptions(other.dcOptions())
|
||||
, _fields(other._fields) {
|
||||
}
|
||||
|
||||
QByteArray Config::serialize() const {
|
||||
auto options = _dcOptions.serialize();
|
||||
auto size = sizeof(qint32) * 2 // version + environment
|
||||
+ Serialize::bytearraySize(options)
|
||||
+ 19 * sizeof(qint32)
|
||||
+ Serialize::stringSize(_fields.internalLinksDomain)
|
||||
+ 6 * sizeof(qint32)
|
||||
+ Serialize::stringSize(_fields.txtDomainString)
|
||||
+ 3 * sizeof(qint32)
|
||||
+ Serialize::stringSize(_fields.reactionDefaultEmoji)
|
||||
+ sizeof(quint64)
|
||||
+ sizeof(qint32)
|
||||
+ Serialize::stringSize(_fields.gifSearchUsername)
|
||||
+ Serialize::stringSize(_fields.venueSearchUsername);
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
{
|
||||
QDataStream stream(&result, QIODevice::WriteOnly);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
stream
|
||||
<< qint32(kVersion)
|
||||
<< qint32(_dcOptions.isTestMode()
|
||||
? Environment::Test
|
||||
: Environment::Production)
|
||||
<< options
|
||||
<< qint32(_fields.chatSizeMax)
|
||||
<< qint32(_fields.megagroupSizeMax)
|
||||
<< qint32(_fields.forwardedCountMax)
|
||||
<< qint32(_fields.onlineUpdatePeriod)
|
||||
<< qint32(_fields.offlineBlurTimeout)
|
||||
<< qint32(_fields.offlineIdleTimeout)
|
||||
<< qint32(_fields.onlineFocusTimeout)
|
||||
<< qint32(_fields.onlineCloudTimeout)
|
||||
<< qint32(_fields.notifyCloudDelay)
|
||||
<< qint32(_fields.notifyDefaultDelay)
|
||||
<< qint32(0) // legacy savedGifsLimit
|
||||
<< qint32(_fields.editTimeLimit)
|
||||
<< qint32(_fields.revokeTimeLimit)
|
||||
<< qint32(_fields.revokePrivateTimeLimit)
|
||||
<< qint32(_fields.revokePrivateInbox ? 1 : 0)
|
||||
<< qint32(_fields.stickersRecentLimit)
|
||||
<< qint32(0) // legacy stickersFavedLimit
|
||||
<< qint32(0) // legacy pinnedDialogsCountMax
|
||||
<< qint32(0) // legacy pinnedDialogsInFolderMax
|
||||
<< _fields.internalLinksDomain
|
||||
<< qint32(_fields.channelsReadMediaPeriod)
|
||||
<< qint32(_fields.callReceiveTimeoutMs)
|
||||
<< qint32(_fields.callRingTimeoutMs)
|
||||
<< qint32(_fields.callConnectTimeoutMs)
|
||||
<< qint32(_fields.callPacketTimeoutMs)
|
||||
<< qint32(_fields.webFileDcId)
|
||||
<< _fields.txtDomainString
|
||||
<< qint32(1) // legacy phoneCallsEnabled
|
||||
<< qint32(_fields.blockedMode ? 1 : 0)
|
||||
<< qint32(_fields.captionLengthMax)
|
||||
<< _fields.reactionDefaultEmoji
|
||||
<< quint64(_fields.reactionDefaultCustom)
|
||||
<< qint32(_fields.ratingDecay)
|
||||
<< _fields.gifSearchUsername
|
||||
<< _fields.venueSearchUsername;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::unique_ptr<Config> Config::FromSerialized(const QByteArray &serialized) {
|
||||
auto result = std::unique_ptr<Config>();
|
||||
auto raw = result.get();
|
||||
|
||||
QDataStream stream(serialized);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
auto version = qint32();
|
||||
stream >> version;
|
||||
if (version != kVersion) {
|
||||
return result;
|
||||
}
|
||||
auto environment = qint32();
|
||||
stream >> environment;
|
||||
switch (environment) {
|
||||
case qint32(Environment::Test):
|
||||
result = std::make_unique<Config>(Environment::Test);
|
||||
break;
|
||||
case qint32(Environment::Production):
|
||||
result = std::make_unique<Config>(Environment::Production);
|
||||
break;
|
||||
}
|
||||
if (!(raw = result.get())) {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto dcOptionsSerialized = QByteArray();
|
||||
auto legacySavedGifsLimit = int();
|
||||
auto legacyStickersFavedLimit = int();
|
||||
auto legacyPinnedDialogsCountMax = 0;
|
||||
auto legacyPinnedDialogsInFolderMax = 0;
|
||||
auto legacyPhoneCallsEnabled = rpl::variable<bool>();
|
||||
const auto read = [&](auto &field) {
|
||||
using Type = std::remove_reference_t<decltype(field)>;
|
||||
if constexpr (std::is_same_v<Type, int>
|
||||
|| std::is_same_v<Type, rpl::variable<int>>) {
|
||||
auto value = qint32();
|
||||
stream >> value;
|
||||
field = value;
|
||||
} else if constexpr (std::is_same_v<Type, uint64>) {
|
||||
auto value = quint64();
|
||||
stream >> value;
|
||||
field = value;
|
||||
} else if constexpr (std::is_same_v<Type, bool>
|
||||
|| std::is_same_v<Type, rpl::variable<bool>>) {
|
||||
auto value = qint32();
|
||||
stream >> value;
|
||||
field = (value == 1);
|
||||
} else if constexpr (std::is_same_v<Type, QByteArray>
|
||||
|| std::is_same_v<Type, QString>) {
|
||||
stream >> field;
|
||||
} else {
|
||||
static_assert(false_(field), "Bad read() call.");
|
||||
}
|
||||
};
|
||||
|
||||
read(dcOptionsSerialized);
|
||||
read(raw->_fields.chatSizeMax);
|
||||
read(raw->_fields.megagroupSizeMax);
|
||||
read(raw->_fields.forwardedCountMax);
|
||||
read(raw->_fields.onlineUpdatePeriod);
|
||||
read(raw->_fields.offlineBlurTimeout);
|
||||
read(raw->_fields.offlineIdleTimeout);
|
||||
read(raw->_fields.onlineFocusTimeout);
|
||||
read(raw->_fields.onlineCloudTimeout);
|
||||
read(raw->_fields.notifyCloudDelay);
|
||||
read(raw->_fields.notifyDefaultDelay);
|
||||
read(legacySavedGifsLimit);
|
||||
read(raw->_fields.editTimeLimit);
|
||||
read(raw->_fields.revokeTimeLimit);
|
||||
read(raw->_fields.revokePrivateTimeLimit);
|
||||
read(raw->_fields.revokePrivateInbox);
|
||||
read(raw->_fields.stickersRecentLimit);
|
||||
read(legacyStickersFavedLimit);
|
||||
read(legacyPinnedDialogsCountMax);
|
||||
read(legacyPinnedDialogsInFolderMax);
|
||||
read(raw->_fields.internalLinksDomain);
|
||||
read(raw->_fields.channelsReadMediaPeriod);
|
||||
read(raw->_fields.callReceiveTimeoutMs);
|
||||
read(raw->_fields.callRingTimeoutMs);
|
||||
read(raw->_fields.callConnectTimeoutMs);
|
||||
read(raw->_fields.callPacketTimeoutMs);
|
||||
read(raw->_fields.webFileDcId);
|
||||
read(raw->_fields.txtDomainString);
|
||||
read(legacyPhoneCallsEnabled);
|
||||
read(raw->_fields.blockedMode);
|
||||
read(raw->_fields.captionLengthMax);
|
||||
if (!stream.atEnd()) {
|
||||
read(raw->_fields.reactionDefaultEmoji);
|
||||
read(raw->_fields.reactionDefaultCustom);
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
read(raw->_fields.ratingDecay);
|
||||
}
|
||||
if (!stream.atEnd()) {
|
||||
read(raw->_fields.gifSearchUsername);
|
||||
read(raw->_fields.venueSearchUsername);
|
||||
}
|
||||
|
||||
if (stream.status() != QDataStream::Ok
|
||||
|| !raw->_dcOptions.constructFromSerialized(dcOptionsSerialized)) {
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const ConfigFields &Config::values() const {
|
||||
return _fields;
|
||||
}
|
||||
|
||||
void Config::apply(const MTPDconfig &data) {
|
||||
if (mtpIsTrue(data.vtest_mode()) != _dcOptions.isTestMode()) {
|
||||
LOG(("MTP Error: config with wrong test mode field received!"));
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("MTP Info: got config, "
|
||||
"chat_size_max: %1, "
|
||||
"date: %2, "
|
||||
"test_mode: %3, "
|
||||
"this_dc: %4, "
|
||||
"dc_options.length: %5"
|
||||
).arg(data.vchat_size_max().v
|
||||
).arg(data.vdate().v
|
||||
).arg(mtpIsTrue(data.vtest_mode())
|
||||
).arg(data.vthis_dc().v
|
||||
).arg(data.vdc_options().v.size()));
|
||||
|
||||
_fields.chatSizeMax = data.vchat_size_max().v;
|
||||
_fields.megagroupSizeMax = data.vmegagroup_size_max().v;
|
||||
_fields.forwardedCountMax = data.vforwarded_count_max().v;
|
||||
_fields.onlineUpdatePeriod = data.vonline_update_period_ms().v;
|
||||
_fields.offlineBlurTimeout = data.voffline_blur_timeout_ms().v;
|
||||
_fields.offlineIdleTimeout = data.voffline_idle_timeout_ms().v;
|
||||
_fields.onlineCloudTimeout = data.vonline_cloud_timeout_ms().v;
|
||||
_fields.notifyCloudDelay = data.vnotify_cloud_delay_ms().v;
|
||||
_fields.notifyDefaultDelay = data.vnotify_default_delay_ms().v;
|
||||
_fields.editTimeLimit = data.vedit_time_limit().v;
|
||||
_fields.revokeTimeLimit = data.vrevoke_time_limit().v;
|
||||
_fields.revokePrivateTimeLimit = data.vrevoke_pm_time_limit().v;
|
||||
_fields.revokePrivateInbox = data.is_revoke_pm_inbox();
|
||||
_fields.stickersRecentLimit = data.vstickers_recent_limit().v;
|
||||
_fields.internalLinksDomain = qs(data.vme_url_prefix());
|
||||
_fields.channelsReadMediaPeriod = data.vchannels_read_media_period().v;
|
||||
_fields.webFileDcId = data.vwebfile_dc_id().v;
|
||||
_fields.callReceiveTimeoutMs = data.vcall_receive_timeout_ms().v;
|
||||
_fields.callRingTimeoutMs = data.vcall_ring_timeout_ms().v;
|
||||
_fields.callConnectTimeoutMs = data.vcall_connect_timeout_ms().v;
|
||||
_fields.callPacketTimeoutMs = data.vcall_packet_timeout_ms().v;
|
||||
_fields.blockedMode = data.is_blocked_mode();
|
||||
_fields.captionLengthMax = data.vcaption_length_max().v;
|
||||
_fields.reactionDefaultEmoji = ConfigDefaultReactionEmoji();
|
||||
_fields.reactionDefaultCustom = 0;
|
||||
if (const auto reaction = data.vreactions_default()) {
|
||||
reaction->match([&](const MTPDreactionEmpty &) {
|
||||
}, [&](const MTPDreactionEmoji &data) {
|
||||
_fields.reactionDefaultEmoji = qs(data.vemoticon());
|
||||
}, [&](const MTPDreactionCustomEmoji &data) {
|
||||
_fields.reactionDefaultCustom = data.vdocument_id().v;
|
||||
}, [&](const MTPDreactionPaid &data) {
|
||||
_fields.reactionDefaultEmoji = QString(QChar('*'));
|
||||
});
|
||||
}
|
||||
_fields.autologinToken = qs(data.vautologin_token().value_or_empty());
|
||||
_fields.ratingDecay = data.vrating_e_decay().v;
|
||||
if (_fields.ratingDecay <= 0) {
|
||||
_fields.ratingDecay = ConfigFields(
|
||||
_dcOptions.environment()
|
||||
).ratingDecay;
|
||||
}
|
||||
_fields.gifSearchUsername = qs(data.vgif_search_username().value_or_empty());
|
||||
_fields.venueSearchUsername = qs(data.vvenue_search_username().value_or_empty());
|
||||
|
||||
if (data.vdc_options().v.empty()) {
|
||||
LOG(("MTP Error: config with empty dc_options received!"));
|
||||
} else {
|
||||
dcOptions().setFromList(data.vdc_options());
|
||||
}
|
||||
|
||||
_updates.fire({});
|
||||
}
|
||||
|
||||
rpl::producer<> Config::updates() const {
|
||||
return _updates.events();
|
||||
}
|
||||
|
||||
void Config::setChatSizeMax(int value) {
|
||||
_fields.chatSizeMax = value;
|
||||
}
|
||||
|
||||
void Config::setStickersRecentLimit(int value) {
|
||||
_fields.stickersRecentLimit = value;
|
||||
}
|
||||
|
||||
void Config::setMegagroupSizeMax(int value) {
|
||||
_fields.megagroupSizeMax = value;
|
||||
}
|
||||
|
||||
void Config::setTxtDomainString(const QString &value) {
|
||||
_fields.txtDomainString = value;
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
95
Telegram/SourceFiles/mtproto/mtproto_config.h
Normal file
95
Telegram/SourceFiles/mtproto/mtproto_config.h
Normal file
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
struct ConfigFields {
|
||||
explicit ConfigFields(Environment environment);
|
||||
|
||||
int chatSizeMax = 200;
|
||||
int megagroupSizeMax = 10000;
|
||||
int forwardedCountMax = 100;
|
||||
int onlineUpdatePeriod = 120000;
|
||||
int offlineBlurTimeout = 5000;
|
||||
int offlineIdleTimeout = 30000;
|
||||
int onlineFocusTimeout = 1000; // Not from the server config.
|
||||
int onlineCloudTimeout = 300000;
|
||||
int notifyCloudDelay = 30000;
|
||||
int notifyDefaultDelay = 1500;
|
||||
int editTimeLimit = 172800;
|
||||
int revokeTimeLimit = 172800;
|
||||
int revokePrivateTimeLimit = 172800;
|
||||
bool revokePrivateInbox = false;
|
||||
int stickersRecentLimit = 30;
|
||||
QString internalLinksDomain = u"https://t.me/"_q;
|
||||
int channelsReadMediaPeriod = 86400 * 7;
|
||||
int callReceiveTimeoutMs = 20000;
|
||||
int callRingTimeoutMs = 90000;
|
||||
int callConnectTimeoutMs = 30000;
|
||||
int callPacketTimeoutMs = 10000;
|
||||
int webFileDcId = 4;
|
||||
QString txtDomainString;
|
||||
bool blockedMode = false;
|
||||
int captionLengthMax = 1024;
|
||||
int ratingDecay = 2419200;
|
||||
QString reactionDefaultEmoji;
|
||||
uint64 reactionDefaultCustom = 0;
|
||||
QString autologinToken;
|
||||
|
||||
QString gifSearchUsername;
|
||||
QString venueSearchUsername;
|
||||
};
|
||||
|
||||
class Config final {
|
||||
struct PrivateTag {
|
||||
};
|
||||
|
||||
public:
|
||||
explicit Config(Environment environment);
|
||||
Config(const Config &other);
|
||||
|
||||
[[nodiscard]] QByteArray serialize() const;
|
||||
[[nodiscard]] static std::unique_ptr<Config> FromSerialized(
|
||||
const QByteArray &serialized);
|
||||
|
||||
[[nodiscard]] DcOptions &dcOptions() {
|
||||
return _dcOptions;
|
||||
}
|
||||
[[nodiscard]] const DcOptions &dcOptions() const {
|
||||
return _dcOptions;
|
||||
}
|
||||
[[nodiscard]] MTP::Environment environment() const {
|
||||
return _dcOptions.environment();
|
||||
}
|
||||
[[nodiscard]] bool isTestMode() const {
|
||||
return _dcOptions.isTestMode();
|
||||
}
|
||||
|
||||
void apply(const MTPDconfig &data);
|
||||
|
||||
[[nodiscard]] const ConfigFields &values() const;
|
||||
[[nodiscard]] rpl::producer<> updates() const;
|
||||
|
||||
// Set from legacy local stored values.
|
||||
void setChatSizeMax(int value);
|
||||
void setStickersRecentLimit(int value);
|
||||
void setMegagroupSizeMax(int value);
|
||||
void setTxtDomainString(const QString &value);
|
||||
|
||||
private:
|
||||
DcOptions _dcOptions;
|
||||
ConfigFields _fields;
|
||||
|
||||
rpl::event_stream<> _updates;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP
|
||||
840
Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp
Normal file
840
Telegram/SourceFiles/mtproto/mtproto_dc_options.cpp
Normal file
@@ -0,0 +1,840 @@
|
||||
/*
|
||||
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/mtproto_dc_options.h"
|
||||
|
||||
#include "mtproto/details/mtproto_rsa_public_key.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "mtproto/connection_tcp.h"
|
||||
#include "storage/serialize_common.h"
|
||||
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
||||
namespace MTP {
|
||||
namespace {
|
||||
|
||||
constexpr auto kVersion = 2;
|
||||
|
||||
using namespace details;
|
||||
|
||||
struct BuiltInDc {
|
||||
int id;
|
||||
const char *ip;
|
||||
int port;
|
||||
};
|
||||
|
||||
const BuiltInDc kBuiltInDcs[] = {
|
||||
{ 1, "188.243.5.131" , 20443 },
|
||||
{ 2, "188.243.5.131" , 20443 },
|
||||
{ 3, "188.243.5.131" , 20443 },
|
||||
};
|
||||
|
||||
const BuiltInDc kBuiltInDcsIPv6[] = {
|
||||
{ 1, "188.243.5.131" , 20443 },
|
||||
{ 2, "188.243.5.131" , 20443 },
|
||||
{ 3, "188.243.5.131" , 20443 },
|
||||
};
|
||||
|
||||
const BuiltInDc kBuiltInDcsTest[] = {
|
||||
{ 1, "188.243.5.131" , 20443 },
|
||||
{ 2, "188.243.5.131" , 20443 },
|
||||
{ 3, "188.243.5.131" , 20443 },
|
||||
};
|
||||
|
||||
const BuiltInDc kBuiltInDcsIPv6Test[] = {
|
||||
{ 1, "188.243.5.131" , 20443 },
|
||||
{ 2, "188.243.5.131" , 20443 },
|
||||
{ 3, "188.243.5.131" , 20443 },
|
||||
};
|
||||
|
||||
const char *kTestPublicRSAKeys[] = { "\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIIBCgKCAQEAu+3tvscWDAlEvVylTeMr5FpU2AjgqzoQHPjzp69r0YAtq0a8rX0M\n\
|
||||
Ue78F/FRAqBaEbZW6WBzF3AjOlNYpOtvvwGhl9rGCgziunbd9nwcKJBMDWS9O7Mz\n\
|
||||
/8xjz/swIB4V56XcjOhrjUHJ/GniFKoum00xeEcYnr5xnLesvpVMq97Ga6b+xt3H\n\
|
||||
RftHY/Zy1dG5zs8upuiAOlEiKilhu1IthfMjFG3NF6TiGrO9YU3YixFbJy67jtHk\n\
|
||||
v5FarscM2fC5iWQ2eP1y6jXR64sGU3QjncvozYOePrH9jGcnmzUmj42x/H28IjJQ\n\
|
||||
9EjEc22sPOuauK0IF2QiCGh+TfsKCK189wIDAQAB\n\
|
||||
-----END RSA PUBLIC KEY-----" };
|
||||
|
||||
const char *kPublicRSAKeys[] = { "\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIIBCgKCAQEAu+3tvscWDAlEvVylTeMr5FpU2AjgqzoQHPjzp69r0YAtq0a8rX0M\n\
|
||||
Ue78F/FRAqBaEbZW6WBzF3AjOlNYpOtvvwGhl9rGCgziunbd9nwcKJBMDWS9O7Mz\n\
|
||||
/8xjz/swIB4V56XcjOhrjUHJ/GniFKoum00xeEcYnr5xnLesvpVMq97Ga6b+xt3H\n\
|
||||
RftHY/Zy1dG5zs8upuiAOlEiKilhu1IthfMjFG3NF6TiGrO9YU3YixFbJy67jtHk\n\
|
||||
v5FarscM2fC5iWQ2eP1y6jXR64sGU3QjncvozYOePrH9jGcnmzUmj42x/H28IjJQ\n\
|
||||
9EjEc22sPOuauK0IF2QiCGh+TfsKCK189wIDAQAB\n\
|
||||
-----END RSA PUBLIC KEY-----" };
|
||||
|
||||
} // namespace
|
||||
|
||||
class DcOptions::WriteLocker {
|
||||
public:
|
||||
WriteLocker(not_null<DcOptions*> that)
|
||||
: _that(that)
|
||||
, _lock(&_that->_useThroughLockers) {
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
_lock.unlock();
|
||||
}
|
||||
|
||||
~WriteLocker() {
|
||||
_that->computeCdnDcIds();
|
||||
}
|
||||
|
||||
private:
|
||||
not_null<DcOptions*> _that;
|
||||
QWriteLocker _lock;
|
||||
|
||||
};
|
||||
|
||||
class DcOptions::ReadLocker {
|
||||
public:
|
||||
ReadLocker(not_null<const DcOptions*> that)
|
||||
: _lock(&that->_useThroughLockers) {
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
_lock.unlock();
|
||||
}
|
||||
|
||||
private:
|
||||
QReadLocker _lock;
|
||||
|
||||
};
|
||||
|
||||
DcOptions::DcOptions(Environment environment)
|
||||
: _environment(environment) {
|
||||
constructFromBuiltIn();
|
||||
}
|
||||
|
||||
DcOptions::DcOptions(const DcOptions &other)
|
||||
: _environment(other._environment)
|
||||
, _data(other._data)
|
||||
, _cdnDcIds(other._cdnDcIds)
|
||||
, _publicKeys(other._publicKeys)
|
||||
, _cdnPublicKeys(other._cdnPublicKeys)
|
||||
, _immutable(other._immutable) {
|
||||
}
|
||||
|
||||
DcOptions::~DcOptions() = default;
|
||||
|
||||
bool DcOptions::ValidateSecret(bytes::const_span secret) {
|
||||
// See also TcpConnection::Protocol::Create.
|
||||
return (secret.size() >= 21 && secret[0] == bytes::type(0xEE))
|
||||
|| (secret.size() == 17 && secret[0] == bytes::type(0xDD))
|
||||
|| (secret.size() == 16)
|
||||
|| secret.empty();
|
||||
}
|
||||
|
||||
void DcOptions::readBuiltInPublicKeys() {
|
||||
const auto builtin = (_environment == Environment::Test)
|
||||
? gsl::make_span(kTestPublicRSAKeys)
|
||||
: gsl::make_span(kPublicRSAKeys);
|
||||
for (const auto key : builtin) {
|
||||
const auto keyBytes = bytes::make_span(key, strlen(key));
|
||||
auto parsed = RSAPublicKey(keyBytes);
|
||||
if (parsed.valid()) {
|
||||
_publicKeys.emplace(parsed.fingerprint(), std::move(parsed));
|
||||
} else {
|
||||
LOG(("MTP Error: could not read this public RSA key:"));
|
||||
LOG((key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Environment DcOptions::environment() const {
|
||||
return _environment;
|
||||
}
|
||||
|
||||
bool DcOptions::isTestMode() const {
|
||||
return (_environment != Environment::Production);
|
||||
}
|
||||
|
||||
void DcOptions::constructFromBuiltIn() {
|
||||
WriteLocker lock(this);
|
||||
_data.clear();
|
||||
|
||||
readBuiltInPublicKeys();
|
||||
|
||||
const auto list = isTestMode()
|
||||
? gsl::make_span(kBuiltInDcsTest)
|
||||
: gsl::make_span(kBuiltInDcs).subspan(0);
|
||||
for (const auto &entry : list) {
|
||||
const auto flags = Flag::f_static | 0;
|
||||
applyOneGuarded(entry.id, flags, entry.ip, entry.port, {});
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3"
|
||||
).arg(entry.id
|
||||
).arg(entry.ip
|
||||
).arg(entry.port));
|
||||
}
|
||||
|
||||
const auto listv6 = isTestMode()
|
||||
? gsl::make_span(kBuiltInDcsIPv6Test)
|
||||
: gsl::make_span(kBuiltInDcsIPv6).subspan(0);
|
||||
for (const auto &entry : listv6) {
|
||||
const auto flags = Flag::f_static | Flag::f_ipv6;
|
||||
applyOneGuarded(entry.id, flags, entry.ip, entry.port, {});
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: "
|
||||
"%2:%3"
|
||||
).arg(entry.id
|
||||
).arg(entry.ip
|
||||
).arg(entry.port));
|
||||
}
|
||||
}
|
||||
|
||||
void DcOptions::processFromList(
|
||||
const QVector<MTPDcOption> &options,
|
||||
bool overwrite) {
|
||||
if (options.empty() || _immutable) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = [&] {
|
||||
if (overwrite) {
|
||||
return base::flat_map<DcId, std::vector<Endpoint>>();
|
||||
}
|
||||
ReadLocker lock(this);
|
||||
return _data;
|
||||
}();
|
||||
for (auto &mtpOption : options) {
|
||||
if (mtpOption.type() != mtpc_dcOption) {
|
||||
LOG(("Wrong type in DcOptions: %1").arg(mtpOption.type()));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &option = mtpOption.c_dcOption();
|
||||
auto dcId = option.vid().v;
|
||||
auto flags = option.vflags().v;
|
||||
auto ip = std::string(
|
||||
option.vip_address().v.constData(),
|
||||
option.vip_address().v.size());
|
||||
auto port = option.vport().v;
|
||||
auto secret = bytes::make_vector(option.vsecret().value_or_empty());
|
||||
ApplyOneOption(data, dcId, flags, ip, port, secret);
|
||||
}
|
||||
|
||||
const auto difference = [&] {
|
||||
WriteLocker lock(this);
|
||||
auto result = CountOptionsDifference(_data, data);
|
||||
if (!result.empty()) {
|
||||
_data = std::move(data);
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
for (const auto dcId : difference) {
|
||||
_changed.fire_copy(dcId);
|
||||
}
|
||||
}
|
||||
|
||||
void DcOptions::setFromList(const MTPVector<MTPDcOption> &options) {
|
||||
processFromList(options.v, true);
|
||||
}
|
||||
|
||||
void DcOptions::addFromList(const MTPVector<MTPDcOption> &options) {
|
||||
processFromList(options.v, false);
|
||||
}
|
||||
|
||||
void DcOptions::addFromOther(DcOptions &&options) {
|
||||
if (this == &options || _immutable) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto idsChanged = std::vector<DcId>();
|
||||
{
|
||||
ReadLocker lock(&options);
|
||||
if (options._data.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
idsChanged.reserve(options._data.size());
|
||||
{
|
||||
WriteLocker lock(this);
|
||||
const auto changed = [&](const std::vector<Endpoint> &list) {
|
||||
auto result = false;
|
||||
for (const auto &endpoint : list) {
|
||||
const auto dcId = endpoint.id;
|
||||
const auto flags = endpoint.flags;
|
||||
const auto &ip = endpoint.ip;
|
||||
const auto port = endpoint.port;
|
||||
const auto &secret = endpoint.secret;
|
||||
if (applyOneGuarded(dcId, flags, ip, port, secret)) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
for (const auto &item : base::take(options._data)) {
|
||||
if (changed(item.second)) {
|
||||
idsChanged.push_back(item.first);
|
||||
}
|
||||
}
|
||||
for (auto &item : options._cdnPublicKeys) {
|
||||
for (auto &entry : item.second) {
|
||||
_cdnPublicKeys[item.first].insert(std::move(entry));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto dcId : idsChanged) {
|
||||
_changed.fire_copy(dcId);
|
||||
}
|
||||
}
|
||||
|
||||
void DcOptions::constructAddOne(
|
||||
int id,
|
||||
Flags flags,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
const bytes::vector &secret) {
|
||||
WriteLocker lock(this);
|
||||
applyOneGuarded(BareDcId(id), flags, ip, port, secret);
|
||||
}
|
||||
|
||||
bool DcOptions::applyOneGuarded(
|
||||
DcId dcId,
|
||||
Flags flags,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
const bytes::vector &secret) {
|
||||
return ApplyOneOption(_data, dcId, flags, ip, port, secret);
|
||||
}
|
||||
|
||||
bool DcOptions::ApplyOneOption(
|
||||
base::flat_map<DcId, std::vector<Endpoint>> &data,
|
||||
DcId dcId,
|
||||
Flags flags,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
const bytes::vector &secret) {
|
||||
auto i = data.find(dcId);
|
||||
if (i != data.cend()) {
|
||||
for (auto &endpoint : i->second) {
|
||||
if (endpoint.ip == ip && endpoint.port == port) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
i->second.emplace_back(dcId, flags, ip, port, secret);
|
||||
} else {
|
||||
data.emplace(dcId, std::vector<Endpoint>(
|
||||
1,
|
||||
Endpoint(dcId, flags, ip, port, secret)));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<DcId> DcOptions::CountOptionsDifference(
|
||||
const base::flat_map<DcId, std::vector<Endpoint>> &a,
|
||||
const base::flat_map<DcId, std::vector<Endpoint>> &b) {
|
||||
auto result = std::vector<DcId>();
|
||||
const auto find = [](
|
||||
const std::vector<Endpoint> &where,
|
||||
const Endpoint &what) {
|
||||
for (const auto &endpoint : where) {
|
||||
if (endpoint.ip == what.ip && endpoint.port == what.port) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto equal = [&](
|
||||
const std::vector<Endpoint> &m,
|
||||
const std::vector<Endpoint> &n) {
|
||||
if (m.size() != n.size()) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &endpoint : m) {
|
||||
if (!find(n, endpoint)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
auto i = begin(a);
|
||||
auto j = begin(b);
|
||||
const auto max = std::numeric_limits<DcId>::max();
|
||||
while (i != end(a) || j != end(b)) {
|
||||
const auto aId = (i == end(a)) ? max : i->first;
|
||||
const auto bId = (j == end(b)) ? max : j->first;
|
||||
if (aId < bId) {
|
||||
result.push_back(aId);
|
||||
++i;
|
||||
} else if (bId < aId) {
|
||||
result.push_back(bId);
|
||||
++j;
|
||||
} else {
|
||||
if (!equal(i->second, j->second)) {
|
||||
result.push_back(aId);
|
||||
}
|
||||
++i;
|
||||
++j;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray DcOptions::serialize() const {
|
||||
if (_immutable) {
|
||||
// Don't write the overriden options to our settings.
|
||||
return DcOptions(_environment).serialize();
|
||||
}
|
||||
|
||||
ReadLocker lock(this);
|
||||
|
||||
auto size = sizeof(qint32);
|
||||
|
||||
// Dc options.
|
||||
auto optionsCount = 0;
|
||||
size += sizeof(qint32);
|
||||
for (const auto &item : _data) {
|
||||
if (isTemporaryDcId(item.first)) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &endpoint : item.second) {
|
||||
++optionsCount;
|
||||
// id + flags + port
|
||||
size += sizeof(qint32) + sizeof(qint32) + sizeof(qint32);
|
||||
size += sizeof(qint32) + endpoint.ip.size();
|
||||
size += sizeof(qint32) + endpoint.secret.size();
|
||||
}
|
||||
}
|
||||
|
||||
// CDN public keys.
|
||||
auto count = 0;
|
||||
for (auto &keysInDc : _cdnPublicKeys) {
|
||||
count += keysInDc.second.size();
|
||||
}
|
||||
struct SerializedPublicKey {
|
||||
DcId dcId;
|
||||
bytes::vector n;
|
||||
bytes::vector e;
|
||||
};
|
||||
std::vector<SerializedPublicKey> publicKeys;
|
||||
publicKeys.reserve(count);
|
||||
size += sizeof(qint32);
|
||||
for (const auto &keysInDc : _cdnPublicKeys) {
|
||||
for (const auto &entry : keysInDc.second) {
|
||||
publicKeys.push_back({
|
||||
keysInDc.first,
|
||||
entry.second.getN(),
|
||||
entry.second.getE()
|
||||
});
|
||||
size += sizeof(qint32)
|
||||
+ Serialize::bytesSize(publicKeys.back().n)
|
||||
+ Serialize::bytesSize(publicKeys.back().e);
|
||||
}
|
||||
}
|
||||
|
||||
auto result = QByteArray();
|
||||
result.reserve(size);
|
||||
{
|
||||
QDataStream stream(&result, QIODevice::WriteOnly);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
stream << qint32(-kVersion);
|
||||
|
||||
// Dc options.
|
||||
stream << qint32(optionsCount);
|
||||
for (const auto &item : _data) {
|
||||
if (isTemporaryDcId(item.first)) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &endpoint : item.second) {
|
||||
stream << qint32(endpoint.id)
|
||||
<< qint32(endpoint.flags)
|
||||
<< qint32(endpoint.port)
|
||||
<< qint32(endpoint.ip.size());
|
||||
stream.writeRawData(endpoint.ip.data(), endpoint.ip.size());
|
||||
stream << qint32(endpoint.secret.size());
|
||||
stream.writeRawData(
|
||||
reinterpret_cast<const char*>(endpoint.secret.data()),
|
||||
endpoint.secret.size());
|
||||
}
|
||||
}
|
||||
|
||||
// CDN public keys.
|
||||
stream << qint32(publicKeys.size());
|
||||
for (auto &key : publicKeys) {
|
||||
stream << qint32(key.dcId)
|
||||
<< Serialize::bytes(key.n)
|
||||
<< Serialize::bytes(key.e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DcOptions::constructFromSerialized(const QByteArray &serialized) {
|
||||
QDataStream stream(serialized);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
auto minusVersion = qint32(0);
|
||||
stream >> minusVersion;
|
||||
const auto version = (minusVersion < 0) ? (-minusVersion) : 0;
|
||||
|
||||
auto count = qint32(0);
|
||||
if (version > 0) {
|
||||
stream >> count;
|
||||
} else {
|
||||
count = minusVersion;
|
||||
}
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("MTP Error: Bad data for DcOptions::constructFromSerialized()"));
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteLocker lock(this);
|
||||
_data.clear();
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
qint32 id = 0, flags = 0, port = 0, ipSize = 0;
|
||||
stream >> id >> flags >> port >> ipSize;
|
||||
|
||||
// https://stackoverflow.com/questions/1076714/max-length-for-client-ip-address
|
||||
constexpr auto kMaxIpSize = 45;
|
||||
if (ipSize <= 0 || ipSize > kMaxIpSize) {
|
||||
LOG(("MTP Error: Bad data inside DcOptions::constructFromSerialized()"));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto ip = std::string(ipSize, ' ');
|
||||
stream.readRawData(ip.data(), ipSize);
|
||||
|
||||
constexpr auto kMaxSecretSize = 32;
|
||||
auto secret = bytes::vector();
|
||||
if (version > 0) {
|
||||
auto secretSize = qint32(0);
|
||||
stream >> secretSize;
|
||||
if (secretSize < 0 || secretSize > kMaxSecretSize) {
|
||||
LOG(("MTP Error: Bad data inside DcOptions::constructFromSerialized()"));
|
||||
return false;
|
||||
} else if (secretSize > 0) {
|
||||
secret.resize(secretSize);
|
||||
stream.readRawData(
|
||||
reinterpret_cast<char*>(secret.data()),
|
||||
secretSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("MTP Error: Bad data inside DcOptions::constructFromSerialized()"));
|
||||
return false;
|
||||
}
|
||||
|
||||
applyOneGuarded(
|
||||
DcId(id),
|
||||
Flags::from_raw(flags),
|
||||
ip,
|
||||
port,
|
||||
secret);
|
||||
}
|
||||
|
||||
// Read CDN config
|
||||
if (!stream.atEnd() && version > 1) {
|
||||
auto count = qint32(0);
|
||||
stream >> count;
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("MTP Error: Bad data for CDN config in DcOptions::constructFromSerialized()"));
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto i = 0; i != count; ++i) {
|
||||
qint32 dcId = 0;
|
||||
bytes::vector n, e;
|
||||
stream >> dcId >> Serialize::bytes(n) >> Serialize::bytes(e);
|
||||
if (stream.status() != QDataStream::Ok) {
|
||||
LOG(("MTP Error: Bad data for CDN config inside DcOptions::constructFromSerialized()"));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto key = RSAPublicKey(n, e);
|
||||
if (key.valid()) {
|
||||
_cdnPublicKeys[dcId].emplace(key.fingerprint(), std::move(key));
|
||||
} else {
|
||||
LOG(("MTP Error: Could not read valid CDN public key."));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
rpl::producer<DcId> DcOptions::changed() const {
|
||||
return _changed.events();
|
||||
}
|
||||
|
||||
rpl::producer<> DcOptions::cdnConfigChanged() const {
|
||||
return _cdnConfigChanged.events();
|
||||
}
|
||||
|
||||
std::vector<DcId> DcOptions::configEnumDcIds() const {
|
||||
auto result = std::vector<DcId>();
|
||||
{
|
||||
ReadLocker lock(this);
|
||||
result.reserve(_data.size());
|
||||
for (auto &item : _data) {
|
||||
const auto dcId = item.first;
|
||||
Assert(!item.second.empty());
|
||||
if (!isCdnDc(item.second.front().flags)
|
||||
&& !isTemporaryDcId(dcId)) {
|
||||
result.push_back(dcId);
|
||||
}
|
||||
}
|
||||
}
|
||||
ranges::sort(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
DcType DcOptions::dcType(ShiftedDcId shiftedDcId) const {
|
||||
if (isTemporaryDcId(shiftedDcId)) {
|
||||
return DcType::Temporary;
|
||||
}
|
||||
ReadLocker lock(this);
|
||||
if (_cdnDcIds.find(BareDcId(shiftedDcId)) != _cdnDcIds.cend()) {
|
||||
return DcType::Cdn;
|
||||
}
|
||||
const auto dcId = BareDcId(shiftedDcId);
|
||||
if (isMediaClusterDcId(shiftedDcId) && hasMediaOnlyOptionsFor(dcId)) {
|
||||
return DcType::MediaCluster;
|
||||
}
|
||||
return DcType::Regular;
|
||||
}
|
||||
|
||||
void DcOptions::setCDNConfig(const MTPDcdnConfig &config) {
|
||||
WriteLocker lock(this);
|
||||
_cdnPublicKeys.clear();
|
||||
for (const auto &key : config.vpublic_keys().v) {
|
||||
key.match([&](const MTPDcdnPublicKey &data) {
|
||||
const auto keyBytes = bytes::make_span(data.vpublic_key().v);
|
||||
auto key = RSAPublicKey(keyBytes);
|
||||
if (key.valid()) {
|
||||
_cdnPublicKeys[data.vdc_id().v].emplace(
|
||||
key.fingerprint(),
|
||||
std::move(key));
|
||||
} else {
|
||||
LOG(("MTP Error: could not read this public RSA key:"));
|
||||
LOG((qs(data.vpublic_key())));
|
||||
}
|
||||
});
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
_cdnConfigChanged.fire({});
|
||||
}
|
||||
|
||||
bool DcOptions::hasCDNKeysForDc(DcId dcId) const {
|
||||
ReadLocker lock(this);
|
||||
return _cdnPublicKeys.find(dcId) != _cdnPublicKeys.cend();
|
||||
}
|
||||
|
||||
RSAPublicKey DcOptions::getDcRSAKey(
|
||||
DcId dcId,
|
||||
const QVector<MTPlong> &fingerprints) const {
|
||||
const auto findKey = [&](
|
||||
const base::flat_map<uint64, RSAPublicKey> &keys) {
|
||||
for (const auto &fingerprint : fingerprints) {
|
||||
const auto it = keys.find(static_cast<uint64>(fingerprint.v));
|
||||
if (it != keys.cend()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
return RSAPublicKey();
|
||||
};
|
||||
{
|
||||
ReadLocker lock(this);
|
||||
const auto it = _cdnPublicKeys.find(dcId);
|
||||
if (it != _cdnPublicKeys.cend()) {
|
||||
return findKey(it->second);
|
||||
}
|
||||
}
|
||||
return findKey(_publicKeys);
|
||||
}
|
||||
|
||||
auto DcOptions::lookup(
|
||||
DcId dcId,
|
||||
DcType type,
|
||||
bool throughProxy) const -> Variants {
|
||||
using Flag = Flag;
|
||||
auto result = Variants();
|
||||
|
||||
ReadLocker lock(this);
|
||||
const auto i = _data.find(dcId);
|
||||
if (i == end(_data)) {
|
||||
return result;
|
||||
}
|
||||
for (const auto &endpoint : i->second) {
|
||||
const auto flags = endpoint.flags;
|
||||
if (type == DcType::Cdn && !(flags & Flag::f_cdn)) {
|
||||
continue;
|
||||
} else if (type != DcType::MediaCluster
|
||||
&& (flags & Flag::f_media_only)) {
|
||||
continue;
|
||||
} else if (!ValidateSecret(endpoint.secret)) {
|
||||
continue;
|
||||
}
|
||||
const auto address = (flags & Flag::f_ipv6)
|
||||
? Variants::IPv6
|
||||
: Variants::IPv4;
|
||||
result.data[address][Variants::Tcp].push_back(endpoint);
|
||||
if (!(flags & (Flag::f_tcpo_only | Flag::f_secret))) {
|
||||
result.data[address][Variants::Http].push_back(endpoint);
|
||||
}
|
||||
}
|
||||
if (type == DcType::MediaCluster) {
|
||||
FilterIfHasWithFlag(result, Flag::f_media_only);
|
||||
}
|
||||
if (throughProxy) {
|
||||
FilterIfHasWithFlag(result, Flag::f_static);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool DcOptions::hasMediaOnlyOptionsFor(DcId dcId) const {
|
||||
ReadLocker lock(this);
|
||||
const auto i = _data.find(dcId);
|
||||
if (i == end(_data)) {
|
||||
return false;
|
||||
}
|
||||
for (const auto &endpoint : i->second) {
|
||||
const auto flags = endpoint.flags;
|
||||
if (flags & Flag::f_media_only) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DcOptions::FilterIfHasWithFlag(Variants &variants, Flag flag) {
|
||||
const auto is = [&](const Endpoint &endpoint) {
|
||||
return (endpoint.flags & flag) != 0;
|
||||
};
|
||||
const auto has = [&](const std::vector<Endpoint> &list) {
|
||||
return ranges::any_of(list, is);
|
||||
};
|
||||
for (auto &byAddress : variants.data) {
|
||||
for (auto &list : byAddress) {
|
||||
if (has(list)) {
|
||||
list = ranges::views::all(
|
||||
list
|
||||
) | ranges::views::filter(
|
||||
is
|
||||
) | ranges::to_vector;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DcOptions::computeCdnDcIds() {
|
||||
_cdnDcIds.clear();
|
||||
for (auto &item : _data) {
|
||||
Assert(!item.second.empty());
|
||||
if (item.second.front().flags & Flag::f_cdn) {
|
||||
_cdnDcIds.insert(BareDcId(item.first));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool DcOptions::loadFromFile(const QString &path) {
|
||||
QVector<MTPDcOption> options;
|
||||
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
LOG(("MTP Error: could not read '%1'").arg(path));
|
||||
return false;
|
||||
}
|
||||
QTextStream stream(&f);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
stream.setCodec("UTF-8");
|
||||
#endif // Qt < 6.0.0
|
||||
while (!stream.atEnd()) {
|
||||
static const auto RegExp = QRegularExpression(R"(\s)");
|
||||
auto line = stream.readLine();
|
||||
auto components = line.split(RegExp, Qt::SkipEmptyParts);
|
||||
if (components.isEmpty() || components[0].startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto error = [line] {
|
||||
LOG(("MTP Error: in .tdesktop-endpoints expected 'dcId host port [tcpo_only] [media_only]', got '%1'").arg(line));
|
||||
return false;
|
||||
};
|
||||
if (components.size() < 3) {
|
||||
return error();
|
||||
}
|
||||
auto dcId = components[0].toInt();
|
||||
auto ip = components[1];
|
||||
auto port = components[2].toInt();
|
||||
auto host = QHostAddress();
|
||||
if (dcId <= 0 || dcId >= kDcShift || !host.setAddress(ip) || port <= 0) {
|
||||
return error();
|
||||
}
|
||||
auto flags = Flags(0);
|
||||
if (host.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
flags |= Flag::f_ipv6;
|
||||
}
|
||||
for (auto &option : components.mid(3)) {
|
||||
if (option.startsWith('#')) {
|
||||
break;
|
||||
} else if (option == u"tcpo_only"_q) {
|
||||
flags |= Flag::f_tcpo_only;
|
||||
} else if (option == u"media_only"_q) {
|
||||
flags |= Flag::f_media_only;
|
||||
} else {
|
||||
return error();
|
||||
}
|
||||
}
|
||||
options.push_back(MTP_dcOption(
|
||||
MTP_flags(flags),
|
||||
MTP_int(dcId),
|
||||
MTP_string(ip),
|
||||
MTP_int(port),
|
||||
MTPbytes()));
|
||||
}
|
||||
if (options.isEmpty()) {
|
||||
LOG(("MTP Error: in .tdesktop-endpoints expected at least one endpoint being provided."));
|
||||
return false;
|
||||
}
|
||||
|
||||
_immutable = false;
|
||||
setFromList(MTP_vector<MTPDcOption>(options));
|
||||
_immutable = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DcOptions::writeToFile(const QString &path) const {
|
||||
QFile f(path);
|
||||
if (!f.open(QIODevice::WriteOnly)) {
|
||||
return false;
|
||||
}
|
||||
QTextStream stream(&f);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
stream.setCodec("UTF-8");
|
||||
#endif // Qt < 6.0.0
|
||||
|
||||
ReadLocker lock(this);
|
||||
for (const auto &item : _data) {
|
||||
for (const auto &option : item.second) {
|
||||
stream
|
||||
<< option.id
|
||||
<< ' '
|
||||
<< QString::fromStdString(option.ip)
|
||||
<< ' ' << option.port;
|
||||
if (option.flags & Flag::f_tcpo_only) {
|
||||
stream << " tcpo_only";
|
||||
}
|
||||
if (option.flags & Flag::f_media_only) {
|
||||
stream << " media_only";
|
||||
}
|
||||
stream << '\n';
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
167
Telegram/SourceFiles/mtproto/mtproto_dc_options.h
Normal file
167
Telegram/SourceFiles/mtproto/mtproto_dc_options.h
Normal file
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/bytes.h"
|
||||
|
||||
#include <QtCore/QReadWriteLock>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
class RSAPublicKey;
|
||||
} // namespace details
|
||||
|
||||
enum class DcType {
|
||||
Regular,
|
||||
Temporary,
|
||||
MediaCluster,
|
||||
Cdn,
|
||||
};
|
||||
|
||||
enum class Environment : uchar {
|
||||
Production,
|
||||
Test,
|
||||
};
|
||||
|
||||
class DcOptions {
|
||||
public:
|
||||
using Flag = MTPDdcOption::Flag;
|
||||
using Flags = MTPDdcOption::Flags;
|
||||
struct Endpoint {
|
||||
Endpoint(
|
||||
DcId id,
|
||||
Flags flags,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
const bytes::vector &secret)
|
||||
: id(id)
|
||||
, flags(flags)
|
||||
, ip(ip)
|
||||
, port(port)
|
||||
, secret(secret) {
|
||||
}
|
||||
|
||||
DcId id;
|
||||
Flags flags;
|
||||
std::string ip;
|
||||
int port;
|
||||
bytes::vector secret;
|
||||
|
||||
};
|
||||
|
||||
explicit DcOptions(Environment environment);
|
||||
DcOptions(const DcOptions &other);
|
||||
~DcOptions();
|
||||
|
||||
[[nodiscard]] static bool ValidateSecret(bytes::const_span secret);
|
||||
|
||||
[[nodiscard]] Environment environment() const;
|
||||
[[nodiscard]] bool isTestMode() const;
|
||||
|
||||
// construct methods don't notify "changed" subscribers.
|
||||
bool constructFromSerialized(const QByteArray &serialized);
|
||||
void constructFromBuiltIn();
|
||||
void constructAddOne(
|
||||
int id,
|
||||
Flags flags,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
const bytes::vector &secret);
|
||||
QByteArray serialize() const;
|
||||
|
||||
[[nodiscard]] rpl::producer<DcId> changed() const;
|
||||
[[nodiscard]] rpl::producer<> cdnConfigChanged() const;
|
||||
void setFromList(const MTPVector<MTPDcOption> &options);
|
||||
void addFromList(const MTPVector<MTPDcOption> &options);
|
||||
void addFromOther(DcOptions &&options);
|
||||
|
||||
[[nodiscard]] std::vector<DcId> configEnumDcIds() const;
|
||||
|
||||
struct Variants {
|
||||
enum Address {
|
||||
IPv4 = 0,
|
||||
IPv6 = 1,
|
||||
AddressTypeCount = 2,
|
||||
};
|
||||
enum Protocol {
|
||||
Tcp = 0,
|
||||
Http = 1,
|
||||
ProtocolCount = 2,
|
||||
};
|
||||
std::vector<Endpoint> data[AddressTypeCount][ProtocolCount];
|
||||
};
|
||||
[[nodiscard]] Variants lookup(
|
||||
DcId dcId,
|
||||
DcType type,
|
||||
bool throughProxy) const;
|
||||
[[nodiscard]] DcType dcType(ShiftedDcId shiftedDcId) const;
|
||||
|
||||
void setCDNConfig(const MTPDcdnConfig &config);
|
||||
[[nodiscard]] bool hasCDNKeysForDc(DcId dcId) const;
|
||||
[[nodiscard]] details::RSAPublicKey getDcRSAKey(
|
||||
DcId dcId,
|
||||
const QVector<MTPlong> &fingerprints) const;
|
||||
|
||||
// Debug feature for now.
|
||||
bool loadFromFile(const QString &path);
|
||||
bool writeToFile(const QString &path) const;
|
||||
|
||||
private:
|
||||
bool applyOneGuarded(
|
||||
DcId dcId,
|
||||
Flags flags,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
const bytes::vector &secret);
|
||||
static bool ApplyOneOption(
|
||||
base::flat_map<DcId, std::vector<Endpoint>> &data,
|
||||
DcId dcId,
|
||||
Flags flags,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
const bytes::vector &secret);
|
||||
static std::vector<DcId> CountOptionsDifference(
|
||||
const base::flat_map<DcId, std::vector<Endpoint>> &a,
|
||||
const base::flat_map<DcId, std::vector<Endpoint>> &b);
|
||||
static void FilterIfHasWithFlag(Variants &variants, Flag flag);
|
||||
|
||||
[[nodiscard]] bool hasMediaOnlyOptionsFor(DcId dcId) const;
|
||||
|
||||
void processFromList(const QVector<MTPDcOption> &options, bool overwrite);
|
||||
void computeCdnDcIds();
|
||||
|
||||
void readBuiltInPublicKeys();
|
||||
|
||||
class WriteLocker;
|
||||
friend class WriteLocker;
|
||||
|
||||
class ReadLocker;
|
||||
friend class ReadLocker;
|
||||
|
||||
const Environment _environment = Environment();
|
||||
base::flat_map<DcId, std::vector<Endpoint>> _data;
|
||||
base::flat_set<DcId> _cdnDcIds;
|
||||
base::flat_map<uint64, details::RSAPublicKey> _publicKeys;
|
||||
base::flat_map<
|
||||
DcId,
|
||||
base::flat_map<uint64, details::RSAPublicKey>> _cdnPublicKeys;
|
||||
mutable QReadWriteLock _useThroughLockers;
|
||||
|
||||
rpl::event_stream<DcId> _changed;
|
||||
rpl::event_stream<> _cdnConfigChanged;
|
||||
|
||||
// True when we have overriden options from a .tdesktop-endpoints file.
|
||||
bool _immutable = false;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP
|
||||
175
Telegram/SourceFiles/mtproto/mtproto_dh_utils.cpp
Normal file
175
Telegram/SourceFiles/mtproto/mtproto_dh_utils.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
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/mtproto_dh_utils.h"
|
||||
|
||||
#include "base/openssl_help.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxModExpSize = 256;
|
||||
|
||||
bool IsPrimeAndGoodCheck(const openssl::BigNum &prime, int g) {
|
||||
constexpr auto kGoodPrimeBitsCount = 2048;
|
||||
|
||||
if (prime.failed()
|
||||
|| prime.isNegative()
|
||||
|| prime.bitsSize() != kGoodPrimeBitsCount) {
|
||||
LOG(("MTP Error: Bad prime bits count %1, expected %2."
|
||||
).arg(prime.bitsSize()
|
||||
).arg(kGoodPrimeBitsCount));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto context = openssl::Context();
|
||||
if (!prime.isPrime(context)) {
|
||||
LOG(("MTP Error: Bad prime."));
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (g) {
|
||||
case 2: {
|
||||
const auto mod8 = prime.countModWord(8);
|
||||
if (mod8 != 7) {
|
||||
LOG(("BigNum PT Error: bad g value: %1, mod8: %2").arg(g).arg(mod8));
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case 3: {
|
||||
const auto mod3 = prime.countModWord(3);
|
||||
if (mod3 != 2) {
|
||||
LOG(("BigNum PT Error: bad g value: %1, mod3: %2").arg(g).arg(mod3));
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case 4: break;
|
||||
case 5: {
|
||||
const auto mod5 = prime.countModWord(5);
|
||||
if (mod5 != 1 && mod5 != 4) {
|
||||
LOG(("BigNum PT Error: bad g value: %1, mod5: %2").arg(g).arg(mod5));
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case 6: {
|
||||
const auto mod24 = prime.countModWord(24);
|
||||
if (mod24 != 19 && mod24 != 23) {
|
||||
LOG(("BigNum PT Error: bad g value: %1, mod24: %2").arg(g).arg(mod24));
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
case 7: {
|
||||
const auto mod7 = prime.countModWord(7);
|
||||
if (mod7 != 3 && mod7 != 5 && mod7 != 6) {
|
||||
LOG(("BigNum PT Error: bad g value: %1, mod7: %2").arg(g).arg(mod7));
|
||||
return false;
|
||||
}
|
||||
} break;
|
||||
default: {
|
||||
LOG(("BigNum PT Error: bad g value: %1").arg(g));
|
||||
return false;
|
||||
} break;
|
||||
}
|
||||
|
||||
if (!openssl::BigNum(prime).subWord(1).divWord(2).isPrime(context)) {
|
||||
LOG(("MTP Error: Bad (prime - 1) / 2."));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsGoodModExpFirst(
|
||||
const openssl::BigNum &modexp,
|
||||
const openssl::BigNum &prime) {
|
||||
const auto diff = openssl::BigNum::Sub(prime, modexp);
|
||||
if (modexp.failed() || prime.failed() || diff.failed()) {
|
||||
return false;
|
||||
}
|
||||
constexpr auto kMinDiffBitsCount = 2048 - 64;
|
||||
if (diff.isNegative()
|
||||
|| diff.bitsSize() < kMinDiffBitsCount
|
||||
|| modexp.bitsSize() < kMinDiffBitsCount
|
||||
|| modexp.bytesSize() > kMaxModExpSize) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPrimeAndGood(bytes::const_span primeBytes, int g) {
|
||||
static constexpr unsigned char GoodPrime[] = {
|
||||
0xC7, 0x1C, 0xAE, 0xB9, 0xC6, 0xB1, 0xC9, 0x04, 0x8E, 0x6C, 0x52, 0x2F, 0x70, 0xF1, 0x3F, 0x73,
|
||||
0x98, 0x0D, 0x40, 0x23, 0x8E, 0x3E, 0x21, 0xC1, 0x49, 0x34, 0xD0, 0x37, 0x56, 0x3D, 0x93, 0x0F,
|
||||
0x48, 0x19, 0x8A, 0x0A, 0xA7, 0xC1, 0x40, 0x58, 0x22, 0x94, 0x93, 0xD2, 0x25, 0x30, 0xF4, 0xDB,
|
||||
0xFA, 0x33, 0x6F, 0x6E, 0x0A, 0xC9, 0x25, 0x13, 0x95, 0x43, 0xAE, 0xD4, 0x4C, 0xCE, 0x7C, 0x37,
|
||||
0x20, 0xFD, 0x51, 0xF6, 0x94, 0x58, 0x70, 0x5A, 0xC6, 0x8C, 0xD4, 0xFE, 0x6B, 0x6B, 0x13, 0xAB,
|
||||
0xDC, 0x97, 0x46, 0x51, 0x29, 0x69, 0x32, 0x84, 0x54, 0xF1, 0x8F, 0xAF, 0x8C, 0x59, 0x5F, 0x64,
|
||||
0x24, 0x77, 0xFE, 0x96, 0xBB, 0x2A, 0x94, 0x1D, 0x5B, 0xCD, 0x1D, 0x4A, 0xC8, 0xCC, 0x49, 0x88,
|
||||
0x07, 0x08, 0xFA, 0x9B, 0x37, 0x8E, 0x3C, 0x4F, 0x3A, 0x90, 0x60, 0xBE, 0xE6, 0x7C, 0xF9, 0xA4,
|
||||
0xA4, 0xA6, 0x95, 0x81, 0x10, 0x51, 0x90, 0x7E, 0x16, 0x27, 0x53, 0xB5, 0x6B, 0x0F, 0x6B, 0x41,
|
||||
0x0D, 0xBA, 0x74, 0xD8, 0xA8, 0x4B, 0x2A, 0x14, 0xB3, 0x14, 0x4E, 0x0E, 0xF1, 0x28, 0x47, 0x54,
|
||||
0xFD, 0x17, 0xED, 0x95, 0x0D, 0x59, 0x65, 0xB4, 0xB9, 0xDD, 0x46, 0x58, 0x2D, 0xB1, 0x17, 0x8D,
|
||||
0x16, 0x9C, 0x6B, 0xC4, 0x65, 0xB0, 0xD6, 0xFF, 0x9C, 0xA3, 0x92, 0x8F, 0xEF, 0x5B, 0x9A, 0xE4,
|
||||
0xE4, 0x18, 0xFC, 0x15, 0xE8, 0x3E, 0xBE, 0xA0, 0xF8, 0x7F, 0xA9, 0xFF, 0x5E, 0xED, 0x70, 0x05,
|
||||
0x0D, 0xED, 0x28, 0x49, 0xF4, 0x7B, 0xF9, 0x59, 0xD9, 0x56, 0x85, 0x0C, 0xE9, 0x29, 0x85, 0x1F,
|
||||
0x0D, 0x81, 0x15, 0xF6, 0x35, 0xB1, 0x05, 0xEE, 0x2E, 0x4E, 0x15, 0xD0, 0x4B, 0x24, 0x54, 0xBF,
|
||||
0x6F, 0x4F, 0xAD, 0xF0, 0x34, 0xB1, 0x04, 0x03, 0x11, 0x9C, 0xD8, 0xE3, 0xB9, 0x2F, 0xCC, 0x5B };
|
||||
|
||||
if (!bytes::compare(bytes::make_span(GoodPrime), primeBytes)) {
|
||||
if (g == 3 || g == 4 || g == 5 || g == 7) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return IsPrimeAndGoodCheck(openssl::BigNum(primeBytes), g);
|
||||
}
|
||||
|
||||
ModExpFirst CreateModExp(
|
||||
int g,
|
||||
bytes::const_span primeBytes,
|
||||
bytes::const_span randomSeed) {
|
||||
Expects(randomSeed.size() == ModExpFirst::kRandomPowerSize);
|
||||
|
||||
using namespace openssl;
|
||||
|
||||
BigNum prime(primeBytes);
|
||||
auto result = ModExpFirst();
|
||||
result.randomPower.resize(ModExpFirst::kRandomPowerSize);
|
||||
while (true) {
|
||||
bytes::set_random(result.randomPower);
|
||||
for (auto i = 0; i != ModExpFirst::kRandomPowerSize; ++i) {
|
||||
result.randomPower[i] ^= randomSeed[i];
|
||||
}
|
||||
const auto modexp = BigNum::ModExp(
|
||||
BigNum(g),
|
||||
BigNum(result.randomPower),
|
||||
prime);
|
||||
if (IsGoodModExpFirst(modexp, prime)) {
|
||||
result.modexp = modexp.getBytes();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bytes::vector CreateAuthKey(
|
||||
bytes::const_span firstBytes,
|
||||
bytes::const_span randomBytes,
|
||||
bytes::const_span primeBytes) {
|
||||
using openssl::BigNum;
|
||||
|
||||
const auto first = BigNum(firstBytes);
|
||||
const auto prime = BigNum(primeBytes);
|
||||
if (!IsGoodModExpFirst(first, prime)) {
|
||||
LOG(("AuthKey Error: Bad first prime in CreateAuthKey()."));
|
||||
return {};
|
||||
}
|
||||
return BigNum::ModExp(first, BigNum(randomBytes), prime).getBytes();
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
38
Telegram/SourceFiles/mtproto/mtproto_dh_utils.h
Normal file
38
Telegram/SourceFiles/mtproto/mtproto_dh_utils.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/bytes.h"
|
||||
|
||||
namespace openssl {
|
||||
class BigNum;
|
||||
} // namespace openssl
|
||||
|
||||
namespace MTP {
|
||||
|
||||
struct ModExpFirst {
|
||||
static constexpr auto kRandomPowerSize = 256;
|
||||
|
||||
bytes::vector modexp;
|
||||
bytes::vector randomPower;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool IsPrimeAndGood(bytes::const_span primeBytes, int g);
|
||||
[[nodiscard]] bool IsGoodModExpFirst(
|
||||
const openssl::BigNum &modexp,
|
||||
const openssl::BigNum &prime);
|
||||
[[nodiscard]] ModExpFirst CreateModExp(
|
||||
int g,
|
||||
bytes::const_span primeBytes,
|
||||
bytes::const_span randomSeed);
|
||||
[[nodiscard]] bytes::vector CreateAuthKey(
|
||||
bytes::const_span firstBytes,
|
||||
bytes::const_span randomBytes,
|
||||
bytes::const_span primeBytes);
|
||||
|
||||
} // namespace MTP
|
||||
23
Telegram/SourceFiles/mtproto/mtproto_pch.h
Normal file
23
Telegram/SourceFiles/mtproto/mtproto_pch.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
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 <QtCore/QObject>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtNetwork/QNetworkProxy>
|
||||
#include <QtNetwork/QTcpSocket>
|
||||
|
||||
#include <range/v3/all.hpp>
|
||||
|
||||
#include <rpl/rpl.h>
|
||||
#include <crl/crl.h>
|
||||
|
||||
#include "base/bytes.h"
|
||||
#include "base/flat_map.h"
|
||||
#include "base/flat_set.h"
|
||||
|
||||
#include "logs.h"
|
||||
#include "scheme.h"
|
||||
248
Telegram/SourceFiles/mtproto/mtproto_proxy_data.cpp
Normal file
248
Telegram/SourceFiles/mtproto/mtproto_proxy_data.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
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/mtproto_proxy_data.h"
|
||||
|
||||
#include "base/qthelp_url.h"
|
||||
#include "base/qt/qt_string_view.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool IsHexMtprotoPassword(const QString &password) {
|
||||
const auto size = password.size();
|
||||
if (size < 32 || size % 2 == 1) {
|
||||
return false;
|
||||
}
|
||||
const auto bad = [](QChar ch) {
|
||||
const auto code = ch.unicode();
|
||||
return (code < 'a' || code > 'f')
|
||||
&& (code < 'A' || code > 'F')
|
||||
&& (code < '0' || code > '9');
|
||||
};
|
||||
const auto i = std::find_if(password.begin(), password.end(), bad);
|
||||
return (i == password.end());
|
||||
}
|
||||
|
||||
[[nodiscard]] ProxyData::Status HexMtprotoPasswordStatus(
|
||||
const QString &password) {
|
||||
const auto size = password.size() / 2;
|
||||
const auto type1 = password[0].toLower();
|
||||
const auto type2 = password[1].toLower();
|
||||
const auto valid = (size == 16)
|
||||
|| (size == 17 && (type1 == 'd') && (type2 == 'd'))
|
||||
|| (size >= 21 && (type1 == 'e') && (type2 == 'e'));
|
||||
if (valid) {
|
||||
return ProxyData::Status::Valid;
|
||||
} else if (size < 16) {
|
||||
return ProxyData::Status::Invalid;
|
||||
}
|
||||
return ProxyData::Status::Unsupported;
|
||||
}
|
||||
|
||||
[[nodiscard]] bytes::vector SecretFromHexMtprotoPassword(
|
||||
const QString &password) {
|
||||
Expects(password.size() % 2 == 0);
|
||||
|
||||
const auto size = password.size() / 2;
|
||||
const auto fromHex = [](QChar ch) -> int {
|
||||
const auto code = int(ch.unicode());
|
||||
if (code >= '0' && code <= '9') {
|
||||
return (code - '0');
|
||||
} else if (code >= 'A' && code <= 'F') {
|
||||
return 10 + (code - 'A');
|
||||
} else if (ch >= 'a' && ch <= 'f') {
|
||||
return 10 + (code - 'a');
|
||||
}
|
||||
Unexpected("Code in ProxyData fromHex.");
|
||||
};
|
||||
auto result = bytes::vector(size);
|
||||
for (auto i = 0; i != size; ++i) {
|
||||
const auto high = fromHex(password[2 * i]);
|
||||
const auto low = fromHex(password[2 * i + 1]);
|
||||
if (high < 0 || low < 0) {
|
||||
return {};
|
||||
}
|
||||
result[i] = static_cast<bytes::type>(high * 16 + low);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] QStringView Base64UrlInner(const QString &password) {
|
||||
Expects(password.size() > 2);
|
||||
|
||||
// Skip one or two '=' at the end of the string.
|
||||
return base::StringViewMid(password, 0, [&] {
|
||||
auto result = password.size();
|
||||
for (auto i = 0; i != 2; ++i) {
|
||||
const auto prev = result - 1;
|
||||
if (password[prev] != '=') {
|
||||
break;
|
||||
}
|
||||
result = prev;
|
||||
}
|
||||
return result;
|
||||
}());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IsBase64UrlMtprotoPassword(const QString &password) {
|
||||
const auto size = password.size();
|
||||
if (size < 22 || size % 4 == 1) {
|
||||
return false;
|
||||
}
|
||||
const auto bad = [](QChar ch) {
|
||||
const auto code = ch.unicode();
|
||||
return (code < 'a' || code > 'z')
|
||||
&& (code < 'A' || code > 'Z')
|
||||
&& (code < '0' || code > '9')
|
||||
&& (code != '_')
|
||||
&& (code != '-');
|
||||
};
|
||||
const auto inner = Base64UrlInner(password);
|
||||
const auto begin = inner.data();
|
||||
const auto end = begin + inner.size();
|
||||
return (std::find_if(begin, end, bad) == end);
|
||||
}
|
||||
|
||||
[[nodiscard]] ProxyData::Status Base64UrlMtprotoPasswordStatus(
|
||||
const QString &password) {
|
||||
// IncorrectSecret
|
||||
const auto inner = Base64UrlInner(password);
|
||||
const auto size = (inner.size() * 3) / 4;
|
||||
const auto valid = (size == 16)
|
||||
|| (size == 17
|
||||
&& (password[0] == '3')
|
||||
&& ((password[1] >= 'Q' && password[1] <= 'Z')
|
||||
|| (password[1] >= 'a' && password[1] <= 'f')))
|
||||
|| (size >= 21
|
||||
&& (password[0] == '7')
|
||||
&& (password[1] >= 'g')
|
||||
&& (password[1] <= 'v'));
|
||||
const auto incorrect = (size >= 21
|
||||
&& password[0].toLower() == 'e'
|
||||
&& password[1].toLower() == 'e');
|
||||
if (size < 16) {
|
||||
return ProxyData::Status::Invalid;
|
||||
} else if (valid) {
|
||||
return ProxyData::Status::Valid;
|
||||
} else if (incorrect) {
|
||||
return ProxyData::Status::IncorrectSecret;
|
||||
}
|
||||
return ProxyData::Status::Unsupported;
|
||||
}
|
||||
|
||||
[[nodiscard]] bytes::vector SecretFromBase64UrlMtprotoPassword(
|
||||
const QString &password) {
|
||||
const auto result = QByteArray::fromBase64(
|
||||
password.toLatin1(),
|
||||
QByteArray::Base64UrlEncoding);
|
||||
return bytes::make_vector(bytes::make_span(result));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ProxyData::valid() const {
|
||||
return status() == Status::Valid;
|
||||
}
|
||||
|
||||
ProxyData::Status ProxyData::status() const {
|
||||
if (type == Type::None || host.isEmpty() || !port) {
|
||||
return Status::Invalid;
|
||||
} else if (type == Type::Mtproto) {
|
||||
return MtprotoPasswordStatus(password);
|
||||
}
|
||||
return Status::Valid;
|
||||
}
|
||||
|
||||
bool ProxyData::supportsCalls() const {
|
||||
return false;// (type == Type::Socks5);
|
||||
}
|
||||
|
||||
bool ProxyData::tryCustomResolve() const {
|
||||
static const auto RegExp = QRegularExpression(
|
||||
QStringLiteral("^\\d+\\.\\d+\\.\\d+\\.\\d+$")
|
||||
);
|
||||
return (type == Type::Socks5 || type == Type::Mtproto)
|
||||
&& !qthelp::is_ipv6(host)
|
||||
&& !RegExp.match(host).hasMatch();
|
||||
}
|
||||
|
||||
bytes::vector ProxyData::secretFromMtprotoPassword() const {
|
||||
Expects(type == Type::Mtproto);
|
||||
|
||||
if (IsHexMtprotoPassword(password)) {
|
||||
return SecretFromHexMtprotoPassword(password);
|
||||
} else if (IsBase64UrlMtprotoPassword(password)) {
|
||||
return SecretFromBase64UrlMtprotoPassword(password);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
ProxyData::operator bool() const {
|
||||
return valid();
|
||||
}
|
||||
|
||||
bool ProxyData::operator==(const ProxyData &other) const {
|
||||
if (!valid()) {
|
||||
return !other.valid();
|
||||
}
|
||||
return (type == other.type)
|
||||
&& (host == other.host)
|
||||
&& (port == other.port)
|
||||
&& (user == other.user)
|
||||
&& (password == other.password);
|
||||
}
|
||||
|
||||
bool ProxyData::operator!=(const ProxyData &other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
bool ProxyData::ValidMtprotoPassword(const QString &password) {
|
||||
return MtprotoPasswordStatus(password) == Status::Valid;
|
||||
}
|
||||
|
||||
ProxyData::Status ProxyData::MtprotoPasswordStatus(const QString &password) {
|
||||
if (IsHexMtprotoPassword(password)) {
|
||||
return HexMtprotoPasswordStatus(password);
|
||||
} else if (IsBase64UrlMtprotoPassword(password)) {
|
||||
return Base64UrlMtprotoPasswordStatus(password);
|
||||
}
|
||||
return Status::Invalid;
|
||||
}
|
||||
|
||||
ProxyData ToDirectIpProxy(const ProxyData &proxy, int ipIndex) {
|
||||
if (!proxy.tryCustomResolve()
|
||||
|| ipIndex < 0
|
||||
|| ipIndex >= proxy.resolvedIPs.size()) {
|
||||
return proxy;
|
||||
}
|
||||
return {
|
||||
proxy.type,
|
||||
proxy.resolvedIPs[ipIndex],
|
||||
proxy.port,
|
||||
proxy.user,
|
||||
proxy.password
|
||||
};
|
||||
}
|
||||
|
||||
QNetworkProxy ToNetworkProxy(const ProxyData &proxy) {
|
||||
if (proxy.type == ProxyData::Type::None) {
|
||||
return QNetworkProxy::DefaultProxy;
|
||||
} else if (proxy.type == ProxyData::Type::Mtproto) {
|
||||
return QNetworkProxy::NoProxy;
|
||||
}
|
||||
return QNetworkProxy(
|
||||
(proxy.type == ProxyData::Type::Socks5
|
||||
? QNetworkProxy::Socks5Proxy
|
||||
: QNetworkProxy::HttpProxy),
|
||||
proxy.host,
|
||||
proxy.port,
|
||||
proxy.user,
|
||||
proxy.password);
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
59
Telegram/SourceFiles/mtproto/mtproto_proxy_data.h
Normal file
59
Telegram/SourceFiles/mtproto/mtproto_proxy_data.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace MTP {
|
||||
|
||||
struct ProxyData {
|
||||
enum class Settings {
|
||||
System,
|
||||
Enabled,
|
||||
Disabled,
|
||||
};
|
||||
enum class Type {
|
||||
None,
|
||||
Socks5,
|
||||
Http,
|
||||
Mtproto,
|
||||
};
|
||||
enum class Status {
|
||||
Valid,
|
||||
Unsupported,
|
||||
IncorrectSecret,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
Type type = Type::None;
|
||||
QString host;
|
||||
uint32 port = 0;
|
||||
QString user, password;
|
||||
|
||||
std::vector<QString> resolvedIPs;
|
||||
crl::time resolvedExpireAt = 0;
|
||||
|
||||
[[nodiscard]] bool valid() const;
|
||||
[[nodiscard]] Status status() const;
|
||||
[[nodiscard]] bool supportsCalls() const;
|
||||
[[nodiscard]] bool tryCustomResolve() const;
|
||||
[[nodiscard]] bytes::vector secretFromMtprotoPassword() const;
|
||||
[[nodiscard]] explicit operator bool() const;
|
||||
[[nodiscard]] bool operator==(const ProxyData &other) const;
|
||||
[[nodiscard]] bool operator!=(const ProxyData &other) const;
|
||||
|
||||
[[nodiscard]] static bool ValidMtprotoPassword(const QString &password);
|
||||
[[nodiscard]] static Status MtprotoPasswordStatus(
|
||||
const QString &password);
|
||||
|
||||
};
|
||||
|
||||
[[nodiscard]] ProxyData ToDirectIpProxy(
|
||||
const ProxyData &proxy,
|
||||
int ipIndex = 0);
|
||||
[[nodiscard]] QNetworkProxy ToNetworkProxy(const ProxyData &proxy);
|
||||
|
||||
} // namespace MTP
|
||||
91
Telegram/SourceFiles/mtproto/mtproto_response.cpp
Normal file
91
Telegram/SourceFiles/mtproto/mtproto_response.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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/mtproto_response.h"
|
||||
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
namespace MTP {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] MTPrpcError ParseError(const mtpBuffer &reply) {
|
||||
auto result = MTPRpcError();
|
||||
auto from = reply.constData();
|
||||
return result.read(from, from + reply.size())
|
||||
? result
|
||||
: Error::MTPLocal("RESPONSE_PARSE_FAILED", "Error parse failed.");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Error::Error(const MTPrpcError &error)
|
||||
: _code(error.c_rpc_error().verror_code().v) {
|
||||
QString text = qs(error.c_rpc_error().verror_message());
|
||||
static const auto Expression = QRegularExpression(
|
||||
"^([A-Z0-9_]+)(: .*)?$",
|
||||
(QRegularExpression::DotMatchesEverythingOption
|
||||
| QRegularExpression::MultilineOption));
|
||||
const auto match = Expression.match(text);
|
||||
if (match.hasMatch()) {
|
||||
_type = match.captured(1);
|
||||
_description = match.captured(2).mid(2);
|
||||
} else if (_code < 0 || _code >= 500) {
|
||||
_type = "INTERNAL_SERVER_ERROR";
|
||||
_description = text;
|
||||
} else {
|
||||
_type = "CLIENT_BAD_RPC_ERROR";
|
||||
_description = "Bad rpc error received, text = '" + text + '\'';
|
||||
}
|
||||
}
|
||||
|
||||
Error::Error(const mtpBuffer &reply) : Error(ParseError(reply)) {
|
||||
}
|
||||
|
||||
int32 Error::code() const {
|
||||
return _code;
|
||||
}
|
||||
|
||||
const QString &Error::type() const {
|
||||
return _type;
|
||||
}
|
||||
|
||||
const QString &Error::description() const {
|
||||
return _description;
|
||||
}
|
||||
|
||||
MTPrpcError Error::MTPLocal(
|
||||
const QString &type,
|
||||
const QString &description) {
|
||||
return MTP_rpc_error(
|
||||
MTP_int(0),
|
||||
MTP_bytes(
|
||||
("CLIENT_"
|
||||
+ type
|
||||
+ (description.length()
|
||||
? (": " + description)
|
||||
: QString())).toUtf8()));
|
||||
}
|
||||
|
||||
Error Error::Local(
|
||||
const QString &type,
|
||||
const QString &description) {
|
||||
return Error(MTPLocal(type, description));
|
||||
}
|
||||
|
||||
QDebug operator<<(QDebug debug, const Error &error) {
|
||||
return debug.nospace()
|
||||
<< "MTP::Error("
|
||||
<< error.code()
|
||||
<< ", "
|
||||
<< error.type()
|
||||
<< ", "
|
||||
<< error.description()
|
||||
<< ")";
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
76
Telegram/SourceFiles/mtproto/mtproto_response.h
Normal file
76
Telegram/SourceFiles/mtproto/mtproto_response.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/flat_set.h"
|
||||
|
||||
class QDebug;
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class Error {
|
||||
public:
|
||||
explicit Error(const MTPrpcError &error);
|
||||
explicit Error(const mtpBuffer &reply);
|
||||
|
||||
enum {
|
||||
NoError,
|
||||
TimeoutError
|
||||
};
|
||||
|
||||
[[nodiscard]] int32 code() const;
|
||||
[[nodiscard]] const QString &type() const;
|
||||
[[nodiscard]] const QString &description() const;
|
||||
|
||||
[[nodiscard]] static Error Local(
|
||||
const QString &type,
|
||||
const QString &description);
|
||||
[[nodiscard]] static MTPrpcError MTPLocal(
|
||||
const QString &type,
|
||||
const QString &description);
|
||||
|
||||
private:
|
||||
int32 _code = 0;
|
||||
QString _type, _description;
|
||||
|
||||
};
|
||||
|
||||
inline bool IsFloodError(const QString &type) {
|
||||
return type.startsWith(u"FLOOD_WAIT_"_q)
|
||||
|| type.startsWith(u"FLOOD_PREMIUM_WAIT_"_q);
|
||||
}
|
||||
|
||||
inline bool IsFloodError(const Error &error) {
|
||||
return IsFloodError(error.type());
|
||||
}
|
||||
|
||||
inline bool IsTemporaryError(const Error &error) {
|
||||
return error.code() < 0 || error.code() >= 500 || IsFloodError(error);
|
||||
}
|
||||
|
||||
inline bool IsDefaultHandledError(const Error &error) {
|
||||
return IsTemporaryError(error);
|
||||
}
|
||||
|
||||
struct Response {
|
||||
mtpBuffer reply;
|
||||
mtpMsgId outerMsgId = 0;
|
||||
mtpRequestId requestId = 0;
|
||||
};
|
||||
|
||||
using DoneHandler = FnMut<bool(const Response&)>;
|
||||
using FailHandler = Fn<bool(const Error&, const Response&)>;
|
||||
|
||||
struct ResponseHandler {
|
||||
DoneHandler done;
|
||||
FailHandler fail;
|
||||
};
|
||||
|
||||
[[nodiscard]] QDebug operator<<(QDebug debug, const Error &error);
|
||||
|
||||
} // namespace MTP
|
||||
2882
Telegram/SourceFiles/mtproto/scheme/api.tl
Normal file
2882
Telegram/SourceFiles/mtproto/scheme/api.tl
Normal file
File diff suppressed because it is too large
Load Diff
128
Telegram/SourceFiles/mtproto/scheme/mtproto.tl
Normal file
128
Telegram/SourceFiles/mtproto/scheme/mtproto.tl
Normal file
@@ -0,0 +1,128 @@
|
||||
// Core types (no need to gen)
|
||||
|
||||
int ? = Int;
|
||||
long ? = Long;
|
||||
double ? = Double;
|
||||
string ? = String;
|
||||
|
||||
vector {t:Type} # [ t ] = Vector t;
|
||||
|
||||
int128 4*[ int ] = Int128;
|
||||
int256 8*[ int ] = Int256;
|
||||
|
||||
///////////////////////////////
|
||||
/// Authorization key creation
|
||||
///////////////////////////////
|
||||
|
||||
resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector<long> = ResPQ;
|
||||
|
||||
p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
|
||||
p_q_inner_data_dc#a9f55f95 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;
|
||||
p_q_inner_data_temp#3c6a84d4 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
|
||||
p_q_inner_data_temp_dc#56fddf88 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;
|
||||
|
||||
bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner;
|
||||
|
||||
server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params;
|
||||
server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;
|
||||
|
||||
server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int = Server_DH_inner_data;
|
||||
|
||||
client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:string = Client_DH_Inner_Data;
|
||||
|
||||
dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer;
|
||||
dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer;
|
||||
dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer;
|
||||
|
||||
destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes;
|
||||
destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes;
|
||||
destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
|
||||
|
||||
---functions---
|
||||
|
||||
req_pq#60469778 nonce:int128 = ResPQ;
|
||||
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
|
||||
|
||||
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
|
||||
|
||||
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:string = Set_client_DH_params_answer;
|
||||
|
||||
destroy_auth_key#d1435160 = DestroyAuthKeyRes;
|
||||
|
||||
///////////////////////////////
|
||||
////////////// System messages
|
||||
///////////////////////////////
|
||||
|
||||
---types---
|
||||
|
||||
msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck;
|
||||
|
||||
bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification;
|
||||
bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification;
|
||||
|
||||
msgs_state_req#da69fb52 msg_ids:Vector<long> = MsgsStateReq;
|
||||
msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
|
||||
msgs_all_info#8cc0d131 msg_ids:Vector<long> info:string = MsgsAllInfo;
|
||||
|
||||
msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
|
||||
msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
|
||||
|
||||
msg_resend_req#7d861a08 msg_ids:Vector<long> = MsgResendReq;
|
||||
|
||||
//rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult; // parsed manually
|
||||
|
||||
rpc_error#2144ca19 error_code:int error_message:string = RpcError;
|
||||
|
||||
rpc_answer_unknown#5e2ad36e = RpcDropAnswer;
|
||||
rpc_answer_dropped_running#cd78e586 = RpcDropAnswer;
|
||||
rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer;
|
||||
|
||||
future_salt#0949d9dc valid_since:int valid_until:int salt:long = FutureSalt;
|
||||
future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = FutureSalts;
|
||||
|
||||
pong#347773c5 msg_id:long ping_id:long = Pong;
|
||||
|
||||
destroy_session_ok#e22045fc session_id:long = DestroySessionRes;
|
||||
destroy_session_none#62d350c9 session_id:long = DestroySessionRes;
|
||||
|
||||
new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession;
|
||||
|
||||
//message msg_id:long seqno:int bytes:int body:Object = Message; // parsed manually
|
||||
//msg_container#73f1f8dc messages:vector<message> = MessageContainer; // parsed manually
|
||||
//msg_copy#e06046b2 orig_message:Message = MessageCopy; // parsed manually, not used - use msg_container
|
||||
//gzip_packed#3072cfa1 packed_data:string = Object; // parsed manually
|
||||
|
||||
http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;
|
||||
|
||||
//ipPort ipv4:int port:int = IpPort;
|
||||
//help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<ipPort> = help.ConfigSimple;
|
||||
|
||||
ipPort#d433ad73 ipv4:int port:int = IpPort;
|
||||
ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
|
||||
accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
|
||||
help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
|
||||
|
||||
tlsClientHello blocks:vector<TlsBlock> = TlsClientHello;
|
||||
|
||||
tlsBlockString data:string = TlsBlock;
|
||||
tlsBlockRandom length:int = TlsBlock;
|
||||
tlsBlockZero length:int = TlsBlock;
|
||||
tlsBlockDomain = TlsBlock;
|
||||
tlsBlockGrease seed:int = TlsBlock;
|
||||
tlsBlockPublicKey = TlsBlock;
|
||||
tlsBlockScope entries:Vector<TlsBlock> = TlsBlock;
|
||||
tlsBlockPermutation entries:Vector<Vector<TlsBlock>> = TlsBlock;
|
||||
tlsBlockM = TlsBlock;
|
||||
tlsBlockE = TlsBlock;
|
||||
tlsBlockPadding = TlsBlock;
|
||||
|
||||
---functions---
|
||||
|
||||
rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;
|
||||
|
||||
get_future_salts#b921bd04 num:int = FutureSalts;
|
||||
|
||||
ping#7abe77ec ping_id:long = Pong;
|
||||
ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
|
||||
|
||||
destroy_session#e7512126 session_id:long = DestroySessionRes;
|
||||
477
Telegram/SourceFiles/mtproto/sender.h
Normal file
477
Telegram/SourceFiles/mtproto/sender.h
Normal file
@@ -0,0 +1,477 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/variant.h"
|
||||
#include "mtproto/mtproto_response.h"
|
||||
#include "mtproto/mtp_instance.h"
|
||||
#include "mtproto/facade.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class Sender {
|
||||
class RequestBuilder {
|
||||
public:
|
||||
RequestBuilder(const RequestBuilder &other) = delete;
|
||||
RequestBuilder &operator=(const RequestBuilder &other) = delete;
|
||||
RequestBuilder &operator=(RequestBuilder &&other) = delete;
|
||||
|
||||
protected:
|
||||
enum class FailSkipPolicy {
|
||||
Simple,
|
||||
HandleFlood,
|
||||
HandleAll,
|
||||
};
|
||||
using FailPlainHandler = Fn<void()>;
|
||||
using FailErrorHandler = Fn<void(const Error&)>;
|
||||
using FailRequestIdHandler = Fn<void(const Error&, mtpRequestId)>;
|
||||
using FailFullHandler = Fn<void(const Error&, const Response&)>;
|
||||
|
||||
template <typename ...Args>
|
||||
static constexpr bool IsCallable
|
||||
= rpl::details::is_callable_plain_v<Args...>;
|
||||
|
||||
template <typename Result, typename Handler>
|
||||
[[nodiscard]] DoneHandler MakeDoneHandler(
|
||||
not_null<Sender*> sender,
|
||||
Handler &&handler) {
|
||||
return [sender, handler = std::forward<Handler>(handler)](
|
||||
const Response &response) mutable {
|
||||
auto onstack = std::move(handler);
|
||||
sender->senderRequestHandled(response.requestId);
|
||||
|
||||
auto result = Result();
|
||||
auto from = response.reply.constData();
|
||||
if (!result.read(from, from + response.reply.size())) {
|
||||
return false;
|
||||
} else if (!onstack) {
|
||||
return true;
|
||||
} else if constexpr (IsCallable<
|
||||
Handler,
|
||||
const Result&,
|
||||
const Response&>) {
|
||||
onstack(result, response);
|
||||
} else if constexpr (IsCallable<
|
||||
Handler,
|
||||
const Result&,
|
||||
mtpRequestId>) {
|
||||
onstack(result, response.requestId);
|
||||
} else if constexpr (IsCallable<
|
||||
Handler,
|
||||
const Result&>) {
|
||||
onstack(result);
|
||||
} else if constexpr (IsCallable<Handler>) {
|
||||
onstack();
|
||||
} else {
|
||||
static_assert(false_t(Handler{}), "Bad done handler.");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
template <typename Handler>
|
||||
[[nodiscard]] FailHandler MakeFailHandler(
|
||||
not_null<Sender*> sender,
|
||||
Handler &&handler,
|
||||
FailSkipPolicy skipPolicy) {
|
||||
return [
|
||||
sender,
|
||||
handler = std::forward<Handler>(handler),
|
||||
skipPolicy
|
||||
](const Error &error, const Response &response) {
|
||||
if (skipPolicy == FailSkipPolicy::Simple) {
|
||||
if (IsDefaultHandledError(error)) {
|
||||
return false;
|
||||
}
|
||||
} else if (skipPolicy == FailSkipPolicy::HandleFlood) {
|
||||
if (IsDefaultHandledError(error) && !IsFloodError(error)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto onstack = handler;
|
||||
sender->senderRequestHandled(response.requestId);
|
||||
|
||||
if (!onstack) {
|
||||
return true;
|
||||
} else if constexpr (IsCallable<
|
||||
Handler,
|
||||
const Error&,
|
||||
const Response&>) {
|
||||
onstack(error, response);
|
||||
} else if constexpr (IsCallable<
|
||||
Handler,
|
||||
const Error&,
|
||||
mtpRequestId>) {
|
||||
onstack(error, response.requestId);
|
||||
} else if constexpr (IsCallable<
|
||||
Handler,
|
||||
const Error&>) {
|
||||
onstack(error);
|
||||
} else if constexpr (IsCallable<Handler>) {
|
||||
onstack();
|
||||
} else {
|
||||
static_assert(false_t(Handler{}), "Bad fail handler.");
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
explicit RequestBuilder(not_null<Sender*> sender) noexcept
|
||||
: _sender(sender) {
|
||||
}
|
||||
RequestBuilder(RequestBuilder &&other) = default;
|
||||
|
||||
void setToDC(ShiftedDcId dcId) noexcept {
|
||||
_dcId = dcId;
|
||||
}
|
||||
void setOverrideRequestId(mtpRequestId id) noexcept {
|
||||
_overrideRequestId = id;
|
||||
}
|
||||
void setCanWait(crl::time ms) noexcept {
|
||||
_canWait = ms;
|
||||
}
|
||||
void setDoneHandler(DoneHandler &&handler) noexcept {
|
||||
_done = std::move(handler);
|
||||
}
|
||||
template <typename Handler>
|
||||
void setFailHandler(Handler &&handler) noexcept {
|
||||
_fail = std::forward<Handler>(handler);
|
||||
}
|
||||
void setFailSkipPolicy(FailSkipPolicy policy) noexcept {
|
||||
_failSkipPolicy = policy;
|
||||
}
|
||||
void setAfter(mtpRequestId requestId) noexcept {
|
||||
_afterRequestId = requestId;
|
||||
}
|
||||
|
||||
[[nodiscard]] ShiftedDcId takeDcId() const noexcept {
|
||||
return _dcId;
|
||||
}
|
||||
[[nodiscard]] crl::time takeCanWait() const noexcept {
|
||||
return _canWait;
|
||||
}
|
||||
[[nodiscard]] DoneHandler takeOnDone() noexcept {
|
||||
return std::move(_done);
|
||||
}
|
||||
[[nodiscard]] FailHandler takeOnFail() {
|
||||
return v::match(_fail, [&](auto &value) {
|
||||
return MakeFailHandler(
|
||||
_sender,
|
||||
std::move(value),
|
||||
_failSkipPolicy);
|
||||
});
|
||||
}
|
||||
[[nodiscard]] mtpRequestId takeAfter() const noexcept {
|
||||
return _afterRequestId;
|
||||
}
|
||||
[[nodiscard]] mtpRequestId takeOverrideRequestId() const noexcept {
|
||||
return _overrideRequestId;
|
||||
}
|
||||
|
||||
[[nodiscard]] not_null<Sender*> sender() const noexcept {
|
||||
return _sender;
|
||||
}
|
||||
void registerRequest(mtpRequestId requestId) {
|
||||
_sender->senderRequestRegister(requestId);
|
||||
}
|
||||
|
||||
private:
|
||||
not_null<Sender*> _sender;
|
||||
ShiftedDcId _dcId = 0;
|
||||
crl::time _canWait = 0;
|
||||
DoneHandler _done;
|
||||
std::variant<
|
||||
FailPlainHandler,
|
||||
FailErrorHandler,
|
||||
FailRequestIdHandler,
|
||||
FailFullHandler> _fail;
|
||||
FailSkipPolicy _failSkipPolicy = FailSkipPolicy::Simple;
|
||||
mtpRequestId _afterRequestId = 0;
|
||||
mtpRequestId _overrideRequestId = 0;
|
||||
|
||||
};
|
||||
|
||||
public:
|
||||
explicit Sender(not_null<Instance*> instance) noexcept
|
||||
: _instance(instance) {
|
||||
}
|
||||
|
||||
[[nodiscard]] Instance &instance() const {
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
template <typename Request>
|
||||
class SpecificRequestBuilder : public RequestBuilder {
|
||||
private:
|
||||
friend class Sender;
|
||||
SpecificRequestBuilder(not_null<Sender*> sender, Request &&request) noexcept
|
||||
: RequestBuilder(sender)
|
||||
, _request(std::move(request)) {
|
||||
}
|
||||
|
||||
public:
|
||||
SpecificRequestBuilder(SpecificRequestBuilder &&other) = default;
|
||||
|
||||
[[nodiscard]] SpecificRequestBuilder &toDC(ShiftedDcId dcId) noexcept {
|
||||
setToDC(dcId);
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &afterDelay(crl::time ms) noexcept {
|
||||
setCanWait(ms);
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &overrideId(mtpRequestId id) noexcept {
|
||||
setOverrideRequestId(id);
|
||||
return *this;
|
||||
}
|
||||
|
||||
using Result = typename Request::ResponseType;
|
||||
[[nodiscard]] SpecificRequestBuilder &done(
|
||||
FnMut<void(
|
||||
const Result &result,
|
||||
mtpRequestId requestId)> callback) {
|
||||
setDoneHandler(
|
||||
MakeDoneHandler<Result>(sender(), std::move(callback)));
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &done(
|
||||
FnMut<void(
|
||||
const Result &result,
|
||||
const Response &response)> callback) {
|
||||
setDoneHandler(
|
||||
MakeDoneHandler<Result>(sender(), std::move(callback)));
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &done(
|
||||
FnMut<void()> callback) {
|
||||
setDoneHandler(
|
||||
MakeDoneHandler<Result>(sender(), std::move(callback)));
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &done(
|
||||
FnMut<void(
|
||||
const typename Request::ResponseType &result)> callback) {
|
||||
setDoneHandler(
|
||||
MakeDoneHandler<Result>(sender(), std::move(callback)));
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] SpecificRequestBuilder &fail(
|
||||
Fn<void(
|
||||
const Error &error,
|
||||
mtpRequestId requestId)> callback) noexcept {
|
||||
setFailHandler(std::move(callback));
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &fail(
|
||||
Fn<void(
|
||||
const Error &error,
|
||||
const Response &response)> callback) noexcept {
|
||||
setFailHandler(std::move(callback));
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &fail(
|
||||
Fn<void()> callback) noexcept {
|
||||
setFailHandler(std::move(callback));
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &fail(
|
||||
Fn<void(const Error &error)> callback) noexcept {
|
||||
setFailHandler(std::move(callback));
|
||||
return *this;
|
||||
}
|
||||
|
||||
[[nodiscard]] SpecificRequestBuilder &handleFloodErrors() noexcept {
|
||||
setFailSkipPolicy(FailSkipPolicy::HandleFlood);
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &handleAllErrors() noexcept {
|
||||
setFailSkipPolicy(FailSkipPolicy::HandleAll);
|
||||
return *this;
|
||||
}
|
||||
[[nodiscard]] SpecificRequestBuilder &afterRequest(mtpRequestId requestId) noexcept {
|
||||
setAfter(requestId);
|
||||
return *this;
|
||||
}
|
||||
|
||||
mtpRequestId send() {
|
||||
const auto id = sender()->_instance->send(
|
||||
_request,
|
||||
takeOnDone(),
|
||||
takeOnFail(),
|
||||
takeDcId(),
|
||||
takeCanWait(),
|
||||
takeAfter(),
|
||||
takeOverrideRequestId());
|
||||
registerRequest(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
private:
|
||||
Request _request;
|
||||
|
||||
};
|
||||
|
||||
class SentRequestWrap {
|
||||
private:
|
||||
friend class Sender;
|
||||
SentRequestWrap(not_null<Sender*> sender, mtpRequestId requestId) : _sender(sender), _requestId(requestId) {
|
||||
}
|
||||
|
||||
public:
|
||||
void cancel() {
|
||||
if (_requestId) {
|
||||
_sender->senderRequestCancel(_requestId);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
not_null<Sender*> _sender;
|
||||
mtpRequestId _requestId = 0;
|
||||
|
||||
};
|
||||
|
||||
template <
|
||||
typename Request,
|
||||
typename = std::enable_if_t<!std::is_reference_v<Request>>,
|
||||
typename = typename Request::Unboxed>
|
||||
[[nodiscard]] SpecificRequestBuilder<Request> request(Request &&request) noexcept;
|
||||
|
||||
[[nodiscard]] SentRequestWrap request(mtpRequestId requestId) noexcept;
|
||||
|
||||
[[nodiscard]] auto requestCanceller() noexcept {
|
||||
return [this](mtpRequestId requestId) {
|
||||
request(requestId).cancel();
|
||||
};
|
||||
}
|
||||
|
||||
void requestSendDelayed() {
|
||||
_instance->sendAnything();
|
||||
}
|
||||
void requestCancellingDiscard() {
|
||||
for (auto &request : base::take(_requests)) {
|
||||
request.handled();
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] mtpRequestId allocateRequestId() noexcept {
|
||||
return details::GetNextRequestId();
|
||||
}
|
||||
[[nodiscard]] bool pending(mtpRequestId requestId) noexcept {
|
||||
return _requests.contains(requestId);
|
||||
}
|
||||
|
||||
private:
|
||||
class RequestWrap {
|
||||
public:
|
||||
RequestWrap(
|
||||
not_null<Instance*> instance,
|
||||
mtpRequestId requestId) noexcept
|
||||
: _instance(instance)
|
||||
, _id(requestId) {
|
||||
}
|
||||
|
||||
RequestWrap(const RequestWrap &other) = delete;
|
||||
RequestWrap &operator=(const RequestWrap &other) = delete;
|
||||
RequestWrap(RequestWrap &&other)
|
||||
: _instance(other._instance)
|
||||
, _id(base::take(other._id)) {
|
||||
}
|
||||
RequestWrap &operator=(RequestWrap &&other) {
|
||||
Expects(_instance == other._instance);
|
||||
|
||||
if (_id != other._id) {
|
||||
cancelRequest();
|
||||
_id = base::take(other._id);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
mtpRequestId id() const noexcept {
|
||||
return _id;
|
||||
}
|
||||
void handled() const noexcept {
|
||||
_id = 0;
|
||||
}
|
||||
|
||||
~RequestWrap() {
|
||||
cancelRequest();
|
||||
}
|
||||
|
||||
private:
|
||||
void cancelRequest() {
|
||||
if (_id) {
|
||||
_instance->cancel(_id);
|
||||
}
|
||||
}
|
||||
const not_null<Instance*> _instance;
|
||||
mutable mtpRequestId _id = 0;
|
||||
|
||||
};
|
||||
|
||||
struct RequestWrapComparator {
|
||||
using is_transparent = std::true_type;
|
||||
|
||||
struct helper {
|
||||
mtpRequestId requestId = 0;
|
||||
|
||||
helper() = default;
|
||||
helper(const helper &other) = default;
|
||||
helper(mtpRequestId requestId) noexcept : requestId(requestId) {
|
||||
}
|
||||
helper(const RequestWrap &request) noexcept : requestId(request.id()) {
|
||||
}
|
||||
bool operator<(helper other) const {
|
||||
return requestId < other.requestId;
|
||||
}
|
||||
};
|
||||
bool operator()(const helper &&lhs, const helper &&rhs) const {
|
||||
return lhs < rhs;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename Request>
|
||||
friend class SpecificRequestBuilder;
|
||||
friend class RequestBuilder;
|
||||
friend class RequestWrap;
|
||||
friend class SentRequestWrap;
|
||||
|
||||
void senderRequestRegister(mtpRequestId requestId) {
|
||||
_requests.emplace(_instance, requestId);
|
||||
}
|
||||
void senderRequestHandled(mtpRequestId requestId) {
|
||||
auto it = _requests.find(requestId);
|
||||
if (it != _requests.cend()) {
|
||||
it->handled();
|
||||
_requests.erase(it);
|
||||
}
|
||||
}
|
||||
void senderRequestCancel(mtpRequestId requestId) {
|
||||
auto it = _requests.find(requestId);
|
||||
if (it != _requests.cend()) {
|
||||
_requests.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
const not_null<Instance*> _instance;
|
||||
base::flat_set<RequestWrap, RequestWrapComparator> _requests;
|
||||
|
||||
};
|
||||
|
||||
template <typename Request, typename, typename>
|
||||
Sender::SpecificRequestBuilder<Request> Sender::request(Request &&request) noexcept {
|
||||
return SpecificRequestBuilder<Request>(this, std::move(request));
|
||||
}
|
||||
|
||||
inline Sender::SentRequestWrap Sender::request(mtpRequestId requestId) noexcept {
|
||||
return SentRequestWrap(this, requestId);
|
||||
}
|
||||
|
||||
} // namespace MTP
|
||||
589
Telegram/SourceFiles/mtproto/session.cpp
Normal file
589
Telegram/SourceFiles/mtproto/session.cpp
Normal file
@@ -0,0 +1,589 @@
|
||||
/*
|
||||
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/session.h"
|
||||
|
||||
#include "mtproto/details/mtproto_dcenter.h"
|
||||
#include "mtproto/session_private.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
#include "core/application.h"
|
||||
#include "core/core_settings.h"
|
||||
#include "base/unixtime.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
|
||||
SessionOptions::SessionOptions(
|
||||
const QString &systemLangCode,
|
||||
const QString &cloudLangCode,
|
||||
const QString &langPackName,
|
||||
const ProxyData &proxy,
|
||||
bool useIPv4,
|
||||
bool useIPv6,
|
||||
bool useHttp,
|
||||
bool useTcp)
|
||||
: systemLangCode(systemLangCode)
|
||||
, cloudLangCode(cloudLangCode)
|
||||
, langPackName(langPackName)
|
||||
, proxy(proxy)
|
||||
, useIPv4(useIPv4)
|
||||
, useIPv6(useIPv6)
|
||||
, useHttp(useHttp)
|
||||
, useTcp(useTcp) {
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
void SessionData::withSession(Callback &&callback) {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
if (const auto session = _owner) {
|
||||
InvokeQueued(session, [
|
||||
session,
|
||||
callback = std::forward<Callback>(callback)
|
||||
] {
|
||||
callback(session);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void SessionData::notifyConnectionInited(const SessionOptions &options) {
|
||||
// #TODO race
|
||||
const auto current = this->options();
|
||||
if (current.cloudLangCode == _options.cloudLangCode
|
||||
&& current.systemLangCode == _options.systemLangCode
|
||||
&& current.langPackName == _options.langPackName
|
||||
&& current.proxy == _options.proxy) {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
if (_owner) {
|
||||
_owner->notifyDcConnectionInited();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SessionData::queueTryToReceive() {
|
||||
withSession([](not_null<Session*> session) {
|
||||
session->tryToReceive();
|
||||
});
|
||||
}
|
||||
|
||||
void SessionData::queueNeedToResumeAndSend() {
|
||||
withSession([](not_null<Session*> session) {
|
||||
session->needToResumeAndSend();
|
||||
});
|
||||
}
|
||||
|
||||
void SessionData::queueConnectionStateChange(int newState) {
|
||||
withSession([=](not_null<Session*> session) {
|
||||
session->connectionStateChange(newState);
|
||||
});
|
||||
}
|
||||
|
||||
void SessionData::queueResetDone() {
|
||||
withSession([](not_null<Session*> session) {
|
||||
session->resetDone();
|
||||
});
|
||||
}
|
||||
|
||||
void SessionData::queueSendAnything(crl::time msCanWait) {
|
||||
withSession([=](not_null<Session*> session) {
|
||||
session->sendAnything(msCanWait);
|
||||
});
|
||||
}
|
||||
|
||||
bool SessionData::connectionInited() const {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
return _owner ? _owner->connectionInited() : false;
|
||||
}
|
||||
|
||||
AuthKeyPtr SessionData::getTemporaryKey(TemporaryKeyType type) const {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
return _owner ? _owner->getTemporaryKey(type) : nullptr;
|
||||
}
|
||||
|
||||
AuthKeyPtr SessionData::getPersistentKey() const {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
return _owner ? _owner->getPersistentKey() : nullptr;
|
||||
}
|
||||
|
||||
CreatingKeyType SessionData::acquireKeyCreation(DcType type) {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
return _owner ? _owner->acquireKeyCreation(type) : CreatingKeyType::None;
|
||||
}
|
||||
|
||||
bool SessionData::releaseKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
const AuthKeyPtr &persistentKeyUsedForBind) {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
return _owner
|
||||
? _owner->releaseKeyCreationOnDone(
|
||||
temporaryKey,
|
||||
persistentKeyUsedForBind)
|
||||
: false;
|
||||
}
|
||||
|
||||
bool SessionData::releaseCdnKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey) {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
return _owner
|
||||
? _owner->releaseCdnKeyCreationOnDone(temporaryKey)
|
||||
: false;
|
||||
}
|
||||
|
||||
void SessionData::releaseKeyCreationOnFail() {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
if (_owner) {
|
||||
_owner->releaseKeyCreationOnFail();
|
||||
}
|
||||
}
|
||||
|
||||
void SessionData::destroyTemporaryKey(uint64 keyId) {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
if (_owner) {
|
||||
_owner->destroyTemporaryKey(keyId);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionData::detach() {
|
||||
QMutexLocker lock(&_ownerMutex);
|
||||
_owner = nullptr;
|
||||
}
|
||||
|
||||
Session::Session(
|
||||
not_null<Instance*> instance,
|
||||
not_null<QThread*> thread,
|
||||
ShiftedDcId shiftedDcId,
|
||||
not_null<Dcenter*> dc)
|
||||
: _instance(instance)
|
||||
, _shiftedDcId(shiftedDcId)
|
||||
, _dc(dc)
|
||||
, _data(std::make_shared<SessionData>(this))
|
||||
, _thread(thread)
|
||||
, _sender([=] { needToResumeAndSend(); }) {
|
||||
refreshOptions();
|
||||
watchDcKeyChanges();
|
||||
watchDcOptionsChanges();
|
||||
start();
|
||||
}
|
||||
|
||||
Session::~Session() {
|
||||
Expects(!_private);
|
||||
|
||||
if (_myKeyCreation != CreatingKeyType::None) {
|
||||
releaseKeyCreationOnFail();
|
||||
}
|
||||
}
|
||||
|
||||
void Session::watchDcKeyChanges() {
|
||||
_instance->dcTemporaryKeyChanged(
|
||||
) | rpl::filter([=](DcId dcId) {
|
||||
return (dcId == _shiftedDcId) || (dcId == BareDcId(_shiftedDcId));
|
||||
}) | rpl::on_next([=] {
|
||||
DEBUG_LOG(("AuthKey Info: dcTemporaryKeyChanged in Session %1"
|
||||
).arg(_shiftedDcId));
|
||||
if (const auto captured = _private) {
|
||||
InvokeQueued(captured, [=] {
|
||||
DEBUG_LOG(("AuthKey Info: calling Connection::updateAuthKey in Session %1"
|
||||
).arg(_shiftedDcId));
|
||||
captured->updateAuthKey();
|
||||
});
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Session::watchDcOptionsChanges() {
|
||||
_instance->dcOptions().changed(
|
||||
) | rpl::filter([=](DcId dcId) {
|
||||
return (BareDcId(_shiftedDcId) == dcId) && (_private != nullptr);
|
||||
}) | rpl::on_next([=] {
|
||||
InvokeQueued(_private, [captured = _private] {
|
||||
captured->dcOptionsChanged();
|
||||
});
|
||||
}, _lifetime);
|
||||
|
||||
_instance->dcOptions().cdnConfigChanged(
|
||||
) | rpl::filter([=] {
|
||||
return (_private != nullptr)
|
||||
&& (_instance->dcOptions().dcType(_shiftedDcId) == DcType::Cdn);
|
||||
}) | rpl::on_next([=] {
|
||||
InvokeQueued(_private, [captured = _private] {
|
||||
captured->cdnConfigChanged();
|
||||
});
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Session::start() {
|
||||
killConnection();
|
||||
_private = new SessionPrivate(
|
||||
_instance,
|
||||
_thread.get(),
|
||||
_data,
|
||||
_shiftedDcId);
|
||||
}
|
||||
|
||||
void Session::restart() {
|
||||
if (_killed) {
|
||||
DEBUG_LOG(("Session Error: can't restart a killed session"));
|
||||
return;
|
||||
}
|
||||
refreshOptions();
|
||||
if (const auto captured = _private) {
|
||||
InvokeQueued(captured, [=] {
|
||||
captured->restartNow();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Session::refreshOptions() {
|
||||
auto &settings = Core::App().settings().proxy();
|
||||
const auto &proxy = settings.selected();
|
||||
const auto isEnabled = settings.isEnabled();
|
||||
const auto proxyType = (isEnabled ? proxy.type : ProxyData::Type::None);
|
||||
const auto useTcp = (proxyType != ProxyData::Type::Http);
|
||||
const auto useHttp = (proxyType != ProxyData::Type::Mtproto);
|
||||
const auto useIPv4 = true;
|
||||
const auto useIPv6 = settings.tryIPv6();
|
||||
_data->setOptions(SessionOptions(
|
||||
_instance->systemLangCode(),
|
||||
_instance->cloudLangCode(),
|
||||
_instance->langPackName(),
|
||||
(isEnabled ? proxy : ProxyData()),
|
||||
useIPv4,
|
||||
useIPv6,
|
||||
useHttp,
|
||||
useTcp));
|
||||
}
|
||||
|
||||
void Session::reInitConnection() {
|
||||
setConnectionNotInited();
|
||||
restart();
|
||||
}
|
||||
|
||||
void Session::setConnectionNotInited() {
|
||||
_dc->setConnectionInited(false);
|
||||
}
|
||||
|
||||
void Session::stop() {
|
||||
if (_killed) {
|
||||
DEBUG_LOG(("Session Error: can't stop a killed session"));
|
||||
return;
|
||||
}
|
||||
DEBUG_LOG(("Session Info: stopping session dcWithShift %1").arg(_shiftedDcId));
|
||||
killConnection();
|
||||
}
|
||||
|
||||
void Session::kill() {
|
||||
stop();
|
||||
_killed = true;
|
||||
_data->detach();
|
||||
DEBUG_LOG(("Session Info: marked session dcWithShift %1 as killed").arg(_shiftedDcId));
|
||||
}
|
||||
|
||||
void Session::unpaused() {
|
||||
if (_needToReceive) {
|
||||
_needToReceive = false;
|
||||
InvokeQueued(this, [=] {
|
||||
tryToReceive();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void Session::sendAnything(crl::time msCanWait) {
|
||||
if (_killed) {
|
||||
DEBUG_LOG(("Session Error: can't send anything in a killed session"));
|
||||
return;
|
||||
}
|
||||
const auto ms = crl::now();
|
||||
if (_msSendCall) {
|
||||
if (ms > _msSendCall + _msWait) {
|
||||
_msWait = 0;
|
||||
} else {
|
||||
_msWait = (_msSendCall + _msWait) - ms;
|
||||
if (_msWait > msCanWait) {
|
||||
_msWait = msCanWait;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_msWait = msCanWait;
|
||||
}
|
||||
if (_msWait) {
|
||||
DEBUG_LOG(("MTP Info: dcWithShift %1 can wait for %2ms from current %3").arg(_shiftedDcId).arg(_msWait).arg(_msSendCall));
|
||||
_msSendCall = ms;
|
||||
_sender.callOnce(_msWait);
|
||||
} else {
|
||||
DEBUG_LOG(("MTP Info: dcWithShift %1 stopped send timer, can wait for %2ms from current %3").arg(_shiftedDcId).arg(_msWait).arg(_msSendCall));
|
||||
_sender.cancel();
|
||||
_msSendCall = 0;
|
||||
needToResumeAndSend();
|
||||
}
|
||||
}
|
||||
|
||||
void Session::needToResumeAndSend() {
|
||||
if (_killed) {
|
||||
DEBUG_LOG(("Session Info: can't resume a killed session"));
|
||||
return;
|
||||
}
|
||||
if (!_private) {
|
||||
DEBUG_LOG(("Session Info: resuming session dcWithShift %1").arg(_shiftedDcId));
|
||||
start();
|
||||
}
|
||||
const auto captured = _private;
|
||||
const auto ping = base::take(_ping);
|
||||
InvokeQueued(captured, [=] {
|
||||
if (ping) {
|
||||
captured->sendPingForce();
|
||||
} else {
|
||||
captured->tryToSend();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Session::connectionStateChange(int newState) {
|
||||
_instance->onStateChange(_shiftedDcId, newState);
|
||||
}
|
||||
|
||||
void Session::resetDone() {
|
||||
_instance->onSessionReset(_shiftedDcId);
|
||||
}
|
||||
|
||||
void Session::cancel(mtpRequestId requestId, mtpMsgId msgId) {
|
||||
if (requestId) {
|
||||
QWriteLocker locker(_data->toSendMutex());
|
||||
_data->toSendMap().remove(requestId);
|
||||
}
|
||||
if (msgId) {
|
||||
QWriteLocker locker(_data->haveSentMutex());
|
||||
_data->haveSentMap().remove(msgId);
|
||||
}
|
||||
}
|
||||
|
||||
void Session::ping() {
|
||||
_ping = true;
|
||||
sendAnything();
|
||||
}
|
||||
|
||||
int32 Session::requestState(mtpRequestId requestId) const {
|
||||
int32 result = MTP::RequestSent;
|
||||
|
||||
bool connected = false;
|
||||
if (_private) {
|
||||
const auto s = _private->getState();
|
||||
if (s == ConnectedState) {
|
||||
connected = true;
|
||||
} else if (s == ConnectingState || s == DisconnectedState) {
|
||||
if (result < 0 || result == MTP::RequestSent) {
|
||||
result = MTP::RequestConnecting;
|
||||
}
|
||||
} else if (s < 0) {
|
||||
if ((result < 0 && s > result) || result == MTP::RequestSent) {
|
||||
result = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!connected) {
|
||||
return result;
|
||||
} else if (!requestId) {
|
||||
return MTP::RequestSent;
|
||||
}
|
||||
|
||||
QWriteLocker locker(_data->toSendMutex());
|
||||
return _data->toSendMap().contains(requestId)
|
||||
? MTP::RequestSending
|
||||
: MTP::RequestSent;
|
||||
}
|
||||
|
||||
int32 Session::getState() const {
|
||||
int32 result = -86400000;
|
||||
|
||||
if (_private) {
|
||||
const auto s = _private->getState();
|
||||
if (s == ConnectedState
|
||||
|| s == ConnectingState
|
||||
|| s == DisconnectedState) {
|
||||
return s;
|
||||
} else if (s < 0) {
|
||||
if (result < 0 && s > result) {
|
||||
result = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == -86400000) {
|
||||
result = DisconnectedState;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QString Session::transport() const {
|
||||
return _private ? _private->transport() : QString();
|
||||
}
|
||||
|
||||
void Session::sendPrepared(
|
||||
const SerializedRequest &request,
|
||||
crl::time msCanWait) {
|
||||
DEBUG_LOG(("MTP Info: adding request to toSendMap, msCanWait %1"
|
||||
).arg(msCanWait));
|
||||
{
|
||||
QWriteLocker locker(_data->toSendMutex());
|
||||
_data->toSendMap().emplace(request->requestId, request);
|
||||
*(mtpMsgId*)(request->data() + 4) = 0;
|
||||
*(request->data() + 6) = 0;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("MTP Info: added, requestId %1").arg(request->requestId));
|
||||
if (msCanWait >= 0) {
|
||||
InvokeQueued(this, [=] {
|
||||
sendAnything(msCanWait);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
CreatingKeyType Session::acquireKeyCreation(DcType type) {
|
||||
Expects(_myKeyCreation == CreatingKeyType::None);
|
||||
|
||||
_myKeyCreation = _dc->acquireKeyCreation(type);
|
||||
return _myKeyCreation;
|
||||
}
|
||||
|
||||
bool Session::releaseKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
const AuthKeyPtr &persistentKeyUsedForBind) {
|
||||
Expects(_myKeyCreation != CreatingKeyType::None);
|
||||
Expects(persistentKeyUsedForBind != nullptr);
|
||||
|
||||
return releaseGenericKeyCreationOnDone(
|
||||
temporaryKey,
|
||||
persistentKeyUsedForBind);
|
||||
}
|
||||
|
||||
bool Session::releaseCdnKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey) {
|
||||
Expects(_myKeyCreation == CreatingKeyType::TemporaryRegular);
|
||||
|
||||
return releaseGenericKeyCreationOnDone(temporaryKey, nullptr);
|
||||
}
|
||||
|
||||
bool Session::releaseGenericKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
const AuthKeyPtr &persistentKeyUsedForBind) {
|
||||
const auto wasKeyCreation = std::exchange(
|
||||
_myKeyCreation,
|
||||
CreatingKeyType::None);
|
||||
const auto result = _dc->releaseKeyCreationOnDone(
|
||||
wasKeyCreation,
|
||||
temporaryKey,
|
||||
persistentKeyUsedForBind);
|
||||
|
||||
if (!result) {
|
||||
DEBUG_LOG(("AuthKey Info: Persistent key changed "
|
||||
"while binding temporary, dcWithShift %1"
|
||||
).arg(_shiftedDcId));
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("AuthKey Info: Session key bound, setting, dcWithShift %1"
|
||||
).arg(_shiftedDcId));
|
||||
|
||||
const auto dcId = _dc->id();
|
||||
const auto instance = _instance;
|
||||
InvokeQueued(instance, [=] {
|
||||
if (wasKeyCreation == CreatingKeyType::Persistent) {
|
||||
instance->dcPersistentKeyChanged(dcId, persistentKeyUsedForBind);
|
||||
} else {
|
||||
instance->dcTemporaryKeyChanged(dcId);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
void Session::releaseKeyCreationOnFail() {
|
||||
Expects(_myKeyCreation != CreatingKeyType::None);
|
||||
|
||||
const auto wasKeyCreation = std::exchange(
|
||||
_myKeyCreation,
|
||||
CreatingKeyType::None);
|
||||
_dc->releaseKeyCreationOnFail(wasKeyCreation);
|
||||
}
|
||||
|
||||
void Session::notifyDcConnectionInited() {
|
||||
DEBUG_LOG(("MTP Info: MTProtoDC::connectionWasInited(), dcWithShift %1"
|
||||
).arg(_shiftedDcId));
|
||||
_dc->setConnectionInited();
|
||||
}
|
||||
|
||||
void Session::destroyTemporaryKey(uint64 keyId) {
|
||||
if (!_dc->destroyTemporaryKey(keyId)) {
|
||||
return;
|
||||
}
|
||||
const auto dcId = _dc->id();
|
||||
const auto instance = _instance;
|
||||
InvokeQueued(instance, [=] {
|
||||
instance->dcTemporaryKeyChanged(dcId);
|
||||
});
|
||||
}
|
||||
|
||||
int32 Session::getDcWithShift() const {
|
||||
return _shiftedDcId;
|
||||
}
|
||||
|
||||
AuthKeyPtr Session::getTemporaryKey(TemporaryKeyType type) const {
|
||||
return _dc->getTemporaryKey(type);
|
||||
}
|
||||
|
||||
AuthKeyPtr Session::getPersistentKey() const {
|
||||
return _dc->getPersistentKey();
|
||||
}
|
||||
|
||||
bool Session::connectionInited() const {
|
||||
return _dc->connectionInited();
|
||||
}
|
||||
|
||||
void Session::tryToReceive() {
|
||||
if (_killed) {
|
||||
DEBUG_LOG(("Session Error: can't receive in a killed session"));
|
||||
return;
|
||||
}
|
||||
if (paused()) {
|
||||
_needToReceive = true;
|
||||
return;
|
||||
}
|
||||
while (true) {
|
||||
auto lock = QWriteLocker(_data->haveReceivedMutex());
|
||||
const auto messages = base::take(_data->haveReceivedMessages());
|
||||
lock.unlock();
|
||||
if (messages.empty()) {
|
||||
break;
|
||||
}
|
||||
const auto guard = QPointer<Session>(this);
|
||||
const auto instance = QPointer<Instance>(_instance);
|
||||
const auto main = (_shiftedDcId == BareDcId(_shiftedDcId));
|
||||
for (const auto &message : messages) {
|
||||
if (message.requestId) {
|
||||
instance->processCallback(message);
|
||||
} else if (main) {
|
||||
// Process updates only in main session.
|
||||
instance->processUpdate(message);
|
||||
}
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!guard) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Session::killConnection() {
|
||||
if (!_private) {
|
||||
return;
|
||||
}
|
||||
|
||||
base::take(_private)->deleteLater();
|
||||
|
||||
Ensures(_private == nullptr);
|
||||
}
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
222
Telegram/SourceFiles/mtproto/session.h
Normal file
222
Telegram/SourceFiles/mtproto/session.h
Normal file
@@ -0,0 +1,222 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "base/timer.h"
|
||||
#include "mtproto/mtproto_response.h"
|
||||
#include "mtproto/mtproto_proxy_data.h"
|
||||
#include "mtproto/details/mtproto_serialized_request.h"
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
namespace MTP {
|
||||
|
||||
class Instance;
|
||||
class AuthKey;
|
||||
using AuthKeyPtr = std::shared_ptr<AuthKey>;
|
||||
enum class DcType;
|
||||
|
||||
namespace details {
|
||||
|
||||
class Dcenter;
|
||||
class SessionPrivate;
|
||||
|
||||
enum class TemporaryKeyType;
|
||||
enum class CreatingKeyType;
|
||||
|
||||
struct SessionOptions {
|
||||
SessionOptions() = default;
|
||||
SessionOptions(
|
||||
const QString &systemLangCode,
|
||||
const QString &cloudLangCode,
|
||||
const QString &langPackName,
|
||||
const ProxyData &proxy,
|
||||
bool useIPv4,
|
||||
bool useIPv6,
|
||||
bool useHttp,
|
||||
bool useTcp);
|
||||
|
||||
QString systemLangCode;
|
||||
QString cloudLangCode;
|
||||
QString langPackName;
|
||||
ProxyData proxy;
|
||||
bool useIPv4 = true;
|
||||
bool useIPv6 = true;
|
||||
bool useHttp = true;
|
||||
bool useTcp = true;
|
||||
|
||||
};
|
||||
|
||||
class Session;
|
||||
class SessionData final {
|
||||
public:
|
||||
explicit SessionData(not_null<Session*> creator) : _owner(creator) {
|
||||
}
|
||||
|
||||
void notifyConnectionInited(const SessionOptions &options);
|
||||
void setOptions(SessionOptions options) {
|
||||
QWriteLocker locker(&_optionsLock);
|
||||
_options = options;
|
||||
}
|
||||
[[nodiscard]] SessionOptions options() const {
|
||||
QReadLocker locker(&_optionsLock);
|
||||
return _options;
|
||||
}
|
||||
|
||||
not_null<QReadWriteLock*> toSendMutex() {
|
||||
return &_toSendLock;
|
||||
}
|
||||
not_null<QReadWriteLock*> haveSentMutex() {
|
||||
return &_haveSentLock;
|
||||
}
|
||||
not_null<QReadWriteLock*> haveReceivedMutex() {
|
||||
return &_haveReceivedLock;
|
||||
}
|
||||
|
||||
base::flat_map<mtpRequestId, SerializedRequest> &toSendMap() {
|
||||
return _toSend;
|
||||
}
|
||||
base::flat_map<mtpMsgId, SerializedRequest> &haveSentMap() {
|
||||
return _haveSent;
|
||||
}
|
||||
std::vector<Response> &haveReceivedMessages() {
|
||||
return _receivedMessages;
|
||||
}
|
||||
|
||||
// SessionPrivate -> Session interface.
|
||||
void queueTryToReceive();
|
||||
void queueNeedToResumeAndSend();
|
||||
void queueConnectionStateChange(int newState);
|
||||
void queueResetDone();
|
||||
void queueSendAnything(crl::time msCanWait = 0);
|
||||
|
||||
[[nodiscard]] bool connectionInited() const;
|
||||
[[nodiscard]] AuthKeyPtr getPersistentKey() const;
|
||||
[[nodiscard]] AuthKeyPtr getTemporaryKey(TemporaryKeyType type) const;
|
||||
[[nodiscard]] CreatingKeyType acquireKeyCreation(DcType type);
|
||||
[[nodiscard]] bool releaseKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
const AuthKeyPtr &persistentKeyUsedForBind);
|
||||
[[nodiscard]] bool releaseCdnKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey);
|
||||
void releaseKeyCreationOnFail();
|
||||
void destroyTemporaryKey(uint64 keyId);
|
||||
|
||||
void detach();
|
||||
|
||||
private:
|
||||
template <typename Callback>
|
||||
void withSession(Callback &&callback);
|
||||
|
||||
Session *_owner = nullptr;
|
||||
mutable QMutex _ownerMutex;
|
||||
|
||||
SessionOptions _options;
|
||||
mutable QReadWriteLock _optionsLock;
|
||||
|
||||
base::flat_map<mtpRequestId, SerializedRequest> _toSend; // map of request_id -> request, that is waiting to be sent
|
||||
QReadWriteLock _toSendLock;
|
||||
|
||||
base::flat_map<mtpMsgId, SerializedRequest> _haveSent; // map of msg_id -> request, that was sent
|
||||
QReadWriteLock _haveSentLock;
|
||||
|
||||
std::vector<Response> _receivedMessages; // list of responses / updates that should be processed in the main thread
|
||||
QReadWriteLock _haveReceivedLock;
|
||||
|
||||
};
|
||||
|
||||
class Session final : public QObject {
|
||||
public:
|
||||
// Main thread.
|
||||
Session(
|
||||
not_null<Instance*> instance,
|
||||
not_null<QThread*> thread,
|
||||
ShiftedDcId shiftedDcId,
|
||||
not_null<Dcenter*> dc);
|
||||
~Session();
|
||||
|
||||
void start();
|
||||
void reInitConnection();
|
||||
void setConnectionNotInited();
|
||||
|
||||
void restart();
|
||||
void refreshOptions();
|
||||
void stop();
|
||||
void kill();
|
||||
|
||||
void unpaused();
|
||||
|
||||
// Thread-safe.
|
||||
[[nodiscard]] ShiftedDcId getDcWithShift() const;
|
||||
[[nodiscard]] AuthKeyPtr getPersistentKey() const;
|
||||
[[nodiscard]] AuthKeyPtr getTemporaryKey(TemporaryKeyType type) const;
|
||||
[[nodiscard]] bool connectionInited() const;
|
||||
void sendPrepared(
|
||||
const SerializedRequest &request,
|
||||
crl::time msCanWait = 0);
|
||||
|
||||
// SessionPrivate thread.
|
||||
[[nodiscard]] CreatingKeyType acquireKeyCreation(DcType type);
|
||||
[[nodiscard]] bool releaseKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
const AuthKeyPtr &persistentKeyUsedForBind);
|
||||
[[nodiscard]] bool releaseCdnKeyCreationOnDone(const AuthKeyPtr &temporaryKey);
|
||||
void releaseKeyCreationOnFail();
|
||||
void destroyTemporaryKey(uint64 keyId);
|
||||
|
||||
void notifyDcConnectionInited();
|
||||
|
||||
void ping();
|
||||
void cancel(mtpRequestId requestId, mtpMsgId msgId);
|
||||
int requestState(mtpRequestId requestId) const;
|
||||
int getState() const;
|
||||
QString transport() const;
|
||||
|
||||
void tryToReceive();
|
||||
void needToResumeAndSend();
|
||||
void connectionStateChange(int newState);
|
||||
void resetDone();
|
||||
void sendAnything(crl::time msCanWait = 0);
|
||||
|
||||
private:
|
||||
void watchDcKeyChanges();
|
||||
void watchDcOptionsChanges();
|
||||
|
||||
void killConnection();
|
||||
|
||||
[[nodiscard]] bool releaseGenericKeyCreationOnDone(
|
||||
const AuthKeyPtr &temporaryKey,
|
||||
const AuthKeyPtr &persistentKeyUsedForBind);
|
||||
|
||||
const not_null<Instance*> _instance;
|
||||
const ShiftedDcId _shiftedDcId = 0;
|
||||
const not_null<Dcenter*> _dc;
|
||||
const std::shared_ptr<SessionData> _data;
|
||||
const not_null<QThread*> _thread;
|
||||
|
||||
SessionPrivate *_private = nullptr;
|
||||
|
||||
bool _killed = false;
|
||||
bool _needToReceive = false;
|
||||
|
||||
AuthKeyPtr _dcKeyForCheck;
|
||||
CreatingKeyType _myKeyCreation = CreatingKeyType();
|
||||
|
||||
crl::time _msSendCall = 0;
|
||||
crl::time _msWait = 0;
|
||||
|
||||
bool _ping = false;
|
||||
|
||||
base::Timer _sender;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
2746
Telegram/SourceFiles/mtproto/session_private.cpp
Normal file
2746
Telegram/SourceFiles/mtproto/session_private.cpp
Normal file
File diff suppressed because it is too large
Load Diff
246
Telegram/SourceFiles/mtproto/session_private.h
Normal file
246
Telegram/SourceFiles/mtproto/session_private.h
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/details/mtproto_received_ids_manager.h"
|
||||
#include "mtproto/details/mtproto_serialized_request.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
#include "mtproto/facade.h"
|
||||
#include "base/timer.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace details {
|
||||
class BoundKeyCreator;
|
||||
} // namespace details
|
||||
|
||||
class Instance;
|
||||
|
||||
namespace details {
|
||||
|
||||
class AbstractConnection;
|
||||
class SessionData;
|
||||
class RSAPublicKey;
|
||||
struct SessionOptions;
|
||||
|
||||
class SessionPrivate final : public QObject {
|
||||
public:
|
||||
SessionPrivate(
|
||||
not_null<Instance*> instance,
|
||||
not_null<QThread*> thread,
|
||||
std::shared_ptr<SessionData> data,
|
||||
ShiftedDcId shiftedDcId);
|
||||
~SessionPrivate();
|
||||
|
||||
[[nodiscard]] int32 getShiftedDcId() const;
|
||||
void dcOptionsChanged();
|
||||
void cdnConfigChanged();
|
||||
|
||||
[[nodiscard]] int32 getState() const;
|
||||
[[nodiscard]] QString transport() const;
|
||||
|
||||
void updateAuthKey();
|
||||
void restartNow();
|
||||
void sendPingForce();
|
||||
void tryToSend();
|
||||
|
||||
private:
|
||||
static constexpr auto kUpdateStateAlways = 666;
|
||||
|
||||
struct TestConnection {
|
||||
ConnectionPointer data;
|
||||
int priority = 0;
|
||||
};
|
||||
struct SentContainer {
|
||||
crl::time sent = 0;
|
||||
std::vector<mtpMsgId> messages;
|
||||
};
|
||||
enum class HandleResult {
|
||||
Success,
|
||||
Ignored,
|
||||
RestartConnection,
|
||||
ResetSession,
|
||||
DestroyTemporaryKey,
|
||||
ParseError,
|
||||
};
|
||||
|
||||
void connectToServer(bool afterConfig = false);
|
||||
void connectingTimedOut();
|
||||
void doDisconnect();
|
||||
void restart();
|
||||
void requestCDNConfig();
|
||||
void handleError(int errorCode);
|
||||
void onError(
|
||||
not_null<AbstractConnection*> connection,
|
||||
qint32 errorCode);
|
||||
void onConnected(not_null<AbstractConnection*> connection);
|
||||
void onDisconnected(not_null<AbstractConnection*> connection);
|
||||
void onSentSome(uint64 size);
|
||||
void onReceivedSome();
|
||||
|
||||
void handleReceived();
|
||||
|
||||
void retryByTimer();
|
||||
void waitConnectedFailed();
|
||||
void waitReceivedFailed();
|
||||
void waitBetterFailed();
|
||||
void markConnectionOld();
|
||||
void sendPingByTimer();
|
||||
void destroyAllConnections();
|
||||
|
||||
void confirmBestConnection();
|
||||
void removeTestConnection(not_null<AbstractConnection*> connection);
|
||||
[[nodiscard]] int16 getProtocolDcId() const;
|
||||
|
||||
void checkSentRequests();
|
||||
void clearOldContainers();
|
||||
|
||||
mtpMsgId placeToContainer(
|
||||
SerializedRequest &toSendRequest,
|
||||
mtpMsgId &bigMsgId,
|
||||
bool forceNewMsgId,
|
||||
SerializedRequest &req);
|
||||
mtpMsgId prepareToSend(
|
||||
SerializedRequest &request,
|
||||
mtpMsgId currentLastId,
|
||||
bool forceNewMsgId);
|
||||
mtpMsgId replaceMsgId(
|
||||
SerializedRequest &request,
|
||||
mtpMsgId newId);
|
||||
|
||||
bool sendSecureRequest(
|
||||
SerializedRequest &&request,
|
||||
bool needAnyResponse);
|
||||
mtpRequestId wasSent(mtpMsgId msgId) const;
|
||||
|
||||
struct OuterInfo {
|
||||
mtpMsgId outerMsgId = 0;
|
||||
uint64 serverSalt = 0;
|
||||
int32 serverTime = 0;
|
||||
bool badTime = false;
|
||||
};
|
||||
[[nodiscard]] HandleResult handleOneReceived(
|
||||
const mtpPrime *from,
|
||||
const mtpPrime *end,
|
||||
uint64 msgId,
|
||||
OuterInfo info);
|
||||
[[nodiscard]] HandleResult handleBindResponse(
|
||||
mtpMsgId requestMsgId,
|
||||
const mtpBuffer &response);
|
||||
mtpBuffer ungzip(const mtpPrime *from, const mtpPrime *end) const;
|
||||
void handleMsgsStates(const QVector<MTPlong> &ids, const QByteArray &states);
|
||||
|
||||
// _sessionDataMutex must be locked for read.
|
||||
bool setState(int state, int ifState = kUpdateStateAlways);
|
||||
|
||||
void appendTestConnection(
|
||||
DcOptions::Variants::Protocol protocol,
|
||||
const QString &ip,
|
||||
int port,
|
||||
const bytes::vector &protocolSecret);
|
||||
|
||||
// if badTime received - search for ids in sessionData->haveSent and sessionData->wereAcked and sync time/salt, return true if found
|
||||
bool requestsFixTimeSalt(const QVector<MTPlong> &ids, const OuterInfo &info);
|
||||
|
||||
// if we had a confirmed fast request use its unixtime as a correct one.
|
||||
void correctUnixtimeByFastRequest(
|
||||
const QVector<MTPlong> &ids,
|
||||
TimeId serverTime);
|
||||
void correctUnixtimeWithBadLocal(TimeId serverTime);
|
||||
|
||||
// remove msgs with such ids from sessionData->haveSent, add to sessionData->wereAcked
|
||||
void requestsAcked(const QVector<MTPlong> &ids, bool byResponse = false);
|
||||
|
||||
void resend(mtpMsgId msgId, crl::time msCanWait = 0);
|
||||
void resendAll();
|
||||
void clearSpecialMsgId(mtpMsgId msgId);
|
||||
|
||||
[[nodiscard]] DcType tryAcquireKeyCreation();
|
||||
void resetSession();
|
||||
void checkAuthKey();
|
||||
void authKeyChecked();
|
||||
void destroyTemporaryKey();
|
||||
void clearUnboundKeyCreator();
|
||||
void releaseKeyCreationOnFail();
|
||||
void applyAuthKey(AuthKeyPtr &&encryptionKey);
|
||||
[[nodiscard]] bool noMediaKeyWithExistingRegularKey() const;
|
||||
bool destroyOldEnoughPersistentKey();
|
||||
|
||||
void setCurrentKeyId(uint64 newKeyId);
|
||||
void changeSessionId();
|
||||
[[nodiscard]] bool markSessionAsStarted();
|
||||
[[nodiscard]] uint32 nextRequestSeqNumber(bool needAck);
|
||||
|
||||
[[nodiscard]] bool realDcTypeChanged();
|
||||
[[nodiscard]] MTPVector<MTPJSONObjectValue> prepareInitParams();
|
||||
|
||||
const not_null<Instance*> _instance;
|
||||
const ShiftedDcId _shiftedDcId = 0;
|
||||
DcType _realDcType = DcType();
|
||||
DcType _currentDcType = DcType();
|
||||
|
||||
mutable QReadWriteLock _stateMutex;
|
||||
int _state = DisconnectedState;
|
||||
|
||||
bool _needSessionReset = false;
|
||||
|
||||
ConnectionPointer _connection;
|
||||
std::vector<TestConnection> _testConnections;
|
||||
crl::time _startedConnectingAt = 0;
|
||||
|
||||
base::Timer _retryTimer; // exp retry timer
|
||||
int _retryTimeout = 1;
|
||||
qint64 _retryWillFinish = 0;
|
||||
|
||||
base::Timer _oldConnectionTimer;
|
||||
bool _oldConnection = true;
|
||||
|
||||
base::Timer _waitForConnectedTimer;
|
||||
base::Timer _waitForReceivedTimer;
|
||||
base::Timer _waitForBetterTimer;
|
||||
crl::time _waitForReceived = 0;
|
||||
crl::time _waitForConnected = 0;
|
||||
crl::time _firstSentAt = -1;
|
||||
|
||||
mtpPingId _pingId = 0;
|
||||
mtpPingId _pingIdToSend = 0;
|
||||
crl::time _pingSendAt = 0;
|
||||
mtpMsgId _pingMsgId = 0;
|
||||
base::Timer _pingSender;
|
||||
base::Timer _checkSentRequestsTimer;
|
||||
base::Timer _clearOldContainersTimer;
|
||||
|
||||
std::shared_ptr<SessionData> _sessionData;
|
||||
std::unique_ptr<SessionOptions> _options;
|
||||
AuthKeyPtr _encryptionKey;
|
||||
uint64 _keyId = 0;
|
||||
uint64 _sessionId = 0;
|
||||
uint64 _sessionSalt = 0;
|
||||
uint32 _messagesCounter = 0;
|
||||
bool _sessionMarkedAsStarted = false;
|
||||
|
||||
QVector<MTPlong> _ackRequestData;
|
||||
QVector<MTPlong> _resendRequestData;
|
||||
base::flat_set<mtpMsgId> _stateRequestData;
|
||||
ReceivedIdsManager _receivedMessageIds;
|
||||
base::flat_map<mtpMsgId, mtpRequestId> _resendingIds;
|
||||
base::flat_map<mtpMsgId, mtpRequestId> _ackedIds;
|
||||
base::flat_map<mtpMsgId, SerializedRequest> _stateAndResendRequests;
|
||||
base::flat_map<mtpMsgId, SentContainer> _sentContainers;
|
||||
|
||||
std::unique_ptr<BoundKeyCreator> _keyCreator;
|
||||
mtpMsgId _bindMsgId = 0;
|
||||
crl::time _bindMessageSent = 0;
|
||||
|
||||
};
|
||||
|
||||
extern const char kOptionPreferIPv6[];
|
||||
|
||||
} // namespace details
|
||||
} // namespace MTP
|
||||
551
Telegram/SourceFiles/mtproto/special_config_request.cpp
Normal file
551
Telegram/SourceFiles/mtproto/special_config_request.cpp
Normal file
@@ -0,0 +1,551 @@
|
||||
/*
|
||||
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/special_config_request.h"
|
||||
|
||||
#include "mtproto/details/mtproto_rsa_public_key.h"
|
||||
#include "mtproto/mtproto_dc_options.h"
|
||||
#include "mtproto/mtproto_auth_key.h"
|
||||
#include "base/unixtime.h"
|
||||
#include "base/openssl_help.h"
|
||||
#include "base/call_delayed.h"
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
namespace MTP::details {
|
||||
namespace {
|
||||
|
||||
constexpr auto kSendNextTimeout = crl::time(800);
|
||||
|
||||
constexpr auto kPublicKey = "\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIIBCgKCAQEAyr+18Rex2ohtVy8sroGPBwXD3DOoKCSpjDqYoXgCqB7ioln4eDCF\n\
|
||||
fOBUlfXUEvM/fnKCpF46VkAftlb4VuPDeQSS/ZxZYEGqHaywlroVnXHIjgqoxiAd\n\
|
||||
192xRGreuXIaUKmkwlM9JID9WS2jUsTpzQ91L8MEPLJ/4zrBwZua8W5fECwCCh2c\n\
|
||||
9G5IzzBm+otMS/YKwmR1olzRCyEkyAEjXWqBI9Ftv5eG8m0VkBzOG655WIYdyV0H\n\
|
||||
fDK/NWcvGqa0w/nriMD6mDjKOryamw0OP9QuYgMN0C9xMW9y8SmP4h92OAWodTYg\n\
|
||||
Y1hZCxdv6cs5UnW9+PWvS+WIbkh+GaWYxwIDAQAB\n\
|
||||
-----END RSA PUBLIC KEY-----\
|
||||
"_cs;
|
||||
|
||||
const auto kRemoteProject = "peak-vista-421";
|
||||
const auto kFireProject = "reserve-5a846";
|
||||
const auto kConfigKey = "ipconfig";
|
||||
const auto kConfigSubKey = "v3";
|
||||
const auto kApiKey = "AIzaSyC2-kAkpDsroixRXw-sTw-Wfqo4NxjMwwM";
|
||||
const auto kAppId = "1:560508485281:web:4ee13a6af4e84d49e67ae0";
|
||||
|
||||
QString ApiDomain(const QString &service) {
|
||||
return service + ".googleapis.com";
|
||||
}
|
||||
|
||||
QString GenerateInstanceId() {
|
||||
auto fid = bytes::array<17>();
|
||||
bytes::set_random(fid);
|
||||
fid[0] = (bytes::type(0xF0) & fid[0]) | bytes::type(0x07);
|
||||
return QString::fromLatin1(
|
||||
QByteArray::fromRawData(
|
||||
reinterpret_cast<const char*>(fid.data()),
|
||||
fid.size()
|
||||
).toBase64(QByteArray::Base64UrlEncoding).mid(0, 22));
|
||||
}
|
||||
|
||||
QString InstanceId() {
|
||||
static const auto result = GenerateInstanceId();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CheckPhoneByPrefixesRules(const QString &phone, const QString &rules) {
|
||||
static const auto RegExp = QRegularExpression("[^0-9]");
|
||||
const auto check = QString(phone).replace(
|
||||
RegExp,
|
||||
QString());
|
||||
auto result = false;
|
||||
for (const auto &prefix : rules.split(',')) {
|
||||
if (prefix.isEmpty()) {
|
||||
result = true;
|
||||
} else if (prefix[0] == '+' && check.startsWith(prefix.mid(1))) {
|
||||
result = true;
|
||||
} else if (prefix[0] == '-' && check.startsWith(prefix.mid(1))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QByteArray ConcatenateDnsTxtFields(const std::vector<DnsEntry> &response) {
|
||||
auto entries = QMultiMap<int, QString>();
|
||||
for (const auto &entry : response) {
|
||||
entries.insert(INT_MAX - entry.data.size(), entry.data);
|
||||
}
|
||||
return QStringList(entries.values()).join(QString()).toLatin1();
|
||||
}
|
||||
|
||||
QByteArray ParseRemoteConfigResponse(const QByteArray &bytes) {
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(bytes, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG(("Config Error: Failed to parse fire response JSON, error: %1"
|
||||
).arg(error.errorString()));
|
||||
return {};
|
||||
} else if (!document.isObject()) {
|
||||
LOG(("Config Error: Not an object received in fire response JSON."));
|
||||
return {};
|
||||
}
|
||||
return document.object().value(
|
||||
"entries"
|
||||
).toObject().value(
|
||||
u"%1%2"_q.arg(kConfigKey, kConfigSubKey)
|
||||
).toString().toLatin1();
|
||||
}
|
||||
|
||||
QByteArray ParseFireStoreResponse(const QByteArray &bytes) {
|
||||
auto error = QJsonParseError{ 0, QJsonParseError::NoError };
|
||||
const auto document = QJsonDocument::fromJson(bytes, &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
LOG(("Config Error: Failed to parse fire response JSON, error: %1"
|
||||
).arg(error.errorString()));
|
||||
return {};
|
||||
} else if (!document.isObject()) {
|
||||
LOG(("Config Error: Not an object received in fire response JSON."));
|
||||
return {};
|
||||
}
|
||||
return document.object().value(
|
||||
"fields"
|
||||
).toObject().value(
|
||||
"data"
|
||||
).toObject().value(
|
||||
"stringValue"
|
||||
).toString().toLatin1();
|
||||
}
|
||||
|
||||
QByteArray ParseRealtimeResponse(const QByteArray &bytes) {
|
||||
if (bytes.size() < 2
|
||||
|| bytes[0] != '"'
|
||||
|| bytes[bytes.size() - 1] != '"') {
|
||||
return QByteArray();
|
||||
}
|
||||
return bytes.mid(1, bytes.size() - 2);
|
||||
}
|
||||
|
||||
[[nodiscard]] QDateTime ParseHttpDate(const QString &date) {
|
||||
// Wed, 10 Jul 2019 14:33:38 GMT
|
||||
static const auto expression = QRegularExpression(
|
||||
R"(\w\w\w, (\d\d) (\w\w\w) (\d\d\d\d) (\d\d):(\d\d):(\d\d) GMT)");
|
||||
const auto match = expression.match(date);
|
||||
if (!match.hasMatch()) {
|
||||
return QDateTime();
|
||||
}
|
||||
|
||||
const auto number = [&](int index) {
|
||||
return match.capturedView(index).toInt();
|
||||
};
|
||||
const auto day = number(1);
|
||||
const auto month = [&] {
|
||||
static const auto months = {
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec"
|
||||
};
|
||||
const auto captured = match.capturedView(2);
|
||||
for (auto i = begin(months); i != end(months); ++i) {
|
||||
if (captured == QString(*i)) {
|
||||
return 1 + int(i - begin(months));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}();
|
||||
const auto year = number(3);
|
||||
const auto hour = number(4);
|
||||
const auto minute = number(5);
|
||||
const auto second = number(6);
|
||||
return QDateTime(
|
||||
QDate(year, month, day),
|
||||
QTime(hour, minute, second),
|
||||
Qt::UTC);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SpecialConfigRequest::SpecialConfigRequest(
|
||||
Fn<void(
|
||||
DcId dcId,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret)> callback,
|
||||
Fn<void()> timeDoneCallback,
|
||||
bool isTestMode,
|
||||
const QString &domainString,
|
||||
const QString &phone)
|
||||
: _callback(std::move(callback))
|
||||
, _timeDoneCallback(std::move(timeDoneCallback))
|
||||
, _domainString(domainString)
|
||||
, _phone(phone) {
|
||||
Expects((_callback == nullptr) != (_timeDoneCallback == nullptr));
|
||||
|
||||
_manager.setProxy(QNetworkProxy::NoProxy);
|
||||
|
||||
std::random_device rd;
|
||||
const auto shuffle = [&](int from, int till) {
|
||||
Expects(till > from);
|
||||
|
||||
ranges::shuffle(
|
||||
begin(_attempts) + from,
|
||||
begin(_attempts) + till,
|
||||
std::mt19937(rd()));
|
||||
};
|
||||
|
||||
_attempts = {};
|
||||
_attempts.push_back({ Type::Google, "dns.google.com" });
|
||||
_attempts.push_back({ Type::Mozilla, "mozilla.cloudflare-dns.com" });
|
||||
_attempts.push_back({ Type::RemoteConfig, "firebaseremoteconfig" });
|
||||
if (!_timeDoneCallback) {
|
||||
_attempts.push_back({ Type::FireStore, "firestore" });
|
||||
for (const auto &domain : DnsDomains()) {
|
||||
_attempts.push_back({ Type::FireStore, domain, "firestore" });
|
||||
}
|
||||
}
|
||||
|
||||
shuffle(0, 2);
|
||||
if (!_timeDoneCallback) {
|
||||
shuffle(_attempts.size() - (int(DnsDomains().size()) + 1), _attempts.size());
|
||||
}
|
||||
if (isTestMode) {
|
||||
_attempts.erase(ranges::remove_if(_attempts, [](
|
||||
const Attempt &attempt) {
|
||||
return (attempt.type != Type::Google)
|
||||
&& (attempt.type != Type::Mozilla);
|
||||
}), _attempts.end());
|
||||
}
|
||||
ranges::reverse(_attempts); // We go from last to first.
|
||||
|
||||
sendNextRequest();
|
||||
}
|
||||
|
||||
SpecialConfigRequest::SpecialConfigRequest(
|
||||
Fn<void(
|
||||
DcId dcId,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret)> callback,
|
||||
bool isTestMode,
|
||||
const QString &domainString,
|
||||
const QString &phone)
|
||||
: SpecialConfigRequest(
|
||||
std::move(callback),
|
||||
nullptr,
|
||||
isTestMode,
|
||||
domainString,
|
||||
phone) {
|
||||
}
|
||||
|
||||
SpecialConfigRequest::SpecialConfigRequest(
|
||||
Fn<void()> timeDoneCallback,
|
||||
bool isTestMode,
|
||||
const QString &domainString)
|
||||
: SpecialConfigRequest(
|
||||
nullptr,
|
||||
std::move(timeDoneCallback),
|
||||
isTestMode,
|
||||
domainString,
|
||||
QString()) {
|
||||
}
|
||||
|
||||
void SpecialConfigRequest::sendNextRequest() {
|
||||
Expects(!_attempts.empty());
|
||||
|
||||
const auto attempt = _attempts.back();
|
||||
_attempts.pop_back();
|
||||
if (!_attempts.empty()) {
|
||||
base::call_delayed(kSendNextTimeout, this, [=] {
|
||||
sendNextRequest();
|
||||
});
|
||||
}
|
||||
performRequest(attempt);
|
||||
}
|
||||
|
||||
void SpecialConfigRequest::performRequest(const Attempt &attempt) {
|
||||
const auto type = attempt.type;
|
||||
auto url = QUrl();
|
||||
url.setScheme(u"https"_q);
|
||||
auto request = QNetworkRequest();
|
||||
auto payload = QByteArray();
|
||||
switch (type) {
|
||||
case Type::Mozilla: {
|
||||
url.setHost(attempt.data);
|
||||
url.setPath(u"/dns-query"_q);
|
||||
url.setQuery(u"name=%1&type=16&random_padding=%2"_q.arg(
|
||||
_domainString,
|
||||
GenerateDnsRandomPadding()));
|
||||
request.setRawHeader("accept", "application/dns-json");
|
||||
} break;
|
||||
case Type::Google: {
|
||||
url.setHost(attempt.data);
|
||||
url.setPath(u"/resolve"_q);
|
||||
url.setQuery(u"name=%1&type=ANY&random_padding=%2"_q.arg(
|
||||
_domainString,
|
||||
GenerateDnsRandomPadding()));
|
||||
if (!attempt.host.isEmpty()) {
|
||||
const auto host = attempt.host + ".google.com";
|
||||
request.setRawHeader("Host", host.toLatin1());
|
||||
}
|
||||
} break;
|
||||
case Type::RemoteConfig: {
|
||||
url.setHost(ApiDomain(attempt.data));
|
||||
url.setPath((u"/v1/projects/%1/namespaces/firebase:fetch"_q
|
||||
).arg(kRemoteProject));
|
||||
url.setQuery(u"key=%1"_q.arg(kApiKey));
|
||||
payload = u"{\"app_id\":\"%1\",\"app_instance_id\":\"%2\"}"_q.arg(
|
||||
kAppId,
|
||||
InstanceId()).toLatin1();
|
||||
request.setRawHeader("Content-Type", "application/json");
|
||||
} break;
|
||||
case Type::Realtime: {
|
||||
url.setHost(kFireProject + u".%1"_q.arg(attempt.data));
|
||||
url.setPath(u"/%1%2.json"_q.arg(kConfigKey, kConfigSubKey));
|
||||
} break;
|
||||
case Type::FireStore: {
|
||||
url.setHost(attempt.host.isEmpty()
|
||||
? ApiDomain(attempt.data)
|
||||
: attempt.data);
|
||||
url.setPath((u"/v1/projects/%1/databases/(default)/documents/%2/%3"_q
|
||||
).arg(
|
||||
kFireProject,
|
||||
kConfigKey,
|
||||
kConfigSubKey));
|
||||
if (!attempt.host.isEmpty()) {
|
||||
const auto host = ApiDomain(attempt.host);
|
||||
request.setRawHeader("Host", host.toLatin1());
|
||||
}
|
||||
} break;
|
||||
default: Unexpected("Type in SpecialConfigRequest::performRequest.");
|
||||
}
|
||||
request.setUrl(url);
|
||||
request.setRawHeader("User-Agent", DnsUserAgent());
|
||||
const auto reply = _requests.emplace_back(payload.isEmpty()
|
||||
? _manager.get(request)
|
||||
: _manager.post(request, payload)
|
||||
).reply;
|
||||
connect(reply, &QNetworkReply::finished, this, [=] {
|
||||
requestFinished(type, reply);
|
||||
});
|
||||
}
|
||||
|
||||
void SpecialConfigRequest::handleHeaderUnixtime(
|
||||
not_null<QNetworkReply*> reply) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
return;
|
||||
}
|
||||
const auto date = QString::fromLatin1([&] {
|
||||
for (const auto &pair : reply->rawHeaderPairs()) {
|
||||
if (pair.first == "Date") {
|
||||
return pair.second;
|
||||
}
|
||||
}
|
||||
return QByteArray();
|
||||
}());
|
||||
if (date.isEmpty()) {
|
||||
LOG(("Config Error: No 'Date' header received."));
|
||||
return;
|
||||
}
|
||||
const auto parsed = ParseHttpDate(date);
|
||||
if (!parsed.isValid()) {
|
||||
LOG(("Config Error: Bad 'Date' header received: %1").arg(date));
|
||||
return;
|
||||
}
|
||||
base::unixtime::http_update(parsed.toSecsSinceEpoch());
|
||||
if (_timeDoneCallback) {
|
||||
_timeDoneCallback();
|
||||
}
|
||||
}
|
||||
|
||||
void SpecialConfigRequest::requestFinished(
|
||||
Type type,
|
||||
not_null<QNetworkReply*> reply) {
|
||||
handleHeaderUnixtime(reply);
|
||||
const auto result = finalizeRequest(reply);
|
||||
if (!_callback || result.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case Type::Mozilla:
|
||||
case Type::Google: {
|
||||
constexpr auto kTypeRestriction = 16; // TXT
|
||||
handleResponse(ConcatenateDnsTxtFields(
|
||||
ParseDnsResponse(result, kTypeRestriction)));
|
||||
} break;
|
||||
case Type::RemoteConfig: {
|
||||
handleResponse(ParseRemoteConfigResponse(result));
|
||||
} break;
|
||||
case Type::Realtime: {
|
||||
handleResponse(ParseRealtimeResponse(result));
|
||||
} break;
|
||||
case Type::FireStore: {
|
||||
handleResponse(ParseFireStoreResponse(result));
|
||||
} break;
|
||||
default: Unexpected("Type in SpecialConfigRequest::requestFinished.");
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray SpecialConfigRequest::finalizeRequest(
|
||||
not_null<QNetworkReply*> reply) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
DEBUG_LOG(("Config Error: Failed to get response, error: %2 (%3)"
|
||||
).arg(reply->errorString()
|
||||
).arg(reply->error()));
|
||||
}
|
||||
const auto result = reply->readAll();
|
||||
const auto from = ranges::remove(
|
||||
_requests,
|
||||
reply,
|
||||
[](const ServiceWebRequest &request) { return request.reply; });
|
||||
_requests.erase(from, end(_requests));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool SpecialConfigRequest::decryptSimpleConfig(const QByteArray &bytes) {
|
||||
auto cleanBytes = bytes;
|
||||
auto removeFrom = std::remove_if(cleanBytes.begin(), cleanBytes.end(), [](char ch) {
|
||||
auto isGoodBase64 = (ch == '+') || (ch == '=') || (ch == '/')
|
||||
|| (ch >= 'a' && ch <= 'z')
|
||||
|| (ch >= 'A' && ch <= 'Z')
|
||||
|| (ch >= '0' && ch <= '9');
|
||||
return !isGoodBase64;
|
||||
});
|
||||
if (removeFrom != cleanBytes.end()) {
|
||||
cleanBytes.remove(removeFrom - cleanBytes.begin(), cleanBytes.end() - removeFrom);
|
||||
}
|
||||
|
||||
constexpr auto kGoodSizeBase64 = 344;
|
||||
if (cleanBytes.size() != kGoodSizeBase64) {
|
||||
LOG(("Config Error: Bad data size %1 required %2").arg(cleanBytes.size()).arg(kGoodSizeBase64));
|
||||
return false;
|
||||
}
|
||||
constexpr auto kGoodSizeData = 256;
|
||||
auto decodedBytes = QByteArray::fromBase64(cleanBytes, QByteArray::Base64Encoding);
|
||||
if (decodedBytes.size() != kGoodSizeData) {
|
||||
LOG(("Config Error: Bad data size %1 required %2").arg(decodedBytes.size()).arg(kGoodSizeData));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto publicKey = details::RSAPublicKey(bytes::make_span(kPublicKey));
|
||||
auto decrypted = publicKey.decrypt(bytes::make_span(decodedBytes));
|
||||
auto decryptedBytes = gsl::make_span(decrypted);
|
||||
|
||||
auto aesEncryptedBytes = decryptedBytes.subspan(CTRState::KeySize);
|
||||
auto aesivec = bytes::make_vector(decryptedBytes.subspan(CTRState::KeySize - CTRState::IvecSize, CTRState::IvecSize));
|
||||
AES_KEY aeskey;
|
||||
AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(decryptedBytes.data()), CTRState::KeySize * CHAR_BIT, &aeskey);
|
||||
AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(aesEncryptedBytes.data()), reinterpret_cast<unsigned char*>(aesEncryptedBytes.data()), aesEncryptedBytes.size(), &aeskey, reinterpret_cast<unsigned char*>(aesivec.data()), AES_DECRYPT);
|
||||
|
||||
constexpr auto kDigestSize = 16;
|
||||
auto dataSize = aesEncryptedBytes.size() - kDigestSize;
|
||||
auto data = aesEncryptedBytes.subspan(0, dataSize);
|
||||
auto hash = openssl::Sha256(data);
|
||||
if (bytes::compare(gsl::make_span(hash).subspan(0, kDigestSize), aesEncryptedBytes.subspan(dataSize)) != 0) {
|
||||
LOG(("Config Error: Bad digest."));
|
||||
return false;
|
||||
}
|
||||
|
||||
mtpBuffer buffer;
|
||||
buffer.resize(data.size() / sizeof(mtpPrime));
|
||||
bytes::copy(bytes::make_span(buffer), data);
|
||||
auto from = &*buffer.cbegin();
|
||||
auto end = from + buffer.size();
|
||||
auto realLength = *from++;
|
||||
if (realLength <= 0 || realLength > dataSize || (realLength & 0x03)) {
|
||||
LOG(("Config Error: Bad length %1.").arg(realLength));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_simpleConfig.read(from, end)) {
|
||||
LOG(("Config Error: Could not read configSimple."));
|
||||
return false;
|
||||
}
|
||||
if ((end - from) * sizeof(mtpPrime) != (dataSize - realLength)) {
|
||||
LOG(("Config Error: Bad read length %1, should be %2.").arg((end - from) * sizeof(mtpPrime)).arg(dataSize - realLength));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SpecialConfigRequest::handleResponse(const QByteArray &bytes) {
|
||||
if (!decryptSimpleConfig(bytes)) {
|
||||
return;
|
||||
}
|
||||
Assert(_simpleConfig.type() == mtpc_help_configSimple);
|
||||
const auto &config = _simpleConfig.c_help_configSimple();
|
||||
const auto now = base::unixtime::http_now();
|
||||
if (now > config.vexpires().v) {
|
||||
LOG(("Config Error: "
|
||||
"Bad date frame for simple config: %1-%2, our time is %3."
|
||||
).arg(config.vdate().v
|
||||
).arg(config.vexpires().v
|
||||
).arg(now));
|
||||
return;
|
||||
}
|
||||
if (config.vrules().v.empty()) {
|
||||
LOG(("Config Error: Empty simple config received."));
|
||||
return;
|
||||
}
|
||||
for (const auto &rule : config.vrules().v) {
|
||||
Assert(rule.type() == mtpc_accessPointRule);
|
||||
const auto &data = rule.c_accessPointRule();
|
||||
const auto phoneRules = qs(data.vphone_prefix_rules());
|
||||
if (!CheckPhoneByPrefixesRules(_phone, phoneRules)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto dcId = data.vdc_id().v;
|
||||
for (const auto &address : data.vips().v) {
|
||||
const auto parseIp = [](const MTPint &ipv4) {
|
||||
const auto ip = *reinterpret_cast<const uint32*>(&ipv4.v);
|
||||
return (u"%1.%2.%3.%4"_q
|
||||
).arg((ip >> 24) & 0xFF
|
||||
).arg((ip >> 16) & 0xFF
|
||||
).arg((ip >> 8) & 0xFF
|
||||
).arg(ip & 0xFF).toStdString();
|
||||
};
|
||||
switch (address.type()) {
|
||||
case mtpc_ipPort: {
|
||||
const auto &fields = address.c_ipPort();
|
||||
const auto ip = parseIp(fields.vipv4());
|
||||
if (!ip.empty()) {
|
||||
_callback(dcId, ip, fields.vport().v, {});
|
||||
}
|
||||
} break;
|
||||
case mtpc_ipPortSecret: {
|
||||
const auto &fields = address.c_ipPortSecret();
|
||||
const auto ip = parseIp(fields.vipv4());
|
||||
if (!ip.empty()) {
|
||||
_callback(
|
||||
dcId,
|
||||
ip,
|
||||
fields.vport().v,
|
||||
bytes::make_span(fields.vsecret().v));
|
||||
}
|
||||
} break;
|
||||
default: Unexpected("Type in simpleConfig ips.");
|
||||
}
|
||||
}
|
||||
}
|
||||
_callback(0, std::string(), 0, {});
|
||||
}
|
||||
|
||||
} // namespace MTP::details
|
||||
85
Telegram/SourceFiles/mtproto/special_config_request.h
Normal file
85
Telegram/SourceFiles/mtproto/special_config_request.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/details/mtproto_domain_resolver.h"
|
||||
#include "base/bytes.h"
|
||||
#include "base/weak_ptr.h"
|
||||
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
|
||||
namespace MTP::details {
|
||||
|
||||
class SpecialConfigRequest : public QObject {
|
||||
public:
|
||||
SpecialConfigRequest(
|
||||
Fn<void(
|
||||
DcId dcId,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret)> callback,
|
||||
bool isTestMode,
|
||||
const QString &domainString,
|
||||
const QString &phone);
|
||||
SpecialConfigRequest(
|
||||
Fn<void()> timeDoneCallback,
|
||||
bool isTestMode,
|
||||
const QString &domainString);
|
||||
|
||||
private:
|
||||
enum class Type {
|
||||
Mozilla,
|
||||
Google,
|
||||
RemoteConfig,
|
||||
Realtime,
|
||||
FireStore,
|
||||
};
|
||||
struct Attempt {
|
||||
Type type;
|
||||
QString data;
|
||||
QString host;
|
||||
};
|
||||
|
||||
SpecialConfigRequest(
|
||||
Fn<void(
|
||||
DcId dcId,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret)> callback,
|
||||
Fn<void()> timeDoneCallback,
|
||||
bool isTestMode,
|
||||
const QString &domainString,
|
||||
const QString &phone);
|
||||
|
||||
void sendNextRequest();
|
||||
void performRequest(const Attempt &attempt);
|
||||
void requestFinished(Type type, not_null<QNetworkReply*> reply);
|
||||
void handleHeaderUnixtime(not_null<QNetworkReply*> reply);
|
||||
QByteArray finalizeRequest(not_null<QNetworkReply*> reply);
|
||||
void handleResponse(const QByteArray &bytes);
|
||||
bool decryptSimpleConfig(const QByteArray &bytes);
|
||||
|
||||
Fn<void(
|
||||
DcId dcId,
|
||||
const std::string &ip,
|
||||
int port,
|
||||
bytes::const_span secret)> _callback;
|
||||
Fn<void()> _timeDoneCallback;
|
||||
QString _domainString;
|
||||
QString _phone;
|
||||
MTPhelp_ConfigSimple _simpleConfig;
|
||||
|
||||
QNetworkAccessManager _manager;
|
||||
std::vector<Attempt> _attempts;
|
||||
std::vector<ServiceWebRequest> _requests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace MTP::details
|
||||
19
Telegram/SourceFiles/mtproto/type_utils.h
Normal file
19
Telegram/SourceFiles/mtproto/type_utils.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
inline MTPbool MTP_bool(bool v) {
|
||||
return v ? MTP_boolTrue() : MTP_boolFalse();
|
||||
}
|
||||
|
||||
inline bool mtpIsTrue(const MTPBool &v) {
|
||||
return v.type() == mtpc_boolTrue;
|
||||
}
|
||||
inline bool mtpIsFalse(const MTPBool &v) {
|
||||
return !mtpIsTrue(v);
|
||||
}
|
||||
Reference in New Issue
Block a user