This commit is contained in:
Daniil Gentili 2020-06-15 20:06:48 +02:00
parent 17e85d361c
commit e147fe3695
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
20 changed files with 837 additions and 11111 deletions

7
.gitignore vendored
View File

@ -124,3 +124,10 @@ coverage
tempConv tempConv
extracted.json extracted.json
.phpunit.result.cache .phpunit.result.cache
src/danog/MadelineProto/VoIP.php
src/danog/MadelineProto/VoIP/AckHandler.php
src/danog/MadelineProto/VoIP/MessageHandler.php
src/danog/MadelineProto/OpusStream.php
ponyScripts

356
.phan/config.php Normal file
View File

@ -0,0 +1,356 @@
<?php
use Phan\Issue;
/**
* This configuration file was automatically generated by 'phan --init --init-level=3'
*
* TODOs (added by 'phan --init'):
*
* - Go through this file and verify that there are no missing/unnecessary files/directories.
* (E.g. this only includes direct composer dependencies - You may have to manually add indirect composer dependencies to 'directory_list')
* - Look at 'plugins' and add or remove plugins if appropriate (see https://github.com/phan/phan/tree/master/.phan/plugins#plugins)
* - Add global suppressions for pre-existing issues to suppress_issue_types (https://github.com/phan/phan/wiki/Tutorial-for-Analyzing-a-Large-Sloppy-Code-Base)
*
* This configuration will be read and overlaid on top of the
* default configuration. Command line arguments will be applied
* after this file is read.
*
* @see src/Phan/Config.php
* See Config for all configurable options.
*
* A Note About Paths
* ==================
*
* Files referenced from this file should be defined as
*
* ```
* Config::projectPath('relative_path/to/file')
* ```
*
* where the relative path is relative to the root of the
* project which is defined as either the working directory
* of the phan executable or a path passed in via the CLI
* '-d' flag.
*/
return [
// Supported values: `'5.6'`, `'7.0'`, `'7.1'`, `'7.2'`, `'7.3'`, `'7.4'`, `null`.
// If this is set to `null`,
// then Phan assumes the PHP version which is closest to the minor version
// of the php executable used to execute Phan.
//
// Note that the **only** effect of choosing `'5.6'` is to infer that functions removed in php 7.0 exist.
// (See `backward_compatibility_checks` for additional options)
// Automatically inferred from composer.json requirement for "php" of ">=7.4.0"
'target_php_version' => '7.4',
// If enabled, missing properties will be created when
// they are first seen. If false, we'll report an
// error message if there is an attempt to write
// to a class property that wasn't explicitly
// defined.
'allow_missing_properties' => false,
// If enabled, null can be cast to any type and any
// type can be cast to null. Setting this to true
// will cut down on false positives.
'null_casts_as_any_type' => false,
// If enabled, allow null to be cast as any array-like type.
//
// This is an incremental step in migrating away from `null_casts_as_any_type`.
// If `null_casts_as_any_type` is true, this has no effect.
'null_casts_as_array' => true,
// If enabled, allow any array-like type to be cast to null.
// This is an incremental step in migrating away from `null_casts_as_any_type`.
// If `null_casts_as_any_type` is true, this has no effect.
'array_casts_as_null' => true,
// If enabled, scalars (int, float, bool, string, null)
// are treated as if they can cast to each other.
// This does not affect checks of array keys. See `scalar_array_key_cast`.
'scalar_implicit_cast' => false,
// If enabled, any scalar array keys (int, string)
// are treated as if they can cast to each other.
// E.g. `array<int,stdClass>` can cast to `array<string,stdClass>` and vice versa.
// Normally, a scalar type such as int could only cast to/from int and mixed.
'scalar_array_key_cast' => true,
// If this has entries, scalars (int, float, bool, string, null)
// are allowed to perform the casts listed.
//
// E.g. `['int' => ['float', 'string'], 'float' => ['int'], 'string' => ['int'], 'null' => ['string']]`
// allows casting null to a string, but not vice versa.
// (subset of `scalar_implicit_cast`)
'scalar_implicit_partial' => [],
// If enabled, Phan will warn if **any** type in a method invocation's object
// is definitely not an object,
// or if **any** type in an invoked expression is not a callable.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_method_checking' => false,
// If enabled, Phan will warn if **any** type of the object expression for a property access
// does not contain that property.
'strict_object_checking' => false,
// If enabled, Phan will warn if **any** type in the argument's union type
// cannot be cast to a type in the parameter's expected union type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_param_checking' => false,
// If enabled, Phan will warn if **any** type in a property assignment's union type
// cannot be cast to a type in the property's declared union type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_property_checking' => false,
// If enabled, Phan will warn if **any** type in a returned value's union type
// cannot be cast to the declared return type.
// Setting this to true will introduce numerous false positives
// (and reveal some bugs).
'strict_return_checking' => false,
// If true, seemingly undeclared variables in the global
// scope will be ignored.
//
// This is useful for projects with complicated cross-file
// globals that you have no hope of fixing.
'ignore_undeclared_variables_in_global_scope' => true,
// Set this to false to emit `PhanUndeclaredFunction` issues for internal functions that Phan has signatures for,
// but aren't available in the codebase, or from Reflection.
// (may lead to false positives if an extension isn't loaded)
//
// If this is true(default), then Phan will not warn.
//
// Even when this is false, Phan will still infer return values and check parameters of internal functions
// if Phan has the signatures.
'ignore_undeclared_functions_with_known_signatures' => true,
// Backwards Compatibility Checking. This is slow
// and expensive, but you should consider running
// it before upgrading your version of PHP to a
// new version that has backward compatibility
// breaks.
//
// If you are migrating from PHP 5 to PHP 7,
// you should also look into using
// [php7cc (no longer maintained)](https://github.com/sstalle/php7cc)
// and [php7mar](https://github.com/Alexia/php7mar),
// which have different backwards compatibility checks.
'backward_compatibility_checks' => false,
// If true, check to make sure the return type declared
// in the doc-block (if any) matches the return type
// declared in the method signature.
'check_docblock_signature_return_type_match' => false,
// This setting maps case-insensitive strings to union types.
//
// This is useful if a project uses phpdoc that differs from the phpdoc2 standard.
//
// If the corresponding value is the empty string,
// then Phan will ignore that union type (E.g. can ignore 'the' in `@return the value`)
//
// If the corresponding value is not empty,
// then Phan will act as though it saw the corresponding UnionTypes(s)
// when the keys show up in a UnionType of `@param`, `@return`, `@var`, `@property`, etc.
//
// This matches the **entire string**, not parts of the string.
// (E.g. `@return the|null` will still look for a class with the name `the`, but `@return the` will be ignored with the below setting)
//
// (These are not aliases, this setting is ignored outside of doc comments).
// (Phan does not check if classes with these names exist)
//
// Example setting: `['unknown' => '', 'number' => 'int|float', 'char' => 'string', 'long' => 'int', 'the' => '']`
'phpdoc_type_mapping' => [],
// Set to true in order to attempt to detect dead
// (unreferenced) code. Keep in mind that the
// results will only be a guess given that classes,
// properties, constants and methods can be referenced
// as variables (like `$class->$property` or
// `$class->$method()`) in ways that we're unable
// to make sense of.
'dead_code_detection' => false,
// Set to true in order to attempt to detect unused variables.
// `dead_code_detection` will also enable unused variable detection.
//
// This has a few known false positives, e.g. for loops or branches.
'unused_variable_detection' => false,
// Set to true in order to attempt to detect redundant and impossible conditions.
//
// This has some false positives involving loops,
// variables set in branches of loops, and global variables.
'redundant_condition_detection' => false,
// If enabled, Phan will act as though it's certain of real return types of a subset of internal functions,
// even if those return types aren't available in reflection (real types were taken from php 7.3 or 8.0-dev, depending on target_php_version).
//
// Note that with php 7 and earlier, php would return null or false for many internal functions if the argument types or counts were incorrect.
// As a result, enabling this setting with target_php_version 8.0 may result in false positives for `--redundant-condition-detection` when codebases also support php 7.x.
'assume_real_types_for_internal_functions' => false,
// If true, this runs a quick version of checks that takes less
// time at the cost of not running as thorough
// of an analysis. You should consider setting this
// to true only when you wish you had more **undiagnosed** issues
// to fix in your code base.
//
// In quick-mode the scanner doesn't rescan a function
// or a method's code block every time a call is seen.
// This means that the problem here won't be detected:
//
// ```php
// <?php
// function test($arg):int {
// return $arg;
// }
// test("abc");
// ```
//
// This would normally generate:
//
// ```
// test.php:3 PhanTypeMismatchReturn Returning type string but test() is declared to return int
// ```
//
// The initial scan of the function's code block has no
// type information for `$arg`. It isn't until we see
// the call and rescan `test()`'s code block that we can
// detect that it is actually returning the passed in
// `string` instead of an `int` as declared.
'quick_mode' => false,
// Override to hardcode existence and types of (non-builtin) globals in the global scope.
// Class names should be prefixed with `\`.
//
// (E.g. `['_FOO' => '\FooClass', 'page' => '\PageClass', 'userId' => 'int']`)
'globals_type_map' => [],
// The minimum severity level to report on. This can be
// set to `Issue::SEVERITY_LOW`, `Issue::SEVERITY_NORMAL` or
// `Issue::SEVERITY_CRITICAL`. Setting it to only
// critical issues is a good place to start on a big
// sloppy mature code base.
'minimum_severity' => Issue::SEVERITY_LOW,
// Add any issue types (such as `'PhanUndeclaredMethod'`)
// to this black-list to inhibit them from being reported.
'suppress_issue_types' => [],
// A regular expression to match files to be excluded
// from parsing and analysis and will not be read at all.
//
// This is useful for excluding groups of test or example
// directories/files, unanalyzable files, or files that
// can't be removed for whatever reason.
// (e.g. `'@Test\.php$@'`, or `'@vendor/.*/(tests|Tests)/@'`)
'exclude_file_regex' => '@^vendor/.*/(tests?|Tests?)/@',
// A list of files that will be excluded from parsing and analysis
// and will not be read at all.
//
// This is useful for excluding hopelessly unanalyzable
// files that can't be removed for whatever reason.
'exclude_file_list' => [],
// A directory list that defines files that will be excluded
// from static analysis, but whose class and method
// information should be included.
//
// Generally, you'll want to include the directories for
// third-party code (such as "vendor/") in this list.
//
// n.b.: If you'd like to parse but not analyze 3rd
// party code, directories containing that code
// should be added to the `directory_list` as well as
// to `exclude_analysis_directory_list`.
'exclude_analysis_directory_list' => [
'vendor/',
],
// Enable this to enable checks of require/include statements referring to valid paths.
// The settings `include_paths` and `warn_about_relative_include_statement` affect the checks.
'enable_include_path_checks' => true,
// The number of processes to fork off during the analysis
// phase.
'processes' => 1,
// List of case-insensitive file extensions supported by Phan.
// (e.g. `['php', 'html', 'htm']`)
'analyzed_file_extensions' => [
'php',
],
// You can put paths to stubs of internal extensions in this config option.
// If the corresponding extension is **not** loaded, then Phan will use the stubs instead.
// Phan will continue using its detailed type annotations,
// but load the constants, classes, functions, and classes (and their Reflection types)
// from these stub files (doubling as valid php files).
// Use a different extension from php to avoid accidentally loading these.
// The `tools/make_stubs` script can be used to generate your own stubs (compatible with php 7.0+ right now)
//
// (e.g. `['xdebug' => '.phan/internal_stubs/xdebug.phan_php']`)
'autoload_internal_extension_signatures' => [],
// A list of plugin files to execute.
//
// Plugins which are bundled with Phan can be added here by providing their name (e.g. `'AlwaysReturnPlugin'`)
//
// Documentation about available bundled plugins can be found [here](https://github.com/phan/phan/tree/master/.phan/plugins).
//
// Alternately, you can pass in the full path to a PHP file with the plugin's implementation (e.g. `'vendor/phan/phan/.phan/plugins/AlwaysReturnPlugin.php'`)
'plugins' => [
'AlwaysReturnPlugin',
'PregRegexCheckerPlugin',
'UnreachableCodePlugin',
],
// A list of directories that should be parsed for class and
// method information. After excluding the directories
// defined in `exclude_analysis_directory_list`, the remaining
// files will be statically analyzed for errors.
//
// Thus, both first-party and third-party code being used by
// your application should be included in this list.
'directory_list' => [
'src/danog/MadelineProto',
'vendor/amphp/amp/lib',
'vendor/amphp/byte-stream/lib',
'vendor/amphp/dns/lib',
'vendor/amphp/file/src',
'vendor/amphp/http-client-cookies/src',
'vendor/amphp/http-client/src',
'vendor/amphp/http-server/src',
'vendor/amphp/http/src',
'vendor/amphp/php-cs-fixer-config/src',
'vendor/amphp/socket/src',
'vendor/amphp/websocket-client/src',
'vendor/amphp/websocket/src',
'vendor/danog/7to5/src',
'vendor/danog/7to70/src',
'vendor/danog/dns-over-https/lib',
'vendor/danog/ipc/lib',
'vendor/danog/magicalserializer/src',
'vendor/danog/primemodule/lib',
'vendor/danog/tg-file-decoder/src',
'vendor/danog/tgseclib/phpseclib',
'vendor/erusev/parsedown',
'vendor/league/uri/src'
],
// A list of individual files to include in analysis
// with a path relative to the root directory of the
// project.
'file_list' => [],
];

