/* 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