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