This commit is contained in:
2024-10-10 19:05:48 +00:00
commit cffdcba6af
1880 changed files with 813614 additions and 0 deletions

74
td/test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,74 @@
if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2"))
message(FATAL_ERROR "CMake >= 3.0.2 is required")
endif()
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}/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}/query_merger.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}/tdclient.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tqueue.cpp
${CMAKE_CURRENT_SOURCE_DIR}/data.cpp
${CMAKE_CURRENT_SOURCE_DIR}/data.h
${TDUTILS_TEST_SOURCE}
${TDACTOR_TEST_SOURCE}
)
set(TD_TEST_SOURCE ${TD_TEST_SOURCE} PARENT_SCOPE)
set(TESTS_MAIN
main.cpp
)
#add_library(all_tests STATIC ${TD_TEST_SOURCE})
#target_include_directories(all_tests PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
#target_link_libraries(all_tests PRIVATE tdcore tdclient)
if (NOT CMAKE_CROSSCOMPILING OR EMSCRIPTEN)
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 tdmtproto)
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 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_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 tdcore)
# target_compile_options(fuzz_url PRIVATE "-fsanitize-coverage=trace-pc-guard")
endif()
add_test(run_all_tests run_all_tests)
endif()

102
td/test/country_info.cpp Normal file
View File

@@ -0,0 +1,102 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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/logging.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,
bool is_anonymous = false) {
auto result = td::CountryInfoManager::get_phone_number_info_sync(td::string(), 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);
if (result->formatted_phone_number_ != formatted_phone_number) {
LOG(ERROR) << phone_number_prefix << ' ' << result->formatted_phone_number_ << ' ' << formatted_phone_number;
CHECK(result->formatted_phone_number_ == formatted_phone_number);
}
CHECK(result->is_anonymous_ == is_anonymous);
}
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", "36014");
check_phone_number_info("4936015", "DE", "49", "36015");
check_phone_number_info("493601419", "DE", "49", "3601419");
check_phone_number_info("4936014198", "DE", "49", "36014198");
check_phone_number_info("49360141980", "DE", "49", "360141980");
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", "9 7- -- -- --");
check_phone_number_info("3198", "NL", "31", "9 8- -- -- --");
check_phone_number_info("88", "", "88", "");
check_phone_number_info("888", "FT", "888", "---- ----", true);
check_phone_number_info("8888", "FT", "888", "8 ---", true);
check_phone_number_info("88888", "FT", "888", "8 8--", true);
check_phone_number_info("888888", "FT", "888", "8 88-", true);
check_phone_number_info("8888888", "FT", "888", "8 888", true);
check_phone_number_info("88888888", "FT", "888", "8 8888", true);
check_phone_number_info("888888888", "FT", "888", "8 88888", true);
check_phone_number_info("8888888888", "FT", "888", "8 888888", true);
}

279
td/test/crypto.cpp Normal file
View File

@@ -0,0 +1,279 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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();
}

503
td/test/data.cpp Normal file
View File

