diff options
Diffstat (limited to 'protocols/Telegram/tdlib/td/tdutils')
283 files changed, 27488 insertions, 5818 deletions
diff --git a/protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt b/protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt index 1fbc34df32..7c568fdeba 100644 --- a/protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt +++ b/protocols/Telegram/tdlib/td/tdutils/CMakeLists.txt @@ -1,4 +1,12 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2")) + message(FATAL_ERROR "CMake >= 3.0.2 is required") +endif() + +option(TDUTILS_MIME_TYPE "Generate mime types conversion; requires gperf" ON) + +if (NOT DEFINED CMAKE_INSTALL_LIBDIR) + set(CMAKE_INSTALL_LIBDIR "lib") +endif() if (NOT ZLIB_FOUND) find_package(ZLIB) @@ -16,6 +24,17 @@ if (ZLIB_FOUND) endif() endif() +if (CRC32C_FOUND) + set(TD_HAVE_CRC32C 1) +endif() + +if (TD_WITH_ABSEIL) + find_package(ABSL QUIET) + if (ABSL_FOUND) + set(TD_HAVE_ABSL 1) + endif() +endif() + configure_file(td/utils/config.h.in td/utils/config.h @ONLY) add_subdirectory(generate) @@ -33,37 +52,55 @@ endif() set(TDUTILS_SOURCE td/utils/port/Clocks.cpp - td/utils/port/Fd.cpp td/utils/port/FileFd.cpp td/utils/port/IPAddress.cpp + td/utils/port/MemoryMapping.cpp td/utils/port/path.cpp + td/utils/port/platform.cpp + td/utils/port/PollFlags.cpp + td/utils/port/rlimit.cpp td/utils/port/ServerSocketFd.cpp td/utils/port/signals.cpp td/utils/port/sleep.cpp td/utils/port/SocketFd.cpp + td/utils/port/stacktrace.cpp td/utils/port/Stat.cpp + td/utils/port/StdStreams.cpp td/utils/port/thread_local.cpp + td/utils/port/UdpSocketFd.cpp + td/utils/port/uname.cpp + td/utils/port/user.cpp td/utils/port/wstring_convert.cpp td/utils/port/detail/Epoll.cpp td/utils/port/detail/EventFdBsd.cpp td/utils/port/detail/EventFdLinux.cpp td/utils/port/detail/EventFdWindows.cpp + td/utils/port/detail/Iocp.cpp td/utils/port/detail/KQueue.cpp + td/utils/port/detail/NativeFd.cpp td/utils/port/detail/Poll.cpp td/utils/port/detail/Select.cpp td/utils/port/detail/ThreadIdGuard.cpp + td/utils/port/detail/ThreadPthread.cpp td/utils/port/detail/WineventPoll.cpp ${TDMIME_AUTO} + td/utils/AsyncFileLog.cpp td/utils/base64.cpp td/utils/BigNum.cpp td/utils/buffer.cpp + td/utils/BufferedUdp.cpp + td/utils/check.cpp td/utils/crypto.cpp + td/utils/emoji.cpp + td/utils/ExitGuard.cpp td/utils/FileLog.cpp td/utils/filesystem.cpp td/utils/find_boundary.cpp + td/utils/FlatHashTable.cpp + td/utils/FloodControlGlobal.cpp td/utils/Gzip.cpp td/utils/GzipByteFlow.cpp td/utils/Hints.cpp @@ -71,14 +108,24 @@ set(TDUTILS_SOURCE td/utils/JsonBuilder.cpp td/utils/logging.cpp td/utils/misc.cpp - td/utils/MimeType.cpp + td/utils/MpmcQueue.cpp + td/utils/OptionParser.cpp + td/utils/PathView.cpp td/utils/Random.cpp + td/utils/SharedSlice.cpp + td/utils/Slice.cpp td/utils/StackAllocator.cpp td/utils/Status.cpp td/utils/StringBuilder.cpp + td/utils/tests.cpp td/utils/Time.cpp td/utils/Timer.cpp + td/utils/TsFileLog.cpp td/utils/tl_parsers.cpp + td/utils/translit.cpp + td/utils/TsCerr.cpp + td/utils/TsFileLog.cpp + td/utils/TsLog.cpp td/utils/unicode.cpp td/utils/utf8.cpp @@ -87,57 +134,98 @@ set(TDUTILS_SOURCE td/utils/port/CxCli.h td/utils/port/EventFd.h td/utils/port/EventFdBase.h - td/utils/port/Fd.h td/utils/port/FileFd.h + td/utils/port/FromApp.h td/utils/port/IPAddress.h + td/utils/port/IoSlice.h + td/utils/port/MemoryMapping.h + td/utils/port/Mutex.h td/utils/port/path.h td/utils/port/platform.h td/utils/port/Poll.h td/utils/port/PollBase.h + td/utils/port/PollFlags.h + td/utils/port/rlimit.h td/utils/port/RwMutex.h td/utils/port/ServerSocketFd.h td/utils/port/signals.h td/utils/port/sleep.h td/utils/port/SocketFd.h + td/utils/port/stacktrace.h td/utils/port/Stat.h + td/utils/port/StdStreams.h td/utils/port/thread.h td/utils/port/thread_local.h + td/utils/port/UdpSocketFd.h + td/utils/port/uname.h + td/utils/port/user.h td/utils/port/wstring_convert.h td/utils/port/detail/Epoll.h td/utils/port/detail/EventFdBsd.h td/utils/port/detail/EventFdLinux.h td/utils/port/detail/EventFdWindows.h + td/utils/port/detail/Iocp.h td/utils/port/detail/KQueue.h + td/utils/port/detail/NativeFd.h td/utils/port/detail/Poll.h + td/utils/port/detail/PollableFd.h td/utils/port/detail/Select.h + td/utils/port/detail/skip_eintr.h td/utils/port/detail/ThreadIdGuard.h td/utils/port/detail/ThreadPthread.h td/utils/port/detail/ThreadStl.h td/utils/port/detail/WineventPoll.h td/utils/AesCtrByteFlow.h + td/utils/algorithm.h + td/utils/as.h + td/utils/AsyncFileLog.h + td/utils/AtomicRead.h td/utils/base64.h td/utils/benchmark.h td/utils/BigNum.h + td/utils/bits.h td/utils/buffer.h td/utils/BufferedFd.h td/utils/BufferedReader.h + td/utils/BufferedUdp.h td/utils/ByteFlow.h + td/utils/CancellationToken.h + td/utils/ChainScheduler.h td/utils/ChangesProcessor.h + td/utils/check.h td/utils/Closure.h + td/utils/CombinedLog.h td/utils/common.h + td/utils/ConcurrentHashTable.h td/utils/Container.h + td/utils/Context.h td/utils/crypto.h + td/utils/DecTree.h + td/utils/Destructor.h + td/utils/emoji.h td/utils/Enumerator.h + td/utils/EpochBasedMemoryReclamation.h + td/utils/ExitGuard.h td/utils/FileLog.h td/utils/filesystem.h + td/utils/fixed_vector.h td/utils/find_boundary.h + td/utils/FlatHashMap.h + td/utils/FlatHashMapChunks.h + td/utils/FlatHashSet.h + td/utils/FlatHashTable.h td/utils/FloodControlFast.h + td/utils/FloodControlGlobal.h td/utils/FloodControlStrict.h td/utils/format.h td/utils/Gzip.h td/utils/GzipByteFlow.h + td/utils/Hash.h + td/utils/HashMap.h + td/utils/HashSet.h + td/utils/HashTableUtils.h td/utils/HazardPointers.h td/utils/Heap.h td/utils/Hints.h @@ -147,8 +235,8 @@ set(TDUTILS_SOURCE td/utils/JsonBuilder.h td/utils/List.h td/utils/logging.h + td/utils/MapNode.h td/utils/MemoryLog.h - td/utils/MimeType.h td/utils/misc.h td/utils/MovableValue.h td/utils/MpmcQueue.h @@ -156,68 +244,110 @@ set(TDUTILS_SOURCE td/utils/MpscPollableQueue.h td/utils/MpscLinkQueue.h td/utils/Named.h + td/utils/NullLog.h td/utils/ObjectPool.h td/utils/Observer.h td/utils/optional.h - td/utils/OptionsParser.h + td/utils/OptionParser.h td/utils/OrderedEventsProcessor.h td/utils/overloaded.h td/utils/Parser.h td/utils/PathView.h + td/utils/Promise.h td/utils/queue.h td/utils/Random.h td/utils/ScopeGuard.h + td/utils/SetNode.h td/utils/SharedObjectPool.h + td/utils/SharedSlice.h td/utils/Slice-decl.h td/utils/Slice.h + td/utils/SliceBuilder.h + td/utils/Span.h td/utils/SpinLock.h td/utils/StackAllocator.h td/utils/Status.h + td/utils/StealingQueue.h td/utils/Storer.h td/utils/StorerBase.h td/utils/StringBuilder.h td/utils/tests.h + td/utils/ThreadLocalStorage.h + td/utils/ThreadSafeCounter.h td/utils/Time.h td/utils/TimedStat.h td/utils/Timer.h td/utils/tl_helpers.h td/utils/tl_parsers.h td/utils/tl_storers.h + td/utils/TlDowncastHelper.h + td/utils/TlStorerToString.h + td/utils/translit.h + td/utils/TsCerr.h + td/utils/TsFileLog.h + td/utils/TsList.h + td/utils/TsLog.h td/utils/type_traits.h + td/utils/UInt.h + td/utils/uint128.h td/utils/unicode.h + td/utils/unique_ptr.h td/utils/utf8.h td/utils/Variant.h + td/utils/VectorQueue.h + td/utils/WaitFreeHashMap.h + td/utils/WaitFreeHashSet.h + td/utils/WaitFreeVector.h ) +if (TDUTILS_MIME_TYPE) + set(TDUTILS_SOURCE + ${TDUTILS_SOURCE} + td/utils/MimeType.cpp + td/utils/MimeType.h + ) +endif() + set(TDUTILS_TEST_SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/test/bitmask.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/buffer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/ChainScheduler.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/ConcurrentHashMap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/crypto.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/emoji.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/Enumerator.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/EpochBasedMemoryReclamation.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/filesystem.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/gzip.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/HazardPointers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/HashSet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/heap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/HttpUrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/json.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/List.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/log.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/misc.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/MpmcQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/MpmcWaiter.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/MpscLinkQueue.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/OptionParser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/OrderedEventsProcessor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/port.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/pq.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/SharedObjectPool.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/SharedSlice.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/StealingQueue.cpp ${CMAKE_CURRENT_SOURCE_DIR}/test/variant.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/WaitFreeHashMap.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/WaitFreeHashSet.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/test/WaitFreeVector.cpp PARENT_SCOPE ) -#RULES #LIBRARIES add_library(tdutils STATIC ${TDUTILS_SOURCE}) -if (WIN32) - # find_library(WS2_32_LIBRARY ws2_32) - # find_library(MSWSOCK_LIBRARY Mswsock) - # target_link_libraries(tdutils PRIVATE ${WS2_32_LIBRARY} ${MSWSOCK_LIBRARY}) - target_link_libraries(tdutils PRIVATE ws2_32 Mswsock) -endif() -if (NOT CMAKE_CROSSCOMPILING) + +if (NOT CMAKE_CROSSCOMPILING AND TDUTILS_MIME_TYPE) add_dependencies(tdutils tdmime_auto) endif() @@ -229,6 +359,14 @@ target_include_directories(tdutils PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOUR if (OPENSSL_FOUND) target_link_libraries(tdutils PRIVATE ${OPENSSL_CRYPTO_LIBRARY} ${CMAKE_DL_LIBS} ${ZLIB_LIBRARIES}) target_include_directories(tdutils SYSTEM PRIVATE ${OPENSSL_INCLUDE_DIR}) + + if (WIN32) + if (MINGW) + target_link_libraries(tdutils PRIVATE ws2_32 mswsock crypt32) + else() + target_link_libraries(tdutils PRIVATE ws2_32 Mswsock Crypt32) + endif() + endif() endif() if (ZLIB_FOUND) @@ -236,9 +374,49 @@ if (ZLIB_FOUND) target_include_directories(tdutils SYSTEM PRIVATE ${ZLIB_INCLUDE_DIR}) endif() +if (CRC32C_FOUND) + target_link_libraries(tdutils PRIVATE crc32c) +endif() +if (ABSL_FOUND) + target_link_libraries(tdutils PUBLIC absl::flat_hash_map absl::flat_hash_set absl::hash) +endif() + +if (WIN32) + # find_library for system libraries doesn't work for UWP builds + # find_library(WS2_32_LIBRARY ws2_32) + # find_library(MSWSOCK_LIBRARY Mswsock) + # target_link_libraries(tdutils PRIVATE ${WS2_32_LIBRARY} ${MSWSOCK_LIBRARY}) + if (MINGW) + target_link_libraries(tdutils PRIVATE ws2_32 mswsock normaliz psapi) + else() + target_link_libraries(tdutils PRIVATE ws2_32 Mswsock Normaliz psapi) + endif() + if (NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") + target_link_libraries(tdutils PRIVATE shell32) + endif() +endif() + +if (ANDROID) + target_link_libraries(tdutils PRIVATE log) +endif() + +if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + target_link_directories(tdutils PUBLIC /usr/pkg/gcc12/x86_64--netbsd/lib /usr/pkg/gcc12/i486--netbsdelf/lib) + target_link_libraries(tdutils PUBLIC atomic) +endif() + install(TARGETS tdutils EXPORT TdTargets - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib - RUNTIME DESTINATION bin - INCLUDES DESTINATION include + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" ) + +if (TD_TEST_FOLLY AND ABSL_FOUND) + find_package(benchmark QUIET) + find_package(folly QUIET) + find_package(gflags QUIET) + + if (benchmark_FOUND AND folly_FOUND) + add_executable(benchmark-hashset test/hashset_benchmark.cpp) + target_link_libraries(benchmark-hashset PRIVATE tdutils benchmark::benchmark Folly::folly absl::flat_hash_map absl::hash) + endif() +endif() diff --git a/protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt b/protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt index 69697b04b8..69b42d9144 100644 --- a/protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt +++ b/protocols/Telegram/tdlib/td/tdutils/generate/CMakeLists.txt @@ -1,7 +1,13 @@ -cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) +if ((CMAKE_MAJOR_VERSION LESS 3) OR (CMAKE_VERSION VERSION_LESS "3.0.2")) + message(FATAL_ERROR "CMake >= 3.0.2 is required") +endif() # Generates files for MIME type <-> extension conversions -# DEPENDS ON: gperf grep bash/powershell +# DEPENDS ON: gperf grep + +if (NOT TDUTILS_MIME_TYPE) + return() +endif() file(MAKE_DIRECTORY auto) @@ -19,7 +25,7 @@ add_custom_target(tdmime_auto DEPENDS ${TDMIME_SOURCE}) if (NOT CMAKE_CROSSCOMPILING) find_program(GPERF_EXECUTABLE gperf) if (NOT GPERF_EXECUTABLE) - message(FATAL_ERROR "Could NOT find gperf. Path to gperf needs to be specified manually, i.e. 'cmake -DGPERF_EXECUTABLE:FILEPATH=\"<path to gperf executable>\" .')") + message(FATAL_ERROR "Could NOT find gperf. Add path to gperf executable to PATH environment variable or specify it manually using GPERF_EXECUTABLE option, i.e. 'cmake -DGPERF_EXECUTABLE:FILEPATH=\"<path to gperf executable>\"'.") endif() set(GPERF_FILES @@ -38,7 +44,7 @@ if (NOT CMAKE_CROSSCOMPILING) DEPENDS generate_mime_types_gperf mime_types.txt ) - if (WIN32) + if (CMAKE_HOST_WIN32) set(MIME_TYPE_TO_EXTENSION_CMD ${GPERF_EXECUTABLE} -m100 --output-file=auto/mime_type_to_extension.cpp auto/mime_type_to_extension.gperf) else() set(MIME_TYPE_TO_EXTENSION_CMD ${GPERF_EXECUTABLE} -m100 auto/mime_type_to_extension.gperf | grep -v __gnu_inline__ > auto/mime_type_to_extension.cpp) @@ -47,10 +53,10 @@ if (NOT CMAKE_CROSSCOMPILING) OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/auto/mime_type_to_extension.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${MIME_TYPE_TO_EXTENSION_CMD} - DEPENDS auto/mime_type_to_extension.gperf + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/auto/mime_type_to_extension.gperf ) - if (WIN32) + if (CMAKE_HOST_WIN32) set(EXTENSION_TO_MIME_TYPE_CMD ${GPERF_EXECUTABLE} -m100 --output-file=auto/extension_to_mime_type.cpp auto/extension_to_mime_type.gperf) else() set(EXTENSION_TO_MIME_TYPE_CMD ${GPERF_EXECUTABLE} -m100 auto/extension_to_mime_type.gperf | grep -v __gnu_inline__ > auto/extension_to_mime_type.cpp) @@ -59,6 +65,6 @@ if (NOT CMAKE_CROSSCOMPILING) OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/auto/extension_to_mime_type.cpp WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${EXTENSION_TO_MIME_TYPE_CMD} - DEPENDS auto/extension_to_mime_type.gperf + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/auto/extension_to_mime_type.gperf ) endif() diff --git a/protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp b/protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp index ac7ff68605..44ab2bd1e9 100644 --- a/protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/generate/generate_mime_types_gperf.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,7 +15,7 @@ #include <utility> #include <vector> -std::pair<std::string, std::string> split(std::string s, char delimiter = ' ') { +static std::pair<std::string, std::string> split(std::string s, char delimiter = ' ') { auto delimiter_pos = s.find(delimiter); if (delimiter_pos == std::string::npos) { return {std::move(s), ""}; @@ -26,8 +26,8 @@ std::pair<std::string, std::string> split(std::string s, char delimiter = ' ') { } } -bool generate(const char *file_name, const char *from_name, const char *to_name, - const std::map<std::string, std::string> &map) { +static bool generate(const char *file_name, const char *from_name, const char *to_name, + const std::map<std::string, std::string> &map) { // binary mode is needed for MSYS2 gperf std::ofstream out(file_name, std::ios_base::trunc | std::ios_base::binary); if (!out) { @@ -71,6 +71,10 @@ bool generate(const char *file_name, const char *from_name, const char *to_name, return true; } +static bool is_private_mime_type(const std::string &mime_type) { + return mime_type.find("/x-") != std::string::npos; +} + int main(int argc, char *argv[]) { if (argc != 4) { std::cerr << "Wrong number of arguments supplied. Expected 'generate_mime_types_gperf <mime_types.txt> " @@ -112,7 +116,7 @@ int main(int argc, char *argv[]) { std::vector<std::string> extensions; while (!extensions_string.empty()) { - extensions.push_back(""); + extensions.emplace_back(); std::tie(extensions.back(), extensions_string) = split(extensions_string); } assert(!extensions.empty()); @@ -132,7 +136,13 @@ int main(int argc, char *argv[]) { for (auto &extension : extensions) { if (!extension_to_mime_type.emplace(extension, mime_type).second) { - std::cerr << "Extension \"" << extension << "\" matches more than one type" << std::endl; + if (is_private_mime_type(extension_to_mime_type[extension]) == is_private_mime_type(mime_type)) { + std::cerr << "Extension \"" << extension << "\" matches more than one type" << std::endl; + } else { + if (!is_private_mime_type(mime_type)) { + extension_to_mime_type[extension] = mime_type; + } + } } } } diff --git a/protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt b/protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt index a2d4abbf29..18dcfb775b 100644 --- a/protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt +++ b/protocols/Telegram/tdlib/td/tdutils/generate/mime_types.txt @@ -14,7 +14,7 @@ application/davmount+xml davmount application/docbook+xml dbk application/dssc+der dssc application/dssc+xml xdssc -application/ecmascript ecma +application/ecmascript es application/emma+xml emma application/epub+zip epub application/exi exi @@ -498,6 +498,7 @@ application/x-dtbresource+xml res application/x-dvi dvi application/x-envoy evy application/x-eva eva +application/x-fictionbook+xml fb2 application/x-font-bdf bdf application/x-font-ghostscript gsf application/x-font-linux-psf psf @@ -506,7 +507,7 @@ application/x-font-pcf pcf application/x-font-snf snf application/x-font-ttf ttf ttc application/x-font-type1 pfa pfb pfm afm -application/font-woff woff +application/x-font-woff woff application/x-freearc arc application/x-futuresplash spl application/x-gca-compressed gca @@ -564,6 +565,8 @@ application/x-tex tex application/x-tex-tfm tfm application/x-texinfo texinfo texi application/x-tgif obj +application/x-tgsticker tgs +application/x-tgwallpattern tgv application/x-ustar ustar application/x-wais-source src application/x-x509-ca-cert der crt @@ -589,9 +592,9 @@ application/zip zip audio/adpcm adp audio/basic au snd audio/midi mid midi kar rmi -audio/mp4 mp4a +audio/mp4 m4a mp4a audio/mpeg mpga mp2 mp2a mp3 m2a m3a -audio/ogg oga ogg spx +audio/ogg oga ogg opus spx audio/s3m s3m audio/silk sil audio/vnd.dece.audio uva uvva @@ -624,10 +627,19 @@ chemical/x-cmdf cmdf chemical/x-cml cml chemical/x-csml csml chemical/x-xyz xyz +font/collection ttc +font/otf otf +font/ttf ttf +font/woff woff +font/woff2 woff2 image/bmp bmp image/cgm cgm image/g3fax g3 image/gif gif +image/heic heic +image/heic-sequence heics +image/heif heif +image/heif-sequence heifs image/ief ief image/jpeg jpeg jpg jpe image/ktx ktx @@ -638,8 +650,8 @@ image/svg+xml svg svgz image/tiff tiff tif image/vnd.adobe.photoshop psd image/vnd.dece.graphic uvi uvvi uvg uvvg -image/vnd.dvb.subtitle sub image/vnd.djvu djvu djv +image/vnd.dvb.subtitle sub image/vnd.dwg dwg image/vnd.dxf dxf image/vnd.fastbidsheet fbs @@ -700,8 +712,8 @@ text/uri-list uri uris urls text/vcard vcard text/vnd.curl curl text/vnd.curl.dcurl dcurl -text/vnd.curl.scurl scurl text/vnd.curl.mcurl mcurl +text/vnd.curl.scurl scurl text/vnd.dvb.subtitle sub text/vnd.fly fly text/vnd.fmi.flexstor flx @@ -715,9 +727,10 @@ text/x-asm s asm text/x-c c cc cxx cpp h hh dic text/x-fortran f for f77 f90 text/x-java-source java +text/x-nfo nfo text/x-opml opml text/x-pascal p pas -text/x-nfo nfo +text/x-php php text/x-setext etx text/x-sfv sfv text/x-uuencode uu @@ -728,6 +741,7 @@ video/3gpp2 3g2 video/h261 h261 video/h263 h263 video/h264 h264 +video/h265 h265 video/jpeg jpgv video/jpm jpm jpgm video/mj2 mj2 mjp2 diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h index 820aa02fbe..5d9057f215 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/AesCtrByteFlow.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,14 +11,15 @@ #include "td/utils/crypto.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" +#include "td/utils/UInt.h" namespace td { #if TD_HAVE_OPENSSL -class AesCtrByteFlow : public ByteFlowInplaceBase { +class AesCtrByteFlow final : public ByteFlowInplaceBase { public: void init(const UInt256 &key, const UInt128 &iv) { - state_.init(key, iv); + state_.init(as_slice(key), as_slice(iv)); } void init(AesCtrState &&state) { state_ = std::move(state); @@ -26,25 +27,20 @@ class AesCtrByteFlow : public ByteFlowInplaceBase { AesCtrState move_aes_ctr_state() { return std::move(state_); } - void loop() override { - bool was_updated = false; - while (true) { - auto ready = input_->prepare_read(); - if (ready.empty()) { - break; - } + bool loop() final { + bool result = false; + auto ready = input_->prepare_read(); + if (!ready.empty()) { state_.encrypt(ready, MutableSlice(const_cast<char *>(ready.data()), ready.size())); input_->confirm_read(ready.size()); output_.advance_end(ready.size()); - was_updated = true; - } - if (was_updated) { - on_output_updated(); + result = true; } + if (!is_input_active_) { finish(Status::OK()); // End of input stream. } - set_need_size(1); + return result; } private: diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.cpp new file mode 100644 index 0000000000..575333745d --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.cpp @@ -0,0 +1,160 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/AsyncFileLog.h" + +#include "td/utils/port/FileFd.h" +#include "td/utils/port/path.h" +#include "td/utils/port/sleep.h" +#include "td/utils/port/StdStreams.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/Time.h" + +namespace td { + +#if !TD_THREAD_UNSUPPORTED + +Status AsyncFileLog::init(string path, int64 rotate_threshold, bool redirect_stderr) { + CHECK(path_.empty()); + CHECK(!path.empty()); + + TRY_RESULT(fd, FileFd::open(path, FileFd::Create | FileFd::Write | FileFd::Append)); + if (!Stderr().empty() && redirect_stderr) { + fd.get_native_fd().duplicate(Stderr().get_native_fd()).ignore(); + } + + auto r_path = realpath(path, true); + if (r_path.is_error()) { + path_ = std::move(path); + } else { + path_ = r_path.move_as_ok(); + } + TRY_RESULT(size, fd.get_size()); + + queue_ = td::make_unique<MpscPollableQueue<Query>>(); + queue_->init(); + + logging_thread_ = td::thread( + [queue = queue_.get(), fd = std::move(fd), path = path_, size, rotate_threshold, redirect_stderr]() mutable { + auto after_rotation = [&] { + fd.close(); + auto r_fd = FileFd::open(path, FileFd::Create | FileFd::Write | FileFd::Append); + if (r_fd.is_error()) { + process_fatal_error(PSLICE() << r_fd.error() << " in " << __FILE__ << " at " << __LINE__ << '\n'); + } + fd = r_fd.move_as_ok(); + if (!Stderr().empty() && redirect_stderr) { + fd.get_native_fd().duplicate(Stderr().get_native_fd()).ignore(); + } + size = 0; + }; + auto append = [&](CSlice slice) { + if (size > rotate_threshold) { + auto status = rename(path, PSLICE() << path << ".old"); + if (status.is_error()) { + process_fatal_error(PSLICE() << status << " in " << __FILE__ << " at " << __LINE__ << '\n'); + } + after_rotation(); + } + while (!slice.empty()) { + if (redirect_stderr) { + while (has_log_guard()) { + // spin + } + } + auto r_size = fd.write(slice); + if (r_size.is_error()) { + process_fatal_error(PSLICE() << r_size.error() << " in " << __FILE__ << " at " << __LINE__ << '\n'); + } + auto written = r_size.ok(); + size += static_cast<int64>(written); + slice.remove_prefix(written); + } + }; + + while (true) { + int ready_count = queue->reader_wait_nonblock(); + if (ready_count == 0) { + queue->reader_get_event_fd().wait(1000); + continue; + } + bool need_close = false; + while (ready_count-- > 0) { + Query query = queue->reader_get_unsafe(); + switch (query.type_) { + case Query::Type::Log: + append(query.data_); + break; + case Query::Type::AfterRotation: + after_rotation(); + break; + case Query::Type::Close: + need_close = true; + break; + default: + process_fatal_error("Invalid query type in AsyncFileLog"); + } + } + queue->reader_flush(); + + if (need_close) { + fd.close(); + break; + } + } + }); + + return Status::OK(); +} + +AsyncFileLog::~AsyncFileLog() { + if (queue_ == nullptr) { + return; + } + Query query; + query.type_ = Query::Type::Close; + queue_->writer_put(std::move(query)); + logging_thread_.join(); +} + +vector<string> AsyncFileLog::get_file_paths() { + vector<string> result; + if (!path_.empty()) { + result.push_back(path_); + result.push_back(PSTRING() << path_ << ".old"); + } + return result; +} + +void AsyncFileLog::after_rotation() { + Query query; + query.type_ = Query::Type::AfterRotation; + if (queue_ == nullptr) { + process_fatal_error("AsyncFileLog is not inited"); + } + queue_->writer_put(std::move(query)); +} + +void AsyncFileLog::do_append(int log_level, CSlice slice) { + Query query; + query.data_ = slice.str(); + if (queue_ == nullptr) { + process_fatal_error("AsyncFileLog is not inited"); + } + queue_->writer_put(std::move(query)); + if (log_level == VERBOSITY_NAME(FATAL)) { + // it is not thread-safe to join logging_thread_ there, so just wait for the log line to be printed + auto end_time = Time::now() + 1.0; + while (!queue_->is_empty() && Time::now() < end_time) { + usleep_for(1000); + } + usleep_for(5000); // allow some time for the log line to be actually printed + } +} + +#endif + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.h new file mode 100644 index 0000000000..1b0ab73897 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/AsyncFileLog.h @@ -0,0 +1,51 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/MpscPollableQueue.h" +#include "td/utils/port/thread.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" + +namespace td { + +#if !TD_THREAD_UNSUPPORTED + +class AsyncFileLog final : public LogInterface { + public: + AsyncFileLog() = default; + AsyncFileLog(const AsyncFileLog &) = delete; + AsyncFileLog &operator=(const AsyncFileLog &) = delete; + AsyncFileLog(AsyncFileLog &&) = delete; + AsyncFileLog &operator=(AsyncFileLog &&) = delete; + ~AsyncFileLog(); + + Status init(string path, int64 rotate_threshold, bool redirect_stderr = true); + + private: + struct Query { + enum class Type : int32 { Log, AfterRotation, Close }; + Type type_ = Type::Log; + string data_; + }; + + string path_; + unique_ptr<MpscPollableQueue<Query>> queue_; + thread logging_thread_; + + vector<string> get_file_paths() final; + + void after_rotation() final; + + void do_append(int log_level, CSlice slice) final; +}; + +#endif + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/AtomicRead.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/AtomicRead.h new file mode 100644 index 0000000000..d30e960a8c --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/AtomicRead.h @@ -0,0 +1,89 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/port/sleep.h" +#include "td/utils/type_traits.h" + +#include <atomic> +#include <cstring> +#include <memory> + +namespace td { + +template <class T> +class AtomicRead { + public: + void read(T &dest) const { + uint32 counter = 0; + auto wait = [&] { + counter++; + const int wait_each_count = 4; + if (counter % wait_each_count == 0) { + usleep_for(1); + } + }; + + while (true) { + static_assert(TD_IS_TRIVIALLY_COPYABLE(T), "T must be trivially copyable"); + auto version_before = version.load(); + if (version_before % 2 == 0) { + std::memcpy(&dest, &value, sizeof(dest)); + auto version_after = version.load(); + if (version_before == version_after) { + break; + } + } + wait(); + } + } + + struct Write { + explicit Write(AtomicRead *read) { + read->do_lock(); + ptr.reset(read); + } + struct Destructor { + void operator()(AtomicRead *read) const { + read->do_unlock(); + } + }; + T &operator*() { + return value(); + } + T *operator->() { + return &value(); + } + T &value() { + CHECK(ptr); + return ptr->value; + } + + private: + std::unique_ptr<AtomicRead, Destructor> ptr; + }; + + Write lock() { + return Write(this); + } + + private: + std::atomic<uint64> version{0}; + T value; + + void do_lock() { + bool is_locked = ++version % 2 == 1; + CHECK(is_locked); + } + void do_unlock() { + bool is_unlocked = ++version % 2 == 0; + CHECK(is_unlocked); + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp index f553661d49..e7a93d4399 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -12,10 +12,13 @@ char disable_linker_warning_about_empty_file_bignum_cpp TD_UNUSED; #include "td/utils/logging.h" #include "td/utils/misc.h" +#include "td/utils/SliceBuilder.h" #include <openssl/bn.h> #include <openssl/crypto.h> +#include <algorithm> + namespace td { class BigNumContext::Impl { @@ -37,9 +40,8 @@ class BigNumContext::Impl { BigNumContext::BigNumContext() : impl_(make_unique<Impl>()) { } -BigNumContext::BigNumContext(BigNumContext &&other) = default; -BigNumContext &BigNumContext::operator=(BigNumContext &&other) = default; - +BigNumContext::BigNumContext(BigNumContext &&other) noexcept = default; +BigNumContext &BigNumContext::operator=(BigNumContext &&other) noexcept = default; BigNumContext::~BigNumContext() = default; class BigNum::Impl { @@ -68,6 +70,9 @@ BigNum::BigNum(const BigNum &other) : BigNum() { } BigNum &BigNum::operator=(const BigNum &other) { + if (this == &other) { + return *this; + } CHECK(impl_ != nullptr); CHECK(other.impl_ != nullptr); BIGNUM *result = BN_copy(impl_->big_num, other.impl_->big_num); @@ -75,20 +80,41 @@ BigNum &BigNum::operator=(const BigNum &other) { return *this; } -BigNum::BigNum(BigNum &&other) = default; - -BigNum &BigNum::operator=(BigNum &&other) = default; - +BigNum::BigNum(BigNum &&other) noexcept = default; +BigNum &BigNum::operator=(BigNum &&other) noexcept = default; BigNum::~BigNum() = default; BigNum BigNum::from_binary(Slice str) { return BigNum(make_unique<Impl>(BN_bin2bn(str.ubegin(), narrow_cast<int>(str.size()), nullptr))); } -BigNum BigNum::from_decimal(CSlice str) { +BigNum BigNum::from_le_binary(Slice str) { +#if defined(OPENSSL_IS_BORINGSSL) + return BigNum(make_unique<Impl>(BN_le2bn(str.ubegin(), narrow_cast<int>(str.size()), nullptr))); +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) + return BigNum(make_unique<Impl>(BN_lebin2bn(str.ubegin(), narrow_cast<int>(str.size()), nullptr))); +#else + string str_copy = str.str(); + std::reverse(str_copy.begin(), str_copy.end()); + return from_binary(str_copy); +#endif +} + +Result<BigNum> BigNum::from_decimal(CSlice str) { BigNum result; - int err = BN_dec2bn(&result.impl_->big_num, str.c_str()); - LOG_IF(FATAL, err == 0); + int res = BN_dec2bn(&result.impl_->big_num, str.c_str()); + if (res == 0 || static_cast<size_t>(res) != str.size()) { + return Status::Error(PSLICE() << "Failed to parse \"" << str << "\" as BigNum"); + } + return result; +} + +Result<BigNum> BigNum::from_hex(CSlice str) { + BigNum result; + int res = BN_hex2bn(&result.impl_->big_num, str.c_str()); + if (res == 0 || static_cast<size_t>(res) != str.size()) { + return Status::Error(PSLICE() << "Failed to parse \"" << str << "\" as hexadecimal BigNum"); + } return result; } @@ -99,10 +125,6 @@ BigNum BigNum::from_raw(void *openssl_big_num) { BigNum::BigNum(unique_ptr<Impl> &&impl) : impl_(std::move(impl)) { } -void BigNum::ensure_const_time() { - BN_set_flags(impl_->big_num, BN_FLG_CONSTTIME); -} - int BigNum::get_num_bits() const { return BN_num_bits(impl_->big_num); } @@ -126,7 +148,12 @@ bool BigNum::is_bit_set(int num) const { } bool BigNum::is_prime(BigNumContext &context) const { - int result = BN_is_prime_ex(impl_->big_num, BN_prime_checks, context.impl_->big_num_context, nullptr); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + int result = BN_check_prime(impl_->big_num, context.impl_->big_num_context, nullptr); +#else + int result = + BN_is_prime_ex(impl_->big_num, get_num_bits() > 2048 ? 128 : 64, context.impl_->big_num_context, nullptr); +#endif LOG_IF(FATAL, result == -1); return result == 1; } @@ -180,10 +207,32 @@ string BigNum::to_binary(int exact_size) const { CHECK(exact_size >= num_size); } string res(exact_size, '\0'); - BN_bn2bin(impl_->big_num, reinterpret_cast<unsigned char *>(&res[exact_size - num_size])); + BN_bn2bin(impl_->big_num, MutableSlice(res).ubegin() + (exact_size - num_size)); return res; } +string BigNum::to_le_binary(int exact_size) const { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) || defined(OPENSSL_IS_BORINGSSL) + int num_size = get_num_bytes(); + if (exact_size == -1) { + exact_size = num_size; + } else { + CHECK(exact_size >= num_size); + } + string result(exact_size, '\0'); +#if defined(OPENSSL_IS_BORINGSSL) + BN_bn2le_padded(MutableSlice(result).ubegin(), exact_size, impl_->big_num); +#else + BN_bn2lebinpad(impl_->big_num, MutableSlice(result).ubegin(), exact_size); +#endif + return result; +#else + string result = to_binary(exact_size); + std::reverse(result.begin(), result.end()); + return result; +#endif +} + string BigNum::to_decimal() const { char *result = BN_bn2dec(impl_->big_num); CHECK(result != nullptr); @@ -214,12 +263,29 @@ void BigNum::mul(BigNum &r, BigNum &a, BigNum &b, BigNumContext &context) { LOG_IF(FATAL, result != 1); } +void BigNum::mod_add(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context) { + int result = BN_mod_add(r.impl_->big_num, a.impl_->big_num, b.impl_->big_num, m.impl_->big_num, + context.impl_->big_num_context); + LOG_IF(FATAL, result != 1); +} + +void BigNum::mod_sub(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context) { + int result = BN_mod_sub(r.impl_->big_num, a.impl_->big_num, b.impl_->big_num, m.impl_->big_num, + context.impl_->big_num_context); + LOG_IF(FATAL, result != 1); +} + void BigNum::mod_mul(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context) { int result = BN_mod_mul(r.impl_->big_num, a.impl_->big_num, b.impl_->big_num, m.impl_->big_num, context.impl_->big_num_context); LOG_IF(FATAL, result != 1); } +void BigNum::mod_inverse(BigNum &r, BigNum &a, const BigNum &m, BigNumContext &context) { + auto result = BN_mod_inverse(r.impl_->big_num, a.impl_->big_num, m.impl_->big_num, context.impl_->big_num_context); + LOG_IF(FATAL, result != r.impl_->big_num); +} + void BigNum::div(BigNum *quotient, BigNum *remainder, const BigNum ÷nd, const BigNum &divisor, BigNumContext &context) { auto q = quotient == nullptr ? nullptr : quotient->impl_->big_num; @@ -247,5 +313,9 @@ int BigNum::compare(const BigNum &a, const BigNum &b) { return BN_cmp(a.impl_->big_num, b.impl_->big_num); } +StringBuilder &operator<<(StringBuilder &sb, const BigNum &bn) { + return sb << bn.to_decimal(); +} + } // namespace td #endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h index 6eecdeab03..9b666f4ce0 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BigNum.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,6 +11,8 @@ #if TD_HAVE_OPENSSL #include "td/utils/Slice.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" namespace td { @@ -19,8 +21,8 @@ class BigNumContext { BigNumContext(); BigNumContext(const BigNumContext &other) = delete; BigNumContext &operator=(const BigNumContext &other) = delete; - BigNumContext(BigNumContext &&other); - BigNumContext &operator=(BigNumContext &&other); + BigNumContext(BigNumContext &&other) noexcept; + BigNumContext &operator=(BigNumContext &&other) noexcept; ~BigNumContext(); private: @@ -35,20 +37,22 @@ class BigNum { BigNum(); BigNum(const BigNum &other); BigNum &operator=(const BigNum &other); - BigNum(BigNum &&other); - BigNum &operator=(BigNum &&other); + BigNum(BigNum &&other) noexcept; + BigNum &operator=(BigNum &&other) noexcept; ~BigNum(); static BigNum from_binary(Slice str); - static BigNum from_decimal(CSlice str); + static BigNum from_le_binary(Slice str); + + static Result<BigNum> from_decimal(CSlice str); + + static Result<BigNum> from_hex(CSlice str); static BigNum from_raw(void *openssl_big_num); void set_value(uint32 new_value); - void ensure_const_time(); - int get_num_bits() const; int get_num_bytes() const; @@ -65,6 +69,8 @@ class BigNum { string to_binary(int exact_size = -1) const; + string to_le_binary(int exact_size = -1) const; + string to_decimal() const; void operator+=(uint32 value); @@ -85,8 +91,14 @@ class BigNum { static void mul(BigNum &r, BigNum &a, BigNum &b, BigNumContext &context); + static void mod_add(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context); + + static void mod_sub(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context); + static void mod_mul(BigNum &r, BigNum &a, BigNum &b, const BigNum &m, BigNumContext &context); + static void mod_inverse(BigNum &r, BigNum &a, const BigNum &m, BigNumContext &context); + static void div(BigNum *quotient, BigNum *remainder, const BigNum ÷nd, const BigNum &divisor, BigNumContext &context); @@ -103,6 +115,8 @@ class BigNum { explicit BigNum(unique_ptr<Impl> &&impl); }; +StringBuilder &operator<<(StringBuilder &sb, const BigNum &bn); + } // namespace td #endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h index 0c8f65408d..6bb9b77098 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedFd.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,10 +7,13 @@ #pragma once #include "td/utils/buffer.h" +#include "td/utils/common.h" #include "td/utils/format.h" #include "td/utils/logging.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/IoSlice.h" #include "td/utils/Slice.h" +#include "td/utils/Span.h" #include "td/utils/Status.h" #include <limits> @@ -28,15 +31,16 @@ class BufferedFdBase : public FdT { Result<size_t> flush_write() TD_WARN_UNUSED_RESULT; bool need_flush_write(size_t at_least = 0) { - CHECK(write_); - write_->sync_with_writer(); - return write_->size() > at_least; + return ready_for_flush_write() > at_least; } size_t ready_for_flush_write() { CHECK(write_); write_->sync_with_writer(); return write_->size(); } + void sync_with_poll() { + ::td::sync_with_poll(*this); + } void set_input_writer(ChainBufferWriter *read) { read_ = read; } @@ -50,7 +54,7 @@ class BufferedFdBase : public FdT { }; template <class FdT> -class BufferedFd : public BufferedFdBase<FdT> { +class BufferedFd final : public BufferedFdBase<FdT> { using Parent = BufferedFdBase<FdT>; ChainBufferWriter input_writer_; ChainBufferReader input_reader_; @@ -62,14 +66,21 @@ class BufferedFd : public BufferedFdBase<FdT> { public: BufferedFd(); explicit BufferedFd(FdT &&fd_); - BufferedFd(BufferedFd &&); - BufferedFd &operator=(BufferedFd &&); + BufferedFd(BufferedFd &&) noexcept; + BufferedFd &operator=(BufferedFd &&) noexcept; BufferedFd(const BufferedFd &) = delete; BufferedFd &operator=(const BufferedFd &) = delete; ~BufferedFd(); void close(); + size_t left_unread() const { + return input_reader_.size(); + } + size_t left_unwritten() const { + return output_reader_.size(); + } + Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT; Result<size_t> flush_write() TD_WARN_UNUSED_RESULT; @@ -89,8 +100,9 @@ template <class FdT> Result<size_t> BufferedFdBase<FdT>::flush_read(size_t max_read) { CHECK(read_); size_t result = 0; - while (::td::can_read(*this) && max_read) { - MutableSlice slice = read_->prepare_append().truncate(max_read); + while (::td::can_read_local(*this) && max_read) { + MutableSlice slice = read_->prepare_append(); + slice.truncate(max_read); TRY_RESULT(x, FdT::read(slice)); slice.truncate(x); read_->confirm_append(x); @@ -102,13 +114,25 @@ Result<size_t> BufferedFdBase<FdT>::flush_read(size_t max_read) { template <class FdT> Result<size_t> BufferedFdBase<FdT>::flush_write() { - size_t result = 0; // TODO: sync on demand write_->sync_with_writer(); - while (!write_->empty() && ::td::can_write(*this)) { - Slice slice = write_->prepare_read(); - TRY_RESULT(x, FdT::write(slice)); - write_->confirm_read(x); + size_t result = 0; + while (!write_->empty() && ::td::can_write_local(*this)) { + constexpr size_t BUF_SIZE = 20; + IoSlice buf[BUF_SIZE]; + + auto it = write_->clone(); + size_t buf_i; + for (buf_i = 0; buf_i < BUF_SIZE; buf_i++) { + Slice slice = it.prepare_read(); + if (slice.empty()) { + break; + } + buf[buf_i] = as_io_slice(slice); + it.confirm_read(slice.size()); + } + TRY_RESULT(x, FdT::writev(Span<IoSlice>(buf, buf_i))); + write_->advance(x); result += x; } return result; @@ -139,12 +163,12 @@ BufferedFd<FdT>::BufferedFd(FdT &&fd_) : Parent(std::move(fd_)) { } template <class FdT> -BufferedFd<FdT>::BufferedFd(BufferedFd &&from) { +BufferedFd<FdT>::BufferedFd(BufferedFd &&from) noexcept { *this = std::move(from); } template <class FdT> -BufferedFd<FdT> &BufferedFd<FdT>::operator=(BufferedFd &&from) { +BufferedFd<FdT> &BufferedFd<FdT>::operator=(BufferedFd &&from) noexcept { FdT::operator=(std::move(static_cast<FdT &>(from))); input_reader_ = std::move(from.input_reader_); input_writer_ = std::move(from.input_writer_); @@ -171,7 +195,7 @@ Result<size_t> BufferedFd<FdT>::flush_read(size_t max_read) { if (result) { // TODO: faster sync is possible if you owns writer. input_reader_.sync_with_writer(); - LOG(DEBUG) << "flush_read: +" << format::as_size(result) << tag("total", format::as_size(input_reader_.size())); + LOG(DEBUG) << "Flush read: +" << format::as_size(result) << tag("total", format::as_size(input_reader_.size())); } return result; } @@ -180,7 +204,7 @@ template <class FdT> Result<size_t> BufferedFd<FdT>::flush_write() { TRY_RESULT(result, Parent::flush_write()); if (result) { - LOG(DEBUG) << "flush_write: +" << format::as_size(result) << tag("left", format::as_size(output_reader_.size())); + LOG(DEBUG) << "Flush write: +" << format::as_size(result) << tag("left", format::as_size(output_reader_.size())); } return result; } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h index 9006d78132..5fd8ac44fe 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedReader.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,12 +11,11 @@ #include "td/utils/Slice.h" #include "td/utils/Status.h" -#include <cstring> - namespace td { + class BufferedReader { public: - explciit BufferedReader(FileFd &file, size_t buff_size = 8152) + explicit BufferedReader(FileFd &file, size_t buff_size = 8152) : file_(file), buff_(buff_size), begin_pos_(0), end_pos_(0) { } @@ -33,13 +32,13 @@ inline Result<size_t> BufferedReader::read(MutableSlice slice) { size_t available = end_pos_ - begin_pos_; if (available >= slice.size()) { // have enough data in buffer - std::memcpy(slice.begin(), &buff_[begin_pos_], slice.size()); + slice.copy_from({&buff_[begin_pos_], slice.size()}); begin_pos_ += slice.size(); return slice.size(); } if (available) { - std::memcpy(slice.begin(), &buff_[begin_pos_], available); + slice.copy_from({&buff_[begin_pos_], available}); begin_pos_ += available; slice.remove_prefix(available); } @@ -54,8 +53,9 @@ inline Result<size_t> BufferedReader::read(MutableSlice slice) { end_pos_ = result; size_t left = min(end_pos_, slice.size()); - std::memcpy(slice.begin(), &buff_[begin_pos_], left); - begin_pos_ += left; + slice.copy_from({&buff_[0], left}); + begin_pos_ = left; return left + available; } + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.cpp new file mode 100644 index 0000000000..e28bb97069 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.cpp @@ -0,0 +1,17 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/BufferedUdp.h" + +char disable_linker_warning_about_empty_file_buffered_udp_cpp TD_UNUSED; + +namespace td { + +#if TD_PORT_POSIX +TD_THREAD_LOCAL detail::UdpReader *BufferedUdp::udp_reader_; +#endif + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.h new file mode 100644 index 0000000000..5907e17557 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/BufferedUdp.h @@ -0,0 +1,177 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/buffer.h" +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/optional.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/thread_local.h" +#include "td/utils/port/UdpSocketFd.h" +#include "td/utils/Span.h" +#include "td/utils/Status.h" +#include "td/utils/VectorQueue.h" + +#include <array> + +namespace td { + +#if TD_PORT_POSIX +namespace detail { +class UdpWriter { + public: + static Status write_once(UdpSocketFd &fd, VectorQueue<UdpMessage> &queue) TD_WARN_UNUSED_RESULT { + std::array<UdpSocketFd::OutboundMessage, 16> messages; + auto to_send = queue.as_span(); + size_t to_send_n = td::min(messages.size(), to_send.size()); + to_send.truncate(to_send_n); + for (size_t i = 0; i < to_send_n; i++) { + messages[i].to = &to_send[i].address; + messages[i].data = to_send[i].data.as_slice(); + } + + size_t cnt; + auto status = fd.send_messages(Span<UdpSocketFd::OutboundMessage>(messages).truncate(to_send_n), cnt); + queue.pop_n(cnt); + return status; + } +}; + +class UdpReaderHelper { + public: + void init_inbound_message(UdpSocketFd::InboundMessage &message) { + message.from = &message_.address; + message.error = &message_.error; + if (buffer_.size() < MAX_PACKET_SIZE) { + buffer_ = BufferSlice(RESERVED_SIZE); + } + CHECK(buffer_.size() >= MAX_PACKET_SIZE); + message.data = buffer_.as_slice().substr(0, MAX_PACKET_SIZE); + } + + UdpMessage extract_udp_message(UdpSocketFd::InboundMessage &message) { + message_.data = buffer_.from_slice(message.data); + auto size = message_.data.size(); + size = (size + 7) & ~7; + CHECK(size <= MAX_PACKET_SIZE); + buffer_.confirm_read(size); + return std::move(message_); + } + + private: + static constexpr size_t MAX_PACKET_SIZE = 2048; + static constexpr size_t RESERVED_SIZE = MAX_PACKET_SIZE * 8; + UdpMessage message_; + BufferSlice buffer_; +}; + +// One for thread is enough +class UdpReader { + public: + UdpReader() { + for (size_t i = 0; i < messages_.size(); i++) { + helpers_[i].init_inbound_message(messages_[i]); + } + } + Status read_once(UdpSocketFd &fd, VectorQueue<UdpMessage> &queue) TD_WARN_UNUSED_RESULT { + for (auto &message : messages_) { + CHECK(message.data.size() == 2048); + } + size_t cnt = 0; + auto status = fd.receive_messages(messages_, cnt); + for (size_t i = 0; i < cnt; i++) { + queue.push(helpers_[i].extract_udp_message(messages_[i])); + helpers_[i].init_inbound_message(messages_[i]); + } + for (size_t i = cnt; i < messages_.size(); i++) { + LOG_CHECK(messages_[i].data.size() == 2048) + << " cnt = " << cnt << " i = " << i << " size = " << messages_[i].data.size() << " status = " << status; + } + if (status.is_error() && !UdpSocketFd::is_critical_read_error(status)) { + queue.push(UdpMessage{{}, {}, std::move(status)}); + status = Status::OK(); + } + return status; + } + + private: + static constexpr size_t BUFFER_SIZE = 16; + std::array<UdpSocketFd::InboundMessage, BUFFER_SIZE> messages_; + std::array<UdpReaderHelper, BUFFER_SIZE> helpers_; +}; + +} // namespace detail + +#endif + +class BufferedUdp final : public UdpSocketFd { + public: + explicit BufferedUdp(UdpSocketFd fd) : UdpSocketFd(std::move(fd)) { + } + +#if TD_PORT_POSIX + void sync_with_poll() { + ::td::sync_with_poll(*this); + } + Result<optional<UdpMessage>> receive() { + if (input_.empty() && can_read_local(*this)) { + TRY_STATUS(flush_read_once()); + } + if (input_.empty()) { + return optional<UdpMessage>(); + } + return input_.pop(); + } + + void send(UdpMessage message) { + output_.push(std::move(message)); + } + + Status flush_send() { + Status status; + while (status.is_ok() && can_write_local(*this) && !output_.empty()) { + status = flush_send_once(); + } + return status; + } +#endif + + UdpSocketFd move_as_udp_socket_fd() { + return std::move(as_fd()); + } + + UdpSocketFd &as_fd() { + return *static_cast<UdpSocketFd *>(this); + } + + private: +#if TD_PORT_POSIX + VectorQueue<UdpMessage> input_; + VectorQueue<UdpMessage> output_; + + VectorQueue<UdpMessage> &input() { + return input_; + } + VectorQueue<UdpMessage> &output() { + return output_; + } + + Status flush_send_once() TD_WARN_UNUSED_RESULT { + return detail::UdpWriter::write_once(as_fd(), output_); + } + + Status flush_read_once() TD_WARN_UNUSED_RESULT { + init_thread_local<detail::UdpReader>(udp_reader_); + return udp_reader_->read_once(as_fd(), input_); + } + + static TD_THREAD_LOCAL detail::UdpReader *udp_reader_; +#endif +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h index fb0c4489eb..2043864656 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ByteFlow.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,9 +8,10 @@ #include "td/utils/buffer.h" #include "td/utils/common.h" -#include "td/utils/logging.h" #include "td/utils/Status.h" +#include <limits> + namespace td { class ByteFlowInterface { @@ -20,6 +21,10 @@ class ByteFlowInterface { virtual void set_parent(ByteFlowInterface &other) = 0; virtual void set_input(ChainBufferReader *input) = 0; virtual size_t get_need_size() = 0; + virtual size_t get_read_size() = 0; + virtual size_t get_write_size() = 0; + virtual void reset_need_size() { + } ByteFlowInterface() = default; ByteFlowInterface(const ByteFlowInterface &) = delete; ByteFlowInterface &operator=(const ByteFlowInterface &) = delete; @@ -42,36 +47,98 @@ class ByteFlowBaseCommon : public ByteFlowInterface { } void wakeup() final { - if (stop_flag_) { + if (stop_flag_ || !input_) { return; } input_->sync_with_writer(); + if (waiting_flag_) { if (!is_input_active_) { finish(Status::OK()); } return; } - if (is_input_active_) { - if (need_size_ != 0 && input_->size() < need_size_) { - return; + while (true) { + if (stop_flag_) { + break; + } + + // update can_read + if (is_input_active_) { + auto read_size = get_read_size(); + if (read_size < min(need_size_, options_.read_watermark.low)) { + can_read = false; + } + if (read_size >= max(need_size_, options_.read_watermark.high)) { + can_read = true; + } + } else { + // always can read when input is closed + can_read = true; + } + + // update can_write + { + auto write_size = get_write_size(); + if (write_size > options_.write_watermark.high) { + can_write = false; + } + if (write_size <= options_.write_watermark.low) { + can_write = true; + } + } + + if (!can_read || !can_write) { + break; + } + need_size_ = 0; + + if (!loop()) { + if (need_size_ <= get_read_size()) { + need_size_ = get_read_size() + 1; + } } } - need_size_ = 0; - loop(); + on_output_updated(); } size_t get_need_size() final { return need_size_; } + void reset_need_size() override { + need_size_ = 0; + } + size_t get_read_size() override { + input_->sync_with_writer(); + return input_->size(); + } + size_t get_write_size() override { + CHECK(parent_); + return parent_->get_read_size(); + } + + struct Watermark { + size_t low{std::numeric_limits<size_t>::max()}; + size_t high{0}; + }; + struct Options { + Watermark write_watermark; + Watermark read_watermark; + }; + void set_options(Options options) { + options_ = options; + } - virtual void loop() = 0; + virtual bool loop() = 0; protected: bool waiting_flag_ = false; - ChainBufferReader *input_; + ChainBufferReader *input_ = nullptr; bool is_input_active_ = true; size_t need_size_ = 0; + bool can_read{true}; + bool can_write{true}; + Options options_; void finish(Status status) { stop_flag_ = true; need_size_ = 0; @@ -115,7 +182,7 @@ class ByteFlowBase : public ByteFlowBaseCommon { parent_ = &other; parent_->set_input(&output_reader_); } - void loop() override = 0; + bool loop() override = 0; // ChainBufferWriter &get_output() { // return output_; @@ -138,7 +205,7 @@ class ByteFlowInplaceBase : public ByteFlowBaseCommon { parent_ = &other; parent_->set_input(&output_); } - void loop() override = 0; + bool loop() override = 0; ChainBufferReader &get_output() { return output_; @@ -153,16 +220,16 @@ inline ByteFlowInterface &operator>>(ByteFlowInterface &from, ByteFlowInterface return to; } -class ByteFlowSource : public ByteFlowInterface { +class ByteFlowSource final : public ByteFlowInterface { public: ByteFlowSource() = default; explicit ByteFlowSource(ChainBufferReader *buffer) : buffer_(buffer) { } - ByteFlowSource(ByteFlowSource &&other) : buffer_(other.buffer_), parent_(other.parent_) { + ByteFlowSource(ByteFlowSource &&other) noexcept : buffer_(other.buffer_), parent_(other.parent_) { other.buffer_ = nullptr; other.parent_ = nullptr; } - ByteFlowSource &operator=(ByteFlowSource &&other) { + ByteFlowSource &operator=(ByteFlowSource &&other) noexcept { buffer_ = other.buffer_; parent_ = other.parent_; other.buffer_ = nullptr; @@ -187,7 +254,9 @@ class ByteFlowSource : public ByteFlowInterface { parent_ = nullptr; } void wakeup() final { - CHECK(parent_); + if (!parent_) { + return; + } parent_->wakeup(); } size_t get_need_size() final { @@ -196,19 +265,27 @@ class ByteFlowSource : public ByteFlowInterface { } return parent_->get_need_size(); } + size_t get_read_size() final { + UNREACHABLE(); + return 0; + } + size_t get_write_size() final { + CHECK(parent_); + return parent_->get_read_size(); + } private: ChainBufferReader *buffer_ = nullptr; ByteFlowInterface *parent_ = nullptr; }; -class ByteFlowSink : public ByteFlowInterface { +class ByteFlowSink final : public ByteFlowInterface { public: void set_input(ChainBufferReader *input) final { CHECK(buffer_ == nullptr); buffer_ = input; } - void set_parent(ByteFlowInterface &parent) final { + void set_parent(ByteFlowInterface & /*parent*/) final { UNREACHABLE(); } void close_input(Status status) final { @@ -224,6 +301,14 @@ class ByteFlowSink : public ByteFlowInterface { UNREACHABLE(); return 0; } + size_t get_read_size() final { + buffer_->sync_with_writer(); + return buffer_->size(); + } + size_t get_write_size() final { + UNREACHABLE(); + return 0; + } bool is_ready() { return !active_; } @@ -244,13 +329,17 @@ class ByteFlowSink : public ByteFlowInterface { ChainBufferReader *buffer_ = nullptr; }; -class ByteFlowMoveSink : public ByteFlowInterface { +class ByteFlowMoveSink final : public ByteFlowInterface { public: + ByteFlowMoveSink() = default; + explicit ByteFlowMoveSink(ChainBufferWriter *output) { + set_output(output); + } void set_input(ChainBufferReader *input) final { CHECK(!input_); input_ = input; } - void set_parent(ByteFlowInterface &parent) final { + void set_parent(ByteFlowInterface & /*parent*/) final { UNREACHABLE(); } void close_input(Status status) final { @@ -267,6 +356,15 @@ class ByteFlowMoveSink : public ByteFlowInterface { UNREACHABLE(); return 0; } + size_t get_read_size() final { + input_->sync_with_writer(); + //TODO: must be input_->size() + output_->size() + return input_->size(); + } + size_t get_write_size() final { + UNREACHABLE(); + return 0; + } void set_output(ChainBufferWriter *output) { CHECK(!output_); output_ = output; @@ -285,4 +383,5 @@ class ByteFlowMoveSink : public ByteFlowInterface { ChainBufferReader *input_ = nullptr; ChainBufferWriter *output_ = nullptr; }; + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/CancellationToken.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/CancellationToken.h new file mode 100644 index 0000000000..19f280655c --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/CancellationToken.h @@ -0,0 +1,71 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include <atomic> +#include <memory> + +namespace td { + +namespace detail { +struct RawCancellationToken { + std::atomic<bool> is_canceled_{false}; +}; +} // namespace detail + +class CancellationToken { + public: + explicit operator bool() const noexcept { + // empty CancellationToken is never canceled + if (!token_) { + return false; + } + return token_->is_canceled_.load(std::memory_order_acquire); + } + CancellationToken() = default; + explicit CancellationToken(std::shared_ptr<detail::RawCancellationToken> token) : token_(std::move(token)) { + } + + private: + std::shared_ptr<detail::RawCancellationToken> token_; +}; + +class CancellationTokenSource { + public: + CancellationTokenSource() = default; + CancellationTokenSource(CancellationTokenSource &&other) noexcept : token_(std::move(other.token_)) { + } + CancellationTokenSource &operator=(CancellationTokenSource &&other) noexcept { + cancel(); + token_ = std::move(other.token_); + return *this; + } + CancellationTokenSource(const CancellationTokenSource &other) = delete; + CancellationTokenSource &operator=(const CancellationTokenSource &other) = delete; + ~CancellationTokenSource() { + cancel(); + } + + CancellationToken get_cancellation_token() { + if (!token_) { + token_ = std::make_shared<detail::RawCancellationToken>(); + } + return CancellationToken(token_); + } + void cancel() { + if (!token_) { + return; + } + token_->is_canceled_.store(true, std::memory_order_release); + token_.reset(); + } + + private: + std::shared_ptr<detail::RawCancellationToken> token_; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ChainScheduler.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ChainScheduler.h new file mode 100644 index 0000000000..721a22e137 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ChainScheduler.h @@ -0,0 +1,376 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/algorithm.h" +#include "td/utils/common.h" +#include "td/utils/Container.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/List.h" +#include "td/utils/logging.h" +#include "td/utils/optional.h" +#include "td/utils/Span.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/VectorQueue.h" + +#include <functional> +#include <unordered_map> + +namespace td { + +struct ChainSchedulerBase { + struct TaskWithParents { + uint64 task_id{}; + vector<uint64> parents; + }; +}; + +template <class ExtraT = Unit> +class ChainScheduler final : public ChainSchedulerBase { + public: + using TaskId = uint64; + using ChainId = uint64; + + TaskId create_task(Span<ChainId> chains, ExtraT extra = {}); + + ExtraT *get_task_extra(TaskId task_id); + + optional<TaskWithParents> start_next_task(); + + void pause_task(TaskId task_id); + + void finish_task(TaskId task_id); + + void reset_task(TaskId task_id); + + template <class F> + void for_each(F &&f) { + tasks_.for_each([&f](auto, Task &task) { f(task.extra); }); + } + + template <class F> + void for_each_dependent(TaskId task_id, F &&f) { + auto *task = tasks_.get(task_id); + CHECK(task != nullptr); + FlatHashSet<TaskId> visited; + bool check_for_collisions = task->chains.size() > 1; + for (TaskChainInfo &task_chain_info : task->chains) { + ChainInfo &chain_info = *task_chain_info.chain_info; + chain_info.chain.foreach_child(&task_chain_info.chain_node, [&](TaskId task_id, uint64) { + if (check_for_collisions && !visited.insert(task_id).second) { + return; + } + f(task_id); + }); + } + } + + private: + struct ChainNode : ListNode { + TaskId task_id{}; + uint64 generation{}; + }; + + class Chain { + public: + void add_task(ChainNode *node) { + head_.put_back(node); + } + + optional<TaskId> get_first() { + if (head_.empty()) { + return {}; + } + return static_cast<ChainNode &>(*head_.get_next()).task_id; + } + + optional<TaskId> get_child(ChainNode *chain_node) { + if (chain_node->get_next() == head_.end()) { + return {}; + } + return static_cast<ChainNode &>(*chain_node->get_next()).task_id; + } + optional<ChainNode *> get_parent(ChainNode *chain_node) { + if (chain_node->get_prev() == head_.end()) { + return {}; + } + return static_cast<ChainNode *>(chain_node->get_prev()); + } + + void finish_task(ChainNode *node) { + node->remove(); + } + + bool empty() const { + return head_.empty(); + } + + void foreach(std::function<void(TaskId, uint64)> f) const { + for (auto it = head_.begin(); it != head_.end(); it = it->get_next()) { + auto &node = static_cast<const ChainNode &>(*it); + f(node.task_id, node.generation); + } + } + void foreach_child(ListNode *start_node, std::function<void(TaskId, uint64)> f) const { + for (auto it = start_node; it != head_.end(); it = it->get_next()) { + auto &node = static_cast<const ChainNode &>(*it); + f(node.task_id, node.generation); + } + } + + private: + ListNode head_; + }; + struct ChainInfo { + Chain chain; + uint32 active_tasks{}; + uint64 generation{1}; + }; + struct TaskChainInfo { + ChainNode chain_node; + ChainId chain_id{}; + ChainInfo *chain_info{}; + }; + struct Task { + enum class State { Pending, Active, Paused } state{State::Pending}; + vector<TaskChainInfo> chains; + ExtraT extra; + }; + std::unordered_map<ChainId, ChainInfo, Hash<ChainId>> chains_; + std::unordered_map<ChainId, TaskId, Hash<ChainId>> limited_tasks_; + Container<Task> tasks_; + VectorQueue<TaskId> pending_tasks_; + + void try_start_task(TaskId task_id) { + auto *task = tasks_.get(task_id); + CHECK(task != nullptr); + if (task->state != Task::State::Pending) { + return; + } + for (TaskChainInfo &task_chain_info : task->chains) { + auto o_parent = task_chain_info.chain_info->chain.get_parent(&task_chain_info.chain_node); + + if (o_parent) { + if (o_parent.value()->generation != task_chain_info.chain_info->generation) { + return; + } + } + + if (task_chain_info.chain_info->active_tasks >= 10) { + limited_tasks_[task_chain_info.chain_id] = task_id; + return; + } + } + + do_start_task(task_id, task); + } + + void do_start_task(TaskId task_id, Task *task) { + for (TaskChainInfo &task_chain_info : task->chains) { + ChainInfo &chain_info = chains_[task_chain_info.chain_id]; + chain_info.active_tasks++; + task_chain_info.chain_node.generation = chain_info.generation; + } + task->state = Task::State::Active; + + pending_tasks_.push(task_id); + for_each_child(task, [&](TaskId task_id) { try_start_task(task_id); }); + } + + template <class F> + void for_each_child(Task *task, F &&f) { + for (TaskChainInfo &task_chain_info : task->chains) { + ChainInfo &chain_info = *task_chain_info.chain_info; + auto o_child = chain_info.chain.get_child(&task_chain_info.chain_node); + if (o_child) { + f(o_child.value()); + } + } + } + + void inactivate_task(TaskId task_id, bool failed) { + LOG(DEBUG) << "Inactivate " << task_id << " " << (failed ? "failed" : "finished"); + auto *task = tasks_.get(task_id); + CHECK(task != nullptr); + bool was_active = task->state == Task::State::Active; + task->state = Task::State::Pending; + for (TaskChainInfo &task_chain_info : task->chains) { + ChainInfo &chain_info = *task_chain_info.chain_info; + if (was_active) { + chain_info.active_tasks--; + } + if (was_active && failed) { + chain_info.generation = td::max(chain_info.generation, task_chain_info.chain_node.generation + 1); + } + + auto it = limited_tasks_.find(task_chain_info.chain_id); + if (it != limited_tasks_.end()) { + auto limited_task_id = it->second; + limited_tasks_.erase(it); + if (limited_task_id != task_id) { + try_start_task_later(limited_task_id); + } + } + + auto o_first = chain_info.chain.get_first(); + if (o_first) { + auto first_task_id = o_first.unwrap(); + if (first_task_id != task_id) { + try_start_task_later(first_task_id); + } + } + } + } + + void finish_chain_task(TaskChainInfo &task_chain_info) { + auto &chain = task_chain_info.chain_info->chain; + chain.finish_task(&task_chain_info.chain_node); + if (chain.empty()) { + chains_.erase(task_chain_info.chain_id); + } + } + + vector<TaskId> to_start_; + + void try_start_task_later(TaskId task_id) { + LOG(DEBUG) << "Start later " << task_id; + to_start_.push_back(task_id); + } + + void flush_try_start_task() { + auto moved_to_start = std::move(to_start_); + for (auto task_id : moved_to_start) { + try_start_task(task_id); + } + CHECK(to_start_.empty()); + } + + template <class ExtraTT> + friend StringBuilder &operator<<(StringBuilder &sb, ChainScheduler<ExtraTT> &scheduler); +}; + +template <class ExtraT> +typename ChainScheduler<ExtraT>::TaskId ChainScheduler<ExtraT>::create_task(Span<ChainScheduler::ChainId> chains, + ExtraT extra) { + auto task_id = tasks_.create(); + Task &task = *tasks_.get(task_id); + task.extra = std::move(extra); + task.chains = transform(chains, [&](auto chain_id) { + TaskChainInfo task_chain_info; + ChainInfo &chain_info = chains_[chain_id]; + task_chain_info.chain_id = chain_id; + task_chain_info.chain_info = &chain_info; + task_chain_info.chain_node.task_id = task_id; + task_chain_info.chain_node.generation = 0; + return task_chain_info; + }); + + for (TaskChainInfo &task_chain_info : task.chains) { + ChainInfo &chain_info = *task_chain_info.chain_info; + chain_info.chain.add_task(&task_chain_info.chain_node); + } + + try_start_task(task_id); + return task_id; +} + +// TODO: return reference +template <class ExtraT> +ExtraT *ChainScheduler<ExtraT>::get_task_extra(ChainScheduler::TaskId task_id) { // may return nullptr + auto *task = tasks_.get(task_id); + if (task == nullptr) { + return nullptr; + } + return &task->extra; +} + +template <class ExtraT> +optional<ChainSchedulerBase::TaskWithParents> ChainScheduler<ExtraT>::start_next_task() { + if (pending_tasks_.empty()) { + return {}; + } + auto task_id = pending_tasks_.pop(); + TaskWithParents res; + res.task_id = task_id; + auto *task = tasks_.get(task_id); + CHECK(task != nullptr); + for (TaskChainInfo &task_chain_info : task->chains) { + Chain &chain = task_chain_info.chain_info->chain; + auto o_parent = chain.get_parent(&task_chain_info.chain_node); + if (o_parent) { + res.parents.push_back(o_parent.value()->task_id); + } + } + return res; +} + +template <class ExtraT> +void ChainScheduler<ExtraT>::finish_task(ChainScheduler::TaskId task_id) { + auto *task = tasks_.get(task_id); + CHECK(task != nullptr); + CHECK(to_start_.empty()); + + inactivate_task(task_id, false); + + for_each_child(task, [&](TaskId task_id) { try_start_task_later(task_id); }); + + for (TaskChainInfo &task_chain_info : task->chains) { + finish_chain_task(task_chain_info); + } + + tasks_.erase(task_id); + flush_try_start_task(); +} + +template <class ExtraT> +void ChainScheduler<ExtraT>::reset_task(ChainScheduler::TaskId task_id) { + CHECK(to_start_.empty()); + auto *task = tasks_.get(task_id); + CHECK(task != nullptr); + inactivate_task(task_id, true); + try_start_task_later(task_id); + flush_try_start_task(); +} + +template <class ExtraT> +void ChainScheduler<ExtraT>::pause_task(TaskId task_id) { + auto *task = tasks_.get(task_id); + CHECK(task != nullptr); + inactivate_task(task_id, true); + task->state = Task::State::Paused; + flush_try_start_task(); +} + +template <class ExtraT> +StringBuilder &operator<<(StringBuilder &sb, ChainScheduler<ExtraT> &scheduler) { + // 1 print chains + sb << '\n'; + for (auto &it : scheduler.chains_) { + sb << "ChainId{" << it.first << "}"; + sb << " active_cnt = " << it.second.active_tasks; + sb << " g = " << it.second.generation; + sb << ':'; + it.second.chain.foreach( + [&](auto task_id, auto generation) { sb << ' ' << *scheduler.get_task_extra(task_id) << ':' << generation; }); + sb << '\n'; + } + scheduler.tasks_.for_each([&](auto id, auto &task) { + sb << "Task: " << task.extra; + sb << " state = " << static_cast<int>(task.state); + for (auto &task_chain_info : task.chains) { + sb << " g = " << task_chain_info.chain_node.generation; + if (task_chain_info.chain_info->generation != task_chain_info.chain_node.generation) { + sb << " chain_g = " << task_chain_info.chain_info->generation; + } + } + sb << '\n'; + }); + return sb; +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h index 9342f45a8b..669111a2ff 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ChangesProcessor.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h index 718f930b8a..345f1b1f89 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Closure.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,9 +8,7 @@ #include "td/utils/common.h" #include "td/utils/invoke.h" -#include "td/utils/logging.h" -#include <cstdlib> #include <tuple> #include <type_traits> #include <utility> @@ -54,8 +52,7 @@ // If delay is needed, just std::forward data to temporary storage, and std::move them when call is executed. // // -// create_immediate_closure(&Actor::func, arg1, arg2, ..., argn).run(actor) -// to_delayed_closure(std::move(immediate)).run(actor) +// create_immediate_closure(&ActorT::func, arg1, arg2, ..., argn).run(actor) namespace td { template <class ActorT, class FunctionT, class... ArgsT> @@ -68,22 +65,22 @@ class ImmediateClosure { friend Delayed; using ActorType = ActorT; - void run(ActorT *actor) { - mem_call_tuple(actor, func, std::move(args)); - } - // no &&. just save references as references. - explicit ImmediateClosure(FunctionT func, ArgsT... args) : func(func), args(std::forward<ArgsT>(args)...) { + explicit ImmediateClosure(FunctionT func, ArgsT... args) : args(func, std::forward<ArgsT>(args)...) { } private: - FunctionT func; - std::tuple<ArgsT...> args; + std::tuple<FunctionT, ArgsT...> args; + + public: + auto run(ActorT *actor) -> decltype(mem_call_tuple(actor, std::move(args))) { + return mem_call_tuple(actor, std::move(args)); + } }; template <class ActorT, class ResultT, class... DestArgsT, class... SrcArgsT> ImmediateClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...> create_immediate_closure( - ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&... args) { + ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&...args) { return ImmediateClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...>(func, std::forward<SrcArgsT>(args)...); } @@ -92,21 +89,11 @@ template <class ActorT, class FunctionT, class... ArgsT> class DelayedClosure { public: using ActorType = ActorT; - using Delayed = DelayedClosure<ActorT, FunctionT, ArgsT...>; - void run(ActorT *actor) { - mem_call_tuple(actor, func, std::move(args)); + explicit DelayedClosure(ImmediateClosure<ActorT, FunctionT, ArgsT...> &&other) : args(std::move(other.args)) { } - DelayedClosure clone() const { - return do_clone(*this); - } - - explicit DelayedClosure(ImmediateClosure<ActorT, FunctionT, ArgsT...> &&other) - : func(std::move(other.func)), args(std::move(other.args)) { - } - - explicit DelayedClosure(FunctionT func, ArgsT... args) : func(func), args(std::forward<ArgsT>(args)...) { + explicit DelayedClosure(FunctionT func, ArgsT... args) : args(func, std::forward<ArgsT>(args)...) { } template <class F> @@ -115,53 +102,16 @@ class DelayedClosure { } private: - using ArgsStorageT = std::tuple<typename std::decay<ArgsT>::type...>; - - FunctionT func; - ArgsStorageT args; + std::tuple<FunctionT, typename std::decay<ArgsT>::type...> args; - template <class FromActorT, class FromFunctionT, class... FromArgsT> - explicit DelayedClosure(const DelayedClosure<FromActorT, FromFunctionT, FromArgsT...> &other, - std::enable_if_t<LogicAnd<std::is_copy_constructible<FromArgsT>::value...>::value, int> = 0) - : func(other.func), args(other.args) { - } - - template <class FromActorT, class FromFunctionT, class... FromArgsT> - explicit DelayedClosure( - const DelayedClosure<FromActorT, FromFunctionT, FromArgsT...> &other, - std::enable_if_t<!LogicAnd<std::is_copy_constructible<FromArgsT>::value...>::value, int> = 0) { - LOG(FATAL) << "Deleted constructor"; - std::abort(); - } - - template <class FromActorT, class FromFunctionT, class... FromArgsT> - std::enable_if_t<!LogicAnd<std::is_copy_constructible<FromArgsT>::value...>::value, - DelayedClosure<FromActorT, FromFunctionT, FromArgsT...>> - do_clone(const DelayedClosure<FromActorT, FromFunctionT, FromArgsT...> &value) const { - LOG(FATAL) << "Trying to clone DelayedClosure that contains noncopyable elements"; - std::abort(); - } - - template <class FromActorT, class FromFunctionT, class... FromArgsT> - std::enable_if_t<LogicAnd<std::is_copy_constructible<FromArgsT>::value...>::value, - DelayedClosure<FromActorT, FromFunctionT, FromArgsT...>> - do_clone(const DelayedClosure<FromActorT, FromFunctionT, FromArgsT...> &value) const { - return DelayedClosure<FromActorT, FromFunctionT, FromArgsT...>(value); + public: + auto run(ActorT *actor) -> decltype(mem_call_tuple(actor, std::move(args))) { + return mem_call_tuple(actor, std::move(args)); } }; -template <class... ArgsT> -typename ImmediateClosure<ArgsT...>::Delayed to_delayed_closure(ImmediateClosure<ArgsT...> &&other) { - return typename ImmediateClosure<ArgsT...>::Delayed(std::move(other)); -} - -template <class... ArgsT> -DelayedClosure<ArgsT...> to_delayed_closure(DelayedClosure<ArgsT...> &&other) { - return std::move(other); -} - template <class ActorT, class ResultT, class... DestArgsT, class... SrcArgsT> -auto create_delayed_closure(ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&... args) { +auto create_delayed_closure(ResultT (ActorT::*func)(DestArgsT...), SrcArgsT &&...args) { return DelayedClosure<ActorT, ResultT (ActorT::*)(DestArgsT...), SrcArgsT &&...>(func, std::forward<SrcArgsT>(args)...); } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/CombinedLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/CombinedLog.h new file mode 100644 index 0000000000..f2fe36069e --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/CombinedLog.h @@ -0,0 +1,86 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/algorithm.h" +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/Slice.h" + +namespace td { + +class CombinedLog final : public LogInterface { + public: + void set_first(LogInterface *first) { + first_ = first; + } + + void set_second(LogInterface *second) { + second_ = second; + } + + void set_first_verbosity_level(int new_verbosity_level) { + first_verbosity_level_ = new_verbosity_level; + } + + void set_second_verbosity_level(int new_verbosity_level) { + second_verbosity_level_ = new_verbosity_level; + } + + const LogInterface *get_first() const { + return first_; + } + + const LogInterface *get_second() const { + return second_; + } + + int get_first_verbosity_level() const { + return first_verbosity_level_; + } + + int get_second_verbosity_level() const { + return second_verbosity_level_; + } + + private: + LogInterface *first_ = nullptr; + int first_verbosity_level_ = VERBOSITY_NAME(FATAL); + LogInterface *second_ = nullptr; + int second_verbosity_level_ = VERBOSITY_NAME(FATAL); + + void do_append(int log_level, CSlice slice) final { + if (first_ && log_level <= first_verbosity_level_) { + first_->do_append(log_level, slice); + } + if (second_ && log_level <= second_verbosity_level_) { + second_->do_append(log_level, slice); + } + } + + void after_rotation() final { + if (first_) { + first_->after_rotation(); + } + if (second_) { + second_->after_rotation(); + } + } + + vector<string> get_file_paths() final { + vector<string> result; + if (first_) { + ::td::append(result, first_->get_file_paths()); + } + if (second_) { + ::td::append(result, second_->get_file_paths()); + } + return result; + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ConcurrentHashTable.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ConcurrentHashTable.h new file mode 100644 index 0000000000..f3f0bcfa92 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ConcurrentHashTable.h @@ -0,0 +1,322 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/HazardPointers.h" +#include "td/utils/logging.h" +#include "td/utils/port/thread_local.h" + +#include <atomic> +#include <condition_variable> +#include <mutex> + +namespace td { + +// AtomicHashArray<KeyT, ValueT> +// Building block for other concurrent hash maps +// +// Support one operation: +// template <class F> +// bool with_value(KeyT key, bool should_create, F &&func); +// +// Finds slot for key, and call func(value) +// Creates slot if should_create is true. +// Returns true if func was called. +// +// Concurrent calls with the same key may result in concurrent calls to func(value) +// It is responsibility of the caller to handle such races. +// +// Key should already be random +// It is responsibility of the caller to provide unique random key. +// One may use injective hash function, or handle collisions in some other way. + +template <class KeyT, class ValueT> +class AtomicHashArray { + public: + explicit AtomicHashArray(size_t n) : nodes_(n) { + } + struct Node { + std::atomic<KeyT> key{KeyT{}}; + ValueT value{}; + }; + size_t size() const { + return nodes_.size(); + } + Node &node_at(size_t i) { + return nodes_[i]; + } + static KeyT empty_key() { + return KeyT{}; + } + + template <class F> + bool with_value(KeyT key, bool should_create, F &&f) { + DCHECK(key != empty_key()); + auto pos = static_cast<size_t>(key) % nodes_.size(); + auto n = td::min(td::max(static_cast<size_t>(300), nodes_.size() / 16 + 2), nodes_.size()); + + for (size_t i = 0; i < n; i++) { + pos++; + if (pos >= nodes_.size()) { + pos = 0; + } + auto &node = nodes_[pos]; + while (true) { + auto node_key = node.key.load(std::memory_order_acquire); + if (node_key == empty_key()) { + if (!should_create) { + return false; + } + KeyT expected_key = empty_key(); + if (node.key.compare_exchange_strong(expected_key, key, std::memory_order_relaxed, + std::memory_order_relaxed)) { + f(node.value); + return true; + } + } else if (node_key == key) { + f(node.value); + return true; + } else { + break; + } + } + } + return false; + } + + private: + std::vector<Node> nodes_; +}; + +// Simple concurrent hash map with multiple limitations +template <class KeyT, class ValueT> +class ConcurrentHashMap { + using HashMap = AtomicHashArray<KeyT, std::atomic<ValueT>>; + static HazardPointers<HashMap> hp_; + + public: + explicit ConcurrentHashMap(size_t n = 32) { + n = 1; + hash_map_.store(make_unique<HashMap>(n).release()); + } + ConcurrentHashMap(const ConcurrentHashMap &) = delete; + ConcurrentHashMap &operator=(const ConcurrentHashMap &) = delete; + ConcurrentHashMap(ConcurrentHashMap &&) = delete; + ConcurrentHashMap &operator=(ConcurrentHashMap &&) = delete; + ~ConcurrentHashMap() { + unique_ptr<HashMap>(hash_map_.load()).reset(); + } + + static std::string get_name() { + return "ConcurrrentHashMap"; + } + + static KeyT empty_key() { + return KeyT{}; + } + static ValueT empty_value() { + return ValueT{}; + } + static ValueT migrate_value() { + return (ValueT)(1); // c-style conversion because reinterpret_cast<int>(1) is CE in MSVC + } + + ValueT insert(KeyT key, ValueT value) { + CHECK(key != empty_key()); + CHECK(value != migrate_value()); + typename HazardPointers<HashMap>::Holder holder(hp_, get_thread_id(), 0); + while (true) { + auto hash_map = holder.protect(hash_map_); + if (!hash_map) { + do_migrate(nullptr); + continue; + } + + bool ok = false; + ValueT inserted_value; + hash_map->with_value(key, true, [&](auto &node_value) { + ValueT expected_value = this->empty_value(); + if (node_value.compare_exchange_strong(expected_value, value, std::memory_order_release, + std::memory_order_acquire)) { + ok = true; + inserted_value = value; + } else { + if (expected_value == this->migrate_value()) { + ok = false; + } else { + ok = true; + inserted_value = expected_value; + } + } + }); + if (ok) { + return inserted_value; + } + do_migrate(hash_map); + } + } + + ValueT find(KeyT key, ValueT value) { + typename HazardPointers<HashMap>::Holder holder(hp_, get_thread_id(), 0); + while (true) { + auto hash_map = holder.protect(hash_map_); + if (!hash_map) { + do_migrate(nullptr); + continue; + } + + bool has_value = hash_map->with_value( + key, false, [&](auto &node_value) { value = node_value.load(std::memory_order_acquire); }); + if (!has_value || value != migrate_value()) { + return value; + } + do_migrate(hash_map); + } + } + + template <class F> + void for_each(F &&f) { + auto hash_map = hash_map_.load(); + CHECK(hash_map); + auto size = hash_map->size(); + for (size_t i = 0; i < size; i++) { + auto &node = hash_map->node_at(i); + auto key = node.key.load(std::memory_order_relaxed); + auto value = node.value.load(std::memory_order_relaxed); + + if (key != empty_key()) { + CHECK(value != migrate_value()); + if (value != empty_value()) { + f(key, value); + } + } + } + } + + private: + // use no padding intentionally + std::atomic<HashMap *> hash_map_{nullptr}; + + std::mutex migrate_mutex_; + std::condition_variable migrate_cv_; + + int migrate_cnt_{0}; + int migrate_generation_{0}; + HashMap *migrate_from_hash_map_{nullptr}; + HashMap *migrate_to_hash_map_{nullptr}; + struct Task { + size_t begin; + size_t end; + bool empty() const { + return begin >= end; + } + size_t size() const { + if (empty()) { + return 0; + } + return end - begin; + } + }; + + struct TaskCreator { + size_t chunk_size; + size_t size; + std::atomic<size_t> pos{0}; + Task create() { + auto i = pos++; + auto begin = i * chunk_size; + auto end = begin + chunk_size; + if (end > size) { + end = size; + } + return {begin, end}; + } + }; + TaskCreator task_creator; + + void do_migrate(HashMap *ptr) { + //LOG(ERROR) << "In do_migrate: " << ptr; + std::unique_lock<std::mutex> lock(migrate_mutex_); + if (hash_map_.load() != ptr) { + return; + } + init_migrate(); + CHECK(!ptr || migrate_from_hash_map_ == ptr); + migrate_cnt_++; + auto migrate_generation = migrate_generation_; + lock.unlock(); + + run_migrate(); + + lock.lock(); + migrate_cnt_--; + if (migrate_cnt_ == 0) { + finish_migrate(); + } + migrate_cv_.wait(lock, [&] { return migrate_generation_ != migrate_generation; }); + } + + void finish_migrate() { + //LOG(ERROR) << "In finish_migrate"; + hash_map_.store(migrate_to_hash_map_); + hp_.retire(get_thread_id(), migrate_from_hash_map_); + migrate_from_hash_map_ = nullptr; + migrate_to_hash_map_ = nullptr; + migrate_generation_++; + migrate_cv_.notify_all(); + } + + void init_migrate() { + if (migrate_from_hash_map_ != nullptr) { + return; + } + //LOG(ERROR) << "In init_migrate"; + CHECK(migrate_cnt_ == 0); + migrate_generation_++; + migrate_from_hash_map_ = hash_map_.exchange(nullptr); + auto new_size = migrate_from_hash_map_->size() * 2; + migrate_to_hash_map_ = make_unique<HashMap>(new_size).release(); + task_creator.chunk_size = 100; + task_creator.size = migrate_from_hash_map_->size(); + task_creator.pos = 0; + } + + void run_migrate() { + //LOG(ERROR) << "In run_migrate"; + size_t cnt = 0; + while (true) { + auto task = task_creator.create(); + cnt += task.size(); + if (task.empty()) { + break; + } + run_task(task); + } + //LOG(ERROR) << "In run_migrate " << cnt; + } + + void run_task(Task task) { + for (auto i = task.begin; i < task.end; i++) { + auto &node = migrate_from_hash_map_->node_at(i); + auto old_value = node.value.exchange(migrate_value(), std::memory_order_acq_rel); + if (old_value == 0) { + continue; + } + auto node_key = node.key.load(std::memory_order_relaxed); + //LOG(ERROR) << node_key << " " << node_key; + auto ok = migrate_to_hash_map_->with_value( + node_key, true, [&](auto &node_value) { node_value.store(old_value, std::memory_order_relaxed); }); + LOG_CHECK(ok) << "Migration overflow"; + } + } +}; + +template <class KeyT, class ValueT> +HazardPointers<typename ConcurrentHashMap<KeyT, ValueT>::HashMap> ConcurrentHashMap<KeyT, ValueT>::hp_(64); + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h index 57b4bb4d16..418edfd56a 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Container.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,7 +7,6 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/logging.h" #include <limits> @@ -107,8 +106,8 @@ class Container { } int32 decode_id(Id id) const { - int32 slot_id = static_cast<int32>(id >> 32); - uint32 generation = static_cast<uint32>(id); + auto slot_id = static_cast<int32>(id >> 32); + auto generation = static_cast<uint32>(id); if (slot_id < 0 || slot_id >= static_cast<int32>(slots_.size())) { return -1; } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Context.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Context.h new file mode 100644 index 0000000000..e9cac2ed09 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Context.h @@ -0,0 +1,44 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/port/thread_local.h" + +namespace td { + +template <class Impl> +class Context { + public: + static Impl *get() { + return context_; + } + class Guard { + public: + explicit Guard(Impl *new_context) { + old_context_ = context_; + context_ = new_context; + } + ~Guard() { + context_ = old_context_; + } + Guard(const Guard &) = delete; + Guard &operator=(const Guard &) = delete; + Guard(Guard &&) = delete; + Guard &operator=(Guard &&) = delete; + + private: + Impl *old_context_; + }; + + private: + static TD_THREAD_LOCAL Impl *context_; +}; + +template <class Impl> +TD_THREAD_LOCAL Impl *Context<Impl>::context_; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/DecTree.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/DecTree.h new file mode 100644 index 0000000000..6044842f69 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/DecTree.h @@ -0,0 +1,216 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Random.h" + +#include <functional> +#include <utility> + +namespace td { + +template <typename KeyType, typename ValueType, typename Compare = std::less<KeyType>> +class DecTree { + struct Node { + unique_ptr<Node> left_; + unique_ptr<Node> right_; + size_t size_; + KeyType key_; + ValueType value_; + uint32 y_; + + void relax() { + size_ = 1; + if (left_ != nullptr) { + size_ += left_->size_; + } + if (right_ != nullptr) { + size_ += right_->size_; + } + } + + Node(KeyType key, ValueType value, uint32 y) : size_(1), key_(std::move(key)), value_(std::move(value)), y_(y) { + } + }; + unique_ptr<Node> root_; + + static unique_ptr<Node> create_node(KeyType key, ValueType value, uint32 y) { + return make_unique<Node>(std::move(key), std::move(value), y); + } + + static unique_ptr<Node> insert_node(unique_ptr<Node> Tree, KeyType key, ValueType value, uint32 y) { + if (Tree == nullptr) { + return create_node(std::move(key), std::move(value), y); + } + if (Tree->y_ < y) { + auto P = split_node(std::move(Tree), key); + auto T = create_node(std::move(key), std::move(value), y); + T->left_ = std::move(P.first); + T->right_ = std::move(P.second); + T->relax(); + return T; + } + if (Compare()(key, Tree->key_)) { + Tree->left_ = insert_node(std::move(Tree->left_), std::move(key), std::move(value), y); + } else if (Compare()(Tree->key_, key)) { + Tree->right_ = insert_node(std::move(Tree->right_), std::move(key), std::move(value), y); + } else { + // ?? assert + } + Tree->relax(); + return Tree; + } + + static unique_ptr<Node> remove_node(unique_ptr<Node> Tree, const KeyType &key) { + if (Tree == nullptr) { + // ?? assert + return nullptr; + } + if (Compare()(key, Tree->key_)) { + Tree->left_ = remove_node(std::move(Tree->left_), key); + } else if (Compare()(Tree->key_, key)) { + Tree->right_ = remove_node(std::move(Tree->right_), key); + } else { + Tree = merge_node(std::move(Tree->left_), std::move(Tree->right_)); + } + if (Tree != nullptr) { + Tree->relax(); + } + return Tree; + } + + static ValueType *get_node(unique_ptr<Node> &Tree, const KeyType &key) { + if (Tree == nullptr) { + return nullptr; + } + if (Compare()(key, Tree->key_)) { + return get_node(Tree->left_, key); + } else if (Compare()(Tree->key_, key)) { + return get_node(Tree->right_, key); + } else { + return &Tree->value_; + } + } + + static ValueType *get_node_by_idx(unique_ptr<Node> &Tree, size_t idx) { + CHECK(Tree != nullptr); + auto s = (Tree->left_ != nullptr) ? Tree->left_->size_ : 0; + if (idx < s) { + return get_node_by_idx(Tree->left_, idx); + } else if (idx == s) { + return &Tree->value_; + } else { + return get_node_by_idx(Tree->right_, idx - s - 1); + } + } + + static const ValueType *get_node(const unique_ptr<Node> &Tree, const KeyType &key) { + if (Tree == nullptr) { + return nullptr; + } + if (Compare()(key, Tree->key_)) { + return get_node(Tree->left_, key); + } else if (Compare()(Tree->key_, key)) { + return get_node(Tree->right_, key); + } else { + return &Tree->value_; + } + } + + static const ValueType *get_node_by_idx(const unique_ptr<Node> &Tree, size_t idx) { + CHECK(Tree != nullptr); + auto s = (Tree->left_ != nullptr) ? Tree->left_->size_ : 0; + if (idx < s) { + return get_node_by_idx(Tree->left_, idx); + } else if (idx == s) { + return &Tree->value_; + } else { + return get_node_by_idx(Tree->right_, idx - s - 1); + } + } + + static std::pair<unique_ptr<Node>, unique_ptr<Node>> split_node(unique_ptr<Node> Tree, const KeyType &key) { + if (Tree == nullptr) { + return {nullptr, nullptr}; + } + if (Compare()(key, Tree->key_)) { + auto P = split_node(std::move(Tree->left_), key); + Tree->left_ = std::move(P.second); + Tree->relax(); + P.second = std::move(Tree); + return P; + } else { + auto P = split_node(std::move(Tree->right_), key); + Tree->right_ = std::move(P.first); + Tree->relax(); + P.first = std::move(Tree); + return P; + } + } + + static unique_ptr<Node> merge_node(unique_ptr<Node> left, unique_ptr<Node> right) { + if (left == nullptr) { + return right; + } + if (right == nullptr) { + return left; + } + if (left->y_ < right->y_) { + right->left_ = merge_node(std::move(left), std::move(right->left_)); + right->relax(); + return right; + } else { + left->right_ = merge_node(std::move(left->right_), std::move(right)); + left->relax(); + return left; + } + } + + public: + size_t size() const { + if (root_ == nullptr) { + return 0; + } else { + return root_->size_; + } + } + void insert(KeyType key, ValueType value) { + root_ = insert_node(std::move(root_), std::move(key), std::move(value), Random::fast_uint32()); + } + void remove(const KeyType &key) { + root_ = remove_node(std::move(root_), key); + } + void reset() { + root_ = nullptr; + } + ValueType *get(const KeyType &key) { + return get_node(root_, key); + } + ValueType *get_random() { + if (size() == 0) { + return nullptr; + } else { + return get_node_by_idx(root_, Random::fast_uint32() % size()); + } + } + const ValueType *get(const KeyType &key) const { + return get_node(root_, key); + } + const ValueType *get_random() const { + if (size() == 0) { + return nullptr; + } else { + return get_node_by_idx(root_, Random::fast_uint32() % size()); + } + } + bool exists(const KeyType &key) const { + return get_node(root_, key) != nullptr; + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Destructor.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Destructor.h new file mode 100644 index 0000000000..ced4a8eca8 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Destructor.h @@ -0,0 +1,52 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#include <memory> +#include <utility> + +namespace td { + +class Destructor { + public: + Destructor() = default; + Destructor(const Destructor &other) = delete; + Destructor &operator=(const Destructor &other) = delete; + Destructor(Destructor &&other) = default; + Destructor &operator=(Destructor &&other) = default; + virtual ~Destructor() = default; +}; + +template <class F> +class LambdaDestructor final : public Destructor { + public: + explicit LambdaDestructor(F &&f) : f_(std::move(f)) { + } + LambdaDestructor(const LambdaDestructor &other) = delete; + LambdaDestructor &operator=(const LambdaDestructor &other) = delete; + LambdaDestructor(LambdaDestructor &&other) = default; + LambdaDestructor &operator=(LambdaDestructor &&other) = default; + ~LambdaDestructor() final { + f_(); + } + + private: + F f_; +}; + +template <class F> +auto create_destructor(F &&f) { + return make_unique<LambdaDestructor<F>>(std::forward<F>(f)); +} +template <class F> +auto create_shared_destructor(F &&f) { + return std::make_shared<LambdaDestructor<F>>(std::forward<F>(f)); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h index ca7c0493ff..367bc9fa8c 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Enumerator.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,9 +7,9 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/logging.h" -#include "td/utils/misc.h" +#include "td/utils/WaitFreeVector.h" +#include <limits> #include <map> #include <tuple> @@ -21,7 +21,8 @@ class Enumerator { using Key = int32; Key add(ValueT v) { - int32 next_id = narrow_cast<int32>(arr_.size() + 1); + CHECK(arr_.size() < static_cast<size_t>(std::numeric_limits<int32>::max() - 1)); + auto next_id = static_cast<int32>(arr_.size() + 1); bool was_inserted; decltype(map_.begin()) it; std::tie(it, was_inserted) = map_.emplace(std::move(v), next_id); @@ -37,9 +38,18 @@ class Enumerator { return *arr_[pos]; } + size_t size() const { + CHECK(map_.size() == arr_.size()); + return arr_.size(); + } + + bool empty() const { + return size() == 0; + } + private: std::map<ValueT, int32> map_; - std::vector<const ValueT *> arr_; + WaitFreeVector<const ValueT *> arr_; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/EpochBasedMemoryReclamation.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/EpochBasedMemoryReclamation.h new file mode 100644 index 0000000000..a11d307672 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/EpochBasedMemoryReclamation.h @@ -0,0 +1,201 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/port/sleep.h" + +#include <atomic> +#include <memory> + +namespace td { + +template <class T> +class EpochBasedMemoryReclamation { + public: + EpochBasedMemoryReclamation(const EpochBasedMemoryReclamation &other) = delete; + EpochBasedMemoryReclamation &operator=(const EpochBasedMemoryReclamation &other) = delete; + EpochBasedMemoryReclamation(EpochBasedMemoryReclamation &&other) = delete; + EpochBasedMemoryReclamation &operator=(EpochBasedMemoryReclamation &&other) = delete; + ~EpochBasedMemoryReclamation() = default; + + class Locker { + public: + Locker(size_t thread_id, EpochBasedMemoryReclamation *ebmr) : thread_id_(thread_id), ebmr_(ebmr) { + } + Locker(const Locker &other) = delete; + Locker &operator=(const Locker &other) = delete; + Locker(Locker &&other) = default; + Locker &operator=(Locker &&other) = delete; + + ~Locker() { + if (ebmr_) { + retire_sync(); + unlock(); + (void)ebmr_.release(); + } + } + void lock() { + DCHECK(ebmr_); + ebmr_->lock(thread_id_); + } + void unlock() { + DCHECK(ebmr_); + ebmr_->unlock(thread_id_); + } + + void retire_sync() { + ebmr_->retire_sync(thread_id_); + } + + void retire() { + ebmr_->retire(thread_id_); + } + + void retire(T *ptr) { + ebmr_->retire(thread_id_, ptr); + } + + private: + size_t thread_id_; + struct Never { + template <class S> + void operator()(S *) const { + UNREACHABLE(); + } + }; + std::unique_ptr<EpochBasedMemoryReclamation, Never> ebmr_; + }; + + explicit EpochBasedMemoryReclamation(size_t threads_n) : threads_(threads_n) { + } + + Locker get_locker(size_t thread_id) { + return Locker{thread_id, this}; + } + + size_t to_delete_size_unsafe() const { + size_t res = 0; + for (auto &thread_data : threads_) { + // LOG(ERROR) << "---" << thread_data.epoch.load() / 2; + for (size_t i = 0; i < MAX_BAGS; i++) { + res += thread_data.to_delete[i].size(); + // LOG(ERROR) << thread_data.to_delete[i].size(); + } + } + return res; + } + + private: + static constexpr size_t MAX_BAGS = 3; + struct ThreadData { + std::atomic<int64> epoch{1}; + char pad[TD_CONCURRENCY_PAD - sizeof(std::atomic<int64>)]; + + size_t to_skip{0}; + size_t checked_thread_i{0}; + size_t bag_i{0}; + std::vector<unique_ptr<T>> to_delete[MAX_BAGS]; + char pad2[TD_CONCURRENCY_PAD - sizeof(std::vector<unique_ptr<T>>) * MAX_BAGS]; + + void rotate_bags() { + bag_i = (bag_i + 1) % MAX_BAGS; + to_delete[bag_i].clear(); + } + + void set_epoch(int64 new_epoch) { + //LOG(ERROR) << new_epoch; + if (epoch.load(std::memory_order_relaxed) / 2 != new_epoch) { + checked_thread_i = 0; + to_skip = 0; + rotate_bags(); + } + epoch = new_epoch * 2; + } + + void idle() { + epoch.store(epoch.load(std::memory_order_relaxed) | 1); + } + + size_t undeleted() const { + size_t res = 0; + for (size_t i = 0; i < MAX_BAGS; i++) { + res += to_delete[i].size(); + } + return res; + } + }; + std::vector<ThreadData> threads_; + char pad[TD_CONCURRENCY_PAD - sizeof(std::vector<ThreadData>)]; + + std::atomic<int64> epoch_{1}; + char pad2[TD_CONCURRENCY_PAD - sizeof(std::atomic<int64>)]; + + void lock(size_t thread_id) { + auto &data = threads_[thread_id]; + auto epoch = epoch_.load(); + data.set_epoch(epoch); + + if (data.to_skip == 0) { + data.to_skip = 30; + step_check(data); + } else { + data.to_skip--; + } + } + + void unlock(size_t thread_id) { + //LOG(ERROR) << "UNLOCK"; + auto &data = threads_[thread_id]; + data.idle(); + } + + bool step_check(ThreadData &data) { + auto epoch = data.epoch.load(std::memory_order_relaxed) / 2; + auto checked_thread_epoch = threads_[data.checked_thread_i].epoch.load(); + if (checked_thread_epoch % 2 == 1 || checked_thread_epoch / 2 == epoch) { + data.checked_thread_i++; + if (data.checked_thread_i == threads_.size()) { + if (epoch_.compare_exchange_strong(epoch, epoch + 1)) { + data.set_epoch(epoch + 1); + } else { + data.set_epoch(epoch); + } + } + return true; + } + return false; + } + + void retire_sync(size_t thread_id) { + auto &data = threads_[thread_id]; + + while (true) { + retire(thread_id); + data.idle(); + if (data.undeleted() == 0) { + break; + } + usleep_for(1000); + } + } + + void retire(size_t thread_id) { + auto &data = threads_[thread_id]; + data.set_epoch(epoch_.load()); + while (step_check(data) && data.undeleted() != 0) { + } + } + + void retire(size_t thread_id, T *ptr) { + auto &data = threads_[thread_id]; + data.to_delete[data.bag_i].push_back(unique_ptr<T>{ptr}); + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.cpp new file mode 100644 index 0000000000..3a410bd0d5 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.cpp @@ -0,0 +1,20 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/ExitGuard.h" + +#include "td/utils/logging.h" + +namespace td { + +std::atomic<bool> ExitGuard::is_exited_{false}; + +ExitGuard::~ExitGuard() { + is_exited_.store(true, std::memory_order_relaxed); + set_verbosity_level(VERBOSITY_NAME(FATAL)); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.h new file mode 100644 index 0000000000..dd721fb5b9 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ExitGuard.h @@ -0,0 +1,30 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include <atomic> + +namespace td { + +class ExitGuard { + public: + ExitGuard() = default; + ExitGuard(ExitGuard &&) = delete; + ExitGuard &operator=(ExitGuard &&) = delete; + ExitGuard(const ExitGuard &) = delete; + ExitGuard &operator=(const ExitGuard &) = delete; + ~ExitGuard(); + + static bool is_exited() { + return is_exited_.load(std::memory_order_relaxed); + } + + private: + static std::atomic<bool> is_exited_; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp index e3c84f1713..a16731442a 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,85 +8,133 @@ #include "td/utils/common.h" #include "td/utils/logging.h" -#include "td/utils/port/Fd.h" #include "td/utils/port/FileFd.h" #include "td/utils/port/path.h" +#include "td/utils/port/StdStreams.h" +#include "td/utils/port/thread_local.h" #include "td/utils/Slice.h" - -#include <limits> +#include "td/utils/SliceBuilder.h" +#include "td/utils/Time.h" namespace td { -bool FileLog::init(string path, int64 rotate_threshold) { +Status FileLog::init(string path, int64 rotate_threshold, bool redirect_stderr) { + if (path.empty()) { + return Status::Error("Log file path must be non-empty"); + } if (path == path_) { set_rotate_threshold(rotate_threshold); - return true; + return Status::OK(); } - auto r_fd = FileFd::open(path, FileFd::Create | FileFd::Write | FileFd::Append); - if (r_fd.is_error()) { - LOG(ERROR) << "Can't open log: " << r_fd.error(); - return false; - } + TRY_RESULT(fd, FileFd::open(path, FileFd::Create | FileFd::Write | FileFd::Append)); fd_.close(); - fd_ = r_fd.move_as_ok(); - Fd::duplicate(fd_.get_fd(), Fd::Stderr()).ignore(); + fd_ = std::move(fd); + if (!Stderr().empty() && redirect_stderr) { + fd_.get_native_fd().duplicate(Stderr().get_native_fd()).ignore(); + } - path_ = std::move(path); - size_ = fd_.get_size(); + auto r_path = realpath(path, true); + if (r_path.is_error()) { + path_ = std::move(path); + } else { + path_ = r_path.move_as_ok(); + } + TRY_RESULT_ASSIGN(size_, fd_.get_size()); rotate_threshold_ = rotate_threshold; - return true; + redirect_stderr_ = redirect_stderr; + return Status::OK(); +} + +Slice FileLog::get_path() const { + return path_; +} + +vector<string> FileLog::get_file_paths() { + vector<string> result; + if (!path_.empty()) { + result.push_back(path_); + result.push_back(PSTRING() << path_ << ".old"); + } + return result; } void FileLog::set_rotate_threshold(int64 rotate_threshold) { rotate_threshold_ = rotate_threshold; } -void FileLog::append(CSlice cslice, int log_level) { - Slice slice = cslice; +int64 FileLog::get_rotate_threshold() const { + return rotate_threshold_; +} + +bool FileLog::get_redirect_stderr() const { + return redirect_stderr_; +} + +void FileLog::do_append(int log_level, CSlice slice) { + auto start_time = Time::now(); + if (size_ > rotate_threshold_ || want_rotate_.load(std::memory_order_relaxed)) { + auto status = rename(path_, PSLICE() << path_ << ".old"); + if (status.is_error()) { + process_fatal_error(PSLICE() << status << " in " << __FILE__ << " at " << __LINE__ << '\n'); + } + do_after_rotation(); + } while (!slice.empty()) { + if (redirect_stderr_) { + while (has_log_guard()) { + // spin + } + } auto r_size = fd_.write(slice); if (r_size.is_error()) { - process_fatal_error(r_size.error().message()); + process_fatal_error(PSLICE() << r_size.error() << " in " << __FILE__ << " at " << __LINE__ << '\n'); } auto written = r_size.ok(); size_ += static_cast<int64>(written); slice.remove_prefix(written); } - if (log_level == VERBOSITY_NAME(FATAL)) { - process_fatal_error(cslice); - } - - if (size_ > rotate_threshold_) { - auto status = rename(path_, path_ + ".old"); - if (status.is_error()) { - process_fatal_error(status.message()); - } - do_rotate(); + auto total_time = Time::now() - start_time; + if (total_time >= 0.1 && log_level >= 1) { + auto thread_id = get_thread_id(); + auto r_size = fd_.write(PSLICE() << "[ 1][t" << (0 <= thread_id && thread_id < 10 ? " " : "") << thread_id + << "] !!! Previous logging took " << total_time << " seconds !!!\n"); + r_size.ignore(); } } -void FileLog::rotate() { +void FileLog::after_rotation() { if (path_.empty()) { return; } - do_rotate(); + do_after_rotation(); +} + +void FileLog::lazy_rotate() { + want_rotate_ = true; } -void FileLog::do_rotate() { - auto current_verbosity_level = GET_VERBOSITY_LEVEL(); - SET_VERBOSITY_LEVEL(std::numeric_limits<int>::min()); // to ensure that nothing will be printed to the closed log +void FileLog::do_after_rotation() { + want_rotate_ = false; + ScopedDisableLog disable_log; // to ensure that nothing will be printed to the closed log CHECK(!path_.empty()); fd_.close(); - auto r_fd = FileFd::open(path_, FileFd::Create | FileFd::Truncate | FileFd::Write); + auto r_fd = FileFd::open(path_, FileFd::Create | FileFd::Write | FileFd::Append); if (r_fd.is_error()) { - process_fatal_error(r_fd.error().message()); + process_fatal_error(PSLICE() << r_fd.error() << " in " << __FILE__ << " at " << __LINE__ << '\n'); } fd_ = r_fd.move_as_ok(); - Fd::duplicate(fd_.get_fd(), Fd::Stderr()).ignore(); + if (!Stderr().empty() && redirect_stderr_) { + fd_.get_native_fd().duplicate(Stderr().get_native_fd()).ignore(); + } size_ = 0; - SET_VERBOSITY_LEVEL(current_verbosity_level); +} + +Result<unique_ptr<LogInterface>> FileLog::create(string path, int64 rotate_threshold, bool redirect_stderr) { + auto l = make_unique<FileLog>(); + TRY_STATUS(l->init(std::move(path), rotate_threshold, redirect_stderr)); + return std::move(l); } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h index 12e9d1479a..ad4ec5eb02 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FileLog.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,28 +10,45 @@ #include "td/utils/logging.h" #include "td/utils/port/FileFd.h" #include "td/utils/Slice.h" +#include "td/utils/Status.h" + +#include <atomic> namespace td { -class FileLog : public LogInterface { +class FileLog final : public LogInterface { static constexpr int64 DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20); public: - bool init(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD); + static Result<unique_ptr<LogInterface>> create(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD, + bool redirect_stderr = true); + Status init(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD, bool redirect_stderr = true); + + Slice get_path() const; + + vector<string> get_file_paths() final; void set_rotate_threshold(int64 rotate_threshold); - void append(CSlice cslice, int log_level) override; + int64 get_rotate_threshold() const; - void rotate() override; + bool get_redirect_stderr() const; + + void after_rotation() final; + + void lazy_rotate(); private: FileFd fd_; string path_; - int64 size_; - int64 rotate_threshold_; + int64 size_ = 0; + int64 rotate_threshold_ = 0; + bool redirect_stderr_ = false; + std::atomic<bool> want_rotate_{false}; + + void do_append(int log_level, CSlice slice) final; - void do_rotate(); + void do_after_rotation(); }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMap.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMap.h new file mode 100644 index 0000000000..51aa6d3e4c --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMap.h @@ -0,0 +1,24 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +//#include "td/utils/FlatHashMapChunks.h" +#include "td/utils/FlatHashTable.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/MapNode.h" + +#include <functional> +//#include <unordered_map> + +namespace td { + +template <class KeyT, class ValueT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>> +using FlatHashMap = FlatHashTable<MapNode<KeyT, ValueT>, HashT, EqT>; +//using FlatHashMap = FlatHashMapChunks<KeyT, ValueT, HashT, EqT>; +//using FlatHashMap = std::unordered_map<KeyT, ValueT, HashT, EqT>; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMapChunks.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMapChunks.h new file mode 100644 index 0000000000..6df4842bc1 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashMapChunks.h @@ -0,0 +1,575 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/bits.h" +#include "td/utils/common.h" +#include "td/utils/fixed_vector.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/MapNode.h" +#include "td/utils/SetNode.h" + +#include <cstddef> +#include <functional> +#include <initializer_list> +#include <iterator> +#include <limits> +#include <utility> + +#if defined(__SSE2__) || (TD_MSVC && (defined(_M_X64) || (defined(_M_IX86) && _M_IX86_FP >= 2))) +#define TD_SSE2 1 +#endif + +#ifdef __aarch64__ +#include <arm_neon.h> +#endif + +#if TD_SSE2 +#include <emmintrin.h> +#endif + +namespace td { +template <int shift> +struct MaskIterator { + uint64 mask; + explicit operator bool() const noexcept { + return mask != 0; + } + int pos() const { + return count_trailing_zeroes64(mask) / shift; + } + void next() { + mask &= mask - 1; + } + + // For foreach + bool operator!=(MaskIterator &other) const { + return mask != other.mask; + } + auto operator*() const { + return pos(); + } + void operator++() { + next(); + } + auto begin() { + return *this; + } + auto end() { + return MaskIterator{0u}; + } +}; + +struct MaskPortable { + static MaskIterator<1> equal_mask(uint8 *bytes, uint8 needle) { + uint64 res = 0; + for (int i = 0; i < 16; i++) { + res |= (bytes[i] == needle) << i; + } + return {res & ((1u << 14) - 1)}; + } +}; + +#ifdef __aarch64__ +struct MaskNeonFolly { + static MaskIterator<4> equal_mask(uint8 *bytes, uint8 needle) { + uint8x16_t input_mask = vld1q_u8(bytes); + auto needle_mask = vdupq_n_u8(needle); + auto eq_mask = vceqq_u8(input_mask, needle_mask); + // get info from every byte into the bottom half of every uint16 + // by shifting right 4, then round to get it into a 64-bit vector + uint8x8_t shifted_eq_mask = vshrn_n_u16(vreinterpretq_u16_u8(eq_mask), 4); + uint64 mask = vget_lane_u64(vreinterpret_u64_u8(shifted_eq_mask), 0); + return {mask & 0x11111111111111}; + } +}; + +struct MaskNeon { + static MaskIterator<1> equal_mask(uint8 *bytes, uint8 needle) { + uint8x16_t input_mask = vld1q_u8(bytes); + auto needle_mask = vdupq_n_u8(needle); + auto eq_mask = vceqq_u8(input_mask, needle_mask); + uint16x8_t MASK = vdupq_n_u16(0x180); + uint16x8_t a_masked = vandq_u16(vreinterpretq_u16_u8(eq_mask), MASK); + const int16 __attribute__((aligned(16))) SHIFT_ARR[8] = {-7, -5, -3, -1, 1, 3, 5, 7}; + int16x8_t SHIFT = vld1q_s16(SHIFT_ARR); + uint16x8_t a_shifted = vshlq_u16(a_masked, SHIFT); + return {vaddvq_u16(a_shifted) & ((1u << 14) - 1)}; + } +}; +#elif TD_SSE2 +struct MaskSse2 { + static MaskIterator<1> equal_mask(uint8 *bytes, uint8 needle) { + auto input_mask = _mm_loadu_si128(reinterpret_cast<const __m128i *>(bytes)); + auto needle_mask = _mm_set1_epi8(needle); + auto match_mask = _mm_cmpeq_epi8(needle_mask, input_mask); + return {static_cast<uint32>(_mm_movemask_epi8(match_mask)) & ((1u << 14) - 1)}; + } +}; +#endif + +#ifdef __aarch64__ +using MaskHelper = MaskNeonFolly; +#elif TD_SSE2 +using MaskHelper = MaskSse2; +#else +using MaskHelper = MaskPortable; +#endif + +template <class NodeT, class HashT, class EqT> +class FlatHashTableChunks { + public: + using Self = FlatHashTableChunks<NodeT, HashT, EqT>; + using Node = NodeT; + using NodeIterator = typename fixed_vector<Node>::iterator; + using ConstNodeIterator = typename fixed_vector<Node>::const_iterator; + + using KeyT = typename Node::public_key_type; + using key_type = typename Node::public_key_type; + using value_type = typename Node::public_type; + + struct Iterator { + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = FlatHashTableChunks::value_type; + using pointer = value_type *; + using reference = value_type &; + + friend class FlatHashTableChunks; + Iterator &operator++() { + do { + ++it_; + } while (it_ != map_->nodes_.end() && it_->empty()); + return *this; + } + Iterator &operator--() { + do { + --it_; + } while (it_->empty()); + return *this; + } + reference operator*() { + return it_->get_public(); + } + pointer operator->() { + return &it_->get_public(); + } + bool operator==(const Iterator &other) const { + DCHECK(map_ == other.map_); + return it_ == other.it_; + } + bool operator!=(const Iterator &other) const { + DCHECK(map_ == other.map_); + return it_ != other.it_; + } + + Iterator() = default; + Iterator(NodeIterator it, Self *map) : it_(std::move(it)), map_(map) { + } + + private: + NodeIterator it_; + Self *map_; + }; + + struct ConstIterator { + using iterator_category = std::bidirectional_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = FlatHashTableChunks::value_type; + using pointer = const value_type *; + using reference = const value_type &; + + friend class FlatHashTableChunks; + ConstIterator &operator++() { + ++it_; + return *this; + } + ConstIterator &operator--() { + --it_; + return *this; + } + reference operator*() { + return *it_; + } + pointer operator->() { + return &*it_; + } + bool operator==(const ConstIterator &other) const { + return it_ == other.it_; + } + bool operator!=(const ConstIterator &other) const { + return it_ != other.it_; + } + + ConstIterator() = default; + ConstIterator(Iterator it) : it_(std::move(it)) { + } + + private: + Iterator it_; + }; + using iterator = Iterator; + using const_iterator = ConstIterator; + + FlatHashTableChunks() = default; + FlatHashTableChunks(const FlatHashTableChunks &other) { + assign(other); + } + FlatHashTableChunks &operator=(const FlatHashTableChunks &other) { + clear(); + assign(other); + return *this; + } + + FlatHashTableChunks(std::initializer_list<Node> nodes) { + reserve(nodes.size()); + for (auto &new_node : nodes) { + CHECK(!new_node.empty()); + if (count(new_node.key()) > 0) { + continue; + } + Node node; + node.copy_from(new_node); + emplace_node(std::move(node)); + } + } + + FlatHashTableChunks(FlatHashTableChunks &&other) noexcept { + swap(other); + } + FlatHashTableChunks &operator=(FlatHashTableChunks &&other) noexcept { + swap(other); + return *this; + } + void swap(FlatHashTableChunks &other) noexcept { + nodes_.swap(other.nodes_); + chunks_.swap(other.chunks_); + std::swap(used_nodes_, other.used_nodes_); + } + ~FlatHashTableChunks() = default; + + size_t bucket_count() const { + return nodes_.size(); + } + + Iterator find(const KeyT &key) { + if (empty() || is_hash_table_key_empty(key)) { + return end(); + } + const auto hash = calc_hash(key); + auto chunk_it = get_chunk_it(hash.chunk_i); + while (true) { + auto chunk_i = chunk_it.pos(); + auto chunk_begin = nodes_.begin() + chunk_i * Chunk::CHUNK_SIZE; + //__builtin_prefetch(chunk_begin); + auto &chunk = chunks_[chunk_i]; + auto mask_it = MaskHelper::equal_mask(chunk.ctrl, hash.small_hash); + for (auto pos : mask_it) { + auto it = chunk_begin + pos; + if (likely(EqT()(it->key(), key))) { + return Iterator{it, this}; + } + } + if (chunk.skipped_cnt == 0) { + break; + } + chunk_it.next(); + } + return end(); + } + + ConstIterator find(const KeyT &key) const { + return ConstIterator(const_cast<Self *>(this)->find(key)); + } + + size_t size() const { + return used_nodes_; + } + + bool empty() const { + return size() == 0; + } + + Iterator begin() { + if (empty()) { + return end(); + } + auto it = nodes_.begin(); + while (it->empty()) { + ++it; + } + return Iterator(it, this); + } + Iterator end() { + return Iterator(nodes_.end(), this); + } + + ConstIterator begin() const { + return ConstIterator(const_cast<Self *>(this)->begin()); + } + ConstIterator end() const { + return ConstIterator(const_cast<Self *>(this)->end()); + } + + void reserve(size_t size) { + //size_t want_size = normalize(size * 5 / 3 + 1); + size_t want_size = normalize(size * 14 / 12 + 1); + // size_t want_size = size * 2; + if (want_size > nodes_.size()) { + resize(want_size); + } + } + + template <class... ArgsT> + std::pair<Iterator, bool> emplace(KeyT key, ArgsT &&...args) { + CHECK(!is_hash_table_key_empty(key)); + auto it = find(key); + if (it != end()) { + return {it, false}; + } + try_grow(); + + auto hash = calc_hash(key); + auto chunk_it = get_chunk_it(hash.chunk_i); + while (true) { + auto chunk_i = chunk_it.pos(); + auto &chunk = chunks_[chunk_i]; + auto mask_it = MaskHelper::equal_mask(chunk.ctrl, 0); + if (mask_it) { + auto shift = mask_it.pos(); + DCHECK(chunk.ctrl[shift] == 0); + auto node_it = nodes_.begin() + shift + chunk_i * Chunk::CHUNK_SIZE; + DCHECK(node_it->empty()); + node_it->emplace(std::move(key), std::forward<ArgsT>(args)...); + DCHECK(!node_it->empty()); + chunk.ctrl[shift] = hash.small_hash; + used_nodes_++; + return {{node_it, this}, true}; + } + CHECK(chunk.skipped_cnt != std::numeric_limits<uint16>::max()); + chunk.skipped_cnt++; + chunk_it.next(); + } + } + + std::pair<Iterator, bool> insert(KeyT key) { + return emplace(std::move(key)); + } + + template <class ItT> + void insert(ItT begin, ItT end) { + for (; begin != end; ++begin) { + emplace(*begin); + } + } + + template <class T = typename Node::second_type> + T &operator[](const KeyT &key) { + return emplace(key).first->second; + } + + size_t erase(const KeyT &key) { + auto it = find(key); + if (it == end()) { + return 0; + } + erase(it); + try_shrink(); + return 1; + } + + size_t count(const KeyT &key) const { + return find(key) != end(); + } + + void clear() { + used_nodes_ = 0; + nodes_ = {}; + chunks_ = {}; + } + + void erase(Iterator it) { + DCHECK(it != end()); + DCHECK(!it.it_->empty()); + erase_node(it.it_); + } + + template <class F> + void remove_if(F &&f) { + for (auto it = nodes_.begin(), end = nodes_.end(); it != end; ++it) { + if (!it->empty() && f(it->get_public())) { + erase_node(it); + } + } + try_shrink(); + } + + private: + struct Chunk { + static constexpr int CHUNK_SIZE = 14; + static constexpr int MASK = (1 << CHUNK_SIZE) - 1; + // 0x0 - empty + uint8 ctrl[CHUNK_SIZE] = {}; + uint16 skipped_cnt{0}; + }; + fixed_vector<Node> nodes_; + fixed_vector<Chunk> chunks_; + size_t used_nodes_{}; + + void assign(const FlatHashTableChunks &other) { + reserve(other.size()); + for (const auto &new_node : other) { + Node node; + node.copy_from(new_node); + emplace_node(std::move(node)); + } + } + + void try_grow() { + if (should_grow(used_nodes_ + 1, nodes_.size())) { + grow(); + } + } + static bool should_grow(size_t used_count, size_t bucket_count) { + return used_count * 14 > bucket_count * 12; + } + void try_shrink() { + if (should_shrink(used_nodes_, nodes_.size())) { + shrink(); + } + } + static bool should_shrink(size_t used_count, size_t bucket_count) { + return used_count * 10 < bucket_count; + } + + static size_t normalize(size_t size) { + auto x = (size / Chunk::CHUNK_SIZE) | 1; + auto y = static_cast<size_t>(1) << (64 - count_leading_zeroes64(x)); + return y * Chunk::CHUNK_SIZE; + } + + void shrink() { + size_t want_size = normalize((used_nodes_ + 1) * 5 / 3 + 1); + resize(want_size); + } + + void grow() { + size_t want_size = normalize(2 * nodes_.size() - !nodes_.empty()); + resize(want_size); + } + + struct HashInfo { + size_t chunk_i; + uint8 small_hash; + }; + struct ChunkIt { + size_t chunk_i; + size_t chunk_mask; + size_t shift; + + size_t pos() const { + return chunk_i; + } + void next() { + DCHECK((chunk_mask & (chunk_mask + 1)) == 0); + shift++; + chunk_i += shift; + chunk_i &= chunk_mask; + } + }; + + ChunkIt get_chunk_it(size_t chunk_i) { + return ChunkIt{chunk_i, chunks_.size() - 1, 0}; + } + + HashInfo calc_hash(const KeyT &key) { + auto h = HashT()(key); + return {(h >> 8) % chunks_.size(), static_cast<uint8>(0x80 | h)}; + } + + void resize(size_t new_size) { + CHECK(new_size >= Chunk::CHUNK_SIZE); + fixed_vector<Node> old_nodes(new_size); + fixed_vector<Chunk> chunks(new_size / Chunk::CHUNK_SIZE); + old_nodes.swap(nodes_); + chunks_ = std::move(chunks); + used_nodes_ = 0; + + for (auto &node : old_nodes) { + if (node.empty()) { + continue; + } + emplace_node(std::move(node)); + } + } + + void emplace_node(Node &&node) { + DCHECK(!node.empty()); + auto hash = calc_hash(node.key()); + auto chunk_it = get_chunk_it(hash.chunk_i); + while (true) { + auto chunk_i = chunk_it.pos(); + auto &chunk = chunks_[chunk_i]; + auto mask_it = MaskHelper::equal_mask(chunk.ctrl, 0); + if (mask_it) { + auto shift = mask_it.pos(); + auto node_it = nodes_.begin() + shift + chunk_i * Chunk::CHUNK_SIZE; + DCHECK(node_it->empty()); + *node_it = std::move(node); + DCHECK(chunk.ctrl[shift] == 0); + chunk.ctrl[shift] = hash.small_hash; + DCHECK(chunk.ctrl[shift] != 0); + used_nodes_++; + break; + } + CHECK(chunk.skipped_cnt != std::numeric_limits<uint16>::max()); + chunk.skipped_cnt++; + chunk_it.next(); + } + } + + void next_bucket(size_t &bucket) const { + bucket++; + if (unlikely(bucket == nodes_.size())) { + bucket = 0; + } + } + + void erase_node(NodeIterator it) { + DCHECK(!it->empty()); + size_t empty_i = it - nodes_.begin(); + DCHECK(0 <= empty_i && empty_i < nodes_.size()); + auto empty_chunk_i = empty_i / Chunk::CHUNK_SIZE; + auto hash = calc_hash(it->key()); + auto chunk_it = get_chunk_it(hash.chunk_i); + while (true) { + auto chunk_i = chunk_it.pos(); + auto &chunk = chunks_[chunk_i]; + if (chunk_i == empty_chunk_i) { + chunk.ctrl[empty_i - empty_chunk_i * Chunk::CHUNK_SIZE] = 0; + break; + } + chunk.skipped_cnt--; + chunk_it.next(); + } + it->clear(); + used_nodes_--; + } +}; + +template <class KeyT, class ValueT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>> +using FlatHashMapChunks = FlatHashTableChunks<MapNode<KeyT, ValueT>, HashT, EqT>; + +template <class KeyT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>> +using FlatHashSetChunks = FlatHashTableChunks<SetNode<KeyT>, HashT, EqT>; + +template <class NodeT, class HashT, class EqT, class FuncT> +void table_remove_if(FlatHashTableChunks<NodeT, HashT, EqT> &table, FuncT &&func) { + table.remove_if(func); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashSet.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashSet.h new file mode 100644 index 0000000000..385485979a --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashSet.h @@ -0,0 +1,24 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +//#include "td/utils/FlatHashMapChunks.h" +#include "td/utils/FlatHashTable.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/SetNode.h" + +#include <functional> +//#include <unordered_set> + +namespace td { + +template <class KeyT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>> +using FlatHashSet = FlatHashTable<SetNode<KeyT>, HashT, EqT>; +//using FlatHashSet = FlatHashSetChunks<KeyT, HashT, EqT>; +//using FlatHashSet = std::unordered_set<KeyT, HashT, EqT>; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.cpp new file mode 100644 index 0000000000..abe7afa764 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.cpp @@ -0,0 +1,24 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/FlatHashTable.h" + +#include "td/utils/bits.h" +#include "td/utils/Random.h" + +namespace td { +namespace detail { + +uint32 normalize_flat_hash_table_size(uint32 size) { + return td::max(static_cast<uint32>(1) << (32 - count_leading_zeroes32(size)), static_cast<uint32>(8)); +} + +uint32 get_random_flat_hash_table_bucket(uint32 bucket_count_mask) { + return Random::fast_uint32() & bucket_count_mask; +} + +} // namespace detail +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.h new file mode 100644 index 0000000000..a312ad9533 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FlatHashTable.h @@ -0,0 +1,551 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" + +#include <cstddef> +#include <initializer_list> +#include <iterator> +#include <utility> + +namespace td { + +namespace detail { +uint32 normalize_flat_hash_table_size(uint32 size); +uint32 get_random_flat_hash_table_bucket(uint32 bucket_count_mask); +} // namespace detail + +template <class NodeT, class HashT, class EqT> +class FlatHashTable { + static constexpr uint32 INVALID_BUCKET = 0xFFFFFFFF; + + void allocate_nodes(uint32 size) { + DCHECK(size >= 8); + DCHECK((size & (size - 1)) == 0); + CHECK(size <= min(static_cast<uint32>(1) << 29, static_cast<uint32>(0x7FFFFFFF / sizeof(NodeT)))); + nodes_ = new NodeT[size]; + // used_node_count_ = 0; + bucket_count_mask_ = size - 1; + bucket_count_ = size; + begin_bucket_ = INVALID_BUCKET; + } + + static void clear_nodes(NodeT *nodes) { + delete[] nodes; + } + + public: + using KeyT = typename NodeT::public_key_type; + using key_type = typename NodeT::public_key_type; + using value_type = typename NodeT::public_type; + + // TODO use EndSentinel for end() after switching to C++17 + // struct EndSentinel {}; + + struct Iterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = FlatHashTable::value_type; + using pointer = value_type *; + using reference = value_type &; + + Iterator &operator++() { + DCHECK(it_ != nullptr); + do { + if (unlikely(++it_ == end_)) { + it_ = begin_; + } + if (unlikely(it_ == start_)) { + it_ = nullptr; + break; + } + } while (it_->empty()); + return *this; + } + reference operator*() { + return it_->get_public(); + } + const value_type &operator*() const { + return it_->get_public(); + } + pointer operator->() { + return &it_->get_public(); + } + const value_type *operator->() const { + return &it_->get_public(); + } + + NodeT *get() { + return it_; + } + + bool operator==(const Iterator &other) const { + DCHECK(other.it_ == nullptr); + return it_ == nullptr; + } + bool operator!=(const Iterator &other) const { + DCHECK(other.it_ == nullptr); + return it_ != nullptr; + } + + Iterator() = default; + Iterator(NodeT *it, NodeT *begin, NodeT *end) : it_(it), begin_(begin), start_(it), end_(end) { + } + + private: + NodeT *it_ = nullptr; + NodeT *begin_ = nullptr; + NodeT *start_ = nullptr; + NodeT *end_ = nullptr; + }; + + struct ConstIterator { + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = FlatHashTable::value_type; + using pointer = const value_type *; + using reference = const value_type &; + + ConstIterator &operator++() { + ++it_; + return *this; + } + reference operator*() const { + return *it_; + } + pointer operator->() const { + return &*it_; + } + bool operator==(const ConstIterator &other) const { + return it_ == other.it_; + } + bool operator!=(const ConstIterator &other) const { + return it_ != other.it_; + } + + ConstIterator() = default; + ConstIterator(Iterator it) : it_(std::move(it)) { + } + + private: + Iterator it_; + }; + using iterator = Iterator; + using const_iterator = ConstIterator; + + struct NodePointer { + value_type &operator*() { + return it_->get_public(); + } + const value_type &operator*() const { + return it_->get_public(); + } + value_type *operator->() { + return &it_->get_public(); + } + const value_type *operator->() const { + return &it_->get_public(); + } + + NodeT *get() { + return it_; + } + + bool operator==(const Iterator &) const { + return it_ == nullptr; + } + bool operator!=(const Iterator &) const { + return it_ != nullptr; + } + + explicit NodePointer(NodeT *it) : it_(it) { + } + + private: + NodeT *it_ = nullptr; + }; + + struct ConstNodePointer { + const value_type &operator*() const { + return it_->get_public(); + } + const value_type *operator->() const { + return &it_->get_public(); + } + + bool operator==(const ConstIterator &) const { + return it_ == nullptr; + } + bool operator!=(const ConstIterator &) const { + return it_ != nullptr; + } + + const NodeT *get() const { + return it_; + } + + explicit ConstNodePointer(const NodeT *it) : it_(it) { + } + + private: + const NodeT *it_ = nullptr; + }; + + FlatHashTable() = default; + FlatHashTable(const FlatHashTable &other) = delete; + FlatHashTable &operator=(const FlatHashTable &other) = delete; + + FlatHashTable(std::initializer_list<NodeT> nodes) { + if (nodes.size() == 0) { + return; + } + reserve(nodes.size()); + uint32 used_nodes = 0; + for (auto &new_node : nodes) { + CHECK(!new_node.empty()); + auto bucket = calc_bucket(new_node.key()); + while (true) { + auto &node = nodes_[bucket]; + if (node.empty()) { + node.copy_from(new_node); + used_nodes++; + break; + } + if (EqT()(node.key(), new_node.key())) { + break; + } + next_bucket(bucket); + } + } + used_node_count_ = used_nodes; + } + + FlatHashTable(FlatHashTable &&other) noexcept + : nodes_(other.nodes_) + , used_node_count_(other.used_node_count_) + , bucket_count_mask_(other.bucket_count_mask_) + , bucket_count_(other.bucket_count_) + , begin_bucket_(other.begin_bucket_) { + other.drop(); + } + void operator=(FlatHashTable &&other) noexcept { + clear(); + nodes_ = other.nodes_; + used_node_count_ = other.used_node_count_; + bucket_count_mask_ = other.bucket_count_mask_; + bucket_count_ = other.bucket_count_; + begin_bucket_ = other.begin_bucket_; + other.drop(); + } + ~FlatHashTable() { + clear_nodes(nodes_); + } + + void swap(FlatHashTable &other) noexcept { + std::swap(nodes_, other.nodes_); + std::swap(used_node_count_, other.used_node_count_); + std::swap(bucket_count_mask_, other.bucket_count_mask_); + std::swap(bucket_count_, other.bucket_count_); + std::swap(begin_bucket_, other.begin_bucket_); + } + + uint32 bucket_count() const { + return bucket_count_; + } + + NodePointer find(const KeyT &key) { + return NodePointer(find_impl(key)); + } + + ConstNodePointer find(const KeyT &key) const { + return ConstNodePointer(const_cast<FlatHashTable *>(this)->find_impl(key)); + } + + size_t size() const { + return used_node_count_; + } + + bool empty() const { + return used_node_count_ == 0; + } + + Iterator begin() { + return create_iterator(begin_impl()); + } + Iterator end() { + return Iterator(); + } + ConstIterator begin() const { + return ConstIterator(const_cast<FlatHashTable *>(this)->begin()); + } + ConstIterator end() const { + return ConstIterator(); + } + + void reserve(size_t size) { + if (size == 0) { + return; + } + CHECK(size <= (1u << 29)); + uint32 want_size = detail::normalize_flat_hash_table_size(static_cast<uint32>(size) * 5 / 3 + 1); + if (want_size > bucket_count()) { + resize(want_size); + } + } + + template <class... ArgsT> + std::pair<NodePointer, bool> emplace(KeyT key, ArgsT &&...args) { + CHECK(!is_hash_table_key_empty(key)); + if (unlikely(bucket_count_mask_ == 0)) { + CHECK(used_node_count_ == 0); + resize(8); + } + auto bucket = calc_bucket(key); + while (true) { + auto &node = nodes_[bucket]; + if (node.empty()) { + if (unlikely(used_node_count_ * 5 >= bucket_count_mask_ * 3)) { + resize(2 * bucket_count_); + CHECK(used_node_count_ * 5 < bucket_count_mask_ * 3); + return emplace(std::move(key), std::forward<ArgsT>(args)...); + } + invalidate_iterators(); + + node.emplace(std::move(key), std::forward<ArgsT>(args)...); + used_node_count_++; + return {NodePointer(&node), true}; + } + if (EqT()(node.key(), key)) { + return {NodePointer(&node), false}; + } + next_bucket(bucket); + } + } + + std::pair<NodePointer, bool> insert(KeyT key) { + return emplace(std::move(key)); + } + + template <class ItT> + void insert(ItT begin, ItT end) { + for (; begin != end; ++begin) { + emplace(*begin); + } + } + + template <class T = typename NodeT::second_type> + T &operator[](const KeyT &key) { + return emplace(key).first->second; + } + + size_t erase(const KeyT &key) { + auto *node = find_impl(key); + if (node == nullptr) { + return 0; + } + erase_node(node); + try_shrink(); + return 1; + } + + size_t count(const KeyT &key) const { + return const_cast<FlatHashTable *>(this)->find_impl(key) != nullptr; + } + + void clear() { + if (nodes_ != nullptr) { + clear_nodes(nodes_); + drop(); + } + } + + void erase(Iterator it) { + DCHECK(it != end()); + erase_node(it.get()); + try_shrink(); + } + + void erase(NodePointer it) { + DCHECK(it != end()); + erase_node(it.get()); + try_shrink(); + } + + template <class F> + void remove_if(F &&f) { + if (empty()) { + return; + } + + auto it = begin_impl(); + auto end = nodes_ + bucket_count(); + while (it != end && !it->empty()) { + ++it; + } + if (it == end) { + do { + --it; + } while (!it->empty()); + } + auto first_empty = it; + while (it != end) { + if (!it->empty() && f(it->get_public())) { + erase_node(it); + } else { + ++it; + } + } + for (it = nodes_; it != first_empty;) { + if (!it->empty() && f(it->get_public())) { + erase_node(it); + } else { + ++it; + } + } + try_shrink(); + } + + private: + NodeT *nodes_ = nullptr; + uint32 used_node_count_ = 0; + uint32 bucket_count_mask_ = 0; + uint32 bucket_count_ = 0; + uint32 begin_bucket_ = 0; + + void drop() { + nodes_ = nullptr; + used_node_count_ = 0; + bucket_count_mask_ = 0; + bucket_count_ = 0; + begin_bucket_ = 0; + } + + NodeT *begin_impl() { + if (empty()) { + return nullptr; + } + if (begin_bucket_ == INVALID_BUCKET) { + begin_bucket_ = detail::get_random_flat_hash_table_bucket(bucket_count_mask_); + while (nodes_[begin_bucket_].empty()) { + next_bucket(begin_bucket_); + } + } + return nodes_ + begin_bucket_; + } + + NodeT *find_impl(const KeyT &key) { + if (unlikely(nodes_ == nullptr) || is_hash_table_key_empty(key)) { + return nullptr; + } + auto bucket = calc_bucket(key); + while (true) { + auto &node = nodes_[bucket]; + if (node.empty()) { + return nullptr; + } + if (EqT()(node.key(), key)) { + return &node; + } + next_bucket(bucket); + } + } + + void try_shrink() { + DCHECK(nodes_ != nullptr); + if (unlikely(used_node_count_ * 10 < bucket_count_mask_ && bucket_count_mask_ > 7)) { + resize(detail::normalize_flat_hash_table_size((used_node_count_ + 1) * 5 / 3 + 1)); + } + invalidate_iterators(); + } + + uint32 calc_bucket(const KeyT &key) const { + return HashT()(key) & bucket_count_mask_; + } + + inline void next_bucket(uint32 &bucket) const { + bucket = (bucket + 1) & bucket_count_mask_; + } + + void resize(uint32 new_size) { + if (unlikely(nodes_ == nullptr)) { + allocate_nodes(new_size); + used_node_count_ = 0; + return; + } + + auto old_nodes = nodes_; + uint32 old_size = used_node_count_; + uint32 old_bucket_count = bucket_count_; + allocate_nodes(new_size); + used_node_count_ = old_size; + + auto old_nodes_end = old_nodes + old_bucket_count; + for (NodeT *old_node = old_nodes; old_node != old_nodes_end; ++old_node) { + if (old_node->empty()) { + continue; + } + auto bucket = calc_bucket(old_node->key()); + while (!nodes_[bucket].empty()) { + next_bucket(bucket); + } + nodes_[bucket] = std::move(*old_node); + } + clear_nodes(old_nodes); + } + + void erase_node(NodeT *it) { + DCHECK(nodes_ <= it && static_cast<size_t>(it - nodes_) < bucket_count()); + it->clear(); + used_node_count_--; + + const auto bucket_count = bucket_count_; + const auto *end = nodes_ + bucket_count; + for (auto *test_node = it + 1; test_node != end; test_node++) { + if (likely(test_node->empty())) { + return; + } + + auto want_node = nodes_ + calc_bucket(test_node->key()); + if (want_node <= it || want_node > test_node) { + *it = std::move(*test_node); + it = test_node; + } + } + + auto empty_i = static_cast<uint32>(it - nodes_); + auto empty_bucket = empty_i; + for (uint32 test_i = bucket_count;; test_i++) { + auto test_bucket = test_i - bucket_count_; + if (nodes_[test_bucket].empty()) { + return; + } + + auto want_i = calc_bucket(nodes_[test_bucket].key()); + if (want_i < empty_i) { + want_i += bucket_count; + } + + if (want_i <= empty_i || want_i > test_i) { + nodes_[empty_bucket] = std::move(nodes_[test_bucket]); + empty_i = test_i; + empty_bucket = test_bucket; + } + } + } + + Iterator create_iterator(NodeT *node) { + return Iterator(node, nodes_, nodes_ + bucket_count()); + } + + void invalidate_iterators() { + begin_bucket_ = INVALID_BUCKET; + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h index 9f047881aa..e5570f6d2a 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlFast.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,56 +7,81 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/TimedStat.h" namespace td { class FloodControlFast { public: - uint32 add_event(int32 now) { - for (auto &limit : limits_) { - limit.stat_.add_event(CounterStat::Event(), now); - if (limit.stat_.get_stat(now).count_ > limit.count_) { - wakeup_at_ = max(wakeup_at_, now + limit.duration_ * 2); - } + void add_event(double now) { + for (auto &bucket : buckets_) { + bucket.add_event(now); + wakeup_at_ = td::max(wakeup_at_, bucket.get_wakeup_at()); } - return wakeup_at_; } - uint32 get_wakeup_at() { + + double get_wakeup_at() const { return wakeup_at_; } - void add_limit(uint32 duration, int32 count) { - limits_.push_back({TimedStat<CounterStat>(duration, 0), duration, count}); + void add_limit(double duration, double count) { + buckets_.emplace_back(duration, count); } void clear_events() { - for (auto &limit : limits_) { - limit.stat_.clear_events(); + for (auto &bucket : buckets_) { + bucket.clear_events(); } wakeup_at_ = 0; } private: - class CounterStat { + class FloodControlBucket { public: - struct Event {}; - int32 count_ = 0; - void on_event(Event e) { - count_++; + FloodControlBucket(double duration, double count) + : max_capacity_(count - 1), speed_(count / duration), volume_(max_capacity_) { } - void clear() { - count_ = 0; + + void add_event(double now, double size = 1) { + CHECK(now >= wakeup_at_); + update_volume(now); + if (volume_ >= size) { + volume_ -= size; + return; + } + size -= volume_; + volume_ = 0; + wakeup_at_ = volume_at_ + size / speed_; + volume_at_ = wakeup_at_; } - }; - uint32 wakeup_at_ = 0; - struct Limit { - TimedStat<CounterStat> stat_; - uint32 duration_; - int32 count_; + double get_wakeup_at() const { + return wakeup_at_; + } + + void clear_events() { + volume_ = max_capacity_; + volume_at_ = 0; + wakeup_at_ = 0; + } + + private: + const double max_capacity_{1}; + const double speed_{1}; + double volume_{1}; + + double volume_at_{0}; + double wakeup_at_{0}; + + void update_volume(double now) { + CHECK(now >= volume_at_); + auto passed = now - volume_at_; + volume_ = td::min(volume_ + passed * speed_, max_capacity_); + volume_at_ = now; + } }; - std::vector<Limit> limits_; + + double wakeup_at_ = 0; + vector<FloodControlBucket> buckets_; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.cpp new file mode 100644 index 0000000000..98709c6b96 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.cpp @@ -0,0 +1,32 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/FloodControlGlobal.h" + +namespace td { + +FloodControlGlobal::FloodControlGlobal(uint64 limit) : limit_(limit) { +} + +void FloodControlGlobal::finish() { + auto old_value = active_count_.fetch_sub(1, std::memory_order_relaxed); + CHECK(old_value > 0); +} + +FloodControlGlobal::Guard FloodControlGlobal::try_start() { + auto old_value = active_count_.fetch_add(1, std::memory_order_relaxed); + if (old_value >= limit_) { + finish(); + return nullptr; + } + return Guard(this); +} + +void FloodControlGlobal::Finish::operator()(FloodControlGlobal *ctrl) const { + ctrl->finish(); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.h new file mode 100644 index 0000000000..e4f318867b --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlGlobal.h @@ -0,0 +1,35 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#include <atomic> +#include <memory> + +namespace td { + +// Restricts the total number of events +class FloodControlGlobal { + public: + explicit FloodControlGlobal(uint64 limit); + + struct Finish { + void operator()(FloodControlGlobal *ctrl) const; + }; + using Guard = std::unique_ptr<FloodControlGlobal, Finish>; + + Guard try_start(); + + private: + std::atomic<uint64> active_count_{0}; + uint64 limit_{0}; + + void finish(); +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h index 521fbbedc0..42894cdf7f 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/FloodControlStrict.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,7 +7,6 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/logging.h" #include <limits> @@ -17,22 +16,23 @@ namespace td { // Should be just fine for small counters. class FloodControlStrict { public: - int32 add_event(int32 now) { + // there is no reason to return wakeup_at_, because it will be a time before the next allowed event, not current + void add_event(double now) { events_.push_back(Event{now}); if (without_update_ > 0) { without_update_--; } else { update(now); } - return wakeup_at_; } - // no more than count in each duration. - void add_limit(int32 duration, int32 count) { + // no more than count in each duration + void add_limit(int32 duration, size_t count) { limits_.push_back(Limit{duration, count, 0}); + without_update_ = 0; } - int32 get_wakeup_at() { + double get_wakeup_at() const { return wakeup_at_; } @@ -42,20 +42,22 @@ class FloodControlStrict { limit.pos_ = 0; } without_update_ = 0; - wakeup_at_ = 0; + wakeup_at_ = 0.0; } - int32 update(int32 now) { + private: + void update(double now) { size_t min_pos = events_.size(); without_update_ = std::numeric_limits<size_t>::max(); for (auto &limit : limits_) { - if (limit.pos_ + limit.count_ < events_.size()) { + if (limit.count_ < events_.size() - limit.pos_) { limit.pos_ = events_.size() - limit.count_; } // binary-search? :D - while (limit.pos_ < events_.size() && events_[limit.pos_].timestamp_ + limit.duration_ < now) { + auto end_time = now - limit.duration_; + while (limit.pos_ < events_.size() && events_[limit.pos_].timestamp_ < end_time) { limit.pos_++; } @@ -64,7 +66,7 @@ class FloodControlStrict { wakeup_at_ = max(wakeup_at_, events_[limit.pos_].timestamp_ + limit.duration_); without_update_ = 0; } else { - without_update_ = min(without_update_, limit.count_ + limit.pos_ - events_.size()); + without_update_ = min(without_update_, limit.count_ + limit.pos_ - events_.size() - 1); } min_pos = min(min_pos, limit.pos_); @@ -76,22 +78,20 @@ class FloodControlStrict { } events_.erase(events_.begin(), events_.begin() + min_pos); } - return wakeup_at_; } - private: - int32 wakeup_at_ = 0; + double wakeup_at_ = 0.0; struct Event { - int32 timestamp_; + double timestamp_; }; struct Limit { int32 duration_; - int32 count_; + size_t count_; size_t pos_; }; size_t without_update_ = 0; - std::vector<Event> events_; - std::vector<Limit> limits_; + vector<Event> events_; + vector<Limit> limits_; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp index d4e60d6e29..64b07a9c04 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -9,10 +9,11 @@ char disable_linker_warning_about_empty_file_gzip_cpp TD_UNUSED; #if TD_HAVE_ZLIB -#include "td/utils/logging.h" +#include "td/utils/SliceBuilder.h" #include <cstring> #include <limits> +#include <utility> #include <zlib.h> @@ -32,23 +33,23 @@ class Gzip::Impl { }; Status Gzip::init_encode() { - CHECK(mode_ == Empty); + CHECK(mode_ == Mode::Empty); init_common(); - mode_ = Encode; + mode_ = Mode::Encode; int ret = deflateInit2(&impl_->stream_, 6, Z_DEFLATED, 15, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); if (ret != Z_OK) { - return Status::Error("zlib deflate init failed"); + return Status::Error(PSLICE() << "zlib deflate init failed: " << ret); } return Status::OK(); } Status Gzip::init_decode() { - CHECK(mode_ == Empty); + CHECK(mode_ == Mode::Empty); init_common(); - mode_ = Decode; + mode_ = Mode::Decode; int ret = inflateInit2(&impl_->stream_, MAX_WBITS + 32); if (ret != Z_OK) { - return Status::Error("zlib inflate init failed"); + return Status::Error(PSLICE() << "zlib inflate init failed: " << ret); } return Status::OK(); } @@ -75,19 +76,19 @@ void Gzip::set_output(MutableSlice output) { Result<Gzip::State> Gzip::run() { while (true) { int ret; - if (mode_ == Decode) { + if (mode_ == Mode::Decode) { ret = inflate(&impl_->stream_, Z_NO_FLUSH); } else { ret = deflate(&impl_->stream_, close_input_flag_ ? Z_FINISH : Z_NO_FLUSH); } if (ret == Z_OK) { - return Running; + return State::Running; } if (ret == Z_STREAM_END) { // TODO(now): fail if input is not empty; clear(); - return Done; + return State::Done; } clear(); return Status::Error(PSLICE() << "zlib error " << ret); @@ -118,20 +119,36 @@ void Gzip::init_common() { } void Gzip::clear() { - if (mode_ == Decode) { + if (mode_ == Mode::Decode) { inflateEnd(&impl_->stream_); - } else if (mode_ == Encode) { + } else if (mode_ == Mode::Encode) { deflateEnd(&impl_->stream_); } - mode_ = Empty; + mode_ = Mode::Empty; } Gzip::Gzip() : impl_(make_unique<Impl>()) { } -Gzip::Gzip(Gzip &&other) = default; +Gzip::Gzip(Gzip &&other) noexcept : Gzip() { + swap(other); +} -Gzip &Gzip::operator=(Gzip &&other) = default; +Gzip &Gzip::operator=(Gzip &&other) noexcept { + CHECK(this != &other); + clear(); + swap(other); + return *this; +} + +void Gzip::swap(Gzip &other) { + using std::swap; + swap(impl_, other.impl_); + swap(input_size_, other.input_size_); + swap(output_size_, other.output_size_); + swap(close_input_flag_, other.close_input_flag_); + swap(mode_, other.mode_); +} Gzip::~Gzip() { clear(); @@ -140,7 +157,7 @@ Gzip::~Gzip() { BufferSlice gzdecode(Slice s) { Gzip gzip; gzip.init_decode().ensure(); - auto message = ChainBufferWriter::create_empty(); + ChainBufferWriter message; gzip.set_input(s); gzip.close_input(); double k = 2; @@ -151,7 +168,7 @@ BufferSlice gzdecode(Slice s) { return BufferSlice(); } auto state = r_state.ok(); - if (state == Gzip::Done) { + if (state == Gzip::State::Done) { message.confirm_append(gzip.flush_output()); break; } @@ -167,12 +184,12 @@ BufferSlice gzdecode(Slice s) { return message.extract_reader().move_as_buffer_slice(); } -BufferSlice gzencode(Slice s, double k) { +BufferSlice gzencode(Slice s, double max_compression_ratio) { Gzip gzip; gzip.init_encode().ensure(); gzip.set_input(s); gzip.close_input(); - size_t max_size = static_cast<size_t>(static_cast<double>(s.size()) * k); + auto max_size = static_cast<size_t>(static_cast<double>(s.size()) * max_compression_ratio); BufferWriter message{max_size}; gzip.set_output(message.prepare_append()); auto r_state = gzip.run(); @@ -180,7 +197,7 @@ BufferSlice gzencode(Slice s, double k) { return BufferSlice(); } auto state = r_state.ok(); - if (state != Gzip::Done) { + if (state != Gzip::State::Done) { return BufferSlice(); } message.confirm_append(gzip.flush_output()); diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h index dd5fba5bf5..d7b68c5e45 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Gzip.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -20,15 +20,15 @@ class Gzip { Gzip(); Gzip(const Gzip &) = delete; Gzip &operator=(const Gzip &) = delete; - Gzip(Gzip &&other); - Gzip &operator=(Gzip &&other); + Gzip(Gzip &&other) noexcept; + Gzip &operator=(Gzip &&other) noexcept; ~Gzip(); - enum Mode { Empty, Encode, Decode }; + enum class Mode { Empty, Encode, Decode }; Status init(Mode mode) TD_WARN_UNUSED_RESULT { - if (mode == Encode) { + if (mode == Mode::Encode) { return init_encode(); - } else if (mode == Decode) { + } else if (mode == Mode::Decode) { return init_decode(); } clear(); @@ -79,7 +79,7 @@ class Gzip { return res; } - enum State { Running, Done }; + enum class State { Running, Done }; Result<State> run() TD_WARN_UNUSED_RESULT; private: @@ -89,15 +89,17 @@ class Gzip { size_t input_size_ = 0; size_t output_size_ = 0; bool close_input_flag_ = false; - Mode mode_ = Empty; + Mode mode_ = Mode::Empty; void init_common(); void clear(); + + void swap(Gzip &other); }; BufferSlice gzdecode(Slice s); -BufferSlice gzencode(Slice s, double k = 0.9); +BufferSlice gzencode(Slice s, double max_compression_ratio); } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp index d225ef800e..d321b68ccf 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -9,62 +9,57 @@ char disable_linker_warning_about_empty_file_gzipbyteflow_cpp TD_UNUSED; #if TD_HAVE_ZLIB -#include "td/utils/logging.h" +#include "td/utils/common.h" #include "td/utils/Status.h" namespace td { -void GzipByteFlow::loop() { - while (true) { - if (gzip_.need_input()) { - auto slice = input_->prepare_read(); - if (slice.empty()) { - if (!is_input_active_) { - gzip_.close_input(); - } else { - break; - } +bool GzipByteFlow::loop() { + if (gzip_.need_input()) { + auto slice = input_->prepare_read(); + if (slice.empty()) { + if (!is_input_active_) { + gzip_.close_input(); } else { - gzip_.set_input(input_->prepare_read()); + return false; } + } else { + gzip_.set_input(input_->prepare_read()); } - if (gzip_.need_output()) { - auto slice = output_.prepare_append(); - CHECK(!slice.empty()); - gzip_.set_output(slice); - } - auto r_state = gzip_.run(); - auto output_size = gzip_.flush_output(); - if (output_size) { - uncommited_size_ += output_size; - total_output_size_ += output_size; - if (total_output_size_ > max_output_size_) { - return finish(Status::Error("Max output size limit exceeded")); - } - output_.confirm_append(output_size); + } + if (gzip_.need_output()) { + auto slice = output_.prepare_append(); + CHECK(!slice.empty()); + gzip_.set_output(slice); + } + auto r_state = gzip_.run(); + auto output_size = gzip_.flush_output(); + if (output_size) { + uncommitted_size_ += output_size; + total_output_size_ += output_size; + if (total_output_size_ > max_output_size_) { + finish(Status::Error("Max output size limit exceeded")); + return false; } + output_.confirm_append(output_size); + } - auto input_size = gzip_.flush_input(); - if (input_size) { - input_->confirm_read(input_size); - } - if (r_state.is_error()) { - return finish(r_state.move_as_error()); - } - auto state = r_state.ok(); - if (state == Gzip::Done) { - on_output_updated(); - return consume_input(); - } + auto input_size = gzip_.flush_input(); + if (input_size) { + input_->confirm_read(input_size); } - if (uncommited_size_ >= MIN_UPDATE_SIZE) { - uncommited_size_ = 0; - on_output_updated(); + if (r_state.is_error()) { + finish(r_state.move_as_error()); + return false; } + auto state = r_state.ok(); + if (state == Gzip::State::Done) { + consume_input(); + return false; + } + return true; } -constexpr size_t GzipByteFlow::MIN_UPDATE_SIZE; - } // namespace td #endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h index c7e07abd0a..94a3a3ea50 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/GzipByteFlow.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -34,14 +34,13 @@ class GzipByteFlow final : public ByteFlowBase { max_output_size_ = max_output_size; } - void loop() override; + bool loop() final; private: Gzip gzip_; - size_t uncommited_size_ = 0; + size_t uncommitted_size_ = 0; size_t total_output_size_ = 0; size_t max_output_size_ = std::numeric_limits<size_t>::max(); - static constexpr size_t MIN_UPDATE_SIZE = 1 << 14; }; #endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hash.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hash.h new file mode 100644 index 0000000000..8c500daf09 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hash.h @@ -0,0 +1,70 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#if TD_HAVE_ABSL +#include <absl/hash/hash.h> +#endif + +#include <utility> + +namespace td { +// A simple wrapper for absl::flat_hash_map, std::unordered_map and probably some our implementaion of hash map in +// the future + +// We will introduce out own Hashing utility like an absl one. +class Hasher { + public: + Hasher() = default; + explicit Hasher(size_t init_value) : hash_(init_value) { + } + std::size_t finalize() const { + return hash_; + } + + static Hasher combine(Hasher hasher, size_t value) { + hasher.hash_ ^= value; + return hasher; + } + + template <class A, class B> + static Hasher combine(Hasher hasher, const std::pair<A, B> &value) { + hasher = AbslHashValue(std::move(hasher), value.first); + hasher = AbslHashValue(std::move(hasher), value.second); + return hasher; + } + + private: + std::size_t hash_{0}; +}; + +template <class IgnoreT> +class TdHash { + public: + template <class T> + std::size_t operator()(const T &value) const noexcept { + return AbslHashValue(Hasher(), value).finalize(); + } +}; + +#if TD_HAVE_ABSL +template <class T> +using AbslHash = absl::Hash<T>; +#else +template <class T> +using AbslHash = TdHash<T>; +#endif + +// default hash implementations +template <class H, class T> +decltype(H::combine(std::declval<H>(), std::declval<T>())) AbslHashValue(H hasher, const T &value) { + return H::combine(std::move(hasher), value); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HashMap.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashMap.h new file mode 100644 index 0000000000..7e0ba4bf07 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashMap.h @@ -0,0 +1,27 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/Hash.h" + +#if TD_HAVE_ABSL +#include <absl/container/flat_hash_map.h> +#else +#include <unordered_map> +#endif + +namespace td { + +#if TD_HAVE_ABSL +template <class Key, class Value, class H = AbslHash<Key>> +using HashMap = absl::flat_hash_map<Key, Value, H>; +#else +template <class Key, class Value, class H = AbslHash<Key>> +using HashMap = std::unordered_map<Key, Value, H>; +#endif + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HashSet.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashSet.h new file mode 100644 index 0000000000..e49e8b94e3 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashSet.h @@ -0,0 +1,27 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/Hash.h" + +#if TD_HAVE_ABSL +#include <absl/container/flat_hash_set.h> +#else +#include <unordered_set> +#endif + +namespace td { + +#if TD_HAVE_ABSL +template <class Key, class H = AbslHash<Key>> +using HashSet = absl::flat_hash_set<Key, H>; +#else +template <class Key, class H = AbslHash<Key>> +using HashSet = std::unordered_set<Key, H>; +#endif + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HashTableUtils.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashTableUtils.h new file mode 100644 index 0000000000..9d72f63c59 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HashTableUtils.h @@ -0,0 +1,72 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#include <cstdint> +#include <functional> + +namespace td { + +template <class KeyT> +bool is_hash_table_key_empty(const KeyT &key) { + return key == KeyT(); +} + +inline uint32 randomize_hash(uint32 h) { + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + return h; +} + +template <class Type> +struct Hash { + uint32 operator()(const Type &value) const; +}; + +template <class Type> +struct Hash<Type *> { + uint32 operator()(Type *pointer) const { + return Hash<uint64>()(reinterpret_cast<std::uintptr_t>(pointer)); + } +}; + +template <> +inline uint32 Hash<char>::operator()(const char &value) const { + return randomize_hash(static_cast<uint32>(value)); +} + +template <> +inline uint32 Hash<int32>::operator()(const int32 &value) const { + return randomize_hash(static_cast<uint32>(value)); +} + +template <> +inline uint32 Hash<uint32>::operator()(const uint32 &value) const { + return randomize_hash(value); +} + +template <> +inline uint32 Hash<int64>::operator()(const int64 &value) const { + return randomize_hash(static_cast<uint32>(value + (value >> 32))); +} + +template <> +inline uint32 Hash<uint64>::operator()(const uint64 &value) const { + return randomize_hash(static_cast<uint32>(value + (value >> 32))); +} + +template <> +inline uint32 Hash<string>::operator()(const string &value) const { + return static_cast<uint32>(std::hash<string>()(value)); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h index e13dc8022e..3ed41a0c9a 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HazardPointers.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,20 +7,25 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/logging.h" #include <array> #include <atomic> +#include <memory> namespace td { -template <class T, int MaxPointersN = 1> +template <class T, int MaxPointersN = 1, class Deleter = std::default_delete<T>> class HazardPointers { public: explicit HazardPointers(size_t threads_n) : threads_(threads_n) { for (auto &data : threads_) { - for (auto &ptr : data.hazard) { + for (auto &ptr : data.hazard_) { +// workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64658 +#if TD_GCC && GCC_VERSION <= 40902 ptr = nullptr; +#else + std::atomic_init(&ptr, static_cast<T *>(nullptr)); +#endif } } } @@ -31,12 +36,17 @@ class HazardPointers { class Holder { public: - T *protect(std::atomic<T *> &to_protect) { + template <class S> + S *protect(std::atomic<S *> &to_protect) { return do_protect(hazard_ptr_, to_protect); } + Holder(HazardPointers &hp, size_t thread_id, size_t pos) : Holder(hp.get_hazard_ptr(thread_id, pos)) { + CHECK(hazard_ptr_.load() == 0); + hazard_ptr_.store(reinterpret_cast<T *>(1)); + } Holder(const Holder &other) = delete; Holder &operator=(const Holder &other) = delete; - Holder(Holder &&other) = default; // TODO + Holder(Holder &&other) = delete; Holder &operator=(Holder &&other) = delete; ~Holder() { clear(); @@ -52,20 +62,16 @@ class HazardPointers { std::atomic<T *> &hazard_ptr_; }; - Holder get_holder(size_t thread_id, size_t pos) { - return Holder(get_hazard_ptr(thread_id, pos)); - } - void retire(size_t thread_id, T *ptr = nullptr) { CHECK(thread_id < threads_.size()); auto &data = threads_[thread_id]; if (ptr) { - data.to_delete.push_back(std::unique_ptr<T>(ptr)); + data.to_delete_.push_back(std::unique_ptr<T, Deleter>(ptr)); } - for (auto it = data.to_delete.begin(); it != data.to_delete.end();) { + for (auto it = data.to_delete_.begin(); it != data.to_delete_.end();) { if (!is_protected(it->get())) { it->reset(); - it = data.to_delete.erase(it); + it = data.to_delete_.erase(it); } else { ++it; } @@ -82,32 +88,33 @@ class HazardPointers { size_t to_delete_size_unsafe() const { size_t res = 0; - for (auto &thread : threads_) { - res += thread.to_delete.size(); + for (auto &thread_data : threads_) { + res += thread_data.to_delete_.size(); } return res; } private: struct ThreadData { - std::array<std::atomic<T *>, MaxPointersN> hazard; + std::array<std::atomic<T *>, MaxPointersN> hazard_; char pad[TD_CONCURRENCY_PAD - sizeof(std::array<std::atomic<T *>, MaxPointersN>)]; // stupid gc - std::vector<std::unique_ptr<T>> to_delete; - char pad2[TD_CONCURRENCY_PAD - sizeof(std::vector<std::unique_ptr<T>>)]; + std::vector<std::unique_ptr<T, Deleter>> to_delete_; + char pad2[TD_CONCURRENCY_PAD - sizeof(std::vector<std::unique_ptr<T, Deleter>>)]; }; std::vector<ThreadData> threads_; char pad2[TD_CONCURRENCY_PAD - sizeof(std::vector<ThreadData>)]; - static T *do_protect(std::atomic<T *> &hazard_ptr, std::atomic<T *> &to_protect) { + template <class S> + static S *do_protect(std::atomic<T *> &hazard_ptr, std::atomic<S *> &to_protect) { T *saved = nullptr; T *to_save; while ((to_save = to_protect.load()) != saved) { hazard_ptr.store(to_save); saved = to_save; } - return saved; + return static_cast<S *>(saved); } static void do_clear(std::atomic<T *> &hazard_ptr) { @@ -115,8 +122,8 @@ class HazardPointers { } bool is_protected(T *ptr) { - for (auto &thread : threads_) { - for (auto &hazard_ptr : thread.hazard) { + for (auto &thread_data : threads_) { + for (auto &hazard_ptr : thread_data.hazard_) { if (hazard_ptr.load() == ptr) { return true; } @@ -126,7 +133,8 @@ class HazardPointers { } std::atomic<T *> &get_hazard_ptr(size_t thread_id, size_t pos) { - return threads_[thread_id].hazard[pos]; + CHECK(thread_id < threads_.size()); + return threads_[thread_id].hazard_[pos]; } }; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h index 54ee391497..154b87089a 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Heap.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,7 +7,6 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/logging.h" namespace td { @@ -21,7 +20,7 @@ struct HeapNode { void remove() { pos_ = -1; } - int pos_ = -1; + int32 pos_ = -1; }; template <class KeyT, int K = 4> @@ -38,23 +37,33 @@ class KHeap { return array_[0].key_; } + KeyT get_key(const HeapNode *node) const { + auto pos = static_cast<size_t>(node->pos_); + CHECK(pos < array_.size()); + return array_[pos].key_; + } + + const HeapNode *top() const { + return array_[0].node_; + } + HeapNode *pop() { CHECK(!empty()); HeapNode *result = array_[0].node_; result->remove(); - erase(0); + erase(static_cast<size_t>(0)); return result; } void insert(KeyT key, HeapNode *node) { CHECK(!node->in_heap()); array_.push_back({key, node}); - fix_up(static_cast<int>(array_.size()) - 1); + fix_up(array_.size() - 1); } void fix(KeyT key, HeapNode *node) { - CHECK(node->in_heap()); - int pos = node->pos_; + auto pos = static_cast<size_t>(node->pos_); + CHECK(pos < array_.size()); KeyT old_key = array_[pos].key_; array_[pos].key_ = key; if (key < old_key) { @@ -65,14 +74,21 @@ class KHeap { } void erase(HeapNode *node) { - CHECK(node->in_heap()); - int pos = node->pos_; + auto pos = static_cast<size_t>(node->pos_); node->remove(); + CHECK(pos < array_.size()); erase(pos); } template <class F> - void for_each(F &f) const { + void for_each(F &&f) const { + for (auto &it : array_) { + f(it.key_, it.node_); + } + } + + template <class F> + void for_each(F &&f) { for (auto &it : array_) { f(it.key_, it.node_); } @@ -81,7 +97,7 @@ class KHeap { void check() const { for (size_t i = 0; i < array_.size(); i++) { for (size_t j = i * K + 1; j < i * K + 1 + K && j < array_.size(); j++) { - CHECK(array_[i].key_ <= array_[j].key_) << i << " " << j; + CHECK(array_[i].key_ <= array_[j].key_); } } } @@ -93,34 +109,34 @@ class KHeap { }; vector<Item> array_; - void fix_up(int pos) { + void fix_up(size_t pos) { auto item = array_[pos]; while (pos) { - int parent_pos = (pos - 1) / K; + auto parent_pos = (pos - 1) / K; auto parent_item = array_[parent_pos]; if (parent_item.key_ < item.key_) { break; } - parent_item.node_->pos_ = pos; + parent_item.node_->pos_ = static_cast<int32>(pos); array_[pos] = parent_item; pos = parent_pos; } - item.node_->pos_ = pos; + item.node_->pos_ = static_cast<int32>(pos); array_[pos] = item; } - void fix_down(int pos) { + void fix_down(size_t pos) { auto item = array_[pos]; while (true) { - int left_pos = pos * K + 1; - int right_pos = min(left_pos + K, static_cast<int>(array_.size())); - int next_pos = pos; + auto left_pos = pos * K + 1; + auto right_pos = min(left_pos + K, array_.size()); + auto next_pos = pos; KeyT next_key = item.key_; - for (int i = left_pos; i < right_pos; i++) { + for (auto i = left_pos; i < right_pos; i++) { KeyT i_key = array_[i].key_; if (i_key < next_key) { next_key = i_key; @@ -131,21 +147,24 @@ class KHeap { break; } array_[pos] = array_[next_pos]; - array_[pos].node_->pos_ = pos; + array_[pos].node_->pos_ = static_cast<int32>(pos); pos = next_pos; } - item.node_->pos_ = pos; + item.node_->pos_ = static_cast<int32>(pos); array_[pos] = item; } - void erase(int pos) { + void erase(size_t pos) { array_[pos] = array_.back(); array_.pop_back(); - if (pos < static_cast<int>(array_.size())) { + if (pos < array_.size()) { fix_down(pos); fix_up(pos); } + if (array_.capacity() > 50 && array_.size() < array_.capacity() / 4) { + array_.shrink_to_fit(); + } } }; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp index 1e7449a668..1041b95d8b 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.cpp @@ -1,49 +1,23 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/Hints.h" +#include "td/utils/algorithm.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Slice.h" -#include "td/utils/unicode.h" +#include "td/utils/translit.h" #include "td/utils/utf8.h" #include <algorithm> namespace td { -vector<string> Hints::get_words(Slice name) { - bool in_word = false; - string word; - vector<string> words; - auto pos = name.ubegin(); - auto end = name.uend(); - while (pos != end) { - uint32 code; - pos = next_utf8_unsafe(pos, &code); - - code = prepare_search_character(code); - if (code == 0) { - continue; - } - if (code == ' ') { - if (in_word) { - words.push_back(std::move(word)); - word.clear(); - in_word = false; - } - } else { - in_word = true; - append_utf8_character(word, code); - } - } - if (in_word) { - words.push_back(std::move(word)); - } +vector<string> Hints::fix_words(vector<string> words) { std::sort(words.begin(), words.end()); size_t new_words_size = 0; @@ -52,14 +26,39 @@ vector<string> Hints::get_words(Slice name) { if (i != new_words_size) { words[new_words_size] = std::move(words[i]); } - // LOG(ERROR) << "Get word " << words[new_words_size]; new_words_size++; } } + if (new_words_size == 1 && words[0].empty()) { + new_words_size = 0; + } words.resize(new_words_size); return words; } +vector<string> Hints::get_words(Slice name) { + return fix_words(utf8_get_search_words(name)); +} + +void Hints::add_word(const string &word, KeyT key, std::map<string, vector<KeyT>> &word_to_keys) { + vector<KeyT> &keys = word_to_keys[word]; + CHECK(!td::contains(keys, key)); + keys.push_back(key); +} + +void Hints::delete_word(const string &word, KeyT key, std::map<string, vector<KeyT>> &word_to_keys) { + vector<KeyT> &keys = word_to_keys[word]; + auto key_it = std::find(keys.begin(), keys.end(), key); + CHECK(key_it != keys.end()); + if (keys.size() == 1) { + word_to_keys.erase(word); + } else { + CHECK(keys.size() > 1); + *key_it = keys.back(); + keys.pop_back(); + } +} + void Hints::add(KeyT key, Slice name) { // LOG(ERROR) << "Add " << key << ": " << name; auto it = key_to_name_.find(key); @@ -67,19 +66,19 @@ void Hints::add(KeyT key, Slice name) { if (it->second == name) { return; } - auto old_words = get_words(it->second); - for (auto &old_word : old_words) { - vector<KeyT> &keys = word_to_keys_[old_word]; - auto key_it = std::find(keys.begin(), keys.end(), key); - CHECK(key_it != keys.end()); - if (keys.size() == 1) { - word_to_keys_.erase(old_word); - } else { - CHECK(keys.size() > 1); - *key_it = keys.back(); - keys.pop_back(); + vector<string> old_transliterations; + for (auto &old_word : get_words(it->second)) { + delete_word(old_word, key, word_to_keys_); + + for (auto &w : get_word_transliterations(old_word, false)) { + if (w != old_word) { + old_transliterations.push_back(std::move(w)); + } } } + for (auto &word : fix_words(old_transliterations)) { + delete_word(word, key, translit_word_to_keys_); + } } if (name.empty()) { if (it != key_to_name_.end()) { @@ -88,12 +87,21 @@ void Hints::add(KeyT key, Slice name) { key_to_rating_.erase(key); return; } - auto words = get_words(name); - for (auto &word : words) { - vector<KeyT> &keys = word_to_keys_[word]; - CHECK(std::find(keys.begin(), keys.end(), key) == keys.end()); - keys.push_back(key); + + vector<string> transliterations; + for (auto &word : get_words(name)) { + add_word(word, key, word_to_keys_); + + for (auto &w : get_word_transliterations(word, false)) { + if (w != word) { + transliterations.push_back(std::move(w)); + } + } + } + for (auto &word : fix_words(transliterations)) { + add_word(word, key, translit_word_to_keys_); } + key_to_name_[key] = name.str(); } @@ -102,17 +110,24 @@ void Hints::set_rating(KeyT key, RatingT rating) { key_to_rating_[key] = rating; } -vector<Hints::KeyT> Hints::search_word(const string &word) const { - // LOG(ERROR) << "Search word " << word; - vector<KeyT> results; - auto it = word_to_keys_.lower_bound(word); - while (it != word_to_keys_.end() && begins_with(it->first, word)) { +void Hints::add_search_results(vector<KeyT> &results, const string &word, + const std::map<string, vector<KeyT>> &word_to_keys) { + LOG(DEBUG) << "Search for word " << word; + auto it = word_to_keys.lower_bound(word); + while (it != word_to_keys.end() && begins_with(it->first, word)) { results.insert(results.end(), it->second.begin(), it->second.end()); ++it; } +} + +vector<Hints::KeyT> Hints::search_word(const string &word) const { + vector<KeyT> results; + add_search_results(results, word, translit_word_to_keys_); + for (const auto &w : get_word_transliterations(word, true)) { + add_search_results(results, w, word_to_keys_); + } - std::sort(results.begin(), results.end()); - results.erase(std::unique(results.begin(), results.end()), results.end()); + td::unique(results); return results; } @@ -169,7 +184,7 @@ std::pair<size_t, vector<Hints::KeyT>> Hints::search(Slice query, int32 limit, b } bool Hints::has_key(KeyT key) const { - return key_to_name_.find(key) != key_to_name_.end(); + return key_to_name_.count(key) > 0; } string Hints::key_to_string(KeyT key) const { diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h index 645896684a..f069da20d0 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Hints.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,6 +7,7 @@ #pragma once #include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" #include "td/utils/Slice.h" #include <map> @@ -41,17 +42,26 @@ class Hints { size_t size() const; + static vector<string> fix_words(vector<string> words); + private: std::map<string, vector<KeyT>> word_to_keys_; - std::unordered_map<KeyT, string> key_to_name_; - std::unordered_map<KeyT, RatingT> key_to_rating_; + std::map<string, vector<KeyT>> translit_word_to_keys_; + std::unordered_map<KeyT, string, Hash<KeyT>> key_to_name_; + std::unordered_map<KeyT, RatingT, Hash<KeyT>> key_to_rating_; + + static void add_word(const string &word, KeyT key, std::map<string, vector<KeyT>> &word_to_keys); + static void delete_word(const string &word, KeyT key, std::map<string, vector<KeyT>> &word_to_keys); static vector<string> get_words(Slice name); + static void add_search_results(vector<KeyT> &results, const string &word, + const std::map<string, vector<KeyT>> &word_to_keys); + vector<KeyT> search_word(const string &word) const; class CompareByRating { - const std::unordered_map<KeyT, RatingT> &key_to_rating_; + const std::unordered_map<KeyT, RatingT, Hash<KeyT>> &key_to_rating_; RatingT get_rating(const KeyT &key) const { auto it = key_to_rating_.find(key); @@ -62,7 +72,8 @@ class Hints { } public: - explicit CompareByRating(const std::unordered_map<KeyT, RatingT> &key_to_rating) : key_to_rating_(key_to_rating) { + explicit CompareByRating(const std::unordered_map<KeyT, RatingT, Hash<KeyT>> &key_to_rating) + : key_to_rating_(key_to_rating) { } bool operator()(const KeyT &lhs, const KeyT &rhs) const { diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp index 55b66f7b3a..e793f940a8 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,16 +10,19 @@ #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Parser.h" +#include "td/utils/port/IPAddress.h" + +#include <algorithm> namespace td { string HttpUrl::get_url() const { string result; switch (protocol_) { - case Protocol::HTTP: + case Protocol::Http: result += "http://"; break; - case Protocol::HTTPS: + case Protocol::Https: result += "https://"; break; default: @@ -29,39 +32,32 @@ string HttpUrl::get_url() const { result += userinfo_; result += '@'; } - if (is_ipv6) { - result += '['; - } result += host_; - if (is_ipv6) { - result += ']'; - } if (specified_port_ > 0) { result += ':'; result += to_string(specified_port_); } - CHECK(!query_.empty() && query_[0] == '/'); + LOG_CHECK(!query_.empty() && query_[0] == '/') << query_; result += query_; return result; } -Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol) { +Result<HttpUrl> parse_url(Slice url, HttpUrl::Protocol default_protocol) { // url == [https?://][userinfo@]host[:port] - Parser parser(url); - string protocol_str = to_lower(parser.read_till_nofail(':')); + ConstParser parser(url); + string protocol_str = to_lower(parser.read_till_nofail(":/?#@[]")); HttpUrl::Protocol protocol; - if (parser.start_with("://")) { - parser.advance(3); + if (parser.try_skip("://")) { if (protocol_str == "http") { - protocol = HttpUrl::Protocol::HTTP; + protocol = HttpUrl::Protocol::Http; } else if (protocol_str == "https") { - protocol = HttpUrl::Protocol::HTTPS; + protocol = HttpUrl::Protocol::Https; } else { return Status::Error("Unsupported URL protocol"); } } else { - parser = Parser(url); + parser = ConstParser(url); protocol = default_protocol; } Slice userinfo_host_port = parser.read_till_nofail("/?#"); @@ -73,7 +69,16 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol) } Slice userinfo_host; if (colon > userinfo_host_port.begin() && *colon == ':') { - port = to_integer<int>(Slice(colon + 1, userinfo_host_port.end())); + Slice port_slice(colon + 1, userinfo_host_port.end()); + while (port_slice.size() > 1 && port_slice[0] == '0') { + port_slice.remove_prefix(1); + } + auto r_port = to_integer_safe<int>(port_slice); + if (r_port.is_error() || r_port.ok() == 0) { + port = -1; + } else { + port = r_port.ok(); + } userinfo_host = Slice(userinfo_host_port.begin(), colon); } else { userinfo_host = userinfo_host_port; @@ -88,20 +93,26 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol) bool is_ipv6 = false; if (!host.empty() && host[0] == '[' && host.back() == ']') { - host.remove_prefix(1); - host.remove_suffix(1); + IPAddress ip_address; + if (ip_address.init_ipv6_port(host.str(), 1).is_error()) { + return Status::Error("Wrong IPv6 address specified in the URL"); + } + CHECK(ip_address.is_ipv6()); is_ipv6 = true; } if (host.empty()) { return Status::Error("URL host is empty"); } + if (host == ".") { + return Status::Error("Host is invalid"); + } int specified_port = port; if (port == 0) { - if (protocol == HttpUrl::Protocol::HTTP) { + if (protocol == HttpUrl::Protocol::Http) { port = 80; } else { - CHECK(protocol == HttpUrl::Protocol::HTTPS); + CHECK(protocol == HttpUrl::Protocol::Https); port = 443; } } @@ -111,7 +122,7 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol) query.remove_suffix(1); } if (query.empty()) { - query = "/"; + query = Slice("/"); } string query_str; if (query[0] != '/') { @@ -130,6 +141,14 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol) string host_str = to_lower(host); for (size_t i = 0; i < host_str.size(); i++) { char c = host_str[i]; + if (is_ipv6) { + if (i == 0 || i + 1 == host_str.size() || c == ':' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || + c == '.') { + continue; + } + return Status::Error("Wrong IPv6 URL host"); + } + if (('a' <= c && c <= 'z') || c == '.' || ('0' <= c && c <= '9') || c == '-' || c == '_' || c == '!' || c == '$' || c == ',' || c == '~' || c == '*' || c == '\'' || c == '(' || c == ')' || c == ';' || c == '&' || c == '+' || c == '=') { @@ -145,9 +164,11 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol) continue; } } + return Status::Error("Wrong percent-encoded symbol in URL host"); } + // all other symbols aren't allowed - unsigned char uc = static_cast<unsigned char>(c); + auto uc = static_cast<unsigned char>(c); if (uc >= 128) { // but we allow plain UTF-8 symbols continue; @@ -159,11 +180,66 @@ Result<HttpUrl> parse_url(MutableSlice url, HttpUrl::Protocol default_protocol) } StringBuilder &operator<<(StringBuilder &sb, const HttpUrl &url) { - sb << tag("protocol", url.protocol_ == HttpUrl::Protocol::HTTP ? "HTTP" : "HTTPS") << tag("userinfo", url.userinfo_) + sb << tag("protocol", url.protocol_ == HttpUrl::Protocol::Http ? "HTTP" : "HTTPS") << tag("userinfo", url.userinfo_) << tag("host", url.host_) << tag("port", url.port_) << tag("query", url.query_); return sb; } +HttpUrlQuery parse_url_query(Slice query) { + if (!query.empty() && query[0] == '/') { + query.remove_prefix(1); + } + + size_t path_size = 0; + while (path_size < query.size() && query[path_size] != '?' && query[path_size] != '#') { + path_size++; + } + + HttpUrlQuery result; + result.path_ = full_split(url_decode(query.substr(0, path_size), false), '/'); + while (!result.path_.empty() && result.path_.back().empty()) { + result.path_.pop_back(); + } + + if (path_size < query.size() && query[path_size] == '?') { + query = query.substr(path_size + 1); + query.truncate(query.find('#')); + + ConstParser parser(query); + while (!parser.data().empty()) { + auto key_value = split(parser.read_till_nofail('&'), '='); + parser.skip_nofail('&'); + auto key = url_decode(key_value.first, true); + if (!key.empty()) { + result.args_.emplace_back(std::move(key), url_decode(key_value.second, true)); + } + } + CHECK(parser.status().is_ok()); + } + + return result; +} + +bool HttpUrlQuery::has_arg(Slice key) const { + auto it = + std::find_if(args_.begin(), args_.end(), [&key](const std::pair<string, string> &s) { return s.first == key; }); + return it != args_.end(); +} + +Slice HttpUrlQuery::get_arg(Slice key) const { + auto it = + std::find_if(args_.begin(), args_.end(), [&key](const std::pair<string, string> &s) { return s.first == key; }); + return it == args_.end() ? Slice() : it->second; +} + +string get_url_host(Slice url) { + auto r_http_url = parse_url(url); + if (r_http_url.is_error()) { + return string(); + } + return r_http_url.ok().host_; +} + string get_url_query_file_name(const string &query) { Slice query_slice = query; query_slice.truncate(query.find_first_of("?#")); @@ -175,10 +251,8 @@ string get_url_query_file_name(const string &query) { return query_slice.str(); } -string get_url_file_name(const string &url) { - // TODO remove copy - string url_copy = url; - auto r_http_url = parse_url(url_copy); +string get_url_file_name(Slice url) { + auto r_http_url = parse_url(url); if (r_http_url.is_error()) { LOG(WARNING) << "Receive wrong URL \"" << url << '"'; return string(); diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h index f7d1e4aaba..9b4e92edce 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/HttpUrl.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,29 +11,54 @@ #include "td/utils/Status.h" #include "td/utils/StringBuilder.h" +#include <utility> + namespace td { class HttpUrl { public: - enum class Protocol { HTTP, HTTPS } protocol_; + enum class Protocol { Http, Https } protocol_ = Protocol::Http; string userinfo_; string host_; - bool is_ipv6; - int specified_port_; - int port_; + bool is_ipv6_ = false; + int specified_port_ = 0; + int port_ = 0; string query_; string get_url() const; + + HttpUrl(Protocol protocol, string userinfo, string host, bool is_ipv6, int specified_port, int port, string query) + : protocol_(protocol) + , userinfo_(std::move(userinfo)) + , host_(std::move(host)) + , is_ipv6_(is_ipv6) + , specified_port_(specified_port) + , port_(port) + , query_(std::move(query)) { + } }; -// TODO Slice instead of MutableSlice -Result<HttpUrl> parse_url(MutableSlice url, - HttpUrl::Protocol default_protocol = HttpUrl::Protocol::HTTP) TD_WARN_UNUSED_RESULT; +Result<HttpUrl> parse_url(Slice url, + HttpUrl::Protocol default_protocol = HttpUrl::Protocol::Http) TD_WARN_UNUSED_RESULT; StringBuilder &operator<<(StringBuilder &sb, const HttpUrl &url); +class HttpUrlQuery { + public: + vector<string> path_; + vector<std::pair<string, string>> args_; + + bool has_arg(Slice key) const; + + Slice get_arg(Slice key) const; +}; + +HttpUrlQuery parse_url_query(Slice query); + +string get_url_host(Slice url); + string get_url_query_file_name(const string &query); -string get_url_file_name(const string &url); +string get_url_file_name(Slice url); } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp index eb654f43cd..f5823ad806 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,10 +8,12 @@ #include "td/utils/misc.h" #include "td/utils/ScopeGuard.h" +#include "td/utils/SliceBuilder.h" #include <cstring> namespace td { + StringBuilder &operator<<(StringBuilder &sb, const JsonRawString &val) { sb << '"'; SCOPE_EXIT { @@ -94,11 +96,11 @@ StringBuilder &operator<<(StringBuilder &sb, const JsonString &val) { break; } if (128 <= ch) { - int a = s[pos]; + uint32 a = ch; CHECK((a & 0x40) != 0); CHECK(pos + 1 < len); - int b = s[++pos]; + uint32 b = static_cast<unsigned char>(s[++pos]); CHECK((b & 0xc0) == 0x80); if ((a & 0x20) == 0) { CHECK((a & 0x1e) > 0); @@ -107,7 +109,7 @@ StringBuilder &operator<<(StringBuilder &sb, const JsonString &val) { } CHECK(pos + 1 < len); - int c = s[++pos]; + uint32 c = static_cast<unsigned char>(s[++pos]); CHECK((c & 0xc0) == 0x80); if ((a & 0x10) == 0) { CHECK(((a & 0x0f) | (b & 0x20)) > 0); @@ -116,7 +118,7 @@ StringBuilder &operator<<(StringBuilder &sb, const JsonString &val) { } CHECK(pos + 1 < len); - int d = s[++pos]; + uint32 d = static_cast<unsigned char>(s[++pos]); CHECK((d & 0xc0) == 0x80); if ((a & 0x08) == 0) { CHECK(((a & 0x07) | (b & 0x30)) > 0); @@ -133,6 +135,7 @@ StringBuilder &operator<<(StringBuilder &sb, const JsonString &val) { } return sb; } + Result<MutableSlice> json_string_decode(Parser &parser) { if (!parser.try_skip('"')) { return Status::Error("Opening '\"' expected"); @@ -232,7 +235,7 @@ Result<MutableSlice> json_string_decode(Parser &parser) { } else if (num < 0x800) { *cur_dest++ = static_cast<char>(0xc0 + (num >> 6)); *cur_dest++ = static_cast<char>(0x80 + (num & 63)); - } else if (num < 0xffff) { + } else if (num <= 0xffff) { *cur_dest++ = static_cast<char>(0xe0 + (num >> 12)); *cur_dest++ = static_cast<char>(0x80 + ((num >> 6) & 63)); *cur_dest++ = static_cast<char>(0x80 + (num & 63)); @@ -341,20 +344,20 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) { parser.skip_whitespaces(); switch (parser.peek_char()) { case 'f': - if (parser.skip_start_with("false")) { + if (parser.try_skip("false")) { return JsonValue::create_boolean(false); } - return Status::Error("Starts with 'f' -- false expected"); + return Status::Error("Token starts with 'f' -- false expected"); case 't': - if (parser.skip_start_with("true")) { + if (parser.try_skip("true")) { return JsonValue::create_boolean(true); } - return Status::Error("Starts with 't' -- true expected"); + return Status::Error("Token starts with 't' -- true expected"); case 'n': - if (parser.skip_start_with("null")) { + if (parser.try_skip("null")) { return JsonValue(); } - return Status::Error("Starts with 'n' -- null expected"); + return Status::Error("Token starts with 'n' -- null expected"); case '"': { TRY_RESULT(slice, json_string_decode(parser)); return JsonValue::create_string(slice); @@ -368,7 +371,7 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) { } while (true) { if (parser.empty()) { - return Status::Error("Unexpected end"); + return Status::Error("Unexpected string end"); } TRY_RESULT(value, do_json_decode(parser, max_depth - 1)); res.emplace_back(std::move(value)); @@ -381,20 +384,23 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) { parser.skip_whitespaces(); continue; } - return Status::Error("Unexpected symbol"); + if (parser.empty()) { + return Status::Error("Unexpected string end"); + } + return Status::Error("Unexpected symbol while parsing JSON Array"); } return JsonValue::create_array(std::move(res)); } case '{': { parser.skip('{'); parser.skip_whitespaces(); - std::vector<std::pair<MutableSlice, JsonValue> > res; + std::vector<std::pair<MutableSlice, JsonValue>> res; if (parser.try_skip('}')) { return JsonValue::make_object(std::move(res)); } while (true) { if (parser.empty()) { - return Status::Error("Unexpected end"); + return Status::Error("Unexpected string end"); } TRY_RESULT(key, json_string_decode(parser)); parser.skip_whitespaces(); @@ -402,7 +408,7 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) { return Status::Error("':' expected"); } TRY_RESULT(value, do_json_decode(parser, max_depth - 1)); - res.emplace_back(std::move(key), std::move(value)); + res.emplace_back(key, std::move(value)); parser.skip_whitespaces(); if (parser.try_skip('}')) { @@ -412,7 +418,10 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) { parser.skip_whitespaces(); continue; } - return Status::Error("Unexpected symbol"); + if (parser.empty()) { + return Status::Error("Unexpected string end"); + } + return Status::Error("Unexpected symbol while parsing JSON Object"); } return JsonValue::make_object(std::move(res)); } @@ -434,7 +443,7 @@ Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) { return JsonValue::create_number(num); } case 0: - return Status::Error("Unexpected end"); + return Status::Error("Unexpected string end"); default: { char next = parser.peek_char(); if (0 < next && next < 127) { @@ -455,17 +464,17 @@ Status do_json_skip(Parser &parser, int32 max_depth) { parser.skip_whitespaces(); switch (parser.peek_char()) { case 'f': - if (parser.skip_start_with("false")) { + if (parser.try_skip("false")) { return Status::OK(); } return Status::Error("Starts with 'f' -- false expected"); case 't': - if (parser.skip_start_with("true")) { + if (parser.try_skip("true")) { return Status::OK(); } return Status::Error("Starts with 't' -- true expected"); case 'n': - if (parser.skip_start_with("null")) { + if (parser.try_skip("null")) { return Status::OK(); } return Status::Error("Starts with 'n' -- null expected"); @@ -576,7 +585,7 @@ Slice JsonValue::get_type_name(Type type) { } } -bool has_json_object_field(JsonObject &object, Slice name) { +bool has_json_object_field(const JsonObject &object, Slice name) { for (auto &field_value : object) { if (field_value.first == name) { return true; @@ -585,6 +594,15 @@ bool has_json_object_field(JsonObject &object, Slice name) { return false; } +JsonValue get_json_object_field_force(JsonObject &object, Slice name) { + for (auto &field_value : object) { + if (field_value.first == name) { + return std::move(field_value.second); + } + } + return JsonValue(); +} + Result<JsonValue> get_json_object_field(JsonObject &object, Slice name, JsonValue::Type type, bool is_optional) { for (auto &field_value : object) { if (field_value.first == name) { @@ -611,11 +629,41 @@ Result<bool> get_json_object_bool_field(JsonObject &object, Slice name, bool is_ } Result<int32> get_json_object_int_field(JsonObject &object, Slice name, bool is_optional, int32 default_value) { - TRY_RESULT(value, get_json_object_field(object, name, JsonValue::Type::Number, is_optional)); - if (value.type() == JsonValue::Type::Null) { + for (auto &field_value : object) { + if (field_value.first == name) { + if (field_value.second.type() == JsonValue::Type::String) { + return to_integer_safe<int32>(field_value.second.get_string()); + } + if (field_value.second.type() == JsonValue::Type::Number) { + return to_integer_safe<int32>(field_value.second.get_number()); + } + + return Status::Error(400, PSLICE() << "Field \"" << name << "\" must be of type Number"); + } + } + if (is_optional) { return default_value; } - return to_integer_safe<int32>(value.get_number()); + return Status::Error(400, PSLICE() << "Can't find field \"" << name << "\""); +} + +Result<int64> get_json_object_long_field(JsonObject &object, Slice name, bool is_optional, int64 default_value) { + for (auto &field_value : object) { + if (field_value.first == name) { + if (field_value.second.type() == JsonValue::Type::String) { + return to_integer_safe<int64>(field_value.second.get_string()); + } + if (field_value.second.type() == JsonValue::Type::Number) { + return to_integer_safe<int64>(field_value.second.get_number()); + } + + return Status::Error(400, PSLICE() << "Field \"" << name << "\" must be a Number"); + } + } + if (is_optional) { + return default_value; + } + return Status::Error(400, PSLICE() << "Can't find field \"" << name << "\""); } Result<double> get_json_object_double_field(JsonObject &object, Slice name, bool is_optional, double default_value) { diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h index 735c4b29ec..90035cd6e2 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/JsonBuilder.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,20 +15,11 @@ #include "td/utils/StringBuilder.h" #include <new> -#include <tuple> #include <type_traits> #include <utility> namespace td { -template <class... Args> -std::tuple<const Args &...> ctie(const Args &... args) TD_WARN_UNUSED_RESULT; - -template <class... Args> -std::tuple<const Args &...> ctie(const Args &... args) { - return std::tie(args...); -} - class JsonTrue { public: friend StringBuilder &operator<<(StringBuilder &sb, const JsonTrue &val) { @@ -104,7 +95,7 @@ class JsonFloat { class JsonOneChar { public: - explicit JsonOneChar(unsigned int c) : c_(c) { + explicit JsonOneChar(uint32 c) : c_(c) { } friend StringBuilder &operator<<(StringBuilder &sb, const JsonOneChar &val) { @@ -114,12 +105,12 @@ class JsonOneChar { } private: - unsigned int c_; + uint32 c_; }; class JsonChar { public: - explicit JsonChar(unsigned int c) : c_(c) { + explicit JsonChar(uint32 c) : c_(c) { } friend StringBuilder &operator<<(StringBuilder &sb, const JsonChar &val) { auto c = val.c_; @@ -138,7 +129,7 @@ class JsonChar { } private: - unsigned int c_; + uint32 c_; }; class JsonRaw { @@ -181,7 +172,7 @@ class JsonObjectScope; class JsonBuilder { public: - explicit JsonBuilder(StringBuilder &&sb) : sb_(std::move(sb)) { + explicit JsonBuilder(StringBuilder &&sb = {}, int32 offset = -1) : sb_(std::move(sb)), offset_(offset) { } StringBuilder &string_builder() { return sb_; @@ -191,22 +182,48 @@ class JsonBuilder { JsonArrayScope enter_array() TD_WARN_UNUSED_RESULT; JsonObjectScope enter_object() TD_WARN_UNUSED_RESULT; + int32 offset() const { + return offset_; + } + bool is_pretty() const { + return offset_ >= 0; + } + void print_offset() { + if (offset_ >= 0) { + sb_ << '\n'; + for (int x = 0; x < offset_; x++) { + sb_ << " "; + } + } + } + void dec_offset() { + if (offset_ >= 0) { + CHECK(offset_ > 0); + offset_--; + } + } + void inc_offset() { + if (offset_ >= 0) { + offset_++; + } + } + private: StringBuilder sb_; JsonScope *scope_ = nullptr; + int32 offset_; }; class Jsonable {}; class JsonScope { public: - explicit JsonScope(JsonBuilder *jb) : sb_(&jb->sb_), jb_(jb) { - save_scope_ = jb_->scope_; + explicit JsonScope(JsonBuilder *jb) : sb_(&jb->sb_), jb_(jb), save_scope_(jb->scope_) { jb_->scope_ = this; CHECK(is_active()); } JsonScope(const JsonScope &other) = delete; - JsonScope(JsonScope &&other) : sb_(other.sb_), jb_(other.jb_), save_scope_(other.save_scope_) { + JsonScope(JsonScope &&other) noexcept : sb_(other.sb_), jb_(other.jb_), save_scope_(other.save_scope_) { other.jb_ = nullptr; } JsonScope &operator=(const JsonScope &) = delete; @@ -272,9 +289,7 @@ class JsonScope { *sb_ << x; return *this; } - JsonScope &operator<<(bool x) { - return *this << JsonBool(x); - } + JsonScope &operator<<(bool x) = delete; JsonScope &operator<<(int32 x) { return *this << JsonInt(x); } @@ -284,8 +299,6 @@ class JsonScope { JsonScope &operator<<(double x) { return *this << JsonFloat(x); } - template <class T> - JsonScope &operator<<(const T *x); // not implemented template <size_t N> JsonScope &operator<<(const char (&x)[N]) { return *this << JsonString(Slice(x)); @@ -293,15 +306,12 @@ class JsonScope { JsonScope &operator<<(const char *x) { return *this << JsonString(Slice(x)); } - JsonScope &operator<<(const string &x) { - return *this << JsonString(Slice(x)); - } JsonScope &operator<<(Slice x) { return *this << JsonString(x); } }; -class JsonValueScope : public JsonScope { +class JsonValueScope final : public JsonScope { public: using JsonScope::JsonScope; template <class T> @@ -326,9 +336,10 @@ class JsonValueScope : public JsonScope { bool was_ = false; }; -class JsonArrayScope : public JsonScope { +class JsonArrayScope final : public JsonScope { public: explicit JsonArrayScope(JsonBuilder *jb) : JsonScope(jb) { + jb->inc_offset(); *sb_ << "["; } JsonArrayScope(JsonArrayScope &&other) = default; @@ -338,10 +349,16 @@ class JsonArrayScope : public JsonScope { } } void leave() { + jb_->dec_offset(); + jb_->print_offset(); *sb_ << "]"; } template <class T> JsonArrayScope &operator<<(const T &x) { + return (*this)(x); + } + template <class T> + JsonArrayScope &operator()(const T &x) { enter_value() << x; return *this; } @@ -352,6 +369,7 @@ class JsonArrayScope : public JsonScope { } else { is_first_ = true; } + jb_->print_offset(); return jb_->enter_value(); } @@ -359,9 +377,10 @@ class JsonArrayScope : public JsonScope { bool is_first_ = false; }; -class JsonObjectScope : public JsonScope { +class JsonObjectScope final : public JsonScope { public: explicit JsonObjectScope(JsonBuilder *jb) : JsonScope(jb) { + jb->inc_offset(); *sb_ << "{"; } JsonObjectScope(JsonObjectScope &&other) = default; @@ -371,23 +390,26 @@ class JsonObjectScope : public JsonScope { } } void leave() { + jb_->dec_offset(); + jb_->print_offset(); *sb_ << "}"; } - template <class S, class T> - JsonObjectScope &operator<<(std::tuple<S, T> key_value) { - return *this << std::pair<S, T>(std::get<0>(key_value), std::get<1>(key_value)); - } - template <class S, class T> - JsonObjectScope &operator<<(std::pair<S, T> key_value) { + template <class T> + JsonObjectScope &operator()(Slice key, T &&value) { CHECK(is_active()); if (is_first_) { *sb_ << ","; } else { is_first_ = true; } - jb_->enter_value() << key_value.first; - *sb_ << ":"; - jb_->enter_value() << key_value.second; + jb_->print_offset(); + jb_->enter_value() << key; + if (jb_->is_pretty()) { + *sb_ << " : "; + } else { + *sb_ << ":"; + } + jb_->enter_value() << value; return *this; } JsonObjectScope &operator<<(const JsonRaw &key_value) { @@ -426,7 +448,7 @@ class JsonValue; using JsonObject = vector<std::pair<MutableSlice, JsonValue>>; using JsonArray = vector<JsonValue>; -class JsonValue : public Jsonable { +class JsonValue final : private Jsonable { public: enum class Type { Null, Number, Boolean, String, Array, Object }; @@ -437,10 +459,10 @@ class JsonValue : public Jsonable { ~JsonValue() { destroy(); } - JsonValue(JsonValue &&other) : JsonValue() { + JsonValue(JsonValue &&other) noexcept : JsonValue() { init(std::move(other)); } - JsonValue &operator=(JsonValue &&other) { + JsonValue &operator=(JsonValue &&other) noexcept { if (&other == this) { return *this; } @@ -557,7 +579,7 @@ class JsonValue : public Jsonable { case Type::Object: { auto object = scope->enter_object(); for (auto &key_value : get_object()) { - object << ctie(JsonString(key_value.first), key_value.second); + object(key_value.first, key_value.second); } break; } @@ -645,25 +667,25 @@ class JsonValue : public Jsonable { inline StringBuilder &operator<<(StringBuilder &sb, JsonValue::Type type) { switch (type) { - case JsonValue::Type::Object: - return sb << "JsonObject"; - case JsonValue::Type::Boolean: - return sb << "JsonBoolean"; case JsonValue::Type::Null: - return sb << "JsonNull"; + return sb << "Null"; case JsonValue::Type::Number: - return sb << "JsonNumber"; - case JsonValue::Type::Array: - return sb << "JsonArray"; + return sb << "Number"; + case JsonValue::Type::Boolean: + return sb << "Boolean"; case JsonValue::Type::String: - return sb << "JsonString"; + return sb << "String"; + case JsonValue::Type::Array: + return sb << "Array"; + case JsonValue::Type::Object: + return sb << "Object"; default: UNREACHABLE(); return sb; } } -class VirtuallyJsonable : public Jsonable { +class VirtuallyJsonable : private Jsonable { public: virtual void store(JsonValueScope *scope) const = 0; VirtuallyJsonable() = default; @@ -674,11 +696,11 @@ class VirtuallyJsonable : public Jsonable { virtual ~VirtuallyJsonable() = default; }; -class VirtuallyJsonableInt : public VirtuallyJsonable { +class VirtuallyJsonableInt final : public VirtuallyJsonable { public: explicit VirtuallyJsonableInt(int32 value) : value_(value) { } - void store(JsonValueScope *scope) const override { + void store(JsonValueScope *scope) const final { *scope << JsonInt(value_); } @@ -686,11 +708,11 @@ class VirtuallyJsonableInt : public VirtuallyJsonable { int32 value_; }; -class VirtuallyJsonableLong : public VirtuallyJsonable { +class VirtuallyJsonableLong final : public VirtuallyJsonable { public: explicit VirtuallyJsonableLong(int64 value) : value_(value) { } - void store(JsonValueScope *scope) const override { + void store(JsonValueScope *scope) const final { *scope << JsonLong(value_); } @@ -698,11 +720,11 @@ class VirtuallyJsonableLong : public VirtuallyJsonable { int64 value_; }; -class VirtuallyJsonableString : public VirtuallyJsonable { +class VirtuallyJsonableString final : public VirtuallyJsonable { public: explicit VirtuallyJsonableString(Slice value) : value_(value) { } - void store(JsonValueScope *scope) const override { + void store(JsonValueScope *scope) const final { *scope << JsonString(value_); } @@ -716,8 +738,8 @@ Status json_string_skip(Parser &parser) TD_WARN_UNUSED_RESULT; Result<JsonValue> do_json_decode(Parser &parser, int32 max_depth) TD_WARN_UNUSED_RESULT; Status do_json_skip(Parser &parser, int32 max_depth) TD_WARN_UNUSED_RESULT; -inline Result<JsonValue> json_decode(MutableSlice from) { - Parser parser(from); +inline Result<JsonValue> json_decode(MutableSlice json) { + Parser parser(json); const int32 DEFAULT_MAX_DEPTH = 100; auto result = do_json_decode(parser, DEFAULT_MAX_DEPTH); if (result.is_ok()) { @@ -730,17 +752,92 @@ inline Result<JsonValue> json_decode(MutableSlice from) { } template <class StrT, class ValT> -StrT json_encode(const ValT &val) { - auto buf_len = 1 << 19; +StrT json_encode(const ValT &val, bool pretty = false) { + auto buf_len = 1 << 18; auto buf = StackAllocator::alloc(buf_len); - JsonBuilder jb(StringBuilder(buf.as_slice())); + JsonBuilder jb(StringBuilder(buf.as_slice(), true), pretty ? 0 : -1); jb.enter_value() << val; - LOG_IF(ERROR, jb.string_builder().is_error()) << "Json buffer overflow"; + if (pretty) { + jb.string_builder() << "\n"; + } + LOG_IF(ERROR, jb.string_builder().is_error()) << "JSON buffer overflow"; auto slice = jb.string_builder().as_cslice(); return StrT(slice.begin(), slice.size()); } -bool has_json_object_field(JsonObject &object, Slice name); +template <class T> +class ToJsonImpl final : private Jsonable { + public: + explicit ToJsonImpl(const T &value) : value_(value) { + } + void store(JsonValueScope *scope) const { + to_json(*scope, value_); + } + + private: + const T &value_; +}; + +template <class T> +auto ToJson(const T &value) { + return ToJsonImpl<T>(value); +} + +template <class T> +void to_json(JsonValueScope &jv, const T &value) { + jv << value; +} + +template <class F> +class JsonObjectImpl : private Jsonable { + public: + explicit JsonObjectImpl(F &&f) : f_(std::forward<F>(f)) { + } + void store(JsonValueScope *scope) const { + auto object = scope->enter_object(); + f_(object); + } + + private: + F f_; +}; + +template <class F> +auto json_object(F &&f) { + return JsonObjectImpl<F>(std::forward<F>(f)); +} + +template <class F> +class JsonArrayImpl : private Jsonable { + public: + explicit JsonArrayImpl(F &&f) : f_(std::forward<F>(f)) { + } + void store(JsonValueScope *scope) const { + auto array = scope->enter_array(); + f_(array); + } + + private: + F f_; +}; + +template <class F> +auto json_array(F &&f) { + return JsonArrayImpl<F>(std::forward<F>(f)); +} + +template <class A, class F> +auto json_array(const A &a, F &&f) { + return json_array([&a, &f](auto &arr) { + for (auto &x : a) { + arr(f(x)); + } + }); +} + +bool has_json_object_field(const JsonObject &object, Slice name); + +JsonValue get_json_object_field_force(JsonObject &object, Slice name) TD_WARN_UNUSED_RESULT; Result<JsonValue> get_json_object_field(JsonObject &object, Slice name, JsonValue::Type type, bool is_optional = true) TD_WARN_UNUSED_RESULT; @@ -751,6 +848,9 @@ Result<bool> get_json_object_bool_field(JsonObject &object, Slice name, bool is_ Result<int32> get_json_object_int_field(JsonObject &object, Slice name, bool is_optional = true, int32 default_value = 0) TD_WARN_UNUSED_RESULT; +Result<int64> get_json_object_long_field(JsonObject &object, Slice name, bool is_optional = true, + int64 default_value = 0) TD_WARN_UNUSED_RESULT; + Result<double> get_json_object_double_field(JsonObject &object, Slice name, bool is_optional = true, double default_value = 0.0) TD_WARN_UNUSED_RESULT; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/List.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/List.h index 1606c44d2b..4f9bb9877f 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/List.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/List.h @@ -1,12 +1,12 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/logging.h" +#include "td/utils/common.h" namespace td { @@ -24,23 +24,23 @@ struct ListNode { ListNode(const ListNode &) = delete; ListNode &operator=(const ListNode &) = delete; - ListNode(ListNode &&other) { + ListNode(ListNode &&other) noexcept { if (other.empty()) { clear(); } else { - ListNode *head = other.prev; - other.remove(); - head->put(this); + init_from(std::move(other)); } } - ListNode &operator=(ListNode &&other) { + ListNode &operator=(ListNode &&other) noexcept { + if (this == &other) { + return *this; + } + this->remove(); if (!other.empty()) { - ListNode *head = other.prev; - other.remove(); - head->put(this); + init_from(std::move(other)); } return *this; @@ -58,11 +58,12 @@ struct ListNode { } void put(ListNode *other) { - other->connect(next); - this->connect(other); + DCHECK(other->empty()); + put_unsafe(other); } void put_back(ListNode *other) { + DCHECK(other->empty()); prev->connect(other); other->connect(this); } @@ -82,11 +83,47 @@ struct ListNode { return next == this; } - private: + ListNode *begin() { + return next; + } + ListNode *end() { + return this; + } + const ListNode *begin() const { + return next; + } + const ListNode *end() const { + return this; + } + ListNode *get_next() { + return next; + } + ListNode *get_prev() { + return prev; + } + const ListNode *get_next() const { + return next; + } + const ListNode *get_prev() const { + return prev; + } + + protected: void clear() { next = this; prev = this; } + + void init_from(ListNode &&other) { + ListNode *head = other.prev; + other.remove(); + head->put_unsafe(this); + } + + void put_unsafe(ListNode *other) { + other->connect(next); + this->connect(other); + } }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MapNode.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MapNode.h new file mode 100644 index 0000000000..cad2ae9b36 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MapNode.h @@ -0,0 +1,166 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" + +#include <new> +#include <type_traits> +#include <utility> + +namespace td { + +template <class KeyT, class ValueT, class Enable = void> +struct MapNode { + using first_type = KeyT; + using second_type = ValueT; + using public_key_type = KeyT; + using public_type = MapNode; + + KeyT first{}; + union { + ValueT second; + }; + + const KeyT &key() const { + return first; + } + + MapNode &get_public() { + return *this; + } + + const MapNode &get_public() const { + return *this; + } + + MapNode() { + } + MapNode(KeyT key, ValueT value) : first(std::move(key)) { + new (&second) ValueT(std::move(value)); + DCHECK(!empty()); + } + MapNode(const MapNode &other) = delete; + MapNode &operator=(const MapNode &other) = delete; + MapNode(MapNode &&other) noexcept { + *this = std::move(other); + } + void operator=(MapNode &&other) noexcept { + DCHECK(empty()); + DCHECK(!other.empty()); + first = std::move(other.first); + other.first = KeyT(); + new (&second) ValueT(std::move(other.second)); + other.second.~ValueT(); + } + ~MapNode() { + if (!empty()) { + second.~ValueT(); + } + } + + void copy_from(const MapNode &other) { + DCHECK(empty()); + DCHECK(!other.empty()); + first = other.first; + new (&second) ValueT(other.second); + } + + bool empty() const { + return is_hash_table_key_empty(first); + } + + void clear() { + DCHECK(!empty()); + first = KeyT(); + second.~ValueT(); + DCHECK(empty()); + } + + template <class... ArgsT> + void emplace(KeyT key, ArgsT &&...args) { + DCHECK(empty()); + first = std::move(key); + new (&second) ValueT(std::forward<ArgsT>(args)...); + DCHECK(!empty()); + } +}; + +template <class KeyT, class ValueT> +struct MapNode<KeyT, ValueT, typename std::enable_if_t<(sizeof(KeyT) + sizeof(ValueT) > 28 * sizeof(void *))>> { + struct Impl { + using first_type = KeyT; + using second_type = ValueT; + + KeyT first{}; + union { + ValueT second; + }; + + template <class InputKeyT, class... ArgsT> + Impl(InputKeyT &&key, ArgsT &&...args) : first(std::forward<InputKeyT>(key)) { + new (&second) ValueT(std::forward<ArgsT>(args)...); + DCHECK(!is_hash_table_key_empty(first)); + } + Impl(const Impl &other) = delete; + Impl &operator=(const Impl &other) = delete; + Impl(Impl &&other) = delete; + void operator=(Impl &&other) = delete; + ~Impl() { + second.~ValueT(); + } + }; + + using first_type = KeyT; + using second_type = ValueT; + using public_key_type = KeyT; + using public_type = Impl; + + unique_ptr<Impl> impl_; + + const KeyT &key() const { + DCHECK(!empty()); + return impl_->first; + } + + Impl &get_public() { + return *impl_; + } + + const Impl &get_public() const { + return *impl_; + } + + MapNode() { + } + MapNode(KeyT key, ValueT value) : impl_(td::make_unique<Impl>(std::move(key), std::move(value))) { + } + + void copy_from(const MapNode &other) { + DCHECK(empty()); + DCHECK(!other.empty()); + impl_ = td::make_unique<Impl>(other.impl_->first, other.impl_->second); + } + + bool empty() const { + return impl_ == nullptr; + } + + void clear() { + DCHECK(!empty()); + impl_ = nullptr; + } + + template <class... ArgsT> + void emplace(KeyT key, ArgsT &&...args) { + DCHECK(empty()); + impl_ = td::make_unique<Impl>(std::move(key), std::forward<ArgsT>(args)...); + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h index aa125df2f7..04b03b60ee 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MemoryLog.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -17,15 +17,27 @@ namespace td { template <int buffer_size = 32 * (1 << 10)> -class MemoryLog : public LogInterface { +class MemoryLog final : public LogInterface { static constexpr size_t MAX_OUTPUT_SIZE = buffer_size / 16 < (8 << 10) ? buffer_size / 16 : (8 << 10); + static_assert((buffer_size & (buffer_size - 1)) == 0, "Buffer size must be power of 2"); + static_assert(buffer_size >= (8 << 10), "Too small buffer size"); + public: MemoryLog() { std::memset(buffer_, ' ', sizeof(buffer_)); } - void append(CSlice new_slice, int log_level) override { + Slice get_buffer() const { + return Slice(buffer_, sizeof(buffer_)); + } + + size_t get_pos() const { + return pos_ & (buffer_size - 1); + } + + private: + void do_append(int log_level, CSlice new_slice) final { Slice slice = new_slice; slice.truncate(MAX_OUTPUT_SIZE); while (!slice.empty() && slice.back() == '\n') { @@ -34,50 +46,34 @@ class MemoryLog : public LogInterface { size_t slice_size = slice.size(); CHECK(slice_size * 3 < buffer_size); size_t pad_size = ((slice_size + 15) & ~15) - slice_size; - constexpr size_t magic_size = 16; - uint32 total_size = static_cast<uint32>(slice_size + pad_size + magic_size); - uint32 real_pos = pos_.fetch_add(total_size, std::memory_order_relaxed); + constexpr size_t MAGIC_SIZE = 16; + auto total_size = static_cast<uint32>(slice_size + pad_size + MAGIC_SIZE); + auto real_pos = pos_.fetch_add(total_size, std::memory_order_relaxed); CHECK((total_size & 15) == 0); uint32 start_pos = real_pos & (buffer_size - 1); uint32 end_pos = start_pos + total_size; if (likely(end_pos <= buffer_size)) { - std::memcpy(&buffer_[start_pos + magic_size], slice.data(), slice_size); - std::memcpy(&buffer_[start_pos + magic_size + slice_size], " ", pad_size); + std::memcpy(&buffer_[start_pos + MAGIC_SIZE], slice.data(), slice_size); + std::memcpy(&buffer_[start_pos + MAGIC_SIZE + slice_size], " ", pad_size); } else { - size_t first = buffer_size - start_pos - magic_size; + size_t first = buffer_size - start_pos - MAGIC_SIZE; size_t second = slice_size - first; - std::memcpy(&buffer_[start_pos + magic_size], slice.data(), first); + std::memcpy(&buffer_[start_pos + MAGIC_SIZE], slice.data(), first); std::memcpy(&buffer_[0], slice.data() + first, second); - std::memcpy(&buffer_[second], " ", pad_size); + std::memcpy(&buffer_[second], " ", pad_size); } CHECK((start_pos & 15) == 0); - CHECK(start_pos <= buffer_size - magic_size); + CHECK(start_pos <= buffer_size - MAGIC_SIZE); buffer_[start_pos] = '\n'; - size_t printed = std::snprintf(&buffer_[start_pos + 1], magic_size - 1, "LOG:%08x: ", real_pos); - CHECK(printed == magic_size - 2); - buffer_[start_pos + magic_size - 1] = ' '; - - if (log_level == VERBOSITY_NAME(FATAL)) { - process_fatal_error(new_slice); - } + size_t printed = std::snprintf(&buffer_[start_pos + 1], MAGIC_SIZE - 1, "LOG:%08x: ", real_pos); + CHECK(printed == MAGIC_SIZE - 2); + buffer_[start_pos + MAGIC_SIZE - 1] = ' '; } - void rotate() override { - } - - Slice get_buffer() const { - return Slice(buffer_, sizeof(buffer_)); - } - - size_t get_pos() const { - return pos_ & (buffer_size - 1); - } - - private: char buffer_[buffer_size]; - std::atomic<uint32> pos_; + std::atomic<uint32> pos_{0}; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp index 75c4fe34b5..a7dde2405f 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h index 11210ceb30..ccaf029a52 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MimeType.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h index 939bf51f28..7a8ef459e0 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MovableValue.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,16 +14,18 @@ class MovableValue { MovableValue() = default; MovableValue(T val) : val_(val) { } - MovableValue(MovableValue &&other) : val_(other.val_) { + MovableValue(MovableValue &&other) noexcept : val_(other.val_) { other.clear(); } - MovableValue &operator=(MovableValue &&other) { - val_ = other.val_; - other.clear(); + MovableValue &operator=(MovableValue &&other) noexcept { + if (this != &other) { + val_ = other.val_; + other.clear(); + } return *this; } - MovableValue(const MovableValue &) = delete; - MovableValue &operator=(const MovableValue &) = delete; + MovableValue(const MovableValue &) = default; + MovableValue &operator=(const MovableValue &) = default; ~MovableValue() = default; void clear() { diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.cpp new file mode 100644 index 0000000000..1fcbc04341 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.cpp @@ -0,0 +1,15 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/MpmcQueue.h" + +namespace td { +namespace detail { + +MpmcStat stat_; + +} // namespace detail +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h index ae65554b72..f1f12a37f4 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcQueue.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,7 +14,7 @@ #include "td/utils/format.h" #include "td/utils/HazardPointers.h" #include "td/utils/logging.h" -#include "td/utils/port/thread.h" +#include "td/utils/port/sleep.h" #include "td/utils/ScopeGuard.h" #include <array> @@ -23,6 +23,7 @@ namespace td { namespace detail { + struct MpmcStat { void alloc_ok(size_t thread_id) { s(thread_id).alloc_ok_cnt++; @@ -64,8 +65,10 @@ struct MpmcStat { return arr[thread_id]; } }; + +extern MpmcStat stat_; + } // namespace detail -//detail::MpmcStat stat_; template <class T> class OneValue { @@ -95,31 +98,35 @@ class OneValue { private: enum Type : int { Empty = 0, Taken, Value }; std::atomic<int> state_{Empty}; - T value_; + T value_{}; }; template <class T> class OneValue<T *> { public: bool set_value(T *value) { - T *was = nullptr; + T *was = Empty(); return state_.compare_exchange_strong(was, value, std::memory_order_acq_rel); } bool get_value(T *&value) { value = state_.exchange(Taken(), std::memory_order_acq_rel); - return value != nullptr; + return value != Empty(); } void reset() { - state_ = nullptr; + state_ = Empty(); } OneValue() { } private: - std::atomic<T *> state_{nullptr}; - T *Taken() { - static T xxx; - return &xxx; + std::atomic<T *> state_{Empty()}; + static T *Empty() { + static int64 xxx; + return reinterpret_cast<T *>(&xxx); + } + static T *Taken() { + static int64 xxx; + return reinterpret_cast<T *>(&xxx); } }; @@ -149,6 +156,11 @@ class MpmcQueueBlock { //returns Ok, Empty or Closed PopStatus try_pop(T &value) { while (true) { + // this check slows 1:1 case but prevents writer starvation in 1:N case + if (write_pos_.load(std::memory_order_relaxed) <= read_pos_.load(std::memory_order_relaxed) && + read_pos_.load(std::memory_order_relaxed) < nodes_.size()) { + return PopStatus::Empty; + } auto read_pos = read_pos_.fetch_add(1, std::memory_order_relaxed); if (read_pos >= nodes_.size()) { return PopStatus::Closed; @@ -199,7 +211,7 @@ class MpmcQueueOld { return "Mpmc queue (fetch and add array queue)"; } MpmcQueueOld(size_t block_size, size_t threads_n) : block_size_{block_size}, hazard_pointers_{threads_n} { - auto node = std::make_unique<Node>(block_size_); + auto node = make_unique<Node>(block_size_); write_pos_ = node.get(); read_pos_ = node.get(); node.release(); @@ -217,7 +229,7 @@ class MpmcQueueOld { delete to_delete; } //stat_.dump(); - //stat_ = MpmcStat(); + //stat_ = detail::MpmcStat(); } size_t hazard_pointers_to_delele_size_unsafe() const { @@ -231,7 +243,7 @@ class MpmcQueueOld { using PopStatus = typename MpmcQueueBlock<T>::PopStatus; void push(T value, size_t thread_id) { - auto hazard_ptr_holder = hazard_pointers_.get_holder(thread_id, 0); + typename decltype(hazard_pointers_)::Holder hazard_ptr_holder(hazard_pointers_, thread_id, 0); while (true) { auto node = hazard_ptr_holder.protect(write_pos_); auto status = node->block.push(value); @@ -263,7 +275,7 @@ class MpmcQueueOld { } bool try_pop(T &value, size_t thread_id) { - auto hazard_ptr_holder = hazard_pointers_.get_holder(thread_id, 0); + typename decltype(hazard_pointers_)::Holder hazard_ptr_holder(hazard_pointers_, thread_id, 0); while (true) { auto node = hazard_ptr_holder.protect(read_pos_); auto status = node->block.try_pop(value); @@ -293,7 +305,7 @@ class MpmcQueueOld { if (try_pop(value, thread_id)) { return value; } - td::this_thread::yield(); + usleep_for(1); } } @@ -306,9 +318,9 @@ class MpmcQueueOld { MpmcQueueBlock<T> block; //Got pad in MpmcQueueBlock }; - std::atomic<Node *> write_pos_; + std::atomic<Node *> write_pos_{nullptr}; char pad[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)]; - std::atomic<Node *> read_pos_; + std::atomic<Node *> read_pos_{nullptr}; char pad2[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)]; size_t block_size_; HazardPointers<Node, 1> hazard_pointers_; @@ -324,7 +336,7 @@ class MpmcQueue { return "NEW Mpmc queue (fetch and add array queue)"; } MpmcQueue(size_t block_size, size_t threads_n) : hazard_pointers_{threads_n} { - auto node = std::make_unique<Node>(); + auto node = make_unique<Node>(); write_pos_ = node.get(); read_pos_ = node.get(); node.release(); @@ -417,7 +429,7 @@ class MpmcQueue { if (try_pop(value, thread_id)) { return value; } - td::this_thread::yield(); + usleep_for(1); } } @@ -438,9 +450,9 @@ class MpmcQueue { char pad[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)]; //Got pad in MpmcQueueBlock }; - std::atomic<Node *> write_pos_; + std::atomic<Node *> write_pos_{nullptr}; char pad[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)]; - std::atomic<Node *> read_pos_; + std::atomic<Node *> read_pos_{nullptr}; char pad2[TD_CONCURRENCY_PAD - sizeof(std::atomic<Node *>)]; HazardPointers<Node, 1> hazard_pointers_; //Got pad in HazardPointers diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h index 0f48620e63..7ece498ff6 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpmcWaiter.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,61 +7,81 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/port/thread.h" +#include "td/utils/logging.h" +#include "td/utils/port/sleep.h" +#include <algorithm> #include <atomic> #include <condition_variable> #include <mutex> namespace td { -class MpmcWaiter { +class MpmcEagerWaiter { public: - int wait(int yields, uint32 worker_id) { - if (yields < RoundsTillSleepy) { - td::this_thread::yield(); - return yields + 1; - } else if (yields == RoundsTillSleepy) { + struct Slot { + private: + friend class MpmcEagerWaiter; + int yields; + uint32 worker_id; + }; + static void init_slot(Slot &slot, uint32 worker_id) { + slot.yields = 0; + slot.worker_id = worker_id; + } + + void wait(Slot &slot) { + if (slot.yields < RoundsTillSleepy) { + yield(); + slot.yields++; + } else if (slot.yields == RoundsTillSleepy) { auto state = state_.load(std::memory_order_relaxed); if (!State::has_worker(state)) { - auto new_state = State::with_worker(state, worker_id); - if (state_.compare_exchange_strong(state, new_state)) { - td::this_thread::yield(); - return yields + 1; + auto new_state = State::with_worker(state, slot.worker_id); + if (state_.compare_exchange_strong(state, new_state, std::memory_order_acq_rel)) { + yield(); + slot.yields++; + return; } if (state == State::awake()) { - return 0; + slot.yields = 0; + return; } } - td::this_thread::yield(); - return 0; - } else if (yields < RoundsTillAsleep) { + yield(); + slot.yields = 0; + } else if (slot.yields < RoundsTillAsleep) { auto state = state_.load(std::memory_order_acquire); - if (State::still_sleepy(state, worker_id)) { - td::this_thread::yield(); - return yields + 1; + if (State::still_sleepy(state, slot.worker_id)) { + yield(); + slot.yields++; + return; } - return 0; + slot.yields = 0; } else { auto state = state_.load(std::memory_order_acquire); - if (State::still_sleepy(state, worker_id)) { + if (State::still_sleepy(state, slot.worker_id)) { std::unique_lock<std::mutex> lock(mutex_); if (state_.compare_exchange_strong(state, State::asleep(), std::memory_order_acq_rel)) { condition_variable_.wait(lock); } } - return 0; + slot.yields = 0; } } - int stop_wait(int yields, uint32 worker_id) { - if (yields > RoundsTillSleepy) { + void stop_wait(Slot &slot) { + if (slot.yields > RoundsTillSleepy) { notify_cold(); } - return 0; + slot.yields = 0; + } + + void close() { } void notify() { + std::atomic_thread_fence(std::memory_order_seq_cst); if (state_.load(std::memory_order_acquire) == State::awake()) { return; } @@ -90,6 +110,7 @@ class MpmcWaiter { } }; enum { RoundsTillSleepy = 32, RoundsTillAsleep = 64 }; + // enum { RoundsTillSleepy = 1, RoundsTillAsleep = 2 }; std::atomic<uint32> state_{State::awake()}; std::mutex mutex_; std::condition_variable condition_variable_; @@ -101,6 +122,216 @@ class MpmcWaiter { condition_variable_.notify_all(); } } + static void yield() { + // whatever, this is better than sched_yield + usleep_for(1); + } }; +class MpmcSleepyWaiter { + public: + struct Slot { + private: + friend class MpmcSleepyWaiter; + + enum State { Search, Work, Sleep } state_{Work}; + + void park() { + std::unique_lock<std::mutex> guard(mutex_); + condition_variable_.wait(guard, [&] { return unpark_flag_; }); + unpark_flag_ = false; + } + + bool cancel_park() { + auto res = unpark_flag_; + unpark_flag_ = false; + return res; + } + + void unpark() { + //TODO: try to unlock guard before notify_all + std::unique_lock<std::mutex> guard(mutex_); + unpark_flag_ = true; + condition_variable_.notify_all(); + } + + std::mutex mutex_; + std::condition_variable condition_variable_; + bool unpark_flag_{false}; // TODO: move out of lock + int yield_cnt{0}; + int32 worker_id{0}; + + public: + char padding[TD_CONCURRENCY_PAD]; + }; + + // There are a lot of workers + // Each has a slot + // + // States of a worker: + // - searching for work | Search + // - processing work | Work + // - sleeping | Sleep + // + // When somebody adds a work it calls notify + // + // notify + // if there are workers in search phase do nothing. + // if all workers are awake do nothing + // otherwise wake some random worker + // + // Initially all workers are in Search mode. + // + // When worker found nothing it may try to call wait. + // This may put it in a Sleep for some time. + // After wait returns worker will be in Search state again. + // + // Suppose worker found a work and ready to process it. + // Then it may call stop_wait. This will cause transition from + // Search to Work state. + // + // Main invariant: + // After notify is called there should be at least on worker in Search or Work state. + // If possible - in Search state + // + + static void init_slot(Slot &slot, int32 worker_id) { + slot.state_ = Slot::State::Work; + slot.unpark_flag_ = false; + slot.worker_id = worker_id; + VLOG(waiter) << "Init slot " << worker_id; + } + + static constexpr int VERBOSITY_NAME(waiter) = VERBOSITY_NAME(DEBUG) + 10; + void wait(Slot &slot) { + if (slot.state_ == Slot::State::Work) { + VLOG(waiter) << "Work -> Search"; + state_++; + slot.state_ = Slot::State::Search; + slot.yield_cnt = 0; + return; + } + if (slot.state_ == Slot::Search) { + if (slot.yield_cnt++ < 10 && false) { + // TODO some sleep backoff is possible + return; + } + + slot.state_ = Slot::State::Sleep; + std::unique_lock<std::mutex> guard(sleepers_mutex_); + auto state_view = StateView(state_.fetch_add((1 << PARKING_SHIFT) - 1)); + CHECK(state_view.searching_count != 0); + bool should_search = state_view.searching_count == 1; + if (closed_) { + return; + } + sleepers_.push_back(&slot); + LOG_CHECK(slot.unpark_flag_ == false) << slot.worker_id; + VLOG(waiter) << "Add to sleepers " << slot.worker_id; + //guard.unlock(); + if (should_search) { + VLOG(waiter) << "Search -> Search once, then Sleep "; + return; + } + VLOG(waiter) << "Search -> Sleep " << state_view.searching_count << " " << state_view.parked_count; + } + + CHECK(slot.state_ == Slot::State::Sleep); + VLOG(waiter) << "Park " << slot.worker_id; + slot.park(); + VLOG(waiter) << "Resume " << slot.worker_id; + slot.state_ = Slot::State::Search; + slot.yield_cnt = 0; + } + + void stop_wait(Slot &slot) { + if (slot.state_ == Slot::State::Work) { + return; + } + if (slot.state_ == Slot::State::Sleep) { + VLOG(waiter) << "Search once, then Sleep -> Work/Search " << slot.worker_id; + slot.state_ = Slot::State::Work; + std::unique_lock<std::mutex> guard(sleepers_mutex_); + auto it = std::find(sleepers_.begin(), sleepers_.end(), &slot); + if (it != sleepers_.end()) { + sleepers_.erase(it); + VLOG(waiter) << "Remove from sleepers " << slot.worker_id; + state_.fetch_sub((1 << PARKING_SHIFT) - 1); + guard.unlock(); + } else { + guard.unlock(); + VLOG(waiter) << "Not in sleepers" << slot.worker_id; + CHECK(slot.cancel_park()); + } + } + VLOG(waiter) << "Search once, then Sleep -> Work " << slot.worker_id; + slot.state_ = Slot::State::Search; + auto state_view = StateView(state_.fetch_sub(1)); + CHECK(state_view.searching_count != 0); + CHECK(state_view.searching_count < 1000); + bool should_notify = state_view.searching_count == 1; + if (should_notify) { + VLOG(waiter) << "Notify others"; + notify(); + } + VLOG(waiter) << "Search -> Work "; + slot.state_ = Slot::State::Work; + } + + void notify() { + auto view = StateView(state_.load()); + //LOG(ERROR) << view.parked_count; + if (view.searching_count > 0 || view.parked_count == 0) { + VLOG(waiter) << "Ingore notify: " << view.searching_count << " " << view.parked_count; + return; + } + + VLOG(waiter) << "Notify: " << view.searching_count << " " << view.parked_count; + std::unique_lock<std::mutex> guard(sleepers_mutex_); + + view = StateView(state_.load()); + if (view.searching_count > 0) { + VLOG(waiter) << "Skip notify: got searching"; + return; + } + + CHECK(view.parked_count == static_cast<int>(sleepers_.size())); + if (sleepers_.empty()) { + VLOG(waiter) << "Skip notify: no sleepers"; + return; + } + + auto sleeper = sleepers_.back(); + sleepers_.pop_back(); + state_.fetch_sub((1 << PARKING_SHIFT) - 1); + VLOG(waiter) << "Unpark " << sleeper->worker_id; + sleeper->unpark(); + } + + void close() { + StateView state(state_.load()); + LOG_CHECK(state.parked_count == 0) << state.parked_count; + LOG_CHECK(state.searching_count == 0) << state.searching_count; + } + + private: + static constexpr int32 PARKING_SHIFT = 16; + struct StateView { + int32 parked_count; + int32 searching_count; + explicit StateView(int32 x) { + parked_count = x >> PARKING_SHIFT; + searching_count = x & ((1 << PARKING_SHIFT) - 1); + } + }; + std::atomic<int32> state_{0}; + + std::mutex sleepers_mutex_; + vector<Slot *> sleepers_; + + bool closed_ = false; +}; + +using MpmcWaiter = MpmcSleepyWaiter; + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h index 4398c7503d..a1b05c2791 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscLinkQueue.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,7 +7,6 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/logging.h" #include <atomic> @@ -149,17 +148,17 @@ template <class Value> class MpscLinkQueueUniquePtrNode { public: MpscLinkQueueUniquePtrNode() = default; - explicit MpscLinkQueueUniquePtrNode(std::unique_ptr<Value> ptr) : ptr_(std::move(ptr)) { + explicit MpscLinkQueueUniquePtrNode(unique_ptr<Value> ptr) : ptr_(std::move(ptr)) { } MpscLinkQueueImpl::Node *to_mpsc_link_queue_node() { return ptr_.release()->to_mpsc_link_queue_node(); } - static MpscLinkQueueUniquePtrNode<Value> from_mpsc_link_queue_node(td::MpscLinkQueueImpl::Node *node) { - return MpscLinkQueueUniquePtrNode<Value>(std::unique_ptr<Value>(Value::from_mpsc_link_queue_node(node))); + static MpscLinkQueueUniquePtrNode<Value> from_mpsc_link_queue_node(MpscLinkQueueImpl::Node *node) { + return MpscLinkQueueUniquePtrNode<Value>(unique_ptr<Value>(Value::from_mpsc_link_queue_node(node))); } - explicit operator bool() { + explicit operator bool() const noexcept { return ptr_ != nullptr; } @@ -168,7 +167,7 @@ class MpscLinkQueueUniquePtrNode { } private: - std::unique_ptr<Value> ptr_; + unique_ptr<Value> ptr_; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h index 89d2df8693..f6de4bf280 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/MpscPollableQueue.h @@ -1,57 +1,63 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once +#include "td/utils/common.h" #include "td/utils/misc.h" #include "td/utils/port/EventFd.h" -#include "td/utils/SpinLock.h" #if !TD_EVENTFD_UNSUPPORTED -#if !TD_WINDOWS -#include <poll.h> -#include <sched.h> -#endif + +#include "td/utils/port/Mutex.h" #include <utility> namespace td { // interface like in PollableQueue -template <class ValueT> +template <class T> class MpscPollableQueue { public: + using ValueType = T; + int reader_wait_nonblock() { auto ready = reader_vector_.size() - reader_pos_; if (ready != 0) { return narrow_cast<int>(ready); } - auto guard = lock_.lock(); - if (writer_vector_.empty()) { + for (int i = 0; i < 2; i++) { + auto guard = lock_.lock(); + if (writer_vector_.empty()) { + if (i == 1) { + wait_event_fd_ = true; + return 0; + } + } else { + reader_vector_.clear(); + reader_pos_ = 0; + std::swap(writer_vector_, reader_vector_); + return narrow_cast<int>(reader_vector_.size()); + } event_fd_.acquire(); - wait_event_fd_ = true; - return 0; - } else { - reader_vector_.clear(); - reader_pos_ = 0; - std::swap(writer_vector_, reader_vector_); - return narrow_cast<int>(reader_vector_.size()); } + UNREACHABLE(); } - ValueT reader_get_unsafe() { + ValueType reader_get_unsafe() { return std::move(reader_vector_[reader_pos_++]); } void reader_flush() { //nop } - void writer_put(ValueT value) { + void writer_put(ValueType value) { auto guard = lock_.lock(); writer_vector_.push_back(std::move(value)); if (wait_event_fd_) { wait_event_fd_ = false; + guard.reset(); event_fd_.release(); } } @@ -62,6 +68,11 @@ class MpscPollableQueue { //nop } + bool is_empty() { + auto guard = lock_.lock(); + return writer_vector_.empty() && reader_vector_.empty(); + } + void init() { event_fd_.init(); } @@ -75,35 +86,27 @@ class MpscPollableQueue { } } -// Just example of usage -#if !TD_WINDOWS + // Just an example of usage int reader_wait() { int res; - while ((res = reader_wait_nonblock()) == 0) { - // TODO: reader_flush? - pollfd fd; - fd.fd = reader_get_event_fd().get_fd().get_native_fd(); - fd.events = POLLIN; - poll(&fd, 1, -1); + reader_get_event_fd().wait(1000); } return res; } -#endif private: - SpinLock lock_; + Mutex lock_; bool wait_event_fd_{false}; EventFd event_fd_; - std::vector<ValueT> writer_vector_; - std::vector<ValueT> reader_vector_; + std::vector<ValueType> writer_vector_; + std::vector<ValueType> reader_vector_; size_t reader_pos_{0}; }; } // namespace td #else -#include "td/utils/logging.h" namespace td { diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h index 202de5f7d4..251f14b86d 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Named.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/NullLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/NullLog.h new file mode 100644 index 0000000000..71b9ad7d7c --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/NullLog.h @@ -0,0 +1,19 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/logging.h" +#include "td/utils/Slice.h" + +namespace td { + +class NullLog final : public LogInterface { + void do_append(int /*log_level*/, CSlice /*slice*/) final { + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h index e6e4549dbb..8ee2a58566 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ObjectPool.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -45,7 +45,7 @@ class ObjectPool { // It is not very usual case of acquire/release use. // Instead of publishing an object via some flag we do the opposite. // We publish new generation via destruction of the data. - // In usual case if we see a flag then we are able to use an object. + // In usual case if we see a flag, then we are able to use an object. // In our case if we have used an object and it is already invalid, then generation will mismatch bool is_alive() const { if (!storage_) { @@ -84,11 +84,11 @@ class ObjectPool { OwnerPtr() = default; OwnerPtr(const OwnerPtr &) = delete; OwnerPtr &operator=(const OwnerPtr &) = delete; - OwnerPtr(OwnerPtr &&other) : storage_(other.storage_), parent_(other.parent_) { + OwnerPtr(OwnerPtr &&other) noexcept : storage_(other.storage_), parent_(other.parent_) { other.storage_ = nullptr; other.parent_ = nullptr; } - OwnerPtr &operator=(OwnerPtr &&other) { + OwnerPtr &operator=(OwnerPtr &&other) noexcept { if (this != &other) { storage_ = other.storage_; parent_ = other.parent_; @@ -156,7 +156,7 @@ class ObjectPool { }; template <class... ArgsT> - OwnerPtr create(ArgsT &&... args) { + OwnerPtr create(ArgsT &&...args) { Storage *storage = get_storage(); storage->init_data(std::forward<ArgsT>(args)...); return OwnerPtr(storage, this); @@ -189,7 +189,7 @@ class ObjectPool { delete to_delete; storage_count_--; } - CHECK(storage_count_.load() == 0) << storage_count_.load(); + LOG_CHECK(storage_count_.load() == 0) << storage_count_.load(); } private: @@ -201,7 +201,7 @@ class ObjectPool { std::atomic<int32> generation{1}; template <class... ArgsT> - void init_data(ArgsT &&... args) { + void init_data(ArgsT &&...args) { // new (&data) DataT(std::forward<ArgsT>(args)...); data = DataT(std::forward<ArgsT>(args)...); } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h index 8511e0ce8b..33e8bc1a4c 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Observer.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -22,13 +22,13 @@ class ObserverBase { virtual void notify() = 0; }; -class Observer : ObserverBase { +class Observer final : private ObserverBase { public: Observer() = default; explicit Observer(unique_ptr<ObserverBase> &&ptr) : observer_ptr_(std::move(ptr)) { } - void notify() override { + void notify() final { if (observer_ptr_) { observer_ptr_->notify(); } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.cpp new file mode 100644 index 0000000000..76571d4954 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.cpp @@ -0,0 +1,263 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/OptionParser.h" + +#include "td/utils/FlatHashMap.h" +#include "td/utils/logging.h" +#include "td/utils/PathView.h" +#include "td/utils/SliceBuilder.h" + +#if TD_PORT_WINDOWS +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#include "td/utils/port/wstring_convert.h" +#endif +#endif + +#if TD_PORT_WINDOWS +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#include <shellapi.h> +#endif +#endif + +namespace td { + +void OptionParser::set_usage(Slice executable_name, Slice usage) { + PathView path_view(executable_name); + usage_ = PSTRING() << path_view.file_name() << " " << usage; +} + +void OptionParser::set_description(string description) { + description_ = std::move(description); +} + +void OptionParser::add_option(Option::Type type, char short_key, Slice long_key, Slice description, + std::function<Status(Slice)> callback) { + for (auto &option : options_) { + if ((short_key != '\0' && option.short_key == short_key) || (!long_key.empty() && long_key == option.long_key)) { + LOG(ERROR) << "Ignore duplicated option '" << (short_key == '\0' ? '-' : short_key) << "' '" << long_key << "'"; + } + } + options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)}); +} + +void OptionParser::add_checked_option(char short_key, Slice long_key, Slice description, + std::function<Status(Slice)> callback) { + add_option(Option::Type::Arg, short_key, long_key, description, std::move(callback)); +} + +void OptionParser::add_checked_option(char short_key, Slice long_key, Slice description, + std::function<Status(void)> callback) { + add_option(Option::Type::NoArg, short_key, long_key, description, + [callback = std::move(callback)](Slice) { return callback(); }); +} + +void OptionParser::add_option(char short_key, Slice long_key, Slice description, std::function<void(Slice)> callback) { + add_option(Option::Type::Arg, short_key, long_key, description, [callback = std::move(callback)](Slice parameter) { + callback(parameter); + return Status::OK(); + }); +} + +void OptionParser::add_option(char short_key, Slice long_key, Slice description, std::function<void(void)> callback) { + add_option(Option::Type::NoArg, short_key, long_key, description, [callback = std::move(callback)](Slice) { + callback(); + return Status::OK(); + }); +} + +void OptionParser::add_check(std::function<Status()> check) { + checks_.push_back(std::move(check)); +} + +Result<vector<char *>> OptionParser::run(int argc, char *argv[], int expected_non_option_count) { +#if TD_PORT_WINDOWS +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + LPWSTR *utf16_argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (utf16_argv == nullptr) { + return Status::Error("Failed to parse command line"); + } + vector<string> args_storage(argc); + vector<char *> args(argc); + for (int i = 0; i < argc; i++) { + TRY_RESULT_ASSIGN(args_storage[i], from_wstring(utf16_argv[i])); + args[i] = &args_storage[i][0]; + } + LocalFree(utf16_argv); + argv = &args[0]; +#endif +#endif + + return run_impl(argc, argv, expected_non_option_count); +} + +Result<vector<char *>> OptionParser::run_impl(int argc, char *argv[], int expected_non_option_count) { + FlatHashMap<char, const Option *> short_options; + FlatHashMap<string, const Option *> long_options; + for (auto &opt : options_) { + if (opt.short_key != '\0') { + short_options[opt.short_key] = &opt; + } + if (!opt.long_key.empty()) { + long_options[opt.long_key] = &opt; + } + } + + vector<char *> non_options; + for (int arg_pos = 1; arg_pos < argc; arg_pos++) { + const char *arg = argv[arg_pos]; + if (arg[0] != '-' || arg[1] == '\0') { + non_options.push_back(argv[arg_pos]); + continue; + } + if (arg[1] == '-' && arg[2] == '\0') { + // "--"; after it everything is non-option + while (++arg_pos < argc) { + non_options.push_back(argv[arg_pos]); + } + break; + } + + if (arg[1] == '-') { + // long option + Slice long_arg(arg + 2); + Slice parameter; + auto equal_pos = long_arg.find('='); + bool has_equal = equal_pos != Slice::npos; + if (has_equal) { + parameter = long_arg.substr(equal_pos + 1); + long_arg = long_arg.substr(0, equal_pos); + } + + auto it = long_options.find(long_arg.str()); + if (it == long_options.end()) { + return Status::Error(PSLICE() << "Option \"" << long_arg << "\" is unrecognized"); + } + + auto option = it->second; + switch (option->type) { + case Option::Type::NoArg: + if (has_equal) { + return Status::Error(PSLICE() << "Option \"" << long_arg << "\" must not have an argument"); + } + break; + case Option::Type::Arg: + if (!has_equal) { + if (++arg_pos == argc) { + return Status::Error(PSLICE() << "Option \"" << long_arg << "\" requires an argument"); + } + parameter = Slice(argv[arg_pos]); + } + break; + default: + UNREACHABLE(); + } + + TRY_STATUS(option->arg_callback(parameter)); + continue; + } + + for (size_t opt_pos = 1; arg[opt_pos] != '\0'; opt_pos++) { + auto it = short_options.find(arg[opt_pos]); + if (it == short_options.end()) { + return Status::Error(PSLICE() << "Option \"" << arg[opt_pos] << "\" is unrecognized"); + } + + auto option = it->second; + Slice parameter; + switch (option->type) { + case Option::Type::NoArg: + // nothing to do + break; + case Option::Type::Arg: + if (arg[opt_pos + 1] == '\0') { + if (++arg_pos == argc) { + return Status::Error(PSLICE() << "Option \"" << arg[opt_pos] << "\" requires an argument"); + } + parameter = Slice(argv[arg_pos]); + } else { + parameter = Slice(arg + opt_pos + 1); + opt_pos += parameter.size(); + } + break; + default: + UNREACHABLE(); + } + + TRY_STATUS(option->arg_callback(parameter)); + } + } + if (expected_non_option_count >= 0 && non_options.size() != static_cast<size_t>(expected_non_option_count)) { + if (expected_non_option_count == 0) { + return Status::Error("Unexpected non-option parameters specified"); + } + if (non_options.size() > static_cast<size_t>(expected_non_option_count)) { + return Status::Error("Too many non-option parameters specified"); + } else { + return Status::Error("Too few non-option parameters specified"); + } + } + for (auto &check : checks_) { + TRY_STATUS(check()); + } + + return std::move(non_options); +} + +StringBuilder &operator<<(StringBuilder &sb, const OptionParser &o) { + if (!o.usage_.empty()) { + sb << "Usage: " << o.usage_ << "\n\n"; + } + if (!o.description_.empty()) { + sb << o.description_ << ". "; + } + sb << "Options:\n"; + + size_t max_length = 0; + for (auto &opt : o.options_) { + size_t length = 2; + if (!opt.long_key.empty()) { + length += 4 + opt.long_key.size(); + } + if (opt.type != OptionParser::Option::Type::NoArg) { + length += 6; + } + if (length > max_length) { + max_length = length; + } + } + max_length++; + + for (auto &opt : o.options_) { + bool has_short_key = opt.short_key != '\0'; + sb << " "; + size_t length = max_length; + if (has_short_key) { + sb << '-' << opt.short_key; + } else { + sb << " "; + } + length -= 2; + if (!opt.long_key.empty()) { + if (has_short_key) { + sb << ", "; + } else { + sb << " "; + } + sb << "--" << opt.long_key; + length -= 4 + opt.long_key.size(); + } + if (opt.type != OptionParser::Option::Type::NoArg) { + sb << "=<arg>"; + length -= 6; + } + sb << string(length, ' ') << opt.description; + sb << '\n'; + } + return sb; +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.h new file mode 100644 index 0000000000..82d4296dca --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionParser.h @@ -0,0 +1,81 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/misc.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" + +#include <functional> + +namespace td { + +class OptionParser { + class Option { + public: + enum class Type { NoArg, Arg }; + Type type; + char short_key; + string long_key; + string description; + std::function<Status(Slice)> arg_callback; + }; + + void add_option(Option::Type type, char short_key, Slice long_key, Slice description, + std::function<Status(Slice)> callback); + + public: + template <class T> + static std::function<Status(Slice)> parse_integer(T &value) { + return [&value](Slice value_str) { + TRY_RESULT_ASSIGN(value, to_integer_safe<T>(value_str)); + return Status::OK(); + }; + } + + static std::function<void(Slice)> parse_string(string &value) { + return [&value](Slice value_str) { + value = value_str.str(); + }; + } + + void set_usage(Slice executable_name, Slice usage); + + void set_description(string description); + + void add_checked_option(char short_key, Slice long_key, Slice description, std::function<Status(Slice)> callback); + + void add_checked_option(char short_key, Slice long_key, Slice description, std::function<Status(void)> callback); + + void add_option(char short_key, Slice long_key, Slice description, std::function<Status(Slice)> callback) = delete; + + void add_option(char short_key, Slice long_key, Slice description, std::function<Status(void)> callback) = delete; + + void add_option(char short_key, Slice long_key, Slice description, std::function<void(Slice)> callback); + + void add_option(char short_key, Slice long_key, Slice description, std::function<void(void)> callback); + + void add_check(std::function<Status()> check); + + // returns found non-option parameters + Result<vector<char *>> run(int argc, char *argv[], int expected_non_option_count = -1) TD_WARN_UNUSED_RESULT; + + // for testing only + Result<vector<char *>> run_impl(int argc, char *argv[], int expected_non_option_count) TD_WARN_UNUSED_RESULT; + + friend StringBuilder &operator<<(StringBuilder &sb, const OptionParser &o); + + private: + vector<Option> options_; + vector<std::function<Status()>> checks_; + string usage_; + string description_; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionsParser.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionsParser.h deleted file mode 100644 index 6ac0385575..0000000000 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/OptionsParser.h +++ /dev/null @@ -1,150 +0,0 @@ -// -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -#pragma once - -#include "td/utils/common.h" -#include "td/utils/logging.h" -#include "td/utils/Slice.h" -#include "td/utils/Status.h" -#include "td/utils/StringBuilder.h" - -#include <functional> -#include <string> - -#if !TD_WINDOWS -#include <getopt.h> -#endif - -namespace td { - -class OptionsParser { - public: - class Option { - public: - enum Type { NoArg, Arg, OptionalArg }; - Type type; - char short_key; - std::string long_key; - std::string description; - std::function<Status(Slice)> arg_callback; - }; - - void set_description(std::string description) { - description_ = std::move(description); - } - - void add_option(Option::Type type, char short_key, Slice long_key, Slice description, - std::function<Status(Slice)> callback) { - options_.push_back(Option{type, short_key, long_key.str(), description.str(), std::move(callback)}); - } - - void add_option(char short_key, Slice long_key, Slice description, std::function<Status(Slice)> callback) { - add_option(Option::Type::Arg, short_key, long_key, description, std::move(callback)); - } - - void add_option(char short_key, Slice long_key, Slice description, std::function<Status(void)> callback) { - // Ouch. There must be some better way - add_option(Option::Type::NoArg, short_key, long_key, description, - std::bind([](std::function<Status(void)> &func, Slice) { return func(); }, std::move(callback), - std::placeholders::_1)); - } - - Result<int> run(int argc, char *argv[]) TD_WARN_UNUSED_RESULT { -#if TD_WINDOWS - return -1; -#else - // use getopt. long keys are not supported for now - char buff[1024]; - StringBuilder sb({buff, sizeof(buff)}); - for (auto &opt : options_) { - CHECK(opt.type != Option::OptionalArg); - sb << opt.short_key; - if (opt.type == Option::Arg) { - sb << ":"; - } - } - if (sb.is_error()) { - return Status::Error("Can't parse options"); - } - CSlice short_options = sb.as_cslice(); - - vector<option> long_options; - for (auto &opt : options_) { - if (opt.long_key.empty()) { - continue; - } - option o; - o.flag = nullptr; - o.val = opt.short_key; - o.has_arg = opt.type == Option::Arg ? required_argument : no_argument; - o.name = opt.long_key.c_str(); - long_options.push_back(o); - } - long_options.push_back({nullptr, 0, nullptr, 0}); - - while (true) { - int opt_i = getopt_long(argc, argv, short_options.c_str(), &long_options[0], nullptr); - if (opt_i == ':') { - return Status::Error("Missing argument"); - } - if (opt_i == '?') { - return Status::Error("Unrecognized option"); - } - if (opt_i == -1) { - break; - } - bool found = false; - for (auto &opt : options_) { - if (opt.short_key == opt_i) { - Slice arg; - if (opt.type == Option::Arg) { - arg = Slice(optarg); - } - auto status = opt.arg_callback(arg); - if (status.is_error()) { - return std::move(status); - } - found = true; - break; - } - } - if (!found) { - return Status::Error("Unknown argument"); - } - } - return optind; -#endif - } - - friend StringBuilder &operator<<(StringBuilder &sb, const OptionsParser &o) { - sb << o.description_ << "\n"; - for (auto &opt : o.options_) { - sb << "-" << opt.short_key; - if (!opt.long_key.empty()) { - sb << "|--" << opt.long_key; - } - if (opt.type == Option::OptionalArg) { - sb << "["; - } - if (opt.type != Option::NoArg) { - sb << "<arg>"; - } - if (opt.type == Option::OptionalArg) { - sb << "]"; - } - sb << "\t" << opt.description; - sb << "\n"; - } - return sb; - } - - private: - std::vector<Option> options_; - std::string description_; -}; - -} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h index 4515b74684..8b3474ab57 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/OrderedEventsProcessor.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -23,9 +23,21 @@ class OrderedEventsProcessor { explicit OrderedEventsProcessor(SeqNo offset) : offset_(offset), begin_(offset_), end_(offset_) { } + template <class FunctionT> + void clear(FunctionT &&function) { + for (auto &it : data_array_) { + if (it.second) { + function(std::move(it.first)); + } + } + *this = OrderedEventsProcessor(); + } + void clear() { + *this = OrderedEventsProcessor(); + } template <class FromDataT, class FunctionT> void add(SeqNo seq_no, FromDataT &&data, FunctionT &&function) { - CHECK(seq_no >= begin_) << seq_no << ">=" << begin_; // or ignore? + LOG_CHECK(seq_no >= begin_) << seq_no << ">=" << begin_; // or ignore? if (seq_no == begin_) { // run now begin_++; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h index 06e95bf807..731f885171 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Parser.h @@ -1,14 +1,14 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/format.h" -#include "td/utils/logging.h" +#include "td/utils/common.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" #include <cstring> @@ -16,14 +16,17 @@ namespace td { -class Parser { +namespace detail { + +template <class SliceT> +class ParserImpl { public: - explicit Parser(MutableSlice data) : ptr_(data.begin()), end_(data.end()), status_() { + explicit ParserImpl(SliceT data) : ptr_(data.begin()), end_(data.end()), status_() { } - Parser(Parser &&other) : ptr_(other.ptr_), end_(other.end_), status_(std::move(other.status_)) { + ParserImpl(ParserImpl &&other) noexcept : ptr_(other.ptr_), end_(other.end_), status_(std::move(other.status_)) { other.clear(); } - Parser &operator=(Parser &&other) { + ParserImpl &operator=(ParserImpl &&other) noexcept { if (&other == this) { return *this; } @@ -33,70 +36,70 @@ class Parser { other.clear(); return *this; } - Parser(const Parser &) = delete; - Parser &operator=(const Parser &) = delete; - ~Parser() = default; + ParserImpl(const ParserImpl &) = delete; + ParserImpl &operator=(const ParserImpl &) = delete; + ~ParserImpl() = default; bool empty() const { return ptr_ == end_; } void clear() { - ptr_ = nullptr; + ptr_ = SliceT().begin(); end_ = ptr_; status_ = Status::OK(); } - MutableSlice read_till_nofail(char c) { + SliceT read_till_nofail(char c) { if (status_.is_error()) { - return MutableSlice(); + return SliceT(); } - char *till = reinterpret_cast<char *>(std::memchr(ptr_, c, end_ - ptr_)); + auto till = static_cast<decltype(ptr_)>(std::memchr(ptr_, c, end_ - ptr_)); if (till == nullptr) { till = end_; } - MutableSlice result(ptr_, till); + SliceT result(ptr_, till); ptr_ = till; return result; } - MutableSlice read_till_nofail(Slice str) { + SliceT read_till_nofail(Slice str) { if (status_.is_error()) { - return MutableSlice(); + return SliceT(); } - char *best_till = end_; + auto best_till = end_; for (auto c : str) { - char *till = reinterpret_cast<char *>(std::memchr(ptr_, c, end_ - ptr_)); + auto till = static_cast<decltype(ptr_)>(std::memchr(ptr_, c, end_ - ptr_)); if (till != nullptr && till < best_till) { best_till = till; } } - MutableSlice result(ptr_, best_till); + SliceT result(ptr_, best_till); ptr_ = best_till; return result; } template <class F> - MutableSlice read_while(const F &f) { + SliceT read_while(const F &f) { auto save_ptr = ptr_; while (ptr_ != end_ && f(*ptr_)) { ptr_++; } - return MutableSlice(save_ptr, ptr_); + return SliceT(save_ptr, ptr_); } - MutableSlice read_all() { + SliceT read_all() { auto save_ptr = ptr_; ptr_ = end_; - return MutableSlice(save_ptr, ptr_); + return SliceT(save_ptr, ptr_); } - MutableSlice read_till(char c) { + SliceT read_till(char c) { if (status_.is_error()) { - return MutableSlice(); + return SliceT(); } - MutableSlice res = read_till_nofail(c); + SliceT res = read_till_nofail(c); if (ptr_ == end_ || ptr_[0] != c) { - status_ = Status::Error(PSLICE() << "Read till " << tag("char", c) << " failed"); - return MutableSlice(); + status_ = Status::Error(PSLICE() << "Read till '" << c << "' failed"); + return SliceT(); } return res; } @@ -122,7 +125,7 @@ class Parser { return; } if (ptr_ == end_ || ptr_[0] != c) { - status_ = Status::Error(PSLICE() << "Skip " << tag("char", c) << " failed"); + status_ = Status::Error(PSLICE() << "Skip '" << c << "' failed"); return; } ptr_++; @@ -135,6 +138,14 @@ class Parser { return false; } + bool try_skip(Slice prefix) { + if (prefix.size() > static_cast<size_t>(end_ - ptr_) || prefix != Slice(ptr_, prefix.size())) { + return false; + } + advance(prefix.size()); + return true; + } + void skip_till_not(Slice str) { while (ptr_ != end_) { if (std::memchr(str.data(), *ptr_, str.size()) == nullptr) { @@ -146,38 +157,33 @@ class Parser { void skip_whitespaces() { skip_till_not(" \t\r\n"); } + SliceT read_word() { + skip_whitespaces(); + return read_till_nofail(" \t\r\n"); + } - MutableSlice data() const { - return MutableSlice(ptr_, end_); + SliceT data() const { + return SliceT(ptr_, end_); } Status &status() { return status_; } - bool start_with(Slice prefix) { - if (prefix.size() + ptr_ > end_) { - return false; - } - return std::memcmp(prefix.begin(), ptr_, prefix.size()) == 0; - } - - bool skip_start_with(Slice prefix) { - if (start_with(prefix)) { - advance(prefix.size()); - return true; - } - return false; - } - void advance(size_t diff) { ptr_ += diff; CHECK(ptr_ <= end_); } private: - char *ptr_; - char *end_; + decltype(std::declval<SliceT>().begin()) ptr_; + decltype(std::declval<SliceT>().end()) end_; Status status_; }; + +} // namespace detail + +using Parser = detail::ParserImpl<MutableSlice>; +using ConstParser = detail::ParserImpl<Slice>; + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.cpp new file mode 100644 index 0000000000..943d8a8c84 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.cpp @@ -0,0 +1,70 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/PathView.h" + +#include "td/utils/common.h" +#include "td/utils/misc.h" + +namespace td { + +PathView::PathView(Slice path) : path_(path) { + last_slash_ = narrow_cast<int32>(path_.size()) - 1; + while (last_slash_ >= 0 && !is_slash(path_[last_slash_])) { + last_slash_--; + } + + last_dot_ = static_cast<int32>(path_.size()); + for (auto i = last_dot_ - 1; i > last_slash_ + 1; i--) { + if (path_[i] == '.') { + last_dot_ = i; + break; + } + } +} + +Slice PathView::parent_dir_noslash() const { + if (last_slash_ < 0) { + return Slice("."); + } + if (last_slash_ == 0) { + static char buf[1]; + buf[0] = TD_DIR_SLASH; + return Slice(buf, 1); + } + return path_.substr(0, last_slash_); +} + +Slice PathView::relative(Slice path, Slice dir, bool force) { + if (begins_with(path, dir)) { + path.remove_prefix(dir.size()); + return path; + } + if (force) { + return Slice(); + } + return path; +} + +Slice PathView::dir_and_file(Slice path) { + auto last_slash = static_cast<int32>(path.size()) - 1; + while (last_slash >= 0 && !is_slash(path[last_slash])) { + last_slash--; + } + if (last_slash < 0) { + return Slice(); + } + last_slash--; + while (last_slash >= 0 && !is_slash(path[last_slash])) { + last_slash--; + } + if (last_slash < 0) { + return Slice(); + } + return path.substr(last_slash + 1); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h index edb5d7c127..1bafc1d038 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/PathView.h @@ -1,32 +1,18 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/misc.h" #include "td/utils/Slice.h" namespace td { class PathView { public: - explicit PathView(Slice path) : path_(path) { - last_slash_ = narrow_cast<int32>(path_.size()) - 1; - while (last_slash_ >= 0 && !is_slash(path_[last_slash_])) { - last_slash_--; - } - - last_dot_ = static_cast<int32>(path_.size()); - for (auto i = last_dot_ - 1; i > last_slash_ + 1; i--) { - if (path_[i] == '.') { - last_dot_ = i; - break; - } - } - } + explicit PathView(Slice path); bool empty() const { return path_.empty(); @@ -40,8 +26,9 @@ class PathView { } Slice parent_dir() const { - return Slice(path_.begin(), last_slash_ + 1); + return path_.substr(0, last_slash_ + 1); } + Slice parent_dir_noslash() const; Slice extension() const { if (last_dot_ == static_cast<int32>(path_.size())) { @@ -51,7 +38,7 @@ class PathView { } Slice without_extension() const { - return Slice(path_.begin(), last_dot_); + return path_.substr(0, last_dot_); } Slice file_stem() const { @@ -62,6 +49,10 @@ class PathView { return path_.substr(last_slash_ + 1); } + Slice file_name_without_extension() const { + return path_.substr(last_slash_ + 1, last_dot_ - last_slash_ - 1); + } + Slice path() const { return path_; } @@ -74,34 +65,8 @@ class PathView { return !is_absolute(); } - static Slice relative(Slice path, Slice dir, bool force = false) { - if (begins_with(path, dir)) { - path.remove_prefix(dir.size()); - return path; - } - if (force) { - return Slice(); - } - return path; - } - - static Slice dir_and_file(Slice path) { - auto last_slash = static_cast<int32>(path.size()) - 1; - while (last_slash >= 0 && !is_slash(path[last_slash])) { - last_slash--; - } - if (last_slash < 0) { - return Slice(); - } - last_slash--; - while (last_slash >= 0 && !is_slash(path[last_slash])) { - last_slash--; - } - if (last_slash < 0) { - return Slice(); - } - return path.substr(last_slash + 1); - } + static Slice relative(Slice path, Slice dir, bool force = false); + static Slice dir_and_file(Slice path); private: static bool is_slash(char c) { diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Promise.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Promise.h new file mode 100644 index 0000000000..4e83045d61 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Promise.h @@ -0,0 +1,373 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/CancellationToken.h" +#include "td/utils/common.h" +#include "td/utils/invoke.h" +#include "td/utils/MovableValue.h" +#include "td/utils/Status.h" + +#include <tuple> +#include <type_traits> +#include <utility> + +namespace td { + +template <class T = Unit> +class PromiseInterface { + public: + PromiseInterface() = default; + PromiseInterface(const PromiseInterface &) = delete; + PromiseInterface &operator=(const PromiseInterface &) = delete; + PromiseInterface(PromiseInterface &&) = default; + PromiseInterface &operator=(PromiseInterface &&) = default; + virtual ~PromiseInterface() = default; + + virtual void set_value(T &&value) { + set_result(std::move(value)); + } + virtual void set_error(Status &&error) { + set_result(std::move(error)); + } + virtual void set_result(Result<T> &&result) { + if (result.is_ok()) { + set_value(result.move_as_ok()); + } else { + set_error(result.move_as_error()); + } + } + + virtual bool is_cancellable() const { + return false; + } + virtual bool is_canceled() const { + return false; + } +}; + +template <class T> +class SafePromise; + +template <class T = Unit> +class Promise; + +namespace detail { + +template <typename T> +struct GetArg final : public GetArg<decltype(&T::operator())> {}; + +template <class C, class R, class Arg> +class GetArg<R (C::*)(Arg)> { + public: + using type = Arg; +}; +template <class C, class R, class Arg> +class GetArg<R (C::*)(Arg) const> { + public: + using type = Arg; +}; + +template <class T> +using get_arg_t = std::decay_t<typename GetArg<T>::type>; + +template <class T> +struct DropResult { + using type = T; +}; + +template <class T> +struct DropResult<Result<T>> { + using type = T; +}; + +template <class T> +using drop_result_t = typename DropResult<T>::type; + +template <class ValueT, class FunctionT> +class LambdaPromise : public PromiseInterface<ValueT> { + enum class State : int32 { Empty, Ready, Complete }; + + public: + void set_value(ValueT &&value) override { + CHECK(state_.get() == State::Ready); + do_ok(std::move(value)); + state_ = State::Complete; + } + + void set_error(Status &&error) override { + if (state_.get() == State::Ready) { + do_error(std::move(error)); + state_ = State::Complete; + } + } + LambdaPromise(const LambdaPromise &other) = delete; + LambdaPromise &operator=(const LambdaPromise &other) = delete; + LambdaPromise(LambdaPromise &&other) = default; + LambdaPromise &operator=(LambdaPromise &&other) = default; + ~LambdaPromise() override { + if (state_.get() == State::Ready) { + do_error(Status::Error("Lost promise")); + } + } + + template <class FromT> + explicit LambdaPromise(FromT &&func) : func_(std::forward<FromT>(func)), state_(State::Ready) { + } + + private: + FunctionT func_; + MovableValue<State> state_{State::Empty}; + + template <class F = FunctionT> + std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_error(Status &&status) { + func_(Result<ValueT>(std::move(status))); + } + template <class Y, class F = FunctionT> + std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_error(Y &&status) { + func_(Auto()); + } + template <class F = FunctionT> + std::enable_if_t<is_callable<F, Result<ValueT>>::value, void> do_ok(ValueT &&value) { + func_(Result<ValueT>(std::move(value))); + } + template <class F = FunctionT> + std::enable_if_t<!is_callable<F, Result<ValueT>>::value, void> do_ok(ValueT &&value) { + func_(std::move(value)); + } +}; + +template <class T> +struct is_promise_interface : std::false_type {}; + +template <class U> +struct is_promise_interface<PromiseInterface<U>> : std::true_type {}; + +template <class U> +struct is_promise_interface<Promise<U>> : std::true_type {}; + +template <class T> +struct is_promise_interface_ptr : std::false_type {}; + +template <class U> +struct is_promise_interface_ptr<unique_ptr<U>> : std::true_type {}; + +template <class T = void, class F = void, std::enable_if_t<std::is_same<T, void>::value, bool> has_t = false> +auto lambda_promise(F &&f) { + return LambdaPromise<drop_result_t<get_arg_t<std::decay_t<F>>>, std::decay_t<F>>(std::forward<F>(f)); +} +template <class T = void, class F = void, std::enable_if_t<!std::is_same<T, void>::value, bool> has_t = true> +auto lambda_promise(F &&f) { + return LambdaPromise<T, std::decay_t<F>>(std::forward<F>(f)); +} + +template <class T, class F, + std::enable_if_t<is_promise_interface<std::decay_t<F>>::value, bool> from_promise_interface = true> +auto &&promise_interface(F &&f) { + return std::forward<F>(f); +} + +template <class T, class F, + std::enable_if_t<!is_promise_interface<std::decay_t<F>>::value, bool> from_promise_interface = false> +auto promise_interface(F &&f) { + return lambda_promise<T>(std::forward<F>(f)); +} + +template <class T, class F, + std::enable_if_t<is_promise_interface_ptr<std::decay_t<F>>::value, bool> from_promise_interface = true> +auto promise_interface_ptr(F &&f) { + return std::forward<F>(f); +} + +template <class T, class F, + std::enable_if_t<!is_promise_interface_ptr<std::decay_t<F>>::value, bool> from_promise_interface = false> +auto promise_interface_ptr(F &&f) { + return td::make_unique<std::decay_t<decltype(promise_interface<T>(std::forward<F>(f)))>>( + promise_interface<T>(std::forward<F>(f))); +} +} // namespace detail + +template <class T> +class Promise { + public: + void set_value(T &&value) { + if (!promise_) { + return; + } + promise_->set_value(std::move(value)); + promise_.reset(); + } + void set_error(Status &&error) { + if (!promise_) { + return; + } + promise_->set_error(std::move(error)); + promise_.reset(); + } + void set_result(Result<T> &&result) { + if (!promise_) { + return; + } + promise_->set_result(std::move(result)); + promise_.reset(); + } + void reset() { + promise_.reset(); + } + bool is_cancellable() const { + if (!promise_) { + return false; + } + return promise_->is_cancellable(); + } + bool is_canceled() const { + if (!promise_) { + return false; + } + return promise_->is_canceled(); + } + unique_ptr<PromiseInterface<T>> release() { + return std::move(promise_); + } + + Promise() = default; + explicit Promise(unique_ptr<PromiseInterface<T>> promise) : promise_(std::move(promise)) { + } + Promise(Auto) { + } + Promise(SafePromise<T> &&other); + Promise &operator=(SafePromise<T> &&other); + template <class F, std::enable_if_t<!std::is_same<std::decay_t<F>, Promise>::value, int> = 0> + Promise(F &&f) : promise_(detail::promise_interface_ptr<T>(std::forward<F>(f))) { + } + + explicit operator bool() const noexcept { + return static_cast<bool>(promise_); + } + + private: + unique_ptr<PromiseInterface<T>> promise_; +}; + +template <class T = Unit> +class SafePromise { + public: + SafePromise(Promise<T> promise, Result<T> result) : promise_(std::move(promise)), result_(std::move(result)) { + } + SafePromise(const SafePromise &other) = delete; + SafePromise &operator=(const SafePromise &other) = delete; + SafePromise(SafePromise &&other) = default; + SafePromise &operator=(SafePromise &&other) = default; + ~SafePromise() { + if (promise_) { + promise_.set_result(std::move(result_)); + } + } + Promise<T> release() { + return std::move(promise_); + } + + private: + Promise<T> promise_; + Result<T> result_; +}; + +template <class T> +Promise<T>::Promise(SafePromise<T> &&other) : Promise(other.release()) { +} + +template <class T> +Promise<T> &Promise<T>::operator=(SafePromise<T> &&other) { + *this = other.release(); + return *this; +} + +namespace detail { +template <class PromiseT> +class CancellablePromise final : public PromiseT { + public: + template <class... ArgsT> + CancellablePromise(CancellationToken cancellation_token, ArgsT &&...args) + : PromiseT(std::forward<ArgsT>(args)...), cancellation_token_(std::move(cancellation_token)) { + } + bool is_cancellable() const final { + return true; + } + bool is_canceled() const final { + return static_cast<bool>(cancellation_token_); + } + + private: + CancellationToken cancellation_token_; +}; + +template <class... ArgsT> +class JoinPromise final : public PromiseInterface<Unit> { + public: + explicit JoinPromise(ArgsT &&...arg) : promises_(std::forward<ArgsT>(arg)...) { + } + void set_value(Unit &&) final { + tuple_for_each(promises_, [](auto &promise) { promise.set_value(Unit()); }); + } + void set_error(Status &&error) final { + tuple_for_each(promises_, [&error](auto &promise) { promise.set_error(error.clone()); }); + } + + private: + std::tuple<std::decay_t<ArgsT>...> promises_; +}; +} // namespace detail + +class PromiseCreator { + public: + template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>> + static Promise<ArgT> lambda(OkT &&ok) { + return Promise<ArgT>(td::make_unique<detail::LambdaPromise<ArgT, std::decay_t<OkT>>>(std::forward<OkT>(ok))); + } + + template <class OkT, class ArgT = detail::drop_result_t<detail::get_arg_t<OkT>>> + static auto cancellable_lambda(CancellationToken cancellation_token, OkT &&ok) { + return Promise<ArgT>(td::make_unique<detail::CancellablePromise<detail::LambdaPromise<ArgT, std::decay_t<OkT>>>>( + std::move(cancellation_token), std::forward<OkT>(ok))); + } + + template <class... ArgsT> + static Promise<> join(ArgsT &&...args) { + return Promise<>(td::make_unique<detail::JoinPromise<ArgsT...>>(std::forward<ArgsT>(args)...)); + } +}; + +inline void set_promises(vector<Promise<Unit>> &promises) { + auto moved_promises = std::move(promises); + promises.clear(); + + for (auto &promise : moved_promises) { + promise.set_value(Unit()); + } +} + +template <class T> +void fail_promises(vector<Promise<T>> &promises, Status &&error) { + CHECK(error.is_error()); + auto moved_promises = std::move(promises); + promises.clear(); + + auto size = moved_promises.size(); + if (size == 0) { + return; + } + size--; + for (size_t i = 0; i < size; i++) { + auto &promise = moved_promises[i]; + if (promise) { + promise.set_error(error.clone()); + } + } + moved_promises[size].set_error(std::move(error)); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp index db11df4dfa..5b4c78d675 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -13,6 +13,7 @@ #include <openssl/rand.h> #endif +#include <atomic> #include <cstring> #include <limits> #include <random> @@ -20,19 +21,35 @@ namespace td { #if TD_HAVE_OPENSSL + +namespace { +std::atomic<int64> random_seed_generation{0}; +} // namespace + void Random::secure_bytes(MutableSlice dest) { Random::secure_bytes(dest.ubegin(), dest.size()); } void Random::secure_bytes(unsigned char *ptr, size_t size) { - constexpr size_t buf_size = 512; + constexpr size_t BUF_SIZE = 512; static TD_THREAD_LOCAL unsigned char *buf; // static zero-initialized static TD_THREAD_LOCAL size_t buf_pos; - if (init_thread_local<unsigned char[]>(buf, buf_size)) { - buf_pos = buf_size; + static TD_THREAD_LOCAL int64 generation; + if (init_thread_local<unsigned char[]>(buf, BUF_SIZE)) { + buf_pos = BUF_SIZE; + generation = 0; + } + if (ptr == nullptr) { + MutableSlice(buf, BUF_SIZE).fill_zero_secure(); + buf_pos = BUF_SIZE; + return; + } + if (generation != random_seed_generation.load(std::memory_order_relaxed)) { + generation = random_seed_generation.load(std::memory_order_acquire); + buf_pos = BUF_SIZE; } - auto ready = min(size, buf_size - buf_pos); + auto ready = min(size, BUF_SIZE - buf_pos); if (ready != 0) { std::memcpy(ptr, buf + buf_pos, ready); buf_pos += ready; @@ -42,8 +59,8 @@ void Random::secure_bytes(unsigned char *ptr, size_t size) { return; } } - if (size < buf_size) { - int err = RAND_bytes(buf, static_cast<int>(buf_size)); + if (size < BUF_SIZE) { + int err = RAND_bytes(buf, static_cast<int>(BUF_SIZE)); // TODO: it CAN fail LOG_IF(FATAL, err != 1); buf_pos = size; @@ -68,6 +85,27 @@ int64 Random::secure_int64() { secure_bytes(reinterpret_cast<unsigned char *>(&res), sizeof(int64)); return res; } + +uint32 Random::secure_uint32() { + uint32 res = 0; + secure_bytes(reinterpret_cast<unsigned char *>(&res), sizeof(uint32)); + return res; +} + +uint64 Random::secure_uint64() { + uint64 res = 0; + secure_bytes(reinterpret_cast<unsigned char *>(&res), sizeof(uint64)); + return res; +} + +void Random::add_seed(Slice bytes, double entropy) { + RAND_add(bytes.data(), static_cast<int>(bytes.size()), entropy); + random_seed_generation++; +} + +void Random::secure_cleanup() { + Random::secure_bytes(nullptr, 0); +} #endif static unsigned int rand_device_helper() { @@ -96,13 +134,70 @@ uint64 Random::fast_uint64() { return static_cast<uint64>((*gen)()); } -int Random::fast(int min, int max) { - if (min == std::numeric_limits<int>::min() && max == std::numeric_limits<int>::max()) { +int Random::fast(int min_value, int max_value) { + if (min_value == std::numeric_limits<int>::min() && max_value == std::numeric_limits<int>::max()) { // to prevent integer overflow and division by zero - min++; + min_value++; + } + DCHECK(min_value <= max_value); + return static_cast<int>(min_value + fast_uint32() % (max_value - min_value + 1)); // TODO signed_cast +} + +double Random::fast(double min_value, double max_value) { + DCHECK(min_value <= max_value); + return min_value + fast_uint32() * 1.0 / std::numeric_limits<uint32>::max() * (max_value - min_value); +} + +bool Random::fast_bool() { + return (fast_uint32() & 1) != 0; +} + +Random::Xorshift128plus::Xorshift128plus(uint64 seed) { + auto next = [&] { + // splitmix64 + seed += static_cast<uint64>(0x9E3779B97F4A7C15ull); + uint64 z = seed; + z = (z ^ (z >> 30)) * static_cast<uint64>(0xBF58476D1CE4E5B9ull); + z = (z ^ (z >> 27)) * static_cast<uint64>(0x94D049BB133111EBull); + return z ^ (z >> 31); + }; + seed_[0] = next(); + seed_[1] = next(); +} + +Random::Xorshift128plus::Xorshift128plus(uint64 seed_a, uint64 seed_b) { + seed_[0] = seed_a; + seed_[1] = seed_b; +} + +uint64 Random::Xorshift128plus::operator()() { + uint64 x = seed_[0]; + const uint64 y = seed_[1]; + seed_[0] = y; + x ^= x << 23; + seed_[1] = x ^ y ^ (x >> 17) ^ (y >> 26); + return seed_[1] + y; +} + +int Random::Xorshift128plus::fast(int min_value, int max_value) { + return static_cast<int>((*this)() % (max_value - min_value + 1) + min_value); +} +int64 Random::Xorshift128plus::fast64(int64 min_value, int64 max_value) { + return static_cast<int64>((*this)() % (max_value - min_value + 1) + min_value); +} + +void Random::Xorshift128plus::bytes(MutableSlice dest) { + int cnt = 0; + uint64 buf = 0; + for (auto &c : dest) { + if (cnt == 0) { + buf = operator()(); + cnt = 8; + } + cnt--; + c = static_cast<char>(buf & 255); + buf >>= 8; } - CHECK(min <= max); - return static_cast<int>(min + fast_uint32() % (max - min + 1)); // TODO signed_cast } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h index efe5d64618..32cb71f770 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Random.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,6 +8,9 @@ #include "td/utils/common.h" #include "td/utils/Slice.h" +#include "td/utils/Span.h" + +#include <utility> namespace td { @@ -18,13 +21,49 @@ class Random { static void secure_bytes(unsigned char *ptr, size_t size); static int32 secure_int32(); static int64 secure_int64(); + static uint32 secure_uint32(); + static uint64 secure_uint64(); + + // works only for current thread + static void add_seed(Slice bytes, double entropy = 0); + static void secure_cleanup(); #endif static uint32 fast_uint32(); static uint64 fast_uint64(); - // distribution is not uniform, min and max are included - static int fast(int min, int max); + // distribution is not uniform, min_value and max_value are included + static int fast(int min_value, int max_value); + static double fast(double min_value, double max_value); + static bool fast_bool(); + + class Fast { + public: + uint64 operator()() { + return fast_uint64(); + } + }; + class Xorshift128plus { + public: + explicit Xorshift128plus(uint64 seed); + Xorshift128plus(uint64 seed_a, uint64 seed_b); + uint64 operator()(); + int fast(int min_value, int max_value); + int64 fast64(int64 min_value, int64 max_value); + void bytes(MutableSlice dest); + + private: + uint64 seed_[2]; + }; }; +template <class T, class R> +void random_shuffle(MutableSpan<T> v, R &rnd) { + for (size_t i = 1; i < v.size(); i++) { + auto pos = static_cast<size_t>(rnd()) % (i + 1); + using std::swap; + swap(v[i], v[pos]); + } +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h index a914ce357c..577514a44b 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ScopeGuard.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,6 +14,7 @@ #include <utility> namespace td { + class Guard { public: Guard() = default; @@ -28,7 +29,7 @@ class Guard { }; template <class FunctionT> -class LambdaGuard : public Guard { +class LambdaGuard final : public Guard { public: explicit LambdaGuard(const FunctionT &func) : func_(func) { } @@ -41,11 +42,11 @@ class LambdaGuard : public Guard { } LambdaGuard &operator=(LambdaGuard &&other) = delete; - void dismiss() { + void dismiss() final { dismissed_ = true; } - ~LambdaGuard() { + ~LambdaGuard() final { if (!dismissed_) { func_(); } @@ -57,8 +58,8 @@ class LambdaGuard : public Guard { }; template <class F> -std::unique_ptr<Guard> create_lambda_guard(F &&f) { - return std::make_unique<LambdaGuard<F>>(std::forward<F>(f)); +unique_ptr<Guard> create_lambda_guard(F &&f) { + return make_unique<LambdaGuard<F>>(std::forward<F>(f)); } template <class F> std::shared_ptr<Guard> create_shared_lambda_guard(F &&f) { @@ -73,4 +74,4 @@ auto operator+(ScopeExit, FunctionT &&func) { } // namespace td -#define SCOPE_EXIT auto TD_CONCAT(SCOPE_EXIT_VAR_, __LINE__) = ::td::ScopeExit() + [&]() +#define SCOPE_EXIT auto TD_CONCAT(SCOPE_EXIT_VAR_, __LINE__) = ::td::ScopeExit() + [&] diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SetNode.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SetNode.h new file mode 100644 index 0000000000..6e5960553d --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SetNode.h @@ -0,0 +1,129 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/HashTableUtils.h" + +#include <type_traits> +#include <utility> + +namespace td { + +template <class KeyT, class Enable = void> +struct SetNode { + using public_key_type = KeyT; + using public_type = const KeyT; + using second_type = KeyT; // TODO: remove second_type? + + KeyT first; + + const KeyT &key() const { + return first; + } + + const KeyT &get_public() { + return first; + } + + SetNode() : first() { + } + explicit SetNode(KeyT key) : first(std::move(key)) { + } + SetNode(const SetNode &other) = delete; + SetNode &operator=(const SetNode &other) = delete; + SetNode(SetNode &&other) noexcept { + *this = std::move(other); + } + void operator=(SetNode &&other) noexcept { + DCHECK(empty()); + DCHECK(!other.empty()); + first = std::move(other.first); + other.first = KeyT(); + } + ~SetNode() = default; + + void copy_from(const SetNode &other) { + DCHECK(empty()); + first = other.first; + DCHECK(!empty()); + } + + bool empty() const { + return is_hash_table_key_empty(first); + } + + void clear() { + first = KeyT(); + DCHECK(empty()); + } + + void emplace(KeyT key) { + first = std::move(key); + } +}; + +template <class KeyT> +struct SetNode<KeyT, typename std::enable_if_t<(sizeof(KeyT) > 28 * sizeof(void *))>> { + struct Impl { + using second_type = KeyT; + + KeyT first; + + template <class InputKeyT> + explicit Impl(InputKeyT &&key) : first(std::forward<InputKeyT>(key)) { + DCHECK(!is_hash_table_key_empty(first)); + } + Impl(const Impl &other) = delete; + Impl &operator=(const Impl &other) = delete; + Impl(Impl &&other) = delete; + void operator=(Impl &&other) = delete; + }; + + using public_key_type = KeyT; + using public_type = const KeyT; + using second_type = KeyT; // TODO: remove second_type? + + unique_ptr<Impl> impl_; + + const KeyT &key() const { + DCHECK(!empty()); + return impl_->first; + } + + const KeyT &get_public() { + DCHECK(!empty()); + return impl_->first; + } + + SetNode() : impl_() { + } + explicit SetNode(KeyT key) : impl_(td::make_unique<Impl>(std::move(key))) { + } + + void copy_from(const SetNode &other) { + DCHECK(empty()); + impl_ = td::make_unique<Impl>(other.impl_->first); + DCHECK(!empty()); + } + + bool empty() const { + return impl_ == nullptr; + } + + void clear() { + DCHECK(!empty()); + impl_ = nullptr; + } + + void emplace(KeyT key) { + DCHECK(empty()); + impl_ = td::make_unique<Impl>(std::move(key)); + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h index dc8512b268..6a7dcc8256 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedObjectPool.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,7 +7,6 @@ #pragma once #include "td/utils/common.h" - #include "td/utils/logging.h" #include "td/utils/MpscLinkQueue.h" @@ -34,7 +33,7 @@ class AtomicRefCnt { } private: - std::atomic<uint64> cnt_; + std::atomic<uint64> cnt_{0}; }; template <class DataT, class DeleterT> @@ -50,7 +49,7 @@ class SharedPtrRaw CHECK(option_magic_ == Magic); } template <class... ArgsT> - void init_data(ArgsT &&... args) { + void init_data(ArgsT &&...args) { new (&option_data_) DataT(std::forward<ArgsT>(args)...); } void destroy_data() { @@ -89,6 +88,7 @@ template <class T, class DeleterT = std::default_delete<T>> class SharedPtr { public: using Raw = detail::SharedPtrRaw<T, DeleterT>; + struct acquire_t {}; SharedPtr() = default; ~SharedPtr() { if (!raw_) { @@ -97,27 +97,36 @@ class SharedPtr { reset(); } explicit SharedPtr(Raw *raw) : raw_(raw) { - raw_->inc(); + if (raw_) { + raw_->inc(); + } + } + SharedPtr(acquire_t, Raw *raw) : raw_(raw) { } SharedPtr(const SharedPtr &other) : SharedPtr(other.raw_) { } SharedPtr &operator=(const SharedPtr &other) { - other.raw_->inc(); + if (this == &other) { + return *this; + } + if (other.raw_) { + other.raw_->inc(); + } reset(other.raw_); return *this; } - SharedPtr(SharedPtr &&other) : raw_(other.raw_) { + SharedPtr(SharedPtr &&other) noexcept : raw_(other.raw_) { other.raw_ = nullptr; } - SharedPtr &operator=(SharedPtr &&other) { + SharedPtr &operator=(SharedPtr &&other) noexcept { reset(other.raw_); other.raw_ = nullptr; return *this; } - bool empty() const { + bool empty() const noexcept { return raw_ == nullptr; } - explicit operator bool() const { + explicit operator bool() const noexcept { return !empty(); } uint64 use_cnt() const { @@ -149,17 +158,20 @@ class SharedPtr { } template <class... ArgsT> - static SharedPtr<T, DeleterT> create(ArgsT &&... args) { - auto raw = std::make_unique<Raw>(DeleterT()); + static SharedPtr<T, DeleterT> create(ArgsT &&...args) { + auto raw = make_unique<Raw>(DeleterT()); raw->init_data(std::forward<ArgsT>(args)...); return SharedPtr<T, DeleterT>(raw.release()); } template <class D, class... ArgsT> - static SharedPtr<T, DeleterT> create_with_deleter(D &&d, ArgsT &&... args) { - auto raw = std::make_unique<Raw>(std::forward<D>(d)); + static SharedPtr<T, DeleterT> create_with_deleter(D &&d, ArgsT &&...args) { + auto raw = make_unique<Raw>(std::forward<D>(d)); raw->init_data(std::forward<ArgsT>(args)...); return SharedPtr<T, DeleterT>(raw.release()); } + bool operator==(const SharedPtr<T, DeleterT> &other) const { + return raw_ == other.raw_; + } private: Raw *raw_{nullptr}; @@ -185,11 +197,11 @@ class SharedObjectPool { while (free_queue_reader_.read()) { free_cnt++; } - CHECK(free_cnt == allocated_.size()) << free_cnt << " " << allocated_.size(); + LOG_CHECK(free_cnt == allocated_.size()) << free_cnt << " " << allocated_.size(); } template <class... ArgsT> - Ptr alloc(ArgsT &&... args) { + Ptr alloc(ArgsT &&...args) { auto *raw = alloc_raw(); raw->init_data(std::forward<ArgsT>(args)...); return Ptr(raw); @@ -202,7 +214,7 @@ class SharedObjectPool { return free_queue_reader_.calc_size(); } - //non thread safe + // non-thread-safe template <class F> void for_each(F &&f) { for (auto &raw : allocated_) { @@ -220,7 +232,7 @@ class SharedObjectPool { if (raw) { return raw; } - allocated_.push_back(std::make_unique<Raw>(deleter())); + allocated_.push_back(make_unique<Raw>(deleter())); return allocated_.back().get(); } @@ -243,7 +255,7 @@ class SharedObjectPool { Raw *get() const { return raw_; } - explicit operator bool() const { + explicit operator bool() const noexcept { return raw_ != nullptr; } @@ -268,7 +280,7 @@ class SharedObjectPool { return Deleter(this); } - std::vector<std::unique_ptr<Raw>> allocated_; + std::vector<unique_ptr<Raw>> allocated_; MpscLinkQueue<Node> free_queue_; typename MpscLinkQueue<Node>::Reader free_queue_reader_; }; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.cpp index 976286b923..df157f4e6d 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.cpp @@ -1,20 +1,17 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include "td/utils/GitInfo.h" +#include "td/utils/SharedSlice.h" -#include "auto/git_info.h" +#include "td/utils/buffer.h" namespace td { -CSlice GitInfo::commit() { - return GIT_COMMIT; -} -bool GitInfo::is_dirty() { - return GIT_DIRTY; +BufferSlice SharedSlice::clone_as_buffer_slice() const { + return BufferSlice{as_slice()}; } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.h new file mode 100644 index 0000000000..e0ea0c17c0 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SharedSlice.h @@ -0,0 +1,382 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Slice.h" + +#include <atomic> +#include <memory> +#include <new> +#include <type_traits> + +namespace td { + +namespace detail { +struct SharedSliceHeader { + explicit SharedSliceHeader(size_t size) : size_{size} { + } + + void inc() { + refcnt_.fetch_add(1, std::memory_order_relaxed); + } + + bool dec() { + return refcnt_.fetch_sub(1, std::memory_order_acq_rel) == 1; + } + + bool is_unique() const { + // NB: race if std::memory_order_relaxed is used + // reader may see a change by a new writer + return refcnt_.load(std::memory_order_acquire) == 1; + } + + size_t size() const { + return size_; + } + + private: + std::atomic<uint64> refcnt_{1}; + size_t size_; +}; + +struct UniqueSliceHeader { + explicit UniqueSliceHeader(size_t size) : size_{size} { + } + + void inc() { + } + + bool dec() { + return true; + } + + bool is_unique() const { + return true; + } + + size_t size() const { + return size_; + } + + private: + size_t size_; +}; + +template <class HeaderT, bool zero_on_destruct = false> +class UnsafeSharedSlice { + public: + UnsafeSharedSlice() = default; + UnsafeSharedSlice clone() const { + if (is_null()) { + return UnsafeSharedSlice(); + } + header()->inc(); + return UnsafeSharedSlice(ptr_.get()); + } + + bool is_null() const { + return !ptr_; + } + + bool is_unique() const { + if (is_null()) { + return true; + } + return header()->is_unique(); + } + + MutableSlice as_mutable_slice() { + if (is_null()) { + return MutableSlice(); + } + return MutableSlice(ptr_.get() + sizeof(HeaderT), header()->size()); + } + + Slice as_slice() const { + if (is_null()) { + return Slice(); + } + return Slice(ptr_.get() + sizeof(HeaderT), header()->size()); + } + + size_t size() const { + if (is_null()) { + return 0; + } + return header()->size(); + } + + static UnsafeSharedSlice create(size_t size) { + static_assert(std::is_standard_layout<HeaderT>::value, "HeaderT must have statdard layout"); + auto ptr = std::make_unique<char[]>(sizeof(HeaderT) + size); + auto header_ptr = new (ptr.get()) HeaderT(size); + CHECK(header_ptr == reinterpret_cast<HeaderT *>(ptr.get())); + + return UnsafeSharedSlice(std::move(ptr)); + } + + static UnsafeSharedSlice create(Slice slice) { + auto res = create(slice.size()); + res.as_mutable_slice().copy_from(slice); + return res; + } + + void clear() { + ptr_.reset(); + } + + private: + explicit UnsafeSharedSlice(char *ptr) : ptr_(ptr) { + } + explicit UnsafeSharedSlice(std::unique_ptr<char[]> from) : ptr_(from.release()) { + } + + HeaderT *header() const { + return reinterpret_cast<HeaderT *>(ptr_.get()); + } + + struct SharedSliceDestructor { + void operator()(char *ptr) { + auto header = reinterpret_cast<HeaderT *>(ptr); + if (header->dec()) { + if (zero_on_destruct) { + MutableSlice(ptr, sizeof(HeaderT) + header->size()).fill_zero_secure(); + } + std::default_delete<char[]>()(ptr); + } + } + }; + + std::unique_ptr<char[], SharedSliceDestructor> ptr_; +}; +} // namespace detail + +class BufferSlice; + +class UniqueSharedSlice; + +class SharedSlice { + using Impl = detail::UnsafeSharedSlice<detail::SharedSliceHeader>; + + public: + SharedSlice() = default; + + explicit SharedSlice(Slice slice) : impl_(Impl::create(slice)) { + } + + explicit SharedSlice(UniqueSharedSlice from); + + SharedSlice(const char *ptr, size_t size) : SharedSlice(Slice(ptr, size)) { + } + + SharedSlice clone() const { + return SharedSlice(impl_.clone()); + } + + Slice as_slice() const { + return impl_.as_slice(); + } + + BufferSlice clone_as_buffer_slice() const; + + operator Slice() const { + return as_slice(); + } + + // like in std::string + const char *data() const { + return as_slice().data(); + } + + char operator[](size_t at) const { + return as_slice()[at]; + } + + bool empty() const { + return size() == 0; + } + + size_t size() const { + return impl_.size(); + } + + // like in std::string + size_t length() const { + return size(); + } + + void clear() { + impl_.clear(); + } + + private: + friend class UniqueSharedSlice; + explicit SharedSlice(Impl impl) : impl_(std::move(impl)) { + } + Impl impl_; +}; + +class UniqueSharedSlice { + using Impl = detail::UnsafeSharedSlice<detail::SharedSliceHeader>; + + public: + UniqueSharedSlice() = default; + + explicit UniqueSharedSlice(size_t size) : impl_(Impl::create(size)) { + } + explicit UniqueSharedSlice(Slice slice) : impl_(Impl::create(slice)) { + } + + UniqueSharedSlice(const char *ptr, size_t size) : UniqueSharedSlice(Slice(ptr, size)) { + } + explicit UniqueSharedSlice(SharedSlice from) : impl_() { + if (from.impl_.is_unique()) { + impl_ = std::move(from.impl_); + } else { + impl_ = Impl::create(from.as_slice()); + } + } + + UniqueSharedSlice copy() const { + return UniqueSharedSlice(as_slice()); + } + + Slice as_slice() const { + return impl_.as_slice(); + } + + MutableSlice as_mutable_slice() { + return impl_.as_mutable_slice(); + } + + operator Slice() const { + return as_slice(); + } + + // like in std::string + char *data() { + return as_mutable_slice().data(); + } + const char *data() const { + return as_slice().data(); + } + char operator[](size_t at) const { + return as_slice()[at]; + } + + bool empty() const { + return size() == 0; + } + + size_t size() const { + return impl_.size(); + } + + // like in std::string + size_t length() const { + return size(); + } + + void clear() { + impl_.clear(); + } + + private: + friend class SharedSlice; + explicit UniqueSharedSlice(Impl impl) : impl_(std::move(impl)) { + } + Impl impl_; +}; + +inline SharedSlice::SharedSlice(UniqueSharedSlice from) : impl_(std::move(from.impl_)) { +} + +template <bool zero_on_destruct> +class UniqueSliceImpl { + using Impl = detail::UnsafeSharedSlice<detail::UniqueSliceHeader, zero_on_destruct>; + + public: + UniqueSliceImpl() = default; + + explicit UniqueSliceImpl(size_t size) : impl_(Impl::create(size)) { + } + UniqueSliceImpl(size_t size, char c) : impl_(Impl::create(size)) { + as_mutable_slice().fill(c); + } + explicit UniqueSliceImpl(Slice slice) : impl_(Impl::create(slice)) { + } + + UniqueSliceImpl(const char *ptr, size_t size) : UniqueSliceImpl(Slice(ptr, size)) { + } + + UniqueSliceImpl copy() const { + return UniqueSliceImpl(as_slice()); + } + + Slice as_slice() const { + return impl_.as_slice(); + } + + MutableSlice as_mutable_slice() { + return impl_.as_mutable_slice(); + } + + operator Slice() const { + return as_slice(); + } + + // like in std::string + char *data() { + return as_mutable_slice().data(); + } + const char *data() const { + return as_slice().data(); + } + char operator[](size_t at) const { + return as_slice()[at]; + } + + bool empty() const { + return size() == 0; + } + + size_t size() const { + return impl_.size(); + } + + // like in std::string + size_t length() const { + return size(); + } + + void clear() { + impl_.clear(); + } + + private: + explicit UniqueSliceImpl(Impl impl) : impl_(std::move(impl)) { + } + Impl impl_; +}; + +using UniqueSlice = UniqueSliceImpl<false>; +using SecureString = UniqueSliceImpl<true>; + +inline MutableSlice as_mutable_slice(UniqueSharedSlice &unique_shared_slice) { + return unique_shared_slice.as_mutable_slice(); +} + +inline MutableSlice as_mutable_slice(UniqueSlice &unique_slice) { + return unique_slice.as_mutable_slice(); +} + +inline MutableSlice as_mutable_slice(SecureString &secure_string) { + return secure_string.as_mutable_slice(); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h index 69b4a4ad21..12d0382861 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice-decl.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,10 +8,10 @@ #include "td/utils/common.h" -#include <cstring> #include <type_traits> namespace td { + class Slice; class MutableSlice { @@ -26,9 +26,7 @@ class MutableSlice { MutableSlice(unsigned char *s, size_t len); MutableSlice(string &s); template <class T> - explicit MutableSlice(T s, std::enable_if_t<std::is_same<char *, T>::value, private_tag> = {}) - : MutableSlice(s, std::strlen(s)) { - } + explicit MutableSlice(T s, std::enable_if_t<std::is_same<char *, T>::value, private_tag> = {}); MutableSlice(char *s, char *t); MutableSlice(unsigned char *s, unsigned char *t); template <size_t N> @@ -54,11 +52,16 @@ class MutableSlice { MutableSlice substr(size_t from, size_t size) const; size_t find(char c) const; size_t rfind(char c) const; + void fill(char c); + void fill_zero(); + void fill_zero_secure(); void copy_from(Slice from); char &back(); char &operator[](size_t i); + + static const size_t npos = static_cast<size_t>(-1); }; class Slice { @@ -74,13 +77,9 @@ class Slice { Slice(const unsigned char *s, size_t len); Slice(const string &s); template <class T> - explicit Slice(T s, std::enable_if_t<std::is_same<char *, std::remove_const_t<T>>::value, private_tag> = {}) - : Slice(s, std::strlen(s)) { - } + explicit Slice(T s, std::enable_if_t<std::is_same<char *, std::remove_const_t<T>>::value, private_tag> = {}); template <class T> - explicit Slice(T s, std::enable_if_t<std::is_same<const char *, std::remove_const_t<T>>::value, private_tag> = {}) - : Slice(s, std::strlen(s)) { - } + explicit Slice(T s, std::enable_if_t<std::is_same<const char *, std::remove_const_t<T>>::value, private_tag> = {}); Slice(const char *s, const char *t); Slice(const unsigned char *s, const unsigned char *t); @@ -91,6 +90,18 @@ class Slice { constexpr Slice(const char (&a)[N]) : s_(a), len_(N - 1) { } + Slice &operator=(string &&s) = delete; + + template <size_t N> + constexpr Slice &operator=(char (&a)[N]) = delete; + + template <size_t N> + constexpr Slice &operator=(const char (&a)[N]) { + s_ = a; + len_ = N - 1; + return *this; + } + bool empty() const; size_t size() const; @@ -114,10 +125,13 @@ class Slice { char back() const; char operator[](size_t i) const; + + static const size_t npos = static_cast<size_t>(-1); }; bool operator==(const Slice &a, const Slice &b); bool operator!=(const Slice &a, const Slice &b); +bool operator<(const Slice &a, const Slice &b); class MutableCSlice : public MutableSlice { struct private_tag {}; @@ -175,13 +189,24 @@ class CSlice : public Slice { CSlice() : CSlice("") { } + CSlice &operator=(string &&s) = delete; + + template <size_t N> + constexpr CSlice &operator=(char (&a)[N]) = delete; + + template <size_t N> + constexpr CSlice &operator=(const char (&a)[N]) { + this->Slice::operator=(a); + return *this; + } + const char *c_str() const { return begin(); } }; struct SliceHash { - std::size_t operator()(Slice slice) const; + uint32 operator()(Slice slice) const; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.cpp new file mode 100644 index 0000000000..921a824479 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.cpp @@ -0,0 +1,34 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/Slice.h" + +#if TD_HAVE_OPENSSL +#include <openssl/crypto.h> +#endif + +namespace td { + +void MutableSlice::fill(char c) { + std::memset(data(), c, size()); +} + +void MutableSlice::fill_zero() { + fill('\0'); +} + +void MutableSlice::fill_zero_secure() { +#if TD_HAVE_OPENSSL + OPENSSL_cleanse(begin(), size()); +#else + volatile char *ptr = begin(); + for (size_t i = 0; i < size(); i++) { + ptr[i] = '\0'; + } +#endif +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h index a9bc6a7551..6c1efb0a69 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Slice.h @@ -1,20 +1,20 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once +#include "td/utils/common.h" #include "td/utils/Slice-decl.h" -#include "td/utils/logging.h" - #include <cstring> +#include <type_traits> namespace td { -/*** MutableSlice ***/ -inline MutableSlice::MutableSlice() : MutableSlice(const_cast<char *>(""), static_cast<size_t>(0)) { + +inline MutableSlice::MutableSlice() : s_(const_cast<char *>("")), len_(0) { } inline MutableSlice::MutableSlice(char *s, size_t len) : s_(s), len_(len) { @@ -25,7 +25,13 @@ inline MutableSlice::MutableSlice(unsigned char *s, size_t len) : s_(reinterpret CHECK(s_ != nullptr); } -inline MutableSlice::MutableSlice(string &s) : MutableSlice(&s[0], s.size()) { +inline MutableSlice::MutableSlice(string &s) : s_(&s[0]), len_(s.size()) { +} + +template <class T> +MutableSlice::MutableSlice(T s, std::enable_if_t<std::is_same<char *, T>::value, private_tag>) : s_(s) { + CHECK(s_ != nullptr); + len_ = std::strlen(s_); } inline MutableSlice::MutableSlice(char *s, char *t) : MutableSlice(s, t - s) { @@ -104,7 +110,7 @@ inline size_t MutableSlice::find(char c) const { return pos; } } - return static_cast<size_t>(-1); + return npos; } inline size_t MutableSlice::rfind(char c) const { @@ -113,7 +119,7 @@ inline size_t MutableSlice::rfind(char c) const { return pos; } } - return static_cast<size_t>(-1); + return npos; } inline void MutableSlice::copy_from(Slice from) { @@ -130,11 +136,10 @@ inline char &MutableSlice::operator[](size_t i) { return s_[i]; } -/*** Slice ***/ -inline Slice::Slice() : Slice("", static_cast<size_t>(0)) { +inline Slice::Slice() : s_(""), len_(0) { } -inline Slice::Slice(const MutableSlice &other) : Slice(other.begin(), other.size()) { +inline Slice::Slice(const MutableSlice &other) : s_(other.begin()), len_(other.size()) { } inline Slice::Slice(const char *s, size_t len) : s_(s), len_(len) { @@ -145,13 +150,28 @@ inline Slice::Slice(const unsigned char *s, size_t len) : s_(reinterpret_cast<co CHECK(s_ != nullptr); } -inline Slice::Slice(const string &s) : Slice(s.c_str(), s.size()) { +inline Slice::Slice(const string &s) : s_(s.c_str()), len_(s.size()) { +} + +template <class T> +Slice::Slice(T s, std::enable_if_t<std::is_same<char *, std::remove_const_t<T>>::value, private_tag>) : s_(s) { + CHECK(s_ != nullptr); + len_ = std::strlen(s_); +} + +template <class T> +Slice::Slice(T s, std::enable_if_t<std::is_same<const char *, std::remove_const_t<T>>::value, private_tag>) : s_(s) { + CHECK(s_ != nullptr); + len_ = std::strlen(s_); } -inline Slice::Slice(const char *s, const char *t) : Slice(s, t - s) { +inline Slice::Slice(const char *s, const char *t) : s_(s), len_(t - s) { + CHECK(s_ != nullptr); } -inline Slice::Slice(const unsigned char *s, const unsigned char *t) : Slice(s, t - s) { +inline Slice::Slice(const unsigned char *s, const unsigned char *t) + : s_(reinterpret_cast<const char *>(s)), len_(t - s) { + CHECK(s_ != nullptr); } inline size_t Slice::size() const { @@ -225,7 +245,7 @@ inline size_t Slice::find(char c) const { return pos; } } - return static_cast<size_t>(-1); + return npos; } inline size_t Slice::rfind(char c) const { @@ -234,7 +254,7 @@ inline size_t Slice::rfind(char c) const { return pos; } } - return static_cast<size_t>(-1); + return npos; } inline char Slice::back() const { @@ -254,6 +274,14 @@ inline bool operator!=(const Slice &a, const Slice &b) { return !(a == b); } +inline bool operator<(const Slice &a, const Slice &b) { + auto x = std::memcmp(a.data(), b.data(), td::min(a.size(), b.size())); + if (x == 0) { + return a.size() < b.size(); + } + return x < 0; +} + inline MutableCSlice::MutableCSlice(char *s, char *t) : MutableSlice(s, t) { CHECK(*t == '\0'); } @@ -262,14 +290,38 @@ inline CSlice::CSlice(const char *s, const char *t) : Slice(s, t) { CHECK(*t == '\0'); } -inline std::size_t SliceHash::operator()(Slice slice) const { +inline uint32 SliceHash::operator()(Slice slice) const { // simple string hash - std::size_t result = 0; - constexpr std::size_t MUL = 123456789; + uint32 result = 0; + constexpr uint32 MUL = 123456789; for (auto c : slice) { result = result * MUL + c; } return result; } +inline Slice as_slice(Slice slice) { + return slice; +} + +inline MutableSlice as_slice(MutableSlice slice) { + return slice; +} + +inline Slice as_slice(const string &str) { + return str; +} + +inline MutableSlice as_slice(string &str) { + return str; +} + +inline MutableSlice as_mutable_slice(MutableSlice slice) { + return slice; +} + +inline MutableSlice as_mutable_slice(string &str) { + return str; +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SliceBuilder.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SliceBuilder.h new file mode 100644 index 0000000000..062ea136a0 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SliceBuilder.h @@ -0,0 +1,57 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Slice.h" +#include "td/utils/StackAllocator.h" +#include "td/utils/StringBuilder.h" + +#define PSLICE() ::td::detail::Slicify() & ::td::SliceBuilder().ref() +#define PSTRING() ::td::detail::Stringify() & ::td::SliceBuilder().ref() + +namespace td { + +class SliceBuilder { + public: + template <class T> + SliceBuilder &operator<<(T &&other) { + sb_ << other; + return *this; + } + + MutableCSlice as_cslice() { + return sb_.as_cslice(); + } + + SliceBuilder &ref() { + return *this; + } + + private: + static const size_t DEFAULT_BUFFER_SIZE = 1024; + decltype(StackAllocator::alloc(0)) buffer_ = StackAllocator::alloc(DEFAULT_BUFFER_SIZE); + StringBuilder sb_ = StringBuilder(buffer_.as_slice(), true); +}; + +namespace detail { +class Slicify { + public: + CSlice operator&(SliceBuilder &slice_builder) { + return slice_builder.as_cslice(); + } +}; + +class Stringify { + public: + string operator&(SliceBuilder &slice_builder) { + return slice_builder.as_cslice().str(); + } +}; +} // namespace detail + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Span.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Span.h new file mode 100644 index 0000000000..877adcf78a --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Span.h @@ -0,0 +1,158 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#include <array> +#include <iterator> + +namespace td { + +namespace detail { +template <class T, class InnerT> +class SpanImpl { + InnerT *data_{nullptr}; + size_t size_{0}; + + public: + SpanImpl() = default; + SpanImpl(InnerT *data, size_t size) : data_(data), size_(size) { + } + SpanImpl(InnerT &data) : SpanImpl(&data, 1) { + } + + template <class OtherInnerT> + SpanImpl(const SpanImpl<T, OtherInnerT> &other) : SpanImpl(other.data(), other.size()) { + } + + template <size_t N> + SpanImpl(const std::array<T, N> &arr) : SpanImpl(arr.data(), arr.size()) { + } + template <size_t N> + SpanImpl(std::array<T, N> &arr) : SpanImpl(arr.data(), arr.size()) { + } + template <size_t N> + SpanImpl(const T (&arr)[N]) : SpanImpl(arr, N) { + } + template <size_t N> + SpanImpl(T (&arr)[N]) : SpanImpl(arr, N) { + } + SpanImpl(const vector<T> &v) : SpanImpl(v.data(), v.size()) { + } + SpanImpl(vector<T> &v) : SpanImpl(v.data(), v.size()) { + } + + template <class OtherInnerT> + SpanImpl &operator=(const SpanImpl<T, OtherInnerT> &other) { + SpanImpl copy{other}; + *this = copy; + } + template <class OtherInnerT> + bool operator==(const SpanImpl<T, OtherInnerT> &other) const { + if (size() != other.size()) { + return false; + } + for (size_t i = 0; i < size(); i++) { + if (!((*this)[i] == other[i])) { + return false; + } + } + return true; + } + + InnerT &operator[](size_t i) { + DCHECK(i < size()); + return data_[i]; + } + + const InnerT &operator[](size_t i) const { + DCHECK(i < size()); + return data_[i]; + } + + InnerT &back() { + DCHECK(!empty()); + return data_[size() - 1]; + } + + const InnerT &back() const { + DCHECK(!empty()); + return data_[size() - 1]; + } + + InnerT &front() { + DCHECK(!empty()); + return data_[0]; + } + + const InnerT &front() const { + DCHECK(!empty()); + return data_[0]; + } + + InnerT *data() const { + return data_; + } + + InnerT *begin() const { + return data_; + } + InnerT *end() const { + return data_ + size_; + } + + std::reverse_iterator<InnerT *> rbegin() const { + return std::reverse_iterator<InnerT *>(end()); + } + std::reverse_iterator<InnerT *> rend() const { + return std::reverse_iterator<InnerT *>(begin()); + } + + size_t size() const { + return size_; + } + bool empty() const { + return size() == 0; + } + + SpanImpl &truncate(size_t size) { + if (size < size_) { + size_ = size; + } + return *this; + } + + SpanImpl substr(size_t offset) const { + CHECK(offset <= size_); + return SpanImpl(begin() + offset, size_ - offset); + } + SpanImpl substr(size_t offset, size_t size) const { + CHECK(offset <= size_); + CHECK(size_ - offset >= size); + return SpanImpl(begin() + offset, size); + } +}; +} // namespace detail + +template <class T> +using Span = detail::SpanImpl<T, const T>; + +template <class T> +using MutableSpan = detail::SpanImpl<T, T>; + +template <class T> +Span<T> as_span(const std::vector<T> &vec) { + return Span<T>(vec); +} + +template <class T> +MutableSpan<T> as_mutable_span(std::vector<T> &vec) { + return MutableSpan<T>(vec); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h index d726b0b2f6..3413f6c5b3 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/SpinLock.h @@ -1,14 +1,15 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/port/thread.h" +#include "td/utils/port/sleep.h" #include <atomic> +#include <memory> namespace td { @@ -26,9 +27,10 @@ class SpinLock { bool next() { cnt++; if (cnt < 50) { + //TODO pause return true; } else { - td::this_thread::yield(); + usleep_for(1); return true; } } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp index 4db905368b..947238165e 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,11 +8,73 @@ #include "td/utils/port/thread_local.h" +#include <array> +#include <cstdlib> + namespace td { +namespace { +class ArrayAllocator final : public StackAllocator::AllocatorImpl { + static const size_t MEM_SIZE = 1024 * 1024; + std::array<char, MEM_SIZE> mem; + size_t pos{0}; + + MutableSlice allocate(size_t size) final { + if (size > MEM_SIZE) { + std::abort(); // too much memory requested + } + char *res = mem.data() + pos; + pos += (size + 7) & -8; + if (pos > MEM_SIZE) { + std::abort(); // memory is over + } + return {res, size}; + } + + void free_ptr(char *ptr, size_t size) final { + size = (size + 7) & -8; + if (size > pos || ptr != mem.data() + (pos - size)) { + std::abort(); // shouldn't happen + } + pos -= size; + } + + public: + ~ArrayAllocator() final { + if (pos != 0) { + std::abort(); // shouldn't happen + } + } +}; + +class NewAllocator final : public StackAllocator::AllocatorImpl { + MutableSlice allocate(size_t size) final { + return {new char[size], size}; + } -StackAllocator::Impl &StackAllocator::impl() { - static TD_THREAD_LOCAL StackAllocator::Impl *impl; // static zero-initialized - init_thread_local<Impl>(impl); - return *impl; + void free_ptr(char *ptr, size_t size) final { + delete[] ptr; + } + + public: + ~NewAllocator() final = default; +}; +} // namespace + +StackAllocator::Ptr::~Ptr() { + if (!slice_.empty()) { + allocator_->free_ptr(slice_.data(), slice_.size()); + } } + +StackAllocator::AllocatorImpl *StackAllocator::impl() { + if (get_thread_id() != 0) { + static TD_THREAD_LOCAL ArrayAllocator *array_allocator; // static zero-initialized + init_thread_local<ArrayAllocator>(array_allocator); + return array_allocator; + } else { + static NewAllocator new_allocator; + return &new_allocator; + } +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h index d2399b9526..7b87aef963 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StackAllocator.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,75 +7,54 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/MovableValue.h" -#include "td/utils/Slice-decl.h" - -#include <array> -#include <cstdlib> +#include "td/utils/Slice.h" namespace td { class StackAllocator { - class Deleter { + public: + class AllocatorImpl { public: - void operator()(char *ptr) { - free_ptr(ptr); - } + AllocatorImpl() = default; + AllocatorImpl(const AllocatorImpl &) = delete; + AllocatorImpl &operator=(const AllocatorImpl &) = delete; + AllocatorImpl(AllocatorImpl &&) = delete; + AllocatorImpl &operator=(AllocatorImpl &&) = delete; + virtual ~AllocatorImpl() = default; + + virtual MutableSlice allocate(size_t size) = 0; + + virtual void free_ptr(char *ptr, size_t size) = 0; }; - // TODO: alloc memory with mmap and unload unused pages - // memory still can be corrupted, but it is better than explicit free function - // TODO: use pointer that can't be even copied - using PtrImpl = std::unique_ptr<char, Deleter>; + private: class Ptr { public: - Ptr(char *ptr, size_t size) : ptr_(ptr), size_(size) { + Ptr(AllocatorImpl *allocator, size_t size) : allocator_(allocator), slice_(allocator_->allocate(size)) { + } + Ptr(const Ptr &other) = delete; + Ptr &operator=(const Ptr &other) = delete; + Ptr(Ptr &&other) noexcept : allocator_(other.allocator_), slice_(other.slice_) { + other.allocator_ = nullptr; + other.slice_ = MutableSlice(); } + Ptr &operator=(Ptr &&other) = delete; + ~Ptr(); MutableSlice as_slice() const { - return MutableSlice(ptr_.get(), size_.get()); + return slice_; } private: - PtrImpl ptr_; - MovableValue<size_t> size_; - }; - - static void free_ptr(char *ptr) { - impl().free_ptr(ptr); - } - - struct Impl { - static const size_t MEM_SIZE = 1024 * 1024; - std::array<char, MEM_SIZE> mem; - - size_t pos{0}; - char *alloc(size_t size) { - if (size == 0) { - size = 1; - } - char *res = mem.data() + pos; - size = (size + 7) & -8; - pos += size; - if (pos > MEM_SIZE) { - std::abort(); // memory is over - } - return res; - } - void free_ptr(char *ptr) { - size_t new_pos = ptr - mem.data(); - if (new_pos >= pos) { - std::abort(); // shouldn't happen - } - pos = new_pos; - } + AllocatorImpl *allocator_; + MutableSlice slice_; }; - static Impl &impl(); + static AllocatorImpl *impl(); public: static Ptr alloc(size_t size) { - return Ptr(impl().alloc(size), size); + return Ptr(impl(), size); } }; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp index b8bb169e60..5a49b1097f 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.cpp @@ -1,11 +1,13 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/Status.h" +#include "td/utils/SliceBuilder.h" + #if TD_PORT_WINDOWS #include "td/utils/port/wstring_convert.h" #endif @@ -42,13 +44,45 @@ string winerror_to_string(int code) { wchar_t wbuf[size]; auto res_size = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, code, 0, wbuf, size - 1, nullptr); if (res_size == 0) { - return "Unknown windows error"; + return "Unknown Windows error"; } while (res_size != 0 && (wbuf[res_size - 1] == '\n' || wbuf[res_size - 1] == '\r')) { res_size--; } - return from_wstring(wbuf, res_size).ok(); + auto error_message = from_wstring(wbuf, res_size); + if (error_message.is_error()) { + return "Invalid Windows error"; + } + return error_message.move_as_ok(); } #endif +Status Status::move_as_error_prefix(Slice prefix) const { + CHECK(is_error()); + Info info = get_info(); + switch (info.error_type) { + case ErrorType::General: + return Error(code(), PSLICE() << prefix << message()); + case ErrorType::Os: + return Status(false, ErrorType::Os, code(), PSLICE() << prefix << message()); + default: + UNREACHABLE(); + return {}; + } +} + +Status Status::move_as_error_suffix(Slice suffix) const { + CHECK(is_error()); + Info info = get_info(); + switch (info.error_type) { + case ErrorType::General: + return Error(code(), PSLICE() << message() << suffix); + case ErrorType::Os: + return Status(false, ErrorType::Os, code(), PSLICE() << message() << suffix); + default: + UNREACHABLE(); + return {}; + } +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h index 8ef2846df1..83e1abcb23 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Status.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,7 +15,9 @@ #include <cerrno> #include <cstring> +#include <memory> #include <new> +#include <type_traits> #include <utility> #define TRY_STATUS(status) \ @@ -25,14 +27,84 @@ return try_status.move_as_error(); \ } \ } -#define TRY_RESULT(name, result) TRY_RESULT_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result) + +#define TRY_STATUS_PREFIX(status, prefix) \ + { \ + auto try_status = (status); \ + if (try_status.is_error()) { \ + return try_status.move_as_error_prefix(prefix); \ + } \ + } + +#define TRY_STATUS_PROMISE(promise_name, status) \ + { \ + auto try_status = (status); \ + if (try_status.is_error()) { \ + promise_name.set_error(try_status.move_as_error()); \ + return; \ + } \ + } + +#define TRY_STATUS_PROMISE_PREFIX(promise_name, status, prefix) \ + { \ + auto try_status = (status); \ + if (try_status.is_error()) { \ + promise_name.set_error(try_status.move_as_error_prefix(prefix)); \ + return; \ + } \ + } + +#define TRY_RESULT(name, result) TRY_RESULT_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result) + +#define TRY_RESULT_PROMISE(promise_name, name, result) \ + TRY_RESULT_PROMISE_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result) + +#define TRY_RESULT_ASSIGN(name, result) TRY_RESULT_IMPL(TD_CONCAT(r_response, __LINE__), name, result) + +#define TRY_RESULT_PROMISE_ASSIGN(promise_name, name, result) \ + TRY_RESULT_PROMISE_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result) + +#define TRY_RESULT_PREFIX(name, result, prefix) \ + TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix) + +#define TRY_RESULT_PREFIX_ASSIGN(name, result, prefix) \ + TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result, prefix) + +#define TRY_RESULT_PROMISE_PREFIX(promise_name, name, result, prefix) \ + TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix) + +#define TRY_RESULT_PROMISE_PREFIX_ASSIGN(promise_name, name, result, prefix) \ + TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result, prefix) #define TRY_RESULT_IMPL(r_name, name, result) \ auto r_name = (result); \ if (r_name.is_error()) { \ return r_name.move_as_error(); \ } \ - auto name = r_name.move_as_ok(); + name = r_name.move_as_ok(); + +#define TRY_RESULT_PREFIX_IMPL(r_name, name, result, prefix) \ + auto r_name = (result); \ + if (r_name.is_error()) { \ + return r_name.move_as_error_prefix(prefix); \ + } \ + name = r_name.move_as_ok(); + +#define TRY_RESULT_PROMISE_IMPL(promise_name, r_name, name, result) \ + auto r_name = (result); \ + if (r_name.is_error()) { \ + promise_name.set_error(r_name.move_as_error()); \ + return; \ + } \ + name = r_name.move_as_ok(); + +#define TRY_RESULT_PROMISE_PREFIX_IMPL(promise_name, r_name, name, result, prefix) \ + auto r_name = (result); \ + if (r_name.is_error()) { \ + promise_name.set_error(r_name.move_as_error_prefix(prefix)); \ + return; \ + } \ + name = r_name.move_as_ok(); #define LOG_STATUS(status) \ { \ @@ -49,19 +121,19 @@ #if TD_PORT_POSIX #define OS_ERROR(message) \ - [&]() { \ + [&] { \ auto saved_errno = errno; \ return ::td::Status::PosixError(saved_errno, (message)); \ }() #define OS_SOCKET_ERROR(message) OS_ERROR(message) #elif TD_PORT_WINDOWS #define OS_ERROR(message) \ - [&]() { \ + [&] { \ auto saved_error = ::GetLastError(); \ return ::td::Status::WindowsError(saved_error, (message)); \ }() #define OS_SOCKET_ERROR(message) \ - [&]() { \ + [&] { \ auto saved_error = ::WSAGetLastError(); \ return ::td::Status::WindowsError(saved_error, (message)); \ }() @@ -78,16 +150,13 @@ string winerror_to_string(int code); #endif class Status { - enum class ErrorType : int8 { general, os }; + enum class ErrorType : int8 { General, Os }; public: Status() = default; bool operator==(const Status &other) const { - if (get_info().static_flag) { - return ptr_ == other.ptr_; - } - return false; + return ptr_ == other.ptr_; } Status clone() const TD_WARN_UNUSED_RESULT { @@ -106,7 +175,7 @@ class Status { } static Status Error(int err, Slice message = Slice()) TD_WARN_UNUSED_RESULT { - return Status(false, ErrorType::general, err, message); + return Status(false, ErrorType::General, err, message); } static Status Error(Slice message) TD_WARN_UNUSED_RESULT { @@ -115,13 +184,13 @@ class Status { #if TD_PORT_WINDOWS static Status WindowsError(int saved_error, Slice message) TD_WARN_UNUSED_RESULT { - return Status(false, ErrorType::os, saved_error, message); + return Status(false, ErrorType::Os, saved_error, message); } #endif #if TD_PORT_POSIX static Status PosixError(int32 saved_errno, Slice message) TD_WARN_UNUSED_RESULT { - return Status(false, ErrorType::os, saved_errno, message); + return Status(false, ErrorType::Os, saved_errno, message); } #endif @@ -131,17 +200,7 @@ class Status { template <int Code> static Status Error() { - static Status status(true, ErrorType::general, Code, ""); - return status.clone_static(); - } - - static Status InvalidId() TD_WARN_UNUSED_RESULT { - static Status status(true, ErrorType::general, 0, "Invalid Id"); - return status.clone_static(); - } - - static Status Hangup() TD_WARN_UNUSED_RESULT { - static Status status(true, ErrorType::general, 0, "Hangup"); + static Status status(true, ErrorType::General, Code, ""); return status.clone_static(); } @@ -151,10 +210,10 @@ class Status { } Info info = get_info(); switch (info.error_type) { - case ErrorType::general: + case ErrorType::General: sb << "[Error"; break; - case ErrorType::os: + case ErrorType::Os: #if TD_PORT_POSIX sb << "[PosixError : " << strerror_safe(info.error_code); #elif TD_PORT_WINDOWS @@ -162,7 +221,6 @@ class Status { #endif break; default: - LOG(FATAL) << "Unknown status type: " << static_cast<int8>(info.error_type); UNREACHABLE(); break; } @@ -186,9 +244,21 @@ class Status { return ptr_ != nullptr; } +#ifdef TD_STATUS_NO_ENSURE + void ensure() const { + if (!is_ok()) { + LOG(FATAL) << "Unexpected Status " << to_string(); + } + } + void ensure_error() const { + if (is_ok()) { + LOG(FATAL) << "Unexpected Status::OK"; + } + } +#else void ensure_impl(CSlice file_name, int line) const { if (!is_ok()) { - LOG(FATAL) << "Unexpexted Status " << to_string() << " in file " << file_name << " at line " << line; + LOG(FATAL) << "Unexpected Status " << to_string() << " in file " << file_name << " at line " << line; } } void ensure_error_impl(CSlice file_name, int line) const { @@ -196,6 +266,7 @@ class Status { LOG(FATAL) << "Unexpected Status::OK in file " << file_name << " at line " << line; } } +#endif void ignore() const { // nop @@ -221,16 +292,15 @@ class Status { } Info info = get_info(); switch (info.error_type) { - case ErrorType::general: + case ErrorType::General: return message().str(); - case ErrorType::os: + case ErrorType::Os: #if TD_PORT_POSIX return strerror_safe(info.error_code).str(); #elif TD_PORT_WINDOWS return winerror_to_string(info.error_code); #endif default: - LOG(FATAL) << "Unknown status type: " << static_cast<int8>(info.error_type); UNREACHABLE(); return ""; } @@ -248,6 +318,16 @@ class Status { return std::move(*this); } + Status move_as_ok() = delete; + + Status move_as_error_prefix(const Status &status) const TD_WARN_UNUSED_RESULT { + return status.move_as_error_suffix(message()); + } + + Status move_as_error_prefix(Slice prefix) const TD_WARN_UNUSED_RESULT; + + Status move_as_error_suffix(Slice suffix) const TD_WARN_UNUSED_RESULT; + private: struct Info { bool static_flag : 1; @@ -277,10 +357,13 @@ class Status { Status(bool static_flag, ErrorType error_type, int error_code, Slice message) : Status(to_info(static_flag, error_type, error_code), message) { + if (static_flag) { + TD_LSAN_IGNORE(ptr_.get()); + } } Status clone_static() const TD_WARN_UNUSED_RESULT { - CHECK(is_ok() || get_info().static_flag); + CHECK(ptr_ != nullptr && get_info().static_flag); Status result; result.ptr_ = std::unique_ptr<char[], Deleter>(ptr_.get()); return result; @@ -325,24 +408,30 @@ class Status { template <class T = Unit> class Result { public: - Result() : status_(Status::Error()) { + using ValueT = T; + Result() : status_(Status::Error<-1>()) { } - template <class S> + template <class S, std::enable_if_t<!std::is_same<std::decay_t<S>, Result>::value, int> = 0> Result(S &&x) : status_(), value_(std::forward<S>(x)) { } + struct emplace_t {}; + template <class... ArgsT> + Result(emplace_t, ArgsT &&...args) : status_(), value_(std::forward<ArgsT>(args)...) { + } Result(Status &&status) : status_(std::move(status)) { CHECK(status_.is_error()); } Result(const Result &) = delete; Result &operator=(const Result &) = delete; - Result(Result &&other) : status_(std::move(other.status_)) { + Result(Result &&other) noexcept : status_(std::move(other.status_)) { if (status_.is_ok()) { new (&value_) T(std::move(other.value_)); other.value_.~T(); } - other.status_ = Status::Error(); + other.status_ = Status::Error<-2>(); } - Result &operator=(Result &&other) { + Result &operator=(Result &&other) noexcept { + CHECK(this != &other); if (status_.is_ok()) { value_.~T(); } @@ -358,21 +447,38 @@ class Result { other.value_.~T(); } status_ = std::move(other.status_); - other.status_ = Status::Error(); + other.status_ = Status::Error<-3>(); return *this; } + template <class... ArgsT> + void emplace(ArgsT &&...args) { + if (status_.is_ok()) { + value_.~T(); + } + new (&value_) T(std::forward<ArgsT>(args)...); + status_ = Status::OK(); + } ~Result() { if (status_.is_ok()) { value_.~T(); } } +#ifdef TD_STATUS_NO_ENSURE + void ensure() const { + status_.ensure(); + } + void ensure_error() const { + status_.ensure_error(); + } +#else void ensure_impl(CSlice file_name, int line) const { status_.ensure_impl(file_name, line); } void ensure_error_impl(CSlice file_name, int line) const { status_.ensure_error_impl(file_name, line); } +#endif void ignore() const { status_.ignore(); } @@ -389,20 +495,42 @@ class Result { Status move_as_error() TD_WARN_UNUSED_RESULT { CHECK(status_.is_error()); SCOPE_EXIT { - status_ = Status::Error(); + status_ = Status::Error<-4>(); }; return std::move(status_); } + Status move_as_error_prefix(Slice prefix) TD_WARN_UNUSED_RESULT { + SCOPE_EXIT { + status_ = Status::Error<-5>(); + }; + return status_.move_as_error_prefix(prefix); + } + Status move_as_error_prefix(const Status &prefix) TD_WARN_UNUSED_RESULT { + SCOPE_EXIT { + status_ = Status::Error<-6>(); + }; + return status_.move_as_error_prefix(prefix); + } + Status move_as_error_suffix(Slice suffix) TD_WARN_UNUSED_RESULT { + SCOPE_EXIT { + status_ = Status::Error<-7>(); + }; + return status_.move_as_error_suffix(suffix); + } const T &ok() const { - CHECK(status_.is_ok()) << status_; + LOG_CHECK(status_.is_ok()) << status_; return value_; } T &ok_ref() { - CHECK(status_.is_ok()) << status_; + LOG_CHECK(status_.is_ok()) << status_; + return value_; + } + const T &ok_ref() const { + LOG_CHECK(status_.is_ok()) << status_; return value_; } T move_as_ok() { - CHECK(status_.is_ok()) << status_; + LOG_CHECK(status_.is_ok()) << status_; return std::move(value_); } @@ -416,6 +544,22 @@ class Result { *this = Result<T>(); } + template <class F> + Result<decltype(std::declval<F>()(std::declval<T>()))> move_map(F &&f) { + if (is_error()) { + return move_as_error(); + } + return f(move_as_ok()); + } + + template <class F> + decltype(std::declval<F>()(std::declval<T>())) move_fmap(F &&f) { + if (is_error()) { + return move_as_error(); + } + return f(move_as_ok()); + } + private: Status status_; union { @@ -432,27 +576,4 @@ inline StringBuilder &operator<<(StringBuilder &string_builder, const Status &st return status.print(string_builder); } -namespace detail { - -class SlicifySafe { - public: - Result<CSlice> operator&(Logger &logger) { - if (logger.is_error()) { - return Status::Error("Buffer overflow"); - } - return logger.as_cslice(); - } -}; - -class StringifySafe { - public: - Result<string> operator&(Logger &logger) { - if (logger.is_error()) { - return Status::Error("Buffer overflow"); - } - return logger.as_cslice().str(); - } -}; - -} // namespace detail } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StealingQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/StealingQueue.h new file mode 100644 index 0000000000..d2fb773f3a --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StealingQueue.h @@ -0,0 +1,125 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/misc.h" + +#include <array> +#include <atomic> + +namespace td { + +template <class T, size_t N = 256> +class StealingQueue { + public: + static_assert(N > 0 && (N & (N - 1)) == 0, ""); + + // tries to put a value + // returns if succeeded + // only owner is allowed to to do this + template <class F> + void local_push(T value, F &&overflow_f) { + while (true) { + auto tail = tail_.load(std::memory_order_relaxed); + auto head = head_.load(); // TODO: memory order + + if (static_cast<size_t>(tail - head) < N) { + buf_[tail & MASK].store(value, std::memory_order_relaxed); + tail_.store(tail + 1, std::memory_order_release); + return; + } + + // queue is full + // TODO: batch insert into global queue? + auto n = N / 2 + 1; + auto new_head = head + n; + if (!head_.compare_exchange_strong(head, new_head)) { + continue; + } + + for (size_t i = 0; i < n; i++) { + overflow_f(buf_[(i + head) & MASK].load(std::memory_order_relaxed)); + } + overflow_f(value); + + return; + } + } + + // tries to pop a value + // returns if succeeded + // only owner is allowed to do this + bool local_pop(T &value) { + auto tail = tail_.load(std::memory_order_relaxed); + auto head = head_.load(); + + if (head == tail) { + return false; + } + + value = buf_[head & MASK].load(std::memory_order_relaxed); + return head_.compare_exchange_strong(head, head + 1); + } + + bool steal(T &value, StealingQueue<T, N> &other) { + while (true) { + auto tail = tail_.load(std::memory_order_relaxed); + auto head = head_.load(); // TODO: memory order + + auto other_head = other.head_.load(); + auto other_tail = other.tail_.load(std::memory_order_acquire); + + if (other_tail < other_head) { + continue; + } + auto n = narrow_cast<size_t>(other_tail - other_head); + if (n > N) { + continue; + } + n -= n / 2; + n = td::min(n, static_cast<size_t>(head + N - tail)); + if (n == 0) { + return false; + } + + for (size_t i = 0; i < n; i++) { + buf_[(i + tail) & MASK].store(other.buf_[(i + other_head) & MASK].load(std::memory_order_relaxed), + std::memory_order_relaxed); + } + + if (!other.head_.compare_exchange_strong(other_head, other_head + n)) { + continue; + } + + n--; + value = buf_[(tail + n) & MASK].load(std::memory_order_relaxed); + tail_.store(tail + n, std::memory_order_release); + return true; + } + } + + StealingQueue() { + for (auto &x : buf_) { +// workaround for https://gcc.gnu.org/bugzilla/show_bug.cgi?id=64658 +#if TD_GCC && GCC_VERSION <= 40902 + x = T(); +#else + std::atomic_init(&x, T()); +#endif + } + std::atomic_thread_fence(std::memory_order_seq_cst); + } + + private: + std::atomic<int64> head_{0}; + std::atomic<int64> tail_{0}; + static constexpr size_t MASK{N - 1}; + std::array<std::atomic<T>, N> buf_; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h index 91750dcd44..7743b81ff5 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Storer.h @@ -1,10 +1,11 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once + #include "td/utils/StorerBase.h" #include "td/utils/common.h" @@ -15,16 +16,17 @@ #include <limits> namespace td { -class SliceStorer : public Storer { + +class SliceStorer final : public Storer { Slice slice; public: explicit SliceStorer(Slice slice) : slice(slice) { } - size_t size() const override { + size_t size() const final { return slice.size(); } - size_t store(uint8 *ptr) const override { + size_t store(uint8 *ptr) const final { std::memcpy(ptr, slice.ubegin(), slice.size()); return slice.size(); } @@ -34,7 +36,7 @@ inline SliceStorer create_storer(Slice slice) { return SliceStorer(slice); } -class ConcatStorer : public Storer { +class ConcatStorer final : public Storer { const Storer &a_; const Storer &b_; @@ -42,11 +44,11 @@ class ConcatStorer : public Storer { ConcatStorer(const Storer &a, const Storer &b) : a_(a), b_(b) { } - size_t size() const override { + size_t size() const final { return a_.size() + b_.size(); } - size_t store(uint8 *ptr) const override { + size_t store(uint8 *ptr) const final { uint8 *ptr_save = ptr; ptr += a_.store(ptr); ptr += b_.store(ptr); @@ -59,17 +61,17 @@ inline ConcatStorer create_storer(const Storer &a, const Storer &b) { } template <class T> -class DefaultStorer : public Storer { +class DefaultStorer final : public Storer { public: explicit DefaultStorer(const T &object) : object_(object) { } - size_t size() const override { + size_t size() const final { if (size_ == std::numeric_limits<size_t>::max()) { size_ = tl_calc_length(object_); } return size_; } - size_t store(uint8 *ptr) const override { + size_t store(uint8 *ptr) const final { return tl_store_unsafe(object_, ptr); } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h index e6fea28e16..05e5edc714 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StorerBase.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -19,7 +19,7 @@ class Storer { Storer &operator=(Storer &&) = default; virtual ~Storer() = default; virtual size_t size() const = 0; - virtual size_t store(uint8 *ptr) const = 0; + virtual size_t store(uint8 *ptr) const TD_WARN_UNUSED_RESULT = 0; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp index ce64bbc9a6..d82fdbea53 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,70 +8,181 @@ #include "td/utils/misc.h" #include "td/utils/port/thread_local.h" +#include "td/utils/Slice.h" #include <cstdio> +#include <cstring> +#include <limits> #include <locale> +#include <memory> #include <sstream> +#include <utility> namespace td { -// TODO: optimize +StringBuilder::StringBuilder(MutableSlice slice, bool use_buffer) + : begin_ptr_(slice.begin()), current_ptr_(begin_ptr_), use_buffer_(use_buffer) { + if (slice.size() <= RESERVED_SIZE) { + auto buffer_size = RESERVED_SIZE + 100; + buffer_ = std::make_unique<char[]>(buffer_size); + begin_ptr_ = buffer_.get(); + current_ptr_ = begin_ptr_; + end_ptr_ = begin_ptr_ + buffer_size - RESERVED_SIZE; + } else { + end_ptr_ = slice.end() - RESERVED_SIZE; + } +} + +StringBuilder &StringBuilder::operator<<(Slice slice) { + size_t size = slice.size(); + if (unlikely(!reserve(size))) { + if (end_ptr_ < current_ptr_) { + return on_error(); + } + auto available_size = static_cast<size_t>(end_ptr_ + RESERVED_SIZE - 1 - current_ptr_); + if (size > available_size) { + error_flag_ = true; + size = available_size; + } + } + + std::memcpy(current_ptr_, slice.begin(), size); + current_ptr_ += size; + return *this; +} + +template <class T> +static char *print_uint(char *current_ptr, T x) { + if (x < 100) { + if (x < 10) { + *current_ptr++ = static_cast<char>('0' + x); + } else { + *current_ptr++ = static_cast<char>('0' + x / 10); + *current_ptr++ = static_cast<char>('0' + x % 10); + } + return current_ptr; + } + + auto begin_ptr = current_ptr; + do { + *current_ptr++ = static_cast<char>('0' + x % 10); + x /= 10; + } while (x > 0); + + auto end_ptr = current_ptr - 1; + while (begin_ptr < end_ptr) { + std::swap(*begin_ptr++, *end_ptr--); + } + + return current_ptr; +} + +template <class T> +static char *print_int(char *current_ptr, T x) { + if (x < 0) { + if (x == std::numeric_limits<T>::min()) { + current_ptr = print_int(current_ptr, x + 1); + CHECK(current_ptr[-1] != '9'); + current_ptr[-1]++; + return current_ptr; + } + + *current_ptr++ = '-'; + x = -x; + } + + return print_uint(current_ptr, x); +} + +bool StringBuilder::reserve_inner(size_t size) { + if (!use_buffer_) { + return false; + } + + size_t old_data_size = current_ptr_ - begin_ptr_; + if (size >= std::numeric_limits<size_t>::max() - RESERVED_SIZE - old_data_size - 1) { + return false; + } + size_t need_data_size = old_data_size + size; + size_t old_buffer_size = end_ptr_ - begin_ptr_; + if (old_buffer_size >= (std::numeric_limits<size_t>::max() - RESERVED_SIZE) / 2 - 2) { + return false; + } + size_t new_buffer_size = (old_buffer_size + 1) * 2; + if (new_buffer_size < need_data_size) { + new_buffer_size = need_data_size; + } + if (new_buffer_size < 100) { + new_buffer_size = 100; + } + new_buffer_size += RESERVED_SIZE; + auto new_buffer = std::make_unique<char[]>(new_buffer_size); + std::memcpy(new_buffer.get(), begin_ptr_, old_data_size); + buffer_ = std::move(new_buffer); + begin_ptr_ = buffer_.get(); + current_ptr_ = begin_ptr_ + old_data_size; + end_ptr_ = begin_ptr_ + new_buffer_size - RESERVED_SIZE; + CHECK(end_ptr_ > current_ptr_); + CHECK(static_cast<size_t>(end_ptr_ - current_ptr_) >= size); + return true; +} + StringBuilder &StringBuilder::operator<<(int x) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve())) { return on_error(); } - current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%d", x); + current_ptr_ = print_int(current_ptr_, x); return *this; } StringBuilder &StringBuilder::operator<<(unsigned int x) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve())) { return on_error(); } - current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%u", x); + current_ptr_ = print_uint(current_ptr_, x); return *this; } StringBuilder &StringBuilder::operator<<(long int x) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve())) { return on_error(); } - current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%ld", x); + current_ptr_ = print_int(current_ptr_, x); return *this; } StringBuilder &StringBuilder::operator<<(long unsigned int x) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve())) { return on_error(); } - current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%lu", x); + current_ptr_ = print_uint(current_ptr_, x); return *this; } StringBuilder &StringBuilder::operator<<(long long int x) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve())) { return on_error(); } - current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%lld", x); + current_ptr_ = print_int(current_ptr_, x); return *this; } StringBuilder &StringBuilder::operator<<(long long unsigned int x) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve())) { return on_error(); } - current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%llu", x); + current_ptr_ = print_uint(current_ptr_, x); return *this; } StringBuilder &StringBuilder::operator<<(FixedDouble x) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve(std::numeric_limits<double>::max_exponent10 + x.precision + 4))) { return on_error(); } static TD_THREAD_LOCAL std::stringstream *ss; if (init_thread_local<std::stringstream>(ss)) { - ss->imbue(std::locale::classic()); + auto previous_locale = ss->imbue(std::locale::classic()); ss->setf(std::ios_base::fixed, std::ios_base::floatfield); } else { ss->str(std::string()); @@ -80,8 +191,8 @@ StringBuilder &StringBuilder::operator<<(FixedDouble x) { ss->precision(x.precision); *ss << x.d; - int len = narrow_cast<int>(static_cast<std::streamoff>(ss->tellp())); - auto left = end_ptr_ + reserved_size - current_ptr_; + auto len = narrow_cast<int>(static_cast<std::streamoff>(ss->tellp())); + auto left = end_ptr_ + RESERVED_SIZE - current_ptr_; if (unlikely(len >= left)) { error_flag_ = true; len = left ? narrow_cast<int>(left - 1) : 0; @@ -92,10 +203,10 @@ StringBuilder &StringBuilder::operator<<(FixedDouble x) { } StringBuilder &StringBuilder::operator<<(const void *ptr) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve())) { return on_error(); } - current_ptr_ += std::snprintf(current_ptr_, reserved_size, "%p", ptr); + current_ptr_ += std::snprintf(current_ptr_, RESERVED_SIZE, "%p", ptr); return *this; } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h index a6345a9273..37adab338e 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/StringBuilder.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,22 +7,19 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/Slice-decl.h" +#include "td/utils/Slice.h" #include "td/utils/StackAllocator.h" #include <cstdlib> -#include <cstring> +#include <memory> #include <type_traits> namespace td { class StringBuilder { public: - explicit StringBuilder(MutableSlice slice) - : begin_ptr_(slice.begin()), current_ptr_(begin_ptr_), end_ptr_(slice.end() - reserved_size) { - if (slice.size() <= reserved_size) { - std::abort(); // shouldn't happen - } + explicit StringBuilder(MutableSlice slice, bool use_buffer = false); + StringBuilder() : StringBuilder({}, true) { } void clear() { @@ -30,8 +27,13 @@ class StringBuilder { error_flag_ = false; } + void pop_back() { + CHECK(current_ptr_ > begin_ptr_); + current_ptr_--; + } + MutableCSlice as_cslice() { - if (current_ptr_ >= end_ptr_ + reserved_size) { + if (current_ptr_ >= end_ptr_ + RESERVED_SIZE) { std::abort(); // shouldn't happen } *current_ptr_ = 0; @@ -42,31 +44,33 @@ class StringBuilder { return error_flag_; } - StringBuilder &operator<<(const char *str) { + template <class T> + std::enable_if_t<std::is_same<char *, std::remove_const_t<T>>::value, StringBuilder> &operator<<(T str) { + return *this << Slice(str); + } + template <class T> + std::enable_if_t<std::is_same<const char *, std::remove_const_t<T>>::value, StringBuilder> &operator<<(T str) { return *this << Slice(str); } - StringBuilder &operator<<(Slice slice) { - if (unlikely(end_ptr_ < current_ptr_)) { - return on_error(); - } - auto size = static_cast<size_t>(end_ptr_ + reserved_size - 1 - current_ptr_); - if (unlikely(slice.size() > size)) { - error_flag_ = true; - } else { - size = slice.size(); - } - std::memcpy(current_ptr_, slice.begin(), size); - current_ptr_ += size; - return *this; + template <size_t N> + StringBuilder &operator<<(char (&str)[N]) = delete; + + template <size_t N> + StringBuilder &operator<<(const char (&str)[N]) { + return *this << Slice(str, N - 1); } + StringBuilder &operator<<(const wchar_t *str) = delete; + + StringBuilder &operator<<(Slice slice); + StringBuilder &operator<<(bool b) { return *this << (b ? Slice("true") : Slice("false")); } StringBuilder &operator<<(char c) { - if (unlikely(end_ptr_ < current_ptr_)) { + if (unlikely(!reserve())) { return on_error(); } *current_ptr_++ = c; @@ -108,22 +112,33 @@ class StringBuilder { StringBuilder &operator<<(const void *ptr); - template <class T> - StringBuilder &operator<<(const T *ptr) { - return *this << static_cast<const void *>(ptr); - } - private: char *begin_ptr_; char *current_ptr_; char *end_ptr_; bool error_flag_ = false; - static constexpr size_t reserved_size = 30; + bool use_buffer_ = false; + std::unique_ptr<char[]> buffer_; + static constexpr size_t RESERVED_SIZE = 30; StringBuilder &on_error() { error_flag_ = true; return *this; } + + bool reserve() { + if (end_ptr_ > current_ptr_) { + return true; + } + return reserve_inner(RESERVED_SIZE); + } + bool reserve(size_t size) { + if (end_ptr_ > current_ptr_ && static_cast<size_t>(end_ptr_ - current_ptr_) >= size) { + return true; + } + return reserve_inner(size); + } + bool reserve_inner(size_t size); }; template <class T> diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadLocalStorage.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadLocalStorage.h new file mode 100644 index 0000000000..da42c4ac0e --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadLocalStorage.h @@ -0,0 +1,55 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/port/thread_local.h" + +#include <array> +#include <atomic> + +namespace td { + +template <class T> +class ThreadLocalStorage { + public: + T &get() { + return thread_local_node().value; + } + + template <class F> + void for_each(F &&f) { + int32 n = max_thread_id_.load(); + for (int32 i = 0; i < n; i++) { + f(nodes_[i].value); + } + } + template <class F> + void for_each(F &&f) const { + int32 n = max_thread_id_.load(); + for (int32 i = 0; i < n; i++) { + f(nodes_[i].value); + } + } + + private: + struct Node { + T value; + char padding[TD_CONCURRENCY_PAD]; + }; + static constexpr int32 MAX_THREAD_ID = 128; + std::atomic<int32> max_thread_id_{MAX_THREAD_ID}; + std::array<Node, MAX_THREAD_ID> nodes_; + + Node &thread_local_node() { + auto thread_id = get_thread_id(); + CHECK(0 <= thread_id && static_cast<size_t>(thread_id) < nodes_.size()); + return nodes_[thread_id]; + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadSafeCounter.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadSafeCounter.h new file mode 100644 index 0000000000..2d6f3f2950 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/ThreadSafeCounter.h @@ -0,0 +1,132 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Slice.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/ThreadLocalStorage.h" + +#include <array> +#include <atomic> +#include <mutex> + +namespace td { + +template <size_t N> +class ThreadSafeMultiCounter { + public: + void add(size_t index, int64 diff) { + CHECK(index < N); + tls_.get()[index].fetch_add(diff, std::memory_order_relaxed); + } + + int64 sum(size_t index) const { + CHECK(index < N); + int64 res = 0; + tls_.for_each([&res, &index](auto &value) { res += value[index].load(std::memory_order_relaxed); }); + return res; + } + void clear() { + tls_.for_each([](auto &value) { + for (auto &x : value) { + x = 0; + } + }); + } + + private: + ThreadLocalStorage<std::array<std::atomic<int64>, N>> tls_; +}; + +class ThreadSafeCounter { + public: + void add(int64 diff) { + counter_.add(0, diff); + } + + int64 sum() const { + return counter_.sum(0); + } + + void clear() { + counter_.clear(); + } + + private: + ThreadSafeMultiCounter<1> counter_; +}; + +class NamedThreadSafeCounter { + static constexpr int N = 128; + using Counter = ThreadSafeMultiCounter<N>; + + public: + class CounterRef { + public: + CounterRef() = default; + CounterRef(size_t index, Counter *counter) : index_(index), counter_(counter) { + } + void add(int64 diff) { + counter_->add(index_, diff); + } + int64 sum() const { + return counter_->sum(index_); + } + + private: + size_t index_{0}; + Counter *counter_{nullptr}; + }; + + CounterRef get_counter(Slice name) { + std::unique_lock<std::mutex> guard(mutex_); + for (size_t i = 0; i < names_.size(); i++) { + if (names_[i] == name) { + return get_counter_ref(i); + } + } + CHECK(names_.size() < N); + names_.emplace_back(name.begin(), name.size()); + return get_counter_ref(names_.size() - 1); + } + + CounterRef get_counter_ref(size_t index) { + return CounterRef(index, &counter_); + } + + static NamedThreadSafeCounter &get_default() { + static NamedThreadSafeCounter res; + return res; + } + + template <class F> + void for_each(F &&f) const { + std::unique_lock<std::mutex> guard(mutex_); + for (size_t i = 0; i < names_.size(); i++) { + f(names_[i], counter_.sum(i)); + } + } + + void clear() { + std::unique_lock<std::mutex> guard(mutex_); + counter_.clear(); + } + + friend StringBuilder &operator<<(StringBuilder &sb, const NamedThreadSafeCounter &counter) { + counter.for_each([&sb](Slice name, int64 cnt) { sb << name << ": " << cnt << "\n"; }); + return sb; + } + + private: + mutable std::mutex mutex_; + std::vector<std::string> names_; + + Counter counter_; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp index 3e62002c18..93ed7d1acd 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.cpp @@ -1,19 +1,49 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/Time.h" +#include "td/utils/port/Clocks.h" + +#include <atomic> #include <cmath> namespace td { -std::atomic<double> Time::now_; - bool operator==(Timestamp a, Timestamp b) { return std::abs(a.at() - b.at()) < 1e-6; } +static std::atomic<double> time_diff; + +double Time::now() { + auto result = now_unadjusted() + time_diff.load(std::memory_order_relaxed); + while (result < 0) { + auto old_time_diff = time_diff.load(); + time_diff.compare_exchange_strong(old_time_diff, old_time_diff - result); + result = now_unadjusted() + time_diff.load(std::memory_order_relaxed); + } + return result; +} + +double Time::now_unadjusted() { + return Clocks::monotonic(); +} + +void Time::jump_in_future(double at) { + while (true) { + auto old_time_diff = time_diff.load(); + auto diff = at - now(); + if (diff < 0) { + return; + } + if (time_diff.compare_exchange_strong(old_time_diff, old_time_diff + diff)) { + return; + } + } +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h index acdb8b52ef..4149c93d9d 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Time.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,25 +7,30 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/port/Clocks.h" - -#include <atomic> namespace td { class Time { public: - static double now() { - double now = Clocks::monotonic(); - now_.store(now, std::memory_order_relaxed); - return now; - } + static double now(); static double now_cached() { - return now_.load(std::memory_order_relaxed); + // Temporary(?) use now in now_cached + // Problem: + // thread A: check that now() > timestamp and notifies thread B + // thread B: must see that now() > timestamp() + // + // now() and now_cached() must be monotonic + // + // if a=now[_cached]() happens before b=now[_cached] than + // a <= b + // + // As an alternative we may say that now_cached is a thread local copy of now + return now(); } + static double now_unadjusted(); - private: - static std::atomic<double> now_; + // Used for testing. After jump_in_future(at) is called, now() >= at. + static void jump_in_future(double at); }; inline void relax_timeout_at(double *timeout, double new_timeout) { @@ -53,15 +58,18 @@ class Timestamp { return Timestamp{timeout}; } - static Timestamp in(double timeout) { - return Timestamp{Time::now_cached() + timeout}; + static Timestamp in(double timeout, Timestamp now = now_cached()) { + return Timestamp{now.at() + timeout}; } + bool is_in_past(Timestamp now) const { + return at_ <= now.at(); + } bool is_in_past() const { - return at_ <= Time::now_cached(); + return is_in_past(now_cached()); } - explicit operator bool() const { + explicit operator bool() const noexcept { return at_ > 0; } @@ -91,14 +99,8 @@ class Timestamp { } }; -template <class T> -void parse(Timestamp ×tamp, T &parser) { - timestamp = Timestamp::in(parser.fetch_double() - Clocks::system()); -} - -template <class T> -void store(const Timestamp ×tamp, T &storer) { - storer.store_binary(timestamp.at() - Time::now() + Clocks::system()); +inline bool operator<(const Timestamp &a, const Timestamp &b) { + return a.at() < b.at(); } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h index fc4197470d..db0e07495b 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TimedStat.h @@ -1,13 +1,15 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/logging.h" +#include "td/utils/common.h" +#include "td/utils/optional.h" +#include <functional> #include <utility> namespace td { @@ -16,7 +18,7 @@ template <class StatT> class TimedStat { public: TimedStat(double duration, double now) - : duration_(duration), current_(), current_timestamp_(now), next_(), next_timestamp_(now) { + : duration_(duration), current_(), current_timestamp_(now - 1), next_(), next_timestamp_(now) { } TimedStat() : TimedStat(0, 0) { } @@ -48,7 +50,7 @@ class TimedStat { void update(double &now) { if (now < next_timestamp_) { - CHECK(now >= next_timestamp_ * (1 - 1e-14)) << now << " " << next_timestamp_; + // LOG_CHECK(now >= next_timestamp_ * (1 - 1e-14)) << now << " " << next_timestamp_; now = next_timestamp_; } if (duration_ == 0) { @@ -56,7 +58,7 @@ class TimedStat { } if (next_timestamp_ + 2 * duration_ < now) { current_ = StatT(); - current_timestamp_ = now; + current_timestamp_ = now - duration_; next_ = StatT(); next_timestamp_ = now; } else if (next_timestamp_ + duration_ < now) { @@ -68,4 +70,28 @@ class TimedStat { } }; +namespace detail { +template <class T, class Cmp> +struct MinMaxStat { + using Event = T; + void on_event(Event event) { + if (!best_ || Cmp()(event, best_.value())) { + best_ = event; + } + } + optional<T> get_stat() const { + return best_.copy(); + } + + private: + optional<T> best_; +}; +} // namespace detail + +template <class T> +using MinStat = detail::MinMaxStat<T, std::less<void>>; + +template <class T> +using MaxStat = detail::MinMaxStat<T, std::greater<void>>; + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp index dc35721caa..215e1664af 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,34 +8,65 @@ #include "td/utils/format.h" #include "td/utils/logging.h" -//#include "td/utils/Slice.h" // TODO move StringBuilder implementation to cpp, remove header #include "td/utils/Time.h" namespace td { -Timer::Timer() : start_time_(Time::now()) { +Timer::Timer(bool is_paused) { + if (!is_paused) { + resume(); + } +} + +void Timer::pause() { + if (is_paused_) { + return; + } + elapsed_ += Time::now() - start_time_; + is_paused_ = true; +} + +void Timer::resume() { + if (!is_paused_) { + return; + } + start_time_ = Time::now(); + is_paused_ = false; +} + +double Timer::elapsed() const { + double res = elapsed_; + if (!is_paused_) { + res += Time::now() - start_time_; + } + return res; } StringBuilder &operator<<(StringBuilder &string_builder, const Timer &timer) { - return string_builder << "in " << Time::now() - timer.start_time_; + return string_builder << " in " << format::as_time(timer.elapsed()); } PerfWarningTimer::PerfWarningTimer(string name, double max_duration) : name_(std::move(name)), start_at_(Time::now()), max_duration_(max_duration) { } -PerfWarningTimer::PerfWarningTimer(PerfWarningTimer &&other) +PerfWarningTimer::PerfWarningTimer(PerfWarningTimer &&other) noexcept : name_(std::move(other.name_)), start_at_(other.start_at_), max_duration_(other.max_duration_) { other.start_at_ = 0; } PerfWarningTimer::~PerfWarningTimer() { + reset(); +} + +void PerfWarningTimer::reset() { if (start_at_ == 0) { return; } double duration = Time::now() - start_at_; LOG_IF(WARNING, duration > max_duration_) << "SLOW: " << tag("name", name_) << tag("duration", format::as_time(duration)); + start_at_ = 0; } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h index 65b879088d..bedab6ad4b 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Timer.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -12,12 +12,22 @@ namespace td { class Timer { public: - Timer(); + Timer() : Timer(false) { + } + explicit Timer(bool is_paused); + + double elapsed() const; + + void pause(); + + void resume(); private: friend StringBuilder &operator<<(StringBuilder &string_builder, const Timer &timer); - double start_time_; + double elapsed_{0}; + double start_time_{0}; + bool is_paused_{true}; }; class PerfWarningTimer { @@ -25,9 +35,10 @@ class PerfWarningTimer { explicit PerfWarningTimer(string name, double max_duration = 0.1); PerfWarningTimer(const PerfWarningTimer &) = delete; PerfWarningTimer &operator=(const PerfWarningTimer &) = delete; - PerfWarningTimer(PerfWarningTimer &&other); + PerfWarningTimer(PerfWarningTimer &&other) noexcept; PerfWarningTimer &operator=(PerfWarningTimer &&) = delete; ~PerfWarningTimer(); + void reset(); private: string name_; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TlDowncastHelper.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TlDowncastHelper.h new file mode 100644 index 0000000000..3f0c09a1cf --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TlDowncastHelper.h @@ -0,0 +1,29 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/TlStorerToString.h" + +namespace td { + +template <class T> +class TlDowncastHelper final : public T { + public: + explicit TlDowncastHelper(int32 constructor) : constructor_(constructor) { + } + int32 get_id() const final { + return constructor_; + } + void store(TlStorerToString &s, const char *field_name) const final { + } + + private: + int32 constructor_{0}; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TlStorerToString.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TlStorerToString.h new file mode 100644 index 0000000000..db246135bf --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TlStorerToString.h @@ -0,0 +1,180 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/SharedSlice.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/UInt.h" + +namespace td { + +class TlStorerToString { + string result; + size_t shift = 0; + + void store_field_begin(const char *name) { + result.append(shift, ' '); + if (name && name[0]) { + result += name; + result += " = "; + } + } + + void store_field_end() { + result += '\n'; + } + + void store_long(int64 value) { + result += (PSLICE() << value).c_str(); + } + + void store_binary(Slice data) { + static const char *hex = "0123456789ABCDEF"; + + result.append("{ ", 2); + for (auto c : data) { + unsigned char byte = c; + result += hex[byte >> 4]; + result += hex[byte & 15]; + result += ' '; + } + result += '}'; + } + + public: + TlStorerToString() = default; + TlStorerToString(const TlStorerToString &other) = delete; + TlStorerToString &operator=(const TlStorerToString &other) = delete; + + void store_field(const char *name, bool value) { + store_field_begin(name); + result += (value ? "true" : "false"); + store_field_end(); + } + + void store_field(const char *name, int32 value) { + store_field(name, static_cast<int64>(value)); + } + + void store_field(const char *name, int64 value) { + store_field_begin(name); + store_long(value); + store_field_end(); + } + + void store_field(const char *name, double value) { + store_field_begin(name); + result += (PSLICE() << value).c_str(); + store_field_end(); + } + + void store_field(const char *name, const char *value) { + store_field_begin(name); + result += value; + store_field_end(); + } + + void store_field(const char *name, const string &value) { + store_field_begin(name); + result += '"'; + result += value; + result += '"'; + store_field_end(); + } + + void store_field(const char *name, const SecureString &value) { + store_field_begin(name); + result.append("<secret>"); + store_field_end(); + } + + template <class T> + void store_field(const char *name, const T &value) { + store_field_begin(name); + result.append(value.data(), value.size()); + store_field_end(); + } + + void store_bytes_field(const char *name, const SecureString &value) { + store_field_begin(name); + result.append("<secret>"); + store_field_end(); + } + + template <class BytesT> + void store_bytes_field(const char *name, const BytesT &value) { + static const char *hex = "0123456789ABCDEF"; + + store_field_begin(name); + result.append("bytes ["); + store_long(static_cast<int64>(value.size())); + result.append("] { "); + size_t len = min(static_cast<size_t>(64), value.size()); + for (size_t i = 0; i < len; i++) { + int b = value[static_cast<int>(i)] & 0xff; + result += hex[b >> 4]; + result += hex[b & 15]; + result += ' '; + } + if (len < value.size()) { + result.append("..."); + } + result += '}'; + store_field_end(); + } + + template <class ObjectT> + void store_object_field(const char *name, const ObjectT *value) { + if (value == nullptr) { + store_field(name, "null"); + } else { + value->store(*this, name); + } + } + + void store_field(const char *name, const UInt128 &value) { + store_field_begin(name); + store_binary(as_slice(value)); + store_field_end(); + } + + void store_field(const char *name, const UInt256 &value) { + store_field_begin(name); + store_binary(as_slice(value)); + store_field_end(); + } + + void store_vector_begin(const char *field_name, size_t vector_size) { + store_field_begin(field_name); + result += "vector["; + result += (PSLICE() << vector_size).c_str(); + result += "] {\n"; + shift += 2; + } + + void store_class_begin(const char *field_name, const char *class_name) { + store_field_begin(field_name); + result += class_name; + result += " {\n"; + shift += 2; + } + + void store_class_end() { + CHECK(shift >= 2); + shift -= 2; + result.append(shift, ' '); + result += "}\n"; + } + + string move_as_string() { + return std::move(result); + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.cpp new file mode 100644 index 0000000000..df7080b68d --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.cpp @@ -0,0 +1,64 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/TsCerr.h" + +#include "td/utils/ExitGuard.h" +#include "td/utils/port/StdStreams.h" +#include "td/utils/Time.h" + +#include <cerrno> + +namespace td { + +std::atomic_flag TsCerr::lock_ = ATOMIC_FLAG_INIT; + +TsCerr::TsCerr() { + enterCritical(); +} + +TsCerr::~TsCerr() { + exitCritical(); +} + +TsCerr &TsCerr::operator<<(Slice slice) { + auto &fd = Stderr(); + if (fd.empty()) { + return *this; + } + double end_time = 0; + while (!slice.empty()) { + auto res = fd.write(slice); + if (res.is_error()) { + if (res.error().code() == EPIPE) { + break; + } + // Resource temporary unavailable + if (end_time == 0) { + end_time = Time::now() + 0.01; + } else if (Time::now() > end_time) { + break; + } + continue; + } + slice.remove_prefix(res.ok()); + } + return *this; +} + +void TsCerr::enterCritical() { + while (lock_.test_and_set(std::memory_order_acquire) && !ExitGuard::is_exited()) { + // spin + } +} + +void TsCerr::exitCritical() { + lock_.clear(std::memory_order_release); +} + +static ExitGuard exit_guard; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.h new file mode 100644 index 0000000000..686003df3b --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsCerr.h @@ -0,0 +1,33 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/Slice.h" + +#include <atomic> + +namespace td { + +class TsCerr { + public: + TsCerr(); + TsCerr(const TsCerr &) = delete; + TsCerr &operator=(const TsCerr &) = delete; + TsCerr(TsCerr &&) = delete; + TsCerr &operator=(TsCerr &&) = delete; + ~TsCerr(); + + TsCerr &operator<<(Slice slice); + + private: + static std::atomic_flag lock_; + + static void enterCritical(); + static void exitCritical(); +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.cpp new file mode 100644 index 0000000000..44d9a420f7 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.cpp @@ -0,0 +1,106 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/TsFileLog.h" + +#include "td/utils/common.h" +#include "td/utils/FileLog.h" +#include "td/utils/logging.h" +#include "td/utils/port/thread_local.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" + +#include <array> +#include <atomic> +#include <limits> +#include <mutex> + +namespace td { + +namespace detail { +class TsFileLog final : public LogInterface { + public: + Status init(string path, int64 rotate_threshold, bool redirect_stderr) { + path_ = std::move(path); + rotate_threshold_ = rotate_threshold; + redirect_stderr_ = redirect_stderr; + for (size_t i = 0; i < logs_.size(); i++) { + logs_[i].id = i; + } + return init_info(&logs_[0]); + } + + void rotate() { + for (auto &info : logs_) { + if (info.is_inited.load(std::memory_order_acquire)) { + info.log.lazy_rotate(); + } + } + } + + private: + struct Info { + FileLog log; + std::atomic<bool> is_inited{false}; + size_t id; + }; + + static constexpr size_t MAX_THREAD_ID = 128; + int64 rotate_threshold_ = 0; + bool redirect_stderr_ = false; + std::string path_; + std::array<Info, MAX_THREAD_ID> logs_; + std::mutex init_mutex_; + + LogInterface *get_current_logger() { + auto *info = get_current_info(); + if (!info->is_inited.load(std::memory_order_relaxed)) { + std::unique_lock<std::mutex> lock(init_mutex_); + if (!info->is_inited.load(std::memory_order_relaxed)) { + init_info(info).ensure(); + } + } + return &info->log; + } + + Info *get_current_info() { + return &logs_[get_thread_id()]; + } + + Status init_info(Info *info) { + TRY_STATUS(info->log.init(get_path(info), std::numeric_limits<int64>::max(), info->id == 0 && redirect_stderr_)); + info->is_inited = true; + return Status::OK(); + } + + string get_path(const Info *info) const { + if (info->id == 0) { + return path_; + } + return PSTRING() << path_ << ".thread" << info->id << ".log"; + } + + void do_append(int log_level, CSlice slice) final { + get_current_logger()->do_append(log_level, slice); + } + + vector<string> get_file_paths() final { + vector<string> res; + for (auto &log : logs_) { + res.push_back(get_path(&log)); + } + return res; + } +}; +} // namespace detail + +Result<unique_ptr<LogInterface>> TsFileLog::create(string path, int64 rotate_threshold, bool redirect_stderr) { + auto res = make_unique<detail::TsFileLog>(); + TRY_STATUS(res->init(std::move(path), rotate_threshold, redirect_stderr)); + return std::move(res); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.h new file mode 100644 index 0000000000..2a84fec2a1 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsFileLog.h @@ -0,0 +1,23 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/Status.h" + +namespace td { + +class TsFileLog { + static constexpr int64 DEFAULT_ROTATE_THRESHOLD = 10 * (1 << 20); + + public: + static Result<unique_ptr<LogInterface>> create(string path, int64 rotate_threshold = DEFAULT_ROTATE_THRESHOLD, + bool redirect_stderr = true); +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsList.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsList.h new file mode 100644 index 0000000000..8302a6d500 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsList.h @@ -0,0 +1,214 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/List.h" + +#include <mutex> + +namespace td { + +template <class DataT> +class TsList; + +template <class DataT> +class TsListNode : protected ListNode { + public: + TsListNode() { + clear(); + } + explicit TsListNode(DataT &&data) : data_(std::move(data)) { + clear(); + } + + ~TsListNode() { + remove(); + } + + std::unique_lock<std::mutex> lock() TD_WARN_UNUSED_RESULT; + + TsListNode(const TsListNode &) = delete; + TsListNode &operator=(const TsListNode &) = delete; + + TsListNode(TsListNode &&other) noexcept { + other.validate(); + if (other.empty()) { + data_ = std::move(other.data_); + clear(); + } else { + auto guard = other.lock(); + init_from(std::move(other)); + } + validate(); + other.validate(); + } + + TsListNode &operator=(TsListNode &&other) noexcept { + validate(); + if (this == &other) { + return *this; + } + other.validate(); + remove(); + + if (other.empty()) { + data_ = std::move(other.data_); + } else { + auto guard = other.lock(); + init_from(std::move(other)); + } + + validate(); + other.validate(); + return *this; + } + + void validate() { + if (empty()) { + CHECK(ListNode::empty()); + } else { + auto guard = lock(); + CHECK(!ListNode::empty() || is_root); + } + } + + void remove() { + validate(); + if (is_root) { + CHECK(ListNode::empty()); + return; + } + if (empty()) { + CHECK(ListNode::empty()); + return; + } + { + auto guard = lock(); + ListNode::remove(); + if (!is_root) { + parent = nullptr; + } + } + validate(); + } + + void put(TsListNode *other) { + validate(); + other->validate(); + DCHECK(other->empty()); + DCHECK(!empty()); + DCHECK(!other->is_root); + { + auto guard = lock(); + ListNode::put(other); + other->parent = parent; + } + validate(); + other->validate(); + } + + void put_back(TsListNode *other) { + DCHECK(other->empty()); + DCHECK(!empty()); + DCHECK(!other->is_root); + auto guard = lock(); + ListNode::put_back(other); + other->parent = parent; + } + + bool empty() const { + return parent == nullptr; + } + + TsListNode *get_next() { + return static_cast<TsListNode *>(next); + } + TsListNode *get_prev() { + return static_cast<TsListNode *>(prev); + } + + DataT &get_data_unsafe() { + return data_; + } + + private: + TsList<DataT> *parent; + bool is_root{false}; + DataT data_; + + friend class TsList<DataT>; + + void clear() { + ListNode::clear(); + if (!is_root) { + parent = nullptr; + } + } + + void init_from(TsListNode &&other) { + ListNode::init_from(std::move(other)); + parent = other.parent; + other.parent = nullptr; + data_ = std::move(other.data_); + } +}; + +template <class DataT> +class TsList final : public TsListNode<DataT> { + public: + TsList() { + this->parent = this; + this->is_root = true; + } + TsList(const TsList &) = delete; + TsList &operator=(const TsList &) = delete; + TsList(TsList &&) = delete; + TsList &operator=(TsList &&) = delete; + ~TsList() { + auto guard = lock(); + while (true) { + auto res = static_cast<TsListNode<DataT> *>(ListNode::get()); + if (!res) { + break; + } + res->parent = nullptr; + } + this->parent = nullptr; + } + std::unique_lock<std::mutex> lock() TD_WARN_UNUSED_RESULT { + return std::unique_lock<std::mutex>(mutex_); + } + TsListNode<DataT> *begin() { + return this->get_next(); + } + TsListNode<DataT> *end() { + return this; + } + TsListNode<DataT> *get() { + auto guard = lock(); + auto res = static_cast<TsListNode<DataT> *>(ListNode::get()); + if (res) { + res->parent = nullptr; + } + return res; + } + + private: + std::mutex mutex_; +}; + +template <class DataT> +std::unique_lock<std::mutex> TsListNode<DataT>::lock() { + if (parent == nullptr) { + return {}; + } + CHECK(parent != nullptr); + return parent->lock(); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.cpp new file mode 100644 index 0000000000..0dba99de8b --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.cpp @@ -0,0 +1,21 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/TsLog.h" + +#include "td/utils/ExitGuard.h" + +namespace td { + +void TsLog::enter_critical() { + while (lock_.test_and_set(std::memory_order_acquire) && !ExitGuard::is_exited()) { + // spin + } +} + +static ExitGuard exit_guard; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.h new file mode 100644 index 0000000000..7ef9f2c4b9 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/TsLog.h @@ -0,0 +1,55 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/Slice.h" + +#include <atomic> + +namespace td { + +class TsLog final : public LogInterface { + public: + explicit TsLog(LogInterface *log) : log_(log) { + } + void init(LogInterface *log) { + enter_critical(); + log_ = log; + exit_critical(); + } + void after_rotation() final { + enter_critical(); + log_->after_rotation(); + exit_critical(); + } + vector<string> get_file_paths() final { + enter_critical(); + auto result = log_->get_file_paths(); + exit_critical(); + return result; + } + + private: + void do_append(int log_level, CSlice slice) final { + enter_critical(); + log_->do_append(log_level, slice); + exit_critical(); + } + + void enter_critical(); + + void exit_critical() { + lock_.clear(std::memory_order_release); + } + + LogInterface *log_ = nullptr; + std::atomic_flag lock_ = ATOMIC_FLAG_INIT; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/UInt.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/UInt.h new file mode 100644 index 0000000000..03714d7a4f --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/UInt.h @@ -0,0 +1,91 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Slice.h" + +namespace td { + +template <size_t size> +struct UInt { + static_assert(size % 8 == 0, "size should be divisible by 8"); + uint8 raw[size / 8]; + + Slice as_slice() const { + return Slice(raw, size / 8); + } + + MutableSlice as_slice() { + return MutableSlice(raw, size / 8); + } + + bool is_zero() const { + for (size_t i = 0; i < size / 8; i++) { + if (raw[i] != 0) { + return false; + } + } + return true; + } + void set_zero() { + for (size_t i = 0; i < size / 8; i++) { + raw[i] = 0; + } + } + static UInt zero() { + UInt v; + v.set_zero(); + return v; + } +}; + +template <size_t size> +bool operator==(const UInt<size> &a, const UInt<size> &b) { + return a.as_slice() == b.as_slice(); +} + +template <size_t size> +bool operator!=(const UInt<size> &a, const UInt<size> &b) { + return !(a == b); +} + +template <size_t size> +UInt<size> operator^(const UInt<size> &a, const UInt<size> &b) { + UInt<size> res; + for (size_t i = 0; i < size / 8; i++) { + res.raw[i] = static_cast<uint8>(a.raw[i] ^ b.raw[i]); + } + return res; +} + +template <size_t size> +int get_kth_bit(const UInt<size> &a, uint32 bit) { + uint8 b = a.raw[bit / 8]; + bit &= 7; + return (b >> (7 - bit)) & 1; +} + +template <size_t size> +Slice as_slice(const UInt<size> &value) { + return value.as_slice(); +} + +template <size_t size> +MutableSlice as_slice(UInt<size> &value) { + return value.as_slice(); +} + +template <size_t size> +bool operator<(const UInt<size> &a, const UInt<size> &b) { + return a.as_slice() < b.as_slice(); +} + +using UInt128 = UInt<128>; +using UInt256 = UInt<256>; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h index 9b6e0561cc..9d064265fb 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/Variant.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -55,7 +55,7 @@ class IthTypeImpl<pos, Skip, Args...> : public IthTypeImpl<pos - 1, Args...> {}; class Dummy {}; template <size_t pos, class... Args> -class IthType : public IthTypeImpl<pos, Args..., Dummy> {}; +class IthType final : public IthTypeImpl<pos, Args..., Dummy> {}; template <bool ok, int offset, class... Types> class FindTypeOffsetImpl {}; @@ -69,7 +69,7 @@ template <int offset, class T, class S, class... Types> class FindTypeOffsetImpl<false, offset, T, S, Types...> : public FindTypeOffsetImpl<std::is_same<T, S>::value, offset + 1, T, Types...> {}; template <class T, class... Types> -class FindTypeOffset : public FindTypeOffsetImpl<false, -1, T, Types...> {}; +class FindTypeOffset final : public FindTypeOffsetImpl<false, -1, T, Types...> {}; template <int offset, class... Types> class ForEachTypeImpl {}; @@ -109,18 +109,21 @@ class Variant { static constexpr int npos = -1; Variant() { } - Variant(Variant &&other) { + Variant(Variant &&other) noexcept { other.visit([&](auto &&value) { this->init_empty(std::forward<decltype(value)>(value)); }); } Variant(const Variant &other) { other.visit([&](auto &&value) { this->init_empty(std::forward<decltype(value)>(value)); }); } - Variant &operator=(Variant &&other) { + Variant &operator=(Variant &&other) noexcept { clear(); other.visit([&](auto &&value) { this->init_empty(std::forward<decltype(value)>(value)); }); return *this; } Variant &operator=(const Variant &other) { + if (this == &other) { + return *this; + } clear(); other.visit([&](auto &&value) { this->init_empty(std::forward<decltype(value)>(value)); }); return *this; @@ -153,11 +156,11 @@ class Variant { return res; } - template <class T> + template <class T, std::enable_if_t<!std::is_same<std::decay_t<T>, Variant>::value, int> = 0> Variant(T &&t) { init_empty(std::forward<T>(t)); } - template <class T> + template <class T, std::enable_if_t<!std::is_same<std::decay_t<T>, Variant>::value, int> = 0> Variant &operator=(T &&t) { clear(); init_empty(std::forward<T>(t)); @@ -170,7 +173,11 @@ class Variant { template <class T> void init_empty(T &&t) { - CHECK(offset_ == npos); + LOG_CHECK(offset_ == npos) << offset_ +#if TD_CLANG || TD_GCC + << ' ' << __PRETTY_FUNCTION__ +#endif + ; offset_ = offset<T>(); new (&get<T>()) std::decay_t<T>(std::forward<T>(t)); } @@ -237,6 +244,10 @@ class Variant { return offset_; } + bool empty() const { + return offset_ == npos; + } + private: union { int64 align_; @@ -283,4 +294,5 @@ template <int T, class... Types> auto &get(const Variant<Types...> &v) { return v.template get<T>(); } + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/VectorQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/VectorQueue.h new file mode 100644 index 0000000000..c67a440caf --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/VectorQueue.h @@ -0,0 +1,94 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Span.h" + +#include <utility> + +namespace td { + +template <class T> +class VectorQueue { + public: + template <class S> + void push(S &&s) { + vector_.emplace_back(std::forward<S>(s)); + } + + template <class... Args> + void emplace(Args &&...args) { + vector_.emplace_back(std::forward<Args>(args)...); + } + + T pop() { + try_shrink(); + return std::move(vector_[read_pos_++]); + } + + template <class RndT> + T pop_rand(RndT &rnd) { + auto i = rnd() % size(); + std::swap(vector_[i], vector_[read_pos_]); + return pop(); + } + + void pop_n(size_t n) { + read_pos_ += n; + try_shrink(); + } + + const T &front() const { + return vector_[read_pos_]; + } + T &front() { + return vector_[read_pos_]; + } + + const T &back() const { + return vector_.back(); + } + T &back() { + return vector_.back(); + } + + bool empty() const { + return size() == 0; + } + + size_t size() const { + return vector_.size() - read_pos_; + } + + const T *data() const { + return vector_.data() + read_pos_; + } + T *data() { + return vector_.data() + read_pos_; + } + + Span<T> as_span() const { + return {data(), size()}; + } + MutableSpan<T> as_mutable_span() { + return {vector_.data() + read_pos_, size()}; + } + + private: + vector<T> vector_; + size_t read_pos_{0}; + + void try_shrink() { + if (read_pos_ * 2 > vector_.size() && read_pos_ > 4) { + vector_.erase(vector_.begin(), vector_.begin() + read_pos_); + read_pos_ = 0; + } + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashMap.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashMap.h new file mode 100644 index 0000000000..2b34bebaa1 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashMap.h @@ -0,0 +1,190 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/HashTableUtils.h" + +#include <functional> + +namespace td { + +template <class KeyT, class ValueT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>> +class WaitFreeHashMap { + static constexpr size_t MAX_STORAGE_COUNT = 1 << 8; + static_assert((MAX_STORAGE_COUNT & (MAX_STORAGE_COUNT - 1)) == 0, ""); + static constexpr uint32 DEFAULT_STORAGE_SIZE = 1 << 12; + + FlatHashMap<KeyT, ValueT, HashT, EqT> default_map_; + struct WaitFreeStorage { + WaitFreeHashMap maps_[MAX_STORAGE_COUNT]; + }; + unique_ptr<WaitFreeStorage> wait_free_storage_; + uint32 hash_mult_ = 1; + uint32 max_storage_size_ = DEFAULT_STORAGE_SIZE; + + uint32 get_wait_free_index(const KeyT &key) const { + return randomize_hash(HashT()(key) * hash_mult_) & (MAX_STORAGE_COUNT - 1); + } + + WaitFreeHashMap &get_wait_free_storage(const KeyT &key) { + return wait_free_storage_->maps_[get_wait_free_index(key)]; + } + + const WaitFreeHashMap &get_wait_free_storage(const KeyT &key) const { + return wait_free_storage_->maps_[get_wait_free_index(key)]; + } + + void split_storage() { + CHECK(wait_free_storage_ == nullptr); + wait_free_storage_ = make_unique<WaitFreeStorage>(); + uint32 next_hash_mult = hash_mult_ * 1000000007; + for (uint32 i = 0; i < MAX_STORAGE_COUNT; i++) { + auto &map = wait_free_storage_->maps_[i]; + map.hash_mult_ = next_hash_mult; + map.max_storage_size_ = DEFAULT_STORAGE_SIZE + i * next_hash_mult % DEFAULT_STORAGE_SIZE; + } + for (auto &it : default_map_) { + get_wait_free_storage(it.first).set(it.first, std::move(it.second)); + } + default_map_.clear(); + } + + public: + void set(const KeyT &key, ValueT value) { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).set(key, std::move(value)); + } + + default_map_[key] = std::move(value); + if (default_map_.size() == max_storage_size_) { + split_storage(); + } + } + + ValueT get(const KeyT &key) const { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).get(key); + } + + auto it = default_map_.find(key); + if (it == default_map_.end()) { + return {}; + } + return it->second; + } + + size_t count(const KeyT &key) const { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).count(key); + } + + return default_map_.count(key); + } + + // specialization for WaitFreeHashMap<..., unique_ptr<T>> + template <class T = ValueT> + typename T::element_type *get_pointer(const KeyT &key) { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).get_pointer(key); + } + + auto it = default_map_.find(key); + if (it == default_map_.end()) { + return nullptr; + } + return it->second.get(); + } + + template <class T = ValueT> + const typename T::element_type *get_pointer(const KeyT &key) const { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).get_pointer(key); + } + + auto it = default_map_.find(key); + if (it == default_map_.end()) { + return nullptr; + } + return it->second.get(); + } + + ValueT &operator[](const KeyT &key) { + if (wait_free_storage_ == nullptr) { + ValueT &result = default_map_[key]; + if (default_map_.size() != max_storage_size_) { + return result; + } + + split_storage(); + } + + return get_wait_free_storage(key)[key]; + } + + size_t erase(const KeyT &key) { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).erase(key); + } + + return default_map_.erase(key); + } + + void foreach(const std::function<void(const KeyT &key, ValueT &value)> &callback) { + if (wait_free_storage_ == nullptr) { + for (auto &it : default_map_) { + callback(it.first, it.second); + } + return; + } + + for (auto &it : wait_free_storage_->maps_) { + it.foreach(callback); + } + } + + void foreach(const std::function<void(const KeyT &key, const ValueT &value)> &callback) const { + if (wait_free_storage_ == nullptr) { + for (auto &it : default_map_) { + callback(it.first, it.second); + } + return; + } + + for (auto &it : wait_free_storage_->maps_) { + it.foreach(callback); + } + } + + size_t calc_size() const { + if (wait_free_storage_ == nullptr) { + return default_map_.size(); + } + + size_t result = 0; + for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) { + result += wait_free_storage_->maps_[i].calc_size(); + } + return result; + } + + bool empty() const { + if (wait_free_storage_ == nullptr) { + return default_map_.empty(); + } + + for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) { + if (!wait_free_storage_->maps_[i].empty()) { + return false; + } + } + return true; + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashSet.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashSet.h new file mode 100644 index 0000000000..5fddc0903d --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeHashSet.h @@ -0,0 +1,141 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/HashTableUtils.h" + +#include <functional> + +namespace td { + +template <class KeyT, class HashT = Hash<KeyT>, class EqT = std::equal_to<KeyT>> +class WaitFreeHashSet { + static constexpr size_t MAX_STORAGE_COUNT = 1 << 8; + static_assert((MAX_STORAGE_COUNT & (MAX_STORAGE_COUNT - 1)) == 0, ""); + static constexpr uint32 DEFAULT_STORAGE_SIZE = 1 << 12; + + FlatHashSet<KeyT, HashT, EqT> default_set_; + struct WaitFreeStorage { + WaitFreeHashSet sets_[MAX_STORAGE_COUNT]; + }; + unique_ptr<WaitFreeStorage> wait_free_storage_; + uint32 hash_mult_ = 1; + uint32 max_storage_size_ = DEFAULT_STORAGE_SIZE; + + uint32 get_wait_free_index(const KeyT &key) const { + return randomize_hash(HashT()(key) * hash_mult_) & (MAX_STORAGE_COUNT - 1); + } + + WaitFreeHashSet &get_wait_free_storage(const KeyT &key) { + return wait_free_storage_->sets_[get_wait_free_index(key)]; + } + + const WaitFreeHashSet &get_wait_free_storage(const KeyT &key) const { + return wait_free_storage_->sets_[get_wait_free_index(key)]; + } + + void split_storage() { + CHECK(wait_free_storage_ == nullptr); + wait_free_storage_ = make_unique<WaitFreeStorage>(); + uint32 next_hash_mult = hash_mult_ * 1000000007; + for (uint32 i = 0; i < MAX_STORAGE_COUNT; i++) { + auto &set = wait_free_storage_->sets_[i]; + set.hash_mult_ = next_hash_mult; + set.max_storage_size_ = DEFAULT_STORAGE_SIZE + i * next_hash_mult % DEFAULT_STORAGE_SIZE; + } + for (auto &it : default_set_) { + get_wait_free_storage(it).insert(it); + } + default_set_.clear(); + } + + public: + void insert(const KeyT &key) { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).insert(key); + } + + default_set_.insert(key); + if (default_set_.size() == max_storage_size_) { + split_storage(); + } + } + + size_t count(const KeyT &key) const { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).count(key); + } + + return default_set_.count(key); + } + + size_t erase(const KeyT &key) { + if (wait_free_storage_ != nullptr) { + return get_wait_free_storage(key).erase(key); + } + + return default_set_.erase(key); + } + + void foreach(const std::function<void(const KeyT &key)> &callback) const { + if (wait_free_storage_ == nullptr) { + for (auto &it : default_set_) { + callback(it); + } + return; + } + + for (auto &it : wait_free_storage_->sets_) { + it.foreach(callback); + } + } + + KeyT get_random() const { + if (wait_free_storage_ != nullptr) { + for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) { + if (!wait_free_storage_->sets_[i].empty()) { + return wait_free_storage_->sets_[i].get_random(); + } + } + // no need to explicitly return KeyT() + } + + if (default_set_.empty()) { + return KeyT(); + } + return *default_set_.begin(); + } + + size_t calc_size() const { + if (wait_free_storage_ == nullptr) { + return default_set_.size(); + } + + size_t result = 0; + for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) { + result += wait_free_storage_->sets_[i].calc_size(); + } + return result; + } + + bool empty() const { + if (wait_free_storage_ == nullptr) { + return default_set_.empty(); + } + + for (size_t i = 0; i < MAX_STORAGE_COUNT; i++) { + if (!wait_free_storage_->sets_[i].empty()) { + return false; + } + } + return true; + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeVector.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeVector.h new file mode 100644 index 0000000000..40ffd41c6b --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/WaitFreeVector.h @@ -0,0 +1,69 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#include <utility> + +namespace td { + +template <class T> +class WaitFreeVector { + static constexpr size_t MAX_VECTOR_SIZE = (1 << 15) - 10; + + vector<vector<T>> storage_; + + public: + template <class... ArgsT> + void emplace_back(ArgsT &&...args) { + if (storage_.empty() || storage_.back().size() == MAX_VECTOR_SIZE) { + storage_.emplace_back(); + } + storage_.back().emplace_back(std::forward<ArgsT>(args)...); + } + + void pop_back() { + storage_.back().pop_back(); + if (storage_.back().empty()) { + storage_.pop_back(); + } + } + + void push_back(T &&value) { + emplace_back(std::move(value)); + } + + void push_back(const T &value) { + emplace_back(value); + } + + const T &back() const { + return storage_.back().back(); + } + + T &operator[](size_t index) { + return storage_[index / MAX_VECTOR_SIZE][index % MAX_VECTOR_SIZE]; + } + + const T &operator[](size_t index) const { + return storage_[index / MAX_VECTOR_SIZE][index % MAX_VECTOR_SIZE]; + } + + size_t size() const { + if (storage_.empty()) { + return 0; + } + return (storage_.size() - 1) * MAX_VECTOR_SIZE + storage_.back().size(); + } + + bool empty() const { + return storage_.empty(); + } +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/algorithm.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/algorithm.h new file mode 100644 index 0000000000..0c3702d8c7 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/algorithm.h @@ -0,0 +1,217 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#include <functional> +#include <type_traits> +#include <utility> + +namespace td { + +namespace detail { + +template <typename V> +struct transform_helper { + template <class Func> + auto transform(const V &v, const Func &f) { + vector<decltype(f(*v.begin()))> result; + result.reserve(v.size()); + for (auto &x : v) { + result.push_back(f(x)); + } + return result; + } + + template <class Func> + auto transform(V &&v, const Func &f) { + vector<decltype(f(std::move(*v.begin())))> result; + result.reserve(v.size()); + for (auto &x : v) { + result.push_back(f(std::move(x))); + } + return result; + } +}; + +} // namespace detail + +template <class V, class Func> +auto transform(V &&v, const Func &f) { + return detail::transform_helper<std::decay_t<V>>().transform(std::forward<V>(v), f); +} + +template <class V, class Func> +bool remove_if(V &v, const Func &f) { + size_t i = 0; + while (i != v.size() && !f(v[i])) { + i++; + } + if (i == v.size()) { + return false; + } + + size_t j = i; + while (++i != v.size()) { + if (!f(v[i])) { + v[j++] = std::move(v[i]); + } + } + v.erase(v.begin() + j, v.end()); + return true; +} + +template <class V, class T> +bool remove(V &v, const T &value) { + size_t i = 0; + while (i != v.size() && v[i] != value) { + i++; + } + if (i == v.size()) { + return false; + } + + size_t j = i; + while (++i != v.size()) { + if (v[i] != value) { + v[j++] = std::move(v[i]); + } + } + v.erase(v.begin() + j, v.end()); + return true; +} + +template <class V> +void unique(V &v) { + if (v.empty()) { + return; + } + + // use ADL to find std::sort + // caller will need to #include <algorithm> + sort(v.begin(), v.end(), std::less<void>()); + + size_t j = 1; + for (size_t i = 1; i < v.size(); i++) { + if (v[i] != v[j - 1]) { + if (i != j) { + v[j] = std::move(v[i]); + } + j++; + } + } + v.resize(j); +} + +template <class V, class T> +bool contains(const V &v, const T &value) { + for (auto &x : v) { + if (x == value) { + return true; + } + } + return false; +} + +template <class V, class F> +bool all_of(const V &v, F &&f) { + for (const auto &x : v) { + if (!f(x)) { + return false; + } + } + return true; +} + +template <class T> +void reset_to_empty(T &value) { + using std::swap; + std::decay_t<T> tmp; + swap(tmp, value); +} + +template <class T> +void append(vector<T> &destination, const vector<T> &source) { + destination.insert(destination.end(), source.begin(), source.end()); +} + +template <class T> +void append(vector<T> &destination, vector<T> &&source) { + if (destination.empty()) { + destination.swap(source); + return; + } + destination.reserve(destination.size() + source.size()); + for (auto &elem : source) { + destination.push_back(std::move(elem)); + } + reset_to_empty(source); +} + +template <class T> +void combine(vector<T> &destination, const vector<T> &source) { + append(destination, source); +} + +template <class T> +void combine(vector<T> &destination, vector<T> &&source) { + if (destination.size() < source.size()) { + destination.swap(source); + } + if (source.empty()) { + return; + } + destination.reserve(destination.size() + source.size()); + for (auto &elem : source) { + destination.push_back(std::move(elem)); + } + reset_to_empty(source); +} + +namespace detail { +template <typename T> +struct reversion_wrapper { + T &iterable; +}; + +template <typename T> +auto begin(reversion_wrapper<T> w) { + return w.iterable.rbegin(); +} + +template <typename T> +auto end(reversion_wrapper<T> w) { + return w.iterable.rend(); +} +} // namespace detail + +template <typename T> +detail::reversion_wrapper<T> reversed(T &iterable) { + return {iterable}; +} + +template <class TableT, class FuncT> +void table_remove_if(TableT &table, FuncT &&func) { + for (auto it = table.begin(); it != table.end();) { + if (func(*it)) { + it = table.erase(it); + } else { + ++it; + } + } +} + +template <class NodeT, class HashT, class EqT> +class FlatHashTable; + +template <class NodeT, class HashT, class EqT, class FuncT> +void table_remove_if(FlatHashTable<NodeT, HashT, EqT> &table, FuncT &&func) { + table.remove_if(func); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/as.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/as.h new file mode 100644 index 0000000000..e73c4a1b3b --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/as.h @@ -0,0 +1,81 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/type_traits.h" + +#include <cstring> +#include <type_traits> + +namespace td { + +namespace detail { + +template <class T> +class As { + public: + explicit As(void *ptr) : ptr_(ptr) { + } + + As(const As &new_value) = delete; + As &operator=(const As &) = delete; + As(As &&) = default; + As &operator=(As &&new_value) &&noexcept { + std::memcpy(ptr_, new_value.ptr_, sizeof(T)); + return *this; + } + ~As() = default; + + As &operator=(const T &new_value) && { + std::memcpy(ptr_, &new_value, sizeof(T)); + return *this; + } + + operator T() const { + T res; + std::memcpy(&res, ptr_, sizeof(T)); + return res; + } + bool operator==(const As &other) const { + return this->operator T() == other.operator T(); + } + + private: + void *ptr_; +}; + +template <class T> +class ConstAs { + public: + explicit ConstAs(const void *ptr) : ptr_(ptr) { + } + + operator T() const { + T res; + std::memcpy(&res, ptr_, sizeof(T)); + return res; + } + + private: + const void *ptr_; +}; + +} // namespace detail + +template <class ToT, class FromT, + std::enable_if_t<TD_IS_TRIVIALLY_COPYABLE(ToT) && TD_IS_TRIVIALLY_COPYABLE(FromT), int> = 0> +detail::As<ToT> as(FromT *from) { + return detail::As<ToT>(from); +} + +template <class ToT, class FromT, + std::enable_if_t<TD_IS_TRIVIALLY_COPYABLE(ToT) && TD_IS_TRIVIALLY_COPYABLE(FromT), int> = 0> +detail::ConstAs<ToT> as(const FromT *from) { + return detail::ConstAs<ToT>(from); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp index 4016feaa58..2e06a21b86 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,7 +7,6 @@ #include "td/utils/base64.h" #include "td/utils/common.h" -#include "td/utils/logging.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" @@ -15,57 +14,68 @@ #include <iterator> namespace td { -//TODO: fix copypaste -static const char *const symbols64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +template <bool is_url> +static const char *get_characters() { + return is_url ? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +} -string base64_encode(Slice input) { +template <bool is_url> +static const unsigned char *get_character_table() { + static unsigned char char_to_value[256]; + static bool is_inited = [] { + auto characters = get_characters<is_url>(); + std::fill(std::begin(char_to_value), std::end(char_to_value), static_cast<unsigned char>(64)); + for (unsigned char i = 0; i < 64; i++) { + char_to_value[static_cast<size_t>(characters[i])] = i; + } + return true; + }(); + CHECK(is_inited); + return char_to_value; +} + +template <bool is_url> +string base64_encode_impl(Slice input) { + auto characters = get_characters<is_url>(); string base64; base64.reserve((input.size() + 2) / 3 * 4); for (size_t i = 0; i < input.size();) { size_t left = min(input.size() - i, static_cast<size_t>(3)); int c = input.ubegin()[i++] << 16; - base64 += symbols64[c >> 18]; + base64 += characters[c >> 18]; if (left != 1) { c |= input.ubegin()[i++] << 8; } - base64 += symbols64[(c >> 12) & 63]; + base64 += characters[(c >> 12) & 63]; if (left == 3) { c |= input.ubegin()[i++]; } if (left != 1) { - base64 += symbols64[(c >> 6) & 63]; - } else { + base64 += characters[(c >> 6) & 63]; + } else if (!is_url) { base64 += '='; } if (left == 3) { - base64 += symbols64[c & 63]; - } else { + base64 += characters[c & 63]; + } else if (!is_url) { base64 += '='; } } return base64; } -static unsigned char char_to_value[256]; -static void init_base64_table() { - static bool is_inited = []() { - std::fill(std::begin(char_to_value), std::end(char_to_value), 64); - for (unsigned char i = 0; i < 64; i++) { - char_to_value[static_cast<size_t>(symbols64[i])] = i; - } - return true; - }(); - CHECK(is_inited); +string base64_encode(Slice input) { + return base64_encode_impl<false>(input); } -Result<string> base64_decode(Slice base64) { - init_base64_table(); - - if ((base64.size() & 3) != 0) { - return Status::Error("Wrong string length"); - } +string base64url_encode(Slice input) { + return base64_encode_impl<true>(input); +} +template <bool is_url> +Result<Slice> base64_drop_padding(Slice base64) { size_t padding_length = 0; while (!base64.empty() && base64.back() == '=') { base64.remove_suffix(1); @@ -74,121 +84,80 @@ Result<string> base64_decode(Slice base64) { if (padding_length >= 3) { return Status::Error("Wrong string padding"); } + if ((!is_url || padding_length > 0) && ((base64.size() + padding_length) & 3) != 0) { + return Status::Error("Wrong padding length"); + } + if (is_url && (base64.size() & 3) == 1) { + return Status::Error("Wrong string length"); + } + return base64; +} - string output; - output.reserve(((base64.size() + 3) >> 2) * 3); +static Status do_base64_decode_impl(Slice base64, const unsigned char *table, char *ptr) { for (size_t i = 0; i < base64.size();) { size_t left = min(base64.size() - i, static_cast<size_t>(4)); int c = 0; for (size_t t = 0; t < left; t++) { - auto value = char_to_value[base64.ubegin()[i++]]; + auto value = table[base64.ubegin()[i++]]; if (value == 64) { return Status::Error("Wrong character in the string"); } c |= value << ((3 - t) * 6); } - output += static_cast<char>(static_cast<unsigned char>(c >> 16)); // implementation-defined + *ptr++ = static_cast<char>(static_cast<unsigned char>(c >> 16)); // implementation-defined if (left == 2) { if ((c & ((1 << 16) - 1)) != 0) { return Status::Error("Wrong padding in the string"); } } else { - output += static_cast<char>(static_cast<unsigned char>(c >> 8)); // implementation-defined + *ptr++ = static_cast<char>(static_cast<unsigned char>(c >> 8)); // implementation-defined if (left == 3) { if ((c & ((1 << 8) - 1)) != 0) { return Status::Error("Wrong padding in the string"); } } else { - output += static_cast<char>(static_cast<unsigned char>(c)); // implementation-defined + *ptr++ = static_cast<char>(static_cast<unsigned char>(c)); // implementation-defined } } } - return output; + return Status::OK(); } -static const char *const url_symbols64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; +template <class T> +static T create_empty(size_t size); -string base64url_encode(Slice input) { - string base64; - base64.reserve((input.size() + 2) / 3 * 4); - for (size_t i = 0; i < input.size();) { - size_t left = min(input.size() - i, static_cast<size_t>(3)); - int c = input.ubegin()[i++] << 16; - base64 += url_symbols64[c >> 18]; - if (left != 1) { - c |= input.ubegin()[i++] << 8; - } - base64 += url_symbols64[(c >> 12) & 63]; - if (left == 3) { - c |= input.ubegin()[i++]; - } - if (left != 1) { - base64 += url_symbols64[(c >> 6) & 63]; - } - if (left == 3) { - base64 += url_symbols64[c & 63]; - } - } - return base64; +template <> +string create_empty<string>(size_t size) { + return string(size, '\0'); } -static unsigned char url_char_to_value[256]; -static void init_base64url_table() { - static bool is_inited = []() { - std::fill(std::begin(url_char_to_value), std::end(url_char_to_value), 64); - for (unsigned char i = 0; i < 64; i++) { - url_char_to_value[static_cast<size_t>(url_symbols64[i])] = i; - } - return true; - }(); - CHECK(is_inited); +template <> +SecureString create_empty<SecureString>(size_t size) { + return SecureString{size}; } -Result<string> base64url_decode(Slice base64) { - init_base64url_table(); +template <bool is_url, class T> +static Result<T> base64_decode_impl(Slice base64) { + TRY_RESULT_ASSIGN(base64, base64_drop_padding<is_url>(base64)); - size_t padding_length = 0; - while (!base64.empty() && base64.back() == '=') { - base64.remove_suffix(1); - padding_length++; - } - if (padding_length >= 3 || (padding_length > 0 && ((base64.size() + padding_length) & 3) != 0)) { - return Status::Error("Wrong string padding"); - } + T result = create_empty<T>(base64.size() / 4 * 3 + ((base64.size() & 3) + 1) / 2); + TRY_STATUS(do_base64_decode_impl(base64, get_character_table<is_url>(), as_mutable_slice(result).begin())); + return std::move(result); +} - if ((base64.size() & 3) == 1) { - return Status::Error("Wrong string length"); - } +Result<string> base64_decode(Slice base64) { + return base64_decode_impl<false, string>(base64); +} - string output; - output.reserve(((base64.size() + 3) >> 2) * 3); - for (size_t i = 0; i < base64.size();) { - size_t left = min(base64.size() - i, static_cast<size_t>(4)); - int c = 0; - for (size_t t = 0; t < left; t++) { - auto value = url_char_to_value[base64.ubegin()[i++]]; - if (value == 64) { - return Status::Error("Wrong character in the string"); - } - c |= value << ((3 - t) * 6); - } - output += static_cast<char>(static_cast<unsigned char>(c >> 16)); // implementation-defined - if (left == 2) { - if ((c & ((1 << 16) - 1)) != 0) { - return Status::Error("Wrong padding in the string"); - } - } else { - output += static_cast<char>(static_cast<unsigned char>(c >> 8)); // implementation-defined - if (left == 3) { - if ((c & ((1 << 8) - 1)) != 0) { - return Status::Error("Wrong padding in the string"); - } - } else { - output += static_cast<char>(static_cast<unsigned char>(c)); // implementation-defined - } - } - } - return output; +Result<SecureString> base64_decode_secure(Slice base64) { + return base64_decode_impl<false, SecureString>(base64); +} + +Result<string> base64url_decode(Slice base64) { + return base64_decode_impl<true, string>(base64); +} +Result<SecureString> base64url_decode_secure(Slice base64) { + return base64_decode_impl<true, SecureString>(base64); } template <bool is_url> @@ -208,14 +177,7 @@ static bool is_base64_impl(Slice input) { return false; } - unsigned char *table; - if (is_url) { - init_base64url_table(); - table = url_char_to_value; - } else { - init_base64_table(); - table = char_to_value; - } + auto table = get_character_table<is_url>(); for (auto c : input) { if (table[static_cast<unsigned char>(c)] == 64) { return false; @@ -223,13 +185,13 @@ static bool is_base64_impl(Slice input) { } if ((input.size() & 3) == 2) { - auto value = table[static_cast<int>(input.back())]; + auto value = table[static_cast<unsigned char>(input.back())]; if ((value & 15) != 0) { return false; } } if ((input.size() & 3) == 3) { - auto value = table[static_cast<int>(input.back())]; + auto value = table[static_cast<unsigned char>(input.back())]; if ((value & 3) != 0) { return false; } @@ -246,16 +208,101 @@ bool is_base64url(Slice input) { return is_base64_impl<true>(input); } +template <bool is_url> +static bool is_base64_characters_impl(Slice input) { + auto table = get_character_table<is_url>(); + for (auto c : input) { + if (table[static_cast<unsigned char>(c)] == 64) { + return false; + } + } + return true; +} + +bool is_base64_characters(Slice input) { + return is_base64_characters_impl<false>(input); +} + +bool is_base64url_characters(Slice input) { + return is_base64_characters_impl<true>(input); +} + string base64_filter(Slice input) { + auto table = get_character_table<false>(); string res; res.reserve(input.size()); - init_base64_table(); for (auto c : input) { - if (char_to_value[static_cast<unsigned char>(c)] != 64 || c == '=') { + if (table[static_cast<unsigned char>(c)] != 64 || c == '=') { res += c; } } return res; } +static const char *get_base32_characters(bool upper_case) { + return upper_case ? "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" : "abcdefghijklmnopqrstuvwxyz234567"; +} + +static const unsigned char *get_base32_character_table() { + static unsigned char char_to_value[256]; + static bool is_inited = [] { + std::fill(std::begin(char_to_value), std::end(char_to_value), static_cast<unsigned char>(32)); + auto characters_lc = get_base32_characters(false); + auto characters_uc = get_base32_characters(true); + for (unsigned char i = 0; i < 32; i++) { + char_to_value[static_cast<size_t>(characters_lc[i])] = i; + char_to_value[static_cast<size_t>(characters_uc[i])] = i; + } + return true; + }(); + CHECK(is_inited); + return char_to_value; +} + +string base32_encode(Slice input, bool upper_case) { + auto *characters = get_base32_characters(upper_case); + string base32; + base32.reserve((input.size() * 8 + 4) / 5); + uint32 c = 0; + uint32 length = 0; + for (size_t i = 0; i < input.size(); i++) { + c = (c << 8) | input.ubegin()[i]; + length += 8; + while (length >= 5) { + length -= 5; + base32.push_back(characters[(c >> length) & 31]); + } + } + if (length != 0) { + base32.push_back(characters[(c << (5 - length)) & 31]); + } + //TODO: optional padding + return base32; +} + +Result<string> base32_decode(Slice base32) { + string res; + res.reserve(base32.size() * 5 / 8); + uint32 c = 0; + uint32 length = 0; + auto *table = get_base32_character_table(); + for (size_t i = 0; i < base32.size(); i++) { + auto value = table[base32.ubegin()[i]]; + if (value == 32) { + return Status::Error("Wrong character in the string"); + } + c = (c << 5) | value; + length += 5; + if (length >= 8) { + length -= 8; + res.push_back(static_cast<char>((c >> length) & 255)); + } + } + if ((c & ((1 << length) - 1)) != 0) { + return Status::Error("Nonzero padding"); + } + //TODO: check padding + return res; +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h index cef2b4cb34..4b7449aa65 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/base64.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,6 +7,7 @@ #pragma once #include "td/utils/common.h" +#include "td/utils/SharedSlice.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" @@ -14,13 +15,21 @@ namespace td { string base64_encode(Slice input); Result<string> base64_decode(Slice base64); +Result<SecureString> base64_decode_secure(Slice base64); string base64url_encode(Slice input); Result<string> base64url_decode(Slice base64); +Result<SecureString> base64url_decode_secure(Slice base64); bool is_base64(Slice input); bool is_base64url(Slice input); +bool is_base64_characters(Slice input); +bool is_base64url_characters(Slice input); + string base64_filter(Slice input); +string base32_encode(Slice input, bool upper_case = false); +Result<string> base32_decode(Slice base32); + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h index ddc7ad75e6..65a03cb7a3 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/benchmark.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,14 +15,14 @@ #include <tuple> #include <utility> -#define BENCH(name, desc) \ - class name##Bench : public ::td::Benchmark { \ - public: \ - std::string get_description() const override { \ - return (desc); \ - } \ - void run(int n) override; \ - }; \ +#define BENCH(name, desc) \ + class name##Bench final : public ::td::Benchmark { \ + public: \ + std::string get_description() const final { \ + return (desc); \ + } \ + void run(int n) final; \ + }; \ void name##Bench::run(int n) namespace td { diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/bits.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/bits.h new file mode 100644 index 0000000000..69bd7ef8c9 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/bits.h @@ -0,0 +1,310 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#if TD_MSVC +#include <intrin.h> +#endif + +#ifdef bswap32 +#undef bswap32 +#endif + +#ifdef bswap64 +#undef bswap64 +#endif + +namespace td { + +int32 count_leading_zeroes32(uint32 x); +int32 count_leading_zeroes64(uint64 x); +int32 count_trailing_zeroes32(uint32 x); +int32 count_trailing_zeroes64(uint64 x); +uint32 bswap32(uint32 x); +uint64 bswap64(uint64 x); +int32 count_bits32(uint32 x); +int32 count_bits64(uint64 x); + +inline uint32 bits_negate32(uint32 x) { + return ~x + 1; +} + +inline uint64 bits_negate64(uint64 x) { + return ~x + 1; +} + +inline uint32 lower_bit32(uint32 x) { + return x & bits_negate32(x); +} + +inline uint64 lower_bit64(uint64 x) { + return x & bits_negate64(x); +} + +inline uint64 host_to_big_endian64(uint64 x) { + // NB: works only for little-endian systems + return bswap64(x); +} +inline uint64 big_endian_to_host64(uint64 x) { + // NB: works only for little-endian systems + return bswap64(x); +} + +//TODO: optimize +inline int32 count_leading_zeroes_non_zero32(uint32 x) { + DCHECK(x != 0); + return count_leading_zeroes32(x); +} +inline int32 count_leading_zeroes_non_zero64(uint64 x) { + DCHECK(x != 0); + return count_leading_zeroes64(x); +} +inline int32 count_trailing_zeroes_non_zero32(uint32 x) { + DCHECK(x != 0); + return count_trailing_zeroes32(x); +} +inline int32 count_trailing_zeroes_non_zero64(uint64 x) { + DCHECK(x != 0); + return count_trailing_zeroes64(x); +} + +// +// Platform specific implementation +// +#if TD_MSVC + +inline int32 count_leading_zeroes32(uint32 x) { + unsigned long res = 0; + if (_BitScanReverse(&res, x)) { + return 31 - res; + } + return 32; +} + +inline int32 count_leading_zeroes64(uint64 x) { +#if defined(_M_X64) + unsigned long res = 0; + if (_BitScanReverse64(&res, x)) { + return 63 - res; + } + return 64; +#else + if ((x >> 32) == 0) { + return count_leading_zeroes32(static_cast<uint32>(x)) + 32; + } else { + return count_leading_zeroes32(static_cast<uint32>(x >> 32)); + } +#endif +} + +inline int32 count_trailing_zeroes32(uint32 x) { + unsigned long res = 0; + if (_BitScanForward(&res, x)) { + return res; + } + return 32; +} + +inline int32 count_trailing_zeroes64(uint64 x) { +#if defined(_M_X64) + unsigned long res = 0; + if (_BitScanForward64(&res, x)) { + return res; + } + return 64; +#else + if (static_cast<uint32>(x) == 0) { + return count_trailing_zeroes32(static_cast<uint32>(x >> 32)) + 32; + } else { + return count_trailing_zeroes32(static_cast<uint32>(x)); + } +#endif +} + +inline uint32 bswap32(uint32 x) { + return _byteswap_ulong(x); +} + +inline uint64 bswap64(uint64 x) { + return _byteswap_uint64(x); +} + +inline int32 count_bits32(uint32 x) { + // Do not use __popcnt because it will fail on some platforms. + x -= (x >> 1) & 0x55555555; + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + x += x >> 8; + return (x + (x >> 16)) & 0x3F; +} + +inline int32 count_bits64(uint64 x) { +#if defined(_M_X64) + return static_cast<int32>(__popcnt64(x)); +#else + return count_bits32(static_cast<uint32>(x >> 32)) + count_bits32(static_cast<uint32>(x)); +#endif +} + +#elif TD_INTEL + +inline int32 count_leading_zeroes32(uint32 x) { + unsigned __int32 res = 0; + if (_BitScanReverse(&res, x)) { + return 31 - res; + } + return 32; +} + +inline int32 count_leading_zeroes64(uint64 x) { +#if defined(_M_X64) || defined(__x86_64__) + unsigned __int32 res = 0; + if (_BitScanReverse64(&res, x)) { + return 63 - res; + } + return 64; +#else + if ((x >> 32) == 0) { + return count_leading_zeroes32(static_cast<uint32>(x)) + 32; + } else { + return count_leading_zeroes32(static_cast<uint32>(x >> 32)); + } +#endif +} + +inline int32 count_trailing_zeroes32(uint32 x) { + unsigned __int32 res = 0; + if (_BitScanForward(&res, x)) { + return res; + } + return 32; +} + +inline int32 count_trailing_zeroes64(uint64 x) { +#if defined(_M_X64) || defined(__x86_64__) + unsigned __int32 res = 0; + if (_BitScanForward64(&res, x)) { + return res; + } + return 64; +#else + if (static_cast<uint32>(x) == 0) { + return count_trailing_zeroes32(static_cast<uint32>(x >> 32)) + 32; + } else { + return count_trailing_zeroes32(static_cast<uint32>(x)); + } +#endif +} + +inline uint32 bswap32(uint32 x) { + return _bswap(static_cast<int>(x)); +} + +inline uint64 bswap64(uint64 x) { + return _bswap64(static_cast<__int64>(x)); +} + +inline int32 count_bits32(uint32 x) { + x -= (x >> 1) & 0x55555555; + x = (x & 0x33333333) + ((x >> 2) & 0x33333333); + x = (x + (x >> 4)) & 0x0F0F0F0F; + x += x >> 8; + return (x + (x >> 16)) & 0x3F; +} + +inline int32 count_bits64(uint64 x) { + return count_bits32(static_cast<uint32>(x >> 32)) + count_bits32(static_cast<uint32>(x)); +} + +#else + +inline int32 count_leading_zeroes32(uint32 x) { + if (x == 0) { + return 32; + } + return __builtin_clz(x); +} + +inline int32 count_leading_zeroes64(uint64 x) { + if (x == 0) { + return 64; + } + return __builtin_clzll(x); +} + +inline int32 count_trailing_zeroes32(uint32 x) { + if (x == 0) { + return 32; + } + return __builtin_ctz(x); +} + +inline int32 count_trailing_zeroes64(uint64 x) { + if (x == 0) { + return 64; + } + return __builtin_ctzll(x); +} + +inline uint32 bswap32(uint32 x) { + return __builtin_bswap32(x); +} + +inline uint64 bswap64(uint64 x) { + return __builtin_bswap64(x); +} + +inline int32 count_bits32(uint32 x) { + return __builtin_popcount(x); +} + +inline int32 count_bits64(uint64 x) { + return __builtin_popcountll(x); +} + +#endif + +struct BitsRange { + explicit BitsRange(uint64 bits = 0) : bits{bits}, pos{-1} { + } + + BitsRange begin() const { + return *this; + } + + BitsRange end() const { + return BitsRange{}; + } + + int32 operator*() const { + if (pos == -1) { + pos = count_trailing_zeroes64(bits); + } + return pos; + } + + bool operator!=(const BitsRange &other) const { + return bits != other.bits; + } + + BitsRange &operator++() { + auto i = **this; + if (i != 64) { + bits ^= 1ull << i; + } + pos = -1; + return *this; + } + + private: + uint64 bits{0}; + mutable int32 pos{-1}; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp index c1a123031c..f767e23ae4 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.cpp @@ -1,21 +1,34 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/buffer.h" +#include "td/utils/logging.h" #include "td/utils/port/thread_local.h" +#include <cstddef> #include <new> +// fixes https://bugs.llvm.org/show_bug.cgi?id=33723 for clang >= 3.6 + c++11 + libc++ +#if TD_CLANG && _LIBCPP_VERSION +#define TD_OFFSETOF __builtin_offsetof +#else +#define TD_OFFSETOF offsetof +#endif + namespace td { TD_THREAD_LOCAL BufferAllocator::BufferRawTls *BufferAllocator::buffer_raw_tls; // static zero-initialized std::atomic<size_t> BufferAllocator::buffer_mem; +int64 BufferAllocator::get_buffer_slice_size() { + return 0; +} + size_t BufferAllocator::get_buffer_mem() { return buffer_mem; } @@ -76,30 +89,113 @@ BufferAllocator::ReaderPtr BufferAllocator::create_reader(const ReaderPtr &raw) void BufferAllocator::dec_ref_cnt(BufferRaw *ptr) { int left = ptr->ref_cnt_.fetch_sub(1, std::memory_order_acq_rel); if (left == 1) { - auto buf_size = max(sizeof(BufferRaw), offsetof(BufferRaw, data_) + ptr->data_size_); + auto buf_size = max(sizeof(BufferRaw), TD_OFFSETOF(BufferRaw, data_) + ptr->data_size_); buffer_mem -= buf_size; ptr->~BufferRaw(); delete[] ptr; } } +size_t ChainBufferReader::advance(size_t offset, MutableSlice dest) { + LOG_CHECK(offset <= size()) << offset << " " << size() << " " << end_.offset() << " " << begin_.offset() << " " + << sync_flag_ << " " << dest.size(); + return begin_.advance(offset, dest); +} + BufferRaw *BufferAllocator::create_buffer_raw(size_t size) { size = (size + 7) & -8; - auto buf_size = offsetof(BufferRaw, data_) + size; + auto buf_size = TD_OFFSETOF(BufferRaw, data_) + size; if (buf_size < sizeof(BufferRaw)) { buf_size = sizeof(BufferRaw); } buffer_mem += buf_size; auto *buffer_raw = reinterpret_cast<BufferRaw *>(new char[buf_size]); - new (buffer_raw) BufferRaw(); - buffer_raw->data_size_ = size; - buffer_raw->begin_ = 0; - buffer_raw->end_ = 0; + return new (buffer_raw) BufferRaw(size); +} + +void BufferBuilder::append(BufferSlice slice) { + if (append_inplace(slice.as_slice())) { + return; + } + append_slow(std::move(slice)); +} + +void BufferBuilder::append(Slice slice) { + if (append_inplace(slice)) { + return; + } + append_slow(BufferSlice(slice)); +} + +void BufferBuilder::prepend(BufferSlice slice) { + if (prepend_inplace(slice.as_slice())) { + return; + } + prepend_slow(std::move(slice)); +} + +void BufferBuilder::prepend(Slice slice) { + if (prepend_inplace(slice)) { + return; + } + prepend_slow(BufferSlice(slice)); +} - buffer_raw->ref_cnt_.store(1, std::memory_order_relaxed); - buffer_raw->has_writer_.store(true, std::memory_order_relaxed); - buffer_raw->was_reader_ = false; - return buffer_raw; +BufferSlice BufferBuilder::extract() { + if (to_append_.empty() && to_prepend_.empty()) { + return buffer_writer_.as_buffer_slice(); + } + size_t total_size = size(); + BufferWriter writer(0, 0, total_size); + std::move(*this).for_each([&](auto &&slice) { + writer.prepare_append().truncate(slice.size()).copy_from(slice.as_slice()); + writer.confirm_append(slice.size()); + }); + *this = {}; + return writer.as_buffer_slice(); } + +size_t BufferBuilder::size() const { + size_t total_size = 0; + for_each([&](auto &&slice) { total_size += slice.size(); }); + return total_size; +} + +bool BufferBuilder::append_inplace(Slice slice) { + if (!to_append_.empty()) { + return false; + } + auto dest = buffer_writer_.prepare_append(); + if (dest.size() < slice.size()) { + return false; + } + dest.remove_suffix(dest.size() - slice.size()); + dest.copy_from(slice); + buffer_writer_.confirm_append(slice.size()); + return true; +} + +void BufferBuilder::append_slow(BufferSlice slice) { + to_append_.push_back(std::move(slice)); +} + +bool BufferBuilder::prepend_inplace(Slice slice) { + if (!to_prepend_.empty()) { + return false; + } + auto dest = buffer_writer_.prepare_prepend(); + if (dest.size() < slice.size()) { + return false; + } + dest.remove_prefix(dest.size() - slice.size()); + dest.copy_from(slice); + buffer_writer_.confirm_prepend(slice.size()); + return true; +} + +void BufferBuilder::prepend_slow(BufferSlice slice) { + to_prepend_.push_back(std::move(slice)); +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h index aa4ef8db26..798ceb9d50 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/buffer.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,33 +7,34 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/logging.h" #include "td/utils/port/thread_local.h" #include "td/utils/Slice.h" #include <atomic> -#include <cstring> #include <limits> +#include <memory> namespace td { struct BufferRaw { + explicit BufferRaw(size_t size) : data_size_(size) { + } size_t data_size_; // Constant after first reader is created. // May be change by writer before it. // So writer may do prepends till there is no reader created. - size_t begin_; + size_t begin_ = 0; // Write by writer. // Read by reader. - std::atomic<size_t> end_; + std::atomic<size_t> end_{0}; - mutable std::atomic<int32> ref_cnt_; - std::atomic<bool> has_writer_; - bool was_reader_; + mutable std::atomic<int32> ref_cnt_{1}; + std::atomic<bool> has_writer_{true}; + bool was_reader_{false}; - alignas(4) char data_[1]; + alignas(4) unsigned char data_[1]; }; class BufferAllocator { @@ -66,10 +67,16 @@ class BufferAllocator { static ReaderPtr create_reader(const ReaderPtr &raw); static size_t get_buffer_mem(); + static int64 get_buffer_slice_size(); static void clear_thread_local(); private: + friend class BufferSlice; + + static void track_buffer_slice(int64 size) { + } + static ReaderPtr create_reader_fast(size_t size); static WriterPtr create_writer_exact(size_t size); @@ -103,25 +110,54 @@ class BufferSlice { return; } begin_ = buffer_->begin_; + end_ = begin_; sync_with_writer(); } BufferSlice(BufferReaderPtr buffer_ptr, size_t begin, size_t end) : buffer_(std::move(buffer_ptr)), begin_(begin), end_(end) { + debug_track(); + } + BufferSlice(const BufferSlice &other) = delete; + BufferSlice &operator=(const BufferSlice &other) = delete; + BufferSlice(BufferSlice &&other) noexcept : BufferSlice(std::move(other.buffer_), other.begin_, other.end_) { + debug_untrack(); // yes, debug_untrack + } + BufferSlice &operator=(BufferSlice &&other) noexcept { + if (this == &other) { + return *this; + } + debug_untrack(); + buffer_ = std::move(other.buffer_); + begin_ = other.begin_; + end_ = other.end_; + return *this; } explicit BufferSlice(size_t size) : buffer_(BufferAllocator::create_reader(size)) { end_ = buffer_->end_.load(std::memory_order_relaxed); begin_ = end_ - ((size + 7) & -8); end_ = begin_ + size; + debug_track(); } explicit BufferSlice(Slice slice) : BufferSlice(slice.size()) { - std::memcpy(as_slice().begin(), slice.begin(), slice.size()); + as_slice().copy_from(slice); } BufferSlice(const char *ptr, size_t size) : BufferSlice(Slice(ptr, size)) { } + ~BufferSlice() { + debug_untrack(); + } + + void debug_track() const { + BufferAllocator::track_buffer_slice(static_cast<int64>(size())); + } + void debug_untrack() const { + BufferAllocator::track_buffer_slice(-static_cast<int64>(size())); + } + BufferSlice clone() const { if (is_null()) { return BufferSlice(BufferReaderPtr(), begin_, end_); @@ -143,6 +179,10 @@ class BufferSlice { return Slice(buffer_->data_ + begin_, size()); } + operator Slice() const { + return as_slice(); + } + MutableSlice as_slice() { if (is_null()) { return MutableSlice(); @@ -161,21 +201,27 @@ class BufferSlice { } bool confirm_read(size_t size) { + debug_untrack(); begin_ += size; CHECK(begin_ <= end_); + debug_track(); return begin_ == end_; } void truncate(size_t limit) { if (size() > limit) { + debug_untrack(); end_ = begin_ + limit; + debug_track(); } } BufferSlice from_slice(Slice slice) const { auto res = BufferSlice(BufferAllocator::create_reader(buffer_)); - res.begin_ = slice.begin() - buffer_->data_; - res.end_ = slice.end() - buffer_->data_; + res.debug_untrack(); + res.begin_ = static_cast<size_t>(slice.ubegin() - buffer_->data_); + res.end_ = static_cast<size_t>(slice.uend() - buffer_->data_); + res.debug_track(); CHECK(buffer_->begin_ <= res.begin_); CHECK(res.begin_ <= res.end_); CHECK(res.end_ <= buffer_->end_.load(std::memory_order_relaxed)); @@ -202,20 +248,36 @@ class BufferSlice { } size_t size() const { + if (is_null()) { + return 0; + } return end_ - begin_; } + // like in std::string + size_t length() const { + return size(); + } + // set end_ into writer's end_ size_t sync_with_writer() { + debug_untrack(); CHECK(!is_null()); auto old_end = end_; end_ = buffer_->end_.load(std::memory_order_acquire); + debug_track(); return end_ - old_end; } bool is_writer_alive() const { CHECK(!is_null()); return buffer_->has_writer_.load(std::memory_order_acquire); } + void clear() { + debug_untrack(); + begin_ = 0; + end_ = 0; + buffer_ = nullptr; + } private: BufferReaderPtr buffer_; @@ -241,6 +303,10 @@ class BufferWriter { BufferWriter(size_t size, size_t prepend, size_t append) : BufferWriter(BufferAllocator::create_writer(size, prepend, append)) { } + BufferWriter(Slice slice, size_t prepend, size_t append) + : BufferWriter(BufferAllocator::create_writer(slice.size(), prepend, append)) { + as_slice().copy_from(slice); + } explicit BufferWriter(BufferWriterPtr buffer_ptr) : buffer_(std::move(buffer_ptr)) { } @@ -263,6 +329,10 @@ class BufferWriter { auto end = buffer_->end_.load(std::memory_order_relaxed); return MutableSlice(buffer_->data_ + buffer_->begin_, buffer_->data_ + end); } + Slice as_slice() const { + auto end = buffer_->end_.load(std::memory_order_relaxed); + return Slice(buffer_->data_ + buffer_->begin_, buffer_->data_ + end); + } MutableSlice prepare_prepend() { if (is_null()) { @@ -481,7 +551,7 @@ class ChainBufferIterator { // copy to dest if possible auto to_dest_size = min(ready.size(), dest.size()); if (to_dest_size != 0) { - std::memcpy(dest.data(), ready.data(), to_dest_size); + dest.copy_from(ready.substr(0, to_dest_size)); dest.remove_prefix(to_dest_size); } @@ -541,10 +611,7 @@ class ChainBufferReader { begin_.confirm_read(size); } - size_t advance(size_t offset, MutableSlice dest = MutableSlice()) { - CHECK(offset <= size()); - return begin_.advance(offset, dest); - } + size_t advance(size_t offset, MutableSlice dest = MutableSlice()); size_t size() const { return end_.offset() - begin_.offset(); @@ -570,14 +637,14 @@ class ChainBufferReader { // Return [begin_, tail.begin_) // *this = tail - ChainBufferReader cut_head(ChainBufferIterator pos) { + ChainBufferReader cut_head(ChainBufferIterator pos) TD_WARN_UNUSED_RESULT { auto tmp = begin_.clone(); begin_ = pos.clone(); return ChainBufferReader(std::move(tmp), std::move(pos), false); } - ChainBufferReader cut_head(size_t offset) { - CHECK(offset <= size()) << offset << " " << size(); + ChainBufferReader cut_head(size_t offset) TD_WARN_UNUSED_RESULT { + CHECK(offset <= size()); auto it = begin_.clone(); it.advance(offset); return cut_head(std::move(it)); @@ -611,19 +678,10 @@ class ChainBufferReader { class ChainBufferWriter { public: - ChainBufferWriter() { - init(); - } - - // legacy - static ChainBufferWriter create_empty(size_t size = 0) { - return ChainBufferWriter(); - } - - void init(size_t size = 0) { - writer_ = BufferWriter(size); - tail_ = ChainBufferNodeAllocator::create(writer_.as_buffer_slice(), true); - head_ = ChainBufferNodeAllocator::clone(tail_); + ChainBufferWriter() + : writer_(0) + , tail_(ChainBufferNodeAllocator::create(writer_.as_buffer_slice(), true)) + , head_(ChainBufferNodeAllocator::clone(tail_)) { } MutableSlice prepare_append(size_t hint = 0) { @@ -634,6 +692,14 @@ class ChainBufferWriter { } return res; } + MutableSlice prepare_append_at_least(size_t size) { + CHECK(!empty()); + auto res = prepare_append_inplace(); + if (res.size() < size) { + return prepare_append_alloc(size); + } + return res; + } MutableSlice prepare_append_inplace() { CHECK(!empty()); return writer_.prepare_append(); @@ -655,11 +721,11 @@ class ChainBufferWriter { writer_.confirm_append(size); } - void append(Slice slice) { + void append(Slice slice, size_t hint = 0) { while (!slice.empty()) { - auto ready = prepare_append(slice.size()); + auto ready = prepare_append(td::max(slice.size(), hint)); auto shift = min(ready.size(), slice.size()); - std::memcpy(ready.data(), slice.data(), shift); + ready.copy_from(slice.substr(0, shift)); confirm_append(shift); slice.remove_prefix(shift); } @@ -700,9 +766,73 @@ class ChainBufferWriter { return !tail_; } - ChainBufferNodeReaderPtr head_; - ChainBufferNodeWriterPtr tail_; BufferWriter writer_; + ChainBufferNodeWriterPtr tail_; + ChainBufferNodeReaderPtr head_; +}; + +class BufferBuilder { + public: + BufferBuilder() = default; + BufferBuilder(Slice slice, size_t prepend_size, size_t append_size) + : buffer_writer_(slice, prepend_size, append_size) { + } + explicit BufferBuilder(BufferWriter &&buffer_writer) : buffer_writer_(std::move(buffer_writer)) { + } + + void append(BufferSlice slice); + void append(Slice slice); + + void prepend(BufferSlice slice); + void prepend(Slice slice); + + template <class F> + void for_each(F &&f) const & { + for (auto i = to_prepend_.size(); i > 0; i--) { + f(to_prepend_[i - 1].as_slice()); + } + if (!buffer_writer_.empty()) { + f(buffer_writer_.as_slice()); + } + for (auto &slice : to_append_) { + f(slice.as_slice()); + } + } + template <class F> + void for_each(F &&f) && { + for (auto i = to_prepend_.size(); i > 0; i--) { + f(std::move(to_prepend_[i - 1])); + } + if (!buffer_writer_.empty()) { + f(buffer_writer_.as_buffer_slice()); + } + for (auto &slice : to_append_) { + f(std::move(slice)); + } + } + size_t size() const; + + BufferSlice extract(); + + private: + BufferWriter buffer_writer_; + std::vector<BufferSlice> to_append_; + std::vector<BufferSlice> to_prepend_; + + bool append_inplace(Slice slice); + void append_slow(BufferSlice slice); + bool prepend_inplace(Slice slice); + void prepend_slow(BufferSlice slice); }; +inline Slice as_slice(const BufferSlice &value) { + return value.as_slice(); +} +inline MutableSlice as_slice(BufferSlice &value) { + return value.as_slice(); +} +inline MutableSlice as_mutable_slice(BufferSlice &value) { + return value.as_slice(); +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/check.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/check.cpp new file mode 100644 index 0000000000..3651452e52 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/check.cpp @@ -0,0 +1,23 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/check.h" + +#include "td/utils/logging.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" + +namespace td { +namespace detail { + +void process_check_error(const char *message, const char *file, int line) { + ::td::Logger(*log_interface, log_options, VERBOSITY_NAME(FATAL), Slice(file), line, Slice()) + << "Check `" << message << "` failed"; + ::td::process_fatal_error(PSLICE() << "Check `" << message << "` failed in " << file << " at " << line << '\n'); +} + +} // namespace detail +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/check.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/check.h new file mode 100644 index 0000000000..e6fba40c6f --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/check.h @@ -0,0 +1,32 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#define TD_DUMMY_CHECK(condition) ((void)(condition)) + +#define CHECK(condition) \ + if (!(condition)) { \ + ::td::detail::process_check_error(#condition, __FILE__, __LINE__); \ + } + +// clang-format off +#ifdef NDEBUG + #define DCHECK TD_DUMMY_CHECK +#else + #define DCHECK CHECK +#endif +// clang-format on + +#define UNREACHABLE() ::td::detail::process_check_error("Unreachable", __FILE__, __LINE__) + +namespace td { +namespace detail { + +[[noreturn]] void process_check_error(const char *message, const char *file, int line); + +} // namespace detail +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/common.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/common.h index d1217016e3..5bf9357f8a 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/common.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/common.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -33,20 +33,29 @@ #define _CRT_SECURE_NO_WARNINGS #endif - #include <Winsock2.h> - #include <ws2tcpip.h> + #ifdef __MINGW32__ + #include <winsock2.h> + #include <ws2tcpip.h> + + #include <mswsock.h> + #include <windows.h> + #else + #include <WinSock2.h> + #include <WS2tcpip.h> + + #include <MSWSock.h> + #include <Windows.h> + #endif - #include <Mswsock.h> - #include <Windows.h> #undef ERROR #endif // clang-format on +#include "td/utils/check.h" #include "td/utils/int_types.h" +#include "td/utils/unique_ptr.h" -#include <memory> #include <string> -#include <utility> #include <vector> #define TD_DEBUG @@ -64,6 +73,13 @@ #endif // clang-format on +#if TD_USE_ASAN +#include <sanitizer/lsan_interface.h> +#define TD_LSAN_IGNORE(x) __lsan_ignore_object(x) +#else +#define TD_LSAN_IGNORE(x) (void)(x) +#endif + namespace td { inline bool likely(bool x) { @@ -99,11 +115,6 @@ using string = std::string; template <class ValueT> using vector = std::vector<ValueT>; -template <class ValueT> -using unique_ptr = std::unique_ptr<ValueT>; - -using std::make_unique; - struct Unit {}; struct Auto { @@ -113,14 +124,4 @@ struct Auto { } }; -template <class ToT, class FromT> -ToT &as(FromT *from) { - return *reinterpret_cast<ToT *>(from); -} - -template <class ToT, class FromT> -const ToT &as(const FromT *from) { - return *reinterpret_cast<const ToT *>(from); -} - } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h index ac7462480d..bb346600e8 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h @@ -1,3 +1,8 @@ #pragma once + #define TD_HAVE_OPENSSL 1 #define TD_HAVE_ZLIB 1 +#define TD_HAVE_CRC32C 0 +#define TD_HAVE_COROUTINES 1 +#define TD_HAVE_ABSL 1 +#define TD_FD_DEBUG 1 diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in b/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in index 92cbd5cdc6..f8b89aeb5c 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/config.h.in @@ -1,3 +1,8 @@ #pragma once + #cmakedefine01 TD_HAVE_OPENSSL #cmakedefine01 TD_HAVE_ZLIB +#cmakedefine01 TD_HAVE_CRC32C +#cmakedefine01 TD_HAVE_COROUTINES +#cmakedefine01 TD_HAVE_ABSL +#cmakedefine01 TD_FD_DEBUG diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp index 3e54e673ab..4fcda63e40 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.cpp @@ -1,103 +1,123 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/crypto.h" +#include "td/utils/as.h" #include "td/utils/BigNum.h" +#include "td/utils/bits.h" +#include "td/utils/common.h" +#include "td/utils/Destructor.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/port/RwMutex.h" #include "td/utils/port/thread_local.h" #include "td/utils/Random.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/SharedSlice.h" +#include "td/utils/StackAllocator.h" +#include "td/utils/StringBuilder.h" #if TD_HAVE_OPENSSL #include <openssl/aes.h> +#include <openssl/bio.h> #include <openssl/crypto.h> +#include <openssl/err.h> #include <openssl/evp.h> #include <openssl/hmac.h> #include <openssl/md5.h> +#include <openssl/opensslv.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> #include <openssl/sha.h> #endif +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) +#include <openssl/core_names.h> +#include <openssl/params.h> +#endif + #if TD_HAVE_ZLIB #include <zlib.h> #endif +#if TD_HAVE_CRC32C +#include "crc32c/crc32c.h" +#endif + #include <algorithm> +#include <cerrno> #include <cstring> +#include <mutex> #include <utility> namespace td { -static uint64 gcd(uint64 a, uint64 b) { +static uint64 pq_gcd(uint64 a, uint64 b) { if (a == 0) { return b; } - if (b == 0) { - return a; - } - - int shift = 0; - while ((a & 1) == 0 && (b & 1) == 0) { + while ((a & 1) == 0) { a >>= 1; - b >>= 1; - shift++; } + DCHECK((b & 1) != 0); while (true) { - while ((a & 1) == 0) { - a >>= 1; - } - while ((b & 1) == 0) { - b >>= 1; - } if (a > b) { - a -= b; + a = (a - b) >> 1; + while ((a & 1) == 0) { + a >>= 1; + } } else if (b > a) { - b -= a; + b = (b - a) >> 1; + while ((b & 1) == 0) { + b >>= 1; + } } else { - return a << shift; + return a; + } + } +} + +// returns (c + a * b) % pq +static uint64 pq_add_mul(uint64 c, uint64 a, uint64 b, uint64 pq) { + while (b) { + if (b & 1) { + c += a; + if (c >= pq) { + c -= pq; + } + } + a += a; + if (a >= pq) { + a -= pq; } + b >>= 1; } + return c; } uint64 pq_factorize(uint64 pq) { - if (pq < 2 || pq > (static_cast<uint64>(1) << 63)) { + if (pq <= 2 || pq > (static_cast<uint64>(1) << 63)) { return 1; } + if ((pq & 1) == 0) { + return 2; + } uint64 g = 0; - for (int i = 0, it = 0; i < 3 || it < 1000; i++) { + for (int i = 0, iter = 0; i < 3 || iter < 1000; i++) { uint64 q = Random::fast(17, 32) % (pq - 1); uint64 x = Random::fast_uint64() % (pq - 1) + 1; uint64 y = x; int lim = 1 << (min(5, i) + 18); for (int j = 1; j < lim; j++) { - it++; - uint64 a = x; - uint64 b = x; - uint64 c = q; - - // c += a * b - while (b) { - if (b & 1) { - c += a; - if (c >= pq) { - c -= pq; - } - } - a += a; - if (a >= pq) { - a -= pq; - } - b >>= 1; - } - - x = c; + iter++; + x = pq_add_mul(q, x, x, pq); uint64 z = x < y ? pq + x - y : x - y; - g = gcd(z, pq); + g = pq_gcd(z, pq); if (g != 1) { break; } @@ -123,31 +143,29 @@ uint64 pq_factorize(uint64 pq) { void init_crypto() { static bool is_inited = [] { #if OPENSSL_VERSION_NUMBER >= 0x10100000L - return OPENSSL_init_crypto(0, nullptr) != 0; + bool result = OPENSSL_init_crypto(0, nullptr) != 0; #else OpenSSL_add_all_algorithms(); - return true; + bool result = true; #endif + clear_openssl_errors("Init crypto"); + return result; }(); CHECK(is_inited); } template <class FromT> static string as_big_endian_string(const FromT &from) { - size_t size = sizeof(from); - string res(size, '\0'); - - auto ptr = reinterpret_cast<const unsigned char *>(&from); - std::memcpy(&res[0], ptr, size); + char res[sizeof(FromT)]; + as<FromT>(res) = from; - size_t i = size; + size_t i = sizeof(FromT); while (i && res[i - 1] == 0) { i--; } - res.resize(i); - std::reverse(res.begin(), res.end()); - return res; + std::reverse(res, res + i); + return string(res, res + i); } static int pq_factorize_big(Slice pq_str, string *p_str, string *q_str) { @@ -164,14 +182,14 @@ static int pq_factorize_big(Slice pq_str, string *p_str, string *q_str) { BigNum pq = BigNum::from_binary(pq_str); bool found = false; - for (int i = 0, it = 0; !found && (i < 3 || it < 1000); i++) { + for (int i = 0, iter = 0; !found && (i < 3 || iter < 1000); i++) { int32 t = Random::fast(17, 32); a.set_value(Random::fast_uint32()); b = a; int32 lim = 1 << (i + 23); for (int j = 1; j < lim; j++) { - it++; + iter++; BigNum::mod_mul(a, a, a, pq, context); a += t; if (BigNum::compare(a, pq) >= 0) { @@ -236,156 +254,679 @@ int pq_factorize(Slice pq_str, string *p_str, string *q_str) { return 0; } -/*** AES ***/ -static void aes_ige_xcrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to, bool encrypt_flag) { - AES_KEY key; - int err; - if (encrypt_flag) { - err = AES_set_encrypt_key(aes_key.raw, 256, &key); - } else { - err = AES_set_decrypt_key(aes_key.raw, 256, &key); +struct AesBlock { + uint64 hi; + uint64 lo; + + uint8 *raw() { + return reinterpret_cast<uint8 *>(this); + } + const uint8 *raw() const { + return reinterpret_cast<const uint8 *>(this); + } + Slice as_slice() const { + return Slice(raw(), AES_BLOCK_SIZE); } - LOG_IF(FATAL, err != 0); - CHECK(from.size() <= to.size()); - AES_ige_encrypt(from.ubegin(), to.ubegin(), from.size(), &key, aes_iv->raw, encrypt_flag); -} -void aes_ige_encrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to) { - aes_ige_xcrypt(aes_key, aes_iv, from, to, true); -} + AesBlock operator^(const AesBlock &b) const { + AesBlock res; + res.hi = hi ^ b.hi; + res.lo = lo ^ b.lo; + return res; + } + void operator^=(const AesBlock &b) { + hi ^= b.hi; + lo ^= b.lo; + } -void aes_ige_decrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to) { - aes_ige_xcrypt(aes_key, aes_iv, from, to, false); -} + void load(const uint8 *from) { + *this = as<AesBlock>(from); + } + void store(uint8 *to) { + as<AesBlock>(to) = *this; + } + + AesBlock inc() const { +#if SIZE_MAX == UINT64_MAX + AesBlock res; + res.lo = host_to_big_endian64(big_endian_to_host64(lo) + 1); + if (res.lo == 0) { + res.hi = host_to_big_endian64(big_endian_to_host64(hi) + 1); + } else { + res.hi = hi; + } + return res; +#else + AesBlock res = *this; + auto ptr = res.raw(); + if (++ptr[15] == 0) { + for (int i = 14; i >= 0; i--) { + if (++ptr[i] != 0) { + break; + } + } + } + return res; +#endif + } +}; +static_assert(sizeof(AesBlock) == 16, ""); +static_assert(sizeof(AesBlock) == AES_BLOCK_SIZE, ""); + +class Evp { + public: + Evp() { + ctx_ = EVP_CIPHER_CTX_new(); + LOG_IF(FATAL, ctx_ == nullptr); + } + Evp(const Evp &from) = delete; + Evp &operator=(const Evp &from) = delete; + Evp(Evp &&from) = delete; + Evp &operator=(Evp &&from) = delete; + ~Evp() { + CHECK(ctx_ != nullptr); + EVP_CIPHER_CTX_free(ctx_); + } + + void init_encrypt_ecb(Slice key) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-ECB"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_ecb(); +#endif + init(true, evp_cipher, key); + } + + void init_decrypt_ecb(Slice key) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-ECB"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_ecb(); +#endif + init(false, evp_cipher, key); + } + + void init_encrypt_cbc(Slice key) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-CBC"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_cbc(); +#endif + init(true, evp_cipher, key); + } + + void init_decrypt_cbc(Slice key) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-CBC"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_cbc(); +#endif + init(false, evp_cipher, key); + } + +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + void init_encrypt_ctr(Slice key) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_CIPHER *evp_cipher; + if (unlikely(evp_cipher == nullptr)) { + init_thread_local_evp_cipher(evp_cipher, "AES-256-CTR"); + } +#else + const EVP_CIPHER *evp_cipher = EVP_aes_256_ctr(); +#endif + init(true, evp_cipher, key); + } +#endif + + void init_iv(Slice iv) { + int res = EVP_CipherInit_ex(ctx_, nullptr, nullptr, nullptr, iv.ubegin(), -1); + LOG_IF(FATAL, res != 1); + } + + void encrypt(const uint8 *src, uint8 *dst, int size) { + int len; + int res = EVP_EncryptUpdate(ctx_, dst, &len, src, size); + LOG_IF(FATAL, res != 1); + CHECK(len == size); + } -static void aes_cbc_xcrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to, bool encrypt_flag) { - AES_KEY key; - int err; - if (encrypt_flag) { - err = AES_set_encrypt_key(aes_key.raw, 256, &key); + void decrypt(const uint8 *src, uint8 *dst, int size) { + CHECK(size % AES_BLOCK_SIZE == 0); + int len; + int res = EVP_DecryptUpdate(ctx_, dst, &len, src, size); + LOG_IF(FATAL, res != 1); + CHECK(len == size); + } + + private: + EVP_CIPHER_CTX *ctx_{nullptr}; + + void init(bool is_encrypt, const EVP_CIPHER *evp_cipher, Slice key) { + int res = EVP_CipherInit_ex(ctx_, evp_cipher, nullptr, key.ubegin(), nullptr, is_encrypt ? 1 : 0); + LOG_IF(FATAL, res != 1); + EVP_CIPHER_CTX_set_padding(ctx_, 0); + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static void init_thread_local_evp_cipher(const EVP_CIPHER *&evp_cipher, const char *algorithm) { + evp_cipher = EVP_CIPHER_fetch(nullptr, algorithm, nullptr); + LOG_IF(FATAL, evp_cipher == nullptr); + detail::add_thread_local_destructor(create_destructor([&evp_cipher]() mutable { + EVP_CIPHER_free(const_cast<EVP_CIPHER *>(evp_cipher)); + evp_cipher = nullptr; + })); + } +#endif +}; + +struct AesState::Impl { + Evp evp; +}; + +AesState::AesState() = default; +AesState::AesState(AesState &&from) noexcept = default; +AesState &AesState::operator=(AesState &&from) noexcept = default; +AesState::~AesState() = default; + +void AesState::init(Slice key, bool encrypt) { + CHECK(key.size() == 32); + if (!impl_) { + impl_ = make_unique<Impl>(); + } + if (encrypt) { + impl_->evp.init_encrypt_ecb(key); } else { - err = AES_set_decrypt_key(aes_key.raw, 256, &key); + impl_->evp.init_decrypt_ecb(key); } - LOG_IF(FATAL, err != 0); - CHECK(from.size() <= to.size()); - AES_cbc_encrypt(from.ubegin(), to.ubegin(), from.size(), &key, aes_iv->raw, encrypt_flag); } -void aes_cbc_encrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to) { - aes_cbc_xcrypt(aes_key, aes_iv, from, to, true); +void AesState::encrypt(const uint8 *src, uint8 *dst, int size) { + CHECK(impl_); + impl_->evp.encrypt(src, dst, size); } -void aes_cbc_decrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to) { - aes_cbc_xcrypt(aes_key, aes_iv, from, to, false); +void AesState::decrypt(const uint8 *src, uint8 *dst, int size) { + CHECK(impl_); + impl_->evp.decrypt(src, dst, size); } -class AesCtrState::Impl { +class AesIgeStateImpl { public: - Impl(const UInt256 &key, const UInt128 &iv) { - if (AES_set_encrypt_key(key.raw, 256, &aes_key) < 0) { - LOG(FATAL) << "Failed to set encrypt key"; + void init(Slice key, Slice iv, bool encrypt) { + CHECK(key.size() == 32); + CHECK(iv.size() == 32); + if (encrypt) { + evp_.init_encrypt_cbc(key); + } else { + evp_.init_decrypt_ecb(key); } - MutableSlice(counter, AES_BLOCK_SIZE).copy_from({iv.raw, AES_BLOCK_SIZE}); - current_pos = 0; + + encrypted_iv_.load(iv.ubegin()); + plaintext_iv_.load(iv.ubegin() + AES_BLOCK_SIZE); + } + + void get_iv(MutableSlice iv) { + CHECK(iv.size() == 32); + encrypted_iv_.store(iv.ubegin()); + plaintext_iv_.store(iv.ubegin() + AES_BLOCK_SIZE); } void encrypt(Slice from, MutableSlice to) { + CHECK(from.size() % AES_BLOCK_SIZE == 0); CHECK(to.size() >= from.size()); - for (size_t i = 0; i < from.size(); i++) { - if (current_pos == 0) { - AES_encrypt(counter, encrypted_counter, &aes_key); - for (int j = 15; j >= 0; j--) { - if (++counter[j] != 0) { - break; - } + auto len = to.size() / AES_BLOCK_SIZE; + auto in = from.ubegin(); + auto out = to.ubegin(); + + static constexpr size_t BLOCK_COUNT = 31; + while (len != 0) { + AesBlock data[BLOCK_COUNT]; + AesBlock data_xored[BLOCK_COUNT]; + + auto count = td::min(BLOCK_COUNT, len); + std::memcpy(data, in, AES_BLOCK_SIZE * count); + data_xored[0] = data[0]; + if (count > 1) { + data_xored[1] = plaintext_iv_ ^ data[1]; + for (size_t i = 2; i < count; i++) { + data_xored[i] = data[i - 2] ^ data[i]; } } - to[i] = static_cast<char>(from[i] ^ encrypted_counter[current_pos]); - current_pos = (current_pos + 1) & 15; + + evp_.init_iv(encrypted_iv_.as_slice()); + auto inlen = static_cast<int>(AES_BLOCK_SIZE * count); + evp_.encrypt(data_xored[0].raw(), data_xored[0].raw(), inlen); + + data_xored[0] ^= plaintext_iv_; + for (size_t i = 1; i < count; i++) { + data_xored[i] ^= data[i - 1]; + } + plaintext_iv_ = data[count - 1]; + encrypted_iv_ = data_xored[count - 1]; + + std::memcpy(out, data_xored, AES_BLOCK_SIZE * count); + len -= count; + in += AES_BLOCK_SIZE * count; + out += AES_BLOCK_SIZE * count; + } + } + + void decrypt(Slice from, MutableSlice to) { + CHECK(from.size() % AES_BLOCK_SIZE == 0); + CHECK(to.size() >= from.size()); + auto len = to.size() / AES_BLOCK_SIZE; + auto in = from.ubegin(); + auto out = to.ubegin(); + + AesBlock encrypted; + + while (len) { + encrypted.load(in); + + plaintext_iv_ ^= encrypted; + evp_.decrypt(plaintext_iv_.raw(), plaintext_iv_.raw(), AES_BLOCK_SIZE); + plaintext_iv_ ^= encrypted_iv_; + + plaintext_iv_.store(out); + encrypted_iv_ = encrypted; + + --len; + in += AES_BLOCK_SIZE; + out += AES_BLOCK_SIZE; } } private: - AES_KEY aes_key; - uint8 counter[AES_BLOCK_SIZE]; - uint8 encrypted_counter[AES_BLOCK_SIZE]; - uint8 current_pos; + Evp evp_; + AesBlock encrypted_iv_; + AesBlock plaintext_iv_; +}; + +AesIgeState::AesIgeState() = default; +AesIgeState::AesIgeState(AesIgeState &&from) noexcept = default; +AesIgeState &AesIgeState::operator=(AesIgeState &&from) noexcept = default; +AesIgeState::~AesIgeState() = default; + +void AesIgeState::init(Slice key, Slice iv, bool encrypt) { + if (!impl_) { + impl_ = make_unique<AesIgeStateImpl>(); + } + + impl_->init(key, iv, encrypt); +} + +void AesIgeState::encrypt(Slice from, MutableSlice to) { + impl_->encrypt(from, to); +} + +void AesIgeState::decrypt(Slice from, MutableSlice to) { + impl_->decrypt(from, to); +} + +void aes_ige_encrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to) { + AesIgeStateImpl state; + state.init(aes_key, aes_iv, true); + state.encrypt(from, to); + state.get_iv(aes_iv); +} + +void aes_ige_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to) { + AesIgeStateImpl state; + state.init(aes_key, aes_iv, false); + state.decrypt(from, to); + state.get_iv(aes_iv); +} + +void aes_cbc_encrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to) { + CHECK(from.size() <= to.size()); + CHECK(from.size() % 16 == 0); + + Evp evp; + evp.init_encrypt_cbc(aes_key); + evp.init_iv(aes_iv); + evp.encrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size())); + aes_iv.copy_from(to.substr(from.size() - 16)); +} + +void aes_cbc_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to) { + CHECK(from.size() <= to.size()); + CHECK(from.size() % 16 == 0); + + Evp evp; + evp.init_decrypt_cbc(aes_key); + evp.init_iv(aes_iv); + aes_iv.copy_from(from.substr(from.size() - 16)); + evp.decrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size())); +} + +struct AesCbcState::Impl { + Evp evp_; +}; + +AesCbcState::AesCbcState(Slice key256, Slice iv128) : raw_{SecureString(key256), SecureString(iv128)} { + CHECK(raw_.key.size() == 32); + CHECK(raw_.iv.size() == 16); +} + +AesCbcState::AesCbcState(AesCbcState &&from) noexcept = default; +AesCbcState &AesCbcState::operator=(AesCbcState &&from) noexcept = default; +AesCbcState::~AesCbcState() = default; + +void AesCbcState::encrypt(Slice from, MutableSlice to) { + if (from.empty()) { + return; + } + + CHECK(from.size() <= to.size()); + CHECK(from.size() % 16 == 0); + if (ctx_ == nullptr) { + ctx_ = make_unique<AesCbcState::Impl>(); + ctx_->evp_.init_encrypt_cbc(raw_.key.as_slice()); + ctx_->evp_.init_iv(raw_.iv.as_slice()); + is_encrypt_ = true; + } else { + CHECK(is_encrypt_); + } + ctx_->evp_.encrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size())); + raw_.iv.as_mutable_slice().copy_from(to.substr(from.size() - 16)); +} + +void AesCbcState::decrypt(Slice from, MutableSlice to) { + if (from.empty()) { + return; + } + + CHECK(from.size() <= to.size()); + CHECK(from.size() % 16 == 0); + if (ctx_ == nullptr) { + ctx_ = make_unique<AesCbcState::Impl>(); + ctx_->evp_.init_decrypt_cbc(raw_.key.as_slice()); + ctx_->evp_.init_iv(raw_.iv.as_slice()); + is_encrypt_ = false; + } else { + CHECK(!is_encrypt_); + } + raw_.iv.as_mutable_slice().copy_from(from.substr(from.size() - 16)); + ctx_->evp_.decrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size())); +} + +struct AesCtrState::Impl { +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + Evp evp_; +#else + AES_KEY aes_key_; + uint8 counter_[AES_BLOCK_SIZE]; + uint8 encrypted_counter_[AES_BLOCK_SIZE]; + uint8 current_pos_; +#endif }; AesCtrState::AesCtrState() = default; -AesCtrState::AesCtrState(AesCtrState &&from) = default; -AesCtrState &AesCtrState::operator=(AesCtrState &&from) = default; +AesCtrState::AesCtrState(AesCtrState &&from) noexcept = default; +AesCtrState &AesCtrState::operator=(AesCtrState &&from) noexcept = default; AesCtrState::~AesCtrState() = default; -void AesCtrState::init(const UInt256 &key, const UInt128 &iv) { - ctx_ = std::make_unique<AesCtrState::Impl>(key, iv); +void AesCtrState::init(Slice key, Slice iv) { + CHECK(key.size() == 32); + CHECK(iv.size() == 16); + ctx_ = make_unique<AesCtrState::Impl>(); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + ctx_->evp_.init_encrypt_ctr(key); + ctx_->evp_.init_iv(iv); +#else + if (AES_set_encrypt_key(key.ubegin(), 256, &ctx_->aes_key_) < 0) { + LOG(FATAL) << "Failed to set encrypt key"; + } + MutableSlice(ctx_->counter_, AES_BLOCK_SIZE).copy_from(iv); + ctx_->current_pos_ = 0; +#endif } void AesCtrState::encrypt(Slice from, MutableSlice to) { - ctx_->encrypt(from, to); + CHECK(from.size() <= to.size()); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L + ctx_->evp_.encrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size())); +#else + auto from_ptr = from.ubegin(); + auto to_ptr = to.ubegin(); + for (size_t i = 0; i < from.size(); i++) { + if (ctx_->current_pos_ == 0) { + AES_encrypt(ctx_->counter_, ctx_->encrypted_counter_, &ctx_->aes_key_); + for (int j = 15; j >= 0; j--) { + if (++ctx_->counter_[j] != 0) { + break; + } + } + } + to_ptr[i] = static_cast<uint8>(from_ptr[i] ^ ctx_->encrypted_counter_[ctx_->current_pos_]); + ctx_->current_pos_ = (ctx_->current_pos_ + 1) & 15; + } +#endif } void AesCtrState::decrypt(Slice from, MutableSlice to) { - encrypt(from, to); // it is the same as decrypt + encrypt(from, to); } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) +static void make_digest(Slice data, MutableSlice output, const EVP_MD *evp_md) { + static TD_THREAD_LOCAL EVP_MD_CTX *ctx; + if (unlikely(ctx == nullptr)) { + ctx = EVP_MD_CTX_new(); + LOG_IF(FATAL, ctx == nullptr); + detail::add_thread_local_destructor(create_destructor([] { + EVP_MD_CTX_free(ctx); + ctx = nullptr; + })); + } + int res = EVP_DigestInit_ex(ctx, evp_md, nullptr); + LOG_IF(FATAL, res != 1); + res = EVP_DigestUpdate(ctx, data.ubegin(), data.size()); + LOG_IF(FATAL, res != 1); + res = EVP_DigestFinal_ex(ctx, output.ubegin(), nullptr); + LOG_IF(FATAL, res != 1); + EVP_MD_CTX_reset(ctx); +} + +static void init_thread_local_evp_md(const EVP_MD *&evp_md, const char *algorithm) { + evp_md = EVP_MD_fetch(nullptr, algorithm, nullptr); + LOG_IF(FATAL, evp_md == nullptr); + detail::add_thread_local_destructor(create_destructor([&evp_md]() mutable { + EVP_MD_free(const_cast<EVP_MD *>(evp_md)); + evp_md = nullptr; + })); +} +#endif + void sha1(Slice data, unsigned char output[20]) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "sha1"); + } + make_digest(data, MutableSlice(output, 20), evp_md); +#else auto result = SHA1(data.ubegin(), data.size(), output); CHECK(result == output); +#endif } void sha256(Slice data, MutableSlice output) { CHECK(output.size() >= 32); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "sha256"); + } + make_digest(data, output, evp_md); +#else auto result = SHA256(data.ubegin(), data.size(), output.ubegin()); CHECK(result == output.ubegin()); +#endif +} + +void sha512(Slice data, MutableSlice output) { + CHECK(output.size() >= 64); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "sha512"); + } + make_digest(data, output, evp_md); +#else + auto result = SHA512(data.ubegin(), data.size(), output.ubegin()); + CHECK(result == output.ubegin()); +#endif } -struct Sha256StateImpl { - SHA256_CTX ctx; +string sha1(Slice data) { + string result(20, '\0'); + sha1(data, MutableSlice(result).ubegin()); + return result; +} + +string sha256(Slice data) { + string result(32, '\0'); + sha256(data, result); + return result; +} + +string sha512(Slice data) { + string result(64, '\0'); + sha512(data, result); + return result; +} + +class Sha256State::Impl { + public: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + EVP_MD_CTX *ctx_; + + Impl() { + ctx_ = EVP_MD_CTX_new(); + LOG_IF(FATAL, ctx_ == nullptr); + } + ~Impl() { + CHECK(ctx_ != nullptr); + EVP_MD_CTX_free(ctx_); + } +#else + SHA256_CTX ctx_; + Impl() = default; + ~Impl() = default; +#endif + + Impl(const Impl &from) = delete; + Impl &operator=(const Impl &from) = delete; + Impl(Impl &&from) = delete; + Impl &operator=(Impl &&from) = delete; }; Sha256State::Sha256State() = default; -Sha256State::Sha256State(Sha256State &&from) = default; -Sha256State &Sha256State::operator=(Sha256State &&from) = default; -Sha256State::~Sha256State() = default; -void sha256_init(Sha256State *state) { - state->impl = std::make_unique<Sha256StateImpl>(); - int err = SHA256_Init(&state->impl->ctx); +Sha256State::Sha256State(Sha256State &&other) noexcept { + impl_ = std::move(other.impl_); + is_inited_ = other.is_inited_; + other.is_inited_ = false; +} + +Sha256State &Sha256State::operator=(Sha256State &&other) noexcept { + Sha256State copy(std::move(other)); + using std::swap; + swap(impl_, copy.impl_); + swap(is_inited_, copy.is_inited_); + return *this; +} + +Sha256State::~Sha256State() { + if (is_inited_) { + char result[32]; + extract(MutableSlice{result, 32}); + CHECK(!is_inited_); + } +} + +void Sha256State::init() { + if (!impl_) { + impl_ = make_unique<Sha256State::Impl>(); + } + CHECK(!is_inited_); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "sha256"); + } + int err = EVP_DigestInit_ex(impl_->ctx_, evp_md, nullptr); +#else + int err = SHA256_Init(&impl_->ctx_); +#endif LOG_IF(FATAL, err != 1); + is_inited_ = true; } -void sha256_update(Slice data, Sha256State *state) { - CHECK(state->impl); - int err = SHA256_Update(&state->impl->ctx, data.ubegin(), data.size()); +void Sha256State::feed(Slice data) { + CHECK(impl_); + CHECK(is_inited_); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + int err = EVP_DigestUpdate(impl_->ctx_, data.ubegin(), data.size()); +#else + int err = SHA256_Update(&impl_->ctx_, data.ubegin(), data.size()); +#endif LOG_IF(FATAL, err != 1); } -void sha256_final(Sha256State *state, MutableSlice output) { +void Sha256State::extract(MutableSlice output, bool destroy) { CHECK(output.size() >= 32); - CHECK(state->impl); - int err = SHA256_Final(output.ubegin(), &state->impl->ctx); + CHECK(impl_); + CHECK(is_inited_); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + int err = EVP_DigestFinal_ex(impl_->ctx_, output.ubegin(), nullptr); +#else + int err = SHA256_Final(output.ubegin(), &impl_->ctx_); +#endif LOG_IF(FATAL, err != 1); - state->impl.reset(); + is_inited_ = false; + if (destroy) { + impl_.reset(); + } } -/*** md5 ***/ void md5(Slice input, MutableSlice output) { - CHECK(output.size() >= MD5_DIGEST_LENGTH); + CHECK(output.size() >= 16); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + static TD_THREAD_LOCAL const EVP_MD *evp_md; + if (unlikely(evp_md == nullptr)) { + init_thread_local_evp_md(evp_md, "md5"); + } + make_digest(input, output, evp_md); +#else auto result = MD5(input.ubegin(), input.size(), output.ubegin()); CHECK(result == output.ubegin()); +#endif } -void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice dest) { - CHECK(dest.size() == 256 / 8) << dest.size(); - CHECK(iteration_count > 0); - auto evp_md = EVP_sha256(); +static void pbkdf2_impl(Slice password, Slice salt, int iteration_count, MutableSlice dest, const EVP_MD *evp_md) { CHECK(evp_md != nullptr); + int hash_size = EVP_MD_size(evp_md); + CHECK(dest.size() == static_cast<size_t>(hash_size)); + CHECK(iteration_count > 0); #if OPENSSL_VERSION_NUMBER < 0x10000000L HMAC_CTX ctx; HMAC_CTX_init(&ctx); unsigned char counter[4] = {0, 0, 0, 1}; - int password_len = narrow_cast<int>(password.size()); + auto password_len = narrow_cast<int>(password.size()); HMAC_Init_ex(&ctx, password.data(), password_len, evp_md, nullptr); HMAC_Update(&ctx, salt.ubegin(), narrow_cast<int>(salt.size())); HMAC_Update(&ctx, counter, 4); @@ -393,14 +934,15 @@ void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice HMAC_CTX_cleanup(&ctx); if (iteration_count > 1) { - unsigned char buf[32]; + CHECK(hash_size <= 64); + unsigned char buf[64]; std::copy(dest.ubegin(), dest.uend(), buf); for (int iter = 1; iter < iteration_count; iter++) { - if (HMAC(evp_md, password.data(), password_len, buf, 32, buf, nullptr) == nullptr) { + if (HMAC(evp_md, password.data(), password_len, buf, hash_size, buf, nullptr) == nullptr) { LOG(FATAL) << "Failed to HMAC"; } - for (int i = 0; i < 32; i++) { - dest[i] ^= buf[i]; + for (int i = 0; i < hash_size; i++) { + dest[i] = static_cast<unsigned char>(dest[i] ^ buf[i]); } } } @@ -412,14 +954,179 @@ void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice #endif } -void hmac_sha256(Slice key, Slice message, MutableSlice dest) { - CHECK(dest.size() == 256 / 8); +void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice dest) { + pbkdf2_impl(password, salt, iteration_count, dest, EVP_sha256()); +} + +void pbkdf2_sha512(Slice password, Slice salt, int iteration_count, MutableSlice dest) { + pbkdf2_impl(password, salt, iteration_count, dest, EVP_sha512()); +} + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) +static void hmac_impl(const char *digest, Slice key, Slice message, MutableSlice dest) { + EVP_MAC *hmac = EVP_MAC_fetch(nullptr, "HMAC", nullptr); + LOG_IF(FATAL, hmac == nullptr); + + EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(hmac); + LOG_IF(FATAL, ctx == nullptr); + + OSSL_PARAM params[2]; + params[0] = OSSL_PARAM_construct_utf8_string(OSSL_MAC_PARAM_DIGEST, const_cast<char *>(digest), 0); + params[1] = OSSL_PARAM_construct_end(); + + int res = EVP_MAC_init(ctx, const_cast<unsigned char *>(key.ubegin()), key.size(), params); + LOG_IF(FATAL, res != 1); + res = EVP_MAC_update(ctx, message.ubegin(), message.size()); + LOG_IF(FATAL, res != 1); + res = EVP_MAC_final(ctx, dest.ubegin(), nullptr, dest.size()); + LOG_IF(FATAL, res != 1); + + EVP_MAC_CTX_free(ctx); + EVP_MAC_free(hmac); +} +#else +static void hmac_impl(const EVP_MD *evp_md, Slice key, Slice message, MutableSlice dest) { unsigned int len = 0; - auto result = HMAC(EVP_sha256(), key.ubegin(), narrow_cast<int>(key.size()), message.ubegin(), + auto result = HMAC(evp_md, key.ubegin(), narrow_cast<int>(key.size()), message.ubegin(), narrow_cast<int>(message.size()), dest.ubegin(), &len); CHECK(result == dest.ubegin()); CHECK(len == dest.size()); } +#endif + +void hmac_sha256(Slice key, Slice message, MutableSlice dest) { + CHECK(dest.size() == 256 / 8); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + hmac_impl("SHA256", key, message, dest); +#else + hmac_impl(EVP_sha256(), key, message, dest); +#endif +} + +void hmac_sha512(Slice key, Slice message, MutableSlice dest) { + CHECK(dest.size() == 512 / 8); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER) + hmac_impl("SHA512", key, message, dest); +#else + hmac_impl(EVP_sha512(), key, message, dest); +#endif +} + +static int get_evp_pkey_type(EVP_PKEY *pkey) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L + return EVP_PKEY_type(pkey->type); +#else + return EVP_PKEY_base_id(pkey); +#endif +} + +Result<BufferSlice> rsa_encrypt_pkcs1_oaep(Slice public_key, Slice data) { + BIO *mem_bio = BIO_new_mem_buf(const_cast<void *>(static_cast<const void *>(public_key.data())), + narrow_cast<int>(public_key.size())); + SCOPE_EXIT { + BIO_vfree(mem_bio); + }; + + EVP_PKEY *pkey = PEM_read_bio_PUBKEY(mem_bio, nullptr, nullptr, nullptr); + if (!pkey) { + return Status::Error("Cannot read public key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey); + }; + if (get_evp_pkey_type(pkey) != EVP_PKEY_RSA) { + return Status::Error("Wrong key type, expected RSA"); + } + +#if OPENSSL_VERSION_NUMBER < 0x10000000L + RSA *rsa = pkey->pkey.rsa; + int outlen = RSA_size(rsa); + BufferSlice res(outlen); + if (RSA_public_encrypt(narrow_cast<int>(data.size()), const_cast<unsigned char *>(data.ubegin()), + res.as_slice().ubegin(), rsa, RSA_PKCS1_OAEP_PADDING) != outlen) { +#else + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (!ctx) { + return Status::Error("Cannot create EVP_PKEY_CTX"); + } + SCOPE_EXIT { + EVP_PKEY_CTX_free(ctx); + }; + + if (EVP_PKEY_encrypt_init(ctx) <= 0) { + return Status::Error("Cannot init EVP_PKEY_CTX"); + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) { + return Status::Error("Cannot set RSA_PKCS1_OAEP padding in EVP_PKEY_CTX"); + } + + size_t outlen; + if (EVP_PKEY_encrypt(ctx, nullptr, &outlen, data.ubegin(), data.size()) <= 0) { + return Status::Error("Cannot calculate encrypted length"); + } + BufferSlice res(outlen); + if (EVP_PKEY_encrypt(ctx, res.as_slice().ubegin(), &outlen, data.ubegin(), data.size()) <= 0) { +#endif + return Status::Error("Cannot encrypt"); + } + return std::move(res); +} + +Result<BufferSlice> rsa_decrypt_pkcs1_oaep(Slice private_key, Slice data) { + BIO *mem_bio = BIO_new_mem_buf(const_cast<void *>(static_cast<const void *>(private_key.data())), + narrow_cast<int>(private_key.size())); + SCOPE_EXIT { + BIO_vfree(mem_bio); + }; + + EVP_PKEY *pkey = PEM_read_bio_PrivateKey(mem_bio, nullptr, nullptr, nullptr); + if (!pkey) { + return Status::Error("Cannot read private key"); + } + SCOPE_EXIT { + EVP_PKEY_free(pkey); + }; + if (get_evp_pkey_type(pkey) != EVP_PKEY_RSA) { + return Status::Error("Wrong key type, expected RSA"); + } + +#if OPENSSL_VERSION_NUMBER < 0x10000000L + RSA *rsa = pkey->pkey.rsa; + size_t outlen = RSA_size(rsa); + BufferSlice res(outlen); + auto inlen = RSA_private_decrypt(narrow_cast<int>(data.size()), const_cast<unsigned char *>(data.ubegin()), + res.as_slice().ubegin(), rsa, RSA_PKCS1_OAEP_PADDING); + if (inlen == -1) { + return Status::Error("Cannot decrypt"); + } + res.truncate(inlen); +#else + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, nullptr); + if (!ctx) { + return Status::Error("Cannot create EVP_PKEY_CTX"); + } + SCOPE_EXIT { + EVP_PKEY_CTX_free(ctx); + }; + + if (EVP_PKEY_decrypt_init(ctx) <= 0) { + return Status::Error("Cannot init EVP_PKEY_CTX"); + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) { + return Status::Error("Cannot set RSA_PKCS1_OAEP padding in EVP_PKEY_CTX"); + } + + size_t outlen; + if (EVP_PKEY_decrypt(ctx, nullptr, &outlen, data.ubegin(), data.size()) <= 0) { + return Status::Error("Cannot calculate decrypted length"); + } + BufferSlice res(outlen); + if (EVP_PKEY_decrypt(ctx, res.as_slice().ubegin(), &outlen, data.ubegin(), data.size()) <= 0) { + return Status::Error("Cannot decrypt"); + } +#endif + return std::move(res); +} #if OPENSSL_VERSION_NUMBER < 0x10100000L namespace { @@ -456,6 +1163,8 @@ void openssl_locking_function(int mode, int n, const char *file, int line) { void init_openssl_threads() { #if OPENSSL_VERSION_NUMBER < 0x10100000L + static std::mutex init_mutex; + std::lock_guard<std::mutex> lock(init_mutex); if (CRYPTO_get_locking_callback() == nullptr) { #if OPENSSL_VERSION_NUMBER >= 0x10000000L CRYPTO_THREADID_set_callback(openssl_threadid_callback); @@ -464,6 +1173,38 @@ void init_openssl_threads() { } #endif } + +Status create_openssl_error(int code, Slice message) { + const int max_result_size = 1 << 12; + auto result = StackAllocator::alloc(max_result_size); + StringBuilder sb(result.as_slice()); + + sb << message; + while (unsigned long error_code = ERR_get_error()) { + char error_buf[1024]; + ERR_error_string_n(error_code, error_buf, sizeof(error_buf)); + Slice error(error_buf, std::strlen(error_buf)); + sb << "{" << error << "}"; + } + LOG_IF(ERROR, sb.is_error()) << "OpenSSL error buffer overflow"; + LOG(DEBUG) << sb.as_cslice(); + return Status::Error(code, sb.as_cslice()); +} + +void clear_openssl_errors(Slice source) { + if (ERR_peek_error() != 0) { + auto error = create_openssl_error(0, "Unprocessed OPENSSL_ERROR"); + if (!ends_with(error.message(), ":def_load:system lib}")) { + LOG(ERROR) << source << ": " << error; + } + } +#if TD_PORT_WINDOWS + WSASetLastError(0); +#else + errno = 0; +#endif +} + #endif #if TD_HAVE_ZLIB @@ -472,6 +1213,68 @@ uint32 crc32(Slice data) { } #endif +#if TD_HAVE_CRC32C +uint32 crc32c(Slice data) { + return crc32c::Crc32c(data.data(), data.size()); +} + +uint32 crc32c_extend(uint32 old_crc, Slice data) { + return crc32c::Extend(old_crc, data.ubegin(), data.size()); +} + +namespace { + +uint32 gf32_matrix_times(const uint32 *matrix, uint32 vector) { + uint32 sum = 0; + while (vector) { + if (vector & 1) { + sum ^= *matrix; + } + vector >>= 1; + matrix++; + } + return sum; +} + +void gf32_matrix_square(uint32 *square, const uint32 *matrix) { + for (int n = 0; n < 32; n++) { + square[n] = gf32_matrix_times(matrix, matrix[n]); + } +} + +} // namespace + +uint32 crc32c_extend(uint32 old_crc, uint32 data_crc, size_t data_size) { + static uint32 power_buf_raw[1024]; + static const uint32 *power_buf = [&] { + auto *buf = power_buf_raw; + buf[0] = 0x82F63B78u; + for (int n = 0; n < 31; n++) { + buf[n + 1] = 1u << n; + } + for (int n = 1; n < 32; n++) { + gf32_matrix_square(buf + (n << 5), buf + ((n - 1) << 5)); + } + return buf; + }(); + + if (data_size == 0) { + return old_crc; + } + + const uint32 *p = power_buf + 64; + do { + p += 32; + if (data_size & 1) { + old_crc = gf32_matrix_times(p, old_crc); + } + data_size >>= 1; + } while (data_size != 0); + return old_crc ^ data_crc; +} + +#endif + static const uint64 crc64_table[256] = { 0x0000000000000000, 0xb32e4cbe03a75f6f, 0xf4843657a840a05b, 0x47aa7ae9abe7ff34, 0x7bd0c384ff8f5e33, 0xc8fe8f3afc28015c, 0x8f54f5d357cffe68, 0x3c7ab96d5468a107, 0xf7a18709ff1ebc66, 0x448fcbb7fcb9e309, @@ -538,4 +1341,34 @@ uint64 crc64(Slice data) { return crc64_partial(data, static_cast<uint64>(-1)) ^ static_cast<uint64>(-1); } +static const uint16 crc16_table[256] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, + 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, + 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, + 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, + 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, + 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, + 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, + 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, + 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, + 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, + 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, + 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, + 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, + 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, + 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0}; + +uint16 crc16(Slice data) { + uint32 crc = 0; + for (auto c : data) { + auto t = (static_cast<unsigned char>(c) ^ (crc >> 8)) & 0xff; + crc = crc16_table[t] ^ (crc << 8); + } + return static_cast<uint16>(crc); +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h index 23ac694bfb..d4c2558035 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/crypto.h @@ -1,13 +1,16 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once +#include "td/utils/buffer.h" #include "td/utils/common.h" +#include "td/utils/SharedSlice.h" #include "td/utils/Slice.h" +#include "td/utils/Status.h" namespace td { @@ -18,62 +21,164 @@ void init_crypto(); int pq_factorize(Slice pq_str, string *p_str, string *q_str); -void aes_ige_encrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to); -void aes_ige_decrypt(const UInt256 &aes_key, UInt256 *aes_iv, Slice from, MutableSlice to); +class AesState { + public: + AesState(); + AesState(const AesState &from) = delete; + AesState &operator=(const AesState &from) = delete; + AesState(AesState &&from) noexcept; + AesState &operator=(AesState &&from) noexcept; + ~AesState(); + + void init(Slice key, bool encrypt); + + void encrypt(const uint8 *src, uint8 *dst, int size); + + void decrypt(const uint8 *src, uint8 *dst, int size); + + private: + struct Impl; + unique_ptr<Impl> impl_; +}; + +void aes_ige_encrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to); +void aes_ige_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to); + +class AesIgeStateImpl; + +class AesIgeState { + public: + AesIgeState(); + AesIgeState(const AesIgeState &from) = delete; + AesIgeState &operator=(const AesIgeState &from) = delete; + AesIgeState(AesIgeState &&from) noexcept; + AesIgeState &operator=(AesIgeState &&from) noexcept; + ~AesIgeState(); + + void init(Slice key, Slice iv, bool encrypt); -void aes_cbc_encrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to); -void aes_cbc_decrypt(const UInt256 &aes_key, UInt128 *aes_iv, Slice from, MutableSlice to); + void encrypt(Slice from, MutableSlice to); + + void decrypt(Slice from, MutableSlice to); + + private: + unique_ptr<AesIgeStateImpl> impl_; +}; + +void aes_cbc_encrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to); +void aes_cbc_decrypt(Slice aes_key, MutableSlice aes_iv, Slice from, MutableSlice to); class AesCtrState { public: AesCtrState(); AesCtrState(const AesCtrState &from) = delete; AesCtrState &operator=(const AesCtrState &from) = delete; - AesCtrState(AesCtrState &&from); - AesCtrState &operator=(AesCtrState &&from); + AesCtrState(AesCtrState &&from) noexcept; + AesCtrState &operator=(AesCtrState &&from) noexcept; ~AesCtrState(); - void init(const UInt256 &key, const UInt128 &iv); + void init(Slice key, Slice iv); void encrypt(Slice from, MutableSlice to); void decrypt(Slice from, MutableSlice to); private: - class Impl; - std::unique_ptr<Impl> ctx_; + struct Impl; + unique_ptr<Impl> ctx_; +}; + +class AesCbcState { + public: + AesCbcState(Slice key256, Slice iv128); + AesCbcState(const AesCbcState &from) = delete; + AesCbcState &operator=(const AesCbcState &from) = delete; + AesCbcState(AesCbcState &&from) noexcept; + AesCbcState &operator=(AesCbcState &&from) noexcept; + ~AesCbcState(); + + void encrypt(Slice from, MutableSlice to); + void decrypt(Slice from, MutableSlice to); + + struct Raw { + SecureString key; + SecureString iv; + }; + const Raw &raw() const { + return raw_; + } + + private: + struct Impl; + unique_ptr<Impl> ctx_; + + Raw raw_; + bool is_encrypt_ = false; }; void sha1(Slice data, unsigned char output[20]); void sha256(Slice data, MutableSlice output); -struct Sha256StateImpl; +void sha512(Slice data, MutableSlice output); -struct Sha256State { +string sha1(Slice data) TD_WARN_UNUSED_RESULT; + +string sha256(Slice data) TD_WARN_UNUSED_RESULT; + +string sha512(Slice data) TD_WARN_UNUSED_RESULT; + +class Sha256State { + public: Sha256State(); - Sha256State(Sha256State &&from); - Sha256State &operator=(Sha256State &&from); + Sha256State(const Sha256State &other) = delete; + Sha256State &operator=(const Sha256State &other) = delete; + Sha256State(Sha256State &&other) noexcept; + Sha256State &operator=(Sha256State &&other) noexcept; ~Sha256State(); - std::unique_ptr<Sha256StateImpl> impl; -}; -void sha256_init(Sha256State *state); -void sha256_update(Slice data, Sha256State *state); -void sha256_final(Sha256State *state, MutableSlice output); + void init(); + + void feed(Slice data); + + void extract(MutableSlice output, bool destroy = false); + + private: + class Impl; + unique_ptr<Impl> impl_; + bool is_inited_ = false; +}; void md5(Slice input, MutableSlice output); void pbkdf2_sha256(Slice password, Slice salt, int iteration_count, MutableSlice dest); +void pbkdf2_sha512(Slice password, Slice salt, int iteration_count, MutableSlice dest); + void hmac_sha256(Slice key, Slice message, MutableSlice dest); +void hmac_sha512(Slice key, Slice message, MutableSlice dest); + +// Interface may be improved +Result<BufferSlice> rsa_encrypt_pkcs1_oaep(Slice public_key, Slice data); +Result<BufferSlice> rsa_decrypt_pkcs1_oaep(Slice private_key, Slice data); void init_openssl_threads(); + +Status create_openssl_error(int code, Slice message); + +void clear_openssl_errors(Slice source); #endif #if TD_HAVE_ZLIB uint32 crc32(Slice data); #endif +#if TD_HAVE_CRC32C +uint32 crc32c(Slice data); +uint32 crc32c_extend(uint32 old_crc, Slice data); +uint32 crc32c_extend(uint32 old_crc, uint32 new_crc, size_t data_size); +#endif + uint64 crc64(Slice data); +uint16 crc16(Slice data); } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.cpp new file mode 100644 index 0000000000..c5f47d8ac9 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.cpp @@ -0,0 +1,302 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/emoji.h" + +#include "td/utils/base64.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/Gzip.h" + +namespace td { + +bool is_emoji(Slice str) { + constexpr size_t MAX_EMOJI_LENGTH = 35; + static const FlatHashSet<Slice, SliceHash> emojis = [max_emoji_length = MAX_EMOJI_LENGTH] { +#if TD_HAVE_ZLIB + Slice packed_emojis( + "eJyNnety27i2rV8lVfvf-XXul7frVmfZ6cS6kJJskrLj7tUUaYmOI1uObcUEWLUfJecBdledFzgaJIfJiUkyqyqAJ4wxJzE_QABF2fGPs-" + "jdj7PVux_TzbFsjyU7lttj2R3L_t2PC3Ms9t2PYHEsy3c_wg_H8vuxfDyWT8dydizjY5kcy_RYZsfiHYt_LPNjKd79iI5foz-P5Rg_" + "OsaPjjGjY8zV-2P5x7EcfVfH-Ktj_-rhWI7XXX07lu_HctReHjWXx-tdHq93efPux9XxmldHn6tj3Kuj39VxXFcX7358Pn79fH4ssI_j_" + "3y89u0xt9vLdz--HMf1Zfnu7-tf3h-r36bH6mSCykPlo5qjWqCC7uQc1QWqAFWIKjpWH35FVVpfUT2g2qN6RPUN1ROqF1QHVN-P1e8zVLja2S-" + "oEOVshOo3VBjV2T9QnaA6RfUB1e-oPqL6hOoM1RgVRn-GPM4Q-ayMjDzOkMcZ8jhDHmfI4wx5nCGPM-RxhtEfZ_" + "1YXaK6QvUZ1TWqP1B9QXWHClme7VDdo0K-Z8j3DPmeId-zZ1TI9wz5niHfs1dUOSqDyqIqjtUY6Y-R_hjpj5H-GOmPkf4Y6Y-R_hjpj5H-" + "GOmPkf4Y6Y-R_hjpj5H-GOmPkf4Y6Y-R_hjpj5H-GOmPkf4Y6Y-R_hjpj5H-GOmPkf4Y6Y-R_hjpj_" + "9E9U9Uf6GKUa1RJahSVDeoNqi2qDJUt6iAbgx0Y6AbA90Y6MZANwa6MdCNgW6MpTIGvzH4jcFvDH5j8BuD3xjoxkA3AboJ0E2AbgJ0E6CbAN0E" + "6CZANwG6CdBNgG4CdBOgmwDdBOgmQDcBugnQTYBuAnQTpD9B-hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_QfoTpD9B-" + "hOkP0H6E6Q_QfoTpD9B-hOkP0H6E6Q_wfKZgMEEDKZgMAWDKRhMwWAKBlMwmILBFAymYDAFgykYTJHvFPlOke8U-U6R7xT5TpHvFPlOke8U-" + "U6R7xT5TpHvFPlOke8U-U6R7xT5TpHvFPlOke8U-" + "U6R4BQZTZHRFBlNkdEUGU2R0RQZTZHRDBnNkNEMGc2Q0QwZzZDRDBnNkNEMGc2Q0QwZzTCrM8zqDLM6w6zOkOUMWc6Q5QxZzpDlDC-" + "IGV4QM7wgZnhBzPCCmOEFMcMLYoYXxAwviBleEDO8IGZ4QcxAaAZCMxCagdAMhGYgNAOhGQjNQGgGQjMQmoHQDIRmIDQDoRkIzUBohhUxw4qYY" + "UXMAGyGFTHDiphhRczAbwZ-M_Cbgd8M_GbgNwM_D-" + "g8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8" + "UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9Q8UPNAzQM1D9S8khpWnQ90PladD34--Png54OfD34--" + "Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--" + "Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--Png54OfD34--M3Bbw5-c_Cbg98c_ObgNwe_" + "OfjNwW8OfnPwm4PfHPzm4DcHvzn4zcFvDn5z8JuD3xz85uA3B785-M3Bbw5-c_Cbg98c_ObgNwe_" + "OfjNwW8OfnPwm4PfHPzm4DcHvzn4zcFvDn5z8JuD3xz85uA3B785-M3Bbw5-c_Cbg98c_ObgNwe_" + "OfjNwW8OfnPwm4PfHPzmoLYAtQWoLUBtAWoLUFuA2gLUFqC2ALUFqC1AbQFqC1BbgNoC1BagtgC1BagtQG0BagtQW4DaAtQWoLYAtQWoLUBtAW" + "oLUFuA2gLUFqC2ALUFqC1AbQFqC1BbgNoC1BagtgC1BagtQG0BagtQW4DaAtQWoLYAtQWoLUBtAWoLUFuA2gLUFqC2ALUFqC1AbQFqC1BbYNUt" + "kf4S6S-R_hLpL5H5EpkvkfkSmS-R-RKZL5H5EpkvkfkSmS-R-RKZL5H5EpkvkfkSmS-R-RKZL5H5EpkvkfkSmS-R-RKZLzG-c1zjHNc4h-" + "QCw73AcC8w3Au8SC4w3QGmO8B0B5juANMdYLoDTHeA6Q4w3QGmO8B0B5juAPkGyDdAvgHyDTDdAZIOkHSApAMkHSDpAAMKMKAASQdIOkDSAZIO" + "kHSApAMkHSDpAEkHSDpA0gGSDpB0gIwCJB0g6QBJB5juANMdYLoDTHeA6Q4w3QGmO8B0B5juANMdYLoDTHeA6Q4w3QGmO8B0B5juANMdAGcAfg" + "H4BeAXgF8AfiH4heAXgl8IfiH4heAXgl8IfiH4heAXgl8IfiH4heAXgl8IfhHiRYgXIV6EeBHiRYgXIV6EeBHiRYgXIV6EeBHiRYgXIV5UxsN8" + "RJiPCPMRYT4izEeE-YgwHxHmI8J8RJiPCPMRYT4izEeE-YgwHxHmI8J8RJiPCPMRYT4izEeE-YgwHxHmI8J8RJiPCPMRYT4izEeE-" + "YgwHxHmI8J8RJiPCPMRYT4izEeE-YgwHxHmI8J8RJiPCPMRYT4izEeE-YgwHxHmYwV-K_Bbgd8K_FbgtwK_" + "FQitgGQFJCsgWQHECiBWALFC0iskvULSKyS9wuhXGP0KQ1thaCsMbYWhrTC0FYa2wtBWGNoKQ7sGq2uwugara7C6BqtrsLoGq2uwugara7C6Bq" + "trXO0aXGIMN8aExpjQGBMaY_" + "QxRh9j9DEmNMaExsgjRh4x8ogxoTEmNMaExpjQGLnFyC1GbjFyizHIGIOMMcgYg4wxyBiDjDHIGIOMMcgYg4wxyBiDjIEkxoTGmNAYExqXA8eE" + "xpjQGBMag1oMajGoxaAWg1oMajGoxQAWYy5jzGWMuVxjLteYyzXmco25XGMu15jLNV4Ga7wM1ngZrPEyWONlsAa1NaitQW0NamtQW4PaGtTWoL" + "YGtTWorUFtDWprUFuD2hrU1qC2BrU1qK1BbQ1qa1Bbg9oa1Nagtga1NaitQW0NamtQW4PaGtTWoLYGtTWorUFtDWprUFuD2hrU1qC2BrU1qK1B" + "bQ1qa1Bbg9oa1NZYa2ugWwPdGujWQJcAXQJ0CdAlQJcAXQJ0CbaRBPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_" + "BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_BPwS8EvALwG_" + "BPwS8EvALwG_BPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_" + "FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_FPxS8EvBLwW_" + "FPw2iLdBvA3ibRBvg3gbxNsg3gbxNoi3QbwN4m0Qb4N4W0zAFhOwxQRsMQFbTMAWE7DFBGwxAVtMwBZ0t6C7Bd0t6G5Bdwu6W9Ddgu4WdLeguw" + "XdLehuQXcLulvQ3YLuFnS3oLsF3S3obkF3C7pb0N2C7hZ0t6C7Bd0t6G5Bdwu6W9Ddgu4WNLagsQWNLWhsQWMLGlvQ2ILGFjS2oLEFjS1obEsa" + "oLsF2AxcMnDJwCUDlwxcMnDJsOAyLLgMSDIgyYAkA5IMSDIgyYAkA5IMSDIgyYAkA5IM6WdIP0P6GdLPkH6G9DOknyH9DOlnyC1Dbhlyy5Bbht" + "wy5JYhtwy5ZS_v_n1z_PcfL9N3_353_Afjxy85Smn--jtKab7_" + "J0plHlBK82SBUplLlMo8R6nMC5TKDFAqM0SpzA1KZW5RSvPsBqU0p2WpzC8olXmHUplfUSrzHqUyH1Aq8wWlMg8olfkdpTSP70WPpTTPtyiVma" + "FU5hNKaV78glKZryiVmaOU5vHu_Vgq81eUyhyhVOZvKJX5HqUyJyiV6aFUZoBSmVcolfkHSmX-E6Uy_" + "0KpzASlMrcolXmHUplfUSrzBaUyDyiV-R2lNI-308dSmSOUyrxGqcw_" + "UCrzL5TKXKNUZoJSmTcolfmKUpkWpTSPt8THUpkLlMpcolTmOUplXqBUZohSmSuUyrxEqcw_" + "UCozRanMHUpl3qOU5uoDSmWWpTI9lMqco1TmBqUydyiVeY9SmY8olfmMUpkvKJV5QCnNyxFKZX5AqczfUSrzDKUyxyiVWZbK9FEqc4FSmecolX" + "mFUpl_olTmHqUyH1FK8-o9SmWeolTmXyiVGaOU5uc_UUrzeGt5LJX5DaU0b_-BUpknKJV5igLz__" + "6yQ6lMg1KaHy9QKjNEgfn39T92ZVU37suqbtiyqhtFWVWND6OyqhvPZVU1zv4sq7oRl1XdWJdV3UjKqm6kZVU3bsqqbmzKqm5sy6puZGVVN27L" + "qm48lVXVGJuyqhqT87KqGxdlVTfCsqobUVnVjVVZ1Y3PZVU3rsuqakw_" + "lVXdOCurujEuq7oxKau6sSirurEsq7pxXlZ146Ks6kZQVnUjLKu6EZVV3ViVVd24LKu6cVVWdeNzWdWN67KqG_uyqhvfyqpuPJdV1ZiVnx_" + "Ui8L7tayqxtyUVdVY_" + "l5WdeNjWdWNr2VVN3ZlVTf2ZVU3Hsuqbnwrq7rxVFZ147ms6sZLWdWNQ1lVjfPTsqobH8uqbnwqq7pxVlZ1Y1xWdWNWVnVjXVZ146as6sZ9WdW" + "Nh7KqG3lZVY2LUVnVjd_Kqm68L6u64ZVV3fDLqm7My6puXJZV3bgqq7rxuazqxp9lVTf-" + "Kqu6cVNWdeNrWdWNfVnVje9lVTVWn8qqbozLqm5MyqpuVFXd-KOs6safZVU3_llWdeOvsqobcVnVjXVZ1Y1NWdWNXVnVjX1ZofFvP377692_" + "HS18_U9lVTf-c1nVjf9SVnXjv5ZV3fhvZVU3_ntZ1Y3_UVZ143-WVd34X2VVN_53WdWN_" + "1NWdePv69PkWG5obGhsaWQ0bmnc0bin8UDjkcYTjWcaLzQONL7TyGkYGniBn6Zo0EhpbGhsaWQ0bml8oXFH4yuNexoPNPY0Hmk80Xim8ULjQOO" + "VRk7D0igHf8PB3xDvDQd_wzHfcMw3HPMNxwxjR-OexgONPY1HGt9oPNP4TuOVRk7D0LA0yjFvSHXDgW2IbsPxbDiMDa--ofuWKW-" + "Z8pYBtwy4ZaZbDnVLvFvi3XLwGWlkHEbGYWQcRsZhZAx4y2Hcctnckvwtx3PLKbjlwG45sFte9Jbkb3mtW5K_" + "JfBbrpbb9tVfaBxofKeR0yjJf2E6X3iJL7zEFwb8wjhfGOeOeO-Y1x3zuuOY7xjwjgHvCOqOY77jJe445jte6ysDfmWcr3T_" + "ytx31OzIcEeGOzLc0X3HYeza7s80chqWRrmi7jmV95zKe-Z-z0vck-E9A94znXumc090MF5plNd64CUeGPmBVB-Y4ANXywMzfWCmD7z6A8k_" + "MOUHpvxAdA_M_YFT8MAxP3DMDxzzA8f8wDE_EBQMQ-Mti5LYnunsmc6eWeyZxZ5Z7Mlwz8HvOdQ9h7rnCPccz57XemSm33jRb7zWN17rG6_" + "1jcS-kdg3XvRbO86exjONFxoHGjmNMvcnXv2ZV39mFs90f-bgn-n-" + "Qi8YKY0bGhsaWxq3NL7QuKPxlcaOxj2NBxp7Go80nmm80DjQeKVhaFgaJfkDB3_gmA8c84HkDxzzgWM-" + "cKgHDvXAoR441AOHeuBQDxzqgSM8cIQHwjxwYN85sO-8-ndG_s7I35nyd-b1ne6vdH9lXq-" + "cglcGfCX5VwZ85eTmzD3nJQwztYxjmUXBaxUcYVGnM338f3_8mh7LP49lfSx3dbtwO_fH8texPHZ1Ph_LbS0q8Oin_GGo6mtefzX1V1t_" + "LfAkodKVX_P6q6m_2vprgR8NrXTl17z-auqvtv4K3ada96nWfap1n2rdp1p3VuvOat1ZrTurdWe1blzrxrVuXOvGtW5c_4DX5B_1T3-" + "VRk7D0LA0SnIj_qjYiD8uNuKPjI34Y2Mjin-j-DeKf6P4N4p_o_g9xe8pfk_xe4rfU3xK8SnFpxSfUnxK8UeKP1L8keKPFH-k-BPFnyj-" + "RPEnij9RfEbxGcVnFJ9RfFaLPaLziM4jOo_oPKLziM4jOo_oPKLziM47ofiE4hOKTyg-oZjoPKLziM4jOo_ovA8Uf6D4A8UfKP5A8e8U_" + "07x7xT_TvHvFHNSPE6Kx0nxOCkeJ8XjpHicFI-" + "T4nFSPE6Kx0nxOCkeJ8XjpHhvkzKmeEzxmOIxxXyleBOKJxRPKJ5QPKF4SvGU4inFU4qnFM8onlE8o3hG8YzihOKE4oTihOKE4pTilOKU4pTil" + "OIbim8ovqH4huIbijcUbyjeULyheENxRnFGcUZxRnFG8S3FtxTfUnxL8S3FXyj-QvEXir9Q_IXiO4rvKL6j-" + "I7iO4p3FO8o3lG8o3hH8T3F9xTfU3xP8T3FDxQ_UPxA8QPFDxTvKd5TvKd4T_Ge4keKHyl-pPiR4keKv1H8jeJvFH-j-BvFTxQ_" + "UfxE8RPFTxQ_U_xM8TPFzxQ_U_xC8QvFLxS_UPxCcU5xTnFOcU5xXov9X2txaeQ0DA1LoxRzf_a5P_vcn33uzz73Z5_7s8_92ef-7HN_9rk_-" + "zxhfZ6wPk9YnyeszxPW52buczP3uZn73Mx9buY-N3Ofm7nPzdznZu5zM_e5I_" + "nckXzuSD53JJ87ku9R7FHsUexR7FG8pXhL8ZbiLcXbWrzkEl1yiS65RJdcoksu0SWX6JJLdMkluuQSXXKJLr9T_J3i7xR_p_" + "h7LT6f1eLSyGkYGpZGKV5SvKR4SfGS4iXF5xSfU3xO8TnF57U45NoIuTZCro2QayPk2gi5NkKujZBrI-" + "TaCLk2Qq6NkGsj5NoIuTZCro2QJ2zIEzbkCRvyhA15woY8YUOesCFP2JAnbMgTNuQJG_KEDXnChjxhQ56wIU_" + "YkCdsyBM25Akb8oQNuZ5DrueQ6znkeg65nqO_" + "anFp5DQMDUujFHOJRlyiEZdoxCUacYlGXKIRl2jEJRpxiUZcohF30Yi7aMRdNOIuGnEXXf1Si0sjp2FoWBqlmJOy4qSsOCkrTsqKkxJTHFMcUx" + "xTHL-" + "JyTkm55icY3KOyTkOKA4oDigOKA4oDikOKQ4pDikOKY4ojiiOKI4ojiheUbyieEXxiuIVxZcUX1J8SfElxZcUX1F8RfEVxVcUX1H8meLPFH-m-" + "DPFnym-pvia4muKrym-ppg3gTFvAmPeBMa8CYx5ExjvKN5RvKN4R_GOYt7JxLyTiXknE_" + "NOJuadTMw7mZh3MjHvZGLeycS8k4l5JxPzTibmnUzMO5mYdzIxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4MxX4Mx" + "72Ri3snEvJOJeScT804m5p1MzDuZmHcyMe9kYt7JxAeKDxQfKD5QfKDYUGwoNhQbig3FlmJLsaXYUmxr8ZoJrpngmgmumeCaCSbknJBzQs4JOS" + "fknJBzQs4JOSfknJBzQnQJ0SVElxBdQnQJ0SVElxBdQnQJ0SWvFL9S_" + "ErxK8WvtTjlaZXytEp5WqU8rVKeVilPq5SnVcrTKuVplfK0SrmLptxFU-6iKXfRlLtoyruvlHdfKe--" + "Ut59pbz7Sn2KfYp9in2KfYrnFM8pnlM8p3hO8YLiBcULihcULyjmPVLKe6SU90gp75FS3iOlvEdKeY-U8h4p5T1Synuk9ILiC4ovKL6g-" + "IJiHkApD6CUB1DKAyjlAZTyAEp5AKU8gFIeQCkPoJQHUMoDKOUBlPIASnkApTyAUh5AKQ-" + "glAdQygMo5QGU8gBKeQClPIBSHkApD6CUB1DKAyjlAZTyAMr4PiXj-5SM71Myvk_J-D4l4yO4jI_gMj6Cy_" + "gILuMjuIw3rhlvXDPeuGa8cc1445rtKN5RvKN4R_GOYh5AGQ-" + "gjAdQxgMo4wGU8QDKeABlPIAyHkAZD6CMB1DGAyjjAZTxAMp4AGU8gDIeQBkPoIwHUMYDKOPGmHFjzLgxZtwYM26MGTfGjBtjxo0x48aYcWPMu" + "D9n3J8z7s8Z9-eM-3PGXTTjLppxF824i2Zvb6Vvfvwy_nEVH-u_61_0rL-DHzpQ3yy_43_qV6u-v-tfJKWtv5W27Y5ureqI2x--4yr9F-u_" + "5kaLN73X3PT6d1xz03FNzKCYmL9bz-" + "Re3Qnq6Gx68iE3t7PpMUNubmfTY4fc3M6mpxhyczvlQhsgM6RRgj5OQxol6KM2pFGCPoZDGiXoI9qrwTuWnkxll-nvsv1db9fKe9d1PrSulVs-" + "5OZ2doy-y83t7Misy83t7M-6e1233TrXrOM9pOlfswNBOnF1rtmBIJ3wOtfsQJBOlJ1rdiCI0Mhl-NrfZfq7bH_X27VM77o2Q-taueVDbm5nx-" + "i73NzOjsy63NzO_qy713XbrXPNOt5Dmv41OxCkE1fnmh0I0gmvc80OBOlE2blmB4IITc-61l15f5ft73q7lu1d13ZoXSu3fMjN7eyg3-" + "XmdnZk1uXmdvZn3b2u226da9bxHtL0r9mBIJ24OtfsQJBOeJ1rdiBIJ8rONTsQRGh61rXuyvu7TH_" + "X27WK3nVdDK1r5ZYPubmdHUPscnM7O3h3ubmd_Vl3r-u2W-eadbyHNP1rdiBIJ67ONTsQpBNe55odCNKJsnPNDgQRmp51rbvy_" + "i7T31X9pIWY99Z35Jt5Idt0yTauTLyv3_Sk3OfUdQnV93f9rrjjXXX77XTH--iON9D1tzri9ofvuEr_xbqu-Xffm_" + "ZWj95UOt3yITe301kffW5up7t2etzcTmd597m5nTW2ISRdnU1PL5KuzqanF0lXZ9PTi6Srs-npRdLV2b2HdngPabr30J8E6cSl9tCfBOmEp_" + "bQnwTpRKn20J8EGQQ7tOSGNErwM7C967At-BnY3lXZFvwMbO8abQt-BlZpOk6p7i7T32X7u3qupajKrp5rKQKy6-1a3Q-jWj3d-3X3w6g-" + "N7ezg1Tvw6hONzvk5nZ2EO59GKXcNkNIujo7Jq3LrRvJZghJV2fHZHe5dSPpWgjd-_Xg87kO7yFN_178s-dzQ0H6qA1p-" + "vfinz2fGwrSR3RI078X9wTpXXmde_FAkEGwQ8txSNO_Fw8EGQQ7tFR7NR1vOLq7TH-X7e_" + "quZaamp79Wnf1XEvk1f2QtdXTvV93P2Ttc3M7O0j1PmTtdLNDbm5nB-Heh6zKbTOEpKuz46XQ5daNZDOEpKuzY7K73LqRdC2E7v168Llzh_" + "eQpn8v_tlz56EgfdSGNP178c-eOw8F6SM6pOnfi3uC9K68zr14IMgg2KHlOKTp34sHggyCHVqqvZqe_brngX53l-3v6rmWmpqeW2_" + "d1XMtkVf3hwetnu79uvvDgz43t7PjJdT74UGnmx1yczs7CPd-eKDcNkNIujo7ZqbLrRvJZghJV2fHZHe5dSPpWgjd-_Xg5ykd3kOa_r34Z5-" + "nDAXpozak6d-Lf_Z5ylCQPqJDmv69uCdI78rr3IsHggyCHVqOQ5r-vXggyCDYoaXaq-nZr3s-qOruMv1dPddSU9OzX-uunmuJvLo_FGv1dO_" + "X3R-K9bm5nR04ej8U63SzQ25uZwfh3g_FlNtmCElXZ8fMdLl1I9kMIenq7FjuXW7dSLoWQvd-3feZWB-ZIU3_" + "XjwQpBNX5148EKQTXudePBCkE2XnXjwQZBDs0JIb0vTvxQNBBsEOLcchTf9ePBBkEOzQUu3V9OzXuivv7zL9Xba7S01Nz36tu3quRTip1_" + "529V9rl78xonnJXyDpft7fqVEC8y8EcTVKYP-FIK5GCYp_IYir-TmMfgqD6ffnPZhwf6aDKerO-m-stdpvy6Jefj1deX-X6e-y_V1vw-" + "p4jOn8QlP_vLma_iU2EMTV9C-xgSCupn-JDQTpZDIIo5_CYPr9eQ8m3J_pYIq6s70MxUNcnU_PMtRdpr_L9ne9Davj6YwcTPfTmU5N_" + "3Y5EMTV9C-xgSCupn-JDQTpZDIIo5_CYPr9eQ8m3J_pYIq6s70MxbMpnU_PMtRdpr_L9ne9DavjTaccTPebzk5N_xIbCOJq-rfLgSCupn-" + "JDQTpZDIIo5_CYPr9eQ8m3J_pYIq6s70MxVtunU_PMtRdpr_L9ne9DavjXloOpvteulPTv8QGgria_iU2EMTV9G-XA0Fczc9h9FMYTL8_78GE-" + "zMdTFF3tpeheCeh8-lZhrrL9HfZ_q7m93rL7zW_rOt8z3R8z3Z87y1e3nzvteN7puN7tuN7b_FMRzzTMWbTEc90xLMd8WxHPNsxZtsRr-" + "iIV3TEKzriFXLM1e_vln8Lj-Z_VP9Vf9U6b5ntjssPLbPVUf8Z7dou_5I07byxyz_jTDtu2a1fGy7_" + "fDHtL43tv7bsVsxF67dty79zWdvlHxSk7Td2-" + "WezaLfiJPL3M2s0TZPJvn3n3Gm6ghpV03QEb8je2jW2pu38NmaNr2nHTvtGtmuUTfuLbPuvTtu53iJ12reyXSNu2r5s16ibthO_" + "QZ5L5LlCnkvkuUKeS-" + "S5Qp47yHMHee4gzx3kuYM8d5DnDvLcQZ47yHMHee4gzx3kuYM8d5DnDvLcQZ47yI1EbhRyI5EbhdxI5EYhNw5y4yA3DnLjIDcOcuMgNw5y4yA3" + "DnLjIDcOcuMgNw5y4yA3DnLjIDcOciuRW4XcSuRWIbcSuVXIrYPcOsitg9w6yK2D3DrIrYPcOsitg9w6yK2D3DrIrYPcOsitg9w6yK2DvJDIC4" + "W8kMgLhbyQyAuFvHCQFw7ywkFeOMgLB3nhIC8c5IWDvHCQFw7ywkFeOMgLB3nhIC8c5IWDvGgh3zS4NwL1psG8EYg3Dd6NQLtpYd20kG5aODct" + "lJsWxk0L4aaFb9NCt2lh27SQbVq4Ni1UmxamTQvRpoVn00KzaWORdxWtJpOVdxWtpiuoUam7Cn7nDZlzV9FqOz9WX-" + "Nz7ipa7RvZrlE6dxVN23912s71FqnTvpXtGrFzV9G0a9TOXUWrLX8ut0GeK-" + "S5RJ4r5LlEnivkuYM8d5DnDvLcQZ47yHMHee4gzx3kuYM8d5DnDvLcQZ47yHMHee4gzx3kuYPcSORGITcSuVHIjURuFHLjIDcOcuMgNw5y4yA3" + "DnLjIDcOcuMgNw5y4yA3DnLjIDcOcuMgNw5y4yC3ErlVyK1EbhVyK5Fbhdw6yK2D3DrIrYPcOsitg9w6yK2D3DrIrYPcOsitg9w6yK2D3DrIrY" + "PcOsgLibxQyAuJvFDIC4m8UMgLB3nhIC8c5IWDvHCQFw7ywkFeOMgLB3nhIC8c5IWDvHCQFw7ywkFeOMhbdxXlJ94VbpoVqLp13jLbHSVemq2O" + "Gmttl0hp541doqQdt-" + "ybxi7x0f7S2P5ry27FLHHRvm3sEhNtv7FLPLRbcRL5mXONxr2raH3n3Gm6ghqVe1fx9p03ZPKuot12PlKu8cm7inb7RrZrlPKuotX2nU-" + "zfed6NVp5V9Fq14jlXUWrXaOWdxXttvx8tUGeK-" + "S5RJ4r5LlEnivkuYM8d5DnDvLcQZ47yHMHee4gzx3kuYM8d5DnDvLcQZ47yHMHee4gzx3kuYPcSORGITcSuVHIjURuFHLjIDcOcuMgNw5y4yA3" + "DnLjIDcOcuMgNw5y4yA3DnLjIDcOcuMgNw5y4yC3ErlVyK1EbhVyK5Fbhdw6yK2D3DrIrYPcOsitg9w6yK2D3DrIrYPcOsitg9w6yK2D3DrIrY" + "PcOsgLibxQyAuJvFDIC4m8UMgLB3nhIC8c5IWDvHCQFw7ywkFeOMgLB3nhIC8c5IWDvHCQFw7ywkFeOMjf7irqP-EFQPjT6-0WcLW_" + "MZKtpjsX3rnrnQvv3PU2wtu43kZ4G9fbCm_relvhbV3vQngXrnchvAvh3XhWHz02jo0Te-o_" + "XDz9rXZqTNExapnsaE2ObCrByGm2BLmMkKsIuYyQqwhGRjAqgpERjIpgZQSrIlgZwaoIhYxQqAiFjFCICO8b7_" + "fC833j9V56SPatphKMnGZLkMsIuYqQywi5imBkBKMiGBnBqAhWRrAqgpURrIpQyAiFilDICJL9x8b7o_" + "D82Hh9lB6SfaupBCOn2RLkMkKuIuQyQq4iGBnBqAhGRjAqgpURrIpgZQSrIhQyQqEiFDKCZP9Jkmw1lWDkNFuCXEbIVYRcRshVBCMjGBXByAhG" + "RbAyglURrIxgVYRCRihUhEJGcEi2vMWe_9Y3En30O5Mz0GoqwchptgS5jJCrCLmMkKsIRkYwKoKREYyKYGUEqyJYGcGqCIWMUKgIhYwgZ-" + "Cs5e3OwFnL0ZkB7-7Nj6boGLVMdog5azeVYOQ0W4JcRshVhFxGyFUEIyMYFcHICEZFsDKCVRGsjGBVhEJGKFSEQkYQc-Z9bby_Cs-" + "vjddX4bFrPHbCY9d47KSHnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq2dnK2dmq37xvteeN43X" + "vfSQ7K_V-zvJft7xf5esr9X7O8l-3vF_l6yv1fs7yX7e8X-XrK_V-zvJft7xf5esr9X7O8l-3vFft9474XnvvHaSw_Jfq_Y7yX7vWK_l-" + "z3iv1est8r9nvJfq_Y7yX7vWK_l-z3iv1est8r9nvJfq_Y7yX7vWL_3Hg_C8_nxutZekj2z4r9s2T_rNg_S_bPiv2zZP-s2D9L9s-K_bNk_" + "6zYP0v2z4r9s2T_rNg_S_bPiv2zZP_ssvd_" + "ffOmKTpGLZMdgn27qQQjp9kS5DJCriLkMkKuIhgZwagIRkYwKoKVEayKYGUEqyIUMkKhIhQygmQ_arxHwnPUeI2kh2Q_" + "UuxHkv1IsR9J9iPFfiTZjxT7kWQ_UuxHkv1IsR9J9iPFfiTZjxT7kWQ_UuxHkv1IsT9pvE-E50njdSI9JPsTxf5Esj9R7E8k-xPF_" + "kSyP1HsTyT7E8X-RLI_UexPJPsTxf5Esj9R7E8k-xPF_kSyP1HsTxvvU-F52nidSg_J_lSxP5XsTxX7U8n-VLE_lexPFftTyf5UsT-V7E8V-" + "1PJ_lSxP5XsTxX7U8n-VLE_lexPXfbVXzN-i9BuKsHIabYEuYyQqwi5jJCrCEZGMCqCkRGMimBlBKsiWBnBqgiFjFCoCIWM4JBseTvvh-u-" + "keir_cJ_" + "vPnRFB2jlskOMWftphKMnGZLkMsIuYqQywi5imBkBKMiGBnBqAhWRrAqgpURrIpQyAiFilDICGLOwmbXD8WuHza7fngiPSR7teuHctcP1a4fyl" + "0_VLt-KHf9UO36odz1Q7Xrh3LXD9WuH8pdP1S7fih3_VDt-qHc9UO164dy1w_Vrh82u34odv2w2fXDU-kh2atdP5S7fqh2_VDu-" + "qHa9UO564dq1w_lrh-qXT-Uu36odv1Q7vqh2vVDueuHatcP5a4fql0_lLt-qHb9sHl6SlN0jFomOyR79eQ6lE-uQ_" + "XkOpRPrkP15DqUT65D9eQ6lE-uQ_XkOpRPrkP15DqUT65D9eQ6lE-uQ_XkOpRPrkP15DqUT65D9eQ6HDfeY-E5brzG0kOyHyv2Y8l-" + "rNiPJfuxYj-W7MeK_ViyHyv2Y8l-rNiPJfuxYj-W7MeK_ViyHyv2Y8l-rNhPGu-J8Jw0XhPpIdlPFPuJZD9R7CeS_USxn0j2E8V-" + "ItlPFPuJZD9R7CeS_USxn0j2E8V-ItlPFPuJZD9x2Ud_" + "vXnTFB2jlskOwb7dVIKR02wJchkhVxFyGSFXEYyMYFQEIyMYFcHKCFZFsDKCVREKGaFQEQoZQbJ_bLwfhedj4_UoPST7R8X-UbJ_" + "VOwfJftHxf5Rsn9U7B8l-0fF_lGyf1TsHyX7R8X-UbJ_VOwfJftHxf5Rsn9U7Jv3BTRFx6hlskOyV-_" + "JWt8ZOc2WIJcRchUhlxFyFcHICEZFMDKCURGsjGBVBCsjWBWhkBEKFaGQEST7p8b7SXg-NV5P0kOyf1LsnyT7J8X-SbJ_" + "UuyfJPsnxf5Jsn9S7J8k-yfF_kmyf1LsnyT7J8X-SbJ_" + "UuyfJPsnl32cvHnTFB2jlskOwb7dVIKR02wJchkhVxFyGSFXEYyMYFQEIyMYFcHKCFZFsDKCVREKGaFQEQoZQbJv9pxY7Dlxs-fE36SHZK_" + "2nFjuObHac2K558Rqz4nlnhOrPSeWe06s9pxY7jmx2nNiuefEas-J5Z4Tqz0nlntOrPacWO45sdpz4ubTq1h8ehU3n17Fz9JDslefXrW-" + "M3KaLUEuI-QqQi4j5CqCkRGMimBkBKMiWBnBqghWRrAqQiEjFCpCISNI9i-N94vwfGm8XqSHZP-i2L9I9i-K_Ytk_6LYv0j2L4r9i2T_oti_" + "SPYviv2LZP-i2L9I9i-K_Ytk_6LYv0j2L4r9ofE-CM9D43WQHpL9QbE_SPYHxf4g2R8U-4Nkf1DsD5L9QbE_SPYHxf4g2R8U-" + "4Nkf1DsD5L9QbE_" + "SPYHxb7FQObfyl3m3cpZ5tvKVeZp5GwZNVtGzpZRs2XkbBk1W0bOllGzZeRsGTVbRs6WUbNl5GwZNVtGzpZRs2XkbBk1W0bOllGz1bq-" + "vHbruvKaVrK3ir2V7K1ibyV7q9hbyd4q9layt4q9leytYm8le6vYW8neKvZWsreKvZXsrcs-aU6IRJwQSXNCJC_SQ7BP1AmRyBMiUSdEIk-" + "IRJ0QiTwhEnVCJPKESNQJkcgTIlEnRCJPiESdEIk8IRJ1QiTyhEjUCZHIEyJRJ0TSnBCJOCGS5oRIDtJDslcnRCJPiESdEIk8IRJ1QiTyhEjUC" + "ZHIEyJRJ0QiT4hEnRCJPCESdUIk8oRI1AmRyBMiUSdEIk-" + "IRJ0QafPUmaboGLVMdgj27aYSjJxmS5DLCLmKkMsIuYpgZASjIhgZwagIVkawKoKVEayKUMgIhYpQyAiSffPUmaboGLVMdkj26qlz6zsjp9kS5" + "DJCriLkMkKuIhgZwagIRkYwKoKVEayKYGUEqyIUMkKhIhQygmQ_bbzFR_J1a9Qy2SHZTxX7qWQ_Veynkv1UsZ9K9lPFfirZTxX7qWQ_" + "Veynkv1UsZ9K9lPFfirZTxX7qWQ_VewXjfdCeC4ar4X0kOwXiv1Csl8o9gvJfqHYLyT7hWK_kOwXiv1Csl8o9gvJfqHYLyT7hWK_" + "kOwXiv1Csl8o9ueN97nwPG-8zqWHZH-u2J9L9ueK_blkf67Yn0v254r9uWR_rtifS_bniv25ZH-u2J9L9ueK_" + "blkf67Yn0v254r9ReN9ITwvGq8L6SHZXyj2F5L9hWJ_IdlfKPYXkv2FYn8h2V8o9heS_YVifyHZXyj2F5L9hWJ_" + "IdlfKPYXkv2FYh803oHwDBqvQHpI9oFiH0j2gWIfSPaBYh9I9oFiH0j2gWIfSPaBYh9I9oFiH0j2gWIfSPaBYh9I9oFiHzbeofAMG69Qekj2oW" + "IfSvahYh9K9qFiH0r2oWIfSvahYh9K9qFiH0r2oWIfSvahYh9K9qFiH0r2oWIfNd6R8Iwar0h6SPaRYh9J9pFiH0n2kWIfSfaRYh9J9pFiH0n2" + "kWIfSfaRYh9J9pFiH0n2kWIfSfaRYr9qvFfCc9V4raSHZL9S7FeS_UqxX0n2K8V-JdmvFPuVZL9S7FeS_UqxX0n2K8V-JdmvFPuVZL9S7FeS_" + "Uqxv2y8L4XnZeN1KT0k-0vF_lKyv1TsLyX7S8X-UrK_VOwvJftLxf5Ssr9U7C8l-0vF_lKyv1TsLyX7S8X-UrK_VOyvGu8r4XnVeF1JD8n-" + "SrG_kuyvFPsryf5Ksb-S7K8U-yvJ_kqxv5LsrxT7K8n-SrG_kuyvFPsryf5Ksb-S7K8U-8-N92fh-bnx-iw8rhuPa-" + "Fx3Xhctz34X9DvWvZ9y35o2c7_EV_7NO17p_3gtJ3_8Lzxzx3_3PHPHX_j-BvH3zj-xvG3jr91_K3jbx3_wvEvHP_C8S9a_" + "puW76blt2n5bNp6h_fG4b1xeG8c3huH98bhvXF4bxzeG4f3xuG9cXhvHN4bh_fG4b1xeG8c3huH98bhvXF4byRv_reFu5Z937IfWrbz_" + "wrWPpJ3u_3gtJ3_JK_xzx3_3PHPHX_j-BvH3zj-xvG3jr91_K3jbx3_wvEvHP_C8X_jzT8Ys1i_a_2FmKpVmptDu-" + "PYOnqXv2EdpaVZ9Rxb1e5U9v19ffah1clm-aOMwR8ts_KZfUDrdgVzWQ3sO-yk-XZ52ly9b5n1Zvhrqb8o_6vSX3m5i5uqN7grmz56g2-" + "Vjf9uLHiqRiXsetut_-PO9-_-P_-VKLY"); + static string all_emojis_str = gzdecode(base64url_decode(packed_emojis).ok()).as_slice().str(); + constexpr size_t EMOJI_COUNT = 4713; +#else + string all_emojis_str; + constexpr size_t EMOJI_COUNT = 0; +#endif + FlatHashSet<Slice, SliceHash> all_emojis; + all_emojis.reserve(EMOJI_COUNT); + for (size_t i = 0; i < all_emojis_str.size(); i++) { + CHECK(all_emojis_str[i] != ' '); + CHECK(all_emojis_str[i + 1] != ' '); + size_t j = i + 2; + while (j < all_emojis_str.size() && all_emojis_str[j] != ' ') { + j++; + } + CHECK(j < all_emojis_str.size()); + all_emojis.insert(Slice(&all_emojis_str[i], &all_emojis_str[j])); + CHECK(j - i <= max_emoji_length); + i = j; + } + CHECK(all_emojis.size() == EMOJI_COUNT); + return all_emojis; + }(); + if (str.size() > MAX_EMOJI_LENGTH) { + return false; + } + return emojis.count(str) != 0; +} + +int get_fitzpatrick_modifier(Slice emoji) { + if (emoji.size() < 4 || emoji[emoji.size() - 4] != '\xF0' || emoji[emoji.size() - 3] != '\x9F' || + emoji[emoji.size() - 2] != '\x8F') { + return 0; + } + auto c = static_cast<unsigned char>(emoji.back()); + if (c < 0xBB || c > 0xBF) { + return 0; + } + return (c - 0xBB) + 2; +} + +Slice remove_fitzpatrick_modifier(Slice emoji) { + while (get_fitzpatrick_modifier(emoji) != 0) { + emoji.remove_suffix(4); + } + return emoji; +} + +string remove_emoji_modifiers(Slice emoji) { + string result = emoji.str(); + remove_emoji_modifiers_in_place(result); + return result; +} + +void remove_emoji_modifiers_in_place(string &emoji) { + static const Slice modifiers[] = {u8"\uFE0F" /* variation selector-16 */, + u8"\u200D\u2640" /* zero width joiner + female sign */, + u8"\u200D\u2642" /* zero width joiner + male sign */, + u8"\U0001F3FB" /* emoji modifier fitzpatrick type-1-2 */, + u8"\U0001F3FC" /* emoji modifier fitzpatrick type-3 */, + u8"\U0001F3FD" /* emoji modifier fitzpatrick type-4 */, + u8"\U0001F3FE" /* emoji modifier fitzpatrick type-5 */, + u8"\U0001F3FF" /* emoji modifier fitzpatrick type-6 */}; + size_t j = 0; + for (size_t i = 0; i < emoji.size();) { + bool is_found = false; + for (auto &modifier : modifiers) { + auto length = modifier.size(); + if (i + length <= emoji.size() && Slice(&emoji[i], length) == modifier) { + // skip modifier + i += length; + is_found = true; + break; + } + } + if (!is_found) { + emoji[j++] = emoji[i++]; + } + } + if (j != 0) { + emoji.resize(j); + } +} + +string remove_emoji_selectors(Slice emoji) { + if (!is_emoji(emoji)) { + return emoji.str(); + } + string str; + for (size_t i = 0; i < emoji.size(); i++) { + if (i + 3 <= emoji.size() && emoji[i] == '\xEF' && emoji[i + 1] == '\xB8' && emoji[i + 2] == '\x8F') { + // skip \uFE0F + i += 2; + } else { + str += emoji[i]; + } + } + CHECK(is_emoji(str)); + return str; +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.h new file mode 100644 index 0000000000..8bbc2904b5 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/emoji.h @@ -0,0 +1,32 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Slice.h" + +namespace td { + +// checks whether the string is an emoji; variation selectors are ignored +bool is_emoji(Slice str); + +// checks whether emoji ends on a Fitzpatrick modifier and returns it's number or 0 +int get_fitzpatrick_modifier(Slice emoji); + +// removes all Fitzpatrick modifier from the end of the string +Slice remove_fitzpatrick_modifier(Slice emoji); + +// removes all emoji modifiers from the string +string remove_emoji_modifiers(Slice emoji); + +// removes all emoji modifiers from the string in-place +void remove_emoji_modifiers_in_place(string &emoji); + +// removes all emoji selectors from the string if it is an emoji +string remove_emoji_selectors(Slice emoji); + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp index b22418151c..edb5df8677 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,28 +7,69 @@ #include "td/utils/filesystem.h" #include "td/utils/buffer.h" -#include "td/utils/logging.h" +#include "td/utils/misc.h" #include "td/utils/PathView.h" #include "td/utils/port/FileFd.h" +#include "td/utils/port/path.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" #include "td/utils/unicode.h" #include "td/utils/utf8.h" namespace td { -Result<BufferSlice> read_file(CSlice path, int64 size) { +namespace { + +template <class T> +T create_empty(size_t size); + +template <> +string create_empty<string>(size_t size) { + return string(size, '\0'); +} + +template <> +BufferSlice create_empty<BufferSlice>(size_t size) { + return BufferSlice{size}; +} + +template <> +SecureString create_empty<SecureString>(size_t size) { + return SecureString{size}; +} + +template <class T> +Result<T> read_file_impl(CSlice path, int64 size, int64 offset) { TRY_RESULT(from_file, FileFd::open(path, FileFd::Read)); - if (size == -1) { - size = from_file.get_size(); + TRY_RESULT(file_size, from_file.get_size()); + if (offset < 0 || offset > file_size) { + return Status::Error("Failed to read file: invalid offset"); + } + if (size < 0 || size > file_size - offset) { + size = file_size - offset; } - BufferWriter content{static_cast<size_t>(size), 0, 0}; - TRY_RESULT(got_size, from_file.read(content.as_slice())); + auto content = create_empty<T>(narrow_cast<size_t>(size)); + TRY_RESULT(got_size, from_file.pread(as_mutable_slice(content), offset)); if (got_size != static_cast<size_t>(size)) { return Status::Error("Failed to read file"); } from_file.close(); - return content.as_buffer_slice(); + return std::move(content); +} + +} // namespace + +Result<BufferSlice> read_file(CSlice path, int64 size, int64 offset) { + return read_file_impl<BufferSlice>(path, size, offset); +} + +Result<string> read_file_str(CSlice path, int64 size, int64 offset) { + return read_file_impl<string>(path, size, offset); +} + +Result<SecureString> read_file_secure(CSlice path, int64 size, int64 offset) { + return read_file_impl<SecureString>(path, size, offset); } // Very straightforward function. Don't expect much of it. @@ -37,18 +78,28 @@ Status copy_file(CSlice from, CSlice to, int64 size) { return write_file(to, content.as_slice()); } -Status write_file(CSlice to, Slice data) { +Status write_file(CSlice to, Slice data, WriteFileOptions options) { auto size = data.size(); TRY_RESULT(to_file, FileFd::open(to, FileFd::Truncate | FileFd::Create | FileFd::Write)); + if (options.need_lock) { + TRY_STATUS(to_file.lock(FileFd::LockFlags::Write, to.str(), 10)); + TRY_STATUS(to_file.truncate_to_current_position(0)); + } TRY_RESULT(written, to_file.write(data)); - if (written != static_cast<size_t>(size)) { + if (written != size) { return Status::Error(PSLICE() << "Failed to write file: written " << written << " bytes instead of " << size); } + if (options.need_sync) { + TRY_STATUS(to_file.sync()); + } + if (options.need_lock) { + to_file.lock(FileFd::LockFlags::Unlock, to.str(), 10).ignore(); + } to_file.close(); return Status::OK(); } -static std::string clean_filename_part(Slice name, int max_length) { +static string clean_filename_part(Slice name, int max_length) { auto is_ok = [](uint32 code) { if (code < 32) { return false; @@ -84,6 +135,9 @@ static std::string clean_filename_part(Slice name, int max_length) { uint32 code; it = next_utf8_unsafe(it, &code); if (!is_ok(code)) { + if (prepare_search_character(code) == 0) { + continue; + } code = ' '; } if (new_name.empty() && (code == ' ' || code == '.')) { @@ -99,14 +153,14 @@ static std::string clean_filename_part(Slice name, int max_length) { return new_name; } -std::string clean_filename(CSlice name) { +string clean_filename(CSlice name) { if (!check_utf8(name)) { return {}; } PathView path_view(name); - auto filename = clean_filename_part(path_view.file_stem(), 60); - auto extension = clean_filename_part(path_view.extension(), 20); + auto filename = clean_filename_part(path_view.file_stem(), 64); + auto extension = clean_filename_part(path_view.extension(), 16); if (!extension.empty()) { if (filename.empty()) { filename = std::move(extension); @@ -120,4 +174,18 @@ std::string clean_filename(CSlice name) { return filename; } +Status atomic_write_file(CSlice path, Slice data, CSlice path_tmp) { + string path_tmp_buf; + if (path_tmp.empty()) { + path_tmp_buf = path.str() + ".tmp"; + path_tmp = path_tmp_buf; + } + + WriteFileOptions options; + options.need_sync = true; + options.need_lock = true; + TRY_STATUS(write_file(path_tmp, data, options)); + return rename(path_tmp, path); +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h index 4bb1b17191..b437105819 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/filesystem.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,16 +7,28 @@ #pragma once #include "td/utils/buffer.h" +#include "td/utils/SharedSlice.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" namespace td { -Result<BufferSlice> read_file(CSlice path, int64 size = -1); +Result<BufferSlice> read_file(CSlice path, int64 size = -1, int64 offset = 0); +Result<string> read_file_str(CSlice path, int64 size = -1, int64 offset = 0); +Result<SecureString> read_file_secure(CSlice path, int64 size = -1, int64 offset = 0); -Status copy_file(CSlice from, CSlice to, int64 size = -1); +Status copy_file(CSlice from, CSlice to, int64 size = -1) TD_WARN_UNUSED_RESULT; -Status write_file(CSlice to, Slice data); +struct WriteFileOptions { + bool need_sync = false; + bool need_lock = true; +}; +Status write_file(CSlice to, Slice data, WriteFileOptions options = {}) TD_WARN_UNUSED_RESULT; + +string clean_filename(CSlice name); + +// writes data to file and ensures that the file is either fully overriden, or is left intact +// uses path_tmp to temporary store data, then calls rename +Status atomic_write_file(CSlice path, Slice data, CSlice path_tmp = {}); -std::string clean_filename(CSlice name); } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp index 44fc264ab5..d3dd6bcd58 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.cpp @@ -1,13 +1,11 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/find_boundary.h" -#include "td/utils/logging.h" - #include <cstring> namespace td { @@ -26,7 +24,7 @@ bool find_boundary(ChainBufferReader range, Slice boundary, size_t &already_read auto save_range = range.clone(); char x[MAX_BOUNDARY_LENGTH + 4]; range.advance(boundary.size(), {x, sizeof(x)}); - if (std::memcmp(x, boundary.data(), boundary.size()) == 0) { + if (Slice(x, boundary.size()) == boundary) { return true; } @@ -35,7 +33,7 @@ bool find_boundary(ChainBufferReader range, Slice boundary, size_t &already_read range.advance(1); already_read++; } else { - const char *ptr = static_cast<const char *>(std::memchr(ready.data(), boundary[0], ready.size())); + const auto *ptr = static_cast<const char *>(std::memchr(ready.data(), boundary[0], ready.size())); size_t shift; if (ptr == nullptr) { shift = ready.size(); diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h index 5b424cf23c..a0c6f4ff54 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/find_boundary.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/fixed_vector.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/fixed_vector.h new file mode 100644 index 0000000000..a4bf0af794 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/fixed_vector.h @@ -0,0 +1,79 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#include <utility> + +namespace td { + +template <class T> +class fixed_vector { + public: + fixed_vector() = default; + explicit fixed_vector(size_t size) : ptr_(new T[size]), size_(size) { + } + fixed_vector(fixed_vector &&other) noexcept { + swap(other); + } + fixed_vector &operator=(fixed_vector &&other) noexcept { + swap(other); + return *this; + } + fixed_vector(const fixed_vector &) = delete; + fixed_vector &operator=(const fixed_vector &) = delete; + ~fixed_vector() { + delete[] ptr_; + } + + using iterator = T *; + using const_iterator = const T *; + + T &operator[](size_t i) { + return ptr_[i]; + } + const T &operator[](size_t i) const { + return ptr_[i]; + } + + T *begin() { + return ptr_; + } + const T *begin() const { + return ptr_; + } + T *end() { + return ptr_ + size_; + } + const T *end() const { + return ptr_ + size_; + } + + bool empty() const { + return size() == 0; + } + size_t size() const { + return size_; + } + + void swap(fixed_vector<T> &other) { + std::swap(ptr_, other.ptr_); + std::swap(size_, other.size_); + } + + private: + T *ptr_{}; + size_t size_{0}; +}; + +template <class T> +void swap(fixed_vector<T> &lhs, fixed_vector<T> &rhs) { + lhs.swap(rhs); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/format.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/format.h index 745ad0d8a5..0baf9b8be0 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/format.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/format.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,7 +7,7 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/invoke.h" // for tuple_for_each +#include "td/utils/invoke.h" #include "td/utils/Slice.h" #include "td/utils/StringBuilder.h" @@ -17,7 +17,7 @@ namespace td { namespace format { /*** HexDump ***/ -template <std::size_t size, bool reversed = true> +template <std::size_t size, bool is_reversed = true> struct HexDumpSize { const unsigned char *data; }; @@ -26,12 +26,12 @@ inline char hex_digit(int x) { return "0123456789abcdef"[x]; } -template <std::size_t size, bool reversed> -StringBuilder &operator<<(StringBuilder &builder, const HexDumpSize<size, reversed> &dump) { +template <std::size_t size, bool is_reversed> +StringBuilder &operator<<(StringBuilder &builder, const HexDumpSize<size, is_reversed> &dump) { const unsigned char *ptr = dump.data; // TODO: append unsafe for (std::size_t i = 0; i < size; i++) { - int xy = ptr[reversed ? size - 1 - i : i]; + int xy = ptr[is_reversed ? size - 1 - i : i]; int x = xy >> 4; int y = xy & 15; builder << hex_digit(x) << hex_digit(y); @@ -46,21 +46,18 @@ struct HexDumpSlice { template <std::size_t align> StringBuilder &operator<<(StringBuilder &builder, const HexDumpSlice<align> &dump) { - std::size_t size = dump.slice.size(); - const unsigned char *ptr = dump.slice.ubegin(); + const auto str = dump.slice; + const auto size = str.size(); builder << '\n'; - const std::size_t part = size % align; - if (part) { - builder << HexDumpSlice<1>{Slice(ptr, part)} << '\n'; + const std::size_t first_part_size = size % align; + if (first_part_size) { + builder << HexDumpSlice<1>{str.substr(0, first_part_size)} << '\n'; } - size -= part; - ptr += part; - for (std::size_t i = 0; i < size; i += align) { - builder << HexDumpSize<align>{ptr}; - ptr += align; + for (std::size_t i = first_part_size; i < size; i += align) { + builder << HexDumpSize<align>{str.ubegin() + i}; if (((i / align) & 15) == 15 || i + align >= size) { builder << '\n'; @@ -148,7 +145,7 @@ inline StringBuilder &operator<<(StringBuilder &builder, const Escaped &escaped) builder << static_cast<char>(c); } else { const char *oct = "01234567"; - builder << "\\0" << oct[c >> 6] << oct[(c >> 3) & 7] << oct[c & 7]; + builder << '\\' << oct[c >> 6] << oct[(c >> 3) & 7] << oct[c & 7]; } } return builder; @@ -176,7 +173,7 @@ inline StringBuilder &operator<<(StringBuilder &logger, Time t) { while (i + 1 < durations_n && t.seconds_ > 10 * durations[i + 1].value) { i++; } - logger << StringBuilder::FixedDouble(t.seconds_ / durations[i].value, 1) << durations[i].name; + logger << StringBuilder::FixedDouble(t.seconds_ / durations[i].value, 1) << Slice(durations[i].name); return logger; } @@ -199,10 +196,10 @@ inline StringBuilder &operator<<(StringBuilder &logger, Size t) { static constexpr size_t sizes_n = sizeof(sizes) / sizeof(NamedValue); size_t i = 0; - while (i + 1 < sizes_n && t.size_ > 10 * sizes[i + 1].value) { + while (i + 1 < sizes_n && t.size_ >= 100000 * sizes[i].value) { i++; } - logger << t.size_ / sizes[i].value << sizes[i].name; + logger << t.size_ / sizes[i].value << Slice(sizes[i].name); return logger; } @@ -230,6 +227,19 @@ StringBuilder &operator<<(StringBuilder &stream, const Array<ArrayT> &array) { return stream << Slice("}"); } +inline StringBuilder &operator<<(StringBuilder &stream, const Array<vector<bool>> &array) { + bool first = true; + stream << Slice("{"); + for (bool x : array.ref) { + if (!first) { + stream << Slice(", "); + } + stream << x; + first = false; + } + return stream << Slice("}"); +} + template <class ArrayT> Array<ArrayT> as_array(const ArrayT &array) { return Array<ArrayT>{array}; @@ -291,10 +301,26 @@ StringBuilder &operator<<(StringBuilder &sb, const Concat<T> &concat) { } template <class... ArgsT> -auto concat(const ArgsT &... args) { +auto concat(const ArgsT &...args) { return Concat<decltype(std::tie(args...))>{std::tie(args...)}; } +template <class F> +struct Lambda { + const F &f; +}; + +template <class F> +StringBuilder &operator<<(StringBuilder &sb, const Lambda<F> &f) { + f.f(sb); + return sb; +} + +template <class LambdaT> +Lambda<LambdaT> lambda(const LambdaT &lambda) { + return Lambda<LambdaT>{lambda}; +} + } // namespace format using format::tag; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h index 08ff1099c2..25493fa183 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/int_types.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,7 +10,6 @@ #include <cstddef> #include <cstdint> -#include <cstring> namespace td { @@ -43,23 +42,4 @@ static_assert(static_cast<char>(-256) == 0, "Unexpected cast to char implementat #pragma warning(pop) #endif -template <size_t size> -struct UInt { - static_assert(size % 8 == 0, "size should be divisible by 8"); - uint8 raw[size / 8]; -}; - -template <size_t size> -inline bool operator==(const UInt<size> &a, const UInt<size> &b) { - return std::memcmp(a.raw, b.raw, sizeof(a.raw)) == 0; -} - -template <size_t size> -inline bool operator!=(const UInt<size> &a, const UInt<size> &b) { - return !(a == b); -} - -using UInt128 = UInt<128>; -using UInt256 = UInt<256>; - } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h index e9e56fc2c5..d69a8422e3 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/invoke.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -54,7 +54,7 @@ struct is_reference_wrapper<std::reference_wrapper<U>> : std::true_type {}; template <class Base, class T, class Derived, class... Args> auto invoke_impl(T Base::*pmf, Derived &&ref, - Args &&... args) noexcept(noexcept((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...))) + Args &&...args) noexcept(noexcept((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...))) -> std::enable_if_t<std::is_function<T>::value && std::is_base_of<Base, std::decay<Derived>>::value, decltype((std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...))> { return (std::forward<Derived>(ref).*pmf)(std::forward<Args>(args)...); @@ -62,7 +62,7 @@ auto invoke_impl(T Base::*pmf, Derived &&ref, template <class Base, class T, class RefWrap, class... Args> auto invoke_impl(T Base::*pmf, RefWrap &&ref, - Args &&... args) noexcept(noexcept((ref.get().*pmf)(std::forward<Args>(args)...))) + Args &&...args) noexcept(noexcept((ref.get().*pmf)(std::forward<Args>(args)...))) -> std::enable_if_t<std::is_function<T>::value && is_reference_wrapper<std::decay_t<RefWrap>>::value, decltype((ref.get().*pmf)(std::forward<Args>(args)...))> @@ -72,7 +72,7 @@ auto invoke_impl(T Base::*pmf, RefWrap &&ref, template <class Base, class T, class Pointer, class... Args> auto invoke_impl(T Base::*pmf, Pointer &&ptr, - Args &&... args) noexcept(noexcept(((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...))) + Args &&...args) noexcept(noexcept(((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...))) -> std::enable_if_t<std::is_function<T>::value && !is_reference_wrapper<std::decay_t<Pointer>>::value && !std::is_base_of<Base, std::decay_t<Pointer>>::value, decltype(((*std::forward<Pointer>(ptr)).*pmf)(std::forward<Args>(args)...))> { @@ -102,7 +102,7 @@ auto invoke_impl(T Base::*pmd, Pointer &&ptr) noexcept(noexcept((*std::forward<P } template <class F, class... Args> -auto invoke_impl(F &&f, Args &&... args) noexcept(noexcept(std::forward<F>(f)(std::forward<Args>(args)...))) +auto invoke_impl(F &&f, Args &&...args) noexcept(noexcept(std::forward<F>(f)(std::forward<Args>(args)...))) -> std::enable_if_t<!std::is_member_pointer<std::decay_t<F>>::value, decltype(std::forward<F>(f)(std::forward<Args>(args)...))> { return std::forward<F>(f)(std::forward<Args>(args)...); @@ -110,24 +110,24 @@ auto invoke_impl(F &&f, Args &&... args) noexcept(noexcept(std::forward<F>(f)(st template <class F, class... ArgTypes> auto invoke(F &&f, - ArgTypes &&... args) noexcept(noexcept(invoke_impl(std::forward<F>(f), std::forward<ArgTypes>(args)...))) + ArgTypes &&...args) noexcept(noexcept(invoke_impl(std::forward<F>(f), std::forward<ArgTypes>(args)...))) -> decltype(invoke_impl(std::forward<F>(f), std::forward<ArgTypes>(args)...)) { return invoke_impl(std::forward<F>(f), std::forward<ArgTypes>(args)...); } template <class F, class... Args, std::size_t... S> -void call_tuple_impl(F &func, std::tuple<Args...> &&tuple, IntSeq<S...>) { - func(std::forward<Args>(std::get<S>(tuple))...); +auto call_tuple_impl(F &&func, std::tuple<Args...> &&tuple, IntSeq<S...>) { + return func(std::forward<Args>(std::get<S>(tuple))...); } template <class... Args, std::size_t... S> -void invoke_tuple_impl(std::tuple<Args...> &&tuple, IntSeq<S...>) { - invoke(std::forward<Args>(std::get<S>(tuple))...); +auto invoke_tuple_impl(std::tuple<Args...> &&tuple, IntSeq<S...>) { + return invoke(std::forward<Args>(std::get<S>(tuple))...); } -template <class Actor, class F, class... Args, std::size_t... S> -void mem_call_tuple_impl(Actor *actor, F &func, std::tuple<Args...> &&tuple, IntSeq<S...>) { - (actor->*func)(std::forward<Args>(std::get<S>(tuple))...); +template <class ActorT, class F, class... Args, std::size_t... S> +auto mem_call_tuple_impl(ActorT *actor, std::tuple<F, Args...> &&tuple, IntSeq<0, S...>) { + return (actor->*std::get<0>(tuple))(std::forward<Args>(std::get<S>(tuple))...); } template <class F, class... Args, std::size_t... S> @@ -151,18 +151,18 @@ class LogicAnd { }; template <class F, class... Args> -void call_tuple(F &func, std::tuple<Args...> &&tuple) { - detail::call_tuple_impl(func, std::move(tuple), detail::IntRange<sizeof...(Args)>()); +auto call_tuple(F &&func, std::tuple<Args...> &&tuple) { + return detail::call_tuple_impl(func, std::move(tuple), detail::IntRange<sizeof...(Args)>()); } template <class... Args> -void invoke_tuple(std::tuple<Args...> &&tuple) { - detail::invoke_tuple_impl(std::move(tuple), detail::IntRange<sizeof...(Args)>()); +auto invoke_tuple(std::tuple<Args...> &&tuple) { + return detail::invoke_tuple_impl(std::move(tuple), detail::IntRange<sizeof...(Args)>()); } -template <class Actor, class F, class... Args> -void mem_call_tuple(Actor *actor, F &func, std::tuple<Args...> &&tuple) { - detail::mem_call_tuple_impl(actor, func, std::move(tuple), detail::IntRange<sizeof...(Args)>()); +template <class ActorT, class... Args> +auto mem_call_tuple(ActorT *actor, std::tuple<Args...> &&tuple) { + return detail::mem_call_tuple_impl(actor, std::move(tuple), detail::IntRange<sizeof...(Args)>()); } template <class F, class... Args> @@ -175,4 +175,36 @@ void tuple_for_each(const std::tuple<Args...> &tuple, const F &func) { detail::tuple_for_each_impl(tuple, func, detail::IntRange<sizeof...(Args)>()); } +template <size_t N, class Arg, class... Args, std::enable_if_t<N == 0, int> = 0> +auto &&get_nth_argument(Arg &&arg, Args &&...args) { + return std::forward<Arg>(arg); +} + +template <size_t N, class Arg, class... Args, std::enable_if_t<N != 0, int> = 0> +auto &&get_nth_argument(Arg &&arg, Args &&...args) { + return get_nth_argument<N - 1>(std::forward<Args &&>(args)...); +} + +template <class... Args> +auto &&get_last_argument(Args &&...args) { + return get_nth_argument<sizeof...(Args) - 1>(std::forward<Args &&>(args)...); +} + +namespace detail { +template <class F, class... Args, std::size_t... S> +auto call_n_arguments_impl(IntSeq<S...>, F &&f, Args &&...args) { + return f(get_nth_argument<S>(std::forward<Args>(args)...)...); +} +} // namespace detail + +template <size_t N, class F, class... Args> +auto call_n_arguments(F &&f, Args &&...args) { + return detail::call_n_arguments_impl(detail::IntRange<N>(), f, std::forward<Args>(args)...); +} + +template <class F, class X, class = void> +struct is_callable final : public std::false_type {}; +template <class F, class X> +struct is_callable<F, X, decltype(std::declval<F>()(std::declval<X>()))> final : public std::true_type {}; + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp index 17403ff87b..563575bdba 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.cpp @@ -1,19 +1,21 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/logging.h" +#include "td/utils/ExitGuard.h" #include "td/utils/port/Clocks.h" -#include "td/utils/port/Fd.h" #include "td/utils/port/thread_local.h" #include "td/utils/Slice.h" -#include "td/utils/Time.h" +#include "td/utils/TsCerr.h" #include <atomic> #include <cstdlib> +#include <limits> +#include <mutex> #if TD_ANDROID #include <android/log.h> @@ -27,115 +29,129 @@ namespace td { -int VERBOSITY_NAME(level) = VERBOSITY_NAME(DEBUG) + 1; -int VERBOSITY_NAME(net_query) = VERBOSITY_NAME(INFO); -int VERBOSITY_NAME(td_requests) = VERBOSITY_NAME(INFO); -int VERBOSITY_NAME(dc) = VERBOSITY_NAME(DEBUG) + 2; -int VERBOSITY_NAME(files) = VERBOSITY_NAME(DEBUG) + 2; -int VERBOSITY_NAME(mtproto) = VERBOSITY_NAME(DEBUG) + 7; -int VERBOSITY_NAME(connections) = VERBOSITY_NAME(DEBUG) + 8; -int VERBOSITY_NAME(raw_mtproto) = VERBOSITY_NAME(DEBUG) + 10; -int VERBOSITY_NAME(fd) = VERBOSITY_NAME(DEBUG) + 9; -int VERBOSITY_NAME(actor) = VERBOSITY_NAME(DEBUG) + 10; -int VERBOSITY_NAME(buffer) = VERBOSITY_NAME(DEBUG) + 10; -int VERBOSITY_NAME(sqlite) = VERBOSITY_NAME(DEBUG) + 10; +LogOptions log_options; + +static std::atomic<int> max_callback_verbosity_level{-2}; +static std::atomic<OnLogMessageCallback> on_log_message_callback{nullptr}; + +void set_log_message_callback(int max_verbosity_level, OnLogMessageCallback callback) { + if (callback == nullptr) { + max_verbosity_level = -2; + } + + max_callback_verbosity_level = max_verbosity_level; + on_log_message_callback = callback; +} + +void LogInterface::append(int log_level, CSlice slice) { + do_append(log_level, slice); + if (log_level == VERBOSITY_NAME(FATAL)) { + process_fatal_error(slice); + } else if (log_level <= max_callback_verbosity_level.load(std::memory_order_relaxed)) { + auto callback = on_log_message_callback.load(std::memory_order_relaxed); + if (callback != nullptr) { + callback(log_level, slice); + } + } +} TD_THREAD_LOCAL const char *Logger::tag_ = nullptr; TD_THREAD_LOCAL const char *Logger::tag2_ = nullptr; -Logger::Logger(LogInterface &log, int log_level, Slice file_name, int line_num, Slice comment, bool simple_mode) - : Logger(log, log_level, simple_mode) { - if (simple_mode) { +Logger::Logger(LogInterface &log, const LogOptions &options, int log_level, Slice file_name, int line_num, + Slice comment) + : Logger(log, options, log_level) { + if (log_level == VERBOSITY_NAME(PLAIN) && &options == &log_options) { + return; + } + if (!options_.add_info) { + return; + } + if (ExitGuard::is_exited()) { return; } - auto last_slash_ = static_cast<int32>(file_name.size()) - 1; - while (last_slash_ >= 0 && file_name[last_slash_] != '/' && file_name[last_slash_] != '\\') { - last_slash_--; + // log level + sb_ << '['; + if (static_cast<uint32>(log_level) < 10) { + sb_ << ' ' << static_cast<char>('0' + log_level); + } else { + sb_ << log_level; } - file_name = file_name.substr(last_slash_ + 1); + sb_ << ']'; + // thread id auto thread_id = get_thread_id(); + sb_ << "[t"; + if (static_cast<uint32>(thread_id) < 10) { + sb_ << ' ' << static_cast<char>('0' + thread_id); + } else { + sb_ << thread_id; + } + sb_ << ']'; - (*this) << '['; - if (log_level < 10) { - (*this) << ' '; + // timestamp + auto time = Clocks::system(); + auto unix_time = static_cast<uint32>(time); + auto nanoseconds = static_cast<uint32>((time - unix_time) * 1e9); + sb_ << '[' << unix_time << '.'; + uint32 limit = 100000000; + while (nanoseconds < limit && limit > 1) { + sb_ << '0'; + limit /= 10; } - (*this) << log_level << "][t"; - if (thread_id < 10) { - (*this) << ' '; + sb_ << nanoseconds << ']'; + + // file : line + if (!file_name.empty()) { + auto last_slash_ = static_cast<int32>(file_name.size()) - 1; + while (last_slash_ >= 0 && file_name[last_slash_] != '/' && file_name[last_slash_] != '\\') { + last_slash_--; + } + file_name = file_name.substr(last_slash_ + 1); + sb_ << '[' << file_name << ':' << static_cast<uint32>(line_num) << ']'; } - (*this) << thread_id << "][" << StringBuilder::FixedDouble(Clocks::system(), 9) << "][" << file_name << ':' - << line_num << ']'; + + // context from tag_ if (tag_ != nullptr && *tag_) { - (*this) << "[#" << Slice(tag_) << "]"; + sb_ << "[#" << Slice(tag_) << ']'; } + + // context from tag2_ if (tag2_ != nullptr && *tag2_) { - (*this) << "[!" << Slice(tag2_) << "]"; + sb_ << "[!" << Slice(tag2_) << ']'; } + + // comment (e.g. condition in LOG_IF) if (!comment.empty()) { - (*this) << "[&" << comment << "]"; + sb_ << "[&" << comment << ']'; } - (*this) << "\t"; + + sb_ << '\t'; } Logger::~Logger() { - if (!simple_mode_) { + if (ExitGuard::is_exited()) { + return; + } + if (options_.fix_newlines) { sb_ << '\n'; auto slice = as_cslice(); if (slice.back() != '\n') { slice.back() = '\n'; } - } - - log_.append(as_cslice(), log_level_); -} - -TsCerr::TsCerr() { - enterCritical(); -} -TsCerr::~TsCerr() { - exitCritical(); -} -TsCerr &TsCerr::operator<<(Slice slice) { - auto &fd = Fd::Stderr(); - if (fd.empty()) { - return *this; - } - double end_time = 0; - while (!slice.empty()) { - auto res = fd.write(slice); - if (res.is_error()) { - if (res.error().code() == EPIPE) { - break; - } - // Resource temporary unavailable - if (end_time == 0) { - end_time = Time::now() + 0.01; - } else if (Time::now() > end_time) { - break; - } - continue; + while (slice.size() > 1 && slice[slice.size() - 2] == '\n') { + slice.back() = '\0'; + slice = MutableCSlice(slice.begin(), slice.begin() + slice.size() - 1); } - slice.remove_prefix(res.ok()); + log_.append(log_level_, slice); + } else { + log_.append(log_level_, as_cslice()); } - return *this; } -void TsCerr::enterCritical() { - while (lock_.test_and_set(std::memory_order_acquire)) { - // spin - } -} - -void TsCerr::exitCritical() { - lock_.clear(std::memory_order_release); -} -TsCerr::Lock TsCerr::lock_ = ATOMIC_FLAG_INIT; - -class DefaultLog : public LogInterface { - public: - void append(CSlice slice, int log_level) override { +class DefaultLog final : public LogInterface { + void do_append(int log_level, CSlice slice) final { #if TD_ANDROID switch (log_level) { case VERBOSITY_NAME(FATAL): @@ -157,27 +173,26 @@ class DefaultLog : public LogInterface { #elif TD_TIZEN switch (log_level) { case VERBOSITY_NAME(FATAL): - dlog_print(DLOG_ERROR, DLOG_TAG, slice.c_str()); + dlog_print(DLOG_ERROR, DLOG_TAG, "%s", slice.c_str()); break; case VERBOSITY_NAME(ERROR): - dlog_print(DLOG_ERROR, DLOG_TAG, slice.c_str()); + dlog_print(DLOG_ERROR, DLOG_TAG, "%s", slice.c_str()); break; case VERBOSITY_NAME(WARNING): - dlog_print(DLOG_WARN, DLOG_TAG, slice.c_str()); + dlog_print(DLOG_WARN, DLOG_TAG, "%s", slice.c_str()); break; case VERBOSITY_NAME(INFO): - dlog_print(DLOG_INFO, DLOG_TAG, slice.c_str()); + dlog_print(DLOG_INFO, DLOG_TAG, "%s", slice.c_str()); break; default: - dlog_print(DLOG_DEBUG, DLOG_TAG, slice.c_str()); + dlog_print(DLOG_DEBUG, DLOG_TAG, "%s", slice.c_str()); break; } #elif TD_EMSCRIPTEN switch (log_level) { case VERBOSITY_NAME(FATAL): - emscripten_log( - EM_LOG_ERROR | EM_LOG_CONSOLE | EM_LOG_C_STACK | EM_LOG_JS_STACK | EM_LOG_DEMANGLE | EM_LOG_FUNC_PARAMS, - "%s", slice.c_str()); + emscripten_log(EM_LOG_ERROR | EM_LOG_CONSOLE | EM_LOG_C_STACK | EM_LOG_JS_STACK | EM_LOG_FUNC_PARAMS, "%s", + slice.c_str()); EM_ASM(throw(UTF8ToString($0)), slice.c_str()); break; case VERBOSITY_NAME(ERROR): @@ -192,28 +207,31 @@ class DefaultLog : public LogInterface { } #elif !TD_WINDOWS Slice color; + Slice no_color("\x1b[0m"); switch (log_level) { case VERBOSITY_NAME(FATAL): case VERBOSITY_NAME(ERROR): - color = TC_RED; + color = Slice("\x1b[1;31m"); // red break; case VERBOSITY_NAME(WARNING): - color = TC_YELLOW; + color = Slice("\x1b[1;33m"); // yellow break; case VERBOSITY_NAME(INFO): - color = TC_CYAN; + color = Slice("\x1b[1;36m"); // cyan + break; + default: + no_color = Slice(); break; } - TsCerr() << color << slice << TC_EMPTY; + if (!slice.empty() && slice.back() == '\n') { + TsCerr() << color << slice.substr(0, slice.size() - 1) << no_color << "\n"; + } else { + TsCerr() << color << slice << no_color; + } #else // TODO: color TsCerr() << slice; #endif - if (log_level == VERBOSITY_NAME(FATAL)) { - process_fatal_error(slice); - } - } - void rotate() override { } }; static DefaultLog default_log; @@ -221,18 +239,59 @@ static DefaultLog default_log; LogInterface *const default_log_interface = &default_log; LogInterface *log_interface = default_log_interface; -static OnFatalErrorCallback on_fatal_error_callback = nullptr; +void process_fatal_error(CSlice message) { + if (0 <= max_callback_verbosity_level.load(std::memory_order_relaxed)) { + auto callback = on_log_message_callback.load(std::memory_order_relaxed); + if (callback != nullptr) { + callback(0, message); + } + } -void set_log_fatal_error_callback(OnFatalErrorCallback callback) { - on_fatal_error_callback = callback; + std::abort(); } -void process_fatal_error(CSlice message) { - auto callback = on_fatal_error_callback; - if (callback) { - callback(message); +static std::atomic<uint32> log_guard; + +LogGuard::LogGuard() { + uint32 expected = 0; + while (!log_guard.compare_exchange_strong(expected, 1, std::memory_order_relaxed, std::memory_order_relaxed)) { + // spin + CHECK(expected == 1); + expected = 0; } - std::abort(); } +LogGuard::~LogGuard() { + CHECK(log_guard.load(std::memory_order_relaxed) == 1); + log_guard.store(0, std::memory_order_relaxed); +} + +bool has_log_guard() { + return log_guard.load(std::memory_order_relaxed) == 1; +} + +namespace { +std::mutex sdl_mutex; +int sdl_cnt = 0; +int sdl_verbosity = 0; +} // namespace + +ScopedDisableLog::ScopedDisableLog() { + std::unique_lock<std::mutex> guard(sdl_mutex); + if (sdl_cnt == 0) { + sdl_verbosity = set_verbosity_level(std::numeric_limits<int>::min()); + } + sdl_cnt++; +} + +ScopedDisableLog::~ScopedDisableLog() { + std::unique_lock<std::mutex> guard(sdl_mutex); + sdl_cnt--; + if (sdl_cnt == 0) { + set_verbosity_level(sdl_verbosity); + } +} + +static ExitGuard exit_guard; + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h index 629a4f248a..b6f45eb686 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/logging.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -25,38 +25,33 @@ #include "td/utils/common.h" #include "td/utils/port/thread_local.h" -#include "td/utils/Slice-decl.h" +#include "td/utils/Slice.h" #include "td/utils/StackAllocator.h" #include "td/utils/StringBuilder.h" #include <atomic> #include <type_traits> -#define PSTR_IMPL() ::td::Logger(::td::NullLog().ref(), 0, true) -#define PSLICE() ::td::detail::Slicify() & PSTR_IMPL() -#define PSTRING() ::td::detail::Stringify() & PSTR_IMPL() -#define PSLICE_SAFE() ::td::detail::SlicifySafe() & PSTR_IMPL() -#define PSTRING_SAFE() ::td::detail::StringifySafe() & PSTR_IMPL() - #define VERBOSITY_NAME(x) verbosity_##x -#define GET_VERBOSITY_LEVEL() (::td::VERBOSITY_NAME(level)) -#define SET_VERBOSITY_LEVEL(new_level) (::td::VERBOSITY_NAME(level) = (new_level)) +#define GET_VERBOSITY_LEVEL() (::td::get_verbosity_level()) +#define SET_VERBOSITY_LEVEL(new_level) (::td::set_verbosity_level(new_level)) #ifndef STRIP_LOG #define STRIP_LOG VERBOSITY_NAME(DEBUG) #endif #define LOG_IS_STRIPPED(strip_level) \ - (std::integral_constant<int, VERBOSITY_NAME(strip_level)>() > std::integral_constant<int, STRIP_LOG>()) + (::std::integral_constant<int, VERBOSITY_NAME(strip_level)>() > ::std::integral_constant<int, STRIP_LOG>()) + +#define LOGGER(interface, options, level, comment) ::td::Logger(interface, options, level, __FILE__, __LINE__, comment) -#define LOGGER(level, comment) \ - ::td::Logger(*::td::log_interface, VERBOSITY_NAME(level), __FILE__, __LINE__, comment, \ - VERBOSITY_NAME(level) == VERBOSITY_NAME(PLAIN)) +#define LOG_IMPL_FULL(interface, options, strip_level, runtime_level, condition, comment) \ + LOG_IS_STRIPPED(strip_level) || runtime_level > options.get_level() || !(condition) \ + ? (void)0 \ + : ::td::detail::Voidify() & LOGGER(interface, options, runtime_level, comment) -#define LOG_IMPL(strip_level, level, condition, comment) \ - LOG_IS_STRIPPED(strip_level) || VERBOSITY_NAME(level) > GET_VERBOSITY_LEVEL() || !(condition) \ - ? (void)0 \ - : ::td::detail::Voidify() & LOGGER(level, comment) +#define LOG_IMPL(strip_level, level, condition, comment) \ + LOG_IMPL_FULL(*::td::log_interface, ::td::log_options, strip_level, VERBOSITY_NAME(level), condition, comment) #define LOG(level) LOG_IMPL(level, level, true, ::td::Slice()) #define LOG_IF(level, condition) LOG_IMPL(level, level, condition, #condition) @@ -64,8 +59,6 @@ #define VLOG(level) LOG_IMPL(DEBUG, level, true, TD_DEFINE_STR(level)) #define VLOG_IF(level, condition) LOG_IMPL(DEBUG, level, condition, TD_DEFINE_STR(level) " " #condition) -#define LOG_ROTATE() ::td::log_interface->rotate() - #define LOG_TAG ::td::Logger::tag_ #define LOG_TAG2 ::td::Logger::tag2_ @@ -78,25 +71,26 @@ inline bool no_return_func() { } // clang-format off -#ifdef CHECK - #undef CHECK -#endif +#define DUMMY_LOG_CHECK(condition) LOG_IF(NEVER, !(condition)) + #ifdef TD_DEBUG #if TD_MSVC - #define CHECK(condition) \ + #define LOG_CHECK(condition) \ __analysis_assume(!!(condition)); \ LOG_IMPL(FATAL, FATAL, !(condition), #condition) #else - #define CHECK(condition) LOG_IMPL(FATAL, FATAL, !(condition) && no_return_func(), #condition) + #define LOG_CHECK(condition) LOG_IMPL(FATAL, FATAL, !(condition) && no_return_func(), #condition) #endif #else - #define CHECK(condition) LOG_IF(NEVER, !(condition)) + #define LOG_CHECK DUMMY_LOG_CHECK #endif -// clang-format on -#define UNREACHABLE() \ - LOG(FATAL); \ - ::td::process_fatal_error("Unreachable in " __FILE__ " at " TD_DEFINE_STR(__LINE__)) +#if NDEBUG + #define LOG_DCHECK DUMMY_LOG_CHECK +#else + #define LOG_DCHECK LOG_CHECK +#endif +// clang-format on constexpr int VERBOSITY_NAME(PLAIN) = -1; constexpr int VERBOSITY_NAME(FATAL) = 0; @@ -107,19 +101,53 @@ constexpr int VERBOSITY_NAME(DEBUG) = 4; constexpr int VERBOSITY_NAME(NEVER) = 1024; namespace td { -extern int VERBOSITY_NAME(level); -// TODO Not part of utils. Should be in some separate file -extern int VERBOSITY_NAME(mtproto); -extern int VERBOSITY_NAME(raw_mtproto); -extern int VERBOSITY_NAME(connections); -extern int VERBOSITY_NAME(dc); -extern int VERBOSITY_NAME(fd); -extern int VERBOSITY_NAME(net_query); -extern int VERBOSITY_NAME(td_requests); -extern int VERBOSITY_NAME(actor); -extern int VERBOSITY_NAME(buffer); -extern int VERBOSITY_NAME(files); -extern int VERBOSITY_NAME(sqlite); + +struct LogOptions { + std::atomic<int> level{VERBOSITY_NAME(DEBUG) + 1}; + bool fix_newlines{true}; + bool add_info{true}; + + int get_level() const { + return level.load(std::memory_order_relaxed); + } + int set_level(int new_level) { + return level.exchange(new_level); + } + + static const LogOptions &plain() { + static LogOptions plain_options{0, false, false}; + return plain_options; + } + + constexpr LogOptions() = default; + constexpr LogOptions(int level, bool fix_newlines, bool add_info) + : level(level), fix_newlines(fix_newlines), add_info(add_info) { + } + + LogOptions(const LogOptions &other) : LogOptions(other.level.load(), other.fix_newlines, other.add_info) { + } + + LogOptions &operator=(const LogOptions &other) { + if (this == &other) { + return *this; + } + level = other.level.load(); + fix_newlines = other.fix_newlines; + add_info = other.add_info; + return *this; + } + LogOptions(LogOptions &&) = delete; + LogOptions &operator=(LogOptions &&) = delete; + ~LogOptions() = default; +}; + +extern LogOptions log_options; +inline int set_verbosity_level(int level) { + return log_options.set_level(level); +} +inline int get_verbosity_level() { + return log_options.get_level(); +} class LogInterface { public: @@ -129,69 +157,43 @@ class LogInterface { LogInterface(LogInterface &&) = delete; LogInterface &operator=(LogInterface &&) = delete; virtual ~LogInterface() = default; - virtual void append(CSlice slice, int log_level_) = 0; - virtual void rotate() = 0; -}; -class NullLog : public LogInterface { - public: - void append(CSlice slice, int log_level_) override { - } - void rotate() override { + void append(int log_level, CSlice slice); + + virtual void after_rotation() { } - NullLog &ref() { - return *this; + + virtual vector<string> get_file_paths() { + return {}; } + + virtual void do_append(int log_level, CSlice slice) = 0; }; extern LogInterface *const default_log_interface; extern LogInterface *log_interface; -using OnFatalErrorCallback = void (*)(CSlice message); -void set_log_fatal_error_callback(OnFatalErrorCallback callback); - [[noreturn]] void process_fatal_error(CSlice message); -#define TC_RED "\e[1;31m" -#define TC_BLUE "\e[1;34m" -#define TC_CYAN "\e[1;36m" -#define TC_GREEN "\e[1;32m" -#define TC_YELLOW "\e[1;33m" -#define TC_EMPTY "\e[0m" - -class TsCerr { - public: - TsCerr(); - TsCerr(const TsCerr &) = delete; - TsCerr &operator=(const TsCerr &) = delete; - TsCerr(TsCerr &&) = delete; - TsCerr &operator=(TsCerr &&) = delete; - ~TsCerr(); - TsCerr &operator<<(Slice slice); - - private: - using Lock = std::atomic_flag; - static Lock lock_; - - void enterCritical(); - void exitCritical(); -}; +using OnLogMessageCallback = void (*)(int verbosity_level, CSlice message); +void set_log_message_callback(int max_verbosity_level, OnLogMessageCallback callback); class Logger { + static const size_t BUFFER_SIZE = 128 * 1024; + public: - static const int BUFFER_SIZE = 128 * 1024; - Logger(LogInterface &log, int log_level, bool simple_mode = false) + Logger(LogInterface &log, const LogOptions &options, int log_level) : buffer_(StackAllocator::alloc(BUFFER_SIZE)) , log_(log) - , log_level_(log_level) , sb_(buffer_.as_slice()) - , simple_mode_(simple_mode) { + , options_(options) + , log_level_(log_level) { } - Logger(LogInterface &log, int log_level, Slice file_name, int line_num, Slice comment, bool simple_mode); + Logger(LogInterface &log, const LogOptions &options, int log_level, Slice file_name, int line_num, Slice comment); template <class T> - Logger &operator<<(const T &other) { + Logger &operator<<(T &&other) { sb_ << other; return *this; } @@ -214,66 +216,40 @@ class Logger { private: decltype(StackAllocator::alloc(0)) buffer_; LogInterface &log_; - int log_level_; StringBuilder sb_; - bool simple_mode_; + const LogOptions &options_; + int log_level_; }; -namespace detail { -class Voidify { +class LogGuard { public: - template <class T> - void operator&(const T &) { - } + LogGuard(); + LogGuard(const LogGuard &) = delete; + LogGuard &operator=(const LogGuard &) = delete; + LogGuard(LogGuard &&) = delete; + LogGuard &operator=(LogGuard &&) = delete; + ~LogGuard(); }; -class Slicify { +bool has_log_guard(); + +class ScopedDisableLog { public: - CSlice operator&(Logger &logger) { - return logger.as_cslice(); - } + ScopedDisableLog(); + ScopedDisableLog(const ScopedDisableLog &) = delete; + ScopedDisableLog &operator=(const ScopedDisableLog &) = delete; + ScopedDisableLog(ScopedDisableLog &&) = delete; + ScopedDisableLog &operator=(ScopedDisableLog &&) = delete; + ~ScopedDisableLog(); }; -class Stringify { +namespace detail { +class Voidify { public: - string operator&(Logger &logger) { - return logger.as_cslice().str(); + template <class T> + void operator&(const T &) { } }; } // namespace detail -class TsLog : public LogInterface { - public: - explicit TsLog(LogInterface *log) : log_(log) { - } - void init(LogInterface *log) { - enter_critical(); - log_ = log; - exit_critical(); - } - void append(CSlice slice, int level) override { - enter_critical(); - log_->append(slice, level); - exit_critical(); - } - void rotate() override { - enter_critical(); - log_->rotate(); - exit_critical(); - } - - private: - LogInterface *log_ = nullptr; - std::atomic_flag lock_ = ATOMIC_FLAG_INIT; - void enter_critical() { - while (lock_.test_and_set(std::memory_order_acquire)) { - // spin - } - } - void exit_critical() { - lock_.clear(std::memory_order_release); - } -}; } // namespace td - -#include "td/utils/Slice.h" diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp index f3068ca6d3..df86382031 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,6 +7,7 @@ #include "td/utils/misc.h" #include "td/utils/port/thread_local.h" +#include "td/utils/utf8.h" #include <algorithm> #include <cstdlib> @@ -16,7 +17,7 @@ namespace td { char *str_dup(Slice str) { - char *res = static_cast<char *>(std::malloc(str.size() + 1)); + auto *res = static_cast<char *>(std::malloc(str.size() + 1)); if (res == nullptr) { return nullptr; } @@ -25,23 +26,41 @@ char *str_dup(Slice str) { return res; } -string implode(vector<string> v, char delimiter) { +string implode(const vector<string> &v, char delimiter) { string result; - for (auto &str : v) { - if (!result.empty()) { + for (size_t i = 0; i < v.size(); i++) { + if (i != 0) { result += delimiter; } - result += str; + result += v[i]; } return result; } +string lpad(string str, size_t size, char c) { + if (str.size() >= size) { + return str; + } + return string(size - str.size(), c) + str; +} + +string lpad0(string str, size_t size) { + return lpad(std::move(str), size, '0'); +} + +string rpad(string str, size_t size, char c) { + if (str.size() >= size) { + return str; + } + return str + string(size - str.size(), c); +} + string oneline(Slice str) { string result; result.reserve(str.size()); bool after_new_line = true; for (auto c : str) { - if (c != '\n') { + if (c != '\n' && c != '\r') { if (after_new_line) { if (c == ' ') { continue; @@ -49,7 +68,7 @@ string oneline(Slice str) { after_new_line = false; } result += c; - } else { + } else if (!after_new_line) { after_new_line = true; result += ' '; } @@ -60,10 +79,20 @@ string oneline(Slice str) { return result; } +namespace detail { +Status get_to_integer_safe_error(Slice str) { + auto status = Status::Error(PSLICE() << "Can't parse \"" << str << "\" as an integer"); + if (!check_utf8(status.message())) { + status = Status::Error("Strings must be encoded in UTF-8"); + } + return status; +} +} // namespace detail + double to_double(Slice str) { static TD_THREAD_LOCAL std::stringstream *ss; if (init_thread_local<std::stringstream>(ss)) { - ss->imbue(std::locale::classic()); + auto previous_locale = ss->imbue(std::locale::classic()); } else { ss->str(std::string()); ss->clear(); @@ -75,4 +104,157 @@ double to_double(Slice str) { return result; } +Result<string> hex_decode(Slice hex) { + if (hex.size() % 2 != 0) { + return Status::Error("Wrong hex string length"); + } + string result(hex.size() / 2, '\0'); + for (size_t i = 0; i < result.size(); i++) { + int high = hex_to_int(hex[i + i]); + int low = hex_to_int(hex[i + i + 1]); + if (high == 16 || low == 16) { + return Status::Error("Wrong hex string"); + } + result[i] = static_cast<char>(high * 16 + low); // TODO implementation-defined + } + return std::move(result); +} + +string hex_encode(Slice data) { + const char *hex = "0123456789abcdef"; + string res; + res.reserve(2 * data.size()); + for (unsigned char c : data) { + res.push_back(hex[c >> 4]); + res.push_back(hex[c & 15]); + } + return res; +} + +static bool is_url_char(char c) { + return is_alnum(c) || c == '-' || c == '.' || c == '_' || c == '~'; +} + +string url_encode(Slice data) { + size_t length = 3 * data.size(); + for (auto c : data) { + length -= 2 * is_url_char(c); + } + if (length == data.size()) { + return data.str(); + } + string result; + result.reserve(length); + for (auto c : data) { + if (is_url_char(c)) { + result += c; + } else { + auto ch = static_cast<unsigned char>(c); + result += '%'; + result += "0123456789ABCDEF"[ch / 16]; + result += "0123456789ABCDEF"[ch % 16]; + } + } + CHECK(result.size() == length); + return result; +} + +size_t url_decode(Slice from, MutableSlice to, bool decode_plus_sign_as_space) { + size_t to_i = 0; + CHECK(to.size() >= from.size()); + for (size_t from_i = 0, n = from.size(); from_i < n; from_i++) { + if (from[from_i] == '%' && from_i + 2 < n) { + int high = hex_to_int(from[from_i + 1]); + int low = hex_to_int(from[from_i + 2]); + if (high < 16 && low < 16) { + to[to_i++] = static_cast<char>(high * 16 + low); + from_i += 2; + continue; + } + } + to[to_i++] = decode_plus_sign_as_space && from[from_i] == '+' ? ' ' : from[from_i]; + } + return to_i; +} + +string url_decode(Slice from, bool decode_plus_sign_as_space) { + string to; + to.resize(from.size()); + to.resize(url_decode(from, to, decode_plus_sign_as_space)); + return to; +} + +MutableSlice url_decode_inplace(MutableSlice str, bool decode_plus_sign_as_space) { + size_t result_size = url_decode(str, str, decode_plus_sign_as_space); + str.truncate(result_size); + return str; +} + +string buffer_to_hex(Slice buffer) { + const char *hex = "0123456789ABCDEF"; + string res(2 * buffer.size(), '\0'); + for (std::size_t i = 0; i < buffer.size(); i++) { + auto c = buffer.ubegin()[i]; + res[2 * i] = hex[c & 15]; + res[2 * i + 1] = hex[c >> 4]; + } + return res; +} + +namespace { + +template <class F> +string x_decode(Slice s, F &&f) { + string res; + for (size_t n = s.size(), i = 0; i < n; i++) { + if (i + 1 < n && f(s[i])) { + res.append(static_cast<unsigned char>(s[i + 1]), s[i]); + i++; + continue; + } + res.push_back(s[i]); + } + return res; +} + +template <class F> +string x_encode(Slice s, F &&f) { + string res; + for (size_t n = s.size(), i = 0; i < n; i++) { + res.push_back(s[i]); + if (f(s[i])) { + unsigned char cnt = 1; + while (cnt < 250 && i + cnt < n && s[i + cnt] == s[i]) { + cnt++; + } + res.push_back(static_cast<char>(cnt)); + i += cnt - 1; + } + } + return res; +} + +bool is_zero(unsigned char c) { + return c == 0; +} + +bool is_zero_or_one(unsigned char c) { + return c == 0 || c == 0xff; +} + +} // namespace + +std::string zero_encode(Slice data) { + return x_encode(data, is_zero); +} +std::string zero_decode(Slice data) { + return x_decode(data, is_zero); +} +std::string zero_one_encode(Slice data) { + return x_encode(data, is_zero_or_one); +} +std::string zero_one_decode(Slice data) { + return x_decode(data, is_zero_or_one); +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h index 62b01794ab..5d9292f43a 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/misc.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -9,12 +9,12 @@ #include "td/utils/common.h" #include "td/utils/logging.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" #include "td/utils/StringBuilder.h" #include <cstdint> #include <limits> -#include <tuple> #include <type_traits> #include <utility> @@ -33,75 +33,26 @@ std::pair<T, T> split(T s, char delimiter = ' ') { } template <class T> -vector<T> full_split(T s, char delimiter = ' ') { - T next; +vector<T> full_split(T s, char delimiter = ' ', size_t max_parts = std::numeric_limits<size_t>::max()) { vector<T> result; - while (!s.empty()) { - std::tie(next, s) = split(s, delimiter); - result.push_back(next); - } - return result; -} - -string implode(vector<string> v, char delimiter = ' '); - -namespace detail { - -template <typename T> -struct transform_helper { - template <class Func> - auto transform(const T &v, const Func &f) { - vector<decltype(f(*v.begin()))> result; - result.reserve(v.size()); - for (auto &x : v) { - result.push_back(f(x)); - } + if (s.empty()) { return result; } - - template <class Func> - auto transform(T &&v, const Func &f) { - vector<decltype(f(std::move(*v.begin())))> result; - result.reserve(v.size()); - for (auto &x : v) { - result.push_back(f(std::move(x))); + while (result.size() + 1 < max_parts) { + auto delimiter_pos = s.find(delimiter); + if (delimiter_pos == string::npos) { + break; } - return result; - } -}; - -} // namespace detail -template <class T, class Func> -auto transform(T &&v, const Func &f) { - return detail::transform_helper<std::decay_t<T>>().transform(std::forward<T>(v), f); -} - -template <class T> -void reset_to_empty(T &value) { - using std::swap; - std::decay_t<T> tmp; - swap(tmp, value); -} - -template <class T> -auto append(vector<T> &destination, const vector<T> &source) { - destination.insert(destination.end(), source.begin(), source.end()); -} - -template <class T> -auto append(vector<T> &destination, vector<T> &&source) { - if (destination.empty()) { - destination.swap(source); - return; + result.push_back(s.substr(0, delimiter_pos)); + s = s.substr(delimiter_pos + 1); } - destination.reserve(destination.size() + source.size()); - for (auto &elem : source) { - destination.push_back(std::move(elem)); - } - reset_to_empty(source); + result.push_back(std::move(s)); + return result; } +string implode(const vector<string> &v, char delimiter = ' '); + inline bool begins_with(Slice str, Slice prefix) { return prefix.size() <= str.size() && prefix == Slice(str.data(), prefix.size()); } @@ -118,10 +69,11 @@ inline char to_lower(char c) { return c; } -inline void to_lower_inplace(MutableSlice slice) { +inline MutableSlice to_lower_inplace(MutableSlice slice) { for (auto &c : slice) { c = to_lower(c); } + return slice; } inline string to_lower(Slice slice) { @@ -191,6 +143,12 @@ T trim(T str) { return T(begin, end); } +string lpad(string str, size_t size, char c); + +string lpad0(string str, size_t size); + +string rpad(string str, size_t size, char c); + string oneline(Slice str); template <class T> @@ -205,7 +163,7 @@ std::enable_if_t<std::is_signed<T>::value, T> to_integer(Slice str) { begin++; } while (begin != end && is_digit(*begin)) { - integer_value = static_cast<unsigned_T>(integer_value * 10 + (*begin++ - '0')); + integer_value = static_cast<unsigned_T>(integer_value * 10 + static_cast<unsigned_T>(*begin++ - '0')); } if (integer_value > static_cast<unsigned_T>(std::numeric_limits<T>::max())) { static_assert(~0 + 1 == 0, "Two's complement"); @@ -227,16 +185,20 @@ std::enable_if_t<std::is_unsigned<T>::value, T> to_integer(Slice str) { auto begin = str.begin(); auto end = str.end(); while (begin != end && is_digit(*begin)) { - integer_value = static_cast<T>(integer_value * 10 + (*begin++ - '0')); + integer_value = static_cast<T>(integer_value * 10 + static_cast<T>(*begin++ - '0')); } return integer_value; } +namespace detail { +Status get_to_integer_safe_error(Slice str); +} // namespace detail + template <class T> Result<T> to_integer_safe(Slice str) { auto res = to_integer<T>(str); - if (to_string(res) != str) { - return Status::Error(PSLICE() << "Can't parse \"" << str << "\" as number"); + if ((PSLICE() << res) != str) { + return detail::get_to_integer_safe_error(str); } return res; } @@ -263,6 +225,27 @@ typename std::enable_if<std::is_unsigned<T>::value, T>::type hex_to_integer(Slic return integer_value; } +template <class T> +Result<typename std::enable_if<std::is_unsigned<T>::value, T>::type> hex_to_integer_safe(Slice str) { + T integer_value = 0; + auto begin = str.begin(); + auto end = str.end(); + if (begin == end) { + return Status::Error("String is empty"); + } + while (begin != end) { + T digit = hex_to_int(*begin++); + if (digit == 16) { + return Status::Error("String contains non-hex digit"); + } + if (integer_value > std::numeric_limits<T>::max() / 16) { + return Status::Error("String hex number overflows"); + } + integer_value = integer_value * 16 + digit; + } + return integer_value; +} + double to_double(Slice str); template <class T> @@ -276,11 +259,23 @@ T clamp(T value, T min_value, T max_value) { return value; } +Result<string> hex_decode(Slice hex); + +string hex_encode(Slice data); + +string url_encode(Slice data); + +size_t url_decode(Slice from, MutableSlice to, bool decode_plus_sign_as_space); + +string url_decode(Slice from, bool decode_plus_sign_as_space); + +MutableSlice url_decode_inplace(MutableSlice str, bool decode_plus_sign_as_space); + // run-time checked narrowing cast (type conversion): namespace detail { template <class T, class U> -struct is_same_signedness +struct is_same_signedness final : public std::integral_constant<bool, std::is_signed<T>::value == std::is_signed<U>::value> {}; template <class T, class Enable = void> @@ -292,22 +287,34 @@ template <class T> struct safe_undeflying_type<T, std::enable_if_t<std::is_enum<T>::value>> { using type = std::underlying_type_t<T>; }; -} // namespace detail -template <class R, class A> -R narrow_cast(const A &a) { - using RT = typename detail::safe_undeflying_type<R>::type; - using AT = typename detail::safe_undeflying_type<A>::type; +class NarrowCast { + const char *file_; + int line_; - static_assert(std::is_integral<RT>::value, "expected integral type to cast to"); - static_assert(std::is_integral<AT>::value, "expected integral type to cast from"); + public: + NarrowCast(const char *file, int line) : file_(file), line_(line) { + } - auto r = R(a); - CHECK(A(r) == a); - CHECK((detail::is_same_signedness<RT, AT>::value) || ((static_cast<RT>(r) < RT{}) == (static_cast<AT>(a) < AT{}))); + template <class R, class A> + R cast(const A &a) { + using RT = typename safe_undeflying_type<R>::type; + using AT = typename safe_undeflying_type<A>::type; - return r; -} + static_assert(std::is_integral<RT>::value, "expected integral type to cast to"); + static_assert(std::is_integral<AT>::value, "expected integral type to cast from"); + + auto r = R(a); + LOG_CHECK(A(r) == a) << static_cast<AT>(a) << " " << static_cast<RT>(r) << " " << file_ << " " << line_; + LOG_CHECK((is_same_signedness<RT, AT>::value) || ((static_cast<RT>(r) < RT{}) == (static_cast<AT>(a) < AT{}))) + << static_cast<AT>(a) << " " << static_cast<RT>(r) << " " << file_ << " " << line_; + + return r; + } +}; +} // namespace detail + +#define narrow_cast detail::NarrowCast(__FILE__, __LINE__).cast template <class R, class A> Result<R> narrow_cast_safe(const A &a) { @@ -334,4 +341,14 @@ bool is_aligned_pointer(const T *pointer) { return (reinterpret_cast<std::uintptr_t>(static_cast<const void *>(pointer)) & (Alignment - 1)) == 0; } +string buffer_to_hex(Slice buffer); + +string zero_encode(Slice data); + +string zero_decode(Slice data); + +string zero_one_encode(Slice data); + +string zero_one_decode(Slice data); + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h index 450b60f94c..579127f625 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/optional.h @@ -1,36 +1,99 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once +#include "td/utils/common.h" #include "td/utils/Status.h" +#include <type_traits> #include <utility> namespace td { -template <class T> +template <class T, bool = std::is_copy_constructible<T>::value> class optional { public: optional() = default; - template <class T1> + template <class T1, + std::enable_if_t<!std::is_same<std::decay_t<T1>, optional>::value && std::is_constructible<T, T1>::value, + int> = 0> optional(T1 &&t) : impl_(std::forward<T1>(t)) { } - explicit operator bool() { + + optional(const optional &other) { + if (other) { + impl_ = Result<T>(other.value()); + } + } + + optional &operator=(const optional &other) { + if (this == &other) { + return *this; + } + if (other) { + impl_ = Result<T>(other.value()); + } else { + impl_ = Result<T>(); + } + return *this; + } + + optional(optional &&other) = default; + optional &operator=(optional &&other) = default; + ~optional() = default; + + explicit operator bool() const noexcept { return impl_.is_ok(); } T &value() { + DCHECK(*this); + return impl_.ok_ref(); + } + const T &value() const { + DCHECK(*this); return impl_.ok_ref(); } T &operator*() { return value(); } + T unwrap() { + CHECK(*this); + auto res = std::move(value()); + impl_ = {}; + return res; + } + + optional<T> copy() const { + if (*this) { + return value(); + } + return {}; + } + + template <class... ArgsT> + void emplace(ArgsT &&...args) { + impl_.emplace(std::forward<ArgsT>(args)...); + } private: Result<T> impl_; }; +template <typename T> +struct optional<T, false> : optional<T, true> { + optional() = default; + + using optional<T, true>::optional; + + optional(const optional &other) = delete; + optional &operator=(const optional &other) = delete; + optional(optional &&) = default; + optional &operator=(optional &&) = default; + ~optional() = default; +}; + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h index 6c6186f6a2..3c54151c55 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/overloaded.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -22,7 +22,7 @@ struct overload<F> : public F { template <class F, class... Fs> struct overload<F, Fs...> : public overload<F> - , overload<Fs...> { + , public overload<Fs...> { overload(F f, Fs... fs) : overload<F>(f), overload<Fs...>(fs...) { } using overload<F>::operator(); diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp index da68754a61..b20beef74f 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.cpp @@ -1,23 +1,98 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/port/Clocks.h" +#include "td/utils/port/platform.h" + #include <chrono> +#include <ctime> + +#if TD_PORT_POSIX +#include <time.h> +#endif namespace td { -ClocksDefault::Duration ClocksDefault::monotonic() { +double Clocks::monotonic() { +#if TD_PORT_POSIX + // use system specific functions, because std::chrono::steady_clock is steady only under Windows + +#ifdef CLOCK_BOOTTIME + { + static bool skip = [] { + struct timespec spec; + return clock_gettime(CLOCK_BOOTTIME, &spec) != 0; + }(); + struct timespec spec; + if (!skip && clock_gettime(CLOCK_BOOTTIME, &spec) == 0) { + return static_cast<double>(spec.tv_nsec) * 1e-9 + static_cast<double>(spec.tv_sec); + } + } +#endif +#ifdef CLOCK_MONOTONIC_RAW + { + static bool skip = [] { + struct timespec spec; + return clock_gettime(CLOCK_MONOTONIC_RAW, &spec) != 0; + }(); + struct timespec spec; + if (!skip && clock_gettime(CLOCK_MONOTONIC_RAW, &spec) == 0) { + return static_cast<double>(spec.tv_nsec) * 1e-9 + static_cast<double>(spec.tv_sec); + } + } +#endif + +#endif + auto duration = std::chrono::steady_clock::now().time_since_epoch(); return static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count()) * 1e-9; } -ClocksDefault::Duration ClocksDefault::system() { +double Clocks::system() { auto duration = std::chrono::system_clock::now().time_since_epoch(); return static_cast<double>(std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count()) * 1e-9; } +int Clocks::tz_offset() { + // not thread-safe on POSIX, so calculate the offset only once + static int offset = [] { + auto now = std::time(nullptr); + + auto time_ptr = std::localtime(&now); + if (time_ptr == nullptr) { + return 0; + } + auto local_time = *time_ptr; + + time_ptr = std::gmtime(&now); + if (time_ptr == nullptr) { + return 0; + } + auto utc_time = *time_ptr; + + int minute_offset = local_time.tm_min - utc_time.tm_min; + int hour_offset = local_time.tm_hour - utc_time.tm_hour; + int day_offset = local_time.tm_mday - utc_time.tm_mday; + if (day_offset >= 20) { + day_offset = -1; + } else if (day_offset <= -20) { + day_offset = 1; + } + int sec_offset = day_offset * 86400 + hour_offset * 3600 + minute_offset * 60; + if (sec_offset >= 15 * 3600 || sec_offset <= -15 * 3600) { + return 0; + } + return sec_offset / 900 * 900; // round to 900 just in case + }(); + return offset; +} + +namespace detail { +int init_tz_offset_private = Clocks::tz_offset(); +} // namespace detail + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h index a4270df4ad..d663d129d7 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Clocks.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,21 +8,12 @@ namespace td { -class ClocksBase { - public: - using Duration = double; -}; - -// TODO: (maybe) write system specific functions. -class ClocksDefault { - public: - using Duration = ClocksBase::Duration; +struct Clocks { + static double monotonic(); - static Duration monotonic(); + static double system(); - static Duration system(); + static int tz_offset(); }; -using Clocks = ClocksDefault; - } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h index b7f01fa401..f88ef5bad8 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/CxCli.h @@ -1,28 +1,37 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once +#pragma managed(push, off) #include "td/utils/port/config.h" +#pragma managed(pop) #include "td/utils/common.h" -#undef small #if TD_WINRT +#pragma managed(push, off) #include "td/utils/port/wstring_convert.h" +#pragma managed(pop) #include "collection.h" +#pragma managed(push, off) #include <cstdint> #include <map> #include <mutex> +#pragma managed(pop) + +#undef small #define REF_NEW ref new #define CLRCALL +#define DEPRECATED_ATTRIBUTE(message) \ + ::Windows::Foundation::Metadata::Deprecated(message, ::Windows::Foundation::Metadata::DeprecationType::Deprecate, 0x0) namespace CxCli { @@ -38,8 +47,9 @@ using Platform::String; using Platform::NullReferenceException; -template <class Key, class Value> class ConcurrentDictionary { -public: +template <class Key, class Value> +class ConcurrentDictionary { + public: bool TryGetValue(Key key, Value &value) { std::lock_guard<std::mutex> guard(mutex_); auto it = impl_.find(key); @@ -59,11 +69,12 @@ public: impl_.erase(it); return true; } - Value &operator [] (Key key) { + Value &operator[](Key key) { std::lock_guard<std::mutex> guard(mutex_); return impl_[key]; } -private: + + private: std::mutex mutex_; std::map<Key, Value> impl_; }; @@ -72,26 +83,31 @@ inline std::int64_t Increment(volatile std::int64_t &value) { return InterlockedIncrement64(&value); } -inline std::string string_to_unmanaged(String^ str) { +inline std::string string_to_unmanaged(String ^ str) { if (!str) { return std::string(); } - return td::from_wstring(str->Data(), str->Length()).ok(); + auto r_unmanaged_str = td::from_wstring(str->Data(), str->Length()); + if (r_unmanaged_str.is_error()) { + return std::string(); + } + return r_unmanaged_str.move_as_ok(); } -inline String^ string_from_unmanaged(const std::string &from) { +inline String ^ string_from_unmanaged(const std::string &from) { auto tmp = td::to_wstring(from).ok(); return REF_NEW String(tmp.c_str(), static_cast<unsigned>(tmp.size())); } -} // namespace CxCli +} // namespace CxCli #elif TD_CLI -#include <msclr\marshal_cppstd.h> +#undef small #define REF_NEW gcnew #define CLRCALL __clrcall +#define DEPRECATED_ATTRIBUTE(message) System::ObsoleteAttribute(message) namespace CxCli { @@ -113,21 +129,34 @@ using System::NullReferenceException; using System::Collections::Concurrent::ConcurrentDictionary; -inline std::int64_t Increment(std::int64_t %value) { +inline std::int64_t Increment(std::int64_t % value) { return System::Threading::Interlocked::Increment(value); } -inline std::string string_to_unmanaged(String^ str) { - if (!str) { +inline std::string string_to_unmanaged(String ^ str) { + if (!str || str->Length == 0) { return std::string(); } - return msclr::interop::marshal_as<std::string>(str); + + Array<System::Byte> ^ bytes = System::Text::Encoding::UTF8->GetBytes(str); + cli::pin_ptr<System::Byte> pinned_ptr = &bytes[0]; + std::string result(reinterpret_cast<const char *>(&pinned_ptr[0]), bytes->Length); + return result; } -inline String^ string_from_unmanaged(const std::string &from) { - return msclr::interop::marshal_as<String^>(from); +inline String ^ string_from_unmanaged(const std::string &from) { + if (from.empty()) { + return String::Empty; + } + + Array<System::Byte> ^ bytes = REF_NEW Vector<System::Byte>(static_cast<ArrayIndexType>(from.size())); + cli::pin_ptr<System::Byte> pinned_ptr = &bytes[0]; + for (size_t i = 0; i < from.size(); ++i) { + pinned_ptr[i] = from[i]; + } + return System::Text::Encoding::UTF8->GetString(bytes); } -} // namespace CxCli +} // namespace CxCli #endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h index ba2edabefd..8972259102 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFd.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h index e119a3c0eb..d4f586c303 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/EventFdBase.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,7 +7,7 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/Status.h" namespace td { @@ -23,10 +23,10 @@ class EventFdBase { virtual void init() = 0; virtual bool empty() = 0; virtual void close() = 0; - virtual const Fd &get_fd() const = 0; - virtual Fd &get_fd() = 0; + virtual PollableFdInfo &get_poll_info() = 0; virtual Status get_pending_error() TD_WARN_UNUSED_RESULT = 0; virtual void release() = 0; virtual void acquire() = 0; + virtual void wait(int timeout_ms) = 0; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.cpp deleted file mode 100644 index cb4cb27306..0000000000 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.cpp +++ /dev/null @@ -1,1104 +0,0 @@ -// -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -#include "td/utils/port/Fd.h" - -#include "td/utils/common.h" -#include "td/utils/format.h" -#include "td/utils/logging.h" -#include "td/utils/misc.h" -#include "td/utils/Observer.h" - -#if TD_PORT_POSIX - -#include <atomic> - -#include <fcntl.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#endif - -#if TD_PORT_WINDOWS - -#include "td/utils/buffer.h" -#include "td/utils/misc.h" - -#include <cstring> - -#endif - -namespace td { - -#if TD_PORT_POSIX - -Fd::InfoSet::InfoSet() { - get_info(0).refcnt = 1; - get_info(1).refcnt = 1; - get_info(2).refcnt = 1; -} -Fd::Info &Fd::InfoSet::get_info(int32 id) { - CHECK(0 <= id && id < InfoSet::MAX_FD) << tag("fd", id); - return fd_array_[id]; -} -Fd::InfoSet Fd::fd_info_set_; - -// TODO(bug) if constuctor call tries to output something to the LOG it will fail, because log is not initialized -Fd Fd::stderr_(2, Mode::Reference); -Fd Fd::stdout_(1, Mode::Reference); -Fd Fd::stdin_(0, Mode::Reference); - -Fd::Fd() = default; - -Fd::Fd(int fd, Mode mode) : mode_(mode), fd_(fd) { - auto *info = get_info(); - int old_ref_cnt = info->refcnt.load(std::memory_order_relaxed); - if (old_ref_cnt == 0) { - old_ref_cnt = info->refcnt.load(std::memory_order_acquire); - CHECK(old_ref_cnt == 0); - CHECK(mode_ == Mode::Owner) << tag("fd", fd_); - VLOG(fd) << "FD created [fd:" << fd_ << "]"; - - auto fcntl_res = fcntl(fd_, F_GETFD); - auto fcntl_errno = errno; - LOG_IF(FATAL, fcntl_res == -1) << Status::PosixError(fcntl_errno, "fcntl F_GET_FD failed"); - - info->refcnt.store(1, std::memory_order_relaxed); - CHECK(mode_ != Mode::Reference); - CHECK(info->observer == nullptr); - info->flags = 0; - info->observer = nullptr; - } else { - CHECK(mode_ == Mode::Reference) << tag("fd", fd_); - auto fcntl_res = fcntl(fd_, F_GETFD); - auto fcntl_errno = errno; - LOG_IF(FATAL, fcntl_res == -1) << Status::PosixError(fcntl_errno, "fcntl F_GET_FD failed"); - - CHECK(mode_ == Mode::Reference); - info->refcnt.fetch_add(1, std::memory_order_relaxed); - } -} - -int Fd::move_as_native_fd() { - clear_info(); - auto res = fd_; - fd_ = -1; - return res; -} - -Fd::~Fd() { - close(); -} - -Fd::Fd(Fd &&other) { - fd_ = other.fd_; - mode_ = other.mode_; - other.fd_ = -1; -} - -Fd &Fd::operator=(Fd &&other) { - if (this != &other) { - close(); - - fd_ = other.fd_; - mode_ = other.mode_; - other.fd_ = -1; - } - return *this; -} - -Fd Fd::clone() const { - return Fd(fd_, Mode::Reference); -} - -Fd &Fd::Stderr() { - return stderr_; -} -Fd &Fd::Stdout() { - return stdout_; -} -Fd &Fd::Stdin() { - return stdin_; -} - -Status Fd::duplicate(const Fd &from, Fd &to) { - CHECK(!from.empty()); - CHECK(!to.empty()); - if (dup2(from.get_native_fd(), to.get_native_fd()) == -1) { - return OS_ERROR("dup2 failed"); - } - return Status::OK(); -} - -bool Fd::empty() const { - return fd_ == -1; -} - -const Fd &Fd::get_fd() const { - return *this; -} - -Fd &Fd::get_fd() { - return *this; -} - -int Fd::get_native_fd() const { - CHECK(!empty()); - return fd_; -} - -void Fd::set_observer(ObserverBase *observer) { - auto *info = get_info(); - CHECK(observer == nullptr || info->observer == nullptr); - info->observer = observer; -} - -ObserverBase *Fd::get_observer() const { - auto *info = get_info(); - return info->observer; -} - -void Fd::close_ref() { - CHECK(mode_ == Mode::Reference); - auto *info = get_info(); - - int old_ref_cnt = info->refcnt.fetch_sub(1, std::memory_order_relaxed); - CHECK(old_ref_cnt > 1) << tag("fd", fd_); - fd_ = -1; -} - -void Fd::close_own() { - CHECK(mode_ == Mode::Owner); - VLOG(fd) << "FD closed [fd:" << fd_ << "]"; - - clear_info(); - ::close(fd_); - fd_ = -1; -} - -void Fd::close() { - if (!empty()) { - switch (mode_) { - case Mode::Reference: - close_ref(); - break; - case Mode::Owner: - close_own(); - break; - } - } -} - -Fd::Info *Fd::get_info() { - CHECK(!empty()); - return &fd_info_set_.get_info(fd_); -} - -const Fd::Info *Fd::get_info() const { - CHECK(!empty()); - return &fd_info_set_.get_info(fd_); -} - -void Fd::clear_info() { - CHECK(!empty()); - CHECK(mode_ != Mode::Reference); - - auto *info = get_info(); - int old_ref_cnt = info->refcnt.load(std::memory_order_relaxed); - CHECK(old_ref_cnt == 1); - info->flags = 0; - info->observer = nullptr; - info->refcnt.store(0, std::memory_order_release); -} - -void Fd::update_flags_notify(Flags flags) { - update_flags_inner(flags, true); -} - -void Fd::update_flags(Flags flags) { - update_flags_inner(flags, false); -} - -void Fd::update_flags_inner(int32 new_flags, bool notify_flag) { - if (new_flags & Error) { - new_flags |= Error; - new_flags |= Close; - } - auto *info = get_info(); - int32 &flags = info->flags; - int32 old_flags = flags; - flags |= new_flags; - if (new_flags & Close) { - // TODO: ??? - flags &= ~Write; - } - if (flags != old_flags) { - VLOG(fd) << "Update flags " << tag("fd", fd_) << tag("from", format::as_binary(old_flags)) - << tag("to", format::as_binary(flags)); - } - if (flags != old_flags && notify_flag) { - auto observer = info->observer; - if (observer != nullptr) { - observer->notify(); - } - } -} - -Fd::Flags Fd::get_flags() const { - return get_info()->flags; -} - -void Fd::clear_flags(Flags flags) { - get_info()->flags &= ~flags; -} - -bool Fd::has_pending_error() const { - return (get_flags() & Fd::Flag::Error) != 0; -} - -Status Fd::get_pending_error() { - if (!has_pending_error()) { - return Status::OK(); - } - clear_flags(Fd::Error); - int error = 0; - socklen_t errlen = sizeof(error); - if (getsockopt(fd_, SOL_SOCKET, SO_ERROR, static_cast<void *>(&error), &errlen) == 0) { - if (error == 0) { - return Status::OK(); - } - return Status::PosixError(error, PSLICE() << "Error on socket [fd_ = " << fd_ << "]"); - } - auto status = OS_SOCKET_ERROR(PSLICE() << "Can't load error on socket [fd_ = " << fd_ << "]"); - LOG(INFO) << "Can't load pending socket error: " << status; - return status; -} - -Result<size_t> Fd::write_unsafe(Slice slice) { - int native_fd = get_native_fd(); - auto write_res = skip_eintr([&] { return ::write(native_fd, slice.begin(), slice.size()); }); - auto write_errno = errno; - if (write_res >= 0) { - return narrow_cast<size_t>(write_res); - } - return Status::PosixError(write_errno, PSLICE() << "Write to fd " << native_fd << " has failed"); -} - -Result<size_t> Fd::write(Slice slice) { - int native_fd = get_native_fd(); - auto write_res = skip_eintr([&] { return ::write(native_fd, slice.begin(), slice.size()); }); - auto write_errno = errno; - if (write_res >= 0) { - return narrow_cast<size_t>(write_res); - } - - if (write_errno == EAGAIN -#if EAGAIN != EWOULDBLOCK - || write_errno == EWOULDBLOCK -#endif - ) { - clear_flags(Write); - return 0; - } - - auto error = Status::PosixError(write_errno, PSLICE() << "Write to fd " << native_fd << " has failed"); - switch (write_errno) { - case EBADF: - case ENXIO: - case EFAULT: - case EINVAL: - LOG(FATAL) << error; - UNREACHABLE(); - default: - LOG(WARNING) << error; - // fallthrough - case ECONNRESET: - case EDQUOT: - case EFBIG: - case EIO: - case ENETDOWN: - case ENETUNREACH: - case ENOSPC: - case EPIPE: - clear_flags(Write); - update_flags(Close); - return std::move(error); - } -} - -Result<size_t> Fd::read(MutableSlice slice) { - int native_fd = get_native_fd(); - CHECK(slice.size() > 0); - auto read_res = skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); }); - auto read_errno = errno; - if (read_res >= 0) { - if (read_res == 0) { - errno = 0; - clear_flags(Read); - update_flags(Close); - } - return narrow_cast<size_t>(read_res); - } - if (read_errno == EAGAIN -#if EAGAIN != EWOULDBLOCK - || read_errno == EWOULDBLOCK -#endif - ) { - clear_flags(Read); - return 0; - } - auto error = Status::PosixError(read_errno, PSLICE() << "Read from fd " << native_fd << " has failed"); - switch (read_errno) { - case EISDIR: - case EBADF: - case ENXIO: - case EFAULT: - case EINVAL: - case ENOTCONN: - LOG(FATAL) << error; - UNREACHABLE(); - default: - LOG(WARNING) << error; - // fallthrough - case EIO: - case ENOBUFS: - case ENOMEM: - case ECONNRESET: - case ETIMEDOUT: - clear_flags(Read); - update_flags(Close); - return std::move(error); - } -} - -Status Fd::set_is_blocking(bool is_blocking) { - auto old_flags = fcntl(fd_, F_GETFL); - if (old_flags == -1) { - return OS_SOCKET_ERROR("Failed to get socket flags"); - } - auto new_flags = is_blocking ? old_flags & ~O_NONBLOCK : old_flags | O_NONBLOCK; - if (new_flags != old_flags && fcntl(fd_, F_SETFL, new_flags) == -1) { - return OS_SOCKET_ERROR("Failed to set socket flags"); - } - - return Status::OK(); -} - -#endif - -#if TD_PORT_WINDOWS - -class Fd::FdImpl { - public: - FdImpl(Fd::Type type, HANDLE handle) - : type_(type), handle_(handle), async_mode_(type_ == Fd::Type::EventFd || type_ == Fd::Type::StdinFileFd) { - init(); - } - FdImpl(Fd::Type type, SOCKET socket, int socket_family) - : type_(type), socket_(socket), socket_family_(socket_family), async_mode_(true) { - init(); - } - - FdImpl(const FdImpl &) = delete; - FdImpl &operator=(const FdImpl &) = delete; - FdImpl(FdImpl &&) = delete; - FdImpl &operator=(FdImpl &&) = delete; - - ~FdImpl() { - close(); - } - void set_observer(ObserverBase *observer) { - observer_ = observer; - } - ObserverBase *get_observer() const { - return observer_; - } - - void update_flags_notify(Fd::Flags flags) { - update_flags_inner(flags, true); - } - - void update_flags(Fd::Flags flags) { - update_flags_inner(flags, false); - } - - void update_flags_inner(int32 new_flags, bool notify_flag) { - if (new_flags & Fd::Error) { - new_flags |= Fd::Error; - new_flags |= Fd::Close; - } - int32 old_flags = flags_; - flags_ |= new_flags; - if (new_flags & Fd::Close) { - // TODO: ??? - flags_ &= ~Fd::Write; - internal_flags_ &= ~Fd::Write; - } - if (flags_ != old_flags) { - VLOG(fd) << "Update flags " << tag("fd", get_io_handle()) << tag("from", format::as_binary(old_flags)) - << tag("to", format::as_binary(flags_)); - } - if (flags_ != old_flags && notify_flag) { - auto observer = get_observer(); - if (observer != nullptr) { - observer->notify(); - } - } - } - - int32 get_flags() const { - return flags_; - } - - void clear_flags(Fd::Flags mask) { - flags_ &= ~mask; - } - - Status get_pending_error() { - if (!has_pending_error()) { - return Status::OK(); - } - clear_flags(Fd::Error); - return std::move(pending_error_); - } - bool has_pending_error() const { - return (get_flags() & Fd::Flag::Error) != 0; - } - - HANDLE get_read_event() { - if (type() == Fd::Type::StdinFileFd) { - return get_io_handle(); - } - return read_event_; - } - void on_read_event() { - if (type_ == Fd::Type::StdinFileFd) { - return try_read_stdin(); - } - ResetEvent(read_event_); - if (type_ == Fd::Type::EventFd) { - return update_flags_notify(Fd::Flag::Read); - } - if (type_ == Fd::Type::SocketFd && !connected_) { - on_connect_ready(); - } else { - if (!async_read_flag_) { - return; - } - - if (type_ == Fd::Type::ServerSocketFd) { - on_accept_ready(); - } else { - on_read_ready(); - } - } - loop(); - } - HANDLE get_write_event() { - return write_event_; - } - void on_write_event() { - CHECK(async_write_flag_); - ResetEvent(write_event_); - on_write_ready(); - loop(); - } - - SOCKET get_native_socket() const { - return socket_; - } - - HANDLE get_io_handle() const { - CHECK(!empty()); - if (type() == Fd::Type::FileFd || type() == Fd::Type::StdinFileFd) { - return handle_; - } - return reinterpret_cast<HANDLE>(socket_); - } - - Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT { - if (async_mode_) { - return write_async(slice); - } else { - return write_sync(slice); - } - } - - Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT { - if (async_mode_) { - return read_async(slice); - } else { - return read_sync(slice); - } - } - - Result<size_t> write_async(Slice slice) TD_WARN_UNUSED_RESULT { - CHECK(async_mode_); - output_writer_.append(slice); - output_reader_.sync_with_writer(); - loop(); - return slice.size(); - } - Result<size_t> write_sync(Slice slice) TD_WARN_UNUSED_RESULT { - CHECK(!async_mode_); - DWORD bytes_written = 0; - auto res = WriteFile(get_io_handle(), slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_written, nullptr); - if (!res) { - return OS_ERROR("Failed to write_sync"); - } - return bytes_written; - } - Result<size_t> read_async(MutableSlice slice) TD_WARN_UNUSED_RESULT { - CHECK(async_mode_); - auto res = input_reader_.advance(min(slice.size(), input_reader_.size()), slice); - if (res == 0) { - clear_flags(Fd::Flag::Read); - } - return res; - } - Result<size_t> read_sync(MutableSlice slice) TD_WARN_UNUSED_RESULT { - CHECK(!async_mode_); - DWORD bytes_read = 0; - auto res = ReadFile(get_io_handle(), slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, nullptr); - if (!res) { - return OS_ERROR("Failed to read_sync"); - } - if (bytes_read == 0) { - clear_flags(Fd::Flag::Read); - } - return bytes_read; - } - - // for ServerSocket - Result<Fd> accept() { - if (accepted_.empty()) { - clear_flags(Fd::Flag::Read); - return Status::Error(-1, "Operation would block"); - } - auto res = std::move(accepted_.back()); - accepted_.pop_back(); - return std::move(res); - } - - void connect(const IPAddress &addr) { - CHECK(!connected_); - CHECK(type_ == Fd::Type::SocketFd); - DWORD bytes_read; - std::memset(&read_overlapped_, 0, sizeof(read_overlapped_)); - read_overlapped_.hEvent = read_event_; - LPFN_CONNECTEX ConnectExPtr = nullptr; - GUID guid = WSAID_CONNECTEX; - DWORD numBytes; - int error = ::WSAIoctl(socket_, SIO_GET_EXTENSION_FUNCTION_POINTER, static_cast<void *>(&guid), sizeof(guid), - static_cast<void *>(&ConnectExPtr), sizeof(ConnectExPtr), &numBytes, nullptr, nullptr); - if (error) { - return on_error(OS_SOCKET_ERROR("WSAIoctl failed"), Fd::Flag::Read); - } - auto status = ConnectExPtr(socket_, addr.get_sockaddr(), narrow_cast<int>(addr.get_sockaddr_len()), nullptr, 0, - &bytes_read, &read_overlapped_); - if (status != 0) { - ResetEvent(read_event_); - connected_ = true; - update_flags_notify(Fd::Flag::Read); - return; - } - - auto last_error = GetLastError(); - if (last_error == ERROR_IO_PENDING) { - return; - } - on_error(OS_SOCKET_ERROR("ConnectEx failed"), Fd::Flag::Read); - } - - // for EventFd - void release() { - CHECK(type_ == Fd::Type::EventFd); - SetEvent(read_event_); - } - - void acquire() { - CHECK(type_ == Fd::Type::EventFd); - ResetEvent(read_event_); - clear_flags(Fd::Flag::Read); - } - - // TODO: interface for BufferedFd optimization. - - bool empty() const { - return type() == Fd::Type::Empty; - } - void close() { - if (empty()) { - return; - } - switch (type()) { - case Fd::Type::StdinFileFd: - case Fd::Type::FileFd: { - if (!CloseHandle(handle_)) { - auto error = OS_ERROR("Failed to close file"); - LOG(ERROR) << error; - } - handle_ = INVALID_HANDLE_VALUE; - break; - } - case Fd::Type::ServerSocketFd: - case Fd::Type::SocketFd: { - if (closesocket(socket_) != 0) { - auto error = OS_SOCKET_ERROR("Failed to close socket"); - LOG(ERROR) << error; - } - socket_ = INVALID_SOCKET; - break; - } - case Fd::Type::EventFd: - break; - default: - UNREACHABLE(); - } - - if (read_event_ != INVALID_HANDLE_VALUE) { - if (!CloseHandle(read_event_)) { - auto error = OS_ERROR("Failed to close event"); - LOG(ERROR) << error; - } - read_event_ = INVALID_HANDLE_VALUE; - } - if (write_event_ != INVALID_HANDLE_VALUE) { - if (!CloseHandle(write_event_)) { - auto error = OS_ERROR("Failed to close event"); - LOG(ERROR) << error; - } - write_event_ = INVALID_HANDLE_VALUE; - } - - type_ = Fd::Type::Empty; - } - - private: - Fd::Type type_; - HANDLE handle_ = INVALID_HANDLE_VALUE; - SOCKET socket_ = INVALID_SOCKET; - - int socket_family_ = 0; - - bool async_mode_ = false; - - ObserverBase *observer_ = nullptr; - Fd::Flags flags_ = Fd::Flag::Write; - Status pending_error_; - Fd::Flags internal_flags_ = Fd::Flag::Write | Fd::Flag::Read; - - HANDLE read_event_ = INVALID_HANDLE_VALUE; // used by WineventPoll - bool async_read_flag_ = false; // do we have pending read? - OVERLAPPED read_overlapped_; - ChainBufferWriter input_writer_; - ChainBufferReader input_reader_ = input_writer_.extract_reader(); - - bool connected_ = false; - - std::vector<Fd> accepted_; - SOCKET accept_socket_ = INVALID_SOCKET; - static constexpr size_t MAX_ADDR_SIZE = sizeof(sockaddr_in6) + 16; - char addr_buf_[MAX_ADDR_SIZE * 2]; - - HANDLE write_event_ = INVALID_HANDLE_VALUE; // used by WineventPoll - bool async_write_flag_ = false; // do we have pending write? - OVERLAPPED write_overlapped_; - ChainBufferWriter output_writer_; - ChainBufferReader output_reader_ = output_writer_.extract_reader(); - - void init() { - if (async_mode_) { - if (type_ != Fd::Type::EventFd) { - write_event_ = CreateEventW(nullptr, true, false, nullptr); - } - read_event_ = CreateEventW(nullptr, true, false, nullptr); - loop(); - } - } - - Fd::Type type() const { - return type_; - } - - void on_error(Status error, Fd::Flag flag) { - VLOG(fd) << tag("fd", get_io_handle()) << error; - pending_error_ = std::move(error); - internal_flags_ &= ~flag; - update_flags_notify(Fd::Flag::Error); - } - void on_eof() { - internal_flags_ &= ~Fd::Flag::Read; - update_flags_notify(Fd::Flag::Close); - } - - void on_read_ready() { - async_read_flag_ = false; - DWORD bytes_read; - auto status = GetOverlappedResult(get_io_handle(), &read_overlapped_, &bytes_read, false); - if (status == 0) { - return on_error(OS_ERROR("ReadFile failed"), Fd::Flag::Read); - } - - VLOG(fd) << "Read " << tag("fd", get_io_handle()) << tag("size", bytes_read); - if (bytes_read == 0) { // eof - return on_eof(); - } - input_writer_.confirm_append(bytes_read); - input_reader_.sync_with_writer(); - update_flags_notify(Fd::Flag::Read); - } - void on_write_ready() { - async_write_flag_ = false; - DWORD bytes_written; - auto status = GetOverlappedResult(get_io_handle(), &write_overlapped_, &bytes_written, false); - if (status == 0) { - return on_error(OS_ERROR("WriteFile failed"), Fd::Flag::Write); - } - if (bytes_written != 0) { - VLOG(fd) << "Write " << tag("fd", get_io_handle()) << tag("size", bytes_written); - output_reader_.confirm_read(bytes_written); - update_flags_notify(Fd::Flag::Write); - } - } - - void on_accept_ready() { - async_read_flag_ = false; - DWORD bytes_read; - auto status = GetOverlappedResult(get_io_handle(), &read_overlapped_, &bytes_read, false); - if (status == 0) { - return on_error(OS_ERROR("AcceptEx failed"), Fd::Flag::Write); - } - accepted_.push_back(Fd::create_socket_fd(accept_socket_)); - accept_socket_ = INVALID_SOCKET; - update_flags_notify(Fd::Flag::Read); - } - - void on_connect_ready() { - async_read_flag_ = false; - DWORD bytes_read; - VLOG(fd) << "on_connect_ready"; - auto status = GetOverlappedResult(get_io_handle(), &read_overlapped_, &bytes_read, false); - if (status == 0) { - return on_error(OS_ERROR("ConnectEx failed"), Fd::Flag::Write); - } - connected_ = true; - VLOG(fd) << "connected = true"; - } - - void try_read_stdin() { - } - void try_start_read() { - auto dest = input_writer_.prepare_append(); - DWORD bytes_read; - std::memset(&read_overlapped_, 0, sizeof(read_overlapped_)); - read_overlapped_.hEvent = read_event_; - VLOG(fd) << "try_read.."; - auto status = - ReadFile(get_io_handle(), dest.data(), narrow_cast<DWORD>(dest.size()), &bytes_read, &read_overlapped_); - if (status != 0) { // ok - ResetEvent(read_event_); - VLOG(fd) << "Read " << tag("fd", get_io_handle()) << tag("size", bytes_read); - if (bytes_read == 0) { // eof - return on_eof(); - } - input_writer_.confirm_append(bytes_read); - input_reader_.sync_with_writer(); - update_flags_notify(Fd::Flag::Read); - return; - } - auto last_error = GetLastError(); - if (last_error == ERROR_IO_PENDING) { - async_read_flag_ = true; - return; - } - on_error(OS_ERROR("ReadFile failed"), Fd::Flag::Read); - } - - void try_start_write() { - auto dest = output_reader_.prepare_read(); - DWORD bytes_written; - std::memset(&write_overlapped_, 0, sizeof(write_overlapped_)); - write_overlapped_.hEvent = write_event_; - VLOG(fd) << "try_start_write"; - auto status = - WriteFile(get_io_handle(), dest.data(), narrow_cast<DWORD>(dest.size()), &bytes_written, &write_overlapped_); - if (status != 0) { // ok - VLOG(fd) << "Write " << tag("fd", get_io_handle()) << tag("size", bytes_written); - ResetEvent(write_event_); - output_reader_.confirm_read(bytes_written); - update_flags_notify(Fd::Flag::Write); - return; - } - auto last_error = GetLastError(); - if (last_error == ERROR_IO_PENDING) { - VLOG(fd) << "try_start_write: ERROR_IO_PENDING"; - async_write_flag_ = true; - return; - } - CHECK(WaitForSingleObject(write_event_, 0) != WAIT_OBJECT_0); - on_error(OS_ERROR("WriteFile failed"), Fd::Flag::Write); - } - - void try_start_accept() { - if (async_read_flag_ == true) { - return; - } - accept_socket_ = socket(socket_family_, SOCK_STREAM, 0); - DWORD bytes_read; - std::memset(&read_overlapped_, 0, sizeof(read_overlapped_)); - read_overlapped_.hEvent = read_event_; - auto status = - AcceptEx(socket_, accept_socket_, addr_buf_, 0, MAX_ADDR_SIZE, MAX_ADDR_SIZE, &bytes_read, &read_overlapped_); - if (status != 0) { - ResetEvent(read_event_); - accepted_.push_back(Fd::create_socket_fd(accept_socket_)); - accept_socket_ = INVALID_SOCKET; - update_flags_notify(Fd::Flag::Read); - return; - } - - auto last_error = GetLastError(); - if (last_error == ERROR_IO_PENDING) { - async_read_flag_ = true; - return; - } - on_error(OS_SOCKET_ERROR("AcceptExFailed"), Fd::Flag::Read); - } - - void loop() { - CHECK(async_mode_); - - if (type_ == Fd::Type::EventFd) { - return; - } - if (type_ == Fd::Type::ServerSocketFd) { - while (async_read_flag_ == false && (internal_flags_ & Fd::Flag::Read) != 0) { - // read always - try_start_accept(); - } - return; - } - if (!connected_) { - return; - } - while (async_read_flag_ == false && (internal_flags_ & Fd::Flag::Read) != 0) { - // read always - try_start_read(); - } - VLOG(fd) << (async_write_flag_ == false) << " " << output_reader_.size() << " " - << ((internal_flags_ & Fd::Flag::Write) != 0); - while (async_write_flag_ == false && output_reader_.size() && (internal_flags_ & Fd::Flag::Write) != 0) { - // write if we have data to write - try_start_write(); - } - } -}; - -Fd::Fd() = default; - -Fd::Fd(Fd &&other) = default; - -Fd &Fd::operator=(Fd &&other) = default; - -Fd::~Fd() = default; - -Fd Fd::create_file_fd(HANDLE handle) { - return Fd(Fd::Type::FileFd, Fd::Mode::Owner, handle); -} - -Fd Fd::create_socket_fd(SOCKET sock) { - return Fd(Fd::Type::SocketFd, Fd::Mode::Owner, sock, AF_UNSPEC); -} - -Fd Fd::create_server_socket_fd(SOCKET sock, int socket_family) { - return Fd(Fd::Type::ServerSocketFd, Fd::Mode::Owner, sock, socket_family); -} - -Fd Fd::create_event_fd() { - return Fd(Fd::Type::EventFd, Fd::Mode::Owner, INVALID_HANDLE_VALUE); -} - -const Fd &Fd::get_fd() const { - return *this; -} - -Fd &Fd::get_fd() { - return *this; -} - -Result<size_t> Fd::read(MutableSlice slice) { - return impl_->read(slice); -} - -Result<size_t> Fd::write(Slice slice) { - CHECK(!empty()); - return impl_->write(slice); -} - -bool Fd::empty() const { - return !impl_; -} - -void Fd::close() { - impl_.reset(); -} - -Result<Fd> Fd::accept() { - return impl_->accept(); -} -void Fd::connect(const IPAddress &addr) { - return impl_->connect(addr); -} - -Fd Fd::clone() const { - return Fd(impl_); -} - -uint64 Fd::get_key() const { - return reinterpret_cast<uint64>(impl_.get()); -} - -void Fd::set_observer(ObserverBase *observer) { - return impl_->set_observer(observer); -} -ObserverBase *Fd::get_observer() const { - return impl_->get_observer(); -} - -Fd::Flags Fd::get_flags() const { - return impl_->get_flags(); -} -void Fd::update_flags(Flags flags) { - impl_->update_flags(flags); -} - -void Fd::on_read_event() { - impl_->on_read_event(); -} -void Fd::on_write_event() { - impl_->on_write_event(); -} - -bool Fd::has_pending_error() const { - return impl_->has_pending_error(); -} -Status Fd::get_pending_error() { - return impl_->get_pending_error(); -} - -HANDLE Fd::get_read_event() { - return impl_->get_read_event(); -} -HANDLE Fd::get_write_event() { - return impl_->get_write_event(); -} - -SOCKET Fd::get_native_socket() const { - return impl_->get_native_socket(); -} - -HANDLE Fd::get_io_handle() const { - return impl_->get_io_handle(); -} - -#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) -Fd &Fd::Stderr() { - static auto handle = GetStdHandle(STD_ERROR_HANDLE); - LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to get stderr"; - static auto fd = Fd(Fd::Type::FileFd, Fd::Mode::Reference, handle); - return fd; -} -Fd &Fd::Stdin() { - static auto handle = GetStdHandle(STD_INPUT_HANDLE); - LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to get stdin"; - static auto fd = Fd(Fd::Type::FileFd, Fd::Mode::Reference, handle); - return fd; -} -Fd &Fd::Stdout() { - static auto handle = GetStdHandle(STD_OUTPUT_HANDLE); - LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to get stdout"; - static auto fd = Fd(Fd::Type::FileFd, Fd::Mode::Reference, handle); - return fd; -} -#else -Fd &Fd::Stderr() { - static Fd result; - result = Fd(); - return result; -} -Fd &Fd::Stdin() { - static Fd result; - result = Fd(); - return result; -} -Fd &Fd::Stdout() { - static Fd result; - result = Fd(); - return result; -} -#endif - -Status Fd::duplicate(const Fd &from, Fd &to) { - return Status::Error("Not supported"); -} - -Status Fd::set_is_blocking(bool is_blocking) { - return detail::set_native_socket_is_blocking(get_native_socket(), is_blocking); -} - -Fd::Fd(Type type, Mode mode, HANDLE handle) : mode_(mode), impl_(std::make_shared<FdImpl>(type, handle)) { -} - -Fd::Fd(Type type, Mode mode, SOCKET sock, int socket_family) - : mode_(mode), impl_(std::make_shared<FdImpl>(type, sock, socket_family)) { -} - -Fd::Fd(std::shared_ptr<FdImpl> impl) : mode_(Mode::Reference), impl_(std::move(impl)) { -} - -void Fd::acquire() { - return impl_->acquire(); -} - -void Fd::release() { - return impl_->release(); -} - -class InitWSA { - public: - InitWSA() { - /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ - WORD wVersionRequested = MAKEWORD(2, 2); - WSADATA wsaData; - if (WSAStartup(wVersionRequested, &wsaData) != 0) { - auto error = OS_SOCKET_ERROR("Failed to init WSA"); - LOG(FATAL) << error; - } - } -}; - -static InitWSA init_wsa; - -#endif - -namespace detail { -#if TD_PORT_POSIX -Status set_native_socket_is_blocking(int fd, bool is_blocking) { - if (fcntl(fd, F_SETFL, is_blocking ? 0 : O_NONBLOCK) == -1) { -#elif TD_PORT_WINDOWS -Status set_native_socket_is_blocking(SOCKET fd, bool is_blocking) { - u_long mode = is_blocking; - if (ioctlsocket(fd, FIONBIO, &mode) != 0) { -#endif - return OS_SOCKET_ERROR("Failed to change socket flags"); - } - return Status::OK(); -} -} // namespace detail - -} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.h deleted file mode 100644 index 5ce9b6cedc..0000000000 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Fd.h +++ /dev/null @@ -1,226 +0,0 @@ -// -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -#pragma once - -#include "td/utils/port/config.h" - -#include "td/utils/common.h" -#include "td/utils/Slice.h" -#include "td/utils/Status.h" - -#if TD_PORT_WINDOWS -#include "td/utils/port/IPAddress.h" - -#include <memory> -#endif - -#if TD_PORT_POSIX -#include <errno.h> - -#include <atomic> -#include <type_traits> -#endif - -namespace td { - -class ObserverBase; - -#if TD_PORT_WINDOWS -namespace detail { -class EventFdWindows; -} // namespace detail -#endif - -class Fd { - public: - // TODO: Close may be not enough - // Sometimes descriptor is half-closed. - - enum Flag : int32 { - Write = 0x001, - Read = 0x002, - Close = 0x004, - Error = 0x008, - All = Write | Read | Close | Error, - None = 0 - }; - using Flags = int32; - enum class Mode { Reference, Owner }; - - Fd(); - Fd(const Fd &) = delete; - Fd &operator=(const Fd &) = delete; - Fd(Fd &&other); - Fd &operator=(Fd &&other); - ~Fd(); - -#if TD_PORT_POSIX - Fd(int fd, Mode mode); -#endif -#if TD_PORT_WINDOWS - static Fd create_file_fd(HANDLE handle); - - static Fd create_socket_fd(SOCKET sock); - - static Fd create_server_socket_fd(SOCKET sock, int socket_family); - - static Fd create_event_fd(); -#endif - - Fd clone() const; - - static Fd &Stderr(); - static Fd &Stdout(); - static Fd &Stdin(); - - static Status duplicate(const Fd &from, Fd &to); - - bool empty() const; - - const Fd &get_fd() const; - Fd &get_fd(); - - void set_observer(ObserverBase *observer); - ObserverBase *get_observer() const; - - void close(); - - void update_flags(Flags flags); - - Flags get_flags() const; - - bool has_pending_error() const; - Status get_pending_error() TD_WARN_UNUSED_RESULT; - - Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT; - Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT; - - Status set_is_blocking(bool is_blocking); - -#if TD_PORT_POSIX - void update_flags_notify(Flags flags); - void clear_flags(Flags flags); - - Result<size_t> write_unsafe(Slice slice) TD_WARN_UNUSED_RESULT; - - int get_native_fd() const; - int move_as_native_fd(); -#endif - -#if TD_PORT_WINDOWS - Result<Fd> accept() TD_WARN_UNUSED_RESULT; - void connect(const IPAddress &addr); - - uint64 get_key() const; - - HANDLE get_read_event(); - HANDLE get_write_event(); - void on_read_event(); - void on_write_event(); - - SOCKET get_native_socket() const; - HANDLE get_io_handle() const; -#endif - - private: - Mode mode_ = Mode::Owner; - -#if TD_PORT_POSIX - struct Info { - std::atomic<int> refcnt; - int32 flags; - ObserverBase *observer; - }; - struct InfoSet { - InfoSet(); - Info &get_info(int32 id); - - private: - static constexpr int MAX_FD = 1 << 18; - Info fd_array_[MAX_FD]; - }; - static InfoSet fd_info_set_; - - static Fd stderr_; - static Fd stdout_; - static Fd stdin_; - - void update_flags_inner(int32 new_flags, bool notify_flag); - Info *get_info(); - const Info *get_info() const; - void clear_info(); - - void close_ref(); - void close_own(); - - int fd_ = -1; -#endif -#if TD_PORT_WINDOWS - class FdImpl; - - enum class Type { Empty, EventFd, FileFd, StdinFileFd, SocketFd, ServerSocketFd }; - - Fd(Type type, Mode mode, HANDLE handle); - Fd(Type type, Mode mode, SOCKET sock, int socket_family); - explicit Fd(std::shared_ptr<FdImpl> impl); - - friend class detail::EventFdWindows; // for release and acquire - - void acquire(); - void release(); - - std::shared_ptr<FdImpl> impl_; -#endif -}; - -#if TD_PORT_POSIX -template <class F> -auto skip_eintr(F &&f) { - decltype(f()) res; - static_assert(std::is_integral<decltype(res)>::value, "integral type expected"); - do { - errno = 0; // just in case - res = f(); - } while (res < 0 && errno == EINTR); - return res; -} -template <class F> -auto skip_eintr_cstr(F &&f) { - char *res; - do { - errno = 0; // just in case - res = f(); - } while (res == nullptr && errno == EINTR); - return res; -} -#endif - -template <class FdT> -bool can_read(const FdT &fd) { - return (fd.get_flags() & Fd::Read) != 0; -} - -template <class FdT> -bool can_write(const FdT &fd) { - return (fd.get_flags() & Fd::Write) != 0; -} - -template <class FdT> -bool can_close(const FdT &fd) { - return (fd.get_flags() & Fd::Close) != 0; -} - -namespace detail { -#if TD_PORT_POSIX -Status set_native_socket_is_blocking(int fd, bool is_blocking); -#endif -#if TD_PORT_WINDOWS -Status set_native_socket_is_blocking(SOCKET fd, bool is_blocking); -#endif -} // namespace detail - -} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp index 17a2727f64..6300f9f85f 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,25 +7,42 @@ #include "td/utils/port/FileFd.h" #if TD_PORT_WINDOWS -#include "td/utils/misc.h" // for narrow_cast - +#include "td/utils/port/FromApp.h" #include "td/utils/port/Stat.h" #include "td/utils/port/wstring_convert.h" #endif +#include "td/utils/common.h" +#include "td/utils/ExitGuard.h" +#include "td/utils/FlatHashSet.h" #include "td/utils/logging.h" #include "td/utils/misc.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/detail/skip_eintr.h" +#include "td/utils/port/PollFlags.h" #include "td/utils/port/sleep.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/StringBuilder.h" #include <cstring> +#include <mutex> +#include <utility> #if TD_PORT_POSIX +#include <cerrno> + #include <fcntl.h> #include <sys/file.h> #include <sys/types.h> #include <sys/uio.h> #include <unistd.h> +#else +#include <limits> +#endif + +#if TD_PORT_WINDOWS && defined(WIN32_LEAN_AND_MEAN) +#include <winioctl.h> #endif namespace td { @@ -38,8 +55,8 @@ struct PrintFlags { StringBuilder &operator<<(StringBuilder &sb, const PrintFlags &print_flags) { auto flags = print_flags.flags; - if (flags & - ~(FileFd::Write | FileFd::Read | FileFd::Truncate | FileFd::Create | FileFd::Append | FileFd::CreateNew)) { + if (flags & ~(FileFd::Write | FileFd::Read | FileFd::Truncate | FileFd::Create | FileFd::Append | FileFd::CreateNew | + FileFd::Direct | FileFd::WinStat)) { return sb << "opened with invalid flags " << flags; } @@ -72,21 +89,34 @@ StringBuilder &operator<<(StringBuilder &sb, const PrintFlags &print_flags) { if (flags & FileFd::Truncate) { sb << " with truncation"; } + if (flags & FileFd::Direct) { + sb << " for direct io"; + } + if (flags & FileFd::WinStat) { + sb << " for stat"; + } return sb; } } // namespace -const Fd &FileFd::get_fd() const { - return fd_; -} +namespace detail { +class FileFdImpl { + public: + PollableFdInfo info_; +}; +} // namespace detail + +FileFd::FileFd() = default; +FileFd::FileFd(FileFd &&) noexcept = default; +FileFd &FileFd::operator=(FileFd &&) noexcept = default; +FileFd::~FileFd() = default; -Fd &FileFd::get_fd() { - return fd_; +FileFd::FileFd(unique_ptr<detail::FileFdImpl> impl) : impl_(std::move(impl)) { } Result<FileFd> FileFd::open(CSlice filepath, int32 flags, int32 mode) { - if (flags & ~(Write | Read | Truncate | Create | Append | CreateNew)) { + if (flags & ~(Write | Read | Truncate | Create | Append | CreateNew | Direct | WinStat)) { return Status::Error(PSLICE() << "File \"" << filepath << "\" has failed to be " << PrintFlags{flags}); } @@ -121,13 +151,17 @@ Result<FileFd> FileFd::open(CSlice filepath, int32 flags, int32 mode) { native_flags |= O_APPEND; } - int native_fd = skip_eintr([&] { return ::open(filepath.c_str(), native_flags, static_cast<mode_t>(mode)); }); +#if TD_LINUX + if (flags & Direct) { + native_flags |= O_DIRECT; + } +#endif + + int native_fd = detail::skip_eintr([&] { return ::open(filepath.c_str(), native_flags, static_cast<mode_t>(mode)); }); if (native_fd < 0) { return OS_ERROR(PSLICE() << "File \"" << filepath << "\" can't be " << PrintFlags{flags}); } - - FileFd result; - result.fd_ = Fd(native_fd, Fd::Mode::Owner); + return from_native_fd(NativeFd(native_fd)); #elif TD_PORT_WINDOWS // TODO: support modes auto r_filepath = to_wstring(filepath); @@ -165,165 +199,243 @@ Result<FileFd> FileFd::open(CSlice filepath, int32 flags, int32 mode) { } } + DWORD native_flags = 0; + if (flags & Direct) { + native_flags |= FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING; + } + if (flags & WinStat) { + native_flags |= FILE_FLAG_BACKUP_SEMANTICS; + } #if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) - auto handle = CreateFile(w_filepath.c_str(), desired_access, share_mode, nullptr, creation_disposition, 0, nullptr); + auto handle = + CreateFile(w_filepath.c_str(), desired_access, share_mode, nullptr, creation_disposition, native_flags, nullptr); #else - auto handle = CreateFile2(w_filepath.c_str(), desired_access, share_mode, creation_disposition, nullptr); + CREATEFILE2_EXTENDED_PARAMETERS extended_parameters; + std::memset(&extended_parameters, 0, sizeof(extended_parameters)); + extended_parameters.dwSize = sizeof(extended_parameters); + extended_parameters.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; + extended_parameters.dwFileFlags = native_flags; + auto handle = td::CreateFile2FromAppW(w_filepath.c_str(), desired_access, share_mode, creation_disposition, + &extended_parameters); #endif if (handle == INVALID_HANDLE_VALUE) { return OS_ERROR(PSLICE() << "File \"" << filepath << "\" can't be " << PrintFlags{flags}); } +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) + if (flags & Write) { + DWORD bytes_returned = 0; + DeviceIoControl(handle, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytes_returned, nullptr); + } +#endif + auto native_fd = NativeFd(handle); if (flags & Append) { LARGE_INTEGER offset; offset.QuadPart = 0; auto set_pointer_res = SetFilePointerEx(handle, offset, nullptr, FILE_END); if (!set_pointer_res) { - auto res = OS_ERROR(PSLICE() << "Failed to seek to the end of file \"" << filepath << "\""); - CloseHandle(handle); - return res; + return OS_ERROR(PSLICE() << "Failed to seek to the end of file \"" << filepath << "\""); } } - FileFd result; - result.fd_ = Fd::create_file_fd(handle); + return from_native_fd(std::move(native_fd)); #endif - result.fd_.update_flags(Fd::Flag::Write); - return std::move(result); +} + +FileFd FileFd::from_native_fd(NativeFd native_fd) { + auto impl = make_unique<detail::FileFdImpl>(); + impl->info_.set_native_fd(std::move(native_fd)); + impl->info_.add_flags(PollFlags::Write()); + return FileFd(std::move(impl)); } Result<size_t> FileFd::write(Slice slice) { + auto native_fd = get_native_fd().fd(); #if TD_PORT_POSIX - CHECK(!fd_.empty()); - int native_fd = get_native_fd(); - auto write_res = skip_eintr([&] { return ::write(native_fd, slice.begin(), slice.size()); }); - if (write_res >= 0) { - return narrow_cast<size_t>(write_res); - } - - auto write_errno = errno; - auto error = Status::PosixError(write_errno, PSLICE() << "Write to [fd = " << native_fd << "] has failed"); - if (write_errno != EAGAIN -#if EAGAIN != EWOULDBLOCK - && write_errno != EWOULDBLOCK -#endif - && write_errno != EIO) { - LOG(ERROR) << error; - } - return std::move(error); + auto bytes_written = detail::skip_eintr([&] { return ::write(native_fd, slice.begin(), slice.size()); }); + bool success = bytes_written >= 0; #elif TD_PORT_WINDOWS - return fd_.write(slice); + DWORD bytes_written = 0; + BOOL success = WriteFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_written, nullptr); #endif + if (success) { + auto result = narrow_cast<size_t>(bytes_written); + CHECK(result <= slice.size()); + return result; + } + return OS_ERROR(PSLICE() << "Write to " << get_native_fd() << " has failed"); } -Result<size_t> FileFd::read(MutableSlice slice) { +Result<size_t> FileFd::writev(Span<IoSlice> slices) { #if TD_PORT_POSIX - CHECK(!fd_.empty()); - int native_fd = get_native_fd(); - auto read_res = skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); }); - auto read_errno = errno; - - if (read_res >= 0) { - if (narrow_cast<size_t>(read_res) < slice.size()) { - fd_.clear_flags(Read); + auto native_fd = get_native_fd().fd(); + TRY_RESULT(slices_size, narrow_cast_safe<int>(slices.size())); + auto bytes_written = detail::skip_eintr([&] { return ::writev(native_fd, slices.begin(), slices_size); }); + bool success = bytes_written >= 0; + if (success) { + auto result = narrow_cast<size_t>(bytes_written); + auto left = result; + for (const auto &slice : slices) { + if (left <= slice.iov_len) { + return result; + } + left -= slice.iov_len; + } + UNREACHABLE(); + } + return OS_ERROR(PSLICE() << "Writev to " << get_native_fd() << " has failed"); +#else + size_t res = 0; + for (const auto &slice : slices) { + if (slice.size() > std::numeric_limits<size_t>::max() - res) { + break; + } + TRY_RESULT(size, write(slice)); + res += size; + if (size != slice.size()) { + CHECK(size < slice.size()); + break; } - return static_cast<size_t>(read_res); } + return res; +#endif +} - auto error = Status::PosixError(read_errno, PSLICE() << "Read from [fd = " << native_fd << "] has failed"); - if (read_errno != EAGAIN +Result<size_t> FileFd::read(MutableSlice slice) { + auto native_fd = get_native_fd().fd(); +#if TD_PORT_POSIX + auto bytes_read = detail::skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); }); + bool success = bytes_read >= 0; + if (!success) { + auto read_errno = errno; + if (read_errno == EAGAIN #if EAGAIN != EWOULDBLOCK - && read_errno != EWOULDBLOCK + || read_errno == EWOULDBLOCK #endif - && read_errno != EIO) { - LOG(ERROR) << error; + ) { + success = true; + bytes_read = 0; + } } - return std::move(error); + bool is_eof = success && narrow_cast<size_t>(bytes_read) < slice.size(); #elif TD_PORT_WINDOWS - return fd_.read(slice); + DWORD bytes_read = 0; + BOOL success = ReadFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, nullptr); + bool is_eof = bytes_read == 0; #endif + if (success) { + if (is_eof) { + get_poll_info().clear_flags(PollFlags::Read()); + } + auto result = narrow_cast<size_t>(bytes_read); + CHECK(result <= slice.size()); + return result; + } + return OS_ERROR(PSLICE() << "Read from " << get_native_fd() << " has failed"); } Result<size_t> FileFd::pwrite(Slice slice, int64 offset) { if (offset < 0) { return Status::Error("Offset must be non-negative"); } + auto native_fd = get_native_fd().fd(); #if TD_PORT_POSIX TRY_RESULT(offset_off_t, narrow_cast_safe<off_t>(offset)); - CHECK(!fd_.empty()); - int native_fd = get_native_fd(); - auto pwrite_res = skip_eintr([&] { return ::pwrite(native_fd, slice.begin(), slice.size(), offset_off_t); }); - if (pwrite_res >= 0) { - return narrow_cast<size_t>(pwrite_res); - } - - auto pwrite_errno = errno; - auto error = Status::PosixError( - pwrite_errno, PSLICE() << "Pwrite to [fd = " << native_fd << "] at [offset = " << offset << "] has failed"); - if (pwrite_errno != EAGAIN -#if EAGAIN != EWOULDBLOCK - && pwrite_errno != EWOULDBLOCK -#endif - && pwrite_errno != EIO) { - LOG(ERROR) << error; - } - return std::move(error); + auto bytes_written = + detail::skip_eintr([&] { return ::pwrite(native_fd, slice.begin(), slice.size(), offset_off_t); }); + bool success = bytes_written >= 0; #elif TD_PORT_WINDOWS DWORD bytes_written = 0; OVERLAPPED overlapped; std::memset(&overlapped, 0, sizeof(overlapped)); overlapped.Offset = static_cast<DWORD>(offset); overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32); - auto res = - WriteFile(fd_.get_io_handle(), slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_written, &overlapped); - if (!res) { - return OS_ERROR("Failed to pwrite"); - } - return bytes_written; + BOOL success = WriteFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_written, &overlapped); #endif + if (success) { + auto result = narrow_cast<size_t>(bytes_written); + CHECK(result <= slice.size()); + return result; + } + return OS_ERROR(PSLICE() << "Pwrite to " << get_native_fd() << " at offset " << offset << " has failed"); } -Result<size_t> FileFd::pread(MutableSlice slice, int64 offset) { +Result<size_t> FileFd::pread(MutableSlice slice, int64 offset) const { if (offset < 0) { return Status::Error("Offset must be non-negative"); } + auto native_fd = get_native_fd().fd(); #if TD_PORT_POSIX TRY_RESULT(offset_off_t, narrow_cast_safe<off_t>(offset)); - CHECK(!fd_.empty()); - int native_fd = get_native_fd(); - auto pread_res = skip_eintr([&] { return ::pread(native_fd, slice.begin(), slice.size(), offset_off_t); }); - if (pread_res >= 0) { - return narrow_cast<size_t>(pread_res); - } - - auto pread_errno = errno; - auto error = Status::PosixError( - pread_errno, PSLICE() << "Pread from [fd = " << native_fd << "] at [offset = " << offset << "] has failed"); - if (pread_errno != EAGAIN -#if EAGAIN != EWOULDBLOCK - && pread_errno != EWOULDBLOCK -#endif - && pread_errno != EIO) { - LOG(ERROR) << error; - } - return std::move(error); + auto bytes_read = detail::skip_eintr([&] { return ::pread(native_fd, slice.begin(), slice.size(), offset_off_t); }); + bool success = bytes_read >= 0; #elif TD_PORT_WINDOWS DWORD bytes_read = 0; OVERLAPPED overlapped; std::memset(&overlapped, 0, sizeof(overlapped)); overlapped.Offset = static_cast<DWORD>(offset); overlapped.OffsetHigh = static_cast<DWORD>(offset >> 32); - auto res = ReadFile(fd_.get_io_handle(), slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, &overlapped); - if (!res) { - return OS_ERROR("Failed to pread"); - } - return bytes_read; + BOOL success = ReadFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, &overlapped); #endif + if (success) { + auto result = narrow_cast<size_t>(bytes_read); + CHECK(result <= slice.size()); + return result; + } + return OS_ERROR(PSLICE() << "Pread from " << get_native_fd() << " at offset " << offset << " has failed"); +} + +static std::mutex in_process_lock_mutex; +static FlatHashSet<string> locked_files; +static ExitGuard exit_guard; + +static Status create_local_lock(const string &path, int32 &max_tries) { + while (true) { + { // mutex lock scope + std::lock_guard<std::mutex> lock(in_process_lock_mutex); + if (!path.empty() && locked_files.count(path) == 0) { + VLOG(fd) << "Lock file \"" << path << '"'; + locked_files.insert(path); + return Status::OK(); + } + } + + if (--max_tries <= 0) { + return Status::Error( + 0, PSLICE() << "Can't lock file \"" << path << "\", because it is already in use by current program"); + } + + usleep_for(100000); + } } -Status FileFd::lock(FileFd::LockFlags flags, int32 max_tries) { +Status FileFd::lock(LockFlags flags, const string &path, int32 max_tries) { if (max_tries <= 0) { - return Status::Error(0, "Can't lock file: wrong max_tries"); + return Status::Error("Can't lock file: wrong max_tries"); + } + + bool need_local_unlock = false; + if (!path.empty()) { + if (flags == LockFlags::Unlock) { + need_local_unlock = true; + } else if (flags == LockFlags::Read) { + LOG(FATAL) << "Local locking in Read mode is unsupported"; + } else { + CHECK(flags == LockFlags::Write); + VLOG(fd) << "Trying to lock file \"" << path << '"'; + TRY_STATUS(create_local_lock(path, max_tries)); + need_local_unlock = true; + } } + SCOPE_EXIT { + if (need_local_unlock) { + remove_local_lock(path); + } + }; +#if TD_PORT_POSIX + auto native_fd = get_native_fd().fd(); +#elif TD_PORT_WINDOWS + auto native_fd = get_native_fd().fd(); +#endif while (true) { #if TD_PORT_POSIX struct flock lock; @@ -344,95 +456,148 @@ Status FileFd::lock(FileFd::LockFlags flags, int32 max_tries) { }()); lock.l_whence = SEEK_SET; - if (fcntl(get_native_fd(), F_SETLK, &lock) == -1) { - if (errno == EAGAIN && --max_tries > 0) { + if (fcntl(native_fd, F_SETLK, &lock) == -1) { + if (errno == EAGAIN) { #elif TD_PORT_WINDOWS OVERLAPPED overlapped; std::memset(&overlapped, 0, sizeof(overlapped)); BOOL result; if (flags == LockFlags::Unlock) { - result = UnlockFileEx(fd_.get_io_handle(), 0, MAXDWORD, MAXDWORD, &overlapped); + result = UnlockFileEx(native_fd, 0, MAXDWORD, MAXDWORD, &overlapped); } else { DWORD dw_flags = LOCKFILE_FAIL_IMMEDIATELY; if (flags == LockFlags::Write) { dw_flags |= LOCKFILE_EXCLUSIVE_LOCK; } - result = LockFileEx(fd_.get_io_handle(), dw_flags, 0, MAXDWORD, MAXDWORD, &overlapped); + result = LockFileEx(native_fd, dw_flags, 0, MAXDWORD, MAXDWORD, &overlapped); } if (!result) { - if (GetLastError() == ERROR_LOCK_VIOLATION && --max_tries > 0) { + if (GetLastError() == ERROR_LOCK_VIOLATION) { #endif - usleep_for(100000); - continue; + if (--max_tries > 0) { + usleep_for(100000); + continue; + } + + return OS_ERROR(PSLICE() << "Can't lock file \"" << path + << "\", because it is already in use; check for another program instance running"); } return OS_ERROR("Can't lock file"); } - return Status::OK(); + + break; } -} -void FileFd::close() { - fd_.close(); + if (flags == LockFlags::Write) { + need_local_unlock = false; + } + return Status::OK(); } -bool FileFd::empty() const { - return fd_.empty(); +void FileFd::remove_local_lock(const string &path) { + if (path.empty() || ExitGuard::is_exited()) { + return; + } + VLOG(fd) << "Unlock file \"" << path << '"'; + std::unique_lock<std::mutex> lock(in_process_lock_mutex); + auto erased_count = locked_files.erase(path); + CHECK(erased_count > 0 || path.empty() || ExitGuard::is_exited()); } -#if TD_PORT_POSIX -int FileFd::get_native_fd() const { - return fd_.get_native_fd(); +void FileFd::close() { + impl_.reset(); } -#endif -int32 FileFd::get_flags() const { - return fd_.get_flags(); +bool FileFd::empty() const { + return !impl_; } -void FileFd::update_flags(Fd::Flags mask) { - fd_.update_flags(mask); +const NativeFd &FileFd::get_native_fd() const { + return get_poll_info().native_fd(); } -int64 FileFd::get_size() { - return stat().size_; +NativeFd FileFd::move_as_native_fd() { + auto res = get_poll_info().move_as_native_fd(); + impl_.reset(); + return res; } #if TD_PORT_WINDOWS -static uint64 filetime_to_unix_time_nsec(LONGLONG filetime) { +namespace { + +uint64 filetime_to_unix_time_nsec(LONGLONG filetime) { const auto FILETIME_UNIX_TIME_DIFF = 116444736000000000ll; return static_cast<uint64>((filetime - FILETIME_UNIX_TIME_DIFF) * 100); } + +struct FileSize { + int64 size_; + int64 real_size_; +}; + +Result<FileSize> get_file_size(const FileFd &file_fd) { + FILE_STANDARD_INFO standard_info; + if (!GetFileInformationByHandleEx(file_fd.get_native_fd().fd(), FileStandardInfo, &standard_info, + sizeof(standard_info))) { + return OS_ERROR("Get FileStandardInfo failed"); + } + FileSize res; + res.size_ = standard_info.EndOfFile.QuadPart; + res.real_size_ = standard_info.AllocationSize.QuadPart; + + if (res.size_ > 0 && res.real_size_ <= 0) { // just in case + LOG(ERROR) << "Fix real file size from " << res.real_size_ << " to " << res.size_; + res.real_size_ = res.size_; + } + + return res; +} + +} // namespace +#endif + +Result<int64> FileFd::get_size() const { +#if TD_PORT_POSIX + TRY_RESULT(s, stat()); +#elif TD_PORT_WINDOWS + TRY_RESULT(s, get_file_size(*this)); #endif + return s.size_; +} -Stat FileFd::stat() { +Result<int64> FileFd::get_real_size() const { +#if TD_PORT_POSIX + TRY_RESULT(s, stat()); +#elif TD_PORT_WINDOWS + TRY_RESULT(s, get_file_size(*this)); +#endif + return s.real_size_; +} + +Result<Stat> FileFd::stat() const { CHECK(!empty()); #if TD_PORT_POSIX - return detail::fstat(get_native_fd()); + return detail::fstat(get_native_fd().fd()); #elif TD_PORT_WINDOWS Stat res; FILE_BASIC_INFO basic_info; - auto status = GetFileInformationByHandleEx(fd_.get_io_handle(), FileBasicInfo, &basic_info, sizeof(basic_info)); + auto status = GetFileInformationByHandleEx(get_native_fd().fd(), FileBasicInfo, &basic_info, sizeof(basic_info)); if (!status) { - auto error = OS_ERROR("Stat failed"); - LOG(FATAL) << error; + return OS_ERROR("Get FileBasicInfo failed"); } res.atime_nsec_ = filetime_to_unix_time_nsec(basic_info.LastAccessTime.QuadPart); res.mtime_nsec_ = filetime_to_unix_time_nsec(basic_info.LastWriteTime.QuadPart); res.is_dir_ = (basic_info.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; - res.is_reg_ = true; + res.is_reg_ = !res.is_dir_; // TODO this is still wrong - FILE_STANDARD_INFO standard_info; - status = GetFileInformationByHandleEx(fd_.get_io_handle(), FileStandardInfo, &standard_info, sizeof(standard_info)); - if (!status) { - auto error = OS_ERROR("Stat failed"); - LOG(FATAL) << error; - } - res.size_ = standard_info.EndOfFile.QuadPart; + TRY_RESULT(file_size, get_file_size(*this)); + res.size_ = file_size.size_; + res.real_size_ = file_size.real_size_; return res; #endif @@ -441,9 +606,13 @@ Stat FileFd::stat() { Status FileFd::sync() { CHECK(!empty()); #if TD_PORT_POSIX - if (fsync(fd_.get_native_fd()) != 0) { +#if TD_DARWIN + if (detail::skip_eintr([&] { return fcntl(get_native_fd().fd(), F_FULLFSYNC); }) == -1) { +#else + if (detail::skip_eintr([&] { return fsync(get_native_fd().fd()); }) != 0) { +#endif #elif TD_PORT_WINDOWS - if (FlushFileBuffers(fd_.get_io_handle()) == 0) { + if (FlushFileBuffers(get_native_fd().fd()) == 0) { #endif return OS_ERROR("Sync failed"); } @@ -454,11 +623,11 @@ Status FileFd::seek(int64 position) { CHECK(!empty()); #if TD_PORT_POSIX TRY_RESULT(position_off_t, narrow_cast_safe<off_t>(position)); - if (skip_eintr([&] { return ::lseek(fd_.get_native_fd(), position_off_t, SEEK_SET); }) < 0) { + if (detail::skip_eintr([&] { return ::lseek(get_native_fd().fd(), position_off_t, SEEK_SET); }) < 0) { #elif TD_PORT_WINDOWS LARGE_INTEGER offset; offset.QuadPart = position; - if (SetFilePointerEx(fd_.get_io_handle(), offset, nullptr, FILE_BEGIN) == 0) { + if (SetFilePointerEx(get_native_fd().fd(), offset, nullptr, FILE_BEGIN) == 0) { #endif return OS_ERROR("Seek failed"); } @@ -469,13 +638,21 @@ Status FileFd::truncate_to_current_position(int64 current_position) { CHECK(!empty()); #if TD_PORT_POSIX TRY_RESULT(current_position_off_t, narrow_cast_safe<off_t>(current_position)); - if (skip_eintr([&] { return ::ftruncate(fd_.get_native_fd(), current_position_off_t); }) < 0) { + if (detail::skip_eintr([&] { return ::ftruncate(get_native_fd().fd(), current_position_off_t); }) < 0) { #elif TD_PORT_WINDOWS - if (SetEndOfFile(fd_.get_io_handle()) == 0) { + if (SetEndOfFile(get_native_fd().fd()) == 0) { #endif return OS_ERROR("Truncate failed"); } return Status::OK(); } +PollableFdInfo &FileFd::get_poll_info() { + CHECK(!empty()); + return impl_->info_; +} +const PollableFdInfo &FileFd::get_poll_info() const { + CHECK(!empty()); + return impl_->info_; +} } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h index bf7166c1de..3dc88da5cb 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FileFd.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -9,42 +9,57 @@ #include "td/utils/port/config.h" #include "td/utils/common.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/IoSlice.h" #include "td/utils/port/Stat.h" #include "td/utils/Slice.h" +#include "td/utils/Span.h" #include "td/utils/Status.h" namespace td { +namespace detail { +class FileFdImpl; +} // namespace detail class FileFd { public: - FileFd() = default; + FileFd(); + FileFd(FileFd &&) noexcept; + FileFd &operator=(FileFd &&) noexcept; + ~FileFd(); + FileFd(const FileFd &) = delete; + FileFd &operator=(const FileFd &) = delete; - enum Flags : int32 { Write = 1, Read = 2, Truncate = 4, Create = 8, Append = 16, CreateNew = 32 }; - - const Fd &get_fd() const; - Fd &get_fd(); + enum Flags : int32 { Write = 1, Read = 2, Truncate = 4, Create = 8, Append = 16, CreateNew = 32, Direct = 64 }; + enum PrivateFlags : int32 { WinStat = 128 }; static Result<FileFd> open(CSlice filepath, int32 flags, int32 mode = 0600) TD_WARN_UNUSED_RESULT; + static FileFd from_native_fd(NativeFd fd) TD_WARN_UNUSED_RESULT; Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT; + Result<size_t> writev(Span<IoSlice> slices) TD_WARN_UNUSED_RESULT; Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT; Result<size_t> pwrite(Slice slice, int64 offset) TD_WARN_UNUSED_RESULT; - Result<size_t> pread(MutableSlice slice, int64 offset) TD_WARN_UNUSED_RESULT; + Result<size_t> pread(MutableSlice slice, int64 offset) const TD_WARN_UNUSED_RESULT; enum class LockFlags { Write, Read, Unlock }; - Status lock(LockFlags flags, int32 max_tries = 1) TD_WARN_UNUSED_RESULT; + Status lock(LockFlags flags, const string &path, int32 max_tries) TD_WARN_UNUSED_RESULT; + static void remove_local_lock(const string &path); + + PollableFdInfo &get_poll_info(); + const PollableFdInfo &get_poll_info() const; void close(); + bool empty() const; - int32 get_flags() const; - void update_flags(Fd::Flags mask); + Result<int64> get_size() const; - int64 get_size(); + Result<int64> get_real_size() const; - Stat stat(); + Result<Stat> stat() const; Status sync() TD_WARN_UNUSED_RESULT; @@ -52,12 +67,13 @@ class FileFd { Status truncate_to_current_position(int64 current_position) TD_WARN_UNUSED_RESULT; -#if TD_PORT_POSIX - int get_native_fd() const; -#endif + const NativeFd &get_native_fd() const; + NativeFd move_as_native_fd(); private: - Fd fd_; + unique_ptr<detail::FileFdImpl> impl_; + + explicit FileFd(unique_ptr<detail::FileFdImpl> impl); }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FromApp.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FromApp.h new file mode 100644 index 0000000000..b33cbf070c --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/FromApp.h @@ -0,0 +1,100 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" + +#ifdef TD_PORT_WINDOWS + +namespace td { + +#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) +inline HMODULE GetKernelModule() { + static const auto kernel_module = []() -> HMODULE { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(VirtualQuery, &mbi, sizeof(MEMORY_BASIC_INFORMATION))) { + return reinterpret_cast<HMODULE>(mbi.AllocationBase); + } + return nullptr; + }(); + return kernel_module; +} + +inline HMODULE LoadLibrary(LPCTSTR lpFileName) { + using pLoadLibrary = HMODULE(WINAPI *)(_In_ LPCTSTR); + static const auto proc_load_library = + reinterpret_cast<pLoadLibrary>(GetProcAddress(GetKernelModule(), "LoadLibraryW")); + return proc_load_library(lpFileName); +} + +inline HMODULE GetFromAppModule() { + static const HMODULE from_app_module = LoadLibrary(L"api-ms-win-core-file-fromapp-l1-1-0.dll"); + return from_app_module; +} +#endif + +template <int num, class T> +T *get_from_app_function(const char *name, T *original_func) { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) + return original_func; +#else + static T *func = [name, original_func]() -> T * { + auto func_pointer = GetProcAddress(GetFromAppModule(), name); + if (func_pointer == nullptr) { + return original_func; + } + return reinterpret_cast<T *>(func_pointer); + }(); + return func; +#endif +} + +inline HANDLE CreateFile2FromAppW(_In_ LPCWSTR lpFileName, _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, + _In_ DWORD dwCreationDisposition, + _In_opt_ LPCREATEFILE2_EXTENDED_PARAMETERS pCreateExParams) { + auto func = get_from_app_function<0>("CreateFile2FromAppW", &CreateFile2); + return func(lpFileName, dwDesiredAccess, dwShareMode, dwCreationDisposition, pCreateExParams); +} + +inline BOOL CreateDirectoryFromAppW(_In_ LPCWSTR lpPathName, _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes) { + auto func = get_from_app_function<1>("CreateDirectoryFromAppW", &CreateDirectory); + return func(lpPathName, lpSecurityAttributes); +} + +inline BOOL RemoveDirectoryFromAppW(_In_ LPCWSTR lpPathName) { + auto func = get_from_app_function<2>("RemoveDirectoryFromAppW", &RemoveDirectory); + return func(lpPathName); +} + +inline BOOL DeleteFileFromAppW(_In_ LPCWSTR lpFileName) { + auto func = get_from_app_function<3>("DeleteFileFromAppW", &DeleteFile); + return func(lpFileName); +} + +inline BOOL MoveFileExFromAppW(_In_ LPCWSTR lpExistingFileName, _In_ LPCWSTR lpNewFileName, _In_ DWORD dwFlags) { + auto func = get_from_app_function<4>("MoveFileFromAppW", static_cast<BOOL(WINAPI *)(LPCWSTR, LPCWSTR)>(nullptr)); + if (func == nullptr || (dwFlags & ~MOVEFILE_REPLACE_EXISTING) != 0) { + // if can't find MoveFileFromAppW or have unsupported flags, call MoveFileEx directly + return MoveFileEx(lpExistingFileName, lpNewFileName, dwFlags); + } + if ((dwFlags & MOVEFILE_REPLACE_EXISTING) != 0) { + td::DeleteFileFromAppW(lpNewFileName); + } + return func(lpExistingFileName, lpNewFileName); +} + +inline HANDLE FindFirstFileExFromAppW(_In_ LPCWSTR lpFileName, _In_ FINDEX_INFO_LEVELS fInfoLevelId, + _Out_writes_bytes_(sizeof(WIN32_FIND_DATAW)) LPVOID lpFindFileData, + _In_ FINDEX_SEARCH_OPS fSearchOp, _Reserved_ LPVOID lpSearchFilter, + _In_ DWORD dwAdditionalFlags) { + auto func = get_from_app_function<5>("FindFirstFileExFromAppW", &FindFirstFileEx); + return func(lpFileName, fInfoLevelId, lpFindFileData, fSearchOp, lpSearchFilter, dwAdditionalFlags); +} + +} // namespace td + +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp index 2d3a3cdbc0..32c26403d5 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.cpp @@ -1,9 +1,11 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#define _WINSOCK_DEPRECATED_NO_WARNINGS // we need to use inet_addr instead of inet_pton + #include "td/utils/port/IPAddress.h" #include "td/utils/format.h" @@ -12,19 +14,185 @@ #include "td/utils/port/SocketFd.h" #include "td/utils/port/thread_local.h" #include "td/utils/ScopeGuard.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/utf8.h" -#if !TD_WINDOWS +#if TD_WINDOWS +#include "td/utils/port/wstring_convert.h" +#else #include <netdb.h> -#include <netinet/in.h> #include <netinet/tcp.h> -#include <sys/socket.h> #include <sys/types.h> #endif #include <cstring> +#include <limits> namespace td { +static bool is_ascii_host_char(char c) { + return static_cast<unsigned char>(c) <= 127; +} + +static bool is_ascii_host(Slice host) { + for (auto c : host) { + if (!is_ascii_host_char(c)) { + return false; + } + } + return true; +} + +#if !TD_WINDOWS +static void punycode(string &result, Slice part) { + vector<uint32> codes; + codes.reserve(utf8_length(part)); + uint32 processed = 0; + auto begin = part.ubegin(); + auto end = part.uend(); + while (begin != end) { + uint32 code; + begin = next_utf8_unsafe(begin, &code); + if (code <= 127u) { + result += to_lower(static_cast<char>(code)); + processed++; + } + codes.push_back(code); + } + + if (processed > 0) { + result += '-'; + } + + uint32 n = 127; + uint32 delta = 0; + int bias = -72; + bool is_first = true; + while (processed < codes.size()) { + // choose lowest not processed code + uint32 next_n = 0x110000; + for (auto code : codes) { + if (code > n && code < next_n) { + next_n = code; + } + } + delta += (next_n - n - 1) * (processed + 1); + + for (auto code : codes) { + if (code < next_n) { + delta++; + } + + if (code == next_n) { + // found next symbol, encode delta + auto left = static_cast<int>(delta); + while (true) { + bias += 36; + auto t = clamp(bias, 1, 26); + if (left < t) { + result += static_cast<char>(left + 'a'); + break; + } + + left -= t; + auto digit = t + left % (36 - t); + result += static_cast<char>(digit < 26 ? digit + 'a' : digit - 26 + '0'); + left /= 36 - t; + } + processed++; + + // update bias + if (is_first) { + delta /= 700; + is_first = false; + } else { + delta /= 2; + } + delta += delta / processed; + + bias = 0; + while (delta > 35 * 13) { + delta /= 35; + bias -= 36; + } + bias -= static_cast<int>(36 * delta / (delta + 38)); + delta = 0; + } + } + + delta++; + n = next_n; + } +} +#endif + +Result<string> idn_to_ascii(CSlice host) { + if (is_ascii_host(host)) { + return to_lower(host); + } + if (!check_utf8(host)) { + return Status::Error("Host name must be encoded in UTF-8"); + } + + const int MAX_DNS_NAME_LENGTH = 255; + if (host.size() >= MAX_DNS_NAME_LENGTH * 4) { // upper bound, 4 characters per symbol + return Status::Error("Host name is too long"); + } + +#if TD_WINDOWS + TRY_RESULT(whost, to_wstring(host)); + wchar_t punycode[MAX_DNS_NAME_LENGTH + 1]; + int result_length = + IdnToAscii(IDN_ALLOW_UNASSIGNED, whost.c_str(), narrow_cast<int>(whost.size()), punycode, MAX_DNS_NAME_LENGTH); + if (result_length == 0) { + return Status::Error("Host can't be converted to ASCII"); + } + + TRY_RESULT(idn_host, from_wstring(punycode, result_length)); + return idn_host; +#else + auto parts = full_split(Slice(host), '.'); + bool is_first = true; + string result; + result.reserve(host.size()); + for (auto part : parts) { + if (!is_first) { + result += '.'; + } + if (is_ascii_host(part)) { + result.append(part.data(), part.size()); + } else { + // TODO nameprep should be applied first, but punycode is better than nothing. + // It is better to use libidn/ICU here if available + result += "xn--"; + punycode(result, part); + } + is_first = false; + } + return result; +#endif +} + +static CSlice get_ip_str(int family, const void *addr) { + const int buf_size = INET6_ADDRSTRLEN; + static TD_THREAD_LOCAL char *buf; + init_thread_local<char[]>(buf, buf_size); + + const char *res = inet_ntop(family, +#if TD_WINDOWS + const_cast<PVOID>(addr), +#else + addr, +#endif + buf, buf_size); + if (res == nullptr) { + return CSlice(); + } else { + return CSlice(res); + } +} + IPAddress::IPAddress() : is_valid_(false) { } @@ -32,19 +200,52 @@ bool IPAddress::is_valid() const { return is_valid_; } +bool IPAddress::is_reserved() const { + CHECK(is_valid()); + + if (is_ipv6()) { + // TODO proper check for reserved IPv6 addresses + return true; + } + + uint32 ip = get_ipv4(); + struct IpBlock { + CSlice ip; + int mask; + IpBlock(CSlice ip, int mask) : ip(ip), mask(mask) { + } + }; + static const IpBlock blocks[] = {{"0.0.0.0", 8}, {"10.0.0.0", 8}, {"100.64.0.0", 10}, {"127.0.0.0", 8}, + {"169.254.0.0", 16}, {"172.16.0.0", 12}, {"192.0.0.0", 24}, {"192.0.2.0", 24}, + {"192.88.99.0", 24}, {"192.168.0.0", 16}, {"198.18.0.0", 15}, {"198.51.100.0", 24}, + {"203.0.113.0", 24}, {"224.0.0.0", 3}}; + for (auto &block : blocks) { + IPAddress block_ip_address; + block_ip_address.init_ipv4_port(block.ip, 80).ensure(); + uint32 range = block_ip_address.get_ipv4(); + CHECK(block.mask != 0); + uint32 mask = std::numeric_limits<uint32>::max() >> (32 - block.mask) << (32 - block.mask); + if ((ip & mask) == (range & mask)) { + return true; + } + } + return false; +} + const sockaddr *IPAddress::get_sockaddr() const { + CHECK(is_valid()); return &sockaddr_; } size_t IPAddress::get_sockaddr_len() const { CHECK(is_valid()); - switch (addr_.ss_family) { + switch (sockaddr_.sa_family) { case AF_INET6: return sizeof(ipv6_addr_); case AF_INET: return sizeof(ipv4_addr_); default: - LOG(FATAL) << "Unknown address family"; + UNREACHABLE(); return 0; } } @@ -54,20 +255,24 @@ int IPAddress::get_address_family() const { } bool IPAddress::is_ipv4() const { - return get_address_family() == AF_INET; + return is_valid() && get_address_family() == AF_INET; +} + +bool IPAddress::is_ipv6() const { + return is_valid() && get_address_family() == AF_INET6; } uint32 IPAddress::get_ipv4() const { CHECK(is_valid()); CHECK(is_ipv4()); - return ipv4_addr_.sin_addr.s_addr; + return htonl(ipv4_addr_.sin_addr.s_addr); } -Slice IPAddress::get_ipv6() const { +string IPAddress::get_ipv6() const { static_assert(sizeof(ipv6_addr_.sin6_addr) == 16, "ipv6 size == 16"); CHECK(is_valid()); CHECK(!is_ipv4()); - return Slice(ipv6_addr_.sin6_addr.s6_addr, 16); + return Slice(ipv6_addr_.sin6_addr.s6_addr, 16).str(); } IPAddress IPAddress::get_any_addr() const { @@ -80,18 +285,23 @@ IPAddress IPAddress::get_any_addr() const { res.init_ipv4_any(); break; default: - LOG(FATAL) << "Unknown address family"; + UNREACHABLE(); + break; } return res; } + void IPAddress::init_ipv4_any() { is_valid_ = true; + std::memset(&ipv4_addr_, 0, sizeof(ipv4_addr_)); ipv4_addr_.sin_family = AF_INET; ipv4_addr_.sin_addr.s_addr = INADDR_ANY; ipv4_addr_.sin_port = 0; } + void IPAddress::init_ipv6_any() { is_valid_ = true; + std::memset(&ipv6_addr_, 0, sizeof(ipv6_addr_)); ipv6_addr_.sin6_family = AF_INET6; ipv6_addr_.sin6_addr = in6addr_any; ipv6_addr_.sin6_port = 0; @@ -100,7 +310,12 @@ void IPAddress::init_ipv6_any() { Status IPAddress::init_ipv6_port(CSlice ipv6, int port) { is_valid_ = false; if (port <= 0 || port >= (1 << 16)) { - return Status::Error(PSLICE() << "Invalid [port=" << port << "]"); + return Status::Error(PSLICE() << "Invalid [IPv6 address port=" << port << "]"); + } + string ipv6_plain; + if (ipv6.size() > 2 && ipv6[0] == '[' && ipv6.back() == ']') { + ipv6_plain.assign(ipv6.begin() + 1, ipv6.size() - 2); + ipv6 = ipv6_plain; } std::memset(&ipv6_addr_, 0, sizeof(ipv6_addr_)); ipv6_addr_.sin6_family = AF_INET6; @@ -122,7 +337,7 @@ Status IPAddress::init_ipv6_as_ipv4_port(CSlice ipv4, int port) { Status IPAddress::init_ipv4_port(CSlice ipv4, int port) { is_valid_ = false; if (port <= 0 || port >= (1 << 16)) { - return Status::Error(PSLICE() << "Invalid [port=" << port << "]"); + return Status::Error(PSLICE() << "Invalid [IPv4 address port=" << port << "]"); } std::memset(&ipv4_addr_, 0, sizeof(ipv4_addr_)); ipv4_addr_.sin_family = AF_INET; @@ -137,36 +352,116 @@ Status IPAddress::init_ipv4_port(CSlice ipv4, int port) { return Status::OK(); } -Status IPAddress::init_host_port(CSlice host, int port) { - auto str_port = to_string(port); - return init_host_port(host, str_port); +Result<IPAddress> IPAddress::get_ip_address(CSlice host) { + auto r_address = get_ipv4_address(host); + if (r_address.is_ok()) { + return r_address.move_as_ok(); + } + r_address = get_ipv6_address(host); + if (r_address.is_ok()) { + return r_address.move_as_ok(); + } + return Status::Error(PSLICE() << '"' << host << "\" is not a valid IP address"); +} + +Result<IPAddress> IPAddress::get_ipv4_address(CSlice host) { + // sometimes inet_addr allows much more valid IPv4 hosts than inet_pton, + // like 0x12.0x34.0x56.0x78, or 0x12345678, or 0x7f.001 + auto ipv4_numeric_addr = inet_addr(host.c_str()); + if (ipv4_numeric_addr == INADDR_NONE) { + return Status::Error(PSLICE() << '"' << host << "\" is not a valid IPv4 address"); + } + + host = ::td::get_ip_str(AF_INET, &ipv4_numeric_addr); + IPAddress result; + auto status = result.init_ipv4_port(host, 1); + if (status.is_error()) { + return std::move(status); + } + return std::move(result); +} + +Result<IPAddress> IPAddress::get_ipv6_address(CSlice host) { + IPAddress result; + auto status = result.init_ipv6_port(host, 1); + if (status.is_error()) { + return Status::Error(PSLICE() << '"' << host << "\" is not a valid IPv6 address"); + } + return std::move(result); +} + +Status IPAddress::init_host_port(CSlice host, int port, bool prefer_ipv6) { + if (host.size() > 2 && host[0] == '[' && host.back() == ']') { + return init_ipv6_port(host, port == 0 ? 1 : port); + } + + return init_host_port(host, PSLICE() << port, prefer_ipv6); } -Status IPAddress::init_host_port(CSlice host, CSlice port) { +Status IPAddress::init_host_port(CSlice host, CSlice port, bool prefer_ipv6) { + is_valid_ = false; + if (host.empty()) { + return Status::Error("Host is empty"); + } +#if TD_WINDOWS + if (host == "..localmachine") { + return Status::Error("Host is invalid"); + } +#endif + TRY_RESULT(ascii_host, idn_to_ascii(host)); + host = ascii_host; // assign string to CSlice + + if (host[0] == '[' && host.back() == ']') { + auto port_int = to_integer<int>(port); + return init_ipv6_port(host, port_int == 0 ? 1 : port_int); + } + + // some getaddrinfo implementations use inet_pton instead of inet_aton and support only decimal-dotted IPv4 form, + // and so doesn't recognize 0x12.0x34.0x56.0x78, or 0x12345678, or 0x7f.001 as valid IPv4 addresses + auto ipv4_numeric_addr = inet_addr(host.c_str()); + if (ipv4_numeric_addr != INADDR_NONE) { + host = ::td::get_ip_str(AF_INET, &ipv4_numeric_addr); + } + addrinfo hints; addrinfo *info = nullptr; std::memset(&hints, 0, sizeof(hints)); - hints.ai_family = AF_INET; // TODO AF_UNSPEC; + hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; - LOG(INFO) << "Try to init IP address of " << host << " with port " << port; - auto s = getaddrinfo(host.c_str(), port.c_str(), &hints, &info); - if (s != 0) { - return Status::Error(PSLICE() << "getaddrinfo: " << gai_strerror(s)); + hints.ai_protocol = IPPROTO_TCP; + LOG(DEBUG + 10) << "Trying to init IP address of " << host << " with port " << port; + auto err = getaddrinfo(host.c_str(), port.c_str(), &hints, &info); + if (err != 0) { +#if TD_WINDOWS + return OS_SOCKET_ERROR("Failed to resolve host"); +#else + return Status::Error(PSLICE() << "Failed to resolve host: " << gai_strerror(err)); +#endif } SCOPE_EXIT { freeaddrinfo(info); }; - // prefer ipv4 - addrinfo *best_info = info; - for (auto *ptr = info->ai_next; ptr != nullptr; ptr = ptr->ai_next) { - if (ptr->ai_socktype == AF_INET) { + addrinfo *best_info = nullptr; + for (auto *ptr = info; ptr != nullptr; ptr = ptr->ai_next) { + if (ptr->ai_family == AF_INET && (!prefer_ipv6 || best_info == nullptr)) { + // just use first IPv4 address if there is no IPv6 and it isn't preferred best_info = ptr; - break; + if (!prefer_ipv6) { + break; + } } + if (ptr->ai_family == AF_INET6 && (prefer_ipv6 || best_info == nullptr)) { + // or first IPv6 address if there is no IPv4 and it isn't preferred + best_info = ptr; + if (prefer_ipv6) { + break; + } + } + } + if (best_info == nullptr) { + return Status::Error("Failed to find IPv4/IPv6 address"); } - // just use first address - CHECK(best_info != nullptr); return init_sockaddr(best_info->ai_addr, narrow_cast<socklen_t>(best_info->ai_addrlen)); } @@ -178,32 +473,39 @@ Status IPAddress::init_host_port(CSlice host_port) { return init_host_port(host_port.substr(0, pos).str(), host_port.substr(pos + 1).str()); } +Status IPAddress::init_sockaddr(sockaddr *addr) { + if (addr->sa_family == AF_INET6) { + return init_sockaddr(addr, sizeof(ipv6_addr_)); + } else if (addr->sa_family == AF_INET) { + return init_sockaddr(addr, sizeof(ipv4_addr_)); + } else { + return init_sockaddr(addr, 0); + } +} Status IPAddress::init_sockaddr(sockaddr *addr, socklen_t len) { if (addr->sa_family == AF_INET6) { CHECK(len == sizeof(ipv6_addr_)); std::memcpy(&ipv6_addr_, reinterpret_cast<sockaddr_in6 *>(addr), sizeof(ipv6_addr_)); - LOG(INFO) << "Have ipv6 address " << get_ip_str() << " with port " << get_port(); } else if (addr->sa_family == AF_INET) { CHECK(len == sizeof(ipv4_addr_)); std::memcpy(&ipv4_addr_, reinterpret_cast<sockaddr_in *>(addr), sizeof(ipv4_addr_)); - LOG(INFO) << "Have ipv4 address " << get_ip_str() << " with port " << get_port(); } else { return Status::Error(PSLICE() << "Unknown " << tag("sa_family", addr->sa_family)); } is_valid_ = true; + LOG(DEBUG + 10) << "Have address " << get_ip_str() << " with port " << get_port(); return Status::OK(); } Status IPAddress::init_socket_address(const SocketFd &socket_fd) { is_valid_ = false; -#if TD_WINDOWS - auto fd = socket_fd.get_fd().get_native_socket(); -#else - auto fd = socket_fd.get_fd().get_native_fd(); -#endif - socklen_t len = sizeof(addr_); - int ret = getsockname(fd, &sockaddr_, &len); + if (socket_fd.empty()) { + return Status::Error("Socket is empty"); + } + auto socket = socket_fd.get_native_fd().socket(); + socklen_t len = storage_size(); + int ret = getsockname(socket, &sockaddr_, &len); if (ret != 0) { return OS_SOCKET_ERROR("Failed to get socket address"); } @@ -213,13 +515,12 @@ Status IPAddress::init_socket_address(const SocketFd &socket_fd) { Status IPAddress::init_peer_address(const SocketFd &socket_fd) { is_valid_ = false; -#if TD_WINDOWS - auto fd = socket_fd.get_fd().get_native_socket(); -#else - auto fd = socket_fd.get_fd().get_native_fd(); -#endif - socklen_t len = sizeof(addr_); - int ret = getpeername(fd, &sockaddr_, &len); + if (socket_fd.empty()) { + return Status::Error("Socket is empty"); + } + auto socket = socket_fd.get_native_fd().socket(); + socklen_t len = storage_size(); + int ret = getpeername(socket, &sockaddr_, &len); if (ret != 0) { return OS_SOCKET_ERROR("Failed to get peer socket address"); } @@ -227,48 +528,57 @@ Status IPAddress::init_peer_address(const SocketFd &socket_fd) { return Status::OK(); } -static CSlice get_ip_str(int family, const void *addr) { - const int buf_size = INET6_ADDRSTRLEN; //, INET_ADDRSTRLEN; - static TD_THREAD_LOCAL char *buf; - init_thread_local<char[]>(buf, buf_size); - - const char *res = inet_ntop(family, -#if TD_WINDOWS - const_cast<PVOID>(addr), -#else - addr, -#endif - buf, buf_size); - if (res == nullptr) { - return CSlice(); - } else { - return CSlice(res); +void IPAddress::clear_ipv6_interface() { + if (!is_valid() || get_address_family() != AF_INET6) { + return; } + + auto *begin = ipv6_addr_.sin6_addr.s6_addr; + static_assert(sizeof(ipv6_addr_.sin6_addr.s6_addr) == 16, "expected 16 bytes buffer for ipv6"); + static_assert(sizeof(*begin) == 1, "expected array of bytes"); + std::memset(begin + 8, 0, 8 * sizeof(*begin)); } -CSlice IPAddress::ipv4_to_str(int32 ipv4) { - auto tmp_ipv4 = ntohl(ipv4); - return ::td::get_ip_str(AF_INET, &tmp_ipv4); +string IPAddress::ipv4_to_str(uint32 ipv4) { + ipv4 = ntohl(ipv4); + return ::td::get_ip_str(AF_INET, &ipv4).str(); } -Slice IPAddress::get_ip_str() const { +string IPAddress::ipv6_to_str(Slice ipv6) { + CHECK(ipv6.size() == 16); + return ::td::get_ip_str(AF_INET6, ipv6.ubegin()).str(); +} + +CSlice IPAddress::get_ip_str() const { if (!is_valid()) { - return Slice("0.0.0.0"); + return CSlice("0.0.0.0"); } - const void *addr; switch (get_address_family()) { case AF_INET6: - addr = &ipv6_addr_.sin6_addr; - break; + return ::td::get_ip_str(AF_INET6, &ipv6_addr_.sin6_addr); case AF_INET: - addr = &ipv4_addr_.sin_addr; - break; + return ::td::get_ip_str(AF_INET, &ipv4_addr_.sin_addr); default: UNREACHABLE(); - return Slice(); + return CSlice(); + } +} + +string IPAddress::get_ip_host() const { + if (!is_valid()) { + return "0.0.0.0"; + } + + switch (get_address_family()) { + case AF_INET6: + return PSTRING() << '[' << ::td::get_ip_str(AF_INET6, &ipv6_addr_.sin6_addr) << ']'; + case AF_INET: + return ::td::get_ip_str(AF_INET, &ipv4_addr_.sin_addr).str(); + default: + UNREACHABLE(); + return string(); } - return ::td::get_ip_str(get_address_family(), addr); } int IPAddress::get_port() const { @@ -304,7 +614,7 @@ void IPAddress::set_port(int port) { bool operator==(const IPAddress &a, const IPAddress &b) { if (!a.is_valid() || !b.is_valid()) { - return false; + return !a.is_valid() && !b.is_valid(); } if (a.get_address_family() != b.get_address_family()) { return false; @@ -318,13 +628,13 @@ bool operator==(const IPAddress &a, const IPAddress &b) { std::memcmp(&a.ipv6_addr_.sin6_addr, &b.ipv6_addr_.sin6_addr, sizeof(a.ipv6_addr_.sin6_addr)) == 0; } - LOG(FATAL) << "Unknown address family"; + UNREACHABLE(); return false; } bool operator<(const IPAddress &a, const IPAddress &b) { - if (a.is_valid() != b.is_valid()) { - return a.is_valid() < b.is_valid(); + if (!a.is_valid() || !b.is_valid()) { + return !a.is_valid() && b.is_valid(); } if (a.get_address_family() != b.get_address_family()) { return a.get_address_family() < b.get_address_family(); @@ -342,7 +652,7 @@ bool operator<(const IPAddress &a, const IPAddress &b) { return std::memcmp(&a.ipv6_addr_.sin6_addr, &b.ipv6_addr_.sin6_addr, sizeof(a.ipv6_addr_.sin6_addr)) < 0; } - LOG(FATAL) << "Unknown address family"; + UNREACHABLE(); return false; } @@ -350,12 +660,7 @@ StringBuilder &operator<<(StringBuilder &builder, const IPAddress &address) { if (!address.is_valid()) { return builder << "[invalid]"; } - if (address.get_address_family() == AF_INET) { - return builder << "[" << address.get_ip_str() << ":" << address.get_port() << "]"; - } else { - CHECK(address.get_address_family() == AF_INET6); - return builder << "[[" << address.get_ip_str() << "]:" << address.get_port() << "]"; - } + return builder << "[" << address.get_ip_host() << ":" << address.get_port() << "]"; } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h index 116a4c5425..f71bc5da91 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IPAddress.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,53 +15,79 @@ #if !TD_WINDOWS #include <arpa/inet.h> +#include <netinet/in.h> #include <sys/socket.h> #endif namespace td { + +Result<string> idn_to_ascii(CSlice host); + class SocketFd; + class IPAddress { public: IPAddress(); bool is_valid() const; - - const sockaddr *get_sockaddr() const; - size_t get_sockaddr_len() const; - int get_address_family() const; - Slice get_ip_str() const; bool is_ipv4() const; - uint32 get_ipv4() const; - Slice get_ipv6() const; + bool is_ipv6() const; + + bool is_reserved() const; + int get_port() const; void set_port(int port); + uint32 get_ipv4() const; + string get_ipv6() const; + + // returns result in a static thread-local buffer, which may be overwritten by any subsequent method call + CSlice get_ip_str() const; + + // returns IP address as a host, i.e. IPv4 or [IPv6] + string get_ip_host() const; + + static string ipv4_to_str(uint32 ipv4); + static string ipv6_to_str(Slice ipv6); + IPAddress get_any_addr() const; + static Result<IPAddress> get_ip_address(CSlice host); // host must be any IPv4 or IPv6 + static Result<IPAddress> get_ipv4_address(CSlice host); + static Result<IPAddress> get_ipv6_address(CSlice host); + Status init_ipv6_port(CSlice ipv6, int port) TD_WARN_UNUSED_RESULT; Status init_ipv6_as_ipv4_port(CSlice ipv4, int port) TD_WARN_UNUSED_RESULT; Status init_ipv4_port(CSlice ipv4, int port) TD_WARN_UNUSED_RESULT; - Status init_host_port(CSlice host, int port) TD_WARN_UNUSED_RESULT; - Status init_host_port(CSlice host, CSlice port) TD_WARN_UNUSED_RESULT; + Status init_host_port(CSlice host, int port, bool prefer_ipv6 = false) TD_WARN_UNUSED_RESULT; + Status init_host_port(CSlice host, CSlice port, bool prefer_ipv6 = false) TD_WARN_UNUSED_RESULT; Status init_host_port(CSlice host_port) TD_WARN_UNUSED_RESULT; Status init_socket_address(const SocketFd &socket_fd) TD_WARN_UNUSED_RESULT; Status init_peer_address(const SocketFd &socket_fd) TD_WARN_UNUSED_RESULT; + void clear_ipv6_interface(); + friend bool operator==(const IPAddress &a, const IPAddress &b); friend bool operator<(const IPAddress &a, const IPAddress &b); - static CSlice ipv4_to_str(int32 ipv4); + // for internal usage only + const sockaddr *get_sockaddr() const; + size_t get_sockaddr_len() const; + int get_address_family() const; + Status init_sockaddr(sockaddr *addr); + Status init_sockaddr(sockaddr *addr, socklen_t len) TD_WARN_UNUSED_RESULT; private: union { - sockaddr_storage addr_; sockaddr sockaddr_; sockaddr_in ipv4_addr_; sockaddr_in6 ipv6_addr_; }; + static constexpr socklen_t storage_size() { + return sizeof(ipv6_addr_); + } bool is_valid_; - Status init_sockaddr(sockaddr *addr, socklen_t len) TD_WARN_UNUSED_RESULT; void init_ipv4_any(); void init_ipv6_any(); }; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IoSlice.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IoSlice.h new file mode 100644 index 0000000000..5044fcec37 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/IoSlice.h @@ -0,0 +1,42 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/Slice.h" + +#if TD_PORT_POSIX +#include <sys/uio.h> +#endif + +namespace td { + +#if TD_PORT_POSIX + +using IoSlice = struct iovec; + +inline IoSlice as_io_slice(Slice slice) { + IoSlice res; + res.iov_len = slice.size(); + res.iov_base = const_cast<char *>(slice.data()); + return res; +} + +inline Slice as_slice(const IoSlice &io_slice) { + return Slice(static_cast<const char *>(io_slice.iov_base), io_slice.iov_len); +} + +#else + +using IoSlice = Slice; + +inline IoSlice as_io_slice(Slice slice) { + return slice; +} + +#endif + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.cpp new file mode 100644 index 0000000000..0781b609b3 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.cpp @@ -0,0 +1,109 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/MemoryMapping.h" + +#include "td/utils/misc.h" +#include "td/utils/SliceBuilder.h" + +// TODO: +// windows, +// anonymous maps, +// huge pages? + +#if TD_WINDOWS +#else +#include <sys/mman.h> +#include <unistd.h> +#endif + +namespace td { + +class MemoryMapping::Impl { + public: + Impl(MutableSlice data, int64 offset) : data_(data), offset_(offset) { + } + Slice as_slice() const { + return data_.substr(narrow_cast<size_t>(offset_)); + } + MutableSlice as_mutable_slice() const { + return {}; + } + + private: + MutableSlice data_; + int64 offset_; +}; + +#if !TD_WINDOWS +static Result<int64> get_page_size() { + static Result<int64> page_size = []() -> Result<int64> { + auto page_size = sysconf(_SC_PAGESIZE); + if (page_size < 0) { + return OS_ERROR("Can't load page size from sysconf"); + } + return page_size; + }(); + return page_size.clone(); +} +#endif + +Result<MemoryMapping> MemoryMapping::create_anonymous(const MemoryMapping::Options &options) { + return Status::Error("Unsupported yet"); +} + +Result<MemoryMapping> MemoryMapping::create_from_file(const FileFd &file_fd, const MemoryMapping::Options &options) { +#if TD_WINDOWS + return Status::Error("Unsupported yet"); +#else + if (file_fd.empty()) { + return Status::Error("Can't create memory mapping: file is empty"); + } + TRY_RESULT(stat, file_fd.stat()); + auto fd = file_fd.get_native_fd().fd(); + auto begin = options.offset; + if (begin < 0) { + return Status::Error(PSLICE() << "Can't create memory mapping: negative offset " << options.offset); + } + + int64 end; + if (options.size < 0) { + end = stat.size_; + } else { + end = begin + stat.size_; + } + + TRY_RESULT(page_size, get_page_size()); + auto fixed_begin = begin / page_size * page_size; + + auto data_offset = begin - fixed_begin; + TRY_RESULT(data_size, narrow_cast_safe<size_t>(end - fixed_begin)); + + void *data = mmap(nullptr, data_size, PROT_READ, MAP_PRIVATE, fd, narrow_cast<off_t>(fixed_begin)); + if (data == MAP_FAILED) { + return OS_ERROR("mmap call failed"); + } + + return MemoryMapping(make_unique<Impl>(MutableSlice(static_cast<char *>(data), data_size), data_offset)); +#endif +} + +MemoryMapping::MemoryMapping(MemoryMapping &&other) noexcept = default; +MemoryMapping &MemoryMapping::operator=(MemoryMapping &&other) noexcept = default; +MemoryMapping::~MemoryMapping() = default; + +MemoryMapping::MemoryMapping(unique_ptr<Impl> impl) : impl_(std::move(impl)) { +} + +Slice MemoryMapping::as_slice() const { + return impl_->as_slice(); +} + +MutableSlice MemoryMapping::as_mutable_slice() { + return impl_->as_mutable_slice(); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.h new file mode 100644 index 0000000000..040e45f989 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/MemoryMapping.h @@ -0,0 +1,52 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/port/FileFd.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" + +namespace td { + +class MemoryMapping { + public: + struct Options { + int64 offset{0}; + int64 size{-1}; + + Options() { + } + Options &with_offset(int64 new_offset) { + offset = new_offset; + return *this; + } + Options &with_size(int64 new_size) { + size = new_size; + return *this; + } + }; + + static Result<MemoryMapping> create_anonymous(const Options &options = {}); + static Result<MemoryMapping> create_from_file(const FileFd &file, const Options &options = {}); + + Slice as_slice() const; + MutableSlice as_mutable_slice(); // returns empty slice if memory is read-only + + MemoryMapping(const MemoryMapping &other) = delete; + const MemoryMapping &operator=(const MemoryMapping &other) = delete; + MemoryMapping(MemoryMapping &&other) noexcept; + MemoryMapping &operator=(MemoryMapping &&other) noexcept; + ~MemoryMapping(); + + private: + class Impl; + unique_ptr<Impl> impl_; + explicit MemoryMapping(unique_ptr<Impl> impl); +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Mutex.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Mutex.h new file mode 100644 index 0000000000..40d9f482db --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Mutex.h @@ -0,0 +1,30 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include <mutex> + +namespace td { + +class Mutex { + public: + struct Guard { + std::unique_lock<std::mutex> guard; + void reset() { + guard.unlock(); + } + }; + + Guard lock() { + return {std::unique_lock<std::mutex>(mutex_)}; + } + + private: + std::mutex mutex_; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h index e23f4382d0..92629d6693 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Poll.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h index eb71367ab9..674f8da677 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollBase.h @@ -1,12 +1,13 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/PollFlags.h" namespace td { class PollBase { @@ -19,9 +20,9 @@ class PollBase { virtual ~PollBase() = default; virtual void init() = 0; virtual void clear() = 0; - virtual void subscribe(const Fd &fd, Fd::Flags flags) = 0; - virtual void unsubscribe(const Fd &fd) = 0; - virtual void unsubscribe_before_close(const Fd &fd) = 0; + virtual void subscribe(PollableFd fd, PollFlags flags) = 0; + virtual void unsubscribe(PollableFdRef fd) = 0; + virtual void unsubscribe_before_close(PollableFdRef fd) = 0; virtual void run(int timeout_ms) = 0; }; } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.cpp new file mode 100644 index 0000000000..d729dcfa1e --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.cpp @@ -0,0 +1,71 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/PollFlags.h" + +namespace td { + +bool PollFlagsSet::write_flags(PollFlags flags) { + if (flags.empty()) { + return false; + } + auto old_flags = to_write_.fetch_or(flags.raw(), std::memory_order_relaxed); + return (flags.raw() & ~old_flags) != 0; +} + +bool PollFlagsSet::write_flags_local(PollFlags flags) { + return flags_.add_flags(flags); +} + +bool PollFlagsSet::flush() const { + if (to_write_.load(std::memory_order_relaxed) == 0) { + return false; + } + auto to_write = to_write_.exchange(0, std::memory_order_relaxed); + auto old_flags = flags_; + flags_.add_flags(PollFlags::from_raw(to_write)); + if (flags_.can_close()) { + flags_.remove_flags(PollFlags::Write()); + } + return flags_ != old_flags; +} + +PollFlags PollFlagsSet::read_flags() const { + flush(); + return flags_; +} + +PollFlags PollFlagsSet::read_flags_local() const { + return flags_; +} + +void PollFlagsSet::clear_flags(PollFlags flags) { + flags_.remove_flags(flags); +} + +void PollFlagsSet::clear() { + to_write_ = 0; + flags_ = {}; +} + +StringBuilder &operator<<(StringBuilder &sb, PollFlags flags) { + sb << '['; + if (flags.can_read()) { + sb << 'R'; + } + if (flags.can_write()) { + sb << 'W'; + } + if (flags.can_close()) { + sb << 'C'; + } + if (flags.has_pending_error()) { + sb << 'E'; + } + return sb << ']'; +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.h new file mode 100644 index 0000000000..f759cb44cb --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/PollFlags.h @@ -0,0 +1,122 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/StringBuilder.h" + +#include <atomic> + +namespace td { + +class PollFlags { + public: + using Raw = int32; + bool can_read() const { + return has_flags(Read()); + } + bool can_write() const { + return has_flags(Write()); + } + bool can_close() const { + return has_flags(Close()); + } + bool has_pending_error() const { + return has_flags(Error()); + } + void remove_flags(PollFlags flags) { + remove_flags(flags.raw()); + } + bool add_flags(PollFlags flags) { + auto old_flags = flags_; + add_flags(flags.raw()); + return old_flags != flags_; + } + bool has_flags(PollFlags flags) const { + return has_flags(flags.raw()); + } + + bool empty() const { + return flags_ == 0; + } + Raw raw() const { + return flags_; + } + static PollFlags from_raw(Raw raw) { + return PollFlags(raw); + } + PollFlags() = default; + + bool operator==(const PollFlags &other) const { + return flags_ == other.flags_; + } + bool operator!=(const PollFlags &other) const { + return !(*this == other); + } + PollFlags operator|(const PollFlags other) const { + return from_raw(raw() | other.raw()); + } + + static PollFlags Write() { + return PollFlags(Flag::Write); + } + static PollFlags Error() { + return PollFlags(Flag::Error); + } + static PollFlags Close() { + return PollFlags(Flag::Close); + } + static PollFlags Read() { + return PollFlags(Flag::Read); + } + static PollFlags ReadWrite() { + return Read() | Write(); + } + + private: + enum class Flag : Raw { Write = 0x001, Read = 0x002, Close = 0x004, Error = 0x008, None = 0 }; + Raw flags_{static_cast<Raw>(Flag::None)}; + + explicit PollFlags(Raw raw) : flags_(raw) { + } + explicit PollFlags(Flag flag) : PollFlags(static_cast<Raw>(flag)) { + } + + PollFlags &add_flags(Raw flags) { + flags_ |= flags; + return *this; + } + PollFlags &remove_flags(Raw flags) { + flags_ &= ~flags; + return *this; + } + bool has_flags(Raw flags) const { + return (flags_ & flags) == flags; + } +}; + +StringBuilder &operator<<(StringBuilder &sb, PollFlags flags); + +class PollFlagsSet { + public: + // write flags from any thread + // this is the only function that should be called from other threads + bool write_flags(PollFlags flags); + + bool write_flags_local(PollFlags flags); + bool flush() const; + + PollFlags read_flags() const; + PollFlags read_flags_local() const; + void clear_flags(PollFlags flags); + void clear(); + + private: + mutable std::atomic<PollFlags::Raw> to_write_{0}; + mutable PollFlags flags_; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h index eee5f3dcdb..01de499bd0 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/RwMutex.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -9,14 +9,16 @@ #include "td/utils/port/config.h" #include "td/utils/common.h" -#include "td/utils/logging.h" #include "td/utils/Status.h" #if TD_PORT_POSIX #include <pthread.h> #endif +#include <memory> + namespace td { + class RwMutex { public: RwMutex() { @@ -24,11 +26,11 @@ class RwMutex { } RwMutex(const RwMutex &) = delete; RwMutex &operator=(const RwMutex &) = delete; - RwMutex(RwMutex &&other) { + RwMutex(RwMutex &&other) noexcept { init(); other.clear(); } - RwMutex &operator=(RwMutex &&other) { + RwMutex &operator=(RwMutex &&other) noexcept { other.clear(); return *this; } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp index ead43a3d4b..139252b405 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,10 +8,15 @@ #include "td/utils/port/config.h" +#include "td/utils/common.h" #include "td/utils/logging.h" +#include "td/utils/port/detail/skip_eintr.h" #include "td/utils/port/IPAddress.h" +#include "td/utils/port/PollFlags.h" +#include "td/utils/SliceBuilder.h" #if TD_PORT_POSIX +#include <cerrno> #include <arpa/inet.h> #include <fcntl.h> @@ -20,141 +25,348 @@ #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> +#endif +#if TD_PORT_WINDOWS +#include "td/utils/port/detail/Iocp.h" +#include "td/utils/port/Mutex.h" +#include "td/utils/VectorQueue.h" #endif +#include <atomic> +#include <cstring> + namespace td { -Result<ServerSocketFd> ServerSocketFd::open(int32 port, CSlice addr) { - ServerSocketFd socket; - TRY_STATUS(socket.init(port, addr)); - return std::move(socket); -} +namespace detail { +#if TD_PORT_WINDOWS +class ServerSocketFdImpl final : private Iocp::Callback { + public: + ServerSocketFdImpl(NativeFd fd, int socket_family) : info_(std::move(fd)), socket_family_(socket_family) { + VLOG(fd) << get_native_fd() << " create ServerSocketFd"; + Iocp::get()->subscribe(get_native_fd(), this); + notify_iocp_read(); + } + void close() { + notify_iocp_close(); + } + PollableFdInfo &get_poll_info() { + return info_; + } + const PollableFdInfo &get_poll_info() const { + return info_; + } -const Fd &ServerSocketFd::get_fd() const { - return fd_; -} + const NativeFd &get_native_fd() const { + return info_.native_fd(); + } -Fd &ServerSocketFd::get_fd() { - return fd_; -} + Result<SocketFd> accept() { + auto lock = lock_.lock(); + if (accepted_.empty()) { + get_poll_info().clear_flags(PollFlags::Read()); + return Status::Error(-1, "Operation would block"); + } + return accepted_.pop(); + } -int32 ServerSocketFd::get_flags() const { - return fd_.get_flags(); -} + Status get_pending_error() { + Status res; + { + auto lock = lock_.lock(); + if (!pending_errors_.empty()) { + res = pending_errors_.pop(); + } + if (res.is_ok()) { + get_poll_info().clear_flags(PollFlags::Error()); + } + } + return res; + } -Status ServerSocketFd::get_pending_error() { - return fd_.get_pending_error(); + private: + PollableFdInfo info_; + + Mutex lock_; + VectorQueue<SocketFd> accepted_; + VectorQueue<Status> pending_errors_; + static constexpr size_t MAX_ADDR_SIZE = sizeof(sockaddr_in6) + 16; + char addr_buf_[MAX_ADDR_SIZE * 2]; + + bool close_flag_{false}; + std::atomic<int> refcnt_{1}; + bool is_read_active_{false}; + WSAOVERLAPPED read_overlapped_; + + char close_overlapped_; + + NativeFd accept_socket_; + int socket_family_; + + void on_close() { + close_flag_ = true; + info_.set_native_fd({}); + } + void on_read() { + VLOG(fd) << get_native_fd() << " on_read"; + if (is_read_active_) { + is_read_active_ = false; + auto r_socket = [&]() -> Result<SocketFd> { + auto from = get_native_fd().socket(); + auto status = setsockopt(accept_socket_.socket(), SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, + reinterpret_cast<const char *>(&from), sizeof(from)); + if (status != 0) { + return OS_SOCKET_ERROR("Failed to set SO_UPDATE_ACCEPT_CONTEXT options"); + } + return SocketFd::from_native_fd(std::move(accept_socket_)); + }(); + VLOG(fd) << get_native_fd() << " finish accept"; + if (r_socket.is_error()) { + return on_error(r_socket.move_as_error()); + } + { + auto lock = lock_.lock(); + accepted_.push(r_socket.move_as_ok()); + } + get_poll_info().add_flags_from_poll(PollFlags::Read()); + } + loop_read(); + } + void loop_read() { + CHECK(!is_read_active_); + accept_socket_ = NativeFd(socket(socket_family_, SOCK_STREAM, 0)); + std::memset(&read_overlapped_, 0, sizeof(read_overlapped_)); + VLOG(fd) << get_native_fd() << " start accept"; + BOOL status = AcceptEx(get_native_fd().socket(), accept_socket_.socket(), addr_buf_, 0, MAX_ADDR_SIZE, + MAX_ADDR_SIZE, nullptr, &read_overlapped_); + if (status == TRUE || check_status("Failed to accept connection")) { + inc_refcnt(); + is_read_active_ = true; + } + } + bool check_status(Slice message) { + auto last_error = WSAGetLastError(); + if (last_error == ERROR_IO_PENDING) { + return true; + } + on_error(OS_SOCKET_ERROR(message)); + return false; + } + bool dec_refcnt() { + if (--refcnt_ == 0) { + delete this; + return true; + } + return false; + } + void inc_refcnt() { + CHECK(refcnt_ != 0); + refcnt_++; + } + + void on_error(Status status) { + { + auto lock = lock_.lock(); + pending_errors_.push(std::move(status)); + } + get_poll_info().add_flags_from_poll(PollFlags::Error()); + } + + void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) final { + // called from other thread + if (dec_refcnt() || close_flag_) { + VLOG(fd) << "Ignore IOCP (server socket is closing)"; + return; + } + if (r_size.is_error()) { + return on_error(get_socket_pending_error(get_native_fd(), overlapped, r_size.move_as_error())); + } + + if (overlapped == nullptr) { + return on_read(); + } + if (overlapped == &read_overlapped_) { + return on_read(); + } + + if (overlapped == reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)) { + return on_close(); + } + UNREACHABLE(); + } + void notify_iocp_read() { + VLOG(fd) << get_native_fd() << " notify_read"; + inc_refcnt(); + Iocp::get()->post(0, this, nullptr); + } + void notify_iocp_close() { + VLOG(fd) << get_native_fd() << " notify_close"; + Iocp::get()->post(0, this, reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)); + } +}; +void ServerSocketFdImplDeleter::operator()(ServerSocketFdImpl *impl) { + impl->close(); } +#elif TD_PORT_POSIX +class ServerSocketFdImpl { + public: + explicit ServerSocketFdImpl(NativeFd fd) : info_(std::move(fd)) { + } + PollableFdInfo &get_poll_info() { + return info_; + } + const PollableFdInfo &get_poll_info() const { + return info_; + } -Result<SocketFd> ServerSocketFd::accept() { -#if TD_PORT_POSIX - sockaddr_storage addr; - socklen_t addr_len = sizeof(addr); - int native_fd = fd_.get_native_fd(); - int r_fd = skip_eintr([&] { return ::accept(native_fd, reinterpret_cast<sockaddr *>(&addr), &addr_len); }); - auto accept_errno = errno; - if (r_fd >= 0) { - return SocketFd::from_native_fd(r_fd); + const NativeFd &get_native_fd() const { + return info_.native_fd(); } + Result<SocketFd> accept() { + sockaddr_storage addr; + socklen_t addr_len = sizeof(addr); + int native_fd = get_native_fd().socket(); + int r_fd = detail::skip_eintr([&] { return ::accept(native_fd, reinterpret_cast<sockaddr *>(&addr), &addr_len); }); + auto accept_errno = errno; + if (r_fd >= 0) { + return SocketFd::from_native_fd(NativeFd(r_fd)); + } - if (accept_errno == EAGAIN + if (accept_errno == EAGAIN #if EAGAIN != EWOULDBLOCK - || accept_errno == EWOULDBLOCK + || accept_errno == EWOULDBLOCK #endif - ) { - fd_.clear_flags(Fd::Read); - return Status::Error(-1, "Operation would block"); - } - - auto error = Status::PosixError(accept_errno, PSLICE() << "Accept from [fd = " << native_fd << "] has failed"); - switch (accept_errno) { - case EBADF: - case EFAULT: - case EINVAL: - case ENOTSOCK: - case EOPNOTSUPP: - LOG(FATAL) << error; - UNREACHABLE(); - break; - default: - LOG(ERROR) << error; - // fallthrough - case EMFILE: - case ENFILE: - case ECONNABORTED: //??? - fd_.clear_flags(Fd::Read); - fd_.update_flags(Fd::Close); - return std::move(error); + ) { + get_poll_info().clear_flags(PollFlags::Read()); + return Status::Error(-1, "Operation would block"); + } + + auto error = Status::PosixError(accept_errno, PSLICE() << "Accept from " << get_native_fd() << " has failed"); + switch (accept_errno) { + case EBADF: + case EFAULT: + case EINVAL: + case ENOTSOCK: + case EOPNOTSUPP: + LOG(FATAL) << error; + UNREACHABLE(); + break; + default: + LOG(ERROR) << error; + // fallthrough + case EMFILE: + case ENFILE: + case ECONNABORTED: //??? + get_poll_info().clear_flags(PollFlags::Read()); + get_poll_info().add_flags(PollFlags::Close()); + return std::move(error); + } } -#elif TD_PORT_WINDOWS - TRY_RESULT(socket_fd, fd_.accept()); - return SocketFd(std::move(socket_fd)); + + Status get_pending_error() { + if (!get_poll_info().get_flags_local().has_pending_error()) { + return Status::OK(); + } + TRY_STATUS(detail::get_socket_pending_error(get_native_fd())); + get_poll_info().clear_flags(PollFlags::Error()); + return Status::OK(); + } + + private: + PollableFdInfo info_; +}; +void ServerSocketFdImplDeleter::operator()(ServerSocketFdImpl *impl) { + delete impl; +} #endif +} // namespace detail + +ServerSocketFd::ServerSocketFd() = default; +ServerSocketFd::ServerSocketFd(ServerSocketFd &&) noexcept = default; +ServerSocketFd &ServerSocketFd::operator=(ServerSocketFd &&) noexcept = default; +ServerSocketFd::~ServerSocketFd() = default; +ServerSocketFd::ServerSocketFd(unique_ptr<detail::ServerSocketFdImpl> impl) : impl_(impl.release()) { +} +PollableFdInfo &ServerSocketFd::get_poll_info() { + return impl_->get_poll_info(); +} + +const PollableFdInfo &ServerSocketFd::get_poll_info() const { + return impl_->get_poll_info(); +} + +Status ServerSocketFd::get_pending_error() { + return impl_->get_pending_error(); +} + +const NativeFd &ServerSocketFd::get_native_fd() const { + return impl_->get_native_fd(); +} + +Result<SocketFd> ServerSocketFd::accept() { + return impl_->accept(); } void ServerSocketFd::close() { - fd_.close(); + impl_.reset(); } bool ServerSocketFd::empty() const { - return fd_.empty(); + return !impl_; } -Status ServerSocketFd::init(int32 port, CSlice addr) { - IPAddress address; - TRY_STATUS(address.init_ipv4_port(addr, port)); - auto fd = socket(address.get_address_family(), SOCK_STREAM, 0); -#if TD_PORT_POSIX - if (fd == -1) { -#elif TD_PORT_WINDOWS - if (fd == INVALID_SOCKET) { -#endif +Result<ServerSocketFd> ServerSocketFd::open(int32 port, CSlice addr) { + if (port <= 0 || port >= (1 << 16)) { + return Status::Error(PSLICE() << "Invalid server port " << port << " specified"); + } + + TRY_RESULT(address, IPAddress::get_ip_address(addr)); + address.set_port(port); + + NativeFd fd{socket(address.get_address_family(), SOCK_STREAM, 0)}; + if (!fd) { return OS_SOCKET_ERROR("Failed to create a socket"); } - auto fd_quard = ScopeExit() + [fd]() { -#if TD_PORT_POSIX - ::close(fd); -#elif TD_PORT_WINDOWS - ::closesocket(fd); -#endif - }; - TRY_STATUS(detail::set_native_socket_is_blocking(fd, false)); + TRY_STATUS(fd.set_is_blocking_unsafe(false)); + auto sock = fd.socket(); linger ling = {0, 0}; #if TD_PORT_POSIX int flags = 1; #ifdef SO_REUSEPORT - setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<const char *>(&flags), sizeof(flags)); + setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, reinterpret_cast<const char *>(&flags), sizeof(flags)); #endif #elif TD_PORT_WINDOWS - BOOL flags = TRUE; + BOOL flags = FALSE; + if (address.is_ipv6()) { + setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&flags), sizeof(flags)); + } + flags = TRUE; #endif - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags)); - setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags)); - setsockopt(fd, SOL_SOCKET, SO_LINGER, reinterpret_cast<const char *>(&ling), sizeof(ling)); - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags)); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags)); + setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags)); + setsockopt(sock, SOL_SOCKET, SO_LINGER, reinterpret_cast<const char *>(&ling), sizeof(ling)); + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags)); - int e_bind = bind(fd, address.get_sockaddr(), static_cast<socklen_t>(address.get_sockaddr_len())); + int e_bind = bind(sock, address.get_sockaddr(), static_cast<socklen_t>(address.get_sockaddr_len())); if (e_bind != 0) { return OS_SOCKET_ERROR("Failed to bind a socket"); } // TODO: magic constant - int e_listen = listen(fd, 8192); + int e_listen = listen(sock, 8192); if (e_listen != 0) { return OS_SOCKET_ERROR("Failed to listen on a socket"); } #if TD_PORT_POSIX - fd_ = Fd(fd, Fd::Mode::Owner); + auto impl = make_unique<detail::ServerSocketFdImpl>(std::move(fd)); #elif TD_PORT_WINDOWS - fd_ = Fd::create_server_socket_fd(fd, address.get_address_family()); + auto impl = make_unique<detail::ServerSocketFdImpl>(std::move(fd), address.get_address_family()); #endif - fd_quard.dismiss(); - return Status::OK(); + return ServerSocketFd(std::move(impl)); } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h index 67b43ad02d..dfff0741bd 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/ServerSocketFd.h @@ -1,32 +1,43 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/SocketFd.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" +#include <memory> + namespace td { +namespace detail { +class ServerSocketFdImpl; +class ServerSocketFdImplDeleter { + public: + void operator()(ServerSocketFdImpl *impl); +}; +} // namespace detail class ServerSocketFd { public: - ServerSocketFd() = default; + ServerSocketFd(); ServerSocketFd(const ServerSocketFd &) = delete; ServerSocketFd &operator=(const ServerSocketFd &) = delete; - ServerSocketFd(ServerSocketFd &&) = default; - ServerSocketFd &operator=(ServerSocketFd &&) = default; + ServerSocketFd(ServerSocketFd &&) noexcept; + ServerSocketFd &operator=(ServerSocketFd &&) noexcept; + ~ServerSocketFd(); static Result<ServerSocketFd> open(int32 port, CSlice addr = CSlice("0.0.0.0")) TD_WARN_UNUSED_RESULT; - const Fd &get_fd() const; - Fd &get_fd(); - int32 get_flags() const; + PollableFdInfo &get_poll_info(); + const PollableFdInfo &get_poll_info() const; + Status get_pending_error() TD_WARN_UNUSED_RESULT; Result<SocketFd> accept() TD_WARN_UNUSED_RESULT; @@ -34,10 +45,10 @@ class ServerSocketFd { void close(); bool empty() const; - private: - Fd fd_; + const NativeFd &get_native_fd() const; - Status init(int32 port, CSlice addr) TD_WARN_UNUSED_RESULT; + private: + std::unique_ptr<detail::ServerSocketFdImpl, detail::ServerSocketFdImplDeleter> impl_; + explicit ServerSocketFd(unique_ptr<detail::ServerSocketFdImpl> impl); }; - } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp index 790bcd1bbd..90d516db36 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.cpp @@ -1,18 +1,31 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/port/SocketFd.h" +#include "td/utils/common.h" +#include "td/utils/format.h" #include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/port/detail/skip_eintr.h" +#include "td/utils/port/PollFlags.h" +#include "td/utils/SliceBuilder.h" #if TD_PORT_WINDOWS -#include "td/utils/misc.h" +#include "td/utils/buffer.h" +#include "td/utils/port/detail/Iocp.h" +#include "td/utils/port/Mutex.h" +#include "td/utils/VectorQueue.h" + +#include <limits> #endif #if TD_PORT_POSIX +#include <cerrno> + #include <arpa/inet.h> #include <fcntl.h> #include <netinet/in.h> @@ -22,118 +35,670 @@ #include <unistd.h> #endif +#include <atomic> +#include <cstring> + namespace td { +namespace detail { +#if TD_PORT_WINDOWS +class SocketFdImpl final : private Iocp::Callback { + public: + explicit SocketFdImpl(NativeFd native_fd) : info_(std::move(native_fd)) { + VLOG(fd) << get_native_fd() << " create from native_fd"; + get_poll_info().add_flags(PollFlags::Write()); + Iocp::get()->subscribe(get_native_fd(), this); + is_read_active_ = true; + notify_iocp_connected(); + } -Result<SocketFd> SocketFd::open(const IPAddress &address) { - SocketFd socket; - TRY_STATUS(socket.init(address)); - return std::move(socket); -} + SocketFdImpl(NativeFd native_fd, const IPAddress &addr) : info_(std::move(native_fd)) { + VLOG(fd) << get_native_fd() << " create from native_fd and connect"; + get_poll_info().add_flags(PollFlags::Write()); + Iocp::get()->subscribe(get_native_fd(), this); + LPFN_CONNECTEX ConnectExPtr = nullptr; + GUID guid = WSAID_CONNECTEX; + DWORD numBytes; + auto error = + ::WSAIoctl(get_native_fd().socket(), SIO_GET_EXTENSION_FUNCTION_POINTER, static_cast<void *>(&guid), + sizeof(guid), static_cast<void *>(&ConnectExPtr), sizeof(ConnectExPtr), &numBytes, nullptr, nullptr); + if (error) { + on_error(OS_SOCKET_ERROR("WSAIoctl failed")); + return; + } + std::memset(&read_overlapped_, 0, sizeof(read_overlapped_)); + inc_refcnt(); + is_read_active_ = true; + auto status = ConnectExPtr(get_native_fd().socket(), addr.get_sockaddr(), narrow_cast<int>(addr.get_sockaddr_len()), + nullptr, 0, nullptr, &read_overlapped_); -#if TD_PORT_POSIX -Result<SocketFd> SocketFd::from_native_fd(int fd) { - auto fd_guard = ScopeExit() + [fd]() { ::close(fd); }; + if (status == TRUE || !check_status("Failed to connect")) { + is_read_active_ = false; + dec_refcnt(); + } + } - TRY_STATUS(detail::set_native_socket_is_blocking(fd, false)); + void close() { + if (!is_write_waiting_ && is_connected_) { + VLOG(fd) << get_native_fd() << " will close after ongoing write"; + auto lock = lock_.lock(); + if (!is_write_waiting_) { + need_close_after_write_ = true; + return; + } + } + notify_iocp_close(); + } - // TODO remove copypaste - int flags = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags)); - setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags)); - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags)); - // TODO: SO_REUSEADDR, SO_KEEPALIVE, TCP_NODELAY, SO_SNDBUF, SO_RCVBUF, TCP_QUICKACK, SO_LINGER + PollableFdInfo &get_poll_info() { + return info_; + } + const PollableFdInfo &get_poll_info() const { + return info_; + } + + const NativeFd &get_native_fd() const { + return info_.native_fd(); + } + + Result<size_t> write(Slice data) { + // LOG(ERROR) << "Write: " << format::as_hex_dump<0>(data); + output_writer_.append(data); + return write_finish(data.size()); + } + + Result<size_t> writev(Span<IoSlice> slices) { + size_t total_size = 0; + for (auto io_slice : slices) { + auto size = as_slice(io_slice).size(); + CHECK(size <= std::numeric_limits<size_t>::max() - total_size); + total_size += size; + } + + auto left_size = total_size; + for (auto io_slice : slices) { + auto slice = as_slice(io_slice); + output_writer_.append(slice, left_size); + left_size -= slice.size(); + } + + return write_finish(total_size); + } + + Result<size_t> write_finish(size_t total_size) { + if (is_write_waiting_) { + auto lock = lock_.lock(); + is_write_waiting_ = false; + lock.reset(); + notify_iocp_write(); + } + return total_size; + } + + Result<size_t> read(MutableSlice slice) { + if (get_poll_info().get_flags_local().has_pending_error()) { + TRY_STATUS(get_pending_error()); + } + input_reader_.sync_with_writer(); + auto res = input_reader_.advance(td::min(slice.size(), input_reader_.size()), slice); + if (res == 0) { + get_poll_info().clear_flags(PollFlags::Read()); + } else { + // LOG(ERROR) << "Read: " << format::as_hex_dump<0>(Slice(slice.substr(0, res))); + } + return res; + } + + Status get_pending_error() { + Status res; + { + auto lock = lock_.lock(); + if (!pending_errors_.empty()) { + res = pending_errors_.pop(); + } + if (res.is_ok()) { + get_poll_info().clear_flags(PollFlags::Error()); + } + } + return res; + } + + private: + PollableFdInfo info_; + Mutex lock_; + + std::atomic<int> refcnt_{1}; + bool close_flag_{false}; + bool need_close_after_write_{false}; - fd_guard.dismiss(); + std::atomic<bool> is_connected_{false}; + bool is_read_active_{false}; + ChainBufferWriter input_writer_; + ChainBufferReader input_reader_ = input_writer_.extract_reader(); + WSAOVERLAPPED read_overlapped_; + VectorQueue<Status> pending_errors_; - SocketFd socket; - socket.fd_ = Fd(fd, Fd::Mode::Owner); - return std::move(socket); + bool is_write_active_{false}; + std::atomic<bool> is_write_waiting_{false}; + ChainBufferWriter output_writer_; + ChainBufferReader output_reader_ = output_writer_.extract_reader(); + WSAOVERLAPPED write_overlapped_; + + char close_overlapped_; + + bool check_status(Slice message) { + auto last_error = WSAGetLastError(); + if (last_error == ERROR_IO_PENDING) { + return true; + } + on_error(OS_SOCKET_ERROR(message)); + return false; + } + + void loop_read() { + CHECK(is_connected_); + CHECK(!is_read_active_); + if (close_flag_ || need_close_after_write_) { + return; + } + std::memset(&read_overlapped_, 0, sizeof(read_overlapped_)); + auto dest = input_writer_.prepare_append(); + WSABUF buf; + buf.len = narrow_cast<ULONG>(dest.size()); + buf.buf = dest.data(); + DWORD flags = 0; + int status = WSARecv(get_native_fd().socket(), &buf, 1, nullptr, &flags, &read_overlapped_, nullptr); + if (status == 0 || check_status("Failed to read from connection")) { + inc_refcnt(); + is_read_active_ = true; + } + } + + void loop_write() { + CHECK(is_connected_); + CHECK(!is_write_active_); + + output_reader_.sync_with_writer(); + auto to_write = output_reader_.prepare_read(); + if (to_write.empty()) { + auto lock = lock_.lock(); + output_reader_.sync_with_writer(); + to_write = output_reader_.prepare_read(); + if (to_write.empty()) { + is_write_waiting_ = true; + if (need_close_after_write_) { + notify_iocp_close(); + } + return; + } + } + if (to_write.empty()) { + return; + } + std::memset(&write_overlapped_, 0, sizeof(write_overlapped_)); + constexpr size_t BUF_SIZE = 20; + WSABUF buf[BUF_SIZE]; + auto it = output_reader_.clone(); + size_t buf_i; + for (buf_i = 0; buf_i < BUF_SIZE; buf_i++) { + auto src = it.prepare_read(); + if (src.empty()) { + break; + } + buf[buf_i].len = narrow_cast<ULONG>(src.size()); + buf[buf_i].buf = const_cast<CHAR *>(src.data()); + it.confirm_read(src.size()); + } + int status = + WSASend(get_native_fd().socket(), buf, narrow_cast<DWORD>(buf_i), nullptr, 0, &write_overlapped_, nullptr); + if (status == 0 || check_status("Failed to write to connection")) { + inc_refcnt(); + is_write_active_ = true; + } + } + + void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) final { + // called from other thread + if (dec_refcnt() || close_flag_) { + VLOG(fd) << "Ignore IOCP (socket is closing)"; + return; + } + if (r_size.is_error()) { + return on_error(get_socket_pending_error(get_native_fd(), overlapped, r_size.move_as_error())); + } + + if (!is_connected_ && overlapped == &read_overlapped_) { + return on_connected(); + } + + auto size = r_size.move_as_ok(); + if (overlapped == &write_overlapped_) { + return on_write(size); + } + if (overlapped == nullptr) { + CHECK(size == 0); + return on_write(size); + } + + if (overlapped == &read_overlapped_) { + return on_read(size); + } + if (overlapped == reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)) { + return on_close(); + } + LOG(ERROR) << this << ' ' << overlapped << ' ' << &read_overlapped_ << ' ' << &write_overlapped_ << ' ' + << reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_) << ' ' << size; + LOG(FATAL) << get_native_fd() << ' ' << info_.get_flags_local() << ' ' << refcnt_.load() << ' ' << close_flag_ + << ' ' << need_close_after_write_ << ' ' << is_connected_ << ' ' << is_read_active_ << ' ' + << is_write_active_ << ' ' << is_write_waiting_.load() << ' ' << input_reader_.size() << ' ' + << output_reader_.size(); + } + + void on_error(Status status) { + VLOG(fd) << get_native_fd() << " on error " << status; + { + auto lock = lock_.lock(); + pending_errors_.push(std::move(status)); + } + get_poll_info().add_flags_from_poll(PollFlags::Error()); + } + + void on_connected() { + VLOG(fd) << get_native_fd() << " on connected"; + CHECK(!is_connected_); + CHECK(is_read_active_); + is_connected_ = true; + is_read_active_ = false; + loop_read(); + loop_write(); + } + + void on_read(size_t size) { + VLOG(fd) << get_native_fd() << " on read " << size; + CHECK(is_read_active_); + is_read_active_ = false; + if (size == 0) { + get_poll_info().add_flags_from_poll(PollFlags::Close()); + return; + } + input_writer_.confirm_append(size); + get_poll_info().add_flags_from_poll(PollFlags::Read()); + loop_read(); + } + + void on_write(size_t size) { + VLOG(fd) << get_native_fd() << " on write " << size; + if (size == 0) { + if (is_write_active_) { + return; + } + is_write_active_ = true; + } + CHECK(is_write_active_); + is_write_active_ = false; + output_reader_.advance(size); + loop_write(); + } + + void on_close() { + VLOG(fd) << get_native_fd() << " on close"; + close_flag_ = true; + info_.set_native_fd({}); + } + bool dec_refcnt() { + VLOG(fd) << get_native_fd() << " dec_refcnt from " << refcnt_; + if (--refcnt_ == 0) { + delete this; + return true; + } + return false; + } + void inc_refcnt() { + CHECK(refcnt_ != 0); + refcnt_++; + VLOG(fd) << get_native_fd() << " inc_refcnt to " << refcnt_; + } + + void notify_iocp_write() { + inc_refcnt(); + Iocp::get()->post(0, this, nullptr); + } + void notify_iocp_close() { + Iocp::get()->post(0, this, reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)); + } + void notify_iocp_connected() { + inc_refcnt(); + Iocp::get()->post(0, this, &read_overlapped_); + } +}; + +void SocketFdImplDeleter::operator()(SocketFdImpl *impl) { + impl->close(); } + +class InitWSA { + public: + InitWSA() { + /* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */ + WORD wVersionRequested = MAKEWORD(2, 2); + WSADATA wsaData; + if (WSAStartup(wVersionRequested, &wsaData) != 0) { + auto error = OS_SOCKET_ERROR("Failed to init WSA"); + LOG(FATAL) << error; + } + } +}; + +static InitWSA init_wsa; + +#else +class SocketFdImpl { + public: + PollableFdInfo info_; + explicit SocketFdImpl(NativeFd fd) : info_(std::move(fd)) { + } + PollableFdInfo &get_poll_info() { + return info_; + } + const PollableFdInfo &get_poll_info() const { + return info_; + } + + const NativeFd &get_native_fd() const { + return info_.native_fd(); + } + + Result<size_t> writev(Span<IoSlice> slices) { + int native_fd = get_native_fd().socket(); + TRY_RESULT(slices_size, narrow_cast_safe<int>(slices.size())); + auto write_res = detail::skip_eintr([&] { + // sendmsg can erroneously return 2^32 - 1 on Android 5.1 and Android 6.0, so it must not be used there +#if defined(MSG_NOSIGNAL) && !TD_ANDROID + msghdr msg; + std::memset(&msg, 0, sizeof(msg)); + msg.msg_iov = const_cast<iovec *>(slices.begin()); + msg.msg_iovlen = slices_size; + return sendmsg(native_fd, &msg, MSG_NOSIGNAL); +#else + return ::writev(native_fd, slices.begin(), slices_size); +#endif + }); + if (write_res >= 0) { + auto result = narrow_cast<size_t>(write_res); + auto left = result; + for (const auto &slice : slices) { + if (left <= slice.iov_len) { + return result; + } + left -= slice.iov_len; + } + LOG(FATAL) << "Receive " << write_res << " as writev response, but tried to write only " << result - left + << " bytes"; + } + return write_finish(); + } + + Result<size_t> write(Slice slice) { + int native_fd = get_native_fd().socket(); + auto write_res = detail::skip_eintr([&] { + return +#ifdef MSG_NOSIGNAL + send(native_fd, slice.begin(), slice.size(), MSG_NOSIGNAL); +#else + ::write(native_fd, slice.begin(), slice.size()); #endif + }); + if (write_res >= 0) { + auto result = narrow_cast<size_t>(write_res); + LOG_CHECK(result <= slice.size()) << "Receive " << write_res << " as write response, but tried to write only " + << slice.size() << " bytes"; + return result; + } + return write_finish(); + } -Status SocketFd::init(const IPAddress &address) { - auto fd = socket(address.get_address_family(), SOCK_STREAM, 0); -#if TD_PORT_POSIX - if (fd == -1) { -#elif TD_PORT_WINDOWS - if (fd == INVALID_SOCKET) { + Result<size_t> write_finish() { + auto write_errno = errno; + if (write_errno == EAGAIN +#if EAGAIN != EWOULDBLOCK + || write_errno == EWOULDBLOCK #endif - return OS_SOCKET_ERROR("Failed to create a socket"); + ) { + get_poll_info().clear_flags(PollFlags::Write()); + return 0; + } + + auto error = Status::PosixError(write_errno, PSLICE() << "Write to " << get_native_fd() << " has failed"); + switch (write_errno) { + case EBADF: + case ENXIO: + case EFAULT: + case EINVAL: + LOG(FATAL) << error; + UNREACHABLE(); + default: + LOG(WARNING) << error; + // fallthrough + case ECONNRESET: + case EDQUOT: + case EFBIG: + case EIO: + case ENETDOWN: + case ENETUNREACH: + case ENOSPC: + case EPIPE: + get_poll_info().clear_flags(PollFlags::Write()); + get_poll_info().add_flags(PollFlags::Close()); + return std::move(error); + } + } + Result<size_t> read(MutableSlice slice) { + if (get_poll_info().get_flags_local().has_pending_error()) { + TRY_STATUS(get_pending_error()); + } + int native_fd = get_native_fd().socket(); + CHECK(!slice.empty()); + auto read_res = detail::skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); }); + auto read_errno = errno; + if (read_res >= 0) { + if (read_res == 0) { + errno = 0; + get_poll_info().clear_flags(PollFlags::Read()); + get_poll_info().add_flags(PollFlags::Close()); + } + auto result = narrow_cast<size_t>(read_res); + CHECK(result <= slice.size()); + return result; + } + if (read_errno == EAGAIN +#if EAGAIN != EWOULDBLOCK + || read_errno == EWOULDBLOCK +#endif + ) { + get_poll_info().clear_flags(PollFlags::Read()); + return 0; + } + auto error = Status::PosixError(read_errno, PSLICE() << "Read from " << get_native_fd() << " has failed"); + switch (read_errno) { + case EISDIR: + case EBADF: + case ENXIO: + case EFAULT: + case EINVAL: + LOG(FATAL) << error; + UNREACHABLE(); + default: + LOG(WARNING) << error; + // fallthrough + case ENOTCONN: + case EIO: + case ENOBUFS: + case ENOMEM: + case ECONNRESET: + case ETIMEDOUT: + get_poll_info().clear_flags(PollFlags::Read()); + get_poll_info().add_flags(PollFlags::Close()); + return std::move(error); + } } - auto fd_quard = ScopeExit() + [fd]() { + Status get_pending_error() { + if (!get_poll_info().get_flags_local().has_pending_error()) { + return Status::OK(); + } + TRY_STATUS(detail::get_socket_pending_error(get_native_fd())); + get_poll_info().clear_flags(PollFlags::Error()); + return Status::OK(); + } +}; + +void SocketFdImplDeleter::operator()(SocketFdImpl *impl) { + delete impl; +} + +#endif + #if TD_PORT_POSIX - ::close(fd); +Status get_socket_pending_error(const NativeFd &fd) { + int error = 0; + socklen_t errlen = sizeof(error); + if (getsockopt(fd.socket(), SOL_SOCKET, SO_ERROR, static_cast<void *>(&error), &errlen) == 0) { + if (error == 0) { + return Status::OK(); + } + return Status::PosixError(error, PSLICE() << "Error on " << fd); + } + auto status = OS_SOCKET_ERROR(PSLICE() << "Can't load error on socket " << fd); + LOG(INFO) << "Can't load pending socket error: " << status; + return status; +} #elif TD_PORT_WINDOWS - ::closesocket(fd); +Status get_socket_pending_error(const NativeFd &fd, WSAOVERLAPPED *overlapped, Status iocp_error) { + // We need to call WSAGetOverlappedResult() just so WSAGetLastError() will return the correct error. See + // https://stackoverflow.com/questions/28925003/calling-wsagetlasterror-from-an-iocp-thread-return-incorrect-result + DWORD num_bytes = 0; + DWORD flags = 0; + BOOL success = WSAGetOverlappedResult(fd.socket(), overlapped, &num_bytes, false, &flags); + if (success) { + LOG(ERROR) << "WSAGetOverlappedResult succeded after " << iocp_error; + return iocp_error; + } + return OS_SOCKET_ERROR(PSLICE() << "Error on " << fd); +} #endif - }; - TRY_STATUS(detail::set_native_socket_is_blocking(fd, false)); +Status init_socket_options(NativeFd &native_fd) { + TRY_STATUS(native_fd.set_is_blocking_unsafe(false)); + auto sock = native_fd.socket(); #if TD_PORT_POSIX int flags = 1; #elif TD_PORT_WINDOWS BOOL flags = TRUE; #endif - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags)); - setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags)); - setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags)); + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags)); + setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, reinterpret_cast<const char *>(&flags), sizeof(flags)); + setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<const char *>(&flags), sizeof(flags)); +#if TD_PORT_POSIX +#ifndef MSG_NOSIGNAL // Darwin + +#ifdef SO_NOSIGPIPE + setsockopt(sock, SOL_SOCKET, SO_NOSIGPIPE, reinterpret_cast<const char *>(&flags), sizeof(flags)); +#else +#warning "Failed to suppress SIGPIPE signals. Use signal(SIGPIPE, SIG_IGN) to suppress them." +#endif + +#endif +#endif // TODO: SO_REUSEADDR, SO_KEEPALIVE, TCP_NODELAY, SO_SNDBUF, SO_RCVBUF, TCP_QUICKACK, SO_LINGER + return Status::OK(); +} + +} // namespace detail + +SocketFd::SocketFd() = default; +SocketFd::SocketFd(SocketFd &&) noexcept = default; +SocketFd &SocketFd::operator=(SocketFd &&) noexcept = default; +SocketFd::~SocketFd() = default; + +SocketFd::SocketFd(unique_ptr<detail::SocketFdImpl> impl) : impl_(impl.release()) { +} + +Result<SocketFd> SocketFd::from_native_fd(NativeFd fd) { + TRY_STATUS(detail::init_socket_options(fd)); + return SocketFd(make_unique<detail::SocketFdImpl>(std::move(fd))); +} + +Result<SocketFd> SocketFd::open(const IPAddress &address) { +#if TD_DARWIN_WATCH_OS + return SocketFd{}; +#endif + + NativeFd native_fd{socket(address.get_address_family(), SOCK_STREAM, IPPROTO_TCP)}; + if (!native_fd) { + return OS_SOCKET_ERROR("Failed to create a socket"); + } + TRY_STATUS(detail::init_socket_options(native_fd)); + #if TD_PORT_POSIX - int e_connect = connect(fd, address.get_sockaddr(), static_cast<socklen_t>(address.get_sockaddr_len())); + int e_connect = + connect(native_fd.socket(), address.get_sockaddr(), narrow_cast<socklen_t>(address.get_sockaddr_len())); if (e_connect == -1) { auto connect_errno = errno; if (connect_errno != EINPROGRESS) { return Status::PosixError(connect_errno, PSLICE() << "Failed to connect to " << address); } } - fd_ = Fd(fd, Fd::Mode::Owner); + return SocketFd(make_unique<detail::SocketFdImpl>(std::move(native_fd))); #elif TD_PORT_WINDOWS auto bind_addr = address.get_any_addr(); - auto e_bind = bind(fd, bind_addr.get_sockaddr(), narrow_cast<int>(bind_addr.get_sockaddr_len())); + auto e_bind = bind(native_fd.socket(), bind_addr.get_sockaddr(), narrow_cast<int>(bind_addr.get_sockaddr_len())); if (e_bind != 0) { return OS_SOCKET_ERROR("Failed to bind a socket"); } - - fd_ = Fd::create_socket_fd(fd); - fd_.connect(address); + return SocketFd(make_unique<detail::SocketFdImpl>(std::move(native_fd), address)); #endif - - fd_quard.dismiss(); - return Status::OK(); } -const Fd &SocketFd::get_fd() const { - return fd_; +void SocketFd::close() { + impl_.reset(); } -Fd &SocketFd::get_fd() { - return fd_; +bool SocketFd::empty() const { + return !impl_; } -void SocketFd::close() { - fd_.close(); +PollableFdInfo &SocketFd::get_poll_info() { + CHECK(!empty()); + return impl_->get_poll_info(); } - -bool SocketFd::empty() const { - return fd_.empty(); +const PollableFdInfo &SocketFd::get_poll_info() const { + CHECK(!empty()); + return impl_->get_poll_info(); } -int32 SocketFd::get_flags() const { - return fd_.get_flags(); +const NativeFd &SocketFd::get_native_fd() const { + CHECK(!empty()); + return impl_->get_native_fd(); } Status SocketFd::get_pending_error() { - return fd_.get_pending_error(); + CHECK(!empty()); + return impl_->get_pending_error(); } Result<size_t> SocketFd::write(Slice slice) { - return fd_.write(slice); + CHECK(!empty()); + return impl_->write(slice); +} + +Result<size_t> SocketFd::writev(Span<IoSlice> slices) { + CHECK(!empty()); + return impl_->writev(slices); } Result<size_t> SocketFd::read(MutableSlice slice) { - return fd_.read(slice); + CHECK(!empty()); + return impl_->read(slice); } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h index c88dd7d789..72aa80de24 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/SocketFd.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,50 +8,63 @@ #include "td/utils/port/config.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/IoSlice.h" #include "td/utils/port/IPAddress.h" #include "td/utils/Slice.h" +#include "td/utils/Span.h" #include "td/utils/Status.h" +#include <memory> + namespace td { +namespace detail { +class SocketFdImpl; +class SocketFdImplDeleter { + public: + void operator()(SocketFdImpl *impl); +}; +} // namespace detail + class SocketFd { public: - SocketFd() = default; + SocketFd(); SocketFd(const SocketFd &) = delete; SocketFd &operator=(const SocketFd &) = delete; - SocketFd(SocketFd &&) = default; - SocketFd &operator=(SocketFd &&) = default; + SocketFd(SocketFd &&) noexcept; + SocketFd &operator=(SocketFd &&) noexcept; + ~SocketFd(); static Result<SocketFd> open(const IPAddress &address) TD_WARN_UNUSED_RESULT; - const Fd &get_fd() const; - Fd &get_fd(); - - int32 get_flags() const; + PollableFdInfo &get_poll_info(); + const PollableFdInfo &get_poll_info() const; Status get_pending_error() TD_WARN_UNUSED_RESULT; Result<size_t> write(Slice slice) TD_WARN_UNUSED_RESULT; + Result<size_t> writev(Span<IoSlice> slices) TD_WARN_UNUSED_RESULT; Result<size_t> read(MutableSlice slice) TD_WARN_UNUSED_RESULT; + const NativeFd &get_native_fd() const; + static Result<SocketFd> from_native_fd(NativeFd fd); + void close(); bool empty() const; private: - Fd fd_; - - friend class ServerSocketFd; - - Status init(const IPAddress &address) TD_WARN_UNUSED_RESULT; + std::unique_ptr<detail::SocketFdImpl, detail::SocketFdImplDeleter> impl_; + explicit SocketFd(unique_ptr<detail::SocketFdImpl> impl); +}; +namespace detail { #if TD_PORT_POSIX - static Result<SocketFd> from_native_fd(int fd); +Status get_socket_pending_error(const NativeFd &fd); +#elif TD_PORT_WINDOWS +Status get_socket_pending_error(const NativeFd &fd, WSAOVERLAPPED *overlapped, Status iocp_error); #endif -#if TD_PORT_WINDOWS - explicit SocketFd(Fd fd) : fd_(std::move(fd)) { - } -#endif -}; +} // namespace detail } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp index edc882761b..3000598719 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,7 +14,9 @@ #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/port/Clocks.h" +#include "td/utils/port/detail/skip_eintr.h" #include "td/utils/ScopeGuard.h" +#include "td/utils/SliceBuilder.h" #include <utility> @@ -37,8 +39,24 @@ #include <sys/syscall.h> #endif +#elif TD_PORT_WINDOWS + +#include "td/utils/port/thread.h" + +#ifndef PSAPI_VERSION +#define PSAPI_VERSION 1 +#endif +#ifdef __MINGW32__ +#include <psapi.h> +#else +#include <Psapi.h> +#endif + +#endif + namespace td { +#if TD_PORT_POSIX namespace detail { template <class...> @@ -56,6 +74,19 @@ struct TimeNsec { } }; +// remove libc compatibility hacks if any: we have our own hacks +#ifdef st_atimespec +#undef st_atimespec +#endif + +#ifdef st_atimensec +#undef st_atimensec +#endif + +#ifdef st_atime_nsec +#undef st_atime_nsec +#endif + template <class T> struct TimeNsec<T, void_t<char, decltype(T::st_atimespec), decltype(T::st_mtimespec)>> { static std::pair<decltype(decltype(T::st_atimespec)::tv_nsec), decltype(decltype(T::st_mtimespec)::tv_nsec)> get( @@ -78,6 +109,13 @@ struct TimeNsec<T, void_t<int, decltype(T::st_atim), decltype(T::st_mtim)>> { } }; +template <class T> +struct TimeNsec<T, void_t<long, decltype(T::st_atime_nsec), decltype(T::st_mtime_nsec)>> { + static std::pair<decltype(T::st_atime_nsec), decltype(T::st_mtime_nsec)> get(const T &s) { + return {s.st_atime_nsec, s.st_mtime_nsec}; + } +}; + Stat from_native_stat(const struct ::stat &buf) { auto time_nsec = TimeNsec<struct ::stat>::get(buf); @@ -85,16 +123,17 @@ Stat from_native_stat(const struct ::stat &buf) { res.atime_nsec_ = static_cast<uint64>(buf.st_atime) * 1000000000 + time_nsec.first; res.mtime_nsec_ = static_cast<uint64>(buf.st_mtime) * 1000000000 + time_nsec.second / 1000 * 1000; res.size_ = buf.st_size; + res.real_size_ = buf.st_blocks * 512; res.is_dir_ = (buf.st_mode & S_IFMT) == S_IFDIR; res.is_reg_ = (buf.st_mode & S_IFMT) == S_IFREG; return res; } -Stat fstat(int native_fd) { +Result<Stat> fstat(int native_fd) { struct ::stat buf; - int err = fstat(native_fd, &buf); - auto fstat_errno = errno; - LOG_IF(FATAL, err < 0) << Status::PosixError(fstat_errno, PSLICE() << "stat for fd " << native_fd << " failed"); + if (detail::skip_eintr([&] { return ::fstat(native_fd, &buf); }) < 0) { + return OS_ERROR(PSLICE() << "Stat for fd " << native_fd << " failed"); + } return detail::from_native_stat(buf); } @@ -103,8 +142,10 @@ Status update_atime(int native_fd) { timespec times[2]; // access time times[0].tv_nsec = UTIME_NOW; + times[0].tv_sec = 0; // modify time times[1].tv_nsec = UTIME_OMIT; + times[1].tv_sec = 0; if (futimens(native_fd, times) < 0) { auto status = OS_ERROR(PSLICE() << "futimens " << tag("fd", native_fd)); LOG(WARNING) << status; @@ -112,7 +153,7 @@ Status update_atime(int native_fd) { } return Status::OK(); #elif TD_DARWIN - auto info = fstat(native_fd); + TRY_RESULT(info, fstat(native_fd)); timeval upd[2]; auto now = Clocks::system(); // access time @@ -150,15 +191,22 @@ Status update_atime(CSlice path) { SCOPE_EXIT { file.close(); }; - return detail::update_atime(file.get_native_fd()); + return detail::update_atime(file.get_native_fd().fd()); } +#endif Result<Stat> stat(CSlice path) { +#if TD_PORT_POSIX struct ::stat buf; - if (stat(path.c_str(), &buf) < 0) { - return OS_ERROR(PSLICE() << "stat for " << tag("file", path) << " failed"); + int err = detail::skip_eintr([&] { return ::stat(path.c_str(), &buf); }); + if (err < 0) { + return OS_ERROR(PSLICE() << "Stat for file \"" << path << "\" failed"); } return detail::from_native_stat(buf); +#elif TD_PORT_WINDOWS + TRY_RESULT(fd, FileFd::open(path, FileFd::Flags::Read | FileFd::PrivateFlags::WinStat)); + return fd.stat(); +#endif } Result<MemStat> mem_stat() { @@ -168,7 +216,7 @@ Result<MemStat> mem_stat() { if (KERN_SUCCESS != task_info(mach_task_self(), TASK_BASIC_INFO, reinterpret_cast<task_info_t>(&t_info), &t_info_count)) { - return Status::Error("task_info failed"); + return Status::Error("Call to task_info failed"); } MemStat res; res.resident_size_ = t_info.resident_size; @@ -226,7 +274,7 @@ Result<MemStat> mem_stat() { LOG(ERROR) << "Failed to parse memory stats " << tag("name", name) << tag("value", value); *x = static_cast<uint64>(-1); } else { - *x = r_mem.ok() * 1024; // memory is in kB + *x = r_mem.ok() * 1024; // memory is in KB } } if (*s == 0) { @@ -236,6 +284,21 @@ Result<MemStat> mem_stat() { } return res; +#elif TD_WINDOWS + PROCESS_MEMORY_COUNTERS_EX counters; + if (!GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast<PROCESS_MEMORY_COUNTERS *>(&counters), + sizeof(counters))) { + return Status::Error("Call to GetProcessMemoryInfo failed"); + } + + // Working set = all non-virtual memory in RAM, including memory-mapped files + // PrivateUsage = Commit charge = all non-virtual memory in RAM and swap file, but not in memory-mapped files + MemStat res; + res.resident_size_ = counters.WorkingSetSize; + res.resident_size_peak_ = counters.PeakWorkingSetSize; + res.virtual_size_ = counters.PrivateUsage; + res.virtual_size_peak_ = counters.PeakPagefileUsage; + return res; #else return Status::Error("Not supported"); #endif @@ -251,7 +314,9 @@ Status cpu_stat_self(CpuStat &stat) { constexpr int TMEM_SIZE = 10000; char mem[TMEM_SIZE]; TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1))); - CHECK(size < TMEM_SIZE - 1); + if (size >= TMEM_SIZE - 1) { + return Status::Error("Failed for read /proc/self/stat"); + } mem[size] = 0; char *s = mem; @@ -260,10 +325,10 @@ Status cpu_stat_self(CpuStat &stat) { while (pass_cnt < 15) { if (pass_cnt == 13) { - stat.process_user_ticks = to_integer<uint64>(Slice(s, t)); + stat.process_user_ticks_ = to_integer<uint64>(Slice(s, t)); } if (pass_cnt == 14) { - stat.process_system_ticks = to_integer<uint64>(Slice(s, t)); + stat.process_system_ticks_ = to_integer<uint64>(Slice(s, t)); } while (*s && *s != ' ') { s++; @@ -272,11 +337,12 @@ Status cpu_stat_self(CpuStat &stat) { s++; pass_cnt++; } else { - return Status::Error("unexpected end of proc file"); + return Status::Error("Unexpected end of proc file"); } } return Status::OK(); } + Status cpu_stat_total(CpuStat &stat) { TRY_RESULT(fd, FileFd::open("/proc/stat", FileFd::Read)); SCOPE_EXIT { @@ -286,14 +352,17 @@ Status cpu_stat_total(CpuStat &stat) { constexpr int TMEM_SIZE = 10000; char mem[TMEM_SIZE]; TRY_RESULT(size, fd.read(MutableSlice(mem, TMEM_SIZE - 1))); - CHECK(size < TMEM_SIZE - 1); + if (size >= TMEM_SIZE - 1) { + return Status::Error("Failed for read /proc/stat"); + } mem[size] = 0; - uint64 sum = 0, cur = 0; + uint64 sum = 0; + uint64 cur = 0; for (size_t i = 0; i < size; i++) { - int c = mem[i]; + char c = mem[i]; if (c >= '0' && c <= '9') { - cur = cur * 10 + (uint64)c - '0'; + cur = cur * 10 + static_cast<uint64>(c) - '0'; } else { sum += cur; cur = 0; @@ -303,7 +372,7 @@ Status cpu_stat_total(CpuStat &stat) { } } - stat.total_ticks = sum; + stat.total_ticks_ = sum; return Status::OK(); } #endif @@ -314,24 +383,27 @@ Result<CpuStat> cpu_stat() { TRY_STATUS(cpu_stat_self(stat)); TRY_STATUS(cpu_stat_total(stat)); return stat; -#else - return Status::Error("Not supported"); -#endif -} -} // namespace td -#endif - -#if TD_PORT_WINDOWS -namespace td { +#elif TD_WINDOWS + CpuStat stat; + stat.total_ticks_ = static_cast<uint64>(GetTickCount64()) * 10000; + auto hardware_concurrency = thread::hardware_concurrency(); + if (hardware_concurrency != 0) { + stat.total_ticks_ *= hardware_concurrency; + } -Result<Stat> stat(CSlice path) { - TRY_RESULT(fd, FileFd::open(path, FileFd::Flags::Read)); - return fd.stat(); -} + FILETIME ignored_time; + FILETIME kernel_time; + FILETIME user_time; + if (!GetProcessTimes(GetCurrentProcess(), &ignored_time, &ignored_time, &kernel_time, &user_time)) { + return Status::Error("Failed to call GetProcessTimes"); + } + stat.process_system_ticks_ = kernel_time.dwLowDateTime + (static_cast<uint64>(kernel_time.dwHighDateTime) << 32); + stat.process_user_ticks_ = user_time.dwLowDateTime + (static_cast<uint64>(user_time.dwHighDateTime) << 32); -Result<CpuStat> cpu_stat() { + return stat; +#else return Status::Error("Not supported"); +#endif } } // namespace td -#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h index d0a98db141..ed8fc3de82 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/Stat.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -18,6 +18,7 @@ struct Stat { bool is_dir_; bool is_reg_; int64 size_; + int64 real_size_; uint64 atime_nsec_; uint64 mtime_nsec_; }; @@ -25,19 +26,12 @@ struct Stat { Result<Stat> stat(CSlice path) TD_WARN_UNUSED_RESULT; struct CpuStat { - uint64 total_ticks{0}; - uint64 process_user_ticks{0}; - uint64 process_system_ticks{0}; + uint64 total_ticks_{0}; + uint64 process_user_ticks_{0}; + uint64 process_system_ticks_{0}; }; -Result<CpuStat> cpu_stat() TD_WARN_UNUSED_RESULT; - -#if TD_PORT_POSIX - -namespace detail { -Stat fstat(int native_fd); // TODO return Result<Stat> -} // namespace detail -Status update_atime(CSlice path) TD_WARN_UNUSED_RESULT; +Result<CpuStat> cpu_stat() TD_WARN_UNUSED_RESULT; struct MemStat { uint64 resident_size_ = 0; @@ -48,6 +42,14 @@ struct MemStat { Result<MemStat> mem_stat() TD_WARN_UNUSED_RESULT; +#if TD_PORT_POSIX + +namespace detail { +Result<Stat> fstat(int native_fd); +} // namespace detail + +Status update_atime(CSlice path) TD_WARN_UNUSED_RESULT; + #endif } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.cpp new file mode 100644 index 0000000000..b2064a9fe6 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.cpp @@ -0,0 +1,251 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/StdStreams.h" + +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/port/detail/Iocp.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/PollFlags.h" +#include "td/utils/port/thread.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" + +#include <atomic> + +namespace td { + +#if TD_PORT_POSIX +template <int id> +static FileFd &get_file_fd() { + static FileFd result = FileFd::from_native_fd(NativeFd(id, true)); + static auto guard = ScopeExit() + [&] { + result.move_as_native_fd().release(); + }; + return result; +} + +FileFd &Stdin() { + return get_file_fd<0>(); +} +FileFd &Stdout() { + return get_file_fd<1>(); +} +FileFd &Stderr() { + return get_file_fd<2>(); +} +#elif TD_PORT_WINDOWS +template <DWORD id> +static FileFd &get_file_fd() { +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) + static auto handle = GetStdHandle(id); + LOG_IF(FATAL, handle == INVALID_HANDLE_VALUE) << "Failed to GetStdHandle " << id; + static FileFd result = handle == nullptr ? FileFd() : FileFd::from_native_fd(NativeFd(handle, true)); + static auto guard = ScopeExit() + [&] { + if (handle != nullptr) { + result.move_as_native_fd().release(); + } + }; +#else + static FileFd result; +#endif + return result; +} + +FileFd &Stdin() { + return get_file_fd<STD_INPUT_HANDLE>(); +} +FileFd &Stdout() { + return get_file_fd<STD_OUTPUT_HANDLE>(); +} +FileFd &Stderr() { + return get_file_fd<STD_ERROR_HANDLE>(); +} +#endif + +#if TD_PORT_WINDOWS +namespace detail { +class BufferedStdinImpl final : private Iocp::Callback { + public: +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) + BufferedStdinImpl() : info_(NativeFd(GetStdHandle(STD_INPUT_HANDLE), true)) { + iocp_ref_ = Iocp::get()->get_ref(); + read_thread_ = thread([this] { this->read_loop(); }); + } +#else + BufferedStdinImpl() { + close(); + } +#endif + BufferedStdinImpl(const BufferedStdinImpl &) = delete; + BufferedStdinImpl &operator=(const BufferedStdinImpl &) = delete; + BufferedStdinImpl(BufferedStdinImpl &&) = delete; + BufferedStdinImpl &operator=(BufferedStdinImpl &&) = delete; + ~BufferedStdinImpl() { + info_.move_as_native_fd().release(); + } + void close() { + close_flag_ = true; + } + + ChainBufferReader &input_buffer() { + return reader_; + } + + PollableFdInfo &get_poll_info() { + return info_; + } + const PollableFdInfo &get_poll_info() const { + return info_; + } + + Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT { + info_.sync_with_poll(); + info_.clear_flags(PollFlags::Read()); + reader_.sync_with_writer(); + return reader_.size(); + } + + private: + PollableFdInfo info_; + ChainBufferWriter writer_; + ChainBufferReader reader_ = writer_.extract_reader(); + thread read_thread_; + std::atomic<bool> close_flag_{false}; + IocpRef iocp_ref_; + std::atomic<int> refcnt_{1}; + + void read_loop() { + while (!close_flag_) { + auto slice = writer_.prepare_append(); + auto r_size = read(slice); + if (r_size.is_error()) { + LOG(ERROR) << "Stop read stdin loop: " << r_size.error(); + break; + } + writer_.confirm_append(r_size.ok()); + inc_refcnt(); + if (!iocp_ref_.post(0, this, nullptr)) { + dec_refcnt(); + } + } + if (!iocp_ref_.post(0, this, nullptr)) { + read_thread_.detach(); + dec_refcnt(); + } + } + void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) final { + info_.add_flags_from_poll(PollFlags::Read()); + dec_refcnt(); + } + + bool dec_refcnt() { + if (--refcnt_ == 0) { + delete this; + return true; + } + return false; + } + void inc_refcnt() { + CHECK(refcnt_ != 0); + refcnt_++; + } + + Result<size_t> read(MutableSlice slice) { + auto native_fd = info_.native_fd().fd(); + DWORD bytes_read = 0; + auto res = ReadFile(native_fd, slice.data(), narrow_cast<DWORD>(slice.size()), &bytes_read, nullptr); + if (res) { + return static_cast<size_t>(bytes_read); + } + return OS_ERROR(PSLICE() << "Read from " << info_.native_fd() << " has failed"); + } +}; +void BufferedStdinImplDeleter::operator()(BufferedStdinImpl *impl) { + // LOG(ERROR) << "Close"; + impl->close(); +} +} // namespace detail +#elif TD_PORT_POSIX +namespace detail { +class BufferedStdinImpl { + public: + BufferedStdinImpl() { + file_fd_ = FileFd::from_native_fd(NativeFd(Stdin().get_native_fd().fd())); + file_fd_.get_native_fd().set_is_blocking(false); + } + BufferedStdinImpl(const BufferedStdinImpl &) = delete; + BufferedStdinImpl &operator=(const BufferedStdinImpl &) = delete; + BufferedStdinImpl(BufferedStdinImpl &&) = delete; + BufferedStdinImpl &operator=(BufferedStdinImpl &&) = delete; + ~BufferedStdinImpl() { + file_fd_.get_native_fd().set_is_blocking(true); + file_fd_.move_as_native_fd().release(); + } + + ChainBufferReader &input_buffer() { + return reader_; + } + + PollableFdInfo &get_poll_info() { + return file_fd_.get_poll_info(); + } + const PollableFdInfo &get_poll_info() const { + return file_fd_.get_poll_info(); + } + + Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT { + size_t result = 0; + ::td::sync_with_poll(*this); + while (::td::can_read_local(*this) && max_read) { + MutableSlice slice = writer_.prepare_append(); + slice.truncate(max_read); + TRY_RESULT(x, file_fd_.read(slice)); + slice.truncate(x); + writer_.confirm_append(x); + result += x; + max_read -= x; + } + if (result) { + reader_.sync_with_writer(); + } + return result; + } + + private: + FileFd file_fd_; + ChainBufferWriter writer_; + ChainBufferReader reader_ = writer_.extract_reader(); +}; +void BufferedStdinImplDeleter::operator()(BufferedStdinImpl *impl) { + delete impl; +} +} // namespace detail +#endif + +BufferedStdin::BufferedStdin() : impl_(make_unique<detail::BufferedStdinImpl>().release()) { +} +BufferedStdin::BufferedStdin(BufferedStdin &&) noexcept = default; +BufferedStdin &BufferedStdin::operator=(BufferedStdin &&) noexcept = default; +BufferedStdin::~BufferedStdin() = default; + +ChainBufferReader &BufferedStdin::input_buffer() { + return impl_->input_buffer(); +} +PollableFdInfo &BufferedStdin::get_poll_info() { + return impl_->get_poll_info(); +} +const PollableFdInfo &BufferedStdin::get_poll_info() const { + return impl_->get_poll_info(); +} +Result<size_t> BufferedStdin::flush_read(size_t max_read) { + return impl_->flush_read(max_read); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.h new file mode 100644 index 0000000000..0922594c9a --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/StdStreams.h @@ -0,0 +1,49 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/buffer.h" +#include "td/utils/common.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/FileFd.h" +#include "td/utils/Status.h" + +#include <limits> +#include <memory> + +namespace td { + +FileFd &Stdin(); +FileFd &Stdout(); +FileFd &Stderr(); + +namespace detail { +class BufferedStdinImpl; +class BufferedStdinImplDeleter { + public: + void operator()(BufferedStdinImpl *impl); +}; +} // namespace detail + +class BufferedStdin { + public: + BufferedStdin(); + BufferedStdin(const BufferedStdin &) = delete; + BufferedStdin &operator=(const BufferedStdin &) = delete; + BufferedStdin(BufferedStdin &&) noexcept; + BufferedStdin &operator=(BufferedStdin &&) noexcept; + ~BufferedStdin(); + ChainBufferReader &input_buffer(); + PollableFdInfo &get_poll_info(); + const PollableFdInfo &get_poll_info() const; + Result<size_t> flush_read(size_t max_read = std::numeric_limits<size_t>::max()) TD_WARN_UNUSED_RESULT; + + private: + std::unique_ptr<detail::BufferedStdinImpl, detail::BufferedStdinImplDeleter> impl_; +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.cpp new file mode 100644 index 0000000000..eee487d44b --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.cpp @@ -0,0 +1,873 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/UdpSocketFd.h" + +#include "td/utils/common.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/port/detail/skip_eintr.h" +#include "td/utils/port/PollFlags.h" +#include "td/utils/port/SocketFd.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/VectorQueue.h" + +#if TD_PORT_WINDOWS +#include "td/utils/port/detail/Iocp.h" +#include "td/utils/port/Mutex.h" +#endif + +#if TD_PORT_POSIX +#include <cerrno> + +#include <arpa/inet.h> +#include <fcntl.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#if TD_LINUX +#include <linux/errqueue.h> +#endif +#endif + +#include <array> +#include <atomic> +#include <cstring> + +namespace td { +namespace detail { +#if TD_PORT_WINDOWS +class UdpSocketReceiveHelper { + public: + void to_native(const UdpMessage &message, WSAMSG &message_header) { + socklen_t addr_len{narrow_cast<socklen_t>(sizeof(addr_))}; + message_header.name = reinterpret_cast<sockaddr *>(&addr_); + message_header.namelen = addr_len; + buf_.buf = const_cast<char *>(message.data.as_slice().begin()); + buf_.len = narrow_cast<DWORD>(message.data.size()); + message_header.lpBuffers = &buf_; + message_header.dwBufferCount = 1; + message_header.Control.buf = nullptr; // control_buf_.data(); + message_header.Control.len = 0; // narrow_cast<decltype(message_header.Control.len)>(control_buf_.size()); + message_header.dwFlags = 0; + } + + static void from_native(WSAMSG &message_header, size_t message_size, UdpMessage &message) { + message.address.init_sockaddr(reinterpret_cast<sockaddr *>(message_header.name), message_header.namelen).ignore(); + message.error = Status::OK(); + + if ((message_header.dwFlags & (MSG_TRUNC | MSG_CTRUNC)) != 0) { + message.error = Status::Error(501, "Message too long"); + message.data = BufferSlice(); + return; + } + + CHECK(message_size <= message.data.size()); + message.data.truncate(message_size); + CHECK(message_size == message.data.size()); + } + + private: + std::array<char, 1024> control_buf_; + sockaddr_storage addr_; + WSABUF buf_; +}; + +class UdpSocketSendHelper { + public: + void to_native(const UdpMessage &message, WSAMSG &message_header) { + message_header.name = const_cast<sockaddr *>(message.address.get_sockaddr()); + message_header.namelen = narrow_cast<socklen_t>(message.address.get_sockaddr_len()); + buf_.buf = const_cast<char *>(message.data.as_slice().begin()); + buf_.len = narrow_cast<DWORD>(message.data.size()); + message_header.lpBuffers = &buf_; + message_header.dwBufferCount = 1; + + message_header.Control.buf = nullptr; + message_header.Control.len = 0; + message_header.dwFlags = 0; + } + + private: + WSABUF buf_; +}; + +class UdpSocketFdImpl final : private Iocp::Callback { + public: + explicit UdpSocketFdImpl(NativeFd fd) : info_(std::move(fd)) { + get_poll_info().add_flags(PollFlags::Write()); + Iocp::get()->subscribe(get_native_fd(), this); + is_receive_active_ = true; + notify_iocp_connected(); + } + PollableFdInfo &get_poll_info() { + return info_; + } + const PollableFdInfo &get_poll_info() const { + return info_; + } + + const NativeFd &get_native_fd() const { + return info_.native_fd(); + } + + void close() { + notify_iocp_close(); + } + + Result<optional<UdpMessage>> receive() { + auto lock = lock_.lock(); + if (!pending_errors_.empty()) { + auto status = pending_errors_.pop(); + if (!UdpSocketFd::is_critical_read_error(status)) { + return UdpMessage{{}, {}, std::move(status)}; + } + return std::move(status); + } + if (!receive_queue_.empty()) { + return receive_queue_.pop(); + } + + return optional<UdpMessage>{}; + } + + void send(UdpMessage message) { + auto lock = lock_.lock(); + send_queue_.push(std::move(message)); + } + + Status flush_send() { + if (is_send_waiting_) { + auto lock = lock_.lock(); + is_send_waiting_ = false; + notify_iocp_send(); + } + return Status::OK(); + } + + private: + PollableFdInfo info_; + Mutex lock_; + + std::atomic<int> refcnt_{1}; + bool is_connected_{false}; + bool close_flag_{false}; + + bool is_send_active_{false}; + bool is_send_waiting_{false}; + VectorQueue<UdpMessage> send_queue_; + WSAOVERLAPPED send_overlapped_; + + bool is_receive_active_{false}; + VectorQueue<UdpMessage> receive_queue_; + VectorQueue<Status> pending_errors_; + UdpMessage to_receive_; + WSAMSG receive_message_; + UdpSocketReceiveHelper receive_helper_; + static constexpr size_t MAX_PACKET_SIZE = 2048; + static constexpr size_t RESERVED_SIZE = MAX_PACKET_SIZE * 8; + BufferSlice receive_buffer_; + + UdpMessage to_send_; + WSAOVERLAPPED receive_overlapped_; + + char close_overlapped_; + + bool check_status(Slice message) { + auto last_error = WSAGetLastError(); + if (last_error == ERROR_IO_PENDING) { + return true; + } + on_error(OS_SOCKET_ERROR(message)); + return false; + } + + void loop_receive() { + CHECK(!is_receive_active_); + if (close_flag_) { + return; + } + std::memset(&receive_overlapped_, 0, sizeof(receive_overlapped_)); + if (receive_buffer_.size() < MAX_PACKET_SIZE) { + receive_buffer_ = BufferSlice(RESERVED_SIZE); + } + to_receive_.data = receive_buffer_.clone(); + receive_helper_.to_native(to_receive_, receive_message_); + + LPFN_WSARECVMSG WSARecvMsgPtr = nullptr; + GUID guid = WSAID_WSARECVMSG; + DWORD numBytes; + auto error = ::WSAIoctl(get_native_fd().socket(), SIO_GET_EXTENSION_FUNCTION_POINTER, static_cast<void *>(&guid), + sizeof(guid), static_cast<void *>(&WSARecvMsgPtr), sizeof(WSARecvMsgPtr), &numBytes, + nullptr, nullptr); + if (error) { + on_error(OS_SOCKET_ERROR("WSAIoctl failed")); + return; + } + + auto status = WSARecvMsgPtr(get_native_fd().socket(), &receive_message_, nullptr, &receive_overlapped_, nullptr); + if (status == 0 || check_status("WSARecvMsg failed")) { + inc_refcnt(); + is_receive_active_ = true; + } + } + + void loop_send() { + CHECK(!is_send_active_); + + { + auto lock = lock_.lock(); + if (send_queue_.empty()) { + is_send_waiting_ = true; + return; + } + to_send_ = send_queue_.pop(); + } + std::memset(&send_overlapped_, 0, sizeof(send_overlapped_)); + WSAMSG message; + UdpSocketSendHelper send_helper; + send_helper.to_native(to_send_, message); + auto status = WSASendMsg(get_native_fd().socket(), &message, 0, nullptr, &send_overlapped_, nullptr); + if (status == 0 || check_status("WSASendMsg failed")) { + inc_refcnt(); + is_send_active_ = true; + } + } + + void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) final { + // called from other thread + if (dec_refcnt() || close_flag_) { + VLOG(fd) << "Ignore IOCP (UDP socket is closing)"; + return; + } + if (r_size.is_error()) { + return on_error(get_socket_pending_error(get_native_fd(), overlapped, r_size.move_as_error())); + } + + if (!is_connected_ && overlapped == &receive_overlapped_) { + return on_connected(); + } + + auto size = r_size.move_as_ok(); + if (overlapped == &send_overlapped_) { + return on_send(size); + } + if (overlapped == nullptr) { + CHECK(size == 0); + return on_send(size); + } + + if (overlapped == &receive_overlapped_) { + return on_receive(size); + } + if (overlapped == reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)) { + return on_close(); + } + UNREACHABLE(); + } + + void on_error(Status status) { + VLOG(fd) << get_native_fd() << " on error " << status; + { + auto lock = lock_.lock(); + pending_errors_.push(std::move(status)); + } + get_poll_info().add_flags_from_poll(PollFlags::Error()); + } + + void on_connected() { + VLOG(fd) << get_native_fd() << " on connected"; + CHECK(!is_connected_); + CHECK(is_receive_active_); + is_connected_ = true; + is_receive_active_ = false; + loop_receive(); + loop_send(); + } + + void on_receive(size_t size) { + VLOG(fd) << get_native_fd() << " on receive " << size; + CHECK(is_receive_active_); + is_receive_active_ = false; + UdpSocketReceiveHelper::from_native(receive_message_, size, to_receive_); + receive_buffer_.confirm_read((to_receive_.data.size() + 7) & ~7); + { + auto lock = lock_.lock(); + // LOG(ERROR) << format::escaped(to_receive_.data.as_slice()); + receive_queue_.push(std::move(to_receive_)); + } + get_poll_info().add_flags_from_poll(PollFlags::Read()); + loop_receive(); + } + + void on_send(size_t size) { + VLOG(fd) << get_native_fd() << " on send " << size; + if (size == 0) { + if (is_send_active_) { + return; + } + is_send_active_ = true; + } + CHECK(is_send_active_); + is_send_active_ = false; + loop_send(); + } + + void on_close() { + VLOG(fd) << get_native_fd() << " on close"; + close_flag_ = true; + info_.set_native_fd({}); + } + + bool dec_refcnt() { + if (--refcnt_ == 0) { + delete this; + return true; + } + return false; + } + + void inc_refcnt() { + CHECK(refcnt_ != 0); + refcnt_++; + } + + void notify_iocp_send() { + inc_refcnt(); + Iocp::get()->post(0, this, nullptr); + } + void notify_iocp_close() { + Iocp::get()->post(0, this, reinterpret_cast<WSAOVERLAPPED *>(&close_overlapped_)); + } + void notify_iocp_connected() { + inc_refcnt(); + Iocp::get()->post(0, this, reinterpret_cast<WSAOVERLAPPED *>(&receive_overlapped_)); + } +}; + +void UdpSocketFdImplDeleter::operator()(UdpSocketFdImpl *impl) { + impl->close(); +} + +#elif TD_PORT_POSIX +//struct iovec { [> Scatter/gather array items <] +// void *iov_base; [> Starting address <] +// size_t iov_len; [> Number of bytes to transfer <] +//}; + +//struct msghdr { +// void *msg_name; [> optional address <] +// socklen_t msg_namelen; [> size of address <] +// struct iovec *msg_iov; [> scatter/gather array <] +// size_t msg_iovlen; [> # elements in msg_iov <] +// void *msg_control; [> ancillary data, see below <] +// size_t msg_controllen; [> ancillary data buffer len <] +// int msg_flags; [> flags on received message <] +//}; + +class UdpSocketReceiveHelper { + public: + void to_native(const UdpSocketFd::InboundMessage &message, msghdr &message_header) { + socklen_t addr_len{narrow_cast<socklen_t>(sizeof(addr_))}; + + message_header.msg_name = &addr_; + message_header.msg_namelen = addr_len; + io_vec_.iov_base = message.data.begin(); + io_vec_.iov_len = message.data.size(); + message_header.msg_iov = &io_vec_; + message_header.msg_iovlen = 1; + message_header.msg_control = control_buf_.data(); + message_header.msg_controllen = narrow_cast<decltype(message_header.msg_controllen)>(control_buf_.size()); + message_header.msg_flags = 0; + } + + static void from_native(msghdr &message_header, size_t message_size, UdpSocketFd::InboundMessage &message) { +#if TD_LINUX + cmsghdr *cmsg; + sock_extended_err *ee = nullptr; + for (cmsg = CMSG_FIRSTHDR(&message_header); cmsg != nullptr; cmsg = CMSG_NXTHDR(&message_header, cmsg)) { + if (cmsg->cmsg_type == IP_PKTINFO && cmsg->cmsg_level == IPPROTO_IP) { + //auto *pi = reinterpret_cast<in_pktinfo *>(CMSG_DATA(cmsg)); + } else if (cmsg->cmsg_type == IPV6_PKTINFO && cmsg->cmsg_level == IPPROTO_IPV6) { + //auto *pi = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsg)); + } else if ((cmsg->cmsg_type == IP_RECVERR && cmsg->cmsg_level == IPPROTO_IP) || + (cmsg->cmsg_type == IPV6_RECVERR && cmsg->cmsg_level == IPPROTO_IPV6)) { + ee = reinterpret_cast<sock_extended_err *>(CMSG_DATA(cmsg)); + } + } + if (ee != nullptr) { + auto *addr = reinterpret_cast<sockaddr *>(SO_EE_OFFENDER(ee)); + IPAddress address; + address.init_sockaddr(addr).ignore(); + if (message.from != nullptr) { + *message.from = address; + } + if (message.error) { + *message.error = Status::PosixError(ee->ee_errno, ""); + } + //message.data = MutableSlice(); + message.data.truncate(0); + return; + } +#endif + if (message.from != nullptr) { + message.from->init_sockaddr(reinterpret_cast<sockaddr *>(message_header.msg_name), message_header.msg_namelen) + .ignore(); + } + if (message.error) { + *message.error = Status::OK(); + } + if (message_header.msg_flags & MSG_TRUNC) { + if (message.error) { + *message.error = Status::Error(501, "Message too long"); + } + message.data.truncate(0); + return; + } + CHECK(message_size <= message.data.size()); + message.data.truncate(message_size); + CHECK(message_size == message.data.size()); + } + + private: + std::array<char, 1024> control_buf_; + sockaddr_storage addr_; + iovec io_vec_; +}; + +class UdpSocketSendHelper { + public: + void to_native(const UdpSocketFd::OutboundMessage &message, msghdr &message_header) { + CHECK(message.to != nullptr && message.to->is_valid()); + message_header.msg_name = const_cast<sockaddr *>(message.to->get_sockaddr()); + message_header.msg_namelen = narrow_cast<socklen_t>(message.to->get_sockaddr_len()); + io_vec_.iov_base = const_cast<char *>(message.data.begin()); + io_vec_.iov_len = message.data.size(); + message_header.msg_iov = &io_vec_; + message_header.msg_iovlen = 1; + //TODO + message_header.msg_control = nullptr; + message_header.msg_controllen = 0; + message_header.msg_flags = 0; + } + + private: + iovec io_vec_; +}; + +class UdpSocketFdImpl { + public: + explicit UdpSocketFdImpl(NativeFd fd) : info_(std::move(fd)) { + } + PollableFdInfo &get_poll_info() { + return info_; + } + const PollableFdInfo &get_poll_info() const { + return info_; + } + + const NativeFd &get_native_fd() const { + return info_.native_fd(); + } + Status get_pending_error() { + if (!get_poll_info().get_flags_local().has_pending_error()) { + return Status::OK(); + } + TRY_STATUS(detail::get_socket_pending_error(get_native_fd())); + get_poll_info().clear_flags(PollFlags::Error()); + return Status::OK(); + } + Status receive_message(UdpSocketFd::InboundMessage &message, bool &is_received) { + is_received = false; + int flags = 0; + if (get_poll_info().get_flags_local().has_pending_error()) { +#ifdef MSG_ERRQUEUE + flags = MSG_ERRQUEUE; +#else + return get_pending_error(); +#endif + } + + msghdr message_header; + detail::UdpSocketReceiveHelper helper; + helper.to_native(message, message_header); + + auto native_fd = get_native_fd().socket(); + auto recvmsg_res = detail::skip_eintr([&] { return recvmsg(native_fd, &message_header, flags); }); + auto recvmsg_errno = errno; + if (recvmsg_res >= 0) { + UdpSocketReceiveHelper::from_native(message_header, recvmsg_res, message); + is_received = true; + return Status::OK(); + } + return process_recvmsg_error(recvmsg_errno, is_received); + } + + Status process_recvmsg_error(int recvmsg_errno, bool &is_received) { + is_received = false; + if (recvmsg_errno == EAGAIN +#if EAGAIN != EWOULDBLOCK + || recvmsg_errno == EWOULDBLOCK +#endif + ) { + if (get_poll_info().get_flags_local().has_pending_error()) { + get_poll_info().clear_flags(PollFlags::Error()); + } else { + get_poll_info().clear_flags(PollFlags::Read()); + } + return Status::OK(); + } + + auto error = Status::PosixError(recvmsg_errno, PSLICE() << "Receive from " << get_native_fd() << " has failed"); + switch (recvmsg_errno) { + case EBADF: + case EFAULT: + case EINVAL: + case ENOTCONN: + case ECONNRESET: + case ETIMEDOUT: + LOG(FATAL) << error; + UNREACHABLE(); + default: + LOG(WARNING) << "Unknown error: " << error; + // fallthrough + case ENOBUFS: + case ENOMEM: +#ifdef MSG_ERRQUEUE + get_poll_info().add_flags(PollFlags::Error()); +#endif + return error; + } + } + + Status send_message(const UdpSocketFd::OutboundMessage &message, bool &is_sent) { + is_sent = false; + msghdr message_header; + detail::UdpSocketSendHelper helper; + helper.to_native(message, message_header); + + auto native_fd = get_native_fd().socket(); + auto sendmsg_res = detail::skip_eintr([&] { return sendmsg(native_fd, &message_header, 0); }); + auto sendmsg_errno = errno; + if (sendmsg_res >= 0) { + is_sent = true; + return Status::OK(); + } + return process_sendmsg_error(sendmsg_errno, is_sent); + } + Status process_sendmsg_error(int sendmsg_errno, bool &is_sent) { + if (sendmsg_errno == EAGAIN +#if EAGAIN != EWOULDBLOCK + || sendmsg_errno == EWOULDBLOCK +#endif + ) { + get_poll_info().clear_flags(PollFlags::Write()); + return Status::OK(); + } + + auto error = Status::PosixError(sendmsg_errno, PSLICE() << "Send from " << get_native_fd() << " has failed"); + switch (sendmsg_errno) { + // Still may send some other packets, but there is no point to resend this particular message + case EACCES: + case EMSGSIZE: + case EPERM: + LOG(WARNING) << "Silently drop packet :( " << error; + //TODO: get errors from MSG_ERRQUEUE is possible + is_sent = true; + return error; + + // Some general problems, which may be fixed in future + case ENOMEM: + case EDQUOT: + case EFBIG: + case ENETDOWN: + case ENETUNREACH: + case ENOSPC: + case EHOSTUNREACH: + case ENOBUFS: + default: +#ifdef MSG_ERRQUEUE + get_poll_info().add_flags(PollFlags::Error()); +#endif + return error; + + case EBADF: // impossible + case ENOTSOCK: // impossible + case EPIPE: // impossible for udp + case ECONNRESET: // impossible for udp + case EDESTADDRREQ: // we checked that address is valid + case ENOTCONN: // we checked that address is valid + case EINTR: // we already skipped all EINTR + case EISCONN: // impossible for udp socket + case EOPNOTSUPP: + case ENOTDIR: + case EFAULT: + case EINVAL: + case EAFNOSUPPORT: + LOG(FATAL) << error; + UNREACHABLE(); + return error; + } + } + + Status send_messages(Span<UdpSocketFd::OutboundMessage> messages, size_t &cnt) { +#if TD_HAS_MMSG + return send_messages_fast(messages, cnt); +#else + return send_messages_slow(messages, cnt); +#endif + } + + Status receive_messages(MutableSpan<UdpSocketFd::InboundMessage> messages, size_t &cnt) { +#if TD_HAS_MMSG + return receive_messages_fast(messages, cnt); +#else + return receive_messages_slow(messages, cnt); +#endif + } + + private: + PollableFdInfo info_; + + Status send_messages_slow(Span<UdpSocketFd::OutboundMessage> messages, size_t &cnt) { + cnt = 0; + for (auto &message : messages) { + CHECK(!message.data.empty()); + bool is_sent; + auto error = send_message(message, is_sent); + cnt += is_sent; + TRY_STATUS(std::move(error)); + } + return Status::OK(); + } + +#if TD_HAS_MMSG + Status send_messages_fast(Span<UdpSocketFd::OutboundMessage> messages, size_t &cnt) { + //struct mmsghdr { + // msghdr msg_hdr; [> Message header <] + // unsigned int msg_len; [> Number of bytes transmitted <] + //}; + std::array<detail::UdpSocketSendHelper, 16> helpers; + std::array<mmsghdr, 16> headers; + size_t to_send = min(messages.size(), headers.size()); + for (size_t i = 0; i < to_send; i++) { + helpers[i].to_native(messages[i], headers[i].msg_hdr); + headers[i].msg_len = 0; + } + + auto native_fd = get_native_fd().socket(); + auto sendmmsg_res = + detail::skip_eintr([&] { return sendmmsg(native_fd, headers.data(), narrow_cast<unsigned int>(to_send), 0); }); + auto sendmmsg_errno = errno; + if (sendmmsg_res >= 0) { + cnt = sendmmsg_res; + return Status::OK(); + } + + bool is_sent = false; + auto status = process_sendmsg_error(sendmmsg_errno, is_sent); + cnt = is_sent; + return status; + } +#endif + Status receive_messages_slow(MutableSpan<UdpSocketFd::InboundMessage> messages, size_t &cnt) { + cnt = 0; + while (cnt < messages.size() && get_poll_info().get_flags_local().can_read()) { + auto &message = messages[cnt]; + CHECK(!message.data.empty()); + bool is_received; + auto error = receive_message(message, is_received); + cnt += is_received; + TRY_STATUS(std::move(error)); + } + return Status::OK(); + } + +#if TD_HAS_MMSG + Status receive_messages_fast(MutableSpan<UdpSocketFd::InboundMessage> messages, size_t &cnt) { + int flags = 0; + cnt = 0; + if (get_poll_info().get_flags_local().has_pending_error()) { +#ifdef MSG_ERRQUEUE + flags = MSG_ERRQUEUE; +#else + return get_pending_error(); +#endif + } + //struct mmsghdr { + // msghdr msg_hdr; [> Message header <] + // unsigned int msg_len; [> Number of bytes transmitted <] + //}; + std::array<detail::UdpSocketReceiveHelper, 16> helpers; + std::array<mmsghdr, 16> headers; + size_t to_receive = min(messages.size(), headers.size()); + for (size_t i = 0; i < to_receive; i++) { + helpers[i].to_native(messages[i], headers[i].msg_hdr); + headers[i].msg_len = 0; + } + + auto native_fd = get_native_fd().socket(); + auto recvmmsg_res = detail::skip_eintr( + [&] { return recvmmsg(native_fd, headers.data(), narrow_cast<unsigned int>(to_receive), flags, nullptr); }); + auto recvmmsg_errno = errno; + if (recvmmsg_res >= 0) { + cnt = narrow_cast<size_t>(recvmmsg_res); + for (size_t i = 0; i < cnt; i++) { + UdpSocketReceiveHelper::from_native(headers[i].msg_hdr, headers[i].msg_len, messages[i]); + } + return Status::OK(); + } + + bool is_received; + auto status = process_recvmsg_error(recvmmsg_errno, is_received); + cnt = is_received; + return status; + } +#endif +}; +void UdpSocketFdImplDeleter::operator()(UdpSocketFdImpl *impl) { + delete impl; +} +#endif +} // namespace detail + +UdpSocketFd::UdpSocketFd() = default; +UdpSocketFd::UdpSocketFd(UdpSocketFd &&) noexcept = default; +UdpSocketFd &UdpSocketFd::operator=(UdpSocketFd &&) noexcept = default; +UdpSocketFd::~UdpSocketFd() = default; +PollableFdInfo &UdpSocketFd::get_poll_info() { + return impl_->get_poll_info(); +} +const PollableFdInfo &UdpSocketFd::get_poll_info() const { + return impl_->get_poll_info(); +} + +Result<UdpSocketFd> UdpSocketFd::open(const IPAddress &address) { + NativeFd native_fd{socket(address.get_address_family(), SOCK_DGRAM, IPPROTO_UDP)}; + if (!native_fd) { + return OS_SOCKET_ERROR("Failed to create a socket"); + } + TRY_STATUS(native_fd.set_is_blocking_unsafe(false)); + + auto sock = native_fd.socket(); +#if TD_PORT_POSIX + int flags = 1; +#elif TD_PORT_WINDOWS + BOOL flags = TRUE; +#endif + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&flags), sizeof(flags)); + // TODO: SO_REUSEADDR, SO_KEEPALIVE, TCP_NODELAY, SO_SNDBUF, SO_RCVBUF, TCP_QUICKACK, SO_LINGER + + auto bind_addr = address.get_any_addr(); + bind_addr.set_port(address.get_port()); + auto e_bind = bind(sock, bind_addr.get_sockaddr(), narrow_cast<int>(bind_addr.get_sockaddr_len())); + if (e_bind != 0) { + return OS_SOCKET_ERROR("Failed to bind a socket"); + } + return UdpSocketFd(make_unique<detail::UdpSocketFdImpl>(std::move(native_fd))); +} + +UdpSocketFd::UdpSocketFd(unique_ptr<detail::UdpSocketFdImpl> impl) : impl_(impl.release()) { +} + +void UdpSocketFd::close() { + impl_.reset(); +} + +bool UdpSocketFd::empty() const { + return !impl_; +} + +const NativeFd &UdpSocketFd::get_native_fd() const { + return get_poll_info().native_fd(); +} + +#if TD_PORT_POSIX +static Result<uint32> maximize_buffer(int socket_fd, int optname, uint32 max_size) { + if (setsockopt(socket_fd, SOL_SOCKET, optname, &max_size, sizeof(max_size)) == 0) { + // fast path + return max_size; + } + + /* Start with the default size. */ + uint32 old_size = 0; + socklen_t intsize = sizeof(old_size); + if (getsockopt(socket_fd, SOL_SOCKET, optname, &old_size, &intsize)) { + return OS_ERROR("getsockopt() failed"); + } +#if TD_LINUX + old_size /= 2; +#endif + + /* Binary-search for the real maximum. */ + uint32 last_good_size = old_size; + uint32 min_size = old_size; + while (min_size <= max_size) { + uint32 avg_size = min_size + (max_size - min_size) / 2; + if (setsockopt(socket_fd, SOL_SOCKET, optname, &avg_size, sizeof(avg_size)) == 0) { + last_good_size = avg_size; + min_size = avg_size + 1; + } else { + max_size = avg_size - 1; + } + } + return last_good_size; +} + +Result<uint32> UdpSocketFd::maximize_snd_buffer(uint32 max_size) { + return maximize_buffer(get_native_fd().fd(), SO_SNDBUF, max_size == 0 ? DEFAULT_UDP_MAX_SND_BUFFER_SIZE : max_size); +} + +Result<uint32> UdpSocketFd::maximize_rcv_buffer(uint32 max_size) { + return maximize_buffer(get_native_fd().fd(), SO_RCVBUF, max_size == 0 ? DEFAULT_UDP_MAX_RCV_BUFFER_SIZE : max_size); +} +#else +Result<uint32> UdpSocketFd::maximize_snd_buffer(uint32 max_size) { + return 0; +} +Result<uint32> UdpSocketFd::maximize_rcv_buffer(uint32 max_size) { + return 0; +} +#endif + +#if TD_PORT_POSIX +Status UdpSocketFd::send_message(const OutboundMessage &message, bool &is_sent) { + return impl_->send_message(message, is_sent); +} +Status UdpSocketFd::receive_message(InboundMessage &message, bool &is_received) { + return impl_->receive_message(message, is_received); +} + +Status UdpSocketFd::send_messages(Span<OutboundMessage> messages, size_t &count) { + return impl_->send_messages(messages, count); +} +Status UdpSocketFd::receive_messages(MutableSpan<InboundMessage> messages, size_t &count) { + return impl_->receive_messages(messages, count); +} +#endif +#if TD_PORT_WINDOWS +Result<optional<UdpMessage>> UdpSocketFd::receive() { + return impl_->receive(); +} + +void UdpSocketFd::send(UdpMessage message) { + return impl_->send(std::move(message)); +} + +Status UdpSocketFd::flush_send() { + return impl_->flush_send(); +} +#endif + +bool UdpSocketFd::is_critical_read_error(const Status &status) { + return status.code() == ENOMEM || status.code() == ENOBUFS; +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.h new file mode 100644 index 0000000000..6ff5cd1491 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/UdpSocketFd.h @@ -0,0 +1,93 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/port/config.h" + +#include "td/utils/buffer.h" +#include "td/utils/optional.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/PollableFd.h" +#include "td/utils/port/IPAddress.h" +#include "td/utils/Slice.h" +#include "td/utils/Span.h" +#include "td/utils/Status.h" + +#include <memory> + +namespace td { +// Udp and errors +namespace detail { +class UdpSocketFdImpl; +class UdpSocketFdImplDeleter { + public: + void operator()(UdpSocketFdImpl *impl); +}; +} // namespace detail + +struct UdpMessage { + IPAddress address; + BufferSlice data; + Status error; +}; + +class UdpSocketFd { + public: + UdpSocketFd(); + UdpSocketFd(UdpSocketFd &&) noexcept; + UdpSocketFd &operator=(UdpSocketFd &&) noexcept; + ~UdpSocketFd(); + + UdpSocketFd(const UdpSocketFd &) = delete; + UdpSocketFd &operator=(const UdpSocketFd &) = delete; + + Result<uint32> maximize_snd_buffer(uint32 max_size = 0); + Result<uint32> maximize_rcv_buffer(uint32 max_size = 0); + + static Result<UdpSocketFd> open(const IPAddress &address) TD_WARN_UNUSED_RESULT; + + PollableFdInfo &get_poll_info(); + const PollableFdInfo &get_poll_info() const; + const NativeFd &get_native_fd() const; + + void close(); + bool empty() const; + + static bool is_critical_read_error(const Status &status); + +#if TD_PORT_POSIX + struct OutboundMessage { + const IPAddress *to; + Slice data; + }; + struct InboundMessage { + IPAddress *from; + MutableSlice data; + Status *error; + }; + + Status send_message(const OutboundMessage &message, bool &is_sent) TD_WARN_UNUSED_RESULT; + Status receive_message(InboundMessage &message, bool &is_received) TD_WARN_UNUSED_RESULT; + + Status send_messages(Span<OutboundMessage> messages, size_t &count) TD_WARN_UNUSED_RESULT; + Status receive_messages(MutableSpan<InboundMessage> messages, size_t &count) TD_WARN_UNUSED_RESULT; +#elif TD_PORT_WINDOWS + Result<optional<UdpMessage> > receive(); + + void send(UdpMessage message); + + Status flush_send(); +#endif + + private: + static constexpr uint32 DEFAULT_UDP_MAX_SND_BUFFER_SIZE = (1 << 24); + static constexpr uint32 DEFAULT_UDP_MAX_RCV_BUFFER_SIZE = (1 << 24); + std::unique_ptr<detail::UdpSocketFdImpl, detail::UdpSocketFdImplDeleter> impl_; + explicit UdpSocketFd(unique_ptr<detail::UdpSocketFdImpl> impl); +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h index 0ffdb3c3bf..59fd94bf00 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/config.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -19,6 +19,9 @@ #if TD_LINUX || TD_ANDROID || TD_TIZEN #define TD_POLL_EPOLL 1 #define TD_EVENTFD_LINUX 1 +#elif TD_FREEBSD || TD_OPENBSD || TD_NETBSD + #define TD_POLL_KQUEUE 1 + #define TD_EVENTFD_BSD 1 #elif TD_CYGWIN #define TD_POLL_SELECT 1 #define TD_EVENTFD_BSD 1 @@ -31,16 +34,26 @@ #elif TD_WINDOWS #define TD_POLL_WINEVENT 1 #define TD_EVENTFD_WINDOWS 1 +#elif TD_ILLUMOS + #define TD_POLL_EPOLL 1 + #define TD_EVENTFD_LINUX 1 +#elif TD_SOLARIS + #define TD_POLL_POLL 1 + #define TD_EVENTFD_BSD 1 #else #error "Poll's implementation is not defined" #endif #if TD_EMSCRIPTEN #define TD_THREAD_UNSUPPORTED 1 -#elif TD_TIZEN - #define TD_THREAD_PTHREAD 1 -#else +#elif TD_WINDOWS #define TD_THREAD_STL 1 +#else + #define TD_THREAD_PTHREAD 1 +#endif + +#if TD_LINUX + #define TD_HAS_MMSG 1 #endif // clang-format on diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp index 2ef026d164..771b06a954 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -14,98 +14,110 @@ char disable_linker_warning_about_empty_file_epoll_cpp TD_UNUSED; #include "td/utils/logging.h" #include "td/utils/Status.h" +#include <cerrno> + #include <unistd.h> namespace td { namespace detail { void Epoll::init() { - CHECK(epoll_fd == -1); - epoll_fd = epoll_create(1); + CHECK(!epoll_fd_); + epoll_fd_ = NativeFd(epoll_create(1)); auto epoll_create_errno = errno; - LOG_IF(FATAL, epoll_fd == -1) << Status::PosixError(epoll_create_errno, "epoll_create failed"); + LOG_IF(FATAL, !epoll_fd_) << Status::PosixError(epoll_create_errno, "epoll_create failed"); - events.resize(1000); + events_.resize(1000); } void Epoll::clear() { - if (epoll_fd == -1) { + if (!epoll_fd_) { return; } - events.clear(); + events_.clear(); + + epoll_fd_.close(); - close(epoll_fd); - epoll_fd = -1; + for (auto *list_node = list_root_.next; list_node != &list_root_;) { + auto pollable_fd = PollableFd::from_list_node(list_node); + list_node = list_node->next; + } } -void Epoll::subscribe(const Fd &fd, Fd::Flags flags) { +void Epoll::subscribe(PollableFd fd, PollFlags flags) { epoll_event event; event.events = EPOLLHUP | EPOLLERR | EPOLLET; #ifdef EPOLLRDHUP event.events |= EPOLLRDHUP; #endif - if (flags & Fd::Read) { + if (flags.can_read()) { event.events |= EPOLLIN; } - if (flags & Fd::Write) { + if (flags.can_write()) { event.events |= EPOLLOUT; } - auto native_fd = fd.get_native_fd(); - event.data.fd = native_fd; - int err = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, native_fd, &event); + auto native_fd = fd.native_fd().fd(); + auto *list_node = fd.release_as_list_node(); + list_root_.put(list_node); + event.data.ptr = list_node; + + int err = epoll_ctl(epoll_fd_.fd(), EPOLL_CTL_ADD, native_fd, &event); auto epoll_ctl_errno = errno; - LOG_IF(FATAL, err == -1) << Status::PosixError(epoll_ctl_errno, "epoll_ctl ADD failed") << ", epoll_fd = " << epoll_fd - << ", fd = " << native_fd; + LOG_IF(FATAL, err == -1) << Status::PosixError(epoll_ctl_errno, "epoll_ctl ADD failed") + << ", epoll_fd = " << epoll_fd_.fd() << ", fd = " << native_fd; } -void Epoll::unsubscribe(const Fd &fd) { - auto native_fd = fd.get_native_fd(); - int err = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, native_fd, nullptr); +void Epoll::unsubscribe(PollableFdRef fd_ref) { + auto fd = fd_ref.lock(); + auto native_fd = fd.native_fd().fd(); + int err = epoll_ctl(epoll_fd_.fd(), EPOLL_CTL_DEL, native_fd, nullptr); auto epoll_ctl_errno = errno; - LOG_IF(FATAL, err == -1) << Status::PosixError(epoll_ctl_errno, "epoll_ctl DEL failed") << ", epoll_fd = " << epoll_fd - << ", fd = " << native_fd; + LOG_IF(FATAL, err == -1) << Status::PosixError(epoll_ctl_errno, "epoll_ctl DEL failed") + << ", epoll_fd = " << epoll_fd_.fd() << ", fd = " << native_fd + << ", status = " << fd.native_fd().validate(); } -void Epoll::unsubscribe_before_close(const Fd &fd) { +void Epoll::unsubscribe_before_close(PollableFdRef fd) { unsubscribe(fd); } void Epoll::run(int timeout_ms) { - int ready_n = epoll_wait(epoll_fd, &events[0], static_cast<int>(events.size()), timeout_ms); + int ready_n = epoll_wait(epoll_fd_.fd(), &events_[0], static_cast<int>(events_.size()), timeout_ms); auto epoll_wait_errno = errno; LOG_IF(FATAL, ready_n == -1 && epoll_wait_errno != EINTR) << Status::PosixError(epoll_wait_errno, "epoll_wait failed"); for (int i = 0; i < ready_n; i++) { - Fd::Flags flags = 0; - epoll_event *event = &events[i]; + PollFlags flags; + epoll_event *event = &events_[i]; if (event->events & EPOLLIN) { event->events &= ~EPOLLIN; - flags |= Fd::Read; + flags = flags | PollFlags::Read(); } if (event->events & EPOLLOUT) { event->events &= ~EPOLLOUT; - flags |= Fd::Write; + flags = flags | PollFlags::Write(); } #ifdef EPOLLRDHUP if (event->events & EPOLLRDHUP) { event->events &= ~EPOLLRDHUP; - // flags |= Fd::Close; - // TODO + flags = flags | PollFlags::Close(); } #endif if (event->events & EPOLLHUP) { event->events &= ~EPOLLHUP; - flags |= Fd::Close; + flags = flags | PollFlags::Close(); } if (event->events & EPOLLERR) { event->events &= ~EPOLLERR; - flags |= Fd::Error; + flags = flags | PollFlags::Error(); } if (event->events) { - LOG(FATAL) << "Unsupported epoll events: " << event->events; + LOG(FATAL) << "Unsupported epoll events: " << static_cast<int32>(event->events); } - // LOG(DEBUG) << "Epoll event " << tag("fd", event->data.fd) << tag("flags", format::as_binary(flags)); - Fd(event->data.fd, Fd::Mode::Reference).update_flags_notify(flags); + //LOG(DEBUG) << "Epoll event " << tag("fd", event->data.fd) << tag("flags", format::as_binary(flags)); + auto pollable_fd = PollableFd::from_list_node(static_cast<ListNode *>(event->data.ptr)); + pollable_fd.add_flags(flags); + pollable_fd.release_as_list_node(); } } } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h index db4f66e5a7..da02de7c7b 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Epoll.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,8 +11,11 @@ #ifdef TD_POLL_EPOLL #include "td/utils/common.h" -#include "td/utils/port/Fd.h" +#include "td/utils/List.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/PollBase.h" +#include "td/utils/port/PollFlags.h" #include <sys/epoll.h> @@ -26,23 +29,28 @@ class Epoll final : public PollBase { Epoll &operator=(const Epoll &) = delete; Epoll(Epoll &&) = delete; Epoll &operator=(Epoll &&) = delete; - ~Epoll() override = default; + ~Epoll() final = default; - void init() override; + void init() final; - void clear() override; + void clear() final; - void subscribe(const Fd &fd, Fd::Flags flags) override; + void subscribe(PollableFd fd, PollFlags flags) final; - void unsubscribe(const Fd &fd) override; + void unsubscribe(PollableFdRef fd) final; - void unsubscribe_before_close(const Fd &fd) override; + void unsubscribe_before_close(PollableFdRef fd) final; - void run(int timeout_ms) override; + void run(int timeout_ms) final; + + static bool is_edge_triggered() { + return true; + } private: - int epoll_fd = -1; - vector<struct epoll_event> events; + NativeFd epoll_fd_; + vector<struct epoll_event> events_; + ListNode list_root_; }; } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp index d51e99ac0a..8f22aeb5c1 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,9 +11,16 @@ char disable_linker_warning_about_empty_file_event_fd_bsd_cpp TD_UNUSED; #ifdef TD_EVENTFD_BSD #include "td/utils/logging.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/skip_eintr.h" +#include "td/utils/port/PollFlags.h" +#include "td/utils/port/SocketFd.h" #include "td/utils/Slice.h" +#include <cerrno> + #include <fcntl.h> +#include <poll.h> #include <sys/socket.h> #include <sys/types.h> @@ -36,11 +43,13 @@ void EventFdBsd::init() { #endif LOG_IF(FATAL, err == -1) << Status::PosixError(socketpair_errno, "socketpair failed"); - detail::set_native_socket_is_blocking(fds[0], false).ensure(); - detail::set_native_socket_is_blocking(fds[1], false).ensure(); + auto fd_a = NativeFd(fds[0]); + auto fd_b = NativeFd(fds[1]); + fd_a.set_is_blocking_unsafe(false).ensure(); + fd_b.set_is_blocking_unsafe(false).ensure(); - in_ = Fd(fds[0], Fd::Mode::Owner); - out_ = Fd(fds[1], Fd::Mode::Owner); + in_ = SocketFd::from_native_fd(std::move(fd_a)).move_as_ok(); + out_ = SocketFd::from_native_fd(std::move(fd_b)).move_as_ok(); } bool EventFdBsd::empty() { @@ -56,12 +65,8 @@ Status EventFdBsd::get_pending_error() { return Status::OK(); } -const Fd &EventFdBsd::get_fd() const { - return out_; -} - -Fd &EventFdBsd::get_fd() { - return out_; +PollableFdInfo &EventFdBsd::get_poll_info() { + return out_.get_poll_info(); } void EventFdBsd::release() { @@ -72,13 +77,14 @@ void EventFdBsd::release() { } size_t size = result.ok(); if (size != sizeof(value)) { - LOG(FATAL) << "EventFdBsd write returned " << value << " instead of " << sizeof(value); + LOG(FATAL) << "EventFdBsd write returned " << size << " instead of " << sizeof(value); } } void EventFdBsd::acquire() { - out_.update_flags(Fd::Read); - while (can_read(out_)) { + sync_with_poll(out_); + out_.get_poll_info().add_flags(PollFlags::Read()); + while (can_read_local(out_)) { uint8 value[1024]; auto result = out_.read(MutableSlice(value, sizeof(value))); if (result.is_error()) { @@ -87,6 +93,17 @@ void EventFdBsd::acquire() { } } +void EventFdBsd::wait(int timeout_ms) { + detail::skip_eintr_timeout( + [this](int timeout_ms) { + pollfd fd; + fd.fd = get_poll_info().native_fd().fd(); + fd.events = POLLIN; + return poll(&fd, 1, timeout_ms); + }, + timeout_ms); +} + } // namespace detail } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h index 08f7ddd308..5278f74b23 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdBsd.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,34 +11,36 @@ #ifdef TD_EVENTFD_BSD #include "td/utils/common.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/EventFdBase.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/SocketFd.h" #include "td/utils/Status.h" namespace td { namespace detail { class EventFdBsd final : public EventFdBase { - Fd in_; - Fd out_; + SocketFd in_; + SocketFd out_; public: EventFdBsd() = default; - void init() override; + void init() final; - bool empty() override; + bool empty() final; - void close() override; + void close() final; - Status get_pending_error() override TD_WARN_UNUSED_RESULT; + Status get_pending_error() final TD_WARN_UNUSED_RESULT; - const Fd &get_fd() const override; - Fd &get_fd() override; + PollableFdInfo &get_poll_info() final; - void release() override; + void release() final; - void acquire() override; + void acquire() final; + + void wait(int timeout_ms) final; }; } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp index fd08c9af08..bcab560af7 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,45 +11,71 @@ char disable_linker_warning_about_empty_file_event_fd_linux_cpp TD_UNUSED; #ifdef TD_EVENTFD_LINUX #include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/skip_eintr.h" +#include "td/utils/port/PollFlags.h" +#include "td/utils/ScopeGuard.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include <cerrno> + +#include <poll.h> #include <sys/eventfd.h> +#include <unistd.h> namespace td { namespace detail { +class EventFdLinuxImpl { + public: + PollableFdInfo info_; +}; + +EventFdLinux::EventFdLinux() = default; +EventFdLinux::EventFdLinux(EventFdLinux &&) noexcept = default; +EventFdLinux &EventFdLinux::operator=(EventFdLinux &&) noexcept = default; +EventFdLinux::~EventFdLinux() = default; void EventFdLinux::init() { - int fd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC); + auto fd = NativeFd(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC)); auto eventfd_errno = errno; - LOG_IF(FATAL, fd == -1) << Status::PosixError(eventfd_errno, "eventfd call failed"); - - fd_ = Fd(fd, Fd::Mode::Owner); + LOG_IF(FATAL, !fd) << Status::PosixError(eventfd_errno, "eventfd call failed"); + impl_ = make_unique<EventFdLinuxImpl>(); + impl_->info_.set_native_fd(std::move(fd)); } bool EventFdLinux::empty() { - return fd_.empty(); + return !impl_; } void EventFdLinux::close() { - fd_.close(); + impl_.reset(); } Status EventFdLinux::get_pending_error() { return Status::OK(); } -const Fd &EventFdLinux::get_fd() const { - return fd_; -} - -Fd &EventFdLinux::get_fd() { - return fd_; +PollableFdInfo &EventFdLinux::get_poll_info() { + return impl_->info_; } +// NB: will be called from multiple threads void EventFdLinux::release() { const uint64 value = 1; - // NB: write_unsafe is used, because release will be called from multiple threads - auto result = fd_.write_unsafe(Slice(reinterpret_cast<const char *>(&value), sizeof(value))); + auto slice = Slice(reinterpret_cast<const char *>(&value), sizeof(value)); + auto native_fd = impl_->info_.native_fd().fd(); + + auto result = [&]() -> Result<size_t> { + auto write_res = detail::skip_eintr([&] { return write(native_fd, slice.begin(), slice.size()); }); + auto write_errno = errno; + if (write_res >= 0) { + return narrow_cast<size_t>(write_res); + } + return Status::PosixError(write_errno, PSLICE() << "Write to fd " << native_fd << " has failed"); + }(); + if (result.is_error()) { LOG(FATAL) << "EventFdLinux write failed: " << result.error(); } @@ -60,12 +86,46 @@ void EventFdLinux::release() { } void EventFdLinux::acquire() { + impl_->info_.sync_with_poll(); + SCOPE_EXIT { + // Clear flags without EAGAIN and EWOULDBLOCK + // Looks like it is safe thing to do with eventfd + get_poll_info().clear_flags(PollFlags::Read()); + }; uint64 res; - auto result = fd_.read(MutableSlice(reinterpret_cast<char *>(&res), sizeof(res))); + auto slice = MutableSlice(reinterpret_cast<char *>(&res), sizeof(res)); + auto native_fd = impl_->info_.native_fd().fd(); + auto result = [&]() -> Result<size_t> { + CHECK(!slice.empty()); + auto read_res = detail::skip_eintr([&] { return ::read(native_fd, slice.begin(), slice.size()); }); + auto read_errno = errno; + if (read_res >= 0) { + CHECK(read_res != 0); + return narrow_cast<size_t>(read_res); + } + if (read_errno == EAGAIN +#if EAGAIN != EWOULDBLOCK + || read_errno == EWOULDBLOCK +#endif + ) { + return 0; + } + return Status::PosixError(read_errno, PSLICE() << "Read from fd " << native_fd << " has failed"); + }(); if (result.is_error()) { LOG(FATAL) << "EventFdLinux read failed: " << result.error(); } - fd_.clear_flags(Fd::Read); +} + +void EventFdLinux::wait(int timeout_ms) { + detail::skip_eintr_timeout( + [this](int timeout_ms) { + pollfd fd; + fd.fd = get_poll_info().native_fd().fd(); + fd.events = POLLIN; + return poll(&fd, 1, timeout_ms); + }, + timeout_ms); } } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h index 3df7ce3a5d..2cd4d12210 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdLinux.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,31 +11,38 @@ #ifdef TD_EVENTFD_LINUX #include "td/utils/common.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/EventFdBase.h" -#include "td/utils/port/Fd.h" #include "td/utils/Status.h" namespace td { namespace detail { +class EventFdLinuxImpl; class EventFdLinux final : public EventFdBase { - Fd fd_; + unique_ptr<EventFdLinuxImpl> impl_; public: - void init() override; + EventFdLinux(); + EventFdLinux(EventFdLinux &&) noexcept; + EventFdLinux &operator=(EventFdLinux &&) noexcept; + ~EventFdLinux() final; - bool empty() override; + void init() final; - void close() override; + bool empty() final; - Status get_pending_error() override TD_WARN_UNUSED_RESULT; + void close() final; - const Fd &get_fd() const override; - Fd &get_fd() override; + Status get_pending_error() final TD_WARN_UNUSED_RESULT; - void release() override; + PollableFdInfo &get_poll_info() final; - void acquire() override; + void release() final; + + void acquire() final; + + void wait(int timeout_ms) final; }; } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp index 8adfd5a686..7acaa3f17b 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,39 +10,56 @@ char disable_linker_warning_about_empty_file_event_fd_windows_cpp TD_UNUSED; #ifdef TD_EVENTFD_WINDOWS +#include "td/utils/logging.h" + namespace td { namespace detail { void EventFdWindows::init() { - fd_ = Fd::create_event_fd(); + auto handle = CreateEventW(nullptr, true, false, nullptr); + if (handle == nullptr) { + auto error = OS_ERROR("CreateEventW failed"); + LOG(FATAL) << error; + } + event_ = NativeFd(handle); } bool EventFdWindows::empty() { - return fd_.empty(); + return !event_; } void EventFdWindows::close() { - fd_.close(); + event_.close(); } Status EventFdWindows::get_pending_error() { return Status::OK(); } -const Fd &EventFdWindows::get_fd() const { - return fd_; -} - -Fd &EventFdWindows::get_fd() { - return fd_; +PollableFdInfo &EventFdWindows::get_poll_info() { + UNREACHABLE(); } void EventFdWindows::release() { - fd_.release(); + if (SetEvent(event_.fd()) == 0) { + auto error = OS_ERROR("SetEvent failed"); + LOG(FATAL) << error; + } } void EventFdWindows::acquire() { - fd_.acquire(); + if (ResetEvent(event_.fd()) == 0) { + auto error = OS_ERROR("ResetEvent failed"); + LOG(FATAL) << error; + } +} + +void EventFdWindows::wait(int timeout_ms) { + WaitForSingleObject(event_.fd(), timeout_ms); + if (ResetEvent(event_.fd()) == 0) { + auto error = OS_ERROR("ResetEvent failed"); + LOG(FATAL) << error; + } } } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h index 48e1c763b3..5794e6c7ca 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/EventFdWindows.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,33 +11,35 @@ #ifdef TD_EVENTFD_WINDOWS #include "td/utils/common.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/EventFdBase.h" -#include "td/utils/port/Fd.h" #include "td/utils/Status.h" namespace td { namespace detail { class EventFdWindows final : public EventFdBase { - Fd fd_; + NativeFd event_; public: EventFdWindows() = default; - void init() override; + void init() final; - bool empty() override; + bool empty() final; - void close() override; + void close() final; - Status get_pending_error() override TD_WARN_UNUSED_RESULT; + Status get_pending_error() final TD_WARN_UNUSED_RESULT; - const Fd &get_fd() const override; - Fd &get_fd() override; + PollableFdInfo &get_poll_info() final; - void release() override; + void release() final; - void acquire() override; + void acquire() final; + + void wait(int timeout_ms) final; }; } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.cpp new file mode 100644 index 0000000000..1e472df67f --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.cpp @@ -0,0 +1,110 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/detail/Iocp.h" + +char disable_linker_warning_about_empty_file_iocp_cpp TD_UNUSED; + +#ifdef TD_PORT_WINDOWS + +#include "td/utils/logging.h" + +namespace td { +namespace detail { +Iocp::~Iocp() { + clear(); +} + +void Iocp::loop() { + Iocp::Guard guard(this); + while (true) { + DWORD bytes = 0; + ULONG_PTR key = 0; + WSAOVERLAPPED *overlapped = nullptr; + BOOL ok = + GetQueuedCompletionStatus(iocp_handle_->fd(), &bytes, &key, reinterpret_cast<OVERLAPPED **>(&overlapped), 1000); + if (bytes || key || overlapped) { + // LOG(ERROR) << "Got IOCP " << bytes << " " << key << " " << overlapped; + } + if (ok) { + auto callback = reinterpret_cast<Iocp::Callback *>(key); + if (callback == nullptr) { + // LOG(ERROR) << "Interrupt IOCP loop"; + return; + } + callback->on_iocp(bytes, overlapped); + } else { + if (overlapped != nullptr) { + auto error = OS_ERROR("Received from IOCP"); + auto callback = reinterpret_cast<Iocp::Callback *>(key); + CHECK(callback != nullptr); + callback->on_iocp(std::move(error), overlapped); + } + } + } +} + +void Iocp::interrupt_loop() { + post(0, nullptr, nullptr); +} + +void Iocp::init() { + CHECK(!iocp_handle_); + auto res = CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 0); + if (res == nullptr) { + auto error = OS_ERROR("IOCP creation failed"); + LOG(FATAL) << error; + } + iocp_handle_ = std::make_shared<NativeFd>(res); +} + +void Iocp::clear() { + iocp_handle_.reset(); +} + +void Iocp::subscribe(const NativeFd &native_fd, Callback *callback) { + CHECK(iocp_handle_); + auto iocp_handle = + CreateIoCompletionPort(native_fd.fd(), iocp_handle_->fd(), reinterpret_cast<ULONG_PTR>(callback), 0); + if (iocp_handle == nullptr) { + auto error = OS_ERROR("CreateIoCompletionPort"); + LOG(FATAL) << error; + } + LOG_CHECK(iocp_handle == iocp_handle_->fd()) << iocp_handle << " " << iocp_handle_->fd(); +} + +IocpRef Iocp::get_ref() const { + return IocpRef(iocp_handle_); +} + +static void iocp_post(NativeFd &iocp_handle, size_t size, Iocp::Callback *callback, WSAOVERLAPPED *overlapped) { + if (PostQueuedCompletionStatus(iocp_handle.fd(), DWORD(size), reinterpret_cast<ULONG_PTR>(callback), + reinterpret_cast<OVERLAPPED *>(overlapped)) == 0) { + auto error = OS_ERROR("IOCP post failed"); + LOG(FATAL) << error; + } +} + +void Iocp::post(size_t size, Callback *callback, WSAOVERLAPPED *overlapped) { + iocp_post(*iocp_handle_, size, callback, overlapped); +} + +IocpRef::IocpRef(std::weak_ptr<NativeFd> iocp_handle) : iocp_handle_(std::move(iocp_handle)) { +} + +bool IocpRef::post(size_t size, Iocp::Callback *callback, WSAOVERLAPPED *overlapped) { + auto iocp_handle = iocp_handle_.lock(); + if (!iocp_handle) { + return false; + } + iocp_post(*iocp_handle, size, callback, overlapped); + return true; +} + +} // namespace detail +} // namespace td + +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.h new file mode 100644 index 0000000000..a37cce7e57 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Iocp.h @@ -0,0 +1,71 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/port/config.h" + +#ifdef TD_PORT_WINDOWS + +#include "td/utils/common.h" +#include "td/utils/Context.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/Status.h" + +#include <memory> + +namespace td { +namespace detail { + +class IocpRef; +class Iocp final : public Context<Iocp> { + public: + Iocp() = default; + Iocp(const Iocp &) = delete; + Iocp &operator=(const Iocp &) = delete; + Iocp(Iocp &&) = delete; + Iocp &operator=(Iocp &&) = delete; + ~Iocp(); + + class Callback { + public: + virtual ~Callback() = default; + virtual void on_iocp(Result<size_t> r_size, WSAOVERLAPPED *overlapped) = 0; + }; + + void init(); + void subscribe(const NativeFd &fd, Callback *callback); + void post(size_t size, Callback *callback, WSAOVERLAPPED *overlapped); + void loop(); + void interrupt_loop(); + void clear(); + + IocpRef get_ref() const; + + private: + std::shared_ptr<NativeFd> iocp_handle_; +}; + +class IocpRef { + public: + IocpRef() = default; + IocpRef(const Iocp &) = delete; + IocpRef &operator=(const Iocp &) = delete; + IocpRef(IocpRef &&) = default; + IocpRef &operator=(IocpRef &&) = default; + + explicit IocpRef(std::weak_ptr<NativeFd> iocp_handle); + + bool post(size_t size, Iocp::Callback *callback, WSAOVERLAPPED *overlapped); + + private: + std::weak_ptr<NativeFd> iocp_handle_; +}; + +} // namespace detail +} // namespace td + +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp index 351f8d7d6c..64c3c54302 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -13,54 +13,58 @@ char disable_linker_warning_about_empty_file_kqueue_cpp TD_UNUSED; #include "td/utils/logging.h" #include "td/utils/Status.h" +#include <cerrno> #include <utility> +#include <sys/time.h> #include <unistd.h> namespace td { namespace detail { -KQueue::KQueue() { - kq = -1; -} KQueue::~KQueue() { clear(); } void KQueue::init() { - kq = kqueue(); + kq_ = NativeFd(kqueue()); auto kqueue_errno = errno; - LOG_IF(FATAL, kq == -1) << Status::PosixError(kqueue_errno, "kqueue creation failed"); + LOG_IF(FATAL, !kq_) << Status::PosixError(kqueue_errno, "kqueue creation failed"); // TODO: const - events.resize(1000); - changes_n = 0; + events_.resize(1000); + changes_n_ = 0; } void KQueue::clear() { - if (kq == -1) { + if (!kq_) { return; } - events.clear(); - close(kq); - kq = -1; + events_.clear(); + kq_.close(); + for (auto *list_node = list_root_.next; list_node != &list_root_;) { + auto pollable_fd = PollableFd::from_list_node(list_node); + list_node = list_node->next; + } } int KQueue::update(int nevents, const timespec *timeout, bool may_fail) { - int err = kevent(kq, &events[0], changes_n, &events[0], nevents, timeout); + int err = kevent(kq_.fd(), &events_[0], changes_n_, &events_[0], nevents, timeout); auto kevent_errno = errno; bool is_fatal_error = [&] { if (err != -1) { return false; } - if (may_fail) { - return kevent_errno != ENOENT; + if (may_fail && kevent_errno == ENOENT) { + return false; } return kevent_errno != EINTR; }(); - LOG_IF(FATAL, is_fatal_error) << Status::PosixError(kevent_errno, "kevent failed"); + if (is_fatal_error) { + LOG(FATAL) << Status::PosixError(kevent_errno, "kevent failed"); + } - changes_n = 0; + changes_n_ = 0; if (err < 0) { return 0; } @@ -68,7 +72,7 @@ int KQueue::update(int nevents, const timespec *timeout, bool may_fail) { } void KQueue::flush_changes(bool may_fail) { - if (!changes_n) { + if (!changes_n_) { return; } int n = update(0, nullptr, may_fail); @@ -77,47 +81,59 @@ void KQueue::flush_changes(bool may_fail) { void KQueue::add_change(std::uintptr_t ident, int16 filter, uint16 flags, uint32 fflags, std::intptr_t data, void *udata) { - if (changes_n == static_cast<int>(events.size())) { + if (changes_n_ == static_cast<int>(events_.size())) { flush_changes(); } - EV_SET(&events[changes_n], ident, filter, flags, fflags, data, udata); +#if TD_NETBSD + auto set_udata = reinterpret_cast<std::intptr_t>(udata); +#else + auto set_udata = udata; +#endif + EV_SET(&events_[changes_n_], ident, filter, flags, fflags, data, set_udata); VLOG(fd) << "Subscribe [fd:" << ident << "] [filter:" << filter << "] [udata: " << udata << "]"; - changes_n++; + changes_n_++; } -void KQueue::subscribe(const Fd &fd, Fd::Flags flags) { - if (flags & Fd::Read) { - add_change(fd.get_native_fd(), EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, nullptr); +void KQueue::subscribe(PollableFd fd, PollFlags flags) { + auto native_fd = fd.native_fd().fd(); + auto list_node = fd.release_as_list_node(); + list_root_.put(list_node); + if (flags.can_read()) { + add_change(native_fd, EVFILT_READ, EV_ADD | EV_CLEAR, 0, 0, list_node); } - if (flags & Fd::Write) { - add_change(fd.get_native_fd(), EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, nullptr); + if (flags.can_write()) { + add_change(native_fd, EVFILT_WRITE, EV_ADD | EV_CLEAR, 0, 0, list_node); } } -void KQueue::invalidate(const Fd &fd) { - for (int i = 0; i < changes_n; i++) { - if (events[i].ident == static_cast<std::uintptr_t>(fd.get_native_fd())) { - changes_n--; - std::swap(events[i], events[changes_n]); +void KQueue::invalidate(int native_fd) { + for (int i = 0; i < changes_n_; i++) { + if (events_[i].ident == static_cast<std::uintptr_t>(native_fd)) { + changes_n_--; + std::swap(events_[i], events_[changes_n_]); i--; } } } -void KQueue::unsubscribe(const Fd &fd) { +void KQueue::unsubscribe(PollableFdRef fd_ref) { + auto pollable_fd = fd_ref.lock(); + auto native_fd = pollable_fd.native_fd().fd(); + // invalidate(fd); flush_changes(); - add_change(fd.get_native_fd(), EVFILT_READ, EV_DELETE, 0, 0, nullptr); + add_change(native_fd, EVFILT_READ, EV_DELETE, 0, 0, nullptr); flush_changes(true); - add_change(fd.get_native_fd(), EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); + add_change(native_fd, EVFILT_WRITE, EV_DELETE, 0, 0, nullptr); flush_changes(true); } -void KQueue::unsubscribe_before_close(const Fd &fd) { - invalidate(fd); +void KQueue::unsubscribe_before_close(PollableFdRef fd_ref) { + auto pollable_fd = fd_ref.lock(); + invalidate(pollable_fd.native_fd().fd()); // just to avoid O(changes_n ^ 2) - if (changes_n != 0) { + if (changes_n_ != 0) { flush_changes(); } } @@ -133,25 +149,32 @@ void KQueue::run(int timeout_ms) { timeout_ptr = &timeout_data; } - int n = update(static_cast<int>(events.size()), timeout_ptr); + int n = update(static_cast<int>(events_.size()), timeout_ptr); for (int i = 0; i < n; i++) { - struct kevent *event = &events[i]; - Fd::Flags flags = 0; + struct kevent *event = &events_[i]; + PollFlags flags; if (event->filter == EVFILT_WRITE) { - flags |= Fd::Write; + flags.add_flags(PollFlags::Write()); } if (event->filter == EVFILT_READ) { - flags |= Fd::Read; + flags.add_flags(PollFlags::Read()); } if (event->flags & EV_EOF) { - flags |= Fd::Close; + flags.add_flags(PollFlags::Close()); } if (event->fflags & EV_ERROR) { LOG(FATAL) << "EV_ERROR in kqueue is not supported"; } - VLOG(fd) << "Event [fd:" << event->ident << "] [filter:" << event->filter << "] [udata: " << event->udata << "]"; - // LOG(WARNING) << "event->ident = " << event->ident << "event->filter = " << event->filter; - Fd(static_cast<int>(event->ident), Fd::Mode::Reference).update_flags_notify(flags); +#if TD_NETBSD + auto udata = reinterpret_cast<void *>(event->udata); +#else + auto udata = event->udata; +#endif + VLOG(fd) << "Event [fd:" << event->ident << "] [filter:" << event->filter << "] [udata: " << udata << "]"; + // LOG(WARNING) << "Have event->ident = " << event->ident << "event->filter = " << event->filter; + auto pollable_fd = PollableFd::from_list_node(static_cast<ListNode *>(udata)); + pollable_fd.add_flags(flags); + pollable_fd.release_as_list_node(); } } } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h index e1b71e5fa5..d74443c475 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/KQueue.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,11 +11,16 @@ #ifdef TD_POLL_KQUEUE #include "td/utils/common.h" -#include "td/utils/port/Fd.h" +#include "td/utils/List.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/PollBase.h" +#include "td/utils/port/PollFlags.h" #include <cstdint> +#include <sys/types.h> // must be included before sys/event.h, which depends on sys/types.h on FreeBSD + #include <sys/event.h> namespace td { @@ -23,33 +28,38 @@ namespace detail { class KQueue final : public PollBase { public: - KQueue(); + KQueue() = default; KQueue(const KQueue &) = delete; KQueue &operator=(const KQueue &) = delete; KQueue(KQueue &&) = delete; KQueue &operator=(KQueue &&) = delete; - ~KQueue() override; + ~KQueue() final; + + void init() final; - void init() override; + void clear() final; - void clear() override; + void subscribe(PollableFd fd, PollFlags flags) final; - void subscribe(const Fd &fd, Fd::Flags flags) override; + void unsubscribe(PollableFdRef fd) final; - void unsubscribe(const Fd &fd) override; + void unsubscribe_before_close(PollableFdRef fd) final; - void unsubscribe_before_close(const Fd &fd) override; + void run(int timeout_ms) final; - void run(int timeout_ms) override; + static bool is_edge_triggered() { + return true; + } private: - vector<struct kevent> events; - int changes_n; - int kq; + vector<struct kevent> events_; + int changes_n_; + NativeFd kq_; + ListNode list_root_; int update(int nevents, const timespec *timeout, bool may_fail = false); - void invalidate(const Fd &fd); + void invalidate(int native_fd); void flush_changes(bool may_fail = false); diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.cpp new file mode 100644 index 0000000000..7a492857ec --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.cpp @@ -0,0 +1,261 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/detail/NativeFd.h" + +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/SliceBuilder.h" + +#if TD_PORT_POSIX +#include <fcntl.h> +#include <unistd.h> +#endif + +#if TD_FD_DEBUG +#include <mutex> +#include <set> +#endif + +namespace td { + +int VERBOSITY_NAME(fd) = VERBOSITY_NAME(DEBUG) + 9; + +#if TD_FD_DEBUG +class FdSet { + public: + void on_create_fd(NativeFd::Fd fd) { + if (!is_valid(fd)) { + return; + } + if (is_stdio(fd)) { + return; + } + std::unique_lock<std::mutex> guard(mutex_); + if (fds_.count(fd) >= 1) { + LOG(FATAL) << "Create duplicated fd: " << fd; + } + fds_.insert(fd); + } + + Status validate(NativeFd::Fd fd) { + if (!is_valid(fd)) { + return Status::Error(PSLICE() << "Invalid fd: " << fd); + } + if (is_stdio(fd)) { + return Status::OK(); + } + std::unique_lock<std::mutex> guard(mutex_); + if (fds_.count(fd) != 1) { + return Status::Error(PSLICE() << "Unknown fd: " << fd); + } + return Status::OK(); + } + + void on_close_fd(NativeFd::Fd fd) { + if (!is_valid(fd)) { + return; + } + if (is_stdio(fd)) { + return; + } + std::unique_lock<std::mutex> guard(mutex_); + if (fds_.count(fd) != 1) { + LOG(FATAL) << "Close unknown fd: " << fd; + } + fds_.erase(fd); + } + + private: + std::mutex mutex_; + std::set<NativeFd::Fd> fds_; + + bool is_stdio(NativeFd::Fd fd) const { +#if TD_PORT_WINDOWS +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) + return fd == GetStdHandle(STD_INPUT_HANDLE) || fd == GetStdHandle(STD_OUTPUT_HANDLE) || + fd == GetStdHandle(STD_ERROR_HANDLE); +#else + return false; +#endif +#else + return fd >= 0 && fd <= 2; +#endif + } + + bool is_valid(NativeFd::Fd fd) const { +#if TD_PORT_WINDOWS + return fd != INVALID_HANDLE_VALUE; +#else + return fd >= 0; +#endif + } +}; + +namespace { +FdSet &get_fd_set() { + static FdSet res; + return res; +} +} // namespace + +#endif + +Status NativeFd::validate() const { +#if TD_FD_DEBUG + return get_fd_set().validate(fd_); +#else + return Status::OK(); +#endif +} + +NativeFd::NativeFd(Fd fd) : fd_(fd) { + VLOG(fd) << *this << " create"; +#if TD_FD_DEBUG + get_fd_set().on_create_fd(fd_); +#endif +} + +NativeFd::NativeFd(Fd fd, bool nolog) : fd_(fd) { +#if TD_FD_DEBUG + get_fd_set().on_create_fd(fd_); +#endif +} + +#if TD_PORT_WINDOWS +NativeFd::NativeFd(Socket socket) : fd_(reinterpret_cast<Fd>(socket)), is_socket_(true) { + VLOG(fd) << *this << " create"; +#if TD_FD_DEBUG + get_fd_set().on_create_fd(fd_); +#endif +} +#endif + +NativeFd::NativeFd(NativeFd &&other) noexcept : fd_(other.fd_) { +#if TD_PORT_WINDOWS + is_socket_ = other.is_socket_; +#endif + other.fd_ = empty_fd(); +} + +NativeFd &NativeFd::operator=(NativeFd &&other) noexcept { + CHECK(this != &other); + close(); + fd_ = other.fd_; +#if TD_PORT_WINDOWS + is_socket_ = other.is_socket_; +#endif + other.fd_ = empty_fd(); + return *this; +} + +NativeFd::~NativeFd() { + close(); +} + +NativeFd::operator bool() const noexcept { + return fd_ != empty_fd(); +} + +NativeFd::Fd NativeFd::empty_fd() { +#if TD_PORT_POSIX + return -1; +#elif TD_PORT_WINDOWS + return INVALID_HANDLE_VALUE; +#endif +} + +NativeFd::Fd NativeFd::fd() const { + return fd_; +} + +NativeFd::Socket NativeFd::socket() const { +#if TD_PORT_POSIX + return fd(); +#elif TD_PORT_WINDOWS + CHECK(is_socket_); + return reinterpret_cast<Socket>(fd_); +#endif +} + +Status NativeFd::set_is_blocking(bool is_blocking) const { +#if TD_PORT_POSIX + auto old_flags = fcntl(fd(), F_GETFL); + if (old_flags == -1) { + return OS_SOCKET_ERROR("Failed to get socket flags"); + } + auto new_flags = is_blocking ? old_flags & ~O_NONBLOCK : old_flags | O_NONBLOCK; + if (new_flags != old_flags && fcntl(fd(), F_SETFL, new_flags) == -1) { + return OS_SOCKET_ERROR("Failed to set socket flags"); + } + + return Status::OK(); +#elif TD_PORT_WINDOWS + return set_is_blocking_unsafe(is_blocking); +#endif +} + +Status NativeFd::set_is_blocking_unsafe(bool is_blocking) const { +#if TD_PORT_POSIX + if (fcntl(fd(), F_SETFL, is_blocking ? 0 : O_NONBLOCK) == -1) { +#elif TD_PORT_WINDOWS + u_long mode = is_blocking; + if (ioctlsocket(socket(), FIONBIO, &mode) != 0) { +#endif + return OS_SOCKET_ERROR("Failed to change socket flags"); + } + return Status::OK(); +} + +Status NativeFd::duplicate(const NativeFd &to) const { +#if TD_PORT_POSIX + CHECK(*this); + CHECK(to); + if (dup2(fd(), to.fd()) == -1) { + return OS_ERROR("Failed to duplicate file descriptor"); + } + return Status::OK(); +#elif TD_PORT_WINDOWS + return Status::Error("Not supported"); +#endif +} + +void NativeFd::close() { + if (!*this) { + return; + } + +#if TD_FD_DEBUG + get_fd_set().on_close_fd(fd()); +#endif + + VLOG(fd) << *this << " close"; +#if TD_PORT_WINDOWS + if (is_socket_ ? closesocket(socket()) : !CloseHandle(fd())) { +#elif TD_PORT_POSIX + if (::close(fd()) < 0) { +#endif + auto error = OS_ERROR("Close fd"); + LOG(ERROR) << error; + } + fd_ = empty_fd(); +} + +NativeFd::Fd NativeFd::release() { + VLOG(fd) << *this << " release"; + auto res = fd_; + fd_ = empty_fd(); +#if TD_FD_DEBUG + get_fd_set().on_close_fd(res); +#endif + return res; +} + +StringBuilder &operator<<(StringBuilder &sb, const NativeFd &fd) { + return sb << tag("fd", fd.fd()); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.h new file mode 100644 index 0000000000..ac4bddccf2 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/NativeFd.h @@ -0,0 +1,68 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/port/config.h" + +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/Status.h" +#include "td/utils/StringBuilder.h" + +namespace td { + +extern int VERBOSITY_NAME(fd); + +class NativeFd { + public: +#if TD_PORT_POSIX + using Fd = int; + using Socket = int; +#elif TD_PORT_WINDOWS + using Fd = HANDLE; + using Socket = SOCKET; +#endif + NativeFd() = default; + explicit NativeFd(Fd fd); + NativeFd(Fd fd, bool nolog); +#if TD_PORT_WINDOWS + explicit NativeFd(Socket socket); +#endif + NativeFd(const NativeFd &) = delete; + NativeFd &operator=(const NativeFd &) = delete; + NativeFd(NativeFd &&other) noexcept; + NativeFd &operator=(NativeFd &&other) noexcept; + ~NativeFd(); + + explicit operator bool() const noexcept; + + Fd fd() const; + Socket socket() const; + + Status set_is_blocking(bool is_blocking) const; + + Status set_is_blocking_unsafe(bool is_blocking) const; // may drop other Fd flags on non-Windows + + Status duplicate(const NativeFd &to) const; + + void close(); + Fd release(); + + Status validate() const; + + private: + static Fd empty_fd(); + + Fd fd_ = empty_fd(); +#if TD_PORT_WINDOWS + bool is_socket_{false}; +#endif +}; + +StringBuilder &operator<<(StringBuilder &sb, const NativeFd &fd); + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp index 87a7391802..c4431207f5 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -13,8 +13,11 @@ char disable_linker_warning_about_empty_file_poll_cpp TD_UNUSED; #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/misc.h" +#include "td/utils/ScopeGuard.h" #include "td/utils/Status.h" +#include <cerrno> + namespace td { namespace detail { @@ -25,31 +28,37 @@ void Poll::clear() { pollfds_.clear(); } -void Poll::subscribe(const Fd &fd, Fd::Flags flags) { - unsubscribe(fd); +void Poll::subscribe(PollableFd fd, PollFlags flags) { + unsubscribe(fd.ref()); struct pollfd pollfd; - pollfd.fd = fd.get_native_fd(); + pollfd.fd = fd.native_fd().fd(); pollfd.events = 0; - if (flags & Fd::Read) { + if (flags.can_read()) { pollfd.events |= POLLIN; } - if (flags & Fd::Write) { + if (flags.can_write()) { pollfd.events |= POLLOUT; } pollfd.revents = 0; pollfds_.push_back(pollfd); + fds_.push_back(std::move(fd)); } -void Poll::unsubscribe(const Fd &fd) { +void Poll::unsubscribe(PollableFdRef fd_ref) { + auto fd = fd_ref.lock(); + SCOPE_EXIT { + fd.release_as_list_node(); + }; for (auto it = pollfds_.begin(); it != pollfds_.end(); ++it) { - if (it->fd == fd.get_native_fd()) { + if (it->fd == fd.native_fd().fd()) { pollfds_.erase(it); + fds_.erase(fds_.begin() + (it - pollfds_.begin())); return; } } } -void Poll::unsubscribe_before_close(const Fd &fd) { +void Poll::unsubscribe_before_close(PollableFdRef fd) { unsubscribe(fd); } @@ -58,23 +67,26 @@ void Poll::run(int timeout_ms) { auto poll_errno = errno; LOG_IF(FATAL, err == -1 && poll_errno != EINTR) << Status::PosixError(poll_errno, "poll failed"); - for (auto &pollfd : pollfds_) { - Fd::Flags flags = 0; + for (size_t i = 0; i < pollfds_.size(); i++) { + auto &pollfd = pollfds_[i]; + auto &fd = fds_[i]; + + PollFlags flags; if (pollfd.revents & POLLIN) { pollfd.revents &= ~POLLIN; - flags |= Fd::Read; + flags = flags | PollFlags::Read(); } if (pollfd.revents & POLLOUT) { pollfd.revents &= ~POLLOUT; - flags |= Fd::Write; + flags = flags | PollFlags::Write(); } if (pollfd.revents & POLLHUP) { pollfd.revents &= ~POLLHUP; - flags |= Fd::Close; + flags = flags | PollFlags::Close(); } if (pollfd.revents & POLLERR) { pollfd.revents &= ~POLLERR; - flags |= Fd::Error; + flags = flags | PollFlags::Error(); } if (pollfd.revents & POLLNVAL) { LOG(FATAL) << "Unexpected POLLNVAL " << tag("fd", pollfd.fd); @@ -82,7 +94,7 @@ void Poll::run(int timeout_ms) { if (pollfd.revents) { LOG(FATAL) << "Unsupported poll events: " << pollfd.revents; } - Fd(pollfd.fd, Fd::Mode::Reference).update_flags_notify(flags); + fd.add_flags(flags); } } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h index 32eca75399..5f9f0e94f6 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Poll.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,8 +11,9 @@ #ifdef TD_POLL_POLL #include "td/utils/common.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/PollBase.h" +#include "td/utils/port/PollFlags.h" #include <poll.h> @@ -26,22 +27,27 @@ class Poll final : public PollBase { Poll &operator=(const Poll &) = delete; Poll(Poll &&) = delete; Poll &operator=(Poll &&) = delete; - ~Poll() override = default; + ~Poll() final = default; - void init() override; + void init() final; - void clear() override; + void clear() final; - void subscribe(const Fd &fd, Fd::Flags flags) override; + void subscribe(PollableFd fd, PollFlags flags) final; - void unsubscribe(const Fd &fd) override; + void unsubscribe(PollableFdRef fd) final; - void unsubscribe_before_close(const Fd &fd) override; + void unsubscribe_before_close(PollableFdRef fd) final; - void run(int timeout_ms) override; + void run(int timeout_ms) final; + + static bool is_edge_triggered() { + return false; + } private: vector<pollfd> pollfds_; + vector<PollableFd> fds_; }; } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/PollableFd.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/PollableFd.h new file mode 100644 index 0000000000..01fb5857e4 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/PollableFd.h @@ -0,0 +1,230 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/format.h" +#include "td/utils/List.h" +#include "td/utils/logging.h" +#include "td/utils/Observer.h" +#include "td/utils/port/detail/NativeFd.h" +#include "td/utils/port/Mutex.h" +#include "td/utils/port/PollFlags.h" + +#include <atomic> +#include <memory> + +namespace td { + +class PollableFdInfo; +class PollableFdInfoUnlock { + public: + void operator()(PollableFdInfo *ptr); +}; + +class PollableFd; +class PollableFdRef { + public: + explicit PollableFdRef(ListNode *list_node) : list_node_(list_node) { + } + PollableFd lock(); + + private: + ListNode *list_node_; +}; + +class PollableFd { + public: + // Interface for kqueue, epoll and e.t.c. + const NativeFd &native_fd() const; + + ListNode *release_as_list_node(); + PollableFdRef ref(); + static PollableFd from_list_node(ListNode *node); + void add_flags(PollFlags flags); + PollFlags get_flags_unsafe() const; + + private: + std::unique_ptr<PollableFdInfo, PollableFdInfoUnlock> fd_info_; + friend class PollableFdInfo; + + explicit PollableFd(std::unique_ptr<PollableFdInfo, PollableFdInfoUnlock> fd_info) : fd_info_(std::move(fd_info)) { + } +}; + +inline PollableFd PollableFdRef::lock() { + return PollableFd::from_list_node(list_node_); +} + +class PollableFdInfo final : private ListNode { + public: + PollableFdInfo() = default; + PollableFdInfo(const PollableFdInfo &) = delete; + PollableFdInfo &operator=(const PollableFdInfo &) = delete; + PollableFdInfo(PollableFdInfo &&) = delete; + PollableFdInfo &operator=(PollableFdInfo &&) = delete; + + PollableFd extract_pollable_fd(ObserverBase *observer) { + VLOG(fd) << native_fd() << " extract pollable fd " << tag("observer", observer); + CHECK(!empty()); + bool was_locked = lock_.test_and_set(std::memory_order_acquire); + CHECK(!was_locked); + set_observer(observer); + return PollableFd{std::unique_ptr<PollableFdInfo, PollableFdInfoUnlock>{this}}; + } + PollableFdRef get_pollable_fd_ref() { + CHECK(!empty()); + bool was_locked = lock_.test_and_set(std::memory_order_acquire); + CHECK(was_locked); + return PollableFdRef{as_list_node()}; + } + + void add_flags(PollFlags flags) { + flags_.write_flags_local(flags); + } + + void clear_flags(PollFlags flags) { + flags_.clear_flags(flags); + } + PollFlags sync_with_poll() const { + return flags_.read_flags(); + } + PollFlags get_flags_local() const { + return flags_.read_flags_local(); + } + + bool empty() const { + return !fd_; + } + + void set_native_fd(NativeFd new_native_fd) { + if (fd_) { + CHECK(!new_native_fd); + bool was_locked = lock_.test_and_set(std::memory_order_acquire); + CHECK(!was_locked); + lock_.clear(std::memory_order_release); + } + + fd_ = std::move(new_native_fd); + } + explicit PollableFdInfo(NativeFd native_fd) { + set_native_fd(std::move(native_fd)); + } + const NativeFd &native_fd() const { + //CHECK(!empty()); + return fd_; + } + NativeFd move_as_native_fd() { + return std::move(fd_); + } + + ~PollableFdInfo() { + VLOG(fd) << native_fd() << " destroy PollableFdInfo"; + bool was_locked = lock_.test_and_set(std::memory_order_acquire); + CHECK(!was_locked); + } + + void add_flags_from_poll(PollFlags flags) { + VLOG(fd) << native_fd() << " add flags from poll " << flags; + if (flags_.write_flags(flags)) { + notify_observer(); + } + } + + private: + NativeFd fd_{}; + std::atomic_flag lock_ = ATOMIC_FLAG_INIT; + PollFlagsSet flags_; +#if TD_PORT_WINDOWS + Mutex observer_lock_; +#endif + ObserverBase *observer_{nullptr}; + + friend class PollableFd; + friend class PollableFdInfoUnlock; + + void set_observer(ObserverBase *observer) { +#if TD_PORT_WINDOWS + auto lock = observer_lock_.lock(); +#endif + CHECK(observer_ == nullptr); + observer_ = observer; + } + void clear_observer() { +#if TD_PORT_WINDOWS + auto lock = observer_lock_.lock(); +#endif + observer_ = nullptr; + } + void notify_observer() { +#if TD_PORT_WINDOWS + auto lock = observer_lock_.lock(); +#endif + VLOG(fd) << native_fd() << " notify " << tag("observer", observer_); + if (observer_ != nullptr) { + observer_->notify(); + } + } + + void unlock() { + clear_observer(); + lock_.clear(std::memory_order_release); + as_list_node()->remove(); + } + + ListNode *as_list_node() { + return static_cast<ListNode *>(this); + } + static PollableFdInfo *from_list_node(ListNode *list_node) { + return static_cast<PollableFdInfo *>(list_node); + } +}; +inline void PollableFdInfoUnlock::operator()(PollableFdInfo *ptr) { + ptr->unlock(); +} + +inline ListNode *PollableFd::release_as_list_node() { + return fd_info_.release()->as_list_node(); +} +inline PollableFdRef PollableFd::ref() { + return PollableFdRef{fd_info_->as_list_node()}; +} +inline PollableFd PollableFd::from_list_node(ListNode *node) { + return PollableFd(std::unique_ptr<PollableFdInfo, PollableFdInfoUnlock>(PollableFdInfo::from_list_node(node))); +} + +inline void PollableFd::add_flags(PollFlags flags) { + fd_info_->add_flags_from_poll(flags); +} +inline PollFlags PollableFd::get_flags_unsafe() const { + return fd_info_->get_flags_local(); +} +inline const NativeFd &PollableFd::native_fd() const { + return fd_info_->native_fd(); +} + +template <class FdT> +void sync_with_poll(const FdT &fd) { + fd.get_poll_info().sync_with_poll(); +} + +template <class FdT> +bool can_read_local(const FdT &fd) { + return fd.get_poll_info().get_flags_local().can_read() || fd.get_poll_info().get_flags_local().has_pending_error(); +} + +template <class FdT> +bool can_write_local(const FdT &fd) { + return fd.get_poll_info().get_flags_local().can_write(); +} + +template <class FdT> +bool can_close_local(const FdT &fd) { + return fd.get_poll_info().get_flags_local().can_close(); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp index b532a0464c..d149246fd1 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -29,22 +29,25 @@ void Select::clear() { fds_.clear(); } -void Select::subscribe(const Fd &fd, Fd::Flags flags) { - int native_fd = fd.get_native_fd(); +void Select::subscribe(PollableFd fd, PollFlags flags) { + int native_fd = fd.native_fd().fd(); for (auto &it : fds_) { - CHECK(it.fd_ref.get_native_fd() != native_fd); + CHECK(it.fd.native_fd().fd() != native_fd); } - fds_.push_back(FdInfo{Fd(native_fd, Fd::Mode::Reference), flags}); - CHECK(0 <= native_fd && native_fd < FD_SETSIZE) << native_fd << " " << FD_SETSIZE; + fds_.push_back(FdInfo{std::move(fd), flags}); + LOG_CHECK(0 <= native_fd && native_fd < FD_SETSIZE) << native_fd << " " << FD_SETSIZE; FD_SET(native_fd, &all_fd_); if (native_fd > max_fd_) { max_fd_ = native_fd; } } -void Select::unsubscribe(const Fd &fd) { - int native_fd = fd.get_native_fd(); - CHECK(0 <= native_fd && native_fd < FD_SETSIZE) << native_fd << " " << FD_SETSIZE; +void Select::unsubscribe(PollableFdRef fd) { + auto fd_locked = fd.lock(); + int native_fd = fd_locked.native_fd().fd(); + fd_locked.release_as_list_node(); + + LOG_CHECK(0 <= native_fd && native_fd < FD_SETSIZE) << native_fd << " " << FD_SETSIZE; FD_CLR(native_fd, &all_fd_); FD_CLR(native_fd, &read_fd_); FD_CLR(native_fd, &write_fd_); @@ -53,17 +56,17 @@ void Select::unsubscribe(const Fd &fd) { max_fd_--; } for (auto it = fds_.begin(); it != fds_.end();) { - if (it->fd_ref.get_native_fd() == native_fd) { + if (it->fd.native_fd().fd() == native_fd) { std::swap(*it, fds_.back()); fds_.pop_back(); break; } else { - it++; + ++it; } } } -void Select::unsubscribe_before_close(const Fd &fd) { +void Select::unsubscribe_before_close(PollableFdRef fd) { unsubscribe(fd); } @@ -79,14 +82,14 @@ void Select::run(int timeout_ms) { } for (auto &it : fds_) { - int native_fd = it.fd_ref.get_native_fd(); - Fd::Flags fd_flags = it.fd_ref.get_flags(); - if ((it.flags & Fd::Write) && !(fd_flags & Fd::Write)) { + int native_fd = it.fd.native_fd().fd(); + PollFlags fd_flags = it.fd.get_flags_unsafe(); // concurrent calls are UB + if (it.flags.can_write() && !fd_flags.can_write()) { FD_SET(native_fd, &write_fd_); } else { FD_CLR(native_fd, &write_fd_); } - if ((it.flags & Fd::Read) && !(fd_flags & Fd::Read)) { + if (it.flags.can_read() && !fd_flags.can_read()) { FD_SET(native_fd, &read_fd_); } else { FD_CLR(native_fd, &read_fd_); @@ -96,20 +99,18 @@ void Select::run(int timeout_ms) { select(max_fd_ + 1, &read_fd_, &write_fd_, &except_fd_, timeout_ptr); for (auto &it : fds_) { - int native_fd = it.fd_ref.get_native_fd(); - Fd::Flags flags = 0; + int native_fd = it.fd.native_fd().fd(); + PollFlags flags; if (FD_ISSET(native_fd, &read_fd_)) { - flags |= Fd::Read; + flags = flags | PollFlags::Read(); } if (FD_ISSET(native_fd, &write_fd_)) { - flags |= Fd::Write; + flags = flags | PollFlags::Write(); } if (FD_ISSET(native_fd, &except_fd_)) { - flags |= Fd::Error; - } - if (flags != 0) { - it.fd_ref.update_flags_notify(flags); + flags = flags | PollFlags::Error(); } + it.fd.add_flags(flags); } } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h index 17f2876f3a..cc8d6a3bc0 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/Select.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,8 +11,9 @@ #ifdef TD_POLL_SELECT #include "td/utils/common.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/PollBase.h" +#include "td/utils/port/PollFlags.h" #include <sys/select.h> @@ -26,24 +27,28 @@ class Select final : public PollBase { Select &operator=(const Select &) = delete; Select(Select &&) = delete; Select &operator=(Select &&) = delete; - ~Select() override = default; + ~Select() final = default; - void init() override; + void init() final; - void clear() override; + void clear() final; - void subscribe(const Fd &fd, Fd::Flags flags) override; + void subscribe(PollableFd fd, PollFlags flags) final; - void unsubscribe(const Fd &fd) override; + void unsubscribe(PollableFdRef fd) final; - void unsubscribe_before_close(const Fd &fd) override; + void unsubscribe_before_close(PollableFdRef fd) final; - void run(int timeout_ms) override; + void run(int timeout_ms) final; + + static bool is_edge_triggered() { + return false; + } private: struct FdInfo { - Fd fd_ref; - Fd::Flags flags; + PollableFd fd; + PollFlags flags; }; vector<FdInfo> fds_; fd_set all_fd_; diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp index d949945e1d..4b6366980e 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.cpp @@ -1,16 +1,17 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/port/detail/ThreadIdGuard.h" -#include "td/utils/logging.h" +#include "td/utils/common.h" +#include "td/utils/ExitGuard.h" #include "td/utils/port/thread_local.h" -#include <array> #include <mutex> +#include <set> namespace td { namespace detail { @@ -18,34 +19,37 @@ class ThreadIdManager { public: int32 register_thread() { std::lock_guard<std::mutex> guard(mutex_); - for (size_t i = 0; i < is_id_used_.size(); i++) { - if (!is_id_used_[i]) { - is_id_used_[i] = true; - return static_cast<int32>(i + 1); - } + if (unused_thread_ids_.empty()) { + return ++max_thread_id_; } - LOG(FATAL) << "Cannot create more than " << max_thread_count() << " threads"; - return 0; + auto it = unused_thread_ids_.begin(); + auto result = *it; + unused_thread_ids_.erase(it); + return result; } void unregister_thread(int32 thread_id) { - thread_id--; std::lock_guard<std::mutex> guard(mutex_); - CHECK(is_id_used_.at(thread_id)); - is_id_used_[thread_id] = false; + CHECK(0 < thread_id && thread_id <= max_thread_id_); + bool is_inserted = unused_thread_ids_.insert(thread_id).second; + CHECK(is_inserted); } private: std::mutex mutex_; - std::array<bool, max_thread_count()> is_id_used_{{false}}; + std::set<int32> unused_thread_ids_; + int32 max_thread_id_ = 0; }; static ThreadIdManager thread_id_manager; +static ExitGuard exit_guard; ThreadIdGuard::ThreadIdGuard() { thread_id_ = thread_id_manager.register_thread(); set_thread_id(thread_id_); } ThreadIdGuard::~ThreadIdGuard() { - thread_id_manager.unregister_thread(thread_id_); + if (!ExitGuard::is_exited()) { + thread_id_manager.unregister_thread(thread_id_); + } set_thread_id(0); } } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h index 434bd5ac4d..b05d9a62a2 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadIdGuard.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.cpp new file mode 100644 index 0000000000..3fa0d19389 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.cpp @@ -0,0 +1,217 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/detail/ThreadPthread.h" + +char disable_linker_warning_about_empty_file_thread_pthread_cpp TD_UNUSED; + +#if TD_THREAD_PTHREAD + +#include "td/utils/misc.h" +#include "td/utils/port/detail/skip_eintr.h" +#if TD_NETBSD +#include "td/utils/ScopeGuard.h" +#endif + +#include <pthread.h> +#if TD_FREEBSD +#include <pthread_np.h> +#endif +#include <sched.h> +#include <signal.h> +#if TD_FREEBSD +#include <sys/cpuset.h> +#endif +#if TD_FREEBSD || TD_OPENBSD || TD_NETBSD +#include <sys/sysctl.h> +#endif +#include <unistd.h> + +namespace td { +namespace detail { +unsigned ThreadPthread::hardware_concurrency() { +// Linux and macOS +#if defined(_SC_NPROCESSORS_ONLN) + { + auto res = sysconf(_SC_NPROCESSORS_ONLN); + if (res > 0) { + return narrow_cast<unsigned>(res); + } + } +#endif + +#if TD_FREEBSD || TD_OPENBSD || TD_NETBSD +#if defined(HW_AVAILCPU) && defined(CTL_HW) + { + int mib[2] = {CTL_HW, HW_AVAILCPU}; + int res{0}; + size_t len = sizeof(res); + if (sysctl(mib, 2, &res, &len, nullptr, 0) == 0 && res != 0) { + return res; + } + } +#endif + +#if defined(HW_NCPU) && defined(CTL_HW) + { + int mib[2] = {CTL_HW, HW_NCPU}; + int res{0}; + size_t len = sizeof(res); + if (sysctl(mib, 2, &res, &len, nullptr, 0) == 0 && res != 0) { + return res; + } + } +#endif +#endif + + // Just in case + return 8; +} + +void ThreadPthread::set_name(CSlice name) { +#if defined(_GNU_SOURCE) && defined(__GLIBC_PREREQ) +#if __GLIBC_PREREQ(2, 12) + pthread_setname_np(thread_, name.c_str()); +#endif +#endif +} + +void ThreadPthread::join() { + if (is_inited_.get()) { + is_inited_ = false; + pthread_join(thread_, nullptr); + } +} + +void ThreadPthread::detach() { + if (is_inited_.get()) { + is_inited_ = false; + pthread_detach(thread_); + } +} + +void ThreadPthread::send_real_time_signal(id thread_id, int real_time_signal_number) { +#ifdef SIGRTMIN + pthread_kill(thread_id, SIGRTMIN + real_time_signal_number); +#endif +} + +int ThreadPthread::do_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), + void *arg) { + return pthread_create(thread, attr, start_routine, arg); +} + +#if TD_HAVE_THREAD_AFFINITY +Status ThreadPthread::set_affinity_mask(id thread_id, uint64 mask) { +#if TD_LINUX || TD_FREEBSD +#if TD_FREEBSD + cpuset_t cpuset; +#else + cpu_set_t cpuset; +#endif + CPU_ZERO(&cpuset); + for (int j = 0; j < 64 && j < CPU_SETSIZE; j++) { + if ((mask >> j) & 1) { + CPU_SET(j, &cpuset); + } + } + + auto res = skip_eintr([&] { return pthread_setaffinity_np(thread_id, sizeof(cpuset), &cpuset); }); + if (res) { + return OS_ERROR("Failed to set thread affinity mask"); + } + return Status::OK(); +#elif TD_NETBSD + cpuset_t *cpuset = cpuset_create(); + if (cpuset == nullptr) { + return OS_ERROR("Failed to create cpuset"); + } + SCOPE_EXIT { + cpuset_destroy(cpuset); + }; + for (int j = 0; j < 64; j++) { + if ((mask >> j) & 1) { + if (cpuset_set(j, cpuset) != 0) { + return OS_ERROR("Failed to set CPU identifier"); + } + } + } + + auto res = skip_eintr([&] { return pthread_setaffinity_np(thread_id, cpuset_size(cpuset), cpuset); }); + if (res) { + return OS_ERROR("Failed to set thread affinity mask"); + } + if (get_affinity_mask(thread_id) != mask) { + return Status::Error("Failed to set exact thread affinity mask"); + } + return Status::OK(); +#else + return Status::Error("Unsupported"); +#endif +} + +uint64 ThreadPthread::get_affinity_mask(id thread_id) { +#if TD_LINUX || TD_FREEBSD +#if TD_FREEBSD + cpuset_t cpuset; +#else + cpu_set_t cpuset; +#endif + CPU_ZERO(&cpuset); + auto res = skip_eintr([&] { return pthread_getaffinity_np(thread_id, sizeof(cpuset), &cpuset); }); + if (res) { + return 0; + } + + uint64 mask = 0; + for (int j = 0; j < 64 && j < CPU_SETSIZE; j++) { + if (CPU_ISSET(j, &cpuset)) { + mask |= static_cast<uint64>(1) << j; + } + } + return mask; +#elif TD_NETBSD + cpuset_t *cpuset = cpuset_create(); + if (cpuset == nullptr) { + return 0; + } + SCOPE_EXIT { + cpuset_destroy(cpuset); + }; + auto res = skip_eintr([&] { return pthread_getaffinity_np(thread_id, cpuset_size(cpuset), cpuset); }); + if (res) { + return 0; + } + + uint64 mask = 0; + for (int j = 0; j < 64; j++) { + if (cpuset_isset(j, cpuset) > 0) { + mask |= static_cast<uint64>(1) << j; + } + } + if (mask == 0) { + // the mask wasn't set, all CPUs are allowed + auto proc_count = sysconf(_SC_NPROCESSORS_ONLN); + for (int j = 0; j < 64 && j < proc_count; j++) { + mask |= static_cast<uint64>(1) << j; + } + } + return mask; +#else + return 0; +#endif +} +#endif + +namespace this_thread_pthread { +ThreadPthread::id get_id() { + return pthread_self(); +} +} // namespace this_thread_pthread + +} // namespace detail +} // namespace td +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h index e42efc3771..b938b58c7e 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadPthread.h @@ -1,89 +1,112 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once + #include "td/utils/port/config.h" #ifdef TD_THREAD_PTHREAD #include "td/utils/common.h" +#include "td/utils/Destructor.h" #include "td/utils/invoke.h" #include "td/utils/MovableValue.h" #include "td/utils/port/detail/ThreadIdGuard.h" #include "td/utils/port/thread_local.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" #include <tuple> #include <type_traits> #include <utility> +#if TD_OPENBSD || TD_SOLARIS #include <pthread.h> -#include <sched.h> +#endif +#include <sys/types.h> + +#if TD_LINUX || TD_FREEBSD || TD_NETBSD +#define TD_HAVE_THREAD_AFFINITY 1 +#endif namespace td { namespace detail { + class ThreadPthread { public: ThreadPthread() = default; ThreadPthread(const ThreadPthread &other) = delete; ThreadPthread &operator=(const ThreadPthread &other) = delete; - ThreadPthread(ThreadPthread &&) = default; - ThreadPthread &operator=(ThreadPthread &&) = default; + ThreadPthread(ThreadPthread &&other) noexcept : is_inited_(std::move(other.is_inited_)), thread_(other.thread_) { + } + ThreadPthread &operator=(ThreadPthread &&other) noexcept { + join(); + is_inited_ = std::move(other.is_inited_); + thread_ = other.thread_; + return *this; + } template <class Function, class... Args> - explicit ThreadPthread(Function &&f, Args &&... args) { - func_ = std::make_unique<std::unique_ptr<Destructor>>( - create_destructor([args = std::make_tuple(decay_copy(std::forward<Function>(f)), - decay_copy(std::forward<Args>(args))...)]() mutable { - invoke_tuple(std::move(args)); - clear_thread_locals(); - })); - pthread_create(&thread_, nullptr, run_thread, func_.get()); + explicit ThreadPthread(Function &&f, Args &&...args) { + auto func = create_destructor([args = std::make_tuple(decay_copy(std::forward<Function>(f)), + decay_copy(std::forward<Args>(args))...)]() mutable { + invoke_tuple(std::move(args)); + clear_thread_locals(); + }); + do_pthread_create(&thread_, nullptr, run_thread, func.release()); is_inited_ = true; } - void join() { - if (is_inited_.get()) { - is_inited_ = false; - pthread_join(thread_, nullptr); - } - } ~ThreadPthread() { join(); } - static unsigned hardware_concurrency() { - return 8; - } + void set_name(CSlice name); + + void join(); + + void detach(); + + static unsigned hardware_concurrency(); using id = pthread_t; + id get_id() noexcept { + return thread_; + } + + static void send_real_time_signal(id thread_id, int real_time_signal_number); + +#if TD_HAVE_THREAD_AFFINITY + static Status set_affinity_mask(id thread_id, uint64 mask); + + static uint64 get_affinity_mask(id thread_id); +#endif + private: MovableValue<bool> is_inited_; pthread_t thread_; - std::unique_ptr<std::unique_ptr<Destructor>> func_; template <class T> std::decay_t<T> decay_copy(T &&v) { return std::forward<T>(v); } + static int do_pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), + void *arg); + static void *run_thread(void *ptr) { ThreadIdGuard thread_id_guard; - auto func = static_cast<decltype(func_.get())>(ptr); - func->reset(); + auto func = unique_ptr<Destructor>(static_cast<Destructor *>(ptr)); return nullptr; } }; namespace this_thread_pthread { -inline void yield() { - sched_yield(); -} -inline ThreadPthread::id get_id() { - return pthread_self(); -} +ThreadPthread::id get_id(); } // namespace this_thread_pthread + } // namespace detail } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h index 64bf3213cf..06dec7ccc0 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/ThreadStl.h @@ -1,26 +1,37 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once + #include "td/utils/port/config.h" #ifdef TD_THREAD_STL #include "td/utils/common.h" #include "td/utils/invoke.h" +#if TD_WINDOWS && TD_MSVC +#include "td/utils/port/detail/NativeFd.h" +#endif #include "td/utils/port/detail/ThreadIdGuard.h" #include "td/utils/port/thread_local.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" #include <thread> #include <tuple> #include <type_traits> #include <utility> +#if TD_WINDOWS && TD_MSVC +#define TD_HAVE_THREAD_AFFINITY 1 +#endif + namespace td { namespace detail { + class ThreadStl { public: ThreadStl() = default; @@ -28,9 +39,12 @@ class ThreadStl { ThreadStl &operator=(const ThreadStl &other) = delete; ThreadStl(ThreadStl &&) = default; ThreadStl &operator=(ThreadStl &&) = default; - ~ThreadStl() = default; + ~ThreadStl() { + join(); + } + template <class Function, class... Args> - explicit ThreadStl(Function &&f, Args &&... args) { + explicit ThreadStl(Function &&f, Args &&...args) { thread_ = std::thread([args = std::make_tuple(decay_copy(std::forward<Function>(f)), decay_copy(std::forward<Args>(args))...)]() mutable { ThreadIdGuard thread_id_guard; @@ -40,14 +54,79 @@ class ThreadStl { } void join() { - thread_.join(); + if (thread_.joinable()) { + thread_.join(); + } + } + + void detach() { + if (thread_.joinable()) { + thread_.detach(); + } + } + + void set_name(CSlice name) { + // not supported } static unsigned hardware_concurrency() { return std::thread::hardware_concurrency(); } +#if TD_WINDOWS && TD_MSVC + using id = DWORD; +#else using id = std::thread::id; +#endif + + id get_id() noexcept { +#if TD_WINDOWS && TD_MSVC + static_assert(std::is_same<decltype(thread_.native_handle()), HANDLE>::value, + "Expected HANDLE as native thread type"); + return GetThreadId(thread_.native_handle()); +#else + return thread_.get_id(); +#endif + } + + static void send_real_time_signal(id thread_id, int real_time_signal_number) { + // not supported + } + +#if TD_HAVE_THREAD_AFFINITY + static Status set_affinity_mask(id thread_id, uint64 mask) { + if (static_cast<DWORD_PTR>(mask) != mask) { + return Status::Error("Invalid thread affinity mask specified"); + } + auto handle = OpenThread(THREAD_SET_LIMITED_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION, FALSE, thread_id); + if (handle == nullptr) { + return Status::Error("Failed to access thread"); + } + NativeFd thread_handle(handle); + if (SetThreadAffinityMask(thread_handle.fd(), static_cast<DWORD_PTR>(mask))) { + return Status::OK(); + } + return OS_ERROR("Failed to set thread affinity mask"); + } + + static uint64 get_affinity_mask(id thread_id) { + DWORD_PTR process_mask = 0; + DWORD_PTR system_mask = 0; + if (GetProcessAffinityMask(GetCurrentProcess(), &process_mask, &system_mask)) { + auto handle = OpenThread(THREAD_SET_LIMITED_INFORMATION | THREAD_QUERY_LIMITED_INFORMATION, FALSE, thread_id); + if (handle == nullptr) { + return 0; + } + NativeFd thread_handle(handle); + auto result = SetThreadAffinityMask(thread_handle.fd(), process_mask); + if (result != 0 && result != process_mask) { + SetThreadAffinityMask(thread_handle.fd(), result); + } + return result; + } + return 0; + } +#endif private: std::thread thread_; @@ -57,7 +136,17 @@ class ThreadStl { return std::forward<T>(v); } }; -namespace this_thread_stl = std::this_thread; + +namespace this_thread_stl { +#if TD_WINDOWS && TD_MSVC +inline ThreadStl::id get_id() { + return GetCurrentThreadId(); +} +#else +using std::this_thread::get_id; +#endif +} // namespace this_thread_stl + } // namespace detail } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp index 8f443d29ab..6f40ee4a13 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,84 +11,30 @@ char disable_linker_warning_about_empty_file_wineventpoll_cpp TD_UNUSED; #ifdef TD_POLL_WINEVENT #include "td/utils/common.h" -#include "td/utils/logging.h" -#include "td/utils/misc.h" -#include "td/utils/port/Fd.h" -#include "td/utils/port/PollBase.h" -#include "td/utils/port/sleep.h" -#include "td/utils/Status.h" - -#include <utility> namespace td { namespace detail { void WineventPoll::init() { - clear(); } void WineventPoll::clear() { - fds_.clear(); } -void WineventPoll::subscribe(const Fd &fd, Fd::Flags flags) { - for (auto &it : fds_) { - if (it.fd_ref.get_key() == fd.get_key()) { - it.flags = flags; - return; - } - } - fds_.push_back({fd.clone(), flags}); +void WineventPoll::subscribe(PollableFd fd, PollFlags flags) { + fd.release_as_list_node(); } -void WineventPoll::unsubscribe(const Fd &fd) { - for (auto it = fds_.begin(); it != fds_.end(); ++it) { - if (it->fd_ref.get_key() == fd.get_key()) { - std::swap(*it, fds_.back()); - fds_.pop_back(); - return; - } - } +void WineventPoll::unsubscribe(PollableFdRef fd) { + auto pollable_fd = fd.lock(); // unlocked in destructor } -void WineventPoll::unsubscribe_before_close(const Fd &fd) { - unsubscribe(fd); +void WineventPoll::unsubscribe_before_close(PollableFdRef fd) { + unsubscribe(std::move(fd)); } void WineventPoll::run(int timeout_ms) { - vector<std::pair<size_t, Fd::Flag>> events_desc; - vector<HANDLE> events; - for (size_t i = 0; i < fds_.size(); i++) { - auto &fd_info = fds_[i]; - if (fd_info.flags & Fd::Flag::Write) { - events_desc.emplace_back(i, Fd::Flag::Write); - events.push_back(fd_info.fd_ref.get_write_event()); - } - if (fd_info.flags & Fd::Flag::Read) { - events_desc.emplace_back(i, Fd::Flag::Read); - events.push_back(fd_info.fd_ref.get_read_event()); - } - } - if (events.empty()) { - usleep_for(timeout_ms * 1000); - return; - } - - auto status = WaitForMultipleObjects(narrow_cast<DWORD>(events.size()), events.data(), false, timeout_ms); - if (status == WAIT_FAILED) { - auto error = OS_ERROR("WaitForMultipleObjects failed"); - LOG(FATAL) << events.size() << " " << timeout_ms << " " << error; - } - for (size_t i = 0; i < events.size(); i++) { - if (WaitForSingleObject(events[i], 0) == WAIT_OBJECT_0) { - auto &fd = fds_[events_desc[i].first].fd_ref; - if (events_desc[i].second == Fd::Flag::Read) { - fd.on_read_event(); - } else { - fd.on_write_event(); - } - } - } + UNREACHABLE(); } } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h index ecc93f33fa..f68bbac48c 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/WineventPoll.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,8 +11,9 @@ #ifdef TD_POLL_WINEVENT #include "td/utils/common.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/detail/PollableFd.h" #include "td/utils/port/PollBase.h" +#include "td/utils/port/PollFlags.h" namespace td { namespace detail { @@ -24,26 +25,23 @@ class WineventPoll final : public PollBase { WineventPoll &operator=(const WineventPoll &) = delete; WineventPoll(WineventPoll &&) = delete; WineventPoll &operator=(WineventPoll &&) = delete; - ~WineventPoll() override = default; + ~WineventPoll() final = default; - void init() override; + void init() final; - void clear() override; + void clear() final; - void subscribe(const Fd &fd, Fd::Flags flags) override; + void subscribe(PollableFd fd, PollFlags flags) final; - void unsubscribe(const Fd &fd) override; + void unsubscribe(PollableFdRef fd) final; - void unsubscribe_before_close(const Fd &fd) override; + void unsubscribe_before_close(PollableFdRef fd) final; - void run(int timeout_ms) override; + void run(int timeout_ms) final; - private: - struct FdInfo { - Fd fd_ref; - Fd::Flags flags; - }; - vector<FdInfo> fds_; + static bool is_edge_triggered() { + return true; + } }; } // namespace detail diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/skip_eintr.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/skip_eintr.h new file mode 100644 index 0000000000..6fde635680 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/detail/skip_eintr.h @@ -0,0 +1,63 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#if TD_PORT_POSIX +#include "td/utils/common.h" +#include "td/utils/Time.h" + +#include <cerrno> +#include <type_traits> +#endif + +namespace td { + +#if TD_PORT_POSIX +namespace detail { +template <class F> +auto skip_eintr(F &&f) { + decltype(f()) res; + static_assert(std::is_integral<decltype(res)>::value, "integral type expected"); + do { + errno = 0; // just in case + res = f(); + } while (res < 0 && errno == EINTR); + return res; +} + +template <class F> +auto skip_eintr_cstr(F &&f) { + char *res; + do { + errno = 0; // just in case + res = f(); + } while (res == nullptr && errno == EINTR); + return res; +} + +template <class F> +auto skip_eintr_timeout(F &&f, int32 timeout_ms) { + decltype(f(timeout_ms)) res; + static_assert(std::is_integral<decltype(res)>::value, "integral type expected"); + + auto start = Timestamp::now(); + auto left_timeout_ms = timeout_ms; + while (true) { + errno = 0; // just in case + res = f(left_timeout_ms); + if (res >= 0 || errno != EINTR) { + break; + } + left_timeout_ms = + static_cast<int32>(td::max((start.at() - Timestamp::now().at()) * 1000 + timeout_ms + 1 - 1e-9, 0.0)); + } + return res; +} +} // namespace detail +#endif + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp index 8b169fefa4..2f77457ac4 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.cpp @@ -1,19 +1,28 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/port/path.h" -#include "td/utils/port/Fd.h" +#include "td/utils/port/config.h" -#if TD_WINDOWS +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/port/detail/skip_eintr.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/SliceBuilder.h" + +#if TD_PORT_WINDOWS +#include "td/utils/port/FromApp.h" +#include "td/utils/port/wstring_convert.h" #include "td/utils/Random.h" #endif #if TD_PORT_POSIX +#include <dirent.h> #include <limits.h> #include <stdio.h> @@ -32,7 +41,13 @@ #endif +#if TD_DARWIN +#include <sys/syslimits.h> +#endif + +#include <cerrno> #include <cstdlib> +#include <string> namespace td { @@ -44,8 +59,7 @@ Status set_temporary_dir(CSlice dir) { input_dir += TD_DIR_SLASH; } TRY_STATUS(mkpath(input_dir, 0750)); - TRY_RESULT(real_dir, realpath(input_dir)); - temporary_dir = std::move(real_dir); + TRY_RESULT_ASSIGN(temporary_dir, realpath(input_dir)); return Status::OK(); } @@ -54,22 +68,47 @@ Status mkpath(CSlice path, int32 mode) { Status last_error = Status::OK(); for (size_t i = 1; i < path.size(); i++) { if (path[i] == TD_DIR_SLASH) { - last_error = mkdir(path.substr(0, i).str(), mode); + last_error = mkdir(PSLICE() << path.substr(0, i), mode); if (last_error.is_error() && first_error.is_ok()) { first_error = last_error.clone(); } } } if (last_error.is_error()) { - return first_error; + if (last_error.message() == first_error.message() && last_error.code() == first_error.code()) { + return first_error; + } + return last_error.move_as_error_suffix(PSLICE() << ": " << first_error); } return Status::OK(); } +Status rmrf(CSlice path) { + return walk_path(path, [](CSlice path, WalkPath::Type type) { + switch (type) { + case WalkPath::Type::EnterDir: + break; + case WalkPath::Type::ExitDir: + rmdir(path).ignore(); + break; + case WalkPath::Type::NotDir: + unlink(path).ignore(); + break; + } + }); +} + #if TD_PORT_POSIX Status mkdir(CSlice dir, int32 mode) { - int mkdir_res = skip_eintr([&] { return ::mkdir(dir.c_str(), static_cast<mode_t>(mode)); }); + int mkdir_res = [&] { + int res; + do { + errno = 0; // just in case + res = ::mkdir(dir.c_str(), static_cast<mode_t>(mode)); + } while (res < 0 && (errno == EINTR || errno == EAGAIN)); + return res; + }(); if (mkdir_res == 0) { return Status::OK(); } @@ -82,7 +121,7 @@ Status mkdir(CSlice dir, int32 mode) { } Status rename(CSlice from, CSlice to) { - int rename_res = skip_eintr([&] { return ::rename(from.c_str(), to.c_str()); }); + int rename_res = detail::skip_eintr([&] { return ::rename(from.c_str(), to.c_str()); }); if (rename_res < 0) { return OS_ERROR(PSLICE() << "Can't rename \"" << from << "\" to \"" << to << '\"'); } @@ -92,9 +131,9 @@ Status rename(CSlice from, CSlice to) { Result<string> realpath(CSlice slice, bool ignore_access_denied) { char full_path[PATH_MAX + 1]; string res; - char *err = skip_eintr_cstr([&] { return ::realpath(slice.c_str(), full_path); }); + char *err = detail::skip_eintr_cstr([&] { return ::realpath(slice.c_str(), full_path); }); if (err != full_path) { - if (ignore_access_denied && errno == EACCES) { + if (ignore_access_denied && (errno == EACCES || errno == EPERM)) { res = slice.str(); } else { return OS_ERROR(PSLICE() << "Realpath failed for \"" << slice << '"'); @@ -114,7 +153,7 @@ Result<string> realpath(CSlice slice, bool ignore_access_denied) { } Status chdir(CSlice dir) { - int chdir_res = skip_eintr([&] { return ::chdir(dir.c_str()); }); + int chdir_res = detail::skip_eintr([&] { return ::chdir(dir.c_str()); }); if (chdir_res) { return OS_ERROR(PSLICE() << "Can't change directory to \"" << dir << '"'); } @@ -122,7 +161,7 @@ Status chdir(CSlice dir) { } Status rmdir(CSlice dir) { - int rmdir_res = skip_eintr([&] { return ::rmdir(dir.c_str()); }); + int rmdir_res = detail::skip_eintr([&] { return ::rmdir(dir.c_str()); }); if (rmdir_res) { return OS_ERROR(PSLICE() << "Can't delete directory \"" << dir << '"'); } @@ -130,7 +169,7 @@ Status rmdir(CSlice dir) { } Status unlink(CSlice path) { - int unlink_res = skip_eintr([&] { return ::unlink(path.c_str()); }); + int unlink_res = detail::skip_eintr([&] { return ::unlink(path.c_str()); }); if (unlink_res) { return OS_ERROR(PSLICE() << "Can't unlink \"" << path << '"'); } @@ -177,7 +216,7 @@ Result<std::pair<FileFd, string>> mkstemp(CSlice dir) { } file_pattern += "tmpXXXXXXXXXX"; - int fd = skip_eintr([&] { return ::mkstemp(&file_pattern[0]); }); + int fd = detail::skip_eintr([&] { return ::mkstemp(&file_pattern[0]); }); if (fd == -1) { return OS_ERROR(PSLICE() << "Can't create temporary file \"" << file_pattern << '"'); } @@ -209,20 +248,159 @@ Result<string> mkdtemp(CSlice dir, Slice prefix) { dir_pattern.append(prefix.begin(), prefix.size()); dir_pattern += "XXXXXX"; - char *result = skip_eintr_cstr([&] { return ::mkdtemp(&dir_pattern[0]); }); + char *result = detail::skip_eintr_cstr([&] { return ::mkdtemp(&dir_pattern[0]); }); if (result == nullptr) { return OS_ERROR(PSLICE() << "Can't create temporary directory \"" << dir_pattern << '"'); } return result; } +namespace detail { +using WalkFunction = std::function<WalkPath::Action(CSlice name, WalkPath::Type type)>; +Result<bool> walk_path_dir(string &path, FileFd fd, const WalkFunction &func) TD_WARN_UNUSED_RESULT; + +Result<bool> walk_path_dir(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT; + +Result<bool> walk_path_file(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT; + +Result<bool> walk_path(string &path, const WalkFunction &func) TD_WARN_UNUSED_RESULT; + +Result<bool> walk_path_subdir(string &path, DIR *dir, const WalkFunction &func) { + while (true) { + errno = 0; + auto *entry = readdir(dir); + auto readdir_errno = errno; + if (readdir_errno) { + return Status::PosixError(readdir_errno, "readdir"); + } + if (entry == nullptr) { + return true; + } + Slice name = Slice(static_cast<const char *>(entry->d_name)); + if (name == "." || name == "..") { + continue; + } + auto size = path.size(); + if (path.back() != TD_DIR_SLASH) { + path += TD_DIR_SLASH; + } + path.append(name.begin(), name.size()); + SCOPE_EXIT { + path.resize(size); + }; + Result<bool> status = true; +#ifdef DT_DIR + if (entry->d_type == DT_UNKNOWN) { + status = walk_path(path, func); + } else if (entry->d_type == DT_DIR) { + status = walk_path_dir(path, func); + } else if (entry->d_type == DT_REG) { + status = walk_path_file(path, func); + } +#else +#if !TD_SOLARIS +#warning "Slow walk_path" +#endif + status = walk_path(path, func); +#endif + if (status.is_error() || !status.ok()) { + return status; + } + } +} + +Result<bool> walk_path_dir(string &path, DIR *subdir, const WalkFunction &func) { + SCOPE_EXIT { + closedir(subdir); + }; + switch (func(path, WalkPath::Type::EnterDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + return true; + case WalkPath::Action::Continue: + break; + } + auto status = walk_path_subdir(path, subdir, func); + if (status.is_error() || !status.ok()) { + return status; + } + switch (func(path, WalkPath::Type::ExitDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + case WalkPath::Action::Continue: + break; + } + return true; +} + +Result<bool> walk_path_dir(string &path, FileFd fd, const WalkFunction &func) { + auto native_fd = fd.move_as_native_fd(); + auto *subdir = fdopendir(native_fd.fd()); + if (subdir == nullptr) { + return OS_ERROR("fdopendir"); + } + native_fd.release(); + return walk_path_dir(path, subdir, func); +} + +Result<bool> walk_path_dir(string &path, const WalkFunction &func) { + auto *subdir = opendir(path.c_str()); + if (subdir == nullptr) { + return OS_ERROR(PSLICE() << tag("opendir", path)); + } + return walk_path_dir(path, subdir, func); +} + +Result<bool> walk_path_file(string &path, const WalkFunction &func) { + switch (func(path, WalkPath::Type::NotDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + case WalkPath::Action::Continue: + break; + } + return true; +} + +Result<bool> walk_path(string &path, const WalkFunction &func) { + TRY_RESULT(fd, FileFd::open(path, FileFd::Read)); + TRY_RESULT(stat, fd.stat()); + + bool is_dir = stat.is_dir_; + bool is_reg = stat.is_reg_; + if (is_dir) { + return walk_path_dir(path, std::move(fd), func); + } + + fd.close(); + if (is_reg) { + return walk_path_file(path, func); + } + + return true; +} +} // namespace detail + +Status WalkPath::do_run(CSlice path, const detail::WalkFunction &func) { + string curr_path; + curr_path.reserve(PATH_MAX + 10); + curr_path = path.c_str(); + TRY_STATUS(detail::walk_path(curr_path, func)); + return Status::OK(); +} + #endif #if TD_PORT_WINDOWS Status mkdir(CSlice dir, int32 mode) { TRY_RESULT(wdir, to_wstring(dir)); - auto status = CreateDirectoryW(wdir.c_str(), nullptr); + while (!wdir.empty() && (wdir.back() == L'/' || wdir.back() == L'\\')) { + wdir.pop_back(); + } + auto status = td::CreateDirectoryFromAppW(wdir.c_str(), nullptr); if (status == 0 && GetLastError() != ERROR_ALREADY_EXISTS) { return OS_ERROR(PSLICE() << "Can't create directory \"" << dir << '"'); } @@ -232,7 +410,7 @@ Status mkdir(CSlice dir, int32 mode) { Status rename(CSlice from, CSlice to) { TRY_RESULT(wfrom, to_wstring(from)); TRY_RESULT(wto, to_wstring(to)); - auto status = MoveFileExW(wfrom.c_str(), wto.c_str(), MOVEFILE_REPLACE_EXISTING); + auto status = td::MoveFileExFromAppW(wfrom.c_str(), wto.c_str(), MOVEFILE_REPLACE_EXISTING); if (status == 0) { return OS_ERROR(PSLICE() << "Can't rename \"" << from << "\" to \"" << to << '\"'); } @@ -251,8 +429,7 @@ Result<string> realpath(CSlice slice, bool ignore_access_denied) { return OS_ERROR(PSLICE() << "GetFullPathNameW failed for \"" << slice << '"'); } } else { - TRY_RESULT(t_res, from_wstring(buf)); - res = std::move(t_res); + TRY_RESULT_ASSIGN(res, from_wstring(buf)); } if (res.empty()) { return Status::Error("Empty path"); @@ -276,7 +453,7 @@ Status chdir(CSlice dir) { Status rmdir(CSlice dir) { TRY_RESULT(wdir, to_wstring(dir)); - int status = RemoveDirectoryW(wdir.c_str()); + int status = td::RemoveDirectoryFromAppW(wdir.c_str()); if (!status) { return OS_ERROR(PSLICE() << "Can't delete directory \"" << dir << '"'); } @@ -285,7 +462,7 @@ Status rmdir(CSlice dir) { Status unlink(CSlice path) { TRY_RESULT(wpath, to_wstring(path)); - int status = DeleteFileW(wpath.c_str()); + int status = td::DeleteFileFromAppW(wpath.c_str()); if (!status) { return OS_ERROR(PSLICE() << "Can't unlink \"" << path << '"'); } @@ -302,7 +479,7 @@ CSlice get_temporary_dir() { } auto rs = from_wstring(buf); LOG_IF(FATAL, rs.is_error()) << "GetTempPathW failed: " << rs.error(); - temporary_dir = rs.ok(); + temporary_dir = rs.move_as_ok(); } if (temporary_dir.size() > 1 && temporary_dir.back() == TD_DIR_SLASH) { temporary_dir.pop_back(); @@ -332,9 +509,9 @@ Result<string> mkdtemp(CSlice dir, Slice prefix) { } dir_pattern.append(prefix.begin(), prefix.size()); - for (auto it = 0; it < 20; it++) { + for (auto iter = 0; iter < 20; iter++) { auto path = dir_pattern; - for (int i = 0; i < 6 + it / 5; i++) { + for (int i = 0; i < 6 + iter / 5; i++) { path += static_cast<char>(Random::fast('a', 'z')); } auto status = mkdir(path); @@ -364,9 +541,9 @@ Result<std::pair<FileFd, string>> mkstemp(CSlice dir) { } file_pattern += "tmp"; - for (auto it = 0; it < 20; it++) { + for (auto iter = 0; iter < 20; iter++) { auto path = file_pattern; - for (int i = 0; i < 6 + it / 5; i++) { + for (int i = 0; i < 6 + iter / 5; i++) { path += static_cast<char>(Random::fast('a', 'z')); } auto r_file = FileFd::open(path, FileFd::Write | FileFd::Read | FileFd::CreateNew); @@ -378,6 +555,79 @@ Result<std::pair<FileFd, string>> mkstemp(CSlice dir) { return Status::Error(PSLICE() << "Can't create temporary file \"" << file_pattern << '"'); } +static Result<bool> walk_path_dir(const std::wstring &dir_name, + const std::function<WalkPath::Action(CSlice name, WalkPath::Type type)> &func) { + std::wstring name = dir_name + L"\\*"; + WIN32_FIND_DATA file_data; + auto handle = + td::FindFirstFileExFromAppW(name.c_str(), FindExInfoStandard, &file_data, FindExSearchNameMatch, nullptr, 0); + if (handle == INVALID_HANDLE_VALUE) { + return OS_ERROR(PSLICE() << "FindFirstFileEx" << tag("name", from_wstring(name).ok())); + } + + SCOPE_EXIT { + FindClose(handle); + }; + + TRY_RESULT(dir_entry_name, from_wstring(dir_name)); + switch (func(dir_entry_name, WalkPath::Type::EnterDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + return true; + case WalkPath::Action::Continue: + break; + } + + while (true) { + auto full_name = dir_name + L"\\" + file_data.cFileName; + TRY_RESULT(entry_name, from_wstring(full_name)); + if (file_data.cFileName[0] != '.') { + if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + TRY_RESULT(is_ok, walk_path_dir(full_name, func)); + if (!is_ok) { + return false; + } + } else { + switch (func(entry_name, WalkPath::Type::NotDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + case WalkPath::Action::Continue: + break; + } + } + } + auto status = FindNextFileW(handle, &file_data); + if (status == 0) { + auto last_error = GetLastError(); + if (last_error == ERROR_NO_MORE_FILES) { + break; + } + return OS_ERROR("FindNextFileW"); + } + } + switch (func(dir_entry_name, WalkPath::Type::ExitDir)) { + case WalkPath::Action::Abort: + return false; + case WalkPath::Action::SkipDir: + case WalkPath::Action::Continue: + break; + } + return true; +} + +Status WalkPath::do_run(CSlice path, const std::function<Action(CSlice name, Type)> &func) { + TRY_RESULT(wpath, to_wstring(path)); + Slice path_slice = path; + while (!path_slice.empty() && (path_slice.back() == '/' || path_slice.back() == '\\')) { + path_slice.remove_suffix(1); + wpath.pop_back(); + } + TRY_STATUS(walk_path_dir(wpath, func)); + return Status::OK(); +} + #endif } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h index 47b7d3a350..0ac73ccb31 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/path.h @@ -1,225 +1,72 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/port/config.h" - #include "td/utils/common.h" -#include "td/utils/format.h" -#include "td/utils/logging.h" #include "td/utils/port/FileFd.h" -#include "td/utils/ScopeGuard.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" +#include <functional> +#include <type_traits> #include <utility> -#if TD_PORT_POSIX -#include <dirent.h> -#include <sys/types.h> -#endif - -#if TD_DARWIN -#include <sys/syslimits.h> -#endif - -#if TD_PORT_WINDOWS -#include "td/utils/port/wstring_convert.h" - -#include <string> -#endif - namespace td { Status mkdir(CSlice dir, int32 mode = 0700) TD_WARN_UNUSED_RESULT; + Status mkpath(CSlice path, int32 mode = 0700) TD_WARN_UNUSED_RESULT; + Status rename(CSlice from, CSlice to) TD_WARN_UNUSED_RESULT; + Result<string> realpath(CSlice slice, bool ignore_access_denied = false) TD_WARN_UNUSED_RESULT; + Status chdir(CSlice dir) TD_WARN_UNUSED_RESULT; + Status rmdir(CSlice dir) TD_WARN_UNUSED_RESULT; + Status unlink(CSlice path) TD_WARN_UNUSED_RESULT; -Status set_temporary_dir(CSlice dir) TD_WARN_UNUSED_RESULT; -CSlice get_temporary_dir(); -Result<std::pair<FileFd, string>> mkstemp(CSlice dir) TD_WARN_UNUSED_RESULT; -Result<string> mkdtemp(CSlice dir, Slice prefix) TD_WARN_UNUSED_RESULT; -template <class Func> -Status walk_path(CSlice path, Func &func) TD_WARN_UNUSED_RESULT; - -#if TD_PORT_POSIX - -// TODO move details somewhere else -namespace detail { -template <class Func> -Status walk_path_dir(string &path, FileFd fd, Func &&func) TD_WARN_UNUSED_RESULT; -template <class Func> -Status walk_path_dir(string &path, Func &&func) TD_WARN_UNUSED_RESULT; -template <class Func> -Status walk_path_file(string &path, Func &&func) TD_WARN_UNUSED_RESULT; -template <class Func> -Status walk_path(string &path, Func &&func) TD_WARN_UNUSED_RESULT; - -template <class Func> -Status walk_path_subdir(string &path, DIR *dir, Func &&func) { - while (true) { - errno = 0; - auto *entry = readdir(dir); - auto readdir_errno = errno; - if (readdir_errno) { - return Status::PosixError(readdir_errno, "readdir"); - } - if (entry == nullptr) { - return Status::OK(); - } - Slice name = Slice(&*entry->d_name); - if (name == "." || name == "..") { - continue; - } - auto size = path.size(); - if (path.back() != TD_DIR_SLASH) { - path += TD_DIR_SLASH; - } - path.append(name.begin(), name.size()); - SCOPE_EXIT { - path.resize(size); - }; - Status status; -#ifdef DT_DIR - if (entry->d_type == DT_UNKNOWN) { - status = walk_path(path, std::forward<Func>(func)); - } else if (entry->d_type == DT_DIR) { - status = walk_path_dir(path, std::forward<Func>(func)); - } else if (entry->d_type == DT_REG) { - status = walk_path_file(path, std::forward<Func>(func)); - } -#else -#warning "Slow walk_path" - status = walk_path(path, std::forward<Func>(func)); -#endif - if (status.is_error()) { - return status; - } - } -} +Status rmrf(CSlice path) TD_WARN_UNUSED_RESULT; -template <class Func> -Status walk_path_dir(string &path, DIR *subdir, Func &&func) { - SCOPE_EXIT { - closedir(subdir); - }; - TRY_STATUS(walk_path_subdir(path, subdir, std::forward<Func>(func))); - std::forward<Func>(func)(path, true); - return Status::OK(); -} +Status set_temporary_dir(CSlice dir) TD_WARN_UNUSED_RESULT; -template <class Func> -Status walk_path_dir(string &path, FileFd fd, Func &&func) { - auto *subdir = fdopendir(fd.get_fd().move_as_native_fd()); - if (subdir == nullptr) { - auto error = OS_ERROR("fdopendir"); - fd.close(); - return error; - } - return walk_path_dir(path, subdir, std::forward<Func>(func)); -} +CSlice get_temporary_dir(); -template <class Func> -Status walk_path_dir(string &path, Func &&func) { - auto *subdir = opendir(path.c_str()); - if (subdir == nullptr) { - return OS_ERROR(PSLICE() << tag("opendir", path)); - } - return walk_path_dir(path, subdir, std::forward<Func>(func)); -} +Result<std::pair<FileFd, string>> mkstemp(CSlice dir) TD_WARN_UNUSED_RESULT; -template <class Func> -Status walk_path_file(string &path, Func &&func) { - std::forward<Func>(func)(path, false); - return Status::OK(); -} +Result<string> mkdtemp(CSlice dir, Slice prefix) TD_WARN_UNUSED_RESULT; -template <class Func> -Status walk_path(string &path, Func &&func) { - TRY_RESULT(fd, FileFd::open(path, FileFd::Read)); - auto stat = fd.stat(); - bool is_dir = stat.is_dir_; - bool is_reg = stat.is_reg_; - if (is_dir) { - return walk_path_dir(path, std::move(fd), std::forward<Func>(func)); - } +class WalkPath { + public: + enum class Action { Continue, Abort, SkipDir }; + enum class Type { EnterDir, ExitDir, NotDir }; - fd.close(); - if (is_reg) { - return walk_path_file(path, std::forward<Func>(func)); + template <class F, class R = decltype(std::declval<F>()("", Type::ExitDir))> + static TD_WARN_UNUSED_RESULT std::enable_if_t<std::is_same<R, Action>::value, Status> run(CSlice path, F &&func) { + return do_run(path, func); } - - return Status::OK(); -} -} // namespace detail - -template <class Func> -Status walk_path(CSlice path, Func &&func) { - string curr_path; - curr_path.reserve(PATH_MAX + 10); - curr_path = path.c_str(); - return detail::walk_path(curr_path, std::forward<Func>(func)); -} - -#endif - -#if TD_PORT_WINDOWS - -namespace detail { -template <class Func> -Status walk_path_dir(const std::wstring &dir_name, Func &&func) { - std::wstring name = dir_name + L"\\*"; - - WIN32_FIND_DATA file_data; - auto handle = FindFirstFileExW(name.c_str(), FindExInfoStandard, &file_data, FindExSearchNameMatch, nullptr, 0); - if (handle == INVALID_HANDLE_VALUE) { - return OS_ERROR(PSLICE() << "FindFirstFileEx" << tag("name", from_wstring(name).ok())); + template <class F, class R = decltype(std::declval<F>()("", Type::ExitDir))> + static TD_WARN_UNUSED_RESULT std::enable_if_t<!std::is_same<R, Action>::value, Status> run(CSlice path, F &&func) { + return do_run(path, [&](CSlice name, Type type) { + func(name, type); + return Action::Continue; + }); } - SCOPE_EXIT { - FindClose(handle); - }; - while (true) { - auto full_name = dir_name + L"\\" + file_data.cFileName; - TRY_RESULT(entry_name, from_wstring(full_name)); - if (file_data.cFileName[0] != '.') { - if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - TRY_STATUS(walk_path_dir(full_name, func)); - func(entry_name, true); - } else { - func(entry_name, false); - } - } - auto status = FindNextFileW(handle, &file_data); - if (status == 0) { - auto last_error = GetLastError(); - if (last_error == ERROR_NO_MORE_FILES) { - return Status::OK(); - } - return OS_ERROR("FindNextFileW"); - } - } -} -} // namespace detail + private: + static TD_WARN_UNUSED_RESULT Status do_run(CSlice path, + const std::function<WalkPath::Action(CSlice name, Type type)> &func); +}; -template <class Func> -Status walk_path(CSlice path, Func &&func) { - Slice path_slice = path; - while (!path_slice.empty() && (path_slice.back() == '/' || path_slice.back() == '\\')) { - path_slice.remove_suffix(1); - } - TRY_RESULT(wpath, to_wstring(path_slice)); - return detail::walk_path_dir(wpath.c_str(), func); +// deprecated interface +template <class F> +TD_WARN_UNUSED_RESULT Status walk_path(CSlice path, F &&func) { + return WalkPath::run(path, func); } -#endif - } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.cpp new file mode 100644 index 0000000000..9fdd36528e --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.cpp @@ -0,0 +1,25 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/platform.h" + +char disable_linker_warning_about_empty_file_platform_cpp TD_UNUSED; + +#if !TD_MSVC + +#if TD_DARWIN_UNKNOWN +#warning "Probably unsupported Apple operating system. Feel free to try to compile" +#endif + +#if TD_UNIX_UNKNOWN +#warning "Probably unsupported Unix operating system. Feel free to try to compile" +#endif + +#if TD_COMPILER_UNKNOWN +#warning "Probably unsupported compiler. Feel free to try to compile" +#endif + +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h index a1c3776a40..0d896ff3c2 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/platform.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -9,7 +9,7 @@ // clang-format off /*** Platform macros ***/ -#if defined(_WIN32) +#if defined(_WIN32) || defined(_WINDOWS) // _WINDOWS is defined by CMake #if defined(__cplusplus_winrt) #define TD_WINRT 1 #endif @@ -20,7 +20,7 @@ #elif defined(__APPLE__) #include "TargetConditionals.h" #if TARGET_OS_IPHONE - // iOS/Apple Watch OS/Apple TV OS + // iOS/iPadOS/watchOS/tvOS #if TARGET_OS_IOS #define TD_DARWIN_IOS 1 #elif TARGET_OS_TV @@ -28,13 +28,13 @@ #elif TARGET_OS_WATCH #define TD_DARWIN_WATCH_OS 1 #else - #warning "Probably unsupported Apple iPhone platform. Feel free to try to compile" + #define TD_DARWIN_UNKNOWN 1 #endif #elif TARGET_OS_MAC - // Other kinds of Mac OS + // Other kinds of macOS #define TD_DARWIN_MAC 1 #else - #warning "Probably unsupported Apple platform. Feel free to try to compile" + #define TD_DARWIN_UNKNOWN 1 #endif #define TD_DARWIN 1 #elif defined(ANDROID) || defined(__ANDROID__) @@ -43,12 +43,21 @@ #define TD_TIZEN 1 #elif defined(__linux__) #define TD_LINUX 1 +#elif defined(__FreeBSD__) + #define TD_FREEBSD 1 +#elif defined(__OpenBSD__) + #define TD_OPENBSD 1 +#elif defined(__NetBSD__) + #define TD_NETBSD 1 #elif defined(__CYGWIN__) #define TD_CYGWIN 1 #elif defined(__EMSCRIPTEN__) #define TD_EMSCRIPTEN 1 +#elif defined(__sun) + #define TD_SOLARIS 1 + // TD_ILLUMOS can be already defined by CMake #elif defined(__unix__) // all unices not caught above - #warning "Probably unsupported Unix platform. Feel free to try to compile" + #define TD_UNIX_UNKNOWN 1 #define TD_CYGWIN 1 #else #error "Probably unsupported platform. Feel free to remove the error and try to recompile" @@ -63,7 +72,7 @@ #elif defined(_MSC_VER) #define TD_MSVC 1 #else - #warning "Probably unsupported compiler. Feel free to try to compile" + #define TD_COMPILER_UNKNOWN 1 #endif #if TD_GCC || TD_CLANG || TD_INTEL @@ -103,4 +112,8 @@ #define TD_CONCURRENCY_PAD 128 +#if !TD_WINDOWS && defined(__SIZEOF_INT128__) +#define TD_HAVE_INT128 1 +#endif + // clang-format on diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.cpp new file mode 100644 index 0000000000..8bc94dbfa2 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.cpp @@ -0,0 +1,97 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/rlimit.h" + +#include "td/utils/port/config.h" + +#include "td/utils/misc.h" + +#if TD_PORT_POSIX +#include <sys/resource.h> +#include <sys/time.h> +#include <sys/types.h> +#endif + +namespace td { + +#if TD_PORT_POSIX +static int get_resource(ResourceLimitType type) { + switch (type) { + case ResourceLimitType::NoFile: + return RLIMIT_NOFILE; + default: + UNREACHABLE(); + return -1; + } +} +#endif + +Status set_resource_limit(ResourceLimitType type, uint64 value, uint64 max_value) { +#if TD_PORT_POSIX + if (max_value != 0 && value > max_value) { + return Status::Error("New resource limit value must not be bigger than max_value"); + } + + int resource = get_resource(type); + + rlimit rlim; + if (getrlimit(resource, &rlim) == -1) { + return OS_ERROR("Failed to get current resource limit"); + } + + TRY_RESULT(new_value, narrow_cast_safe<rlim_t>(value)); + TRY_RESULT(new_max_value, narrow_cast_safe<rlim_t>(max_value)); + if (new_max_value) { + rlim.rlim_max = new_max_value; + } else if (rlim.rlim_max < new_value) { + rlim.rlim_max = new_value; + } + rlim.rlim_cur = new_value; + + if (setrlimit(resource, &rlim) < 0) { + return OS_ERROR("Failed to set resource limit"); + } + return Status::OK(); +#elif TD_PORT_WINDOWS + return Status::OK(); // Windows has no limits +#endif +} + +Status set_maximize_resource_limit(ResourceLimitType type, uint64 value) { +#if TD_PORT_POSIX + int resource = get_resource(type); + + rlimit rlim; + if (getrlimit(resource, &rlim) == -1) { + return OS_ERROR("Failed to get current resource limit"); + } + + TRY_RESULT(new_value, narrow_cast_safe<rlim_t>(value)); + if (rlim.rlim_max < new_value) { + // trying to increase rlim_max + rlimit new_rlim; + new_rlim.rlim_cur = new_value; + new_rlim.rlim_max = new_value; + if (setrlimit(resource, &new_rlim) >= 0) { + return Status::OK(); + } + + // do not increase rlim_max if have no rights + new_value = rlim.rlim_max; + } + rlim.rlim_cur = new_value; + + if (setrlimit(resource, &rlim) < 0) { + return OS_ERROR("Failed to set resource limit"); + } + return Status::OK(); +#elif TD_PORT_WINDOWS + return Status::OK(); // Windows has no limits +#endif +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.h new file mode 100644 index 0000000000..a987a7bdac --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/rlimit.h @@ -0,0 +1,20 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Status.h" + +namespace td { + +enum class ResourceLimitType { NoFile }; + +Status set_resource_limit(ResourceLimitType type, uint64 value, uint64 max_value = 0); + +Status set_maximize_resource_limit(ResourceLimitType type, uint64 value); + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp index 8627474d63..d61cf69d94 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,9 +7,11 @@ #include "td/utils/port/signals.h" #include "td/utils/port/config.h" +#include "td/utils/port/stacktrace.h" +#include "td/utils/port/StdStreams.h" +#include "td/utils/common.h" #include "td/utils/format.h" -#include "td/utils/logging.h" #if TD_PORT_POSIX #include <signal.h> @@ -22,6 +24,7 @@ #include <cerrno> #include <cstdint> +#include <cstdlib> #include <cstring> #include <ctime> #include <limits> @@ -63,23 +66,24 @@ Status setup_signals_alt_stack() { } #if TD_PORT_POSIX +static void set_handler(struct sigaction &act, decltype(act.sa_handler) handler) { + act.sa_handler = handler; +} +static void set_handler(struct sigaction &act, decltype(act.sa_sigaction) handler) { + act.sa_sigaction = handler; + act.sa_flags |= SA_SIGINFO; +} template <class F> -static Status set_signal_handler_impl(vector<int> signals, F func, bool is_extended = false) { +static Status set_signal_handler_impl(vector<int> &&signals, F func) { struct sigaction act; std::memset(&act, '\0', sizeof(act)); - if (is_extended) { // TODO if constexpr, remove useless reinterpret_cast - act.sa_handler = reinterpret_cast<decltype(act.sa_handler)>(func); - } else { - act.sa_sigaction = reinterpret_cast<decltype(act.sa_sigaction)>(func); - } + sigemptyset(&act.sa_mask); for (auto signal : signals) { sigaddset(&act.sa_mask, signal); } act.sa_flags = SA_RESTART | SA_ONSTACK; - if (is_extended) { - act.sa_flags |= SA_SIGINFO; - } + set_handler(act, func); for (auto signal : signals) { if (sigaction(signal, &act, nullptr) != 0) { @@ -109,10 +113,23 @@ static vector<int> get_native_signals(SignalType type) { return {}; } } -#endif -#if TD_PORT_WINDOWS -static Status set_signal_handler_impl(vector<int> signals, void (*func)(int sig), bool /*unused*/ = true) { +#elif TD_PORT_WINDOWS +using signal_handler = void (*)(int sig); +static signal_handler signal_handlers[NSIG] = {}; + +static void signal_handler_func(int sig) { + std::signal(sig, signal_handler_func); + auto handler = signal_handlers[sig]; + handler(sig); +} + +static Status set_signal_handler_impl(vector<int> &&signals, void (*func)(int sig)) { for (auto signal : signals) { + CHECK(0 <= signal && signal < NSIG); + if (func != SIG_IGN && func != SIG_DFL) { + signal_handlers[signal] = func; + func = signal_handler_func; + } if (std::signal(signal, func) == SIG_ERR) { return Status::Error("Failed to set signal handler"); } @@ -142,7 +159,7 @@ static vector<int> get_native_signals(SignalType type) { } #endif -Status set_signal_handler(SignalType type, void (*func)(int)) { +Status set_signal_handler(SignalType type, void (*func)(int sig)) { return set_signal_handler_impl(get_native_signals(type), func == nullptr ? SIG_DFL : func); } @@ -171,13 +188,13 @@ Status set_extended_signal_handler(SignalType type, extended_signal_handler func UNREACHABLE(); } } - return set_signal_handler_impl(std::move(signals), siginfo_handler, true); + return set_signal_handler_impl(std::move(signals), siginfo_handler); } -Status set_runtime_signal_handler(int runtime_signal_number, void (*func)(int)) { +Status set_real_time_signal_handler(int real_time_signal_number, void (*func)(int)) { #ifdef SIGRTMIN - CHECK(SIGRTMIN + runtime_signal_number <= SIGRTMAX); - return set_signal_handler_impl({SIGRTMIN + runtime_signal_number}, func == nullptr ? SIG_DFL : func); + CHECK(SIGRTMIN + real_time_signal_number <= SIGRTMAX); + return set_signal_handler_impl({SIGRTMIN + real_time_signal_number}, func == nullptr ? SIG_DFL : func); #else return Status::OK(); #endif @@ -279,13 +296,13 @@ void signal_safe_write_signal_number(int sig, bool add_header) { } void signal_safe_write_pointer(void *p, bool add_header) { - std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(p); + auto addr = reinterpret_cast<std::uintptr_t>(p); char buf[100]; char *end = buf + sizeof(buf); char *ptr = end; *--ptr = '\n'; do { - *--ptr = td::format::hex_digit(addr % 16); + *--ptr = format::hex_digit(addr % 16); addr /= 16; } while (addr != 0); *--ptr = 'x'; @@ -295,4 +312,33 @@ void signal_safe_write_pointer(void *p, bool add_header) { signal_safe_write(Slice(ptr, end), add_header); } +static void block_stdin() { +#if TD_PORT_POSIX + Stdin().get_native_fd().set_is_blocking(true).ignore(); +#endif +} + +static void default_failure_signal_handler(int sig) { + Stacktrace::init(); + signal_safe_write_signal_number(sig); + + Stacktrace::PrintOptions options; + options.use_gdb = true; + Stacktrace::print_to_stderr(options); + + block_stdin(); + _Exit(EXIT_FAILURE); +} + +Status set_default_failure_signal_handler() { +#if TD_PORT_POSIX + Stdin(); // init static variables before atexit + std::atexit(block_stdin); +#endif + TRY_STATUS(setup_signals_alt_stack()); + TRY_STATUS(set_signal_handler(SignalType::Abort, default_failure_signal_handler)); + TRY_STATUS(set_signal_handler(SignalType::Error, default_failure_signal_handler)); + return Status::OK(); +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h index 1f6ed24732..fa5fbae976 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/signals.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -20,7 +20,7 @@ Status set_signal_handler(SignalType type, void (*func)(int sig)) TD_WARN_UNUSED Status set_extended_signal_handler(SignalType type, void (*func)(int sig, void *addr)) TD_WARN_UNUSED_RESULT; -Status set_runtime_signal_handler(int runtime_signal_number, void (*func)(int sig)) TD_WARN_UNUSED_RESULT; +Status set_real_time_signal_handler(int real_time_signal_number, void (*func)(int sig)) TD_WARN_UNUSED_RESULT; Status ignore_signal(SignalType type) TD_WARN_UNUSED_RESULT; @@ -31,4 +31,6 @@ void signal_safe_write_signal_number(int sig, bool add_header = true); void signal_safe_write_pointer(void *p, bool add_header = true); +Status set_default_failure_signal_handler(); + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp index 4f02f69dfb..c35de1f2ad 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h index 56cd9f7bcd..2f9d27a3d5 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/sleep.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.cpp new file mode 100644 index 0000000000..7fd07b2a62 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.cpp @@ -0,0 +1,139 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/stacktrace.h" + +#include "td/utils/port/signals.h" + +#if __GLIBC__ +#include <execinfo.h> +#endif + +#if TD_LINUX || TD_FREEBSD +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#if TD_LINUX +#include <sys/prctl.h> +#endif +#endif + +namespace td { + +namespace { + +void print_backtrace() { +#if TD_PORT_WINDOWS + void *buffer[128]; + USHORT nptrs = CaptureStackBackTrace(0, 128, buffer, nullptr); +#elif __GLIBC__ + void *buffer[128]; + int nptrs = backtrace(buffer, 128); +#else + return; +#endif + + signal_safe_write("------- Stack Backtrace -------\n", false); +#if TD_PORT_WINDOWS + for (USHORT i = 0; i < nptrs; i++) { + signal_safe_write_pointer(buffer[i], false); + } +#elif __GLIBC__ + backtrace_symbols_fd(buffer, nptrs, 2); +#endif + signal_safe_write("-------------------------------\n", false); +} + +void print_backtrace_gdb() { +#if TD_LINUX || TD_FREEBSD + char pid_buf[30]; + char *pid_buf_begin = pid_buf + sizeof(pid_buf); + pid_t pid = getpid(); + *--pid_buf_begin = '\0'; + do { + *--pid_buf_begin = static_cast<char>(pid % 10 + '0'); + pid /= 10; + } while (pid > 0); + + char name_buf[512]; + ssize_t res = readlink("/proc/self/exe", name_buf, 511); // TODO works only under Linux + if (res >= 0) { + name_buf[res] = 0; + +#if TD_LINUX +#if defined(PR_SET_DUMPABLE) + if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) { + signal_safe_write("Can't set dumpable\n"); + return; + } +#endif +#if defined(PR_SET_PTRACER) + // We can't use event fd because we are in a signal handler + int fds[2]; + bool need_set_ptracer = true; + if (pipe(fds) < 0) { + need_set_ptracer = false; + signal_safe_write("Can't create a pipe\n"); + } +#endif +#endif + + int child_pid = fork(); + if (child_pid < 0) { + signal_safe_write("Can't fork() to run gdb\n"); + return; + } + if (!child_pid) { +#if TD_LINUX && defined(PR_SET_PTRACER) + if (need_set_ptracer) { + char c; + if (read(fds[0], &c, 1) < 0) { + signal_safe_write("Failed to read from pipe\n"); + } + } +#endif + dup2(2, 1); // redirect output to stderr + execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "thread apply all bt full", name_buf, pid_buf_begin, + nullptr); + return; + } else { +#if TD_LINUX && defined(PR_SET_PTRACER) + if (need_set_ptracer) { + if (prctl(PR_SET_PTRACER, child_pid, 0, 0, 0) < 0) { + signal_safe_write("Can't set ptracer\n"); + } + if (write(fds[1], "a", 1) != 1) { + signal_safe_write("Can't write to pipe\n"); + } + } +#endif + waitpid(child_pid, nullptr, 0); + } + } else { + signal_safe_write("Can't get name of executable file to pass to gdb\n"); + } +#endif +} + +} // namespace + +void Stacktrace::print_to_stderr(const PrintOptions &options) { + print_backtrace(); + if (options.use_gdb) { + print_backtrace_gdb(); + } +} + +void Stacktrace::init() { +#if __GLIBC__ + // backtrace needs to be called once to ensure that next calls are async-signal-safe + void *buffer[1]; + backtrace(buffer, 1); +#endif +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.h new file mode 100644 index 0000000000..469a20dfe6 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/stacktrace.h @@ -0,0 +1,23 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +namespace td { + +class Stacktrace { + public: + struct PrintOptions { + bool use_gdb = false; + PrintOptions() { + } + }; + static void print_to_stderr(const PrintOptions &options = PrintOptions()); + + static void init(); +}; + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h index 3034e456e8..35053a32fb 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -22,9 +22,6 @@ namespace td { using thread = detail::ThreadStl; namespace this_thread = detail::this_thread_stl; #elif TD_THREAD_UNSUPPORTED - namespace this_thread { - inline void yield() {} - } #else #error "Thread's implementation is not defined" #endif diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp index aa4e371405..ee56a119cf 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.cpp @@ -1,23 +1,21 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/port/thread_local.h" -#include "td/utils/logging.h" - namespace td { namespace detail { static TD_THREAD_LOCAL int32 thread_id_; -static TD_THREAD_LOCAL std::vector<std::unique_ptr<Guard>> *thread_local_destructors; +static TD_THREAD_LOCAL std::vector<unique_ptr<Destructor>> *thread_local_destructors; -void add_thread_local_destructor(std::unique_ptr<Guard> destructor) { +void add_thread_local_destructor(unique_ptr<Destructor> destructor) { if (thread_local_destructors == nullptr) { - thread_local_destructors = new std::vector<std::unique_ptr<Guard>>(); + thread_local_destructors = new std::vector<unique_ptr<Destructor>>(); } thread_local_destructors->push_back(std::move(destructor)); } @@ -31,6 +29,7 @@ void clear_thread_locals() { delete to_delete; CHECK(detail::thread_local_destructors == nullptr); } + void set_thread_id(int32 id) { detail::thread_id_ = id; } @@ -38,4 +37,5 @@ void set_thread_id(int32 id) { int32 get_thread_id() { return detail::thread_id_; } + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h index 6d8c135e88..deaeb09508 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/thread_local.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -9,7 +9,7 @@ #include "td/utils/port/config.h" #include "td/utils/common.h" -#include "td/utils/ScopeGuard.h" +#include "td/utils/Destructor.h" #include <memory> #include <utility> @@ -27,13 +27,9 @@ namespace td { #endif // clang-format on -inline constexpr size_t max_thread_count() { - return 256; -} - // If raw_ptr is not nullptr, allocate T as in std::make_unique<T>(args...) and store pointer into raw_ptr template <class T, class P, class... ArgsT> -bool init_thread_local(P &raw_ptr, ArgsT &&... args); +bool init_thread_local(P &raw_ptr, ArgsT &&...args); // Destroy all thread locals, and store nullptr into corresponding pointers void clear_thread_locals(); @@ -43,14 +39,14 @@ void set_thread_id(int32 id); int32 get_thread_id(); namespace detail { -void add_thread_local_destructor(std::unique_ptr<Guard> destructor); +void add_thread_local_destructor(unique_ptr<Destructor> destructor); template <class T, class P, class... ArgsT> -void do_init_thread_local(P &raw_ptr, ArgsT &&... args) { +void do_init_thread_local(P &raw_ptr, ArgsT &&...args) { auto ptr = std::make_unique<T>(std::forward<ArgsT>(args)...); raw_ptr = ptr.get(); - detail::add_thread_local_destructor(create_lambda_guard([ptr = std::move(ptr), &raw_ptr]() mutable { + detail::add_thread_local_destructor(create_destructor([ptr = std::move(ptr), &raw_ptr]() mutable { ptr.reset(); raw_ptr = nullptr; })); @@ -58,7 +54,7 @@ void do_init_thread_local(P &raw_ptr, ArgsT &&... args) { } // namespace detail template <class T, class P, class... ArgsT> -bool init_thread_local(P &raw_ptr, ArgsT &&... args) { +bool init_thread_local(P &raw_ptr, ArgsT &&...args) { if (likely(raw_ptr != nullptr)) { return false; } diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.cpp new file mode 100644 index 0000000000..5c380b5e05 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.cpp @@ -0,0 +1,289 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/uname.h" + +#include "td/utils/port/config.h" + +#include "td/utils/common.h" +#include "td/utils/filesystem.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/port/Stat.h" +#include "td/utils/SliceBuilder.h" + +#if TD_PORT_POSIX + +#include <cstring> + +#if TD_ANDROID +#include <sys/system_properties.h> +#elif TD_EMSCRIPTEN +#include <cstdlib> + +#include <emscripten.h> +#else +#if TD_DARWIN +#include <sys/sysctl.h> +#include <sys/types.h> +#endif +#include <sys/utsname.h> +#endif + +#endif + +namespace td { + +#if TD_DARWIN || TD_LINUX +static string read_os_name(CSlice os_version_file_path, CSlice prefix, CSlice suffix) { + auto r_stat = stat(os_version_file_path); + if (r_stat.is_ok() && r_stat.ok().is_reg_ && r_stat.ok().size_ < (1 << 16)) { + auto r_file = read_file_str(os_version_file_path, r_stat.ok().size_); + if (r_file.is_ok()) { + auto begin_pos = r_file.ok().find(prefix.c_str()); + if (begin_pos != string::npos) { + begin_pos += prefix.size(); + auto end_pos = r_file.ok().find(suffix.c_str(), begin_pos); + if (end_pos != string::npos) { + auto os_version = trim(r_file.ok().substr(begin_pos, end_pos - begin_pos)); + if (os_version.find('\n') == string::npos) { + return os_version; + } + } + } + } + } + return string(); +} +#endif + +Slice get_operating_system_version() { + static string result = []() -> string { +#if TD_DARWIN + char version[256]; + size_t size = sizeof(version); + string os_version; + if (sysctlbyname("kern.osproductversion", version, &size, nullptr, 0) == 0) { + os_version = trim(string(version, size)); + } + if (os_version.empty()) { + os_version = read_os_name("/System/Library/CoreServices/SystemVersion.plist", + "<key>ProductUserVisibleVersion</key>\n\t<string>", "</string>\n"); + } + if (!os_version.empty()) { + os_version = " " + os_version; + } + +#if TD_DARWIN_IOS + return "iOS" + os_version; +#elif TD_DARWIN_TV_OS + return "tvOS" + os_version; +#elif TD_DARWIN_WATCH_OS + return "watchOS" + os_version; +#elif TD_DARWIN_MAC + return "macOS" + os_version; +#else + return "Darwin" + os_version; +#endif +#elif TD_PORT_POSIX +#if TD_ANDROID + char version[PROP_VALUE_MAX + 1]; + int length = __system_property_get("ro.build.version.release", version); + if (length > 0) { + return "Android " + string(version, length); + } +#elif TD_EMSCRIPTEN + // clang-format off + char *os_name_js = (char*)EM_ASM_INT(({ + function detectOsName() { + if (typeof process === 'object' && typeof process.platform === 'string') { // Node.js + switch (process.platform) { + case 'aix': + return 'IBM AIX'; + case 'android': + return 'Android'; + case 'darwin': + return 'macOS'; + case 'freebsd': + return 'FreeBSD'; + case 'linux': + return 'Linux'; + case 'openbsd': + return 'OpenBSD'; + case 'sunos': + return 'SunOS'; + case 'win32': + return 'Windows'; + case 'darwin': + return 'macOS'; + default: + return 'Node.js'; + } + } + + var userAgent = 'Unknown'; + if (typeof window === 'object') { // Web + userAgent = window.navigator.userAgent; + } else if (typeof importScripts === 'function') { // Web Worker + userAgent = navigator.userAgent; + } + + var match = /(Mac OS|Mac OS X|MacPPC|MacIntel|Mac_PowerPC|Macintosh) ([._0-9]+)/.exec(userAgent); + if (match !== null) { + return 'macOS ' + match[2].replace('_', '.'); + } + + match = /Android [._0-9]+/.exec(userAgent); + if (match !== null) { + return match[0].replace('_', '.'); + } + + if (/(iPhone|iPad|iPod)/.test(userAgent)) { + match = /OS ([._0-9]+)/.exec(userAgent); + if (match !== null) { + return 'iOS ' + match[1].replace('_', '.'); + } + return 'iOS'; + } + + var clientStrings = [ + {s:'Windows 11', r:/(Windows 11|Windows NT 11)/}, + // there is no way to distinguish Windows 10 from newer versions, so report it as just Windows. + // {s:'Windows 10 or later', r:/(Windows 10|Windows NT 10)/}, + {s:'Windows 8.1', r:/(Windows 8.1|Windows NT 6.3)/}, + {s:'Windows 8', r:/(Windows 8|Windows NT 6.2)/}, + {s:'Windows 7', r:/(Windows 7|Windows NT 6.1)/}, + {s:'Windows Vista', r:/Windows NT 6.0/}, + {s:'Windows Server 2003', r:/Windows NT 5.2/}, + {s:'Windows XP', r:/(Windows XP|Windows NT 5.1)/}, + {s:'Windows', r:/Windows/}, + {s:'Android', r:/Android/}, + {s:'FreeBSD', r:/FreeBSD/}, + {s:'OpenBSD', r:/OpenBSD/}, + {s:'Chrome OS', r:/CrOS/}, + {s:'Linux', r:/(Linux|X11)/}, + {s:'macOS', r:/(Mac OS|MacPPC|MacIntel|Mac_PowerPC|Macintosh)/}, + {s:'QNX', r:/QNX/}, + {s:'BeOS', r:/BeOS/} + ]; + for (var id in clientStrings) { + var cs = clientStrings[id]; + if (cs.r.test(userAgent)) { + return cs.s; + } + } + return 'Emscripten'; + } + + var os_name = detectOsName(); + var length = lengthBytesUTF8(os_name) + 1; + var result = _malloc(length); + stringToUTF8(os_name, result, length); + return result; + })); + // clang-format on + string os_name(os_name_js); + std::free(os_name_js); + + return os_name; +#else +#if TD_LINUX + auto os_name = read_os_name("/etc/os-release", "PRETTY_NAME=\"", "\"\n"); + if (!os_name.empty()) { + return os_name; + } +#endif + + struct utsname name; + int err = uname(&name); + if (err == 0) { + auto os_name = trim(PSTRING() << Slice(name.sysname, std::strlen(name.sysname)) << " " + << Slice(name.release, std::strlen(name.release))); + if (!os_name.empty()) { + return os_name; + } + } +#endif + LOG(ERROR) << "Failed to identify OS name; use generic one"; + +#if TD_ANDROID + return "Android"; +#elif TD_TIZEN + return "Tizen"; +#elif TD_LINUX + return "Linux"; +#elif TD_FREEBSD + return "FreeBSD"; +#elif TD_OPENBSD + return "OpenBSD"; +#elif TD_NETBSD + return "NetBSD"; +#elif TD_CYGWIN + return "Cygwin"; +#else + return "Unix"; +#endif + +#else + +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP | WINAPI_PARTITION_SYSTEM) + auto handle = GetModuleHandle(L"ntdll.dll"); + if (handle != nullptr) { + using RtlGetVersionPtr = LONG(WINAPI *)(_Out_ PRTL_OSVERSIONINFOEXW); + RtlGetVersionPtr RtlGetVersion = reinterpret_cast<RtlGetVersionPtr>(GetProcAddress(handle, "RtlGetVersion")); + if (RtlGetVersion != nullptr) { + RTL_OSVERSIONINFOEXW os_version_info = {}; + os_version_info.dwOSVersionInfoSize = sizeof(os_version_info); + if (RtlGetVersion(&os_version_info) == 0) { + auto major = os_version_info.dwMajorVersion; + auto minor = os_version_info.dwMinorVersion; + bool is_server = os_version_info.wProductType != VER_NT_WORKSTATION; + + if (major == 10) { + if (is_server) { + if (os_version_info.dwBuildNumber >= 20201) { + // https://techcommunity.microsoft.com/t5/windows-server-insiders/announcing/m-p/1614436 + return "Windows Server 2022"; + } + if (os_version_info.dwBuildNumber >= 17623) { + // https://techcommunity.microsoft.com/t5/windows-server-insiders/announcing/m-p/173715 + return "Windows Server 2019"; + } + return "Windows Server 2016"; + } + if (os_version_info.dwBuildNumber >= 21900) { // build numbers between 21391 and 21999 aren't used + return "Windows 11"; + } + return "Windows 10"; + } + if (major == 6 && minor == 3) { + return is_server ? "Windows Server 2012 R2" : "Windows 8.1"; + } + if (major == 6 && minor == 2) { + return is_server ? "Windows Server 2012" : "Windows 8"; + } + if (major == 6 && minor == 1) { + return is_server ? "Windows Server 2008 R2" : "Windows 7"; + } + if (major == 6 && minor == 0) { + return is_server ? "Windows Server 2008" : "Windows Vista"; + } + return is_server ? "Windows Server" : "Windows"; + } + } + } +#elif TD_WINRT + return "Windows 10"; +#endif + + LOG(ERROR) << "Failed to identify OS name; use generic one"; + return "Windows"; +#endif + }(); + return result; +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.h index a3ba32602f..aacb0210aa 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/GitInfo.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/uname.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,10 +10,6 @@ namespace td { -class GitInfo { - public: - static CSlice commit(); - static bool is_dirty(); -}; +Slice get_operating_system_version(); } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.cpp new file mode 100644 index 0000000000..b793405b82 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.cpp @@ -0,0 +1,57 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/port/user.h" + +#include "td/utils/port/config.h" + +#if TD_PORT_POSIX +#include "td/utils/SliceBuilder.h" + +#include <grp.h> +#include <pwd.h> +#if TD_DARWIN || TD_FREEBSD || TD_NETBSD +#include <sys/param.h> +#endif +#include <sys/types.h> +#include <unistd.h> +#endif + +namespace td { + +Status change_user(CSlice username, CSlice groupname) { +#if TD_PORT_POSIX + passwd *pw = getpwnam(username.c_str()); + if (pw == nullptr) { + return OS_ERROR(PSTRING() << "Can't find the user '" << username << "' to switch to"); + } + uid_t uid = pw->pw_uid; + gid_t gid = pw->pw_gid; + if (setgroups(1, &gid) == -1) { + return OS_ERROR("Failed to clear supplementary group list"); + } + if (!groupname.empty()) { + group *g = getgrnam(groupname.c_str()); + if (g == nullptr) { + return OS_ERROR("Can't find the group to switch to"); + } + gid = g->gr_gid; + } else if (initgroups(username.c_str(), gid) == -1) { + return OS_ERROR("Failed to load groups of user"); + } + if (setgid(gid) == -1) { + return OS_ERROR("failed to set effective group ID"); + } + if (setuid(uid) == -1) { + return OS_ERROR("failed to set effective user ID"); + } + return Status::OK(); +#else + return Status::Error("Changing effective user is not supported"); +#endif +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.h new file mode 100644 index 0000000000..041df03e83 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/user.h @@ -0,0 +1,16 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/Slice.h" +#include "td/utils/Status.h" + +namespace td { + +Status change_user(CSlice username, CSlice groupname = CSlice()); + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp index 56da62b96d..fd6f4dfa23 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,44 +10,95 @@ char disable_linker_warning_about_empty_file_wstring_convert_cpp TD_UNUSED; #if TD_PORT_WINDOWS -#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING +#include "td/utils/base64.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/utf8.h" -#include "td/utils/port/wstring_convert.h" - -#include <codecvt> -#include <locale> -#include <utility> +#include <cwchar> namespace td { -namespace detail { -template <class Facet> -class UsableFacet : public Facet { - public: - template <class... Args> - explicit UsableFacet(Args &&... args) : Facet(std::forward<Args>(args)...) { +Result<std::wstring> to_wstring(CSlice slice) { + if (!check_utf8(slice)) { + return Status::Error(PSLICE() << "String was expected to be encoded in UTF-8: " << base64_encode(slice)); } - ~UsableFacet() = default; -}; -} // namespace detail - -Result<std::wstring> to_wstring(Slice slice) { - // TODO(perf): optimize - std::wstring_convert<detail::UsableFacet<std::codecvt_utf8_utf16<wchar_t>>> converter; - auto res = converter.from_bytes(slice.begin(), slice.end()); - if (converter.converted() != slice.size()) { - return Status::Error("Wrong encoding"); + + size_t wstring_len = utf8_utf16_length(slice); + + std::wstring result(wstring_len, static_cast<wchar_t>(0)); + if (wstring_len) { + wchar_t *res = &result[0]; + for (size_t i = 0; i < slice.size();) { + uint32 a = static_cast<unsigned char>(slice[i++]); + if (a >= 0x80) { + uint32 b = static_cast<unsigned char>(slice[i++]); + if (a >= 0xe0) { + uint32 c = static_cast<unsigned char>(slice[i++]); + if (a >= 0xf0) { + uint32 d = static_cast<unsigned char>(slice[i++]); + uint32 val = ((a & 0x07) << 18) + ((b & 0x3f) << 12) + ((c & 0x3f) << 6) + (d & 0x3f) - 0x10000; + *res++ = static_cast<wchar_t>(0xD800 + (val >> 10)); + *res++ = static_cast<wchar_t>(0xDC00 + (val & 0x3ff)); + } else { + *res++ = static_cast<wchar_t>(((a & 0x0f) << 12) + ((b & 0x3f) << 6) + (c & 0x3f)); + } + } else { + *res++ = static_cast<wchar_t>(((a & 0x1f) << 6) + (b & 0x3f)); + } + } else { + *res++ = static_cast<wchar_t>(a); + } + } + CHECK(res == &result[0] + wstring_len); } - return res; + return result; } Result<string> from_wstring(const wchar_t *begin, size_t size) { - std::wstring_convert<detail::UsableFacet<std::codecvt_utf8_utf16<wchar_t>>> converter; - auto res = converter.to_bytes(begin, begin + size); - if (converter.converted() != size) { - return Status::Error("Wrong encoding"); + size_t result_len = 0; + for (size_t i = 0; i < size; i++) { + uint32 cur = begin[i]; + if ((cur & 0xF800) == 0xD800) { + if (i < size) { + uint32 next = begin[++i]; + if ((next & 0xFC00) == 0xDC00 && (cur & 0x400) == 0) { + result_len += 4; + continue; + } + } + + return Status::Error("Wrong wstring encoding"); + } + result_len += 1 + (cur >= 0x80) + (cur >= 0x800); + } + + std::string result(result_len, '\0'); + if (result_len) { + char *res = &result[0]; + for (size_t i = 0; i < size; i++) { + uint32 cur = begin[i]; + // TODO conversion uint32 -> signed char is implementation defined + if (cur <= 0x7f) { + *res++ = static_cast<char>(cur); + } else if (cur <= 0x7ff) { + *res++ = static_cast<char>(0xc0 | (cur >> 6)); + *res++ = static_cast<char>(0x80 | (cur & 0x3f)); + } else if ((cur & 0xF800) != 0xD800) { + *res++ = static_cast<char>(0xe0 | (cur >> 12)); + *res++ = static_cast<char>(0x80 | ((cur >> 6) & 0x3f)); + *res++ = static_cast<char>(0x80 | (cur & 0x3f)); + } else { + uint32 next = begin[++i]; + uint32 val = ((cur - 0xD800) << 10) + next - 0xDC00 + 0x10000; + + *res++ = static_cast<char>(0xf0 | (val >> 18)); + *res++ = static_cast<char>(0x80 | ((val >> 12) & 0x3f)); + *res++ = static_cast<char>(0x80 | ((val >> 6) & 0x3f)); + *res++ = static_cast<char>(0x80 | (val & 0x3f)); + } + } } - return res; + return result; } Result<string> from_wstring(const std::wstring &str) { @@ -55,7 +106,7 @@ Result<string> from_wstring(const std::wstring &str) { } Result<string> from_wstring(const wchar_t *begin) { - return from_wstring(begin, wcslen(begin)); + return from_wstring(begin, std::wcslen(begin)); } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h index a795d2bd92..fe453380d3 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/port/wstring_convert.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -18,7 +18,7 @@ namespace td { -Result<std::wstring> to_wstring(Slice slice); +Result<std::wstring> to_wstring(CSlice slice); Result<string> from_wstring(const std::wstring &str); diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h index 6d107e37f2..e6d69aae3e 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/queue.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,15 +7,10 @@ #pragma once #include "td/utils/port/EventFd.h" -#include "td/utils/port/thread.h" +#include "td/utils/port/sleep.h" #if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED -#if !TD_WINDOWS -#include <poll.h> -#include <sched.h> -#endif - #include <atomic> #include <type_traits> #include <utility> @@ -37,7 +32,7 @@ class Backoff { if (cnt < 1) { // 50 return true; } else { - td::this_thread::yield(); + usleep_for(1); return cnt < 3; // 500 } } @@ -52,7 +47,7 @@ class InfBackoff { if (cnt < 50) { return true; } else { - td::this_thread::yield(); + usleep_for(1); return true; } } @@ -72,7 +67,7 @@ class SPSCBlockQueue { } struct Position { - std::atomic<uint32> i; + std::atomic<uint32> i{0}; char pad[64 - sizeof(std::atomic<uint32>)]; uint32 local_writer_i; char pad2[64 - sizeof(uint32)]; @@ -155,7 +150,7 @@ class SPSCBlockQueue { } }; -template <class T, class BlockQueueT = SPSCBlockQueue<T> > +template <class T, class BlockQueueT = SPSCBlockQueue<T>> class SPSCChainQueue { public: using ValueType = T; @@ -257,7 +252,7 @@ class SPSCChainQueue { private: struct Node { BlockQueueT q_; - std::atomic<bool> is_closed_; + std::atomic<bool> is_closed_{false}; Node *next_; void init() { @@ -313,11 +308,11 @@ class BackoffQueue : public QueueT { } }; -template <class T, class QueueT = SPSCChainQueue<T> > +template <class T, class QueueT = SPSCChainQueue<T>> using InfBackoffQueue = BackoffQueue<T, QueueT, detail::InfBackoff>; -template <class T, class QueueT = BackoffQueue<T> > -class PollQueue : public QueueT { +template <class T, class QueueT = BackoffQueue<T>> +class PollQueue final : public QueueT { public: using ValueType = T; using QueueType = QueueT; @@ -393,25 +388,18 @@ class PollQueue : public QueueT { return res; } -// Just example of usage -#if !TD_WINDOWS + // Just an example of usage int reader_wait() { int res; - while ((res = reader_wait_nonblock()) == 0) { - // TODO: reader_flush? - pollfd fd; - fd.fd = reader_get_event_fd().get_fd().get_native_fd(); - fd.events = POLLIN; - poll(&fd, 1, -1); + reader_get_event_fd().wait(1000); } return res; } -#endif private: EventFd event_fd_; - std::atomic<int> wait_state_; + std::atomic<int> wait_state_{0}; int writer_wait_state_; int get_wait_state() { @@ -433,7 +421,7 @@ class PollQueue : public QueueT { #else -#include "td/utils/logging.h" +#include "td/utils/common.h" namespace td { diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.cpp new file mode 100644 index 0000000000..54fda579ed --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.cpp @@ -0,0 +1,271 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/tests.h" + +#include "td/utils/crypto.h" +#include "td/utils/filesystem.h" +#include "td/utils/Parser.h" +#include "td/utils/PathView.h" +#include "td/utils/port/path.h" +#include "td/utils/port/Stat.h" +#include "td/utils/Random.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" + +#include <map> + +namespace td { + +string rand_string(int from, int to, size_t len) { + string res(len, '\0'); + for (auto &c : res) { + c = static_cast<char>(Random::fast(from, to)); + } + return res; +} + +vector<string> rand_split(Slice str) { + vector<string> res; + size_t pos = 0; + while (pos < str.size()) { + size_t len; + if (Random::fast_bool()) { + len = Random::fast(1, 10); + } else { + len = Random::fast(100, 200); + } + res.push_back(str.substr(pos, len).str()); + pos += len; + } + return res; +} + +struct TestInfo { + string name; + string result_hash; // base64 +}; + +StringBuilder &operator<<(StringBuilder &sb, const TestInfo &info) { + // should I use JSON? + CHECK(!info.name.empty()); + CHECK(!info.result_hash.empty()); + return sb << info.name << " " << info.result_hash << "\n"; +} + +class RegressionTesterImpl final : public RegressionTester { + public: + static void destroy(CSlice db_path) { + unlink(db_path).ignore(); + } + + RegressionTesterImpl(string db_path, string db_cache_dir) + : db_path_(std::move(db_path)), db_cache_dir_(std::move(db_cache_dir)) { + load_db(db_path_).ignore(); + if (db_cache_dir_.empty()) { + db_cache_dir_ = PathView(db_path_).without_extension().str() + ".cache/"; + } + mkdir(db_cache_dir_).ensure(); + } + + Status verify_test(Slice name, Slice result) final { +#if TD_HAVE_OPENSSL + auto hash = PSTRING() << format::as_hex_dump<0>(Slice(sha256(result))); +#else + auto hash = to_string(crc64(result)); +#endif + TestInfo &old_test_info = tests_[name.str()]; + if (!old_test_info.result_hash.empty() && old_test_info.result_hash != hash) { + auto wa_path = db_cache_dir_ + "WA"; + write_file(wa_path, result).ensure(); + return Status::Error(PSLICE() << "Test " << name << " changed: " << tag("expected", old_test_info.result_hash) + << tag("got", hash)); + } + auto result_cache_path = db_cache_dir_ + hash; + if (stat(result_cache_path).is_error()) { + write_file(result_cache_path, result).ensure(); + } + if (!old_test_info.result_hash.empty()) { + return Status::OK(); + } + old_test_info.name = name.str(); + old_test_info.result_hash = hash; + is_dirty_ = true; + + return Status::OK(); + } + + void save_db() final { + if (!is_dirty_) { + return; + } + SCOPE_EXIT { + is_dirty_ = false; + }; + string buf(2000000, ' '); + StringBuilder sb(buf); + save_db(sb); + string new_db_path = db_path_ + ".new"; + write_file(new_db_path, sb.as_cslice()).ensure(); + rename(new_db_path, db_path_).ensure(); + } + + Slice magic() const { + return Slice("abce"); + } + + void save_db(StringBuilder &sb) { + sb << magic() << "\n"; + for (const auto &it : tests_) { + sb << it.second; + } + } + + Status load_db(CSlice path) { + TRY_RESULT(data, read_file(path)); + ConstParser parser(data.as_slice()); + auto db_magic = parser.read_word(); + if (db_magic != magic()) { + return Status::Error(PSLICE() << "Wrong magic " << db_magic); + } + while (true) { + TestInfo info; + info.name = parser.read_word().str(); + if (info.name.empty()) { + break; + } + info.result_hash = parser.read_word().str(); + tests_[info.name] = info; + } + return Status::OK(); + } + + private: + string db_path_; + string db_cache_dir_; + bool is_dirty_{false}; + + std::map<string, TestInfo> tests_; +}; + +void RegressionTester::destroy(CSlice path) { + RegressionTesterImpl::destroy(path); +} + +unique_ptr<RegressionTester> RegressionTester::create(string db_path, string db_cache_dir) { + return td::make_unique<RegressionTesterImpl>(std::move(db_path), std::move(db_cache_dir)); +} + +TestsRunner &TestsRunner::get_default() { + static TestsRunner default_runner; + return default_runner; +} + +void TestsRunner::add_test(string name, std::function<unique_ptr<Test>()> test) { + for (auto &it : tests_) { + if (it.first == name) { + LOG(FATAL) << "Test name collision " << name; + } + } + tests_.emplace_back(std::move(name), TestInfo{std::move(test), nullptr}); +} + +void TestsRunner::add_substr_filter(string str) { + if (str[0] != '+' && str[0] != '-') { + str = "+" + str; + } + substr_filters_.push_back(std::move(str)); +} + +void TestsRunner::set_regression_tester(unique_ptr<RegressionTester> regression_tester) { + regression_tester_ = std::move(regression_tester); +} + +void TestsRunner::set_stress_flag(bool flag) { + stress_flag_ = flag; +} + +void TestsRunner::run_all() { + while (run_all_step()) { + } +} + +bool TestsRunner::run_all_step() { + Guard guard(this); + if (state_.it == state_.end) { + state_.end = tests_.size(); + state_.it = 0; + } + + while (state_.it != state_.end) { + auto &name = tests_[state_.it].first; + auto &test = tests_[state_.it].second.test; + if (!state_.is_running) { + bool ok = true; + for (const auto &filter : substr_filters_) { + bool is_match = name.find(filter.substr(1)) != string::npos; + if (is_match != (filter[0] == '+')) { + ok = false; + break; + } + } + if (!ok) { + ++state_.it; + continue; + } + LOG(ERROR) << "Run test " << tag("name", name); + state_.start = Time::now(); + state_.start_unadjusted = Time::now_unadjusted(); + state_.is_running = true; + + CHECK(!test); + test = tests_[state_.it].second.creator(); + } + + if (test->step()) { + break; + } + + test = {}; + + auto passed = Time::now() - state_.start; + auto real_passed = Time::now_unadjusted() - state_.start_unadjusted; + if (real_passed + 1e-1 > passed) { + LOG(ERROR) << format::as_time(passed); + } else { + LOG(ERROR) << format::as_time(real_passed) << " adjusted [" << format::as_time(real_passed) << "]"; + } + if (regression_tester_) { + regression_tester_->save_db(); + } + state_.is_running = false; + ++state_.it; + } + + auto ret = state_.it != state_.end; + if (!ret) { + state_ = State(); + } + return ret || stress_flag_; +} + +Slice TestsRunner::name() { + CHECK(state_.is_running); + return tests_[state_.it].first; +} + +Status TestsRunner::verify(Slice data) { + if (!regression_tester_) { + LOG(INFO) << data; + LOG(ERROR) << "Cannot verify and save <" << name() << "> answer. Use --regression <regression_db> option"; + return Status::OK(); + } + return regression_tester_->verify_test(PSLICE() << name() << "_default", data); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h index 24e2f3fe22..69b9206268 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tests.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,139 +7,133 @@ #pragma once #include "td/utils/common.h" +#include "td/utils/Context.h" #include "td/utils/format.h" -#include "td/utils/List.h" #include "td/utils/logging.h" -#include "td/utils/port/thread.h" -#include "td/utils/Random.h" +#include "td/utils/port/sleep.h" #include "td/utils/Slice.h" -#include "td/utils/Time.h" +#include "td/utils/Status.h" #include <atomic> - -#define REGISTER_TESTS(x) \ - void TD_CONCAT(register_tests_, x)() { \ - } -#define DESC_TESTS(x) void TD_CONCAT(register_tests_, x)() -#define LOAD_TESTS(x) TD_CONCAT(register_tests_, x)() +#include <condition_variable> +#include <functional> +#include <mutex> +#include <utility> namespace td { -class Test : private ListNode { +class RandomSteps { public: - explicit Test(CSlice name) : name_(name) { - get_tests_list()->put_back(this); - } - - Test(const Test &) = delete; - Test &operator=(const Test &) = delete; - Test(Test &&) = delete; - Test &operator=(Test &&) = delete; - virtual ~Test() = default; + struct Step { + std::function<void()> func; + uint32 weight; + }; - static void add_substr_filter(std::string str) { - if (str[0] != '+' && str[0] != '-') { - str = "+" + str; + explicit RandomSteps(vector<Step> steps) : steps_(std::move(steps)) { + for (const auto &step : steps_) { + steps_sum_ += step.weight; } - get_substr_filters()->push_back(std::move(str)); } - static void set_stress_flag(bool flag) { - get_stress_flag() = flag; - } - static void run_all() { - while (run_all_step()) { - } - } - - static bool run_all_step() { - auto *state = get_state(); - if (state->it == nullptr) { - state->end = get_tests_list(); - state->it = state->end->next; - } - while (state->it != state->end) { - auto test = static_cast<td::Test *>(state->it); - if (!state->is_running) { - bool ok = true; - for (const auto &filter : *get_substr_filters()) { - bool is_match = test->name_.str().find(filter.substr(1)) != std::string::npos; - if (is_match != (filter[0] == '+')) { - ok = false; - break; - } - } - if (!ok) { - state->it = state->it->next; - continue; - } - LOG(ERROR) << "Run test " << tag("name", test->name_); - state->start = Time::now(); - state->is_running = true; - } - - if (test->step()) { + template <class Random> + void step(Random &rnd) const { + auto w = rnd() % steps_sum_; + for (const auto &step : steps_) { + if (w < step.weight) { + step.func(); break; } - - LOG(ERROR) << format::as_time(Time::now() - state->start); - state->is_running = false; - state->it = state->it->next; + w -= step.weight; } - - auto ret = state->it != state->end; - if (!ret) { - *state = State(); - } - return ret || get_stress_flag(); } private: - CSlice name_; - struct State { - ListNode *it = nullptr; - bool is_running = false; - double start; - ListNode *end = nullptr; - }; - static State *get_state() { - static State state; - return &state; - } - static std::vector<std::string> *get_substr_filters() { - static std::vector<std::string> substr_filters_; - return &substr_filters_; - } + vector<Step> steps_; + int32 steps_sum_ = 0; +}; - static ListNode *get_tests_list() { - static ListNode root; - return &root; - } - static bool &get_ok_flag() { - static bool is_ok = true; - return is_ok; - } - static bool &get_stress_flag() { - static bool stress_flag = false; - return stress_flag; - } +class RegressionTester { + public: + virtual ~RegressionTester() = default; + static void destroy(CSlice db_path); + static unique_ptr<RegressionTester> create(string db_path, string db_cache_dir = ""); + + virtual Status verify_test(Slice name, Slice result) = 0; + virtual void save_db() = 0; +}; + +class Test { + public: + virtual ~Test() = default; virtual void run() { while (step()) { } } - virtual bool step() { run(); return false; } + Test() = default; + Test(const Test &) = delete; + Test &operator=(const Test &) = delete; + Test(Test &&) = delete; + Test &operator=(Test &&) = delete; +}; + +class TestContext : public Context<TestContext> { + public: + virtual ~TestContext() = default; + virtual Slice name() = 0; + virtual Status verify(Slice data) = 0; +}; + +class TestsRunner final : public TestContext { + public: + static TestsRunner &get_default(); + + void add_test(string name, std::function<unique_ptr<Test>()> test); + void add_substr_filter(string str); + void set_stress_flag(bool flag); + void run_all(); + bool run_all_step(); + void set_regression_tester(unique_ptr<RegressionTester> regression_tester); + + private: + struct State { + size_t it{0}; + bool is_running = false; + double start{0}; + double start_unadjusted{0}; + size_t end{0}; + }; + bool stress_flag_{false}; + vector<string> substr_filters_; + struct TestInfo { + std::function<unique_ptr<Test>()> creator; + unique_ptr<Test> test; + }; + vector<std::pair<string, TestInfo>> tests_; + State state_; + unique_ptr<RegressionTester> regression_tester_; + + Slice name() final; + Status verify(Slice data) final; +}; + +template <class T> +class RegisterTest { + public: + explicit RegisterTest(string name, TestsRunner &runner = TestsRunner::get_default()) { + runner.add_test(std::move(name), [] { return make_unique<T>(); }); + } }; -class Stage { +class StageWait { public: void wait(uint64 need) { value_.fetch_add(1, std::memory_order_release); while (value_.load(std::memory_order_acquire) < need) { - td::this_thread::yield(); + usleep_for(1); } }; @@ -147,38 +141,38 @@ class Stage { std::atomic<uint64> value_{0}; }; -inline string rand_string(char from, char to, int len) { - string res(len, 0); - for (auto &c : res) { - c = static_cast<char>(Random::fast(from, to)); - } - return res; -} - -inline std::vector<string> rand_split(string str) { - std::vector<string> res; - size_t pos = 0; - while (pos < str.size()) { - size_t len; - if (Random::fast(0, 1) == 1) { - len = Random::fast(1, 10); - } else { - len = Random::fast(100, 200); +class StageMutex { + public: + void wait(uint64 need) { + std::unique_lock<std::mutex> lock{mutex_}; + value_++; + if (value_ == need) { + cond_.notify_all(); + return; } - res.push_back(str.substr(pos, len)); - pos += len; - } - return res; -} + cond_.wait(lock, [&] { return value_ >= need; }); + }; + + private: + std::mutex mutex_; + std::condition_variable cond_; + uint64 value_{0}; +}; + +using Stage = StageMutex; + +string rand_string(int from, int to, size_t len); + +vector<string> rand_split(Slice str); template <class T1, class T2> void assert_eq_impl(const T1 &expected, const T2 &got, const char *file, int line) { - CHECK(expected == got) << tag("expected", expected) << tag("got", got) << " in " << file << " at line " << line; + LOG_CHECK(expected == got) << tag("expected", expected) << tag("got", got) << " in " << file << " at line " << line; } template <class T> void assert_true_impl(const T &got, const char *file, int line) { - CHECK(got) << "Expected true in " << file << " at line " << line; + LOG_CHECK(got) << "Expected true in " << file << " at line " << line; } } // namespace td @@ -190,16 +184,18 @@ void assert_true_impl(const T &got, const char *file, int line) { #define ASSERT_STREQ(expected, got) \ ::td::assert_eq_impl(::td::Slice((expected)), ::td::Slice((got)), __FILE__, __LINE__) +#define REGRESSION_VERIFY(data) ::td::TestContext::get()->verify(data).ensure() + #define TEST_NAME(test_case_name, test_name) \ TD_CONCAT(Test, TD_CONCAT(_, TD_CONCAT(test_case_name, TD_CONCAT(_, test_name)))) #define TEST(test_case_name, test_name) TEST_IMPL(TEST_NAME(test_case_name, test_name)) -#define TEST_IMPL(test_name) \ - class test_name : public ::td::Test { \ - public: \ - using Test::Test; \ - void run() final; \ - }; \ - test_name TD_CONCAT(test_instance_, TD_CONCAT(test_name, __LINE__))(TD_DEFINE_STR(test_name)); \ +#define TEST_IMPL(test_name) \ + class test_name final : public ::td::Test { \ + public: \ + using Test::Test; \ + void run() final; \ + }; \ + ::td::RegisterTest<test_name> TD_CONCAT(test_instance_, TD_CONCAT(test_name, __LINE__))(TD_DEFINE_STR(test_name)); \ void test_name::run() diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h index 686dacbeef..ee5c9e6c3e 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_helpers.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -7,43 +7,57 @@ #pragma once #include "td/utils/common.h" -#include "td/utils/logging.h" +#include "td/utils/FlatHashSet.h" #include "td/utils/misc.h" +#include "td/utils/SharedSlice.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/StackAllocator.h" #include "td/utils/Status.h" #include "td/utils/tl_parsers.h" #include "td/utils/tl_storers.h" +#include "td/utils/Variant.h" #include <type_traits> -#include <unordered_set> +#include <utility> -#define BEGIN_STORE_FLAGS() \ - uint32 flags_store = 0; \ - uint32 bit_offset_store = 0 +#define BEGIN_STORE_FLAGS() \ + do { \ + ::td::uint32 flags_store = 0; \ + ::td::uint32 bit_offset_store = 0 -#define STORE_FLAG(flag) \ - flags_store |= (flag) << bit_offset_store; \ +#define STORE_FLAG(flag) \ + static_assert(std::is_same<decltype(flag), bool>::value, "flag must be bool"); \ + flags_store |= (flag) << bit_offset_store; \ bit_offset_store++ -#define END_STORE_FLAGS() \ - CHECK(bit_offset_store < 31); \ - td::store(flags_store, storer) +#define END_STORE_FLAGS() \ + CHECK(bit_offset_store < 31); \ + ::td::store(flags_store, storer); \ + } \ + while (false) -#define BEGIN_PARSE_FLAGS() \ - uint32 flags_parse; \ - uint32 bit_offset_parse = 0; \ - td::parse(flags_parse, parser) +#define BEGIN_PARSE_FLAGS() \ + do { \ + ::td::uint32 flags_parse; \ + ::td::uint32 bit_offset_parse = 0; \ + ::td::parse(flags_parse, parser) -#define PARSE_FLAG(flag) \ - flag = ((flags_parse >> bit_offset_parse) & 1) != 0; \ +#define PARSE_FLAG(flag) \ + static_assert(std::is_same<decltype(flag), bool>::value, "flag must be bool"); \ + flag = ((flags_parse >> bit_offset_parse) & 1) != 0; \ bit_offset_parse++ -#define END_PARSE_FLAGS() \ - CHECK(bit_offset_parse < 31); \ - CHECK((flags_parse & ~((1 << bit_offset_parse) - 1)) == 0) << flags_parse << " " << bit_offset_parse; +#define END_PARSE_FLAGS() \ + CHECK(bit_offset_parse < 31); \ + if ((flags_parse & ~((1 << bit_offset_parse) - 1)) != 0) { \ + parser.set_error(PSTRING() << "Invalid flags " << flags_parse << " left, current bit is " << bit_offset_parse); \ + } \ + } \ + while (false) namespace td { + template <class StorerT> void store(bool x, StorerT &storer) { storer.store_binary(static_cast<int32>(x)); @@ -105,11 +119,20 @@ template <class StorerT> void store(const string &x, StorerT &storer) { storer.store_string(x); } +template <class StorerT> +void store(const SecureString &x, StorerT &storer) { + storer.store_string(x.as_slice()); +} template <class ParserT> void parse(string &x, ParserT &parser) { x = parser.template fetch_string<string>(); } +template <class ParserT> +void parse(SecureString &x, ParserT &parser) { + x = parser.template fetch_string<SecureString>(); +} + template <class T, class StorerT> void store(const vector<T> &vec, StorerT &storer) { storer.store_binary(narrow_cast<int32>(vec.size())); @@ -117,6 +140,13 @@ void store(const vector<T> &vec, StorerT &storer) { store(val, storer); } } +template <class T, class StorerT> +void store(const vector<T *> &vec, StorerT &storer) { + storer.store_binary(narrow_cast<int32>(vec.size())); + for (auto &val : vec) { + store(*val, storer); + } +} template <class T, class ParserT> void parse(vector<T> &vec, ParserT &parser) { uint32 size = parser.fetch_int(); @@ -130,28 +160,51 @@ void parse(vector<T> &vec, ParserT &parser) { } } -template <class Key, class Hash, class KeyEqual, class Allocator, class StorerT> -void store(const std::unordered_set<Key, Hash, KeyEqual, Allocator> &s, StorerT &storer) { +template <class T, class StorerT> +void store(const unique_ptr<T> &ptr, StorerT &storer) { + CHECK(ptr != nullptr); + store(*ptr, storer); +} +template <class T, class ParserT> +void parse(unique_ptr<T> &ptr, ParserT &parser) { + CHECK(ptr == nullptr); + ptr = make_unique<T>(); + parse(*ptr, parser); +} + +template <class Key, class Hash, class KeyEqual, class StorerT> +void store(const FlatHashSet<Key, Hash, KeyEqual> &s, StorerT &storer) { storer.store_binary(narrow_cast<int32>(s.size())); for (auto &val : s) { store(val, storer); } } -template <class Key, class Hash, class KeyEqual, class Allocator, class ParserT> -void parse(std::unordered_set<Key, Hash, KeyEqual, Allocator> &s, ParserT &parser) { +template <class Key, class Hash, class KeyEqual, class ParserT> +void parse(FlatHashSet<Key, Hash, KeyEqual> &s, ParserT &parser) { uint32 size = parser.fetch_int(); if (parser.get_left_len() < size) { parser.set_error("Wrong set length"); return; } s.clear(); - Key val; for (uint32 i = 0; i < size; i++) { + Key val; parse(val, parser); s.insert(std::move(val)); } } +template <class U, class V, class StorerT> +void store(const std::pair<U, V> &pair, StorerT &storer) { + store(pair.first, storer); + store(pair.second, storer); +} +template <class U, class V, class ParserT> +void parse(std::pair<U, V> &pair, ParserT &parser) { + parse(pair.first, parser); + parse(pair.second, parser); +} + template <class T, class StorerT> std::enable_if_t<std::is_enum<T>::value> store(const T &val, StorerT &storer) { store(static_cast<int32>(val), storer); @@ -172,6 +225,29 @@ std::enable_if_t<!std::is_enum<T>::value> parse(T &val, ParserT &parser) { val.parse(parser); } +template <class... Types, class StorerT> +void store(const Variant<Types...> &variant, StorerT &storer) { + store(variant.get_offset(), storer); + variant.visit([&storer](auto &&value) { + using td::store; + store(value, storer); + }); +} +template <class... Types, class ParserT> +void parse(Variant<Types...> &variant, ParserT &parser) { + auto type_offset = parser.fetch_int(); + if (type_offset < 0 || type_offset >= static_cast<int32>(sizeof...(Types))) { + return parser.set_error("Invalid type"); + } + variant.for_each([type_offset, &parser, &variant](int offset, auto *ptr) { + using T = std::decay_t<decltype(*ptr)>; + if (offset == type_offset) { + variant = T(); + parse(variant.template get<T>(), parser); + } + }); +} + template <class T> string serialize(const T &object) { TlStorerCalcLength calc_length; @@ -182,22 +258,40 @@ string serialize(const T &object) { if (!is_aligned_pointer<4>(key.data())) { auto ptr = StackAllocator::alloc(length); MutableSlice data = ptr.as_slice(); - TlStorerUnsafe storer(data.begin()); + TlStorerUnsafe storer(data.ubegin()); store(object, storer); + CHECK(storer.get_buf() == data.uend()); key.assign(data.begin(), data.size()); } else { MutableSlice data = key; - TlStorerUnsafe storer(data.begin()); + TlStorerUnsafe storer(data.ubegin()); store(object, storer); + CHECK(storer.get_buf() == data.uend()); } return key; } template <class T> +SecureString serialize_secure(const T &object) { + TlStorerCalcLength calc_length; + store(object, calc_length); + size_t length = calc_length.get_length(); + + SecureString key(length, '\0'); + CHECK(is_aligned_pointer<4>(key.data())); + MutableSlice data = key.as_mutable_slice(); + TlStorerUnsafe storer(data.ubegin()); + store(object, storer); + CHECK(storer.get_buf() == data.uend()); + return key; +} + +template <class T> TD_WARN_UNUSED_RESULT Status unserialize(T &object, Slice data) { TlParser parser(data); parse(object, parser); parser.fetch_end(); return parser.get_status(); } + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp index 534e7793cf..d4315a43de 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.cpp @@ -1,15 +1,35 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/tl_parsers.h" +#include "td/utils/misc.h" + namespace td { alignas(4) const unsigned char TlParser::empty_data[sizeof(UInt256)] = {}; // static zero-initialized +TlParser::TlParser(Slice slice) { + data_len = left_len = slice.size(); + if (is_aligned_pointer<4>(slice.begin())) { + data = slice.ubegin(); + } else { + int32 *buf; + if (data_len <= small_data_array.size() * sizeof(int32)) { + buf = &small_data_array[0]; + } else { + LOG(ERROR) << "Unexpected big unaligned data pointer of length " << slice.size() << " at " << slice.begin(); + data_buf = std::make_unique<int32[]>(1 + data_len / sizeof(int32)); + buf = data_buf.get(); + } + std::memcpy(buf, slice.begin(), slice.size()); + data = reinterpret_cast<unsigned char *>(buf); + } +} + void TlParser::set_error(const string &error_message) { if (error.empty()) { CHECK(!error_message.empty()); @@ -19,11 +39,21 @@ void TlParser::set_error(const string &error_message) { left_len = 0; data_len = 0; } else { + LOG_CHECK(error_pos != std::numeric_limits<size_t>::max() && data_len == 0 && left_len == 0) + << data_len << " " << left_len << " " << data << " " << &empty_data[0] << " " << error_pos << " " << error + << " " << data << " " << &empty_data; data = empty_data; - CHECK(error_pos != std::numeric_limits<size_t>::max()); - CHECK(data_len == 0); - CHECK(left_len == 0); } } +BufferSlice TlBufferParser::as_buffer_slice(Slice slice) { + if (slice.empty()) { + return BufferSlice(); + } + if (is_aligned_pointer<4>(slice.data())) { + return parent_->from_slice(slice); + } + return BufferSlice(slice); +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h index ffb669bdeb..da9f2144e4 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_parsers.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -10,14 +10,16 @@ #include "td/utils/common.h" #include "td/utils/format.h" #include "td/utils/logging.h" -#include "td/utils/misc.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/Status.h" +#include "td/utils/UInt.h" #include "td/utils/utf8.h" #include <array> #include <cstring> #include <limits> +#include <memory> #include <string> namespace td { @@ -29,35 +31,14 @@ class TlParser { size_t error_pos = std::numeric_limits<size_t>::max(); std::string error; - unique_ptr<int32[]> data_buf; + std::unique_ptr<int32[]> data_buf; static constexpr size_t SMALL_DATA_ARRAY_SIZE = 6; std::array<int32, SMALL_DATA_ARRAY_SIZE> small_data_array; alignas(4) static const unsigned char empty_data[sizeof(UInt256)]; public: - explicit TlParser(Slice slice) { - if (slice.size() % sizeof(int32) != 0) { - set_error("Wrong length"); - return; - } - - data_len = left_len = slice.size(); - if (is_aligned_pointer<4>(slice.begin())) { - data = slice.ubegin(); - } else { - int32 *buf; - if (data_len <= small_data_array.size() * sizeof(int32)) { - buf = &small_data_array[0]; - } else { - LOG(ERROR) << "Unexpected big unaligned data pointer of length " << slice.size() << " at " << slice.begin(); - data_buf = make_unique<int32[]>(data_len / sizeof(int32)); - buf = data_buf.get(); - } - std::memcpy(static_cast<void *>(buf), static_cast<const void *>(slice.begin()), slice.size()); - data = reinterpret_cast<unsigned char *>(buf); - } - } + explicit TlParser(Slice slice); TlParser(const TlParser &other) = delete; TlParser &operator=(const TlParser &other) = delete; @@ -90,8 +71,19 @@ class TlParser { } } + bool can_prefetch_int() const { + return get_left_len() >= sizeof(int32); + } + + int32 prefetch_int_unsafe() const { + int32 result; + std::memcpy(&result, data, sizeof(int32)); + return result; + } + int32 fetch_int_unsafe() { - int32 result = *reinterpret_cast<const int32 *>(data); + int32 result; + std::memcpy(&result, data, sizeof(int32)); data += sizeof(int32); return result; } @@ -103,7 +95,7 @@ class TlParser { int64 fetch_long_unsafe() { int64 result; - std::memcpy(reinterpret_cast<unsigned char *>(&result), data, sizeof(int64)); + std::memcpy(&result, data, sizeof(int64)); data += sizeof(int64); return result; } @@ -115,7 +107,7 @@ class TlParser { double fetch_double_unsafe() { double result; - std::memcpy(reinterpret_cast<unsigned char *>(&result), data, sizeof(double)); + std::memcpy(&result, data, sizeof(double)); data += sizeof(double); return result; } @@ -128,7 +120,7 @@ class TlParser { template <class T> T fetch_binary_unsafe() { T result; - std::memcpy(reinterpret_cast<unsigned char *>(&result), data, sizeof(T)); + std::memcpy(&result, data, sizeof(T)); data += sizeof(T); return result; } @@ -136,7 +128,7 @@ class TlParser { template <class T> T fetch_binary() { static_assert(sizeof(T) <= sizeof(empty_data), "too big fetch_binary"); - static_assert(sizeof(T) % sizeof(int32) == 0, "wrong call to fetch_binary"); + //static_assert(sizeof(T) % sizeof(int32) == 0, "wrong call to fetch_binary"); check_len(sizeof(T)); return fetch_binary_unsafe<T>(); } @@ -145,29 +137,48 @@ class TlParser { T fetch_string() { check_len(sizeof(int32)); size_t result_len = *data; - const char *result_begin; + const unsigned char *result_begin; size_t result_aligned_len; if (result_len < 254) { - result_begin = reinterpret_cast<const char *>(data + 1); + result_begin = data + 1; result_aligned_len = (result_len >> 2) << 2; + data += sizeof(int32); } else if (result_len == 254) { result_len = data[1] + (data[2] << 8) + (data[3] << 16); - result_begin = reinterpret_cast<const char *>(data + 4); + result_begin = data + 4; result_aligned_len = ((result_len + 3) >> 2) << 2; + data += sizeof(int32); } else { - set_error("Can't fetch string, 255 found"); - return T(); + check_len(sizeof(int32)); + auto result_len_uint64 = static_cast<uint64>(data[1]) + (static_cast<uint64>(data[2]) << 8) + + (static_cast<uint64>(data[3]) << 16) + (static_cast<uint64>(data[4]) << 24) + + (static_cast<uint64>(data[5]) << 32) + (static_cast<uint64>(data[6]) << 40) + + (static_cast<uint64>(data[7]) << 48); + if (result_len_uint64 > std::numeric_limits<size_t>::max() - 3) { + set_error("Too big string found"); + return T(); + } + result_len = static_cast<size_t>(result_len_uint64); + result_begin = data + 8; + result_aligned_len = ((result_len + 3) >> 2) << 2; + data += sizeof(int64); } check_len(result_aligned_len); - data += result_aligned_len + sizeof(int32); - return T(result_begin, result_len); + if (!error.empty()) { + return T(); + } + data += result_aligned_len; + return T(reinterpret_cast<const char *>(result_begin), result_len); } template <class T> T fetch_string_raw(const size_t size) { - CHECK(size % sizeof(int32) == 0); + //CHECK(size % sizeof(int32) == 0); check_len(size); - const char *result = reinterpret_cast<const char *>(data); + if (!error.empty()) { + return T(); + } + auto result = reinterpret_cast<const char *>(data); data += size; return T(result, size); } @@ -187,6 +198,7 @@ class TlBufferParser : public TlParser { public: explicit TlBufferParser(const BufferSlice *buffer_slice) : TlParser(buffer_slice->as_slice()), parent_(buffer_slice) { } + template <class T> T fetch_string() { auto result = TlParser::fetch_string<T>(); @@ -213,6 +225,7 @@ class TlBufferParser : public TlParser { return T(); } + template <class T> T fetch_string_raw(const size_t size) { return TlParser::fetch_string_raw<T>(size); @@ -221,12 +234,7 @@ class TlBufferParser : public TlParser { private: const BufferSlice *parent_; - BufferSlice as_buffer_slice(Slice slice) { - if (is_aligned_pointer<4>(slice.data())) { - return parent_->from_slice(slice); - } - return BufferSlice(slice); - } + BufferSlice as_buffer_slice(Slice slice); }; template <> diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h index f389451d8a..6264226a01 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/tl_storers.h @@ -1,14 +1,13 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/int_types.h" +#include "td/utils/common.h" #include "td/utils/logging.h" -#include "td/utils/misc.h" #include "td/utils/Slice.h" #include "td/utils/StorerBase.h" @@ -17,11 +16,10 @@ namespace td { class TlStorerUnsafe { - char *buf; + unsigned char *buf_; public: - explicit TlStorerUnsafe(char *buf) : buf(buf) { - CHECK(is_aligned_pointer<4>(buf)); + explicit TlStorerUnsafe(unsigned char *buf) : buf_(buf) { } TlStorerUnsafe(const TlStorerUnsafe &other) = delete; @@ -29,13 +27,12 @@ class TlStorerUnsafe { template <class T> void store_binary(const T &x) { - std::memcpy(buf, reinterpret_cast<const unsigned char *>(&x), sizeof(T)); - buf += sizeof(T); + std::memcpy(buf_, &x, sizeof(T)); + buf_ += sizeof(T); } void store_int(int32 x) { - *reinterpret_cast<int32 *>(buf) = x; - buf += sizeof(int32); + store_binary<int32>(x); } void store_long(int64 x) { @@ -43,45 +40,55 @@ class TlStorerUnsafe { } void store_slice(Slice slice) { - std::memcpy(buf, slice.begin(), slice.size()); - buf += slice.size(); + std::memcpy(buf_, slice.begin(), slice.size()); + buf_ += slice.size(); } + void store_storer(const Storer &storer) { - size_t size = storer.store(reinterpret_cast<unsigned char *>(buf)); - buf += size; + size_t size = storer.store(buf_); + buf_ += size; } template <class T> void store_string(const T &str) { size_t len = str.size(); if (len < 254) { - *buf++ = static_cast<char>(len); + *buf_++ = static_cast<unsigned char>(len); len++; } else if (len < (1 << 24)) { - *buf++ = static_cast<char>(static_cast<unsigned char>(254)); - *buf++ = static_cast<char>(len & 255); - *buf++ = static_cast<char>((len >> 8) & 255); - *buf++ = static_cast<char>(len >> 16); + *buf_++ = static_cast<unsigned char>(254); + *buf_++ = static_cast<unsigned char>(len & 255); + *buf_++ = static_cast<unsigned char>((len >> 8) & 255); + *buf_++ = static_cast<unsigned char>(len >> 16); + } else if (static_cast<uint64>(len) < (static_cast<uint64>(1) << 32)) { + *buf_++ = static_cast<unsigned char>(255); + *buf_++ = static_cast<unsigned char>(len & 255); + *buf_++ = static_cast<unsigned char>((len >> 8) & 255); + *buf_++ = static_cast<unsigned char>((len >> 16) & 255); + *buf_++ = static_cast<unsigned char>((len >> 24) & 255); + *buf_++ = static_cast<unsigned char>(0); + *buf_++ = static_cast<unsigned char>(0); + *buf_++ = static_cast<unsigned char>(0); } else { LOG(FATAL) << "String size " << len << " is too big to be stored"; } - std::memcpy(buf, str.data(), str.size()); - buf += str.size(); + std::memcpy(buf_, str.data(), str.size()); + buf_ += str.size(); switch (len & 3) { case 1: - *buf++ = '\0'; - // fallthrough + *buf_++ = 0; + // fallthrough case 2: - *buf++ = '\0'; - // fallthrough + *buf_++ = 0; + // fallthrough case 3: - *buf++ = '\0'; + *buf_++ = 0; } } - char *get_buf() const { - return buf; + unsigned char *get_buf() const { + return buf_; } }; @@ -119,8 +126,10 @@ class TlStorerCalcLength { size_t add = str.size(); if (add < 254) { add += 1; - } else { + } else if (add < (1 << 24)) { add += 4; + } else { + add += 8; } add = (add + 3) & -4; length += add; @@ -131,138 +140,6 @@ class TlStorerCalcLength { } }; -class TlStorerToString { - std::string result; - int shift = 0; - - void store_field_begin(const char *name) { - for (int i = 0; i < shift; i++) { - result += ' '; - } - if (name && name[0]) { - result += name; - result += " = "; - } - } - - void store_field_end() { - result += "\n"; - } - - void store_long(int64 value) { - result += (PSLICE() << value).c_str(); - } - - void store_binary(Slice data) { - static const char *hex = "0123456789ABCDEF"; - - result.append("{ "); - for (auto c : data) { - unsigned char byte = c; - result += hex[byte >> 4]; - result += hex[byte & 15]; - result += ' '; - } - result.append("}"); - } - - public: - TlStorerToString() = default; - TlStorerToString(const TlStorerToString &other) = delete; - TlStorerToString &operator=(const TlStorerToString &other) = delete; - - void store_field(const char *name, bool value) { - store_field_begin(name); - result += (value ? "true" : "false"); - store_field_end(); - } - - void store_field(const char *name, int32 value) { - store_field(name, static_cast<int64>(value)); - } - - void store_field(const char *name, int64 value) { - store_field_begin(name); - store_long(value); - store_field_end(); - } - - void store_field(const char *name, double value) { - store_field_begin(name); - result += (PSLICE() << value).c_str(); - store_field_end(); - } - - void store_field(const char *name, const char *value) { - store_field_begin(name); - result += value; - store_field_end(); - } - - void store_field(const char *name, const string &value) { - store_field_begin(name); - result += '"'; - result.append(value.data(), value.size()); - result += '"'; - store_field_end(); - } - - template <class T> - void store_field(const char *name, const T &value) { - store_field_begin(name); - result.append(value.data(), value.size()); - store_field_end(); - } - - template <class BytesT> - void store_bytes_field(const char *name, const BytesT &value) { - static const char *hex = "0123456789ABCDEF"; - - store_field_begin(name); - result.append("bytes { "); - for (size_t i = 0; i < value.size(); i++) { - int b = value[static_cast<int>(i)] & 0xff; - result += hex[b >> 4]; - result += hex[b & 15]; - result += ' '; - } - result.append("}"); - store_field_end(); - } - - void store_field(const char *name, const UInt128 &value) { - store_field_begin(name); - store_binary(Slice(reinterpret_cast<const unsigned char *>(&value), sizeof(value))); - store_field_end(); - } - - void store_field(const char *name, const UInt256 &value) { - store_field_begin(name); - store_binary(Slice(reinterpret_cast<const unsigned char *>(&value), sizeof(value))); - store_field_end(); - } - - void store_class_begin(const char *field_name, const char *class_name) { - store_field_begin(field_name); - result += class_name; - result += " {\n"; - shift += 2; - } - - void store_class_end() { - shift -= 2; - for (int i = 0; i < shift; i++) { - result += ' '; - } - result += "}\n"; - CHECK(shift >= 0); - } - - std::string str() const { - return result; - } -}; - template <class T> size_t tl_calc_length(const T &data) { TlStorerCalcLength storer_calc_length; @@ -270,12 +147,14 @@ size_t tl_calc_length(const T &data) { return storer_calc_length.get_length(); } -template <class T, class CharT> -size_t tl_store_unsafe(const T &data, CharT *dst) { - char *start = reinterpret_cast<char *>(dst); - TlStorerUnsafe storer_unsafe(start); +template <class T> +size_t tl_store_unsafe(const T &data, unsigned char *dst) TD_WARN_UNUSED_RESULT; + +template <class T> +size_t tl_store_unsafe(const T &data, unsigned char *dst) { + TlStorerUnsafe storer_unsafe(dst); data.store(storer_unsafe); - return storer_unsafe.get_buf() - start; + return static_cast<size_t>(storer_unsafe.get_buf() - dst); } } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.cpp new file mode 100644 index 0000000000..65cfcf0074 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.cpp @@ -0,0 +1,115 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/translit.h" + +#include "td/utils/algorithm.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/misc.h" +#include "td/utils/utf8.h" + +#include <algorithm> +#include <utility> + +namespace td { + +static const FlatHashMap<uint32, string> &get_en_to_ru_simple_rules() { + static const FlatHashMap<uint32, string> rules{ + {'a', "а"}, {'b', "б"}, {'c', "к"}, {'d', "д"}, {'e', "е"}, {'f', "ф"}, {'g', "г"}, {'h', "х"}, {'i', "и"}, + {'j', "й"}, {'k', "к"}, {'l', "л"}, {'m', "м"}, {'n', "н"}, {'o', "о"}, {'p', "п"}, {'q', "к"}, {'r', "р"}, + {'s', "с"}, {'t', "т"}, {'u', "у"}, {'v', "в"}, {'w', "в"}, {'x', "кс"}, {'y', "и"}, {'z', "з"}}; + return rules; +} + +static const std::vector<std::pair<string, string>> &get_en_to_ru_complex_rules() { + static const std::vector<std::pair<string, string>> rules{ + {"ch", "ч"}, {"ei", "ей"}, {"ey", "ей"}, {"ia", "ия"}, {"iy", "ий"}, {"jo", "е"}, + {"ju", "ю"}, {"ja", "я"}, {"kh", "х"}, {"shch", "щ"}, {"sh", "ш"}, {"sch", "щ"}, + {"ts", "ц"}, {"yo", "е"}, {"yu", "ю"}, {"ya", "я"}, {"zh", "ж"}}; + return rules; +} + +static const FlatHashMap<uint32, string> &get_ru_to_en_simple_rules() { + static const FlatHashMap<uint32, string> rules{ + {0x430, "a"}, {0x431, "b"}, {0x432, "v"}, {0x433, "g"}, {0x434, "d"}, {0x435, "e"}, {0x451, "e"}, + {0x436, "zh"}, {0x437, "z"}, {0x438, "i"}, {0x439, "y"}, {0x43a, "k"}, {0x43b, "l"}, {0x43c, "m"}, + {0x43d, "n"}, {0x43e, "o"}, {0x43f, "p"}, {0x440, "r"}, {0x441, "s"}, {0x442, "t"}, {0x443, "u"}, + {0x444, "f"}, {0x445, "kh"}, {0x446, "ts"}, {0x447, "ch"}, {0x448, "sh"}, {0x449, "sch"}, {0x44a, ""}, + {0x44b, "y"}, {0x44c, ""}, {0x44d, "e"}, {0x44e, "yu"}, {0x44f, "ya"}}; + return rules; +} + +static const std::vector<std::pair<string, string>> &get_ru_to_en_complex_rules() { + static const std::vector<std::pair<string, string>> rules{ + {"ий", "y"}, {"ия", "ia"}, {"кс", "x"}, {"yo", "e"}, {"jo", "e"}}; + return rules; +} + +static void add_word_transliterations(vector<string> &result, Slice word, bool allow_partial, + const FlatHashMap<uint32, string> &simple_rules, + const vector<std::pair<string, string>> &complex_rules) { + string s; + auto pos = word.ubegin(); + auto end = word.uend(); + while (pos != end) { + uint32 code; + pos = next_utf8_unsafe(pos, &code); + auto it = simple_rules.find(code); + if (it != simple_rules.end()) { + s += it->second; + } else { + append_utf8_character(s, code); + } + } + if (!s.empty()) { + result.push_back(std::move(s)); + s.clear(); + } + + pos = word.ubegin(); + while (pos != end) { + auto suffix = Slice(pos, end); + bool found = false; + for (auto &rule : complex_rules) { + if (begins_with(suffix, rule.first)) { + found = true; + pos += rule.first.size(); + s.append(rule.second); + break; + } + if (allow_partial && begins_with(rule.first, suffix)) { + result.push_back(s + rule.second); + } + } + if (found) { + continue; + } + + uint32 code; + pos = next_utf8_unsafe(pos, &code); + auto it = simple_rules.find(code); + if (it != simple_rules.end()) { + s += it->second; + } else { + append_utf8_character(s, code); + } + } + if (!s.empty()) { + result.push_back(std::move(s)); + } +} + +vector<string> get_word_transliterations(Slice word, bool allow_partial) { + vector<string> result; + + add_word_transliterations(result, word, allow_partial, get_en_to_ru_simple_rules(), get_en_to_ru_complex_rules()); + add_word_transliterations(result, word, allow_partial, get_ru_to_en_simple_rules(), get_ru_to_en_complex_rules()); + + td::unique(result); + return result; +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.h new file mode 100644 index 0000000000..e72cb5c8e5 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/translit.h @@ -0,0 +1,16 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/common.h" +#include "td/utils/Slice.h" + +namespace td { + +vector<string> get_word_transliterations(Slice word, bool allow_partial); + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h index ef9c159420..71bbf77b79 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/type_traits.h @@ -1,22 +1,41 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once +#include "td/utils/int_types.h" + +#include <type_traits> + namespace td { template <class FunctionT> struct member_function_class; -template <class ReturnType, class Type> -struct member_function_class<ReturnType Type::*> { +template <class ReturnType, class Type, class... Args> +struct member_function_class<ReturnType (Type::*)(Args...)> { using type = Type; + static constexpr size_t argument_count() { + return sizeof...(Args); + } }; template <class FunctionT> using member_function_class_t = typename member_function_class<FunctionT>::type; +template <class FunctionT> +constexpr size_t member_function_argument_count() { + return member_function_class<FunctionT>::argument_count(); +} + +// no std::is_trivially_copyable in libstdc++ before 5.0 +#if __GLIBCXX__ +#define TD_IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T) +#else +#define TD_IS_TRIVIALLY_COPYABLE(T) ::std::is_trivially_copyable<T>::value +#endif + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/uint128.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/uint128.h new file mode 100644 index 0000000000..902900ef27 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/uint128.h @@ -0,0 +1,293 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include "td/utils/bits.h" +#include "td/utils/common.h" + +#include <limits> +#include <type_traits> + +namespace td { + +class uint128_emulated { + public: + using uint128 = uint128_emulated; + uint128_emulated(uint64 hi, uint64 lo) : hi_(hi), lo_(lo) { + } + template <class T, typename = std::enable_if_t<std::is_unsigned<T>::value>> + uint128_emulated(T lo) : uint128_emulated(0, lo) { + } + uint128_emulated() = default; + + uint64 hi() const { + return hi_; + } + uint64 lo() const { + return lo_; + } + uint64 rounded_hi() const { + return hi_ + (lo_ >> 63); + } + static uint128 from_signed(int64 x) { + if (x >= 0) { + return uint128(0, x); + } + return uint128(std::numeric_limits<uint64>::max(), static_cast<uint64>(x)); + } + static uint128 from_unsigned(uint64 x) { + return uint128(0, x); + } + + uint128 add(uint128 other) const { + uint128 res(other.hi() + hi(), other.lo() + lo()); + if (res.lo() < lo()) { + res.hi_++; + } + return res; + } + + uint128 shl(int cnt) const { + if (cnt == 0) { + return *this; + } + if (cnt < 64) { + return uint128((hi() << cnt) | (lo() >> (64 - cnt)), lo() << cnt); + } + if (cnt < 128) { + return uint128(lo() << (cnt - 64), 0); + } + return uint128(); + } + uint128 shr(int cnt) const { + if (cnt == 0) { + return *this; + } + if (cnt < 64) { + return uint128(hi() >> cnt, (lo() >> cnt) | (hi() << (64 - cnt))); + } + if (cnt < 128) { + return uint128(0, hi() >> (cnt - 64)); + } + return uint128(); + } + + uint128 mult(uint128 other) const { + uint64 a_lo = lo() & 0xffffffff; + uint64 a_hi = lo() >> 32; + uint64 b_lo = other.lo() & 0xffffffff; + uint64 b_hi = other.lo() >> 32; + uint128 res(lo() * other.hi() + hi() * other.lo() + a_hi * b_hi, a_lo * b_lo); + uint128 add1(0, a_lo * b_hi); + uint128 add2(0, a_hi * b_lo); + return res.add(add1.shl(32)).add(add2.shl(32)); + } + uint128 mult(uint64 other) const { + return mult(uint128(0, other)); + } + uint128 mult_signed(int64 other) const { + return mult(uint128::from_signed(other)); + } + bool is_zero() const { + return lo() == 0 && hi() == 0; + } + uint128 sub(uint128 other) const { + uint32 carry = 0; + if (other.lo() > lo()) { + carry = 1; + } + return uint128(hi() - other.hi() - carry, lo() - other.lo()); + } + void divmod(uint128 other, uint128 *div_res, uint128 *mod_res) const { + CHECK(!other.is_zero()); + + auto from = *this; + auto ctz = from.count_leading_zeroes(); + auto other_ctz = other.count_leading_zeroes(); + if (ctz > other_ctz) { + *div_res = uint128(); + *mod_res = from; + return; + } + auto shift = other_ctz - ctz; + auto res = uint128(); + for (int i = shift; i >= 0; i--) { + auto sub = other.shl(i); + res = res.shl(1); + if (from.greater_or_equal(sub)) { + from = from.sub(sub); + res = res.set_lower_bit(); + } + } + + *div_res = res; + *mod_res = from; + } + uint128 div(uint128 other) const { + uint128 a; + uint128 b; + divmod(other, &a, &b); + return a; + } + uint128 mod(uint128 other) const { + uint128 a; + uint128 b; + divmod(other, &a, &b); + return b; + } + + void divmod_signed(int64 y, int64 *quot, int64 *rem) const { + CHECK(y != 0); + auto x = *this; + int x_sgn = x.is_negative(); + int y_sgn = y < 0; + if (x_sgn) { + x = x.negate(); + } + uint128 uy = from_signed(y); + if (uy.is_negative()) { + uy = uy.negate(); + } + + uint128 t_quot; + uint128 t_mod; + x.divmod(uy, &t_quot, &t_mod); + *quot = t_quot.lo(); + *rem = t_mod.lo(); + if (x_sgn != y_sgn) { + *quot = -*quot; + } + if (x_sgn) { + *rem = -*rem; + } + } + + private: + uint64 hi_{0}; + uint64 lo_{0}; + + bool is_negative() const { + return (hi_ >> 63) == 1; + } + + int32 count_leading_zeroes() const { + if (hi() == 0) { + return 64 + count_leading_zeroes64(lo()); + } + return count_leading_zeroes64(hi()); + } + uint128 set_lower_bit() const { + return uint128(hi(), lo() | 1); + } + bool greater_or_equal(uint128 other) const { + return hi() > other.hi() || (hi() == other.hi() && lo() >= other.lo()); + } + uint128 negate() const { + uint128 res(~hi(), ~lo() + 1); + if (res.lo() == 0) { + return uint128(res.hi() + 1, 0); + } + return res; + } +}; + +#if TD_HAVE_INT128 +class uint128_intrinsic { + public: + using ValueT = unsigned __int128; + using uint128 = uint128_intrinsic; + explicit uint128_intrinsic(ValueT value) : value_(value) { + } + uint128_intrinsic(uint64 hi, uint64 lo) : value_((ValueT(hi) << 64) | lo) { + } + uint128_intrinsic() = default; + + static uint128 from_signed(int64 x) { + return uint128(static_cast<ValueT>(x)); + } + static uint128 from_unsigned(uint64 x) { + return uint128(static_cast<ValueT>(x)); + } + uint64 hi() const { + return uint64(value() >> 64); + } + uint64 lo() const { + return uint64(value() & std::numeric_limits<uint64>::max()); + } + uint64 rounded_hi() const { + return uint64((value() + (1ULL << 63)) >> 64); + } + uint128 add(uint128 other) const { + return uint128(value() + other.value()); + } + uint128 sub(uint128 other) const { + return uint128(value() - other.value()); + } + + uint128 shl(int cnt) const { + if (cnt >= 128) { + return uint128(); + } + return uint128(value() << cnt); + } + + uint128 shr(int cnt) const { + if (cnt >= 128) { + return uint128(); + } + return uint128(value() >> cnt); + } + + uint128 mult(uint128 other) const { + return uint128(value() * other.value()); + } + uint128 mult(uint64 other) const { + return uint128(value() * other); + } + uint128 mult_signed(int64 other) const { + return uint128(value() * other); + } + bool is_zero() const { + return value() == 0; + } + void divmod(uint128 other, uint128 *div_res, uint128 *mod_res) const { + CHECK(!other.is_zero()); + *div_res = uint128(value() / other.value()); + *mod_res = uint128(value() % other.value()); + } + uint128 div(uint128 other) const { + CHECK(!other.is_zero()); + return uint128(value() / other.value()); + } + uint128 mod(uint128 other) const { + CHECK(!other.is_zero()); + return uint128(value() % other.value()); + } + void divmod_signed(int64 y, int64 *quot, int64 *rem) const { + CHECK(y != 0); + *quot = static_cast<int64>(signed_value() / y); + *rem = static_cast<int64>(signed_value() % y); + } + + private: + unsigned __int128 value_{0}; + ValueT value() const { + return value_; + } + __int128 signed_value() const { + return static_cast<__int128>(value()); + } +}; +#endif + +#if TD_HAVE_INT128 +using uint128 = uint128_intrinsic; +#else +using uint128 = uint128_emulated; +#endif + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp index 11e76b7979..1b16809f0c 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,130 +8,209 @@ #include "td/utils/logging.h" -#include <algorithm> -#include <iterator> - namespace td { // list of [(range_begin << 5) + range_type] static const uint32 unicode_simple_category_ranges[] = { - 0, 1028, 1056, 1538, 1856, 2081, 2912, 3105, 3936, 5124, 5152, 5441, - 5472, 5699, 5760, 5793, 5824, 5923, 5953, 5984, 6019, 6112, 6145, 6880, - 6913, 7904, 7937, 22592, 22721, 23104, 23553, 23712, 23937, 23968, 24001, 24032, - 28161, 28320, 28353, 28416, 28481, 28608, 28641, 28672, 28865, 28896, 28929, 29024, - 29057, 29088, 29121, 29760, 29793, 32448, 32481, 36928, 37185, 42496, 42529, 43744, - 43809, 43840, 44065, 45312, 47617, 48480, 48641, 48736, 50177, 51552, 52226, 52544, - 52673, 52736, 52769, 55936, 55969, 56000, 56481, 56544, 56769, 56834, 57153, 57248, - 57313, 57344, 57857, 57888, 57921, 58880, 59809, 62656, 63009, 63040, 63490, 63809, - 64864, 65153, 65216, 65345, 65376, 65537, 66240, 66369, 66400, 66689, 66720, 66817, - 66848, 67585, 68384, 70657, 71328, 71361, 71616, 73857, 75584, 75681, 75712, 76289, - 76320, 76545, 76864, 76994, 77312, 77345, 77856, 77985, 78240, 78305, 78368, 78433, - 79136, 79169, 79392, 79425, 79456, 79553, 79680, 79777, 79808, 80321, 80352, 80769, - 80832, 80865, 80960, 81090, 81409, 81472, 81539, 81728, 82081, 82272, 82401, 82464, - 82529, 83232, 83265, 83488, 83521, 83584, 83617, 83680, 83713, 83776, 84769, 84896, - 84929, 84960, 85186, 85504, 85569, 85664, 86177, 86464, 86497, 86592, 86625, 87328, - 87361, 87584, 87617, 87680, 87713, 87872, 87969, 88000, 88577, 88608, 89089, 89152, - 89282, 89600, 89889, 89920, 90273, 90528, 90593, 90656, 90721, 91424, 91457, 91680, - 91713, 91776, 91809, 91968, 92065, 92096, 93057, 93120, 93153, 93248, 93378, 93696, - 93729, 93763, 93952, 94305, 94336, 94369, 94560, 94657, 94752, 94785, 94912, 95009, - 95072, 95105, 95136, 95169, 95232, 95329, 95392, 95489, 95584, 95681, 96064, 96769, - 96800, 97474, 97795, 97888, 98465, 98720, 98753, 98848, 98881, 99616, 99649, 100160, - 100257, 100288, 101121, 101216, 101377, 101440, 101570, 101888, 102147, 102368, 102401, 102432, - 102561, 102816, 102849, 102944, 102977, 103712, 103745, 104064, 104097, 104256, 104353, 104384, - 105409, 105440, 105473, 105536, 105666, 105984, 106017, 106080, 106657, 106912, 106945, 107040, - 107073, 108384, 108449, 108480, 108993, 109024, 109185, 109280, 109315, 109537, 109632, 109762, - 110083, 110368, 110401, 110592, 110753, 111328, 111425, 112192, 112225, 112512, 112545, 112576, - 112641, 112864, 113858, 114176, 114721, 116256, 116289, 116352, 116737, 116960, 117250, 117568, - 118817, 118880, 118913, 118944, 119009, 119072, 119105, 119136, 119201, 119232, 119425, 119552, - 119585, 119808, 119841, 119936, 119969, 120000, 120033, 120064, 120129, 120192, 120225, 120352, - 120385, 120448, 120737, 120768, 120833, 120992, 121025, 121056, 121346, 121664, 121729, 121856, - 122881, 122912, 123906, 124227, 124544, 124929, 125184, 125217, 126368, 127233, 127392, 131073, - 132448, 133089, 133122, 133440, 133633, 133824, 133953, 134080, 134177, 134208, 134305, 134368, - 134593, 134688, 134817, 135232, 135617, 135648, 135682, 136000, 136193, 137408, 137441, 137472, - 137633, 137664, 137729, 139104, 139137, 149792, 149825, 149952, 150017, 150240, 150273, 150304, - 150337, 150464, 150529, 151840, 151873, 152000, 152065, 153120, 153153, 153280, 153345, 153568, - 153601, 153632, 153665, 153792, 153857, 154336, 154369, 156192, 156225, 156352, 156417, 158560, - 159011, 159648, 159745, 160256, 160769, 163520, 163585, 163776, 163873, 183712, 183777, 184324, - 184353, 185184, 185345, 187744, 187843, 187937, 188192, 188417, 188832, 188865, 188992, 189441, - 190016, 190465, 191040, 191489, 191904, 191937, 192032, 192513, 194176, 195297, 195328, 195457, - 195488, 195586, 195904, 196099, 196416, 197122, 197440, 197633, 200448, 200705, 200864, 200929, - 202016, 202049, 202080, 202241, 204480, 204801, 205792, 207042, 207361, 208320, 208385, 208544, - 208897, 210304, 210433, 211264, 211458, 211779, 211808, 212993, 213728, 214017, 215712, 217090, - 217408, 217602, 217920, 218337, 218368, 221345, 222848, 223393, 223616, 223746, 224064, 225377, - 226336, 226753, 226818, 227137, 228544, 229377, 230528, 231426, 231744, 231841, 231938, 232257, - 233408, 233473, 233760, 236833, 236960, 236993, 237120, 237217, 237280, 237569, 243712, 245761, - 254656, 254721, 254912, 254977, 256192, 256257, 256448, 256513, 256768, 256801, 256832, 256865, - 256896, 256929, 256960, 256993, 257984, 258049, 259744, 259777, 260000, 260033, 260064, 260161, - 260256, 260289, 260512, 260609, 260736, 260801, 260992, 261121, 261536, 261697, 261792, 261825, - 262048, 262148, 262496, 263428, 263488, 263652, 263680, 265188, 265216, 265731, 265761, 265792, - 265859, 266048, 266209, 266243, 266560, 266753, 267168, 270401, 270432, 270561, 270592, 270657, - 270976, 271009, 271040, 271137, 271296, 271489, 271520, 271553, 271584, 271617, 271648, 271681, - 271808, 271841, 272192, 272257, 272384, 272545, 272704, 272833, 272864, 272899, 274529, 274595, - 274752, 297987, 299904, 302403, 303104, 323267, 324224, 360449, 361952, 361985, 363488, 363521, - 367776, 367969, 368096, 368193, 368256, 368547, 368576, 368641, 369856, 369889, 369920, 370081, - 370112, 370177, 371968, 372193, 372224, 372737, 373472, 373761, 373984, 374017, 374240, 374273, - 374496, 374529, 374752, 374785, 375008, 375041, 375264, 375297, 375520, 375553, 375776, 378337, - 378368, 393220, 393248, 393377, 393443, 393472, 394275, 394560, 394785, 394944, 395011, 395105, - 395168, 395297, 398048, 398241, 398336, 398369, 401248, 401281, 401408, 401569, 402880, 402977, - 405984, 406083, 406208, 406529, 407392, 409089, 409600, 410627, 410944, 411907, 412160, 412195, - 412672, 413699, 414016, 415267, 415744, 425985, 636608, 638977, 1309376, 1310721, 1348000, 1350145, - 1351616, 1351681, 1360288, 1360385, 1360898, 1361217, 1361280, 1361921, 1363424, 1363937, 1364928, 1364993, - 1367235, 1367552, 1368801, 1369088, 1369153, 1372448, 1372513, 1373664, 1373697, 1373952, 1375969, 1376320, - 1376353, 1376448, 1376481, 1376608, 1376641, 1377376, 1377795, 1377984, 1378305, 1379968, 1380417, 1382016, - 1382914, 1383232, 1384001, 1384192, 1384289, 1384320, 1384353, 1384384, 1384450, 1384769, 1385664, 1385985, - 1386720, 1387521, 1388448, 1388673, 1390176, 1391073, 1391106, 1391424, 1391617, 1391776, 1391809, 1392130, - 1392449, 1392608, 1392641, 1393952, 1394689, 1394784, 1394817, 1395072, 1395202, 1395520, 1395713, 1396448, - 1396545, 1396576, 1396673, 1398272, 1398305, 1398336, 1398433, 1398496, 1398561, 1398720, 1398785, 1398816, - 1398849, 1398880, 1399649, 1399744, 1399809, 1400160, 1400385, 1400480, 1400865, 1401056, 1401121, 1401312, - 1401377, 1401568, 1401857, 1402080, 1402113, 1402336, 1402369, 1403744, 1403777, 1404096, 1404417, 1408096, - 1408514, 1408832, 1409025, 1766528, 1766913, 1767648, 1767777, 1769344, 2039809, 2051520, 2051585, 2054976, - 2056193, 2056416, 2056801, 2056960, 2057121, 2057152, 2057185, 2057504, 2057537, 2057952, 2057985, 2058144, - 2058177, 2058208, 2058241, 2058304, 2058337, 2058400, 2058433, 2061888, 2062945, 2074560, 2075137, 2077184, - 2077249, 2078976, 2080257, 2080640, 2084353, 2084512, 2084545, 2088864, 2089474, 2089792, 2090017, 2090848, - 2091041, 2091872, 2092225, 2095072, 2095169, 2095360, 2095425, 2095616, 2095681, 2095872, 2095937, 2096032, - 2097153, 2097536, 2097569, 2098400, 2098433, 2099040, 2099073, 2099136, 2099169, 2099648, 2099713, 2100160, - 2101249, 2105184, 2105571, 2107008, 2107395, 2109216, 2109763, 2109824, 2117633, 2118560, 2118657, 2120224, - 2120739, 2121600, 2121729, 2122755, 2122880, 2123265, 2123811, 2123841, 2124099, 2124128, 2124289, 2125504, - 2125825, 2126784, 2126849, 2128000, 2128129, 2128384, 2128419, 2128576, 2129921, 2134976, 2135042, 2135360, - 2135553, 2136704, 2136833, 2137984, 2138113, 2139392, 2139649, 2141312, 2146305, 2156256, 2156545, 2157248, - 2157569, 2157824, 2162689, 2162880, 2162945, 2162976, 2163009, 2164416, 2164449, 2164512, 2164609, 2164640, - 2164705, 2165440, 2165507, 2165761, 2166496, 2166563, 2166785, 2167776, 2168035, 2168320, 2169857, 2170464, - 2170497, 2170560, 2170723, 2170881, 2171587, 2171776, 2171905, 2172736, 2174977, 2176768, 2176899, 2176961, - 2177027, 2177536, 2177603, 2179073, 2179104, 2179585, 2179712, 2179745, 2179840, 2179873, 2180736, 2181123, - 2181376, 2182145, 2183075, 2183136, 2183169, 2184099, 2184192, 2185217, 2185472, 2185505, 2186400, 2186595, - 2186752, 2187265, 2188992, 2189313, 2190016, 2190083, 2190337, 2190944, 2191107, 2191361, 2191936, 2192675, - 2192896, 2195457, 2197792, 2199553, 2201184, 2201601, 2203232, 2203459, 2203648, 2214915, 2215904, 2228321, - 2230016, 2230851, 2231490, 2231808, 2232417, 2233856, 2234881, 2235680, 2235906, 2236224, 2236513, 2237664, - 2238146, 2238464, 2238977, 2240096, 2240193, 2240224, 2240609, 2242144, 2242593, 2242720, 2243074, 2243393, - 2243424, 2243457, 2243488, 2243619, 2244256, 2244609, 2245184, 2245217, 2246016, 2248705, 2248928, 2248961, - 2248992, 2249025, 2249152, 2249185, 2249664, 2249697, 2250016, 2250241, 2251744, 2252290, 2252608, 2252961, - 2253216, 2253281, 2253344, 2253409, 2254112, 2254145, 2254368, 2254401, 2254464, 2254497, 2254656, 2254753, - 2254784, 2255361, 2255392, 2255777, 2255936, 2260993, 2262688, 2263265, 2263392, 2263554, 2263872, 2265089, - 2266624, 2267265, 2267328, 2267361, 2267392, 2267650, 2267968, 2273281, 2274784, 2276097, 2276224, 2277377, - 2278912, 2279553, 2279584, 2279938, 2280256, 2281473, 2282848, 2283522, 2283840, 2285569, 2286400, 2287106, - 2287427, 2287488, 2298881, 2300930, 2301251, 2301536, 2301921, 2301952, 2316289, 2318112, 2326529, 2326816, - 2326849, 2328032, 2328577, 2328608, 2329090, 2329411, 2330016, 2330177, 2331136, 2359297, 2388800, 2392067, - 2395616, 2396161, 2402432, 2490369, 2524640, 2654209, 2672864, 2949121, 2967328, 2967553, 2968544, 2968578, - 2968896, 2972161, 2973120, 2973697, 2975232, 2975745, 2975872, 2976258, 2976576, 2976611, 2976832, 2976865, - 2977536, 2977697, 2978304, 3006465, 3008672, 3009025, 3009056, 3011169, 3011584, 3013633, 3013664, 3014657, - 3210656, 3211265, 3235424, 3538945, 3539008, 3637249, 3640672, 3640833, 3641248, 3641345, 3641632, 3641857, - 3642176, 3828739, 3829312, 3833857, 3836576, 3836609, 3838880, 3838913, 3838976, 3839041, 3839072, 3839137, - 3839200, 3839265, 3839392, 3839425, 3839808, 3839841, 3839872, 3839905, 3840128, 3840161, 3842240, 3842273, - 3842400, 3842465, 3842720, 3842753, 3842976, 3843009, 3843904, 3843937, 3844064, 3844097, 3844256, 3844289, - 3844320, 3844417, 3844640, 3844673, 3855552, 3855617, 3856416, 3856449, 3857248, 3857281, 3858272, 3858305, - 3859104, 3859137, 3860128, 3860161, 3860960, 3860993, 3861984, 3862017, 3862816, 3862849, 3863840, 3863873, - 3864672, 3864705, 3864960, 3865026, 3866624, 3997697, 4004000, 4004067, 4004352, 4005889, 4008064, 4008450, - 4008768, 4046849, 4046976, 4047009, 4047872, 4047905, 4047968, 4048001, 4048032, 4048097, 4048128, 4048161, - 4048480, 4048513, 4048640, 4048673, 4048704, 4048737, 4048768, 4048961, 4048992, 4049121, 4049152, 4049185, - 4049216, 4049249, 4049280, 4049313, 4049408, 4049441, 4049504, 4049537, 4049568, 4049633, 4049664, 4049697, - 4049728, 4049761, 4049792, 4049825, 4049856, 4049889, 4049920, 4049953, 4050016, 4050049, 4050080, 4050145, - 4050272, 4050305, 4050528, 4050561, 4050688, 4050721, 4050848, 4050881, 4050912, 4050945, 4051264, 4051297, - 4051840, 4052001, 4052096, 4052129, 4052288, 4052321, 4052864, 4071427, 4071840, 4194305, 5561056, 5562369, - 5695136, 5695489, 5702592, 5702657, 5887040, 6225921, 6243264, 4294967295}; + 0, 1028, 1056, 1538, 1856, 2081, 2912, 3105, 3936, 5124, 5152, 5441, + 5472, 5699, 5760, 5793, 5824, 5923, 5953, 5984, 6019, 6112, 6145, 6880, + 6913, 7904, 7937, 22592, 22721, 23104, 23553, 23712, 23937, 23968, 24001, 24032, + 28161, 28320, 28353, 28416, 28481, 28608, 28641, 28672, 28865, 28896, 28929, 29024, + 29057, 29088, 29121, 29760, 29793, 32448, 32481, 36928, 37185, 42496, 42529, 43744, + 43809, 43840, 44033, 45344, 47617, 48480, 48609, 48736, 50177, 51552, 52226, 52544, + 52673, 52736, 52769, 55936, 55969, 56000, 56481, 56544, 56769, 56834, 57153, 57248, + 57313, 57344, 57857, 57888, 57921, 58880, 59809, 62656, 63009, 63040, 63490, 63809, + 64864, 65153, 65216, 65345, 65376, 65537, 66240, 66369, 66400, 66689, 66720, 66817, + 66848, 67585, 68384, 68609, 68960, 69121, 69888, 69921, 70112, 70657, 72000, 73857, + 75584, 75681, 75712, 76289, 76320, 76545, 76864, 76994, 77312, 77345, 77856, 77985, + 78240, 78305, 78368, 78433, 79136, 79169, 79392, 79425, 79456, 79553, 79680, 79777, + 79808, 80321, 80352, 80769, 80832, 80865, 80960, 81090, 81409, 81472, 81539, 81728, + 81793, 81824, 82081, 82272, 82401, 82464, 82529, 83232, 83265, 83488, 83521, 83584, + 83617, 83680, 83713, 83776, 84769, 84896, 84929, 84960, 85186, 85504, 85569, 85664, + 86177, 86464, 86497, 86592, 86625, 87328, 87361, 87584, 87617, 87680, 87713, 87872, + 87969, 88000, 88577, 88608, 89089, 89152, 89282, 89600, 89889, 89920, 90273, 90528, + 90593, 90656, 90721, 91424, 91457, 91680, 91713, 91776, 91809, 91968, 92065, 92096, + 93057, 93120, 93153, 93248, 93378, 93696, 93729, 93763, 93952, 94305, 94336, 94369, + 94560, 94657, 94752, 94785, 94912, 95009, 95072, 95105, 95136, 95169, 95232, 95329, + 95392, 95489, 95584, 95681, 96064, 96769, 96800, 97474, 97795, 97888, 98465, 98720, + 98753, 98848, 98881, 99616, 99649, 100160, 100257, 100288, 101121, 101216, 101281, 101312, + 101377, 101440, 101570, 101888, 102147, 102368, 102401, 102432, 102561, 102816, 102849, 102944, + 102977, 103712, 103745, 104064, 104097, 104256, 104353, 104384, 105377, 105440, 105473, 105536, + 105666, 105984, 106017, 106080, 106625, 106912, 106945, 107040, 107073, 108384, 108449, 108480, + 108993, 109024, 109185, 109280, 109315, 109537, 109632, 109762, 110083, 110368, 110401, 110592, + 110753, 111328, 111425, 112192, 112225, 112512, 112545, 112576, 112641, 112864, 113858, 114176, + 114721, 116256, 116289, 116352, 116737, 116960, 117250, 117568, 118817, 118880, 118913, 118944, + 118977, 119136, 119169, 119936, 119969, 120000, 120033, 120352, 120385, 120448, 120737, 120768, + 120833, 120992, 121025, 121056, 121346, 121664, 121729, 121856, 122881, 122912, 123906, 124227, + 124544, 124929, 125184, 125217, 126368, 127233, 127392, 131073, 132448, 133089, 133122, 133440, + 133633, 133824, 133953, 134080, 134177, 134208, 134305, 134368, 134593, 134688, 134817, 135232, + 135617, 135648, 135682, 136000, 136193, 137408, 137441, 137472, 137633, 137664, 137729, 139104, + 139137, 149792, 149825, 149952, 150017, 150240, 150273, 150304, 150337, 150464, 150529, 151840, + 151873, 152000, 152065, 153120, 153153, 153280, 153345, 153568, 153601, 153632, 153665, 153792, + 153857, 154336, 154369, 156192, 156225, 156352, 156417, 158560, 159011, 159648, 159745, 160256, + 160769, 163520, 163585, 163776, 163873, 183712, 183777, 184324, 184353, 185184, 185345, 187744, + 187843, 187937, 188192, 188417, 188992, 189409, 190016, 190465, 191040, 191489, 191904, 191937, + 192032, 192513, 194176, 195297, 195328, 195457, 195488, 195586, 195904, 196099, 196416, 197122, + 197440, 197633, 200480, 200705, 200864, 200929, 202016, 202049, 202080, 202241, 204480, 204801, + 205792, 207042, 207361, 208320, 208385, 208544, 208897, 210304, 210433, 211264, 211458, 211779, + 211808, 212993, 213728, 214017, 215712, 217090, 217408, 217602, 217920, 218337, 218368, 221345, + 222848, 223393, 223648, 223746, 224064, 225377, 226336, 226753, 226818, 227137, 228544, 229377, + 230528, 231426, 231744, 231841, 231938, 232257, 233408, 233473, 233760, 233985, 235360, 235425, + 235520, 236833, 236960, 236993, 237184, 237217, 237280, 237377, 237408, 237569, 243712, 245761, + 254656, 254721, 254912, 254977, 256192, 256257, 256448, 256513, 256768, 256801, 256832, 256865, + 256896, 256929, 256960, 256993, 257984, 258049, 259744, 259777, 260000, 260033, 260064, 260161, + 260256, 260289, 260512, 260609, 260736, 260801, 260992, 261121, 261536, 261697, 261792, 261825, + 262048, 262148, 262496, 263428, 263488, 263652, 263680, 265188, 265216, 265731, 265761, 265792, + 265859, 266048, 266209, 266243, 266560, 266753, 267168, 270401, 270432, 270561, 270592, 270657, + 270976, 271009, 271040, 271137, 271296, 271489, 271520, 271553, 271584, 271617, 271648, 271681, + 271808, 271841, 272192, 272257, 272384, 272545, 272704, 272833, 272864, 272899, 274529, 274595, + 274752, 297987, 299904, 302403, 303104, 323267, 324224, 360449, 367776, 367969, 368096, 368193, + 368256, 368547, 368576, 368641, 369856, 369889, 369920, 370081, 370112, 370177, 371968, 372193, + 372224, 372737, 373472, 373761, 373984, 374017, 374240, 374273, 374496, 374529, 374752, 374785, + 375008, 375041, 375264, 375297, 375520, 375553, 375776, 378337, 378368, 393220, 393248, 393377, + 393443, 393472, 394275, 394560, 394785, 394944, 395011, 395105, 395168, 395297, 398048, 398241, + 398336, 398369, 401248, 401281, 401408, 401569, 402944, 402977, 405984, 406083, 406208, 406529, + 407552, 409089, 409600, 410627, 410944, 411907, 412160, 412195, 412672, 413699, 414016, 415267, + 415744, 425985, 636928, 638977, 1348000, 1350145, 1351616, 1351681, 1360288, 1360385, 1360898, 1361217, + 1361280, 1361921, 1363424, 1363937, 1364928, 1364993, 1367235, 1367552, 1368801, 1369088, 1369153, 1372448, + 1372513, 1374560, 1374721, 1374784, 1374817, 1374848, 1374881, 1375040, 1375809, 1376320, 1376353, 1376448, + 1376481, 1376608, 1376641, 1377376, 1377795, 1377984, 1378305, 1379968, 1380417, 1382016, 1382914, 1383232, + 1384001, 1384192, 1384289, 1384320, 1384353, 1384416, 1384450, 1384769, 1385664, 1385985, 1386720, 1387521, + 1388448, 1388673, 1390176, 1391073, 1391106, 1391424, 1391617, 1391776, 1391809, 1392130, 1392449, 1392608, + 1392641, 1393952, 1394689, 1394784, 1394817, 1395072, 1395202, 1395520, 1395713, 1396448, 1396545, 1396576, + 1396673, 1398272, 1398305, 1398336, 1398433, 1398496, 1398561, 1398720, 1398785, 1398816, 1398849, 1398880, + 1399649, 1399744, 1399809, 1400160, 1400385, 1400480, 1400865, 1401056, 1401121, 1401312, 1401377, 1401568, + 1401857, 1402080, 1402113, 1402336, 1402369, 1403744, 1403777, 1404224, 1404417, 1408096, 1408514, 1408832, + 1409025, 1766528, 1766913, 1767648, 1767777, 1769344, 2039809, 2051520, 2051585, 2054976, 2056193, 2056416, + 2056801, 2056960, 2057121, 2057152, 2057185, 2057504, 2057537, 2057952, 2057985, 2058144, 2058177, 2058208, + 2058241, 2058304, 2058337, 2058400, 2058433, 2061888, 2062945, 2074560, 2075137, 2077184, 2077249, 2078976, + 2080257, 2080640, 2084353, 2084512, 2084545, 2088864, 2089474, 2089792, 2090017, 2090848, 2091041, 2091872, + 2092225, 2095072, 2095169, 2095360, 2095425, 2095616, 2095681, 2095872, 2095937, 2096032, 2097153, 2097536, + 2097569, 2098400, 2098433, 2099040, 2099073, 2099136, 2099169, 2099648, 2099713, 2100160, 2101249, 2105184, + 2105571, 2107008, 2107395, 2109216, 2109763, 2109824, 2117633, 2118560, 2118657, 2120224, 2120739, 2121600, + 2121729, 2122755, 2122880, 2123169, 2123811, 2123841, 2124099, 2124128, 2124289, 2125504, 2125825, 2126784, + 2126849, 2128000, 2128129, 2128384, 2128419, 2128576, 2129921, 2134976, 2135042, 2135360, 2135553, 2136704, + 2136833, 2137984, 2138113, 2139392, 2139649, 2141312, 2141697, 2142048, 2142081, 2142560, 2142593, 2142816, + 2142849, 2142912, 2142945, 2143296, 2143329, 2143808, 2143841, 2144064, 2144097, 2144160, 2146305, 2156256, + 2156545, 2157248, 2157569, 2157824, 2158593, 2158784, 2158817, 2160160, 2160193, 2160480, 2162689, 2162880, + 2162945, 2162976, 2163009, 2164416, 2164449, 2164512, 2164609, 2164640, 2164705, 2165440, 2165507, 2165761, + 2166496, 2166563, 2166785, 2167776, 2168035, 2168320, 2169857, 2170464, 2170497, 2170560, 2170723, 2170881, + 2171587, 2171776, 2171905, 2172736, 2174977, 2176768, 2176899, 2176961, 2177027, 2177536, 2177603, 2179073, + 2179104, 2179585, 2179712, 2179745, 2179840, 2179873, 2180800, 2181123, 2181408, 2182145, 2183075, 2183136, + 2183169, 2184099, 2184192, 2185217, 2185472, 2185505, 2186400, 2186595, 2186752, 2187265, 2188992, 2189313, + 2190016, 2190083, 2190337, 2190944, 2191107, 2191361, 2191936, 2192675, 2192896, 2195457, 2197792, 2199553, + 2201184, 2201601, 2203232, 2203459, 2203649, 2204800, 2205186, 2205504, 2214915, 2215904, 2215937, 2217280, + 2217473, 2217536, 2220033, 2220963, 2221281, 2221312, 2221569, 2222272, 2222627, 2222752, 2223617, 2224192, + 2225665, 2226339, 2226560, 2227201, 2227936, 2228321, 2230016, 2230851, 2231490, 2231808, 2231841, 2231904, + 2231969, 2232000, 2232417, 2233856, 2234881, 2235680, 2235906, 2236224, 2236513, 2237664, 2238146, 2238464, + 2238593, 2238624, 2238689, 2238720, 2238977, 2240096, 2240193, 2240224, 2240609, 2242144, 2242593, 2242720, + 2243074, 2243393, 2243424, 2243457, 2243488, 2243619, 2244256, 2244609, 2245184, 2245217, 2246016, 2246625, + 2246688, 2248705, 2248928, 2248961, 2248992, 2249025, 2249152, 2249185, 2249664, 2249697, 2250016, 2250241, + 2251744, 2252290, 2252608, 2252961, 2253216, 2253281, 2253344, 2253409, 2254112, 2254145, 2254368, 2254401, + 2254464, 2254497, 2254656, 2254753, 2254784, 2255361, 2255392, 2255777, 2255936, 2260993, 2262688, 2263265, + 2263392, 2263554, 2263872, 2264033, 2264128, 2265089, 2266624, 2267265, 2267328, 2267361, 2267392, 2267650, + 2267968, 2273281, 2274784, 2276097, 2276224, 2277377, 2278912, 2279553, 2279584, 2279938, 2280256, 2281473, + 2282848, 2283265, 2283296, 2283522, 2283840, 2285569, 2286432, 2287106, 2287427, 2287488, 2287617, 2287840, + 2293761, 2295168, 2298881, 2300930, 2301251, 2301536, 2301921, 2302176, 2302241, 2302272, 2302337, 2302592, + 2302625, 2302688, 2302721, 2303488, 2303969, 2304000, 2304033, 2304064, 2304514, 2304832, 2307073, 2307328, + 2307393, 2308640, 2309153, 2309184, 2309217, 2309248, 2310145, 2310176, 2310497, 2311776, 2312001, 2312032, + 2312705, 2312736, 2313089, 2314560, 2315169, 2315200, 2315777, 2318112, 2326529, 2326816, 2326849, 2328032, + 2328577, 2328608, 2329090, 2329411, 2330016, 2330177, 2331136, 2334721, 2334944, 2334977, 2335040, 2335073, + 2336288, 2336961, 2336992, 2337282, 2337600, 2337793, 2337984, 2338017, 2338080, 2338113, 2339136, 2339585, + 2339616, 2339842, 2340160, 2350081, 2350688, 2351169, 2351200, 2351233, 2351648, 2351681, 2352768, 2353666, + 2353984, 2356737, 2356768, 2357251, 2357920, 2359297, 2388800, 2392067, 2395616, 2396161, 2402432, 2486785, + 2489888, 2490369, 2524672, 2525217, 2525408, 2654209, 2672864, 2949121, 2967328, 2967553, 2968544, 2968578, + 2968896, 2969089, 2971616, 2971650, 2971968, 2972161, 2973120, 2973697, 2975232, 2975745, 2975872, 2976258, + 2976576, 2976611, 2976832, 2976865, 2977536, 2977697, 2978304, 3000321, 3002371, 3003104, 3006465, 3008864, + 3009025, 3009056, 3011169, 3011584, 3013633, 3013696, 3013729, 3013760, 3014657, 3211008, 3211265, 3250880, + 3252225, 3252512, 3538433, 3538560, 3538593, 3538816, 3538849, 3538912, 3538945, 3548256, 3548737, 3548768, + 3549697, 3549792, 3549857, 3549888, 3550337, 3550464, 3550721, 3563392, 3637249, 3640672, 3640833, 3641248, + 3641345, 3641632, 3641857, 3642176, 3823619, 3824256, 3824643, 3825280, 3828739, 3829536, 3833857, 3836576, + 3836609, 3838880, 3838913, 3838976, 3839041, 3839072, 3839137, 3839200, 3839265, 3839392, 3839425, 3839808, + 3839841, 3839872, 3839905, 3840128, 3840161, 3842240, 3842273, 3842400, 3842465, 3842720, 3842753, 3842976, + 3843009, 3843904, 3843937, 3844064, 3844097, 3844256, 3844289, 3844320, 3844417, 3844640, 3844673, 3855552, + 3855617, 3856416, 3856449, 3857248, 3857281, 3858272, 3858305, 3859104, 3859137, 3860128, 3860161, 3860960, + 3860993, 3861984, 3862017, 3862816, 3862849, 3863840, 3863873, 3864672, 3864705, 3864960, 3865026, 3866624, + 3923969, 3924960, 3925153, 3925344, 3933697, 3935680, 3940353, 3941792, 3942113, 3942336, 3942402, 3942720, + 3942849, 3942880, 3953153, 3954112, 3954689, 3956096, 3956226, 3956544, 3971585, 3972480, 3972610, 3972928, + 3996673, 3996896, 3996929, 3997056, 3997089, 3997152, 3997185, 3997664, 3997697, 4004000, 4004067, 4004352, + 4005889, 4008064, 4008289, 4008320, 4008450, 4008768, 4034083, 4035968, 4036003, 4036096, 4036131, 4036256, + 4038691, 4040128, 4040163, 4040640, 4046849, 4046976, 4047009, 4047872, 4047905, 4047968, 4048001, 4048032, + 4048097, 4048128, 4048161, 4048480, 4048513, 4048640, 4048673, 4048704, 4048737, 4048768, 4048961, 4048992, + 4049121, 4049152, 4049185, 4049216, 4049249, 4049280, 4049313, 4049408, 4049441, 4049504, 4049537, 4049568, + 4049633, 4049664, 4049697, 4049728, 4049761, 4049792, 4049825, 4049856, 4049889, 4049920, 4049953, 4050016, + 4050049, 4050080, 4050145, 4050272, 4050305, 4050528, 4050561, 4050688, 4050721, 4050848, 4050881, 4050912, + 4050945, 4051264, 4051297, 4051840, 4052001, 4052096, 4052129, 4052288, 4052321, 4052864, 4071427, 4071840, + 4161026, 4161344, 4194305, 5561344, 5562369, 5695296, 5695489, 5702592, 5702657, 5887040, 5887489, 6126624, + 6225921, 6243264, 6291457, 6449504, 6449665, 6583808, 4294967295}; + +static const uint16 unicode_simple_category_jump_pos[] = { + 1, 9, 27, 27, 27, 27, 36, 44, 55, 55, 57, 63, 68, 75, 86, 91, 102, 114, 119, + 130, 158, 180, 202, 225, 250, 271, 292, 312, 324, 332, 357, 365, 368, 383, 397, 397, 397, 407, + 423, 431, 436, 437, 437, 437, 437, 440, 448, 458, 467, 472, 480, 487, 494, 498, 503, 509, 516, + 524, 538, 538, 540, 540, 540, 558, 578, 592, 595, 622, 625, 625, 625, 625, 625, 626, 629, 629, + 629, 629, 629, 630, 631, 631, 631, 631, 631, 631, 631, 631, 632, 632, 640, 650, 667, 669, 669, + 669, 670, 682, 689, 692, 699, 706, 709, 709, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, + 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, + 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, 710, + 710, 710, 710, 710, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, 712, + 712, 712, 712, 712, 712, 712, 712, 716, 716, 716, 724, 728, 731, 741, 752, 763, 769, 781, 793, + 810, 825, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, + 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, + 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, + 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, + 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 829, 834, 834, 834, 834, 834, + 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, + 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, + 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, 834, + 834, 834, 834, 834, 835, 835, 835, 837, 839, 857, 859, 859, 859, 861, 866, 869, 870, 877, 887, + 899, 900, 904, 906, 907, 913, 923, 931, 931, 939, 945, 959, 959, 959, 965, 971, 987, 996, 1001, + 1008, 1021, 1030, 1038, 1042, 1044, 1049, 1052, 1052, 1055, 1059, 1067, 1073, 1082, 1088, 1100, 1112, 1118, 1131, + 1149, 1150, 1158, 1165, 1166, 1170, 1176, 1182, 1188, 1189, 1190, 1195, 1210, 1219, 1227, 1232, 1232, 1233, 1242, + 1244, 1258, 1263, 1263, 1265, 1273, 1278, 1278, 1278, 1278, 1278, 1278, 1278, 1278, 1280, 1282, 1282, 1283, 1283, + 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, 1283, + 1286, 1286, 1286, 1286, 1286, 1286, 1286, 1286, 1286, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, + 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, 1289, + 1289, 1289, 1290, 1290, 1290, 1290, 1290, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, + 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, + 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, + 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1291, 1292, 1292, + 1292, 1292, 1292, 1298, 1304, 1314, 1315, 1315, 1315, 1315, 1315, 1317, 1319, 1322, 1329, 1329, 1329, 1329, 1329, + 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, + 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, 1329, + 1329, 1329, 1329, 1329, 1329, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1331, 1333, 1334, 1334, 1334, + 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, + 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, + 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, + 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1334, 1341, 1341, 1341, 1351, 1351, 1351, 1352, 1352, 1352, 1352, + 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1352, 1353, 1357, 1360, 1360, 1360, + 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, + 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, 1360, + 1360, 1360, 1360, 1364, 1366, 1367, 1369, 1385, 1403, 1403, 1403, 1411, 1419, 1428, 1428, 1428, 1428, 1428, 1428, + 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1428, 1429, 1432, 1432, 1434, 1435, 1442, 1442, 1442, 1448, 1448, 1448, + 1448, 1452, 1452, 1452, 1452, 1452, 1452, 1461, 1461, 1465, 1470, 1470, 1470, 1470, 1470, 1470, 1471, 1476, 1480, + 1481, 1537, 1546, 1546, 1546, 1546, 1547, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, + 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1548, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1550, 1551, 1563, + 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566, 1566}; + +static const char *unicode_simple_category_table = + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x02\x02\x02\x02\x02\x02\x02" + "\x02\x02\x00\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" + "\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01" + "\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00"; static constexpr uint32 TABLE_SIZE = 1280; -static int16 prepare_search_character_table[TABLE_SIZE] = { +static const int16 prepare_search_character_table[TABLE_SIZE] = { 0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 48, 49, 50, 51, 52, 53, 54, 55, 56, @@ -202,219 +281,252 @@ static int16 prepare_search_character_table[TABLE_SIZE] = { 1273, 1275, 1275, 1277, 1277, 1279, 1279}; static const int32 prepare_search_character_ranges[] = { - 1280, 2097153, 1328, 1328, 1329, -1378, 1367, -1368, 1370, 32, 1376, -1377, - 1417, 32, 1419, -1420, 1421, 32, 1424, 1424, 1425, 0, 1470, 32, - 1471, 0, 1472, 32, 1473, 0, 1475, 32, 1476, 0, 1478, 32, - 1479, 0, 1480, -1481, 1523, 32, 1525, -1526, 1536, 0, 1542, 32, - 1552, 0, 1563, 32, 1564, 0, 1565, 1565, 1566, 32, 1568, -1569, - 1611, 0, 1632, -1633, 1642, 32, 1646, -1647, 1648, 0, 1649, -1650, - 1748, 32, 1749, 1749, 1750, 0, 1758, 32, 1759, 0, 1765, -1766, - 1767, 0, 1769, 32, 1770, 0, 1774, -1775, 1789, 32, 1791, 1791, - 1792, 32, 1806, 1806, 1807, 0, 1808, 1808, 1809, 0, 1810, -1811, - 1840, 0, 1867, -1868, 1958, 0, 1969, -1970, 2027, 0, 2036, -2037, - 2038, 32, 2042, -2043, 2070, 0, 2074, 2074, 2075, 0, 2084, 2084, - 2085, 0, 2088, 2088, 2089, 0, 2094, -2095, 2096, 32, 2111, -2112, - 2137, 0, 2140, -2141, 2142, 32, 2143, -2144, 2260, 0, 2308, -2309, - 2362, 0, 2365, 2365, 2366, 0, 2384, 2384, 2385, 0, 2392, -2393, - 2402, 0, 2404, 32, 2406, -2407, 2416, 32, 2417, -2418, 2433, 0, - 2436, -2437, 2492, 0, 2493, 2493, 2494, 0, 2501, -2502, 2503, 0, - 2505, -2506, 2507, 0, 2510, -2511, 2519, 0, 2520, -2521, 2530, 0, - 2532, -2533, 2546, 32, 2548, -2549, 2554, 32, 2556, -2557, 2561, 0, - 2564, -2565, 2620, 0, 2621, 2621, 2622, 0, 2627, -2628, 2631, 0, - 2633, -2634, 2635, 0, 2638, -2639, 2641, 0, 2642, -2643, 2672, 0, - 2674, -2675, 2677, 0, 2678, -2679, 2689, 0, 2692, -2693, 2748, 0, - 2749, 2749, 2750, 0, 2758, 2758, 2759, 0, 2762, 2762, 2763, 0, - 2766, -2767, 2786, 0, 2788, -2789, 2800, 32, 2802, -2803, 2817, 0, - 2820, -2821, 2876, 0, 2877, 2877, 2878, 0, 2885, -2886, 2887, 0, - 2889, -2890, 2891, 0, 2894, -2895, 2902, 0, 2904, -2905, 2914, 0, - 2916, -2917, 2928, 32, 2929, -2930, 2946, 0, 2947, -2948, 3006, 0, - 3011, -3012, 3014, 0, 3017, 3017, 3018, 0, 3022, -3023, 3031, 0, - 3032, -3033, 3059, 32, 3067, -3068, 3072, 0, 3076, -3077, 3134, 0, - 3141, 3141, 3142, 0, 3145, 3145, 3146, 0, 3150, -3151, 3157, 0, - 3159, -3160, 3170, 0, 3172, -3173, 3199, 32, 3200, 3200, 3201, 0, - 3204, -3205, 3260, 0, 3261, 3261, 3262, 0, 3269, 3269, 3270, 0, - 3273, 3273, 3274, 0, 3278, -3279, 3285, 0, 3287, -3288, 3298, 0, - 3300, -3301, 3329, 0, 3332, -3333, 3390, 0, 3397, 3397, 3398, 0, - 3401, 3401, 3402, 0, 3406, 3406, 3407, 32, 3408, -3409, 3415, 0, - 3416, -3417, 3426, 0, 3428, -3429, 3449, 32, 3450, -3451, 3458, 0, - 3460, -3461, 3530, 0, 3531, -3532, 3535, 0, 3541, 3541, 3542, 0, - 3543, 3543, 3544, 0, 3552, -3553, 3570, 0, 3572, 32, 3573, -3574, - 3633, 0, 3634, -3635, 3636, 0, 3643, -3644, 3647, 32, 3648, -3649, - 3655, 0, 3663, 32, 3664, -3665, 3674, 32, 3676, -3677, 3761, 0, - 3762, -3763, 3764, 0, 3770, 3770, 3771, 0, 3773, -3774, 3784, 0, - 3790, -3791, 3841, 32, 3864, 0, 3866, 32, 3872, -3873, 3892, 32, - 3893, 0, 3894, 32, 3895, 0, 3896, 32, 3897, 0, 3898, 32, - 3902, 0, 3904, -3905, 3953, 0, 3973, 32, 3974, 0, 3976, -3977, - 3981, 0, 3992, 3992, 3993, 0, 4029, 4029, 4030, 32, 4038, 0, - 4039, 32, 4045, 4045, 4046, 32, 4059, -4060, 4139, 0, 4159, -4160, - 4170, 32, 4176, -4177, 4182, 0, 4186, -4187, 4190, 0, 4193, 4193, - 4194, 0, 4197, -4198, 4199, 0, 4206, -4207, 4209, 0, 4213, -4214, - 4226, 0, 4238, 4238, 4239, 0, 4240, -4241, 4250, 0, 4254, 32, - 4256, -11521, 4294, 4294, 4295, 11559, 4296, -4297, 4301, 11565, 4302, -4303, - 4347, 32, 4348, -4349, 4957, 0, 4960, 32, 4969, -4970, 5008, 32, - 5018, -5019, 5112, -5105, 5118, -5119, 5120, 32, 5121, -5122, 5741, 32, - 5743, -5744, 5760, 32, 5761, -5762, 5787, 32, 5789, -5790, 5867, 32, - 5870, -5871, 5906, 0, 5909, -5910, 5938, 0, 5941, 32, 5943, -5944, - 5970, 0, 5972, -5973, 6002, 0, 6004, -6005, 6068, 0, 6100, 32, - 6103, 6103, 6104, 32, 6108, 6108, 6109, 0, 6110, -6111, 6144, 32, - 6155, 0, 6159, -6160, 6277, 0, 6279, -6280, 6313, 0, 6314, -6315, - 6432, 0, 6444, -6445, 6448, 0, 6460, -6461, 6464, 32, 6465, -6466, - 6468, 32, 6470, -6471, 6622, 32, 6656, -6657, 6679, 0, 6684, -6685, - 6686, 32, 6688, -6689, 6741, 0, 6751, 6751, 6752, 0, 6781, -6782, - 6783, 0, 6784, -6785, 6816, 32, 6823, 6823, 6824, 32, 6830, -6831, - 6832, 0, 6847, -6848, 6912, 0, 6917, -6918, 6964, 0, 6981, -6982, - 7002, 32, 7019, 0, 7028, 32, 7037, -7038, 7040, 0, 7043, -7044, - 7073, 0, 7086, -7087, 7142, 0, 7156, -7157, 7164, 32, 7168, -7169, - 7204, 0, 7224, -7225, 7227, 32, 7232, -7233, 7294, 32, 7296, 1074, - 7297, 1076, 7298, 1086, 7299, -1090, 7301, 1090, 7302, 1098, 7303, 1123, - 7304, 42571, 7305, -7306, 7360, 32, 7368, -7369, 7376, 0, 7379, 32, - 7380, 0, 7401, -7402, 7405, 0, 7406, -7407, 7410, 0, 7413, -7414, - 7416, 0, 7418, -7419, 7468, 97, 7469, 230, 7470, 98, 7471, 7471, - 7472, -101, 7474, 477, 7475, -104, 7483, 7483, 7484, 111, 7485, 547, - 7486, 112, 7487, 114, 7488, -117, 7490, 119, 7491, -7492, 7616, 0, - 7670, -7671, 7675, 0, 7680, 2097153, 7830, -7831, 7835, 7777, 7836, -7837, - 7838, 223, 7839, 2097153, 7936, -7937, 7944, -7937, 7952, -7953, 7960, -7953, - 7966, -7967, 7976, -7969, 7984, -7985, 7992, -7985, 8000, -8001, 8008, -8001, - 8014, -8015, 8025, 8017, 8026, 8026, 8027, 8019, 8028, 8028, 8029, 8021, - 8030, 8030, 8031, 8023, 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065, - 8080, -8081, 8088, -8081, 8096, -8097, 8104, -8097, 8112, -8113, 8120, -8113, - 8122, -8049, 8124, 8115, 8125, 32, 8126, 953, 8127, 32, 8130, -8131, - 8136, -8051, 8140, 8131, 8141, 32, 8144, -8145, 8152, -8145, 8154, -8055, - 8156, 8156, 8157, 32, 8160, -8161, 8168, -8161, 8170, -8059, 8172, 8165, - 8173, 32, 8176, -8177, 8184, -8057, 8186, -8061, 8188, 8179, 8189, 32, - 8191, 8191, 8192, 32, 8203, 0, 8208, 32, 8234, 0, 8239, 32, - 8288, 0, 8293, 8293, 8294, 0, 8304, -8305, 8314, 32, 8319, -8320, - 8330, 32, 8335, -8336, 8352, 32, 8383, -8384, 8400, 0, 8433, -8434, - 8448, 32, 8450, 99, 8452, 32, 8455, 603, 8456, 32, 8457, 102, - 8458, 8458, 8459, 104, 8462, -8463, 8464, 105, 8466, 108, 8467, 8467, - 8468, 32, 8469, 110, 8470, 32, 8473, -113, 8476, 114, 8478, 32, - 8484, 122, 8485, 32, 8486, 969, 8487, 32, 8488, 122, 8489, 32, - 8490, 107, 8491, 229, 8492, -99, 8494, 32, 8495, 8495, 8496, -102, - 8498, 8526, 8499, 109, 8500, -8501, 8506, 32, 8508, -8509, 8510, 947, - 8511, 960, 8512, 32, 8517, 100, 8518, -8519, 8522, 32, 8526, 8526, - 8527, 32, 8528, -8529, 8544, -8561, 8560, -8561, 8579, 8580, 8581, -8582, - 8586, 32, 8588, -8589, 8592, 32, 9215, 9215, 9216, 32, 9255, -9256, - 9280, 32, 9291, -9292, 9372, 32, 9398, -9425, 9424, -9425, 9472, 32, - 10102, -10103, 10132, 32, 11124, -11125, 11126, 32, 11158, -11159, 11160, 32, - 11194, -11195, 11197, 32, 11209, 11209, 11210, 32, 11218, -11219, 11244, 32, - 11248, -11249, 11264, -11313, 11311, -11312, 11360, 11361, 11362, 619, 11363, 7549, - 11364, 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371, 11372, 11373, 593, - 11374, 625, 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380, 11381, 11382, - 11383, -11384, 11389, 118, 11390, -576, 11392, 2097153, 11492, 11492, 11493, 32, - 11499, 11500, 11501, 11502, 11503, 0, 11506, 11507, 11508, -11509, 11513, 32, - 11517, 11517, 11518, 32, 11520, -11521, 11632, 32, 11633, -11634, 11647, 0, - 11648, -11649, 11744, 0, 11776, 32, 11823, 11823, 11824, 32, 11845, -11846, - 11904, 32, 11930, 11930, 11931, 32, 11935, 11935, 11936, 32, 12019, -12020, - 12272, 32, 12284, -12285, 12288, 32, 12293, -12294, 12296, 32, 12321, -12322, - 12330, 0, 12336, 32, 12337, -12338, 12342, 32, 12344, -12345, 12349, 32, - 12352, -12353, 12441, 0, 12443, 32, 12445, -12446, 12448, 32, 12449, -12450, - 12539, 32, 12540, 0, 12541, -12542, 12688, 32, 12690, -12691, 12736, 32, - 12772, -12773, 12800, 32, 12831, -12832, 12842, 32, 12868, -12869, 12880, 32, - 12881, -12882, 12910, 32, 12928, -12929, 12992, 32, 13008, -13009, 13056, 32, - 13312, -13313, 19904, 32, 19968, -19969, 42128, 32, 42183, -42184, 42238, 32, - 42240, -42241, 42509, 32, 42512, -42513, 42560, 2097153, 42606, 42606, 42607, 0, - 42611, 32, 42612, 0, 42622, 32, 42623, 2097153, 42652, -42653, 42654, 0, - 42656, -42657, 42736, 0, 42738, 32, 42744, -42745, 42752, 32, 42775, -42776, - 42784, 32, 42786, 2097153, 42800, -42801, 42802, 2097153, 42864, -42865, 42873, 42874, - 42875, 42876, 42877, 7545, 42878, 2097153, 42888, 42888, 42889, 32, 42891, 42892, - 42893, 613, 42894, -42895, 42896, 2097153, 42900, -42901, 42902, 2097153, 42922, 614, - 42923, 604, 42924, 609, 42925, 620, 42926, 618, 42927, 42927, 42928, 670, - 42929, 647, 42930, 669, 42931, 43859, 42932, 2097153, 42936, -42937, 43000, 295, - 43001, -43002, 43010, 0, 43011, -43012, 43014, 0, 43015, -43016, 43019, 0, - 43020, -43021, 43043, 0, 43048, 32, 43052, -43053, 43062, 32, 43066, -43067, - 43124, 32, 43128, -43129, 43136, 0, 43138, -43139, 43188, 0, 43206, -43207, - 43214, 32, 43216, -43217, 43232, 0, 43250, -43251, 43256, 32, 43259, 43259, - 43260, 32, 43261, -43262, 43302, 0, 43310, 32, 43312, -43313, 43335, 0, - 43348, -43349, 43359, 32, 43360, -43361, 43392, 0, 43396, -43397, 43443, 0, - 43457, 32, 43470, -43471, 43486, 32, 43488, -43489, 43493, 0, 43494, -43495, - 43561, 0, 43575, -43576, 43587, 0, 43588, -43589, 43596, 0, 43598, -43599, - 43612, 32, 43616, -43617, 43639, 32, 43642, 43642, 43643, 0, 43646, -43647, - 43696, 0, 43697, 43697, 43698, 0, 43701, -43702, 43703, 0, 43705, -43706, - 43710, 0, 43712, 43712, 43713, 0, 43714, -43715, 43742, 32, 43744, -43745, - 43755, 0, 43760, 32, 43762, -43763, 43765, 0, 43767, -43768, 43867, 32, - 43868, -43869, 43888, -5025, 43968, -43969, 44003, 0, 44011, 32, 44012, 0, - 44014, -44015, 55296, 0, 57344, -57345, 64286, 0, 64287, -64288, 64297, 32, - 64298, -64299, 64434, 32, 64450, -64451, 64830, 32, 64832, -64833, 64976, 32, - 65008, -65009, 65020, 32, 65022, -65023, 65024, 0, 65040, 32, 65050, -65051, - 65056, 0, 65072, 32, 65107, 65107, 65108, 32, 65127, 65127, 65128, 32, - 65132, -65133, 65279, 0, 65280, 65280, 65281, 32, 65296, -65297, 65306, 32, - 65313, -65346, 65339, 32, 65345, -65346, 65371, 32, 65382, -65383, 65504, 32, - 65511, 65511, 65512, 32, 65519, -65520, 65529, 0, 65532, 32, 65536, -65537, - 65792, 32, 65795, -65796, 65847, 32, 65856, -65857, 65913, 32, 65930, -65931, - 65932, 32, 65935, 65935, 65936, 32, 65948, -65949, 65952, 32, 65953, -65954, - 66000, 32, 66045, 0, 66046, -66047, 66272, 0, 66273, -66274, 66422, 0, - 66427, -66428, 66463, 32, 66464, -66465, 66512, 32, 66513, -66514, 66560, -66601, - 66600, -66601, 66736, -66777, 66772, -66773, 66927, 32, 66928, -66929, 67671, 32, - 67672, -67673, 67703, 32, 67705, -67706, 67871, 32, 67872, -67873, 67903, 32, - 67904, -67905, 68097, 0, 68100, 68100, 68101, 0, 68103, -68104, 68108, 0, - 68112, -68113, 68152, 0, 68155, -68156, 68159, 0, 68160, -68161, 68176, 32, - 68185, -68186, 68223, 32, 68224, -68225, 68296, 32, 68297, -68298, 68325, 0, - 68327, -68328, 68336, 32, 68343, -68344, 68409, 32, 68416, -68417, 68505, 32, - 68509, -68510, 68736, -68801, 68787, -68788, 69632, 0, 69635, -69636, 69688, 0, - 69703, 32, 69710, -69711, 69759, 0, 69763, -69764, 69808, 0, 69819, 32, - 69821, 0, 69822, 32, 69826, -69827, 69888, 0, 69891, -69892, 69927, 0, - 69941, -69942, 69952, 32, 69956, -69957, 70003, 0, 70004, 32, 70006, -70007, - 70016, 0, 70019, -70020, 70067, 0, 70081, -70082, 70085, 32, 70090, 0, - 70093, 32, 70094, -70095, 70107, 32, 70108, 70108, 70109, 32, 70112, -70113, - 70188, 0, 70200, 32, 70206, 0, 70207, -70208, 70313, 32, 70314, -70315, - 70367, 0, 70379, -70380, 70400, 0, 70404, -70405, 70460, 0, 70461, 70461, - 70462, 0, 70469, -70470, 70471, 0, 70473, -70474, 70475, 0, 70478, -70479, - 70487, 0, 70488, -70489, 70498, 0, 70500, -70501, 70502, 0, 70509, -70510, - 70512, 0, 70517, -70518, 70709, 0, 70727, -70728, 70731, 32, 70736, -70737, - 70747, 32, 70748, 70748, 70749, 32, 70750, -70751, 70832, 0, 70852, -70853, - 70854, 32, 70855, -70856, 71087, 0, 71094, -71095, 71096, 0, 71105, 32, - 71128, -71129, 71132, 0, 71134, -71135, 71216, 0, 71233, 32, 71236, -71237, - 71264, 32, 71277, -71278, 71339, 0, 71352, -71353, 71453, 0, 71468, -71469, - 71484, 32, 71488, -71489, 71840, -71873, 71872, -71873, 72751, 0, 72759, 72759, - 72760, 0, 72768, 72768, 72769, 32, 72774, -72775, 72816, 32, 72818, -72819, - 72850, 0, 72872, 72872, 72873, 0, 72887, -72888, 74864, 32, 74869, -74870, - 92782, 32, 92784, -92785, 92912, 0, 92917, 32, 92918, -92919, 92976, 0, - 92983, 32, 92992, -92993, 92996, 32, 92998, -92999, 94033, 0, 94079, -94080, - 94095, 0, 94099, -94100, 113820, 32, 113821, 0, 113823, 32, 113824, 0, - 113828, -113829, 118784, 32, 119030, -119031, 119040, 32, 119079, -119080, 119081, 32, - 119141, 0, 119146, 32, 119149, 0, 119171, 32, 119173, 0, 119180, 32, - 119210, 0, 119214, 32, 119273, -119274, 119296, 32, 119362, 0, 119365, 32, - 119366, -119367, 119552, 32, 119639, -119640, 119808, -98, 119834, -119835, 119860, -98, - 119886, -119887, 119912, -98, 119938, -119939, 119964, 97, 119965, 119965, 119966, -100, - 119968, -119969, 119970, 103, 119971, -119972, 119973, -107, 119975, -119976, 119977, -111, - 119981, 119981, 119982, -116, 119990, -119991, 120016, -98, 120042, -120043, 120068, -98, - 120070, 120070, 120071, -101, 120075, -120076, 120077, -107, 120085, 120085, 120086, -116, - 120093, -120094, 120120, -98, 120122, 120122, 120123, -101, 120127, 120127, 120128, -106, - 120133, 120133, 120134, 111, 120135, -120136, 120138, -116, 120145, -120146, 120172, -98, - 120198, -120199, 120224, -98, 120250, -120251, 120276, -98, 120302, -120303, 120328, -98, - 120354, -120355, 120380, -98, 120406, -120407, 120432, -98, 120458, -120459, 120488, -946, - 120505, 952, 120506, -964, 120513, 32, 120514, -120515, 120531, 963, 120532, -120533, - 120539, 32, 120540, -120541, 120546, -946, 120563, 952, 120564, -964, 120571, 32, - 120572, -120573, 120589, 963, 120590, -120591, 120597, 32, 120598, -120599, 120604, -946, - 120621, 952, 120622, -964, 120629, 32, 120630, -120631, 120647, 963, 120648, -120649, - 120655, 32, 120656, -120657, 120662, -946, 120679, 952, 120680, -964, 120687, 32, - 120688, -120689, 120705, 963, 120706, -120707, 120713, 32, 120714, -120715, 120720, -946, - 120737, 952, 120738, -964, 120745, 32, 120746, -120747, 120763, 963, 120764, -120765, - 120771, 32, 120772, -120773, 120778, 989, 120779, -120780, 120832, 32, 121344, 0, - 121399, 32, 121403, 0, 121453, 32, 121461, 0, 121462, 32, 121476, 0, - 121477, 32, 121484, -121485, 121499, 0, 121504, 121504, 121505, 0, 121520, -121521, - 122880, 0, 122887, 122887, 122888, 0, 122905, -122906, 122907, 0, 122914, 122914, - 122915, 0, 122917, 122917, 122918, 0, 122923, -122924, 125136, 0, 125143, -125144, - 125184, -125219, 125218, -125219, 125252, 0, 125259, -125260, 125278, 32, 125280, -125281, - 126704, 32, 126706, -126707, 126976, 32, 127020, -127021, 127024, 32, 127124, -127125, - 127136, 32, 127151, -127152, 127153, 32, 127168, 127168, 127169, 32, 127184, 127184, - 127185, 32, 127222, -127223, 127248, 32, 127275, 99, 127276, 114, 127277, 32, - 127279, 127279, 127280, -98, 127306, 32, 127340, -127341, 127344, 32, 127405, -127406, - 127462, 32, 127490, -127491, 127552, 32, 127561, -127562, 127744, 32, 128723, -128724, - 128736, 32, 128749, -128750, 128752, 32, 128759, -128760, 128768, 32, 128884, -128885, - 128896, 32, 128981, -128982, 129024, 32, 129036, -129037, 129040, 32, 129096, -129097, - 129104, 32, 129114, -129115, 129120, 32, 129160, -129161, 129168, 32, 129198, -129199, - 129296, 32, 129311, 129311, 129312, 32, 129320, -129321, 129328, 32, 129329, -129330, - 129331, 32, 129343, 129343, 129344, 32, 129356, -129357, 129360, 32, 129375, -129376, - 129408, 32, 129426, -129427, 129472, 32, 129473, -129474, 131070, 32, 131072, -131073, - 196606, 32, 196608, -196609, 262142, 32, 262144, -262145, 327678, 32, 327680, -327681, - 393214, 32, 393216, -393217, 458750, 32, 458752, -458753, 524286, 32, 524288, -524289, - 589822, 32, 589824, -589825, 655358, 32, 655360, -655361, 720894, 32, 720896, -720897, - 786430, 32, 786432, -786433, 851966, 32, 851968, -851969, 917502, 32, 917504, 917504, - 917505, 0, 917506, -917507, 917536, 0, 917632, -917633, 917760, 0, 918000, -918001, - 983038, 32, 983040, -983041, 1048574, 32, 1048576, -1048577, 1114110, 32, 2147483647, 0}; + 1280, 2097153, 1328, 1328, 1329, -1378, 1367, -1368, 1370, 32, 1376, -1377, + 1417, 32, 1419, -1420, 1421, 32, 1424, 1424, 1425, 0, 1470, 32, + 1471, 0, 1472, 32, 1473, 0, 1475, 32, 1476, 0, 1478, 32, + 1479, 0, 1480, -1481, 1523, 32, 1525, -1526, 1536, 0, 1542, 32, + 1552, 0, 1563, 32, 1564, 0, 1565, 32, 1568, -1569, 1611, 0, + 1632, -1633, 1642, 32, 1646, -1647, 1648, 0, 1649, -1650, 1748, 32, + 1749, 1749, 1750, 0, 1758, 32, 1759, 0, 1765, -1766, 1767, 0, + 1769, 32, 1770, 0, 1774, -1775, 1789, 32, 1791, 1791, 1792, 32, + 1806, 1806, 1807, 0, 1808, 1808, 1809, 0, 1810, -1811, 1840, 0, + 1867, -1868, 1958, 0, 1969, -1970, 2027, 0, 2036, -2037, 2038, 32, + 2042, -2043, 2045, 0, 2046, 32, 2048, -2049, 2070, 0, 2074, 2074, + 2075, 0, 2084, 2084, 2085, 0, 2088, 2088, 2089, 0, 2094, -2095, + 2096, 32, 2111, -2112, 2137, 0, 2140, -2141, 2142, 32, 2143, -2144, + 2184, 32, 2185, -2186, 2192, 0, 2194, -2195, 2200, 0, 2208, -2209, + 2250, 0, 2308, -2309, 2362, 0, 2365, 2365, 2366, 0, 2384, 2384, + 2385, 0, 2392, -2393, 2402, 0, 2404, 32, 2406, -2407, 2416, 32, + 2417, -2418, 2433, 0, 2436, -2437, 2492, 0, 2493, 2493, 2494, 0, + 2501, -2502, 2503, 0, 2505, -2506, 2507, 0, 2510, -2511, 2519, 0, + 2520, -2521, 2530, 0, 2532, -2533, 2546, 32, 2548, -2549, 2554, 32, + 2556, 2556, 2557, 32, 2558, 0, 2559, -2560, 2561, 0, 2564, -2565, + 2620, 0, 2621, 2621, 2622, 0, 2627, -2628, 2631, 0, 2633, -2634, + 2635, 0, 2638, -2639, 2641, 0, 2642, -2643, 2672, 0, 2674, -2675, + 2677, 0, 2678, 32, 2679, -2680, 2689, 0, 2692, -2693, 2748, 0, + 2749, 2749, 2750, 0, 2758, 2758, 2759, 0, 2762, 2762, 2763, 0, + 2766, -2767, 2786, 0, 2788, -2789, 2800, 32, 2802, -2803, 2810, 0, + 2816, 2816, 2817, 0, 2820, -2821, 2876, 0, 2877, 2877, 2878, 0, + 2885, -2886, 2887, 0, 2889, -2890, 2891, 0, 2894, -2895, 2901, 0, + 2904, -2905, 2914, 0, 2916, -2917, 2928, 32, 2929, -2930, 2946, 0, + 2947, -2948, 3006, 0, 3011, -3012, 3014, 0, 3017, 3017, 3018, 0, + 3022, -3023, 3031, 0, 3032, -3033, 3059, 32, 3067, -3068, 3072, 0, + 3077, -3078, 3132, 0, 3133, 3133, 3134, 0, 3141, 3141, 3142, 0, + 3145, 3145, 3146, 0, 3150, -3151, 3157, 0, 3159, -3160, 3170, 0, + 3172, -3173, 3191, 32, 3192, -3193, 3199, 32, 3200, 3200, 3201, 0, + 3204, 32, 3205, -3206, 3260, 0, 3261, 3261, 3262, 0, 3269, 3269, + 3270, 0, 3273, 3273, 3274, 0, 3278, -3279, 3285, 0, 3287, -3288, + 3298, 0, 3300, -3301, 3315, 0, 3316, -3317, 3328, 0, 3332, -3333, + 3387, 0, 3389, 3389, 3390, 0, 3397, 3397, 3398, 0, 3401, 3401, + 3402, 0, 3406, 3406, 3407, 32, 3408, -3409, 3415, 0, 3416, -3417, + 3426, 0, 3428, -3429, 3449, 32, 3450, -3451, 3457, 0, 3460, -3461, + 3530, 0, 3531, -3532, 3535, 0, 3541, 3541, 3542, 0, 3543, 3543, + 3544, 0, 3552, -3553, 3570, 0, 3572, 32, 3573, -3574, 3633, 0, + 3634, -3635, 3636, 0, 3643, -3644, 3647, 32, 3648, -3649, 3655, 0, + 3663, 32, 3664, -3665, 3674, 32, 3676, -3677, 3761, 0, 3762, -3763, + 3764, 0, 3773, -3774, 3784, 0, 3791, -3792, 3841, 32, 3864, 0, + 3866, 32, 3872, -3873, 3892, 32, 3893, 0, 3894, 32, 3895, 0, + 3896, 32, 3897, 0, 3898, 32, 3902, 0, 3904, -3905, 3953, 0, + 3973, 32, 3974, 0, 3976, -3977, 3981, 0, 3992, 3992, 3993, 0, + 4029, 4029, 4030, 32, 4038, 0, 4039, 32, 4045, 4045, 4046, 32, + 4059, -4060, 4139, 0, 4159, -4160, 4170, 32, 4176, -4177, 4182, 0, + 4186, -4187, 4190, 0, 4193, 4193, 4194, 0, 4197, -4198, 4199, 0, + 4206, -4207, 4209, 0, 4213, -4214, 4226, 0, 4238, 4238, 4239, 0, + 4240, -4241, 4250, 0, 4254, 32, 4256, -11521, 4294, 4294, 4295, 11559, + 4296, -4297, 4301, 11565, 4302, -4303, 4347, 32, 4348, -4349, 4957, 0, + 4960, 32, 4969, -4970, 5008, 32, 5018, -5019, 5112, -5105, 5118, -5119, + 5120, 32, 5121, -5122, 5741, 32, 5743, -5744, 5760, 32, 5761, -5762, + 5787, 32, 5789, -5790, 5867, 32, 5870, -5871, 5906, 0, 5910, -5911, + 5938, 0, 5941, 32, 5943, -5944, 5970, 0, 5972, -5973, 6002, 0, + 6004, -6005, 6068, 0, 6100, 32, 6103, 6103, 6104, 32, 6108, 6108, + 6109, 0, 6110, -6111, 6144, 32, 6155, 0, 6160, -6161, 6277, 0, + 6279, -6280, 6313, 0, 6314, -6315, 6432, 0, 6444, -6445, 6448, 0, + 6460, -6461, 6464, 32, 6465, -6466, 6468, 32, 6470, -6471, 6622, 32, + 6656, -6657, 6679, 0, 6684, -6685, 6686, 32, 6688, -6689, 6741, 0, + 6751, 6751, 6752, 0, 6781, -6782, 6783, 0, 6784, -6785, 6816, 32, + 6823, 6823, 6824, 32, 6830, -6831, 6832, 0, 6863, -6864, 6912, 0, + 6917, -6918, 6964, 0, 6981, -6982, 7002, 32, 7019, 0, 7028, 32, + 7039, 7039, 7040, 0, 7043, -7044, 7073, 0, 7086, -7087, 7142, 0, + 7156, -7157, 7164, 32, 7168, -7169, 7204, 0, 7224, -7225, 7227, 32, + 7232, -7233, 7294, 32, 7296, 1074, 7297, 1076, 7298, 1086, 7299, -1090, + 7301, 1090, 7302, 1098, 7303, 1123, 7304, 42571, 7305, -7306, 7312, -4305, + 7355, -7356, 7357, -4350, 7360, 32, 7368, -7369, 7376, 0, 7379, 32, + 7380, 0, 7401, -7402, 7405, 0, 7406, -7407, 7412, 0, 7413, -7414, + 7415, 0, 7418, -7419, 7468, 97, 7469, 230, 7470, 98, 7471, 7471, + 7472, -101, 7474, 477, 7475, -104, 7483, 7483, 7484, 111, 7485, 547, + 7486, 112, 7487, 114, 7488, -117, 7490, 119, 7491, -7492, 7616, 0, + 7680, 2097153, 7830, -7831, 7835, 7777, 7836, -7837, 7838, 223, 7839, 2097153, + 7936, -7937, 7944, -7937, 7952, -7953, 7960, -7953, 7966, -7967, 7976, -7969, + 7984, -7985, 7992, -7985, 8000, -8001, 8008, -8001, 8014, -8015, 8025, 8017, + 8026, 8026, 8027, 8019, 8028, 8028, 8029, 8021, 8030, 8030, 8031, 8023, + 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065, 8080, -8081, 8088, -8081, + 8096, -8097, 8104, -8097, 8112, -8113, 8120, -8113, 8122, -8049, 8124, 8115, + 8125, 32, 8126, 953, 8127, 32, 8130, -8131, 8136, -8051, 8140, 8131, + 8141, 32, 8144, -8145, 8152, -8145, 8154, -8055, 8156, 8156, 8157, 32, + 8160, -8161, 8168, -8161, 8170, -8059, 8172, 8165, 8173, 32, 8176, -8177, + 8184, -8057, 8186, -8061, 8188, 8179, 8189, 32, 8191, 8191, 8192, 32, + 8203, 0, 8208, 32, 8234, 0, 8239, 32, 8288, 0, 8293, 8293, + 8294, 0, 8304, -8305, 8314, 32, 8319, -8320, 8330, 32, 8335, -8336, + 8352, 32, 8385, -8386, 8400, 0, 8433, -8434, 8448, 32, 8450, 99, + 8452, 32, 8455, 603, 8456, 32, 8457, 102, 8458, 8458, 8459, 104, + 8462, -8463, 8464, 105, 8466, 108, 8467, 8467, 8468, 32, 8469, 110, + 8470, 32, 8473, -113, 8476, 114, 8478, 32, 8484, 122, 8485, 32, + 8486, 969, 8487, 32, 8488, 122, 8489, 32, 8490, 107, 8491, 229, + 8492, -99, 8494, 32, 8495, 8495, 8496, -102, 8498, 8526, 8499, 109, + 8500, -8501, 8506, 32, 8508, -8509, 8510, 947, 8511, 960, 8512, 32, + 8517, 100, 8518, -8519, 8522, 32, 8526, 8526, 8527, 32, 8528, -8529, + 8544, -8561, 8560, -8561, 8579, 8580, 8581, -8582, 8586, 32, 8588, -8589, + 8592, 32, 9255, -9256, 9280, 32, 9291, -9292, 9372, 32, 9398, -9425, + 9424, -9425, 9472, 32, 10102, -10103, 10132, 32, 11124, -11125, 11126, 32, + 11158, 11158, 11159, 32, 11264, -11313, 11312, -11313, 11360, 11361, 11362, 619, + 11363, 7549, 11364, 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371, 11372, + 11373, 593, 11374, 625, 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380, + 11381, 11382, 11383, -11384, 11389, 118, 11390, -576, 11392, 2097153, 11492, 11492, + 11493, 32, 11499, 11500, 11501, 11502, 11503, 0, 11506, 11507, 11508, -11509, + 11513, 32, 11517, 11517, 11518, 32, 11520, -11521, 11632, 32, 11633, -11634, + 11647, 0, 11648, -11649, 11744, 0, 11776, 32, 11823, 11823, 11824, 32, + 11870, -11871, 11904, 32, 11930, 11930, 11931, 32, 11935, 11935, 11936, 32, + 12019, -12020, 12272, 32, 12284, -12285, 12288, 32, 12293, -12294, 12296, 32, + 12321, -12322, 12330, 0, 12336, 32, 12337, -12338, 12342, 32, 12344, -12345, + 12349, 32, 12352, -12353, 12441, 0, 12443, 32, 12445, -12446, 12448, 32, + 12449, -12450, 12539, 32, 12540, 0, 12541, -12542, 12688, 32, 12690, -12691, + 12736, 32, 12772, -12773, 12800, 32, 12831, -12832, 12842, 32, 12868, -12869, + 12880, 32, 12881, -12882, 12910, 32, 12928, -12929, 12992, 32, 13008, -13009, + 13055, 32, 13312, -13313, 19904, 32, 19968, -19969, 42128, 32, 42183, -42184, + 42238, 32, 42240, -42241, 42509, 32, 42512, -42513, 42560, 2097153, 42606, 42606, + 42607, 0, 42611, 32, 42612, 0, 42622, 32, 42623, 2097153, 42652, -42653, + 42654, 0, 42656, -42657, 42736, 0, 42738, 32, 42744, -42745, 42752, 32, + 42775, -42776, 42784, 32, 42786, 2097153, 42800, -42801, 42802, 2097153, 42864, -42865, + 42873, 42874, 42875, 42876, 42877, 7545, 42878, 2097153, 42888, 42888, 42889, 32, + 42891, 42892, 42893, 613, 42894, -42895, 42896, 2097153, 42900, -42901, 42902, 2097153, + 42922, 614, 42923, 604, 42924, 609, 42925, 620, 42926, 618, 42927, 42927, + 42928, 670, 42929, 647, 42930, 669, 42931, 43859, 42932, 2097153, 42948, 42900, + 42949, 642, 42950, 7566, 42951, 42952, 42953, 42954, 42955, -42956, 42960, 42961, + 42962, -42963, 42966, 2097153, 42970, -42971, 42994, 99, 42995, 102, 42996, 113, + 42997, 42998, 42999, 42999, 43000, 295, 43001, -43002, 43010, 0, 43011, -43012, + 43014, 0, 43015, -43016, 43019, 0, 43020, -43021, 43043, 0, 43048, 32, + 43052, 0, 43053, -43054, 43062, 32, 43066, -43067, 43124, 32, 43128, -43129, + 43136, 0, 43138, -43139, 43188, 0, 43206, -43207, 43214, 32, 43216, -43217, + 43232, 0, 43250, -43251, 43256, 32, 43259, 43259, 43260, 32, 43261, -43262, + 43263, 0, 43264, -43265, 43302, 0, 43310, 32, 43312, -43313, 43335, 0, + 43348, -43349, 43359, 32, 43360, -43361, 43392, 0, 43396, -43397, 43443, 0, + 43457, 32, 43470, -43471, 43486, 32, 43488, -43489, 43493, 0, 43494, -43495, + 43561, 0, 43575, -43576, 43587, 0, 43588, -43589, 43596, 0, 43598, -43599, + 43612, 32, 43616, -43617, 43639, 32, 43642, 43642, 43643, 0, 43646, -43647, + 43696, 0, 43697, 43697, 43698, 0, 43701, -43702, 43703, 0, 43705, -43706, + 43710, 0, 43712, 43712, 43713, 0, 43714, -43715, 43742, 32, 43744, -43745, + 43755, 0, 43760, 32, 43762, -43763, 43765, 0, 43767, -43768, 43867, 32, + 43868, -43869, 43882, 32, 43884, -43885, 43888, -5025, 43968, -43969, 44003, 0, + 44011, 32, 44012, 0, 44014, -44015, 55296, 0, 57344, -57345, 64286, 0, + 64287, -64288, 64297, 32, 64298, -64299, 64434, 32, 64451, -64452, 64830, 32, + 64848, -64849, 64975, 32, 65008, -65009, 65020, 32, 65024, 0, 65040, 32, + 65050, -65051, 65056, 0, 65072, 32, 65107, 65107, 65108, 32, 65127, 65127, + 65128, 32, 65132, -65133, 65279, 0, 65280, 65280, 65281, 32, 65296, -65297, + 65306, 32, 65313, -65346, 65339, 32, 65345, -65346, 65371, 32, 65382, -65383, + 65504, 32, 65511, 65511, 65512, 32, 65519, -65520, 65529, 0, 65532, 32, + 65536, -65537, 65792, 32, 65795, -65796, 65847, 32, 65856, -65857, 65913, 32, + 65930, -65931, 65932, 32, 65935, 65935, 65936, 32, 65949, -65950, 65952, 32, + 65953, -65954, 66000, 32, 66045, 0, 66046, -66047, 66272, 0, 66273, -66274, + 66422, 0, 66427, -66428, 66463, 32, 66464, -66465, 66512, 32, 66513, -66514, + 66560, -66601, 66600, -66601, 66736, -66777, 66772, -66773, 66927, 32, 66928, -66968, + 66939, 66939, 66940, -66980, 66955, 66955, 66956, -66996, 66963, 66963, 66964, -67004, + 66966, -66967, 67671, 32, 67672, -67673, 67703, 32, 67705, -67706, 67871, 32, + 67872, -67873, 67903, 32, 67904, -67905, 68097, 0, 68100, 68100, 68101, 0, + 68103, -68104, 68108, 0, 68112, -68113, 68152, 0, 68155, -68156, 68159, 0, + 68160, -68161, 68176, 32, 68185, -68186, 68223, 32, 68224, -68225, 68296, 32, + 68297, -68298, 68325, 0, 68327, -68328, 68336, 32, 68343, -68344, 68409, 32, + 68416, -68417, 68505, 32, 68509, -68510, 68736, -68801, 68787, -68788, 68900, 0, + 68904, -68905, 69291, 0, 69293, 32, 69294, -69295, 69373, 0, 69376, -69377, + 69446, 0, 69457, -69458, 69461, 32, 69466, -69467, 69506, 0, 69510, 32, + 69514, -69515, 69632, 0, 69635, -69636, 69688, 0, 69703, 32, 69710, -69711, + 69744, 0, 69745, -69746, 69747, 0, 69749, -69750, 69759, 0, 69763, -69764, + 69808, 0, 69819, 32, 69821, 0, 69822, 32, 69826, 0, 69827, -69828, + 69837, 0, 69838, -69839, 69888, 0, 69891, -69892, 69927, 0, 69941, -69942, + 69952, 32, 69956, 69956, 69957, 0, 69959, -69960, 70003, 0, 70004, 32, + 70006, -70007, 70016, 0, 70019, -70020, 70067, 0, 70081, -70082, 70085, 32, + 70089, 0, 70093, 32, 70094, 0, 70096, -70097, 70107, 32, 70108, 70108, + 70109, 32, 70112, -70113, 70188, 0, 70200, 32, 70206, 0, 70207, -70208, + 70209, 0, 70210, -70211, 70313, 32, 70314, -70315, 70367, 0, 70379, -70380, + 70400, 0, 70404, -70405, 70459, 0, 70461, 70461, 70462, 0, 70469, -70470, + 70471, 0, 70473, -70474, 70475, 0, 70478, -70479, 70487, 0, 70488, -70489, + 70498, 0, 70500, -70501, 70502, 0, 70509, -70510, 70512, 0, 70517, -70518, + 70709, 0, 70727, -70728, 70731, 32, 70736, -70737, 70746, 32, 70748, 70748, + 70749, 32, 70750, 0, 70751, -70752, 70832, 0, 70852, -70853, 70854, 32, + 70855, -70856, 71087, 0, 71094, -71095, 71096, 0, 71105, 32, 71128, -71129, + 71132, 0, 71134, -71135, 71216, 0, 71233, 32, 71236, -71237, 71264, 32, + 71277, -71278, 71339, 0, 71352, 71352, 71353, 32, 71354, -71355, 71453, 0, + 71468, -71469, 71484, 32, 71488, -71489, 71724, 0, 71739, 32, 71740, -71741, + 71840, -71873, 71872, -71873, 71984, 0, 71990, 71990, 71991, 0, 71993, -71994, + 71995, 0, 71999, 71999, 72000, 0, 72001, 72001, 72002, 0, 72004, 32, + 72007, -72008, 72145, 0, 72152, -72153, 72154, 0, 72161, 72161, 72162, 32, + 72163, 72163, 72164, 0, 72165, -72166, 72193, 0, 72203, -72204, 72243, 0, + 72250, 72250, 72251, 0, 72255, 32, 72263, 0, 72264, -72265, 72273, 0, + 72284, -72285, 72330, 0, 72346, 32, 72349, 72349, 72350, 32, 72355, -72356, + 72448, 32, 72458, -72459, 72751, 0, 72759, 72759, 72760, 0, 72768, 72768, + 72769, 32, 72774, -72775, 72816, 32, 72818, -72819, 72850, 0, 72872, 72872, + 72873, 0, 72887, -72888, 73009, 0, 73015, -73016, 73018, 0, 73019, 73019, + 73020, 0, 73022, 73022, 73023, 0, 73030, 73030, 73031, 0, 73032, -73033, + 73098, 0, 73103, 73103, 73104, 0, 73106, 73106, 73107, 0, 73112, -73113, + 73459, 0, 73463, 32, 73465, -73466, 73472, 0, 73474, 73474, 73475, 0, + 73476, -73477, 73524, 0, 73531, -73532, 73534, 0, 73539, 32, 73552, -73553, + 73685, 32, 73714, -73715, 73727, 32, 73728, -73729, 74864, 32, 74869, -74870, + 77809, 32, 77811, -77812, 78896, 0, 78913, -78914, 78919, 0, 78934, -78935, + 92782, 32, 92784, -92785, 92912, 0, 92917, 32, 92918, -92919, 92976, 0, + 92983, 32, 92992, -92993, 92996, 32, 92998, -92999, 93760, -93793, 93792, -93793, + 93847, 32, 93851, -93852, 94031, 0, 94032, 94032, 94033, 0, 94088, -94089, + 94095, 0, 94099, -94100, 94178, 32, 94179, 94179, 94180, 0, 94181, -94182, + 94192, 0, 94194, -94195, 113820, 32, 113821, 0, 113823, 32, 113824, 0, + 113828, -113829, 118528, 0, 118574, -118575, 118576, 0, 118599, -118600, 118608, 32, + 118724, -118725, 118784, 32, 119030, -119031, 119040, 32, 119079, -119080, 119081, 32, + 119141, 0, 119146, 32, 119149, 0, 119171, 32, 119173, 0, 119180, 32, + 119210, 0, 119214, 32, 119275, -119276, 119296, 32, 119362, 0, 119365, 32, + 119366, -119367, 119552, 32, 119639, -119640, 119808, -98, 119834, -119835, 119860, -98, + 119886, -119887, 119912, -98, 119938, -119939, 119964, 97, 119965, 119965, 119966, -100, + 119968, -119969, 119970, 103, 119971, -119972, 119973, -107, 119975, -119976, 119977, -111, + 119981, 119981, 119982, -116, 119990, -119991, 120016, -98, 120042, -120043, 120068, -98, + 120070, 120070, 120071, -101, 120075, -120076, 120077, -107, 120085, 120085, 120086, -116, + 120093, -120094, 120120, -98, 120122, 120122, 120123, -101, 120127, 120127, 120128, -106, + 120133, 120133, 120134, 111, 120135, -120136, 120138, -116, 120145, -120146, 120172, -98, + 120198, -120199, 120224, -98, 120250, -120251, 120276, -98, 120302, -120303, 120328, -98, + 120354, -120355, 120380, -98, 120406, -120407, 120432, -98, 120458, -120459, 120488, -946, + 120505, 952, 120506, -964, 120513, 32, 120514, -120515, 120531, 963, 120532, -120533, + 120539, 32, 120540, -120541, 120546, -946, 120563, 952, 120564, -964, 120571, 32, + 120572, -120573, 120589, 963, 120590, -120591, 120597, 32, 120598, -120599, 120604, -946, + 120621, 952, 120622, -964, 120629, 32, 120630, -120631, 120647, 963, 120648, -120649, + 120655, 32, 120656, -120657, 120662, -946, 120679, 952, 120680, -964, 120687, 32, + 120688, -120689, 120705, 963, 120706, -120707, 120713, 32, 120714, -120715, 120720, -946, + 120737, 952, 120738, -964, 120745, 32, 120746, -120747, 120763, 963, 120764, -120765, + 120771, 32, 120772, -120773, 120778, 989, 120779, -120780, 120832, 32, 121344, 0, + 121399, 32, 121403, 0, 121453, 32, 121461, 0, 121462, 32, 121476, 0, + 121477, 32, 121484, -121485, 121499, 0, 121504, 121504, 121505, 0, 121520, -121521, + 122880, 0, 122887, 122887, 122888, 0, 122905, -122906, 122907, 0, 122914, 122914, + 122915, 0, 122917, 122917, 122918, 0, 122923, -122924, 123023, 0, 123024, -123025, + 123184, 0, 123191, -123192, 123215, 32, 123216, -123217, 123566, 0, 123567, -123568, + 123628, 0, 123632, -123633, 123647, 32, 123648, -123649, 124140, 0, 124144, -124145, + 125136, 0, 125143, -125144, 125184, -125219, 125218, -125219, 125252, 0, 125259, -125260, + 125278, 32, 125280, -125281, 126124, 32, 126125, -126126, 126128, 32, 126129, -126130, + 126254, 32, 126255, -126256, 126704, 32, 126706, -126707, 126976, 32, 127020, -127021, + 127024, 32, 127124, -127125, 127136, 32, 127151, -127152, 127153, 32, 127168, 127168, + 127169, 32, 127184, 127184, 127185, 32, 127222, -127223, 127245, 32, 127275, 99, + 127276, 114, 127277, 32, 127280, -98, 127306, 32, 127406, -127407, 127462, 32, + 127490, -127491, 127552, 32, 127561, -127562, 127584, 32, 127590, -127591, 127744, 32, + 128728, -128729, 128732, 32, 128749, -128750, 128752, 32, 128765, -128766, 128768, 32, + 128887, -128888, 128891, 32, 128986, -128987, 128992, 32, 129004, -129005, 129008, 32, + 129009, -129010, 129024, 32, 129036, -129037, 129040, 32, 129096, -129097, 129104, 32, + 129114, -129115, 129120, 32, 129160, -129161, 129168, 32, 129198, -129199, 129200, 32, + 129202, -129203, 129280, 32, 129620, -129621, 129632, 32, 129646, -129647, 129648, 32, + 129661, -129662, 129664, 32, 129673, -129674, 129680, 32, 129726, 129726, 129727, 32, + 129734, -129735, 129742, 32, 129756, -129757, 129760, 32, 129769, -129770, 129776, 32, + 129785, -129786, 129792, 32, 129939, 129939, 129940, 32, 129995, -129996, 131070, 32, + 131072, -131073, 196606, 32, 196608, -196609, 262142, 32, 262144, -262145, 327678, 32, + 327680, -327681, 393214, 32, 393216, -393217, 458750, 32, 458752, -458753, 524286, 32, + 524288, -524289, 589822, 32, 589824, -589825, 655358, 32, 655360, -655361, 720894, 32, + 720896, -720897, 786430, 32, 786432, -786433, 851966, 32, 851968, -851969, 917502, 32, + 917504, 917504, 917505, 0, 917506, -917507, 917536, 0, 917632, -917633, 917760, 0, + 918000, -918001, 983038, 32, 983040, -983041, 1048574, 32, 1048576, -1048577, 1114110, 32, + 2147483647, 0}; -static int16 to_lower_table[TABLE_SIZE] = { +static const int16 to_lower_table[TABLE_SIZE] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, @@ -485,33 +597,680 @@ static int16 to_lower_table[TABLE_SIZE] = { 1273, 1275, 1275, 1277, 1277, 1279, 1279}; static const int32 to_lower_ranges[] = { - 1280, 2097153, 1328, 1328, 1329, -1378, 1367, -1368, 4256, -11521, 4294, 4294, 4295, - 11559, 4296, -4297, 4301, 11565, 4302, -4303, 5024, -43889, 5104, -5113, 5110, -5111, - 7680, 2097153, 7830, -7831, 7838, 223, 7839, 2097153, 7936, -7937, 7944, -7937, 7952, - -7953, 7960, -7953, 7966, -7967, 7976, -7969, 7984, -7985, 7992, -7985, 8000, -8001, - 8008, -8001, 8014, -8015, 8025, 8017, 8026, 8026, 8027, 8019, 8028, 8028, 8029, - 8021, 8030, 8030, 8031, 8023, 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065, - 8080, -8081, 8088, -8081, 8096, -8097, 8104, -8097, 8112, -8113, 8120, -8113, 8122, - -8049, 8124, 8115, 8125, -8126, 8136, -8051, 8140, 8131, 8141, -8142, 8152, -8145, - 8154, -8055, 8156, -8157, 8168, -8161, 8170, -8059, 8172, 8165, 8173, -8174, 8184, - -8057, 8186, -8061, 8188, 8179, 8189, -8190, 8486, 969, 8487, -8488, 8490, 107, - 8491, 229, 8492, -8493, 8498, 8526, 8499, -8500, 8544, -8561, 8560, -8561, 8579, - 8580, 8581, -8582, 9398, -9425, 9424, -9425, 11264, -11313, 11311, -11312, 11360, 11361, - 11362, 619, 11363, 7549, 11364, 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371, - 11372, 11373, 593, 11374, 625, 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380, - 11381, 11382, 11383, -11384, 11390, -576, 11392, 2097153, 11492, -11493, 11499, 11500, 11501, - 11502, 11503, -11504, 11506, 11507, 11508, -11509, 42560, 2097153, 42606, -42607, 42624, 2097153, - 42652, -42653, 42786, 2097153, 42800, -42801, 42802, 2097153, 42864, -42865, 42873, 42874, 42875, - 42876, 42877, 7545, 42878, 2097153, 42888, -42889, 42891, 42892, 42893, 613, 42894, -42895, - 42896, 2097153, 42900, -42901, 42902, 2097153, 42922, 614, 42923, 604, 42924, 609, 42925, - 620, 42926, 618, 42927, 42927, 42928, 670, 42929, 647, 42930, 669, 42931, 43859, - 42932, 2097153, 42936, -42937, 65313, -65346, 65339, -65340, 66560, -66601, 66600, -66601, 66736, - -66777, 66772, -66773, 68736, -68801, 68787, -68788, 71840, -71873, 71872, -71873, 125184, -125219, - 125218, -125219, 2147483647, 0}; + 1280, 2097153, 1328, 1328, 1329, -1378, 1367, -1368, 4256, -11521, 4294, 4294, 4295, + 11559, 4296, -4297, 4301, 11565, 4302, -4303, 5024, -43889, 5104, -5113, 5110, -5111, + 7312, -4305, 7355, -7356, 7357, -4350, 7360, -7361, 7680, 2097153, 7830, -7831, 7838, + 223, 7839, 2097153, 7936, -7937, 7944, -7937, 7952, -7953, 7960, -7953, 7966, -7967, + 7976, -7969, 7984, -7985, 7992, -7985, 8000, -8001, 8008, -8001, 8014, -8015, 8025, + 8017, 8026, 8026, 8027, 8019, 8028, 8028, 8029, 8021, 8030, 8030, 8031, 8023, + 8032, -8033, 8040, -8033, 8048, -8049, 8072, -8065, 8080, -8081, 8088, -8081, 8096, + -8097, 8104, -8097, 8112, -8113, 8120, -8113, 8122, -8049, 8124, 8115, 8125, -8126, + 8136, -8051, 8140, 8131, 8141, -8142, 8152, -8145, 8154, -8055, 8156, -8157, 8168, + -8161, 8170, -8059, 8172, 8165, 8173, -8174, 8184, -8057, 8186, -8061, 8188, 8179, + 8189, -8190, 8486, 969, 8487, -8488, 8490, 107, 8491, 229, 8492, -8493, 8498, + 8526, 8499, -8500, 8544, -8561, 8560, -8561, 8579, 8580, 8581, -8582, 9398, -9425, + 9424, -9425, 11264, -11313, 11312, -11313, 11360, 11361, 11362, 619, 11363, 7549, 11364, + 637, 11365, -11366, 11367, 11368, 11369, 11370, 11371, 11372, 11373, 593, 11374, 625, + 11375, 592, 11376, 594, 11377, 2097153, 11380, 11380, 11381, 11382, 11383, -11384, 11390, + -576, 11392, 2097153, 11492, -11493, 11499, 11500, 11501, 11502, 11503, -11504, 11506, 11507, + 11508, -11509, 42560, 2097153, 42606, -42607, 42624, 2097153, 42652, -42653, 42786, 2097153, 42800, + -42801, 42802, 2097153, 42864, -42865, 42873, 42874, 42875, 42876, 42877, 7545, 42878, 2097153, + 42888, -42889, 42891, 42892, 42893, 613, 42894, -42895, 42896, 2097153, 42900, -42901, 42902, + 2097153, 42922, 614, 42923, 604, 42924, 609, 42925, 620, 42926, 618, 42927, 42927, + 42928, 670, 42929, 647, 42930, 669, 42931, 43859, 42932, 2097153, 42948, 42900, 42949, + 642, 42950, 7566, 42951, 42952, 42953, 42954, 42955, -42956, 42960, 42961, 42962, -42963, + 42966, 2097153, 42970, -42971, 42997, 42998, 42999, -43000, 65313, -65346, 65339, -65340, 66560, + -66601, 66600, -66601, 66736, -66777, 66772, -66773, 66928, -66968, 66939, 66939, 66940, -66980, + 66955, 66955, 66956, -66996, 66963, 66963, 66964, -67004, 66966, -66967, 68736, -68801, 68787, + -68788, 71840, -71873, 71872, -71873, 93760, -93793, 93792, -93793, 125184, -125219, 125218, -125219, + 2147483647, 0}; + +static const int16 without_diacritics_table[TABLE_SIZE] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, + 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, + 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, + 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, + 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, + 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, + 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, + 152, 153, 154, 155, 156, 157, 158, 159, 32, 161, 162, 163, 164, 165, 166, 167, 32, 169, 97, + 171, 172, 0, 174, 32, 176, 177, 50, 51, 32, 956, 182, 0, 32, 49, 111, 187, 188, 189, + 190, 191, 65, 65, 65, 65, 65, 65, 198, 67, 69, 69, 69, 69, 73, 73, 73, 73, 208, + 78, 79, 79, 79, 79, 79, 215, 216, 85, 85, 85, 85, 89, 222, 223, 97, 97, 97, 97, + 97, 97, 230, 99, 101, 101, 101, 101, 105, 105, 105, 105, 240, 110, 111, 111, 111, 111, 111, + 247, 248, 117, 117, 117, 117, 121, 254, 121, 65, 97, 65, 97, 65, 97, 67, 99, 67, 99, + 67, 99, 67, 99, 68, 100, 272, 273, 69, 101, 69, 101, 69, 101, 69, 101, 69, 101, 71, + 103, 71, 103, 71, 103, 71, 103, 72, 104, 294, 295, 73, 105, 73, 105, 73, 105, 73, 105, + 73, 305, 306, 307, 74, 106, 75, 107, 312, 76, 108, 76, 108, 76, 108, 76, 108, 321, 322, + 78, 110, 78, 110, 78, 110, 110, 330, 331, 79, 111, 79, 111, 79, 111, 338, 339, 82, 114, + 82, 114, 82, 114, 83, 115, 83, 115, 83, 115, 83, 115, 84, 116, 84, 116, 358, 359, 85, + 117, 85, 117, 85, 117, 85, 117, 85, 117, 85, 117, 87, 119, 89, 121, 89, 90, 122, 90, + 122, 90, 122, 115, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, + 399, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 79, 111, + 418, 419, 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 85, 117, 433, 434, 435, 436, + 437, 438, 439, 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, + 456, 457, 458, 459, 460, 65, 97, 73, 105, 79, 111, 85, 117, 85, 117, 85, 117, 85, 117, + 85, 117, 477, 65, 97, 65, 97, 198, 230, 484, 485, 71, 103, 75, 107, 79, 111, 79, 111, + 439, 658, 106, 497, 498, 499, 71, 103, 502, 503, 78, 110, 65, 97, 198, 230, 216, 248, 65, + 97, 65, 97, 69, 101, 69, 101, 73, 105, 73, 105, 79, 111, 79, 111, 82, 114, 82, 114, + 85, 117, 85, 117, 83, 115, 84, 116, 540, 541, 72, 104, 544, 545, 546, 547, 548, 549, 65, + 97, 69, 101, 79, 111, 79, 111, 79, 111, 79, 111, 89, 121, 564, 565, 566, 567, 568, 569, + 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, 580, 581, 582, 583, 584, 585, 586, 587, 588, + 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, 600, 601, 602, 603, 604, 605, 606, 607, + 608, 609, 610, 611, 612, 613, 614, 615, 616, 617, 618, 619, 620, 621, 622, 623, 624, 625, 626, + 627, 628, 629, 630, 631, 632, 633, 634, 635, 636, 637, 638, 639, 640, 641, 642, 643, 644, 645, + 646, 647, 648, 649, 650, 651, 652, 653, 654, 655, 656, 657, 658, 659, 660, 661, 662, 663, 664, + 665, 666, 667, 668, 669, 670, 671, 672, 673, 674, 675, 676, 677, 678, 679, 680, 681, 682, 683, + 684, 685, 686, 687, 104, 614, 106, 114, 633, 635, 641, 119, 121, 697, 698, 699, 0, 701, 0, + 703, 704, 705, 706, 707, 708, 709, 710, 711, 712, 713, 714, 715, 716, 717, 718, 719, 720, 721, + 722, 723, 724, 725, 726, 727, 32, 32, 32, 32, 32, 32, 734, 735, 611, 108, 115, 120, 661, + 741, 742, 743, 744, 745, 746, 747, 748, 749, 750, 751, 752, 753, 754, 755, 756, 757, 758, 759, + 760, 761, 762, 763, 764, 765, 766, 767, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 837, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 880, 881, 882, 883, 697, 885, 886, 887, 888, 889, 32, 891, 892, + 893, 59, 895, 896, 897, 898, 899, 32, 32, 913, 903, 917, 919, 921, 907, 927, 909, 933, 937, + 953, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 930, + 931, 932, 933, 934, 935, 936, 937, 921, 933, 945, 949, 951, 953, 965, 945, 946, 947, 948, 949, + 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, + 969, 953, 965, 959, 965, 969, 975, 946, 952, 933, 933, 933, 966, 960, 983, 984, 985, 986, 987, + 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, + 1007, 954, 961, 962, 1011, 920, 949, 1014, 1015, 1016, 931, 1018, 1019, 1020, 1021, 1022, 1023, 1045, 1045, + 1026, 1043, 1028, 1029, 1030, 1030, 1032, 1033, 1034, 1035, 1050, 1048, 1059, 1039, 1040, 1041, 1042, 1043, 1044, + 1045, 1046, 1047, 1048, 1048, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, + 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1080, 1082, + 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, + 1102, 1103, 1077, 1077, 1106, 1075, 1108, 1109, 1110, 1110, 1112, 1113, 1114, 1115, 1082, 1080, 1091, 1119, 1120, + 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130, 1131, 1132, 1133, 1134, 1135, 1136, 1137, 1138, 1139, + 1140, 1141, 1140, 1141, 1144, 1145, 1146, 1147, 1148, 1149, 1150, 1151, 1152, 1153, 1154, 0, 0, 0, 0, + 0, 0, 0, 1162, 1163, 1164, 1165, 1166, 1167, 1168, 1169, 1170, 1171, 1172, 1173, 1174, 1175, 1176, 1177, + 1178, 1179, 1180, 1181, 1182, 1183, 1184, 1185, 1186, 1187, 1188, 1189, 1190, 1191, 1192, 1193, 1194, 1195, 1196, + 1197, 1198, 1199, 1200, 1201, 1202, 1203, 1204, 1205, 1206, 1207, 1208, 1209, 1210, 1211, 1212, 1213, 1214, 1215, + 1216, 1046, 1078, 1219, 1220, 1221, 1222, 1223, 1224, 1225, 1226, 1227, 1228, 1229, 1230, 1231, 1040, 1072, 1040, + 1072, 1236, 1237, 1045, 1077, 1240, 1241, 1240, 1241, 1046, 1078, 1047, 1079, 1248, 1249, 1048, 1080, 1048, 1080, + 1054, 1086, 1256, 1257, 1256, 1257, 1069, 1101, 1059, 1091, 1059, 1091, 1059, 1091, 1063, 1095, 1270, 1271, 1067, + 1099, 1274, 1275, 1276, 1277, 1278, 1279}; + +static const int32 without_diacritics_ranges[] = { + 1280, -1281, 1425, 0, 1470, 1470, 1471, 0, 1472, 1472, 1473, 0, + 1475, 1475, 1476, 0, 1478, 1478, 1479, 0, 1480, -1481, 1536, 0, + 1542, -1543, 1552, 0, 1563, 1563, 1564, 0, 1565, -1566, 1570, 1575, + 1572, 1608, 1573, 1575, 1574, 1610, 1575, -1576, 1611, 0, 1632, -1633, + 1648, 0, 1649, -1650, 1728, 1749, 1729, 2097154, 1732, -1733, 1747, 2097152, + 1749, 1749, 1750, 0, 1758, 1758, 1759, 0, 1765, -1766, 1767, 0, + 1769, 1769, 1770, 0, 1774, -1775, 1807, 0, 1808, 1808, 1809, 0, + 1810, -1811, 1840, 0, 1867, -1868, 1958, 0, 1969, -1970, 2027, 0, + 2036, -2037, 2045, 0, 2046, -2047, 2070, 0, 2074, 2074, 2075, 0, + 2084, 2084, 2085, 0, 2088, 2088, 2089, 0, 2094, -2095, 2137, 0, + 2140, -2141, 2192, 0, 2194, -2195, 2200, 0, 2208, -2209, 2250, 0, + 2308, -2309, 2345, 2097152, 2347, -2348, 2353, 2097152, 2355, 2097154, 2358, -2359, + 2362, 0, 2365, 2365, 2366, 0, 2384, 2384, 2385, 0, 2392, -2326, + 2395, 2332, 2396, -2338, 2398, 2347, 2399, 2351, 2400, -2401, 2402, 0, + 2404, -2405, 2433, 0, 2436, -2437, 2492, 0, 2493, 2493, 2494, 0, + 2501, -2502, 2503, 0, 2505, -2506, 2507, 0, 2510, -2511, 2519, 0, + 2520, -2521, 2524, -2466, 2526, 2526, 2527, 2479, 2528, -2529, 2530, 0, + 2532, -2533, 2558, 0, 2559, -2560, 2561, 0, 2564, -2565, 2611, 2097152, + 2613, 2613, 2614, 2616, 2615, -2616, 2620, 0, 2621, 2621, 2622, 0, + 2627, -2628, 2631, 0, 2633, -2634, 2635, 0, 2638, -2639, 2641, 0, + 2642, -2643, 2649, -2583, 2651, 2588, 2652, -2653, 2654, 2603, 2655, -2656, + 2672, 0, 2674, -2675, 2677, 0, 2678, -2679, 2689, 0, 2692, -2693, + 2748, 0, 2749, 2749, 2750, 0, 2758, 2758, 2759, 0, 2762, 2762, + 2763, 0, 2766, -2767, 2786, 0, 2788, -2789, 2810, 0, 2816, 2816, + 2817, 0, 2820, -2821, 2876, 0, 2877, 2877, 2878, 0, 2885, -2886, + 2887, 0, 2889, -2890, 2891, 0, 2894, -2895, 2901, 0, 2904, -2905, + 2908, -2850, 2910, -2911, 2914, 0, 2916, -2917, 2946, 0, 2947, -2948, + 2964, 2962, 2965, -2966, 3006, 0, 3011, -3012, 3014, 0, 3017, 3017, + 3018, 0, 3022, -3023, 3031, 0, 3032, -3033, 3072, 0, 3077, -3078, + 3132, 0, 3133, 3133, 3134, 0, 3141, 3141, 3142, 0, 3145, 3145, + 3146, 0, 3150, -3151, 3157, 0, 3159, -3160, 3170, 0, 3172, -3173, + 3201, 0, 3204, -3205, 3260, 0, 3261, 3261, 3262, 0, 3269, 3269, + 3270, 0, 3273, 3273, 3274, 0, 3278, -3279, 3285, 0, 3287, -3288, + 3298, 0, 3300, -3301, 3315, 0, 3316, -3317, 3328, 0, 3332, -3333, + 3387, 0, 3389, 3389, 3390, 0, 3397, 3397, 3398, 0, 3401, 3401, + 3402, 0, 3406, -3407, 3415, 0, 3416, -3417, 3426, 0, 3428, -3429, + 3457, 0, 3460, -3461, 3530, 0, 3531, -3532, 3535, 0, 3541, 3541, + 3542, 0, 3543, 3543, 3544, 0, 3552, -3553, 3570, 0, 3572, -3573, + 3633, 0, 3634, 3634, 3636, 0, 3643, -3644, 3655, 0, 3663, -3664, + 3761, 0, 3762, 3762, 3764, 0, 3773, -3774, 3784, 0, 3791, -3792, + 3852, 2097154, 3854, -3855, 3864, 0, 3866, -3867, 3893, 0, 3894, 3894, + 3895, 0, 3896, 3896, 3897, 0, 3898, -3899, 3902, 0, 3904, -3905, + 3907, 2097152, 3909, -3910, 3917, 2097152, 3919, -3920, 3922, 2097154, 3924, -3925, + 3927, 2097152, 3929, -3930, 3932, 2097154, 3934, -3935, 3945, 3904, 3946, -3947, + 3953, 0, 3973, 3973, 3974, 0, 3976, -3977, 3981, 0, 3992, 3992, + 3993, 0, 4029, -4030, 4038, 0, 4039, -4040, 4134, 2097154, 4136, -4137, + 4139, 0, 4159, -4160, 4182, 0, 4186, -4187, 4190, 0, 4193, 4193, + 4194, 0, 4197, -4198, 4199, 0, 4206, -4207, 4209, 0, 4213, -4214, + 4226, 0, 4238, 4238, 4239, 0, 4240, -4241, 4250, 0, 4254, -4255, + 4348, 4316, 4349, -4350, 4957, 0, 4960, -4961, 5906, 0, 5910, -5911, + 5938, 0, 5941, -5942, 5970, 0, 5972, -5973, 6002, 0, 6004, -6005, + 6068, 0, 6100, -6101, 6109, 0, 6110, -6111, 6155, 0, 6160, -6161, + 6277, 0, 6279, -6280, 6313, 0, 6314, -6315, 6432, 0, 6444, -6445, + 6448, 0, 6460, -6461, 6679, 0, 6684, -6685, 6741, 0, 6751, 6751, + 6752, 0, 6781, -6782, 6783, 0, 6784, -6785, 6832, 0, 6863, -6864, + 6912, 0, 6917, 2097154, 6928, -6929, 6930, 2097154, 6932, -6933, 6964, 0, + 6981, -6982, 7019, 0, 7028, -7029, 7040, 0, 7043, -7044, 7073, 0, + 7086, -7087, 7142, 0, 7156, -7157, 7204, 0, 7224, -7225, 7376, 0, + 7379, 7379, 7380, 0, 7401, -7402, 7405, 0, 7406, -7407, 7412, 0, + 7413, -7414, 7415, 0, 7418, -7419, 7468, 65, 7469, 198, 7470, 66, + 7471, 7471, 7472, -69, 7474, 398, 7475, -72, 7483, 7483, 7484, 79, + 7485, 546, 7486, 80, 7487, 82, 7488, -85, 7490, 87, 7491, 97, + 7492, -593, 7494, 7426, 7495, 98, 7496, -101, 7498, 601, 7499, -604, + 7501, 103, 7502, 7502, 7503, 107, 7504, 109, 7505, 331, 7506, 111, + 7507, 596, 7508, -7447, 7510, 112, 7511, -117, 7513, 7453, 7514, 623, + 7515, 118, 7516, 7461, 7517, -947, 7520, -967, 7522, 105, 7523, 114, + 7524, -118, 7526, -947, 7528, 961, 7529, -967, 7531, -7532, 7544, 1085, + 7545, -7546, 7579, 594, 7580, 99, 7581, 597, 7582, 240, 7583, 604, + 7584, 102, 7585, 607, 7586, 609, 7587, 613, 7588, -617, 7591, 7547, + 7592, 669, 7593, 621, 7594, 7557, 7595, 671, 7596, 625, 7597, 624, + 7598, -627, 7602, 632, 7603, -643, 7605, 427, 7606, -650, 7608, 7452, + 7609, -652, 7611, 122, 7612, -657, 7615, 952, 7616, 0, 7680, 65, + 7681, 97, 7682, 66, 7683, 98, 7684, 66, 7685, 98, 7686, 66, + 7687, 98, 7688, 67, 7689, 99, 7690, 68, 7691, 100, 7692, 68, + 7693, 100, 7694, 68, 7695, 100, 7696, 68, 7697, 100, 7698, 68, + 7699, 100, 7700, 69, 7701, 101, 7702, 69, 7703, 101, 7704, 69, + 7705, 101, 7706, 69, 7707, 101, 7708, 69, 7709, 101, 7710, 70, + 7711, 102, 7712, 71, 7713, 103, 7714, 72, 7715, 104, 7716, 72, + 7717, 104, 7718, 72, 7719, 104, 7720, 72, 7721, 104, 7722, 72, + 7723, 104, 7724, 73, 7725, 105, 7726, 73, 7727, 105, 7728, 75, + 7729, 107, 7730, 75, 7731, 107, 7732, 75, 7733, 107, 7734, 76, + 7735, 108, 7736, 76, 7737, 108, 7738, 76, 7739, 108, 7740, 76, + 7741, 108, 7742, 77, 7743, 109, 7744, 77, 7745, 109, 7746, 77, + 7747, 109, 7748, 78, 7749, 110, 7750, 78, 7751, 110, 7752, 78, + 7753, 110, 7754, 78, 7755, 110, 7756, 79, 7757, 111, 7758, 79, + 7759, 111, 7760, 79, 7761, 111, 7762, 79, 7763, 111, 7764, 80, + 7765, 112, 7766, 80, 7767, 112, 7768, 82, 7769, 114, 7770, 82, + 7771, 114, 7772, 82, 7773, 114, 7774, 82, 7775, 114, 7776, 83, + 7777, 115, 7778, 83, 7779, 115, 7780, 83, 7781, 115, 7782, 83, + 7783, 115, 7784, 83, 7785, 115, 7786, 84, 7787, 116, 7788, 84, + 7789, 116, 7790, 84, 7791, 116, 7792, 84, 7793, 116, 7794, 85, + 7795, 117, 7796, 85, 7797, 117, 7798, 85, 7799, 117, 7800, 85, + 7801, 117, 7802, 85, 7803, 117, 7804, 86, 7805, 118, 7806, 86, + 7807, 118, 7808, 87, 7809, 119, 7810, 87, 7811, 119, 7812, 87, + 7813, 119, 7814, 87, 7815, 119, 7816, 87, 7817, 119, 7818, 88, + 7819, 120, 7820, 88, 7821, 120, 7822, 89, 7823, 121, 7824, 90, + 7825, 122, 7826, 90, 7827, 122, 7828, 90, 7829, 122, 7830, 104, + 7831, 116, 7832, 119, 7833, 121, 7834, 97, 7835, 115, 7836, -7837, + 7840, 65, 7841, 97, 7842, 65, 7843, 97, 7844, 65, 7845, 97, + 7846, 65, 7847, 97, 7848, 65, 7849, 97, 7850, 65, 7851, 97, + 7852, 65, 7853, 97, 7854, 65, 7855, 97, 7856, 65, 7857, 97, + 7858, 65, 7859, 97, 7860, 65, 7861, 97, 7862, 65, 7863, 97, + 7864, 69, 7865, 101, 7866, 69, 7867, 101, 7868, 69, 7869, 101, + 7870, 69, 7871, 101, 7872, 69, 7873, 101, 7874, 69, 7875, 101, + 7876, 69, 7877, 101, 7878, 69, 7879, 101, 7880, 73, 7881, 105, + 7882, 73, 7883, 105, 7884, 79, 7885, 111, 7886, 79, 7887, 111, + 7888, 79, 7889, 111, 7890, 79, 7891, 111, 7892, 79, 7893, 111, + 7894, 79, 7895, 111, 7896, 79, 7897, 111, 7898, 79, 7899, 111, + 7900, 79, 7901, 111, 7902, 79, 7903, 111, 7904, 79, 7905, 111, + 7906, 79, 7907, 111, 7908, 85, 7909, 117, 7910, 85, 7911, 117, + 7912, 85, 7913, 117, 7914, 85, 7915, 117, 7916, 85, 7917, 117, + 7918, 85, 7919, 117, 7920, 85, 7921, 117, 7922, 89, 7923, 121, + 7924, 89, 7925, 121, 7926, 89, 7927, 121, 7928, 89, 7929, 121, + 7930, -7931, 7936, 945, 7944, 913, 7952, 949, 7958, -7959, 7960, 917, + 7966, -7967, 7968, 951, 7976, 919, 7984, 953, 7992, 921, 8000, 959, + 8006, -8007, 8008, 927, 8014, -8015, 8016, 965, 8024, 8024, 8025, 933, + 8026, 8026, 8027, 933, 8028, 8028, 8029, 933, 8030, 8030, 8031, 933, + 8032, 969, 8040, 937, 8048, 945, 8050, 949, 8052, 951, 8054, 953, + 8056, 959, 8058, 965, 8060, 969, 8062, -8063, 8064, 945, 8072, 913, + 8080, 951, 8088, 919, 8096, 969, 8104, 937, 8112, 945, 8117, 8117, + 8118, 945, 8120, 913, 8125, 32, 8126, 953, 8127, 32, 8130, 951, + 8133, 8133, 8134, 951, 8136, 917, 8138, 919, 8141, 32, 8144, 953, + 8148, -8149, 8150, 953, 8152, 921, 8156, 8156, 8157, 32, 8160, 965, + 8164, 961, 8166, 965, 8168, 933, 8172, 929, 8173, 32, 8175, 96, + 8176, -8177, 8178, 969, 8181, 8181, 8182, 969, 8184, 927, 8186, 937, + 8189, 32, 8191, 8191, 8192, 32, 8203, 0, 8208, 2097152, 8211, -8212, + 8215, 32, 8216, -8217, 8228, 46, 8229, -8230, 8234, 0, 8239, 32, + 8240, -8241, 8254, 32, 8255, -8256, 8287, 32, 8288, 0, 8293, 8293, + 8294, 0, 8304, 48, 8305, 105, 8306, -8307, 8308, -53, 8314, 43, + 8315, 8722, 8316, 61, 8317, -41, 8319, 110, 8320, -49, 8330, 43, + 8331, 8722, 8332, 61, 8333, -41, 8335, 8335, 8336, 97, 8337, 101, + 8338, 111, 8339, 120, 8340, 601, 8341, 104, 8342, -108, 8346, 112, + 8347, -116, 8349, -8350, 8400, 0, 8433, -8434, 8450, 67, 8452, -8453, + 8455, 400, 8456, 8456, 8457, 70, 8458, 103, 8459, 72, 8462, 104, + 8463, 295, 8464, 73, 8466, 76, 8467, 108, 8468, 8468, 8469, 78, + 8470, -8471, 8473, -81, 8476, 82, 8478, -8479, 8484, 90, 8485, 8485, + 8486, 937, 8487, 8487, 8488, 90, 8489, 8489, 8490, 75, 8491, -66, + 8494, 8494, 8495, 101, 8496, -70, 8498, 8498, 8499, 77, 8500, 111, + 8501, -1489, 8505, 105, 8506, -8507, 8508, 960, 8509, 947, 8510, 915, + 8511, 928, 8512, 8721, 8513, -8514, 8517, 68, 8518, -101, 8520, -106, + 8522, -8523, 8543, 49, 8544, 73, 8545, -8546, 8548, 86, 8549, -8550, + 8553, 88, 8554, -8555, 8556, 76, 8557, -68, 8559, 77, 8560, 105, + 8561, -8562, 8564, 118, 8565, -8566, 8569, 120, 8570, -8571, 8572, 108, + 8573, -100, 8575, 109, 8576, -8577, 8602, 8592, 8603, 8594, 8604, -8605, + 8622, 8596, 8623, -8624, 8653, 8656, 8654, 8660, 8655, 8658, 8656, -8657, + 8708, 2097154, 8710, -8711, 8713, 2097152, 8715, 2097154, 8718, -8719, 8740, 2097154, + 8744, -8745, 8769, 8764, 8770, -8771, 8772, 2097154, 8774, 8774, 8775, 8773, + 8776, 2097152, 8779, -8780, 8800, 61, 8801, 2097154, 8804, -8805, 8813, 8781, + 8814, 60, 8815, 62, 8816, -8805, 8818, -8819, 8820, -8819, 8822, -8823, + 8824, -8823, 8826, -8827, 8832, -8827, 8834, -8835, 8836, -8835, 8838, -8839, + 8840, -8839, 8842, -8843, 8876, 8866, 8877, -8873, 8879, 8875, 8880, -8881, + 8928, -8829, 8930, -8850, 8932, -8933, 8938, -8883, 8942, -8943, 9001, -12297, + 9003, -9004, 9312, -50, 9321, -9322, 9352, -50, 9361, -9362, 9398, -66, + 9424, -98, 9450, 48, 9451, -9452, 10972, 10973, 10974, -10975, 11388, 106, + 11389, 86, 11390, -11391, 11503, 0, 11506, -11507, 11631, 11617, 11632, -11633, + 11647, 0, 11648, -11649, 11744, 0, 11776, -11777, 11935, 27597, 11936, -11937, + 12019, 40863, 12020, -12021, 12032, 19968, 12033, 20008, 12034, 20022, 12035, 20031, + 12036, 20057, 12037, 20101, 12038, 20108, 12039, 20128, 12040, 20154, 12041, 20799, + 12042, 20837, 12043, 20843, 12044, 20866, 12045, 20886, 12046, 20907, 12047, 20960, + 12048, 20981, 12049, 20992, 12050, 21147, 12051, 21241, 12052, 21269, 12053, 21274, + 12054, 21304, 12055, 21313, 12056, 21340, 12057, 21353, 12058, 21378, 12059, 21430, + 12060, 21448, 12061, 21475, 12062, 22231, 12063, 22303, 12064, 22763, 12065, 22786, + 12066, 22794, 12067, 22805, 12068, 22823, 12069, 22899, 12070, 23376, 12071, 23424, + 12072, 23544, 12073, 23567, 12074, 23586, 12075, 23608, 12076, 23662, 12077, 23665, + 12078, 24027, 12079, 24037, 12080, 24049, 12081, 24062, 12082, 24178, 12083, 24186, + 12084, 24191, 12085, 24308, 12086, 24318, 12087, 24331, 12088, 24339, 12089, 24400, + 12090, 24417, 12091, 24435, 12092, 24515, 12093, 25096, 12094, 25142, 12095, 25163, + 12096, 25903, 12097, 25908, 12098, 25991, 12099, 26007, 12100, 26020, 12101, 26041, + 12102, 26080, 12103, 26085, 12104, 26352, 12105, 26376, 12106, 26408, 12107, 27424, + 12108, 27490, 12109, 27513, 12110, 27571, 12111, 27595, 12112, 27604, 12113, 27611, + 12114, 27663, 12115, 27668, 12116, 27700, 12117, 28779, 12118, 29226, 12119, 29238, + 12120, 29243, 12121, 29247, 12122, 29255, 12123, 29273, 12124, 29275, 12125, 29356, + 12126, 29572, 12127, 29577, 12128, 29916, 12129, 29926, 12130, 29976, 12131, 29983, + 12132, 29992, 12133, 30000, 12134, 30091, 12135, 30098, 12136, 30326, 12137, 30333, + 12138, 30382, 12139, 30399, 12140, 30446, 12141, 30683, 12142, 30690, 12143, 30707, + 12144, 31034, 12145, 31160, 12146, 31166, 12147, 31348, 12148, 31435, 12149, 31481, + 12150, 31859, 12151, 31992, 12152, 32566, 12153, 32593, 12154, 32650, 12155, 32701, + 12156, 32769, 12157, 32780, 12158, 32786, 12159, 32819, 12160, 32895, 12161, 32905, + 12162, 33251, 12163, 33258, 12164, 33267, 12165, 33276, 12166, 33292, 12167, 33307, + 12168, 33311, 12169, 33390, 12170, 33394, 12171, 33400, 12172, 34381, 12173, 34411, + 12174, 34880, 12175, 34892, 12176, 34915, 12177, 35198, 12178, 35211, 12179, 35282, + 12180, 35328, 12181, 35895, 12182, 35910, 12183, 35925, 12184, 35960, 12185, 35997, + 12186, 36196, 12187, 36208, 12188, 36275, 12189, 36523, 12190, 36554, 12191, 36763, + 12192, 36784, 12193, 36789, 12194, 37009, 12195, 37193, 12196, 37318, 12197, 37324, + 12198, 37329, 12199, 38263, 12200, 38272, 12201, 38428, 12202, 38582, 12203, 38585, + 12204, 38632, 12205, 38737, 12206, 38750, 12207, 38754, 12208, 38761, 12209, 38859, + 12210, 38893, 12211, 38899, 12212, 38913, 12213, 39080, 12214, 39131, 12215, 39135, + 12216, 39318, 12217, 39321, 12218, 39340, 12219, 39592, 12220, 39640, 12221, 39647, + 12222, 39717, 12223, 39727, 12224, 39730, 12225, 39740, 12226, 39770, 12227, 40165, + 12228, 40565, 12229, 40575, 12230, 40613, 12231, 40635, 12232, 40643, 12233, 40653, + 12234, 40657, 12235, 40697, 12236, 40701, 12237, 40718, 12238, 40723, 12239, 40736, + 12240, 40763, 12241, 40778, 12242, 40786, 12243, 40845, 12244, 40860, 12245, 40864, + 12246, -12247, 12288, 32, 12289, -12290, 12330, 0, 12336, -12337, 12342, 12306, + 12343, 12343, 12344, 21313, 12345, -21317, 12347, -12348, 12364, 2097154, 12388, 2097152, + 12395, -12396, 12400, 12399, 12402, 12402, 12405, 12405, 12408, 12408, 12411, 12411, + 12414, -12415, 12436, 12358, 12437, -12438, 12441, 0, 12443, 32, 12445, 2097154, + 12448, -12449, 12460, 2097154, 12484, 2097152, 12491, -12492, 12496, 12495, 12498, 12498, + 12501, 12501, 12504, 12504, 12507, 12507, 12510, -12511, 12532, 12454, 12533, -12534, + 12535, -12528, 12539, 12539, 12540, 0, 12541, 2097154, 12544, -12545, 12593, -4353, + 12595, 4522, 12596, 4354, 12597, -4525, 12599, -4356, 12602, -4529, 12608, 4378, + 12609, -4359, 12612, 4385, 12613, -4362, 12623, -4450, 12644, 4448, 12645, -4373, + 12647, -4552, 12649, 4556, 12650, 4558, 12651, 4563, 12652, 4567, 12653, 4569, + 12654, 4380, 12655, 4573, 12656, 4575, 12657, -4382, 12659, 4384, 12660, -4387, + 12662, 4391, 12663, 4393, 12664, -4396, 12669, 4402, 12670, 4406, 12671, 4416, + 12672, 4423, 12673, 4428, 12674, -4594, 12676, -4440, 12679, -4485, 12681, 4488, + 12682, -4498, 12684, 4500, 12685, 4510, 12686, 4513, 12687, -12688, 12690, 19968, + 12691, 20108, 12692, 19977, 12693, 22235, 12694, 19978, 12695, 20013, 12696, 19979, + 12697, 30002, 12698, 20057, 12699, 19993, 12700, 19969, 12701, 22825, 12702, 22320, + 12703, 20154, 12704, -12705, 12868, 21839, 12869, 24188, 12870, 25991, 12871, 31631, + 12872, -12873, 12896, 4352, 12897, -4355, 12899, -4358, 12902, 4361, 12903, -4364, + 12905, -4367, 12910, -12911, 12928, 19968, 12929, 20108, 12930, 19977, 12931, 22235, + 12932, 20116, 12933, 20845, 12934, 19971, 12935, 20843, 12936, 20061, 12937, 21313, + 12938, 26376, 12939, 28779, 12940, 27700, 12941, 26408, 12942, 37329, 12943, 22303, + 12944, 26085, 12945, 26666, 12946, 26377, 12947, 31038, 12948, 21517, 12949, 29305, + 12950, 36001, 12951, 31069, 12952, 21172, 12953, 31192, 12954, 30007, 12955, 22899, + 12956, 36969, 12957, 20778, 12958, 21360, 12959, 27880, 12960, 38917, 12961, 20241, + 12962, 20889, 12963, 27491, 12964, 19978, 12965, 20013, 12966, 19979, 12967, 24038, + 12968, 21491, 12969, 21307, 12970, 23447, 12971, 23398, 12972, 30435, 12973, 20225, + 12974, 36039, 12975, 21332, 12976, 22812, 12977, -12978, 13008, 12450, 13009, 12452, + 13010, 12454, 13011, 12456, 13012, -12459, 13014, 12461, 13015, 12463, 13016, 12465, + 13017, 12467, 13018, 12469, 13019, 12471, 13020, 12473, 13021, 12475, 13022, 12477, + 13023, 12479, 13024, 12481, 13025, 12484, 13026, 12486, 13027, 12488, 13028, -12491, + 13034, 12498, 13035, 12501, 13036, 12504, 13037, 12507, 13038, -12511, 13043, 12516, + 13044, 12518, 13045, -12521, 13051, -12528, 13055, -13056, 42607, 0, 42611, 42611, + 42612, 0, 42622, -42623, 42652, 1098, 42653, 1100, 42654, 0, 42656, -42657, + 42736, 0, 42738, -42739, 42864, 2097154, 42866, -42867, 42994, 67, 42995, 70, + 42996, 81, 42997, -42998, 43000, 294, 43001, 339, 43002, -43003, 43010, 0, + 43011, -43012, 43014, 0, 43015, -43016, 43019, 0, 43020, -43021, 43043, 0, + 43048, -43049, 43052, 0, 43053, -43054, 43136, 0, 43138, -43139, 43188, 0, + 43206, -43207, 43232, 0, 43250, -43251, 43263, 0, 43264, -43265, 43302, 0, + 43310, -43311, 43335, 0, 43348, -43349, 43392, 0, 43396, -43397, 43443, 0, + 43457, -43458, 43493, 0, 43494, -43495, 43561, 0, 43575, -43576, 43587, 0, + 43588, -43589, 43596, 0, 43598, -43599, 43643, 0, 43646, -43647, 43696, 0, + 43697, 43697, 43698, 0, 43701, -43702, 43703, 0, 43705, -43706, 43710, 0, + 43712, 43712, 43713, 0, 43714, -43715, 43755, 0, 43760, -43761, 43765, 0, + 43767, -43768, 43868, 42791, 43869, 43831, 43870, 619, 43871, 43858, 43872, -43873, + 43881, 653, 43882, -43883, 44003, 0, 44011, 44011, 44012, 0, 44014, -44015, + 55296, 0, 57344, -57345, 63744, 35912, 63745, 26356, 63746, 36554, 63747, 36040, + 63748, 28369, 63749, 20018, 63750, 21477, 63751, 40860, 63753, 22865, 63754, 37329, + 63755, 21895, 63756, 22856, 63757, 25078, 63758, 30313, 63759, 32645, 63760, 34367, + 63761, 34746, 63762, 35064, 63763, 37007, 63764, 27138, 63765, 27931, 63766, 28889, + 63767, 29662, 63768, 33853, 63769, 37226, 63770, 39409, 63771, 20098, 63772, 21365, + 63773, 27396, 63774, 29211, 63775, 34349, 63776, 40478, 63777, 23888, 63778, 28651, + 63779, 34253, 63780, 35172, 63781, 25289, 63782, 33240, 63783, 34847, 63784, 24266, + 63785, 26391, 63786, 28010, 63787, 29436, 63788, 37070, 63789, 20358, 63790, 20919, + 63791, 21214, 63792, 25796, 63793, 27347, 63794, 29200, 63795, 30439, 63796, 32769, + 63797, 34310, 63798, 34396, 63799, 36335, 63800, 38706, 63801, 39791, 63802, 40442, + 63803, 30860, 63804, 31103, 63805, 32160, 63806, 33737, 63807, 37636, 63808, 40575, + 63809, 35542, 63810, 22751, 63811, 24324, 63812, 31840, 63813, 32894, 63814, 29282, + 63815, 30922, 63816, 36034, 63817, 38647, 63818, 22744, 63819, 23650, 63820, 27155, + 63821, 28122, 63822, 28431, 63823, 32047, 63824, 32311, 63825, 38475, 63826, 21202, + 63827, 32907, 63828, 20956, 63829, 20940, 63830, 31260, 63831, 32190, 63832, 33777, + 63833, 38517, 63834, 35712, 63835, 25295, 63836, 27138, 63837, 35582, 63838, 20025, + 63839, 23527, 63840, 24594, 63841, 29575, 63842, 30064, 63843, 21271, 63844, 30971, + 63845, 20415, 63846, 24489, 63847, 19981, 63848, 27852, 63849, 25976, 63850, 32034, + 63851, 21443, 63852, 22622, 63853, 30465, 63854, 33865, 63855, 35498, 63856, 27578, + 63857, 36784, 63858, 27784, 63859, 25342, 63860, 33509, 63861, 25504, 63862, 30053, + 63863, 20142, 63864, 20841, 63865, 20937, 63866, 26753, 63867, 31975, 63868, 33391, + 63869, 35538, 63870, 37327, 63871, 21237, 63872, 21570, 63873, 22899, 63874, 24300, + 63875, 26053, 63876, 28670, 63877, 31018, 63878, 38317, 63879, 39530, 63880, 40599, + 63881, 40654, 63882, 21147, 63883, 26310, 63884, 27511, 63885, 36706, 63886, 24180, + 63887, 24976, 63888, 25088, 63889, 25754, 63890, 28451, 63891, 29001, 63892, 29833, + 63893, 31178, 63894, 32244, 63895, 32879, 63896, 36646, 63897, 34030, 63898, 36899, + 63899, 37706, 63900, 21015, 63901, 21155, 63902, 21693, 63903, 28872, 63904, 35010, + 63905, 35498, 63906, 24265, 63907, 24565, 63908, 25467, 63909, 27566, 63910, 31806, + 63911, 29557, 63912, 20196, 63913, 22265, 63914, 23527, 63915, 23994, 63916, 24604, + 63917, 29618, 63918, 29801, 63919, 32666, 63920, 32838, 63921, 37428, 63922, 38646, + 63923, 38728, 63924, 38936, 63925, 20363, 63926, 31150, 63927, 37300, 63928, 38584, + 63929, 24801, 63930, 20102, 63931, 20698, 63932, 23534, 63933, 23615, 63934, 26009, + 63935, 27138, 63936, 29134, 63937, 30274, 63938, 34044, 63939, 36988, 63940, 40845, + 63941, 26248, 63942, 38446, 63943, 21129, 63944, 26491, 63945, 26611, 63946, 27969, + 63947, 28316, 63948, 29705, 63949, 30041, 63950, 30827, 63951, 32016, 63952, 39006, + 63953, 20845, 63954, 25134, 63955, 38520, 63956, 20523, 63957, 23833, 63958, 28138, + 63959, 36650, 63960, 24459, 63961, 24900, 63962, 26647, 63963, 29575, 63964, 38534, + 63965, 21033, 63966, 21519, 63967, 23653, 63968, 26131, 63969, 26446, 63970, 26792, + 63971, 27877, 63972, 29702, 63973, 30178, 63974, 32633, 63975, 35023, 63976, 35041, + 63977, 37324, 63978, 38626, 63979, 21311, 63980, 28346, 63981, 21533, 63982, 29136, + 63983, 29848, 63984, 34298, 63985, 38563, 63986, 40023, 63987, 40607, 63988, 26519, + 63989, 28107, 63990, 33256, 63991, 31435, 63992, 31520, 63993, 31890, 63994, 29376, + 63995, 28825, 63996, 35672, 63997, 20160, 63998, 33590, 63999, 21050, 64000, 20999, + 64001, 24230, 64002, 25299, 64003, 31958, 64004, 23429, 64005, 27934, 64006, 26292, + 64007, 36667, 64008, 34892, 64009, 38477, 64010, 35211, 64011, 24275, 64012, 20800, + 64013, 21952, 64014, -64015, 64016, 22618, 64017, 64017, 64018, 26228, 64019, -64020, + 64021, 20958, 64022, 29482, 64023, 30410, 64024, 31036, 64025, 31070, 64026, 31077, + 64027, 31119, 64028, 38742, 64029, 31934, 64030, 32701, 64031, 64031, 64032, 34322, + 64033, 64033, 64034, 35576, 64035, -64036, 64037, 36920, 64038, 37117, 64039, -64040, + 64042, 39151, 64043, 39164, 64044, 39208, 64045, 40372, 64046, 37086, 64047, 38583, + 64048, 20398, 64049, 20711, 64050, 20813, 64051, 21193, 64052, 21220, 64053, 21329, + 64054, 21917, 64055, 22022, 64056, 22120, 64057, 22592, 64058, 22696, 64059, 23652, + 64060, 23662, 64061, 24724, 64062, 24936, 64063, 24974, 64064, 25074, 64065, 25935, + 64066, 26082, 64067, 26257, 64068, 26757, 64069, 28023, 64070, 28186, 64071, 28450, + 64072, 29038, 64073, 29227, 64074, 29730, 64075, 30865, 64076, 31038, 64077, 31049, + 64078, 31048, 64079, 31056, 64080, 31062, 64081, 31069, 64082, -31118, 64084, 31296, + 64085, 31361, 64086, 31680, 64087, 32244, 64088, 32265, 64089, 32321, 64090, 32626, + 64091, 32773, 64092, 33261, 64093, 33401, 64095, 33879, 64096, 35088, 64097, 35222, + 64098, 35585, 64099, 35641, 64100, 36051, 64101, 36104, 64102, 36790, 64103, 36920, + 64104, 38627, 64105, 38911, 64106, 38971, 64107, 24693, 64108, 148206, 64109, 33304, + 64110, -64111, 64112, 20006, 64113, 20917, 64114, 20840, 64115, 20352, 64116, 20805, + 64117, 20864, 64118, 21191, 64119, 21242, 64120, 21917, 64121, 21845, 64122, 21913, + 64123, 21986, 64124, 22618, 64125, 22707, 64126, 22852, 64127, 22868, 64128, 23138, + 64129, 23336, 64130, 24274, 64131, 24281, 64132, 24425, 64133, 24493, 64134, 24792, + 64135, 24910, 64136, 24840, 64137, 24974, 64138, 24928, 64139, 25074, 64140, 25140, + 64141, 25540, 64142, 25628, 64143, 25682, 64144, 25942, 64145, 26228, 64146, 26391, + 64147, 26395, 64148, 26454, 64149, 27513, 64150, 27578, 64151, 27969, 64152, 28379, + 64153, 28363, 64154, 28450, 64155, 28702, 64156, 29038, 64157, 30631, 64158, 29237, + 64159, 29359, 64160, 29482, 64161, 29809, 64162, 29958, 64163, 30011, 64164, 30237, + 64165, 30239, 64166, 30410, 64167, 30427, 64168, 30452, 64169, 30538, 64170, 30528, + 64171, 30924, 64172, 31409, 64173, 31680, 64174, 31867, 64175, 32091, 64176, 32244, + 64177, 32574, 64178, 32773, 64179, 33618, 64180, 33775, 64181, 34681, 64182, 35137, + 64183, 35206, 64184, 35222, 64185, 35519, 64186, 35576, 64187, 35531, 64188, 35585, + 64189, 35582, 64190, 35565, 64191, 35641, 64192, 35722, 64193, 36104, 64194, 36664, + 64195, 36978, 64196, 37273, 64197, 37494, 64198, 38524, 64199, 38627, 64200, 38742, + 64201, 38875, 64202, 38911, 64203, 38923, 64204, 38971, 64205, 39698, 64206, 40860, + 64207, 141386, 64208, 141380, 64209, 144341, 64210, 15261, 64211, 16408, 64212, 16441, + 64213, 152137, 64214, 154832, 64215, 163539, 64216, 40771, 64217, 40846, 64218, -64219, + 64285, 1497, 64286, 0, 64287, 1522, 64288, 1506, 64289, 1488, 64290, -1492, + 64292, -1500, 64295, 1512, 64296, 1514, 64297, 43, 64298, 1513, 64302, 1488, + 64305, -1490, 64311, 64311, 64312, -1497, 64317, 64317, 64318, 1502, 64319, 64319, + 64320, -1505, 64322, 64322, 64323, -1508, 64325, 64325, 64326, -1511, 64331, 1493, + 64332, 1489, 64333, 1499, 64334, 1508, 64335, 64335, 64336, 1649, 64338, 1659, + 64342, 1662, 64346, 1664, 64350, 1658, 64354, 1663, 64358, 1657, 64362, 1700, + 64366, 1702, 64370, 1668, 64374, 1667, 64378, 1670, 64382, 1671, 64386, 1677, + 64388, 1676, 64390, 1678, 64392, 1672, 64394, 1688, 64396, 1681, 64398, 1705, + 64402, 1711, 64406, 1715, 64410, 1713, 64414, 1722, 64416, 1723, 64420, 1749, + 64422, 1729, 64426, 1726, 64430, 1746, 64434, -64435, 64467, 1709, 64471, 1735, + 64473, 1734, 64475, 1736, 64477, 1655, 64478, 1739, 64480, 1733, 64482, 1737, + 64484, 1744, 64488, 1609, 64490, -64491, 64508, 1740, 64512, -64513, 64603, -1585, + 64605, 1609, 64606, 32, 64612, -64613, 64656, 1609, 64657, -64658, 64729, 1607, + 64730, -64731, 64754, 1600, 64757, -64758, 64828, 1575, 64830, -64831, 65024, 0, + 65040, 44, 65041, -12290, 65043, -59, 65045, 33, 65046, 63, 65047, -12311, + 65049, 8230, 65050, -65051, 65056, 0, 65072, 8229, 65073, 8212, 65074, 8211, + 65075, 95, 65077, -41, 65079, 123, 65080, 125, 65081, -12309, 65083, -12305, + 65085, -12299, 65087, -12297, 65089, -12301, 65093, -65094, 65095, 91, 65096, 93, + 65097, 32, 65101, 95, 65104, 44, 65105, 12289, 65106, 46, 65107, 65107, + 65108, 59, 65109, 58, 65110, 63, 65111, 33, 65112, 8212, 65113, -41, + 65115, 123, 65116, 125, 65117, -12309, 65119, 35, 65120, 38, 65121, -43, + 65123, 45, 65124, 60, 65125, 62, 65126, 61, 65127, 65127, 65128, 92, + 65129, -37, 65131, 64, 65132, -65133, 65136, 32, 65137, 1600, 65138, 32, + 65139, 65139, 65140, 32, 65141, 65141, 65142, 32, 65143, 1600, 65144, 32, + 65145, 1600, 65146, 32, 65147, 1600, 65148, 32, 65149, 1600, 65150, 32, + 65151, 1600, 65152, 1569, 65153, 1575, 65157, 1608, 65159, 1575, 65161, 1610, + 65165, 1575, 65167, 1576, 65171, 1577, 65173, 1578, 65177, 1579, 65181, 1580, + 65185, 1581, 65189, 1582, 65193, 1583, 65195, 1584, 65197, 1585, 65199, 1586, + 65201, 1587, 65205, 1588, 65209, 1589, 65213, 1590, 65217, 1591, 65221, 1592, + 65225, 1593, 65229, 1594, 65233, 1601, 65237, 1602, 65241, 1603, 65245, 1604, + 65249, 1605, 65253, 1606, 65257, 1607, 65261, 1608, 65263, 1609, 65265, 1610, + 65269, -65270, 65279, 0, 65280, 65280, 65281, -34, 65375, -10630, 65377, 12290, + 65378, -12301, 65380, 12289, 65381, 12539, 65382, 12530, 65383, 12449, 65384, 12451, + 65385, 12453, 65386, 12455, 65387, 12457, 65388, 12515, 65389, 12517, 65390, 12519, + 65391, 12483, 65392, 65392, 65393, 12450, 65394, 12452, 65395, 12454, 65396, 12456, + 65397, -12459, 65399, 12461, 65400, 12463, 65401, 12465, 65402, 12467, 65403, 12469, + 65404, 12471, 65405, 12473, 65406, 12475, 65407, 12477, 65408, 12479, 65409, 12481, + 65410, 12484, 65411, 12486, 65412, 12488, 65413, -12491, 65419, 12498, 65420, 12501, + 65421, 12504, 65422, 12507, 65423, -12511, 65428, 12516, 65429, 12518, 65430, -12521, + 65436, 12527, 65437, 12531, 65438, -65439, 65440, 4448, 65441, -4353, 65443, 4522, + 65444, 4354, 65445, -4525, 65447, -4356, 65450, -4529, 65456, 4378, 65457, -4359, + 65460, 4385, 65461, -4362, 65471, -65472, 65474, -4450, 65480, -65481, 65482, -4456, + 65488, -65489, 65490, -4462, 65496, -65497, 65498, -4468, 65501, -65502, 65504, -163, + 65506, 172, 65507, 32, 65508, 166, 65509, 165, 65510, 8361, 65511, 65511, + 65512, 9474, 65513, -8593, 65517, 9632, 65518, 9675, 65519, -65520, 65529, 0, + 65532, -65533, 66045, 0, 66046, -66047, 66272, 0, 66273, -66274, 66422, 0, + 66427, -66428, 67457, -721, 67459, 230, 67460, 665, 67461, 595, 67462, 67462, + 67463, 675, 67464, 43878, 67465, 677, 67466, 676, 67467, -599, 67469, 7569, + 67470, 600, 67471, 606, 67472, 681, 67473, 612, 67474, 610, 67475, 608, + 67476, 667, 67477, 295, 67478, 668, 67479, 615, 67480, 644, 67481, -683, + 67483, 620, 67484, 122628, 67485, 42894, 67486, 622, 67487, 122629, 67488, 654, + 67489, 122630, 67490, 248, 67491, -631, 67493, 113, 67494, 634, 67495, 122632, + 67496, -638, 67498, 640, 67499, 680, 67500, 678, 67501, 43879, 67502, 679, + 67503, 648, 67504, 11377, 67505, 67505, 67506, 655, 67507, -674, 67509, 664, + 67510, -449, 67513, 122634, 67514, 122654, 67515, -67516, 68097, 0, 68100, 68100, + 68101, 0, 68103, -68104, 68108, 0, 68112, -68113, 68152, 0, 68155, -68156, + 68159, 0, 68160, -68161, 68325, 0, 68327, -68328, 68900, 0, 68904, -68905, + 69291, 0, 69293, -69294, 69373, 0, 69376, -69377, 69446, 0, 69457, -69458, + 69506, 0, 69510, -69511, 69632, 0, 69635, -69636, 69688, 0, 69703, -69704, + 69744, 0, 69745, -69746, 69747, 0, 69749, -69750, 69759, 0, 69763, -69764, + 69786, 2097154, 69790, -69791, 69803, 69797, 69804, -69805, 69808, 0, 69819, -69820, + 69821, 0, 69822, -69823, 69826, 0, 69827, -69828, 69837, 0, 69838, -69839, + 69888, 0, 69891, -69892, 69927, 0, 69941, -69942, 69957, 0, 69959, -69960, + 70003, 0, 70004, -70005, 70016, 0, 70019, -70020, 70067, 0, 70081, -70082, + 70089, 0, 70093, 70093, 70094, 0, 70096, -70097, 70188, 0, 70200, -70201, + 70206, 0, 70207, -70208, 70209, 0, 70210, -70211, 70367, 0, 70379, -70380, + 70400, 0, 70404, -70405, 70459, 0, 70461, 70461, 70462, 0, 70469, -70470, + 70471, 0, 70473, -70474, 70475, 0, 70478, -70479, 70487, 0, 70488, -70489, + 70498, 0, 70500, -70501, 70502, 0, 70509, -70510, 70512, 0, 70517, -70518, + 70709, 0, 70727, -70728, 70750, 0, 70751, -70752, 70832, 0, 70852, -70853, + 71087, 0, 71094, -71095, 71096, 0, 71105, -71106, 71132, 0, 71134, -71135, + 71216, 0, 71233, -71234, 71339, 0, 71352, -71353, 71453, 0, 71468, -71469, + 71724, 0, 71739, -71740, 71984, 0, 71990, 71990, 71991, 0, 71993, -71994, + 71995, 0, 71999, 71999, 72000, 0, 72001, 72001, 72002, 0, 72004, -72005, + 72145, 0, 72152, -72153, 72154, 0, 72161, -72162, 72164, 0, 72165, -72166, + 72193, 0, 72203, -72204, 72243, 0, 72250, 72250, 72251, 0, 72255, -72256, + 72263, 0, 72264, -72265, 72273, 0, 72284, -72285, 72330, 0, 72346, -72347, + 72751, 0, 72759, 72759, 72760, 0, 72768, -72769, 72850, 0, 72872, 72872, + 72873, 0, 72887, -72888, 73009, 0, 73015, -73016, 73018, 0, 73019, 73019, + 73020, 0, 73022, 73022, 73023, 0, 73030, 73030, 73031, 0, 73032, -73033, + 73098, 0, 73103, 73103, 73104, 0, 73106, 73106, 73107, 0, 73112, -73113, + 73459, 0, 73463, -73464, 73472, 0, 73474, 73474, 73475, 0, 73476, -73477, + 73524, 0, 73531, -73532, 73534, 0, 73539, -73540, 78896, 0, 78913, -78914, + 78919, 0, 78934, -78935, 92912, 0, 92917, -92918, 92976, 0, 92983, -92984, + 94031, 0, 94032, 94032, 94033, 0, 94088, -94089, 94095, 0, 94099, -94100, + 94180, 0, 94181, -94182, 94192, 0, 94194, -94195, 113821, 0, 113823, 113823, + 113824, 0, 113828, -113829, 118528, 0, 118574, -118575, 118576, 0, 118599, -118600, + 119134, -119128, 119136, 119128, 119141, 0, 119146, -119147, 119149, 0, 119171, -119172, + 119173, 0, 119180, -119181, 119210, 0, 119214, -119215, 119227, -119226, 119229, -119226, + 119231, -119226, 119233, -119234, 119362, 0, 119365, -119366, 119808, -66, 119834, -98, + 119860, -66, 119886, -98, 119893, 119893, 119894, -106, 119912, -66, 119938, -98, + 119964, 65, 119965, 119965, 119966, -68, 119968, -119969, 119970, 71, 119971, -119972, + 119973, -75, 119975, -119976, 119977, -79, 119981, 119981, 119982, -84, 119990, -98, + 119994, 119994, 119995, 102, 119996, 119996, 119997, -105, 120004, 120004, 120005, -113, + 120016, -66, 120042, -98, 120068, -66, 120070, 120070, 120071, -69, 120075, -120076, + 120077, -75, 120085, 120085, 120086, -84, 120093, 120093, 120094, -98, 120120, -66, + 120122, 120122, 120123, -69, 120127, 120127, 120128, -74, 120133, 120133, 120134, 79, + 120135, -120136, 120138, -84, 120145, 120145, 120146, -98, 120172, -66, 120198, -98, + 120224, -66, 120250, -98, 120276, -66, 120302, -98, 120328, -66, 120354, -98, + 120380, -66, 120406, -98, 120432, -66, 120458, -98, 120484, 305, 120485, 567, + 120486, -120487, 120488, -914, 120505, 920, 120506, -932, 120513, 8711, 120514, -946, + 120539, 8706, 120540, 949, 120541, 952, 120542, 954, 120543, 966, 120544, 961, + 120545, 960, 120546, -914, 120563, 920, 120564, -932, 120571, 8711, 120572, -946, + 120597, 8706, 120598, 949, 120599, 952, 120600, 954, 120601, 966, 120602, 961, + 120603, 960, 120604, -914, 120621, 920, 120622, -932, 120629, 8711, 120630, -946, + 120655, 8706, 120656, 949, 120657, 952, 120658, 954, 120659, 966, 120660, 961, + 120661, 960, 120662, -914, 120679, 920, 120680, -932, 120687, 8711, 120688, -946, + 120713, 8706, 120714, 949, 120715, 952, 120716, 954, 120717, 966, 120718, 961, + 120719, 960, 120720, -914, 120737, 920, 120738, -932, 120745, 8711, 120746, -946, + 120771, 8706, 120772, 949, 120773, 952, 120774, 954, 120775, 966, 120776, 961, + 120777, 960, 120778, -989, 120780, -120781, 120782, -49, 120792, -49, 120802, -49, + 120812, -49, 120822, -49, 120832, -120833, 121344, 0, 121399, -121400, 121403, 0, + 121453, -121454, 121461, 0, 121462, -121463, 121476, 0, 121477, -121478, 121499, 0, + 121504, 121504, 121505, 0, 121520, -121521, 122880, 0, 122887, 122887, 122888, 0, + 122905, -122906, 122907, 0, 122914, 122914, 122915, 0, 122917, 122917, 122918, 0, + 122923, -122924, 122928, -1073, 122937, -1083, 122940, -1087, 122951, 1099, 122952, -1102, + 122954, 42633, 122955, 1241, 122956, 1110, 122957, 1112, 122958, 1257, 122959, 1199, + 122960, 1231, 122961, -1073, 122970, -1083, 122972, -1087, 122974, 1089, 122975, -1092, + 122981, -1099, 122983, 1169, 122984, 1110, 122985, 1109, 122986, 1119, 122987, 1195, + 122988, 42577, 122989, 1201, 122990, -122991, 123023, 0, 123024, -123025, 123184, 0, + 123191, -123192, 123566, 0, 123567, -123568, 123628, 0, 123632, -123633, 124140, 0, + 124144, -124145, 125136, 0, 125143, -125144, 125252, 0, 125259, -125260, 126464, -1576, + 126466, 1580, 126467, 1583, 126468, 126468, 126469, 1608, 126470, 1586, 126471, 1581, + 126472, 1591, 126473, 1610, 126474, -1604, 126478, 1587, 126479, 1593, 126480, 1601, + 126481, 1589, 126482, 1602, 126483, 1585, 126484, 1588, 126485, -1579, 126487, 1582, + 126488, 1584, 126489, 1590, 126490, 1592, 126491, 1594, 126492, 1646, 126493, 1722, + 126494, 1697, 126495, 1647, 126496, 126496, 126497, 1576, 126498, 1580, 126499, 126499, + 126500, 1607, 126501, -126502, 126503, 1581, 126504, 126504, 126505, 1610, 126506, -1604, + 126510, 1587, 126511, 1593, 126512, 1601, 126513, 1589, 126514, 1602, 126515, 126515, + 126516, 1588, 126517, -1579, 126519, 1582, 126520, 126520, 126521, 1590, 126522, 126522, + 126523, 1594, 126524, -126525, 126530, 1580, 126531, -126532, 126535, 1581, 126536, 126536, + 126537, 1610, 126538, 126538, 126539, 1604, 126540, 126540, 126541, 1606, 126542, 1587, + 126543, 1593, 126544, 126544, 126545, 1589, 126546, 1602, 126547, 126547, 126548, 1588, + 126549, -126550, 126551, 1582, 126552, 126552, 126553, 1590, 126554, 126554, 126555, 1594, + 126556, 126556, 126557, 1722, 126558, 126558, 126559, 1647, 126560, 126560, 126561, 1576, + 126562, 1580, 126563, 126563, 126564, 1607, 126565, -126566, 126567, 1581, 126568, 1591, + 126569, 1610, 126570, 1603, 126571, 126571, 126572, -1606, 126574, 1587, 126575, 1593, + 126576, 1601, 126577, 1589, 126578, 1602, 126579, 126579, 126580, 1588, 126581, -1579, + 126583, 1582, 126584, 126584, 126585, 1590, 126586, 1592, 126587, 1594, 126588, 1646, + 126589, 126589, 126590, 1697, 126591, 126591, 126592, -1576, 126594, 1580, 126595, 1583, + 126596, -1608, 126598, 1586, 126599, 1581, 126600, 1591, 126601, 1610, 126602, 126602, + 126603, -1605, 126606, 1587, 126607, 1593, 126608, 1601, 126609, 1589, 126610, 1602, + 126611, 1585, 126612, 1588, 126613, -1579, 126615, 1582, 126616, 1584, 126617, 1590, + 126618, 1592, 126619, 1594, 126620, -126621, 126625, 1576, 126626, 1580, 126627, 1583, + 126628, 126628, 126629, 1608, 126630, 1586, 126631, 1581, 126632, 1591, 126633, 1610, + 126634, 126634, 126635, -1605, 126638, 1587, 126639, 1593, 126640, 1601, 126641, 1589, + 126642, 1602, 126643, 1585, 126644, 1588, 126645, -1579, 126647, 1582, 126648, 1584, + 126649, 1590, 126650, 1592, 126651, 1594, 126652, -126653, 127232, 48, 127234, -50, + 127243, -127244, 127275, 67, 127276, 82, 127277, -127278, 127280, -66, 127306, -127307, + 127490, 12469, 127491, -127492, 127504, 25163, 127505, 23383, 127506, 21452, 127507, 12486, + 127508, 20108, 127509, 22810, 127510, 35299, 127511, 22825, 127512, 20132, 127513, 26144, + 127514, 28961, 127515, 26009, 127516, 21069, 127517, 24460, 127518, 20877, 127519, 26032, + 127520, 21021, 127521, 32066, 127522, 29983, 127523, 36009, 127524, 22768, 127525, 21561, + 127526, 28436, 127527, 25237, 127528, 25429, 127529, 19968, 127530, 19977, 127531, 36938, + 127532, 24038, 127533, 20013, 127534, 21491, 127535, 25351, 127536, 36208, 127537, 25171, + 127538, 31105, 127539, 31354, 127540, 21512, 127541, 28288, 127542, 26377, 127543, 26376, + 127544, 30003, 127545, 21106, 127546, 21942, 127547, 37197, 127548, -127549, 127568, 24471, + 127569, 21487, 127570, -127571, 130032, -49, 130042, -130043, 194560, 20029, 194561, 20024, + 194562, 20033, 194563, 131362, 194564, 20320, 194565, 20398, 194566, 20411, 194567, 20482, + 194568, 20602, 194569, 20633, 194570, 20711, 194571, 20687, 194572, 13470, 194573, 132666, + 194574, 20813, 194575, 20820, 194576, 20836, 194577, 20855, 194578, 132380, 194579, 13497, + 194580, 20839, 194581, 20877, 194582, 132427, 194583, 20887, 194584, 20900, 194585, 20172, + 194586, 20908, 194587, 20917, 194588, 168415, 194589, 20981, 194590, 20995, 194591, 13535, + 194592, 21051, 194593, 21062, 194594, 21106, 194595, 21111, 194596, 13589, 194597, 21191, + 194598, 21193, 194599, 21220, 194600, 21242, 194601, -21254, 194603, 21271, 194604, 21321, + 194605, 21329, 194606, 21338, 194607, 21363, 194608, 21373, 194609, 21375, 194612, 133676, + 194613, 28784, 194614, 21450, 194615, 21471, 194616, 133987, 194617, 21483, 194618, 21489, + 194619, 21510, 194620, 21662, 194621, 21560, 194622, 21576, 194623, 21608, 194624, 21666, + 194625, 21750, 194626, 21776, 194627, 21843, 194628, 21859, 194629, 21892, 194631, 21913, + 194632, 21931, 194633, 21939, 194634, 21954, 194635, 22294, 194636, 22022, 194637, 22295, + 194638, 22097, 194639, 22132, 194640, 20999, 194641, 22766, 194642, 22478, 194643, 22516, + 194644, 22541, 194645, 22411, 194646, 22578, 194647, 22577, 194648, 22700, 194649, 136420, + 194650, 22770, 194651, 22775, 194652, 22790, 194653, 22810, 194654, 22818, 194655, 22882, + 194656, 136872, 194657, 136938, 194658, 23020, 194659, 23067, 194660, 23079, 194661, 23000, + 194662, 23142, 194663, 14062, 194664, 14076, 194665, 23304, 194666, 23358, 194668, 137672, + 194669, 23491, 194670, 23512, 194671, 23527, 194672, 23539, 194673, 138008, 194674, 23551, + 194675, 23558, 194676, 24403, 194677, 23586, 194678, 14209, 194679, 23648, 194680, 23662, + 194681, 23744, 194682, 23693, 194683, 138724, 194684, 23875, 194685, 138726, 194686, 23918, + 194687, 23915, 194688, 23932, 194689, -24034, 194691, 14383, 194692, 24061, 194693, 24104, + 194694, 24125, 194695, 24169, 194696, 14434, 194697, 139651, 194698, 14460, 194699, 24240, + 194700, 24243, 194701, 24246, 194702, 24266, 194703, 172946, 194704, 24318, 194705, 140081, + 194707, 33281, 194708, 24354, 194710, 14535, 194711, 144056, 194712, 156122, 194713, 24418, + 194714, 24427, 194715, 14563, 194716, 24474, 194717, 24525, 194718, 24535, 194719, 24569, + 194720, 24705, 194721, 14650, 194722, 14620, 194723, 24724, 194724, 141012, 194725, 24775, + 194726, 24904, 194727, 24908, 194728, 24910, 194729, 24908, 194730, 24954, 194731, 24974, + 194732, 25010, 194733, 24996, 194734, 25007, 194735, 25054, 194736, 25074, 194737, 25078, + 194738, 25104, 194739, 25115, 194740, 25181, 194741, 25265, 194742, 25300, 194743, 25424, + 194744, 142092, 194745, 25405, 194746, 25340, 194747, 25448, 194748, 25475, 194749, 25572, + 194750, 142321, 194751, 25634, 194752, 25541, 194753, 25513, 194754, 14894, 194755, 25705, + 194756, 25726, 194757, 25757, 194758, 25719, 194759, 14956, 194760, 25935, 194761, 25964, + 194762, 143370, 194763, 26083, 194764, 26360, 194765, 26185, 194766, 15129, 194767, 26257, + 194768, 15112, 194769, 15076, 194770, 20882, 194771, 20885, 194772, 26368, 194773, 26268, + 194774, 32941, 194775, 17369, 194776, 26391, 194777, 26395, 194778, 26401, 194779, 26462, + 194780, 26451, 194781, 144323, 194782, 15177, 194783, 26618, 194784, 26501, 194785, 26706, + 194786, 26757, 194787, 144493, 194788, 26766, 194789, 26655, 194790, 26900, 194791, 15261, + 194792, 26946, 194793, 27043, 194794, 27114, 194795, 27304, 194796, 145059, 194797, 27355, + 194798, 15384, 194799, 27425, 194800, 145575, 194801, 27476, 194802, 15438, 194803, 27506, + 194804, 27551, 194805, -27579, 194807, 146061, 194808, 138507, 194809, 146170, 194810, 27726, + 194811, 146620, 194812, 27839, 194813, 27853, 194814, 27751, 194815, 27926, 194816, 27966, + 194817, 28023, 194818, 27969, 194819, 28009, 194820, 28024, 194821, 28037, 194822, 146718, + 194823, 27956, 194824, 28207, 194825, 28270, 194826, 15667, 194827, 28363, 194828, 28359, + 194829, 147153, 194830, 28153, 194831, 28526, 194832, 147294, 194833, 147342, 194834, 28614, + 194835, 28729, 194836, 28702, 194837, 28699, 194838, 15766, 194839, 28746, 194840, 28797, + 194841, 28791, 194842, 28845, 194843, 132389, 194844, 28997, 194845, 148067, 194846, 29084, + 194847, 148395, 194848, 29224, 194849, 29237, 194850, 29264, 194851, 149000, 194852, 29312, + 194853, 29333, 194854, 149301, 194855, 149524, 194856, 29562, 194857, 29579, 194858, 16044, + 194859, 29605, 194860, 16056, 194862, 29767, 194863, 29788, 194864, 29809, 194865, 29829, + 194866, 29898, 194867, 16155, 194868, 29988, 194869, 150582, 194870, 30014, 194871, 150674, + 194872, 30064, 194873, 139679, 194874, 30224, 194875, 151457, 194876, 151480, 194877, 151620, + 194878, 16380, 194879, 16392, 194880, 30452, 194881, 151795, 194882, 151794, 194883, 151833, + 194884, 151859, 194885, -30495, 194887, 30495, 194888, 30538, 194889, 16441, 194890, 30603, + 194891, 16454, 194892, 16534, 194893, 152605, 194894, 30798, 194895, 30860, 194896, 30924, + 194897, 16611, 194898, 153126, 194899, 31062, 194900, 153242, 194901, 153285, 194902, 31119, + 194903, 31211, 194904, 16687, 194905, 31296, 194906, 31306, 194907, 31311, 194908, 153980, + 194909, 154279, 194911, 31470, 194912, 16898, 194913, 154539, 194914, 31686, 194915, 31689, + 194916, 16935, 194917, 154752, 194918, 31954, 194919, 17056, 194920, 31976, 194921, 31971, + 194922, 32000, 194923, 155526, 194924, 32099, 194925, 17153, 194926, 32199, 194927, 32258, + 194928, 32325, 194929, 17204, 194930, 156200, 194931, 156231, 194932, 17241, 194933, 156377, + 194934, 32634, 194935, 156478, 194936, 32661, 194937, 32762, 194938, 32773, 194939, 156890, + 194940, 156963, 194941, 32864, 194942, 157096, 194943, 32880, 194944, 144223, 194945, 17365, + 194946, 32946, 194947, 33027, 194948, 17419, 194949, 33086, 194950, 23221, 194951, 157607, + 194952, 157621, 194953, 144275, 194954, 144284, 194955, 33281, 194956, 33284, 194957, 36766, + 194958, 17515, 194959, 33425, 194960, 33419, 194961, 33437, 194962, 21171, 194963, 33457, + 194964, 33459, 194965, 33469, 194966, 33510, 194967, 158524, 194968, 33509, 194969, 33565, + 194970, 33635, 194971, 33709, 194972, 33571, 194973, 33725, 194974, 33767, 194975, 33879, + 194976, 33619, 194977, 33738, 194978, 33740, 194979, 33756, 194980, 158774, 194981, 159083, + 194982, 158933, 194983, 17707, 194984, 34033, 194985, 34035, 194986, 34070, 194987, 160714, + 194988, 34148, 194989, 159532, 194990, 17757, 194991, 17761, 194992, 159665, 194993, 159954, + 194994, 17771, 194995, 34384, 194996, 34396, 194997, 34407, 194998, 34409, 194999, 34473, + 195000, 34440, 195001, 34574, 195002, 34530, 195003, 34681, 195004, 34600, 195005, 34667, + 195006, 34694, 195007, 17879, 195008, 34785, 195009, 34817, 195010, 17913, 195011, 34912, + 195012, 34915, 195013, 161383, 195014, 35031, 195015, 35038, 195016, 17973, 195017, 35066, + 195018, 13499, 195019, 161966, 195020, 162150, 195021, 18110, 195022, 18119, 195023, 35488, + 195024, 35565, 195025, 35722, 195026, 35925, 195027, 162984, 195028, 36011, 195029, 36033, + 195030, 36123, 195031, 36215, 195032, 163631, 195033, 133124, 195034, 36299, 195035, 36284, + 195036, 36336, 195037, 133342, 195038, 36564, 195039, 36664, 195040, 165330, 195041, 165357, + 195042, 37012, 195043, 37105, 195044, 37137, 195045, 165678, 195046, 37147, 195047, 37432, + 195048, -37592, 195050, 37500, 195051, 37881, 195052, 37909, 195053, 166906, 195054, 38283, + 195055, 18837, 195056, 38327, 195057, 167287, 195058, 18918, 195059, 38595, 195060, 23986, + 195061, 38691, 195062, 168261, 195063, 168474, 195064, 19054, 195065, 19062, 195066, 38880, + 195067, 168970, 195068, 19122, 195069, 169110, 195070, 38923, 195072, 38953, 195073, 169398, + 195074, 39138, 195075, 19251, 195076, 39209, 195077, 39335, 195078, 39362, 195079, 39422, + 195080, 19406, 195081, 170800, 195082, 39698, 195083, 40000, 195084, 40189, 195085, 19662, + 195086, 19693, 195087, 40295, 195088, 172238, 195089, 19704, 195090, 172293, 195091, 172558, + 195092, 172689, 195093, 40635, 195094, 19798, 195095, 40697, 195096, 40702, 195097, 40709, + 195098, 40719, 195099, 40726, 195100, 40763, 195101, 173568, 195102, -195103, 917505, 0, + 917506, -917507, 917536, 0, 917632, -917633, 917760, 0, 918000, -918001, 2147483647, 0}; UnicodeSimpleCategory get_unicode_simple_category(uint32 code) { - auto it = std::upper_bound(std::begin(unicode_simple_category_ranges), std::end(unicode_simple_category_ranges), - (code << 5) + 30); + if (code < 128) { + return static_cast<UnicodeSimpleCategory>(unicode_simple_category_table[code]); + } + auto jump_pos_index = code <= 0x20000 ? code >> 7 : (0x20000 >> 7) - (0x20000 >> 16) + (code >> 16); + // CHECK(jump_pos_index < sizeof(unicode_simple_category_ranges) / sizeof(unicode_simple_category_ranges[0])); + auto it = unicode_simple_category_ranges + unicode_simple_category_jump_pos[jump_pos_index]; + code = (code << 5) + 30; + // CHECK(unicode_simple_category_ranges[unicode_simple_category_jump_pos[jump_pos_index + 1]] > code); + while (*it <= code) { + ++it; + } return static_cast<UnicodeSimpleCategory>(*(it - 1) & 31); } @@ -524,8 +1283,9 @@ static uint32 binary_search_ranges(const int32 (&ranges)[N], uint32 code) { return 0; } - int32 code_int = static_cast<int32>(code); - size_t l = 0, r = N; + auto code_int = static_cast<int32>(code); + size_t l = 0; + size_t r = N; while (l < r) { size_t m = ((l + r + 2) >> 2) << 1; if (ranges[m] <= code_int) { @@ -550,7 +1310,7 @@ static uint32 binary_search_ranges(const int32 (&ranges)[N], uint32 code) { case 2: return ((code - 1) | 1); default: - UNREACHABLE(); + LOG(FATAL) << code << " " << l << " " << r << " " << t; return 0; } } @@ -571,4 +1331,12 @@ uint32 unicode_to_lower(uint32 code) { } } +uint32 remove_diacritics(uint32 code) { + if (code < TABLE_SIZE) { + return without_diacritics_table[code]; + } else { + return binary_search_ranges(without_diacritics_ranges, code); + } +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h index 1c75397d6e..9012e4633f 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/unicode.h @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -16,7 +16,7 @@ UnicodeSimpleCategory get_unicode_simple_category(uint32 code); /** * Prepares unicode character for search, leaving only digits and lowercased letters. - * Return code of replacing character or 0 if the character should be skipped. + * Returns code of replacing character or 0 if the character should be skipped. */ uint32 prepare_search_character(uint32 code); @@ -25,4 +25,9 @@ uint32 prepare_search_character(uint32 code); */ uint32 unicode_to_lower(uint32 code); +/** + * Removes diacritics from a unicode character. + */ +uint32 remove_diacritics(uint32 code); + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/unique_ptr.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/unique_ptr.h new file mode 100644 index 0000000000..c8f3b48d1f --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/unique_ptr.h @@ -0,0 +1,106 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#pragma once + +#include <cstddef> +#include <type_traits> +#include <utility> + +namespace td { + +// const-correct and compiler-friendly (g++ RAM and CPU usage 10 times less than for std::unique_ptr) +// replacement for std::unique_ptr +template <class T> +class unique_ptr final { + public: + using pointer = T *; + using element_type = T; + + unique_ptr() noexcept = default; + unique_ptr(const unique_ptr &other) = delete; + unique_ptr &operator=(const unique_ptr &other) = delete; + unique_ptr(unique_ptr &&other) noexcept : ptr_(other.release()) { + } + unique_ptr &operator=(unique_ptr &&other) noexcept { + reset(other.release()); + return *this; + } + ~unique_ptr() { + reset(); + } + + unique_ptr(std::nullptr_t) noexcept { + } + explicit unique_ptr(T *ptr) noexcept : ptr_(ptr) { + } + template <class S, class = std::enable_if_t<std::is_base_of<T, S>::value>> + unique_ptr(unique_ptr<S> &&other) noexcept : ptr_(static_cast<S *>(other.release())) { + } + template <class S, class = std::enable_if_t<std::is_base_of<T, S>::value>> + unique_ptr &operator=(unique_ptr<S> &&other) noexcept { + reset(static_cast<T *>(other.release())); + return *this; + } + void reset(T *new_ptr = nullptr) noexcept { + static_assert(sizeof(T) > 0, "Can't destroy unique_ptr with incomplete type"); + delete ptr_; + ptr_ = new_ptr; + } + T *release() noexcept { + auto res = ptr_; + ptr_ = nullptr; + return res; + } + T *get() noexcept { + return ptr_; + } + const T *get() const noexcept { + return ptr_; + } + T *operator->() noexcept { + return ptr_; + } + const T *operator->() const noexcept { + return ptr_; + } + T &operator*() noexcept { + return *ptr_; + } + const T &operator*() const noexcept { + return *ptr_; + } + explicit operator bool() const noexcept { + return ptr_ != nullptr; + } + + private: + T *ptr_{nullptr}; +}; + +template <class T> +bool operator==(std::nullptr_t, const unique_ptr<T> &p) { + return !p; +} +template <class T> +bool operator==(const unique_ptr<T> &p, std::nullptr_t) { + return !p; +} +template <class T> +bool operator!=(std::nullptr_t, const unique_ptr<T> &p) { + return static_cast<bool>(p); +} +template <class T> +bool operator!=(const unique_ptr<T> &p, std::nullptr_t) { + return static_cast<bool>(p); +} + +template <class Type, class... Args> +unique_ptr<Type> make_unique(Args &&...args) { + return unique_ptr<Type>(new Type(std::forward<Args>(args)...)); +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp b/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp index 50f82d6393..16c31e5b2d 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.cpp @@ -1,12 +1,13 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/utf8.h" -#include "td/utils/logging.h" // for UNREACHABLE +#include "td/utils/misc.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/unicode.h" namespace td { @@ -15,7 +16,7 @@ bool check_utf8(CSlice str) { const char *data = str.data(); const char *data_end = data + str.size(); do { - unsigned int a = static_cast<unsigned char>(*data++); + uint32 a = static_cast<unsigned char>(*data++); if ((a & 0x80) == 0) { if (data == data_end + 1) { return true; @@ -30,25 +31,25 @@ bool check_utf8(CSlice str) { ENSURE((a & 0x40) != 0); - unsigned int b = static_cast<unsigned char>(*data++); + uint32 b = static_cast<unsigned char>(*data++); ENSURE((b & 0xc0) == 0x80); if ((a & 0x20) == 0) { ENSURE((a & 0x1e) > 0); continue; } - unsigned int c = static_cast<unsigned char>(*data++); + uint32 c = static_cast<unsigned char>(*data++); ENSURE((c & 0xc0) == 0x80); if ((a & 0x10) == 0) { - int x = (((a & 0x0f) << 6) | (b & 0x20)); + uint32 x = (((a & 0x0f) << 6) | (b & 0x20)); ENSURE(x != 0 && x != 0x360); // surrogates continue; } - unsigned int d = static_cast<unsigned char>(*data++); + uint32 d = static_cast<unsigned char>(*data++); ENSURE((d & 0xc0) == 0x80); if ((a & 0x08) == 0) { - int t = (((a & 0x07) << 6) | (b & 0x30)); + uint32 t = (((a & 0x07) << 6) | (b & 0x30)); ENSURE(0 < t && t < 0x110); // end of unicode continue; } @@ -82,30 +83,20 @@ void append_utf8_character(string &str, uint32 ch) { const unsigned char *next_utf8_unsafe(const unsigned char *ptr, uint32 *code) { uint32 a = ptr[0]; if ((a & 0x80) == 0) { - if (code) { - *code = a; - } + *code = a; return ptr + 1; } else if ((a & 0x20) == 0) { - if (code) { - *code = ((a & 0x1f) << 6) | (ptr[1] & 0x3f); - } + *code = ((a & 0x1f) << 6) | (ptr[1] & 0x3f); return ptr + 2; } else if ((a & 0x10) == 0) { - if (code) { - *code = ((a & 0x0f) << 12) | ((ptr[1] & 0x3f) << 6) | (ptr[2] & 0x3f); - } + *code = ((a & 0x0f) << 12) | ((ptr[1] & 0x3f) << 6) | (ptr[2] & 0x3f); return ptr + 3; } else if ((a & 0x08) == 0) { - if (code) { - *code = ((a & 0x07) << 18) | ((ptr[1] & 0x3f) << 12) | ((ptr[2] & 0x3f) << 6) | (ptr[3] & 0x3f); - } + *code = ((a & 0x07) << 18) | ((ptr[1] & 0x3f) << 12) | ((ptr[2] & 0x3f) << 6) | (ptr[3] & 0x3f); return ptr + 4; } UNREACHABLE(); - if (code) { - *code = 0; - } + *code = 0; return ptr; } @@ -121,4 +112,84 @@ string utf8_to_lower(Slice str) { return result; } +vector<string> utf8_get_search_words(Slice str) { + bool in_word = false; + string word; + vector<string> words; + auto pos = str.ubegin(); + auto end = str.uend(); + while (pos != end) { + uint32 code; + pos = next_utf8_unsafe(pos, &code); + + code = prepare_search_character(code); + if (code == 0) { + continue; + } + if (code == ' ') { + if (in_word) { + words.push_back(std::move(word)); + word.clear(); + in_word = false; + } + } else { + in_word = true; + code = remove_diacritics(code); + append_utf8_character(word, code); + } + } + if (in_word) { + words.push_back(std::move(word)); + } + return words; +} + +string utf8_prepare_search_string(Slice str) { + return implode(utf8_get_search_words(str)); +} + +string utf8_encode(CSlice data) { + if (check_utf8(data)) { + return data.str(); + } + return PSTRING() << "url_decode(" << url_encode(data) << ')'; +} + +size_t utf8_utf16_length(Slice str) { + size_t result = 0; + for (auto c : str) { + result += is_utf8_character_first_code_unit(c) + ((c & 0xf8) == 0xf0); + } + return result; +} + +Slice utf8_utf16_truncate(Slice str, size_t length) { + for (size_t i = 0; i < str.size(); i++) { + auto c = static_cast<unsigned char>(str[i]); + if (is_utf8_character_first_code_unit(c)) { + if (length <= 0) { + return str.substr(0, i); + } else { + length--; + if (c >= 0xf0) { // >= 4 bytes in symbol => surrogate pair + length--; + } + } + } + } + return str; +} + +Slice utf8_utf16_substr(Slice str, size_t offset) { + if (offset == 0) { + return str; + } + auto offset_pos = utf8_utf16_truncate(str, offset).size(); + return str.substr(offset_pos); +} + +Slice utf8_utf16_substr(Slice str, size_t offset, size_t length) { + return utf8_utf16_truncate(utf8_utf16_substr(str, offset), length); +} + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h b/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h index 6be1952c19..27c8b5bd5d 100644 --- a/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h +++ b/protocols/Telegram/tdlib/td/tdutils/td/utils/utf8.h @@ -1,12 +1,12 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #pragma once -#include "td/utils/int_types.h" +#include "td/utils/common.h" #include "td/utils/Slice.h" namespace td { @@ -28,6 +28,9 @@ inline size_t utf8_length(Slice str) { return result; } +/// returns length of UTF-8 string in UTF-16 code units +size_t utf8_utf16_length(Slice str); + /// appends a Unicode character using UTF-8 encoding void append_utf8_character(string &str, uint32 ch); @@ -60,26 +63,13 @@ T utf8_truncate(T str, size_t length) { } /// truncates UTF-8 string to the given length given in UTF-16 code units -template <class T> -T utf8_utf16_truncate(T str, size_t length) { - for (size_t i = 0; i < str.size(); i++) { - auto c = static_cast<unsigned char>(str[i]); - if (is_utf8_character_first_code_unit(c)) { - if (length <= 0) { - return str.substr(0, i); - } else { - length--; - if (c >= 0xf0) { // >= 4 bytes in symbol => surrogaite pair - length--; - } - } - } - } - return str; -} +Slice utf8_utf16_truncate(Slice str, size_t length); template <class T> T utf8_substr(T str, size_t offset) { + if (offset == 0) { + return str; + } auto offset_pos = utf8_truncate(str, offset).size(); return str.substr(offset_pos); } @@ -89,18 +79,20 @@ T utf8_substr(T str, size_t offset, size_t length) { return utf8_truncate(utf8_substr(str, offset), length); } -template <class T> -T utf8_utf16_substr(T str, size_t offset) { - auto offset_pos = utf8_utf16_truncate(str, offset).size(); - return str.substr(offset_pos); -} +Slice utf8_utf16_substr(Slice str, size_t offset); -template <class T> -T utf8_utf16_substr(T str, size_t offset, size_t length) { - return utf8_utf16_truncate(utf8_utf16_substr(str, offset), length); -} +Slice utf8_utf16_substr(Slice str, size_t offset, size_t length); /// Returns UTF-8 string converted to lower case. string utf8_to_lower(Slice str); +/// Returns UTF-8 string split by words for search. +vector<string> utf8_get_search_words(Slice str); + +/// Returns UTF-8 string prepared for search, leaving only digits and lowercased letters. +string utf8_prepare_search_string(Slice str); + +/// Returns valid UTF-8 representation of the string. +string utf8_encode(CSlice data); + } // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/test/ChainScheduler.cpp b/protocols/Telegram/tdlib/td/tdutils/test/ChainScheduler.cpp new file mode 100644 index 0000000000..d3bcb934fc --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/ChainScheduler.cpp @@ -0,0 +1,244 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/algorithm.h" +#include "td/utils/ChainScheduler.h" +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/Random.h" +#include "td/utils/Span.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/tests.h" + +#include <memory> +#include <numeric> + +TEST(ChainScheduler, CreateAfterActive) { + td::ChainScheduler<int> scheduler; + td::vector<td::ChainScheduler<int>::ChainId> chains{1}; + + auto first_task_id = scheduler.create_task(chains, 1); + ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id); + auto second_task_id = scheduler.create_task(chains, 2); + ASSERT_EQ(second_task_id, scheduler.start_next_task().unwrap().task_id); +} + +TEST(ChainScheduler, RestartAfterActive) { + td::ChainScheduler<int> scheduler; + std::vector<td::ChainScheduler<int>::ChainId> chains{1}; + + auto first_task_id = scheduler.create_task(chains, 1); + auto second_task_id = scheduler.create_task(chains, 2); + ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id); + ASSERT_EQ(second_task_id, scheduler.start_next_task().unwrap().task_id); + + scheduler.reset_task(first_task_id); + ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id); + + scheduler.reset_task(second_task_id); + ASSERT_EQ(second_task_id, scheduler.start_next_task().unwrap().task_id); +} + +TEST(ChainScheduler, SendAfterRestart) { + td::ChainScheduler<int> scheduler; + std::vector<td::ChainScheduler<int>::ChainId> chains{1}; + + auto first_task_id = scheduler.create_task(chains, 1); + auto second_task_id = scheduler.create_task(chains, 2); + ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id); + ASSERT_EQ(second_task_id, scheduler.start_next_task().unwrap().task_id); + + scheduler.reset_task(first_task_id); + + scheduler.create_task(chains, 3); + + ASSERT_EQ(first_task_id, scheduler.start_next_task().unwrap().task_id); + ASSERT_TRUE(!scheduler.start_next_task()); +} + +TEST(ChainScheduler, Basic) { + td::ChainScheduler<int> scheduler; + for (int i = 0; i < 100; i++) { + scheduler.create_task({td::ChainScheduler<int>::ChainId{1}}, i); + } + int j = 0; + while (j != 100) { + td::vector<td::ChainScheduler<int>::TaskId> tasks; + while (true) { + auto o_task_id = scheduler.start_next_task(); + if (!o_task_id) { + break; + } + auto task_id = o_task_id.value().task_id; + auto extra = *scheduler.get_task_extra(task_id); + auto parents = + td::transform(o_task_id.value().parents, [&](auto parent) { return *scheduler.get_task_extra(parent); }); + LOG(INFO) << "Start " << extra << parents; + CHECK(extra == j); + j++; + tasks.push_back(task_id); + } + for (auto &task_id : tasks) { + auto extra = *scheduler.get_task_extra(task_id); + LOG(INFO) << "Finish " << extra; + scheduler.finish_task(task_id); + } + } +} + +struct ChainSchedulerQuery; +using QueryPtr = std::shared_ptr<ChainSchedulerQuery>; +using ChainId = td::ChainScheduler<QueryPtr>::ChainId; +using TaskId = td::ChainScheduler<QueryPtr>::TaskId; + +struct ChainSchedulerQuery { + int id{}; + TaskId task_id{}; + bool is_ok{}; + bool skipped{}; +}; + +TEST(ChainScheduler, Stress) { + td::Random::Xorshift128plus rnd(123); + int max_query_id = 100000; + int MAX_INFLIGHT_QUERIES = 20; + int ChainsN = 4; + + struct QueryWithParents { + TaskId task_id; + QueryPtr id; + td::vector<QueryPtr> parents; + }; + td::vector<QueryWithParents> active_queries; + + td::ChainScheduler<QueryPtr> scheduler; + td::vector<td::vector<QueryPtr>> chains(ChainsN); + int inflight_queries{}; + int current_query_id{}; + int sent_cnt{}; + bool done = false; + std::vector<TaskId> pending_queries; + + auto schedule_new_query = [&] { + if (current_query_id > max_query_id) { + if (inflight_queries == 0) { + done = true; + } + return; + } + if (inflight_queries >= MAX_INFLIGHT_QUERIES) { + return; + } + auto query_id = current_query_id++; + auto query = std::make_shared<ChainSchedulerQuery>(); + query->id = query_id; + int chain_n = rnd.fast(1, ChainsN); + td::vector<ChainId> chain_ids(ChainsN); + std::iota(chain_ids.begin(), chain_ids.end(), 0); + td::random_shuffle(td::as_mutable_span(chain_ids), rnd); + chain_ids.resize(chain_n); + for (auto chain_id : chain_ids) { + chains[td::narrow_cast<size_t>(chain_id)].push_back(query); + } + auto task_id = scheduler.create_task(chain_ids, query); + query->task_id = task_id; + pending_queries.push_back(task_id); + inflight_queries++; + }; + + auto check_parents_ok = [&](const QueryWithParents &query_with_parents) -> bool { + return td::all_of(query_with_parents.parents, [](auto &parent) { return parent->is_ok; }); + }; + + auto to_query_ptr = [&](TaskId task_id) { + return *scheduler.get_task_extra(task_id); + }; + auto flush_pending_queries = [&] { + while (true) { + auto o_task_with_parents = scheduler.start_next_task(); + if (!o_task_with_parents) { + break; + } + auto task_with_parents = o_task_with_parents.unwrap(); + QueryWithParents query_with_parents; + query_with_parents.task_id = task_with_parents.task_id; + query_with_parents.id = to_query_ptr(task_with_parents.task_id); + query_with_parents.parents = td::transform(task_with_parents.parents, to_query_ptr); + active_queries.push_back(query_with_parents); + sent_cnt++; + } + }; + auto skip_one_query = [&] { + if (pending_queries.empty()) { + return; + } + auto it = pending_queries.begin() + rnd.fast(0, static_cast<int>(pending_queries.size()) - 1); + auto task_id = *it; + pending_queries.erase(it); + td::remove_if(active_queries, [&](auto &q) { return q.task_id == task_id; }); + + auto query = *scheduler.get_task_extra(task_id); + query->skipped = true; + scheduler.finish_task(task_id); + inflight_queries--; + LOG(INFO) << "Skip " << query->id; + }; + auto execute_one_query = [&] { + if (active_queries.empty()) { + return; + } + auto it = active_queries.begin() + rnd.fast(0, static_cast<int>(active_queries.size()) - 1); + auto query_with_parents = *it; + active_queries.erase(it); + + auto query = query_with_parents.id; + if (rnd.fast(0, 20) == 0) { + scheduler.finish_task(query->task_id); + td::remove(pending_queries, query->task_id); + inflight_queries--; + LOG(INFO) << "Fail " << query->id; + } else if (check_parents_ok(query_with_parents)) { + query->is_ok = true; + scheduler.finish_task(query->task_id); + td::remove(pending_queries, query->task_id); + inflight_queries--; + LOG(INFO) << "OK " << query->id; + } else { + scheduler.reset_task(query->task_id); + LOG(INFO) << "Reset " << query->id; + } + }; + + td::RandomSteps steps({{schedule_new_query, 100}, {execute_one_query, 100}, {skip_one_query, 10}}); + while (!done) { + steps.step(rnd); + flush_pending_queries(); + // LOG(INFO) << scheduler; + } + LOG(INFO) << "Sent queries count " << sent_cnt; + LOG(INFO) << "Total queries " << current_query_id; + for (auto &chain : chains) { + int prev_ok = -1; + int failed_cnt = 0; + int ok_cnt = 0; + int skipped_cnt = 0; + for (auto &q : chain) { + if (q->is_ok) { + CHECK(prev_ok < q->id); + prev_ok = q->id; + ok_cnt++; + } else { + if (q->skipped) { + skipped_cnt++; + } else { + failed_cnt++; + } + } + } + LOG(INFO) << "Chain ok " << ok_cnt << " failed " << failed_cnt << " skipped " << skipped_cnt; + } +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/ConcurrentHashMap.cpp b/protocols/Telegram/tdlib/td/tdutils/test/ConcurrentHashMap.cpp new file mode 100644 index 0000000000..a90f11d525 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/ConcurrentHashMap.cpp @@ -0,0 +1,252 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/benchmark.h" +#include "td/utils/common.h" +#include "td/utils/ConcurrentHashTable.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/misc.h" +#include "td/utils/port/Mutex.h" +#include "td/utils/port/thread.h" +#include "td/utils/SpinLock.h" +#include "td/utils/tests.h" + +#include <atomic> + +#if !TD_THREAD_UNSUPPORTED + +#if TD_HAVE_ABSL +#include <absl/container/flat_hash_map.h> +#else +#include <unordered_map> +#endif + +#if TD_WITH_LIBCUCKOO +#include <third-party/libcuckoo/libcuckoo/cuckoohash_map.hh> +#endif + +#if TD_WITH_JUNCTION +#include <junction/ConcurrentMap_Grampa.h> +#include <junction/ConcurrentMap_Leapfrog.h> +#include <junction/ConcurrentMap_Linear.h> +#endif + +// Non resizable HashMap. Just an example +template <class KeyT, class ValueT> +class ArrayHashMap { + public: + explicit ArrayHashMap(std::size_t n) : array_(n) { + } + struct Node { + std::atomic<KeyT> key{KeyT{}}; + std::atomic<ValueT> value{ValueT{}}; + }; + static td::string get_name() { + return "ArrayHashMap"; + } + KeyT empty_key() const { + return KeyT{}; + } + + void insert(KeyT key, ValueT value) { + array_.with_value(key, true, [&](auto &node_value) { node_value.store(value, std::memory_order_release); }); + } + ValueT find(KeyT key, ValueT value) { + array_.with_value(key, false, [&](auto &node_value) { value = node_value.load(std::memory_order_acquire); }); + return value; + } + + private: + td::AtomicHashArray<KeyT, std::atomic<ValueT>> array_; +}; + +template <class KeyT, class ValueT> +class ConcurrentHashMapMutex { + public: + explicit ConcurrentHashMapMutex(std::size_t) { + } + static td::string get_name() { + return "ConcurrentHashMapMutex"; + } + void insert(KeyT key, ValueT value) { + auto guard = mutex_.lock(); + hash_map_.emplace(key, value); + } + ValueT find(KeyT key, ValueT default_value) { + auto guard = mutex_.lock(); + auto it = hash_map_.find(key); + if (it == hash_map_.end()) { + return default_value; + } + return it->second; + } + + private: + td::Mutex mutex_; +#if TD_HAVE_ABSL + absl::flat_hash_map<KeyT, ValueT> hash_map_; +#else + std::unordered_map<KeyT, ValueT, td::Hash<KeyT>> hash_map_; +#endif +}; + +template <class KeyT, class ValueT> +class ConcurrentHashMapSpinlock { + public: + explicit ConcurrentHashMapSpinlock(size_t) { + } + static td::string get_name() { + return "ConcurrentHashMapSpinlock"; + } + void insert(KeyT key, ValueT value) { + auto guard = spinlock_.lock(); + hash_map_.emplace(key, value); + } + ValueT find(KeyT key, ValueT default_value) { + auto guard = spinlock_.lock(); + auto it = hash_map_.find(key); + if (it == hash_map_.end()) { + return default_value; + } + return it->second; + } + + private: + td::SpinLock spinlock_; +#if TD_HAVE_ABSL + absl::flat_hash_map<KeyT, ValueT> hash_map_; +#else + std::unordered_map<KeyT, ValueT, td::Hash<KeyT>> hash_map_; +#endif +}; + +#if TD_WITH_LIBCUCKOO +template <class KeyT, class ValueT> +class ConcurrentHashMapLibcuckoo { + public: + explicit ConcurrentHashMapLibcuckoo(size_t) { + } + static td::string get_name() { + return "ConcurrentHashMapLibcuckoo"; + } + void insert(KeyT key, ValueT value) { + hash_map_.insert(key, value); + } + ValueT find(KeyT key, ValueT default_value) { + hash_map_.find(key, default_value); + return default_value; + } + + private: + cuckoohash_map<KeyT, ValueT> hash_map_; +}; +#endif + +#if TD_WITH_JUNCTION +template <class KeyT, class ValueT> +class ConcurrentHashMapJunction { + public: + explicit ConcurrentHashMapJunction(std::size_t size) : hash_map_() { + } + static td::string get_name() { + return "ConcurrentHashMapJunction"; + } + void insert(KeyT key, ValueT value) { + hash_map_.assign(key, value); + } + ValueT find(KeyT key, ValueT default_value) { + return hash_map_.get(key); + } + + ConcurrentHashMapJunction(const ConcurrentHashMapJunction &) = delete; + ConcurrentHashMapJunction &operator=(const ConcurrentHashMapJunction &) = delete; + ConcurrentHashMapJunction(ConcurrentHashMapJunction &&other) = delete; + ConcurrentHashMapJunction &operator=(ConcurrentHashMapJunction &&) = delete; + ~ConcurrentHashMapJunction() { + junction::DefaultQSBR.flush(); + } + + private: + junction::ConcurrentMap_Leapfrog<KeyT, ValueT> hash_map_; +}; +#endif + +template <class HashMap> +class HashMapBenchmark final : public td::Benchmark { + struct Query { + int key; + int value; + }; + td::vector<Query> queries; + td::unique_ptr<HashMap> hash_map; + + std::size_t threads_n = 16; + static constexpr std::size_t MUL = 7273; //1000000000 + 7; + int n_ = 0; + + public: + explicit HashMapBenchmark(std::size_t threads_n) : threads_n(threads_n) { + } + td::string get_description() const final { + return HashMap::get_name(); + } + void start_up_n(int n) final { + n *= static_cast<int>(threads_n); + n_ = n; + hash_map = td::make_unique<HashMap>(n * 2); + } + + void run(int n) final { + n = n_; + td::vector<td::thread> threads; + + for (std::size_t i = 0; i < threads_n; i++) { + std::size_t l = n * i / threads_n; + std::size_t r = n * (i + 1) / threads_n; + threads.emplace_back([l, r, this] { + for (size_t i = l; i < r; i++) { + auto x = td::narrow_cast<int>((i + 1) * MUL % n_) + 3; + auto y = td::narrow_cast<int>(i + 2); + hash_map->insert(x, y); + } + }); + } + for (auto &thread : threads) { + thread.join(); + } + } + + void tear_down() final { + for (int i = 0; i < n_; i++) { + auto x = td::narrow_cast<int>((i + 1) * MUL % n_) + 3; + auto y = td::narrow_cast<int>(i + 2); + ASSERT_EQ(y, hash_map->find(x, -1)); + } + queries.clear(); + hash_map.reset(); + } +}; + +template <class HashMap> +static void bench_hash_map() { + td::bench(HashMapBenchmark<HashMap>(16)); + td::bench(HashMapBenchmark<HashMap>(1)); +} + +TEST(ConcurrentHashMap, Benchmark) { + bench_hash_map<td::ConcurrentHashMap<td::int32, td::int32>>(); + bench_hash_map<ArrayHashMap<td::int32, td::int32>>(); + bench_hash_map<ConcurrentHashMapSpinlock<td::int32, td::int32>>(); + bench_hash_map<ConcurrentHashMapMutex<td::int32, td::int32>>(); +#if TD_WITH_LIBCUCKOO + bench_hash_map<ConcurrentHashMapLibcuckoo<td::int32, td::int32>>(); +#endif +#if TD_WITH_JUNCTION + bench_hash_map<ConcurrentHashMapJunction<td::int32, td::int32>>(); +#endif +} + +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp b/protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp index b617485462..210ab415cc 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/Enumerator.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) diff --git a/protocols/Telegram/tdlib/td/tdutils/test/EpochBasedMemoryReclamation.cpp b/protocols/Telegram/tdlib/td/tdutils/test/EpochBasedMemoryReclamation.cpp new file mode 100644 index 0000000000..c97679bb83 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/EpochBasedMemoryReclamation.cpp @@ -0,0 +1,68 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/EpochBasedMemoryReclamation.h" +#include "td/utils/logging.h" +#include "td/utils/port/thread.h" +#include "td/utils/Random.h" +#include "td/utils/tests.h" + +#include <atomic> + +#if !TD_THREAD_UNSUPPORTED +TEST(EpochBaseMemoryReclamation, stress) { + struct Node { + std::atomic<std::string *> name_{nullptr}; + char pad[64]; + }; + + int threads_n = 10; + std::vector<Node> nodes(threads_n); + td::EpochBasedMemoryReclamation<std::string> ebmr(threads_n + 1); + auto locker = ebmr.get_locker(threads_n); + locker.lock(); + locker.unlock(); + std::vector<td::thread> threads(threads_n); + int thread_id = 0; + for (auto &thread : threads) { + thread = td::thread([&, thread_id] { + auto locker = ebmr.get_locker(thread_id); + locker.lock(); + for (int i = 0; i < 1000000; i++) { + auto &node = nodes[td::Random::fast(0, threads_n - 1)]; + auto *str = node.name_.load(std::memory_order_acquire); + if (str) { + CHECK(*str == "one" || *str == "twotwo"); + } + if ((i + 1) % 100 == 0) { + locker.retire(); + } + if (td::Random::fast(0, 5) == 0) { + auto *new_str = new td::string(td::Random::fast_bool() ? "one" : "twotwo"); + if (node.name_.compare_exchange_strong(str, new_str, std::memory_order_acq_rel)) { + locker.retire(str); + } else { + delete new_str; + } + } + } + locker.retire_sync(); + locker.unlock(); + }); + thread_id++; + } + for (auto &thread : threads) { + thread.join(); + } + LOG(INFO) << "Undeleted pointers: " << ebmr.to_delete_size_unsafe(); + //CHECK(static_cast<int>(ebmr.to_delete_size_unsafe()) <= threads_n * threads_n); + for (int i = 0; i < threads_n; i++) { + ebmr.get_locker(i).retire_sync(); + } + CHECK(ebmr.to_delete_size_unsafe() == 0); +} +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/HashSet.cpp b/protocols/Telegram/tdlib/td/tdutils/test/HashSet.cpp new file mode 100644 index 0000000000..94ebf8733b --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/HashSet.cpp @@ -0,0 +1,438 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/algorithm.h" +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/FlatHashMapChunks.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/logging.h" +#include "td/utils/Random.h" +#include "td/utils/Slice.h" +#include "td/utils/tests.h" + +#include <algorithm> +#include <array> +#include <random> +#include <unordered_map> +#include <unordered_set> +#include <utility> + +template <class T> +static auto extract_kv(const T &reference) { + auto expected = td::transform(reference, [](auto &it) { return std::make_pair(it.first, it.second); }); + std::sort(expected.begin(), expected.end()); + return expected; +} + +template <class T> +static auto extract_k(const T &reference) { + auto expected = td::transform(reference, [](auto &it) { return it; }); + std::sort(expected.begin(), expected.end()); + return expected; +} + +TEST(FlatHashMapChunks, basic) { + td::FlatHashMapChunks<int, int> kv; + kv[5] = 3; + ASSERT_EQ(3, kv[5]); + kv[3] = 4; + ASSERT_EQ(4, kv[3]); +} + +TEST(FlatHashMap, probing) { + auto test = [](int buckets, int elements) { + CHECK(buckets >= elements); + td::vector<bool> data(buckets, false); + std::random_device rnd; + std::mt19937 mt(rnd()); + std::uniform_int_distribution<td::int32> d(0, buckets - 1); + for (int i = 0; i < elements; i++) { + int pos = d(mt); + while (data[pos]) { + pos++; + if (pos == buckets) { + pos = 0; + } + } + data[pos] = true; + } + int max_chain = 0; + int cur_chain = 0; + for (auto x : data) { + if (x) { + cur_chain++; + max_chain = td::max(max_chain, cur_chain); + } else { + cur_chain = 0; + } + } + LOG(INFO) << "Buckets=" << buckets << " elements=" << elements << " max_chain=" << max_chain; + }; + test(8192, static_cast<int>(8192 * 0.8)); + test(8192, static_cast<int>(8192 * 0.6)); + test(8192, static_cast<int>(8192 * 0.3)); +} + +struct A { + int a; +}; + +struct AHash { + td::uint32 operator()(A a) const { + return td::Hash<int>()(a.a); + } +}; + +static bool operator==(const A &lhs, const A &rhs) { + return lhs.a == rhs.a; +} + +TEST(FlatHashSet, foreach) { + td::FlatHashSet<A, AHash> s; + for (auto it : s) { + LOG(ERROR) << it.a; + } + s.insert({1}); + LOG(INFO) << s.begin()->a; +} + +TEST(FlatHashSet, TL) { + td::FlatHashSet<int> s; + int N = 100000; + for (int i = 0; i < 10000000; i++) { + s.insert((i + N / 2) % N + 1); + s.erase(i % N + 1); + } +} + +TEST(FlatHashMap, basic) { + { + td::FlatHashMap<td::int32, int> map; + map[1] = 2; + ASSERT_EQ(2, map[1]); + ASSERT_EQ(1, map.find(1)->first); + ASSERT_EQ(2, map.find(1)->second); + // ASSERT_EQ(1, map.find(1)->key()); + // ASSERT_EQ(2, map.find(1)->value()); + for (auto &kv : map) { + ASSERT_EQ(1, kv.first); + ASSERT_EQ(2, kv.second); + } + map.erase(map.find(1)); + } + + td::FlatHashMap<td::int32, std::array<td::unique_ptr<td::string>, 10>> x; + auto y = std::move(x); + x[12]; + x.erase(x.find(12)); + + { + td::FlatHashMap<td::int32, td::string> map = {{1, "hello"}, {2, "world"}}; + ASSERT_EQ("hello", map[1]); + ASSERT_EQ("world", map[2]); + ASSERT_EQ(2u, map.size()); + ASSERT_EQ("", map[3]); + ASSERT_EQ(3u, map.size()); + } + + { + td::FlatHashMap<td::int32, td::string> map = {{1, "hello"}, {1, "world"}}; + ASSERT_EQ("hello", map[1]); + ASSERT_EQ(1u, map.size()); + } + + using KV = td::FlatHashMap<td::string, td::string>; + using Data = td::vector<std::pair<td::string, td::string>>; + auto data = Data{{"a", "b"}, {"c", "d"}}; + { ASSERT_EQ(Data{}, extract_kv(KV())); } + + { + KV kv; + for (auto &pair : data) { + kv.emplace(pair.first, pair.second); + } + ASSERT_EQ(data, extract_kv(kv)); + + KV moved_kv(std::move(kv)); + ASSERT_EQ(data, extract_kv(moved_kv)); + ASSERT_EQ(Data{}, extract_kv(kv)); + ASSERT_TRUE(kv.empty()); + kv = std::move(moved_kv); + ASSERT_EQ(data, extract_kv(kv)); + + KV assign_moved_kv; + assign_moved_kv = std::move(kv); + ASSERT_EQ(data, extract_kv(assign_moved_kv)); + ASSERT_EQ(Data{}, extract_kv(kv)); + ASSERT_TRUE(kv.empty()); + kv = std::move(assign_moved_kv); + + KV it_copy_kv; + for (auto &pair : kv) { + it_copy_kv.emplace(pair.first, pair.second); + } + ASSERT_EQ(data, extract_kv(it_copy_kv)); + } + + { + KV kv; + ASSERT_TRUE(kv.empty()); + ASSERT_EQ(0u, kv.size()); + for (auto &pair : data) { + kv.emplace(pair.first, pair.second); + } + ASSERT_TRUE(!kv.empty()); + ASSERT_EQ(2u, kv.size()); + + ASSERT_EQ("a", kv.find("a")->first); + ASSERT_EQ("b", kv.find("a")->second); + kv.find("a")->second = "c"; + ASSERT_EQ("c", kv.find("a")->second); + ASSERT_EQ("c", kv["a"]); + + ASSERT_EQ(0u, kv.count("x")); + ASSERT_EQ(1u, kv.count("a")); + } + { + KV kv; + kv["d"]; + ASSERT_EQ((Data{{"d", ""}}), extract_kv(kv)); + kv.erase(kv.find("d")); + ASSERT_EQ(Data{}, extract_kv(kv)); + } +} + +TEST(FlatHashMap, remove_if_basic) { + td::Random::Xorshift128plus rnd(123); + + constexpr int TESTS_N = 1000; + constexpr int MAX_TABLE_SIZE = 1000; + for (int test_i = 0; test_i < TESTS_N; test_i++) { + std::unordered_map<td::uint64, td::uint64, td::Hash<td::uint64>> reference; + td::FlatHashMap<td::uint64, td::uint64> table; + int N = rnd.fast(1, MAX_TABLE_SIZE); + for (int i = 0; i < N; i++) { + auto key = rnd(); + auto value = i; + reference[key] = value; + table[key] = value; + } + ASSERT_EQ(extract_kv(reference), extract_kv(table)); + + td::vector<std::pair<td::uint64, td::uint64>> kv; + td::table_remove_if(table, [&](auto &it) { + kv.emplace_back(it.first, it.second); + return it.second % 2 == 0; + }); + std::sort(kv.begin(), kv.end()); + ASSERT_EQ(extract_kv(reference), kv); + + td::table_remove_if(reference, [](auto &it) { return it.second % 2 == 0; }); + ASSERT_EQ(extract_kv(reference), extract_kv(table)); + } +} + +static constexpr size_t MAX_TABLE_SIZE = 1000; +TEST(FlatHashMap, stress_test) { + td::Random::Xorshift128plus rnd(123); + size_t max_table_size = MAX_TABLE_SIZE; // dynamic value + std::unordered_map<td::uint64, td::uint64, td::Hash<td::uint64>> ref; + td::FlatHashMap<td::uint64, td::uint64> tbl; + + auto validate = [&] { + ASSERT_EQ(ref.empty(), tbl.empty()); + ASSERT_EQ(ref.size(), tbl.size()); + ASSERT_EQ(extract_kv(ref), extract_kv(tbl)); + for (auto &kv : ref) { + auto tbl_it = tbl.find(kv.first); + ASSERT_TRUE(tbl_it != tbl.end()); + ASSERT_EQ(kv.second, tbl_it->second); + } + }; + + td::vector<td::RandomSteps::Step> steps; + auto add_step = [&](td::Slice step_name, td::uint32 weight, auto f) { + auto g = [&, f = std::move(f)] { + //ASSERT_EQ(ref.size(), tbl.size()); + f(); + ASSERT_EQ(ref.size(), tbl.size()); + //validate(); + }; + steps.emplace_back(td::RandomSteps::Step{std::move(g), weight}); + }; + + auto gen_key = [&] { + auto key = rnd() % 4000 + 1; + return key; + }; + + add_step("Reset hash table", 1, [&] { + validate(); + td::reset_to_empty(ref); + td::reset_to_empty(tbl); + max_table_size = rnd.fast(1, MAX_TABLE_SIZE); + }); + add_step("Clear hash table", 1, [&] { + validate(); + ref.clear(); + tbl.clear(); + max_table_size = rnd.fast(1, MAX_TABLE_SIZE); + }); + + add_step("Insert random value", 1000, [&] { + if (tbl.size() > max_table_size) { + return; + } + auto key = gen_key(); + auto value = rnd(); + ref[key] = value; + tbl[key] = value; + ASSERT_EQ(ref[key], tbl[key]); + }); + + add_step("Emplace random value", 1000, [&] { + if (tbl.size() > max_table_size) { + return; + } + auto key = gen_key(); + auto value = rnd(); + auto ref_it = ref.emplace(key, value); + auto tbl_it = tbl.emplace(key, value); + ASSERT_EQ(ref_it.second, tbl_it.second); + ASSERT_EQ(key, tbl_it.first->first); + }); + + add_step("empty operator[]", 1000, [&] { + if (tbl.size() > max_table_size) { + return; + } + auto key = gen_key(); + ASSERT_EQ(ref[key], tbl[key]); + }); + + add_step("reserve", 10, [&] { tbl.reserve(static_cast<size_t>(rnd() % max_table_size)); }); + + add_step("find", 1000, [&] { + auto key = gen_key(); + auto ref_it = ref.find(key); + auto tbl_it = tbl.find(key); + ASSERT_EQ(ref_it == ref.end(), tbl_it == tbl.end()); + if (ref_it != ref.end()) { + ASSERT_EQ(ref_it->first, tbl_it->first); + ASSERT_EQ(ref_it->second, tbl_it->second); + } + }); + + add_step("find_and_erase", 100, [&] { + auto key = gen_key(); + auto ref_it = ref.find(key); + auto tbl_it = tbl.find(key); + ASSERT_EQ(ref_it == ref.end(), tbl_it == tbl.end()); + if (ref_it != ref.end()) { + ref.erase(ref_it); + tbl.erase(tbl_it); + } + }); + + add_step("remove_if", 5, [&] { + auto mul = rnd(); + auto bit = rnd() % 64; + auto condition = [&](auto &it) { + return (((it.second * mul) >> bit) & 1) == 0; + }; + td::table_remove_if(tbl, condition); + td::table_remove_if(ref, condition); + }); + + td::RandomSteps runner(std::move(steps)); + for (size_t i = 0; i < 1000000; i++) { + runner.step(rnd); + } +} + +TEST(FlatHashSet, stress_test) { + td::vector<td::RandomSteps::Step> steps; + auto add_step = [&steps](td::Slice, td::uint32 weight, auto f) { + steps.emplace_back(td::RandomSteps::Step{std::move(f), weight}); + }; + + td::Random::Xorshift128plus rnd(123); + size_t max_table_size = MAX_TABLE_SIZE; // dynamic value + std::unordered_set<td::uint64, td::Hash<td::uint64>> ref; + td::FlatHashSet<td::uint64> tbl; + + auto validate = [&] { + ASSERT_EQ(ref.empty(), tbl.empty()); + ASSERT_EQ(ref.size(), tbl.size()); + ASSERT_EQ(extract_k(ref), extract_k(tbl)); + }; + auto gen_key = [&] { + auto key = rnd() % 4000 + 1; + return key; + }; + + add_step("Reset hash table", 1, [&] { + validate(); + td::reset_to_empty(ref); + td::reset_to_empty(tbl); + max_table_size = rnd.fast(1, MAX_TABLE_SIZE); + }); + add_step("Clear hash table", 1, [&] { + validate(); + ref.clear(); + tbl.clear(); + max_table_size = rnd.fast(1, MAX_TABLE_SIZE); + }); + + add_step("Insert random value", 1000, [&] { + if (tbl.size() > max_table_size) { + return; + } + auto key = gen_key(); + ref.insert(key); + tbl.insert(key); + }); + + add_step("reserve", 10, [&] { tbl.reserve(static_cast<size_t>(rnd() % max_table_size)); }); + + add_step("find", 1000, [&] { + auto key = gen_key(); + auto ref_it = ref.find(key); + auto tbl_it = tbl.find(key); + ASSERT_EQ(ref_it == ref.end(), tbl_it == tbl.end()); + if (ref_it != ref.end()) { + ASSERT_EQ(*ref_it, *tbl_it); + } + }); + + add_step("find_and_erase", 100, [&] { + auto key = gen_key(); + auto ref_it = ref.find(key); + auto tbl_it = tbl.find(key); + ASSERT_EQ(ref_it == ref.end(), tbl_it == tbl.end()); + if (ref_it != ref.end()) { + ref.erase(ref_it); + tbl.erase(tbl_it); + } + }); + + add_step("remove_if", 5, [&] { + auto mul = rnd(); + auto bit = rnd() % 64; + auto condition = [&](auto &it) { + return (((it * mul) >> bit) & 1) == 0; + }; + td::table_remove_if(tbl, condition); + td::table_remove_if(ref, condition); + }); + + td::RandomSteps runner(std::move(steps)); + for (size_t i = 0; i < 10000000; i++) { + runner.step(rnd); + } +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp b/protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp index 36b0570530..0c4174db0f 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/HazardPointers.cpp @@ -1,13 +1,15 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "td/utils/common.h" #include "td/utils/HazardPointers.h" #include "td/utils/logging.h" #include "td/utils/port/thread.h" #include "td/utils/Random.h" +#include "td/utils/Slice.h" #include "td/utils/tests.h" #include <atomic> @@ -15,7 +17,7 @@ #if !TD_THREAD_UNSUPPORTED TEST(HazardPointers, stress) { struct Node { - std::atomic<std::string *> name_; + std::atomic<std::string *> name_{nullptr}; char pad[64]; }; int threads_n = 10; @@ -25,16 +27,16 @@ TEST(HazardPointers, stress) { int thread_id = 0; for (auto &thread : threads) { thread = td::thread([&, thread_id] { - auto holder = hazard_pointers.get_holder(thread_id, 0); + std::remove_reference_t<decltype(hazard_pointers)>::Holder holder(hazard_pointers, thread_id, 0); for (int i = 0; i < 1000000; i++) { auto &node = nodes[td::Random::fast(0, threads_n - 1)]; auto *str = holder.protect(node.name_); if (str) { - CHECK(*str == "one" || *str == "twotwo"); + CHECK(*str == td::Slice("one") || *str == td::Slice("twotwo")); } holder.clear(); if (td::Random::fast(0, 5) == 0) { - std::string *new_str = new std::string(td::Random::fast(0, 1) == 0 ? "one" : "twotwo"); + auto *new_str = new td::string(td::Random::fast_bool() ? "one" : "twotwo"); if (node.name_.compare_exchange_strong(str, new_str, std::memory_order_acq_rel)) { hazard_pointers.retire(thread_id, str); } else { @@ -48,11 +50,11 @@ TEST(HazardPointers, stress) { for (auto &thread : threads) { thread.join(); } - LOG(ERROR) << "Undeleted pointers: " << hazard_pointers.to_delete_size_unsafe(); + LOG(INFO) << "Undeleted pointers: " << hazard_pointers.to_delete_size_unsafe(); CHECK(static_cast<int>(hazard_pointers.to_delete_size_unsafe()) <= threads_n * threads_n); for (int i = 0; i < threads_n; i++) { hazard_pointers.retire(i); } CHECK(hazard_pointers.to_delete_size_unsafe() == 0); } -#endif //!TD_THREAD_UNSUPPORTED +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/HttpUrl.cpp b/protocols/Telegram/tdlib/td/tdutils/test/HttpUrl.cpp new file mode 100644 index 0000000000..6e91d48803 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/HttpUrl.cpp @@ -0,0 +1,54 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/HttpUrl.h" +#include "td/utils/tests.h" + +#include <utility> + +static void test_get_url_query_file_name(const char *prefix, const char *suffix, const char *file_name) { + auto path = td::string(prefix) + td::string(file_name) + td::string(suffix); + ASSERT_STREQ(file_name, td::get_url_query_file_name(path)); + ASSERT_STREQ(file_name, td::get_url_file_name("http://telegram.org" + path)); + ASSERT_STREQ(file_name, td::get_url_file_name("http://telegram.org:80" + path)); + ASSERT_STREQ(file_name, td::get_url_file_name("telegram.org" + path)); +} + +TEST(HttpUrl, get_url_query_file_name) { + for (auto suffix : {"?t=1#test", "#test?t=1", "#?t=1", "?t=1#", "#test", "?t=1", "#", "?", ""}) { + test_get_url_query_file_name("", suffix, ""); + test_get_url_query_file_name("/", suffix, ""); + test_get_url_query_file_name("/a/adasd/", suffix, ""); + test_get_url_query_file_name("/a/lklrjetn/", suffix, "adasd.asdas"); + test_get_url_query_file_name("/", suffix, "a123asadas"); + test_get_url_query_file_name("/", suffix, "\\a\\1\\2\\3\\a\\s\\a\\das"); + } +} + +static void test_parse_url_query(const td::string &query, const td::vector<td::string> &path, + const td::vector<std::pair<td::string, td::string>> &args) { + for (auto hash : {"", "#", "#?t=1", "#t=1&a=b"}) { + auto url_query = td::parse_url_query(query + hash); + ASSERT_EQ(path, url_query.path_); + ASSERT_EQ(args, url_query.args_); + } +} + +TEST(HttpUrl, parse_url_query) { + test_parse_url_query("", {}, {}); + test_parse_url_query("a", {"a"}, {}); + test_parse_url_query("/", {}, {}); + test_parse_url_query("//", {}, {}); + test_parse_url_query("///?a", {}, {{"a", ""}}); + test_parse_url_query("/a/b/c/", {"a", "b", "c"}, {}); + test_parse_url_query("/a/b/?c/", {td::string("a"), td::string("b")}, {{"c/", ""}}); + test_parse_url_query("?", {}, {}); + test_parse_url_query("???", {}, {{"??", ""}}); + test_parse_url_query("?a=b=c=d?e=f=g=h&x=y=z?d=3&", {}, {{"a", "b=c=d?e=f=g=h"}, {"x", "y=z?d=3"}}); + test_parse_url_query("c?&&&a=b", {"c"}, {{"a", "b"}}); + test_parse_url_query("c?&&&=b", {"c"}, {}); +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/List.cpp b/protocols/Telegram/tdlib/td/tdutils/test/List.cpp new file mode 100644 index 0000000000..02ab080d89 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/List.cpp @@ -0,0 +1,169 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/List.h" +#include "td/utils/MovableValue.h" +#include "td/utils/port/thread.h" +#include "td/utils/Random.h" +#include "td/utils/tests.h" +#include "td/utils/TsList.h" + +#include <atomic> +#include <mutex> +#include <set> +#include <utility> + +struct ListData { + td::MovableValue<td::uint64> value_; + td::MovableValue<bool> in_list_; + + ListData() = default; + ListData(td::uint64 value, bool in_list) : value_(value), in_list_(in_list) { + } +}; + +struct Node final : public td::ListNode { + Node() = default; + explicit Node(ListData data) : data_(std::move(data)) { + } + + ListData data_; +}; + +static ListData &get_data(Node &node) { + return node.data_; +} + +static ListData &get_data(td::TsListNode<ListData> &node) { + return node.get_data_unsafe(); +} + +static std::unique_lock<std::mutex> lock(td::ListNode &node) { + return {}; +} + +static std::unique_lock<std::mutex> lock(td::TsListNode<ListData> &node) { + return node.lock(); +} + +template <class ListNodeT, class ListRootT, class NodeT> +static void do_run_list_test(ListRootT &root, std::atomic<td::uint64> &id) { + td::vector<NodeT> nodes; + + td::Random::Xorshift128plus rnd(123); + + auto next_id = [&] { + return ++id; + }; + auto add_node = [&] { + if (nodes.size() >= 20) { + return; + } + nodes.push_back(NodeT({next_id(), false})); + }; + auto pop_node = [&] { + if (nodes.empty()) { + return; + } + nodes.pop_back(); + }; + auto random_node_index = [&] { + CHECK(!nodes.empty()); + return rnd.fast(0, static_cast<int>(nodes.size()) - 1); + }; + + auto link_node = [&] { + if (nodes.empty()) { + return; + } + auto i = random_node_index(); + nodes[i].remove(); + get_data(nodes[i]) = ListData(next_id(), true); + root.put(&nodes[i]); + }; + auto unlink_node = [&] { + if (nodes.empty()) { + return; + } + auto i = random_node_index(); + nodes[i].remove(); + get_data(nodes[i]).in_list_ = false; + }; + auto swap_nodes = [&] { + if (nodes.empty()) { + return; + } + auto i = random_node_index(); + auto j = random_node_index(); + std::swap(nodes[i], nodes[j]); + }; + auto set_node = [&] { + if (nodes.empty()) { + return; + } + auto i = random_node_index(); + auto j = random_node_index(); + nodes[i] = std::move(nodes[j]); + }; + auto validate = [&] { + std::multiset<td::uint64> in_list; + std::multiset<td::uint64> not_in_list; + for (auto &node : nodes) { + if (get_data(node).in_list_.get()) { + in_list.insert(get_data(node).value_.get()); + } else { + not_in_list.insert(get_data(node).value_.get()); + } + } + auto guard = lock(root); + for (auto *begin = root.begin(), *end = root.end(); begin != end; begin = begin->get_next()) { + auto &data = get_data(*static_cast<NodeT *>(begin)); + CHECK(data.in_list_.get()); + CHECK(data.value_.get() != 0); + auto it = in_list.find(data.value_.get()); + if (it != in_list.end()) { + in_list.erase(it); + } else { + ASSERT_EQ(0u, not_in_list.count(data.value_.get())); + } + } + ASSERT_EQ(0u, in_list.size()); + }; + td::RandomSteps steps( + {{add_node, 3}, {pop_node, 1}, {unlink_node, 1}, {link_node, 3}, {swap_nodes, 1}, {set_node, 1}, {validate, 1}}); + for (int i = 0; i < 10000; i++) { + steps.step(rnd); + } +} + +TEST(Misc, List) { + td::ListNode root; + std::atomic<td::uint64> id{0}; + for (std::size_t i = 0; i < 4; i++) { + do_run_list_test<td::ListNode, td::ListNode, Node>(root, id); + } +} + +TEST(Misc, TsList) { + td::TsList<ListData> root; + std::atomic<td::uint64> id{0}; + for (std::size_t i = 0; i < 4; i++) { + do_run_list_test<td::TsListNode<ListData>, td::TsList<ListData>, td::TsListNode<ListData>>(root, id); + } +} + +#if !TD_THREAD_UNSUPPORTED +TEST(Misc, TsListConcurrent) { + td::TsList<ListData> root; + td::vector<td::thread> threads; + std::atomic<td::uint64> id{0}; + for (std::size_t i = 0; i < 4; i++) { + threads.emplace_back( + [&] { do_run_list_test<td::TsListNode<ListData>, td::TsList<ListData>, td::TsListNode<ListData>>(root, id); }); + } +} +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp index 2da3f0cd3f..c038303c37 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/MpmcQueue.cpp @@ -1,9 +1,10 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "td/utils/common.h" #include "td/utils/logging.h" #include "td/utils/MpmcQueue.h" #include "td/utils/port/thread.h" @@ -49,7 +50,7 @@ TEST(OneValue, stress) { std::vector<td::thread> threads; td::OneValue<std::string> value; for (size_t i = 0; i < 2; i++) { - threads.push_back(td::thread([&, id = i] { + threads.emplace_back([&, id = i] { for (td::uint64 round = 1; round < 100000; round++) { if (id == 0) { value.reset(); @@ -68,7 +69,7 @@ TEST(OneValue, stress) { if (set_status) { CHECK(get_status); CHECK(from.empty()); - CHECK(to == "hello") << to; + LOG_CHECK(to == "hello") << to; } else { CHECK(!get_status); CHECK(from == "hello"); @@ -76,17 +77,17 @@ TEST(OneValue, stress) { } } } - })); + }); } for (auto &thread : threads) { thread.join(); } } -#endif //!TD_THREAD_UNSUPPORTED +#endif TEST(MpmcQueueBlock, simple) { // Test doesn't work now and it is ok, try_pop, logic changed - return; + /* td::MpmcQueueBlock<std::string> block(2); std::string x = "hello"; using PushStatus = td::MpmcQueueBlock<std::string>::PushStatus; @@ -111,6 +112,7 @@ TEST(MpmcQueueBlock, simple) { CHECK(pop_status == PopStatus::Ok); pop_status = block.try_pop(x); CHECK(pop_status == PopStatus::Closed); + */ } TEST(MpmcQueue, simple) { @@ -121,7 +123,7 @@ TEST(MpmcQueue, simple) { } for (int i = 0; i < 100; i++) { int x = q.pop(0); - CHECK(x == i) << x << " expected " << i; + LOG_CHECK(x == i) << x << " expected " << i; } } } @@ -188,18 +190,18 @@ TEST(MpmcQueue, multi_thread) { from[data.from] = data.value; } } - CHECK(all.size() == n * qn) << all.size(); + LOG_CHECK(all.size() == n * qn) << all.size(); std::sort(all.begin(), all.end(), [](const auto &a, const auto &b) { return std::tie(a.from, a.value) < std::tie(b.from, b.value); }); for (size_t i = 0; i < n * qn; i++) { CHECK(all[i].from == i / qn); CHECK(all[i].value == i % qn + 1); } - LOG(ERROR) << "Undeleted pointers: " << q.hazard_pointers_to_delele_size_unsafe(); + LOG(INFO) << "Undeleted pointers: " << q.hazard_pointers_to_delele_size_unsafe(); CHECK(q.hazard_pointers_to_delele_size_unsafe() <= (n + m + 1) * (n + m + 1)); for (size_t id = 0; id < n + m + 1; id++) { q.gc(id); } - CHECK(q.hazard_pointers_to_delele_size_unsafe() == 0) << q.hazard_pointers_to_delele_size_unsafe(); + LOG_CHECK(q.hazard_pointers_to_delele_size_unsafe() == 0) << q.hazard_pointers_to_delele_size_unsafe(); } -#endif //!TD_THREAD_UNSUPPORTED +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp b/protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp index e27e217713..4ac882dcaf 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/MpmcWaiter.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -13,21 +13,22 @@ #include <atomic> #if !TD_THREAD_UNSUPPORTED -TEST(MpmcWaiter, stress_one_one) { +template <class W> +static void test_waiter_stress_one_one() { td::Stage run; td::Stage check; std::vector<td::thread> threads; - std::atomic<size_t> value; + std::atomic<size_t> value{0}; size_t write_cnt = 10; - std::unique_ptr<td::MpmcWaiter> waiter; + td::unique_ptr<W> waiter; size_t threads_n = 2; for (size_t i = 0; i < threads_n; i++) { threads.push_back(td::thread([&, id = static_cast<td::uint32>(i)] { for (td::uint64 round = 1; round < 100000; round++) { if (id == 0) { value = 0; - waiter = std::make_unique<td::MpmcWaiter>(); + waiter = td::make_unique<W>(); write_cnt = td::Random::fast(1, 10); } run.wait(round * threads_n); @@ -37,17 +38,19 @@ TEST(MpmcWaiter, stress_one_one) { waiter->notify(); } } else { - int yields = 0; + typename W::Slot slot; + W::init_slot(slot, id); for (size_t i = 1; i <= write_cnt; i++) { while (true) { auto x = value.load(std::memory_order_relaxed); if (x >= i) { break; } - yields = waiter->wait(yields, id); + waiter->wait(slot); } - yields = waiter->stop_wait(yields, id); + waiter->stop_wait(slot); } + waiter->stop_wait(slot); } check.wait(round * threads_n); } @@ -57,19 +60,29 @@ TEST(MpmcWaiter, stress_one_one) { thread.join(); } } -TEST(MpmcWaiter, stress) { + +TEST(MpmcEagerWaiter, stress_one_one) { + test_waiter_stress_one_one<td::MpmcEagerWaiter>(); +} + +TEST(MpmcSleepyWaiter, stress_one_one) { + test_waiter_stress_one_one<td::MpmcSleepyWaiter>(); +} + +template <class W> +static void test_waiter_stress() { td::Stage run; td::Stage check; std::vector<td::thread> threads; size_t write_n; size_t read_n; - std::atomic<size_t> write_pos; - std::atomic<size_t> read_pos; + std::atomic<size_t> write_pos{0}; + std::atomic<size_t> read_pos{0}; size_t end_pos; size_t write_cnt; size_t threads_n = 20; - std::unique_ptr<td::MpmcWaiter> waiter; + td::unique_ptr<W> waiter; for (size_t i = 0; i < threads_n; i++) { threads.push_back(td::thread([&, id = static_cast<td::uint32>(i)] { for (td::uint64 round = 1; round < 1000; round++) { @@ -80,7 +93,7 @@ TEST(MpmcWaiter, stress) { end_pos = write_n * write_cnt; write_pos = 0; read_pos = 0; - waiter = std::make_unique<td::MpmcWaiter>(); + waiter = td::make_unique<W>(); } run.wait(round * threads_n); if (id <= write_n) { @@ -92,21 +105,26 @@ TEST(MpmcWaiter, stress) { waiter->notify(); } } else if (id > 10 && id - 10 <= read_n) { - int yields = 0; + typename W::Slot slot; + W::init_slot(slot, id); while (true) { auto x = read_pos.load(std::memory_order_relaxed); if (x == end_pos) { + waiter->stop_wait(slot); break; } if (x == write_pos.load(std::memory_order_relaxed)) { - yields = waiter->wait(yields, id); + waiter->wait(slot); continue; } - yields = waiter->stop_wait(yields, id); + waiter->stop_wait(slot); read_pos.compare_exchange_strong(x, x + 1, std::memory_order_relaxed); } } check.wait(round * threads_n); + if (id == 0) { + waiter->close(); + } } })); } @@ -114,4 +132,12 @@ TEST(MpmcWaiter, stress) { thread.join(); } } -#endif // !TD_THREAD_UNSUPPORTED + +TEST(MpmcEagerWaiter, stress_multi) { + test_waiter_stress<td::MpmcEagerWaiter>(); +} + +TEST(MpmcSleepyWaiter, stress_multi) { + test_waiter_stress<td::MpmcSleepyWaiter>(); +} +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp index 629e5b7223..43b0ccf086 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/MpscLinkQueue.cpp @@ -1,16 +1,17 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "td/utils/common.h" #include "td/utils/format.h" #include "td/utils/logging.h" #include "td/utils/MpscLinkQueue.h" #include "td/utils/port/thread.h" #include "td/utils/tests.h" -class NodeX : public td::MpscLinkQueueImpl::Node { +class NodeX final : public td::MpscLinkQueueImpl::Node { public: explicit NodeX(int value) : value_(value) { } @@ -29,8 +30,8 @@ class NodeX : public td::MpscLinkQueueImpl::Node { }; using QueueNode = td::MpscLinkQueueUniquePtrNode<NodeX>; -QueueNode create_node(int value) { - return QueueNode(std::make_unique<NodeX>(value)); +static QueueNode create_node(int value) { + return QueueNode(td::make_unique<NodeX>(value)); } TEST(MpscLinkQueue, one_thread) { @@ -48,7 +49,7 @@ TEST(MpscLinkQueue, one_thread) { while (auto node = reader.read()) { v.push_back(node.value().value()); } - CHECK((v == std::vector<int>{1, 2, 3, 4})) << td::format::as_array(v); + LOG_CHECK((v == std::vector<int>{1, 2, 3, 4})) << td::format::as_array(v); v.clear(); queue.push(create_node(5)); @@ -56,7 +57,7 @@ TEST(MpscLinkQueue, one_thread) { while (auto node = reader.read()) { v.push_back(node.value().value()); } - CHECK((v == std::vector<int>{5})) << td::format::as_array(v); + LOG_CHECK((v == std::vector<int>{5})) << td::format::as_array(v); } { @@ -70,7 +71,7 @@ TEST(MpscLinkQueue, one_thread) { while (auto node = reader.read()) { v.push_back(node.value().value()); } - CHECK((v == std::vector<int>{3, 2, 1, 0})) << td::format::as_array(v); + LOG_CHECK((v == std::vector<int>{3, 2, 1, 0})) << td::format::as_array(v); } } @@ -112,4 +113,4 @@ TEST(MpscLinkQueue, multi_thread) { thread.join(); } } -#endif //!TD_THREAD_UNSUPPORTED +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/OptionParser.cpp b/protocols/Telegram/tdlib/td/tdutils/test/OptionParser.cpp new file mode 100644 index 0000000000..8600eb9f19 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/OptionParser.cpp @@ -0,0 +1,82 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/misc.h" +#include "td/utils/OptionParser.h" +#include "td/utils/Slice.h" +#include "td/utils/tests.h" + +TEST(OptionParser, run) { + td::OptionParser options; + options.set_description("test description"); + + td::string exename = "exename"; + td::vector<td::string> args; + auto run_option_parser = [&](td::string command_line) { + args = td::full_split(std::move(command_line), ' '); + td::vector<char *> argv; + argv.push_back(&exename[0]); + for (auto &arg : args) { + argv.push_back(&arg[0]); + } + return options.run_impl(static_cast<int>(argv.size()), &argv[0], -1); + }; + + td::uint64 chosen_options = 0; + td::vector<td::string> chosen_parameters; + auto test_success = [&](td::string command_line, td::uint64 expected_options, + const td::vector<td::string> &expected_parameters, + const td::vector<td::string> &expected_result) { + chosen_options = 0; + chosen_parameters.clear(); + auto result = run_option_parser(std::move(command_line)); + ASSERT_TRUE(result.is_ok()); + ASSERT_EQ(expected_options, chosen_options); + ASSERT_EQ(expected_parameters, chosen_parameters); + ASSERT_EQ(expected_result.size(), result.ok().size()); + for (size_t i = 0; i < expected_result.size(); i++) { + ASSERT_STREQ(expected_result[i], td::string(result.ok()[i])); + } + }; + auto test_fail = [&](td::string command_line) { + auto result = run_option_parser(std::move(command_line)); + ASSERT_TRUE(result.is_error()); + }; + + options.add_option('q', "", "", [&] { chosen_options += 1; }); + options.add_option('\0', "http-port2", "", [&] { chosen_options += 10; }); + options.add_option('p', "http-port", "", [&](td::Slice parameter) { + chosen_options += 100; + chosen_parameters.push_back(parameter.str()); + }); + options.add_option('v', "test", "", [&] { chosen_options += 1000; }); + + test_fail("-http-port2"); + test_success("-", 0, {}, {"-"}); + test_fail("--http-port"); + test_fail("--http-port3"); + test_fail("--http-por"); + test_fail("--http-port2=1"); + test_fail("--q"); + test_fail("-qvp"); + test_fail("-p"); + test_fail("-u"); + test_success("-q", 1, {}, {}); + test_success("-vvvvvvvvvv", 10000, {}, {}); + test_success("-qpv", 101, {"v"}, {}); + test_success("-qp -v", 101, {"-v"}, {}); + test_success("-qp --http-port2", 101, {"--http-port2"}, {}); + test_success("-qp -- -v", 1101, {"--"}, {}); + test_success("-qvqvpqv", 2102, {"qv"}, {}); + test_success("aba --http-port2 caba --http-port2 dabacaba", 20, {}, {"aba", "caba", "dabacaba"}); + test_success("das -pqwerty -- -v asd --http-port", 100, {"qwerty"}, {"das", "-v", "asd", "--http-port"}); + test_success("-p option --http-port option2 --http-port=option3 --http-port=", 400, + {"option", "option2", "option3", ""}, {}); + test_success("", 0, {}, {}); + test_success("a", 0, {}, {"a"}); + test_success("", 0, {}, {}); +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp b/protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp index 6a5a20015f..c5c963bedc 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/OrderedEventsProcessor.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -18,8 +18,8 @@ TEST(OrderedEventsProcessor, random) { int offset = 1000000; std::vector<std::pair<int, int>> v; for (int i = 0; i < n; i++) { - auto shift = td::Random::fast(0, 1) ? td::Random::fast(0, d) : td::Random::fast(0, 1) * d; - v.push_back({i + shift, i + offset}); + auto shift = td::Random::fast_bool() ? td::Random::fast(0, d) : td::Random::fast(0, 1) * d; + v.emplace_back(i + shift, i + offset); } std::sort(v.begin(), v.end()); diff --git a/protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp b/protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp index 61d956f4e6..a4762e25f3 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/SharedObjectPool.cpp @@ -1,10 +1,10 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include "td/utils/logging.h" +#include "td/utils/common.h" #include "td/utils/SharedObjectPool.h" #include "td/utils/tests.h" @@ -56,7 +56,16 @@ TEST(SharedPtr, simple) { ptr2 = std::move(ptr); CHECK(ptr.empty()); CHECK(*ptr2 == "hello"); +#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-assign-overloaded" +#endif ptr2 = ptr2; +#if TD_CLANG +#pragma clang diagnostic pop +#endif CHECK(*ptr2 == "hello"); CHECK(!Deleter::was_delete()); ptr2.reset(); @@ -80,15 +89,15 @@ TEST(SharedObjectPool, simple) { }; { td::SharedObjectPool<Node> pool; - pool.alloc(); - pool.alloc(); - pool.alloc(); - pool.alloc(); - pool.alloc(); + { auto ptr1 = pool.alloc(); } + { auto ptr2 = pool.alloc(); } + { auto ptr3 = pool.alloc(); } + { auto ptr4 = pool.alloc(); } + { auto ptr5 = pool.alloc(); } CHECK(Node::cnt() == 0); CHECK(pool.total_size() == 1); CHECK(pool.calc_free_size() == 1); - pool.alloc(), pool.alloc(), pool.alloc(); + { auto ptr6 = pool.alloc(), ptr7 = pool.alloc(), ptr8 = pool.alloc(); } CHECK(pool.total_size() == 3); CHECK(pool.calc_free_size() == 3); } diff --git a/protocols/Telegram/tdlib/td/tdutils/test/SharedSlice.cpp b/protocols/Telegram/tdlib/td/tdutils/test/SharedSlice.cpp new file mode 100644 index 0000000000..7327f0dbb3 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/SharedSlice.cpp @@ -0,0 +1,91 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/port/thread.h" +#include "td/utils/SharedSlice.h" +#include "td/utils/tests.h" + +char disable_linker_warning_about_empty_file_tdutils_test_shared_slice_cpp TD_UNUSED; + +#if !TD_THREAD_UNSUPPORTED +TEST(SharedSlice, Hands) { + { + td::SharedSlice h("hello"); + ASSERT_EQ("hello", h.as_slice()); + // auto g = h; // CE + auto g = h.clone(); + ASSERT_EQ("hello", h.as_slice()); + ASSERT_EQ("hello", g.as_slice()); + } + + { + td::SharedSlice h("hello"); + td::UniqueSharedSlice g(std::move(h)); + ASSERT_EQ("", h.as_slice()); + ASSERT_EQ("hello", g.as_slice()); + } + { + td::SharedSlice h("hello"); + td::SharedSlice t = h.clone(); + td::UniqueSharedSlice g(std::move(h)); + ASSERT_EQ("", h.as_slice()); + ASSERT_EQ("hello", g.as_slice()); + ASSERT_EQ("hello", t.as_slice()); + } + + { + td::UniqueSharedSlice g(5); + g.as_mutable_slice().copy_from("hello"); + td::SharedSlice h(std::move(g)); + ASSERT_EQ("hello", h); + ASSERT_EQ("", g); + } + + { + td::UniqueSlice h("hello"); + td::UniqueSlice g(std::move(h)); + ASSERT_EQ("", h.as_slice()); + ASSERT_EQ("hello", g.as_slice()); + } + + { + td::SecureString h("hello"); + td::SecureString g(std::move(h)); + ASSERT_EQ("", h.as_slice()); + ASSERT_EQ("hello", g.as_slice()); + } + + { + td::Stage stage; + td::SharedSlice a; + td::SharedSlice b; + td::vector<td::thread> threads(2); + for (int i = 0; i < 2; i++) { + threads[i] = td::thread([i, &stage, &a, &b] { + for (int j = 0; j < 10000; j++) { + if (i == 0) { + a = td::SharedSlice("hello"); + b = a.clone(); + } + stage.wait((2 * j + 1) * 2); + if (i == 0) { + ASSERT_EQ('h', a[0]); + a.clear(); + } else { + td::UniqueSharedSlice c(std::move(b)); + c.as_mutable_slice()[0] = '!'; + } + stage.wait((2 * j + 2) * 2); + } + }); + } + for (auto &thread : threads) { + thread.join(); + } + } +} +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/StealingQueue.cpp b/protocols/Telegram/tdlib/td/tdutils/test/StealingQueue.cpp new file mode 100644 index 0000000000..453a63179f --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/StealingQueue.cpp @@ -0,0 +1,180 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/AtomicRead.h" +#include "td/utils/benchmark.h" +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/MpmcQueue.h" +#include "td/utils/port/thread.h" +#include "td/utils/Random.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/StealingQueue.h" +#include "td/utils/tests.h" + +#include <atomic> +#include <cstring> + +TEST(StealingQueue, very_simple) { + td::StealingQueue<int, 8> q; + q.local_push(1, [](auto x) { UNREACHABLE(); }); + int x; + CHECK(q.local_pop(x)); + ASSERT_EQ(1, x); +} + +#if !TD_THREAD_UNSUPPORTED +TEST(AtomicRead, simple) { + td::Stage run; + td::Stage check; + + std::size_t threads_n = 10; + td::vector<td::thread> threads; + + int x{0}; + std::atomic<int> version{0}; + + td::int64 res = 0; + for (std::size_t i = 0; i < threads_n; i++) { + threads.emplace_back([&, id = static_cast<td::uint32>(i)] { + for (td::uint64 round = 1; round < 10000; round++) { + run.wait(round * threads_n); + if (id == 0) { + version++; + x++; + version++; + } else { + int y = 0; + auto v1 = version.load(); + y = x; + auto v2 = version.load(); + if (v1 == v2 && v1 % 2 == 0) { + res += y; + } + } + + check.wait(round * threads_n); + } + }); + } + td::do_not_optimize_away(res); + for (auto &thread : threads) { + thread.join(); + } +} + +TEST(AtomicRead, simple2) { + td::Stage run; + td::Stage check; + + std::size_t threads_n = 10; + td::vector<td::thread> threads; + + struct Value { + td::uint64 value = 0; + char str[50] = "0 0 0 0"; + }; + td::AtomicRead<Value> value; + + auto to_str = [](td::uint64 i) { + return PSTRING() << i << " " << i << " " << i << " " << i; + }; + for (std::size_t i = 0; i < threads_n; i++) { + threads.emplace_back([&, id = static_cast<td::uint32>(i)] { + for (td::uint64 round = 1; round < 10000; round++) { + run.wait(round * threads_n); + if (id == 0) { + auto x = value.lock(); + x->value = round; + auto str = to_str(round); + std::memcpy(x->str, str.c_str(), str.size() + 1); + } else { + Value x; + value.read(x); + LOG_CHECK(x.value == round || x.value == round - 1) << x.value << " " << round; + CHECK(x.str == to_str(x.value)); + } + check.wait(round * threads_n); + } + }); + } + for (auto &thread : threads) { + thread.join(); + } +} + +TEST(StealingQueue, simple) { + td::uint64 sum = 0; + std::atomic<td::uint64> got_sum{0}; + + td::Stage run; + td::Stage check; + + std::size_t threads_n = 10; + td::vector<td::thread> threads; + td::vector<td::StealingQueue<int, 8>> lq(threads_n); + td::MpmcQueue<int> gq(threads_n); + + constexpr td::uint64 XN = 20; + td::uint64 x_sum[XN]; + x_sum[0] = 0; + x_sum[1] = 1; + for (td::uint64 i = 2; i < XN; i++) { + x_sum[i] = i + x_sum[i - 1] + x_sum[i - 2]; + } + + td::Random::Xorshift128plus rnd(123); + for (std::size_t i = 0; i < threads_n; i++) { + threads.emplace_back([&, id = static_cast<td::uint32>(i)] { + for (td::uint64 round = 1; round < 1000; round++) { + if (id == 0) { + sum = 0; + auto n = static_cast<int>(rnd() % 5); + for (int j = 0; j < n; j++) { + auto x = static_cast<int>(rnd() % XN); + sum += x_sum[x]; + gq.push(x, id); + } + got_sum = 0; + } + run.wait(round * threads_n); + while (got_sum.load() != sum) { + auto x = [&] { + int res; + if (lq[id].local_pop(res)) { + return res; + } + if (gq.try_pop(res, id)) { + return res; + } + if (lq[id].steal(res, lq[static_cast<size_t>(rnd()) % threads_n])) { + //LOG(ERROR) << "STEAL"; + return res; + } + return 0; + }(); + if (x == 0) { + continue; + } + //LOG(ERROR) << x << " " << got_sum.load() << " " << sum; + got_sum.fetch_add(x, std::memory_order_relaxed); + lq[id].local_push(x - 1, [&](auto y) { + //LOG(ERROR) << "OVERFLOW"; + gq.push(y, id); + }); + if (x > 1) { + lq[id].local_push(x - 2, [&](auto y) { gq.push(y, id); }); + } + } + check.wait(round * threads_n); + } + }); + } + for (auto &thread : threads) { + thread.join(); + } +} +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashMap.cpp b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashMap.cpp new file mode 100644 index 0000000000..38def77772 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashMap.cpp @@ -0,0 +1,95 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/Random.h" +#include "td/utils/tests.h" +#include "td/utils/WaitFreeHashMap.h" + +TEST(WaitFreeHashMap, stress_test) { + td::Random::Xorshift128plus rnd(123); + td::FlatHashMap<td::uint64, td::uint64> reference; + td::WaitFreeHashMap<td::uint64, td::uint64> map; + + td::vector<td::RandomSteps::Step> steps; + auto add_step = [&](td::uint32 weight, auto f) { + steps.emplace_back(td::RandomSteps::Step{std::move(f), weight}); + }; + + auto gen_key = [&] { + return rnd() % 100000 + 1; + }; + + auto check = [&](bool check_size = false) { + if (check_size) { + ASSERT_EQ(reference.size(), map.calc_size()); + } + ASSERT_EQ(reference.empty(), map.empty()); + + if (reference.size() < 100) { + td::uint64 result = 0; + for (auto &it : reference) { + result += it.first * 101; + result += it.second; + } + map.foreach([&](const td::uint64 &key, td::uint64 &value) { + result -= key * 101; + result -= value; + }); + ASSERT_EQ(0u, result); + } + }; + + add_step(2000, [&] { + auto key = gen_key(); + auto value = rnd(); + reference[key] = value; + if (td::Random::fast_bool()) { + map.set(key, value); + } else { + map[key] = value; + } + ASSERT_EQ(reference[key], map.get(key)); + check(); + }); + + add_step(200, [&] { + auto key = gen_key(); + ASSERT_EQ(reference[key], map[key]); + check(true); + }); + + add_step(2000, [&] { + auto key = gen_key(); + auto ref_it = reference.find(key); + auto ref_value = ref_it == reference.end() ? 0 : ref_it->second; + ASSERT_EQ(ref_value, map.get(key)); + check(); + }); + + add_step(500, [&] { + auto key = gen_key(); + size_t reference_erased_count = reference.erase(key); + size_t map_erased_count = map.erase(key); + ASSERT_EQ(reference_erased_count, map_erased_count); + check(); + }); + + td::RandomSteps runner(std::move(steps)); + for (size_t i = 0; i < 1000000; i++) { + runner.step(rnd); + } + + for (size_t test = 0; test < 1000; test++) { + reference = {}; + map = {}; + + for (size_t i = 0; i < 100; i++) { + runner.step(rnd); + } + } +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashSet.cpp b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashSet.cpp new file mode 100644 index 0000000000..ec4096c850 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeHashSet.cpp @@ -0,0 +1,73 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/FlatHashSet.h" +#include "td/utils/Random.h" +#include "td/utils/tests.h" +#include "td/utils/WaitFreeHashSet.h" + +TEST(WaitFreeHashSet, stress_test) { + td::Random::Xorshift128plus rnd(123); + td::FlatHashSet<td::uint64> reference; + td::WaitFreeHashSet<td::uint64> set; + + td::vector<td::RandomSteps::Step> steps; + auto add_step = [&](td::uint32 weight, auto f) { + steps.emplace_back(td::RandomSteps::Step{std::move(f), weight}); + }; + + auto gen_key = [&] { + return rnd() % 100000 + 1; + }; + + auto check = [&](bool check_size = false) { + if (check_size) { + ASSERT_EQ(reference.size(), set.calc_size()); + } + ASSERT_EQ(reference.empty(), set.empty()); + + if (reference.size() < 100) { + td::uint64 result = 0; + for (auto &it : reference) { + result += it * 101; + } + set.foreach([&](const td::uint64 &key) { result -= key * 101; }); + ASSERT_EQ(0u, result); + } + }; + + add_step(2000, [&] { + auto key = gen_key(); + ASSERT_EQ(reference.count(key), set.count(key)); + reference.insert(key); + set.insert(key); + ASSERT_EQ(reference.count(key), set.count(key)); + check(); + }); + + add_step(500, [&] { + auto key = gen_key(); + size_t reference_erased_count = reference.erase(key); + size_t set_erased_count = set.erase(key); + ASSERT_EQ(reference_erased_count, set_erased_count); + check(); + }); + + td::RandomSteps runner(std::move(steps)); + for (size_t i = 0; i < 1000000; i++) { + runner.step(rnd); + } + + for (size_t test = 0; test < 1000; test++) { + reference = {}; + set = {}; + + for (size_t i = 0; i < 100; i++) { + runner.step(rnd); + } + } +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeVector.cpp b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeVector.cpp new file mode 100644 index 0000000000..0f0cc58796 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/WaitFreeVector.cpp @@ -0,0 +1,69 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/Random.h" +#include "td/utils/tests.h" +#include "td/utils/WaitFreeVector.h" + +TEST(WaitFreeVector, stress_test) { + td::Random::Xorshift128plus rnd(123); + td::vector<td::uint64> reference; + td::WaitFreeVector<td::uint64> vector; + + td::vector<td::RandomSteps::Step> steps; + auto add_step = [&](td::uint32 weight, auto f) { + steps.emplace_back(td::RandomSteps::Step{std::move(f), weight}); + }; + + auto gen_key = [&] { + return static_cast<size_t>(rnd() % reference.size()); + }; + + add_step(2000, [&] { + ASSERT_EQ(reference.size(), vector.size()); + ASSERT_EQ(reference.empty(), vector.empty()); + if (reference.empty()) { + return; + } + auto key = gen_key(); + ASSERT_EQ(reference[key], vector[key]); + auto value = rnd(); + reference[key] = value; + vector[key] = value; + ASSERT_EQ(reference[key], vector[key]); + }); + + add_step(2000, [&] { + ASSERT_EQ(reference.size(), vector.size()); + ASSERT_EQ(reference.empty(), vector.empty()); + auto value = rnd(); + reference.emplace_back(value); + if (rnd() & 1) { + vector.emplace_back(value); + } else if (rnd() & 1) { + vector.push_back(value); + } else { + vector.push_back(std::move(value)); + } + ASSERT_EQ(reference.back(), vector.back()); + }); + + add_step(500, [&] { + ASSERT_EQ(reference.size(), vector.size()); + ASSERT_EQ(reference.empty(), vector.empty()); + if (reference.empty()) { + return; + } + reference.pop_back(); + vector.pop_back(); + }); + + td::RandomSteps runner(std::move(steps)); + for (size_t i = 0; i < 1000000; i++) { + runner.step(rnd); + } +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/bitmask.cpp b/protocols/Telegram/tdlib/td/tdutils/test/bitmask.cpp new file mode 100644 index 0000000000..e81c406bbe --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/bitmask.cpp @@ -0,0 +1,249 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/common.h" +#include "td/utils/misc.h" +#include "td/utils/Slice.h" +#include "td/utils/Status.h" +#include "td/utils/tests.h" +#include "td/utils/utf8.h" + +#include <algorithm> + +namespace td { + +class RangeSet { + template <class T> + static auto find(T &ranges, int64 begin) { + return std::lower_bound(ranges.begin(), ranges.end(), begin, + [](const Range &range, int64 begin) { return range.end < begin; }); + } + auto find(int64 begin) const { + return find(ranges_, begin); + } + auto find(int64 begin) { + return find(ranges_, begin); + } + + public: + struct Range { + int64 begin; + int64 end; + }; + + static constexpr int64 BIT_SIZE = 1024; + + static RangeSet create_one_range(int64 end, int64 begin = 0) { + RangeSet res; + res.ranges_.push_back({begin, end}); + return res; + } + static Result<RangeSet> decode(CSlice data) { + if (!check_utf8(data)) { + return Status::Error("Invalid encoding"); + } + uint32 curr = 0; + bool is_empty = false; + RangeSet res; + for (auto begin = data.ubegin(); begin != data.uend();) { + uint32 size; + begin = next_utf8_unsafe(begin, &size); + + if (!is_empty && size != 0) { + res.ranges_.push_back({curr * BIT_SIZE, (curr + size) * BIT_SIZE}); + } + curr += size; + is_empty = !is_empty; + } + return res; + } + + string encode(int64 prefix_size = -1) const { + vector<uint32> sizes; + uint32 all_end = 0; + + if (prefix_size != -1) { + prefix_size = (prefix_size + BIT_SIZE - 1) / BIT_SIZE * BIT_SIZE; + } + for (auto it : ranges_) { + if (prefix_size != -1 && it.begin >= prefix_size) { + break; + } + if (prefix_size != -1 && it.end > prefix_size) { + it.end = prefix_size; + } + + CHECK(it.begin % BIT_SIZE == 0); + CHECK(it.end % BIT_SIZE == 0); + auto begin = narrow_cast<uint32>(it.begin / BIT_SIZE); + auto end = narrow_cast<uint32>(it.end / BIT_SIZE); + if (sizes.empty()) { + if (begin != 0) { + sizes.push_back(0); + sizes.push_back(begin); + } + } else { + sizes.push_back(begin - all_end); + } + sizes.push_back(end - begin); + all_end = end; + } + + string res; + for (auto c : sizes) { + append_utf8_character(res, c); + } + return res; + } + + int64 get_ready_prefix_size(int64 offset, int64 file_size = -1) const { + auto it = find(offset); + if (it == ranges_.end()) { + return 0; + } + if (it->begin > offset) { + return 0; + } + CHECK(offset >= it->begin); + CHECK(offset <= it->end); + auto end = it->end; + if (file_size != -1 && end > file_size) { + end = file_size; + } + if (end < offset) { + return 0; + } + return end - offset; + } + int64 get_total_size(int64 file_size) const { + int64 res = 0; + for (auto it : ranges_) { + if (it.begin >= file_size) { + break; + } + if (it.end > file_size) { + it.end = file_size; + } + res += it.end - it.begin; + } + return res; + } + int64 get_ready_parts(int64 offset_part, int32 part_size) const { + auto offset = offset_part * part_size; + auto it = find(offset); + if (it == ranges_.end()) { + return 0; + } + if (it->begin > offset) { + return 0; + } + return (it->end - offset) / part_size; + } + + bool is_ready(int64 begin, int64 end) const { + auto it = find(begin); + if (it == ranges_.end()) { + return false; + } + return it->begin <= begin && end <= it->end; + } + + void set(int64 begin, int64 end) { + CHECK(begin % BIT_SIZE == 0); + CHECK(end % BIT_SIZE == 0); + // 1. skip all with r.end < begin + auto it_begin = find(begin); + + // 2. combine with all r.begin <= end + auto it_end = it_begin; + for (; it_end != ranges_.end() && it_end->begin <= end; ++it_end) { + } + + if (it_begin == it_end) { + ranges_.insert(it_begin, Range{begin, end}); + } else { + begin = td::min(begin, it_begin->begin); + --it_end; + end = td::max(end, it_end->end); + *it_end = Range{begin, end}; + ranges_.erase(it_begin, it_end); + } + } + + vector<int32> as_vector(int32 part_size) const { + vector<int32> res; + for (const auto &it : ranges_) { + auto begin = narrow_cast<int32>((it.begin + part_size - 1) / part_size); + auto end = narrow_cast<int32>(it.end / part_size); + while (begin < end) { + res.push_back(begin++); + } + } + return res; + } + + private: + vector<Range> ranges_; +}; + +TEST(Bitmask, simple) { + auto validate_encoding = [](auto &rs) { + auto str = rs.encode(); + RangeSet rs2 = RangeSet::decode(str).move_as_ok(); + auto str2 = rs2.encode(); + rs = std::move(rs2); + CHECK(str2 == str); + }; + { + RangeSet rs; + int32 S = 128 * 1024; + int32 O = S * 5000; + for (int i = 1; i < 30; i++) { + if (i % 2 == 0) { + rs.set(O + S * i, O + S * (i + 1)); + } + } + validate_encoding(rs); + } + { + RangeSet rs; + int32 S = 1024; + auto get = [&](auto p) { + return rs.get_ready_prefix_size(p * S) / S; + }; + auto set = [&](auto l, auto r) { + rs.set(l * S, r * S); + validate_encoding(rs); + ASSERT_TRUE(rs.is_ready(l * S, r * S)); + ASSERT_TRUE(get(l) >= (r - l)); + }; + set(6, 7); + ASSERT_EQ(1, get(6)); + ASSERT_EQ(0, get(5)); + set(8, 9); + ASSERT_EQ(0, get(7)); + set(7, 8); + ASSERT_EQ(2, get(7)); + ASSERT_EQ(3, get(6)); + set(3, 5); + ASSERT_EQ(1, get(4)); + set(4, 6); + ASSERT_EQ(5, get(4)); + set(10, 11); + set(9, 10); + ASSERT_EQ(8, get(3)); + set(14, 16); + set(12, 13); + ASSERT_EQ(8, get(3)); + + ASSERT_EQ(10, rs.get_ready_prefix_size(S * 3, S * 3 + 10)); + ASSERT_TRUE(!rs.is_ready(S * 11, S * 12)); + ASSERT_EQ(3, rs.get_ready_parts(2, S * 2)); + ASSERT_EQ(vector<int32>({2, 3, 4, 7}), rs.as_vector(S * 2)); + } +} + +} // namespace td diff --git a/protocols/Telegram/tdlib/td/tdutils/test/buffer.cpp b/protocols/Telegram/tdlib/td/tdutils/test/buffer.cpp new file mode 100644 index 0000000000..4bc406cc64 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/buffer.cpp @@ -0,0 +1,56 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/tests.h" + +#include "td/utils/buffer.h" +#include "td/utils/Random.h" + +TEST(Buffer, buffer_builder) { + { + td::BufferBuilder builder; + builder.append("b"); + builder.prepend("a"); + builder.append("c"); + ASSERT_EQ(builder.extract().as_slice(), "abc"); + } + { + td::BufferBuilder builder{"hello", 0, 0}; + ASSERT_EQ(builder.extract().as_slice(), "hello"); + } + { + td::BufferBuilder builder{"hello", 1, 1}; + builder.prepend("A "); + builder.append(" B"); + ASSERT_EQ(builder.extract().as_slice(), "A hello B"); + } + { + auto str = td::rand_string('a', 'z', 10000); + auto split_str = td::rand_split(str); + + int l = td::Random::fast(0, static_cast<int>(split_str.size() - 1)); + int r = l; + td::BufferBuilder builder(split_str[l], 123, 1000); + while (l != 0 || r != static_cast<int>(split_str.size()) - 1) { + if (l == 0 || (td::Random::fast_bool() && r != static_cast<int>(split_str.size() - 1))) { + r++; + if (td::Random::fast_bool()) { + builder.append(split_str[r]); + } else { + builder.append(td::BufferSlice(split_str[r])); + } + } else { + l--; + if (td::Random::fast_bool()) { + builder.prepend(split_str[l]); + } else { + builder.prepend(td::BufferSlice(split_str[l])); + } + } + } + ASSERT_EQ(builder.extract().as_slice(), str); + } +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp b/protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp index faf4ef61a4..9e81ef132c 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/crypto.cpp @@ -1,20 +1,47 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/base64.h" +#include "td/utils/benchmark.h" #include "td/utils/common.h" #include "td/utils/crypto.h" +#include "td/utils/Random.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" #include "td/utils/tests.h" +#include "td/utils/UInt.h" #include <limits> static td::vector<td::string> strings{"", "1", "short test string", td::string(1000000, 'a')}; #if TD_HAVE_OPENSSL +#if TD_HAVE_ZLIB +TEST(Crypto, Aes) { + td::Random::Xorshift128plus rnd(123); + td::UInt256 key; + rnd.bytes(as_slice(key)); + td::string plaintext(16, '\0'); + td::string encrypted(16, '\0'); + td::string decrypted(16, '\0'); + rnd.bytes(plaintext); + + td::AesState encryptor; + encryptor.init(as_slice(key), true); + td::AesState decryptor; + decryptor.init(as_slice(key), false); + + encryptor.encrypt(td::as_slice(plaintext).ubegin(), td::as_slice(encrypted).ubegin(), 16); + decryptor.decrypt(td::as_slice(encrypted).ubegin(), td::as_slice(decrypted).ubegin(), 16); + + CHECK(decrypted == plaintext); + CHECK(decrypted != encrypted); + CHECK(td::crc32(encrypted) == 178892237); +} + TEST(Crypto, AesCtrState) { td::vector<td::uint32> answers1{0u, 1141589763u, 596296607u, 3673001485u, 2302125528u, 330967191u, 2047392231u, 3537459563u, 307747798u, 2149598133u}; @@ -42,46 +69,177 @@ TEST(Crypto, AesCtrState) { } td::AesCtrState state; - state.init(key, iv); + state.init(as_slice(key), as_slice(iv)); td::string t(length, '\0'); - state.encrypt(s, t); + std::size_t pos = 0; + for (const auto &str : td::rand_split(td::string(length, '\0'))) { + auto len = str.size(); + state.encrypt(td::Slice(s).substr(pos, len), td::MutableSlice(t).substr(pos, len)); + pos += len; + } ASSERT_EQ(answers1[i], td::crc32(t)); - state.init(key, iv); - state.decrypt(t, t); - ASSERT_STREQ(s, t); + state.init(as_slice(key), as_slice(iv)); + pos = 0; + for (const auto &str : td::rand_split(td::string(length, '\0'))) { + auto len = str.size(); + state.decrypt(td::Slice(t).substr(pos, len), td::MutableSlice(t).substr(pos, len)); + pos += len; + } + ASSERT_STREQ(td::base64_encode(s), td::base64_encode(t)); for (auto &c : iv.raw) { c = 0xFF; } - state.init(key, iv); - state.encrypt(s, t); + state.init(as_slice(key), as_slice(iv)); + pos = 0; + for (const auto &str : td::rand_split(td::string(length, '\0'))) { + auto len = str.size(); + state.encrypt(td::Slice(s).substr(pos, len), td::MutableSlice(t).substr(pos, len)); + pos += len; + } ASSERT_EQ(answers2[i], td::crc32(t)); i++; } } +TEST(Crypto, AesIgeState) { + td::vector<td::uint32> answers1{0u, 2045698207u, 2423540300u, 525522475u, 1545267325u, 724143417u}; + + std::size_t i = 0; + for (auto length : {0, 16, 32, 256, 1024, 65536}) { + td::uint32 seed = length; + td::string s(length, '\0'); + for (auto &c : s) { + seed = seed * 123457567u + 987651241u; + c = static_cast<char>((seed >> 23) & 255); + } + + td::UInt256 key; + for (auto &c : key.raw) { + seed = seed * 123457567u + 987651241u; + c = (seed >> 23) & 255; + } + td::UInt256 iv; + for (auto &c : iv.raw) { + seed = seed * 123457567u + 987651241u; + c = (seed >> 23) & 255; + } + + td::AesIgeState state; + state.init(as_slice(key), as_slice(iv), true); + td::string t(length, '\0'); + td::UInt256 iv_copy = iv; + td::string u(length, '\0'); + std::size_t pos = 0; + for (const auto &str : td::rand_split(td::string(length / 16, '\0'))) { + auto len = 16 * str.size(); + state.encrypt(td::Slice(s).substr(pos, len), td::MutableSlice(t).substr(pos, len)); + td::aes_ige_encrypt(as_slice(key), as_slice(iv_copy), td::Slice(s).substr(pos, len), + td::MutableSlice(u).substr(pos, len)); + pos += len; + } + + ASSERT_EQ(answers1[i], td::crc32(t)); + ASSERT_EQ(answers1[i], td::crc32(u)); + + state.init(as_slice(key), as_slice(iv), false); + iv_copy = iv; + pos = 0; + for (const auto &str : td::rand_split(td::string(length / 16, '\0'))) { + auto len = 16 * str.size(); + state.decrypt(td::Slice(t).substr(pos, len), td::MutableSlice(t).substr(pos, len)); + td::aes_ige_decrypt(as_slice(key), as_slice(iv_copy), td::Slice(u).substr(pos, len), + td::MutableSlice(u).substr(pos, len)); + pos += len; + } + ASSERT_STREQ(td::base64_encode(s), td::base64_encode(t)); + ASSERT_STREQ(td::base64_encode(s), td::base64_encode(u)); + + i++; + } +} + +TEST(Crypto, AesCbcState) { + td::vector<td::uint32> answers1{0u, 3617355989u, 3449188102u, 186999968u, 4244808847u, 2626031206u}; + + std::size_t i = 0; + for (auto length : {0, 16, 32, 256, 1024, 65536}) { + td::uint32 seed = length; + td::string s(length, '\0'); + for (auto &c : s) { + seed = seed * 123457567u + 987651241u; + c = static_cast<char>((seed >> 23) & 255); + } + + td::UInt256 key; + for (auto &c : key.raw) { + seed = seed * 123457567u + 987651241u; + c = (seed >> 23) & 255; + } + td::UInt128 iv; + for (auto &c : iv.raw) { + seed = seed * 123457567u + 987651241u; + c = (seed >> 23) & 255; + } + + td::AesCbcState state(as_slice(key), as_slice(iv)); + td::string t(length, '\0'); + td::UInt128 iv_copy = iv; + td::string u(length, '\0'); + std::size_t pos = 0; + for (const auto &str : td::rand_split(td::string(length / 16, '\0'))) { + auto len = 16 * str.size(); + state.encrypt(td::Slice(s).substr(pos, len), td::MutableSlice(t).substr(pos, len)); + td::aes_cbc_encrypt(as_slice(key), as_slice(iv_copy), td::Slice(s).substr(pos, len), + td::MutableSlice(u).substr(pos, len)); + pos += len; + } + + ASSERT_EQ(answers1[i], td::crc32(t)); + ASSERT_EQ(answers1[i], td::crc32(u)); + + state = td::AesCbcState(as_slice(key), as_slice(iv)); + iv_copy = iv; + pos = 0; + for (const auto &str : td::rand_split(td::string(length / 16, '\0'))) { + auto len = 16 * str.size(); + state.decrypt(td::Slice(t).substr(pos, len), td::MutableSlice(t).substr(pos, len)); + td::aes_cbc_decrypt(as_slice(key), as_slice(iv_copy), td::Slice(u).substr(pos, len), + td::MutableSlice(u).substr(pos, len)); + pos += len; + } + ASSERT_STREQ(td::base64_encode(s), td::base64_encode(t)); + ASSERT_STREQ(td::base64_encode(s), td::base64_encode(u)); + + i++; + } +} +#endif + TEST(Crypto, Sha256State) { for (auto length : {0, 1, 31, 32, 33, 9999, 10000, 10001, 999999, 1000001}) { auto s = td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), length); td::UInt256 baseline; - td::sha256(s, td::MutableSlice(baseline.raw, 32)); + td::sha256(s, as_slice(baseline)); td::Sha256State state; - td::sha256_init(&state); + state.init(); + td::Sha256State state2 = std::move(state); auto v = td::rand_split(s); for (auto &x : v) { - td::sha256_update(x, &state); + state2.feed(x); } + state = std::move(state2); td::UInt256 result; - td::sha256_final(&state, td::MutableSlice(result.raw, 32)); + state.extract(as_slice(result)); ASSERT_TRUE(baseline == result); } } TEST(Crypto, PBKDF) { - td::vector<td::string> passwords{"", "qwerty", std::string(1000, 'a')}; - td::vector<td::string> salts{"", "qwerty", std::string(1000, 'a')}; + td::vector<td::string> passwords{"", "qwerty", td::string(1000, 'a')}; + td::vector<td::string> salts{"", "qwerty", td::string(1000, 'a')}; td::vector<int> iteration_counts{1, 2, 1000}; td::vector<td::Slice> answers{ "984LZT0tcqQQjPWr6RL/3Xd2Ftu7J6cOggTzri0Pb60=", "lzmEEdaupDp3rO+SImq4J41NsGaL0denanJfdoCsRcU=", @@ -145,6 +303,32 @@ TEST(Crypto, md5) { ASSERT_STREQ(answers[i], td::base64_encode(output)); } } + +TEST(Crypto, hmac_sha256) { + td::vector<td::Slice> answers{ + "t33rfT85UOe6N00BhsNwobE+f2TnW331HhdvQ4GdJp8=", "BQl5HF2jqhCz4JTqhAs+H364oxboh7QlluOMHuuRVh8=", + "NCCPuZBsAPBd/qr3SyeYE+e1RNgzkKJCS/+eXDBw8zU=", "mo3ahTkyLKfoQoYA0s7vRZULuH++vqwFJD0U5n9HHw0="}; + + for (std::size_t i = 0; i < strings.size(); i++) { + td::string output(32, '\0'); + td::hmac_sha256("cucumber", strings[i], output); + ASSERT_STREQ(answers[i], td::base64_encode(output)); + } +} + +TEST(Crypto, hmac_sha512) { + td::vector<td::Slice> answers{ + "o28hTN1m/TGlm/VYxDIzOdUE4wMpQzO8hVcTkiP2ezEJXtrOvCjRnl20aOV1S8axA5Te0TzIjfIoEAtpzamIsA==", + "32X3GslSz0HDznSrCNt++ePRcFVSUSD+tfOVannyxS+yLt/om11qILCE64RFTS8/B84gByMzC3FuAlfcIam/KA==", + "BVqe5rK1Fg1i+C7xXTAzT9vDPcf3kQQpTtse6rT/EVDzKo9AUo4ZwyUyJ0KcLHoffIjul/TuJoBg+wLz7Z7r7g==", + "WASmeku5Pcfz7N0Kp4Q3I9sxtO2MiaBXA418CY0HvjdtmAo7QY+K3E0o9UemgGzz41KqeypzRC92MwOAOnXJLA=="}; + + for (std::size_t i = 0; i < strings.size(); i++) { + td::string output(64, '\0'); + td::hmac_sha512("cucumber", strings[i], output); + ASSERT_STREQ(answers[i], td::base64_encode(output)); + } +} #endif #if TD_HAVE_ZLIB @@ -157,6 +341,69 @@ TEST(Crypto, crc32) { } #endif +#if TD_HAVE_CRC32C +TEST(Crypto, crc32c) { + td::vector<td::uint32> answers{0u, 2432014819u, 1077264849u, 1131405888u}; + + for (std::size_t i = 0; i < strings.size(); i++) { + ASSERT_EQ(answers[i], td::crc32c(strings[i])); + + auto v = td::rand_split(strings[i]); + td::uint32 a = 0; + td::uint32 b = 0; + for (auto &x : v) { + a = td::crc32c_extend(a, x); + auto x_crc = td::crc32c(x); + b = td::crc32c_extend(b, x_crc, x.size()); + } + ASSERT_EQ(answers[i], a); + ASSERT_EQ(answers[i], b); + } +} + +TEST(Crypto, crc32c_benchmark) { + class Crc32cExtendBenchmark final : public td::Benchmark { + public: + explicit Crc32cExtendBenchmark(size_t chunk_size) : chunk_size_(chunk_size) { + } + td::string get_description() const final { + return PSTRING() << "Crc32c with chunk_size=" << chunk_size_; + } + void start_up_n(int n) final { + if (n > (1 << 20)) { + cnt_ = n / (1 << 20); + n = (1 << 20); + } else { + cnt_ = 1; + } + data_ = td::string(n, 'a'); + } + void run(int n) final { + td::uint32 res = 0; + for (int i = 0; i < cnt_; i++) { + td::Slice data(data_); + while (!data.empty()) { + auto head = data.substr(0, chunk_size_); + data = data.substr(head.size()); + res = td::crc32c_extend(res, head); + } + } + td::do_not_optimize_away(res); + } + + private: + size_t chunk_size_; + td::string data_; + int cnt_; + }; + bench(Crc32cExtendBenchmark(2)); + bench(Crc32cExtendBenchmark(8)); + bench(Crc32cExtendBenchmark(32)); + bench(Crc32cExtendBenchmark(128)); + bench(Crc32cExtendBenchmark(65536)); +} +#endif + TEST(Crypto, crc64) { td::vector<td::uint64> answers{0ull, 3039664240384658157ull, 17549519902062861804ull, 8794730974279819706ull}; @@ -164,3 +411,61 @@ TEST(Crypto, crc64) { ASSERT_EQ(answers[i], td::crc64(strings[i])); } } + +TEST(Crypto, crc16) { + td::vector<td::uint16> answers{0, 9842, 25046, 37023}; + + for (std::size_t i = 0; i < strings.size(); i++) { + ASSERT_EQ(answers[i], td::crc16(strings[i])); + } +} + +static td::Slice rsa_private_key = R"ABCD( +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDeYT5/prmLEa2Q +tZND+UwTmif8kl2VlXaMCjj1k1lJJq8BqS8cVM2vPnOPzFoiC2LYykhm4kk7goCC +ZH6wez9yakg28fcq0Ycv0x8DL1K+VKHJuwIhVfQs//IY1/cBOrMESc+NQowPbv1t +TIFxBO2gebnpLuseht8ix7XtpGC4qAaHN2aEvT2cRsnA76TAK1RVxf1OYGUFBDzY +318WpVZfVIjcQ7K9+eU6b2Yb84VLlvJXw3e1rvw+fBzx2EjpD4zhXy11YppWDyV6 +HEb2hs3cGS/LbHfHvdcSfil2omaJP97MDEEY2HFxjR/E5CEf2suvPzX4XS3RE+S3 +2aEJaaQbAgMBAAECggEAKo3XRNwls0wNt5xXcvF4smOUdUuY5u/0AHZQUgYBVvM1 +GA9E+ZnsxjUgLgs/0DX3k16aHj39H4sohksuxxy+lmlqKkGBN8tioC85RwW+Qre1 +QgIsNS7ai+XqcQCavrx51z88nV53qNhnXIwAVR1JT6Ubg1i8G1pZxrEKyk/jRlJd +mGjf6vjitH//PPkghPJ/D42k93YRcy+duOgqYDQpLZp8DiEGfYrX10B1H7HrWLV+ +Wp5KO1YXtKgQUplj6kYy72bVajbxYTvzgjaaKsh74jBO0uT3tHTtXG0dcKGb0VR/ +cqP/1H/lC9bAnAqAGefNusGJQZIElvTsrpIQXOeZsQKBgQD2W04S+FjqYYFjnEFX +6eL4it01afs5M3/C6CcI5JQtN6p+Na4NCSILol33xwhakn87zqdADHawBYQVQ8Uw +dPurl805wfkzN3AbfdDmtx0IJ8vK4HFpktRjfpwBVhlVtm1doAYFqqsuCF2vWW1t +mM2YOSq4AnRHCeBb/P6kRIW0MwKBgQDnFawKKqiC4tuyBOkkEhexlm7x9he0md7D +3Z2hc3Bmdcq1niw4wBq3HUxGLReGCcSr5epKSQwkunlTn5ZSC6Rmbe4zxsGIwbb3 +5W3342swBaoxEIuBokBvZ/xUOXVwiqKj+S/NzVkZcnT6K9V/HnUCQR+JBbQxFQaX +iiezcjKoeQKBgCIVUcDoIQ0UPl10ocmy7xbpx177calhSZzCl5vwW9vBptHdRV5C +VDZ92ThNjgdR205/8b23u7fwm2yBusdQd/0ufFMwVfTTB6yWBI/W56pYLya7VJWB +nebB/n1k1w53tbvNRugDy7kLqUJ4Qd521ILp7dIVbNbjM+omH2jEnibnAoGBAIM5 +a1jaoJay/M86uqohHBNcuePtO8jzF+1iDAGC7HFCsrov+CzB6mnR2V6AfLtBEM4M +4d8NXDf/LKawGUy+D72a74m3dG+UkbJ0Nt5t5pB+pwb1vkL/QFgDVOb/OhGOqI01 +FFBqLA6nUIZAHhzxzsBY+u90rb6xkey8J49faiUBAoGAaMgOgEvQB5H19ZL5tMkl +A/DKtTz/NFzN4Zw/vNPVb7eNn4jg9M25d9xqvL4acOa+nuV3nLHbcUWE1/7STXw1 +gT58CvoEmD1AiP95nup+HKHENJ1DWMgF5MDfVQwGCvWP5/Qy89ybr0eG8HjbldbN +MpSmzz2wOz152oGdOd3syT4= +-----END PRIVATE KEY----- +)ABCD"; + +static td::Slice rsa_public_key = R"ABCD( +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3mE+f6a5ixGtkLWTQ/lM +E5on/JJdlZV2jAo49ZNZSSavAakvHFTNrz5zj8xaIgti2MpIZuJJO4KAgmR+sHs/ +cmpINvH3KtGHL9MfAy9SvlShybsCIVX0LP/yGNf3ATqzBEnPjUKMD279bUyBcQTt +oHm56S7rHobfIse17aRguKgGhzdmhL09nEbJwO+kwCtUVcX9TmBlBQQ82N9fFqVW +X1SI3EOyvfnlOm9mG/OFS5byV8N3ta78Pnwc8dhI6Q+M4V8tdWKaVg8lehxG9obN +3Bkvy2x3x73XEn4pdqJmiT/ezAxBGNhxcY0fxOQhH9rLrz81+F0t0RPkt9mhCWmk +GwIDAQAB +-----END PUBLIC KEY----- +)ABCD"; + +TEST(Crypto, rsa) { + auto value = td::rand_string('a', 'z', 200); + auto encrypted_value = td::rsa_encrypt_pkcs1_oaep(rsa_public_key, value).move_as_ok(); + auto decrypted_value = td::rsa_decrypt_pkcs1_oaep(rsa_private_key, encrypted_value.as_slice()).move_as_ok(); + ASSERT_TRUE(decrypted_value.as_slice().truncate(value.size()) == value); +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/emoji.cpp b/protocols/Telegram/tdlib/td/tdutils/test/emoji.cpp new file mode 100644 index 0000000000..c8e6539a99 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/emoji.cpp @@ -0,0 +1,128 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/emoji.h" +#include "td/utils/tests.h" + +TEST(Emoji, is_emoji) { + ASSERT_TRUE(!td::is_emoji("")); + ASSERT_TRUE(td::is_emoji("👩🏼❤💋👩🏻")); + ASSERT_TRUE(td::is_emoji("👩🏼❤️💋👩🏻")); + ASSERT_TRUE(!td::is_emoji("👩🏼❤️️💋👩🏻")); + ASSERT_TRUE(td::is_emoji("⌚")); + ASSERT_TRUE(td::is_emoji("↔")); + ASSERT_TRUE(td::is_emoji("🪗")); + ASSERT_TRUE(td::is_emoji("2️⃣")); + ASSERT_TRUE(td::is_emoji("2⃣")); + ASSERT_TRUE(!td::is_emoji(" 2⃣")); + ASSERT_TRUE(!td::is_emoji("2⃣ ")); + ASSERT_TRUE(!td::is_emoji(" ")); + ASSERT_TRUE(!td::is_emoji("")); + ASSERT_TRUE(!td::is_emoji("1234567890123456789012345678901234567890123456789012345678901234567890")); + ASSERT_TRUE(td::is_emoji("❤️")); + ASSERT_TRUE(td::is_emoji("❤")); + ASSERT_TRUE(td::is_emoji("⌚")); + ASSERT_TRUE(td::is_emoji("🎄")); + ASSERT_TRUE(td::is_emoji("🧑🎄")); +} + +static void test_get_fitzpatrick_modifier(td::string emoji, int result) { + ASSERT_EQ(result, td::get_fitzpatrick_modifier(emoji)); +} + +TEST(Emoji, get_fitzpatrick_modifier) { + test_get_fitzpatrick_modifier("", 0); + test_get_fitzpatrick_modifier("👩🏼❤💋👩🏻", 2); + test_get_fitzpatrick_modifier("👩🏼❤️💋👩🏻", 2); + test_get_fitzpatrick_modifier("👋", 0); + test_get_fitzpatrick_modifier("👋🏻", 2); + test_get_fitzpatrick_modifier("👋🏼", 3); + test_get_fitzpatrick_modifier("👋🏽", 4); + test_get_fitzpatrick_modifier("👋🏾", 5); + test_get_fitzpatrick_modifier("👋🏿", 6); + test_get_fitzpatrick_modifier("🏻", 2); + test_get_fitzpatrick_modifier("🏼", 3); + test_get_fitzpatrick_modifier("🏽", 4); + test_get_fitzpatrick_modifier("🏾", 5); + test_get_fitzpatrick_modifier("🏿", 6); + test_get_fitzpatrick_modifier("⌚", 0); + test_get_fitzpatrick_modifier("↔", 0); + test_get_fitzpatrick_modifier("🪗", 0); + test_get_fitzpatrick_modifier("2️⃣", 0); + test_get_fitzpatrick_modifier("2⃣", 0); + test_get_fitzpatrick_modifier("❤️", 0); + test_get_fitzpatrick_modifier("❤", 0); + test_get_fitzpatrick_modifier("⌚", 0); + test_get_fitzpatrick_modifier("🎄", 0); + test_get_fitzpatrick_modifier("🧑🎄", 0); +} + +static void test_remove_emoji_modifiers(td::string emoji, const td::string &result) { + ASSERT_STREQ(result, td::remove_emoji_modifiers(emoji)); + td::remove_emoji_modifiers_in_place(emoji); + ASSERT_STREQ(result, emoji); + ASSERT_STREQ(emoji, td::remove_emoji_modifiers(emoji)); +} + +TEST(Emoji, remove_emoji_modifiers) { + test_remove_emoji_modifiers("", ""); + test_remove_emoji_modifiers("👩🏼❤💋👩🏻", "👩❤💋👩"); + test_remove_emoji_modifiers("👩🏼❤️💋👩🏻", "👩❤💋👩"); + test_remove_emoji_modifiers("👋🏻", "👋"); + test_remove_emoji_modifiers("👋🏼", "👋"); + test_remove_emoji_modifiers("👋🏽", "👋"); + test_remove_emoji_modifiers("👋🏾", "👋"); + test_remove_emoji_modifiers("👋🏿", "👋"); + test_remove_emoji_modifiers("🏻", "🏻"); + test_remove_emoji_modifiers("🏼", "🏼"); + test_remove_emoji_modifiers("🏽", "🏽"); + test_remove_emoji_modifiers("🏾", "🏾"); + test_remove_emoji_modifiers("🏿", "🏿"); + test_remove_emoji_modifiers("⌚", "⌚"); + test_remove_emoji_modifiers("↔", "↔"); + test_remove_emoji_modifiers("🪗", "🪗"); + test_remove_emoji_modifiers("2️⃣", "2⃣"); + test_remove_emoji_modifiers("2⃣", "2⃣"); + test_remove_emoji_modifiers("❤️", "❤"); + test_remove_emoji_modifiers("❤", "❤"); + test_remove_emoji_modifiers("⌚", "⌚"); + test_remove_emoji_modifiers("️", "️"); + test_remove_emoji_modifiers("️️️🏻", "️️️🏻"); + test_remove_emoji_modifiers("️️️🏻a", "a"); + test_remove_emoji_modifiers("🎄", "🎄"); + test_remove_emoji_modifiers("🧑🎄", "🧑🎄"); +} + +static void test_remove_emoji_selectors(td::string emoji, const td::string &result) { + ASSERT_STREQ(result, td::remove_emoji_selectors(result)); + ASSERT_STREQ(result, td::remove_emoji_selectors(emoji)); +} + +TEST(Emoji, remove_emoji_selectors) { + test_remove_emoji_selectors("", ""); + test_remove_emoji_selectors("👩🏼❤💋👩🏻", "👩🏼❤💋👩🏻"); + test_remove_emoji_selectors("👩🏼❤️💋👩🏻", "👩🏼❤💋👩🏻"); + test_remove_emoji_selectors("👋🏻", "👋🏻"); + test_remove_emoji_selectors("👋🏼", "👋🏼"); + test_remove_emoji_selectors("👋🏽", "👋🏽"); + test_remove_emoji_selectors("👋🏾", "👋🏾"); + test_remove_emoji_selectors("👋🏿", "👋🏿"); + test_remove_emoji_selectors("🏻", "🏻"); + test_remove_emoji_selectors("🏼", "🏼"); + test_remove_emoji_selectors("🏽", "🏽"); + test_remove_emoji_selectors("🏾", "🏾"); + test_remove_emoji_selectors("🏿", "🏿"); + test_remove_emoji_selectors("⌚", "⌚"); + test_remove_emoji_selectors("↔", "↔"); + test_remove_emoji_selectors("🪗", "🪗"); + test_remove_emoji_selectors("2️⃣", "2⃣"); + test_remove_emoji_selectors("2⃣", "2⃣"); + test_remove_emoji_selectors("❤️", "❤"); + test_remove_emoji_selectors("❤", "❤"); + test_remove_emoji_selectors("⌚", "⌚"); + test_remove_emoji_selectors("🎄", "🎄"); + test_remove_emoji_selectors("🧑🎄", "🧑🎄"); +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp b/protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp index a0a92c14eb..de2def1e5e 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/filesystem.cpp @@ -1,41 +1,47 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/filesystem.h" +#include "td/utils/Slice.h" #include "td/utils/tests.h" +static void test_clean_filename(td::CSlice name, td::Slice result) { + ASSERT_STREQ(td::clean_filename(name), result); +} + TEST(Misc, clean_filename) { - using td::clean_filename; - ASSERT_STREQ(clean_filename("-1234567"), "-1234567"); - ASSERT_STREQ(clean_filename(".git"), "git"); - ASSERT_STREQ(clean_filename("../../.git"), "git"); - ASSERT_STREQ(clean_filename(".././.."), ""); - ASSERT_STREQ(clean_filename("../"), ""); - ASSERT_STREQ(clean_filename(".."), ""); - ASSERT_STREQ(clean_filename("test/git/ as dsa . a"), "as dsa.a"); - ASSERT_STREQ(clean_filename(" . "), ""); - ASSERT_STREQ(clean_filename("!@#$%^&*()_+-=[]{;|:\"}'<>?,.`~"), "!@#$%^ ()_+-=[]{; } ,.~"); - ASSERT_STREQ(clean_filename("!@#$%^&*()_+-=[]{}\\|:\";'<>?,.`~"), "; ,.~"); - ASSERT_STREQ(clean_filename("عرفها بعد قد. هذا مع تاريخ اليميني واندونيسيا،, لعدم تاريخ لهيمنة الى"), - "عرفها بعد قد.هذا مع تاريخ اليميني"); - ASSERT_STREQ( - clean_filename( - "012345678901234567890123456789012345678901234567890123456789adsasdasdsaa.01234567890123456789asdasdasdasd"), - "012345678901234567890123456789012345678901234567890123456789.01234567890123456789"); - ASSERT_STREQ(clean_filename("01234567890123456789012345678901234567890123456789<>*?: <>*?:0123456789adsasdasdsaa. " - "0123456789`<><<>><><>0123456789asdasdasdasd"), - "01234567890123456789012345678901234567890123456789.0123456789"); - ASSERT_STREQ(clean_filename("01234567890123456789012345678901234567890123456789<>*?: <>*?:0123456789adsasdasdsaa. " - "0123456789`<><><>0123456789asdasdasdasd"), - "01234567890123456789012345678901234567890123456789.0123456789 012"); - ASSERT_STREQ(clean_filename("C:/document.tar.gz"), "document.tar.gz"); - ASSERT_STREQ(clean_filename("test...."), "test"); - ASSERT_STREQ(clean_filename("....test"), "test"); - ASSERT_STREQ(clean_filename("test.exe...."), "test.exe"); // extension has changed - ASSERT_STREQ(clean_filename("test.exe01234567890123456789...."), - "test.exe01234567890123456789"); // extension may be more then 20 characters - ASSERT_STREQ(clean_filename("....test....asdf"), "test.asdf"); + test_clean_filename("-1234567", "-1234567"); + test_clean_filename(".git", "git"); + test_clean_filename("../../.git", "git"); + test_clean_filename(".././..", ""); + test_clean_filename("../", ""); + test_clean_filename("..", ""); + test_clean_filename("test/git/ as dsa . a", "as dsa.a"); + test_clean_filename(" . ", ""); + test_clean_filename("!@#$%^&*()_+-=[]{;|:\"}'<>?,.`~", "!@#$%^ ()_+-=[]{; } ,.~"); + test_clean_filename("!@#$%^&*()_+-=[]{}\\|:\";'<>?,.`~", "; ,.~"); + test_clean_filename("عرفها بعد قد. هذا مع تاريخ اليميني واندونيسيا،, لعدم تاريخ لهيمنة الى", + "عرفها بعد قد.هذا مع تاريخ الي"); + test_clean_filename( + "012345678901234567890123456789012345678901234567890123456789adsasdasdsaa.01234567890123456789asdasdasdasd", + "012345678901234567890123456789012345678901234567890123456789adsa.0123456789012345"); + test_clean_filename( + "01234567890123456789012345678901234567890123456789adsa<>*?: <>*?:0123456789adsasdasdsaa. " + "0123456789`<><<>><><>0123456789asdasdasdasd", + "01234567890123456789012345678901234567890123456789adsa.0123456789"); + test_clean_filename( + "012345678901234567890123456789012345678901234567890123<>*?: <>*?:0123456789adsasdasdsaa. " + "0123456789`<>0123456789asdasdasdasd", + "012345678901234567890123456789012345678901234567890123.0123456789 012"); + test_clean_filename("C:/document.tar.gz", "document.tar.gz"); + test_clean_filename("test....", "test"); + test_clean_filename("....test", "test"); + test_clean_filename("test.exe....", "test.exe"); // extension has changed + test_clean_filename("test.exe01234567890123456789....", + "test.exe01234567890123456789"); // extension may be more than 16 characters + test_clean_filename("....test....asdf", "test.asdf"); + test_clean_filename("കറുപ്പ്.txt", "കറപപ.txt"); } diff --git a/protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp b/protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp index e4bd81eb0d..32d75474e8 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/gzip.cpp @@ -1,49 +1,59 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/ByteFlow.h" +#include "td/utils/common.h" #include "td/utils/Gzip.h" #include "td/utils/GzipByteFlow.h" #include "td/utils/logging.h" +#include "td/utils/port/thread_local.h" +#include "td/utils/Slice.h" #include "td/utils/Status.h" #include "td/utils/tests.h" +#include "td/utils/Time.h" -static void encode_decode(td::string s) { +static void encode_decode(const td::string &s) { auto r = td::gzencode(s, 2); ASSERT_TRUE(!r.empty()); - if (r.empty()) { - return; - } - auto new_s = td::gzdecode(r.as_slice()); - ASSERT_TRUE(!new_s.empty()); - if (new_s.empty()) { - return; - } - ASSERT_EQ(s, new_s.as_slice().str()); + ASSERT_EQ(s, td::gzdecode(r.as_slice())); } TEST(Gzip, gzencode_gzdecode) { - auto str = td::rand_string(0, 127, 1000); - encode_decode(str); - str = td::rand_string('a', 'z', 1000000); - encode_decode(str); - str = td::string(1000000, 'a'); - encode_decode(str); + encode_decode(td::rand_string(0, 255, 1000)); + encode_decode(td::rand_string('a', 'z', 1000000)); + encode_decode(td::string(1000000, 'a')); +} + +static void test_gzencode(const td::string &s) { + auto begin_time = td::Time::now(); + auto r = td::gzencode(s, td::max(2, static_cast<int>(100 / s.size()))); + ASSERT_TRUE(!r.empty()); + LOG(INFO) << "Encoded string of size " << s.size() << " in " << (td::Time::now() - begin_time) + << " with compression ratio " << static_cast<double>(r.size()) / static_cast<double>(s.size()); +} + +TEST(Gzip, gzencode) { + for (size_t len = 1; len <= 10000000; len *= 10) { + test_gzencode(td::rand_string('a', 'a', len)); + test_gzencode(td::rand_string('a', 'z', len)); + test_gzencode(td::rand_string(0, 255, len)); + } } TEST(Gzip, flow) { auto str = td::rand_string('a', 'z', 1000000); auto parts = td::rand_split(str); - auto input_writer = td::ChainBufferWriter::create_empty(); + td::ChainBufferWriter input_writer; auto input = input_writer.extract_reader(); td::ByteFlowSource source(&input); - td::GzipByteFlow gzip_flow(td::Gzip::Encode); - gzip_flow = td::GzipByteFlow(td::Gzip::Encode); + td::GzipByteFlow gzip_flow(td::Gzip::Mode::Encode); + gzip_flow = td::GzipByteFlow(td::Gzip::Mode::Encode); td::ByteFlowSink sink; source >> gzip_flow >> sink; @@ -63,14 +73,15 @@ TEST(Gzip, flow) { } TEST(Gzip, flow_error) { auto str = td::rand_string('a', 'z', 1000000); - auto zip = td::gzencode(str).as_slice().str(); + auto zip = td::gzencode(str, 0.9).as_slice().str(); + ASSERT_TRUE(!zip.empty()); zip.resize(zip.size() - 1); auto parts = td::rand_split(zip); auto input_writer = td::ChainBufferWriter(); auto input = input_writer.extract_reader(); td::ByteFlowSource source(&input); - td::GzipByteFlow gzip_flow(td::Gzip::Decode); + td::GzipByteFlow gzip_flow(td::Gzip::Mode::Decode); td::ByteFlowSink sink; source >> gzip_flow >> sink; @@ -89,13 +100,13 @@ TEST(Gzip, flow_error) { TEST(Gzip, encode_decode_flow) { auto str = td::rand_string('a', 'z', 1000000); auto parts = td::rand_split(str); - auto input_writer = td::ChainBufferWriter::create_empty(); + td::ChainBufferWriter input_writer; auto input = input_writer.extract_reader(); td::ByteFlowSource source(&input); - td::GzipByteFlow gzip_encode_flow(td::Gzip::Encode); - td::GzipByteFlow gzip_decode_flow(td::Gzip::Decode); - td::GzipByteFlow gzip_encode_flow2(td::Gzip::Encode); - td::GzipByteFlow gzip_decode_flow2(td::Gzip::Decode); + td::GzipByteFlow gzip_encode_flow(td::Gzip::Mode::Encode); + td::GzipByteFlow gzip_decode_flow(td::Gzip::Mode::Decode); + td::GzipByteFlow gzip_encode_flow2(td::Gzip::Mode::Encode); + td::GzipByteFlow gzip_decode_flow2(td::Gzip::Mode::Decode); td::ByteFlowSink sink; source >> gzip_encode_flow >> gzip_decode_flow >> gzip_encode_flow2 >> gzip_decode_flow2 >> sink; @@ -111,3 +122,126 @@ TEST(Gzip, encode_decode_flow) { ASSERT_TRUE(sink.status().is_ok()); ASSERT_EQ(str, sink.result()->move_as_buffer_slice().as_slice().str()); } + +TEST(Gzip, encode_decode_flow_big) { + td::clear_thread_locals(); + auto start_mem = td::BufferAllocator::get_buffer_mem(); + { + auto str = td::string(200000, 'a'); + td::ChainBufferWriter input_writer; + auto input = input_writer.extract_reader(); + td::ByteFlowSource source(&input); + td::GzipByteFlow gzip_encode_flow(td::Gzip::Mode::Encode); + td::GzipByteFlow gzip_decode_flow(td::Gzip::Mode::Decode); + td::GzipByteFlow gzip_encode_flow2(td::Gzip::Mode::Encode); + td::GzipByteFlow gzip_decode_flow2(td::Gzip::Mode::Decode); + td::ByteFlowSink sink; + source >> gzip_encode_flow >> gzip_decode_flow >> gzip_encode_flow2 >> gzip_decode_flow2 >> sink; + + ASSERT_TRUE(!sink.is_ready()); + size_t n = 200; + size_t left_size = n * str.size(); + auto validate = [&](td::Slice chunk) { + CHECK(chunk.size() <= left_size); + left_size -= chunk.size(); + ASSERT_TRUE(td::all_of(chunk, [](auto c) { return c == 'a'; })); + }; + + for (size_t i = 0; i < n; i++) { + input_writer.append(str); + source.wakeup(); + auto extra_mem = td::BufferAllocator::get_buffer_mem() - start_mem; + // limit means nothing. just check that we do not use 200Mb or so + CHECK(extra_mem < (10 << 20)); + + auto size = sink.get_output()->size(); + validate(sink.get_output()->cut_head(size).move_as_buffer_slice().as_slice()); + } + 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()); + validate(sink.result()->move_as_buffer_slice().as_slice()); + ASSERT_EQ(0u, left_size); + } + td::clear_thread_locals(); + ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem()); +} + +TEST(Gzip, decode_encode_flow_bomb) { + td::string gzip_bomb_str; + size_t N = 200; + { + 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 (size_t i = 0; i < N; 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(); + } + + td::clear_thread_locals(); + auto start_mem = td::BufferAllocator::get_buffer_mem(); + { + td::ChainBufferWriter input_writer; + auto input = input_writer.extract_reader(); + td::ByteFlowSource source(&input); + td::GzipByteFlow::Options decode_options; + decode_options.write_watermark.low = 2 << 20; + decode_options.write_watermark.high = 4 << 20; + td::GzipByteFlow::Options encode_options; + encode_options.read_watermark.low = 2 << 20; + encode_options.read_watermark.high = 4 << 20; + td::GzipByteFlow gzip_decode_flow(td::Gzip::Mode::Decode); + gzip_decode_flow.set_options(decode_options); + td::GzipByteFlow gzip_encode_flow(td::Gzip::Mode::Encode); + gzip_encode_flow.set_options(encode_options); + td::GzipByteFlow gzip_decode_flow2(td::Gzip::Mode::Decode); + gzip_decode_flow2.set_options(decode_options); + td::GzipByteFlow gzip_encode_flow2(td::Gzip::Mode::Encode); + gzip_encode_flow2.set_options(encode_options); + td::GzipByteFlow gzip_decode_flow3(td::Gzip::Mode::Decode); + gzip_decode_flow3.set_options(decode_options); + td::ByteFlowSink sink; + source >> gzip_decode_flow >> gzip_encode_flow >> gzip_decode_flow2 >> gzip_encode_flow2 >> gzip_decode_flow3 >> + sink; + + ASSERT_TRUE(!sink.is_ready()); + size_t left_size = N * (1 << 16); + auto validate = [&](td::Slice chunk) { + CHECK(chunk.size() <= left_size); + left_size -= chunk.size(); + ASSERT_TRUE(td::all_of(chunk, [](auto c) { return c == 'a'; })); + }; + + input_writer.append(gzip_bomb_str); + source.close_input(td::Status::OK()); + + do { + gzip_decode_flow3.wakeup(); + gzip_decode_flow2.wakeup(); + gzip_decode_flow.wakeup(); + source.wakeup(); + auto extra_mem = td::BufferAllocator::get_buffer_mem() - start_mem; + // limit means nothing. just check that we do not use 15Mb or so + CHECK(extra_mem < (5 << 20)); + auto size = sink.get_output()->size(); + validate(sink.get_output()->cut_head(size).move_as_buffer_slice().as_slice()); + } while (!sink.is_ready()); + ASSERT_EQ(0u, left_size); + } + td::clear_thread_locals(); + ASSERT_EQ(start_mem, td::BufferAllocator::get_buffer_mem()); +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/hashset_benchmark.cpp b/protocols/Telegram/tdlib/td/tdutils/test/hashset_benchmark.cpp new file mode 100644 index 0000000000..f07f58c8f0 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/hashset_benchmark.cpp @@ -0,0 +1,647 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/algorithm.h" +#include "td/utils/common.h" +#include "td/utils/FlatHashMap.h" +#include "td/utils/FlatHashMapChunks.h" +#include "td/utils/FlatHashTable.h" +#include "td/utils/format.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/logging.h" +#include "td/utils/MapNode.h" +#include "td/utils/Random.h" +#include "td/utils/Slice.h" +#include "td/utils/Span.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Time.h" +#include "td/utils/VectorQueue.h" + +#ifdef SCOPE_EXIT +#undef SCOPE_EXIT +#endif + +#include <absl/container/flat_hash_map.h> +#include <absl/hash/hash.h> +#include <algorithm> +#include <benchmark/benchmark.h> +#include <folly/container/F14Map.h> +#include <functional> +#include <map> +#include <random> +#include <unordered_map> +#include <utility> + +template <class TableT> +static void reserve(TableT &table, std::size_t size) { + table.reserve(size); +} + +template <class A, class B> +static void reserve(std::map<A, B> &table, std::size_t size) { +} + +template <class KeyT, class ValueT> +class NoOpTable { + public: + using key_type = KeyT; + using value_type = std::pair<const KeyT, ValueT>; + template <class It> + NoOpTable(It begin, It end) { + } + + ValueT &operator[](const KeyT &) const { + static ValueT dummy; + return dummy; + } + + KeyT find(const KeyT &key) const { + return key; + } +}; + +template <class KeyT, class ValueT> +class VectorTable { + public: + using key_type = KeyT; + using value_type = std::pair<const KeyT, ValueT>; + template <class It> + VectorTable(It begin, It end) : table_(begin, end) { + } + + ValueT &operator[](const KeyT &needle) { + auto it = find(needle); + if (it == table_.end()) { + table_.emplace_back(needle, ValueT{}); + return table_.back().second; + } + return it->second; + } + auto find(const KeyT &needle) { + return std::find_if(table_.begin(), table_.end(), [&](auto &key) { return key.first == needle; }); + } + + private: + using KeyValue = value_type; + td::vector<KeyValue> table_; +}; + +template <class KeyT, class ValueT> +class SortedVectorTable { + public: + using key_type = KeyT; + using value_type = std::pair<KeyT, ValueT>; + template <class It> + SortedVectorTable(It begin, It end) : table_(begin, end) { + std::sort(table_.begin(), table_.end()); + } + + ValueT &operator[](const KeyT &needle) { + auto it = std::lower_bound(table_.begin(), table_.end(), needle, + [](const auto &l, const auto &r) { return l.first < r; }); + if (it == table_.end() || it->first != needle) { + it = table_.insert(it, {needle, ValueT{}}); + } + return it->second; + } + + auto find(const KeyT &needle) { + auto it = std::lower_bound(table_.begin(), table_.end(), needle, + [](const auto &l, const auto &r) { return l.first < r; }); + if (it != table_.end() && it->first == needle) { + return it; + } + return table_.end(); + } + + private: + using KeyValue = value_type; + td::vector<KeyValue> table_; +}; + +template <class KeyT, class ValueT, class HashT = td::Hash<KeyT>> +class SimpleHashTable { + public: + using key_type = KeyT; + using value_type = std::pair<KeyT, ValueT>; + template <class It> + SimpleHashTable(It begin, It end) { + nodes_.resize((end - begin) * 2); + for (; begin != end; ++begin) { + insert(begin->first, begin->second); + } + } + + ValueT &operator[](const KeyT &needle) { + UNREACHABLE(); + } + + ValueT *find(const KeyT &needle) { + auto hash = HashT()(needle); + std::size_t i = hash % nodes_.size(); + while (true) { + if (nodes_[i].key == needle) { + return &nodes_[i].value; + } + if (nodes_[i].hash == 0) { + return nullptr; + } + i++; + if (i == nodes_.size()) { + i = 0; + } + } + } + + private: + using KeyValue = value_type; + struct Node { + std::size_t hash{0}; + KeyT key; + ValueT value; + }; + td::vector<Node> nodes_; + + void insert(KeyT key, ValueT value) { + auto hash = HashT()(key); + std::size_t i = hash % nodes_.size(); + while (true) { + if (nodes_[i].hash == 0 || (nodes_[i].hash == hash && nodes_[i].key == key)) { + nodes_[i].value = value; + nodes_[i].key = key; + nodes_[i].hash = hash; + return; + } + i++; + if (i == nodes_.size()) { + i = 0; + } + } + } +}; + +template <typename TableT> +static void BM_Get(benchmark::State &state) { + std::size_t n = state.range(0); + constexpr std::size_t BATCH_SIZE = 1024; + td::Random::Xorshift128plus rnd(123); + using Key = typename TableT::key_type; + using Value = typename TableT::value_type::second_type; + using KeyValue = std::pair<Key, Value>; + td::vector<KeyValue> data; + td::vector<Key> keys; + + TableT table; + for (std::size_t i = 0; i < n; i++) { + auto key = rnd(); + auto value = rnd(); + data.emplace_back(key, value); + table.emplace(key, value); + keys.push_back(key); + } + + std::size_t key_i = 0; + td::random_shuffle(td::as_mutable_span(keys), rnd); + auto next_key = [&] { + key_i++; + if (key_i == data.size()) { + key_i = 0; + } + return keys[key_i]; + }; + + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + benchmark::DoNotOptimize(table.find(next_key())); + } + } +} + +template <typename TableT> +static void BM_find_same(benchmark::State &state) { + td::Random::Xorshift128plus rnd(123); + TableT table; + constexpr std::size_t N = 100000; + constexpr std::size_t BATCH_SIZE = 1024; + reserve(table, N); + + for (std::size_t i = 0; i < N; i++) { + table.emplace(rnd(), i); + } + + auto key = td::Random::secure_uint64(); + table[key] = 123; + + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + benchmark::DoNotOptimize(table.find(key)); + } + } +} + +template <typename TableT> +static void BM_emplace_same(benchmark::State &state) { + td::Random::Xorshift128plus rnd(123); + TableT table; + constexpr std::size_t N = 100000; + constexpr std::size_t BATCH_SIZE = 1024; + reserve(table, N); + + for (std::size_t i = 0; i < N; i++) { + table.emplace(rnd(), i); + } + + auto key = 123743; + table[key] = 123; + + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + benchmark::DoNotOptimize(table.emplace(key + (i & 15) * 100, 43784932)); + } + } +} + +template <typename TableT> +static void BM_emplace_string(benchmark::State &state) { + td::Random::Xorshift128plus rnd(123); + TableT table; + constexpr std::size_t N = 100000; + constexpr std::size_t BATCH_SIZE = 1024; + reserve(table, N); + + for (std::size_t i = 0; i < N; i++) { + table.emplace(td::to_string(rnd()), i); + } + + table["0"] = 123; + td::vector<td::string> strings; + for (std::size_t i = 0; i < 16; i++) { + strings.emplace_back(1, static_cast<char>('0' + i)); + } + + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + benchmark::DoNotOptimize(table.emplace(strings[i & 15], 43784932)); + } + } +} + +namespace td { +template <class K, class V, class FunctT> +static void table_remove_if(absl::flat_hash_map<K, V> &table, FunctT &&func) { + for (auto it = table.begin(); it != table.end();) { + if (func(*it)) { + auto copy = it; + ++it; + table.erase(copy); + } else { + ++it; + } + } +} +} // namespace td + +template <typename TableT> +static void BM_remove_if(benchmark::State &state) { + constexpr std::size_t N = 100000; + constexpr std::size_t BATCH_SIZE = N; + + TableT table; + reserve(table, N); + while (state.KeepRunningBatch(BATCH_SIZE)) { + state.PauseTiming(); + td::Random::Xorshift128plus rnd(123); + for (std::size_t i = 0; i < N; i++) { + table.emplace(rnd(), i); + } + state.ResumeTiming(); + + td::table_remove_if(table, [](auto &it) { return it.second % 2 == 0; }); + } +} + +template <typename TableT> +static void BM_erase_all_with_begin(benchmark::State &state) { + constexpr std::size_t N = 100000; + constexpr std::size_t BATCH_SIZE = N; + + TableT table; + td::Random::Xorshift128plus rnd(123); + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + table.emplace(rnd() + 1, i); + } + while (!table.empty()) { + table.erase(table.begin()); + } + } +} + +template <typename TableT> +static void BM_cache(benchmark::State &state) { + constexpr std::size_t N = 1000; + constexpr std::size_t BATCH_SIZE = 1000000; + + TableT table; + td::Random::Xorshift128plus rnd(123); + td::VectorQueue<td::uint64> keys; + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + auto key = rnd() + 1; + keys.push(key); + table.emplace(key, i); + if (table.size() > N) { + table.erase(keys.pop()); + } + } + } +} + +template <typename TableT> +static void BM_cache2(benchmark::State &state) { + constexpr std::size_t N = 1000; + constexpr std::size_t BATCH_SIZE = 1000000; + + TableT table; + td::Random::Xorshift128plus rnd(123); + td::VectorQueue<td::uint64> keys; + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + auto key = rnd() + 1; + keys.push(key); + table.emplace(key, i); + if (table.size() > N) { + table.erase(keys.pop_rand(rnd)); + } + } + } +} + +template <typename TableT> +static void BM_cache3(benchmark::State &state) { + std::size_t N = state.range(0); + constexpr std::size_t BATCH_SIZE = 1000000; + + TableT table; + td::Random::Xorshift128plus rnd(123); + td::VectorQueue<td::uint64> keys; + std::size_t step = 20; + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i += step) { + auto key = rnd() + 1; + keys.push(key); + table.emplace(key, i); + + for (std::size_t j = 1; j < step; j++) { + auto key_to_find = keys.data()[rnd() % keys.size()]; + benchmark::DoNotOptimize(table.find(key_to_find)); + } + + if (table.size() > N) { + table.erase(keys.pop_rand(rnd)); + } + } + } +} + +template <typename TableT> +static void BM_remove_if_slow(benchmark::State &state) { + constexpr std::size_t N = 5000; + constexpr std::size_t BATCH_SIZE = 500000; + + TableT table; + td::Random::Xorshift128plus rnd(123); + for (std::size_t i = 0; i < N; i++) { + table.emplace(rnd() + 1, i); + } + auto first_key = table.begin()->first; + { + std::size_t cnt = 0; + td::table_remove_if(table, [&cnt, n = N](auto &) { + cnt += 2; + return cnt <= n; + }); + } + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + table.emplace(first_key, i); + table.erase(first_key); + } + } +} + +template <typename TableT> +static void BM_remove_if_slow_old(benchmark::State &state) { + constexpr std::size_t N = 100000; + constexpr std::size_t BATCH_SIZE = 5000000; + + TableT table; + while (state.KeepRunningBatch(BATCH_SIZE)) { + td::Random::Xorshift128plus rnd(123); + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + table.emplace(rnd() + 1, i); + if (table.size() > N) { + std::size_t cnt = 0; + td::table_remove_if(table, [&cnt, n = N](auto &) { + cnt += 2; + return cnt <= n; + }); + } + } + } +} + +template <typename TableT> +static void benchmark_create(td::Slice name) { + td::Random::Xorshift128plus rnd(123); + { + constexpr std::size_t N = 10000000; + TableT table; + reserve(table, N); + auto start = td::Timestamp::now(); + for (std::size_t i = 0; i < N; i++) { + table.emplace(rnd(), i); + } + auto end = td::Timestamp::now(); + LOG(INFO) << name << ": create " << N << " elements: " << td::format::as_time(end.at() - start.at()); + + double res = 0; + td::vector<std::pair<std::size_t, td::format::Time>> pauses; + for (std::size_t i = 0; i < N; i++) { + auto emplace_start = td::Timestamp::now(); + table.emplace(rnd(), i); + auto emplace_end = td::Timestamp::now(); + auto pause = emplace_end.at() - emplace_start.at(); + res = td::max(pause, res); + if (pause > 0.001) { + pauses.emplace_back(i, td::format::as_time(pause)); + } + } + + LOG(INFO) << name << ": create another " << N << " elements, max pause = " << td::format::as_time(res) << " " + << pauses; + } +} + +struct CacheMissNode { + td::uint32 data{}; + char padding[64 - sizeof(data)]; +}; + +class IterateFast { + public: + static td::uint32 iterate(CacheMissNode *ptr, std::size_t max_shift) { + td::uint32 res = 1; + for (std::size_t i = 0; i < max_shift; i++) { + if (ptr[i].data % max_shift != 0) { + res *= ptr[i].data; + } else { + res /= ptr[i].data; + } + } + return res; + } +}; + +class IterateSlow { + public: + static td::uint32 iterate(CacheMissNode *ptr, std::size_t max_shift) { + td::uint32 res = 1; + for (std::size_t i = 0;; i++) { + if (ptr[i].data % max_shift != 0) { + res *= ptr[i].data; + } else { + break; + } + } + return res; + } +}; + +template <class F> +static void BM_cache_miss(benchmark::State &state) { + td::uint32 max_shift = state.range(0); + bool flag = state.range(1); + std::random_device rd; + std::mt19937 rnd(rd()); + int N = 50000000; + td::vector<CacheMissNode> nodes(N); + td::uint32 i = 0; + for (auto &node : nodes) { + if (flag) { + node.data = i++ % max_shift; + } else { + node.data = rnd(); + } + } + + td::vector<int> positions(N); + std::uniform_int_distribution<td::uint32> rnd_pos(0, N - 1000); + for (auto &pos : positions) { + pos = rnd_pos(rnd); + if (flag) { + pos = pos / max_shift * max_shift + 1; + } + } + + while (state.KeepRunningBatch(positions.size())) { + for (const auto pos : positions) { + auto *ptr = &nodes[pos]; + auto res = F::iterate(ptr, max_shift); + benchmark::DoNotOptimize(res); + } + } +} + +static td::uint64 equal_mask_slow(td::uint8 *bytes, td::uint8 needle) { + td::uint64 mask = 0; + for (int i = 0; i < 16; i++) { + mask |= (bytes[i] == needle) << i; + } + return mask; +} + +template <class MaskT> +static void BM_mask(benchmark::State &state) { + std::size_t BATCH_SIZE = 1024; + td::vector<td::uint8> bytes(BATCH_SIZE + 16); + for (auto &b : bytes) { + b = static_cast<td::uint8>(td::Random::fast(0, 17)); + } + + while (state.KeepRunningBatch(BATCH_SIZE)) { + for (std::size_t i = 0; i < BATCH_SIZE; i++) { + benchmark::DoNotOptimize(MaskT::equal_mask(bytes.data() + i, 17)); + } + } +} + +BENCHMARK_TEMPLATE(BM_mask, td::MaskPortable); +#ifdef __aarch64__ +BENCHMARK_TEMPLATE(BM_mask, td::MaskNeonFolly); +BENCHMARK_TEMPLATE(BM_mask, td::MaskNeon); +#endif +#if TD_SSE2 +BENCHMARK_TEMPLATE(BM_mask, td::MaskSse2); +#endif + +template <class KeyT, class ValueT, class HashT = td::Hash<KeyT>, class EqT = std::equal_to<KeyT>> +using FlatHashMapImpl = td::FlatHashTable<td::MapNode<KeyT, ValueT>, HashT, EqT>; + +#define FOR_EACH_TABLE(F) \ + F(FlatHashMapImpl) \ + F(td::FlatHashMapChunks) \ + F(folly::F14FastMap) \ + F(absl::flat_hash_map) \ + F(std::unordered_map) \ + F(std::map) + +//BENCHMARK(BM_cache_miss<IterateSlow>)->Ranges({{1, 16}, {0, 1}}); +//BENCHMARK(BM_cache_miss<IterateFast>)->Ranges({{1, 16}, {0, 1}}); +//BENCHMARK_TEMPLATE(BM_Get, VectorTable<td::uint64, td::uint64>)->Range(1, 1 << 26); +//BENCHMARK_TEMPLATE(BM_Get, SortedVectorTable<td::uint64, td::uint64>)->Range(1, 1 << 26); +//BENCHMARK_TEMPLATE(BM_Get, NoOpTable<td::uint64, td::uint64>)->Range(1, 1 << 26); + +#define REGISTER_GET_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_Get, HT<td::uint64, td::uint64>)->Range(1, 1 << 23); + +#define REGISTER_FIND_BENCHMARK(HT) \ + BENCHMARK_TEMPLATE(BM_find_same, HT<td::uint64, td::uint64>) \ + ->ComputeStatistics("max", [](const td::vector<double> &v) { return *std::max_element(v.begin(), v.end()); }) \ + ->ComputeStatistics("min", [](const td::vector<double> &v) { return *std::min_element(v.begin(), v.end()); }) \ + ->Repetitions(20) \ + ->DisplayAggregatesOnly(true); + +#define REGISTER_REMOVE_IF_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_remove_if, HT<td::uint64, td::uint64>); +#define REGISTER_EMPLACE_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_emplace_same, HT<td::uint64, td::uint64>); +#define REGISTER_EMPLACE_STRING_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_emplace_string, HT<td::string, td::uint64>); +#define REGISTER_CACHE_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_cache, HT<td::uint64, td::uint64>); +#define REGISTER_CACHE2_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_cache2, HT<td::uint64, td::uint64>); +#define REGISTER_CACHE3_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_cache3, HT<td::uint64, td::uint64>)->Range(1, 1 << 23); +#define REGISTER_ERASE_ALL_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_erase_all_with_begin, HT<td::uint64, td::uint64>); +#define REGISTER_REMOVE_IF_SLOW_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_remove_if_slow, HT<td::uint64, td::uint64>); +#define REGISTER_REMOVE_IF_SLOW_OLD_BENCHMARK(HT) BENCHMARK_TEMPLATE(BM_remove_if_slow_old, HT<td::uint64, td::uint64>); + +FOR_EACH_TABLE(REGISTER_GET_BENCHMARK) +FOR_EACH_TABLE(REGISTER_CACHE3_BENCHMARK) +FOR_EACH_TABLE(REGISTER_CACHE2_BENCHMARK) +FOR_EACH_TABLE(REGISTER_CACHE_BENCHMARK) +FOR_EACH_TABLE(REGISTER_REMOVE_IF_BENCHMARK) +FOR_EACH_TABLE(REGISTER_EMPLACE_BENCHMARK) +FOR_EACH_TABLE(REGISTER_EMPLACE_STRING_BENCHMARK) +FOR_EACH_TABLE(REGISTER_ERASE_ALL_BENCHMARK) +FOR_EACH_TABLE(REGISTER_FIND_BENCHMARK) +FOR_EACH_TABLE(REGISTER_REMOVE_IF_SLOW_OLD_BENCHMARK) +FOR_EACH_TABLE(REGISTER_REMOVE_IF_SLOW_BENCHMARK) + +#define RUN_CREATE_BENCHMARK(HT) benchmark_create<HT<td::uint64, td::uint64>>(#HT); + +int main(int argc, char **argv) { + // FOR_EACH_TABLE(RUN_CREATE_BENCHMARK); + + benchmark::Initialize(&argc, argv); + benchmark::RunSpecifiedBenchmarks(); + benchmark::Shutdown(); +} diff --git a/protocols/Telegram/tdlib/td/tdutils/test/heap.cpp b/protocols/Telegram/tdlib/td/tdutils/test/heap.cpp index 0dcfcf98ff..02b6d81424 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/heap.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/heap.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -8,29 +8,24 @@ #include "td/utils/common.h" #include "td/utils/Heap.h" -#include "td/utils/logging.h" #include "td/utils/Random.h" +#include "td/utils/Span.h" -#include <algorithm> #include <cstdio> -#include <cstdlib> #include <set> #include <utility> -REGISTER_TESTS(heap) - -using namespace td; - TEST(Heap, sort_random_perm) { int n = 1000000; - std::vector<int> v(n); + + td::vector<int> v(n); for (int i = 0; i < n; i++) { v[i] = i; } - std::srand(123); - std::random_shuffle(v.begin(), v.end()); - std::vector<HeapNode> nodes(n); - KHeap<int> kheap; + td::Random::Xorshift128plus rnd(123); + td::random_shuffle(td::as_mutable_span(v), rnd); + td::vector<td::HeapNode> nodes(n); + td::KHeap<int> kheap; for (int i = 0; i < n; i++) { kheap.insert(v[i], &nodes[i]); } @@ -38,7 +33,7 @@ TEST(Heap, sort_random_perm) { ASSERT_EQ(i, kheap.top_key()); kheap.pop(); } -}; +} class CheckedHeap { public: @@ -51,7 +46,7 @@ class CheckedHeap { nodes[i].value = i; } } - static void xx(int key, const HeapNode *heap_node) { + static void xx(int key, const td::HeapNode *heap_node) { const Node *node = static_cast<const Node *>(heap_node); std::fprintf(stderr, "(%d;%d)", node->key, node->value); } @@ -66,9 +61,9 @@ class CheckedHeap { } int random_id() const { CHECK(!empty()); - return ids[Random::fast(0, static_cast<int>(ids.size() - 1))]; + return ids[td::Random::fast(0, static_cast<int>(ids.size() - 1))]; } - size_t size() const { + std::size_t size() const { return ids.size(); } bool empty() const { @@ -140,19 +135,19 @@ class CheckedHeap { } private: - struct Node : public HeapNode { + struct Node final : public td::HeapNode { Node() = default; Node(int key, int value) : key(key), value(value) { } int key = 0; int value = 0; }; - vector<int> ids; - vector<int> rev_ids; - vector<int> free_ids; - vector<Node> nodes; + td::vector<int> ids; + td::vector<int> rev_ids; + td::vector<int> free_ids; + td::vector<Node> nodes; std::set<std::pair<int, int>> set_heap; - KHeap<int> kheap; + td::KHeap<int> kheap; }; TEST(Heap, random_events) { @@ -163,11 +158,11 @@ TEST(Heap, random_events) { heap.top_key(); } - int x = Random::fast(0, 4); + int x = td::Random::fast(0, 4); if (heap.empty() || (x < 2 && heap.size() < 1000)) { - heap.insert(Random::fast(0, 99)); + heap.insert(td::Random::fast(0, 99)); } else if (x < 3) { - heap.fix_key(Random::fast(0, 99), heap.random_id()); + heap.fix_key(td::Random::fast(0, 99), heap.random_id()); } else if (x < 4) { heap.erase(heap.random_id()); } else if (x < 5) { diff --git a/protocols/Telegram/tdlib/td/tdutils/test/json.cpp b/protocols/Telegram/tdlib/td/tdutils/test/json.cpp index deca81a791..4e57b2d562 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/json.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/json.cpp @@ -1,31 +1,26 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#include "td/utils/tests.h" - #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" +#include "td/utils/Slice.h" #include "td/utils/StringBuilder.h" +#include "td/utils/tests.h" -#include <tuple> #include <utility> -REGISTER_TESTS(json) - -using namespace td; - -static void decode_encode(string str, string result = "") { +static void decode_encode(const td::string &str, td::string result = td::string()) { auto str_copy = str; - auto r_value = json_decode(str_copy); + auto r_value = td::json_decode(str_copy); ASSERT_TRUE(r_value.is_ok()); if (r_value.is_error()) { LOG(INFO) << r_value.error(); return; } - auto new_str = json_encode<string>(r_value.ok()); + auto new_str = td::json_encode<td::string>(r_value.ok()); if (result.empty()) { result = str; } @@ -34,21 +29,22 @@ static void decode_encode(string str, string result = "") { TEST(JSON, array) { char tmp[1000]; - StringBuilder sb({tmp, sizeof(tmp)}); - JsonBuilder jb(std::move(sb)); + td::StringBuilder sb(td::MutableSlice{tmp, sizeof(tmp)}); + td::JsonBuilder jb(std::move(sb)); jb.enter_value().enter_array() << "Hello" << -123; ASSERT_EQ(jb.string_builder().is_error(), false); auto encoded = jb.string_builder().as_cslice().str(); ASSERT_EQ("[\"Hello\",-123]", encoded); decode_encode(encoded); } + TEST(JSON, object) { char tmp[1000]; - StringBuilder sb({tmp, sizeof(tmp)}); - JsonBuilder jb(std::move(sb)); + td::StringBuilder sb(td::MutableSlice{tmp, sizeof(tmp)}); + td::JsonBuilder jb(std::move(sb)); auto c = jb.enter_object(); - c << std::tie("key", "value"); - c << std::make_pair("1", 2); + c("key", "value"); + c("1", 2); c.leave(); ASSERT_EQ(jb.string_builder().is_error(), false); auto encoded = jb.string_builder().as_cslice().str(); @@ -58,8 +54,8 @@ TEST(JSON, object) { TEST(JSON, nested) { char tmp[1000]; - StringBuilder sb({tmp, sizeof(tmp)}); - JsonBuilder jb(std::move(sb)); + td::StringBuilder sb(td::MutableSlice{tmp, sizeof(tmp)}); + td::JsonBuilder jb(std::move(sb)); { auto a = jb.enter_array(); a << 1; diff --git a/protocols/Telegram/tdlib/td/tdutils/test/log.cpp b/protocols/Telegram/tdlib/td/tdutils/test/log.cpp new file mode 100644 index 0000000000..34af21353c --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/log.cpp @@ -0,0 +1,187 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/AsyncFileLog.h" +#include "td/utils/benchmark.h" +#include "td/utils/CombinedLog.h" +#include "td/utils/FileLog.h" +#include "td/utils/format.h" +#include "td/utils/logging.h" +#include "td/utils/MemoryLog.h" +#include "td/utils/NullLog.h" +#include "td/utils/port/path.h" +#include "td/utils/port/thread.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/tests.h" +#include "td/utils/TsFileLog.h" +#include "td/utils/TsLog.h" + +#include <functional> +#include <limits> + +char disable_linker_warning_about_empty_file_tdutils_test_log_cpp TD_UNUSED; + +#if !TD_THREAD_UNSUPPORTED +template <class Log> +class LogBenchmark final : public td::Benchmark { + public: + LogBenchmark(std::string name, int threads_n, bool test_full_logging, std::function<td::unique_ptr<Log>()> creator) + : name_(std::move(name)) + , threads_n_(threads_n) + , test_full_logging_(test_full_logging) + , creator_(std::move(creator)) { + } + std::string get_description() const final { + return PSTRING() << name_ << " " << (test_full_logging_ ? "ERROR" : "PLAIN") << " " + << td::tag("threads_n", threads_n_); + } + void start_up() final { + log_ = creator_(); + threads_.resize(threads_n_); + } + void tear_down() final { + if (log_ == nullptr) { + return; + } + for (const auto &path : log_->get_file_paths()) { + td::unlink(path).ignore(); + } + log_.reset(); + } + void run(int n) final { + auto old_log_interface = td::log_interface; + if (log_ != nullptr) { + td::log_interface = log_.get(); + } + + for (auto &thread : threads_) { + thread = td::thread([this, n] { this->run_thread(n); }); + } + for (auto &thread : threads_) { + thread.join(); + } + + td::log_interface = old_log_interface; + } + + void run_thread(int n) { + auto str = PSTRING() << "#" << n << " : fsjklfdjsklfjdsklfjdksl\n"; + for (int i = 0; i < n; i++) { + if (i % 10000 == 0 && log_ != nullptr) { + log_->after_rotation(); + } + if (test_full_logging_) { + LOG(ERROR) << str; + } else { + LOG(PLAIN) << str; + } + } + } + + private: + std::string name_; + td::unique_ptr<td::LogInterface> log_; + int threads_n_{0}; + bool test_full_logging_{false}; + std::function<td::unique_ptr<Log>()> creator_; + std::vector<td::thread> threads_; +}; + +template <class F> +static void bench_log(std::string name, F &&f) { + for (auto test_full_logging : {false, true}) { + for (auto threads_n : {1, 4, 8}) { + bench(LogBenchmark<typename decltype(f())::element_type>(name, threads_n, test_full_logging, f)); + } + } +} + +TEST(Log, Bench) { + bench_log("NullLog", [] { return td::make_unique<td::NullLog>(); }); + + // bench_log("Default", []() -> td::unique_ptr<td::NullLog> { return nullptr; }); + + bench_log("MemoryLog", [] { return td::make_unique<td::MemoryLog<1 << 20>>(); }); + + bench_log("CombinedLogEmpty", [] { return td::make_unique<td::CombinedLog>(); }); + + bench_log("CombinedLogMemory", [] { + auto result = td::make_unique<td::CombinedLog>(); + static td::NullLog null_log; + static td::MemoryLog<1 << 20> memory_log; + result->set_first(&null_log); + result->set_second(&memory_log); + result->set_first_verbosity_level(VERBOSITY_NAME(DEBUG)); + result->set_second_verbosity_level(VERBOSITY_NAME(DEBUG)); + return result; + }); + + bench_log("TsFileLog", + [] { return td::TsFileLog::create("tmplog", std::numeric_limits<td::int64>::max(), false).move_as_ok(); }); + + bench_log("FileLog + TsLog", [] { + class FileLog final : public td::LogInterface { + public: + FileLog() { + file_log_.init("tmplog", std::numeric_limits<td::int64>::max(), false).ensure(); + ts_log_.init(&file_log_); + } + void do_append(int log_level, td::CSlice slice) final { + static_cast<td::LogInterface &>(ts_log_).do_append(log_level, slice); + } + std::vector<std::string> get_file_paths() final { + return file_log_.get_file_paths(); + } + + private: + td::FileLog file_log_; + td::TsLog ts_log_{nullptr}; + }; + return td::make_unique<FileLog>(); + }); + + bench_log("FileLog", [] { + class FileLog final : public td::LogInterface { + public: + FileLog() { + file_log_.init("tmplog", std::numeric_limits<td::int64>::max(), false).ensure(); + } + void do_append(int log_level, td::CSlice slice) final { + static_cast<td::LogInterface &>(file_log_).do_append(log_level, slice); + } + std::vector<std::string> get_file_paths() final { + return file_log_.get_file_paths(); + } + + private: + td::FileLog file_log_; + }; + return td::make_unique<FileLog>(); + }); + +#if !TD_EVENTFD_UNSUPPORTED + bench_log("AsyncFileLog", [] { + class AsyncFileLog final : public td::LogInterface { + public: + AsyncFileLog() { + file_log_.init("tmplog", std::numeric_limits<td::int64>::max()).ensure(); + } + void do_append(int log_level, td::CSlice slice) final { + static_cast<td::LogInterface &>(file_log_).do_append(log_level, slice); + } + std::vector<std::string> get_file_paths() final { + return static_cast<td::LogInterface &>(file_log_).get_file_paths(); + } + + private: + td::AsyncFileLog file_log_; + }; + return td::make_unique<AsyncFileLog>(); + }); +#endif +} +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/misc.cpp b/protocols/Telegram/tdlib/td/tdutils/test/misc.cpp index dd1f1ec457..7db990dad1 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/misc.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/misc.cpp @@ -1,47 +1,91 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "td/utils/algorithm.h" +#include "td/utils/as.h" #include "td/utils/base64.h" -#include "td/utils/HttpUrl.h" +#include "td/utils/BigNum.h" +#include "td/utils/bits.h" +#include "td/utils/CancellationToken.h" +#include "td/utils/common.h" +#include "td/utils/ExitGuard.h" +#include "td/utils/FloodControlFast.h" +#include "td/utils/Hash.h" +#include "td/utils/HashMap.h" +#include "td/utils/HashSet.h" +#include "td/utils/HashTableUtils.h" +#include "td/utils/invoke.h" #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/port/EventFd.h" #include "td/utils/port/FileFd.h" +#include "td/utils/port/IPAddress.h" #include "td/utils/port/path.h" #include "td/utils/port/sleep.h" #include "td/utils/port/Stat.h" #include "td/utils/port/thread.h" +#include "td/utils/port/uname.h" +#include "td/utils/port/wstring_convert.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/Status.h" #include "td/utils/StringBuilder.h" #include "td/utils/tests.h" +#include "td/utils/Time.h" +#include "td/utils/tl_helpers.h" +#include "td/utils/translit.h" +#include "td/utils/uint128.h" +#include "td/utils/unicode.h" +#include "td/utils/utf8.h" +#include <algorithm> #include <atomic> -#include <clocale> #include <limits> #include <locale> +#include <unordered_map> +#include <utility> -using namespace td; +#if TD_HAVE_ABSL +#include <absl/container/flat_hash_map.h> +#include <absl/hash/hash.h> +#endif + +struct CheckExitGuard { + explicit CheckExitGuard(bool expected_value) : expected_value_(expected_value) { + } + CheckExitGuard(CheckExitGuard &&) = delete; + CheckExitGuard &operator=(CheckExitGuard &&) = delete; + CheckExitGuard(const CheckExitGuard &) = delete; + CheckExitGuard &operator=(const CheckExitGuard &) = delete; + ~CheckExitGuard() { + ASSERT_EQ(expected_value_, td::ExitGuard::is_exited()); + } + + bool expected_value_; +}; + +static CheckExitGuard check_exit_guard_true{true}; +static td::ExitGuard exit_guard; #if TD_LINUX || TD_DARWIN TEST(Misc, update_atime_saves_mtime) { - SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); - std::string name = "test_file"; - unlink(name).ignore(); - auto r_file = FileFd::open(name, FileFd::Read | FileFd::Flags::Create | FileFd::Flags::Truncate); + td::string name = "test_file"; + td::unlink(name).ignore(); + auto r_file = td::FileFd::open(name, td::FileFd::Read | td::FileFd::Flags::Create | td::FileFd::Flags::Truncate); LOG_IF(ERROR, r_file.is_error()) << r_file.error(); ASSERT_TRUE(r_file.is_ok()); r_file.move_as_ok().close(); - auto info = stat(name).ok(); - int32 tests_ok = 0; - int32 tests_wa = 0; + auto info = td::stat(name).ok(); + td::int32 tests_ok = 0; + td::int32 tests_wa = 0; for (int i = 0; i < 10000; i++) { - update_atime(name).ensure(); - auto new_info = stat(name).ok(); + td::update_atime(name).ensure(); + auto new_info = td::stat(name).ok(); if (info.mtime_nsec_ == new_info.mtime_nsec_) { tests_ok++; } else { @@ -49,31 +93,30 @@ TEST(Misc, update_atime_saves_mtime) { info.mtime_nsec_ = new_info.mtime_nsec_; } ASSERT_EQ(info.mtime_nsec_, new_info.mtime_nsec_); - usleep_for(Random::fast(0, 1000)); + td::usleep_for(td::Random::fast(0, 1000)); } if (tests_wa > 0) { LOG(ERROR) << "Access time was unexpectedly updated " << tests_wa << " times"; } - unlink(name).ensure(); + td::unlink(name).ensure(); } TEST(Misc, update_atime_change_atime) { - SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); - std::string name = "test_file"; - unlink(name).ignore(); - auto r_file = FileFd::open(name, FileFd::Read | FileFd::Flags::Create | FileFd::Flags::Truncate); + td::string name = "test_file"; + td::unlink(name).ignore(); + auto r_file = td::FileFd::open(name, td::FileFd::Read | td::FileFd::Flags::Create | td::FileFd::Flags::Truncate); LOG_IF(ERROR, r_file.is_error()) << r_file.error(); ASSERT_TRUE(r_file.is_ok()); r_file.move_as_ok().close(); - auto info = stat(name).ok(); + auto info = td::stat(name).ok(); // not enough for fat and e.t.c. - usleep_for(5000000); - update_atime(name).ensure(); - auto new_info = stat(name).ok(); + td::usleep_for(5000000); + td::update_atime(name).ensure(); + auto new_info = td::stat(name).ok(); if (info.atime_nsec_ == new_info.atime_nsec_) { LOG(ERROR) << "Access time was unexpectedly not changed"; } - unlink(name).ensure(); + td::unlink(name).ensure(); } #endif @@ -84,9 +127,9 @@ TEST(Misc, errno_tls_bug) { // CHECK(errno == 0); #if !TD_THREAD_UNSUPPORTED && !TD_EVENTFD_UNSUPPORTED - EventFd test_event_fd; + td::EventFd test_event_fd; test_event_fd.init(); - std::atomic<int> s(0); + std::atomic<int> s{0}; s = 1; td::thread th([&] { while (s != 1) { @@ -96,21 +139,21 @@ TEST(Misc, errno_tls_bug) { th.join(); for (int i = 0; i < 1000; i++) { - vector<EventFd> events(10); - vector<td::thread> threads; + td::vector<td::EventFd> events(10); + td::vector<td::thread> threads; for (auto &event : events) { event.init(); event.release(); } for (auto &event : events) { - threads.push_back(td::thread([&] { + threads.emplace_back([&] { { - EventFd tmp; + td::EventFd tmp; tmp.init(); tmp.acquire(); } event.acquire(); - })); + }); } for (auto &thread : threads) { thread.join(); @@ -119,81 +162,281 @@ TEST(Misc, errno_tls_bug) { #endif } +TEST(Misc, get_last_argument) { + auto a = td::make_unique<int>(5); + ASSERT_EQ(*td::get_last_argument(std::move(a)), 5); + ASSERT_EQ(*td::get_last_argument(1, 2, 3, 4, a), 5); + ASSERT_EQ(*td::get_last_argument(a), 5); + auto b = td::get_last_argument(1, 2, 3, std::move(a)); + ASSERT_TRUE(!a); + ASSERT_EQ(*b, 5); +} + +TEST(Misc, call_n_arguments) { + auto f = [](int, int) { + }; + td::call_n_arguments<2>(f, 1, 3, 4); +} + TEST(Misc, base64) { - ASSERT_TRUE(is_base64("dGVzdA==") == true); - ASSERT_TRUE(is_base64("dGVzdB==") == false); - ASSERT_TRUE(is_base64("dGVzdA=") == false); - ASSERT_TRUE(is_base64("dGVzdA") == false); - ASSERT_TRUE(is_base64("dGVz") == true); - ASSERT_TRUE(is_base64("") == true); - ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == true); - ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") == false); - ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false); - ASSERT_TRUE(is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == false); - ASSERT_TRUE(is_base64("====") == false); - - ASSERT_TRUE(is_base64url("dGVzdA==") == true); - ASSERT_TRUE(is_base64url("dGVzdB==") == false); - ASSERT_TRUE(is_base64url("dGVzdA=") == false); - ASSERT_TRUE(is_base64url("dGVzdA") == true); - ASSERT_TRUE(is_base64url("dGVz") == true); - ASSERT_TRUE(is_base64url("") == true); - ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == true); - ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=") == false); - ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false); - ASSERT_TRUE(is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == false); - ASSERT_TRUE(is_base64url("====") == false); + ASSERT_TRUE(td::is_base64("dGVzdA==") == true); + ASSERT_TRUE(td::is_base64("dGVzdB==") == false); + ASSERT_TRUE(td::is_base64("dGVzdA=") == false); + ASSERT_TRUE(td::is_base64("dGVzdA") == false); + ASSERT_TRUE(td::is_base64("dGVzd") == false); + ASSERT_TRUE(td::is_base64("dGVz") == true); + ASSERT_TRUE(td::is_base64("dGVz====") == false); + ASSERT_TRUE(td::is_base64("") == true); + ASSERT_TRUE(td::is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == true); + ASSERT_TRUE(td::is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") == false); + ASSERT_TRUE(td::is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false); + ASSERT_TRUE(td::is_base64("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == false); + ASSERT_TRUE(td::is_base64("====") == false); + + ASSERT_TRUE(td::is_base64url("dGVzdA==") == true); + ASSERT_TRUE(td::is_base64url("dGVzdB==") == false); + ASSERT_TRUE(td::is_base64url("dGVzdA=") == false); + ASSERT_TRUE(td::is_base64url("dGVzdA") == true); + ASSERT_TRUE(td::is_base64url("dGVzd") == false); + ASSERT_TRUE(td::is_base64url("dGVz") == true); + ASSERT_TRUE(td::is_base64url("dGVz====") == false); + ASSERT_TRUE(td::is_base64url("") == true); + ASSERT_TRUE(td::is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == true); + ASSERT_TRUE(td::is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=") == false); + ASSERT_TRUE(td::is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false); + ASSERT_TRUE(td::is_base64url("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == false); + ASSERT_TRUE(td::is_base64url("====") == false); + + ASSERT_TRUE(td::is_base64_characters("dGVzdA==") == false); + ASSERT_TRUE(td::is_base64_characters("dGVzdB==") == false); + ASSERT_TRUE(td::is_base64_characters("dGVzdA=") == false); + ASSERT_TRUE(td::is_base64_characters("dGVzdA") == true); + ASSERT_TRUE(td::is_base64_characters("dGVz") == true); + ASSERT_TRUE(td::is_base64_characters("") == true); + ASSERT_TRUE(td::is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == true); + ASSERT_TRUE(td::is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=") == false); + ASSERT_TRUE(td::is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false); + ASSERT_TRUE(td::is_base64_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == false); + ASSERT_TRUE(td::is_base64_characters("====") == false); + + ASSERT_TRUE(td::is_base64url_characters("dGVzdA==") == false); + ASSERT_TRUE(td::is_base64url_characters("dGVzdB==") == false); + ASSERT_TRUE(td::is_base64url_characters("dGVzdA=") == false); + ASSERT_TRUE(td::is_base64url_characters("dGVzdA") == true); + ASSERT_TRUE(td::is_base64url_characters("dGVz") == true); + ASSERT_TRUE(td::is_base64url_characters("") == true); + ASSERT_TRUE(td::is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_") == true); + ASSERT_TRUE(td::is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=") == + false); + ASSERT_TRUE(td::is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-/") == false); + ASSERT_TRUE(td::is_base64url_characters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/") == false); + ASSERT_TRUE(td::is_base64url_characters("====") == false); for (int l = 0; l < 300000; l += l / 20 + l / 1000 * 500 + 1) { for (int t = 0; t < 10; t++) { - string s = rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), l); - string encoded = base64url_encode(s); - auto decoded = base64url_decode(encoded); + auto s = td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), l); + auto encoded = td::base64url_encode(s); + auto decoded = td::base64url_decode(encoded); ASSERT_TRUE(decoded.is_ok()); ASSERT_TRUE(decoded.ok() == s); - encoded = base64_encode(s); - decoded = base64_decode(encoded); + encoded = td::base64_encode(s); + decoded = td::base64_decode(encoded); ASSERT_TRUE(decoded.is_ok()); ASSERT_TRUE(decoded.ok() == s); + + auto decoded_secure = td::base64_decode_secure(encoded); + ASSERT_TRUE(decoded_secure.is_ok()); + ASSERT_TRUE(decoded_secure.ok().as_slice() == s); } } - ASSERT_TRUE(base64url_decode("dGVzdA").is_ok()); - ASSERT_TRUE(base64url_decode("dGVzdB").is_error()); - ASSERT_TRUE(base64_encode(base64url_decode("dGVzdA").ok()) == "dGVzdA=="); - ASSERT_TRUE(base64_encode("any carnal pleas") == "YW55IGNhcm5hbCBwbGVhcw=="); - ASSERT_TRUE(base64_encode("any carnal pleasu") == "YW55IGNhcm5hbCBwbGVhc3U="); - ASSERT_TRUE(base64_encode("any carnal pleasur") == "YW55IGNhcm5hbCBwbGVhc3Vy"); - ASSERT_TRUE(base64_encode(" /'.;.';≤.];,].',[.;/,.;/]/..;!@#!*(%?::;!%\";") == - "ICAgICAgLycuOy4nO+KJpC5dOyxdLicsWy47LywuOy9dLy4uOyFAIyEqKCU/" - "Ojo7ISUiOw=="); + ASSERT_TRUE(td::base64url_decode("dGVzdA").is_ok()); + ASSERT_TRUE(td::base64url_decode("dGVzdB").is_error()); + ASSERT_TRUE(td::base64_encode(td::base64url_decode("dGVzdA").ok()) == "dGVzdA=="); + ASSERT_TRUE(td::base64_encode("any carnal pleas") == "YW55IGNhcm5hbCBwbGVhcw=="); + ASSERT_TRUE(td::base64_encode("any carnal pleasu") == "YW55IGNhcm5hbCBwbGVhc3U="); + ASSERT_TRUE(td::base64_encode("any carnal pleasur") == "YW55IGNhcm5hbCBwbGVhc3Vy"); + ASSERT_TRUE(td::base64_encode(" /'.;.';≤.];,].',[.;/,.;/]/..;!@#!*(%?::;!%\";") == + "ICAgICAgLycuOy4nO+KJpC5dOyxdLicsWy47LywuOy9dLy4uOyFAIyEqKCU/Ojo7ISUiOw=="); + ASSERT_TRUE(td::base64url_encode("ab><") == "YWI-PA"); + ASSERT_TRUE(td::base64url_encode("ab><c") == "YWI-PGM"); + ASSERT_TRUE(td::base64url_encode("ab><cd") == "YWI-PGNk"); +} + +template <class T> +static void test_remove_if(td::vector<int> v, const T &func, const td::vector<int> &expected) { + td::remove_if(v, func); + if (expected != v) { + LOG(FATAL) << "Receive " << v << ", expected " << expected << " in remove_if"; + } +} + +TEST(Misc, remove_if) { + auto odd = [](int x) { + return x % 2 == 1; + }; + auto even = [](int x) { + return x % 2 == 0; + }; + auto all = [](int x) { + return true; + }; + auto none = [](int x) { + return false; + }; + + td::vector<int> v{1, 2, 3, 4, 5, 6}; + test_remove_if(v, odd, {2, 4, 6}); + test_remove_if(v, even, {1, 3, 5}); + test_remove_if(v, all, {}); + test_remove_if(v, none, v); + + v = td::vector<int>{1, 3, 5, 2, 4, 6}; + test_remove_if(v, odd, {2, 4, 6}); + test_remove_if(v, even, {1, 3, 5}); + test_remove_if(v, all, {}); + test_remove_if(v, none, v); + + v.clear(); + test_remove_if(v, odd, v); + test_remove_if(v, even, v); + test_remove_if(v, all, v); + test_remove_if(v, none, v); + + v.push_back(-1); + test_remove_if(v, odd, v); + test_remove_if(v, even, v); + test_remove_if(v, all, {}); + test_remove_if(v, none, v); + + v[0] = 1; + test_remove_if(v, odd, {}); + test_remove_if(v, even, v); + test_remove_if(v, all, {}); + test_remove_if(v, none, v); + + v[0] = 2; + test_remove_if(v, odd, v); + test_remove_if(v, even, {}); + test_remove_if(v, all, {}); + test_remove_if(v, none, v); +} + +static void test_remove(td::vector<int> v, int value, const td::vector<int> &expected) { + bool is_found = expected != v; + ASSERT_EQ(is_found, td::remove(v, value)); + if (expected != v) { + LOG(FATAL) << "Receive " << v << ", expected " << expected << " in remove"; + } +} + +TEST(Misc, remove) { + td::vector<int> v{1, 2, 3, 4, 5, 6}; + test_remove(v, 0, {1, 2, 3, 4, 5, 6}); + test_remove(v, 1, {2, 3, 4, 5, 6}); + test_remove(v, 2, {1, 3, 4, 5, 6}); + test_remove(v, 3, {1, 2, 4, 5, 6}); + test_remove(v, 4, {1, 2, 3, 5, 6}); + test_remove(v, 5, {1, 2, 3, 4, 6}); + test_remove(v, 6, {1, 2, 3, 4, 5}); + test_remove(v, 7, {1, 2, 3, 4, 5, 6}); + + v.clear(); + test_remove(v, -1, v); + test_remove(v, 0, v); + test_remove(v, 1, v); +} + +static void test_unique(td::vector<int> v, const td::vector<int> &expected) { + auto v_str = td::transform(v, &td::to_string<int>); + auto expected_str = td::transform(expected, &td::to_string<int>); + + td::unique(v); + ASSERT_EQ(expected, v); + + td::unique(v_str); + ASSERT_EQ(expected_str, v_str); +} + +TEST(Misc, unique) { + test_unique({1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6}); + test_unique({5, 2, 1, 6, 3, 4}, {1, 2, 3, 4, 5, 6}); + test_unique({}, {}); + test_unique({0}, {0}); + test_unique({0, 0}, {0}); + test_unique({0, 1}, {0, 1}); + test_unique({1, 0}, {0, 1}); + test_unique({1, 1}, {1}); + test_unique({1, 1, 0, 0}, {0, 1}); + test_unique({3, 3, 3, 3, 3, 2, 2, 2, 1, 1, 0}, {0, 1, 2, 3}); + test_unique({3, 3, 3, 3, 3}, {3}); + test_unique({3, 3, -1, 3, 3}, {-1, 3}); +} + +TEST(Misc, contains) { + td::vector<int> v{1, 3, 5, 7, 4, 2}; + for (int i = -10; i < 20; i++) { + ASSERT_EQ(td::contains(v, i), (1 <= i && i <= 5) || i == 7); + } + + v.clear(); + ASSERT_TRUE(!td::contains(v, 0)); + ASSERT_TRUE(!td::contains(v, 1)); + + td::string str = "abacaba"; + ASSERT_TRUE(!td::contains(str, '0')); + ASSERT_TRUE(!td::contains(str, 0)); + ASSERT_TRUE(!td::contains(str, 'd')); + ASSERT_TRUE(td::contains(str, 'a')); + ASSERT_TRUE(td::contains(str, 'b')); + ASSERT_TRUE(td::contains(str, 'c')); +} + +TEST(Misc, base32) { + ASSERT_EQ("", td::base32_encode("")); + ASSERT_EQ("me", td::base32_encode("a")); + td::base32_decode("me").ensure(); + ASSERT_EQ("mfra", td::base32_encode("ab")); + ASSERT_EQ("mfrgg", td::base32_encode("abc")); + ASSERT_EQ("mfrggza", td::base32_encode("abcd")); + ASSERT_EQ("mfrggzdg", td::base32_encode("abcdf")); + ASSERT_EQ("mfrggzdgm4", td::base32_encode("abcdfg")); + for (int l = 0; l < 300000; l += l / 20 + l / 1000 * 500 + 1) { + for (int t = 0; t < 10; t++) { + auto s = td::rand_string(std::numeric_limits<char>::min(), std::numeric_limits<char>::max(), l); + auto encoded = td::base32_encode(s); + auto decoded = td::base32_decode(encoded); + ASSERT_TRUE(decoded.is_ok()); + ASSERT_TRUE(decoded.ok() == s); + } + } } TEST(Misc, to_integer) { - ASSERT_EQ(to_integer<int32>("-1234567"), -1234567); - ASSERT_EQ(to_integer<int64>("-1234567"), -1234567); - ASSERT_EQ(to_integer<uint32>("-1234567"), 0u); - ASSERT_EQ(to_integer<int16>("-1234567"), 10617); - ASSERT_EQ(to_integer<uint16>("-1234567"), 0u); - ASSERT_EQ(to_integer<int16>("-1254567"), -9383); - ASSERT_EQ(to_integer<uint16>("1254567"), 9383u); - ASSERT_EQ(to_integer<int64>("-12345678910111213"), -12345678910111213); - ASSERT_EQ(to_integer<uint64>("12345678910111213"), 12345678910111213ull); - - ASSERT_EQ(to_integer_safe<int32>("-1234567").ok(), -1234567); - ASSERT_EQ(to_integer_safe<int64>("-1234567").ok(), -1234567); - ASSERT_TRUE(to_integer_safe<uint32>("-1234567").is_error()); - ASSERT_TRUE(to_integer_safe<int16>("-1234567").is_error()); - ASSERT_TRUE(to_integer_safe<uint16>("-1234567").is_error()); - ASSERT_TRUE(to_integer_safe<int16>("-1254567").is_error()); - ASSERT_TRUE(to_integer_safe<uint16>("1254567").is_error()); - ASSERT_EQ(to_integer_safe<int64>("-12345678910111213").ok(), -12345678910111213); - ASSERT_EQ(to_integer_safe<uint64>("12345678910111213").ok(), 12345678910111213ull); - ASSERT_TRUE(to_integer_safe<uint64>("-12345678910111213").is_error()); -} - -static void test_to_double_one(CSlice str, Slice expected, int precision = 6) { + ASSERT_EQ(td::to_integer<td::int32>("-1234567"), -1234567); + ASSERT_EQ(td::to_integer<td::int64>("-1234567"), -1234567); + ASSERT_EQ(td::to_integer<td::uint32>("-1234567"), 0u); + ASSERT_EQ(td::to_integer<td::int16>("-1234567"), 10617); + ASSERT_EQ(td::to_integer<td::uint16>("-1234567"), 0u); + ASSERT_EQ(td::to_integer<td::int16>("-1254567"), -9383); + ASSERT_EQ(td::to_integer<td::uint16>("1254567"), 9383u); + ASSERT_EQ(td::to_integer<td::int64>("-12345678910111213"), -12345678910111213); + ASSERT_EQ(td::to_integer<td::uint64>("12345678910111213"), 12345678910111213ull); + + ASSERT_EQ(td::to_integer_safe<td::int32>("-1234567").ok(), -1234567); + ASSERT_EQ(td::to_integer_safe<td::int64>("-1234567").ok(), -1234567); + ASSERT_TRUE(td::to_integer_safe<td::uint32>("-1234567").is_error()); + ASSERT_TRUE(td::to_integer_safe<td::int16>("-1234567").is_error()); + ASSERT_TRUE(td::to_integer_safe<td::uint16>("-1234567").is_error()); + ASSERT_TRUE(td::to_integer_safe<td::int16>("-1254567").is_error()); + ASSERT_TRUE(td::to_integer_safe<td::uint16>("1254567").is_error()); + ASSERT_EQ(td::to_integer_safe<td::int64>("-12345678910111213").ok(), -12345678910111213); + ASSERT_EQ(td::to_integer_safe<td::uint64>("12345678910111213").ok(), 12345678910111213ull); + ASSERT_TRUE(td::to_integer_safe<td::uint64>("-12345678910111213").is_error()); +} + +static void test_to_double_one(td::CSlice str, td::Slice expected, int precision = 6) { auto result = PSTRING() << td::StringBuilder::FixedDouble(to_double(str), precision); if (expected != result) { LOG(ERROR) << "To double conversion failed: have " << str << ", expected " << expected << ", parsed " @@ -234,29 +477,786 @@ static void test_to_double() { TEST(Misc, to_double) { test_to_double(); - const char *locale_name = (std::setlocale(LC_ALL, "fr-FR") == nullptr ? "" : "fr-FR"); - std::locale new_locale(locale_name); - std::locale::global(new_locale); + std::locale new_locale("C"); + auto host_locale = std::locale::global(new_locale); + test_to_double(); + new_locale = std::locale::global(std::locale::classic()); test_to_double(); - std::locale::global(std::locale::classic()); + auto classic_locale = std::locale::global(host_locale); test_to_double(); } -static void test_get_url_query_file_name_one(const char *prefix, const char *suffix, const char *file_name) { - auto path = string(prefix) + string(file_name) + string(suffix); - ASSERT_STREQ(file_name, get_url_query_file_name(path)); - ASSERT_STREQ(file_name, get_url_file_name("http://telegram.org" + path)); - ASSERT_STREQ(file_name, get_url_file_name("http://telegram.org:80" + path)); - ASSERT_STREQ(file_name, get_url_file_name("telegram.org" + path)); +TEST(Misc, print_int) { + ASSERT_STREQ("-9223372036854775808", PSLICE() << -9223372036854775807 - 1); + ASSERT_STREQ("-2147483649", PSLICE() << -2147483649ll); + ASSERT_STREQ("-2147483648", PSLICE() << -2147483647 - 1); + ASSERT_STREQ("-2147483647", PSLICE() << -2147483647); + ASSERT_STREQ("-123456789", PSLICE() << -123456789); + ASSERT_STREQ("-1", PSLICE() << -1); + ASSERT_STREQ("0", PSLICE() << 0); + ASSERT_STREQ("1", PSLICE() << 1); + ASSERT_STREQ("9", PSLICE() << 9); + ASSERT_STREQ("10", PSLICE() << 10); + ASSERT_STREQ("2147483647", PSLICE() << 2147483647); + ASSERT_STREQ("2147483648", PSLICE() << 2147483648ll); + ASSERT_STREQ("2147483649", PSLICE() << 2147483649ll); + ASSERT_STREQ("9223372036854775807", PSLICE() << 9223372036854775807ll); +} + +TEST(Misc, print_uint) { + ASSERT_STREQ("0", PSLICE() << 0u); + ASSERT_STREQ("1", PSLICE() << 1u); + ASSERT_STREQ("9", PSLICE() << 9u); + ASSERT_STREQ("10", PSLICE() << 10u); + ASSERT_STREQ("2147483647", PSLICE() << 2147483647u); + ASSERT_STREQ("2147483648", PSLICE() << 2147483648u); + ASSERT_STREQ("2147483649", PSLICE() << 2147483649u); + ASSERT_STREQ("9223372036854775807", PSLICE() << 9223372036854775807u); +} + +static void test_idn_to_ascii_one(const td::string &host, const td::string &result) { + if (result != td::idn_to_ascii(host).ok()) { + LOG(ERROR) << "Failed to convert " << host << " to " << result << ", got \"" << td::idn_to_ascii(host).ok() << "\""; + } +} + +TEST(Misc, idn_to_ascii) { + test_idn_to_ascii_one("::::::::::::::::::::::::::::::::::::::@/", "::::::::::::::::::::::::::::::::::::::@/"); + test_idn_to_ascii_one("", ""); + test_idn_to_ascii_one("%30", "%30"); + test_idn_to_ascii_one("127.0.0.1", "127.0.0.1"); + test_idn_to_ascii_one("fe80::", "fe80::"); + test_idn_to_ascii_one("fe80:0:0:0:200:f8ff:fe21:67cf", "fe80:0:0:0:200:f8ff:fe21:67cf"); + test_idn_to_ascii_one("2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d", "2001:0db8:11a3:09d7:1f34:8a2e:07a0:765d"); + test_idn_to_ascii_one("::ffff:192.0.2.1", "::ffff:192.0.2.1"); + test_idn_to_ascii_one("ABCDEF", "abcdef"); + test_idn_to_ascii_one("abcdef", "abcdef"); + test_idn_to_ascii_one("abæcdöef", "xn--abcdef-qua4k"); + test_idn_to_ascii_one("schön", "xn--schn-7qa"); + test_idn_to_ascii_one("ยจฆฟคฏข", "xn--22cdfh1b8fsa"); + test_idn_to_ascii_one("☺", "xn--74h"); + test_idn_to_ascii_one("правда", "xn--80aafi6cg"); + test_idn_to_ascii_one("büücher", "xn--bcher-kvaa"); + test_idn_to_ascii_one("BüüCHER", "xn--bcher-kvaa"); + test_idn_to_ascii_one("bücüher", "xn--bcher-kvab"); + test_idn_to_ascii_one("bücherü", "xn--bcher-kvae"); + test_idn_to_ascii_one("ýbücher", "xn--bcher-kvaf"); + test_idn_to_ascii_one("übücher", "xn--bcher-jvab"); + test_idn_to_ascii_one("bücher.tld", "xn--bcher-kva.tld"); + test_idn_to_ascii_one("кто.рф", "xn--j1ail.xn--p1ai"); + test_idn_to_ascii_one("wіkіреdіа.org", "xn--wkd-8cdx9d7hbd.org"); + test_idn_to_ascii_one("cnwin2k8中国.avol.com", "xn--cnwin2k8-sd0mx14e.avol.com"); + test_idn_to_ascii_one("win-2k12r2-addc.阿伯测阿伯测ad.hai.com", "win-2k12r2-addc.xn--ad-tl3ca3569aba8944eca.hai.com"); + test_idn_to_ascii_one("✌.ws", "xn--7bi.ws"); + // test_idn_to_ascii_one("✌️.ws", "xn--7bi.ws"); // needs nameprep to succeed + test_idn_to_ascii_one("⛧", "xn--59h"); + test_idn_to_ascii_one("--рф.рф", "xn-----mmcq.xn--p1ai"); + ASSERT_TRUE(td::idn_to_ascii("\xc0").is_error()); +} + +#if TD_WINDOWS +static void test_to_wstring_one(const td::string &str) { + ASSERT_STREQ(str, td::from_wstring(td::to_wstring(str).ok()).ok()); +} + +TEST(Misc, to_wstring) { + test_to_wstring_one(""); + for (int i = 0; i < 10; i++) { + test_to_wstring_one("test"); + test_to_wstring_one("тест"); + } + td::string str; + for (td::uint32 i = 0; i <= 0xD7FF; i++) { + td::append_utf8_character(str, i); + } + for (td::uint32 i = 0xE000; i <= 0x10FFFF; i++) { + td::append_utf8_character(str, i); + } + test_to_wstring_one(str); + ASSERT_TRUE(td::to_wstring("\xc0").is_error()); + auto emoji = td::to_wstring("🏟").ok(); + ASSERT_TRUE(td::from_wstring(emoji).ok() == "🏟"); + ASSERT_TRUE(emoji.size() == 2); + auto emoji2 = emoji; + emoji[0] = emoji[1]; + emoji2[1] = emoji2[0]; + ASSERT_TRUE(td::from_wstring(emoji).is_error()); + ASSERT_TRUE(td::from_wstring(emoji2).is_error()); + emoji2[0] = emoji[0]; + ASSERT_TRUE(td::from_wstring(emoji2).is_error()); +} +#endif + +static void test_translit(const td::string &word, const td::vector<td::string> &result, bool allow_partial = true) { + ASSERT_EQ(result, td::get_word_transliterations(word, allow_partial)); +} + +TEST(Misc, translit) { + test_translit("word", {"word", "ворд"}); + test_translit("", {}); + test_translit("ььььььььь", {"ььььььььь"}); + test_translit("крыло", {"krylo", "крыло"}); + test_translit("krylo", {"krylo", "крило"}); + test_translit("crylo", {"crylo", "крило"}); + test_translit("cheiia", {"cheiia", "кхеииа", "чейия"}); + test_translit("cheii", {"cheii", "кхеии", "чейи", "чейий", "чейия"}); + test_translit("s", {"s", "с", "ш", "щ"}); + test_translit("y", {"e", "y", "е", "и", "ю", "я"}); + test_translit("j", {"e", "j", "е", "й", "ю", "я"}); + test_translit("yo", {"e", "yo", "е", "ио"}); + test_translit("artjom", {"artem", "artjom", "артем", "артйом"}); + test_translit("artyom", {"artem", "artyom", "артем", "артиом"}); + test_translit("arty", {"arte", "arty", "арте", "арти", "артю", "артя"}); + test_translit("льи", {"li", "lia", "ly", "льи"}); + test_translit("y", {"y", "и"}, false); + test_translit("yo", {"e", "yo", "е", "ио"}, false); +} + +static void test_unicode(td::uint32 (*func)(td::uint32)) { + for (td::uint32 i = 0; i <= 0x110000; i++) { + auto res = func(i); + CHECK(res <= 0x10ffff); + } +} + +TEST(Misc, unicode) { + test_unicode(td::prepare_search_character); + test_unicode(td::unicode_to_lower); + test_unicode(td::remove_diacritics); +} + +TEST(Misc, get_unicode_simple_category) { + td::uint32 result = 0; + for (size_t t = 0; t < 100; t++) { + for (td::uint32 i = 0; i <= 0x10ffff; i++) { + result = result * 123 + static_cast<td::uint32>(static_cast<int>(td::get_unicode_simple_category(i))); + } + } + LOG(INFO) << result; +} + +TEST(Misc, get_unicode_simple_category_small) { + td::uint32 result = 0; + for (size_t t = 0; t < 1000; t++) { + for (td::uint32 i = 0; i <= 0xffff; i++) { + result = result * 123 + static_cast<td::uint32>(static_cast<int>(td::get_unicode_simple_category(i))); + } + } + LOG(INFO) << result; +} + +TEST(BigNum, from_decimal) { + ASSERT_TRUE(td::BigNum::from_decimal("").is_error()); + ASSERT_TRUE(td::BigNum::from_decimal("a").is_error()); + ASSERT_TRUE(td::BigNum::from_decimal("123a").is_error()); + ASSERT_TRUE(td::BigNum::from_decimal("-123a").is_error()); + // ASSERT_TRUE(td::BigNum::from_decimal("-").is_error()); + ASSERT_TRUE(td::BigNum::from_decimal("123").is_ok()); + ASSERT_TRUE(td::BigNum::from_decimal("-123").is_ok()); + ASSERT_TRUE(td::BigNum::from_decimal("0").is_ok()); + ASSERT_TRUE(td::BigNum::from_decimal("-0").is_ok()); + ASSERT_TRUE(td::BigNum::from_decimal("-999999999999999999999999999999999999999999999999").is_ok()); + ASSERT_TRUE(td::BigNum::from_decimal("999999999999999999999999999999999999999999999999").is_ok()); +} + +TEST(BigNum, from_binary) { + ASSERT_STREQ(td::BigNum::from_binary("").to_decimal(), "0"); + ASSERT_STREQ(td::BigNum::from_binary("a").to_decimal(), "97"); + ASSERT_STREQ(td::BigNum::from_binary("\x00\xff").to_decimal(), "255"); + ASSERT_STREQ(td::BigNum::from_binary("\x00\x01\x00\x00").to_decimal(), "65536"); + ASSERT_STREQ(td::BigNum::from_le_binary("").to_decimal(), "0"); + ASSERT_STREQ(td::BigNum::from_le_binary("a").to_decimal(), "97"); + ASSERT_STREQ(td::BigNum::from_le_binary("\x00\xff").to_decimal(), "65280"); + ASSERT_STREQ(td::BigNum::from_le_binary("\x00\x01\x00\x00").to_decimal(), "256"); + ASSERT_STREQ(td::BigNum::from_decimal("255").move_as_ok().to_binary(), "\xff"); + ASSERT_STREQ(td::BigNum::from_decimal("255").move_as_ok().to_le_binary(), "\xff"); + ASSERT_STREQ(td::BigNum::from_decimal("255").move_as_ok().to_binary(2), "\x00\xff"); + ASSERT_STREQ(td::BigNum::from_decimal("255").move_as_ok().to_le_binary(2), "\xff\x00"); + ASSERT_STREQ(td::BigNum::from_decimal("65280").move_as_ok().to_binary(), "\xff\x00"); + ASSERT_STREQ(td::BigNum::from_decimal("65280").move_as_ok().to_le_binary(), "\x00\xff"); + ASSERT_STREQ(td::BigNum::from_decimal("65280").move_as_ok().to_binary(2), "\xff\x00"); + ASSERT_STREQ(td::BigNum::from_decimal("65280").move_as_ok().to_le_binary(2), "\x00\xff"); + ASSERT_STREQ(td::BigNum::from_decimal("65536").move_as_ok().to_binary(), "\x01\x00\x00"); + ASSERT_STREQ(td::BigNum::from_decimal("65536").move_as_ok().to_le_binary(), "\x00\x00\x01"); + ASSERT_STREQ(td::BigNum::from_decimal("65536").move_as_ok().to_binary(4), "\x00\x01\x00\x00"); + ASSERT_STREQ(td::BigNum::from_decimal("65536").move_as_ok().to_le_binary(4), "\x00\x00\x01\x00"); +} + +static void test_get_ipv4(td::uint32 ip) { + td::IPAddress ip_address; + ip_address.init_ipv4_port(td::IPAddress::ipv4_to_str(ip), 80).ensure(); + ASSERT_EQ(ip_address.get_ipv4(), ip); +} + +TEST(Misc, IPAddress_get_ipv4) { + test_get_ipv4(0x00000000); + test_get_ipv4(0x010000FF); + test_get_ipv4(0xFF000001); + test_get_ipv4(0x01020304); + test_get_ipv4(0x04030201); + test_get_ipv4(0xFFFFFFFF); +} + +static void test_is_reserved(const td::string &ip, bool is_reserved) { + td::IPAddress ip_address; + ip_address.init_ipv4_port(ip, 80).ensure(); + ASSERT_EQ(is_reserved, ip_address.is_reserved()); +} + +TEST(Misc, IPAddress_is_reserved) { + test_is_reserved("0.0.0.0", true); + test_is_reserved("0.255.255.255", true); + test_is_reserved("1.0.0.0", false); + test_is_reserved("5.0.0.0", false); + test_is_reserved("9.255.255.255", false); + test_is_reserved("10.0.0.0", true); + test_is_reserved("10.255.255.255", true); + test_is_reserved("11.0.0.0", false); + test_is_reserved("100.63.255.255", false); + test_is_reserved("100.64.0.0", true); + test_is_reserved("100.127.255.255", true); + test_is_reserved("100.128.0.0", false); + test_is_reserved("126.255.255.255", false); + test_is_reserved("127.0.0.0", true); + test_is_reserved("127.255.255.255", true); + test_is_reserved("128.0.0.0", false); + test_is_reserved("169.253.255.255", false); + test_is_reserved("169.254.0.0", true); + test_is_reserved("169.254.255.255", true); + test_is_reserved("169.255.0.0", false); + test_is_reserved("172.15.255.255", false); + test_is_reserved("172.16.0.0", true); + test_is_reserved("172.31.255.255", true); + test_is_reserved("172.32.0.0", false); + test_is_reserved("191.255.255.255", false); + test_is_reserved("192.0.0.0", true); + test_is_reserved("192.0.0.255", true); + test_is_reserved("192.0.1.0", false); + test_is_reserved("192.0.1.255", false); + test_is_reserved("192.0.2.0", true); + test_is_reserved("192.0.2.255", true); + test_is_reserved("192.0.3.0", false); + test_is_reserved("192.88.98.255", false); + test_is_reserved("192.88.99.0", true); + test_is_reserved("192.88.99.255", true); + test_is_reserved("192.88.100.0", false); + test_is_reserved("192.167.255.255", false); + test_is_reserved("192.168.0.0", true); + test_is_reserved("192.168.255.255", true); + test_is_reserved("192.169.0.0", false); + test_is_reserved("198.17.255.255", false); + test_is_reserved("198.18.0.0", true); + test_is_reserved("198.19.255.255", true); + test_is_reserved("198.20.0.0", false); + test_is_reserved("198.51.99.255", false); + test_is_reserved("198.51.100.0", true); + test_is_reserved("198.51.100.255", true); + test_is_reserved("198.51.101.0", false); + test_is_reserved("203.0.112.255", false); + test_is_reserved("203.0.113.0", true); + test_is_reserved("203.0.113.255", true); + test_is_reserved("203.0.114.0", false); + test_is_reserved("223.255.255.255", false); + test_is_reserved("224.0.0.0", true); + test_is_reserved("239.255.255.255", true); + test_is_reserved("240.0.0.0", true); + test_is_reserved("255.255.255.254", true); + test_is_reserved("255.255.255.255", true); +} + +TEST(Misc, ipv6_clear) { + td::IPAddress ip_address; + ip_address.init_host_port("2001:0db8:85a3:0000:0000:8a2e:0370:7334", 123).ensure(); + ASSERT_EQ("2001:db8:85a3::8a2e:370:7334", ip_address.get_ip_str()); + ip_address.clear_ipv6_interface(); + ASSERT_EQ("2001:db8:85a3::", ip_address.get_ip_str()); +} + +static void test_split(td::Slice str, std::pair<td::Slice, td::Slice> expected) { + ASSERT_EQ(expected, td::split(str)); +} + +TEST(Misc, split) { + test_split("", {"", ""}); + test_split(" ", {"", ""}); + test_split("abcdef", {"abcdef", ""}); + test_split("abc def", {"abc", "def"}); + test_split("a bcdef", {"a", "bcdef"}); + test_split(" abcdef", {"", "abcdef"}); + test_split("abcdef ", {"abcdef", ""}); + test_split("ab cd ef", {"ab", "cd ef"}); + test_split("ab cdef ", {"ab", "cdef "}); + test_split(" abcd ef", {"", "abcd ef"}); + test_split(" abcdef ", {"", "abcdef "}); } -TEST(Misc, get_url_query_file_name) { - for (auto suffix : {"?t=1#test", "#test?t=1", "#?t=1", "?t=1#", "#test", "?t=1", "#", "?", ""}) { - test_get_url_query_file_name_one("", suffix, ""); - test_get_url_query_file_name_one("/", suffix, ""); - test_get_url_query_file_name_one("/a/adasd/", suffix, ""); - test_get_url_query_file_name_one("/a/lklrjetn/", suffix, "adasd.asdas"); - test_get_url_query_file_name_one("/", suffix, "a123asadas"); - test_get_url_query_file_name_one("/", suffix, "\\a\\1\\2\\3\\a\\s\\a\\das"); +static void test_full_split(td::Slice str, const td::vector<td::Slice> &expected) { + ASSERT_EQ(expected, td::full_split(str)); +} + +static void test_full_split(td::Slice str, char c, std::size_t max_parts, const td::vector<td::Slice> &expected) { + ASSERT_EQ(expected, td::full_split(str, c, max_parts)); +} + +TEST(Misc, full_split) { + test_full_split("", {}); + test_full_split(" ", {"", ""}); + test_full_split(" ", {"", "", ""}); + test_full_split("abcdef", {"abcdef"}); + test_full_split("abc def", {"abc", "def"}); + test_full_split("a bcdef", {"a", "bcdef"}); + test_full_split(" abcdef", {"", "abcdef"}); + test_full_split("abcdef ", {"abcdef", ""}); + test_full_split("ab cd ef", {"ab", "cd", "ef"}); + test_full_split("ab cdef ", {"ab", "cdef", ""}); + test_full_split(" abcd ef", {"", "abcd", "ef"}); + test_full_split(" abcdef ", {"", "abcdef", ""}); + test_full_split(" ab cd ef ", {"", "ab", "cd", "ef", ""}); + test_full_split(" ab cd ef ", {"", "", "ab", "", "cd", "", "ef", "", ""}); + test_full_split("ab cd ef gh", ' ', 3, {"ab", "cd", "ef gh"}); +} + +TEST(Misc, StringBuilder) { + auto small_str = td::string{"abcdefghij"}; + auto big_str = td::string(1000, 'a'); + using V = td::vector<td::string>; + for (auto use_buf : {false, true}) { + for (std::size_t initial_buffer_size : {0, 1, 5, 10, 100, 1000, 2000}) { + for (const auto &test : + {V{small_str}, V{small_str, big_str, big_str, small_str}, V{big_str, small_str, big_str}}) { + td::string buf(initial_buffer_size, '\0'); + td::StringBuilder sb(buf, use_buf); + td::string res; + for (const auto &x : test) { + res += x; + sb << x; + } + if (use_buf) { + ASSERT_EQ(res, sb.as_cslice()); + } else { + auto got = sb.as_cslice(); + res.resize(got.size()); + ASSERT_EQ(res, got); + } + } + } + } +} + +TEST(Misc, As) { + char buf[100]; + td::as<int>(buf) = 123; + ASSERT_EQ(123, td::as<int>(static_cast<const char *>(buf))); + ASSERT_EQ(123, td::as<int>(static_cast<char *>(buf))); + char buf2[100]; + td::as<int>(buf2) = td::as<int>(buf); + ASSERT_EQ(123, td::as<int>(static_cast<const char *>(buf2))); + ASSERT_EQ(123, td::as<int>(static_cast<char *>(buf2))); +} + +TEST(Misc, Regression) { + td::string name = "regression_db"; + td::RegressionTester::destroy(name); + + { + auto tester = td::RegressionTester::create(name); + tester->save_db(); + tester->verify_test("one_plus_one", "two").ensure(); + tester->verify_test("one_plus_one", "two").ensure(); + tester->verify_test("two_plus_one", "three").ensure(); + tester->verify_test("one_plus_one", "two").ensure(); + tester->verify_test("two_plus_one", "three").ensure(); + tester->save_db(); + } + { + auto tester = td::RegressionTester::create(name); + tester->save_db(); + tester->verify_test("one_plus_one", "two").ensure(); + tester->verify_test("one_plus_one", "two").ensure(); + tester->verify_test("two_plus_one", "three").ensure(); + tester->verify_test("one_plus_one", "two").ensure(); + tester->verify_test("two_plus_one", "three").ensure(); + tester->save_db(); + tester->verify_test("one_plus_one", "three").ensure_error(); + tester->verify_test("two_plus_one", "two").ensure_error(); + } + { + auto tester = td::RegressionTester::create(name); + tester->verify_test("one_plus_one", "three").ensure_error(); + tester->verify_test("two_plus_one", "two").ensure_error(); + } +} + +TEST(Misc, Bits) { + ASSERT_EQ(32, td::count_leading_zeroes32(0)); + ASSERT_EQ(64, td::count_leading_zeroes64(0)); + ASSERT_EQ(32, td::count_trailing_zeroes32(0)); + ASSERT_EQ(64, td::count_trailing_zeroes64(0)); + + for (int i = 0; i < 32; i++) { + ASSERT_EQ(31 - i, td::count_leading_zeroes32(1u << i)); + ASSERT_EQ(i, td::count_trailing_zeroes32(1u << i)); + ASSERT_EQ(31 - i, td::count_leading_zeroes_non_zero32(1u << i)); + ASSERT_EQ(i, td::count_trailing_zeroes_non_zero32(1u << i)); + } + for (int i = 0; i < 64; i++) { + ASSERT_EQ(63 - i, td::count_leading_zeroes64(1ull << i)); + ASSERT_EQ(i, td::count_trailing_zeroes64(1ull << i)); + ASSERT_EQ(63 - i, td::count_leading_zeroes_non_zero64(1ull << i)); + ASSERT_EQ(i, td::count_trailing_zeroes_non_zero64(1ull << i)); + } + + ASSERT_EQ(0x12345678u, td::bswap32(0x78563412u)); + ASSERT_EQ(0x12345678abcdef67ull, td::bswap64(0x67efcdab78563412ull)); + + td::uint8 buf[8] = {1, 90, 2, 18, 129, 255, 0, 2}; + td::uint64 num2 = td::bswap64(td::as<td::uint64>(buf)); + td::uint64 num = (static_cast<td::uint64>(buf[0]) << 56) | (static_cast<td::uint64>(buf[1]) << 48) | + (static_cast<td::uint64>(buf[2]) << 40) | (static_cast<td::uint64>(buf[3]) << 32) | + (static_cast<td::uint64>(buf[4]) << 24) | (static_cast<td::uint64>(buf[5]) << 16) | + (static_cast<td::uint64>(buf[6]) << 8) | (static_cast<td::uint64>(buf[7])); + ASSERT_EQ(num, num2); + + ASSERT_EQ(0, td::count_bits32(0)); + ASSERT_EQ(0, td::count_bits64(0)); + ASSERT_EQ(4, td::count_bits32((1u << 31) | 7)); + ASSERT_EQ(4, td::count_bits64((1ull << 63) | 7)); +} + +TEST(Misc, BitsRange) { + auto to_vec_a = [](td::uint64 x) { + td::vector<td::int32> bits; + for (auto i : td::BitsRange(x)) { + bits.push_back(i); + } + return bits; + }; + + auto to_vec_b = [](td::uint64 x) { + td::vector<td::int32> bits; + td::int32 pos = 0; + while (x != 0) { + if ((x & 1) != 0) { + bits.push_back(pos); + } + x >>= 1; + pos++; + } + return bits; + }; + + auto do_check = [](const td::vector<td::int32> &a, const td::vector<td::int32> &b) { + ASSERT_EQ(b, a); + }; + auto check = [&](td::uint64 x) { + do_check(to_vec_a(x), to_vec_b(x)); + }; + + do_check(to_vec_a(21), {0, 2, 4}); + for (int x = 0; x < 100; x++) { + check(x); + check(std::numeric_limits<td::uint32>::max() - x); + check(std::numeric_limits<td::uint64>::max() - x); + } +} + +#if !TD_THREAD_UNSUPPORTED +TEST(Misc, Time) { + td::Stage run; + td::Stage check; + td::Stage finish; + + std::size_t threads_n = 3; + td::vector<td::thread> threads; + td::vector<std::atomic<double>> ts(threads_n); + for (std::size_t i = 0; i < threads_n; i++) { + threads.emplace_back([&, thread_id = i] { + for (td::uint64 round = 1; round < 10000; round++) { + ts[thread_id] = 0; + run.wait(round * threads_n); + ts[thread_id] = td::Time::now(); + check.wait(round * threads_n); + for (auto &ts_ref : ts) { + auto other_ts = ts_ref.load(); + if (other_ts != 0) { + ASSERT_TRUE(other_ts <= td::Time::now_cached()); + } + } + + finish.wait(round * threads_n); + } + }); + } + for (auto &thread : threads) { + thread.join(); + } +} +#endif + +TEST(Misc, uint128) { + td::vector<td::uint64> parts = {0, + 1, + 2000, + 2001, + std::numeric_limits<td::uint64>::max(), + std::numeric_limits<td::uint64>::max() - 1, + std::numeric_limits<td::uint32>::max(), + static_cast<td::uint64>(std::numeric_limits<td::uint32>::max()) + 1}; + td::vector<td::int64> signed_parts = {0, + 1, + 2000, + 2001, + -1, + -2000, + -2001, + std::numeric_limits<td::int64>::max(), + std::numeric_limits<td::int64>::max() - 1, + std::numeric_limits<td::int64>::min(), + std::numeric_limits<td::int64>::min() + 1, + std::numeric_limits<td::int32>::max(), + static_cast<td::int64>(std::numeric_limits<td::int32>::max()) + 1, + std::numeric_limits<td::int32>::max() - 1, + std::numeric_limits<td::int32>::min(), + std::numeric_limits<td::int32>::min() + 1, + static_cast<td::int64>(std::numeric_limits<td::int32>::min()) - 1}; + +#if TD_HAVE_INT128 + auto to_intrinsic = [](td::uint128_emulated num) { + return td::uint128_intrinsic(num.hi(), num.lo()); + }; + auto eq = [](td::uint128_emulated a, td::uint128_intrinsic b) { + return a.hi() == b.hi() && a.lo() == b.lo(); + }; + auto ensure_eq = [&](td::uint128_emulated a, td::uint128_intrinsic b) { + if (!eq(a, b)) { + LOG(FATAL) << "[" << a.hi() << ";" << a.lo() << "] vs [" << b.hi() << ";" << b.lo() << "]"; + } + }; +#endif + + td::vector<td::uint128_emulated> nums; + for (auto hi : parts) { + for (auto lo : parts) { + auto a = td::uint128_emulated(hi, lo); +#if TD_HAVE_INT128 + auto ia = td::uint128_intrinsic(hi, lo); + ensure_eq(a, ia); +#endif + nums.push_back(a); + nums.pop_back(); + nums.push_back({hi, lo}); + } + } + +#if TD_HAVE_INT128 + for (auto a : nums) { + auto ia = to_intrinsic(a); + ensure_eq(a, ia); + CHECK(a.is_zero() == ia.is_zero()); + for (int i = 0; i <= 130; i++) { + ensure_eq(a.shl(i), ia.shl(i)); + ensure_eq(a.shr(i), ia.shr(i)); + } + for (auto b : parts) { + ensure_eq(a.mult(b), ia.mult(b)); + } + for (auto b : signed_parts) { + ensure_eq(a.mult_signed(b), ia.mult_signed(b)); + if (b == 0) { + continue; + } + td::int64 q; + td::int64 r; + a.divmod_signed(b, &q, &r); + td::int64 iq; + td::int64 ir; + ia.divmod_signed(b, &iq, &ir); + ASSERT_EQ(q, iq); + ASSERT_EQ(r, ir); + } + for (auto b : nums) { + auto ib = to_intrinsic(b); + //LOG(ERROR) << ia.hi() << ";" << ia.lo() << " " << ib.hi() << ";" << ib.lo(); + ensure_eq(a.mult(b), ia.mult(ib)); + ensure_eq(a.add(b), ia.add(ib)); + ensure_eq(a.sub(b), ia.sub(ib)); + if (!b.is_zero()) { + ensure_eq(a.div(b), ia.div(ib)); + ensure_eq(a.mod(b), ia.mod(ib)); + } + } + } + + for (auto signed_part : signed_parts) { + auto a = td::uint128_emulated::from_signed(signed_part); + auto ia = td::uint128_intrinsic::from_signed(signed_part); + ensure_eq(a, ia); + } +#endif +} + +template <template <class T> class HashT, class ValueT> +static td::Status test_hash(const td::vector<ValueT> &values) { + for (std::size_t i = 0; i < values.size(); i++) { + for (std::size_t j = i; j < values.size(); j++) { + auto &a = values[i]; + auto &b = values[j]; + auto a_hash = HashT<ValueT>()(a); + auto b_hash = HashT<ValueT>()(b); + if (a == b) { + if (a_hash != b_hash) { + return td::Status::Error("Hash differs for same values"); + } + } else { + if (a_hash == b_hash) { + return td::Status::Error("Hash is the same for different values"); + } + } + } + } + return td::Status::OK(); +} + +class BadValue { + public: + explicit BadValue(std::size_t value) : value_(value) { + } + + template <class H> + friend H AbslHashValue(H hasher, const BadValue &value) { + return hasher; + } + bool operator==(const BadValue &other) const { + return value_ == other.value_; + } + + private: + std::size_t value_; +}; + +class ValueA { + public: + explicit ValueA(std::size_t value) : value_(value) { + } + template <class H> + friend H AbslHashValue(H hasher, ValueA value) { + return H::combine(std::move(hasher), value.value_); + } + bool operator==(const ValueA &other) const { + return value_ == other.value_; + } + + private: + std::size_t value_; +}; + +class ValueB { + public: + explicit ValueB(std::size_t value) : value_(value) { + } + + template <class H> + friend H AbslHashValue(H hasher, ValueB value) { + return H::combine(std::move(hasher), value.value_); + } + bool operator==(const ValueB &other) const { + return value_ == other.value_; + } + + private: + std::size_t value_; +}; + +template <template <class T> class HashT> +static void test_hash() { + // Just check that the following compiles + AbslHashValue(td::Hasher(), ValueA{1}); + HashT<ValueA>()(ValueA{1}); + std::unordered_map<ValueA, int, HashT<ValueA>> s; + s[ValueA{1}] = 1; + td::HashMap<ValueA, int> su; + su[ValueA{1}] = 1; + td::HashSet<ValueA> su2; + su2.insert(ValueA{1}); +#if TD_HAVE_ABSL + std::unordered_map<ValueA, int, absl::Hash<ValueA>> x; + absl::flat_hash_map<ValueA, int, HashT<ValueA>> sa; + sa[ValueA{1}] = 1; +#endif + + test_hash<HashT, std::size_t>({1, 2, 3, 4, 5}).ensure(); + test_hash<HashT, BadValue>({BadValue{1}, BadValue{2}}).ensure_error(); + test_hash<HashT, ValueA>({ValueA{1}, ValueA{2}}).ensure(); + test_hash<HashT, ValueB>({ValueB{1}, ValueB{2}}).ensure(); + test_hash<HashT, std::pair<int, int>>({{1, 1}, {1, 2}}).ensure(); + // FIXME: use some better hash + //test_hash<HashT, std::pair<int, int>>({{1, 1}, {1, 2}, {2, 1}, {2, 2}}).ensure(); +} + +TEST(Misc, Hasher) { + test_hash<td::TdHash>(); +#if TD_HAVE_ABSL + test_hash<td::AbslHash>(); +#endif +} + +TEST(Misc, CancellationToken) { + td::CancellationTokenSource source; + source.cancel(); + auto token1 = source.get_cancellation_token(); + auto token2 = source.get_cancellation_token(); + CHECK(!token1); + source.cancel(); + CHECK(token1); + CHECK(token2); + auto token3 = source.get_cancellation_token(); + CHECK(!token3); + source.cancel(); + CHECK(token3); + + auto token4 = source.get_cancellation_token(); + CHECK(!token4); + source = td::CancellationTokenSource{}; + CHECK(token4); +} + +TEST(Misc, Xorshift128plus) { + td::Random::Xorshift128plus rnd(123); + ASSERT_EQ(11453256657207062272ull, rnd()); + ASSERT_EQ(14917490455889357332ull, rnd()); + ASSERT_EQ(5645917797309401285ull, rnd()); + ASSERT_EQ(13554822455746959330ull, rnd()); +} + +TEST(Misc, uname) { + auto first_version = td::get_operating_system_version(); + auto second_version = td::get_operating_system_version(); + ASSERT_STREQ(first_version, second_version); + ASSERT_EQ(first_version.begin(), second_version.begin()); + ASSERT_TRUE(!first_version.empty()); +} + +TEST(Misc, serialize) { + td::int32 x = 1; + ASSERT_EQ(td::base64_encode(td::serialize(x)), td::base64_encode(td::string("\x01\x00\x00\x00", 4))); + td::int64 y = -2; + ASSERT_EQ(td::base64_encode(td::serialize(y)), td::base64_encode(td::string("\xfe\xff\xff\xff\xff\xff\xff\xff", 8))); +} + +TEST(Misc, check_reset_guard) { + CheckExitGuard check_exit_guard{false}; +} + +TEST(FloodControl, Fast) { + td::FloodControlFast fc; + fc.add_limit(1, 5); + fc.add_limit(5, 10); + + td::int32 count = 0; + double now = 0; + for (int i = 0; i < 100; i++) { + now = fc.get_wakeup_at(); + fc.add_event(now); + LOG(INFO) << ++count << ": " << now; } } diff --git a/protocols/Telegram/tdlib/td/tdutils/test/port.cpp b/protocols/Telegram/tdlib/td/tdutils/test/port.cpp new file mode 100644 index 0000000000..92b1729977 --- /dev/null +++ b/protocols/Telegram/tdlib/td/tdutils/test/port.cpp @@ -0,0 +1,316 @@ +// +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +#include "td/utils/algorithm.h" +#include "td/utils/common.h" +#include "td/utils/logging.h" +#include "td/utils/misc.h" +#include "td/utils/port/EventFd.h" +#include "td/utils/port/FileFd.h" +#include "td/utils/port/IoSlice.h" +#include "td/utils/port/path.h" +#include "td/utils/port/signals.h" +#include "td/utils/port/sleep.h" +#include "td/utils/port/thread.h" +#include "td/utils/port/thread_local.h" +#include "td/utils/Random.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/Slice.h" +#include "td/utils/SliceBuilder.h" +#include "td/utils/tests.h" +#include "td/utils/Time.h" + +#if TD_PORT_POSIX && !TD_THREAD_UNSUPPORTED +#include <algorithm> +#include <atomic> +#include <mutex> + +#include <pthread.h> +#include <signal.h> +#endif + +TEST(Port, files) { + td::CSlice main_dir = "test_dir"; + td::rmrf(main_dir).ignore(); + ASSERT_TRUE(td::FileFd::open(main_dir, td::FileFd::Write).is_error()); + ASSERT_TRUE(td::walk_path(main_dir, [](td::CSlice name, td::WalkPath::Type type) { UNREACHABLE(); }).is_error()); + td::mkdir(main_dir).ensure(); + td::mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "A").ensure(); + td::mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "B").ensure(); + td::mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "B" << TD_DIR_SLASH << "D").ensure(); + td::mkdir(PSLICE() << main_dir << TD_DIR_SLASH << "C").ensure(); + ASSERT_TRUE(td::FileFd::open(main_dir, td::FileFd::Write).is_error()); + td::string fd_path = PSTRING() << main_dir << TD_DIR_SLASH << "t.txt"; + td::string fd2_path = PSTRING() << main_dir << TD_DIR_SLASH << "C" << TD_DIR_SLASH << "t2.txt"; + + auto fd = td::FileFd::open(fd_path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok(); + auto fd2 = td::FileFd::open(fd2_path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok(); + fd2.close(); + + int cnt = 0; + const int ITER_COUNT = 1000; + for (int i = 0; i < ITER_COUNT; i++) { + td::walk_path(main_dir, [&](td::CSlice name, td::WalkPath::Type type) { + if (type == td::WalkPath::Type::NotDir) { + ASSERT_TRUE(name == fd_path || name == fd2_path); + } + cnt++; + }).ensure(); + } + ASSERT_EQ((5 * 2 + 2) * ITER_COUNT, cnt); + bool was_abort = false; + td::walk_path(main_dir, [&](td::CSlice name, td::WalkPath::Type type) { + CHECK(!was_abort); + if (type == td::WalkPath::Type::EnterDir && ends_with(name, PSLICE() << TD_DIR_SLASH << "B")) { + was_abort = true; + return td::WalkPath::Action::Abort; + } + return td::WalkPath::Action::Continue; + }).ensure(); + CHECK(was_abort); + + cnt = 0; + bool is_first_dir = true; + td::walk_path(main_dir, [&](td::CSlice name, td::WalkPath::Type type) { + cnt++; + if (type == td::WalkPath::Type::EnterDir) { + if (is_first_dir) { + is_first_dir = false; + } else { + return td::WalkPath::Action::SkipDir; + } + } + return td::WalkPath::Action::Continue; + }).ensure(); + ASSERT_EQ(6, cnt); + + ASSERT_EQ(0u, fd.get_size().move_as_ok()); + ASSERT_EQ(12u, fd.write("Hello world!").move_as_ok()); + ASSERT_EQ(4u, fd.pwrite("abcd", 1).move_as_ok()); + char buf[100]; + td::MutableSlice buf_slice(buf, sizeof(buf)); + ASSERT_TRUE(fd.pread(buf_slice.substr(0, 4), 2).is_error()); + fd.seek(11).ensure(); + ASSERT_EQ(2u, fd.write("?!").move_as_ok()); + + ASSERT_TRUE(td::FileFd::open(main_dir, td::FileFd::Read | td::FileFd::CreateNew).is_error()); + fd = td::FileFd::open(fd_path, td::FileFd::Read | td::FileFd::Create).move_as_ok(); + ASSERT_EQ(13u, fd.get_size().move_as_ok()); + ASSERT_EQ(4u, fd.pread(buf_slice.substr(0, 4), 1).move_as_ok()); + ASSERT_STREQ("abcd", buf_slice.substr(0, 4)); + + fd.seek(0).ensure(); + ASSERT_EQ(13u, fd.read(buf_slice.substr(0, 13)).move_as_ok()); + ASSERT_STREQ("Habcd world?!", buf_slice.substr(0, 13)); +} + +TEST(Port, SparseFiles) { + td::CSlice path = "sparse.txt"; + td::unlink(path).ignore(); + auto fd = td::FileFd::open(path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok(); + ASSERT_EQ(0, fd.get_size().move_as_ok()); + td::int64 offset = 100000000; + fd.pwrite("a", offset).ensure(); + ASSERT_EQ(offset + 1, fd.get_size().move_as_ok()); + auto real_size = fd.get_real_size().move_as_ok(); + if (real_size >= offset + 1) { + LOG(ERROR) << "File system doesn't support sparse files, rewind during streaming can be slow"; + } + td::unlink(path).ensure(); +} + +TEST(Port, LargeFiles) { + td::CSlice path = "large.txt"; + td::unlink(path).ignore(); + auto fd = td::FileFd::open(path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok(); + ASSERT_EQ(0, fd.get_size().move_as_ok()); + td::int64 offset = static_cast<td::int64>(3) << 30; + if (fd.pwrite("abcd", offset).is_error()) { + LOG(ERROR) << "Writing to large files isn't supported"; + td::unlink(path).ensure(); + return; + } + fd = td::FileFd::open(path, td::FileFd::Read).move_as_ok(); + ASSERT_EQ(offset + 4, fd.get_size().move_as_ok()); + td::string res(4, '\0'); + if (fd.pread(res, offset).is_error()) { + LOG(ERROR) << "Reading of large files isn't supported"; + td::unlink(path).ensure(); + return; + } + ASSERT_STREQ(res, "abcd"); + fd.close(); + td::unlink(path).ensure(); +} + +TEST(Port, Writev) { + td::vector<td::IoSlice> vec; + td::CSlice test_file_path = "test.txt"; + td::unlink(test_file_path).ignore(); + auto fd = td::FileFd::open(test_file_path, td::FileFd::Write | td::FileFd::CreateNew).move_as_ok(); + vec.push_back(td::as_io_slice("a")); + vec.push_back(td::as_io_slice("b")); + vec.push_back(td::as_io_slice("cd")); + ASSERT_EQ(4u, fd.writev(vec).move_as_ok()); + vec.clear(); + vec.push_back(td::as_io_slice("efg")); + vec.push_back(td::as_io_slice("")); + vec.push_back(td::as_io_slice("hi")); + ASSERT_EQ(5u, fd.writev(vec).move_as_ok()); + fd.close(); + fd = td::FileFd::open(test_file_path, td::FileFd::Read).move_as_ok(); + td::Slice expected_content = "abcdefghi"; + ASSERT_EQ(static_cast<td::int64>(expected_content.size()), fd.get_size().ok()); + td::string content(expected_content.size(), '\0'); + ASSERT_EQ(content.size(), fd.read(content).move_as_ok()); + ASSERT_EQ(expected_content, content); +} + +#if TD_PORT_POSIX && !TD_THREAD_UNSUPPORTED + +static std::mutex m; +static td::vector<td::string> ptrs; +static td::vector<int *> addrs; +static TD_THREAD_LOCAL int thread_id; + +static void on_user_signal(int sig) { + int addr; + addrs[thread_id] = &addr; + std::unique_lock<std::mutex> guard(m); + ptrs.push_back(td::to_string(thread_id)); +} + +TEST(Port, SignalsAndThread) { + td::setup_signals_alt_stack().ensure(); + td::set_signal_handler(td::SignalType::User, on_user_signal).ensure(); + SCOPE_EXIT { + td::set_signal_handler(td::SignalType::User, nullptr).ensure(); + }; + td::vector<td::string> ans = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}; + { + td::vector<td::thread> threads; + int thread_n = 10; + td::vector<td::Stage> stages(thread_n); + ptrs.clear(); + addrs.resize(thread_n); + for (int i = 0; i < 10; i++) { + threads.emplace_back([&, i] { + td::setup_signals_alt_stack().ensure(); + if (i != 0) { + stages[i].wait(2); + } + thread_id = i; + pthread_kill(pthread_self(), SIGUSR1); + if (i + 1 < thread_n) { + stages[i + 1].wait(2); + } + }); + } + for (auto &t : threads) { + t.join(); + } + CHECK(ptrs == ans); + + //LOG(ERROR) << ptrs; + //LOG(ERROR) << addrs; + } + + { + td::Stage stage; + td::vector<td::thread> threads; + int thread_n = 10; + ptrs.clear(); + addrs.resize(thread_n); + for (int i = 0; i < 10; i++) { + threads.emplace_back([&, i] { + stage.wait(thread_n); + thread_id = i; + pthread_kill(pthread_self(), SIGUSR1); + }); + } + for (auto &t : threads) { + t.join(); + } + std::sort(ptrs.begin(), ptrs.end()); + CHECK(ptrs == ans); + auto addrs_size = addrs.size(); + td::unique(addrs); + ASSERT_EQ(addrs_size, addrs.size()); + //LOG(ERROR) << addrs; + } +} + +#if !TD_EVENTFD_UNSUPPORTED +TEST(Port, EventFdAndSignals) { + td::set_signal_handler(td::SignalType::User, [](int signal) {}).ensure(); + SCOPE_EXIT { + td::set_signal_handler(td::SignalType::User, nullptr).ensure(); + }; + + std::atomic_flag flag; + flag.test_and_set(); + auto main_thread = pthread_self(); + td::thread interrupt_thread{[&flag, &main_thread] { + td::setup_signals_alt_stack().ensure(); + while (flag.test_and_set()) { + pthread_kill(main_thread, SIGUSR1); + td::usleep_for(1000 * td::Random::fast(1, 10)); // 0.001s - 0.01s + } + }}; + + for (int timeout_ms : {0, 1, 2, 10, 100, 500}) { + double min_diff = 10000000; + double max_diff = 0; + for (int t = 0; t < td::max(5, 1000 / td::max(timeout_ms, 1)); t++) { + td::EventFd event_fd; + event_fd.init(); + auto start = td::Timestamp::now(); + event_fd.wait(timeout_ms); + auto end = td::Timestamp::now(); + auto passed = end.at() - start.at(); + auto diff = passed * 1000 - timeout_ms; + min_diff = td::min(min_diff, diff); + max_diff = td::max(max_diff, diff); + } + + LOG_CHECK(min_diff >= 0) << min_diff; + // LOG_CHECK(max_diff < 10) << max_diff; + LOG(INFO) << min_diff << " " << max_diff; + } + flag.clear(); +} +#endif +#endif + +#if TD_HAVE_THREAD_AFFINITY +TEST(Port, ThreadAffinityMask) { + auto thread_id = td::this_thread::get_id(); + auto old_mask = td::thread::get_affinity_mask(thread_id); + LOG(INFO) << "Initial thread " << thread_id << " affinity mask: " << old_mask; + for (size_t i = 0; i < 64; i++) { + auto mask = td::thread::get_affinity_mask(thread_id); + LOG(INFO) << mask; + auto result = td::thread::set_affinity_mask(thread_id, static_cast<td::uint64>(1) << i); + LOG(INFO) << i << ": " << result << ' ' << td::thread::get_affinity_mask(thread_id); + + if (i <= 1) { + td::thread thread([] { + auto thread_id = td::this_thread::get_id(); + auto mask = td::thread::get_affinity_mask(thread_id); + LOG(INFO) << "New thread " << thread_id << " affinity mask: " << mask; + auto result = td::thread::set_affinity_mask(thread_id, 1); + LOG(INFO) << "Thread " << thread_id << ": " << result << ' ' << td::thread::get_affinity_mask(thread_id); + }); + LOG(INFO) << "Will join new thread " << thread.get_id() + << " with affinity mask: " << td::thread::get_affinity_mask(thread.get_id()); + } + } + auto result = td::thread::set_affinity_mask(thread_id, old_mask); + LOG(INFO) << result; + old_mask = td::thread::get_affinity_mask(thread_id); + LOG(INFO) << old_mask; +} +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/pq.cpp b/protocols/Telegram/tdlib/td/tdutils/test/pq.cpp index 5210cc2638..d919d661b5 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/pq.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/pq.cpp @@ -1,29 +1,24 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/utils/tests.h" +#include "td/utils/algorithm.h" #include "td/utils/BigNum.h" #include "td/utils/common.h" #include "td/utils/crypto.h" #include "td/utils/format.h" #include "td/utils/logging.h" -#include "td/utils/misc.h" +#include "td/utils/SliceBuilder.h" -#include <algorithm> #include <limits> #include <utility> -REGISTER_TESTS(pq) - -using namespace td; - -#if TD_HAVE_OPENSSL -static bool is_prime(uint64 x) { - for (uint64 d = 2; d < x && d * d <= x; d++) { +static bool is_prime(td::uint64 x) { + for (td::uint64 d = 2; d < x && d * d <= x; d++) { if (x % d == 0) { return false; } @@ -31,9 +26,9 @@ static bool is_prime(uint64 x) { return true; } -static std::vector<uint64> gen_primes(uint64 L, uint64 R, int limit = 0) { - std::vector<uint64> res; - for (auto x = L; x <= R && (limit <= 0 || res.size() < static_cast<std::size_t>(limit)); x++) { +static td::vector<td::uint64> gen_primes(td::uint64 L, td::uint64 R, std::size_t limit = 0) { + td::vector<td::uint64> res; + for (auto x = L; x <= R && (limit <= 0 || res.size() < limit); x++) { if (is_prime(x)) { res.push_back(x); } @@ -41,22 +36,26 @@ static std::vector<uint64> gen_primes(uint64 L, uint64 R, int limit = 0) { return res; } -static std::vector<uint64> gen_primes() { - std::vector<uint64> result; - append(result, gen_primes(1, 100)); - append(result, gen_primes((1ull << 31) - 500000, std::numeric_limits<uint64>::max(), 5)); - append(result, gen_primes((1ull << 32) - 500000, std::numeric_limits<uint64>::max(), 5)); - append(result, gen_primes((1ull << 39) - 500000, std::numeric_limits<uint64>::max(), 1)); +static td::vector<td::uint64> gen_primes(int mode) { + td::vector<td::uint64> result; + if (mode == 1) { + for (size_t i = 10; i <= 19; i++) { + td::append(result, gen_primes(i * 100000000, (i + 1) * 100000000, 1)); + } + } else { + td::append(result, gen_primes(1, 100)); + td::append(result, gen_primes((1ull << 31) - 500000, std::numeric_limits<td::uint64>::max(), 5)); + td::append(result, gen_primes((1ull << 32) - 500000, std::numeric_limits<td::uint64>::max(), 2)); + td::append(result, gen_primes((1ull << 39) - 500000, std::numeric_limits<td::uint64>::max(), 1)); + } return result; } -using PqQuery = std::pair<uint64, uint64>; -static bool cmp(const PqQuery &a, const PqQuery &b) { - return a.first * a.second < b.first * b.second; -} -static std::vector<PqQuery> gen_pq_queries() { - std::vector<PqQuery> res; - auto primes = gen_primes(); +using PqQuery = std::pair<td::uint64, td::uint64>; + +static td::vector<PqQuery> gen_pq_queries(int mode = 0) { + td::vector<PqQuery> res; + auto primes = gen_primes(mode); for (auto q : primes) { for (auto p : primes) { if (p > q) { @@ -65,28 +64,56 @@ static std::vector<PqQuery> gen_pq_queries() { res.emplace_back(p, q); } } - std::sort(res.begin(), res.end(), cmp); return res; } -static void test_pq(uint64 first, uint64 second) { - BigNum p = BigNum::from_decimal(PSLICE() << first); - BigNum q = BigNum::from_decimal(PSLICE() << second); +static td::string to_binary(td::uint64 x) { + td::string result; + do { + result = static_cast<char>(x & 255) + result; + x >>= 8; + } while (x > 0); + return result; +} + +static void test_pq_fast(td::uint64 first, td::uint64 second) { + if ((static_cast<td::uint64>(1) << 63) / first <= second) { + return; + } + + td::string p_str; + td::string q_str; + int err = td::pq_factorize(to_binary(first * second), &p_str, &q_str); + ASSERT_EQ(err, 0); + + ASSERT_STREQ(p_str, to_binary(first)); + ASSERT_STREQ(q_str, to_binary(second)); +} + +#if TD_HAVE_OPENSSL +static void test_pq_slow(td::uint64 first, td::uint64 second) { + if ((static_cast<td::uint64>(1) << 63) / first > second) { + return; + } + + td::BigNum p = td::BigNum::from_decimal(PSLICE() << first).move_as_ok(); + td::BigNum q = td::BigNum::from_decimal(PSLICE() << second).move_as_ok(); - BigNum pq; - BigNumContext context; - BigNum::mul(pq, p, q, context); - std::string pq_str = pq.to_binary(); + td::BigNum pq; + td::BigNumContext context; + td::BigNum::mul(pq, p, q, context); + td::string pq_str = pq.to_binary(); - std::string p_str, q_str; + td::string p_str; + td::string q_str; int err = td::pq_factorize(pq_str, &p_str, &q_str); - CHECK(err == 0) << first << " * " << second; + LOG_CHECK(err == 0) << first << " * " << second; - BigNum p_res = BigNum::from_binary(p_str); - BigNum q_res = BigNum::from_binary(q_str); + td::BigNum p_res = td::BigNum::from_binary(p_str); + td::BigNum q_res = td::BigNum::from_binary(q_str); - CHECK(p_str == p.to_binary()) << td::tag("got", p_res.to_decimal()) << td::tag("expected", first); - CHECK(q_str == q.to_binary()) << td::tag("got", q_res.to_decimal()) << td::tag("expected", second); + LOG_CHECK(p_str == p.to_binary()) << td::tag("got", p_res.to_decimal()) << td::tag("expected", first); + LOG_CHECK(q_str == q.to_binary()) << td::tag("got", q_res.to_decimal()) << td::tag("expected", second); } #endif @@ -101,18 +128,35 @@ TEST(CryptoPQ, hands) { ASSERT_EQ(179424611ull, td::pq_factorize(179424611ull * 179424673ull)); #if TD_HAVE_OPENSSL - test_pq(4294467311, 4294467449); + test_pq_slow(4294467311, 4294467449); #endif } -#if TD_HAVE_OPENSSL -TEST(CryptoPQ, generated_slow) { +TEST(CryptoPQ, four) { for (int i = 0; i < 100000; i++) { - test_pq(2, 2); + test_pq_fast(2, 2); + } +} + +TEST(CryptoPQ, generated_fast) { + auto queries = gen_pq_queries(); + for (const auto &query : queries) { + test_pq_fast(query.first, query.second); } +} + +TEST(CryptoPQ, generated_server) { + auto queries = gen_pq_queries(1); + for (const auto &query : queries) { + test_pq_fast(query.first, query.second); + } +} + +#if TD_HAVE_OPENSSL +TEST(CryptoPQ, generated_slow) { auto queries = gen_pq_queries(); - for (auto query : queries) { - test_pq(query.first, query.second); + for (const auto &query : queries) { + test_pq_slow(query.first, query.second); } } -#endif
\ No newline at end of file +#endif diff --git a/protocols/Telegram/tdlib/td/tdutils/test/variant.cpp b/protocols/Telegram/tdlib/td/tdutils/test/variant.cpp index 5c5e18d1d8..755acdfa98 100644 --- a/protocols/Telegram/tdlib/td/tdutils/test/variant.cpp +++ b/protocols/Telegram/tdlib/td/tdutils/test/variant.cpp @@ -1,5 +1,5 @@ // -// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2018 +// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2022 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -9,22 +9,18 @@ #include "td/utils/tests.h" #include "td/utils/Variant.h" -REGISTER_TESTS(variant); - -using namespace td; - static const size_t BUF_SIZE = 1024 * 1024; static char buf[BUF_SIZE], buf2[BUF_SIZE]; -static StringBuilder sb(MutableSlice(buf, BUF_SIZE - 1)); -static StringBuilder sb2(MutableSlice(buf2, BUF_SIZE - 1)); +static td::StringBuilder sb(td::MutableSlice(buf, BUF_SIZE - 1)); +static td::StringBuilder sb2(td::MutableSlice(buf2, BUF_SIZE - 1)); -static std::string move_sb() { +static td::string move_sb() { auto res = sb.as_cslice().str(); sb.clear(); return res; } -static std::string name(int id) { +static td::string name(int id) { if (id == 1) { return "A"; } @@ -58,18 +54,18 @@ using C = Class<3>; TEST(Variant, simple) { { - Variant<std::unique_ptr<A>, std::unique_ptr<B>, std::unique_ptr<C>> abc; + td::Variant<td::unique_ptr<A>, td::unique_ptr<B>, td::unique_ptr<C>> abc; ASSERT_STREQ("", sb.as_cslice()); - abc = std::make_unique<A>(); + abc = td::make_unique<A>(); ASSERT_STREQ("+A", sb.as_cslice()); sb.clear(); - abc = std::make_unique<B>(); + abc = td::make_unique<B>(); ASSERT_STREQ("+B-A", sb.as_cslice()); sb.clear(); - abc = std::make_unique<C>(); + abc = td::make_unique<C>(); ASSERT_STREQ("+C-B", sb.as_cslice()); sb.clear(); } ASSERT_STREQ("-C", move_sb()); sb.clear(); -}; +} |