mirror of
https://github.com/telegramdesktop/tdesktop
synced 2024-12-31 19:12:17 +00:00
Extract Calls::Call. Add a call button to TopBar.
This commit is contained in:
parent
d18164bc51
commit
0a716036c2
344
Telegram/SourceFiles/calls/calls_call.cpp
Normal file
344
Telegram/SourceFiles/calls/calls_call.cpp
Normal file
@ -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 <openssl/bn.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#ifdef slots
|
||||
#undef slots
|
||||
#define NEED_TO_RESTORE_SLOTS
|
||||
#endif // slots
|
||||
|
||||
#include <VoIPController.h>
|
||||
#include <VoIPServerConfig.h>
|
||||
|
||||
#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<tgvoip::Endpoint> &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*> delegate, gsl::not_null<UserData*> 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<const unsigned char*>(_salt.data()), _salt.size(), &saltBN);
|
||||
BIGNUM pbytesBN;
|
||||
BN_init(&pbytesBN);
|
||||
BN_bin2bn(reinterpret_cast<const unsigned char*>(_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<gsl::byte>(g_a_length, gsl::byte());
|
||||
BN_bn2bin(&tmp, reinterpret_cast<unsigned char*>(_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<gsl::byte>(slice.begin(), slice.end());
|
||||
}
|
||||
BN_CTX_free(ctx);
|
||||
|
||||
auto randomID = rand_value<int32>();
|
||||
auto g_a_hash = std::array<gsl::byte, SHA256_DIGEST_LENGTH>();
|
||||
SHA256(reinterpret_cast<const unsigned char*>(_g_a.data()), _g_a.size(), reinterpret_cast<unsigned char*>(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<const unsigned char*>(_dhConfig.p.data()), _dhConfig.p.size(), &p);
|
||||
BN_bin2bn(reinterpret_cast<const unsigned char*>(call.vg_b.v.constData()), call.vg_b.v.length(), &i_authKey);
|
||||
BN_bin2bn(reinterpret_cast<const unsigned char*>(_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<unsigned char*>(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<const unsigned char*>(_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<Endpoint> 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<tgvoip::VoIPController>();
|
||||
_controller->implData = static_cast<void*>(this);
|
||||
_controller->SetRemoteEndpoints(endpoints, true);
|
||||
_controller->SetConfig(&config);
|
||||
_controller->SetEncryptionKey(reinterpret_cast<char*>(_authKey.data()), true);
|
||||
_controller->SetStateCallback([](tgvoip::VoIPController *controller, int state) {
|
||||
static_cast<Call*>(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<void*>(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 <typename Type>
|
||||
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
|
88
Telegram/SourceFiles/calls/calls_call.h
Normal file
88
Telegram/SourceFiles/calls/calls_call.h
Normal file
@ -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<gsl::byte> 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*> call, const MTPPhoneCallDiscardReason &reason) = 0;
|
||||
virtual void callFailed(gsl::not_null<Call*> call) = 0;
|
||||
|
||||
};
|
||||
|
||||
static constexpr auto kSaltSize = 256;
|
||||
|
||||
Call(gsl::not_null<Delegate*> instance, gsl::not_null<UserData*> 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 <typename Type>
|
||||
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*> _delegate;
|
||||
gsl::not_null<UserData*> _user;
|
||||
std::vector<gsl::byte> _g_a;
|
||||
std::array<gsl::byte, kSaltSize> _salt;
|
||||
std::array<gsl::byte, kAuthKeySize> _authKey;
|
||||
uint64 _id = 0;
|
||||
uint64 _accessHash = 0;
|
||||
uint64 _keyFingerprint = 0;
|
||||
|
||||
std::unique_ptr<tgvoip::VoIPController> _controller;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Calls
|
@ -22,221 +22,93 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
|
||||
|
||||
#include "mtproto/connection.h"
|
||||
#include "auth_session.h"
|
||||
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#ifdef slots
|
||||
#undef slots
|
||||
#define NEED_TO_RESTORE_SLOTS
|
||||
#endif // slots
|
||||
|
||||
#include <VoIPController.h>
|
||||
#include <VoIPServerConfig.h>
|
||||
|
||||
#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<tgvoip::Endpoint> &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<UserData*> user) {
|
||||
if (_controller) {
|
||||
if (_currentCall) {
|
||||
return; // Already in a call.
|
||||
}
|
||||
|
||||
_controller = std::make_unique<tgvoip::VoIPController>();
|
||||
request(MTPmessages_GetDhConfig(MTP_int(_dhConfigVersion), MTP_int(256))).done([this, user](const MTPmessages_DhConfig &result) {
|
||||
auto random = QByteArray();
|
||||
_currentCall = std::make_unique<Call>(getCallDelegate(), user);
|
||||
request(MTPmessages_GetDhConfig(MTP_int(_dhConfig.version), MTP_int(Call::kSaltSize))).done([this, call = base::weak_unique_ptr<Call>(_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<Call>(_currentCall)](const RPCError &error) {
|
||||
if (!call) {
|
||||
DEBUG_LOG(("API Warning: call was destroyed before got dhConfig."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto randomBytes = reinterpret_cast<const unsigned char*>(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<const unsigned char*>(_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<unsigned char*>(_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<int32>();
|
||||
QByteArray g_a_hash;
|
||||
g_a_hash.resize(SHA256_DIGEST_LENGTH);
|
||||
SHA256(reinterpret_cast<const unsigned char*>(_g_a.constData()), _g_a.size(), reinterpret_cast<unsigned char*>(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<const unsigned char*>(_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<unsigned char*>(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<Endpoint> 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*> 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*> 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<char*>(_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;
|
||||
|
@ -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<Call::Delegate*> getCallDelegate() {
|
||||
return static_cast<Call::Delegate*>(this);
|
||||
}
|
||||
DhConfig getDhConfig() const override {
|
||||
return _dhConfig;
|
||||
}
|
||||
void callFinished(gsl::not_null<Call*> call, const MTPPhoneCallDiscardReason &reason) override;
|
||||
void callFailed(gsl::not_null<Call*> 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<unsigned char, kSaltSize> _salt;
|
||||
std::array<unsigned char, kAuthKeySize> _authKey;
|
||||
uint64 _callId = 0;
|
||||
uint64 _accessHash = 0;
|
||||
uint64 _keyFingerprint = 0;
|
||||
|
||||
std::unique_ptr<tgvoip::VoIPController> _controller;
|
||||
std::unique_ptr<Call> _currentCall;
|
||||
|
||||
};
|
||||
|
||||
|
@ -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<const char*>(bytes.data()), bytes.size()));
|
||||
}
|
||||
inline MTPbytes MTP_bytes(const std::vector<gsl::byte> &bytes) {
|
||||
return MTP_bytes(gsl::make_span(bytes));
|
||||
}
|
||||
template <size_t N>
|
||||
inline MTPbytes MTP_bytes(const std::array<gsl::byte, N> &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<gsl::byte> byteVectorFromMTP(const MTPbytes &v) {
|
||||
auto bytes = bytesFromMTP(v);
|
||||
return std::vector<gsl::byte>(bytes.cbegin(), bytes.cend());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class MTPvector {
|
||||
public:
|
||||
|
@ -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<Window::Controller*> 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<Window::Controller*> 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();
|
||||
|
@ -65,6 +65,7 @@ private:
|
||||
void onDeleteSelection();
|
||||
void onClearSelection();
|
||||
void onInfoClicked();
|
||||
void onCall();
|
||||
void onSearch();
|
||||
void showMenu();
|
||||
|
||||
@ -85,6 +86,7 @@ private:
|
||||
object_ptr<Ui::PeerAvatarButton> _info;
|
||||
object_ptr<Ui::RoundButton> _mediaType;
|
||||
|
||||
object_ptr<Ui::IconButton> _call;
|
||||
object_ptr<Ui::IconButton> _search;
|
||||
object_ptr<Ui::IconButton> _menuToggle;
|
||||
object_ptr<Ui::DropdownMenu> _menu = { nullptr };
|
||||
|
@ -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 }};
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user