@@ -0,0 +1,503 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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"
static const char thumbnail_arr[] =
"_9j_4AAQSkZJRgABAQEASABIAAD_2wBDAAICAgICAQICAgIDAgIDAwYEAwMDAwcFBQQGCAcJCAgHCAgJCg0LCQoMCggICw8LDA0ODg8OCQsQERAOEQ"
"0ODg7_2wBDAQIDAwMDAwcEBAcOCQgJDg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg4ODg7_wAARCAAyADIDASIA"
"AhEBAxEB_8QAHAAAAQUBAQEAAAAAAAAAAAAAAAUGBwgJAgQD_8QAMRAAAgEDAwMDAgUDBQAAAAAAAQIDBAURAAYSByExCBNBFCIVMlFhcZGhsQkjUm"
"KB_8QAGgEAAgMBAQAAAAAAAAAAAAAABgcAAggEBf_EACgRAAECBgIBBAMBAQAAAAAAAAECEQADBAUhMRJBBhMyUWEUIlJCkf_aAAwDAQACEQMRAD8A"
"381CvUXrbtzYtTLa6ZPx3cKj76SGQLHTn491-_E_9QC38a464dSH2H08gpLZMI9x3dmhom7EwKB98uP2yAP3Ofg6z2M8jVXvM5qqkyFpeTsCCR3Lk9"
"y2eWe_yD3znTG8esEqtR-TVAlJ9qRgqbZJ6T1956ELjyG_T6RRpqQhJHvWrIS-gkf6UfjrGHMTVePUH1HuVYxp7rTWOFj9sVDRocfoOUgYk_00nUXX"
"XqdQTrIdzmsQgHhWUcLowOcdwoPwfB1W3fG8KjaNlpnoqNK-veCWVYXk9qL26eMSOXkPZclUUdxkk-QMaiPZPqFp9wdT7btC92iltlRcZvpqea33Na"
"mNZSuVjcY7BsYDA-SMjByD5c2w01V-DMp5aS4HtffypjnXffULxNPeqmjNwlVU1YZRcFtfCSRjfXRIfUa1bG9SFuuddDbt6UUdkqHIVbhTkmmJPjmp"
"-6P-ckfrjVno5ElgSWJ1kjdQyspyGB8EH51kUWDShpFIbAAkQEjsDktk5wAMAD9hq2Pp16lTrdl2BeJzJSyKzWWWRs8Co5NCD8qR3A-CCv6aE7_45T"
"y5SqijSUKTlSNhvlJ-tkPrI0YMLD5DUmYiTVrE2WssmYMEK_lYxvQLbYHYi4-jRo0p4bUZ6-oC7y3P1IXajJ5QW-lio4jyIKgqsreDg_cwPceQMY86"
"hQAKoUeAMdzk6lzrhb5aT1R7kUqSKpoaiLt-YNEo_wAqw_8ANMuqsVbTWy3x1tEbeXV2E0isWkOcn7QPABAyTnPxp9zfJPH_ABO1US6xYSJwSMZIHE"
"krIyriFYJA2obyIzbMst_8oulZLpwVeiVkA4BPIAIBwORTkAnQOsExZNddoWn1T9Kju6G1yU9bLW01ILraKetiqKgLDJHSH3gVi94hoxIBkOYwCM6r"
"x6yLDYdt9fOi9T03l3DfN2_h8VRPBdYF-uWp-pWSCmZY40CyZYqYuPJOSqQp-0TtuK1dPt0b6oLNvfZg3R9HDM8UU8X3orcQxTDqAGwnEk4J8kAEa8"
"th2NtTZnVjprfZ62fd9vgsMU9jmu1OZJ7YaZ-ApqFsKEVXYq0vEBnZeJIw5UdRfrd5Fd6mptyipCVMFMwUyQXDsWwWcAk6Goc1ts9dZLNT01enishy"
"PcQ6jgs40csSAHc4MPynrYKyxwXHgaenngWfhMvExBl5cWB8Fc4IPgg6WbJdqi07ntd5pJ-Ro6mGpgfOSntsGwD-hGRj9zpSHTjfm-t6XWt2zXWqhg"
"J-rqqGpo-VNLKMMY4nVwW5sCzKue5YjAPHTbobXf6eWjs9-t0duv1RHFIaSGpWYAT_AJMEAEEkkFWAYEeMYJd9vvtHeJyKWanPByT_AEBkD6ZzCUuX"
"j1fYKeZVypmCviAM_ofaTjB5MPrbuRGucUizU0c0bZR1DL2-CMjRriliFNbKanzn2olTP8DGjWczvGo0onQfcV666bXhFwsXUGKnhq6m0LItRSTSGN"
"KpUR5olZgDxAdCCcHCuT8Y1nd0O3dW9X-gta1nv0fTvct_u9xZZ4LuK-sjZgrSSokhb2hyBVEAwBGDjuQdft02GPcuxLjZ3dYnniIhlePmI5B-Vivy"
"AfIyMjI-dUZpukO99qbiEtv6YxVFQjk-9QVkP08mD5BGJMHzhhkds_roG8lNVUKlOhSwkJCSHPEJUVMwBcEqJDgx7NqlU9P6hQySslSgeyQA-W6SIb"
"O6tqQbUk2jcEu0k93WrRPrGjVZPd4_mRhjv7gRyvEqCMnHjTT6f1VmrfUttCyb2RL7FT0sFos9NV0iJTQUsCPUksVHeV5QWLcTyZBnHwp9RoKy47tp"
"pr5a7haK6mOGiniaBBjHZc_GRnlnP9iI1u1huF9r7dV2GrnpNx0NSs9BUUqM5Dg5AKr3Iz_Tv8Egnvg_h12Rbq2pXI9JC-BlBRZSikK5nJ_UEEAOzk"
"dCF_5N5hbJVzpKZM4rKeYmcQ6U8m4aDqKSC_F2B7OIsX1grfTIfVHsSh6gRXPZW5LeJLlYLxaqeWgt0pMyoyTVKf7QkZ4lAVuLEHAbDYMc7F3jsHdX"
"-tXPtXb9FX38PRrW1N5p0MlKwp6f3qcyOo4BFaQxA5yWVVxjSjvDp31r6rdNVgvHS2WgqaWZaumd6yn9mr44WSLizLKnuAI6lk7PChPbOrhem3phH0"
"k9Ie1NpyWZbHdVgM9ypRULMY5nYkoZFGG4jio8-PJ8nsE2bSLJQeK8j_oIPz0SNwRLpqKsokKUrk5H6l2YEKSrQ7AwexE76NGjXmx0QaMDRo1IkfKS"
"KKZCk0ayp_xdQR_fXEVLS0yn6emip8-fbjC_40aNWc66ijDbZj0YGjRo1WLwaNGjUiR__9k";
const char *thumbnail = thumbnail_arr;
const size_t thumbnail_size = sizeof(thumbnail_arr) - 1;
static const char gzip_bomb_arr[] =
"eJzt3VlQU2cYxvGTSFSkqHWJuIHTUqotAetIBmWzVUeUqlRrFcIqZlxCUKRshZBULVS0oqLiwqJWUVwAQQNBgSgt1EHFlS1AQCsZ1qAsASKTRonnnJ"
"ledHrViz5cfvMOOd_v_d-eJCK5vf6MaUmDLK7Jk_tZ9sSDEyYmdo7fOMeqdKSs-jXRxA_m8_fZPGTMW-Rhlf-AP7XxrGTW-qCOyWPtB90qdo-"
"NHtXfEi3Wlul0bWrxYNa1QaVY12ure9OdRgz_SVzyX-"
"nS59eYP1uYV68zHBLi7xcf2qyd7ddSbhX2geFsfPs3aeO04xhKLWffzBGGQyOHIeckkUmXZ1qZoH2a4XBWddSKRKGJ-"
"9UQxe8HGIbDXaZLU4U8SfDf_-UhiXroGG0wJSatvIgVcbXihV2YKTnpdP6ulHWxUtaxsez9U96O_ahCypHn9_AnOow0nI3uu1gRN-"
"Qq18SU0wZXFVfwlcxblapX025Sk6mPNymY37bVhV0LeK_hp7-3gu3nNZizl7pj1FNVSYxlY1QxnzZ4QanK2c7waeuO-"
"MWbmly9NUfIuBQadLP6C8PZouwzf-QKTebaiGp-o-7o1Nrt7zKhq1CZQxt8og6pySOyeiNvlWaSk4UX5tdICf12vNvNSF_"
"3P2ukLLOkokDaHc17B7ItjPsbtnvQBls0snaepGrQ0SegipxsWHsykCfZlqKwoTYROPt1oIK5382XS7tjwmB0dYJRtDrPmrayHq1XH0cuEJlnXROQk"
"-o5K6WcRTceCKdQm-BOipQKGaXnbU_R7rhM5Ny-"
"jFmk4bGpwbBBkY2W3XjDOaGq2o6c1ByxUrBPe6qktNqmxzsqpMSLx0kraXf81dmi72NkOTyILJElskSWBLJElgSyRJbDg8gSWSJLZEkgS2RJIEtkOT"
"yILJElskSWBLJElgSyRJbDi0CWyBJZIktkiSyH74gskSWyRJYEskSWBLJElu_uiCyRJbJElgSyRJbDvsgSWSJLZIkskeXwJLJElsgSWSJLZPluEFn-"
"11nq_NUHhOP3MGfHl7pe3eBeYz_S1Jhbye4pZRxnTCxxbZeP9Jw3gnXUOLb4jm1MyAOjcEmSo-ESnr5HI-2Nj9_kupErWfL5sYyx-"
"phmrlrZSlZXqylU6gsrH5TVryVZHqWWLdY_hU9x2cDJhe8f7efK9XzzVczmzPtrqMF4_qUS_"
"ebj1iYWkHN9InW4gt2YExOkeUru6brq1UH951Spg4vSyY2e7V13Ii5RIhPk0gY3nmjNZplVNT09R-7zrEiprZey8q-kKZxCSdSOSMdN-s_"
"pjUrOyiD5t4jWNTdZ-nXWWlODVzekLwxklIba22WQ-FuUmpg8IcOmTRW2mhKKMh-"
"nbyRiyFwlaCV7ACUoQQlKUIISlKAEJShBCUpQghKUoAQlKEEJSlCCEpSgBCUoQQlKUIISlKAEJShBCUpQghKUoAQlKEEJSlCCEpSgBCUoQQlKUIISl"
"KAEJShBCcp_ouzPDS_"
"jjv6KMWnp4tNs6yuBM3bHLpkuGBP1nLgnMfa3DN8xwjrl9p14o26PpinFCzbvcpC7meuhEyLZUuJCaJGU9nmanev3cOQBLQeSaHfIyLjcYdno0LOUW"
"khtk2bB9revxtcp15Cvxj9qCEj9iSPfmpIlpE0W1k32N-naUPDiE-oOwm2t2xyM-8eKDlELCesXPVC8_XoFTd5T8usVrmuOb_"
"Y32eGpEpyiTdqGL61mOh7rjFhO5XDqZEHtKmbRYt9z1EJmxqR1S99-F8IQJ5T8LoQOp2aPamZy5UAtbXX15d4fSomE8w4-"
"tBzS0zOliRL1wyk0oDil6pa-"
"pbYQsZs31SwoQQlKUIISlKAEJShBCUpQghKUoAQlKEEJSlCCEpSgBCUoQQlKUIISlKAEJShBCUpQghKUoAQlKEEJSlCCEpSgBCUoQQlKUIISlKAEJS"
"hBCUpQ_jtKiTgqPPDO3MOL_Pv3TxL68UbFJ5tlxpXs_XLFDAd3Gy-JIPvL_"
"KOXG414uxZmy1zt7B9axXrM53w9d0a0L0vMGGduuE7ODydO5bOO39kQKiPf5d-Udqb6Ck8SEOGbKSMXsSmvXuqVR1T4ireEkA9010f1xDnVKNpCMy_"
"8PeXBKz7NVkGM9LnfeXeS7_LXbJ7NbWM3mjUkCTrJRdQI8zhVHHl3lUVtHbnbnKqB6wmfMpRlhfdISsvLSbK9PMmc_euSyN-sd-2bGn-_"
"gGUfq3l5kvyhddcwhbA81KTrTW8cl3qg9lDnjhfL5ZpgW9qTt_KDnrP9xgQ8e0m-y39YFLsxN4hxoEkkayYXcbg-"
"TLGznukoHjpzn3qgqJTV2ogJXaK6ctqTgxKUoPyfUeqmx4szeRJuj3josnbUu0O57kcXXX-bi25IozxSONDzF5jalI4";
const char *gzip_bomb = gzip_bomb_arr;
const size_t gzip_bomb_size = sizeof(gzip_bomb_arr) - 1;
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/"
"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"
"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+"
"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"
"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/"
"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"
"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+"
"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"
"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;

