Add OGG OPUS parser

This commit is contained in:
Daniil Gentili 2020-10-26 21:38:23 +01:00
parent 47b808ca9b
commit d0189b1ef2
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
6 changed files with 1460 additions and 2 deletions

View 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__;
}
}

View File

@ -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);

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}