tdlight/td/telegram/logevent/SecretChatEvent.h
2022-06-27 13:30:18 +03:00

521 lines
15 KiB
C++

//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once
#include "td/telegram/EncryptedFile.h"
#include "td/telegram/Global.h"
#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/secret_api.h"
#include "td/telegram/telegram_api.h"
#include "td/telegram/UserId.h"
#include "td/utils/buffer.h"
#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/Promise.h"
#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/StorerBase.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tl_helpers.h"
#include "td/utils/tl_parsers.h"
#include "td/utils/tl_storers.h"
#include <type_traits>
namespace td {
namespace log_event {
namespace detail {
template <class T>
class StorerImpl final : public Storer {
public:
explicit StorerImpl(const T &event) : event_(event) {
}
size_t size() const final {
WithContext<TlStorerCalcLength, Global *> storer;
storer.set_context(G());
storer.store_int(T::version());
td::store(static_cast<int32>(event_.get_type()), storer);
td::store(event_, storer);
return storer.get_length();
}
size_t store(uint8 *ptr) const final {
WithContext<TlStorerUnsafe, Global *> storer(ptr);
storer.set_context(G());
storer.store_int(T::version());
td::store(static_cast<int32>(event_.get_type()), storer);
td::store(event_, storer);
return static_cast<size_t>(storer.get_buf() - ptr);
}
private:
const T &event_;
};
} // namespace detail
class SecretChatEvent : public LogEvent {
public:
// append only enum
enum class Type : int32 {
InboundSecretMessage = 1,
OutboundSecretMessage = 2,
CloseSecretChat = 3,
CreateSecretChat = 4
};
virtual Type get_type() const = 0;
static constexpr int32 version() {
return 4;
}
template <class F>
static void downcast_call(Type type, F &&f);
template <class StorerT>
void store(StorerT &storer) const {
downcast_call(get_type(),
[&](auto *ptr) { static_cast<const std::decay_t<decltype(*ptr)> *>(this)->store(storer); });
}
static Result<unique_ptr<SecretChatEvent>> from_buffer_slice(BufferSlice slice) {
WithVersion<WithContext<TlBufferParser, Global *>> parser{&slice};
auto version = parser.fetch_int();
parser.set_version(version);
parser.set_context(G());
auto magic = static_cast<Type>(parser.fetch_int());
unique_ptr<SecretChatEvent> event;
downcast_call(magic, [&](auto *ptr) {
auto tmp = make_unique<std::decay_t<decltype(*ptr)>>();
tmp->parse(parser);
event = std::move(tmp);
});
parser.fetch_end();
TRY_STATUS(parser.get_status());
if (event != nullptr) {
return std::move(event);
}
return Status::Error(PSLICE() << "Unknown SecretChatEvent type: " << format::as_hex(magic));
}
};
template <class ChildT>
class SecretChatLogEventBase : public SecretChatEvent {
public:
typename SecretChatEvent::Type get_type() const final {
return ChildT::type;
}
constexpr int32 magic() const {
return static_cast<int32>(get_type());
}
};
// Internal structure
// inputEncryptedFileEmpty#1837c364 = InputEncryptedFile;
// inputEncryptedFileUploaded#64bd0306 id:long parts:int md5_checksum:string key_fingerprint:int = InputEncryptedFile;
// inputEncryptedFile#5a17b5e5 id:long access_hash:long = InputEncryptedFile;
// inputEncryptedFileBigUploaded#2dc173c8 id:long parts:int key_fingerprint:int = InputEncryptedFile;
struct EncryptedInputFile {
static constexpr int32 MAGIC = 0x4328d38a;
enum Type : int32 { Empty = 0, Uploaded = 1, BigUploaded = 2, Location = 3 } type = Type::Empty;
int64 id = 0;
int64 access_hash = 0;
int32 parts = 0;
int32 key_fingerprint = 0;
template <class StorerT>
void store(StorerT &storer) const {
using td::store;
store(MAGIC, storer);
store(type, storer);
store(id, storer);
store(access_hash, storer);
store(parts, storer);
store(key_fingerprint, storer);
}
EncryptedInputFile() = default;
EncryptedInputFile(Type type, int64 id, int64 access_hash, int32 parts, int32 key_fingerprint)
: type(type), id(id), access_hash(access_hash), parts(parts), key_fingerprint(key_fingerprint) {
}
bool empty() const {
return type == Empty;
}
template <class ParserT>
void parse(ParserT &parser) {
using td::parse;
int32 got_magic;
parse(got_magic, parser);
parse(type, parser);
parse(id, parser);
parse(access_hash, parser);
parse(parts, parser);
parse(key_fingerprint, parser);
if (got_magic != MAGIC) {
parser.set_error("EncryptedInputFile magic mismatch");
return;
}
}
static EncryptedInputFile from_input_encrypted_file(const tl_object_ptr<telegram_api::InputEncryptedFile> &from) {
if (from == nullptr) {
return EncryptedInputFile();
}
switch (from->get_id()) {
case telegram_api::inputEncryptedFileEmpty::ID:
return EncryptedInputFile{Empty, 0, 0, 0, 0};
case telegram_api::inputEncryptedFileUploaded::ID: {
auto &uploaded = static_cast<const telegram_api::inputEncryptedFileUploaded &>(*from);
return EncryptedInputFile{Uploaded, uploaded.id_, 0, uploaded.parts_, uploaded.key_fingerprint_};
}
case telegram_api::inputEncryptedFileBigUploaded::ID: {
auto &uploaded = static_cast<const telegram_api::inputEncryptedFileBigUploaded &>(*from);
return EncryptedInputFile{BigUploaded, uploaded.id_, 0, uploaded.parts_, uploaded.key_fingerprint_};
}
case telegram_api::inputEncryptedFile::ID: {
auto &uploaded = static_cast<const telegram_api::inputEncryptedFile &>(*from);
return EncryptedInputFile{Location, uploaded.id_, uploaded.access_hash_, 0, 0};
}
default:
UNREACHABLE();
return EncryptedInputFile();
}
}
tl_object_ptr<telegram_api::InputEncryptedFile> as_input_encrypted_file() const {
switch (type) {
case Empty:
return make_tl_object<telegram_api::inputEncryptedFileEmpty>();
case Uploaded:
return make_tl_object<telegram_api::inputEncryptedFileUploaded>(id, parts, "", key_fingerprint);
case BigUploaded:
return make_tl_object<telegram_api::inputEncryptedFileBigUploaded>(id, parts, key_fingerprint);
case Location:
return make_tl_object<telegram_api::inputEncryptedFile>(id, access_hash);
}
UNREACHABLE();
return nullptr;
}
};
inline StringBuilder &operator<<(StringBuilder &sb, const EncryptedInputFile &file) {
return sb << to_string(file.as_input_encrypted_file());
}
// LogEvents
// TODO: Qts and SeqNoState could be just Logevents that are updated during regenerate
class InboundSecretMessage final : public SecretChatLogEventBase<InboundSecretMessage> {
public:
static constexpr Type type = SecretChatEvent::Type::InboundSecretMessage;
int32 chat_id = 0;
int32 date = 0;
BufferSlice encrypted_message; // empty when we store event to binlog
Promise<Unit> promise;
bool is_checked = false;
// after decrypted and checked
tl_object_ptr<secret_api::decryptedMessageLayer> decrypted_message_layer;
uint64 auth_key_id = 0;
int32 message_id = 0;
int32 my_in_seq_no = -1;
int32 my_out_seq_no = -1;
int32 his_in_seq_no = -1;
int32 his_layer() const {
return decrypted_message_layer->layer_;
}
unique_ptr<EncryptedFile> file;
bool is_pending = false;
template <class StorerT>
void store(StorerT &storer) const {
using td::store;
bool has_encrypted_file = file != nullptr;
BEGIN_STORE_FLAGS();
STORE_FLAG(has_encrypted_file);
STORE_FLAG(is_pending);
STORE_FLAG(true);
END_STORE_FLAGS();
store(chat_id, storer);
store(date, storer);
// skip encrypted_message
// skip promise
// TODO
decrypted_message_layer->store(storer);
storer.store_long(static_cast<int64>(auth_key_id));
store(message_id, storer);
store(my_in_seq_no, storer);
store(my_out_seq_no, storer);
store(his_in_seq_no, storer);
if (has_encrypted_file) {
store(file, storer);
}
}
template <class ParserT>
void parse(ParserT &parser) {
using td::parse;
bool has_encrypted_file;
bool no_qts;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(has_encrypted_file);
PARSE_FLAG(is_pending);
PARSE_FLAG(no_qts);
END_PARSE_FLAGS();
if (!no_qts) {
int32 legacy_qts;
parse(legacy_qts, parser);
}
parse(chat_id, parser);
parse(date, parser);
// skip encrypted_message
// skip promise
// TODO
decrypted_message_layer = secret_api::decryptedMessageLayer::fetch(parser);
auth_key_id = static_cast<uint64>(parser.fetch_long());
parse(message_id, parser);
parse(my_in_seq_no, parser);
parse(my_out_seq_no, parser);
parse(his_in_seq_no, parser);
if (has_encrypted_file) {
parse(file, parser);
}
is_checked = true;
}
StringBuilder &print(StringBuilder &sb) const final {
return sb << "[Logevent InboundSecretMessage " << tag("id", log_event_id()) << tag("chat_id", chat_id)
<< tag("date", date) << tag("auth_key_id", format::as_hex(auth_key_id)) << tag("message_id", message_id)
<< tag("my_in_seq_no", my_in_seq_no) << tag("my_out_seq_no", my_out_seq_no)
<< tag("his_in_seq_no", his_in_seq_no) << tag("message", to_string(decrypted_message_layer))
<< tag("is_pending", is_pending) << format::cond(file != nullptr, tag("file", *file)) << "]";
}
};
class OutboundSecretMessage final : public SecretChatLogEventBase<OutboundSecretMessage> {
public:
static constexpr Type type = SecretChatEvent::Type::OutboundSecretMessage;
int32 chat_id = 0;
int64 random_id = 0;
BufferSlice encrypted_message;
EncryptedInputFile file;
int32 message_id = 0;
int32 my_in_seq_no = -1;
int32 my_out_seq_no = -1;
int32 his_in_seq_no = -1;
int32 his_layer() const {
return -1;
}
bool is_sent = false;
// need send push notification to the receiver
// should send such messages with messages_sendEncryptedService
bool need_notify_user = false;
bool is_rewritable = false;
// should notify our parent about state of this message (using context and random_id)
bool is_external = false;
bool is_silent = false;
tl_object_ptr<secret_api::DecryptedMessageAction> action;
uint64 crc = 0; // DEBUG;
// Flags:
// 2. can_fail = !file.empty() // send of other messages can't fail if chat is ok. It is usless to rewrite them with
// empty
// 3. can_rewrite_with_empty // false for almost all service messages
// TODO: combine these two functions into one macros hell. Or a lambda hell.
template <class StorerT>
void store(StorerT &storer) const {
using td::store;
store(chat_id, storer);
store(random_id, storer);
store(encrypted_message, storer);
store(file, storer);
store(message_id, storer);
store(my_in_seq_no, storer);
store(my_out_seq_no, storer);
store(his_in_seq_no, storer);
bool has_action = action != nullptr;
BEGIN_STORE_FLAGS();
STORE_FLAG(is_sent);
STORE_FLAG(need_notify_user);
STORE_FLAG(has_action);
STORE_FLAG(is_rewritable);
STORE_FLAG(is_external);
STORE_FLAG(is_silent);
END_STORE_FLAGS();
if (has_action) {
CHECK(action);
// TODO
storer.store_int(action->get_id());
action->store(storer);
}
}
template <class ParserT>
void parse(ParserT &parser) {
using td::parse;
parse(chat_id, parser);
parse(random_id, parser);
parse(encrypted_message, parser);
parse(file, parser);
parse(message_id, parser);
parse(my_in_seq_no, parser);
parse(my_out_seq_no, parser);
parse(his_in_seq_no, parser);
bool has_action;
BEGIN_PARSE_FLAGS();
PARSE_FLAG(is_sent);
PARSE_FLAG(need_notify_user);
PARSE_FLAG(has_action);
PARSE_FLAG(is_rewritable);
PARSE_FLAG(is_external);
PARSE_FLAG(is_silent);
END_PARSE_FLAGS();
if (has_action) {
// TODO:
action = secret_api::DecryptedMessageAction::fetch(parser);
}
}
StringBuilder &print(StringBuilder &sb) const final {
return sb << "[Logevent OutboundSecretMessage " << tag("id", log_event_id()) << tag("chat_id", chat_id)
<< tag("is_sent", is_sent) << tag("need_notify_user", need_notify_user)
<< tag("is_rewritable", is_rewritable) << tag("is_external", is_external) << tag("message_id", message_id)
<< tag("random_id", random_id) << tag("my_in_seq_no", my_in_seq_no) << tag("my_out_seq_no", my_out_seq_no)
<< tag("his_in_seq_no", his_in_seq_no) << tag("file", file) << tag("action", to_string(action)) << "]";
}
};
class CloseSecretChat final : public SecretChatLogEventBase<CloseSecretChat> {
public:
static constexpr Type type = SecretChatEvent::Type::CloseSecretChat;
int32 chat_id = 0;
bool delete_history = false;
bool is_already_discarded = false;
template <class StorerT>
void store(StorerT &storer) const {
using td::store;
BEGIN_STORE_FLAGS();
STORE_FLAG(delete_history);
STORE_FLAG(is_already_discarded);
END_STORE_FLAGS();
store(chat_id, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
using td::parse;
if (parser.version() >= 3) {
BEGIN_PARSE_FLAGS();
PARSE_FLAG(delete_history);
PARSE_FLAG(is_already_discarded);
END_PARSE_FLAGS();
}
parse(chat_id, parser);
}
StringBuilder &print(StringBuilder &sb) const final {
return sb << "[Logevent CloseSecretChat " << tag("id", log_event_id()) << tag("chat_id", chat_id)
<< tag("delete_history", delete_history) << tag("is_already_discarded", is_already_discarded) << "]";
}
};
class CreateSecretChat final : public SecretChatLogEventBase<CreateSecretChat> {
public:
static constexpr Type type = SecretChatEvent::Type::CreateSecretChat;
int32 random_id = 0;
UserId user_id;
int64 user_access_hash = 0;
template <class StorerT>
void store(StorerT &storer) const {
using td::store;
store(random_id, storer);
store(user_id, storer);
store(user_access_hash, storer);
}
template <class ParserT>
void parse(ParserT &parser) {
using td::parse;
parse(random_id, parser);
user_id = UserId(parser.version() >= 4 ? parser.fetch_long() : static_cast<int64>(parser.fetch_int()));
parse(user_access_hash, parser);
}
StringBuilder &print(StringBuilder &sb) const final {
return sb << "[Logevent CreateSecretChat " << tag("id", log_event_id()) << tag("chat_id", random_id) << user_id
<< "]";
}
};
template <class F>
void SecretChatEvent::downcast_call(Type type, F &&f) {
switch (type) {
case Type::InboundSecretMessage:
f(static_cast<InboundSecretMessage *>(nullptr));
break;
case Type::OutboundSecretMessage:
f(static_cast<OutboundSecretMessage *>(nullptr));
break;
case Type::CloseSecretChat:
f(static_cast<CloseSecretChat *>(nullptr));
break;
case Type::CreateSecretChat:
f(static_cast<CreateSecretChat *>(nullptr));
break;
default:
break;
}
}
} // namespace log_event
inline auto create_storer(log_event::SecretChatEvent &event) {
return log_event::detail::StorerImpl<log_event::SecretChatEvent>(event);
}
} // namespace td