Reticulum/Examples/Echo.py

331 lines
12 KiB
Python
Raw Normal View History

2018-04-24 22:20:57 +00:00
##########################################################
# This RNS example demonstrates a simple client/server #
# echo utility. A client can send an echo request to the #
# server, and the server will respond by proving receipt #
# of the packet. #
##########################################################
2018-03-20 11:32:41 +00:00
import argparse
import RNS
2018-03-20 11:32:41 +00:00
# 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 08:57:54 +00:00
APP_NAME = "example_utilities"
2018-03-20 11:32:41 +00:00
2018-04-25 10:23:39 +00:00
##########################################################
#### Server Part #########################################
##########################################################
2018-03-20 11:32:41 +00:00
# This initialisation is executed when the users chooses
# to run as a server
def server(configpath):
2021-10-12 16:09:02 +00:00
global reticulum
2020-08-13 10:15:56 +00:00
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
2024-09-04 09:23:08 +00:00
# Randomly create a new identity for our echo server
server_identity = RNS.Identity()
2020-08-13 10:15:56 +00:00
# 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"
)
2020-08-13 10:15:56 +00:00
# We configure the destination to automatically prove all
2023-10-01 09:46:30 +00:00
# packets addressed to it. By doing this, RNS will automatically
2020-08-13 10:15:56 +00:00
# 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.set_packet_callback(server_callback)
2020-08-13 10:15:56 +00:00
# Everything's ready!
# Let's Wait for client requests or user input
announceLoop(echo_destination)
2018-03-20 11:32:41 +00:00
def announceLoop(destination):
2020-08-13 10:15:56 +00:00
# Let the user know that everything is ready
RNS.log(
"Echo server "+
RNS.prettyhexrep(destination.hash)+
" running, hit enter to manually send an announce (Ctrl-C to quit)"
)
2018-03-20 11:32:41 +00:00
2020-08-13 10:15:56 +00:00
# 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()
2024-10-07 08:44:18 +00:00
RNS.log(f"Sent announce from {RNS.prettyhexrep(destination.hash)}")
2018-03-20 11:32:41 +00:00
2018-04-24 22:20:57 +00:00
def server_callback(message, packet):
2021-10-12 16:09:02 +00:00
global reticulum
2020-08-13 10:15:56 +00:00
# Tell the user that we received an echo request, and
# that we are going to send a reply to the requester.
# Sending the proof is handled automatically, since we
# set up the destination to prove all incoming packets.
2021-10-12 16:04:55 +00:00
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(packet.packet_hash)
reception_snr = reticulum.get_packet_snr(packet.packet_hash)
if reception_rssi != None:
2024-10-07 08:44:18 +00:00
reception_stats += f" [RSSI {reception_rssi} dBm]"
2021-10-12 16:04:55 +00:00
if reception_snr != None:
2024-10-07 08:44:18 +00:00
reception_stats += f" [SNR {reception_snr} dBm]"
2021-10-12 16:04:55 +00:00
else:
if packet.rssi != None:
2024-10-07 08:44:18 +00:00
reception_stats += f" [RSSI {packet.rssi} dBm]"
2021-10-12 16:04:55 +00:00
if packet.snr != None:
2024-10-07 08:44:18 +00:00
reception_stats += f" [SNR {packet.snr} dB]"
2021-10-12 16:04:55 +00:00
2024-10-07 08:44:18 +00:00
RNS.log(f"Received packet from echo client, proof sent{reception_stats}")
2018-04-24 22:20:57 +00:00
2018-04-25 10:23:39 +00:00
##########################################################
#### Client Part #########################################
##########################################################
2018-03-20 11:32:41 +00:00
# This initialisation is executed when the users chooses
# to run as a client
2018-04-25 09:19:19 +00:00
def client(destination_hexhash, configpath, timeout=None):
2021-10-12 16:09:02 +00:00
global reticulum
2020-08-13 10:15:56 +00:00
# We need a binary representation of the destination
# hash that was entered on the command line
try:
2022-06-30 17:12:44 +00:00
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
if len(destination_hexhash) != dest_len:
raise ValueError(
2024-10-07 08:44:18 +00:00
f"Destination length is invalid, must be {dest_len} hexadecimal characters ({dest_len // 2} bytes)."
)
2020-08-13 10:15:56 +00:00
destination_hash = bytes.fromhex(destination_hexhash)
2022-06-30 17:12:44 +00:00
except Exception as e:
RNS.log("Invalid destination entered. Check your input!")
2024-10-07 08:44:18 +00:00
RNS.log(f"{e}\n")
2020-08-13 10:15:56 +00:00
exit()
# We must first initialise Reticulum
reticulum = RNS.Reticulum(configpath)
# We override the loglevel to provide feedback when
# an announce is received
if RNS.loglevel < RNS.LOG_INFO:
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)"
)
2020-08-13 10:15:56 +00:00
# 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:
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
2021-05-14 19:36:44 +00:00
if RNS.Transport.has_path(destination_hash):
2020-08-13 10:15:56 +00:00
# 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"
)
2020-08-13 10:15:56 +00:00
# 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.
2021-05-16 14:15:57 +00:00
echo_request = RNS.Packet(request_destination, RNS.Identity.get_random_hash())
2020-08-13 10:15:56 +00:00
# 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)
2021-05-20 20:31:09 +00:00
packet_receipt.set_timeout_callback(packet_timed_out)
2020-08-13 10:15:56 +00:00
# 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.set_delivery_callback(packet_delivered)
2020-08-13 10:15:56 +00:00
# Tell the user that the echo request was sent
2024-10-07 08:44:18 +00:00
RNS.log(f"Sent echo request to {RNS.prettyhexrep(request_destination.hash)}")
2020-08-13 10:15:56 +00:00
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...")
2023-03-02 11:04:50 +00:00
RNS.log("Hit enter to manually retry once an announce is received.")
2021-05-16 15:33:39 +00:00
RNS.Transport.request_path(destination_hash)
2018-03-20 11:32:41 +00:00
2018-04-25 09:19:19 +00:00
# This function is called when our reply destination
2018-04-04 08:35:21 +00:00
# receives a proof packet.
2018-04-24 22:20:57 +00:00
def packet_delivered(receipt):
2021-10-12 16:09:02 +00:00
global reticulum
2020-08-13 10:15:56 +00:00
if receipt.status == RNS.PacketReceipt.DELIVERED:
rtt = receipt.get_rtt()
2020-08-13 10:15:56 +00:00
if (rtt >= 1):
rtt = round(rtt, 3)
2024-10-07 08:44:18 +00:00
rttstring = f"{rtt} seconds"
2020-08-13 10:15:56 +00:00
else:
rtt = round(rtt*1000, 3)
2024-10-07 08:44:18 +00:00
rttstring = f"{rtt} milliseconds"
2018-03-20 11:32:41 +00:00
2021-10-12 16:04:55 +00:00
reception_stats = ""
if reticulum.is_connected_to_shared_instance:
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
if reception_rssi != None:
2024-10-07 08:44:18 +00:00
reception_stats += f" [RSSI {reception_rssi} dBm]"
2021-10-12 16:04:55 +00:00
if reception_snr != None:
2024-10-07 08:44:18 +00:00
reception_stats += f" [SNR {reception_snr} dB]"
2021-10-12 16:04:55 +00:00
else:
if receipt.proof_packet != None:
if receipt.proof_packet.rssi != None:
2024-10-07 08:44:18 +00:00
reception_stats += f" [RSSI {receipt.proof_packet.rssi} dBm]"
2021-10-12 16:04:55 +00:00
if receipt.proof_packet.snr != None:
2024-10-07 08:44:18 +00:00
reception_stats += f" [SNR {receipt.proof_packet.snr} dB]"
2021-10-12 16:04:55 +00:00
RNS.log(
"Valid reply received from "+
RNS.prettyhexrep(receipt.destination.hash)+
2021-10-12 16:04:55 +00:00
", round-trip time is "+rttstring+
reception_stats
)
2018-03-20 11:32:41 +00:00
2018-04-25 09:19:19 +00:00
# This function is called if a packet times out.
def packet_timed_out(receipt):
2020-08-13 10:15:56 +00:00
if receipt.status == RNS.PacketReceipt.FAILED:
2024-10-07 08:44:18 +00:00
RNS.log(f"Packet {RNS.prettyhexrep(receipt.hash)} timed out")
2018-03-20 11:32:41 +00:00
2018-04-25 10:23:39 +00:00
##########################################################
#### Program Startup #####################################
##########################################################
2018-04-25 09:43:06 +00:00
# This part of the program gets run at startup,
2018-04-25 10:23:39 +00:00
# and parses input from the user, and then starts
# the desired program mode.
2018-03-20 11:32:41 +00:00
if __name__ == "__main__":
2020-08-13 10:15:56 +00:00
try:
parser = argparse.ArgumentParser(description="Simple echo server and client utility")
parser.add_argument(
"-s",
"--server",
action="store_true",
help="wait for incoming packets from clients"
)
parser.add_argument(
"-t",
"--timeout",
action="store",
metavar="s",
default=None,
help="set a reply timeout in seconds",
type=float
)
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 10:15:56 +00:00
args = parser.parse_args()
if args.server:
configarg=None
if args.config:
configarg = args.config
server(configarg)
else:
if args.config:
configarg = args.config
else:
configarg = None
if args.timeout:
timeoutarg = float(args.timeout)
else:
timeoutarg = None
if (args.destination == None):
print("")
parser.print_help()
print("")
else:
client(args.destination, configarg, timeout=timeoutarg)
except KeyboardInterrupt:
print("")
2024-09-04 09:23:08 +00:00
exit()