Improve confcall join.

This commit is contained in:
John Preston 2025-04-11 17:16:32 +04:00
parent 59abfcbd6d
commit 01d927aceb
5 changed files with 192 additions and 97 deletions

View file

@ -682,8 +682,11 @@ GroupCall::GroupCall(
setupMediaDevices(); setupMediaDevices();
setupOutgoingVideo(); setupOutgoingVideo();
if (_conferenceCall || conference.migrating || conference.show) { if (_conferenceCall) {
setupConference(); setupConferenceCall();
initConferenceE2E();
} else if (conference.migrating || conference.show) {
initConferenceE2E();
} }
if (conference.migrating || (conference.show && !_conferenceCall)) { if (conference.migrating || (conference.show && !_conferenceCall)) {
if (!conference.muted) { if (!conference.muted) {
@ -739,6 +742,7 @@ void GroupCall::processConferenceStart(StartConferenceInfo conference) {
} }
GroupCall::~GroupCall() { GroupCall::~GroupCall() {
_e2e = nullptr;
destroyScreencast(); destroyScreencast();
destroyController(); destroyController();
if (!_rtmp) { if (!_rtmp) {
@ -746,37 +750,54 @@ GroupCall::~GroupCall() {
} }
} }
void GroupCall::setupConference() { void GroupCall::initConferenceE2E() {
if (!_e2e) { if (!_e2eEncryptDecrypt) {
_e2e = std::make_shared<TdE2E::Call>( _e2eEncryptDecrypt = std::make_shared<TdE2E::EncryptDecrypt>();
TdE2E::MakeUserId(_peer->session().user()));
} }
for (auto &state : _subchains) {
_api.request(base::take(state.requestId)).cancel();
state = SubChainState();
}
_e2e = nullptr;
_pendingOutboundBlock = QByteArray();
const auto tde2eUserId = TdE2E::MakeUserId(_peer->session().user());
_e2e = std::make_unique<TdE2E::Call>(tde2eUserId);
_e2e->subchainRequests( _e2e->subchainRequests(
) | rpl::start_with_next([=](TdE2E::Call::SubchainRequest request) { ) | rpl::start_with_next([=](TdE2E::Call::SubchainRequest request) {
requestSubchainBlocks(request.subchain, request.height); requestSubchainBlocks(request.subchain, request.height);
}, _lifetime); }, _e2e->lifetime());
_e2e->sendOutboundBlock( _e2e->sendOutboundBlock(
) | rpl::start_with_next([=](QByteArray &&block) { ) | rpl::start_with_next([=](QByteArray &&block) {
sendOutboundBlock(std::move(block)); sendOutboundBlock(std::move(block));
}, _lifetime); }, _e2e->lifetime());
_e2e->failures() | rpl::start_with_next([=] { _e2e->failures() | rpl::start_with_next([=] {
LOG(("TdE2E: Got failure!")); LOG(("TdE2E: Got failure!"));
hangup(); startRejoin();
}, _lifetime); }, _e2e->lifetime());
if (_conferenceCall) { _e2e->registerEncryptDecrypt(_e2eEncryptDecrypt);
setupConferenceCall();
} _emojiHash = _e2e->emojiHashValue();
} }
void GroupCall::setupConferenceCall() { void GroupCall::setupConferenceCall() {
Expects(_conferenceCall != nullptr && _e2e != nullptr); Expects(_conferenceCall != nullptr);
_conferenceCall->staleParticipantIds( _conferenceCall->staleParticipantIds(
) | rpl::start_with_next([=](const base::flat_set<UserId> &staleIds) { ) | rpl::start_with_next([=](const base::flat_set<UserId> &staleIds) {
removeConferenceParticipants(staleIds, true); removeConferenceParticipants(staleIds, true);
}, _lifetime); }, _lifetime);
}
void GroupCall::trackParticipantsWithAccess() {
if (!_conferenceCall || !_e2e) {
return;
}
_e2e->participantsSetValue( _e2e->participantsSetValue(
) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) { ) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) {
@ -786,7 +807,7 @@ void GroupCall::setupConferenceCall() {
users.emplace(UserId(id.v)); users.emplace(UserId(id.v));
} }
_conferenceCall->setParticipantsWithAccess(std::move(users)); _conferenceCall->setParticipantsWithAccess(std::move(users));
}, _lifetime); }, _e2e->lifetime());
} }
void GroupCall::removeConferenceParticipants( void GroupCall::removeConferenceParticipants(
@ -1233,9 +1254,7 @@ rpl::producer<not_null<Data::GroupCall*>> GroupCall::real() const {
} }
rpl::producer<QByteArray> GroupCall::emojiHashValue() const { rpl::producer<QByteArray> GroupCall::emojiHashValue() const {
Expects(_e2e != nullptr); return _emojiHash.value();
return _e2e->emojiHashValue();
} }
void GroupCall::start(TimeId scheduleDate, bool rtmp) { void GroupCall::start(TimeId scheduleDate, bool rtmp) {
@ -1482,9 +1501,16 @@ void GroupCall::markTrackPaused(const VideoEndpoint &endpoint, bool paused) {
} }
void GroupCall::startRejoin() { void GroupCall::startRejoin() {
if (_joinState.action != JoinAction::None || _createRequestId) {
// Don't reset _e2e in that case, if rejoin() is a no-op.
return;
}
for (const auto &[task, part] : _broadcastParts) { for (const auto &[task, part] : _broadcastParts) {
_api.request(part.requestId).cancel(); _api.request(part.requestId).cancel();
} }
if (_conferenceCall || _startConferenceInfo) {
initConferenceE2E();
}
setState(State::Joining); setState(State::Joining);
rejoin(); rejoin();
} }
@ -1720,7 +1746,15 @@ void GroupCall::joinDone(
applyMeInCallLocally(); applyMeInCallLocally();
maybeSendMutedUpdate(wasMuteState); maybeSendMutedUpdate(wasMuteState);
for (auto &state : _subchains) {
// Accept initial join blocks.
_api.request(base::take(state.requestId)).cancel();
state.inShortPoll = true;
}
_peer->session().api().applyUpdates(result); _peer->session().api().applyUpdates(result);
for (auto &state : _subchains) {
state.inShortPoll = false;
}
if (justCreated) { if (justCreated) {
subscribeToReal(_conferenceCall.get()); subscribeToReal(_conferenceCall.get());
@ -1732,6 +1766,7 @@ void GroupCall::joinDone(
*_startConferenceInfo); *_startConferenceInfo);
} }
trackParticipantsWithAccess();
applyQueuedSelfUpdates(); applyQueuedSelfUpdates();
checkFirstTimeJoined(); checkFirstTimeJoined();
_screenJoinState.nextActionPending = true; _screenJoinState.nextActionPending = true;
@ -1766,8 +1801,7 @@ void GroupCall::joinDone(
void GroupCall::joinFail(const QString &error) { void GroupCall::joinFail(const QString &error) {
if (_e2e) { if (_e2e) {
if (error == u"BLOCK_INVALID"_q if (error.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) {
|| error.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) {
if (_id) { if (_id) {
refreshLastBlockAndJoin(); refreshLastBlockAndJoin();
} else { } else {
@ -2443,10 +2477,12 @@ void GroupCall::applySubChainUpdate(
Expects(subchain >= 0 && subchain < kSubChainsCount); Expects(subchain >= 0 && subchain < kSubChainsCount);
auto &entry = _subchains[subchain]; auto &entry = _subchains[subchain];
auto now = next - int(blocks.size()); auto raw = std::vector<TdE2E::Block>();
raw.reserve(blocks.size());
for (const auto &block : blocks) { for (const auto &block : blocks) {
_e2e->apply(subchain, now++, { block.v }, entry.inShortPoll); raw.push_back({ block.v });
} }
_e2e->apply(subchain, next, raw, entry.inShortPoll);
} }
void GroupCall::applyQueuedSelfUpdates() { void GroupCall::applyQueuedSelfUpdates() {
@ -2955,7 +2991,9 @@ bool GroupCall::tryCreateController() {
}); });
return result; return result;
}, },
.e2eEncryptDecrypt = _e2e ? _e2e->callbackEncryptDecrypt() : nullptr, .e2eEncryptDecrypt = (_e2eEncryptDecrypt
? _e2eEncryptDecrypt->callback()
: nullptr),
}; };
if (Logs::DebugEnabled()) { if (Logs::DebugEnabled()) {
auto callLogFolder = cWorkingDir() + u"DebugLogs"_q; auto callLogFolder = cWorkingDir() + u"DebugLogs"_q;
@ -3008,7 +3046,9 @@ bool GroupCall::tryCreateScreencast() {
.videoCapture = _screenCapture, .videoCapture = _screenCapture,
.videoContentType = tgcalls::VideoContentType::Screencast, .videoContentType = tgcalls::VideoContentType::Screencast,
.videoCodecPreferences = lookupVideoCodecPreferences(), .videoCodecPreferences = lookupVideoCodecPreferences(),
.e2eEncryptDecrypt = _e2e ? _e2e->callbackEncryptDecrypt() : nullptr, .e2eEncryptDecrypt = (_e2eEncryptDecrypt
? _e2eEncryptDecrypt->callback()
: nullptr),
}; };
LOG(("Call Info: Creating group screen instance")); LOG(("Call Info: Creating group screen instance"));

View file

@ -45,6 +45,7 @@ class GroupCall;
namespace TdE2E { namespace TdE2E {
class Call; class Call;
class EncryptDecrypt;
} // namespace TdE2E } // namespace TdE2E
namespace Calls { namespace Calls {
@ -621,8 +622,9 @@ private:
void setupMediaDevices(); void setupMediaDevices();
void setupOutgoingVideo(); void setupOutgoingVideo();
void setupConference(); void initConferenceE2E();
void setupConferenceCall(); void setupConferenceCall();
void trackParticipantsWithAccess();
void setScreenEndpoint(std::string endpoint); void setScreenEndpoint(std::string endpoint);
void setCameraEndpoint(std::string endpoint); void setCameraEndpoint(std::string endpoint);
void addVideoOutput(const std::string &endpoint, SinkPointer sink); void addVideoOutput(const std::string &endpoint, SinkPointer sink);
@ -648,7 +650,9 @@ private:
const not_null<Delegate*> _delegate; const not_null<Delegate*> _delegate;
std::shared_ptr<Data::GroupCall> _conferenceCall; std::shared_ptr<Data::GroupCall> _conferenceCall;
std::shared_ptr<TdE2E::Call> _e2e; std::unique_ptr<TdE2E::Call> _e2e;
std::shared_ptr<TdE2E::EncryptDecrypt> _e2eEncryptDecrypt;
rpl::variable<QByteArray> _emojiHash;
QByteArray _pendingOutboundBlock; QByteArray _pendingOutboundBlock;
std::shared_ptr<StartConferenceInfo> _startConferenceInfo; std::shared_ptr<StartConferenceInfo> _startConferenceInfo;

View file

@ -83,9 +83,13 @@ constexpr auto kHideControlsTimeout = 5 * crl::time(1000);
const auto fp = bytes::make_span(hash).subspan(0, 32); const auto fp = bytes::make_span(hash).subspan(0, 32);
const auto emoji = Calls::ComputeEmojiFingerprint(fp); const auto emoji = Calls::ComputeEmojiFingerprint(fp);
result += QString::fromUtf8(" \xc2\xb7 "); result += QString::fromUtf8(" \xc2\xb7 ");
const auto base = result.size();
for (const auto &single : emoji) { for (const auto &single : emoji) {
result += single->text(); result += single->text();
} }
MTP_LOG(0, ("Got Emoji: %1.").arg(result.mid(base)));
} else {
MTP_LOG(0, ("Cleared Emoji."));
} }
return result; return result;
} }

View file

@ -74,6 +74,43 @@ constexpr auto kShortPollChainBlocksWaitFor = crl::time(1000);
} // namespace } // namespace
auto EncryptDecrypt::callback()
-> Fn<EncryptionBuffer(const EncryptionBuffer&, int64_t, bool)> {
return [that = shared_from_this()](
const EncryptionBuffer &data,
int64_t userId,
bool encrypt) -> EncryptionBuffer {
const auto libId = that->_id.load();
if (!libId) {
return {};
}
const auto channelId = tde2e_api::CallChannelId(0);
const auto slice = Slice(data);
const auto result = encrypt
? tde2e_api::call_encrypt(libId, channelId, slice)
: tde2e_api::call_decrypt(libId, userId, channelId, slice);
if (!result.is_ok()) {
return {};
}
const auto &value = result.value();
const auto start = reinterpret_cast<const uint8_t*>(value.data());
const auto end = start + value.size();
return { start, end };
};
}
void EncryptDecrypt::setCallId(CallId id) {
Expects(id.v != 0);
_id.store(id.v);
}
void EncryptDecrypt::clearCallId(CallId fromId) {
Expects(fromId.v != 0);
_id.compare_exchange_strong(fromId.v, 0);
}
Call::Call(UserId myUserId) Call::Call(UserId myUserId)
: _myUserId(myUserId) { : _myUserId(myUserId) {
const auto id = tde2e_api::key_generate_temporary_private_key(); const auto id = tde2e_api::key_generate_temporary_private_key();
@ -88,6 +125,9 @@ Call::Call(UserId myUserId)
Call::~Call() { Call::~Call() {
if (const auto id = libId()) { if (const auto id = libId()) {
if (const auto raw = _encryptDecrypt.get()) {
raw->clearCallId(_id);
}
tde2e_api::call_destroy(id); tde2e_api::call_destroy(id);
} }
} }
@ -190,10 +230,13 @@ rpl::producer<ParticipantsSet> Call::participantsSetValue() const {
} }
void Call::joined() { void Call::joined() {
shortPoll(0); if (!_id) {
if (_id) { LOG(("TdE2E Error: Call::joined() without id."));
shortPoll(1); _failure = CallFailure::Unknown;
return;
} }
shortPoll(0);
shortPoll(1);
} }
void Call::apply(int subchain, const Block &last) { void Call::apply(int subchain, const Block &last) {
@ -251,7 +294,6 @@ void Call::apply(int subchain, const Block &last) {
return; return;
} }
setId({ uint64(id.value()) }); setId({ uint64(id.value()) });
shortPoll(1);
for (auto i = 0; i != kSubChainsCount; ++i) { for (auto i = 0; i != kSubChainsCount; ++i) {
auto &entry = _subchains[i]; auto &entry = _subchains[i];
@ -278,9 +320,8 @@ void Call::setId(CallId id) {
Expects(!_id); Expects(!_id);
_id = id; _id = id;
if (const auto raw = _guardedId.get()) { if (const auto raw = _encryptDecrypt.get()) {
raw->value = id; raw->setCallId(id);
raw->exists = true;
} }
} }
@ -299,38 +340,50 @@ void Call::checkForOutboundMessages() {
void Call::apply( void Call::apply(
int subchain, int subchain,
int index, int indexAfterLast,
const Block &block, const std::vector<Block> &blocks,
bool fromShortPoll) { bool fromShortPoll) {
Expects(subchain >= 0 && subchain < kSubChainsCount); Expects(subchain >= 0 && subchain < kSubChainsCount);
Expects(_id || !fromShortPoll || !subchain);
if (!subchain && index >= _lastBlock0Height) { if (!subchain && !blocks.empty() && indexAfterLast > _lastBlock0Height) {
_lastBlock0 = block; _lastBlock0 = blocks.back();
_lastBlock0Height = index; _lastBlock0Height = indexAfterLast;
}
if (failed()) {
return;
} }
auto &entry = _subchains[subchain]; auto &entry = _subchains[subchain];
if (!fromShortPoll) { if (fromShortPoll) {
entry.lastUpdate = crl::now(); auto i = begin(entry.waiting);
if (index > entry.height || (!_id && subchain != 0)) { while (i != end(entry.waiting) && i->first < indexAfterLast) {
entry.waiting.emplace(index, block); ++i;
checkWaitingBlocks(subchain); }
entry.waiting.erase(begin(entry.waiting), i);
if (subchain && !_id && !blocks.empty()) {
LOG(("TdE2E Error: Broadcast shortpoll block without id."));
fail(CallFailure::Unknown);
return; return;
} }
} else {
entry.lastUpdate = crl::now();
} }
if (failed()) { if (failed()) {
return; return;
} else if (!_id }
|| (subchain && !entry.height && fromShortPoll)
|| (entry.height == index)) { auto index = indexAfterLast - int(blocks.size());
if (!fromShortPoll && (index > entry.height || (!_id && subchain))) {
for (const auto &block : blocks) {
entry.waiting.emplace(index++, block);
}
} else {
for (const auto &block : blocks) {
if (!_id || (entry.height == index)) {
apply(subchain, block); apply(subchain, block);
} }
entry.height = std::max(entry.height, index + 1); entry.height = std::max(entry.height, ++index);
}
entry.height = std::max(entry.height, indexAfterLast);
}
checkWaitingBlocks(subchain); checkWaitingBlocks(subchain);
} }
@ -451,37 +504,18 @@ rpl::producer<QByteArray> Call::emojiHashValue() const {
return _emojiHash.value(); return _emojiHash.value();
} }
auto Call::callbackEncryptDecrypt() void Call::registerEncryptDecrypt(std::shared_ptr<EncryptDecrypt> object) {
-> Fn<std::vector<uint8_t>(const std::vector<uint8_t>&, int64_t, bool)> { Expects(object != nullptr);
if (!_guardedId) { Expects(_encryptDecrypt == nullptr);
_guardedId = std::make_shared<GuardedCallId>();
if (const auto raw = _id ? _guardedId.get() : nullptr) { _encryptDecrypt = std::move(object);
raw->value = _id; if (_id) {
raw->exists = true; _encryptDecrypt->setCallId(_id);
} }
} }
return [id = _guardedId](
const std::vector<uint8_t> &data, rpl::lifetime &Call::lifetime() {
int64_t userId, return _lifetime;
bool encrypt) {
const auto raw = id.get();
if (!raw->exists) {
return std::vector<uint8_t>();
}
const auto libId = std::int64_t(raw->value.v);
const auto channelId = tde2e_api::CallChannelId(0);
const auto slice = Slice(data);
const auto result = encrypt
? tde2e_api::call_encrypt(libId, channelId, slice)
: tde2e_api::call_decrypt(libId, userId, channelId, slice);
if (!result.is_ok()) {
return std::vector<uint8_t>();
}
const auto &value = result.value();
const auto start = reinterpret_cast<const uint8_t*>(value.data());
const auto end = start + value.size();
return std::vector<uint8_t>{ start, end };
};
} }
} // namespace TdE2E } // namespace TdE2E

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/timer.h" #include "base/timer.h"
#include <rpl/event_stream.h> #include <rpl/event_stream.h>
#include <rpl/lifetime.h>
#include <rpl/producer.h> #include <rpl/producer.h>
#include <rpl/variable.h> #include <rpl/variable.h>
@ -66,6 +67,22 @@ enum class CallFailure {
Unknown, Unknown,
}; };
using EncryptionBuffer = std::vector<uint8_t>;
class EncryptDecrypt final
: public std::enable_shared_from_this<EncryptDecrypt> {
public:
[[nodiscard]] auto callback()
-> Fn<EncryptionBuffer(const EncryptionBuffer&, int64_t, bool)>;
void setCallId(CallId id);
void clearCallId(CallId fromId);
private:
std::atomic<uint64> _id = 0;
};
class Call final { class Call final {
public: public:
explicit Call(UserId myUserId); explicit Call(UserId myUserId);
@ -76,8 +93,8 @@ public:
void joined(); void joined();
void apply( void apply(
int subchain, int subchain,
int index, int indexAfterLast,
const Block &block, const std::vector<Block> &blocks,
bool fromShortPoll); bool fromShortPoll);
struct SubchainRequest { struct SubchainRequest {
@ -100,22 +117,16 @@ public:
[[nodiscard]] Block makeJoinBlock(); [[nodiscard]] Block makeJoinBlock();
[[nodiscard]] Block makeRemoveBlock(const base::flat_set<UserId> &ids); [[nodiscard]] Block makeRemoveBlock(const base::flat_set<UserId> &ids);
[[nodiscard]] rpl::producer<ParticipantsSet> participantsSetValue() const; [[nodiscard]] auto participantsSetValue() const
-> rpl::producer<ParticipantsSet>;
[[nodiscard]] auto callbackEncryptDecrypt() void registerEncryptDecrypt(std::shared_ptr<EncryptDecrypt> object);
-> Fn<std::vector<uint8_t>(
const std::vector<uint8_t>&, [[nodiscard]] rpl::lifetime &lifetime();
int64_t,
bool)>;
private: private:
static constexpr int kSubChainsCount = 2; static constexpr int kSubChainsCount = 2;
struct GuardedCallId {
CallId value;
std::atomic<bool> exists;
};
struct SubChainState { struct SubChainState {
base::Timer shortPollTimer; base::Timer shortPollTimer;
base::Timer waitingTimer; base::Timer waitingTimer;
@ -141,7 +152,7 @@ private:
PublicKey _myKey; PublicKey _myKey;
std::optional<CallFailure> _failure; std::optional<CallFailure> _failure;
rpl::event_stream<CallFailure> _failures; rpl::event_stream<CallFailure> _failures;
std::shared_ptr<GuardedCallId> _guardedId; std::shared_ptr<EncryptDecrypt> _encryptDecrypt;
SubChainState _subchains[kSubChainsCount]; SubChainState _subchains[kSubChainsCount];
rpl::event_stream<SubchainRequest> _subchainRequests; rpl::event_stream<SubchainRequest> _subchainRequests;
@ -153,6 +164,8 @@ private:
rpl::variable<ParticipantsSet> _participantsSet; rpl::variable<ParticipantsSet> _participantsSet;
rpl::variable<QByteArray> _emojiHash; rpl::variable<QByteArray> _emojiHash;
rpl::lifetime _lifetime;
}; };
} // namespace TdE2E } // namespace TdE2E