def handle_pong(self, msg_id, sequence, reader, request): Log.d('Handling pong') reader.read_int(signed=False) # code recv_msg_id = reader.read_long(signed=False) if recv_msg_id == request.msg_id: Log.w('Pong confirmed a request') request.confirm_received = True return False
def handle_rpc_result(self, msg_id, sequence, reader, request): Log.d('Handling RPC result, request is%s None', ' not' if request else '') reader.read_int(signed=False) # code request_id = reader.read_long(signed=False) inner_code = reader.read_int(signed=False) if request and request_id == request.msg_id: request.confirm_received = True if inner_code == 0x2144ca19: # RPC Error error = RPCError(code=reader.read_int(), message=reader.tgread_string()) Log.w('Read RPC error: %s', str(error)) if error.must_resend: if not request: raise ValueError( 'The previously sent request must be resent. ' 'However, no request was previously sent (called from updates thread).' ) request.confirm_received = False if error.message.startswith('FLOOD_WAIT_'): self.updates_thread_sleep = error.additional_data print('Should wait {}s. Sleeping until then.'.format( error.additional_data)) sleep(error.additional_data) elif '_MIGRATE_' in error.message: raise InvalidDCError(error.additional_data) else: raise error else: if not request: raise ValueError( 'Cannot receive a request from inside an RPC result from the updates thread.' ) Log.d('Reading request response') if inner_code == 0x3072cfa1: # GZip packed unpacked_data = gzip.decompress(reader.tgread_bytes()) with BinaryReader(unpacked_data) as compressed_reader: request.on_response(compressed_reader) else: reader.seek(-4) request.on_response(reader)
def process_msg(self, msg_id, sequence, reader, request=None): """Processes and handles a Telegram message""" # TODO Check salt, session_id and sequence_number self.need_confirmation.append(msg_id) code = reader.read_int(signed=False) reader.seek(-4) # The following codes are "parsed manually" if code == 0xf35c6d01: # rpc_result, (response of an RPC call, i.e., we sent a request) return self.handle_rpc_result(msg_id, sequence, reader, request) if code == 0x347773c5: # pong return self.handle_pong(msg_id, sequence, reader, request) if code == 0x73f1f8dc: # msg_container return self.handle_container(msg_id, sequence, reader, request) if code == 0x3072cfa1: # gzip_packed return self.handle_gzip_packed(msg_id, sequence, reader, request) if code == 0xedab447b: # bad_server_salt return self.handle_bad_server_salt(msg_id, sequence, reader, request) if code == 0xa7eff811: # bad_msg_notification return self.handle_bad_msg_notification(msg_id, sequence, reader) # msgs_ack, it may handle the request we wanted if code == 0x62d6b459: ack = reader.tgread_object() if request and request.msg_id in ack.msg_ids: Log.w('Ack found for the current request ID') if self.logging_out: Log.i('Message ack confirmed the logout request') request.confirm_received = True return False # If the code is not parsed manually, then it was parsed by the code generator! # In this case, we will simply treat the incoming TLObject as an Update, # if we can first find a matching TLObject if code in tlobjects.keys(): return self.handle_update(msg_id, sequence, reader) print('Unknown message: {}'.format(hex(code))) return False
def updates_thread_method(self): """This method will run until specified and listen for incoming updates""" # Set a reasonable timeout when checking for updates timeout = timedelta(minutes=1) while self.updates_thread_running: # Always sleep a bit before each iteration to relax the CPU, # since it's possible to early 'continue' the loop to reach # the next iteration, but we still should to sleep. if self.updates_thread_sleep: sleep(self.updates_thread_sleep) self.updates_thread_sleep = None else: # Longer sleep if we're not expecting updates (only pings) sleep(0.1 if self.on_update_handlers else 1) # Only try to receive updates if we're not waiting to receive a request if not self.waiting_receive: with self.lock: Log.d('Updates thread acquired the lock') try: now = time() # If ping_interval seconds passed since last ping, send a new one if now >= self.ping_time_last + self.ping_interval: self.ping_time_last = now self.send_ping() Log.d('Ping sent from the updates thread') # Exit the loop if we're not expecting to receive any updates if not self.on_update_handlers: Log.d('No updates handlers found, continuing') continue self.updates_thread_receiving = True Log.d( 'Trying to receive updates from the updates thread' ) seq, body = self.transport.receive(timeout) message, remote_msg_id, remote_sequence = self.decode_msg( body) Log.i('Received update from the updates thread') with BinaryReader(message) as reader: self.process_msg(remote_msg_id, remote_sequence, reader) except TimeoutError: Log.d('Receiving updates timed out') # TODO Workaround for issue #50 r = GetStateRequest() try: Log.d( 'Sending GetStateRequest (workaround for issue #50)' ) self.send(r) self.receive(r) except TimeoutError: Log.w( 'Timed out inside a timeout, trying to reconnect...' ) self.reconnect() self.send(r) self.receive(r) except ReadCancelledError: Log.i('Receiving updates cancelled') except OSError: Log.w('OSError on updates thread, %s logging out', 'was' if self.logging_out else 'was not') if self.logging_out: # This error is okay when logging out, means we got disconnected # TODO Not sure why this happens because we call disconnect()… self.set_updates_thread(running=False) else: raise Log.d('Updates thread released the lock') self.updates_thread_receiving = False