View File

@ -33,7 +33,8 @@
"danog/magicalserializer": "^1.0", "danog/magicalserializer": "^1.0",
"league/uri": "^6", "league/uri": "^6",
"danog/ipc": "^0.1", "danog/ipc": "^0.1",
"tivie/htaccess-parser": "^0.2.3" "tivie/htaccess-parser": "^0.2.3",
"amphp/log": "^1.1"
}, },
"require-dev": { "require-dev": {
"vlucas/phpdotenv": "^3", "vlucas/phpdotenv": "^3",

View File

@ -102,7 +102,7 @@ $settings = [
], ],
'serialization' => [ 'serialization' => [
'serialization_interval' => 30, 'serialization_interval' => 30,
], ]
]; ];
$MadelineProto = new API('bot.madeline', $settings); $MadelineProto = new API('bot.madeline', $settings);

View File

@ -0,0 +1,102 @@
<?php
/**
* Server 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-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
/*
* Socket server for multi-language API
*/
class Server
{
private $settings;
private $pids = [];
private $mypid;
public function __construct($settings)
{
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
\danog\MadelineProto\Logger::constructor(3);
if (!\extension_loaded('sockets')) {
throw new Exception(['extension', 'sockets']);
}
if (!\extension_loaded('pcntl')) {
throw new Exception(['extension', 'pcntl']);
}
$this->settings = $settings;
$this->mypid = \getmypid();
}
public function start()
{
\pcntl_signal(SIGTERM, [$this, 'sigHandler']);
\pcntl_signal(SIGINT, [$this, 'sigHandler']);
\pcntl_signal(SIGCHLD, [$this, 'sigHandler']);
$this->sock = new \Socket($this->settings['type'], SOCK_STREAM, $this->settings['protocol']);
$this->sock->bind($this->settings['address'], $this->settings['port']);
$this->sock->listen();
$this->sock->setBlocking(true);
$timeout = 2;
$this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout);
$this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout);
\danog\MadelineProto\Logger::log('Server started! Listening on ' . $this->settings['address'] . ':' . $this->settings['port']);
while (true) {
\pcntl_signal_dispatch();
try {
if ($sock = $this->sock->accept()) {
$this->handle($sock);
}
} catch (\danog\MadelineProto\Exception $e) {
}
}
}
private function handle($socket)
{
$pid = \pcntl_fork();
if ($pid == -1) {
die('could not fork');
} elseif ($pid) {
return $this->pids[] = $pid;
}
$handler = new $this->settings['handler']($socket, $this->settings['extra'], null, null, null, null, null);
$handler->loop();
die;
}
public function __destruct()
{
if ($this->mypid === \getmypid()) {
\danog\MadelineProto\Logger::log('Shutting main process ' . $this->mypid . ' down');
unset($this->sock);
foreach ($this->pids as $pid) {
\danog\MadelineProto\Logger::log("Waiting for {$pid}");
\pcntl_wait($pid);
}
\danog\MadelineProto\Logger::log('Done, closing main process');
return;
}
}
public function sigHandler($sig)
{
switch ($sig) {
case SIGTERM:
case SIGINT:
exit;
case SIGCHLD:
\pcntl_waitpid(-1, $status);
break;
}
}
}

