diff options
author | George Hazan <ghazan@miranda.im> | 2022-11-30 17:48:47 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2022-11-30 17:48:47 +0300 |
commit | 0ece30dc7c0e34b4c5911969b8fa99c33c6d023c (patch) | |
tree | 671325d3fec09b999411e4e3ab84ef8259261818 /protocols/Telegram/tdlib/td/test | |
parent | 46c53ffc6809c67e4607e99951a2846c382b63b2 (diff) |
Telegram: update for TDLIB
Diffstat (limited to 'protocols/Telegram/tdlib/td/test')
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("�", invalid_surrogate_pair_error_message); + check_parse_html("�", invalid_surrogate_pair_error_message); + check_parse_html("�", invalid_surrogate_pair_error_message); + check_parse_html("🏟 🏟<<abacaba", "Unclosed start tag at byte offset 13"); + check_parse_html("🏟 🏟<<abac aba>", "Unsupported start tag \"abac\" at byte offset 13"); + check_parse_html("🏟 🏟<<abac>", "Unsupported start tag \"abac\" at byte offset 13"); + check_parse_html("🏟 🏟<<i =aba>", "Empty attribute name in the tag \"i\" at byte offset 13"); + check_parse_html("🏟 🏟<<i aba>", + "Expected equal sign in declaration of an attribute of the tag \"i\" at byte offset 13"); + check_parse_html("🏟 🏟<<i aba = ", "Unclosed start tag \"i\" at byte offset 13"); + check_parse_html("🏟 🏟<<i aba = 190azAz-.,", "Unexpected end of name token at byte offset 27"); + check_parse_html("🏟 🏟<<i aba = \"<>">", "Unclosed start tag at byte offset 13"); + check_parse_html("🏟 🏟<<i aba = \'<>">", "Unclosed start tag at byte offset 13"); + check_parse_html("🏟 🏟<</", "Unexpected end tag at byte offset 13"); + check_parse_html("🏟 🏟<<b></b></", "Unexpected end tag at byte offset 20"); + check_parse_html("🏟 🏟<<i>a</i ", "Unclosed end tag at byte offset 17"); + check_parse_html("🏟 🏟<<i>a</em >", + "Unmatched end tag at byte offset 17, expected \"</i>\", found \"</em>\""); + + check_parse_html("", "", {}); + check_parse_html("➡️ ➡️", "➡️ ➡️", {}); + check_parse_html("<>&"«»�", "<>&\"«»�", {}); + 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>🏟 <🏟</i>", "🏟 🏟🏟 <🏟", {{td::MessageEntity::Type::Italic, 5, 6}}); + check_parse_html("🏟 🏟<i>🏟 ><b aba = caba><🏟</b></i>", "🏟 🏟🏟 ><🏟", + {{td::MessageEntity::Type::Italic, 5, 7}, {td::MessageEntity::Type::Bold, 9, 3}}); + check_parse_html("🏟 🏟<<i aba = 190azAz-. >a</i>", "🏟 🏟<a", + {{td::MessageEntity::Type::Italic, 6, 1}}); + check_parse_html("🏟 🏟<<i aba = 190azAz-.>a</i>", "🏟 🏟<a", + {{td::MessageEntity::Type::Italic, 6, 1}}); + check_parse_html("🏟 🏟<<i aba = \"<>"\">a</i>", "🏟 🏟<a", + {{td::MessageEntity::Type::Italic, 6, 1}}); + check_parse_html("🏟 🏟<<i aba = '<>"'>a</i>", "🏟 🏟<a", + {{td::MessageEntity::Type::Italic, 6, 1}}); + check_parse_html("🏟 🏟<<i aba = '<>"'>a</>", "🏟 🏟<a", + {{td::MessageEntity::Type::Italic, 6, 1}}); + check_parse_html("🏟 🏟<<i>🏟 🏟<</>", "🏟 🏟<🏟 🏟<", + {{td::MessageEntity::Type::Italic, 6, 6}}); + check_parse_html("🏟 🏟<<i>a</ >", "🏟 🏟<a", {{td::MessageEntity::Type::Italic, 6, 1}}); + check_parse_html("🏟 🏟<<i>a</i >", "🏟 🏟<a", {{td::MessageEntity::Type::Italic, 6, 1}}); + check_parse_html("🏟 🏟<<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\">🏟 <🏟</span>", "🏟 🏟🏟 <🏟", + {{td::MessageEntity::Type::Spoiler, 5, 6}}); + check_parse_html("🏟 🏟<span class=\"tg-spoiler\">🏟 ><b aba = caba><🏟</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>🏟 <🏟</tg-spoiler>", "🏟 🏟🏟 <🏟", + {{td::MessageEntity::Type::Spoiler, 5, 6}}); + check_parse_html("🏟 🏟<tg-spoiler>🏟 ><b aba = caba><🏟</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?<' > </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("🏟 🏟<<pre >🏟 🏟<</>", "🏟 🏟<🏟 🏟<", + {{td::MessageEntity::Type::Pre, 6, 6}}); + check_parse_html("🏟 🏟<<code >🏟 🏟<</>", "🏟 🏟<🏟 🏟<", + {{td::MessageEntity::Type::Code, 6, 6}}); + check_parse_html("🏟 🏟<<pre><code>🏟 🏟<</code></>", "🏟 🏟<🏟 🏟<", + {{td::MessageEntity::Type::Pre, 6, 6}, {td::MessageEntity::Type::Code, 6, 6}}); + check_parse_html("🏟 🏟<<pre><code class=\"language-\">🏟 🏟<</code></>", "🏟 🏟<🏟 🏟<", + {{td::MessageEntity::Type::Pre, 6, 6}, {td::MessageEntity::Type::Code, 6, 6}}); + check_parse_html("🏟 🏟<<pre><code class=\"language-fift\">🏟 🏟<</></>", "🏟 🏟<🏟 🏟<", + {{td::MessageEntity::Type::PreCode, 6, 6, "fift"}}); + check_parse_html("🏟 🏟<<code class=\"language-fift\"><pre>🏟 🏟<</></>", "🏟 🏟<🏟 🏟<", + {{td::MessageEntity::Type::PreCode, 6, 6, "fift"}}); + check_parse_html("🏟 🏟<<pre><code class=\"language-fift\">🏟 🏟<</> </>", "🏟 🏟<🏟 🏟< ", + {{td::MessageEntity::Type::Pre, 6, 7}, {td::MessageEntity::Type::Code, 6, 6}}); + check_parse_html("🏟 🏟<<pre> <code class=\"language-fift\">🏟 🏟<</></>", "🏟 🏟< 🏟 🏟<", + {{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\">🏟 <🏟</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); +} |