diff --git a/Examples/Link.py b/Examples/Link.py new file mode 100644 index 0000000..d05d716 --- /dev/null +++ b/Examples/Link.py @@ -0,0 +1,232 @@ +########################################################## +# 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_utilitites" + +########################################################## +#### 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.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("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("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.link_closed_callback(client_disconnected) + link.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 + + text = message.decode("utf-8") + RNS.log("Received data on the link: "+text) + + reply_text = "I got \""+text+"\" from you" + 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: + 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.hasPath(destination_hash): + RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...") + RNS.Transport.requestPath(destination_hash) + while not RNS.Transport.hasPath(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.packet_callback(client_packet_received) + + # We'll also set up functions to inform the + # user when the link is established or closed + link.link_established_callback(link_established) + link.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: + 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") + RNS.Packet(server_link, data).send() + +# 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("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() \ No newline at end of file