mirror of
https://github.com/danog/MadelineProto.git
synced 2024-11-30 04:08:59 +01:00
IPC&file fixes
This commit is contained in:
parent
0689c16309
commit
a26f906c5b
@ -107,6 +107,10 @@ class Exception extends \Exception
|
||||
{
|
||||
$print = function (string $s): void {
|
||||
Logger::log($s, Logger::FATAL_ERROR);
|
||||
if (\headers_sent()) {
|
||||
return;
|
||||
}
|
||||
\http_response_code(500);
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
echo($s.PHP_EOL);
|
||||
} else {
|
||||
|
@ -116,7 +116,7 @@ final class MTProto implements TLCallback, LoggerGetter
|
||||
* @internal
|
||||
* @var int
|
||||
*/
|
||||
const V = 170;
|
||||
const V = 171;
|
||||
/**
|
||||
* Bad message error codes.
|
||||
*
|
||||
@ -529,22 +529,11 @@ final class MTProto implements TLCallback, LoggerGetter
|
||||
$this->cleanupProperties();
|
||||
// Start IPC server
|
||||
if (!$this->ipcServer) {
|
||||
try {
|
||||
$this->ipcServer = new Server($this);
|
||||
$this->ipcServer->setSettings($this->settings->getIpc());
|
||||
$this->ipcServer->setIpcPath($this->wrapper->getSession());
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR);
|
||||
}
|
||||
}
|
||||
try {
|
||||
$this->ipcServer->start();
|
||||
} catch (Throwable $e) {
|
||||
if (Magic::$isIpcWorker) {
|
||||
throw $e;
|
||||
}
|
||||
$this->logger->logger("Error while starting IPC server: $e", Logger::FATAL_ERROR);
|
||||
$this->ipcServer = new Server($this);
|
||||
$this->ipcServer->setSettings($this->settings->getIpc());
|
||||
$this->ipcServer->setIpcPath($this->wrapper->getSession());
|
||||
}
|
||||
$this->ipcServer->start();
|
||||
// Load rsa keys
|
||||
$this->rsa_keys = [];
|
||||
foreach ($this->settings->getConnection()->getRSAKeys() as $key) {
|
||||
|
@ -1113,8 +1113,8 @@ trait Files
|
||||
if ($offset['part_start_at'] || $offset['part_end_at'] !== $offset['limit']) {
|
||||
$res['bytes'] = \substr($res['bytes'], $offset['part_start_at'], $offset['part_end_at'] - $offset['part_start_at']);
|
||||
}
|
||||
if (!$seekable) {
|
||||
$offset['previous_promise'];
|
||||
if (!$seekable && $offset['previous_promise'] instanceof Future) {
|
||||
$offset['previous_promise']->await();
|
||||
}
|
||||
$len = \strlen($res['bytes']);
|
||||
$res = $callable($res['bytes'], $offset['offset'] + $offset['part_start_at']);
|
||||
|
@ -106,14 +106,14 @@ final class ResponseInfo
|
||||
}
|
||||
$seek_start = empty($seek_start) ? 0 : \abs(\intval($seek_start));
|
||||
|
||||
if ($range !== '') {
|
||||
/*if ($range !== '') {
|
||||
//Safari video streaming fix
|
||||
$length = ($seek_end - $seek_start + 1);
|
||||
$maxChunkSize = 10 * 1024 ** 2;
|
||||
if ($length > $maxChunkSize) {
|
||||
$seek_end = $seek_start + $maxChunkSize - 1;
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
$this->serve = $method !== 'HEAD';
|
||||
if ($seek_start > 0 || $seek_end < $size - 1) {
|
||||
|
631
src/Ogg.php
Normal file
631
src/Ogg.php
Normal file
@ -0,0 +1,631 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\File\Whence;
|
||||
use AssertionError;
|
||||
use danog\MadelineProto\Stream\BufferedStreamInterface;
|
||||
use danog\MadelineProto\Stream\BufferInterface;
|
||||
use FFI;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
use function Amp\File\openFile;
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Async OGG stream reader and writer.
|
||||
*
|
||||
* @author Charles-Édouard Coste <contact@ccoste.fr>
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
final class Ogg
|
||||
{
|
||||
private const CRC = [
|
||||
0x00000000,0x04c11db7,0x09823b6e,0x0d4326d9,
|
||||
0x130476dc,0x17c56b6b,0x1a864db2,0x1e475005,
|
||||
0x2608edb8,0x22c9f00f,0x2f8ad6d6,0x2b4bcb61,
|
||||
0x350c9b64,0x31cd86d3,0x3c8ea00a,0x384fbdbd,
|
||||
0x4c11db70,0x48d0c6c7,0x4593e01e,0x4152fda9,
|
||||
0x5f15adac,0x5bd4b01b,0x569796c2,0x52568b75,
|
||||
0x6a1936c8,0x6ed82b7f,0x639b0da6,0x675a1011,
|
||||
0x791d4014,0x7ddc5da3,0x709f7b7a,0x745e66cd,
|
||||
0x9823b6e0,0x9ce2ab57,0x91a18d8e,0x95609039,
|
||||
0x8b27c03c,0x8fe6dd8b,0x82a5fb52,0x8664e6e5,
|
||||
0xbe2b5b58,0xbaea46ef,0xb7a96036,0xb3687d81,
|
||||
0xad2f2d84,0xa9ee3033,0xa4ad16ea,0xa06c0b5d,
|
||||
0xd4326d90,0xd0f37027,0xddb056fe,0xd9714b49,
|
||||
0xc7361b4c,0xc3f706fb,0xceb42022,0xca753d95,
|
||||
0xf23a8028,0xf6fb9d9f,0xfbb8bb46,0xff79a6f1,
|
||||
0xe13ef6f4,0xe5ffeb43,0xe8bccd9a,0xec7dd02d,
|
||||
0x34867077,0x30476dc0,0x3d044b19,0x39c556ae,
|
||||
0x278206ab,0x23431b1c,0x2e003dc5,0x2ac12072,
|
||||
0x128e9dcf,0x164f8078,0x1b0ca6a1,0x1fcdbb16,
|
||||
0x018aeb13,0x054bf6a4,0x0808d07d,0x0cc9cdca,
|
||||
0x7897ab07,0x7c56b6b0,0x71159069,0x75d48dde,
|
||||
0x6b93dddb,0x6f52c06c,0x6211e6b5,0x66d0fb02,
|
||||
0x5e9f46bf,0x5a5e5b08,0x571d7dd1,0x53dc6066,
|
||||
0x4d9b3063,0x495a2dd4,0x44190b0d,0x40d816ba,
|
||||
0xaca5c697,0xa864db20,0xa527fdf9,0xa1e6e04e,
|
||||
0xbfa1b04b,0xbb60adfc,0xb6238b25,0xb2e29692,
|
||||
0x8aad2b2f,0x8e6c3698,0x832f1041,0x87ee0df6,
|
||||
0x99a95df3,0x9d684044,0x902b669d,0x94ea7b2a,
|
||||
0xe0b41de7,0xe4750050,0xe9362689,0xedf73b3e,
|
||||
0xf3b06b3b,0xf771768c,0xfa325055,0xfef34de2,
|
||||
0xc6bcf05f,0xc27dede8,0xcf3ecb31,0xcbffd686,
|
||||
0xd5b88683,0xd1799b34,0xdc3abded,0xd8fba05a,
|
||||
0x690ce0ee,0x6dcdfd59,0x608edb80,0x644fc637,
|
||||
0x7a089632,0x7ec98b85,0x738aad5c,0x774bb0eb,
|
||||
0x4f040d56,0x4bc510e1,0x46863638,0x42472b8f,
|
||||
0x5c007b8a,0x58c1663d,0x558240e4,0x51435d53,
|
||||
0x251d3b9e,0x21dc2629,0x2c9f00f0,0x285e1d47,
|
||||
0x36194d42,0x32d850f5,0x3f9b762c,0x3b5a6b9b,
|
||||
0x0315d626,0x07d4cb91,0x0a97ed48,0x0e56f0ff,
|
||||
0x1011a0fa,0x14d0bd4d,0x19939b94,0x1d528623,
|
||||
0xf12f560e,0xf5ee4bb9,0xf8ad6d60,0xfc6c70d7,
|
||||
0xe22b20d2,0xe6ea3d65,0xeba91bbc,0xef68060b,
|
||||
0xd727bbb6,0xd3e6a601,0xdea580d8,0xda649d6f,
|
||||
0xc423cd6a,0xc0e2d0dd,0xcda1f604,0xc960ebb3,
|
||||
0xbd3e8d7e,0xb9ff90c9,0xb4bcb610,0xb07daba7,
|
||||
0xae3afba2,0xaafbe615,0xa7b8c0cc,0xa379dd7b,
|
||||
0x9b3660c6,0x9ff77d71,0x92b45ba8,0x9675461f,
|
||||
0x8832161a,0x8cf30bad,0x81b02d74,0x857130c3,
|
||||
0x5d8a9099,0x594b8d2e,0x5408abf7,0x50c9b640,
|
||||
0x4e8ee645,0x4a4ffbf2,0x470cdd2b,0x43cdc09c,
|
||||
0x7b827d21,0x7f436096,0x7200464f,0x76c15bf8,
|
||||
0x68860bfd,0x6c47164a,0x61043093,0x65c52d24,
|
||||
0x119b4be9,0x155a565e,0x18197087,0x1cd86d30,
|
||||
0x029f3d35,0x065e2082,0x0b1d065b,0x0fdc1bec,
|
||||
0x3793a651,0x3352bbe6,0x3e119d3f,0x3ad08088,
|
||||
0x2497d08d,0x2056cd3a,0x2d15ebe3,0x29d4f654,
|
||||
0xc5a92679,0xc1683bce,0xcc2b1d17,0xc8ea00a0,
|
||||
0xd6ad50a5,0xd26c4d12,0xdf2f6bcb,0xdbee767c,
|
||||
0xe3a1cbc1,0xe760d676,0xea23f0af,0xeee2ed18,
|
||||
0xf0a5bd1d,0xf464a0aa,0xf9278673,0xfde69bc4,
|
||||
0x89b8fd09,0x8d79e0be,0x803ac667,0x84fbdbd0,
|
||||
0x9abc8bd5,0x9e7d9662,0x933eb0bb,0x97ffad0c,
|
||||
0xafb010b1,0xab710d06,0xa6322bdf,0xa2f33668,
|
||||
0xbcb4666d,0xb8757bda,0xb5365d03,0xb1f740b4
|
||||
];
|
||||
private const OPUS_SET_APPLICATION_REQUEST = 4000;
|
||||
private const OPUS_GET_APPLICATION_REQUEST = 4001;
|
||||
private const OPUS_SET_BITRATE_REQUEST = 4002;
|
||||
private const OPUS_GET_BITRATE_REQUEST = 4003;
|
||||
private const OPUS_SET_MAX_BANDWIDTH_REQUEST = 4004;
|
||||
private const OPUS_GET_MAX_BANDWIDTH_REQUEST = 4005;
|
||||
private const OPUS_SET_VBR_REQUEST = 4006;
|
||||
private const OPUS_GET_VBR_REQUEST = 4007;
|
||||
private const OPUS_SET_BANDWIDTH_REQUEST = 4008;
|
||||
private const OPUS_GET_BANDWIDTH_REQUEST = 4009;
|
||||
private const OPUS_SET_COMPLEXITY_REQUEST = 4010;
|
||||
private const OPUS_GET_COMPLEXITY_REQUEST = 4011;
|
||||
private const OPUS_SET_INBAND_FEC_REQUEST = 4012;
|
||||
private const OPUS_GET_INBAND_FEC_REQUEST = 4013;
|
||||
private const OPUS_SET_PACKET_LOSS_PERC_REQUEST = 4014;
|
||||
private const OPUS_GET_PACKET_LOSS_PERC_REQUEST = 4015;
|
||||
private const OPUS_SET_DTX_REQUEST = 4016;
|
||||
private const OPUS_GET_DTX_REQUEST = 4017;
|
||||
private const OPUS_SET_VBR_CONSTRAINT_REQUEST = 4020;
|
||||
private const OPUS_GET_VBR_CONSTRAINT_REQUEST = 4021;
|
||||
private const OPUS_SET_FORCE_CHANNELS_REQUEST = 4022;
|
||||
private const OPUS_GET_FORCE_CHANNELS_REQUEST = 4023;
|
||||
private const OPUS_SET_SIGNAL_REQUEST = 4024;
|
||||
private const OPUS_GET_SIGNAL_REQUEST = 4025;
|
||||
private const OPUS_GET_LOOKAHEAD_REQUEST = 4027;
|
||||
private const OPUS_GET_SAMPLE_RATE_REQUEST = 4029;
|
||||
private const OPUS_GET_FINAL_RANGE_REQUEST = 4031;
|
||||
private const OPUS_GET_PITCH_REQUEST = 4033;
|
||||
private const OPUS_SET_GAIN_REQUEST = 4034;
|
||||
private const OPUS_GET_GAIN_REQUEST = 4045;
|
||||
private const OPUS_SET_LSB_DEPTH_REQUEST = 4036;
|
||||
private const OPUS_GET_LSB_DEPTH_REQUEST = 4037;
|
||||
private const OPUS_GET_LAST_PACKET_DURATION_REQUEST = 4039;
|
||||
private const OPUS_SET_EXPERT_FRAME_DURATION_REQUEST = 4040;
|
||||
private const OPUS_GET_EXPERT_FRAME_DURATION_REQUEST = 4041;
|
||||
private const OPUS_SET_PREDICTION_DISABLED_REQUEST = 4042;
|
||||
private const OPUS_GET_PREDICTION_DISABLED_REQUEST = 4043;
|
||||
private const OPUS_SET_PHASE_INVERSION_DISABLED_REQUEST = 4046;
|
||||
private const OPUS_GET_PHASE_INVERSION_DISABLED_REQUEST = 4047;
|
||||
private const OPUS_GET_IN_DTX_REQUEST = 4049;
|
||||
|
||||
/* Values for the various encoder CTLs */
|
||||
private const OPUS_AUTO = -1000 /**<Auto/default setting @hideinitializer*/;
|
||||
private const OPUS_BITRATE_MAX = -1 /**<Maximum bitrate @hideinitializer*/;
|
||||
|
||||
/** Best for most VoIP/videoconference applications where listening quality and intelligibility matter most.
|
||||
* @hideinitializer */
|
||||
private const OPUS_APPLICATION_VOIP = 2048;
|
||||
/** Best for broadcast/high-fidelity application where the decoded audio should be as close as possible to the input.
|
||||
* @hideinitializer */
|
||||
private const OPUS_APPLICATION_AUDIO = 2049;
|
||||
/** Only use when lowest-achievable latency is what matters most. Voice-optimized modes cannot be used.
|
||||
* @hideinitializer */
|
||||
private const OPUS_APPLICATION_RESTRICTED_LOWDELAY = 2051;
|
||||
|
||||
private const OPUS_SIGNAL_VOICE = 3001 /**< Signal being encoded is voice */;
|
||||
private const OPUS_SIGNAL_MUSIC = 3002 /**< Signal being encoded is music */;
|
||||
private const OPUS_BANDWIDTH_NARROWBAND = 1101 /**< 4 kHz bandpass @hideinitializer*/;
|
||||
private const OPUS_BANDWIDTH_MEDIUMBAND = 1102 /**< 6 kHz bandpass @hideinitializer*/;
|
||||
private const OPUS_BANDWIDTH_WIDEBAND = 1103 /**< 8 kHz bandpass @hideinitializer*/;
|
||||
private const OPUS_BANDWIDTH_SUPERWIDEBAND = 1104 /**<12 kHz bandpass @hideinitializer*/;
|
||||
private const OPUS_BANDWIDTH_FULLBAND = 1105 /**<20 kHz bandpass @hideinitializer*/;
|
||||
|
||||
private const OPUS_FRAMESIZE_ARG = 5000 /**< Select frame size from the argument (default) */;
|
||||
private const OPUS_FRAMESIZE_2_5_MS = 5001 /**< Use 2.5 ms frames */;
|
||||
private const OPUS_FRAMESIZE_5_MS = 5002 /**< Use 5 ms frames */;
|
||||
private const OPUS_FRAMESIZE_10_MS = 5003 /**< Use 10 ms frames */;
|
||||
private const OPUS_FRAMESIZE_20_MS = 5004 /**< Use 20 ms frames */;
|
||||
private const OPUS_FRAMESIZE_40_MS = 5005 /**< Use 40 ms frames */;
|
||||
private const OPUS_FRAMESIZE_60_MS = 5006 /**< Use 60 ms frames */;
|
||||
private const OPUS_FRAMESIZE_80_MS = 5007 /**< Use 80 ms frames */;
|
||||
private const OPUS_FRAMESIZE_100_MS = 5008 /**< Use 100 ms frames */;
|
||||
private const OPUS_FRAMESIZE_120_MS = 5009 /**< Use 120 ms frames */;
|
||||
|
||||
private const CAPTURE_PATTERN = "OggS";
|
||||
const CONTINUATION = 1;
|
||||
const BOS = 2;
|
||||
const EOS = 4;
|
||||
|
||||
const STATE_READ_HEADER = 0;
|
||||
const STATE_READ_COMMENT = 1;
|
||||
const STATE_STREAMING = 3;
|
||||
const STATE_END = 4;
|
||||
|
||||
/**
|
||||
* Frame duration in microseconds.
|
||||
*/
|
||||
private int $frameDuration;
|
||||
/**
|
||||
* 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 iterator.
|
||||
*
|
||||
* @var iterable<string>
|
||||
*/
|
||||
public readonly iterable $opusPackets;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param BufferedStreamInterface $stream The stream
|
||||
*/
|
||||
public function __construct(BufferedStreamInterface $stream)
|
||||
{
|
||||
$this->stream = $stream->getReadBuffer($l);
|
||||
$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',
|
||||
];
|
||||
|
||||
$this->packFormat = \implode(
|
||||
'/',
|
||||
\array_map(
|
||||
fn (string $v, string $k): string => $v.$k,
|
||||
$pack_format,
|
||||
\array_keys($pack_format),
|
||||
),
|
||||
);
|
||||
$it = $this->read();
|
||||
$it->current();
|
||||
$this->opusPackets = $it;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read OPUS length.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @psalm-suppress InvalidArrayOffset
|
||||
*/
|
||||
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;
|
||||
}
|
||||
$this->frameDuration ??= $frameDuration;
|
||||
|
||||
$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);
|
||||
}
|
||||
if (!$selfDelimited) {
|
||||
$sizes []= ($len - ($offset + $padding));
|
||||
}
|
||||
} 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);
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$this->opusPayload .= \substr($content, $preOffset, (int) (($offset - $preOffset) + $sum + $paddingLen));
|
||||
if ($this->currentDuration === $this->frameDuration) {
|
||||
yield $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->opusPayload;
|
||||
$this->opusPayload = '';
|
||||
$this->currentDuration = 0;
|
||||
}
|
||||
}
|
||||
$offset += $paddingLen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read frames.
|
||||
*
|
||||
* @return \Generator<string>
|
||||
*/
|
||||
private function read(): \Generator
|
||||
{
|
||||
$state = self::STATE_READ_HEADER;
|
||||
$content = '';
|
||||
$granule = 0;
|
||||
$ignoredStreams = [];
|
||||
|
||||
while (true) {
|
||||
$capture = $this->stream->bufferRead(4);
|
||||
if ($capture !== self::CAPTURE_PATTERN) {
|
||||
if ($capture === null) {
|
||||
return;
|
||||
}
|
||||
throw new Exception('Bad capture pattern: '.\bin2hex($capture));
|
||||
}
|
||||
|
||||
$headers = \unpack(
|
||||
$this->packFormat,
|
||||
$this->stream->bufferRead(23)
|
||||
);
|
||||
$ignore = \in_array($headers['bitstream_serial_number'], $ignoredStreams, true);
|
||||
|
||||
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'] & self::BOS);
|
||||
$lastPage = (bool) ($headers['header_type_flag'] & self::EOS);
|
||||
|
||||
$segments = \unpack(
|
||||
'C*',
|
||||
$this->stream->bufferRead($headers['number_page_segments']),
|
||||
);
|
||||
|
||||
//$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) {
|
||||
$piece = $this->stream->bufferRead($sizeAccumulated);
|
||||
$sizeAccumulated = 0;
|
||||
if ($ignore) {
|
||||
continue;
|
||||
}
|
||||
$content .= $piece;
|
||||
if ($state === self::STATE_STREAMING) {
|
||||
yield $content;
|
||||
//yield from $this->opusStateMachine($content);
|
||||
} elseif ($state === self::STATE_READ_HEADER) {
|
||||
Assert::true($firstPage);
|
||||
$head = \substr($content, 0, 8);
|
||||
if ($head !== 'OpusHead') {
|
||||
$ignoredStreams[]= $headers['bitstream_serial_number'];
|
||||
$content = '';
|
||||
$ignore = true;
|
||||
continue;
|
||||
}
|
||||
$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'];
|
||||
if ($opus_head['sample_rate'] !== 48000) {
|
||||
throw new AssertionError("The sample rate must be 48khz, got {$opus_head['sample_rate']}");
|
||||
}
|
||||
$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 = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function convert(string $wavIn, string $oggOut): void
|
||||
{
|
||||
$opus = FFI::cdef('
|
||||
typedef struct OpusEncoder OpusEncoder;
|
||||
|
||||
OpusEncoder *opus_encoder_create(
|
||||
int32_t Fs,
|
||||
int channels,
|
||||
int application,
|
||||
int *error
|
||||
);
|
||||
|
||||
int opus_encoder_ctl(OpusEncoder *st, int request, int arg);
|
||||
|
||||
int32_t opus_encode(
|
||||
OpusEncoder *st,
|
||||
const char *pcm,
|
||||
int frame_size,
|
||||
const char *data,
|
||||
int32_t max_data_bytes
|
||||
);
|
||||
void opus_encoder_destroy(OpusEncoder *st);
|
||||
const char *opus_strerror(int error);
|
||||
const char *opus_get_version_string(void);
|
||||
|
||||
', 'libopus.so.0.9.0');
|
||||
$err = FFI::new('int');
|
||||
$encoder = $opus->opus_encoder_create(48000, 2, self::OPUS_APPLICATION_AUDIO, FFI::addr($err));
|
||||
$opus->opus_encoder_ctl($encoder, self::OPUS_SET_COMPLEXITY_REQUEST, 10);
|
||||
$opus->opus_encoder_ctl($encoder, self::OPUS_SET_PACKET_LOSS_PERC_REQUEST, 1);
|
||||
$opus->opus_encoder_ctl($encoder, self::OPUS_SET_INBAND_FEC_REQUEST, 1);
|
||||
$opus->opus_encoder_ctl($encoder, self::OPUS_SET_SIGNAL_REQUEST, self::OPUS_SIGNAL_MUSIC);
|
||||
$opus->opus_encoder_ctl($encoder, self::OPUS_SET_BANDWIDTH_REQUEST, self::OPUS_BANDWIDTH_FULLBAND);
|
||||
$opus->opus_encoder_ctl($encoder, self::OPUS_SET_BITRATE_REQUEST, 130*1000);
|
||||
|
||||
$in = openFile($wavIn, 'r');
|
||||
Assert::eq($in->read(length: 4), 'RIFF');
|
||||
$totalLength = \unpack('V', $in->read(length: 4))[1];
|
||||
Assert::eq($in->read(length: 4), 'WAVE');
|
||||
do {
|
||||
$type = $in->read(length: 4);
|
||||
$length = \unpack('V', $in->read(length: 4))[1];
|
||||
if ($type === 'fmt ') {
|
||||
Assert::eq($length, 16);
|
||||
$contents = $in->read(length: $length + ($length % 2));
|
||||
$header = \unpack('vaudioFormat/vchannels/VsampleRate/VbyteRate/vblockAlign/vbitsPerSample', $contents);
|
||||
Assert::eq($header['audioFormat'], 1);
|
||||
Assert::eq($header['sampleRate'], 48000);
|
||||
} elseif ($type === 'data') {
|
||||
break;
|
||||
} else {
|
||||
$in->seek($length, Whence::Current);
|
||||
}
|
||||
} while (true);
|
||||
|
||||
$sampleCount = 0.06 * $header['sampleRate'];
|
||||
$chunkSize = (int) $sampleCount * $header['channels'] * ($header['bitsPerSample'] >> 3);
|
||||
$shift = (int) \log($header['channels'] * ($header['bitsPerSample'] >> 3), 2);
|
||||
|
||||
$out = openFile($oggOut, 'w');
|
||||
|
||||
$writePage = function (int $header_type_flag, int $granule, int $streamId, int &$streamSeqno, string $packet) use ($out): void {
|
||||
Assert::true(\strlen($packet) < 65025);
|
||||
$segments = [
|
||||
...\array_fill(0, (int) (\strlen($packet) / 255), 255),
|
||||
\strlen($packet) % 255
|
||||
];
|
||||
$data = 'OggS'.\pack(
|
||||
'CCPVVVCC*',
|
||||
0, // stream_structure_version
|
||||
$header_type_flag,
|
||||
$granule,
|
||||
$streamId,
|
||||
$streamSeqno++,
|
||||
0,
|
||||
\count($segments),
|
||||
...$segments
|
||||
).$packet;
|
||||
|
||||
$c = 0;
|
||||
for ($i = 0; $i < \strlen($data); $i++) {
|
||||
$c = ($c<<8)^self::CRC[(($c >> 24)&0xFF)^(\ord($data[$i]))];
|
||||
}
|
||||
$crc = \pack('V', $c);
|
||||
|
||||
$data = \substr_replace(
|
||||
$data,
|
||||
$crc,
|
||||
22,
|
||||
4
|
||||
);
|
||||
$out->write($data);
|
||||
};
|
||||
|
||||
$streamId = \unpack('V', Tools::random(4))[1];
|
||||
$seqno = 0;
|
||||
|
||||
$writePage(
|
||||
Ogg::BOS,
|
||||
0,
|
||||
$streamId,
|
||||
$seqno,
|
||||
'OpusHead'.\pack(
|
||||
'CCvVvC',
|
||||
1,
|
||||
$header['channels'],
|
||||
312,
|
||||
$header['sampleRate'],
|
||||
0,
|
||||
0,
|
||||
)
|
||||
);
|
||||
|
||||
$tags = 'OpusTags';
|
||||
$writeTag = function (string $tag) use (&$tags): void {
|
||||
$tags .= \pack('V', \strlen($tag)).$tag;
|
||||
};
|
||||
$writeTag("MadelineProto ".API::RELEASE.", ".$opus->opus_get_version_string());
|
||||
$tags .= \pack('V', 2);
|
||||
$writeTag("ENCODER=MadelineProto ".API::RELEASE." with ".$opus->opus_get_version_string());
|
||||
$writeTag('See https://docs.madelineproto.xyz/docs/VOIP.html for more info');
|
||||
$writePage(
|
||||
0,
|
||||
0,
|
||||
$streamId,
|
||||
$seqno,
|
||||
$tags
|
||||
);
|
||||
|
||||
$granule = 0;
|
||||
$buf = FFI::cast(FFI::type('char*'), FFI::addr($opus->new('char[1024]')));
|
||||
while (!$in->eof()) {
|
||||
$chunk = $in->read(length: $chunkSize);
|
||||
$granuleDiff = \strlen($chunk) >> $shift;
|
||||
$len = $opus->opus_encode($encoder, $chunk, $granuleDiff, $buf, 1024);
|
||||
$writePage(
|
||||
$in->eof() ? self::EOS : 0,
|
||||
$granule += $granuleDiff,
|
||||
$streamId,
|
||||
$seqno,
|
||||
FFI::string($buf, $len)
|
||||
);
|
||||
}
|
||||
$opus->opus_encoder_destroy($encoder);
|
||||
unset($buf, $encoder, $opus);
|
||||
|
||||
$out->close();
|
||||
}
|
||||
}
|
@ -1,338 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace danog\MadelineProto\Stream\Ogg;
|
||||
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Stream\BufferedStreamInterface;
|
||||
use danog\MadelineProto\Stream\BufferInterface;
|
||||
use RuntimeException;
|
||||
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Async OGG stream reader.
|
||||
*
|
||||
* @author Charles-Édouard Coste <contact@ccoste.fr>
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
*/
|
||||
final 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.
|
||||
* @param iterable<string>
|
||||
*/
|
||||
private iterable $emitter;
|
||||
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param BufferedStreamInterface $stream The stream
|
||||
* @param int $frameDuration Required frame duration, microseconds
|
||||
*/
|
||||
public static function init(BufferedStreamInterface $stream, int $frameDuration): self
|
||||
{
|
||||
$self = new self;
|
||||
$self->frameDuration = $frameDuration;
|
||||
$self->stream = $stream->getReadBuffer($l);
|
||||
$self->emitter = $self->read();
|
||||
$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.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @psalm-suppress InvalidArrayOffset
|
||||
*/
|
||||
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);
|
||||
}
|
||||
if (!$selfDelimited) {
|
||||
$sizes []= ($len - ($offset + $padding));
|
||||
}
|
||||
} 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);
|
||||
/** @psalm-suppress InvalidArgument */
|
||||
$this->opusPayload .= \substr($content, $preOffset, ($offset - $preOffset) + $sum + $paddingLen);
|
||||
if ($this->currentDuration === $this->frameDuration) {
|
||||
yield $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->opusPayload;
|
||||
$this->opusPayload = '';
|
||||
$this->currentDuration = 0;
|
||||
}
|
||||
}
|
||||
$offset += $paddingLen;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read frames.
|
||||
*
|
||||
* @return iterable<string>
|
||||
*/
|
||||
private function read(): \Generator
|
||||
{
|
||||
$state = self::STATE_READ_HEADER;
|
||||
$content = '';
|
||||
|
||||
while (true) {
|
||||
$init = $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*',
|
||||
$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 .= $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'];
|
||||
$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 iterable<string>
|
||||
*/
|
||||
public function getEmitter(): iterable
|
||||
{
|
||||
return $this->emitter;
|
||||
}
|
||||
}
|
167
src/VoIP.php
167
src/VoIP.php
@ -18,7 +18,6 @@ namespace danog\MadelineProto;
|
||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||
use danog\MadelineProto\Stream\Common\FileBufferedStream;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
use danog\MadelineProto\Stream\Ogg\Ogg;
|
||||
use danog\MadelineProto\VoIP\AckHandler;
|
||||
use danog\MadelineProto\VoIP\Endpoint;
|
||||
use danog\MadelineProto\VoIP\MessageHandler;
|
||||
@ -99,11 +98,11 @@ final class VoIP
|
||||
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_DECRYPTED_AUDIO_BLOCK = "\xc1\xdb\xf9\x48";
|
||||
const TLID_SIMPLE_AUDIO_BLOCK = "\x0d\x0e\x76\xcc";
|
||||
|
||||
const TLID_REFLECTOR_SELF_INFO_HEX = 'c01572c7';
|
||||
const TLID_REFLECTOR_PEER_INFO_HEX = '27D9371C';
|
||||
const TLID_REFLECTOR_SELF_INFO = "\xC7\x72\x15\xc0";
|
||||
const TLID_REFLECTOR_PEER_INFO = "\x1C\x37\xD9\x27";
|
||||
|
||||
const PROTO_ID = 'GrVP';
|
||||
|
||||
@ -115,21 +114,15 @@ final class VoIP
|
||||
|
||||
const CODEC_OPUS = 'SUPO';
|
||||
|
||||
private $TLID_DECRYPTED_AUDIO_BLOCK;
|
||||
private $TLID_SIMPLE_AUDIO_BLOCK;
|
||||
private $TLID_REFLECTOR_SELF_INFO;
|
||||
private $TLID_REFLECTOR_PEER_INFO;
|
||||
|
||||
private MTProto $MadelineProto;
|
||||
public MTProto $madeline;
|
||||
public $received_timestamp_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
public $remote_ack_timestamp_map = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
public $session_out_seq_no = 0;
|
||||
public $session_in_seq_no = 0;
|
||||
public $voip_state = 0;
|
||||
public $configuration = ['endpoints' => [], 'shared_config' => []];
|
||||
public $storage = [];
|
||||
public $internalStorage = [];
|
||||
public array $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 array $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 int $session_out_seq_no = 0;
|
||||
public int $session_in_seq_no = 0;
|
||||
public int $voip_state = 0;
|
||||
public array $configuration = ['endpoints' => [], 'shared_config' => []];
|
||||
public array $storage = [];
|
||||
public array $internalStorage = [];
|
||||
private $signal = 0;
|
||||
private ?int $callState = null;
|
||||
private $callID;
|
||||
@ -152,10 +145,17 @@ final class VoIP
|
||||
*/
|
||||
private array $sockets = [];
|
||||
|
||||
private ?Endpoint $bestEndpoint = null;
|
||||
|
||||
private bool $pendingPing = true;
|
||||
/**
|
||||
* Timeout watcher.
|
||||
*/
|
||||
private ?string $timeoutWatcher = null;
|
||||
/**
|
||||
* Ping watcher.
|
||||
*/
|
||||
private ?string $pingWatcher = null;
|
||||
|
||||
/**
|
||||
* Last incoming timestamp.
|
||||
@ -182,7 +182,7 @@ final class VoIP
|
||||
public function __sleep(): array
|
||||
{
|
||||
$vars = \get_object_vars($this);
|
||||
unset($vars['sockets'], $vars['timeoutWatcher']);
|
||||
unset($vars['sockets'], $vars['bestEndpoint'], $vars['timeoutWatcher']);
|
||||
|
||||
return \array_keys($vars);
|
||||
}
|
||||
@ -203,13 +203,9 @@ final class VoIP
|
||||
{
|
||||
$this->creator = $creator;
|
||||
$this->otherID = $otherID;
|
||||
$this->madeline = $this->MadelineProto = $MadelineProto;
|
||||
$this->MadelineProto = $MadelineProto;
|
||||
$this->callState = $callState;
|
||||
$this->packetQueue = new SplQueue;
|
||||
$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));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -325,20 +321,25 @@ final class VoIP
|
||||
$this->authKey->setAuthKey($this->configuration['auth_key']);
|
||||
|
||||
foreach ($this->configuration['endpoints'] as $endpoint) {
|
||||
$this->sockets['v6 '.$endpoint['id']] = new Endpoint('['.$endpoint['ipv6'].']', $endpoint['port'], $endpoint['peer_tag'], true, $this);
|
||||
$this->sockets['v4 '.$endpoint['id']] = new Endpoint($endpoint['ip'], $endpoint['port'], $endpoint['peer_tag'], true, $this);
|
||||
}
|
||||
foreach ($this->sockets as $k => $socket) {
|
||||
try {
|
||||
$socket->connect();
|
||||
} catch (Throwable $e) {
|
||||
unset($this->sockets[$k]);
|
||||
$this->sockets['v6 '.$endpoint['id']] = new Endpoint('['.$endpoint['ipv6'].']', $endpoint['port'], $endpoint['peer_tag'], true, $this);
|
||||
} catch (Throwable) {
|
||||
}
|
||||
try {
|
||||
$this->sockets['v4 '.$endpoint['id']] = new Endpoint($endpoint['ip'], $endpoint['port'], $endpoint['peer_tag'], true, $this);
|
||||
} catch (Throwable) {
|
||||
}
|
||||
}
|
||||
foreach ($this->sockets as $socket) {
|
||||
$this->send_message(['_' => self::PKT_INIT, 'protocol' => self::PROTOCOL_VERSION, 'min_protocol' => self::MIN_PROTOCOL_VERSION, 'audio_streams' => [self::CODEC_OPUS], 'video_streams' => []], $socket);
|
||||
$socket->write($this->encryptPacket([
|
||||
'_' => self::PKT_INIT,
|
||||
'protocol' => self::PROTOCOL_VERSION,
|
||||
'min_protocol' => self::MIN_PROTOCOL_VERSION,
|
||||
'audio_streams' => [self::CODEC_OPUS],
|
||||
'video_streams' => []
|
||||
], false));
|
||||
EventLoop::queue(function () use ($socket): void {
|
||||
while ($payload = $this->recv_message($socket)) {
|
||||
while ($payload = $socket->read()) {
|
||||
$this->lastIncomingTimestamp = \microtime(true);
|
||||
EventLoop::queue($this->handlePacket(...), $socket, $payload);
|
||||
}
|
||||
@ -356,12 +357,32 @@ final class VoIP
|
||||
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]]], $socket);
|
||||
$this->peerVersion = $packet['protocol'];
|
||||
$socket->write(
|
||||
$this->encryptPacket([
|
||||
'_' => 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]
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
$this->startWriteLoop($socket);
|
||||
break;
|
||||
// no break
|
||||
case self::PKT_INIT_ACK:
|
||||
$this->startWriteLoop($socket);
|
||||
if (!$this->bestEndpoint) {
|
||||
$this->bestEndpoint = $socket;
|
||||
$this->pingWatcher = EventLoop::delay(1.0, function (): void {
|
||||
$this->pendingPing = true;
|
||||
foreach ($this->sockets as $socket) {
|
||||
//$socket->udpPing();
|
||||
$packet = $this->encryptPacket(['_' => self::PKT_PING]);
|
||||
EventLoop::queue(fn () => $socket->write($packet));
|
||||
}
|
||||
});
|
||||
$this->startWriteLoop();
|
||||
}
|
||||
break;
|
||||
case self::PKT_STREAM_DATA:
|
||||
$cnt = 1;
|
||||
@ -372,19 +393,26 @@ final class VoIP
|
||||
case self::PKT_STREAM_DATA_X3:
|
||||
$cnt = 3;
|
||||
break;
|
||||
}
|
||||
if (isset($cnt)) {
|
||||
case self::PKT_PONG:
|
||||
if ($this->pendingPing) {
|
||||
$this->pendingPing = false;
|
||||
if ($this->bestEndpoint !== $socket) {
|
||||
Logger::log("Changing best endpoint from {$this->bestEndpoint} to $socket");
|
||||
$this->bestEndpoint = $socket;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
\var_dump($packet);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start write loop.
|
||||
*/
|
||||
private function startWriteLoop(Endpoint $socket): void
|
||||
private function startWriteLoop(): void
|
||||
{
|
||||
if ($this->voip_state === self::STATE_ESTABLISHED) {
|
||||
return;
|
||||
}
|
||||
$this->voip_state = self::STATE_ESTABLISHED;
|
||||
Logger::log("Call established, sending OPUS data!");
|
||||
|
||||
$this->tempHoldFiles = [];
|
||||
while (true) {
|
||||
@ -399,51 +427,34 @@ final class VoIP
|
||||
$file = \array_shift($this->tempHoldFiles);
|
||||
}
|
||||
$it = $this->openFile($file);
|
||||
if ($this->MadelineProto->getSettings()->getVoip()->getPreloadAudio()) {
|
||||
while ($it->advance()) {
|
||||
$this->packetQueue->enqueue($it->getCurrent());
|
||||
foreach ($it->opusPackets as $packet) {
|
||||
$this->packetQueue->enqueue($packet);
|
||||
}
|
||||
$t = \microtime(true) + 0.060;
|
||||
while (!$this->packetQueue->isEmpty()) {
|
||||
$packet = $this->encryptPacket(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $this->packetQueue->dequeue(), 'timestamp' => $this->timestamp]);
|
||||
|
||||
//Logger::log("Writing {$this->timestamp} in $this!");
|
||||
$diff = $t - \microtime(true);
|
||||
if ($diff > 0) {
|
||||
delay($diff);
|
||||
}
|
||||
$t = \microtime(true) + 0.060;
|
||||
while (!$this->packetQueue->isEmpty()) {
|
||||
if (!$this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $this->packetQueue->dequeue(), 'timestamp' => $this->timestamp], $socket)) {
|
||||
Logger::log("Exiting VoIP write loop in $this!");
|
||||
return;
|
||||
}
|
||||
$this->bestEndpoint->write($packet);
|
||||
$t += 0.060;
|
||||
|
||||
//Logger::log("Writing {$this->timestamp} in $this!");
|
||||
delay($t - \microtime(true));
|
||||
$t = \microtime(true) + 0.060;
|
||||
|
||||
$this->timestamp += 60;
|
||||
}
|
||||
} else {
|
||||
$t = \microtime(true) + 0.060;
|
||||
while ($it->advance()) {
|
||||
if (!$this->send_message(['_' => self::PKT_STREAM_DATA, 'stream_id' => 0, 'data' => $it->getCurrent(), 'timestamp' => $this->timestamp], $socket)) {
|
||||
Logger::log("Exiting VoIP write loop in $this!");
|
||||
return;
|
||||
}
|
||||
|
||||
//Logger::log("Writing {$this->timestamp} in $this!");
|
||||
delay($t - \microtime(true));
|
||||
$t = \microtime(true) + 0.060;
|
||||
|
||||
$this->timestamp += 60;
|
||||
}
|
||||
$this->timestamp += 60;
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Open OGG file for reading.
|
||||
*/
|
||||
private function openFile(string $file)
|
||||
private function openFile(string $file): Ogg
|
||||
{
|
||||
$ctx = new ConnectionContext;
|
||||
$ctx->addStream(FileBufferedStream::class, openFile($file, 'r'));
|
||||
$stream = $ctx->getStream();
|
||||
$ogg = Ogg::init($stream, 60000);
|
||||
$it = $ogg->getEmitter();
|
||||
return $it;
|
||||
return new Ogg($stream);
|
||||
}
|
||||
/**
|
||||
* Play file.
|
||||
@ -500,7 +511,7 @@ final class VoIP
|
||||
*/
|
||||
public function setMadeline(MTProto $MadelineProto): void
|
||||
{
|
||||
$this->MadelineProto = $this->madeline = $MadelineProto;
|
||||
$this->MadelineProto = $MadelineProto;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,11 +22,11 @@ use danog\MadelineProto\Logger;
|
||||
*/
|
||||
trait AckHandler
|
||||
{
|
||||
private function seqgt($s1, $s2)
|
||||
private function seqgt(int $s1, int $s2): bool
|
||||
{
|
||||
return $s1 > $s2;
|
||||
}
|
||||
public function received_packet($last_ack_id, $packet_seq_no, $ack_mask)
|
||||
public function received_packet(int $last_ack_id, int $packet_seq_no, int $ack_mask): bool
|
||||
{
|
||||
if ($this->seqgt($packet_seq_no, $this->session_in_seq_no)) {
|
||||
$diff = $packet_seq_no - $this->session_in_seq_no;
|
||||
|
@ -282,6 +282,9 @@ trait AuthKeyHandler
|
||||
$this->logger->logger(\sprintf('Saving debug data for call %s...', $call['id']), Logger::VERBOSE);
|
||||
$this->methodCallAsyncRead('phone.saveCallDebug', ['peer' => $call, 'debug' => $this->calls[$call['id']]->getDebugLog()]);
|
||||
}
|
||||
if (!isset($this->calls[$call['id']])) {
|
||||
return null;
|
||||
}
|
||||
$c = $this->calls[$call['id']];
|
||||
unset($this->calls[$call['id']]);
|
||||
return $c;
|
||||
|
@ -64,16 +64,13 @@ final class Endpoint
|
||||
$this->instance = $instance;
|
||||
$this->creator = $instance->isCreator();
|
||||
$this->authKey = $instance->getAuthKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to endpoint.
|
||||
*/
|
||||
public function connect(): void
|
||||
{
|
||||
$this->socket = connect("udp://{$this->ip}:{$this->port}");
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return "{$this->ip}:{$this->port}";
|
||||
}
|
||||
/**
|
||||
* Disconnect from endpoint.
|
||||
*/
|
||||
@ -84,64 +81,283 @@ final class Endpoint
|
||||
$this->socket = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static function unpack_string($stream): string
|
||||
{
|
||||
$l = \ord(\stream_get_contents($stream, 1));
|
||||
if ($l > 254) {
|
||||
throw new Exception(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 = Tools::posmod(-$long_len, 4);
|
||||
if ($resto > 0) {
|
||||
\stream_get_contents($stream, $resto);
|
||||
}
|
||||
} else {
|
||||
$x = \stream_get_contents($stream, $l);
|
||||
$resto = Tools::posmod(-($l + 1), 4);
|
||||
if ($resto > 0) {
|
||||
\stream_get_contents($stream, $resto);
|
||||
}
|
||||
}
|
||||
return $x;
|
||||
}
|
||||
/**
|
||||
* Read packet.
|
||||
*/
|
||||
public function read()
|
||||
public function read(): ?array
|
||||
{
|
||||
do {
|
||||
$packet = $this->socket->read();
|
||||
if ($packet === null) {
|
||||
return null;
|
||||
}
|
||||
$packet = $this->socket->read();
|
||||
if ($packet === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
do {
|
||||
$payload = \fopen('php://memory', 'rw+b');
|
||||
\fwrite($payload, $packet);
|
||||
\fseek($payload, 0);
|
||||
|
||||
$hasPeerTag = false;
|
||||
$pos = 0;
|
||||
if ($this->instance->getPeerVersion() < 9 || $this->reflector) {
|
||||
$hasPeerTag = true;
|
||||
if (\stream_get_contents($payload, 16) !== $this->peerTag) {
|
||||
/*if (\fread($payload, 16) !== $this->peerTag) {
|
||||
Logger::log('Received packet has wrong peer tag', Logger::ERROR);
|
||||
continue;
|
||||
}
|
||||
}*/
|
||||
$pos = 16;
|
||||
}
|
||||
if (\stream_get_contents($payload, 12) === "\0\0\0\0\0\0\0\0\0\0\0\0") {
|
||||
$payload = \stream_get_contents($payload);
|
||||
if (\fread($payload, 12) === "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF") {
|
||||
switch ($crc = \fread($payload, 4)) {
|
||||
case VoIP::TLID_REFLECTOR_SELF_INFO:
|
||||
$result['_'] = 'reflectorSelfInfo';
|
||||
$result['date'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
$result['query_id'] = Tools::unpackSignedLong(\stream_get_contents($payload, 8));
|
||||
$result['my_ip'] = \stream_get_contents($payload, 16);
|
||||
$result['my_port'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
return $result;
|
||||
case VoIP::TLID_REFLECTOR_PEER_INFO:
|
||||
$result['_'] = 'reflectorPeerInfo';
|
||||
$result['my_address'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
$result['my_port'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
$result['peer_address'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
$result['peer_port'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
return $result;
|
||||
default:
|
||||
Logger::log('Unknown unencrypted packet received: '.\bin2hex($crc), Logger::ERROR);
|
||||
continue 2;
|
||||
}
|
||||
} else {
|
||||
\fseek($payload, $hasPeerTag ? 16 : 0);
|
||||
$message_key = \stream_get_contents($payload, 16);
|
||||
fseek($payload, $pos);
|
||||
$message_key = \fread($payload, 16);
|
||||
[$aes_key, $aes_iv] = Crypt::aesCalculate($message_key, $this->authKey->getAuthKey(), !$this->creator);
|
||||
$encrypted_data = \stream_get_contents($payload);
|
||||
$packet = Crypt::igeDecrypt($encrypted_data, $aes_key, $aes_iv);
|
||||
|
||||
if ($message_key != \substr(\hash('sha256', \substr($this->authKey->getAuthKey(), 88 + ($this->creator ? 8 : 0), 32).$packet, true), 8, 16)) {
|
||||
Logger::log('msg_key mismatch!', Logger::ERROR);
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
$innerLen = \unpack('v', \substr($packet, 0, 2))[1];
|
||||
if ($innerLen > \strlen($packet)) {
|
||||
Logger::log('Received packet has wrong inner length!', Logger::ERROR);
|
||||
return false;
|
||||
continue;
|
||||
}
|
||||
$packet = \substr($packet, 2);
|
||||
}
|
||||
$stream = \fopen('php://memory', 'rw+b');
|
||||
\fwrite($stream, $packet);
|
||||
\fseek($stream, 0);
|
||||
$payload = \fopen('php://memory', 'rw+b');
|
||||
\fwrite($payload, $packet);
|
||||
\fseek($payload, 0);
|
||||
|
||||
return $stream;
|
||||
$result = [];
|
||||
switch ($crc = \stream_get_contents($payload, 4)) {
|
||||
case VoIP::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->instance->configuration['call_id']) {
|
||||
Logger::log('Call ID mismatch', Logger::ERROR);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
if ($flags & 16) {
|
||||
$in_seq_no = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$out_seq_no = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
}
|
||||
if ($flags & 32) {
|
||||
$ack_mask = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
}
|
||||
if ($flags & 8) {
|
||||
if (\stream_get_contents($payload, 4) !== VoIP::PROTO_ID) {
|
||||
Logger::log('Protocol mismatch', Logger::ERROR);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
if ($flags & 2) {
|
||||
$result['extra'] = $this->unpack_string($payload);
|
||||
}
|
||||
$message = \fopen('php://memory', 'rw+b');
|
||||
|
||||
if ($flags & 1) {
|
||||
\fwrite($message, $this->unpack_string($payload));
|
||||
\fseek($message, 0);
|
||||
}
|
||||
break;
|
||||
case VoIP::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($payload));
|
||||
\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;
|
||||
default:
|
||||
if ($this->instance->getPeerVersion() >= 8 || (!$this->instance->getPeerVersion())) {
|
||||
\fseek($payload, -4, SEEK_CUR);
|
||||
$result['_'] = \ord(\stream_get_contents($payload, 1));
|
||||
$in_seq_no = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$out_seq_no = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$ack_mask = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$flags = \ord(\stream_get_contents($payload, 1));
|
||||
if ($flags & 1) {
|
||||
$result['extra'] = [];
|
||||
$count = \ord(\stream_get_contents($payload, 1));
|
||||
for ($x = 0; $x < $count; $x++) {
|
||||
$len = \ord(\stream_get_contents($payload, 1));
|
||||
$result['extra'][]= \stream_get_contents($payload, $len);
|
||||
}
|
||||
}
|
||||
$message = \fopen('php://memory', 'rw+b');
|
||||
|
||||
\fwrite($message, \stream_get_contents($payload));
|
||||
\fseek($message, 0);
|
||||
} else {
|
||||
Logger::log('Unknown packet received: '.\bin2hex($crc), Logger::ERROR);
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
if (!$this->instance->received_packet($in_seq_no, $out_seq_no, $ack_mask)) {
|
||||
return $this->read();
|
||||
}
|
||||
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 VoIP::PKT_INIT:
|
||||
$result['protocol'] = Tools::unpackSignedInt(\stream_get_contents($message, 4));
|
||||
$result['min_protocol'] = Tools::unpackSignedInt(\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] = \stream_get_contents($message, 4);
|
||||
}
|
||||
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 VoIP::PKT_INIT_ACK:
|
||||
$result['protocol'] = Tools::unpackSignedInt(\stream_get_contents($message, 4));
|
||||
$result['min_protocol'] = Tools::unpackSignedInt(\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'] = \stream_get_contents($message, 4);
|
||||
$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 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 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 VoIP::PKT_PONG:
|
||||
if (\fstat($payload)['size'] - \ftell($payload)) {
|
||||
$result['out_seq_no'] = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
}
|
||||
break;
|
||||
case 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 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 VoIP::PKT_LAN_ENDPOINT:
|
||||
$result['address'] = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$result['port'] = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
break;
|
||||
// packetNetworkChanged#B flags:# data_saving_enabled:flags.0?true = Packet;
|
||||
case VoIP::PKT_NETWORK_CHANGED:
|
||||
$result['data_saving_enabled'] = (bool) (\unpack('V', \stream_get_contents($payload, 4))[1] & 1);
|
||||
break;
|
||||
// packetSwitchPreferredRelay#C relay_id:long = Packet;
|
||||
case VoIP::PKT_SWITCH_PREF_RELAY:
|
||||
$result['relay_id'] = Tools::unpackSignedLong(\stream_get_contents($payload, 8));
|
||||
break;
|
||||
/*case \danog\MadelineProto\VoIP::PKT_SWITCH_TO_P2P:
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_NOP:
|
||||
break;*/
|
||||
default:
|
||||
Logger::log('Unknown packet received: '.$result['_'], Logger::ERROR);
|
||||
continue 2;
|
||||
}
|
||||
return $result;
|
||||
} while (true);
|
||||
}
|
||||
/**
|
||||
* Write data.
|
||||
*/
|
||||
public function write(string $payload): void
|
||||
public function write(string $payload): bool
|
||||
{
|
||||
if ($this->socket === null) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
$plaintext = \pack('v', \strlen($payload)).$payload;
|
||||
$padding = 16 - (\strlen($plaintext) % 16);
|
||||
@ -158,6 +374,15 @@ final class Endpoint
|
||||
}
|
||||
|
||||
$this->socket->write($payload);
|
||||
return true;
|
||||
}
|
||||
public function udpPing(): bool
|
||||
{
|
||||
if ($this->socket === null) {
|
||||
return false;
|
||||
}
|
||||
$this->socket->write($this->peerTag.Tools::packSignedLong(-1).Tools::packSignedInt(-1).Tools::packSignedInt(-2).Tools::random(8));
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Get peer tag.
|
||||
|
@ -15,9 +15,6 @@ If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
namespace danog\MadelineProto\VoIP;
|
||||
|
||||
use danog\MadelineProto\Lang;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\TL\Exception;
|
||||
use danog\MadelineProto\Tools;
|
||||
use danog\MadelineProto\VoIP;
|
||||
|
||||
@ -28,7 +25,7 @@ use danog\MadelineProto\VoIP;
|
||||
*/
|
||||
trait MessageHandler
|
||||
{
|
||||
public function pack_string($object)
|
||||
private static function pack_string(string $object): string
|
||||
{
|
||||
$l = \strlen($object);
|
||||
$concat = '';
|
||||
@ -45,33 +42,8 @@ trait MessageHandler
|
||||
|
||||
return $concat;
|
||||
}
|
||||
public function unpack_string($stream)
|
||||
private function encryptPacket(array $args, bool $increase_seqno = true): string
|
||||
{
|
||||
$l = \ord(\stream_get_contents($stream, 1));
|
||||
if ($l > 254) {
|
||||
throw new Exception(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 = Tools::posmod(-$long_len, 4);
|
||||
if ($resto > 0) {
|
||||
\stream_get_contents($stream, $resto);
|
||||
}
|
||||
} else {
|
||||
$x = \stream_get_contents($stream, $l);
|
||||
$resto = Tools::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, \reset($this->sockets));
|
||||
}
|
||||
$message = '';
|
||||
switch ($args['_']) {
|
||||
// streamTypeSimple codec:int8 = StreamType;
|
||||
@ -200,7 +172,7 @@ trait MessageHandler
|
||||
$payload .= \chr(0);
|
||||
$payload .= $message;
|
||||
} elseif (\in_array($this->voip_state, [VoIP::STATE_WAIT_INIT, VoIP::STATE_WAIT_INIT_ACK], true)) {
|
||||
$payload = $this->TLID_DECRYPTED_AUDIO_BLOCK;
|
||||
$payload = VoIP::TLID_DECRYPTED_AUDIO_BLOCK;
|
||||
$payload .= Tools::random(8);
|
||||
$payload .= \chr(7);
|
||||
$payload .= Tools::random(7);
|
||||
@ -225,7 +197,7 @@ trait MessageHandler
|
||||
$payload .= $this->pack_string($message);
|
||||
}
|
||||
} else {
|
||||
$payload = $this->TLID_SIMPLE_AUDIO_BLOCK;
|
||||
$payload = VoIP::TLID_SIMPLE_AUDIO_BLOCK;
|
||||
$payload .= Tools::random(8);
|
||||
$payload .= \chr(7);
|
||||
$payload .= Tools::random(7);
|
||||
@ -233,208 +205,10 @@ trait MessageHandler
|
||||
|
||||
$payload .= $this->pack_string($message);
|
||||
}
|
||||
$this->session_out_seq_no++;
|
||||
|
||||
return $datacenter->write($payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reading connection and receiving message from server.
|
||||
*/
|
||||
public function recv_message(Endpoint $endpoint)
|
||||
{
|
||||
if (!$payload = $endpoint->read()) {
|
||||
return null;
|
||||
if ($increase_seqno) {
|
||||
$this->session_out_seq_no++;
|
||||
}
|
||||
|
||||
$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']) {
|
||||
Logger::log('Call ID mismatch', Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($flags & 16) {
|
||||
$in_seq_no = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$out_seq_no = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
}
|
||||
if ($flags & 32) {
|
||||
$ack_mask = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
}
|
||||
if ($flags & 8) {
|
||||
if (\stream_get_contents($payload, 4) !== VoIP::PROTO_ID) {
|
||||
Logger::log('Protocol mismatch', Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if ($flags & 2) {
|
||||
$result['extra'] = $this->unpack_string($payload);
|
||||
}
|
||||
$message = \fopen('php://memory', 'rw+b');
|
||||
|
||||
if ($flags & 1) {
|
||||
\fwrite($message, $this->unpack_string($payload));
|
||||
\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($payload));
|
||||
\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'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
$result['query_id'] = Tools::unpackSignedLong(\stream_get_contents($payload, 8));
|
||||
$result['my_ip'] = \stream_get_contents($payload, 16);
|
||||
$result['my_port'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
return $result;
|
||||
case $this->TLID_REFLECTOR_PEER_INFO:
|
||||
$result['my_address'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
$result['my_port'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
$result['peer_address'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
$result['peer_port'] = Tools::unpackSignedInt(\stream_get_contents($payload, 4));
|
||||
return $result;
|
||||
default:
|
||||
if ($this->peerVersion >= 8 || (!$this->peerVersion)) {
|
||||
\fseek($payload, 0);
|
||||
$result['_'] = \ord(\stream_get_contents($payload, 1));
|
||||
$in_seq_no = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$out_seq_no = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$ack_mask = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$flags = \ord(\stream_get_contents($payload, 1));
|
||||
if ($flags & 1) {
|
||||
$result['extra'] = [];
|
||||
$count = \ord(\stream_get_contents($payload, 1));
|
||||
for ($x = 0; $x < $count; $x++) {
|
||||
$len = \ord(\stream_get_contents($payload, 1));
|
||||
$result['extra'][]= \stream_get_contents($payload, $len);
|
||||
}
|
||||
}
|
||||
$message = \fopen('php://memory', 'rw+b');
|
||||
|
||||
\fwrite($message, \stream_get_contents($payload));
|
||||
\fseek($message, 0);
|
||||
} else {
|
||||
Logger::log('Unknown packet received: '.\bin2hex($crc), Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!$this->received_packet($in_seq_no, $out_seq_no, $ack_mask)) {
|
||||
return $this->recv_message($endpoint);
|
||||
}
|
||||
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 VoIP::PKT_INIT:
|
||||
$result['protocol'] = $this->peerVersion = Tools::unpackSignedInt(\stream_get_contents($message, 4));
|
||||
$result['min_protocol'] = Tools::unpackSignedInt(\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] = \stream_get_contents($message, 4);
|
||||
}
|
||||
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 VoIP::PKT_INIT_ACK:
|
||||
$result['protocol'] = Tools::unpackSignedInt(\stream_get_contents($message, 4));
|
||||
$result['min_protocol'] = Tools::unpackSignedInt(\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'] = \stream_get_contents($message, 4);
|
||||
$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 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 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 VoIP::PKT_PONG:
|
||||
if (\fstat($payload)['size'] - \ftell($payload)) {
|
||||
$result['out_seq_no'] = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
}
|
||||
break;
|
||||
case 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 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 VoIP::PKT_LAN_ENDPOINT:
|
||||
$result['address'] = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
$result['port'] = \unpack('V', \stream_get_contents($payload, 4))[1];
|
||||
break;
|
||||
// packetNetworkChanged#B flags:# data_saving_enabled:flags.0?true = Packet;
|
||||
case VoIP::PKT_NETWORK_CHANGED:
|
||||
$result['data_saving_enabled'] = (bool) (\unpack('V', \stream_get_contents($payload, 4))[1] & 1);
|
||||
break;
|
||||
// packetSwitchPreferredRelay#C relay_id:long = Packet;
|
||||
case VoIP::PKT_SWITCH_PREF_RELAY:
|
||||
$result['relay_id'] = Tools::unpackSignedLong(\stream_get_contents($payload, 8));
|
||||
break;
|
||||
/*case \danog\MadelineProto\VoIP::PKT_SWITCH_TO_P2P:
|
||||
break;
|
||||
case \danog\MadelineProto\VoIP::PKT_NOP:
|
||||
break;*/
|
||||
}
|
||||
return $result;
|
||||
return $payload;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user