From 1f608c0a4c40a3593c4255fdea2f954ec7505611 Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Sun, 29 Nov 2020 19:25:54 +0100 Subject: [PATCH] Fixes --- docs | 2 +- examples/magnaluna | 2 +- schemas | 2 +- .../MTProtoSession/ResponseHandler.php | 5 +- src/danog/MadelineProto/Stream/Ogg/Ogg.php | 2 +- src/danog/MadelineProto/Tools.php | 2 +- src/danog/MadelineProto/VoIP.php | 359 +++++++++++++----- src/danog/MadelineProto/VoIP/Endpoint.php | 18 +- src/danog/MadelineProto/VoIPServerConfig.php | 2 +- 9 files changed, 301 insertions(+), 93 deletions(-) diff --git a/docs b/docs index e83c3ea02..2afa14cd2 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit e83c3ea026fc2142b6558378d0c9636c8c29f248 +Subproject commit 2afa14cd21754fbbcbc8bd25cecfeb3592662eca diff --git a/examples/magnaluna b/examples/magnaluna index 499f1d8ec..3bd78bce6 160000 --- a/examples/magnaluna +++ b/examples/magnaluna @@ -1 +1 @@ -Subproject commit 499f1d8ec8f7f2f554d0b347bd06c26404a2e402 +Subproject commit 3bd78bce691319dc07cadc154056c7b68594eb55 diff --git a/schemas b/schemas index 8fd40f0a4..d0b2b612c 160000 --- a/schemas +++ b/schemas @@ -1 +1 @@ -Subproject commit 8fd40f0a4170769465e4943e4a5da37bd58f7e88 +Subproject commit d0b2b612cbece1fd3dc971634326f7470dc0099b diff --git a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php index af2fddbf0..0f92e17ea 100644 --- a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php @@ -231,7 +231,10 @@ trait ResponseHandler $body = $request->getBodyOrEmpty(); $trimmed = []; if (isset($body['peer'])) { - $trimmed['peer'] = \is_string($body['peer']) ? $body['peer'] : $this->API->getId($body['peer']); + try { + $trimmed['peer'] = \is_string($body['peer']) ? $body['peer'] : $this->API->getId($body['peer']); + } catch (\Throwable $e) { + } } if (isset($body['message'])) { $trimmed['message'] = (string) $body['message']; diff --git a/src/danog/MadelineProto/Stream/Ogg/Ogg.php b/src/danog/MadelineProto/Stream/Ogg/Ogg.php index 08c250581..a73f0aae7 100644 --- a/src/danog/MadelineProto/Stream/Ogg/Ogg.php +++ b/src/danog/MadelineProto/Stream/Ogg/Ogg.php @@ -3,6 +3,7 @@ namespace danog\MadelineProto\Stream\Ogg; use Amp\Emitter; +use danog\MadelineProto\Exception; use danog\MadelineProto\Logger; use danog\MadelineProto\Stream\BufferedStreamInterface; use danog\MadelineProto\Stream\BufferInterface; @@ -307,7 +308,6 @@ class Ogg } } $this->streamCount = $opus_head['channel_mapping']['stream_count']; - \var_dump($opus_head); $state = self::STATE_READ_COMMENT; } elseif ($state === self::STATE_READ_COMMENT) { $vendor_string_length = \unpack('V', \substr($content, 8, 4))[1]; diff --git a/src/danog/MadelineProto/Tools.php b/src/danog/MadelineProto/Tools.php index 6d448a661..400ad0165 100644 --- a/src/danog/MadelineProto/Tools.php +++ b/src/danog/MadelineProto/Tools.php @@ -305,7 +305,7 @@ abstract class Tools extends StrTools $resolved = false; do { try { - Logger::log("Starting event loop..."); + //Logger::log("Starting event loop..."); Loop::run(function () use (&$resolved, &$value, &$exception, $promise) { $promise->onResolve(function ($e, $v) use (&$resolved, &$value, &$exception) { Loop::stop(); diff --git a/src/danog/MadelineProto/VoIP.php b/src/danog/MadelineProto/VoIP.php index 981695200..3ff0da685 100644 --- a/src/danog/MadelineProto/VoIP.php +++ b/src/danog/MadelineProto/VoIP.php @@ -13,6 +13,7 @@ If not, see . namespace danog\MadelineProto; use Amp\Delayed; +use Amp\Loop; use danog\MadelineProto\MTProto\PermAuthKey; use danog\MadelineProto\Stream\Common\FileBufferedStream; use danog\MadelineProto\Stream\ConnectionContext; @@ -113,7 +114,8 @@ class VoIP private $TLID_REFLECTOR_SELF_INFO; private $TLID_REFLECTOR_PEER_INFO; - private $MadelineProto; + private MTProto $MadelineProto; + public MTProto $madeline; public $received_timestamp_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; public $remote_ack_timestamp_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; public $session_out_seq_no = 0; @@ -123,7 +125,7 @@ class VoIP public $storage = []; public $internalStorage = []; private $signal = 0; - private $callState; + private int $callState; private $callID; private $creatorID; private $otherID; @@ -139,69 +141,135 @@ class VoIP private PermAuthKey $authKey; private int $peerVersion = 0; + /** * @var Endpoint[] */ private array $sockets = []; + /** + * Timeout watcher. + */ + private string $timeoutWatcher; + private $connection_settings = []; private $dclist = []; - private $datacenter; + private $socket; - public function __construct(bool $creator, int $otherID, MTProto $MadelineProto, $callState) + /** + * Constructor. + * + * @param boolean $creator + * @param integer $otherID + * @param MTProto $MadelineProto + * @param integer $callState + */ + public function __construct(bool $creator, int $otherID, MTProto $MadelineProto, int $callState) { $this->creator = $creator; $this->otherID = $otherID; - //$this->callID = $callID; $this->madeline = $this->MadelineProto = $MadelineProto; $this->callState = $callState; - //$this->protocol = $protocol; $this->TLID_REFLECTOR_SELF_INFO = \strrev(\hex2bin(self::TLID_REFLECTOR_SELF_INFO_HEX)); $this->TLID_REFLECTOR_PEER_INFO = \strrev(\hex2bin(self::TLID_REFLECTOR_PEER_INFO_HEX)); $this->TLID_DECRYPTED_AUDIO_BLOCK = \strrev(\hex2bin(self::TLID_DECRYPTED_AUDIO_BLOCK_HEX)); $this->TLID_SIMPLE_AUDIO_BLOCK = \strrev(\hex2bin(self::TLID_SIMPLE_AUDIO_BLOCK_HEX)); } + /** + * Get max layer. + * + * @return integer + */ public static function getConnectionMaxLayer(): int { return 92; } - public function deInitVoIPController() - { - } - + /** + * Get debug string. + * + * @return string + */ public function getDebugString(): string { return ''; } - public function setCall($callID) + /** + * Set call constructor. + * + * @param array $callID + * @return void + */ + public function setCall(array $callID): void { - $this->callID = $callID; + $this->protocol = $callID['protocol']; + $this->callID = [ + '_' => 'inputPhoneCall', + 'id' => $callID['id'], + 'access_hash' => $callID['access_hash'] + ]; } - public function setVisualization($visualization) + /** + * Set emojis. + * + * @param array $visualization + * @return void + */ + public function setVisualization(array $visualization): void { $this->visualization = $visualization; } - public function getVisualization() + /** + * Get emojis. + * + * @return array + */ + public function getVisualization(): array { return $this->visualization; } + /** + * Discard call. + * + * @param array $reason + * @param array $rating + * @param boolean $debug + * @return self|false + */ public function discard($reason = ['_' => 'phoneCallDiscardReasonDisconnect'], $rating = [], $debug = false) { if ($this->callState === self::CALL_STATE_ENDED || empty($this->configuration)) { return false; } - $this->deinitVoIPController(); + Logger::log("Now closing $this"); + if (isset($this->timeoutWatcher)) { + Loop::cancel($this->timeoutWatcher); + } + + Logger::log("Closing all sockets in $this"); + foreach ($this->sockets as $socket) { + $socket->disconnect(); + } + Logger::log("Closed all sockets, discarding $this"); return Tools::callFork($this->MadelineProto->discardCall($this->callID, $reason, $rating, $debug)); } + public function __destruct() + { + $this->discard(['_' => 'phoneCallDiscardReasonDisconnect']); + } + /** + * Accept call. + * + * @return self|false + */ public function accept() { if ($this->callState !== self::CALL_STATE_INCOMING) { @@ -218,13 +286,26 @@ class VoIP return $this; } - public function close() - { - $this->deinitVoIPController(); - } - - public function startTheMagic() + /** + * Last incoming timestamp. + * + * @var float + */ + private $lastIncomingTimestamp = 0.0; + /** + * Start the actual call. + */ + public function startTheMagic(): self { + if ($this->voip_state !== self::STATE_CREATED) { + return $this; + } + $this->voip_state = self::STATE_WAIT_INIT; + $this->timeoutWatcher = Loop::repeat(10000, function () { + if (\microtime(true) - $this->lastIncomingTimestamp > 10) { + $this->discard(['_' => 'phoneCallDiscardReasonDisconnect']); + } + }); Tools::callFork((function () { $this->authKey = new PermAuthKey(); $this->authKey->setAuthKey($this->configuration['auth_key']); @@ -237,160 +318,257 @@ class VoIP try { yield from $socket->connect(); } catch (\Throwable $e) { - Logger::log($e); unset($this->sockets[$k]); } } - $this->init_all(); - Tools::callFork((function () use ($socket) { - while ($payload = yield from $this->recv_message($socket)) { - Tools::callFork($this->handlePacket($socket, $payload)); - } - })()); + foreach ($this->sockets as $socket) { + $this->send_message(['_' => self::PKT_INIT, 'protocol' => self::PROTOCOL_VERSION, 'min_protocol' => self::MIN_PROTOCOL_VERSION, 'audio_streams' => [self::CODEC_OPUS], 'video_streams' => []], $socket); + Tools::callFork((function () use ($socket) { + while ($payload = yield from $this->recv_message($socket)) { + $this->lastIncomingTimestamp = \microtime(true); + Tools::callFork($this->handlePacket($socket, $payload)); + } + Logger::log("Exiting VoIP read loop in $this!"); + })()); + } })()); return $this; } - public function handlePacket($datacenter, $packet) + /** + * Handle incoming packet. + */ + private function handlePacket(Endpoint $socket, array $packet): \Generator { - //\var_dump($packet); switch ($packet['_']) { case self::PKT_INIT: //$this->voip_state = self::STATE_WAIT_INIT_ACK; - $this->send_message(['_' => self::PKT_INIT_ACK, 'protocol' => self::PROTOCOL_VERSION, 'min_protocol' => self::MIN_PROTOCOL_VERSION, 'all_streams' => [['id' => 0, 'type' => self::STREAM_TYPE_AUDIO, 'codec' => self::CODEC_OPUS, 'frame_duration' => 60, 'enabled' => 1]]], $datacenter); + $this->send_message(['_' => self::PKT_INIT_ACK, 'protocol' => self::PROTOCOL_VERSION, 'min_protocol' => self::MIN_PROTOCOL_VERSION, 'all_streams' => [['id' => 0, 'type' => self::STREAM_TYPE_AUDIO, 'codec' => self::CODEC_OPUS, 'frame_duration' => 60, 'enabled' => 1]]], $socket); - if ($this->voip_state !== self::STATE_ESTABLISHED) { - $this->voip_state = self::STATE_ESTABLISHED; - - $ctx = new ConnectionContext; - $ctx->addStream(FileBufferedStream::class, yield open('kda.opus', 'r')); - $stream = yield from $ctx->getStream(); - $ogg = yield from Ogg::init($stream, 60000); - $it = $ogg->getEmitter()->iterate(); - Tools::callFork($ogg->read()); - Tools::callFork((function () use ($it, $datacenter) { - $timestamp = 0; - $frames = []; - while (yield $it->advance()) { - $frames []= $it->getCurrent(); - } - foreach ($frames as $frame) { - $t = (\microtime(true) / 1000) + 60; - yield $this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $frame, 'timestamp' => $timestamp], $datacenter); - - yield new Delayed((int) ($t - (\microtime(true) / 1000))); - - $timestamp += 60; - } - })()); - } + yield from $this->startWriteLoop($socket); break; case self::PKT_INIT_ACK: + yield from $this->startWriteLoop($socket); break; } } - public $timestamp = 0; - public function oggCallback($data) + /** + * Start write loop. + * + * @param Endpoint $socket + * @return \Generator + */ + private function startWriteLoop(Endpoint $socket): \Generator { - \var_dump(\strlen($data)); - $this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $data, 'timestamp' => $this->timestamp]); - $this->timestamp += 60; + if ($this->voip_state !== self::STATE_ESTABLISHED) { + $this->voip_state = self::STATE_ESTABLISHED; + + $ctx = new ConnectionContext; + $ctx->addStream(FileBufferedStream::class, yield open('kda.opus', 'r')); + $stream = yield from $ctx->getStream(); + $ogg = yield from Ogg::init($stream, 60000); + $it = $ogg->getEmitter()->iterate(); + Tools::callFork($ogg->read()); + Tools::callFork((function () use ($it, $socket) { + $timestamp = 0; + $frames = []; + while (yield $it->advance()) { + $frames []= $it->getCurrent(); + } + foreach ($frames as $k => $frame) { + $t = (\microtime(true) / 1000) + 60; + if (!yield $this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $frame, 'timestamp' => $timestamp], $socket)) { + Logger::log("Exiting VoIP write loop in $this!"); + return; + } + + + Logger::log("Writing $k in $this!"); + yield new Delayed((int) ($t - (\microtime(true) / 1000))); + + $timestamp += 60; + } + })()); + } } - public function play($file) + /** + * Play file. + * + * @param string $file + * @return self + */ + public function play(string $file): self { $this->inputFiles[] = $file; return $this; } - public function then($file) + /** + * Play file. + * + * @param string $file + * @return self + */ + public function then(string $file): self { $this->inputFiles[] = $file; return $this; } - public function playOnHold($files) + /** + * Files to play on hold. + * + * @param array $files + * @return self + */ + public function playOnHold(array $files): self { $this->holdFiles = $files; return $this; } - public function setOutputFile($file) + /** + * Set output file. + * + * @param string $file + * @return self + */ + public function setOutputFile(string $file): self { $this->outputFile = $file; return $this; } - public function unsetOutputFile() + /** + * Unset output file. + * + * @return self + */ + public function unsetOutputFile(): self { $this->outputFile = null; + + return $this; } - public function setMadeline($MadelineProto) + + /** + * Set MadelineProto instance. + * + * @param MTProto $MadelineProto + * @return void + */ + public function setMadeline(MTProto $MadelineProto): void { - $this->MadelineProto = $MadelineProto; + $this->MadelineProto = $this->madeline = $MadelineProto; } - public function getProtocol() + /** + * Get call protocol. + * + * @return array + */ + public function getProtocol(): array { return $this->protocol; } - public function getOtherID() + /** + * Get ID of other user. + * + * @return int + */ + public function getOtherID(): int { return $this->otherID; } + /** + * Get call ID. + * + * @return string|int + */ public function getCallID() { return $this->callID; } + /** + * Get creation date. + * + * @return int|bool + */ public function whenCreated() { return isset($this->internalStorage['created']) ? $this->internalStorage['created'] : false; } - public function parseConfig() + /** + * Parse config. + * + * @return void + */ + public function parseConfig(): void { } - private function init_all() - { - foreach ($this->sockets as $socket) { - $this->send_message(['_' => self::PKT_INIT, 'protocol' => self::PROTOCOL_VERSION, 'min_protocol' => self::MIN_PROTOCOL_VERSION, 'audio_streams' => [self::CODEC_OPUS], 'video_streams' => []], $socket); - $this->voip_state = self::STATE_WAIT_INIT; - } - } - - public function getCallState() + /** + * Get call state. + * + * @return int + */ + public function getCallState(): int { return $this->callState; } - public function getVersion() + /** + * Get library version. + * + * @return string + */ + public function getVersion(): string { return 'libponyvoip-1.0'; } - public function getPreferredRelayID() + /** + * Get preferred relay ID. + * + * @return integer + */ + public function getPreferredRelayID(): int { return 0; } - public function getLastError() + /** + * Get last error. + * + * @return string + */ + public function getLastError(): string { return ''; } - public function getDebugLog() + /** + * Get debug log. + * + * @return string + */ + public function getDebugLog(): string { return ''; } - public function getSignalBarsCount() + /** + * Get signal bar count. + */ + public function getSignalBarsCount(): int { return $this->signal; } @@ -424,4 +602,15 @@ class VoIP { return $this->peerVersion; } + + /** + * Get call representation. + * + * @return string + */ + public function __toString() + { + $id = $this->callID['id']; + return "call {$id} with {$this->otherID}"; + } } diff --git a/src/danog/MadelineProto/VoIP/Endpoint.php b/src/danog/MadelineProto/VoIP/Endpoint.php index c45ecaa56..20c993bf2 100644 --- a/src/danog/MadelineProto/VoIP/Endpoint.php +++ b/src/danog/MadelineProto/VoIP/Endpoint.php @@ -4,6 +4,7 @@ namespace danog\MadelineProto\VoIP; use Amp\Promise; use Amp\Socket\EncryptableSocket; +use Amp\Success; use danog\MadelineProto\MTProto\PermAuthKey; use danog\MadelineProto\MTProtoTools\Crypt; use danog\MadelineProto\VoIP; @@ -37,7 +38,7 @@ class Endpoint /** * The socket. */ - private EncryptableSocket $socket; + private ?EncryptableSocket $socket = null; /** * Whether we're the creator. @@ -78,6 +79,18 @@ class Endpoint $this->socket = yield connect("udp://{$this->ip}:{$this->port}"); } + /** + * Disconnect from endpoint. + * + * @return void + */ + public function disconnect(): void + { + if ($this->socket !== null) { + $this->socket->close(); + $this->socket = null; + } + } /** * Read packet. * @@ -139,6 +152,9 @@ class Endpoint */ public function write(string $payload): Promise { + if ($this->socket === null) { + return new Success(0); + } $plaintext = \pack('v', \strlen($payload)).$payload; $padding = 16 - (\strlen($plaintext) % 16); if ($padding < 16) { diff --git a/src/danog/MadelineProto/VoIPServerConfig.php b/src/danog/MadelineProto/VoIPServerConfig.php index 6b886d530..1cf36b510 100644 --- a/src/danog/MadelineProto/VoIPServerConfig.php +++ b/src/danog/MadelineProto/VoIPServerConfig.php @@ -82,7 +82,7 @@ if (\class_exists(VoIPServerConfig::class)) { /** * Get final settings. * - * @return void + * @return array */ public static function getFinal(): array {