| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668 |
- /*
- 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::start_with_next([=] {
- socketConnected();
- }, _connectedLifetime);
- _socket->disconnected(
- ) | rpl::start_with_next([=] {
- socketDisconnected();
- }, _lifetime);
- _socket->readyRead(
- ) | rpl::start_with_next([=] {
- socketRead();
- }, _lifetime);
- _socket->error(
- ) | rpl::start_with_next([=] {
- socketError();
- }, _lifetime);
- _socket->syncTimeRequests(
- ) | rpl::start_with_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
|