From 9046b46fcd24b6fa083476c043021e8193e1638a Mon Sep 17 00:00:00 2001 From: Lonami Exo Date: Thu, 30 Nov 2017 13:20:51 +0100 Subject: [PATCH] Document the network/ module --- telethon/network/__init__.py | 4 + telethon/network/authenticator.py | 30 +++- telethon/network/connection.py | 102 ++++++++++++- telethon/network/mtproto_plain_sender.py | 31 +++- telethon/network/mtproto_sender.py | 179 +++++++++++++++++++---- 5 files changed, 300 insertions(+), 46 deletions(-) diff --git a/telethon/network/__init__.py b/telethon/network/__init__.py index 77bd4406..d2538924 100644 --- a/telethon/network/__init__.py +++ b/telethon/network/__init__.py @@ -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 .authenticator import do_authentication from .mtproto_sender import MtProtoSender diff --git a/telethon/network/authenticator.py b/telethon/network/authenticator.py index 00a28fdf..a73bae38 100644 --- a/telethon/network/authenticator.py +++ b/telethon/network/authenticator.py @@ -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 time from hashlib import sha1 @@ -18,6 +22,14 @@ from ..tl.functions import ( 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: retries = 1 @@ -32,9 +44,11 @@ def do_authentication(connection, retries=5): def _do_authentication(connection): - """Executes the authentication process with the Telegram servers. - If no error is raised, returns both the authorization key and the - time offset. + """ + Executes the authentication process with the Telegram servers. + + :param connection: the connection to be used (must be connected). + :return: returns a (authorization key, time offset) tuple. """ sender = MtProtoPlainSender(connection) @@ -195,8 +209,12 @@ def _do_authentication(connection): def get_int(byte_array, signed=True): - """Gets the specified integer from its byte array. - This should be used by the authenticator, - who requires the data to be in big endian + """ + Gets the specified integer from its byte array. + 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) diff --git a/telethon/network/connection.py b/telethon/network/connection.py index fe04352f..ff255d00 100644 --- a/telethon/network/connection.py +++ b/telethon/network/connection.py @@ -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 struct from datetime import timedelta @@ -35,16 +39,24 @@ class ConnectionMode(Enum): 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 - will be packed accordingly, whereas '.write()' and '.read()' - work on plain bytes, with no further additions. + Note that '.send()' and '.recv()' refer to messages, which + will be packed accordingly, whereas '.write()' and '.read()' + work on plain bytes, with no further additions. """ def __init__(self, mode=ConnectionMode.TCP_FULL, 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._send_counter = 0 self._aes_encrypt, self._aes_decrypt = None, None @@ -75,6 +87,12 @@ class Connection: setattr(self, 'read', self._read_plain) 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: self.conn.connect(ip, port) except OSError as e: @@ -92,9 +110,13 @@ class Connection: self._setup_obfuscation() def get_timeout(self): + """Returns the timeout used by the connection.""" return self.conn.timeout def _setup_obfuscation(self): + """ + Sets up the obfuscated protocol. + """ # Obfuscated messages secrets cannot start with any of these keywords = (b'PVrG', b'GET ', b'POST', b'\xee' * 4) while True: @@ -122,13 +144,19 @@ class Connection: self.conn.write(bytes(random)) def is_connected(self): + """ + Determines whether the connection is alive or not. + + :return: true if it's connected. + """ return self.conn.connected def close(self): + """Closes the connection.""" self.conn.close() def clone(self): - """Creates a copy of this Connection""" + """Creates a copy of this Connection.""" return Connection( 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)) 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 = struct.unpack('= 127: length = struct.unpack('> 2 if length < 127: length = struct.pack('B', length) @@ -201,9 +268,21 @@ class Connection: raise ValueError('Invalid connection mode specified: ' + str(self._mode)) 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) 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( self.conn.read(length) ) @@ -216,9 +295,20 @@ class Connection: raise ValueError('Invalid connection mode specified: ' + str(self._mode)) 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) 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)) # endregion diff --git a/telethon/network/mtproto_plain_sender.py b/telethon/network/mtproto_plain_sender.py index c7c021be..cb6d63af 100644 --- a/telethon/network/mtproto_plain_sender.py +++ b/telethon/network/mtproto_plain_sender.py @@ -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 time @@ -6,32 +10,47 @@ from ..extensions import BinaryReader 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): + """ + Initializes the MTProto plain sender. + + :param connection: the Connection to be used. + """ self._sequence = 0 self._time_offset = 0 self._last_msg_id = 0 self._connection = connection def connect(self): + """Connects to Telegram's servers.""" self._connection.connect() def disconnect(self): + """Disconnects from Telegram's servers.""" self._connection.close() 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( struct.pack('