Fixed handling of all service messages exactly as described in the API (closes #20), fixed bug in authorization module, added hover logo.

This commit is contained in:
Daniil Gentili 2016-12-21 11:31:34 +01:00
parent 1cd457fdcd
commit 7e9383981c
7 changed files with 128 additions and 27 deletions

View File

@ -4,9 +4,9 @@
Created by [Daniil Gentili](https://daniil.it), licensed under AGPLv3.
![MadelineProto logo](https://daniil.it/MadelineProto/logo.png)
<img src='https://daniil.it/logo.png' alt='MadelineProto logo' onmouseover="this.src='https://daniil.it/logo-hover.png';" onmouseout="this.src='https://daniil.it/logo.png';" />
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).

View File

@ -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)
<img src='https://daniil.it/logo.png' alt='MadelineProto logo' onmouseover="this.src='https://daniil.it/logo-hover.png';" onmouseout="this.src='https://daniil.it/logo.png';" />
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).

View File

@ -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 = [];
}
}

View File

@ -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 = [];
}
}

View File

@ -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.'.');
}
}

View File

@ -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;
}
}

View File

@ -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;