Reticulum/Examples/Link.py

291 lines
9.1 KiB
Python
Raw Normal View History

2020-04-27 09:28:14 -06:00
##########################################################
# 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"
2021-05-15 02:57:54 -06:00
APP_NAME = "example_utilities"
2020-04-27 09:28:14 -06:00
##########################################################
#### 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):
2020-08-13 04:15:56 -06:00
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# Randomly create a new identity for our link example
server_identity = RNS.Identity()
2020-04-27 09:28:14 -06:00
2020-08-13 04:15:56 -06:00
# 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"
)
2020-04-27 09:28:14 -06:00
2020-08-13 04:15:56 -06:00
# 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)
2020-04-27 09:28:14 -06:00
2020-08-13 04:15:56 -06:00
# Everything's ready!
# Let's Wait for client requests or user input
server_loop(server_destination)
2020-04-27 09:28:14 -06:00
def server_loop(destination):
2020-08-13 04:15:56 -06:00
# Let the user know that everything is ready
RNS.log(
"Link example "+
RNS.prettyhexrep(destination.hash)+
" running, waiting for a connection."
)
2020-08-13 04:15:56 -06:00
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))
2020-04-27 09:28:14 -06:00
# 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):
2020-08-13 04:15:56 -06:00
global latest_client_link
2020-04-27 09:28:14 -06:00
2020-08-13 04:15:56 -06:00
RNS.log("Client connected")
link.set_link_closed_callback(client_disconnected)
link.set_packet_callback(server_packet_received)
2020-08-13 04:15:56 -06:00
latest_client_link = link
2020-04-27 09:28:14 -06:00
def client_disconnected(link):
2020-08-13 04:15:56 -06:00
RNS.log("Client disconnected")
2020-04-27 09:28:14 -06:00
def server_packet_received(message, packet):
2020-08-13 04:15:56 -06:00
global latest_client_link
2020-04-27 09:28:14 -06:00
2020-08-13 04:15:56 -06:00
# 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("Received data on the link: "+text)
reply_text = "I received \""+text+"\" over the link"
reply_data = reply_text.encode("utf-8")
RNS.Packet(latest_client_link, reply_data).send()
2020-04-27 09:28:14 -06:00
##########################################################
#### 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):
2020-08-13 04:15:56 -06:00
# We need a binary representation of the destination
# hash that was entered on the command line
try:
2022-06-30 12:07:48 -06:00
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
)
2020-08-13 04:15:56 -06:00
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
2021-05-14 13:36:44 -06:00
if not RNS.Transport.has_path(destination_hash):
2020-08-13 04:15:56 -06:00
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
2021-05-16 09:33:39 -06:00
RNS.Transport.request_path(destination_hash)
2021-05-14 13:36:44 -06:00
while not RNS.Transport.has_path(destination_hash):
2020-08-13 04:15:56 -06:00
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"
)
2020-08-13 04:15:56 -06:00
# 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)
2020-08-13 04:15:56 -06:00
# 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)
2020-08-13 04:15:56 -06:00
# Everything is set up, so let's enter a loop
# for the user to interact with the example
client_loop()
2020-04-27 09:28:14 -06:00
def client_loop():
2020-08-13 04:15:56 -06:00
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")
2021-05-20 05:37:48 -06:00
if len(data) <= RNS.Link.MDU:
RNS.Packet(server_link, data).send()
else:
RNS.log(
"Cannot send this packet, the data size of "+
str(len(data))+" bytes exceeds the link packet MDU of "+
str(RNS.Link.MDU)+" bytes",
RNS.LOG_ERROR
)
2020-08-13 04:15:56 -06:00
except Exception as e:
2021-05-20 05:37:48 -06:00
RNS.log("Error while sending data over the link: "+str(e))
2020-08-13 04:15:56 -06:00
should_quit = True
server_link.teardown()
2020-04-27 09:28:14 -06:00
# This function is called when a link
# has been established with the server
def link_established(link):
2020-08-13 04:15:56 -06:00
# We store a reference to the link
# instance for later use
global server_link
server_link = link
2020-04-27 09:28:14 -06:00
2020-08-13 04:15:56 -06:00
# Inform the user that the server is
# connected
RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
2020-04-27 09:28:14 -06:00
# When a link is closed, we'll inform the
# user, and exit the program
def link_closed(link):
2020-08-13 04:15:56 -06:00
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)
2020-04-27 09:28:14 -06:00
# When a packet is received over the link, we
# simply print out the data.
def client_packet_received(message, packet):
2020-08-13 04:15:56 -06:00
text = message.decode("utf-8")
RNS.log("Received data on the link: "+text)
print("> ", end=" ")
sys.stdout.flush()
2020-04-27 09:28:14 -06:00
##########################################################
#### 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__":
2020-08-13 04:15:56 -06:00
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
)
2020-08-13 04:15:56 -06:00
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()