mirror of
				https://github.com/LonamiWebs/Telethon.git
				synced 2025-10-24 20:51:05 +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,7 +39,8 @@ class ConnectionMode(Enum): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class Connection: | class Connection: | ||||||
|     """Represents an abstract connection (TCP, TCP abridged...). |     """ | ||||||
|  |     Represents an abstract connection (TCP, TCP abridged...). | ||||||
|     'mode' must be any of the ConnectionMode enumeration. |     '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 | ||||||
|  | @ -45,6 +50,13 @@ class Connection: | ||||||
| 
 | 
 | ||||||
|     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 |     """ | ||||||
|  |     MTProto Mobile Protocol plain sender | ||||||
|     (https://core.telegram.org/mtproto/description#unencrypted-messages) |     (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,11 +109,12 @@ 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 | ||||||
|  | @ -103,6 +122,10 @@ class MtProtoSender: | ||||||
| 
 | 
 | ||||||
|         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 |         """ | ||||||
|  |         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. |         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