mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2024-11-26 11:23:46 +03:00
Document the network/ module
This commit is contained in:
parent
7509ba9067
commit
9046b46fcd
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
This module contains several classes regarding network, low level connection
|
||||||
|
with Telegram's servers and the protocol used (TCP full, abridged, etc.).
|
||||||
|
"""
|
||||||
from .mtproto_plain_sender import MtProtoPlainSender
|
from .mtproto_plain_sender import MtProtoPlainSender
|
||||||
from .authenticator import do_authentication
|
from .authenticator import do_authentication
|
||||||
from .mtproto_sender import MtProtoSender
|
from .mtproto_sender import MtProtoSender
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
This module contains several functions that authenticate the client machine
|
||||||
|
with Telegram's servers, effectively creating an authorization key.
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
|
@ -18,6 +22,14 @@ from ..tl.functions import (
|
||||||
|
|
||||||
|
|
||||||
def do_authentication(connection, retries=5):
|
def do_authentication(connection, retries=5):
|
||||||
|
"""
|
||||||
|
Performs the authentication steps on the given connection.
|
||||||
|
Raises an error if all attempts fail.
|
||||||
|
|
||||||
|
:param connection: the connection to be used (must be connected).
|
||||||
|
:param retries: how many times should we retry on failure.
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
if not retries or retries < 0:
|
if not retries or retries < 0:
|
||||||
retries = 1
|
retries = 1
|
||||||
|
|
||||||
|
@ -32,9 +44,11 @@ def do_authentication(connection, retries=5):
|
||||||
|
|
||||||
|
|
||||||
def _do_authentication(connection):
|
def _do_authentication(connection):
|
||||||
"""Executes the authentication process with the Telegram servers.
|
"""
|
||||||
If no error is raised, returns both the authorization key and the
|
Executes the authentication process with the Telegram servers.
|
||||||
time offset.
|
|
||||||
|
:param connection: the connection to be used (must be connected).
|
||||||
|
:return: returns a (authorization key, time offset) tuple.
|
||||||
"""
|
"""
|
||||||
sender = MtProtoPlainSender(connection)
|
sender = MtProtoPlainSender(connection)
|
||||||
|
|
||||||
|
@ -195,8 +209,12 @@ def _do_authentication(connection):
|
||||||
|
|
||||||
|
|
||||||
def get_int(byte_array, signed=True):
|
def get_int(byte_array, signed=True):
|
||||||
"""Gets the specified integer from its byte array.
|
"""
|
||||||
This should be used by the authenticator,
|
Gets the specified integer from its byte array.
|
||||||
who requires the data to be in big endian
|
This should be used by this module alone, as it works with big endian.
|
||||||
|
|
||||||
|
:param byte_array: the byte array representing th integer.
|
||||||
|
:param signed: whether the number is signed or not.
|
||||||
|
:return: the integer representing the given byte array.
|
||||||
"""
|
"""
|
||||||
return int.from_bytes(byte_array, byteorder='big', signed=signed)
|
return int.from_bytes(byte_array, byteorder='big', signed=signed)
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
This module holds both the Connection class and the ConnectionMode enum,
|
||||||
|
which specifies the protocol to be used by the Connection.
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import struct
|
import struct
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
@ -35,16 +39,24 @@ class ConnectionMode(Enum):
|
||||||
|
|
||||||
|
|
||||||
class Connection:
|
class Connection:
|
||||||
"""Represents an abstract connection (TCP, TCP abridged...).
|
"""
|
||||||
'mode' must be any of the ConnectionMode enumeration.
|
Represents an abstract connection (TCP, TCP abridged...).
|
||||||
|
'mode' must be any of the ConnectionMode enumeration.
|
||||||
|
|
||||||
Note that '.send()' and '.recv()' refer to messages, which
|
Note that '.send()' and '.recv()' refer to messages, which
|
||||||
will be packed accordingly, whereas '.write()' and '.read()'
|
will be packed accordingly, whereas '.write()' and '.read()'
|
||||||
work on plain bytes, with no further additions.
|
work on plain bytes, with no further additions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, mode=ConnectionMode.TCP_FULL,
|
def __init__(self, mode=ConnectionMode.TCP_FULL,
|
||||||
proxy=None, timeout=timedelta(seconds=5)):
|
proxy=None, timeout=timedelta(seconds=5)):
|
||||||
|
"""
|
||||||
|
Initializes a new connection.
|
||||||
|
|
||||||
|
:param mode: the ConnectionMode to be used.
|
||||||
|
:param proxy: whether to use a proxy or not.
|
||||||
|
:param timeout: timeout to be used for all operations.
|
||||||
|
"""
|
||||||
self._mode = mode
|
self._mode = mode
|
||||||
self._send_counter = 0
|
self._send_counter = 0
|
||||||
self._aes_encrypt, self._aes_decrypt = None, None
|
self._aes_encrypt, self._aes_decrypt = None, None
|
||||||
|
@ -75,6 +87,12 @@ class Connection:
|
||||||
setattr(self, 'read', self._read_plain)
|
setattr(self, 'read', self._read_plain)
|
||||||
|
|
||||||
def connect(self, ip, port):
|
def connect(self, ip, port):
|
||||||
|
"""
|
||||||
|
Estabilishes a connection to IP:port.
|
||||||
|
|
||||||
|
:param ip: the IP to connect to.
|
||||||
|
:param port: the port to connect to.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
self.conn.connect(ip, port)
|
self.conn.connect(ip, port)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
@ -92,9 +110,13 @@ class Connection:
|
||||||
self._setup_obfuscation()
|
self._setup_obfuscation()
|
||||||
|
|
||||||
def get_timeout(self):
|
def get_timeout(self):
|
||||||
|
"""Returns the timeout used by the connection."""
|
||||||
return self.conn.timeout
|
return self.conn.timeout
|
||||||
|
|
||||||
def _setup_obfuscation(self):
|
def _setup_obfuscation(self):
|
||||||
|
"""
|
||||||
|
Sets up the obfuscated protocol.
|
||||||
|
"""
|
||||||
# Obfuscated messages secrets cannot start with any of these
|
# Obfuscated messages secrets cannot start with any of these
|
||||||
keywords = (b'PVrG', b'GET ', b'POST', b'\xee' * 4)
|
keywords = (b'PVrG', b'GET ', b'POST', b'\xee' * 4)
|
||||||
while True:
|
while True:
|
||||||
|
@ -122,13 +144,19 @@ class Connection:
|
||||||
self.conn.write(bytes(random))
|
self.conn.write(bytes(random))
|
||||||
|
|
||||||
def is_connected(self):
|
def is_connected(self):
|
||||||
|
"""
|
||||||
|
Determines whether the connection is alive or not.
|
||||||
|
|
||||||
|
:return: true if it's connected.
|
||||||
|
"""
|
||||||
return self.conn.connected
|
return self.conn.connected
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
"""Closes the connection."""
|
||||||
self.conn.close()
|
self.conn.close()
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
"""Creates a copy of this Connection"""
|
"""Creates a copy of this Connection."""
|
||||||
return Connection(
|
return Connection(
|
||||||
mode=self._mode, proxy=self.conn.proxy, timeout=self.conn.timeout
|
mode=self._mode, proxy=self.conn.proxy, timeout=self.conn.timeout
|
||||||
)
|
)
|
||||||
|
@ -141,6 +169,15 @@ class Connection:
|
||||||
raise ValueError('Invalid connection mode specified: ' + str(self._mode))
|
raise ValueError('Invalid connection mode specified: ' + str(self._mode))
|
||||||
|
|
||||||
def _recv_tcp_full(self):
|
def _recv_tcp_full(self):
|
||||||
|
"""
|
||||||
|
Receives a message from the network,
|
||||||
|
internally encoded using the TCP full protocol.
|
||||||
|
|
||||||
|
May raise InvalidChecksumError if the received data doesn't
|
||||||
|
match its valid checksum.
|
||||||
|
|
||||||
|
:return: the read message payload.
|
||||||
|
"""
|
||||||
packet_len_seq = self.read(8) # 4 and 4
|
packet_len_seq = self.read(8) # 4 and 4
|
||||||
packet_len, seq = struct.unpack('<ii', packet_len_seq)
|
packet_len, seq = struct.unpack('<ii', packet_len_seq)
|
||||||
|
|
||||||
|
@ -154,9 +191,21 @@ class Connection:
|
||||||
return body
|
return body
|
||||||
|
|
||||||
def _recv_intermediate(self):
|
def _recv_intermediate(self):
|
||||||
|
"""
|
||||||
|
Receives a message from the network,
|
||||||
|
internally encoded using the TCP intermediate protocol.
|
||||||
|
|
||||||
|
:return: the read message payload.
|
||||||
|
"""
|
||||||
return self.read(struct.unpack('<i', self.read(4))[0])
|
return self.read(struct.unpack('<i', self.read(4))[0])
|
||||||
|
|
||||||
def _recv_abridged(self):
|
def _recv_abridged(self):
|
||||||
|
"""
|
||||||
|
Receives a message from the network,
|
||||||
|
internally encoded using the TCP abridged protocol.
|
||||||
|
|
||||||
|
:return: the read message payload.
|
||||||
|
"""
|
||||||
length = struct.unpack('<B', self.read(1))[0]
|
length = struct.unpack('<B', self.read(1))[0]
|
||||||
if length >= 127:
|
if length >= 127:
|
||||||
length = struct.unpack('<i', self.read(3) + b'\0')[0]
|
length = struct.unpack('<i', self.read(3) + b'\0')[0]
|
||||||
|
@ -173,6 +222,12 @@ class Connection:
|
||||||
raise ValueError('Invalid connection mode specified: ' + str(self._mode))
|
raise ValueError('Invalid connection mode specified: ' + str(self._mode))
|
||||||
|
|
||||||
def _send_tcp_full(self, message):
|
def _send_tcp_full(self, message):
|
||||||
|
"""
|
||||||
|
Encapsulates and sends the given message payload
|
||||||
|
using the TCP full mode (length, sequence, message, crc32).
|
||||||
|
|
||||||
|
:param message: the message to be sent.
|
||||||
|
"""
|
||||||
# https://core.telegram.org/mtproto#tcp-transport
|
# https://core.telegram.org/mtproto#tcp-transport
|
||||||
# total length, sequence number, packet and checksum (CRC32)
|
# total length, sequence number, packet and checksum (CRC32)
|
||||||
length = len(message) + 12
|
length = len(message) + 12
|
||||||
|
@ -182,9 +237,21 @@ class Connection:
|
||||||
self.write(data + crc)
|
self.write(data + crc)
|
||||||
|
|
||||||
def _send_intermediate(self, message):
|
def _send_intermediate(self, message):
|
||||||
|
"""
|
||||||
|
Encapsulates and sends the given message payload
|
||||||
|
using the TCP intermediate mode (length, message).
|
||||||
|
|
||||||
|
:param message: the message to be sent.
|
||||||
|
"""
|
||||||
self.write(struct.pack('<i', len(message)) + message)
|
self.write(struct.pack('<i', len(message)) + message)
|
||||||
|
|
||||||
def _send_abridged(self, message):
|
def _send_abridged(self, message):
|
||||||
|
"""
|
||||||
|
Encapsulates and sends the given message payload
|
||||||
|
using the TCP abridged mode (short length, message).
|
||||||
|
|
||||||
|
:param message: the message to be sent.
|
||||||
|
"""
|
||||||
length = len(message) >> 2
|
length = len(message) >> 2
|
||||||
if length < 127:
|
if length < 127:
|
||||||
length = struct.pack('B', length)
|
length = struct.pack('B', length)
|
||||||
|
@ -201,9 +268,21 @@ class Connection:
|
||||||
raise ValueError('Invalid connection mode specified: ' + str(self._mode))
|
raise ValueError('Invalid connection mode specified: ' + str(self._mode))
|
||||||
|
|
||||||
def _read_plain(self, length):
|
def _read_plain(self, length):
|
||||||
|
"""
|
||||||
|
Reads data from the socket connection.
|
||||||
|
|
||||||
|
:param length: how many bytes should be read.
|
||||||
|
:return: a byte sequence with len(data) == length
|
||||||
|
"""
|
||||||
return self.conn.read(length)
|
return self.conn.read(length)
|
||||||
|
|
||||||
def _read_obfuscated(self, length):
|
def _read_obfuscated(self, length):
|
||||||
|
"""
|
||||||
|
Reads data and decrypts from the socket connection.
|
||||||
|
|
||||||
|
:param length: how many bytes should be read.
|
||||||
|
:return: the decrypted byte sequence with len(data) == length
|
||||||
|
"""
|
||||||
return self._aes_decrypt.encrypt(
|
return self._aes_decrypt.encrypt(
|
||||||
self.conn.read(length)
|
self.conn.read(length)
|
||||||
)
|
)
|
||||||
|
@ -216,9 +295,20 @@ class Connection:
|
||||||
raise ValueError('Invalid connection mode specified: ' + str(self._mode))
|
raise ValueError('Invalid connection mode specified: ' + str(self._mode))
|
||||||
|
|
||||||
def _write_plain(self, data):
|
def _write_plain(self, data):
|
||||||
|
"""
|
||||||
|
Writes the given data through the socket connection.
|
||||||
|
|
||||||
|
:param data: the data in bytes to be written.
|
||||||
|
"""
|
||||||
self.conn.write(data)
|
self.conn.write(data)
|
||||||
|
|
||||||
def _write_obfuscated(self, data):
|
def _write_obfuscated(self, data):
|
||||||
|
"""
|
||||||
|
Writes the given data through the socket connection,
|
||||||
|
using the obfuscated mode (AES encryption is applied on top).
|
||||||
|
|
||||||
|
:param data: the data in bytes to be written.
|
||||||
|
"""
|
||||||
self.conn.write(self._aes_encrypt.encrypt(data))
|
self.conn.write(self._aes_encrypt.encrypt(data))
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
This module contains the class used to communicate with Telegram's servers
|
||||||
|
in plain text, when no authorization key has been created yet.
|
||||||
|
"""
|
||||||
import struct
|
import struct
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
@ -6,32 +10,47 @@ from ..extensions import BinaryReader
|
||||||
|
|
||||||
|
|
||||||
class MtProtoPlainSender:
|
class MtProtoPlainSender:
|
||||||
"""MTProto Mobile Protocol plain sender
|
"""
|
||||||
(https://core.telegram.org/mtproto/description#unencrypted-messages)
|
MTProto Mobile Protocol plain sender
|
||||||
|
(https://core.telegram.org/mtproto/description#unencrypted-messages)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, connection):
|
def __init__(self, connection):
|
||||||
|
"""
|
||||||
|
Initializes the MTProto plain sender.
|
||||||
|
|
||||||
|
:param connection: the Connection to be used.
|
||||||
|
"""
|
||||||
self._sequence = 0
|
self._sequence = 0
|
||||||
self._time_offset = 0
|
self._time_offset = 0
|
||||||
self._last_msg_id = 0
|
self._last_msg_id = 0
|
||||||
self._connection = connection
|
self._connection = connection
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
|
"""Connects to Telegram's servers."""
|
||||||
self._connection.connect()
|
self._connection.connect()
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
|
"""Disconnects from Telegram's servers."""
|
||||||
self._connection.close()
|
self._connection.close()
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
"""Sends a plain packet (auth_key_id = 0) containing the
|
"""
|
||||||
given message body (data)
|
Sends a plain packet (auth_key_id = 0) containing the
|
||||||
|
given message body (data).
|
||||||
|
|
||||||
|
:param data: the data to be sent.
|
||||||
"""
|
"""
|
||||||
self._connection.send(
|
self._connection.send(
|
||||||
struct.pack('<QQi', 0, self._get_new_msg_id(), len(data)) + data
|
struct.pack('<QQi', 0, self._get_new_msg_id(), len(data)) + data
|
||||||
)
|
)
|
||||||
|
|
||||||
def receive(self):
|
def receive(self):
|
||||||
"""Receives a plain packet, returning the body of the response"""
|
"""
|
||||||
|
Receives a plain packet from the network.
|
||||||
|
|
||||||
|
:return: the response body.
|
||||||
|
"""
|
||||||
body = self._connection.recv()
|
body = self._connection.recv()
|
||||||
if body == b'l\xfe\xff\xff': # -404 little endian signed
|
if body == b'l\xfe\xff\xff': # -404 little endian signed
|
||||||
# Broken authorization, must reset the auth key
|
# Broken authorization, must reset the auth key
|
||||||
|
@ -46,7 +65,7 @@ class MtProtoPlainSender:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def _get_new_msg_id(self):
|
def _get_new_msg_id(self):
|
||||||
"""Generates a new message ID based on the current time since epoch"""
|
"""Generates a new message ID based on the current time since epoch."""
|
||||||
# See core.telegram.org/mtproto/description#message-identifier-msg-id
|
# See core.telegram.org/mtproto/description#message-identifier-msg-id
|
||||||
now = time.time()
|
now = time.time()
|
||||||
nanoseconds = int((now - int(now)) * 1e+9)
|
nanoseconds = int((now - int(now)) * 1e+9)
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
"""
|
||||||
|
This module contains the class used to communicate with Telegram's servers
|
||||||
|
encrypting every packet, and relies on a valid AuthKey in the used Session.
|
||||||
|
"""
|
||||||
import gzip
|
import gzip
|
||||||
import logging
|
import logging
|
||||||
import struct
|
import struct
|
||||||
|
@ -31,8 +35,14 @@ class MtProtoSender:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, session, connection):
|
def __init__(self, session, connection):
|
||||||
"""Creates a new MtProtoSender configured to send messages through
|
"""
|
||||||
'connection' and using the parameters from 'session'.
|
Initializes a new MTProto sender.
|
||||||
|
|
||||||
|
:param session:
|
||||||
|
the Session to be used with this sender. Must contain the IP and
|
||||||
|
port of the server, salt, ID, and AuthKey,
|
||||||
|
:param connection:
|
||||||
|
the Connection to be used.
|
||||||
"""
|
"""
|
||||||
self.session = session
|
self.session = session
|
||||||
self.connection = connection
|
self.connection = connection
|
||||||
|
@ -45,28 +55,36 @@ class MtProtoSender:
|
||||||
self._pending_receive = {}
|
self._pending_receive = {}
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""Connects to the server"""
|
"""Connects to the server."""
|
||||||
self.connection.connect(self.session.server_address, self.session.port)
|
self.connection.connect(self.session.server_address, self.session.port)
|
||||||
|
|
||||||
def is_connected(self):
|
def is_connected(self):
|
||||||
|
"""
|
||||||
|
Determines whether the sender is connected or not.
|
||||||
|
|
||||||
|
:return: true if the sender is connected.
|
||||||
|
"""
|
||||||
return self.connection.is_connected()
|
return self.connection.is_connected()
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
"""Disconnects from the server"""
|
"""Disconnects from the server."""
|
||||||
self.connection.close()
|
self.connection.close()
|
||||||
self._need_confirmation.clear()
|
self._need_confirmation.clear()
|
||||||
self._clear_all_pending()
|
self._clear_all_pending()
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
"""Creates a copy of this MtProtoSender as a new connection"""
|
"""Creates a copy of this MtProtoSender as a new connection."""
|
||||||
return MtProtoSender(self.session, self.connection.clone())
|
return MtProtoSender(self.session, self.connection.clone())
|
||||||
|
|
||||||
# region Send and receive
|
# region Send and receive
|
||||||
|
|
||||||
def send(self, *requests):
|
def send(self, *requests):
|
||||||
"""Sends the specified MTProtoRequest, previously sending any message
|
"""
|
||||||
which needed confirmation."""
|
Sends the specified TLObject(s) (which must be requests),
|
||||||
|
and acknowledging any message which needed confirmation.
|
||||||
|
|
||||||
|
:param requests: the requests to be sent.
|
||||||
|
"""
|
||||||
# Finally send our packed request(s)
|
# Finally send our packed request(s)
|
||||||
messages = [TLMessage(self.session, r) for r in requests]
|
messages = [TLMessage(self.session, r) for r in requests]
|
||||||
self._pending_receive.update({m.msg_id: m for m in messages})
|
self._pending_receive.update({m.msg_id: m for m in messages})
|
||||||
|
@ -91,18 +109,23 @@ class MtProtoSender:
|
||||||
self._send_message(message)
|
self._send_message(message)
|
||||||
|
|
||||||
def _send_acknowledge(self, msg_id):
|
def _send_acknowledge(self, msg_id):
|
||||||
"""Sends a message acknowledge for the given msg_id"""
|
"""Sends a message acknowledge for the given msg_id."""
|
||||||
self._send_message(TLMessage(self.session, MsgsAck([msg_id])))
|
self._send_message(TLMessage(self.session, MsgsAck([msg_id])))
|
||||||
|
|
||||||
def receive(self, update_state):
|
def receive(self, update_state):
|
||||||
"""Receives a single message from the connected endpoint.
|
"""
|
||||||
|
Receives a single message from the connected endpoint.
|
||||||
|
|
||||||
This method returns nothing, and will only affect other parts
|
This method returns nothing, and will only affect other parts
|
||||||
of the MtProtoSender such as the updates callback being fired
|
of the MtProtoSender such as the updates callback being fired
|
||||||
or a pending request being confirmed.
|
or a pending request being confirmed.
|
||||||
|
|
||||||
Any unhandled object (likely updates) will be passed to
|
Any unhandled object (likely updates) will be passed to
|
||||||
update_state.process(TLObject).
|
update_state.process(TLObject).
|
||||||
|
|
||||||
|
:param update_state:
|
||||||
|
the UpdateState that will process all the received
|
||||||
|
Update and Updates objects.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
body = self.connection.recv()
|
body = self.connection.recv()
|
||||||
|
@ -126,8 +149,11 @@ class MtProtoSender:
|
||||||
# region Low level processing
|
# region Low level processing
|
||||||
|
|
||||||
def _send_message(self, message):
|
def _send_message(self, message):
|
||||||
"""Sends the given Message(TLObject) encrypted through the network"""
|
"""
|
||||||
|
Sends the given encrypted through the network.
|
||||||
|
|
||||||
|
:param message: the TLMessage to be sent.
|
||||||
|
"""
|
||||||
plain_text = \
|
plain_text = \
|
||||||
struct.pack('<QQ', self.session.salt, self.session.id) \
|
struct.pack('<QQ', self.session.salt, self.session.id) \
|
||||||
+ bytes(message)
|
+ bytes(message)
|
||||||
|
@ -141,7 +167,12 @@ class MtProtoSender:
|
||||||
self.connection.send(result)
|
self.connection.send(result)
|
||||||
|
|
||||||
def _decode_msg(self, body):
|
def _decode_msg(self, body):
|
||||||
"""Decodes an received encrypted message body bytes"""
|
"""
|
||||||
|
Decodes the body of the payload received from the network.
|
||||||
|
|
||||||
|
:param body: the body to be decoded.
|
||||||
|
:return: a tuple of (decoded message, remote message id, remote seq).
|
||||||
|
"""
|
||||||
message = None
|
message = None
|
||||||
remote_msg_id = None
|
remote_msg_id = None
|
||||||
remote_sequence = None
|
remote_sequence = None
|
||||||
|
@ -172,12 +203,15 @@ class MtProtoSender:
|
||||||
return message, remote_msg_id, remote_sequence
|
return message, remote_msg_id, remote_sequence
|
||||||
|
|
||||||
def _process_msg(self, msg_id, sequence, reader, state):
|
def _process_msg(self, msg_id, sequence, reader, state):
|
||||||
"""Processes and handles a Telegram message.
|
|
||||||
|
|
||||||
Returns True if the message was handled correctly and doesn't
|
|
||||||
need to be skipped. Returns False otherwise.
|
|
||||||
"""
|
"""
|
||||||
|
Processes the message read from the network inside reader.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the BinaryReader that contains the message.
|
||||||
|
:param state: the current UpdateState.
|
||||||
|
:return: true if the message was handled correctly, false otherwise.
|
||||||
|
"""
|
||||||
# TODO Check salt, session_id and sequence_number
|
# TODO Check salt, session_id and sequence_number
|
||||||
self._need_confirmation.add(msg_id)
|
self._need_confirmation.add(msg_id)
|
||||||
|
|
||||||
|
@ -249,24 +283,34 @@ class MtProtoSender:
|
||||||
# region Message handling
|
# region Message handling
|
||||||
|
|
||||||
def _pop_request(self, msg_id):
|
def _pop_request(self, msg_id):
|
||||||
"""Pops a pending REQUEST from self._pending_receive, or
|
"""
|
||||||
returns None if it's not found.
|
Pops a pending **request** from self._pending_receive.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message that belongs to the request.
|
||||||
|
:return: the request, or None if it wasn't found.
|
||||||
"""
|
"""
|
||||||
message = self._pending_receive.pop(msg_id, None)
|
message = self._pending_receive.pop(msg_id, None)
|
||||||
if message:
|
if message:
|
||||||
return message.request
|
return message.request
|
||||||
|
|
||||||
def _pop_request_of_type(self, msg_id, t):
|
def _pop_request_of_type(self, msg_id, t):
|
||||||
"""Pops a pending REQUEST from self._pending_receive if it matches
|
"""
|
||||||
the given type, or returns None if it's not found/doesn't match.
|
Pops a pending **request** from self._pending_receive.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message that belongs to the request.
|
||||||
|
:param t: the type of the desired request.
|
||||||
|
:return: the request matching the type t, or None if it wasn't found.
|
||||||
"""
|
"""
|
||||||
message = self._pending_receive.get(msg_id, None)
|
message = self._pending_receive.get(msg_id, None)
|
||||||
if message and isinstance(message.request, t):
|
if message and isinstance(message.request, t):
|
||||||
return self._pending_receive.pop(msg_id).request
|
return self._pending_receive.pop(msg_id).request
|
||||||
|
|
||||||
def _pop_requests_of_container(self, container_msg_id):
|
def _pop_requests_of_container(self, container_msg_id):
|
||||||
"""Pops the pending requests (plural) from self._pending_receive if
|
"""
|
||||||
they were sent on a container that matches container_msg_id.
|
Pops pending **requests** from self._pending_receive.
|
||||||
|
|
||||||
|
:param container_msg_id: the ID of the container.
|
||||||
|
:return: the requests that belong to the given container. May be empty.
|
||||||
"""
|
"""
|
||||||
msgs = [msg for msg in self._pending_receive.values()
|
msgs = [msg for msg in self._pending_receive.values()
|
||||||
if msg.container_msg_id == container_msg_id]
|
if msg.container_msg_id == container_msg_id]
|
||||||
|
@ -277,13 +321,19 @@ class MtProtoSender:
|
||||||
return requests
|
return requests
|
||||||
|
|
||||||
def _clear_all_pending(self):
|
def _clear_all_pending(self):
|
||||||
|
"""
|
||||||
|
Clears all pending requests, and flags them all as received.
|
||||||
|
"""
|
||||||
for r in self._pending_receive.values():
|
for r in self._pending_receive.values():
|
||||||
r.request.confirm_received.set()
|
r.request.confirm_received.set()
|
||||||
self._pending_receive.clear()
|
self._pending_receive.clear()
|
||||||
|
|
||||||
def _resend_request(self, msg_id):
|
def _resend_request(self, msg_id):
|
||||||
"""Re-sends the request that belongs to a certain msg_id. This may
|
"""
|
||||||
also be the msg_id of a container if they were sent in one.
|
Re-sends the request that belongs to a certain msg_id. This may
|
||||||
|
also be the msg_id of a container if they were sent in one.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the request to be resent.
|
||||||
"""
|
"""
|
||||||
request = self._pop_request(msg_id)
|
request = self._pop_request(msg_id)
|
||||||
if request:
|
if request:
|
||||||
|
@ -293,6 +343,14 @@ class MtProtoSender:
|
||||||
return self.send(*requests)
|
return self.send(*requests)
|
||||||
|
|
||||||
def _handle_pong(self, msg_id, sequence, reader):
|
def _handle_pong(self, msg_id, sequence, reader):
|
||||||
|
"""
|
||||||
|
Handles a Pong response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the Pong.
|
||||||
|
:return: true, as it always succeeds.
|
||||||
|
"""
|
||||||
self._logger.debug('Handling pong')
|
self._logger.debug('Handling pong')
|
||||||
pong = reader.tgread_object()
|
pong = reader.tgread_object()
|
||||||
assert isinstance(pong, Pong)
|
assert isinstance(pong, Pong)
|
||||||
|
@ -306,6 +364,14 @@ class MtProtoSender:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_container(self, msg_id, sequence, reader, state):
|
def _handle_container(self, msg_id, sequence, reader, state):
|
||||||
|
"""
|
||||||
|
Handles a MessageContainer response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the MessageContainer.
|
||||||
|
:return: true, as it always succeeds.
|
||||||
|
"""
|
||||||
self._logger.debug('Handling container')
|
self._logger.debug('Handling container')
|
||||||
for inner_msg_id, _, inner_len in MessageContainer.iter_read(reader):
|
for inner_msg_id, _, inner_len in MessageContainer.iter_read(reader):
|
||||||
begin_position = reader.tell_position()
|
begin_position = reader.tell_position()
|
||||||
|
@ -323,6 +389,14 @@ class MtProtoSender:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_bad_server_salt(self, msg_id, sequence, reader):
|
def _handle_bad_server_salt(self, msg_id, sequence, reader):
|
||||||
|
"""
|
||||||
|
Handles a BadServerSalt response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the BadServerSalt.
|
||||||
|
:return: true, as it always succeeds.
|
||||||
|
"""
|
||||||
self._logger.debug('Handling bad server salt')
|
self._logger.debug('Handling bad server salt')
|
||||||
bad_salt = reader.tgread_object()
|
bad_salt = reader.tgread_object()
|
||||||
assert isinstance(bad_salt, BadServerSalt)
|
assert isinstance(bad_salt, BadServerSalt)
|
||||||
|
@ -339,6 +413,14 @@ class MtProtoSender:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_bad_msg_notification(self, msg_id, sequence, reader):
|
def _handle_bad_msg_notification(self, msg_id, sequence, reader):
|
||||||
|
"""
|
||||||
|
Handles a BadMessageError response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the BadMessageError.
|
||||||
|
:return: true, as it always succeeds.
|
||||||
|
"""
|
||||||
self._logger.debug('Handling bad message notification')
|
self._logger.debug('Handling bad message notification')
|
||||||
bad_msg = reader.tgread_object()
|
bad_msg = reader.tgread_object()
|
||||||
assert isinstance(bad_msg, BadMsgNotification)
|
assert isinstance(bad_msg, BadMsgNotification)
|
||||||
|
@ -367,6 +449,14 @@ class MtProtoSender:
|
||||||
raise error
|
raise error
|
||||||
|
|
||||||
def _handle_msg_detailed_info(self, msg_id, sequence, reader):
|
def _handle_msg_detailed_info(self, msg_id, sequence, reader):
|
||||||
|
"""
|
||||||
|
Handles a MsgDetailedInfo response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the MsgDetailedInfo.
|
||||||
|
:return: true, as it always succeeds.
|
||||||
|
"""
|
||||||
msg_new = reader.tgread_object()
|
msg_new = reader.tgread_object()
|
||||||
assert isinstance(msg_new, MsgDetailedInfo)
|
assert isinstance(msg_new, MsgDetailedInfo)
|
||||||
|
|
||||||
|
@ -376,6 +466,14 @@ class MtProtoSender:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_msg_new_detailed_info(self, msg_id, sequence, reader):
|
def _handle_msg_new_detailed_info(self, msg_id, sequence, reader):
|
||||||
|
"""
|
||||||
|
Handles a MsgNewDetailedInfo response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the MsgNewDetailedInfo.
|
||||||
|
:return: true, as it always succeeds.
|
||||||
|
"""
|
||||||
msg_new = reader.tgread_object()
|
msg_new = reader.tgread_object()
|
||||||
assert isinstance(msg_new, MsgNewDetailedInfo)
|
assert isinstance(msg_new, MsgNewDetailedInfo)
|
||||||
|
|
||||||
|
@ -385,12 +483,29 @@ class MtProtoSender:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_new_session_created(self, msg_id, sequence, reader):
|
def _handle_new_session_created(self, msg_id, sequence, reader):
|
||||||
|
"""
|
||||||
|
Handles a NewSessionCreated response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the NewSessionCreated.
|
||||||
|
:return: true, as it always succeeds.
|
||||||
|
"""
|
||||||
new_session = reader.tgread_object()
|
new_session = reader.tgread_object()
|
||||||
assert isinstance(new_session, NewSessionCreated)
|
assert isinstance(new_session, NewSessionCreated)
|
||||||
# TODO https://goo.gl/LMyN7A
|
# TODO https://goo.gl/LMyN7A
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _handle_rpc_result(self, msg_id, sequence, reader):
|
def _handle_rpc_result(self, msg_id, sequence, reader):
|
||||||
|
"""
|
||||||
|
Handles a RPCResult response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the RPCResult.
|
||||||
|
:return: true if the request ID to which this result belongs is found,
|
||||||
|
false otherwise (meaning nothing was read).
|
||||||
|
"""
|
||||||
self._logger.debug('Handling RPC result')
|
self._logger.debug('Handling RPC result')
|
||||||
reader.read_int(signed=False) # code
|
reader.read_int(signed=False) # code
|
||||||
request_id = reader.read_long()
|
request_id = reader.read_long()
|
||||||
|
@ -440,6 +555,14 @@ class MtProtoSender:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _handle_gzip_packed(self, msg_id, sequence, reader, state):
|
def _handle_gzip_packed(self, msg_id, sequence, reader, state):
|
||||||
|
"""
|
||||||
|
Handles a GzipPacked response.
|
||||||
|
|
||||||
|
:param msg_id: the ID of the message.
|
||||||
|
:param sequence: the sequence of the message.
|
||||||
|
:param reader: the reader containing the GzipPacked.
|
||||||
|
:return: the result of processing the packed message.
|
||||||
|
"""
|
||||||
self._logger.debug('Handling gzip packed data')
|
self._logger.debug('Handling gzip packed data')
|
||||||
with BinaryReader(GzipPacked.read(reader)) as compressed_reader:
|
with BinaryReader(GzipPacked.read(reader)) as compressed_reader:
|
||||||
# We are reentering process_msg, which seemingly the same msg_id
|
# We are reentering process_msg, which seemingly the same msg_id
|
||||||
|
|
Loading…
Reference in New Issue
Block a user