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

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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/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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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
*/
#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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View 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

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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"

View 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

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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;

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

View 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

View 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

View 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

View 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);
}