MadelineProto/src/danog/MadelineProto/DataCenterConnection.php

555 lines
14 KiB
PHP
Raw Normal View History

2019-08-31 22:43:58 +02:00
<?php
/**
* Connection module handling all connections to a datacenter.
*
* 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;
2019-09-02 15:30:29 +02:00
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
use danog\MadelineProto\MTProto\AuthKey;
use danog\MadelineProto\MTProto\PermAuthKey;
use danog\MadelineProto\MTProto\TempAuthKey;
2019-08-31 22:43:58 +02:00
use danog\MadelineProto\Stream\ConnectionContext;
2019-09-02 14:37:30 +02:00
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
2019-09-01 14:07:04 +02:00
use JsonSerializable;
2019-08-31 22:43:58 +02:00
2019-09-01 14:07:04 +02:00
class DataCenterConnection implements JsonSerializable
2019-08-31 22:43:58 +02:00
{
const READ_WEIGHT = 1;
const READ_WEIGHT_MEDIA = 5;
const WRITE_WEIGHT = 10;
2019-09-02 17:08:36 +02:00
2019-08-31 22:43:58 +02:00
/**
* Temporary auth key.
*
2019-09-01 23:39:29 +02:00
* @var TempAuthKey|null
2019-08-31 22:43:58 +02:00
*/
private $tempAuthKey;
/**
* Permanent auth key.
*
2019-09-01 23:39:29 +02:00
* @var PermAuthKey|null
2019-08-31 22:43:58 +02:00
*/
private $permAuthKey;
2019-08-31 22:43:58 +02:00
/**
2019-09-01 01:52:28 +02:00
* Connections open to a certain DC.
2019-08-31 22:43:58 +02:00
*
2019-09-01 14:07:04 +02:00
* @var array<string, Connection>
2019-08-31 22:43:58 +02:00
*/
private $connections = [];
2019-09-01 01:52:28 +02:00
/**
2019-09-01 14:07:04 +02:00
* Connection weights.
2019-09-01 01:52:28 +02:00
*
2019-09-01 14:07:04 +02:00
* @var array<string, int>
2019-09-01 01:52:28 +02:00
*/
private $availableConnections = [];
2019-08-31 22:43:58 +02:00
/**
2019-09-01 01:52:28 +02:00
* Main API instance.
2019-08-31 22:43:58 +02:00
*
* @var \danog\MadelineProto\MTProto
*/
private $API;
/**
2019-09-01 01:52:28 +02:00
* Connection context.
2019-08-31 22:43:58 +02:00
*
* @var ConnectionContext
*/
private $ctx;
/**
2019-09-01 01:52:28 +02:00
* DC ID.
2019-08-31 22:43:58 +02:00
*
* @var string
*/
private $datacenter;
2019-09-01 01:52:28 +02:00
/**
2019-09-01 23:39:29 +02:00
* Linked DC ID.
2019-09-01 01:52:28 +02:00
*
2019-09-01 23:39:29 +02:00
* @var string
2019-09-01 01:52:28 +02:00
*/
2019-09-01 23:39:29 +02:00
private $linked;
2019-09-01 01:52:28 +02:00
/**
2019-09-01 14:07:04 +02:00
* Loop to keep weights at sane value.
2019-09-01 01:52:28 +02:00
*
* @var \danog\MadelineProto\Loop\Generic\PeriodicLoop
*/
private $robinLoop;
2019-09-02 14:37:30 +02:00
/**
* Decrement roundrobin weight by this value if busy reading.
*
* @var integer
*/
private $decRead = 1;
/**
* Decrement roundrobin weight by this value if busy writing.
*
* @var integer
*/
private $decWrite = 10;
2019-08-31 22:43:58 +02:00
/**
* Get auth key.
*
* @param boolean $temp Whether to fetch the temporary auth key
*
2019-09-01 14:07:04 +02:00
* @return AuthKey
2019-08-31 22:43:58 +02:00
*/
2019-09-01 14:07:04 +02:00
public function getAuthKey(bool $temp = true): AuthKey
2019-08-31 22:43:58 +02:00
{
return $this->{$temp ? 'tempAuthKey' : 'permAuthKey'};
2019-08-31 22:43:58 +02:00
}
/**
* Check if auth key is present.
*
2019-09-01 23:39:29 +02:00
* @param boolean|null $temp Whether to fetch the temporary auth key
2019-08-31 22:43:58 +02:00
*
* @return bool
*/
public function hasAuthKey(bool $temp = true): bool
{
return $this->{$temp ? 'tempAuthKey' : 'permAuthKey'} !== null && $this->{$temp ? 'tempAuthKey' : 'permAuthKey'}->hasAuthKey();
2019-08-31 22:43:58 +02:00
}
/**
* Set auth key.
*
2019-09-01 14:07:04 +02:00
* @param AuthKey|null $key The auth key
2019-09-01 23:39:29 +02:00
* @param boolean|null $temp Whether to set the temporary auth key
2019-08-31 22:43:58 +02:00
*
* @return void
*/
2019-09-01 14:07:04 +02:00
public function setAuthKey(?AuthKey $key, bool $temp = true)
2019-08-31 22:43:58 +02:00
{
$this->{$temp ? 'tempAuthKey' : 'permAuthKey'} = $key;
2019-08-31 22:43:58 +02:00
}
2019-09-01 23:39:29 +02:00
/**
* Get temporary authorization key.
*
* @return AuthKey
*/
public function getTempAuthKey(): TempAuthKey
{
return $this->getAuthKey(true);
}
/**
* Get permanent authorization key.
*
* @return AuthKey
*/
public function getPermAuthKey(): PermAuthKey
{
return $this->getAuthKey(false);
}
/**
* Check if has temporary authorization key.
*
* @return boolean
*/
public function hasTempAuthKey(): bool
{
return $this->hasAuthKey(true);
}
/**
* Check if has permanent authorization key.
*
* @return boolean
*/
public function hasPermAuthKey(): bool
{
return $this->hasAuthKey(false);
}
/**
* Set temporary authorization key.
*
* @param TempAuthKey|null $key Auth key
*
* @return void
*/
public function setTempAuthKey(?TempAuthKey $key)
{
return $this->setAuthKey($key, true);
}
/**
* Set permanent authorization key.
*
* @param PermAuthKey|null $key Auth key
*
* @return void
*/
public function setPermAuthKey(?PermAuthKey $key)
{
return $this->setAuthKey($key, false);
}
/**
* Bind temporary and permanent auth keys.
*
* @param bool $pfs Whether to bind using PFS
*
* @return void
*/
public function bind(bool $pfs = true)
{
$this->tempAuthKey->bind($this->permAuthKey, $pfs);
2019-09-01 23:39:29 +02:00
}
2019-08-31 22:43:58 +02:00
/**
* Check if we are logged in.
*
* @return boolean
*/
public function isAuthorized(): bool
{
2019-09-01 23:39:29 +02:00
return $this->hasTempAuthKey() ? $this->getTempAuthKey()->isAuthorized() : false;
2019-08-31 22:43:58 +02:00
}
/**
* Set the authorized boolean.
*
* @param boolean $authorized Whether we are authorized
*
* @return void
*/
public function authorized(bool $authorized)
{
2019-09-02 15:30:29 +02:00
if ($authorized) {
$this->getTempAuthKey()->authorized($authorized);
} elseif ($this->hasTempAuthKey()) {
2019-09-02 15:30:29 +02:00
$this->getTempAuthKey()->authorized($authorized);
}
2019-09-01 23:39:29 +02:00
}
/**
* Link permanent authorization info of main DC to media DC.
*
* @param string $dc Main DC ID
*
* @return void
*/
public function link(string $dc)
{
$this->linked = $dc;
$this->permAuthKey = &$this->API->datacenter->getDataCenterConnection($dc)->permAuthKey;
2019-08-31 22:43:58 +02:00
}
2019-09-01 14:07:04 +02:00
/**
* Reset MTProto sessions.
*
* @return void
*/
public function resetSession()
{
foreach ($this->connections as $socket) {
$socket->resetSession();
}
}
/**
2019-09-02 17:08:36 +02:00
* Create MTProto sessions if needed.
*
* @return void
*/
public function createSession()
{
foreach ($this->connections as $socket) {
$socket->createSession();
}
}
2019-09-01 14:07:04 +02:00
/**
* Flush all pending packets.
*
* @return void
*/
public function flush()
{
foreach ($this->connections as $socket) {
$socket->flush();
}
}
2019-09-01 23:39:29 +02:00
2019-08-31 22:43:58 +02:00
/**
2019-09-01 01:52:28 +02:00
* Get connection context.
2019-08-31 22:43:58 +02:00
*
* @return ConnectionContext
*/
public function getCtx(): ConnectionContext
{
return $this->ctx;
}
/**
* Connect function.
*
* @param ConnectionContext $ctx Connection context
* @param int $id Optional connection ID to reconnect
2019-08-31 22:43:58 +02:00
*
* @return \Generator
*/
public function connect(ConnectionContext $ctx, int $id = -1): \Generator
2019-08-31 22:43:58 +02:00
{
2019-09-01 01:52:28 +02:00
$this->API->logger->logger("Trying shared connection via $ctx", \danog\MadelineProto\Logger::WARNING);
2019-08-31 22:43:58 +02:00
$this->ctx = $ctx->getCtx();
$this->datacenter = $ctx->getDc();
$media = $ctx->isMedia() || $ctx->isCDN();
2019-08-31 22:43:58 +02:00
$count = $media ? $this->API->settings['connection_settings']['media_socket_count']['min'] : 1;
2019-08-31 22:43:58 +02:00
2019-09-01 01:52:28 +02:00
if ($count > 1) {
if (!$this->robinLoop) {
$this->robinLoop = new PeriodicLoop($this->API, [$this, 'even'], "robin loop DC {$this->datacenter}", $this->API->settings['connection_settings']['robin_period']);
2019-09-01 01:52:28 +02:00
}
$this->robinLoop->start();
}
$this->decRead = $media ? self::READ_WEIGHT_MEDIA : self::READ_WEIGHT;
$this->decWrite = self::WRITE_WEIGHT;
2019-09-01 01:52:28 +02:00
if ($id === -1 || !isset($this->connections[$id])) {
$this->connections = [];
$this->availableConnections = [];
2019-09-02 17:08:36 +02:00
yield $this->connectMore($count);
} else {
yield $this->connections[$id]->connect($ctx);
}
}
/**
2019-09-02 17:08:36 +02:00
* Connect to the DC using count more sockets.
*
* @param integer $count Number of sockets to open
2019-09-02 17:08:36 +02:00
*
* @return void
*/
private function connectMore(int $count)
{
$ctx = $this->ctx->getCtx();
2019-09-02 17:08:36 +02:00
$count += $previousCount = \count($this->connections);
for ($x = $previousCount; $x < $count; $x++) {
2019-09-01 01:52:28 +02:00
$this->availableConnections[$x] = 0;
2019-08-31 23:07:20 +02:00
$this->connections[$x] = new Connection();
2019-09-02 14:37:30 +02:00
$this->connections[$x]->setExtra($this, $x);
2019-09-01 14:07:04 +02:00
yield $this->connections[$x]->connect($ctx);
2019-08-31 22:43:58 +02:00
$ctx = $this->ctx->getCtx();
}
}
2019-09-01 01:52:28 +02:00
/**
2019-09-01 14:07:04 +02:00
* Close all connections to DC.
2019-09-01 01:52:28 +02:00
*
* @return void
*/
public function disconnect()
2019-08-31 22:43:58 +02:00
{
2019-09-01 01:52:28 +02:00
$this->API->logger->logger("Disconnecting from shared DC {$this->datacenter}");
if ($this->robinLoop) {
$this->robinLoop->signal(true);
$this->robinLoop = null;
}
foreach ($this->connections as $connection) {
$connection->disconnect();
}
$this->connections = [];
$this->availableConnections = [];
2019-08-31 22:43:58 +02:00
}
2019-09-01 01:52:28 +02:00
/**
2019-09-01 14:07:04 +02:00
* Reconnect to DC.
2019-09-01 01:52:28 +02:00
*
* @return \Generator
*/
public function reconnect(): \Generator
2019-08-31 22:43:58 +02:00
{
2019-09-01 01:52:28 +02:00
$this->API->logger->logger("Reconnecting shared DC {$this->datacenter}");
$this->disconnect();
yield $this->connect($this->ctx);
2019-08-31 22:43:58 +02:00
}
2019-09-01 23:39:29 +02:00
/**
* Get connection for authorization.
*
* @return Connection
*/
public function getAuthConnection(): Connection
{
return $this->connections[0];
}
2019-09-01 01:52:28 +02:00
/**
* Get best socket in round robin.
*
* @return Connection
*/
public function getConnection(): Connection
2019-08-31 22:43:58 +02:00
{
2019-09-02 15:30:29 +02:00
if (\count($this->availableConnections) <= 1) {
2019-09-01 01:52:28 +02:00
return $this->connections[0];
2019-08-31 22:43:58 +02:00
}
$max = \max($this->availableConnections);
2019-09-02 17:08:36 +02:00
$key = \array_search($max, $this->availableConnections);
2019-09-01 01:52:28 +02:00
// Decrease to implement round robin
$this->availableConnections[$key]--;
return $this->connections[$key];
2019-08-31 22:43:58 +02:00
}
2019-09-01 01:52:28 +02:00
/**
2019-09-01 14:07:04 +02:00
* Even out round robin values.
2019-09-01 01:52:28 +02:00
*
* @return void
*/
public function even()
2019-08-31 22:43:58 +02:00
{
if (\min($this->availableConnections) < 100) {
$max = $this->isMedia() || $this->isCDN() ? $this->API->settings['connection_settings']['media_socket_count']['max'] : 1;
if (\count($this->availableConnections) < $max) {
$this->connectMore(2);
} else {
foreach ($this->availableConnections as &$value) {
$value += 1000;
}
2019-09-01 01:52:28 +02:00
}
2019-08-31 22:43:58 +02:00
}
}
2019-09-02 14:37:30 +02:00
/**
* Indicate that one of the sockets is busy reading.
*
* @param boolean $reading Whether we're busy reading
* @param int $x Connection ID
*
* @return void
*/
public function reading(bool $reading, int $x)
{
$this->availableConnections[$x] += $reading ? -$this->decRead : $this->decRead;
}
/**
* Indicate that one of the sockets is busy writing.
*
* @param boolean $writing Whether we're busy writing
* @param int $x Connection ID
*
* @return void
*/
public function writing(bool $writing, int $x)
{
$this->availableConnections[$x] += $writing ? -$this->decWrite : $this->decWrite;
}
2019-09-01 01:52:28 +02:00
/**
2019-09-01 14:07:04 +02:00
* Set main instance.
2019-09-01 01:52:28 +02:00
*
* @param MTProto $API Main instance
2019-09-01 14:07:04 +02:00
*
2019-09-01 01:52:28 +02:00
* @return void
*/
public function setExtra(MTProto $API)
{
$this->API = $API;
}
/**
2019-09-01 14:07:04 +02:00
* Get main instance.
2019-09-01 01:52:28 +02:00
*
* @return MTProto
*/
public function getExtra(): MTProto
{
return $this->API;
}
2019-09-02 14:37:30 +02:00
/**
* Check if is an HTTP connection.
*
* @return boolean
*/
2019-09-02 15:30:29 +02:00
public function isHttp(): bool
2019-09-02 14:37:30 +02:00
{
return \in_array($this->ctx->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]);
}
2019-09-02 15:30:29 +02:00
/**
* Check if is a media connection.
2019-09-02 15:30:29 +02:00
*
* @return boolean
*/
public function isMedia(): bool
{
return $this->ctx->isMedia();
}
/**
* Check if is a CDN connection.
2019-09-02 15:30:29 +02:00
*
* @return boolean
*/
public function isCDN(): bool
{
return $this->ctx->isCDN();
}
2019-09-02 14:37:30 +02:00
/**
* Get DC-specific settings.
2019-09-02 14:37:30 +02:00
*
* @return array
*/
public function getSettings(): array
{
$dc_config_number = isset($this->API->settings['connection_settings'][$this->datacenter]) ? $this->datacenter : 'all';
return $this->API->settings['connection_settings'][$dc_config_number];
}
2019-09-01 14:07:04 +02:00
/**
* JSON serialize function.
*
* @return array
*/
public function jsonSerialize(): array
{
2019-09-01 23:39:29 +02:00
return $this->linked ?
[
'linked' => $this->linked,
'tempAuthKey' => $this->tempAuthKey
] :
[
'permAuthKey' => $this->permAuthKey,
2019-09-01 23:39:29 +02:00
'tempAuthKey' => $this->tempAuthKey
2019-09-01 14:07:04 +02:00
];
}
2019-08-31 22:43:58 +02:00
/**
* Sleep function.
*
* @internal
*
* @return array
*/
public function __sleep()
{
return $this->linked ? ['linked', 'tempAuthKey'] : ['permAuthKey', 'tempAuthKey'];
2019-08-31 22:43:58 +02:00
}
}