def send(self, request): """Sends the specified MTProtoRequest, previously sending any message which needed confirmation. This also pauses the updates thread""" # Only cancel the receive *if* it was the # updates thread who was receiving. We do # not want to cancel other pending requests! if self.updates_thread_receiving: Log.i('Cancelling updates receive from send()...') self.transport.cancel_receive() # Now only us can be using this method with self.lock: Log.d('send() acquired the lock') # Set the flag to true so the updates thread stops trying to receive self.waiting_receive = True # If any message needs confirmation send an AckRequest first if self.need_confirmation: msgs_ack = MsgsAck(self.need_confirmation) with BinaryWriter() as writer: msgs_ack.on_send(writer) self.send_packet(writer.get_bytes(), msgs_ack) del self.need_confirmation[:] # Finally send our packed request with BinaryWriter() as writer: request.on_send(writer) self.send_packet(writer.get_bytes(), request) # And update the saved session self.session.save() Log.d('send() released the lock')
def handle_update(self, msg_id, sequence, reader): tlobject = reader.tgread_object() Log.d('Handling update for object %s', repr(tlobject)) for handler in self.on_update_handlers: handler(tlobject) return False
def handle_bad_msg_notification(self, msg_id, sequence, reader): Log.d('Handling bad message notification') reader.read_int(signed=False) # code reader.read_long(signed=False) # request_id reader.read_int() # request_sequence error_code = reader.read_int() raise BadMessageError(error_code)
def handle_gzip_packed(self, msg_id, sequence, reader, request): Log.d('Handling gzip packed data') reader.read_int(signed=False) # code packed_data = reader.tgread_bytes() unpacked_data = gzip.decompress(packed_data) with BinaryReader(unpacked_data) as compressed_reader: return self.process_msg(msg_id, sequence, compressed_reader, request)
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_container(self, msg_id, sequence, reader, request): Log.d('Handling container') reader.read_int(signed=False) # code size = reader.read_int() for _ in range(size): inner_msg_id = reader.read_long(signed=False) reader.read_int() # inner_sequence inner_length = reader.read_int() begin_position = reader.tell_position() if not self.process_msg(inner_msg_id, sequence, reader, request): reader.set_position(begin_position + inner_length) 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 handle_bad_server_salt(self, msg_id, sequence, reader, request): Log.d('Handling bad server salt') reader.read_int(signed=False) # code reader.read_long(signed=False) # bad_msg_id reader.read_int() # bad_msg_seq_no reader.read_int() # error_code new_salt = reader.read_long(signed=False) self.session.salt = new_salt if request is None: raise ValueError( 'Tried to handle a bad server salt with no request specified') # Resend self.send(request) return True
def receive(self, request, timeout=timedelta(seconds=5)): """Receives the specified MTProtoRequest ("fills in it" the received data). This also restores the updates thread. An optional timeout can be specified to cancel the operation if no data has been read after its time delta""" with self.lock: Log.d('receive() acquired the lock') # Don't stop trying to receive until we get the request we wanted while not request.confirm_received: Log.i('Trying to .receive() the request result...') seq, body = self.transport.receive(timeout) message, remote_msg_id, remote_sequence = self.decode_msg(body) with BinaryReader(message) as reader: self.process_msg(remote_msg_id, remote_sequence, reader, request) Log.i('Request result received') # We can now set the flag to False thus resuming the updates thread self.waiting_receive = False Log.d('receive() released the lock')
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