########################################################## # This RNS example demonstrates how to set up a link to # # a destination, and pass data back and forth over it. # ########################################################## import os import sys import time import argparse import RNS # Let's define an app name. We'll use this for all # destinations we create. Since this echo example # is part of a range of example utilities, we'll put # them all within the app namespace "example_utilities" APP_NAME = "example_utilities" ########################################################## #### Server Part ######################################### ########################################################## # A reference to the latest client link that connected latest_client_link = None # This initialisation is executed when the users chooses # to run as a server 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 server_loop(destination): # Let the user know that everything is ready RNS.log( f"Link example {RNS.prettyhexrep(destination.hash)} running, waiting for a connection." ) RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)") # We enter a loop that runs until the users exits. # If the user hits enter, we will announce our server # destination on the network, which will let clients # know how to create messages directed towards it. while True: entered = input() destination.announce() RNS.log(f"Sent announce from {RNS.prettyhexrep(destination.hash)}") # When a client establishes a link to our server # destination, this function will be called with # a reference to the link. def client_connected(link): global latest_client_link RNS.log("Client connected") link.set_link_closed_callback(client_disconnected) link.set_packet_callback(server_packet_received) latest_client_link = link def client_disconnected(link): RNS.log("Client disconnected") def server_packet_received(message, packet): global latest_client_link # When data is received over any active link, # it will all be directed to the last client # that connected. text = message.decode("utf-8") RNS.log(f"Received data on the link: {text}") reply_text = f"I received \"{text}\" over the link" reply_data = reply_text.encode("utf-8") RNS.Packet(latest_client_link, reply_data).send() ########################################################## #### Client Part ######################################### ########################################################## # A reference to the server link server_link = None # This initialisation is executed when the users chooses # to run as a client def client(destination_hexhash, configpath): # We need a binary representation of the destination # hash that was entered on the command line try: dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2 if len(destination_hexhash) != dest_len: raise ValueError( f"Destination length is invalid, must be {dest_len} hexadecimal characters ({dest_len // 2} 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 client_loop(): global server_link # Wait for the link to become active while not server_link: time.sleep(0.1) should_quit = False while not should_quit: try: print("> ", end=" ") text = input() # Check if we should quit the example if text == "quit" or text == "q" or text == "exit": should_quit = True server_link.teardown() # If not, send the entered text over the link if text != "": data = text.encode("utf-8") if len(data) <= RNS.Link.MDU: RNS.Packet(server_link, data).send() else: RNS.log( f"Cannot send this packet, the data size of {len(data)} bytes exceeds the link packet MDU of {RNS.Link.MDU} bytes", RNS.LOG_ERROR ) except Exception as e: RNS.log(f"Error while sending data over the link: {e}") should_quit = True server_link.teardown() # This function is called when a link # has been established with the server def link_established(link): # We store a reference to the link # instance for later use global server_link server_link = link # Inform the user that the server is # connected RNS.log("Link established with server, enter some text to send, or \"quit\" to quit") # When a link is closed, we'll inform the # user, and exit the program def link_closed(link): if link.teardown_reason == RNS.Link.TIMEOUT: RNS.log("The link timed out, exiting now") elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED: RNS.log("The link was closed by the server, exiting now") else: RNS.log("Link closed, exiting now") RNS.Reticulum.exit_handler() time.sleep(1.5) os._exit(0) # When a packet is received over the link, we # simply print out the data. def client_packet_received(message, packet): text = message.decode("utf-8") RNS.log(f"Received data on the link: {text}") print("> ", end=" ") sys.stdout.flush() ########################################################## #### Program Startup ##################################### ########################################################## # This part of the program runs at startup, # and parses input of from the user, and then # starts up the desired program mode. if __name__ == "__main__": try: parser = argparse.ArgumentParser(description="Simple link example") parser.add_argument( "-s", "--server", action="store_true", help="wait for incoming link requests from clients" ) parser.add_argument( "--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str ) parser.add_argument( "destination", nargs="?", default=None, help="hexadecimal hash of the server destination", type=str ) args = parser.parse_args() if args.config: configarg = args.config else: configarg = None if args.server: server(configarg) else: if (args.destination == None): print("") parser.print_help() print("") else: client(args.destination, configarg) except KeyboardInterrupt: print("") exit()