2018-12-31 20:04:05 +01:00
|
|
|
//
|
2018-01-02 14:42:31 +01:00
|
|
|
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
|
2018-12-31 20:04:05 +01:00
|
|
|
//
|
|
|
|
// 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/secret_api.h"
|
|
|
|
#include "td/telegram/telegram_api.h"
|
|
|
|
|
|
|
|
#include "td/actor/actor.h"
|
|
|
|
#include "td/actor/PromiseFuture.h"
|
|
|
|
|
|
|
|
#include "td/mtproto/AuthKey.h"
|
|
|
|
#include "td/mtproto/crypto.h"
|
|
|
|
#include "td/mtproto/Transport.h"
|
|
|
|
|
|
|
|
#include "td/telegram/DhConfig.h"
|
|
|
|
#include "td/telegram/logevent/SecretChatEvent.h"
|
|
|
|
#include "td/telegram/MessageId.h"
|
|
|
|
#include "td/telegram/PtsManager.h"
|
|
|
|
#include "td/telegram/SecretChatDb.h"
|
|
|
|
#include "td/telegram/SecretChatId.h"
|
|
|
|
#include "td/telegram/UserId.h"
|
|
|
|
|
|
|
|
#include "td/utils/buffer.h"
|
|
|
|
#include "td/utils/ChangesProcessor.h"
|
|
|
|
#include "td/utils/Container.h"
|
|
|
|
#include "td/utils/format.h"
|
|
|
|
#include "td/utils/port/Clocks.h"
|
|
|
|
#include "td/utils/Slice.h"
|
|
|
|
#include "td/utils/Status.h"
|
|
|
|
#include "td/utils/StringBuilder.h"
|
|
|
|
#include "td/utils/Time.h"
|
|
|
|
#include "td/utils/tl_helpers.h"
|
|
|
|
|
|
|
|
#include <map>
|
|
|
|
#include <memory>
|
|
|
|
#include <tuple>
|
|
|
|
#include <utility>
|
|
|
|
|
|
|
|
namespace td {
|
|
|
|
class BinlogInterface;
|
|
|
|
class DhCache;
|
|
|
|
class NetQueryCreator;
|
|
|
|
class SequenceDispatcher;
|
|
|
|
|
|
|
|
class SecretChatActor : public NetQueryCallback {
|
|
|
|
public:
|
|
|
|
// do not change DEFAULT_LAYER, unless all it's usages are fixed
|
|
|
|
enum : int32 { DEFAULT_LAYER = 46, VOICE_NOTES_LAYER = 66, MTPROTO_2_LAYER = 73, MY_LAYER = MTPROTO_2_LAYER };
|
|
|
|
|
|
|
|
class Context {
|
|
|
|
public:
|
|
|
|
Context() = default;
|
|
|
|
Context(const Context &) = delete;
|
|
|
|
Context &operator=(const Context &) = delete;
|
|
|
|
virtual ~Context() = default;
|
|
|
|
virtual DhCallback *dh_callback() = 0;
|
|
|
|
virtual BinlogInterface *binlog() = 0;
|
|
|
|
virtual SecretChatDb *secret_chat_db() = 0;
|
|
|
|
|
|
|
|
virtual NetQueryCreator &net_query_creator() = 0;
|
|
|
|
virtual std::shared_ptr<DhConfig> dh_config() = 0;
|
|
|
|
virtual void set_dh_config(std::shared_ptr<DhConfig> dh_config) = 0;
|
|
|
|
|
|
|
|
virtual bool get_config_option_boolean(const string &name) const = 0;
|
|
|
|
|
|
|
|
virtual int32 unix_time() = 0;
|
|
|
|
|
|
|
|
virtual bool close_flag() = 0;
|
|
|
|
|
|
|
|
// We don't want to expose the whole NetQueryDispatcher, MessagesManager and ContactsManager.
|
|
|
|
// So it is more clear which parts of MessagesManager is really used. And it is much easier to create tests.
|
|
|
|
virtual void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) = 0;
|
|
|
|
|
|
|
|
virtual void on_update_secret_chat(int64 access_hash, UserId user_id, SecretChatState state, bool is_outbound,
|
|
|
|
int32 ttl, int32 date, string key_hash, int32 layer) = 0;
|
|
|
|
|
|
|
|
// Promise must be set only after the update is processed.
|
|
|
|
//
|
|
|
|
// For example, one may set promise, after update was sent to binlog. It is ok, because SecretChatsActor will delete
|
|
|
|
// this update thought binlog too. So it wouldn't be deleted before update is saved.
|
|
|
|
|
|
|
|
// inbound messages
|
|
|
|
virtual void on_inbound_message(UserId user_id, MessageId message_id, int32 date,
|
|
|
|
tl_object_ptr<telegram_api::encryptedFile> file,
|
|
|
|
tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise) = 0;
|
|
|
|
virtual void on_delete_messages(std::vector<int64> random_id, Promise<> promise) = 0;
|
|
|
|
virtual void on_flush_history(MessageId message_id, Promise<> promise) = 0;
|
|
|
|
virtual void on_read_message(int64 random_id, Promise<> promise) = 0;
|
|
|
|
virtual void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id,
|
|
|
|
Promise<> promise) = 0;
|
|
|
|
virtual void on_set_ttl(UserId user_id, MessageId message_id, int32 date, int32 ttl, int64 random_id,
|
|
|
|
Promise<> promise) = 0;
|
|
|
|
|
|
|
|
// outbound messages
|
|
|
|
virtual void on_send_message_ack(int64 random_id) = 0;
|
|
|
|
virtual void on_send_message_ok(int64 random_id, MessageId message_id, int32 date,
|
|
|
|
tl_object_ptr<telegram_api::EncryptedFile> file, Promise<> promise) = 0;
|
|
|
|
virtual void on_send_message_error(int64 random_id, Status error, Promise<> promise) = 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
SecretChatActor(int32 id, std::unique_ptr<Context> context, bool can_be_empty);
|
|
|
|
|
|
|
|
// First query to new chat must be on of this two
|
|
|
|
void update_chat(telegram_api::object_ptr<telegram_api::EncryptedChat> chat);
|
|
|
|
void create_chat(int32 user_id, int64 user_access_hash, int32 random_id, Promise<SecretChatId> promise);
|
|
|
|
void cancel_chat(Promise<> promise);
|
|
|
|
|
|
|
|
// Inbound messages
|
|
|
|
// Logevent is created by SecretChatsManager, because it must contain qts
|
|
|
|
void add_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message);
|
|
|
|
|
|
|
|
// Outbound messages
|
|
|
|
// Promise will be set just after correspoiding logevent will be SENT to binlog.
|
|
|
|
void send_message(tl_object_ptr<secret_api::DecryptedMessage> message,
|
|
|
|
tl_object_ptr<telegram_api::InputEncryptedFile> file, Promise<> promise);
|
|
|
|
void send_message_action(tl_object_ptr<secret_api::SendMessageAction> action);
|
|
|
|
void send_read_history(int32 date,
|
|
|
|
Promise<>); // no binlog event. TODO: Promise will be set after the net query is sent
|
|
|
|
void send_open_message(int64 random_id, Promise<>);
|
|
|
|
void delete_message(int64 random_id, Promise<> promise);
|
|
|
|
void delete_messages(std::vector<int64> random_ids, Promise<> promise);
|
|
|
|
void delete_all_messages(Promise<> promise);
|
|
|
|
void notify_screenshot_taken(Promise<> promise);
|
|
|
|
void send_set_ttl_message(int32 ttl, int64 random_id, Promise<> promise);
|
|
|
|
|
|
|
|
// Binlog replay interface
|
|
|
|
void replay_inbound_message(std::unique_ptr<logevent::InboundSecretMessage> message);
|
|
|
|
void replay_outbound_message(std::unique_ptr<logevent::OutboundSecretMessage> message);
|
|
|
|
void replay_close_chat(std::unique_ptr<logevent::CloseSecretChat> event);
|
|
|
|
void replay_create_chat(std::unique_ptr<logevent::CreateSecretChat> event);
|
|
|
|
void binlog_replay_finish();
|
|
|
|
|
|
|
|
private:
|
|
|
|
enum class State { Empty, SendRequest, SendAccept, WaitRequestResponse, WaitAcceptResponse, Ready, Closed };
|
|
|
|
enum { MAX_RESEND_COUNT = 1000 };
|
|
|
|
|
|
|
|
// We have git state that should be shynchronized with db.
|
|
|
|
// It is splitted into several parts because:
|
|
|
|
// 1. Some parts are BIG (auth_key, for example) and are rarely updated.
|
|
|
|
// 2. Other are frequently updated, so probably should be as small as possible.
|
|
|
|
// 3. Some parts must be updated atomically.
|
|
|
|
struct SeqNoState {
|
|
|
|
int32 message_id = 0;
|
|
|
|
int32 my_in_seq_no = 0;
|
|
|
|
int32 my_out_seq_no = 0;
|
|
|
|
int32 his_in_seq_no = 0;
|
|
|
|
|
|
|
|
int32 resend_end_seq_no = -1;
|
|
|
|
|
|
|
|
static Slice key() {
|
|
|
|
return Slice("state");
|
|
|
|
}
|
|
|
|
template <class StorerT>
|
|
|
|
void store(StorerT &storer) const {
|
|
|
|
storer.store_int(message_id);
|
|
|
|
storer.store_int(my_in_seq_no);
|
|
|
|
storer.store_int(my_out_seq_no);
|
|
|
|
storer.store_int(his_in_seq_no);
|
|
|
|
storer.store_int(resend_end_seq_no);
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class ParserT>
|
|
|
|
void parse(ParserT &parser) {
|
|
|
|
message_id = parser.fetch_int();
|
|
|
|
my_in_seq_no = parser.fetch_int();
|
|
|
|
my_out_seq_no = parser.fetch_int();
|
|
|
|
his_in_seq_no = parser.fetch_int();
|
|
|
|
resend_end_seq_no = parser.fetch_int();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ConfigState {
|
|
|
|
int32 his_layer = 8;
|
|
|
|
int32 my_layer = 8;
|
|
|
|
int32 ttl = 0;
|
|
|
|
|
|
|
|
static Slice key() {
|
|
|
|
return Slice("config");
|
|
|
|
}
|
|
|
|
template <class StorerT>
|
|
|
|
void store(StorerT &storer) const {
|
|
|
|
storer.store_int(his_layer | HAS_FLAGS);
|
|
|
|
storer.store_int(ttl);
|
|
|
|
storer.store_int(my_layer);
|
|
|
|
//for future usage
|
|
|
|
BEGIN_STORE_FLAGS();
|
|
|
|
END_STORE_FLAGS();
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class ParserT>
|
|
|
|
void parse(ParserT &parser) {
|
|
|
|
his_layer = parser.fetch_int();
|
|
|
|
ttl = parser.fetch_int();
|
|
|
|
bool has_flags = (his_layer & HAS_FLAGS) != 0;
|
|
|
|
if (has_flags) {
|
|
|
|
his_layer &= ~HAS_FLAGS;
|
|
|
|
my_layer = parser.fetch_int();
|
|
|
|
// for future usage
|
|
|
|
BEGIN_PARSE_FLAGS();
|
|
|
|
END_PARSE_FLAGS();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enum { HAS_FLAGS = 1 << 31 };
|
|
|
|
};
|
|
|
|
|
|
|
|
// PfsAction
|
|
|
|
struct PfsState {
|
|
|
|
enum State : int32 {
|
|
|
|
Empty,
|
|
|
|
WaitSendRequest,
|
|
|
|
SendRequest,
|
|
|
|
WaitRequestResponse,
|
|
|
|
WaitSendAccept,
|
|
|
|
SendAccept,
|
|
|
|
WaitAcceptResponse,
|
|
|
|
WaitSendCommit,
|
|
|
|
SendCommit
|
|
|
|
} state = Empty;
|
|
|
|
|
|
|
|
enum Flags : int32 { CanForgetOtherKey = 1 };
|
|
|
|
mtproto::AuthKey auth_key;
|
|
|
|
mtproto::AuthKey other_auth_key;
|
|
|
|
bool can_forget_other_key = true;
|
|
|
|
|
|
|
|
int32 message_id = 0; // to skip old actions
|
|
|
|
int32 wait_message_id = 0;
|
|
|
|
int64 exchange_id = 0;
|
|
|
|
int32 last_message_id = 0;
|
|
|
|
double last_timestamp = 0;
|
|
|
|
int32 last_out_seq_no = 0;
|
|
|
|
DhHandshake handshake;
|
|
|
|
|
|
|
|
static Slice key() {
|
|
|
|
return Slice("pfs_state");
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class StorerT>
|
|
|
|
void store(StorerT &storer) const {
|
|
|
|
int32 flags = 0;
|
|
|
|
if (can_forget_other_key) {
|
|
|
|
flags |= CanForgetOtherKey;
|
|
|
|
}
|
|
|
|
storer.store_int(flags);
|
|
|
|
storer.store_int(state);
|
|
|
|
auth_key.store(storer);
|
|
|
|
other_auth_key.store(storer);
|
|
|
|
storer.store_int(message_id);
|
|
|
|
storer.store_long(exchange_id);
|
|
|
|
storer.store_int(last_message_id);
|
|
|
|
storer.store_long(static_cast<int64>((last_timestamp - Time::now() + Clocks::system()) * 1000000));
|
|
|
|
storer.store_int(last_out_seq_no);
|
|
|
|
handshake.store(storer);
|
|
|
|
}
|
|
|
|
template <class ParserT>
|
|
|
|
void parse(ParserT &parser) {
|
|
|
|
int32 flags = parser.fetch_int();
|
|
|
|
can_forget_other_key = (flags & CanForgetOtherKey) != 0;
|
|
|
|
state = static_cast<State>(parser.fetch_int());
|
|
|
|
auth_key.parse(parser);
|
|
|
|
other_auth_key.parse(parser);
|
|
|
|
message_id = parser.fetch_int();
|
|
|
|
exchange_id = parser.fetch_long();
|
|
|
|
last_message_id = parser.fetch_int();
|
|
|
|
last_timestamp = static_cast<double>(parser.fetch_long()) / 1000000 - Clocks::system() + Time::now();
|
|
|
|
if (last_timestamp > Time::now_cached()) {
|
|
|
|
last_timestamp = Time::now_cached();
|
|
|
|
}
|
|
|
|
last_out_seq_no = parser.fetch_int();
|
|
|
|
handshake.parse(parser);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
friend StringBuilder &operator<<(StringBuilder &sb, const PfsState &state) {
|
|
|
|
return sb << "PfsState["
|
|
|
|
<< tag("state",
|
|
|
|
[&] {
|
|
|
|
switch (state.state) {
|
|
|
|
case PfsState::Empty:
|
|
|
|
return "Empty";
|
|
|
|
case PfsState::WaitSendRequest:
|
|
|
|
return "WaitSendRequest";
|
|
|
|
case PfsState::SendRequest:
|
|
|
|
return "SendRequest";
|
|
|
|
case PfsState::WaitRequestResponse:
|
|
|
|
return "WaitRequestResponse";
|
|
|
|
case PfsState::WaitSendAccept:
|
|
|
|
return "WaitSendAccept";
|
|
|
|
case PfsState::SendAccept:
|
|
|
|
return "SendAccept";
|
|
|
|
case PfsState::WaitAcceptResponse:
|
|
|
|
return "WaitAcceptResponse";
|
|
|
|
case PfsState::WaitSendCommit:
|
|
|
|
return "WaitSendCommit";
|
|
|
|
case PfsState::SendCommit:
|
|
|
|
return "SendCommit";
|
|
|
|
}
|
|
|
|
return "UNKNOWN";
|
|
|
|
}())
|
|
|
|
<< tag("message_id", state.message_id) << tag("auth_key", format::as_hex(state.auth_key.id()))
|
|
|
|
<< tag("last_message_id", state.last_message_id)
|
|
|
|
<< tag("other_auth_key", format::as_hex(state.other_auth_key.id()))
|
|
|
|
<< tag("can_forget", state.can_forget_other_key) << "]";
|
|
|
|
}
|
|
|
|
|
|
|
|
PfsState pfs_state_;
|
|
|
|
bool pfs_state_changed_ = false;
|
|
|
|
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionSetMessageTTL &set_ttl);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionReadMessages &read_messages);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionDeleteMessages &delete_messages);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionScreenshotMessages &screenshot);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionFlushHistory &flush_history);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionResend &resend);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionNotifyLayer ¬ify_layer);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionTyping &typing);
|
|
|
|
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionSetMessageTTL &set_ttl);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionReadMessages &read_messages);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionDeleteMessages &delete_messages);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionScreenshotMessages &screenshot);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionFlushHistory &screenshot);
|
|
|
|
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionResend &resend);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionNotifyLayer ¬ify_layer);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionTyping &typing);
|
|
|
|
|
|
|
|
// Perfect Forward Secrecy
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionRequestKey &request_key);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionAcceptKey &accept_key);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionAbortKey &abort_key);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionCommitKey &commit_key);
|
|
|
|
void on_outbound_action(secret_api::decryptedMessageActionNoop &noop);
|
|
|
|
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionRequestKey &request_key);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionAcceptKey &accept_key);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionAbortKey &abort_key);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionCommitKey &commit_key);
|
|
|
|
Status on_inbound_action(secret_api::decryptedMessageActionNoop &noop);
|
|
|
|
|
|
|
|
Status on_inbound_action(secret_api::DecryptedMessageAction &action, int32 message_id);
|
|
|
|
|
|
|
|
void on_outbound_action(secret_api::DecryptedMessageAction &action, int32 message_id);
|
|
|
|
|
|
|
|
void request_new_key();
|
|
|
|
|
|
|
|
struct AuthState {
|
|
|
|
State state = State::Empty;
|
|
|
|
int x = -1;
|
|
|
|
string key_hash;
|
|
|
|
|
|
|
|
int32 id = 0;
|
|
|
|
int64 access_hash = 0;
|
|
|
|
|
|
|
|
int32 user_id = 0;
|
|
|
|
int64 user_access_hash = 0;
|
|
|
|
int32 random_id = 0;
|
|
|
|
|
|
|
|
int32 date = 0;
|
|
|
|
|
|
|
|
DhConfig dh_config;
|
|
|
|
DhHandshake handshake;
|
|
|
|
|
|
|
|
static Slice key() {
|
|
|
|
return Slice("auth_state");
|
|
|
|
}
|
|
|
|
template <class StorerT>
|
|
|
|
void store(StorerT &storer) const {
|
|
|
|
uint32 flags = 0;
|
|
|
|
bool date_flag = date != 0;
|
|
|
|
bool key_hash_flag = true;
|
|
|
|
if (date_flag) {
|
|
|
|
flags |= 1;
|
|
|
|
}
|
|
|
|
if (key_hash_flag) {
|
|
|
|
flags |= 2;
|
|
|
|
}
|
|
|
|
storer.store_int((flags << 8) | static_cast<int32>(state));
|
|
|
|
storer.store_int(x);
|
|
|
|
|
|
|
|
storer.store_int(id);
|
|
|
|
storer.store_long(access_hash);
|
|
|
|
storer.store_int(user_id);
|
|
|
|
storer.store_long(user_access_hash);
|
|
|
|
storer.store_int(random_id);
|
|
|
|
if (date_flag) {
|
|
|
|
storer.store_int(date);
|
|
|
|
}
|
|
|
|
if (key_hash_flag) {
|
|
|
|
storer.store_string(key_hash);
|
|
|
|
}
|
|
|
|
dh_config.store(storer);
|
|
|
|
if (state == State::SendRequest || state == State::WaitRequestResponse) {
|
|
|
|
handshake.store(storer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template <class ParserT>
|
|
|
|
void parse(ParserT &parser) {
|
|
|
|
uint32 tmp = parser.fetch_int();
|
|
|
|
state = static_cast<State>(tmp & 255);
|
|
|
|
uint32 flags = tmp >> 8;
|
|
|
|
bool date_flag = (flags & 1) != 0;
|
|
|
|
bool key_hash_flag = (flags & 2) != 0;
|
|
|
|
|
|
|
|
x = parser.fetch_int();
|
|
|
|
|
|
|
|
id = parser.fetch_int();
|
|
|
|
access_hash = parser.fetch_long();
|
|
|
|
user_id = parser.fetch_int();
|
|
|
|
user_access_hash = parser.fetch_long();
|
|
|
|
random_id = parser.fetch_int();
|
|
|
|
if (date_flag) {
|
|
|
|
date = parser.fetch_int();
|
|
|
|
}
|
|
|
|
if (key_hash_flag) {
|
|
|
|
key_hash = parser.template fetch_string<std::string>();
|
|
|
|
}
|
|
|
|
dh_config.parse(parser);
|
|
|
|
if (state == State::SendRequest || state == State::WaitRequestResponse) {
|
|
|
|
handshake.parse(parser);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
std::shared_ptr<SecretChatDb> db_;
|
|
|
|
std::unique_ptr<Context> context_;
|
|
|
|
|
|
|
|
bool binlog_replay_finish_flag_ = false;
|
|
|
|
bool close_flag_ = false;
|
|
|
|
LogEvent::Id close_logevent_id_ = 0;
|
|
|
|
|
|
|
|
LogEvent::Id create_logevent_id_ = 0;
|
|
|
|
|
|
|
|
enum class QueryType : uint8 { DhConfig, EncryptedChat, Message, Ignore, DiscardEncryption };
|
|
|
|
|
|
|
|
bool can_be_empty_;
|
|
|
|
AuthState auth_state_;
|
|
|
|
ConfigState config_state_;
|
|
|
|
|
|
|
|
// Turns out, that all changes should be made made through StateChange.
|
|
|
|
//
|
|
|
|
// The problem is the time between the moment we made decision about change and
|
|
|
|
// the moment we actually apply the change to memory.
|
|
|
|
// We may accept some other change during that time, and there goes our problem
|
|
|
|
// The reason for the change may already be invalid. So we should somehow recheck change, that
|
|
|
|
// is already written to binlog, and apply it only if necessary.
|
|
|
|
// This is completly flawed.
|
|
|
|
// (A-start_save_to_binlog ----> B-start_save_to_binlog+change_memory ----> A-finish_save_to_binlog+surprise)
|
|
|
|
//
|
|
|
|
// Instead I suggest general solution that is already used with SeqNoState and Qts
|
|
|
|
// 1. We APPLY CHANGE to memory immidiately AFTER correspoding EVENT is SENT to the binlog.
|
|
|
|
// 2. We SEND CHANGE to db only after correspoiding EVENT is SAVED to the binlog.
|
|
|
|
// 3. The we are able to ERASE EVENT just AFTER the CHANGE is SAVED to the binlog.
|
|
|
|
//
|
|
|
|
// Actually the change will be saved do binlog too.
|
|
|
|
// So we can do it immidiatelly after EVENT is SENT to the binlog, because SEND CHANGE and ERASE EVENT will be
|
|
|
|
// ordered automatically.
|
|
|
|
//
|
|
|
|
// We will use common ChangeProcessor for all changes (inside one SecretChatActor).
|
|
|
|
// So all changes will be saved in exactly the same order as they are applied
|
|
|
|
|
|
|
|
template <class StateT>
|
|
|
|
class Change {
|
|
|
|
public:
|
|
|
|
Change() = default;
|
|
|
|
explicit operator bool() const {
|
|
|
|
return !data.empty();
|
|
|
|
}
|
|
|
|
explicit Change(const StateT &state) {
|
|
|
|
data = serialize(state);
|
|
|
|
message_id = state.message_id;
|
|
|
|
}
|
|
|
|
template <class StorerT>
|
|
|
|
void store(StorerT &storer) const {
|
|
|
|
// NB: rely that storer will the same as in serialize
|
|
|
|
storer.store_slice(data);
|
|
|
|
}
|
|
|
|
static Slice key() {
|
|
|
|
return StateT::key();
|
|
|
|
}
|
|
|
|
|
|
|
|
friend StringBuilder &operator<<(StringBuilder &sb, const Change<StateT> &change) {
|
|
|
|
if (change) {
|
|
|
|
StateT state;
|
|
|
|
unserialize(state, change.data).ensure();
|
|
|
|
return sb << state;
|
|
|
|
}
|
|
|
|
return sb;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32 message_id;
|
|
|
|
|
|
|
|
private:
|
|
|
|
std::string data;
|
|
|
|
};
|
|
|
|
|
|
|
|
// SeqNoState
|
|
|
|
using SeqNoStateChange = Change<SeqNoState>;
|
|
|
|
using PfsStateChange = Change<PfsState>;
|
|
|
|
struct StateChange {
|
|
|
|
// TODO(perf): Less allocations, please? May be BufferSlice instead of std::string?
|
|
|
|
SeqNoStateChange seq_no_state_change;
|
|
|
|
PfsStateChange pfs_state_change;
|
|
|
|
Promise<Unit> save_changes_finish;
|
|
|
|
};
|
|
|
|
|
|
|
|
ChangesProcessor<StateChange> changes_processor_;
|
|
|
|
int32 saved_pfs_state_message_id_;
|
|
|
|
|
|
|
|
SeqNoState seq_no_state_;
|
|
|
|
bool seq_no_state_changed_ = false;
|
|
|
|
int32 last_binlog_message_id_ = -1;
|
|
|
|
|
|
|
|
Status check_seq_no(int in_seq_no, int out_seq_no) TD_WARN_UNUSED_RESULT;
|
|
|
|
void on_his_in_seq_no_updated();
|
|
|
|
|
|
|
|
void on_seq_no_state_changed();
|
|
|
|
template <class T>
|
|
|
|
void update_seq_no_state(const T &new_seq_no_state);
|
|
|
|
void on_pfs_state_changed();
|
|
|
|
Promise<> add_changes(Promise<> save_changes_finish = Promise<>());
|
|
|
|
// called only via Promise
|
|
|
|
void on_save_changes_start(ChangesProcessor<StateChange>::Id save_changes_token);
|
|
|
|
|
|
|
|
// InboundMessage
|
|
|
|
struct InboundMessageState {
|
|
|
|
bool save_changes_finish = false;
|
|
|
|
bool save_message_finish = false;
|
|
|
|
LogEvent::Id logevent_id = 0;
|
|
|
|
int32 message_id;
|
|
|
|
};
|
|
|
|
Container<InboundMessageState> inbound_message_states_;
|
|
|
|
|
|
|
|
std::map<int32, std::unique_ptr<logevent::InboundSecretMessage>> pending_inbound_messages_;
|
|
|
|
|
|
|
|
Result<std::tuple<uint64, BufferSlice, int32>> decrypt(BufferSlice &encrypted_message);
|
|
|
|
|
|
|
|
Status do_inbound_message_encrypted(std::unique_ptr<logevent::InboundSecretMessage> message);
|
|
|
|
Status do_inbound_message_decrypted_unchecked(std::unique_ptr<logevent::InboundSecretMessage> message);
|
|
|
|
Status do_inbound_message_decrypted(std::unique_ptr<logevent::InboundSecretMessage> message);
|
|
|
|
Status do_inbound_message_decrypted_pending(std::unique_ptr<logevent::InboundSecretMessage> message);
|
|
|
|
|
|
|
|
void on_inbound_save_message_finish(uint64 state_id);
|
|
|
|
void on_inbound_save_changes_finish(uint64 state_id);
|
|
|
|
void inbound_loop(InboundMessageState *state, uint64 state_id);
|
|
|
|
|
|
|
|
// OutboundMessage
|
|
|
|
struct OutboundMessageState {
|
|
|
|
std::unique_ptr<logevent::OutboundSecretMessage> message;
|
|
|
|
|
|
|
|
Promise<> outer_send_message_finish;
|
|
|
|
Promise<> send_message_finish;
|
|
|
|
|
|
|
|
bool save_changes_finish_flag = false;
|
|
|
|
bool send_message_finish_flag = false;
|
|
|
|
bool ack_flag = false;
|
|
|
|
|
|
|
|
uint64 net_query_id = 0;
|
|
|
|
NetQueryRef net_query_ref;
|
|
|
|
bool net_query_may_fail = false;
|
|
|
|
};
|
|
|
|
std::map<uint64, uint64> random_id_to_outbound_message_state_token_;
|
|
|
|
std::map<int32, uint64> out_seq_no_to_outbound_message_state_token_;
|
|
|
|
|
|
|
|
Container<OutboundMessageState> outbound_message_states_;
|
|
|
|
|
|
|
|
enum SendFlag {
|
|
|
|
None = 0,
|
|
|
|
External = 1,
|
|
|
|
Push = 2,
|
|
|
|
};
|
|
|
|
void send_action(tl_object_ptr<secret_api::DecryptedMessageAction> action, int32 flags, Promise<> promise);
|
|
|
|
|
|
|
|
void send_message_impl(tl_object_ptr<secret_api::DecryptedMessage> message,
|
|
|
|
tl_object_ptr<telegram_api::InputEncryptedFile> file, int32 flags, Promise<> promise);
|
|
|
|
|
|
|
|
void do_outbound_message_impl(std::unique_ptr<logevent::OutboundSecretMessage>, Promise<> promise);
|
|
|
|
Result<BufferSlice> create_encrypted_message(int32 layer, int32 my_in_seq_no, int32 my_out_seq_no,
|
|
|
|
tl_object_ptr<secret_api::DecryptedMessage> &message);
|
|
|
|
|
|
|
|
NetQueryPtr create_net_query(const logevent::OutboundSecretMessage &message);
|
|
|
|
|
|
|
|
void outbound_resend(uint64 state_id);
|
|
|
|
Status outbound_rewrite_with_empty(uint64 state_id);
|
|
|
|
void on_outbound_send_message_start(uint64 state_id);
|
|
|
|
void on_outbound_send_message_result(NetQueryPtr query, Promise<NetQueryPtr> resend_promise);
|
|
|
|
void on_outbound_send_message_error(uint64 state_id, Status error, Promise<NetQueryPtr> resend_promise);
|
|
|
|
void on_outbound_send_message_finish(uint64 state_id);
|
|
|
|
void on_outbound_save_changes_finish(uint64 state_id);
|
|
|
|
void on_outbound_ack(uint64 state_id);
|
|
|
|
void on_outbound_outer_send_message_promise(uint64 state_id, Promise<> promise);
|
|
|
|
void outbound_loop(OutboundMessageState *state, uint64 state_id);
|
|
|
|
|
|
|
|
// DiscardEncryption
|
|
|
|
void on_fatal_error(Status status);
|
|
|
|
void do_close_chat_impl(std::unique_ptr<logevent::CloseSecretChat> event);
|
|
|
|
void on_discard_encryption_result(NetQueryPtr result);
|
|
|
|
|
|
|
|
// Other
|
|
|
|
template <class T>
|
|
|
|
Status save_common_info(T &update);
|
|
|
|
|
|
|
|
int32 current_layer() const {
|
|
|
|
int32 layer = MY_LAYER;
|
|
|
|
if (config_state_.his_layer < layer) {
|
|
|
|
layer = config_state_.his_layer;
|
|
|
|
}
|
|
|
|
if (layer < DEFAULT_LAYER) {
|
|
|
|
layer = DEFAULT_LAYER;
|
|
|
|
}
|
|
|
|
return layer;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ask_on_binlog_replay_finish();
|
|
|
|
|
|
|
|
void check_status(Status status);
|
|
|
|
void start_up() override;
|
|
|
|
void loop() override;
|
|
|
|
Status do_loop();
|
|
|
|
void tear_down() override;
|
|
|
|
|
|
|
|
void on_result_resendable(NetQueryPtr net_query, Promise<NetQueryPtr> promise) override;
|
|
|
|
|
|
|
|
Status run_auth();
|
|
|
|
void run_pfs();
|
|
|
|
void run_fill_gaps();
|
|
|
|
void on_send_message_ack(int64 random_id);
|
|
|
|
Status on_delete_messages(const vector<int64> &random_ids);
|
|
|
|
Status on_flush_history(int32 last_message_id);
|
|
|
|
|
|
|
|
telegram_api::object_ptr<telegram_api::inputUser> get_input_user();
|
|
|
|
telegram_api::object_ptr<telegram_api::inputEncryptedChat> get_input_chat();
|
|
|
|
|
|
|
|
Status on_update_chat(telegram_api::encryptedChatRequested &update) TD_WARN_UNUSED_RESULT;
|
|
|
|
Status on_update_chat(telegram_api::encryptedChatEmpty &update) TD_WARN_UNUSED_RESULT;
|
|
|
|
Status on_update_chat(telegram_api::encryptedChatWaiting &update) TD_WARN_UNUSED_RESULT;
|
|
|
|
Status on_update_chat(telegram_api::encryptedChat &update) TD_WARN_UNUSED_RESULT;
|
|
|
|
Status on_update_chat(telegram_api::encryptedChatDiscarded &update) TD_WARN_UNUSED_RESULT;
|
|
|
|
|
|
|
|
Status on_update_chat(NetQueryPtr query) TD_WARN_UNUSED_RESULT;
|
|
|
|
Status on_update_chat(telegram_api::object_ptr<telegram_api::EncryptedChat> chat) TD_WARN_UNUSED_RESULT;
|
|
|
|
|
|
|
|
void on_promise_error(Status error, string desc);
|
|
|
|
|
|
|
|
void get_dh_config();
|
|
|
|
Status on_dh_config(NetQueryPtr query) TD_WARN_UNUSED_RESULT;
|
|
|
|
void on_dh_config(telegram_api::messages_dhConfigNotModified &dh_not_modified);
|
|
|
|
void on_dh_config(telegram_api::messages_dhConfig &dh);
|
|
|
|
|
|
|
|
void do_create_chat_impl(std::unique_ptr<logevent::CreateSecretChat> event);
|
|
|
|
|
|
|
|
SecretChatId get_secret_chat_id() {
|
|
|
|
return SecretChatId(auth_state_.id);
|
|
|
|
}
|
|
|
|
UserId get_user_id() {
|
|
|
|
return UserId(auth_state_.user_id);
|
|
|
|
}
|
|
|
|
void send_update_ttl(int32 ttl);
|
|
|
|
void send_update_secret_chat();
|
|
|
|
void calc_key_hash();
|
|
|
|
|
|
|
|
friend inline StringBuilder &operator<<(StringBuilder &sb, const SecretChatActor::SeqNoState &state) {
|
|
|
|
return sb << "[" << tag("my_in_seq_no", state.my_in_seq_no) << tag("my_out_seq_no", state.my_out_seq_no)
|
|
|
|
<< tag("his_in_seq_no", state.his_in_seq_no) << "]";
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} // namespace td
|