2016-03-24 12:57:10 +00:00
|
|
|
/*
|
|
|
|
This file is part of Telegram Desktop,
|
|
|
|
the official desktop version of Telegram messaging app, see https://telegram.org
|
|
|
|
|
|
|
|
Telegram Desktop is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
It is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
In addition, as a special exception, the copyright holders give permission
|
|
|
|
to link the code of portions of this program with the OpenSSL library.
|
|
|
|
|
|
|
|
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
2017-01-11 18:31:31 +00:00
|
|
|
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
2016-03-24 12:57:10 +00:00
|
|
|
*/
|
|
|
|
#include "mtproto/connection_auto.h"
|
|
|
|
|
|
|
|
#include "mtproto/connection_http.h"
|
|
|
|
|
|
|
|
namespace MTP {
|
|
|
|
namespace internal {
|
|
|
|
|
|
|
|
AutoConnection::AutoConnection(QThread *thread) : AbstractTCPConnection(thread)
|
|
|
|
, status(WaitingBoth)
|
|
|
|
, tcpNonce(rand_value<MTPint128>())
|
|
|
|
, httpNonce(rand_value<MTPint128>())
|
|
|
|
, _flagsTcp(0)
|
|
|
|
, _flagsHttp(0)
|
|
|
|
, _tcpTimeout(MTPMinReceiveDelay) {
|
|
|
|
manager.moveToThread(thread);
|
|
|
|
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
|
|
|
manager.setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy));
|
2016-10-18 07:56:38 +00:00
|
|
|
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
|
2016-03-24 12:57:10 +00:00
|
|
|
|
|
|
|
httpStartTimer.moveToThread(thread);
|
|
|
|
httpStartTimer.setSingleShot(true);
|
|
|
|
connect(&httpStartTimer, SIGNAL(timeout()), this, SLOT(onHttpStart()));
|
|
|
|
|
|
|
|
tcpTimeoutTimer.moveToThread(thread);
|
|
|
|
tcpTimeoutTimer.setSingleShot(true);
|
|
|
|
connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer()));
|
|
|
|
|
|
|
|
sock.moveToThread(thread);
|
|
|
|
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
|
|
|
sock.setProxy(QNetworkProxy(QNetworkProxy::NoProxy));
|
2016-10-18 07:56:38 +00:00
|
|
|
#endif // !TDESKTOP_DISABLE_NETWORK_PROXY
|
2016-03-24 12:57:10 +00:00
|
|
|
connect(&sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
|
|
|
|
connect(&sock, SIGNAL(connected()), this, SLOT(onSocketConnected()));
|
|
|
|
connect(&sock, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::onHttpStart() {
|
|
|
|
if (status == HttpReady) {
|
|
|
|
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by timer").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
status = UsingHttp;
|
|
|
|
sock.disconnectFromHost();
|
|
|
|
emit connected();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::onSocketConnected() {
|
|
|
|
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
|
|
|
|
mtpBuffer buffer(preparePQFake(tcpNonce));
|
|
|
|
|
|
|
|
DEBUG_LOG(("Connection Info: sending fake req_pq through TCP/%1 transport").arg((_flagsTcp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
|
|
|
|
if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout;
|
|
|
|
tcpTimeoutTimer.start(_tcpTimeout);
|
|
|
|
|
|
|
|
tcpSend(buffer);
|
|
|
|
} else if (status == WaitingHttp || status == UsingHttp) {
|
|
|
|
sock.disconnectFromHost();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::onTcpTimeoutTimer() {
|
|
|
|
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
|
|
|
|
if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2;
|
|
|
|
_tcpTimeout = -_tcpTimeout;
|
|
|
|
|
|
|
|
QAbstractSocket::SocketState state = sock.state();
|
|
|
|
if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) {
|
|
|
|
sock.disconnectFromHost();
|
|
|
|
} else if (state != QAbstractSocket::ClosingState) {
|
|
|
|
sock.connectToHost(QHostAddress(_addrTcp), _portTcp);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::onSocketDisconnected() {
|
|
|
|
if (_tcpTimeout < 0) {
|
|
|
|
_tcpTimeout = -_tcpTimeout;
|
|
|
|
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
|
|
|
|
sock.connectToHost(QHostAddress(_addrTcp), _portTcp);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (status == WaitingBoth) {
|
|
|
|
status = WaitingHttp;
|
|
|
|
} else if (status == WaitingTcp || status == UsingTcp) {
|
|
|
|
emit disconnected();
|
|
|
|
} else if (status == HttpReady) {
|
|
|
|
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by socket disconnect").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
status = UsingHttp;
|
|
|
|
emit connected();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::sendData(mtpBuffer &buffer) {
|
|
|
|
if (status == FinishedWork) return;
|
|
|
|
|
|
|
|
if (buffer.size() < 3) {
|
|
|
|
LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime)));
|
|
|
|
TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str()));
|
2017-02-25 16:44:02 +00:00
|
|
|
emit error(kErrorCodeOther);
|
2016-03-24 12:57:10 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status == UsingTcp) {
|
|
|
|
tcpSend(buffer);
|
|
|
|
} else {
|
|
|
|
httpSend(buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::httpSend(mtpBuffer &buffer) {
|
|
|
|
int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime);
|
|
|
|
|
|
|
|
QNetworkRequest request(address);
|
|
|
|
request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize));
|
|
|
|
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded")));
|
|
|
|
|
|
|
|
TCP_LOG(("HTTP Info: sending %1 len request").arg(requestSize));
|
|
|
|
requests.insert(manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize)));
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::disconnectFromServer() {
|
|
|
|
if (status == FinishedWork) return;
|
|
|
|
status = FinishedWork;
|
|
|
|
|
|
|
|
Requests copy = requests;
|
|
|
|
requests.clear();
|
|
|
|
for (Requests::const_iterator i = copy.cbegin(), e = copy.cend(); i != e; ++i) {
|
|
|
|
(*i)->abort();
|
|
|
|
(*i)->deleteLater();
|
|
|
|
}
|
|
|
|
|
|
|
|
disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
|
|
|
|
|
|
|
|
address = QUrl();
|
|
|
|
|
|
|
|
disconnect(&sock, SIGNAL(readyRead()), 0, 0);
|
|
|
|
sock.close();
|
|
|
|
|
|
|
|
httpStartTimer.stop();
|
|
|
|
}
|
|
|
|
|
2017-02-23 06:57:04 +00:00
|
|
|
void AutoConnection::connectTcp(const DcOptions::Endpoint &endpoint) {
|
|
|
|
_addrTcp = QString::fromStdString(endpoint.ip);
|
|
|
|
_portTcp = endpoint.port;
|
|
|
|
_flagsTcp = endpoint.flags;
|
2016-03-24 12:57:10 +00:00
|
|
|
|
|
|
|
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
|
|
|
|
sock.connectToHost(QHostAddress(_addrTcp), _portTcp);
|
|
|
|
}
|
|
|
|
|
2017-02-23 06:57:04 +00:00
|
|
|
void AutoConnection::connectHttp(const DcOptions::Endpoint &endpoint) {
|
|
|
|
_addrHttp = QString::fromStdString(endpoint.ip);
|
|
|
|
_portHttp = endpoint.port;
|
|
|
|
_flagsHttp = endpoint.flags;
|
|
|
|
|
|
|
|
// not endpoint.port - always 80 port for http transport
|
|
|
|
address = QUrl(((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(_addrHttp).arg(80));
|
2016-03-24 12:57:10 +00:00
|
|
|
TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
|
|
|
|
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
|
|
|
|
|
|
|
|
mtpBuffer buffer(preparePQFake(httpNonce));
|
|
|
|
|
|
|
|
DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP/%1 transport").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
|
|
|
|
httpSend(buffer);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AutoConnection::isConnected() const {
|
|
|
|
return (status == UsingTcp) || (status == UsingHttp);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::requestFinished(QNetworkReply *reply) {
|
|
|
|
if (status == FinishedWork) return;
|
|
|
|
|
|
|
|
reply->deleteLater();
|
|
|
|
if (reply->error() == QNetworkReply::NoError) {
|
|
|
|
requests.remove(reply);
|
|
|
|
|
|
|
|
mtpBuffer data = HTTPConnection::handleResponse(reply);
|
|
|
|
if (data.size() == 1) {
|
|
|
|
if (status == WaitingBoth) {
|
|
|
|
status = WaitingTcp;
|
|
|
|
} else {
|
2017-02-25 16:44:02 +00:00
|
|
|
emit error(data[0]);
|
2016-03-24 12:57:10 +00:00
|
|
|
}
|
|
|
|
} else if (!data.isEmpty()) {
|
|
|
|
if (status == UsingHttp) {
|
2017-02-27 09:51:03 +00:00
|
|
|
_receivedQueue.push_back(data);
|
2016-03-24 12:57:10 +00:00
|
|
|
emit receivedData();
|
|
|
|
} else if (status == WaitingBoth || status == WaitingHttp) {
|
|
|
|
try {
|
2016-04-08 10:44:35 +00:00
|
|
|
auto res_pq = readPQFakeReply(data);
|
|
|
|
const auto &res_pq_data(res_pq.c_resPQ());
|
2016-03-24 12:57:10 +00:00
|
|
|
if (res_pq_data.vnonce == httpNonce) {
|
|
|
|
if (status == WaitingBoth) {
|
|
|
|
status = HttpReady;
|
|
|
|
httpStartTimer.start(MTPTcpConnectionWaitTimeout);
|
|
|
|
} else {
|
|
|
|
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by pq-response, awaited").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
status = UsingHttp;
|
|
|
|
sock.disconnectFromHost();
|
|
|
|
emit connected();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Exception &e) {
|
|
|
|
DEBUG_LOG(("Connection Error: exception in parsing HTTP fake pq-responce, %1").arg(e.what()));
|
|
|
|
if (status == WaitingBoth) {
|
|
|
|
status = WaitingTcp;
|
|
|
|
} else {
|
2017-02-25 16:44:02 +00:00
|
|
|
emit error(kErrorCodeOther);
|
2016-03-24 12:57:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (status == UsingTcp) {
|
|
|
|
DEBUG_LOG(("Connection Info: already using tcp, ignoring http response"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (!requests.remove(reply)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status == WaitingBoth) {
|
|
|
|
status = WaitingTcp;
|
|
|
|
} else if (status == WaitingHttp || status == UsingHttp) {
|
2017-02-25 16:44:02 +00:00
|
|
|
emit error(HTTPConnection::handleError(reply));
|
2016-03-24 12:57:10 +00:00
|
|
|
} else {
|
|
|
|
LOG(("Strange Http Error: status %1").arg(status));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::socketPacket(const char *packet, uint32 length) {
|
|
|
|
if (status == FinishedWork) return;
|
|
|
|
|
2016-03-24 18:12:30 +00:00
|
|
|
mtpBuffer data = AbstractTCPConnection::handleResponse(packet, length);
|
2016-03-24 12:57:10 +00:00
|
|
|
if (data.size() == 1) {
|
|
|
|
if (status == WaitingBoth) {
|
|
|
|
status = WaitingHttp;
|
|
|
|
sock.disconnectFromHost();
|
|
|
|
} else if (status == HttpReady) {
|
|
|
|
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by bad tcp response, ready").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
status = UsingHttp;
|
|
|
|
sock.disconnectFromHost();
|
|
|
|
emit connected();
|
|
|
|
} else if (status == WaitingTcp || status == UsingTcp) {
|
2017-02-25 16:44:02 +00:00
|
|
|
emit error(data[0]);
|
2016-03-24 12:57:10 +00:00
|
|
|
} else {
|
|
|
|
LOG(("Strange Tcp Error; status %1").arg(status));
|
|
|
|
}
|
|
|
|
} else if (status == UsingTcp) {
|
2017-02-27 09:51:03 +00:00
|
|
|
_receivedQueue.push_back(data);
|
2016-03-24 12:57:10 +00:00
|
|
|
emit receivedData();
|
|
|
|
} else if (status == WaitingBoth || status == WaitingTcp || status == HttpReady) {
|
|
|
|
tcpTimeoutTimer.stop();
|
|
|
|
try {
|
2016-04-08 10:44:35 +00:00
|
|
|
auto res_pq = readPQFakeReply(data);
|
|
|
|
const auto &res_pq_data(res_pq.c_resPQ());
|
2016-03-24 12:57:10 +00:00
|
|
|
if (res_pq_data.vnonce == tcpNonce) {
|
|
|
|
DEBUG_LOG(("Connection Info: TCP/%1-transport chosen by pq-response").arg((_flagsTcp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
status = UsingTcp;
|
|
|
|
emit connected();
|
|
|
|
}
|
|
|
|
} catch (Exception &e) {
|
|
|
|
DEBUG_LOG(("Connection Error: exception in parsing TCP fake pq-responce, %1").arg(e.what()));
|
|
|
|
if (status == WaitingBoth) {
|
|
|
|
status = WaitingHttp;
|
|
|
|
sock.disconnectFromHost();
|
|
|
|
} else if (status == HttpReady) {
|
|
|
|
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by bad tcp response, awaited").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
status = UsingHttp;
|
|
|
|
sock.disconnectFromHost();
|
|
|
|
emit connected();
|
|
|
|
} else {
|
2017-02-25 16:44:02 +00:00
|
|
|
emit error(kErrorCodeOther);
|
2016-03-24 12:57:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AutoConnection::usingHttpWait() {
|
|
|
|
return (status == UsingHttp);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool AutoConnection::needHttpWait() {
|
|
|
|
return (status == UsingHttp) ? requests.isEmpty() : false;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32 AutoConnection::debugState() const {
|
2017-03-30 15:20:51 +00:00
|
|
|
return (status == UsingHttp) ? -1 : ((status == UsingTcp) ? sock.state() : -777);
|
2016-03-24 12:57:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QString AutoConnection::transport() const {
|
|
|
|
if (status == UsingTcp) {
|
|
|
|
return qsl("TCP");
|
|
|
|
} else if (status == UsingHttp) {
|
|
|
|
return qsl("HTTP");
|
|
|
|
} else {
|
|
|
|
return QString();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AutoConnection::socketError(QAbstractSocket::SocketError e) {
|
|
|
|
if (status == FinishedWork) return;
|
|
|
|
|
2016-03-24 18:12:30 +00:00
|
|
|
AbstractTCPConnection::handleError(e, sock);
|
2016-03-24 12:57:10 +00:00
|
|
|
if (status == WaitingBoth) {
|
|
|
|
status = WaitingHttp;
|
|
|
|
} else if (status == HttpReady) {
|
|
|
|
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by tcp error, ready").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
|
|
|
status = UsingHttp;
|
|
|
|
emit connected();
|
|
|
|
} else if (status == WaitingTcp || status == UsingTcp) {
|
2017-02-25 16:44:02 +00:00
|
|
|
emit error(kErrorCodeOther);
|
2016-03-24 12:57:10 +00:00
|
|
|
} else {
|
|
|
|
LOG(("Strange Tcp Error: status %1").arg(status));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace internal
|
|
|
|
} // namespace MTP
|