From f8ea85a5514dcbfac606d5b4bdf6b897f9567f5c Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 7 Feb 2020 21:13:49 +0100 Subject: [PATCH] Performance improvements --- composer.json | 4 +- .../MadelineProto/AbstractAPIFactory.php | 2 +- src/danog/MadelineProto/Connection.php | 3 +- .../MadelineProto/DataCenterConnection.php | 4 +- src/danog/MadelineProto/Exception.php | 2 +- src/danog/MadelineProto/InternalDoc.php | 23 +- src/danog/MadelineProto/Lang.php | 206 +++++++++++++++++- .../Loop/Connection/CheckLoop.php | 4 +- .../Loop/Connection/ReadLoop.php | 8 +- .../Loop/Connection/WriteLoop.php | 6 +- .../MTProtoSession/CallHandler.php | 1 + .../MTProtoSession/MsgIdHandler.php | 85 +------- .../MsgIdHandler/MsgIdHandler32.php | 142 ++++++++++++ .../MsgIdHandler/MsgIdHandler64.php | 127 +++++++++++ .../MTProtoSession/MsgIdHandlerAbstract.php | 66 ++++++ .../MTProtoSession/ResponseHandler.php | 4 +- .../MadelineProto/MTProtoSession/Session.php | 18 +- .../MTProtoTools/AuthKeyHandler.php | 2 +- src/danog/MadelineProto/TL/TL.php | 1 + .../MadelineProto/TON/ADNLConnection.php | 4 +- src/danog/MadelineProto/TON/APIFactory.php | 6 + src/danog/MadelineProto/TON/InternalDoc.php | 55 ++++- src/danog/MadelineProto/TON/Lite.php | 7 +- .../MadelineProto/TON/schemes/lite_api.tl | 2 + .../MadelineProto/TON/schemes/ton_api.tl | 22 ++ .../MadelineProto/TON/schemes/tonlib_api.tl | 138 +++++++----- src/danog/MadelineProto/Wrappers/Loop.php | 11 + ton/lite-client.php | 1 + ton/ton-lite-client-test1.config.json | 10 +- 29 files changed, 783 insertions(+), 181 deletions(-) create mode 100644 src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler32.php create mode 100644 src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler64.php create mode 100644 src/danog/MadelineProto/MTProtoSession/MsgIdHandlerAbstract.php diff --git a/composer.json b/composer.json index 6cf0763a2..bbf4a134e 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,6 @@ "require": { "php": ">=7.4.0", "danog/primemodule": "^1", - "danog/magicalserializer": "^1.0", "danog/tgseclib": "^3", "erusev/parsedown": "^1.7", "ext-mbstring": "*", @@ -31,7 +30,8 @@ "danog/dns-over-https": "^0.2", "amphp/http-client-cookies": "^1", "amphp/uri": "^0.1", - "danog/tg-file-decoder": "^0.1" + "danog/tg-file-decoder": "^0.1", + "danog/magicalserializer": "^1.0" }, "require-dev": { "vlucas/phpdotenv": "^3", diff --git a/src/danog/MadelineProto/AbstractAPIFactory.php b/src/danog/MadelineProto/AbstractAPIFactory.php index d7bb3dc0c..2075c1563 100644 --- a/src/danog/MadelineProto/AbstractAPIFactory.php +++ b/src/danog/MadelineProto/AbstractAPIFactory.php @@ -97,7 +97,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct public function __call(string $name, array $arguments) { $yielded = Tools::call($this->__call_async($name, $arguments)); - $async = $this->lua === false && (\is_array(\end($arguments)) && isset(\end($arguments)['async']) ? \end($arguments)['async'] : $this->async && $name !== 'loop'); + $async = !$this->lua && (\end($arguments)['async'] ?? ($this->async && $name !== 'loop')); if ($async) { return $yielded; } diff --git a/src/danog/MadelineProto/Connection.php b/src/danog/MadelineProto/Connection.php index d77cc9009..c13c03b5a 100644 --- a/src/danog/MadelineProto/Connection.php +++ b/src/danog/MadelineProto/Connection.php @@ -21,7 +21,6 @@ namespace danog\MadelineProto; use Amp\ByteStream\ClosedException; use Amp\Deferred; -use Amp\Promise; use danog\MadelineProto\Loop\Connection\CheckLoop; use danog\MadelineProto\Loop\Connection\HttpWaitLoop; use danog\MadelineProto\Loop\Connection\PingLoop; @@ -116,7 +115,7 @@ class Connection extends Session * * @var MTProto */ - protected $API; + public $API; /** * Shared connection instance. * diff --git a/src/danog/MadelineProto/DataCenterConnection.php b/src/danog/MadelineProto/DataCenterConnection.php index 22ba09499..ba80f7a29 100644 --- a/src/danog/MadelineProto/DataCenterConnection.php +++ b/src/danog/MadelineProto/DataCenterConnection.php @@ -479,7 +479,9 @@ class DataCenterConnection implements JsonSerializable $count = \count($backup); $this->API->logger->logger("Restoring {$count} messages to DC {$this->datacenter}"); foreach ($backup as $message) { - Tools::callFork($this->getConnection()->sendMessage($message, false)); + if (isset($message['body'])) { + Tools::callFork($this->getConnection()->sendMessage($message, false)); + } } $this->flush(); } diff --git a/src/danog/MadelineProto/Exception.php b/src/danog/MadelineProto/Exception.php index 05b6723c3..97e9d7008 100644 --- a/src/danog/MadelineProto/Exception.php +++ b/src/danog/MadelineProto/Exception.php @@ -40,7 +40,7 @@ class Exception extends \Exception if (\strpos($message, 'socket_accept') === false) { \danog\MadelineProto\Logger::log($message . ' in ' . \basename($this->file) . ':' . $this->line, \danog\MadelineProto\Logger::FATAL_ERROR); } - if (\in_array($message, ['The session is corrupted!', 'Re-executing query...', 'I had to recreate the temporary authorization key', 'This peer is not present in the internal peer database', "Couldn't get response", 'Chat forbidden', 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', 'File does not exist', 'Please install this fork of phpseclib: https://github.com/danog/phpseclib'])) { + if (\in_array($message, ['The session is corrupted!', 'Re-executing query...', 'I had to recreate the temporary authorization key', 'This peer is not present in the internal peer database', "Couldn't get response", 'Chat forbidden', 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', 'File does not exist', 'Please install this fork of phpseclib: https://github.com/danog/tgseclib'])) { return; } if (\strpos($message, 'pg_query') !== false || \strpos($message, 'Undefined variable: ') !== false || \strpos($message, 'socket_write') !== false || \strpos($message, 'socket_read') !== false || \strpos($message, 'Received request to switch to DC ') !== false || \strpos($message, "Couldn't get response") !== false || \strpos($message, 'Re-executing query...') !== false || \strpos($message, "Couldn't find peer by provided") !== false || \strpos($message, 'id.pwrtelegram.xyz') !== false || \strpos($message, 'Please update ') !== false || \strpos($message, 'posix_isatty') !== false) { diff --git a/src/danog/MadelineProto/InternalDoc.php b/src/danog/MadelineProto/InternalDoc.php index adb0ea322..6d4881179 100644 --- a/src/danog/MadelineProto/InternalDoc.php +++ b/src/danog/MadelineProto/InternalDoc.php @@ -4224,7 +4224,7 @@ class InternalDoc extends APIFactory * @param array $args Arguments * @param array $aargs Additional arguments * - * @return Promise + * @return \Generator */ public function methodCall(string $method, $args = [ ], array $aargs = [ @@ -4240,7 +4240,7 @@ class InternalDoc extends APIFactory * @param array $args Arguments * @param array $aargs Additional arguments * - * @return Promise + * @return \Generator */ public function methodCallWrite(string $method, $args = [ ], array $aargs = [ @@ -4725,13 +4725,13 @@ class InternalDoc extends APIFactory /** * Unpack bot API file ID. * - * @param string $file_id Bot API file ID + * @param string $fileId Bot API file ID * * @return array Unpacked file ID */ - public function unpackFileId(string $file_id): array + public function unpackFileId(string $fileId): array { - return $this->API->unpackFileId($file_id); + return $this->API->unpackFileId($fileId); } /** * Get mime type from file extension. @@ -5310,7 +5310,7 @@ class InternalDoc extends APIFactory * * @return \Generator */ - public function discardCall(array $call, string $reason, array $rating = [ + public function discardCall(array $call, array $reason, array $rating = [ ], bool $need_debug = true, array $extra = []) { return $this->__call(__FUNCTION__, [$call, $reason, $rating, $need_debug, $extra]); @@ -5585,6 +5585,17 @@ class InternalDoc extends APIFactory { $this->API->stop(); } + /** + * Start MadelineProto's update handling loop in background, or run the provided async callable. + * + * @param callable $callback Async callable to run + * + * @return mixed + */ + public function loopFork($callback = null): void + { + $this->API->loopFork($callback); + } /** * Close connection with client, connected via web. * diff --git a/src/danog/MadelineProto/Lang.php b/src/danog/MadelineProto/Lang.php index 773b111e8..a374d02c1 100644 --- a/src/danog/MadelineProto/Lang.php +++ b/src/danog/MadelineProto/Lang.php @@ -12,7 +12,7 @@ * @author Daniil Gentili * @copyright 2016-2019 Daniil Gentili * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 - * @link https://docs.madelineproto.xyz MadelineProto documentation + * @link https://docs.madelineproto.xyz MadelineProto documentation */ namespace danog\MadelineProto; @@ -5629,6 +5629,108 @@ Contains the reason why access to a certain object must be restricted. Clients a 'object_message_param_reactions_type_MessageReactions' => '', 'object_message_param_restriction_reason_type_string' => '', 'object_messageReactions_param_results_type_Vector t' => '', + 'method_auth.exportLoginToken' => '', + 'method_auth.exportLoginToken_param_api_id_type_int' => '', + 'method_auth.exportLoginToken_param_api_hash_type_string' => '', + 'method_auth.exportLoginToken_param_except_ids_type_Vector t' => '', + 'method_auth.importLoginToken' => '', + 'method_auth.importLoginToken_param_token_type_bytes' => '', + 'method_auth.acceptLoginToken' => '', + 'method_auth.acceptLoginToken_param_token_type_bytes' => '', + 'method_account.createTheme_param_settings_type_InputThemeSettings' => '', + 'method_account.updateTheme_param_settings_type_InputThemeSettings' => '', + 'method_account.setContentSettings' => '', + 'method_account.setContentSettings_param_sensitive_enabled_type_true' => '', + 'method_account.getContentSettings' => '', + 'method_account.getMultiWallPapers' => '', + 'method_account.getMultiWallPapers_param_wallpapers_type_Vector t' => '', + 'method_messages.getPollVotes' => '', + 'method_messages.getPollVotes_param_peer_type_InputPeer' => '', + 'method_messages.getPollVotes_param_id_type_int' => '', + 'method_messages.getPollVotes_param_option_type_bytes' => '', + 'method_messages.getPollVotes_param_offset_type_string' => '', + 'method_messages.getPollVotes_param_limit_type_int' => '', + 'method_upload.getFile_param_cdn_supported_type_true' => '', + 'method_channels.getInactiveChannels' => '', + 'object_inputMediaPoll_param_correct_answers_type_Vector t' => '', + 'object_wallPaperNoFile' => '', + 'object_wallPaperNoFile_param_default_type_true' => '', + 'object_wallPaperNoFile_param_dark_type_true' => '', + 'object_wallPaperNoFile_param_settings_type_WallPaperSettings' => '', + 'object_updateGeoLiveViewed' => '', + 'object_updateGeoLiveViewed_param_peer_type_Peer' => '', + 'object_updateGeoLiveViewed_param_msg_id_type_int' => '', + 'object_updateLoginToken' => '', + 'object_updateMessagePollVote' => '', + 'object_updateMessagePollVote_param_poll_id_type_long' => '', + 'object_updateMessagePollVote_param_user_id_type_int' => '', + 'object_updateMessagePollVote_param_options_type_Vector t' => '', + 'object_webPage_param_attributes_type_Vector t' => '', + 'object_keyboardButtonRequestPoll' => '', + 'object_keyboardButtonRequestPoll_param_quiz_type_Bool' => '', + 'object_keyboardButtonRequestPoll_param_text_type_string' => '', + 'object_poll_param_public_voters_type_true' => '', + 'object_poll_param_multiple_choice_type_true' => '', + 'object_poll_param_quiz_type_true' => '', + 'object_pollAnswerVoters_param_correct_type_true' => '', + 'object_pollResults_param_recent_voters_type_Vector t' => '', + 'object_inputWallPaperNoFile' => '', + 'object_wallPaperSettings_param_second_background_color_type_int' => '', + 'object_wallPaperSettings_param_rotation_type_int' => '', + 'object_autoDownloadSettings_param_video_upload_maxbitrate_type_int' => '', + 'object_theme_param_settings_type_ThemeSettings' => '', + 'object_auth.loginToken' => '', + 'object_auth.loginToken_param_expires_type_int' => '', + 'object_auth.loginToken_param_token_type_bytes' => '', + 'object_auth.loginTokenMigrateTo' => '', + 'object_auth.loginTokenMigrateTo_param_dc_id_type_int' => '', + 'object_auth.loginTokenMigrateTo_param_token_type_bytes' => '', + 'object_auth.loginTokenSuccess' => '', + 'object_auth.loginTokenSuccess_param_authorization_type_auth.Authorization' => '', + 'object_account.contentSettings' => '', + 'object_account.contentSettings_param_sensitive_enabled_type_true' => '', + 'object_account.contentSettings_param_sensitive_can_change_type_true' => '', + 'object_messages.inactiveChats' => '', + 'object_messages.inactiveChats_param_dates_type_Vector t' => '', + 'object_messages.inactiveChats_param_chats_type_Vector t' => '', + 'object_messages.inactiveChats_param_users_type_Vector t' => '', + 'object_baseThemeClassic' => '', + 'object_baseThemeDay' => '', + 'object_baseThemeNight' => '', + 'object_baseThemeTinted' => '', + 'object_baseThemeArctic' => '', + 'object_inputThemeSettings' => '', + 'object_inputThemeSettings_param_base_theme_type_BaseTheme' => '', + 'object_inputThemeSettings_param_accent_color_type_int' => '', + 'object_inputThemeSettings_param_message_top_color_type_int' => '', + 'object_inputThemeSettings_param_message_bottom_color_type_int' => '', + 'object_inputThemeSettings_param_wallpaper_type_InputWallPaper' => '', + 'object_inputThemeSettings_param_wallpaper_settings_type_WallPaperSettings' => '', + 'object_themeSettings' => '', + 'object_themeSettings_param_base_theme_type_BaseTheme' => '', + 'object_themeSettings_param_accent_color_type_int' => '', + 'object_themeSettings_param_message_top_color_type_int' => '', + 'object_themeSettings_param_message_bottom_color_type_int' => '', + 'object_themeSettings_param_wallpaper_type_WallPaper' => '', + 'object_webPageAttributeTheme' => '', + 'object_webPageAttributeTheme_param_documents_type_Vector t' => '', + 'object_webPageAttributeTheme_param_settings_type_ThemeSettings' => '', + 'object_messageUserVote' => '', + 'object_messageUserVote_param_user_id_type_int' => '', + 'object_messageUserVote_param_option_type_bytes' => '', + 'object_messageUserVote_param_date_type_int' => '', + 'object_messageUserVoteInputOption' => '', + 'object_messageUserVoteInputOption_param_user_id_type_int' => '', + 'object_messageUserVoteInputOption_param_date_type_int' => '', + 'object_messageUserVoteMultiple' => '', + 'object_messageUserVoteMultiple_param_user_id_type_int' => '', + 'object_messageUserVoteMultiple_param_options_type_Vector t' => '', + 'object_messageUserVoteMultiple_param_date_type_int' => '', + 'object_messages.votesList' => '', + 'object_messages.votesList_param_count_type_int' => '', + 'object_messages.votesList_param_votes_type_Vector t' => '', + 'object_messages.votesList_param_users_type_Vector t' => '', + 'object_messages.votesList_param_next_offset_type_string' => '', ], ]; @@ -11099,5 +11201,107 @@ Contains the reason why access to a certain object must be restricted. Clients a 'object_message_param_reactions_type_MessageReactions' => '', 'object_message_param_restriction_reason_type_string' => '', 'object_messageReactions_param_results_type_Vector t' => '', + 'method_auth.exportLoginToken' => '', + 'method_auth.exportLoginToken_param_api_id_type_int' => '', + 'method_auth.exportLoginToken_param_api_hash_type_string' => '', + 'method_auth.exportLoginToken_param_except_ids_type_Vector t' => '', + 'method_auth.importLoginToken' => '', + 'method_auth.importLoginToken_param_token_type_bytes' => '', + 'method_auth.acceptLoginToken' => '', + 'method_auth.acceptLoginToken_param_token_type_bytes' => '', + 'method_account.createTheme_param_settings_type_InputThemeSettings' => '', + 'method_account.updateTheme_param_settings_type_InputThemeSettings' => '', + 'method_account.setContentSettings' => '', + 'method_account.setContentSettings_param_sensitive_enabled_type_true' => '', + 'method_account.getContentSettings' => '', + 'method_account.getMultiWallPapers' => '', + 'method_account.getMultiWallPapers_param_wallpapers_type_Vector t' => '', + 'method_messages.getPollVotes' => '', + 'method_messages.getPollVotes_param_peer_type_InputPeer' => '', + 'method_messages.getPollVotes_param_id_type_int' => '', + 'method_messages.getPollVotes_param_option_type_bytes' => '', + 'method_messages.getPollVotes_param_offset_type_string' => '', + 'method_messages.getPollVotes_param_limit_type_int' => '', + 'method_upload.getFile_param_cdn_supported_type_true' => '', + 'method_channels.getInactiveChannels' => '', + 'object_inputMediaPoll_param_correct_answers_type_Vector t' => '', + 'object_wallPaperNoFile' => '', + 'object_wallPaperNoFile_param_default_type_true' => '', + 'object_wallPaperNoFile_param_dark_type_true' => '', + 'object_wallPaperNoFile_param_settings_type_WallPaperSettings' => '', + 'object_updateGeoLiveViewed' => '', + 'object_updateGeoLiveViewed_param_peer_type_Peer' => '', + 'object_updateGeoLiveViewed_param_msg_id_type_int' => '', + 'object_updateLoginToken' => '', + 'object_updateMessagePollVote' => '', + 'object_updateMessagePollVote_param_poll_id_type_long' => '', + 'object_updateMessagePollVote_param_user_id_type_int' => '', + 'object_updateMessagePollVote_param_options_type_Vector t' => '', + 'object_webPage_param_attributes_type_Vector t' => '', + 'object_keyboardButtonRequestPoll' => '', + 'object_keyboardButtonRequestPoll_param_quiz_type_Bool' => '', + 'object_keyboardButtonRequestPoll_param_text_type_string' => '', + 'object_poll_param_public_voters_type_true' => '', + 'object_poll_param_multiple_choice_type_true' => '', + 'object_poll_param_quiz_type_true' => '', + 'object_pollAnswerVoters_param_correct_type_true' => '', + 'object_pollResults_param_recent_voters_type_Vector t' => '', + 'object_inputWallPaperNoFile' => '', + 'object_wallPaperSettings_param_second_background_color_type_int' => '', + 'object_wallPaperSettings_param_rotation_type_int' => '', + 'object_autoDownloadSettings_param_video_upload_maxbitrate_type_int' => '', + 'object_theme_param_settings_type_ThemeSettings' => '', + 'object_auth.loginToken' => '', + 'object_auth.loginToken_param_expires_type_int' => '', + 'object_auth.loginToken_param_token_type_bytes' => '', + 'object_auth.loginTokenMigrateTo' => '', + 'object_auth.loginTokenMigrateTo_param_dc_id_type_int' => '', + 'object_auth.loginTokenMigrateTo_param_token_type_bytes' => '', + 'object_auth.loginTokenSuccess' => '', + 'object_auth.loginTokenSuccess_param_authorization_type_auth.Authorization' => '', + 'object_account.contentSettings' => '', + 'object_account.contentSettings_param_sensitive_enabled_type_true' => '', + 'object_account.contentSettings_param_sensitive_can_change_type_true' => '', + 'object_messages.inactiveChats' => '', + 'object_messages.inactiveChats_param_dates_type_Vector t' => '', + 'object_messages.inactiveChats_param_chats_type_Vector t' => '', + 'object_messages.inactiveChats_param_users_type_Vector t' => '', + 'object_baseThemeClassic' => '', + 'object_baseThemeDay' => '', + 'object_baseThemeNight' => '', + 'object_baseThemeTinted' => '', + 'object_baseThemeArctic' => '', + 'object_inputThemeSettings' => '', + 'object_inputThemeSettings_param_base_theme_type_BaseTheme' => '', + 'object_inputThemeSettings_param_accent_color_type_int' => '', + 'object_inputThemeSettings_param_message_top_color_type_int' => '', + 'object_inputThemeSettings_param_message_bottom_color_type_int' => '', + 'object_inputThemeSettings_param_wallpaper_type_InputWallPaper' => '', + 'object_inputThemeSettings_param_wallpaper_settings_type_WallPaperSettings' => '', + 'object_themeSettings' => '', + 'object_themeSettings_param_base_theme_type_BaseTheme' => '', + 'object_themeSettings_param_accent_color_type_int' => '', + 'object_themeSettings_param_message_top_color_type_int' => '', + 'object_themeSettings_param_message_bottom_color_type_int' => '', + 'object_themeSettings_param_wallpaper_type_WallPaper' => '', + 'object_webPageAttributeTheme' => '', + 'object_webPageAttributeTheme_param_documents_type_Vector t' => '', + 'object_webPageAttributeTheme_param_settings_type_ThemeSettings' => '', + 'object_messageUserVote' => '', + 'object_messageUserVote_param_user_id_type_int' => '', + 'object_messageUserVote_param_option_type_bytes' => '', + 'object_messageUserVote_param_date_type_int' => '', + 'object_messageUserVoteInputOption' => '', + 'object_messageUserVoteInputOption_param_user_id_type_int' => '', + 'object_messageUserVoteInputOption_param_date_type_int' => '', + 'object_messageUserVoteMultiple' => '', + 'object_messageUserVoteMultiple_param_user_id_type_int' => '', + 'object_messageUserVoteMultiple_param_options_type_Vector t' => '', + 'object_messageUserVoteMultiple_param_date_type_int' => '', + 'object_messages.votesList' => '', + 'object_messages.votesList_param_count_type_int' => '', + 'object_messages.votesList_param_votes_type_Vector t' => '', + 'object_messages.votesList_param_users_type_Vector t' => '', + 'object_messages.votesList_param_next_offset_type_string' => '', ]; } diff --git a/src/danog/MadelineProto/Loop/Connection/CheckLoop.php b/src/danog/MadelineProto/Loop/Connection/CheckLoop.php index 44ca43489..ffa1c0281 100644 --- a/src/danog/MadelineProto/Loop/Connection/CheckLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/CheckLoop.php @@ -73,7 +73,7 @@ class CheckLoop extends ResumableSignalLoop } } if ($connection->hasPendingCalls()) { - $last_msgid = $connection->getMaxId(true); + $last_msgid = $connection->msgIdHandler->getMaxId(true); $last_chunk = $connection->getLastChunk(); if ($shared->hasTempAuthKey()) { $full_message_ids = $connection->getPendingCalls(); @@ -157,7 +157,7 @@ class CheckLoop extends ResumableSignalLoop if (yield $this->waitSignal($this->pause($timeout))) { return; } - if ($connection->getMaxId(true) === $last_msgid && $connection->getLastChunk() === $last_chunk) { + if ($connection->msgIdHandler->getMaxId(true) === $last_msgid && $connection->getLastChunk() === $last_chunk) { $API->logger->logger("We did not receive a response for {$timeout} seconds: reconnecting and exiting check loop on DC {$datacenter}"); //$this->exitedLoop(); Tools::callForkDefer($connection->reconnect()); diff --git a/src/danog/MadelineProto/Loop/Connection/ReadLoop.php b/src/danog/MadelineProto/Loop/Connection/ReadLoop.php index 332bfbf8d..d5a2fb9a8 100644 --- a/src/danog/MadelineProto/Loop/Connection/ReadLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/ReadLoop.php @@ -155,9 +155,9 @@ class ReadLoop extends SignalLoop $auth_key_id = yield $buffer->bufferRead(8); if ($auth_key_id === "\0\0\0\0\0\0\0\0") { $message_id = yield $buffer->bufferRead(8); - if (!\in_array($message_id, [1, 0])) { - $connection->checkMessageId($message_id, ['outgoing' => false, 'container' => false]); - } + //if (!\in_array($message_id, [\1, \0])) { + $connection->msgIdHandler->checkMessageId($message_id, ['outgoing' => false, 'container' => false]); + //} $message_length = \unpack('V', yield $buffer->bufferRead(4))[1]; $message_data = yield $buffer->bufferRead($message_length); $left = $payload_length - $message_length - 4 - 8 - 8; @@ -191,7 +191,7 @@ class ReadLoop extends SignalLoop throw new NothingInTheSocketException(); } $message_id = \substr($decrypted_data, 16, 8); - $connection->checkMessageId($message_id, ['outgoing' => false, 'container' => false]); + $connection->msgIdHandler->checkMessageId($message_id, ['outgoing' => false, 'container' => false]); $seq_no = \unpack('V', \substr($decrypted_data, 24, 4))[1]; $message_data_length = \unpack('V', \substr($decrypted_data, 28, 4))[1]; if ($message_data_length > \strlen($decrypted_data)) { diff --git a/src/danog/MadelineProto/Loop/Connection/WriteLoop.php b/src/danog/MadelineProto/Loop/Connection/WriteLoop.php index 9c565efee..1d6fa6296 100644 --- a/src/danog/MadelineProto/Loop/Connection/WriteLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/WriteLoop.php @@ -122,7 +122,7 @@ class WriteLoop extends ResumableSignalLoop } $skipped_all = false; $API->logger->logger("Sending {$message['_']} as unencrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE); - $message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->generateMessageId(); + $message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->msgIdHandler->generateMessageId(); $length = \strlen($message['serialized_body']); $pad_length = -$length & 15; $pad_length += 16 * \danog\MadelineProto\Tools::randomInt($modulus = 16); @@ -209,7 +209,7 @@ class WriteLoop extends ResumableSignalLoop $API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE); break; } - $message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->generateMessageId(); + $message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->msgIdHandler->generateMessageId(); $API->logger->logger("Sending {$message['_']} as encrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE); $MTmessage = ['_' => 'MTmessage', 'msg_id' => $message_id, 'body' => $message['serialized_body'], 'seqno' => $connection->generateOutSeqNo($message['contentRelated'])]; if (isset($message['method']) && $message['method'] && $message['_'] !== 'http_wait') { @@ -262,7 +262,7 @@ class WriteLoop extends ResumableSignalLoop $MTmessage = null; if ($count > 1) { $API->logger->logger("Wrapping in msg_container ({$count} messages of total size {$total_length}) as encrypted message for DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE); - $message_id = $connection->generateMessageId(); + $message_id = $connection->msgIdHandler->generateMessageId(); $connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msg_container', 'container' => \array_values($keys), 'contentRelated' => false, 'method' => false, 'unencrypted' => false]; //var_dumP("container ".bin2hex($message_id)); $keys[$connection->pending_outgoing_key++] = $message_id; diff --git a/src/danog/MadelineProto/MTProtoSession/CallHandler.php b/src/danog/MadelineProto/MTProtoSession/CallHandler.php index 3419b6820..64a9470d4 100644 --- a/src/danog/MadelineProto/MTProtoSession/CallHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/CallHandler.php @@ -134,6 +134,7 @@ trait CallHandler if (isset($args['multiple'])) { unset($args['multiple']); } + $promises = []; foreach ($args as $single_args) { $promises[] = Tools::call($this->methodCallAsyncWrite($method, $single_args, $new_aargs)); } diff --git a/src/danog/MadelineProto/MTProtoSession/MsgIdHandler.php b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler.php index d8c7a76f8..b610b079e 100644 --- a/src/danog/MadelineProto/MTProtoSession/MsgIdHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler.php @@ -17,84 +17,11 @@ * @link https://docs.madelineproto.xyz MadelineProto documentation */ -namespace danog\MadelineProto\MTProtoSession; +use danog\MadelineProto\MTProtoSession\MsgIdHandler\MsgIdHandler32; +use danog\MadelineProto\MTProtoSession\MsgIdHandler\MsgIdHandler64; -/** - * Manages message ids. - */ -trait MsgIdHandler -{ - public $max_incoming_id; - public $max_outgoing_id; - public function checkMessageId($new_message_id, $aargs) - { - if (!\is_object($new_message_id)) { - $new_message_id = new \tgseclib\Math\BigInteger(\strrev($new_message_id), 256); - } - $min_message_id = (new \tgseclib\Math\BigInteger(\time() + $this->time_delta - 300))->bitwise_leftShift(32); - if ($min_message_id->compare($new_message_id) > 0) { - $this->API->logger->logger('Given message id (' . $new_message_id . ') is too old compared to the min value (' . $min_message_id . ').', \danog\MadelineProto\Logger::WARNING); - } - $max_message_id = (new \tgseclib\Math\BigInteger(\time() + $this->time_delta + 30))->bitwise_leftShift(32); - if ($max_message_id->compare($new_message_id) < 0) { - throw new \danog\MadelineProto\Exception('Given message id (' . $new_message_id . ') is too new compared to the max value (' . $max_message_id . '). Consider syncing your date.'); - } - if ($aargs['outgoing']) { - if (!$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$zero)) { - throw new \danog\MadelineProto\Exception('Given message id (' . $new_message_id . ') is not divisible by 4. Consider syncing your date.'); - } - if (!\danog\MadelineProto\Magic::$has_thread && $new_message_id->compare($key = $this->getMaxId($incoming = false)) <= 0) { - throw new \danog\MadelineProto\Exception('Given message id (' . $new_message_id . ') is lower than or equal to the current limit (' . $key . '). Consider syncing your date.', 1); - } - if (\count($this->outgoing_messages) > $this->API->settings['msg_array_limit']['outgoing']) { - \reset($this->outgoing_messages); - $key = \key($this->outgoing_messages); - if (!isset($this->outgoing_messages[$key]['promise'])) { - unset($this->outgoing_messages[$key]); - } - } - $this->max_outgoing_id = $new_message_id; - $this->outgoing_messages[\strrev($new_message_id->toBytes())] = []; - } else { - if (!$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$one) && !$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$three)) { - throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3'); - } - $key = $this->getMaxId($incoming = true); - if ($aargs['container']) { - if ($new_message_id->compare($key = $this->getMaxId($incoming = true)) >= 0) { - $this->API->logger->logger('WARNING: Given message id (' . $new_message_id . ') is bigger than or equal to the current limit (' . $key . '). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING); - } - } else { - if ($new_message_id->compare($key = $this->getMaxId($incoming = true)) <= 0) { - $this->API->logger->logger('WARNING: Given message id (' . $new_message_id . ') is lower than or equal to the current limit (' . $key . '). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING); - } - } - if (\count($this->incoming_messages) > $this->API->settings['msg_array_limit']['incoming']) { - \reset($this->incoming_messages); - $key = \key($this->incoming_messages); - if (!isset($this->incoming_messages[$key]['promise'])) { - unset($this->incoming_messages[$key]); - } - } - $this->max_incoming_id = $new_message_id; - $this->incoming_messages[\strrev($new_message_id->toBytes())] = []; - } - } - public function generateMessageId() - { - $message_id = (new \tgseclib\Math\BigInteger(\time() + $this->time_delta))->bitwise_leftShift(32); - if ($message_id->compare($key = $this->getMaxId($incoming = false)) <= 0) { - $message_id = $key->add(\danog\MadelineProto\Magic::$four); - } - $this->checkMessageId($message_id, ['outgoing' => true, 'container' => false]); - return \strrev($message_id->toBytes()); - } - public function getMaxId($incoming) - { - $incoming = $incoming ? 'incoming' : 'outgoing'; - if (isset($this->{'max_' . $incoming . '_id'}) && \is_object($this->{'max_' . $incoming . '_id'})) { - return $this->{'max_' . $incoming . '_id'}; - } - return \danog\MadelineProto\Magic::$zero; - } +if (PHP_INT_SIZE === 8) { + \class_alias(MsgIdHandler64::class, \danog\MadelineProto\MTProtoSession\MsgIdHandler::class); +} else { + \class_alias(MsgIdHandler32::class, \danog\MadelineProto\MTProtoSession\MsgIdHandler::class); } diff --git a/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler32.php b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler32.php new file mode 100644 index 000000000..c85107b24 --- /dev/null +++ b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler32.php @@ -0,0 +1,142 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * + * @link https://docs.madelineproto.xyz MadelineProto documentation + */ + +namespace danog\MadelineProto\MTProtoSession\MsgIdHandler; + +use danog\MadelineProto\MTProtoSession\MsgIdHandlerAbstract; + +/** + * Manages message ids. + */ +class MsgIdHandler32 extends MsgIdHandlerAbstract +{ + /** + * Maximum incoming ID. + * + * @var BigInteger + */ + private $maxIncomingId; + /** + * Maximum outgoing ID. + * + * @var BigInteger + */ + private $maxOutgoingId; + /** + * Check validity of given message ID. + * + * @param string $newMessageId New message ID + * @param array $aargs Params + * + * @return void + */ + public function checkMessageId($newMessageId, array $aargs): void + { + $newMessageId = \is_object($newMessageId) ? $newMessageId : new \tgseclib\Math\BigInteger(\strrev($newMessageId), 256); + + $minMessageId = (new \tgseclib\Math\BigInteger(\time() + $this->session->time_delta - 300))->bitwise_leftShift(32); + if ($minMessageId->compare($newMessageId) > 0) { + $this->session->API->logger->logger('Given message id ('.$newMessageId.') is too old compared to the min value ('.$minMessageId.').', \danog\MadelineProto\Logger::WARNING); + } + $maxMessageId = (new \tgseclib\Math\BigInteger(\time() + $this->session->time_delta + 30))->bitwise_leftShift(32); + if ($maxMessageId->compare($newMessageId) < 0) { + throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is too new compared to the max value ('.$maxMessageId.'). Consider syncing your date.'); + } + if ($aargs['outgoing']) { + if (!$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$zero)) { + throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is not divisible by 4. Consider syncing your date.'); + } + if ($newMessageId->compare($key = $this->getMaxId($incoming = false)) <= 0) { + throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', 1); + } + if (\count($this->session->outgoing_messages) > $this->session->API->settings['msg_array_limit']['outgoing']) { + \reset($this->session->outgoing_messages); + $key = \key($this->session->outgoing_messages); + if (!isset($this->session->outgoing_messages[$key]['promise'])) { + unset($this->session->outgoing_messages[$key]); + } + } + $this->maxOutgoingId = $newMessageId; + $this->session->outgoing_messages[\strrev($newMessageId->toBytes())] = []; + } else { + if (!$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$one) && !$newMessageId->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$three)) { + throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3'); + } + $key = $this->getMaxId($incoming = true); + if ($aargs['container']) { + if ($newMessageId->compare($key = $this->getMaxId($incoming = true)) >= 0) { + $this->session->API->logger->logger('WARNING: Given message id ('.$newMessageId.') is bigger than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING); + } + } else { + if ($newMessageId->compare($key = $this->getMaxId($incoming = true)) <= 0) { + $this->session->API->logger->logger('WARNING: Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING); + } + } + if (\count($this->session->incoming_messages) > $this->session->API->settings['msg_array_limit']['incoming']) { + \reset($this->session->incoming_messages); + $key = \key($this->session->incoming_messages); + if (!isset($this->session->incoming_messages[$key]['promise'])) { + unset($this->session->incoming_messages[$key]); + } + } + $this->maxIncomingId = $newMessageId; + $this->session->incoming_messages[\strrev($newMessageId->toBytes())] = []; + } + } + /** + * Generate message ID. + * + * @return string + */ + public function generateMessageId(): string + { + $message_id = (new \tgseclib\Math\BigInteger(\time() + $this->session->time_delta))->bitwise_leftShift(32); + if ($message_id->compare($key = $this->getMaxId($incoming = false)) <= 0) { + $message_id = $key->add(\danog\MadelineProto\Magic::$four); + } + $this->checkMessageId($message_id, ['outgoing' => true, 'container' => false]); + return \strrev($message_id->toBytes()); + } + /** + * Get maximum message ID. + * + * @param boolean $incoming Incoming or outgoing message ID + * + * @return mixed + */ + public function getMaxId(bool $incoming) + { + $incoming = $incoming ? 'Incoming' : 'Outgoing'; + if (isset($this->{'max'.$incoming.'Id'}) && \is_object($this->{'max'.$incoming.'Id'})) { + return $this->{'max'.$incoming.'Id'}; + } + return \danog\MadelineProto\Magic::$zero; + } + + /** + * Reset message IDs. + * + * @return void + */ + public function reset(): void + { + $this->maxIncomingId = null; + $this->maxOutgoingId = null; + } +} diff --git a/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler64.php b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler64.php new file mode 100644 index 000000000..d012d3aec --- /dev/null +++ b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler64.php @@ -0,0 +1,127 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * + * @link https://docs.madelineproto.xyz MadelineProto documentation + */ + +namespace danog\MadelineProto\MTProtoSession\MsgIdHandler; + +use danog\MadelineProto\MTProtoSession\MsgIdHandlerAbstract; +use danog\MadelineProto\Tools; + +/** + * Manages message ids. + */ +class MsgIdHandler64 extends MsgIdHandlerAbstract +{ + /** + * Maximum incoming ID. + * + * @var int + */ + private $maxIncomingId = 0; + /** + * Maximum outgoing ID. + * + * @var int + */ + private $maxOutgoingId = 0; + /** + * Check validity of given message ID + * + * @param string $newMessageId New message ID + * @param array $aargs Params + * + * @return void + */ + public function checkMessageId($newMessageId, array $aargs): void + { + $newMessageId = is_integer($newMessageId) ? $newMessageId : Tools::unpackSignedLong($newMessageId); + $minMessageId = (\time() + $this->session->time_delta - 300) << 32; + if ($newMessageId < $minMessageId) { + $this->session->API->logger->logger('Given message id ('.$newMessageId.') is too old compared to the min value ('.$minMessageId.').', \danog\MadelineProto\Logger::WARNING); + } + $maxMessageId = (\time() + $this->session->time_delta + 30) << 32; + if ($newMessageId > $maxMessageId) { + throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is too new compared to the max value ('.$maxMessageId.'). Consider syncing your date.'); + } + if ($aargs['outgoing']) { + if ($newMessageId % 4) { + throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is not divisible by 4. Consider syncing your date.'); + } + if ($newMessageId <= $this->maxOutgoingId) { + throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$this->maxOutgoingId.'). Consider syncing your date.'); + } + if (\count($this->session->outgoing_messages) > $this->session->API->settings['msg_array_limit']['outgoing']) { + \reset($this->session->outgoing_messages); + $key = \key($this->session->outgoing_messages); + if (!isset($this->session->outgoing_messages[$key]['promise'])) { + unset($this->session->outgoing_messages[$key]); + } + } + $this->maxOutgoingId = $newMessageId; + $this->session->outgoing_messages[Tools::packSignedLong($newMessageId)] = []; + } else { + if (!($newMessageId % 2)) { + throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3'); + } + $key = $this->maxIncomingId; + if ($aargs['container']) { + if ($newMessageId >= $key) { + $this->session->API->logger->logger('WARNING: Given message id ('.$newMessageId.') is bigger than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING); + } + } else { + if ($newMessageId <= $key) { + $this->session->API->logger->logger('WARNING: Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING); + } + } + if (\count($this->session->incoming_messages) > $this->session->API->settings['msg_array_limit']['incoming']) { + \reset($this->session->incoming_messages); + $key = \key($this->session->incoming_messages); + if (!isset($this->session->incoming_messages[$key]['promise'])) { + unset($this->session->incoming_messages[$key]); + } + } + $this->maxIncomingId = $newMessageId; + $this->session->incoming_messages[Tools::packSignedLong($newMessageId)] = []; + } + } + /** + * Generate message ID. + * + * @return string + */ + public function generateMessageId(): string + { + $messageId = (\time() + $this->session->time_delta) << 32; + if ($messageId <= $this->maxOutgoingId) { + $messageId = $this->maxOutgoingId + 4; + } + $this->checkMessageId($messageId, ['outgoing' => true, 'container' => false]); + return Tools::packSignedLong($messageId); + } + /** + * Get maximum message ID. + * + * @param boolean $incoming Incoming or outgoing message ID + * + * @return mixed + */ + public function getMaxId(bool $incoming) + { + return $this->{$incoming ? 'maxIncomingId' : 'maxOutgoingId'}; + } +} \ No newline at end of file diff --git a/src/danog/MadelineProto/MTProtoSession/MsgIdHandlerAbstract.php b/src/danog/MadelineProto/MTProtoSession/MsgIdHandlerAbstract.php new file mode 100644 index 000000000..cf796736d --- /dev/null +++ b/src/danog/MadelineProto/MTProtoSession/MsgIdHandlerAbstract.php @@ -0,0 +1,66 @@ +. + * + * @author Daniil Gentili + * @copyright 2016-2019 Daniil Gentili + * @license https://opensource.org/licenses/AGPL-3.0 AGPLv3 + * + * @link https://docs.madelineproto.xyz MadelineProto documentation + */ + +namespace danog\MadelineProto\MTProtoSession; + +/** + * Manages message ids. + */ +abstract class MsgIdHandlerAbstract +{ + /** + * Session instance. + * + * @var Session + */ + protected $session; + /** + * Constructor. + * + * @param Session $session Session + */ + public function __construct(Session $session) + { + $this->session = $session; + } + + /** + * Check validity of given message ID. + * + * @param string $newMessageId New message ID + * @param array $aargs Params + * + * @return void + */ + abstract public function checkMessageId(string $newMessageId, array $aargs): void; + /** + * Generate outgoing message ID. + * + * @return string + */ + abstract public function generateMessageId(): string; + /** + * Get maximum message ID. + * + * @param boolean $incoming Incoming or outgoing message ID + * + * @return mixed + */ + abstract public function getMaxId(bool $incoming); +} diff --git a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php index feb71bc6d..76b21e757 100644 --- a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php @@ -119,7 +119,7 @@ trait ResponseHandler unset($this->new_incoming[$current_msg_id]); $only_updates = false; foreach ($this->incoming_messages[$current_msg_id]['content']['messages'] as $message) { - $this->checkMessageId($message['msg_id'], ['outgoing' => false, 'container' => true]); + $this->msgIdHandler->checkMessageId($message['msg_id'], ['outgoing' => false, 'container' => true]); $this->incoming_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'content' => $message['body'], 'from_container' => true]; $this->new_incoming[$message['msg_id']] = $message['msg_id']; } @@ -139,7 +139,7 @@ trait ResponseHandler // Acknowledge that I received the server's response } else { $message = $this->incoming_messages[$current_msg_id]['content']; - $this->checkMessageId($message['orig_message']['msg_id'], ['outgoing' => false, 'container' => true]); + $this->msgIdHandler->checkMessageId($message['orig_message']['msg_id'], ['outgoing' => false, 'container' => true]); $this->incoming_messages[$message['orig_message']['msg_id']] = ['content' => $this->incoming_messages[$current_msg_id]['content']['orig_message']]; $this->new_incoming[$message['orig_message']['msg_id']] = $message['orig_message']['msg_id']; } diff --git a/src/danog/MadelineProto/MTProtoSession/Session.php b/src/danog/MadelineProto/MTProtoSession/Session.php index f50830215..ca6452e31 100644 --- a/src/danog/MadelineProto/MTProtoSession/Session.php +++ b/src/danog/MadelineProto/MTProtoSession/Session.php @@ -25,7 +25,6 @@ namespace danog\MadelineProto\MTProtoSession; abstract class Session { use AckHandler; - use MsgIdHandler; use ResponseHandler; use SeqNoHandler; use CallHandler; @@ -38,30 +37,33 @@ abstract class Session public $time_delta = 0; public $call_queue = []; public $ack_queue = []; + /** + * Message ID handler. + * + * @var MsgIdHandlerAbstract + */ + public $msgIdHandler; /** * Reset MTProto session. * * @return void */ - public function resetSession() + public function resetSession(): void { $this->session_id = \danog\MadelineProto\Tools::random(8); $this->session_in_seq_no = 0; $this->session_out_seq_no = 0; - $this->max_incoming_id = null; - $this->max_outgoing_id = null; + $this->msgIdHandler = new MsgIdHandler($this); } /** * Create MTProto session if needed. * * @return void */ - public function createSession() + public function createSession(): void { if ($this->session_id === null) { - $this->session_id = \danog\MadelineProto\Tools::random(8); - $this->session_in_seq_no = 0; - $this->session_out_seq_no = 0; + $this->resetSession(); } } /** diff --git a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php index d4560c197..131758040 100644 --- a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php @@ -540,7 +540,7 @@ trait AuthKeyHandler $perm_auth_key_id = $datacenterConnection->getPermAuthKey()->getID(); $temp_session_id = $connection->session_id; $message_data = (yield from $this->TL->serializeObject(['type' => ''], ['_' => 'bind_auth_key_inner', 'nonce' => $nonce, 'temp_auth_key_id' => $temp_auth_key_id, 'perm_auth_key_id' => $perm_auth_key_id, 'temp_session_id' => $temp_session_id, 'expires_at' => $expires_at], 'bindTempAuthKey_inner')); - $message_id = $connection->generateMessageId(); + $message_id = $connection->msgIdHandler->generateMessageId(); $seq_no = 0; $encrypted_data = \danog\MadelineProto\Tools::random(16) . $message_id . \pack('VV', $seq_no, \strlen($message_data)) . $message_data; $message_key = \substr(\sha1($encrypted_data, true), -16); diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index cedc0d982..89e74e73f 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -272,6 +272,7 @@ class TL $TL_dict['methods'][$key]['id'] = \danog\MadelineProto\Tools::packSignedInt($TL_dict['methods'][$key]['id']); } } + if (empty($TL_dict) || empty($TL_dict['constructors']) || !isset($TL_dict['methods'])) { throw new Exception(\danog\MadelineProto\Lang::$current_lang['src_file_invalid'].$file); } diff --git a/src/danog/MadelineProto/TON/ADNLConnection.php b/src/danog/MadelineProto/TON/ADNLConnection.php index 6ae9e7f6b..5b7a09a83 100644 --- a/src/danog/MadelineProto/TON/ADNLConnection.php +++ b/src/danog/MadelineProto/TON/ADNLConnection.php @@ -131,11 +131,11 @@ class ADNLConnection $buffer = yield $this->stream->getReadBuffer($length); if ($length) { $data = yield $buffer->bufferRead($length); - $data = yield $this->TL->deserialize($data); + $data = $this->TL->deserialize($data); if ($data['_'] !== 'adnl.message.answer') { throw new Exception('Wrong answer type: ' . $data['_']); } - $this->requests[$data['query_id']]->resolve(yield $this->TL->deserialize((string) $data['answer'])); + $this->requests[$data['query_id']]->resolve($this->TL->deserialize((string) $data['answer'])); } } })()); diff --git a/src/danog/MadelineProto/TON/APIFactory.php b/src/danog/MadelineProto/TON/APIFactory.php index 94893efb3..ea246cc21 100644 --- a/src/danog/MadelineProto/TON/APIFactory.php +++ b/src/danog/MadelineProto/TON/APIFactory.php @@ -23,6 +23,12 @@ use danog\MadelineProto\AbstractAPIFactory; class APIFactory extends AbstractAPIFactory { + /** + * @internal this is a internal property generated by build_docs.php, don't change manually + * + * @var http + */ + public $http; /** * @internal this is a internal property generated by build_docs.php, don't change manually * diff --git a/src/danog/MadelineProto/TON/InternalDoc.php b/src/danog/MadelineProto/TON/InternalDoc.php index fd9de6dac..312372c58 100644 --- a/src/danog/MadelineProto/TON/InternalDoc.php +++ b/src/danog/MadelineProto/TON/InternalDoc.php @@ -104,6 +104,22 @@ interface liteServer */ public function getAccountState($params); + /** + * + * + * Parameters: + * * `#` **mode** - + * * `tonNode.blockIdExt` **id** - + * * `liteServer.accountId` **account** - + * * `long` **method_id** - + * * `bytes` **params** -. + * + * @param array $params Parameters + * + * @return liteServer.RunMethodResult + */ + public function runSmcMethod($params); + /** * * @@ -861,6 +877,39 @@ interface engine public function validator($params); } +interface http +{ + /** + * + * + * Parameters: + * * `int256` **id** - + * * `string` **method** - + * * `string` **url** - + * * `string` **http_version** - + * * `[http.header]` **headers** -. + * + * @param array $params Parameters + * + * @return http.Response + */ + public function request($params); + + /** + * + * + * Parameters: + * * `int256` **id** - + * * `int` **seqno** - + * * `int` **max_chunk_size** -. + * + * @param array $params Parameters + * + * @return http.PayloadPart + */ + public function getNextPayloadPart($params); +} + class InternalDoc extends APIFactory { /** @@ -917,11 +966,11 @@ class InternalDoc extends APIFactory * * @param array $parameters Parameters * - * @return array + * @return \Generator */ - public function botAPItoMTProto(array $parameters): array + public function botAPItoMTProto(array $parameters, array $extra = []) { - return $this->API->botAPItoMTProto($parameters); + return $this->__call(__FUNCTION__, [$parameters, $extra]); } /** * Get TL method namespaces. diff --git a/src/danog/MadelineProto/TON/Lite.php b/src/danog/MadelineProto/TON/Lite.php index a3885a8dc..172c879c6 100644 --- a/src/danog/MadelineProto/TON/Lite.php +++ b/src/danog/MadelineProto/TON/Lite.php @@ -84,7 +84,7 @@ class Lite $config['_'] = 'liteclient.config.global'; $config = Tools::convertJsonTL($config); $config['validator']['init_block'] = $config['validator']['init_block'] ?? $config['validator']['zero_state']; - $this->config = yield $this->TL->deserialize(yield from $this->TL->serializeObject(['type' => ''], $config, 'cleanup')); + $this->config = $this->TL->deserialize(yield from $this->TL->serializeObject(['type' => ''], $config, 'cleanup')); foreach ($this->config['liteservers'] as $lite) { $this->connections[] = $connection = new ADNLConnection($this->TL); yield from $connection->connect($lite); @@ -136,11 +136,12 @@ class Lite * * @param array $parameters Parameters * - * @return array + * @return \Generator */ - public function botAPItoMTProto(array $parameters): array + public function botAPItoMTProto(array $parameters): \Generator { return $parameters; + yield; } /** * Get TL method namespaces. diff --git a/src/danog/MadelineProto/TON/schemes/lite_api.tl b/src/danog/MadelineProto/TON/schemes/lite_api.tl index 0dd3fa22f..7d4495e72 100644 --- a/src/danog/MadelineProto/TON/schemes/lite_api.tl +++ b/src/danog/MadelineProto/TON/schemes/lite_api.tl @@ -35,6 +35,7 @@ liteServer.blockState id:tonNode.blockIdExt root_hash:int256 file_hash:int256 da liteServer.blockHeader id:tonNode.blockIdExt mode:# header_proof:bytes = liteServer.BlockHeader; liteServer.sendMsgStatus status:int = liteServer.SendMsgStatus; liteServer.accountState id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:bytes proof:bytes state:bytes = liteServer.AccountState; +liteServer.runMethodResult mode:# id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:mode.0?bytes proof:mode.0?bytes state_proof:mode.1?bytes init_c7:mode.3?bytes lib_extras:mode.4?bytes exit_code:int result:mode.2?bytes = liteServer.RunMethodResult; liteServer.shardInfo id:tonNode.blockIdExt shardblk:tonNode.blockIdExt shard_proof:bytes shard_descr:bytes = liteServer.ShardInfo; liteServer.allShardsInfo id:tonNode.blockIdExt proof:bytes data:bytes = liteServer.AllShardsInfo; liteServer.transactionInfo id:tonNode.blockIdExt proof:bytes transaction:bytes = liteServer.TransactionInfo; @@ -63,6 +64,7 @@ liteServer.getState id:tonNode.blockIdExt = liteServer.BlockState; liteServer.getBlockHeader id:tonNode.blockIdExt mode:# = liteServer.BlockHeader; liteServer.sendMessage body:bytes = liteServer.SendMsgStatus; liteServer.getAccountState id:tonNode.blockIdExt account:liteServer.accountId = liteServer.AccountState; +liteServer.runSmcMethod mode:# id:tonNode.blockIdExt account:liteServer.accountId method_id:long params:bytes = liteServer.RunMethodResult; liteServer.getShardInfo id:tonNode.blockIdExt workchain:int shard:long exact:Bool = liteServer.ShardInfo; liteServer.getAllShardsInfo id:tonNode.blockIdExt = liteServer.AllShardsInfo; liteServer.getOneTransaction id:tonNode.blockIdExt account:liteServer.accountId lt:long = liteServer.TransactionInfo; diff --git a/src/danog/MadelineProto/TON/schemes/ton_api.tl b/src/danog/MadelineProto/TON/schemes/ton_api.tl index 2460201a6..1f819d5dd 100644 --- a/src/danog/MadelineProto/TON/schemes/ton_api.tl +++ b/src/danog/MadelineProto/TON/schemes/ton_api.tl @@ -614,3 +614,25 @@ engine.validator.createElectionBid election_date:int election_addr:string wallet engine.validator.checkDhtServers id:int256 = engine.validator.DhtServersStatus; engine.validator.controlQuery data:bytes = Object; + + +---types--- + +http.header name:string value:string = http.Header; +http.payloadPart data:bytes trailer:(vector http.header) last:Bool = http.PayloadPart; +http.response http_version:string status_code:int reason:string headers:(vector http.header) = http.Response; + +---functions--- + +http.request id:int256 method:string url:string http_version:string headers:(vector http.header) = http.Response; +http.getNextPayloadPart id:int256 seqno:int max_chunk_size:int = http.PayloadPart; + +---types--- + + +http.server.dnsEntry domain:string addr:adnl.id.short = http.server.DnsEntry; +http.server.host domains:(vector string) ip:int port:int adnl_id:adnl.id.short = http.server.Host; + +http.server.config dhs:(vector http.server.dnsEntry) local_hosts:(vector http.server.host) = http.server.Config; + +---functions--- diff --git a/src/danog/MadelineProto/TON/schemes/tonlib_api.tl b/src/danog/MadelineProto/TON/schemes/tonlib_api.tl index 9235ab35b..a7385eb3e 100644 --- a/src/danog/MadelineProto/TON/schemes/tonlib_api.tl +++ b/src/danog/MadelineProto/TON/schemes/tonlib_api.tl @@ -8,6 +8,9 @@ bytes = Bytes; secureString = SecureString; secureBytes = SecureBytes; +object ? = Object; +function ? = Function; + boolFalse = Bool; boolTrue = Bool; @@ -22,7 +25,7 @@ keyStoreTypeInMemory = KeyStoreType; config config:string blockchain_name:string use_callbacks_for_network:Bool ignore_cache:Bool = Config; options config:config keystore_type:KeyStoreType = Options; -options.configInfo default_wallet_id:int53 = options.ConfigInfo; +options.configInfo default_wallet_id:int64 = options.ConfigInfo; options.info config_info:options.configInfo = options.Info; key public_key:string secret:secureBytes = Key; @@ -35,52 +38,87 @@ exportedEncryptedKey data:secureBytes = ExportedEncryptedKey; bip39Hints words:vector = Bip39Hints; accountAddress account_address:string = AccountAddress; +accountRevisionList revisions:vector = AccountRevisionList; unpackedAccountAddress workchain_id:int32 bounceable:Bool testnet:Bool addr:bytes = UnpackedAccountAddress; internal.transactionId lt:int64 hash:bytes = internal.TransactionId; -raw.initialAccountState code:bytes data:bytes = raw.InitialAccountState; -raw.accountState balance:int64 code:bytes data:bytes last_transaction_id:internal.transactionId frozen_hash:bytes sync_utime:int53 = raw.AccountState; +ton.blockId workchain:int32 shard:int64 seqno:int32 = internal.BlockId; +ton.blockIdExt workchain:int32 shard:int64 seqno:int32 root_hash:bytes file_hash:bytes = ton.BlockIdExt; + +raw.fullAccountState balance:int64 code:bytes data:bytes last_transaction_id:internal.transactionId block_id:ton.blockIdExt frozen_hash:bytes sync_utime:int53 = raw.FullAccountState; raw.message source:string destination:string value:int64 fwd_fee:int64 ihr_fee:int64 created_lt:int64 body_hash:bytes message:bytes = raw.Message; raw.transaction utime:int53 data:bytes transaction_id:internal.transactionId fee:int64 storage_fee:int64 other_fee:int64 in_msg:raw.message out_msgs:vector = raw.Transaction; raw.transactions transactions:vector previous_transaction_id:internal.transactionId = raw.Transactions; -testWallet.initialAccountState public_key:string = testWallet.InitialAccountState; -testWallet.accountState balance:int64 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53 = testWallet.AccountState; +raw.initialAccountState code:bytes data:bytes = InitialAccountState; +testGiver.initialAccountState = InitialAccountState; +testWallet.initialAccountState public_key:string = InitialAccountState; +wallet.initialAccountState public_key:string = InitialAccountState; +wallet.v3.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; +wallet.highload.v1.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; +wallet.highload.v2.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; +dns.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; -wallet.initialAccountState public_key:string = wallet.InitialAccountState; -wallet.accountState balance:int64 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53 = wallet.AccountState; +raw.accountState code:bytes data:bytes frozen_hash:bytes = AccountState; +testWallet.accountState seqno:int32 = AccountState; +wallet.accountState seqno:int32 = AccountState; +wallet.v3.accountState wallet_id:int64 seqno:int32 = AccountState; +wallet.highload.v1.accountState wallet_id:int64 seqno:int32 = AccountState; +wallet.highload.v2.accountState wallet_id:int64 = AccountState; +testGiver.accountState seqno:int32 = AccountState; +dns.accountState wallet_id:int64 = AccountState; +uninited.accountState frozen_hash:bytes = AccountState; -wallet.v3.initialAccountState public_key:string wallet_id:int53 = wallet.v3.InitialAccountState; -wallet.v3.accountState balance:int64 wallet_id:int53 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53 = wallet.v3.AccountState; - -testGiver.accountState balance:int64 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53= testGiver.AccountState; - -uninited.accountState balance:int64 last_transaction_id:internal.transactionId frozen_hash:bytes sync_utime:int53 = uninited.AccountState; - -//generic.initialAccountStateRaw initital_account_state:raw.initialAccountState = generic.InitialAccountState; -//generic.initialAccountStateTestWallet initital_account_state:testWallet.initialAccountState = generic.InitialAccountState; -//generic.initialAccountStateWallet initital_account_state:wallet.initialAccountState = generic.InitialAccountState; - -generic.accountStateRaw account_state:raw.accountState = generic.AccountState; -generic.accountStateTestWallet account_state:testWallet.accountState = generic.AccountState; -generic.accountStateWallet account_state:wallet.accountState = generic.AccountState; -generic.accountStateWalletV3 account_state:wallet.v3.accountState = generic.AccountState; -generic.accountStateTestGiver account_state:testGiver.accountState = generic.AccountState; -generic.accountStateUninited account_state:uninited.accountState = generic.AccountState; - -sendGramsResult sent_until:int53 body_hash:bytes = SendGramsResult; +fullAccountState balance:int64 last_transaction_id:internal.transactionId block_id:ton.blockIdExt sync_utime:int53 account_state:AccountState = FullAccountState; syncStateDone = SyncState; syncStateInProgress from_seqno:int32 to_seqno:int32 current_seqno:int32 = SyncState; -fees in_fwd_fee:int53 storage_fee:int53 gas_fee:int53 fwd_fee:int53= Fees; -query.fees source_fees:fees destination_fees:fees = query.Fees; +// +// MSG +// + +msg.dataText text:string = msg.Data; +msg.dataEncryptedText text:string = msg.Data; +msg.message destination:accountAddress amount:int64 data:msg.Data = msg.Message; + +// +// DNS +// + +dns.entryDataUnknown bytes:bytes = dns.EntryData; +dns.entryDataText text:string = dns.EntryData; +dns.entryDataNextResolver resolver:AccountAddress = dns.EntryData; +dns.entryDataSmcAddress smc_address:AccountAddress = dns.EntryData; +//TODO: other entry types + +dns.entry name:string category:int32 entry:dns.EntryData = dns.Entry; + +dns.actionDeleteAll = dns.Action; +// use category = 0 to delete all entries +dns.actionDelete name:string category:int32 = dns.Action; +dns.actionSet entry:dns.entry = dns.Action; + +dns.resolved entries:vector = dns.Resolved; + +// +// Actions +// + +actionNoop = Action; +actionMsg messages:vector allow_send_to_uninited:Bool = Action; +actionDns actions:vector = Action; +//actionMultisig actions:vector = Action; + +fees in_fwd_fee:int53 storage_fee:int53 gas_fee:int53 fwd_fee:int53 = Fees; +query.fees source_fees:fees destination_fees:vector = query.Fees; +// query.emulationResult exit_code:int32 fees:fees = query.EmulationResult; query.info id:int53 valid_until:int53 body_hash:bytes = query.Info; -tvm.slice bytes:string = tvm.Slice; -tvm.cell bytes:string = tvm.Cell; +tvm.slice bytes:bytes = tvm.Slice; +tvm.cell bytes:bytes = tvm.Cell; tvm.numberDecimal number:string = tvm.Number; tvm.tuple elements:vector = tvm.Tuple; tvm.list elements:vector = tvm.List; @@ -152,51 +190,41 @@ packAccountAddress account_address:unpackedAccountAddress = AccountAddress; getBip39Hints prefix:string = Bip39Hints; //raw.init initial_account_state:raw.initialAccountState = Ok; -raw.getAccountAddress initital_account_state:raw.initialAccountState = AccountAddress; -raw.getAccountState account_address:accountAddress = raw.AccountState; -raw.getTransactions account_address:accountAddress from_transaction_id:internal.transactionId = raw.Transactions; +raw.getAccountState account_address:accountAddress = raw.FullAccountState; +raw.getTransactions private_key:InputKey account_address:accountAddress from_transaction_id:internal.transactionId = raw.Transactions; raw.sendMessage body:bytes = Ok; raw.createAndSendMessage destination:accountAddress initial_account_state:bytes data:bytes = Ok; raw.createQuery destination:accountAddress init_code:bytes init_data:bytes body:bytes = query.Info; -testWallet.init private_key:InputKey = Ok; -testWallet.getAccountAddress initital_account_state:testWallet.initialAccountState = AccountAddress; -testWallet.getAccountState account_address:accountAddress = testWallet.AccountState; -testWallet.sendGrams private_key:InputKey destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult; +sync = ton.BlockIdExt; -wallet.init private_key:InputKey = Ok; -wallet.getAccountAddress initital_account_state:wallet.initialAccountState = AccountAddress; -wallet.getAccountState account_address:accountAddress = wallet.AccountState; -wallet.sendGrams private_key:InputKey destination:accountAddress seqno:int32 valid_until:int53 amount:int64 message:bytes = SendGramsResult; - -wallet.v3.getAccountAddress initital_account_state:wallet.v3.initialAccountState = AccountAddress; - -testGiver.getAccountState = testGiver.AccountState; -testGiver.getAccountAddress = AccountAddress; -testGiver.sendGrams destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult; - -sync = Ok; - -//generic.getAccountAddress initital_account_state:generic.InitialAccountState = AccountAddress; -generic.getAccountState account_address:accountAddress = generic.AccountState; -generic.sendGrams private_key:InputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = SendGramsResult; - -generic.createSendGramsQuery private_key:InputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = query.Info; +// revision = 0 -- use default revision +// revision = x (x > 0) -- use revision x +getAccountAddress initial_account_state:InitialAccountState revision:int32 = AccountAddress; +// guessAccountRevision initial_account_state:InitialAccountState = AccountRevisionList; +getAccountState account_address:accountAddress = FullAccountState; +createQuery private_key:InputKey address:accountAddress timeout:int32 action:Action = query.Info; query.send id:int53 = Ok; query.forget id:int53 = Ok; query.estimateFees id:int53 ignore_chksig:Bool = query.Fees; +// query.emulate id:int53 ignore_chksig:Bool = query.EmulationResult; query.getInfo id:int53 = query.Info; smc.load account_address:accountAddress = smc.Info; +//smc.forget id:int53 = Ok; smc.getCode id:int53 = tvm.Cell; smc.getData id:int53 = tvm.Cell; smc.getState id:int53 = tvm.Cell; smc.runGetMethod id:int53 method:smc.MethodId stack:vector = smc.RunResult; +dns.resolve account_address:accountAddress name:string category:int32 = dns.Resolved; + onLiteServerQueryResult id:int64 bytes:bytes = Ok; onLiteServerQueryError id:int64 error:error = Ok; +withBlock id:ton.blockIdExt function:Function = Object; + runTests dir:string = Ok; liteServer.getInfo = liteServer.Info; diff --git a/src/danog/MadelineProto/Wrappers/Loop.php b/src/danog/MadelineProto/Wrappers/Loop.php index ecdebdb4c..f92ab2c01 100644 --- a/src/danog/MadelineProto/Wrappers/Loop.php +++ b/src/danog/MadelineProto/Wrappers/Loop.php @@ -178,6 +178,17 @@ trait Loop $this->stopLoop = true; $this->signalUpdate(); } + /** + * Start MadelineProto's update handling loop in background, or run the provided async callable. + * + * @param callable $callback Async callable to run + * + * @return mixed + */ + public function loopFork($callback = null): void + { + Tools::callFork($this->loop($callback)); + } /** * Close connection with client, connected via web. * diff --git a/ton/lite-client.php b/ton/lite-client.php index 2ea6bf50f..5fdfbf08a 100644 --- a/ton/lite-client.php +++ b/ton/lite-client.php @@ -2,6 +2,7 @@ use danog\MadelineProto\Logger; use danog\MadelineProto\TON\API; +use danog\MadelineProto\Tools; require 'vendor/autoload.php'; diff --git a/ton/ton-lite-client-test1.config.json b/ton/ton-lite-client-test1.config.json index 3d34832ee..c9fa58900 100644 --- a/ton/ton-lite-client-test1.config.json +++ b/ton/ton-lite-client-test1.config.json @@ -1,13 +1,13 @@ { "liteservers": [ - { + { "ip": 861606190, "port": 9999, - "id": { - "@type": "pub.ed25519", + "id": { + "@type": "pub.ed25519", "key": "T10NJq2tgx6LpTHj734fSNYJ6S2w1hTdFRXJaj5st80" - } - } + } + } ], "validator": { "@type": "validator.config.global",