24
td/test/data.h Normal file
View File

@@ -0,0 +1,24 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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"
extern const char *thumbnail;
extern const size_t thumbnail_size;
extern const char *gzip_bomb;
extern const size_t gzip_bomb_size;
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;

809
td/test/db.cpp Normal file
View File

@@ -0,0 +1,809 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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"
#include "td/utils/Random.h"
#include "td/utils/Slice.h"
#include "td/utils/Status.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/tests.h"
#include <limits>
#include <map>
#include <memory>
template <class ContainerT>
static typename ContainerT::value_type &rand_elem(ContainerT &cont) {
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();
}
td::Binlog::destroy(binlog_name).ignore();
}
TEST(DB, binlog_encryption) {
td::CSlice binlog_name = "test_binlog";
td::Binlog::destroy(binlog_name).ignore();
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');
{
td::Binlog binlog;
binlog.init(binlog_name.str(), [](const td::BinlogEvent &x) {}).ensure();
binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_event_id(), 1, 0, td::create_storer("AAAA")),
td::BinlogDebugInfo{__FILE__, __LINE__});
binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_event_id(), 1, 0, td::create_storer("BBBB")),
td::BinlogDebugInfo{__FILE__, __LINE__});
binlog.add_raw_event(td::BinlogEvent::create_raw(binlog.next_event_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(td::BinlogEvent::create_raw(binlog.next_event_id(), 1, 0, td::create_storer("CCCC")),
td::BinlogDebugInfo{__FILE__, __LINE__});
binlog.close().ensure();
}
td::Binlog::destroy(binlog_name).ignore();
return;
auto add_suffix = [&] {
auto fd = td::FileFd::open(binlog_name, td::FileFd::Flags::Write | td::FileFd::Flags::Append).move_as_ok();
fd.write("abacabadaba").ensure();
};
add_suffix();
{
td::vector<td::string> v;
LOG(INFO) << "RESTART";
td::Binlog binlog;
binlog
.init(
binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.get_data().str()); }, hello)
.ensure();
CHECK(v == td::vector<td::string>({"AAAA", "BBBB", long_data, "CCCC"}));
}
add_suffix();
{
td::vector<td::string> v;
LOG(INFO) << "RESTART";
td::Binlog binlog;
auto status = binlog.init(
binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.get_data().str()); }, cucumber);
CHECK(status.is_error());
}
add_suffix();
{
td::vector<td::string> v;
LOG(INFO) << "RESTART";
td::Binlog binlog;
auto status = binlog.init(
binlog_name.str(), [&](const td::BinlogEvent &x) { v.push_back(x.get_data().str()); }, cucumber, hello);
CHECK(v == td::vector<td::string>({"AAAA", "BBBB", long_data, "CCCC"}));
}
td::Binlog::destroy(binlog_name).ignore();
}
TEST(DB, sqlite_lfs) {
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) {
td::string path = "test_sqlite_db";
td::SqliteDb::destroy(path).ignore();
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 = 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");
}
td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
td::SqliteDb::change_key(path, false, cucumber, empty).ensure();
td::SqliteDb::change_key(path, false, cucumber, empty).ensure();
td::SqliteDb::open_with_key(path, false, tomato).ensure_error();
{
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);
}
td::SqliteDb::change_key(path, false, tomato, cucumber).ensure();
td::SqliteDb::change_key(path, false, tomato, cucumber).ensure();
td::SqliteDb::open_with_key(path, false, cucumber).ensure_error();
{
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);
}
td::SqliteDb::change_key(path, false, empty, tomato).ensure();
td::SqliteDb::change_key(path, false, empty, tomato).ensure();
{
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);
}
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 = td::uint64;
struct DbQuery {
enum class Type { Get, Set, Erase, EraseBatch } type = Type::Get;
SeqNo tid = 0;
td::string key;
td::string value;
// for EraseBatch
td::vector<td::string> erased_keys;
};
static td::StringBuilder &operator<<(td::StringBuilder &string_builder, const DbQuery &query) {
string_builder << "seq_no = " << query.tid << ": ";
switch (query.type) {
case DbQuery::Type::Get:
return string_builder << "Get " << query.key << " = " << query.value;
case DbQuery::Type::Set:
return string_builder << "Set " << query.key << " = " << query.value;
case DbQuery::Type::Erase:
return string_builder << "Del " << query.key;
case DbQuery::Type::EraseBatch:
return string_builder << "Del " << query.erased_keys;
default:
UNREACHABLE();
return string_builder;
}
}
template <class ImplT>
class QueryHandler {
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::Type::Set:
impl_.set(query.key, query.value);
query.tid = 1;
return;
case DbQuery::Type::Erase:
impl_.erase(query.key);
query.tid = 1;
return;
case DbQuery::Type::EraseBatch:
impl_.erase_batch(query.erased_keys);
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::Type::Set:
query.tid = impl_.set(query.key, query.value);
return;
case DbQuery::Type::Erase:
query.tid = impl_.erase(query.key);
return;
case DbQuery::Type::EraseBatch:
query.tid = impl_.erase_batch(query.erased_keys);
return;
}
}
private:
ImplT impl_;
};
class SqliteKV {
public:
td::string get(const td::string &key) {
return kv_->get().get(key);
}
SeqNo set(const td::string &key, const td::string &value) {
kv_->get().set(key, value);
return 0;
}
SeqNo erase(const td::string &key) {
kv_->get().erase(key);
return 0;
}
SeqNo erase_batch(td::vector<td::string> keys) {
for (auto &key : keys) {
kv_->get().erase(key);
}
return 0;
}
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<td::SqliteKeyValueSafe> kv_;
};
class BaselineKV {
public:
td::string get(const td::string &key) {
return map_[key];
}
SeqNo set(const td::string &key, td::string value) {
map_[key] = std::move(value);
return ++current_tid_;
}
SeqNo erase(const td::string &key) {
map_.erase(key);
return ++current_tid_;
}
SeqNo erase_batch(td::vector<td::string> keys) {
for (auto &key : keys) {
map_.erase(key);
}
SeqNo result = current_tid_ + 1;
current_tid_ += map_.size();
return result;
}
private:
std::map<td::string, td::string> map_;
SeqNo current_tid_ = 0;
};
TEST(DB, key_value) {
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)));
}
int queries_n = 1000;
td::vector<DbQuery> queries(queries_n);
for (auto &q : queries) {
int op = td::Random::fast(0, 3);
const auto &key = rand_elem(keys);
if (op == 0) {
q.type = DbQuery::Type::Get;
q.key = key;
} else if (op == 1) {
q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 2) {
q.type = DbQuery::Type::Set;
q.key = key;
q.value = rand_elem(values);
} else if (op == 3) {
q.type = DbQuery::Type::EraseBatch;
q.erased_keys.resize(td::Random::fast(0, 3));
for (auto &erased_key : q.erased_keys) {
erased_key = rand_elem(keys);
}
}
}
QueryHandler<BaselineKV> baseline;
QueryHandler<td::SeqKeyValue> kv;
QueryHandler<td::TsSeqKeyValue> ts_kv;
QueryHandler<td::BinlogKeyValue<td::Binlog>> new_kv;
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<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) {
DbQuery a = q;
DbQuery b = q;
DbQuery c = q;
DbQuery d = q;
DbQuery e = q;
baseline.do_query(a);
kv.do_query(b);
ts_kv.do_query(c);
sqlite_kv.do_query(d);
new_kv.do_query(e);
ASSERT_EQ(a.value, b.value);
ASSERT_EQ(a.value, c.value);
ASSERT_EQ(a.value, d.value);
ASSERT_EQ(a.value, e.value);
if (cnt++ % 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();
}
#if !TD_THREAD_UNSUPPORTED
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(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 1000; i++) {
values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
int threads_n = 4;
int queries_n = 10000;
td::vector<td::vector<DbQuery>> queries(threads_n, td::vector<DbQuery>(queries_n));
for (auto &qs : queries) {
for (auto &q : qs) {
int op = td::Random::fast(0, 10);
const auto &key = rand_elem(keys);
if (op == 0) {
q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 1) {
q.type = DbQuery::Type::EraseBatch;
q.erased_keys.resize(td::Random::fast(0, 3));
for (auto &erased_key : q.erased_keys) {
erased_key = rand_elem(keys);
}
} else if (op <= 6) {
q.type = DbQuery::Type::Set;
q.key = key;
q.value = rand_elem(values);
} else {
q.type = DbQuery::Type::Get;
q.key = key;
}
}
}
QueryHandler<BaselineKV> baseline;
SeqQueryHandler<td::TsSeqKeyValue> ts_kv;
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] = td::thread([&ts_kv, &queries, &res, i] {
for (auto q : queries[i]) {
ts_kv.do_query(q);
res[i].push_back(q);
}
});
}
for (auto &thread : threads) {
thread.join();
}
td::vector<std::size_t> pos(threads_n);
while (true) {
bool was = false;
for (int i = 0; i < threads_n; i++) {
auto p = pos[i];
if (p == res[i].size()) {
continue;
}
auto &q = res[i][p];
if (q.tid == 0) {
if (q.type == DbQuery::Type::Get) {
auto nq = q;
baseline.do_query(nq);
if (nq.value == q.value) {
was = true;
pos[i]++;
}
} else {
was = true;
pos[i]++;
}
}
}
if (was) {
continue;
}
int best = -1;
SeqNo best_tid = 0;
for (int i = 0; i < threads_n; i++) {
auto p = pos[i];
if (p == res[i].size()) {
continue;
}
was = true;
auto &q = res[i][p];
if (q.tid != 0) {
if (best == -1 || q.tid < best_tid) {
best = i;
best_tid = q.tid;
}
}
}
if (!was) {
break;
}
ASSERT_TRUE(best != -1);
baseline.do_query(res[best][pos[best]]);
pos[best]++;
}
}
#endif
TEST(DB, persistent_key_value) {
using KeyValue = 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(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
for (int i = 0; i < 1000; i++) {
values.push_back(td::rand_string('a', 'b', td::Random::fast(1, 10)));
}
QueryHandler<BaselineKV> baseline;
for (int iter = 0; iter < 25; iter++) {
int threads_n = 4;
int queries_n = 3000 / threads_n;
td::vector<td::vector<DbQuery>> queries(threads_n, td::vector<DbQuery>(queries_n));
for (auto &qs : queries) {
for (auto &q : qs) {
int op = td::Random::fast(0, 10);
const auto &key = rand_elem(keys);
if (op == 0) {
q.type = DbQuery::Type::Erase;
q.key = key;
} else if (op == 1) {
q.type = DbQuery::Type::EraseBatch;
q.erased_keys.resize(td::Random::fast(0, 3));
for (auto &erased_key : q.erased_keys) {
erased_key = rand_elem(keys);
}
} else if (op <= 6) {
q.type = DbQuery::Type::Set;
q.key = key;
q.value = rand_elem(values);
} else {
q.type = DbQuery::Type::Get;
q.key = key;
}
}
}
td::vector<td::vector<DbQuery>> res(threads_n);
class Worker final : public td::Actor {
public:
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() final {
for (auto q : *queries_) {
kv_->do_query(q);
res_->push_back(q);
}
stop();
}
private:
td::ActorShared<> parent_;
std::shared_ptr<SeqQueryHandler<KeyValue>> kv_;
const td::vector<DbQuery> *queries_;
td::vector<DbQuery> *res_;
};
class Main final : public td::Actor {
public:
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() final {
LOG(INFO) << "Start up";
kv_->impl().init("test_pmc").ensure();
for (int i = 0; i < threads_n_; 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() final {
LOG(INFO) << "Tear down";
// kv_->impl().close();
}
void hangup_shared() final {
LOG(INFO) << "Hang up";
ref_cnt_--;
if (ref_cnt_ == 0) {
kv_->impl().close();
td::Scheduler::instance()->finish();
stop();
}
}
void hangup() final {
LOG(ERROR) << "BAD HANGUP";
}
private:
int threads_n_;
const td::vector<td::vector<DbQuery>> *queries_;
td::vector<td::vector<DbQuery>> *res_;
std::shared_ptr<SeqQueryHandler<KeyValue>> kv_{new SeqQueryHandler<KeyValue>()};
int ref_cnt_;
};
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)) {
// empty
}
sched.finish();
td::vector<std::size_t> pos(threads_n);
while (true) {
bool was = false;
for (int i = 0; i < threads_n; i++) {
auto p = pos[i];
if (p == res[i].size()) {
continue;
}
auto &q = res[i][p];
if (q.tid == 0) {
if (q.type == DbQuery::Type::Get) {
auto nq = q;
baseline.do_query(nq);
if (nq.value == q.value) {
was = true;
pos[i]++;
}
} else {
was = true;
pos[i]++;
}
}
}
if (was) {
continue;
}
LOG(DEBUG) << pos;
int best = -1;
SeqNo best_tid = 0;
for (int i = 0; i < threads_n; i++) {
auto p = pos[i];
if (p == res[i].size()) {
continue;
}
was = true;
auto &q = res[i][p];
LOG(DEBUG) << i << ' ' << p << ' ' << q;
if (q.tid != 0) {
if (best == -1 || q.tid < best_tid) {
best = i;
best_tid = q.tid;
}
}
}
if (!was) {
break;
}
ASSERT_TRUE(best != -1);
baseline.do_query(res[best][pos[best]]);
pos[best]++;
}
}
td::SqliteDb::destroy(path).ignore();
}

33
td/test/fuzz_url.cpp Normal file
View File

@@ -0,0 +1,33 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/MessageEntity.h"
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include "td/utils/Slice.h"
#include <cstddef>
#include <cstdint>
static td::string get_utf_string(td::Slice from) {
td::string res;
td::string alph = " ab@./01#";
for (auto c : from) {
res += alph[static_cast<td::uint8>(c) % alph.size()];
}
LOG(ERROR) << res;
return res;
}
extern "C" int LLVMFuzzerTestOneInput(std::uint8_t *data, std::size_t data_size) {
td::find_urls(get_utf_string(td::Slice(data, data_size)));
//td::find_hashtags(get_utf_string(td::Slice(data, data_size)));
//td::find_bot_commands(get_utf_string(td::Slice(data, data_size)));
//td::is_email_address(get_utf_string(td::Slice(data, data_size)));
//td::find_mentions(get_utf_string(td::Slice(data, data_size)));
return 0;
}

565
td/test/http.cpp Normal file
View File

@@ -0,0 +1,565 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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"
#if TD_DARWIN_WATCH_OS
#include "td/net/DarwinHttp.h"
#endif
#include "td/net/HttpChunkedByteFlow.h"
#include "td/net/HttpHeaderCreator.h"
#include "td/net/HttpQuery.h"
#include "td/net/HttpReader.h"
#include "td/utils/AesCtrByteFlow.h"
#include "td/utils/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/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 "td/utils/tests.h"
#include "td/utils/UInt.h"
#include <algorithm>
#include <condition_variable>
#include <limits>
#include <mutex>
static td::string make_chunked(const td::string &str) {
auto v = td::rand_split(str);
td::string res;
for (auto &s : v) {
res += PSTRING() << td::format::as_hex_dump(static_cast<td::int32>(s.size()));
res += "\r\n";
res += s;
res += "\r\n";
}
res += "0\r\n\r\n";
return res;
}
static td::string gen_http_content() {
int t = td::Random::fast(0, 2);
int len;
if (t == 0) {
len = td::Random::fast(1, 10);
} else if (t == 1) {
len = td::Random::fast(100, 200);
} else {
len = td::Random::fast(1000, 20000);
}
return td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), len);
}
static td::string make_http_query(td::string content, td::string content_type, 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", td::rand_string('a', 'z', td::Random::fast(1, 2000)));
if (!content_type.empty()) {
hc.add_header("content-type", content_type);
}
if (is_gzip) {
td::BufferSlice zip;
if (zip_override.empty()) {
zip = td::gzencode(content, gzip_k);
} else {
zip = td::BufferSlice(zip_override);
}
if (!zip.empty()) {
hc.add_header("content-encoding", "gzip");
content = zip.as_slice().str();
}
}
if (is_chunked) {
hc.add_header("transfer-encoding", "chunked");
content = make_chunked(content);
} else {
hc.set_content_size(content.size());
}
auto r_header = hc.finish();
CHECK(r_header.is_ok());
return PSTRING() << r_header.ok() << content;
}
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), td::string(), is_chunked, is_gzip);
}
static td::string join(const td::vector<td::string> &v) {
td::string res;
for (auto &s : v) {
res += s;
}
return res;
}
TEST(Http, stack_overflow) {
td::ChainBufferWriter writer;
td::BufferSlice slice(td::string(256, 'A'));
for (int i = 0; i < 1000000; i++) {
td::ChainBufferWriter tmp_writer;
writer.append(slice.clone());
}
{
auto reader = writer.extract_reader();
reader.sync_with_writer();
}
}
TEST(Http, reader) {
#if TD_ANDROID || TD_TIZEN
return;
#endif
td::clear_thread_locals();
auto start_mem = td::BufferAllocator::get_buffer_mem();
auto start_size = td::BufferAllocator::get_buffer_slice_size();
{
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
#if TD_GCC
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wself-move"
#endif
a = std::move(a);
b = std::move(b);
#if TD_GCC
#pragma GCC diagnostic pop
#endif
#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();
td::HttpReader reader;
int max_post_size = 10000;
reader.init(&input, max_post_size, 0);
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 = td::rand_split(join(v));
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() << td::tag("ok", res.size());
ASSERT_TRUE(r_state.is_ok());
auto state = r_state.ok();
if (state == 0) {
if (q.files_.empty()) {
ASSERT_TRUE(td::narrow_cast<int>(q.content_.size()) <= max_post_size);
auto expected = contents[res.size()];
ASSERT_EQ(expected, q.content_.str());
res.push_back(q.content_.str());
} else {
auto r_fd = 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();
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);
ASSERT_EQ(contents[res.size()], content);
res.push_back(content);
fd.close();
}
} else {
break;
}
}
}
ASSERT_EQ(contents.size(), res.size());
ASSERT_EQ(contents, res);
}
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 must be disabled on low-memory systems
return;
#endif
auto gzip_bomb_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(td::string(), td::string(), 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, 100000000, 0);
for (auto &part : parts) {
input_writer.append(part);
input.sync_with_writer();
auto r_state = reader.read_next(&q);
if (r_state.is_error()) {
LOG(INFO) << r_state.error();
return;
}
ASSERT_TRUE(r_state.ok() != 0);
}
}
TEST(Http, 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(td::string(), "application/json", 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 = td::rand_string('a', 'z', 1000000);
auto parts = td::rand_split(str);
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
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);
td::AesCtrByteFlow aes_decode;
aes_decode.init(key, iv);
td::ByteFlowSink sink;
source >> aes_encode >> aes_decode >> sink;
ASSERT_TRUE(!sink.is_ready());
for (auto &part : parts) {
input_writer.append(part);
source.wakeup();
}
ASSERT_TRUE(!sink.is_ready());
source.close_input(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, aes_file_encryption) {
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));
{
td::BufferedFdBase<td::FileFd> fd(td::FileFd::open(name, td::FileFd::Write | td::FileFd::Create).move_as_ok());
auto parts = td::rand_split(str);
td::ChainBufferWriter output_writer;
auto output_reader = output_writer.extract_reader();
td::ByteFlowSource source(&output_reader);
td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
td::ByteFlowSink sink;
source >> aes_encode >> sink;
fd.set_output_reader(sink.get_output());
for (auto &part : parts) {
output_writer.append(part);
source.wakeup();
fd.flush_write().ensure();
}
fd.close();
}
{
td::BufferedFdBase<td::FileFd> fd(td::FileFd::open(name, td::FileFd::Read).move_as_ok());
td::ChainBufferWriter input_writer;
auto input_reader = input_writer.extract_reader();
td::ByteFlowSource source(&input_reader);
td::AesCtrByteFlow aes_encode;
aes_encode.init(key, iv);
td::ByteFlowSink sink;
source >> aes_encode >> sink;
fd.set_input_writer(&input_writer);
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(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
auto result = sink.result()->move_as_buffer_slice().as_slice().str();
ASSERT_EQ(str, result);
}
td::unlink(name).ignore();
}
TEST(Http, chunked_flow) {
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();
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(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
LOG_IF(ERROR, sink.status().is_error()) << sink.status();
ASSERT_TRUE(sink.status().is_ok());
auto res = sink.result()->move_as_buffer_slice().as_slice().str();
ASSERT_EQ(str.size(), res.size());
ASSERT_EQ(str, res);
}
TEST(Http, chunked_flow_error) {
auto str = 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 = td::rand_split(new_str);
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
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();
}
ASSERT_TRUE(!sink.is_ready());
source.close_input(td::Status::OK());
ASSERT_TRUE(sink.is_ready());
ASSERT_TRUE(!sink.status().is_ok());
}
}
TEST(Http, gzip_chunked_flow) {
auto str = td::rand_string('a', 'z', 1000000);
auto parts = td::rand_split(make_chunked(td::gzencode(str, 2.0).as_slice().str()));
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
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(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(td::string(), td::string(), 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);
}
TEST(Http, partial_form_data) {
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
td::HttpReader reader;
reader.init(&input, 0, 0);
auto query =
make_http_query("------abcd\r\nCo", "Content-Type: multipart/form-data; boundary=----abcd", false, false);
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(400, r_state.error().code());
}
TEST(Http, form_data) {
td::ChainBufferWriter input_writer;
auto input = input_writer.extract_reader();
td::HttpReader reader;
reader.init(&input, 0, 1);
auto query = make_http_query(
"------abcd\r\n"
"Content-Disposition: form-data; name=\"text\"\r\n"
"\r\n"
"some text\r\n"
"------abcd\r\n"
"Content-Disposition: form-data; name=\"text2\"\r\n"
"\r\n"
"some text\r\n"
"more text\r\n"
"------abcd\r\n"
"Content-Disposition: form-data; name=\"file\"; filename=\"file.txt\"\r\n"
"Content-Type: text/plain\r\n"
"\r\n"
"File content\r\n"
"------abcd--",
"Content-Type: multipart/form-data; boundary=----abcd", false, false);
input_writer.append(query);
input.sync_with_writer();
td::HttpQuery q;
auto r_state = reader.read_next(&q);
ASSERT_TRUE(r_state.is_ok());
ASSERT_EQ(2u, q.args_.size());
ASSERT_EQ("text", q.args_[0].first);
ASSERT_EQ("some text", q.args_[0].second);
ASSERT_EQ("text2", q.args_[1].first);
ASSERT_EQ("some text\r\nmore text", q.args_[1].second);
ASSERT_EQ(1u, q.files_.size());
ASSERT_EQ("file.txt", q.files_[0].name);
ASSERT_EQ("file", q.files_[0].field_name);
ASSERT_EQ("text/plain", q.files_[0].content_type);
ASSERT_EQ(12, q.files_[0].size);
ASSERT_TRUE(!q.files_[0].temp_file_name.empty());
}
#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

1381
td/test/link.cpp Normal file

File diff suppressed because it is too large Load Diff

66
td/test/main.cpp Normal file
View File

@@ -0,0 +1,66 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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/common.h"
#include "td/utils/crypto.h"
#include "td/utils/ExitGuard.h"
#include "td/utils/logging.h"
#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) {
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('o', "offset", "run tests from the specified test",
[&](td::Slice offset) { runner.set_offset(offset.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(
[] {
td::TestsRunner &default_runner = td::TestsRunner::get_default();
if (!default_runner.run_all_step()) {
emscripten_cancel_main_loop();
}
},
10, 0);
#else
runner.run_all();
#endif
}

2009
td/test/message_entities.cpp Normal file

File diff suppressed because it is too large Load Diff

742
td/test/mtproto.cpp Normal file
View File

@@ -0,0 +1,742 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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/ConfigManager.h"
#include "td/telegram/net/PublicRsaKeySharedMain.h"
#include "td/telegram/NotificationManager.h"
#include "td/telegram/telegram_api.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/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/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/HttpDate.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"
#include <memory>
TEST(Mtproto, GetHostByNameActor) {
int threads_n = 1;
td::ConcurrentScheduler sched(threads_n, 0);
int cnt = 1;
td::vector<td::ActorOwn<td::GetHostByNameActor>> actors;
{
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));
};
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);
}
}
}
}
cnt--;
sched.start();
while (sched.run_main(10)) {
// empty
}
sched.finish();
}
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) {
td::string data =
" hO//tt \b\n\tiwPVovorKtIYtQ8y2ik7CqfJiJ4pJOCLRa4fBmNPixuRPXnBFF/3mTAAZoSyHq4SNylGHz0Cv1/"
"FnWWdEV+BPJeOTk+ARHcNkuJBt0CqnfcVCoDOpKqGyq0U31s2MOpQvHgAG+Tlpg02syuH0E4dCGRw5CbJPARiynteb9y5fT5x/"
"kmdp6BMR5tWQSQF0liH16zLh8BDSIdiMsikdcwnAvBwdNhRqQBqGx9MTh62MDmlebjtczE9Gz0z5cscUO2yhzGdphgIy6SP+"
"bwaqLWYF0XdPGjKLMUEJW+rou6fbL1t/EUXPtU0XmQAnO0Fh86h+AqDMOe30N4qKrPQ== ";
td::telegram_api::object_ptr<td::telegram_api::help_configSimple> config = td::decode_config(data).move_as_ok();
}
class TestPingActor final : public td::Actor {
public:
TestPingActor(td::IPAddress ip_address, td::Status *result) : ip_address_(ip_address), result_(result) {
}
private:
td::IPAddress ip_address_;
td::unique_ptr<td::mtproto::PingConnection> ping_connection_;
td::Status *result_;
bool is_inited_ = false;
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_ = 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() final {
if (is_inited_) {
td::Scheduler::unsubscribe_before_close(ping_connection_->get_poll_info().get_pollable_fd_ref());
}
td::Scheduler::instance()->finish();
}
void loop() final {
auto status = ping_connection_->flush();
if (status.is_error()) {
*result_ = std::move(status);
return stop();
}
if (ping_connection_->was_pong()) {
LOG(INFO) << "Receive pong";
return stop();
}
}
void timeout_expired() final {
*result_ = td::Status::Error("Timeout expired");
stop();
}
};
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;
}
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_.create_actor_unsafe<TestPingActor>(0, "Pinger", get_default_ip_address(), &result_).release();
sched_.start();
is_inited_ = true;
}
bool ret = sched_.run_main(10);
if (ret) {
return true;
}
sched_.finish();
if (result_.is_error()) {
LOG(ERROR) << result_;
}
return false;
}
private:
bool is_inited_ = false;
td::ConcurrentScheduler sched_{0, 0};
td::Status result_;
};
td::RegisterTest<Mtproto_ping> mtproto_ping("Mtproto_ping");
class HandshakeContext final : public td::mtproto::AuthKeyHandshakeContext {
public:
td::mtproto::DhCallback *get_dh_callback() final {
return nullptr;
}
td::mtproto::PublicRsaKeyInterface *get_public_rsa_key_interface() final {
return public_rsa_key_.get();
}
private:
std::shared_ptr<td::mtproto::PublicRsaKeyInterface> public_rsa_key_ = td::PublicRsaKeySharedMain::create(true);
};
class HandshakeTestActor final : public td::Actor {
public:
HandshakeTestActor(td::int32 dc_id, td::Status *result) : dc_id_(dc_id), result_(result) {
}
private:
td::int32 dc_id_ = 0;
td::Status *result_;
bool wait_for_raw_connection_ = false;
td::unique_ptr<td::mtproto::RawConnection> raw_connection_;
bool wait_for_handshake_ = false;
td::unique_ptr<td::mtproto::AuthKeyHandshake> handshake_;
td::Status status_;
bool wait_for_result_ = false;
void tear_down() final {
if (raw_connection_) {
raw_connection_->close();
}
finish(td::Status::Error("Interrupted"));
}
void loop() final {
if (!wait_for_raw_connection_ && !raw_connection_) {
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_ = td::make_unique<td::mtproto::AuthKeyHandshake>(dc_id_, 3600);
}
if (raw_connection_ && handshake_) {
if (wait_for_result_) {
wait_for_result_ = false;
if (status_.is_error()) {
finish(std::move(status_));
return stop();
}
if (!handshake_->is_ready_for_finish()) {
finish(td::Status::Error("Key is not ready.."));
return stop();
}
finish(td::Status::OK());
return stop();
}
wait_for_result_ = true;
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::on_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::on_handshake, std::move(handshake), 1);
}))
.release();
wait_for_raw_connection_ = true;
wait_for_handshake_ = true;
}
}
void on_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_ = td::Status::OK();
} else {
status_ = r_raw_connection.move_as_error();
}
// TODO: save error
loop();
}
void on_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());
handshake_ = r_handshake.move_as_ok();
loop();
}
void finish(td::Status status) {
if (!result_) {
return;
}
*result_ = std::move(status);
result_ = nullptr;
td::Scheduler::instance()->finish();
}
};
class Mtproto_handshake final : public td::Test {
public:
using Test::Test;
bool step() final {
if (!is_inited_) {
sched_.create_actor_unsafe<HandshakeTestActor>(0, "HandshakeTestActor", get_default_dc_id(), &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_handshake> mtproto_handshake("Mtproto_handshake");
class Socks5TestActor final : public td::Actor {
public:
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 final : public td::TransparentProxy::Callback {
public:
explicit Callback(td::Promise<td::BufferedFd<td::SocketFd>> promise) : promise_(std::move(promise)) {
}
void set_result(td::Result<td::BufferedFd<td::SocketFd>> result) final {
promise_.set_result(std::move(result));
}
void on_connected() final {
}
private:
td::Promise<td::BufferedFd<td::SocketFd>> promise_;
};
td::IPAddress socks5_ip;
socks5_ip.init_ipv4_port("131.191.89.104", 43077).ensure();
td::IPAddress mtproto_ip_address = get_default_ip_address();
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(td::Result<td::BufferedFd<td::SocketFd>> res, bool dummy) {
res.ensure();
td::Scheduler::instance()->finish();
}
};
TEST(Mtproto, socks5) {
return;
int threads_n = 0;
td::ConcurrentScheduler sched(threads_n, 0);
sched.create_actor_unsafe<Socks5TestActor>(0, "Socks5TestActor").release();
sched.start();
while (sched.run_main(10)) {
// empty;
}
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::on_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::on_handshake, std::move(handshake), 1);
}))
.release();
}
void on_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 on_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 on_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->reset_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::on_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)));
}

