1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-11-27 01:14:39 +01:00
This commit is contained in:
Daniil Gentili 2020-11-29 19:25:54 +01:00
parent 0424539a1f
commit 1f608c0a4c
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
9 changed files with 301 additions and 93 deletions

2
docs

@ -1 +1 @@
Subproject commit e83c3ea026fc2142b6558378d0c9636c8c29f248 Subproject commit 2afa14cd21754fbbcbc8bd25cecfeb3592662eca

@ -1 +1 @@
Subproject commit 499f1d8ec8f7f2f554d0b347bd06c26404a2e402 Subproject commit 3bd78bce691319dc07cadc154056c7b68594eb55

@ -1 +1 @@
Subproject commit 8fd40f0a4170769465e4943e4a5da37bd58f7e88 Subproject commit d0b2b612cbece1fd3dc971634326f7470dc0099b

View File

@ -231,7 +231,10 @@ trait ResponseHandler
$body = $request->getBodyOrEmpty(); $body = $request->getBodyOrEmpty();
$trimmed = []; $trimmed = [];
if (isset($body['peer'])) { 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'])) { if (isset($body['message'])) {
$trimmed['message'] = (string) $body['message']; $trimmed['message'] = (string) $body['message'];

View File

@ -3,6 +3,7 @@
namespace danog\MadelineProto\Stream\Ogg; namespace danog\MadelineProto\Stream\Ogg;
use Amp\Emitter; use Amp\Emitter;
use danog\MadelineProto\Exception;
use danog\MadelineProto\Logger; use danog\MadelineProto\Logger;
use danog\MadelineProto\Stream\BufferedStreamInterface; use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\BufferInterface; use danog\MadelineProto\Stream\BufferInterface;
@ -307,7 +308,6 @@ class Ogg
} }
} }
$this->streamCount = $opus_head['channel_mapping']['stream_count']; $this->streamCount = $opus_head['channel_mapping']['stream_count'];
\var_dump($opus_head);
$state = self::STATE_READ_COMMENT; $state = self::STATE_READ_COMMENT;
} elseif ($state === self::STATE_READ_COMMENT) { } elseif ($state === self::STATE_READ_COMMENT) {
$vendor_string_length = \unpack('V', \substr($content, 8, 4))[1]; $vendor_string_length = \unpack('V', \substr($content, 8, 4))[1];

View File

@ -305,7 +305,7 @@ abstract class Tools extends StrTools
$resolved = false; $resolved = false;
do { do {
try { try {
Logger::log("Starting event loop..."); //Logger::log("Starting event loop...");
Loop::run(function () use (&$resolved, &$value, &$exception, $promise) { Loop::run(function () use (&$resolved, &$value, &$exception, $promise) {
$promise->onResolve(function ($e, $v) use (&$resolved, &$value, &$exception) { $promise->onResolve(function ($e, $v) use (&$resolved, &$value, &$exception) {
Loop::stop(); Loop::stop();

View File

@ -13,6 +13,7 @@ If not, see <http://www.gnu.org/licenses/>.
namespace danog\MadelineProto; namespace danog\MadelineProto;
use Amp\Delayed; use Amp\Delayed;
use Amp\Loop;
use danog\MadelineProto\MTProto\PermAuthKey; use danog\MadelineProto\MTProto\PermAuthKey;
use danog\MadelineProto\Stream\Common\FileBufferedStream; use danog\MadelineProto\Stream\Common\FileBufferedStream;
use danog\MadelineProto\Stream\ConnectionContext; use danog\MadelineProto\Stream\ConnectionContext;
@ -113,7 +114,8 @@ class VoIP
private $TLID_REFLECTOR_SELF_INFO; private $TLID_REFLECTOR_SELF_INFO;
private $TLID_REFLECTOR_PEER_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 $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 $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; public $session_out_seq_no = 0;
@ -123,7 +125,7 @@ class VoIP
public $storage = []; public $storage = [];
public $internalStorage = []; public $internalStorage = [];
private $signal = 0; private $signal = 0;
private $callState; private int $callState;
private $callID; private $callID;
private $creatorID; private $creatorID;
private $otherID; private $otherID;
@ -139,69 +141,135 @@ class VoIP
private PermAuthKey $authKey; private PermAuthKey $authKey;
private int $peerVersion = 0; private int $peerVersion = 0;
/** /**
* @var Endpoint[] * @var Endpoint[]
*/ */
private array $sockets = []; private array $sockets = [];
/**
* Timeout watcher.
*/
private string $timeoutWatcher;
private $connection_settings = []; private $connection_settings = [];
private $dclist = []; 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->creator = $creator;
$this->otherID = $otherID; $this->otherID = $otherID;
//$this->callID = $callID;
$this->madeline = $this->MadelineProto = $MadelineProto; $this->madeline = $this->MadelineProto = $MadelineProto;
$this->callState = $callState; $this->callState = $callState;
//$this->protocol = $protocol;
$this->TLID_REFLECTOR_SELF_INFO = \strrev(\hex2bin(self::TLID_REFLECTOR_SELF_INFO_HEX)); $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_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_DECRYPTED_AUDIO_BLOCK = \strrev(\hex2bin(self::TLID_DECRYPTED_AUDIO_BLOCK_HEX));
$this->TLID_SIMPLE_AUDIO_BLOCK = \strrev(\hex2bin(self::TLID_SIMPLE_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 public static function getConnectionMaxLayer(): int
{ {
return 92; return 92;
} }
public function deInitVoIPController() /**
{ * Get debug string.
} *
* @return string
*/
public function getDebugString(): string public function getDebugString(): string
{ {
return ''; 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; $this->visualization = $visualization;
} }
public function getVisualization() /**
* Get emojis.
*
* @return array
*/
public function getVisualization(): array
{ {
return $this->visualization; 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) public function discard($reason = ['_' => 'phoneCallDiscardReasonDisconnect'], $rating = [], $debug = false)
{ {
if ($this->callState === self::CALL_STATE_ENDED || empty($this->configuration)) { if ($this->callState === self::CALL_STATE_ENDED || empty($this->configuration)) {
return false; 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)); 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() public function accept()
{ {
if ($this->callState !== self::CALL_STATE_INCOMING) { if ($this->callState !== self::CALL_STATE_INCOMING) {
@ -218,13 +286,26 @@ class VoIP
return $this; return $this;
} }
public function close() /**
{ * Last incoming timestamp.
$this->deinitVoIPController(); *
} * @var float
*/
public function startTheMagic() 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 () { Tools::callFork((function () {
$this->authKey = new PermAuthKey(); $this->authKey = new PermAuthKey();
$this->authKey->setAuthKey($this->configuration['auth_key']); $this->authKey->setAuthKey($this->configuration['auth_key']);
@ -237,160 +318,257 @@ class VoIP
try { try {
yield from $socket->connect(); yield from $socket->connect();
} catch (\Throwable $e) { } catch (\Throwable $e) {
Logger::log($e);
unset($this->sockets[$k]); unset($this->sockets[$k]);
} }
} }
$this->init_all(); foreach ($this->sockets as $socket) {
Tools::callFork((function () use ($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);
while ($payload = yield from $this->recv_message($socket)) { Tools::callFork((function () use ($socket) {
Tools::callFork($this->handlePacket($socket, $payload)); 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; return $this;
} }
public function handlePacket($datacenter, $packet) /**
* Handle incoming packet.
*/
private function handlePacket(Endpoint $socket, array $packet): \Generator
{ {
//\var_dump($packet);
switch ($packet['_']) { switch ($packet['_']) {
case self::PKT_INIT: case self::PKT_INIT:
//$this->voip_state = self::STATE_WAIT_INIT_ACK; //$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) { yield from $this->startWriteLoop($socket);
$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;
}
})());
}
break; break;
case self::PKT_INIT_ACK: case self::PKT_INIT_ACK:
yield from $this->startWriteLoop($socket);
break; 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)); if ($this->voip_state !== self::STATE_ESTABLISHED) {
$this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $data, 'timestamp' => $this->timestamp]); $this->voip_state = self::STATE_ESTABLISHED;
$this->timestamp += 60;
$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; $this->inputFiles[] = $file;
return $this; return $this;
} }
public function then($file) /**
* Play file.
*
* @param string $file
* @return self
*/
public function then(string $file): self
{ {
$this->inputFiles[] = $file; $this->inputFiles[] = $file;
return $this; 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; $this->holdFiles = $files;
return $this; return $this;
} }
public function setOutputFile($file) /**
* Set output file.
*
* @param string $file
* @return self
*/
public function setOutputFile(string $file): self
{ {
$this->outputFile = $file; $this->outputFile = $file;
return $this; return $this;
} }
public function unsetOutputFile() /**
* Unset output file.
*
* @return self
*/
public function unsetOutputFile(): self
{ {
$this->outputFile = null; $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; return $this->protocol;
} }
public function getOtherID() /**
* Get ID of other user.
*
* @return int
*/
public function getOtherID(): int
{ {
return $this->otherID; return $this->otherID;
} }
/**
* Get call ID.
*
* @return string|int
*/
public function getCallID() public function getCallID()
{ {
return $this->callID; return $this->callID;
} }
/**
* Get creation date.
*
* @return int|bool
*/
public function whenCreated() public function whenCreated()
{ {
return isset($this->internalStorage['created']) ? $this->internalStorage['created'] : false; return isset($this->internalStorage['created']) ? $this->internalStorage['created'] : false;
} }
public function parseConfig() /**
* Parse config.
*
* @return void
*/
public function parseConfig(): void
{ {
} }
private function init_all() /**
{ * Get call state.
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); * @return int
$this->voip_state = self::STATE_WAIT_INIT; */
} public function getCallState(): int
}
public function getCallState()
{ {
return $this->callState; return $this->callState;
} }
public function getVersion() /**
* Get library version.
*
* @return string
*/
public function getVersion(): string
{ {
return 'libponyvoip-1.0'; return 'libponyvoip-1.0';
} }
public function getPreferredRelayID() /**
* Get preferred relay ID.
*
* @return integer
*/
public function getPreferredRelayID(): int
{ {
return 0; return 0;
} }
public function getLastError() /**
* Get last error.
*
* @return string
*/
public function getLastError(): string
{ {
return ''; return '';
} }
public function getDebugLog() /**
* Get debug log.
*
* @return string
*/
public function getDebugLog(): string
{ {
return ''; return '';
} }
public function getSignalBarsCount() /**
* Get signal bar count.
*/
public function getSignalBarsCount(): int
{ {
return $this->signal; return $this->signal;
} }
@ -424,4 +602,15 @@ class VoIP
{ {
return $this->peerVersion; return $this->peerVersion;
} }
/**
* Get call representation.
*
* @return string
*/
public function __toString()
{
$id = $this->callID['id'];
return "call {$id} with {$this->otherID}";
}
} }

View File

@ -4,6 +4,7 @@ namespace danog\MadelineProto\VoIP;
use Amp\Promise; use Amp\Promise;
use Amp\Socket\EncryptableSocket; use Amp\Socket\EncryptableSocket;
use Amp\Success;
use danog\MadelineProto\MTProto\PermAuthKey; use danog\MadelineProto\MTProto\PermAuthKey;
use danog\MadelineProto\MTProtoTools\Crypt; use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\VoIP; use danog\MadelineProto\VoIP;
@ -37,7 +38,7 @@ class Endpoint
/** /**
* The socket. * The socket.
*/ */
private EncryptableSocket $socket; private ?EncryptableSocket $socket = null;
/** /**
* Whether we're the creator. * Whether we're the creator.
@ -78,6 +79,18 @@ class Endpoint
$this->socket = yield connect("udp://{$this->ip}:{$this->port}"); $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. * Read packet.
* *
@ -139,6 +152,9 @@ class Endpoint
*/ */
public function write(string $payload): Promise public function write(string $payload): Promise
{ {
if ($this->socket === null) {
return new Success(0);
}
$plaintext = \pack('v', \strlen($payload)).$payload; $plaintext = \pack('v', \strlen($payload)).$payload;
$padding = 16 - (\strlen($plaintext) % 16); $padding = 16 - (\strlen($plaintext) % 16);
if ($padding < 16) { if ($padding < 16) {

View File

@ -82,7 +82,7 @@ if (\class_exists(VoIPServerConfig::class)) {
/** /**
* Get final settings. * Get final settings.
* *
* @return void * @return array
*/ */
public static function getFinal(): array public static function getFinal(): array
{ {