. */ 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 video_streams:byteVector = 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 = 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 video_streams:byteVector = 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 = 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; } }