632
td/test/online.cpp Normal file
View File

@@ -0,0 +1,632 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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 "td/utils/tests.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_;
void start_up() override {
send_query(td::make_tl_object<td::td_api::getOption>("version"),
[](td::Result<td::td_api::object_ptr<td::td_api::OptionValue>> res) {
LOG(INFO) << td::td_api::to_string(res.ok());
});
}
void process_authorization_state(td::tl_object_ptr<td::td_api::Object> authorization_state) {
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";
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](td::Result<typename T::element_type::ReturnType> res) {
if (is_alive()) {
res.ensure();
}
});
}
void process_update(std::shared_ptr<TestClient::Update> update) override {
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](td::Result<td::td_api::object_ptr<td::td_api::user>> 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](td::Result<td::td_api::object_ptr<td::td_api::chat>> 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) << "Receive 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, nullptr, 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](td::Result<td::td_api::object_ptr<td::td_api::message>> 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](td::Result<td::td_api::object_ptr<td::td_api::file>> 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;
}
rand_shuffle(as_mutable_span(ranges_), rnd);
start_chunk();
}
void on_get_chunk(const td_api::file &file) {
LOG(ERROR) << "Receive chunk";
auto range = ranges_.back();
std::string received_chunk(range.end - range.begin, '\0');
FileFd::open(file.local_->path_, FileFd::Flags::Read).move_as_ok().pread(received_chunk, range.begin).ensure();
CHECK(received_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](td::Result<td::td_api::object_ptr<td::td_api::file>> res) { on_get_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);
}

