PHPDOC
This commit is contained in:
parent
8b6cd0cb9a
commit
3107aee776
@ -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.
|
||||
*
|
||||
|
@ -1,487 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Conversion module.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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<string, DataCenterConnection>
|
||||
*/
|
||||
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<string, DataCenterConnection>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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<string, Connection>
|
||||
*/
|
||||
private $connections = [];
|
||||
/**
|
||||
* Connection weights
|
||||
* Connection weights.
|
||||
*
|
||||
* @var array
|
||||
* @var array<string, int>
|
||||
*/
|
||||
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.
|
||||
*
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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];
|
||||
|
@ -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 = [];
|
||||
|
File diff suppressed because one or more lines are too long
217
src/danog/MadelineProto/MTProto/AuthKey.php
Normal file
217
src/danog/MadelineProto/MTProto/AuthKey.php
Normal file
@ -0,0 +1,217 @@
|
||||
<?php
|
||||
/**
|
||||
* MTProto Auth key
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
|
||||
* @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
|
||||
];
|
||||
}
|
||||
}
|
@ -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']);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* SaltHandler module.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
|
||||
* @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];
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
69
src/danog/MadelineProto/MTProtoTools/CallHandler.php
Normal file
69
src/danog/MadelineProto/MTProtoTools/CallHandler.php
Normal file
@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* CallHandler module.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
|
||||
* @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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user