/* 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 "storage/file_download_mtproto.h" #include "data/data_document.h" #include "data/data_file_origin.h" #include "storage/cache/storage_cache_types.h" #include "main/main_session.h" #include "apiwrap.h" #include "mtproto/mtp_instance.h" #include "mtproto/mtproto_auth_key.h" #include "base/openssl_help.h" #include "facades.h" mtpFileLoader::mtpFileLoader( const StorageFileLocation &location, Data::FileOrigin origin, LocationType type, const QString &to, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading, uint8 cacheTag) : FileLoader( to, size, type, toCache, fromCloud, autoLoading, cacheTag) , _downloader(&session().downloader()) , _dcId(location.dcId()) , _location(location) , _origin(origin) { } mtpFileLoader::mtpFileLoader( const WebFileLocation &location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading, uint8 cacheTag) : FileLoader( QString(), size, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading, cacheTag) , _downloader(&session().downloader()) , _dcId(Global::WebFileDcId()) , _location(location) { } mtpFileLoader::mtpFileLoader( const GeoPointLocation &location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading, uint8 cacheTag) : FileLoader( QString(), size, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading, cacheTag) , _downloader(&session().downloader()) , _dcId(Global::WebFileDcId()) , _location(location) { } mtpFileLoader::~mtpFileLoader() { cancelRequests(); _downloader->remove(this); } Data::FileOrigin mtpFileLoader::fileOrigin() const { return _origin; } uint64 mtpFileLoader::objId() const { if (const auto storage = base::get_if(&_location)) { return storage->objectId(); } return 0; } void mtpFileLoader::refreshFileReferenceFrom( const Data::UpdatedFileReferences &updates, int requestId, const QByteArray ¤t) { if (const auto storage = base::get_if(&_location)) { storage->refreshFileReference(updates); if (storage->fileReference() == current) { cancel(true); return; } } else { cancel(true); return; } makeRequest(finishSentRequest(requestId)); } MTP::DcId mtpFileLoader::dcId() const { return _dcId; } bool mtpFileLoader::readyToRequest() const { return !_finished && !_lastComplete && (_sentRequests.empty() || _size != 0) && (!_size || _nextRequestOffset < _size); } void mtpFileLoader::loadPart(int dcIndex) { Expects(readyToRequest()); makeRequest({ _nextRequestOffset, dcIndex }); _nextRequestOffset += Storage::kDownloadPartSize; } mtpRequestId mtpFileLoader::sendRequest(const RequestData &requestData) { const auto offset = requestData.offset; const auto limit = Storage::kDownloadPartSize; const auto shiftedDcId = MTP::downloadDcId( _cdnDcId ? _cdnDcId : dcId(), requestData.dcIndex); if (_cdnDcId) { return MTP::send( MTPupload_GetCdnFile( MTP_bytes(_cdnToken), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::cdnPartLoaded), rpcFail(&mtpFileLoader::cdnPartFailed), shiftedDcId, 50); } return _location.match([&](const WebFileLocation &location) { return MTP::send( MTPupload_GetWebFile( MTP_inputWebFileLocation( MTP_bytes(location.url()), MTP_long(location.accessHash())), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::webPartLoaded), rpcFail(&mtpFileLoader::partFailed), shiftedDcId, 50); }, [&](const GeoPointLocation &location) { return MTP::send( MTPupload_GetWebFile( MTP_inputWebFileGeoPointLocation( MTP_inputGeoPoint( MTP_double(location.lat), MTP_double(location.lon)), MTP_long(location.access), MTP_int(location.width), MTP_int(location.height), MTP_int(location.zoom), MTP_int(location.scale)), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::webPartLoaded), rpcFail(&mtpFileLoader::partFailed), shiftedDcId, 50); }, [&](const StorageFileLocation &location) { return MTP::send( MTPupload_GetFile( MTP_flags(0), location.tl(session().userId()), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::normalPartLoaded), rpcFail( &mtpFileLoader::normalPartFailed, location.fileReference()), shiftedDcId, 50); }); } void mtpFileLoader::makeRequest(const RequestData &requestData) { Expects(!_finished); placeSentRequest(sendRequest(requestData), requestData); } void mtpFileLoader::requestMoreCdnFileHashes() { Expects(!_finished); if (_cdnHashesRequestId || _cdnUncheckedParts.empty()) { return; } const auto requestData = _cdnUncheckedParts.cbegin()->first; const auto shiftedDcId = MTP::downloadDcId( dcId(), requestData.dcIndex); const auto requestId = _cdnHashesRequestId = MTP::send( MTPupload_GetCdnFileHashes( MTP_bytes(_cdnToken), MTP_int(requestData.offset)), rpcDone(&mtpFileLoader::getCdnFileHashesDone), rpcFail(&mtpFileLoader::cdnPartFailed), shiftedDcId); placeSentRequest(requestId, requestData); } void mtpFileLoader::normalPartLoaded( const MTPupload_File &result, mtpRequestId requestId) { Expects(!_finished); const auto requestData = finishSentRequest(requestId); result.match([&](const MTPDupload_fileCdnRedirect &data) { switchToCDN(requestData, data); }, [&](const MTPDupload_file &data) { partLoaded(requestData.offset, bytes::make_span(data.vbytes().v)); }); } void mtpFileLoader::webPartLoaded( const MTPupload_WebFile &result, mtpRequestId requestId) { result.match([&](const MTPDupload_webFile &data) { const auto requestData = finishSentRequest(requestId); if (!_size) { _size = data.vsize().v; } else if (data.vsize().v != _size) { LOG(("MTP Error: " "Bad size provided by bot for webDocument: %1, real: %2" ).arg(_size ).arg(data.vsize().v)); cancel(true); return; } partLoaded(requestData.offset, bytes::make_span(data.vbytes().v)); }); } void mtpFileLoader::cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId) { Expects(!_finished); const auto requestData = finishSentRequest(requestId); result.match([&](const MTPDupload_cdnFileReuploadNeeded &data) { const auto shiftedDcId = MTP::downloadDcId( dcId(), requestData.dcIndex); const auto requestId = MTP::send( MTPupload_ReuploadCdnFile( MTP_bytes(_cdnToken), data.vrequest_token()), rpcDone(&mtpFileLoader::reuploadDone), rpcFail(&mtpFileLoader::cdnPartFailed), shiftedDcId); placeSentRequest(requestId, requestData); }, [&](const MTPDupload_cdnFile &data) { auto key = bytes::make_span(_cdnEncryptionKey); auto iv = bytes::make_span(_cdnEncryptionIV); Expects(key.size() == MTP::CTRState::KeySize); Expects(iv.size() == MTP::CTRState::IvecSize); auto state = MTP::CTRState(); auto ivec = bytes::make_span(state.ivec); std::copy(iv.begin(), iv.end(), ivec.begin()); auto counterOffset = static_cast(requestData.offset) >> 4; state.ivec[15] = static_cast(counterOffset & 0xFF); state.ivec[14] = static_cast((counterOffset >> 8) & 0xFF); state.ivec[13] = static_cast((counterOffset >> 16) & 0xFF); state.ivec[12] = static_cast((counterOffset >> 24) & 0xFF); auto decryptInPlace = data.vbytes().v; auto buffer = bytes::make_detached_span(decryptInPlace); MTP::aesCtrEncrypt(buffer, key.data(), &state); switch (checkCdnFileHash(requestData.offset, buffer)) { case CheckCdnHashResult::NoHash: { _cdnUncheckedParts.emplace(requestData, decryptInPlace); requestMoreCdnFileHashes(); } return; case CheckCdnHashResult::Invalid: { LOG(("API Error: Wrong cdnFileHash for offset %1." ).arg(requestData.offset)); cancel(true); } return; case CheckCdnHashResult::Good: { partLoaded(requestData.offset, buffer); } return; } Unexpected("Result of checkCdnFileHash()"); }); } mtpFileLoader::CheckCdnHashResult mtpFileLoader::checkCdnFileHash( int offset, bytes::const_span buffer) { const auto cdnFileHashIt = _cdnFileHashes.find(offset); if (cdnFileHashIt == _cdnFileHashes.cend()) { return CheckCdnHashResult::NoHash; } const auto realHash = openssl::Sha256(buffer); const auto receivedHash = bytes::make_span(cdnFileHashIt->second.hash); if (bytes::compare(realHash, receivedHash)) { return CheckCdnHashResult::Invalid; } return CheckCdnHashResult::Good; } void mtpFileLoader::reuploadDone( const MTPVector &result, mtpRequestId requestId) { const auto requestData = finishSentRequest(requestId); addCdnHashes(result.v); makeRequest(requestData); } void mtpFileLoader::getCdnFileHashesDone( const MTPVector &result, mtpRequestId requestId) { Expects(!_finished); Expects(_cdnHashesRequestId == requestId); _cdnHashesRequestId = 0; const auto requestData = finishSentRequest(requestId); addCdnHashes(result.v); auto someMoreChecked = false; for (auto i = _cdnUncheckedParts.begin(); i != _cdnUncheckedParts.cend();) { const auto uncheckedData = i->first; const auto uncheckedBytes = bytes::make_span(i->second); switch (checkCdnFileHash(uncheckedData.offset, uncheckedBytes)) { case CheckCdnHashResult::NoHash: { ++i; } break; case CheckCdnHashResult::Invalid: { LOG(("API Error: Wrong cdnFileHash for offset %1." ).arg(uncheckedData.offset)); cancel(true); return; } break; case CheckCdnHashResult::Good: { someMoreChecked = true; const auto goodOffset = uncheckedData.offset; const auto goodBytes = std::move(i->second); const auto weak = QPointer(this); i = _cdnUncheckedParts.erase(i); if (!feedPart(goodOffset, bytes::make_span(goodBytes)) || !weak) { return; } else if (_finished) { notifyAboutProgress(); return; } } break; default: Unexpected("Result of checkCdnFileHash()"); } } if (someMoreChecked) { const auto weak = QPointer(this); notifyAboutProgress(); if (weak) { requestMoreCdnFileHashes(); } return; } LOG(("API Error: " "Could not find cdnFileHash for offset %1 " "after getCdnFileHashes request." ).arg(requestData.offset)); cancel(true); } void mtpFileLoader::placeSentRequest( mtpRequestId requestId, const RequestData &requestData) { Expects(!_finished); _downloader->requestedAmountIncrement( dcId(), requestData.dcIndex, Storage::kDownloadPartSize); _sentRequests.emplace(requestId, requestData); } auto mtpFileLoader::finishSentRequest(mtpRequestId requestId) -> RequestData { auto it = _sentRequests.find(requestId); Assert(it != _sentRequests.cend()); const auto result = it->second; _downloader->requestedAmountIncrement( dcId(), result.dcIndex, -Storage::kDownloadPartSize); _sentRequests.erase(it); return result; } bool mtpFileLoader::feedPart(int offset, bytes::const_span buffer) { if (!writeResultPart(offset, buffer)) { return false; } if (buffer.empty() || (buffer.size() % 1024)) { // bad next offset _lastComplete = true; } const auto finished = _sentRequests.empty() && _cdnUncheckedParts.empty() && (_lastComplete || (_size && _nextRequestOffset >= _size)); if (finished) { _downloader->remove(this); if (!finalizeResult()) { return false; } } return true; } void mtpFileLoader::partLoaded(int offset, bytes::const_span buffer) { if (feedPart(offset, buffer)) { notifyAboutProgress(); } } bool mtpFileLoader::normalPartFailed( QByteArray fileReference, const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) { return false; } if (error.code() == 400 && error.type().startsWith(qstr("FILE_REFERENCE_"))) { session().api().refreshFileReference( _origin, this, requestId, fileReference); return true; } return partFailed(error, requestId); } bool mtpFileLoader::partFailed( const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) { return false; } cancel(true); return true; } bool mtpFileLoader::cdnPartFailed( const RPCError &error, mtpRequestId requestId) { if (MTP::isDefaultHandledError(error)) { return false; } if (requestId == _cdnHashesRequestId) { _cdnHashesRequestId = 0; } if (error.type() == qstr("FILE_TOKEN_INVALID") || error.type() == qstr("REQUEST_TOKEN_INVALID")) { const auto requestData = finishSentRequest(requestId); changeCDNParams( requestData, 0, QByteArray(), QByteArray(), QByteArray(), QVector()); return true; } return partFailed(error, requestId); } void mtpFileLoader::startLoading() { _downloader->enqueue(this); } void mtpFileLoader::cancelRequests() { while (!_sentRequests.empty()) { auto requestId = _sentRequests.begin()->first; MTP::cancel(requestId); [[maybe_unused]] const auto data = finishSentRequest(requestId); } } void mtpFileLoader::switchToCDN( const RequestData &requestData, const MTPDupload_fileCdnRedirect &redirect) { changeCDNParams( requestData, redirect.vdc_id().v, redirect.vfile_token().v, redirect.vencryption_key().v, redirect.vencryption_iv().v, redirect.vfile_hashes().v); } void mtpFileLoader::addCdnHashes(const QVector &hashes) { for (const auto &hash : hashes) { hash.match([&](const MTPDfileHash &data) { _cdnFileHashes.emplace( data.voffset().v, CdnFileHash{ data.vlimit().v, data.vhash().v }); }); } } void mtpFileLoader::changeCDNParams( const RequestData &requestData, MTP::DcId dcId, const QByteArray &token, const QByteArray &encryptionKey, const QByteArray &encryptionIV, const QVector &hashes) { if (dcId != 0 && (encryptionKey.size() != MTP::CTRState::KeySize || encryptionIV.size() != MTP::CTRState::IvecSize)) { LOG(("Message Error: Wrong key (%1) / iv (%2) size in CDN params").arg(encryptionKey.size()).arg(encryptionIV.size())); cancel(true); return; } auto resendAllRequests = (_cdnDcId != dcId || _cdnToken != token || _cdnEncryptionKey != encryptionKey || _cdnEncryptionIV != encryptionIV); _cdnDcId = dcId; _cdnToken = token; _cdnEncryptionKey = encryptionKey; _cdnEncryptionIV = encryptionIV; addCdnHashes(hashes); if (resendAllRequests && !_sentRequests.empty()) { auto resendRequests = std::vector(); resendRequests.reserve(_sentRequests.size()); while (!_sentRequests.empty()) { auto requestId = _sentRequests.begin()->first; MTP::cancel(requestId); resendRequests.push_back(finishSentRequest(requestId)); } for (const auto &requestData : resendRequests) { makeRequest(requestData); } } makeRequest(requestData); } Storage::Cache::Key mtpFileLoader::cacheKey() const { return _location.match([&](const WebFileLocation &location) { return Data::WebDocumentCacheKey(location); }, [&](const GeoPointLocation &location) { return Data::GeoPointCacheKey(location); }, [&](const StorageFileLocation &location) { return location.cacheKey(); }); } std::optional mtpFileLoader::fileLocationKey() const { if (_locationType != UnknownFileLocation) { return mediaKey(_locationType, dcId(), objId()); } return std::nullopt; }