439 lines
15 KiB
C++
439 lines
15 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#pragma once
|
|
|
|
#include "base/weak_ptr.h"
|
|
#include "e2e_cloud/group/group_state.h"
|
|
#include "e2e_cloud/storage/file_atomic_blob_store.h"
|
|
#include "e2e_cloud/storage/persistent_content_store.h"
|
|
#include "e2e_cloud/transport/cloud_vault_sync_controller.h"
|
|
#include "e2e_cloud/transport/file_chunk_download_controller.h"
|
|
#include "e2e_cloud/transport/observed_content_sync_controller.h"
|
|
#include "e2e_cloud/transport/outbox_upload_controller.h"
|
|
#include "e2e_cloud/transport/public_bootstrap_discovery_controller.h"
|
|
#include "e2e_cloud/transport/public_bootstrap_sync_controller.h"
|
|
#include "e2e_cloud/transport/telegram_session_carrier_backend.h"
|
|
#include "e2e_cloud/vault/argon2id_password_kdf.h"
|
|
#include "e2e_cloud/vault/persistent_cloud_vault_anchor.h"
|
|
|
|
#include <rpl/producer.h>
|
|
#include <rpl/variable.h>
|
|
|
|
#include <QtCore/QByteArray>
|
|
#include <QtCore/QString>
|
|
|
|
#include <cstddef>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <vector>
|
|
|
|
namespace Main {
|
|
class Session;
|
|
} // namespace Main
|
|
|
|
class PeerData;
|
|
class HistoryItem;
|
|
class QImage;
|
|
|
|
namespace E2ECloud {
|
|
|
|
struct ProtectedMessageBody;
|
|
|
|
enum class DesktopVaultState {
|
|
Uninitialized,
|
|
Discovering,
|
|
Locked,
|
|
Loading,
|
|
Missing,
|
|
Creating,
|
|
Ready,
|
|
WrongPasswordOrDamaged,
|
|
RetryableTransportError,
|
|
PermanentTransportError,
|
|
DiscoveryRetryableError,
|
|
DiscoveryPermanentError,
|
|
SecurityBlocked,
|
|
};
|
|
|
|
enum class DesktopGroupCreationState {
|
|
Idle,
|
|
Preparing,
|
|
UpdatingVault,
|
|
PublishingBootstrap,
|
|
AwaitingAdmission,
|
|
AwaitingFreshness,
|
|
Ready,
|
|
RetryableTransportError,
|
|
PermanentTransportError,
|
|
LocalFailure,
|
|
};
|
|
|
|
enum class DesktopContentState {
|
|
Idle,
|
|
Synchronizing,
|
|
Ready,
|
|
AwaitingFreshness,
|
|
RetryableTransportError,
|
|
PermanentTransportError,
|
|
LocalFailure,
|
|
SecurityBlocked,
|
|
};
|
|
|
|
enum class ProtectedFileSaveResult {
|
|
Saved,
|
|
Busy,
|
|
InvalidRequest,
|
|
RetryableTransportError,
|
|
PermanentTransportError,
|
|
MissingChunks,
|
|
SecurityBlocked,
|
|
LocalFailure,
|
|
};
|
|
|
|
using ProtectedFileSaveProgress = std::function<void(
|
|
std::uint64_t receivedBytes,
|
|
std::uint64_t totalBytes)>;
|
|
|
|
struct DesktopProtectedGroupSummary {
|
|
ConversationId conversationId;
|
|
std::uint64_t telegramPeerIdBinding = 0;
|
|
QString title;
|
|
std::uint64_t generation = 0;
|
|
std::size_t contentCount = 0;
|
|
bool active = false;
|
|
bool removed = false;
|
|
bool sendingAllowed = false;
|
|
bool fileTransferPending = false;
|
|
};
|
|
|
|
struct DesktopProtectedMember {
|
|
AccountId accountId;
|
|
std::uint64_t telegramUserIdBinding = 0;
|
|
GroupRole role = GroupRole::Member;
|
|
HistoryAccess historyAccess;
|
|
std::size_t clientCount = 0;
|
|
QString accountSafetyCode;
|
|
QString pairwiseSafetyCode;
|
|
bool local = false;
|
|
};
|
|
|
|
struct DesktopProtectedSecurity {
|
|
std::uint64_t generation = 0;
|
|
QString groupSafetyCode;
|
|
std::size_t memberCount = 0;
|
|
std::size_t witnessCount = 0;
|
|
HistoryAccess defaultHistoryAccess;
|
|
bool canSetRoles = false;
|
|
bool canRemoveMembers = false;
|
|
bool canGrantHistory = false;
|
|
bool canGrantFullHistory = false;
|
|
bool canChangeDefaultHistory = false;
|
|
std::vector<DesktopProtectedMember> members;
|
|
};
|
|
|
|
class DesktopService final : public base::has_weak_ptr {
|
|
public:
|
|
explicit DesktopService(not_null<Main::Session*> session);
|
|
~DesktopService();
|
|
|
|
[[nodiscard]] DesktopVaultState vaultState() const;
|
|
[[nodiscard]] rpl::producer<DesktopVaultState> vaultStateValue() const;
|
|
[[nodiscard]] const UnlockedCloudVault *vault() const;
|
|
|
|
void ensureVaultDiscovery();
|
|
[[nodiscard]] bool unlock(QByteArray password);
|
|
[[nodiscard]] bool createVault(QByteArray password);
|
|
[[nodiscard]] bool retryCreateVault();
|
|
[[nodiscard]] DesktopGroupCreationState groupCreationState() const;
|
|
[[nodiscard]] rpl::producer<DesktopGroupCreationState>
|
|
groupCreationStateValue() const;
|
|
[[nodiscard]] bool createProtectedGroup(
|
|
not_null<PeerData*> peer,
|
|
HistoryAccess defaultHistoryAccess);
|
|
[[nodiscard]] bool retryProtectedGroupCreation();
|
|
[[nodiscard]] std::vector<DesktopProtectedGroupSummary>
|
|
protectedGroups() const;
|
|
[[nodiscard]] std::optional<ConversationId>
|
|
protectedConversationForPeer(
|
|
std::uint64_t telegramPeerIdBinding) const;
|
|
[[nodiscard]] bool isProtectedPeerForPresentation(
|
|
std::uint64_t telegramPeerIdBinding,
|
|
std::uint64_t linkedTelegramPeerIdBinding = 0);
|
|
[[nodiscard]] std::vector<ProtectedContentRecord> protectedContent(
|
|
ConversationId conversationId,
|
|
std::size_t offset,
|
|
std::size_t limit,
|
|
std::optional<ObjectKind> kind = std::nullopt) const;
|
|
[[nodiscard]] std::size_t protectedContentCount(
|
|
ConversationId conversationId,
|
|
std::optional<ObjectKind> kind = std::nullopt) const;
|
|
void materializeProtectedHistory(
|
|
ConversationId conversationId,
|
|
std::uint64_t telegramPeerIdBinding,
|
|
std::size_t limit);
|
|
[[nodiscard]] std::optional<DesktopProtectedSecurity> protectedSecurity(
|
|
ConversationId conversationId) const;
|
|
[[nodiscard]] bool sendProtectedText(
|
|
ConversationId conversationId,
|
|
QString text);
|
|
[[nodiscard]] bool editProtectedText(
|
|
ConversationId conversationId,
|
|
ObjectId targetEventObjectId,
|
|
QString text);
|
|
[[nodiscard]] bool deleteProtectedMessage(
|
|
ConversationId conversationId,
|
|
ObjectId targetEventObjectId);
|
|
[[nodiscard]] bool sendProtectedFile(
|
|
ConversationId conversationId,
|
|
QString path);
|
|
[[nodiscard]] bool sendProtectedImage(
|
|
ConversationId conversationId,
|
|
const QImage &image);
|
|
[[nodiscard]] bool cancelProtectedFileTransfer(
|
|
ConversationId conversationId);
|
|
[[nodiscard]] bool saveProtectedFile(
|
|
ConversationId conversationId,
|
|
ObjectId eventObjectId,
|
|
QString path,
|
|
std::function<void(ProtectedFileSaveResult)> callback,
|
|
ProtectedFileSaveProgress progress = {});
|
|
[[nodiscard]] bool setProtectedDefaultHistory(
|
|
ConversationId conversationId,
|
|
HistoryAccess historyAccess);
|
|
[[nodiscard]] bool setProtectedMemberHistory(
|
|
ConversationId conversationId,
|
|
AccountId accountId,
|
|
HistoryAccess historyAccess);
|
|
[[nodiscard]] bool setProtectedMemberRole(
|
|
ConversationId conversationId,
|
|
AccountId accountId,
|
|
GroupRole role);
|
|
[[nodiscard]] bool removeProtectedMember(
|
|
ConversationId conversationId,
|
|
AccountId accountId);
|
|
[[nodiscard]] DesktopContentState contentState(
|
|
ConversationId conversationId) const;
|
|
[[nodiscard]] rpl::producer<DesktopContentState>
|
|
contentStateValue() const;
|
|
[[nodiscard]] rpl::producer<std::uint64_t>
|
|
contentRevisionValue() const;
|
|
[[nodiscard]] rpl::producer<std::uint64_t>
|
|
fileTransferRevisionValue() const;
|
|
[[nodiscard]] rpl::producer<std::uint64_t>
|
|
securityRevisionValue() const;
|
|
void synchronizeProtectedContent(ConversationId conversationId);
|
|
void lock();
|
|
|
|
private:
|
|
struct PendingGroupCreation;
|
|
enum class FileDownloadCarrierSearch {
|
|
Current,
|
|
LegacyFileId,
|
|
LegacyTruncatedFileId,
|
|
Content,
|
|
Legacy,
|
|
Family,
|
|
};
|
|
struct PendingGroupJoin;
|
|
struct PendingGroupDiscovery;
|
|
enum class LocalGroupRecoveryResult;
|
|
enum class FileTransferCancellationResult;
|
|
enum class QueuedContentRecoveryResult;
|
|
|
|
[[nodiscard]] bool vaultReady() const;
|
|
[[nodiscard]] bool hasProtectedRuntimeState() const;
|
|
void scheduleSecurityLock();
|
|
void applyVaultDiscoveryResult(CloudVaultSyncCompletion result);
|
|
void applyVaultCreationDiscoveryResult(CloudVaultSyncCompletion result);
|
|
void applySyncResult(CloudVaultSyncCompletion result);
|
|
void applyGroupVaultSyncResult(CloudVaultSyncCompletion result);
|
|
void uploadPendingCreation();
|
|
void beginGroupVaultPreflight();
|
|
void uploadPendingConversationIndex();
|
|
void publishNextBootstrapObject();
|
|
void resumePendingGroupCreation();
|
|
void beginIndexedGroupJoin(const CloudVaultConversation &conversation);
|
|
void applyPublicBootstrapSyncResult(
|
|
PublicBootstrapSyncCompletion result);
|
|
[[nodiscard]] bool prepareGroupJoin(
|
|
CloudVaultConversation conversation,
|
|
VerifiedPublicGroupBootstrap verified,
|
|
std::vector<TelegramTransport::UntrustedObject> objects,
|
|
bool discovered);
|
|
void queueLoadedGroupDiscoveries();
|
|
void queueGroupDiscovery(std::uint64_t telegramPeerIdBinding);
|
|
void startNextGroupDiscovery();
|
|
void applyGroupDiscovery(PublicBootstrapSyncCompletion result);
|
|
void beginGroupObservation(ConversationId conversationId);
|
|
void resumeDeferredGroupObservations();
|
|
void scheduleGroupObservationRetry(ConversationId conversationId);
|
|
void resetGroupObservationRetry(PendingGroupCreation &group);
|
|
void applyGroupObservation(
|
|
ConversationId conversationId,
|
|
PublicBootstrapSyncCompletion result);
|
|
[[nodiscard]] bool queueFreshnessChallenge(
|
|
PendingGroupCreation &group);
|
|
[[nodiscard]] bool resumeQueuedFreshnessChallenge(
|
|
PendingGroupCreation &group,
|
|
const EncodedEnvelope &encoded);
|
|
[[nodiscard]] bool processObservedFreshness(
|
|
ConversationId conversationId,
|
|
const std::vector<TelegramTransport::UntrustedObject> &objects);
|
|
[[nodiscard]] bool processObservedSafetyGossip(
|
|
ConversationId conversationId,
|
|
const std::vector<TelegramTransport::UntrustedObject> &objects);
|
|
[[nodiscard]] bool synchronizeObservedGroupChanges(
|
|
ConversationId conversationId,
|
|
const std::vector<TelegramTransport::UntrustedObject> &objects);
|
|
void publishQueuedGroupOutbox(ConversationId conversationId);
|
|
[[nodiscard]] QueuedContentRecoveryResult recoverQueuedContentHistory(
|
|
PendingGroupCreation &group);
|
|
[[nodiscard]] bool initializeActivePipeline(
|
|
PendingGroupCreation &group);
|
|
[[nodiscard]] bool queueProtectedMessageBody(
|
|
ConversationId conversationId,
|
|
ProtectedMessageBody body,
|
|
bool pump = true);
|
|
void pumpActiveOutbox(ConversationId conversationId);
|
|
[[nodiscard]] bool prepareActiveUploadAcknowledgement(
|
|
ConversationId conversationId,
|
|
ObjectId objectId);
|
|
[[nodiscard]] bool prepareFileManifestAcknowledgement(
|
|
PendingGroupCreation &group,
|
|
ObjectId objectId);
|
|
[[nodiscard]] bool commitPreparedFileTransfer(
|
|
ConversationId conversationId);
|
|
[[nodiscard]] FileTransferCancellationResult
|
|
finishFileTransferCancellation(
|
|
PendingGroupCreation &group);
|
|
void completeActiveUpload(
|
|
ConversationId conversationId,
|
|
UploadCompletion completion);
|
|
[[nodiscard]] bool pumpFileTransfer(ConversationId conversationId);
|
|
void completeFileChunkUpload(
|
|
ConversationId conversationId,
|
|
std::uint32_t chunkIndex,
|
|
TelegramTransport::UploadResult result);
|
|
void scheduleFileTransferRetry(ConversationId conversationId);
|
|
void resetFileTransferRetry(PendingGroupCreation &group);
|
|
[[nodiscard]] bool startNextProtectedFileDownload(
|
|
ConversationId conversationId);
|
|
[[nodiscard]] bool beginFileChunkDownload(
|
|
ConversationId conversationId,
|
|
FileDownloadCarrierSearch carrierSearch);
|
|
void scheduleFileDownloadRetry(
|
|
ConversationId conversationId,
|
|
ObjectId eventObjectId,
|
|
FileDownloadCarrierSearch carrierSearch);
|
|
[[nodiscard]] FileChunkDownloadPageStatus processFileChunkDownloadPage(
|
|
ConversationId conversationId,
|
|
std::vector<TelegramTransport::UntrustedObject> objects);
|
|
void applyFileChunkDownload(
|
|
ConversationId conversationId,
|
|
ObjectId eventObjectId,
|
|
std::uint64_t operationEpoch,
|
|
FileChunkDownloadCompletion completion);
|
|
[[nodiscard]] bool writePendingProtectedFile(
|
|
ConversationId conversationId);
|
|
void continuePendingProtectedFileWrite(
|
|
ConversationId conversationId,
|
|
ObjectId eventObjectId,
|
|
std::uint64_t operationEpoch);
|
|
void finishFileChunkDownload(
|
|
ConversationId conversationId,
|
|
ProtectedFileSaveResult result);
|
|
[[nodiscard]] bool queuePendingFileManifest(
|
|
ConversationId conversationId);
|
|
[[nodiscard]] bool finalizeFileTransfer(
|
|
ConversationId conversationId);
|
|
void beginContentObservation(ConversationId conversationId);
|
|
[[nodiscard]] ObservedContentPageResult previewObservedFileManifests(
|
|
ConversationId conversationId,
|
|
const std::vector<TelegramTransport::UntrustedObject> &objects,
|
|
std::size_t objectLimit);
|
|
[[nodiscard]] ObservedContentPageResult processObservedContentPage(
|
|
ConversationId conversationId,
|
|
std::vector<TelegramTransport::UntrustedObject> objects);
|
|
void applyContentObservation(
|
|
ConversationId conversationId,
|
|
ObservedContentSyncCompletion completion);
|
|
void setContentState(
|
|
ConversationId conversationId,
|
|
DesktopContentState state);
|
|
void notifyContentRevision();
|
|
void clearMaterializedProtectedHistory(PendingGroupCreation &group);
|
|
void refreshMaterializedProtectedHistory(PendingGroupCreation &group);
|
|
void notifyFileTransferRevision();
|
|
void notifySecurityRevision();
|
|
void rememberProtectedPeerForPresentation(
|
|
std::uint64_t telegramPeerIdBinding);
|
|
[[nodiscard]] bool completeObservedJoin(
|
|
ConversationId conversationId,
|
|
const PublicBootstrapSyncCompletion &result);
|
|
bool acceptObservedHistoryGrant(
|
|
ConversationId conversationId,
|
|
const std::vector<TelegramTransport::UntrustedObject> &objects);
|
|
[[nodiscard]] bool resumeObservedJoinHistory(
|
|
ConversationId conversationId);
|
|
[[nodiscard]] bool admitObservedClient(
|
|
ConversationId conversationId);
|
|
[[nodiscard]] bool applyAdministrativeTransition(
|
|
ConversationId conversationId,
|
|
GroupTransition transition);
|
|
void handleNewTelegramItem(not_null<HistoryItem*> item);
|
|
[[nodiscard]] LocalGroupRecoveryResult restoreLocalGroup(
|
|
ConversationId conversationId,
|
|
const CloudVaultConversation *indexedConversation);
|
|
[[nodiscard]] bool commitVaultAnchor(
|
|
const UnlockedCloudVault &vault);
|
|
[[nodiscard]] bool commitVaultAnchor(
|
|
const PreparedCloudVaultUpdate &update,
|
|
const AccountPrivateIdentity &identity);
|
|
|
|
const not_null<Main::Session*> _session;
|
|
const std::uint64_t _telegramUserIdBinding = 0;
|
|
const std::uint64_t _telegramSelfPeerId = 0;
|
|
Argon2idPasswordKdf _passwordKdf;
|
|
OpenSslSha256Provider _sha256;
|
|
CloudVaultCodecV1 _vaultCodec;
|
|
CloudVaultSelector _vaultSelector;
|
|
FileAtomicBlobStore _vaultAnchorBlob;
|
|
PersistentCloudVaultAnchor _vaultAnchor;
|
|
std::unique_ptr<TelegramSessionCarrierBackend> _backend;
|
|
std::unique_ptr<TelegramCloudVaultTransport> _remote;
|
|
std::unique_ptr<CloudVaultSyncController> _sync;
|
|
QByteArray _pendingUnlockPassword;
|
|
QByteArray _unlockedPassword;
|
|
std::optional<CreatedCloudVault> _pendingCreation;
|
|
std::optional<UnlockedCloudVault> _vault;
|
|
std::unique_ptr<PendingGroupCreation> _pendingGroupCreation;
|
|
std::unique_ptr<PendingGroupJoin> _pendingGroupJoin;
|
|
std::unique_ptr<PendingGroupDiscovery> _pendingGroupDiscovery;
|
|
std::set<std::uint64_t> _groupDiscoveryQueue;
|
|
std::map<ConversationId, std::unique_ptr<PendingGroupCreation>> _groups;
|
|
std::set<std::uint64_t> _presentationProtectedPeers;
|
|
bool _presentationProtectedPeersValid = true;
|
|
rpl::variable<DesktopVaultState> _vaultState
|
|
= DesktopVaultState::Uninitialized;
|
|
rpl::variable<DesktopGroupCreationState> _groupCreationState
|
|
= DesktopGroupCreationState::Idle;
|
|
rpl::variable<DesktopContentState> _contentState
|
|
= DesktopContentState::Idle;
|
|
std::map<ConversationId, DesktopContentState> _contentStates;
|
|
rpl::variable<std::uint64_t> _contentRevision = 0;
|
|
rpl::variable<std::uint64_t> _fileTransferRevision = 0;
|
|
rpl::variable<std::uint64_t> _securityRevision = 0;
|
|
std::uint64_t _operationEpoch = 1;
|
|
bool _securityLockScheduled = false;
|
|
rpl::lifetime _lifetime;
|
|
};
|
|
|
|
} // namespace E2ECloud
|