diff --git a/src/danog/MadelineProto/Connection.php b/src/danog/MadelineProto/Connection.php index dec06d17..19d2eb12 100644 --- a/src/danog/MadelineProto/Connection.php +++ b/src/danog/MadelineProto/Connection.php @@ -25,8 +25,8 @@ use danog\MadelineProto\Loop\Connection\CheckLoop; use danog\MadelineProto\Loop\Connection\HttpWaitLoop; use danog\MadelineProto\Loop\Connection\ReadLoop; use danog\MadelineProto\Loop\Connection\WriteLoop; +use danog\MadelineProto\MTProtoSession\Session; use danog\MadelineProto\Stream\ConnectionContext; -use danog\MadelineProto\Stream\MTProtoTools\Session; /** * Connection class. @@ -349,9 +349,9 @@ class Connection extends Session * @param array $message The message to send * @param boolean $flush Whether to flush the message right away * - * @return Promise + * @return \Generator */ - public function sendMessage(array $message, bool $flush = true): Promise + public function sendMessage(array $message, bool $flush = true): \Generator { $deferred = new Deferred(); @@ -387,6 +387,15 @@ class Connection extends Session return $deferred->promise(); } + /** + * Flush pending packets + * + * @return void + */ + public function flush() + { + $this->writer->resume(); + } /** * Connect main instance. * diff --git a/src/danog/MadelineProto/Conversion.php b/src/danog/MadelineProto/Conversion.php deleted file mode 100644 index b318c202..00000000 --- a/src/danog/MadelineProto/Conversion.php +++ /dev/null @@ -1,487 +0,0 @@ -. - * - * @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; - -class Conversion -{ - public static function random($length) - { - return $length === 0 ? '' : \phpseclib\Crypt\Random::string($length); - } - - public static function unpack_signed_int($value) - { - if (strlen($value) !== 4) { - throw new TL\Exception(\danog\MadelineProto\Lang::$current_lang['length_not_4']); - } - - return unpack('l', !\danog\MadelineProto\Magic::$BIG_ENDIAN ? strrev($value) : $value)[1]; - } - - public static function pack_signed_int($value) - { - if ($value > 2147483647) { - throw new TL\Exception(sprintf(\danog\MadelineProto\Lang::$current_lang['value_bigger_than_2147483647'], $value)); - } - if ($value < -2147483648) { - throw new TL\Exception(sprintf(\danog\MadelineProto\Lang::$current_lang['value_smaller_than_2147483648'], $value)); - } - $res = pack('l', $value); - - return !\danog\MadelineProto\Magic::$BIG_ENDIAN ? strrev($res) : $res; - } - - public static function old_aes_calculate($msg_key, $auth_key, $to_server = true) - { - $x = $to_server ? 0 : 8; - $sha1_a = sha1($msg_key.substr($auth_key, $x, 32), true); - $sha1_b = sha1(substr($auth_key, 32 + $x, 16).$msg_key.substr($auth_key, 48 + $x, 16), true); - $sha1_c = sha1(substr($auth_key, 64 + $x, 32).$msg_key, true); - $sha1_d = sha1($msg_key.substr($auth_key, 96 + $x, 32), true); - $aes_key = substr($sha1_a, 0, 8).substr($sha1_b, 8, 12).substr($sha1_c, 4, 12); - $aes_iv = substr($sha1_a, 8, 12).substr($sha1_b, 0, 8).substr($sha1_c, 16, 4).substr($sha1_d, 0, 8); - - return [$aes_key, $aes_iv]; - } - - public static function ige_decrypt($message, $key, $iv) - { - $cipher = new \phpseclib\Crypt\AES('ige'); - $cipher->setKey($key); - $cipher->setIV($iv); - - return @$cipher->decrypt($message); - } - - public static function telethon($session, $new_session, $settings = []) - { - set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); - if (!extension_loaded('sqlite3')) { - throw Exception::extension('sqlite3'); - } - if (!isset(pathinfo($session)['extension'])) { - $session .= '.session'; - } - $session = Absolute::absolute($session); - $sqlite = new \PDO("sqlite:$session"); - $sqlite->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_WARNING); - - $sessions = $sqlite->query('SELECT * FROM sessions')->fetchAll(); - $MadelineProto = new \danog\MadelineProto\API($new_session, $settings); - foreach ($sessions as $dc) { - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->auth_key = ['server_salt' => '', 'connection_inited' => true, 'id' => substr(sha1($dc['auth_key'], true), -8), 'auth_key' => $dc['auth_key']]; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->temp_auth_key = null; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->ip = $dc['server_address']; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->port = $dc['port']; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->authorized = true; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->session_id = $MadelineProto->random(8); - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->session_in_seq_no = 0; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->session_out_seq_no = 0; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->incoming_messages = []; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->outgoing_messages = []; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->new_outgoing = []; - $MadelineProto->API->datacenter->sockets[$dc['dc_id']]->incoming = []; - } - $MadelineProto->API->authorized = MTProto::LOGGED_IN; - $MadelineProto->API->init_authorization(); - - return $MadelineProto; - } - - public static function pyrogram($session, $new_session, $settings = []) - { - set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); - if (!isset(pathinfo($session)['extension'])) { - $session .= '.session'; - } - $session = Absolute::absolute($session); - $session = json_decode(file_get_contents($session), true); - $session['auth_key'] = base64_decode(implode('', $session['auth_key'])); - - $settings['connection_settings']['all']['test_mode'] = $session['test_mode']; - - $MadelineProto = new \danog\MadelineProto\API($new_session, $settings); - - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->auth_key = ['server_salt' => '', 'connection_inited' => true, 'id' => substr(sha1($session['auth_key'], true), -8), 'auth_key' => $session['auth_key']]; - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->temp_auth_key = null; - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->authorized = true; - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->session_id = $MadelineProto->random(8); - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->session_in_seq_no = 0; - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->session_out_seq_no = 0; - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->incoming_messages = []; - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->outgoing_messages = []; - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->new_outgoing = []; - $MadelineProto->API->datacenter->sockets[$session['dc_id']]->incoming = []; - - $MadelineProto->API->authorized = MTProto::LOGGED_IN; - $MadelineProto->API->init_authorization(); - - return $MadelineProto; - } - - public static function zerobias($session, $new_session, $settings = []) - { - set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); - if (is_string($session)) { - $session = json_decode($session, true); - } - $dc = $session['dc']; - $session['auth_key'] = hex2bin($session["dc$dc".'_auth_key']); - - $MadelineProto = new \danog\MadelineProto\API($new_session, $settings); - - $MadelineProto->API->datacenter->sockets[$dc]->auth_key = ['server_salt' => '', 'connection_inited' => true, 'id' => substr(sha1($session['auth_key'], true), -8), 'auth_key' => $session['auth_key']]; - $MadelineProto->API->datacenter->sockets[$dc]->temp_auth_key = null; - $MadelineProto->API->datacenter->sockets[$dc]->authorized = true; - $MadelineProto->API->datacenter->sockets[$dc]->session_id = $MadelineProto->random(8); - $MadelineProto->API->datacenter->sockets[$dc]->session_in_seq_no = 0; - $MadelineProto->API->datacenter->sockets[$dc]->session_out_seq_no = 0; - $MadelineProto->API->datacenter->sockets[$dc]->incoming_messages = []; - $MadelineProto->API->datacenter->sockets[$dc]->outgoing_messages = []; - $MadelineProto->API->datacenter->sockets[$dc]->new_outgoing = []; - $MadelineProto->API->datacenter->sockets[$dc]->incoming = []; - - $MadelineProto->API->authorized = MTProto::LOGGED_IN; - $MadelineProto->API->init_authorization(); - - return $MadelineProto; - } - - public static function tdesktop_md5($data) - { - $result = ''; - foreach (str_split(md5($data), 2) as $byte) { - $result .= strrev($byte); - } - - return strtoupper($result); - } - - const FILEOPTION_SAFE = 1; - const FILEOPTION_USER = 2; - public static $tdesktop_base_path; - public static $tdesktop_user_base_path; - public static $tdesktop_key; - - public static function tdesktop_fopen($fileName, $options = 3) - { - $name = ($options & self::FILEOPTION_USER ? self::$tdesktop_user_base_path : self::$tdesktop_base_path).$fileName; - $totry = []; - for ($x = 0; $x <= 1; $x++) { - if (file_exists($name.$x)) { - $totry[] = fopen($name.$x, 'rb'); - } - } - foreach ($totry as $fp) { - if (stream_get_contents($fp, 4) !== 'TDF$') { - \danog\MadelineProto\Logger::log('Wrong magic', Logger::ERROR); - continue; - } - $versionBytes = stream_get_contents($fp, 4); - $version = self::unpack_signed_int($versionBytes); - \danog\MadelineProto\Logger::log("TDesktop version: $version"); - $data = stream_get_contents($fp); - $md5 = substr($data, -16); - $data = substr($data, 0, -16); - - $length = pack('l', strlen($data)); - $length = \danog\MadelineProto\Magic::$BIG_ENDIAN ? strrev($length) : $length; - - if (md5($data.$length.$versionBytes.'TDF$', true) !== $md5) { - \danog\MadelineProto\Logger::log('Wrong MD5', Logger::ERROR); - } - $res = fopen('php://memory', 'rw+b'); - fwrite($res, $data); - fseek($res, 0); - - return $res; - } - - throw new Exception("Could not open $fileName"); - } - - public static function tdesktop_fopen_encrypted($fileName, $options = 3) - { - $f = self::tdesktop_fopen($fileName, $options); - $data = self::tdesktop_read_bytearray($f); - $res = self::tdesktop_decrypt($data, self::$tdesktop_key); - $length = unpack('V', stream_get_contents($res, 4))[1]; - - if ($length > fstat($res)['size'] || $length < 4) { - throw new \danog\MadelineProto\Exception('Wrong length'); - } - - return $res; - } - - public static function tdesktop_read_bytearray($fp) - { - $length = self::unpack_signed_int(stream_get_contents($fp, 4)); - $data = $length ? stream_get_contents($fp, $length) : ''; - $res = fopen('php://memory', 'rw+b'); - fwrite($res, $data); - fseek($res, 0); - - return $res; - } - - public static function tdesktop_decrypt($data, $auth_key) - { - $message_key = stream_get_contents($data, 16); - $encrypted_data = stream_get_contents($data); - - list($aes_key, $aes_iv) = self::old_aes_calculate($message_key, $auth_key, false); - $decrypted_data = self::ige_decrypt($encrypted_data, $aes_key, $aes_iv); - - if ($message_key != substr(sha1($decrypted_data, true), 0, 16)) { - throw new \danog\MadelineProto\SecurityException('msg_key mismatch'); - } - - $res = fopen('php://memory', 'rw+b'); - fwrite($res, $decrypted_data); - fseek($res, 0); - - return $res; - } - - const dbiKey = 0x00; - const dbiUser = 0x01; - const dbiDcOptionOldOld = 0x02; - const dbiChatSizeMax = 0x03; - const dbiMutePeer = 0x04; - const dbiSendKey = 0x05; - const dbiAutoStart = 0x06; - const dbiStartMinimized = 0x07; - const dbiSoundNotify = 0x08; - const dbiWorkMode = 0x09; - const dbiSeenTrayTooltip = 0x0a; - const dbiDesktopNotify = 0x0b; - const dbiAutoUpdate = 0x0c; - const dbiLastUpdateCheck = 0x0d; - const dbiWindowPosition = 0x0e; - const dbiConnectionTypeOld = 0x0f; - // 0x10 reserved - const dbiDefaultAttach = 0x11; - const dbiCatsAndDogs = 0x12; - const dbiReplaceEmojis = 0x13; - const dbiAskDownloadPath = 0x14; - const dbiDownloadPathOld = 0x15; - const dbiScale = 0x16; - const dbiEmojiTabOld = 0x17; - const dbiRecentEmojiOldOld = 0x18; - const dbiLoggedPhoneNumber = 0x19; - const dbiMutedPeers = 0x1a; - // 0x1b reserved - const dbiNotifyView = 0x1c; - const dbiSendToMenu = 0x1d; - const dbiCompressPastedImage = 0x1e; - const dbiLangOld = 0x1f; - const dbiLangFileOld = 0x20; - const dbiTileBackground = 0x21; - const dbiAutoLock = 0x22; - const dbiDialogLastPath = 0x23; - const dbiRecentEmojiOld = 0x24; - const dbiEmojiVariantsOld = 0x25; - const dbiRecentStickers = 0x26; - const dbiDcOptionOld = 0x27; - const dbiTryIPv6 = 0x28; - const dbiSongVolume = 0x29; - const dbiWindowsNotificationsOld = 0x30; - const dbiIncludeMuted = 0x31; - const dbiMegagroupSizeMax = 0x32; - const dbiDownloadPath = 0x33; - const dbiAutoDownload = 0x34; - const dbiSavedGifsLimit = 0x35; - const dbiShowingSavedGifsOld = 0x36; - const dbiAutoPlay = 0x37; - const dbiAdaptiveForWide = 0x38; - const dbiHiddenPinnedMessages = 0x39; - const dbiRecentEmoji = 0x3a; - const dbiEmojiVariants = 0x3b; - const dbiDialogsMode = 0x40; - const dbiModerateMode = 0x41; - const dbiVideoVolume = 0x42; - const dbiStickersRecentLimit = 0x43; - const dbiNativeNotifications = 0x44; - const dbiNotificationsCount = 0x45; - const dbiNotificationsCorner = 0x46; - const dbiThemeKey = 0x47; - const dbiDialogsWidthRatioOld = 0x48; - const dbiUseExternalVideoPlayer = 0x49; - const dbiDcOptions = 0x4a; - const dbiMtpAuthorization = 0x4b; - const dbiLastSeenWarningSeenOld = 0x4c; - const dbiAuthSessionSettings = 0x4d; - const dbiLangPackKey = 0x4e; - const dbiConnectionType = 0x4f; - const dbiStickersFavedLimit = 0x50; - const dbiSuggestStickersByEmoji = 0x51; - - const dbiEncryptedWithSalt = 333; - const dbiEncrypted = 444; - - // 500-600 reserved - - const dbiVersion = 666; - - public static function tdesktop($session, $new_session, $settings = []) - { - set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); - if (!isset($settings['old_session_key'])) { - $settings['old_session_key'] = 'data'; - } - if (!isset($settings['old_session_passcode'])) { - $settings['old_session_passcode'] = ''; - } - - if (basename($session) !== 'tdata') { - $session .= '/tdata'; - } - - list($part_one_md5, $part_two_md5) = str_split(self::tdesktop_md5($settings['old_session_key']), 16); - self::$tdesktop_base_path = $session.'/'; - self::$tdesktop_user_base_path = self::$tdesktop_base_path.$part_one_md5.'/'; - - $data = self::tdesktop_fopen('map'); - - $salt = self::tdesktop_read_bytearray($data); - $salt = fstat($salt)['size'] ? $salt : self::random(32); - $encryptedKey = self::tdesktop_read_bytearray($data); - - $keyIterCount = strlen($settings['old_session_passcode']) ? 4000 : 4; - - $passKey = openssl_pbkdf2($settings['old_session_passcode'], stream_get_contents($salt), 256, $keyIterCount); - self::$tdesktop_key = stream_get_contents(self::tdesktop_read_bytearray(self::tdesktop_decrypt($encryptedKey, $passKey))); - - $main = self::tdesktop_fopen_encrypted($part_one_md5, self::FILEOPTION_SAFE); - $auth_keys = []; - - while (true) { - $magic = self::unpack_signed_int(stream_get_contents($main, 4)); - switch ($magic) { - case self::dbiDcOptionOldOld: - stream_get_contents($main, 4); - self::tdesktop_read_bytearray($main); - self::tdesktop_read_bytearray($main); - stream_get_contents($main, 4); - break; - case self::dbiDcOptionOld: - stream_get_contents($main, 8); - self::tdesktop_read_bytearray($main); - stream_get_contents($main, 4); - break; - case self::dbiDcOptions: - self::tdesktop_read_bytearray($main); - break; - case self::dbiUser: - stream_get_contents($main, 4); - $main_dc_id = self::unpack_signed_int(stream_get_contents($main, 4)); - break; - case self::dbiKey: - $auth_keys[self::unpack_signed_int(stream_get_contents($main, 4))] = stream_get_contents($main, 256); - break; - case self::dbiMtpAuthorization: - $main = self::tdesktop_read_bytearray($main); - //stream_get_contents($main, 4); - $user_id = self::unpack_signed_int(stream_get_contents($main, 4)); - $main_dc_id = self::unpack_signed_int(stream_get_contents($main, 4)); - $length = self::unpack_signed_int(stream_get_contents($main, 4)); - for ($x = 0; $x < $length; $x++) { - $auth_keys[self::unpack_signed_int(stream_get_contents($main, 4))] = stream_get_contents($main, 256); - } - break 2; - case self::dbiAutoDownload: - stream_get_contents($main, 12); - break; - case self::dbiDialogsMode: - stream_get_contents($main, 8); - break; - case self::dbiAuthSessionSettings: - self::tdesktop_read_bytearray($main); - break; - case self::dbiConnectionTypeOld: - switch (self::unpack_signed_int(stream_get_contents($main, 4))) { - case 2: - case 3: - self::tdesktop_read_bytearray($main); - stream_get_contents($main, 4); - self::tdesktop_read_bytearray($main); - self::tdesktop_read_bytearray($main); - break; - } - break; - case self::dbiConnectionType: - stream_get_contents($main, 8); - self::tdesktop_read_bytearray($main); - stream_get_contents($main, 4); - self::tdesktop_read_bytearray($main); - self::tdesktop_read_bytearray($main); - break; - case self::dbiThemeKey: - case self::dbiLangPackKey: - case self::dbiMutePeer: - stream_get_contents($main, 8); - break; - case self::dbiWindowPosition: - stream_get_contents($main, 24); - break; - case self::dbiLoggedPhoneNumber: - self::tdesktop_read_bytearray($main); - break; - case self::dbiMutedPeers: - $length = self::unpack_signed_int(stream_get_contents($main, 4)); - for ($x = 0; $x < $length; $x++) { - stream_get_contents($main, 8); - } - case self::dbiDownloadPathOld: - self::tdesktop_read_bytearray($main); - break; - case self::dbiDialogLastPath: - self::tdesktop_read_bytearray($main); - break; - case self::dbiDownloadPath: - self::tdesktop_read_bytearray($main); - self::tdesktop_read_bytearray($main); - break; - default: - stream_get_contents($main, 4); - break; - } - } - $MadelineProto = new \danog\MadelineProto\API($new_session, $settings); - foreach ($auth_keys as $dc => $auth_key) { - $MadelineProto->API->datacenter->sockets[$dc]->auth_key = ['server_salt' => '', 'connection_inited' => true, 'id' => substr(sha1($auth_key, true), -8), 'auth_key' => $auth_key]; - $MadelineProto->API->datacenter->sockets[$dc]->temp_auth_key = null; - $MadelineProto->API->datacenter->sockets[$dc]->authorized = true; - $MadelineProto->API->datacenter->sockets[$dc]->session_id = $MadelineProto->random(8); - $MadelineProto->API->datacenter->sockets[$dc]->session_in_seq_no = 0; - $MadelineProto->API->datacenter->sockets[$dc]->session_out_seq_no = 0; - $MadelineProto->API->datacenter->sockets[$dc]->incoming_messages = []; - $MadelineProto->API->datacenter->sockets[$dc]->outgoing_messages = []; - $MadelineProto->API->datacenter->sockets[$dc]->new_outgoing = []; - $MadelineProto->API->datacenter->sockets[$dc]->incoming = []; - } - $MadelineProto->API->authorized = MTProto::LOGGED_IN; - $MadelineProto->API->authorized_dc = $main_dc_id; - $MadelineProto->API->init_authorization(); - - return $MadelineProto; - } -} diff --git a/src/danog/MadelineProto/DataCenter.php b/src/danog/MadelineProto/DataCenter.php index c38d3afa..4ad11a19 100644 --- a/src/danog/MadelineProto/DataCenter.php +++ b/src/danog/MadelineProto/DataCenter.php @@ -41,6 +41,7 @@ use Amp\Socket\ClientTlsContext; use Amp\Socket\ConnectException; use Amp\Socket\Socket; use Amp\TimeoutException; +use danog\MadelineProto\AuthKey\AuthKey; use danog\MadelineProto\Stream\Common\BufferedRawStream; use danog\MadelineProto\Stream\ConnectionContext; use danog\MadelineProto\Stream\MTProtoTransport\AbridgedStream; @@ -65,14 +66,59 @@ class DataCenter { use \danog\MadelineProto\Tools; use \danog\Serializable; + /** + * All socket connections to DCs. + * + * @var array + */ public $sockets = []; + /** + * Current DC ID. + * + * @var string + */ public $curdc = 0; + /** + * Main instance. + * + * @var MTProto + */ private $API; + /** + * DC list. + * + * @var array + */ private $dclist = []; + /** + * Settings. + * + * @var array + */ private $settings = []; + /** + * HTTP client. + * + * @var \Amp\Artax\Client + */ private $HTTPClient; + /** + * DNS over HTTPS client. + * + * @var \Amp\DoH\Rfc8484StubResolver + */ private $DoHClient; + /** + * Non-proxied DNS over HTTPS client. + * + * @var \Amp\DoH\Rfc8484StubResolver + */ private $NonProxiedDoHClient; + /** + * Cookie jar. + * + * @var \Amp\Artax\Cookie\CookieJar + */ private $CookieJar; public function __sleep() @@ -86,10 +132,10 @@ class DataCenter if ($socket instanceof Connection) { $new = new DataCenterConnection; if ($socket->temp_auth_key) { - $new->setAuthKey($socket->temp_auth_key, true); + $new->setAuthKey(new AuthKey($socket->temp_auth_key), true); } if ($socket->auth_key) { - $new->setAuthKey($socket->auth_key, false); + $new->setAuthKey(new AuthKey($socket->auth_key), false); } $new->authorized($socket->authorized); } @@ -101,8 +147,8 @@ class DataCenter $this->dclist = $dclist; $this->settings = $settings; foreach ($this->sockets as $key => $socket) { - if ($socket instanceof Connection && !strpos($key, '_bk')) { - $this->API->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['dc_con_stop'], $key), \danog\MadelineProto\Logger::VERBOSE); + if ($socket instanceof Connection && !\strpos($key, '_bk')) { + $this->API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['dc_con_stop'], $key), \danog\MadelineProto\Logger::VERBOSE); $socket->old = true; $socket->setExtra($this->API); $socket->disconnect(); @@ -226,7 +272,7 @@ class DataCenter list($scheme, $host, $port) = parseUri($uri); if ($host[0] === '[') { - $host = substr($host, 1, -1); + $host = \substr($host, 1, -1); } if ($port === 0 || @\inet_pton($host)) { @@ -265,7 +311,7 @@ class DataCenter return $a->getType() - $b->getType(); }); if ($ctx->getIpv6()) { - $records = array_reverse($records); + $records = \array_reverse($records); } foreach ($records as $record) { @@ -344,8 +390,7 @@ class DataCenter continue; // Could not connect to host, try next host in the list. } if ($ctx->hasReadCallback()) { - $socket = new class($socket) extends ClientSocket - { + $socket = new class($socket) extends ClientSocket { private $callback; public function setReadCallback($callback) { @@ -393,7 +438,7 @@ class DataCenter return $result->getSocket(); } catch (\Throwable $e) { - if (defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') { + if (\defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') { throw $e; } $this->API->logger->logger('Connection failed: '.$e, \danog\MadelineProto\Logger::ERROR); @@ -420,7 +465,7 @@ class DataCenter $this->sockets[$dc_number]->setExtra($this->API); yield $this->sockets[$dc_number]->connect($ctx); } else { - $this->sockets[$dc_number] = new Connection(); + $this->sockets[$dc_number] = new DataCenterConnection(); $this->sockets[$dc_number]->setExtra($this->API); yield $this->sockets[$dc_number]->connect($ctx); } @@ -428,7 +473,7 @@ class DataCenter return true; } catch (\Throwable $e) { - if (defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') { + if (\defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') { throw $e; } $this->API->logger->logger('Connection failed: '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR); @@ -459,6 +504,7 @@ class DataCenter case 'obfuscated2': $this->settings[$dc_config_number]['protocol'] = 'tcp_intermediate_padded'; $this->settings[$dc_config_number]['obfuscated'] = true; + // no break case 'tcp_intermediate_padded': $default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [IntermediatePaddedStream::getName(), []]]; break; @@ -474,21 +520,21 @@ class DataCenter default: throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_invalid']); } - if ($this->settings[$dc_config_number]['obfuscated'] && !in_array($default[2][0], [HttpsStream::getName(), HttpStream::getName()])) { - $default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)]; + if ($this->settings[$dc_config_number]['obfuscated'] && !\in_array($default[2][0], [HttpsStream::getName(), HttpStream::getName()])) { + $default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)]; } - if ($this->settings[$dc_config_number]['transport'] && !in_array($default[2][0], [HttpsStream::getName(), HttpStream::getName()])) { + if ($this->settings[$dc_config_number]['transport'] && !\in_array($default[2][0], [HttpsStream::getName(), HttpStream::getName()])) { switch ($this->settings[$dc_config_number]['transport']) { case 'tcp': if ($this->settings[$dc_config_number]['obfuscated']) { - $default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)]; + $default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)]; } break; case 'wss': - $default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)]; + $default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)]; break; case 'ws': - $default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)]; + $default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)]; break; } } @@ -503,7 +549,7 @@ class DataCenter $combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), $extra], [IntermediatePaddedStream::getName(), []]]; } - if (is_iterable($this->settings[$dc_config_number]['proxy'])) { + if (\is_iterable($this->settings[$dc_config_number]['proxy'])) { $proxies = $this->settings[$dc_config_number]['proxy']; $proxy_extras = $this->settings[$dc_config_number]['proxy_extra']; } else { @@ -531,37 +577,37 @@ class DataCenter continue; } $extra = $proxy_extras[$key]; - if (!isset(class_implements($proxy)['danog\\MadelineProto\\Stream\\StreamInterface'])) { + if (!isset(\class_implements($proxy)['danog\\MadelineProto\\Stream\\StreamInterface'])) { throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['proxy_class_invalid']); } - if ($proxy === ObfuscatedStream::getName() && in_array(strlen($extra['secret']), [17, 34])) { + if ($proxy === ObfuscatedStream::getName() && \in_array(\strlen($extra['secret']), [17, 34])) { $combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [$proxy, $extra], [IntermediatePaddedStream::getName(), []]]; } foreach ($combos as $k => $orig) { $combo = []; if ($proxy === ObfuscatedStream::getName()) { $combo = $orig; - if ($combo[count($combo) - 2][0] === ObfuscatedStream::getName()) { - $combo[count($combo) - 2][1] = $extra; + if ($combo[\count($combo) - 2][0] === ObfuscatedStream::getName()) { + $combo[\count($combo) - 2][1] = $extra; } else { - $mtproto = end($combo); - $combo[count($combo) - 1] = [$proxy, $extra]; + $mtproto = \end($combo); + $combo[\count($combo) - 1] = [$proxy, $extra]; $combo[] = $mtproto; } } else { if ($orig[1][0] === BufferedRawStream::getName()) { - list($first, $second) = [array_slice($orig, 0, 2), array_slice($orig, 2)]; + list($first, $second) = [\array_slice($orig, 0, 2), \array_slice($orig, 2)]; $first[] = [$proxy, $extra]; - $combo = array_merge($first, $second); - } elseif (in_array($orig[1][0], [WsStream::getName(), WssStream::getName()])) { - list($first, $second) = [array_slice($orig, 0, 1), array_slice($orig, 1)]; + $combo = \array_merge($first, $second); + } elseif (\in_array($orig[1][0], [WsStream::getName(), WssStream::getName()])) { + list($first, $second) = [\array_slice($orig, 0, 1), \array_slice($orig, 1)]; $first[] = [BufferedRawStream::getName(), []]; $first[] = [$proxy, $extra]; - $combo = array_merge($first, $second); + $combo = \array_merge($first, $second); } } - array_unshift($combos, $combo); + \array_unshift($combos, $combo); //unset($combos[$k]); } } @@ -569,7 +615,7 @@ class DataCenter if ($dc_number) { $combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]]; } - $combos = array_unique($combos, SORT_REGULAR); + $combos = \array_unique($combos, SORT_REGULAR); } /* @var $context \Amp\ClientConnectContext */ $context = $context ?? (new ClientConnectContext())->withMaxAttempts(1)->withConnectTimeout(1000 * $this->settings[$dc_config_number]['timeout']); @@ -620,12 +666,12 @@ class DataCenter $address = $this->dclist[$test][$ipv6][$dc_number]['ip_address']; $port = $this->dclist[$test][$ipv6][$dc_number]['port']; - foreach (array_unique([$port, 443, 80, 88, 5222]) as $port) { - $stream = end($combo)[0]; + foreach (\array_unique([$port, 443, 80, 88, 5222]) as $port) { + $stream = \end($combo)[0]; if ($stream === HttpsStream::getName()) { - $subdomain = $this->dclist['ssl_subdomains'][preg_replace('/\D+/', '', $dc_number)]; - if (strpos($dc_number, '_media') !== false) { + $subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\D+/', '', $dc_number)]; + if (\strpos($dc_number, '_media') !== false) { $subdomain .= '-1'; } $path = $this->settings[$dc_config_number]['test_mode'] ? 'apiw_test1' : 'apiw1'; @@ -638,16 +684,16 @@ class DataCenter } if ($combo[1][0] === WssStream::getName()) { - $subdomain = $this->dclist['ssl_subdomains'][preg_replace('/\D+/', '', $dc_number)]; - if (strpos($dc_number, '_media') !== false) { + $subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\D+/', '', $dc_number)]; + if (\strpos($dc_number, '_media') !== false) { $subdomain .= '-1'; } $path = $this->settings[$dc_config_number]['test_mode'] ? 'apiws_test' : 'apiws'; $uri = 'tcp://'.$subdomain.'.'.'web.telegram.org'.':'.$port.'/'.$path; } elseif ($combo[1][0] === WsStream::getName()) { - $subdomain = $this->dclist['ssl_subdomains'][preg_replace('/\D+/', '', $dc_number)]; - if (strpos($dc_number, '_media') !== false) { + $subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\D+/', '', $dc_number)]; + if (\strpos($dc_number, '_media') !== false) { $subdomain .= '-1'; } $path = $this->settings[$dc_config_number]['test_mode'] ? 'apiws_test' : 'apiws'; @@ -692,14 +738,14 @@ class DataCenter } if (isset($this->dclist[$test][$ipv6][$dc_number.'_bk']['ip_address'])) { - $ctxs = array_merge($ctxs, $this->generateContexts($dc_number.'_bk')); + $ctxs = \array_merge($ctxs, $this->generateContexts($dc_number.'_bk')); } if (empty($ctxs)) { unset($this->sockets[$dc_number]); $this->API->logger->logger("No info for DC $dc_number", \danog\MadelineProto\Logger::ERROR); - } elseif (defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') { + } elseif (\defined('MADELINEPROTO_TEST') && MADELINEPROTO_TEST === 'pony') { return [$ctxs[0]]; } @@ -741,9 +787,10 @@ class DataCenter } /** - * Get Connection instance + * Get Connection instance. + * + * @param string $dc DC ID * - * @param string $dc * @return Connection */ public function getConnection(string $dc): Connection @@ -751,10 +798,30 @@ class DataCenter return $this->sockets[$dc]->getConnection(); } /** - * Check if a DC is present + * Get DataCenterConnection instance. * * @param string $dc DC ID - * + * + * @return DataCenterConnection + */ + public function getDataCenterConnection(string $dc): DataCenterConnection + { + return $this->sockets[$dc]; + } + /** + * Get all DataCenterConnection instances. + * + * @return array + */ + public function getDataCenterConnections(): array + { + return $this->sockets; + } + /** + * Check if a DC is present. + * + * @param string $dc DC ID + * * @return boolean */ public function has(string $dc): bool @@ -766,6 +833,6 @@ class DataCenter $test = $this->settings['all']['test_mode'] ? 'test' : 'main'; $ipv6 = $this->settings['all']['ipv6'] ? 'ipv6' : 'ipv4'; - return $all ? array_keys((array) $this->dclist[$test][$ipv6]) : array_keys((array) $this->sockets); + return $all ? \array_keys((array) $this->dclist[$test][$ipv6]) : \array_keys((array) $this->sockets); } } diff --git a/src/danog/MadelineProto/DataCenterConnection.php b/src/danog/MadelineProto/DataCenterConnection.php index bea52e68..c5b6a48f 100644 --- a/src/danog/MadelineProto/DataCenterConnection.php +++ b/src/danog/MadelineProto/DataCenterConnection.php @@ -18,21 +18,22 @@ namespace danog\MadelineProto; -use Amp\Promise; +use danog\MadelineProto\AuthKey\AuthKey; use danog\MadelineProto\Stream\ConnectionContext; +use JsonSerializable; -class DataCenterConnection +class DataCenterConnection implements JsonSerializable { /** * Temporary auth key. * - * @var array + * @var AuthKey */ private $tempAuthKey; /** * Permanent auth key. * - * @var array + * @var AuthKey */ private $authKey; @@ -46,13 +47,13 @@ class DataCenterConnection /** * Connections open to a certain DC. * - * @var array + * @var array */ private $connections = []; /** - * Connection weights + * Connection weights. * - * @var array + * @var array */ private $availableConnections = []; @@ -85,7 +86,7 @@ class DataCenterConnection private $index = 0; /** - * Loop to keep weights at sane value + * Loop to keep weights at sane value. * * @var \danog\MadelineProto\Loop\Generic\PeriodicLoop */ @@ -96,9 +97,9 @@ class DataCenterConnection * * @param boolean $temp Whether to fetch the temporary auth key * - * @return array + * @return AuthKey */ - public function getAuthKey(bool $temp = true): array + public function getAuthKey(bool $temp = true): AuthKey { return $this->{$temp ? 'tempAuthKey' : 'authKey'}; } @@ -116,11 +117,12 @@ class DataCenterConnection /** * Set auth key. * - * @param boolean $temp Whether to fetch the temporary auth key + * @param AuthKey|null $key The auth key + * @param boolean $temp Whether to set the temporary auth key * * @return void */ - public function setAuthKey(array $key, bool $temp = true) + public function setAuthKey(?AuthKey $key, bool $temp = true) { $this->{$temp ? 'tempAuthKey' : 'authKey'} = $key; } @@ -147,6 +149,28 @@ class DataCenterConnection $this->authorized = $authorized; } + /** + * Reset MTProto sessions. + * + * @return void + */ + public function resetSession() + { + foreach ($this->connections as $socket) { + $socket->resetSession(); + } + } + /** + * Flush all pending packets. + * + * @return void + */ + public function flush() + { + foreach ($this->connections as $socket) { + $socket->flush(); + } + } /** * Get connection context. * @@ -198,13 +222,13 @@ class DataCenterConnection $this->availableConnections[$x] += $writing ? -10 : 10; } ); - yield $this->connections[$x]->connect(yield $ctx->getStream()); + yield $this->connections[$x]->connect($ctx); $ctx = $this->ctx->getCtx(); } } /** - * Close all connections to DC + * Close all connections to DC. * * @return void */ @@ -223,7 +247,7 @@ class DataCenterConnection } /** - * Reconnect to DC + * Reconnect to DC. * * @return \Generator */ @@ -241,18 +265,18 @@ class DataCenterConnection */ public function getConnection(): Connection { - if (count($this->availableConnections) === 1) { + if (\count($this->availableConnections) === 1) { return $this->connections[0]; } - max($this->availableConnections); - $key = key($this->availableConnections); + \max($this->availableConnections); + $key = \key($this->availableConnections); // Decrease to implement round robin $this->availableConnections[$key]--; return $this->connections[$key]; } /** - * Even out round robin values + * Even out round robin values. * * @return void */ @@ -266,10 +290,10 @@ class DataCenterConnection } /** - * Set main instance + * Set main instance. * * @param MTProto $API Main instance - * + * * @return void */ public function setExtra(MTProto $API) @@ -278,7 +302,7 @@ class DataCenterConnection } /** - * Get main instance + * Get main instance. * * @return MTProto */ @@ -286,6 +310,19 @@ class DataCenterConnection { return $this->API; } + /** + * JSON serialize function. + * + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'authKey' => $this->authKey, + 'tempAuthKey' => $this->tempAuthKey, + 'authorized' => $this->authorized, + ]; + } /** * Sleep function. * diff --git a/src/danog/MadelineProto/Loop/Connection/CheckLoop.php b/src/danog/MadelineProto/Loop/Connection/CheckLoop.php index 2f70e575..83887f54 100644 --- a/src/danog/MadelineProto/Loop/Connection/CheckLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/CheckLoop.php @@ -19,8 +19,8 @@ namespace danog\MadelineProto\Loop\Connection; use Amp\Deferred; +use danog\MadelineProto\Connection; use danog\MadelineProto\Loop\Impl\ResumableSignalLoop; -use danog\MadelineProto\MTProto; /** * RPC call status check loop. @@ -30,13 +30,13 @@ use danog\MadelineProto\MTProto; class CheckLoop extends ResumableSignalLoop { /** - * Connection instance + * Connection instance. * * @var \danog\Madelineproto\Connection */ protected $connection; /** - * DC ID + * DC ID. * * @var string */ @@ -72,7 +72,7 @@ class CheckLoop extends ResumableSignalLoop if ($connection->temp_auth_key !== null) { $full_message_ids = $connection->getPendingCalls(); //array_values($connection->new_outgoing); - foreach (array_chunk($full_message_ids, 8192) as $message_ids) { + foreach (\array_chunk($full_message_ids, 8192) as $message_ids) { $deferred = new Deferred(); $deferred->promise()->onResolve( function ($e, $result) use ($message_ids, $API, $connection, $datacenter) { @@ -83,7 +83,7 @@ class CheckLoop extends ResumableSignalLoop return; } $reply = []; - foreach (str_split($result['info']) as $key => $chr) { + foreach (\str_split($result['info']) as $key => $chr) { $message_id = $message_ids[$key]; if (!isset($connection->outgoing_messages[$message_id])) { $API->logger->logger('Already got response for and forgot about message ID '.($message_id)); @@ -93,7 +93,7 @@ class CheckLoop extends ResumableSignalLoop $API->logger->logger('Already got response for '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id)); continue; } - $chr = ord($chr); + $chr = \ord($chr); switch ($chr & 7) { case 0: $API->logger->logger('Wrong message status 0 for '.$connection->outgoing_messages[$message_id]['_'], \danog\MadelineProto\Logger::FATAL_ERROR); @@ -140,7 +140,7 @@ class CheckLoop extends ResumableSignalLoop } else { foreach ($connection->new_outgoing as $message_id) { if (isset($connection->outgoing_messages[$message_id]['sent']) - && $connection->outgoing_messages[$message_id]['sent'] + $timeout < time() + && $connection->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $connection->outgoing_messages[$message_id]['unencrypted'] ) { $API->logger->logger('Still missing '.$connection->outgoing_messages[$message_id]['_'].' with message id '.($message_id)." on DC $datacenter, resending", \danog\MadelineProto\Logger::ERROR); diff --git a/src/danog/MadelineProto/Loop/Connection/HttpWaitLoop.php b/src/danog/MadelineProto/Loop/Connection/HttpWaitLoop.php index b65eda42..422a7fae 100644 --- a/src/danog/MadelineProto/Loop/Connection/HttpWaitLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/HttpWaitLoop.php @@ -31,13 +31,13 @@ use danog\MadelineProto\Stream\MTProtoTransport\HttpStream; class HttpWaitLoop extends ResumableSignalLoop { /** - * Connection instance + * Connection instance. * * @var \danog\Madelineproto\Connection */ protected $connection; /** - * DC ID + * DC ID. * * @var string */ @@ -57,7 +57,7 @@ class HttpWaitLoop extends ResumableSignalLoop $datacenter = $this->datacenter; $connection = $this->connection; - if (!in_array($connection->getCtx()->getStreamName(), [HttpStream::getName(), HttpsStream::getName()])) { + if (!\in_array($connection->getCtx()->getStreamName(), [HttpStream::getName(), HttpsStream::getName()])) { return; } @@ -66,7 +66,7 @@ class HttpWaitLoop extends ResumableSignalLoop if (yield $this->waitSignal($this->pause())) { return; } - if (!in_array($connection->getCtx()->getStreamName(), [HttpStream::getName(), HttpsStream::getName()])) { + if (!\in_array($connection->getCtx()->getStreamName(), [HttpStream::getName(), HttpsStream::getName()])) { return; } while ($connection->temp_auth_key === null) { diff --git a/src/danog/MadelineProto/Loop/Connection/ReadLoop.php b/src/danog/MadelineProto/Loop/Connection/ReadLoop.php index 6edafb26..8a7d44e5 100644 --- a/src/danog/MadelineProto/Loop/Connection/ReadLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/ReadLoop.php @@ -22,6 +22,7 @@ use Amp\ByteStream\PendingReadError; use Amp\ByteStream\StreamException; use Amp\Loop; use Amp\Websocket\ClosedException; +use danog\MadelineProto\Connection; use danog\MadelineProto\Logger; use danog\MadelineProto\Loop\Impl\SignalLoop; use danog\MadelineProto\MTProtoTools\Crypt; @@ -39,13 +40,13 @@ class ReadLoop extends SignalLoop use Crypt; /** - * Connection instance + * Connection instance. * * @var \danog\Madelineproto\Connection */ protected $connection; /** - * DC ID + * DC ID. * * @var string */ @@ -79,7 +80,7 @@ class ReadLoop extends SignalLoop continue; } - if (is_int($error)) { + if (\is_int($error)) { $this->exitedLoop(); if ($error === -404) { @@ -135,8 +136,8 @@ class ReadLoop extends SignalLoop $buffer = yield $connection->stream->getReadBuffer($payload_length); } catch (ClosedException $e) { $API->logger->logger($e->getReason()); - if (strpos($e->getReason(), ' ') === 0) { - $payload = -substr($e->getReason(), 7); + if (\strpos($e->getReason(), ' ') === 0) { + $payload = -\substr($e->getReason(), 7); $API->logger->logger("Received $payload from DC ".$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE); return $payload; @@ -155,10 +156,10 @@ class ReadLoop extends SignalLoop 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])) { + if (!\in_array($message_id, [1, 0])) { $connection->check_message_id($message_id, ['outgoing' => false, 'container' => false]); } - $message_length = unpack('V', yield $buffer->bufferRead(4))[1]; + $message_length = \unpack('V', yield $buffer->bufferRead(4))[1]; $message_data = yield $buffer->bufferRead($message_length); $left = $payload_length - $message_length - 4 - 8 - 8; if ($left) { @@ -174,9 +175,9 @@ class ReadLoop extends SignalLoop list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $connection->temp_auth_key['auth_key'], false); $encrypted_data = yield $buffer->bufferRead($payload_length - 24); - $protocol_padding = strlen($encrypted_data) % 16; + $protocol_padding = \strlen($encrypted_data) % 16; if ($protocol_padding) { - $encrypted_data = substr($encrypted_data, 0, -$protocol_padding); + $encrypted_data = \substr($encrypted_data, 0, -$protocol_padding); } $decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv); /* @@ -185,22 +186,22 @@ class ReadLoop extends SignalLoop $API->logger->logger('WARNING: Server salt mismatch (my server salt '.$connection->temp_auth_key['server_salt'].' is not equal to server server salt '.$server_salt.').', \danog\MadelineProto\Logger::WARNING); } */ - $session_id = substr($decrypted_data, 8, 8); + $session_id = \substr($decrypted_data, 8, 8); if ($session_id != $connection->session_id) { throw new \danog\MadelineProto\Exception('Session id mismatch.'); } - $message_id = substr($decrypted_data, 16, 8); + $message_id = \substr($decrypted_data, 16, 8); $connection->check_message_id($message_id, ['outgoing' => false, 'container' => false]); - $seq_no = unpack('V', substr($decrypted_data, 24, 4))[1]; + $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)) { + $message_data_length = \unpack('V', \substr($decrypted_data, 28, 4))[1]; + if ($message_data_length > \strlen($decrypted_data)) { throw new \danog\MadelineProto\SecurityException('message_data_length is too big'); } - if (strlen($decrypted_data) - 32 - $message_data_length < 12) { + if (\strlen($decrypted_data) - 32 - $message_data_length < 12) { throw new \danog\MadelineProto\SecurityException('padding is too small'); } - if (strlen($decrypted_data) - 32 - $message_data_length > 1024) { + if (\strlen($decrypted_data) - 32 - $message_data_length > 1024) { throw new \danog\MadelineProto\SecurityException('padding is too big'); } if ($message_data_length < 0) { @@ -209,8 +210,8 @@ class ReadLoop extends SignalLoop if ($message_data_length % 4 != 0) { throw new \danog\MadelineProto\SecurityException('message_data_length not divisible by 4'); } - $message_data = substr($decrypted_data, 32, $message_data_length); - if ($message_key != substr(hash('sha256', substr($connection->temp_auth_key['auth_key'], 96, 32).$decrypted_data, true), 8, 16)) { + $message_data = \substr($decrypted_data, 32, $message_data_length); + if ($message_key != \substr(\hash('sha256', \substr($connection->temp_auth_key['auth_key'], 96, 32).$decrypted_data, true), 8, 16)) { throw new \danog\MadelineProto\SecurityException('msg_key mismatch'); } $connection->incoming_messages[$message_id] = ['seq_no' => $seq_no]; diff --git a/src/danog/MadelineProto/Loop/Connection/WriteLoop.php b/src/danog/MadelineProto/Loop/Connection/WriteLoop.php index c4ae81bb..c6dc0983 100644 --- a/src/danog/MadelineProto/Loop/Connection/WriteLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/WriteLoop.php @@ -18,13 +18,13 @@ namespace danog\MadelineProto\Loop\Connection; +use Amp\ByteStream\StreamException; use danog\MadelineProto\Connection; use danog\MadelineProto\Logger; use danog\MadelineProto\Loop\Impl\ResumableSignalLoop; use danog\MadelineProto\Magic; use danog\MadelineProto\MTProtoTools\Crypt; use danog\MadelineProto\Tools; -use Amp\ByteStream\StreamException; /** * Socket write loop. @@ -37,13 +37,13 @@ class WriteLoop extends ResumableSignalLoop use Tools; /** - * Connection instance + * Connection instance. * * @var \danog\Madelineproto\Connection */ protected $connection; /** - * DC ID + * DC ID. * * @var string */ @@ -110,7 +110,7 @@ class WriteLoop extends ResumableSignalLoop $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->generate_message_id(); - $length = strlen($message['serialized_body']); + $length = \strlen($message['serialized_body']); $pad_length = -$length & 15; $pad_length += 16 * $this->random_int($modulus = 16); @@ -123,7 +123,7 @@ class WriteLoop extends ResumableSignalLoop //var_dump("plain ".bin2hex($message_id)); $connection->httpSent(); $connection->outgoing_messages[$message_id] = $message; - $connection->outgoing_messages[$message_id]['sent'] = time(); + $connection->outgoing_messages[$message_id]['sent'] = \time(); $connection->outgoing_messages[$message_id]['tries'] = 0; $connection->outgoing_messages[$message_id]['unencrypted'] = true; $connection->new_outgoing[$message_id] = $message_id; @@ -155,8 +155,8 @@ class WriteLoop extends ResumableSignalLoop if ($this->API->is_http($datacenter) && empty($connection->pending_outgoing)) { return; } - if (count($to_ack = $connection->ack_queue)) { - foreach (array_chunk($connection->ack_queue, 8192) as $acks) { + if (\count($to_ack = $connection->ack_queue)) { + foreach (\array_chunk($connection->ack_queue, 8192) as $acks) { $connection->pending_outgoing[$connection->pending_outgoing_key++] = ['_' => 'msgs_ack', 'serialized_body' => yield $this->API->serialize_object_async(['type' => 'msgs_ack'], ['msg_ids' => $acks], 'msgs_ack'), 'content_related' => false, 'unencrypted' => false, 'method' => false]; $connection->pending_outgoing_key %= Connection::PENDING_MAX; } @@ -180,7 +180,7 @@ class WriteLoop extends ResumableSignalLoop $total_length = 0; $count = 0; - ksort($connection->pending_outgoing); + \ksort($connection->pending_outgoing); $skipped = false; foreach ($connection->pending_outgoing as $k => $message) { if ($message['unencrypted']) { @@ -190,12 +190,12 @@ class WriteLoop extends ResumableSignalLoop unset($connection->pending_outgoing[$k]); continue; } - if ($API->settings['connection_settings'][$dc_config_number]['pfs'] && !isset($connection->temp_auth_key['bound']) && !strpos($datacenter, 'cdn') && !in_array($message['_'], ['http_wait', 'auth.bindTempAuthKey']) && $message['method']) { + if ($API->settings['connection_settings'][$dc_config_number]['pfs'] && !isset($connection->temp_auth_key['bound']) && !\strpos($datacenter, 'cdn') && !\in_array($message['_'], ['http_wait', 'auth.bindTempAuthKey']) && $message['method']) { $API->logger->logger("Skipping {$message['_']} due to unbound keys in DC {$datacenter}"); $skipped = true; continue; } - $body_length = strlen($message['serialized_body']); + $body_length = \strlen($message['serialized_body']); $actual_length = $body_length + 32; if ($total_length && $total_length + $actual_length > 32760 || $count >= 1020) { $API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE); @@ -210,7 +210,7 @@ class WriteLoop extends ResumableSignalLoop if (isset($message['method']) && $message['method'] && $message['_'] !== 'http_wait') { if ((!isset($connection->temp_auth_key['connection_inited']) || $connection->temp_auth_key['connection_inited'] === false) && $message['_'] !== 'auth.bindTempAuthKey') { - $API->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $message['_']), \danog\MadelineProto\Logger::NOTICE); + $API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $message['_']), \danog\MadelineProto\Logger::NOTICE); $MTmessage['body'] = yield $API->serialize_method_async( 'invokeWithLayer', [ @@ -220,8 +220,8 @@ class WriteLoop extends ResumableSignalLoop [ 'api_id' => $API->settings['app_info']['api_id'], 'api_hash' => $API->settings['app_info']['api_hash'], - 'device_model' => strpos($datacenter, 'cdn') === false ? $API->settings['app_info']['device_model'] : 'n/a', - 'system_version' => strpos($datacenter, 'cdn') === false ? $API->settings['app_info']['system_version'] : 'n/a', + 'device_model' => \strpos($datacenter, 'cdn') === false ? $API->settings['app_info']['device_model'] : 'n/a', + 'system_version' => \strpos($datacenter, 'cdn') === false ? $API->settings['app_info']['system_version'] : 'n/a', 'app_version' => $API->settings['app_info']['app_version'], 'system_lang_code' => $API->settings['app_info']['lang_code'], 'lang_code' => $API->settings['app_info']['lang_code'], @@ -240,9 +240,9 @@ class WriteLoop extends ResumableSignalLoop $MTmessage['body'] = yield $API->serialize_method_async('invokeAfterMsgs', ['msg_ids' => $connection->call_queue[$message['queue']], 'query' => $MTmessage['body']]); $connection->call_queue[$message['queue']][$message_id] = $message_id; - if (count($connection->call_queue[$message['queue']]) > $API->settings['msg_array_limit']['call_queue']) { - reset($connection->call_queue[$message['queue']]); - $key = key($connection->call_queue[$message['queue']]); + if (\count($connection->call_queue[$message['queue']]) > $API->settings['msg_array_limit']['call_queue']) { + \reset($connection->call_queue[$message['queue']]); + $key = \key($connection->call_queue[$message['queue']]); unset($connection->call_queue[$message['queue']][$key]); } } @@ -256,7 +256,7 @@ class WriteLoop extends ResumableSignalLoop }*/ } } - $body_length = strlen($MTmessage['body']); + $body_length = \strlen($MTmessage['body']); $actual_length = $body_length + 32; if ($total_length && $total_length + $actual_length > 32760) { $API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE); @@ -275,7 +275,7 @@ class WriteLoop extends ResumableSignalLoop $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->generate_message_id($datacenter); - $connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msg_container', 'container' => array_values($keys), 'content_related' => false, 'method' => false, 'unencrypted' => false]; + $connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msg_container', 'container' => \array_values($keys), 'content_related' => false, 'method' => false, 'unencrypted' => false]; //var_dumP("container ".bin2hex($message_id)); $keys[$connection->pending_outgoing_key++] = $message_id; @@ -283,7 +283,7 @@ class WriteLoop extends ResumableSignalLoop $message_data = yield $API->serialize_object_async(['type' => ''], ['_' => 'msg_container', 'messages' => $messages], 'container'); - $message_data_length = strlen($message_data); + $message_data_length = \strlen($message_data); $seq_no = $connection->generate_out_seq_no(false); } elseif ($count) { $message = $messages[0]; @@ -299,26 +299,26 @@ class WriteLoop extends ResumableSignalLoop unset($messages); - $plaintext = $connection->temp_auth_key['server_salt'].$connection->session_id.$message_id.pack('VV', $seq_no, $message_data_length).$message_data; - $padding = $this->posmod(-strlen($plaintext), 16); + $plaintext = $connection->temp_auth_key['server_salt'].$connection->session_id.$message_id.\pack('VV', $seq_no, $message_data_length).$message_data; + $padding = $this->posmod(-\strlen($plaintext), 16); if ($padding < 12) { $padding += 16; } $padding = $this->random($padding); - $message_key = substr(hash('sha256', substr($connection->temp_auth_key['auth_key'], 88, 32).$plaintext.$padding, true), 8, 16); + $message_key = \substr(\hash('sha256', \substr($connection->temp_auth_key['auth_key'], 88, 32).$plaintext.$padding, true), 8, 16); list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $connection->temp_auth_key['auth_key']); $message = $connection->temp_auth_key['id'].$message_key.$this->ige_encrypt($plaintext.$padding, $aes_key, $aes_iv); - $buffer = yield $connection->stream->getWriteBuffer($len = strlen($message)); + $buffer = yield $connection->stream->getWriteBuffer($len = \strlen($message)); - $t = microtime(true); + $t = \microtime(true); yield $buffer->bufferWrite($message); $connection->httpSent(); $API->logger->logger("Sent encrypted payload to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE); - $sent = time(); + $sent = \time(); if ($to_ack) { $connection->ack_queue = []; diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index c58253d0..95d628f2 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -38,13 +38,10 @@ use danog\MadelineProto\TL\TLCallback; class MTProto extends AsyncConstruct implements TLCallback { use \danog\Serializable; - use \danog\MadelineProto\MTProtoTools\AckHandler; use \danog\MadelineProto\MTProtoTools\AuthKeyHandler; use \danog\MadelineProto\MTProtoTools\CallHandler; use \danog\MadelineProto\MTProtoTools\Crypt; use \danog\MadelineProto\MTProtoTools\PeerHandler; - use \danog\MadelineProto\MTProtoTools\ResponseHandler; - use \danog\MadelineProto\MTProtoTools\SeqNoHandler; use \danog\MadelineProto\MTProtoTools\UpdateHandler; use \danog\MadelineProto\MTProtoTools\Files; use \danog\MadelineProto\SecretChats\AuthKeyHandler; @@ -72,16 +69,91 @@ class MTProto extends AsyncConstruct implements TLCallback /* const V = 71; */ + /** + * Internal version of MadelineProto. + * + * Increased every time the default settings array or something big changes + * + * @var int + */ const V = 129; - const RELEASE = '4.0'; + /** + * String release version. + * + * @var string + */ + const RELEASE = '5.0'; + /** + * We're not logged in. + * + * @var int + */ const NOT_LOGGED_IN = 0; + /** + * We're waiting for the login code. + * + * @var int + */ const WAITING_CODE = 1; + /** + * We're waiting for parameters to sign up. + * + * @var int + */ const WAITING_SIGNUP = -1; + /** + * We're waiting for the 2FA password. + * + * @var int + */ const WAITING_PASSWORD = 2; + /** + * We're logged in. + * + * @var int + */ const LOGGED_IN = 3; + /** + * Disallowed methods. + * + * @var array + */ const DISALLOWED_METHODS = ['account.updatePasswordSettings' => 'You cannot use this method directly; use $MadelineProto->update_2fa($params), instead (see https://docs.madelineproto.xyz for more info)', 'account.getPasswordSettings' => 'You cannot use this method directly; use $MadelineProto->update_2fa($params), instead (see https://docs.madelineproto.xyz for more info)', 'messages.receivedQueue' => 'You cannot use this method directly', 'messages.getDhConfig' => 'You cannot use this method directly, instead use $MadelineProto->get_dh_config();', 'auth.bindTempAuthKey' => 'You cannot use this method directly, instead modify the PFS and default_temp_auth_key_expires_in settings, see https://docs.madelineproto.xyz/docs/SETTINGS.html for more info', 'auth.exportAuthorization' => 'You cannot use this method directly, use $MadelineProto->export_authorization() instead, see https://docs.madelineproto.xyz/docs/LOGIN.html', 'auth.importAuthorization' => 'You cannot use this method directly, use $MadelineProto->import_authorization($authorization) instead, see https://docs.madelineproto.xyz/docs/LOGIN.html', 'auth.logOut' => 'You cannot use this method directly, use the logout method instead (see https://docs.madelineproto.xyz for more info)', 'auth.importBotAuthorization' => 'You cannot use this method directly, use the bot_login method instead (see https://docs.madelineproto.xyz for more info)', 'auth.sendCode' => 'You cannot use this method directly, use the phone_login method instead (see https://docs.madelineproto.xyz for more info)', 'auth.signIn' => 'You cannot use this method directly, use the complete_phone_login method instead (see https://docs.madelineproto.xyz for more info)', 'auth.checkPassword' => 'You cannot use this method directly, use the complete_2fa_login method instead (see https://docs.madelineproto.xyz for more info)', 'auth.signUp' => 'You cannot use this method directly, use the complete_signup method instead (see https://docs.madelineproto.xyz for more info)', 'users.getFullUser' => 'You cannot use this method directly, use the get_pwr_chat, get_info, get_full_info methods instead (see https://docs.madelineproto.xyz for more info)', 'channels.getFullChannel' => 'You cannot use this method directly, use the get_pwr_chat, get_info, get_full_info methods instead (see https://docs.madelineproto.xyz for more info)', 'messages.getFullChat' => 'You cannot use this method directly, use the get_pwr_chat, get_info, get_full_info methods instead (see https://docs.madelineproto.xyz for more info)', 'contacts.resolveUsername' => 'You cannot use this method directly, use the resolve_username, get_pwr_chat, get_info, get_full_info methods instead (see https://docs.madelineproto.xyz for more info)', 'messages.acceptEncryption' => 'You cannot use this method directly, see https://docs.madelineproto.xyz for more info on handling secret chats', 'messages.discardEncryption' => 'You cannot use this method directly, see https://docs.madelineproto.xyz for more info on handling secret chats', 'messages.requestEncryption' => 'You cannot use this method directly, see https://docs.madelineproto.xyz for more info on handling secret chats', 'phone.requestCall' => 'You cannot use this method directly, see https://docs.madelineproto.xyz#calls for more info on handling calls', 'phone.acceptCall' => 'You cannot use this method directly, see https://docs.madelineproto.xyz#calls for more info on handling calls', 'phone.confirmCall' => 'You cannot use this method directly, see https://docs.madelineproto.xyz#calls for more info on handling calls', 'phone.discardCall' => 'You cannot use this method directly, see https://docs.madelineproto.xyz#calls for more info on handling calls', 'updates.getChannelDifference' => 'You cannot use this method directly, see https://docs.madelineproto.xyz for more info on handling updates', 'updates.getDifference' => 'You cannot use this method directly, see https://docs.madelineproto.xyz for more info on handling updates', 'updates.getState' => 'You cannot use this method directly, see https://docs.madelineproto.xyz for more info on handling updates', 'upload.getCdnFile' => 'You cannot use this method directly, use the upload, download_to_stream, download_to_file, download_to_dir methods instead; see https://docs.madelineproto.xyz for more info', 'upload.getFileHashes' => 'You cannot use this method directly, use the upload, download_to_stream, download_to_file, download_to_dir methods instead; see https://docs.madelineproto.xyz for more info', 'upload.getCdnFileHashes' => 'You cannot use this method directly, use the upload, download_to_stream, download_to_file, download_to_dir methods instead; see https://docs.madelineproto.xyz for more info', 'upload.reuploadCdnFile' => 'You cannot use this method directly, use the upload, download_to_stream, download_to_file, download_to_dir methods instead; see https://docs.madelineproto.xyz for more info', 'upload.getFile' => 'You cannot use this method directly, use the upload, download_to_stream, download_to_file, download_to_dir methods instead; see https://docs.madelineproto.xyz for more info', 'upload.saveFilePart' => 'You cannot use this method directly, use the upload, download_to_stream, download_to_file, download_to_dir methods instead; see https://docs.madelineproto.xyz for more info', 'upload.saveBigFilePart' => 'You cannot use this method directly, use the upload, download_to_stream, download_to_file, download_to_dir methods instead; see https://docs.madelineproto.xyz for more info']; - const BAD_MSG_ERROR_CODES = [16 => 'msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)', 17 => 'msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)', 18 => 'incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)', 19 => 'container msg_id is the same as msg_id of a previously received message (this must never happen)', 20 => 'message too old, and it cannot be verified whether the server has received a message with this msg_id or not', 32 => 'msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)', 33 => 'msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)', 34 => 'an even msg_seqno expected (irrelevant message), but odd received', 35 => 'odd msg_seqno expected (relevant message), but even received', 48 => 'incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)', 64 => 'invalid container.']; - const MSGS_INFO_FLAGS = [1 => 'nothing is known about the message (msg_id too low, the other party may have forgotten it)', 2 => 'message not received (msg_id falls within the range of stored identifiers; however, the other party has certainly not received a message like that)', 3 => 'message not received (msg_id too high; however, the other party has certainly not received it yet)', 4 => 'message received (note that this response is also at the same time a receipt acknowledgment)', 8 => ' and message already acknowledged', 16 => ' and message not requiring acknowledgment', 32 => ' and RPC query contained in message being processed or processing already complete', 64 => ' and content-related response to message already generated', 128 => ' and other party knows for a fact that message is already received']; + /** + * Bad message error codes. + * + * @var array + */ + const BAD_MSG_ERROR_CODES = [ + 16 => 'msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)', + 17 => 'msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)', + 18 => 'incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)', + 19 => 'container msg_id is the same as msg_id of a previously received message (this must never happen)', + 20 => 'message too old, and it cannot be verified whether the server has received a message with this msg_id or not', + 32 => 'msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)', + 33 => 'msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)', + 34 => 'an even msg_seqno expected (irrelevant message), but odd received', + 35 => 'odd msg_seqno expected (relevant message), but even received', + 48 => 'incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)', + 64 => 'invalid container' + ]; + + /** + * Localized message info flags + * + * @var array + */ + const MSGS_INFO_FLAGS = [ + 1 => 'nothing is known about the message (msg_id too low, the other party may have forgotten it)', + 2 => 'message not received (msg_id falls within the range of stored identifiers; however, the other party has certainly not received a message like that)', + 3 => 'message not received (msg_id too high; however, the other party has certainly not received it yet)', + 4 => 'message received (note that this response is also at the same time a receipt acknowledgment)', + 8 => ' and message already acknowledged', + 16 => ' and message not requiring acknowledgment', + 32 => ' and RPC query contained in message being processed or processing already complete', + 64 => ' and content-related response to message already generated', + 128 => ' and other party knows for a fact that message is already received' + ]; const REQUESTED = 0; const ACCEPTED = 1; const CONFIRMED = 2; @@ -124,20 +196,91 @@ class MTProto extends AsyncConstruct implements TLCallback ]; const DEFAULT_GETUPDATES_PARAMS = ['offset' => 0, 'limit' => null, 'timeout' => 0]; + /** + * Instance of wrapper API. + * + * @var API|null + */ public $wrapper; + /** + * PWRTelegram webhook URL. + * + * @var boolean|string + */ public $hook_url = false; + /** + * Settings array. + * + * @var array + */ public $settings = []; + /** + * Config array. + * + * @var array + */ private $config = ['expires' => -1]; + /** + * TOS info. + * + * @var array + */ private $tos = ['expires' => 0, 'accepted' => true]; + /** + * Whether we're initing authorization. + * + * @var boolean + */ private $initing_authorization = false; + /** + * Authorization info (User). + * + * @var [type] + */ public $authorization = null; - public $authorized = 0; + /** + * Whether we're authorized. + * + * @var integer + */ + public $authorized = self::NOT_LOGGED_IN; + /** + * Main authorized DC ID. + * + * @var integer + */ public $authorized_dc = -1; + /** + * RSA keys. + * + * @var array + */ private $rsa_keys = []; + /** + * CDN RSA keys. + * + * @var array + */ private $cdn_rsa_keys = []; + /** + * Diffie-hellman config. + * + * @var array + */ private $dh_config = ['version' => 0]; + /** + * Internal peer database. + * + * @var array + */ public $chats = []; + /** + * Cached parameters for fetching channel participants. + * + * @var array + */ public $channel_participants = []; + public $last_stored = 0; public $qres = []; public $full_chats = []; @@ -154,6 +297,14 @@ class MTProto extends AsyncConstruct implements TLCallback public $feeders = []; public $updaters = []; public $destructing = false; // Avoid problems with exceptions thrown by forked strands, see tools + + /** + * DataCenter instance. + * + * @var DataCenter + */ + public $datacenter; + public function __magic_construct($settings = []) { $this->setInitPromise($this->__construct_async($settings)); @@ -198,7 +349,7 @@ class MTProto extends AsyncConstruct implements TLCallback yield $this->connect_to_all_dcs_async(); $this->startLoops(); $this->datacenter->curdc = 2; - if ((!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) && $this->datacenter->sockets[$this->datacenter->curdc]->temp_auth_key !== null) { + if ((!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) && $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->hasAuthKey()) { try { $nearest_dc = yield $this->method_call_async_read('help.getNearestDc', [], ['datacenter' => $this->datacenter->curdc]); $this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['nearest_dc'], $nearest_dc['country'], $nearest_dc['nearest_dc']), Logger::NOTICE); @@ -266,6 +417,15 @@ class MTProto extends AsyncConstruct implements TLCallback { return $this->datacenter->fileGetContents($url); } + /** + * Get all datacenter connections. + * + * @return array + */ + public function getDataCenterConnections(): array + { + return $this->datacenter->getDataCenterConnections(); + } public function hasAllAuth() { @@ -273,8 +433,8 @@ class MTProto extends AsyncConstruct implements TLCallback return false; } - foreach ($this->datacenter->sockets as $dc) { - if (!$dc->authorized || $dc->temp_auth_key === null) { + foreach ($this->datacenter->getDataCenterConnections() as $dc) { + if (!$dc->isAuthorized() || $dc->hasAuthKey() === null) { return false; } } @@ -367,7 +527,7 @@ class MTProto extends AsyncConstruct implements TLCallback $this->setEventHandler($this->event_handler); } $force = false; - $this->reset_session(); + $this->resetSession(); if (isset($backtrace[2]['function'], $backtrace[2]['class'], $backtrace[2]['args']) && $backtrace[2]['class'] === 'danog\\MadelineProto\\API' && $backtrace[2]['function'] === '__construct_async') { if (\count($backtrace[2]['args']) >= 2) { $this->parse_settings(\array_replace_recursive($this->settings, $backtrace[2]['args'][1])); @@ -380,9 +540,9 @@ class MTProto extends AsyncConstruct implements TLCallback if (!isset($this->v) || $this->v !== self::V) { $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['serialization_ofd'], Logger::WARNING); - foreach ($this->datacenter->sockets as $dc_id => $socket) { - if ($this->authorized === self::LOGGED_IN && \strpos($dc_id, '_') === false && $socket->auth_key !== null && $socket->temp_auth_key !== null) { - $socket->authorized = true; + foreach ($this->datacenter->getDataCenterConnections() as $dc_id => $socket) { + if ($this->authorized === self::LOGGED_IN && \strpos($dc_id, '_') === false && $socket->hasAuthKey(true) && $socket->hasAuthKey(false)) { + $socket->authorized(true); } } $settings = $this->settings; @@ -441,7 +601,7 @@ class MTProto extends AsyncConstruct implements TLCallback if ($settings['app_info']['api_id'] === 6) { unset($settings['app_info']); } - $this->reset_session(true, true); + $this->resetSession(true, true); $this->config = ['expires' => -1]; $this->dh_config = ['version' => 0]; yield $this->__construct_async($settings); @@ -514,7 +674,7 @@ class MTProto extends AsyncConstruct implements TLCallback $this->updaters[$channelId]->signal(true); } } - foreach ($this->datacenter->sockets as $datacenter) { + foreach ($this->datacenter->getDataCenterConnections() as $datacenter) { $datacenter->disconnect(); } $this->logger("Successfully destroyed MadelineProto"); @@ -868,35 +1028,32 @@ class MTProto extends AsyncConstruct implements TLCallback $this->logger = Logger::getLoggerFromSettings($this->settings, isset($this->authorization['user']) ? isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'] : ''); } - public function reset_session($de = true, $auth_key = false) + /** + * Reset all MTProto sessions. + * + * @param boolean $de Whether to reset the session ID + * @param boolean $auth_key Whether to reset the auth key + * + * @return void + */ + public function resetSession(bool $de = true, bool $auth_key = false) { if (!\is_object($this->datacenter)) { throw new Exception(\danog\MadelineProto\Lang::$current_lang['session_corrupted']); } - foreach ($this->datacenter->sockets as $id => $socket) { + foreach ($this->datacenter->getDataCenterConnections() as $id => $socket) { if ($de) { - //$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['reset_session_seqno'], $id), Logger::VERBOSE); - $socket->session_id = $this->random(8); - $socket->session_in_seq_no = 0; - $socket->session_out_seq_no = 0; - $socket->max_incoming_id = null; - $socket->max_outgoing_id = null; + $socket->resetSession(); } if ($auth_key) { - $socket->temp_auth_key = null; + $socket->setAuthKey(null); } - /* - $socket->incoming_messages = []; - $socket->outgoing_messages = []; - $socket->new_outgoing = []; - $socket->new_incoming = []; - */ } } public function is_http($datacenter) { - return \in_array($this->datacenter->sockets[$datacenter]->getCtx()->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]); + return \in_array($this->datacenter->getDataCenterConection($datacenter)->getCtx()->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]); } // Connects to all datacenters and if necessary creates authorization keys, binds them and writes client info @@ -956,8 +1113,8 @@ class MTProto extends AsyncConstruct implements TLCallback unset($this->updaters[$channelId]); } } - foreach ($this->datacenter->sockets as $socket) { - $socket->authorized = false; + foreach ($this->datacenter->getDataCenterConnections() as $socket) { + $socket->authorized(false); } $this->channels_state = new CombinedUpdatesState(); @@ -1033,8 +1190,8 @@ class MTProto extends AsyncConstruct implements TLCallback $this->updaters[$channelId]->resume(); } } - foreach ($this->datacenter->sockets as $datacenter) { - $datacenter->writer->resume(); + foreach ($this->datacenter->getDataCenterConnections() as $datacenter) { + $datacenter->flush(); } if ($this->seqUpdater->start()) { $this->seqUpdater->resume(); @@ -1043,7 +1200,7 @@ class MTProto extends AsyncConstruct implements TLCallback public function get_phone_config_async($watcherId = null) { - if ($this->authorized === self::LOGGED_IN && \class_exists('\\danog\\MadelineProto\\VoIPServerConfigInternal') && !$this->authorization['user']['bot'] && $this->datacenter->sockets[$this->settings['connection_settings']['default_dc']]->temp_auth_key !== null) { + if ($this->authorized === self::LOGGED_IN && \class_exists('\\danog\\MadelineProto\\VoIPServerConfigInternal') && !$this->authorization['user']['bot'] && $this->datacenter->getDataCenterConnection($this->settings['connection_settings']['default_dc'])->hasAuthKey()) { $this->logger->logger('Fetching phone config...'); VoIPServerConfig::updateDefault(yield $this->method_call_async_read('phone.getCallConfig', [], ['datacenter' => $this->settings['connection_settings']['default_dc']])); } else { @@ -1117,12 +1274,6 @@ class MTProto extends AsyncConstruct implements TLCallback yield $this->connect_to_all_dcs_async(); $this->datacenter->curdc = $curdc; } - public function content_related($method) - { - $method = \is_array($method) && isset($method['_']) ? $method['_'] : $method; - - return \is_string($method) ? !\in_array($method, MTProto::NOT_CONTENT_RELATED) : true; - } public function get_self_async() { diff --git a/src/danog/MadelineProto/MTProto/AuthKey.php b/src/danog/MadelineProto/MTProto/AuthKey.php new file mode 100644 index 00000000..d226dde3 --- /dev/null +++ b/src/danog/MadelineProto/MTProto/AuthKey.php @@ -0,0 +1,217 @@ +. + * + * @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\AuthKey; + +use JsonSerializable; + +/** + * MTProto auth key + */ +class AuthKey implements JsonSerializable +{ + /** + * Auth key + * + * @var string + */ + private $authKey; + /** + * Auth key ID + * + * @var string + */ + private $id; + /** + * Server salt + * + * @var string + */ + private $serverSalt; + /** + * Whether the auth key is bound + * + * @var boolean + */ + private $bound = false; + /** + * Whether the connection is inited for this auth key + * + * @var boolean + */ + private $inited = false; + + /** + * Constructor function + * + * @param array $old Old auth key array + */ + public function __construct(array $old = []) + { + if (isset($old['auth_key'])) { + if (strlen($old['auth_key']) !== 2048/8 && strpos($old['authkey'], 'pony') === 0) { + $old['auth_key'] = base64_decode(substr($old['auth_key'], 4)); + } + $this->setAuthKey($old['auth_key']); + } + if (isset($old['server_salt'])) { + $this->setServerSalt($old['server_salt']); + } + if (isset($old['bound'])) { + $this->bind($old['bound']); + } + if (isset($old['connection_inited'])) { + $this->init($old['connection_inited']); + } + } + + + /** + * Set auth key + * + * @param string $authKey Authorization key + * + * @return void + */ + public function setAuthKey(string $authKey) + { + $this->authKey = $authKey; + $this->id = substr(sha1($authKey, true), -8); + } + + /** + * Check if auth key is present + * + * @return boolean + */ + public function hasAuthKey(): bool + { + return $this->authKey !== null; + } + + /** + * Get auth key + * + * @return string + */ + public function getAuthKey(): string + { + return $this->authKey; + } + + /** + * Get auth key ID + * + * @return string + */ + public function getID(): string + { + return $this->id; + } + + /** + * Set server salt + * + * @param string $salt Server salt + * + * @return void + */ + public function setServerSalt(string $salt) + { + $this->serverSalt = $salt; + } + + /** + * Get server salt + * + * @return string + */ + public function getServerSalt(): string + { + return $this->serverSalt; + } + + /** + * Check if has server salt + * + * @return boolean + */ + public function hasServerSalt(): bool + { + return $this->serverSalt !== null; + } + + /** + * Bind auth key + * + * @param boolean $bound Bind or unbind + * + * @return void + */ + public function bind(bool $bound = true) + { + $this->bound = $bound; + } + + /** + * Check if auth key is bound + * + * @return boolean + */ + public function isBound(): bool + { + return $this->bound; + } + + /** + * Init or deinit connection for auth key + * + * @param boolean $init Init or deinit + * + * @return void + */ + public function init(bool $init = true) + { + $this->inited = $init; + } + /** + * Check if connection is inited for auth key + * + * @return boolean + */ + public function isInited(): bool + { + return $this->inited; + } + + + /** + * JSON serialization function + * + * @return array + */ + public function jsonSerialize(): array + { + return [ + 'auth_key' => 'pony'.base64_encode($this->authKey), + 'server_salt' => $this->serverSalt, + 'bound' => $this->bound, + 'connection_inited' => $this->inited + ]; + } +} \ No newline at end of file diff --git a/src/danog/MadelineProto/MTProtoSession/CallHandler.php b/src/danog/MadelineProto/MTProtoSession/CallHandler.php index 86f7906f..d7e5979c 100644 --- a/src/danog/MadelineProto/MTProtoSession/CallHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/CallHandler.php @@ -38,18 +38,21 @@ trait CallHandler * * @return void */ - public function method_recall(string $watcherId, $args) + public function method_recall(string $watcherId, array $args) { $message_id = $args['message_id']; $postpone = $args['postpone'] ?? false; $datacenter = $args['datacenter'] ?? false; + if ($datacenter === $this->datacenter) { + $datacenter = false; + } $message_ids = $this->outgoing_messages[$message_id]['container'] ?? [$message_id]; foreach ($message_ids as $message_id) { if (isset($this->outgoing_messages[$message_id]['body'])) { if ($datacenter) { - $res = $this->API->datacenter->sockets[$datacenter]->sendMessage($this->outgoing_messages[$message_id], false); + $res = $this->API->datacenter->getDataCenterConnection($datacenter)->sendMessage($this->outgoing_messages[$message_id], false); } else { $res = $this->sendMessage($this->outgoing_messages[$message_id], false); } @@ -62,27 +65,13 @@ trait CallHandler } if (!$postpone) { if ($datacenter) { - $this->API->datacenter->sockets[$datacenter]->writer->resume(); + $this->API->datacenter->getDataCenterConnection($datacenter)->flush(); } else { - $this->writer->resume(); + $this->flush(); } } } - /** - * Synchronous wrapper for method_call. - * - * @param string $method Method name - * @param array $args Arguments - * @param array $aargs Additional arguments - * - * @return array - */ - public function method_call(string $method, $args = [], array $aargs = ['msg_id' => null]) - { - return $this->wait($this->method_call_async_read($method, $args, $aargs)); - } - /** * Call method and wait asynchronously for response. * @@ -236,6 +225,7 @@ trait CallHandler $message['promise'] = $aargs['promise']; } - return $this->sendMessage($message, isset($aargs['postpone']) ? !$aargs['postpone'] : true); + $aargs['postpone'] = $aargs['postpone'] ?? false; + return $this->sendMessage($message, !$aargs['postpone']); } } diff --git a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php index 5de1fffa..4bb2ff16 100644 --- a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php @@ -118,7 +118,7 @@ trait ResponseHandler $this->ack_incoming_message_id($current_msg_id); // Acknowledge that I received the server's response - if ($this->authorized === self::LOGGED_IN && !$this->initing_authorization && $this->API->datacenter->sockets[$this->API->datacenter->curdc]->temp_auth_key !== null && isset($this->updaters[false])) { + if ($this->authorized === self::LOGGED_IN && !$this->initing_authorization && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasAuthKey() && isset($this->updaters[false])) { $this->updaters[false]->resumeDefer(); } @@ -387,7 +387,7 @@ trait ResponseHandler case 303: $this->API->datacenter->curdc = $datacenter = (int) \preg_replace('/[^0-9]+/', '', $response['error_message']); - if (isset($request['file']) && $request['file'] && isset($this->API->datacenter->sockets[$datacenter.'_media'])) { + if (isset($request['file']) && $request['file'] && $this->API->datacenter->has($datacenter.'_media')) { \danog\MadelineProto\Logger::log('Using media DC'); $datacenter .= '_media'; } @@ -407,11 +407,11 @@ trait ResponseHandler $this->got_response_for_outgoing_message_id($request_id); $this->logger->logger($response['error_message'], \danog\MadelineProto\Logger::FATAL_ERROR); - foreach ($this->API->datacenter->sockets as $socket) { - $socket->temp_auth_key = null; - $socket->session_id = null; - $socket->auth_key = null; - $socket->authorized = false; + foreach ($this->API->datacenter->getDataCenterConnections() as $socket) { + $socket->authKey(null, true); + $socket->authKey(null, false); + $socket->authorized(false); + $socket->resetSession(); } if ($response['error_message'] === 'USER_DEACTIVATED') { @@ -457,11 +457,12 @@ trait ResponseHandler $this->got_response_for_outgoing_message_id($request_id); $this->logger->logger('Permanent auth key was main authorized key, logging out...', \danog\MadelineProto\Logger::FATAL_ERROR); - foreach ($this->API->datacenter->sockets as $socket) { - $socket->temp_auth_key = null; - $socket->auth_key = null; - $socket->authorized = false; + foreach ($this->API->datacenter->getDataCenterConnections() as $socket) { + $socket->authKey(null, true); + $socket->authKey(null, false); + $socket->authorized(false); } + $this->logger->logger('!!!!!!! WARNING !!!!!!!', \danog\MadelineProto\Logger::FATAL_ERROR); $this->logger->logger("Telegram's flood prevention system suspended this account.", \danog\MadelineProto\Logger::ERROR); $this->logger->logger('To continue, manual verification is required.', \danog\MadelineProto\Logger::FATAL_ERROR); @@ -542,7 +543,7 @@ trait ResponseHandler case 17: $this->time_delta = (int) (new \phpseclib\Math\BigInteger(\strrev($response_id), 256))->bitwise_rightShift(32)->subtract(new \phpseclib\Math\BigInteger(\time()))->toString(); $this->logger->logger('Set time delta to '.$this->time_delta, \danog\MadelineProto\Logger::WARNING); - $this->reset_session(); + $this->API->resetSession(); $this->temp_auth_key = null; $this->callFork((function () use ($request_id) { yield $this->API->init_authorization_async(); diff --git a/src/danog/MadelineProto/MTProtoSession/SaltHandler.php b/src/danog/MadelineProto/MTProtoSession/SaltHandler.php deleted file mode 100644 index 3afcd3e3..00000000 --- a/src/danog/MadelineProto/MTProtoSession/SaltHandler.php +++ /dev/null @@ -1,40 +0,0 @@ -. - * - * @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. - */ -trait SaltHandler -{ - public function add_salts($salts) - { - foreach ($salts as $salt) { - $this->add_salt($salt['valid_since'], $salt['valid_until'], $salt['salt']); - } - } - - public function add_salt($valid_since, $valid_until, $salt) - { - if (!isset($this->temp_auth_key['salts'][$salt])) { - $this->temp_auth_key['salts'][$salt] = ['valid_since' => $valid_since, 'valid_until' => $valid_until]; - } - } -} diff --git a/src/danog/MadelineProto/MTProtoSession/SeqNoHandler.php b/src/danog/MadelineProto/MTProtoSession/SeqNoHandler.php index e247d0d0..39e760df 100644 --- a/src/danog/MadelineProto/MTProtoSession/SeqNoHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/SeqNoHandler.php @@ -24,8 +24,6 @@ namespace danog\MadelineProto\MTProtoSession; */ trait SeqNoHandler { - use \danog\MadelineProto\MTProtoTools\SeqNoHandler; - public $session_out_seq_no = 0; public $session_in_seq_no = 0; @@ -58,4 +56,12 @@ trait SeqNoHandler //$this->API->logger->logger("IN: $value + $in = ".$this->session_in_seq_no); return $value * 2 + $in; } + + public function content_related($method) + { + $method = \is_array($method) && isset($method['_']) ? $method['_'] : $method; + + return \is_string($method) ? !\in_array($method, MTProto::NOT_CONTENT_RELATED) : true; + } + } diff --git a/src/danog/MadelineProto/MTProtoSession/Session.php b/src/danog/MadelineProto/MTProtoSession/Session.php index f5365ce6..2a6a6b74 100644 --- a/src/danog/MadelineProto/MTProtoSession/Session.php +++ b/src/danog/MadelineProto/MTProtoSession/Session.php @@ -43,5 +43,17 @@ abstract class Session public $call_queue = []; public $ack_queue = []; - + /** + * Reset MTProto session + * + * @return void + */ + public function resetSession() + { + $this->session_id = $this->random(8); + $this->session_in_seq_no = 0; + $this->session_out_seq_no = 0; + $this->max_incoming_id = null; + $this->max_outgoing_id = null; + } } diff --git a/src/danog/MadelineProto/MTProtoTools/CallHandler.php b/src/danog/MadelineProto/MTProtoTools/CallHandler.php new file mode 100644 index 00000000..aa99fe25 --- /dev/null +++ b/src/danog/MadelineProto/MTProtoTools/CallHandler.php @@ -0,0 +1,69 @@ +. + * + * @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\MTProtoTools; + +/** + * Manages method and object calls. + */ +trait CallHandler +{ + /** + * Synchronous wrapper for method_call. + * + * @param string $method Method name + * @param array $args Arguments + * @param array $aargs Additional arguments + * + * @return array + */ + public function method_call(string $method, $args = [], array $aargs = ['msg_id' => null]) + { + return $this->wait($this->method_call_async_read($method, $args, $aargs)); + } + + /** + * Call method and wait asynchronously for response. + * + * If the $aargs['noResponse'] is true, will not wait for a response. + * + * @param string $method Method name + * @param array $args Arguments + * @param array $aargs Additional arguments + * + * @return Promise + */ + public function method_call_async_read(string $method, $args = [], array $aargs = ['msg_id' => null]): Promise + { + return $this->datacenter->getConnection($aargs['datacenter'] ?? $this->datacenter->curdc)->method_call_async_read($method, $args, $aargs); + } + /** + * Call method and make sure it is asynchronously sent. + * + * @param string $method Method name + * @param array $args Arguments + * @param array $aargs Additional arguments + * + * @return Promise + */ + public function method_call_async_write(string $method, $args = [], array $aargs = ['msg_id' => null]): Promise + { + return $this->datacenter->getConnection($aargs['datacenter'] ?? $this->datacenter->curdc)->method_call_async_write($method, $args, $aargs); + } +} \ No newline at end of file