MadelineProto/src/danog/MadelineProto/VoIP/MessageHandler.php

434 lines
22 KiB
PHP

<?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;
use danog\MadelineProto\TL\Exception;
use danog\MadelineProto\Tools;
/**
* 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('@'.Tools::posmod(-$l - 1, 4));
} else {
$concat .= \chr(254);
$concat .= \substr(Tools::packSignedInt($l), 0, 3);
$concat .= $object;
$concat .= \pack('@'.Tools::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 = 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;
//
// 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 .= Tools::packSignedInt($args['protocol']);
$message .= Tools::packSignedInt($args['min_protocol']);
$flags = 0;
$flags = isset($args['data_saving_enabled']) && $args['data_saving_enabled'] ? $flags | 1 : $flags & ~1;
$message .= Tools::packUnsignedInt($flags);
$message .= \chr(\count($args['audio_streams']));
foreach ($args['audio_streams'] as $codec) {
$message .= $codec;
}
$message .= chr(0);
$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 .= Tools::packSignedInt($args['protocol']);
$message .= Tools::packSignedInt($args['min_protocol']);
$message .= \chr(\count($args['all_streams']));
foreach ($args['all_streams'] as $stream) {
$message .= \chr($stream['id']);
$message .= \chr($stream['type']);
$message .= $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 .= Tools::packUnsignedInt($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 .= Tools::packUnsignedInt($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 .= Tools::packUnsignedInt($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 .= Tools::packUnsignedInt($args[$x]['timestamp']);
$message .= $args[$x]['data'];
}
break;
// packetLanEndpoint#A address:int port:int = Packet;
case \danog\MadelineProto\VoIP::PKT_LAN_ENDPOINT:
$message .= Tools::packSignedInt($args['address']);
$message .= Tools::packSignedInt($args['port']);
break;
// packetNetworkChanged#B flags:# data_saving_enabled:flags.0?true = Packet;
case \danog\MadelineProto\VoIP::PKT_NETWORK_CHANGED:
$message .= Tools::packSignedInt(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 .= Tools::packSignedLong($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 ($this->peerVersion >= 8 || (!$this->peerVersion && true)) {
$payload = \chr($args['_']);
$payload .= Tools::packUnsignedInt($this->session_in_seq_no);
$payload .= Tools::packUnsignedInt($this->session_out_seq_no);
$payload .= Tools::packUnsignedInt($ack_mask);
$payload .= \chr(0);
$payload .= $message;
} elseif (\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 .= Tools::random(8);
$payload .= \chr(7);
$payload .= Tools::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 .= Tools::packUnsignedInt($flags);
$payload .= $this->configuration['call_id'];
$payload .= Tools::packUnsignedInt($this->session_in_seq_no);
$payload .= Tools::packUnsignedInt($this->session_out_seq_no);
$payload .= Tools::packUnsignedInt($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 .= Tools::random(8);
$payload .= \chr(7);
$payload .= Tools::random(7);
$message = \chr($args['_']).Tools::packUnsignedInt($this->session_in_seq_no).Tools::packUnsignedInt($this->session_out_seq_no).Tools::packUnsignedInt($ack_mask).$message;
$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 = yield from $endpoint->read()) {
return null;
}
$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($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) !== \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($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 && true)) {
\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 {
\danog\MadelineProto\Logger::log('Unknown packet received: '.\bin2hex($crc), \danog\MadelineProto\Logger::ERROR);
return false;
}
}
if (!$this->received_packet($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->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 \danog\MadelineProto\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 \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($payload)['size'] - \ftell($payload)) {
$result['out_seq_no'] = \unpack('V', \stream_get_contents($payload, 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($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 \danog\MadelineProto\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 \danog\MadelineProto\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;
}
}