diff options
Diffstat (limited to 'protocols/Telegram/tdlib/td/test')
-rw-r--r-- | protocols/Telegram/tdlib/td/test/CMakeLists.txt | 50 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/TestsRunner.cpp | 63 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/TestsRunner.h | 19 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/data.cpp | 69 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/data.h | 15 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/db.cpp | 575 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/fuzz_url.cpp | 33 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/http.cpp | 373 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/main.cpp | 40 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/message_entities.cpp | 529 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/mtproto.cpp | 347 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/secret.cpp | 1056 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/string_cleaning.cpp | 107 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/tdclient.cpp | 837 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/tests_runner.cpp | 18 | ||||
-rw-r--r-- | protocols/Telegram/tdlib/td/test/tests_runner.h | 18 |
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 |