Add OGG OPUS parser
This commit is contained in:
parent
47b808ca9b
commit
d0189b1ef2
206
src/danog/MadelineProto/Stream/Common/FileBufferedStream.php
Normal file
206
src/danog/MadelineProto/Stream/Common/FileBufferedStream.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Buffered raw stream.
|
||||
*
|
||||
* 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\File\File;
|
||||
use Amp\Promise;
|
||||
use Amp\Socket\Socket;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\Stream\Async\RawStream;
|
||||
use danog\MadelineProto\Stream\BufferedStreamInterface;
|
||||
use danog\MadelineProto\Stream\BufferInterface;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
use danog\MadelineProto\Stream\ProxyStreamInterface;
|
||||
use danog\MadelineProto\Stream\RawStreamInterface;
|
||||
|
||||
/**
|
||||
* Buffered raw stream.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class FileBufferedStream implements BufferedStreamInterface, BufferInterface, ProxyStreamInterface, RawStreamInterface
|
||||
{
|
||||
private File $stream;
|
||||
private int $append_after;
|
||||
private string $append;
|
||||
/**
|
||||
* Connect
|
||||
*
|
||||
* @param ConnectionContext $ctx
|
||||
* @param string $header
|
||||
* @return \Generator
|
||||
*/
|
||||
public function connect(ConnectionContext $ctx, string $header = ''): \Generator
|
||||
{
|
||||
if ($header !== '') {
|
||||
yield $this->stream->write($header);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Async chunked read.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function read(): Promise
|
||||
{
|
||||
if (!$this->stream) {
|
||||
throw new ClosedException("MadelineProto stream was disconnected");
|
||||
}
|
||||
return $this->stream->read();
|
||||
}
|
||||
/**
|
||||
* Async write.
|
||||
*
|
||||
* @param string $data Data to write
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function write(string $data): Promise
|
||||
{
|
||||
if (!$this->stream) {
|
||||
throw new ClosedException("MadelineProto stream was disconnected");
|
||||
}
|
||||
return $this->stream->write($data);
|
||||
}
|
||||
/**
|
||||
* Async write.
|
||||
*
|
||||
* @param string $data Data to write
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function end(string $finalData = ''): Promise
|
||||
{
|
||||
if (!$this->stream) {
|
||||
throw new ClosedException("MadelineProto stream was disconnected");
|
||||
}
|
||||
return $this->stream->end($finalData);
|
||||
}
|
||||
/**
|
||||
* Async close.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function disconnect()
|
||||
{
|
||||
if ($this->stream) {
|
||||
$this->stream = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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) {
|
||||
throw new ClosedException("MadelineProto stream was disconnected");
|
||||
}
|
||||
return new \Amp\Success($this);
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
if (\strlen($append)) {
|
||||
$this->append = $append;
|
||||
$this->append_after = $length - \strlen($append);
|
||||
}
|
||||
return new \Amp\Success($this);
|
||||
}
|
||||
/**
|
||||
* Read data asynchronously.
|
||||
*
|
||||
* @param int $length Amount of data to read
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function bufferRead(int $length): Promise
|
||||
{
|
||||
return $this->stream->read($length);
|
||||
}
|
||||
/**
|
||||
* Async write.
|
||||
*
|
||||
* @param string $data Data to write
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function bufferWrite(string $data): Promise
|
||||
{
|
||||
if ($this->append_after) {
|
||||
$this->append_after -= \strlen($data);
|
||||
if ($this->append_after === 0) {
|
||||
$data .= $this->append;
|
||||
$this->append = '';
|
||||
} 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 $this->write($data);
|
||||
}
|
||||
/**
|
||||
* Set file handle
|
||||
*
|
||||
* @param File $extra
|
||||
* @return void
|
||||
*/
|
||||
public function setExtra($extra)
|
||||
{
|
||||
$this->stream = $extra;
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @return RawStreamInterface
|
||||
*/
|
||||
public function getStream(): RawStreamInterface
|
||||
{
|
||||
throw new \RuntimeException("Can't get underlying RawStreamInterface, is a File handle!");
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getSocket(): Socket
|
||||
{
|
||||
throw new \RuntimeException("Can't get underlying socket, is a File handle!");
|
||||
}
|
||||
/**
|
||||
* Get class name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getName(): string
|
||||
{
|
||||
return __CLASS__;
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ use danog\MadelineProto\Stream\BufferInterface;
|
||||
use danog\MadelineProto\Stream\RawStreamInterface;
|
||||
|
||||
/**
|
||||
* Buffered raw stream.
|
||||
* Buffered raw stream, that simply returns less data on EOF instead of throwing.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
@ -48,7 +48,6 @@ class SimpleBufferedRawStream extends BufferedRawStream implements BufferedStrea
|
||||
while ($buffer_length < $length) {
|
||||
$chunk = yield $this->read();
|
||||
if ($chunk === null) {
|
||||
\fseek($this->memory_stream, $offset);
|
||||
break;
|
||||
}
|
||||
\fwrite($this->memory_stream, $chunk);
|
||||
|
335
src/danog/MadelineProto/Stream/Ogg/Ogg.php
Normal file
335
src/danog/MadelineProto/Stream/Ogg/Ogg.php
Normal file
@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Stream\Ogg;
|
||||
|
||||
use Amp\Emitter;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Stream\BufferedStreamInterface;
|
||||
use danog\MadelineProto\Stream\BufferInterface;
|
||||
|
||||
/**
|
||||
* Async OGG stream reader.
|
||||
*
|
||||
* @author Charles-Édouard Coste <contact@ccoste.fr>
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
class Ogg
|
||||
{
|
||||
private const CAPTURE_PATTERN = "\x4f\x67\x67\x53"; // ASCII encoded "OggS" string
|
||||
private const BOS = 2;
|
||||
private const EOS = 4;
|
||||
|
||||
const STATE_READ_HEADER = 0;
|
||||
const STATE_READ_COMMENT = 1;
|
||||
const STATE_STREAMING = 3;
|
||||
const STATE_END = 4;
|
||||
|
||||
/**
|
||||
* Required frame duration in microseconds.
|
||||
*/
|
||||
private int $frameDuration = 60000;
|
||||
/**
|
||||
* Current total frame duration in microseconds.
|
||||
*/
|
||||
private int $currentDuration = 0;
|
||||
|
||||
/**
|
||||
* Current OPUS payload.
|
||||
*/
|
||||
private string $opusPayload = '';
|
||||
|
||||
/**
|
||||
* OGG Stream count.
|
||||
*/
|
||||
private int $streamCount;
|
||||
|
||||
/**
|
||||
* Buffered stream interface.
|
||||
*/
|
||||
private BufferInterface $stream;
|
||||
|
||||
/**
|
||||
* Pack format.
|
||||
*/
|
||||
private string $packFormat;
|
||||
|
||||
/**
|
||||
* OPUS packet emitter.
|
||||
*/
|
||||
private Emitter $emitter;
|
||||
|
||||
private function __construct() {}
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param BufferedStreamInterface $stream The stream
|
||||
* @param int $frameDuration Required frame duration, microseconds
|
||||
*
|
||||
* @return \Generator
|
||||
* @psalm-return \Generator<mixed, mixed, mixed, self>
|
||||
*/
|
||||
public static function init(BufferedStreamInterface $stream, int $frameDuration): \Generator
|
||||
{
|
||||
$self = new self;
|
||||
$self->frameDuration = $frameDuration;
|
||||
$self->stream = yield $stream->getReadBuffer($l);
|
||||
$self->emitter = new Emitter;
|
||||
$pack_format = [
|
||||
'stream_structure_version' => 'C',
|
||||
'header_type_flag' => 'C',
|
||||
'granule_position' => 'P',
|
||||
'bitstream_serial_number' => 'V',
|
||||
'page_sequence_number' => 'V',
|
||||
'CRC_checksum' => 'V',
|
||||
'number_page_segments' => 'C'
|
||||
];
|
||||
|
||||
$self->packFormat = \implode(
|
||||
'/',
|
||||
\array_map(
|
||||
fn (string $v, string $k): string => $v.$k,
|
||||
$pack_format,
|
||||
\array_keys($pack_format)
|
||||
)
|
||||
);
|
||||
|
||||
return $self;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read OPUS length.
|
||||
*
|
||||
* @param string $content
|
||||
* @param integer $offset
|
||||
* @return integer
|
||||
*/
|
||||
private function readLen(string $content, int &$offset): int
|
||||
{
|
||||
$len = \ord($content[$offset++]);
|
||||
if ($len > 251) {
|
||||
$len += \ord($content[$offset++]) << 2;
|
||||
}
|
||||
return $len;
|
||||
}
|
||||
/**
|
||||
* OPUS state machine.
|
||||
*
|
||||
* @param string $content
|
||||
* @return \Generator
|
||||
*/
|
||||
private function opusStateMachine(string $content): \Generator
|
||||
{
|
||||
$curStream = 0;
|
||||
$offset = 0;
|
||||
$len = \strlen($content);
|
||||
while ($offset < $len) {
|
||||
$selfDelimited = $curStream++ < $this->streamCount - 1;
|
||||
$sizes = [];
|
||||
|
||||
$preOffset = $offset;
|
||||
|
||||
$toc = \ord($content[$offset++]);
|
||||
$stereo = $toc & 4;
|
||||
$conf = $toc >> 3;
|
||||
$c = $toc & 3;
|
||||
|
||||
if ($conf < 12) {
|
||||
$frameDuration = $conf % 4;
|
||||
if ($frameDuration === 0) {
|
||||
$frameDuration = 10000;
|
||||
} else {
|
||||
$frameDuration *= 20000;
|
||||
}
|
||||
} elseif ($conf < 16) {
|
||||
$frameDuration = 2**($conf % 2) * 10000;
|
||||
} else {
|
||||
$frameDuration = 2**($conf % 4) * 2500;
|
||||
}
|
||||
|
||||
$paddingLen = 0;
|
||||
if ($c === 0) {
|
||||
// Exactly 1 frame
|
||||
$sizes []= $selfDelimited
|
||||
? $this->readLen($content, $offset)
|
||||
: $len - $offset;
|
||||
} elseif ($c === 1) {
|
||||
// Exactly 2 frames, equal size
|
||||
$size = $selfDelimited
|
||||
? $this->readLen($content, $offset)
|
||||
: ($len - $offset)/2;
|
||||
$sizes []= $size;
|
||||
$sizes []= $size;
|
||||
} elseif ($c === 2) {
|
||||
// Exactly 2 frames, different size
|
||||
$size = $this->readLen($content, $offset);
|
||||
$sizes []= $size;
|
||||
$sizes []= $selfDelimited
|
||||
? $this->readLen($content, $offset)
|
||||
: $len - ($offset + $size);
|
||||
} else {
|
||||
// Arbitrary number of frames
|
||||
$ch = \ord($content[$offset++]);
|
||||
$len--;
|
||||
$count = $ch & 0x3F;
|
||||
$vbr = $ch & 0x80;
|
||||
$padding = $ch & 0x40;
|
||||
if ($padding) {
|
||||
$paddingLen = $padding = \ord($content[$offset++]);
|
||||
while ($padding === 255) {
|
||||
$padding = \ord($content[$offset++]);
|
||||
$paddingLen += $padding - 1;
|
||||
}
|
||||
}
|
||||
if ($vbr) {
|
||||
if (!$selfDelimited) {
|
||||
$count -= 1;
|
||||
}
|
||||
for ($x = 0; $x < $count; $x++) {
|
||||
$sizes[]= $this->readLen($content, $offset);
|
||||
}
|
||||
} else { // CBR
|
||||
$size = $selfDelimited
|
||||
? $this->readLen($content, $offset)
|
||||
: ($len - ($offset + $padding)) / $count;
|
||||
\array_push($sizes, ...\array_fill(0, $count, $size));
|
||||
}
|
||||
}
|
||||
|
||||
$totalDuration = \count($sizes) * $frameDuration;
|
||||
if (!$selfDelimited && $totalDuration + $this->currentDuration <= $this->frameDuration) {
|
||||
$this->currentDuration += $totalDuration;
|
||||
$sum = array_sum($sizes);
|
||||
$this->opusPayload .= \substr($content, $preOffset, ($offset - $preOffset) + $sum + $paddingLen);
|
||||
if ($this->currentDuration === $this->frameDuration) {
|
||||
yield $this->emitter->emit($this->opusPayload);
|
||||
$this->opusPayload = '';
|
||||
$this->currentDuration = 0;
|
||||
}
|
||||
$offset += $sum;
|
||||
$offset += $paddingLen;
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($sizes as $size) {
|
||||
$this->opusPayload .= chr($toc & ~3);
|
||||
$this->opusPayload .= substr($content, $offset, $size);
|
||||
$offset += $size;
|
||||
$this->currentDuration += $frameDuration;
|
||||
if ($this->currentDuration >= $this->frameDuration) {
|
||||
if ($this->currentDuration > $this->frameDuration) {
|
||||
Logger::log("Emitting packet with duration {$this->currentDuration} but need {$this->frameDuration}, please reconvert the OGG file with a proper frame size.", Logger::WARNING);
|
||||
}
|
||||
yield $this->emitter->emit($this->opusPayload);
|
||||
$this->opusPayload = '';
|
||||
$this->currentDuration = 0;
|
||||
}
|
||||
}
|
||||
$offset += $paddingLen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read frames.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function read(): \Generator
|
||||
{
|
||||
$state = self::STATE_READ_HEADER;
|
||||
$content = '';
|
||||
|
||||
while (true) {
|
||||
$init = yield $this->stream->bufferRead(4+23);
|
||||
if (empty($init)) {
|
||||
return false; // EOF
|
||||
}
|
||||
if (\substr($init, 0, 4) !== self::CAPTURE_PATTERN) {
|
||||
throw new Exception("Bad capture pattern");
|
||||
}
|
||||
|
||||
/*$headers = \unpack(
|
||||
$this->packFormat,
|
||||
\substr($init, 4)
|
||||
);
|
||||
|
||||
if ($headers['stream_structure_version'] != 0x00) {
|
||||
throw new Exception("Bad stream version");
|
||||
}
|
||||
$granule_diff = $headers['granule_position'] - $granule;
|
||||
$granule = $headers['granule_position'];
|
||||
|
||||
$continuation = (bool) ($headers['header_type_flag'] & 0x01);
|
||||
$firstPage = (bool) ($headers['header_type_flag'] & 0x02);
|
||||
$lastPage = (bool) ($headers['header_type_flag'] & 0x04);
|
||||
*/
|
||||
|
||||
$segments = \unpack(
|
||||
'C*',
|
||||
yield $this->stream->bufferRead(\ord($init[26]))
|
||||
);
|
||||
|
||||
//$serial = $headers['bitstream_serial_number'];
|
||||
/*if ($headers['header_type_flag'] & Ogg::BOS) {
|
||||
$this->emit('ogg:stream:start', [$serial]);
|
||||
} elseif ($headers['header_type_flag'] & Ogg::EOS) {
|
||||
$this->emit('ogg:stream:end', [$serial]);
|
||||
} else {
|
||||
$this->emit('ogg:stream:continue', [$serial]);
|
||||
}*/
|
||||
$sizeAccumulated = 0;
|
||||
foreach ($segments as $segment_size) {
|
||||
$sizeAccumulated += $segment_size;
|
||||
if ($segment_size < 255) {
|
||||
$content .= yield $this->stream->bufferRead($sizeAccumulated);
|
||||
if ($state === self::STATE_STREAMING) {
|
||||
yield from $this->opusStateMachine($content);
|
||||
} elseif ($state === self::STATE_READ_HEADER) {
|
||||
if (\substr($content, 0, 8) !== 'OpusHead') {
|
||||
throw new \RuntimeException("This is not an OPUS stream!");
|
||||
}
|
||||
$opus_head = \unpack('Cversion/Cchannel_count/vpre_skip/Vsample_rate/voutput_gain/Cchannel_mapping_family/', \substr($content, 8));
|
||||
if ($opus_head['channel_mapping_family']) {
|
||||
$opus_head['channel_mapping'] = \unpack('Cstream_count/Ccoupled_count/C*channel_mapping', \substr($content, 19));
|
||||
} else {
|
||||
$opus_head['channel_mapping'] = [
|
||||
'stream_count' => 1,
|
||||
'coupled_count' => $opus_head['channel_count'] - 1,
|
||||
'channel_mapping' => [0]
|
||||
];
|
||||
if ($opus_head['channel_count'] === 2) {
|
||||
$opus_head['channel_mapping']['channel_mapping'][] = 1;
|
||||
}
|
||||
}
|
||||
$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];
|
||||
$result = [];
|
||||
$result['vendor_string'] = \substr($content, 12, $vendor_string_length);
|
||||
$comment_count = \unpack('V', \substr($content, 12+$vendor_string_length, 4))[1];
|
||||
$offset = 16+$vendor_string_length;
|
||||
for ($x = 0; $x < $comment_count; $x++) {
|
||||
$length = \unpack('V', \substr($content, $offset, 4))[1];
|
||||
$result['comments'][$x] = \substr($content, $offset += 4, $length);
|
||||
$offset += $length;
|
||||
}
|
||||
$state = self::STATE_STREAMING;
|
||||
}
|
||||
$content = '';
|
||||
$sizeAccumulated = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get OPUS packet emitter.
|
||||
*
|
||||
* @return Emitter
|
||||
*/
|
||||
public function getEmitter(): Emitter
|
||||
{
|
||||
return $this->emitter;
|
||||
}
|
||||
}
|
386
src/danog/MadelineProto/VoIP.php
Normal file
386
src/danog/MadelineProto/VoIP.php
Normal file
@ -0,0 +1,386 @@
|
||||
<?php
|
||||
/*
|
||||
Copyright 2016-2018 Daniil Gentili
|
||||
(https://daniil.it)
|
||||
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/>.
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
if (\extension_loaded('php-libtgvoip')) {
|
||||
return;
|
||||
}
|
||||
|
||||
class VoIP extends Tools
|
||||
{
|
||||
use \danog\MadelineProto\VoIP\MessageHandler;
|
||||
use \danog\MadelineProto\VoIP\AckHandler;
|
||||
|
||||
const PHP_LIBTGVOIP_VERSION = '1.1.2';
|
||||
const STATE_CREATED = 0;
|
||||
const STATE_WAIT_INIT = 1;
|
||||
const STATE_WAIT_INIT_ACK = 2;
|
||||
const STATE_ESTABLISHED = 3;
|
||||
const STATE_FAILED = 4;
|
||||
const STATE_RECONNECTING = 5;
|
||||
|
||||
const TGVOIP_ERROR_UNKNOWN = 0;
|
||||
const TGVOIP_ERROR_INCOMPATIBLE = 1;
|
||||
const TGVOIP_ERROR_TIMEOUT = 2;
|
||||
const TGVOIP_ERROR_AUDIO_IO = 3;
|
||||
|
||||
const NET_TYPE_UNKNOWN = 0;
|
||||
const NET_TYPE_GPRS = 1;
|
||||
const NET_TYPE_EDGE = 2;
|
||||
const NET_TYPE_3G = 3;
|
||||
const NET_TYPE_HSPA = 4;
|
||||
const NET_TYPE_LTE = 5;
|
||||
const NET_TYPE_WIFI = 6;
|
||||
const NET_TYPE_ETHERNET = 7;
|
||||
const NET_TYPE_OTHER_HIGH_SPEED = 8;
|
||||
const NET_TYPE_OTHER_LOW_SPEED = 9;
|
||||
const NET_TYPE_DIALUP = 10;
|
||||
const NET_TYPE_OTHER_MOBILE = 11;
|
||||
|
||||
const DATA_SAVING_NEVER = 0;
|
||||
const DATA_SAVING_MOBILE = 1;
|
||||
const DATA_SAVING_ALWAYS = 2;
|
||||
|
||||
const PROXY_NONE = 0;
|
||||
const PROXY_SOCKS5 = 1;
|
||||
|
||||
const AUDIO_STATE_NONE = -1;
|
||||
const AUDIO_STATE_CREATED = 0;
|
||||
const AUDIO_STATE_CONFIGURED = 1;
|
||||
const AUDIO_STATE_RUNNING = 2;
|
||||
|
||||
const CALL_STATE_NONE = -1;
|
||||
const CALL_STATE_REQUESTED = 0;
|
||||
const CALL_STATE_INCOMING = 1;
|
||||
const CALL_STATE_ACCEPTED = 2;
|
||||
const CALL_STATE_CONFIRMED = 3;
|
||||
const CALL_STATE_READY = 4;
|
||||
const CALL_STATE_ENDED = 5;
|
||||
|
||||
const PKT_INIT = 1;
|
||||
const PKT_INIT_ACK = 2;
|
||||
const PKT_STREAM_STATE = 3;
|
||||
const PKT_STREAM_DATA = 4;
|
||||
const PKT_UPDATE_STREAMS = 5;
|
||||
const PKT_PING = 6;
|
||||
const PKT_PONG = 7;
|
||||
const PKT_STREAM_DATA_X2 = 8;
|
||||
const PKT_STREAM_DATA_X3 = 9;
|
||||
const PKT_LAN_ENDPOINT = 10;
|
||||
const PKT_NETWORK_CHANGED = 11;
|
||||
const PKT_SWITCH_PREF_RELAY = 12;
|
||||
const PKT_SWITCH_TO_P2P = 13;
|
||||
const PKT_NOP = 14;
|
||||
|
||||
const TLID_DECRYPTED_AUDIO_BLOCK_HEX = 'dbf948c1';
|
||||
const TLID_SIMPLE_AUDIO_BLOCK_HEX = 'cc0d0e76';
|
||||
|
||||
const TLID_REFLECTOR_SELF_INFO_HEX = 'c01572c7';
|
||||
const TLID_REFLECTOR_PEER_INFO_HEX = '27D9371C';
|
||||
|
||||
const PROTO_ID = 'GrVP';
|
||||
|
||||
const PROTOCOL_VERSION = 3;
|
||||
const MIN_PROTOCOL_VERSION = 3;
|
||||
|
||||
const STREAM_TYPE_AUDIO = 1;
|
||||
const STREAM_TYPE_VIDEO = 2;
|
||||
|
||||
const CODEC_OPUS = 1;
|
||||
|
||||
|
||||
private $TLID_DECRYPTED_AUDIO_BLOCK;
|
||||
private $TLID_SIMPLE_AUDIO_BLOCK;
|
||||
private $TLID_REFLECTOR_SELF_INFO;
|
||||
private $TLID_REFLECTOR_PEER_INFO;
|
||||
|
||||
private $MadelineProto;
|
||||
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;
|
||||
public $session_in_seq_no = 0;
|
||||
public $voip_state = 0;
|
||||
public $configuration = ['endpoints' => [], 'shared_config' => []];
|
||||
public $storage = [];
|
||||
public $internalStorage = [];
|
||||
private $signal = 0;
|
||||
private $callState;
|
||||
private $callID;
|
||||
private $creatorID;
|
||||
private $otherID;
|
||||
private $protocol;
|
||||
private $visualization;
|
||||
private $holdFiles = [];
|
||||
private $inputFiles;
|
||||
private $outputFile;
|
||||
private $isPlaying = false;
|
||||
|
||||
private $connection_settings = [];
|
||||
private $dclist = [];
|
||||
|
||||
private $datacenter;
|
||||
|
||||
public function __construct(bool $creator, int $otherID, $callID, MTProto $MadelineProto, $callState, $protocol)
|
||||
{
|
||||
$this->creator = $creator;
|
||||
$this->otherID = $otherID;
|
||||
$this->callID = $callID;
|
||||
$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));
|
||||
}
|
||||
|
||||
public function deInitVoIPController()
|
||||
{
|
||||
}
|
||||
|
||||
public function setVisualization($visualization)
|
||||
{
|
||||
$this->visualization = $visualization;
|
||||
}
|
||||
|
||||
public function getVisualization()
|
||||
{
|
||||
return $this->visualization;
|
||||
}
|
||||
|
||||
public function discard($reason = ['_' => 'phoneCallDiscardReasonDisconnect'], $rating = [], $debug = false)
|
||||
{
|
||||
if ($this->callState === self::CALL_STATE_ENDED || empty($this->configuration)) {
|
||||
return false;
|
||||
}
|
||||
$this->deinitVoIPController();
|
||||
|
||||
return Tools::callFork($this->MadelineProto->discardCall($this->callID, $reason, $rating, $debug));
|
||||
}
|
||||
|
||||
public function accept()
|
||||
{
|
||||
if ($this->callState !== self::CALL_STATE_INCOMING) {
|
||||
return false;
|
||||
}
|
||||
$this->callState = self::CALL_STATE_ACCEPTED;
|
||||
|
||||
Tools::call($this->MadelineProto->acceptCall($this->callID))->onResolve(function ($e, $res) {
|
||||
if ($e || !$res) {
|
||||
$this->discard(['_' => 'phoneCallDiscardReasonDisconnect']);
|
||||
}
|
||||
});
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function close()
|
||||
{
|
||||
$this->deinitVoIPController();
|
||||
}
|
||||
|
||||
public function startTheMagic()
|
||||
{
|
||||
while (true) {
|
||||
$waiting = $this->datacenter->select();
|
||||
foreach ($waiting as $dc) {
|
||||
if ($packet = $this->recv_message($dc)) {
|
||||
$this->handlePacket($dc, $packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
public function handlePacket($datacenter, $packet)
|
||||
{
|
||||
\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);
|
||||
//$a = fopen('paloma.opus', 'rb');
|
||||
//(new Ogg($a, [$this, 'oggCallback']))->run();
|
||||
break;
|
||||
case self::PKT_INIT_ACK:
|
||||
$this->voip_state = self::STATE_ESTABLISHED;
|
||||
$a = \fopen('paloma.opus', 'rb');
|
||||
(new Ogg($a, [$this, 'oggCallback']))->run();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
public $timestamp = 0;
|
||||
public function oggCallback($data)
|
||||
{
|
||||
\var_dump(\strlen($data));
|
||||
$this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $data, 'timestamp' => $this->timestamp]);
|
||||
$this->timestamp += 60;
|
||||
}
|
||||
public function play($file)
|
||||
{
|
||||
$this->inputFiles[] = $file;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function then($file)
|
||||
{
|
||||
$this->inputFiles[] = $file;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function playOnHold($files)
|
||||
{
|
||||
$this->holdFiles = $files;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setOutputFile($file)
|
||||
{
|
||||
$this->outputFile = $file;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function unsetOutputFile()
|
||||
{
|
||||
$this->outputFile = null;
|
||||
}
|
||||
|
||||
public function setMadeline($MadelineProto)
|
||||
{
|
||||
$this->MadelineProto = $MadelineProto;
|
||||
}
|
||||
|
||||
public function getProtocol()
|
||||
{
|
||||
return $this->protocol;
|
||||
}
|
||||
|
||||
public function getOtherID()
|
||||
{
|
||||
return $this->otherID;
|
||||
}
|
||||
|
||||
public function getCallID()
|
||||
{
|
||||
return $this->callID;
|
||||
}
|
||||
|
||||
public function isCreator()
|
||||
{
|
||||
return $this->creator;
|
||||
}
|
||||
|
||||
public function whenCreated()
|
||||
{
|
||||
return isset($this->internalStorage['created']) ? $this->internalStorage['created'] : false;
|
||||
}
|
||||
|
||||
public function parseConfig()
|
||||
{
|
||||
if (\count($this->configuration['endpoints'])) {
|
||||
$this->connection_settings['all'] = $this->MadelineProto->settings['connection_settings']['all'];
|
||||
$this->connection_settings['all']['protocol'] = 'obfuscated2';
|
||||
$this->connection_settings['all']['timeout'] = 1;
|
||||
$this->connection_settings['all']['do_not_retry'] = true;
|
||||
|
||||
$test = $this->connection_settings['all']['test_mode'] ? 'test' : 'main';
|
||||
foreach ($this->configuration['endpoints'] as $endpoint) {
|
||||
$this->dclist[$test]['ipv6'][$endpoint['id']] = ['ip_address' => $endpoint['ipv6'], 'port' => $endpoint['port'], 'peer_tag' => $endpoint['peer_tag']];
|
||||
$this->dclist[$test]['ipv4'][$endpoint['id']] = ['ip_address' => $endpoint['ip'], 'port' => $endpoint['port'], 'peer_tag' => $endpoint['peer_tag']];
|
||||
}
|
||||
if (!isset($this->datacenter)) {
|
||||
$this->datacenter = new DataCenter($this->dclist, $this->connection_settings);
|
||||
}
|
||||
//$this->datacenter->__construct($this->dclist, $this->connection_settings);
|
||||
|
||||
foreach ($this->datacenter->get_dcs() as $new_dc) {
|
||||
try {
|
||||
$this->datacenter->dc_connect($new_dc);
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
}
|
||||
}
|
||||
$this->init_all();
|
||||
foreach ($this->datacenter->get_dcs(false) as $new_dc) {
|
||||
try {
|
||||
$this->datacenter->dc_connect($new_dc);
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
}
|
||||
}
|
||||
$this->init_all();
|
||||
}
|
||||
}
|
||||
|
||||
private function init_all()
|
||||
{
|
||||
$test = $this->connection_settings['all']['test_mode'] ? 'test' : 'main';
|
||||
foreach ($this->datacenter->sockets as $dc_id => $socket) {
|
||||
if ($socket->auth_key === null) {
|
||||
$socket->auth_key = ['id' => $this->configuration['auth_key_id'], 'auth_key' => $this->configuration['auth_key'], 'connection_inited' => false];
|
||||
}
|
||||
if ($socket->type === Connection::API_ENDPOINT) {
|
||||
$socket->type = Connection::VOIP_TCP_REFLECTOR_ENDPOINT;
|
||||
}
|
||||
if ($socket->peer_tag === null) {
|
||||
switch ($socket->type) {
|
||||
case Connection::VOIP_TCP_REFLECTOR_ENDPOINT:
|
||||
case Connection::VOIP_UDP_REFLECTOR_ENDPOINT:
|
||||
$socket->peer_tag = $this->dclist[$test]['ipv4'][$dc_id]['peer_tag'];
|
||||
break;
|
||||
default:
|
||||
$socket->peer_tag = $this->configuration['call_id'];
|
||||
}
|
||||
}
|
||||
//if ($this->voip_state === self::STATE_CREATED) {
|
||||
$this->send_message(['_' => self::PKT_INIT, 'protocol' => self::PROTOCOL_VERSION, 'min_protocol' => self::MIN_PROTOCOL_VERSION, 'audio_streams' => [self::CODEC_OPUS], 'video_streams' => []], $dc_id);
|
||||
$this->voip_state = self::STATE_WAIT_INIT;
|
||||
//}
|
||||
if (isset($this->datacenter->sockets[$dc_id])) {
|
||||
$this->send_message(['_' => self::PKT_PING], $dc_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getCallState()
|
||||
{
|
||||
return $this->callState;
|
||||
}
|
||||
|
||||
public function getVersion()
|
||||
{
|
||||
return 'libponyvoip-1.0';
|
||||
}
|
||||
|
||||
public function getPreferredRelayID()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getLastError()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getDebugLog()
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getSignalBarsCount()
|
||||
{
|
||||
return $this->signal;
|
||||
}
|
||||
}
|
73
src/danog/MadelineProto/VoIP/AckHandler.php
Normal file
73
src/danog/MadelineProto/VoIP/AckHandler.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
Copyright 2016-2018 Daniil Gentili
|
||||
(https://daniil.it)
|
||||
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/>.
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\VoIP;
|
||||
|
||||
trait AckHandler
|
||||
{
|
||||
public function seqgt($s1, $s2)
|
||||
{
|
||||
return $s1 > $s2;
|
||||
}
|
||||
public function received_packet($datacenter, $last_ack_id, $packet_seq_no, $ack_mask)
|
||||
{
|
||||
if ($this->seqgt($packet_seq_no, $this->session_in_seq_no)) {
|
||||
$diff = $packet_seq_no - $this->session_in_seq_no;
|
||||
if ($diff > 31) {
|
||||
$this->received_timestamp_map = \array_fill(0, 32, 0);
|
||||
} else {
|
||||
$remaining = 32-$diff;
|
||||
for ($x = 0; $x < $remaining; $x++) {
|
||||
$this->received_timestamp_map[$diff+$x] = $this->received_timestamp_map[$x];
|
||||
}
|
||||
for ($x = 1; $x < $diff; $x++) {
|
||||
$this->received_timestamp_map[$x] = 0;
|
||||
}
|
||||
$this->received_timestamp_map[0] = \microtime(true);
|
||||
}
|
||||
$this->session_in_seq_no = $packet_seq_no;
|
||||
} elseif (($diff = $this->session_in_seq_no - $packet_seq_no) < 32) {
|
||||
if (!$this->received_timestamp_map[$diff]) {
|
||||
\danog\MadelineProto\Logger::log("Got duplicate $packet_seq_no");
|
||||
return false;
|
||||
}
|
||||
$this->received_timestamp_map[$diff] = \microtime(true);
|
||||
} else {
|
||||
\danog\MadelineProto\Logger::log("Packet $packet_seq_no is out of order and too late");
|
||||
return false;
|
||||
}
|
||||
if ($this->seqgt($last_ack_id, $this->session_out_seq_no)) {
|
||||
$diff = $last_ack_id - $this->session_out_seq_no;
|
||||
if ($diff > 31) {
|
||||
$this->remote_ack_timestamp_map = \array_fill(0, 32, 0);
|
||||
} else {
|
||||
$remaining = 32-$diff;
|
||||
for ($x = 0; $x < $remaining; $x++) {
|
||||
$this->remote_ack_timestamp_map[$diff+$x] = $this->remote_ack_timestamp_map[$x];
|
||||
}
|
||||
for ($x = 1; $x < $diff; $x++) {
|
||||
$this->remote_ack_timestamp_map[$x] = 0;
|
||||
}
|
||||
$this->remote_ack_timestamp_map[0] = \microtime(true);
|
||||
}
|
||||
$this->session_out_seq_no = $last_ack_id;
|
||||
|
||||
for ($x = 1; $x < 32; $x++) {
|
||||
if (!$this->remote_ack_timestamp_map[$x] && ($ack_mask >> 32-$x) & 1) {
|
||||
$this->remote_ack_timestamp_map[$x] = \microtime(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
459
src/danog/MadelineProto/VoIP/MessageHandler.php
Normal file
459
src/danog/MadelineProto/VoIP/MessageHandler.php
Normal file
@ -0,0 +1,459 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
Copyright 2016-2018 Daniil Gentili
|
||||
(https://daniil.it)
|
||||
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/>.
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\VoIP;
|
||||
|
||||
/**
|
||||
* Manages packing and unpacking of messages, and the list of sent and received messages.
|
||||
*/
|
||||
trait MessageHandler
|
||||
{
|
||||
public function pack_string($object)
|
||||
{
|
||||
$l = \strlen($object);
|
||||
$concat = '';
|
||||
if ($l <= 253) {
|
||||
$concat .= \chr($l);
|
||||
$concat .= $object;
|
||||
$concat .= \pack('@'.$this->posmod(-$l - 1, 4));
|
||||
} else {
|
||||
$concat .= \chr(254);
|
||||
$concat .= \substr($this->pack_signed_int($l), 0, 3);
|
||||
$concat .= $object;
|
||||
$concat .= \pack('@'.$this->posmod(-$l, 4));
|
||||
}
|
||||
|
||||
return $concat;
|
||||
}
|
||||
public function unpack_string($stream)
|
||||
{
|
||||
$l = \ord(\stream_get_contents($stream, 1));
|
||||
if ($l > 254) {
|
||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['length_too_big']);
|
||||
}
|
||||
if ($l === 254) {
|
||||
$long_len = \unpack('V', \stream_get_contents($stream, 3).\chr(0))[1];
|
||||
$x = \stream_get_contents($stream, $long_len);
|
||||
$resto = $this->posmod(-$long_len, 4);
|
||||
if ($resto > 0) {
|
||||
\stream_get_contents($stream, $resto);
|
||||
}
|
||||
} else {
|
||||
$x = \stream_get_contents($stream, $l);
|
||||
$resto = $this->posmod(-($l + 1), 4);
|
||||
if ($resto > 0) {
|
||||
\stream_get_contents($stream, $resto);
|
||||
}
|
||||
}
|
||||
return $x;
|
||||
}
|
||||
public function send_message($args, $datacenter = null)
|
||||
{
|
||||
if ($datacenter === null) {
|
||||
return $this->send_message($args, \key($this->datacenter->sockets));
|
||||
}
|
||||
$message = '';
|
||||
switch ($args['_']) {
|
||||
// streamTypeSimple codec:int8 = StreamType;
|
||||
//
|
||||
// packetInit#1 protocol:int min_protocol:int flags:# data_saving_enabled:flags.0?true audio_streams:byteVector<streamTypeSimple> video_streams:byteVector<streamTypeSimple> = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_INIT:
|
||||
$message .= $this->pack_signed_int($args['protocol']);
|
||||
$message .= $this->pack_signed_int($args['min_protocol']);
|
||||
$flags = 0;
|
||||
$flags = isset($args['data_saving_enabled']) && $args['data_saving_enabled'] ? $flags | 1 : $flags & ~1;
|
||||
$message .= $this->pack_unsigned_int($flags);
|
||||
$message .= \chr(\count($args['audio_streams']));
|
||||
foreach ($args['audio_streams'] as $codec) {
|
||||
$message .= \chr($codec);
|
||||
}
|
||||
$message .= \chr(\count($args['video_streams']));
|
||||
foreach ($args['video_streams'] as $codec) {
|
||||
$message .= \chr($codec);
|
||||
}
|
||||
break;
|
||||
// streamType id:int8 type:int8 codec:int8 frame_duration:int16 enabled:int8 = StreamType;
|
||||
//
|
||||
// packetInitAck#2 protocol:int min_protocol:int all_streams:byteVector<streamType> = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_INIT_ACK:
|
||||
$message .= $this->pack_signed_int($args['protocol']);
|
||||
$message .= $this->pack_signed_int($args['min_protocol']);
|
||||
$message .= \chr(\count($args['all_streams']));
|
||||
foreach ($args['all_streams'] as $stream) {
|
||||
$message .= \chr($stream['id']);
|
||||
$message .= \chr($stream['type']);
|
||||
$message .= \chr($stream['codec']);
|
||||
$message .= \pack('v', $stream['frame_duration']);
|
||||
$message .= \chr($stream['enabled']);
|
||||
}
|
||||
break;
|
||||
// streamTypeState id:int8 enabled:int8 = StreamType;
|
||||
// packetStreamState#3 state:streamTypeState = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_STREAM_STATE:
|
||||
$message .= \chr($args['id']);
|
||||
$message .= \chr($args['enabled']);
|
||||
break;
|
||||
// streamData flags:int2 stream_id:int6 has_more_flags:flags.1?true length:(flags.0?int16:int8) timestamp:int data:byteArray = StreamData;
|
||||
// packetStreamData#4 stream_data:streamData = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_STREAM_DATA:
|
||||
$length = \strlen($args['data']);
|
||||
$flags = 0;
|
||||
$flags = $length > 255 ? $flags | 1 : $flags & ~1;
|
||||
$flags = isset($args['has_more_flags']) && $args['has_more_flags'] ? $flags | 2 : $flags & ~2;
|
||||
$flags = $flags << 6;
|
||||
$flags = $flags | $args['stream_id'];
|
||||
$message .= \chr($flags);
|
||||
$message .= $length > 255 ? \pack('v', $length) : \chr($length);
|
||||
$message .= $this->pack_unsigned_int($args['timestamp']);
|
||||
$message .= $args['data'];
|
||||
break;
|
||||
/*case \danog\MadelineProto\VoIP::PKT_UPDATE_STREAMS:
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_PING:
|
||||
break;*/
|
||||
case \danog\MadelineProto\VoIP::PKT_PONG:
|
||||
$message .= $this->pack_unsigned_int($args['out_seq_no']);
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_STREAM_DATA_X2:
|
||||
for ($x = 0; $x < 2; $x++) {
|
||||
$length = \strlen($args[$x]['data']);
|
||||
$flags = 0;
|
||||
$flags = $length > 255 ? $flags | 1 : $flags & ~1;
|
||||
$flags = isset($args[$x]['has_more_flags']) && $args[$x]['has_more_flags'] ? $flags | 2 : $flags & ~2;
|
||||
$flags = $flags << 6;
|
||||
$flags = $flags | $args[$x]['stream_id'];
|
||||
$message .= \chr($flags);
|
||||
$message .= $length > 255 ? \pack('v', $length) : \chr($length);
|
||||
$message .= $this->pack_unsigned_int($args[$x]['timestamp']);
|
||||
$message .= $args[$x]['data'];
|
||||
}
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_STREAM_DATA_X3:
|
||||
for ($x = 0; $x < 3; $x++) {
|
||||
$length = \strlen($args[$x]['data']);
|
||||
$flags = 0;
|
||||
$flags = $length > 255 ? $flags | 1 : $flags & ~1;
|
||||
$flags = isset($args[$x]['has_more_flags']) && $args[$x]['has_more_flags'] ? $flags | 2 : $flags & ~2;
|
||||
$flags = $flags << 6;
|
||||
$flags = $flags | $args[$x]['stream_id'];
|
||||
$message .= \chr($flags);
|
||||
$message .= $length > 255 ? \pack('v', $length) : \chr($length);
|
||||
$message .= $this->pack_unsigned_int($args[$x]['timestamp']);
|
||||
$message .= $args[$x]['data'];
|
||||
}
|
||||
break;
|
||||
// packetLanEndpoint#A address:int port:int = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_LAN_ENDPOINT:
|
||||
$message .= $this->pack_signed_int($args['address']);
|
||||
$message .= $this->pack_signed_int($args['port']);
|
||||
break;
|
||||
// packetNetworkChanged#B flags:# data_saving_enabled:flags.0?true = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_NETWORK_CHANGED:
|
||||
$message .= $this->pack_signed_int(isset($args['data_saving_enabled']) && $args['data_saving_enabled'] ? 1 : 0);
|
||||
break;
|
||||
// packetSwitchPreferredRelay#C relay_id:long = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_SWITCH_PREF_RELAY:
|
||||
$message .= $this->pack_signed_long($args['relay_d']);
|
||||
break;
|
||||
/*case \danog\MadelineProto\VoIP::PKT_SWITCH_TO_P2P:
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_NOP:
|
||||
break;*/
|
||||
}
|
||||
|
||||
$ack_mask = 0;
|
||||
for ($x=0;$x<32;$x++) {
|
||||
if ($this->received_timestamp_map[$x]>0) {
|
||||
$ack_mask|=1;
|
||||
}
|
||||
if ($x<31) {
|
||||
$ack_mask<<=1;
|
||||
}
|
||||
}
|
||||
|
||||
if (\in_array($this->voip_state, [\danog\MadelineProto\VoIP::STATE_WAIT_INIT, \danog\MadelineProto\VoIP::STATE_WAIT_INIT_ACK])) {
|
||||
$payload = $this->TLID_DECRYPTED_AUDIO_BLOCK;
|
||||
$payload .= $this->random(8);
|
||||
$payload .= \chr(7);
|
||||
$payload .= $this->random(7);
|
||||
$flags = 0;
|
||||
$flags = $flags | 4; // call_id
|
||||
$flags = $flags | 16; // seqno
|
||||
$flags = $flags | 32; // ack mask
|
||||
$flags = $flags | 8; // proto
|
||||
$flags = isset($args['extra']) ? $flags | 2 : $flags & ~2; // extra
|
||||
$flags = \strlen($message) ? $flags | 1 : $flags & ~1; // raw_data
|
||||
$flags = $flags | ($args['_'] << 24);
|
||||
$payload .= $this->pack_unsigned_int($flags);
|
||||
$payload .= $this->configuration['call_id'];
|
||||
$payload .= $this->pack_unsigned_int($this->session_in_seq_no);
|
||||
$payload .= $this->pack_unsigned_int($this->session_out_seq_no);
|
||||
$payload .= $this->pack_unsigned_int($ack_mask);
|
||||
$payload .= \danog\MadelineProto\VoIP::PROTO_ID;
|
||||
if ($flags & 2) {
|
||||
$payload .= $this->pack_string($args['extra']);
|
||||
}
|
||||
if ($flags & 1) {
|
||||
$payload .= $this->pack_string($message);
|
||||
}
|
||||
} else {
|
||||
$payload = $this->TLID_SIMPLE_AUDIO_BLOCK;
|
||||
$payload .= $this->random(8);
|
||||
$payload .= \chr(7);
|
||||
$payload .= $this->random(7);
|
||||
$message = \chr($args['_']).$this->pack_unsigned_int($this->session_in_seq_no).$this->pack_unsigned_int($this->session_out_seq_no).$this->pack_unsigned_int($ack_mask).$message;
|
||||
|
||||
$payload .= $this->pack_string($message);
|
||||
}
|
||||
$this->session_out_seq_no++;
|
||||
|
||||
$payload = $this->pack_unsigned_int(\strlen($payload)).$payload;
|
||||
$payload_key = \substr(\sha1($payload, true), -16);
|
||||
list($aes_key, $aes_iv) = $this->old_aes_calculate($payload_key, $this->datacenter->sockets[$datacenter]->auth_key['auth_key'], $this->creator);
|
||||
$payload .= $this->random($this->posmod(-\strlen($payload), 16));
|
||||
$payload = $this->datacenter->sockets[$datacenter]->peer_tag.$this->datacenter->sockets[$datacenter]->auth_key['id'].$payload_key.$this->ige_encrypt($payload, $aes_key, $aes_iv);
|
||||
try {
|
||||
$this->datacenter->sockets[$datacenter]->send_message($payload);
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
unset($this->datacenter->sockets[$datacenter]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reading connection and receiving message from server.
|
||||
*/
|
||||
public function recv_message($datacenter)
|
||||
{
|
||||
$payload = \fopen('php://memory', 'rw+b');
|
||||
\fwrite($payload, $this->datacenter->sockets[$datacenter]->read_message());
|
||||
\fseek($payload, 0);
|
||||
|
||||
|
||||
if (\stream_get_contents($payload, 16) !== $this->datacenter->sockets[$datacenter]->peer_tag) {
|
||||
\danog\MadelineProto\Logger::log("Received packet has wrong peer tag", \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
if (\stream_get_contents($payload, 12) === "\0\0\0\0\0\0\0\0\0\0\0\0") {
|
||||
$payload = \stream_get_contents($payload);
|
||||
} else {
|
||||
\fseek($payload, 16);
|
||||
if (\stream_get_contents($payload, 8) !== $this->datacenter->sockets[$datacenter]->auth_key['id']) {
|
||||
\danog\MadelineProto\Logger::log('Wrong auth key ID', \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
$message_key = \stream_get_contents($payload, 16);
|
||||
list($aes_key, $aes_iv) = $this->old_aes_calculate($message_key, $this->datacenter->sockets[$datacenter]->auth_key['auth_key'], !$this->creator);
|
||||
$encrypted_data = \stream_get_contents($payload);
|
||||
if (\strlen($encrypted_data) % 16 != 0) {
|
||||
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['length_not_divisible_16'], \danog\MadelineProto\Logger::ERROR);
|
||||
}
|
||||
|
||||
$decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv);
|
||||
$message_data_length = \unpack('V', \substr($decrypted_data, 0, 4))[1];
|
||||
$payload = \substr($decrypted_data, 4, $message_data_length);
|
||||
|
||||
if ($message_data_length > \strlen($decrypted_data)) {
|
||||
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['msg_data_length_too_big'], \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
if ($message_key != \substr(\sha1(\substr($decrypted_data, 0, 4 + $message_data_length), true), -16)) {
|
||||
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['msg_key_mismatch'], \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
if (\strlen($decrypted_data) - 4 - $message_data_length > 15) {
|
||||
\danog\MadelineProto\Logger::log('difference between message_data_length and the length of the remaining decrypted buffer is too big', \danog\MadelineProto\Logger::ERROR);
|
||||
}
|
||||
if (\strlen($decrypted_data) % 16 != 0) {
|
||||
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['length_not_divisible_16'], \danog\MadelineProto\Logger::ERROR);
|
||||
}
|
||||
}
|
||||
$stream = \fopen('php://memory', 'rw+b');
|
||||
\fwrite($stream, $payload);
|
||||
$payload = $stream;
|
||||
\fseek($payload, 0);
|
||||
|
||||
$result = [];
|
||||
switch ($crc = \stream_get_contents($payload, 4)) {
|
||||
case $this->TLID_DECRYPTED_AUDIO_BLOCK:
|
||||
\stream_get_contents($payload, 8);
|
||||
$this->unpack_string($payload);
|
||||
$flags = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$result['_'] = $flags >> 24;
|
||||
if ($flags & 4) {
|
||||
if (\stream_get_contents($payload, 16) !== $this->configuration['call_id']) {
|
||||
\danog\MadelineProto\Logger::log('Call ID mismatch', \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($flags & 16) {
|
||||
$in_seq_no = \unpack('V', \stream_get_contents($stream, 4))[1];
|
||||
$out_seq_no = \unpack('V', \stream_get_contents($stream, 4))[1];
|
||||
}
|
||||
if ($flags & 32) {
|
||||
$ack_mask = \unpack('V', \stream_get_contents($stream, 4))[1];
|
||||
}
|
||||
if ($flags & 8) {
|
||||
if (\stream_get_contents($stream, 4) !== \danog\MadelineProto\VoIP::PROTO_ID) {
|
||||
\danog\MadelineProto\Logger::log('Protocol mismatch', \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($flags & 2) {
|
||||
$result['extra'] = $this->unpack_string($stream);
|
||||
}
|
||||
$message = \fopen('php://memory', 'rw+b');
|
||||
|
||||
if ($flags & 1) {
|
||||
\fwrite($message, $this->unpack_string($stream));
|
||||
\fseek($message, 0);
|
||||
}
|
||||
break;
|
||||
case $this->TLID_SIMPLE_AUDIO_BLOCK:
|
||||
\stream_get_contents($payload, 8);
|
||||
$this->unpack_string($payload);
|
||||
$flags = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
|
||||
$message = \fopen('php://memory', 'rw+b');
|
||||
\fwrite($message, $this->unpack_string($stream));
|
||||
\fseek($message, 0);
|
||||
$result['_'] = \ord(\stream_get_contents($message, 1));
|
||||
$in_seq_no = \unpack('V', \stream_get_contents($message, 4))[1];
|
||||
$out_seq_no = \unpack('V', \stream_get_contents($message, 4))[1];
|
||||
$ack_mask = \unpack('V', \stream_get_contents($message, 4))[1];
|
||||
|
||||
break;
|
||||
case $this->TLID_REFLECTOR_SELF_INFO:
|
||||
$result['date'] = $this->unpack_signed_int(\stream_get_contents($payload, 4));
|
||||
$result['query_id'] = $this->unpack_signed_long(\stream_get_contents($payload, 8));
|
||||
$result['my_ip'] = \stream_get_contents($payload, 16);
|
||||
$result['my_port'] = $this->unpack_signed_int(\stream_get_contents($payload, 4));
|
||||
return $result;
|
||||
case $this->TLID_REFLECTOR_PEER_INFO:
|
||||
$result['my_address'] = $this->unpack_signed_int(\stream_get_contents($payload, 4));
|
||||
$result['my_port'] = $this->unpack_signed_int(\stream_get_contents($payload, 4));
|
||||
$result['peer_address'] = $this->unpack_signed_int(\stream_get_contents($payload, 4));
|
||||
$result['peer_port'] = $this->unpack_signed_int(\stream_get_contents($payload, 4));
|
||||
return $result;
|
||||
default:
|
||||
\danog\MadelineProto\Logger::log('Unknown packet received: '.\bin2hex($crc), \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
if (!$this->received_packet($datacenter, $in_seq_no, $out_seq_no, $ack_mask)) {
|
||||
return false;
|
||||
}
|
||||
switch ($result['_']) {
|
||||
// streamTypeSimple codec:int8 = StreamType;
|
||||
//
|
||||
// packetInit#1 protocol:int min_protocol:int flags:# data_saving_enabled:flags.0?true audio_streams:byteVector<streamTypeSimple> video_streams:byteVector<streamTypeSimple> = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_INIT:
|
||||
$result['protocol'] = $this->unpack_signed_int(\stream_get_contents($message, 4));
|
||||
$result['min_protocol'] = $this->unpack_signed_int(\stream_get_contents($message, 4));
|
||||
$flags = \unpack('V', \stream_get_contents($message, 4))[1];
|
||||
$result['data_saving_enabled'] = (bool) ($flags & 1);
|
||||
$result['audio_streams'] = [];
|
||||
$length = \ord(\stream_get_contents($message, 1));
|
||||
for ($x = 0; $x < $length; $x++) {
|
||||
$result['audio_streams'][$x] = \ord(\stream_get_contents($message, 1));
|
||||
}
|
||||
$result['video_streams'] = [];
|
||||
$length = \ord(\stream_get_contents($message, 1));
|
||||
for ($x = 0; $x < $length; $x++) {
|
||||
$result['video_streams'][$x] = \ord(\stream_get_contents($message, 1));
|
||||
}
|
||||
break;
|
||||
// streamType id:int8 type:int8 codec:int8 frame_duration:int16 enabled:int8 = StreamType;
|
||||
//
|
||||
// packetInitAck#2 protocol:int min_protocol:int all_streams:byteVector<streamType> = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_INIT_ACK:
|
||||
$result['protocol'] = $this->unpack_signed_int(\stream_get_contents($message, 4));
|
||||
$result['min_protocol'] = $this->unpack_signed_int(\stream_get_contents($message, 4));
|
||||
$result['all_streams'] = [];
|
||||
$length = \ord(\stream_get_contents($message, 1));
|
||||
for ($x = 0; $x < $length; $x++) {
|
||||
$result['all_streams'][$x]['id'] = \ord(\stream_get_contents($message, 1));
|
||||
$result['all_streams'][$x]['type'] = \ord(\stream_get_contents($message, 1));
|
||||
$result['all_streams'][$x]['codec'] = \ord(\stream_get_contents($message, 1));
|
||||
$result['all_streams'][$x]['frame_duration'] = \unpack('v', \stream_get_contents($message, 2))[1];
|
||||
$result['all_streams'][$x]['enabled'] = \ord(\stream_get_contents($message, 1));
|
||||
}
|
||||
|
||||
break;
|
||||
// streamTypeState id:int8 enabled:int8 = StreamType;
|
||||
// packetStreamState#3 state:streamTypeState = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_STREAM_STATE:
|
||||
$result['id'] = \ord(\stream_get_contents($message, 1));
|
||||
$result['enabled'] = \ord(\stream_get_contents($message, 1));
|
||||
break;
|
||||
// streamData flags:int2 stream_id:int6 has_more_flags:flags.1?true length:(flags.0?int16:int8) timestamp:int data:byteArray = StreamData;
|
||||
// packetStreamData#4 stream_data:streamData = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_STREAM_DATA:
|
||||
$flags = \ord(\stream_get_contents($message, 1));
|
||||
$result['stream_id'] = $flags & 0x3F;
|
||||
$flags = ($flags & 0xC0) >> 6;
|
||||
$result['has_more_flags'] = (bool) ($flags & 2);
|
||||
$length = $flags & 1 ? \unpack('v', \stream_get_contents($message, 2))[1] : \ord(\stream_get_contents($message, 1));
|
||||
$result['timestamp'] = \unpack('V', \stream_get_contents($message, 4))[1];
|
||||
$result['data'] = \stream_get_contents($message, $length);
|
||||
break;
|
||||
/*case \danog\MadelineProto\VoIP::PKT_UPDATE_STREAMS:
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_PING:
|
||||
break;*/
|
||||
case \danog\MadelineProto\VoIP::PKT_PONG:
|
||||
if (\fstat($stream)['size'] - \ftell($stream)) {
|
||||
$result['out_seq_no'] = \unpack('V', \stream_get_contents($stream, 4))[1];
|
||||
}
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_STREAM_DATA_X2:
|
||||
for ($x = 0; $x < 2; $x++) {
|
||||
$flags = \ord(\stream_get_contents($message, 1));
|
||||
$result[$x]['stream_id'] = $flags & 0x3F;
|
||||
$flags = ($flags & 0xC0) >> 6;
|
||||
$result[$x]['has_more_flags'] = (bool) ($flags & 2);
|
||||
$length = $flags & 1 ? \unpack('v', \stream_get_contents($message, 2))[1] : \ord(\stream_get_contents($message, 1));
|
||||
$result[$x]['timestamp'] = \unpack('V', \stream_get_contents($message, 4))[1];
|
||||
$result[$x]['data'] = \stream_get_contents($message, $length);
|
||||
}
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_STREAM_DATA_X3:
|
||||
for ($x = 0; $x < 3; $x++) {
|
||||
$flags = \ord(\stream_get_contents($message, 1));
|
||||
$result[$x]['stream_id'] = $flags & 0x3F;
|
||||
$flags = ($flags & 0xC0) >> 6;
|
||||
$result[$x]['has_more_flags'] = (bool) ($flags & 2);
|
||||
$length = $flags & 1 ? \unpack('v', \stream_get_contents($message, 2))[1] : \ord(\stream_get_contents($message, 1));
|
||||
$result[$x]['timestamp'] = \unpack('V', \stream_get_contents($message, 4))[1];
|
||||
$result[$x]['data'] = \stream_get_contents($message, $length);
|
||||
}
|
||||
break;
|
||||
// packetLanEndpoint#A address:int port:int = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_LAN_ENDPOINT:
|
||||
$result['address'] = \unpack('V', \stream_get_contents($stream, 4))[1];
|
||||
$result['port'] = \unpack('V', \stream_get_contents($stream, 4))[1];
|
||||
break;
|
||||
// packetNetworkChanged#B flags:# data_saving_enabled:flags.0?true = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_NETWORK_CHANGED:
|
||||
$result['data_saving_enabled'] = (bool) (\unpack('V', \stream_get_contents($stream, 4))[1] & 1);
|
||||
break;
|
||||
// packetSwitchPreferredRelay#C relay_id:long = Packet;
|
||||
case \danog\MadelineProto\VoIP::PKT_SWITCH_PREF_RELAY:
|
||||
$result['relay_id'] = $this->unpack_signed_long(\stream_get_contents($stream, 8));
|
||||
break;
|
||||
/*case \danog\MadelineProto\VoIP::PKT_SWITCH_TO_P2P:
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_NOP:
|
||||
break;*/
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user