diff --git a/td/telegram/SecureStorage.h b/td/telegram/SecureStorage.h index 57e26e64..2cac75b2 100644 --- a/td/telegram/SecureStorage.h +++ b/td/telegram/SecureStorage.h @@ -131,6 +131,10 @@ class Secret { int64 get_hash() const; Secret clone() const; + static constexpr size_t size() { + return sizeof(secret_.raw); + } + private: Secret(UInt256 secret, int64 hash); UInt256 secret_; diff --git a/td/telegram/SecureValue.cpp b/td/telegram/SecureValue.cpp index f1e63199..ce6dcb73 100644 --- a/td/telegram/SecureValue.cpp +++ b/td/telegram/SecureValue.cpp @@ -9,8 +9,10 @@ #include "td/telegram/files/FileManager.h" #include "td/telegram/td_api.h" #include "td/telegram/telegram_api.h" +#include "td/telegram/telegram_api.hpp" #include "td/utils/misc.h" +#include "td/utils/overloaded.h" namespace td { @@ -161,10 +163,9 @@ SecureFile get_secure_file(FileManager *file_manager, tl_object_ptrregister_remote( + FullRemoteFileLocation(FileType::Secure, secure_file->id_, secure_file->access_hash_, DcId::internal(dc_id)), + FileLocationSource::FromServer, {}, 0, 0, ""); result.encrypted_secret = secure_file->secret_.as_slice().str(); result.file_hash = secure_file->file_hash_.as_slice().str(); break; @@ -189,13 +190,30 @@ vector get_secure_files(FileManager *file_manager, } telegram_api::object_ptr get_input_secure_file_object(FileManager *file_manager, - const SecureFile &file) { - //TODO: - return nullptr; + const SecureFile &file, + SecureInputFile &input_file) { + CHECK(file_manager->get_file_view(file.file_id).file_id() == + file_manager->get_file_view(input_file.file_id).file_id()); + auto res = std::move(input_file.input_file); + if (res == nullptr) { + return file_manager->get_file_view(file.file_id).remote_location().as_input_secure_file(); + } + telegram_api::downcast_call(*res, overloaded( + [&](telegram_api::inputSecureFileUploaded &uploaded) { + uploaded.secret_ = BufferSlice(file.encrypted_secret); + uploaded.file_hash_ = BufferSlice(file.file_hash); + }, + [&](telegram_api::inputSecureFile &) { UNREACHABLE(); })); + return res; } td_api::object_ptr get_encrypted_file_object(FileManager *file_manager, const SecureFile &file) { - return file_manager->get_file_object(file.file_id); + auto file_view = file_manager->get_file_view(file.file_id); + auto file_id = file_manager->register_remote( + FullRemoteFileLocation(FileType::SecureRaw, file_view.remote_location().get_id(), + file_view.remote_location().get_access_hash(), file_view.remote_location().get_dc_id()), + FileLocationSource::FromServer, {}, 0, 0, ""); + return file_manager->get_file_object(file_id); } vector> get_encrypted_files_object(FileManager *file_manager, @@ -205,9 +223,15 @@ vector> get_encrypted_files_object(FileManager } vector> get_input_secure_files_object( - FileManager *file_manager, const vector &file) { - //TODO: - return {}; + FileManager *file_manager, const vector &files, vector &input_files) { + CHECK(files.size() == input_files.size()); + vector> res; + res.resize(files.size()); + for (size_t i = 0; i < files.size(); i++) { + res[i] = get_input_secure_file_object(file_manager, files[i], input_files[i]); + } + LOG(ERROR) << res.size(); + return res; } bool operator==(const SecureData &lhs, const SecureData &rhs) { @@ -281,11 +305,12 @@ td_api::object_ptr get_encrypted_passport_data_ob bool is_plain = value.data.hash.empty(); return td_api::make_object( get_passport_data_type_object(value.type), is_plain ? string() : value.data.data, - get_encrypted_files_object(file_manager, value.files), is_plain ? value.data.data : string(), get_encrypted_file_object(file_manager, value.selfie)); + get_encrypted_files_object(file_manager, value.files), is_plain ? value.data.data : string(), + get_encrypted_file_object(file_manager, value.selfie)); } telegram_api::object_ptr get_input_secure_value_object( - FileManager *file_manager, const EncryptedSecureValue &value) { + FileManager *file_manager, const EncryptedSecureValue &value, std::vector &input_files) { bool is_plain = value.type == SecureValueType::PhoneNumber || value.type == SecureValueType::EmailAddress; bool has_selfie = value.selfie.file_id.is_valid(); int32 flags = 0; @@ -307,9 +332,9 @@ telegram_api::object_ptr get_input_secure_value_ flags |= telegram_api::inputSecureValue::SELFIE_MASK; } return telegram_api::make_object( - 0, get_secure_value_type_telegram_object(value.type), is_plain ? nullptr : get_secure_data_object(value.data), - get_input_secure_files_object(file_manager, value.files), std::move(plain_data), - has_selfie ? get_input_secure_file_object(file_manager, value.selfie) : nullptr); + flags, get_secure_value_type_telegram_object(value.type), is_plain ? nullptr : get_secure_data_object(value.data), + get_input_secure_files_object(file_manager, value.files, input_files), std::move(plain_data), + has_selfie ? /*TODO*/ nullptr : nullptr); } vector> get_encrypted_passport_data_object( @@ -342,7 +367,8 @@ td_api::object_ptr get_encrypted_credentials_objec credentials.encrypted_secret); } -Result get_secure_value(td_api::object_ptr &&input_passport_data) { +Result get_secure_value(FileManager *file_manager, + td_api::object_ptr &&input_passport_data) { if (input_passport_data == nullptr) { return Status::Error(400, "InputPassportData must not be empty"); } @@ -350,14 +376,31 @@ Result get_secure_value(td_api::object_ptrtype_)); res.data = std::move(input_passport_data->data_); - // res.files = TODO + for (auto &file : input_passport_data->files_) { + TRY_RESULT(file_id, file_manager->get_input_file_id(FileType::Secure, std::move(file), DialogId{}, false, false, + false, true)); + res.files.push_back(file_id); + } // res.selfie = TODO return res; } +td_api::object_ptr get_passport_data_object(FileManager *file_manager, const SecureValue &value) { + std::vector> files; + files = transform(value.files, [&](FileId id) { return file_manager->get_file_object(id, true); }); + //TODO selfie + return td_api::make_object(get_passport_data_type_object(value.type), value.data, + std::move(files), nullptr); +} -Result decrypt_secure_file(FileManager *file_manager, const secure_storage::Secret &secret, +Result decrypt_secure_file(FileManager *file_manager, const secure_storage::Secret &master_secret, const SecureFile &secure_file) { - return Status::Error("TODO"); + TRY_RESULT(hash, secure_storage::ValueHash::create(secure_file.file_hash)); + TRY_RESULT(encrypted_secret, secure_storage::EncryptedSecret::create(secure_file.encrypted_secret)); + TRY_RESULT(secret, encrypted_secret.decrypt(PSLICE() << master_secret.as_slice() << hash.as_slice())); + FileEncryptionKey key{secret}; + key.set_value_hash(hash); + file_manager->set_encryption_key(secure_file.file_id, std::move(key)); + return secure_file.file_id; } Result> decrypt_secure_files(FileManager *file_manager, const secure_storage::Secret &secret, @@ -391,9 +434,9 @@ Result decrypt_encrypted_secure_value(FileManager *file_manager, co default: { TRY_RESULT(data, decrypt_secure_data(secret, encrypted_secure_value.data)); res.data = std::move(data); + TRY_RESULT(files, decrypt_secure_files(file_manager, secret, encrypted_secure_value.files)); + res.files = std::move(files); // TODO - //TRY_RESULT(files, decrypt_secure_files(file_manager, secret, encrypted_secure_value.files)); - //res.files = std::move(files); //TRY_RESULT(selfie, decrypt_secure_file(file_manager, secret, encrypted_secure_value.selfie)); //res.selfie = std::move(selfie); break; @@ -404,8 +447,28 @@ Result decrypt_encrypted_secure_value(FileManager *file_manager, co SecureFile encrypt_secure_file(FileManager *file_manager, const secure_storage::Secret &master_secret, FileId file, string &to_hash) { - //TODO: - return SecureFile{}; + auto file_view = file_manager->get_file_view(file); + if (file_view.empty()) { + return {}; + } + if (!file_view.encryption_key().is_secure()) { + LOG(ERROR) << "File has no encryption key"; + return {}; + } + if (!file_view.encryption_key().has_value_hash()) { + LOG(ERROR) << "File has no hash"; + return {}; + } + auto value_hash = file_view.encryption_key().value_hash(); + auto secret = file_view.encryption_key().secret(); + SecureFile res; + res.file_id = file; + res.file_hash = value_hash.as_slice().str(); + res.encrypted_secret = secret.encrypt(PSLICE() << master_secret.as_slice() << value_hash.as_slice()).as_slice().str(); + + to_hash.append(res.file_hash); + to_hash.append(secret.as_slice().str()); + return res; } vector encrypt_secure_files(FileManager *file_manager, const secure_storage::Secret &master_secret, @@ -442,8 +505,8 @@ EncryptedSecureValue encrypt_secure_value(FileManager *file_manager, const secur default: { string to_hash; res.data = encrypt_secure_data(master_secret, secure_value.data, to_hash); + res.files = encrypt_secure_files(file_manager, master_secret, secure_value.files, to_hash); // TODO - //res.files = encrypt_secure_files(file_manager, master_secret, secure_value.files, to_hash); //res.selfie = encrypt_secure_file(file_manager, master_secret, secure_value.selfie, to_hash); res.hash = ss::calc_value_hash(to_hash).as_slice().str(); break; diff --git a/td/telegram/SecureValue.h b/td/telegram/SecureValue.h index f2299d66..ee410cb4 100644 --- a/td/telegram/SecureValue.h +++ b/td/telegram/SecureValue.h @@ -59,8 +59,13 @@ SecureFile get_secure_file(FileManager *file_manager, tl_object_ptr get_secure_files(FileManager *file_manager, vector> &&secure_files); +struct SecureInputFile { + FileId file_id; + tl_object_ptr input_file; +}; telegram_api::object_ptr get_input_secure_file_object(FileManager *file_manager, - const SecureFile &file); + const SecureFile &file, + SecureInputFile &input_file); td_api::object_ptr get_encrypted_file_object(FileManager *file_manager, const SecureFile &file); @@ -68,7 +73,7 @@ vector> get_encrypted_files_object(FileManager const vector &files); vector> get_input_secure_files_object( - FileManager *file_manager, const vector &file); + FileManager *file_manager, const vector &file, vector &input_files); struct SecureData { string data; @@ -103,7 +108,7 @@ vector get_encrypted_secure_values( td_api::object_ptr get_encrypted_passport_data_object(FileManager *file_manager, const EncryptedSecureValue &value); telegram_api::object_ptr get_input_secure_value_object( - FileManager *file_manager, const EncryptedSecureValue &value); + FileManager *file_manager, const EncryptedSecureValue &value, vector &input_files); vector> get_encrypted_passport_data_object( FileManager *file_manager, const vector &values); @@ -129,7 +134,10 @@ class SecureValue { FileId selfie; }; -Result get_secure_value(td_api::object_ptr &&input_passport_data); +Result get_secure_value(FileManager *file_manager, + td_api::object_ptr &&input_passport_data); + +td_api::object_ptr get_passport_data_object(FileManager *file_manager, const SecureValue &value); Result decrypt_secure_file(FileManager *file_manager, const secure_storage::Secret &secret, const SecureFile &secure_file); diff --git a/td/telegram/Td.cpp b/td/telegram/Td.cpp index 60dd5b0c..8a910b5b 100644 --- a/td/telegram/Td.cpp +++ b/td/telegram/Td.cpp @@ -3800,16 +3800,17 @@ class GetTermsOfServiceRequest : public RequestActor { } }; +using TdApiSecureValue = td_api::object_ptr; class GetSecureValue : public NetQueryCallback { public: - GetSecureValue(std::string password, SecureValueType type, Promise promise) + GetSecureValue(std::string password, SecureValueType type, Promise promise) : password_(std::move(password)), type_(type), promise_(std::move(promise)) { } private: string password_; SecureValueType type_; - Promise promise_; + Promise promise_; optional encrypted_secure_value_; optional secret_; @@ -3832,8 +3833,12 @@ class GetSecureValue : public NetQueryCallback { if (!encrypted_secure_value_ || !secret_) { return; } - promise_.set_result(decrypt_encrypted_secure_value(G()->td().get_actor_unsafe()->file_manager_.get(), *secret_, - *encrypted_secure_value_)); + auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get(); + auto r_secure_value = decrypt_encrypted_secure_value(file_manager, *secret_, *encrypted_secure_value_); + if (r_secure_value.is_error()) { + return on_error(r_secure_value.move_as_error()); + } + promise_.set_result(get_passport_data_object(file_manager, r_secure_value.move_as_ok())); stop(); } void start_up() override { @@ -3868,18 +3873,61 @@ class GetSecureValue : public NetQueryCallback { class SetSecureValue : public NetQueryCallback { public: - SetSecureValue(string password, SecureValue secure_value, Promise promise) + SetSecureValue(string password, SecureValue secure_value, Promise promise) : password_(std::move(password)), secure_value_(std::move(secure_value)), promise_(std::move(promise)) { } private: string password_; SecureValue secure_value_; - Promise promise_; + Promise promise_; optional secret_; + size_t files_left_to_upload_ = 0; + vector to_upload_; + class UploadCallback; + std::shared_ptr upload_callback_; + enum class State { WaitSecret, WaitSetValue } state_ = State::WaitSecret; + class UploadCallback : public FileManager::UploadCallback { + public: + explicit UploadCallback(ActorId actor_id) : actor_id_(actor_id) { + } + + private: + ActorId actor_id_; + void on_upload_ok(FileId file_id, tl_object_ptr input_file) override { + UNREACHABLE(); + } + void on_upload_encrypted_ok(FileId file_id, tl_object_ptr input_file) override { + UNREACHABLE(); + } + void on_upload_secure_ok(FileId file_id, tl_object_ptr input_file) override { + send_closure(actor_id_, &SetSecureValue::on_upload_ok, file_id, std::move(input_file)); + } + void on_upload_error(FileId file_id, Status error) override { + send_closure(actor_id_, &SetSecureValue::on_upload_error, file_id, std::move(error)); + } + }; + + void on_upload_ok(FileId file_id, tl_object_ptr input_file) { + for (auto &info : to_upload_) { + if (info.file_id != file_id) { + continue; + } + CHECK(!info.input_file); + info.input_file = std::move(input_file); + CHECK(files_left_to_upload_ != 0); + files_left_to_upload_--; + return loop(); + } + UNREACHABLE(); + } + void on_upload_error(FileId file_id, Status error) { + return on_error(std::move(error)); + } + void on_error(Status status) { promise_.set_error(std::move(status)); stop(); @@ -3900,6 +3948,31 @@ class SetSecureValue : public NetQueryCallback { PromiseCreator::lambda([actor_id = actor_id(this)](Result r_secret) { send_closure(actor_id, &SetSecureValue::on_secret, std::move(r_secret), true); })); + auto *file_manager = G()->file_manager().get_actor_unsafe(); + + // Remove duplicated files + for (auto it = secure_value_.files.begin(); it != secure_value_.files.end();) { + bool is_duplicate = false; + for (auto pit = secure_value_.files.begin(); pit != it; pit++) { + if (file_manager->get_file_view(*it).file_id() == file_manager->get_file_view(*pit).file_id()) { + is_duplicate = true; + break; + } + } + if (is_duplicate) { + it = secure_value_.files.erase(it); + } else { + it++; + } + } + + to_upload_.resize(secure_value_.files.size()); + upload_callback_ = std::make_shared(actor_id(this)); + for (size_t i = 0; i < to_upload_.size(); i++) { + to_upload_[i].file_id = file_manager->dup_file_id(secure_value_.files[i]); + file_manager->upload(to_upload_[i].file_id, upload_callback_, 1, 0); + files_left_to_upload_++; + } } void loop() override { @@ -3907,10 +3980,14 @@ class SetSecureValue : public NetQueryCallback { if (!secret_) { return; } + if (files_left_to_upload_ != 0) { + return; + } auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get(); - auto save_secure_value = telegram_api::account_saveSecureValue( - get_input_secure_value_object(file_manager, encrypt_secure_value(file_manager, *secret_, secure_value_)), - secret_.value().get_hash()); + auto input_secure_value = get_input_secure_value_object( + file_manager, encrypt_secure_value(file_manager, *secret_, secure_value_), to_upload_); + auto save_secure_value = + telegram_api::account_saveSecureValue(std::move(input_secure_value), secret_.value().get_hash()); LOG(ERROR) << to_string(save_secure_value); auto query = G()->net_query_creator().create(create_storer(save_secure_value)); @@ -3925,6 +4002,28 @@ class SetSecureValue : public NetQueryCallback { } auto result = r_result.move_as_ok(); LOG(ERROR) << to_string(result); + auto *file_manager = G()->td().get_actor_unsafe()->file_manager_.get(); + auto encrypted_secure_value = get_encrypted_secure_value(file_manager, std::move(result)); + if (secure_value_.files.size() != encrypted_secure_value.files.size()) { + return on_error(Status::Error("Different files count")); + } + for (size_t i = 0; i < secure_value_.files.size(); i++) { + auto file_view = file_manager->get_file_view(secure_value_.files[i]); + CHECK(!file_view.empty()); + CHECK(file_view.encryption_key().has_value_hash()); + if (file_view.encryption_key().value_hash().as_slice() != encrypted_secure_value.files[i].file_hash) { + LOG(ERROR) << "hash mismatch"; + continue; + } + auto status = file_manager->merge(encrypted_secure_value.files[i].file_id, secure_value_.files[i]); + LOG_IF(ERROR, status.is_error()) << status.error(); + } + auto r_secure_value = decrypt_encrypted_secure_value(file_manager, *secret_, encrypted_secure_value); + if (r_secure_value.is_error()) { + return on_error(r_secure_value.move_as_error()); + } + promise_.set_result(get_passport_data_object(file_manager, r_secure_value.move_as_ok())); + stop(); } }; @@ -6205,7 +6304,9 @@ void Td::on_request(uint64 id, td_api::uploadFile &request) { auto file_type = request.file_type_ == nullptr ? FileType::Temp : from_td_api(*request.file_type_); bool is_secret = file_type == FileType::Encrypted || file_type == FileType::EncryptedThumbnail; - auto r_file_id = file_manager_->get_input_file_id(file_type, request.file_, DialogId(), false, is_secret, true); + bool is_secure = file_type == FileType::Secure; + auto r_file_id = file_manager_->get_input_file_id(file_type, request.file_, DialogId(), false, is_secret, + !is_secure && !is_secret, is_secure); if (r_file_id.is_error()) { return send_error_raw(id, 400, r_file_id.error().message()); } @@ -6951,15 +7052,12 @@ void Td::on_request(uint64 id, td_api::getPassportData &request) { CHECK_AUTH(); CHECK_IS_USER(); CLEAN_INPUT_STRING(request.password_); + CREATE_REQUEST_PROMISE(promise); if (request.type_ == nullptr) { - return send_error_raw(id, 400, "Type must not be empty"); + return promise.set_error(Status::Error(400, "Type must not be empty")); } create_actor("GetSecureValue", std::move(request.password_), - get_secure_value_type_td_api(std::move(request.type_)), - PromiseCreator::lambda([](Result r_value) { - LOG_IF(ERROR, r_value.is_error()) << r_value.error(); - LOG_IF(ERROR, r_value.is_ok()) << r_value.ok().data; - })) + get_secure_value_type_td_api(std::move(request.type_)), std::move(promise)) .release(); } @@ -6967,13 +7065,13 @@ void Td::on_request(uint64 id, td_api::setPassportData &request) { CHECK_AUTH(); CHECK_IS_USER(); CLEAN_INPUT_STRING(request.password_); - auto r_secure_value = get_secure_value(std::move(request.value_)); + CREATE_REQUEST_PROMISE(promise); + auto r_secure_value = get_secure_value(file_manager_.get(), std::move(request.value_)); if (r_secure_value.is_error()) { - return send_closure(actor_id(this), &Td::send_error, id, r_secure_value.move_as_error()); + return promise.set_error(r_secure_value.move_as_error()); } - create_actor( - "SetSecureValue", std::move(request.password_), r_secure_value.move_as_ok(), - PromiseCreator::lambda([](Result result) { LOG_IF(ERROR, result.is_error()) << result.error(); })) + create_actor("SetSecureValue", std::move(request.password_), r_secure_value.move_as_ok(), + std::move(promise)) .release(); } diff --git a/td/telegram/cli.cpp b/td/telegram/cli.cpp index 82646449..4aa0c849 100644 --- a/td/telegram/cli.cpp +++ b/td/telegram/cli.cpp @@ -946,21 +946,24 @@ class CliClient final : public Actor { } return make_tl_object(); } - static tl_object_ptr as_input_passport_data(string passport_data_type) { - return nullptr; - /* TODO + static tl_object_ptr as_input_passport_data(string passport_data_type, string file) { vector> files; + LOG(ERROR) << "FILE " << file; + if (!file.empty()) { + files.push_back(make_tl_object(file)); + } + auto data_type = as_passport_data_type(passport_data_type); + string data; if (passport_data_type == "address" || passport_data_type == "a") { - return make_tl_object("cucumber lives here", std::move(files)); + data = "cucumber lives here"; + } else if (passport_data_type == "email" || passport_data_type == "e") { + data = "{todo}"; + } else if (passport_data_type == "phone" || passport_data_type == "p") { + data = "{todo}"; + } else { + data = "I am cucumber"; } - if (passport_data_type == "email" || passport_data_type == "e") { - return make_tl_object("{todo}"); - } - if (passport_data_type == "phone" || passport_data_type == "p") { - return make_tl_object("{todo}"); - } - return make_tl_object("I am cucumber", std::move(files)); - */ + return make_tl_object(std::move(data_type), std::move(data), std::move(files), nullptr); } static td_api::object_ptr execute(tl_object_ptr f) { @@ -1086,8 +1089,10 @@ class CliClient final : public Actor { } else if (op == "spd") { string password; string passport_data_type; - std::tie(password, passport_data_type) = split(args); - send_request(make_tl_object(as_input_passport_data(passport_data_type), password)); + string file; + std::tie(password, args) = split(args); + std::tie(passport_data_type, file) = split(args); + send_request(make_tl_object(as_input_passport_data(passport_data_type, file), password)); } else if (op == "pdu" || op == "processDcUpdate") { string dc_id; string ip_port; diff --git a/td/telegram/files/FileDownloader.cpp b/td/telegram/files/FileDownloader.cpp index 29180dd7..50c8621d 100644 --- a/td/telegram/files/FileDownloader.cpp +++ b/td/telegram/files/FileDownloader.cpp @@ -36,7 +36,7 @@ FileDownloader::FileDownloader(const FullRemoteFileLocation &remote, const Local , callback_(std::move(callback)) , is_small_(is_small) , search_file_(search_file) { - if (!encryption_key.empty()) { + if (!encryption_key.is_secret()) { set_ordered_flag(true); } } @@ -48,6 +48,12 @@ Result FileDownloader::init() { if (local_.type() == LocalFileLocation::Type::Full) { return Status::Error("File is already downloaded"); } + if (encryption_key_.is_secure() && !encryption_key_.has_value_hash()) { + LOG(ERROR) << "Can't download Secure file with unknown value_hash"; + } + if (remote_.file_type_ == FileType::Secure) { + size_ = 0; + } int ready_part_count = 0; int32 part_size = 0; if (local_.type() == LocalFileLocation::Type::Partial) { @@ -56,7 +62,7 @@ Result FileDownloader::init() { auto result_fd = FileFd::open(path_, FileFd::Write | FileFd::Read); // TODO: check timestamps.. if (result_fd.is_ok()) { - if (!encryption_key_.empty()) { + if (encryption_key_.is_secret()) { CHECK(partial.iv_.size() == 32) << partial.iv_.size(); encryption_key_.mutable_iv() = as(partial.iv_.data()); next_part_ = partial.ready_part_count_; @@ -104,13 +110,22 @@ Status FileDownloader::on_ok(int64 size) { auto dir = get_files_dir(remote_.file_type_); std::string path; + fd_.close(); + if (encryption_key_.is_secure()) { + TRY_RESULT(file_path, open_temp_file(remote_.file_type_)); + string tmp_path; + std::tie(std::ignore, tmp_path) = std::move(file_path); + TRY_STATUS(secure_storage::decrypt_file(encryption_key_.secret(), encryption_key_.value_hash(), path_, tmp_path)); + path_ = std::move(tmp_path); + TRY_RESULT(path_stat, stat(path_)); + size = path_stat.size_; + } if (only_check_) { path = path_; } else { TRY_RESULT(perm_path, create_from_temp(path_, dir, name_)); path = std::move(perm_path); } - fd_.close(); callback_->on_ok(FullLocalFileLocation(remote_.file_type_, std::move(path), 0), size); return Status::OK(); } @@ -192,7 +207,7 @@ Result FileDownloader::should_restart_part(Part part, NetQueryPtr &net_que return false; } Result> FileDownloader::start_part(Part part, int32 part_count) { - if (!encryption_key_.empty()) { + if (encryption_key_.is_secret()) { part.size = (part.size + 15) & ~15; // fix for last part } // auto size = part.size; @@ -279,7 +294,7 @@ Result FileDownloader::process_part(Part part, NetQueryPtr net_query) { } auto padded_size = part.size; - if (!encryption_key_.empty()) { + if (encryption_key_.is_secret()) { padded_size = (part.size + 15) & ~15; } LOG(INFO) << "Got " << bytes.size() << " padded_size=" << padded_size; @@ -304,7 +319,7 @@ Result FileDownloader::process_part(Part part, NetQueryPtr net_query) { ctr_state.init(key, iv); ctr_state.decrypt(bytes.as_slice(), bytes.as_slice()); } - if (!encryption_key_.empty()) { + if (encryption_key_.is_secret()) { CHECK(next_part_ == part.id) << tag("expected part.id", next_part_) << "!=" << tag("part.id", part.id); CHECK(!next_part_stop_); next_part_++; @@ -332,10 +347,10 @@ void FileDownloader::on_progress(int32 part_count, int32 part_size, int32 ready_ if (ready_size == 0 || path_.empty()) { return; } - if (encryption_key_.empty()) { + if (encryption_key_.empty() || encryption_key_.is_secure()) { callback_->on_partial_download(PartialLocalFileLocation{remote_.file_type_, path_, part_size, ready_part_count, ""}, ready_size); - } else { + } else if (encryption_key_.is_secret()) { UInt256 iv; if (ready_part_count == next_part_) { iv = encryption_key_.mutable_iv(); @@ -345,6 +360,8 @@ void FileDownloader::on_progress(int32 part_count, int32 part_size, int32 ready_ callback_->on_partial_download(PartialLocalFileLocation{remote_.file_type_, path_, part_size, ready_part_count, Slice(iv.raw, sizeof(iv)).str()}, ready_size); + } else { + UNREACHABLE(); } } diff --git a/td/telegram/files/FileLoadManager.cpp b/td/telegram/files/FileLoadManager.cpp index 85ef8359..2836b149 100644 --- a/td/telegram/files/FileLoadManager.cpp +++ b/td/telegram/files/FileLoadManager.cpp @@ -181,6 +181,17 @@ void FileLoadManager::on_partial_download(const PartialLocalFileLocation &partia } } +void FileLoadManager::on_hash(string hash) { + auto node_id = get_link_token(); + auto node = nodes_container_.get(node_id); + if (node == nullptr) { + return; + } + if (!stop_flag_) { + send_closure(callback_, &Callback::on_hash, node->query_id_, std::move(hash)); + } +} + void FileLoadManager::on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) { auto node_id = get_link_token(); auto node = nodes_container_.get(node_id); diff --git a/td/telegram/files/FileLoadManager.h b/td/telegram/files/FileLoadManager.h index d61df5d0..e2dcc216 100644 --- a/td/telegram/files/FileLoadManager.h +++ b/td/telegram/files/FileLoadManager.h @@ -36,6 +36,7 @@ class FileLoadManager final : public Actor { virtual void on_start_download(QueryId id) = 0; virtual void on_partial_download(QueryId id, const PartialLocalFileLocation &partial_local, int64 ready_size) = 0; virtual void on_partial_upload(QueryId id, const PartialRemoteFileLocation &partial_remote, int64 ready_size) = 0; + virtual void on_hash(QueryId id, string hash) = 0; virtual void on_upload_ok(QueryId id, FileType file_type, const PartialRemoteFileLocation &remtoe, int64 size) = 0; virtual void on_upload_full_ok(QueryId id, const FullRemoteFileLocation &remote) = 0; virtual void on_download_ok(QueryId id, const FullLocalFileLocation &local, int64 size) = 0; @@ -85,6 +86,7 @@ class FileLoadManager final : public Actor { void on_start_download(); void on_partial_download(const PartialLocalFileLocation &partial_local, int64 ready_size); void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size); + void on_hash(string hash); void on_ok_download(const FullLocalFileLocation &local, int64 size); void on_ok_upload(FileType file_type, const PartialRemoteFileLocation &remote, int64 size); void on_ok_upload_full(const FullRemoteFileLocation &remote); @@ -121,6 +123,9 @@ class FileLoadManager final : public Actor { private: ActorShared actor_id_; + void on_hash(string hash) override { + send_closure(actor_id_, &FileLoadManager::on_hash, std::move(hash)); + } void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) override { send_closure(actor_id_, &FileLoadManager::on_partial_upload, partial_remote, ready_size); } diff --git a/td/telegram/files/FileLocation.h b/td/telegram/files/FileLocation.h index 3fa19d5c..24c88c02 100644 --- a/td/telegram/files/FileLocation.h +++ b/td/telegram/files/FileLocation.h @@ -10,6 +10,7 @@ #include "td/telegram/telegram_api.h" #include "td/telegram/DialogId.h" +#include "td/telegram/SecureStorage.h" #include "td/telegram/net/DcId.h" #include "td/utils/buffer.h" @@ -157,42 +158,84 @@ constexpr int32 file_type_size = static_cast(FileType::Size); extern const char *file_type_name[file_type_size]; struct FileEncryptionKey { + enum class Type { None, Secret, Secure }; FileEncryptionKey() = default; - FileEncryptionKey(Slice key, Slice iv) : key_iv_(key.size() + iv.size(), '\0') { + FileEncryptionKey(Slice key, Slice iv) : key_iv_(key.size() + iv.size(), '\0'), type_(Type::Secret) { if (key.size() != 32 || iv.size() != 32) { LOG(ERROR) << "Wrong key/iv sizes: " << key.size() << " " << iv.size(); + type_ = Type::None; return; } CHECK(key_iv_.size() == 64); - std::memcpy(&key_iv_[0], key.data(), key.size()); - std::memcpy(&key_iv_[key.size()], iv.data(), iv.size()); + MutableSlice(key_iv_).copy_from(key); + MutableSlice(key_iv_).remove_suffix(key.size()).copy_from(iv); } + + FileEncryptionKey(const secure_storage::Secret &secret) : type_(Type::Secure) { + key_iv_ = secret.as_slice().str(); + } + + bool is_secret() const { + return type_ == Type::Secret; + } + bool is_secure() const { + return type_ == Type::Secure; + } + static FileEncryptionKey create() { FileEncryptionKey res; res.key_iv_.resize(64); Random::secure_bytes(res.key_iv_); + res.type_ = Type::Secret; return res; } + static FileEncryptionKey create_secure_key() { + return FileEncryptionKey(secure_storage::Secret::create_new()); + } const UInt256 &key() const { + CHECK(is_secret()); CHECK(key_iv_.size() == 64); return *reinterpret_cast(key_iv_.data()); } Slice key_slice() const { + CHECK(is_secret()); CHECK(key_iv_.size() == 64); return Slice(key_iv_.data(), 32); } + secure_storage::Secret secret() const { + CHECK(is_secure()); + return secure_storage::Secret::create(Slice(key_iv_).truncate(32)).move_as_ok(); + } + + bool has_value_hash() const { + CHECK(is_secure()); + return key_iv_.size() > secure_storage::Secret::size(); + } + + void set_value_hash(const secure_storage::ValueHash &value_hash) { + key_iv_.resize(secure_storage::Secret::size() + value_hash.as_slice().size()); + MutableSlice(key_iv_).remove_prefix(secure_storage::Secret::size()).copy_from(value_hash.as_slice()); + } + + secure_storage::ValueHash value_hash() const { + CHECK(has_value_hash()); + return secure_storage::ValueHash::create(Slice(key_iv_).remove_prefix(secure_storage::Secret::size())).move_as_ok(); + } UInt256 &mutable_iv() { + CHECK(is_secret()); CHECK(key_iv_.size() == 64); return *reinterpret_cast(&key_iv_[0] + 32); } Slice iv_slice() const { + CHECK(is_secret()); CHECK(key_iv_.size() == 64); return Slice(key_iv_.data() + 32, 32); } int32 calc_fingerprint() const { + CHECK(is_secret()); char buf[16]; md5(key_iv_, {buf, sizeof(buf)}); return as(buf) ^ as(buf + 4); @@ -207,11 +250,17 @@ struct FileEncryptionKey { td::store(key_iv_, storer); } template - void parse(ParserT &parser) { + void parse(Type type, ParserT &parser) { td::parse(key_iv_, parser); + if (key_iv_.empty()) { + type_ = Type::None; + } else { + type_ = type; + } } string key_iv_; // TODO wrong alignment is possible + Type type_; }; inline bool operator==(const FileEncryptionKey &lhs, const FileEncryptionKey &rhs) { @@ -1141,9 +1190,11 @@ class FileData { using ::td::store; bool has_owner_dialog_id = owner_dialog_id_.is_valid(); bool has_expected_size = size_ == 0 && expected_size_ != 0; + bool encryption_key_is_secure = encryption_key_.is_secure(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_owner_dialog_id); STORE_FLAG(has_expected_size); + STORE_FLAG(encryption_key_is_secure); END_STORE_FLAGS(); if (has_owner_dialog_id) { @@ -1168,9 +1219,11 @@ class FileData { using ::td::parse; bool has_owner_dialog_id; bool has_expected_size; + bool encryption_key_is_secure; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_owner_dialog_id); PARSE_FLAG(has_expected_size); + PARSE_FLAG(encryption_key_is_secure); END_PARSE_FLAGS(); if (has_owner_dialog_id) { @@ -1193,7 +1246,8 @@ class FileData { } parse(remote_name_, parser); parse(url_, parser); - parse(encryption_key_, parser); + encryption_key_.parse(encryption_key_is_secure ? FileEncryptionKey::Type::Secure : FileEncryptionKey::Type::Secret, + parser); } }; inline StringBuilder &operator<<(StringBuilder &sb, const FileData &file_data) { diff --git a/td/telegram/files/FileManager.cpp b/td/telegram/files/FileManager.cpp index 835e14b7..f760471a 100644 --- a/td/telegram/files/FileManager.cpp +++ b/td/telegram/files/FileManager.cpp @@ -300,8 +300,13 @@ int64 FileView::local_size() const { switch (node_->local_.type()) { case LocalFileLocation::Type::Full: return node_->size_; - case LocalFileLocation::Type::Partial: + case LocalFileLocation::Type::Partial: { + if (is_encrypted_secure()) { + // File is not decrypted yet + return 0; + } return node_->local_.partial().part_size_ * node_->local_.partial().ready_part_count_; + } default: return 0; } @@ -509,6 +514,8 @@ string FileManager::get_file_name(FileType file_type, Slice path) { case FileType::Temp: case FileType::EncryptedThumbnail: case FileType::Wallpaper: + case FileType::Secure: + case FileType::SecureRaw: break; default: UNREACHABLE(); @@ -1213,6 +1220,9 @@ void FileManager::flush_to_pmc(FileNodePtr node, bool new_remote, bool new_local data.local_ = LocalFileLocation(); data.remote_ = RemoteFileLocation(); } + if (data.remote_.type() != RemoteFileLocation::Type::Full && node->encryption_key_.is_secure()) { + data.remote_ = RemoteFileLocation(); + } data.size_ = node->size_; data.expected_size_ = node->expected_size_; @@ -1670,6 +1680,10 @@ void FileManager::run_upload(FileNodePtr node, std::vector bad_parts) { if (node->get_by_hash_ || node->generate_id_ == 0 || !node->generate_was_update_) { return; } + if (file_view.has_generate_location() && file_view.generate_location().file_type_ == FileType::Secure) { + // Can't upload secure file before its size is known. + return; + } } int8 priority = 0; FileId file_id = node->main_file_id_; @@ -1701,6 +1715,14 @@ void FileManager::run_upload(FileNodePtr node, std::vector bad_parts) { LOG_IF(FATAL, !success) << "Failed to set encryption key for file " << file_id; } + // create encryption key if necessary + if (file_view.has_local_location() && file_view.local_location().file_type_ == FileType::Secure && + file_view.encryption_key().empty()) { + CHECK(!node->file_ids_.empty()); + bool success = set_encryption_key(node->file_ids_[0], FileEncryptionKey::create_secure_key()); + LOG_IF(FATAL, !success) << "Failed to set encryption key for file " << file_id; + } + if (old_priority != 0) { LOG(INFO) << "File " << file_id << " is already uploading"; CHECK(node->upload_id_ != 0); @@ -1894,7 +1916,7 @@ vector> FileManager::get_files_object(const vector FileManager::check_input_file_id(FileType type, Result result, bool is_encrypted, - bool allow_zero) { + bool allow_zero, bool is_secure) { TRY_RESULT(file_id, std::move(result)); if (allow_zero && !file_id.is_valid()) { return FileId(); @@ -1906,7 +1928,7 @@ Result FileManager::check_input_file_id(FileType type, Result re } auto file_view = FileView(file_node); FileType real_type = file_view.get_type(); - if (!is_encrypted) { + if (!is_encrypted && !is_secure) { if (real_type != type && !(real_type == FileType::Temp && file_view.has_url()) && !(is_document_type(real_type) && is_document_type(type))) { // TODO: send encrypted file to unencrypted chat @@ -1953,7 +1975,7 @@ Result FileManager::get_input_thumbnail_file_id(const tl_object_ptr FileManager::get_input_file_id(FileType type, const tl_object_ptr &file, DialogId owner_dialog_id, bool allow_zero, bool is_encrypted, - bool get_by_hash) { + bool get_by_hash, bool is_secure) { if (is_encrypted) { get_by_hash = false; } @@ -1964,6 +1986,8 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr return Status::Error(6, "InputFile not specified"); } + auto new_type = is_encrypted ? FileType::Encrypted : (is_secure ? FileType::Secure : type); + auto r_file_id = [&]() -> Result { switch (file->get_id()) { case td_api::inputFileLocal::ID: { @@ -1971,8 +1995,7 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr if (allow_zero && path.empty()) { return FileId(); } - return register_local(FullLocalFileLocation(is_encrypted ? FileType::Encrypted : type, path, 0), - owner_dialog_id, 0, get_by_hash); + return register_local(FullLocalFileLocation(new_type, path, 0), owner_dialog_id, 0, get_by_hash); } case td_api::inputFileId::ID: { FileId file_id(static_cast(file.get())->id_, 0); @@ -1990,9 +2013,8 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr } case td_api::inputFileGenerated::ID: { auto *generated_file = static_cast(file.get()); - return register_generate(is_encrypted ? FileType::Encrypted : type, FileLocationSource::FromUser, - generated_file->original_path_, generated_file->conversion_, owner_dialog_id, - generated_file->expected_size_); + return register_generate(new_type, FileLocationSource::FromUser, generated_file->original_path_, + generated_file->conversion_, owner_dialog_id, generated_file->expected_size_); } default: UNREACHABLE(); @@ -2000,7 +2022,7 @@ Result FileManager::get_input_file_id(FileType type, const tl_object_ptr } }(); - return check_input_file_id(type, std::move(r_file_id), is_encrypted, allow_zero); + return check_input_file_id(type, std::move(r_file_id), is_encrypted, allow_zero, is_secure); } vector> FileManager::get_input_documents(const vector &file_ids) { @@ -2069,6 +2091,23 @@ void FileManager::on_partial_download(QueryId query_id, const PartialLocalFileLo try_flush_node(file_node); } +void FileManager::on_hash(QueryId query_id, string hash) { + auto query = queries_container_.get(query_id); + CHECK(query != nullptr); + + auto file_id = query->file_id_; + + auto file_node = get_file_node(file_id); + if (!file_node) { + return; + } + if (file_node->upload_id_ != query_id) { + return; + } + + file_node->encryption_key_.set_value_hash(secure_storage::ValueHash::create(hash).move_as_ok()); +} + void FileManager::on_partial_upload(QueryId query_id, const PartialRemoteFileLocation &partial_remote, int64 ready_size) { auto query = queries_container_.get(query_id); diff --git a/td/telegram/files/FileManager.h b/td/telegram/files/FileManager.h index 88734930..bfa4ab91 100644 --- a/td/telegram/files/FileManager.h +++ b/td/telegram/files/FileManager.h @@ -345,7 +345,7 @@ class FileManager : public FileLoadManager::Callback { DialogId owner_dialog_id, bool is_encrypted) TD_WARN_UNUSED_RESULT; Result get_input_file_id(FileType type, const tl_object_ptr &file, DialogId owner_dialog_id, bool allow_zero, bool is_encrypted, - bool get_by_hash = false) TD_WARN_UNUSED_RESULT; + bool get_by_hash = false, bool is_secure = false) TD_WARN_UNUSED_RESULT; vector> get_input_documents(const vector &file_ids); @@ -356,8 +356,8 @@ class FileManager : public FileLoadManager::Callback { FileId parse_file(T &parser); private: - Result check_input_file_id(FileType type, Result result, bool is_encrypted, - bool allow_zero) TD_WARN_UNUSED_RESULT; + Result check_input_file_id(FileType type, Result result, bool is_encrypted, bool allow_zero, + bool is_secure) TD_WARN_UNUSED_RESULT; FileId register_url(string url, FileType file_type, FileLocationSource file_location_source, DialogId owner_dialog_id); @@ -465,6 +465,7 @@ class FileManager : public FileLoadManager::Callback { void on_start_download(QueryId query_id) override; void on_partial_download(QueryId query_id, const PartialLocalFileLocation &partial_local, int64 ready_size) override; + void on_hash(QueryId query_id, string hash) override; void on_partial_upload(QueryId query_id, const PartialRemoteFileLocation &partial_remote, int64 ready_size) override; void on_download_ok(QueryId query_id, const FullLocalFileLocation &local, int64 size) override; void on_upload_ok(QueryId query_id, FileType file_type, const PartialRemoteFileLocation &partial_remote, diff --git a/td/telegram/files/FileManager.hpp b/td/telegram/files/FileManager.hpp index be2a5cff..55a32880 100644 --- a/td/telegram/files/FileManager.hpp +++ b/td/telegram/files/FileManager.hpp @@ -39,11 +39,14 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const { bool has_encryption_key = false; bool has_expected_size = file_store_type == FileStoreType::Remote && file_view.size() == 0 && file_view.expected_size() != 0; + bool has_secure_key = false; if (file_store_type != FileStoreType::Empty) { has_encryption_key = !file_view.empty() && file_view.is_encrypted_secret(); + has_secure_key = !file_view.empty() && file_view.is_encrypted_secure(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_encryption_key); STORE_FLAG(has_expected_size); + STORE_FLAG(has_secure_key); END_STORE_FLAGS(); } @@ -99,6 +102,8 @@ void FileManager::store_file(FileId file_id, StorerT &storer, int32 ttl) const { } if (has_encryption_key) { store(file_view.encryption_key(), storer); + } else if (has_secure_key) { + store(file_view.encryption_key(), storer); } } @@ -113,11 +118,13 @@ FileId FileManager::parse_file(ParserT &parser) { bool has_encryption_key = false; bool has_expected_size = false; + bool has_secure_key = false; if (file_store_type != FileStoreType::Empty) { if (parser.version() >= static_cast(Version::StoreFileEncryptionKey)) { BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_encryption_key); PARSE_FLAG(has_expected_size); + PARSE_FLAG(has_secure_key); END_PARSE_FLAGS(); } } @@ -212,7 +219,11 @@ FileId FileManager::parse_file(ParserT &parser) { if (has_encryption_key) { FileEncryptionKey encryption_key; - parse(encryption_key, parser); + encryption_key.parse(FileEncryptionKey::Type::Secret, parser); + set_encryption_key(file_id, std::move(encryption_key)); + } else if (has_secure_key) { + FileEncryptionKey encryption_key; + encryption_key.parse(FileEncryptionKey::Type::Secure, parser); set_encryption_key(file_id, std::move(encryption_key)); } diff --git a/td/telegram/files/FileUploader.cpp b/td/telegram/files/FileUploader.cpp index 635e5c89..d3e76d98 100644 --- a/td/telegram/files/FileUploader.cpp +++ b/td/telegram/files/FileUploader.cpp @@ -8,6 +8,8 @@ #include "td/telegram/telegram_api.h" +#include "td/telegram/files/FileLoaderUtils.h" + #include "td/telegram/Global.h" #include "td/telegram/net/NetQueryDispatcher.h" @@ -16,6 +18,7 @@ #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" +#include "td/utils/port/path.h" #include "td/utils/Random.h" #include "td/utils/ScopeGuard.h" @@ -29,7 +32,7 @@ FileUploader::FileUploader(const LocalFileLocation &local, const RemoteFileLocat , encryption_key_(encryption_key) , bad_parts_(std::move(bad_parts)) , callback_(std::move(callback)) { - if (!encryption_key_.empty()) { + if (encryption_key_.is_secret()) { iv_ = encryption_key_.mutable_iv(); generate_iv_ = encryption_key_.iv_slice().str(); } @@ -83,11 +86,17 @@ Result FileUploader::on_update_local_location(const Loca SCOPE_EXIT { try_release_fd(); }; + + if (encryption_key_.is_secure() && !fd_path_.empty()) { + return Status::Error("Can't change local location for Secure file"); + } + string path; int64 local_size = 0; bool local_is_ready{false}; FileType file_type{FileType::Temp}; - if (location.type() == LocalFileLocation::Type::Empty) { + if (location.type() == LocalFileLocation::Type::Empty || + (location.type() == LocalFileLocation::Type::Partial && encryption_key_.is_secure())) { path = ""; local_size = 0; local_is_ready = false; @@ -103,6 +112,18 @@ Result FileUploader::on_update_local_location(const Loca file_type = location.full().file_type_; } + bool is_temp = false; + if (encryption_key_.is_secure() && local_is_ready) { + TRY_RESULT(file_fd_path, open_temp_file(FileType::Temp)); + file_fd_path.first.close(); + auto new_path = std::move(file_fd_path.second); + TRY_RESULT(hash, secure_storage::encrypt_file(encryption_key_.secret(), path, new_path)); + LOG(ERROR) << "ENCRYPT " << path << " " << new_path; + callback_->on_hash(hash.as_slice().str()); + path = new_path; + is_temp = true; + } + if (!path.empty() && path != fd_path_) { auto res_fd = FileFd::open(path, FileFd::Read); @@ -120,6 +141,7 @@ Result FileUploader::on_update_local_location(const Loca fd_.close(); fd_ = res_fd.move_as_ok(); fd_path_ = path; + is_temp_ = is_temp; } if (local_is_ready) { @@ -154,10 +176,18 @@ Result FileUploader::on_update_local_location(const Loca Status FileUploader::on_ok(int64 size) { fd_.close(); + if (is_temp_) { + LOG(ERROR) << "UNLINK " << fd_path_; + unlink(fd_path_).ignore(); + } return Status::OK(); } void FileUploader::on_error(Status status) { fd_.close(); + if (is_temp_) { + LOG(ERROR) << "UNLINK " << fd_path_; + unlink(fd_path_).ignore(); + } callback_->on_error(std::move(status)); } @@ -196,12 +226,12 @@ void FileUploader::after_start_parts() { Result> FileUploader::start_part(Part part, int32 part_count) { auto padded_size = part.size; - if (!encryption_key_.empty()) { + if (encryption_key_.is_secret()) { padded_size = (padded_size + 15) & ~15; } BufferSlice bytes(padded_size); TRY_RESULT(size, fd_.pread(bytes.as_slice().truncate(part.size), part.offset)); - if (!encryption_key_.empty()) { + if (encryption_key_.is_secret()) { Random::secure_bytes(bytes.as_slice().substr(part.size)); if (next_offset_ == part.offset) { aes_ige_encrypt(encryption_key_.key(), &iv_, bytes.as_slice(), bytes.as_slice()); diff --git a/td/telegram/files/FileUploader.h b/td/telegram/files/FileUploader.h index 7f2ccfe8..cc81804b 100644 --- a/td/telegram/files/FileUploader.h +++ b/td/telegram/files/FileUploader.h @@ -22,6 +22,7 @@ class FileUploader : public FileLoader { public: class Callback : public FileLoader::Callback { public: + virtual void on_hash(string hash) = 0; virtual void on_partial_upload(const PartialRemoteFileLocation &partial_remote, int64 ready_size) = 0; virtual void on_ok(FileType file_type, const PartialRemoteFileLocation &partial_remote, int64 size) = 0; virtual void on_error(Status status) = 0; @@ -52,6 +53,7 @@ class FileUploader : public FileLoader { FileFd fd_; string fd_path_; + bool is_temp_ = false; int64 file_id_; bool big_flag_;