18
psalm.xml Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0"?>
<psalm
totallyTyped="false"
errorLevel="6"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
<file name="src/danog/MadelineProto/InternalDoc.php" />
<file name="src/danog/MadelineProto/TON/InternalDoc.php" />
</ignoreFiles>
</projectFiles>
</psalm>

View File

@ -271,7 +271,7 @@ class API extends InternalDoc
* *
* @return \Generator * @return \Generator
*/ */
private function startAndLoopAsync(string $eventHandler): \Generator public function startAndLoopAsync(string $eventHandler): \Generator
{ {
$this->async(true); $this->async(true);
while (true) { while (true) {

View File

@ -39,6 +39,7 @@ use Amp\Websocket\Client\Rfc6455Connector;
use danog\MadelineProto\MTProto\PermAuthKey; use danog\MadelineProto\MTProto\PermAuthKey;
use danog\MadelineProto\MTProto\TempAuthKey; use danog\MadelineProto\MTProto\TempAuthKey;
use danog\MadelineProto\Stream\Common\BufferedRawStream; use danog\MadelineProto\Stream\Common\BufferedRawStream;
use danog\MadelineProto\Stream\Common\UdpBufferedStream;
use danog\MadelineProto\Stream\ConnectionContext; use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoTransport\AbridgedStream; use danog\MadelineProto\Stream\MTProtoTransport\AbridgedStream;
use danog\MadelineProto\Stream\MTProtoTransport\FullStream; use danog\MadelineProto\Stream\MTProtoTransport\FullStream;
@ -212,7 +213,7 @@ class DataCenter
$this->settings = $settings; $this->settings = $settings;
foreach ($this->sockets as $key => $socket) { foreach ($this->sockets as $key => $socket) {
if ($socket instanceof DataCenterConnection && !\strpos($key, '_bk')) { if ($socket instanceof DataCenterConnection && !\strpos($key, '_bk')) {
//$this->API->logger->logger(\sprintf(Lang::$current_lang['dc_con_stop'], $key), \danog\MadelineProto\Logger::VERBOSE); //$this->API->logger->logger(\sprintf(Lang::$current_lang['dc_con_stop'], $key), Logger::VERBOSE);
if ($reconnectAll || isset($changed[$id])) { if ($reconnectAll || isset($changed[$id])) {
$this->API->logger->logger("Disconnecting all before reconnect!"); $this->API->logger->logger("Disconnecting all before reconnect!");
$socket->needReconnect(true); $socket->needReconnect(true);
@ -238,6 +239,24 @@ class DataCenter
} }
} }
} }
/**
* Set VoIP endpoints.
*
* @param array $endpoints Endpoints
*
* @return void
*/
public function setVoIPEndpoints(array $endpoints): void
{
}
/**
* Connect to specified DC.
*
* @param string $dc_number DC to connect to
* @param integer $id Connection ID to re-establish (optional)
*
* @return \Generator<bool>
*/
public function dcConnect(string $dc_number, int $id = -1): \Generator public function dcConnect(string $dc_number, int $id = -1): \Generator
{ {
$old = isset($this->sockets[$dc_number]) && ($this->sockets[$dc_number]->shouldReconnect() || $id !== -1 && $this->sockets[$dc_number]->hasConnection($id) && $this->sockets[$dc_number]->getConnection($id)->shouldReconnect()); $old = isset($this->sockets[$dc_number]) && ($this->sockets[$dc_number]->shouldReconnect() || $id !== -1 && $this->sockets[$dc_number]->hasConnection($id) && $this->sockets[$dc_number]->getConnection($id)->shouldReconnect());
@ -252,30 +271,36 @@ class DataCenter
foreach ($ctxs as $ctx) { foreach ($ctxs as $ctx) {
try { try {
if ($old) { if ($old) {
$this->API->logger->logger("Reconnecting to DC {$dc_number} ({$id}) from existing", \danog\MadelineProto\Logger::WARNING); $this->API->logger->logger("Reconnecting to DC {$dc_number} ({$id}) from existing", Logger::WARNING);
$this->sockets[$dc_number]->setExtra($this->API); $this->sockets[$dc_number]->setExtra($this->API);
yield from $this->sockets[$dc_number]->connect($ctx, $id); yield from $this->sockets[$dc_number]->connect($ctx, $id);
} else { } else {
$this->API->logger->logger("Connecting to DC {$dc_number} from scratch", \danog\MadelineProto\Logger::WARNING); $this->API->logger->logger("Connecting to DC {$dc_number} from scratch", Logger::WARNING);
$this->sockets[$dc_number] = new DataCenterConnection(); $this->sockets[$dc_number] = new DataCenterConnection();
$this->sockets[$dc_number]->setExtra($this->API); $this->sockets[$dc_number]->setExtra($this->API);
yield from $this->sockets[$dc_number]->connect($ctx); yield from $this->sockets[$dc_number]->connect($ctx);
} }
$this->API->logger->logger('OK!', \danog\MadelineProto\Logger::WARNING); $this->API->logger->logger('OK!', Logger::WARNING);
return true; return true;
} catch (\Throwable $e) { } catch (\Throwable $e) {
if (@\constant("MADELINEPROTO_TEST") === 'pony') { if (@\constant("MADELINEPROTO_TEST") === 'pony') {
throw $e; throw $e;
} }
$this->API->logger->logger("Connection failed ({$dc_number}): ".$e->getMessage(), \danog\MadelineProto\Logger::ERROR); $this->API->logger->logger("Connection failed ({$dc_number}): ".$e->getMessage(), Logger::ERROR);
} }
} }
throw new Exception("Could not connect to DC {$dc_number}"); throw new Exception("Could not connect to DC {$dc_number}");
} }
/** /**
* @param int|string $dc_number * Generate contexts.
*
* @param integer $dc_number DC ID to generate contexts for
* @param string $uri URI
* @param ConnectContext $context Connection context
*
* @return array
*/ */
public function generateContexts($dc_number = 0, string $uri = '', ConnectContext $context = null) public function generateContexts($dc_number = 0, string $uri = '', ConnectContext $context = null): array
{ {
$ctxs = []; $ctxs = [];
$combos = []; $combos = [];
@ -309,6 +334,9 @@ class DataCenter
case 'https': case 'https':
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]]; $default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]];
break; break;
case 'udp':
$default = [[DefaultStream::getName(), []], [UdpBufferedStream::getName(), []]];
break;
default: default:
throw new Exception(Lang::$current_lang['protocol_invalid']); throw new Exception(Lang::$current_lang['protocol_invalid']);
} }
@ -490,7 +518,7 @@ class DataCenter
} }
if (empty($ctxs)) { if (empty($ctxs)) {
unset($this->sockets[$dc_number]); unset($this->sockets[$dc_number]);
$this->API->logger->logger("No info for DC {$dc_number}", \danog\MadelineProto\Logger::ERROR); $this->API->logger->logger("No info for DC {$dc_number}", Logger::ERROR);
} elseif (@\constant("MADELINEPROTO_TEST") === 'pony') { } elseif (@\constant("MADELINEPROTO_TEST") === 'pony') {
return [$ctxs[0]]; return [$ctxs[0]];
} }

