tdesktop/Telegram/SourceFiles/mtproto/connection_auto.cpp
John Preston 8e89486fbc Error handling changed, 'auto' keyword used for MTP types.
All errors that lead to MTP request resending by default
error handler now can be handled differently. For example
inline bot requests are not being resent on 5XX error codes.
+ extensive use of auto keyword in MTP types handling.
2016-04-08 14:44:35 +04:00

345 lines
11 KiB
C++

/*
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
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
*/
#include "stdafx.h"
#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));
#endif
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));
#endif
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()));
emit error();
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();
}
void AutoConnection::connectTcp(const QString &addr, int32 port, MTPDdcOption::Flags flags) {
_addrTcp = addr;
_portTcp = port;
_flagsTcp = flags;
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
sock.connectToHost(QHostAddress(_addrTcp), _portTcp);
}
void AutoConnection::connectHttp(const QString &addr, int32 port, MTPDdcOption::Flags flags) {
address = QUrl(((flags & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80));//not p - always 80 port for http transport
TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
_addrHttp = addr;
_portHttp = port;
_flagsHttp = flags;
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 {
emit error();
}
} else if (!data.isEmpty()) {
if (status == UsingHttp) {
receivedQueue.push_back(data);
emit receivedData();
} else if (status == WaitingBoth || status == WaitingHttp) {
try {
auto res_pq = readPQFakeReply(data);
const auto &res_pq_data(res_pq.c_resPQ());
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 {
emit error();
}
}
} else if (status == UsingTcp) {
DEBUG_LOG(("Connection Info: already using tcp, ignoring http response"));
}
}
} else {
if (!requests.remove(reply)) {
return;
}
bool mayBeBadKey = HTTPConnection::handleError(reply) && _sentEncrypted;
if (status == WaitingBoth) {
status = WaitingTcp;
} else if (status == WaitingHttp || status == UsingHttp) {
emit error(mayBeBadKey);
} else {
LOG(("Strange Http Error: status %1").arg(status));
}
}
}
void AutoConnection::socketPacket(const char *packet, uint32 length) {
if (status == FinishedWork) return;
mtpBuffer data = AbstractTCPConnection::handleResponse(packet, length);
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) {
bool mayBeBadKey = (data[0] == -410) && _sentEncrypted;
emit error(mayBeBadKey);
} else {
LOG(("Strange Tcp Error; status %1").arg(status));
}
} else if (status == UsingTcp) {
receivedQueue.push_back(data);
emit receivedData();
} else if (status == WaitingBoth || status == WaitingTcp || status == HttpReady) {
tcpTimeoutTimer.stop();
try {
auto res_pq = readPQFakeReply(data);
const auto &res_pq_data(res_pq.c_resPQ());
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 {
emit error();
}
}
}
}
bool AutoConnection::usingHttpWait() {
return (status == UsingHttp);
}
bool AutoConnection::needHttpWait() {
return (status == UsingHttp) ? requests.isEmpty() : false;
}
int32 AutoConnection::debugState() const {
return (status == UsingHttp) ? -1 : (UsingTcp ? sock.state() : -777);
}
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;
AbstractTCPConnection::handleError(e, sock);
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) {
emit error();
} else {
LOG(("Strange Tcp Error: status %1").arg(status));
}
}
} // namespace internal
} // namespace MTP