58
td/test/poll.cpp Normal file
View File

@@ -0,0 +1,58 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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});
}

103
td/test/query_merger.cpp Normal file
View File

@@ -0,0 +1,103 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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/QueryMerger.h"
#include "td/actor/actor.h"
#include "td/actor/ConcurrentScheduler.h"
#include "td/actor/SleepActor.h"
#include "td/utils/common.h"
#include "td/utils/FlatHashSet.h"
#include "td/utils/logging.h"
#include "td/utils/Promise.h"
#include "td/utils/Random.h"
#include "td/utils/Status.h"
#include "td/utils/tests.h"
#include <queue>
class TestQueryMerger final : public td::Actor {
void start_up() final {
query_merger_.set_merge_function([this](td::vector<td::int64> query_ids, td::Promise<td::Unit> &&promise) {
ASSERT_TRUE(!query_ids.empty());
ASSERT_EQ(query_ids.size(), td::min(next_query_ids_.size(), MAX_MERGED_QUERY_COUNT));
for (auto query_id : query_ids) {
auto next_query_id = next_query_ids_.front();
next_query_ids_.pop();
ASSERT_EQ(query_id, next_query_id);
}
current_query_count_++;
ASSERT_TRUE(current_query_count_ <= MAX_CONCURRENT_QUERY_COUNT);
if (!next_query_ids_.empty()) {
ASSERT_EQ(current_query_count_, MAX_CONCURRENT_QUERY_COUNT);
}
td::create_actor<td::SleepActor>("CompleteMergeQuery", 0.02,
td::PromiseCreator::lambda([this, query_ids, promise = std::move(promise)](
td::Result<td::Unit> result) mutable {
for (auto query_id : query_ids) {
LOG(INFO) << "Complete " << query_id;
bool is_erased = pending_query_ids_.erase(query_id) > 0;
ASSERT_TRUE(is_erased);
}
current_query_count_--;
promise.set_result(std::move(result));
}))
.release();
yield();
});
loop();
}
void loop() final {
std::size_t query_count = 0;
std::size_t added_queries = td::Random::fast(1, 3);
while (query_count++ < added_queries && total_query_count_++ < MAX_QUERY_COUNT) {
td::int64 query_id = td::Random::fast(1, 20);
if (pending_query_ids_.insert(query_id).second) {
next_query_ids_.push(query_id);
}
query_merger_.add_query(query_id, td::PromiseCreator::lambda([this](td::Result<td::Unit> result) mutable {
completed_query_count_++;
if (completed_query_count_ == MAX_QUERY_COUNT) {
ASSERT_EQ(current_query_count_, 0u);
ASSERT_TRUE(next_query_ids_.empty());
ASSERT_TRUE(pending_query_ids_.empty());
td::Scheduler::instance()->finish();
} else {
yield();
}
}),
"TestQueryMerger::loop");
}
}
static constexpr std::size_t MAX_CONCURRENT_QUERY_COUNT = 5;
static constexpr std::size_t MAX_MERGED_QUERY_COUNT = 3;
static constexpr std::size_t MAX_QUERY_COUNT = 1000;
td::QueryMerger query_merger_{"QueryMerger", MAX_CONCURRENT_QUERY_COUNT, MAX_MERGED_QUERY_COUNT};
std::size_t current_query_count_ = 0;
std::size_t total_query_count_ = 0;
std::size_t completed_query_count_ = 0;
std::queue<td::int64> next_query_ids_;
td::FlatHashSet<td::int64> pending_query_ids_;
};
constexpr std::size_t TestQueryMerger::MAX_CONCURRENT_QUERY_COUNT;
constexpr std::size_t TestQueryMerger::MAX_MERGED_QUERY_COUNT;
constexpr std::size_t TestQueryMerger::MAX_QUERY_COUNT;
TEST(QueryMerger, stress) {
td::ConcurrentScheduler sched(0, 0);
sched.create_actor_unsafe<TestQueryMerger>(0, "TestQueryMerger").release();
sched.start();
while (sched.run_main(10)) {
// empty
}
sched.finish();
}

