initial
This commit is contained in:
74
td/test/CMakeLists.txt
Normal file
74
td/test/CMakeLists.txt
Normal 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
102
td/test/country_info.cpp
Normal 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
279
td/test/crypto.cpp
Normal 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
503
td/test/data.cpp
Normal 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/"
|
||||
"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"
|
||||
"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
24
td/test/data.h
Normal 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
809
td/test/db.cpp
Normal 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
33
td/test/fuzz_url.cpp
Normal 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
565
td/test/http.cpp
Normal 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
1381
td/test/link.cpp
Normal file
File diff suppressed because it is too large
Load Diff
66
td/test/main.cpp
Normal file
66
td/test/main.cpp
Normal 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
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
742
td/test/mtproto.cpp
Normal 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
632
td/test/online.cpp
Normal 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
58
td/test/poll.cpp
Normal 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
103
td/test/query_merger.cpp
Normal 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
1025
td/test/secret.cpp
Normal file
File diff suppressed because it is too large
Load Diff
74
td/test/secure_storage.cpp
Normal file
74
td/test/secure_storage.cpp
Normal 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();
|
||||
}
|
||||
}
|
||||
263
td/test/set_with_position.cpp
Normal file
263
td/test/set_with_position.cpp
Normal 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
119
td/test/string_cleaning.cpp
Normal 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
1192
td/test/tdclient.cpp
Normal file
File diff suppressed because it is too large
Load Diff
260
td/test/tqueue.cpp
Normal file
260
td/test/tqueue.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user