def program_setup(configpath): # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # Randomly create a new identity for our example identity = RNS.Identity() # Using the identity we just created, we create a destination. # Destinations are endpoints in Reticulum, that can be addressed # and communicated with. Destinations can also announce their # existence, which will let the network know they are reachable # and autoomatically create paths to them, from anywhere else # in the network. destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "minimalsample") # We configure the destination to automatically prove all # packets adressed to it. By doing this, RNS will automatically # generate a proof for each incoming packet and transmit it # back to the sender of that packet. This will let anyone that # tries to communicate with the destination know whether their # communication was received correctly. destination.set_proof_strategy(RNS.Destination.PROVE_ALL) # Everything's ready! # Let's hand over control to the announce loop announceLoop(destination)
def program_setup(configpath, channel=None): # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # If the user did not select a "channel" we use # a default one called "public_information". # This "channel" is added to the destination name- # space, so the user can select different broadcast # channels. if channel == None: channel = "public_information" # We create a PLAIN destination. This is an uncencrypted endpoint # that anyone can listen to and send information to. broadcast_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, APP_NAME, "broadcast", channel) # We specify a callback that will get called every time # the destination receives data. broadcast_destination.packet_callback(packet_callback) # Everything's ready! # Let's hand over control to the main loop broadcastLoop(broadcast_destination)
def client(destination_hexhash, configpath): # We need a binary representation of the destination # hash that was entered on the command line try: if len(destination_hexhash) != 20: raise ValueError( "Destination length is invalid, must be 20 hexadecimal characters (10 bytes)" ) destination_hash = bytes.fromhex(destination_hexhash) except: RNS.log("Invalid destination entered. Check your input!\n") exit() # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # Check if we know a path to the destination if not RNS.Transport.has_path(destination_hash): RNS.log( "Destination is not yet known. Requesting path and waiting for announce to arrive..." ) RNS.Transport.request_path(destination_hash) while not RNS.Transport.has_path(destination_hash): time.sleep(0.1) # Recall the server identity server_identity = RNS.Identity.recall(destination_hash) # Inform the user that we'll begin connecting RNS.log("Establishing link with server...") # When the server identity is known, we set # up a destination server_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "filetransfer", "server") # We also want to automatically prove incoming packets server_destination.set_proof_strategy(RNS.Destination.PROVE_ALL) # And create a link link = RNS.Link(server_destination) # We expect any normal data packets on the link # to contain a list of served files, so we set # a callback accordingly link.set_packet_callback(filelist_received) # We'll also set up functions to inform the # user when the link is established or closed link.set_link_established_callback(link_established) link.set_link_closed_callback(link_closed) # And set the link to automatically begin # downloading advertised resources link.set_resource_strategy(RNS.Link.ACCEPT_ALL) link.set_resource_started_callback(download_began) link.set_resource_concluded_callback(download_concluded) menu()
def server(configpath): # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # Randomly create a new identity for our echo server server_identity = RNS.Identity() # We create a destination that clients can query. We want # to be able to verify echo replies to our clients, so we # create a "single" destination that can receive encrypted # messages. This way the client can send a request and be # certain that no-one else than this destination was able # to read it. echo_destination = RNS.Destination(server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "echo", "request") # We configure the destination to automatically prove all # packets adressed to it. By doing this, RNS will automatically # generate a proof for each incoming packet and transmit it # back to the sender of that packet. echo_destination.set_proof_strategy(RNS.Destination.PROVE_ALL) # Tell the destination which function in our program to # run when a packet is received. We do this so we can # print a log message when the server receives a request echo_destination.packet_callback(server_callback) # Everything's ready! # Let's Wait for client requests or user input announceLoop(echo_destination)
def server(configpath): # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # Randomly create a new identity for our link example server_identity = RNS.Identity() # We create a destination that clients can connect to. We # want clients to create links to this destination, so we # need to create a "single" destination type. server_destination = RNS.Destination( server_identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "linkexample" ) # We configure a function that will get called every time # a new client creates a link to this destination. server_destination.set_link_established_callback(client_connected) # Everything's ready! # Let's Wait for client requests or user input server_loop(server_destination)
def __init__(self, app): RNS.log("Nomad Network Node starting...", RNS.LOG_VERBOSE) self.app = app self.identity = self.app.identity self.destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, "nomadnetwork", "node") self.last_announce = time.time() self.announce_interval = self.app.node_announce_interval self.job_interval = Node.JOB_INTERVAL self.should_run_jobs = True self.app_data = None self.name = self.app.node_name self.register_pages() self.register_files() self.destination.set_link_established_callback(self.peer_connected) if self.name == None: self.name = self.app.peer_settings["display_name"]+"'s Node" RNS.log("Node \""+self.name+"\" ready for incoming connections on "+RNS.prettyhexrep(self.destination.hash), RNS.LOG_VERBOSE) if self.app.node_announce_at_start: def delayed_announce(): time.sleep(Node.START_ANNOUNCE_DELAY) self.announce() da_thread = threading.Thread(target=delayed_announce) da_thread.setDaemon(True) da_thread.start() job_thread = threading.Thread(target=self.__jobs) job_thread.setDaemon(True) job_thread.start()
def program_setup(configpath): # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # Randomly create a new identity for our example identity = RNS.Identity() # Using the identity we just created, we create two destinations # in the "example_utilities.announcesample" application space. # # Destinations are endpoints in Reticulum, that can be addressed # and communicated with. Destinations can also announce their # existence, which will let the network know they are reachable # and autoomatically create paths to them, from anywhere else # in the network. destination_1 = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "announcesample", "fruits") destination_2 = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "announcesample", "noble_gases") # We configure the destinations to automatically prove all # packets adressed to it. By doing this, RNS will automatically # generate a proof for each incoming packet and transmit it # back to the sender of that packet. This will let anyone that # tries to communicate with the destination know whether their # communication was received correctly. destination_1.set_proof_strategy(RNS.Destination.PROVE_ALL) destination_2.set_proof_strategy(RNS.Destination.PROVE_ALL) # We create an announce handler and configure it to only ask for # announces from "example_utilities.announcesample.fruits". # Try changing the filter and see what happens. announce_handler = ExampleAnnounceHandler( aspect_filter="example_utilities.announcesample.fruits") # We register the announce handler with Reticulum RNS.Transport.register_announce_handler(announce_handler) # Everything's ready! # Let's hand over control to the announce loop announceLoop(destination_1, destination_2)
def requestPath(destination_hash): path_request_data = destination_hash + RNS.Identity.getRandomHash() path_request_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request") packet = RNS.Packet(path_request_dst, path_request_data, packet_type=RNS.Packet.DATA, transport_type=RNS.Transport.BROADCAST, header_type=RNS.Packet.HEADER_1) packet.send()
def client(destination_hexhash, configpath): # We need a binary representation of the destination # hash that was entered on the command line try: if len(destination_hexhash) != 20: raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)") destination_hash = bytes.fromhex(destination_hexhash) except: RNS.log("Invalid destination entered. Check your input!\n") exit() # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # Check if we know a path to the destination if not RNS.Transport.has_path(destination_hash): RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...") RNS.Transport.request_path(destination_hash) while not RNS.Transport.has_path(destination_hash): time.sleep(0.1) # Recall the server identity server_identity = RNS.Identity.recall(destination_hash) # Inform the user that we'll begin connecting RNS.log("Establishing link with server...") # When the server identity is known, we set # up a destination server_destination = RNS.Destination( server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "linkexample" ) # And create a link link = RNS.Link(server_destination) # We set a callback that will get executed # every time a packet is received over the # link link.set_packet_callback(client_packet_received) # We'll also set up functions to inform the # user when the link is established or closed link.set_link_established_callback(link_established) link.set_link_closed_callback(link_closed) # Everything is set up, so let's enter a loop # for the user to interact with the example client_loop()
def __init__(self): self.pending_inbound = [] self.pending_outbound = [] self.failed_outbound = [] self.direct_links = {} self.delivery_destinations = {} self.processing_outbound = False self.processing_inbound = False self.identity = RNS.Identity() self.lxmf_query_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, APP_NAME, "query") self.propagation_destination = RNS.Destination(self.identity, RNS.Destination.IN, RNS.Destination.SINGLE, APP_NAME, "propagation") self.__delivery_callback = None job_thread = threading.Thread(target=self.jobloop) job_thread.setDaemon(True) job_thread.start()
def register_delivery_identity(self, identity, display_name=None): delivery_destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, "lxmf", "delivery") delivery_destination.set_packet_callback(self.delivery_packet) delivery_destination.set_link_established_callback( self.delivery_link_established) delivery_destination.display_name = display_name if display_name != None: delivery_destination.set_default_app_data( display_name.encode("utf-8")) self.delivery_destinations[ delivery_destination.hash] = delivery_destination return delivery_destination
def start(): if Transport.identity == None: transport_identity_path = RNS.Reticulum.configdir + "/transportidentity" if os.path.isfile(transport_identity_path): Transport.identity = RNS.Identity.from_file( transport_identity_path) if Transport.identity == None: RNS.log("No valid Transport Identity on disk, creating...", RNS.LOG_VERBOSE) Transport.identity = RNS.Identity() Transport.identity.save(transport_identity_path) else: RNS.log("Loaded Transport Identity from disk", RNS.LOG_VERBOSE) packet_hashlist_path = RNS.Reticulum.configdir + "/packet_hashlist" if os.path.isfile(packet_hashlist_path): try: file = open(packet_hashlist_path, "r") Transport.packet_hashlist = umsgpack.unpackb(file.read()) file.close() except Exception as e: RNS.log( "Could not load packet hashlist from disk, the contained exception was: " + str(e), RNS.LOG_ERROR) # Create transport-specific destinations path_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request") path_request_destination.packet_callback(Transport.pathRequestHandler) thread = threading.Thread(target=Transport.jobloop) thread.setDaemon(True) thread.start() RNS.log("Transport instance " + str(Transport.identity) + " started")
def __init__(self, source_hash, app, initiator=False): self.app = app self.source_hash = source_hash self.send_destination = None self.messages = [] self.messages_path = app.conversationpath + "/" + source_hash self.messages_load_time = None self.source_known = False self.source_trusted = False self.source_blocked = False self.unread = False self.__changed_callback = None self.source_identity = RNS.Identity.recall( bytes.fromhex(self.source_hash)) if self.source_identity: self.source_known = True self.send_destination = RNS.Destination(self.source_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "lxmf", "delivery") if initiator: if not os.path.isdir(self.messages_path): os.makedirs(self.messages_path) if Conversation.created_callback != None: Conversation.created_callback() self.scan_storage() self.trust_level = app.directory.trust_level( bytes.fromhex(self.source_hash)) Conversation.cache_conversation(self)
def client(destination_hexhash, configpath, timeout=None): # We need a binary representation of the destination # hash that was entered on the command line try: if len(destination_hexhash) != 20: raise ValueError("Destination length is invalid, must be 20 hexadecimal characters (10 bytes)") destination_hash = destination_hexhash.decode("hex") except: RNS.log("Invalid destination entered. Check your input!\n") exit() # We must first initialise Reticulum reticulum = RNS.Reticulum(configpath) # We override the loglevel to provide feedback when # an announce is received RNS.loglevel = RNS.LOG_INFO # Tell the user that the client is ready! RNS.log("Echo client ready, hit enter to send echo request to "+destination_hexhash+" (Ctrl-C to quit)") # We enter a loop that runs until the user exits. # If the user hits enter, we will try to send an # echo request to the destination specified on the # command line. while True: raw_input() # Let's first check if RNS knows a path to the destination. # If it does, we'll load the server identity and create a packet if RNS.Transport.hasPath(destination_hash): # To address the server, we need to know it's public # key, so we check if Reticulum knows this destination. # This is done by calling the "recall" method of the # Identity module. If the destination is known, it will # return an Identity instance that can be used in # outgoing destinations. server_identity = RNS.Identity.recall(destination_hash) # We got the correct identity instance from the # recall method, so let's create an outgoing # destination. We use the naming convention: # example_utilities.echo.request # This matches the naming we specified in the # server part of the code. request_destination = RNS.Destination(server_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "echo", "request") # The destination is ready, so let's create a packet. # We set the destination to the request_destination # that was just created, and the only data we add # is a random hash. echo_request = RNS.Packet(request_destination, RNS.Identity.getRandomHash()) # Send the packet! If the packet is successfully # sent, it will return a PacketReceipt instance. packet_receipt = echo_request.send() # If the user specified a timeout, we set this # timeout on the packet receipt, and configure # a callback function, that will get called if # the packet times out. if timeout != None: packet_receipt.set_timeout(timeout) packet_receipt.timeout_callback(packet_timed_out) # We can then set a delivery callback on the receipt. # This will get automatically called when a proof for # this specific packet is received from the destination. packet_receipt.delivery_callback(packet_delivered) # Tell the user that the echo request was sent RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash)) else: # If we do not know this destination, tell the # user to wait for an announce to arrive. RNS.log("Destination is not yet known. Requesting path...") RNS.Transport.requestPath(destination_hash)
def jobs(): outgoing = [] Transport.jobs_running = True try: if not Transport.jobs_locked: # Process receipts list for timed-out packets if time.time( ) > Transport.receipts_last_checked + Transport.receipts_check_interval: for receipt in Transport.receipts: thread = threading.Thread(target=receipt.check_timeout) thread.setDaemon(True) thread.start() if receipt.status != RNS.PacketReceipt.SENT: Transport.receipts.remove(receipt) Transport.receipts_last_checked = time.time() # Process announces needing retransmission if time.time( ) > Transport.announces_last_checked + Transport.announces_check_interval: for destination_hash in Transport.announce_table: announce_entry = Transport.announce_table[ destination_hash] if announce_entry[2] > Transport.PATHFINDER_R: RNS.log( "Dropping announce for " + RNS.prettyhexrep(destination_hash) + ", retries exceeded", RNS.LOG_DEBUG) Transport.announce_table.pop(destination_hash) break else: if time.time() > announce_entry[1]: announce_entry[1] = time.time() + math.pow( Transport.PATHFINDER_C, announce_entry[4] ) + Transport.PATHFINDER_T + Transport.PATHFINDER_RW announce_entry[2] += 1 packet = announce_entry[5] block_rebroadcasts = announce_entry[7] announce_context = RNS.Packet.NONE if block_rebroadcasts: announce_context = RNS.Packet.PATH_RESPONSE announce_data = packet.data announce_identity = RNS.Identity.recall( packet.destination_hash) announce_destination = RNS.Destination( announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown") announce_destination.hash = packet.destination_hash announce_destination.hexhash = announce_destination.hash.encode( "hex_codec") new_packet = RNS.Packet( announce_destination, announce_data, RNS.Packet.ANNOUNCE, context=announce_context, header_type=RNS.Packet.HEADER_2, transport_type=Transport.TRANSPORT, transport_id=Transport.identity.hash) new_packet.hops = announce_entry[4] RNS.log( "Rebroadcasting announce for " + RNS.prettyhexrep(announce_destination.hash) + " with hop count " + str(new_packet.hops), RNS.LOG_DEBUG) outgoing.append(new_packet) Transport.announces_last_checked = time.time() # Cull the packet hashlist if it has reached max size while (len(Transport.packet_hashlist) > Transport.hashlist_maxsize): Transport.packet_hashlist.pop(0) if time.time( ) > Transport.tables_last_culled + Transport.tables_cull_interval: # Cull the reverse table according to timeout for truncated_packet_hash in Transport.reverse_table: reverse_entry = Transport.reverse_table[ truncated_packet_hash] if time.time( ) > reverse_entry[2] + Transport.REVERSE_TIMEOUT: Transport.reverse_table.pop(truncated_packet_hash) # Cull the link table according to timeout for link_id in Transport.link_table: link_entry = Transport.link_table[link_id] if time.time( ) > link_entry[0] + Transport.LINK_TIMEOUT: Transport.link_table.pop(link_id) # Cull the destination table in some way for destination_hash in Transport.destination_table: destination_entry = Transport.destination_table[ destination_hash] if time.time() > destination_entry[ 0] + Transport.DESTINATION_TIMEOUT: Transport.destination_table.pop(destination_hash) Transport.tables_last_culled = time.time() except Exception as e: RNS.log("An exception occurred while running Transport jobs.", RNS.LOG_ERROR) RNS.log("The contained exception was: " + str(e), RNS.LOG_ERROR) traceback.print_exc() Transport.jobs_running = False for packet in outgoing: packet.send()
def unpack_from_bytes(lxmf_bytes): destination_hash = lxmf_bytes[:LXMessage.DESTINATION_LENGTH] source_hash = lxmf_bytes[LXMessage.DESTINATION_LENGTH:2 * LXMessage.DESTINATION_LENGTH] signature = lxmf_bytes[2 * LXMessage.DESTINATION_LENGTH:2 * LXMessage.DESTINATION_LENGTH + LXMessage.SIGNATURE_LENGTH] packed_payload = lxmf_bytes[2 * LXMessage.DESTINATION_LENGTH + LXMessage.SIGNATURE_LENGTH:] hashed_part = b"" + destination_hash + source_hash + packed_payload message_hash = RNS.Identity.full_hash(hashed_part) signed_part = b"" + hashed_part + message_hash unpacked_payload = msgpack.unpackb(packed_payload) timestamp = unpacked_payload[0] title_bytes = unpacked_payload[1] content_bytes = unpacked_payload[2] fields = unpacked_payload[3] destination_identity = RNS.Identity.recall(destination_hash) if destination_identity != None: destination = RNS.Destination(destination_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "delivery") else: destination = None source_identity = RNS.Identity.recall(source_hash) if source_identity != None: source = RNS.Destination(source_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "delivery") else: source = None message = LXMessage(destination=destination, source=source, content="", title="", fields=fields, destination_hash=destination_hash, source_hash=source_hash) message.hash = message_hash message.signature = signature message.incoming = True message.timestamp = timestamp message.packed = lxmf_bytes message.packed_size = len(lxmf_bytes) message.set_title_from_bytes(title_bytes) message.set_content_from_bytes(content_bytes) try: if source: if source.identity.validate(signature, signed_part): message.signature_validated = True else: message.signature_validated = False message.unverified_reason = LXMessage.SIGNATURE_INVALID else: signature_validated = False message.unverified_reason = LXMessage.SOURCE_UNKNOWN RNS.log( "Unpacked LXMF message signature could not be validated, since source identity is unknown", RNS.LOG_DEBUG) except Exception as e: message.signature_validated = False RNS.log( "Error while validating LXMF message signature. The contained exception was: " + str(e), RNS.LOG_ERROR) return message
def jobs(): outgoing = [] Transport.jobs_running = True try: if not Transport.jobs_locked: # Process receipts list for timed-out packets if time.time( ) > Transport.receipts_last_checked + Transport.receipts_check_interval: for receipt in Transport.receipts: thread = threading.Thread(target=receipt.check_timeout) thread.setDaemon(True) thread.start() if receipt.status != RNS.PacketReceipt.SENT: Transport.receipts.remove(receipt) Transport.receipts_last_checked = time.time() # Process announces needing retransmission if time.time( ) > Transport.announces_last_checked + Transport.announces_check_interval: for destination_hash in Transport.announce_table: announce_entry = Transport.announce_table[ destination_hash] # TODO: remove comment and log output # [time_heard, retransmit_timeout, retries, received_from, packet.hops, packet] # RNS.log("Announce entry retries: "+str(announce_entry[2]), RNS.LOG_INFO) # RNS.log("Max retries: "+str(Transport.PATHFINDER_R), RNS.LOG_INFO) if announce_entry[2] > Transport.PATHFINDER_R: RNS.log( "Dropping announce for " + RNS.prettyhexrep(destination_hash) + ", retries exceeded", RNS.LOG_DEBUG) Transport.announce_table.pop(destination_hash) break else: if time.time() > announce_entry[1]: announce_entry[1] = time.time() + math.pow( Transport.PATHFINDER_C, announce_entry[4] ) + Transport.PATHFINDER_T + Transport.PATHFINDER_RW announce_entry[2] += 1 packet = announce_entry[5] announce_data = packet.data announce_identity = RNS.Identity.recall( packet.destination_hash) announce_destination = RNS.Destination( announce_identity, RNS.Destination.OUT, RNS.Destination.SINGLE, "unknown", "unknown") announce_destination.hash = packet.destination_hash announce_destination.hexhash = announce_destination.hash.encode( "hex_codec") new_packet = RNS.Packet( announce_destination, announce_data, RNS.Packet.ANNOUNCE, header_type=RNS.Packet.HEADER_2, transport_type=Transport.TRANSPORT, transport_id=Transport.identity.hash) new_packet.hops = announce_entry[4] RNS.log( "Rebroadcasting announce for " + RNS.prettyhexrep(announce_destination.hash) + " with hop count " + str(new_packet.hops), RNS.LOG_DEBUG) outgoing.append(new_packet) Transport.announces_last_checked = time.time() # Cull the packet hashlist if it has reached max size while (len(Transport.packet_hashlist) > Transport.hashlist_maxsize): Transport.packet_hashlist.pop(0) except Exception as e: RNS.log("An exception occurred while running Transport jobs.", RNS.LOG_ERROR) RNS.log("The contained exception was: " + str(e), RNS.LOG_ERROR) traceback.print_exc() Transport.jobs_running = False for packet in outgoing: packet.send()
def __load(self): # If an established link exists, but it doesn't match the target # destination, we close and clear it. if self.link != None and self.link.destination.hash != self.destination_hash: self.link.teardown() self.link = None # If no link to the destination exists, we create one. if self.link == None: if not RNS.Transport.has_path(self.destination_hash): self.status = Browser.NO_PATH self.update_display() RNS.Transport.request_path(self.destination_hash) self.status = Browser.PATH_REQUESTED self.update_display() pr_time = time.time() while not RNS.Transport.has_path(self.destination_hash): now = time.time() if now > pr_time + self.timeout: self.request_timeout() return time.sleep(0.25) self.status = Browser.ESTABLISHING_LINK self.update_display() identity = RNS.Identity.recall(self.destination_hash) destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, self.app_name, self.aspects) self.link = RNS.Link(destination, established_callback=self.link_established, closed_callback=self.link_closed) while self.status == Browser.ESTABLISHING_LINK: time.sleep(0.1) if self.status != Browser.LINK_ESTABLISHED: return self.update_display() # Send the request self.status = Browser.REQUESTING self.response_progress = 0 self.response_size = None self.response_transfer_size = None self.saved_file_name = None self.update_display() receipt = self.link.request(self.path, data=None, response_callback=self.response_received, failed_callback=self.request_failed, progress_callback=self.response_progressed) if receipt: self.last_request_receipt = receipt self.last_request_id = receipt.request_id self.status = Browser.REQUEST_SENT self.update_display() else: self.link.teardown()