diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 88852f99b..d4ac5914c 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -144,7 +144,6 @@ #include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/port/IPAddress.h" -#include "td/utils/port/path.h" #include "td/utils/port/SocketFd.h" #include "td/utils/port/uname.h" #include "td/utils/Random.h" @@ -3042,6 +3041,10 @@ void Td::request(uint64 id, tl_object_ptr function) { } void Td::run_request(uint64 id, tl_object_ptr function) { + if (set_parameters_request_id_ > 0) { + pending_set_parameters_requests_.emplace_back(id, std::move(function)); + return; + } if (init_request_id_ > 0) { pending_init_requests_.emplace_back(id, std::move(function)); return; @@ -3073,9 +3076,20 @@ void Td::run_request(uint64 id, tl_object_ptr function) { switch (state_) { case State::WaitParameters: { switch (function_id) { - case td_api::setTdlibParameters::ID: - return answer_ok_query( - id, set_parameters(std::move(move_tl_object_as(function)->parameters_))); + case td_api::setTdlibParameters::ID: { + auto status = set_parameters(std::move(move_tl_object_as(function)->parameters_)); + if (status.is_error()) { + return send_closure(actor_id(this), &Td::send_error, id, std::move(status)); + } + + VLOG(td_init) << "Begin to check parameters"; + set_parameters_request_id_ = id; + auto promise = + PromiseCreator::lambda([actor_id = actor_id(this)](Result r_checked_parameters) { + send_closure(actor_id, &Td::on_parameters_checked, std::move(r_checked_parameters)); + }); + return TdDb::check_parameters(get_database_scheduler_id(), parameters_, std::move(promise)); + } default: if (is_preinitialization_request(function_id)) { break; @@ -3653,6 +3667,45 @@ int32 Td::get_database_scheduler_id() { return min(current_scheduler_id + 1, scheduler_count - 1); } +void Td::on_parameters_checked(Result r_checked_parameters) { + CHECK(set_parameters_request_id_ != 0); + if (r_checked_parameters.is_error()) { + send_closure(actor_id(this), &Td::send_error, set_parameters_request_id_, + Status::Error(400, r_checked_parameters.error().message())); + return finish_set_parameters(); + } + auto checked_parameters = r_checked_parameters.move_as_ok(); + + parameters_.database_directory = std::move(checked_parameters.database_directory); + parameters_.files_directory = std::move(checked_parameters.files_directory); + is_database_encrypted_ = checked_parameters.is_database_encrypted; + + state_ = State::Decrypt; + VLOG(td_init) << "Send authorizationStateWaitEncryptionKey"; + send_closure(actor_id(this), &Td::send_update, + td_api::make_object( + td_api::make_object(is_database_encrypted_))); + VLOG(td_init) << "Finish set parameters"; + send_closure(actor_id(this), &Td::send_result, set_parameters_request_id_, td_api::make_object()); + return finish_set_parameters(); +} + +void Td::finish_set_parameters() { + CHECK(set_parameters_request_id_ != 0); + set_parameters_request_id_ = 0; + + if (pending_set_parameters_requests_.empty()) { + return; + } + + VLOG(td_init) << "Continue to execute " << pending_set_parameters_requests_.size() << " pending requests"; + auto requests = std::move(pending_set_parameters_requests_); + for (auto &request : requests) { + run_request(request.first, std::move(request.second)); + } + CHECK(pending_set_parameters_requests_.size() < requests.size()); +} + void Td::start_init(uint64 id, string &&key) { VLOG(td_init) << "Begin to init database"; init_request_id_ = id; @@ -4213,35 +4266,6 @@ Status Td::fix_parameters(TdParameters ¶meters) { VLOG(td_init) << "Invalid api_hash"; return Status::Error(400, "Valid api_hash must be provided. Can be obtained at https://my.telegram.org"); } - - auto prepare_dir = [](string dir) -> Result { - CHECK(!dir.empty()); - if (dir.back() != TD_DIR_SLASH) { - dir += TD_DIR_SLASH; - } - TRY_STATUS(mkpath(dir, 0750)); - TRY_RESULT(real_dir, realpath(dir, true)); - if (dir.back() != TD_DIR_SLASH) { - dir += TD_DIR_SLASH; - } - return real_dir; - }; - - auto r_database_directory = prepare_dir(parameters.database_directory); - if (r_database_directory.is_error()) { - VLOG(td_init) << "Invalid database_directory"; - return Status::Error(400, PSLICE() << "Can't init database in the directory \"" << parameters.database_directory - << "\": " << r_database_directory.error()); - } - parameters.database_directory = r_database_directory.move_as_ok(); - auto r_files_directory = prepare_dir(parameters.files_directory); - if (r_files_directory.is_error()) { - VLOG(td_init) << "Invalid files_directory"; - return Status::Error(400, PSLICE() << "Can't init files directory \"" << parameters.files_directory - << "\": " << r_files_directory.error()); - } - parameters.files_directory = r_files_directory.move_as_ok(); - return Status::OK(); } @@ -4252,8 +4276,8 @@ Status Td::set_parameters(td_api::object_ptr parameters return Status::Error(400, "Parameters aren't specified"); } - if (!clean_input_string(parameters->api_hash_) && !clean_input_string(parameters->system_language_code_) && - !clean_input_string(parameters->device_model_) && !clean_input_string(parameters->system_version_) && + if (!clean_input_string(parameters->api_hash_) || !clean_input_string(parameters->system_language_code_) || + !clean_input_string(parameters->device_model_) || !clean_input_string(parameters->system_version_) || !clean_input_string(parameters->application_version_)) { VLOG(td_init) << "Wrong string encoding"; return Status::Error(400, "Strings must be encoded in UTF-8"); @@ -4271,11 +4295,7 @@ Status Td::set_parameters(td_api::object_ptr parameters parameters_.use_chat_info_db = parameters->use_chat_info_database_; parameters_.use_message_db = parameters->use_message_database_; - VLOG(td_init) << "Fix parameters..."; TRY_STATUS(fix_parameters(parameters_)); - VLOG(td_init) << "Check binlog encryption..."; - TRY_RESULT(encryption_info, TdDb::check_encryption(parameters_)); - is_database_encrypted_ = encryption_info.is_encrypted; VLOG(td_init) << "Create MtprotoHeader::Options"; options_.api_id = parameters->api_id_; @@ -4306,12 +4326,6 @@ Status Td::set_parameters(td_api::object_ptr parameters options_.is_emulator = false; options_.proxy = Proxy(); - state_ = State::Decrypt; - VLOG(td_init) << "Send authorizationStateWaitEncryptionKey"; - send_closure(actor_id(this), &Td::send_update, - td_api::make_object( - td_api::make_object(is_database_encrypted_))); - VLOG(td_init) << "Finish set parameters"; return Status::OK(); } diff --git a/td/telegram/Td.h b/td/telegram/Td.h index ad4e551ad..497ffbdd0 100644 --- a/td/telegram/Td.h +++ b/td/telegram/Td.h @@ -296,6 +296,7 @@ class Td final : public Actor { enum class State : int32 { WaitParameters, Decrypt, Run, Close } state_ = State::WaitParameters; uint64 init_request_id_ = 0; + uint64 set_parameters_request_id_ = 0; bool is_database_encrypted_ = false; FlatHashMap> result_handlers_; @@ -321,6 +322,7 @@ class Td final : public Actor { vector>> pending_preauthentication_requests_; + vector>> pending_set_parameters_requests_; vector>> pending_init_requests_; template @@ -1428,6 +1430,10 @@ class Td final : public Actor { static int32 get_database_scheduler_id(); + void on_parameters_checked(Result r_checked_parameters); + + void finish_set_parameters(); + void start_init(uint64 id, string &&key); void init(Result r_opened_database); diff --git a/td/telegram/TdDb.cpp b/td/telegram/TdDb.cpp index c24f0be43..dc4e6f617 100644 --- a/td/telegram/TdDb.cpp +++ b/td/telegram/TdDb.cpp @@ -51,19 +51,6 @@ std::string get_sqlite_path(const TdParameters ¶meters) { return parameters.database_directory + db_name + ".sqlite"; } -Result check_encryption(string path) { - Binlog binlog; - auto status = binlog.init(std::move(path), Binlog::Callback()); - if (status.is_error() && status.code() != Binlog::Error::WrongPassword) { - LOG(WARNING) << "Failed to check binlog: " << status; - return Status::Error(400, status.message()); - } - TdDb::EncryptionInfo info; - info.is_encrypted = binlog.get_info().wrong_password; - binlog.close(false /*need_sync*/).ensure(); - return info; -} - Status init_binlog(Binlog &binlog, string path, BinlogKeyValue &binlog_pmc, BinlogKeyValue &config_pmc, TdDb::OpenedDatabase &events, DbKey key) { auto callback = [&](const BinlogEvent &event) { @@ -495,8 +482,62 @@ void TdDb::open(int32 scheduler_id, TdParameters parameters, DbKey key, Promise< TdDb::TdDb() = default; TdDb::~TdDb() = default; -Result TdDb::check_encryption(const TdParameters ¶meters) { - return ::td::check_encryption(get_binlog_path(parameters)); +void TdDb::check_parameters(int32 scheduler_id, TdParameters parameters, Promise promise) { + if (scheduler_id >= 0 && Scheduler::instance()->sched_id() != scheduler_id) { + class Worker final : public Actor { + public: + void run(TdParameters parameters, Promise promise) { + TdDb::check_parameters(-1, std::move(parameters), std::move(promise)); + stop(); + } + }; + send_closure(create_actor_on_scheduler("Worker", scheduler_id), &Worker::run, std::move(parameters), + std::move(promise)); + return; + } + CheckedParameters result; + + auto prepare_dir = [](string dir) -> Result { + CHECK(!dir.empty()); + if (dir.back() != TD_DIR_SLASH) { + dir += TD_DIR_SLASH; + } + TRY_STATUS(mkpath(dir, 0750)); + TRY_RESULT(real_dir, realpath(dir, true)); + if (dir.back() != TD_DIR_SLASH) { + dir += TD_DIR_SLASH; + } + return real_dir; + }; + + auto r_database_directory = prepare_dir(parameters.database_directory); + if (r_database_directory.is_error()) { + VLOG(td_init) << "Invalid database_directory"; + return promise.set_error(Status::Error(PSLICE() + << "Can't init database in the directory \"" << parameters.database_directory + << "\": " << r_database_directory.error())); + } + result.database_directory = r_database_directory.move_as_ok(); + parameters.database_directory = result.database_directory; + + auto r_files_directory = prepare_dir(parameters.files_directory); + if (r_files_directory.is_error()) { + VLOG(td_init) << "Invalid files_directory"; + return promise.set_error(Status::Error(PSLICE() << "Can't init files directory \"" << parameters.files_directory + << "\": " << r_files_directory.error())); + } + result.files_directory = r_files_directory.move_as_ok(); + + Binlog binlog; + auto status = binlog.init(get_binlog_path(parameters), Binlog::Callback()); + if (status.is_error() && status.code() != Binlog::Error::WrongPassword) { + LOG(WARNING) << "Failed to check binlog: " << status; + return promise.set_error(std::move(status)); + } + result.is_database_encrypted = binlog.get_info().wrong_password; + binlog.close(false /*need_sync*/).ensure(); + + promise.set_value(std::move(result)); } void TdDb::change_key(DbKey key, Promise<> promise) { diff --git a/td/telegram/TdDb.h b/td/telegram/TdDb.h index 34eda69b0..957d0d842 100644 --- a/td/telegram/TdDb.h +++ b/td/telegram/TdDb.h @@ -48,6 +48,13 @@ class TdDb { TdDb &operator=(TdDb &&) = delete; ~TdDb(); + struct CheckedParameters { + string database_directory; + string files_directory; + bool is_database_encrypted{false}; + }; + static void check_parameters(int32 scheduler_id, TdParameters parameters, Promise promise); + struct OpenedDatabase { unique_ptr database; @@ -64,11 +71,6 @@ class TdDb { }; static void open(int32 scheduler_id, TdParameters parameters, DbKey key, Promise &&promise); - struct EncryptionInfo { - bool is_encrypted{false}; - }; - static Result check_encryption(const TdParameters ¶meters); - static Status destroy(const TdParameters ¶meters); std::shared_ptr get_file_db_shared(); diff --git a/test/online.cpp b/test/online.cpp index 6895c8781..458e4ba4e 100644 --- a/test/online.cpp +++ b/test/online.cpp @@ -440,7 +440,7 @@ class TestDownloadFile : public Task { begin = end; } - std::random_shuffle(as_mutable_span(ranges_), rnd); + random_shuffle(as_mutable_span(ranges_), rnd); start_chunk(); }