From 016bb106f6be7305492df5ea1d3916aa1ee3d04e Mon Sep 17 00:00:00 2001 From: danogentili Date: Wed, 14 Sep 2016 00:14:50 +0200 Subject: [PATCH] Rewrote request/response module --- src/danog/MadelineProto/API.php | 1 - src/danog/MadelineProto/Crypt.php | 77 ------------- src/danog/MadelineProto/MTProto.php | 12 +- .../MadelineProto/MTProtoTools/AckHandler.php | 16 +-- .../MTProtoTools/AuthKeyHandler.php | 4 +- .../MTProtoTools/CallHandler.php | 47 ++++++-- .../MadelineProto/MTProtoTools/Crypt.php | 62 +++++++++++ .../MadelineProto/MTProtoTools/Exception.php | 40 +++++++ .../MTProtoTools/MessageHandler.php | 13 +-- .../MTProtoTools/MsgIdHandler.php | 14 +-- .../MTProtoTools/ResponseHandler.php | 103 +++++++++--------- src/danog/MadelineProto/TL/TL.php | 7 -- 12 files changed, 216 insertions(+), 180 deletions(-) delete mode 100644 src/danog/MadelineProto/Crypt.php create mode 100644 src/danog/MadelineProto/MTProtoTools/Exception.php diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php index aa5c72c8..5873278c 100644 --- a/src/danog/MadelineProto/API.php +++ b/src/danog/MadelineProto/API.php @@ -21,7 +21,6 @@ class API set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); $this->session = new MTProto($params); $future_salts = $this->get_future_salts(3); - $future_salts = $this->get_future_salts(3); } public function __destruct() diff --git a/src/danog/MadelineProto/Crypt.php b/src/danog/MadelineProto/Crypt.php deleted file mode 100644 index d58cb112..00000000 --- a/src/danog/MadelineProto/Crypt.php +++ /dev/null @@ -1,77 +0,0 @@ -. -*/ - -namespace danog\MadelineProto; - -class Crypt -{ - public static function ige_encrypt($message, $key, $iv) - { - return self::_ige($message, $key, $iv, 'encrypt'); - } - - public static function ige_decrypt($message, $key, $iv) - { - return self::_ige($message, $key, $iv, 'decrypt'); - } - - /** - * Given a key, given an iv, and message - * do whatever operation asked in the operation field. - * Operation will be checked for: "decrypt" and "encrypt" strings. - * Returns the message encrypted/decrypted. - * message must be a multiple by 16 bytes (for division in 16 byte blocks) - * key must be 32 byte - * iv must be 32 byte (it's not internally used in AES 256 ECB, but it's - * needed for IGE). - */ - public static function _ige($message, $key, $iv, $operation = 'decrypt') - { - if (strlen($key) != 32) { - throw new Exception('key must be 32 bytes long (was '.strlen($key).' bytes)'); - } - if (strlen($iv) != 32) { - throw new Exception('iv must be 32 bytes long (was '.strlen($iv).' bytes)'); - } - $cipher = new \phpseclib\Crypt\AES(\phpseclib\Crypt\AES::MODE_ECB); - $cipher->setKey($key); - $cipher->paddable = false; - $blocksize = $cipher->block_size; - if ((strlen($message) % $blocksize) != 0) { - throw new Exception('message must be a multiple of 16 bytes (try adding '.(16 - (strlen($message) % 16)).' bytes of padding)'); - } - $ivp = substr($iv, 0, $blocksize); - $ivp2 = substr($iv, $blocksize); - $ciphered = ''; - foreach (Tools::range(0, strlen($message), $blocksize) as $i) { - $indata = substr($message, $i, $blocksize); - if ($operation == 'decrypt') { - $xored = $indata ^ $ivp2; - $decrypt_xored = $cipher->decrypt($xored); - $outdata = $decrypt_xored ^ $ivp; - $ivp = $indata; - $ivp2 = $outdata; - } elseif ($operation == 'encrypt') { - $xored = $indata ^ $ivp; - $encrypt_xored = $cipher->encrypt($xored); - $outdata = $encrypt_xored ^ $ivp2; - $ivp = $outdata; - $ivp2 = $indata; - } else { - throw new Exception('Crypt: operation must be either \'decrypt\' or \'encrypt\''); - } - $ciphered .= $outdata; - } - - return $ciphered; - } -} diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index 29bd4729..d66c1198 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -36,7 +36,6 @@ Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB -----END RSA PUBLIC KEY-----', - 'message_ids_limit' => 5, ], 'connection' => [ 'ip_address' => '149.154.167.50', @@ -62,7 +61,12 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB 'max_tries' => [ 'query' => 5, 'authorization' => 5, + 'response' => 5 ], + 'msg_array_limit' => [ + 'incoming' => 30, + 'outgoing' => 30, + ] ]; foreach ($default_settings as $key => $param) { if (!isset($settings[$key])) { @@ -92,10 +96,8 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB $this->seq_no = 0; $this->timedelta = 0; // time delta - $this->incoming_message_ids = []; - $this->outgoing_message_ids = []; - $this->ack_incoming_message_ids = []; - $this->ack_outgoing_message_ids = []; + $this->incoming_messages = []; + $this->outgoing_messages = []; $this->future_salts = []; if ($this->settings['authorization']['temp_auth_key'] == null || $this->settings['authorization']['auth_key'] == null) { diff --git a/src/danog/MadelineProto/MTProtoTools/AckHandler.php b/src/danog/MadelineProto/MTProtoTools/AckHandler.php index e43991f8..d6bc2bcd 100644 --- a/src/danog/MadelineProto/MTProtoTools/AckHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/AckHandler.php @@ -20,13 +20,10 @@ class AckHandler extends \danog\MadelineProto\Tools public function ack_outgoing_message_id($message_id) { // The server acknowledges that it received my message - if (!in_array($message_id, $this->outgoing_message_ids)) { - throw new Exception("Couldn't find message id ".$message_id.' in the array of outgoing message ids. Maybe try to increase its size?'); - } - $this->ack_outgoing_message_ids[] = $message_id; - if (count($this->ack_outgoing_message_ids) > $this->settings['authorization']['message_ids_limit']) { - array_shift($this->ack_outgoing_message_ids); + if (!isset($this->outgoing_messages[$message_id])) { + throw new Exception("Couldn't find message id ".$message_id.' in the array of outgoing messages. Maybe try to increase its size?'); } + $this->outgoing_messages[$message_id]['ack'] = true; } public function ack_incoming_message_id($message_id) @@ -35,13 +32,10 @@ class AckHandler extends \danog\MadelineProto\Tools return; } // I let the server know that I received its message - if (!in_array($message_id, $this->incoming_message_ids)) { + if (!isset($this->incoming_messages[$message_id])) { throw new Exception("Couldn't find message id ".$message_id.' in the array of incoming message ids. Maybe try to increase its size?'); } $this->object_call('msgs_ack', ['msg_ids' => [$message_id]]); - $this->ack_incoming_message_ids[] = $message_id; - if (count($this->ack_incoming_message_ids) > $this->settings['authorization']['message_ids_limit']) { - array_shift($this->ack_incoming_message_ids); - } + $this->incoming_messages[$message_id]['ack'] = true; } } diff --git a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php index dbd1bd09..55adefc9 100644 --- a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php @@ -98,7 +98,7 @@ class AuthKeyHandler extends AckHandler $encrypted_answer = $server_dh_params['encrypted_answer']; $tmp_aes_key = sha1($new_nonce.$server_nonce, true).substr(sha1($server_nonce.$new_nonce, true), 0, 12); $tmp_aes_iv = substr(sha1($server_nonce.$new_nonce, true), 12, 8).sha1($new_nonce.$new_nonce, true).substr($new_nonce, 0, 4); - $answer_with_hash = \danog\MadelineProto\Crypt::ige_decrypt($encrypted_answer, $tmp_aes_key, $tmp_aes_iv); + $answer_with_hash = $this->ige_decrypt($encrypted_answer, $tmp_aes_key, $tmp_aes_iv); // Separate answer and hash $answer_hash = substr($answer_with_hash, 0, 20); @@ -187,7 +187,7 @@ class AuthKeyHandler extends AckHandler $data_with_sha_padded = $data_with_sha.\phpseclib\Crypt\Random::string(\danog\MadelineProto\Tools::posmod(-strlen($data_with_sha), 16)); // encrypt client_DH_inner_data - $encrypted_data = \danog\MadelineProto\Crypt::ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv); + $encrypted_data = $this->ige_encrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv); // Send set_client_DH_params query $Set_client_DH_params_answer = $this->method_call('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data]); diff --git a/src/danog/MadelineProto/MTProtoTools/CallHandler.php b/src/danog/MadelineProto/MTProtoTools/CallHandler.php index 7109fba3..b8b6f052 100644 --- a/src/danog/MadelineProto/MTProtoTools/CallHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/CallHandler.php @@ -17,41 +17,64 @@ namespace danog\MadelineProto\MTProtoTools; */ class CallHandler extends AuthKeyHandler { + public function wait_for_response($res_id) { + $response = null; + $count = 0; + while ($response == null && $count++ < $this->settings['max_tries']['response']) { + $server_answer = $this->recv_message(); + $deserialized = $this->tl->deserialize(\danog\MadelineProto\Tools::fopen_and_write('php://memory', 'rw+b', $server_answer)); + $tempres = $this->handle_message($deserialized); + if ($tempres == null) { + if (isset($this->outgoing_messages[$res_id]["response"])) { + $response = $this->outgoing_messages[$res_id]["response"]; + } + } else { + $response = $tempres; + } + } + switch ($response["_"]) { + case 'rpc_error': + throw new Exception('Got rpc error '.$response['error_code'].': '.$response['error_message']); + break; + default: + return $response; + break; + } + } public function method_call($method, $args) { - $opts = $this->tl->get_opts($method); foreach (range(1, $this->settings['max_tries']['query']) as $i) { try { - $this->send_message($this->tl->serialize_method($method, $args), $this->tl->content_related($method)); - if ($opts['requires_answer'] || true) { - $server_answer = $this->recv_message(); - } + $int_message_id = $this->send_message($this->tl->serialize_method($method, $args), $this->tl->content_related($method)); + $this->outgoing_messages[$int_message_id]["method"] = $method; + $this->outgoing_messages[$int_message_id]["args"] = $args; + $server_answer = $this->wait_for_response($int_message_id); } catch (Exception $e) { $this->log->log('An error occurred while calling method '.$method.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call method...'); unset($this->sock); - $this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']); + $this->sock = new \danog\MadelineProto\Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']); continue; } if ($server_answer == null) { throw new Exception('An error occurred while calling method '.$method.'.'); } - $deserialized = $this->tl->deserialize(\danog\MadelineProto\Tools::fopen_and_write('php://memory', 'rw+b', $server_answer)); - - return $this->handle_response($deserialized, $method, $args); + return $server_answer; } throw new Exception('An error occurred while calling method '.$method.'.'); } - public function object_call($object, $kwargs) + public function object_call($object, $args) { foreach (range(1, $this->settings['max_tries']['query']) as $i) { try { - $this->send_message($this->tl->serialize_obj($object, $kwargs), $this->tl->content_related($object)); + $int_message_id = $this->send_message($this->tl->serialize_obj($object, $args), $this->tl->content_related($object)); // $server_answer = $this->recv_message(); + $this->outgoing_messages[$int_message_id]["method"] = $object; + $this->outgoing_messages[$int_message_id]["args"] = $args; } catch (Exception $e) { $this->log->log('An error occurred while calling object '.$object.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call object...'); unset($this->sock); - $this->sock = new Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']); + $this->sock = new \danog\MadelineProto\Connection($this->settings['connection']['ip_address'], $this->settings['connection']['port'], $this->settings['connection']['protocol']); continue; } diff --git a/src/danog/MadelineProto/MTProtoTools/Crypt.php b/src/danog/MadelineProto/MTProtoTools/Crypt.php index ed63fda6..39a251ff 100644 --- a/src/danog/MadelineProto/MTProtoTools/Crypt.php +++ b/src/danog/MadelineProto/MTProtoTools/Crypt.php @@ -26,4 +26,66 @@ class Crypt extends CallHandler return [$aes_key, $aes_iv]; } + + public function ige_encrypt($message, $key, $iv) + { + return $this->_ige($message, $key, $iv, 'encrypt'); + } + + public function ige_decrypt($message, $key, $iv) + { + return $this->_ige($message, $key, $iv, 'decrypt'); + } + + /** + * Given a key, given an iv, and message + * do whatever operation asked in the operation field. + * Operation will be checked for: "decrypt" and "encrypt" strings. + * Returns the message encrypted/decrypted. + * message must be a multiple by 16 bytes (for division in 16 byte blocks) + * key must be 32 byte + * iv must be 32 byte (it's not internally used in AES 256 ECB, but it's + * needed for IGE). + */ + public function _ige($message, $key, $iv, $operation = 'decrypt') + { + if (strlen($key) != 32) { + throw new Exception('key must be 32 bytes long (was '.strlen($key).' bytes)'); + } + if (strlen($iv) != 32) { + throw new Exception('iv must be 32 bytes long (was '.strlen($iv).' bytes)'); + } + $cipher = new \phpseclib\Crypt\AES(\phpseclib\Crypt\AES::MODE_ECB); + $cipher->setKey($key); + $cipher->paddable = false; + $blocksize = $cipher->block_size; + if ((strlen($message) % $blocksize) != 0) { + throw new Exception('message must be a multiple of 16 bytes (try adding '.(16 - (strlen($message) % 16)).' bytes of padding)'); + } + $ivp = substr($iv, 0, $blocksize); + $ivp2 = substr($iv, $blocksize); + $ciphered = ''; + foreach (\danog\MadelineProto\Tools::range(0, strlen($message), $blocksize) as $i) { + $indata = substr($message, $i, $blocksize); + if ($operation == 'decrypt') { + $xored = $indata ^ $ivp2; + $decrypt_xored = $cipher->decrypt($xored); + $outdata = $decrypt_xored ^ $ivp; + $ivp = $indata; + $ivp2 = $outdata; + } elseif ($operation == 'encrypt') { + $xored = $indata ^ $ivp; + $encrypt_xored = $cipher->encrypt($xored); + $outdata = $encrypt_xored ^ $ivp2; + $ivp = $outdata; + $ivp2 = $indata; + } else { + throw new Exception('Crypt: operation must be either \'decrypt\' or \'encrypt\''); + } + $ciphered .= $outdata; + } + + return $ciphered; + } + } diff --git a/src/danog/MadelineProto/MTProtoTools/Exception.php b/src/danog/MadelineProto/MTProtoTools/Exception.php new file mode 100644 index 00000000..2ad547b2 --- /dev/null +++ b/src/danog/MadelineProto/MTProtoTools/Exception.php @@ -0,0 +1,40 @@ +. +*/ + +namespace danog\MadelineProto\MTProtoTools; + +class Exception extends \Exception +{ + public function __construct($message, $code = 0, Exception $previous = null) + { + // some code + if (isset($GLOBALS['doingphptests']) && $GLOBALS['doingphptests']) { + var_dump($message); + } + // make sure everything is assigned properly + parent::__construct($message, $code, $previous); + } + + /** + * ExceptionErrorHandler. + * + * Error handler + */ + public static function ExceptionErrorHandler($errno = 0, $errstr = null, $errfile = null, $errline = null) + { + // If error is suppressed with @, don't throw an exception + if (error_reporting() === 0) { + return true; // return true to continue through the others error handlers + } + throw new self($errstr.' on line '.$errline.' of file '.$errfile, $errno); + } +} diff --git a/src/danog/MadelineProto/MTProtoTools/MessageHandler.php b/src/danog/MadelineProto/MTProtoTools/MessageHandler.php index 8ae9781a..cc620e76 100644 --- a/src/danog/MadelineProto/MTProtoTools/MessageHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/MessageHandler.php @@ -13,7 +13,7 @@ If not, see . namespace danog\MadelineProto\MTProtoTools; /** - * Manages packing and unpacking of messages. + * Manages packing and unpacking of messages, and the list of sent and received messages. */ class MessageHandler extends Crypt { @@ -28,17 +28,17 @@ class MessageHandler extends Crypt $this->check_message_id($int_message_id, true); if (($this->settings['authorization']['temp_auth_key']['auth_key'] == null) || ($this->settings['authorization']['temp_auth_key']['server_salt'] == null)) { $message = \danog\MadelineProto\Tools::string2bin('\x00\x00\x00\x00\x00\x00\x00\x00').$message_id.$this->struct->pack('last_sent = ['message_id' => $int_message_id]; } else { $seq_no = $this->generate_seq_no($content_related); $encrypted_data = $this->settings['authorization']['temp_auth_key']['server_salt'].$this->settings['authorization']['session_id'].$message_id.$this->struct->pack('aes_calculate($message_key); - $message = $this->settings['authorization']['temp_auth_key']['id'].$message_key.\danog\MadelineProto\Crypt::ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv); - $this->last_sent = ['message_id' => $int_message_id, 'seq_no' => $seq_no]; + $message = $this->settings['authorization']['temp_auth_key']['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv); + $this->outgoing_messages[$int_message_id]['seq_no'] = $seq_no; } $this->sock->send_message($message); + return $int_message_id; } /** @@ -55,12 +55,11 @@ class MessageHandler extends Crypt list($message_id, $message_length) = $this->struct->unpack('check_message_id($message_id, false); $message_data = fread($payload, $message_length); - $this->last_received = ['message_id' => $message_id]; } elseif ($auth_key_id == $this->settings['authorization']['temp_auth_key']['id']) { $message_key = fread($payload, 16); $encrypted_data = stream_get_contents($payload); list($aes_key, $aes_iv) = $this->aes_calculate($message_key, 'from server'); - $decrypted_data = \danog\MadelineProto\Crypt::ige_decrypt($encrypted_data, $aes_key, $aes_iv); + $decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv); $server_salt = substr($decrypted_data, 0, 8); if ($server_salt != $this->settings['authorization']['temp_auth_key']['server_salt']) { @@ -100,7 +99,7 @@ class MessageHandler extends Crypt if ($message_key != substr(sha1(substr($decrypted_data, 0, 32 + $message_data_length), true), -16)) { throw new Exception('msg_key mismatch'); } - $this->last_received = ['message_id' => $message_id, 'seq_no' => $seq_no]; + $this->incoming_messages[$message_id]['seq_no'] = $seq_no; } else { throw new Exception('Got unknown auth_key id'); } diff --git a/src/danog/MadelineProto/MTProtoTools/MsgIdHandler.php b/src/danog/MadelineProto/MTProtoTools/MsgIdHandler.php index 05c4aaa7..2e0db13c 100644 --- a/src/danog/MadelineProto/MTProtoTools/MsgIdHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/MsgIdHandler.php @@ -29,22 +29,22 @@ class MsgIdHandler extends MessageHandler if ($new_message_id % 4 != 0) { throw new Exception('Given message id ('.$new_message_id.') is not divisible by 4.'); } - $this->outgoing_message_ids[] = $new_message_id; - if (count($this->outgoing_message_ids) > $this->settings['authorization']['message_ids_limit']) { - array_shift($this->outgoing_message_ids); + $this->outgoing_messages[$new_message_id] = []; + if (count($this->outgoing_messages) > $this->settings['msg_array_limit']['outgoing']) { + array_shift($this->outgoing_messages); } } else { if ($new_message_id % 4 != 1 && $new_message_id % 4 != 3) { throw new Exception('message id mod 4 != 1 or 3'); } - foreach ($this->incoming_message_ids as $message_id) { + foreach (array_keys($this->incoming_messages) as $message_id) { if ($new_message_id <= $message_id) { throw new Exception('Given message id ('.$new_message_id.') is lower than or equal than the current limit ('.$message_id.').'); } } - $this->incoming_message_ids[] = $new_message_id; - if (count($this->incoming_message_ids) > $this->settings['authorization']['message_ids_limit']) { - array_shift($this->incoming_message_ids); + $this->incoming_messages[$new_message_id] = []; + if (count($this->incoming_messages) > $this->settings['msg_array_limit']['incoming']) { + array_shift($this->incoming_messages); } } } diff --git a/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php b/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php index f2cba42c..a4525dbc 100644 --- a/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php @@ -17,59 +17,46 @@ namespace danog\MadelineProto\MTProtoTools; */ class ResponseHandler extends MsgIdHandler { - public function handle_response($response, $name, $args) + public function handle_message($response) { switch ($response['_']) { - case 'rpc_result': - $this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response - $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my - if ($response['req_msg_id'] != $this->last_sent['message_id']) { - throw new Exception('Message id mismatch; req_msg_id ('.$response['req_msg_id'].') != last sent msg id ('.$this->last_sent['message_id'].').'); - } - - return $this->handle_response($response['result'], $name, $args); - break; - case 'rpc_error': - throw new Exception('Got rpc error '.$response['error_code'].': '.$response['error_message']); - break; - case 'rpc_answer_unknown': - $this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message - return $response; // I'm not handling this error - break; - case 'rpc_answer_dropped_running': - $this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response - $this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message - - $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget) - return $response; // I'm not handling this - break; - case 'rpc_answer_dropped': - $this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response - $this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message - - $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget) - return $response; // I'm not handling this - break; - case 'future_salts': - $this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message - if ($response['req_msg_id'] != $this->last_sent['message_id']) { - throw new Exception('Message id mismatch; req_msg_id ('.$response['req_msg_id'].') != last sent msg id ('.$this->last_sent['message_id'].').'); - } - $this->log->log('Received future salts.'); - $this->future_salts = $response['salts']; - break; - case 'pong': - $this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response - $this->ack_outgoing_message_id($this->last_sent['message_id']); // Acknowledge that the server received my message - $this->log->log('pong'); - break; case 'msgs_ack': foreach ($response['msg_ids'] as $msg_id) { $this->ack_outgoing_message_id($msg_id); // Acknowledge that the server received my message } break; + + case 'rpc_result': + end($this->incoming_messages); + end($this->outgoing_messages); + $this->ack_incoming_message_id(key($this->incoming_messages)); // Acknowledge that I received the server's response + $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my request + if ($response['req_msg_id'] != key($this->outgoing_messages)) { + throw new Exception('Message id mismatch; req_msg_id ('.$response['req_msg_id'].') != last sent msg id ('.key($this->outgoing_messages).').'); + } + return $this->handle_response($response['result'], $response['req_msg_id']); + break; + + case 'future_salts': + end($this->outgoing_messages); + $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my request + if ($response['req_msg_id'] != key($this->outgoing_messages)) { + throw new Exception('Message id mismatch; req_msg_id ('.$response['req_msg_id'].') != last sent msg id ('.key($this->outgoing_messages).').'); + } + $this->log->log('Received future salts.'); + return $this->handle_response($response, $response['req_msg_id']); + break; + + case 'pong': + foreach ($this->outgoing_messages as $omessage) { + if ($omessage["args"]["ping_id"] == $response["ping_id"]) { + $this->outgoing_messages[$response["msg_id"]]["response"] = $response; + } + } + break; case 'new_session_created': - $this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response + end($this->incoming_messages); + $this->ack_incoming_message_id(key($this->incoming_messages)); // Acknowledge that I received the server's response $this->log->log('new session created'); $this->log->log($response); break; @@ -78,17 +65,17 @@ class ResponseHandler extends MsgIdHandler $this->log->log('Received container.'); $this->log->log($response['messages']); foreach ($response['messages'] as $message) { - $this->last_recieved = ['message_id' => $message['msg_id'], 'seq_no' => $message['seqno']]; - $responses[] = $this->handle_response($message['body'], $name, $args); + $this->incoming_messages[$message['msg_id']] = [ 'seq_no' => $message['seqno']]; + $responses[] = $this->handle_message($message['body']); } foreach ($responses as $response) { - if ($response != null) { + if ($response !== null) { return $response; } } break; case 'msg_copy': - $this->handle_response($response['orig_message'], $name, $args); + return $this->handle_response($response['orig_message']); break; case 'gzip_packed': return $this->handle_response(gzdecode($response)); @@ -98,8 +85,22 @@ class ResponseHandler extends MsgIdHandler $this->log->log($response); break; default: - $this->ack_incoming_message_id($this->last_received['message_id']); // Acknowledge that I received the server's response - return $response; + end($this->incoming_messages); + $this->ack_incoming_message_id(key($this->incoming_messages)); // Acknowledge that I received the server's response + return $this->handle_response($response); + break; + } + } + public function handle_response($response, $res_id = null) { + if ($res_id == null) { + return $response; + } + switch ($response['_']) { + case 'rpc_answer_dropped_running': + case 'rpc_answer_dropped': + $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget) + default: + $this->outgoing_messages[$res_id]["response"] = $response; break; } } diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index c5ce14ca..50439ad1 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -263,11 +263,4 @@ class TL 'msg_resend_ans_req', ]); } - - public function get_opts($method) - { - $opts = ['requires_answer' => !in_array($method, [ - 'msgs_ack', - ])]; - } }