/* 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-2017 John Preston, https://desktop.telegram.org */ #pragma once #include "mtproto/dcenter.h" #include "core/single_timer.h" namespace MTP { class Instance; namespace internal { class Connection; class ReceivedMsgIds { public: bool registerMsgId(mtpMsgId msgId, bool needAck) { auto i = _idsNeedAck.constFind(msgId); if (i == _idsNeedAck.cend()) { if (_idsNeedAck.size() < MTPIdsBufferSize || msgId > min()) { _idsNeedAck.insert(msgId, needAck); return true; } MTP_LOG(-1, ("No need to handle - %1 < min = %2").arg(msgId).arg(min())); } else { MTP_LOG(-1, ("No need to handle - %1 already is in map").arg(msgId)); } return false; } mtpMsgId min() const { return _idsNeedAck.isEmpty() ? 0 : _idsNeedAck.cbegin().key(); } mtpMsgId max() const { auto end = _idsNeedAck.cend(); return _idsNeedAck.isEmpty() ? 0 : (--end).key(); } void shrink() { auto size = _idsNeedAck.size(); while (size-- > MTPIdsBufferSize) { _idsNeedAck.erase(_idsNeedAck.begin()); } } enum class State { NotFound, NeedsAck, NoAckNeeded, }; State lookup(mtpMsgId msgId) const { auto i = _idsNeedAck.constFind(msgId); if (i == _idsNeedAck.cend()) { return State::NotFound; } return i.value() ? State::NeedsAck : State::NoAckNeeded; } void clear() { _idsNeedAck.clear(); } private: QMap _idsNeedAck; }; class Session; class SessionData { public: SessionData(Session *creator) : _owner(creator) { } void setSession(uint64 session) { DEBUG_LOG(("MTP Info: setting server_session: %1").arg(session)); QWriteLocker locker(&lock); if (_session != session) { _session = session; _messagesSent = 0; } } uint64 getSession() const { QReadLocker locker(&lock); return _session; } bool layerWasInited() const { QReadLocker locker(&lock); return _layerInited; } void setLayerWasInited(bool was) { QWriteLocker locker(&lock); _layerInited = was; } void setSalt(uint64 salt) { QWriteLocker locker(&lock); _salt = salt; } uint64 getSalt() const { QReadLocker locker(&lock); return _salt; } const AuthKeyPtr &getKey() const { return _authKey; } void setKey(const AuthKeyPtr &key) { if (_authKey != key) { uint64 session = rand_value(); _authKey = key; DEBUG_LOG(("MTP Info: new auth key set in SessionData, id %1, setting random server_session %2").arg(key ? key->keyId() : 0).arg(session)); QWriteLocker locker(&lock); if (_session != session) { _session = session; _messagesSent = 0; } _layerInited = false; } } bool isCheckedKey() const { QReadLocker locker(&lock); return _keyChecked; } void setCheckedKey(bool checked) { QWriteLocker locker(&lock); _keyChecked = checked; } QReadWriteLock *keyMutex() const; QReadWriteLock *toSendMutex() const { return &toSendLock; } QReadWriteLock *haveSentMutex() const { return &haveSentLock; } QReadWriteLock *toResendMutex() const { return &toResendLock; } QReadWriteLock *wereAckedMutex() const { return &wereAckedLock; } QReadWriteLock *receivedIdsMutex() const { return &receivedIdsLock; } QReadWriteLock *haveReceivedMutex() const { return &haveReceivedLock; } QReadWriteLock *stateRequestMutex() const { return &stateRequestLock; } mtpPreRequestMap &toSendMap() { return toSend; } const mtpPreRequestMap &toSendMap() const { return toSend; } mtpRequestMap &haveSentMap() { return haveSent; } const mtpRequestMap &haveSentMap() const { return haveSent; } mtpRequestIdsMap &toResendMap() { // msgId -> requestId, on which toSend: requestId -> request for resended requests return toResend; } const mtpRequestIdsMap &toResendMap() const { return toResend; } ReceivedMsgIds &receivedIdsSet() { return receivedIds; } const ReceivedMsgIds &receivedIdsSet() const { return receivedIds; } mtpRequestIdsMap &wereAckedMap() { return wereAcked; } const mtpRequestIdsMap &wereAckedMap() const { return wereAcked; } mtpResponseMap &haveReceivedMap() { return haveReceived; } const mtpResponseMap &haveReceivedMap() const { return haveReceived; } mtpMsgIdsSet &stateRequestMap() { return stateRequest; } const mtpMsgIdsSet &stateRequestMap() const { return stateRequest; } mtpRequestId nextFakeRequestId() { // must be locked by haveReceivedMutex() if (haveReceived.isEmpty() || haveReceived.cbegin().key() > 0) { _fakeRequestId = -2000000000; } else { ++_fakeRequestId; } return _fakeRequestId; } Session *owner() { return _owner; } const Session *owner() const { return _owner; } uint32 nextRequestSeqNumber(bool needAck = true) { QWriteLocker locker(&lock); uint32 result(_messagesSent); _messagesSent += (needAck ? 1 : 0); return result * 2 + (needAck ? 1 : 0); } void clear(Instance *instance); private: uint64 _session = 0; uint64 _salt = 0; uint32 _messagesSent = 0; mtpRequestId _fakeRequestId = -2000000000; Session *_owner = nullptr; AuthKeyPtr _authKey; bool _keyChecked = false; bool _layerInited = false; mtpPreRequestMap toSend; // map of request_id -> request, that is waiting to be sent mtpRequestMap haveSent; // map of msg_id -> request, that was sent, msDate = 0 for msgs_state_req (no resend / state req), msDate = 0, seqNo = 0 for containers mtpRequestIdsMap toResend; // map of msg_id -> request_id, that request_id -> request lies in toSend and is waiting to be resent ReceivedMsgIds receivedIds; // set of received msg_id's, for checking new msg_ids mtpRequestIdsMap wereAcked; // map of msg_id -> request_id, this msg_ids already were acked or do not need ack mtpResponseMap haveReceived; // map of request_id -> response, that should be processed in other thread mtpMsgIdsSet stateRequest; // set of msg_id's, whose state should be requested // mutexes mutable QReadWriteLock lock; mutable QReadWriteLock toSendLock; mutable QReadWriteLock haveSentLock; mutable QReadWriteLock toResendLock; mutable QReadWriteLock receivedIdsLock; mutable QReadWriteLock wereAckedLock; mutable QReadWriteLock haveReceivedLock; mutable QReadWriteLock stateRequestLock; }; class Session : public QObject { Q_OBJECT public: Session(Instance *instance, ShiftedDcId shiftedDcId); void start(); void restart(); void stop(); void kill(); void unpaused(); ShiftedDcId getDcWithShift() const; QReadWriteLock *keyMutex() const; void notifyKeyCreated(AuthKeyPtr &&key); void destroyKey(); void notifyLayerInited(bool wasInited); template mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), TimeMs msCanWait = 0, bool needsLayer = false, bool toMainDC = false, mtpRequestId after = 0); // send mtp request void ping(); void cancel(mtpRequestId requestId, mtpMsgId msgId); int32 requestState(mtpRequestId requestId) const; int32 getState() const; QString transport() const; void sendPrepared(const mtpRequest &request, TimeMs msCanWait = 0, bool newRequest = true); // nulls msgId and seqNo in request, if newRequest = true ~Session(); signals: void authKeyCreated(); void needToSend(); void needToPing(); void needToRestart(); public slots: void needToResumeAndSend(); mtpRequestId resend(quint64 msgId, qint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false); void resendMany(QVector msgIds, qint64 msCanWait, bool forceContainer, bool sendMsgStateInfo); void resendAll(); // after connection restart void authKeyCreatedForDC(); void layerWasInitedForDC(bool wasInited); void tryToReceive(); void checkRequestsByTimer(); void onConnectionStateChange(qint32 newState); void onResetDone(); void sendAnything(qint64 msCanWait = 0); void sendPong(quint64 msgId, quint64 pingId); void sendMsgsStateInfo(quint64 msgId, QByteArray data); private: void createDcData(); void registerRequest(mtpRequestId requestId, ShiftedDcId dcWithShift); mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser); mtpRequest getRequest(mtpRequestId requestId); bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err); Instance *_instance; std::unique_ptr _connection; bool _killed = false; bool _needToReceive = false; SessionData data; ShiftedDcId dcWithShift = 0; DcenterPtr dc; TimeMs msSendCall = 0; TimeMs msWait = 0; bool _ping = false; QTimer timeouter; SingleTimer sender; }; inline QReadWriteLock *SessionData::keyMutex() const { return _owner->keyMutex(); } MTPrpcError rpcClientError(const QString &type, const QString &description = QString()); } // namespace internal } // namespace MTP