diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp new file mode 100644 index 0000000000..745ddfbc07 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -0,0 +1,344 @@ +/* +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 +*/ +#include "calls/calls_call.h" + +#include "auth_session.h" +#include "calls/calls_instance.h" + +#include +#include +#include + +#ifdef slots +#undef slots +#define NEED_TO_RESTORE_SLOTS +#endif // slots + +#include +#include + +#ifdef NEED_TO_RESTORE_SLOTS +#define slots Q_SLOTS +#undef NEED_TO_RESTORE_SLOTS +#endif // NEED_TO_RESTORE_SLOTS + +namespace Calls { +namespace { + +constexpr auto kMinLayer = 65; +constexpr auto kMaxLayer = 65; // MTP::CurrentLayer? + +using tgvoip::Endpoint; + +void ConvertEndpoint(std::vector &ep, const MTPDphoneConnection &mtc) { + if (mtc.vpeer_tag.v.length() != 16) + return; + auto ipv4 = tgvoip::IPv4Address(std::string(mtc.vip.v.constData(), mtc.vip.v.size())); + auto ipv6 = tgvoip::IPv6Address(std::string(mtc.vipv6.v.constData(), mtc.vipv6.v.size())); + ep.push_back(Endpoint((int64_t)mtc.vid.v, (uint16_t)mtc.vport.v, ipv4, ipv6, EP_TYPE_UDP_RELAY, (unsigned char*)mtc.vpeer_tag.v.data())); +} + +} // namespace + +Call::Call(gsl::not_null delegate, gsl::not_null user) +: _delegate(delegate) +, _user(user) { + // Save config here, because it is possible that it changes between + // different usages inside the same call. + _dhConfig = _delegate->getDhConfig(); +} + +void Call::generateSalt(base::const_byte_span random) { + Expects(random.size() == _salt.size()); + memset_rand(_salt.data(), _salt.size()); + for (auto i = 0, count = int(_salt.size()); i != count; i++) { + _salt[i] ^= random[i]; + } +} + +void Call::startOutgoing(base::const_byte_span random) { + generateSalt(random); + + BN_CTX* ctx = BN_CTX_new(); + BN_CTX_init(ctx); + BIGNUM i_g_a; + BN_init(&i_g_a); + BN_set_word(&i_g_a, _dhConfig.g); + BIGNUM tmp; + BN_init(&tmp); + BIGNUM saltBN; + BN_init(&saltBN); + BN_bin2bn(reinterpret_cast(_salt.data()), _salt.size(), &saltBN); + BIGNUM pbytesBN; + BN_init(&pbytesBN); + BN_bin2bn(reinterpret_cast(_dhConfig.p.data()), _dhConfig.p.size(), &pbytesBN); + BN_mod_exp(&tmp, &i_g_a, &saltBN, &pbytesBN, ctx); + auto g_a_length = BN_num_bytes(&tmp); + _g_a = std::vector(g_a_length, gsl::byte()); + BN_bn2bin(&tmp, reinterpret_cast(_g_a.data())); + constexpr auto kMaxGASize = 256; + if (_g_a.size() > kMaxGASize) { + auto slice = gsl::make_span(_g_a).subspan(1, kMaxGASize); + _g_a = std::vector(slice.begin(), slice.end()); + } + BN_CTX_free(ctx); + + auto randomID = rand_value(); + auto g_a_hash = std::array(); + SHA256(reinterpret_cast(_g_a.data()), _g_a.size(), reinterpret_cast(g_a_hash.data())); + + request(MTPphone_RequestCall(_user->inputUser, MTP_int(randomID), MTP_bytes(g_a_hash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) { + Expects(result.type() == mtpc_phone_phoneCall); + auto &call = result.c_phone_phoneCall(); + App::feedUsers(call.vusers); + if (call.vphone_call.type() != mtpc_phoneCallWaiting) { + LOG(("API Error: Expected phoneCallWaiting in response to phone.requestCall()")); + failed(); + return; + } + auto &phoneCall = call.vphone_call.c_phoneCallWaiting(); + _id = phoneCall.vid.v; + _accessHash = phoneCall.vaccess_hash.v; + }).fail([this](const RPCError &error) { + failed(); + }).send(); +} + +bool Call::handleUpdate(const MTPPhoneCall &call) { + switch (call.type()) { + case mtpc_phoneCallRequested: Unexpected("phoneCallRequested call inside an existing call handleUpdate()"); + + case mtpc_phoneCallEmpty: { + auto &data = call.c_phoneCallEmpty(); + if (data.vid.v != _id) { + return false; + } + LOG(("Call Error: phoneCallEmpty received.")); + failed(); + } return true; + + case mtpc_phoneCallWaiting: { + auto &data = call.c_phoneCallWaiting(); + if (data.vid.v != _id) { + return false; + } + } return true; + + case mtpc_phoneCall: { + auto &data = call.c_phoneCall(); + if (data.vid.v != _id) { + return false; + } + } return true; + + case mtpc_phoneCallDiscarded: { + auto &data = call.c_phoneCallDiscarded(); + if (data.vid.v != _id) { + return false; + } + _delegate->callFinished(this, data.vreason); + } return true; + + case mtpc_phoneCallAccepted: { + auto &data = call.c_phoneCallAccepted(); + if (data.vid.v != _id) { + return false; + } + if (checkCallFields(data)) { + confirmAcceptedCall(data); + } + } return true; + } + + Unexpected("phoneCall type inside an existing call handleUpdate()"); +} + +void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { + // TODO check isGoodGaAndGb + + BN_CTX *ctx = BN_CTX_new(); + BN_CTX_init(ctx); + BIGNUM p; + BIGNUM i_authKey; + BIGNUM res; + BIGNUM salt; + BN_init(&p); + BN_init(&i_authKey); + BN_init(&res); + BN_init(&salt); + BN_bin2bn(reinterpret_cast(_dhConfig.p.data()), _dhConfig.p.size(), &p); + BN_bin2bn(reinterpret_cast(call.vg_b.v.constData()), call.vg_b.v.length(), &i_authKey); + BN_bin2bn(reinterpret_cast(_salt.data()), _salt.size(), &salt); + + BN_mod_exp(&res, &i_authKey, &salt, &p, ctx); + BN_CTX_free(ctx); + auto realAuthKeyLength = BN_num_bytes(&res); + auto realAuthKeyBytes = QByteArray(realAuthKeyLength, Qt::Uninitialized); + BN_bn2bin(&res, reinterpret_cast(realAuthKeyBytes.data())); + + if (realAuthKeyLength > kAuthKeySize) { + memcpy(_authKey.data(), realAuthKeyBytes.constData() + (realAuthKeyLength - kAuthKeySize), kAuthKeySize); + } else if (realAuthKeyLength < kAuthKeySize) { + memset(_authKey.data(), 0, kAuthKeySize - realAuthKeyLength); + memcpy(_authKey.data() + (kAuthKeySize - realAuthKeyLength), realAuthKeyBytes.constData(), realAuthKeyLength); + } else { + memcpy(_authKey.data(), realAuthKeyBytes.constData(), kAuthKeySize); + } + + unsigned char authKeyHash[SHA_DIGEST_LENGTH]; + SHA1(reinterpret_cast(_authKey.data()), _authKey.size(), authKeyHash); + + _keyFingerprint = ((uint64)authKeyHash[19] << 56) + | ((uint64)authKeyHash[18] << 48) + | ((uint64)authKeyHash[17] << 40) + | ((uint64)authKeyHash[16] << 32) + | ((uint64)authKeyHash[15] << 24) + | ((uint64)authKeyHash[14] << 16) + | ((uint64)authKeyHash[13] << 8) + | ((uint64)authKeyHash[12]); + + request(MTPphone_ConfirmCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_bytes(_g_a), MTP_long(_keyFingerprint), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) { + Expects(result.type() == mtpc_phone_phoneCall); + auto &call = result.c_phone_phoneCall(); + App::feedUsers(call.vusers); + if (call.vphone_call.type() != mtpc_phoneCall) { + LOG(("API Error: Expected phoneCall in response to phone.confirmCall()")); + failed(); + return; + } + createAndStartController(call.vphone_call.c_phoneCall()); + }).fail([this](const RPCError &error) { + failed(); + }).send(); +} + +void Call::createAndStartController(const MTPDphoneCall &call) { + if (!checkCallFields(call)) { + return; + } + + voip_config_t config; + config.data_saving = DATA_SAVING_NEVER; + config.enableAEC = true; + config.enableNS = true; + config.enableAGC = true; + config.init_timeout = 30; + config.recv_timeout = 10; + + std::vector endpoints; + ConvertEndpoint(endpoints, call.vconnection.c_phoneConnection()); + for (int i = 0; i < call.valternative_connections.v.length(); i++) { + ConvertEndpoint(endpoints, call.valternative_connections.v[i].c_phoneConnection()); + } + + _controller = std::make_unique(); + _controller->implData = static_cast(this); + _controller->SetRemoteEndpoints(endpoints, true); + _controller->SetConfig(&config); + _controller->SetEncryptionKey(reinterpret_cast(_authKey.data()), true); + _controller->SetStateCallback([](tgvoip::VoIPController *controller, int state) { + static_cast(controller->implData)->handleControllerStateChange(controller, state); + }); + _controller->Start(); + _controller->Connect(); +} + +void Call::handleControllerStateChange(tgvoip::VoIPController *controller, int state) { + // NB! Can be called from an arbitrary thread! + Expects(controller == _controller.get()); + Expects(controller->implData == static_cast(this)); + + switch (state) { + case STATE_WAIT_INIT: { + DEBUG_LOG(("Call Info: State changed to Established.")); + } break; + + case STATE_WAIT_INIT_ACK: { + DEBUG_LOG(("Call Info: State changed to Established.")); + } break; + + case STATE_ESTABLISHED: { + DEBUG_LOG(("Call Info: State changed to Established.")); + } break; + + case STATE_FAILED: { + DEBUG_LOG(("Call Info: State changed to Failed.")); + failed(); + } break; + + default: LOG(("Call Error: Unexpected state in handleStateChange: %1").arg(state)); + } +} + +template +bool Call::checkCallCommonFields(const Type &call) { + auto checkFailed = [this] { + failed(); + return false; + }; + if (call.vaccess_hash.v != _accessHash) { + LOG(("API Error: Wrong call access_hash.")); + return checkFailed(); + } + if (call.vadmin_id.v != AuthSession::CurrentUserId()) { + LOG(("API Error: Wrong call admin_id %1, expected %2.").arg(call.vadmin_id.v).arg(AuthSession::CurrentUserId())); + return checkFailed(); + } + if (call.vparticipant_id.v != peerToUser(_user->id)) { + LOG(("API Error: Wrong call participant_id %1, expected %2.").arg(call.vparticipant_id.v).arg(peerToUser(_user->id))); + return checkFailed(); + } + return true; +} + +bool Call::checkCallFields(const MTPDphoneCall &call) { + if (!checkCallCommonFields(call)) { + return false; + } + if (call.vkey_fingerprint.v != _keyFingerprint) { + LOG(("API Error: Wrong call fingerprint.")); + failed(); + return false; + } + return true; +} + +bool Call::checkCallFields(const MTPDphoneCallAccepted &call) { + return checkCallCommonFields(call); +} + +void Call::destroyController() { + if (_controller) { + DEBUG_LOG(("Call Info: Destroying call controller..")); + _controller.reset(); + DEBUG_LOG(("Call Info: Call controller destroyed.")); + } +} + +void Call::failed() { + InvokeQueued(this, [this] { _delegate->callFailed(this); }); +} + +Call::~Call() { + destroyController(); +} + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h new file mode 100644 index 0000000000..350e255d28 --- /dev/null +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -0,0 +1,88 @@ +/* +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 "base/weak_unique_ptr.h" +#include "mtproto/sender.h" + +namespace tgvoip { +class VoIPController; +} // namespace tgvoip + +namespace Calls { + +struct DhConfig { + int32 version = 0; + int32 g = 0; + std::vector p; +}; + +class Call : public base::enable_weak_from_this, private MTP::Sender { +public: + class Delegate { + public: + virtual DhConfig getDhConfig() const = 0; + virtual void callFinished(gsl::not_null call, const MTPPhoneCallDiscardReason &reason) = 0; + virtual void callFailed(gsl::not_null call) = 0; + + }; + + static constexpr auto kSaltSize = 256; + + Call(gsl::not_null instance, gsl::not_null user); + + void startOutgoing(base::const_byte_span random); + bool handleUpdate(const MTPPhoneCall &call); + + ~Call(); + +private: + static constexpr auto kAuthKeySize = 256; + + void generateSalt(base::const_byte_span random); + void handleControllerStateChange(tgvoip::VoIPController *controller, int state); + void createAndStartController(const MTPDphoneCall &call); + void destroyController(); + + template + bool checkCallCommonFields(const Type &call); + bool checkCallFields(const MTPDphoneCall &call); + bool checkCallFields(const MTPDphoneCallAccepted &call); + + void confirmAcceptedCall(const MTPDphoneCallAccepted &call); + + void failed(); + + DhConfig _dhConfig; + gsl::not_null _delegate; + gsl::not_null _user; + std::vector _g_a; + std::array _salt; + std::array _authKey; + uint64 _id = 0; + uint64 _accessHash = 0; + uint64 _keyFingerprint = 0; + + std::unique_ptr _controller; + +}; + +} // namespace Calls diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 679f541015..6a98c96f3b 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -22,221 +22,93 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mtproto/connection.h" #include "auth_session.h" - -#include -#include -#include - -#ifdef slots -#undef slots -#define NEED_TO_RESTORE_SLOTS -#endif // slots - -#include -#include - -#ifdef NEED_TO_RESTORE_SLOTS -#define slots Q_SLOTS -#undef NEED_TO_RESTORE_SLOTS -#endif // NEED_TO_RESTORE_SLOTS +#include "calls/calls_call.h" namespace Calls { -namespace { - -constexpr auto kMinLayer = 65; -constexpr auto kMaxLayer = 65; // MTP::CurrentLayer? - -using tgvoip::Endpoint; - -void ConvertEndpoint(std::vector &ep, const MTPDphoneConnection &mtc) { - if (mtc.vpeer_tag.v.length() != 16) - return; - auto ipv4 = tgvoip::IPv4Address(std::string(mtc.vip.v.constData(), mtc.vip.v.size())); - auto ipv6 = tgvoip::IPv6Address(std::string(mtc.vipv6.v.constData(), mtc.vipv6.v.size())); - ep.push_back(Endpoint((int64_t)mtc.vid.v, (uint16_t)mtc.vport.v, ipv4, ipv6, EP_TYPE_UDP_RELAY, (unsigned char*)mtc.vpeer_tag.v.data())); -} - -} // namespace Instance::Instance() = default; void Instance::startOutgoingCall(gsl::not_null user) { - if (_controller) { + if (_currentCall) { return; // Already in a call. } - _controller = std::make_unique(); - request(MTPmessages_GetDhConfig(MTP_int(_dhConfigVersion), MTP_int(256))).done([this, user](const MTPmessages_DhConfig &result) { - auto random = QByteArray(); + _currentCall = std::make_unique(getCallDelegate(), user); + request(MTPmessages_GetDhConfig(MTP_int(_dhConfig.version), MTP_int(Call::kSaltSize))).done([this, call = base::weak_unique_ptr(_currentCall)](const MTPmessages_DhConfig &result) { + if (!call) { + DEBUG_LOG(("API Warning: call was destroyed before got dhConfig.")); + return; + } + + auto random = base::const_byte_span(); switch (result.type()) { case mtpc_messages_dhConfig: { auto &config = result.c_messages_dhConfig(); if (!MTP::IsPrimeAndGood(config.vp.v, config.vg.v)) { LOG(("API Error: bad p/g received in dhConfig.")); - callFailed(); + callFailed(call.get()); return; } - _dhConfigG = config.vg.v; - _dhConfigP = config.vp.v; - random = qba(config.vrandom); + _dhConfig.g = config.vg.v; + _dhConfig.p = byteVectorFromMTP(config.vp); + random = bytesFromMTP(config.vrandom); } break; case mtpc_messages_dhConfigNotModified: { auto &config = result.c_messages_dhConfigNotModified(); - random = qba(config.vrandom); - if (!_dhConfigG || _dhConfigP.isEmpty()) { + random = bytesFromMTP(config.vrandom); + if (!_dhConfig.g || _dhConfig.p.empty()) { LOG(("API Error: dhConfigNotModified on zero version.")); - callFailed(); + callFailed(call.get()); return; } } break; default: Unexpected("Type in messages.getDhConfig"); } - if (random.size() != kSaltSize) { + + if (random.size() != Call::kSaltSize) { LOG(("API Error: dhConfig random bytes wrong size: %1").arg(random.size())); - callFailed(); + callFailed(call.get()); + return; + } + call->startOutgoing(random); + }).fail([this, call = base::weak_unique_ptr(_currentCall)](const RPCError &error) { + if (!call) { + DEBUG_LOG(("API Warning: call was destroyed before got dhConfig.")); return; } - auto randomBytes = reinterpret_cast(random.constData()); - RAND_bytes(_salt.data(), kSaltSize); - for (auto i = 0; i != kSaltSize; i++) { - _salt[i] ^= randomBytes[i]; - } - BN_CTX* ctx = BN_CTX_new(); - BN_CTX_init(ctx); - BIGNUM i_g_a; - BN_init(&i_g_a); - BN_set_word(&i_g_a, _dhConfigG); - BIGNUM tmp; - BN_init(&tmp); - BIGNUM saltBN; - BN_init(&saltBN); - BN_bin2bn(_salt.data(), kSaltSize, &saltBN); - BIGNUM pbytesBN; - BN_init(&pbytesBN); - BN_bin2bn(reinterpret_cast(_dhConfigP.constData()), _dhConfigP.size(), &pbytesBN); - BN_mod_exp(&tmp, &i_g_a, &saltBN, &pbytesBN, ctx); - auto g_a_length = BN_num_bytes(&tmp); - _g_a = QByteArray(g_a_length, Qt::Uninitialized); - BN_bn2bin(&tmp, reinterpret_cast(_g_a.data())); - constexpr auto kMaxGASize = 256; - if (g_a_length > kMaxGASize) { - _g_a = _g_a.mid(1, kMaxGASize); - } - BN_CTX_free(ctx); - - auto randomID = rand_value(); - QByteArray g_a_hash; - g_a_hash.resize(SHA256_DIGEST_LENGTH); - SHA256(reinterpret_cast(_g_a.constData()), _g_a.size(), reinterpret_cast(g_a_hash.data())); - - request(MTPphone_RequestCall(user->inputUser, MTP_int(randomID), MTP_bytes(g_a_hash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) { - Expects(result.type() == mtpc_phone_phoneCall); - auto &call = result.c_phone_phoneCall(); - App::feedUsers(call.vusers); - if (call.vphone_call.type() != mtpc_phoneCallWaiting) { - LOG(("API Error: Expected phoneCallWaiting in response to phone.requestCall()")); - callFailed(); - return; - } - auto &phoneCall = call.vphone_call.c_phoneCallWaiting(); - _callId = phoneCall.vid.v; - _accessHash = phoneCall.vaccess_hash.v; - }).fail([this](const RPCError &error) { - callFailed(); - }).send(); - }).fail([this](const RPCError &error) { - callFailed(); + callFailed(call.get()); }).send(); } -void Instance::handleUpdate(const MTPDupdatePhoneCall& update) { - // TODO check call id - switch (update.vphone_call.type()) { - case mtpc_phoneCallAccepted: { - // state changed STATE_EXCHANGING_KEYS - auto &call = update.vphone_call.c_phoneCallAccepted(); - // TODO check isGoodGaAndGb - - BN_CTX *ctx = BN_CTX_new(); - BN_CTX_init(ctx); - BIGNUM p; - BIGNUM i_authKey; - BIGNUM res; - BIGNUM salt; - BN_init(&p); - BN_init(&i_authKey); - BN_init(&res); - BN_init(&salt); - BN_bin2bn(reinterpret_cast(_dhConfigP.constData()), _dhConfigP.size(), &p); - BN_bin2bn((const unsigned char*)call.vg_b.v.constData(), call.vg_b.v.length(), &i_authKey); - BN_bin2bn(_salt.data(), kSaltSize, &salt); - - BN_mod_exp(&res, &i_authKey, &salt, &p, ctx); - BN_CTX_free(ctx); - auto realAuthKeyLength = BN_num_bytes(&res); - auto realAuthKeyBytes = QByteArray(realAuthKeyLength, Qt::Uninitialized); - BN_bn2bin(&res, reinterpret_cast(realAuthKeyBytes.data())); - - if (realAuthKeyLength > kAuthKeySize) { - memcpy(_authKey.data(), realAuthKeyBytes.constData() + (realAuthKeyLength - kAuthKeySize), kAuthKeySize); - } else if (realAuthKeyLength < kAuthKeySize) { - memset(_authKey.data(), 0, kAuthKeySize - realAuthKeyLength); - memcpy(_authKey.data() + (kAuthKeySize - realAuthKeyLength), realAuthKeyBytes.constData(), realAuthKeyLength); - } else { - memcpy(_authKey.data(), realAuthKeyBytes.constData(), kAuthKeySize); - } - - unsigned char authKeyHash[SHA_DIGEST_LENGTH]; - SHA1(_authKey.data(), _authKey.size(), authKeyHash); - - _keyFingerprint = ((uint64)authKeyHash[19] << 56) - | ((uint64)authKeyHash[18] << 48) - | ((uint64)authKeyHash[17] << 40) - | ((uint64)authKeyHash[16] << 32) - | ((uint64)authKeyHash[15] << 24) - | ((uint64)authKeyHash[14] << 16) - | ((uint64)authKeyHash[13] << 8) - | ((uint64)authKeyHash[12]); - - request(MTPphone_ConfirmCall(MTP_inputPhoneCall(MTP_long(_callId), MTP_long(_accessHash)), MTP_bytes(_g_a), MTP_long(_keyFingerprint), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) { - auto &call = result.c_phone_phoneCall().vphone_call.c_phoneCall(); - - std::vector endpoints; - ConvertEndpoint(endpoints, call.vconnection.c_phoneConnection()); - for (int i = 0; i < call.valternative_connections.v.length(); i++) { - ConvertEndpoint(endpoints, call.valternative_connections.v[i].c_phoneConnection()); - } - _controller->SetRemoteEndpoints(endpoints, true); - - initiateActualCall(); - }).fail([this](const RPCError &error) { - callFailed(); - }).send(); - } break; +void Instance::callFinished(gsl::not_null call, const MTPPhoneCallDiscardReason &reason) { + if (_currentCall.get() == call) { + _currentCall.reset(); } } -void Instance::callFailed() { - InvokeQueued(this, [this] { - _controller.reset(); - }); +void Instance::callFailed(gsl::not_null call) { + if (_currentCall.get() == call) { + _currentCall.reset(); + } } -void Instance::initiateActualCall() { - voip_config_t config; - config.data_saving = DATA_SAVING_NEVER; - config.enableAEC = true; - config.enableNS = true; - config.enableAGC = true; - config.init_timeout = 30; - config.recv_timeout = 10; - _controller->SetConfig(&config); - _controller->SetEncryptionKey(reinterpret_cast(_authKey.data()), true); - _controller->Start(); - _controller->Connect(); +void Instance::handleUpdate(const MTPDupdatePhoneCall& update) { + handleCallUpdate(update.vphone_call); +} + +void Instance::handleCallUpdate(const MTPPhoneCall &call) { + if (call.type() == mtpc_phoneCallRequested) { + if (_currentCall) { + // discard ? + } else { + // show call + } + } else if (!_currentCall || !_currentCall->handleUpdate(call)) { + DEBUG_LOG(("API Warning: unexpected phone call update %1").arg(call.type())); + } } Instance::~Instance() = default; diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 50f1cd40eb..ed3846ffe1 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -21,17 +21,11 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "mtproto/sender.h" -#include "base/weak_unique_ptr.h" - -class AuthSession; - -namespace tgvoip { -class VoIPController; -} // namespace tgvoip +#include "calls/calls_call.h" namespace Calls { -class Instance : public base::enable_weak_from_this, private MTP::Sender { +class Instance : private MTP::Sender, private Call::Delegate { public: Instance(); @@ -42,24 +36,20 @@ public: ~Instance(); private: - void initiateActualCall(); - void callFailed(); + gsl::not_null getCallDelegate() { + return static_cast(this); + } + DhConfig getDhConfig() const override { + return _dhConfig; + } + void callFinished(gsl::not_null call, const MTPPhoneCallDiscardReason &reason) override; + void callFailed(gsl::not_null call) override; - static constexpr auto kSaltSize = 256; - static constexpr auto kAuthKeySize = 256; + void handleCallUpdate(const MTPPhoneCall &call); - int32 _dhConfigVersion = 0; - int32 _dhConfigG = 0; - QByteArray _dhConfigP; + DhConfig _dhConfig; - QByteArray _g_a; - std::array _salt; - std::array _authKey; - uint64 _callId = 0; - uint64 _accessHash = 0; - uint64 _keyFingerprint = 0; - - std::unique_ptr _controller; + std::unique_ptr _currentCall; }; diff --git a/Telegram/SourceFiles/mtproto/core_types.h b/Telegram/SourceFiles/mtproto/core_types.h index 00589cd8c7..00b3d59b3a 100644 --- a/Telegram/SourceFiles/mtproto/core_types.h +++ b/Telegram/SourceFiles/mtproto/core_types.h @@ -707,6 +707,16 @@ inline MTPbytes MTP_bytes(const QByteArray &v) { inline MTPbytes MTP_bytes(QByteArray &&v) { return MTPbytes(std::move(v)); } +inline MTPbytes MTP_bytes(base::const_byte_span bytes) { + return MTP_bytes(QByteArray(reinterpret_cast(bytes.data()), bytes.size())); +} +inline MTPbytes MTP_bytes(const std::vector &bytes) { + return MTP_bytes(gsl::make_span(bytes)); +} +template +inline MTPbytes MTP_bytes(const std::array &bytes) { + return MTP_bytes(gsl::make_span(bytes)); +} inline bool operator==(const MTPstring &a, const MTPstring &b) { return a.v == b.v; @@ -723,6 +733,15 @@ inline QByteArray qba(const MTPstring &v) { return v.v; } +inline base::const_byte_span bytesFromMTP(const MTPbytes &v) { + return gsl::as_bytes(gsl::make_span(v.v)); +} + +inline std::vector byteVectorFromMTP(const MTPbytes &v) { + auto bytes = bytesFromMTP(v); + return std::vector(bytes.cbegin(), bytes.cend()); +} + template class MTPvector { public: diff --git a/Telegram/SourceFiles/window/top_bar_widget.cpp b/Telegram/SourceFiles/window/top_bar_widget.cpp index 01e5ae3e09..dd07fb3595 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.cpp +++ b/Telegram/SourceFiles/window/top_bar_widget.cpp @@ -32,6 +32,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "ui/widgets/dropdown_menu.h" #include "dialogs/dialogs_layout.h" #include "window/window_controller.h" +#include "calls/calls_instance.h" namespace Window { @@ -42,6 +43,7 @@ TopBarWidget::TopBarWidget(QWidget *parent, gsl::not_null c , _delete(this, lang(lng_selected_delete), st::defaultActiveButton) , _info(this, nullptr, st::topBarInfoButton) , _mediaType(this, lang(lng_media_type), st::topBarButton) +, _call(this, st::topBarCall) , _search(this, st::topBarSearch) , _menuToggle(this, st::topBarMenuToggle) { _forward->setClickedCallback([this] { onForwardSelection(); }); @@ -50,6 +52,7 @@ TopBarWidget::TopBarWidget(QWidget *parent, gsl::not_null c _delete->setWidthChangedCallback([this] { updateControlsGeometry(); }); _clearSelection->setClickedCallback([this] { onClearSelection(); }); _info->setClickedCallback([this] { onInfoClicked(); }); + _call->setClickedCallback([this] { onCall(); }); _search->setClickedCallback([this] { onSearch(); }); _menuToggle->setClickedCallback([this] { showMenu(); }); @@ -104,6 +107,16 @@ void TopBarWidget::onSearch() { } } +void TopBarWidget::onCall() { + if (auto main = App::main()) { + if (auto peer = main->peer()) { + if (auto user = peer->asUser()) { + Calls::Current().startOutgoingCall(user); + } + } + } +} + void TopBarWidget::showMenu() { if (auto main = App::main()) { if (auto peer = main->peer()) { @@ -177,6 +190,9 @@ void TopBarWidget::paintEvent(QPaintEvent *e) { if (!_menuToggle->isHidden()) { decreaseWidth += _menuToggle->width(); } + if (!_call->isHidden()) { + decreaseWidth += _call->width(); + } if (!_search->isHidden()) { decreaseWidth += _search->width(); } @@ -252,10 +268,18 @@ void TopBarWidget::updateControlsGeometry() { _delete->moveToLeft(buttonsLeft, selectedButtonsTop); _clearSelection->moveToRight(st::topBarActionSkip, selectedButtonsTop); - _info->moveToRight(0, otherButtonsTop); - _menuToggle->moveToRight(0, otherButtonsTop); - _mediaType->moveToRight(0, otherButtonsTop); - _search->moveToRight(_info->isHidden() ? _menuToggle->width() : _info->width(), otherButtonsTop); + auto right = 0; + _info->moveToRight(right, otherButtonsTop); + _menuToggle->moveToRight(right, otherButtonsTop); + _mediaType->moveToRight(right, otherButtonsTop); + if (_info->isHidden()) { + right += _menuToggle->width(); + } else { + right += _info->width(); + } + _call->moveToRight(right, otherButtonsTop); + if (!_call->isHidden()) right += _call->width(); + _search->moveToRight(right, otherButtonsTop); } void TopBarWidget::animationFinished() { @@ -283,8 +307,10 @@ void TopBarWidget::showAll() { _menuToggle->show(); } _search->show(); + _call->setVisible(historyPeer->isUser()); } else { _search->hide(); + _call->hide(); _info->hide(); _menuToggle->hide(); _menu.destroy(); diff --git a/Telegram/SourceFiles/window/top_bar_widget.h b/Telegram/SourceFiles/window/top_bar_widget.h index b358537970..f16c778683 100644 --- a/Telegram/SourceFiles/window/top_bar_widget.h +++ b/Telegram/SourceFiles/window/top_bar_widget.h @@ -65,6 +65,7 @@ private: void onDeleteSelection(); void onClearSelection(); void onInfoClicked(); + void onCall(); void onSearch(); void showMenu(); @@ -85,6 +86,7 @@ private: object_ptr _info; object_ptr _mediaType; + object_ptr _call; object_ptr _search; object_ptr _menuToggle; object_ptr _menu = { nullptr }; diff --git a/Telegram/SourceFiles/window/window.style b/Telegram/SourceFiles/window/window.style index 8cf49e9990..5271edef4f 100644 --- a/Telegram/SourceFiles/window/window.style +++ b/Telegram/SourceFiles/window/window.style @@ -245,11 +245,11 @@ topBarClearButton: RoundButton(defaultLightButton) { width: -18px; } topBarSearch: IconButton { - width: 45px; + width: 44px; height: topBarHeight; - icon: icon {{ "title_search", menuIconFg }}; - iconOver: icon {{ "title_search", menuIconFgOver }}; + icon: icon {{ "title_search-flip_horizontal", menuIconFg }}; + iconOver: icon {{ "title_search-flip_horizontal", menuIconFgOver }}; iconPosition: point(15px, 18px); rippleAreaPosition: point(4px, 7px); @@ -258,6 +258,10 @@ topBarSearch: IconButton { color: windowBgOver; } } +topBarCall: IconButton(topBarSearch) { + icon: icon {{ "add_contact_phone", menuIconFg }}; + iconOver: icon {{ "add_contact_phone", menuIconFgOver }}; +} topBarMenuToggle: IconButton(topBarSearch) { icon: icon {{ "title_menu_dots", menuIconFg }}; iconOver: icon {{ "title_menu_dots", menuIconFgOver }}; diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index c52b382681..5dffcd4378 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -79,6 +79,8 @@ <(src_loc)/boxes/stickers_box.h <(src_loc)/boxes/username_box.cpp <(src_loc)/boxes/username_box.h +<(src_loc)/calls/calls_call.cpp +<(src_loc)/calls/calls_call.h <(src_loc)/calls/calls_instance.cpp <(src_loc)/calls/calls_instance.h <(src_loc)/chat_helpers/bot_keyboard.cpp