summaryrefslogtreecommitdiff
path: root/protocols/Telegram/tdlib/td/test
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/Telegram/tdlib/td/test')
-rw-r--r--protocols/Telegram/tdlib/td/test/CMakeLists.txt50
-rw-r--r--protocols/Telegram/tdlib/td/test/TestsRunner.cpp63
-rw-r--r--protocols/Telegram/tdlib/td/test/TestsRunner.h19
-rw-r--r--protocols/Telegram/tdlib/td/test/data.cpp69
-rw-r--r--protocols/Telegram/tdlib/td/test/data.h15
-rw-r--r--protocols/Telegram/tdlib/td/test/db.cpp575
-rw-r--r--protocols/Telegram/tdlib/td/test/fuzz_url.cpp33
-rw-r--r--protocols/Telegram/tdlib/td/test/http.cpp373
-rw-r--r--protocols/Telegram/tdlib/td/test/main.cpp40
-rw-r--r--protocols/Telegram/tdlib/td/test/message_entities.cpp529
-rw-r--r--protocols/Telegram/tdlib/td/test/mtproto.cpp347
-rw-r--r--protocols/Telegram/tdlib/td/test/secret.cpp1056
-rw-r--r--protocols/Telegram/tdlib/td/test/string_cleaning.cpp107
-rw-r--r--protocols/Telegram/tdlib/td/test/tdclient.cpp837
-rw-r--r--protocols/Telegram/tdlib/td/test/tests_runner.cpp18
-rw-r--r--protocols/Telegram/tdlib/td/test/tests_runner.h18
16 files changed, 4149 insertions, 0 deletions
diff --git a/protocols/Telegram/tdlib/td/test/CMakeLists.txt b/protocols/Telegram/tdlib/td/test/CMakeLists.txt
new file mode 100644
index 0000000000..d120d8d3fb
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/CMakeLists.txt
@@ -0,0 +1,50 @@
+cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+
+#SOURCE SETS
+set(TD_TEST_SOURCE
+ ${CMAKE_CURRENT_SOURCE_DIR}/db.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/http.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mtproto.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/message_entities.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/secret.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/string_cleaning.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/TestsRunner.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests_runner.cpp
+
+ ${CMAKE_CURRENT_SOURCE_DIR}/TestsRunner.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/tests_runner.h
+
+ ${CMAKE_CURRENT_SOURCE_DIR}/data.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/data.h
+
+ ${TDUTILS_TEST_SOURCE}
+ ${TDACTOR_TEST_SOURCE}
+)
+set(TD_TEST_SOURCE ${TD_TEST_SOURCE} PARENT_SCOPE)
+
+set(TESTS_MAIN
+ main.cpp
+)
+
+add_library(all_tests STATIC ${TD_TEST_SOURCE})
+target_include_directories(all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+target_link_libraries(all_tests PRIVATE tdactor tddb tdcore tdnet tdutils)
+
+if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN)
+ #Tests
+ add_executable(run_all_tests ${TESTS_MAIN} ${TD_TEST_SOURCE})
+ if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN)
+ target_compile_options(run_all_tests PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize=vptr")
+ endif()
+ target_include_directories(run_all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+ target_link_libraries(run_all_tests PRIVATE tdactor tddb tdcore tdnet tdutils)
+
+ if (CLANG)
+# add_executable(fuzz_url fuzz_url.cpp)
+# target_link_libraries(fuzz_url PRIVATE tdclient)
+# target_compile_options(fuzz_url PRIVATE "-fsanitize-coverage=trace-pc-guard")
+ endif()
+
+ add_test(run_all_tests run_all_tests)
+endif()
diff --git a/protocols/Telegram/tdlib/td/test/TestsRunner.cpp b/protocols/Telegram/tdlib/td/test/TestsRunner.cpp
new file mode 100644
index 0000000000..fbe155738e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/TestsRunner.cpp
@@ -0,0 +1,63 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "test/TestsRunner.h"
+
+#include "td/utils/common.h"
+#include "td/utils/FileLog.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/path.h"
+#include "td/utils/tests.h"
+
+#include <limits>
+
+DESC_TESTS(string_cleaning);
+DESC_TESTS(message_entities);
+DESC_TESTS(variant);
+DESC_TESTS(secret);
+DESC_TESTS(actors_main);
+DESC_TESTS(actors_simple);
+DESC_TESTS(actors_workers);
+DESC_TESTS(db);
+DESC_TESTS(json);
+DESC_TESTS(http);
+DESC_TESTS(heap);
+DESC_TESTS(pq);
+DESC_TESTS(mtproto);
+
+namespace td {
+
+void TestsRunner::run_all_tests() {
+ LOAD_TESTS(string_cleaning);
+ LOAD_TESTS(message_entities);
+ LOAD_TESTS(variant);
+ LOAD_TESTS(secret);
+ LOAD_TESTS(actors_main);
+ LOAD_TESTS(actors_simple);
+ LOAD_TESTS(actors_workers);
+ LOAD_TESTS(db);
+ LOAD_TESTS(json);
+ LOAD_TESTS(http);
+ LOAD_TESTS(heap);
+ LOAD_TESTS(pq);
+ LOAD_TESTS(mtproto);
+ Test::run_all();
+}
+
+static FileLog file_log;
+static TsLog ts_log(&file_log);
+
+void TestsRunner::init(string dir) {
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
+ chdir(dir).ensure();
+ LOG(WARNING) << "Redirect log into " << tag("file", dir + TD_DIR_SLASH + "log.txt");
+ if (file_log.init("log.txt", std::numeric_limits<int64>::max())) {
+ log_interface = &ts_log;
+ }
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/test/TestsRunner.h b/protocols/Telegram/tdlib/td/test/TestsRunner.h
new file mode 100644
index 0000000000..a5bc66d855
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/TestsRunner.h
@@ -0,0 +1,19 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#include "td/utils/common.h"
+
+namespace td {
+
+class TestsRunner {
+ public:
+ static void init(string dir);
+ static void run_all_tests();
+};
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/test/data.cpp b/protocols/Telegram/tdlib/td/test/data.cpp
new file mode 100644
index 0000000000..a57a9147c5
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/data.cpp
@@ -0,0 +1,69 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "test/data.h"
+namespace td {
+static const char thumbnail_arr[] =
+ "_9j_4AAQSkZJRgABAQEASABIAAD_2wBDAAICAgICAQICAgIDAgIDAwYEAwMDAwcFBQQGCAcJCAgHCAgJCg0LCQoMCggICw8LDA0ODg8OCQsQERAOEQ"
+ "0ODg7_2wBDAQIDAwMDAwcEBAcOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7_wAARCAAyADIDASIA"
+ "AhEBAxEB_8QAHAAAAQUBAQEAAAAAAAAAAAAAAAUGBwgJAgQD_8QAMRAAAgEDAwMDAgUDBQAAAAAAAQIDBAURAAYSByExCBNBFCIVMlFhcZGhsQkjUm"
+ "KB_8QAGgEAAgMBAQAAAAAAAAAAAAAABgcAAggEBf_EACgRAAECBgIBBAMBAQAAAAAAAAECEQADBAUhMRJBBhMyUWEUIlJCkf_aAAwDAQACEQMRAD8A"
+ "381CvUXrbtzYtTLa6ZPx3cKj76SGQLHTn491-_E_9QC38a464dSH2H08gpLZMI9x3dmhom7EwKB98uP2yAP3Ofg6z2M8jVXvM5qqkyFpeTsCCR3Lk9"
+ "y2eWe_yD3znTG8esEqtR-TVAlJ9qRgqbZJ6T1956ELjyG_T6RRpqQhJHvWrIS-gkf6UfjrGHMTVePUH1HuVYxp7rTWOFj9sVDRocfoOUgYk_00nUXX"
+ "XqdQTrIdzmsQgHhWUcLowOcdwoPwfB1W3fG8KjaNlpnoqNK-veCWVYXk9qL26eMSOXkPZclUUdxkk-QMaiPZPqFp9wdT7btC92iltlRcZvpqea33Na"
+ "mNZSuVjcY7BsYDA-SMjByD5c2w01V-DMp5aS4HtffypjnXffULxNPeqmjNwlVU1YZRcFtfCSRjfXRIfUa1bG9SFuuddDbt6UUdkqHIVbhTkmmJPjmp"
+ "-6P-ckfrjVno5ElgSWJ1kjdQyspyGB8EH51kUWDShpFIbAAkQEjsDktk5wAMAD9hq2Pp16lTrdl2BeJzJSyKzWWWRs8Co5NCD8qR3A-CCv6aE7_45T"
+ "y5SqijSUKTlSNhvlJ-tkPrI0YMLD5DUmYiTVrE2WssmYMEK_lYxvQLbYHYi4-jRo0p4bUZ6-oC7y3P1IXajJ5QW-lio4jyIKgqsreDg_cwPceQMY86"
+ "hQAKoUeAMdzk6lzrhb5aT1R7kUqSKpoaiLt-YNEo_wAqw_8ANMuqsVbTWy3x1tEbeXV2E0isWkOcn7QPABAyTnPxp9zfJPH_ABO1US6xYSJwSMZIHE"
+ "krIyriFYJA2obyIzbMst_8oulZLpwVeiVkA4BPIAIBwORTkAnQOsExZNddoWn1T9Kju6G1yU9bLW01ILraKetiqKgLDJHSH3gVi94hoxIBkOYwCM6r"
+ "x6yLDYdt9fOi9T03l3DfN2_h8VRPBdYF-uWp-pWSCmZY40CyZYqYuPJOSqQp-0TtuK1dPt0b6oLNvfZg3R9HDM8UU8X3orcQxTDqAGwnEk4J8kAEa8"
+ "th2NtTZnVjprfZ62fd9vgsMU9jmu1OZJ7YaZ-ApqFsKEVXYq0vEBnZeJIw5UdRfrd5Fd6mptyipCVMFMwUyQXDsWwWcAk6Goc1ts9dZLNT01enishy"
+ "PcQ6jgs40csSAHc4MPynrYKyxwXHgaenngWfhMvExBl5cWB8Fc4IPgg6WbJdqi07ntd5pJ-Ro6mGpgfOSntsGwD-hGRj9zpSHTjfm-t6XWt2zXWqhg"
+ "J-rqqGpo-VNLKMMY4nVwW5sCzKue5YjAPHTbobXf6eWjs9-t0duv1RHFIaSGpWYAT_AJMEAEEkkFWAYEeMYJd9vvtHeJyKWanPByT_AEBkD6ZzCUuX"
+ "j1fYKeZVypmCviAM_ofaTjB5MPrbuRGucUizU0c0bZR1DL2-CMjRriliFNbKanzn2olTP8DGjWczvGo0onQfcV666bXhFwsXUGKnhq6m0LItRSTSGN"
+ "KpUR5olZgDxAdCCcHCuT8Y1nd0O3dW9X-gta1nv0fTvct_u9xZZ4LuK-sjZgrSSokhb2hyBVEAwBGDjuQdft02GPcuxLjZ3dYnniIhlePmI5B-Vivy"
+ "AfIyMjI-dUZpukO99qbiEtv6YxVFQjk-9QVkP08mD5BGJMHzhhkds_roG8lNVUKlOhSwkJCSHPEJUVMwBcEqJDgx7NqlU9P6hQySslSgeyQA-W6SIb"
+ "O6tqQbUk2jcEu0k93WrRPrGjVZPd4_mRhjv7gRyvEqCMnHjTT6f1VmrfUttCyb2RL7FT0sFos9NV0iJTQUsCPUksVHeV5QWLcTyZBnHwp9RoKy47tp"
+ "pr5a7haK6mOGiniaBBjHZc_GRnlnP9iI1u1huF9r7dV2GrnpNx0NSs9BUUqM5Dg5AKr3Iz_Tv8Egnvg_h12Rbq2pXI9JC-BlBRZSikK5nJ_UEEAOzk"
+ "dCF_5N5hbJVzpKZM4rKeYmcQ6U8m4aDqKSC_F2B7OIsX1grfTIfVHsSh6gRXPZW5LeJLlYLxaqeWgt0pMyoyTVKf7QkZ4lAVuLEHAbDYMc7F3jsHdX"
+ "-tXPtXb9FX38PRrW1N5p0MlKwp6f3qcyOo4BFaQxA5yWVVxjSjvDp31r6rdNVgvHS2WgqaWZaumd6yn9mr44WSLizLKnuAI6lk7PChPbOrhem3phH0"
+ "k9Ie1NpyWZbHdVgM9ypRULMY5nYkoZFGG4jio8-PJ8nsE2bSLJQeK8j_oIPz0SNwRLpqKsokKUrk5H6l2YEKSrQ7AwexE76NGjXmx0QaMDRo1IkfKS"
+ "KKZCk0ayp_xdQR_fXEVLS0yn6emip8-fbjC_40aNWc66ijDbZj0YGjRo1WLwaNGjUiR__9k";
+
+const char *thumbnail = thumbnail_arr;
+const size_t thumbnail_size = sizeof(thumbnail_arr) - 1;
+
+static const char gzip_bomb_arr[] =
+ "eJzt3VlQU2cYxvGTSFSkqHWJuIHTUqotAetIBmWzVUeUqlRrFcIqZlxCUKRshZBULVS0oqLiwqJWUVwAQQNBgSgt1EHFlS1AQCsZ1qAsASKTRonnnJ"
+ "ledHrViz5cfvMOOd_v_d-eJCK5vf6MaUmDLK7Jk_tZ9sSDEyYmdo7fOMeqdKSs-jXRxA_m8_fZPGTMW-Rhlf-AP7XxrGTW-qCOyWPtB90qdo-"
+ "NHtXfEi3Wlul0bWrxYNa1QaVY12ure9OdRgz_SVzyX-"
+ "nS59eYP1uYV68zHBLi7xcf2qyd7ddSbhX2geFsfPs3aeO04xhKLWffzBGGQyOHIeckkUmXZ1qZoH2a4XBWddSKRKGJ-"
+ "9UQxe8HGIbDXaZLU4U8SfDf_-UhiXroGG0wJSatvIgVcbXihV2YKTnpdP6ulHWxUtaxsez9U96O_ahCypHn9_AnOow0nI3uu1gRN-"
+ "Qq18SU0wZXFVfwlcxblapX025Sk6mPNymY37bVhV0LeK_hp7-3gu3nNZizl7pj1FNVSYxlY1QxnzZ4QanK2c7waeuO-"
+ "MWbmly9NUfIuBQadLP6C8PZouwzf-QKTebaiGp-o-7o1Nrt7zKhq1CZQxt8og6pySOyeiNvlWaSk4UX5tdICf12vNvNSF_"
+ "3P2ukLLOkokDaHc17B7ItjPsbtnvQBls0snaepGrQ0SegipxsWHsykCfZlqKwoTYROPt1oIK5382XS7tjwmB0dYJRtDrPmrayHq1XH0cuEJlnXROQk"
+ "-o5K6WcRTceCKdQm-BOipQKGaXnbU_R7rhM5Ny-"
+ "jFmk4bGpwbBBkY2W3XjDOaGq2o6c1ByxUrBPe6qktNqmxzsqpMSLx0kraXf81dmi72NkOTyILJElskSWBLJElgSyRJbDg8gSWSJLZEkgS2RJIEtkOT"
+ "yILJElskSWBLJElgSyRJbDi0CWyBJZIktkiSyH74gskSWyRJYEskSWBLJElu_uiCyRJbJElgSyRJbDvsgSWSJLZIkskeXwJLJElsgSWSJLZPluEFn-"
+ "11nq_NUHhOP3MGfHl7pe3eBeYz_S1Jhbye4pZRxnTCxxbZeP9Jw3gnXUOLb4jm1MyAOjcEmSo-ESnr5HI-2Nj9_kupErWfL5sYyx-"
+ "phmrlrZSlZXqylU6gsrH5TVryVZHqWWLdY_hU9x2cDJhe8f7efK9XzzVczmzPtrqMF4_qUS_"
+ "ebj1iYWkHN9InW4gt2YExOkeUru6brq1UH951Spg4vSyY2e7V13Ii5RIhPk0gY3nmjNZplVNT09R-7zrEiprZey8q-kKZxCSdSOSMdN-s_"
+ "pjUrOyiD5t4jWNTdZ-nXWWlODVzekLwxklIba22WQ-FuUmpg8IcOmTRW2mhKKMh-"
+ "nbyRiyFwlaCV7ACUoQQlKUIISlKAEJShBCUpQghKUoAQlKEEJSlCCEpSgBCUoQQlKUIISlKAEJShBCUpQghKUoAQlKEEJSlCCEpSgBCUoQQlKUIISl"
+ "KAEJShBCcp_ouzPDS_"
+ "jjv6KMWnp4tNs6yuBM3bHLpkuGBP1nLgnMfa3DN8xwjrl9p14o26PpinFCzbvcpC7meuhEyLZUuJCaJGU9nmanev3cOQBLQeSaHfIyLjcYdno0LOUW"
+ "khtk2bB9revxtcp15Cvxj9qCEj9iSPfmpIlpE0W1k32N-naUPDiE-oOwm2t2xyM-8eKDlELCesXPVC8_XoFTd5T8usVrmuOb_"
+ "Y32eGpEpyiTdqGL61mOh7rjFhO5XDqZEHtKmbRYt9z1EJmxqR1S99-F8IQJ5T8LoQOp2aPamZy5UAtbXX15d4fSomE8w4-"
+ "tBzS0zOliRL1wyk0oDil6pa-"
+ "pbYQsZs31SwoQQlKUIISlKAEJShBCUpQghKUoAQlKEEJSlCCEpSgBCUoQQlKUIISlKAEJShBCUpQghKUoAQlKEEJSlCCEpSgBCUoQQlKUIISlKAEJS"
+ "hBCUpQ_jtKiTgqPPDO3MOL_Pv3TxL68UbFJ5tlxpXs_XLFDAd3Gy-JIPvL_"
+ "KOXG414uxZmy1zt7B9axXrM53w9d0a0L0vMGGduuE7ODydO5bOO39kQKiPf5d-Udqb6Ck8SEOGbKSMXsSmvXuqVR1T4ireEkA9010f1xDnVKNpCMy_"
+ "8PeXBKz7NVkGM9LnfeXeS7_LXbJ7NbWM3mjUkCTrJRdQI8zhVHHl3lUVtHbnbnKqB6wmfMpRlhfdISsvLSbK9PMmc_euSyN-sd-2bGn-_"
+ "gGUfq3l5kvyhddcwhbA81KTrTW8cl3qg9lDnjhfL5ZpgW9qTt_KDnrP9xgQ8e0m-y39YFLsxN4hxoEkkayYXcbg-"
+ "TLGznukoHjpzn3qgqJTV2ogJXaK6ctqTgxKUoPyfUeqmx4szeRJuj3josnbUu0O57kcXXX-bi25IozxSONDzF5jalI4";
+const char *gzip_bomb = gzip_bomb_arr;
+const size_t gzip_bomb_size = sizeof(gzip_bomb_arr) - 1;
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/test/data.h b/protocols/Telegram/tdlib/td/test/data.h
new file mode 100644
index 0000000000..c447d5cba6
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/data.h
@@ -0,0 +1,15 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+#include "td/utils/common.h"
+namespace td {
+extern const char *thumbnail;
+extern const size_t thumbnail_size;
+
+extern const char *gzip_bomb;
+extern const size_t gzip_bomb_size;
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/test/db.cpp b/protocols/Telegram/tdlib/td/test/db.cpp
new file mode 100644
index 0000000000..8917dd65b8
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/db.cpp
@@ -0,0 +1,575 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/BinlogKeyValue.h"
+#include "td/db/SeqKeyValue.h"
+#include "td/db/SqliteKeyValue.h"
+#include "td/db/SqliteKeyValueSafe.h"
+#include "td/db/TsSeqKeyValue.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/tests.h"
+
+#include <limits>
+#include <map>
+#include <memory>
+
+REGISTER_TESTS(db);
+
+using namespace td;
+
+template <class ContainerT>
+static typename ContainerT::value_type &rand_elem(ContainerT &cont) {
+ CHECK(0 < cont.size() && cont.size() <= static_cast<size_t>(std::numeric_limits<int>::max()));
+ return cont[Random::fast(0, static_cast<int>(cont.size()) - 1)];
+}
+
+TEST(DB, binlog_encryption) {
+ CSlice binlog_name = "test_binlog";
+ Binlog::destroy(binlog_name).ignore();
+
+ auto hello = DbKey::raw_key(std::string(32, 'A'));
+ auto cucumber = DbKey::password("cucumber");
+ auto empty = DbKey::empty();
+ auto long_data = string(10000, 'Z');
+ {
+ Binlog binlog;
+ binlog.init(binlog_name.str(), [](const BinlogEvent &x) {}).ensure();
+ binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("AAAA")));
+ binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("BBBB")));
+ binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer(long_data)));
+ LOG(INFO) << "SET PASSWORD";
+ binlog.change_key(cucumber);
+ binlog.change_key(hello);
+ LOG(INFO) << "OK";
+ binlog.add_raw_event(BinlogEvent::create_raw(binlog.next_id(), 1, 0, create_storer("CCCC")));
+ binlog.close().ensure();
+ }
+
+ auto add_suffix = [&] {
+ auto fd = FileFd::open(binlog_name, FileFd::Flags::Write | FileFd::Flags::Append).move_as_ok();
+ fd.write("abacabadaba").ensure();
+ };
+
+ add_suffix();
+
+ {
+ std::vector<string> v;
+ LOG(INFO) << "RESTART";
+ Binlog binlog;
+ binlog.init(binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, hello).ensure();
+ CHECK(v == std::vector<string>({"AAAA", "BBBB", long_data, "CCCC"}));
+ }
+
+ add_suffix();
+
+ {
+ std::vector<string> v;
+ LOG(INFO) << "RESTART";
+ Binlog binlog;
+ auto status = binlog.init(binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber);
+ CHECK(status.is_error());
+ }
+
+ add_suffix();
+
+ {
+ std::vector<string> v;
+ LOG(INFO) << "RESTART";
+ Binlog binlog;
+ auto status =
+ binlog.init(binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber, hello);
+ CHECK(v == std::vector<string>({"AAAA", "BBBB", long_data, "CCCC"}));
+ }
+};
+
+TEST(DB, sqlite_lfs) {
+ string path = "test_sqlite_db";
+ SqliteDb::destroy(path).ignore();
+ SqliteDb db;
+ db.init(path).ensure();
+ db.exec("PRAGMA journal_mode=WAL").ensure();
+ db.exec("PRAGMA user_version").ensure();
+}
+
+TEST(DB, sqlite_encryption) {
+ string path = "test_sqlite_db";
+ SqliteDb::destroy(path).ignore();
+
+ auto empty = DbKey::empty();
+ auto cucumber = DbKey::password("cucumber");
+ auto tomato = DbKey::raw_key(string(32, 'a'));
+
+ {
+ auto db = SqliteDb::open_with_key(path, empty).move_as_ok();
+ db.set_user_version(123);
+ auto kv = SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv");
+ kv.set("a", "b");
+ }
+ SqliteDb::open_with_key(path, cucumber).ensure_error(); // key was set...
+
+ SqliteDb::change_key(path, cucumber, empty).ensure();
+
+ SqliteDb::open_with_key(path, tomato).ensure_error();
+ {
+ auto db = SqliteDb::open_with_key(path, cucumber).move_as_ok();
+ auto kv = SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv");
+ CHECK(kv.get("a") == "b");
+ CHECK(db.user_version().ok() == 123);
+ }
+
+ SqliteDb::change_key(path, tomato, cucumber).ensure();
+ SqliteDb::change_key(path, tomato, cucumber).ensure();
+
+ SqliteDb::open_with_key(path, cucumber).ensure_error();
+ {
+ auto db = SqliteDb::open_with_key(path, tomato).move_as_ok();
+ auto kv = SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv");
+ CHECK(kv.get("a") == "b");
+ CHECK(db.user_version().ok() == 123);
+ }
+
+ SqliteDb::change_key(path, empty, tomato).ensure();
+ SqliteDb::change_key(path, empty, tomato).ensure();
+
+ {
+ auto db = SqliteDb::open_with_key(path, empty).move_as_ok();
+ auto kv = SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv");
+ CHECK(kv.get("a") == "b");
+ CHECK(db.user_version().ok() == 123);
+ }
+ SqliteDb::open_with_key(path, cucumber).ensure_error();
+}
+
+using SeqNo = uint64;
+struct DbQuery {
+ enum Type { Get, Set, Erase } type;
+ SeqNo tid = 0;
+ int32 id = 0;
+ string key;
+ string value;
+};
+
+template <class ImplT>
+class QueryHandler {
+ public:
+ ImplT &impl() {
+ return impl_;
+ }
+ void do_query(DbQuery &query) {
+ switch (query.type) {
+ case DbQuery::Get:
+ query.value = impl_.get(query.key);
+ return;
+ case DbQuery::Set:
+ query.tid = impl_.set(query.key, query.value);
+ return;
+ case DbQuery::Erase:
+ query.tid = impl_.erase(query.key);
+ return;
+ }
+ }
+
+ private:
+ ImplT impl_;
+};
+
+class SqliteKV {
+ public:
+ string get(string key) {
+ return kv_->get().get(key);
+ }
+ SeqNo set(string key, string value) {
+ kv_->get().set(key, value);
+ return 0;
+ }
+ SeqNo erase(string key) {
+ kv_->get().erase(key);
+ return 0;
+ }
+ Status init(string name) {
+ auto sql_connection = std::make_shared<SqliteConnectionSafe>(name);
+ kv_ = std::make_shared<SqliteKeyValueSafe>("kv", sql_connection);
+ return Status::OK();
+ }
+ void close() {
+ kv_.reset();
+ }
+
+ private:
+ std::shared_ptr<SqliteKeyValueSafe> kv_;
+};
+
+class BaselineKV {
+ public:
+ string get(string key) {
+ return map_[key];
+ }
+ SeqNo set(string key, string value) {
+ map_[key] = value;
+ return ++current_tid_;
+ }
+ SeqNo erase(string key) {
+ map_.erase(key);
+ return ++current_tid_;
+ }
+
+ private:
+ std::map<string, string> map_;
+ SeqNo current_tid_ = 0;
+};
+
+TEST(DB, key_value) {
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
+ std::vector<std::string> keys;
+ std::vector<std::string> values;
+
+ for (int i = 0; i < 100; i++) {
+ keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ }
+ for (int i = 0; i < 1000; i++) {
+ values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ }
+
+ int queries_n = 300000;
+ std::vector<DbQuery> queries(queries_n);
+ for (auto &q : queries) {
+ int op = Random::fast(0, 2);
+ const auto &key = rand_elem(keys);
+ const auto &value = rand_elem(values);
+ if (op == 0) {
+ q.type = DbQuery::Get;
+ q.key = key;
+ } else if (op == 1) {
+ q.type = DbQuery::Erase;
+ q.key = key;
+ } else if (op == 2) {
+ q.type = DbQuery::Set;
+ q.key = key;
+ q.value = value;
+ }
+ }
+
+ QueryHandler<BaselineKV> baseline;
+ QueryHandler<SeqKeyValue> kv;
+ QueryHandler<TsSeqKeyValue> ts_kv;
+ QueryHandler<BinlogKeyValue<Binlog>> new_kv;
+
+ CSlice new_kv_name = "test_new_kv";
+ Binlog::destroy(new_kv_name).ignore();
+ new_kv.impl().init(new_kv_name.str()).ensure();
+
+ QueryHandler<SqliteKeyValue> sqlite_kv;
+ CSlice name = "test_sqlite_kv";
+ SqliteDb::destroy(name).ignore();
+ sqlite_kv.impl().init(name.str()).ensure();
+
+ int cnt = 0;
+ for (auto &q : queries) {
+ DbQuery a = q;
+ DbQuery b = q;
+ DbQuery c = q;
+ DbQuery d = q;
+ DbQuery e = q;
+ baseline.do_query(a);
+ kv.do_query(b);
+ ts_kv.do_query(c);
+ sqlite_kv.do_query(d);
+ new_kv.do_query(e);
+ ASSERT_EQ(a.value, b.value);
+ ASSERT_EQ(a.value, c.value);
+ ASSERT_EQ(a.value, d.value);
+ ASSERT_EQ(a.value, e.value);
+ if (cnt++ % 10000 == 0) {
+ new_kv.impl().init(new_kv_name.str()).ensure();
+ }
+ }
+}
+
+TEST(DB, thread_key_value) {
+#if !TD_THREAD_UNSUPPORTED
+ std::vector<std::string> keys;
+ std::vector<std::string> values;
+
+ for (int i = 0; i < 100; i++) {
+ keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ }
+ for (int i = 0; i < 1000; i++) {
+ values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ }
+
+ int threads_n = 4;
+ int queries_n = 100000;
+
+ std::vector<std::vector<DbQuery>> queries(threads_n, std::vector<DbQuery>(queries_n));
+ for (auto &qs : queries) {
+ for (auto &q : qs) {
+ int op = Random::fast(0, 10);
+ const auto &key = rand_elem(keys);
+ const auto &value = rand_elem(values);
+ if (op > 1) {
+ q.type = DbQuery::Get;
+ q.key = key;
+ } else if (op == 0) {
+ q.type = DbQuery::Erase;
+ q.key = key;
+ } else if (op == 1) {
+ q.type = DbQuery::Set;
+ q.key = key;
+ q.value = value;
+ }
+ }
+ }
+
+ QueryHandler<BaselineKV> baseline;
+ QueryHandler<TsSeqKeyValue> ts_kv;
+
+ std::vector<thread> threads(threads_n);
+ std::vector<std::vector<DbQuery>> res(threads_n);
+ for (int i = 0; i < threads_n; i++) {
+ threads[i] = thread([&ts_kv, &queries, &res, i]() {
+ for (auto q : queries[i]) {
+ ts_kv.do_query(q);
+ res[i].push_back(q);
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+
+ std::vector<std::size_t> pos(threads_n);
+ while (true) {
+ bool was = false;
+ for (int i = 0; i < threads_n; i++) {
+ auto p = pos[i];
+ if (p == res[i].size()) {
+ continue;
+ }
+ auto &q = res[i][p];
+ if (q.tid == 0) {
+ if (q.type == DbQuery::Get) {
+ auto nq = q;
+ baseline.do_query(nq);
+ if (nq.value == q.value) {
+ was = true;
+ pos[i]++;
+ }
+ } else {
+ was = true;
+ pos[i]++;
+ }
+ }
+ }
+ if (was) {
+ continue;
+ }
+
+ int best = -1;
+ SeqNo best_tid = 0;
+ for (int i = 0; i < threads_n; i++) {
+ auto p = pos[i];
+ if (p == res[i].size()) {
+ continue;
+ }
+ was = true;
+ auto &q = res[i][p];
+ if (q.tid != 0) {
+ if (best == -1 || q.tid < best_tid) {
+ best = i;
+ best_tid = q.tid;
+ }
+ }
+ }
+ if (!was) {
+ break;
+ }
+ ASSERT_TRUE(best != -1);
+ baseline.do_query(res[best][pos[best]]);
+ pos[best]++;
+ }
+#endif
+}
+
+TEST(DB, persistent_key_value) {
+ using KeyValue = BinlogKeyValue<ConcurrentBinlog>;
+ // using KeyValue = PersistentKeyValue;
+ // using KeyValue = SqliteKV;
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(WARNING));
+ std::vector<std::string> keys;
+ std::vector<std::string> values;
+ CSlice name = "test_pmc";
+ Binlog::destroy(name).ignore();
+ SqliteDb::destroy(name).ignore();
+
+ for (int i = 0; i < 100; i++) {
+ keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ }
+ for (int i = 0; i < 1000; i++) {
+ values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ }
+
+ QueryHandler<BaselineKV> baseline;
+
+ for (int iter = 0; iter < 25; iter++) {
+ int threads_n = 4;
+ int queries_n = 3000 / threads_n;
+
+ std::vector<std::vector<DbQuery>> queries(threads_n, std::vector<DbQuery>(queries_n));
+ for (auto &qs : queries) {
+ for (auto &q : qs) {
+ int op = Random::fast(0, 10);
+ const auto &key = rand_elem(keys);
+ const auto &value = rand_elem(values);
+ if (op > 1) {
+ q.type = DbQuery::Get;
+ q.key = key;
+ } else if (op == 0) {
+ q.type = DbQuery::Erase;
+ q.key = key;
+ } else if (op == 1) {
+ q.type = DbQuery::Set;
+ q.key = key;
+ q.value = value;
+ }
+ }
+ }
+
+ std::vector<std::vector<DbQuery>> res(threads_n);
+ class Worker : public Actor {
+ public:
+ Worker(ActorShared<> parent, std::shared_ptr<QueryHandler<KeyValue>> kv, const std::vector<DbQuery> *queries,
+ std::vector<DbQuery> *res)
+ : parent_(std::move(parent)), kv_(std::move(kv)), queries_(queries), res_(res) {
+ }
+ void loop() override {
+ for (auto q : *queries_) {
+ kv_->do_query(q);
+ res_->push_back(q);
+ }
+ stop();
+ }
+
+ private:
+ ActorShared<> parent_;
+ std::shared_ptr<QueryHandler<KeyValue>> kv_;
+ const std::vector<DbQuery> *queries_;
+ std::vector<DbQuery> *res_;
+ };
+ class Main : public Actor {
+ public:
+ Main(int threads_n, const std::vector<std::vector<DbQuery>> *queries, std::vector<std::vector<DbQuery>> *res)
+ : threads_n_(threads_n), queries_(queries), res_(res) {
+ }
+
+ void start_up() override {
+ LOG(INFO) << "start_up";
+ kv_->impl().init("test_pmc").ensure();
+ ref_cnt_ = threads_n_;
+ for (int i = 0; i < threads_n_; i++) {
+ create_actor_on_scheduler<Worker>("Worker", i + 1, actor_shared(this, 2), kv_, &queries_->at(i), &res_->at(i))
+ .release();
+ }
+ }
+
+ void tear_down() override {
+ LOG(INFO) << "tear_down";
+ // kv_->impl().close();
+ }
+ void hangup_shared() override {
+ LOG(INFO) << "hangup";
+ ref_cnt_--;
+ if (ref_cnt_ == 0) {
+ kv_->impl().close();
+ Scheduler::instance()->finish();
+ stop();
+ }
+ }
+ void hangup() override {
+ LOG(ERROR) << "BAD HANGUP";
+ }
+
+ private:
+ int threads_n_;
+ const std::vector<std::vector<DbQuery>> *queries_;
+ std::vector<std::vector<DbQuery>> *res_;
+
+ std::shared_ptr<QueryHandler<KeyValue>> kv_{new QueryHandler<KeyValue>()};
+ int ref_cnt_;
+ };
+
+ ConcurrentScheduler sched;
+ sched.init(threads_n);
+ sched.create_actor_unsafe<Main>(0, "Main", threads_n, &queries, &res).release();
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty
+ }
+ sched.finish();
+
+ std::vector<std::size_t> pos(threads_n);
+ while (true) {
+ bool was = false;
+ for (int i = 0; i < threads_n; i++) {
+ auto p = pos[i];
+ if (p == res[i].size()) {
+ continue;
+ }
+ auto &q = res[i][p];
+ if (q.tid == 0) {
+ if (q.type == DbQuery::Get) {
+ auto nq = q;
+ baseline.do_query(nq);
+ if (nq.value == q.value) {
+ was = true;
+ pos[i]++;
+ }
+ } else {
+ was = true;
+ pos[i]++;
+ }
+ }
+ }
+ if (was) {
+ continue;
+ }
+
+ int best = -1;
+ SeqNo best_tid = 0;
+ for (int i = 0; i < threads_n; i++) {
+ auto p = pos[i];
+ if (p == res[i].size()) {
+ continue;
+ }
+ was = true;
+ auto &q = res[i][p];
+ if (q.tid != 0) {
+ if (best == -1 || q.tid < best_tid) {
+ best = i;
+ best_tid = q.tid;
+ }
+ }
+ }
+ if (!was) {
+ break;
+ }
+ ASSERT_TRUE(best != -1);
+ baseline.do_query(res[best][pos[best]]);
+ pos[best]++;
+ }
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/test/fuzz_url.cpp b/protocols/Telegram/tdlib/td/test/fuzz_url.cpp
new file mode 100644
index 0000000000..74047135c0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/fuzz_url.cpp
@@ -0,0 +1,33 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageEntity.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/Slice.h"
+
+#include <cstddef>
+#include <cstdint>
+
+static td::string get_utf_string(td::Slice from) {
+ td::string res;
+ td::string alph = " ab@./01#";
+ for (auto c : from) {
+ res += alph[td::uint8(c) % alph.size()];
+ }
+ LOG(ERROR) << res;
+ return res;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(std::uint8_t *data, std::size_t data_size) {
+ td::find_urls(get_utf_string(td::Slice(data, data_size)));
+ //td::find_hashtags(get_utf_string(td::Slice(data, data_size)));
+ //td::find_bot_commands(get_utf_string(td::Slice(data, data_size)));
+ //td::is_email_address(get_utf_string(td::Slice(data, data_size)));
+ //td::find_mentions(get_utf_string(td::Slice(data, data_size)));
+ return 0;
+}
diff --git a/protocols/Telegram/tdlib/td/test/http.cpp b/protocols/Telegram/tdlib/td/test/http.cpp
new file mode 100644
index 0000000000..98c94b2e8a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/http.cpp
@@ -0,0 +1,373 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/tests.h"
+
+#include "td/net/HttpChunkedByteFlow.h"
+#include "td/net/HttpHeaderCreator.h"
+#include "td/net/HttpQuery.h"
+#include "td/net/HttpReader.h"
+
+#include "td/utils/AesCtrByteFlow.h"
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/BufferedFd.h"
+#include "td/utils/ByteFlow.h"
+#include "td/utils/crypto.h"
+#include "td/utils/format.h"
+#include "td/utils/Gzip.h"
+#include "td/utils/GzipByteFlow.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/Fd.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/thread_local.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+
+#include "test/data.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <limits>
+
+REGISTER_TESTS(http)
+
+using namespace td;
+
+static string make_chunked(string str) {
+ auto v = rand_split(str);
+ string res;
+ for (auto &s : v) {
+ res += PSTRING() << format::as_hex_dump(int(s.size()));
+ res += "\r\n";
+ res += s;
+ res += "\r\n";
+ }
+ res += "0\r\n\r\n";
+ return res;
+}
+
+static string gen_http_content() {
+ int t = Random::fast(0, 2);
+ int len;
+ if (t == 0) {
+ len = Random::fast(1, 10);
+ } else if (t == 1) {
+ len = Random::fast(100, 200);
+ } else {
+ len = Random::fast(1000, 20000);
+ }
+ return rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), len);
+}
+
+static string make_http_query(string content, bool is_chunked, bool is_gzip, double gzip_k = 5,
+ string zip_override = "") {
+ HttpHeaderCreator hc;
+ hc.init_post("/");
+ hc.add_header("jfkdlsahhjk", rand_string('a', 'z', Random::fast(1, 2000)));
+ if (is_gzip) {
+ BufferSlice zip;
+ if (zip_override.empty()) {
+ zip = gzencode(content, gzip_k);
+ } else {
+ zip = BufferSlice(zip_override);
+ }
+ if (!zip.empty()) {
+ hc.add_header("content-encoding", "gzip");
+ content = zip.as_slice().str();
+ }
+ }
+ if (is_chunked) {
+ hc.add_header("transfer-encoding", "chunked");
+ content = make_chunked(content);
+ } else {
+ hc.set_content_size(content.size());
+ }
+ string res;
+ auto r_header = hc.finish();
+ CHECK(r_header.is_ok());
+ res += r_header.ok().str();
+ res += content;
+ return res;
+}
+
+static string rand_http_query(string content) {
+ bool is_chunked = Random::fast(0, 1) == 0;
+ bool is_gzip = Random::fast(0, 1) == 0;
+ return make_http_query(std::move(content), is_chunked, is_gzip);
+}
+
+static string join(const std::vector<string> &v) {
+ string res;
+ for (auto &s : v) {
+ res += s;
+ }
+ return res;
+}
+
+TEST(Http, stack_overflow) {
+ ChainBufferWriter writer;
+ BufferSlice slice(string(256, 'A'));
+ for (int i = 0; i < 1000000; i++) {
+ ChainBufferWriter tmp_writer;
+ writer.append(slice.clone());
+ }
+ {
+ auto reader = writer.extract_reader();
+ reader.sync_with_writer();
+ }
+}
+
+TEST(Http, reader) {
+#if TD_ANDROID || TD_TIZEN
+ return;
+#endif
+ clear_thread_locals();
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
+ auto start_mem = BufferAllocator::get_buffer_mem();
+ {
+ auto input_writer = ChainBufferWriter::create_empty();
+ auto input = input_writer.extract_reader();
+ HttpReader reader;
+ int max_post_size = 10000;
+ reader.init(&input, max_post_size, 0);
+
+ std::srand(4);
+ std::vector<string> contents(1000);
+ std::generate(contents.begin(), contents.end(), gen_http_content);
+ auto v = td::transform(contents, rand_http_query);
+ auto vec_str = rand_split(join(v));
+
+ HttpQuery q;
+ std::vector<string> res;
+ for (auto &str : vec_str) {
+ input_writer.append(str);
+ input.sync_with_writer();
+ while (true) {
+ auto r_state = reader.read_next(&q);
+ LOG_IF(ERROR, r_state.is_error()) << r_state.error() << tag("ok", res.size());
+ ASSERT_TRUE(r_state.is_ok());
+ auto state = r_state.ok();
+ if (state == 0) {
+ if (q.files_.empty()) {
+ ASSERT_TRUE(td::narrow_cast<int>(q.content_.size()) <= max_post_size);
+ auto expected = contents[res.size()];
+ ASSERT_EQ(expected, q.content_.str());
+ res.push_back(q.content_.str());
+ } else {
+ auto r_fd = FileFd::open(q.files_[0].temp_file_name, FileFd::Read);
+ ASSERT_TRUE(r_fd.is_ok());
+ auto fd = r_fd.move_as_ok();
+ string content(td::narrow_cast<size_t>(q.files_[0].size), '\0');
+ auto r_size = fd.read(MutableSlice(content));
+ ASSERT_TRUE(r_size.is_ok());
+ ASSERT_TRUE(r_size.ok() == content.size());
+ ASSERT_TRUE(td::narrow_cast<int>(content.size()) > max_post_size);
+ ASSERT_EQ(contents[res.size()], content);
+ res.push_back(content);
+ fd.close();
+ }
+ } else {
+ break;
+ }
+ }
+ }
+ ASSERT_EQ(contents.size(), res.size());
+ ASSERT_EQ(contents, res);
+ }
+ clear_thread_locals();
+ ASSERT_EQ(start_mem, BufferAllocator::get_buffer_mem());
+}
+
+TEST(Http, gzip_bomb) {
+#if TD_ANDROID || TD_TIZEN || TD_EMSCRIPTEN // the test should be disabled on low-memory systems
+ return;
+#endif
+ auto gzip_bomb_str =
+ gzdecode(gzdecode(base64url_decode(Slice(gzip_bomb, gzip_bomb_size)).ok()).as_slice()).as_slice().str();
+
+ auto query = make_http_query("", false, true, 0.01, gzip_bomb_str);
+ auto parts = rand_split(query);
+ auto input_writer = ChainBufferWriter::create_empty();
+ auto input = input_writer.extract_reader();
+ HttpReader reader;
+ HttpQuery q;
+ reader.init(&input, 100000000, 0);
+ for (auto &part : parts) {
+ input_writer.append(part);
+ input.sync_with_writer();
+ auto r_state = reader.read_next(&q);
+ if (r_state.is_error()) {
+ LOG(INFO) << r_state.error();
+ return;
+ }
+ ASSERT_TRUE(r_state.ok() != 0);
+ }
+}
+
+TEST(Http, aes_ctr_encode_decode_flow) {
+ auto str = rand_string('a', 'z', 1000000);
+ auto parts = rand_split(str);
+ auto input_writer = ChainBufferWriter::create_empty();
+ auto input = input_writer.extract_reader();
+ ByteFlowSource source(&input);
+ UInt256 key;
+ UInt128 iv;
+ Random::secure_bytes(key.raw, sizeof(key));
+ Random::secure_bytes(iv.raw, sizeof(iv));
+ AesCtrByteFlow aes_encode;
+ aes_encode.init(key, iv);
+ AesCtrByteFlow aes_decode;
+ aes_decode.init(key, iv);
+ ByteFlowSink sink;
+ source >> aes_encode >> aes_decode >> sink;
+
+ ASSERT_TRUE(!sink.is_ready());
+ for (auto &part : parts) {
+ input_writer.append(part);
+ source.wakeup();
+ }
+ ASSERT_TRUE(!sink.is_ready());
+ source.close_input(Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ LOG_IF(ERROR, sink.status().is_error()) << sink.status();
+ ASSERT_TRUE(sink.status().is_ok());
+ ASSERT_EQ(str, sink.result()->move_as_buffer_slice().as_slice().str());
+}
+
+TEST(Http, aes_file_encryption) {
+ auto str = rand_string('a', 'z', 1000000);
+ CSlice name = "test_encryption";
+ unlink(name).ignore();
+ UInt256 key;
+ UInt128 iv;
+ Random::secure_bytes(key.raw, sizeof(key));
+ Random::secure_bytes(iv.raw, sizeof(iv));
+
+ {
+ BufferedFdBase<FileFd> fd(FileFd::open(name, FileFd::Write | FileFd::Create).move_as_ok());
+
+ auto parts = rand_split(str);
+
+ ChainBufferWriter output_writer;
+ auto output_reader = output_writer.extract_reader();
+ ByteFlowSource source(&output_reader);
+ AesCtrByteFlow aes_encode;
+ aes_encode.init(key, iv);
+ ByteFlowSink sink;
+
+ source >> aes_encode >> sink;
+ fd.set_output_reader(sink.get_output());
+
+ for (auto &part : parts) {
+ output_writer.append(part);
+ source.wakeup();
+ fd.flush_write().ensure();
+ }
+ fd.close();
+ }
+
+ {
+ BufferedFdBase<FileFd> fd(FileFd::open(name, FileFd::Read).move_as_ok());
+
+ ChainBufferWriter input_writer;
+ auto input_reader = input_writer.extract_reader();
+ ByteFlowSource source(&input_reader);
+ AesCtrByteFlow aes_encode;
+ aes_encode.init(key, iv);
+ ByteFlowSink sink;
+ source >> aes_encode >> sink;
+ fd.set_input_writer(&input_writer);
+
+ fd.update_flags(Fd::Flag::Read);
+ while (can_read(fd)) {
+ fd.flush_read(4096).ensure();
+ source.wakeup();
+ }
+
+ fd.close();
+
+ source.close_input(Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ LOG_IF(ERROR, sink.status().is_error()) << sink.status();
+ ASSERT_TRUE(sink.status().is_ok());
+ auto result = sink.result()->move_as_buffer_slice().as_slice().str();
+ ASSERT_EQ(str, result);
+ }
+}
+
+TEST(Http, chunked_flow) {
+ auto str = rand_string('a', 'z', 100);
+ auto parts = rand_split(make_chunked(str));
+ auto input_writer = ChainBufferWriter::create_empty();
+ auto input = input_writer.extract_reader();
+ ByteFlowSource source(&input);
+ HttpChunkedByteFlow chunked_flow;
+ ByteFlowSink sink;
+ source >> chunked_flow >> sink;
+
+ for (auto &part : parts) {
+ input_writer.append(part);
+ source.wakeup();
+ }
+ source.close_input(Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ LOG_IF(ERROR, sink.status().is_error()) << sink.status();
+ ASSERT_TRUE(sink.status().is_ok());
+ auto res = sink.result()->move_as_buffer_slice().as_slice().str();
+ ASSERT_EQ(str.size(), res.size());
+ ASSERT_EQ(str, res);
+}
+
+TEST(Http, chunked_flow_error) {
+ auto str = rand_string('a', 'z', 100000);
+ for (int d = 1; d < 100; d += 10) {
+ auto new_str = make_chunked(str);
+ new_str.resize(str.size() - d);
+ auto parts = rand_split(new_str);
+ auto input_writer = ChainBufferWriter::create_empty();
+ auto input = input_writer.extract_reader();
+ ByteFlowSource source(&input);
+ HttpChunkedByteFlow chunked_flow;
+ ByteFlowSink sink;
+ source >> chunked_flow >> sink;
+
+ for (auto &part : parts) {
+ input_writer.append(part);
+ source.wakeup();
+ }
+ ASSERT_TRUE(!sink.is_ready());
+ source.close_input(Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ ASSERT_TRUE(!sink.status().is_ok());
+ }
+}
+
+TEST(Http, gzip_chunked_flow) {
+ auto str = rand_string('a', 'z', 1000000);
+ auto parts = rand_split(make_chunked(gzencode(str).as_slice().str()));
+
+ auto input_writer = ChainBufferWriter::create_empty();
+ auto input = input_writer.extract_reader();
+ ByteFlowSource source(&input);
+ HttpChunkedByteFlow chunked_flow;
+ GzipByteFlow gzip_flow(Gzip::Decode);
+ ByteFlowSink sink;
+ source >> chunked_flow >> gzip_flow >> sink;
+
+ for (auto &part : parts) {
+ input_writer.append(part);
+ source.wakeup();
+ }
+ source.close_input(Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ LOG_IF(ERROR, sink.status().is_error()) << sink.status();
+ ASSERT_TRUE(sink.status().is_ok());
+ ASSERT_EQ(str, sink.result()->move_as_buffer_slice().as_slice().str());
+}
diff --git a/protocols/Telegram/tdlib/td/test/main.cpp b/protocols/Telegram/tdlib/td/test/main.cpp
new file mode 100644
index 0000000000..0ef46c75b2
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/main.cpp
@@ -0,0 +1,40 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/tests.h"
+
+#include "td/utils/logging.h"
+
+#include <cstring>
+
+#if TD_EMSCRIPTEN
+#include <emscripten.h>
+#endif
+
+int main(int argc, char **argv) {
+ // TODO port OptionsParser to Windows
+ for (int i = 1; i < argc; i++) {
+ if (!std::strcmp(argv[i], "--filter")) {
+ CHECK(i + 1 < argc);
+ td::Test::add_substr_filter(argv[++i]);
+ }
+ if (!std::strcmp(argv[i], "--stress")) {
+ td::Test::set_stress_flag(true);
+ }
+ }
+#if TD_EMSCRIPTEN
+ emscripten_set_main_loop(
+ [] {
+ if (!td::Test::run_all_step()) {
+ emscripten_cancel_main_loop();
+ }
+ },
+ 10, 0);
+#else
+ td::Test::run_all();
+#endif
+ return 0;
+}
diff --git a/protocols/Telegram/tdlib/td/test/message_entities.cpp b/protocols/Telegram/tdlib/td/test/message_entities.cpp
new file mode 100644
index 0000000000..473a87140a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/message_entities.cpp
@@ -0,0 +1,529 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/MessageEntity.h"
+
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/tests.h"
+
+REGISTER_TESTS(message_entities);
+
+using namespace td;
+
+static void check_mention(string str, std::vector<string> expected) {
+ auto result_slice = find_mentions(str);
+ std::vector<string> result;
+ for (auto &it : result_slice) {
+ result.push_back(it.str());
+ }
+ if (result != expected) {
+ LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result))
+ << tag("expected", format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, mention) {
+ check_mention("@mention", {"@mention"});
+ check_mention("@mention ", {"@mention"});
+ check_mention(" @mention", {"@mention"});
+ check_mention(" @mention ", {"@mention"});
+ check_mention("@abc @xyz @abc @xyz @xxx@yyy @ttt", {});
+ check_mention("@abcde @xyzxy @abcde @xyzxy @xxxxx@yyyyy @ttttt",
+ {"@abcde", "@xyzxy", "@abcde", "@xyzxy", "@xxxxx", "@ttttt"});
+ check_mention("no@mention", {});
+ check_mention("@n", {});
+ check_mention("@abcdefghijklmnopqrstuvwxyz123456", {"@abcdefghijklmnopqrstuvwxyz123456"});
+ check_mention("@abcdefghijklmnopqrstuvwxyz1234567", {});
+ check_mention("нет@mention", {});
+ check_mention("@ya @gif @wiki @vid @bing @pic @bold @imdb @coub @like @vote @giff @cap ya cap @y @yar @bingg @bin",
+ {"@ya", "@gif", "@wiki", "@vid", "@bing", "@pic", "@bold", "@imdb", "@coub", "@like", "@vote", "@giff",
+ "@cap", "@bingg"});
+};
+
+static void check_bot_command(string str, std::vector<string> expected) {
+ auto result_slice = find_bot_commands(str);
+ std::vector<string> result;
+ for (auto &it : result_slice) {
+ result.push_back(it.str());
+ }
+ if (result != expected) {
+ LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result))
+ << tag("expected", format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, bot_command) {
+ // 1..64@3..32
+ check_bot_command("/abc", {"/abc"});
+ check_bot_command(" /abc", {"/abc"});
+ check_bot_command("/abc ", {"/abc"});
+ check_bot_command(" /abc ", {"/abc"});
+ check_bot_command("/a@abc", {"/a@abc"});
+ check_bot_command("/a@b", {});
+ check_bot_command("/@bfdsa", {});
+ check_bot_command("/test/", {});
+}
+
+static void check_hashtag(string str, std::vector<string> expected) {
+ auto result_slice = find_hashtags(str);
+ std::vector<string> result;
+ for (auto &it : result_slice) {
+ result.push_back(it.str());
+ }
+ if (result != expected) {
+ LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result))
+ << tag("expected", format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, hashtag) {
+ check_hashtag("", {});
+ check_hashtag("#", {});
+ check_hashtag("##", {});
+ check_hashtag("###", {});
+ check_hashtag("#a", {"#a"});
+ check_hashtag(" #a", {"#a"});
+ check_hashtag("#a ", {"#a"});
+ check_hashtag(" #я ", {"#я"});
+ check_hashtag(" я#a ", {});
+ check_hashtag(" #a# ", {});
+ check_hashtag(" #123 ", {});
+ check_hashtag(" #123a ", {"#123a"});
+ check_hashtag(" #a123 ", {"#a123"});
+ check_hashtag(" #123a# ", {});
+ check_hashtag(" #" + string(300, '1'), {});
+ check_hashtag(" #" + string(256, '1'), {});
+ check_hashtag(" #" + string(256, '1') + "a ", {});
+ check_hashtag(" #" + string(255, '1') + "a", {"#" + string(255, '1') + "a"});
+ check_hashtag(" #" + string(255, '1') + "Я", {"#" + string(255, '1') + "Я"});
+ check_hashtag(" #" + string(255, '1') + "a" + string(255, 'b') + "# ", {});
+ check_hashtag("#a#b #c #d", {"#c", "#d"});
+ check_hashtag("#test", {"#test"});
+ check_hashtag(u8"\U0001F604\U0001F604\U0001F604\U0001F604 \U0001F604\U0001F604\U0001F604#" + string(200, '1') +
+ "ООО" + string(200, '2'),
+ {"#" + string(200, '1') + "ООО" + string(53, '2')});
+ check_hashtag(u8"#a\u2122", {"#a"});
+}
+
+static void check_cashtag(string str, std::vector<string> expected) {
+ auto result_slice = find_cashtags(str);
+ std::vector<string> result;
+ for (auto &it : result_slice) {
+ result.push_back(it.str());
+ }
+ if (result != expected) {
+ LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result))
+ << tag("expected", format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, cashtag) {
+ check_cashtag("", {});
+ check_cashtag("$", {});
+ check_cashtag("$$", {});
+ check_cashtag("$$$", {});
+ check_cashtag("$a", {});
+ check_cashtag(" $a", {});
+ check_cashtag("$a ", {});
+ check_cashtag(" $я ", {});
+ check_cashtag("$ab", {});
+ check_cashtag("$abc", {});
+ check_cashtag("$", {});
+ check_cashtag("$A", {});
+ check_cashtag("$AB", {});
+ check_cashtag("$АBC", {});
+ check_cashtag("$АВС", {});
+ check_cashtag("$ABC", {"$ABC"});
+ check_cashtag("$ABCD", {"$ABCD"});
+ check_cashtag("$ABCDE", {"$ABCDE"});
+ check_cashtag("$ABCDEF", {"$ABCDEF"});
+ check_cashtag("$ABCDEFG", {"$ABCDEFG"});
+ check_cashtag("$ABCDEFGH", {"$ABCDEFGH"});
+ check_cashtag("$ABCDEFGHJ", {});
+ check_cashtag("$ABCDEFGH1", {});
+ check_cashtag(" $XYZ", {"$XYZ"});
+ check_cashtag("$XYZ ", {"$XYZ"});
+ check_cashtag(" $XYZ ", {"$XYZ"});
+ check_cashtag(" $$XYZ ", {});
+ check_cashtag(" $XYZ$ ", {});
+ check_cashtag(" $ABC1 ", {});
+ check_cashtag(" $1ABC ", {});
+ check_cashtag(" 1$ABC ", {});
+ check_cashtag(" А$ABC ", {});
+ check_cashtag("$ABC$DEF $GHI $KLM", {"$GHI", "$KLM"});
+ check_cashtag("$TEST", {"$TEST"});
+ check_cashtag(u8"$ABC\u2122", {"$ABC"});
+ check_cashtag(u8"\u2122$ABC", {"$ABC"});
+ check_cashtag(u8"\u2122$ABC\u2122", {"$ABC"});
+}
+
+static void check_is_email_address(string str, bool expected) {
+ bool result = is_email_address(str);
+ LOG_IF(FATAL, result != expected) << "Expected " << expected << " as result of is_email_address(" << str << ")";
+}
+
+TEST(MessageEntities, is_email_address) {
+ check_is_email_address("telegram.org", false);
+ check_is_email_address("security@telegram.org", true);
+ check_is_email_address("security.telegram.org", false);
+ check_is_email_address("", false);
+ check_is_email_address("@", false);
+ check_is_email_address("A@a.a.a.ab", true);
+ check_is_email_address("A@a.ab", true);
+ check_is_email_address("Test@aa.aa.aa.aa", true);
+ check_is_email_address("Test@test.abd", true);
+ check_is_email_address("a@a.a.a.ab", true);
+ check_is_email_address("test@test.abd", true);
+ check_is_email_address("test@test.com", true);
+ check_is_email_address("test.abd", false);
+ check_is_email_address("a.ab", false);
+ check_is_email_address("a.bc@d.ef", true);
+
+ vector<string> bad_userdatas = {"",
+ "a.a.a.a.a.a.a.a.a.a.a.a",
+ "+.+.+.+.+.+",
+ "*.a.a",
+ "a.*.a",
+ "a.a.*",
+ "a.a.",
+ "a.a.abcdefghijklmnopqrstuvwxyz0123456789",
+ "a.abcdefghijklmnopqrstuvwxyz0.a",
+ "abcdefghijklmnopqrstuvwxyz0.a.a"};
+ vector<string> good_userdatas = {"a.a.a.a.a.a.a.a.a.a.a",
+ "a+a+a+a+a+a+a+a+a+a+a",
+ "+.+.+.+.+._",
+ "aozAQZ0-5-9_+-aozAQZ0-5-9_.aozAQZ0-5-9_.-._.+-",
+ "a.a.a",
+ "a.a.abcdefghijklmnopqrstuvwxyz012345678",
+ "a.abcdefghijklmnopqrstuvwxyz.a",
+ "a..a",
+ "abcdefghijklmnopqrstuvwxyz.a.a",
+ ".a.a"};
+
+ vector<string> bad_domains = {"",
+ ".",
+ "abc",
+ "localhost",
+ "a.a.a.a.a.a.a.ab",
+ ".......",
+ "a.a.a.a.a.a+ab",
+ "a+a.a.a.a.a.ab",
+ "a.a.a.a.a.a.a",
+ "a.a.a.a.a.a.abcdefg",
+ "a.a.a.a.a.a.ab0yz",
+ "a.a.a.a.a.a.ab9yz",
+ "a.a.a.a.a.a.ab-yz",
+ "a.a.a.a.a.a.ab_yz",
+ "a.a.a.a.a.a.ab*yz",
+ ".ab",
+ ".a.ab",
+ "a..ab",
+ "a.a.a..a.ab",
+ ".a.a.a.a.ab",
+ "abcdefghijklmnopqrstuvwxyz01234.ab",
+ "ab0cd.abd.aA*sd.0.9.0-9.ABOYZ",
+ "ab*cd.abd.aAasd.0.9.0-9.ABOYZ",
+ "ab0cd.abd.aAasd.0.9.0*9.ABOYZ",
+ "*b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0c*.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.0-*.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.*-9.ABOYZ",
+ "-b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0c-.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.-.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.--9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.0--.ABOYZ",
+ "_b0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0c_.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd._.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9._-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.0-_.ABOYZ",
+ "-.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.-.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9.-.ABOYZ",
+ "_.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d._.0.9.0-9.ABOYZ",
+ "ab0cd.ab_d.aA-sd.0.9._.ABOYZ"};
+ vector<string> good_domains = {"a.a.a.a.a.a.ab",
+ "a.a.a.a.a.a.abcdef",
+ "a.a.a.a.a.a.aboyz",
+ "a.a.a.a.a.a.ABOYZ",
+ "a.a.a.a.a.a.AbOyZ",
+ "abcdefghijklmnopqrstuvwxyz0123.ab",
+ "ab0cd.ab_d.aA-sd.0.9.0-9.ABOYZ",
+ "A.Z.aA-sd.a.z.0-9.ABOYZ"};
+
+ for (auto &userdata : bad_userdatas) {
+ for (auto &domain : bad_domains) {
+ check_is_email_address(userdata + '@' + domain, false);
+ check_is_email_address(userdata + domain, false);
+ }
+ for (auto &domain : good_domains) {
+ check_is_email_address(userdata + '@' + domain, false);
+ check_is_email_address(userdata + domain, false);
+ }
+ }
+ for (auto &userdata : good_userdatas) {
+ for (auto &domain : bad_domains) {
+ check_is_email_address(userdata + '@' + domain, false);
+ check_is_email_address(userdata + domain, false);
+ }
+ for (auto &domain : good_domains) {
+ check_is_email_address(userdata + '@' + domain, true);
+ check_is_email_address(userdata + domain, false);
+ }
+ }
+}
+
+static void check_url(string str, std::vector<string> expected_urls,
+ std::vector<string> expected_email_addresses = {}) {
+ auto result_slice = find_urls(str);
+ std::vector<string> result_urls;
+ std::vector<string> result_email_addresses;
+ for (auto &it : result_slice) {
+ if (!it.second) {
+ result_urls.push_back(it.first.str());
+ } else {
+ result_email_addresses.push_back(it.first.str());
+ }
+ }
+ if (result_urls != expected_urls) {
+ LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result_urls))
+ << tag("expected", format::as_array(expected_urls));
+ }
+ if (result_email_addresses != expected_email_addresses) {
+ LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result_email_addresses))
+ << tag("expected", format::as_array(expected_email_addresses));
+ }
+}
+
+TEST(MessageEntities, url) {
+ check_url("telegram.org", {"telegram.org"});
+ check_url("(telegram.org)", {"telegram.org"});
+ check_url("\ntelegram.org)", {"telegram.org"});
+ check_url(" telegram.org)", {"telegram.org"});
+ check_url(".telegram.org)", {});
+ check_url("()telegram.org/?q=()", {"telegram.org/?q=()"});
+ check_url("\"telegram.org\"", {"telegram.org"});
+ check_url(" telegram. org. www. com... telegram.org... ...google.com...", {"telegram.org"});
+ check_url(" telegram.org ", {"telegram.org"});
+ check_url("Такой сайт: http://www.google.com или такой telegram.org ", {"http://www.google.com", "telegram.org"});
+ check_url(" telegram.org. ", {"telegram.org"});
+ check_url("http://google,.com", {});
+ check_url("http://telegram.org/?asd=123#123.", {"http://telegram.org/?asd=123#123"});
+ check_url("[http://google.com](test)", {"http://google.com"});
+ check_url("", {});
+ check_url(".", {});
+ check_url("http://@google.com", {});
+ check_url("http://@goog.com", {}); // TODO: server fix
+ check_url("http://@@google.com", {"http://@@google.com"});
+ check_url("http://a@google.com", {"http://a@google.com"});
+ check_url("http://test@google.com", {"http://test@google.com"});
+ check_url("google.com:᪉᪉᪉᪉᪉", {"google.com"});
+ check_url("https://telegram.org", {"https://telegram.org"});
+ check_url("http://telegram.org", {"http://telegram.org"});
+ check_url("ftp://telegram.org", {"ftp://telegram.org"});
+ check_url("ftps://telegram.org", {});
+ check_url("sftp://telegram.org", {"sftp://telegram.org"});
+ check_url("hTtPs://telegram.org", {"hTtPs://telegram.org"});
+ check_url("HTTP://telegram.org", {"HTTP://telegram.org"});
+ check_url("аHTTP://telegram.org", {"HTTP://telegram.org"});
+ check_url("sHTTP://telegram.org", {});
+ check_url("://telegram.org", {});
+ check_url("google.com:᪀᪀", {"google.com"});
+ check_url(
+ "http://"
+ "abcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkab"
+ "cdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcd"
+ "efghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdef"
+ "ghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefgh"
+ "ijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghij"
+ "kabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijka"
+ "bcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabc"
+ "defghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijkabcdefghijk.com",
+ {});
+ check_url("http://  .com", {});
+ check_url("URL:     .com", {});
+ check_url("URL: .com", {});
+ check_url(".com", {});
+ check_url("http://  .", {});
+ check_url("http://.", {});
+ check_url("http://.com", {});
+ check_url("http:// .", {});
+ check_url(",ahttp://google.com", {"http://google.com"});
+ check_url(".ahttp://google.com", {});
+ check_url("http://1.0", {});
+ check_url("http://a.0", {});
+ check_url("http://a.a", {});
+ check_url("google.com:1#ab c", {"google.com:1#ab"});
+ check_url("google.com:1#", {"google.com:1#"});
+ check_url("google.com:1#1", {"google.com:1#1"});
+ check_url("google.com:00000001/abs", {"google.com:00000001/abs"});
+ check_url("google.com:000000065535/abs", {"google.com:000000065535/abs"});
+ check_url("google.com:000000065536/abs", {"google.com"});
+ check_url("google.com:000000080/abs", {"google.com:000000080/abs"});
+ check_url("google.com:0000000/abs", {"google.com"});
+ check_url("google.com:0/abs", {"google.com"});
+ check_url("google.com:/abs", {"google.com"});
+ check_url("google.com:65535", {"google.com:65535"});
+ check_url("google.com:65536", {"google.com"});
+ check_url("google.com:99999", {"google.com"});
+ check_url("google.com:100000", {"google.com"});
+ check_url("127.001", {});
+ check_url("127.0.0.1", {"127.0.0.1"});
+ check_url("127.0.0.01", {});
+ check_url("127.0.0.256", {});
+ check_url("127.0.0.300", {});
+ check_url("127.0.0.1000", {});
+ check_url("127.0.0.260", {});
+ check_url("1.0", {});
+ check_url("www.🤙.tk", {"www.🤙.tk"});
+ check_url("a.ab", {});
+ check_url("test.abd", {});
+ check_url("ТеСт.Москва", {});
+ check_url("ТеСт.МоСкВΑ", {});
+ check_url("ТеСт.МоСкВа", {"ТеСт.МоСкВа"});
+ check_url("ТеСт.МоСкВач", {});
+ check_url("http://ÀТеСт.МоСкВач", {"http://ÀТеСт.МоСкВач"});
+ check_url("ÀÁ.com. ÀÁ.com.", {"ÀÁ.com", "ÀÁ.com"});
+ check_url("ÀÁ.com,ÀÁ.com.", {"ÀÁ.com", "ÀÁ.com"});
+ check_url("teiegram.org", {});
+ check_url("http://test.google.com/?q=abc()}[]def", {"http://test.google.com/?q=abc()"});
+ check_url("http://test.google.com/?q=abc([{)]}def", {"http://test.google.com/?q=abc([{)]}def"});
+ check_url("http://test.google.com/?q=abc(){}]def", {"http://test.google.com/?q=abc(){}"});
+ check_url("http://test.google.com/?q=abc){}[]def", {"http://test.google.com/?q=abc"});
+ check_url("http://test.google.com/?q=abc(){}[]def", {"http://test.google.com/?q=abc(){}[]def"});
+ check_url("http://test-.google.com", {});
+ check_url("http://test_.google.com", {"http://test_.google.com"});
+ check_url("http://google_.com", {});
+ check_url("http://google._com_", {});
+ check_url("http://[2001:4860:0:2001::68]/", {}); // TODO
+ check_url("test.abd", {});
+ check_url("/.b/..a @.....@/. a.ba", {"a.ba"});
+ check_url("bbbbbbbbbbbbbb.@.@", {});
+ check_url("http://google.com/", {"http://google.com/"});
+ check_url("http://google.com?", {"http://google.com"});
+ check_url("http://google.com#", {"http://google.com#"});
+ check_url("http://google.com/?", {"http://google.com/"});
+ check_url("https://www.google.com/ab,", {"https://www.google.com/ab"});
+ check_url("@.", {});
+ check_url(
+ "a.b.google.com dfsknnfs gsdfgsg http://códuia.de/ dffdg,\" 12)(cpia.de/())(\" http://гришка.рф/ sdufhdf "
+ "http://xn--80afpi2a3c.xn--p1ai/ I have a good time.Thanks, guys!\n\n(hdfughidufhgdis)go#ogle.com гришка.рф "
+ "hsighsdf gi почта.рф\n\n✪df.ws/123 "
+ "xn--80afpi2a3c.xn--p1ai\n\nhttp://foo.com/blah_blah\nhttp://foo.com/blah_blah/\n(Something like "
+ "http://foo.com/blah_blah)\nhttp://foo.com/blah_blah_(wikipedi8989a_Вася)\n(Something like "
+ "http://foo.com/blah_blah_(Стакан_007))\nhttp://foo.com/blah_blah.\nhttp://foo.com/blah_blah/.\n<http://foo.com/"
+ "blah_blah>\n<http://fo@@@@@@@@@^%#*@^&@$#*@#%^*&!^#o.com/blah_blah/>\nhttp://foo.com/blah_blah,\nhttp://"
+ "www.example.com/wpstyle/?p=364.\nhttp://✪df.ws/123\nrdar://1234\nrdar:/1234\nhttp://"
+ "userid:password@example.com:8080\nhttp://userid@example.com\nhttp://userid@example.com:8080\nhttp://"
+ "userid:password@example.com\nhttp://example.com:8080 "
+ "x-yojimbo-item://6303E4C1-xxxx-45A6-AB9D-3A908F59AE0E\nmessage://"
+ "%3c330e7f8409726r6a4ba78dkf1fd71420c1bf6ff@mail.gmail.com%3e\nhttp://➡️.ws/䨹\nwww.➡️.ws/"
+ "䨹\n<tag>http://example.com</tag>\nJust a www.example.com "
+ "link.\n\n➡️.ws/"
+ "䨹\n\nabcdefghijklmnopqrstuvwxyz0123456789qwe_sdfsdf.aweawe-sdfs.com\nwww.🤙.tk:1\ngoogle.com:"
+ "᪉᪉᪉᪉\ngoogle."
+ "com:᪀᪀\nhttp://  .com\nURL:     .com\nURL: "
+ ".com\n\ngoogle.com?qwe\ngoogle.com#qwe\ngoogle.com/?\ngoogle.com/#\ngoogle.com?\ngoogle.com#\n",
+ {"a.b.google.com",
+ "http://códuia.de/",
+ "cpia.de/()",
+ "http://гришка.рф/",
+ "http://xn--80afpi2a3c.xn--p1ai/",
+ "гришка.рф",
+ "почта.рф",
+ "✪df.ws/123",
+ "xn--80afpi2a3c.xn--p1ai",
+ "http://foo.com/blah_blah",
+ "http://foo.com/blah_blah/",
+ "http://foo.com/blah_blah",
+ "http://foo.com/blah_blah_(wikipedi8989a_Вася)",
+ "http://foo.com/blah_blah_(Стакан_007)",
+ "http://foo.com/blah_blah",
+ "http://foo.com/blah_blah/",
+ "http://foo.com/blah_blah",
+ "http://foo.com/blah_blah",
+ "http://www.example.com/wpstyle/?p=364",
+ "http://✪df.ws/123",
+ "http://userid:password@example.com:8080",
+ "http://userid@example.com",
+ "http://userid@example.com:8080",
+ "http://userid:password@example.com",
+ "http://example.com:8080",
+ "http://➡️.ws/䨹",
+ "www.➡️.ws/䨹",
+ "http://example.com",
+ "www.example.com",
+ "➡️.ws/䨹",
+ "abcdefghijklmnopqrstuvwxyz0123456789qwe_sdfsdf.aweawe-sdfs.com",
+ "www.🤙.tk:1",
+ "google.com",
+ "google.com",
+ "google.com?qwe",
+ "google.com#qwe",
+ "google.com/",
+ "google.com/#",
+ "google.com",
+ "google.com#"});
+ check_url("https://t.…", {});
+ check_url("('http://telegram.org/a-b/?br=ie&lang=en',)", {"http://telegram.org/a-b/?br=ie&lang=en"});
+ check_url("https://ai.telegram.org/bot%20bot/test-...", {"https://ai.telegram.org/bot%20bot/test-"});
+ check_url("a.bc@c.com", {}, {"a.bc@c.com"});
+ check_url("https://a.bc@c.com", {"https://a.bc@c.com"});
+ check_url("https://a.de[bc@c.com", {"https://a.de"}, {"bc@c.com"});
+ check_url("https://a.de/bc@c.com", {"https://a.de/bc@c.com"});
+ check_url("https://a.de]bc@c.com", {"https://a.de"}, {"bc@c.com"});
+ check_url("https://a.de{bc@c.com", {"https://a.de"}, {"bc@c.com"});
+ check_url("https://a.de}bc@c.com", {"https://a.de"}, {"bc@c.com"});
+ check_url("https://a.de(bc@c.com", {"https://a.de"}, {"bc@c.com"});
+ check_url("https://a.de)bc@c.com", {"https://a.de"}, {"bc@c.com"});
+ check_url("https://a.de\\bc@c.com", {"https://a.de\\bc@c.com"});
+ check_url("https://a.de'bc@c.com", {"https://a.de"}, {"bc@c.com"});
+ check_url("https://a.de`bc@c.com", {"https://a.de"}, {"bc@c.com"});
+ check_url("https://a.bc:de.fg@c.com", {"https://a.bc:de.fg@c.com"});
+ check_url("https://a:h.bc:de.fg@c.com", {"https://a:h.bc:de.fg@c.com"});
+ check_url("https://abc@c.com", {"https://abc@c.com"});
+ check_url("https://de[bc@c.com", {}, {"bc@c.com"});
+ check_url("https://de/bc@c.com", {});
+ check_url("https://de]bc@c.com", {}, {"bc@c.com"});
+ check_url("https://de{bc@c.com", {}, {"bc@c.com"});
+ check_url("https://de}bc@c.com", {}, {"bc@c.com"});
+ check_url("https://de(bc@c.com", {}, {"bc@c.com"});
+ check_url("https://de)bc@c.com", {}, {"bc@c.com"});
+ check_url("https://de\\bc@c.com", {"https://de\\bc@c.com"});
+ check_url("https://de'bc@c.com", {}, {"bc@c.com"});
+ check_url("https://de`bc@c.com", {}, {"bc@c.com"});
+ check_url("https://bc:defg@c.com", {"https://bc:defg@c.com"});
+ check_url("https://a:hbc:defg@c.com", {"https://a:hbc:defg@c.com"});
+ check_url("https://a.bc@test.com:cd.com", {"https://a.bc@test.com", "cd.com"});
+ check_url("telegram.Org", {});
+ check_url("telegram.ORG", {"telegram.ORG"});
+ check_url("a.b.c.com.a.b.c", {});
+ check_url("File '/usr/views.py'", {}); // TODO server fix
+ check_url("@views.py'", {}); // TODO server fix
+ check_url("#views.py'", {}); // TODO server fix
+ check_url("/views.py'", {}); // TODO server fix
+ check_url(".views.py", {});
+ check_url("'views.py'", {"views.py"});
+ check_url("bug.http://test.com/test/+#", {}); // TODO {"http://test.com/test/+#"}
+ check_url("//AB.C.D.E.F.GH//", {});
+ check_url("<http://www.ics.uci.edu/pub/ietf/uri/historical.html#WARNING>",
+ {"http://www.ics.uci.edu/pub/ietf/uri/historical.html#WARNING"});
+ check_url("Look :test@example.com", {":test@example.com"}, {}); // TODO {}, {"test@example.com"}
+ check_url("http://test.com#a", {"http://test.com#a"});
+ check_url("http://test.com#", {"http://test.com#"});
+ check_url("http://test.com?#", {"http://test.com?#"});
+ check_url("http://test.com/?#", {"http://test.com/?#"});
+ check_url("https://t.me/abcdef…", {"https://t.me/abcdef"});
+ check_url("https://t.me…", {"https://t.me"});
+ check_url("https://t.m…", {});
+ check_url("https://t.…", {});
+ check_url("https://t…", {});
+ check_url("👉http://ab.com/cdefgh-1IJ", {"http://ab.com/cdefgh-1IJ"});
+ check_url("...👉http://ab.com/cdefgh-1IJ", {}); // TODO
+}
diff --git a/protocols/Telegram/tdlib/td/test/mtproto.cpp b/protocols/Telegram/tdlib/td/test/mtproto.cpp
new file mode 100644
index 0000000000..7702a1e37b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/mtproto.cpp
@@ -0,0 +1,347 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/utils/tests.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/mtproto/crypto.h"
+#include "td/mtproto/Handshake.h"
+#include "td/mtproto/HandshakeActor.h"
+#include "td/mtproto/HandshakeConnection.h"
+#include "td/mtproto/PingConnection.h"
+#include "td/mtproto/RawConnection.h"
+
+#include "td/net/Socks5.h"
+
+#include "td/telegram/ConfigManager.h"
+#include "td/telegram/net/PublicRsaKeyShared.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/port/IPAddress.h"
+#include "td/utils/port/SocketFd.h"
+#include "td/utils/Status.h"
+
+REGISTER_TESTS(mtproto);
+
+using namespace td;
+using namespace mtproto;
+
+#if !TD_WINDOWS && !TD_EMSCRIPTEN // TODO
+TEST(Mtproto, config) {
+ ConcurrentScheduler sched;
+ int threads_n = 0;
+ sched.init(threads_n);
+
+ int cnt = 3;
+ {
+ auto guard = sched.get_current_guard();
+ get_simple_config_azure(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
+ LOG(ERROR) << to_string(r_simple_config.ok());
+ if (--cnt == 0) {
+ Scheduler::instance()->finish();
+ }
+ }))
+ .release();
+
+ get_simple_config_google_app(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
+ LOG(ERROR) << to_string(r_simple_config.ok());
+ if (--cnt == 0) {
+ Scheduler::instance()->finish();
+ }
+ }))
+ .release();
+
+ get_simple_config_google_dns(PromiseCreator::lambda([&](Result<SimpleConfig> r_simple_config) {
+ LOG(ERROR) << to_string(r_simple_config.ok());
+ if (--cnt == 0) {
+ Scheduler::instance()->finish();
+ }
+ }))
+ .release();
+ }
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty;
+ }
+ sched.finish();
+}
+#endif
+
+TEST(Mtproto, encrypted_config) {
+ string data =
+ " LQ2 \b\n\tru6xVXpHHckW4eQWK0X3uThupVOor5sXT8t298IjDksYeUseQTOIrnUqiQj7o"
+ "+ZgPfhnfe+lfcQA+naax9akgllimjlJtL5riTf3O7iqZSnJ614qmCucxqqVTbXk/"
+ "hY2KaJTtsMqk7cShJjM3aQ4DD40h2InTaG7uyVO2q7K0GMUTeY3AM0Rt1lUjKHLD"
+ "g4RwjTzZaG8TwfgL/mZ7jsvgTTTATPWKUo7SmxQ9Hsj+07NMGqr6JKZS6aiU1Knz"
+ "VGCZ3OJEyRYocktN4HjaLpkguilaHWlVM2UNFUd5a+ajfLIiiKlH0FRC3XZ12CND"
+ "Y+NBjv0I57N2O4fBfswTlA== ";
+ auto config = decode_config(data).move_as_ok();
+}
+
+class TestPingActor : public Actor {
+ public:
+ TestPingActor(IPAddress ip_address, Status *result) : ip_address_(ip_address), result_(result) {
+ }
+
+ private:
+ IPAddress ip_address_;
+ std::unique_ptr<mtproto::PingConnection> ping_connection_;
+ Status *result_;
+
+ void start_up() override {
+ ping_connection_ = std::make_unique<mtproto::PingConnection>(std::make_unique<mtproto::RawConnection>(
+ SocketFd::open(ip_address_).move_as_ok(), mtproto::TransportType::Tcp, nullptr));
+
+ ping_connection_->get_pollable().set_observer(this);
+ subscribe(ping_connection_->get_pollable());
+ set_timeout_in(10);
+ yield();
+ }
+ void tear_down() override {
+ unsubscribe_before_close(ping_connection_->get_pollable());
+ ping_connection_->close();
+ Scheduler::instance()->finish();
+ }
+
+ void loop() override {
+ auto status = ping_connection_->flush();
+ if (status.is_error()) {
+ *result_ = std::move(status);
+ return stop();
+ }
+ if (ping_connection_->was_pong()) {
+ LOG(ERROR) << "GOT PONG";
+ return stop();
+ }
+ }
+
+ void timeout_expired() override {
+ *result_ = Status::Error("Timeout expired");
+ stop();
+ }
+};
+
+static IPAddress get_default_ip_address() {
+ IPAddress ip_address;
+ ip_address.init_ipv4_port("149.154.167.40", 80).ensure();
+ return ip_address;
+}
+
+class Mtproto_ping : public td::Test {
+ public:
+ using Test::Test;
+ bool step() final {
+ if (!is_inited_) {
+ sched_.init(0);
+ sched_.create_actor_unsafe<TestPingActor>(0, "Pinger", get_default_ip_address(), &result_).release();
+ sched_.start();
+ is_inited_ = true;
+ }
+
+ bool ret = sched_.run_main(10);
+ if (ret) {
+ return true;
+ }
+ sched_.finish();
+ if (result_.is_error()) {
+ LOG(ERROR) << result_;
+ }
+ ASSERT_TRUE(result_.is_ok());
+ return false;
+ }
+
+ private:
+ bool is_inited_ = false;
+ ConcurrentScheduler sched_;
+ Status result_;
+};
+Mtproto_ping mtproto_ping("Mtproto_ping");
+
+class Context : public AuthKeyHandshakeContext {
+ public:
+ DhCallback *get_dh_callback() override {
+ return nullptr;
+ }
+ PublicRsaKeyInterface *get_public_rsa_key_interface() override {
+ return &public_rsa_key;
+ }
+
+ private:
+ PublicRsaKeyShared public_rsa_key{DcId::empty()};
+};
+
+class HandshakeTestActor : public Actor {
+ public:
+ explicit HandshakeTestActor(Status *result) : result_(result) {
+ }
+
+ private:
+ Status *result_;
+ bool wait_for_raw_connection_ = false;
+ std::unique_ptr<RawConnection> raw_connection_;
+ bool wait_for_handshake_ = false;
+ std::unique_ptr<AuthKeyHandshake> handshake_;
+ Status status_;
+ bool wait_for_result_ = false;
+
+ void tear_down() override {
+ if (raw_connection_) {
+ raw_connection_->close();
+ }
+ finish(Status::Error("Interrupted"));
+ }
+ void loop() override {
+ if (!wait_for_raw_connection_ && !raw_connection_) {
+ raw_connection_ = std::make_unique<mtproto::RawConnection>(SocketFd::open(get_default_ip_address()).move_as_ok(),
+ mtproto::TransportType::Tcp, nullptr);
+ }
+ if (!wait_for_handshake_ && !handshake_) {
+ handshake_ = std::make_unique<AuthKeyHandshake>(0);
+ }
+ if (raw_connection_ && handshake_) {
+ if (wait_for_result_) {
+ wait_for_result_ = false;
+ if (status_.is_error()) {
+ finish(std::move(status_));
+ return stop();
+ }
+ if (!handshake_->is_ready_for_finish()) {
+ finish(Status::Error("Key is not ready.."));
+ return stop();
+ }
+ finish(Status::OK());
+ return stop();
+ }
+
+ wait_for_result_ = true;
+ create_actor<HandshakeActor>(
+ "HandshakeActor", std::move(handshake_), std::move(raw_connection_), std::make_unique<Context>(), 10.0,
+ PromiseCreator::lambda([self = actor_id(this)](Result<std::unique_ptr<RawConnection>> raw_connection) {
+ send_closure(self, &HandshakeTestActor::got_connection, std::move(raw_connection), 1);
+ }),
+ PromiseCreator::lambda([self = actor_id(this)](Result<std::unique_ptr<AuthKeyHandshake>> handshake) {
+ send_closure(self, &HandshakeTestActor::got_handshake, std::move(handshake), 1);
+ }))
+ .release();
+ wait_for_raw_connection_ = true;
+ wait_for_handshake_ = true;
+ }
+ }
+
+ void got_connection(Result<std::unique_ptr<RawConnection>> r_raw_connection, int32 dummy) {
+ CHECK(wait_for_raw_connection_);
+ wait_for_raw_connection_ = false;
+ if (r_raw_connection.is_ok()) {
+ raw_connection_ = r_raw_connection.move_as_ok();
+ status_ = Status::OK();
+ } else {
+ status_ = r_raw_connection.move_as_error();
+ }
+ // TODO: save error
+ loop();
+ }
+
+ void got_handshake(Result<std::unique_ptr<AuthKeyHandshake>> r_handshake, int32 dummy) {
+ CHECK(wait_for_handshake_);
+ wait_for_handshake_ = false;
+ CHECK(r_handshake.is_ok());
+ handshake_ = r_handshake.move_as_ok();
+ loop();
+ }
+
+ void finish(Status status) {
+ if (!result_) {
+ return;
+ }
+ *result_ = std::move(status);
+ result_ = nullptr;
+ Scheduler::instance()->finish();
+ }
+};
+
+class Mtproto_handshake : public td::Test {
+ public:
+ using Test::Test;
+ bool step() final {
+ if (!is_inited_) {
+ sched_.init(0);
+ sched_.create_actor_unsafe<HandshakeTestActor>(0, "HandshakeTestActor", &result_).release();
+ sched_.start();
+ is_inited_ = true;
+ }
+
+ bool ret = sched_.run_main(10);
+ if (ret) {
+ return true;
+ }
+ sched_.finish();
+ if (result_.is_error()) {
+ LOG(ERROR) << result_;
+ }
+ ASSERT_TRUE(result_.is_ok());
+ return false;
+ }
+
+ private:
+ bool is_inited_ = false;
+ ConcurrentScheduler sched_;
+ Status result_;
+};
+Mtproto_handshake mtproto_handshake("Mtproto_handshake");
+
+class Socks5TestActor : public Actor {
+ public:
+ void start_up() override {
+ auto promise = PromiseCreator::lambda([actor_id = actor_id(this)](Result<SocketFd> res) {
+ send_closure(actor_id, &Socks5TestActor::on_result, std::move(res), false);
+ });
+
+ class Callback : public Socks5::Callback {
+ public:
+ explicit Callback(Promise<SocketFd> promise) : promise_(std::move(promise)) {
+ }
+ void set_result(Result<SocketFd> result) override {
+ promise_.set_result(std::move(result));
+ }
+ void on_connected() override {
+ }
+
+ private:
+ Promise<SocketFd> promise_;
+ };
+
+ IPAddress socks5_ip;
+ socks5_ip.init_ipv4_port("131.191.89.104", 43077).ensure();
+ IPAddress mtproto_ip = get_default_ip_address();
+
+ auto r_socket = SocketFd::open(socks5_ip);
+ create_actor<Socks5>("socks5", r_socket.move_as_ok(), mtproto_ip, "", "",
+ std::make_unique<Callback>(std::move(promise)), actor_shared())
+ .release();
+ }
+
+ private:
+ void on_result(Result<SocketFd> res, bool dummy) {
+ res.ensure();
+ Scheduler::instance()->finish();
+ }
+};
+
+TEST(Mtproto, socks5) {
+ return;
+ ConcurrentScheduler sched;
+ int threads_n = 0;
+ sched.init(threads_n);
+
+ sched.create_actor_unsafe<Socks5TestActor>(0, "Socks5TestActor").release();
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty;
+ }
+ sched.finish();
+}
diff --git a/protocols/Telegram/tdlib/td/test/secret.cpp b/protocols/Telegram/tdlib/td/test/secret.cpp
new file mode 100644
index 0000000000..2abed9787b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/secret.cpp
@@ -0,0 +1,1056 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/actor/PromiseFuture.h"
+
+#include "td/db/binlog/detail/BinlogEventsProcessor.h"
+
+#include "td/mtproto/crypto.h"
+#include "td/mtproto/utils.h"
+
+#include "td/telegram/Global.h"
+#include "td/telegram/MessageId.h"
+#include "td/telegram/SecretChatActor.h"
+#include "td/telegram/SecretChatId.h"
+
+#include "td/telegram/secret_api.h"
+#include "td/telegram/telegram_api.h"
+
+#include "td/tl/tl_object_parse.h"
+#include "td/tl/tl_object_store.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/buffer.h"
+#include "td/utils/crypto.h"
+#include "td/utils/format.h"
+#include "td/utils/Gzip.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/tests.h"
+#include "td/utils/tl_helpers.h"
+#include "td/utils/tl_parsers.h"
+#include "td/utils/tl_storers.h"
+
+#include <cstdio>
+#include <ctime>
+#include <limits>
+#include <map>
+#include <memory>
+
+REGISTER_TESTS(secret);
+
+namespace my_api {
+
+using namespace td;
+
+//messages_getDhConfig
+class messages_getDhConfig {
+ public:
+ int32 version_;
+ int32 random_length_;
+
+ messages_getDhConfig() = default;
+
+ messages_getDhConfig(int32 version_, int32 random_length_);
+
+ static const int32 ID = 651135312;
+
+ explicit messages_getDhConfig(TlBufferParser &p)
+#define FAIL(error) p.set_error(error)
+ : version_(TlFetchInt::parse(p))
+ , random_length_(TlFetchInt::parse(p))
+#undef FAIL
+ {
+ }
+};
+
+//InputUser
+class InputUser {
+ public:
+ static tl_object_ptr<InputUser> fetch(TlBufferParser &p);
+};
+
+class inputUser final : public InputUser {
+ public:
+ int32 user_id_;
+ int64 access_hash_;
+
+ static const int32 ID = -668391402;
+ inputUser() = default;
+
+ explicit inputUser(TlBufferParser &p)
+#define FAIL(error) p.set_error(error)
+ : user_id_(TlFetchInt::parse(p))
+ , access_hash_(TlFetchLong::parse(p))
+#undef FAIL
+ {
+ }
+};
+tl_object_ptr<InputUser> InputUser::fetch(TlBufferParser &p) {
+#define FAIL(error) \
+ p.set_error(error); \
+ return nullptr;
+ int constructor = p.fetch_int();
+ switch (constructor) {
+ case inputUser::ID:
+ return make_tl_object<inputUser>(p);
+ default:
+ FAIL(PSTRING() << "Unknown constructor found " << format::as_hex(constructor));
+ }
+#undef FAIL
+}
+
+class messages_requestEncryption final {
+ public:
+ tl_object_ptr<InputUser> user_id_;
+ int32 random_id_;
+ BufferSlice g_a_;
+
+ static const int32 ID = -162681021;
+ messages_requestEncryption();
+
+ explicit messages_requestEncryption(TlBufferParser &p)
+#define FAIL(error) p.set_error(error)
+ : user_id_(TlFetchObject<InputUser>::parse(p))
+ , random_id_(TlFetchInt::parse(p))
+ , g_a_(TlFetchBytes<BufferSlice>::parse(p))
+#undef FAIL
+ {
+ }
+};
+
+class inputEncryptedChat final {
+ public:
+ int32 chat_id_;
+ int64 access_hash_;
+
+ inputEncryptedChat() = default;
+
+ static const int32 ID = -247351839;
+ explicit inputEncryptedChat(TlBufferParser &p)
+#define FAIL(error) p.set_error(error)
+ : chat_id_(TlFetchInt::parse(p))
+ , access_hash_(TlFetchLong::parse(p))
+#undef FAIL
+ {
+ }
+ static tl_object_ptr<inputEncryptedChat> fetch(TlBufferParser &p) {
+ return make_tl_object<inputEncryptedChat>(p);
+ }
+};
+
+class messages_acceptEncryption final {
+ public:
+ tl_object_ptr<inputEncryptedChat> peer_;
+ BufferSlice g_b_;
+ int64 key_fingerprint_;
+
+ messages_acceptEncryption() = default;
+
+ static const int32 ID = 1035731989;
+
+ explicit messages_acceptEncryption(TlBufferParser &p)
+#define FAIL(error) p.set_error(error)
+ : peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
+ , g_b_(TlFetchBytes<BufferSlice>::parse(p))
+ , key_fingerprint_(TlFetchLong::parse(p))
+#undef FAIL
+ {
+ }
+};
+
+class messages_sendEncryptedService final {
+ public:
+ tl_object_ptr<inputEncryptedChat> peer_;
+ int64 random_id_;
+ BufferSlice data_;
+
+ messages_sendEncryptedService() = default;
+ static const int32 ID = 852769188;
+ explicit messages_sendEncryptedService(TlBufferParser &p)
+#define FAIL(error) p.set_error(error)
+ : peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
+ , random_id_(TlFetchLong::parse(p))
+ , data_(TlFetchBytes<BufferSlice>::parse(p))
+#undef FAIL
+ {
+ }
+};
+
+class messages_sendEncrypted final {
+ public:
+ tl_object_ptr<inputEncryptedChat> peer_;
+ int64 random_id_;
+ BufferSlice data_;
+
+ messages_sendEncrypted() = default;
+ static const int32 ID = -1451792525;
+
+ explicit messages_sendEncrypted(TlBufferParser &p)
+#define FAIL(error) p.set_error(error)
+ : peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
+ , random_id_(TlFetchLong::parse(p))
+ , data_(TlFetchBytes<BufferSlice>::parse(p))
+#undef FAIL
+ {
+ }
+};
+
+template <class F>
+static void downcast_call(TlBufferParser &p, F &&f) {
+ auto id = p.fetch_int();
+ switch (id) {
+ case messages_getDhConfig::ID:
+ return f(*make_tl_object<messages_getDhConfig>(p));
+ case messages_requestEncryption::ID:
+ return f(*make_tl_object<messages_requestEncryption>(p));
+ case messages_acceptEncryption::ID:
+ return f(*make_tl_object<messages_acceptEncryption>(p));
+ case messages_sendEncrypted::ID:
+ return f(*make_tl_object<messages_sendEncrypted>(p));
+ case messages_sendEncryptedService::ID:
+ return f(*make_tl_object<messages_sendEncryptedService>(p));
+ default:
+ CHECK(0) << id;
+ UNREACHABLE();
+ }
+}
+
+class messages_dhConfig final {
+ public:
+ int32 g_;
+ BufferSlice p_;
+ int32 version_;
+ BufferSlice random_;
+
+ messages_dhConfig() = default;
+
+ messages_dhConfig(int32 g_, BufferSlice &&p_, int32 version_, BufferSlice &&random_)
+ : g_(g_), p_(std::move(p_)), version_(version_), random_(std::move(random_)) {
+ }
+
+ static const int32 ID = 740433629;
+ int32 get_id() const {
+ return ID;
+ }
+
+ void store(TlStorerCalcLength &s) const {
+ (void)sizeof(s);
+ TlStoreBinary::store(g_, s);
+ TlStoreString::store(p_, s);
+ TlStoreBinary::store(version_, s);
+ TlStoreString::store(random_, s);
+ }
+ void store(TlStorerUnsafe &s) const {
+ (void)sizeof(s);
+ TlStoreBinary::store(g_, s);
+ TlStoreString::store(p_, s);
+ TlStoreBinary::store(version_, s);
+ TlStoreString::store(random_, s);
+ }
+};
+
+class encryptedChat final {
+ public:
+ int32 id_;
+ int64 access_hash_;
+ int32 date_;
+ int32 admin_id_;
+ int32 participant_id_;
+ BufferSlice g_a_or_b_;
+ int64 key_fingerprint_;
+
+ encryptedChat() = default;
+
+ encryptedChat(int32 id_, int64 access_hash_, int32 date_, int32 admin_id_, int32 participant_id_,
+ BufferSlice &&g_a_or_b_, int64 key_fingerprint_)
+ : id_(id_)
+ , access_hash_(access_hash_)
+ , date_(date_)
+ , admin_id_(admin_id_)
+ , participant_id_(participant_id_)
+ , g_a_or_b_(std::move(g_a_or_b_))
+ , key_fingerprint_(key_fingerprint_) {
+ }
+
+ static const int32 ID = -94974410;
+ int32 get_id() const {
+ return ID;
+ }
+
+ void store(TlStorerCalcLength &s) const {
+ (void)sizeof(s);
+ TlStoreBinary::store(id_, s);
+ TlStoreBinary::store(access_hash_, s);
+ TlStoreBinary::store(date_, s);
+ TlStoreBinary::store(admin_id_, s);
+ TlStoreBinary::store(participant_id_, s);
+ TlStoreString::store(g_a_or_b_, s);
+ TlStoreBinary::store(key_fingerprint_, s);
+ }
+
+ void store(TlStorerUnsafe &s) const {
+ (void)sizeof(s);
+ TlStoreBinary::store(id_, s);
+ TlStoreBinary::store(access_hash_, s);
+ TlStoreBinary::store(date_, s);
+ TlStoreBinary::store(admin_id_, s);
+ TlStoreBinary::store(participant_id_, s);
+ TlStoreString::store(g_a_or_b_, s);
+ TlStoreBinary::store(key_fingerprint_, s);
+ }
+};
+
+class messages_sentEncryptedMessage final {
+ public:
+ int32 date_;
+
+ messages_sentEncryptedMessage() = default;
+
+ explicit messages_sentEncryptedMessage(int32 date_) : date_(date_) {
+ }
+
+ static const int32 ID = 1443858741;
+ int32 get_id() const {
+ return ID;
+ }
+
+ void store(TlStorerCalcLength &s) const {
+ (void)sizeof(s);
+ TlStoreBinary::store(date_, s);
+ }
+
+ void store(TlStorerUnsafe &s) const {
+ (void)sizeof(s);
+ TlStoreBinary::store(date_, s);
+ }
+};
+
+} // namespace my_api
+
+namespace td {
+static int32 g = 3;
+static string prime_base64 =
+ "xxyuucaxyQSObFIvcPE_c5gNQCOOPiHBSTTQN1Y9kw9IGYoKp8FAWCKUk9IlMPTb-jNvbgrJJROVQ67UTM58NyD9UfaUWHBaxozU_mtrE6vcl0ZRKW"
+ "kyhFTxj6-MWV9kJHf-lrsqlB1bzR1KyMxJiAcI-ps3jjxPOpBgvuZ8-aSkppWBEFGQfhYnU7VrD2tBDbp02KhLKhSzFE4O8ShHVP0X7ZUNWWW0ud1G"
+ "WC2xF40WnGvEZbDW_5yjko_vW5rk5Bj8Feg-vqD4f6n_Xu1wBQ3tKEn0e_lZ2VaFDOkphR8NgRX2NbEF7i5OFdBLJFS_b0-t8DSxBAMRnNjjuS_MW"
+ "w";
+
+class FakeDhCallback : public DhCallback {
+ public:
+ int is_good_prime(Slice prime_str) const override {
+ auto it = cache.find(prime_str.str());
+ if (it == cache.end()) {
+ return -1;
+ }
+ return it->second;
+ }
+ void add_good_prime(Slice prime_str) const override {
+ cache[prime_str.str()] = 1;
+ }
+ void add_bad_prime(Slice prime_str) const override {
+ cache[prime_str.str()] = 0;
+ }
+ mutable std::map<string, int> cache;
+};
+
+class FakeBinlog
+ : public BinlogInterface
+ , public Actor {
+ public:
+ FakeBinlog() {
+ register_actor("FakeBinlog", this).release();
+ }
+ void force_sync(Promise<> promise) override {
+ if (pending_events_.empty()) {
+ pending_events_.emplace_back();
+ }
+ pending_events_.back().promises_.push_back(std::move(promise));
+ pending_events_.back().sync_flag = true;
+ request_sync();
+ }
+ void request_sync() {
+ if (!has_request_sync) {
+ has_request_sync = true;
+ if (Random::fast(0, 4) == 0) {
+ set_timeout_in(Random::fast(0, 99) / 100.0 * 0.005 + 0.001);
+ } else {
+ yield();
+ }
+ }
+ }
+ void force_flush() override {
+ }
+
+ uint64 next_id() override {
+ auto res = last_id_;
+ last_id_++;
+ return res;
+ }
+ uint64 next_id(int32 shift) override {
+ auto res = last_id_;
+ last_id_ += shift;
+ return res;
+ }
+ template <class F>
+ void for_each(const F &f) {
+ events_processor_.for_each([&](auto &x) {
+ LOG(INFO) << "REPLAY: " << x.id_;
+ f(x);
+ });
+ }
+
+ void restart() {
+ has_request_sync = false;
+ cancel_timeout();
+ for (auto &pending : pending_events_) {
+ auto &event = pending.event;
+ if (!event.empty()) {
+ // LOG(ERROR) << "FORGET EVENT: " << event.id_ << " " << event;
+ }
+ }
+ pending_events_.clear();
+ }
+
+ void change_key(DbKey key, Promise<> promise) override {
+ }
+
+ protected:
+ void close_impl(Promise<> promise) override {
+ }
+ void close_and_destroy_impl(Promise<> promise) override {
+ }
+ void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise) override {
+ auto event = BinlogEvent(std::move(raw_event));
+ LOG(INFO) << "ADD EVENT: " << event.id_ << " " << event;
+ pending_events_.emplace_back();
+ pending_events_.back().event = std::move(event);
+ pending_events_.back().promises_.push_back(std::move(promise));
+ }
+ void do_force_sync() {
+ if (pending_events_.empty()) {
+ return;
+ }
+ cancel_timeout();
+ has_request_sync = false;
+ auto pos = static_cast<size_t>(Random::fast_uint64() % pending_events_.size());
+ // pos = pending_events_.size() - 1;
+ std::vector<Promise<>> promises;
+ for (size_t i = 0; i <= pos; i++) {
+ auto &pending = pending_events_[i];
+ auto event = std::move(pending.event);
+ if (!event.empty()) {
+ LOG(INFO) << "SAVE EVENT: " << event.id_ << " " << event;
+ events_processor_.add_event(std::move(event));
+ }
+ append(promises, std::move(pending.promises_));
+ }
+ pending_events_.erase(pending_events_.begin(), pending_events_.begin() + pos + 1);
+ for (auto &promise : promises) {
+ promise.set_value(Unit());
+ }
+
+ for (auto &event : pending_events_) {
+ if (event.sync_flag) {
+ request_sync();
+ break;
+ }
+ }
+ }
+ void timeout_expired() override {
+ do_force_sync();
+ }
+ void wakeup() override {
+ if (has_request_sync) {
+ do_force_sync();
+ }
+ }
+ bool has_request_sync = false;
+ uint64 last_id_ = 1;
+ detail::BinlogEventsProcessor events_processor_;
+
+ struct PendingEvent {
+ BinlogEvent event;
+ bool sync_flag = false;
+ std::vector<Promise<>> promises_;
+ };
+
+ std::vector<PendingEvent> pending_events_;
+};
+
+using FakeKeyValue = BinlogKeyValue<BinlogInterface>;
+class OldFakeKeyValue : public KeyValueSyncInterface {
+ SeqNo set(string key, string value) override {
+ kv_[key] = value;
+ return 0;
+ }
+
+ SeqNo erase(const string &key) override {
+ kv_.erase(key);
+ return 0;
+ }
+
+ bool isset(const string &key) override {
+ return kv_.count(key) > 0;
+ }
+
+ string get(const string &key) override {
+ auto it = kv_.find(key);
+ if (it != kv_.end()) {
+ return it->second;
+ }
+ return string();
+ }
+
+ private:
+ std::map<string, string> kv_;
+};
+
+class Master;
+class FakeSecretChatContext : public SecretChatActor::Context {
+ public:
+ FakeSecretChatContext(std::shared_ptr<BinlogInterface> binlog, std::shared_ptr<KeyValueSyncInterface> key_value,
+ std::shared_ptr<bool> close_flag, ActorShared<Master> master)
+ : binlog_(std::move(binlog))
+ , key_value_(std::move(key_value))
+ , close_flag_(std::move(close_flag))
+ , master_(std::move(master)) {
+ secret_chat_db_ = std::make_unique<SecretChatDb>(key_value_, 1);
+ net_query_creator_.stop_check(); // :(
+ }
+ DhCallback *dh_callback() override {
+ return &fake_dh_callback_;
+ }
+ NetQueryCreator &net_query_creator() override {
+ return net_query_creator_;
+ }
+ int32 unix_time() override {
+ return static_cast<int32>(std::time(nullptr));
+ }
+ bool close_flag() override {
+ return *close_flag_;
+ }
+ BinlogInterface *binlog() override {
+ return binlog_.get();
+ }
+ SecretChatDb *secret_chat_db() override {
+ return secret_chat_db_.get();
+ }
+ std::shared_ptr<DhConfig> dh_config() override {
+ static auto config = [] {
+ DhConfig dh_config;
+ dh_config.version = 12;
+ dh_config.g = g;
+ dh_config.prime = base64url_decode(prime_base64).move_as_ok();
+ return std::make_shared<DhConfig>(dh_config);
+ }();
+
+ return config;
+ }
+ void set_dh_config(std::shared_ptr<DhConfig> dh_config) override {
+ // empty
+ }
+
+ bool get_config_option_boolean(const string &name) const override {
+ return false;
+ }
+
+ // We don't want to expose the whole NetQueryDispatcher, MessagesManager and ContactsManager.
+ // So it is more clear which parts of MessagesManager is really used. And it is much easier to create tests.
+ void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) override;
+
+ void on_update_secret_chat(int64 access_hash, UserId user_id, SecretChatState state, bool is_outbound, int32 ttl,
+ int32 date, string key_hash, int32 layer) override {
+ }
+
+ void on_inbound_message(UserId user_id, MessageId message_id, int32 date,
+ tl_object_ptr<telegram_api::encryptedFile> file,
+ tl_object_ptr<secret_api::decryptedMessage> message, Promise<>) override;
+
+ void on_send_message_error(int64 random_id, Status error, Promise<>) override;
+ void on_send_message_ack(int64 random_id) override;
+ void on_send_message_ok(int64 random_id, MessageId message_id, int32 date,
+ tl_object_ptr<telegram_api::EncryptedFile> file, Promise<>) override;
+ void on_delete_messages(std::vector<int64> random_id, Promise<>) override;
+ void on_flush_history(MessageId, Promise<>) override;
+ void on_read_message(int64, Promise<>) override;
+
+ void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id,
+ Promise<> promise) override {
+ }
+ void on_set_ttl(UserId user_id, MessageId message_id, int32 date, int32 ttl, int64 random_id,
+ Promise<> promise) override {
+ }
+
+ private:
+ FakeDhCallback fake_dh_callback_;
+ static NetQueryCreator net_query_creator_;
+ std::shared_ptr<BinlogInterface> binlog_;
+ std::shared_ptr<KeyValueSyncInterface> key_value_;
+ std::shared_ptr<bool> close_flag_;
+ ActorShared<Master> master_;
+
+ std::shared_ptr<SecretChatDb> secret_chat_db_;
+};
+NetQueryCreator FakeSecretChatContext::net_query_creator_;
+
+class Master : public Actor {
+ public:
+ explicit Master(Status *status) : status_(status) {
+ }
+ class SecretChatProxy : public Actor {
+ public:
+ SecretChatProxy(string name, ActorShared<Master> parent) : name_(std::move(name)) {
+ binlog_ = std::make_shared<FakeBinlog>();
+ key_value_ = std::make_shared<FakeKeyValue>();
+ key_value_->external_init_begin(LogEvent::HandlerType::BinlogPmcMagic);
+ key_value_->external_init_finish(binlog_);
+ close_flag_ = std::make_shared<bool>(false);
+ parent_ = parent.get();
+ parent_token_ = parent.token();
+ actor_ = create_actor<SecretChatActor>(
+ "SecretChat " + name_, 123,
+ std::make_unique<FakeSecretChatContext>(binlog_, key_value_, close_flag_, std::move(parent)), true);
+ on_binlog_replay_finish();
+ }
+
+ ActorOwn<SecretChatActor> actor_;
+
+ void add_inbound_message(int32 chat_id, BufferSlice data, uint64 crc) {
+ CHECK(crc64(data.as_slice()) == crc);
+ auto event = std::make_unique<logevent::InboundSecretMessage>();
+ event->qts = 0;
+ event->chat_id = chat_id;
+ event->date = 0;
+ event->encrypted_message = std::move(data);
+ event->qts_ack = PromiseCreator::lambda(
+ [actor_id = actor_id(this), chat_id, data = event->encrypted_message.copy(), crc](Result<> result) mutable {
+ if (result.is_ok()) {
+ LOG(INFO) << "FINISH add_inbound_message " << tag("crc", crc);
+ return;
+ }
+ LOG(INFO) << "RESEND add_inbound_message " << tag("crc", crc) << result.error();
+ send_closure(actor_id, &SecretChatProxy::add_inbound_message, chat_id, std::move(data), crc);
+ });
+
+ add_event(Event::delayed_closure(&SecretChatActor::add_inbound_message, std::move(event)));
+ }
+
+ void send_message(tl_object_ptr<secret_api::decryptedMessage> message) {
+ BufferSlice serialized_message(serialize(*message));
+ auto resend_promise = PromiseCreator::lambda(
+ [actor_id = actor_id(this), serialized_message = std::move(serialized_message)](Result<> result) mutable {
+ TlBufferParser parser(&serialized_message);
+ auto message = secret_api::decryptedMessage::fetch(parser);
+ if (result.is_ok()) {
+ LOG(INFO) << "FINISH send_message " << tag("message", to_string(message));
+ return;
+ }
+ LOG(INFO) << "RESEND send_message " << tag("message", to_string(message)) << result.error();
+ CHECK(serialize(*message) == serialized_message.as_slice());
+ send_closure(actor_id, &SecretChatProxy::send_message, std::move(message));
+ });
+ auto sync_promise = PromiseCreator::lambda([actor_id = actor_id(this), generation = this->binlog_generation_,
+ resend_promise = std::move(resend_promise)](Result<> result) mutable {
+ if (result.is_error()) {
+ resend_promise.set_error(result.move_as_error());
+ return;
+ }
+ send_closure(actor_id, &SecretChatProxy::sync_binlog, generation, std::move(resend_promise));
+ });
+
+ add_event(
+ Event::delayed_closure(&SecretChatActor::send_message, std::move(message), nullptr, std::move(sync_promise)));
+ }
+ int32 binlog_generation_ = 0;
+ void sync_binlog(int32 binlog_generation, Promise<> promise) {
+ if (binlog_generation != binlog_generation_) {
+ return promise.set_error(Status::Error("binlog generation mismatch"));
+ }
+ binlog_->force_sync(std::move(promise));
+ }
+ void on_closed() {
+ LOG(INFO) << "CLOSED";
+ ready_ = false;
+ *close_flag_ = false;
+
+ key_value_ = std::make_shared<FakeKeyValue>();
+ key_value_->external_init_begin(LogEvent::HandlerType::BinlogPmcMagic);
+
+ std::vector<BinlogEvent> events;
+ binlog_generation_++;
+ binlog_->restart();
+ binlog_->for_each([&](const BinlogEvent &event) {
+ if (event.type_ == LogEvent::HandlerType::BinlogPmcMagic) {
+ key_value_->external_init_handle(event);
+ } else {
+ events.push_back(event.clone());
+ }
+ });
+
+ key_value_->external_init_finish(binlog_);
+
+ actor_ = create_actor<SecretChatActor>(
+ "SecretChat " + name_, 123,
+ std::make_unique<FakeSecretChatContext>(binlog_, key_value_, close_flag_,
+ ActorShared<Master>(parent_, parent_token_)),
+ true);
+
+ for (auto &event : events) {
+ CHECK(event.type_ == LogEvent::HandlerType::SecretChats);
+ auto r_message = logevent::SecretChatEvent::from_buffer_slice(event.data_as_buffer_slice());
+ LOG_IF(FATAL, r_message.is_error()) << "Failed to deserialize event: " << r_message.error();
+ auto message = r_message.move_as_ok();
+ message->set_logevent_id(event.id_);
+ LOG(INFO) << "Process binlog event " << *message;
+ switch (message->get_type()) {
+ case logevent::SecretChatEvent::Type::InboundSecretMessage:
+ send_closure_later(actor_, &SecretChatActor::replay_inbound_message,
+ std::unique_ptr<logevent::InboundSecretMessage>(
+ static_cast<logevent::InboundSecretMessage *>(message.release())));
+ break;
+ case logevent::SecretChatEvent::Type::OutboundSecretMessage:
+ send_closure_later(actor_, &SecretChatActor::replay_outbound_message,
+ std::unique_ptr<logevent::OutboundSecretMessage>(
+ static_cast<logevent::OutboundSecretMessage *>(message.release())));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ };
+ start_test();
+ on_binlog_replay_finish();
+ }
+ void on_binlog_replay_finish() {
+ ready_ = true;
+ LOG(INFO) << "on_binlog_replay_finish!";
+ send_closure(actor_, &SecretChatActor::binlog_replay_finish);
+ for (auto &event : pending_events_) {
+ send_event(actor_, std::move(event));
+ }
+ pending_events_.clear();
+ }
+ void start_test() {
+ set_timeout_in(Random::fast(50, 99) * 0.3 / 50);
+ events_cnt_ = 0;
+ }
+
+ private:
+ string name_;
+
+ ActorId<Master> parent_;
+ uint64 parent_token_;
+ std::shared_ptr<FakeBinlog> binlog_;
+ std::shared_ptr<FakeKeyValue> key_value_;
+ std::shared_ptr<bool> close_flag_;
+ int events_cnt_ = 0;
+
+ std::vector<Event> pending_events_;
+ bool ready_ = false;
+
+ bool is_active() {
+ return !actor_.empty() && ready_;
+ }
+ void add_event(Event event) {
+ events_cnt_++;
+ if (is_active()) {
+ LOG(INFO) << "EMIT";
+ send_event(actor_, std::move(event));
+ } else {
+ LOG(INFO) << "DELAY";
+ pending_events_.push_back(std::move(event));
+ }
+ }
+
+ int32 bad_cnt_ = 0;
+ void timeout_expired() override {
+ LOG(INFO) << "TIMEOUT EXPIRED";
+ if (events_cnt_ < 4) {
+ bad_cnt_++;
+ CHECK(bad_cnt_ < 10);
+ } else {
+ bad_cnt_ = 0;
+ }
+ *close_flag_ = true;
+ actor_.reset();
+ }
+ };
+
+ auto &get_by_id(uint64 id) {
+ if (id == 1) {
+ return alice_;
+ } else {
+ return bob_;
+ }
+ }
+ auto &from() {
+ return get_by_id(get_link_token());
+ }
+ auto &to() {
+ return get_by_id(3 - get_link_token());
+ }
+ void start_up() override {
+ set_context(std::make_shared<Global>());
+ alice_ = create_actor<SecretChatProxy>("SecretChatProxy alice", "alice", actor_shared(this, 1));
+ bob_ = create_actor<SecretChatProxy>("SecretChatProxy bob", "bob", actor_shared(this, 2));
+ send_closure(alice_->get_actor_unsafe()->actor_, &SecretChatActor::create_chat, 2, 0, 123,
+ PromiseCreator::lambda([actor_id = actor_id(this)](Result<SecretChatId> res) {
+ send_closure(actor_id, &Master::got_secret_chat_id, std::move(res), 0);
+ }));
+ }
+ void got_secret_chat_id(Result<SecretChatId> res, int) { // second parameter is needed to workaround clang bug
+ CHECK(res.is_ok());
+ auto id = res.move_as_ok();
+ LOG(INFO) << "SecretChatId = " << id;
+ }
+ bool can_fail(NetQueryPtr &query) {
+ static int cnt = 20;
+ if (cnt > 0) {
+ cnt--;
+ return false;
+ }
+ if (query->tl_constructor() == telegram_api::messages_sendEncrypted::ID ||
+ query->tl_constructor() == telegram_api::messages_sendEncryptedFile::ID) {
+ return true;
+ }
+ return false;
+ }
+ void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) {
+ if (can_fail(query) && Random::fast(0, 1) == 0) {
+ LOG(INFO) << "Fail query " << query;
+ auto resend_promise =
+ PromiseCreator::lambda([id = actor_shared(this, get_link_token()), callback_actor = callback.get(),
+ callback_token = callback.token()](Result<NetQueryPtr> r_net_query) mutable {
+ if (r_net_query.is_error()) {
+ id.release();
+ return;
+ }
+ send_closure(std::move(id), &Master::send_net_query, r_net_query.move_as_ok(),
+ ActorShared<NetQueryCallback>(callback_actor, callback_token), true);
+ });
+ query->set_error(Status::Error(429, "Test error"));
+ send_closure(std::move(callback), &NetQueryCallback::on_result_resendable, std::move(query),
+ std::move(resend_promise));
+ return;
+ } else {
+ LOG(INFO) << "Do not fail " << query;
+ }
+ auto query_slice = query->query().clone();
+ if (query->gzip_flag() == NetQuery::GzipFlag::On) {
+ query_slice = gzdecode(query_slice.as_slice());
+ }
+ TlBufferParser parser(&query_slice);
+ //auto object = telegram_api::Function::fetch(parser);
+ //LOG(INFO) << query_slice.size();
+ //parser.get_status().ensure();
+ my_api::downcast_call(parser, [&](auto &object) {
+ this->process_net_query(std::move(object), std::move(query), std::move(callback));
+ });
+ }
+ template <class T>
+ void process_net_query(T &&object, NetQueryPtr query, ActorShared<NetQueryCallback> callback) {
+ LOG(FATAL) << "Unsupported query: " << to_string(object);
+ }
+ void process_net_query(my_api::messages_getDhConfig &&get_dh_config, NetQueryPtr net_query,
+ ActorShared<NetQueryCallback> callback) {
+ //LOG(INFO) << "Got query " << to_string(get_dh_config);
+ my_api::messages_dhConfig config;
+ config.p_ = BufferSlice(base64url_decode(prime_base64).move_as_ok());
+ config.g_ = g;
+ config.version_ = 12;
+ auto storer = TLObjectStorer<my_api::messages_dhConfig>(config);
+ BufferSlice answer(storer.size());
+ storer.store(answer.as_slice().ubegin());
+ net_query->set_ok(std::move(answer));
+ send_closure(std::move(callback), &NetQueryCallback::on_result, std::move(net_query));
+ }
+ void process_net_query(my_api::messages_requestEncryption &&request_encryption, NetQueryPtr net_query,
+ ActorShared<NetQueryCallback> callback) {
+ CHECK(get_link_token() == 1);
+ send_closure(alice_->get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
+ make_tl_object<telegram_api::encryptedChatWaiting>(123, 321, 0, 1, 2));
+ send_closure(
+ bob_->get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
+ make_tl_object<telegram_api::encryptedChatRequested>(123, 321, 0, 1, 2, request_encryption.g_a_.clone()));
+ net_query->clear();
+ }
+ void process_net_query(my_api::messages_acceptEncryption &&request_encryption, NetQueryPtr net_query,
+ ActorShared<NetQueryCallback> callback) {
+ CHECK(get_link_token() == 2);
+ send_closure(alice_->get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
+ make_tl_object<telegram_api::encryptedChat>(123, 321, 0, 1, 2, request_encryption.g_b_.clone(),
+ request_encryption.key_fingerprint_));
+
+ my_api::encryptedChat encrypted_chat(123, 321, 0, 1, 2, BufferSlice(), request_encryption.key_fingerprint_);
+ auto storer = TLObjectStorer<my_api::encryptedChat>(encrypted_chat);
+ BufferSlice answer(storer.size());
+ storer.store(answer.as_slice().ubegin());
+ net_query->set_ok(std::move(answer));
+ send_closure(std::move(callback), &NetQueryCallback::on_result, std::move(net_query));
+ send_closure(alice_, &SecretChatProxy::start_test);
+ send_closure(bob_, &SecretChatProxy::start_test);
+ send_ping(1, 5000);
+ set_timeout_in(1);
+ }
+ void timeout_expired() override {
+ send_message(1, "oppa");
+ send_message(2, "appo");
+ set_timeout_in(1);
+ }
+ void send_ping(int id, int cnt) {
+ if (cnt % 200 == 0) {
+ LOG(ERROR) << "send ping " << tag("id", id) << tag("cnt", cnt);
+ } else {
+ LOG(INFO) << "send ping " << tag("id", id) << tag("cnt", cnt);
+ }
+ string text = PSTRING() << "PING: " << cnt;
+ send_message(id, std::move(text));
+ }
+ void send_message(int id, string text) {
+ auto random_id = Random::secure_int64();
+ LOG(INFO) << "send message: " << tag("id", id) << tag("text", text) << tag("random_id", random_id);
+ sent_messages_[random_id] = Message{id, text};
+ send_closure(get_by_id(id), &SecretChatProxy::send_message,
+ secret_api::make_object<secret_api::decryptedMessage>(0, random_id, 0, text, Auto(), Auto(), Auto(),
+ Auto(), 0));
+ }
+ void process_net_query(my_api::messages_sendEncryptedService &&message, NetQueryPtr net_query,
+ ActorShared<NetQueryCallback> callback) {
+ process_net_query_send_enrypted(std::move(message.data_), std::move(net_query), std::move(callback));
+ }
+ void process_net_query(my_api::messages_sendEncrypted &&message, NetQueryPtr net_query,
+ ActorShared<NetQueryCallback> callback) {
+ process_net_query_send_enrypted(std::move(message.data_), std::move(net_query), std::move(callback));
+ }
+ void process_net_query_send_enrypted(BufferSlice data, NetQueryPtr net_query,
+ ActorShared<NetQueryCallback> callback) {
+ my_api::messages_sentEncryptedMessage sent_message;
+ sent_message.date_ = 0;
+ auto storer = TLObjectStorer<my_api::messages_sentEncryptedMessage>(sent_message);
+ BufferSlice answer(storer.size());
+ storer.store(answer.as_slice().ubegin());
+ net_query->set_ok(std::move(answer));
+ send_closure(std::move(callback), &NetQueryCallback::on_result, std::move(net_query));
+
+ // We can't loose updates yet :(
+ auto crc = crc64(data.as_slice());
+ LOG(INFO) << "send SecretChatProxy::add_inbound_message" << tag("crc", crc);
+ send_closure(to(), &SecretChatProxy::add_inbound_message, narrow_cast<int32>(3 - get_link_token()), std::move(data),
+ crc);
+ }
+
+ int32 last_ping_ = std::numeric_limits<int32>::max();
+ void on_inbound_message(string message, Promise<> promise) {
+ promise.set_value(Unit());
+ LOG(INFO) << "GOT INBOUND MESSAGE: " << message << " " << get_link_token();
+ int32 cnt;
+ int x = std::sscanf(message.c_str(), "PING: %d", &cnt);
+ if (x != 1) {
+ return;
+ }
+ if (cnt == 0) {
+ Scheduler::instance()->finish();
+ *status_ = Status::OK();
+ return;
+ }
+ if (cnt >= last_ping_) {
+ return;
+ }
+ last_ping_ = cnt;
+ send_ping(narrow_cast<int32>(get_link_token()), cnt - 1);
+ }
+ void on_send_message_error(int64 random_id, Status error, Promise<> promise) {
+ promise.set_value(Unit());
+ LOG(INFO) << "on_send_message_error: " << tag("random_id", random_id) << error;
+ auto it = sent_messages_.find(random_id);
+ if (it == sent_messages_.end()) {
+ LOG(INFO) << "TODO: try fix errors about message after it is sent";
+ return;
+ }
+ CHECK(it != sent_messages_.end());
+ auto message = it->second;
+ // sent_messages_.erase(it);
+ send_message(message.id, message.text);
+ }
+ void on_send_message_ok(int64 random_id, Promise<> promise) {
+ promise.set_value(Unit());
+ LOG(INFO) << "on_send_message_ok: " << tag("random_id", random_id);
+ auto it = sent_messages_.find(random_id);
+ if (it == sent_messages_.end()) {
+ LOG(INFO) << "TODO: try fix errors about message after it is sent";
+ return;
+ }
+ CHECK(it != sent_messages_.end());
+ // sent_messages_.erase(it);
+ }
+
+ private:
+ Status *status_;
+ ActorOwn<SecretChatProxy> alice_;
+ ActorOwn<SecretChatProxy> bob_;
+ struct Message {
+ int32 id;
+ string text;
+ };
+ std::map<int64, Message> sent_messages_;
+
+ void hangup_shared() override {
+ LOG(INFO) << "GOT HANGUP: " << get_link_token();
+ send_closure(from(), &SecretChatProxy::on_closed);
+ }
+};
+
+void FakeSecretChatContext::send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) {
+ send_closure(master_, &Master::send_net_query, std::move(query), std::move(callback), ordered);
+}
+void FakeSecretChatContext::on_inbound_message(UserId user_id, MessageId message_id, int32 date,
+ tl_object_ptr<telegram_api::encryptedFile> file,
+ tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise) {
+ send_closure(master_, &Master::on_inbound_message, message->message_, std::move(promise));
+}
+void FakeSecretChatContext::on_send_message_error(int64 random_id, Status error, Promise<> promise) {
+ send_closure(master_, &Master::on_send_message_error, random_id, std::move(error), std::move(promise));
+}
+void FakeSecretChatContext::on_send_message_ack(int64 random_id) {
+}
+void FakeSecretChatContext::on_send_message_ok(int64 random_id, MessageId message_id, int32 date,
+ tl_object_ptr<telegram_api::EncryptedFile> file, Promise<> promise) {
+ send_closure(master_, &Master::on_send_message_ok, random_id, std::move(promise));
+}
+void FakeSecretChatContext::on_delete_messages(std::vector<int64> random_id, Promise<> promise) {
+ promise.set_value(Unit());
+}
+void FakeSecretChatContext::on_flush_history(MessageId, Promise<> promise) {
+ promise.set_error(Status::Error("unsupported"));
+}
+void FakeSecretChatContext::on_read_message(int64, Promise<> promise) {
+ promise.set_error(Status::Error("unsupported"));
+}
+
+TEST(Secret, go) {
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
+ ConcurrentScheduler sched;
+ int threads_n = 0;
+ sched.init(threads_n);
+
+ Status result;
+ sched.create_actor_unsafe<Master>(0, "HandshakeTestActor", &result).release();
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty;
+ }
+ sched.finish();
+
+ if (result.is_error()) {
+ LOG(ERROR) << result;
+ }
+ ASSERT_TRUE(result.is_ok());
+}
+
+} // namespace td
diff --git a/protocols/Telegram/tdlib/td/test/string_cleaning.cpp b/protocols/Telegram/tdlib/td/test/string_cleaning.cpp
new file mode 100644
index 0000000000..6fbd8150a4
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/string_cleaning.cpp
@@ -0,0 +1,107 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "td/telegram/misc.h"
+
+#include "td/utils/Slice.h"
+#include "td/utils/tests.h"
+
+REGISTER_TESTS(string_cleaning);
+
+using namespace td;
+
+TEST(StringCleaning, clean_name) {
+ ASSERT_EQ("@mention", clean_name("@mention", 1000000));
+ ASSERT_EQ("@mention", clean_name(" @mention ", 1000000));
+ ASSERT_EQ("@MENTION", clean_name("@MENTION", 1000000));
+ ASSERT_EQ("ЛШТШФУМ", clean_name("ЛШТШФУМ", 1000000));
+ ASSERT_EQ("....", clean_name("....", 1000000));
+ ASSERT_EQ(". ASD ..", clean_name(". ASD ..", 1000000));
+ ASSERT_EQ(". ASD", clean_name(". ASD ..", 10));
+ ASSERT_EQ(". ASD", clean_name(".\n\n\nASD\n\n\n..", 10));
+ ASSERT_EQ("", clean_name("\n\n\n\n\n\n", 1000000));
+ ASSERT_EQ("", clean_name("\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0\n\n\n\n\n\n \n\xC2\xA0 \xC2\xA0 \n", 100000));
+ ASSERT_EQ("abc", clean_name("\xC2\xA0\xC2\xA0"
+ "abc\xC2\xA0\xC2\xA0\xC2\xA0\xC2\xA0",
+ 1000000));
+};
+
+TEST(StringCleaning, clean_username) {
+ ASSERT_EQ("@mention", clean_username("@mention"));
+ ASSERT_EQ("@mention", clean_username(" @mention "));
+ ASSERT_EQ("@mention", clean_username("@MENTION"));
+ ASSERT_EQ("ЛШТШФУМ", clean_username("ЛШТШФУМ"));
+ ASSERT_EQ("", clean_username("...."));
+ ASSERT_EQ("asd", clean_username(". ASD .."));
+};
+
+static void check_clean_input_string(string str, string expected, bool expected_result) {
+ auto result = clean_input_string(str);
+ ASSERT_EQ(expected_result, result);
+ if (result) {
+ ASSERT_EQ(expected, str);
+ }
+}
+
+TEST(StringCleaning, clean_input_string) {
+ check_clean_input_string("/abc", "/abc", true);
+ check_clean_input_string(string(50000, 'a'), string(34996, 'a'), true);
+ check_clean_input_string("\xff", "", false);
+ check_clean_input_string("\xc0\x80", "", false);
+ check_clean_input_string("\xd0", "", false);
+ check_clean_input_string("\xe0\xaf", "", false);
+ check_clean_input_string("\xf0\xa6", "", false);
+ check_clean_input_string("\xf0\xa6\x88", "", false);
+ check_clean_input_string("\xf4\x8f\xbf\xbf", "\xf4\x8f\xbf\xbf", true);
+ check_clean_input_string("\xf4\x8f\xbf\xc0", "", false);
+ check_clean_input_string("\r\r\r\r\r\r\r", "", true);
+ check_clean_input_string("\r\n\r\n\r\n\r\n\r\n\r\n\r", "\n\n\n\n\n\n", true);
+ check_clean_input_string(Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
+ .str(),
+ " \x0a \x21", true);
+ check_clean_input_string(
+ "\xe2\x80\xa7\xe2\x80\xa8\xe2\x80\xa9\xe2\x80\xaa\xe2\x80\xab\xe2\x80\xac\xe2\x80\xad\xe2\x80\xae\xe2\x80\xaf",
+ "\xe2\x80\xa7\xe2\x80\xaf", true);
+ check_clean_input_string("\xcc\xb3\xcc\xbf\xcc\x8a", "", true);
+}
+
+static void check_strip_empty_characters(string str, size_t max_length, string expected) {
+ ASSERT_EQ(expected, strip_empty_characters(str, max_length));
+}
+
+TEST(StringCleaning, strip_empty_characters) {
+ check_strip_empty_characters("/abc", 4, "/abc");
+ check_strip_empty_characters("/abc", 3, "/ab");
+ check_strip_empty_characters("/abc", 0, "");
+ check_strip_empty_characters("/abc", 10000000, "/abc");
+ string spaces =
+ u8"\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u202F\u205F\u3000\uFEFF"
+ u8"\uFFFC\uFFFC";
+ string spaces_replace = " ";
+ string empty = "\xE2\x80\x8C\xE2\x80\x8D\xE2\x80\xAE\xC2\xA0\xC2\xA0";
+
+ check_strip_empty_characters(spaces, 1000000, "");
+ check_strip_empty_characters(empty, 1000000, "");
+ check_strip_empty_characters(empty + "a", 1000000, empty + "a");
+ check_strip_empty_characters(spaces + empty + spaces + "abc" + spaces, 1000000, empty + spaces_replace + "abc");
+ check_strip_empty_characters(spaces + spaces + empty + spaces + spaces + empty + empty, 1000000,
+ empty + spaces_replace + spaces_replace + empty + empty);
+ check_strip_empty_characters("\r\r\r\r\r\r\r", 1000000, "");
+ check_strip_empty_characters("\r\n\r\n\r\n\r\n\r\n\r\n\r", 1000000, "");
+ check_strip_empty_characters(Slice(" \t\r\n\0\va\v\0\n\r\t ").str(), 1000000, "a");
+ check_strip_empty_characters(
+ Slice("\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21")
+ .str(),
+ 1000000,
+ "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14"
+ "\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21");
+ check_strip_empty_characters("\xcc\xb3\xcc\xbf\xcc\x8a", 2, "\xcc\xb3\xcc\xbf");
+ check_strip_empty_characters(
+ "\xe2\x80\xa7\xe2\x80\xa8\xe2\x80\xa9\xe2\x80\xaa\xe2\x80\xab\xe2\x80\xac\xe2\x80\xad\xe2\x80\xae", 3,
+ "\xe2\x80\xa7\xe2\x80\xa8\xe2\x80\xa9");
+}
diff --git a/protocols/Telegram/tdlib/td/test/tdclient.cpp b/protocols/Telegram/tdlib/td/test/tdclient.cpp
new file mode 100644
index 0000000000..7cef0fcbc0
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/tdclient.cpp
@@ -0,0 +1,837 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "data.h"
+
+#include "td/actor/PromiseFuture.h"
+
+#include "td/telegram/ClientActor.h"
+
+#include "td/telegram/td_api.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/BufferedFd.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/format.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/port/FileFd.h"
+#include "td/utils/port/path.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/tests.h"
+
+#include <cstdio>
+#include <functional>
+#include <map>
+#include <memory>
+#include <utility>
+
+REGISTER_TESTS(tdclient);
+
+namespace td {
+
+template <class T>
+static void check_td_error(T &result) {
+ CHECK(result->get_id() != td_api::error::ID) << to_string(result);
+}
+
+static void rmrf(CSlice path) {
+ td::walk_path(path, [](CSlice path, bool is_dir) {
+ if (is_dir) {
+ td::rmdir(path).ignore();
+ } else {
+ td::unlink(path).ignore();
+ }
+ });
+}
+
+class TestClient : public Actor {
+ public:
+ explicit TestClient(string name) : name_(std::move(name)) {
+ }
+ struct Update {
+ uint64 id;
+ tl_object_ptr<td_api::Object> object;
+ Update(uint64 id, tl_object_ptr<td_api::Object> object) : id(id), object(std::move(object)) {
+ }
+ };
+ class Listener {
+ public:
+ Listener() = default;
+ Listener(const Listener &) = delete;
+ Listener &operator=(const Listener &) = delete;
+ Listener(Listener &&) = delete;
+ Listener &operator=(Listener &&) = delete;
+ virtual ~Listener() = default;
+ virtual void start_listen(TestClient *client) {
+ }
+ virtual void stop_listen() {
+ }
+ virtual void on_update(std::shared_ptr<Update> update) = 0;
+ };
+ void close(Promise<> close_promise) {
+ close_promise_ = std::move(close_promise);
+ td_.reset();
+ }
+
+ unique_ptr<TdCallback> make_td_callback() {
+ class TdCallbackImpl : public TdCallback {
+ public:
+ explicit TdCallbackImpl(ActorId<TestClient> client) : client_(client) {
+ }
+ void on_result(uint64 id, tl_object_ptr<td_api::Object> result) override {
+ send_closure(client_, &TestClient::on_result, id, std::move(result));
+ }
+ void on_error(uint64 id, tl_object_ptr<td_api::error> error) override {
+ send_closure(client_, &TestClient::on_error, id, std::move(error));
+ }
+ void on_closed() override {
+ send_closure(client_, &TestClient::on_closed);
+ }
+
+ private:
+ ActorId<TestClient> client_;
+ };
+ return make_unique<TdCallbackImpl>(actor_id(this));
+ }
+
+ void add_listener(std::unique_ptr<Listener> listener) {
+ auto *ptr = listener.get();
+ listeners_.push_back(std::move(listener));
+ ptr->start_listen(this);
+ }
+ void remove_listener(Listener *listener) {
+ pending_remove_.push_back(listener);
+ }
+ void do_pending_remove_listeners() {
+ for (auto listener : pending_remove_) {
+ do_remove_listener(listener);
+ }
+ pending_remove_.clear();
+ }
+ void do_remove_listener(Listener *listener) {
+ for (size_t i = 0; i < listeners_.size(); i++) {
+ if (listeners_[i].get() == listener) {
+ listener->stop_listen();
+ listeners_.erase(listeners_.begin() + i);
+ break;
+ }
+ }
+ }
+
+ void on_result(uint64 id, tl_object_ptr<td_api::Object> result) {
+ on_update(std::make_shared<Update>(id, std::move(result)));
+ }
+ void on_error(uint64 id, tl_object_ptr<td_api::error> error) {
+ on_update(std::make_shared<Update>(id, std::move(error)));
+ }
+ void on_update(std::shared_ptr<Update> update) {
+ for (auto &listener : listeners_) {
+ listener->on_update(update);
+ }
+ do_pending_remove_listeners();
+ }
+
+ void on_closed() {
+ stop();
+ }
+
+ void start_up() override {
+ rmrf(name_);
+ set_context(std::make_shared<td::ActorContext>());
+ set_tag(name_);
+ LOG(INFO) << "START UP!";
+
+ td_ = create_actor<ClientActor>("Td-proxy", make_td_callback());
+ }
+
+ ActorOwn<ClientActor> td_;
+
+ string name_;
+
+ private:
+ std::vector<std::unique_ptr<Listener>> listeners_;
+ std::vector<Listener *> pending_remove_;
+
+ Promise<> close_promise_;
+};
+
+class Task : public TestClient::Listener {
+ public:
+ void on_update(std::shared_ptr<TestClient::Update> update) override {
+ auto it = sent_queries_.find(update->id);
+ if (it != sent_queries_.end()) {
+ it->second(std::move(update->object));
+ sent_queries_.erase(it);
+ }
+ process_update(update);
+ }
+ void start_listen(TestClient *client) override {
+ client_ = client;
+ start_up();
+ }
+ virtual void process_update(std::shared_ptr<TestClient::Update> update) {
+ }
+
+ template <class F>
+ void send_query(tl_object_ptr<td_api::Function> function, F callback) {
+ auto id = current_query_id_++;
+ sent_queries_[id] = std::forward<F>(callback);
+ send_closure(client_->td_, &ClientActor::request, id, std::move(function));
+ }
+
+ protected:
+ std::map<uint64, std::function<void(tl_object_ptr<td_api::Object>)>> sent_queries_;
+ TestClient *client_;
+ uint64 current_query_id_ = 1;
+
+ virtual void start_up() {
+ }
+ void stop() {
+ client_->remove_listener(this);
+ }
+};
+
+class DoAuthentication : public Task {
+ public:
+ DoAuthentication(string name, string phone, string code, Promise<> promise)
+ : name_(std::move(name)), phone_(std::move(phone)), code_(std::move(code)), promise_(std::move(promise)) {
+ }
+ void start_up() override {
+ send_query(make_tl_object<td_api::getAuthorizationState>(),
+ [this](auto res) { this->process_authorization_state(std::move(res)); });
+ }
+ void process_authorization_state(tl_object_ptr<td_api::Object> authorization_state) {
+ start_flag_ = true;
+ tl_object_ptr<td_api::Function> function;
+ switch (authorization_state->get_id()) {
+ case td_api::authorizationStateWaitEncryptionKey::ID:
+ function = make_tl_object<td_api::checkDatabaseEncryptionKey>();
+ break;
+ case td_api::authorizationStateWaitPhoneNumber::ID:
+ function = make_tl_object<td_api::setAuthenticationPhoneNumber>(phone_, false, true);
+ break;
+ case td_api::authorizationStateWaitCode::ID:
+ function = make_tl_object<td_api::checkAuthenticationCode>(code_, name_, "");
+ break;
+ case td_api::authorizationStateWaitTdlibParameters::ID: {
+ auto parameters = td_api::make_object<td_api::tdlibParameters>();
+ parameters->use_test_dc_ = true;
+ parameters->database_directory_ = name_ + TD_DIR_SLASH;
+ parameters->use_message_database_ = true;
+ parameters->use_secret_chats_ = true;
+ parameters->api_id_ = 94575;
+ parameters->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
+ parameters->system_language_code_ = "en";
+ parameters->device_model_ = "Desktop";
+ parameters->system_version_ = "Unknown";
+ parameters->application_version_ = "tdclient-test";
+ parameters->ignore_file_names_ = false;
+ parameters->enable_storage_optimizer_ = true;
+ function = td_api::make_object<td_api::setTdlibParameters>(std::move(parameters));
+ break;
+ }
+ case td_api::authorizationStateReady::ID:
+ on_authorization_ready();
+ return;
+ default:
+ CHECK(false) << "Unexpected authorization state " << to_string(authorization_state);
+ }
+ send_query(std::move(function), [this](auto res) { CHECK(res->get_id() == td_api::ok::ID) << to_string(res); });
+ }
+ void on_authorization_ready() {
+ LOG(INFO) << "GOT AUTHORIZED";
+ stop();
+ }
+
+ private:
+ string name_;
+ string phone_;
+ string code_;
+ Promise<> promise_;
+ bool start_flag_{false};
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!start_flag_) {
+ return;
+ }
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td_api::updateAuthorizationState::ID) {
+ auto o = std::move(update->object);
+ process_authorization_state(std::move(static_cast<td_api::updateAuthorizationState &>(*o).authorization_state_));
+ }
+ }
+};
+
+class SetUsername : public Task {
+ public:
+ SetUsername(string username, Promise<> promise) : username_(std::move(username)), promise_(std::move(promise)) {
+ }
+
+ private:
+ string username_;
+ Promise<> promise_;
+ int32 self_id_;
+ string tag_;
+
+ void start_up() override {
+ send_query(make_tl_object<td_api::getMe>(), [this](auto res) { this->process_me_user(std::move(res)); });
+ }
+
+ void process_me_user(tl_object_ptr<td_api::Object> res) {
+ CHECK(res->get_id() == td_api::user::ID);
+ auto user = move_tl_object_as<td_api::user>(res);
+ self_id_ = user->id_;
+ if (user->username_ != username_) {
+ LOG(INFO) << "SET USERNAME: " << username_;
+ send_query(make_tl_object<td_api::setUsername>(username_), [this](auto res) {
+ CHECK(res->get_id() == td_api::ok::ID);
+ this->send_self_message();
+ });
+ } else {
+ send_self_message();
+ }
+ }
+ void send_self_message() {
+ tag_ = PSTRING() << format::as_hex(Random::secure_int64());
+
+ send_query(make_tl_object<td_api::createPrivateChat>(self_id_, false), [this](auto res) {
+ CHECK(res->get_id() == td_api::chat::ID);
+ auto chat = move_tl_object_as<td_api::chat>(res);
+ this->send_query(
+ make_tl_object<td_api::sendMessage>(
+ chat->id_, 0, false, false, nullptr,
+ make_tl_object<td_api::inputMessageText>(
+ make_tl_object<td_api::formattedText>(PSTRING() << tag_ << " INIT", Auto()), false, false)),
+ [](auto res) {});
+ });
+ }
+
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td_api::updateMessageSendSucceeded::ID) {
+ auto updateNewMessage = move_tl_object_as<td_api::updateMessageSendSucceeded>(update->object);
+ auto &message = updateNewMessage->message_;
+ if (message->content_->get_id() == td_api::messageText::ID) {
+ auto messageText = move_tl_object_as<td_api::messageText>(message->content_);
+ auto text = messageText->text_->text_;
+ if (text.substr(0, tag_.size()) == tag_) {
+ LOG(INFO) << "GOT SELF MESSAGE";
+ return stop();
+ }
+ }
+ }
+ }
+};
+
+class CheckTestA : public Task {
+ public:
+ CheckTestA(string tag, Promise<> promise) : tag_(std::move(tag)), promise_(std::move(promise)) {
+ }
+
+ private:
+ string tag_;
+ Promise<> promise_;
+ string previous_text_;
+ int cnt_ = 20;
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (update->object->get_id() == td_api::updateNewMessage::ID) {
+ auto updateNewMessage = move_tl_object_as<td_api::updateNewMessage>(update->object);
+ auto &message = updateNewMessage->message_;
+ if (message->content_->get_id() == td_api::messageText::ID) {
+ auto messageText = move_tl_object_as<td_api::messageText>(message->content_);
+ auto text = messageText->text_->text_;
+ if (text.substr(0, tag_.size()) == tag_) {
+ CHECK(text > previous_text_) << tag("now", text) << tag("previous", previous_text_);
+ previous_text_ = text;
+ cnt_--;
+ LOG(INFO) << "GOT " << tag("text", text) << tag("left", cnt_);
+ if (cnt_ == 0) {
+ return stop();
+ }
+ }
+ }
+ }
+ }
+};
+
+class TestA : public Task {
+ public:
+ TestA(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ }
+ void start_up() override {
+ send_query(make_tl_object<td_api::searchPublicChat>(username_), [this](auto res) {
+ CHECK(res->get_id() == td_api::chat::ID);
+ auto chat = move_tl_object_as<td_api::chat>(res);
+ for (int i = 0; i < 20; i++) {
+ this->send_query(make_tl_object<td_api::sendMessage>(
+ chat->id_, 0, false, false, nullptr,
+ make_tl_object<td_api::inputMessageText>(
+ make_tl_object<td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), Auto()),
+ false, false)),
+ [&](auto res) { this->stop(); });
+ }
+ });
+ }
+
+ private:
+ string tag_;
+ string username_;
+};
+
+class TestSecretChat : public Task {
+ public:
+ TestSecretChat(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ }
+
+ void start_up() override {
+ auto f = [this](auto res) {
+ CHECK(res->get_id() == td_api::chat::ID);
+ auto chat = move_tl_object_as<td_api::chat>(res);
+ this->chat_id_ = chat->id_;
+ this->secret_chat_id_ = move_tl_object_as<td_api::chatTypeSecret>(chat->type_)->secret_chat_id_;
+ };
+ send_query(make_tl_object<td_api::searchPublicChat>(username_), [this, f = std::move(f)](auto res) mutable {
+ CHECK(res->get_id() == td_api::chat::ID);
+ auto chat = move_tl_object_as<td_api::chat>(res);
+ CHECK(chat->type_->get_id() == td_api::chatTypePrivate::ID);
+ auto info = move_tl_object_as<td_api::chatTypePrivate>(chat->type_);
+ this->send_query(make_tl_object<td_api::createNewSecretChat>(info->user_id_), std::move(f));
+ });
+ }
+
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td_api::updateSecretChat::ID) {
+ auto update_secret_chat = move_tl_object_as<td_api::updateSecretChat>(update->object);
+ if (update_secret_chat->secret_chat_->id_ != secret_chat_id_ ||
+ update_secret_chat->secret_chat_->state_->get_id() != td_api::secretChatStateReady::ID) {
+ return;
+ }
+ LOG(INFO) << "SEND ENCRYPTED MESSAGES";
+ for (int i = 0; i < 20; i++) {
+ send_query(make_tl_object<td_api::sendMessage>(
+ chat_id_, 0, false, false, nullptr,
+ make_tl_object<td_api::inputMessageText>(
+ make_tl_object<td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), Auto()), false,
+ false)),
+ [](auto res) {});
+ }
+ }
+ }
+
+ private:
+ string tag_;
+ string username_;
+ int64 secret_chat_id_ = 0;
+ int64 chat_id_ = 0;
+};
+
+class TestFileGenerated : public Task {
+ public:
+ TestFileGenerated(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ }
+
+ void start_up() override {
+ }
+
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td_api::updateNewMessage::ID) {
+ auto updateNewMessage = move_tl_object_as<td_api::updateNewMessage>(update->object);
+ auto &message = updateNewMessage->message_;
+ chat_id_ = message->chat_id_;
+ if (message->content_->get_id() == td_api::messageText::ID) {
+ auto messageText = move_tl_object_as<td_api::messageText>(message->content_);
+ auto text = messageText->text_->text_;
+ if (text.substr(0, tag_.size()) == tag_) {
+ if (text.substr(tag_.size() + 1) == "ONE_FILE") {
+ return one_file();
+ }
+ }
+ }
+ } else if (update->object->get_id() == td_api::updateFileGenerationStart::ID) {
+ auto info = move_tl_object_as<td_api::updateFileGenerationStart>(update->object);
+ generate_file(info->generation_id_, info->original_path_, info->destination_path_, info->conversion_);
+ } else if (update->object->get_id() == td_api::updateFile::ID) {
+ auto file = move_tl_object_as<td_api::updateFile>(update->object);
+ LOG(INFO) << to_string(file);
+ }
+ }
+ void one_file() {
+ LOG(ERROR) << "Start ONE_FILE test";
+ string file_path = string("test_documents") + TD_DIR_SLASH + "a.txt";
+ mkpath(file_path).ensure();
+ auto raw_file =
+ FileFd::open(file_path, FileFd::Flags::Create | FileFd::Flags::Truncate | FileFd::Flags::Write).move_as_ok();
+ auto file = BufferedFd<FileFd>(std::move(raw_file));
+ for (int i = 1; i < 100000; i++) {
+ file.write(PSLICE() << i << "\n").ensure();
+ }
+ file.flush_write().ensure(); // important
+ file.close();
+ this->send_query(make_tl_object<td_api::sendMessage>(
+ chat_id_, 0, false, false, nullptr,
+ make_tl_object<td_api::inputMessageDocument>(
+ make_tl_object<td_api::inputFileGenerated>(file_path, "square", 0),
+ make_tl_object<td_api::inputThumbnail>(
+ make_tl_object<td_api::inputFileGenerated>(file_path, "thumbnail", 0), 0, 0),
+ make_tl_object<td_api::formattedText>(tag_, Auto()))),
+ [](auto res) { check_td_error(res); });
+
+ this->send_query(
+ make_tl_object<td_api::sendMessage>(chat_id_, 0, false, false, nullptr,
+ make_tl_object<td_api::inputMessageDocument>(
+ make_tl_object<td_api::inputFileGenerated>(file_path, "square", 0),
+ nullptr, make_tl_object<td_api::formattedText>(tag_, Auto()))),
+ [](auto res) { check_td_error(res); });
+ }
+
+ friend class GenerateFile;
+ class GenerateFile : public Actor {
+ public:
+ GenerateFile(Task *parent, int64 id, string original_path, string destination_path, string conversion)
+ : parent_(parent)
+ , id_(id)
+ , original_path_(std::move(original_path))
+ , destination_path_(std::move(destination_path))
+ , conversion_(std::move(conversion)) {
+ }
+
+ private:
+ Task *parent_;
+ int64 id_;
+ string original_path_;
+ string destination_path_;
+ string conversion_;
+
+ FILE *from = nullptr;
+ FILE *to = nullptr;
+
+ void start_up() override {
+ from = std::fopen(original_path_.c_str(), "rb");
+ CHECK(from);
+ to = std::fopen(destination_path_.c_str(), "wb");
+ CHECK(to);
+ yield();
+ }
+ void loop() override {
+ int cnt = 0;
+ while (true) {
+ uint32 x;
+ auto r = std::fscanf(from, "%u", &x);
+ if (r != 1) {
+ return stop();
+ }
+ std::fprintf(to, "%u\n", x * x);
+ if (++cnt >= 10000) {
+ break;
+ }
+ }
+ auto ready = std::ftell(to);
+ LOG(ERROR) << "READY: " << ready;
+ parent_->send_query(make_tl_object<td_api::setFileGenerationProgress>(
+ id_, 1039823 /*yeah, exact size of this file*/, narrow_cast<int32>(ready)),
+ [](auto result) { check_td_error(result); });
+ set_timeout_in(0.02);
+ }
+ void tear_down() override {
+ std::fclose(from);
+ std::fclose(to);
+ parent_->send_query(make_tl_object<td_api::finishFileGeneration>(id_, nullptr),
+ [](auto result) { check_td_error(result); });
+ }
+ };
+ void generate_file(int64 id, string original_path, string destination_path, string conversion) {
+ LOG(ERROR) << "Generate file " << tag("id", id) << tag("original_path", original_path)
+ << tag("destination_path", destination_path) << tag("conversion", conversion);
+ if (conversion == "square") {
+ create_actor<GenerateFile>("GenerateFile", this, id, original_path, destination_path, conversion).release();
+ } else if (conversion == "thumbnail") {
+ write_file(destination_path, base64url_decode(Slice(thumbnail, thumbnail_size)).ok()).ensure();
+ this->send_query(make_tl_object<td_api::finishFileGeneration>(id, nullptr),
+ [](auto result) { check_td_error(result); });
+ } else {
+ LOG(FATAL) << "unknown " << tag("conversion", conversion);
+ }
+ }
+
+ private:
+ string tag_;
+ string username_;
+ int64 chat_id_ = 0;
+};
+
+class CheckTestC : public Task {
+ public:
+ CheckTestC(string username, string tag, Promise<> promise)
+ : username_(std::move(username)), tag_(std::move(tag)), promise_(std::move(promise)) {
+ }
+
+ void start_up() override {
+ send_query(make_tl_object<td_api::searchPublicChat>(username_), [this](auto res) {
+ CHECK(res->get_id() == td_api::chat::ID);
+ auto chat = move_tl_object_as<td_api::chat>(res);
+ chat_id_ = chat->id_;
+ this->one_file();
+ });
+ }
+
+ private:
+ string username_;
+ string tag_;
+ Promise<> promise_;
+ int64 chat_id_;
+
+ void one_file() {
+ this->send_query(
+ make_tl_object<td_api::sendMessage>(
+ chat_id_, 0, false, false, nullptr,
+ make_tl_object<td_api::inputMessageText>(
+ make_tl_object<td_api::formattedText>(PSTRING() << tag_ << " ONE_FILE", Auto()), false, false)),
+ [](auto res) { check_td_error(res); });
+ }
+
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td_api::updateNewMessage::ID) {
+ auto updateNewMessage = move_tl_object_as<td_api::updateNewMessage>(update->object);
+ auto &message = updateNewMessage->message_;
+ if (message->content_->get_id() == td_api::messageDocument::ID) {
+ auto messageDocument = move_tl_object_as<td_api::messageDocument>(message->content_);
+ auto text = messageDocument->caption_->text_;
+ if (text.substr(0, tag_.size()) == tag_) {
+ file_id_to_check_ = messageDocument->document_->document_->id_;
+ LOG(ERROR) << "GOT FILE " << to_string(messageDocument->document_->document_);
+ this->send_query(make_tl_object<td_api::downloadFile>(file_id_to_check_, 1),
+ [](auto res) { check_td_error(res); });
+ }
+ }
+ } else if (update->object->get_id() == td_api::updateFile::ID) {
+ auto updateFile = move_tl_object_as<td_api::updateFile>(update->object);
+ if (updateFile->file_->id_ == file_id_to_check_ && (updateFile->file_->local_->is_downloading_completed_)) {
+ check_file(updateFile->file_->local_->path_);
+ }
+ }
+ }
+
+ void check_file(CSlice path) {
+ FILE *from = std::fopen(path.c_str(), "rb");
+ CHECK(from);
+ uint32 x;
+ uint32 y = 1;
+ while (std::fscanf(from, "%u", &x) == 1) {
+ CHECK(x == y * y);
+ y++;
+ }
+ std::fclose(from);
+ stop();
+ }
+ int32 file_id_to_check_ = 0;
+};
+
+class LoginTestActor : public Actor {
+ public:
+ explicit LoginTestActor(Status *status) : status_(status) {
+ *status_ = Status::OK();
+ }
+
+ private:
+ Status *status_;
+ ActorOwn<TestClient> alice_;
+ ActorOwn<TestClient> bob_;
+
+ string alice_phone_ = "9996636437";
+ string bob_phone_ = "9996636438";
+ string alice_username_ = "alice_" + alice_phone_;
+ string bob_username_ = "bob_" + bob_phone_;
+
+ string stage_name_;
+
+ void begin_stage(string stage_name, double timeout) {
+ LOG(WARNING) << "Begin stage '" << stage_name << "'";
+ stage_name_ = std::move(stage_name);
+ set_timeout_in(timeout);
+ }
+
+ void start_up() override {
+ begin_stage("Logging in", 160);
+ alice_ = create_actor<TestClient>("AliceClient", "alice");
+ bob_ = create_actor<TestClient>("BobClient", "bob");
+
+ send_closure(alice_, &TestClient::add_listener,
+ std::make_unique<DoAuthentication>(
+ "alice", alice_phone_, "33333",
+ PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec))));
+
+ send_closure(bob_, &TestClient::add_listener,
+ std::make_unique<DoAuthentication>(
+ "bob", bob_phone_, "33333",
+ PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec))));
+ }
+
+ int start_up_fence_ = 3;
+ void start_up_fence_dec() {
+ --start_up_fence_;
+ if (start_up_fence_ == 0) {
+ init();
+ } else if (start_up_fence_ == 1) {
+ return init();
+ class WaitActor : public Actor {
+ public:
+ WaitActor(double timeout, Promise<> promise) : timeout_(timeout), promise_(std::move(promise)) {
+ }
+ void start_up() override {
+ set_timeout_in(timeout_);
+ }
+ void timeout_expired() override {
+ stop();
+ }
+
+ private:
+ double timeout_;
+ Promise<> promise_;
+ };
+ create_actor<WaitActor>("WaitActor", 2,
+ PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec)))
+ .release();
+ }
+ }
+
+ void init() {
+ send_closure(alice_, &TestClient::add_listener,
+ std::make_unique<SetUsername>(
+ alice_username_, PromiseCreator::event(self_closure(this, &LoginTestActor::init_fence_dec))));
+ send_closure(bob_, &TestClient::add_listener,
+ std::make_unique<SetUsername>(
+ bob_username_, PromiseCreator::event(self_closure(this, &LoginTestActor::init_fence_dec))));
+ }
+
+ int init_fence_ = 2;
+ void init_fence_dec() {
+ if (--init_fence_ == 0) {
+ test_a();
+ }
+ }
+
+ int32 test_a_fence_ = 2;
+ void test_a_fence() {
+ if (--test_a_fence_ == 0) {
+ test_b();
+ }
+ }
+
+ void test_a() {
+ begin_stage("Ready to create chats", 80);
+ string alice_tag = PSTRING() << format::as_hex(Random::secure_int64());
+ string bob_tag = PSTRING() << format::as_hex(Random::secure_int64());
+
+ send_closure(bob_, &TestClient::add_listener,
+ std::make_unique<CheckTestA>(
+ alice_tag, PromiseCreator::event(self_closure(this, &LoginTestActor::test_a_fence))));
+ send_closure(alice_, &TestClient::add_listener,
+ std::make_unique<CheckTestA>(
+ bob_tag, PromiseCreator::event(self_closure(this, &LoginTestActor::test_a_fence))));
+
+ send_closure(alice_, &TestClient::add_listener, std::make_unique<TestA>(alice_tag, bob_username_));
+ send_closure(bob_, &TestClient::add_listener, std::make_unique<TestA>(bob_tag, alice_username_));
+ // send_closure(alice_, &TestClient::add_listener, std::make_unique<TestChat>(bob_username_));
+ }
+
+ void timeout_expired() override {
+ LOG(FATAL) << "Timeout expired in stage '" << stage_name_ << "'";
+ }
+
+ int32 test_b_fence_ = 1;
+ void test_b_fence() {
+ if (--test_b_fence_ == 0) {
+ test_c();
+ }
+ }
+
+ int32 test_c_fence_ = 1;
+ void test_c_fence() {
+ if (--test_c_fence_ == 0) {
+ finish();
+ }
+ }
+
+ void test_b() {
+ begin_stage("Create secret chat", 40);
+ string tag = PSTRING() << format::as_hex(Random::secure_int64());
+
+ send_closure(
+ bob_, &TestClient::add_listener,
+ std::make_unique<CheckTestA>(tag, PromiseCreator::event(self_closure(this, &LoginTestActor::test_b_fence))));
+ send_closure(alice_, &TestClient::add_listener, std::make_unique<TestSecretChat>(tag, bob_username_));
+ }
+
+ void test_c() {
+ begin_stage("Send generated file", 240);
+ string tag = PSTRING() << format::as_hex(Random::secure_int64());
+
+ send_closure(bob_, &TestClient::add_listener,
+ std::make_unique<CheckTestC>(
+ alice_username_, tag, PromiseCreator::event(self_closure(this, &LoginTestActor::test_c_fence))));
+ send_closure(alice_, &TestClient::add_listener, std::make_unique<TestFileGenerated>(tag, bob_username_));
+ }
+
+ int32 finish_fence_ = 2;
+ void finish_fence() {
+ finish_fence_--;
+ if (finish_fence_ == 0) {
+ Scheduler::instance()->finish();
+ stop();
+ }
+ }
+ void finish() {
+ send_closure(alice_, &TestClient::close, PromiseCreator::event(self_closure(this, &LoginTestActor::finish_fence)));
+ send_closure(bob_, &TestClient::close, PromiseCreator::event(self_closure(this, &LoginTestActor::finish_fence)));
+ }
+};
+
+class Tdclient_login : public td::Test {
+ public:
+ using Test::Test;
+ bool step() final {
+ if (!is_inited_) {
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(DEBUG) + 2);
+ sched_.init(4);
+ sched_.create_actor_unsafe<LoginTestActor>(0, "LoginTestActor", &result_).release();
+ sched_.start();
+ is_inited_ = true;
+ }
+
+ bool ret = sched_.run_main(10);
+ if (ret) {
+ return true;
+ }
+ sched_.finish();
+ if (result_.is_error()) {
+ LOG(ERROR) << result_;
+ }
+ ASSERT_TRUE(result_.is_ok());
+ return false;
+ }
+
+ private:
+ bool is_inited_ = false;
+ ConcurrentScheduler sched_;
+ Status result_;
+};
+Tdclient_login Tdclient_login("Tdclient_login");
+}; // namespace td
diff --git a/protocols/Telegram/tdlib/td/test/tests_runner.cpp b/protocols/Telegram/tdlib/td/test/tests_runner.cpp
new file mode 100644
index 0000000000..0edb186f0a
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/tests_runner.cpp
@@ -0,0 +1,18 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#include "test/tests_runner.h"
+
+#include "test/TestsRunner.h"
+
+extern "C" {
+void tests_runner_init(const char *dir) {
+ td::TestsRunner::init(dir);
+}
+void run_all_tests() {
+ td::TestsRunner::run_all_tests();
+}
+}
diff --git a/protocols/Telegram/tdlib/td/test/tests_runner.h b/protocols/Telegram/tdlib/td/test/tests_runner.h
new file mode 100644
index 0000000000..3de566858b
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/tests_runner.h
@@ -0,0 +1,18 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void tests_runner_init(const char *dir);
+void run_all_tests();
+
+#ifdef __cplusplus
+}
+#endif