View File

@ -621,7 +621,7 @@ class Lang
{ {
if (!isset(\danog\MadelineProto\Lang::$lang['en'][$key]) || $force) { if (!isset(\danog\MadelineProto\Lang::$lang['en'][$key]) || $force) {
\danog\MadelineProto\Lang::$lang['en'][$key] = $value; \danog\MadelineProto\Lang::$lang['en'][$key] = $value;
\file_put_contents(__DIR__.'/Lang.php', \sprintf(self::$template, \var_export(\danog\MadelineProto\Lang::$lang, true), \var_export(\danog\MadelineProto\Lang::$lang['en'], true))); //\file_put_contents(__DIR__.'/Lang.php', \sprintf(self::$template, \var_export(\danog\MadelineProto\Lang::$lang, true), \var_export(\danog\MadelineProto\Lang::$lang['en'], true)));
} }
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -87,7 +87,7 @@ class ReadLoop extends SignalLoop
Tools::callForkDefer((function () use ($error, $shared, $connection, $datacenter, $API): \Generator { Tools::callForkDefer((function () use ($error, $shared, $connection, $datacenter, $API): \Generator {
if ($error === -404) { if ($error === -404) {
if ($shared->hasTempAuthKey()) { if ($shared->hasTempAuthKey()) {
$API->logger->logger("WARNING: Resetting auth key in DC {$datacenter}...", \danog\MadelineProto\Logger::WARNING); $API->logger->logger("WARNING: Resetting auth key in DC {$datacenter}...", Logger::WARNING);
$shared->setTempAuthKey(null); $shared->setTempAuthKey(null);
$shared->resetSession(); $shared->resetSession();
foreach ($connection->new_outgoing as $message_id) { foreach ($connection->new_outgoing as $message_id) {
@ -99,13 +99,13 @@ class ReadLoop extends SignalLoop
yield from $connection->reconnect(); yield from $connection->reconnect();
} }
} elseif ($error === -1) { } elseif ($error === -1) {
$API->logger->logger("WARNING: Got quick ack from DC {$datacenter}", \danog\MadelineProto\Logger::WARNING); $API->logger->logger("WARNING: Got quick ack from DC {$datacenter}", Logger::WARNING);
yield from $connection->reconnect(); yield from $connection->reconnect();
} elseif ($error === 0) { } elseif ($error === 0) {
$API->logger->logger("Got NOOP from DC {$datacenter}", \danog\MadelineProto\Logger::WARNING); $API->logger->logger("Got NOOP from DC {$datacenter}", Logger::WARNING);
yield from $connection->reconnect(); yield from $connection->reconnect();
} elseif ($error === -429) { } elseif ($error === -429) {
$API->logger->logger("Got -429 from DC {$datacenter}", \danog\MadelineProto\Logger::WARNING); $API->logger->logger("Got -429 from DC {$datacenter}", Logger::WARNING);
yield Tools::sleep(1); yield Tools::sleep(1);
yield from $connection->reconnect(); yield from $connection->reconnect();
} else { } else {
@ -138,14 +138,14 @@ class ReadLoop extends SignalLoop
$API->logger->logger($e->getReason()); $API->logger->logger($e->getReason());
if (\strpos($e->getReason(), ' ') === 0) { if (\strpos($e->getReason(), ' ') === 0) {
$payload = -\substr($e->getReason(), 7); $payload = -\substr($e->getReason(), 7);
$API->logger->logger("Received {$payload} from DC ".$datacenter, \danog\MadelineProto\Logger::ERROR); $API->logger->logger("Received {$payload} from DC ".$datacenter, Logger::ERROR);
return $payload; return $payload;
} }
throw $e; throw $e;
} }
if ($payload_length === 4) { if ($payload_length === 4) {
$payload = \danog\MadelineProto\Tools::unpackSignedInt(yield $buffer->bufferRead(4)); $payload = \danog\MadelineProto\Tools::unpackSignedInt(yield $buffer->bufferRead(4));
$API->logger->logger("Received {$payload} from DC ".$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE); $API->logger->logger("Received {$payload} from DC ".$datacenter, Logger::ULTRA_VERBOSE);
return $payload; return $payload;
} }
$connection->reading(true); $connection->reading(true);
@ -160,9 +160,9 @@ class ReadLoop extends SignalLoop
$message_data = yield $buffer->bufferRead($message_length); $message_data = yield $buffer->bufferRead($message_length);
$left = $payload_length - $message_length - 4 - 8 - 8; $left = $payload_length - $message_length - 4 - 8 - 8;
if ($left) { if ($left) {
$API->logger->logger('Padded unencrypted message', \danog\MadelineProto\Logger::ULTRA_VERBOSE); $API->logger->logger('Padded unencrypted message', Logger::ULTRA_VERBOSE);
if ($left < (-$message_length & 15)) { if ($left < (-$message_length & 15)) {
$API->logger->logger('Protocol padded unencrypted message', \danog\MadelineProto\Logger::ULTRA_VERBOSE); $API->logger->logger('Protocol padded unencrypted message', Logger::ULTRA_VERBOSE);
} }
yield $buffer->bufferRead($left); yield $buffer->bufferRead($left);
} }
@ -179,7 +179,7 @@ class ReadLoop extends SignalLoop
/* /*
$server_salt = substr($decrypted_data, 0, 8); $server_salt = substr($decrypted_data, 0, 8);
if ($server_salt != $shared->getTempAuthKey()->getServerSalt()) { if ($server_salt != $shared->getTempAuthKey()->getServerSalt()) {
$API->logger->logger('WARNING: Server salt mismatch (my server salt '.$shared->getTempAuthKey()->getServerSalt().' is not equal to server server salt '.$server_salt.').', \danog\MadelineProto\Logger::WARNING); $API->logger->logger('WARNING: Server salt mismatch (my server salt '.$shared->getTempAuthKey()->getServerSalt().' is not equal to server server salt '.$server_salt.').', Logger::WARNING);
} }
*/ */
$session_id = \substr($decrypted_data, 8, 8); $session_id = \substr($decrypted_data, 8, 8);
@ -213,16 +213,18 @@ class ReadLoop extends SignalLoop
} }
$connection->incoming_messages[$message_id] = ['seq_no' => $seq_no]; $connection->incoming_messages[$message_id] = ['seq_no' => $seq_no];
} else { } else {
$API->logger->logger('Got unknown auth_key id', \danog\MadelineProto\Logger::ERROR); $API->logger->logger('Got unknown auth_key id', Logger::ERROR);
return -404; return -404;
} }
$deserialized = $API->getTL()->deserialize($message_data, ['type' => '', 'connection' => $connection]); $deserialized = $API->getTL()->deserialize($message_data, ['type' => '', 'connection' => $connection]);
if (isset($API->referenceDatabase)) {
$API->referenceDatabase->reset(); $API->referenceDatabase->reset();
}
$connection->incoming_messages[$message_id]['content'] = $deserialized; $connection->incoming_messages[$message_id]['content'] = $deserialized;
$connection->incoming_messages[$message_id]['response'] = -1; $connection->incoming_messages[$message_id]['response'] = -1;
$connection->new_incoming[$message_id] = $message_id; $connection->new_incoming[$message_id] = $message_id;
//$connection->last_http_wait = 0; //$connection->last_http_wait = 0;
$API->logger->logger('Received payload from DC '.$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE); $API->logger->logger('Received payload from DC '.$datacenter, Logger::ULTRA_VERBOSE);
} finally { } finally {
$connection->reading(false); $connection->reading(false);
} }

View File

@ -349,7 +349,7 @@ trait ResponseHandler
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]); $this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
return; return;
} }
if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'HISTORY_GET_FAILED', 'RPC_CALL_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running', 'No workers running'])) { if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'HISTORY_GET_FAILED', 'RPC_CONNECT_FAILED', 'RPC_CALL_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running', 'No workers running'])) {
Loop::delay(1 * 1000, [$this, 'methodRecall'], ['message_id' => $request_id]); Loop::delay(1 * 1000, [$this, 'methodRecall'], ['message_id' => $request_id]);
return; return;
} }

