mirror of
https://github.com/markqvist/Reticulum.git
synced 2024-12-26 16:09:40 -05:00
287 lines
9.1 KiB
Python
287 lines
9.1 KiB
Python
##########################################################
|
||
# This RNS example demonstrates how to set perform #
|
||
# requests and receive responses over a link. #
|
||
##########################################################
|
||
|
||
import os
|
||
import sys
|
||
import time
|
||
import random
|
||
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
|
||
|
||
def random_text_generator(path, data, request_id, link_id, remote_identity, requested_at):
|
||
RNS.log("Generating response to request "+RNS.prettyhexrep(request_id)+" on link "+RNS.prettyhexrep(link_id))
|
||
texts = ["They looked up", "On each full moon", "Becky was upset", "I’ll stay away from it", "The pet shop stocks everything"]
|
||
return texts[random.randint(0, len(texts)-1)]
|
||
|
||
# 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,
|
||
"requestexample"
|
||
)
|
||
|
||
# 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)
|
||
|
||
# We register a request handler for handling incoming
|
||
# requests over any established links.
|
||
server_destination.register_request_handler(
|
||
"/random/text",
|
||
response_generator = random_text_generator,
|
||
allow = RNS.Destination.ALLOW_ALL
|
||
)
|
||
|
||
# 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(
|
||
"Request 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.set_link_closed_callback(client_disconnected)
|
||
latest_client_link = link
|
||
|
||
def client_disconnected(link):
|
||
RNS.log("Client disconnected")
|
||
|
||
|
||
##########################################################
|
||
#### 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(
|
||
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
|
||
)
|
||
|
||
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,
|
||
"requestexample"
|
||
)
|
||
|
||
# And create a link
|
||
link = RNS.Link(server_destination)
|
||
|
||
# We'll 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()
|
||
|
||
else:
|
||
server_link.request(
|
||
"/random/text",
|
||
data = None,
|
||
response_callback = got_response,
|
||
failed_callback = request_failed
|
||
)
|
||
|
||
|
||
except Exception as e:
|
||
RNS.log("Error while sending request over the link: "+str(e))
|
||
should_quit = True
|
||
server_link.teardown()
|
||
|
||
def got_response(request_receipt):
|
||
request_id = request_receipt.request_id
|
||
response = request_receipt.response
|
||
|
||
RNS.log("Got response for request "+RNS.prettyhexrep(request_id)+": "+str(response))
|
||
|
||
def request_received(request_receipt):
|
||
RNS.log("The request "+RNS.prettyhexrep(request_receipt.request_id)+" was received by the remote peer.")
|
||
|
||
def request_failed(request_receipt):
|
||
RNS.log("The request "+RNS.prettyhexrep(request_receipt.request_id)+" failed.")
|
||
|
||
|
||
# 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, hit enter to perform a request, or type in \"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)
|
||
|
||
|
||
##########################################################
|
||
#### 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 request/response example")
|
||
|
||
parser.add_argument(
|
||
"-s",
|
||
"--server",
|
||
action="store_true",
|
||
help="wait for incoming 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() |