This commit is contained in:
Daniil Gentili 2019-09-01 14:07:04 +02:00
parent 8b6cd0cb9a
commit 3107aee776
16 changed files with 762 additions and 729 deletions

View File

@ -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.
*

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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.
*

View File

@ -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);

View File

@ -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) {

View File

@ -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];

View File

@ -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

View 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
];
}
}

View File

@ -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']);
}
}

View File

@ -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();

View File

@ -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];
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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);
}
}