1 //
2 // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 #include "data.h"
8
9 #include "td/telegram/Client.h"
10 #include "td/telegram/ClientActor.h"
11 #include "td/telegram/files/PartsManager.h"
12 #include "td/telegram/td_api.h"
13
14 #include "td/actor/actor.h"
15 #include "td/actor/ConcurrentScheduler.h"
16 #include "td/actor/PromiseFuture.h"
17
18 #include "td/utils/base64.h"
19 #include "td/utils/BufferedFd.h"
20 #include "td/utils/common.h"
21 #include "td/utils/filesystem.h"
22 #include "td/utils/format.h"
23 #include "td/utils/logging.h"
24 #include "td/utils/misc.h"
25 #include "td/utils/port/FileFd.h"
26 #include "td/utils/port/path.h"
27 #include "td/utils/port/sleep.h"
28 #include "td/utils/port/thread.h"
29 #include "td/utils/Random.h"
30 #include "td/utils/Slice.h"
31 #include "td/utils/SliceBuilder.h"
32 #include "td/utils/Status.h"
33 #include "td/utils/tests.h"
34
35 #include <atomic>
36 #include <cstdio>
37 #include <functional>
38 #include <map>
39 #include <memory>
40 #include <mutex>
41 #include <set>
42 #include <utility>
43
44 template <class T>
check_td_error(T & result)45 static void check_td_error(T &result) {
46 LOG_CHECK(result->get_id() != td::td_api::error::ID) << to_string(result);
47 }
48
49 class TestClient final : public td::Actor {
50 public:
TestClient(td::string name)51 explicit TestClient(td::string name) : name_(std::move(name)) {
52 }
53 struct Update {
54 td::uint64 id;
55 td::tl_object_ptr<td::td_api::Object> object;
UpdateTestClient::Update56 Update(td::uint64 id, td::tl_object_ptr<td::td_api::Object> object) : id(id), object(std::move(object)) {
57 }
58 };
59 class Listener {
60 public:
61 Listener() = default;
62 Listener(const Listener &) = delete;
63 Listener &operator=(const Listener &) = delete;
64 Listener(Listener &&) = delete;
65 Listener &operator=(Listener &&) = delete;
66 virtual ~Listener() = default;
start_listen(TestClient * client)67 virtual void start_listen(TestClient *client) {
68 }
stop_listen()69 virtual void stop_listen() {
70 }
71 virtual void on_update(std::shared_ptr<Update> update) = 0;
72 };
close(td::Promise<> close_promise)73 void close(td::Promise<> close_promise) {
74 close_promise_ = std::move(close_promise);
75 td_client_.reset();
76 }
77
make_td_callback()78 td::unique_ptr<td::TdCallback> make_td_callback() {
79 class TdCallbackImpl final : public td::TdCallback {
80 public:
81 explicit TdCallbackImpl(td::ActorId<TestClient> client) : client_(client) {
82 }
83 void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) final {
84 send_closure(client_, &TestClient::on_result, id, std::move(result));
85 }
86 void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) final {
87 send_closure(client_, &TestClient::on_error, id, std::move(error));
88 }
89 TdCallbackImpl(const TdCallbackImpl &) = delete;
90 TdCallbackImpl &operator=(const TdCallbackImpl &) = delete;
91 TdCallbackImpl(TdCallbackImpl &&) = delete;
92 TdCallbackImpl &operator=(TdCallbackImpl &&) = delete;
93 ~TdCallbackImpl() final {
94 send_closure(client_, &TestClient::on_closed);
95 }
96
97 private:
98 td::ActorId<TestClient> client_;
99 };
100 return td::make_unique<TdCallbackImpl>(actor_id(this));
101 }
102
add_listener(td::unique_ptr<Listener> listener)103 void add_listener(td::unique_ptr<Listener> listener) {
104 auto *ptr = listener.get();
105 listeners_.push_back(std::move(listener));
106 ptr->start_listen(this);
107 }
remove_listener(Listener * listener)108 void remove_listener(Listener *listener) {
109 pending_remove_.push_back(listener);
110 }
do_pending_remove_listeners()111 void do_pending_remove_listeners() {
112 for (auto listener : pending_remove_) {
113 do_remove_listener(listener);
114 }
115 pending_remove_.clear();
116 }
do_remove_listener(Listener * listener)117 void do_remove_listener(Listener *listener) {
118 for (size_t i = 0; i < listeners_.size(); i++) {
119 if (listeners_[i].get() == listener) {
120 listener->stop_listen();
121 listeners_.erase(listeners_.begin() + i);
122 break;
123 }
124 }
125 }
126
on_result(td::uint64 id,td::tl_object_ptr<td::td_api::Object> result)127 void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) {
128 on_update(std::make_shared<Update>(id, std::move(result)));
129 }
on_error(td::uint64 id,td::tl_object_ptr<td::td_api::error> error)130 void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) {
131 on_update(std::make_shared<Update>(id, std::move(error)));
132 }
on_update(std::shared_ptr<Update> update)133 void on_update(std::shared_ptr<Update> update) {
134 for (auto &listener : listeners_) {
135 listener->on_update(update);
136 }
137 do_pending_remove_listeners();
138 }
139
on_closed()140 void on_closed() {
141 stop();
142 }
143
start_up()144 void start_up() final {
145 td::rmrf(name_).ignore();
146 auto old_context = set_context(std::make_shared<td::ActorContext>());
147 set_tag(name_);
148 LOG(INFO) << "START UP!";
149
150 td_client_ = td::create_actor<td::ClientActor>("Td-proxy", make_td_callback());
151 }
152
153 td::ActorOwn<td::ClientActor> td_client_;
154
155 td::string name_;
156
157 private:
158 td::vector<td::unique_ptr<Listener>> listeners_;
159 td::vector<Listener *> pending_remove_;
160
161 td::Promise<> close_promise_;
162 };
163
164 class TestClinetTask : public TestClient::Listener {
165 public:
on_update(std::shared_ptr<TestClient::Update> update)166 void on_update(std::shared_ptr<TestClient::Update> update) final {
167 auto it = sent_queries_.find(update->id);
168 if (it != sent_queries_.end()) {
169 it->second(std::move(update->object));
170 sent_queries_.erase(it);
171 }
172 process_update(update);
173 }
start_listen(TestClient * client)174 void start_listen(TestClient *client) final {
175 client_ = client;
176 start_up();
177 }
process_update(std::shared_ptr<TestClient::Update> update)178 virtual void process_update(std::shared_ptr<TestClient::Update> update) {
179 }
180
181 template <class F>
send_query(td::tl_object_ptr<td::td_api::Function> function,F callback)182 void send_query(td::tl_object_ptr<td::td_api::Function> function, F callback) {
183 auto id = current_query_id_++;
184 sent_queries_[id] = std::forward<F>(callback);
185 send_closure(client_->td_client_, &td::ClientActor::request, id, std::move(function));
186 }
187
188 protected:
189 std::map<td::uint64, std::function<void(td::tl_object_ptr<td::td_api::Object>)>> sent_queries_;
190 TestClient *client_ = nullptr;
191 td::uint64 current_query_id_ = 1;
192
start_up()193 virtual void start_up() {
194 }
stop()195 void stop() {
196 client_->remove_listener(this);
197 }
198 };
199
200 class DoAuthentication final : public TestClinetTask {
201 public:
DoAuthentication(td::string name,td::string phone,td::string code,td::Promise<> promise)202 DoAuthentication(td::string name, td::string phone, td::string code, td::Promise<> promise)
203 : name_(std::move(name)), phone_(std::move(phone)), code_(std::move(code)), promise_(std::move(promise)) {
204 }
start_up()205 void start_up() final {
206 send_query(td::make_tl_object<td::td_api::getAuthorizationState>(),
207 [this](auto res) { this->process_authorization_state(std::move(res)); });
208 }
process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state)209 void process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state) {
210 start_flag_ = true;
211 td::tl_object_ptr<td::td_api::Function> function;
212 switch (authorization_state->get_id()) {
213 case td::td_api::authorizationStateWaitEncryptionKey::ID:
214 function = td::make_tl_object<td::td_api::checkDatabaseEncryptionKey>();
215 break;
216 case td::td_api::authorizationStateWaitPhoneNumber::ID:
217 function = td::make_tl_object<td::td_api::setAuthenticationPhoneNumber>(phone_, nullptr);
218 break;
219 case td::td_api::authorizationStateWaitCode::ID:
220 function = td::make_tl_object<td::td_api::checkAuthenticationCode>(code_);
221 break;
222 case td::td_api::authorizationStateWaitRegistration::ID:
223 function = td::make_tl_object<td::td_api::registerUser>(name_, "");
224 break;
225 case td::td_api::authorizationStateWaitTdlibParameters::ID: {
226 auto parameters = td::td_api::make_object<td::td_api::tdlibParameters>();
227 parameters->use_test_dc_ = true;
228 parameters->database_directory_ = name_ + TD_DIR_SLASH;
229 parameters->use_message_database_ = true;
230 parameters->use_secret_chats_ = true;
231 parameters->api_id_ = 94575;
232 parameters->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
233 parameters->system_language_code_ = "en";
234 parameters->device_model_ = "Desktop";
235 parameters->application_version_ = "tdclient-test";
236 parameters->ignore_file_names_ = false;
237 parameters->enable_storage_optimizer_ = true;
238 function = td::td_api::make_object<td::td_api::setTdlibParameters>(std::move(parameters));
239 break;
240 }
241 case td::td_api::authorizationStateReady::ID:
242 on_authorization_ready();
243 return;
244 default:
245 LOG(ERROR) << "Unexpected authorization state " << to_string(authorization_state);
246 UNREACHABLE();
247 }
248 send_query(std::move(function), [](auto res) { LOG_CHECK(res->get_id() == td::td_api::ok::ID) << to_string(res); });
249 }
on_authorization_ready()250 void on_authorization_ready() {
251 LOG(INFO) << "GOT AUTHORIZED";
252 stop();
253 }
254
255 private:
256 td::string name_;
257 td::string phone_;
258 td::string code_;
259 td::Promise<> promise_;
260 bool start_flag_{false};
261
process_update(std::shared_ptr<TestClient::Update> update)262 void process_update(std::shared_ptr<TestClient::Update> update) final {
263 if (!start_flag_) {
264 return;
265 }
266 if (!update->object) {
267 return;
268 }
269 if (update->object->get_id() == td::td_api::updateAuthorizationState::ID) {
270 auto update_authorization_state = td::move_tl_object_as<td::td_api::updateAuthorizationState>(update->object);
271 process_authorization_state(std::move(update_authorization_state->authorization_state_));
272 }
273 }
274 };
275
276 class SetUsername final : public TestClinetTask {
277 public:
SetUsername(td::string username,td::Promise<> promise)278 SetUsername(td::string username, td::Promise<> promise)
279 : username_(std::move(username)), promise_(std::move(promise)) {
280 }
281
282 private:
283 td::string username_;
284 td::Promise<> promise_;
285 td::int64 self_id_ = 0;
286 td::string tag_;
287
start_up()288 void start_up() final {
289 send_query(td::make_tl_object<td::td_api::getMe>(), [this](auto res) { this->process_me_user(std::move(res)); });
290 }
291
process_me_user(td::tl_object_ptr<td::td_api::Object> res)292 void process_me_user(td::tl_object_ptr<td::td_api::Object> res) {
293 CHECK(res->get_id() == td::td_api::user::ID);
294 auto user = td::move_tl_object_as<td::td_api::user>(res);
295 self_id_ = user->id_;
296 if (user->username_ != username_) {
297 LOG(INFO) << "SET USERNAME: " << username_;
298 send_query(td::make_tl_object<td::td_api::setUsername>(username_), [this](auto res) {
299 CHECK(res->get_id() == td::td_api::ok::ID);
300 this->send_self_message();
301 });
302 } else {
303 send_self_message();
304 }
305 }
306
send_self_message()307 void send_self_message() {
308 tag_ = PSTRING() << td::format::as_hex(td::Random::secure_int64());
309
310 send_query(td::make_tl_object<td::td_api::createPrivateChat>(self_id_, false), [this](auto res) {
311 CHECK(res->get_id() == td::td_api::chat::ID);
312 auto chat = td::move_tl_object_as<td::td_api::chat>(res);
313 this->send_query(td::make_tl_object<td::td_api::sendMessage>(
314 chat->id_, 0, 0, nullptr, nullptr,
315 td::make_tl_object<td::td_api::inputMessageText>(
316 td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " INIT", td::Auto()),
317 false, false)),
318 [](auto res) {});
319 });
320 }
321
process_update(std::shared_ptr<TestClient::Update> update)322 void process_update(std::shared_ptr<TestClient::Update> update) final {
323 if (!update->object) {
324 return;
325 }
326 if (update->object->get_id() == td::td_api::updateMessageSendSucceeded::ID) {
327 auto updateNewMessage = td::move_tl_object_as<td::td_api::updateMessageSendSucceeded>(update->object);
328 auto &message = updateNewMessage->message_;
329 if (message->content_->get_id() == td::td_api::messageText::ID) {
330 auto messageText = td::move_tl_object_as<td::td_api::messageText>(message->content_);
331 auto text = messageText->text_->text_;
332 if (text.substr(0, tag_.size()) == tag_) {
333 LOG(INFO) << "GOT SELF MESSAGE";
334 return stop();
335 }
336 }
337 }
338 }
339 };
340
341 class CheckTestA final : public TestClinetTask {
342 public:
CheckTestA(td::string tag,td::Promise<> promise)343 CheckTestA(td::string tag, td::Promise<> promise) : tag_(std::move(tag)), promise_(std::move(promise)) {
344 }
345
346 private:
347 td::string tag_;
348 td::Promise<> promise_;
349 td::string previous_text_;
350 int cnt_ = 20;
351
process_update(std::shared_ptr<TestClient::Update> update)352 void process_update(std::shared_ptr<TestClient::Update> update) final {
353 if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
354 auto updateNewMessage = td::move_tl_object_as<td::td_api::updateNewMessage>(update->object);
355 auto &message = updateNewMessage->message_;
356 if (message->content_->get_id() == td::td_api::messageText::ID) {
357 auto messageText = td::move_tl_object_as<td::td_api::messageText>(message->content_);
358 auto text = messageText->text_->text_;
359 if (text.substr(0, tag_.size()) == tag_) {
360 LOG_CHECK(text > previous_text_) << td::tag("now", text) << td::tag("previous", previous_text_);
361 previous_text_ = text;
362 cnt_--;
363 LOG(INFO) << "GOT " << td::tag("text", text) << td::tag("left", cnt_);
364 if (cnt_ == 0) {
365 return stop();
366 }
367 }
368 }
369 }
370 }
371 };
372
373 class TestA final : public TestClinetTask {
374 public:
TestA(td::string tag,td::string username)375 TestA(td::string tag, td::string username) : tag_(std::move(tag)), username_(std::move(username)) {
376 }
377
start_up()378 void start_up() final {
379 send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this](auto res) {
380 CHECK(res->get_id() == td::td_api::chat::ID);
381 auto chat = td::move_tl_object_as<td::td_api::chat>(res);
382 for (int i = 0; i < 20; i++) {
383 this->send_query(
384 td::make_tl_object<td::td_api::sendMessage>(
385 chat->id_, 0, 0, nullptr, nullptr,
386 td::make_tl_object<td::td_api::inputMessageText>(
387 td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), td::Auto()),
388 false, false)),
389 [&](auto res) { this->stop(); });
390 }
391 });
392 }
393
394 private:
395 td::string tag_;
396 td::string username_;
397 };
398
399 class TestSecretChat final : public TestClinetTask {
400 public:
TestSecretChat(td::string tag,td::string username)401 TestSecretChat(td::string tag, td::string username) : tag_(std::move(tag)), username_(std::move(username)) {
402 }
403
start_up()404 void start_up() final {
405 auto f = [this](auto res) {
406 CHECK(res->get_id() == td::td_api::chat::ID);
407 auto chat = td::move_tl_object_as<td::td_api::chat>(res);
408 this->chat_id_ = chat->id_;
409 this->secret_chat_id_ = td::move_tl_object_as<td::td_api::chatTypeSecret>(chat->type_)->secret_chat_id_;
410 };
411 send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this, f = std::move(f)](auto res) mutable {
412 CHECK(res->get_id() == td::td_api::chat::ID);
413 auto chat = td::move_tl_object_as<td::td_api::chat>(res);
414 CHECK(chat->type_->get_id() == td::td_api::chatTypePrivate::ID);
415 auto info = td::move_tl_object_as<td::td_api::chatTypePrivate>(chat->type_);
416 this->send_query(td::make_tl_object<td::td_api::createNewSecretChat>(info->user_id_), std::move(f));
417 });
418 }
419
process_update(std::shared_ptr<TestClient::Update> update)420 void process_update(std::shared_ptr<TestClient::Update> update) final {
421 if (!update->object) {
422 return;
423 }
424 if (update->object->get_id() == td::td_api::updateSecretChat::ID) {
425 auto update_secret_chat = td::move_tl_object_as<td::td_api::updateSecretChat>(update->object);
426 if (update_secret_chat->secret_chat_->id_ != secret_chat_id_ ||
427 update_secret_chat->secret_chat_->state_->get_id() != td::td_api::secretChatStateReady::ID) {
428 return;
429 }
430 LOG(INFO) << "SEND ENCRYPTED MESSAGES";
431 for (int i = 0; i < 20; i++) {
432 send_query(
433 td::make_tl_object<td::td_api::sendMessage>(
434 chat_id_, 0, 0, nullptr, nullptr,
435 td::make_tl_object<td::td_api::inputMessageText>(
436 td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), td::Auto()),
437 false, false)),
438 [](auto res) {});
439 }
440 }
441 }
442
443 private:
444 td::string tag_;
445 td::string username_;
446 td::int64 secret_chat_id_ = 0;
447 td::int64 chat_id_ = 0;
448 };
449
450 class TestFileGenerated final : public TestClinetTask {
451 public:
TestFileGenerated(td::string tag,td::string username)452 TestFileGenerated(td::string tag, td::string username) : tag_(std::move(tag)), username_(std::move(username)) {
453 }
454
start_up()455 void start_up() final {
456 }
457
process_update(std::shared_ptr<TestClient::Update> update)458 void process_update(std::shared_ptr<TestClient::Update> update) final {
459 if (!update->object) {
460 return;
461 }
462 if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
463 auto updateNewMessage = td::move_tl_object_as<td::td_api::updateNewMessage>(update->object);
464 auto &message = updateNewMessage->message_;
465 chat_id_ = message->chat_id_;
466 if (message->content_->get_id() == td::td_api::messageText::ID) {
467 auto messageText = td::move_tl_object_as<td::td_api::messageText>(message->content_);
468 auto text = messageText->text_->text_;
469 if (text.substr(0, tag_.size()) == tag_) {
470 if (text.substr(tag_.size() + 1) == "ONE_FILE") {
471 return one_file();
472 }
473 }
474 }
475 } else if (update->object->get_id() == td::td_api::updateFileGenerationStart::ID) {
476 auto info = td::move_tl_object_as<td::td_api::updateFileGenerationStart>(update->object);
477 generate_file(info->generation_id_, info->original_path_, info->destination_path_, info->conversion_);
478 } else if (update->object->get_id() == td::td_api::updateFile::ID) {
479 auto file = td::move_tl_object_as<td::td_api::updateFile>(update->object);
480 LOG(INFO) << to_string(file);
481 }
482 }
483
one_file()484 void one_file() {
485 LOG(ERROR) << "Start ONE_FILE test";
486 auto file_path = PSTRING() << "test_documents" << TD_DIR_SLASH << "a.txt";
487 td::mkpath(file_path).ensure();
488 auto raw_file =
489 td::FileFd::open(file_path, td::FileFd::Flags::Create | td::FileFd::Flags::Truncate | td::FileFd::Flags::Write)
490 .move_as_ok();
491 auto file = td::BufferedFd<td::FileFd>(std::move(raw_file));
492 for (int i = 1; i < 100000; i++) {
493 file.write(PSLICE() << i << "\n").ensure();
494 }
495 file.flush_write().ensure(); // important
496 file.close();
497 send_query(td::make_tl_object<td::td_api::sendMessage>(
498 chat_id_, 0, 0, nullptr, nullptr,
499 td::make_tl_object<td::td_api::inputMessageDocument>(
500 td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "square", 0),
501 td::make_tl_object<td::td_api::inputThumbnail>(
502 td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "thumbnail", 0), 0, 0),
503 true, td::make_tl_object<td::td_api::formattedText>(tag_, td::Auto()))),
504 [](auto res) { check_td_error(res); });
505
506 send_query(td::make_tl_object<td::td_api::sendMessage>(
507 chat_id_, 0, 0, nullptr, nullptr,
508 td::make_tl_object<td::td_api::inputMessageDocument>(
509 td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "square", 0), nullptr, true,
510 td::make_tl_object<td::td_api::formattedText>(tag_, td::Auto()))),
511 [](auto res) { check_td_error(res); });
512 }
513
514 class GenerateFile final : public td::Actor {
515 public:
GenerateFile(TestClinetTask * parent,td::int64 id,td::string original_path,td::string destination_path,td::string conversion)516 GenerateFile(TestClinetTask *parent, td::int64 id, td::string original_path, td::string destination_path,
517 td::string conversion)
518 : parent_(parent)
519 , id_(id)
520 , original_path_(std::move(original_path))
521 , destination_path_(std::move(destination_path))
522 , conversion_(std::move(conversion)) {
523 }
524
525 private:
526 TestClinetTask *parent_;
527 td::int64 id_;
528 td::string original_path_;
529 td::string destination_path_;
530 td::string conversion_;
531
532 FILE *from = nullptr;
533 FILE *to = nullptr;
534
start_up()535 void start_up() final {
536 from = std::fopen(original_path_.c_str(), "rb");
537 CHECK(from);
538 to = std::fopen(destination_path_.c_str(), "wb");
539 CHECK(to);
540 yield();
541 }
542
loop()543 void loop() final {
544 int cnt = 0;
545 while (true) {
546 td::uint32 x;
547 auto r = std::fscanf(from, "%u", &x);
548 if (r != 1) {
549 return stop();
550 }
551 std::fprintf(to, "%u\n", x * x);
552 if (++cnt >= 10000) {
553 break;
554 }
555 }
556 auto ready = std::ftell(to);
557 LOG(ERROR) << "READY: " << ready;
558 parent_->send_query(td::make_tl_object<td::td_api::setFileGenerationProgress>(
559 id_, 1039823 /*yeah, exact size of this file*/, td::narrow_cast<td::int32>(ready)),
560 [](auto result) { check_td_error(result); });
561 set_timeout_in(0.02);
562 }
tear_down()563 void tear_down() final {
564 std::fclose(from);
565 std::fclose(to);
566 parent_->send_query(td::make_tl_object<td::td_api::finishFileGeneration>(id_, nullptr),
567 [](auto result) { check_td_error(result); });
568 }
569 };
570
generate_file(td::int64 id,const td::string & original_path,const td::string & destination_path,const td::string & conversion)571 void generate_file(td::int64 id, const td::string &original_path, const td::string &destination_path,
572 const td::string &conversion) {
573 LOG(ERROR) << "Generate file " << td::tag("id", id) << td::tag("original_path", original_path)
574 << td::tag("destination_path", destination_path) << td::tag("conversion", conversion);
575 if (conversion == "square") {
576 td::create_actor<GenerateFile>("GenerateFile", this, id, original_path, destination_path, conversion).release();
577 } else if (conversion == "thumbnail") {
578 td::write_file(destination_path, td::base64url_decode(td::Slice(thumbnail, thumbnail_size)).ok()).ensure();
579 send_query(td::make_tl_object<td::td_api::finishFileGeneration>(id, nullptr),
580 [](auto result) { check_td_error(result); });
581 } else {
582 LOG(FATAL) << "Unknown " << td::tag("conversion", conversion);
583 }
584 }
585
586 private:
587 td::string tag_;
588 td::string username_;
589 td::int64 chat_id_ = 0;
590 };
591
592 class CheckTestC final : public TestClinetTask {
593 public:
CheckTestC(td::string username,td::string tag,td::Promise<> promise)594 CheckTestC(td::string username, td::string tag, td::Promise<> promise)
595 : username_(std::move(username)), tag_(std::move(tag)), promise_(std::move(promise)) {
596 }
597
start_up()598 void start_up() final {
599 send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this](auto res) {
600 CHECK(res->get_id() == td::td_api::chat::ID);
601 auto chat = td::move_tl_object_as<td::td_api::chat>(res);
602 chat_id_ = chat->id_;
603 this->one_file();
604 });
605 }
606
607 private:
608 td::string username_;
609 td::string tag_;
610 td::Promise<> promise_;
611 td::int64 chat_id_ = 0;
612
one_file()613 void one_file() {
614 send_query(td::make_tl_object<td::td_api::sendMessage>(
615 chat_id_, 0, 0, nullptr, nullptr,
616 td::make_tl_object<td::td_api::inputMessageText>(
617 td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " ONE_FILE", td::Auto()),
618 false, false)),
619 [](auto res) { check_td_error(res); });
620 }
621
process_update(std::shared_ptr<TestClient::Update> update)622 void process_update(std::shared_ptr<TestClient::Update> update) final {
623 if (!update->object) {
624 return;
625 }
626 if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
627 auto updateNewMessage = td::move_tl_object_as<td::td_api::updateNewMessage>(update->object);
628 auto &message = updateNewMessage->message_;
629 if (message->content_->get_id() == td::td_api::messageDocument::ID) {
630 auto messageDocument = td::move_tl_object_as<td::td_api::messageDocument>(message->content_);
631 auto text = messageDocument->caption_->text_;
632 if (text.substr(0, tag_.size()) == tag_) {
633 file_id_to_check_ = messageDocument->document_->document_->id_;
634 LOG(ERROR) << "GOT FILE " << to_string(messageDocument->document_->document_);
635 send_query(td::make_tl_object<td::td_api::downloadFile>(file_id_to_check_, 1, 0, 0, false),
636 [](auto res) { check_td_error(res); });
637 }
638 }
639 } else if (update->object->get_id() == td::td_api::updateFile::ID) {
640 auto updateFile = td::move_tl_object_as<td::td_api::updateFile>(update->object);
641 if (updateFile->file_->id_ == file_id_to_check_ && (updateFile->file_->local_->is_downloading_completed_)) {
642 check_file(updateFile->file_->local_->path_);
643 }
644 }
645 }
646
check_file(td::CSlice path)647 void check_file(td::CSlice path) {
648 FILE *from = std::fopen(path.c_str(), "rb");
649 CHECK(from);
650 td::uint32 x;
651 td::uint32 y = 1;
652 while (std::fscanf(from, "%u", &x) == 1) {
653 CHECK(x == y * y);
654 y++;
655 }
656 std::fclose(from);
657 stop();
658 }
659 td::int32 file_id_to_check_ = 0;
660 };
661
662 class LoginTestActor final : public td::Actor {
663 public:
LoginTestActor(td::Status * status)664 explicit LoginTestActor(td::Status *status) : status_(status) {
665 *status_ = td::Status::OK();
666 }
667
668 private:
669 td::Status *status_;
670 td::ActorOwn<TestClient> alice_;
671 td::ActorOwn<TestClient> bob_;
672
673 td::string alice_phone_ = "9996636437";
674 td::string bob_phone_ = "9996636438";
675 td::string alice_username_ = "alice_" + alice_phone_;
676 td::string bob_username_ = "bob_" + bob_phone_;
677
678 td::string stage_name_;
679
begin_stage(td::string stage_name,double timeout)680 void begin_stage(td::string stage_name, double timeout) {
681 LOG(WARNING) << "Begin stage '" << stage_name << "'";
682 stage_name_ = std::move(stage_name);
683 set_timeout_in(timeout);
684 }
685
start_up()686 void start_up() final {
687 begin_stage("Logging in", 160);
688 alice_ = td::create_actor<TestClient>("AliceClient", "alice");
689 bob_ = td::create_actor<TestClient>("BobClient", "bob");
690
691 td::send_closure(alice_, &TestClient::add_listener,
692 td::make_unique<DoAuthentication>(
693 "alice", alice_phone_, "33333",
694 td::PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec))));
695
696 td::send_closure(bob_, &TestClient::add_listener,
697 td::make_unique<DoAuthentication>(
698 "bob", bob_phone_, "33333",
699 td::PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec))));
700 }
701
702 int start_up_fence_ = 3;
start_up_fence_dec()703 void start_up_fence_dec() {
704 --start_up_fence_;
705 if (start_up_fence_ == 0) {
706 init();
707 } else if (start_up_fence_ == 1) {
708 return init();
709 class WaitActor final : public td::Actor {
710 public:
711 WaitActor(double timeout, td::Promise<> promise) : timeout_(timeout), promise_(std::move(promise)) {
712 }
713 void start_up() final {
714 set_timeout_in(timeout_);
715 }
716 void timeout_expired() final {
717 stop();
718 }
719
720 private:
721 double timeout_;
722 td::Promise<> promise_;
723 };
724 td::create_actor<WaitActor>("WaitActor", 2,
725 td::PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec)))
726 .release();
727 }
728 }
729
init()730 void init() {
731 td::send_closure(alice_, &TestClient::add_listener,
732 td::make_unique<SetUsername>(alice_username_, td::PromiseCreator::event(self_closure(
733 this, &LoginTestActor::init_fence_dec))));
734 td::send_closure(bob_, &TestClient::add_listener,
735 td::make_unique<SetUsername>(bob_username_, td::PromiseCreator::event(self_closure(
736 this, &LoginTestActor::init_fence_dec))));
737 }
738
739 int init_fence_ = 2;
init_fence_dec()740 void init_fence_dec() {
741 if (--init_fence_ == 0) {
742 test_a();
743 }
744 }
745
746 int test_a_fence_ = 2;
test_a_fence()747 void test_a_fence() {
748 if (--test_a_fence_ == 0) {
749 test_b();
750 }
751 }
752
test_a()753 void test_a() {
754 begin_stage("Ready to create chats", 80);
755 td::string alice_tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
756 td::string bob_tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
757
758 td::send_closure(bob_, &TestClient::add_listener,
759 td::make_unique<CheckTestA>(
760 alice_tag, td::PromiseCreator::event(self_closure(this, &LoginTestActor::test_a_fence))));
761 td::send_closure(alice_, &TestClient::add_listener,
762 td::make_unique<CheckTestA>(
763 bob_tag, td::PromiseCreator::event(self_closure(this, &LoginTestActor::test_a_fence))));
764
765 td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestA>(alice_tag, bob_username_));
766 td::send_closure(bob_, &TestClient::add_listener, td::make_unique<TestA>(bob_tag, alice_username_));
767 // td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestChat>(bob_username_));
768 }
769
timeout_expired()770 void timeout_expired() final {
771 LOG(FATAL) << "Timeout expired in stage '" << stage_name_ << "'";
772 }
773
774 int test_b_fence_ = 1;
test_b_fence()775 void test_b_fence() {
776 if (--test_b_fence_ == 0) {
777 test_c();
778 }
779 }
780
781 int test_c_fence_ = 1;
test_c_fence()782 void test_c_fence() {
783 if (--test_c_fence_ == 0) {
784 finish();
785 }
786 }
787
test_b()788 void test_b() {
789 begin_stage("Create secret chat", 40);
790 td::string tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
791
792 td::send_closure(
793 bob_, &TestClient::add_listener,
794 td::make_unique<CheckTestA>(tag, td::PromiseCreator::event(self_closure(this, &LoginTestActor::test_b_fence))));
795 td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestSecretChat>(tag, bob_username_));
796 }
797
test_c()798 void test_c() {
799 begin_stage("Send generated file", 240);
800 td::string tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
801
802 td::send_closure(
803 bob_, &TestClient::add_listener,
804 td::make_unique<CheckTestC>(alice_username_, tag,
805 td::PromiseCreator::event(self_closure(this, &LoginTestActor::test_c_fence))));
806 td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestFileGenerated>(tag, bob_username_));
807 }
808
809 int finish_fence_ = 2;
finish_fence()810 void finish_fence() {
811 finish_fence_--;
812 if (finish_fence_ == 0) {
813 td::Scheduler::instance()->finish();
814 stop();
815 }
816 }
817
finish()818 void finish() {
819 td::send_closure(alice_, &TestClient::close,
820 td::PromiseCreator::event(self_closure(this, &LoginTestActor::finish_fence)));
821 td::send_closure(bob_, &TestClient::close,
822 td::PromiseCreator::event(self_closure(this, &LoginTestActor::finish_fence)));
823 }
824 };
825
826 class Tdclient_login final : public td::Test {
827 public:
828 using Test::Test;
step()829 bool step() final {
830 if (!is_inited_) {
831 sched_.init(4);
832 sched_.create_actor_unsafe<LoginTestActor>(0, "LoginTestActor", &result_).release();
833 sched_.start();
834 is_inited_ = true;
835 }
836
837 bool ret = sched_.run_main(10);
838 if (ret) {
839 return true;
840 }
841 sched_.finish();
842 if (result_.is_error()) {
843 LOG(ERROR) << result_;
844 }
845 ASSERT_TRUE(result_.is_ok());
846 return false;
847 }
848
849 private:
850 bool is_inited_ = false;
851 td::ConcurrentScheduler sched_;
852 td::Status result_;
853 };
854 //RegisterTest<Tdclient_login> Tdclient_login("Tdclient_login");
855
TEST(Client,Simple)856 TEST(Client, Simple) {
857 td::Client client;
858 // client.execute({1, td::td_api::make_object<td::td_api::setLogTagVerbosityLevel>("actor", 1)});
859 client.send({3, td::make_tl_object<td::td_api::testSquareInt>(3)});
860 while (true) {
861 auto result = client.receive(10);
862 if (result.id == 3) {
863 auto test_int = td::td_api::move_object_as<td::td_api::testInt>(result.object);
864 ASSERT_EQ(test_int->value_, 9);
865 break;
866 }
867 }
868 }
869
TEST(Client,SimpleMulti)870 TEST(Client, SimpleMulti) {
871 std::vector<td::Client> clients(40);
872 //for (auto &client : clients) {
873 //client.execute({1, td::td_api::make_object<td::td_api::setLogTagVerbosityLevel>("td_requests", 1)});
874 //}
875
876 for (size_t i = 0; i < clients.size(); i++) {
877 clients[i].send({i + 2, td::make_tl_object<td::td_api::testSquareInt>(3)});
878 if (td::Random::fast_bool()) {
879 clients[i].send({1, td::make_tl_object<td::td_api::close>()});
880 }
881 }
882
883 for (size_t i = 0; i < clients.size(); i++) {
884 while (true) {
885 auto result = clients[i].receive(10);
886 if (result.id == i + 2) {
887 CHECK(result.object->get_id() == td::td_api::testInt::ID);
888 auto test_int = td::td_api::move_object_as<td::td_api::testInt>(result.object);
889 ASSERT_EQ(test_int->value_, 9);
890 break;
891 }
892 }
893 }
894 }
895
896 #if !TD_THREAD_UNSUPPORTED
TEST(Client,Multi)897 TEST(Client, Multi) {
898 td::vector<td::thread> threads;
899 std::atomic<int> ok_count{0};
900 for (int i = 0; i < 4; i++) {
901 threads.emplace_back([i, &ok_count] {
902 for (int j = 0; j < 1000; j++) {
903 td::Client client;
904 auto request_id = static_cast<td::uint64>(j + 2 + 1000 * i);
905 client.send({request_id, td::make_tl_object<td::td_api::testSquareInt>(3)});
906 if (j & 1) {
907 client.send({1, td::make_tl_object<td::td_api::close>()});
908 }
909 while (true) {
910 auto result = client.receive(10);
911 if (result.id == request_id) {
912 ok_count++;
913 if ((j & 1) == 0) {
914 client.send({1, td::make_tl_object<td::td_api::close>()});
915 }
916 }
917 if (result.id == 0 && result.object != nullptr &&
918 result.object->get_id() == td::td_api::updateAuthorizationState::ID &&
919 static_cast<const td::td_api::updateAuthorizationState *>(result.object.get())
920 ->authorization_state_->get_id() == td::td_api::authorizationStateClosed::ID) {
921 ok_count++;
922 break;
923 }
924 }
925 }
926 });
927 }
928
929 for (auto &thread : threads) {
930 thread.join();
931 }
932 ASSERT_EQ(8 * 1000, ok_count.load());
933 }
934
TEST(Client,Manager)935 TEST(Client, Manager) {
936 td::vector<td::thread> threads;
937 td::ClientManager client;
938 #if !TD_EVENTFD_UNSUPPORTED // Client must be used from a single thread if there is no EventFd
939 int threads_n = 4;
940 #else
941 int threads_n = 1;
942 #endif
943 int clients_n = 1000;
944 client.send(0, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
945 client.send(-1, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
946 for (int i = 0; i < threads_n; i++) {
947 threads.emplace_back([&] {
948 for (int i = 0; i <= clients_n; i++) {
949 auto id = client.create_client_id();
950 if (i != 0) {
951 client.send(id, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
952 }
953 }
954 });
955 }
956 for (auto &thread : threads) {
957 thread.join();
958 }
959
960 std::set<td::int32> ids;
961 while (ids.size() != static_cast<size_t>(threads_n) * clients_n) {
962 auto event = client.receive(10);
963 if (event.client_id == 0 || event.client_id == -1) {
964 ASSERT_EQ(td::td_api::error::ID, event.object->get_id());
965 ASSERT_EQ(400, static_cast<td::td_api::error &>(*event.object).code_);
966 continue;
967 }
968 if (event.request_id == 3) {
969 ASSERT_EQ(td::td_api::testInt::ID, event.object->get_id());
970 ASSERT_TRUE(ids.insert(event.client_id).second);
971 }
972 }
973 }
974
975 #if !TD_EVENTFD_UNSUPPORTED // Client must be used from a single thread if there is no EventFd
TEST(Client,Close)976 TEST(Client, Close) {
977 std::atomic<bool> stop_send{false};
978 std::atomic<bool> can_stop_receive{false};
979 std::atomic<td::int64> send_count{1};
980 std::atomic<td::int64> receive_count{0};
981 td::Client client;
982
983 std::mutex request_ids_mutex;
984 std::set<td::uint64> request_ids;
985 request_ids.insert(1);
986 td::thread send_thread([&] {
987 td::uint64 request_id = 2;
988 while (!stop_send.load()) {
989 {
990 std::unique_lock<std::mutex> guard(request_ids_mutex);
991 request_ids.insert(request_id);
992 }
993 client.send({request_id++, td::make_tl_object<td::td_api::testSquareInt>(3)});
994 send_count++;
995 }
996 can_stop_receive = true;
997 });
998
999 td::thread receive_thread([&] {
1000 auto max_continue_send = td::Random::fast_bool() ? 0 : 1000;
1001 while (true) {
1002 auto response = client.receive(10.0);
1003 if (response.object == nullptr) {
1004 if (!stop_send) {
1005 stop_send = true;
1006 } else {
1007 return;
1008 }
1009 }
1010 if (response.id > 0) {
1011 if (!stop_send && response.object->get_id() == td::td_api::error::ID &&
1012 static_cast<td::td_api::error &>(*response.object).code_ == 500 &&
1013 td::Random::fast(0, max_continue_send) == 0) {
1014 stop_send = true;
1015 }
1016 receive_count++;
1017 {
1018 std::unique_lock<std::mutex> guard(request_ids_mutex);
1019 size_t erased_count = request_ids.erase(response.id);
1020 CHECK(erased_count > 0);
1021 }
1022 }
1023 if (can_stop_receive && receive_count == send_count) {
1024 break;
1025 }
1026 }
1027 });
1028
1029 td::usleep_for((td::Random::fast_bool() ? 0 : 1000) * (td::Random::fast_bool() ? 1 : 50));
1030 client.send({1, td::make_tl_object<td::td_api::close>()});
1031
1032 send_thread.join();
1033 receive_thread.join();
1034 ASSERT_EQ(send_count.load(), receive_count.load());
1035 ASSERT_TRUE(request_ids.empty());
1036 }
1037
TEST(Client,ManagerClose)1038 TEST(Client, ManagerClose) {
1039 std::atomic<bool> stop_send{false};
1040 std::atomic<bool> can_stop_receive{false};
1041 std::atomic<td::int64> send_count{1};
1042 std::atomic<td::int64> receive_count{0};
1043 td::ClientManager client_manager;
1044 auto client_id = client_manager.create_client_id();
1045
1046 std::mutex request_ids_mutex;
1047 std::set<td::uint64> request_ids;
1048 request_ids.insert(1);
1049 td::thread send_thread([&] {
1050 td::uint64 request_id = 2;
1051 while (!stop_send.load()) {
1052 {
1053 std::unique_lock<std::mutex> guard(request_ids_mutex);
1054 request_ids.insert(request_id);
1055 }
1056 client_manager.send(client_id, request_id++, td::make_tl_object<td::td_api::testSquareInt>(3));
1057 send_count++;
1058 }
1059 can_stop_receive = true;
1060 });
1061
1062 td::thread receive_thread([&] {
1063 auto max_continue_send = td::Random::fast_bool() ? 0 : 1000;
1064 bool can_stop_send = false;
1065 while (true) {
1066 auto response = client_manager.receive(10.0);
1067 if (response.object == nullptr) {
1068 if (!stop_send) {
1069 can_stop_send = true;
1070 } else {
1071 return;
1072 }
1073 }
1074 if (can_stop_send && max_continue_send-- <= 0) {
1075 stop_send = true;
1076 }
1077 if (response.request_id > 0) {
1078 receive_count++;
1079 {
1080 std::unique_lock<std::mutex> guard(request_ids_mutex);
1081 size_t erased_count = request_ids.erase(response.request_id);
1082 CHECK(erased_count > 0);
1083 }
1084 }
1085 if (can_stop_receive && receive_count == send_count) {
1086 break;
1087 }
1088 }
1089 });
1090
1091 td::usleep_for((td::Random::fast_bool() ? 0 : 1000) * (td::Random::fast_bool() ? 1 : 50));
1092 client_manager.send(client_id, 1, td::make_tl_object<td::td_api::close>());
1093
1094 send_thread.join();
1095 receive_thread.join();
1096 ASSERT_EQ(send_count.load(), receive_count.load());
1097 ASSERT_TRUE(request_ids.empty());
1098 }
1099 #endif
1100 #endif
1101
TEST(Client,ManagerCloseOneThread)1102 TEST(Client, ManagerCloseOneThread) {
1103 td::ClientManager client_manager;
1104
1105 td::uint64 request_id = 2;
1106 std::map<td::uint64, td::int32> sent_requests;
1107 td::uint64 sent_count = 0;
1108 td::uint64 receive_count = 0;
1109
1110 auto send_request = [&](td::int32 client_id, td::int32 expected_error_code) {
1111 sent_count++;
1112 sent_requests.emplace(request_id, expected_error_code);
1113 client_manager.send(client_id, request_id++, td::make_tl_object<td::td_api::testSquareInt>(3));
1114 };
1115
1116 auto receive = [&] {
1117 while (receive_count != sent_count) {
1118 auto response = client_manager.receive(1.0);
1119 if (response.object == nullptr) {
1120 continue;
1121 }
1122 if (response.request_id > 0) {
1123 receive_count++;
1124 auto it = sent_requests.find(response.request_id);
1125 CHECK(it != sent_requests.end());
1126 auto expected_error_code = it->second;
1127 sent_requests.erase(it);
1128
1129 if (expected_error_code == 0) {
1130 if (response.request_id == 1) {
1131 ASSERT_EQ(td::td_api::ok::ID, response.object->get_id());
1132 } else {
1133 ASSERT_EQ(td::td_api::testInt::ID, response.object->get_id());
1134 }
1135 } else {
1136 ASSERT_EQ(td::td_api::error::ID, response.object->get_id());
1137 ASSERT_EQ(expected_error_code, static_cast<td::td_api::error &>(*response.object).code_);
1138 }
1139 }
1140 }
1141 };
1142
1143 for (int t = 0; t < 3; t++) {
1144 for (td::int32 i = -5; i <= 0; i++) {
1145 send_request(i, 400);
1146 }
1147
1148 receive();
1149
1150 auto client_id = client_manager.create_client_id();
1151
1152 for (td::int32 i = -5; i < 5; i++) {
1153 send_request(i, i == client_id ? 0 : (i > 0 && i < client_id ? 500 : 400));
1154 }
1155
1156 receive();
1157
1158 for (int i = 0; i < 10; i++) {
1159 send_request(client_id, 0);
1160 }
1161
1162 receive();
1163
1164 sent_count++;
1165 sent_requests.emplace(1, 0);
1166 client_manager.send(client_id, 1, td::make_tl_object<td::td_api::close>());
1167
1168 for (int i = 0; i < 10; i++) {
1169 send_request(client_id, 500);
1170 }
1171
1172 receive();
1173
1174 for (int i = 0; i < 10; i++) {
1175 send_request(client_id, 500);
1176 }
1177
1178 receive();
1179 }
1180
1181 ASSERT_TRUE(sent_requests.empty());
1182 }
1183
TEST(PartsManager,hands)1184 TEST(PartsManager, hands) {
1185 {
1186 td::PartsManager pm;
1187 pm.init(0, 100000, false, 10, {0, 1, 2}, false, true).ensure_error();
1188 //pm.set_known_prefix(0, false).ensure();
1189 }
1190 {
1191 td::PartsManager pm;
1192 pm.init(1, 100000, true, 10, {0, 1, 2}, false, true).ensure_error();
1193 }
1194 }
1195