521 lines
15 KiB
C++
521 lines
15 KiB
C++
//
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2023
|
|
//
|
|
// 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
|