View File

@ -19,29 +19,11 @@
namespace danog\MadelineProto\Stream; namespace danog\MadelineProto\Stream;
use Amp\Promise;
/** /**
* Buffer interface. * Buffer interface.
* *
* @author Daniil Gentili <daniil@daniil.it> * @author Daniil Gentili <daniil@daniil.it>
*/ */
interface BufferInterface interface BufferInterface extends ReadBufferInterface, WriteBufferInterface
{ {
/**
* Read data asynchronously.
*
* @param int $length How much data to read
*
* @return Promise
*/
public function bufferRead(int $length): Promise;
/**
* Write data asynchronously.
*
* @param string $data Data to write
*
* @return Promise
*/
public function bufferWrite(string $data): Promise;
} }

View File

@ -33,7 +33,7 @@ interface BufferedStreamInterface extends StreamInterface
* *
* @param int $length Length of payload, as detected by this layer * @param int $length Length of payload, as detected by this layer
* *
* @return Promise * @return Promise<BufferInterface>
*/ */
public function getReadBuffer(&$length): Promise; public function getReadBuffer(&$length): Promise;
/** /**

View File

@ -207,6 +207,21 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
} }
return $this->write($data); return $this->write($data);
} }
/**
* Get remaining data from buffer
*
* @return string
*/
public function bufferClear(): string
{
$size = \fstat($this->memory_stream)['size'];
$offset = \ftell($this->memory_stream);
$buffer_length = $size - $offset;
$data = fread($this->memory_stream, $buffer_length);
fclose($this->memory_stream);
$this->memory_stream = null;
return $data;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
* *

View File

@ -0,0 +1,201 @@
<?php
/**
* UDP stream wrapper.
*
* 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-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Common;
use Amp\ByteStream\ClosedException;
use Amp\Failure;
use Amp\Promise;
use Amp\Socket\EncryptableSocket;
use Amp\Success;
use danog\MadelineProto\Exception;
use danog\MadelineProto\Stream\Async\BufferedStream;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\RawStreamInterface;
use danog\MadelineProto\Stream\ReadBufferInterface;
use danog\MadelineProto\Stream\StreamInterface;
use danog\MadelineProto\Stream\Transport\DefaultStream;
use danog\MadelineProto\Stream\WriteBufferInterface;
/**
* UDP stream wrapper.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class UdpBufferedStream extends DefaultStream implements BufferedStreamInterface
{
use BufferedStream;
private RawStreamInterface $stream;
/**
* Connect to stream.
*
* @param ConnectionContext $ctx The connection context
*
* @return \Generator
*/
public function connect(ConnectionContext $ctx, string $header = ''): \Generator
{
$this->stream = (yield from $ctx->getStream($header));
}
/**
* Async close.
*
* @return Promise
*/
public function disconnect()
{
return $this->stream->disconnect();
}
/**
* Get read buffer asynchronously.
*
* @param int $length Length of payload, as detected by this layer
*
* @return Promise
*/
public function getReadBuffer(&$length): Promise
{
if (!$this->stream) {
return new Failure(new ClosedException("MadelineProto stream was disconnected"));
}
$chunk = yield $this->read();
if ($chunk === null) {
$this->disconnect();
throw new \danog\MadelineProto\NothingInTheSocketException();
}
$length = \strlen($chunk);
return new Success(new class($chunk) implements ReadBufferInterface {
/**
* Buffer.
*
* @var resource
*/
private $buffer;
/**
* Constructor function.
*
* @param string $buf Buffer
*/
public function __construct(string $buf)
{
$this->buffer = \fopen('php://memory', 'r+');
\fwrite($this->buffer, $buf);
\fseek($this->buffer, 0);
}
/**
* Read data from buffer.
*
* @param integer $length Length
*
* @return Promise<string>
*/
public function bufferRead(int $length): Promise
{
return new Success(\fread($this->buffer, $length));
}
/**
* Destructor function.
*/
public function __destruct(): void
{
\fclose($this->buffer);
}
});
}
/**
* Get write buffer asynchronously.
*
* @param int $length Total length of data that is going to be piped in the buffer
*
* @return Promise
*/
public function getWriteBuffer(int $length, string $append = ''): Promise
{
return new Success(new class($length, $append, $this) implements WriteBufferInterface {
private int $length;
private string $append;
private int $append_after;
private RawStreamInterface $stream;
private string $data = '';
/**
* Constructor function
*
* @param integer $length
* @param string $append
* @param RawStreamInterface $rawStreamInterface
*/
public function __construct(int $length, string $append, RawStreamInterface $rawStreamInterface)
{
$this->stream = $rawStreamInterface;
$this->length = $length;
if (\strlen($append)) {
$this->append = $append;
$this->append_after = $length - \strlen($append);
}
}
/**
* Async write.
*
* @param string $data Data to write
*
* @return Promise
*/
public function bufferWrite(string $data): Promise
{
$this->data .= $data;
if ($this->append_after) {
$this->append_after -= \strlen($data);
if ($this->append_after === 0) {
$this->data .= $this->append;
$this->append = '';
return $this->stream->write($this->data);
} elseif ($this->append_after < 0) {
$this->append_after = 0;
$this->append = '';
throw new Exception('Tried to send too much out of frame data, cannot append');
}
}
return new Success(strlen($data));
}
});
}
/**
* {@inheritdoc}
*
* @return EncryptableSocket
*/
public function getSocket(): EncryptableSocket
{
return $this->stream->getSocket();
}
/**
* {@inheritDoc}
*
* @return RawStreamInterface
*/
public function getStream(): RawStreamInterface
{
return $this->stream;
}
public static function getName(): string
{
return __CLASS__;
}
}

