summaryrefslogtreecommitdiff
path: root/protocols/Telegram/tdlib/td/test
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2022-11-30 17:48:47 +0300
committerGeorge Hazan <ghazan@miranda.im>2022-11-30 17:48:47 +0300
commit0ece30dc7c0e34b4c5911969b8fa99c33c6d023c (patch)
tree671325d3fec09b999411e4e3ab84ef8259261818 /protocols/Telegram/tdlib/td/test
parent46c53ffc6809c67e4607e99951a2846c382b63b2 (diff)
Telegram: update for TDLIB
Diffstat (limited to 'protocols/Telegram/tdlib/td/test')
-rw-r--r--protocols/Telegram/tdlib/td/test/CMakeLists.txt51
-rw-r--r--protocols/Telegram/tdlib/td/test/TestsRunner.cpp63
-rw-r--r--protocols/Telegram/tdlib/td/test/TestsRunner.h19
-rw-r--r--protocols/Telegram/tdlib/td/test/country_info.cpp87
-rw-r--r--protocols/Telegram/tdlib/td/test/crypto.cpp279
-rw-r--r--protocols/Telegram/tdlib/td/test/data.cpp442
-rw-r--r--protocols/Telegram/tdlib/td/test/data.h15
-rw-r--r--protocols/Telegram/tdlib/td/test/db.cpp512
-rw-r--r--protocols/Telegram/tdlib/td/test/fuzz_url.cpp4
-rw-r--r--protocols/Telegram/tdlib/td/test/http.cpp358
-rw-r--r--protocols/Telegram/tdlib/td/test/link.cpp983
-rw-r--r--protocols/Telegram/tdlib/td/test/main.cpp56
-rw-r--r--protocols/Telegram/tdlib/td/test/message_entities.cpp1652
-rw-r--r--protocols/Telegram/tdlib/td/test/mtproto.cpp663
-rw-r--r--protocols/Telegram/tdlib/td/test/online.cpp632
-rw-r--r--protocols/Telegram/tdlib/td/test/poll.cpp58
-rw-r--r--protocols/Telegram/tdlib/td/test/secret.cpp376
-rw-r--r--protocols/Telegram/tdlib/td/test/secure_storage.cpp70
-rw-r--r--protocols/Telegram/tdlib/td/test/set_with_position.cpp263
-rw-r--r--protocols/Telegram/tdlib/td/test/string_cleaning.cpp103
-rw-r--r--protocols/Telegram/tdlib/td/test/tdclient.cpp1031
-rw-r--r--protocols/Telegram/tdlib/td/test/tests_runner.cpp18
-rw-r--r--protocols/Telegram/tdlib/td/test/tests_runner.h18
-rw-r--r--protocols/Telegram/tdlib/td/test/tqueue.cpp250
24 files changed, 6700 insertions, 1303 deletions
diff --git a/protocols/Telegram/tdlib/td/test/CMakeLists.txt b/protocols/Telegram/tdlib/td/test/CMakeLists.txt
index d120d8d3fb..2ac9ad32d6 100644
--- a/protocols/Telegram/tdlib/td/test/CMakeLists.txt
+++ b/protocols/Telegram/tdlib/td/test/CMakeLists.txt
@@ -1,18 +1,22 @@
-cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
+if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
+ message(FATAL_ERROR "CMake >= 3.0.2 is required")
+endif()
#SOURCE SETS
set(TD_TEST_SOURCE
+ ${CMAKE_CURRENT_SOURCE_DIR}/country_info.cpp
${CMAKE_CURRENT_SOURCE_DIR}/db.cpp
${CMAKE_CURRENT_SOURCE_DIR}/http.cpp
- ${CMAKE_CURRENT_SOURCE_DIR}/mtproto.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/link.cpp
${CMAKE_CURRENT_SOURCE_DIR}/message_entities.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/mtproto.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/poll.cpp
${CMAKE_CURRENT_SOURCE_DIR}/secret.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/secure_storage.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/set_with_position.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}/tdclient.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/tqueue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/data.cpp
${CMAKE_CURRENT_SOURCE_DIR}/data.h
@@ -26,23 +30,44 @@ 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)
+#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 tdcore tdclient)
if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN)
#Tests
+ if (OPENSSL_FOUND)
+ add_executable(test-crypto EXCLUDE_FROM_ALL crypto.cpp)
+ target_include_directories(test-crypto SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR})
+ target_link_libraries(test-crypto PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES} tdutils tdcore)
+
+ if (WIN32)
+ if (MINGW)
+ target_link_libraries(test-crypto PRIVATE ws2_32 mswsock crypt32)
+ else()
+ target_link_libraries(test-crypto PRIVATE ws2_32 Mswsock Crypt32)
+ endif()
+ endif()
+ endif()
+
+ add_executable(test-tdutils EXCLUDE_FROM_ALL ${TESTS_MAIN} ${TDUTILS_TEST_SOURCE})
+ add_executable(test-online EXCLUDE_FROM_ALL online.cpp)
add_executable(run_all_tests ${TESTS_MAIN} ${TD_TEST_SOURCE})
- if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN)
+ if (CLANG AND NOT CYGWIN AND NOT EMSCRIPTEN AND NOT (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") AND NOT (CMAKE_SIZEOF_VOID_P EQUAL 4))
+ target_compile_options(test-tdutils PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
target_compile_options(run_all_tests PUBLIC -fsanitize=undefined -fno-sanitize=vptr)
+ target_compile_options(test-online 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)
+ target_include_directories(test-tdutils PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
+ target_link_libraries(test-tdutils PRIVATE tdutils)
+ target_link_libraries(run_all_tests PRIVATE tdcore tdclient)
+ target_link_libraries(test-online PRIVATE tdcore tdclient tdutils tdactor)
if (CLANG)
# add_executable(fuzz_url fuzz_url.cpp)
-# target_link_libraries(fuzz_url PRIVATE tdclient)
+# target_link_libraries(fuzz_url PRIVATE tdcore)
# target_compile_options(fuzz_url PRIVATE "-fsanitize-coverage=trace-pc-guard")
endif()
diff --git a/protocols/Telegram/tdlib/td/test/TestsRunner.cpp b/protocols/Telegram/tdlib/td/test/TestsRunner.cpp
deleted file mode 100644
index fbe155738e..0000000000
--- a/protocols/Telegram/tdlib/td/test/TestsRunner.cpp
+++ /dev/null
@@ -1,63 +0,0 @@
-//
-// 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
deleted file mode 100644
index a5bc66d855..0000000000
--- a/protocols/Telegram/tdlib/td/test/TestsRunner.h
+++ /dev/null
@@ -1,19 +0,0 @@
-//
-// 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/country_info.cpp b/protocols/Telegram/tdlib/td/test/country_info.cpp
new file mode 100644
index 0000000000..bf88173915
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/country_info.cpp
@@ -0,0 +1,87 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// 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/CountryInfoManager.h"
+
+#include "td/utils/common.h"
+#include "td/utils/tests.h"
+
+static void check_phone_number_info(td::string phone_number_prefix, const td::string &country_code,
+ const td::string &calling_code, const td::string &formatted_phone_number) {
+ auto result = td::CountryInfoManager::get_phone_number_info_sync(td::string(), std::move(phone_number_prefix));
+ CHECK(result != nullptr);
+ if (result->country_ == nullptr) {
+ CHECK(country_code.empty());
+ } else {
+ CHECK(result->country_->country_code_ == country_code);
+ }
+ CHECK(result->country_calling_code_ == calling_code);
+ CHECK(result->formatted_phone_number_ == formatted_phone_number);
+}
+
+TEST(CountryInfo, phone_number_info) {
+ check_phone_number_info("", "", "", "");
+ check_phone_number_info("aba c aba", "", "", "");
+
+ td::string str;
+ td::string reverse_str;
+ for (auto i = 0; i < 256; i++) {
+ str += static_cast<char>(i);
+ reverse_str += static_cast<char>(255 - i);
+ }
+ check_phone_number_info(str, "", "", "0123456789");
+ check_phone_number_info(reverse_str, "IR", "98", "765 432 10--");
+
+ check_phone_number_info("1", "US", "1", "--- --- ----");
+ check_phone_number_info("12", "US", "1", "2-- --- ----");
+ check_phone_number_info("126", "US", "1", "26- --- ----");
+ check_phone_number_info("128", "US", "1", "28- --- ----");
+ check_phone_number_info("1289", "CA", "1", "289 --- ----");
+ check_phone_number_info("1289123123", "CA", "1", "289 123 123-");
+ check_phone_number_info("128912312345", "CA", "1", "289 123 12345");
+ check_phone_number_info("1268", "AG", "1268", "--- ----");
+ check_phone_number_info("126801", "AG", "1268", "01- ----");
+ check_phone_number_info("12680123", "AG", "1268", "012 3---");
+ check_phone_number_info("12680123456", "AG", "1268", "012 3456");
+ check_phone_number_info("1268012345678", "AG", "1268", "012 345678");
+ check_phone_number_info("7", "RU", "7", "--- --- ----");
+ check_phone_number_info("71234567", "RU", "7", "123 456 7---");
+ check_phone_number_info("77654321", "KZ", "7", "765 432 1- --");
+ check_phone_number_info("3", "", "3", "");
+ check_phone_number_info("37", "", "37", "");
+ check_phone_number_info("372", "EE", "372", "---- ----");
+ check_phone_number_info("42", "", "42", "");
+ check_phone_number_info("420", "CZ", "420", "--- --- ---");
+ check_phone_number_info("421", "SK", "421", "--- --- ---");
+ check_phone_number_info("422", "", "", "422");
+ check_phone_number_info("423", "LI", "423", "--- ----");
+ check_phone_number_info("424", "YL", "42", "4");
+ check_phone_number_info("4241234567890", "YL", "42", "41234567890");
+ check_phone_number_info("4", "", "4", "");
+ check_phone_number_info("49", "DE", "49", "---- -------");
+ check_phone_number_info("491", "DE", "49", "1--- -------");
+ check_phone_number_info("492", "DE", "49", "2--- -------");
+ check_phone_number_info("4915", "DE", "49", "15-- -------");
+ check_phone_number_info("4916", "DE", "49", "16- -------");
+ check_phone_number_info("4917", "DE", "49", "17- -------");
+ check_phone_number_info("4918", "DE", "49", "18-- -------");
+ check_phone_number_info("493", "DE", "49", "3--- -------");
+ check_phone_number_info("4936", "DE", "49", "36-- -------");
+ check_phone_number_info("49360", "DE", "49", "360- -------");
+ check_phone_number_info("493601", "DE", "49", "3601 -------");
+ check_phone_number_info("4936014", "DE", "49", "3601 4------");
+ check_phone_number_info("4936015", "DE", "49", "3601 5------");
+ check_phone_number_info("493601419", "DE", "49", "3601 419----");
+ check_phone_number_info("4936014198", "DE", "49", "3601 4198--");
+ check_phone_number_info("49360141980", "DE", "49", "3601 41980-");
+ check_phone_number_info("841234567890", "VN", "84", "1234567890");
+ check_phone_number_info("31", "NL", "31", "- -- -- -- --");
+ check_phone_number_info("318", "NL", "31", "8 -- -- -- --");
+ check_phone_number_info("319", "NL", "31", "9 -- -- -- --");
+ check_phone_number_info("3196", "NL", "31", "9 6- -- -- --");
+ check_phone_number_info("3197", "NL", "31", "97 ---- -----");
+ check_phone_number_info("3198", "NL", "31", "9 8- -- -- --");
+}
diff --git a/protocols/Telegram/tdlib/td/test/crypto.cpp b/protocols/Telegram/tdlib/td/test/crypto.cpp
new file mode 100644
index 0000000000..1837cef631
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/crypto.cpp
@@ -0,0 +1,279 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// 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/mtproto/AuthKey.h"
+#include "td/mtproto/Transport.h"
+
+#include "td/utils/base64.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/logging.h"
+#include "td/utils/port/detail/ThreadIdGuard.h"
+#include "td/utils/ScopeGuard.h"
+#include "td/utils/SharedSlice.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/StringBuilder.h"
+#include "td/utils/UInt.h"
+
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/pem.h>
+
+class Handshake {
+ public:
+ struct KeyPair {
+ td::SecureString private_key;
+ td::SecureString public_key;
+ };
+
+ static td::Result<KeyPair> generate_key_pair() {
+ EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(NID_X25519, nullptr);
+ if (pctx == nullptr) {
+ return td::Status::Error("Can't create EXP_PKEY_CTX");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_CTX_free(pctx);
+ };
+ if (EVP_PKEY_keygen_init(pctx) <= 0) {
+ return td::Status::Error("Can't init keygen");
+ }
+
+ EVP_PKEY *pkey = nullptr;
+ if (EVP_PKEY_keygen(pctx, &pkey) <= 0) {
+ return td::Status::Error("Can't generate key");
+ }
+
+ TRY_RESULT(private_key, X25519_key_from_PKEY(pkey, true));
+ TRY_RESULT(public_key, X25519_key_from_PKEY(pkey, false));
+
+ KeyPair res;
+ res.private_key = std::move(private_key);
+ res.public_key = std::move(public_key);
+
+ return std::move(res);
+ }
+
+ static td::SecureString expand_secret(td::Slice secret) {
+ td::SecureString res(128);
+ td::hmac_sha512(secret, "0", res.as_mutable_slice().substr(0, 64));
+ td::hmac_sha512(secret, "1", res.as_mutable_slice().substr(64, 64));
+ return res;
+ }
+
+ static td::Result<td::SecureString> privateKeyToPem(td::Slice key) {
+ auto pkey_private = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, nullptr, key.ubegin(), 32);
+ CHECK(pkey_private != nullptr);
+ auto res = X25519_pem_from_PKEY(pkey_private, true);
+ EVP_PKEY_free(pkey_private);
+ return res;
+ }
+
+ static td::Result<td::SecureString> calc_shared_secret(td::Slice private_key, td::Slice other_public_key) {
+ auto pkey_private = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, nullptr, private_key.ubegin(), 32);
+ if (pkey_private == nullptr) {
+ return td::Status::Error("Invalid X25520 private key");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_free(pkey_private);
+ };
+
+ auto pkey_public =
+ EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, nullptr, other_public_key.ubegin(), other_public_key.size());
+ if (pkey_public == nullptr) {
+ return td::Status::Error("Invalid X25519 public key");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_free(pkey_public);
+ };
+
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey_private, nullptr);
+ if (ctx == nullptr) {
+ return td::Status::Error("Can't create EVP_PKEY_CTX");
+ }
+ SCOPE_EXIT {
+ EVP_PKEY_CTX_free(ctx);
+ };
+
+ if (EVP_PKEY_derive_init(ctx) <= 0) {
+ return td::Status::Error("Can't init derive");
+ }
+ if (EVP_PKEY_derive_set_peer(ctx, pkey_public) <= 0) {
+ return td::Status::Error("Can't init derive");
+ }
+
+ size_t result_len = 0;
+ if (EVP_PKEY_derive(ctx, nullptr, &result_len) <= 0) {
+ return td::Status::Error("Can't get result length");
+ }
+ if (result_len != 32) {
+ return td::Status::Error("Unexpected result length");
+ }
+
+ td::SecureString result(result_len, '\0');
+ if (EVP_PKEY_derive(ctx, result.as_mutable_slice().ubegin(), &result_len) <= 0) {
+ return td::Status::Error("Failed to compute shared secret");
+ }
+ return std::move(result);
+ }
+
+ private:
+ static td::Result<td::SecureString> X25519_key_from_PKEY(EVP_PKEY *pkey, bool is_private) {
+ auto func = is_private ? &EVP_PKEY_get_raw_private_key : &EVP_PKEY_get_raw_public_key;
+ size_t len = 0;
+ if (func(pkey, nullptr, &len) == 0) {
+ return td::Status::Error("Failed to get raw key length");
+ }
+ CHECK(len == 32);
+
+ td::SecureString result(len);
+ if (func(pkey, result.as_mutable_slice().ubegin(), &len) == 0) {
+ return td::Status::Error("Failed to get raw key");
+ }
+ return std::move(result);
+ }
+
+ static td::Result<td::SecureString> X25519_pem_from_PKEY(EVP_PKEY *pkey, bool is_private) {
+ BIO *mem_bio = BIO_new(BIO_s_mem());
+ SCOPE_EXIT {
+ BIO_vfree(mem_bio);
+ };
+ if (is_private) {
+ PEM_write_bio_PrivateKey(mem_bio, pkey, nullptr, nullptr, 0, nullptr, nullptr);
+ } else {
+ PEM_write_bio_PUBKEY(mem_bio, pkey);
+ }
+ char *data_ptr = nullptr;
+ auto data_size = BIO_get_mem_data(mem_bio, &data_ptr);
+ return td::SecureString(data_ptr, data_size);
+ }
+};
+
+struct HandshakeTest {
+ Handshake::KeyPair alice;
+ Handshake::KeyPair bob;
+
+ td::SecureString shared_secret;
+ td::SecureString key;
+};
+
+static void KDF3(td::Slice auth_key, const td::UInt128 &msg_key, int X, td::UInt256 *aes_key, td::UInt128 *aes_iv) {
+ td::uint8 buf_raw[36 + 16];
+ td::MutableSlice buf(buf_raw, 36 + 16);
+ td::Slice msg_key_slice = td::as_slice(msg_key);
+
+ // sha256_a = SHA256 (msg_key + substr(auth_key, x, 36));
+ buf.copy_from(msg_key_slice);
+ buf.substr(16, 36).copy_from(auth_key.substr(X, 36));
+ td::uint8 sha256_a_raw[32];
+ td::MutableSlice sha256_a(sha256_a_raw, 32);
+ sha256(buf, sha256_a);
+
+ // sha256_b = SHA256 (substr(auth_key, 40+x, 36) + msg_key);
+ buf.copy_from(auth_key.substr(40 + X, 36));
+ buf.substr(36).copy_from(msg_key_slice);
+ td::uint8 sha256_b_raw[32];
+ td::MutableSlice sha256_b(sha256_b_raw, 32);
+ sha256(buf, sha256_b);
+
+ // aes_key = substr(sha256_a, 0, 8) + substr(sha256_b, 8, 16) + substr(sha256_a, 24, 8);
+ td::MutableSlice aes_key_slice(aes_key->raw, sizeof(aes_key->raw));
+ aes_key_slice.copy_from(sha256_a.substr(0, 8));
+ aes_key_slice.substr(8).copy_from(sha256_b.substr(8, 16));
+ aes_key_slice.substr(24).copy_from(sha256_a.substr(24, 8));
+
+ // aes_iv = substr(sha256_b, 0, 4) + substr(sha256_a, 8, 8) + substr(sha256_b, 24, 4);
+ td::MutableSlice aes_iv_slice(aes_iv->raw, sizeof(aes_iv->raw));
+ aes_iv_slice.copy_from(sha256_b.substr(0, 4));
+ aes_iv_slice.substr(4).copy_from(sha256_a.substr(8, 8));
+ aes_iv_slice.substr(12).copy_from(sha256_b.substr(24, 4));
+}
+
+static td::SecureString encrypt(td::Slice key, td::Slice data, td::int32 seqno, int X) {
+ td::SecureString res(data.size() + 4 + 16);
+ res.as_mutable_slice().substr(20).copy_from(data);
+
+ // big endian
+ td::uint8 *ptr = res.as_mutable_slice().substr(16).ubegin();
+ ptr[0] = static_cast<td::uint8>((seqno >> 24) & 255);
+ ptr[1] = static_cast<td::uint8>((seqno >> 16) & 255);
+ ptr[2] = static_cast<td::uint8>((seqno >> 8) & 255);
+ ptr[3] = static_cast<td::uint8>(seqno & 255);
+
+ td::mtproto::AuthKey auth_key(0, key.str());
+ auto payload = res.as_mutable_slice().substr(16);
+ td::UInt128 msg_key = td::mtproto::Transport::calc_message_key2(auth_key, X, payload).second;
+ td::UInt256 aes_key;
+ td::UInt128 aes_iv;
+ KDF3(key, msg_key, X, &aes_key, &aes_iv);
+ td::AesCtrState aes;
+ aes.init(aes_key.as_slice(), aes_iv.as_slice());
+ aes.encrypt(payload, payload);
+ res.as_mutable_slice().copy_from(msg_key.as_slice());
+ return res;
+}
+
+static HandshakeTest gen_test() {
+ HandshakeTest res;
+ res.alice = Handshake::generate_key_pair().move_as_ok();
+
+ res.bob = Handshake::generate_key_pair().move_as_ok();
+ res.shared_secret = Handshake::calc_shared_secret(res.alice.private_key, res.bob.public_key).move_as_ok();
+ res.key = Handshake::expand_secret(res.shared_secret);
+ return res;
+}
+
+static void run_test(const HandshakeTest &test) {
+ auto alice_secret = Handshake::calc_shared_secret(test.alice.private_key, test.bob.public_key).move_as_ok();
+ auto bob_secret = Handshake::calc_shared_secret(test.bob.private_key, test.alice.public_key).move_as_ok();
+ auto key = Handshake::expand_secret(alice_secret);
+ CHECK(alice_secret == bob_secret);
+ CHECK(alice_secret == test.shared_secret);
+ LOG(ERROR) << "Key\n\t" << td::base64url_encode(key) << "\n";
+ CHECK(key == test.key);
+}
+
+static td::StringBuilder &operator<<(td::StringBuilder &sb, const Handshake::KeyPair &key_pair) {
+ sb << "\tpublic_key (base64url) = " << td::base64url_encode(key_pair.public_key) << "\n";
+ sb << "\tprivate_key (base64url) = " << td::base64url_encode(key_pair.private_key) << "\n";
+ sb << "\tprivate_key (pem) = \n" << Handshake::privateKeyToPem(key_pair.private_key).ok() << "\n";
+ return sb;
+}
+
+static td::StringBuilder &operator<<(td::StringBuilder &sb, const HandshakeTest &test) {
+ sb << "Alice\n" << test.alice;
+ sb << "Bob\n" << test.bob;
+ sb << "SharedSecret\n\t" << td::base64url_encode(test.shared_secret) << "\n";
+ sb << "Key\n\t" << td::base64url_encode(test.key) << "\n";
+
+ std::string data = "hello world";
+ sb << "encrypt(\"" << data << "\", X=0) = \n\t" << td::base64url_encode(encrypt(test.key, data, 1, 0)) << "\n";
+ sb << "encrypt(\"" << data << "\", X=8) = \n\t" << td::base64url_encode(encrypt(test.key, data, 1, 8)) << "\n";
+ return sb;
+}
+
+static HandshakeTest pregenerated_test() {
+ HandshakeTest test;
+ test.alice.public_key = td::base64url_decode_secure("QlCME5fXLyyQQWeYnBiGAZbmzuD4ayOuADCFgmioOBY").move_as_ok();
+ test.alice.private_key = td::base64url_decode_secure("8NZGWKfRCJfiks74RG9_xHmYydarLiRsoq8VcJGPglg").move_as_ok();
+ test.bob.public_key = td::base64url_decode_secure("I1yzfmMCZzlI7xwMj1FJ3O3I3_aEUtv6CxbHiDGzr18").move_as_ok();
+ test.bob.private_key = td::base64url_decode_secure("YMGoowtnZ99roUM2y5JRwiQrwGaNJ-ZRE5boy-l4aHg").move_as_ok();
+ test.shared_secret = td::base64url_decode_secure("0IIvKJuXEwmAp41fYhjUnWqLTYQJ7QeKZKYuCG8mFz8").move_as_ok();
+ test.key = td::base64url_decode_secure(
+ "JHmD-XW9j-13G6KP0tArIhQNDRVbRkKxx0MG0pa2nOgwJHNfiggM0I0RiNIr23-1wRboRtan4WvqOHsxAt_cngYX15_"
+ "HYe8tJdEwHcmlnXq7LtprigzExaNJS7skfOo2irClj-7EL06-jMrhfwngSJFsak8JFSw8s6R4fwCsr50")
+ .move_as_ok();
+
+ return test;
+}
+
+int main() {
+ td::detail::ThreadIdGuard thread_id_guard;
+ auto test = gen_test();
+ run_test(test);
+ run_test(pregenerated_test());
+ LOG(ERROR) << "\n" << pregenerated_test();
+}
diff --git a/protocols/Telegram/tdlib/td/test/data.cpp b/protocols/Telegram/tdlib/td/test/data.cpp
index a57a9147c5..34aff9b333 100644
--- a/protocols/Telegram/tdlib/td/test/data.cpp
+++ b/protocols/Telegram/tdlib/td/test/data.cpp
@@ -1,11 +1,11 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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 {
+#include "data.h"
+
static const char thumbnail_arr[] =
"_9j_4AAQSkZJRgABAQEASABIAAD_2wBDAAICAgICAQICAgIDAgIDAwYEAwMDAwcFBQQGCAcJCAgHCAgJCg0LCQoMCggICw8LDA0ODg8OCQsQERAOEQ"
"0ODg7_2wBDAQIDAwMDAwcEBAcOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7_wAARCAAyADIDASIA"
@@ -66,4 +66,438 @@ static const char gzip_bomb_arr[] =
"TLGznukoHjpzn3qgqJTV2ogJXaK6ctqTgxKUoPyfUeqmx4szeRJuj3josnbUu0O57kcXXX-bi25IozxSONDzF5jalI4";
const char *gzip_bomb = gzip_bomb_arr;
const size_t gzip_bomb_size = sizeof(gzip_bomb_arr) - 1;
-} // namespace td
+
+static const char gzip_arr[] =
+ "eJztxT1LQmEAgNGXMJEIukNI0pDS0hAUSiCa4uZfELqgQwUFuQjXhkDFisrARajBocmISFA0GstBg4owopuIgdAiKE0SfkSBf6HB5TnLmYnpxMBv89"
+ "ExVnBN7pWvll_ePZqMpPo2Fg6dT-GQdfbA_zH_eftzVE16g8l9Ze7cP33ZTn2dlPP9XEfpXbyeqtnrm50HS7G0dbzyZpPNSkW_"
+ "tLmeCCT0pbrzO21otbfjqqyNuIySTjNCRERERERERERERERERERERERERERERERERERE_2k3ZA8YhRBhcb_"
+ "2XHN7zoR5alwbvfMtEhERERERERERERERERER0ZCqTzREVzKNyvlV8Qf1dzn-";
+const char *gzip = gzip_arr;
+const size_t gzip_size = sizeof(gzip_arr) - 1;
+
+static const char sqlite_sample_db_v3_arr[] =
+ "olFZ1MdfY0Abj+LtR9ft6DTZgEHW7/"
+ "z7yAhC07NKr7pBAHWkbQyMPtyVSIW7PLdVaQIHYwLgd7ovQSzD7eTINxZh6Nxpwa8HTynvjhHIdQhtysRL9m3mTEj4mbjU48zq+jcFdsnzG+"
+ "GJAzk5MQRCi+NyDWGYnBoPhiiPc4QBoFgKoYGIhQwfVci5kf2fYIQrCM1H7QQZ8RHoqCuQ3IjMpZjt/Gzqy+P8kn2PXKWHG8/y5eDc5nQCk/"
+ "XhkY7A9Wl7ZUsgK7mkA1O1VHp9X0Kz/WCuWhsULuUSeshXmJ1zKciUmTMhS3T7/"
+ "mc2jVrR00SyT22XwpL6dRwaNtUXVJKJwtzxrTHTxWq33KdxKXFlMr5ffbWsOfpkEHlpeKybJF70bzOgKUF5pmCHOll+3gfZcJOVFI4DvHFSnt/"
+ "Y3XlhJDrHliXeXcfsSUNzpBsBO+/+O/MMGLi2m/kJ9GHXGpieJsHPSQG1RMNqe84QIymLEwnGptg/"
+ "I+gw6xqFsUqBo2Z3Hllgz0t9GqvI1ENZr16nwf9hUGMEpj2pdKGR+eSSHMac3BJaiGgoKE7BfUjaaXzI5a8FfZTyl+"
+ "JUMf3ou2YBuO9fiuR1AhpAyydLSAn93OdbIF+VUHea8944nyUQB2k6idA9zfOFOVPZlaWC8/KxJeLDIAYYv3s+aK3kz/"
+ "EyU4SSRvDLXhMdSrkNaGM4zbYc/8r4WJSAIgFq5yg3jidRq0xJ3eb+BHs6pCTz3ql50BtPd2ngzz2y51kiW2xVIP3K/"
+ "hI53loi9UWTIYQPxtWl8RBWD2J1rbMsG0c8CbCQAo3u0SJ2A+4gQ2q5ZRPl9rp0IEgHwhg1xtjZhz7Ss2X7HO9Bfb/"
+ "ioe6ZlRPPhLR5AueL6YjretqfCs0C6Y3D7Ax6ho3vae5a2HCzasqgD42OSn6YwSq4d85AurIf4GhGykmQnkHPiblLSi9LE8w59B1a9IQgrdnVHqto3"
+ "IYYJELdEHrEOzDOsHoXM3aiXi3kURyDw+L/ljvIwWpgrVic94CCBlwUkUCtbw+n9Gfn06GE4V1J7/"
+ "omTHBKJXfd1hEDBegyEf6l2mx6p6zWCRcBEIZNObMRsX0P7P9A09/ZyzQPAMJrfI25fy2PvEuHWpVmbFBm7Xsf/B/"
+ "pGuP3j4td+e62Fu8yhMpSNhPclCdz+MXO9yCCondvU9DUzPQxU26yQhYNasu4cLipXCYpNhc9+dpLakoqlBB7Qq52R1S/f3PgsbTYy/uJs0NwoQV7/"
+ "aQTgjWutM5Nb+"
+ "yxxebr6dfRG6HfN0AXvLq8ON2FimTK6mXa9YI5YXKROAX4VssccBw1dGM8RaaAvMKQKEjfzwcPMo0C2mCcfFB2Nkj3A2SLvWdL7APgkEnGATDjs8Kn"
+ "bbI5Idr9ReU38zyB2l3Ys+CVbl6dvXdCu9QMS5t9Ez6e6zu3ZJKbMDEmrD7+0QyryYwSTFMDR1LJsbHavYzfGm/"
+ "bAC2yFlwGuN1nqpxvHCrFJH4Gjvs/"
+ "IVsX1Hnr1IZFJdIaMI8DRkksKijAYxxizan62LjrGQ8Lx4SGEYqTKU182XJdG9joSEuIvh2BaLZwu6AGEO1BbtJpGeVUiupqORQMGD9+"
+ "jOzo3KN0u3amsHO9QASeJ2fhMM7ej7C2aBnUrsX3zayB+seS0PTpZPqhI4IImvRmuwWiWXlWqhuEMSrnfuUN+"
+ "oZ8LfhsUPrRriUsuM7ZQJhzeVHGLXKeO0uFhL2KW7EQs7bdnOtQw6vw2FEe6bzYUZdR7SNWvRpN1BFm48vSLnLyhjtRN0ZAI5j6FNB6sWGVXRp32uH"
+ "p0BH/Nuss6Q+xcn0ZhBKIUGPtVtyUZcgoy1JT23JusETl83vsEx5NkWhSUCdhThOyB/N7fl4lRgfF0VxRabLXa0RcPlF7m79R5Mn/+/mveGQtd/"
+ "3UurCuDd2bjJ2ZL+GNgwGpliPpjPa1LHDX7WXi/yyYHHjGq7uYuauQql0ID/"
+ "bZY4kx9W0YTpzeKRd3Y4FZifDtFkxbosTFsUL+svpNSmQLuGptxPrfIHadHBkwP4g5CvAswfos6FGAx+ntE9+"
+ "jVt9WF6n73F7awQY0RxKOPRz0ESRPI3Z3r5m4AXoaEWkzqBIcCsYLBf3gIuxQxCbo5kMxhj7+qAAbJlqUTZQZS21ZbQS/"
+ "CRxXEv5TIYrQv0h004kzRenULxyM2fVgnPF09eLCW1wIeLfy5q5ShfkewBJ2xoqon5Pq9MgyG+EkwZ5ZphjGTEQkRlLfVNeD6s+HBildUL1+"
+ "sQkICmRxIKhubnrJ0WpD4EkpvTOEZkFalGjkp3t1L7KeBseX/qFfCBlcNThLbBVZQMRLGeCYZ/OZ1Z1qkN/KB6ltAjI+ZALJ73G/"
+ "PzPF19S0vUmka0Aeq+Q13X1fD27HGTIW2zVhgQvgIk+shlQWLCDn9qDwNxyGWNplR8dIsnl1lAIVXokjA+qIz3RNAkwDkk60x+"
+ "rZKJSjLiy3RtbJ6ZTsDl4U6NIzln1zbPkbS6ttbtoI86aBtSQribN7zoG3zG9pkdr+"
+ "hGso6jP7QhhEVskGfdbHTtOkTOhGqcrg3jALtv5J4fTNK4kzyjJRYQ0HVuWtPMvP022bI4uA8SbjnWkH7Z2Kt8LujV9ji9RDs4+"
+ "Y3bQn2TlPiShyPeOJ0f2Tlaeo2liySol7PZT1rbDjr8Zp/"
+ "UIbkyeEl0BIg8O6BAJ3h8JMXRQTklu23Fkl6PMHsGHcz0GE58+WJMgM6RyCgc2ka7L4YeGu7mPjJRSo3umdhi2GqOfSgELUzWr/"
+ "gVmFljsiHJkg5xMrksDmGoG+kXv57iyNi8Dphr6SsT3lFdSG4Tx9WeMSSNOngSyK8yzJStqUmq5wiTNeBeIW2KgSQMzGETMpMVZqoAX/"
+ "fHdgAjYxwooM15G2R98Vs1xlX5fjEHz1DNJQ39pSPBJInvdZAQqDFVzD9A4IktiIthEIWj0RemW+HRQKQ/"
+ "rODTVeZB2bY+IAx+qr7JyUPkYziNWQKnXhRUm1t4ZJarr1Ud6/"
+ "G6RxdNjt3pX1HBfn0t4+6nOuWA7MAsdA+EvQjXd8XPLPEFReAHnIXqE0DAAQPmRdcnfDCfXtPQv+"
+ "hD82s4sQCsaqA1dwV3CsRzhfWiZlQ5iMPJtjU0A3UgGPg+"
+ "7MAXNfPUV8eiRyCwC4fEnSkj2LLMf1de1wlyjosXcM6GTMroBo8wFsUWxdTSGA1xsPWDAL3DWi6szxVLp7EGNssLZ8OWKHALYy9P6bN5qUS23FahYg"
+ "Tu6jVCGVAuCGwnaPLGi4yUdQIOTvTEgJiPNf8jZ2wKiOZZ4belGxh6H7Y54kfVCZwxGdhcyktUV+H3TgBZmR70x/"
+ "SF6ct1GeZd1W9IxEJTTcRc179RNcRk4pd5KjwyIO6/WFOtSRKOmPs+cb9bArVV/"
+ "HjeIDM0rvahdWvqjMo8q9BSyzjvrvLrSxkaLskfhu3KHnfftGZdiYypGsngBFN9RDmSHknNttmal5tnWZP/"
+ "5RhfDnfnDwCq9ARP3USRkaUpKl87l0qzZkHnSoKBUpcQ70qrhrMGyQyn7dobM7mHw8UZ4fOuCxY/vEQ3dql4RsczsaqHsl3z/"
+ "AgXKhs2+MI32L9lC5RSz/"
+ "ix2uzbExljLdYgUe6aNsUqKbYkZfugFEbTtwHXPsrSioXuusKGX00muMADmvxpqrVt63omwKlK4IJ9la10DCsJiv5NOFsIZhPTIb2fzJUR6eSRyNFL"
+ "Wt3IKpVTwsLXqI/"
+ "biB6vInyvJvqNWgc8wSIQWvngtSyJugYmFG3k9GuuJQ6VdbafPAR5BCeuJy8JvX0dANbaRvsZPsLWa7nNCM2iwmtkNMMLdQe6+"
+ "DdCHz5XGwW91spC1pORsSTM/"
+ "pegYHDptkICvczJSSTwDs1aATBZGMfVtKzed4Vxx9nxN23uqwcVCwDljkRB7AXbFaCbRLi0glGmK8CaeGq+KAuLj7eAm8w6ihS9IWGAD8oGhVyJR+"
+ "D4U4ah5YX/GAysJA8aK3g0xKmUfveHTUQKDs0dzdTOrTr18++t7IqK1x2jDCmU9/"
+ "C6saN75gcZr4Q9QyULNHFgtXCpydThPIMBJ47jZFuvFSmoBbqD6m8WUjZjDJ4+odZISIGBzUoCKut9jjok8o2RzK6oY265stKOE0Ud7EI+"
+ "7rAtQY71LhsUSC6Sb4QbIsJlG7vb/"
+ "l0fF6Kh5IRGrzNVBJFsd+0ws6JQxDEVFSuO5UZVviYt0GHS+Qtt4EnoWbARCPCQfq4EiZJSbUhIPGWLoh6ozv/"
+ "U+5LArEPD7JcAXvpe+ZwYyrS5c8HWtMng7B7eiNdfjv9HtpfTVidxawNwhgC4igZ18yigsRJvyGuqpCipA/"
+ "p5sfXFb4hibSZsif7HAPja0G4CFxGL96Vxx1eKiSCNyLYL42m2slvgI6rvYKFTVvdQtBFhiav7vg+"
+ "Oz2wwzYRWRlHsIdOfcNws8XAgTL384gT8km9UtO4WtUyJZL5xDb2iWGsSoqRCAFiNYsxpwcCfZa4p5YP8E7PX4tzdUJ+"
+ "m2XNxuM7esUyjDouRnVu49PP78Q5mFAsKUZ/4mwPGlzInGVr8g7piw4FueB5qFfCHibo8gsC355UnT6WB9Gdu2h4+dsI0wh51a5UW7/"
+ "GY8zQ7VDmorGfSiELm3SpS3xvRhBOHGBLgn68hyFNMpzg/"
+ "XfJkEUUlVvayVpmJsKqeQEVwPzLW02XkvsFmjY98JIIDg4PqRw+2vWTQu4yHDx5AwtAYZaCDUYS10OjYZgzVp0NqlA2Lmc4oRYRumyr8w+"
+ "gNFxX16vxrG6DMAJeMtYyACbiuAp3RsW8G3rAWifNmTx8mEkTzZHW6Xqt9nD1qPeVxCZMq+wq7LH333FAVFW/"
+ "V6qccLaqy2uquIW41fWrKW3MkrD3fKqjdGSz0rj278X4kLakpOVvr9ww6FNk8wRL0TcfRxurhhLtRLQp6zgyu5BFA44xFIbq+"
+ "0Yvmics8iY9He45gODgaWRB9vfM8JhpTpI6c6ovoxL8fOuWEPzadjg388Lhaqv/"
+ "CJa8IVbKO9BBMrrHQVbF1iVKDO5UrGR+4LY7NSe7Jt5vrwzhFPaOhDw6VoL3p5jUq3D7ABG3tKbcdrrL+"
+ "lCJ44J0T0fYMVlzFlRjc75oNqPiKDyKOI1e9Fvw9ybZw5b0iU0kyBf1hW+JTtgr/qsszaWe/mHDdGVNXk6OBBCzECTrUj4WdHGi/"
+ "VorxztlwJ5GXoTTdFMhckg//ExJweev/"
+ "s2aYgKZRSgZVkpKHVqyNTVuEaB8UQuPhGGwEerawjiK0hRJaF4Bg9UtJMPuIu8V1bis9oCjWKVcqgiyam4t6N06O5tM38wfPTWtyD4b8ZUDl0koAfa"
+ "n9+uE1mJQw1DSMixM4QDnGXFA9bxgt/"
+ "LvKWdz0MwN9wgI5yhYtmHau0l5lO2WRsRhZvJJof1z3LvZCEqQB2F2z7GHuzmmNnyvZ8QqrnV9EBScfY25vjjBisylp2dorLh7oI2W4z8RsvegvXn4"
+ "nsh8bsEj1DJtXNRIHuhQGY9ewKOYFTc3zUK1NJaNpzBZmW8ccWYJYi6Mk7ZLfpL1A6V/C0AyePBAysg4r/"
+ "VoaLnZxgvhCZvjTH2uqfX7iQuS4F0t+qAijrgPwmm5Vy9JJrgWPKujTZkCg6Px0VVtFC+EfJSx9QyvPY6w4St/"
+ "0D4EjTxRXuEoDGkr1oYBQ8kVBj5KHyIkHCEEBAmrwA0pwtdOyzpRMj79IahmbNNuWftCHx3LklCpaD3Yo0pjTfML4ToJl5S2sQ8wvHsd960fr5fdXZ"
+ "EORmJtkLLgu5Ca6eiKf5GIw/yKSLUwY0F7rMvySfHB3PzBjJjgnt04MzGFg9vSEt6+OsRMKhV1eRGgPpLai6WzxcGY5vuiqw/"
+ "14TsoLKeHbkRO6qJgiJzo2HlDc2+MCrYNp31aeyro7WWQr/"
+ "sPybVj7BVFLuplVprsOG5TWwpzP84I3euP12UlSUma6TgVgyQmoms9rutumle3d26+b7fmnS/aQ3ps9kqYwN0+oDXzkQaP7apliI1Ks/"
+ "3z3ZsGaiLlyVSkDxgLmov1p23Qy7yI1TxxsGt4hVyD5kSJIxzoFnqT09wKhHHa1/"
+ "smUNIqeDLoo5L5e+TiBEGsiHWPcG4bsUWBF9NkbjFxp34tnO9RlE5cFALntzx2418tlQ+"
+ "4CnAljWUAiKEFJrHGH5IBIOU4jMiHmA6w0WYa9bzTnvhaRXVtxWKK9VuNbkciK8+PZOVEz7F/foJ7ERZ2P+9w8htm9J/"
+ "rxKW17GAJN5VVEotpmOPao4PJ/bi3x7Tt/ckZr8444ax/w2BZw0aLt/DgA6Kd04+MOr+pEpi9J3WHKvNnYGFWgYvSg0alOsp/"
+ "Nnwjo6OK6fvTOokwi78PxOy7+yQ2uWZOTzDkFnZ1Ri7X11X1MlAXqGehz+QfjtF2FcPVDLVFsAiLlt/"
+ "Di7LUUhzf3xitZHFkphtSE2ilrIgQAF7cPeRJBFM2MIXNhhlMfAgychBtUcPgzSzkdUbRpZ2pKNtpNZr9Xq5GQLIcKJ4MkBOBUeoduEBjWPxiGcYWo"
+ "tFatmZRoxiYAHxNTcN2FrQ0I9E12UC3NcaFTkqvCaBonx2CvBayKeXuewhxbLB503TQi8E9FSrsi9efYPhGDqqX2EsJD/"
+ "3DHOg28NZvOpZGLs9AEWpgrl+x/"
+ "Wk6mXCxM++OZxA8K3MGlQuG7Gmodcz6FHh9mqoIZZh6OrObpBUrJfdoZeWXR+"
+ "GVt8zi3m0oPlAhNUyi3a6zeZcvqfwI3M7zoXxGU2q0ETZgfCE26H9E+PNxes7mw4SwEl78lclmnNhUlZ5C4Y8v2YJnmFn8+a6WdrgjTU2awQ/"
+ "osSJFtKuNgOw9n72uyhPOkEB4qcVZ1A=";
+
+const char *sqlite_sample_db_v3 = sqlite_sample_db_v3_arr;
+const size_t sqlite_sample_db_v3_size = sizeof(sqlite_sample_db_v3_arr) - 1;
+
+static const char sqlite_sample_db_v4_arr[] =
+ "6YZiAqwf2RbWHjDRoAmm2jMPm70HtfDOgIbyJR9Btp1RWtJiNmwmqsZCfh872BvqFVfYdhtfUJwVIpOe3se/"
+ "9d9v+"
+ "eMJFKyUxyxkeWjwLJiAEa1fQtF9C8RVtmiTUweBQFmuAKlIVjTHfM0C2IspViXoBYMElb6CpuwKIN0XptduYh3GPC2IBjBET0eGWycgrOPCeTSe8PC"
+ "SfBtaX7dlCOE3zTeRqR+iP3G5Tb67xB2e8CDp4EbPJiXbNwKMyAek73D8dVqeFcjtuUfRkah/Iu6hIkBLKiS02ut/"
+ "jD0b1Sy5RyR7NDiCOKVmqnwwKDrcDbofPYmll8ilK4/qzcmzRBJKhxJeaRRy1X9UvzSbYaj22wCKrQLMqHeZ7EVOsPXJEhLVsSWnym/"
+ "vkl9mLxhiDUhvP4pNK3KjmoK1bTEIpqABLhQELQjl+EzBDt+"
+ "tYjeyZ0vCXL2JUYScZcWHn0kD1jwIYGm7r4QIfEMOwwPkkIcsIUOQjW2ICqCVfffoP5JQO9e5352tpAYcUYyiagGKDYtt1yDmFqqvL8Jn+"
+ "r19aJHMsQgUgE7dEusHXiefgeaVRmmI8gW88xvYqyPcM5dz9t8eU7jl7ipq4EPYPOl6K4p0OU/"
+ "Ro2CJTekwHkaouF2T+"
+ "L9WOuRT5WgGsUpcvmY7xDtmS85Iy3uQHZuNeL6FWSCJZ8qjbFbx3zKrlSoUwef86H8OGNA4rYmpKaEpQAsiI1IUi21q26HlgjPE9+"
+ "m2M3HZH72fnJ6lZw6v+vy7Jz/M80nIv0A5A67DE/"
+ "Gu7FPHx4NmxLPbU3pWMIg8fUl3uB5Pmk6hL+Gxi2+W+jvpLk31Qk7HPDX2H+bdTEgetpKzgjcoh/"
+ "XbvrBS82jWgHpWzw9vlbd2FUKbT20ULRE9zSHFYbdWmqchJc4Ma6iNY8rDgH4slTCt3koEaIXTwCsBguVS9a/X1QLUcWlBzt7ot5hovXQUuKd/"
+ "XHDRvusNMvcyxSOwPpF3tbMO9or+BKUq4nmwnzFUzzijl/v0KYUaTmYXQ2HgLW71oUl0cqdJ9cYk0s4MSppzo1wYF3BdQ/"
+ "348785aLf4O3HwZSXfdu2juvZTMkksahRiRCQgUDaB9izTwe/PyfRYv/"
+ "fm5VY6xzoQc3RiCa4zAQBDj3JffYw6cAYk9RHh290Gcvf8QgfBthp7dwlS3XzTNsubyd4l4FRQkvpWInHiFEUPd7wwHQNCnxE20DK/"
+ "S8Jw86Q7VpGnmPlVGQn/vdW2RrJ/"
+ "ATsQnBQPUb2+"
+ "vIoVqFRhHWk81Qxp3LL0Jy1xWe8B7y7L47R8lzI98vOwze7KBr8IH3Sw0snD86vp0kqTqnmJVYrWIROMYm9be7dvc9GyqFPF3lAhnApCetJ4+"
+ "PSkh0hnW6a1rwhb/r1QFBrhChatbFGh2fJ0nyGV5i9+Y5EzB3hdgyupU5m+rulS2DngmhEl/PQ7L7ALIZksbYwi/"
+ "1O5qjRKGf8pp93LhMysP21f2GU/Yl6JX2QZT9139ag1M/Kh/"
+ "pqYTuqrLsE38Lc878Q4kcBX45tklajnY6wGCzqSCGnhDu79SvmgE8Ymw1klxen17FDtgjqJ9ZnkJsd4RoES9kGWyI1zEKFFVH7yq0K2Psl+"
+ "ft8MQjHXNn80H1plnpQ7SRINJZBZFq8PPhRVuiJdO1+byGy/"
+ "J4TLN4jrc0CVIufAa60SOqU2S236nXsF0fW0b1V8zrgEK4qULe7EA+ubk4RxHZIotRffpHmZS5enm/r+ENZL8lECFnnUPbOQ5VhL/"
+ "J6ICCsXykofIKiYGcegY+"
+ "GQD0QvqEXoFDQQeABbIzeGB42BqQRJzMFnBCzHoSaCDOrrmJpvn1aPZoJrdejZ6DRkDLauuNretHKXyqQNvoj6jNZzrhKWLSIoqy4Sk6bec2KGjX2j"
+ "d2h9U1RahlGIR8JkTP1LHmLnq2ZqHNyDgVfDHnHEeimbxrcv8vEtxxR0rJ2t0vL5dAsC4cyZTtBfX3kpOMjVZYH4etX1Ob4mpsLbGX+"
+ "4xhqP8Tmed3gfurVkremgANwmJO0cn3/CHI/5tzsJiN5/MN2zlADJGMUYASvOPNUvxtS7rfFu8dpRpLOQt5nmlx/Trtdomixj/DlHCu0hyK9j409V/"
+ "SpChv3oHn6x5h52IMjULbSyUDYbJigvTJVWJd1CZ9Ui34rKqNp/"
+ "R+q7TtBCioHuTDT0J8m94LO08nQyAGP8ZiS8HRlcfIRorLy4OXrQ7TVaYBC7eXDW8VxkxTJIlBMFQzRykAC4/"
+ "AXBPCIADMlnRr7ClxFxJeu8M6QA4J73WyaWIuRMN3xzcZ9xzAY1xQUMx8gF6MRtB7CdKB/sRSDy0+L0XN7dXhKDtlUykputDeNj4/"
+ "ff0h8YLW3p+aozOh42a44OhPj6IwkGee9R09pnRGPooY7ytNMpoXOMnbZ4UJAwz1yhDKtHV5DJZD+ubjN92SDrAjpDjwjEVmde9F4kbPPUlJ+"
+ "jDIeeVrAOUtmlL0owMk4yhRDYZzLkstHqsLUDKc/"
+ "YWjhPBWwzMcwdDvDJe6ygkj6FSv6OgKCuHVcZqx6McuuYK4dJXpneTnxRyDvzlLdFvnB4ZczLxgYkdQfHjzVfgKILYLWxuDsgHuLJME+"
+ "00HZf3ueP8OH/qF3uhfMKOVzNPBBHlwU4Tb1s48XogYWqBoaR4iTuzVYne6CjbbgAMGi19X8J3C6BImIlu7eOs6O1wtXeYCeR/"
+ "HVljBjQKTcfgT743P9z5ZswpDVb7z2altSnx5opnRPhQnw/"
+ "FEGy1CasmBRUtPBxR0ZJpD7w0AqDrd6PPEsqWJIUqs3IBzXNVN97nV5amhJG3ZXmDwC5/"
+ "myWCszP0pXKxK5j5+LQRuEoZ5SaVWNAP7WIp5zb3m2qNbnEOUlsOCXqDMHakgQWyQt98zLCvB/ekO9/NESy53FxGWlst7/"
+ "P3BYzYalrBwpeWiEtNHXhqWiRhjWyKYghMU2ydsYGuS5huQPUEYxNBjyIuuG3N4UF/"
+ "PJcPfZG7cJCy5HVY6SHA874Cd93biGb6l8GMDoYtxfTT6YTKwWxplNOwI8fppQvc9yuFexiLJFrXWFWSFMxTE+/"
+ "l9nEdCYT5PvG7EayejuXvEXkK2fUWlhm3BXCIcQI4M18kN95NM+fTFTpC9espq12gyh6vIJv4e++"
+ "93SvbldyE262VJzWFu3AXnmCLevgBwJr9Bu5QpQ5GBsDc0sniAxEZIyGrSl0kOvozlZ0D3nqVex8JV2/"
+ "+ms8IocZlfUbDPO8W0t9M3cwdGfXT5Vjf4wVhyX+mRih860iz8Gesub26IUt9xeqk+pAdh5gV+v3rBoYzWrm3/"
+ "fPr5E6LvOqziK+9L2m3nFCEPX02oM79QfX/9plwSH5aMx1iY+LUtWELbOq8p1cLc8tGBWP839Z3RN58D0HRwVTonEbxP6vYFte5Fjhr/"
+ "ljlhI0JpRcqLgJEoaYoWOLgNcrTiCUSVMtHEdY1OfwJlHWOXfQkXGBLjNnseVGEHUe2m2xSG2EyRaC+Jph+jfLlfbu3WPEh1ZgS2sT6R+"
+ "9cn60m2YoyADp1M8ZaJ/huIwbcpiE3fPtVlPgpoiuXUpFpFAPRCdTQ87vK9dQbWwwfgttgbNQW9LwxpbTxtmUdGFzokw9tV/"
+ "joebqeinBw0R2iAtESjbWyQKbhbRO9mJXVzb5IDkVRH6mkVLdkpShRAGd3EnBQH+et75wPZ5HOuSDWFVDg1GuJs3j/"
+ "8vCZI334ABSiPKuvyNGnkWAxxsCAiU+ekeFJ8GTK8qXxRLGhQRiDCM1ly2cB9TXqV3dT5+"
+ "mgXrxbdXUPV8oqFwJhBk4WGMbzghfEMPbrn60wL12Rou2w3SnmHbPOcHhu91ENmCRTN0gaXYzqE77fQ4OUfTwQR5xTtyH2Z5VSZGl6Z5tR3KunrOg7"
+ "27g8BeolNCgMLOgonGQMsuf+Uur/M4vxBMTheHNaKc4roUfB3x0ruRv6LGRygPRigtLOLEwehjYOTK/"
+ "ZpZ4w6Tu9277NAVQlNiD+yOx9lq94jXcmfTw7c0WtAPb3wjVUuaIpODD4MjYTmcuQgd8iRHm1htgXoAJW4dUQ/"
+ "z4benIDjcikAGgxEaWQr1G3pXHlFFuz1uOCv98d+x3wnTVaSerAaj6lxf33IG3LnwCalCL4DkKyLY+"
+ "MeiNtcDxPKvVSooXxB7p8r2t8Ljjawa0SUy9ob1mYPLkuOMyigr3ozWKsBH4RhuCF7hYiGgwMXwSH0HUSkoo+"
+ "NTAEndQNU1ayjzJgnfN6QlXWmspwu5hIbYtSmMmf+YjKHkiDeFcSMaZ742rR/LS9O36UA44vpjX5gtuwRp5Cx3W5ginYwTm5F9/"
+ "xVpYzsfabq+vpIoEKWpSyt75C3Fm0CSxNV8zEvcMEiZL+"
+ "kYXcsIGEWAD5bs66HDjwQ4nylBPXZn28v6E8PU1SKTSTPPK8U2fpbM2HVnnhUJzhAMc6ooMjOB1stiTPbNK+"
+ "t5G6GbguKbW0OH9COEALEpZ9WCAisRJVUVi3umqdzIUuOU0TR1LSPkdw58wUVwNko5Y/mTqET2Ew/8sbb9Lo/"
+ "LwABF09LWdz4rnhjMviM1ScKkv4n7Q2EeI/"
+ "VWskgjS+34Ue0xDDp0+JZfkS3B2xfs4eh9XHkpig95sedAqfDOyHXI4lX10FushcbP5fni6CpFpKxoQheFv8Ek8AHgeRlXSFn8MzE7Gl+"
+ "5bC7iJ2r1ZXh0lUeVDUwXgM7EHQMqTMwB0MxjJLsIWtGpE329hbAJvSvJWI24InXoa4M0jGd+x3ULJ3EUITYiqf1k7c+"
+ "nKYd9jlaWYkZKZb7xHlNcOrVWLYl9HqiiFICQpkcWfbbqElaUUmWWRrC2/RhQZQ/"
+ "1QUXa8KO61bZdH3oK8VgIbo2MGNveROktCFiyrHhGYZ49WCWznhRd7w7CThjXb89BdMyXzA+7kiBkr894Y1sx1gx+tzksmsKvY/"
+ "a4fOkDoClnVV8T0EF8AwvpkAJyBjF+Cgc72iXNxPAPNnqOxvdC/"
+ "ySZmWOPN459ctwNPhhbUpM+nOsGD0VaC1TlAn9Balw66P4ETQH7D1OjaNf7zQ3lc5yhz5Sw3a7hZLDu5QwA+NK/"
+ "7SuE1+xntGd846b1ozytfPvEW7w3paf35d8NpIUFs0fCzF7eG76vNp4hYNy88xi/zGQ080HfGk/"
+ "lojlEpRqjqbLAatkWBUuuaKKdKIVf3ykKHix3+eW7zyY2G4nCaM3fk+eOCp4+v85l37n7/"
+ "pLWhn3X1HA1OvLenhe8Cmub0AT1IDDG4NlstG9VRDoqtXOjizahX8BQXXziAqhdOUMObMrXZlAD/"
+ "ZhuOBxIDlg8EJCUhUzqTGWnX2TOA4RaceCQhIZHzoYTCHvpUtIAOTqdiFOjRPDMuUETNNtAFk8N5vaHqlWlZIltl7wzEd0mJc7DVL5ht/"
+ "nhcvLWEhfOZAZWVMIErnjyy9oJuOVU9GYsiRDkkvRmIBAk7ziRwg1562jDtYskgrPeVm56O9nODyQaEFbDpH+K5KmoWlsAyA3btgkZ/"
+ "Vp0HSePyXS3xRw5Aqa766McOpfDXMQe/"
+ "QEc6+"
+ "gTpjjWPa9zMgmMTOUEYxutV0iMFC86LWjsWuDnj3Uo4V1tNosEPebxYKkePTRxArsVV8AbDlKNkj9Njh5EvrxJv3Ukg5eLvXDZKvcsdOsaYvkRtkC1"
+ "8+mW6hF6TVJ2Aj/NJ4+JmdM6gdVp0QHBaEAgh5CGQ/"
+ "piPFthNmvCRpeDroD8tQD24C6ssTDl6pysRNorhzO261iGCEe+"
+ "uCdhnoBjsjZzfcPYsKEQN3uMR6s17FGESaCbv5Dhm0RzInu8YtUFevKxvAsGoZqiOXxhvA6Amvo2PEWyQXD2lS8a9YB1MvUTkY4aAXQZxynRb6STnu"
+ "bmzl0HewbhXSYQ+qejqdBRxCbFhu/uMNnxZbzGjBVyYNPPMgPtH5glPfLj89arPr12nxVIV4/9CfGiu0STliVhZLTvh4xS/"
+ "7lgXC9NqwD5VfQNcFDn5ybgVbWeF/KF4k7eHA4C3pPTpgEK4UQKqZeUXuZap6xUgmPkHAD2tY5S3KhaeiI1GURDk1LVEuYMqQYg0SszamHEf/xJHM/"
+ "CzmFENCzfFvW6iJw1f/lrMHDdRJmjPRB26/7JPitYpRYWASG1FRb8IzaxqXJ3vFXBn3RMpY5iTt6nk8mWgwLCjqLiTUZPpttGLEgrpeb5e/dD1P5k/"
+ "pIiqfQobv6DfN78w4tcQhpOI0Y0SwlyzKjLjohUmKEeD70ZRZB5CC/bT/Uk4lry+bGdwfdZg8KvCgT0P+/"
+ "ciaPyARWKJxkjyUsB7meweTZncXsOy87kJHhRtK+YeK0Ee0qW3q15L1CcbSWvAG+GdNjzKN9qpFTmGZaOrKUyk+QzuQp/"
+ "jghdOOe464NgK+V4LL1Z5kJFpsBhslpJY24E6i+laEIPi03wFcztYG45zi9H9IwxTEUifoljWEbcnFFYfUh/"
+ "hsAiUnNDdMlsqPwyA+KMnD9kYyTM5EXZ+RhkVebpNeBHjtUSwoTO4Hyeve/DrKs4BMnokPvbCybs0okso7JFA0o9YLq5/"
+ "VMh5Iihxyq3hxfTykJfbGID0TMTbzGQ1zJW8wh322l14VmnNa/"
+ "rSSWit5LsCCqgrlruqIEjD8Myx4nB6uu9Z4goffZKF5vtheukwq2fDEF9IsgoVQu4byzsIccL3OQ/JcXvXwjrrkDa5gqZKn/"
+ "c2pyVwQF0vLf5O+xIiskza/vu2KdAfXdjY2GA+s3GZeBauwyM4PKkgNwb4mdRSIxjPBd+lcu2CfT4YjeGz7Ckn9f1jaNHUEcPqGYtk4x/"
+ "AEwereiXAi+"
+ "CWxhChrjULbs43IDlcwHUyTXiltyCJTyP5KakHGUgQX80mcdUYRw4DZggwoBUgAH3VX9gow3GJgMoRfegqh2fF6ExNxcBDdL5ybHirQN3Dg2RKh1WE"
+ "G2KLKS922uj3qSZ9VwbAp8T5Y11Z0Tp2QataJuSlt1Yvv22SnXBHYTVvg29xdT88ZhC5ICiFqXWLc1x5WgL413ul7oUcQD+"
+ "VZeGFBB68835DbTDoghbmaklEVuS8Yn1bwP+tAiIK3QOFpJ4+"
+ "JhCRqPf2grJEarkcxuKvc3T6EoaGQnSKoOUwj48AozJAH1QGamhV3C1akpqv6f9rtidXuVwx3BAbYAIe7HRNZ+wA0klnmHjkLcsIIOZ6lGbN/"
+ "Iy8luQlV0KS4a1DN7NrognIZ8d5zYZc+vS2qe7V7dxOp5mrKrnUDA+lZYZGVt2IoACYVfmNVkQULqs6+"
+ "FpfLMBWp4Yr55gt1riZCxXJ9bBgMCBzt1PTUVsHzZW+q22S05MMgBKaO89yTEU++46jws+W+7QZxi7BwmEPrpj+"
+ "GuuWxtnBjdqqSsOwoxVendEeLbhOm+cXSDSnXuNQE4STC/mF3DyGrHHpITdw2yLpltGwNnICiTp4slwg0Htsgwd72Ciym9Qvv+rzIwN5WR0PT9QB/"
+ "K5q4Wu2u3yxzXHPF0qwhJjVPec09/"
+ "QeBFkVo8eR1mXG9jZhNcxPPYP10wqAMVtgL65nhzzEDyS9cRoovNExH6LzPz8uMUGhZBuiN1crX5EqkVeJ0WVQbj/"
+ "ixqM9vpxKRG2WNX5A9jfTjgqx/"
+ "LqAo2i9Hf5YPMuJdGH2Sa8cwUMxlNNsjdanERNuhDF7TFeFM97hXGhVRGn4nasYEQmxQYepmf3ZhDOPk5R99gJZPPDBYSB+"
+ "UfGmqkBD1imYvrviGj0KATGpFveUmwa2BHv22gMRdidv75fEbVfpbhvHU+Vj69rWBtrTSAodscfXvmwyOPJOK97Z4hEUA2T+n/"
+ "8x2P8NYjayHJv7HrYID130WV3bOJWgAUGz++314+W7EN/"
+ "agjWDVH78SbiaquPkdVTa4GmBu4hKDNxsZYm7p6ridKNQ6dqKWUR4lAhFyUug0ohYL7DytzCW+36ICW3AsZmoSybyvI5czXNTx0+z+"
+ "zo60gODtb7LbqNb1Ff2ixlc8okhgOomHg7mvAH23t72hgq7Xl9TFk06pKshGpljpm38G62rihKxgidJ1/OaGsrobnwRmEbF6PYDqjp1NAORsG4u//"
+ "c8wKAQlA51saIpVkHDfQemKwGuLsg4t4zXkCpQd2mW1QP2ZzOOS60qG/r/V0DVVFELJzNkOz8B3feH6CjDVgACgIeAlAKYfTS8JizfcCbVCFpv/"
+ "kaJ2sTle34TxH3oMKfJYFJ8kzXqni4UAyF0jAVJOblTbuySNKKkODQJjOa+rmivHGMtMS1o51oKU+vzXGavTm0Op+"
+ "1wTsOrlDbdv1ItenEycdXQnLci6xHT98AgjVmjBAqP5RKZXEEAtokNfLkq9+"
+ "GekgmiOPTtSbazIsuBFM7ZbqrhuNY3ielOPL0hO6PGxgHdBA79KfhDVswMtsPT68L/I+RmaEA571TgzUWRwMC4QlaOoPxJULJxiJ187KeXV/GS/"
+ "e7JpGQo7pAJBZfamo7+OLKeA+QoW3vIWWnKabByiKGgp6rpdUxPGUSn+/ny4UAV1zsNB2jMaE/"
+ "1OVxznFDpgsQ2TiYc3Bi6dzrD93xWn3BVgobD1oFdXynBWLdFn+"
+ "Y0IoN5GLCSElZ4h5IVT9UB54wZ05hC3zY9qOi7VWDSAQ2AeegtNxvYg3i2D4bLt3mEd0+"
+ "3PbhV1DB1XpR3Gdoxr2XYifvicmuxNUCcRcjAvTO82IENMWqd7+/kJsIW8ZJWUzP6BrsRaM/"
+ "HboweCRehOWOEftlwNRYrq00h6gkNqGUNtKl2CDoVqr28Anbn+5uKmKwLgf3Lq3Q3bdD1zadOc5fI7NELLLGZ70WOx+"
+ "EORCVRFDSDoDKQwdixEEJiJZcEhG/"
+ "Sai+"
+ "4iYVrRQ5hwyWKHEVrGcE0YPsT8PN08RDb3HR6L7mPNN5KIaKultAxESCVQMbTYeLKpuoCesrbWn7xtejikKGeMEyLI7bF60PZ3DBNzYF3jnEEcwj5P"
+ "d1sTubA6r+D7tpzaZwSg+5VRD4PLySG7hmT4BPP3QEJvtxfNFTbdW0Yp76tSZJi82zTVY9EgpGZ5t68ZhQtzmkTUmtAQfAuNEEZ+"
+ "UE8Uuq03bEkSlCjlyUVWedQT1Qs3+oT7DwOU8ystBo6HiwEji3c5sfopnwQ8myk3/ADrVWcuCPtIB0E5OtoJ6IpwIW7enMk41vd/"
+ "wdAKi8zFrjslkjZ4q1BKPcw4TR/"
+ "UT6P0WLQhfy1qV2qBiZA1Xjzb0to398aKws8WY0fAtR8OTJTmeyHbo0QlzhkDO5Ui+6YO41lYWC3DLzEdOQYKHHiYNJac08ai9PW5/"
+ "lgZwfa75WrXnrD9C5gBtd6h8S6Mctq/qLhXpw+3B4+4XerXf7P1UXQqsKioSXR82tvF84BmCn5XpeDjXm4drhq/vtgqI7780RqYz8OutXN/"
+ "3WnLx8ru+YpT8/YZqx4B+LzKO1kGxIxP5662vIVA8arp00OhEe+e+Ss5PQTTvStEybK/w9bWhGgZHgQEu365q7TeGXO97XTibUTU/"
+ "hhf899plPuWSf4jJZFbRdTq4OMDsbAi0hDnsdhb8KYjgRE5dnOFqZr3T74Z18OB2D5INP6qBJN57su7mYlLxmsEJlMMuub3KwNUHEQdwk+"
+ "7FACRTTIxi0rTGw5olnZKXOTiGVfI0OSzjSeSCxEruR7IUdNcWVJKc6CsZVOyIdLof/ZxJrlpa67DcoylqDLmh+di06tpVCkXyWjH/PAIC/WXA35r/"
+ "7kmGUilbf2jyWPBbaw84dq+14OyGvRad4T1kRLqIGTb+DB3rWZ+"
+ "cwMgIZskWGNQSCWYTXs3mHN0VrUqNrjNBVwqzyM0Y09bxubyeBwcWOIp4oa3wvm9is95vgvUS4THuKJ0NJjP1hrcqhVLJPyia2hmtqq092PdoMBLem"
+ "yCdIaK95P6twe/+47iwnURsbBkWx6oBy+tRcCFHTW/Weie2H/FwrreTMBWvOQzKqeQ1fF0/"
+ "zUAX01cBzQRyKorHZQmHhEPUoY1HK++VYrR3TgH05KxiSJ+"
+ "NlMFZlGW8zJT47w7fp8olfMnv5AreCesNwNMz6VHBEVgAdUzjvgQoUqho7kgBOz8kwCHIEg+6DXCom+"
+ "VeIPCqEnS627km5jy46vhZQJGeMvwxQ8OxLKtDw6SiEMTFstuJPjJfRiePMsC9nI0U9BxhCIDy89LjUbttuCRpvYg4lzVq8lSFOIzPB2jlk5w9ywVf"
+ "efdKaZBkOfHAycE4n7YzdxyN4EboBQbNcMBNIHblxCSgJ7jFx1b/"
+ "H1HD4CcTfRTcqPnalHkTaqluO9OzH8GfbP6XDTJlMGyn65BMhnIBZrfZwT0uIIFId/p/"
+ "vtMo4xsVIKzivp3ZAb1CdsDxzpYCduJWygwAdpHyZLc2nxmpSJ2dv8wjwLZmltmpTrP6GptTCdSl0P5F8+3gu3U/"
+ "vFjJGAnVuyhototIkOOoYH364DiaMVrGeSMGoZQc3ue6UifdZx8V9VlQywsBP1UqBXgwpUWCruPv8B7QzZnJJHf9cHSWz5xA4TjCymVqQ/"
+ "J9PSBUYn3+H9USYO9rXU2K8dvzFY8iuIf29w8Kdzlu9NV35vDMQCp2j8vZIoJqFgaydNcCDBOU3su6+"
+ "1LHhCGYtnZmJ6rfbymVHRiJPnYAL70iOBI0i3iqbCrNhHEqgA87+a0XIqncjJPwDRPYD+"
+ "uhSSoZEKpFohKVvE68tv0OmSX9UO096qeDemGJf3HyCtRsZ8oSRQBHDccn4JBDEsmq7Dyn8EnbxAm4G5u5GzokrK8tdVdd+"
+ "I70UpLj2T0COYQLS8enk0rSj4m3Cg2x67EPOPbTDfcJC9zJLSfMrYq5J5JjUWaaLqYzqy37IkSdYiLYhAQz5/"
+ "aCNaVW0T4aWrgLIadgP2HCeiYcM3ZXztJ9p3hmExBZG4MKyMzW3K7bTGZZxFxlTgPv2t75cMsZtTJyLB4f09cx+"
+ "P9sRFrU1EnC0iMzgBpvUXYGBsUCjW347TAyCbBQrca3Ntk/"
+ "ssfDODvQNxWYwDpkAl1SvSwp3XcIHZjQ7yP5i6f07xewXCfOH6oWFJV9z9ZzPdEYvGcMlJJZ75tIHDIvvADOGmpUWRnmMrr/"
+ "M7Sp7Pd4unBuTk9TtfYj9pvYesEKYWbUm/rXw4qrZ2msaaVKHfYZRqmx99zlipvzD6ejD10rPu46a8VbzVMKDElVkQpCoCmS5a4wMsk1nV8NR3/"
+ "ZKvMjocpVaFpjKjBziHWWERK8vu7j02FGM7fY+aXro5JdNi5Ikw14QYH9qscorwl9RF9Go/rKm7ax3Xrm/"
+ "mKsOtd5zqr58A4GnRY+lXx7702r1ejQ4EmJWsZnEULRmUbEYLsCIzgwG49EGATewor3QTs2habI3qSPHZpfbZtk7D4ltqZYq/"
+ "lLt+z44D+e+iz1sygH1ypyOtfAM0it9ncbAgPKzel2Lylv9yw26dtsxxmT3/dujL1FBDMAMMTrv91RgE+CtPZ/"
+ "KaXZdqNxmBXzhLQ3bIiFcHPdXAExBYc81/Dj0q2WWfwlUw9xibH+08Ekr5hRFaLk8auTfKfh/V599caOfKc2pw2RmLLjpuI+/ilqM/"
+ "YFb3RlNB5C2TOxWyQW6cEy5FrQ58AFfm4eAC8K3XuzTH5qLj32GhI4Du2Kwtqp+7k/mZptSJE4bO7pw3JD03kafimrcWF28t9x4TTtyDLUXbdxRJy/"
+ "q+C/eCIzhcBSwjokFuyD/E+yifABEVQ97jOVFCJWJ2KHU13vajRPyaa/t6COrUideYto4pX6jB8aMBiJ/749UK5Iu9XW1hxVrXs/"
+ "F8RbPrGzI+0hLE7Wp7Adwrd1kZOs/"
+ "VshtUIVMUt3fYTRSWGlK9DJ4U7B5+ASMn6OGXibPR+CjPCEOn0AnofaliR0zzf9pao1Mcclx0dBpg0IUjWNDu89De/"
+ "kccVxzmkh9DapZSq8Z57B3wtZ91T93KPNljzPLTbShwp87xMABC0W/j4VGmiB6/"
+ "UYYDG6DqEfZ9p41X18kH+dAI4cWFvlWSIEXfZcqo4T4T2sZm3bI9u48v3ESECI3v19ZF9fTWUEiEVCWQ6tZZxquL+"
+ "KW7aQZB8tYw9PmiB89ifP4bguhdf3y4t+iJZTg2byG3YecCDlcidosjcSXLXz8udeHDxbtVfDEYb7PzO4GYMDIAIevJ3froG3jygY3mHzh/"
+ "uQor+v1No9+7tCHrbuBLn2JzGbz/rIraN8y228siXpA6NPZ7eXIMZSmsYCBgfFrtxN7TMVAQrKWzBY2HS3O6HrBkGIlskUXzXI/"
+ "feeBiXUFaKvAxBoIyGKuCbirseFNRjmC6Lq/wh9+/xwjN/aDG/GXr9qzQdNQtT51hILFCg6H4/"
+ "gRJnwLeWX7j31raA8E4HcSaJldlsYlSVYzh7qLWdhmuSjJo8fK88wQRyTKiR2jykS/PCd1U0OCylFA6YqtwQNknw3HTbDVLGdH9FXHPV14/7DB/"
+ "JximCbtPRlO6WTo/qls/"
+ "Rl4vdwvKo4avT2LBALxm21vrFFeNjV97vUgthq+r9Tciemw3QxyOIafJeLue12Bv07MUS1VLklfaAn+XhHj21exq8RiVLL2vgT+"
+ "CAe4OYwjE3BPHO71L5NCCizIOBShWno6j+ndo0k4Y0FApp6UXsLgv4sQbrRp7eW8rJVlstEWP8nlhbtn5GiQlH2qFLfkdP3Djq46aHXfao0+"
+ "Q3SbTtw5lZMJoapqO1pcx9C5XQ6vZk64DpqXNWVKgAaVrM7aCNB9a7tcWwU2yVICOtPyDaOPg4874Bhe7sGUh/ONA1GihrnX/ikNYSZ7R+gtfZmZ/"
+ "AICmbhJ+nBicOMMfOmliJTTvS6Cae3X/Tp/bM6JjT5paEVX/NvDqlOWc7bjvLyIB9vTAUHSYX7bZITgXAnMfjN2ODxA/"
+ "KE0trGknKA5nRyeddqbSn4z+vouYydv48OHj7EYx9ntEZNVi8qNtEkOVIJaE3HMKxAq2A/kUSrxiFXynB3twj/D76c6AE3blSNgf5/"
+ "eviDke7vg0wAwbW2NBOT7HKvwdIesPLb00Q1xli78Gr2N6rN/cFxF3k7olYethpJXt30kyPRc56wWR/"
+ "jPLrWl4lChCf6JiWjjJT0ZYQV1x3hODAnVye7UFout05YlwDvzAw9XmbCUl868Sz12gLfYYD9YLjemZiR7wykpTS2lxaoWMWyZPo4CC3Gi8D3cxBfR"
+ "1aYk/S4KShnnbUkTfidQ/Eicc2z54i/BUUyeCmPort+siOZOizzsOl7HOMmLQGzB7jVACOEga4bsa4F0FpShXXuqTWvOP/oUlvxlqlKZLb5+1aoN/"
+ "hlgFwlnKDKh/P9HFAa/OrrkkqIxHOj9YzBZIoWeRna/"
+ "4aHx5mqlAHIqD9edVVwmSVhHMTl2fvcfDYWiyPbVgK5cKo7Gyw0AQGlBs62xYgEKAT+RLnqhuutHWHyWnlNK2PRnrR43+BwIWYByBG4LL5uituRr/"
+ "oMPHgJtc6KM0JqT9OeeQt7lksCDtN4mey3SNo5VU1ng3qwytI+t3KLveG9vL4Qx1BNRuP4Kz1UqRrbriZTM7bz7L/"
+ "HjnbjIGIg9c20pitmqJHKO71x2hA1mZ6PMH/ziDwkfI3kej3F8gqSKazY/"
+ "twjZrf1dn0DqG+"
+ "P5nI9oGw6Kf91o7gRclhv7pHqCKGRMwq8Grf6rpW6VlSnOisoKMmd7hE4hcGy7BH5VUTZkGUsZ30meBJgMySM2aty9dCHFbedF87+"
+ "kWrzWlzaTAZCGmb5Y5b9Afob/9oPbgK5IbDb9NPyhIStJiY5B313bCvrxPDZnqqyLkJ/"
+ "mAnVGXARrPWxV9x7NfnK07iVUZQ7Eh2x4kwKFJKpuIcG+GKpMPyh3FMI4IP0QJDL9e2sHH7CmtZoxtuCOZcLE8MfQ5/"
+ "+aYdvfddw+MN84IWiUMdnHWSx9mOjDpZbWCze/CVLgfIgscA1KOyqB83KUV1krfvv7ZpliagXDQBpkBHJR3TdIGynxcoKLMWqGU/"
+ "AjtjP1CZyFFlw1i0rrLfRICJUjWc156EhBzPh3yvTJOOrJ+mDNmFl/tvGRhvs7tZyWTGgNNejHoxK3HS50fRaaZ1t50jKiyEWIUD3fz7/nEZ/"
+ "jhcgmYWw7ZNZg45r6D7p4QCbaSv4ug8OjRmojMzDkZ4bD/"
+ "+Xxd35Der1XB8iacTdNtfu1BioT8P+eNSK9NITrVtbl+CeJkj4hm+gOSdKsMtfR5QpGTmklfZRVm9IR//"
+ "BZTTu35XJk1Iso+DK9UGXQzDpVSChouFo7Qt34cyNWfgT1mq+vYbPcDIOwrIVb34KiaxO+tP1f0+"
+ "uARYTyImrJz2IchLlIL4OtgKDalcVfUPS0IcUzvxguC74NMw4ZR0tO+"
+ "XmB4E5Ob5qNzA291p5ZsIn6blF4661oDE6CcItyEJD8z7fhWKlQnevgjSC5+E7yzTH/"
+ "OHZBobmNyWF4+Gk39c90YqtUVskfURQ1rjiBSr84Cako5vZKI0VW0zg65Q7vv0qujh6tMqMJDTTChzQTlmZFm+eMI7vSFMb/"
+ "qte9NHiqajYG5fqcmubCwqdsxQeJMz8Byfi3CqfP0WcoyV9HX8X8VRkF69maJzRHNSmUC/"
+ "P7WoUW79gAwIeg69OjuQpZrPdybJTNTZ0c5+ijSXGSA+rKNDNB2Xa6uCWkkNtfMjZwB/"
+ "PaNu2lAlXkuKtEDETuW63wcWTHMMadwVS7uVZ7tOfiOxIbRxfCjp095xqA+9Mot+qF0i4qi0/"
+ "VUmjaP8xkuFYXmcNESOFKtOINuQxbGk+wX1xxrWvklvIlLOeEY1OO4kOe4VLg9TEf48Pr/FEHnnejXG6ReVHI/"
+ "uSra6XCzXt4y8i7bPYsNXUlsKDtKkynjclWKWq1CuSGJ42QdRDqrIg3cob1gOVZgjgFFloEqyZ3bzq8QX2XP2Uf9FpFMh6n9E3rJI5TE0DO4lNEOLs"
+ "dDi11yGFnMPxhI92P3bkSqTiqS1gJvoPZsIIsJanaQMD6WLDY7ajr6j+"
+ "tIgehqVBPLUgbDEKOKy2acIknat7v3p5tWUob3F2I9HUDDQ2vdvxeDzVC18QssZx1OmxzQ6FYLcdP+"
+ "YXaw796hGoKcGLjUb68AXMX5d6cwwrgQFMYzDsGxh5Ev2jRe+3fxmQclO1iOOSwhAOrW/oLRLideFyZz5taRjhg7RyEvbSdLY1BJyxJTL+AKk9/m/"
+ "+QR1RUGQYbUOn4xYD3mTpeBHJ16rXvRVZbQ0T5M097mG7dJpb8TMLl18kSKJLeOdsD3No4UqhHz9kB5oH4iCh4QI84YN8urLb19KblcI7QmRVF4UZ6"
+ "zZWf02teRFSflUmgXrnQak/psMttftzsU4/"
+ "8hWqNu6vdDBHnXyJc196LEi7OGY9si68feBHDtAn+wfdPk9OsYMueq0Dhgc0Gt0XOkUO1MhzqZwg00bW/4VOVhO/2cq6YCB6l8r/"
+ "KPxjHZOBAcob5FcA9bkBRqEDttaIbf4WZPdxGx6nDjULZ30NJkHc8j3CaTG3sLyCxPXk2pnMmjtT8csQzzRniVas4PbQfhPXySsWPCJ+"
+ "pGefADdEZK1nprxxaOd9r5y6Db41UxuFqf8iqWvsH0tFZ57WJoSgrrMLCO13pH5w2emK2GWaPWH712QRvipEbKIRUnHJqCURLF8pKDOg4gHJcazCcU"
+ "tLil/EPgYmlVk6jyw5VNbZI5sn8LLSApe1EN+J2v7oMkWEzp0VHLJzKc/AiWLp+c+CUOBlFBU2jO+f/bMzVCgqNJWYGbCGdX2jXha0MBl+W/"
+ "IteUFsyaqZcyZLE2qQ2HK8677mUNJFSoaPvSTmxpoeHZnjHluuIcWIfWYJ4O8ZlXmMS1krUbZllung93W25Yc0DqE26HLZAT/EcU/"
+ "iuxXMSVt9mzpOULLmvmQloPIWvn25LZBZaIkAD62a/"
+ "V+rydpVu58OmXiD6ev5470oBCrES93AOrg+"
+ "xUVjrpc34gr8SnXcRAGeOTY4GRWvAOxq9gKhQhyF7DseiM7azcXzXKx7sdN05puCIUN13JaDmorKy42Bpd/X/"
+ "GYXVZyWO0BIJxs35T3U0RdAAxOzALwD0IxazvK/HhXfQhrb2frFmRVmXVzvbbMwx/4B3jnBNCQYIm0Km+hpCymTlBYJQLHMe/"
+ "RqSfEkbh62ofxy+SRsMYArcGRgltqM0tP0+rmz8cO4pGZXXL0b9viD/BlcIhgvnj/w9EsFB80A8/L3JkyJbNG6SSgTH7viJrTdAv3BE6/"
+ "xVE0IWDPqoU/NnGc2/NJ7//A5GzhpaVlIQV7MuLZXW+mwjcT8yl36JGef+3l+x8/"
+ "X5+wwGVmMGwSIWvG5Gatyq7gm0VNOQyWHGVKFysBBgF335vpkK7JUfI7WY7YxpgNYX94DwrSOjNtkORKbY4qfEj4w+"
+ "iA4cCFQNOZPfk9TBM9MjOTKAF0Ky0MhVE9/"
+ "72U9RNAM1+rI8mImTJpCZN67JjoBohbuB91rC9LFR2o95i4tgS7uHbABj67rDO+"
+ "2P11E6su0r0b8zXkHa6sk6DGYlyUPiD7j40uV9jTX3hfsV1D7iLypNpXWVjvDTfhDW92Upp50H8nYb4XyVFHo2BL5KrZHpsHYM4FFJZfcwRX2n0tDj"
+ "sCVD0jMVKBeom3CtKMtFFkFYfwZE/wKdTEjCdvZ51IDUteDLagSQtdFm03qch/"
+ "1jJoMneD5hV1EbB1l7EoGY19Sj53Oy+TETDCurHCO1xMT+3yTTdphK/"
+ "QweO9f7oUhuOW3aqt9lE+mggbHYOo3cHDpSKCTGst2buBJiPupLoXtcV7w5bpRluTIVt/"
+ "DXUqhNkZ763RJ0CCHX8DP2ARQIU+OL0og8NTB4pdpncOFiUkmhD4YOviNTn2uh9naxuc2YPP8VfASUDRdN+"
+ "d3MWUj4vtkXdGHKP1P7dy6KonOgss2kCT1eO5NdEEWGjCtMD7CsM8X+YVZAdZU7MGOamtzBGzeZzyP+jdBJ3eLl0Sqi73YkGt68sKSQ3G3Rr0sube+"
+ "Pud/8LMP9PzEg13AB6Z92cG0V8WmIn8KbOKRXziWNXg4rWNNYW5GE/Zsobxka4RufAhUCsz2JADImveFx0sn+N43Wd/"
+ "KmzbNQbsimMhmXlad70CXFaRc6CUP1p9vb2zwokW5T1UJ0zUgbDy1BTM6mfnmvWD4weTavswLUXonIxhSvxEP3OJXWDY6lfWYO/"
+ "hANyGHJRu46lD4m0KHMomR3PLCuo5mg5EGTdSe+FifRhM8P9gvpZ5TyqF4J3GJSlCudp7CmfSuhgPN9fYWIb9B0n04D20sPvP45grRvNj5sg3Jak6+"
+ "bBhLdqGyNl6ePO0RHUy5woMIVfM3cWvc3WfVwWYj4I+Nb8JafMo/0WO2fe0XjUjZVmzS9I5ZyrxTjBKl5VTQart+qc1hU15LaD4U+/VuuaF7/"
+ "qWtnUzZjRB4kv/"
+ "TIaP9mVO2SQfR5aYjqlWRC6hHkvjt1h2WHRqSSZq97EUmBH+"
+ "IaTQiuvuL9zMIPUCcL4TTLtot0C3e1u04D7Ur7P5GI6xfSw6teaxNcBYiQnVzpL7IHOZWYvvBGZNPznx+"
+ "JcjzM1q5lmEYr6sZNXuPM8x6wOsL7uzGGc3ej3KJrubXSJ2lxlogDNMyfa9Fgb2y21BlTNPDiSzgVODmNjr6GiTP6Pi+"
+ "991ivIirGVKe9KHaTq755IJzqscUkFDu/"
+ "TppL59J6seMaQPrqKs+JwQClvxVbeGdKsoFL2RVOeB5dvnDZ6yaHUq2RtW9QjyNxb7cderCh5ry+n5+5y+d+"
+ "EUsqnjaa8Rr83nQpgf81lOk3ty4KPSBAtOE+2l8Z4Vmk5GDGS3YBcqCv5/tqUN7rUxJ/ZkAWR4qixomL+u2y2/"
+ "UOhyK4wvUXxauXseraOFROoZGHyVMkeLoroi3eYZh3qsJqd+uIBlSkor3Cu+RYtNaEDvGhYQRmRnuDo9DbVf5SRt6lzrj/"
+ "w3cvMUJxlhlStMWUuGyjw4bV306JsOQvZeZa3NhUTnCCC2zBuPQyeUw4xZlD34nzzNIozP1JtU/"
+ "kFfmCZp50EpLqyVm00JD8V4omUP+k6XFgBTp73thc5Ys2fbXJiE/wL1OQ9yAQ/"
+ "jcZ0iO9nFHYNHcZpTFzGlb3LZ+V99u9GcIOjxdJCVv+eBMB21ZIvzMHDg9gUI09EvJy0nHLEkGpZ0Uxq/"
+ "vl6PPAUswq4GcrmjDFP3dqWdqY5ZavZjZiTiRRFYFZL6++fdeCUtQjVbQjI1B/b3BDq3ltaPUKGMm8j5FlwBo8OjUkYEtrUE4Z0QpCiTKAslT9gNh/"
+ "jZyNwhM8RwcYF5p5TT4oIHkayKac95NuSmH8HHw1dWfJQkRHTfoxMZ5W3LSAU6L4418P5D8rdbVS+"
+ "QkoiYExjomgDVk8D8EwLYaQhgKOCD15RC6R2KOy/VOSsOLLEaBOdZBeuVcnXFH7wfE4WdprH2N0xaGKpsSX2t7INGwxAwWltbUzmXaN2Q8O3HNo/"
+ "VVhpLqaoBucAVOgif0C6Q9vDDCXP+PbO2HB6uxudk4bHfkGOpCs1FvBndk8eHdBqBLLpJqicv1uh3Xz8iRpNXun5BH8SpdDJjXQMXNdN/"
+ "R8nE1MS3Zt4Ec9SVSZLvFG/23y3O1A1lLynTE0Llki+ns3pnby00kR4297Waimb4iBLTu3LLIvSr5TArjGOmDQ3lRRlPZoB87Fg5wFSvd7HHKU/"
+ "WKAzU4YiCB8Plc8+JJo9oHxugmsyBEnsnJYMLkyNxFzfvtKDaKHyv96e5jgunUjOE5cPl45hlK8OOk+"
+ "v4bPB8PqanUFPPzPi2PZSZ8aF9ISb0zCnGdXVzkMQ7aapmCWBcN43LnUcDbUHNiPZI3sZsPJnJtX8WEXgzBPNA5hjpKCezPzb+"
+ "rkMCvLzZIiboYwJzPIxWAZ5tFtMDwmKjyDVQA93krtINXoZF8QOHFYTzBW8zemPGapWPa0coAre9+9KJNMQRssSg+y+"
+ "mAudtnpYAeOhjgK2kA9wKjQytRhXl2wlR13QoeqhAQBhomlPOpScfly4iXskQFF2A2JTblUAQv5ZGl/"
+ "LyQB0Dcmd6VRcx3tk888wtL7K3gWKXk21NFI14Zja4/"
+ "3QH6rInL3Et002Yrujl7A83yZntGZzZ75hfgKh22ZEZFimaaD07dR6xv9iONeU+WkuOFJxnt3ER5XzX2Gp6BFgREujNF9/"
+ "kx9GP6hn7ZHTmceAesR84VT0Wa2pEVGnACbLAk0YucJXUmWT4yctFJyNUVfGibO53wVLat/iiANFX9u9Hdp5KxBgekgHXrNyx/"
+ "Msw4S53e73nMckP8BuRALtGIkl5Bgo6f3lMZPENtpku4NTqXFZaeFgjaOy9frPeDDDOinmP6SmdOsi8ekF4jAU9ix4jAYtS+E+Jy9baVQP+kF/"
+ "gx9MQkFwVuZCpM9ZxgteElStBY13shhfCaeufGIQe0UjUGs5VdnmFxiSwVxME+"
+ "fwkKxRMazAC9ycUGih0r0R9mpIUC2bzOmezrtLzUccJVyCg1q48ew3xGAfN5whzowPGS/"
+ "Lz1QHs+"
+ "mt2QGoDjtpZSSHyMcxJ4zcxcleXUWv9LLTfRkcyqhR3TG2OJYeA1FelKS42lDuaqrMbTET0XC4oUjYzUQAu0HBnJsa8gseM1YI5R85LknFf3rQYg82"
+ "DO1GJJEUngKeEb8RB6uvCcJ4xs6t5+X7vFEg/"
+ "1bWl82jRsCji59AFMuDZGBQ+nr3jKTf3wFKtJvPD5bMxPdbYdN9nkyn5yooMLQUtJXhSHgy1SpCoW/UshSbOU+o9/"
+ "i+Qf3BbhgC36kU0zH8KyPlq0U7a+jJc0BCyuZEUD1FlHBl5Iu5opdPxXB29NzDUIMozrfy+iGJ+e0wZUbkV/"
+ "25q+7PHiBQs6mV2iSPOxSK2aGKxv4Ti/Pav/4WsO5OyiFt2cPJkmMZaQh8mqvcY+1ewSp9rRiM/"
+ "yHLSExz0mMN3ENGeQGsSwNjIzuIJfX5SXI7rsRMLJUTKnbWKcNT4hYthBcMkmLbGZph5i35+qiZvhSkaRCrhACNM+MLHkO28TFyRyYvB4Ev8ck+"
+ "Pj76loJ0CU4JcsvIwdY7adXWEacSNPfP9oWAyMd9OUUoem0duSRK4Nsu1Cs3mQUITE6top4qEs2qG3eregjwDQYqSQWPXjOCop2VN1YlUnsv6d8Uum"
+ "nFmZkKvFWZBeUTs90VuEe9MWQpkj8kiA16R+s/uKByZP4vFjlBeDwn/RcvxL+K6fALw8e6QTE+80ZzXN/um85Tasidi8AiRS1JtOh+5Z65/"
+ "gesyNh9GnhONOJz2fNr2QmWJ87VfTmKQqhLI4eSOTDB4dgRaVOAbyfh1slzYQQFwfh5PBqixs/oRj/V3gYfW/"
+ "kXqcCfINgebEbhef8mqBejMpwzZfF9Blptc1h93Z2oKzESD+gYaxpSnzcVdXN38QY/"
+ "pBNkgDsok0+qsh7pe2Ansr2bPTcj2wLDkqBwHW3M6xGYowXWB43b8CGbLjDcGG3QQSJ3vjCOK8JqOkci2d+"
+ "5YihgrYEBcPBRevkOHqxejYHdxRGTWNhNWPDXXqFEyS6iP+7x1tawvC6An702tqDGf3ADMYP5CVSCdH0UcwisOHXjb7aH+PmQFCqn/"
+ "YYB2yvOwRwj9K1jB9T9n+0ZASF3c71tyS7cmwIfYw+Xmmkg1SbQEXKibnWxtnX2urZlRACJGv8zKCUeK8/"
+ "N7zUJRwNteIrerYy+giNU2dFtfvwQTK6uk+"
+ "4jsuh0J8ovZT1AaDKetaFgBJ1xmWCoZZZJ0wuNJbfzOFfnavTlmDJRlqAXPeQ1F6mogTwkSSmPKSjpG/rqDhQ3AwK/"
+ "hKnYkJafUcyvU0VTIapAgxXM1bPj+atxRfNBaa8h8+emoqPOIbDWYuwt5ObCzjcsyd401t0sUIoy43rBPRVrR+64VP8aafisJrb6x6/"
+ "kT5MtSSdOTY9n+dK+fbaf1j/EHfEPI2zyZWwQdyRgxo0eqYnuTcNQLOxS0k+DQYD1C557d3ELuKKopZGUyvgWP86oWPX4DwzqK3uqkMilJaeq4/"
+ "vCNP6+viUSIonmSjTxDZZj/WJR86UOlnWebpYmSZwEklU9tX6ScaaScfzTmjPe/yXgo3/"
+ "6GIC4XtXl8b7sb6QEbec+8c5+iJXrNhrYu6Bc99S1X7RVwFV9TjqAwRwjmZQtNZiK83/"
+ "UNj54ZwrHU2W5TSOCJ6Zr24yZzFkeyNvFzSwj4jeFrc0GKjRPHEJwXzAv4w4uvgADD5yRoNLNq/"
+ "xNLFEsu0Mt8hjLkEIBI1JLXfkfHsKZccjtJGbjRwiI8cFvc1PeKER0qeywNq/tL8Rzx/iXDeuqavrogwfLOnR+aRN8N66LZys/6rAcsSTNfHoXf/"
+ "7uiJS3rjuzcuWpPpxaPSJV3f8ZL8++duB80E94r4vaBJSHQ946FRyU11aj2x2mnyTl8o8oWndW5UcvFJjvTeBdCYHUTusH9G7FPTG9/"
+ "efdh1uHnXABe8EwdihKnAAGA5Glws9rbA3qjebj8q17Ade+M+01LIInfq8vpPY6Fn9s8kqqv9lC4rRmgECgAMQ86R9bbymaS/"
+ "8qKlzh22OGztZl2b77brFtvXxSQ7+u2gV8I8nef0A2SO/7OxxDRb+KiRgstoB5zMIEF7GdTZc5cwF+7Zf3qlsffJKE3/"
+ "2Pma4ITXT+OecySK3WV2UpkeYwiUduPXxq/LfnWK5IiKYtvBpeT0LVX/"
+ "9fXrvBcwUjUn6NOCfkwpUj2JjVC5cPeMESncTJoExArTrhTTD+gHSvBXuamgUhmnLR+cXc7VWV+x2Pj5ZvlJcpAO0V1GEya94o4rneF9vJ4kWCKda/"
+ "z/L3lEguEh8mK4ZuRPgeWiOKg3fQsSKIU2bVW9Kqs/avpAnrsY5Xov4bVrV4KzaLQkp8wZCwgpoAYWAFm5s+rTwuM0TPas8eWsSb/"
+ "7swP2H3rVlIEtb8wIODDBvWOw3Fn6KoqG+WkjGR1oF3YlqD2x1AswpyH1x29n52YSuKDT7LXIUyNfeX/"
+ "vRIU6jtq+DxE93u4cngBwDOVzVAQq2j5kflq5+"
+ "Y0JY3Th7JvFinclWhlQrZWOFmMpQOB9tObkt8fWn5uXVMgUX4QWtI10mw7TqYcvkzpfcz2FJyYN54OAbtefRGmbvGqqPZd+"
+ "lodOpyMGe7RKKUBJG8LMBtOkdmpsy1tjxsQe3BAydXH4ldwjgyWaw3SRryJIxu736T+wK9AoFLTpE4dxnunRndFzQRzTZy20iTmX+"
+ "3FD6ENJhfcuFW9E1w1fA982Dy+jh9EEOIe1KJe8TcLW/UIeeUFQYyhqLkEEW3cCj8/"
+ "CoK49aDvfanO0QhdCAqQf9WH0MsGOJEL5w1OcRvchd9HgXGeyvg3DVM8vQzqCSwC87OU2REEr9iRMSfcxUAVVhcoV0N7UBceYgUsiPqR6pXnH6oPPB"
+ "AYI9aBYkogPTgb1o0+WFRw9gyx0D6TG0L8h5t54EpMpYm2qVZejvw9+"
+ "Ey34KOlfTMJaL0wxenVYen0JDgbbeg7KyHA31Gnbz2iLnH2rFE3kPAj0XjW68SNY0B7u2XMJUs1qPFwBhLGEwRwN4Coo5iUo7mNn75yhMNNnZtqoEt"
+ "5JbV7kExbML4ho24GjazqZxeslDyJJcdb4NCq06FDIHO/1o4eK9Ui/"
+ "t7+rWD2ChLfTiDr7wV8XgqBqHbTNagFN3Ij7OK49yCsFfPu7U54MMwxRbslqVwsG+"
+ "Frqokly2o8iTQVGa7JEqJR824PuMbTYhobKR23pJRAEMDr5oImipjO8/"
+ "pPyeNRTBdx2oVnRc1JDITeDwlJZcrUNojHKuEZTLXx9UjomT1GlgzJYjmxhQrklLiu+"
+ "LVjSHsIWdElLYFFG6d0JlM292TpgsmOUapwEHNrEogdcaaqPqcyvsKdUNFF2WqgZBo5ZtBeP0blD2StUjwrAJmcdDNFruhyuJZ4Unowg667e20RDp5"
+ "5c3eyMqZ28mTZ3IbD/g5edMKDtkToX2QIrx1qk/HmBa6dNPMIpf3iFmT1jZx9mjQ0hE8A/"
+ "w2JW9rFPQQU8eFHXoIt5bDQ0Won66YodBTzHtGAaNxd1h1plysaM2AikXqd8R4NOntJ2mwwk7mGV+RBZ70pTj9mpwJOYGAvFLSFnb4/"
+ "2x6GQIJyCMYi4R7HgbgJE7ZOXHACNttN1swtJeeKyyHalHcRnp0qxUZnc7jSsPeSO6YdY1PwG3CRf3zAP8o+"
+ "yU6n11wsweIu39N5MeL4CoCaR7tfnU+25I0Q86M8GIBGyqAdLYJfBD8K9Hp8R+aKCAOaauUCYrm/"
+ "KSYFGffiVA2ZTWd4Mjgc+DP+BkBHDAQH3ckMVz0RYHlV6N7rKPBAH3vcZaqyVH+ROdSyGeYy8pxXA+ohoYqjKtdnqBXBwCFTnK8+/MNmwIljErYj/"
+ "mDy+j+"
+ "dB5QiIpe1tiaDDBrvD0GoQcC1nrBbC7mIrYAI13LhH55KzMUmLR5TpyFQaVsq1oEyhVpW1ReYm24FSViucw6CqxGWkX95LL7llVgamAYRhc6bwpmcm"
+ "9g0+pcTktPmKRhaiYes4dvkHrVtpO84cCYBchHVReSA2Rx63Rk3Vzmxzon9CN+ghrzLDcqpC+52B+Qj4z0fvJY3rE91VIGDUbtsoO3ajxId3+"
+ "sjHp5Y0IiCvgZhEeW7BVe+45U/h6mUJk53HBaJhutNBBRklF7Pcl8LWXWkGBZ0IiwgeT9SxbIxK9mFf1jTS5DWhF3pvq11/"
+ "3r4vTBBTDsu1Uw3IVQjJTe8fmvM+AJc7pD9hFfEoDSbaIWFJ86BIDV3uhWcQPxKiXMq829Lg9s33oni5gicFDceYPOVM/"
+ "xEGJzz2Gpsx9GQQXpEHiJDFCzIJLbu89mvrAToeetKlbx5Bz36WNZ3tQEp7QZjltZFOWAzcT4ONg+Jr+9aeXqqCYVus/Mx2V0DWg9v0LzLplYGd/"
+ "J3cevTtWsmXD4nyrhF7xggXzhjC0PxfdDTHvw4kE+SkXHJUE+qGGPaa4ESd1YiccfC7C/"
+ "N04+EV6hsW4s9IqhnzLwaq3LH9KDXToipOcgsbydKC83tcGC1sdfB30FVAkRZ64Q+"
+ "8rYyHqQYeizOkMEPsw11rQfm0inegLdbhIcWzW7s1343bu1u8g23GverRCKBL1Yu0Nz31KrMOwn0VkdFC4qn7W3PypUpieztKHGSUHu2q4mp/"
+ "YMHTIyff/2+XsvXWDeW+HD+CQykMSJreicbirWbRgtlDRyBJ7p288kJdZ8KYJzxtBesu1VZ3m0gk46/"
+ "LiIIIxlge3Pie+OoVla8uCpc4IQUkhfx6aD5UY1WECRfKrYw0Q+kd1bdm4WinXRWEkG8icUHghPElrYys7FLqqBJ1sRym9fmxCoAnyVm+"
+ "ay8w0THD9pIK41zMpiIa84kEuIQ9fIaQHPNmKnD+"
+ "VZokWdm1SbLUr4cxRyPhRrOQHHTGrXsE3o0ahx1alE9QU2q3doijOuGH0Jb0qih19OrdDO03rbS+Y47jEd+"
+ "eOyQcvDzwWfgsPeO2XbsnOF0ajYYMwodOqn7zSJqazVgd6xOo9y0VgGPyWt0ZsZhmb4h1PwJaO3BDuiZsQod7YMdkadil0rS6R9u6001tHVhwdtXGJ"
+ "v+UGFOqNySiew7IXiBeQd/LNsCPLi9ij0peDgkBecJRpKNJY9xkeLNl9Mzb7++jY37uUQUUB1ZdPdDKvEEPZP0gXRkC48iaIY+Cn9BaBZD+Topksw/"
+ "mGyGuYPYng3aS86DBPM7DEiM/Q8sCGhqWCRbY3mMejFEb090/eVAJR8eKp0yKztmcHQlvEvhP9nU4/"
+ "x7+IDgMUBcTCLYX5lO4FtfY1FkqaWE1h2OPND7YqMX22vqpyw6C5SalKPS7w+RxKlOQIpIBgn/"
+ "WBfiKftJZaBLwvzyYwkwi6rqx9sdVoA2THGEUapo3XSg4PJMg9xQZ0ebdma6Xai5xRAao3MOUBHzFDp1TtogGydcDGUzINqVv+JRlyMMCs+"
+ "BDwd1NhSrT91b4+8pZvNsGiNSrtcjZGNBU0KnGRt29b26mASwJ2eHikKnh/"
+ "Vy7F4Xjl+8tOwfrGEHoZpOyyMDkH7jAv+DgsAls4QmuCX4D6uQhFHLtZCSMMEr4phG/"
+ "Vl+ijApCRvMBuFWA6768J5YRnwsakj+uvCwEUfECB8A3NhfA3FY8ybv9yJuCTNuGYt81onqqXJM/"
+ "7hMww7f2ZSJPk7K8HBITJ+eiqIfDQMNoN8mJYpvRORI5/dftZSY/kUQT2/"
+ "mv9xK7qT660lVUK8XfIkMwHuLWQxY1roKACFuGJoVkax3dHqSEjkpvzFjIVXlFgPuB/"
+ "jvW2VGaGnaKyTd+ViD4volcecrmmqKYzfsxiReiD1TTdmudYLjfsmqNOH7tBou7s5EFMLJwKWQoW+"
+ "ZyXOJ2wZerCV6l4uCTsUAUhYEt34ssrxp1eTh2H0L+Uq+6YX1E/"
+ "EpGZCxz4hOpuwW7GemEf3nF0WI1dQ9GAr2gwUB7xLCAeSo6XM6V5RyU1nrVM6Py1JI6xe1uk3X2zH5R1rrA+"
+ "eHZFeEAutRhnG1Ptca2QMwrnBPRKusOkF4HL3XdJKIgCXW4WK0+"
+ "QyF28B9UtPVgarzkEOanc70CTC5kJgx0f0YU2E1gHQtvGZcTj8pUCsOudHqeVQGCWl0DbhMjfZ/4/"
+ "OoXI3oeOykBetHFDIA37MlGvZ5vpVL7OQwLaPk5qJ4L0NS5ZE0rypA+qrLzD6jhWlRBCkcNJXNi8/"
+ "mtZC3U5btyQ2M610i7cvgaY1P8CbFv5CS0bBblTBVlQR/"
+ "vHUmKA+BHGL9xnezUiGdQOP9jut+8rFta35AQz19Cnp5XNlRgreJFgFcuyyz2r0ks3Minfa1shk97zoHE+Ly8r23sIwgJ7NpRvIsg0A+"
+ "1hpp2rmOhpnWogq+ISx3sZzTgddeP8COZo9c0czbc9lraqfhHL3wOqutD6u89AS204cfM8oTjSIdV+"
+ "UA3NRpPV9Tsyo8nx10IIoRVMLrlrL5vGyt8pql5cCq2DUxWCfF+MNUPKlDJyb6ANJmZiWCP5VNapsQN1CfFUiDt9w9JKL8crZbRYkzIw4sy/"
+ "Zx5f57K50Oqb+zqykkFjO0opeHQ2/B2gRl5TEodpj5aB/z91lXE8y1WOxCmyOjrAul718DDr/oqBuTW+HwaJpx15nMZfC+mN4g3KT/5zye/"
+ "vPoACRcOksgTX2bDXuVx6BL7M0bewTUTa/J3eLSzK1ulhXGb/hUZnSUWUcn4FY3p6c/Axj9kae8AAh4AnOYYyjllzoaCloGZqoFKg3KXl2Z/Egp/"
+ "uiikZLaGHJK2SaL1zkxP+Dghknt0kEGJp/na/1JmYQVYZZ56kjtEqG38Jx/JTy1p2+a/q/fh6Xf6rvkXqSpEuNlNK0YTDkgHBkmXSTcn+njTzR/"
+ "HcCS4Wkktg7Fwb7VVpN2mWAMdiU4nnOes0e5/ETvGYh5vFGYKtTMAFr6nGORLVNauds4hb+nTlmQtQQq/ZDkpnSSw1+L/"
+ "yH4rpNdUsOVxeI+O7iObPbxoxoDAUBBb5s+HjoJ+NwxQGVXQJDVCG3oey3S9iDCThtE3LMlAP/"
+ "HUgg6GTY1D01o0ITi9ttFBnTJjsMw0sCeJxsMsn6GsAdqy6M3W7T1QsJCbbp3U+UCV/"
+ "dv1fgLJrz9dgY9ihWQnejzXcpXaqTM8sLAGZKr4Spqy98rMweMrjc0MhC8YKPk48daHp5woc5aJbtNLvNAbiBJDingnTKJYSa9bfZRMsnQdJBs/"
+ "Ksl1F/Dw9MTOhyvL/sTg7uCJwEcZGN5xoPbxlYbEEXu+yxzt7x0fry+hBrZrM/"
+ "4PsXkc2jGErOBgjv80Bmm1DeE0djpdHgLfjGwsQH8bTkXy1zNsbfQC3lptfXVQ4HTP1lA5/tv2jGDsU6ygQjdu+7Q2+tobC15ovL+/"
+ "7l9WfjdVuYp9DgInepSH8ujZKzGdKgDho4LE//9aUgste9TH88dhvAVwoLbDfMGl9I7wXRgGz3Z+3uH9Pmz9frCvI4D0V/"
+ "kKgJzAcFrmVPRWtJktxdJ8JEUdyORQpjDH7992Hwz2DpUGyI9YFovjziSLO4igHHYSWFD6JS9ePy2n0WFDFymghm/XW2ssm629dd59SGz5gGNaLm/"
+ "FPj386FpIR6lhZ999R2Fpf79H+KO4cwF8m/"
+ "EhXWk8irqHcAdQO5ctI7IiAARLkkpJCrS0Xnm7zZncjDmWBbsJXmW4yMCzTTeTZButuh30wuC1PJWlgvdVQASe0oM1jybVfCRlRn0jMRWRcyvNROvo"
+ "DutinVezcIAX9Ktp7C3d1JSd0uh6VTaD6L3l+ZAKA5by736BeBMZfSVqe2G3j577QqiVDCKT6D3uNA2x3r4cnT8oWdGcuy297H15I83LLp+1/RDo/"
+ "745K02B3ejdgO6adv3G3DEio9yWEyaIQfFSL12J3Xt4JG0hhD5d3GpjizCzTx+SrBcdZJ/"
+ "k2PwBeYFKhFTAWQkhDDdyAY0sMlGDQ6Ev1KaT1TFqcOxd22FRaNCj2DLwK70uFsnJAt6vzcVDxrxqI=";
+
+const char *sqlite_sample_db_v4 = sqlite_sample_db_v4_arr;
+const size_t sqlite_sample_db_v4_size = sizeof(sqlite_sample_db_v4_arr) - 1;
diff --git a/protocols/Telegram/tdlib/td/test/data.h b/protocols/Telegram/tdlib/td/test/data.h
index c447d5cba6..07d863a894 100644
--- a/protocols/Telegram/tdlib/td/test/data.h
+++ b/protocols/Telegram/tdlib/td/test/data.h
@@ -1,15 +1,24 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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
+
+extern const char *gzip;
+extern const size_t gzip_size;
+
+extern const char *sqlite_sample_db_v3;
+extern const size_t sqlite_sample_db_v3_size;
+
+extern const char *sqlite_sample_db_v4;
+extern const size_t sqlite_sample_db_v4_size;
diff --git a/protocols/Telegram/tdlib/td/test/db.cpp b/protocols/Telegram/tdlib/td/test/db.cpp
index 8917dd65b8..61757e9893 100644
--- a/protocols/Telegram/tdlib/td/test/db.cpp
+++ b/protocols/Telegram/tdlib/td/test/db.cpp
@@ -1,17 +1,29 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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/db/binlog/BinlogHelper.h"
+#include "td/db/binlog/ConcurrentBinlog.h"
#include "td/db/BinlogKeyValue.h"
+#include "td/db/DbKey.h"
#include "td/db/SeqKeyValue.h"
+#include "td/db/SqliteConnectionSafe.h"
+#include "td/db/SqliteDb.h"
#include "td/db/SqliteKeyValue.h"
#include "td/db/SqliteKeyValueSafe.h"
#include "td/db/TsSeqKeyValue.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
+#include "td/utils/base64.h"
#include "td/utils/common.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/FlatHashMap.h"
#include "td/utils/logging.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/thread.h"
@@ -24,144 +36,239 @@
#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)];
+ CHECK(0 < cont.size() && cont.size() <= static_cast<std::size_t>(std::numeric_limits<int>::max()));
+ return cont[td::Random::fast(0, static_cast<int>(cont.size()) - 1)];
+}
+
+TEST(DB, binlog_encryption_bug) {
+ td::CSlice binlog_name = "test_binlog";
+ td::Binlog::destroy(binlog_name).ignore();
+
+ auto cucumber = td::DbKey::password("cucu'\"mb er");
+ auto empty = td::DbKey::empty();
+ {
+ td::Binlog binlog;
+ binlog
+ .init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) {}, cucumber)
+ .ensure();
+ }
+ {
+ td::Binlog binlog;
+ binlog
+ .init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) {}, cucumber)
+ .ensure();
+ }
}
TEST(DB, binlog_encryption) {
- CSlice binlog_name = "test_binlog";
- Binlog::destroy(binlog_name).ignore();
+ td::CSlice binlog_name = "test_binlog";
+ td::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');
+ auto hello = td::DbKey::raw_key(td::string(32, 'A'));
+ auto cucumber = td::DbKey::password("cucu'\"mb er");
+ auto empty = td::DbKey::empty();
+ auto long_data = td::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)));
+ td::Binlog binlog;
+ binlog.init(binlog_name.str(), [](const td::BinlogEvent &x) {}).ensure();
+ binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("AAAA")),
+ td::BinlogDebugInfo{__FILE__, __LINE__});
+ binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("BBBB")),
+ td::BinlogDebugInfo{__FILE__, __LINE__});
+ binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer(long_data)),
+ td::BinlogDebugInfo{__FILE__, __LINE__});
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.add_raw_event(td::BinlogEvent::create_raw(binlog.next_id(), 1, 0, td::create_storer("CCCC")),
+ td::BinlogDebugInfo{__FILE__, __LINE__});
binlog.close().ensure();
}
+ return;
+
auto add_suffix = [&] {
- auto fd = FileFd::open(binlog_name, FileFd::Flags::Write | FileFd::Flags::Append).move_as_ok();
+ auto fd = td::FileFd::open(binlog_name, td::FileFd::Flags::Write | td::FileFd::Flags::Append).move_as_ok();
fd.write("abacabadaba").ensure();
};
add_suffix();
{
- std::vector<string> v;
+ td::vector<td::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"}));
+ td::Binlog binlog;
+ binlog
+ .init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, hello)
+ .ensure();
+ CHECK(v == td::vector<td::string>({"AAAA", "BBBB", long_data, "CCCC"}));
}
add_suffix();
{
- std::vector<string> v;
+ td::vector<td::string> v;
LOG(INFO) << "RESTART";
- Binlog binlog;
- auto status = binlog.init(binlog_name.str(), [&](const BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber);
+ td::Binlog binlog;
+ auto status = binlog.init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber);
CHECK(status.is_error());
}
add_suffix();
{
- std::vector<string> v;
+ td::vector<td::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"}));
+ td::Binlog binlog;
+ auto status = binlog.init(
+ binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.data_.str()); }, cucumber, hello);
+ CHECK(v == td::vector<td::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();
+ td::string path = "test_sqlite_db";
+ td::SqliteDb::destroy(path).ignore();
+ auto db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
db.exec("PRAGMA journal_mode=WAL").ensure();
db.exec("PRAGMA user_version").ensure();
+ td::SqliteDb::destroy(path).ignore();
}
TEST(DB, sqlite_encryption) {
- string path = "test_sqlite_db";
- SqliteDb::destroy(path).ignore();
+ td::string path = "test_sqlite_db";
+ td::SqliteDb::destroy(path).ignore();
- auto empty = DbKey::empty();
- auto cucumber = DbKey::password("cucumber");
- auto tomato = DbKey::raw_key(string(32, 'a'));
+ auto empty = td::DbKey::empty();
+ auto cucumber = td::DbKey::password("cucu'\"mb er");
+ auto tomato = td::DbKey::raw_key(td::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");
+ auto db = td::SqliteDb::open_with_key(path, true, empty).move_as_ok();
+ db.set_user_version(123).ensure();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
kv.set("a", "b");
}
- SqliteDb::open_with_key(path, cucumber).ensure_error(); // key was set...
+ td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
- SqliteDb::change_key(path, cucumber, empty).ensure();
+ td::SqliteDb::change_key(path, false, cucumber, empty).ensure();
+ td::SqliteDb::change_key(path, false, cucumber, empty).ensure();
- SqliteDb::open_with_key(path, tomato).ensure_error();
+ td::SqliteDb::open_with_key(path, false, tomato).ensure_error();
{
- auto db = SqliteDb::open_with_key(path, cucumber).move_as_ok();
- auto kv = SqliteKeyValue();
- kv.init_with_connection(db.clone(), "kv");
+ auto db = td::SqliteDb::open_with_key(path, false, cucumber).move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
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();
+ td::SqliteDb::change_key(path, false, tomato, cucumber).ensure();
+ td::SqliteDb::change_key(path, false, tomato, cucumber).ensure();
- SqliteDb::open_with_key(path, cucumber).ensure_error();
+ td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
{
- auto db = SqliteDb::open_with_key(path, tomato).move_as_ok();
- auto kv = SqliteKeyValue();
- kv.init_with_connection(db.clone(), "kv");
+ auto db = td::SqliteDb::open_with_key(path, false, tomato).move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
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();
+ td::SqliteDb::change_key(path, false, empty, tomato).ensure();
+ td::SqliteDb::change_key(path, false, empty, tomato).ensure();
{
- auto db = SqliteDb::open_with_key(path, empty).move_as_ok();
- auto kv = SqliteKeyValue();
- kv.init_with_connection(db.clone(), "kv");
+ auto db = td::SqliteDb::open_with_key(path, false, empty).move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
CHECK(kv.get("a") == "b");
CHECK(db.user_version().ok() == 123);
}
- SqliteDb::open_with_key(path, cucumber).ensure_error();
+ td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
+ td::SqliteDb::destroy(path).ignore();
+}
+
+TEST(DB, sqlite_encryption_migrate_v3) {
+ td::string path = "test_sqlite_db";
+ td::SqliteDb::destroy(path).ignore();
+ auto cucumber = td::DbKey::password("cucumber");
+ auto empty = td::DbKey::empty();
+ if (false) {
+ // sqlite_sample_db was generated by the following code using SQLCipher based on SQLite 3.15.2
+ {
+ auto db = td::SqliteDb::change_key(path, true, cucumber, empty).move_as_ok();
+ db.set_user_version(123).ensure();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
+ kv.set("hello", "world");
+ }
+ LOG(ERROR) << td::base64_encode(td::read_file(path).move_as_ok());
+ }
+ td::write_file(path, td::base64_decode(td::Slice(sqlite_sample_db_v3, sqlite_sample_db_v3_size)).move_as_ok())
+ .ensure();
+ {
+ auto db = td::SqliteDb::open_with_key(path, true, cucumber).move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
+ CHECK(kv.get("hello") == "world");
+ CHECK(db.user_version().ok() == 123);
+ }
+ td::SqliteDb::destroy(path).ignore();
+}
+
+TEST(DB, sqlite_encryption_migrate_v4) {
+ td::string path = "test_sqlite_db";
+ td::SqliteDb::destroy(path).ignore();
+ auto cucumber = td::DbKey::password("cucu'\"mb er");
+ auto empty = td::DbKey::empty();
+ if (false) {
+ // sqlite_sample_db was generated by the following code using SQLCipher 4.4.0
+ {
+ auto db = td::SqliteDb::change_key(path, true, cucumber, empty).move_as_ok();
+ db.set_user_version(123).ensure();
+ auto kv = td::SqliteKeyValue();
+ kv.init_with_connection(db.clone(), "kv").ensure();
+ kv.set("hello", "world");
+ }
+ LOG(ERROR) << td::base64_encode(td::read_file(path).move_as_ok());
+ }
+ td::write_file(path, td::base64_decode(td::Slice(sqlite_sample_db_v4, sqlite_sample_db_v4_size)).move_as_ok())
+ .ensure();
+ {
+ auto r_db = td::SqliteDb::open_with_key(path, true, cucumber);
+ if (r_db.is_error()) {
+ LOG(ERROR) << r_db.error();
+ return;
+ }
+ auto db = r_db.move_as_ok();
+ auto kv = td::SqliteKeyValue();
+ auto status = kv.init_with_connection(db.clone(), "kv");
+ if (status.is_error()) {
+ LOG(ERROR) << status;
+ } else {
+ CHECK(kv.get("hello") == "world");
+ CHECK(db.user_version().ok() == 123);
+ }
+ }
+ td::SqliteDb::destroy(path).ignore();
}
-using SeqNo = uint64;
+using SeqNo = td::uint64;
struct DbQuery {
- enum Type { Get, Set, Erase } type;
+ enum class Type { Get, Set, Erase } type = Type::Get;
SeqNo tid = 0;
- int32 id = 0;
- string key;
- string value;
+ td::int32 id = 0;
+ td::string key;
+ td::string value;
};
template <class ImplT>
@@ -172,13 +279,39 @@ class QueryHandler {
}
void do_query(DbQuery &query) {
switch (query.type) {
- case DbQuery::Get:
+ case DbQuery::Type::Get:
+ query.value = impl_.get(query.key);
+ return;
+ case DbQuery::Type::Set:
+ impl_.set(query.key, query.value);
+ query.tid = 1;
+ return;
+ case DbQuery::Type::Erase:
+ impl_.erase(query.key);
+ query.tid = 1;
+ return;
+ }
+ }
+
+ private:
+ ImplT impl_;
+};
+
+template <class ImplT>
+class SeqQueryHandler {
+ public:
+ ImplT &impl() {
+ return impl_;
+ }
+ void do_query(DbQuery &query) {
+ switch (query.type) {
+ case DbQuery::Type::Get:
query.value = impl_.get(query.key);
return;
- case DbQuery::Set:
+ case DbQuery::Type::Set:
query.tid = impl_.set(query.key, query.value);
return;
- case DbQuery::Erase:
+ case DbQuery::Type::Erase:
query.tid = impl_.erase(query.key);
return;
}
@@ -190,93 +323,93 @@ class QueryHandler {
class SqliteKV {
public:
- string get(string key) {
+ td::string get(const td::string &key) {
return kv_->get().get(key);
}
- SeqNo set(string key, string value) {
+ SeqNo set(const td::string &key, const td::string &value) {
kv_->get().set(key, value);
return 0;
}
- SeqNo erase(string key) {
+ SeqNo erase(const td::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();
+ td::Status init(const td::string &name) {
+ auto sql_connection = std::make_shared<td::SqliteConnectionSafe>(name, td::DbKey::empty());
+ kv_ = std::make_shared<td::SqliteKeyValueSafe>("kv", sql_connection);
+ return td::Status::OK();
}
void close() {
kv_.reset();
}
private:
- std::shared_ptr<SqliteKeyValueSafe> kv_;
+ std::shared_ptr<td::SqliteKeyValueSafe> kv_;
};
class BaselineKV {
public:
- string get(string key) {
+ td::string get(const td::string &key) {
return map_[key];
}
- SeqNo set(string key, string value) {
- map_[key] = value;
+ SeqNo set(const td::string &key, td::string value) {
+ map_[key] = std::move(value);
return ++current_tid_;
}
- SeqNo erase(string key) {
+ SeqNo erase(const td::string &key) {
map_.erase(key);
return ++current_tid_;
}
private:
- std::map<string, string> map_;
+ std::map<td::string, td::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;
+ td::vector<td::string> keys;
+ td::vector<td::string> values;
for (int i = 0; i < 100; i++) {
- keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
- for (int i = 0; i < 1000; i++) {
- values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ for (int i = 0; i < 10; i++) {
+ values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
- int queries_n = 300000;
- std::vector<DbQuery> queries(queries_n);
+ int queries_n = 1000;
+ td::vector<DbQuery> queries(queries_n);
for (auto &q : queries) {
- int op = Random::fast(0, 2);
+ int op = td::Random::fast(0, 2);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op == 0) {
- q.type = DbQuery::Get;
+ q.type = DbQuery::Type::Get;
q.key = key;
} else if (op == 1) {
- q.type = DbQuery::Erase;
+ q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 2) {
- q.type = DbQuery::Set;
+ q.type = DbQuery::Type::Set;
q.key = key;
q.value = value;
}
}
QueryHandler<BaselineKV> baseline;
- QueryHandler<SeqKeyValue> kv;
- QueryHandler<TsSeqKeyValue> ts_kv;
- QueryHandler<BinlogKeyValue<Binlog>> new_kv;
+ QueryHandler<td::SeqKeyValue> kv;
+ QueryHandler<td::TsSeqKeyValue> ts_kv;
+ QueryHandler<td::BinlogKeyValue<td::Binlog>> new_kv;
- CSlice new_kv_name = "test_new_kv";
- Binlog::destroy(new_kv_name).ignore();
+ td::CSlice new_kv_name = "test_new_kv";
+ td::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();
+ QueryHandler<td::SqliteKeyValue> sqlite_kv;
+ td::CSlice path = "test_sqlite_kv";
+ td::SqliteDb::destroy(path).ignore();
+ auto db = td::SqliteDb::open_with_key(path, true, td::DbKey::empty()).move_as_ok();
+ sqlite_kv.impl().init_with_connection(std::move(db), "KV").ensure();
int cnt = 0;
for (auto &q : queries) {
@@ -294,41 +427,82 @@ TEST(DB, key_value) {
ASSERT_EQ(a.value, c.value);
ASSERT_EQ(a.value, d.value);
ASSERT_EQ(a.value, e.value);
- if (cnt++ % 10000 == 0) {
+ if (cnt++ % 200 == 0) {
new_kv.impl().init(new_kv_name.str()).ensure();
}
}
+ td::SqliteDb::destroy(path).ignore();
+ td::Binlog::destroy(new_kv_name).ignore();
+}
+
+TEST(DB, key_value_set_all) {
+ td::vector<td::string> keys;
+ td::vector<td::string> values;
+
+ for (int i = 0; i < 100; i++) {
+ keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
+ }
+ for (int i = 0; i < 10; i++) {
+ values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
+ }
+
+ td::SqliteKeyValue sqlite_kv;
+ td::CSlice sqlite_kv_name = "test_sqlite_kv";
+ td::SqliteDb::destroy(sqlite_kv_name).ignore();
+ auto db = td::SqliteDb::open_with_key(sqlite_kv_name, true, td::DbKey::empty()).move_as_ok();
+ sqlite_kv.init_with_connection(std::move(db), "KV").ensure();
+
+ BaselineKV kv;
+
+ int queries_n = 100;
+ while (queries_n-- > 0) {
+ int cnt = td::Random::fast(0, 10);
+ td::FlatHashMap<td::string, td::string> key_values;
+ for (int i = 0; i < cnt; i++) {
+ auto key = rand_elem(keys);
+ auto value = rand_elem(values);
+ key_values[key] = value;
+ kv.set(key, value);
+ }
+
+ sqlite_kv.set_all(key_values);
+
+ for (auto &key : keys) {
+ CHECK(kv.get(key) == sqlite_kv.get(key));
+ }
+ }
+ td::SqliteDb::destroy(sqlite_kv_name).ignore();
}
-TEST(DB, thread_key_value) {
#if !TD_THREAD_UNSUPPORTED
- std::vector<std::string> keys;
- std::vector<std::string> values;
+TEST(DB, thread_key_value) {
+ td::vector<td::string> keys;
+ td::vector<td::string> values;
for (int i = 0; i < 100; i++) {
- keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 1000; i++) {
- values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
int threads_n = 4;
- int queries_n = 100000;
+ int queries_n = 10000;
- std::vector<std::vector<DbQuery>> queries(threads_n, std::vector<DbQuery>(queries_n));
+ td::vector<td::vector<DbQuery>> queries(threads_n, td::vector<DbQuery>(queries_n));
for (auto &qs : queries) {
for (auto &q : qs) {
- int op = Random::fast(0, 10);
+ int op = td::Random::fast(0, 10);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op > 1) {
- q.type = DbQuery::Get;
+ q.type = DbQuery::Type::Get;
q.key = key;
} else if (op == 0) {
- q.type = DbQuery::Erase;
+ q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 1) {
- q.type = DbQuery::Set;
+ q.type = DbQuery::Type::Set;
q.key = key;
q.value = value;
}
@@ -336,12 +510,12 @@ TEST(DB, thread_key_value) {
}
QueryHandler<BaselineKV> baseline;
- QueryHandler<TsSeqKeyValue> ts_kv;
+ SeqQueryHandler<td::TsSeqKeyValue> ts_kv;
- std::vector<thread> threads(threads_n);
- std::vector<std::vector<DbQuery>> res(threads_n);
+ td::vector<td::thread> threads(threads_n);
+ td::vector<td::vector<DbQuery>> res(threads_n);
for (int i = 0; i < threads_n; i++) {
- threads[i] = thread([&ts_kv, &queries, &res, i]() {
+ threads[i] = td::thread([&ts_kv, &queries, &res, i] {
for (auto q : queries[i]) {
ts_kv.do_query(q);
res[i].push_back(q);
@@ -352,7 +526,7 @@ TEST(DB, thread_key_value) {
thread.join();
}
- std::vector<std::size_t> pos(threads_n);
+ td::vector<std::size_t> pos(threads_n);
while (true) {
bool was = false;
for (int i = 0; i < threads_n; i++) {
@@ -362,7 +536,7 @@ TEST(DB, thread_key_value) {
}
auto &q = res[i][p];
if (q.tid == 0) {
- if (q.type == DbQuery::Get) {
+ if (q.type == DbQuery::Type::Get) {
auto nq = q;
baseline.do_query(nq);
if (nq.value == q.value) {
@@ -402,25 +576,23 @@ TEST(DB, thread_key_value) {
baseline.do_query(res[best][pos[best]]);
pos[best]++;
}
-#endif
}
+#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();
+ using KeyValue = td::BinlogKeyValue<td::ConcurrentBinlog>;
+ // using KeyValue = td::SqliteKeyValue;
+ td::vector<td::string> keys;
+ td::vector<td::string> values;
+ td::CSlice path = "test_pmc";
+ td::Binlog::destroy(path).ignore();
+ td::SqliteDb::destroy(path).ignore();
for (int i = 0; i < 100; i++) {
- keys.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ keys.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 1000; i++) {
- values.push_back(rand_string('a', 'b', Random::fast(1, 10)));
+ values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
QueryHandler<BaselineKV> baseline;
@@ -429,34 +601,34 @@ TEST(DB, persistent_key_value) {
int threads_n = 4;
int queries_n = 3000 / threads_n;
- std::vector<std::vector<DbQuery>> queries(threads_n, std::vector<DbQuery>(queries_n));
+ td::vector<td::vector<DbQuery>> queries(threads_n, td::vector<DbQuery>(queries_n));
for (auto &qs : queries) {
for (auto &q : qs) {
- int op = Random::fast(0, 10);
+ int op = td::Random::fast(0, 10);
const auto &key = rand_elem(keys);
const auto &value = rand_elem(values);
if (op > 1) {
- q.type = DbQuery::Get;
+ q.type = DbQuery::Type::Get;
q.key = key;
} else if (op == 0) {
- q.type = DbQuery::Erase;
+ q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 1) {
- q.type = DbQuery::Set;
+ q.type = DbQuery::Type::Set;
q.key = key;
q.value = value;
}
}
}
- std::vector<std::vector<DbQuery>> res(threads_n);
- class Worker : public Actor {
+ td::vector<td::vector<DbQuery>> res(threads_n);
+ class Worker final : public td::Actor {
public:
- Worker(ActorShared<> parent, std::shared_ptr<QueryHandler<KeyValue>> kv, const std::vector<DbQuery> *queries,
- std::vector<DbQuery> *res)
+ Worker(td::ActorShared<> parent, std::shared_ptr<SeqQueryHandler<KeyValue>> kv,
+ const td::vector<DbQuery> *queries, td::vector<DbQuery> *res)
: parent_(std::move(parent)), kv_(std::move(kv)), queries_(queries), res_(res) {
}
- void loop() override {
+ void loop() final {
for (auto q : *queries_) {
kv_->do_query(q);
res_->push_back(q);
@@ -465,55 +637,54 @@ TEST(DB, persistent_key_value) {
}
private:
- ActorShared<> parent_;
- std::shared_ptr<QueryHandler<KeyValue>> kv_;
- const std::vector<DbQuery> *queries_;
- std::vector<DbQuery> *res_;
+ td::ActorShared<> parent_;
+ std::shared_ptr<SeqQueryHandler<KeyValue>> kv_;
+ const td::vector<DbQuery> *queries_;
+ td::vector<DbQuery> *res_;
};
- class Main : public Actor {
+ class Main final : public td::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) {
+ Main(int threads_n, const td::vector<td::vector<DbQuery>> *queries, td::vector<td::vector<DbQuery>> *res)
+ : threads_n_(threads_n), queries_(queries), res_(res), ref_cnt_(threads_n) {
}
- void start_up() override {
- LOG(INFO) << "start_up";
+ void start_up() final {
+ 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))
+ td::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";
+ void tear_down() final {
+ LOG(INFO) << "Tear down";
// kv_->impl().close();
}
- void hangup_shared() override {
- LOG(INFO) << "hangup";
+ void hangup_shared() final {
+ LOG(INFO) << "Hang up";
ref_cnt_--;
if (ref_cnt_ == 0) {
kv_->impl().close();
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
stop();
}
}
- void hangup() override {
+ void hangup() final {
LOG(ERROR) << "BAD HANGUP";
}
private:
int threads_n_;
- const std::vector<std::vector<DbQuery>> *queries_;
- std::vector<std::vector<DbQuery>> *res_;
+ const td::vector<td::vector<DbQuery>> *queries_;
+ td::vector<td::vector<DbQuery>> *res_;
- std::shared_ptr<QueryHandler<KeyValue>> kv_{new QueryHandler<KeyValue>()};
+ std::shared_ptr<SeqQueryHandler<KeyValue>> kv_{new SeqQueryHandler<KeyValue>()};
int ref_cnt_;
};
- ConcurrentScheduler sched;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(threads_n, 0);
sched.create_actor_unsafe<Main>(0, "Main", threads_n, &queries, &res).release();
sched.start();
while (sched.run_main(10)) {
@@ -521,7 +692,7 @@ TEST(DB, persistent_key_value) {
}
sched.finish();
- std::vector<std::size_t> pos(threads_n);
+ td::vector<std::size_t> pos(threads_n);
while (true) {
bool was = false;
for (int i = 0; i < threads_n; i++) {
@@ -531,7 +702,7 @@ TEST(DB, persistent_key_value) {
}
auto &q = res[i][p];
if (q.tid == 0) {
- if (q.type == DbQuery::Get) {
+ if (q.type == DbQuery::Type::Get) {
auto nq = q;
baseline.do_query(nq);
if (nq.value == q.value) {
@@ -572,4 +743,5 @@ TEST(DB, persistent_key_value) {
pos[best]++;
}
}
+ td::SqliteDb::destroy(path).ignore();
}
diff --git a/protocols/Telegram/tdlib/td/test/fuzz_url.cpp b/protocols/Telegram/tdlib/td/test/fuzz_url.cpp
index 74047135c0..0a1e880cbb 100644
--- a/protocols/Telegram/tdlib/td/test/fuzz_url.cpp
+++ b/protocols/Telegram/tdlib/td/test/fuzz_url.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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)
@@ -17,7 +17,7 @@ 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()];
+ res += alph[static_cast<td::uint8>(c) % alph.size()];
}
LOG(ERROR) << res;
return res;
diff --git a/protocols/Telegram/tdlib/td/test/http.cpp b/protocols/Telegram/tdlib/td/test/http.cpp
index 98c94b2e8a..2498c53e5c 100644
--- a/protocols/Telegram/tdlib/td/test/http.cpp
+++ b/protocols/Telegram/tdlib/td/test/http.cpp
@@ -1,10 +1,14 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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 "data.h"
+
+#if TD_DARWIN_WATCH_OS
+#include "td/net/DarwinHttp.h"
+#endif
#include "td/net/HttpChunkedByteFlow.h"
#include "td/net/HttpHeaderCreator.h"
@@ -12,39 +16,40 @@
#include "td/net/HttpReader.h"
#include "td/utils/AesCtrByteFlow.h"
+#include "td/utils/algorithm.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
#include "td/utils/BufferedFd.h"
#include "td/utils/ByteFlow.h"
+#include "td/utils/common.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/detail/PollableFd.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/port/path.h"
+#include "td/utils/port/PollFlags.h"
#include "td/utils/port/thread_local.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
-
-#include "test/data.h"
+#include "td/utils/tests.h"
+#include "td/utils/UInt.h"
#include <algorithm>
-#include <cstdlib>
+#include <condition_variable>
#include <limits>
+#include <mutex>
-REGISTER_TESTS(http)
-
-using namespace td;
-
-static string make_chunked(string str) {
- auto v = rand_split(str);
- string res;
+static td::string make_chunked(const td::string &str) {
+ auto v = td::rand_split(str);
+ td::string res;
for (auto &s : v) {
- res += PSTRING() << format::as_hex_dump(int(s.size()));
+ res += PSTRING() << td::format::as_hex_dump(static_cast<td::int32>(s.size()));
res += "\r\n";
res += s;
res += "\r\n";
@@ -53,30 +58,33 @@ static string make_chunked(string str) {
return res;
}
-static string gen_http_content() {
- int t = Random::fast(0, 2);
+static td::string gen_http_content() {
+ int t = td::Random::fast(0, 2);
int len;
if (t == 0) {
- len = Random::fast(1, 10);
+ len = td::Random::fast(1, 10);
} else if (t == 1) {
- len = Random::fast(100, 200);
+ len = td::Random::fast(100, 200);
} else {
- len = Random::fast(1000, 20000);
+ len = td::Random::fast(1000, 20000);
}
- return rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), len);
+ return td::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;
+static td::string make_http_query(td::string content, bool is_json, bool is_chunked, bool is_gzip, double gzip_k = 5,
+ td::string zip_override = td::string()) {
+ td::HttpHeaderCreator hc;
hc.init_post("/");
- hc.add_header("jfkdlsahhjk", rand_string('a', 'z', Random::fast(1, 2000)));
+ hc.add_header("jfkdlsahhjk", td::rand_string('a', 'z', td::Random::fast(1, 2000)));
+ if (is_json) {
+ hc.add_header("content-type", "application/json");
+ }
if (is_gzip) {
- BufferSlice zip;
+ td::BufferSlice zip;
if (zip_override.empty()) {
- zip = gzencode(content, gzip_k);
+ zip = td::gzencode(content, gzip_k);
} else {
- zip = BufferSlice(zip_override);
+ zip = td::BufferSlice(zip_override);
}
if (!zip.empty()) {
hc.add_header("content-encoding", "gzip");
@@ -89,22 +97,19 @@ static string make_http_query(string content, bool is_chunked, bool is_gzip, dou
} 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;
+ return PSTRING() << r_header.ok() << content;
}
-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 td::string rand_http_query(td::string content) {
+ bool is_chunked = td::Random::fast_bool();
+ bool is_gzip = td::Random::fast_bool();
+ return make_http_query(std::move(content), false, is_chunked, is_gzip);
}
-static string join(const std::vector<string> &v) {
- string res;
+static td::string join(const td::vector<td::string> &v) {
+ td::string res;
for (auto &s : v) {
res += s;
}
@@ -112,10 +117,10 @@ static string join(const std::vector<string> &v) {
}
TEST(Http, stack_overflow) {
- ChainBufferWriter writer;
- BufferSlice slice(string(256, 'A'));
+ td::ChainBufferWriter writer;
+ td::BufferSlice slice(td::string(256, 'A'));
for (int i = 0; i < 1000000; i++) {
- ChainBufferWriter tmp_writer;
+ td::ChainBufferWriter tmp_writer;
writer.append(slice.clone());
}
{
@@ -128,30 +133,50 @@ 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();
+ td::clear_thread_locals();
+ auto start_mem = td::BufferAllocator::get_buffer_mem();
+ auto start_size = td::BufferAllocator::get_buffer_slice_size();
{
- auto input_writer = ChainBufferWriter::create_empty();
+ td::BufferSlice a("test test");
+ td::BufferSlice b = std::move(a);
+#if TD_CLANG
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunknown-pragmas"
+#pragma clang diagnostic ignored "-Wunknown-warning-option"
+#pragma clang diagnostic ignored "-Wself-move"
+#endif
+ a = std::move(a);
+ b = std::move(b);
+#if TD_CLANG
+#pragma clang diagnostic pop
+#endif
+ a = std::move(b);
+ td::BufferSlice c = a.from_slice(a);
+ CHECK(c.size() == a.size());
+ }
+ td::clear_thread_locals();
+ ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem());
+ ASSERT_EQ(start_size, td::BufferAllocator::get_buffer_slice_size());
+ for (int i = 0; i < 20; i++) {
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- HttpReader reader;
+ td::HttpReader reader;
int max_post_size = 10000;
reader.init(&input, max_post_size, 0);
- std::srand(4);
- std::vector<string> contents(1000);
+ td::vector<td::string> contents(100);
std::generate(contents.begin(), contents.end(), gen_http_content);
auto v = td::transform(contents, rand_http_query);
- auto vec_str = rand_split(join(v));
+ auto vec_str = td::rand_split(join(v));
- HttpQuery q;
- std::vector<string> res;
+ td::HttpQuery q;
+ td::vector<td::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());
+ LOG_IF(ERROR, r_state.is_error()) << r_state.error() << td::tag("ok", res.size());
ASSERT_TRUE(r_state.is_ok());
auto state = r_state.ok();
if (state == 0) {
@@ -161,11 +186,11 @@ TEST(Http, reader) {
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);
+ auto r_fd = td::FileFd::open(q.files_[0].temp_file_name, td::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));
+ td::string content(td::narrow_cast<std::size_t>(q.files_[0].size), '\0');
+ auto r_size = fd.read(td::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);
@@ -181,23 +206,26 @@ TEST(Http, reader) {
ASSERT_EQ(contents.size(), res.size());
ASSERT_EQ(contents, res);
}
- clear_thread_locals();
- ASSERT_EQ(start_mem, BufferAllocator::get_buffer_mem());
+ td::clear_thread_locals();
+ ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem());
+ ASSERT_EQ(start_size, td::BufferAllocator::get_buffer_slice_size());
}
TEST(Http, gzip_bomb) {
-#if TD_ANDROID || TD_TIZEN || TD_EMSCRIPTEN // the test should be disabled on low-memory systems
+#if TD_ANDROID || TD_TIZEN || TD_EMSCRIPTEN // the test must 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();
+ td::gzdecode(td::gzdecode(td::base64url_decode(td::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 query = make_http_query("", false, false, true, 0.01, gzip_bomb_str);
+ auto parts = td::rand_split(query);
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- HttpReader reader;
- HttpQuery q;
+ td::HttpReader reader;
+ td::HttpQuery q;
reader.init(&input, 100000000, 0);
for (auto &part : parts) {
input_writer.append(part);
@@ -211,21 +239,40 @@ TEST(Http, gzip_bomb) {
}
}
+TEST(Http, gzip) {
+ auto gzip_str = td::gzdecode(td::base64url_decode(td::Slice(gzip, gzip_size)).ok()).as_slice().str();
+
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+
+ td::HttpReader reader;
+ reader.init(&input, 0, 0);
+
+ auto query = make_http_query("", true, false, true, 0.01, gzip_str);
+ input_writer.append(query);
+ input.sync_with_writer();
+
+ td::HttpQuery q;
+ auto r_state = reader.read_next(&q);
+ ASSERT_TRUE(r_state.is_error());
+ ASSERT_EQ(413, r_state.error().code());
+}
+
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 str = td::rand_string('a', 'z', 1000000);
+ auto parts = td::rand_split(str);
+ td::ChainBufferWriter input_writer;
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;
+ td::ByteFlowSource source(&input);
+ td::UInt256 key;
+ td::UInt128 iv;
+ td::Random::secure_bytes(key.raw, sizeof(key));
+ td::Random::secure_bytes(iv.raw, sizeof(iv));
+ td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
- AesCtrByteFlow aes_decode;
+ td::AesCtrByteFlow aes_decode;
aes_decode.init(key, iv);
- ByteFlowSink sink;
+ td::ByteFlowSink sink;
source >> aes_encode >> aes_decode >> sink;
ASSERT_TRUE(!sink.is_ready());
@@ -234,7 +281,7 @@ TEST(Http, aes_ctr_encode_decode_flow) {
source.wakeup();
}
ASSERT_TRUE(!sink.is_ready());
- source.close_input(Status::OK());
+ source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@@ -242,25 +289,25 @@ TEST(Http, aes_ctr_encode_decode_flow) {
}
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));
+ auto str = td::rand_string('a', 'z', 1000000);
+ td::CSlice name = "test_encryption";
+ td::unlink(name).ignore();
+ td::UInt256 key;
+ td::UInt128 iv;
+ td::Random::secure_bytes(key.raw, sizeof(key));
+ td::Random::secure_bytes(iv.raw, sizeof(iv));
{
- BufferedFdBase<FileFd> fd(FileFd::open(name, FileFd::Write | FileFd::Create).move_as_ok());
+ td::BufferedFdBase<td::FileFd> fd(td::FileFd::open(name, td::FileFd::Write | td::FileFd::Create).move_as_ok());
- auto parts = rand_split(str);
+ auto parts = td::rand_split(str);
- ChainBufferWriter output_writer;
+ td::ChainBufferWriter output_writer;
auto output_reader = output_writer.extract_reader();
- ByteFlowSource source(&output_reader);
- AesCtrByteFlow aes_encode;
+ td::ByteFlowSource source(&output_reader);
+ td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
- ByteFlowSink sink;
+ td::ByteFlowSink sink;
source >> aes_encode >> sink;
fd.set_output_reader(sink.get_output());
@@ -274,26 +321,26 @@ TEST(Http, aes_file_encryption) {
}
{
- BufferedFdBase<FileFd> fd(FileFd::open(name, FileFd::Read).move_as_ok());
+ td::BufferedFdBase<td::FileFd> fd(td::FileFd::open(name, td::FileFd::Read).move_as_ok());
- ChainBufferWriter input_writer;
+ td::ChainBufferWriter input_writer;
auto input_reader = input_writer.extract_reader();
- ByteFlowSource source(&input_reader);
- AesCtrByteFlow aes_encode;
+ td::ByteFlowSource source(&input_reader);
+ td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
- ByteFlowSink sink;
+ td::ByteFlowSink sink;
source >> aes_encode >> sink;
fd.set_input_writer(&input_writer);
- fd.update_flags(Fd::Flag::Read);
- while (can_read(fd)) {
+ fd.get_poll_info().add_flags(td::PollFlags::Read());
+ while (can_read_local(fd)) {
fd.flush_read(4096).ensure();
source.wakeup();
}
fd.close();
- source.close_input(Status::OK());
+ source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@@ -303,20 +350,20 @@ TEST(Http, aes_file_encryption) {
}
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 str = td::rand_string('a', 'z', 100);
+ auto parts = td::rand_split(make_chunked(str));
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- ByteFlowSource source(&input);
- HttpChunkedByteFlow chunked_flow;
- ByteFlowSink sink;
+ td::ByteFlowSource source(&input);
+ td::HttpChunkedByteFlow chunked_flow;
+ td::ByteFlowSink sink;
source >> chunked_flow >> sink;
for (auto &part : parts) {
input_writer.append(part);
source.wakeup();
}
- source.close_input(Status::OK());
+ source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
@@ -326,16 +373,16 @@ TEST(Http, chunked_flow) {
}
TEST(Http, chunked_flow_error) {
- auto str = rand_string('a', 'z', 100000);
+ auto str = td::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 parts = td::rand_split(new_str);
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- ByteFlowSource source(&input);
- HttpChunkedByteFlow chunked_flow;
- ByteFlowSink sink;
+ td::ByteFlowSource source(&input);
+ td::HttpChunkedByteFlow chunked_flow;
+ td::ByteFlowSink sink;
source >> chunked_flow >> sink;
for (auto &part : parts) {
@@ -343,31 +390,106 @@ TEST(Http, chunked_flow_error) {
source.wakeup();
}
ASSERT_TRUE(!sink.is_ready());
- source.close_input(Status::OK());
+ source.close_input(td::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 str = td::rand_string('a', 'z', 1000000);
+ auto parts = td::rand_split(make_chunked(td::gzencode(str, 2.0).as_slice().str()));
- auto input_writer = ChainBufferWriter::create_empty();
+ td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
- ByteFlowSource source(&input);
- HttpChunkedByteFlow chunked_flow;
- GzipByteFlow gzip_flow(Gzip::Decode);
- ByteFlowSink sink;
+ td::ByteFlowSource source(&input);
+ td::HttpChunkedByteFlow chunked_flow;
+ td::GzipByteFlow gzip_flow(td::Gzip::Mode::Decode);
+ td::ByteFlowSink sink;
source >> chunked_flow >> gzip_flow >> sink;
for (auto &part : parts) {
input_writer.append(part);
source.wakeup();
}
- source.close_input(Status::OK());
+ source.close_input(td::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, gzip_bomb_with_limit) {
+ td::string gzip_bomb_str;
+ {
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+ td::GzipByteFlow gzip_flow(td::Gzip::Mode::Encode);
+ td::ByteFlowSource source(&input);
+ td::ByteFlowSink sink;
+ source >> gzip_flow >> sink;
+
+ td::string s(1 << 16, 'a');
+ for (int i = 0; i < 1000; i++) {
+ input_writer.append(s);
+ source.wakeup();
+ }
+ source.close_input(td::Status::OK());
+ ASSERT_TRUE(sink.is_ready());
+ LOG_IF(ERROR, sink.status().is_error()) << sink.status();
+ ASSERT_TRUE(sink.status().is_ok());
+ gzip_bomb_str = sink.result()->move_as_buffer_slice().as_slice().str();
+ }
+
+ auto query = make_http_query("", false, false, true, 0.01, gzip_bomb_str);
+ auto parts = td::rand_split(query);
+ td::ChainBufferWriter input_writer;
+ auto input = input_writer.extract_reader();
+ td::HttpReader reader;
+ td::HttpQuery q;
+ reader.init(&input, 1000000);
+ bool ok = false;
+ 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(FATAL) << r_state.error();
+ return;
+ } else if (r_state.ok() == 0) {
+ ok = true;
+ }
+ }
+ ASSERT_TRUE(ok);
+}
+
+#if TD_DARWIN_WATCH_OS
+struct Baton {
+ std::mutex mutex;
+ std::condition_variable cond;
+ bool is_ready{false};
+
+ void wait() {
+ std::unique_lock<std::mutex> lock(mutex);
+ cond.wait(lock, [&] { return is_ready; });
+ }
+
+ void post() {
+ {
+ std::unique_lock<std::mutex> lock(mutex);
+ is_ready = true;
+ }
+ cond.notify_all();
+ }
+
+ void reset() {
+ is_ready = false;
+ }
+};
+
+TEST(Http, Darwin) {
+ Baton baton;
+ td::DarwinHttp::get("http://example.com", [&](td::BufferSlice data) { baton.post(); });
+ baton.wait();
+}
+#endif
diff --git a/protocols/Telegram/tdlib/td/test/link.cpp b/protocols/Telegram/tdlib/td/test/link.cpp
new file mode 100644
index 0000000000..13c4613174
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/link.cpp
@@ -0,0 +1,983 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// 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/LinkManager.h"
+
+#include "td/telegram/MessageEntity.h"
+#include "td/telegram/td_api.h"
+
+#include "td/utils/common.h"
+#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/tests.h"
+
+static void check_find_urls(const td::string &url, bool is_valid) {
+ auto url_lower = td::to_lower(url);
+ {
+ auto tg_urls = td::find_tg_urls(url);
+ if (is_valid && (td::begins_with(url_lower, "tg://") || td::begins_with(url_lower, "ton://"))) {
+ ASSERT_EQ(1u, tg_urls.size());
+ ASSERT_STREQ(url, tg_urls[0]);
+ } else {
+ ASSERT_TRUE(tg_urls.empty() || tg_urls[0] != url);
+ }
+ }
+
+ {
+ if (is_valid && (td::begins_with(url_lower, "http") || td::begins_with(url_lower, "t.me")) &&
+ url.find('.') != td::string::npos && url.find(' ') == td::string::npos && url != "http://.." &&
+ url.find("ra.ph") == td::string::npos && url.find("Aph") == td::string::npos) {
+ auto urls = td::find_urls(url);
+ ASSERT_EQ(1u, urls.size());
+ ASSERT_STREQ(url, urls[0].first);
+ }
+ }
+}
+
+static void check_link(const td::string &url, const td::string &expected) {
+ auto result = td::LinkManager::check_link(url);
+ if (result.is_ok()) {
+ ASSERT_STREQ(expected, result.ok());
+ } else {
+ ASSERT_TRUE(expected.empty());
+ }
+
+ check_find_urls(url, result.is_ok());
+}
+
+TEST(Link, check_link) {
+ check_link("sftp://google.com", "");
+ check_link("tg://google_com", "tg://google_com/");
+ check_link("tOn://google", "ton://google/");
+ check_link("httP://google.com?1#tes", "http://google.com/?1#tes");
+ check_link("httPs://google.com/?1#tes", "https://google.com/?1#tes");
+ check_link("http://google.com:0", "");
+ check_link("http://google.com:0000000001", "http://google.com:1/");
+ check_link("http://google.com:-1", "");
+ check_link("tg://google?1#tes", "tg://google?1#tes");
+ check_link("tg://google/?1#tes", "tg://google?1#tes");
+ check_link("TG:_", "tg://_/");
+ check_link("sftp://google.com", "");
+ check_link("sftp://google.com", "");
+ check_link("http:google.com", "");
+ check_link("tg://http://google.com", "");
+ check_link("tg:http://google.com", "");
+ check_link("tg:https://google.com", "");
+ check_link("tg:test@google.com", "");
+ check_link("tg:google.com:80", "");
+ check_link("tg:google-com", "tg://google-com/");
+ check_link("tg:google.com", "");
+ check_link("tg:google.com:0", "");
+ check_link("tg:google.com:a", "");
+ check_link("tg:[2001:db8:0:0:0:ff00:42:8329]", "");
+ check_link("tg:127.0.0.1", "");
+ check_link("http://[2001:db8:0:0:0:ff00:42:8329]", "http://[2001:db8:0:0:0:ff00:42:8329]/");
+ check_link("http://localhost", "");
+ check_link("http://..", "http://../");
+ check_link("..", "http://../");
+ check_link("https://.", "");
+}
+
+static void parse_internal_link(const td::string &url, td::td_api::object_ptr<td::td_api::InternalLinkType> expected) {
+ auto result = td::LinkManager::parse_internal_link(url);
+ if (result != nullptr) {
+ auto object = result->get_internal_link_type_object();
+ if (object->get_id() == td::td_api::internalLinkTypeMessageDraft::ID) {
+ static_cast<td::td_api::internalLinkTypeMessageDraft *>(object.get())->text_->entities_.clear();
+ }
+ ASSERT_STREQ(url + ' ' + to_string(expected), url + ' ' + to_string(object));
+ } else {
+ LOG_IF(ERROR, expected != nullptr) << url;
+ ASSERT_TRUE(expected == nullptr);
+ }
+
+ check_find_urls(url, result != nullptr);
+}
+
+TEST(Link, parse_internal_link) {
+ auto chat_administrator_rights = [](bool can_manage_chat, bool can_change_info, bool can_post_messages,
+ bool can_edit_messages, bool can_delete_messages, bool can_invite_users,
+ bool can_restrict_members, bool can_pin_messages, bool can_manage_topics,
+ bool can_promote_members, bool can_manage_video_chats, bool is_anonymous) {
+ return td::td_api::make_object<td::td_api::chatAdministratorRights>(
+ can_manage_chat, can_change_info, can_post_messages, can_edit_messages, can_delete_messages, can_invite_users,
+ can_restrict_members, can_pin_messages, can_manage_topics, can_promote_members, can_manage_video_chats,
+ is_anonymous);
+ };
+ auto target_chat_chosen = [](bool allow_users, bool allow_bots, bool allow_groups, bool allow_channels) {
+ return td::td_api::make_object<td::td_api::targetChatChosen>(allow_users, allow_bots, allow_groups, allow_channels);
+ };
+
+ auto active_sessions = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeActiveSessions>();
+ };
+ auto attachment_menu_bot = [](td::td_api::object_ptr<td::td_api::targetChatChosen> chat_types,
+ td::td_api::object_ptr<td::td_api::InternalLinkType> chat_link,
+ const td::string &bot_username, const td::string &start_parameter) {
+ td::td_api::object_ptr<td::td_api::TargetChat> target_chat;
+ if (chat_link != nullptr) {
+ target_chat = td::td_api::make_object<td::td_api::targetChatInternalLink>(std::move(chat_link));
+ } else if (chat_types != nullptr) {
+ target_chat = std::move(chat_types);
+ } else {
+ target_chat = td::td_api::make_object<td::td_api::targetChatCurrent>();
+ }
+ return td::td_api::make_object<td::td_api::internalLinkTypeAttachmentMenuBot>(
+ std::move(target_chat), bot_username, start_parameter.empty() ? td::string() : "start://" + start_parameter);
+ };
+ auto authentication_code = [](const td::string &code) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeAuthenticationCode>(code);
+ };
+ auto background = [](const td::string &background_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeBackground>(background_name);
+ };
+ auto bot_add_to_channel = [](const td::string &bot_username,
+ td::td_api::object_ptr<td::td_api::chatAdministratorRights> &&administrator_rights) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeBotAddToChannel>(bot_username,
+ std::move(administrator_rights));
+ };
+ auto bot_start = [](const td::string &bot_username, const td::string &start_parameter) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeBotStart>(bot_username, start_parameter, false);
+ };
+ auto bot_start_in_group = [](const td::string &bot_username, const td::string &start_parameter,
+ td::td_api::object_ptr<td::td_api::chatAdministratorRights> &&administrator_rights) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeBotStartInGroup>(bot_username, start_parameter,
+ std::move(administrator_rights));
+ };
+ auto change_phone_number = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeChangePhoneNumber>();
+ };
+ auto chat_invite = [](const td::string &hash) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeChatInvite>("tg:join?invite=" + hash);
+ };
+ auto filter_settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeFilterSettings>();
+ };
+ auto game = [](const td::string &bot_username, const td::string &game_short_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeGame>(bot_username, game_short_name);
+ };
+ auto instant_view = [](const td::string &url, const td::string &fallback_url) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeInstantView>(url, fallback_url);
+ };
+ auto invoice = [](const td::string &invoice_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeInvoice>(invoice_name);
+ };
+ auto language_pack = [](const td::string &language_pack_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeLanguagePack>(language_pack_name);
+ };
+ auto language_settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeLanguageSettings>();
+ };
+ auto message = [](const td::string &url) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeMessage>(url);
+ };
+ auto message_draft = [](td::string text, bool contains_url) {
+ auto formatted_text = td::td_api::make_object<td::td_api::formattedText>();
+ formatted_text->text_ = std::move(text);
+ return td::td_api::make_object<td::td_api::internalLinkTypeMessageDraft>(std::move(formatted_text), contains_url);
+ };
+ auto passport_data_request = [](td::int32 bot_user_id, const td::string &scope, const td::string &public_key,
+ const td::string &nonce, const td::string &callback_url) {
+ return td::td_api::make_object<td::td_api::internalLinkTypePassportDataRequest>(bot_user_id, scope, public_key,
+ nonce, callback_url);
+ };
+ auto phone_number_confirmation = [](const td::string &hash, const td::string &phone_number) {
+ return td::td_api::make_object<td::td_api::internalLinkTypePhoneNumberConfirmation>(hash, phone_number);
+ };
+ auto premium_features = [](const td::string &referrer) {
+ return td::td_api::make_object<td::td_api::internalLinkTypePremiumFeatures>(referrer);
+ };
+ auto privacy_and_security_settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypePrivacyAndSecuritySettings>();
+ };
+ auto proxy_mtproto = [](const td::string &server, td::int32 port, const td::string &secret) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeProxy>(
+ server, port, td::td_api::make_object<td::td_api::proxyTypeMtproto>(secret));
+ };
+ auto proxy_socks = [](const td::string &server, td::int32 port, const td::string &username,
+ const td::string &password) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeProxy>(
+ server, port, td::td_api::make_object<td::td_api::proxyTypeSocks5>(username, password));
+ };
+ auto public_chat = [](const td::string &chat_username) {
+ return td::td_api::make_object<td::td_api::internalLinkTypePublicChat>(chat_username);
+ };
+ auto qr_code_authentication = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeQrCodeAuthentication>();
+ };
+ auto restore_purchases = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeRestorePurchases>();
+ };
+ auto settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeSettings>();
+ };
+ auto sticker_set = [](const td::string &sticker_set_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeStickerSet>(sticker_set_name);
+ };
+ auto theme = [](const td::string &theme_name) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeTheme>(theme_name);
+ };
+ auto theme_settings = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeThemeSettings>();
+ };
+ auto unknown_deep_link = [](const td::string &link) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeUnknownDeepLink>(link);
+ };
+ auto unsupported_proxy = [] {
+ return td::td_api::make_object<td::td_api::internalLinkTypeUnsupportedProxy>();
+ };
+ auto user_phone_number = [](const td::string &phone_number) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeUserPhoneNumber>(phone_number);
+ };
+ auto video_chat = [](const td::string &chat_username, const td::string &invite_hash, bool is_live_stream) {
+ return td::td_api::make_object<td::td_api::internalLinkTypeVideoChat>(chat_username, invite_hash, is_live_stream);
+ };
+
+ parse_internal_link("t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("telegram.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("telegram.dog/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("www.t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("www%2etelegram.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("www%2Etelegram.dog/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("www%252Etelegram.dog/levlam/1", nullptr);
+ parse_internal_link("www.t.me/s/s/s/s/s/joinchat/1", chat_invite("1"));
+ parse_internal_link("www.t.me/s/%73/%73/s/%73/joinchat/1", chat_invite("1"));
+ parse_internal_link("http://t.me/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/s/joinchat/1", chat_invite("1"));
+ parse_internal_link("http://t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("https://t.me/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("hTtp://www.t.me:443/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("httPs://t.me:80/levlam/1", message("tg:resolve?domain=levlam&post=1"));
+ parse_internal_link("https://t.me:200/levlam/1", nullptr);
+ parse_internal_link("http:t.me/levlam/1", nullptr);
+ parse_internal_link("t.dog/levlam/1", nullptr);
+ parse_internal_link("t.m/levlam/1", nullptr);
+ parse_internal_link("t.men/levlam/1", nullptr);
+
+ parse_internal_link("tg:resolve?domain=username&post=12345&single",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("tg:resolve?domain=username&post=12345&single&startattach=1&attach=test",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("tg:resolve?domain=user%31name&post=%312345&single&comment=456&t=789&single&thread=123%20%31",
+ message("tg:resolve?domain=user1name&post=12345&single&thread=123%201&comment=456&t=789"));
+ parse_internal_link("TG://resolve?domain=username&post=12345&single&voicechat=aasd",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("TG://test@resolve?domain=username&post=12345&single", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&post=12345&single", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&post=12345&single", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&post=12345&single", nullptr);
+ parse_internal_link("tg:resolve?domain=&post=12345&single",
+ unknown_deep_link("tg://resolve?domain=&post=12345&single"));
+ parse_internal_link("tg:resolve?domain=telegram&post=&single", public_chat("telegram"));
+ parse_internal_link("tg:resolve?domain=123456&post=&single",
+ unknown_deep_link("tg://resolve?domain=123456&post=&single"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach", attachment_menu_bot(nullptr, nullptr, "telegram", ""));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1",
+ attachment_menu_bot(nullptr, nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=cats+dogs",
+ attachment_menu_bot(nullptr, nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=users",
+ attachment_menu_bot(target_chat_chosen(true, false, false, false), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=bots",
+ attachment_menu_bot(target_chat_chosen(false, true, false, false), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=groups",
+ attachment_menu_bot(target_chat_chosen(false, false, true, false), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=channels",
+ attachment_menu_bot(target_chat_chosen(false, false, false, true), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&startattach=1&choose=users+channels",
+ attachment_menu_bot(target_chat_chosen(true, false, false, true), nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&attach=&startattach",
+ attachment_menu_bot(nullptr, nullptr, "telegram", ""));
+ parse_internal_link("tg:resolve?domain=telegram&attach=&startattach=1",
+ attachment_menu_bot(nullptr, nullptr, "telegram", "1"));
+ parse_internal_link("tg:resolve?domain=telegram&attach=test&startattach",
+ attachment_menu_bot(nullptr, public_chat("telegram"), "test", ""));
+ parse_internal_link("tg:resolve?domain=telegram&attach=test&startattach=1",
+ attachment_menu_bot(nullptr, public_chat("telegram"), "test", "1"));
+
+ parse_internal_link("tg:resolve?phone=1", user_phone_number("1"));
+ parse_internal_link("tg:resolve?phone=123456", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&startattach", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&startattach=123", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&attach=", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&attach=&startattach", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&attach=&startattach=123", user_phone_number("123456"));
+ parse_internal_link("tg:resolve?phone=123456&attach=test",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "test", ""));
+ parse_internal_link("tg:resolve?phone=123456&attach=test&startattach&choose=users",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "test", ""));
+ parse_internal_link("tg:resolve?phone=123456&attach=test&startattach=123",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "test", "123"));
+ parse_internal_link("tg:resolve?phone=01234567890123456789012345678912",
+ user_phone_number("01234567890123456789012345678912"));
+ parse_internal_link("tg:resolve?phone=012345678901234567890123456789123",
+ unknown_deep_link("tg://resolve?phone=012345678901234567890123456789123"));
+ parse_internal_link("tg:resolve?phone=", unknown_deep_link("tg://resolve?phone="));
+ parse_internal_link("tg:resolve?phone=+123", unknown_deep_link("tg://resolve?phone=+123"));
+ parse_internal_link("tg:resolve?phone=123456 ", unknown_deep_link("tg://resolve?phone=123456 "));
+
+ parse_internal_link("t.me/username/12345?single", message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("t.me/username/12345?asdasd", message("tg:resolve?domain=username&post=12345"));
+ parse_internal_link("t.me/username/12345", message("tg:resolve?domain=username&post=12345"));
+ parse_internal_link("t.me/username/12345/", message("tg:resolve?domain=username&post=12345"));
+ parse_internal_link("t.me/username/12345#asdasd", message("tg:resolve?domain=username&post=12345"));
+ parse_internal_link("t.me/username/12345//?voicechat=&single",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("t.me/username/12345/asdasd//asd/asd/asd/?single",
+ message("tg:resolve?domain=username&post=12345&single"));
+ parse_internal_link("t.me/username/12345/67890/asdasd//asd/asd/asd/?single",
+ message("tg:resolve?domain=username&post=67890&single&thread=12345"));
+ parse_internal_link("t.me/username/1asdasdas/asdasd//asd/asd/asd/?single",
+ message("tg:resolve?domain=username&post=1&single"));
+ parse_internal_link("t.me/username/asd", public_chat("username"));
+ parse_internal_link("t.me/username/0", public_chat("username"));
+ parse_internal_link("t.me/username/-12345", public_chat("username"));
+ parse_internal_link("t.me//12345?single", nullptr);
+ parse_internal_link("https://telegram.dog/telegram/?single", public_chat("telegram"));
+ parse_internal_link("t.me/username?startattach", attachment_menu_bot(nullptr, nullptr, "username", ""));
+ parse_internal_link("t.me/username?startattach=1", attachment_menu_bot(nullptr, nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=cats+dogs",
+ attachment_menu_bot(nullptr, nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=users",
+ attachment_menu_bot(target_chat_chosen(true, false, false, false), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=bots",
+ attachment_menu_bot(target_chat_chosen(false, true, false, false), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=groups",
+ attachment_menu_bot(target_chat_chosen(false, false, true, false), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=channels",
+ attachment_menu_bot(target_chat_chosen(false, false, false, true), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?startattach=1&choose=bots+groups",
+ attachment_menu_bot(target_chat_chosen(false, true, true, false), nullptr, "username", "1"));
+ parse_internal_link("t.me/username?attach=", public_chat("username"));
+ parse_internal_link("t.me/username?attach=&startattach", attachment_menu_bot(nullptr, nullptr, "username", ""));
+ parse_internal_link("t.me/username?attach=&startattach=1", attachment_menu_bot(nullptr, nullptr, "username", "1"));
+ parse_internal_link("t.me/username?attach=bot", attachment_menu_bot(nullptr, public_chat("username"), "bot", ""));
+ parse_internal_link("t.me/username?attach=bot&startattach",
+ attachment_menu_bot(nullptr, public_chat("username"), "bot", ""));
+ parse_internal_link("t.me/username?attach=bot&startattach=1&choose=users",
+ attachment_menu_bot(nullptr, public_chat("username"), "bot", "1"));
+
+ parse_internal_link("tg:privatepost?domain=username/12345&single",
+ unknown_deep_link("tg://privatepost?domain=username/12345&single"));
+ parse_internal_link("tg:privatepost?channel=username/12345&single",
+ unknown_deep_link("tg://privatepost?channel=username/12345&single"));
+ parse_internal_link("tg:privatepost?channel=username&post=12345",
+ message("tg:privatepost?channel=username&post=12345"));
+
+ parse_internal_link("t.me/c/12345?single", nullptr);
+ parse_internal_link("t.me/c/1/c?single", nullptr);
+ parse_internal_link("t.me/c/c/1?single", nullptr);
+ parse_internal_link("t.me/c//1?single", nullptr);
+ parse_internal_link("t.me/c/12345/123", message("tg:privatepost?channel=12345&post=123"));
+ parse_internal_link("t.me/c/12345/123?single", message("tg:privatepost?channel=12345&post=123&single"));
+ parse_internal_link("t.me/c/12345/123/asd/asd////?single", message("tg:privatepost?channel=12345&post=123&single"));
+ parse_internal_link("t.me/c/12345/123/456/asd/asd////?single",
+ message("tg:privatepost?channel=12345&post=456&single&thread=123"));
+ parse_internal_link("t.me/c/%312345/%3123?comment=456&t=789&single&thread=123%20%31",
+ message("tg:privatepost?channel=12345&post=123&single&thread=123%201&comment=456&t=789"));
+
+ parse_internal_link("tg:bg?color=111111#asdasd", background("111111"));
+ parse_internal_link("tg:bg?color=11111%31", background("111111"));
+ parse_internal_link("tg:bg?color=11111%20", background("11111%20"));
+ parse_internal_link("tg:bg?gradient=111111-222222", background("111111-222222"));
+ parse_internal_link("tg:bg?rotation=180%20&gradient=111111-222222%20",
+ background("111111-222222%20?rotation=180%20"));
+ parse_internal_link("tg:bg?gradient=111111~222222", background("111111~222222"));
+ parse_internal_link("tg:bg?gradient=abacaba", background("abacaba"));
+ parse_internal_link("tg:bg?slug=111111~222222#asdasd", background("111111~222222"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12&text=1", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12&mode=1", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=test&mode=12&rotation=4&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3&rotation=4"));
+ parse_internal_link("tg:bg?mode=12&&slug=test&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3"));
+ parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3",
+ unknown_deep_link("tg://bg?mode=12&intensity=2&bg_color=3"));
+
+ parse_internal_link("tg:bg?color=111111#asdasd", background("111111"));
+ parse_internal_link("tg:bg?color=11111%31", background("111111"));
+ parse_internal_link("tg:bg?color=11111%20", background("11111%20"));
+ parse_internal_link("tg:bg?gradient=111111-222222", background("111111-222222"));
+ parse_internal_link("tg:bg?rotation=180%20&gradient=111111-222222%20",
+ background("111111-222222%20?rotation=180%20"));
+ parse_internal_link("tg:bg?gradient=111111~222222", background("111111~222222"));
+ parse_internal_link("tg:bg?gradient=abacaba", background("abacaba"));
+ parse_internal_link("tg:bg?slug=111111~222222#asdasd", background("111111~222222"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12&text=1", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=111111~222222&mode=12&mode=1", background("111111~222222?mode=12"));
+ parse_internal_link("tg:bg?slug=test&mode=12&rotation=4&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3&rotation=4"));
+ parse_internal_link("tg:bg?mode=12&&slug=test&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3"));
+ parse_internal_link("tg:bg?mode=12&intensity=2&bg_color=3",
+ unknown_deep_link("tg://bg?mode=12&intensity=2&bg_color=3"));
+
+ parse_internal_link("%54.me/bg/111111#asdasd", background("111111"));
+ parse_internal_link("t.me/bg/11111%31", background("111111"));
+ parse_internal_link("t.me/bg/11111%20", background("11111%20"));
+ parse_internal_link("t.me/bg/111111-222222", background("111111-222222"));
+ parse_internal_link("t.me/bg/111111-222222%20?rotation=180%20", background("111111-222222%20?rotation=180%20"));
+ parse_internal_link("t.me/bg/111111~222222", background("111111~222222"));
+ parse_internal_link("t.me/bg/abacaba", background("abacaba"));
+ parse_internal_link("t.me/Bg/abacaba", public_chat("Bg"));
+ parse_internal_link("t.me/bg/111111~222222#asdasd", background("111111~222222"));
+ parse_internal_link("t.me/bg/111111~222222?mode=12", background("111111~222222?mode=12"));
+ parse_internal_link("t.me/bg/111111~222222?mode=12&text=1", background("111111~222222?mode=12"));
+ parse_internal_link("t.me/bg/111111~222222?mode=12&mode=1", background("111111~222222?mode=12"));
+ parse_internal_link("t.me/bg/test?mode=12&rotation=4&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3&rotation=4"));
+ parse_internal_link("t.me/%62g/test/?mode=12&&&intensity=2&bg_color=3",
+ background("test?mode=12&intensity=2&bg_color=3"));
+ parse_internal_link("t.me/bg//", nullptr);
+ parse_internal_link("t.me/bg/%20/", background("%20"));
+ parse_internal_link("t.me/bg/", nullptr);
+
+ parse_internal_link("t.me/invoice?slug=abcdef", nullptr);
+ parse_internal_link("t.me/invoice", nullptr);
+ parse_internal_link("t.me/invoice/", nullptr);
+ parse_internal_link("t.me/invoice//abcdef", nullptr);
+ parse_internal_link("t.me/invoice?/abcdef", nullptr);
+ parse_internal_link("t.me/invoice/?abcdef", nullptr);
+ parse_internal_link("t.me/invoice/#abcdef", nullptr);
+ parse_internal_link("t.me/invoice/abacaba", invoice("abacaba"));
+ parse_internal_link("t.me/invoice/aba%20aba", invoice("aba aba"));
+ parse_internal_link("t.me/invoice/123456a", invoice("123456a"));
+ parse_internal_link("t.me/invoice/12345678901", invoice("12345678901"));
+ parse_internal_link("t.me/invoice/123456", invoice("123456"));
+ parse_internal_link("t.me/invoice/123456/123123/12/31/a/s//21w/?asdas#test", invoice("123456"));
+
+ parse_internal_link("t.me/$?slug=abcdef", nullptr);
+ parse_internal_link("t.me/$", nullptr);
+ parse_internal_link("t.me/$/abcdef", nullptr);
+ parse_internal_link("t.me/$?/abcdef", nullptr);
+ parse_internal_link("t.me/$?abcdef", nullptr);
+ parse_internal_link("t.me/$#abcdef", nullptr);
+ parse_internal_link("t.me/$abacaba", invoice("abacaba"));
+ parse_internal_link("t.me/$aba%20aba", invoice("aba aba"));
+ parse_internal_link("t.me/$123456a", invoice("123456a"));
+ parse_internal_link("t.me/$12345678901", invoice("12345678901"));
+ parse_internal_link("t.me/$123456", invoice("123456"));
+ parse_internal_link("t.me/%24123456", invoice("123456"));
+ parse_internal_link("t.me/$123456/123123/12/31/a/s//21w/?asdas#test", invoice("123456"));
+
+ parse_internal_link("tg:invoice?slug=abcdef", invoice("abcdef"));
+ parse_internal_link("tg:invoice?slug=abc%30ef", invoice("abc0ef"));
+ parse_internal_link("tg://invoice?slug=", unknown_deep_link("tg://invoice?slug="));
+
+ parse_internal_link("tg:share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true));
+ parse_internal_link("tg:share?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("tg:share?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("tg:msg_url?url=google.com&text=text", message_draft("google.com\ntext", true));
+ parse_internal_link("tg:msg_url?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("tg:msg_url?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("tg:msg?url=google.com&text=text", message_draft("google.com\ntext", true));
+ parse_internal_link("tg:msg?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("tg:msg?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("tg:msg?url=&text=\n\n\n\n\n\n\n\n", nullptr);
+ parse_internal_link("tg:msg?url=%20\n&text=", nullptr);
+ parse_internal_link("tg:msg?url=%20\n&text=google.com", message_draft("google.com", false));
+ parse_internal_link("tg:msg?url=@&text=", message_draft(" @", false));
+ parse_internal_link("tg:msg?url=&text=@", message_draft(" @", false));
+ parse_internal_link("tg:msg?url=@&text=@", message_draft(" @\n@", true));
+ parse_internal_link("tg:msg?url=%FF&text=1", nullptr);
+
+ parse_internal_link("https://t.me/share?url=google.com&text=text#asdasd", message_draft("google.com\ntext", true));
+ parse_internal_link("https://t.me/share?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("https://t.me/share?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=google.com&text=text", message_draft("google.com\ntext", true));
+ parse_internal_link("https://t.me/msg?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=google.com&text=text", message_draft("google.com\ntext", true));
+ parse_internal_link("https://t.me/msg?url=google.com&text=", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=&text=google.com", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=&text=\n\n\n\n\n\n\n\n", nullptr);
+ parse_internal_link("https://t.me/msg?url=%20%0A&text=", nullptr);
+ parse_internal_link("https://t.me/msg?url=%20%0A&text=google.com", message_draft("google.com", false));
+ parse_internal_link("https://t.me/msg?url=@&text=", message_draft(" @", false));
+ parse_internal_link("https://t.me/msg?url=&text=@", message_draft(" @", false));
+ parse_internal_link("https://t.me/msg?url=@&text=@", message_draft(" @\n@", true));
+ parse_internal_link("https://t.me/msg?url=%FF&text=1", nullptr);
+
+ parse_internal_link("tg:login?codec=12345", unknown_deep_link("tg://login?codec=12345"));
+ parse_internal_link("tg:login", unknown_deep_link("tg://login"));
+ parse_internal_link("tg:login?code=abacaba", authentication_code("abacaba"));
+ parse_internal_link("tg:login?code=123456", authentication_code("123456"));
+
+ parse_internal_link("t.me/login?codec=12345", nullptr);
+ parse_internal_link("t.me/login", nullptr);
+ parse_internal_link("t.me/login/", nullptr);
+ parse_internal_link("t.me/login//12345", nullptr);
+ parse_internal_link("t.me/login?/12345", nullptr);
+ parse_internal_link("t.me/login/?12345", nullptr);
+ parse_internal_link("t.me/login/#12345", nullptr);
+ parse_internal_link("t.me/login/abacaba", authentication_code("abacaba"));
+ parse_internal_link("t.me/login/aba%20aba", authentication_code("aba aba"));
+ parse_internal_link("t.me/login/123456a", authentication_code("123456a"));
+ parse_internal_link("t.me/login/12345678901", authentication_code("12345678901"));
+ parse_internal_link("t.me/login/123456", authentication_code("123456"));
+ parse_internal_link("t.me/login/123456/123123/12/31/a/s//21w/?asdas#test", authentication_code("123456"));
+
+ parse_internal_link("tg:login?token=abacaba", qr_code_authentication());
+ parse_internal_link("tg:login?token=", unknown_deep_link("tg://login?token="));
+
+ parse_internal_link("tg:restore_purchases?token=abacaba", restore_purchases());
+ parse_internal_link("tg:restore_purchases?#", restore_purchases());
+ parse_internal_link("tg:restore_purchases/?#", restore_purchases());
+ parse_internal_link("tg:restore_purchases", restore_purchases());
+ parse_internal_link("tg:restore_purchase", unknown_deep_link("tg://restore_purchase"));
+ parse_internal_link("tg:restore_purchasess", unknown_deep_link("tg://restore_purchasess"));
+ parse_internal_link("tg:restore_purchases/test?#", unknown_deep_link("tg://restore_purchases/test?"));
+
+ parse_internal_link("t.me/joinchat?invite=abcdef", nullptr);
+ parse_internal_link("t.me/joinchat", nullptr);
+ parse_internal_link("t.me/joinchat/", nullptr);
+ parse_internal_link("t.me/joinchat//abcdef", nullptr);
+ parse_internal_link("t.me/joinchat?/abcdef", nullptr);
+ parse_internal_link("t.me/joinchat/?abcdef", nullptr);
+ parse_internal_link("t.me/joinchat/#abcdef", nullptr);
+ parse_internal_link("t.me/joinchat/abacaba", chat_invite("abacaba"));
+ parse_internal_link("t.me/joinchat/aba%20aba", chat_invite("aba%20aba"));
+ parse_internal_link("t.me/joinchat/aba%30aba", chat_invite("aba0aba"));
+ parse_internal_link("t.me/joinchat/123456a", chat_invite("123456a"));
+ parse_internal_link("t.me/joinchat/12345678901", chat_invite("12345678901"));
+ parse_internal_link("t.me/joinchat/123456", chat_invite("123456"));
+ parse_internal_link("t.me/joinchat/123456/123123/12/31/a/s//21w/?asdas#test", chat_invite("123456"));
+
+ parse_internal_link("t.me/+?invite=abcdef", nullptr);
+ parse_internal_link("t.me/+a", chat_invite("a"));
+ parse_internal_link("t.me/+", nullptr);
+ parse_internal_link("t.me/+/abcdef", nullptr);
+ parse_internal_link("t.me/ ?/abcdef", nullptr);
+ parse_internal_link("t.me/+?abcdef", nullptr);
+ parse_internal_link("t.me/+#abcdef", nullptr);
+ parse_internal_link("t.me/ abacaba", chat_invite("abacaba"));
+ parse_internal_link("t.me/+aba%20aba", chat_invite("aba%20aba"));
+ parse_internal_link("t.me/+aba%30aba", chat_invite("aba0aba"));
+ parse_internal_link("t.me/+123456a", chat_invite("123456a"));
+ parse_internal_link("t.me/%2012345678901", user_phone_number("12345678901"));
+ parse_internal_link("t.me/+123456", user_phone_number("123456"));
+ parse_internal_link("t.me/ 123456/123123/12/31/a/s//21w/?asdas#test", user_phone_number("123456"));
+ parse_internal_link("t.me/ /123456/123123/12/31/a/s//21w/?asdas#test", nullptr);
+ parse_internal_link("t.me/+123456?startattach", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?startattach=1", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?attach=", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?attach=&startattach", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?attach=&startattach=1", user_phone_number("123456"));
+ parse_internal_link("t.me/+123456?attach=bot", attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", ""));
+ parse_internal_link("t.me/+123456?attach=bot&startattach",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", ""));
+ parse_internal_link("t.me/+123456?attach=bot&startattach=1",
+ attachment_menu_bot(nullptr, user_phone_number("123456"), "bot", "1"));
+
+ parse_internal_link("tg:join?invite=abcdef", chat_invite("abcdef"));
+ parse_internal_link("tg:join?invite=abc%20def", chat_invite("abc%20def"));
+ parse_internal_link("tg://join?invite=abc%30def", chat_invite("abc0def"));
+ parse_internal_link("tg:join?invite=", unknown_deep_link("tg://join?invite="));
+
+ parse_internal_link("t.me/addstickers?set=abcdef", nullptr);
+ parse_internal_link("t.me/addstickers", nullptr);
+ parse_internal_link("t.me/addstickers/", nullptr);
+ parse_internal_link("t.me/addstickers//abcdef", nullptr);
+ parse_internal_link("t.me/addstickers?/abcdef", nullptr);
+ parse_internal_link("t.me/addstickers/?abcdef", nullptr);
+ parse_internal_link("t.me/addstickers/#abcdef", nullptr);
+ parse_internal_link("t.me/addstickers/abacaba", sticker_set("abacaba"));
+ parse_internal_link("t.me/addstickers/aba%20aba", sticker_set("aba aba"));
+ parse_internal_link("t.me/addstickers/123456a", sticker_set("123456a"));
+ parse_internal_link("t.me/addstickers/12345678901", sticker_set("12345678901"));
+ parse_internal_link("t.me/addstickers/123456", sticker_set("123456"));
+ parse_internal_link("t.me/addstickers/123456/123123/12/31/a/s//21w/?asdas#test", sticker_set("123456"));
+
+ parse_internal_link("tg:addstickers?set=abcdef", sticker_set("abcdef"));
+ parse_internal_link("tg:addstickers?set=abc%30ef", sticker_set("abc0ef"));
+ parse_internal_link("tg://addstickers?set=", unknown_deep_link("tg://addstickers?set="));
+
+ parse_internal_link("t.me/addemoji?set=abcdef", nullptr);
+ parse_internal_link("t.me/addemoji", nullptr);
+ parse_internal_link("t.me/addemoji/", nullptr);
+ parse_internal_link("t.me/addemoji//abcdef", nullptr);
+ parse_internal_link("t.me/addemoji?/abcdef", nullptr);
+ parse_internal_link("t.me/addemoji/?abcdef", nullptr);
+ parse_internal_link("t.me/addemoji/#abcdef", nullptr);
+ parse_internal_link("t.me/addemoji/abacaba", sticker_set("abacaba"));
+ parse_internal_link("t.me/addemoji/aba%20aba", sticker_set("aba aba"));
+ parse_internal_link("t.me/addemoji/123456a", sticker_set("123456a"));
+ parse_internal_link("t.me/addemoji/12345678901", sticker_set("12345678901"));
+ parse_internal_link("t.me/addemoji/123456", sticker_set("123456"));
+ parse_internal_link("t.me/addemoji/123456/123123/12/31/a/s//21w/?asdas#test", sticker_set("123456"));
+
+ parse_internal_link("tg:addemoji?set=abcdef", sticker_set("abcdef"));
+ parse_internal_link("tg:addemoji?set=abc%30ef", sticker_set("abc0ef"));
+ parse_internal_link("tg://addemoji?set=", unknown_deep_link("tg://addemoji?set="));
+
+ parse_internal_link("t.me/confirmphone?hash=abc%30ef&phone=", nullptr);
+ parse_internal_link("t.me/confirmphone/123456/123123/12/31/a/s//21w/?hash=abc%30ef&phone=123456789",
+ phone_number_confirmation("abc0ef", "123456789"));
+ parse_internal_link("t.me/confirmphone?hash=abc%30ef&phone=123456789",
+ phone_number_confirmation("abc0ef", "123456789"));
+
+ parse_internal_link("tg:confirmphone?hash=abc%30ef&phone=",
+ unknown_deep_link("tg://confirmphone?hash=abc%30ef&phone="));
+ parse_internal_link("tg:confirmphone?hash=abc%30ef&phone=123456789",
+ phone_number_confirmation("abc0ef", "123456789"));
+ parse_internal_link("tg://confirmphone?hash=123&phone=123456789123456789",
+ phone_number_confirmation("123", "123456789123456789"));
+ parse_internal_link("tg://confirmphone?hash=&phone=123456789123456789",
+ unknown_deep_link("tg://confirmphone?hash=&phone=123456789123456789"));
+ parse_internal_link("tg://confirmphone?hash=123456789123456789&phone=",
+ unknown_deep_link("tg://confirmphone?hash=123456789123456789&phone="));
+
+ parse_internal_link("t.me/setlanguage?lang=abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage", nullptr);
+ parse_internal_link("t.me/setlanguage/", nullptr);
+ parse_internal_link("t.me/setlanguage//abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage?/abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage/?abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage/#abcdef", nullptr);
+ parse_internal_link("t.me/setlanguage/abacaba", language_pack("abacaba"));
+ parse_internal_link("t.me/setlanguage/aba%20aba", language_pack("aba aba"));
+ parse_internal_link("t.me/setlanguage/123456a", language_pack("123456a"));
+ parse_internal_link("t.me/setlanguage/12345678901", language_pack("12345678901"));
+ parse_internal_link("t.me/setlanguage/123456", language_pack("123456"));
+ parse_internal_link("t.me/setlanguage/123456/123123/12/31/a/s//21w/?asdas#test", language_pack("123456"));
+
+ parse_internal_link("tg:setlanguage?lang=abcdef", language_pack("abcdef"));
+ parse_internal_link("tg:setlanguage?lang=abc%30ef", language_pack("abc0ef"));
+ parse_internal_link("tg://setlanguage?lang=", unknown_deep_link("tg://setlanguage?lang="));
+
+ parse_internal_link(
+ "http://telegram.dog/iv?url=https://telegram.org&rhash=abcdef&test=1&tg_rhash=1",
+ instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash=abcdef", "https://telegram.org"));
+ parse_internal_link("t.me/iva?url=https://telegram.org&rhash=abcdef", public_chat("iva"));
+ parse_internal_link("t.me/iv?url=&rhash=abcdef", nullptr);
+ parse_internal_link("t.me/iv?url=https://telegram.org&rhash=",
+ instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash", "https://telegram.org"));
+ parse_internal_link("t.me/iv//////?url=https://telegram.org&rhash=",
+ instant_view("https://t.me/iv?url=https%3A%2F%2Ftelegram.org&rhash", "https://telegram.org"));
+ parse_internal_link("t.me/iv/////1/?url=https://telegram.org&rhash=", nullptr);
+ parse_internal_link("t.me/iv", nullptr);
+ parse_internal_link("t.me/iv?#url=https://telegram.org&rhash=abcdef", nullptr);
+ parse_internal_link("tg:iv?url=https://telegram.org&rhash=abcdef",
+ unknown_deep_link("tg://iv?url=https://telegram.org&rhash=abcdef"));
+
+ parse_internal_link("t.me/addtheme?slug=abcdef", nullptr);
+ parse_internal_link("t.me/addtheme", nullptr);
+ parse_internal_link("t.me/addtheme/", nullptr);
+ parse_internal_link("t.me/addtheme//abcdef", nullptr);
+ parse_internal_link("t.me/addtheme?/abcdef", nullptr);
+ parse_internal_link("t.me/addtheme/?abcdef", nullptr);
+ parse_internal_link("t.me/addtheme/#abcdef", nullptr);
+ parse_internal_link("t.me/addtheme/abacaba", theme("abacaba"));
+ parse_internal_link("t.me/addtheme/aba%20aba", theme("aba aba"));
+ parse_internal_link("t.me/addtheme/123456a", theme("123456a"));
+ parse_internal_link("t.me/addtheme/12345678901", theme("12345678901"));
+ parse_internal_link("t.me/addtheme/123456", theme("123456"));
+ parse_internal_link("t.me/addtheme/123456/123123/12/31/a/s//21w/?asdas#test", theme("123456"));
+
+ parse_internal_link("tg:addtheme?slug=abcdef", theme("abcdef"));
+ parse_internal_link("tg:addtheme?slug=abc%30ef", theme("abc0ef"));
+ parse_internal_link("tg://addtheme?slug=", unknown_deep_link("tg://addtheme?slug="));
+
+ parse_internal_link("t.me/proxy?server=1.2.3.4&port=80&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("t.me/proxy?server=1.2.3.4&port=80adasdas&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("t.me/proxy?server=1.2.3.4&port=adasdas&secret=1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=1.2.3.4&port=65536&secret=1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=", unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=12", unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("google.com", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=dd1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("google.com", 80, "dd1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=de1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=ee1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=ee1234567890abcdef1234567890ABCDEF0",
+ unsupported_proxy());
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=ee1234567890abcdef1234567890ABCDEF%30%30",
+ proxy_mtproto("google.com", 80, "ee1234567890abcdef1234567890ABCDEF00"));
+ parse_internal_link(
+ "t.me/proxy?server=google.com&port=8%30&secret=ee1234567890abcdef1234567890ABCDEF010101010101010101",
+ proxy_mtproto("google.com", 80, "ee1234567890abcdef1234567890ABCDEF010101010101010101"));
+ parse_internal_link("t.me/proxy?server=google.com&port=8%30&secret=7tAAAAAAAAAAAAAAAAAAAAAAAAcuZ29vZ2xlLmNvbQ",
+ proxy_mtproto("google.com", 80, "7tAAAAAAAAAAAAAAAAAAAAAAAAcuZ29vZ2xlLmNvbQ"));
+
+ parse_internal_link("tg:proxy?server=1.2.3.4&port=80&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("tg:proxy?server=1.2.3.4&port=80adasdas&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("1.2.3.4", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("tg:proxy?server=1.2.3.4&port=adasdas&secret=1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("tg:proxy?server=1.2.3.4&port=65536&secret=1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+ parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("google.com", 80, "1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=dd1234567890abcdef1234567890ABCDEF",
+ proxy_mtproto("google.com", 80, "dd1234567890abcdef1234567890ABCDEF"));
+ parse_internal_link("tg:proxy?server=google.com&port=8%30&secret=de1234567890abcdef1234567890ABCDEF",
+ unsupported_proxy());
+
+ parse_internal_link("t.me/socks?server=1.2.3.4&port=80", proxy_socks("1.2.3.4", 80, "", ""));
+ parse_internal_link("t.me/socks?server=1.2.3.4&port=80adasdas", proxy_socks("1.2.3.4", 80, "", ""));
+ parse_internal_link("t.me/socks?server=1.2.3.4&port=adasdas", unsupported_proxy());
+ parse_internal_link("t.me/socks?server=1.2.3.4&port=65536", unsupported_proxy());
+ parse_internal_link("t.me/socks?server=google.com&port=8%30", proxy_socks("google.com", 80, "", ""));
+ parse_internal_link("t.me/socks?server=google.com&port=8%30&user=1&pass=", proxy_socks("google.com", 80, "1", ""));
+ parse_internal_link("t.me/socks?server=google.com&port=8%30&user=&pass=2", proxy_socks("google.com", 80, "", "2"));
+ parse_internal_link("t.me/socks?server=google.com&port=80&user=1&pass=2", proxy_socks("google.com", 80, "1", "2"));
+
+ parse_internal_link("tg:socks?server=1.2.3.4&port=80", proxy_socks("1.2.3.4", 80, "", ""));
+ parse_internal_link("tg:socks?server=1.2.3.4&port=80adasdas", proxy_socks("1.2.3.4", 80, "", ""));
+ parse_internal_link("tg:socks?server=1.2.3.4&port=adasdas", unsupported_proxy());
+ parse_internal_link("tg:socks?server=1.2.3.4&port=65536", unsupported_proxy());
+ parse_internal_link("tg:socks?server=google.com&port=8%30", proxy_socks("google.com", 80, "", ""));
+ parse_internal_link("tg:socks?server=google.com&port=8%30&user=1&pass=", proxy_socks("google.com", 80, "1", ""));
+ parse_internal_link("tg:socks?server=google.com&port=8%30&user=&pass=2", proxy_socks("google.com", 80, "", "2"));
+ parse_internal_link("tg:socks?server=google.com&port=80&user=1&pass=2", proxy_socks("google.com", 80, "1", "2"));
+
+ parse_internal_link("tg:resolve?domain=username&voice%63hat=aasdasd", video_chat("username", "aasdasd", false));
+ parse_internal_link("tg:resolve?domain=username&video%63hat=aasdasd", video_chat("username", "aasdasd", false));
+ parse_internal_link("tg:resolve?domain=username&livestream=aasdasd", video_chat("username", "aasdasd", true));
+ parse_internal_link("TG://resolve?domain=username&voicechat=", video_chat("username", "", false));
+ parse_internal_link("TG://test@resolve?domain=username&voicechat=", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&voicechat=", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&voicechat=", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&voicechat=", nullptr);
+ parse_internal_link("tg:resolve?domain=&voicechat=", unknown_deep_link("tg://resolve?domain=&voicechat="));
+ parse_internal_link("tg:resolve?domain=telegram&&&&&&&voicechat=%30", video_chat("telegram", "0", false));
+
+ parse_internal_link("t.me/username/0/a//s/as?voicechat=", video_chat("username", "", false));
+ parse_internal_link("t.me/username/0/a//s/as?videochat=2", video_chat("username", "2", false));
+ parse_internal_link("t.me/username/0/a//s/as?livestream=3", video_chat("username", "3", true));
+ parse_internal_link("t.me/username/aasdas?test=1&voicechat=#12312", video_chat("username", "", false));
+ parse_internal_link("t.me/username/0?voicechat=", video_chat("username", "", false));
+ parse_internal_link("t.me/username/-1?voicechat=asdasd", video_chat("username", "asdasd", false));
+ parse_internal_link("t.me/username?voicechat=", video_chat("username", "", false));
+ parse_internal_link("t.me/username#voicechat=asdas", public_chat("username"));
+ parse_internal_link("t.me//username?voicechat=", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram?voi%63e%63hat=t%63st", video_chat("telecram", "tcst", false));
+
+ parse_internal_link("tg:resolve?domain=username&start=aasdasd", bot_start("username", "aasdasd"));
+ parse_internal_link("TG://resolve?domain=username&start=", bot_start("username", ""));
+ parse_internal_link("TG://test@resolve?domain=username&start=", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&start=", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&start=", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&start=", nullptr);
+ parse_internal_link("tg:resolve?domain=&start=", unknown_deep_link("tg://resolve?domain=&start="));
+ parse_internal_link("tg:resolve?domain=telegram&&&&&&&start=%30", bot_start("telegram", "0"));
+
+ parse_internal_link("t.me/username/0/a//s/as?start=", bot_start("username", ""));
+ parse_internal_link("t.me/username/aasdas?test=1&start=#12312", bot_start("username", ""));
+ parse_internal_link("t.me/username/0?start=", bot_start("username", ""));
+ parse_internal_link("t.me/username/-1?start=asdasd", bot_start("username", "asdasd"));
+ parse_internal_link("t.me/username?start=", bot_start("username", ""));
+ parse_internal_link("t.me/username#start=asdas", public_chat("username"));
+ parse_internal_link("t.me//username?start=", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram?start=t%63st", bot_start("telecram", "tcst"));
+
+ parse_internal_link("tg:resolve?domain=username&startgroup=aasdasd",
+ bot_start_in_group("username", "aasdasd", nullptr));
+ parse_internal_link("TG://resolve?domain=username&startgroup=", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("TG://test@resolve?domain=username&startgroup=", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&startgroup=", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&startgroup=", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&startgroup=", nullptr);
+ parse_internal_link("tg:resolve?domain=&startgroup=", unknown_deep_link("tg://resolve?domain=&startgroup="));
+ parse_internal_link("tg:resolve?domain=telegram&&&&&&&startgroup=%30", bot_start_in_group("telegram", "0", nullptr));
+
+ parse_internal_link("tg:resolve?domain=username&startgroup", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("tg:resolve?domain=username&startgroup&admin=asdas", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("tg:resolve?domain=username&startgroup&admin=post_messages",
+ bot_start_in_group("username", "", nullptr));
+ parse_internal_link("tg:resolve?domain=username&startgroup=1&admin=delete_messages+anonymous",
+ bot_start_in_group("username", "1",
+ chat_administrator_rights(true, false, false, false, true, false, false, false,
+ false, false, false, true)));
+ parse_internal_link(
+ "tg:resolve?domain=username&startgroup&admin=manage_chat+change_info+post_messages+edit_messages+delete_messages+"
+ "invite_users+restrict_members+pin_messages+manage_topics+promote_members+manage_video_chats+anonymous",
+ bot_start_in_group(
+ "username", "",
+ chat_administrator_rights(true, true, false, false, true, true, true, true, true, true, true, true)));
+
+ parse_internal_link("tg:resolve?domain=username&startchannel", public_chat("username"));
+ parse_internal_link("tg:resolve?domain=username&startchannel&admin=", public_chat("username"));
+ parse_internal_link(
+ "tg:resolve?domain=username&startchannel&admin=post_messages",
+ bot_add_to_channel("username", chat_administrator_rights(true, false, true, false, false, false, true, false,
+ false, false, false, false)));
+ parse_internal_link(
+ "tg:resolve?domain=username&startchannel&admin=manage_chat+change_info+post_messages+edit_messages+delete_"
+ "messages+invite_users+restrict_members+pin_messages+manage_topics+promote_members+manage_video_chats+anonymous",
+ bot_add_to_channel("username", chat_administrator_rights(true, true, true, true, true, true, true, false, false,
+ true, true, false)));
+
+ parse_internal_link("t.me/username/0/a//s/as?startgroup=", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username/aasdas?test=1&startgroup=#12312", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username/0?startgroup=", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username/-1?startgroup=asdasd", bot_start_in_group("username", "asdasd", nullptr));
+ parse_internal_link("t.me/username?startgroup=", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username#startgroup=asdas", public_chat("username"));
+ parse_internal_link("t.me//username?startgroup=", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram?startgroup=t%63st",
+ bot_start_in_group("telecram", "tcst", nullptr));
+
+ parse_internal_link("t.me/username?startgroup", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username?startgroup&admin=asdas", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username?startgroup&admin=post_messages", bot_start_in_group("username", "", nullptr));
+ parse_internal_link("t.me/username?startgroup=1&admin=delete_messages+anonymous",
+ bot_start_in_group("username", "1",
+ chat_administrator_rights(true, false, false, false, true, false, false, false,
+ false, false, false, true)));
+ parse_internal_link(
+ "t.me/"
+ "username?startgroup&admin=manage_chat+change_info+post_messages+edit_messages+delete_messages+invite_users+"
+ "restrict_members+pin_messages+manage_topics+promote_members+manage_video_chats+anonymous",
+ bot_start_in_group(
+ "username", "",
+ chat_administrator_rights(true, true, false, false, true, true, true, true, true, true, true, true)));
+
+ parse_internal_link("t.me/username?startchannel", public_chat("username"));
+ parse_internal_link("t.me/username?startchannel&admin=", public_chat("username"));
+ parse_internal_link(
+ "t.me/username?startchannel&admin=post_messages",
+ bot_add_to_channel("username", chat_administrator_rights(true, false, true, false, false, false, true, false,
+ false, false, false, false)));
+ parse_internal_link(
+ "t.me/"
+ "username?startchannel&admin=manage_chat+change_info+post_messages+edit_messages+delete_messages+invite_users+"
+ "restrict_members+pin_messages+manage_topics+promote_members+manage_video_chats+anonymous",
+ bot_add_to_channel("username", chat_administrator_rights(true, true, true, true, true, true, true, false, false,
+ true, true, false)));
+
+ parse_internal_link("tg:resolve?domain=username&game=aasdasd", game("username", "aasdasd"));
+ parse_internal_link("TG://resolve?domain=username&game=", public_chat("username"));
+ parse_internal_link("TG://test@resolve?domain=username&game=asd", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username&game=asd", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username&game=asd", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username&game=asd", nullptr);
+ parse_internal_link("tg:resolve?domain=&game=asd", unknown_deep_link("tg://resolve?domain=&game=asd"));
+ parse_internal_link("tg:resolve?domain=telegram&&&&&&&game=%30", game("telegram", "0"));
+
+ parse_internal_link("t.me/username/0/a//s/as?game=asd", game("username", "asd"));
+ parse_internal_link("t.me/username/aasdas?test=1&game=asd#12312", game("username", "asd"));
+ parse_internal_link("t.me/username/0?game=asd", game("username", "asd"));
+ parse_internal_link("t.me/username/-1?game=asdasd", game("username", "asdasd"));
+ parse_internal_link("t.me/username?game=asd", game("username", "asd"));
+ parse_internal_link("t.me/username?game=", public_chat("username"));
+ parse_internal_link("t.me/username#game=asdas", public_chat("username"));
+ parse_internal_link("t.me//username?game=asd", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram?game=t%63st", game("telecram", "tcst"));
+
+ parse_internal_link("tg:resolve?domain=username&Game=asd", public_chat("username"));
+ parse_internal_link("TG://test@resolve?domain=username", nullptr);
+ parse_internal_link("tg:resolve:80?domain=username", nullptr);
+ parse_internal_link("tg:http://resolve?domain=username", nullptr);
+ parse_internal_link("tg:https://resolve?domain=username", nullptr);
+ parse_internal_link("tg:resolve?domain=", unknown_deep_link("tg://resolve?domain="));
+ parse_internal_link("tg:resolve?&&&&&&&domain=telegram", public_chat("telegram"));
+
+ parse_internal_link("t.me/a", public_chat("a"));
+ parse_internal_link("t.me/abcdefghijklmnopqrstuvwxyz123456", public_chat("abcdefghijklmnopqrstuvwxyz123456"));
+ parse_internal_link("t.me/abcdefghijklmnopqrstuvwxyz1234567", nullptr);
+ parse_internal_link("t.me/abcdefghijklmnop-qrstuvwxyz", nullptr);
+ parse_internal_link("t.me/abcdefghijklmnop~qrstuvwxyz", nullptr);
+ parse_internal_link("t.me/_asdf", nullptr);
+ parse_internal_link("t.me/0asdf", nullptr);
+ parse_internal_link("t.me/9asdf", nullptr);
+ parse_internal_link("t.me/Aasdf", public_chat("Aasdf"));
+ parse_internal_link("t.me/asdf_", nullptr);
+ parse_internal_link("t.me/asdf0", public_chat("asdf0"));
+ parse_internal_link("t.me/asd__fg", nullptr);
+ parse_internal_link("t.me/username/0/a//s/as?gam=asd", public_chat("username"));
+ parse_internal_link("t.me/username/aasdas?test=1", public_chat("username"));
+ parse_internal_link("t.me/username/0", public_chat("username"));
+ parse_internal_link("t.me//username", nullptr);
+ parse_internal_link("https://telegram.dog/tele%63ram", public_chat("telecram"));
+
+ parse_internal_link(
+ "tg://"
+ "resolve?domain=telegrampassport&bot_id=543260180&scope=%7B%22v%22%3A1%2C%22d%22%3A%5B%7B%22%22%5D%7D%5D%7D&"
+ "public_key=BEGIN%20PUBLIC%20KEY%0A&nonce=b8ee&callback_url=https%3A%2F%2Fcore.telegram.org%2Fpassport%2Fexample%"
+ "3Fpassport_ssid%3Db8ee&payload=nonce",
+ passport_data_request(543260180, "{\"v\":1,\"d\":[{\"\"]}]}", "BEGIN PUBLIC KEY\n", "b8ee",
+ "https://core.telegram.org/passport/example?passport_ssid=b8ee"));
+ parse_internal_link("tg://resolve?domain=telegrampassport&bot_id=12345&public_key=key&scope=asd&payload=nonce",
+ passport_data_request(12345, "asd", "key", "nonce", ""));
+ parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload=nonce",
+ passport_data_request(12345, "asd", "key", "nonce", ""));
+ parse_internal_link("tg://passport?bot_id=0&public_key=key&scope=asd&payload=nonce",
+ unknown_deep_link("tg://passport?bot_id=0&public_key=key&scope=asd&payload=nonce"));
+ parse_internal_link("tg://passport?bot_id=-1&public_key=key&scope=asd&payload=nonce",
+ unknown_deep_link("tg://passport?bot_id=-1&public_key=key&scope=asd&payload=nonce"));
+ parse_internal_link("tg://passport?bot_id=12345&public_key=&scope=asd&payload=nonce",
+ unknown_deep_link("tg://passport?bot_id=12345&public_key=&scope=asd&payload=nonce"));
+ parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=&payload=nonce",
+ unknown_deep_link("tg://passport?bot_id=12345&public_key=key&scope=&payload=nonce"));
+ parse_internal_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload=",
+ unknown_deep_link("tg://passport?bot_id=12345&public_key=key&scope=asd&payload="));
+ parse_internal_link("t.me/telegrampassport?bot_id=12345&public_key=key&scope=asd&payload=nonce",
+ public_chat("telegrampassport"));
+
+ parse_internal_link("tg:premium_offer?ref=abcdef", premium_features("abcdef"));
+ parse_internal_link("tg:premium_offer?ref=abc%30ef", premium_features("abc0ef"));
+ parse_internal_link("tg://premium_offer?ref=", premium_features(""));
+
+ parse_internal_link("tg://settings", settings());
+ parse_internal_link("tg://setting", unknown_deep_link("tg://setting"));
+ parse_internal_link("tg://settings?asdsa?D?SADasD?asD", settings());
+ parse_internal_link("tg://settings#test", settings());
+ parse_internal_link("tg://settings/#test", settings());
+ parse_internal_link("tg://settings/aadsa#test", settings());
+ parse_internal_link("tg://settings/theme#test", settings());
+ parse_internal_link("tg://settings/themes#test", theme_settings());
+ parse_internal_link("tg://settings/themesa#test", settings());
+ parse_internal_link("tg://settings/themes/?as#rad", theme_settings());
+ parse_internal_link("tg://settings/themes/a", settings());
+ parse_internal_link("tg://settings/asdsathemesasdas/devices", settings());
+ parse_internal_link("tg://settings/devices", active_sessions());
+ parse_internal_link("tg://settings/change_number", change_phone_number());
+ parse_internal_link("tg://settings/folders", filter_settings());
+ parse_internal_link("tg://settings/filters", settings());
+ parse_internal_link("tg://settings/language", language_settings());
+ parse_internal_link("tg://settings/privacy", privacy_and_security_settings());
+
+ parse_internal_link("username.t.me////0/a//s/as?start=", bot_start("username", ""));
+ parse_internal_link("username.t.me?start=as", bot_start("username", "as"));
+ parse_internal_link("username.t.me", public_chat("username"));
+ parse_internal_link("aAAb.t.me/12345?single", message("tg:resolve?domain=aaab&post=12345&single"));
+ parse_internal_link("telegram.t.me/195", message("tg:resolve?domain=telegram&post=195"));
+ parse_internal_link("shares.t.me", public_chat("shares"));
+
+ parse_internal_link("c.t.me/12345?single", nullptr);
+ parse_internal_link("aaa.t.me/12345?single", nullptr);
+ parse_internal_link("aaa_.t.me/12345?single", nullptr);
+ parse_internal_link("0aaa.t.me/12345?single", nullptr);
+ parse_internal_link("_aaa.t.me/12345?single", nullptr);
+ parse_internal_link("addemoji.t.me", nullptr);
+ parse_internal_link("addstickers.t.me", nullptr);
+ parse_internal_link("addtheme.t.me", nullptr);
+ parse_internal_link("auth.t.me", nullptr);
+ parse_internal_link("confirmphone.t.me", nullptr);
+ parse_internal_link("invoice.t.me", nullptr);
+ parse_internal_link("joinchat.t.me", nullptr);
+ parse_internal_link("login.t.me", nullptr);
+ parse_internal_link("proxy.t.me", nullptr);
+ parse_internal_link("setlanguage.t.me", nullptr);
+ parse_internal_link("share.t.me", nullptr);
+ parse_internal_link("socks.t.me", nullptr);
+
+ parse_internal_link("www.telegra.ph/", nullptr);
+ parse_internal_link("www.telegrA.ph/#", nullptr);
+ parse_internal_link("www.telegrA.ph/?", instant_view("https://telegra.ph/?", "www.telegrA.ph/?"));
+ parse_internal_link("http://te.leGra.ph/?", instant_view("https://telegra.ph/?", "http://te.leGra.ph/?"));
+ parse_internal_link("https://grAph.org/12345", instant_view("https://telegra.ph/12345", "https://grAph.org/12345"));
+}
diff --git a/protocols/Telegram/tdlib/td/test/main.cpp b/protocols/Telegram/tdlib/td/test/main.cpp
index 0ef46c75b2..064871ae31 100644
--- a/protocols/Telegram/tdlib/td/test/main.cpp
+++ b/protocols/Telegram/tdlib/td/test/main.cpp
@@ -1,40 +1,64 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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/common.h"
+#include "td/utils/crypto.h"
+#include "td/utils/ExitGuard.h"
#include "td/utils/logging.h"
-
-#include <cstring>
+#include "td/utils/OptionParser.h"
+#include "td/utils/port/detail/ThreadIdGuard.h"
+#include "td/utils/port/stacktrace.h"
+#include "td/utils/Slice.h"
+#include "td/utils/Status.h"
+#include "td/utils/tests.h"
#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);
+ SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL));
+ td::ExitGuard exit_guard;
+ td::detail::ThreadIdGuard thread_id_guard;
+ td::Stacktrace::init();
+ td::init_openssl_threads();
+
+ td::TestsRunner &runner = td::TestsRunner::get_default();
+
+ int default_verbosity_level = 1;
+ td::OptionParser options;
+ options.add_option('f', "filter", "run only specified tests",
+ [&](td::Slice filter) { runner.add_substr_filter(filter.str()); });
+ options.add_option('s', "stress", "run tests infinitely", [&] { runner.set_stress_flag(true); });
+ options.add_checked_option('v', "verbosity", "log verbosity level",
+ td::OptionParser::parse_integer(default_verbosity_level));
+ options.add_check([&] {
+ if (default_verbosity_level < 0) {
+ return td::Status::Error("Wrong verbosity level specified");
}
+ return td::Status::OK();
+ });
+ auto r_non_options = options.run(argc, argv, 0);
+ if (r_non_options.is_error()) {
+ LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
+ LOG(PLAIN) << options;
+ return 1;
}
+ SET_VERBOSITY_LEVEL(default_verbosity_level);
+
#if TD_EMSCRIPTEN
emscripten_set_main_loop(
[] {
- if (!td::Test::run_all_step()) {
+ td::TestsRunner &default_runner = td::TestsRunner::get_default();
+ if (!default_runner.run_all_step()) {
emscripten_cancel_main_loop();
}
},
10, 0);
#else
- td::Test::run_all();
+ runner.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
index 473a87140a..9c62415579 100644
--- a/protocols/Telegram/tdlib/td/test/message_entities.cpp
+++ b/protocols/Telegram/tdlib/td/test/message_entities.cpp
@@ -1,28 +1,36 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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/CustomEmojiId.h"
#include "td/telegram/MessageEntity.h"
+#include "td/telegram/UserId.h"
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
#include "td/utils/format.h"
#include "td/utils/logging.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/tests.h"
+#include "td/utils/utf8.h"
-REGISTER_TESTS(message_entities);
+#include <algorithm>
+#include <utility>
-using namespace td;
-
-static void check_mention(string str, std::vector<string> expected) {
- auto result_slice = find_mentions(str);
- std::vector<string> result;
+static void check_mention(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_mentions(str);
+ td::vector<td::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));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
}
}
@@ -39,20 +47,21 @@ TEST(MessageEntities, mention) {
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;
+ check_mention(
+ "@ya @gif @wiki @vid @bing @pic @bold @imdb @ImDb @coub @like @vote @giff @cap ya cap @y @yar @bingg @bin",
+ {"@gif", "@wiki", "@vid", "@bing", "@pic", "@bold", "@imdb", "@ImDb", "@coub", "@like", "@vote", "@giff",
+ "@bingg"});
+}
+
+static void check_bot_command(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_bot_commands(str);
+ td::vector<td::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));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
}
}
@@ -68,15 +77,15 @@ TEST(MessageEntities, bot_command) {
check_bot_command("/test/", {});
}
-static void check_hashtag(string str, std::vector<string> expected) {
- auto result_slice = find_hashtags(str);
- std::vector<string> result;
+static void check_hashtag(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_hashtags(str);
+ td::vector<td::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));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
}
}
@@ -95,29 +104,32 @@ TEST(MessageEntities, hashtag) {
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(" #" + td::string(300, '1'), {});
+ check_hashtag(" #" + td::string(256, '1'), {});
+ check_hashtag(" #" + td::string(256, '1') + "a ", {});
+ check_hashtag(" #" + td::string(255, '1') + "a", {"#" + td::string(255, '1') + "a"});
+ check_hashtag(" #" + td::string(255, '1') + "Я", {"#" + td::string(255, '1') + "Я"});
+ check_hashtag(" #" + td::string(255, '1') + "a" + td::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("#te·st", {"#te·st"});
+ check_hashtag(u8"\U0001F604\U0001F604\U0001F604\U0001F604 \U0001F604\U0001F604\U0001F604#" + td::string(200, '1') +
+ "ООО" + td::string(200, '2'),
+ {"#" + td::string(200, '1') + "ООО" + td::string(53, '2')});
check_hashtag(u8"#a\u2122", {"#a"});
+ check_hashtag("#a൹", {"#a"});
+ check_hashtag("#aඁං෴ก฿", {"#aඁං෴ก"});
}
-static void check_cashtag(string str, std::vector<string> expected) {
- auto result_slice = find_cashtags(str);
- std::vector<string> result;
+static void check_cashtag(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_cashtags(str);
+ td::vector<td::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));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
}
}
@@ -133,8 +145,9 @@ TEST(MessageEntities, cashtag) {
check_cashtag("$ab", {});
check_cashtag("$abc", {});
check_cashtag("$", {});
- check_cashtag("$A", {});
- check_cashtag("$AB", {});
+ check_cashtag("$A", {"$A"});
+ check_cashtag("$AB", {"$AB"});
+ check_cashtag("$ABС", {});
check_cashtag("$АBC", {});
check_cashtag("$АВС", {});
check_cashtag("$ABC", {"$ABC"});
@@ -156,13 +169,172 @@ TEST(MessageEntities, cashtag) {
check_cashtag(" А$ABC ", {});
check_cashtag("$ABC$DEF $GHI $KLM", {"$GHI", "$KLM"});
check_cashtag("$TEST", {"$TEST"});
+ check_cashtag("$1INC", {});
+ check_cashtag("$1INCH", {"$1INCH"});
+ check_cashtag("...$1INCH...", {"$1INCH"});
+ check_cashtag("$1inch", {});
+ check_cashtag("$1INCHA", {});
+ check_cashtag("$1INCHА", {});
check_cashtag(u8"$ABC\u2122", {"$ABC"});
check_cashtag(u8"\u2122$ABC", {"$ABC"});
check_cashtag(u8"\u2122$ABC\u2122", {"$ABC"});
+ check_cashtag("$ABC൹", {"$ABC"});
+ check_cashtag("$ABCඁ", {});
+ check_cashtag("$ABCං", {});
+ check_cashtag("$ABC෴", {});
+ check_cashtag("$ABCก", {});
+ check_cashtag("$ABC฿", {"$ABC"});
+}
+
+static void check_media_timestamp(const td::string &str, const td::vector<std::pair<td::string, td::int32>> &expected) {
+ auto result = td::transform(td::find_media_timestamps(str),
+ [](auto &&entity) { return std::make_pair(entity.first.str(), entity.second); });
+ if (result != expected) {
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, media_timestamp) {
+ check_media_timestamp("", {});
+ check_media_timestamp(":", {});
+ check_media_timestamp(":1", {});
+ check_media_timestamp("a:1", {});
+ check_media_timestamp("01", {});
+ check_media_timestamp("01:", {});
+ check_media_timestamp("01::", {});
+ check_media_timestamp("01::", {});
+ check_media_timestamp("a1:1a", {});
+ check_media_timestamp("a1::01a", {});
+ check_media_timestamp("2001:db8::8a2e:f70:13a4", {});
+ check_media_timestamp("0:00", {{"0:00", 0}});
+ check_media_timestamp("+0:00", {{"0:00", 0}});
+ check_media_timestamp("0:00+", {{"0:00", 0}});
+ check_media_timestamp("a0:00", {});
+ check_media_timestamp("0:00a", {});
+ check_media_timestamp("б0:00", {});
+ check_media_timestamp("0:00б", {});
+ check_media_timestamp("_0:00", {});
+ check_media_timestamp("0:00_", {});
+ check_media_timestamp("00:00:00:00", {});
+ check_media_timestamp("1:1:01 1:1:1", {{"1:1:01", 3661}});
+ check_media_timestamp("0:0:00 00:00 000:00 0000:00 00000:00 00:00:00 000:00:00 00:000:00 00:00:000",
+ {{"0:0:00", 0}, {"00:00", 0}, {"000:00", 0}, {"0000:00", 0}, {"00:00:00", 0}});
+ check_media_timestamp("00:0:00 0:00:00 00::00 :00:00 00:00: 00:00:0 00:00:", {{"00:0:00", 0}, {"0:00:00", 0}});
+ check_media_timestamp("1:1:59 1:1:-1 1:1:60", {{"1:1:59", 3719}});
+ check_media_timestamp("1:59:00 1:-1:00 1:60:00", {{"1:59:00", 7140}, {"1:00", 60}});
+ check_media_timestamp("59:59 60:00", {{"59:59", 3599}, {"60:00", 3600}});
+ check_media_timestamp("9999:59 99:59:59 99:60:59", {{"9999:59", 599999}, {"99:59:59", 360000 - 1}});
+ check_media_timestamp("2001:db8::8a2e:f70:13a4", {});
+}
+
+static void check_bank_card_number(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_bank_card_numbers(str);
+ td::vector<td::string> result;
+ for (auto &it : result_slice) {
+ result.push_back(it.str());
+ }
+ if (result != expected) {
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, bank_card_number) {
+ check_bank_card_number("", {});
+ check_bank_card_number("123456789015", {});
+ check_bank_card_number("1234567890120", {});
+ check_bank_card_number("1234567890121", {});
+ check_bank_card_number("1234567890122", {});
+ check_bank_card_number("1234567890123", {});
+ check_bank_card_number("1234567890124", {});
+ check_bank_card_number("1234567890125", {});
+ check_bank_card_number("1234567890126", {});
+ check_bank_card_number("1234567890127", {});
+ check_bank_card_number("1234567890128", {"1234567890128"});
+ check_bank_card_number("1234567890129", {});
+ check_bank_card_number("12345678901500", {"12345678901500"});
+ check_bank_card_number("123456789012800", {"123456789012800"});
+ check_bank_card_number("1234567890151800", {"1234567890151800"});
+ check_bank_card_number("12345678901280000", {"12345678901280000"});
+ check_bank_card_number("123456789015009100", {"123456789015009100"});
+ check_bank_card_number("1234567890128000000", {"1234567890128000000"});
+ check_bank_card_number("12345678901500910000", {});
+ check_bank_card_number(" - - - - 1 - -- 2 - - -- 34 - - - 56- - 7890150000 - - - -", {});
+ check_bank_card_number(" - - - - 1 - -- 234 - - 56- - 7890150000 - - - -", {"1 - -- 234 - - 56- - 7890150000"});
+ check_bank_card_number("4916-3385-0608-2832; 5280 9342 8317 1080 ;345936346788903",
+ {"4916-3385-0608-2832", "5280 9342 8317 1080", "345936346788903"});
+ check_bank_card_number("4556728228023269, 4916141675244747020, 49161416752447470, 4556728228023269",
+ {"4556728228023269", "4916141675244747020", "4556728228023269"});
+ check_bank_card_number("a1234567890128", {});
+ check_bank_card_number("1234567890128a", {});
+ check_bank_card_number("1234567890128а", {});
+ check_bank_card_number("а1234567890128", {});
+ check_bank_card_number("1234567890128_", {});
+ check_bank_card_number("_1234567890128", {});
+ check_bank_card_number("1234567890128/", {"1234567890128"});
+ check_bank_card_number("\"1234567890128", {"1234567890128"});
+ check_bank_card_number("+1234567890128", {});
}
-static void check_is_email_address(string str, bool expected) {
- bool result = is_email_address(str);
+static void check_tg_url(const td::string &str, const td::vector<td::string> &expected) {
+ auto result_slice = td::find_tg_urls(str);
+ td::vector<td::string> result;
+ for (auto &it : result_slice) {
+ result.push_back(it.str());
+ }
+ if (result != expected) {
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result))
+ << td::tag("expected", td::format::as_array(expected));
+ }
+}
+
+TEST(MessageEntities, tg_url) {
+ check_tg_url("", {});
+ check_tg_url("tg://", {});
+ check_tg_url("tg://a", {"tg://a"});
+ check_tg_url("a", {});
+ check_tg_url("stg://a", {"tg://a"});
+ check_tg_url("asd asdas das ton:asd tg:test ton://resolve tg://resolve TON://_-RESOLVE_- TG://-_RESOLVE-_",
+ {"ton://resolve", "tg://resolve", "TON://_-RESOLVE_-", "TG://-_RESOLVE-_"});
+ check_tg_url("tg:test/", {});
+ check_tg_url("tg:/test/", {});
+ check_tg_url("tg://test/", {"tg://test/"});
+ check_tg_url("tg://test/?", {"tg://test/"});
+ check_tg_url("tg://test/#", {"tg://test/#"});
+ check_tg_url("tg://test?", {"tg://test"});
+ check_tg_url("tg://test#", {"tg://test"});
+ check_tg_url("tg://test/―asd―?asd=asd&asdas=―#――――", {"tg://test/―asd―?asd=asd&asdas=―#――――"});
+ check_tg_url("tg://test/?asd", {"tg://test/?asd"});
+ check_tg_url("tg://test/?.:;,('?!`.:;,('?!`", {"tg://test/"});
+ check_tg_url("tg://test/#asdf", {"tg://test/#asdf"});
+ check_tg_url("tg://test?asdf", {"tg://test?asdf"});
+ check_tg_url("tg://test#asdf", {"tg://test#asdf"});
+ check_tg_url("tg://test?as‖df", {"tg://test?as"});
+ check_tg_url("tg://test?sa<df", {"tg://test?sa"});
+ check_tg_url("tg://test?as>df", {"tg://test?as"});
+ check_tg_url("tg://test?as\"df", {"tg://test?as"});
+ check_tg_url("tg://test?as«df", {"tg://test?as"});
+ check_tg_url("tg://test?as»df", {"tg://test?as"});
+ check_tg_url("tg://test?as(df", {"tg://test?as(df"});
+ check_tg_url("tg://test?as)df", {"tg://test?as)df"});
+ check_tg_url("tg://test?as[df", {"tg://test?as[df"});
+ check_tg_url("tg://test?as]df", {"tg://test?as]df"});
+ check_tg_url("tg://test?as{df", {"tg://test?as{df"});
+ check_tg_url("tg://test?as'df", {"tg://test?as'df"});
+ check_tg_url("tg://test?as}df", {"tg://test?as}df"});
+ check_tg_url("tg://test?as$df", {"tg://test?as$df"});
+ check_tg_url("tg://test?as%df", {"tg://test?as%df"});
+ check_tg_url("tg://%30/sccct", {});
+ check_tg_url("tg://test:asd@google.com:80", {"tg://test"});
+ check_tg_url("tg://google.com", {"tg://google"});
+ check_tg_url("tg://google/.com", {"tg://google/.com"});
+ check_tg_url("tg://127.0.0.1", {"tg://127"});
+ check_tg_url("tg://б.а.н.а.на", {});
+}
+
+static void check_is_email_address(const td::string &str, bool expected) {
+ bool result = td::is_email_address(str);
LOG_IF(FATAL, result != expected) << "Expected " << expected << " as result of is_email_address(" << str << ")";
}
@@ -183,79 +355,79 @@ TEST(MessageEntities, is_email_address) {
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"};
+ td::vector<td::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"};
+ td::vector<td::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"};
+
+ td::vector<td::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.abcdefghi",
+ "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"};
+ td::vector<td::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) {
@@ -279,11 +451,11 @@ TEST(MessageEntities, is_email_address) {
}
}
-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;
+static void check_url(const td::string &str, const td::vector<td::string> &expected_urls,
+ td::vector<td::string> expected_email_addresses = {}) {
+ auto result_slice = td::find_urls(str);
+ td::vector<td::string> result_urls;
+ td::vector<td::string> result_email_addresses;
for (auto &it : result_slice) {
if (!it.second) {
result_urls.push_back(it.first.str());
@@ -292,12 +464,12 @@ static void check_url(string str, std::vector<string> expected_urls,
}
}
if (result_urls != expected_urls) {
- LOG(FATAL) << tag("text", str) << tag("got", format::as_array(result_urls))
- << tag("expected", format::as_array(expected_urls));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result_urls))
+ << td::tag("expected", td::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));
+ LOG(FATAL) << td::tag("text", str) << td::tag("got", td::format::as_array(result_email_addresses))
+ << td::tag("expected", td::format::as_array(expected_email_addresses));
}
}
@@ -320,7 +492,7 @@ TEST(MessageEntities, 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://@@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"});
@@ -328,7 +500,7 @@ TEST(MessageEntities, url) {
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("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"});
@@ -360,7 +532,7 @@ TEST(MessageEntities, url) {
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#", {"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"});
@@ -391,7 +563,8 @@ TEST(MessageEntities, url) {
check_url("http://ÀТеСт.МоСкВач", {"http://ÀТеСт.МоСкВач"});
check_url("ÀÁ.com. ÀÁ.com.", {"ÀÁ.com", "ÀÁ.com"});
check_url("ÀÁ.com,ÀÁ.com.", {"ÀÁ.com", "ÀÁ.com"});
- check_url("teiegram.org", {});
+ check_url("teiegram.org/test", {});
+ check_url("TeiegraM.org/test", {});
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(){}"});
@@ -402,18 +575,20 @@ TEST(MessageEntities, url) {
check_url("http://google_.com", {});
check_url("http://google._com_", {});
check_url("http://[2001:4860:0:2001::68]/", {}); // TODO
+ check_url("tg://resolve", {});
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("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 гришка.рф "
+ "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 "
@@ -469,7 +644,7 @@ TEST(MessageEntities, url) {
"google.com/",
"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-"});
@@ -482,11 +657,11 @@ TEST(MessageEntities, url) {
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.debc@c.com", {"https://a.debc@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://a.bcde.fg@c.com", {"https://a.bcde.fg@c.com"});
+ check_url("https://a:h.bcde.fg@c.com", {"https://a:h.bcde.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", {});
@@ -515,8 +690,9 @@ TEST(MessageEntities, url) {
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("Look mailto:test@example.com", {}, {"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("http://test.com/?#", {"http://test.com/?#"});
check_url("https://t.me/abcdef…", {"https://t.me/abcdef"});
@@ -526,4 +702,1210 @@ TEST(MessageEntities, url) {
check_url("https://t…", {});
check_url("👉http://ab.com/cdefgh-1IJ", {"http://ab.com/cdefgh-1IJ"});
check_url("...👉http://ab.com/cdefgh-1IJ", {}); // TODO
+ check_url(".?", {});
+ check_url("http://test―‑@―google―.―com―/―–―‐―/―/―/―?―‑―#―――", {"http://test―‑@―google―.―com―/―–―‐―/―/―/―?―‑―#―――"});
+ check_url("http://google.com/‖", {"http://google.com/"});
+ check_url("a@b@c.com", {}, {});
+ check_url("abc@c.com@d.com", {});
+ check_url("a@b.com:c@1", {}, {"a@b.com"});
+ check_url("test@test.software", {}, {"test@test.software"});
+ check_url("a:b?@gmail.com", {});
+ check_url("a?:b@gmail.com", {});
+ check_url("a#:b@gmail.com", {});
+ check_url("a:b#@gmail.com", {});
+ check_url("a!:b@gmail.com", {"a!:b@gmail.com"});
+ check_url("a:b!@gmail.com", {"a:b!@gmail.com"});
+ check_url("http://test_.com", {});
+ check_url("test_.com", {});
+ check_url("_test.com", {});
+ check_url("_.test.com", {"_.test.com"});
+}
+
+static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntity> entities,
+ const td::string &expected_str,
+ const td::vector<td::MessageEntity> &expected_entities, bool allow_empty = true,
+ bool skip_new_entities = false, bool skip_bot_commands = false,
+ bool for_draft = true) {
+ ASSERT_TRUE(td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, true, for_draft)
+ .is_ok());
+ ASSERT_STREQ(expected_str, str);
+ ASSERT_EQ(expected_entities, entities);
+}
+
+static void check_fix_formatted_text(td::string str, td::vector<td::MessageEntity> entities, bool allow_empty,
+ bool skip_new_entities, bool skip_bot_commands, bool for_draft) {
+ ASSERT_TRUE(td::fix_formatted_text(str, entities, allow_empty, skip_new_entities, skip_bot_commands, true, for_draft)
+ .is_error());
+}
+
+TEST(MessageEntities, fix_formatted_text) {
+ td::string str;
+ td::string fixed_str;
+ for (auto i = 0; i <= 32; i++) {
+ str += static_cast<char>(i);
+ if (i != 13) {
+ if (i != 10) {
+ fixed_str += ' ';
+ } else {
+ fixed_str += str.back();
+ }
+ }
+ }
+
+ check_fix_formatted_text(str, {}, "", {}, true, true, true, true);
+ check_fix_formatted_text(str, {}, "", {}, true, true, false, true);
+ check_fix_formatted_text(str, {}, "", {}, true, false, true, true);
+ check_fix_formatted_text(str, {}, "", {}, true, false, false, true);
+ check_fix_formatted_text(str, {}, "", {}, true, false, false, false);
+ check_fix_formatted_text(str, {}, false, false, false, false);
+ check_fix_formatted_text(str, {}, false, false, false, true);
+
+ check_fix_formatted_text(" aba\n ", {}, " aba\n ", {}, true, true, true, true);
+ check_fix_formatted_text(" aba\n ", {}, "aba", {}, true, true, true, false);
+ check_fix_formatted_text(" \n ", {}, "", {}, true, true, true, true);
+ check_fix_formatted_text(" \n ", {}, "", {}, true, true, true, false);
+ check_fix_formatted_text(" \n ", {}, false, true, true, false);
+
+ str += "a \r\n ";
+ fixed_str += "a \n ";
+
+ for (td::int32 i = 33; i <= 35; i++) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::Pre, 0, i);
+
+ td::vector<td::MessageEntity> fixed_entities = entities;
+ fixed_entities.back().length = i - 1;
+ check_fix_formatted_text(str, entities, fixed_str, fixed_entities, true, false, false, true);
+
+ td::string expected_str = fixed_str.substr(0, 33);
+ fixed_entities.back().length = i == 33 ? 32 : 33;
+ check_fix_formatted_text(str, entities, expected_str, fixed_entities, false, false, false, false);
+ }
+
+ for (td::int32 i = 33; i <= 35; i++) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::Bold, 0, i);
+
+ td::vector<td::MessageEntity> fixed_entities;
+ if (i != 33) {
+ fixed_entities.emplace_back(td::MessageEntity::Type::Bold, 32, i - 33);
+ }
+ check_fix_formatted_text(str, entities, fixed_str, fixed_entities, true, false, false, true);
+
+ td::string expected_str;
+ if (i != 33) {
+ fixed_entities.back().offset = 0;
+ fixed_entities.back().length = 1;
+ }
+ expected_str = "a";
+ check_fix_formatted_text(str, entities, expected_str, fixed_entities, false, false, false, false);
+ }
+
+ str = "👉 👉 ";
+ for (int i = 0; i < 10; i++) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::Bold, i, 1);
+ if (i != 2 && i != 5 && i != 6) {
+ check_fix_formatted_text(str, entities, true, true, true, true);
+ check_fix_formatted_text(str, entities, false, false, false, false);
+ } else {
+ check_fix_formatted_text(str, entities, str, {}, true, true, true, true);
+ check_fix_formatted_text(str, entities, str.substr(0, str.size() - 2), {}, false, false, false, false);
+ }
+ }
+
+ str = " /test @abaca #ORD $ABC telegram.org ";
+ for (auto for_draft : {false, true}) {
+ td::int32 shift = for_draft ? 2 : 0;
+ td::string expected_str = for_draft ? str : str.substr(2, str.size() - 3);
+
+ for (auto skip_new_entities : {false, true}) {
+ for (auto skip_bot_commands : {false, true}) {
+ td::vector<td::MessageEntity> entities;
+ if (!skip_new_entities) {
+ if (!skip_bot_commands) {
+ entities.emplace_back(td::MessageEntity::Type::BotCommand, shift, 5);
+ }
+ entities.emplace_back(td::MessageEntity::Type::Mention, shift + 6, 6);
+ entities.emplace_back(td::MessageEntity::Type::Hashtag, shift + 13, 4);
+ entities.emplace_back(td::MessageEntity::Type::Cashtag, shift + 18, 4);
+ entities.emplace_back(td::MessageEntity::Type::Url, shift + 24, 12);
+ }
+
+ check_fix_formatted_text(str, {}, expected_str, entities, true, skip_new_entities, skip_bot_commands,
+ for_draft);
+ check_fix_formatted_text(str, {}, expected_str, entities, false, skip_new_entities, skip_bot_commands,
+ for_draft);
+ }
+ }
+ }
+
+ str = "aba \r\n caba ";
+ td::UserId user_id(static_cast<td::int64>(1));
+ for (td::int32 length = 1; length <= 3; length++) {
+ for (td::int32 offset = 0; static_cast<size_t>(offset + length) <= str.size(); offset++) {
+ for (auto type : {td::MessageEntity::Type::Bold, td::MessageEntity::Type::Url, td::MessageEntity::Type::TextUrl,
+ td::MessageEntity::Type::MentionName}) {
+ for (auto for_draft : {false, true}) {
+ fixed_str = for_draft ? "aba \n caba " : "aba \n caba";
+ auto fixed_length = offset <= 4 && offset + length >= 5 ? length - 1 : length;
+ auto fixed_offset = offset >= 5 ? offset - 1 : offset;
+ if (static_cast<size_t>(fixed_offset) >= fixed_str.size()) {
+ fixed_length = 0;
+ }
+ while (static_cast<size_t>(fixed_offset + fixed_length) > fixed_str.size()) {
+ fixed_length--;
+ }
+ if (type == td::MessageEntity::Type::Bold || type == td::MessageEntity::Type::Url) {
+ while (fixed_length > 0 && (fixed_str[fixed_offset] == ' ' || fixed_str[fixed_offset] == '\n')) {
+ fixed_offset++;
+ fixed_length--;
+ }
+ }
+
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(type, offset, length);
+ if (type == td::MessageEntity::Type::TextUrl) {
+ entities.back().argument = "t.me";
+ } else if (type == td::MessageEntity::Type::MentionName) {
+ entities.back().user_id = user_id;
+ }
+ td::vector<td::MessageEntity> fixed_entities;
+ if (fixed_length > 0) {
+ for (auto i = 0; i < length; i++) {
+ if (!td::is_space(str[offset + i]) || type == td::MessageEntity::Type::TextUrl ||
+ type == td::MessageEntity::Type::MentionName) {
+ fixed_entities.emplace_back(type, fixed_offset, fixed_length);
+ if (type == td::MessageEntity::Type::TextUrl) {
+ fixed_entities.back().argument = "t.me";
+ } else if (type == td::MessageEntity::Type::MentionName) {
+ fixed_entities.back().user_id = user_id;
+ }
+ break;
+ }
+ }
+ }
+ check_fix_formatted_text(str, entities, fixed_str, fixed_entities, true, false, false, for_draft);
+ }
+ }
+ }
+ }
+
+ str = "aba caba";
+ for (td::int32 length = -10; length <= 10; length++) {
+ for (td::int32 offset = -10; offset <= 10; offset++) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::Bold, offset, length);
+ if (length < 0 || offset < 0 || (length > 0 && static_cast<size_t>(length + offset) > str.size())) {
+ check_fix_formatted_text(str, entities, true, false, false, false);
+ check_fix_formatted_text(str, entities, false, false, false, true);
+ continue;
+ }
+
+ td::vector<td::MessageEntity> fixed_entities;
+ if (length > 0) {
+ if (offset == 3) {
+ if (length >= 2) {
+ fixed_entities.emplace_back(td::MessageEntity::Type::Bold, offset + 1, length - 1);
+ }
+ } else {
+ fixed_entities.emplace_back(td::MessageEntity::Type::Bold, offset, length);
+ }
+ }
+
+ check_fix_formatted_text(str, entities, str, fixed_entities, true, false, false, false);
+ check_fix_formatted_text(str, entities, str, fixed_entities, false, false, false, true);
+ }
+ }
+
+ str = "abadcaba";
+ for (td::int32 length = 1; length <= 7; length++) {
+ for (td::int32 offset = 0; offset <= 8 - length; offset++) {
+ for (td::int32 length2 = 1; length2 <= 7; length2++) {
+ for (td::int32 offset2 = 0; offset2 <= 8 - length2; offset2++) {
+ if (offset != offset2) {
+ td::vector<td::MessageEntity> entities;
+ entities.emplace_back(td::MessageEntity::Type::TextUrl, offset, length, "t.me");
+ entities.emplace_back(td::MessageEntity::Type::TextUrl, offset2, length2, "t.me");
+ entities.emplace_back(td::MessageEntity::Type::TextUrl, offset2 + length2, 1);
+ td::vector<td::MessageEntity> fixed_entities = entities;
+ fixed_entities.pop_back();
+ std::sort(fixed_entities.begin(), fixed_entities.end());
+ if (fixed_entities[0].offset + fixed_entities[0].length > fixed_entities[1].offset) {
+ fixed_entities.pop_back();
+ }
+ check_fix_formatted_text(str, entities, str, fixed_entities, false, false, false, false);
+ }
+ }
+ }
+ }
+ }
+
+ for (auto text : {" \n ➡️ ➡️ ➡️ ➡️ \n ", "\n\n\nab cd ef gh "}) {
+ str = text;
+ td::vector<td::MessageEntity> entities;
+ td::vector<td::MessageEntity> fixed_entities;
+
+ auto length = td::narrow_cast<int>(td::utf8_utf16_length(str));
+ for (int i = 0; i < 10; i++) {
+ if ((i + 1) * 3 + 2 <= length) {
+ entities.emplace_back(td::MessageEntity::Type::Bold, (i + 1) * 3, 2);
+ }
+ if ((i + 2) * 3 <= length) {
+ entities.emplace_back(td::MessageEntity::Type::Italic, (i + 1) * 3 + 2, 1);
+ }
+
+ if (i < 4) {
+ fixed_entities.emplace_back(td::MessageEntity::Type::Bold, i * 3, 2);
+ }
+ }
+
+ check_fix_formatted_text(str, entities, td::utf8_utf16_substr(str, 3, 11).str(), fixed_entities, false, false,
+ false, false);
+ }
+
+ for (td::string text : {"\t", "\r", "\n", "\t ", "\r ", "\n "}) {
+ for (auto type : {td::MessageEntity::Type::Bold, td::MessageEntity::Type::TextUrl}) {
+ check_fix_formatted_text(text, {{type, 0, 1, "http://telegram.org/"}}, "", {}, true, false, false, true);
+ }
+ }
+ check_fix_formatted_text("\r ", {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Underline, 0, 1}},
+ "", {}, true, false, false, true);
+ check_fix_formatted_text("a \r", {{td::MessageEntity::Type::Bold, 0, 3}, {td::MessageEntity::Type::Underline, 2, 1}},
+ "a ", {{td::MessageEntity::Type::Bold, 0, 2}}, true, false, false, true);
+ check_fix_formatted_text("a \r ", {{td::MessageEntity::Type::Bold, 0, 4}, {td::MessageEntity::Type::Underline, 2, 1}},
+ "a ", {{td::MessageEntity::Type::Bold, 0, 2}}, true, false, false, true);
+ check_fix_formatted_text(
+ "a \r b", {{td::MessageEntity::Type::Bold, 0, 5}, {td::MessageEntity::Type::Underline, 2, 1}}, "a b",
+ {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Bold, 3, 1}}, true, false, false, true);
+
+ check_fix_formatted_text("a\rbc\r",
+ {{td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 0, 2},
+ {td::MessageEntity::Type::Italic, 3, 2},
+ {td::MessageEntity::Type::Bold, 3, 1}},
+ "abc",
+ {{td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Italic, 2, 1}});
+ check_fix_formatted_text("a ", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Bold, 0, 1}}, "a",
+ {{td::MessageEntity::Type::Bold, 0, 1}, {td::MessageEntity::Type::Italic, 0, 1}}, false,
+ false, false, false);
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 1, 1}, {td::MessageEntity::Type::Italic, 0, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 2}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 1, 1}, {td::MessageEntity::Type::Italic, 1, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 1, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Italic, 1, 2}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 3}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Italic, 2, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 3}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 1}, {td::MessageEntity::Type::Italic, 2, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 1}, {td::MessageEntity::Type::Italic, 2, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Bold, 1, 2}},
+ "abc",
+ {{td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 1, 2},
+ {td::MessageEntity::Type::Italic, 1, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Italic, 0, 1}, {td::MessageEntity::Type::Bold, 2, 1}},
+ "abc", {{td::MessageEntity::Type::Italic, 0, 1}, {td::MessageEntity::Type::Bold, 2, 1}});
+ check_fix_formatted_text("@tests @tests", {{td::MessageEntity::Type::Italic, 0, 13}}, "@tests @tests",
+ {{td::MessageEntity::Type::Mention, 0, 6},
+ {td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Mention, 7, 6},
+ {td::MessageEntity::Type::Italic, 7, 6}});
+
+ // __a~b~__
+ check_fix_formatted_text(
+ "ab", {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Strikethrough, 1, 1}}, "ab",
+ {{td::MessageEntity::Type::Underline, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}});
+ check_fix_formatted_text("ab",
+ {{td::MessageEntity::Type::Underline, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}},
+ "ab",
+ {{td::MessageEntity::Type::Underline, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}});
+ check_fix_formatted_text(
+ "ab", {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Underline, 1, 1}}, "ab",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}});
+ check_fix_formatted_text("ab",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1},
+ {td::MessageEntity::Type::Underline, 1, 1}},
+ "ab",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 1, 1}});
+
+ // __||a||b__
+ check_fix_formatted_text("ab", {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}},
+ "ab",
+ {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}});
+ check_fix_formatted_text("ab",
+ {{td::MessageEntity::Type::Underline, 0, 1},
+ {td::MessageEntity::Type::Underline, 1, 1},
+ {td::MessageEntity::Type::Spoiler, 0, 1}},
+ "ab",
+ {{td::MessageEntity::Type::Underline, 0, 2}, {td::MessageEntity::Type::Spoiler, 0, 1}});
+
+ // _*a*_\r_*b*_
+ check_fix_formatted_text("a\rb",
+ {{td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Italic, 2, 1}},
+ "ab", {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Italic, 0, 2}});
+ check_fix_formatted_text("a\nb",
+ {{td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Italic, 2, 1}},
+ "a\nb",
+ {{td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Italic, 2, 1}});
+
+ // ||`a`||
+ check_fix_formatted_text("a", {{td::MessageEntity::Type::Pre, 0, 1}, {td::MessageEntity::Type::Spoiler, 0, 1}}, "a",
+ {{td::MessageEntity::Type::Pre, 0, 1}});
+ check_fix_formatted_text("a", {{td::MessageEntity::Type::Spoiler, 0, 1}, {td::MessageEntity::Type::Pre, 0, 1}}, "a",
+ {{td::MessageEntity::Type::Pre, 0, 1}});
+
+ check_fix_formatted_text("abc",
+ {{td::MessageEntity::Type::Pre, 0, 3}, {td::MessageEntity::Type::Strikethrough, 1, 1}},
+ "abc", {{td::MessageEntity::Type::Pre, 0, 3}});
+ check_fix_formatted_text(
+ "abc", {{td::MessageEntity::Type::Pre, 1, 1}, {td::MessageEntity::Type::Strikethrough, 0, 3}}, "abc",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1},
+ {td::MessageEntity::Type::Pre, 1, 1},
+ {td::MessageEntity::Type::Strikethrough, 2, 1}});
+ check_fix_formatted_text(
+ "abc", {{td::MessageEntity::Type::Pre, 1, 1}, {td::MessageEntity::Type::Strikethrough, 1, 2}}, "abc",
+ {{td::MessageEntity::Type::Pre, 1, 1}, {td::MessageEntity::Type::Strikethrough, 2, 1}});
+ check_fix_formatted_text(
+ "abc", {{td::MessageEntity::Type::Pre, 1, 1}, {td::MessageEntity::Type::Strikethrough, 0, 2}}, "abc",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::Pre, 0, 3}, {td::MessageEntity::Type::BlockQuote, 1, 1}},
+ "abc", {{td::MessageEntity::Type::BlockQuote, 1, 1}});
+ check_fix_formatted_text("abc", {{td::MessageEntity::Type::BlockQuote, 0, 3}, {td::MessageEntity::Type::Pre, 1, 1}},
+ "abc", {{td::MessageEntity::Type::BlockQuote, 0, 3}, {td::MessageEntity::Type::Pre, 1, 1}});
+
+ check_fix_formatted_text("example.com", {}, "example.com", {{td::MessageEntity::Type::Url, 0, 11}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::Pre, 0, 3}}, "example.com",
+ {{td::MessageEntity::Type::Pre, 0, 3}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::BlockQuote, 0, 3}}, "example.com",
+ {{td::MessageEntity::Type::BlockQuote, 0, 3}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::BlockQuote, 0, 11}}, "example.com",
+ {{td::MessageEntity::Type::BlockQuote, 0, 11}, {td::MessageEntity::Type::Url, 0, 11}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::Italic, 0, 11}}, "example.com",
+ {{td::MessageEntity::Type::Url, 0, 11}, {td::MessageEntity::Type::Italic, 0, 11}});
+ check_fix_formatted_text("example.com", {{td::MessageEntity::Type::Italic, 0, 3}}, "example.com",
+ {{td::MessageEntity::Type::Url, 0, 11}, {td::MessageEntity::Type::Italic, 0, 3}});
+ check_fix_formatted_text("example.com a", {{td::MessageEntity::Type::Italic, 0, 13}}, "example.com a",
+ {{td::MessageEntity::Type::Url, 0, 11},
+ {td::MessageEntity::Type::Italic, 0, 11},
+ {td::MessageEntity::Type::Italic, 12, 1}});
+ check_fix_formatted_text("a example.com", {{td::MessageEntity::Type::Italic, 0, 13}}, "a example.com",
+ {{td::MessageEntity::Type::Italic, 0, 2},
+ {td::MessageEntity::Type::Url, 2, 11},
+ {td::MessageEntity::Type::Italic, 2, 11}});
+
+ for (size_t test_n = 0; test_n < 100000; test_n++) {
+ bool is_url = td::Random::fast_bool();
+ td::int32 url_offset = 0;
+ td::int32 url_end = 0;
+ if (is_url) {
+ str = td::string(td::Random::fast(1, 5), 'a') + ":example.com:" + td::string(td::Random::fast(1, 5), 'a');
+ url_offset = static_cast<td::int32>(str.find('e'));
+ url_end = url_offset + 11;
+ } else {
+ str = td::string(td::Random::fast(1, 20), 'a');
+ }
+
+ auto n = td::Random::fast(1, 20);
+ td::vector<td::MessageEntity> entities;
+ for (int j = 0; j < n; j++) {
+ td::int32 type = td::Random::fast(4, 16);
+ td::int32 offset = td::Random::fast(0, static_cast<int>(str.size()) - 1);
+ auto max_length = static_cast<int>(str.size() - offset);
+ if ((test_n & 1) != 0 && max_length > 4) {
+ max_length = 4;
+ }
+ td::int32 length = td::Random::fast(0, max_length);
+ entities.emplace_back(static_cast<td::MessageEntity::Type>(type), offset, length);
+ }
+
+ auto get_type_mask = [](std::size_t length, const td::vector<td::MessageEntity> &entities) {
+ td::vector<td::int32> result(length);
+ for (auto &entity : entities) {
+ for (auto pos = 0; pos < entity.length; pos++) {
+ result[entity.offset + pos] |= (1 << static_cast<td::int32>(entity.type));
+ }
+ }
+ return result;
+ };
+ auto old_type_mask = get_type_mask(str.size(), entities);
+ ASSERT_TRUE(td::fix_formatted_text(str, entities, false, false, true, true, false).is_ok());
+ auto new_type_mask = get_type_mask(str.size(), entities);
+ auto splittable_mask = (1 << 5) | (1 << 6) | (1 << 14) | (1 << 15);
+ auto pre_mask = (1 << 7) | (1 << 8) | (1 << 9);
+ for (std::size_t pos = 0; pos < str.size(); pos++) {
+ if ((new_type_mask[pos] & pre_mask) != 0) {
+ ASSERT_EQ(0, new_type_mask[pos] & splittable_mask);
+ } else {
+ ASSERT_EQ(old_type_mask[pos] & splittable_mask, new_type_mask[pos] & splittable_mask);
+ }
+ }
+ bool keep_url = is_url;
+ td::MessageEntity url_entity(td::MessageEntity::Type::Url, url_offset, url_end - url_offset);
+ for (auto &entity : entities) {
+ if (entity == url_entity) {
+ continue;
+ }
+ td::int32 offset = entity.offset;
+ td::int32 end = offset + entity.length;
+
+ if (keep_url && ((1 << static_cast<td::int32>(entity.type)) & splittable_mask) == 0 &&
+ !(end <= url_offset || url_end <= offset)) {
+ keep_url = (entity.type == td::MessageEntity::Type::BlockQuote && offset <= url_offset && url_end <= end);
+ }
+ }
+ ASSERT_EQ(keep_url, std::count(entities.begin(), entities.end(), url_entity) == 1);
+
+ for (size_t i = 0; i < entities.size(); i++) {
+ auto type_mask = 1 << static_cast<td::int32>(entities[i].type);
+ for (size_t j = i + 1; j < entities.size(); j++) {
+ // sorted
+ ASSERT_TRUE(entities[j].offset > entities[i].offset ||
+ (entities[j].offset == entities[i].offset && entities[j].length <= entities[i].length));
+
+ // not intersecting
+ ASSERT_TRUE(entities[j].offset >= entities[i].offset + entities[i].length ||
+ entities[j].offset + entities[j].length <= entities[i].offset + entities[i].length);
+
+ if (entities[j].offset < entities[i].offset + entities[i].length) { // if nested
+ // types are different
+ ASSERT_TRUE(entities[j].type != entities[i].type);
+
+ // pre can't contain other entities
+ ASSERT_TRUE((type_mask & pre_mask) == 0);
+
+ if ((type_mask & splittable_mask) == 0 && entities[i].type != td::MessageEntity::Type::BlockQuote) {
+ // continuous entities can contain only splittable entities
+ ASSERT_TRUE(((1 << static_cast<td::int32>(entities[j].type)) & splittable_mask) != 0);
+ }
+ }
+ }
+ }
+ }
+
+ check_fix_formatted_text(
+ "\xe2\x80\x8f\xe2\x80\x8f \xe2\x80\x8e\xe2\x80\x8e\xe2\x80\x8e\xe2\x80\x8c \xe2\x80\x8f\xe2\x80\x8e "
+ "\xe2\x80\x8f",
+ {},
+ "\xe2\x80\x8c\xe2\x80\x8f \xe2\x80\x8c\xe2\x80\x8c\xe2\x80\x8e\xe2\x80\x8c \xe2\x80\x8c\xe2\x80\x8e "
+ "\xe2\x80\x8f",
+ {});
+}
+
+static void check_parse_html(td::string text, const td::string &result, const td::vector<td::MessageEntity> &entities) {
+ auto r_entities = td::parse_html(text);
+ ASSERT_TRUE(r_entities.is_ok());
+ ASSERT_EQ(entities, r_entities.ok());
+ ASSERT_STREQ(result, text);
+}
+
+static void check_parse_html(td::string text, td::Slice error_message) {
+ auto r_entities = td::parse_html(text);
+ ASSERT_TRUE(r_entities.is_error());
+ ASSERT_EQ(400, r_entities.error().code());
+ ASSERT_STREQ(error_message, r_entities.error().message());
+}
+
+TEST(MessageEntities, parse_html) {
+ td::string invalid_surrogate_pair_error_message =
+ "Text contains invalid Unicode characters after decoding HTML entities, check for unmatched surrogate code units";
+ check_parse_html("&#57311;", invalid_surrogate_pair_error_message);
+ check_parse_html("&#xDFDF;", invalid_surrogate_pair_error_message);
+ check_parse_html("&#xDFDF", invalid_surrogate_pair_error_message);
+ check_parse_html("🏟 🏟&lt;<abacaba", "Unclosed start tag at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<abac aba>", "Unsupported start tag \"abac\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<abac>", "Unsupported start tag \"abac\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i =aba>", "Empty attribute name in the tag \"i\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i aba>",
+ "Expected equal sign in declaration of an attribute of the tag \"i\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i aba = ", "Unclosed start tag \"i\" at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i aba = 190azAz-.,", "Unexpected end of name token at byte offset 27");
+ check_parse_html("🏟 🏟&lt;<i aba = \"&lt;&gt;&quot;>", "Unclosed start tag at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<i aba = \'&lt;&gt;&quot;>", "Unclosed start tag at byte offset 13");
+ check_parse_html("🏟 🏟&lt;</", "Unexpected end tag at byte offset 13");
+ check_parse_html("🏟 🏟&lt;<b></b></", "Unexpected end tag at byte offset 20");
+ check_parse_html("🏟 🏟&lt;<i>a</i ", "Unclosed end tag at byte offset 17");
+ check_parse_html("🏟 🏟&lt;<i>a</em >",
+ "Unmatched end tag at byte offset 17, expected \"</i>\", found \"</em>\"");
+
+ check_parse_html("", "", {});
+ check_parse_html("➡️ ➡️", "➡️ ➡️", {});
+ check_parse_html("&lt;&gt;&amp;&quot;&laquo;&raquo;&#12345678;", "<>&\"&laquo;&raquo;&#12345678;", {});
+ check_parse_html("➡️ ➡️<i>➡️ ➡️</i>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}});
+ check_parse_html("➡️ ➡️<em>➡️ ➡️</em>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}});
+ check_parse_html("➡️ ➡️<b>➡️ ➡️</b>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Bold, 5, 5}});
+ check_parse_html("➡️ ➡️<strong>➡️ ➡️</strong>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Bold, 5, 5}});
+ check_parse_html("➡️ ➡️<u>➡️ ➡️</u>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Underline, 5, 5}});
+ check_parse_html("➡️ ➡️<ins>➡️ ➡️</ins>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Underline, 5, 5}});
+ check_parse_html("➡️ ➡️<s>➡️ ➡️</s>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Strikethrough, 5, 5}});
+ check_parse_html("➡️ ➡️<strike>➡️ ➡️</strike>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Strikethrough, 5, 5}});
+ check_parse_html("➡️ ➡️<del>➡️ ➡️</del>", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Strikethrough, 5, 5}});
+ check_parse_html("➡️ ➡️<i>➡️ ➡️</i><b>➡️ ➡️</b>", "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_html("🏟 🏟<i>🏟 &lt🏟</i>", "🏟 🏟🏟 <🏟", {{td::MessageEntity::Type::Italic, 5, 6}});
+ check_parse_html("🏟 🏟<i>🏟 &gt;<b aba = caba>&lt🏟</b></i>", "🏟 🏟🏟 ><🏟",
+ {{td::MessageEntity::Type::Italic, 5, 7}, {td::MessageEntity::Type::Bold, 9, 3}});
+ check_parse_html("🏟 🏟&lt;<i aba = 190azAz-. >a</i>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i aba = 190azAz-.>a</i>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i aba = \"&lt;&gt;&quot;\">a</i>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i aba = '&lt;&gt;&quot;'>a</i>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i aba = '&lt;&gt;&quot;'>a</>", "🏟 🏟<a",
+ {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i>🏟 🏟&lt;</>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Italic, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<i>a</ >", "🏟 🏟<a", {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<i>a</i >", "🏟 🏟<a", {{td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_html("🏟 🏟&lt;<b></b>", "🏟 🏟<", {});
+ check_parse_html("<i>\t</i>", "\t", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_html("<i>\r</i>", "\r", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_html("<i>\n</i>", "\n", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_html("➡️ ➡️<span class = \"tg-spoiler\">➡️ ➡️</span><b>➡️ ➡️</b>",
+ "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Spoiler, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_html("🏟 🏟<span class=\"tg-spoiler\">🏟 &lt🏟</span>", "🏟 🏟🏟 <🏟",
+ {{td::MessageEntity::Type::Spoiler, 5, 6}});
+ check_parse_html("🏟 🏟<span class=\"tg-spoiler\">🏟 &gt;<b aba = caba>&lt🏟</b></span>",
+ "🏟 🏟🏟 ><🏟",
+ {{td::MessageEntity::Type::Spoiler, 5, 7}, {td::MessageEntity::Type::Bold, 9, 3}});
+ check_parse_html("➡️ ➡️<tg-spoiler>➡️ ➡️</tg-spoiler><b>➡️ ➡️</b>",
+ "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Spoiler, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_html("🏟 🏟<tg-spoiler>🏟 &lt🏟</tg-spoiler>", "🏟 🏟🏟 <🏟",
+ {{td::MessageEntity::Type::Spoiler, 5, 6}});
+ check_parse_html("🏟 🏟<tg-spoiler>🏟 &gt;<b aba = caba>&lt🏟</b></tg-spoiler>", "🏟 🏟🏟 ><🏟",
+ {{td::MessageEntity::Type::Spoiler, 5, 7}, {td::MessageEntity::Type::Bold, 9, 3}});
+ check_parse_html("<a href=telegram.org>\t</a>", "\t",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href=telegram.org>\r</a>", "\r",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href=telegram.org>\n</a>", "\n",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<code><i><b> </b></i></code><i><b><code> </code></b></i>", " ",
+ {{td::MessageEntity::Type::Code, 0, 1},
+ {td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Code, 1, 1},
+ {td::MessageEntity::Type::Bold, 1, 1},
+ {td::MessageEntity::Type::Italic, 1, 1}});
+ check_parse_html("<i><b> </b> <code> </code></i>", " ",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Code, 2, 1}});
+ check_parse_html("<a href=telegram.org> </a>", " ",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href =\"telegram.org\" > </a>", " ",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href= 'telegram.org' > </a>", " ",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_html("<a href= 'telegram.org?&lt;' > </a>", " ",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/?<"}});
+ check_parse_html("<a> </a>", " ", {});
+ check_parse_html("<a>telegram.org </a>", "telegram.org ", {});
+ check_parse_html("<a>telegram.org</a>", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "http://telegram.org/"}});
+ check_parse_html("<a>https://telegram.org/asdsa?asdasdwe#12e3we</a>", "https://telegram.org/asdsa?asdasdwe#12e3we",
+ {{td::MessageEntity::Type::TextUrl, 0, 42, "https://telegram.org/asdsa?asdasdwe#12e3we"}});
+ check_parse_html("🏟 🏟&lt;<pre >🏟 🏟&lt;</>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Pre, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<code >🏟 🏟&lt;</>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Code, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<pre><code>🏟 🏟&lt;</code></>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Pre, 6, 6}, {td::MessageEntity::Type::Code, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<pre><code class=\"language-\">🏟 🏟&lt;</code></>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::Pre, 6, 6}, {td::MessageEntity::Type::Code, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<pre><code class=\"language-fift\">🏟 🏟&lt;</></>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::PreCode, 6, 6, "fift"}});
+ check_parse_html("🏟 🏟&lt;<code class=\"language-fift\"><pre>🏟 🏟&lt;</></>", "🏟 🏟<🏟 🏟<",
+ {{td::MessageEntity::Type::PreCode, 6, 6, "fift"}});
+ check_parse_html("🏟 🏟&lt;<pre><code class=\"language-fift\">🏟 🏟&lt;</> </>", "🏟 🏟<🏟 🏟< ",
+ {{td::MessageEntity::Type::Pre, 6, 7}, {td::MessageEntity::Type::Code, 6, 6}});
+ check_parse_html("🏟 🏟&lt;<pre> <code class=\"language-fift\">🏟 🏟&lt;</></>", "🏟 🏟< 🏟 🏟<",
+ {{td::MessageEntity::Type::Pre, 6, 7}, {td::MessageEntity::Type::Code, 7, 6}});
+ check_parse_html("➡️ ➡️<tg-emoji emoji-id = \"12345\">➡️ ➡️</tg-emoji><b>➡️ ➡️</b>",
+ "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::CustomEmoji, 5, 5, td::CustomEmojiId(static_cast<td::int64>(12345))},
+ {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_html("🏟 🏟<tg-emoji emoji-id=\"54321\">🏟 &lt🏟</tg-emoji>", "🏟 🏟🏟 <🏟",
+ {{td::MessageEntity::Type::CustomEmoji, 5, 6, td::CustomEmojiId(static_cast<td::int64>(54321))}});
+ check_parse_html("🏟 🏟<b aba = caba><tg-emoji emoji-id=\"1\">🏟</tg-emoji>1</b>", "🏟 🏟🏟1",
+ {{td::MessageEntity::Type::Bold, 5, 3},
+ {td::MessageEntity::Type::CustomEmoji, 5, 2, td::CustomEmojiId(static_cast<td::int64>(1))}});
+}
+
+static void check_parse_markdown(td::string text, const td::string &result,
+ const td::vector<td::MessageEntity> &entities) {
+ auto r_entities = td::parse_markdown_v2(text);
+ ASSERT_TRUE(r_entities.is_ok());
+ ASSERT_EQ(entities, r_entities.ok());
+ ASSERT_STREQ(result, text);
+}
+
+static void check_parse_markdown(td::string text, td::Slice error_message) {
+ auto r_entities = td::parse_markdown_v2(text);
+ ASSERT_TRUE(r_entities.is_error());
+ ASSERT_EQ(400, r_entities.error().code());
+ ASSERT_STREQ(error_message, r_entities.error().message());
+}
+
+TEST(MessageEntities, parse_markdown) {
+ td::Slice reserved_characters("]()>#+-=|{}.!");
+ td::Slice begin_characters("_*[~`");
+ for (char c = 1; c < 126; c++) {
+ if (begin_characters.find(c) != td::Slice::npos) {
+ continue;
+ }
+
+ td::string text(1, c);
+ if (reserved_characters.find(c) == td::Slice::npos) {
+ check_parse_markdown(text, text, {});
+ } else {
+ check_parse_markdown(
+ text, PSLICE() << "Character '" << c << "' is reserved and must be escaped with the preceding '\\'");
+
+ td::string escaped_text = "\\" + text;
+ check_parse_markdown(escaped_text, text, {});
+ }
+ }
+
+ check_parse_markdown("🏟 🏟_abacaba", "Can't find end of Italic entity at byte offset 9");
+ check_parse_markdown("🏟 🏟_abac * asd ", "Can't find end of Bold entity at byte offset 15");
+ check_parse_markdown("🏟 🏟_abac * asd _", "Can't find end of Italic entity at byte offset 21");
+ check_parse_markdown("🏟 🏟`", "Can't find end of Code entity at byte offset 9");
+ check_parse_markdown("🏟 🏟```", "Can't find end of Pre entity at byte offset 9");
+ check_parse_markdown("🏟 🏟```a", "Can't find end of Pre entity at byte offset 9");
+ check_parse_markdown("🏟 🏟```a ", "Can't find end of PreCode entity at byte offset 9");
+ check_parse_markdown("🏟 🏟__🏟 🏟_", "Can't find end of Italic entity at byte offset 20");
+ check_parse_markdown("🏟 🏟_🏟 🏟__", "Can't find end of Underline entity at byte offset 19");
+ check_parse_markdown("🏟 🏟```🏟 🏟`", "Can't find end of Code entity at byte offset 21");
+ check_parse_markdown("🏟 🏟```🏟 🏟_", "Can't find end of PreCode entity at byte offset 9");
+ check_parse_markdown("🏟 🏟```🏟 🏟\\`", "Can't find end of PreCode entity at byte offset 9");
+ check_parse_markdown("[telegram\\.org](asd\\)", "Can't find end of a URL at byte offset 16");
+ check_parse_markdown("[telegram\\.org](", "Can't find end of a URL at byte offset 16");
+ check_parse_markdown("[telegram\\.org](asd", "Can't find end of a URL at byte offset 16");
+ check_parse_markdown("🏟 🏟__🏟 _🏟___", "Can't find end of Italic entity at byte offset 23");
+ check_parse_markdown("🏟 🏟__", "Can't find end of Underline entity at byte offset 9");
+ check_parse_markdown("🏟 🏟||test\\|", "Can't find end of Spoiler entity at byte offset 9");
+ check_parse_markdown("🏟 🏟!", "Character '!' is reserved and must be escaped with the preceding '\\'");
+ check_parse_markdown("🏟 🏟![", "Can't find end of CustomEmoji entity at byte offset 9");
+ check_parse_markdown("🏟 🏟![👍", "Can't find end of CustomEmoji entity at byte offset 9");
+ check_parse_markdown("🏟 🏟![👍]", "Custom emoji entity must contain a tg://emoji URL");
+ check_parse_markdown("🏟 🏟![👍](tg://emoji?id=1234", "Can't find end of a custom emoji URL at byte offset 17");
+ check_parse_markdown("🏟 🏟![👍](t://emoji?id=1234)", "Custom emoji URL must have scheme tg");
+ check_parse_markdown("🏟 🏟![👍](tg:emojis?id=1234)", "Custom emoji URL must have host \"emoji\"");
+ check_parse_markdown("🏟 🏟![👍](tg://emoji#test)", "Custom emoji URL must have an emoji identifier");
+ check_parse_markdown("🏟 🏟![👍](tg://emoji?test=1#&id=25)", "Custom emoji URL must have an emoji identifier");
+ check_parse_markdown("🏟 🏟![👍](tg://emoji?test=1231&id=025)", "Invalid custom emoji identifier specified");
+
+ check_parse_markdown("", "", {});
+ check_parse_markdown("\\\\", "\\", {});
+ check_parse_markdown("\\\\\\", "\\\\", {});
+ check_parse_markdown("\\\\\\\\\\_\\*\\`", "\\\\_*`", {});
+ check_parse_markdown("➡️ ➡️", "➡️ ➡️", {});
+ check_parse_markdown("🏟 🏟``", "🏟 🏟", {});
+ check_parse_markdown("🏟 🏟_abac \\* asd _", "🏟 🏟abac * asd ", {{td::MessageEntity::Type::Italic, 5, 11}});
+ check_parse_markdown("🏟 \\.🏟_🏟\\. 🏟_", "🏟 .🏟🏟. 🏟", {{td::MessageEntity::Type::Italic, 6, 6}});
+ check_parse_markdown("\\\\\\a\\b\\c\\d\\e\\f\\1\\2\\3\\4\\➡️\\", "\\abcdef1234\\➡️\\", {});
+ check_parse_markdown("➡️ ➡️_➡️ ➡️_", "➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}});
+ check_parse_markdown("➡️ ➡️_➡️ ➡️_*➡️ ➡️*", "➡️ ➡️➡️ ➡️➡️ ➡️",
+ {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Bold, 10, 5}});
+ check_parse_markdown("🏟 🏟_🏟 \\.🏟_", "🏟 🏟🏟 .🏟", {{td::MessageEntity::Type::Italic, 5, 6}});
+ check_parse_markdown("🏟 🏟_🏟 *🏟*_", "🏟 🏟🏟 🏟",
+ {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Bold, 8, 2}});
+ check_parse_markdown("🏟 🏟_🏟 __🏟___", "🏟 🏟🏟 🏟",
+ {{td::MessageEntity::Type::Italic, 5, 5}, {td::MessageEntity::Type::Underline, 8, 2}});
+ check_parse_markdown("🏟 🏟__🏟 _🏟_ __", "🏟 🏟🏟 🏟 ",
+ {{td::MessageEntity::Type::Underline, 5, 6}, {td::MessageEntity::Type::Italic, 8, 2}});
+ check_parse_markdown("🏟 🏟__🏟 _🏟_\\___", "🏟 🏟🏟 🏟_",
+ {{td::MessageEntity::Type::Underline, 5, 6}, {td::MessageEntity::Type::Italic, 8, 2}});
+ check_parse_markdown("🏟 🏟`🏟 🏟```", "🏟 🏟🏟 🏟", {{td::MessageEntity::Type::Code, 5, 5}});
+ check_parse_markdown("🏟 🏟```🏟 🏟```", "🏟 🏟 🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 3, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\n🏟```", "🏟 🏟🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 2, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\r🏟```", "🏟 🏟🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 2, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\n\r🏟```", "🏟 🏟🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 2, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\r\n🏟```", "🏟 🏟🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 2, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\n\n🏟```", "🏟 🏟\n🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 3, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟\r\r🏟```", "🏟 🏟\r🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 3, "🏟"}});
+ check_parse_markdown("🏟 🏟```🏟 \\\\\\`🏟```", "🏟 🏟 \\`🏟",
+ {{td::MessageEntity::Type::PreCode, 5, 5, "🏟"}});
+ check_parse_markdown("🏟 🏟**", "🏟 🏟", {});
+ check_parse_markdown("||test||", "test", {{td::MessageEntity::Type::Spoiler, 0, 4}});
+ check_parse_markdown("🏟 🏟``", "🏟 🏟", {});
+ check_parse_markdown("🏟 🏟``````", "🏟 🏟", {});
+ check_parse_markdown("🏟 🏟____", "🏟 🏟", {});
+ check_parse_markdown("`_* *_`__*` `*__", "_* *_ ",
+ {{td::MessageEntity::Type::Code, 0, 5},
+ {td::MessageEntity::Type::Code, 5, 1},
+ {td::MessageEntity::Type::Bold, 5, 1},
+ {td::MessageEntity::Type::Underline, 5, 1}});
+ check_parse_markdown("_* * ` `_", " ",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::Bold, 0, 1},
+ {td::MessageEntity::Type::Code, 2, 1}});
+ check_parse_markdown("[](telegram.org)", "", {});
+ check_parse_markdown("[ ](telegram.org)", " ", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://telegram.org/"}});
+ check_parse_markdown("[ ](as)", " ", {});
+ check_parse_markdown("[telegram\\.org]", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "http://telegram.org/"}});
+ check_parse_markdown("[telegram\\.org]a", "telegram.orga",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "http://telegram.org/"}});
+ check_parse_markdown("[telegram\\.org](telegram.dog)", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "http://telegram.dog/"}});
+ check_parse_markdown("[telegram\\.org](https://telegram.dog?)", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "https://telegram.dog/?"}});
+ check_parse_markdown("[telegram\\.org](https://telegram.dog?\\\\\\()", "telegram.org",
+ {{td::MessageEntity::Type::TextUrl, 0, 12, "https://telegram.dog/?\\("}});
+ check_parse_markdown("[telegram\\.org]()", "telegram.org", {});
+ check_parse_markdown("[telegram\\.org](asdasd)", "telegram.org", {});
+ check_parse_markdown("[telegram\\.org](tg:user?id=123456)", "telegram.org",
+ {{0, 12, td::UserId(static_cast<td::int64>(123456))}});
+ check_parse_markdown("🏟 🏟![👍](TG://EMoJI/?test=1231&id=25#id=32)a", "🏟 🏟👍a",
+ {{td::MessageEntity::Type::CustomEmoji, 5, 2, td::CustomEmojiId(static_cast<td::int64>(25))}});
+}
+
+static void check_parse_markdown_v3(td::string text, td::vector<td::MessageEntity> entities,
+ const td::string &result_text, const td::vector<td::MessageEntity> &result_entities,
+ bool fix = false) {
+ auto parsed_text = td::parse_markdown_v3({std::move(text), std::move(entities)});
+ if (fix) {
+ ASSERT_TRUE(td::fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).is_ok());
+ }
+ ASSERT_STREQ(result_text, parsed_text.text);
+ ASSERT_EQ(result_entities, parsed_text.entities);
+ if (fix) {
+ auto markdown_text = td::get_markdown_v3(parsed_text);
+ ASSERT_TRUE(parsed_text == markdown_text || parsed_text == td::parse_markdown_v3(markdown_text));
+ }
+}
+
+static void check_parse_markdown_v3(td::string text, const td::string &result_text,
+ const td::vector<td::MessageEntity> &result_entities, bool fix = false) {
+ check_parse_markdown_v3(std::move(text), td::vector<td::MessageEntity>(), result_text, result_entities, fix);
+}
+
+TEST(MessageEntities, parse_markdown_v3) {
+ check_parse_markdown_v3("🏟````🏟``🏟`aba🏟```c🏟`aba🏟 daba🏟```c🏟`aba🏟```🏟 `🏟``🏟```",
+ "🏟````🏟``🏟aba🏟```c🏟aba🏟 daba🏟c🏟`aba🏟🏟 `🏟``🏟```",
+ {{td::MessageEntity::Type::Code, 12, 11}, {td::MessageEntity::Type::Pre, 35, 9}});
+ check_parse_markdown_v3(
+ "🏟````🏟``🏟`aba🏟```c🏟`aba🏟 daba🏟```c🏟`aba🏟🏟```🏟 `🏟``🏟```",
+ {{td::MessageEntity::Type::Italic, 12, 1},
+ {td::MessageEntity::Type::Italic, 44, 1},
+ {td::MessageEntity::Type::Bold, 45, 1},
+ {td::MessageEntity::Type::Bold, 49, 2}},
+ "🏟````🏟``🏟`aba🏟c🏟`aba🏟 daba🏟c🏟`aba🏟🏟🏟 `🏟``🏟",
+ {{td::MessageEntity::Type::Italic, 12, 1},
+ {td::MessageEntity::Type::Pre, 18, 16},
+ {td::MessageEntity::Type::Italic, 38, 1},
+ {td::MessageEntity::Type::Bold, 39, 1},
+ {td::MessageEntity::Type::Bold, 43, 2},
+ {td::MessageEntity::Type::Pre, 45, 10}});
+ check_parse_markdown_v3("` `", " ", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_parse_markdown_v3("`\n`", "\n", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_parse_markdown_v3("` `a", " a", {{td::MessageEntity::Type::Code, 0, 1}}, true);
+ check_parse_markdown_v3("`\n`a", "\na", {{td::MessageEntity::Type::Code, 0, 1}}, true);
+ check_parse_markdown_v3("``", "``", {});
+ check_parse_markdown_v3("`a````b```", "`a````b```", {});
+ check_parse_markdown_v3("ab", {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}}, "ab",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}});
+
+ check_parse_markdown_v3("[a](b[c](t.me)", "[a](b[c](t.me)", {});
+ check_parse_markdown_v3("[](t.me)", "[](t.me)", {});
+ check_parse_markdown_v3("[ ](t.me)", " ", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}});
+ check_parse_markdown_v3("[ ](t.me)", "", {}, true);
+ check_parse_markdown_v3("[ ](t.me)a", " a", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}}, true);
+ check_parse_markdown_v3(
+ "[ ](t.me) [ ](t.me)",
+ {{td::MessageEntity::Type::TextUrl, 8, 1, "http://t.me/"}, {10, 1, td::UserId(static_cast<td::int64>(1))}},
+ "[ ](t.me) [ ](t.me)",
+ {{td::MessageEntity::Type::TextUrl, 8, 1, "http://t.me/"}, {10, 1, td::UserId(static_cast<td::int64>(1))}});
+ check_parse_markdown_v3("[\n](t.me)", "\n", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}});
+ check_parse_markdown_v3("[\n](t.me)a", "\na", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}}, true);
+ check_parse_markdown_v3("asd[abcd](google.com)", {{td::MessageEntity::Type::Italic, 0, 5}}, "asdabcd",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::TextUrl, 3, 4, "http://google.com/"},
+ {td::MessageEntity::Type::Italic, 3, 1}});
+ check_parse_markdown_v3("asd[abcd](google.com)efg[hi](https://t.me?t=1#h)e",
+ {{td::MessageEntity::Type::Italic, 0, 5}, {td::MessageEntity::Type::Italic, 18, 31}},
+ "asdabcdefghie",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::TextUrl, 3, 4, "http://google.com/"},
+ {td::MessageEntity::Type::Italic, 3, 1},
+ {td::MessageEntity::Type::Italic, 7, 3},
+ {td::MessageEntity::Type::TextUrl, 10, 2, "https://t.me/?t=1#h"},
+ {td::MessageEntity::Type::Italic, 10, 2},
+ {td::MessageEntity::Type::Italic, 12, 1}});
+ check_parse_markdown_v3(
+ "🏟🏟🏟[🏟🏟🏟🏟🏟](www.🤙.tk#1)🤙🤙🤙[🏟🏟🏟🏟](www.🤙.tk#2)🤙🤙🤙["
+ "🏟🏟🏟🏟](www.🤙.tk#3)🏟🏟🏟[🏟🏟🏟🏟](www.🤙.tk#4)🤙🤙",
+ "🏟🏟🏟🏟🏟🏟🏟🏟🤙🤙🤙🏟🏟🏟🏟🤙🤙🤙🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟"
+ "🏟🤙🤙",
+ {{td::MessageEntity::Type::TextUrl, 6, 10, "http://www.🤙.tk/#1"},
+ {td::MessageEntity::Type::TextUrl, 22, 8, "http://www.🤙.tk/#2"},
+ {td::MessageEntity::Type::TextUrl, 36, 8, "http://www.🤙.tk/#3"},
+ {td::MessageEntity::Type::TextUrl, 50, 8, "http://www.🤙.tk/#4"}});
+ check_parse_markdown_v3(
+ "[🏟🏟🏟🏟🏟](www.🤙.tk#1)[🏟🏟🏟🏟](www.🤙.tk#2)[🏟🏟🏟🏟](www.🤙.tk#3)["
+ "🏟🏟🏟🏟](www.🤙.tk#4)",
+ "🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟",
+ {{td::MessageEntity::Type::TextUrl, 0, 10, "http://www.🤙.tk/#1"},
+ {td::MessageEntity::Type::TextUrl, 10, 8, "http://www.🤙.tk/#2"},
+ {td::MessageEntity::Type::TextUrl, 18, 8, "http://www.🤙.tk/#3"},
+ {td::MessageEntity::Type::TextUrl, 26, 8, "http://www.🤙.tk/#4"}});
+ check_parse_markdown_v3(
+ "🏟🏟🏟[🏟🏟🏟🏟🏟](www.🤙.tk)🤙🤙🤙[🏟🏟🏟🏟](www.🤙.tk)🤙🤙🤙["
+ "🏟🏟🏟🏟](www.🤙.tk)🏟🏟🏟[🏟🏟🏟🏟](www.🤙.tk)🤙🤙",
+ {{td::MessageEntity::Type::Bold, 0, 2},
+ {td::MessageEntity::Type::Bold, 4, 2},
+ {td::MessageEntity::Type::Bold, 7, 2},
+ {td::MessageEntity::Type::Bold, 11, 2},
+ {td::MessageEntity::Type::Bold, 15, 2},
+ {td::MessageEntity::Type::Bold, 18, 2},
+ {td::MessageEntity::Type::Bold, 26, 2},
+ {31, 2, td::UserId(static_cast<td::int64>(1))},
+ {td::MessageEntity::Type::Bold, 35, 1},
+ {td::MessageEntity::Type::Bold, 44, 2},
+ {td::MessageEntity::Type::Bold, 50, 2},
+ {td::MessageEntity::Type::Bold, 54, 2},
+ {56, 2, td::UserId(static_cast<td::int64>(2))},
+ {td::MessageEntity::Type::Bold, 58, 7},
+ {60, 2, td::UserId(static_cast<td::int64>(3))},
+ {td::MessageEntity::Type::Bold, 67, 7},
+ {td::MessageEntity::Type::Bold, 80, 7},
+ {td::MessageEntity::Type::Bold, 89, 25}},
+ "🏟🏟🏟🏟🏟🏟🏟🏟🤙🤙🤙🏟🏟🏟🏟🤙🤙🤙🏟🏟🏟🏟🏟🏟🏟🏟🏟🏟"
+ "🏟🤙🤙",
+ {{td::MessageEntity::Type::Bold, 0, 2},
+ {td::MessageEntity::Type::Bold, 4, 2},
+ {td::MessageEntity::Type::TextUrl, 6, 10, "http://www.🤙.tk/"},
+ {td::MessageEntity::Type::Bold, 6, 2},
+ {td::MessageEntity::Type::Bold, 10, 2},
+ {td::MessageEntity::Type::Bold, 14, 2},
+ {18, 2, td::UserId(static_cast<td::int64>(1))},
+ {td::MessageEntity::Type::TextUrl, 22, 8, "http://www.🤙.tk/"},
+ {30, 2, td::UserId(static_cast<td::int64>(2))},
+ {td::MessageEntity::Type::Bold, 32, 2},
+ {34, 2, td::UserId(static_cast<td::int64>(3))},
+ {td::MessageEntity::Type::Bold, 34, 2},
+ {td::MessageEntity::Type::TextUrl, 36, 8, "http://www.🤙.tk/"},
+ {td::MessageEntity::Type::Bold, 36, 2},
+ {td::MessageEntity::Type::Bold, 40, 4},
+ {td::MessageEntity::Type::Bold, 44, 4},
+ {td::MessageEntity::Type::TextUrl, 50, 8, "http://www.🤙.tk/"},
+ {td::MessageEntity::Type::Bold, 50, 8},
+ {td::MessageEntity::Type::Bold, 58, 4}});
+ check_parse_markdown_v3("[`a`](t.me) [b](t.me)", {{td::MessageEntity::Type::Code, 13, 1}}, "[a](t.me) [b](t.me)",
+ {{td::MessageEntity::Type::Code, 1, 1}, {td::MessageEntity::Type::Code, 11, 1}});
+ check_parse_markdown_v3(
+ "[text](example.com)",
+ {{td::MessageEntity::Type::Strikethrough, 0, 1}, {td::MessageEntity::Type::Strikethrough, 5, 14}}, "text",
+ {{td::MessageEntity::Type::TextUrl, 0, 4, "http://example.com/"}});
+ check_parse_markdown_v3("[text](example.com)",
+ {{td::MessageEntity::Type::Spoiler, 0, 1}, {td::MessageEntity::Type::Spoiler, 5, 14}}, "text",
+ {{td::MessageEntity::Type::TextUrl, 0, 4, "http://example.com/"}});
+
+ check_parse_markdown_v3("🏟[🏟](t.me) `🏟` [🏟](t.me) `a`", "🏟🏟 🏟 🏟 a",
+ {{td::MessageEntity::Type::TextUrl, 2, 2, "http://t.me/"},
+ {td::MessageEntity::Type::Code, 5, 2},
+ {td::MessageEntity::Type::TextUrl, 8, 2, "http://t.me/"},
+ {td::MessageEntity::Type::Code, 11, 1}});
+
+ check_parse_markdown_v3("__ __", " ", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_markdown_v3("__\n__", "\n", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_parse_markdown_v3("__ __a", " a", {}, true);
+ check_parse_markdown_v3("__\n__a", "\na", {}, true);
+ check_parse_markdown_v3("**** __a__ **b** ~~c~~ ||d||", "**** a b c d",
+ {{td::MessageEntity::Type::Italic, 5, 1},
+ {td::MessageEntity::Type::Bold, 7, 1},
+ {td::MessageEntity::Type::Strikethrough, 9, 1},
+ {td::MessageEntity::Type::Spoiler, 11, 1}});
+ check_parse_markdown_v3("тест __аааа__ **бббб** ~~вввв~~ ||гггг||", "тест аааа бббб вввв гггг",
+ {{td::MessageEntity::Type::Italic, 5, 4},
+ {td::MessageEntity::Type::Bold, 10, 4},
+ {td::MessageEntity::Type::Strikethrough, 15, 4},
+ {td::MessageEntity::Type::Spoiler, 20, 4}});
+ check_parse_markdown_v3("___a___ ***b** ~c~~", "___a___ ***b** ~c~~", {});
+ check_parse_markdown_v3(
+ "__asd[ab__cd](t.me)", "asdabcd",
+ {{td::MessageEntity::Type::Italic, 0, 5}, {td::MessageEntity::Type::TextUrl, 3, 4, "http://t.me/"}});
+ check_parse_markdown_v3("__asd[ab__cd](t.me)", "asdabcd",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::TextUrl, 3, 4, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 3, 2}},
+ true);
+ check_parse_markdown_v3("__a #test__test", "__a #test__test", {});
+ check_parse_markdown_v3("a #testtest", {{td::MessageEntity::Type::Italic, 0, 7}}, "a #testtest",
+ {{td::MessageEntity::Type::Italic, 0, 7}});
+
+ // TODO parse_markdown_v3 is not idempotent now, which is bad
+ check_parse_markdown_v3(
+ "~~**~~__**a__", {{td::MessageEntity::Type::Strikethrough, 2, 1}, {td::MessageEntity::Type::Bold, 6, 1}},
+ "**__**a__", {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}}, true);
+ check_parse_markdown_v3("**__**a__",
+ {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}},
+ "__a__", {{td::MessageEntity::Type::Bold, 0, 2}}, true);
+ check_parse_markdown_v3("__a__", {{td::MessageEntity::Type::Bold, 0, 2}}, "a",
+ {{td::MessageEntity::Type::Italic, 0, 1}}, true);
+ check_parse_markdown_v3("~~__~~#test__test", "__#test__test", {{td::MessageEntity::Type::Strikethrough, 0, 2}});
+ check_parse_markdown_v3("__#test__test", {{td::MessageEntity::Type::Strikethrough, 0, 2}}, "#testtest",
+ {{td::MessageEntity::Type::Italic, 0, 5}});
+
+ check_parse_markdown_v3(
+ "~~**~~||**a||", {{td::MessageEntity::Type::Strikethrough, 2, 1}, {td::MessageEntity::Type::Bold, 6, 1}},
+ "**||**a||", {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}}, true);
+ check_parse_markdown_v3("**||**a||",
+ {{td::MessageEntity::Type::Strikethrough, 0, 2}, {td::MessageEntity::Type::Bold, 2, 1}},
+ "||a||", {{td::MessageEntity::Type::Bold, 0, 2}}, true);
+ check_parse_markdown_v3("||a||", {{td::MessageEntity::Type::Bold, 0, 2}}, "a",
+ {{td::MessageEntity::Type::Spoiler, 0, 1}}, true);
+ check_parse_markdown_v3("~~||~~#test||test", "#testtest", {{td::MessageEntity::Type::Spoiler, 0, 5}});
+ check_parse_markdown_v3("||#test||test", {{td::MessageEntity::Type::Strikethrough, 0, 2}}, "#testtest",
+ {{td::MessageEntity::Type::Spoiler, 0, 5}});
+
+ check_parse_markdown_v3("__[ab_](t.me)_", "__ab__", {{td::MessageEntity::Type::TextUrl, 2, 3, "http://t.me/"}});
+ check_parse_markdown_v3(
+ "__[ab__](t.me)_", "ab_",
+ {{td::MessageEntity::Type::TextUrl, 0, 2, "http://t.me/"}, {td::MessageEntity::Type::Italic, 0, 2}});
+ check_parse_markdown_v3("__[__ab__](t.me)__", "____ab____",
+ {{td::MessageEntity::Type::TextUrl, 2, 6, "http://t.me/"}});
+ check_parse_markdown_v3(
+ "__[__ab__](t.me)a__", "____aba",
+ {{td::MessageEntity::Type::TextUrl, 2, 4, "http://t.me/"}, {td::MessageEntity::Type::Italic, 6, 1}});
+ check_parse_markdown_v3("`a` __ab__", {{td::MessageEntity::Type::Bold, 6, 3}}, "a __ab__",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Bold, 4, 3}});
+ check_parse_markdown_v3("`a` __ab__", {{td::MessageEntity::Type::Underline, 5, 1}}, "a __ab__",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Underline, 3, 1}});
+
+ check_parse_markdown_v3("||[ab|](t.me)|", "||ab||", {{td::MessageEntity::Type::TextUrl, 2, 3, "http://t.me/"}});
+ check_parse_markdown_v3(
+ "||[ab||](t.me)|", "ab|",
+ {{td::MessageEntity::Type::TextUrl, 0, 2, "http://t.me/"}, {td::MessageEntity::Type::Spoiler, 0, 2}});
+ check_parse_markdown_v3("||[||ab||](t.me)||", "||||ab||||",
+ {{td::MessageEntity::Type::TextUrl, 2, 6, "http://t.me/"}});
+ check_parse_markdown_v3(
+ "||[||ab||](t.me)a||", "||||aba",
+ {{td::MessageEntity::Type::TextUrl, 2, 4, "http://t.me/"}, {td::MessageEntity::Type::Spoiler, 6, 1}});
+ check_parse_markdown_v3("`a` ||ab||", {{td::MessageEntity::Type::Bold, 6, 3}}, "a ||ab||",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Bold, 4, 3}});
+ check_parse_markdown_v3("`a` ||ab||", {{td::MessageEntity::Type::Underline, 5, 1}}, "a ||ab||",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Underline, 3, 1}});
+
+ check_parse_markdown_v3("`a` @test__test__test", "a @test__test__test", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_parse_markdown_v3("`a` #test__test__test", "a #test__test__test", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_parse_markdown_v3("`a` __@test_test_test__", "a @test_test_test",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Italic, 2, 15}});
+ check_parse_markdown_v3("`a` __#test_test_test__", "a #test_test_test",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Italic, 2, 15}});
+ check_parse_markdown_v3("[a](t.me) __@test**test**test__", "a @testtesttest",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 2, 13},
+ {td::MessageEntity::Type::Bold, 7, 4}});
+ check_parse_markdown_v3("[a](t.me) __#test~~test~~test__", "a #testtesttest",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 2, 13},
+ {td::MessageEntity::Type::Strikethrough, 7, 4}});
+ check_parse_markdown_v3("[a](t.me) __@test__test__test__", "a @testtesttest",
+ {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 2, 5},
+ {td::MessageEntity::Type::Italic, 11, 4}});
+ check_parse_markdown_v3("__**~~__gh**~~", "gh",
+ {{td::MessageEntity::Type::Bold, 0, 2}, {td::MessageEntity::Type::Strikethrough, 0, 2}});
+ check_parse_markdown_v3("__ab**cd~~ef__gh**ij~~", "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Bold, 2, 6},
+ {td::MessageEntity::Type::Strikethrough, 4, 6}});
+ check_parse_markdown_v3("__ab**cd~~ef||gh__ij**kl~~mn||", "abcdefghijklmn",
+ {{td::MessageEntity::Type::Italic, 0, 2},
+ {td::MessageEntity::Type::Bold, 2, 2},
+ {td::MessageEntity::Type::Italic, 2, 2},
+ {td::MessageEntity::Type::Bold, 4, 2},
+ {td::MessageEntity::Type::Italic, 4, 2},
+ {td::MessageEntity::Type::Strikethrough, 4, 2},
+ {td::MessageEntity::Type::Spoiler, 6, 8},
+ {td::MessageEntity::Type::Strikethrough, 6, 6},
+ {td::MessageEntity::Type::Bold, 6, 4},
+ {td::MessageEntity::Type::Italic, 6, 2}},
+ true);
+ check_parse_markdown_v3("__ab**[cd~~ef__](t.me)gh**ij~~", "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Bold, 2, 6},
+ {td::MessageEntity::Type::TextUrl, 2, 4, "http://t.me/"},
+ {td::MessageEntity::Type::Strikethrough, 4, 6}});
+ check_parse_markdown_v3("__ab**[cd~~e](t.me)f__gh**ij~~", "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Bold, 2, 6},
+ {td::MessageEntity::Type::TextUrl, 2, 3, "http://t.me/"},
+ {td::MessageEntity::Type::Strikethrough, 4, 6}});
+ check_parse_markdown_v3("__ab**[cd~~](t.me)ef__gh**ij~~", "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Bold, 2, 6},
+ {td::MessageEntity::Type::TextUrl, 2, 2, "http://t.me/"},
+ {td::MessageEntity::Type::Strikethrough, 4, 6}});
+ check_parse_markdown_v3("[__**bold italic link**__](example.com)", "bold italic link",
+ {{td::MessageEntity::Type::TextUrl, 0, 16, "http://example.com/"},
+ {td::MessageEntity::Type::Bold, 0, 16},
+ {td::MessageEntity::Type::Italic, 0, 16}});
+ check_parse_markdown_v3(
+ "__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) __italic**bold "
+ "italic__bold**__italic__ ~~strikethrough~~ **bold** `code` ```pre``` __[italic__ text_url](telegram.org) "
+ "__italic**bold italic__bold** ||spoiler||",
+ "italic strikethrough bold code pre italic text_url italicbold italicbolditalic strikethrough bold code pre "
+ "italic text_url italicbold italicbold spoiler",
+ {{td::MessageEntity::Type::Italic, 0, 6},
+ {td::MessageEntity::Type::Strikethrough, 7, 13},
+ {td::MessageEntity::Type::Bold, 21, 4},
+ {td::MessageEntity::Type::Code, 26, 4},
+ {td::MessageEntity::Type::Pre, 31, 3},
+ {td::MessageEntity::Type::TextUrl, 35, 15, "http://telegram.org/"},
+ {td::MessageEntity::Type::Italic, 35, 6},
+ {td::MessageEntity::Type::Italic, 51, 17},
+ {td::MessageEntity::Type::Bold, 57, 15},
+ {td::MessageEntity::Type::Italic, 72, 6},
+ {td::MessageEntity::Type::Strikethrough, 79, 13},
+ {td::MessageEntity::Type::Bold, 93, 4},
+ {td::MessageEntity::Type::Code, 98, 4},
+ {td::MessageEntity::Type::Pre, 103, 3},
+ {td::MessageEntity::Type::TextUrl, 107, 15, "http://telegram.org/"},
+ {td::MessageEntity::Type::Italic, 107, 6},
+ {td::MessageEntity::Type::Italic, 123, 17},
+ {td::MessageEntity::Type::Bold, 129, 15},
+ {td::MessageEntity::Type::Spoiler, 145, 7}});
+
+ td::vector<td::string> parts{"a", " #test__a", "__", "**", "~~", "||", "[", "](t.me)", "`"};
+ td::vector<td::MessageEntity::Type> types{
+ td::MessageEntity::Type::Bold, td::MessageEntity::Type::Italic, td::MessageEntity::Type::Underline,
+ td::MessageEntity::Type::Strikethrough, td::MessageEntity::Type::Spoiler, td::MessageEntity::Type::Code,
+ td::MessageEntity::Type::Pre, td::MessageEntity::Type::PreCode, td::MessageEntity::Type::TextUrl,
+ td::MessageEntity::Type::MentionName, td::MessageEntity::Type::Cashtag};
+ for (size_t test_n = 0; test_n < 1000; test_n++) {
+ td::string str;
+ int part_n = td::Random::fast(1, 200);
+ for (int i = 0; i < part_n; i++) {
+ str += parts[td::Random::fast(0, static_cast<int>(parts.size()) - 1)];
+ }
+ td::vector<td::MessageEntity> entities;
+ int entity_n = td::Random::fast(1, 20);
+ for (int i = 0; i < entity_n; i++) {
+ auto type = types[td::Random::fast(0, static_cast<int>(types.size()) - 1)];
+ td::int32 offset = td::Random::fast(0, static_cast<int>(str.size()) - 1);
+ auto max_length = static_cast<int>(str.size() - offset);
+ if ((test_n & 1) != 0 && max_length > 4) {
+ max_length = 4;
+ }
+ td::int32 length = td::Random::fast(0, max_length);
+ entities.emplace_back(type, offset, length);
+ }
+
+ td::FormattedText text{std::move(str), std::move(entities)};
+ while (true) {
+ ASSERT_TRUE(td::fix_formatted_text(text.text, text.entities, true, true, true, true, true).is_ok());
+ auto parsed_text = td::parse_markdown_v3(text);
+ ASSERT_TRUE(td::fix_formatted_text(parsed_text.text, parsed_text.entities, true, true, true, true, true).is_ok());
+ if (parsed_text == text) {
+ break;
+ }
+ text = std::move(parsed_text);
+ }
+ ASSERT_EQ(text, td::parse_markdown_v3(text));
+ auto markdown_text = td::get_markdown_v3(text);
+ ASSERT_TRUE(text == markdown_text || text == td::parse_markdown_v3(markdown_text));
+ }
+}
+
+static void check_get_markdown_v3(const td::string &result_text, const td::vector<td::MessageEntity> &result_entities,
+ td::string text, td::vector<td::MessageEntity> entities) {
+ auto markdown_text = td::get_markdown_v3({std::move(text), std::move(entities)});
+ ASSERT_STREQ(result_text, markdown_text.text);
+ ASSERT_EQ(result_entities, markdown_text.entities);
+}
+
+TEST(MessageEntities, get_markdown_v3) {
+ check_get_markdown_v3("``` ```", {}, " ", {{td::MessageEntity::Type::Pre, 0, 1}});
+ check_get_markdown_v3("` `", {}, " ", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_get_markdown_v3("`\n`", {}, "\n", {{td::MessageEntity::Type::Code, 0, 1}});
+ check_get_markdown_v3("ab", {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}}, "ab",
+ {{td::MessageEntity::Type::Code, 0, 1}, {td::MessageEntity::Type::Pre, 1, 1}});
+
+ check_get_markdown_v3("[ ](http://t.me/)", {}, " ", {{td::MessageEntity::Type::TextUrl, 0, 1, "http://t.me/"}});
+ check_get_markdown_v3(
+ "[ ]t.me[)](http://t.me/) [ ](t.me)", {{25, 1, td::UserId(static_cast<td::int64>(1))}}, "[ ]t.me) [ ](t.me)",
+ {{td::MessageEntity::Type::TextUrl, 7, 1, "http://t.me/"}, {9, 1, td::UserId(static_cast<td::int64>(1))}});
+
+ check_get_markdown_v3("__ __", {}, " ", {{td::MessageEntity::Type::Italic, 0, 1}});
+ check_get_markdown_v3("** **", {}, " ", {{td::MessageEntity::Type::Bold, 0, 1}});
+ check_get_markdown_v3("~~ ~~", {}, " ", {{td::MessageEntity::Type::Strikethrough, 0, 1}});
+ check_get_markdown_v3("|| ||", {}, " ", {{td::MessageEntity::Type::Spoiler, 0, 1}});
+ check_get_markdown_v3("__a__ **b** ~~c~~ ||d|| e", {{td::MessageEntity::Type::PreCode, 24, 1, "C++"}}, "a b c d e",
+ {{td::MessageEntity::Type::Italic, 0, 1},
+ {td::MessageEntity::Type::Bold, 2, 1},
+ {td::MessageEntity::Type::Strikethrough, 4, 1},
+ {td::MessageEntity::Type::Spoiler, 6, 1},
+ {td::MessageEntity::Type::PreCode, 8, 1, "C++"}});
+ check_get_markdown_v3("`ab` ```cd``` ef", {{td::MessageEntity::Type::PreCode, 14, 2, "C++"}}, "ab cd ef",
+ {{td::MessageEntity::Type::Code, 0, 2},
+ {td::MessageEntity::Type::Pre, 3, 2},
+ {td::MessageEntity::Type::PreCode, 6, 2, "C++"}});
+ check_get_markdown_v3("__asd__[__ab__cd](http://t.me/)", {}, "asdabcd",
+ {{td::MessageEntity::Type::Italic, 0, 3},
+ {td::MessageEntity::Type::TextUrl, 3, 4, "http://t.me/"},
+ {td::MessageEntity::Type::Italic, 3, 2}});
+
+ check_get_markdown_v3("__ab", {{td::MessageEntity::Type::Italic, 3, 1}}, "__ab",
+ {{td::MessageEntity::Type::Italic, 3, 1}});
+ check_get_markdown_v3("__ab__**__cd__**~~**__ef__gh**ij~~", {}, "abcdefghij",
+ {{td::MessageEntity::Type::Italic, 0, 2},
+ {td::MessageEntity::Type::Bold, 2, 2},
+ {td::MessageEntity::Type::Italic, 2, 2},
+ {td::MessageEntity::Type::Strikethrough, 4, 6},
+ {td::MessageEntity::Type::Bold, 4, 4},
+ {td::MessageEntity::Type::Italic, 4, 2}});
+ check_get_markdown_v3("[**__bold italic link__**](http://example.com/)", {}, "bold italic link",
+ {{td::MessageEntity::Type::TextUrl, 0, 16, "http://example.com/"},
+ {td::MessageEntity::Type::Bold, 0, 16},
+ {td::MessageEntity::Type::Italic, 0, 16}});
}
diff --git a/protocols/Telegram/tdlib/td/test/mtproto.cpp b/protocols/Telegram/tdlib/td/test/mtproto.cpp
index 7702a1e37b..89f5441ab5 100644
--- a/protocols/Telegram/tdlib/td/test/mtproto.cpp
+++ b/protocols/Telegram/tdlib/td/test/mtproto.cpp
@@ -1,143 +1,276 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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/telegram/ConfigManager.h"
+#include "td/telegram/net/DcId.h"
+#include "td/telegram/net/PublicRsaKeyShared.h"
+#include "td/telegram/net/Session.h"
+#include "td/telegram/NotificationManager.h"
-#include "td/mtproto/crypto.h"
+#include "td/mtproto/AuthData.h"
+#include "td/mtproto/DhCallback.h"
+#include "td/mtproto/DhHandshake.h"
#include "td/mtproto/Handshake.h"
#include "td/mtproto/HandshakeActor.h"
-#include "td/mtproto/HandshakeConnection.h"
+#include "td/mtproto/Ping.h"
#include "td/mtproto/PingConnection.h"
+#include "td/mtproto/ProxySecret.h"
#include "td/mtproto/RawConnection.h"
+#include "td/mtproto/RSA.h"
+#include "td/mtproto/TlsInit.h"
+#include "td/mtproto/TransportType.h"
+#include "td/net/GetHostByNameActor.h"
#include "td/net/Socks5.h"
+#include "td/net/TransparentProxy.h"
-#include "td/telegram/ConfigManager.h"
-#include "td/telegram/net/PublicRsaKeyShared.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+#include "td/utils/base64.h"
+#include "td/utils/BufferedFd.h"
+#include "td/utils/common.h"
+#include "td/utils/crypto.h"
#include "td/utils/logging.h"
+#include "td/utils/port/Clocks.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/port/SocketFd.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
+#include "td/utils/tests.h"
+#include "td/utils/Time.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);
+TEST(Mtproto, GetHostByNameActor) {
+ int threads_n = 1;
+ td::ConcurrentScheduler sched(threads_n, 0);
- int cnt = 3;
+ int cnt = 1;
+ td::vector<td::ActorOwn<td::GetHostByNameActor>> actors;
{
- 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();
+ auto guard = sched.get_main_guard();
+
+ auto run = [&](td::ActorId<td::GetHostByNameActor> actor_id, td::string host, bool prefer_ipv6, bool allow_ok,
+ bool allow_error) {
+ auto promise = td::PromiseCreator::lambda([&cnt, &actors, num = cnt, host, allow_ok,
+ allow_error](td::Result<td::IPAddress> r_ip_address) {
+ if (r_ip_address.is_error() && !allow_error) {
+ LOG(ERROR) << num << " \"" << host << "\" " << r_ip_address.error();
+ }
+ if (r_ip_address.is_ok() && !allow_ok && (r_ip_address.ok().is_ipv6() || r_ip_address.ok().get_ipv4() != 0)) {
+ LOG(ERROR) << num << " \"" << host << "\" " << r_ip_address.ok();
+ }
+ if (--cnt == 0) {
+ actors.clear();
+ td::Scheduler::instance()->finish();
+ }
+ });
+ cnt++;
+ td::send_closure_later(actor_id, &td::GetHostByNameActor::run, host, 443, prefer_ipv6, std::move(promise));
+ };
- 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();
+ td::vector<td::string> hosts = {"127.0.0.2",
+ "1.1.1.1",
+ "localhost",
+ "web.telegram.org",
+ "web.telegram.org.",
+ "москва.рф",
+ "",
+ "%",
+ " ",
+ "a",
+ "\x80",
+ "[]",
+ "127.0.0.1.",
+ "0x12.0x34.0x56.0x78",
+ "0x7f.001",
+ "2001:0db8:85a3:0000:0000:8a2e:0370:7334",
+ "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
+ "[[2001:0db8:85a3:0000:0000:8a2e:0370:7334]]"};
+ for (const auto &types :
+ {td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Native},
+ td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Google},
+ td::vector<td::GetHostByNameActor::ResolverType>{td::GetHostByNameActor::ResolverType::Google,
+ td::GetHostByNameActor::ResolverType::Google,
+ td::GetHostByNameActor::ResolverType::Native}}) {
+ td::GetHostByNameActor::Options options;
+ options.resolver_types = types;
+ options.scheduler_id = threads_n;
+
+ auto actor = td::create_actor<td::GetHostByNameActor>("GetHostByNameActor", std::move(options));
+ auto actor_id = actor.get();
+ actors.push_back(std::move(actor));
+
+ for (auto host : hosts) {
+ for (auto prefer_ipv6 : {false, true}) {
+ bool allow_ok = host.size() > 2 && host[1] != '[';
+ bool allow_both = host == "127.0.0.1." || host == "localhost" || (host == "москва.рф" && prefer_ipv6);
+ bool allow_error = !allow_ok || allow_both;
+ run(actor_id, host, prefer_ipv6, allow_ok, allow_error);
+ }
}
- }))
- .release();
+ }
}
+ cnt--;
sched.start();
while (sched.run_main(10)) {
- // empty;
+ // empty
}
sched.finish();
}
-#endif
+
+TEST(Time, to_unix_time) {
+ ASSERT_EQ(0, td::HttpDate::to_unix_time(1970, 1, 1, 0, 0, 0).move_as_ok());
+ ASSERT_EQ(60 * 60 + 60 + 1, td::HttpDate::to_unix_time(1970, 1, 1, 1, 1, 1).move_as_ok());
+ ASSERT_EQ(24 * 60 * 60, td::HttpDate::to_unix_time(1970, 1, 2, 0, 0, 0).move_as_ok());
+ ASSERT_EQ(31 * 24 * 60 * 60, td::HttpDate::to_unix_time(1970, 2, 1, 0, 0, 0).move_as_ok());
+ ASSERT_EQ(365 * 24 * 60 * 60, td::HttpDate::to_unix_time(1971, 1, 1, 0, 0, 0).move_as_ok());
+ ASSERT_EQ(1562780559, td::HttpDate::to_unix_time(2019, 7, 10, 17, 42, 39).move_as_ok());
+}
+
+TEST(Time, parse_http_date) {
+ ASSERT_EQ(784887151, td::HttpDate::parse_http_date("Tue, 15 Nov 1994 08:12:31 GMT").move_as_ok());
+}
+
+TEST(Mtproto, config) {
+ int threads_n = 0;
+ td::ConcurrentScheduler sched(threads_n, 0);
+
+ int cnt = 1;
+ {
+ auto guard = sched.get_main_guard();
+
+ auto run = [&](auto &func, bool is_test) {
+ auto promise =
+ td::PromiseCreator::lambda([&, num = cnt](td::Result<td::SimpleConfigResult> r_simple_config_result) {
+ if (r_simple_config_result.is_ok()) {
+ auto simple_config_result = r_simple_config_result.move_as_ok();
+ auto date = simple_config_result.r_http_date.is_ok()
+ ? td::to_string(simple_config_result.r_http_date.ok())
+ : (PSTRING() << simple_config_result.r_http_date.error());
+ auto config = simple_config_result.r_config.is_ok()
+ ? to_string(simple_config_result.r_config.ok())
+ : (PSTRING() << simple_config_result.r_config.error());
+ LOG(ERROR) << num << " " << date << " " << config;
+ } else {
+ LOG(ERROR) << num << " " << r_simple_config_result.error();
+ }
+ if (--cnt == 0) {
+ td::Scheduler::instance()->finish();
+ }
+ });
+ cnt++;
+ func(std::move(promise), false, td::Slice(), is_test, -1).release();
+ };
+
+ run(td::get_simple_config_azure, false);
+ run(td::get_simple_config_google_dns, false);
+ run(td::get_simple_config_mozilla_dns, false);
+ run(td::get_simple_config_azure, true);
+ run(td::get_simple_config_google_dns, true);
+ run(td::get_simple_config_mozilla_dns, true);
+ run(td::get_simple_config_firebase_remote_config, false);
+ run(td::get_simple_config_firebase_realtime, false);
+ run(td::get_simple_config_firebase_firestore, false);
+ }
+ cnt--;
+ if (cnt != 0) {
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty;
+ }
+ sched.finish();
+ }
+}
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();
+ td::string data =
+ " hO//tt \b\n\tiwPVovorKtIYtQ8y2ik7CqfJiJ4pJOCLRa4fBmNPixuRPXnBFF/3mTAAZoSyHq4SNylGHz0Cv1/"
+ "FnWWdEV+BPJeOTk+ARHcNkuJBt0CqnfcVCoDOpKqGyq0U31s2MOpQvHgAG+Tlpg02syuH0E4dCGRw5CbJPARiynteb9y5fT5x/"
+ "kmdp6BMR5tWQSQF0liH16zLh8BDSIdiMsikdcwnAvBwdNhRqQBqGx9MTh62MDmlebjtczE9Gz0z5cscUO2yhzGdphgIy6SP+"
+ "bwaqLWYF0XdPGjKLMUEJW+rou6fbL1t/EUXPtU0XmQAnO0Fh86h+AqDMOe30N4qKrPQ== ";
+ auto config = td::decode_config(data).move_as_ok();
}
-class TestPingActor : public Actor {
+class TestPingActor final : public td::Actor {
public:
- TestPingActor(IPAddress ip_address, Status *result) : ip_address_(ip_address), result_(result) {
+ TestPingActor(td::IPAddress ip_address, td::Status *result) : ip_address_(ip_address), result_(result) {
}
private:
- IPAddress ip_address_;
- std::unique_ptr<mtproto::PingConnection> ping_connection_;
- Status *result_;
+ td::IPAddress ip_address_;
+ td::unique_ptr<td::mtproto::PingConnection> ping_connection_;
+ td::Status *result_;
+ bool is_inited_ = false;
- 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));
+ void start_up() final {
+ auto r_socket = td::SocketFd::open(ip_address_);
+ if (r_socket.is_error()) {
+ LOG(ERROR) << "Failed to open socket: " << r_socket.error();
+ return stop();
+ }
- ping_connection_->get_pollable().set_observer(this);
- subscribe(ping_connection_->get_pollable());
+ ping_connection_ = td::mtproto::PingConnection::create_req_pq(
+ td::mtproto::RawConnection::create(
+ ip_address_, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
+ td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr),
+ 3);
+
+ td::Scheduler::subscribe(ping_connection_->get_poll_info().extract_pollable_fd(this));
+ is_inited_ = true;
set_timeout_in(10);
yield();
}
- void tear_down() override {
- unsubscribe_before_close(ping_connection_->get_pollable());
- ping_connection_->close();
- Scheduler::instance()->finish();
+
+ void tear_down() final {
+ if (is_inited_) {
+ td::Scheduler::unsubscribe_before_close(ping_connection_->get_poll_info().get_pollable_fd_ref());
+ }
+ td::Scheduler::instance()->finish();
}
- void loop() override {
+ void loop() final {
auto status = ping_connection_->flush();
if (status.is_error()) {
*result_ = std::move(status);
return stop();
}
if (ping_connection_->was_pong()) {
- LOG(ERROR) << "GOT PONG";
+ LOG(INFO) << "GOT PONG";
return stop();
}
}
- void timeout_expired() override {
- *result_ = Status::Error("Timeout expired");
+ void timeout_expired() final {
+ *result_ = td::Status::Error("Timeout expired");
stop();
}
};
-static IPAddress get_default_ip_address() {
- IPAddress ip_address;
+static td::IPAddress get_default_ip_address() {
+ td::IPAddress ip_address;
+#if TD_EMSCRIPTEN
+ ip_address.init_host_port("venus.web.telegram.org/apiws", 443).ensure();
+#else
ip_address.init_ipv4_port("149.154.167.40", 80).ensure();
+#endif
return ip_address;
}
-class Mtproto_ping : public td::Test {
+static td::int32 get_default_dc_id() {
+ return 10002;
+}
+
+class Mtproto_ping final : 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;
@@ -151,57 +284,65 @@ class Mtproto_ping : public td::Test {
if (result_.is_error()) {
LOG(ERROR) << result_;
}
- ASSERT_TRUE(result_.is_ok());
return false;
}
private:
bool is_inited_ = false;
- ConcurrentScheduler sched_;
- Status result_;
+ td::ConcurrentScheduler sched_{0, 0};
+ td::Status result_;
};
-Mtproto_ping mtproto_ping("Mtproto_ping");
+td::RegisterTest<Mtproto_ping> mtproto_ping("Mtproto_ping");
-class Context : public AuthKeyHandshakeContext {
+class HandshakeContext final : public td::mtproto::AuthKeyHandshakeContext {
public:
- DhCallback *get_dh_callback() override {
+ td::mtproto::DhCallback *get_dh_callback() final {
return nullptr;
}
- PublicRsaKeyInterface *get_public_rsa_key_interface() override {
+ td::mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final {
return &public_rsa_key;
}
private:
- PublicRsaKeyShared public_rsa_key{DcId::empty()};
+ td::PublicRsaKeyShared public_rsa_key{td::DcId::empty(), true};
};
-class HandshakeTestActor : public Actor {
+class HandshakeTestActor final : public td::Actor {
public:
- explicit HandshakeTestActor(Status *result) : result_(result) {
+ HandshakeTestActor(td::int32 dc_id, td::Status *result) : dc_id_(dc_id), result_(result) {
}
private:
- Status *result_;
+ td::int32 dc_id_ = 0;
+ td::Status *result_;
bool wait_for_raw_connection_ = false;
- std::unique_ptr<RawConnection> raw_connection_;
+ td::unique_ptr<td::mtproto::RawConnection> raw_connection_;
bool wait_for_handshake_ = false;
- std::unique_ptr<AuthKeyHandshake> handshake_;
- Status status_;
+ td::unique_ptr<td::mtproto::AuthKeyHandshake> handshake_;
+ td::Status status_;
bool wait_for_result_ = false;
- void tear_down() override {
+ void tear_down() final {
if (raw_connection_) {
raw_connection_->close();
}
- finish(Status::Error("Interrupted"));
+ finish(td::Status::Error("Interrupted"));
}
- void loop() override {
+ void loop() final {
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);
+ auto ip_address = get_default_ip_address();
+ auto r_socket = td::SocketFd::open(ip_address);
+ if (r_socket.is_error()) {
+ finish(td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error()));
+ return stop();
+ }
+
+ raw_connection_ = td::mtproto::RawConnection::create(
+ ip_address, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
+ td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr);
}
if (!wait_for_handshake_ && !handshake_) {
- handshake_ = std::make_unique<AuthKeyHandshake>(0);
+ handshake_ = td::make_unique<td::mtproto::AuthKeyHandshake>(dc_id_, 3600);
}
if (raw_connection_ && handshake_) {
if (wait_for_result_) {
@@ -211,34 +352,37 @@ class HandshakeTestActor : public Actor {
return stop();
}
if (!handshake_->is_ready_for_finish()) {
- finish(Status::Error("Key is not ready.."));
+ finish(td::Status::Error("Key is not ready.."));
return stop();
}
- finish(Status::OK());
+ finish(td::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);
- }))
+ td::create_actor<td::mtproto::HandshakeActor>(
+ "HandshakeActor", std::move(handshake_), std::move(raw_connection_), td::make_unique<HandshakeContext>(),
+ 10.0,
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> raw_connection) {
+ td::send_closure(actor_id, &HandshakeTestActor::got_connection, std::move(raw_connection), 1);
+ }),
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> handshake) {
+ td::send_closure(actor_id, &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) {
+ void got_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection, bool 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();
+ status_ = td::Status::OK();
} else {
status_ = r_raw_connection.move_as_error();
}
@@ -246,7 +390,7 @@ class HandshakeTestActor : public Actor {
loop();
}
- void got_handshake(Result<std::unique_ptr<AuthKeyHandshake>> r_handshake, int32 dummy) {
+ void got_handshake(td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> r_handshake, bool dummy) {
CHECK(wait_for_handshake_);
wait_for_handshake_ = false;
CHECK(r_handshake.is_ok());
@@ -254,23 +398,22 @@ class HandshakeTestActor : public Actor {
loop();
}
- void finish(Status status) {
+ void finish(td::Status status) {
if (!result_) {
return;
}
*result_ = std::move(status);
result_ = nullptr;
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
};
-class Mtproto_handshake : public td::Test {
+class Mtproto_handshake final : 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_.create_actor_unsafe<HandshakeTestActor>(0, "HandshakeTestActor", get_default_dc_id(), &result_).release();
sched_.start();
is_inited_ = true;
}
@@ -283,60 +426,62 @@ class Mtproto_handshake : public td::Test {
if (result_.is_error()) {
LOG(ERROR) << result_;
}
- ASSERT_TRUE(result_.is_ok());
return false;
}
private:
bool is_inited_ = false;
- ConcurrentScheduler sched_;
- Status result_;
+ td::ConcurrentScheduler sched_{0, 0};
+ td::Status result_;
};
-Mtproto_handshake mtproto_handshake("Mtproto_handshake");
+td::RegisterTest<Mtproto_handshake> mtproto_handshake("Mtproto_handshake");
-class Socks5TestActor : public Actor {
+class Socks5TestActor final : public td::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);
- });
+ void start_up() final {
+ auto promise =
+ td::PromiseCreator::lambda([actor_id = actor_id(this)](td::Result<td::BufferedFd<td::SocketFd>> res) {
+ td::send_closure(actor_id, &Socks5TestActor::on_result, std::move(res), false);
+ });
- class Callback : public Socks5::Callback {
+ class Callback final : public td::TransparentProxy::Callback {
public:
- explicit Callback(Promise<SocketFd> promise) : promise_(std::move(promise)) {
+ explicit Callback(td::Promise<td::BufferedFd<td::SocketFd>> promise) : promise_(std::move(promise)) {
}
- void set_result(Result<SocketFd> result) override {
+ void set_result(td::Result<td::BufferedFd<td::SocketFd>> result) final {
promise_.set_result(std::move(result));
}
- void on_connected() override {
+ void on_connected() final {
}
private:
- Promise<SocketFd> promise_;
+ td::Promise<td::BufferedFd<td::SocketFd>> promise_;
};
- IPAddress socks5_ip;
+ td::IPAddress socks5_ip;
socks5_ip.init_ipv4_port("131.191.89.104", 43077).ensure();
- IPAddress mtproto_ip = get_default_ip_address();
+ td::IPAddress mtproto_ip_address = 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())
+ auto r_socket = td::SocketFd::open(socks5_ip);
+ if (r_socket.is_error()) {
+ return promise.set_error(td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error()));
+ }
+ td::create_actor<td::Socks5>("Socks5", r_socket.move_as_ok(), mtproto_ip_address, "", "",
+ td::make_unique<Callback>(std::move(promise)), actor_shared(this))
.release();
}
private:
- void on_result(Result<SocketFd> res, bool dummy) {
+ void on_result(td::Result<td::BufferedFd<td::SocketFd>> res, bool dummy) {
res.ensure();
- Scheduler::instance()->finish();
+ td::Scheduler::instance()->finish();
}
};
TEST(Mtproto, socks5) {
return;
- ConcurrentScheduler sched;
int threads_n = 0;
- sched.init(threads_n);
+ td::ConcurrentScheduler sched(threads_n, 0);
sched.create_actor_unsafe<Socks5TestActor>(0, "Socks5TestActor").release();
sched.start();
@@ -345,3 +490,251 @@ TEST(Mtproto, socks5) {
}
sched.finish();
}
+
+TEST(Mtproto, notifications) {
+ td::vector<td::string> pushes = {
+ "eyJwIjoiSkRnQ3NMRWxEaWhyVWRRN1pYM3J1WVU4TlRBMFhMb0N6UWRNdzJ1cWlqMkdRbVR1WXVvYXhUeFJHaG1QQm8yVElYZFBzX2N3b2RIb3lY"
+ "b2drVjM1dVl0UzdWeElNX1FNMDRKMG1mV3ZZWm4zbEtaVlJ0aFVBNGhYUWlaN0pfWDMyZDBLQUlEOWgzRnZwRjNXUFRHQWRaVkdFYzg3bnFPZ3hD"
+ "NUNMRkM2SU9fZmVqcEpaV2RDRlhBWWpwc1k2aktrbVNRdFZ1MzE5ZW04UFVieXZudFpfdTNud2hjQ0czMk96TGp4S1kyS1lzU21JZm1GMzRmTmw1"
+ "QUxaa2JvY2s2cE5rZEdrak9qYmRLckJyU0ZtWU8tQ0FsRE10dEplZFFnY1U5bVJQdU80b1d2NG5sb1VXS19zSlNTaXdIWEZyb1pWTnZTeFJ0Z1dN"
+ "ZyJ9",
+ "eyJwIjoiSkRnQ3NMRWxEaWlZby1GRWJndk9WaTFkUFdPVmZndzBBWHYwTWNzWDFhWEtNZC03T1Q2WWNfT0taRURHZDJsZ0h0WkhMSllyVG50RE95"
+ "TkY1aXJRQlZ4UUFLQlRBekhPTGZIS3BhQXdoaWd5b3NQd0piWnJVV2xRWmh4eEozUFUzZjBNRTEwX0xNT0pFN0xsVUFaY2dabUNaX2V1QmNPZWNK"
+ "VERxRkpIRGZjN2pBOWNrcFkyNmJRT2dPUEhCeHlEMUVrNVdQcFpLTnlBODVuYzQ1eHFPdERqcU5aVmFLU3pKb2VIcXBQMnJqR29kN2M5YkxsdGd5"
+ "Q0NGd2NBU3dJeDc3QWNWVXY1UnVZIn0"};
+ td::string key =
+ "uBa5yu01a-nJJeqsR3yeqMs6fJLYXjecYzFcvS6jIwS3nefBIr95LWrTm-IbRBNDLrkISz1Sv0KYpDzhU8WFRk1D0V_"
+ "qyO7XsbDPyrYxRBpGxofJUINSjb1uCxoSdoh1_F0UXEA2fWWKKVxL0DKUQssZfbVj3AbRglsWpH-jDK1oc6eBydRiS3i4j-"
+ "H0yJkEMoKRgaF9NaYI4u26oIQ-Ez46kTVU-R7e3acdofOJKm7HIKan_5ZMg82Dvec2M6vc_"
+ "I54Vs28iBx8IbBO1y5z9WSScgW3JCvFFKP2MXIu7Jow5-cpUx6jXdzwRUb9RDApwAFKi45zpv8eb3uPCDAmIQ";
+ td::vector<td::string> decrypted_payloads = {
+ "eyJsb2Nfa2V5IjoiTUVTU0FHRV9URVhUIiwibG9jX2FyZ3MiOlsiQXJzZW55IFNtaXJub3YiLCJhYmNkZWZnIl0sImN1c3RvbSI6eyJtc2dfaWQi"
+ "OiI1OTAwNDciLCJmcm9tX2lkIjoiNjI4MTQifSwiYmFkZ2UiOiI0MDkifQ",
+ "eyJsb2Nfa2V5IjoiIiwibG9jX2FyZ3MiOltdLCJjdXN0b20iOnsiY2hhbm5lbF9pZCI6IjExNzY4OTU0OTciLCJtYXhfaWQiOiIxMzU5In0sImJh"
+ "ZGdlIjoiMCJ9"};
+ key = td::base64url_decode(key).move_as_ok();
+
+ for (size_t i = 0; i < pushes.size(); i++) {
+ auto push = td::base64url_decode(pushes[i]).move_as_ok();
+ auto decrypted_payload = td::base64url_decode(decrypted_payloads[i]).move_as_ok();
+
+ auto key_id = td::mtproto::DhHandshake::calc_key_id(key);
+ ASSERT_EQ(key_id, td::NotificationManager::get_push_receiver_id(push).ok());
+ ASSERT_EQ(decrypted_payload, td::NotificationManager::decrypt_push(key_id, key, push).ok());
+ }
+}
+
+class FastPingTestActor final : public td::Actor {
+ public:
+ explicit FastPingTestActor(td::Status *result) : result_(result) {
+ }
+
+ private:
+ td::Status *result_;
+ td::unique_ptr<td::mtproto::RawConnection> connection_;
+ td::unique_ptr<td::mtproto::AuthKeyHandshake> handshake_;
+ td::ActorOwn<> fast_ping_;
+ int iteration_{0};
+
+ void start_up() final {
+ // Run handshake to create key and salt
+ auto ip_address = get_default_ip_address();
+ auto r_socket = td::SocketFd::open(ip_address);
+ if (r_socket.is_error()) {
+ *result_ = td::Status::Error(PSTRING() << "Failed to open socket: " << r_socket.error());
+ return stop();
+ }
+
+ auto raw_connection = td::mtproto::RawConnection::create(
+ ip_address, td::BufferedFd<td::SocketFd>(r_socket.move_as_ok()),
+ td::mtproto::TransportType{td::mtproto::TransportType::Tcp, 0, td::mtproto::ProxySecret()}, nullptr);
+ auto handshake = td::make_unique<td::mtproto::AuthKeyHandshake>(get_default_dc_id(), 60 * 100 /*temp*/);
+ td::create_actor<td::mtproto::HandshakeActor>(
+ "HandshakeActor", std::move(handshake), std::move(raw_connection), td::make_unique<HandshakeContext>(), 10.0,
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> raw_connection) {
+ td::send_closure(actor_id, &FastPingTestActor::got_connection, std::move(raw_connection), 1);
+ }),
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> handshake) {
+ td::send_closure(actor_id, &FastPingTestActor::got_handshake, std::move(handshake), 1);
+ }))
+ .release();
+ }
+
+ void got_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection, bool dummy) {
+ if (r_raw_connection.is_error()) {
+ *result_ = r_raw_connection.move_as_error();
+ LOG(INFO) << "Receive " << *result_ << " instead of a connection";
+ return stop();
+ }
+ connection_ = r_raw_connection.move_as_ok();
+ loop();
+ }
+
+ void got_handshake(td::Result<td::unique_ptr<td::mtproto::AuthKeyHandshake>> r_handshake, bool dummy) {
+ if (r_handshake.is_error()) {
+ *result_ = r_handshake.move_as_error();
+ LOG(INFO) << "Receive " << *result_ << " instead of a handshake";
+ return stop();
+ }
+ handshake_ = r_handshake.move_as_ok();
+ loop();
+ }
+
+ void got_raw_connection(td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_connection) {
+ if (r_connection.is_error()) {
+ *result_ = r_connection.move_as_error();
+ LOG(INFO) << "Receive " << *result_ << " instead of a handshake";
+ return stop();
+ }
+ connection_ = r_connection.move_as_ok();
+ LOG(INFO) << "RTT: " << connection_->extra().rtt;
+ connection_->extra().rtt = 0;
+ loop();
+ }
+
+ void loop() final {
+ if (handshake_ && connection_) {
+ LOG(INFO) << "Iteration " << iteration_;
+ if (iteration_ == 6) {
+ return stop();
+ }
+ td::unique_ptr<td::mtproto::AuthData> auth_data;
+ if (iteration_ % 2 == 0) {
+ auth_data = td::make_unique<td::mtproto::AuthData>();
+ auth_data->set_tmp_auth_key(handshake_->get_auth_key());
+ auth_data->set_server_time_difference(handshake_->get_server_time_diff());
+ auth_data->set_server_salt(handshake_->get_server_salt(), td::Time::now());
+ auth_data->set_future_salts({td::mtproto::ServerSalt{0u, 1e20, 1e30}}, td::Time::now());
+ auth_data->set_use_pfs(true);
+ td::uint64 session_id = 0;
+ do {
+ td::Random::secure_bytes(reinterpret_cast<td::uint8 *>(&session_id), sizeof(session_id));
+ } while (session_id == 0);
+ auth_data->set_session_id(session_id);
+ }
+ iteration_++;
+ fast_ping_ = create_ping_actor(
+ td::Slice(), std::move(connection_), std::move(auth_data),
+ td::PromiseCreator::lambda(
+ [actor_id = actor_id(this)](td::Result<td::unique_ptr<td::mtproto::RawConnection>> r_raw_connection) {
+ td::send_closure(actor_id, &FastPingTestActor::got_raw_connection, std::move(r_raw_connection));
+ }),
+ td::ActorShared<>());
+ }
+ }
+
+ void tear_down() final {
+ td::Scheduler::instance()->finish();
+ }
+};
+
+class Mtproto_FastPing final : public td::Test {
+ public:
+ using Test::Test;
+ bool step() final {
+ if (!is_inited_) {
+ sched_.create_actor_unsafe<FastPingTestActor>(0, "FastPingTestActor", &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_;
+ }
+ return false;
+ }
+
+ private:
+ bool is_inited_ = false;
+ td::ConcurrentScheduler sched_{0, 0};
+ td::Status result_;
+};
+td::RegisterTest<Mtproto_FastPing> mtproto_fastping("Mtproto_FastPing");
+
+TEST(Mtproto, Grease) {
+ td::string s(10000, '0');
+ td::mtproto::Grease::init(s);
+ for (auto c : s) {
+ CHECK((c & 0xF) == 0xA);
+ }
+ for (size_t i = 1; i < s.size(); i += 2) {
+ CHECK(s[i] != s[i - 1]);
+ }
+}
+
+TEST(Mtproto, TlsTransport) {
+ int threads_n = 1;
+ td::ConcurrentScheduler sched(threads_n, 0);
+ {
+ auto guard = sched.get_main_guard();
+ class RunTest final : public td::Actor {
+ void start_up() final {
+ class Callback final : public td::TransparentProxy::Callback {
+ public:
+ void set_result(td::Result<td::BufferedFd<td::SocketFd>> result) final {
+ if (result.is_ok()) {
+ LOG(ERROR) << "Unexpectedly succeeded to connect to MTProto proxy";
+ } else if (result.error().message() != "Response hash mismatch") {
+ LOG(ERROR) << "Receive unexpected result " << result.error();
+ }
+ td::Scheduler::instance()->finish();
+ }
+ void on_connected() final {
+ }
+ };
+
+ const td::string domain = "www.google.com";
+ td::IPAddress ip_address;
+ auto resolve_status = ip_address.init_host_port(domain, 443);
+ if (resolve_status.is_error()) {
+ LOG(ERROR) << resolve_status;
+ td::Scheduler::instance()->finish();
+ return;
+ }
+ auto r_socket = td::SocketFd::open(ip_address);
+ if (r_socket.is_error()) {
+ LOG(ERROR) << "Failed to open socket: " << r_socket.error();
+ td::Scheduler::instance()->finish();
+ return;
+ }
+ td::create_actor<td::mtproto::TlsInit>("TlsInit", r_socket.move_as_ok(), domain, "0123456789secret",
+ td::make_unique<Callback>(), td::ActorShared<>(),
+ td::Clocks::system() - td::Time::now())
+ .release();
+ }
+ };
+ td::create_actor<RunTest>("RunTest").release();
+ }
+
+ sched.start();
+ while (sched.run_main(10)) {
+ // empty
+ }
+ sched.finish();
+}
+
+TEST(Mtproto, RSA) {
+ auto pem = td::Slice(
+ "-----BEGIN RSA PUBLIC KEY-----\n"
+ "MIIBCgKCAQEAr4v4wxMDXIaMOh8bayF/NyoYdpcysn5EbjTIOZC0RkgzsRj3SGlu\n"
+ "52QSz+ysO41dQAjpFLgxPVJoOlxXokaOq827IfW0bGCm0doT5hxtedu9UCQKbE8j\n"
+ "lDOk+kWMXHPZFJKWRgKgTu9hcB3y3Vk+JFfLpq3d5ZB48B4bcwrRQnzkx5GhWOFX\n"
+ "x73ZgjO93eoQ2b/lDyXxK4B4IS+hZhjzezPZTI5upTRbs5ljlApsddsHrKk6jJNj\n"
+ "8Ygs/ps8e6ct82jLXbnndC9s8HjEvDvBPH9IPjv5JUlmHMBFZ5vFQIfbpo0u0+1P\n"
+ "n6bkEi5o7/ifoyVv2pAZTRwppTz0EuXD8QIDAQAB\n"
+ "-----END RSA PUBLIC KEY-----");
+ auto rsa = td::mtproto::RSA::from_pem_public_key(pem).move_as_ok();
+ ASSERT_EQ(-7596991558377038078, rsa.get_fingerprint());
+ ASSERT_EQ(256u, rsa.size());
+
+ td::string to(256, '\0');
+ rsa.encrypt(pem.substr(0, 256), to);
+ ASSERT_EQ("U2nJEtB2AgpHrm3HB0yhpTQgb0wbesi9Pv/W1v/vULU=", td::base64_encode(td::sha256(to)));
+}
diff --git a/protocols/Telegram/tdlib/td/test/online.cpp b/protocols/Telegram/tdlib/td/test/online.cpp
new file mode 100644
index 0000000000..6bcdcec833
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/online.cpp
@@ -0,0 +1,632 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// 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/ClientActor.h"
+#include "td/telegram/Log.h"
+#include "td/telegram/td_api_json.h"
+#include "td/telegram/TdCallback.h"
+
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+#include "td/actor/MultiPromise.h"
+#include "td/actor/PromiseFuture.h"
+
+#include "td/utils/crypto.h"
+#include "td/utils/FileLog.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/misc.h"
+#include "td/utils/OptionParser.h"
+#include "td/utils/port/path.h"
+#include "td/utils/port/signals.h"
+#include "td/utils/Promise.h"
+#include "td/utils/Random.h"
+
+#include <iostream>
+#include <map>
+#include <memory>
+
+namespace td {
+
+template <class T>
+static void check_td_error(T &result) {
+ LOG_CHECK(result->get_id() != td::td_api::error::ID) << to_string(result);
+}
+
+class TestClient : public Actor {
+ public:
+ explicit TestClient(td::string name) : name_(std::move(name)) {
+ }
+ struct Update {
+ td::uint64 id;
+ td::tl_object_ptr<td::td_api::Object> object;
+ Update(td::uint64 id, td::tl_object_ptr<td::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;
+ };
+ struct RemoveListener {
+ void operator()(Listener *listener) {
+ send_closure(self, &TestClient::remove_listener, listener);
+ }
+ ActorId<TestClient> self;
+ };
+ using ListenerToken = std::unique_ptr<Listener, RemoveListener>;
+ void close(td::Promise<> close_promise) {
+ close_promise_ = std::move(close_promise);
+ td_client_.reset();
+ }
+
+ td::unique_ptr<td::TdCallback> make_td_callback() {
+ class TdCallbackImpl : public td::TdCallback {
+ public:
+ explicit TdCallbackImpl(td::ActorId<TestClient> client) : client_(client) {
+ }
+ void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) override {
+ send_closure(client_, &TestClient::on_result, id, std::move(result));
+ }
+ void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) override {
+ send_closure(client_, &TestClient::on_error, id, std::move(error));
+ }
+ TdCallbackImpl(const TdCallbackImpl &) = delete;
+ TdCallbackImpl &operator=(const TdCallbackImpl &) = delete;
+ TdCallbackImpl(TdCallbackImpl &&) = delete;
+ TdCallbackImpl &operator=(TdCallbackImpl &&) = delete;
+ ~TdCallbackImpl() override {
+ send_closure(client_, &TestClient::on_closed);
+ }
+
+ private:
+ td::ActorId<TestClient> client_;
+ };
+ return td::make_unique<TdCallbackImpl>(actor_id(this));
+ }
+
+ void add_listener(td::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(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) {
+ on_update(std::make_shared<Update>(id, std::move(result)));
+ }
+ void on_error(td::uint64 id, td::tl_object_ptr<td::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 {
+ auto old_context = set_context(std::make_shared<td::ActorContext>());
+ set_tag(name_);
+ LOG(INFO) << "START UP!";
+
+ td_client_ = td::create_actor<td::ClientActor>("Td-proxy", make_td_callback());
+ }
+
+ td::ActorOwn<td::ClientActor> td_client_;
+
+ td::string name_;
+
+ private:
+ td::vector<td::unique_ptr<Listener>> listeners_;
+ td::vector<Listener *> pending_remove_;
+
+ td::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.set_value(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 FunctionT, class CallbackT>
+ void send_query(td::tl_object_ptr<FunctionT> function, CallbackT callback) {
+ auto id = current_query_id_++;
+
+ using ResultT = typename FunctionT::ReturnType;
+ sent_queries_[id] =
+ [callback = Promise<ResultT>(std::move(callback))](Result<tl_object_ptr<td_api::Object>> r_obj) mutable {
+ TRY_RESULT_PROMISE(callback, obj, std::move(r_obj));
+ if (obj->get_id() == td::td_api::error::ID) {
+ auto err = move_tl_object_as<td_api::error>(std::move(obj));
+ callback.set_error(Status::Error(err->code_, err->message_));
+ return;
+ }
+ callback.set_value(move_tl_object_as<typename ResultT::element_type>(std::move(obj)));
+ };
+ send_closure(client_->td_client_, &td::ClientActor::request, id, std::move(function));
+ }
+
+ protected:
+ std::map<td::uint64, Promise<td::tl_object_ptr<td::td_api::Object>>> sent_queries_;
+ TestClient *client_ = nullptr;
+ td::uint64 current_query_id_ = 1;
+
+ virtual void start_up() {
+ }
+ void stop() {
+ client_->remove_listener(this);
+ client_ = nullptr;
+ }
+ bool is_alive() const {
+ return client_ != nullptr;
+ }
+};
+
+class InitTask : public Task {
+ public:
+ struct Options {
+ string name;
+ int32 api_id;
+ string api_hash;
+ };
+ InitTask(Options options, td::Promise<> promise) : options_(std::move(options)), promise_(std::move(promise)) {
+ }
+
+ private:
+ Options options_;
+ td::Promise<> promise_;
+ bool start_flag_{false};
+
+ void start_up() override {
+ send_query(td::make_tl_object<td::td_api::getAuthorizationState>(),
+ [this](auto res) { this->process_authorization_state(res.move_as_ok()); });
+ }
+ void process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state) {
+ start_flag_ = true;
+ td::tl_object_ptr<td::td_api::Function> function;
+ switch (authorization_state->get_id()) {
+ case td::td_api::authorizationStateReady::ID:
+ promise_.set_value({});
+ stop();
+ break;
+ case td::td_api::authorizationStateWaitTdlibParameters::ID: {
+ auto request = td::td_api::make_object<td::td_api::setTdlibParameters>();
+ request->use_test_dc_ = true;
+ request->database_directory_ = options_.name + TD_DIR_SLASH;
+ request->use_message_database_ = true;
+ request->use_secret_chats_ = true;
+ request->api_id_ = options_.api_id;
+ request->api_hash_ = options_.api_hash;
+ request->system_language_code_ = "en";
+ request->device_model_ = "Desktop";
+ request->application_version_ = "tdclient-test";
+ request->enable_storage_optimizer_ = true;
+ send(std::move(request));
+ break;
+ }
+ default:
+ LOG(ERROR) << "???";
+ promise_.set_error(
+ Status::Error(PSLICE() << "Unexpected authorization state " << to_string(authorization_state)));
+ stop();
+ break;
+ }
+ }
+ template <class T>
+ void send(T &&query) {
+ send_query(std::move(query), [this](auto res) {
+ if (is_alive()) {
+ res.ensure();
+ }
+ });
+ }
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!start_flag_) {
+ return;
+ }
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td::td_api::updateAuthorizationState::ID) {
+ auto update_authorization_state = td::move_tl_object_as<td::td_api::updateAuthorizationState>(update->object);
+ process_authorization_state(std::move(update_authorization_state->authorization_state_));
+ }
+ }
+};
+
+class GetMe : public Task {
+ public:
+ struct Result {
+ int64 user_id;
+ int64 chat_id;
+ };
+ explicit GetMe(Promise<Result> promise) : promise_(std::move(promise)) {
+ }
+ void start_up() override {
+ send_query(td::make_tl_object<td::td_api::getMe>(), [this](auto res) { with_user_id(res.move_as_ok()->id_); });
+ }
+
+ private:
+ Promise<Result> promise_;
+ Result result_;
+
+ void with_user_id(int64 user_id) {
+ result_.user_id = user_id;
+ send_query(td::make_tl_object<td::td_api::createPrivateChat>(user_id, false),
+ [this](auto res) { with_chat_id(res.move_as_ok()->id_); });
+ }
+
+ void with_chat_id(int64 chat_id) {
+ result_.chat_id = chat_id;
+ promise_.set_value(std::move(result_));
+ stop();
+ }
+};
+
+class UploadFile : public Task {
+ public:
+ struct Result {
+ std::string content;
+ std::string remote_id;
+ };
+ UploadFile(std::string dir, std::string content, int64 chat_id, Promise<Result> promise)
+ : dir_(std::move(dir)), content_(std::move(content)), chat_id_(std::move(chat_id)), promise_(std::move(promise)) {
+ }
+ void start_up() override {
+ auto hash = hex_encode(sha256(content_)).substr(0, 10);
+ content_path_ = dir_ + TD_DIR_SLASH + hash + ".data";
+ id_path_ = dir_ + TD_DIR_SLASH + hash + ".id";
+
+ auto r_id = read_file(id_path_);
+ if (r_id.is_ok() && r_id.ok().size() > 10) {
+ auto id = r_id.move_as_ok();
+ LOG(ERROR) << "Got file from cache";
+ Result res;
+ res.content = std::move(content_);
+ res.remote_id = id.as_slice().str();
+ promise_.set_value(std::move(res));
+ stop();
+ return;
+ }
+
+ write_file(content_path_, content_).ensure();
+
+ send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageDocument>(
+ td::make_tl_object<td::td_api::inputFileLocal>(content_path_), nullptr, true,
+ td::make_tl_object<td::td_api::formattedText>("tag", td::Auto()))),
+ [this](auto res) { with_message(res.move_as_ok()); });
+ }
+
+ private:
+ std::string dir_;
+ std::string content_path_;
+ std::string id_path_;
+ std::string content_;
+ int64 chat_id_;
+ Promise<Result> promise_;
+ int64 file_id_{0};
+
+ void with_message(td::tl_object_ptr<td_api::message> message) {
+ CHECK(message->content_->get_id() == td::td_api::messageDocument::ID);
+ auto messageDocument = td::move_tl_object_as<td::td_api::messageDocument>(message->content_);
+ on_file(*messageDocument->document_->document_, true);
+ }
+
+ void on_file(const td_api::file &file, bool force = false) {
+ if (force) {
+ file_id_ = file.id_;
+ }
+ if (file.id_ != file_id_) {
+ return;
+ }
+ if (file.remote_->is_uploading_completed_) {
+ Result res;
+ res.content = std::move(content_);
+ res.remote_id = file.remote_->id_;
+
+ unlink(content_path_).ignore();
+ atomic_write_file(id_path_, res.remote_id).ignore();
+
+ promise_.set_value(std::move(res));
+ stop();
+ }
+ }
+
+ void process_update(std::shared_ptr<TestClient::Update> update) override {
+ if (!update->object) {
+ return;
+ }
+ if (update->object->get_id() == td::td_api::updateFile::ID) {
+ auto updateFile = td::move_tl_object_as<td::td_api::updateFile>(update->object);
+ on_file(*updateFile->file_);
+ }
+ }
+};
+
+class TestDownloadFile : public Task {
+ public:
+ TestDownloadFile(std::string remote_id, std::string content, Promise<Unit> promise)
+ : remote_id_(std::move(remote_id)), content_(std::move(content)), promise_(std::move(promise)) {
+ }
+ void start_up() override {
+ send_query(td::make_tl_object<td::td_api::getRemoteFile>(remote_id_, nullptr),
+ [this](auto res) { start_file(*res.ok()); });
+ }
+
+ private:
+ std::string remote_id_;
+ std::string content_;
+ Promise<Unit> promise_;
+ struct Range {
+ size_t begin;
+ size_t end;
+ };
+ int32 file_id_{0};
+ std::vector<Range> ranges_;
+
+ void start_file(const td_api::file &file) {
+ LOG(ERROR) << "Start";
+ file_id_ = file.id_;
+ // CHECK(!file.local_->is_downloading_active_);
+ // CHECK(!file.local_->is_downloading_completed_);
+ // CHECK(file.local_->download_offset_ == 0);
+ if (!file.local_->path_.empty()) {
+ unlink(file.local_->path_).ignore();
+ }
+
+ auto size = narrow_cast<size_t>(file.size_);
+ Random::Xorshift128plus rnd(123);
+
+ size_t begin = 0;
+
+ while (begin + 128u < size) {
+ auto chunk_size = rnd.fast(128, 3096);
+ auto end = begin + chunk_size;
+ if (end > size) {
+ end = size;
+ }
+
+ ranges_.push_back({begin, end});
+ begin = end;
+ }
+
+ random_shuffle(as_mutable_span(ranges_), rnd);
+ start_chunk();
+ }
+
+ void got_chunk(const td_api::file &file) {
+ LOG(ERROR) << "Got chunk";
+ auto range = ranges_.back();
+ std::string got_chunk(range.end - range.begin, '\0');
+ FileFd::open(file.local_->path_, FileFd::Flags::Read).move_as_ok().pread(got_chunk, range.begin).ensure();
+ CHECK(got_chunk == as_slice(content_).substr(range.begin, range.end - range.begin));
+ ranges_.pop_back();
+ if (ranges_.empty()) {
+ promise_.set_value(Unit{});
+ return stop();
+ }
+ start_chunk();
+ }
+
+ void start_chunk() {
+ send_query(td::make_tl_object<td::td_api::downloadFile>(
+ file_id_, 1, static_cast<int64>(ranges_.back().begin),
+ static_cast<int64>(ranges_.back().end - ranges_.back().begin), true),
+ [this](auto res) { got_chunk(*res.ok()); });
+ }
+};
+
+static std::string gen_readable_file(size_t block_size, size_t block_count) {
+ std::string content;
+ for (size_t block_id = 0; block_id < block_count; block_id++) {
+ std::string block;
+ for (size_t line = 0; block.size() < block_size; line++) {
+ block += PSTRING() << "\nblock=" << block_id << ", line=" << line;
+ }
+ block.resize(block_size);
+ content += block;
+ }
+ return content;
+}
+
+class TestTd : public Actor {
+ public:
+ struct Options {
+ std::string alice_dir = "alice";
+ std::string bob_dir = "bob";
+ int32 api_id{0};
+ string api_hash;
+ };
+
+ explicit TestTd(Options options) : options_(std::move(options)) {
+ }
+
+ private:
+ Options options_;
+ ActorOwn<TestClient> alice_;
+ GetMe::Result alice_id_;
+ std::string alice_cache_dir_;
+ ActorOwn<TestClient> bob_;
+
+ void start_up() override {
+ alice_ = create_actor<TestClient>("Alice", "Alice");
+ bob_ = create_actor<TestClient>("Bob", "Bob");
+
+ MultiPromiseActorSafe mp("init");
+ mp.add_promise(promise_send_closure(actor_id(this), &TestTd::check_init));
+
+ InitTask::Options options;
+ options.api_id = options_.api_id;
+ options.api_hash = options_.api_hash;
+
+ options.name = options_.alice_dir;
+ td::send_closure(alice_, &TestClient::add_listener, td::make_unique<InitTask>(options, mp.get_promise()));
+ options.name = options_.bob_dir;
+ td::send_closure(bob_, &TestClient::add_listener, td::make_unique<InitTask>(options, mp.get_promise()));
+ }
+
+ void check_init(Result<Unit> res) {
+ LOG_IF(FATAL, res.is_error()) << res.error();
+ alice_cache_dir_ = options_.alice_dir + TD_DIR_SLASH + "cache";
+ mkdir(alice_cache_dir_).ignore();
+
+ td::send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<GetMe>(promise_send_closure(actor_id(this), &TestTd::with_alice_id)));
+
+ //close();
+ }
+
+ void with_alice_id(Result<GetMe::Result> alice_id) {
+ alice_id_ = alice_id.move_as_ok();
+ LOG(ERROR) << "Alice user_id=" << alice_id_.user_id << ", chat_id=" << alice_id_.chat_id;
+ auto content = gen_readable_file(65536, 20);
+ send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<UploadFile>(alice_cache_dir_, std::move(content), alice_id_.chat_id,
+ promise_send_closure(actor_id(this), &TestTd::with_file)));
+ }
+ void with_file(Result<UploadFile::Result> r_result) {
+ auto result = r_result.move_as_ok();
+ send_closure(
+ alice_, &TestClient::add_listener,
+ td::make_unique<TestDownloadFile>(result.remote_id, std::move(result.content),
+ promise_send_closure(actor_id(this), &TestTd::after_test_download_file)));
+ }
+ void after_test_download_file(Result<Unit>) {
+ close();
+ }
+
+ void close() {
+ MultiPromiseActorSafe mp("close");
+ mp.add_promise(promise_send_closure(actor_id(this), &TestTd::check_close));
+ td::send_closure(alice_, &TestClient::close, mp.get_promise());
+ td::send_closure(bob_, &TestClient::close, mp.get_promise());
+ }
+
+ void check_close(Result<Unit> res) {
+ Scheduler::instance()->finish();
+ stop();
+ }
+};
+
+static void fail_signal(int sig) {
+ signal_safe_write_signal_number(sig);
+ while (true) {
+ // spin forever to allow debugger to attach
+ }
+}
+
+static void on_fatal_error(const char *error) {
+ std::cerr << "Fatal error: " << error << std::endl;
+}
+int main(int argc, char **argv) {
+ ignore_signal(SignalType::HangUp).ensure();
+ ignore_signal(SignalType::Pipe).ensure();
+ set_signal_handler(SignalType::Error, fail_signal).ensure();
+ set_signal_handler(SignalType::Abort, fail_signal).ensure();
+ Log::set_fatal_error_callback(on_fatal_error);
+ init_openssl_threads();
+
+ TestTd::Options test_options;
+
+ test_options.api_id = [](auto x) -> int32 {
+ if (x) {
+ return to_integer<int32>(Slice(x));
+ }
+ return 0;
+ }(std::getenv("TD_API_ID"));
+ test_options.api_hash = [](auto x) -> std::string {
+ if (x) {
+ return x;
+ }
+ return std::string();
+ }(std::getenv("TD_API_HASH"));
+
+ int new_verbosity_level = VERBOSITY_NAME(INFO);
+
+ OptionParser options;
+ options.set_description("TDLib experimental tester");
+ options.add_option('v', "verbosity", "Set verbosity level", [&](Slice level) {
+ int new_verbosity = 1;
+ while (begins_with(level, "v")) {
+ new_verbosity++;
+ level.remove_prefix(1);
+ }
+ if (!level.empty()) {
+ new_verbosity += to_integer<int>(level) - (new_verbosity == 1);
+ }
+ new_verbosity_level = VERBOSITY_NAME(FATAL) + new_verbosity;
+ });
+ options.add_check([&] {
+ if (test_options.api_id == 0 || test_options.api_hash.empty()) {
+ return Status::Error("You must provide valid api-id and api-hash obtained at https://my.telegram.org");
+ }
+ return Status::OK();
+ });
+ auto r_non_options = options.run(argc, argv, 0);
+ if (r_non_options.is_error()) {
+ LOG(PLAIN) << argv[0] << ": " << r_non_options.error().message();
+ LOG(PLAIN) << options;
+ return 1;
+ }
+ SET_VERBOSITY_LEVEL(new_verbosity_level);
+
+ td::ConcurrentScheduler sched(4, 0);
+ sched.create_actor_unsafe<TestTd>(0, "TestTd", std::move(test_options)).release();
+ sched.start();
+ while (sched.run_main(10)) {
+ }
+ sched.finish();
+ return 0;
+}
+} // namespace td
+
+int main(int argc, char **argv) {
+ return td::main(argc, argv);
+}
diff --git a/protocols/Telegram/tdlib/td/test/poll.cpp b/protocols/Telegram/tdlib/td/test/poll.cpp
new file mode 100644
index 0000000000..4c8012e441
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/poll.cpp
@@ -0,0 +1,58 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// 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/PollManager.h"
+
+#include "td/utils/logging.h"
+#include "td/utils/tests.h"
+
+static void check_vote_percentage(const std::vector<td::int32> &voter_counts, td::int32 total_count,
+ const std::vector<td::int32> &expected) {
+ auto result = td::PollManager::get_vote_percentage(voter_counts, total_count);
+ if (result != expected) {
+ LOG(FATAL) << "Have " << voter_counts << " and " << total_count << ", but received " << result << " instead of "
+ << expected;
+ }
+}
+
+TEST(Poll, get_vote_percentage) {
+ check_vote_percentage({1}, 1, {100});
+ check_vote_percentage({999}, 999, {100});
+ check_vote_percentage({0}, 0, {0});
+ check_vote_percentage({2, 1}, 3, {67, 33});
+ check_vote_percentage({4, 1, 1}, 6, {66, 17, 17});
+ check_vote_percentage({100, 100}, 200, {50, 50});
+ check_vote_percentage({101, 99}, 200, {50, 50});
+ check_vote_percentage({102, 98}, 200, {51, 49});
+ check_vote_percentage({198, 2}, 200, {99, 1});
+ check_vote_percentage({199, 1}, 200, {99, 1});
+ check_vote_percentage({200}, 200, {100});
+ check_vote_percentage({0, 999}, 999, {0, 100});
+ check_vote_percentage({999, 999}, 999, {100, 100});
+ check_vote_percentage({499, 599}, 999, {50, 60});
+ check_vote_percentage({1, 1}, 2, {50, 50});
+ check_vote_percentage({1, 1, 1}, 3, {33, 33, 33});
+ check_vote_percentage({1, 1, 1, 1}, 4, {25, 25, 25, 25});
+ check_vote_percentage({1, 1, 1, 1, 1}, 5, {20, 20, 20, 20, 20});
+ check_vote_percentage({1, 1, 1, 1, 1, 1}, 6, {16, 16, 16, 16, 16, 16});
+ check_vote_percentage({1, 1, 1, 1, 1, 1, 1}, 7, {14, 14, 14, 14, 14, 14, 14});
+ check_vote_percentage({1, 1, 1, 1, 1, 1, 1, 1}, 8, {12, 12, 12, 12, 12, 12, 12, 12});
+ check_vote_percentage({1, 1, 1, 1, 1, 1, 1, 1, 1}, 9, {11, 11, 11, 11, 11, 11, 11, 11, 11});
+ check_vote_percentage({1, 1, 1, 1, 1, 1, 2}, 8, {12, 12, 12, 12, 12, 12, 25});
+ check_vote_percentage({1, 1, 1, 2, 2, 2, 3}, 12, {8, 8, 8, 17, 17, 17, 25});
+ check_vote_percentage({0, 1, 1, 1, 2, 2, 2, 3}, 12, {0, 8, 8, 8, 17, 17, 17, 25});
+ check_vote_percentage({1, 1, 1, 0}, 3, {33, 33, 33, 0});
+ check_vote_percentage({0, 1, 1, 1}, 3, {0, 33, 33, 33});
+ check_vote_percentage({9949, 9950, 9999}, 10000, {99, 100, 100});
+ check_vote_percentage({1234, 2345, 3456, 2841}, 9876,
+ {12 /* 12.49 */, 24 /* 23.74 */, 35 /* 34.99 */, 29 /* 28.76 */});
+ check_vote_percentage({1234, 2301, 3500, 2841}, 9876,
+ {12 /* 12.49 */, 23 /* 23.29 */, 35 /* 35.43 */, 29 /* 28.76 */});
+ check_vote_percentage({200, 200, 200, 270, 270, 60}, 1200, {17, 17, 17, 22, 22, 5});
+ check_vote_percentage({200, 200, 200, 300, 240, 60}, 1200, {16, 16, 16, 25, 20, 5});
+ check_vote_percentage({200, 200, 200, 250, 250, 20}, 1120, {18, 18, 18, 22, 22, 2});
+ check_vote_percentage({200, 200, 200, 250, 250, 40}, 1140, {17, 17, 17, 22, 22, 4});
+}
diff --git a/protocols/Telegram/tdlib/td/test/secret.cpp b/protocols/Telegram/tdlib/td/test/secret.cpp
index 2abed9787b..2c79b7e2d8 100644
--- a/protocols/Telegram/tdlib/td/test/secret.cpp
+++ b/protocols/Telegram/tdlib/td/test/secret.cpp
@@ -1,36 +1,48 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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/EncryptedFile.h"
+#include "td/telegram/FolderId.h"
#include "td/telegram/Global.h"
+#include "td/telegram/logevent/LogEvent.h"
#include "td/telegram/MessageId.h"
+#include "td/telegram/secret_api.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/telegram/UserId.h"
+
+#include "td/db/binlog/BinlogInterface.h"
+#include "td/db/binlog/detail/BinlogEventsProcessor.h"
+#include "td/db/BinlogKeyValue.h"
+#include "td/db/DbKey.h"
+
+#include "td/mtproto/DhCallback.h"
+#include "td/mtproto/utils.h"
#include "td/tl/tl_object_parse.h"
#include "td/tl/tl_object_store.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/as.h"
#include "td/utils/base64.h"
#include "td/utils/buffer.h"
+#include "td/utils/common.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/Promise.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/tests.h"
#include "td/utils/tl_helpers.h"
@@ -43,17 +55,14 @@
#include <map>
#include <memory>
-REGISTER_TESTS(secret);
-
namespace my_api {
using namespace td;
-//messages_getDhConfig
class messages_getDhConfig {
public:
- int32 version_;
- int32 random_length_;
+ int32 version_{};
+ int32 random_length_{};
messages_getDhConfig() = default;
@@ -70,7 +79,6 @@ class messages_getDhConfig {
}
};
-//InputUser
class InputUser {
public:
static tl_object_ptr<InputUser> fetch(TlBufferParser &p);
@@ -78,8 +86,8 @@ class InputUser {
class inputUser final : public InputUser {
public:
- int32 user_id_;
- int64 access_hash_;
+ int64 user_id_{};
+ int64 access_hash_{};
static const int32 ID = -668391402;
inputUser() = default;
@@ -92,6 +100,7 @@ class inputUser final : public InputUser {
{
}
};
+
tl_object_ptr<InputUser> InputUser::fetch(TlBufferParser &p) {
#define FAIL(error) \
p.set_error(error); \
@@ -109,36 +118,28 @@ tl_object_ptr<InputUser> InputUser::fetch(TlBufferParser &p) {
class messages_requestEncryption final {
public:
tl_object_ptr<InputUser> user_id_;
- int32 random_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
- {
+ , g_a_(TlFetchBytes<BufferSlice>::parse(p)) {
}
};
class inputEncryptedChat final {
public:
- int32 chat_id_;
- int64 access_hash_;
+ 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
- {
+ explicit inputEncryptedChat(TlBufferParser &p) : chat_id_(TlFetchInt::parse(p)), access_hash_(TlFetchLong::parse(p)) {
}
static tl_object_ptr<inputEncryptedChat> fetch(TlBufferParser &p) {
return make_tl_object<inputEncryptedChat>(p);
@@ -149,56 +150,49 @@ class messages_acceptEncryption final {
public:
tl_object_ptr<inputEncryptedChat> peer_;
BufferSlice g_b_;
- int64 key_fingerprint_;
+ 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
- {
+ , key_fingerprint_(TlFetchLong::parse(p)) {
}
};
class messages_sendEncryptedService final {
public:
tl_object_ptr<inputEncryptedChat> peer_;
- int64 random_id_;
+ 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
- {
+ , data_(TlFetchBytes<BufferSlice>::parse(p)) {
}
};
class messages_sendEncrypted final {
public:
+ int32 flags_;
tl_object_ptr<inputEncryptedChat> peer_;
- int64 random_id_;
+ int64 random_id_{};
BufferSlice data_;
messages_sendEncrypted() = default;
- static const int32 ID = -1451792525;
+ static const int32 ID = 1157265941;
explicit messages_sendEncrypted(TlBufferParser &p)
-#define FAIL(error) p.set_error(error)
- : peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
+ : flags_(TlFetchInt::parse(p))
+ , peer_(TlFetchBoxed<TlFetchObject<inputEncryptedChat>, -247351839>::parse(p))
, random_id_(TlFetchLong::parse(p))
- , data_(TlFetchBytes<BufferSlice>::parse(p))
-#undef FAIL
- {
+ , data_(TlFetchBytes<BufferSlice>::parse(p)) {
}
};
@@ -217,16 +211,16 @@ static void downcast_call(TlBufferParser &p, F &&f) {
case messages_sendEncryptedService::ID:
return f(*make_tl_object<messages_sendEncryptedService>(p));
default:
- CHECK(0) << id;
+ LOG(ERROR) << "Unknown constructor " << id;
UNREACHABLE();
}
}
class messages_dhConfig final {
public:
- int32 g_;
+ int32 g_{};
BufferSlice p_;
- int32 version_;
+ int32 version_{};
BufferSlice random_;
messages_dhConfig() = default;
@@ -258,17 +252,17 @@ class messages_dhConfig final {
class encryptedChat final {
public:
- int32 id_;
- int64 access_hash_;
- int32 date_;
- int32 admin_id_;
- int32 participant_id_;
+ int32 id_{};
+ int64 access_hash_{};
+ int32 date_{};
+ int64 admin_id_{};
+ int64 participant_id_{};
BufferSlice g_a_or_b_;
- int64 key_fingerprint_;
+ int64 key_fingerprint_{};
encryptedChat() = default;
- encryptedChat(int32 id_, int64 access_hash_, int32 date_, int32 admin_id_, int32 participant_id_,
+ encryptedChat(int32 id_, int64 access_hash_, int32 date_, int64 admin_id_, int64 participant_id_,
BufferSlice &&g_a_or_b_, int64 key_fingerprint_)
: id_(id_)
, access_hash_(access_hash_)
@@ -309,7 +303,7 @@ class encryptedChat final {
class messages_sentEncryptedMessage final {
public:
- int32 date_;
+ int32 date_{};
messages_sentEncryptedMessage() = default;
@@ -342,32 +336,32 @@ static string prime_base64 =
"WC2xF40WnGvEZbDW_5yjko_vW5rk5Bj8Feg-vqD4f6n_Xu1wBQ3tKEn0e_lZ2VaFDOkphR8NgRX2NbEF7i5OFdBLJFS_b0-t8DSxBAMRnNjjuS_MW"
"w";
-class FakeDhCallback : public DhCallback {
+class FakeDhCallback final : public mtproto::DhCallback {
public:
- int is_good_prime(Slice prime_str) const override {
+ int is_good_prime(Slice prime_str) const final {
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 {
+ void add_good_prime(Slice prime_str) const final {
cache[prime_str.str()] = 1;
}
- void add_bad_prime(Slice prime_str) const override {
+ void add_bad_prime(Slice prime_str) const final {
cache[prime_str.str()] = 0;
}
mutable std::map<string, int> cache;
};
-class FakeBinlog
+class FakeBinlog final
: public BinlogInterface
, public Actor {
public:
FakeBinlog() {
register_actor("FakeBinlog", this).release();
}
- void force_sync(Promise<> promise) override {
+ void force_sync(Promise<> promise) final {
if (pending_events_.empty()) {
pending_events_.emplace_back();
}
@@ -385,15 +379,15 @@ class FakeBinlog
}
}
}
- void force_flush() override {
+ void force_flush() final {
}
- uint64 next_id() override {
+ uint64 next_id() final {
auto res = last_id_;
last_id_++;
return res;
}
- uint64 next_id(int32 shift) override {
+ uint64 next_id(int32 shift) final {
auto res = last_id_;
last_id_ += shift;
return res;
@@ -418,16 +412,16 @@ class FakeBinlog
pending_events_.clear();
}
- void change_key(DbKey key, Promise<> promise) override {
+ void change_key(DbKey key, Promise<> promise) final {
}
protected:
- void close_impl(Promise<> promise) override {
+ void close_impl(Promise<> promise) final {
}
- void close_and_destroy_impl(Promise<> promise) override {
+ void close_and_destroy_impl(Promise<> promise) final {
}
- void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise) override {
- auto event = BinlogEvent(std::move(raw_event));
+ void add_raw_event_impl(uint64 id, BufferSlice &&raw_event, Promise<> promise, BinlogDebugInfo info) final {
+ auto event = BinlogEvent(std::move(raw_event), info);
LOG(INFO) << "ADD EVENT: " << event.id_ << " " << event;
pending_events_.emplace_back();
pending_events_.back().event = std::move(event);
@@ -441,20 +435,18 @@ class FakeBinlog
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;
+ td::vector<Promise<Unit>> 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));
+ events_processor_.add_event(std::move(event)).ensure();
}
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());
- }
+ set_promises(promises);
for (auto &event : pending_events_) {
if (event.sync_flag) {
@@ -463,10 +455,10 @@ class FakeBinlog
}
}
}
- void timeout_expired() override {
+ void timeout_expired() final {
do_force_sync();
}
- void wakeup() override {
+ void wakeup() final {
if (has_request_sync) {
do_force_sync();
}
@@ -478,42 +470,16 @@ class FakeBinlog
struct PendingEvent {
BinlogEvent event;
bool sync_flag = false;
- std::vector<Promise<>> promises_;
+ td::vector<Promise<Unit>> 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 {
+class FakeSecretChatContext final : 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)
@@ -521,28 +487,28 @@ class FakeSecretChatContext : public SecretChatActor::Context {
, 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);
+ secret_chat_db_ = std::make_shared<SecretChatDb>(key_value_, 1);
net_query_creator_.stop_check(); // :(
}
- DhCallback *dh_callback() override {
+ mtproto::DhCallback *dh_callback() final {
return &fake_dh_callback_;
}
- NetQueryCreator &net_query_creator() override {
+ NetQueryCreator &net_query_creator() final {
return net_query_creator_;
}
- int32 unix_time() override {
+ int32 unix_time() final {
return static_cast<int32>(std::time(nullptr));
}
- bool close_flag() override {
+ bool close_flag() final {
return *close_flag_;
}
- BinlogInterface *binlog() override {
+ BinlogInterface *binlog() final {
return binlog_.get();
}
- SecretChatDb *secret_chat_db() override {
+ SecretChatDb *secret_chat_db() final {
return secret_chat_db_.get();
}
- std::shared_ptr<DhConfig> dh_config() override {
+ std::shared_ptr<DhConfig> dh_config() final {
static auto config = [] {
DhConfig dh_config;
dh_config.version = 12;
@@ -553,39 +519,37 @@ class FakeSecretChatContext : public SecretChatActor::Context {
return config;
}
- void set_dh_config(std::shared_ptr<DhConfig> dh_config) override {
+ void set_dh_config(std::shared_ptr<DhConfig> dh_config) final {
// empty
}
- bool get_config_option_boolean(const string &name) const override {
+ bool get_config_option_boolean(const string &name) const final {
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 send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) final;
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 {
+ int32 date, string key_hash, int32 layer, FolderId initial_folder_id) final {
}
- 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_inbound_message(UserId user_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
+ tl_object_ptr<secret_api::decryptedMessage> message, Promise<>) final;
- 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_send_message_error(int64 random_id, Status error, Promise<>) final;
+ void on_send_message_ack(int64 random_id) final;
+ void on_send_message_ok(int64 random_id, MessageId message_id, int32 date, unique_ptr<EncryptedFile> file,
+ Promise<>) final;
+ void on_delete_messages(std::vector<int64> random_id, Promise<>) final;
+ void on_flush_history(bool, MessageId, Promise<>) final;
+ void on_read_message(int64, Promise<>) final;
- void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id,
- Promise<> promise) override {
+ void on_screenshot_taken(UserId user_id, MessageId message_id, int32 date, int64 random_id, Promise<> promise) final {
}
void on_set_ttl(UserId user_id, MessageId message_id, int32 date, int32 ttl, int64 random_id,
- Promise<> promise) override {
+ Promise<> promise) final {
}
private:
@@ -598,13 +562,13 @@ class FakeSecretChatContext : public SecretChatActor::Context {
std::shared_ptr<SecretChatDb> secret_chat_db_;
};
-NetQueryCreator FakeSecretChatContext::net_query_creator_;
+NetQueryCreator FakeSecretChatContext::net_query_creator_{nullptr};
-class Master : public Actor {
+class Master final : public Actor {
public:
explicit Master(Status *status) : status_(status) {
}
- class SecretChatProxy : public Actor {
+ class SecretChatProxy final : public Actor {
public:
SecretChatProxy(string name, ActorShared<Master> parent) : name_(std::move(name)) {
binlog_ = std::make_shared<FakeBinlog>();
@@ -615,8 +579,8 @@ class Master : public Actor {
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);
+ PSLICE() << "SecretChat " << name_, 123,
+ td::make_unique<FakeSecretChatContext>(binlog_, key_value_, close_flag_, std::move(parent)), true);
on_binlog_replay_finish();
}
@@ -624,12 +588,11 @@ class Master : public 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;
+ auto event = make_unique<log_event::InboundSecretMessage>();
event->chat_id = chat_id;
event->date = 0;
event->encrypted_message = std::move(data);
- event->qts_ack = PromiseCreator::lambda(
+ event->promise = 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);
@@ -642,7 +605,7 @@ class Master : public Actor {
add_event(Event::delayed_closure(&SecretChatActor::add_inbound_message, std::move(event)));
}
- void send_message(tl_object_ptr<secret_api::decryptedMessage> message) {
+ 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 {
@@ -671,7 +634,7 @@ class Master : public Actor {
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"));
+ return promise.set_error(Status::Error("Binlog generation mismatch"));
}
binlog_->force_sync(std::move(promise));
}
@@ -697,28 +660,28 @@ class Master : public Actor {
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_)),
+ PSLICE() << "SecretChat " << name_, 123,
+ td::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());
+ auto r_message = log_event::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_);
+ message->set_log_event_id(event.id_);
LOG(INFO) << "Process binlog event " << *message;
switch (message->get_type()) {
- case logevent::SecretChatEvent::Type::InboundSecretMessage:
+ case log_event::SecretChatEvent::Type::InboundSecretMessage:
send_closure_later(actor_, &SecretChatActor::replay_inbound_message,
- std::unique_ptr<logevent::InboundSecretMessage>(
- static_cast<logevent::InboundSecretMessage *>(message.release())));
+ unique_ptr<log_event::InboundSecretMessage>(
+ static_cast<log_event::InboundSecretMessage *>(message.release())));
break;
- case logevent::SecretChatEvent::Type::OutboundSecretMessage:
+ case log_event::SecretChatEvent::Type::OutboundSecretMessage:
send_closure_later(actor_, &SecretChatActor::replay_outbound_message,
- std::unique_ptr<logevent::OutboundSecretMessage>(
- static_cast<logevent::OutboundSecretMessage *>(message.release())));
+ unique_ptr<log_event::OutboundSecretMessage>(
+ static_cast<log_event::OutboundSecretMessage *>(message.release())));
break;
default:
UNREACHABLE();
@@ -729,7 +692,7 @@ class Master : public Actor {
}
void on_binlog_replay_finish() {
ready_ = true;
- LOG(INFO) << "on_binlog_replay_finish!";
+ LOG(INFO) << "Finish replay binlog";
send_closure(actor_, &SecretChatActor::binlog_replay_finish);
for (auto &event : pending_events_) {
send_event(actor_, std::move(event));
@@ -769,7 +732,7 @@ class Master : public Actor {
}
int32 bad_cnt_ = 0;
- void timeout_expired() override {
+ void timeout_expired() final {
LOG(INFO) << "TIMEOUT EXPIRED";
if (events_cnt_ < 4) {
bad_cnt_++;
@@ -795,21 +758,23 @@ class Master : public Actor {
auto &to() {
return get_by_id(3 - get_link_token());
}
- void start_up() override {
- set_context(std::make_shared<Global>());
+ void start_up() final {
+ auto old_context = 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);
+ send_closure(alice_.get_actor_unsafe()->actor_, &SecretChatActor::create_chat, UserId(static_cast<int64>(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), false);
}));
}
- void got_secret_chat_id(Result<SecretChatId> res, int) { // second parameter is needed to workaround clang bug
+
+ void got_secret_chat_id(Result<SecretChatId> res, bool dummy) {
CHECK(res.is_ok());
auto id = res.move_as_ok();
LOG(INFO) << "SecretChatId = " << id;
}
- bool can_fail(NetQueryPtr &query) {
+
+ static bool can_fail(NetQueryPtr &query) {
static int cnt = 20;
if (cnt > 0) {
cnt--;
@@ -821,17 +786,18 @@ class Master : public Actor {
}
return false;
}
+
void send_net_query(NetQueryPtr query, ActorShared<NetQueryCallback> callback, bool ordered) {
- if (can_fail(query) && Random::fast(0, 1) == 0) {
+ if (can_fail(query) && Random::fast_bool()) {
LOG(INFO) << "Fail query " << query;
auto resend_promise =
- PromiseCreator::lambda([id = actor_shared(this, get_link_token()), callback_actor = callback.get(),
+ PromiseCreator::lambda([self = 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();
+ self.release();
return;
}
- send_closure(std::move(id), &Master::send_net_query, r_net_query.move_as_ok(),
+ send_closure(std::move(self), &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"));
@@ -866,31 +832,33 @@ class Master : public Actor {
config.version_ = 12;
auto storer = TLObjectStorer<my_api::messages_dhConfig>(config);
BufferSlice answer(storer.size());
- storer.store(answer.as_slice().ubegin());
+ auto real_size = storer.store(answer.as_slice().ubegin());
+ CHECK(real_size == answer.size());
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,
+ 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()));
+ send_closure(bob_.get_actor_unsafe()->actor_, &SecretChatActor::update_chat,
+ make_tl_object<telegram_api::encryptedChatRequested>(0, false, 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,
+ 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());
+ auto real_size = storer.store(answer.as_slice().ubegin());
+ CHECK(real_size == answer.size());
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);
@@ -898,49 +866,47 @@ class Master : public Actor {
send_ping(1, 5000);
set_timeout_in(1);
}
- void timeout_expired() override {
+ void timeout_expired() final {
send_message(1, "oppa");
send_message(2, "appo");
set_timeout_in(1);
}
- void send_ping(int id, int cnt) {
+ void send_ping(int32 id, int cnt) {
if (cnt % 200 == 0) {
- LOG(ERROR) << "send ping " << tag("id", id) << tag("cnt", cnt);
+ LOG(ERROR) << "Send ping " << tag("id", id) << tag("cnt", cnt);
} else {
- LOG(INFO) << "send ping " << tag("id", id) << tag("cnt", cnt);
+ 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) {
+ void send_message(int32 id, string text) {
auto random_id = Random::secure_int64();
- LOG(INFO) << "send message: " << tag("id", id) << tag("text", text) << tag("random_id", random_id);
+ 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));
+ secret_api::make_object<secret_api::decryptedMessage>(0, false /*ignored*/, 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));
+ process_net_query_send_encrypted(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));
+ process_net_query_send_encrypted(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());
+ void process_net_query_send_encrypted(BufferSlice data, NetQueryPtr net_query,
+ ActorShared<NetQueryCallback> callback) {
+ BufferSlice answer(8);
+ answer.as_slice().fill(0);
+ as<int32>(answer.as_slice().begin()) = static_cast<int32>(my_api::messages_sentEncryptedMessage::ID);
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);
+ 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);
}
@@ -967,10 +933,10 @@ class Master : public Actor {
}
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;
+ LOG(INFO) << "Receive 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";
+ LOG(INFO) << "TODO: try to fix errors about message after it is sent";
return;
}
CHECK(it != sent_messages_.end());
@@ -980,10 +946,10 @@ class Master : public Actor {
}
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);
+ LOG(INFO) << "Receive 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";
+ LOG(INFO) << "TODO: try to fix errors about message after it is sent";
return;
}
CHECK(it != sent_messages_.end());
@@ -1000,7 +966,7 @@ class Master : public Actor {
};
std::map<int64, Message> sent_messages_;
- void hangup_shared() override {
+ void hangup_shared() final {
LOG(INFO) << "GOT HANGUP: " << get_link_token();
send_closure(from(), &SecretChatProxy::on_closed);
}
@@ -1010,7 +976,7 @@ void FakeSecretChatContext::send_net_query(NetQueryPtr query, ActorShared<NetQue
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,
+ unique_ptr<EncryptedFile> file,
tl_object_ptr<secret_api::decryptedMessage> message, Promise<> promise) {
send_closure(master_, &Master::on_inbound_message, message->message_, std::move(promise));
}
@@ -1020,24 +986,22 @@ void FakeSecretChatContext::on_send_message_error(int64 random_id, Status error,
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) {
+ unique_ptr<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_flush_history(bool, MessageId, Promise<> promise) {
+ promise.set_error(Status::Error("Unsupported"));
}
void FakeSecretChatContext::on_read_message(int64, Promise<> promise) {
- promise.set_error(Status::Error("unsupported"));
+ 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);
+ return;
+ ConcurrentScheduler sched(0, 0);
Status result;
sched.create_actor_unsafe<Master>(0, "HandshakeTestActor", &result).release();
diff --git a/protocols/Telegram/tdlib/td/test/secure_storage.cpp b/protocols/Telegram/tdlib/td/test/secure_storage.cpp
new file mode 100644
index 0000000000..774dead5ac
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/secure_storage.cpp
@@ -0,0 +1,70 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// 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/SecureStorage.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/filesystem.h"
+#include "td/utils/port/path.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/tests.h"
+
+TEST(SecureStorage, secret) {
+ auto secret = td::secure_storage::Secret::create_new();
+ td::string key = "cucumber";
+ auto encrypted_secret = secret.encrypt(key, "", td::secure_storage::EnryptionAlgorithm::Sha512);
+ ASSERT_TRUE(encrypted_secret.as_slice() != secret.as_slice());
+ auto decrypted_secret = encrypted_secret.decrypt(key, "", td::secure_storage::EnryptionAlgorithm::Sha512).ok();
+ ASSERT_TRUE(secret.as_slice() == decrypted_secret.as_slice());
+ ASSERT_TRUE(encrypted_secret.decrypt("notcucumber", "", td::secure_storage::EnryptionAlgorithm::Sha512).is_error());
+}
+
+TEST(SecureStorage, simple) {
+ td::BufferSlice value("Small tale about cucumbers");
+ auto value_secret = td::secure_storage::Secret::create_new();
+
+ {
+ td::secure_storage::BufferSliceDataView value_view(value.copy());
+ td::BufferSlice prefix = td::secure_storage::gen_random_prefix(value_view.size());
+ td::secure_storage::BufferSliceDataView prefix_view(std::move(prefix));
+ td::secure_storage::ConcatDataView full_value_view(prefix_view, value_view);
+ auto hash = td::secure_storage::calc_value_hash(full_value_view).move_as_ok();
+
+ td::secure_storage::Encryptor encryptor(
+ td::secure_storage::calc_aes_cbc_state_sha512(PSLICE() << value_secret.as_slice() << hash.as_slice()),
+ full_value_view);
+ auto encrypted_value = encryptor.pread(0, encryptor.size()).move_as_ok();
+
+ td::secure_storage::Decryptor decryptor(
+ td::secure_storage::calc_aes_cbc_state_sha512(PSLICE() << value_secret.as_slice() << hash.as_slice()));
+ auto res = decryptor.append(encrypted_value.copy()).move_as_ok();
+ auto decrypted_hash = decryptor.finish().ok();
+ ASSERT_TRUE(decrypted_hash.as_slice() == hash.as_slice());
+ ASSERT_TRUE(res.as_slice() == value.as_slice());
+ }
+
+ {
+ auto encrypted_value = td::secure_storage::encrypt_value(value_secret, value.as_slice()).move_as_ok();
+ auto decrypted_value =
+ td::secure_storage::decrypt_value(value_secret, encrypted_value.hash, encrypted_value.data.as_slice())
+ .move_as_ok();
+ ASSERT_TRUE(decrypted_value.as_slice() == value.as_slice());
+ }
+
+ {
+ td::string value_path = "value.txt";
+ td::string encrypted_path = "encrypted.txt";
+ td::string decrypted_path = "decrypted.txt";
+ td::unlink(value_path).ignore();
+ td::unlink(encrypted_path).ignore();
+ td::unlink(decrypted_path).ignore();
+ td::string file_value(100000, 'a');
+ td::write_file(value_path, file_value).ensure();
+ auto hash = td::secure_storage::encrypt_file(value_secret, value_path, encrypted_path).move_as_ok();
+ td::secure_storage::decrypt_file(value_secret, hash, encrypted_path, decrypted_path).ensure();
+ ASSERT_TRUE(td::read_file(decrypted_path).move_as_ok().as_slice() == file_value);
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/test/set_with_position.cpp b/protocols/Telegram/tdlib/td/test/set_with_position.cpp
new file mode 100644
index 0000000000..c65b8bc362
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/set_with_position.cpp
@@ -0,0 +1,263 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// 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/SetWithPosition.h"
+
+#include "td/utils/algorithm.h"
+#include "td/utils/common.h"
+#include "td/utils/misc.h"
+#include "td/utils/Random.h"
+#include "td/utils/tests.h"
+
+#include <algorithm>
+#include <functional>
+#include <set>
+#include <utility>
+
+template <class T>
+class OldSetWithPosition {
+ public:
+ void add(T value) {
+ if (td::contains(values_, value)) {
+ return;
+ }
+ values_.push_back(value);
+ }
+ void remove(T value) {
+ auto it = std::find(values_.begin(), values_.end(), value);
+ if (it == values_.end()) {
+ return;
+ }
+ std::size_t i = it - values_.begin();
+ values_.erase(it);
+ if (pos_ > i) {
+ pos_--;
+ }
+ }
+ void reset_position() {
+ pos_ = 0;
+ }
+ T next() {
+ CHECK(has_next());
+ return values_[pos_++];
+ }
+ bool has_next() const {
+ return pos_ < values_.size();
+ }
+ void merge(OldSetWithPosition &&other) {
+ OldSetWithPosition res;
+ for (std::size_t i = 0; i < pos_; i++) {
+ res.add(values_[i]);
+ }
+ for (std::size_t i = 0; i < other.pos_; i++) {
+ res.add(other.values_[i]);
+ }
+ res.pos_ = res.values_.size();
+ for (std::size_t i = pos_; i < values_.size(); i++) {
+ res.add(values_[i]);
+ }
+ for (std::size_t i = other.pos_; i < other.values_.size(); i++) {
+ res.add(other.values_[i]);
+ }
+ *this = std::move(res);
+ }
+
+ private:
+ td::vector<T> values_;
+ std::size_t pos_{0};
+};
+
+template <class T, template <class> class SetWithPosition>
+class CheckedSetWithPosition {
+ public:
+ void add(int x) {
+ s_.add(x);
+ if (checked_.count(x) != 0) {
+ return;
+ }
+ not_checked_.insert(x);
+ }
+ void remove(int x) {
+ s_.remove(x);
+ checked_.erase(x);
+ not_checked_.erase(x);
+ }
+ bool has_next() const {
+ auto res = !not_checked_.empty();
+ //LOG(ERROR) << res;
+ ASSERT_EQ(res, s_.has_next());
+ return res;
+ }
+ void reset_position() {
+ s_.reset_position();
+ not_checked_.insert(checked_.begin(), checked_.end());
+ checked_ = {};
+ }
+
+ T next() {
+ CHECK(has_next());
+ auto next = s_.next();
+ //LOG(ERROR) << next;
+ ASSERT_TRUE(not_checked_.count(next) != 0);
+ not_checked_.erase(next);
+ checked_.insert(next);
+ return next;
+ }
+
+ void merge(CheckedSetWithPosition &&other) {
+ if (size() < other.size()) {
+ std::swap(*this, other);
+ std::swap(this->s_, other.s_);
+ }
+ for (auto x : other.checked_) {
+ not_checked_.erase(x);
+ checked_.insert(x);
+ }
+ for (auto x : other.not_checked_) {
+ if (checked_.count(x) != 0) {
+ continue;
+ }
+ not_checked_.insert(x);
+ }
+ s_.merge(std::move(other.s_));
+ }
+ std::size_t size() const {
+ return checked_.size() + not_checked_.size();
+ }
+
+ private:
+ std::set<T> checked_;
+ std::set<T> not_checked_;
+ td::SetWithPosition<T> s_;
+};
+
+template <template <class> class RawSet>
+static void test_hands() {
+ CheckedSetWithPosition<int, RawSet> a;
+ a.add(1);
+ a.add(2);
+ a.next();
+
+ CheckedSetWithPosition<int, RawSet> b;
+ b.add(1);
+ b.add(3);
+
+ a.merge(std::move(b));
+ while (a.has_next()) {
+ a.next();
+ }
+}
+
+#if !TD_CLANG
+template <template <class> class RawSet>
+static void test_stress() {
+ td::Random::Xorshift128plus rnd(123);
+ using Set = CheckedSetWithPosition<int, RawSet>;
+ for (int t = 0; t < 10; t++) {
+ td::vector<td::unique_ptr<Set>> sets(100);
+ for (auto &s : sets) {
+ s = td::make_unique<Set>();
+ }
+ int n;
+ auto merge = [&] {
+ int a = rnd.fast(0, n - 2);
+ int b = rnd.fast(a + 1, n - 1);
+ std::swap(sets[b], sets[n - 1]);
+ std::swap(sets[a], sets[n - 2]);
+ a = n - 2;
+ b = n - 1;
+ if (rnd.fast(0, 1) == 0) {
+ std::swap(sets[a], sets[b]);
+ }
+ sets[a]->merge(std::move(*sets[b]));
+ sets.pop_back();
+ };
+ auto next = [&] {
+ int i = rnd.fast(0, n - 1);
+ if (sets[i]->has_next()) {
+ sets[i]->next();
+ }
+ };
+ auto add = [&] {
+ int i = rnd.fast(0, n - 1);
+ int x = rnd.fast(0, 10);
+ sets[i]->add(x);
+ };
+ auto remove = [&] {
+ int i = rnd.fast(0, n - 1);
+ int x = rnd.fast(0, 10);
+ sets[i]->remove(x);
+ };
+ auto reset_position = [&] {
+ int i = rnd.fast(0, n - 1);
+ sets[i]->reset_position();
+ };
+ struct Step {
+ std::function<void()> func;
+ td::uint32 weight;
+ };
+ td::vector<Step> steps{{merge, 1}, {next, 10}, {add, 10}, {remove, 10}, {reset_position, 5}};
+ td::uint32 steps_sum = 0;
+ for (auto &step : steps) {
+ steps_sum += step.weight;
+ }
+
+ while (true) {
+ n = static_cast<int>(sets.size());
+ if (n == 1) {
+ break;
+ }
+ auto w = rnd() % steps_sum;
+ for (auto &step : steps) {
+ if (w < step.weight) {
+ step.func();
+ break;
+ }
+ w -= step.weight;
+ }
+ }
+ }
+}
+#endif
+
+template <template <class> class RawSet>
+static void test_speed() {
+ td::Random::Xorshift128plus rnd(123);
+ using Set = CheckedSetWithPosition<int, RawSet>;
+ const size_t total_size = 1 << 13;
+ td::vector<td::unique_ptr<Set>> sets(total_size);
+ for (size_t i = 0; i < sets.size(); i++) {
+ sets[i] = td::make_unique<Set>();
+ sets[i]->add(td::narrow_cast<int>(i));
+ }
+ for (size_t d = 1; d < sets.size(); d *= 2) {
+ for (size_t i = 0; i < sets.size(); i += 2 * d) {
+ size_t j = i + d;
+ CHECK(j < sets.size());
+ sets[i]->merge(std::move(*sets[j]));
+ }
+ }
+ ASSERT_EQ(total_size, sets[0]->size());
+}
+
+TEST(SetWithPosition, hands) {
+ test_hands<td::FastSetWithPosition>();
+ test_hands<OldSetWithPosition>();
+ test_hands<td::SetWithPosition>();
+}
+
+#if !TD_CLANG
+TEST(SetWithPosition, stress) {
+ test_stress<td::FastSetWithPosition>();
+ test_stress<OldSetWithPosition>();
+ test_stress<td::SetWithPosition>();
+}
+#endif
+
+TEST(SetWithPosition, speed) {
+ test_speed<td::FastSetWithPosition>();
+ test_speed<td::SetWithPosition>();
+}
diff --git a/protocols/Telegram/tdlib/td/test/string_cleaning.cpp b/protocols/Telegram/tdlib/td/test/string_cleaning.cpp
index 6fbd8150a4..442f82d913 100644
--- a/protocols/Telegram/tdlib/td/test/string_cleaning.cpp
+++ b/protocols/Telegram/tdlib/td/test/string_cleaning.cpp
@@ -1,5 +1,5 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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)
@@ -9,37 +9,34 @@
#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));
-};
+ ASSERT_EQ("@mention", td::clean_name("@mention", 1000000));
+ ASSERT_EQ("@mention", td::clean_name(" @mention ", 1000000));
+ ASSERT_EQ("@MENTION", td::clean_name("@MENTION", 1000000));
+ ASSERT_EQ("ЛШТШФУМ", td::clean_name("ЛШТШФУМ", 1000000));
+ ASSERT_EQ("....", td::clean_name("....", 1000000));
+ ASSERT_EQ(". ASD ..", td::clean_name(". ASD ..", 1000000));
+ ASSERT_EQ(". ASD", td::clean_name(". ASD ..", 10));
+ ASSERT_EQ(". ASD", td::clean_name(".\n\n\nASD\n\n\n..", 10));
+ ASSERT_EQ("", td::clean_name("\n\n\n\n\n\n", 1000000));
+ ASSERT_EQ("",
+ td::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", td::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 .."));
-};
+ ASSERT_EQ("@mention", td::clean_username("@mention"));
+ ASSERT_EQ("@mention", td::clean_username(" @mention "));
+ ASSERT_EQ("@mention", td::clean_username("@MENTION"));
+ ASSERT_EQ("ЛШТШФУМ", td::clean_username("ЛШТШФУМ"));
+ ASSERT_EQ("", td::clean_username("...."));
+ ASSERT_EQ("asd", td::clean_username(". ASD .."));
+}
-static void check_clean_input_string(string str, string expected, bool expected_result) {
- auto result = clean_input_string(str);
+static void check_clean_input_string(td::string str, const td::string &expected, bool expected_result) {
+ auto result = td::clean_input_string(str);
ASSERT_EQ(expected_result, result);
if (result) {
ASSERT_EQ(expected, str);
@@ -48,7 +45,7 @@ static void check_clean_input_string(string str, string expected, bool expected_
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(td::string(50000, 'a'), td::string(34996, 'a'), true);
check_clean_input_string("\xff", "", false);
check_clean_input_string("\xc0\x80", "", false);
check_clean_input_string("\xd0", "", false);
@@ -59,18 +56,25 @@ TEST(StringCleaning, clean_input_string) {
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")
+ check_clean_input_string(td::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(
+ "\xe2\x80\x8f\xe2\x80\x8f \xe2\x80\x8e\xe2\x80\x8e\xe2\x80\x8e\xe2\x80\x8c \xe2\x80\x8f\xe2\x80\x8e "
+ "\xe2\x80\x8f",
+ "\xe2\x80\x8c\xe2\x80\x8f \xe2\x80\x8c\xe2\x80\x8c\xe2\x80\x8e\xe2\x80\x8c \xe2\x80\x8c\xe2\x80\x8e "
+ "\xe2\x80\x8f",
+ 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));
+static void check_strip_empty_characters(td::string str, std::size_t max_length, const td::string &expected,
+ bool strip_rtlo = false) {
+ ASSERT_EQ(expected, td::strip_empty_characters(std::move(str), max_length, strip_rtlo));
}
TEST(StringCleaning, strip_empty_characters) {
@@ -78,28 +82,31 @@ TEST(StringCleaning, strip_empty_characters) {
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";
+ td::string spaces =
+ u8"\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2800\u3000\uFFFC"
+ u8"\uFFFC";
+ td::string spaces_replace = " ";
+ td::string rtlo = u8"\u202E";
+ td::string empty = "\xE2\x80\x8B\xE2\x80\x8C\xE2\x80\x8D\xE2\x80\x8E\xE2\x80\x8F\xE2\x80\xAE\xC2\xA0\xC2\xA0";
check_strip_empty_characters(spaces, 1000000, "");
+ check_strip_empty_characters(spaces + rtlo, 1000000, "");
+ check_strip_empty_characters(spaces + rtlo, 1000000, "", true);
+ check_strip_empty_characters(spaces + rtlo + "a", 1000000, rtlo + "a");
+ check_strip_empty_characters(spaces + rtlo + "a", 1000000, "a", true);
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(spaces + spaces + empty + spaces + spaces + empty + empty, 1000000, "");
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(td::Slice(" \t\r\n\0\va\v\0\n\r\t ").str(), 1000000, "a");
+ check_strip_empty_characters(td::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,
diff --git a/protocols/Telegram/tdlib/td/test/tdclient.cpp b/protocols/Telegram/tdlib/td/test/tdclient.cpp
index 7cef0fcbc0..6ced39da30 100644
--- a/protocols/Telegram/tdlib/td/test/tdclient.cpp
+++ b/protocols/Telegram/tdlib/td/test/tdclient.cpp
@@ -1,63 +1,60 @@
//
-// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
//
// 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/Client.h"
#include "td/telegram/ClientActor.h"
-
+#include "td/telegram/files/PartsManager.h"
#include "td/telegram/td_api.h"
+#include "td/actor/actor.h"
+#include "td/actor/ConcurrentScheduler.h"
+#include "td/actor/PromiseFuture.h"
+
#include "td/utils/base64.h"
#include "td/utils/BufferedFd.h"
+#include "td/utils/common.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/port/sleep.h"
+#include "td/utils/port/thread.h"
+#include "td/utils/Promise.h"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
#include "td/utils/Status.h"
#include "td/utils/tests.h"
+#include <atomic>
#include <cstdio>
#include <functional>
#include <map>
#include <memory>
+#include <mutex>
+#include <set>
#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);
+ LOG_CHECK(result->get_id() != td::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 {
+class TestClient final : public td::Actor {
public:
- explicit TestClient(string name) : name_(std::move(name)) {
+ explicit TestClient(td::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)) {
+ td::uint64 id;
+ td::tl_object_ptr<td::td_api::Object> object;
+ Update(td::uint64 id, td::tl_object_ptr<td::td_api::Object> object) : id(id), object(std::move(object)) {
}
};
class Listener {
@@ -74,33 +71,37 @@ class TestClient : public Actor {
}
virtual void on_update(std::shared_ptr<Update> update) = 0;
};
- void close(Promise<> close_promise) {
+ void close(td::Promise<> close_promise) {
close_promise_ = std::move(close_promise);
- td_.reset();
+ td_client_.reset();
}
- unique_ptr<TdCallback> make_td_callback() {
- class TdCallbackImpl : public TdCallback {
+ td::unique_ptr<td::TdCallback> make_td_callback() {
+ class TdCallbackImpl final : public td::TdCallback {
public:
- explicit TdCallbackImpl(ActorId<TestClient> client) : client_(client) {
+ explicit TdCallbackImpl(td::ActorId<TestClient> client) : client_(client) {
}
- void on_result(uint64 id, tl_object_ptr<td_api::Object> result) override {
+ void on_result(td::uint64 id, td::tl_object_ptr<td::td_api::Object> result) final {
send_closure(client_, &TestClient::on_result, id, std::move(result));
}
- void on_error(uint64 id, tl_object_ptr<td_api::error> error) override {
+ void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) final {
send_closure(client_, &TestClient::on_error, id, std::move(error));
}
- void on_closed() override {
+ TdCallbackImpl(const TdCallbackImpl &) = delete;
+ TdCallbackImpl &operator=(const TdCallbackImpl &) = delete;
+ TdCallbackImpl(TdCallbackImpl &&) = delete;
+ TdCallbackImpl &operator=(TdCallbackImpl &&) = delete;
+ ~TdCallbackImpl() final {
send_closure(client_, &TestClient::on_closed);
}
private:
- ActorId<TestClient> client_;
+ td::ActorId<TestClient> client_;
};
- return make_unique<TdCallbackImpl>(actor_id(this));
+ return td::make_unique<TdCallbackImpl>(actor_id(this));
}
- void add_listener(std::unique_ptr<Listener> listener) {
+ void add_listener(td::unique_ptr<Listener> listener) {
auto *ptr = listener.get();
listeners_.push_back(std::move(listener));
ptr->start_listen(this);
@@ -124,10 +125,10 @@ class TestClient : public Actor {
}
}
- void on_result(uint64 id, tl_object_ptr<td_api::Object> result) {
+ void on_result(td::uint64 id, td::tl_object_ptr<td::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) {
+ void on_error(td::uint64 id, td::tl_object_ptr<td::td_api::error> error) {
on_update(std::make_shared<Update>(id, std::move(error)));
}
void on_update(std::shared_ptr<Update> update) {
@@ -141,29 +142,29 @@ class TestClient : public Actor {
stop();
}
- void start_up() override {
- rmrf(name_);
- set_context(std::make_shared<td::ActorContext>());
+ void start_up() final {
+ td::rmrf(name_).ignore();
+ auto old_context = set_context(std::make_shared<td::ActorContext>());
set_tag(name_);
LOG(INFO) << "START UP!";
- td_ = create_actor<ClientActor>("Td-proxy", make_td_callback());
+ td_client_ = td::create_actor<td::ClientActor>("Td-proxy", make_td_callback());
}
- ActorOwn<ClientActor> td_;
+ td::ActorOwn<td::ClientActor> td_client_;
- string name_;
+ td::string name_;
private:
- std::vector<std::unique_ptr<Listener>> listeners_;
- std::vector<Listener *> pending_remove_;
+ td::vector<td::unique_ptr<Listener>> listeners_;
+ td::vector<Listener *> pending_remove_;
- Promise<> close_promise_;
+ td::Promise<> close_promise_;
};
-class Task : public TestClient::Listener {
+class TestClinetTask : public TestClient::Listener {
public:
- void on_update(std::shared_ptr<TestClient::Update> update) override {
+ void on_update(std::shared_ptr<TestClient::Update> update) final {
auto it = sent_queries_.find(update->id);
if (it != sent_queries_.end()) {
it->second(std::move(update->object));
@@ -171,7 +172,7 @@ class Task : public TestClient::Listener {
}
process_update(update);
}
- void start_listen(TestClient *client) override {
+ void start_listen(TestClient *client) final {
client_ = client;
start_up();
}
@@ -179,16 +180,16 @@ class Task : public TestClient::Listener {
}
template <class F>
- void send_query(tl_object_ptr<td_api::Function> function, F callback) {
+ void send_query(td::tl_object_ptr<td::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));
+ send_closure(client_->td_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;
+ std::map<td::uint64, std::function<void(td::tl_object_ptr<td::td_api::Object>)>> sent_queries_;
+ TestClient *client_ = nullptr;
+ td::uint64 current_query_id_ = 1;
virtual void start_up() {
}
@@ -197,52 +198,58 @@ class Task : public TestClient::Listener {
}
};
-class DoAuthentication : public Task {
+class DoAuthentication final : public TestClinetTask {
public:
- DoAuthentication(string name, string phone, string code, Promise<> promise)
+ DoAuthentication(td::string name, td::string phone, td::string code, td::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>(),
+ void start_up() final {
+ send_query(td::make_tl_object<td::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) {
+ void process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state) {
start_flag_ = true;
- tl_object_ptr<td_api::Function> function;
+ td::tl_object_ptr<td::td_api::Function> function;
switch (authorization_state->get_id()) {
- case td_api::authorizationStateWaitEncryptionKey::ID:
- function = make_tl_object<td_api::checkDatabaseEncryptionKey>();
+ case td::td_api::authorizationStateWaitPhoneNumber::ID:
+ function = td::make_tl_object<td::td_api::setAuthenticationPhoneNumber>(phone_, nullptr);
+ break;
+ case td::td_api::authorizationStateWaitEmailAddress::ID:
+ function = td::make_tl_object<td::td_api::setAuthenticationEmailAddress>("alice_test@gmail.com");
break;
- case td_api::authorizationStateWaitPhoneNumber::ID:
- function = make_tl_object<td_api::setAuthenticationPhoneNumber>(phone_, false, true);
+ case td::td_api::authorizationStateWaitEmailCode::ID:
+ function = td::make_tl_object<td::td_api::checkAuthenticationEmailCode>(
+ td::make_tl_object<td::td_api::emailAddressAuthenticationCode>(code_));
break;
- case td_api::authorizationStateWaitCode::ID:
- function = make_tl_object<td_api::checkAuthenticationCode>(code_, name_, "");
+ case td::td_api::authorizationStateWaitCode::ID:
+ function = td::make_tl_object<td::td_api::checkAuthenticationCode>(code_);
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));
+ case td::td_api::authorizationStateWaitRegistration::ID:
+ function = td::make_tl_object<td::td_api::registerUser>(name_, "");
+ break;
+ case td::td_api::authorizationStateWaitTdlibParameters::ID: {
+ auto request = td::td_api::make_object<td::td_api::setTdlibParameters>();
+ request->use_test_dc_ = true;
+ request->database_directory_ = name_ + TD_DIR_SLASH;
+ request->use_message_database_ = true;
+ request->use_secret_chats_ = true;
+ request->api_id_ = 94575;
+ request->api_hash_ = "a3406de8d171bb422bb6ddf3bbd800e2";
+ request->system_language_code_ = "en";
+ request->device_model_ = "Desktop";
+ request->application_version_ = "tdclient-test";
+ request->enable_storage_optimizer_ = true;
+ function = std::move(request);
break;
}
- case td_api::authorizationStateReady::ID:
+ case td::td_api::authorizationStateReady::ID:
on_authorization_ready();
return;
default:
- CHECK(false) << "Unexpected authorization state " << to_string(authorization_state);
+ LOG(ERROR) << "Unexpected authorization state " << to_string(authorization_state);
+ UNREACHABLE();
}
- send_query(std::move(function), [this](auto res) { CHECK(res->get_id() == td_api::ok::ID) << to_string(res); });
+ send_query(std::move(function), [](auto res) { LOG_CHECK(res->get_id() == td::td_api::ok::ID) << to_string(res); });
}
void on_authorization_ready() {
LOG(INFO) << "GOT AUTHORIZED";
@@ -250,78 +257,82 @@ class DoAuthentication : public Task {
}
private:
- string name_;
- string phone_;
- string code_;
- Promise<> promise_;
+ td::string name_;
+ td::string phone_;
+ td::string code_;
+ td::Promise<> promise_;
bool start_flag_{false};
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
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_));
+ if (update->object->get_id() == td::td_api::updateAuthorizationState::ID) {
+ auto update_authorization_state = td::move_tl_object_as<td::td_api::updateAuthorizationState>(update->object);
+ process_authorization_state(std::move(update_authorization_state->authorization_state_));
}
}
};
-class SetUsername : public Task {
+class SetUsername final : public TestClinetTask {
public:
- SetUsername(string username, Promise<> promise) : username_(std::move(username)), promise_(std::move(promise)) {
+ SetUsername(td::string username, td::Promise<> promise)
+ : username_(std::move(username)), promise_(std::move(promise)) {
}
private:
- string username_;
- Promise<> promise_;
- int32 self_id_;
- string tag_;
+ td::string username_;
+ td::Promise<> promise_;
+ td::int64 self_id_ = 0;
+ td::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 start_up() final {
+ send_query(td::make_tl_object<td::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);
+ void process_me_user(td::tl_object_ptr<td::td_api::Object> res) {
+ CHECK(res->get_id() == td::td_api::user::ID);
+ auto user = td::move_tl_object_as<td::td_api::user>(res);
self_id_ = user->id_;
- if (user->username_ != username_) {
+ auto current_username = user->usernames_ != nullptr ? user->usernames_->editable_username_ : td::string();
+ if (current_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);
+ send_query(td::make_tl_object<td::td_api::setUsername>(username_), [this](auto res) {
+ CHECK(res->get_id() == td::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) {});
+ tag_ = PSTRING() << td::format::as_hex(td::Random::secure_int64());
+
+ send_query(td::make_tl_object<td::td_api::createPrivateChat>(self_id_, false), [this](auto res) {
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::td_api::chat>(res);
+ this->send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat->id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageText>(
+ td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " INIT", td::Auto()),
+ false, false)),
+ [](auto res) {});
});
}
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
if (!update->object) {
return;
}
- if (update->object->get_id() == td_api::updateMessageSendSucceeded::ID) {
- auto updateNewMessage = move_tl_object_as<td_api::updateMessageSendSucceeded>(update->object);
+ if (update->object->get_id() == td::td_api::updateMessageSendSucceeded::ID) {
+ auto updateNewMessage = td::move_tl_object_as<td::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_);
+ if (message->content_->get_id() == td::td_api::messageText::ID) {
+ auto messageText = td::move_tl_object_as<td::td_api::messageText>(message->content_);
auto text = messageText->text_->text_;
if (text.substr(0, tag_.size()) == tag_) {
LOG(INFO) << "GOT SELF MESSAGE";
@@ -332,28 +343,29 @@ class SetUsername : public Task {
}
};
-class CheckTestA : public Task {
+class CheckTestA final : public TestClinetTask {
public:
- CheckTestA(string tag, Promise<> promise) : tag_(std::move(tag)), promise_(std::move(promise)) {
+ CheckTestA(td::string tag, td::Promise<> promise) : tag_(std::move(tag)), promise_(std::move(promise)) {
}
private:
- string tag_;
- Promise<> promise_;
- string previous_text_;
+ td::string tag_;
+ td::Promise<> promise_;
+ td::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);
+
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
+ if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
+ auto updateNewMessage = td::move_tl_object_as<td::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_);
+ if (message->content_->get_id() == td::td_api::messageText::ID) {
+ auto messageText = td::move_tl_object_as<td::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_);
+ LOG_CHECK(text > previous_text_) << td::tag("now", text) << td::tag("previous", previous_text_);
previous_text_ = text;
cnt_--;
- LOG(INFO) << "GOT " << tag("text", text) << tag("left", cnt_);
+ LOG(INFO) << "GOT " << td::tag("text", text) << td::tag("left", cnt_);
if (cnt_ == 0) {
return stop();
}
@@ -363,98 +375,101 @@ class CheckTestA : public Task {
}
};
-class TestA : public Task {
+class TestA final : public TestClinetTask {
public:
- TestA(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ TestA(td::string tag, td::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);
+
+ void start_up() final {
+ send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this](auto res) {
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::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(); });
+ this->send_query(
+ td::make_tl_object<td::td_api::sendMessage>(
+ chat->id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageText>(
+ td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), td::Auto()),
+ false, false)),
+ [&](auto res) { this->stop(); });
}
});
}
private:
- string tag_;
- string username_;
+ td::string tag_;
+ td::string username_;
};
-class TestSecretChat : public Task {
+class TestSecretChat final : public TestClinetTask {
public:
- TestSecretChat(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ TestSecretChat(td::string tag, td::string username) : tag_(std::move(tag)), username_(std::move(username)) {
}
- void start_up() override {
+ void start_up() final {
auto f = [this](auto res) {
- CHECK(res->get_id() == td_api::chat::ID);
- auto chat = move_tl_object_as<td_api::chat>(res);
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::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_;
+ this->secret_chat_id_ = td::move_tl_object_as<td::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));
+ send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this, f = std::move(f)](auto res) mutable {
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::td_api::chat>(res);
+ CHECK(chat->type_->get_id() == td::td_api::chatTypePrivate::ID);
+ auto info = td::move_tl_object_as<td::td_api::chatTypePrivate>(chat->type_);
+ this->send_query(td::make_tl_object<td::td_api::createNewSecretChat>(info->user_id_), std::move(f));
});
}
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
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->object->get_id() == td::td_api::updateSecretChat::ID) {
+ auto update_secret_chat = td::move_tl_object_as<td::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) {
+ update_secret_chat->secret_chat_->state_->get_id() != td::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) {});
+ send_query(
+ td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageText>(
+ td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " " << (1000 + i), td::Auto()),
+ false, false)),
+ [](auto res) {});
}
}
}
private:
- string tag_;
- string username_;
- int64 secret_chat_id_ = 0;
- int64 chat_id_ = 0;
+ td::string tag_;
+ td::string username_;
+ td::int64 secret_chat_id_ = 0;
+ td::int64 chat_id_ = 0;
};
-class TestFileGenerated : public Task {
+class TestFileGenerated final : public TestClinetTask {
public:
- TestFileGenerated(string tag, string username) : tag_(std::move(tag)), username_(std::move(username)) {
+ TestFileGenerated(td::string tag, td::string username) : tag_(std::move(tag)), username_(std::move(username)) {
}
- void start_up() override {
+ void start_up() final {
}
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
if (!update->object) {
return;
}
- if (update->object->get_id() == td_api::updateNewMessage::ID) {
- auto updateNewMessage = move_tl_object_as<td_api::updateNewMessage>(update->object);
+ if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
+ auto updateNewMessage = td::move_tl_object_as<td::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_);
+ if (message->content_->get_id() == td::td_api::messageText::ID) {
+ auto messageText = td::move_tl_object_as<td::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") {
@@ -462,47 +477,49 @@ class TestFileGenerated : public Task {
}
}
}
- } else if (update->object->get_id() == td_api::updateFileGenerationStart::ID) {
- auto info = move_tl_object_as<td_api::updateFileGenerationStart>(update->object);
+ } else if (update->object->get_id() == td::td_api::updateFileGenerationStart::ID) {
+ auto info = td::move_tl_object_as<td::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);
+ } else if (update->object->get_id() == td::td_api::updateFile::ID) {
+ auto file = td::move_tl_object_as<td::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 file_path = PSTRING() << "test_documents" << TD_DIR_SLASH << "a.txt";
+ td::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));
+ td::FileFd::open(file_path, td::FileFd::Flags::Create | td::FileFd::Flags::Truncate | td::FileFd::Flags::Write)
+ .move_as_ok();
+ auto file = td::BufferedFd<td::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); });
+ send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageDocument>(
+ td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "square", 0),
+ td::make_tl_object<td::td_api::inputThumbnail>(
+ td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "thumbnail", 0), 0, 0),
+ true, td::make_tl_object<td::td_api::formattedText>(tag_, td::Auto()))),
+ [](auto res) { check_td_error(res); });
+
+ send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageDocument>(
+ td::make_tl_object<td::td_api::inputFileGenerated>(file_path, "square", 0), nullptr, true,
+ td::make_tl_object<td::td_api::formattedText>(tag_, td::Auto()))),
+ [](auto res) { check_td_error(res); });
}
- friend class GenerateFile;
- class GenerateFile : public Actor {
+ class GenerateFile final : public td::Actor {
public:
- GenerateFile(Task *parent, int64 id, string original_path, string destination_path, string conversion)
+ GenerateFile(TestClinetTask *parent, td::int64 id, td::string original_path, td::string destination_path,
+ td::string conversion)
: parent_(parent)
, id_(id)
, original_path_(std::move(original_path))
@@ -511,26 +528,27 @@ class TestFileGenerated : public Task {
}
private:
- Task *parent_;
- int64 id_;
- string original_path_;
- string destination_path_;
- string conversion_;
+ TestClinetTask *parent_;
+ td::int64 id_;
+ td::string original_path_;
+ td::string destination_path_;
+ td::string conversion_;
FILE *from = nullptr;
FILE *to = nullptr;
- void start_up() override {
+ void start_up() final {
from = std::fopen(original_path_.c_str(), "rb");
CHECK(from);
to = std::fopen(destination_path_.c_str(), "wb");
CHECK(to);
yield();
}
- void loop() override {
+
+ void loop() final {
int cnt = 0;
while (true) {
- uint32 x;
+ td::uint32 x;
auto r = std::fscanf(from, "%u", &x);
if (r != 1) {
return stop();
@@ -542,98 +560,100 @@ class TestFileGenerated : public Task {
}
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)),
+ parent_->send_query(td::make_tl_object<td::td_api::setFileGenerationProgress>(
+ id_, 1039823 /*yeah, exact size of this file*/, td::narrow_cast<td::int32>(ready)),
[](auto result) { check_td_error(result); });
set_timeout_in(0.02);
}
- void tear_down() override {
+ void tear_down() final {
std::fclose(from);
std::fclose(to);
- parent_->send_query(make_tl_object<td_api::finishFileGeneration>(id_, nullptr),
+ parent_->send_query(td::make_tl_object<td::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);
+
+ void generate_file(td::int64 id, const td::string &original_path, const td::string &destination_path,
+ const td::string &conversion) {
+ LOG(ERROR) << "Generate file " << td::tag("id", id) << td::tag("original_path", original_path)
+ << td::tag("destination_path", destination_path) << td::tag("conversion", conversion);
if (conversion == "square") {
- create_actor<GenerateFile>("GenerateFile", this, id, original_path, destination_path, conversion).release();
+ td::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); });
+ td::write_file(destination_path, td::base64url_decode(td::Slice(thumbnail, thumbnail_size)).ok()).ensure();
+ send_query(td::make_tl_object<td::td_api::finishFileGeneration>(id, nullptr),
+ [](auto result) { check_td_error(result); });
} else {
- LOG(FATAL) << "unknown " << tag("conversion", conversion);
+ LOG(FATAL) << "Unknown " << td::tag("conversion", conversion);
}
}
private:
- string tag_;
- string username_;
- int64 chat_id_ = 0;
+ td::string tag_;
+ td::string username_;
+ td::int64 chat_id_ = 0;
};
-class CheckTestC : public Task {
+class CheckTestC final : public TestClinetTask {
public:
- CheckTestC(string username, string tag, Promise<> promise)
+ CheckTestC(td::string username, td::string tag, td::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);
+ void start_up() final {
+ send_query(td::make_tl_object<td::td_api::searchPublicChat>(username_), [this](auto res) {
+ CHECK(res->get_id() == td::td_api::chat::ID);
+ auto chat = td::move_tl_object_as<td::td_api::chat>(res);
chat_id_ = chat->id_;
this->one_file();
});
}
private:
- string username_;
- string tag_;
- Promise<> promise_;
- int64 chat_id_;
+ td::string username_;
+ td::string tag_;
+ td::Promise<> promise_;
+ td::int64 chat_id_ = 0;
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); });
+ send_query(td::make_tl_object<td::td_api::sendMessage>(
+ chat_id_, 0, 0, nullptr, nullptr,
+ td::make_tl_object<td::td_api::inputMessageText>(
+ td::make_tl_object<td::td_api::formattedText>(PSTRING() << tag_ << " ONE_FILE", td::Auto()),
+ false, false)),
+ [](auto res) { check_td_error(res); });
}
- void process_update(std::shared_ptr<TestClient::Update> update) override {
+ void process_update(std::shared_ptr<TestClient::Update> update) final {
if (!update->object) {
return;
}
- if (update->object->get_id() == td_api::updateNewMessage::ID) {
- auto updateNewMessage = move_tl_object_as<td_api::updateNewMessage>(update->object);
+ if (update->object->get_id() == td::td_api::updateNewMessage::ID) {
+ auto updateNewMessage = td::move_tl_object_as<td::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_);
+ if (message->content_->get_id() == td::td_api::messageDocument::ID) {
+ auto messageDocument = td::move_tl_object_as<td::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); });
+ send_query(td::make_tl_object<td::td_api::downloadFile>(file_id_to_check_, 1, 0, 0, false),
+ [](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);
+ } else if (update->object->get_id() == td::td_api::updateFile::ID) {
+ auto updateFile = td::move_tl_object_as<td::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) {
+ void check_file(td::CSlice path) {
FILE *from = std::fopen(path.c_str(), "rb");
CHECK(from);
- uint32 x;
- uint32 y = 1;
+ td::uint32 x;
+ td::uint32 y = 1;
while (std::fscanf(from, "%u", &x) == 1) {
CHECK(x == y * y);
y++;
@@ -641,47 +661,47 @@ class CheckTestC : public Task {
std::fclose(from);
stop();
}
- int32 file_id_to_check_ = 0;
+ td::int32 file_id_to_check_ = 0;
};
-class LoginTestActor : public Actor {
+class LoginTestActor final : public td::Actor {
public:
- explicit LoginTestActor(Status *status) : status_(status) {
- *status_ = Status::OK();
+ explicit LoginTestActor(td::Status *status) : status_(status) {
+ *status_ = td::Status::OK();
}
private:
- Status *status_;
- ActorOwn<TestClient> alice_;
- ActorOwn<TestClient> bob_;
+ td::Status *status_;
+ td::ActorOwn<TestClient> alice_;
+ td::ActorOwn<TestClient> bob_;
- string alice_phone_ = "9996636437";
- string bob_phone_ = "9996636438";
- string alice_username_ = "alice_" + alice_phone_;
- string bob_username_ = "bob_" + bob_phone_;
+ td::string alice_phone_ = "9996636437";
+ td::string bob_phone_ = "9996636438";
+ td::string alice_username_ = "alice_" + alice_phone_;
+ td::string bob_username_ = "bob_" + bob_phone_;
- string stage_name_;
+ td::string stage_name_;
- void begin_stage(string stage_name, double timeout) {
+ void begin_stage(td::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 {
+ void start_up() final {
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))));
+ alice_ = td::create_actor<TestClient>("AliceClient", "alice");
+ bob_ = td::create_actor<TestClient>("BobClient", "bob");
+
+ td::send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<DoAuthentication>(
+ "alice", alice_phone_, "33333",
+ td::create_event_promise(self_closure(this, &LoginTestActor::start_up_fence_dec))));
+
+ td::send_closure(bob_, &TestClient::add_listener,
+ td::make_unique<DoAuthentication>(
+ "bob", bob_phone_, "33333",
+ td::create_event_promise(self_closure(this, &LoginTestActor::start_up_fence_dec))));
}
int start_up_fence_ = 3;
@@ -691,34 +711,34 @@ class LoginTestActor : public Actor {
init();
} else if (start_up_fence_ == 1) {
return init();
- class WaitActor : public Actor {
+ class WaitActor final : public td::Actor {
public:
- WaitActor(double timeout, Promise<> promise) : timeout_(timeout), promise_(std::move(promise)) {
+ WaitActor(double timeout, td::Promise<> promise) : timeout_(timeout), promise_(std::move(promise)) {
}
- void start_up() override {
+ void start_up() final {
set_timeout_in(timeout_);
}
- void timeout_expired() override {
+ void timeout_expired() final {
stop();
}
private:
double timeout_;
- Promise<> promise_;
+ td::Promise<> promise_;
};
- create_actor<WaitActor>("WaitActor", 2,
- PromiseCreator::event(self_closure(this, &LoginTestActor::start_up_fence_dec)))
+ td::create_actor<WaitActor>("WaitActor", 2,
+ td::create_event_promise(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))));
+ td::send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<SetUsername>(alice_username_, td::create_event_promise(self_closure(
+ this, &LoginTestActor::init_fence_dec))));
+ td::send_closure(bob_, &TestClient::add_listener,
+ td::make_unique<SetUsername>(
+ bob_username_, td::create_event_promise(self_closure(this, &LoginTestActor::init_fence_dec))));
}
int init_fence_ = 2;
@@ -728,7 +748,7 @@ class LoginTestActor : public Actor {
}
}
- int32 test_a_fence_ = 2;
+ int test_a_fence_ = 2;
void test_a_fence() {
if (--test_a_fence_ == 0) {
test_b();
@@ -737,33 +757,33 @@ class LoginTestActor : public Actor {
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_));
+ td::string alice_tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
+ td::string bob_tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
+
+ td::send_closure(bob_, &TestClient::add_listener,
+ td::make_unique<CheckTestA>(
+ alice_tag, td::create_event_promise(self_closure(this, &LoginTestActor::test_a_fence))));
+ td::send_closure(alice_, &TestClient::add_listener,
+ td::make_unique<CheckTestA>(
+ bob_tag, td::create_event_promise(self_closure(this, &LoginTestActor::test_a_fence))));
+
+ td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestA>(alice_tag, bob_username_));
+ td::send_closure(bob_, &TestClient::add_listener, td::make_unique<TestA>(bob_tag, alice_username_));
+ // td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestChat>(bob_username_));
}
- void timeout_expired() override {
+ void timeout_expired() final {
LOG(FATAL) << "Timeout expired in stage '" << stage_name_ << "'";
}
- int32 test_b_fence_ = 1;
+ int test_b_fence_ = 1;
void test_b_fence() {
if (--test_b_fence_ == 0) {
test_c();
}
}
- int32 test_c_fence_ = 1;
+ int test_c_fence_ = 1;
void test_c_fence() {
if (--test_c_fence_ == 0) {
finish();
@@ -772,45 +792,47 @@ class LoginTestActor : public Actor {
void test_b() {
begin_stage("Create secret chat", 40);
- string tag = PSTRING() << format::as_hex(Random::secure_int64());
+ td::string tag = PSTRING() << td::format::as_hex(td::Random::secure_int64());
- send_closure(
+ td::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_));
+ td::make_unique<CheckTestA>(tag, td::create_event_promise(self_closure(this, &LoginTestActor::test_b_fence))));
+ td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestSecretChat>(tag, bob_username_));
}
void test_c() {
begin_stage("Send generated file", 240);
- string tag = PSTRING() << format::as_hex(Random::secure_int64());
+ td::string tag = PSTRING() << td::format::as_hex(td::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_));
+ td::send_closure(
+ bob_, &TestClient::add_listener,
+ td::make_unique<CheckTestC>(alice_username_, tag,
+ td::create_event_promise(self_closure(this, &LoginTestActor::test_c_fence))));
+ td::send_closure(alice_, &TestClient::add_listener, td::make_unique<TestFileGenerated>(tag, bob_username_));
}
- int32 finish_fence_ = 2;
+ int finish_fence_ = 2;
void finish_fence() {
finish_fence_--;
if (finish_fence_ == 0) {
- Scheduler::instance()->finish();
+ td::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)));
+ td::send_closure(alice_, &TestClient::close,
+ td::create_event_promise(self_closure(this, &LoginTestActor::finish_fence)));
+ td::send_closure(bob_, &TestClient::close,
+ td::create_event_promise(self_closure(this, &LoginTestActor::finish_fence)));
}
};
-class Tdclient_login : public td::Test {
+class Tdclient_login final : 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;
@@ -830,8 +852,347 @@ class Tdclient_login : public td::Test {
private:
bool is_inited_ = false;
- ConcurrentScheduler sched_;
- Status result_;
+ td::ConcurrentScheduler sched_{4, 0};
+ td::Status result_;
};
-Tdclient_login Tdclient_login("Tdclient_login");
-}; // namespace td
+//RegisterTest<Tdclient_login> Tdclient_login("Tdclient_login");
+
+TEST(Client, Simple) {
+ td::Client client;
+ // client.execute({1, td::td_api::make_object<td::td_api::setLogTagVerbosityLevel>("actor", 1)});
+ client.send({3, td::make_tl_object<td::td_api::testSquareInt>(3)});
+ while (true) {
+ auto result = client.receive(10);
+ if (result.id == 3) {
+ auto test_int = td::td_api::move_object_as<td::td_api::testInt>(result.object);
+ ASSERT_EQ(test_int->value_, 9);
+ break;
+ }
+ }
+}
+
+TEST(Client, SimpleMulti) {
+ std::vector<td::Client> clients(7);
+ //for (auto &client : clients) {
+ //client.execute({1, td::td_api::make_object<td::td_api::setLogTagVerbosityLevel>("td_requests", 1)});
+ //}
+
+ for (size_t i = 0; i < clients.size(); i++) {
+ clients[i].send({i + 2, td::make_tl_object<td::td_api::testSquareInt>(3)});
+ if (td::Random::fast_bool()) {
+ clients[i].send({1, td::make_tl_object<td::td_api::close>()});
+ }
+ }
+
+ for (size_t i = 0; i < clients.size(); i++) {
+ while (true) {
+ auto result = clients[i].receive(10);
+ if (result.id == i + 2) {
+ CHECK(result.object->get_id() == td::td_api::testInt::ID);
+ auto test_int = td::td_api::move_object_as<td::td_api::testInt>(result.object);
+ ASSERT_EQ(test_int->value_, 9);
+ break;
+ }
+ }
+ }
+}
+
+#if !TD_THREAD_UNSUPPORTED
+TEST(Client, Multi) {
+ td::vector<td::thread> threads;
+ std::atomic<int> ok_count{0};
+ for (int i = 0; i < 4; i++) {
+ threads.emplace_back([i, &ok_count] {
+ for (int j = 0; j < 1000; j++) {
+ td::Client client;
+ auto request_id = static_cast<td::uint64>(j + 2 + 1000 * i);
+ client.send({request_id, td::make_tl_object<td::td_api::testSquareInt>(3)});
+ if (j & 1) {
+ client.send({1, td::make_tl_object<td::td_api::close>()});
+ }
+ while (true) {
+ auto result = client.receive(10);
+ if (result.id == request_id) {
+ ok_count++;
+ if ((j & 1) == 0) {
+ client.send({1, td::make_tl_object<td::td_api::close>()});
+ }
+ }
+ if (result.id == 0 && result.object != nullptr &&
+ result.object->get_id() == td::td_api::updateAuthorizationState::ID &&
+ static_cast<const td::td_api::updateAuthorizationState *>(result.object.get())
+ ->authorization_state_->get_id() == td::td_api::authorizationStateClosed::ID) {
+ ok_count++;
+ break;
+ }
+ }
+ }
+ });
+ }
+
+ for (auto &thread : threads) {
+ thread.join();
+ }
+ ASSERT_EQ(8 * 1000, ok_count.load());
+}
+
+TEST(Client, Manager) {
+ td::vector<td::thread> threads;
+ td::ClientManager client;
+#if !TD_EVENTFD_UNSUPPORTED // Client must be used from a single thread if there is no EventFd
+ int threads_n = 4;
+#else
+ int threads_n = 1;
+#endif
+ int clients_n = 1000;
+ client.send(0, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
+ client.send(-1, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
+ for (int i = 0; i < threads_n; i++) {
+ threads.emplace_back([&] {
+ for (int i = 0; i <= clients_n; i++) {
+ auto id = client.create_client_id();
+ if (i != 0) {
+ client.send(id, 3, td::make_tl_object<td::td_api::testSquareInt>(3));
+ }
+ }
+ });
+ }
+ for (auto &thread : threads) {
+ thread.join();
+ }
+
+ std::set<td::int32> ids;
+ while (ids.size() != static_cast<size_t>(threads_n) * clients_n) {
+ auto event = client.receive(10);
+ if (event.client_id == 0 || event.client_id == -1) {
+ ASSERT_EQ(td::td_api::error::ID, event.object->get_id());
+ ASSERT_EQ(400, static_cast<td::td_api::error &>(*event.object).code_);
+ continue;
+ }
+ if (event.request_id == 3) {
+ ASSERT_EQ(td::td_api::testInt::ID, event.object->get_id());
+ ASSERT_TRUE(ids.insert(event.client_id).second);
+ }
+ }
+}
+
+#if !TD_EVENTFD_UNSUPPORTED // Client must be used from a single thread if there is no EventFd
+TEST(Client, Close) {
+ std::atomic<bool> stop_send{false};
+ std::atomic<bool> can_stop_receive{false};
+ std::atomic<td::int64> send_count{1};
+ std::atomic<td::int64> receive_count{0};
+ td::Client client;
+
+ std::mutex request_ids_mutex;
+ std::set<td::uint64> request_ids;
+ request_ids.insert(1);
+ td::thread send_thread([&] {
+ td::uint64 request_id = 2;
+ while (!stop_send.load()) {
+ {
+ std::unique_lock<std::mutex> guard(request_ids_mutex);
+ request_ids.insert(request_id);
+ }
+ client.send({request_id++, td::make_tl_object<td::td_api::testSquareInt>(3)});
+ send_count++;
+ }
+ can_stop_receive = true;
+ });
+
+ td::thread receive_thread([&] {
+ auto max_continue_send = td::Random::fast_bool() ? 0 : 1000;
+ while (true) {
+ auto response = client.receive(10.0);
+ if (response.object == nullptr) {
+ if (!stop_send) {
+ stop_send = true;
+ } else {
+ return;
+ }
+ }
+ if (response.id > 0) {
+ if (!stop_send && response.object->get_id() == td::td_api::error::ID &&
+ static_cast<td::td_api::error &>(*response.object).code_ == 500 &&
+ td::Random::fast(0, max_continue_send) == 0) {
+ stop_send = true;
+ }
+ receive_count++;
+ {
+ std::unique_lock<std::mutex> guard(request_ids_mutex);
+ size_t erased_count = request_ids.erase(response.id);
+ CHECK(erased_count > 0);
+ }
+ }
+ if (can_stop_receive && receive_count == send_count) {
+ break;
+ }
+ }
+ });
+
+ td::usleep_for((td::Random::fast_bool() ? 0 : 1000) * (td::Random::fast_bool() ? 1 : 50));
+ client.send({1, td::make_tl_object<td::td_api::close>()});
+
+ send_thread.join();
+ receive_thread.join();
+ ASSERT_EQ(send_count.load(), receive_count.load());
+ ASSERT_TRUE(request_ids.empty());
+}
+
+TEST(Client, ManagerClose) {
+ std::atomic<bool> stop_send{false};
+ std::atomic<bool> can_stop_receive{false};
+ std::atomic<td::int64> send_count{1};
+ std::atomic<td::int64> receive_count{0};
+ td::ClientManager client_manager;
+ auto client_id = client_manager.create_client_id();
+
+ std::mutex request_ids_mutex;
+ std::set<td::uint64> request_ids;
+ request_ids.insert(1);
+ td::thread send_thread([&] {
+ td::uint64 request_id = 2;
+ while (!stop_send.load()) {
+ {
+ std::unique_lock<std::mutex> guard(request_ids_mutex);
+ request_ids.insert(request_id);
+ }
+ client_manager.send(client_id, request_id++, td::make_tl_object<td::td_api::testSquareInt>(3));
+ send_count++;
+ }
+ can_stop_receive = true;
+ });
+
+ td::thread receive_thread([&] {
+ auto max_continue_send = td::Random::fast_bool() ? 0 : 1000;
+ bool can_stop_send = false;
+ while (true) {
+ auto response = client_manager.receive(10.0);
+ if (response.object == nullptr) {
+ if (!stop_send) {
+ can_stop_send = true;
+ } else {
+ return;
+ }
+ }
+ if (can_stop_send && max_continue_send-- <= 0) {
+ stop_send = true;
+ }
+ if (response.request_id > 0) {
+ receive_count++;
+ {
+ std::unique_lock<std::mutex> guard(request_ids_mutex);
+ size_t erased_count = request_ids.erase(response.request_id);
+ CHECK(erased_count > 0);
+ }
+ }
+ if (can_stop_receive && receive_count == send_count) {
+ break;
+ }
+ }
+ });
+
+ td::usleep_for((td::Random::fast_bool() ? 0 : 1000) * (td::Random::fast_bool() ? 1 : 50));
+ client_manager.send(client_id, 1, td::make_tl_object<td::td_api::close>());
+
+ send_thread.join();
+ receive_thread.join();
+ ASSERT_EQ(send_count.load(), receive_count.load());
+ ASSERT_TRUE(request_ids.empty());
+}
+#endif
+#endif
+
+TEST(Client, ManagerCloseOneThread) {
+ td::ClientManager client_manager;
+
+ td::uint64 request_id = 2;
+ std::map<td::uint64, td::int32> sent_requests;
+ td::uint64 sent_count = 0;
+ td::uint64 receive_count = 0;
+
+ auto send_request = [&](td::int32 client_id, td::int32 expected_error_code) {
+ sent_count++;
+ sent_requests.emplace(request_id, expected_error_code);
+ client_manager.send(client_id, request_id++, td::make_tl_object<td::td_api::testSquareInt>(3));
+ };
+
+ auto receive = [&] {
+ while (receive_count != sent_count) {
+ auto response = client_manager.receive(1.0);
+ if (response.object == nullptr) {
+ continue;
+ }
+ if (response.request_id > 0) {
+ receive_count++;
+ auto it = sent_requests.find(response.request_id);
+ CHECK(it != sent_requests.end());
+ auto expected_error_code = it->second;
+ sent_requests.erase(it);
+
+ if (expected_error_code == 0) {
+ if (response.request_id == 1) {
+ ASSERT_EQ(td::td_api::ok::ID, response.object->get_id());
+ } else {
+ ASSERT_EQ(td::td_api::testInt::ID, response.object->get_id());
+ }
+ } else {
+ ASSERT_EQ(td::td_api::error::ID, response.object->get_id());
+ ASSERT_EQ(expected_error_code, static_cast<td::td_api::error &>(*response.object).code_);
+ }
+ }
+ }
+ };
+
+ for (int t = 0; t < 3; t++) {
+ for (td::int32 i = -5; i <= 0; i++) {
+ send_request(i, 400);
+ }
+
+ receive();
+
+ auto client_id = client_manager.create_client_id();
+
+ for (td::int32 i = -5; i < 5; i++) {
+ send_request(i, i == client_id ? 0 : (i > 0 && i < client_id ? 500 : 400));
+ }
+
+ receive();
+
+ for (int i = 0; i < 10; i++) {
+ send_request(client_id, 0);
+ }
+
+ receive();
+
+ sent_count++;
+ sent_requests.emplace(1, 0);
+ client_manager.send(client_id, 1, td::make_tl_object<td::td_api::close>());
+
+ for (int i = 0; i < 10; i++) {
+ send_request(client_id, 500);
+ }
+
+ receive();
+
+ for (int i = 0; i < 10; i++) {
+ send_request(client_id, 500);
+ }
+
+ receive();
+ }
+
+ ASSERT_TRUE(sent_requests.empty());
+}
+
+TEST(PartsManager, hands) {
+ {
+ td::PartsManager pm;
+ pm.init(0, 100000, false, 10, {0, 1, 2}, false, true).ensure_error();
+ //pm.set_known_prefix(0, false).ensure();
+ }
+ {
+ td::PartsManager pm;
+ pm.init(1, 100000, true, 10, {0, 1, 2}, false, true).ensure_error();
+ }
+}
diff --git a/protocols/Telegram/tdlib/td/test/tests_runner.cpp b/protocols/Telegram/tdlib/td/test/tests_runner.cpp
deleted file mode 100644
index 0edb186f0a..0000000000
--- a/protocols/Telegram/tdlib/td/test/tests_runner.cpp
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// 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
deleted file mode 100644
index 3de566858b..0000000000
--- a/protocols/Telegram/tdlib/td/test/tests_runner.h
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// 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
diff --git a/protocols/Telegram/tdlib/td/test/tqueue.cpp b/protocols/Telegram/tdlib/td/test/tqueue.cpp
new file mode 100644
index 0000000000..b1bf94a09e
--- /dev/null
+++ b/protocols/Telegram/tdlib/td/test/tqueue.cpp
@@ -0,0 +1,250 @@
+//
+// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022
+//
+// 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/Binlog.h"
+#include "td/db/binlog/BinlogEvent.h"
+#include "td/db/binlog/BinlogHelper.h"
+#include "td/db/TQueue.h"
+
+#include "td/utils/buffer.h"
+#include "td/utils/common.h"
+#include "td/utils/int_types.h"
+#include "td/utils/logging.h"
+#include "td/utils/Random.h"
+#include "td/utils/Slice.h"
+#include "td/utils/SliceBuilder.h"
+#include "td/utils/Span.h"
+#include "td/utils/tests.h"
+#include "td/utils/Time.h"
+
+#include <memory>
+#include <utility>
+
+TEST(TQueue, hands) {
+ td::TQueue::Event events[100];
+ auto events_span = td::MutableSpan<td::TQueue::Event>(events, 100);
+
+ auto tqueue = td::TQueue::create();
+ auto qid = 12;
+ ASSERT_EQ(true, tqueue->get_head(qid).empty());
+ ASSERT_EQ(true, tqueue->get_tail(qid).empty());
+ tqueue->push(qid, "hello", 1, 0, td::TQueue::EventId());
+ auto head = tqueue->get_head(qid);
+ auto tail = tqueue->get_tail(qid);
+ ASSERT_EQ(head.next().ok(), tail);
+ ASSERT_EQ(1u, tqueue->get(qid, head, true, 0, events_span).move_as_ok());
+ ASSERT_EQ(1u, tqueue->get(qid, head, true, 0, events_span).move_as_ok());
+ ASSERT_EQ(1u, tqueue->get(qid, tail, false, 0, events_span).move_as_ok());
+ ASSERT_EQ(1u, tqueue->get(qid, head, true, 0, events_span).move_as_ok());
+ ASSERT_EQ(0u, tqueue->get(qid, tail, true, 0, events_span).move_as_ok());
+ ASSERT_EQ(0u, tqueue->get(qid, head, true, 0, events_span).move_as_ok());
+}
+
+class TestTQueue {
+ public:
+ using EventId = td::TQueue::EventId;
+
+ static td::CSlice binlog_path() {
+ return td::CSlice("tqueue_binlog");
+ }
+
+ TestTQueue() {
+ baseline_ = td::TQueue::create();
+
+ memory_ = td::TQueue::create();
+ auto memory_storage = td::make_unique<td::TQueueMemoryStorage>();
+ memory_storage_ = memory_storage.get();
+ memory_->set_callback(std::move(memory_storage));
+
+ binlog_ = td::TQueue::create();
+ auto tqueue_binlog = td::make_unique<td::TQueueBinlog<td::Binlog>>();
+ td::Binlog::destroy(binlog_path()).ensure();
+ auto binlog = std::make_shared<td::Binlog>();
+ binlog->init(binlog_path().str(), [&](const td::BinlogEvent &event) { UNREACHABLE(); }).ensure();
+ tqueue_binlog->set_binlog(std::move(binlog));
+ binlog_->set_callback(std::move(tqueue_binlog));
+ }
+
+ void restart(td::Random::Xorshift128plus &rnd, td::int32 now) {
+ if (rnd.fast(0, 10) == 0) {
+ baseline_->run_gc(now);
+ }
+
+ memory_->extract_callback().release();
+ auto memory_storage = td::unique_ptr<td::TQueueMemoryStorage>(memory_storage_);
+ memory_ = td::TQueue::create();
+ memory_storage->replay(*memory_);
+ memory_->set_callback(std::move(memory_storage));
+ if (rnd.fast(0, 10) == 0) {
+ memory_->run_gc(now);
+ }
+
+ if (rnd.fast(0, 30) != 0) {
+ return;
+ }
+
+ LOG(INFO) << "Restart binlog";
+ binlog_ = td::TQueue::create();
+ auto tqueue_binlog = td::make_unique<td::TQueueBinlog<td::Binlog>>();
+ auto binlog = std::make_shared<td::Binlog>();
+ binlog
+ ->init(binlog_path().str(),
+ [&](const td::BinlogEvent &event) { tqueue_binlog->replay(event, *binlog_).ignore(); })
+ .ensure();
+ tqueue_binlog->set_binlog(std::move(binlog));
+ binlog_->set_callback(std::move(tqueue_binlog));
+ if (rnd.fast(0, 2) == 0) {
+ binlog_->run_gc(now);
+ }
+ }
+
+ EventId push(td::TQueue::QueueId queue_id, const td::string &data, td::int32 expires_at, EventId new_id = EventId()) {
+ auto a_id = baseline_->push(queue_id, data, expires_at, 0, new_id).move_as_ok();
+ auto b_id = memory_->push(queue_id, data, expires_at, 0, new_id).move_as_ok();
+ auto c_id = binlog_->push(queue_id, data, expires_at, 0, new_id).move_as_ok();
+ ASSERT_EQ(a_id, b_id);
+ ASSERT_EQ(a_id, c_id);
+ return a_id;
+ }
+
+ void check_head_tail(td::TQueue::QueueId qid) {
+ //ASSERT_EQ(baseline_->get_head(qid), memory_->get_head(qid));
+ //ASSERT_EQ(baseline_->get_head(qid), binlog_->get_head(qid));
+ ASSERT_EQ(baseline_->get_tail(qid), memory_->get_tail(qid));
+ ASSERT_EQ(baseline_->get_tail(qid), binlog_->get_tail(qid));
+ }
+
+ void check_get(td::TQueue::QueueId qid, td::Random::Xorshift128plus &rnd, td::int32 now) {
+ td::TQueue::Event a[10];
+ td::MutableSpan<td::TQueue::Event> a_span(a, 10);
+ td::TQueue::Event b[10];
+ td::MutableSpan<td::TQueue::Event> b_span(b, 10);
+ td::TQueue::Event c[10];
+ td::MutableSpan<td::TQueue::Event> c_span(c, 10);
+
+ auto a_from = baseline_->get_head(qid);
+ //auto b_from = memory_->get_head(qid);
+ //auto c_from = binlog_->get_head(qid);
+ //ASSERT_EQ(a_from, b_from);
+ //ASSERT_EQ(a_from, c_from);
+
+ auto tmp = a_from.advance(rnd.fast(-10, 10));
+ if (tmp.is_ok()) {
+ a_from = tmp.move_as_ok();
+ }
+ baseline_->get(qid, a_from, true, now, a_span).move_as_ok();
+ memory_->get(qid, a_from, true, now, b_span).move_as_ok();
+ binlog_->get(qid, a_from, true, now, c_span).move_as_ok();
+ ASSERT_EQ(a_span.size(), b_span.size());
+ ASSERT_EQ(a_span.size(), c_span.size());
+ for (size_t i = 0; i < a_span.size(); i++) {
+ ASSERT_EQ(a_span[i].id, b_span[i].id);
+ ASSERT_EQ(a_span[i].id, c_span[i].id);
+ ASSERT_EQ(a_span[i].data, b_span[i].data);
+ ASSERT_EQ(a_span[i].data, c_span[i].data);
+ }
+ }
+
+ private:
+ td::unique_ptr<td::TQueue> baseline_;
+ td::unique_ptr<td::TQueue> memory_;
+ td::unique_ptr<td::TQueue> binlog_;
+ td::TQueueMemoryStorage *memory_storage_{nullptr};
+};
+
+TEST(TQueue, random) {
+ using EventId = td::TQueue::EventId;
+ td::Random::Xorshift128plus rnd(123);
+ auto next_queue_id = [&rnd] {
+ return rnd.fast(1, 10);
+ };
+ auto next_first_id = [&rnd] {
+ if (rnd.fast(0, 3) == 0) {
+ return EventId::from_int32(EventId::MAX_ID - 20).move_as_ok();
+ }
+ return EventId::from_int32(rnd.fast(1000000000, 1500000000)).move_as_ok();
+ };
+
+ TestTQueue q;
+ td::int32 now = 1000;
+ auto push_event = [&] {
+ auto data = PSTRING() << rnd();
+ if (rnd.fast(0, 10000) == 0) {
+ data = td::string(1 << 19, '\0');
+ }
+ q.push(next_queue_id(), data, now + rnd.fast(-10, 10) * 10 + 5, next_first_id());
+ };
+ auto inc_now = [&] {
+ now += 10;
+ };
+ auto check_head_tail = [&] {
+ q.check_head_tail(next_queue_id());
+ };
+ auto restart = [&] {
+ q.restart(rnd, now);
+ };
+ auto get = [&] {
+ q.check_get(next_queue_id(), rnd, now);
+ };
+ td::RandomSteps steps({{push_event, 100}, {check_head_tail, 10}, {get, 40}, {inc_now, 5}, {restart, 1}});
+ for (int i = 0; i < 100000; i++) {
+ steps.step(rnd);
+ }
+}
+
+TEST(TQueue, memory_leak) {
+ return;
+ auto tqueue = td::TQueue::create();
+ auto tqueue_binlog = td::make_unique<td::TQueueBinlog<td::Binlog>>();
+ std::string binlog_path = "test_tqueue.binlog";
+ td::Binlog::destroy(binlog_path).ensure();
+ auto binlog = std::make_shared<td::Binlog>();
+ binlog->init(binlog_path, [&](const td::BinlogEvent &event) { UNREACHABLE(); }).ensure();
+ tqueue_binlog->set_binlog(std::move(binlog));
+ tqueue->set_callback(std::move(tqueue_binlog));
+
+ td::int32 now = 0;
+ std::vector<td::TQueue::EventId> ids;
+ td::Random::Xorshift128plus rnd(123);
+ int i = 0;
+ while (true) {
+ auto id = tqueue->push(1, "a", now + 600000, 0, {}).move_as_ok();
+ ids.push_back(id);
+ if (ids.size() > static_cast<std::size_t>(rnd()) % 100000) {
+ auto it = static_cast<std::size_t>(rnd()) % ids.size();
+ std::swap(ids.back(), ids[it]);
+ tqueue->forget(1, ids.back());
+ ids.pop_back();
+ }
+ now++;
+ if (i++ % 100000 == 0) {
+ LOG(ERROR) << td::BufferAllocator::get_buffer_mem() << " " << tqueue->get_size(1) << " "
+ << td::BufferAllocator::get_buffer_slice_size();
+ }
+ }
+}
+
+TEST(TQueue, clear) {
+ auto tqueue = td::TQueue::create();
+
+ auto start_time = td::Time::now();
+ td::int32 now = 0;
+ td::vector<td::TQueue::EventId> ids;
+ td::Random::Xorshift128plus rnd(123);
+ for (size_t i = 0; i < 1000000; i++) {
+ tqueue->push(1, td::string(td::Random::fast(100, 500), 'a'), now + 600000, 0, {}).ensure();
+ }
+ auto tail_id = tqueue->get_tail(1);
+ auto clear_start_time = td::Time::now();
+ size_t keep_count = td::Random::fast(0, 2);
+ tqueue->clear(1, keep_count);
+ auto finish_time = td::Time::now();
+ LOG(INFO) << "Added TQueue events in " << clear_start_time - start_time << " seconds and cleared them in "
+ << finish_time - clear_start_time << " seconds";
+ CHECK(tqueue->get_size(1) == keep_count);
+ CHECK(tqueue->get_head(1).advance(keep_count).ok() == tail_id);
+ CHECK(tqueue->get_tail(1) == tail_id);
+}