Merge remote-tracking branch 'td/master'

This commit is contained in:
Andrea Cavalli 2020-11-22 01:11:21 +01:00
commit cba9f74cba
46 changed files with 6477 additions and 459 deletions

View File

@ -1,3 +1,349 @@
Changes in 1.7.0:
* Added a new simplified JSON interface in which updates and responses to requests from all TDLib instances
are received in the same thread:
- The TDLib instance is identified by the unique `client_id` identifier, which is returned by the method
`td_create_client_id`.
- Use the method `td_send` to send a request to a specified client. The TDLib instance is created on the first
request sent to it.
- Use the method `td_receive` to receive updates and request responses from TDLib. The response will contain
the identifier of the client from which the event was received in the field "@client_id".
- Use the method `td_execute` to synchronously execute suitable TDLib methods.
* Added support for adding chats to more than one chat list:
- Added the class `chatPosition`, describing the position of the chat within a chat list.
- Replaced the fields `chat_list`, `order`, `is_sponsored` and `is_pinned` in the class `chat` with
the field `positions`, containing a list of the chat positions in various chat list.
- Replaced the field `order` with the field `positions` in the updates `updateChatLastMessage` and
`updateChatDraftMessage`.
- Added the update `updateChatPosition`.
- Removed the superfluous updates `updateChatChatList`, `updateChatIsSponsored`, `updateChatOrder` and
`updateChatIsPinned`.
- Added the parameter `chat_list` to the method `toggleChatIsPinned`.
- Added the class `chatLists`, containing a list of chat lists.
- Added the method `getChatListsToAddChat`, returning all chat lists to which a chat can be added.
- Added the method `addChatToList`, which can be used to add a chat to a chat list.
- Remove the method `setChatChatList`.
* Added support for chat filters:
- Added the new chat list type `chatListFilter`.
- Added the classes `chatFilterInfo` and `chatFilter`, describing a filter of user chats.
- Added the update `updateChatFilters`, which is sent when the list of chat filters is changed.
- Added the methods `createChatFilter`, `editChatFilter` and `deleteChatFilter` for managing chat filters.
- Added the method `reorderChatFilters` for changing the order of chat filters.
- Added the method `getChatFilter`, returning full information about a chat filter.
- Added the synchronous method `getChatFilterDefaultIconName`.
- Added the classes `recommendedChatFilter` and `recommendedChatFilters`.
- Added the method `getRecommendedChatFilters`, returning a list of recommended chat filters.
* Added support for messages sent on behalf of chats instead of users:
- Added the class `MessageSender`, representing a user or a chat which sent a message.
- Added the class `MessageSenders`, representing a list of message senders.
- Replaced the field `sender_user_id` with the field `sender` of the type `MessageSender` in the classes `message`
and `notificationTypeNewPushMessage`.
- Added the class `messageForwardOriginChat`, which describe a chat as the original sender of a message.
- Added the ability to search messages sent by a chat by replacing the parameter `sender_user_id` with
the parameter `sender` of the type `MessageSender` in the method `searchChatMessages`.
- Added the ability to specify a chat as a local message sender by replacing the parameter `sender_user_id` with
the parameter `sender` of the type `MessageSender` in the method `addLocalMessage`.
* Added support for video calls:
- Added the class `callServer`, describing a server for relaying call data.
- Added the classes `callServerTypeTelegramReflector` and `callServerTypeWebrtc`, representing different types of
supported call servers.
- Replaced the field `connections` with the field `servers` in the class `callStateReady`.
- Removed the class `callConnection`.
- Added the update `updateNewCallSignalingData`.
- Added the method `sendCallSignalingData`.
- Added the field `supports_video_calls` to the class `userFullInfo`.
- Added the field `is_video` to the class `messageCall`.
- Added the field `is_video` to the class `call`.
- Added the parameter `is_video` to the method `createCall`.
- Added the parameter `is_video` to the method `discardCall`.
- Added two new types of call problems `callProblemDistortedVideo` and `callProblemPixelatedVideo`.
- Added the field `library_versions` to the class `callProtocol`, which must be used to specify all supported
call library versions.
* Added support for multiple pinned messages and the ability to pin messages in private chats:
- Added the ability to pin messages in all private chats.
- Added the ability to pin mutiple messages in all chats.
- Added the field `is_pinned` to the class `message`.
- Added the update `updateMessageIsPinned`.
- Added the parameter `only_for_self` to the method `pinChatMessage`, allowing to pin messages in private chats for
one side only.
- Added the ability to find pinned messages in a chat using the filter `searchMessagesFilterPinned`.
- Added the parameter `message_id` to the method `unpinChatMessage`.
- Added the field `message` to the class `chatEventMessageUnpinned`.
- Added the method `unpinAllChatMessages`, which can be used to simultaneously unpin all pinned messages in a chat.
- Documented that notifications about new pinned messages are always silent in channels and private chats.
- The method `getChatPinnedMessage` now returns the newest pinned message in the chat.
- Removed the field `pinned_message_id` from the class `chat`.
- Removed the update `updateChatPinnedMessage`.
* Improved thumbnail representation and added support for animated MPEG4 thumbnails:
- Added the class `ThumbnailFormat`, representing the various supported thumbnail formats.
- Added the class `thumbnail`, containing information about a thumbnail.
- Changed the type of all thumbnail fields from `photoSize` to `thumbnail`.
- Added support for thumbnails in the format `thumbnailFormatMpeg4` for some animations and videos.
- Replaced the classes `inputInlineQueryResultAnimatedGif` and `inputInlineQueryResultAnimatedMpeg4` with
the generic class `inputInlineQueryResultAnimation`.
- Added support for animated thumbnails in the class `inputInlineQueryResultAnimation`.
- The class `photoSize` is now only used for JPEG images.
* Improved support for user profile photos and chat photos:
- Added the field `photo` to the class `userFullInfo`, containing full information about the user photo.
- Added the field `photo` to the class `basicGroupFullInfo`, containing full information about the group photo.
- Added the field `photo` to the class `supergroupFullInfo`, containing full information about the group photo.
- Renamed the class `chatPhoto` to `chatPhotoInfo`.
- Added the field `has_animation` to the classes `profilePhoto` and `chatPhotoInfo`, which is set to true for
animated chat photos.
- Added the classes `chatPhoto` and `chatPhotos`.
- Added minithumbnail support via the field `minithumbnail` in the class `chatPhoto`.
- Added the class `animatedChatPhoto`.
- Added animated chat photo support via the field `animation` in the class `chatPhoto`.
- Removed the classes `userProfilePhoto` and `userProfilePhotos`.
- Changed the type of the field `photo` in the class `messageChatChangePhoto` to `chatPhoto`.
- Changed the type of the fields `old_photo` and `new_photo` in the class `chatEventPhotoChanged` to `chatPhoto`.
- Changed the return type of the method `getUserProfilePhotos` to `chatPhotos`.
- Added the class `InputChatPhoto`, representing a chat or a profile photo to set.
- Changed the type of the parameter `photo` in the methods `setProfilePhoto` and `setChatPhoto` to
the `InputChatPhoto`.
- Added the ability to explicitly re-use previously set profile photos using the class `inputChatPhotoPrevious`.
- Added the ability to set animated chat photos using the class `inputChatPhotoAnimated`.
* Added support for message threads in supergroups and channel comments:
- Added the field `message_thread_id` to the class `message`.
- Added the class `messageThreadInfo`, containing information about a message thread.
- Added the class `messageReplyInfo`, containing information about replies to a message.
- Added the field `reply_info` to the class `messageInteractionInfo`, containing information about message replies.
- Added the field `can_get_message_thread` to the class `message`.
- Added the method `getMessageThread`, returning information about the message thread to which a message belongs.
- Added the method `getMessageThreadHistory`, returning messages belonging to a message thread.
- Added the parameter `message_thread_id` to the methods `sendMessage`, `sendMessageAlbum` and
`sendInlineQueryResultMessage` for sending messages within a thread.
- Added the parameter `message_thread_id` to the method `searchChatMessages` to search messages within a thread.
- Added the parameter `message_thread_id` to the method `viewMessages`.
- Added the parameter `message_thread_id` to the method `setChatDraftMessage`.
- Added the parameter `message_thread_id` to the method `sendChatAction` to send chat actions to a thread.
- Added the field `message_thread_id` to the update `updateUserChatAction`.
* Improved support for message albums:
- Added support for sending and receiving messages of the types `messageAudio` and `messageDocument` as albums.
- Added automatic grouping into audio or document albums in the method `forwardMessages` if all forwarded or
copied messages are of the same type.
- Removed the parameter `as_album` from the method `forwardMessages`. Forwarded message albums are now determined
automatically.
* Simplified usage of methods generating an HTTP link to a message:
- Added the class `messageLink`, representing an HTTP link to a message.
- Combined the methods `getPublicMessageLink` and `getMessageLink` into the method `getMessageLink`, which
now returns a public link to the message if possible and a private link otherwise. The combined method is
an offline method now.
- Added the parameter `for_comment` to the method `getMessageLink`, which allows to get a message link to the message
that opens it in a thread.
- Removed the class `publicMessageLink`.
- Added the field `for_comment` to the class `messageLinkInfo`.
- Added the separate method `getMessageEmbeddingCode`, returning an HTML code for embedding a message.
* Added the ability to block private messages sent via the @replies bot from chats:
- Added the field `is_blocked` to the class `chat`.
- Added the update `updateChatIsBlocked`.
- Added the method `blockMessageSenderFromReplies`.
- Replaced the methods `blockUser` and `unblockUser` with the method `toggleMessageSenderIsBlocked`.
- Replaced the method `getBlockedUsers` with the method `getBlockedMessageSenders`.
* Added support for incoming messages which are replies to messages in different chats:
- Added the field `reply_in_chat_id` to the class `message`.
- The method `getRepliedMessage` can now return the replied message in a different chat.
* Renamed the class `sendMessageOptions` to `messageSendOptions`.
* Added the new `tdapi` static library, which needs to be additionally linked in when static linking is used.
* Changed the type of the field `value` in the class `optionValueInteger` from `int32` to `int64`.
* Changed the type of the field `description` in the class `webPage` from `string` to `formattedText`.
* Improved Instant View support:
- Added the field `view_count` to the class `webPageInstantView`.
- Added the class `richTextAnchorLink`, containing a link to an anchor on the same page.
- Added the class `richTextReference`, containing a reference to a text on the same page.
- Removed the field `text` from the class `richTextAnchor`.
- Removed the field `url` which is no longer needed from the class `webPageInstantView`.
* Allowed the update `updateServiceNotification` to be sent before authorization is completed.
* Disallowed to pass messages in non-strictly increasing order to the method `forwardMessages`.
* Improved sending copies of messages:
- Added the class `messageCopyOptions` and the field `copy_options` to the class `inputMessageForwarded`.
- Removed the fields `send_copy` and `remove_caption` from the class `inputMessageForwarded`.
- Allowed to replace captions in copied messages using the fields `replace_caption` and `new_caption` in
the class `messageCopyOptions`.
- Allowed to specify `reply_to_message_id` when sending a copy of a message.
- Allowed to specify `reply_markup` when sending a copy of a message.
* Allowed passing multiple input language codes to `searchEmojis` by replacing the parameter `input_language_code` with
the parameter `input_language_codes`.
* Added support for public service announcements:
- Added the class `ChatSource` and the field `source` to the class `chatPosition`.
- Added the new type of chat source `chatSourcePublicServiceAnnouncement`.
- Added the field `public_service_announcement_type` to the class `messageForwardInfo`.
* Added support for previewing of private supergroups and channels by their invite link.
- The field `chat_id` in the class `chatInviteLinkInfo` is now non-zero for private supergroups and channels to which
the temporary read access is granted.
- Added the field `accessible_for` to the class `chatInviteLinkInfo`, containing the amount of time for which
read access to the chat will remain available.
* Improved methods for message search:
- Replaced the field `next_from_search_id` with a string field `next_offset` in the class `foundMessages`.
- Added the field `total_count` to the class `foundMessages`; can be -1 if the total count of matching messages is
unknown.
- Replaced the parameter `from_search_id` with the parameter `offset` in the method `searchSecretMessages`.
- Added the parameter `filter` to the method `searchMessages`.
- Added the parameters `min_date` and `max_date` to the method `searchMessages` to search messages sent only within
a particular timeframe.
* Added pkg-config file generation for all installed libraries.
* Added automatic operating system version detection. Use an empty field `system_version` in
the class `tdlibParameters` for the automatic detection.
* Increased maximum file size from 1500 MB to 2000 MB.
* Added support for human-friendly Markdown formatting:
- Added the synchronous method `parseMarkdown` for human-friendly parsing of text entities.
- Added the synchronous method `getMarkdownText` for replacing text entities with a human-friendly
Markdown formatting.
- Added the writable option "always_parse_markdown" which enables automatic parsing of text entities in
all `inputMessageText` objects.
* Added support for dice with random values in messages:
- Added the class `messageDice` to the types of message content; contains a dice.
- Added the class `DiceStickers`, containing animated stickers needed to show the dice.
- Added the class `inputMessageDice` to the types of new input message content; can be used to send a dice.
- Added the update `updateDiceEmojis`, containing information about supported dice emojis.
* Added support for chat statistics in channels and supergroups:
- Added the field `can_get_statistics` to the class `supergroupFullInfo`.
- Added the class `ChatStatistics`, which represents a supergroup or a channel statistics.
- Added the method `getChatStatistics` returning detailed statistics about a chat.
- Added the classes `chatStatisticsMessageInteractionInfo`, `chatStatisticsAdministratorActionsInfo`,
`chatStatisticsMessageSenderInfo` and `chatStatisticsInviterInfo` representing various parts of chat statistics.
- Added the class `statisticalValue` describing recent changes of a statistical value.
- Added the class `StatisticalGraph` describing a statistical graph.
- Added the method `getStatisticalGraph`, which can be used for loading asynchronous or zoomed in statistical graphs.
- Added the class `dateRange` representing a date range for which statistics are available.
- Removed the field `can_view_statistics` from the class `supergroupFullInfo` and marked
the method `getChatStatisticsUrl` as disabled and not working.
* Added support for detailed statistics about interactions with messages:
- Added the class `messageInteractionInfo`, containing information about message views, forwards and replies.
- Added the field `interaction_info` to the class `message`.
- Added the update `updateMessageInteractionInfo`.
- Added the field `can_get_statistics` to the class `message`.
- Added the class `messageStatistics`.
- Added the method `getMessageStatistics`.
- Added the method `getMessagePublicForwards`, returning all forwards of a message to public channels.
- Removed the now superfluous field `views` from the class `message`.
- Removed the now superfluous update `updateMessageViews`.
* Improved support for native polls:
- Added the field `explanation` to the class `pollTypeQuiz`.
- Added the fields `close_date` and `open_period` to the class `poll`.
- Added the fields `close_date` and `open_period` to the class `inputMessagePoll`; for bots only.
- Increased maximum poll question length to 300 characters for bots.
* Added support for anonymous administrators in supergroups:
- Added the field `is_anonymous` to the classes `chatMemberStatusCreator` and `chatMemberStatusAdministrator`.
- The field `author_signature` in the class `message` can now contain a custom title of the anonymous administrator
that sent the message.
* Added support for a new type of inline keyboard buttons, requiring user password entry:
- Added the class `inlineKeyboardButtonTypeCallbackWithPassword`, representing a button requiring password entry from
a user.
- Added the class `callbackQueryPayloadDataWithPassword`, representing new type of callback button payload,
which must be used for the buttons of the type `inlineKeyboardButtonTypeCallbackWithPassword`.
* Added support for making the location of the user public:
- Added the writable option "is_location_visible" to allow other users see location of the current user.
- Added the method `setLocation`, which should be called if `getOption("is_location_visible")` is true and location
changes by more than 1 kilometer.
* Improved Notification API:
- Added the field `sender_name` to the class `notificationTypeNewPushMessage`.
- Added the writable option "disable_sent_scheduled_message_notifications" for disabling notifications about
outgoing scheduled messages that were sent.
- Added the field `is_outgoing` to the class `notificationTypeNewPushMessage` for recognizing
outgoing scheduled messages that were sent.
- Added the fields `has_audios` and `has_documents` to the class `pushMessageContentMediaAlbum`.
* Added the field `date` to the class `draftMessage`.
* Added the update `updateStickerSet`, which is sent after a sticker set is changed.
* Added support for pagination in trending sticker sets:
- Added the parameters `offset` and `limit` to the method `getTrendingStickerSets`.
- Changed the field `sticker_sets` in the update `updateTrendingStickerSets` to contain only the prefix of
trending sticker sets.
* Messages that failed to send can now be found using the filter `searchMessagesFilterFailedToSend`.
* Added the ability to disable automatic server-side file type detection using the new field
`disable_content_type_detection` of the class `inputMessageDocument`.
* Improved chat action bar:
- Added the field `can_unarchive` to the classes `chatActionBarReportSpam` and `chatActionBarReportAddBlock`,
which is true whenever the chat was automatically archived.
- Added the field `distance` to the class `chatActionBarReportAddBlock`,
which denotes the distance between the users.
* Added support for actions suggested to the user by the server:
- Added the class `SuggestedAction`, representing possible actions suggested by the server.
- Added the update `updateSuggestedActions`.
- Added the method `hideSuggestedAction`, which can be used to dismiss a suggested action.
* Supported attaching stickers to animations:
- Added the field `has_stickers` to the class `animation`.
- Added the field `added_sticker_file_ids` to the class `inputMessageAnimation`.
* Added methods for phone number formatting:
- Added the class `countryInfo`, describing a country.
- Added the class `countries`, containing a list of countries.
- Added the method `getCountries`, returning a list of all existing countries.
- Added the class `phonenumberinfo` and the method `getPhoneNumberInfo`, which can be used to format a phone number
according to local rules.
* Improved location support:
- Added the field `horizontal_accuracy` to the class `location`.
- Added the field `heading` to the classes `messageLocation` and `inputMessageLocation` for live locations.
- Added the parameter `heading` to the methods `editMessageLiveLocation` and `editInlineMessageLiveLocation`.
* Added support for proximity alerts in live locations:
- Added the field `proximity_alert_radius` to the classes `messageLocation` and `inputMessageLocation`.
- Added the parameter `proximity_alert_radius` to the methods `editMessageLiveLocation` and
`editInlineMessageLiveLocation`.
- Added the new message content `messageProximityAlertTriggered`, received whenever a proximity alert is triggered.
* Added `CentOS 7` and `CentOS 8` operating systems to the
[TDLib build instructions generator](https://tdlib.github.io/td/build.html).
* Added the CMake configuration option TD_ENABLE_MULTI_PROCESSOR_COMPILATION, which can be used to enable parallel
build with MSVC.
* Added support for sending and receiving messages in secret chats with silent notifications.
* Added the field `progressive_sizes` to the class `photo` to allow partial progressive JPEG photo download.
* Added the field `redirect_stderr` to the class `logStreamFile` to allow explicit control over stderr redirection to
the log file.
* Added the writable option "archive_and_mute_new_chats_from_unknown_users", which can be used to automatically archive
and mute new chats from non-contacts. The option can be set only if the change was suggested by the server.
* Added the writable option "message_unload_delay", which can be used to change the minimum delay before messages are
unloaded from the memory.
* Added the writable option "disable_persistent_network_statistics", which can be used to disable persistent
network usage statistics, significantly reducing disk usage.
* Added the writable option "disable_time_adjustment_protection", which can be used to disable protection from
external time adjustment, significantly reducing disk usage.
* Added the writable option "ignore_default_disable_notification" to allow the application to manually specify the
`disable_notification` option each time when sending messages instead of following the default per-chat settings.
* Added the read-only option "telegram_service_notifications_chat_id", containing the identifier of
the Telegram service notifications chat.
* Added the read-only option "replies_bot_chat_id", containing the identifier of the @replies bot.
* Added the read-only option "group_anonymous_bot_user_id", containing the identifier of the bot which is shown as
the sender of anonymous group messages when viewed from an outdated client.
* Added the new venue provider value "gplaces" for Google Places.
* Added the parameter `return_deleted_file_statistics` to the method `optimizeStorage` to return information about
the files that were deleted instead of the ones that were not.
* Added the ability to search for supergroup members to mention by their name and username:
- Added the new filter `supergroupMembersFilterMention` for the method `getSupergroupMembers`.
- Added the new filter `chatMembersFilterMention` for the method `searchChatMembers`.
* Added support for highlighting bank card numbers:
- Added the new text entity `textEntityTypeBankCardNumber`.
- Added the classes `bankCardInfo` and `bankCardActionOpenUrl`, containing information about a bank card.
- Added the method `getBankCardInfo`, returning information about a bank card.
* Improved methods for managing sticker sets by bots:
- Added the method `setStickerSetThumbnail`.
- Added the ability to create new animated sticker sets and add new stickers to them by adding
the class `inputStickerAnimated`.
- Renamed the class `inputSticker` to `inputStickerStatic`.
- Renamed the field `png_sticker` to `sticker` in the class `inputStickerStatic`.
* Added the method `setCommands` for bots.
* Added the method `getCallbackQueryMessage` for bots.
* Added support for starting bots in private chats through `sendBotStartMessage`.
* Added the field `total_count` to the class `chats`. The field should have a precise value for the responses of
the methods `getChats`, `searchChats` and `getGroupsInCommon`.
* Added the update `updateAnimationSearchParameters`, containing information about animation search parameters.
* Documented that `getRepliedMessage` can be used to get a pinned message, a game message, or an invoice message for
messages of the types `messagePinMessage`, `messageGameScore`, and `messagePaymentSuccessful` respectively.
* Added guarantees that the field `member_count` in the class `supergroup` is known if the supergroup was received from
the methods `searchChatsNearby`, `getInactiveSupergroupChats`, `getSuitableDiscussionChats`, `getGroupsInCommon`, or
`getUserPrivacySettingRules`.
* Updated SQLCipher to 4.4.0.
* Updated dependencies in the prebuilt TDLib for Android:
- Updated SDK to SDK 30.
- Updated NDK to r21d, which dropped support for 32-bit ARM devices without Neon support.
* Updated recommended `emsdk` version for `tdweb` building to the 2.0.6.
* Removed the ability to change the update handler after client creation in native .NET binding, Java example and
prebuilt library for Android.
* Removed the ability to change the default exception handler after client creation in Java example and
prebuilt library for Android.
* Removed the ability to close Client using close() method in Java example and prebuilt library for Android.
Use the method TdApi.close() instead.
* Changed license of source code in prebuilt library for Android to Boost Software License, Version 1.0.
-----------------------------------------------------------------------------------------------------------------------
Changes in 1.6.0 (31 Jan 2020): Changes in 1.6.0 (31 Jan 2020):
* Added support for multiple chat lists. Currently, only two chat lists Main and Archive are supported: * Added support for multiple chat lists. Currently, only two chat lists Main and Archive are supported:

View File

@ -166,6 +166,7 @@ class AesCtrBench : public td::Benchmark {
} }
}; };
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
class AesCtrOpenSSLBench : public td::Benchmark { class AesCtrOpenSSLBench : public td::Benchmark {
public: public:
alignas(64) unsigned char data[DATA_SIZE]; alignas(64) unsigned char data[DATA_SIZE];
@ -200,6 +201,7 @@ class AesCtrOpenSSLBench : public td::Benchmark {
EVP_CIPHER_CTX_free(ctx); EVP_CIPHER_CTX_free(ctx);
} }
}; };
#endif
class AesCbcDecryptBench : public td::Benchmark { class AesCbcDecryptBench : public td::Benchmark {
public: public:
@ -410,7 +412,9 @@ class Crc64Bench : public td::Benchmark {
int main() { int main() {
td::init_openssl_threads(); td::init_openssl_threads();
td::bench(AesCtrBench()); td::bench(AesCtrBench());
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
td::bench(AesCtrOpenSSLBench()); td::bench(AesCtrOpenSSLBench());
#endif
td::bench(AesCbcDecryptBench()); td::bench(AesCbcDecryptBench());
td::bench(AesCbcEncryptBench()); td::bench(AesCbcEncryptBench());

View File

@ -33,6 +33,9 @@ select.large { font-size: large; }
<option>PHP</option> <option>PHP</option>
<option>Lua</option> <option>Lua</option>
<option>Ruby</option> <option>Ruby</option>
<option>Crystal</option>
<option>Haskell</option>
<option>Nim</option>
<option>Clojure</option> <option>Clojure</option>
<option>D</option> <option>D</option>
<option>Elixir</option> <option>Elixir</option>
@ -197,6 +200,9 @@ function getExampleAnchor(language) {
case 'PHP': case 'PHP':
case 'Lua': case 'Lua':
case 'Ruby': case 'Ruby':
case 'Crystal':
case 'Haskell':
case 'Nim':
case 'Clojure': case 'Clojure':
case 'D': case 'D':
case 'Elixir': case 'Elixir':
@ -492,7 +498,7 @@ function onOptionsChanged() {
if (target !== 'C++/CLI' && target !== 'C++/CX') { if (target !== 'C++/CLI' && target !== 'C++/CX') {
pre_text.push('Note that Windows Subsystem for Linux (WSL) and Cygwin are not Windows environments, so you need to use instructions for Linux for them instead.'); pre_text.push('Note that Windows Subsystem for Linux (WSL) and Cygwin are not Windows environments, so you need to use instructions for Linux for them instead.');
} }
pre_text.push('Download and install <a href="https://visualstudio.microsoft.com/ru/vs/community/">Microsoft Visual Studio</a>. Enable C++' + win10_sdk + ' support while installing.'); pre_text.push('Download and install <a href="https://visualstudio.microsoft.com/vs/community/">Microsoft Visual Studio</a>. Enable C++' + win10_sdk + ' support while installing.');
pre_text.push('Download and install <a href="https://cmake.org/download/">CMake</a>; choose "Add CMake to the system PATH" option while installing.'); pre_text.push('Download and install <a href="https://cmake.org/download/">CMake</a>; choose "Add CMake to the system PATH" option while installing.');
pre_text.push('Download and install <a href="https://git-scm.com/download/win">Git</a>.'); pre_text.push('Download and install <a href="https://git-scm.com/download/win">Git</a>.');
pre_text.push('Download and install <a href="https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/">gperf</a>. Add the path to gperf.exe to the PATH environment variable.'); pre_text.push('Download and install <a href="https://sourceforge.net/projects/gnuwin32/files/gperf/3.0.1/">gperf</a>. Add the path to gperf.exe to the PATH environment variable.');
@ -594,10 +600,10 @@ function onOptionsChanged() {
var cmake = 'cmake'; var cmake = 'cmake';
if (os_mac) { if (os_mac) {
commands.push('xcode-select --install'); commands.push('xcode-select --install');
commands.push('/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"'); commands.push('/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"');
commands.push('brew install gperf cmake openssl' + (target === 'JNI' ? ' coreutils' : '')); commands.push('brew install gperf cmake openssl' + (target === 'JNI' ? ' coreutils' : ''));
if (target === 'JNI') { if (target === 'JNI') {
commands.push('brew cask install java'); commands.push('brew install openjdk');
} }
} else if (os_linux && linux_distro !== 'Other') { } else if (os_linux && linux_distro !== 'Other') {
switch (linux_distro) { switch (linux_distro) {

View File

@ -25,6 +25,9 @@ Choose your preferred programming language to see examples of usage and a detail
- [PHP](#php) - [PHP](#php)
- [Lua](#lua) - [Lua](#lua)
- [Ruby](#ruby) - [Ruby](#ruby)
- [Crystal](#crystal)
- [Haskell](#haskell)
- [Nim](#nim)
- [Clojure](#clojure) - [Clojure](#clojure)
- [Emacs Lisp](#emacslisp) - [Emacs Lisp](#emacslisp)
- [D](#d) - [D](#d)
@ -44,8 +47,8 @@ If you use modern Python >= 3.6, take a look at [python-telegram](https://github
The wrapper uses the full power of asyncio, has a good documentation and has several examples. It can be installed through pip or used in a Docker container. The wrapper uses the full power of asyncio, has a good documentation and has several examples. It can be installed through pip or used in a Docker container.
You can also try a fork [python-telegram](https://github.com/iTeam-co/python-telegram) of this library. You can also try a fork [python-telegram](https://github.com/iTeam-co/python-telegram) of this library.
For older Python versions you can use [pytdlib](https://github.com/pytdlib/pytdlib) or [python-tdlib](https://github.com/andrew-ld/python-tdlib). For older Python versions you can use [pytdlib](https://github.com/pytdlib/pytdlib).
These wrappers contain generators for TDLib API classes and basic classes for interaction with TDLib. This wrapper contains generator for TDLib API classes and basic interface for interaction with TDLib.
You can also check out [example/python/tdjson_example.py](https://github.com/tdlib/td/tree/master/example/python/tdjson_example.py) and You can also check out [example/python/tdjson_example.py](https://github.com/tdlib/td/tree/master/example/python/tdjson_example.py) and
[tdlib-python](https://github.com/JunaidBabu/tdlib-python) for some very basic examples of TDLib JSON interface integration with Python. [tdlib-python](https://github.com/JunaidBabu/tdlib-python) for some very basic examples of TDLib JSON interface integration with Python.
@ -62,8 +65,8 @@ Convenient Node.js wrappers already exist for our JSON interface.
For example, take a look at [Airgram](https://github.com/airgram/airgram) modern TDLib framework for TypeScript/JavaScript, or For example, take a look at [Airgram](https://github.com/airgram/airgram) modern TDLib framework for TypeScript/JavaScript, or
at [tdl](https://github.com/Bannerets/tdl), which provides a convenient, fully-asynchronous interface for interaction with TDLib and contains a bunch of examples. at [tdl](https://github.com/Bannerets/tdl), which provides a convenient, fully-asynchronous interface for interaction with TDLib and contains a bunch of examples.
You can also see [tglib](https://github.com/nodegin/tglib), [node-tdlib](https://github.com/wfjsw/node-tdlib), [tdlnode](https://github.com/fonbah/tdlnode), You can also see [TdNode](https://github.com/puppy0cam/TdNode), [tglib](https://github.com/nodegin/tglib), [node-tdlib](https://github.com/wfjsw/node-tdlib), [tdlnode](https://github.com/fonbah/tdlnode),
[Paper Plane](https://github.com/BlackSuited/paper-plane) and [node-tlg](https://github.com/dilongfa/node-tlg) for other examples of TDLib JSON interface integration with Node.js. [Paper Plane](https://github.com/BlackSuited/paper-plane), or [node-tlg](https://github.com/dilongfa/node-tlg) for other examples of TDLib JSON interface integration with Node.js.
TDLib can be used also from NativeScript through the [JSON](https://github.com/tdlib/td#using-json) interface. TDLib can be used also from NativeScript through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [nativescript-tglib](https://github.com/arpit2438735/nativescript-tglib) as an example of a NativeScript library for building Telegram clients. See [nativescript-tglib](https://github.com/arpit2438735/nativescript-tglib) as an example of a NativeScript library for building Telegram clients.
@ -94,20 +97,25 @@ You can also see [JTDLib](https://github.com/ErnyTech/JTDLib) for another exampl
TDLib can be used from the Kotlin/JVM programming language through same way as in [Java](#java). TDLib can be used from the Kotlin/JVM programming language through same way as in [Java](#java).
You can also use [ktd](https://github.com/whyoleg/ktd) library with kotlin specific bindings. You can also use [ktd](https://github.com/whyoleg/ktd) library with Kotlin-specific bindings.
See also [KtLib](https://github.com/nekohasekai/KtLib), which provides an automatically generated classes for TDLib API, and
[td-ktx](https://github.com/tdlibx/td-ktx) - Kotlin coroutines wrapper for TDLib.
<a name="csharp"></a> <a name="csharp"></a>
## Using TDLib in C# projects ## Using TDLib in C# projects
TDLib provides a native [.NET](https://github.com/tdlib/td#using-dotnet) interface through `C++/CLI` and `C++/CX`. TDLib provides a native [.NET](https://github.com/tdlib/td#using-dotnet) interface through `C++/CLI` and `C++/CX`.
See [tdlib-netcore](https://github.com/dantmnf/tdlib-netcore) for a SWIG-like binding with automatically generated classes for TDLib API.
See [example/uwp](https://github.com/tdlib/td/tree/master/example/uwp) for an example of building TDLib SDK for the Universal Windows Platform and an example of its usage from C#. See [example/uwp](https://github.com/tdlib/td/tree/master/example/uwp) for an example of building TDLib SDK for the Universal Windows Platform and an example of its usage from C#.
See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for an example of building TDLib with `C++/CLI` support and an example of TDLib usage from C# on Windows. See [example/csharp](https://github.com/tdlib/td/tree/master/example/csharp) for an example of building TDLib with `C++/CLI` support and an example of TDLib usage from C# on Windows.
If you want to write a cross-platform C# application using .NET Core, see [tdsharp](https://github.com/egramtel/tdsharp). It uses our [JSON](https://github.com/tdlib/td#using-json) interface, If you want to write a cross-platform C# application using .NET Core, see [tdsharp](https://github.com/egramtel/tdsharp). It uses our [JSON](https://github.com/tdlib/td#using-json) interface,
provides an asynchronous interface for interaction with TDLib, automatically generated classes for TDLib API and has some examples. provides an asynchronous interface for interaction with TDLib, automatically generated classes for TDLib API and has some examples.
Also see [Unigram](https://github.com/UnigramDev/Unigram), which is a full-featured client rewritten from scratch in C# using TDLib SDK for Universal Windows Platform in less than 2 months, or Also see [Unigram](https://github.com/UnigramDev/Unigram), which is a full-featured client rewritten from scratch in C# using TDLib SDK for Universal Windows Platform in less than 2 months,
[egram.tel](https://github.com/egramtel/egram.tel) a cross-platform Telegram client written in C#, .NET Core, ReactiveUI and Avalonia. [egram.tel](https://github.com/egramtel/egram.tel) a cross-platform Telegram client written in C#, .NET Core, ReactiveUI and Avalonia, or
[telewear](https://github.com/telewear/telewear) - a Telegram client for Samsung watches.
<a name="cxx"></a> <a name="cxx"></a>
## Using TDLib in C++ projects ## Using TDLib in C++ projects
@ -117,14 +125,15 @@ TDLib has a simple and convenient C++11-interface for sending and receiving requ
See [example/cpp](https://github.com/tdlib/td/tree/master/example/cpp) for an example of TDLib usage from C++. See [example/cpp](https://github.com/tdlib/td/tree/master/example/cpp) for an example of TDLib usage from C++.
[td_example.cpp](https://github.com/tdlib/td/tree/master/example/cpp/td_example.cpp) contains an example of authorization, processing new incoming messages, getting a list of chats and sending a text message. [td_example.cpp](https://github.com/tdlib/td/tree/master/example/cpp/td_example.cpp) contains an example of authorization, processing new incoming messages, getting a list of chats and sending a text message.
See also the source code of [Depecher](https://github.com/blacksailer/depecher) a Telegram app for Sailfish OS, and [TELEports](https://gitlab.com/ubports/apps/teleports) a Qt-client for Ubuntu Touch, both of which are based on TDLib. See also the source code of [Depecher](https://github.com/blacksailer/depecher) a Telegram app for Sailfish OS, [TELEports](https://gitlab.com/ubports/apps/teleports) a Qt-client for Ubuntu Touch, or
[tdlib-purple](https://github.com/ars3niy/tdlib-purple) - Telegram plugin for Pidgin, all of which are based on TDLib.
<a name="swift"></a> <a name="swift"></a>
## Using TDLib in Swift projects ## Using TDLib in Swift projects
TDLib can be used from the Swift programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically. TDLib can be used from the Swift programming language through the [JSON](https://github.com/tdlib/td#using-json) interface and can be linked statically or dynamically.
See [TDLib-iOS](https://github.com/leoMehlig/TDLib-iOS), which provide a convenient TDLib client with automatically generated and fully-documented classes for all TDLib API methods and objects. See [TDLib-iOS](https://github.com/leoMehlig/TDLib-iOS) or [tdlib-swift](https://github.com/modestman/tdlib-swift), which provide convenient TDLib clients with automatically generated and fully-documented classes for all TDLib API methods and objects.
See [example/swift](https://github.com/tdlib/td/tree/master/example/swift) for an example of a macOS Swift application. See [example/swift](https://github.com/tdlib/td/tree/master/example/swift) for an example of a macOS Swift application.
See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, and macOS. See [example/ios](https://github.com/tdlib/td/tree/master/example/ios) for an example of building TDLib for iOS, watchOS, tvOS, and macOS.
@ -152,15 +161,19 @@ TDLib can be used from the Dart programming language through the [JSON](https://
See [dart_tdlib](https://github.com/periodicaidan/dart_tdlib) or [Dart wrapper for TDLib](https://github.com/tdlib/td/pull/708/commits/237060abd4c205768153180e9f814298d1aa9d49) for an example of a TDLib Dart bindings through FFI. See [dart_tdlib](https://github.com/periodicaidan/dart_tdlib) or [Dart wrapper for TDLib](https://github.com/tdlib/td/pull/708/commits/237060abd4c205768153180e9f814298d1aa9d49) for an example of a TDLib Dart bindings through FFI.
See [project.scarlet](https://github.com/aaugmentum/project.scarlet) or [tdlib-dart](https://github.com/triedcatched/tdlib-dart) for an example of using TDLib from Dart. See [project.scarlet](https://github.com/aaugmentum/project.scarlet), [tdlib](https://github.com/i-Naji/tdlib), [tdlib](https://github.com/pqhaz/tdlib),
[tdlib-dart](https://github.com/drewpayment/tdlib-dart), [tdlib-dart](https://github.com/triedcatched/tdlib-dart), or [telegram-service](https://github.com/igorder-dev/telegram-service) for examples of using TDLib from Dart.
<a name="rust"></a> <a name="rust"></a>
## Using TDLib in Rust projects ## Using TDLib in Rust projects
TDLib can be used from the Rust programming language through the [JSON](https://github.com/tdlib/td#using-json) interface. TDLib can be used from the Rust programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [tdlib-rs](https://github.com/agnipau/tdlib-rs], which contains automatically generated classes for all TDLib API methods and objects.
See [rtdlib](https://github.com/fewensa/rtdlib), [tdlib-rs](https://github.com/d653/tdlib-rs), [tdlib-futures](https://github.com/yuri91/tdlib-futures), See [rtdlib](https://github.com/fewensa/rtdlib), [tdlib-rs](https://github.com/d653/tdlib-rs), [tdlib-futures](https://github.com/yuri91/tdlib-futures),
[rust-tdlib](https://github.com/lattenwald/rust-tdlib) or [tdjson-rs](https://github.com/mersinvald/tdjson-rs) for an example of TDLib Rust bindings. [tdlib-sys](https://github.com/nuxeh/tdlib-sys), [rust-tdlib](https://github.com/lattenwald/rust-tdlib), or
[tdjson-rs](https://github.com/mersinvald/tdjson-rs) for examples of TDLib Rust bindings.
<a name="erlang"></a> <a name="erlang"></a>
## Using TDLib in Erlang projects ## Using TDLib in Erlang projects
@ -178,7 +191,8 @@ See also [tdlib-schema](https://github.com/aurimasniekis/php-tdlib-schema) - a g
For older PHP versions you can use TDLib by wrapping its functionality in a PHP extension. For older PHP versions you can use TDLib by wrapping its functionality in a PHP extension.
See [phptdlib](https://github.com/yaroslavche/phptdlib), [tdlib](https://github.com/aurimasniekis/php-ext-tdlib) or [PIF-TDPony](https://github.com/danog/pif-tdpony) for examples of such extensions which provide access to TDLib from PHP. See [phptdlib](https://github.com/yaroslavche/phptdlib), [tdlib](https://github.com/aurimasniekis/php-ext-tdlib), or [PIF-TDPony](https://github.com/danog/pif-tdpony)
for examples of such extensions which provide access to TDLib from PHP.
See [tdlib-bundle](https://github.com/yaroslavche/tdlib-bundle) a Symfony bundle based on [phptdlib](https://github.com/yaroslavche/phptdlib). See [tdlib-bundle](https://github.com/yaroslavche/tdlib-bundle) a Symfony bundle based on [phptdlib](https://github.com/yaroslavche/phptdlib).
@ -187,7 +201,8 @@ See [tdlib-bundle](https://github.com/yaroslavche/tdlib-bundle) a Symfony bu
TDLib can be used from the Lua programming language through the [JSON](https://github.com/tdlib/td#using-json) interface. TDLib can be used from the Lua programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [tdlua](https://github.com/giuseppeM99/tdlua) for examples of TDLib Lua bindings and basic usage examples. See [luajit-tdlib](https://github.com/Rami-Sabbagh/luajit-tdlib), [tdlua](https://github.com/giuseppeM99/tdlua), [Luagram](https://github.com/Luagram/LuagramProject), or
[luajit-tdlib](https://github.com/Playermet/luajit-tdlib) for examples of TDLib Lua bindings and basic usage examples.
See also [tdbot](https://github.com/vysheng/tdbot), which makes all TDLib features available from Lua scripts. See also [tdbot](https://github.com/vysheng/tdbot), which makes all TDLib features available from Lua scripts.
@ -205,12 +220,34 @@ TDLib can be used from the Ruby programming language through the [JSON](https://
See [tdlib-ruby](https://github.com/centosadmin/tdlib-ruby) for examples of Ruby bindings and a client for TDLib. See [tdlib-ruby](https://github.com/centosadmin/tdlib-ruby) for examples of Ruby bindings and a client for TDLib.
<a name="Crystal"></a>
## Using TDLib in Crystal projects
TDLib can be used from the Crystal programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [Proton](https://github.com/protoncr/proton) for examples of Crystal bindings with automatically generated types for all TDLib API methods and objects.
<a name="haskell"></a>
## Using TDLib in Haskell projects
TDLib can be used from the Haskell programming language.
See [haskell-tdlib](https://github.com/mejgun/haskell-tdlib) or [tdlib](https://github.com/poscat0x04/tdlib) for examples of such usage and Haskell wrappers for TDLib.
This library contains automatically generated Haskell types for all TDLib API methods and objects.
<a name="nim"></a>
## Using TDLib in Nim projects
TDLib can be used from the Nim programming language.
See [telenim](https://github.com/Yardanico/telenim) for example of such usage and a Nim wrapper for TDLib.
<a name="clojure"></a> <a name="clojure"></a>
## Using TDLib in Clojure projects ## Using TDLib in Clojure projects
TDLib can be used from the Clojure programming language through the [JSON](https://github.com/tdlib/td#using-json) interface. TDLib can be used from the Clojure programming language through the [JSON](https://github.com/tdlib/td#using-json) interface.
See [clojure-tdlib-json-wrapper](https://github.com/MityaSaray/clojure-tdlib-json-wrapper) for an example of TDLib Clojure bindings. See [clojure-tdlib-json-wrapper](https://github.com/MityaSaray/clojure-tdlib-json) for an example of TDLib Clojure bindings.
<a name="emacslisp"></a> <a name="emacslisp"></a>
## Using TDLib in Emacs Lisp projects ## Using TDLib in Emacs Lisp projects
@ -225,7 +262,7 @@ See [telega.el](https://github.com/zevlg/telega.el) for an example of a GNU Emac
TDLib can be used from the Elixir programming language. TDLib can be used from the Elixir programming language.
See [Elixir TDLib](https://gitlab.com/Fnux/elixir-tdlib) or [Elixir TDLib](https://github.com/QuantLayer/elixir-tdlib) for examples of such usage and an Elixir client for TDLib. See [Elixir TDLib](https://gitlab.com/Fnux/elixir-tdlib) or [Elixir TDLib](https://github.com/QuantLayer/elixir-tdlib) for examples of such usage and an Elixir client for TDLib.
This library contains automatically generated and fully-documented classes for all TDLib API methods and objects. These libraries contain automatically generated and fully-documented classes for all TDLib API methods and objects.
<a name="1s"></a> <a name="1s"></a>
## Using TDLib from 1С:Enterprise ## Using TDLib from 1С:Enterprise

View File

@ -9,7 +9,7 @@ import Foundation
// TDLib Client Swift binding // TDLib Client Swift binding
class TdClient { class TdClient {
var client_id = td_create_client_id()! var client_id = td_create_client_id()
let tdlibMainLoop = DispatchQueue(label: "TDLib") let tdlibMainLoop = DispatchQueue(label: "TDLib")
let tdlibQueryQueue = DispatchQueue(label: "TDLibQuery") let tdlibQueryQueue = DispatchQueue(label: "TDLibQuery")
var queryF = Dictionary<Int64, (Dictionary<String,Any>)->()>() var queryF = Dictionary<Int64, (Dictionary<String,Any>)->()>()

View File

@ -124,7 +124,7 @@ export async function instantiateAny(version, url, importObject) {
try { try {
return await instantiateStreaming(url, importObject); return await instantiateStreaming(url, importObject);
} catch (e) { } catch (e) {
console.log("instantiateSteaming failed", e); console.log("instantiateStreaming failed", e);
} }
try { try {
return await instantiateCachedURL(version, url, importObject); return await instantiateCachedURL(version, url, importObject);

5093
sqlite/sqlite/sqlite3.c vendored

File diff suppressed because it is too large Load Diff

View File

@ -662,7 +662,7 @@ messageForwardOriginChannel chat_id:int53 message_id:int53 author_signature:stri
//@from_message_id For messages forwarded to the chat with the current user (Saved Messages), to the Replies bot chat, or to the channel's discussion group, the identifier of the original message from which the new message was forwarded last time; 0 if unknown //@from_message_id For messages forwarded to the chat with the current user (Saved Messages), to the Replies bot chat, or to the channel's discussion group, the identifier of the original message from which the new message was forwarded last time; 0 if unknown
messageForwardInfo origin:MessageForwardOrigin date:int32 public_service_announcement_type:string from_chat_id:int53 from_message_id:int53 = MessageForwardInfo; messageForwardInfo origin:MessageForwardOrigin date:int32 public_service_announcement_type:string from_chat_id:int53 from_message_id:int53 = MessageForwardInfo;
//@description Contains information about message replies //@description Contains information about replies to a message
//@reply_count Number of times the message was directly or indirectly replied //@reply_count Number of times the message was directly or indirectly replied
//@recent_repliers Recent repliers to the message; available in channels with a discussion supergroup //@recent_repliers Recent repliers to the message; available in channels with a discussion supergroup
//@last_read_inbox_message_id Identifier of the last read incoming reply to the message //@last_read_inbox_message_id Identifier of the last read incoming reply to the message
@ -713,7 +713,7 @@ messageSendingStateFailed error_code:int32 error_message:string can_retry:Bool r
//@ttl For self-destructing messages, the message's TTL (Time To Live), in seconds; 0 if none. TDLib will send updateDeleteMessages or updateMessageContent once the TTL expires //@ttl For self-destructing messages, the message's TTL (Time To Live), in seconds; 0 if none. TDLib will send updateDeleteMessages or updateMessageContent once the TTL expires
//@ttl_expires_in Time left before the message expires, in seconds //@ttl_expires_in Time left before the message expires, in seconds
//@via_bot_user_id If non-zero, the user identifier of the bot through which this message was sent //@via_bot_user_id If non-zero, the user identifier of the bot through which this message was sent
//@author_signature For channel posts and anonymous administrator messages, optional author signature //@author_signature For channel posts and anonymous group messages, optional author signature
//@media_album_id Unique identifier of an album this message belongs to. Only photos and videos can be grouped together in albums //@media_album_id Unique identifier of an album this message belongs to. Only photos and videos can be grouped together in albums
//@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted //@restriction_reason If non-empty, contains a human-readable description of the reason why access to this message must be restricted
//@content Content of the message //@content Content of the message
@ -882,7 +882,7 @@ chatInviteLink invite_link:string = ChatInviteLink;
//@description Contains information about a chat invite link //@description Contains information about a chat invite link
//@chat_id Chat identifier of the invite link; 0 if the user has no access to the chat before joining //@chat_id Chat identifier of the invite link; 0 if the user has no access to the chat before joining
//@accessible_for If non-zero, the remaining time for which read access is granted to the chat, in seconds //@accessible_for If non-zero, the amount of time for which read access to the chat will remain available, in seconds
//@type Contains information about the type of the chat //@type Contains information about the type of the chat
//@title Title of the chat //@title Title of the chat
//@photo Chat photo; may be null //@photo Chat photo; may be null
@ -3065,20 +3065,20 @@ inputStickerAnimated sticker:InputFile emojis:string = InputSticker;
dateRange start_date:int32 end_date:int32 = DateRange; dateRange start_date:int32 end_date:int32 = DateRange;
//@description A statistics value @value The value @previous_value The value for the previous day @growth_rate_percentage The growth rate of the value, as a percentage //@description A value with information about its recent changes @value The current value @previous_value The value for the previous day @growth_rate_percentage The growth rate of the value, as a percentage
statisticsValue value:double previous_value:double growth_rate_percentage:double = StatisticsValue; statisticalValue value:double previous_value:double growth_rate_percentage:double = StatisticalValue;
//@class StatisticsGraph @description Describes a statistics graph //@class StatisticalGraph @description Describes a statistical graph
//@description A graph data @json_data Graph data in JSON format @zoom_token If non-empty, a token which can be used to receive a zoomed in graph //@description A graph data @json_data Graph data in JSON format @zoom_token If non-empty, a token which can be used to receive a zoomed in graph
statisticsGraphData json_data:string zoom_token:string = StatisticsGraph; statisticalGraphData json_data:string zoom_token:string = StatisticalGraph;
//@description The graph data to be asynchronously loaded through getStatisticsGraph @token The token to use for data loading //@description The graph data to be asynchronously loaded through getStatisticalGraph @token The token to use for data loading
statisticsGraphAsync token:string = StatisticsGraph; statisticalGraphAsync token:string = StatisticalGraph;
//@description An error message to be shown to the user instead of the graph @error_message The error message //@description An error message to be shown to the user instead of the graph @error_message The error message
statisticsGraphError error_message:string = StatisticsGraph; statisticalGraphError error_message:string = StatisticalGraph;
//@description Contains statistics about interactions with a message //@description Contains statistics about interactions with a message
@ -3125,7 +3125,7 @@ chatStatisticsInviterInfo user_id:int32 added_member_count:int32 = ChatStatistic
//@top_senders List of users sent most messages in the last week //@top_senders List of users sent most messages in the last week
//@top_administrators List of most active administrators in the last week //@top_administrators List of most active administrators in the last week
//@top_inviters List of most active inviters of new members in the last week //@top_inviters List of most active inviters of new members in the last week
chatStatisticsSupergroup period:dateRange member_count:statisticsValue message_count:statisticsValue viewer_count:statisticsValue sender_count:statisticsValue member_count_graph:StatisticsGraph join_graph:StatisticsGraph join_by_source_graph:StatisticsGraph language_graph:StatisticsGraph message_content_graph:StatisticsGraph action_graph:StatisticsGraph day_graph:StatisticsGraph week_graph:StatisticsGraph top_senders:vector<chatStatisticsMessageSenderInfo> top_administrators:vector<chatStatisticsAdministratorActionsInfo> top_inviters:vector<chatStatisticsInviterInfo> = ChatStatistics; chatStatisticsSupergroup period:dateRange member_count:statisticalValue message_count:statisticalValue viewer_count:statisticalValue sender_count:statisticalValue member_count_graph:StatisticalGraph join_graph:StatisticalGraph join_by_source_graph:StatisticalGraph language_graph:StatisticalGraph message_content_graph:StatisticalGraph action_graph:StatisticalGraph day_graph:StatisticalGraph week_graph:StatisticalGraph top_senders:vector<chatStatisticsMessageSenderInfo> top_administrators:vector<chatStatisticsAdministratorActionsInfo> top_inviters:vector<chatStatisticsInviterInfo> = ChatStatistics;
//@description A detailed statistics about a channel chat //@description A detailed statistics about a channel chat
//@period A period to which the statistics applies //@period A period to which the statistics applies
@ -3143,11 +3143,11 @@ chatStatisticsSupergroup period:dateRange member_count:statisticsValue message_c
//@message_interaction_graph A graph containing number of chat message views and shares //@message_interaction_graph A graph containing number of chat message views and shares
//@instant_view_interaction_graph A graph containing number of views of associated with the chat instant views //@instant_view_interaction_graph A graph containing number of views of associated with the chat instant views
//@recent_message_interactions Detailed statistics about number of views and shares of recently sent messages //@recent_message_interactions Detailed statistics about number of views and shares of recently sent messages
chatStatisticsChannel period:dateRange member_count:statisticsValue mean_view_count:statisticsValue mean_share_count:statisticsValue enabled_notifications_percentage:double member_count_graph:StatisticsGraph join_graph:StatisticsGraph mute_graph:StatisticsGraph view_count_by_hour_graph:StatisticsGraph view_count_by_source_graph:StatisticsGraph join_by_source_graph:StatisticsGraph language_graph:StatisticsGraph message_interaction_graph:StatisticsGraph instant_view_interaction_graph:StatisticsGraph recent_message_interactions:vector<chatStatisticsMessageInteractionInfo> = ChatStatistics; chatStatisticsChannel period:dateRange member_count:statisticalValue mean_view_count:statisticalValue mean_share_count:statisticalValue enabled_notifications_percentage:double member_count_graph:StatisticalGraph join_graph:StatisticalGraph mute_graph:StatisticalGraph view_count_by_hour_graph:StatisticalGraph view_count_by_source_graph:StatisticalGraph join_by_source_graph:StatisticalGraph language_graph:StatisticalGraph message_interaction_graph:StatisticalGraph instant_view_interaction_graph:StatisticalGraph recent_message_interactions:vector<chatStatisticsMessageInteractionInfo> = ChatStatistics;
//@description A detailed statistics about a message @message_interaction_graph A graph containing number of message views and shares //@description A detailed statistics about a message @message_interaction_graph A graph containing number of message views and shares
messageStatistics message_interaction_graph:StatisticsGraph = MessageStatistics; messageStatistics message_interaction_graph:StatisticalGraph = MessageStatistics;
//@class Update @description Contains notifications about data changes //@class Update @description Contains notifications about data changes
@ -3602,7 +3602,7 @@ getMessageLocally chat_id:int53 message_id:int53 = Message;
//@chat_id Identifier of the chat the message belongs to @message_id Identifier of the message reply to which to get //@chat_id Identifier of the chat the message belongs to @message_id Identifier of the message reply to which to get
getRepliedMessage chat_id:int53 message_id:int53 = Message; getRepliedMessage chat_id:int53 message_id:int53 = Message;
//@description Returns information about a newest pinned chat message @chat_id Identifier of the chat the message belongs to //@description Returns information about a newest pinned message in the chat @chat_id Identifier of the chat the message belongs to
getChatPinnedMessage chat_id:int53 = Message; getChatPinnedMessage chat_id:int53 = Message;
//@description Returns information about a message with the callback button that originated a callback query; for bots only @chat_id Identifier of the chat the message belongs to @message_id Message identifier @callback_query_id Identifier of the callback query //@description Returns information about a message with the callback button that originated a callback query; for bots only @chat_id Identifier of the chat the message belongs to @message_id Message identifier @callback_query_id Identifier of the callback query
@ -4126,7 +4126,7 @@ setChatSlowModeDelay chat_id:int53 slow_mode_delay:int32 = Ok;
//@chat_id Identifier of the chat //@chat_id Identifier of the chat
//@message_id Identifier of the new pinned message //@message_id Identifier of the new pinned message
//@disable_notification True, if there should be no notification about the pinned message. Notifications are always disabled in channels and private chats //@disable_notification True, if there should be no notification about the pinned message. Notifications are always disabled in channels and private chats
//@only_for_self True, if the message needs to be pinned only for self; private chats only //@only_for_self True, if the message needs to be pinned for one side only; private chats only
pinChatMessage chat_id:int53 message_id:int53 disable_notification:Bool only_for_self:Bool = Ok; pinChatMessage chat_id:int53 message_id:int53 disable_notification:Bool only_for_self:Bool = Ok;
//@description Removes a pinned message from a chat; requires can_pin_messages rights in the group or can_edit_messages rights in the channel @chat_id Identifier of the chat @message_id Identifier of the removed pinned message //@description Removes a pinned message from a chat; requires can_pin_messages rights in the group or can_edit_messages rights in the channel @chat_id Identifier of the chat @message_id Identifier of the removed pinned message
@ -4644,8 +4644,8 @@ getChatStatistics chat_id:int53 is_dark:Bool = ChatStatistics;
//@description Returns detailed statistics about a message. Can be used only if Message.can_get_statistics == true @chat_id Chat identifier @message_id Message identifier @is_dark Pass true if a dark theme is used by the application //@description Returns detailed statistics about a message. Can be used only if Message.can_get_statistics == true @chat_id Chat identifier @message_id Message identifier @is_dark Pass true if a dark theme is used by the application
getMessageStatistics chat_id:int53 message_id:int53 is_dark:Bool = MessageStatistics; getMessageStatistics chat_id:int53 message_id:int53 is_dark:Bool = MessageStatistics;
//@description Loads asynchronous or zoomed in chat or message statistics graph @chat_id Chat identifier @token The token for graph loading @x X-value for zoomed in graph or 0 otherwise //@description Loads an asynchronous or a zoomed in statistical graph @chat_id Chat identifier @token The token for graph loading @x X-value for zoomed in graph or 0 otherwise
getStatisticsGraph chat_id:int53 token:string x:int53 = StatisticsGraph; getStatisticalGraph chat_id:int53 token:string x:int53 = StatisticalGraph;
//@description Returns storage usage statistics. Can be called before authorization @chat_limit The maximum number of chats with the largest storage usage for which separate statistics should be returned. All other chats will be grouped in entries with chat_id == 0. If the chat info database is not used, the chat_limit is ignored and is always set to 0 //@description Returns storage usage statistics. Can be called before authorization @chat_limit The maximum number of chats with the largest storage usage for which separate statistics should be returned. All other chats will be grouped in entries with chat_id == 0. If the chat info database is not used, the chat_limit is ignored and is always set to 0
@ -4673,7 +4673,7 @@ optimizeMemory full:Bool = Ok;
//@file_types If not empty, only files with the given type(s) are considered. By default, all types except thumbnails, profile photos, stickers and wallpapers are deleted //@file_types If not empty, only files with the given type(s) are considered. By default, all types except thumbnails, profile photos, stickers and wallpapers are deleted
//@chat_ids If not empty, only files from the given chats are considered. Use 0 as chat identifier to delete files not belonging to any chat (e.g., profile photos) //@chat_ids If not empty, only files from the given chats are considered. Use 0 as chat identifier to delete files not belonging to any chat (e.g., profile photos)
//@exclude_chat_ids If not empty, files from the given chats are excluded. Use 0 as chat identifier to exclude all files not belonging to any chat (e.g., profile photos) //@exclude_chat_ids If not empty, files from the given chats are excluded. Use 0 as chat identifier to exclude all files not belonging to any chat (e.g., profile photos)
//@return_deleted_file_statistics Pass true if deleted file statistics must be returned instead of the whole storage usage statistics. Affects only returned statistics //@return_deleted_file_statistics Pass true if statistics about the files that were deleted must be returned instead of the whole storage usage statistics. Affects only returned statistics
//@chat_limit Same as in getStorageStatistics. Affects only returned statistics //@chat_limit Same as in getStorageStatistics. Affects only returned statistics
optimizeStorage size:int53 ttl:int32 count:int32 immunity_delay:int32 file_types:vector<FileType> chat_ids:vector<int53> exclude_chat_ids:vector<int53> return_deleted_file_statistics:Bool chat_limit:int32 = StorageStatistics; optimizeStorage size:int53 ttl:int32 count:int32 immunity_delay:int32 file_types:vector<FileType> chat_ids:vector<int53> exclude_chat_ids:vector<int53> return_deleted_file_statistics:Bool chat_limit:int32 = StorageStatistics;

View File

@ -140,6 +140,7 @@ AnimationsManager::AnimationsManager(Td *td, ActorShared<> parent) : td_(td), pa
LOG(ERROR) << "Wrong saved animations limit = \"" << limit_string << "\" stored in database"; LOG(ERROR) << "Wrong saved animations limit = \"" << limit_string << "\" stored in database";
} }
} }
next_saved_animations_load_time_ = Time::now();
} }
void AnimationsManager::tear_down() { void AnimationsManager::tear_down() {
@ -540,10 +541,10 @@ void AnimationsManager::reload_saved_animations(bool force) {
return; return;
} }
if (!td_->auth_manager_->is_bot() && next_saved_animations_load_time_ >= 0 && if (!td_->auth_manager_->is_bot() && !are_saved_animations_being_loaded_ &&
(next_saved_animations_load_time_ < Time::now() || force)) { (next_saved_animations_load_time_ < Time::now() || force)) {
LOG_IF(INFO, force) << "Reload saved animations"; LOG_IF(INFO, force) << "Reload saved animations";
next_saved_animations_load_time_ = -1; are_saved_animations_being_loaded_ = true;
td_->create_handler<GetSavedGifsQuery>()->send(false, get_saved_animations_hash("reload_saved_animations")); td_->create_handler<GetSavedGifsQuery>()->send(false, get_saved_animations_hash("reload_saved_animations"));
} }
} }
@ -630,6 +631,7 @@ void AnimationsManager::on_get_saved_animations(
bool is_repair, tl_object_ptr<telegram_api::messages_SavedGifs> &&saved_animations_ptr) { bool is_repair, tl_object_ptr<telegram_api::messages_SavedGifs> &&saved_animations_ptr) {
CHECK(!td_->auth_manager_->is_bot()); CHECK(!td_->auth_manager_->is_bot());
if (!is_repair) { if (!is_repair) {
are_saved_animations_being_loaded_ = false;
next_saved_animations_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60); next_saved_animations_load_time_ = Time::now_cached() + Random::fast(30 * 60, 50 * 60);
} }
@ -684,6 +686,7 @@ void AnimationsManager::on_get_saved_animations(
void AnimationsManager::on_get_saved_animations_failed(bool is_repair, Status error) { void AnimationsManager::on_get_saved_animations_failed(bool is_repair, Status error) {
CHECK(error.is_error()); CHECK(error.is_error());
if (!is_repair) { if (!is_repair) {
are_saved_animations_being_loaded_ = false;
next_saved_animations_load_time_ = Time::now_cached() + Random::fast(5, 10); next_saved_animations_load_time_ = Time::now_cached() + Random::fast(5, 10);
} }
auto &queries = is_repair ? repair_saved_animations_queries_ : load_saved_animations_queries_; auto &queries = is_repair ? repair_saved_animations_queries_ : load_saved_animations_queries_;

View File

@ -155,6 +155,7 @@ class AnimationsManager : public Actor {
vector<FileId> saved_animation_ids_; vector<FileId> saved_animation_ids_;
vector<FileId> saved_animation_file_ids_; vector<FileId> saved_animation_file_ids_;
double next_saved_animations_load_time_ = 0; double next_saved_animations_load_time_ = 0;
bool are_saved_animations_being_loaded_ = false;
bool are_saved_animations_loaded_ = false; bool are_saved_animations_loaded_ = false;
vector<Promise<Unit>> load_saved_animations_queries_; vector<Promise<Unit>> load_saved_animations_queries_;
vector<Promise<Unit>> repair_saved_animations_queries_; vector<Promise<Unit>> repair_saved_animations_queries_;

View File

@ -23,10 +23,12 @@
#include <algorithm> #include <algorithm>
#include <atomic> #include <atomic>
#include <limits>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <queue> #include <queue>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
namespace td { namespace td {

View File

@ -25,7 +25,7 @@ namespace td {
* this function must not be called simultaneously from two different threads. Also note that all updates and * this function must not be called simultaneously from two different threads. Also note that all updates and
* responses to requests should be applied in the same order as they were received, to ensure consistency. * responses to requests should be applied in the same order as they were received, to ensure consistency.
* Given this information, it's advisable to call this function from a dedicated thread. * Given this information, it's advisable to call this function from a dedicated thread.
* Some service TDLib requests can be executed synchronously from any thread by using the Client::execute method. * Some service TDLib requests can be executed synchronously from any thread using the Client::execute method.
* *
* General pattern of usage: * General pattern of usage:
* \code * \code
@ -134,13 +134,13 @@ class Client final {
/** /**
* The future native C++ interface for interaction with TDLib. * The future native C++ interface for interaction with TDLib.
* *
* The TDLib client instance is created using the ClientManager::create_client_id method, returning a client identifier. * A TDLib client instance can be created through the method ClientManager::create_client_id.
* Requests to a TDLib client instance can be sent using the ClientManager::send method from any thread. * Requests can be sent using the method ClientManager::send from any thread.
* New updates and responses to requests can be received using the ClientManager::receive method from any thread * New updates and responses to requests can be received using the method ClientManager::receive from any thread after
* after a first request is sent to the client instance. ClientManager::receive must not be called simultaneously from * the first request has been sent to the client instance. ClientManager::receive must not be called simultaneously from
* two different threads. Also note that all updates and responses to requests should be applied in the same order as * two different threads. Also note that all updates and responses to requests should be applied in the same order as
* they were received, to ensure consistency. * they were received, to ensure consistency.
* Some TDLib requests can be executed synchronously from any thread by using the ClientManager::execute method. * Some TDLib requests can be executed synchronously from any thread using the method ClientManager::execute.
* *
* General pattern of usage: * General pattern of usage:
* \code * \code
@ -159,8 +159,8 @@ class Client final {
* continue; * continue;
* } * }
* *
* if (response.id == 0) { * if (response.request_id == 0) {
* // process response.object as an incoming update of type td_api::Update for the client response.client_id * // process response.object as an incoming update of the type td_api::Update for the client response.client_id
* } else { * } else {
* // process response.object as an answer to a request response.request_id for the client response.client_id * // process response.object as an answer to a request response.request_id for the client response.client_id
* } * }
@ -182,14 +182,14 @@ class ClientManager final {
/** /**
* Request identifier. * Request identifier.
* Responses to TDLib requests will have the same request id as the corresponding request. * Responses to TDLib requests will have the same request id as the corresponding request.
* Updates from TDLib will have request id == 0, incoming requests are thus disallowed to have request id == 0. * Updates from TDLib will have the request_id == 0, incoming requests are thus not allowed to have request_id == 0.
*/ */
using RequestId = std::uint64_t; using RequestId = std::uint64_t;
/** /**
* Returns an opaque identifier of a new TDLib instance. * Returns an opaque identifier of a new TDLib instance.
* The TDLib instance will not send updates until the first request is sent to it. * The TDLib instance will not send updates until the first request is sent to it.
* \return Opaque indentifier of a new TDLib instance. * \return Opaque identifier of a new TDLib instance.
*/ */
ClientId create_client_id(); ClientId create_client_id();
@ -206,7 +206,7 @@ class ClientManager final {
*/ */
struct Response { struct Response {
/** /**
* TDLib client instance identifier, for which the response is received. * TDLib client instance identifier, for which the response was received.
*/ */
ClientId client_id; ClientId client_id;
@ -222,24 +222,24 @@ class ClientManager final {
}; };
/** /**
* Receives incoming updates and request responses from TDLib. May be called from any thread, but must not be * Receives incoming updates and responses to requests from TDLib. May be called from any thread, but must not be
* called simultaneously from two different threads. * called simultaneously from two different threads.
* \param[in] timeout The maximum number of seconds allowed for this function to wait for new data. * \param[in] timeout The maximum number of seconds allowed for this function to wait for new data.
* \return An incoming update or request response. The object returned in the response may be a nullptr * \return An incoming update or response to a request. The object returned in the response may be a nullptr
* if the timeout expires. * if the timeout expires.
*/ */
Response receive(double timeout); Response receive(double timeout);
/** /**
* Synchronously executes TDLib requests. Only a few requests can be executed synchronously. * Synchronously executes a TDLib request.
* May be called from any thread. * A request can be executed synchronously, only if it is documented with "Can be called synchronously".
* \param[in] request Request to the TDLib. * \param[in] request Request to the TDLib.
* \return The request response. * \return The request response.
*/ */
static td_api::object_ptr<td_api::Object> execute(td_api::object_ptr<td_api::Function> &&request); static td_api::object_ptr<td_api::Object> execute(td_api::object_ptr<td_api::Function> &&request);
/** /**
* Destroys the client manager and all TDLib client instance managed by it. * Destroys the client manager and all TDLib client instances managed by it.
*/ */
~ClientManager(); ~ClientManager();

View File

@ -2604,22 +2604,23 @@ tl_object_ptr<td_api::dateRange> ContactsManager::convert_date_range(
return make_tl_object<td_api::dateRange>(obj->min_date_, obj->max_date_); return make_tl_object<td_api::dateRange>(obj->min_date_, obj->max_date_);
} }
tl_object_ptr<td_api::StatisticsGraph> ContactsManager::convert_stats_graph( tl_object_ptr<td_api::StatisticalGraph> ContactsManager::convert_stats_graph(
tl_object_ptr<telegram_api::StatsGraph> obj) { tl_object_ptr<telegram_api::StatsGraph> obj) {
CHECK(obj != nullptr); CHECK(obj != nullptr);
switch (obj->get_id()) { switch (obj->get_id()) {
case telegram_api::statsGraphAsync::ID: { case telegram_api::statsGraphAsync::ID: {
auto graph = move_tl_object_as<telegram_api::statsGraphAsync>(obj); auto graph = move_tl_object_as<telegram_api::statsGraphAsync>(obj);
return make_tl_object<td_api::statisticsGraphAsync>(std::move(graph->token_)); return make_tl_object<td_api::statisticalGraphAsync>(std::move(graph->token_));
} }
case telegram_api::statsGraphError::ID: { case telegram_api::statsGraphError::ID: {
auto graph = move_tl_object_as<telegram_api::statsGraphError>(obj); auto graph = move_tl_object_as<telegram_api::statsGraphError>(obj);
return make_tl_object<td_api::statisticsGraphError>(std::move(graph->error_)); return make_tl_object<td_api::statisticalGraphError>(std::move(graph->error_));
} }
case telegram_api::statsGraph::ID: { case telegram_api::statsGraph::ID: {
auto graph = move_tl_object_as<telegram_api::statsGraph>(obj); auto graph = move_tl_object_as<telegram_api::statsGraph>(obj);
return make_tl_object<td_api::statisticsGraphData>(std::move(graph->json_->data_), std::move(graph->zoom_token_)); return make_tl_object<td_api::statisticalGraphData>(std::move(graph->json_->data_),
std::move(graph->zoom_token_));
} }
default: default:
UNREACHABLE(); UNREACHABLE();
@ -2640,10 +2641,10 @@ double ContactsManager::get_percentage_value(double part, double total) {
return part / total * 100; return part / total * 100;
} }
tl_object_ptr<td_api::statisticsValue> ContactsManager::convert_stats_absolute_value( tl_object_ptr<td_api::statisticalValue> ContactsManager::convert_stats_absolute_value(
const tl_object_ptr<telegram_api::statsAbsValueAndPrev> &obj) { const tl_object_ptr<telegram_api::statsAbsValueAndPrev> &obj) {
return make_tl_object<td_api::statisticsValue>(obj->current_, obj->previous_, return make_tl_object<td_api::statisticalValue>(obj->current_, obj->previous_,
get_percentage_value(obj->current_ - obj->previous_, obj->previous_)); get_percentage_value(obj->current_ - obj->previous_, obj->previous_));
} }
tl_object_ptr<td_api::chatStatisticsSupergroup> ContactsManager::convert_megagroup_stats( tl_object_ptr<td_api::chatStatisticsSupergroup> ContactsManager::convert_megagroup_stats(
@ -2838,10 +2839,10 @@ class GetMessageStatsQuery : public Td::ResultHandler {
}; };
class LoadAsyncGraphQuery : public Td::ResultHandler { class LoadAsyncGraphQuery : public Td::ResultHandler {
Promise<td_api::object_ptr<td_api::StatisticsGraph>> promise_; Promise<td_api::object_ptr<td_api::StatisticalGraph>> promise_;
public: public:
explicit LoadAsyncGraphQuery(Promise<td_api::object_ptr<td_api::StatisticsGraph>> &&promise) explicit LoadAsyncGraphQuery(Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise)
: promise_(std::move(promise)) { : promise_(std::move(promise)) {
} }
@ -6035,7 +6036,7 @@ void ContactsManager::send_get_channel_message_stats_query(
} }
void ContactsManager::load_statistics_graph(DialogId dialog_id, const string &token, int64 x, void ContactsManager::load_statistics_graph(DialogId dialog_id, const string &token, int64 x,
Promise<td_api::object_ptr<td_api::StatisticsGraph>> &&promise) { Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise) {
auto dc_id_promise = PromiseCreator::lambda( auto dc_id_promise = PromiseCreator::lambda(
[actor_id = actor_id(this), token, x, promise = std::move(promise)](Result<DcId> r_dc_id) mutable { [actor_id = actor_id(this), token, x, promise = std::move(promise)](Result<DcId> r_dc_id) mutable {
if (r_dc_id.is_error()) { if (r_dc_id.is_error()) {
@ -6048,7 +6049,7 @@ void ContactsManager::load_statistics_graph(DialogId dialog_id, const string &to
} }
void ContactsManager::send_load_async_graph_query(DcId dc_id, string token, int64 x, void ContactsManager::send_load_async_graph_query(DcId dc_id, string token, int64 x,
Promise<td_api::object_ptr<td_api::StatisticsGraph>> &&promise) { Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise) {
if (G()->close_flag()) { if (G()->close_flag()) {
return promise.set_error(Status::Error(500, "Request aborted")); return promise.set_error(Status::Error(500, "Request aborted"));
} }

View File

@ -381,7 +381,7 @@ class ContactsManager : public Actor {
Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise); Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise);
void load_statistics_graph(DialogId dialog_id, const string &token, int64 x, void load_statistics_graph(DialogId dialog_id, const string &token, int64 x,
Promise<td_api::object_ptr<td_api::StatisticsGraph>> &&promise); Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise);
void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, Promise<Unit> &&promise); void add_chat_participant(ChatId chat_id, UserId user_id, int32 forward_limit, Promise<Unit> &&promise);
@ -569,11 +569,11 @@ class ContactsManager : public Actor {
static tl_object_ptr<td_api::dateRange> convert_date_range( static tl_object_ptr<td_api::dateRange> convert_date_range(
const tl_object_ptr<telegram_api::statsDateRangeDays> &obj); const tl_object_ptr<telegram_api::statsDateRangeDays> &obj);
static tl_object_ptr<td_api::StatisticsGraph> convert_stats_graph(tl_object_ptr<telegram_api::StatsGraph> obj); static tl_object_ptr<td_api::StatisticalGraph> convert_stats_graph(tl_object_ptr<telegram_api::StatsGraph> obj);
static double get_percentage_value(double new_value, double old_value); static double get_percentage_value(double new_value, double old_value);
static tl_object_ptr<td_api::statisticsValue> convert_stats_absolute_value( static tl_object_ptr<td_api::statisticalValue> convert_stats_absolute_value(
const tl_object_ptr<telegram_api::statsAbsValueAndPrev> &obj); const tl_object_ptr<telegram_api::statsAbsValueAndPrev> &obj);
tl_object_ptr<td_api::chatStatisticsSupergroup> convert_megagroup_stats( tl_object_ptr<td_api::chatStatisticsSupergroup> convert_megagroup_stats(
@ -1445,7 +1445,7 @@ class ContactsManager : public Actor {
Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise); Promise<td_api::object_ptr<td_api::messageStatistics>> &&promise);
void send_load_async_graph_query(DcId dc_id, string token, int64 x, void send_load_async_graph_query(DcId dc_id, string token, int64 x,
Promise<td_api::object_ptr<td_api::StatisticsGraph>> &&promise); Promise<td_api::object_ptr<td_api::StatisticalGraph>> &&promise);
static void on_user_online_timeout_callback(void *contacts_manager_ptr, int64 user_id_long); static void on_user_online_timeout_callback(void *contacts_manager_ptr, int64 user_id_long);

View File

@ -163,6 +163,7 @@ class SetInlineBotResultsQuery : public Td::ResultHandler {
InlineQueriesManager::InlineQueriesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { InlineQueriesManager::InlineQueriesManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) {
drop_inline_query_result_timeout_.set_callback(on_drop_inline_query_result_timeout_callback); drop_inline_query_result_timeout_.set_callback(on_drop_inline_query_result_timeout_callback);
drop_inline_query_result_timeout_.set_callback_data(static_cast<void *>(this)); drop_inline_query_result_timeout_.set_callback_data(static_cast<void *>(this));
next_inline_query_time_ = Time::now();
} }
void InlineQueriesManager::tear_down() { void InlineQueriesManager::tear_down() {

View File

@ -126,7 +126,7 @@ class InlineQueriesManager : public Actor {
Promise<Unit> promise; Promise<Unit> promise;
}; };
double next_inline_query_time_ = -1.0; double next_inline_query_time_ = 0.0;
unique_ptr<PendingInlineQuery> pending_inline_query_; unique_ptr<PendingInlineQuery> pending_inline_query_;
NetQueryRef sent_query_; NetQueryRef sent_query_;

View File

@ -20582,6 +20582,9 @@ std::pair<int32, vector<MessageId>> MessagesManager::search_dialog_messages(
promise.set_value(Unit()); promise.set_value(Unit());
return result; return result;
case DialogType::None: case DialogType::None:
if (sender_dialog_id == DialogId()) {
break;
}
promise.set_error(Status::Error(6, "Invalid sender chat identifier specified")); promise.set_error(Status::Error(6, "Invalid sender chat identifier specified"));
return result; return result;
default: default:
@ -20592,7 +20595,7 @@ std::pair<int32, vector<MessageId>> MessagesManager::search_dialog_messages(
default: default:
UNREACHABLE(); UNREACHABLE();
} }
if (!have_input_peer(sender_dialog_id, AccessRights::Read)) { if (sender_dialog_id != DialogId() && !have_input_peer(sender_dialog_id, AccessRights::Read)) {
promise.set_error(Status::Error(6, "Invalid message sender specified")); promise.set_error(Status::Error(6, "Invalid message sender specified"));
return result; return result;
} }
@ -21307,9 +21310,8 @@ MessagesManager::FoundMessages MessagesManager::offline_search_messages(DialogId
found_fts_messages_[random_id]; // reserve place for result found_fts_messages_[random_id]; // reserve place for result
G()->td_db()->get_messages_db_async()->get_messages_fts( G()->td_db()->get_messages_db_async()->get_messages_fts(
std::move(fts_query), std::move(fts_query), PromiseCreator::lambda([random_id, offset, limit, promise = std::move(promise)](
PromiseCreator::lambda([random_id, offset = std::move(offset), limit, Result<MessagesDbFtsResult> fts_result) mutable {
promise = std::move(promise)](Result<MessagesDbFtsResult> fts_result) mutable {
send_closure(G()->messages_manager(), &MessagesManager::on_messages_db_fts_result, std::move(fts_result), send_closure(G()->messages_manager(), &MessagesManager::on_messages_db_fts_result, std::move(fts_result),
std::move(offset), limit, random_id, std::move(promise)); std::move(offset), limit, random_id, std::move(promise));
})); }));

View File

@ -724,8 +724,8 @@ void PasswordManager::cache_secret(secure_storage::Secret secret) {
secret_ = std::move(secret); secret_ = std::move(secret);
const int32 max_cache_time = 3600; const int32 max_cache_time = 3600;
secret_expire_date_ = Time::now() + max_cache_time; secret_expire_time_ = Time::now() + max_cache_time;
set_timeout_at(secret_expire_date_); set_timeout_at(secret_expire_time_);
} }
void PasswordManager::drop_cached_secret() { void PasswordManager::drop_cached_secret() {
@ -734,8 +734,10 @@ void PasswordManager::drop_cached_secret() {
} }
void PasswordManager::timeout_expired() { void PasswordManager::timeout_expired() {
if (Time::now() >= secret_expire_date_) { if (Time::now() >= secret_expire_time_) {
drop_cached_secret(); drop_cached_secret();
} else {
set_timeout_at(secret_expire_time_);
} }
} }

View File

@ -151,7 +151,7 @@ class PasswordManager : public NetQueryCallback {
}; };
optional<secure_storage::Secret> secret_; optional<secure_storage::Secret> secret_;
double secret_expire_date_ = 0; double secret_expire_time_ = 0;
TempPasswordState temp_password_state_; TempPasswordState temp_password_state_;
Promise<TempState> create_temp_password_promise_; Promise<TempState> create_temp_password_promise_;

View File

@ -13,6 +13,10 @@
namespace td { namespace td {
QueryCombiner::QueryCombiner(Slice name, double min_delay) : next_query_time_(Time::now()), min_delay_(min_delay) {
register_actor(name, this).release();
}
void QueryCombiner::add_query(int64 query_id, Promise<Promise<Unit>> &&send_query, Promise<Unit> &&promise) { void QueryCombiner::add_query(int64 query_id, Promise<Promise<Unit>> &&send_query, Promise<Unit> &&promise) {
LOG(INFO) << "Add query " << query_id; LOG(INFO) << "Add query " << query_id;
auto &query = queries_[query_id]; auto &query = queries_[query_id];

View File

@ -21,9 +21,7 @@ namespace td {
// combines identical queries into one request // combines identical queries into one request
class QueryCombiner : public Actor { class QueryCombiner : public Actor {
public: public:
explicit QueryCombiner(Slice name, double min_delay = 0) : min_delay_(min_delay) { QueryCombiner(Slice name, double min_delay);
register_actor(name, this).release();
}
void add_query(int64 query_id, Promise<Promise<Unit>> &&send_query, Promise<Unit> &&promise); void add_query(int64 query_id, Promise<Promise<Unit>> &&send_query, Promise<Unit> &&promise);
@ -36,7 +34,7 @@ class QueryCombiner : public Actor {
int32 query_count_ = 0; int32 query_count_ = 0;
double next_query_time_ = 0.0; double next_query_time_;
double min_delay_; double min_delay_;
std::queue<int64> delayed_queries_; std::queue<int64> delayed_queries_;

View File

@ -1880,9 +1880,9 @@ void StickersManager::reload_installed_sticker_sets(bool is_masks, bool force) {
} }
auto &next_load_time = next_installed_sticker_sets_load_time_[is_masks]; auto &next_load_time = next_installed_sticker_sets_load_time_[is_masks];
if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) { if (!td_->auth_manager_->is_bot() && next_load_time >= -9e9 && (next_load_time < Time::now() || force)) {
LOG_IF(INFO, force) << "Reload sticker sets"; LOG_IF(INFO, force) << "Reload sticker sets";
next_load_time = -1; next_load_time = -1e10;
td_->create_handler<GetAllStickersQuery>()->send(is_masks, installed_sticker_sets_hash_[is_masks]); td_->create_handler<GetAllStickersQuery>()->send(is_masks, installed_sticker_sets_hash_[is_masks]);
} }
} }
@ -1892,10 +1892,10 @@ void StickersManager::reload_featured_sticker_sets(bool force) {
return; return;
} }
if (!td_->auth_manager_->is_bot() && next_featured_sticker_sets_load_time_ >= 0 && auto &next_load_time = next_featured_sticker_sets_load_time_;
(next_featured_sticker_sets_load_time_ < Time::now() || force)) { if (!td_->auth_manager_->is_bot() && next_load_time >= -9e9 && (next_load_time < Time::now() || force)) {
LOG_IF(INFO, force) << "Reload trending sticker sets"; LOG_IF(INFO, force) << "Reload trending sticker sets";
next_featured_sticker_sets_load_time_ = -1; next_load_time = -1e10;
td_->create_handler<GetFeaturedStickerSetsQuery>()->send(featured_sticker_sets_hash_); td_->create_handler<GetFeaturedStickerSetsQuery>()->send(featured_sticker_sets_hash_);
} }
} }
@ -5018,9 +5018,9 @@ void StickersManager::reload_recent_stickers(bool is_attached, bool force) {
} }
auto &next_load_time = next_recent_stickers_load_time_[is_attached]; auto &next_load_time = next_recent_stickers_load_time_[is_attached];
if (!td_->auth_manager_->is_bot() && next_load_time >= 0 && (next_load_time < Time::now() || force)) { if (!td_->auth_manager_->is_bot() && next_load_time >= -9e9 && (next_load_time < Time::now() || force)) {
LOG_IF(INFO, force) << "Reload recent " << (is_attached ? "attached " : "") << "stickers"; LOG_IF(INFO, force) << "Reload recent " << (is_attached ? "attached " : "") << "stickers";
next_load_time = -1; next_load_time = -1e10;
td_->create_handler<GetRecentStickersQuery>()->send(false, is_attached, recent_stickers_hash_[is_attached]); td_->create_handler<GetRecentStickersQuery>()->send(false, is_attached, recent_stickers_hash_[is_attached]);
} }
} }
@ -5440,10 +5440,10 @@ void StickersManager::reload_favorite_stickers(bool force) {
return; return;
} }
if (!td_->auth_manager_->is_bot() && next_favorite_stickers_load_time_ >= 0 && auto &next_load_time = next_favorite_stickers_load_time_;
(next_favorite_stickers_load_time_ < Time::now() || force)) { if (!td_->auth_manager_->is_bot() && next_load_time >= -9e9 && (next_load_time < Time::now() || force)) {
LOG_IF(INFO, force) << "Reload favorite stickers"; LOG_IF(INFO, force) << "Reload favorite stickers";
next_favorite_stickers_load_time_ = -1; next_load_time = -1e10;
td_->create_handler<GetFavedStickersQuery>()->send(false, get_favorite_stickers_hash()); td_->create_handler<GetFavedStickersQuery>()->send(false, get_favorite_stickers_hash());
} }
} }

View File

@ -634,10 +634,10 @@ class StickersManager : public Actor {
vector<FileId> recent_sticker_ids_[2]; vector<FileId> recent_sticker_ids_[2];
vector<FileId> favorite_sticker_ids_; vector<FileId> favorite_sticker_ids_;
double next_installed_sticker_sets_load_time_[2] = {0, 0}; double next_installed_sticker_sets_load_time_[2] = {-1e10, -1e10};
double next_featured_sticker_sets_load_time_ = 0; double next_featured_sticker_sets_load_time_ = -1e10;
double next_recent_stickers_load_time_[2] = {0, 0}; double next_recent_stickers_load_time_[2] = {-1e10, -1e10};
double next_favorite_stickers_load_time_ = 0; double next_favorite_stickers_load_time_ = -1e10;
int32 installed_sticker_sets_hash_[2] = {0, 0}; int32 installed_sticker_sets_hash_[2] = {0, 0};
int32 featured_sticker_sets_hash_ = 0; int32 featured_sticker_sets_hash_ = 0;

View File

@ -6916,7 +6916,7 @@ void Td::on_request(uint64 id, const td_api::getMessageStatistics &request) {
request.is_dark_, std::move(promise)); request.is_dark_, std::move(promise));
} }
void Td::on_request(uint64 id, td_api::getStatisticsGraph &request) { void Td::on_request(uint64 id, td_api::getStatisticalGraph &request) {
CHECK_IS_USER(); CHECK_IS_USER();
CREATE_REQUEST_PROMISE(); CREATE_REQUEST_PROMISE();
CLEAN_INPUT_STRING(request.token_); CLEAN_INPUT_STRING(request.token_);

View File

@ -944,7 +944,7 @@ class Td final : public NetQueryCallback {
void on_request(uint64 id, const td_api::getMessageStatistics &request); void on_request(uint64 id, const td_api::getMessageStatistics &request);
void on_request(uint64 id, td_api::getStatisticsGraph &request); void on_request(uint64 id, td_api::getStatisticalGraph &request);
void on_request(uint64 id, const td_api::getMapThumbnailFile &request); void on_request(uint64 id, const td_api::getMapThumbnailFile &request);

View File

@ -4197,7 +4197,7 @@ class CliClient final : public Actor {
std::tie(chat_id, args) = split(args); std::tie(chat_id, args) = split(args);
std::tie(token, x) = split(args); std::tie(token, x) = split(args);
send_request(td_api::make_object<td_api::getStatisticsGraph>(as_chat_id(chat_id), token, to_integer<int64>(x))); send_request(td_api::make_object<td_api::getStatisticalGraph>(as_chat_id(chat_id), token, to_integer<int64>(x)));
} else if (op == "hsa" || op == "glu" || op == "glua") { } else if (op == "hsa" || op == "glu" || op == "glua") {
send_request(td_api::make_object<td_api::hideSuggestedAction>(as_suggested_action(args))); send_request(td_api::make_object<td_api::hideSuggestedAction>(as_suggested_action(args)));
} else if (op == "glui" || op == "glu" || op == "glua") { } else if (op == "glui" || op == "glu" || op == "glua") {

View File

@ -90,8 +90,8 @@ Status PartsManager::init_no_size(size_t part_size, const std::vector<int> &read
if (part_size != 0) { if (part_size != 0) {
part_size_ = part_size; part_size_ = part_size;
} else { } else {
part_size_ = 32 * (1 << 10); part_size_ = 32 << 10;
while (use_part_count_limit_ && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { while (calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) {
part_size_ *= 2; part_size_ *= 2;
CHECK(part_size_ <= MAX_PART_SIZE); CHECK(part_size_ <= MAX_PART_SIZE);
} }
@ -128,11 +128,12 @@ Status PartsManager::init(int64 size, int64 expected_size, bool is_size_final, s
if (part_size != 0) { if (part_size != 0) {
part_size_ = part_size; part_size_ = part_size;
if (use_part_count_limit_ && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { if (use_part_count_limit_ && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) {
CHECK(is_upload_);
return Status::Error("FILE_UPLOAD_RESTART"); return Status::Error("FILE_UPLOAD_RESTART");
} }
} else { } else {
part_size_ = 64 * (1 << 10); part_size_ = 64 << 10;
while (use_part_count_limit && calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) { while (calc_part_count(expected_size_, part_size_) > MAX_PART_COUNT) {
part_size_ *= 2; part_size_ *= 2;
CHECK(part_size_ <= MAX_PART_SIZE); CHECK(part_size_ <= MAX_PART_SIZE);
} }
@ -285,7 +286,7 @@ Result<Part> PartsManager::start_part() {
if (part_i == part_count_) { if (part_i == part_count_) {
if (unknown_size_flag_) { if (unknown_size_flag_) {
part_count_++; part_count_++;
if (part_count_ > MAX_PART_COUNT) { if (part_count_ > MAX_PART_COUNT + (use_part_count_limit_ ? 0 : 64)) {
if (!is_upload_) { if (!is_upload_) {
// Caller will try to increase part size if it is possible // Caller will try to increase part size if it is possible
return Status::Error("FILE_DOWNLOAD_RESTART_INCREASE_PART_SIZE"); return Status::Error("FILE_DOWNLOAD_RESTART_INCREASE_PART_SIZE");

View File

@ -720,8 +720,8 @@ Status Session::on_message_result_ok(uint64 id, BufferSlice packet, size_t origi
if (it == sent_queries_.end()) { if (it == sent_queries_.end()) {
LOG(DEBUG) << "Drop result to " << tag("request_id", format::as_hex(id)) << tag("tl", format::as_hex(ID)); LOG(DEBUG) << "Drop result to " << tag("request_id", format::as_hex(id)) << tag("tl", format::as_hex(ID));
if (packet.size() > 16 * 1024) { if (original_size > 16 * 1024) {
dropped_size_ += packet.size(); dropped_size_ += original_size;
if (dropped_size_ > (256 * 1024)) { if (dropped_size_ > (256 * 1024)) {
auto dropped_size = dropped_size_; auto dropped_size = dropped_size_;
dropped_size_ = 0; dropped_size_ = 0;

View File

@ -97,18 +97,18 @@ TDJSON_EXPORT void td_json_client_destroy(void *client);
/* /*
* New TDLib JSON interface. * New TDLib JSON interface.
* *
* The main TDLib interface is asynchronous. To match requests with a corresponding response a field "@extra" can * The main TDLib interface is asynchronous. To match requests with a corresponding response, the field "@extra" can
* be added to the request object. The corresponding response will have an "@extra" field with exactly the same value. * be added to the request object. The corresponding response will have an "@extra" field with exactly the same value.
* Each returned object will have an "@client_id" field, containing and identifier of the client for which * Each returned object will have an "@client_id" field, containing the identifier of the client for which
* a response or an update is received. * a response or an update was received.
* *
* A TDLib client instance can be created through td_create_client_id. * A TDLib client instance can be created through td_create_client_id.
* Requests then can be sent using td_send from any thread and the received client identifier. * Requests can be sent using td_send and the received client identifier.
* New updates and request responses can be received through td_receive from any thread. This function * New updates and responses to requests can be received through td_receive from any thread after the first request
* must not be called simultaneously from two different threads. Also note that all updates and request responses * has been sent to the client instance. This function must not be called simultaneously from two different threads.
* must be applied in the order they were received to ensure consistency. * Also note that all updates and responses to requests must be applied in the order they were received for consistency.
* Some TDLib requests can be executed synchronously from any thread by using td_execute. * Some TDLib requests can be executed synchronously from any thread using td_execute.
* The TDLib client instances are destroyed automatically after they are closed. * TDLib client instances are destroyed automatically after they are closed.
* *
* General pattern of usage: * General pattern of usage:
* \code * \code
@ -119,7 +119,7 @@ TDJSON_EXPORT void td_json_client_destroy(void *client);
* while (true) { * while (true) {
* const char *result = td_receive(WAIT_TIMEOUT); * const char *result = td_receive(WAIT_TIMEOUT);
* if (result) { * if (result) {
* // parse the result as JSON object and process it as an incoming update or an answer to a previously sent request * // parse the result as a JSON object and process it as an incoming update or the answer to a previously sent request
* } * }
* } * }
* \endcode * \endcode
@ -128,31 +128,29 @@ TDJSON_EXPORT void td_json_client_destroy(void *client);
/** /**
* Returns an opaque identifier of a new TDLib instance. * Returns an opaque identifier of a new TDLib instance.
* The TDLib instance will not send updates until the first request is sent to it. * The TDLib instance will not send updates until the first request is sent to it.
* \return Opaque indentifier of a new TDLib instance. * \return Opaque identifier of a new TDLib instance.
*/ */
TDJSON_EXPORT int td_create_client_id(); TDJSON_EXPORT int td_create_client_id();
/** /**
* Sends request to the TDLib client. May be called from any thread. * Sends request to the TDLib client. May be called from any thread.
* \param[in] client_id The TDLib client identifier. * \param[in] client_id TDLib client identifier.
* \param[in] request JSON-serialized null-terminated request to TDLib. * \param[in] request JSON-serialized null-terminated request to TDLib.
*/ */
TDJSON_EXPORT void td_send(int client_id, const char *request); TDJSON_EXPORT void td_send(int client_id, const char *request);
/** /**
* Receives incoming updates and request responses. Must not be called simultaneously from two different threads. * Receives incoming updates and request responses. Must not be called simultaneously from two different threads.
* Returned pointer will be deallocated by TDLib during next call to td_receive or td_execute * The returned pointer can be used until the next call to td_receive or td_execute, after which it will be deallocated by TDLib.
* in the same thread, so it can't be used after that.
* \param[in] timeout The maximum number of seconds allowed for this function to wait for new data. * \param[in] timeout The maximum number of seconds allowed for this function to wait for new data.
* \return JSON-serialized null-terminated incoming update or request response. May be NULL if the timeout expires. * \return JSON-serialized null-terminated incoming update or request response. May be NULL if the timeout expires.
*/ */
TDJSON_EXPORT const char *td_receive(double timeout); TDJSON_EXPORT const char *td_receive(double timeout);
/** /**
* Synchronously executes TDLib request. May be called from any thread. * Synchronously executes a TDLib request.
* Only a few requests can be executed synchronously. * A request can be executed synchronously, only if it is documented with "Can be called synchronously".
* Returned pointer will be deallocated by TDLib during next call to td_receive or td_execute * The returned pointer can be used until the next call to td_receive or td_execute, after which it will be deallocated by TDLib.
* in the same thread, so it can't be used after that.
* \param[in] request JSON-serialized null-terminated request to TDLib. * \param[in] request JSON-serialized null-terminated request to TDLib.
* \return JSON-serialized null-terminated request response. * \return JSON-serialized null-terminated request response.
*/ */

View File

@ -82,6 +82,8 @@ class TimeoutManager : public Actor {
void test_timeout() { void test_timeout() {
CHECK(count > 0); CHECK(count > 0);
// we must yield scheduler, so run_main breaks immediately, if timeouts are handled immediately
Scheduler::instance()->yield();
} }
MultiTimeout test_timeout_{"TestTimeout"}; MultiTimeout test_timeout_{"TestTimeout"};

View File

@ -110,7 +110,7 @@ class Manager final : public Actor {
int query_size_; int query_size_;
}; };
void test_workers(int threads_n, int workers_n, int queries_n, int query_size) { static void test_workers(int threads_n, int workers_n, int queries_n, int query_size) {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR));
ConcurrentScheduler sched; ConcurrentScheduler sched;

View File

@ -19,7 +19,52 @@
namespace td { namespace td {
namespace { namespace {
string quote_string(Slice str) {
size_t cnt = 0;
for (auto &c : str) {
if (c == '\'') {
cnt++;
}
}
if (cnt == 0) {
return str.str();
}
string result;
result.reserve(str.size() + cnt);
for (auto &c : str) {
if (c == '\'') {
result += '\'';
}
result += c;
}
return result;
}
string db_key_to_sqlcipher_key(const DbKey &db_key) {
if (db_key.is_empty()) {
return "''";
}
if (db_key.is_password()) {
return PSTRING() << "'" << quote_string(db_key.data()) << "'";
}
CHECK(db_key.is_raw_key());
Slice raw_key = db_key.data();
CHECK(raw_key.size() == 32);
size_t expected_size = 64 + 5;
string res(expected_size + 50, ' ');
StringBuilder sb(res);
sb << '"';
sb << 'x';
sb << '\'';
sb << format::as_hex_dump<0>(raw_key);
sb << '\'';
sb << '"';
CHECK(!sb.is_error());
CHECK(sb.as_cslice().size() == expected_size);
res.resize(expected_size);
return res;
}
} // namespace } // namespace
SqliteDb::~SqliteDb() = default; SqliteDb::~SqliteDb() = default;
@ -176,7 +221,52 @@ Result<SqliteDb> SqliteDb::change_key(CSlice path, const DbKey &new_db_key, cons
SqliteDb db; SqliteDb db;
TRY_STATUS(db.init(path)); TRY_STATUS(db.init(path));
return std::move(db); // fast path
{
auto r_db = open_with_key(path, new_db_key);
if (r_db.is_ok()) {
return r_db;
}
}
TRY_RESULT(db, open_with_key(path, old_db_key));
TRY_RESULT(user_version, db.user_version());
auto new_key = db_key_to_sqlcipher_key(new_db_key);
if (old_db_key.is_empty() && !new_db_key.is_empty()) {
LOG(DEBUG) << "ENCRYPT";
PerfWarningTimer timer("Encrypt SQLite database", 0.1);
auto tmp_path = path.str() + ".encrypted";
TRY_STATUS(destroy(tmp_path));
// make shure that database is not empty
TRY_STATUS(db.exec("CREATE TABLE IF NOT EXISTS encryption_dummy_table(id INT PRIMARY KEY)"));
TRY_STATUS(db.exec(PSLICE() << "ATTACH DATABASE '" << quote_string(tmp_path) << "' AS encrypted KEY " << new_key));
TRY_STATUS(db.exec("SELECT sqlcipher_export('encrypted')"));
TRY_STATUS(db.exec(PSLICE() << "PRAGMA encrypted.user_version = " << user_version));
TRY_STATUS(db.exec("DETACH DATABASE encrypted"));
db.close();
TRY_STATUS(rename(tmp_path, path));
} else if (!old_db_key.is_empty() && new_db_key.is_empty()) {
LOG(DEBUG) << "DECRYPT";
PerfWarningTimer timer("Decrypt SQLite database", 0.1);
auto tmp_path = path.str() + ".encrypted";
TRY_STATUS(destroy(tmp_path));
TRY_STATUS(db.exec(PSLICE() << "ATTACH DATABASE '" << quote_string(tmp_path) << "' AS decrypted KEY ''"));
TRY_STATUS(db.exec("SELECT sqlcipher_export('decrypted')"));
TRY_STATUS(db.exec(PSLICE() << "PRAGMA decrypted.user_version = " << user_version));
TRY_STATUS(db.exec("DETACH DATABASE decrypted"));
db.close();
TRY_STATUS(rename(tmp_path, path));
} else {
LOG(DEBUG) << "REKEY";
PerfWarningTimer timer("Rekey SQLite database", 0.1);
TRY_STATUS(db.exec(PSLICE() << "PRAGMA rekey = " << new_key));
}
TRY_RESULT(new_db, open_with_key(path, new_db_key));
LOG_CHECK(new_db.user_version().ok() == user_version) << new_db.user_version().ok() << " " << user_version;
return std::move(new_db);
} }
Status SqliteDb::destroy(Slice path) { Status SqliteDb::destroy(Slice path) {

View File

@ -130,8 +130,8 @@ void GetHostByNameActor::run(string host, int port, bool prefer_ipv6, Promise<IP
} }
auto ascii_host = r_ascii_host.move_as_ok(); auto ascii_host = r_ascii_host.move_as_ok();
auto &value = cache_[prefer_ipv6].emplace(ascii_host, Value{{}, 0}).first->second;
auto begin_time = Time::now(); auto begin_time = Time::now();
auto &value = cache_[prefer_ipv6].emplace(ascii_host, Value{{}, begin_time - 1.0}).first->second;
if (value.expires_at > begin_time) { if (value.expires_at > begin_time) {
return promise.set_result(value.get_ip_port(port)); return promise.set_result(value.get_ip_port(port));
} }

View File

@ -10,6 +10,7 @@
#include "td/utils/port/ServerSocketFd.h" #include "td/utils/port/ServerSocketFd.h"
#include "td/utils/port/SocketFd.h" #include "td/utils/port/SocketFd.h"
#include "td/utils/Slice.h"
namespace td { namespace td {

View File

@ -91,6 +91,10 @@ Result<vector<char *>> OptionParser::run(int argc, char *argv[], int expected_no
#endif #endif
#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) {
std::unordered_map<char, const Option *> short_options; std::unordered_map<char, const Option *> short_options;
std::unordered_map<string, const Option *> long_options; std::unordered_map<string, const Option *> long_options;
for (auto &opt : options_) { for (auto &opt : options_) {

View File

@ -66,6 +66,9 @@ class OptionParser {
// returns found non-option parameters // returns found non-option parameters
Result<vector<char *>> run(int argc, char *argv[], int expected_non_option_count = -1) TD_WARN_UNUSED_RESULT; 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); friend StringBuilder &operator<<(StringBuilder &sb, const OptionParser &o);
private: private:

View File

@ -18,7 +18,13 @@ bool operator==(Timestamp a, Timestamp b) {
static std::atomic<double> time_diff; static std::atomic<double> time_diff;
double Time::now() { double Time::now() {
return now_unadjusted() + time_diff.load(std::memory_order_relaxed); 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() { double Time::now_unadjusted() {

View File

@ -341,9 +341,11 @@ class Evp {
init(Type::Cbc, false, EVP_aes_256_cbc(), key); init(Type::Cbc, false, EVP_aes_256_cbc(), key);
} }
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
void init_encrypt_ctr(Slice key) { void init_encrypt_ctr(Slice key) {
init(Type::Ctr, true, EVP_aes_256_ctr(), key); init(Type::Ctr, true, EVP_aes_256_ctr(), key);
} }
#endif
void init_iv(Slice iv) { void init_iv(Slice iv) {
int res = EVP_CipherInit_ex(ctx_, nullptr, nullptr, nullptr, iv.ubegin(), -1); int res = EVP_CipherInit_ex(ctx_, nullptr, nullptr, nullptr, iv.ubegin(), -1);
@ -615,7 +617,14 @@ void AesCbcState::decrypt(Slice from, MutableSlice to) {
} }
struct AesCtrState::Impl { struct AesCtrState::Impl {
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
Evp evp_; 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() = default;
@ -627,13 +636,38 @@ void AesCtrState::init(Slice key, Slice iv) {
CHECK(key.size() == 32); CHECK(key.size() == 32);
CHECK(iv.size() == 16); CHECK(iv.size() == 16);
ctx_ = make_unique<AesCtrState::Impl>(); ctx_ = make_unique<AesCtrState::Impl>();
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
ctx_->evp_.init_encrypt_ctr(key); ctx_->evp_.init_encrypt_ctr(key);
ctx_->evp_.init_iv(iv); 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) { void AesCtrState::encrypt(Slice from, MutableSlice to) {
CHECK(from.size() <= to.size()); CHECK(from.size() <= to.size());
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
ctx_->evp_.encrypt(from.ubegin(), to.ubegin(), narrow_cast<int>(from.size())); 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) { void AesCtrState::decrypt(Slice from, MutableSlice to) {

View File

@ -91,6 +91,8 @@ int Clocks::tz_offset() {
return offset; return offset;
} }
static int init_tz_offset = Clocks::tz_offset(); namespace detail {
int init_tz_offset_private = Clocks::tz_offset();
} // namespace detail
} // namespace td } // namespace td

View File

@ -30,7 +30,7 @@ class NodeX : public td::MpscLinkQueueImpl::Node {
}; };
using QueueNode = td::MpscLinkQueueUniquePtrNode<NodeX>; using QueueNode = td::MpscLinkQueueUniquePtrNode<NodeX>;
QueueNode create_node(int value) { static QueueNode create_node(int value) {
return QueueNode(td::make_unique<NodeX>(value)); return QueueNode(td::make_unique<NodeX>(value));
} }

View File

@ -23,7 +23,7 @@ TEST(OptionParser, run) {
for (auto &arg : args) { for (auto &arg : args) {
argv.push_back(&arg[0]); argv.push_back(&arg[0]);
} }
return options.run(static_cast<int>(argv.size()), &argv[0]); return options.run_impl(static_cast<int>(argv.size()), &argv[0], -1);
}; };
td::uint64 chosen_options = 0; td::uint64 chosen_options = 0;

File diff suppressed because it is too large Load Diff

View File

@ -67,7 +67,7 @@ static const char gzip_bomb_arr[] =
const char *gzip_bomb = gzip_bomb_arr; const char *gzip_bomb = gzip_bomb_arr;
const size_t gzip_bomb_size = sizeof(gzip_bomb_arr) - 1; const size_t gzip_bomb_size = sizeof(gzip_bomb_arr) - 1;
static const char sqlite_sample_db_arr[] = static const char sqlite_sample_db_v3_arr[] =
"olFZ1MdfY0Abj+LtR9ft6DTZgEHW7/" "olFZ1MdfY0Abj+LtR9ft6DTZgEHW7/"
"z7yAhC07NKr7pBAHWkbQyMPtyVSIW7PLdVaQIHYwLgd7ovQSzD7eTINxZh6Nxpwa8HTynvjhHIdQhtysRL9m3mTEj4mbjU48zq+jcFdsnzG+" "z7yAhC07NKr7pBAHWkbQyMPtyVSIW7PLdVaQIHYwLgd7ovQSzD7eTINxZh6Nxpwa8HTynvjhHIdQhtysRL9m3mTEj4mbjU48zq+jcFdsnzG+"
"GJAzk5MQRCi+NyDWGYnBoPhiiPc4QBoFgKoYGIhQwfVci5kf2fYIQrCM1H7QQZ8RHoqCuQ3IjMpZjt/Gzqy+P8kn2PXKWHG8/y5eDc5nQCk/" "GJAzk5MQRCi+NyDWGYnBoPhiiPc4QBoFgKoYGIhQwfVci5kf2fYIQrCM1H7QQZ8RHoqCuQ3IjMpZjt/Gzqy+P8kn2PXKWHG8/y5eDc5nQCk/"
@ -153,5 +153,343 @@ static const char sqlite_sample_db_arr[] =
"GVt8zi3m0oPlAhNUyi3a6zeZcvqfwI3M7zoXxGU2q0ETZgfCE26H9E+PNxes7mw4SwEl78lclmnNhUlZ5C4Y8v2YJnmFn8+a6WdrgjTU2awQ/" "GVt8zi3m0oPlAhNUyi3a6zeZcvqfwI3M7zoXxGU2q0ETZgfCE26H9E+PNxes7mw4SwEl78lclmnNhUlZ5C4Y8v2YJnmFn8+a6WdrgjTU2awQ/"
"osSJFtKuNgOw9n72uyhPOkEB4qcVZ1A="; "osSJFtKuNgOw9n72uyhPOkEB4qcVZ1A=";
const char *sqlite_sample_db = sqlite_sample_db_arr; const char *sqlite_sample_db_v3 = sqlite_sample_db_v3_arr;
const size_t sqlite_sample_db_size = sizeof(sqlite_sample_db_arr) - 1; const size_t sqlite_sample_db_v3_size = sizeof(sqlite_sample_db_v3_arr) - 1;
static const char sqlite_sample_db_v4_arr[] =
"6YZiAqwf2RbWHjDRoAmm2jMPm70HtfDOgIbyJR9Btp1RWtJiNmwmqsZCfh872BvqFVfYdhtfUJwVIpOe3se/"
"9d9v+"
"eMJFKyUxyxkeWjwLJiAEa1fQtF9C8RVtmiTUweBQFmuAKlIVjTHfM0C2IspViXoBYMElb6CpuwKIN0XptduYh3GPC2IBjBET0eGWycgrOPCeTSe8PC"
"SfBtaX7dlCOE3zTeRqR+iP3G5Tb67xB2e8CDp4EbPJiXbNwKMyAek73D8dVqeFcjtuUfRkah/Iu6hIkBLKiS02ut/"
"jD0b1Sy5RyR7NDiCOKVmqnwwKDrcDbofPYmll8ilK4/qzcmzRBJKhxJeaRRy1X9UvzSbYaj22wCKrQLMqHeZ7EVOsPXJEhLVsSWnym/"
"vkl9mLxhiDUhvP4pNK3KjmoK1bTEIpqABLhQELQjl+EzBDt+"
"tYjeyZ0vCXL2JUYScZcWHn0kD1jwIYGm7r4QIfEMOwwPkkIcsIUOQjW2ICqCVfffoP5JQO9e5352tpAYcUYyiagGKDYtt1yDmFqqvL8Jn+"
"r19aJHMsQgUgE7dEusHXiefgeaVRmmI8gW88xvYqyPcM5dz9t8eU7jl7ipq4EPYPOl6K4p0OU/"
"Ro2CJTekwHkaouF2T+"
"L9WOuRT5WgGsUpcvmY7xDtmS85Iy3uQHZuNeL6FWSCJZ8qjbFbx3zKrlSoUwef86H8OGNA4rYmpKaEpQAsiI1IUi21q26HlgjPE9+"
"m2M3HZH72fnJ6lZw6v+vy7Jz/M80nIv0A5A67DE/"
"Gu7FPHx4NmxLPbU3pWMIg8fUl3uB5Pmk6hL+Gxi2+W+jvpLk31Qk7HPDX2H+bdTEgetpKzgjcoh/"
"XbvrBS82jWgHpWzw9vlbd2FUKbT20ULRE9zSHFYbdWmqchJc4Ma6iNY8rDgH4slTCt3koEaIXTwCsBguVS9a/X1QLUcWlBzt7ot5hovXQUuKd/"
"XHDRvusNMvcyxSOwPpF3tbMO9or+BKUq4nmwnzFUzzijl/v0KYUaTmYXQ2HgLW71oUl0cqdJ9cYk0s4MSppzo1wYF3BdQ/"
"348785aLf4O3HwZSXfdu2juvZTMkksahRiRCQgUDaB9izTwe/PyfRYv/"
"fm5VY6xzoQc3RiCa4zAQBDj3JffYw6cAYk9RHh290Gcvf8QgfBthp7dwlS3XzTNsubyd4l4FRQkvpWInHiFEUPd7wwHQNCnxE20DK/"
"S8Jw86Q7VpGnmPlVGQn/vdW2RrJ/"
"ATsQnBQPUb2+"
"vIoVqFRhHWk81Qxp3LL0Jy1xWe8B7y7L47R8lzI98vOwze7KBr8IH3Sw0snD86vp0kqTqnmJVYrWIROMYm9be7dvc9GyqFPF3lAhnApCetJ4+"
"PSkh0hnW6a1rwhb/r1QFBrhChatbFGh2fJ0nyGV5i9+Y5EzB3hdgyupU5m+rulS2DngmhEl/PQ7L7ALIZksbYwi/"
"1O5qjRKGf8pp93LhMysP21f2GU/Yl6JX2QZT9139ag1M/Kh/"
"pqYTuqrLsE38Lc878Q4kcBX45tklajnY6wGCzqSCGnhDu79SvmgE8Ymw1klxen17FDtgjqJ9ZnkJsd4RoES9kGWyI1zEKFFVH7yq0K2Psl+"
"ft8MQjHXNn80H1plnpQ7SRINJZBZFq8PPhRVuiJdO1+byGy/"
"J4TLN4jrc0CVIufAa60SOqU2S236nXsF0fW0b1V8zrgEK4qULe7EA+ubk4RxHZIotRffpHmZS5enm/r+ENZL8lECFnnUPbOQ5VhL/"
"J6ICCsXykofIKiYGcegY+"
"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"
"LwABF09LWdz4rnhjMviM1ScKkv4n7Q2EeI/"
"VWskgjS+34Ue0xDDp0+JZfkS3B2xfs4eh9XHkpig95sedAqfDOyHXI4lX10FushcbP5fni6CpFpKxoQheFv8Ek8AHgeRlXSFn8MzE7Gl+"
"5bC7iJ2r1ZXh0lUeVDUwXgM7EHQMqTMwB0MxjJLsIWtGpE329hbAJvSvJWI24InXoa4M0jGd+x3ULJ3EUITYiqf1k7c+"
"nKYd9jlaWYkZKZb7xHlNcOrVWLYl9HqiiFICQpkcWfbbqElaUUmWWRrC2/RhQZQ/"
"1QUXa8KO61bZdH3oK8VgIbo2MGNveROktCFiyrHhGYZ49WCWznhRd7w7CThjXb89BdMyXzA+7kiBkr894Y1sx1gx+tzksmsKvY/"
"a4fOkDoClnVV8T0EF8AwvpkAJyBjF+Cgc72iXNxPAPNnqOxvdC/"
"ySZmWOPN459ctwNPhhbUpM+nOsGD0VaC1TlAn9Balw66P4ETQH7D1OjaNf7zQ3lc5yhz5Sw3a7hZLDu5QwA+NK/"
"7SuE1+xntGd846b1ozytfPvEW7w3paf35d8NpIUFs0fCzF7eG76vNp4hYNy88xi/zGQ080HfGk/"
"lojlEpRqjqbLAatkWBUuuaKKdKIVf3ykKHix3+eW7zyY2G4nCaM3fk+eOCp4+v85l37n7/"
"pLWhn3X1HA1OvLenhe8Cmub0AT1IDDG4NlstG9VRDoqtXOjizahX8BQXXziAqhdOUMObMrXZlAD/"
"ZhuOBxIDlg8EJCUhUzqTGWnX2TOA4RaceCQhIZHzoYTCHvpUtIAOTqdiFOjRPDMuUETNNtAFk8N5vaHqlWlZIltl7wzEd0mJc7DVL5ht/"
"nhcvLWEhfOZAZWVMIErnjyy9oJuOVU9GYsiRDkkvRmIBAk7ziRwg1562jDtYskgrPeVm56O9nODyQaEFbDpH+K5KmoWlsAyA3btgkZ/"
"Vp0HSePyXS3xRw5Aqa766McOpfDXMQe/"
"QEc6+"
"gTpjjWPa9zMgmMTOUEYxutV0iMFC86LWjsWuDnj3Uo4V1tNosEPebxYKkePTRxArsVV8AbDlKNkj9Njh5EvrxJv3Ukg5eLvXDZKvcsdOsaYvkRtkC1"
"8+mW6hF6TVJ2Aj/NJ4+JmdM6gdVp0QHBaEAgh5CGQ/"
"piPFthNmvCRpeDroD8tQD24C6ssTDl6pysRNorhzO261iGCEe+"
"uCdhnoBjsjZzfcPYsKEQN3uMR6s17FGESaCbv5Dhm0RzInu8YtUFevKxvAsGoZqiOXxhvA6Amvo2PEWyQXD2lS8a9YB1MvUTkY4aAXQZxynRb6STnu"
"bmzl0HewbhXSYQ+qejqdBRxCbFhu/uMNnxZbzGjBVyYNPPMgPtH5glPfLj89arPr12nxVIV4/9CfGiu0STliVhZLTvh4xS/"
"7lgXC9NqwD5VfQNcFDn5ybgVbWeF/KF4k7eHA4C3pPTpgEK4UQKqZeUXuZap6xUgmPkHAD2tY5S3KhaeiI1GURDk1LVEuYMqQYg0SszamHEf/xJHM/"
"CzmFENCzfFvW6iJw1f/lrMHDdRJmjPRB26/7JPitYpRYWASG1FRb8IzaxqXJ3vFXBn3RMpY5iTt6nk8mWgwLCjqLiTUZPpttGLEgrpeb5e/dD1P5k/"
"pIiqfQobv6DfN78w4tcQhpOI0Y0SwlyzKjLjohUmKEeD70ZRZB5CC/bT/Uk4lry+bGdwfdZg8KvCgT0P+/"
"ciaPyARWKJxkjyUsB7meweTZncXsOy87kJHhRtK+YeK0Ee0qW3q15L1CcbSWvAG+GdNjzKN9qpFTmGZaOrKUyk+QzuQp/"
"jghdOOe464NgK+V4LL1Z5kJFpsBhslpJY24E6i+laEIPi03wFcztYG45zi9H9IwxTEUifoljWEbcnFFYfUh/"
"hsAiUnNDdMlsqPwyA+KMnD9kYyTM5EXZ+RhkVebpNeBHjtUSwoTO4Hyeve/DrKs4BMnokPvbCybs0okso7JFA0o9YLq5/"
"VMh5Iihxyq3hxfTykJfbGID0TMTbzGQ1zJW8wh322l14VmnNa/"
"rSSWit5LsCCqgrlruqIEjD8Myx4nB6uu9Z4goffZKF5vtheukwq2fDEF9IsgoVQu4byzsIccL3OQ/JcXvXwjrrkDa5gqZKn/"
"c2pyVwQF0vLf5O+xIiskza/vu2KdAfXdjY2GA+s3GZeBauwyM4PKkgNwb4mdRSIxjPBd+lcu2CfT4YjeGz7Ckn9f1jaNHUEcPqGYtk4x/"
"AEwereiXAi+"
"CWxhChrjULbs43IDlcwHUyTXiltyCJTyP5KakHGUgQX80mcdUYRw4DZggwoBUgAH3VX9gow3GJgMoRfegqh2fF6ExNxcBDdL5ybHirQN3Dg2RKh1WE"
"G2KLKS922uj3qSZ9VwbAp8T5Y11Z0Tp2QataJuSlt1Yvv22SnXBHYTVvg29xdT88ZhC5ICiFqXWLc1x5WgL413ul7oUcQD+"
"VZeGFBB68835DbTDoghbmaklEVuS8Yn1bwP+tAiIK3QOFpJ4+"
"JhCRqPf2grJEarkcxuKvc3T6EoaGQnSKoOUwj48AozJAH1QGamhV3C1akpqv6f9rtidXuVwx3BAbYAIe7HRNZ+wA0klnmHjkLcsIIOZ6lGbN/"
"Iy8luQlV0KS4a1DN7NrognIZ8d5zYZc+vS2qe7V7dxOp5mrKrnUDA+lZYZGVt2IoACYVfmNVkQULqs6+"
"FpfLMBWp4Yr55gt1riZCxXJ9bBgMCBzt1PTUVsHzZW+q22S05MMgBKaO89yTEU++46jws+W+7QZxi7BwmEPrpj+"
"GuuWxtnBjdqqSsOwoxVendEeLbhOm+cXSDSnXuNQE4STC/mF3DyGrHHpITdw2yLpltGwNnICiTp4slwg0Htsgwd72Ciym9Qvv+rzIwN5WR0PT9QB/"
"K5q4Wu2u3yxzXHPF0qwhJjVPec09/"
"QeBFkVo8eR1mXG9jZhNcxPPYP10wqAMVtgL65nhzzEDyS9cRoovNExH6LzPz8uMUGhZBuiN1crX5EqkVeJ0WVQbj/"
"ixqM9vpxKRG2WNX5A9jfTjgqx/"
"LqAo2i9Hf5YPMuJdGH2Sa8cwUMxlNNsjdanERNuhDF7TFeFM97hXGhVRGn4nasYEQmxQYepmf3ZhDOPk5R99gJZPPDBYSB+"
"UfGmqkBD1imYvrviGj0KATGpFveUmwa2BHv22gMRdidv75fEbVfpbhvHU+Vj69rWBtrTSAodscfXvmwyOPJOK97Z4hEUA2T+n/"
"8x2P8NYjayHJv7HrYID130WV3bOJWgAUGz++314+W7EN/"
"agjWDVH78SbiaquPkdVTa4GmBu4hKDNxsZYm7p6ridKNQ6dqKWUR4lAhFyUug0ohYL7DytzCW+36ICW3AsZmoSybyvI5czXNTx0+z+"
"zo60gODtb7LbqNb1Ff2ixlc8okhgOomHg7mvAH23t72hgq7Xl9TFk06pKshGpljpm38G62rihKxgidJ1/OaGsrobnwRmEbF6PYDqjp1NAORsG4u//"
"c8wKAQlA51saIpVkHDfQemKwGuLsg4t4zXkCpQd2mW1QP2ZzOOS60qG/r/V0DVVFELJzNkOz8B3feH6CjDVgACgIeAlAKYfTS8JizfcCbVCFpv/"
"kaJ2sTle34TxH3oMKfJYFJ8kzXqni4UAyF0jAVJOblTbuySNKKkODQJjOa+rmivHGMtMS1o51oKU+vzXGavTm0Op+"
"1wTsOrlDbdv1ItenEycdXQnLci6xHT98AgjVmjBAqP5RKZXEEAtokNfLkq9+"
"GekgmiOPTtSbazIsuBFM7ZbqrhuNY3ielOPL0hO6PGxgHdBA79KfhDVswMtsPT68L/I+RmaEA571TgzUWRwMC4QlaOoPxJULJxiJ187KeXV/GS/"
"e7JpGQo7pAJBZfamo7+OLKeA+QoW3vIWWnKabByiKGgp6rpdUxPGUSn+/ny4UAV1zsNB2jMaE/"
"1OVxznFDpgsQ2TiYc3Bi6dzrD93xWn3BVgobD1oFdXynBWLdFn+"
"Y0IoN5GLCSElZ4h5IVT9UB54wZ05hC3zY9qOi7VWDSAQ2AeegtNxvYg3i2D4bLt3mEd0+"
"3PbhV1DB1XpR3Gdoxr2XYifvicmuxNUCcRcjAvTO82IENMWqd7+/kJsIW8ZJWUzP6BrsRaM/"
"HboweCRehOWOEftlwNRYrq00h6gkNqGUNtKl2CDoVqr28Anbn+5uKmKwLgf3Lq3Q3bdD1zadOc5fI7NELLLGZ70WOx+"
"EORCVRFDSDoDKQwdixEEJiJZcEhG/"
"Sai+"
"4iYVrRQ5hwyWKHEVrGcE0YPsT8PN08RDb3HR6L7mPNN5KIaKultAxESCVQMbTYeLKpuoCesrbWn7xtejikKGeMEyLI7bF60PZ3DBNzYF3jnEEcwj5P"
"d1sTubA6r+D7tpzaZwSg+5VRD4PLySG7hmT4BPP3QEJvtxfNFTbdW0Yp76tSZJi82zTVY9EgpGZ5t68ZhQtzmkTUmtAQfAuNEEZ+"
"UE8Uuq03bEkSlCjlyUVWedQT1Qs3+oT7DwOU8ystBo6HiwEji3c5sfopnwQ8myk3/ADrVWcuCPtIB0E5OtoJ6IpwIW7enMk41vd/"
"wdAKi8zFrjslkjZ4q1BKPcw4TR/"
"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"
"F8RbPrGzI+0hLE7Wp7Adwrd1kZOs/"
"VshtUIVMUt3fYTRSWGlK9DJ4U7B5+ASMn6OGXibPR+CjPCEOn0AnofaliR0zzf9pao1Mcclx0dBpg0IUjWNDu89De/"
"kccVxzmkh9DapZSq8Z57B3wtZ91T93KPNljzPLTbShwp87xMABC0W/j4VGmiB6/"
"UYYDG6DqEfZ9p41X18kH+dAI4cWFvlWSIEXfZcqo4T4T2sZm3bI9u48v3ESECI3v19ZF9fTWUEiEVCWQ6tZZxquL+"
"KW7aQZB8tYw9PmiB89ifP4bguhdf3y4t+iJZTg2byG3YecCDlcidosjcSXLXz8udeHDxbtVfDEYb7PzO4GYMDIAIevJ3froG3jygY3mHzh/"
"uQor+v1No9+7tCHrbuBLn2JzGbz/rIraN8y228siXpA6NPZ7eXIMZSmsYCBgfFrtxN7TMVAQrKWzBY2HS3O6HrBkGIlskUXzXI/"
"feeBiXUFaKvAxBoIyGKuCbirseFNRjmC6Lq/wh9+/xwjN/aDG/GXr9qzQdNQtT51hILFCg6H4/"
"gRJnwLeWX7j31raA8E4HcSaJldlsYlSVYzh7qLWdhmuSjJo8fK88wQRyTKiR2jykS/PCd1U0OCylFA6YqtwQNknw3HTbDVLGdH9FXHPV14/7DB/"
"JximCbtPRlO6WTo/qls/"
"Rl4vdwvKo4avT2LBALxm21vrFFeNjV97vUgthq+r9Tciemw3QxyOIafJeLue12Bv07MUS1VLklfaAn+XhHj21exq8RiVLL2vgT+"
"CAe4OYwjE3BPHO71L5NCCizIOBShWno6j+ndo0k4Y0FApp6UXsLgv4sQbrRp7eW8rJVlstEWP8nlhbtn5GiQlH2qFLfkdP3Djq46aHXfao0+"
"Q3SbTtw5lZMJoapqO1pcx9C5XQ6vZk64DpqXNWVKgAaVrM7aCNB9a7tcWwU2yVICOtPyDaOPg4874Bhe7sGUh/ONA1GihrnX/ikNYSZ7R+gtfZmZ/"
"AICmbhJ+nBicOMMfOmliJTTvS6Cae3X/Tp/bM6JjT5paEVX/NvDqlOWc7bjvLyIB9vTAUHSYX7bZITgXAnMfjN2ODxA/"
"KE0trGknKA5nRyeddqbSn4z+vouYydv48OHj7EYx9ntEZNVi8qNtEkOVIJaE3HMKxAq2A/kUSrxiFXynB3twj/D76c6AE3blSNgf5/"
"eviDke7vg0wAwbW2NBOT7HKvwdIesPLb00Q1xli78Gr2N6rN/cFxF3k7olYethpJXt30kyPRc56wWR/"
"jPLrWl4lChCf6JiWjjJT0ZYQV1x3hODAnVye7UFout05YlwDvzAw9XmbCUl868Sz12gLfYYD9YLjemZiR7wykpTS2lxaoWMWyZPo4CC3Gi8D3cxBfR"
"1aYk/S4KShnnbUkTfidQ/Eicc2z54i/BUUyeCmPort+siOZOizzsOl7HOMmLQGzB7jVACOEga4bsa4F0FpShXXuqTWvOP/oUlvxlqlKZLb5+1aoN/"
"hlgFwlnKDKh/P9HFAa/OrrkkqIxHOj9YzBZIoWeRna/"
"4aHx5mqlAHIqD9edVVwmSVhHMTl2fvcfDYWiyPbVgK5cKo7Gyw0AQGlBs62xYgEKAT+RLnqhuutHWHyWnlNK2PRnrR43+BwIWYByBG4LL5uituRr/"
"oMPHgJtc6KM0JqT9OeeQt7lksCDtN4mey3SNo5VU1ng3qwytI+t3KLveG9vL4Qx1BNRuP4Kz1UqRrbriZTM7bz7L/"
"HjnbjIGIg9c20pitmqJHKO71x2hA1mZ6PMH/ziDwkfI3kej3F8gqSKazY/"
"twjZrf1dn0DqG+"
"P5nI9oGw6Kf91o7gRclhv7pHqCKGRMwq8Grf6rpW6VlSnOisoKMmd7hE4hcGy7BH5VUTZkGUsZ30meBJgMySM2aty9dCHFbedF87+"
"kWrzWlzaTAZCGmb5Y5b9Afob/9oPbgK5IbDb9NPyhIStJiY5B313bCvrxPDZnqqyLkJ/"
"mAnVGXARrPWxV9x7NfnK07iVUZQ7Eh2x4kwKFJKpuIcG+GKpMPyh3FMI4IP0QJDL9e2sHH7CmtZoxtuCOZcLE8MfQ5/"
"+aYdvfddw+MN84IWiUMdnHWSx9mOjDpZbWCze/CVLgfIgscA1KOyqB83KUV1krfvv7ZpliagXDQBpkBHJR3TdIGynxcoKLMWqGU/"
"AjtjP1CZyFFlw1i0rrLfRICJUjWc156EhBzPh3yvTJOOrJ+mDNmFl/tvGRhvs7tZyWTGgNNejHoxK3HS50fRaaZ1t50jKiyEWIUD3fz7/nEZ/"
"jhcgmYWw7ZNZg45r6D7p4QCbaSv4ug8OjRmojMzDkZ4bD/"
"+Xxd35Der1XB8iacTdNtfu1BioT8P+eNSK9NITrVtbl+CeJkj4hm+gOSdKsMtfR5QpGTmklfZRVm9IR//"
"BZTTu35XJk1Iso+DK9UGXQzDpVSChouFo7Qt34cyNWfgT1mq+vYbPcDIOwrIVb34KiaxO+tP1f0+"
"uARYTyImrJz2IchLlIL4OtgKDalcVfUPS0IcUzvxguC74NMw4ZR0tO+"
"XmB4E5Ob5qNzA291p5ZsIn6blF4661oDE6CcItyEJD8z7fhWKlQnevgjSC5+E7yzTH/"
"OHZBobmNyWF4+Gk39c90YqtUVskfURQ1rjiBSr84Cako5vZKI0VW0zg65Q7vv0qujh6tMqMJDTTChzQTlmZFm+eMI7vSFMb/"
"qte9NHiqajYG5fqcmubCwqdsxQeJMz8Byfi3CqfP0WcoyV9HX8X8VRkF69maJzRHNSmUC/"
"P7WoUW79gAwIeg69OjuQpZrPdybJTNTZ0c5+ijSXGSA+rKNDNB2Xa6uCWkkNtfMjZwB/"
"PaNu2lAlXkuKtEDETuW63wcWTHMMadwVS7uVZ7tOfiOxIbRxfCjp095xqA+9Mot+qF0i4qi0/"
"VUmjaP8xkuFYXmcNESOFKtOINuQxbGk+wX1xxrWvklvIlLOeEY1OO4kOe4VLg9TEf48Pr/FEHnnejXG6ReVHI/"
"uSra6XCzXt4y8i7bPYsNXUlsKDtKkynjclWKWq1CuSGJ42QdRDqrIg3cob1gOVZgjgFFloEqyZ3bzq8QX2XP2Uf9FpFMh6n9E3rJI5TE0DO4lNEOLs"
"dDi11yGFnMPxhI92P3bkSqTiqS1gJvoPZsIIsJanaQMD6WLDY7ajr6j+"
"tIgehqVBPLUgbDEKOKy2acIknat7v3p5tWUob3F2I9HUDDQ2vdvxeDzVC18QssZx1OmxzQ6FYLcdP+"
"YXaw796hGoKcGLjUb68AXMX5d6cwwrgQFMYzDsGxh5Ev2jRe+3fxmQclO1iOOSwhAOrW/oLRLideFyZz5taRjhg7RyEvbSdLY1BJyxJTL+AKk9/m/"
"+QR1RUGQYbUOn4xYD3mTpeBHJ16rXvRVZbQ0T5M097mG7dJpb8TMLl18kSKJLeOdsD3No4UqhHz9kB5oH4iCh4QI84YN8urLb19KblcI7QmRVF4UZ6"
"zZWf02teRFSflUmgXrnQak/psMttftzsU4/"
"8hWqNu6vdDBHnXyJc196LEi7OGY9si68feBHDtAn+wfdPk9OsYMueq0Dhgc0Gt0XOkUO1MhzqZwg00bW/4VOVhO/2cq6YCB6l8r/"
"KPxjHZOBAcob5FcA9bkBRqEDttaIbf4WZPdxGx6nDjULZ30NJkHc8j3CaTG3sLyCxPXk2pnMmjtT8csQzzRniVas4PbQfhPXySsWPCJ+"
"pGefADdEZK1nprxxaOd9r5y6Db41UxuFqf8iqWvsH0tFZ57WJoSgrrMLCO13pH5w2emK2GWaPWH712QRvipEbKIRUnHJqCURLF8pKDOg4gHJcazCcU"
"tLil/EPgYmlVk6jyw5VNbZI5sn8LLSApe1EN+J2v7oMkWEzp0VHLJzKc/AiWLp+c+CUOBlFBU2jO+f/bMzVCgqNJWYGbCGdX2jXha0MBl+W/"
"IteUFsyaqZcyZLE2qQ2HK8677mUNJFSoaPvSTmxpoeHZnjHluuIcWIfWYJ4O8ZlXmMS1krUbZllung93W25Yc0DqE26HLZAT/EcU/"
"iuxXMSVt9mzpOULLmvmQloPIWvn25LZBZaIkAD62a/"
"V+rydpVu58OmXiD6ev5470oBCrES93AOrg+"
"xUVjrpc34gr8SnXcRAGeOTY4GRWvAOxq9gKhQhyF7DseiM7azcXzXKx7sdN05puCIUN13JaDmorKy42Bpd/X/"
"GYXVZyWO0BIJxs35T3U0RdAAxOzALwD0IxazvK/HhXfQhrb2frFmRVmXVzvbbMwx/4B3jnBNCQYIm0Km+hpCymTlBYJQLHMe/"
"RqSfEkbh62ofxy+SRsMYArcGRgltqM0tP0+rmz8cO4pGZXXL0b9viD/BlcIhgvnj/w9EsFB80A8/L3JkyJbNG6SSgTH7viJrTdAv3BE6/"
"xVE0IWDPqoU/NnGc2/NJ7//A5GzhpaVlIQV7MuLZXW+mwjcT8yl36JGef+3l+x8/"
"X5+wwGVmMGwSIWvG5Gatyq7gm0VNOQyWHGVKFysBBgF335vpkK7JUfI7WY7YxpgNYX94DwrSOjNtkORKbY4qfEj4w+"
"iA4cCFQNOZPfk9TBM9MjOTKAF0Ky0MhVE9/"
"72U9RNAM1+rI8mImTJpCZN67JjoBohbuB91rC9LFR2o95i4tgS7uHbABj67rDO+"
"2P11E6su0r0b8zXkHa6sk6DGYlyUPiD7j40uV9jTX3hfsV1D7iLypNpXWVjvDTfhDW92Upp50H8nYb4XyVFHo2BL5KrZHpsHYM4FFJZfcwRX2n0tDj"
"sCVD0jMVKBeom3CtKMtFFkFYfwZE/wKdTEjCdvZ51IDUteDLagSQtdFm03qch/"
"1jJoMneD5hV1EbB1l7EoGY19Sj53Oy+TETDCurHCO1xMT+3yTTdphK/"
"QweO9f7oUhuOW3aqt9lE+mggbHYOo3cHDpSKCTGst2buBJiPupLoXtcV7w5bpRluTIVt/"
"DXUqhNkZ763RJ0CCHX8DP2ARQIU+OL0og8NTB4pdpncOFiUkmhD4YOviNTn2uh9naxuc2YPP8VfASUDRdN+"
"d3MWUj4vtkXdGHKP1P7dy6KonOgss2kCT1eO5NdEEWGjCtMD7CsM8X+YVZAdZU7MGOamtzBGzeZzyP+jdBJ3eLl0Sqi73YkGt68sKSQ3G3Rr0sube+"
"Pud/8LMP9PzEg13AB6Z92cG0V8WmIn8KbOKRXziWNXg4rWNNYW5GE/Zsobxka4RufAhUCsz2JADImveFx0sn+N43Wd/"
"KmzbNQbsimMhmXlad70CXFaRc6CUP1p9vb2zwokW5T1UJ0zUgbDy1BTM6mfnmvWD4weTavswLUXonIxhSvxEP3OJXWDY6lfWYO/"
"hANyGHJRu46lD4m0KHMomR3PLCuo5mg5EGTdSe+FifRhM8P9gvpZ5TyqF4J3GJSlCudp7CmfSuhgPN9fYWIb9B0n04D20sPvP45grRvNj5sg3Jak6+"
"bBhLdqGyNl6ePO0RHUy5woMIVfM3cWvc3WfVwWYj4I+Nb8JafMo/0WO2fe0XjUjZVmzS9I5ZyrxTjBKl5VTQart+qc1hU15LaD4U+/VuuaF7/"
"qWtnUzZjRB4kv/"
"TIaP9mVO2SQfR5aYjqlWRC6hHkvjt1h2WHRqSSZq97EUmBH+"
"IaTQiuvuL9zMIPUCcL4TTLtot0C3e1u04D7Ur7P5GI6xfSw6teaxNcBYiQnVzpL7IHOZWYvvBGZNPznx+"
"JcjzM1q5lmEYr6sZNXuPM8x6wOsL7uzGGc3ej3KJrubXSJ2lxlogDNMyfa9Fgb2y21BlTNPDiSzgVODmNjr6GiTP6Pi+"
"991ivIirGVKe9KHaTq755IJzqscUkFDu/"
"TppL59J6seMaQPrqKs+JwQClvxVbeGdKsoFL2RVOeB5dvnDZ6yaHUq2RtW9QjyNxb7cderCh5ry+n5+5y+d+"
"EUsqnjaa8Rr83nQpgf81lOk3ty4KPSBAtOE+2l8Z4Vmk5GDGS3YBcqCv5/tqUN7rUxJ/ZkAWR4qixomL+u2y2/"
"UOhyK4wvUXxauXseraOFROoZGHyVMkeLoroi3eYZh3qsJqd+uIBlSkor3Cu+RYtNaEDvGhYQRmRnuDo9DbVf5SRt6lzrj/"
"w3cvMUJxlhlStMWUuGyjw4bV306JsOQvZeZa3NhUTnCCC2zBuPQyeUw4xZlD34nzzNIozP1JtU/"
"kFfmCZp50EpLqyVm00JD8V4omUP+k6XFgBTp73thc5Ys2fbXJiE/wL1OQ9yAQ/"
"jcZ0iO9nFHYNHcZpTFzGlb3LZ+V99u9GcIOjxdJCVv+eBMB21ZIvzMHDg9gUI09EvJy0nHLEkGpZ0Uxq/"
"vl6PPAUswq4GcrmjDFP3dqWdqY5ZavZjZiTiRRFYFZL6++fdeCUtQjVbQjI1B/b3BDq3ltaPUKGMm8j5FlwBo8OjUkYEtrUE4Z0QpCiTKAslT9gNh/"
"jZyNwhM8RwcYF5p5TT4oIHkayKac95NuSmH8HHw1dWfJQkRHTfoxMZ5W3LSAU6L4418P5D8rdbVS+"
"QkoiYExjomgDVk8D8EwLYaQhgKOCD15RC6R2KOy/VOSsOLLEaBOdZBeuVcnXFH7wfE4WdprH2N0xaGKpsSX2t7INGwxAwWltbUzmXaN2Q8O3HNo/"
"VVhpLqaoBucAVOgif0C6Q9vDDCXP+PbO2HB6uxudk4bHfkGOpCs1FvBndk8eHdBqBLLpJqicv1uh3Xz8iRpNXun5BH8SpdDJjXQMXNdN/"
"R8nE1MS3Zt4Ec9SVSZLvFG/23y3O1A1lLynTE0Llki+ns3pnby00kR4297Waimb4iBLTu3LLIvSr5TArjGOmDQ3lRRlPZoB87Fg5wFSvd7HHKU/"
"WKAzU4YiCB8Plc8+JJo9oHxugmsyBEnsnJYMLkyNxFzfvtKDaKHyv96e5jgunUjOE5cPl45hlK8OOk+"
"v4bPB8PqanUFPPzPi2PZSZ8aF9ISb0zCnGdXVzkMQ7aapmCWBcN43LnUcDbUHNiPZI3sZsPJnJtX8WEXgzBPNA5hjpKCezPzb+"
"rkMCvLzZIiboYwJzPIxWAZ5tFtMDwmKjyDVQA93krtINXoZF8QOHFYTzBW8zemPGapWPa0coAre9+9KJNMQRssSg+y+"
"mAudtnpYAeOhjgK2kA9wKjQytRhXl2wlR13QoeqhAQBhomlPOpScfly4iXskQFF2A2JTblUAQv5ZGl/"
"LyQB0Dcmd6VRcx3tk888wtL7K3gWKXk21NFI14Zja4/"
"3QH6rInL3Et002Yrujl7A83yZntGZzZ75hfgKh22ZEZFimaaD07dR6xv9iONeU+WkuOFJxnt3ER5XzX2Gp6BFgREujNF9/"
"kx9GP6hn7ZHTmceAesR84VT0Wa2pEVGnACbLAk0YucJXUmWT4yctFJyNUVfGibO53wVLat/iiANFX9u9Hdp5KxBgekgHXrNyx/"
"Msw4S53e73nMckP8BuRALtGIkl5Bgo6f3lMZPENtpku4NTqXFZaeFgjaOy9frPeDDDOinmP6SmdOsi8ekF4jAU9ix4jAYtS+E+Jy9baVQP+kF/"
"gx9MQkFwVuZCpM9ZxgteElStBY13shhfCaeufGIQe0UjUGs5VdnmFxiSwVxME+"
"fwkKxRMazAC9ycUGih0r0R9mpIUC2bzOmezrtLzUccJVyCg1q48ew3xGAfN5whzowPGS/"
"Lz1QHs+"
"mt2QGoDjtpZSSHyMcxJ4zcxcleXUWv9LLTfRkcyqhR3TG2OJYeA1FelKS42lDuaqrMbTET0XC4oUjYzUQAu0HBnJsa8gseM1YI5R85LknFf3rQYg82"
"DO1GJJEUngKeEb8RB6uvCcJ4xs6t5+X7vFEg/"
"1bWl82jRsCji59AFMuDZGBQ+nr3jKTf3wFKtJvPD5bMxPdbYdN9nkyn5yooMLQUtJXhSHgy1SpCoW/UshSbOU+o9/"
"i+Qf3BbhgC36kU0zH8KyPlq0U7a+jJc0BCyuZEUD1FlHBl5Iu5opdPxXB29NzDUIMozrfy+iGJ+e0wZUbkV/"
"25q+7PHiBQs6mV2iSPOxSK2aGKxv4Ti/Pav/4WsO5OyiFt2cPJkmMZaQh8mqvcY+1ewSp9rRiM/"
"yHLSExz0mMN3ENGeQGsSwNjIzuIJfX5SXI7rsRMLJUTKnbWKcNT4hYthBcMkmLbGZph5i35+qiZvhSkaRCrhACNM+MLHkO28TFyRyYvB4Ev8ck+"
"Pj76loJ0CU4JcsvIwdY7adXWEacSNPfP9oWAyMd9OUUoem0duSRK4Nsu1Cs3mQUITE6top4qEs2qG3eregjwDQYqSQWPXjOCop2VN1YlUnsv6d8Uum"
"nFmZkKvFWZBeUTs90VuEe9MWQpkj8kiA16R+s/uKByZP4vFjlBeDwn/RcvxL+K6fALw8e6QTE+80ZzXN/um85Tasidi8AiRS1JtOh+5Z65/"
"gesyNh9GnhONOJz2fNr2QmWJ87VfTmKQqhLI4eSOTDB4dgRaVOAbyfh1slzYQQFwfh5PBqixs/oRj/V3gYfW/"
"kXqcCfINgebEbhef8mqBejMpwzZfF9Blptc1h93Z2oKzESD+gYaxpSnzcVdXN38QY/"
"pBNkgDsok0+qsh7pe2Ansr2bPTcj2wLDkqBwHW3M6xGYowXWB43b8CGbLjDcGG3QQSJ3vjCOK8JqOkci2d+"
"5YihgrYEBcPBRevkOHqxejYHdxRGTWNhNWPDXXqFEyS6iP+7x1tawvC6An702tqDGf3ADMYP5CVSCdH0UcwisOHXjb7aH+PmQFCqn/"
"YYB2yvOwRwj9K1jB9T9n+0ZASF3c71tyS7cmwIfYw+Xmmkg1SbQEXKibnWxtnX2urZlRACJGv8zKCUeK8/"
"N7zUJRwNteIrerYy+giNU2dFtfvwQTK6uk+"
"4jsuh0J8ovZT1AaDKetaFgBJ1xmWCoZZZJ0wuNJbfzOFfnavTlmDJRlqAXPeQ1F6mogTwkSSmPKSjpG/rqDhQ3AwK/"
"hKnYkJafUcyvU0VTIapAgxXM1bPj+atxRfNBaa8h8+emoqPOIbDWYuwt5ObCzjcsyd401t0sUIoy43rBPRVrR+64VP8aafisJrb6x6/"
"kT5MtSSdOTY9n+dK+fbaf1j/EHfEPI2zyZWwQdyRgxo0eqYnuTcNQLOxS0k+DQYD1C557d3ELuKKopZGUyvgWP86oWPX4DwzqK3uqkMilJaeq4/"
"vCNP6+viUSIonmSjTxDZZj/WJR86UOlnWebpYmSZwEklU9tX6ScaaScfzTmjPe/yXgo3/"
"6GIC4XtXl8b7sb6QEbec+8c5+iJXrNhrYu6Bc99S1X7RVwFV9TjqAwRwjmZQtNZiK83/"
"UNj54ZwrHU2W5TSOCJ6Zr24yZzFkeyNvFzSwj4jeFrc0GKjRPHEJwXzAv4w4uvgADD5yRoNLNq/"
"xNLFEsu0Mt8hjLkEIBI1JLXfkfHsKZccjtJGbjRwiI8cFvc1PeKER0qeywNq/tL8Rzx/iXDeuqavrogwfLOnR+aRN8N66LZys/6rAcsSTNfHoXf/"
"7uiJS3rjuzcuWpPpxaPSJV3f8ZL8++duB80E94r4vaBJSHQ946FRyU11aj2x2mnyTl8o8oWndW5UcvFJjvTeBdCYHUTusH9G7FPTG9/"
"efdh1uHnXABe8EwdihKnAAGA5Glws9rbA3qjebj8q17Ade+M+01LIInfq8vpPY6Fn9s8kqqv9lC4rRmgECgAMQ86R9bbymaS/"
"8qKlzh22OGztZl2b77brFtvXxSQ7+u2gV8I8nef0A2SO/7OxxDRb+KiRgstoB5zMIEF7GdTZc5cwF+7Zf3qlsffJKE3/"
"2Pma4ITXT+OecySK3WV2UpkeYwiUduPXxq/LfnWK5IiKYtvBpeT0LVX/"
"9fXrvBcwUjUn6NOCfkwpUj2JjVC5cPeMESncTJoExArTrhTTD+gHSvBXuamgUhmnLR+cXc7VWV+x2Pj5ZvlJcpAO0V1GEya94o4rneF9vJ4kWCKda/"
"z/L3lEguEh8mK4ZuRPgeWiOKg3fQsSKIU2bVW9Kqs/avpAnrsY5Xov4bVrV4KzaLQkp8wZCwgpoAYWAFm5s+rTwuM0TPas8eWsSb/"
"7swP2H3rVlIEtb8wIODDBvWOw3Fn6KoqG+WkjGR1oF3YlqD2x1AswpyH1x29n52YSuKDT7LXIUyNfeX/"
"vRIU6jtq+DxE93u4cngBwDOVzVAQq2j5kflq5+"
"Y0JY3Th7JvFinclWhlQrZWOFmMpQOB9tObkt8fWn5uXVMgUX4QWtI10mw7TqYcvkzpfcz2FJyYN54OAbtefRGmbvGqqPZd+"
"lodOpyMGe7RKKUBJG8LMBtOkdmpsy1tjxsQe3BAydXH4ldwjgyWaw3SRryJIxu736T+wK9AoFLTpE4dxnunRndFzQRzTZy20iTmX+"
"3FD6ENJhfcuFW9E1w1fA982Dy+jh9EEOIe1KJe8TcLW/UIeeUFQYyhqLkEEW3cCj8/"
"CoK49aDvfanO0QhdCAqQf9WH0MsGOJEL5w1OcRvchd9HgXGeyvg3DVM8vQzqCSwC87OU2REEr9iRMSfcxUAVVhcoV0N7UBceYgUsiPqR6pXnH6oPPB"
"AYI9aBYkogPTgb1o0+WFRw9gyx0D6TG0L8h5t54EpMpYm2qVZejvw9+"
"Ey34KOlfTMJaL0wxenVYen0JDgbbeg7KyHA31Gnbz2iLnH2rFE3kPAj0XjW68SNY0B7u2XMJUs1qPFwBhLGEwRwN4Coo5iUo7mNn75yhMNNnZtqoEt"
"5JbV7kExbML4ho24GjazqZxeslDyJJcdb4NCq06FDIHO/1o4eK9Ui/"
"t7+rWD2ChLfTiDr7wV8XgqBqHbTNagFN3Ij7OK49yCsFfPu7U54MMwxRbslqVwsG+"
"Frqokly2o8iTQVGa7JEqJR824PuMbTYhobKR23pJRAEMDr5oImipjO8/"
"pPyeNRTBdx2oVnRc1JDITeDwlJZcrUNojHKuEZTLXx9UjomT1GlgzJYjmxhQrklLiu+"
"LVjSHsIWdElLYFFG6d0JlM292TpgsmOUapwEHNrEogdcaaqPqcyvsKdUNFF2WqgZBo5ZtBeP0blD2StUjwrAJmcdDNFruhyuJZ4Unowg667e20RDp5"
"5c3eyMqZ28mTZ3IbD/g5edMKDtkToX2QIrx1qk/HmBa6dNPMIpf3iFmT1jZx9mjQ0hE8A/"
"w2JW9rFPQQU8eFHXoIt5bDQ0Won66YodBTzHtGAaNxd1h1plysaM2AikXqd8R4NOntJ2mwwk7mGV+RBZ70pTj9mpwJOYGAvFLSFnb4/"
"2x6GQIJyCMYi4R7HgbgJE7ZOXHACNttN1swtJeeKyyHalHcRnp0qxUZnc7jSsPeSO6YdY1PwG3CRf3zAP8o+"
"yU6n11wsweIu39N5MeL4CoCaR7tfnU+25I0Q86M8GIBGyqAdLYJfBD8K9Hp8R+aKCAOaauUCYrm/"
"KSYFGffiVA2ZTWd4Mjgc+DP+BkBHDAQH3ckMVz0RYHlV6N7rKPBAH3vcZaqyVH+ROdSyGeYy8pxXA+ohoYqjKtdnqBXBwCFTnK8+/MNmwIljErYj/"
"mDy+j+"
"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"
"FPj386FpIR6lhZ999R2Fpf79H+KO4cwF8m/"
"EhXWk8irqHcAdQO5ctI7IiAARLkkpJCrS0Xnm7zZncjDmWBbsJXmW4yMCzTTeTZButuh30wuC1PJWlgvdVQASe0oM1jybVfCRlRn0jMRWRcyvNROvo"
"DutinVezcIAX9Ktp7C3d1JSd0uh6VTaD6L3l+ZAKA5by736BeBMZfSVqe2G3j577QqiVDCKT6D3uNA2x3r4cnT8oWdGcuy297H15I83LLp+1/RDo/"
"745K02B3ejdgO6adv3G3DEio9yWEyaIQfFSL12J3Xt4JG0hhD5d3GpjizCzTx+SrBcdZJ/"
"k2PwBeYFKhFTAWQkhDDdyAY0sMlGDQ6Ev1KaT1TFqcOxd22FRaNCj2DLwK70uFsnJAt6vzcVDxrxqI=";
const char *sqlite_sample_db_v4 = sqlite_sample_db_v4_arr;
const size_t sqlite_sample_db_v4_size = sizeof(sqlite_sample_db_v4_arr) - 1;

View File

@ -14,5 +14,8 @@ extern const size_t thumbnail_size;
extern const char *gzip_bomb; extern const char *gzip_bomb;
extern const size_t gzip_bomb_size; extern const size_t gzip_bomb_size;
extern const char *sqlite_sample_db; extern const char *sqlite_sample_db_v3;
extern const size_t sqlite_sample_db_size; extern const size_t sqlite_sample_db_v3_size;
extern const char *sqlite_sample_db_v4;
extern const size_t sqlite_sample_db_v4_size;

View File

@ -48,7 +48,7 @@ TEST(DB, binlog_encryption_bug) {
CSlice binlog_name = "test_binlog"; CSlice binlog_name = "test_binlog";
Binlog::destroy(binlog_name).ignore(); Binlog::destroy(binlog_name).ignore();
auto cucumber = DbKey::password("cucumber"); auto cucumber = DbKey::password("cucu'\"mb er");
auto empty = DbKey::empty(); auto empty = DbKey::empty();
{ {
Binlog binlog; Binlog binlog;
@ -71,7 +71,7 @@ TEST(DB, binlog_encryption) {
Binlog::destroy(binlog_name).ignore(); Binlog::destroy(binlog_name).ignore();
auto hello = DbKey::raw_key(std::string(32, 'A')); auto hello = DbKey::raw_key(std::string(32, 'A'));
auto cucumber = DbKey::password("cucumber"); auto cucumber = DbKey::password("cucu'\"mb er");
auto empty = DbKey::empty(); auto empty = DbKey::empty();
auto long_data = string(10000, 'Z'); auto long_data = string(10000, 'Z');
{ {
@ -147,7 +147,7 @@ TEST(DB, sqlite_encryption) {
SqliteDb::destroy(path).ignore(); SqliteDb::destroy(path).ignore();
auto empty = DbKey::empty(); auto empty = DbKey::empty();
auto cucumber = DbKey::password("cucumber"); auto cucumber = DbKey::password("cucu'\"mb er");
auto tomato = DbKey::raw_key(string(32, 'a')); auto tomato = DbKey::raw_key(string(32, 'a'));
{ {
@ -195,13 +195,13 @@ TEST(DB, sqlite_encryption) {
SqliteDb::open_with_key(path, cucumber).ensure_error(); SqliteDb::open_with_key(path, cucumber).ensure_error();
} }
TEST(DB, sqlite_encryption_migrate) { TEST(DB, sqlite_encryption_migrate_v3) {
string path = "test_sqlite_db"; string path = "test_sqlite_db";
SqliteDb::destroy(path).ignore(); SqliteDb::destroy(path).ignore();
auto cucumber = DbKey::password("cucumber"); auto cucumber = DbKey::password("cucumber");
auto empty = DbKey::empty(); auto empty = DbKey::empty();
if (false) { if (false) {
// sqlite_sample_db was generated by the following code // sqlite_sample_db was generated by the following code using SQLCipher based on SQLite 3.15.2
{ {
SqliteDb::change_key(path, cucumber, empty).ensure(); SqliteDb::change_key(path, cucumber, empty).ensure();
auto db = SqliteDb::open_with_key(path, cucumber).move_as_ok(); auto db = SqliteDb::open_with_key(path, cucumber).move_as_ok();
@ -212,7 +212,7 @@ TEST(DB, sqlite_encryption_migrate) {
} }
LOG(ERROR) << base64_encode(read_file(path).move_as_ok()); LOG(ERROR) << base64_encode(read_file(path).move_as_ok());
} }
write_file(path, base64_decode(Slice(sqlite_sample_db, sqlite_sample_db_size)).move_as_ok()).ensure(); write_file(path, base64_decode(Slice(sqlite_sample_db_v3, sqlite_sample_db_v3_size)).move_as_ok()).ensure();
{ {
auto db = SqliteDb::open_with_key(path, cucumber).move_as_ok(); auto db = SqliteDb::open_with_key(path, cucumber).move_as_ok();
auto kv = SqliteKeyValue(); auto kv = SqliteKeyValue();
@ -222,6 +222,42 @@ TEST(DB, sqlite_encryption_migrate) {
} }
} }
TEST(DB, sqlite_encryption_migrate_v4) {
string path = "test_sqlite_db";
SqliteDb::destroy(path).ignore();
auto cucumber = DbKey::password("cucu'\"mb er");
auto empty = DbKey::empty();
if (false) {
// sqlite_sample_db was generated by the following code using SQLCipher 4.4.0
{
SqliteDb::change_key(path, cucumber, empty).ensure();
auto db = SqliteDb::open_with_key(path, cucumber).move_as_ok();
db.set_user_version(123).ensure();
auto kv = SqliteKeyValue();
kv.init_with_connection(db.clone(), "kv").ensure();
kv.set("hello", "world");
}
LOG(ERROR) << base64_encode(read_file(path).move_as_ok());
}
write_file(path, base64_decode(Slice(sqlite_sample_db_v4, sqlite_sample_db_v4_size)).move_as_ok()).ensure();
{
auto r_db = SqliteDb::open_with_key(path, cucumber);
if (r_db.is_error()) {
LOG(ERROR) << r_db.error();
return;
}
auto db = r_db.move_as_ok();
auto kv = SqliteKeyValue();
auto status = kv.init_with_connection(db.clone(), "kv");
if (status.is_error()) {
LOG(ERROR) << status;
} else {
CHECK(kv.get("hello") == "world");
CHECK(db.user_version().ok() == 123);
}
}
}
using SeqNo = uint64; using SeqNo = uint64;
struct DbQuery { struct DbQuery {
enum class Type { Get, Set, Erase } type = Type::Get; enum class Type { Get, Set, Erase } type = Type::Get;

View File

@ -71,7 +71,7 @@ TEST(Mtproto, GetHostByNameActor) {
} }
}); });
cnt++; cnt++;
send_closure(actor_id, &GetHostByNameActor::run, host, 443, prefer_ipv6, std::move(promise)); send_closure_later(actor_id, &GetHostByNameActor::run, host, 443, prefer_ipv6, std::move(promise));
}; };
std::vector<std::string> hosts = {"127.0.0.2", std::vector<std::string> hosts = {"127.0.0.2",
@ -177,11 +177,13 @@ TEST(Mtproto, config) {
run(get_simple_config_firebase_firestore, false); run(get_simple_config_firebase_firestore, false);
} }
cnt--; cnt--;
sched.start(); if (cnt != 0) {
while (sched.run_main(10)) { sched.start();
// empty; while (sched.run_main(10)) {
// empty;
}
sched.finish();
} }
sched.finish();
} }
TEST(Mtproto, encrypted_config) { TEST(Mtproto, encrypted_config) {