diff --git a/README.md b/README.md index 3a8b0916..94e689e4 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ Created by [Daniil Gentili](https://daniil.it), licensed under AGPLv3. -![MadelineProto logo](https://daniil.it/MadelineProto/logo.png) +MadelineProto logo -Logo created by [Matthew Hesketh](https://telegram.me/wrxck) (thanks again!). +Logo created by [Matthew Hesketh](https://matthewhesketh.com) (thanks again!). PHP implementation of MTProto, based on [telepy](https://github.com/griganton/telepy_old). diff --git a/docs/index.md b/docs/index.md index b66a0ea9..e7f59d70 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,9 +8,9 @@ description: PHP implementation of telegram's MTProto protocol Created by [Daniil Gentili](https://daniil.it), licensed under AGPLv3. -![MadelineProto logo](https://daniil.it/MadelineProto/logo.png) +MadelineProto logo -Logo created by [Matthew Hesketh](https://telegram.me/wrxck) (thanks again!). +Logo created by [Matthew Hesketh](https://matthewhesketh.com) (thanks again!). PHP implementation of MTProto, based on [telepy](https://github.com/griganton/telepy_old). diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index fa1b6104..70856d1d 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -28,23 +28,8 @@ class MTProto extends PrimeModule use \danog\MadelineProto\MTProtoTools\SeqNoHandler; public $settings = []; - public $authorized = false; - public $waiting_code = false; public $config = ['expires' => -1]; public $ipv6 = false; - public $bad_msg_error_codes = [ - 16 => 'msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)', - 17 => 'msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)', - 18 => 'incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)', - 19 => 'container msg_id is the same as msg_id of a previously received message (this must never happen)', - 20 => 'message too old, and it cannot be verified whether the server has received a message with this msg_id or not', - 32 => 'msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)', - 33 => 'msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)', - 34 => 'an even msg_seqno expected (irrelevant message), but odd received', - 35 => 'odd msg_seqno expected (relevant message), but even received', - 48 => 'incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)', - 64 => 'invalid container.', - ]; public function __construct($settings = []) { @@ -238,6 +223,10 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB \danog\MadelineProto\Logger::log('Resetting session id and seq_no in DC '.$id.'...'); $socket->session_id = \phpseclib\Crypt\Random::string(8); $socket->seq_no = 0; + $socket->incoming_messages = []; + $socket->outgoing_messages = []; + $socket->new_outgoing = []; + $socket->new_incoming = []; } } diff --git a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php index 097afe3f..30ace3d4 100644 --- a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php @@ -469,6 +469,9 @@ trait AuthKeyHandler \danog\MadelineProto\Logger::log('An exception occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...'); } catch (\danog\MadelineProto\RPCErrorException $e) { \danog\MadelineProto\Logger::log('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...'); + } finally { + $this->datacenter->new_outgoing = []; + $this->datacenter->new_incoming = []; } } diff --git a/src/danog/MadelineProto/MTProtoTools/CallHandler.php b/src/danog/MadelineProto/MTProtoTools/CallHandler.php index 1ebf8cb4..0cd23c5c 100644 --- a/src/danog/MadelineProto/MTProtoTools/CallHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/CallHandler.php @@ -115,15 +115,15 @@ trait CallHandler try { \danog\MadelineProto\Logger::log('Sending object (try number '.$count.' for '.$object.')...'); $int_message_id = $this->send_message($this->tl->serialize_object(['type' => $object], $args), $this->tl->content_related($object)); - $this->datacenter->outgoing_messages[$int_message_id]['content'] = ['object' => $object, 'args' => $args]; + $this->datacenter->outgoing_messages[$int_message_id]['content'] = ['method' => $object, 'args' => $args]; } catch (Exception $e) { \danog\MadelineProto\Logger::log('An error occurred while calling object '.$object.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call object...'); $this->datacenter->close_and_reopen(); continue; } - return; + return $int_message_id; } - throw new \danog\MadelineProto\Exception('An error occurred while calling object '.$object.'.'); + throw new \danog\MadelineProto\Exception('An error occurred while sending object '.$object.'.'); } } diff --git a/src/danog/MadelineProto/MTProtoTools/MessageHandler.php b/src/danog/MadelineProto/MTProtoTools/MessageHandler.php index 5dfd280e..f7692f31 100644 --- a/src/danog/MadelineProto/MTProtoTools/MessageHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/MessageHandler.php @@ -123,6 +123,7 @@ trait MessageHandler } $deserialized = $this->tl->deserialize($this->fopen_and_write('php://memory', 'rw+b', $message_data)); $this->datacenter->incoming_messages[$message_id]['content'] = $deserialized; + $this->datacenter->incoming_messages[$message_id]['response'] = -1; $this->datacenter->new_incoming[$message_id] = $message_id; } } diff --git a/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php b/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php index 9cbcec0e..95b5d8f4 100644 --- a/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php @@ -17,10 +17,58 @@ namespace danog\MadelineProto\MTProtoTools; */ trait ResponseHandler { + + private $bad_msg_error_codes = [ + 16 => 'msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct” msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)', + 17 => 'msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)', + 18 => 'incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)', + 19 => 'container msg_id is the same as msg_id of a previously received message (this must never happen)', + 20 => 'message too old, and it cannot be verified whether the server has received a message with this msg_id or not', + 32 => 'msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)', + 33 => 'msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)', + 34 => 'an even msg_seqno expected (irrelevant message), but odd received', + 35 => 'odd msg_seqno expected (relevant message), but even received', + 48 => 'incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)', + 64 => 'invalid container.', + ]; + private $msgs_info_flags = [ + 1 => 'nothing is known about the message (msg_id too low, the other party may have forgotten it)', + 2 => 'message not received (msg_id falls within the range of stored identifiers; however, the other party has certainly not received a message like that)', + 3 => 'message not received (msg_id too high; however, the other party has certainly not received it yet)', + 4 => 'message received (note that this response is also at the same time a receipt acknowledgment)', + 8 => ' and message already acknowledged', + 16 => ' and message not requiring acknowledgment', + 32 => ' and RPC query contained in message being processed or processing already complete', + 64 => ' and content-related response to message already generated', + 128 => ' and other party knows for a fact that message is already received', + ]; + public function send_msgs_state_info($req_msg_id, $msg_ids) { + $info = ''; + foreach ($msg_ids as $msg_id) { + $cur_info = 0; + if (!in_array($msg_id, $this->datacenter->incoming_messages)) { + if (((int) ((time() + $this->datacenter->time_delta + 30) << 32)) < $msg_id) { + $cur_info |= 3; + } else if (((int) ((time() + $this->datacenter->time_delta - 300) << 32)) > $msg_id) { + $cur_info |= 1; + } else { + $cur_info |= 2; + } + } else { + $cur_info |= 4; + if ($this->datacenter->incoming_messages[$msg_id]['ack']) { + $cur_info |= 8; + } + } + $info .= chr($cur_info); + } + $this->datacenter->outgoing_messages[$this->object_call('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info])]['response'] = $req_msg_id; + } public function handle_messages() { foreach ($this->datacenter->new_incoming as $current_msg_id) { $response = $this->datacenter->incoming_messages[$current_msg_id]['content']; + \danog\MadelineProto\Logger::log('Received '.$response['_'].'.'); switch ($response['_']) { case 'msgs_ack': foreach ($response['msg_ids'] as $msg_id) { @@ -66,7 +114,7 @@ trait ResponseHandler unset($this->datacenter->new_incoming[$current_msg_id]); break; case 'msg_container': - \danog\MadelineProto\Logger::log('Received container.'); + \danog\MadelineProto\Logger::log($response['messages']); unset($this->datacenter->new_incoming[$current_msg_id]); foreach ($response['messages'] as $message) { @@ -91,24 +139,84 @@ trait ResponseHandler unset($this->datacenter->new_incoming[$current_msg_id]); break; case 'http_wait': - \danog\MadelineProto\Logger::log('Received http wait.'); + \danog\MadelineProto\Logger::log($response); unset($this->datacenter->new_incoming[$current_msg_id]); break; - 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) + case 'msgs_state_info': + $this->datacenter->outgoing_messages[$response['req_msg_id']]['response'] = $current_msg_id; + unset($this->datacenter->new_incoming[$current_msg_id]); + unset($this->datacenter->new_outgoing[$response['req_msg_id']]); + break; + case 'msgs_state_req': + unset($this->datacenter->new_incoming[$current_msg_id]); + $this->send_msgs_state_info($current_msg_id, $response['msg_ids']); + break; + case 'msgs_all_info': + unset($this->datacenter->new_incoming[$current_msg_id]); + + foreach ($response['msg_ids'] as $key => $msg_id) { + $status = 'Status for message id '.$msg_id.': '; + if (($response['info'][$key] & 4) == 1) { + $this->ack_outgoing_message_id($msg_id); + } + foreach ($msgs_info_flags as $flag => $description) { + if (($response['info'][$key] & $flag) == 1) { + $status .= $description; + } + } + \danog\MadelineProto\Logger::log($status); + } + break; + case 'msg_new_detailed_info': + case 'msg_detailed_info': + + if (isset($this->datacenter->incoming_messages[$response['answer_msg_id']])) { + $this->ack_incoming_message_id($response['answer_msg_id']); + } else { + $this->object_call('msg_resend_req', ['msg_ids' => [$response['answer_msg_id']]]); + } + break; + case 'msg_resend_req': + $ok = true; + unset($this->datacenter->new_incoming[$current_msg_id]); + foreach ($response['msg_ids'] as $msg_id) { + if (!isset($this->datacenter->outgoing_messages[$msg_id]) || isset($this->datacenter->incoming_messages[$msg_id])) { + $ok = false; + } + } + if ($ok) { + foreach ($response['msg_ids'] as $msg_id) { + $this->object_call($this->datacenter->outgoing_messages[$msg_id]['content']['method'], $this->datacenter->outgoing_messages[$msg_id]['content']['args']); + } + } else { + $this->send_msgs_state_info($current_msg_id, $response['msg_ids']); + } + break; + case 'msg_resend_ans_req': + unset($this->datacenter->new_incoming[$current_msg_id]); + $this->send_msgs_state_info($response['msg_ids']); + foreach ($response['msg_ids'] as $msg_id) { + if (isset($this->datacenter->incoming_messages[$msg_id]) && isset($this->datacenter->outgoing_messages[$this->datacenter->incoming_messages[$msg_id]['response']])) { + $this->object_call($this->datacenter->outgoing_messages[$this->datacenter->incoming_messages[$msg_id]['response']]['method'], $this->datacenter->outgoing_messages[$this->datacenter->incoming_messages[$msg_id]['response']]['args']); + } + } + break; default: $this->ack_incoming_message_id($current_msg_id); // Acknowledge that I received the server's response $response_type = $this->tl->constructors->find_by_predicate($response['_'])['type']; + \danog\MadelineProto\Logger::log('Trying to assign a response of type '.$response_type.' to its request...'); foreach ($this->datacenter->new_outgoing as $key => $expecting) { + \danog\MadelineProto\Logger::log('Does the request of return type '.$expecting['type'].' and msg_id '.$expecting['msg_id'].' match?'); if ($response_type == $expecting['type']) { + \danog\MadelineProto\Logger::log('Yes'); $this->datacenter->outgoing_messages[$expecting['msg_id']]['response'] = $current_msg_id; unset($this->datacenter->new_outgoing[$key]); unset($this->datacenter->new_incoming[$current_msg_id]); return; } + \danog\MadelineProto\Logger::log('No'); } throw new \danog\MadelineProto\ResponseException('Dunno how to handle '.PHP_EOL.var_export($response, true)); break;