View File

@ -411,9 +411,7 @@ class ConnectionContext
/** /**
* Get a stream from the stream chain. * Get a stream from the stream chain.
* *
* @internal Generator func * @return \Generator<StreamInterface>
*
* @return \Generator
*/ */
public function getStream(string $buffer = ''): \Generator public function getStream(string $buffer = ''): \Generator
{ {

View File

@ -0,0 +1,39 @@
<?php
/**
* Buffer interface.
*
* 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-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream;
use Amp\Promise;
/**
* Read buffer interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface ReadBufferInterface
{
/**
* Read data asynchronously.
*
* @param int $length How much data to read
*
* @return Promise
*/
public function bufferRead(int $length): Promise;
}

View File

@ -46,7 +46,7 @@ class DefaultStream implements RawStreamInterface, ProxyStreamInterface
* *
* @var EncryptableSocket * @var EncryptableSocket
*/ */
private $stream; protected $stream;
/** /**
* Connector. * Connector.
* *

View File

@ -0,0 +1,39 @@
<?php
/**
* Buffer interface.
*
* 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-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream;
use Amp\Promise;
/**
* Write buffer interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface WriteBufferInterface
{
/**
* Write data asynchronously.
*
* @param string $data Data to write
*
* @return Promise
*/
public function bufferWrite(string $data): Promise;
}