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.
		
			
				
	
	
		
			344 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			344 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
 |