1025
td/test/secret.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,74 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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/common.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);
td::unlink(value_path).ignore();
td::unlink(encrypted_path).ignore();
td::unlink(decrypted_path).ignore();
}
}

View File

@@ -0,0 +1,263 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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>();
}

119
td/test/string_cleaning.cpp Normal file
View File

@@ -0,0 +1,119 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/telegram/misc.h"
#include "td/utils/common.h"
#include "td/utils/Slice.h"
#include "td/utils/tests.h"
TEST(StringCleaning, clean_name) {
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", 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(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);
}
}
TEST(StringCleaning, clean_input_string) {
check_clean_input_string("/abc", "/abc", 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);
check_clean_input_string("\xe0\xaf", "", false);
check_clean_input_string("\xf0\xa6", "", false);
check_clean_input_string("\xf0\xa6\x88", "", false);
check_clean_input_string("\xf4\x8f\xbf\xbf", "\xf4\x8f\xbf\xbf", true);
check_clean_input_string("\xf4\x8f\xbf\xc0", "", false);
check_clean_input_string("\r\r\r\r\r\r\r", "", true);
check_clean_input_string("\r\n\r\n\r\n\r\n\r\n\r\n\r", "\n\n\n\n\n\n", true);
check_clean_input_string(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(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) {
check_strip_empty_characters("/abc", 4, "/abc");
check_strip_empty_characters("/abc", 3, "/ab");
check_strip_empty_characters("/abc", 0, "");
check_strip_empty_characters("/abc", 10000000, "/abc");
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, "");
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(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,
"\xe2\x80\xa7\xe2\x80\xa8\xe2\x80\xa9");
check_strip_empty_characters(
"\xF3\x9F\xBF\xBF\xF3\xA0\x80\x80\xF3\xA0\x80\x81\xF3\xA0\x80\xBF\xF3\xA0\x81\x80\xF3\xA0\x81\x81\xF3\xA0\x81\xBF"
"\xF3\xA0\x82\x80",
9, "\xF3\x9F\xBF\xBF \xF3\xA0\x82\x80");
}

1192
td/test/tdclient.cpp Normal file

File diff suppressed because it is too large Load Diff

260
td/test/tqueue.cpp Normal file
View File

@@ -0,0 +1,260 @@
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024
//
// 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()).ignore();
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));
}
TestTQueue(const TestTQueue &) = delete;
TestTQueue &operator=(const TestTQueue &) = delete;
TestTQueue(TestTQueue &&) = delete;
TestTQueue &operator=(TestTQueue &&) = delete;
~TestTQueue() {
td::Binlog::destroy(binlog_path()).ensure();
}
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 < 100000; 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);
auto deleted_events = 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);
CHECK(deleted_events.size() == 100000 - keep_count);
}