Compare commits

..

74 Commits

Author SHA1 Message Date
Mark Qvist c7e5f4612a Updated documentation. 2021-09-11 16:11:44 +02:00
Mark Qvist f80e09cb5c Included six internally. 2021-09-11 16:03:35 +02:00
Mark Qvist 91d94f2f6f Fixed incorrect transfer size indications on single-packet request responses with msgpacked dictionaries as payloads. 2021-09-10 21:35:30 +02:00
Mark Qvist 53ca86ebfc Merge branch 'master' of github.com:markqvist/Reticulum 2021-09-05 20:06:21 +02:00
Mark Qvist 534bb28900 Fixed removal of non-existing receipts. 2021-09-05 20:05:12 +02:00
Mark Qvist 0de5ec73ad Merge pull request #8 from xtoddx/master
Record dependency on six
2021-09-05 15:02:49 +02:00
xtoddx c0f627b50b Record dependency on six 2021-09-04 22:58:42 -04:00
Mark Qvist 5629a062a5 Added resource window timeout recalculations during transfer. 2021-09-03 22:53:25 +02:00
Mark Qvist 83232f0446 Work on resource timing. 2021-09-03 22:20:16 +02:00
Mark Qvist aa794514b3 Work on resource timing. 2021-09-03 22:01:58 +02:00
Mark Qvist 07cf180ea8 Added continous resource timeout adjustment. Fixes missing response timeout check. 2021-09-03 21:08:20 +02:00
Mark Qvist 42a3d23e99 Optimised resource transfer timings. Improved request/response timeout handling. 2021-09-03 18:53:37 +02:00
Mark Qvist d28c888d1c Improved link request/response handling. 2021-09-03 16:24:47 +02:00
Mark Qvist 58d48c18f4 Improved link request/response handling. 2021-09-03 16:23:31 +02:00
Mark Qvist ecf0c55fd6 Improved link establishment. 2021-09-03 16:14:08 +02:00
Mark Qvist 32e4c262ef Improved link timeout handling. 2021-09-03 15:47:42 +02:00
Mark Qvist f87a6a57df Added link error handling. 2021-09-03 15:08:38 +02:00
Mark Qvist 6373f159f8 Added link error handling. 2021-09-03 14:42:59 +02:00
Mark Qvist ad9f548eeb Improved request timeout calculation and handling. 2021-09-03 14:22:53 +02:00
Mark Qvist 425f0153d0 Added flow control timeouts to AX.25 interface and optimised timeouts. 2021-09-03 10:56:49 +02:00
Mark Qvist cd9daaefee Removed option to allow unencrypted links. 2021-09-03 10:13:48 +02:00
Mark Qvist 0fe76d50f6 Improved documentation. 2021-09-02 20:35:42 +02:00
Mark Qvist 9562803bb3 Optimised sent Fernet token data. 2021-09-02 18:42:17 +02:00
Mark Qvist e9c89209c7 Optimised sent Fernet token data. 2021-09-02 18:34:58 +02:00
Mark Qvist cd8de64201 Implemented ability to change MTU. 2021-09-02 18:00:03 +02:00
Mark Qvist 40f7a6d359 Added resource HMU/part request hash filter. Fixes #7. 2021-09-02 14:44:53 +02:00
Mark Qvist 0c96508cca Updated default config. 2021-08-29 13:48:12 +02:00
Mark Qvist 1fd59f1a02 Fixed resource sequencing fail handling. 2021-08-29 13:46:31 +02:00
Mark Qvist 0a0d0af821 Updated docs. 2021-08-29 13:13:51 +02:00
Mark Qvist b694cbdc91 Improved announce handling for local clients. 2021-08-29 12:43:54 +02:00
Mark Qvist 71c3333e10 Improved announce handling for local clients. 2021-08-29 12:28:30 +02:00
Mark Qvist 972fcdee22 Fixed identity saving. 2021-08-29 01:24:21 +02:00
Mark Qvist 17dbfe6401 Updated speed test example. 2021-08-29 00:13:50 +02:00
Mark Qvist 781cb4712d Fixed request packet receipts timing out in spite of delivery. 2021-08-28 23:53:51 +02:00
Mark Qvist cdb08325cc Fixed timeout calculation condition. 2021-08-28 20:34:41 +02:00
Mark Qvist 62d954d7bf Fixed timeout calculation condition. 2021-08-28 20:21:50 +02:00
Mark Qvist 4bbf1ae57d Updated docs 2021-08-28 20:10:00 +02:00
Mark Qvist 2678aeb6a1 Improved timeout calculation and handling. 2021-08-28 20:01:01 +02:00
Mark Qvist 6d441dac02 Better resource advertisement timeout. 2021-08-28 14:41:25 +02:00
Mark Qvist 66b2be87f4 Added speedtest example. 2021-08-27 22:59:34 +02:00
Mark Qvist 2e7126ef39 Cleaned up log statements 2021-08-27 22:51:16 +02:00
Mark Qvist c0f909850b Updated docs. 2021-08-27 19:54:57 +02:00
Mark Qvist a199e4c929 Improved link and resource callbacks and resource handling. 2021-08-27 19:52:48 +02:00
Mark Qvist da13ee9cb9 Updated manual 2021-08-21 21:27:56 +02:00
Mark Qvist f719d44db5 Transport optimisations 2021-08-21 20:23:36 +02:00
Mark Qvist af890d91d2 Fixed race condition in outbound handling packet filter 2021-08-21 19:42:01 +02:00
Mark Qvist 242941fec4 Updated readme 2021-08-21 15:33:14 +02:00
Mark Qvist 0f79197945 Updated docs 2021-08-21 15:19:24 +02:00
Mark Qvist 212518a345 Implemented requests and responses of arbitrary sizes using resources. 2021-08-21 14:52:31 +02:00
Mark Qvist 1dc6655017 Implemented request and response API 2021-08-20 23:29:06 +02:00
Mark Qvist 69930e5652 Updated default config 2021-08-20 11:23:35 +02:00
Mark Qvist 2b8b95da2b Added config options for TCP server interface binding to network interface instead of IP. 2021-08-19 20:13:53 +02:00
Mark Qvist 6382409194 Added config options for UDP interface binding to network interface instead of IP. 2021-08-19 19:56:35 +02:00
Mark Qvist 4fd3d26714 Fixed UDP broadcast echo packets not being filtered. 2021-08-19 17:05:07 +02:00
Mark Qvist 8b6870fad8 Updated docs 2021-08-19 14:11:22 +02:00
Mark Qvist 384a7db974 Implemented link peer identification 2021-08-19 14:10:37 +02:00
Mark Qvist 772ae44ab8 Updated readme and docs 2021-07-25 23:48:18 +02:00
Mark Qvist d326df6c5a Cleanup 2021-05-20 23:31:26 +02:00
Mark Qvist 4269c48293 Updated readme 2021-05-20 23:16:19 +02:00
Mark Qvist 719764fd81 Updated documentation 2021-05-20 22:35:10 +02:00
Mark Qvist 5ccbc825fd Updated examples 2021-05-20 22:31:09 +02:00
Mark Qvist ad67c553d7 Added exception when trying to remember an invalid public key 2021-05-20 22:30:54 +02:00
Mark Qvist d68cfaa8f7 Optimised link establishment 2021-05-20 20:32:08 +02:00
Mark Qvist cf9934810b Updated documentation 2021-05-20 18:37:59 +02:00
Mark Qvist e8ca88377a Updated documentation 2021-05-20 18:37:12 +02:00
Mark Qvist bf410e006f Updated docs 2021-05-20 17:18:38 +02:00
Mark Qvist db527b6759 Optimised announces to 151 bytes 2021-05-20 16:56:08 +02:00
Mark Qvist 9c995b33dd Updated documentation 2021-05-20 16:06:12 +02:00
Mark Qvist f18fb35aba Updated documentation 2021-05-20 15:31:58 +02:00
Mark Qvist ce405b9252 Migrated all asymmetric crypto operations to ECIES on Curve25519. 2021-05-20 15:31:38 +02:00
Mark Qvist 7f5625a526 Cleanup 2021-05-20 13:38:57 +02:00
Mark Qvist e8fb435f00 Updated link example 2021-05-20 13:37:48 +02:00
Mark Qvist f880edbeb8 Store GROUP destination symmetric key as bytes instead of base64 2021-05-20 12:44:12 +02:00
Mark Qvist 2b97c89566 Updated docs 2021-05-20 10:28:58 +02:00
53 changed files with 4885 additions and 1224 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ def program_setup(configpath, channel=None):
# We specify a callback that will get called every time
# the destination receives data.
broadcast_destination.packet_callback(packet_callback)
broadcast_destination.set_packet_callback(packet_callback)
# Everything's ready!
# Let's hand over control to the main loop
+4 -4
View File
@@ -52,7 +52,7 @@ def server(configpath):
# 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.packet_callback(server_callback)
echo_destination.set_packet_callback(server_callback)
# Everything's ready!
# Let's Wait for client requests or user input
@@ -170,12 +170,12 @@ def client(destination_hexhash, configpath, timeout=None):
# the packet times out.
if timeout != None:
packet_receipt.set_timeout(timeout)
packet_receipt.timeout_callback(packet_timed_out)
packet_receipt.set_timeout_callback(packet_timed_out)
# 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.delivery_callback(packet_delivered)
packet_receipt.set_delivery_callback(packet_delivered)
# Tell the user that the echo request was sent
RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash))
@@ -189,7 +189,7 @@ def client(destination_hexhash, configpath, timeout=None):
# receives a proof packet.
def packet_delivered(receipt):
if receipt.status == RNS.PacketReceipt.DELIVERED:
rtt = receipt.rtt()
rtt = receipt.get_rtt()
if (rtt >= 1):
rtt = round(rtt, 3)
rttstring = str(rtt)+" seconds"
+17 -13
View File
@@ -65,7 +65,7 @@ def server(configpath, path):
# 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)
server_destination.set_link_established_callback(client_connected)
# Everything's ready!
# Let's Wait for client requests or user input
@@ -102,7 +102,7 @@ def client_connected(link):
if os.path.isdir(serve_path):
RNS.log("Client connected, sending file list...")
link.link_closed_callback(client_disconnected)
link.set_link_closed_callback(client_disconnected)
# We pack a list of files for sending in a packet
data = umsgpack.packb(list_files())
@@ -114,8 +114,8 @@ def client_connected(link):
list_packet = RNS.Packet(link, data)
list_receipt = list_packet.send()
list_receipt.set_timeout(APP_TIMEOUT)
list_receipt.delivery_callback(list_delivered)
list_receipt.timeout_callback(list_timeout)
list_receipt.set_delivery_callback(list_delivered)
list_receipt.set_timeout_callback(list_timeout)
else:
RNS.log("Too many files in served directory!", RNS.LOG_ERROR)
RNS.log("You should implement a function to split the filelist over multiple packets.", RNS.LOG_ERROR)
@@ -125,7 +125,7 @@ def client_connected(link):
# open until the client requests a file. We'll
# configure a function that get's called when
# the client sends a packet with a file request.
link.packet_callback(client_request)
link.set_packet_callback(client_request)
else:
RNS.log("Client connected, but served path no longer exists!", RNS.LOG_ERROR)
link.teardown()
@@ -135,7 +135,12 @@ def client_disconnected(link):
def client_request(message, packet):
global serve_path
filename = message.decode("utf-8")
try:
filename = message.decode("utf-8")
except Exception as e:
filename = None
if filename in list_files():
try:
# If we have the requested file, we'll
@@ -254,18 +259,18 @@ def client(destination_hexhash, configpath):
# We expect any normal data packets on the link
# to contain a list of served files, so we set
# a callback accordingly
link.packet_callback(filelist_received)
link.set_packet_callback(filelist_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)
link.set_link_established_callback(link_established)
link.set_link_closed_callback(link_closed)
# And set the link to automatically begin
# downloading advertised resources
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
link.resource_started_callback(download_began)
link.resource_concluded_callback(download_concluded)
link.set_resource_started_callback(download_began)
link.set_resource_concluded_callback(download_concluded)
menu()
@@ -353,7 +358,7 @@ def print_menu():
print("")
while menu_mode == "downloading":
global current_download
percent = round(current_download.progress() * 100.0, 1)
percent = round(current_download.get_progress() * 100.0, 1)
print(("\rProgress: "+str(percent)+" % "), end=' ')
sys.stdout.flush()
time.sleep(0.1)
@@ -497,7 +502,6 @@ def download_concluded(resource):
saved_filename = current_filename
if resource.status == RNS.Resource.COMPLETE:
counter = 0
while os.path.isfile(saved_filename):
+310
View File
@@ -0,0 +1,310 @@
##########################################################
# This RNS example demonstrates how to set up a link to #
# a destination, and identify the initiator to it's peer #
##########################################################
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,
"identifyexample"
)
# 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(
"Link identification 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)
link.set_packet_callback(server_packet_received)
link.set_remote_identified_callback(remote_identified)
latest_client_link = link
def client_disconnected(link):
RNS.log("Client disconnected")
def remote_identified(identity):
RNS.log("Remote identified as: "+str(identity))
def server_packet_received(message, packet):
global latest_client_link
# Get the originating identity for display
remote_peer = "unidentified peer"
if packet.link.get_remote_identity() != None:
remote_peer = str(packet.link.get_remote_identity())
# 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 from "+remote_peer+": "+text)
reply_text = "I received \""+text+"\" over the link from "+remote_peer
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
# A reference to the client identity
client_identity = None
# This initialisation is executed when the users chooses
# to run as a client
def client(destination_hexhash, configpath):
global client_identity
# 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)
# Create a new client identity
client_identity = RNS.Identity()
RNS.log(
"Client created new identity "+
str(client_identity)
)
# 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,
"identifyexample"
)
# 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(
"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
)
except Exception as e:
RNS.log("Error while sending data over the link: "+str(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, client_identity
server_link = link
# Inform the user that the server is
# connected
RNS.log("Link established with server, identifying to remote peer...")
link.identify(client_identity)
# 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()
+17 -7
View File
@@ -44,7 +44,7 @@ def server(configpath):
# 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)
server_destination.set_link_established_callback(client_connected)
# Everything's ready!
# Let's Wait for client requests or user input
@@ -76,8 +76,8 @@ def client_connected(link):
global latest_client_link
RNS.log("Client connected")
link.link_closed_callback(client_disconnected)
link.packet_callback(server_packet_received)
link.set_link_closed_callback(client_disconnected)
link.set_packet_callback(server_packet_received)
latest_client_link = link
def client_disconnected(link):
@@ -149,12 +149,12 @@ def client(destination_hexhash, configpath):
# We set a callback that will get executed
# every time a packet is received over the
# link
link.packet_callback(client_packet_received)
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.link_established_callback(link_established)
link.link_closed_callback(link_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
@@ -181,8 +181,18 @@ def client_loop():
# If not, send the entered text over the link
if text != "":
data = text.encode("utf-8")
RNS.Packet(server_link, data).send()
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
)
except Exception as e:
RNS.log("Error while sending data over the link: "+str(e))
should_quit = True
server_link.teardown()
+283
View File
@@ -0,0 +1,283 @@
##########################################################
# 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, remote_identity, requested_at):
RNS.log("Generating response to request "+RNS.prettyhexrep(request_id))
texts = ["They looked up", "On each full moon", "Becky was upset", "Ill 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:
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.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()
+337
View File
@@ -0,0 +1,337 @@
##########################################################
# This RNS example demonstrates a simple speedtest #
# program to measure link throughput. #
##########################################################
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.
APP_NAME = "example_utilities"
##########################################################
#### Server Part #########################################
##########################################################
latest_client_link = None
first_packet_at = None
last_packet_at = None
received_data = 0
rc = 0
data_cap = 2*1024*1024
printed = False
# 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,
"speedtest"
)
# 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(
"Speedtest "+
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, first_packet_at, rc
RNS.log("Client connected")
first_packet_at = time.time()
rc = 0
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")
# A convenience function for printing a human-
# readable file size
def size_str(num, suffix='B'):
units = ['','Ki','Mi','Gi','Ti','Pi','Ei','Zi']
last_unit = 'Yi'
if suffix == 'b':
num *= 8
units = ['','K','M','G','T','P','E','Z']
last_unit = 'Y'
for unit in units:
if abs(num) < 1024.0:
return "%3.2f %s%s" % (num, unit, suffix)
num /= 1024.0
return "%.2f %s%s" % (num, last_unit, suffix)
def server_packet_received(message, packet):
global latest_client_link, first_packet_at, last_packet_at, received_data, rc, data_cap
received_data += len(packet.data)
rc += 1
if rc >= 50:
RNS.log(size_str(received_data))
rc = 0
if received_data > data_cap:
rcv_d = received_data
received_data = 0
rc = 0
last_packet_at = time.time()
# Print statistics
download_time = last_packet_at-first_packet_at
hours, rem = divmod(download_time, 3600)
minutes, seconds = divmod(rem, 60)
timestring = "{:0>2}:{:0>2}:{:05.2f}".format(int(hours),int(minutes),seconds)
print("")
print("")
print("--- Statistics -----")
print("\tTime taken : "+timestring)
print("\tData transferred : "+size_str(rcv_d))
print("\tTransfer rate : "+size_str(rcv_d/download_time, suffix='b')+"/s")
print("")
sys.stdout.flush()
latest_client_link.teardown()
time.sleep(0.2)
rc = 0
received_data = 0
# latest_client_link.teardown()
# os._exit(0)
##########################################################
#### 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.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,
"speedtest"
)
# And create a link
link = RNS.Link(server_destination)
# 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:
text = input()
# Check if we should quit the example
if text == "quit" or text == "q" or text == "exit":
should_quit = True
server_link.teardown()
except Exception as e:
raise e
# 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, data_cap, printed
server_link = link
data_sent = 0
# Inform the user that the server is
# connected
RNS.log("Link established with server,sending...")
rd = os.urandom(RNS.Link.MDU)
started = time.time()
while link.status == RNS.Link.ACTIVE and data_sent < data_cap*1.25:
RNS.Packet(server_link, rd, create_receipt=False).send()
data_sent += len(rd)
if data_sent > data_cap and not printed:
printed = True
ended = time.time()
# Print statistics
download_time = ended-started
hours, rem = divmod(download_time, 3600)
minutes, seconds = divmod(rem, 60)
timestring = "{:0>2}:{:0>2}:{:05.2f}".format(int(hours),int(minutes),seconds)
print("")
print("")
print("--- Statistics -----")
print("\tTime taken : "+timestring)
print("\tData transferred : "+size_str(data_sent))
print("\tTransfer rate : "+size_str(data_sent/download_time, suffix='b')+"/s")
print("")
sys.stdout.flush()
time.sleep(0.1)
# 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)
def client_packet_received(message, packet):
pass
##########################################################
#### 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="Speedtest 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()
-54
View File
@@ -1,54 +0,0 @@
Reticulum Wire Format
Header Types
-----------------
type 1 00 Two byte header, one 10 byte address field
type 2 01 Two byte header, two 10 byte address fields
type 3 10 Reserved
type 4 11 Reserved
Propagation Types
-----------------
broadcast 00
transport 01
reserved 10
reserved 11
Destination Types
-----------------
single 00
group 01
plain 10
link 11
Packet Types
-----------------
data 00
announce 01
link request 10
proof 11
+- Packet Example -+
01010000 00000100 [ADDR1, 10 bytes] [ADDR2, 10 bytes] [CONTEXT, 1 byte] [DATA]
| | | | |
| | | | +-- Hops = 4
| | | +------- DATA packet
| | +--------- SINGLE destination
| +----------- TRANSPORT propagation type
+------------- HEADER_2, two byte header, two address fields
+- Packet Example -+
00000000 00000111 [ADDR1, 10 bytes] [CONTEXT, 1 byte] [DATA]
| | | | |
| | | | +-- Hops = 7
| | | +------- DATA packet
| | +--------- SINGLE destination
| +----------- BROADCAST propagation type
+------------- HEADER_1, two byte header, one address field
+12 -41
View File
@@ -18,12 +18,13 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
## Notable Features
- Coordination-less globally unique adressing and identification
- Fully self-configuring multi-hop routing
- Asymmetric RSA encryption and signatures as basis for all communication
- Perfect Forward Secrecy on links with ephemereal Elliptic Curve Diffie-Hellman keys (on Curve25519)
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for encryption on links and to group destinations
- Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
- Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for encryption
- AES-128 in CBC mode with PKCS7 padding
- HMAC using SHA256 for authentication
- IVs are generated through os.urandom()
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
- Unforgeable packet delivery confirmations
- A variety of supported interface types
- An intuitive and easy-to-use API
@@ -31,9 +32,13 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
- Reticulum can handle a few bytes of data or files of many gigabytes
- Sequencing, transfer coordination and checksumming is automatic
- The API is very easy to use, and provides transfer progress
- Lightweight, flexible and expandable Request/Response mechanism
- Efficient link establishment
- Total bandwidth cost of setting up a link is 3 packets totalling 237 bytes
- Low cost of keeping links open at only 0.62 bits per second
## Where can Reticulum be used?
On practically any hardware that can support at least a half-duplex channel with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, ad-hoc WiFi, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for.
Over practically any medium that can support at least a half-duplex channel with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios, modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes, ad-hoc WiFi, free-space optical links and similar systems are all examples of the types of interfaces Reticulum was designed for.
An open-source LoRa-based interface called [RNode](https://unsigned.io/projects/rnode/) has been designed specifically for use with Reticulum. It is possible to build yourself, or it can be purchased as a complete transceiver that just needs a USB connection to the host.
@@ -70,49 +75,15 @@ Some countries still ban the use of encryption when operating under an amateur r
- pyserial
## How do I get started?
Full documentation and tutorials are coming with the stable alpha release. Until then, you are mostly on your own. If you want to experiment already, you could take a look in the "Examples" folder, for some well-documented example programs. The default configuration file created by Reticulum on the first run is also worth reading. Be sure to also read the [Reticulum Overview Document](http://unsigned.io/wp-content/uploads/2018/04/Reticulum_Overview_v0.4.pdf).
The best way to get started with the Reticulum Network Stack depends on what
you want to do. For full details and examples, have a look at the [Getting Started Fast](https://markqvist.github.io/Reticulum/manual/gettingstartedfast.html) section of the [Reticulum Manual](https://markqvist.github.io/Reticulum/manual/).
If you just need Reticulum as a dependency for another application, the easiest way is probably via pip:
If you just need Reticulum as a dependency for another application, the easiest way is via pip:
```bash
pip3 install rns
```
For Reticulum development, you might want to get the latest source from GitHub. In that case, don't use pip, but try this recipe:
```bash
# Install dependencies
pip3 install cryptography pyserial
# Clone repository
git clone https://github.com/markqvist/Reticulum.git
# Move into Reticulum folder and symlink library to examples folder
cd Reticulum
ln -s ../RNS ./Examples/
# Run an example
python3 Examples/Echo.py -s
# Unless you've manually created a config file, Reticulum will do so now,
# and immediately exit. Make any necessary changes to the file:
nano ~/.reticulum/config
# ... and launch the example again.
python3 Examples/Echo.py -s
# You can now repeat the process on another computer,
# and run the same example with -h to get command line options.
python3 Examples/Echo.py -h
# Run the example in client mode to "ping" the server.
# Replace the hash below with the actual destination hash of your server.
python3 Examples/Echo.py 3e12fc71692f8ec47bc5
# Have a look at another example
python3 Examples/Filetransfer.py -h
```
The default config file contains examples for using Reticulum with LoRa transceivers (specifically [RNode](https://unsigned.io/projects/rnode/)), packet radio TNCs/modems and UDP. By default a UDP interface is already enabled in the default config, which will enable Reticulum communication in your local ethernet broadcast domain.
You can use the examples in the config file to expand communication over other mediums such as packet radio or LoRa, or over fast IP links using the UDP interface. I'll add in-depth tutorials and explanations on these topics later. For now, the included examples will hopefully be enough to get started.
+64 -27
View File
@@ -5,9 +5,6 @@ import RNS
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import padding
class Callbacks:
def __init__(self):
@@ -25,15 +22,12 @@ class Destination:
encrypted communication with it.
:param identity: An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
:param type: ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
:param app_name: A string specifying the app name.
:param \*aspects: Any non-zero number of string arguments.
"""
KEYSIZE = RNS.Identity.KEYSIZE;
PADDINGSIZE= RNS.Identity.PADDINGSIZE;
# Constants
SINGLE = 0x00
GROUP = 0x01
@@ -46,6 +40,11 @@ class Destination:
PROVE_ALL = 0x23
proof_strategies = [PROVE_NONE, PROVE_APP, PROVE_ALL]
ALLOW_NONE = 0x00
ALLOW_ALL = 0x01
ALLOW_LIST = 0x02
request_policies = [ALLOW_NONE, ALLOW_ALL, ALLOW_LIST]
IN = 0x11;
OUT = 0x12;
directions = [IN, OUT]
@@ -103,6 +102,7 @@ class Destination:
if not type in Destination.types: raise ValueError("Unknown destination type")
if not direction in Destination.directions: raise ValueError("Unknown destination direction")
self.callbacks = Callbacks()
self.request_handlers = {}
self.type = type
self.direction = direction
self.proof_strategy = Destination.PROVE_NONE
@@ -139,8 +139,8 @@ class Destination:
def announce(self, app_data=None, path_response=False):
"""
Creates an announce packet for this destination and broadcasts it on
all interfaces. Application specific data can be added to the announce.
Creates an announce packet for this destination and broadcasts it on all
relevant interfaces. Application specific data can be added to the announce.
:param app_data: *bytes* containing the app_data.
:param path_response: Internal flag used by :ref:`RNS.Transport<api-transport>`. Ignore.
@@ -162,10 +162,7 @@ class Destination:
signature = self.identity.sign(signed_data)
# TODO: Check if this could be optimised by only
# carrying the hash in the destination field, not
# also redundantly inside the signed blob as here
announce_data = self.hash+self.identity.get_public_key()+random_hash+signature
announce_data = self.identity.get_public_key()+random_hash+signature
if app_data != None:
announce_data += app_data
@@ -178,7 +175,7 @@ class Destination:
RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context).send()
def link_established_callback(self, callback):
def set_link_established_callback(self, callback):
"""
Registers a function to be called when a link has been established to
this destination.
@@ -187,7 +184,7 @@ class Destination:
"""
self.callbacks.link_established = callback
def packet_callback(self, callback):
def set_packet_callback(self, callback):
"""
Registers a function to be called when a packet has been received by
this destination.
@@ -196,7 +193,7 @@ class Destination:
"""
self.callbacks.packet = callback
def proof_requested_callback(self, callback):
def set_proof_requested_callback(self, callback):
"""
Registers a function to be called when a proof has been requested for
a packet sent to this destination. Allows control over when and if
@@ -217,15 +214,55 @@ class Destination:
else:
self.proof_strategy = proof_strategy
def receive(self, packet):
plaintext = self.decrypt(packet.data)
if plaintext != None:
if packet.packet_type == RNS.Packet.LINKREQUEST:
self.incoming_link_request(plaintext, packet)
if packet.packet_type == RNS.Packet.DATA:
if self.callbacks.packet != None:
self.callbacks.packet(plaintext, packet)
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None):
"""
Registers a request handler.
:param path: The path for the request handler to be registered.
:param response_generator: A function or method with the signature *response_generator(path, data, request_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
:param allow: One of ``RNS.Destination.ALLOW_NONE``, ``RNS.Destination.ALLOW_ALL`` or ``RNS.Destination.ALLOW_LIST``. If ``RNS.Destination.ALLOW_LIST`` is set, the request handler will only respond to requests for identified peers in the supplied list.
:param allowed_list: A list of *bytes-like* :ref:`RNS.Identity<api-identity>` hashes.
:raises: ``ValueError`` if any of the supplied arguments are invalid.
"""
if path == None or path == "":
raise ValueError("Invalid path specified")
elif not callable(response_generator):
raise ValueError("Invalid response generator specified")
elif not allow in Destination.request_policies:
raise ValueError("Invalid request policy")
else:
path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
request_handler = [path, response_generator, allow, allowed_list]
self.request_handlers[path_hash] = request_handler
def deregister_request_handler(self, path):
"""
Deregisters a request handler.
:param path: The path for the request handler to be deregistered.
:returns: True if the handler was deregistered, otherwise False.
"""
path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
if path_hash in self.request_handlers:
self.request_handlers.pop(path_hash)
return True
else:
return False
def receive(self, packet):
if packet.packet_type == RNS.Packet.LINKREQUEST:
plaintext = packet.data
self.incoming_link_request(plaintext, packet)
else:
plaintext = self.decrypt(packet.data)
if plaintext != None:
if packet.packet_type == RNS.Packet.DATA:
if self.callbacks.packet != None:
self.callbacks.packet(plaintext, packet)
def incoming_link_request(self, data, packet):
link = RNS.Link.validate_request(self, data, packet)
@@ -245,8 +282,8 @@ class Destination:
raise TypeError("A single destination holds keys through an Identity instance")
if self.type == Destination.GROUP:
self.prv_bytes = Fernet.generate_key()
self.prv = Fernet(self.prv_bytes)
self.prv_bytes = base64.urlsafe_b64decode(Fernet.generate_key())
self.prv = Fernet(base64.urlsafe_b64encode(self.prv_bytes))
def get_private_key(self):
@@ -278,7 +315,7 @@ class Destination:
if self.type == Destination.GROUP:
self.prv_bytes = key
self.prv = Fernet(self.prv_bytes)
self.prv = Fernet(base64.urlsafe_b64encode(self.prv_bytes))
def load_public_key(self, key):
if self.type != Destination.SINGLE:
+230 -191
View File
@@ -4,14 +4,15 @@ import os
import RNS
import time
import atexit
import base64
from .vendor import umsgpack as umsgpack
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.serialization import load_der_public_key
from cryptography.hazmat.primitives.serialization import load_der_private_key
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.fernet import Fernet
class Identity:
"""
@@ -19,26 +20,30 @@ class Identity:
for encryption, decryption, signatures and verification, and is the basis
for all encrypted communication over Reticulum networks.
:param public_only: Specifies whether this destination only holds a public key.
:param create_keys: Specifies whether new encryption and signing keys should be generated.
"""
KEYSIZE = 1024
CURVE = "Curve25519"
"""
RSA key size in bits.
The curve used for Elliptic Curve DH key exchanges
"""
DERKEYSIZE = KEYSIZE+272
KEYSIZE = 256*2
"""
X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
"""
# Non-configurable constants
PADDINGSIZE = 336 # In bits
HASHLENGTH = 256 # In bits
SIGLENGTH = KEYSIZE
FERNET_VERSION = 0x80
FERNET_OVERHEAD = 54 # In bytes
AES128_BLOCKSIZE = 16 # In bytes
HASHLENGTH = 256 # In bits
SIGLENGTH = KEYSIZE # In bits
ENCRYPT_CHUNKSIZE = (KEYSIZE-PADDINGSIZE)//8
DECRYPT_CHUNKSIZE = KEYSIZE//8
TRUNCATED_HASHLENGTH = 80 # In bits
TRUNCATED_HASHLENGTH = RNS.Reticulum.TRUNCATED_HASHLENGTH
"""
Constant specifying the truncated hash length (in bits) used by Reticulum
for addressable hashes. Non-configurable.
for addressable hashes and other purposes. Non-configurable.
"""
# Storage
@@ -46,7 +51,10 @@ class Identity:
@staticmethod
def remember(packet_hash, destination_hash, public_key, app_data = None):
Identity.known_destinations[destination_hash] = [time.time(), packet_hash, public_key, app_data]
if len(public_key) != Identity.KEYSIZE//8:
raise TypeError("Can't remember "+RNS.prettyhexrep(destination_hash)+", the public key size of "+str(len(public_key))+" is not valid.", RNS.LOG_ERROR)
else:
Identity.known_destinations[destination_hash] = [time.time(), packet_hash, public_key, app_data]
@staticmethod
@@ -57,16 +65,13 @@ class Identity:
:param destination_hash: Destination hash as *bytes*.
:returns: An :ref:`RNS.Identity<api-identity>` instance that can be used to create an outgoing :ref:`RNS.Destination<api-destination>`, or *None* if the destination is unknown.
"""
RNS.log("Searching for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
if destination_hash in Identity.known_destinations:
identity_data = Identity.known_destinations[destination_hash]
identity = Identity(public_only=True)
identity = Identity(create_keys=False)
identity.load_public_key(identity_data[2])
identity.app_data = identity_data[3]
RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
return identity
else:
RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" in known destinations", RNS.LOG_EXTREME)
return None
@staticmethod
@@ -77,13 +82,10 @@ class Identity:
:param destination_hash: Destination hash as *bytes*.
:returns: *Bytes* containing app_data, or *None* if the destination is unknown.
"""
RNS.log("Searching for app_data for "+RNS.prettyhexrep(destination_hash)+"...", RNS.LOG_EXTREME)
if destination_hash in Identity.known_destinations:
app_data = Identity.known_destinations[destination_hash][3]
RNS.log("Found "+RNS.prettyhexrep(destination_hash)+" app_data in known destinations", RNS.LOG_EXTREME)
return app_data
else:
RNS.log("Could not find "+RNS.prettyhexrep(destination_hash)+" app_data in known destinations", RNS.LOG_EXTREME)
return None
@staticmethod
@@ -145,19 +147,19 @@ class Identity:
if packet.packet_type == RNS.Packet.ANNOUNCE:
RNS.log("Validating announce from "+RNS.prettyhexrep(packet.destination_hash), RNS.LOG_DEBUG)
destination_hash = packet.destination_hash
public_key = packet.data[10:Identity.DERKEYSIZE//8+10]
random_hash = packet.data[Identity.DERKEYSIZE//8+10:Identity.DERKEYSIZE//8+20]
signature = packet.data[Identity.DERKEYSIZE//8+20:Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8]
public_key = packet.data[:Identity.KEYSIZE//8]
random_hash = packet.data[Identity.KEYSIZE//8:Identity.KEYSIZE//8+10]
signature = packet.data[Identity.KEYSIZE//8+10:Identity.KEYSIZE//8+10+Identity.KEYSIZE//8]
app_data = b""
if len(packet.data) > Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8:
app_data = packet.data[Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8:]
if len(packet.data) > Identity.KEYSIZE//8+10+Identity.KEYSIZE//8:
app_data = packet.data[Identity.KEYSIZE//8+10+Identity.KEYSIZE//8:]
signed_data = destination_hash+public_key+random_hash+app_data
if not len(packet.data) > Identity.DERKEYSIZE//8+20+Identity.KEYSIZE//8:
if not len(packet.data) > Identity.KEYSIZE//8+10+Identity.KEYSIZE//8:
app_data = None
announced_identity = Identity(public_only=True)
announced_identity = Identity(create_keys=False)
announced_identity.load_public_key(public_key)
if announced_identity.pub != None and announced_identity.validate(signature, signed_data):
@@ -175,6 +177,22 @@ class Identity:
Identity.save_known_destinations()
@staticmethod
def from_bytes(prv_bytes):
"""
Create a new :ref:`RNS.Identity<api-identity>` instance from *bytes* of private key.
Can be used to load previously created and saved identities into Reticulum.
:param prv_bytes: The *bytes* of private a saved private key. **HAZARD!** Never use this to generate a new key by feeding random data in prv_bytes.
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the *bytes* data was invalid.
"""
identity = Identity(create_keys=False)
if identity.load_private_key(prv_bytes):
return identity
else:
return None
@staticmethod
def from_file(path):
"""
@@ -184,104 +202,12 @@ class Identity:
:param path: The full path to the saved :ref:`RNS.Identity<api-identity>` data
:returns: A :ref:`RNS.Identity<api-identity>` instance, or *None* if the loaded data was invalid.
"""
identity = Identity(public_only=True)
identity = Identity(create_keys=False)
if identity.load(path):
return identity
else:
return None
def __init__(self,public_only=False):
# Initialize keys to none
self.prv = None
self.pub = None
self.prv_bytes = None
self.pub_bytes = None
self.hash = None
self.hexhash = None
if not public_only:
self.create_keys()
def create_keys(self):
self.prv = rsa.generate_private_key(
public_exponent=65537,
key_size=Identity.KEYSIZE,
backend=default_backend()
)
self.prv_bytes = self.prv.private_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
self.update_hashes()
RNS.log("Identity keys created for "+RNS.prettyhexrep(self.hash), RNS.LOG_VERBOSE)
def get_private_key(self):
"""
:returns: The private key as *bytes*
"""
return self.prv_bytes
def get_public_key(self):
"""
:returns: The public key as *bytes*
"""
return self.pub_bytes
def load_private_key(self, prv_bytes):
"""
Load a private key into the instance.
:param prv_bytes: The private key as *bytes*.
:returns: True if the key was loaded, otherwise False.
"""
try:
self.prv_bytes = prv_bytes
self.prv = serialization.load_der_private_key(
self.prv_bytes,
password=None,
backend=default_backend()
)
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
self.update_hashes()
return True
except Exception as e:
RNS.log("Failed to load identity key", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
return False
def load_public_key(self, key):
"""
Load a public key into the instance.
:param prv_bytes: The public key as *bytes*.
:returns: True if the key was loaded, otherwise False.
"""
try:
self.pub_bytes = key
self.pub = load_der_public_key(self.pub_bytes, backend=default_backend())
self.update_hashes()
except Exception as e:
RNS.log("Error while loading public key, the contained exception was: "+str(e), RNS.LOG_ERROR)
def update_hashes(self):
self.hash = Identity.truncated_hash(self.pub_bytes)
self.hexhash = self.hash.hex()
def to_file(self, path):
"""
Saves the identity to a file. This will write the private key to disk,
@@ -293,13 +219,131 @@ class Identity:
"""
try:
with open(path, "wb") as key_file:
key_file.write(self.prv_bytes)
key_file.write(self.get_private_key())
return True
return False
except Exception as e:
RNS.log("Error while saving identity to "+str(path), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e))
def __init__(self,create_keys=True):
# Initialize keys to none
self.prv = None
self.prv_bytes = None
self.sig_prv = None
self.sig_prv_bytes = None
self.pub = None
self.pub_bytes = None
self.sig_pub = None
self.sig_pub_bytes = None
self.hash = None
self.hexhash = None
if create_keys:
self.create_keys()
def create_keys(self):
self.prv = X25519PrivateKey.generate()
self.prv_bytes = self.prv.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
self.sig_prv = Ed25519PrivateKey.generate()
self.sig_prv_bytes = self.sig_prv.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.sig_pub = self.sig_prv.public_key()
self.sig_pub_bytes = self.sig_pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.update_hashes()
RNS.log("Identity keys created for "+RNS.prettyhexrep(self.hash), RNS.LOG_VERBOSE)
def get_private_key(self):
"""
:returns: The private key as *bytes*
"""
return self.prv_bytes+self.sig_prv_bytes
def get_public_key(self):
"""
:returns: The public key as *bytes*
"""
return self.pub_bytes+self.sig_pub_bytes
def load_private_key(self, prv_bytes):
"""
Load a private key into the instance.
:param prv_bytes: The private key as *bytes*.
:returns: True if the key was loaded, otherwise False.
"""
try:
self.prv_bytes = prv_bytes[:Identity.KEYSIZE//8//2]
self.prv = X25519PrivateKey.from_private_bytes(self.prv_bytes)
self.sig_prv_bytes = prv_bytes[Identity.KEYSIZE//8//2:]
self.sig_prv = Ed25519PrivateKey.from_private_bytes(self.sig_prv_bytes)
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.sig_pub = self.sig_prv.public_key()
self.sig_pub_bytes = self.sig_pub.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
self.update_hashes()
return True
except Exception as e:
raise e
RNS.log("Failed to load identity key", RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
return False
def load_public_key(self, pub_bytes):
"""
Load a public key into the instance.
:param pub_bytes: The public key as *bytes*.
:returns: True if the key was loaded, otherwise False.
"""
try:
self.pub_bytes = pub_bytes[:Identity.KEYSIZE//8//2]
self.sig_pub_bytes = pub_bytes[Identity.KEYSIZE//8//2:]
self.pub = X25519PublicKey.from_public_bytes(self.pub_bytes)
self.sig_pub = Ed25519PublicKey.from_public_bytes(self.sig_pub_bytes)
self.update_hashes()
except Exception as e:
RNS.log("Error while loading public key, the contained exception was: "+str(e), RNS.LOG_ERROR)
def update_hashes(self):
self.hash = Identity.truncated_hash(self.get_public_key())
self.hexhash = self.hash.hex()
def load(self, path):
try:
with open(path, "rb") as key_file:
@@ -310,71 +354,78 @@ class Identity:
RNS.log("Error while loading identity from "+str(path), RNS.LOG_ERROR)
RNS.log("The contained exception was: "+str(e))
def get_salt(self):
return self.hash
def get_context(self):
return None
def encrypt(self, plaintext):
"""
Encrypts information for the identity.
:param plaintext: The plaintext to be encrypted as *bytes*.
:returns: Ciphertext as *bytes*.
:raises: *KeyError* if the instance does not hold a public key
:returns: Ciphertext token as *bytes*.
:raises: *KeyError* if the instance does not hold a public key.
"""
if self.pub != None:
chunksize = Identity.ENCRYPT_CHUNKSIZE
chunks = int(math.ceil(len(plaintext)/(float(chunksize))))
ephemeral_key = X25519PrivateKey.generate()
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
ciphertext = b"";
for chunk in range(chunks):
start = chunk*chunksize
end = (chunk+1)*chunksize
if (chunk+1)*chunksize > len(plaintext):
end = len(plaintext)
ciphertext += self.pub.encrypt(
plaintext[start:end],
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
return ciphertext
shared_key = ephemeral_key.exchange(self.pub)
derived_key = derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=self.get_salt(),
info=self.get_context(),
).derive(shared_key)
fernet = Fernet(base64.urlsafe_b64encode(derived_key))
ciphertext = base64.urlsafe_b64decode(fernet.encrypt(plaintext))
token = ephemeral_pub_bytes+ciphertext
return token
else:
raise KeyError("Encryption failed because identity does not hold a public key")
def decrypt(self, ciphertext):
def decrypt(self, ciphertext_token):
"""
Decrypts information for the identity.
:param ciphertext: The ciphertext to be decrypted as *bytes*.
:returns: Plaintext as *bytes*, or *None* if decryption fails.
:raises: *KeyError* if the instance does not hold a private key
:raises: *KeyError* if the instance does not hold a private key.
"""
if self.prv != None:
plaintext = None
try:
chunksize = Identity.DECRYPT_CHUNKSIZE
chunks = int(math.ceil(len(ciphertext)/(float(chunksize))))
if len(ciphertext_token) > Identity.KEYSIZE//8//2:
plaintext = None
try:
peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2]
peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
plaintext = b"";
for chunk in range(chunks):
start = chunk*chunksize
end = (chunk+1)*chunksize
if (chunk+1)*chunksize > len(ciphertext):
end = len(ciphertext)
shared_key = self.prv.exchange(peer_pub)
derived_key = derived_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=self.get_salt(),
info=self.get_context(),
).derive(shared_key)
plaintext += self.prv.decrypt(
ciphertext[start:end],
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA1()),
algorithm=hashes.SHA1(),
label=None
)
)
except:
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed", RNS.LOG_VERBOSE)
return plaintext;
fernet = Fernet(base64.urlsafe_b64encode(derived_key))
ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
plaintext = fernet.decrypt(base64.urlsafe_b64encode(ciphertext))
except Exception as e:
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG)
return plaintext;
else:
RNS.log("Decryption failed because the token size was invalid.", RNS.LOG_DEBUG)
return None
else:
raise KeyError("Decryption failed because identity does not hold a private key")
@@ -385,18 +436,14 @@ class Identity:
:param message: The message to be signed as *bytes*.
:returns: Signature as *bytes*.
:raises: *KeyError* if the instance does not hold a private key
:raises: *KeyError* if the instance does not hold a private key.
"""
if self.prv != None:
signature = self.prv.sign(
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return signature
if self.sig_prv != None:
try:
return self.sig_prv.sign(message)
except Exception as e:
RNS.log("The identity "+str(self)+" could not sign the requested message. The contained exception was: "+str(e), RNS.LOG_ERROR)
raise e
else:
raise KeyError("Signing failed because identity does not hold a private key")
@@ -407,19 +454,11 @@ class Identity:
:param signature: The signature to be validated as *bytes*.
:param message: The message to be validated as *bytes*.
:returns: True if the signature is valid, otherwise False.
:raises: *KeyError* if the instance does not hold a public key
:raises: *KeyError* if the instance does not hold a public key.
"""
if self.pub != None:
try:
self.pub.verify(
signature,
message,
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
self.sig_pub.verify(signature, message)
return True
except Exception as e:
return False
+10 -8
View File
@@ -62,13 +62,12 @@ class AX25KISSInterface(Interface):
self.stopbits = stopbits
self.timeout = 100
self.online = False
# TODO: Sane default and make this configurable
# TODO: Changed to 25ms instead of 100ms, check it
self.txdelay = 0.025
self.packet_queue = []
self.flow_control = flow_control
self.interface_ready = False
self.flow_control_timeout = 5
self.flow_control_locked = time.time()
if (len(self.src_call) < 3 or len(self.src_call) > 6):
raise ValueError("Invalid callsign for "+str(self))
@@ -197,6 +196,7 @@ class AX25KISSInterface(Interface):
if self.interface_ready:
if self.flow_control:
self.interface_ready = False
self.flow_control_locked = time.time()
encoded_dst_ssid = bytes([0x60 | (self.dst_ssid << 1)])
encoded_src_ssid = bytes([0x60 | (self.src_ssid << 1) | 0x01])
@@ -223,10 +223,6 @@ class AX25KISSInterface(Interface):
data = data.replace(bytes([0xc0]), bytes([0xdb])+bytes([0xdc]))
kiss_frame = bytes([KISS.FEND])+bytes([0x00])+data+bytes([KISS.FEND])
if (self.txdelay > 0):
RNS.log(str(self.name)+" delaying TX for "+str(self.txdelay)+" seconds", RNS.LOG_EXTREME)
sleep(self.txdelay)
written = self.serial.write(kiss_frame)
if written != len(kiss_frame):
if self.flow_control:
@@ -294,7 +290,13 @@ class AX25KISSInterface(Interface):
in_frame = False
command = KISS.CMD_UNKNOWN
escape = False
sleep(0.08)
sleep(0.05)
if self.flow_control:
if not self.interface_ready:
if time.time() > self.flow_control_locked + self.flow_control_timeout:
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command, or maybe it does not support flow-control.", RNS.LOG_WARNING)
self.process_queue()
except Exception as e:
self.online = False
-1
View File
@@ -11,5 +11,4 @@ class Interface:
pass
def get_hash(self):
# TODO: Maybe expand this to something more unique
return RNS.Identity.full_hash(str(self).encode("utf-8"))
+3 -3
View File
@@ -60,7 +60,7 @@ class KISSInterface(Interface):
self.packet_queue = []
self.flow_control = flow_control
self.interface_ready = False
self.flow_control_timeout = 10
self.flow_control_timeout = 5
self.flow_control_locked = time.time()
self.preamble = preamble if preamble != None else 350;
@@ -259,12 +259,12 @@ class KISSInterface(Interface):
in_frame = False
command = KISS.CMD_UNKNOWN
escape = False
sleep(0.08)
sleep(0.05)
if self.flow_control:
if not self.interface_ready:
if time.time() > self.flow_control_locked + self.flow_control_timeout:
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command.", RNS.LOG_WARNING)
RNS.log("Interface "+str(self)+" is unlocking flow control due to time-out. This should not happen. Your hardware might have missed a flow-control READY command, or maybe it does not support flow-control.", RNS.LOG_WARNING)
self.process_queue()
if self.beacon_i != None and self.beacon_d != None:
+11 -1
View File
@@ -1,6 +1,7 @@
from .Interface import Interface
import socketserver
import threading
import netifaces
import socket
import time
import sys
@@ -127,12 +128,21 @@ class TCPClientInterface(Interface):
class TCPServerInterface(Interface):
@staticmethod
def get_address_for_if(name):
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
def __init__(self, owner, name, bindip=None, bindport=None):
def get_broadcast_for_if(name):
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
def __init__(self, owner, name, device=None, bindip=None, bindport=None):
self.IN = True
self.OUT = False
self.name = name
if device != None:
bindip = TCPServerInterface.get_address_for_if(device)
if (bindip != None and bindport != None):
self.receives = True
self.bind_ip = bindip
+17 -1
View File
@@ -1,18 +1,34 @@
from .Interface import Interface
import socketserver
import threading
import netifaces
import socket
import time
import sys
import RNS
class UDPInterface(Interface):
def __init__(self, owner, name, bindip=None, bindport=None, forwardip=None, forwardport=None):
@staticmethod
def get_address_for_if(name):
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
def get_broadcast_for_if(name):
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
self.IN = True
self.OUT = False
self.name = name
if device != None:
if bindip == None:
bindip = UDPInterface.get_broadcast_for_if(device)
if forwardip == None:
forwardip = UDPInterface.get_broadcast_for_if(device)
if (bindip != None and bindport != None):
self.receives = True
self.bind_ip = bindip
+485 -102
View File
@@ -23,38 +23,41 @@ class LinkCallbacks:
self.resource = None
self.resource_started = None
self.resource_concluded = None
self.remote_identified = None
class Link:
"""
This class.
This class is used to establish and manage links to other peers. When a
link instance is created, Reticulum will attempt to establish verified
connectivity with the specified destination.
:param destination: A :ref:`RNS.Destination<api-destination>` instance which to establish a link to.
:param owner: Internal use by :ref:`RNS.Transport<api-Transport>`, ignore this argument.
:param peer_pub_bytes: Internal use, ignore this argument.
:param peer_sig_pub_bytes: Internal use, ignore this argument.
:param established_callback: An optional function or method with the signature *callback(link)* to be called when the link has been established.
:param closed_callback: An optional function or method with the signature *callback(link)* to be called when the link is closed.
"""
CURVE = "Curve25519"
CURVE = RNS.Identity.CURVE
"""
The curve used for Elliptic Curve DH key exchanges
"""
ECPUBSIZE = 32+32
BLOCKSIZE = 16
KEYSIZE = 32
ECPUBSIZE = 32+32
KEYSIZE = 32
AES_HMAC_OVERHEAD = 58
MDU = math.floor((RNS.Reticulum.MDU-AES_HMAC_OVERHEAD)/BLOCKSIZE)*BLOCKSIZE - 1
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.FERNET_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
# TODO: This should not be hardcoded,
# but calculated from something like
# first-hop RTT latency and distance
DEFAULT_TIMEOUT = 15.0
# This value is set at a reasonable level for a 1 Kb/s channel.
#
# TODO: Find a way to automatically raise or lower this according to
# channel bandwidth and utilisation.
ESTABLISHMENT_TIMEOUT_PER_HOP = 5
"""
Default timeout for link establishment in seconds.
Default timeout for link establishment in seconds per hop to destination.
"""
TIMEOUT_FACTOR = 3
TRAFFIC_TIMEOUT_FACTOR = 6
KEEPALIVE_TIMEOUT_FACTOR = 4
STALE_GRACE = 2
KEEPALIVE = 180
KEEPALIVE = 360
"""
Interval for sending keep-alive packets on established links in seconds.
"""
@@ -81,6 +84,7 @@ class Link:
link = Link(owner = owner, peer_pub_bytes=data[:Link.ECPUBSIZE//2], peer_sig_pub_bytes=data[Link.ECPUBSIZE//2:Link.ECPUBSIZE])
link.set_link_id(packet)
link.destination = packet.destination
link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops)
RNS.log("Validating link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_VERBOSE)
link.handshake()
link.attached_interface = packet.receiving_interface
@@ -89,11 +93,6 @@ class Link:
RNS.Transport.register_link(link)
link.last_inbound = time.time()
link.start_watchdog()
# TODO: Why was link_established callback here? Seems weird
# to call this before RTT packet has been received
#if self.owner.callbacks.link_established != None:
# self.owner.callbacks.link_established(link)
RNS.log("Incoming link request "+str(link)+" accepted", RNS.LOG_VERBOSE)
return link
@@ -108,7 +107,7 @@ class Link:
return None
def __init__(self, destination=None, owner=None, peer_pub_bytes = None, peer_sig_pub_bytes = None):
def __init__(self, destination=None, established_callback = None, closed_callback = None, owner=None, peer_pub_bytes = None, peer_sig_pub_bytes = None):
if destination != None and destination.type != RNS.Destination.SINGLE:
raise TypeError("Links can only be established to the \"single\" destination type")
self.rtt = None
@@ -116,15 +115,15 @@ class Link:
self.resource_strategy = Link.ACCEPT_NONE
self.outgoing_resources = []
self.incoming_resources = []
self.pending_requests = []
self.last_inbound = 0
self.last_outbound = 0
self.tx = 0
self.rx = 0
self.txbytes = 0
self.rxbytes = 0
self.default_timeout = Link.DEFAULT_TIMEOUT
self.proof_timeout = self.default_timeout
self.timeout_factor = Link.TIMEOUT_FACTOR
self.traffic_timeout_factor = Link.TRAFFIC_TIMEOUT_FACTOR
self.keepalive_timeout_factor = Link.KEEPALIVE_TIMEOUT_FACTOR
self.keepalive = Link.KEEPALIVE
self.watchdog_lock = False
self.status = Link.PENDING
@@ -132,17 +131,19 @@ class Link:
self.owner = owner
self.destination = destination
self.attached_interface = None
self.__encryption_disabled = False
self.__remote_identity = None
if self.destination == None:
self.initiator = False
self.prv = self.owner.identity.prv
self.sig_prv = self.owner.identity.sig_prv
else:
self.initiator = True
self.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, RNS.Transport.hops_to(destination.hash))
self.prv = X25519PrivateKey.generate()
self.sig_prv = Ed25519PrivateKey.generate()
self.fernet = None
self.prv = X25519PrivateKey.generate()
self.sig_prv = Ed25519PrivateKey.generate()
self.pub = self.prv.public_key()
self.pub_bytes = self.pub.public_bytes(
encoding=serialization.Encoding.Raw,
@@ -161,11 +162,21 @@ class Link:
else:
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
if established_callback != None:
self.set_link_established_callback(established_callback)
if closed_callback != None:
self.set_link_closed_callback(closed_callback)
if (self.initiator):
peer_pub_bytes = self.destination.identity.get_public_key()[:Link.ECPUBSIZE//2]
peer_sig_pub_bytes = self.destination.identity.get_public_key()[Link.ECPUBSIZE//2:Link.ECPUBSIZE]
self.request_data = self.pub_bytes+self.sig_pub_bytes
self.packet = RNS.Packet(destination, self.request_data, packet_type=RNS.Packet.LINKREQUEST)
self.packet.pack()
self.set_link_id(self.packet)
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
self.handshake()
RNS.Transport.register_link(self)
self.request_time = time.time()
self.start_watchdog()
@@ -202,7 +213,7 @@ class Link:
signed_data = self.link_id+self.pub_bytes+self.sig_pub_bytes
signature = self.owner.identity.sign(signed_data)
proof_data = self.pub_bytes+self.sig_pub_bytes+signature
proof_data = signature
proof = RNS.Packet(self, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.LRPROOF)
proof.send()
self.had_outbound()
@@ -221,32 +232,98 @@ class Link:
self.had_outbound()
def validate_proof(self, packet):
if self.initiator and len(packet.data) == Link.ECPUBSIZE+RNS.Identity.KEYSIZE//8:
peer_pub_bytes = packet.data[:Link.ECPUBSIZE//2]
peer_sig_pub_bytes = packet.data[Link.ECPUBSIZE//2:Link.ECPUBSIZE]
signed_data = self.link_id+peer_pub_bytes+peer_sig_pub_bytes
signature = packet.data[Link.ECPUBSIZE:RNS.Identity.KEYSIZE//8+Link.ECPUBSIZE]
if self.status == Link.HANDSHAKE:
if self.initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8:
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
if self.destination.identity.validate(signature, signed_data):
self.rtt = time.time() - self.request_time
self.attached_interface = packet.receiving_interface
self.__remote_identity = self.destination.identity
RNS.Transport.activate_link(self)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(self.rtt), RNS.LOG_VERBOSE)
rtt_data = umsgpack.packb(self.rtt)
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
rtt_packet.send()
self.had_outbound()
if self.destination.identity.validate(signature, signed_data):
self.load_peer(peer_pub_bytes, peer_sig_pub_bytes)
self.handshake()
self.rtt = time.time() - self.request_time
self.attached_interface = packet.receiving_interface
RNS.Transport.activate_link(self)
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(self.rtt), RNS.LOG_VERBOSE)
rtt_data = umsgpack.packb(self.rtt)
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
RNS.log("Sending RTT packet", RNS.LOG_EXTREME);
rtt_packet.send()
self.had_outbound()
self.status = Link.ACTIVE
if self.callbacks.link_established != None:
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
thread.setDaemon(True)
thread.start()
else:
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
self.status = Link.ACTIVE
if self.callbacks.link_established != None:
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
thread.setDaemon(True)
thread.start()
def identify(self, identity):
"""
Identifies the initiator of the link to the remote peer. This can only happen
once the link has been established, and is carried out over the encrypted link.
The identity is only revealed to the remote peer, and initiator anonymity is
thus preserved. This method can be used for authentication.
:param identity: An RNS.Identity instance to identify as.
"""
if self.initiator:
signed_data = self.link_id + identity.get_public_key()
signature = identity.sign(signed_data)
proof_data = identity.get_public_key() + signature
proof = RNS.Packet(self, proof_data, RNS.Packet.DATA, context = RNS.Packet.LINKIDENTIFY)
proof.send()
self.had_outbound()
def request(self, path, data = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None):
"""
Sends a request to the remote peer.
:param path: The request path.
:param response_callback: An optional function or method with the signature *response_callback(request_receipt)* to be called when a response is received. See the :ref:`Request Example<example-request>` for more info.
:param failed_callback: An optional function or method with the signature *failed_callback(request_receipt)* to be called when a request fails. See the :ref:`Request Example<example-request>` for more info.
:param progress_callback: An optional function or method with the signature *progress_callback(request_receipt)* to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the *request_receipt.progress* property.
:param timeout: An optional timeout in seconds for the request. If *None* is supplied it will be calculated based on link RTT.
:returns: A :ref:`RNS.RequestReceipt<api-requestreceipt>` instance if the request was sent, or *False* if it was not.
"""
request_path_hash = RNS.Identity.truncated_hash(path.encode("utf-8"))
unpacked_request = [time.time(), request_path_hash, data]
packed_request = umsgpack.packb(unpacked_request)
if timeout == None:
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME
if len(packed_request) <= Link.MDU:
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
packet_receipt = request_packet.send()
if packet_receipt == False:
return False
else:
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
packet_receipt.set_timeout(timeout)
return RequestReceipt(
self,
packet_receipt = packet_receipt,
response_callback = response_callback,
failed_callback = failed_callback,
progress_callback = progress_callback,
timeout = timeout
)
else:
request_id = RNS.Identity.truncated_hash(packed_request)
RNS.log("Sending request "+RNS.prettyhexrep(request_id)+" as resource.", RNS.LOG_DEBUG)
request_resource = RNS.Resource(packed_request, self, request_id = request_id, is_response = False, timeout = timeout)
return RequestReceipt(
self,
resource = request_resource,
response_callback = response_callback,
failed_callback = failed_callback,
progress_callback = progress_callback,
timeout = timeout
)
def rtt_packet(self, packet):
@@ -265,8 +342,7 @@ class Link:
if self.owner.callbacks.link_established != None:
self.owner.callbacks.link_established(self)
except Exception as e:
RNS.log("Error occurred while processing RTT packet, tearing down link", RNS.LOG_ERROR)
traceback.print_exc()
RNS.log("Error occurred while processing RTT packet, tearing down link. The contained exception was: "+str(e), RNS.LOG_ERROR)
self.teardown()
def get_salt(self):
@@ -293,6 +369,12 @@ class Link:
"""
return min(self.no_inbound_for(), self.no_outbound_for())
def get_remote_identity(self):
"""
:returns: The identity of the remote peer, if it is known
"""
return self.__remote_identity
def had_outbound(self):
self.last_outbound = time.time()
@@ -354,9 +436,9 @@ class Link:
# Link was initiated, but no response
# from destination yet
if self.status == Link.PENDING:
next_check = self.request_time + self.proof_timeout
next_check = self.request_time + self.establishment_timeout
sleep_time = next_check - time.time()
if time.time() >= self.request_time + self.proof_timeout:
if time.time() >= self.request_time + self.establishment_timeout:
RNS.log("Link establishment timed out", RNS.LOG_VERBOSE)
self.status = Link.CLOSED
self.teardown_reason = Link.TIMEOUT
@@ -364,10 +446,14 @@ class Link:
sleep_time = 0.001
elif self.status == Link.HANDSHAKE:
next_check = self.request_time + self.proof_timeout
next_check = self.request_time + self.establishment_timeout
sleep_time = next_check - time.time()
if time.time() >= self.request_time + self.proof_timeout:
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
if time.time() >= self.request_time + self.establishment_timeout:
if self.initiator:
RNS.log("Timeout waiting link request proof", RNS.LOG_DEBUG)
else:
RNS.log("Timeout waiting for RTT packet from link initiator", RNS.LOG_DEBUG)
self.status = Link.CLOSED
self.teardown_reason = Link.TIMEOUT
self.link_closed()
@@ -375,7 +461,7 @@ class Link:
elif self.status == Link.ACTIVE:
if time.time() >= self.last_inbound + self.keepalive:
sleep_time = self.rtt * self.timeout_factor + Link.STALE_GRACE
sleep_time = self.rtt * self.keepalive_timeout_factor + Link.STALE_GRACE
self.status = Link.STALE
if self.initiator:
self.send_keepalive()
@@ -404,6 +490,84 @@ class Link:
keepalive_packet.send()
self.had_outbound()
def handle_request(self, request_id, unpacked_request):
if self.status == Link.ACTIVE:
requested_at = unpacked_request[0]
path_hash = unpacked_request[1]
request_data = unpacked_request[2]
if path_hash in self.destination.request_handlers:
request_handler = self.destination.request_handlers[path_hash]
path = request_handler[0]
response_generator = request_handler[1]
allow = request_handler[2]
allowed_list = request_handler[3]
allowed = False
if not allow == RNS.Destination.ALLOW_NONE:
if allow == RNS.Destination.ALLOW_LIST:
if self.__remote_identity.hash in allowed_list:
allowed = True
elif allow == RNS.Destination.ALLOW_ALL:
allowed = True
if allowed:
RNS.log("Handling request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_DEBUG)
response = response_generator(path, request_data, request_id, self.__remote_identity, requested_at)
if response != None:
packed_response = umsgpack.packb([request_id, response])
if len(packed_response) <= Link.MDU:
RNS.Packet(self, packed_response, RNS.Packet.DATA, context = RNS.Packet.RESPONSE).send()
else:
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True)
else:
identity_string = RNS.prettyhexrep(self.get_remote_identity()) if self.get_remote_identity() != None else "<Unknown>"
RNS.log("Request "+RNS.prettyhexrep(request_id)+" from "+identity_string+" not allowed for: "+str(path), RNS.LOG_DEBUG)
def handle_response(self, request_id, response_data, response_size, response_transfer_size):
if self.status == Link.ACTIVE:
remove = None
for pending_request in self.pending_requests:
if pending_request.request_id == request_id:
remove = pending_request
try:
pending_request.response_size = response_size
pending_request.response_transfer_size = response_transfer_size
pending_request.response_received(response_data)
except Exception as e:
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
break
if remove != None:
self.pending_requests.remove(remove)
def request_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
packed_request = resource.data.read()
unpacked_request = umsgpack.unpackb(packed_request)
request_id = RNS.Identity.truncated_hash(packed_request)
request_data = unpacked_request
self.handle_request(request_id, request_data)
else:
RNS.log("Incoming request resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
def response_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
packed_response = resource.data.read()
unpacked_response = umsgpack.unpackb(packed_response)
request_id = unpacked_response[0]
response_data = unpacked_response[1]
self.handle_response(request_id, response_data, resource.total_size, resource.size)
else:
RNS.log("Incoming response resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
for pending_request in self.pending_requests:
if pending_request.request_id == resource.request_id:
pending_request.request_timed_out(None)
def receive(self, packet):
self.watchdog_lock = True
if not self.status == Link.CLOSED and not (self.initiator and packet.context == RNS.Packet.KEEPALIVE and packet.data == bytes([0xFF])):
@@ -431,6 +595,41 @@ class Link:
if self.destination.callbacks.proof_requested:
self.destination.callbacks.proof_requested(packet)
elif packet.context == RNS.Packet.LINKIDENTIFY:
plaintext = self.decrypt(packet.data)
if not self.initiator and len(plaintext) == RNS.Identity.KEYSIZE//8 + RNS.Identity.SIGLENGTH//8:
public_key = plaintext[:RNS.Identity.KEYSIZE//8]
signed_data = self.link_id+public_key
signature = plaintext[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.SIGLENGTH//8]
identity = RNS.Identity(create_keys=False)
identity.load_public_key(public_key)
if identity.validate(signature, signed_data):
self.__remote_identity = identity
if self.callbacks.remote_identified != None:
self.callbacks.remote_identified(self.__remote_identity)
elif packet.context == RNS.Packet.REQUEST:
try:
request_id = packet.getTruncatedHash()
packed_request = self.decrypt(packet.data)
unpacked_request = umsgpack.unpackb(packed_request)
self.handle_request(request_id, unpacked_request)
except Exception as e:
RNS.log("Error occurred while handling request. The contained exception was: "+str(e), RNS.LOG_ERROR)
elif packet.context == RNS.Packet.RESPONSE:
try:
packed_response = self.decrypt(packet.data)
unpacked_response = umsgpack.unpackb(packed_response)
request_id = unpacked_response[0]
response_data = unpacked_response[1]
transfer_size = len(umsgpack.packb(response_data))-2
self.handle_response(request_id, response_data, transfer_size, transfer_size)
except Exception as e:
RNS.log("Error occurred while handling response. The contained exception was: "+str(e), RNS.LOG_ERROR)
elif packet.context == RNS.Packet.LRRTT:
if not self.initiator:
self.rtt_packet(packet)
@@ -440,7 +639,18 @@ class Link:
elif packet.context == RNS.Packet.RESOURCE_ADV:
packet.plaintext = self.decrypt(packet.data)
if self.resource_strategy == Link.ACCEPT_NONE:
if RNS.ResourceAdvertisement.is_request(packet):
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
elif RNS.ResourceAdvertisement.is_response(packet):
request_id = RNS.ResourceAdvertisement.get_request_id(packet)
for pending_request in self.pending_requests:
if pending_request.request_id == request_id:
RNS.Resource.accept(packet, callback=self.response_resource_concluded, progress_callback=pending_request.response_resource_progress, request_id = request_id)
pending_request.response_size = RNS.ResourceAdvertisement.get_size(packet)
pending_request.response_transfer_size = RNS.ResourceAdvertisement.get_transfer_size(packet)
pending_request.started_at = time.time()
elif self.resource_strategy == Link.ACCEPT_NONE:
pass
elif self.resource_strategy == Link.ACCEPT_APP:
if self.callbacks.resource != None:
@@ -457,7 +667,11 @@ class Link:
resource_hash = plaintext[1:RNS.Identity.HASHLENGTH//8+1]
for resource in self.outgoing_resources:
if resource.hash == resource_hash:
resource.request(plaintext)
# We need to check that this request has not been
# received before in order to avoid sequencing errors.
if not packet.packet_hash in resource.req_hashlist:
resource.req_hashlist.append(packet.packet_hash)
resource.request(plaintext)
elif packet.context == RNS.Packet.RESOURCE_HMU:
plaintext = self.decrypt(packet.data)
@@ -499,32 +713,42 @@ class Link:
def encrypt(self, plaintext):
if self.__encryption_disabled:
return plaintext
try:
if not self.fernet:
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
try:
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
except Exception as e:
RNS.log("Could not "+str(self)+" instantiate Fernet while performin encryption on link. The contained exception was: "+str(e), RNS.LOG_ERROR)
raise e
ciphertext = base64.urlsafe_b64decode(self.fernet.encrypt(plaintext))
# The fernet token VERSION field is stripped here and
# reinserted on the receiving end, since it is always
# set to 0x80.
#
# Since we're also quite content with supporting time-
# stamps until the year 8921556 AD, we'll also strip 2
# bytes from the timestamp field and reinsert those as
# 0x00 when received.
ciphertext = base64.urlsafe_b64decode(self.fernet.encrypt(plaintext))[3:]
return ciphertext
except Exception as e:
RNS.log("Encryption on link "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
raise e
def decrypt(self, ciphertext):
if self.__encryption_disabled:
return ciphertext
try:
if not self.fernet:
self.fernet = Fernet(base64.urlsafe_b64encode(self.derived_key))
plaintext = self.fernet.decrypt(base64.urlsafe_b64encode(ciphertext))
plaintext = self.fernet.decrypt(base64.urlsafe_b64encode(bytes([RNS.Identity.FERNET_VERSION, 0x00, 0x00])+ciphertext))
return plaintext
except Exception as e:
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
RNS.log(traceback.format_exc(), RNS.LOG_ERROR)
# TODO: Do we really need to do this? Or can we recover somehow?
self.teardown()
# TODO: Think long about implications here
# self.teardown()
def sign(self, message):
@@ -537,13 +761,13 @@ class Link:
except Exception as e:
return False
def link_established_callback(self, callback):
def set_link_established_callback(self, callback):
self.callbacks.link_established = callback
def link_closed_callback(self, callback):
def set_link_closed_callback(self, callback):
self.callbacks.link_closed = callback
def packet_callback(self, callback):
def set_packet_callback(self, callback):
"""
Registers a function to be called when a packet has been
received over this link.
@@ -552,7 +776,7 @@ class Link:
"""
self.callbacks.packet = callback
def resource_callback(self, callback):
def set_resource_callback(self, callback):
"""
Registers a function to be called when a resource has been
advertised over this link. If the function returns *True*
@@ -563,7 +787,7 @@ class Link:
"""
self.callbacks.resource = callback
def resource_started_callback(self, callback):
def set_resource_started_callback(self, callback):
"""
Registers a function to be called when a resource has begun
transferring over this link.
@@ -572,7 +796,7 @@ class Link:
"""
self.callbacks.resource_started = callback
def resource_concluded_callback(self, callback):
def set_resource_concluded_callback(self, callback):
"""
Registers a function to be called when a resource has concluded
transferring over this link.
@@ -581,6 +805,15 @@ class Link:
"""
self.callbacks.resource_concluded = callback
def set_remote_identified_callback(self, callback):
"""
Registers a function to be called when an initiating peer has
identified over this link.
:param callback: A function or method with the signature *callback(identity)* to be called.
"""
self.callbacks.remote_identified = callback
def resource_concluded(self, resource):
if resource in self.incoming_resources:
self.incoming_resources.remove(resource)
@@ -623,27 +856,177 @@ class Link:
else:
return True
def disable_encryption(self):
"""
HAZARDOUS. This will downgrade the link to encryptionless. All
information over the link will be sent in plaintext. Never use
this in production applications. Should only be used for debugging
purposes, and will disappear in a future version.
If encryptionless links are not explicitly allowed in the users
configuration file, Reticulum will terminate itself along with the
client application and throw an error message to the user.
"""
if (RNS.Reticulum.should_allow_unencrypted()):
RNS.log("The link "+str(self)+" was downgraded to an encryptionless link", RNS.LOG_NOTICE)
self.__encryption_disabled = True
else:
RNS.log("Attempt to disable encryption on link, but encryptionless links are not allowed by config.", RNS.LOG_CRITICAL)
RNS.log("Shutting down Reticulum now!", RNS.LOG_CRITICAL)
RNS.panic()
def encryption_disabled(self):
return self.__encryption_disabled
def __str__(self):
return RNS.prettyhexrep(self.link_id)
return RNS.prettyhexrep(self.link_id)
class RequestReceipt():
"""
An instance of this class is returned by the ``request`` method of ``RNS.Link``
instances. It should never be instantiated manually. It provides methods to
check status, response time and response data when the request concludes.
"""
FAILED = 0x00
SENT = 0x01
DELIVERED = 0x02
RECEIVING = 0x03
READY = 0x04
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None, progress_callback = None, timeout = None):
self.packet_receipt = packet_receipt
self.resource = resource
self.started_at = None
if self.packet_receipt != None:
self.hash = packet_receipt.truncated_hash
self.packet_receipt.set_timeout_callback(self.request_timed_out)
self.started_at = time.time()
elif self.resource != None:
self.hash = resource.request_id
resource.set_callback(self.request_resource_concluded)
self.link = link
self.request_id = self.hash
self.response = None
self.response_transfer_size = None
self.response_size = None
self.status = RequestReceipt.SENT
self.sent_at = time.time()
self.progress = 0
self.concluded_at = None
self.response_concluded_at = None
if timeout != None:
self.timeout = timeout
else:
raise ValueError("No timeout specified for request receipt")
self.callbacks = RequestReceiptCallbacks()
self.callbacks.response = response_callback
self.callbacks.failed = failed_callback
self.callbacks.progress = progress_callback
self.link.pending_requests.append(self)
def request_resource_concluded(self, resource):
if resource.status == RNS.Resource.COMPLETE:
RNS.log("Request "+RNS.prettyhexrep(self.request_id)+" successfully sent as resource.", RNS.LOG_DEBUG)
self.started_at = time.time()
self.status = RequestReceipt.DELIVERED
self.__resource_response_timeout = time.time()+self.timeout
response_timeout_thread = threading.Thread(target=self.__response_timeout_job)
response_timeout_thread.setDaemon(True)
response_timeout_thread.start()
else:
RNS.log("Sending request "+RNS.prettyhexrep(self.request_id)+" as resource failed with status: "+RNS.hexrep([resource.status]), RNS.LOG_DEBUG)
self.status = RequestReceipt.FAILED
self.concluded_at = time.time()
self.link.pending_requests.remove(self)
if self.callbacks.failed != None:
self.callbacks.failed(self)
def __response_timeout_job(self):
while self.status == RequestReceipt.DELIVERED:
now = time.time()
if now > self.__resource_response_timeout:
self.request_timed_out(None)
time.sleep(0.1)
def request_timed_out(self, packet_receipt):
self.status = RequestReceipt.FAILED
self.concluded_at = time.time()
self.link.pending_requests.remove(self)
if self.callbacks.failed != None:
self.callbacks.failed(self)
def response_resource_progress(self, resource):
if not self.status == RequestReceipt.FAILED:
self.status = RequestReceipt.RECEIVING
if self.packet_receipt != None:
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
self.packet_receipt.proved = True
self.packet_receipt.concluded_at = time.time()
if self.packet_receipt.callbacks.delivery != None:
self.packet_receipt.callbacks.delivery(self.packet_receipt)
self.progress = resource.get_progress()
if self.callbacks.progress != None:
self.callbacks.progress(self)
else:
resource.cancel()
def response_received(self, response):
if not self.status == RequestReceipt.FAILED:
self.progress = 1.0
self.response = response
self.status = RequestReceipt.READY
self.response_concluded_at = time.time()
if self.packet_receipt != None:
self.packet_receipt.status = RNS.PacketReceipt.DELIVERED
self.packet_receipt.proved = True
self.packet_receipt.concluded_at = time.time()
if self.packet_receipt.callbacks.delivery != None:
self.packet_receipt.callbacks.delivery(self.packet_receipt)
if self.callbacks.progress != None:
self.callbacks.progress(self)
if self.callbacks.response != None:
self.callbacks.response(self)
def get_request_id(self):
"""
:returns: The request ID as *bytes*.
"""
return self.request_id
def get_status(self):
"""
:returns: The current status of the request, one of ``RNS.RequestReceipt.FAILED``, ``RNS.RequestReceipt.SENT``, ``RNS.RequestReceipt.DELIVERED``, ``RNS.RequestReceipt.READY``.
"""
return self.status
def get_progress(self):
"""
:returns: The progress of a response being received as a *float* between 0.0 and 1.0.
"""
return self.progress
def get_response(self):
"""
:returns: The response as *bytes* if it is ready, otherwise *None*.
"""
if self.status == RequestReceipt.READY:
return self.response
else:
return None
def get_response_time(self):
"""
:returns: The response time of the request in seconds.
"""
if self.status == RequestReceipt.READY:
return self.response_concluded_at - self.started_at
else:
return None
class RequestReceiptCallbacks:
def __init__(self):
self.response = None
self.failed = None
self.progress = None
+53 -35
View File
@@ -6,17 +6,20 @@ import RNS
class Packet:
"""
The Packet class is used to create packet instances that can be
sent over a Reticulum network.
The Packet class is used to create packet instances that can be sent
over a Reticulum network. Packets to will automatically be encrypted if
they are adressed to a ``RNS.Destination.SINGLE`` destination,
``RNS.Destination.GROUP`` destination or a :ref:`RNS.Link<api-link>`.
For ``RNS.Destination.GROUP`` destinations, Reticulum will use the
pre-shared key configured for the destination.
For ``RNS.Destination.SINGLE`` destinations and :ref:`RNS.Link<api-link>`
destinations, reticulum will use ephemeral keys, and offers **Forward Secrecy**.
:param destination: A :ref:`RNS.Destination<api-destination>` instance to which the packet will be sent.
:param data: The data payload to be included in the packet as *bytes*.
:param create_receipt: Specifies whether a :ref:`RNS.PacketReceipt<api-packetreceipt>` should be created when instantiating the packet.
:param type: Internal use by :ref:`RNS.Transport<api-transport>`. Defaults to ``RNS.Packet.DATA``, and should not be specified.
:param context: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param transport_type: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param transport_id: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
:param attached_interface: Internal use by :ref:`RNS.Transport<api-transport>`. Ignore.
"""
# Packet types
@@ -33,7 +36,7 @@ class Packet:
HEADER_4 = 0x03 # Reserved
header_types = [HEADER_1, HEADER_2, HEADER_3, HEADER_4]
# Data packet context types
# Packet context types
NONE = 0x00 # Generic data packet
RESOURCE = 0x01 # Packet is part of a resource
RESOURCE_ADV = 0x02 # Packet is a resource advertisement
@@ -48,7 +51,8 @@ class Packet:
PATH_RESPONSE = 0x0B # Packet is a response to a path request
COMMAND = 0x0C # Packet is a command
COMMAND_STATUS = 0x0D # Packet is a status of an executed command
KEEPALIVE = 0xFB # Packet is a keepalive packet
KEEPALIVE = 0xFA # Packet is a keepalive packet
LINKIDENTIFY = 0xFB # Packet is a link peer identification proof
LINKCLOSE = 0xFC # Packet is a link close message
LINKPROOF = 0xFD # Packet is a link packet proof
LRRTT = 0xFE # Packet is a link request round-trip time measurement
@@ -56,19 +60,24 @@ class Packet:
# This is used to calculate allowable
# payload sizes
HEADER_MAXSIZE = 23
HEADER_MAXSIZE = RNS.Reticulum.HEADER_MAXSIZE
MDU = RNS.Reticulum.MDU
# With an MTU of 500, the maximum RSA-encrypted
# amount of data we can send in a single packet
# is given by the below calculation; 258 bytes.
RSA_MDU = math.floor(MDU/RNS.Identity.DECRYPT_CHUNKSIZE)*RNS.Identity.ENCRYPT_CHUNKSIZE
PLAIN_MDU = MDU
# With an MTU of 500, the maximum of data we can
# send in a single encrypted packet is given by
# the below calculation; 383 bytes.
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.FERNET_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
"""
The maximum size of the payload data in a single encrypted packet
"""
PLAIN_MDU = MDU
"""
The maximum size of the payload data in a single unencrypted packet
"""
# TODO: This should be calculated
# more intelligently
# Default packet timeout
TIMEOUT = 60
# This value is set at a reasonable
# level for a 1 Kb/s channel.
TIMEOUT_PER_HOP = 5
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True):
if destination != None:
@@ -128,6 +137,9 @@ class Packet:
if self.packet_type == Packet.ANNOUNCE:
# Announce packets are not encrypted
self.ciphertext = self.data
elif self.packet_type == Packet.LINKREQUEST:
# Link request packets are not encrypted
self.ciphertext = self.data
elif self.packet_type == Packet.PROOF and self.context == Packet.RESOURCE_PRF:
# Resource proofs are not encrypted
self.ciphertext = self.data
@@ -171,6 +183,7 @@ class Packet:
self.packed = True
self.update_hash()
def unpack(self):
self.flags = self.raw[0]
self.hops = self.raw[1]
@@ -291,8 +304,8 @@ class PacketReceipt:
"""
The PacketReceipt class is used to receive notifications about
:ref:`RNS.Packet<api-packet>` instances sent over the network. Instances
of this class should never be created manually, but always returned
from a the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
of this class are never created manually, but always returned from
the *send()* method of a :ref:`RNS.Packet<api-packet>` instance.
"""
# Receipt status constants
FAILED = 0x00
@@ -306,15 +319,21 @@ class PacketReceipt:
# Creates a new packet receipt from a sent packet
def __init__(self, packet):
self.hash = packet.get_hash()
self.sent = True
self.sent_at = time.time()
self.timeout = Packet.TIMEOUT
self.proved = False
self.status = PacketReceipt.SENT
self.destination = packet.destination
self.callbacks = PacketReceiptCallbacks()
self.concluded_at = None
self.hash = packet.get_hash()
self.truncated_hash = packet.getTruncatedHash()
self.sent = True
self.sent_at = time.time()
self.proved = False
self.status = PacketReceipt.SENT
self.destination = packet.destination
self.callbacks = PacketReceiptCallbacks()
self.concluded_at = None
if packet.destination.type == RNS.Destination.LINK:
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
else:
self.timeout = Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
def get_status(self):
"""
@@ -406,7 +425,7 @@ class PacketReceipt:
else:
return False
def rtt(self):
def get_rtt(self):
"""
:returns: The round-trip-time in seconds
"""
@@ -416,7 +435,7 @@ class PacketReceipt:
return (self.sent_at+self.timeout < time.time())
def check_timeout(self):
if self.is_timed_out():
if self.status == PacketReceipt.SENT and self.is_timed_out():
if self.timeout == -1:
self.status = PacketReceipt.CULLED
else:
@@ -428,7 +447,6 @@ class PacketReceipt:
thread = threading.Thread(target=self.callbacks.timeout, args=(self,))
thread.setDaemon(True)
thread.start()
#self.callbacks.timeout(self)
def set_timeout(self, timeout):
@@ -439,7 +457,7 @@ class PacketReceipt:
"""
self.timeout = float(timeout)
def delivery_callback(self, callback):
def set_delivery_callback(self, callback):
"""
Sets a function that gets called if a successfull delivery has been proven.
@@ -449,7 +467,7 @@ class PacketReceipt:
# Set a function that gets called if the
# delivery times out
def timeout_callback(self, callback):
def set_timeout_callback(self, callback):
"""
Sets a function that gets called if the delivery times out.
+154 -57
View File
@@ -15,13 +15,10 @@ class Resource:
:param data: The data to be transferred. Can be *bytes* or an open *file handle*. See the :ref:`Filetransfer Example<example-filetransfer>` for details.
:param link: The :ref:`RNS.Link<api-link>` instance on which to transfer the data.
:param advertise: Whether to automatically advertise the resource. Can be *True* or *False*.
:param auto_compress: Whether to auto-compress the resource. Can be *True* or *False*.
:param auto_compress: Whether the resource must be compressed. Can be *True* or *False*. Used for debugging, will disappear in the future.
:param callback: A *callable* with the signature *callback(resource)*. Will be called when the resource transfer concludes.
:param progress_callback: A *callable* with the signature *callback(resource)*. Will be called whenever the resource transfer progress is updated.
:param segment_index: Internal use, ignore.
:param original_hash: Internal use, ignore.
:param advertise: Optional. Whether to automatically advertise the resource. Can be *True* or *False*.
:param auto_compress: Optional. Whether to auto-compress the resource. Can be *True* or *False*.
:param callback: An optional *callable* with the signature *callback(resource)*. Will be called when the resource transfer concludes.
:param progress_callback: An optional *callable* with the signature *callback(resource)*. Will be called whenever the resource transfer progress is updated.
"""
WINDOW_FLEXIBILITY = 4
WINDOW_MIN = 1
@@ -35,26 +32,31 @@ class Resource:
# maximum size a resource should be, if
# it is to be handled within reasonable
# time constraint, even on small systems.
#
# A small system in this regard is
# defined as a Raspberry Pi, which should
# be able to compress, encrypt and hash-map
# the resource in about 10 seconds.
#
# This constant will be used when determining
# how to sequence the sending of large resources.
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024
#
# Capped at 16777215 (0xFFFFFF) per segment to
# fit in 3 bytes in resource advertisements.
MAX_EFFICIENT_SIZE = 16 * 1024 * 1024 - 1
RESPONSE_MAX_GRACE_TIME = 10
# The maximum size to auto-compress with
# bz2 before sending.
AUTO_COMPRESS_MAX_SIZE = MAX_EFFICIENT_SIZE
# TODO: Should be allocated more
# intelligently
# TODO: Set higher
MAX_RETRIES = 5
SENDER_GRACE_TIME = 10
RETRY_GRACE_TIME = 0.25
PART_TIMEOUT_FACTOR = 4
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
MAX_RETRIES = 5
SENDER_GRACE_TIME = 10
RETRY_GRACE_TIME = 0.25
WATCHDOG_MAX_SLEEP = 1
HASHMAP_IS_NOT_EXHAUSTED = 0x00
HASHMAP_IS_EXHAUSTED = 0xFF
@@ -71,11 +73,11 @@ class Resource:
CORRUPT = 0x08
@staticmethod
def accept(advertisement_packet, callback=None, progress_callback = None):
def accept(advertisement_packet, callback=None, progress_callback = None, request_id = None):
try:
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
resource = Resource(None, advertisement_packet.link)
resource = Resource(None, advertisement_packet.link, request_id = request_id)
resource.status = Resource.TRANSFERRING
resource.flags = adv.f
@@ -120,7 +122,8 @@ class Resource:
resource.link.register_incoming_resource(resource)
RNS.log("Accepting resource advertisement for "+RNS.prettyhexrep(resource.hash), RNS.LOG_DEBUG)
resource.link.callbacks.resource_started(resource)
if resource.link.callbacks.resource_started != None:
resource.link.callbacks.resource_started(resource)
resource.hashmap_update(0, resource.hashmap_raw)
@@ -134,7 +137,7 @@ class Resource:
# Create a resource for transmission to a remote destination
# The data passed can be either a bytes-array or a file opened
# in binary read mode.
def __init__(self, data, link, advertise=True, auto_compress=True, must_compress=False, callback=None, progress_callback=None, segment_index = 1, original_hash = None):
def __init__(self, data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout = None, segment_index = 1, original_hash = None, request_id = None, is_response = False):
data_size = None
resource_data = None
if hasattr(data, "read"):
@@ -181,24 +184,32 @@ class Resource:
self.link = link
self.max_retries = Resource.MAX_RETRIES
self.retries_left = self.max_retries
self.default_timeout = self.link.default_timeout
self.timeout_factor = self.link.timeout_factor
self.timeout_factor = self.link.traffic_timeout_factor
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR
self.sender_grace_time = Resource.SENDER_GRACE_TIME
self.hmu_retry_ok = False
self.watchdog_lock = False
self.__watchdog_job_id = 0
self.__progress_callback = progress_callback
self.rtt = None
self.request_id = request_id
self.is_response = is_response
self.req_hashlist = []
self.receiver_min_consecutive_height = 0
if timeout != None:
self.timeout = timeout
else:
self.timeout = self.link.rtt * self.link.traffic_timeout_factor
if data != None:
self.initiator = True
self.callback = callback
self.uncompressed_data = data
compression_began = time.time()
if must_compress or (auto_compress and len(self.uncompressed_data) < Resource.AUTO_COMPRESS_MAX_SIZE):
if (auto_compress and len(self.uncompressed_data) < Resource.AUTO_COMPRESS_MAX_SIZE):
RNS.log("Compressing resource data...", RNS.LOG_DEBUG)
self.compressed_data = bz2.compress(self.uncompressed_data)
RNS.log("Compression completed in "+str(round(time.time()-compression_began, 3))+" seconds", RNS.LOG_DEBUG)
@@ -234,11 +245,8 @@ class Resource:
# make optimal use of packet MTU on an entire
# encrypted stream. The Resource instance will
# use it's underlying link directly to encrypt.
if not self.link.encryption_disabled():
self.data = self.link.encrypt(self.data)
self.encrypted = True
else:
self.encrypted = False
self.data = self.link.encrypt(self.data)
self.encrypted = True
self.size = len(self.data)
self.sent_parts = 0
@@ -251,6 +259,7 @@ class Resource:
self.random_hash = RNS.Identity.get_random_hash()[:Resource.RANDOM_HASH_SIZE]
self.hash = RNS.Identity.full_hash(data+self.random_hash)
self.truncated_hash = RNS.Identity.truncated_hash(data+self.random_hash)
self.expected_proof = RNS.Identity.full_hash(data+self.hash)
if original_hash == None:
@@ -365,7 +374,7 @@ class Resource:
sleep_time = None
if self.status == Resource.ADVERTISED:
sleep_time = (self.adv_sent+self.default_timeout)-time.time()
sleep_time = (self.adv_sent+self.timeout)-time.time()
if sleep_time < 0:
if self.retries_left <= 0:
RNS.log("Resource transfer timeout after sending advertisement", RNS.LOG_DEBUG)
@@ -386,8 +395,24 @@ class Resource:
elif self.status == Resource.TRANSFERRING:
if not self.initiator:
rtt = self.link.rtt if self.rtt == None else self.rtt
sleep_time = self.last_activity + (rtt*self.timeout_factor) + Resource.RETRY_GRACE_TIME - time.time()
if self.rtt == None:
rtt = self.link.rtt
else:
rtt = self.rtt
window_remaining = self.outstanding_parts
sleep_time = self.last_activity + (rtt*(self.part_timeout_factor+window_remaining)) + Resource.RETRY_GRACE_TIME - time.time()
# TODO: Remove debug info
# RNS.log("rtt "+str(rtt))
# RNS.log("ptof "+str(self.part_timeout_factor))
# RNS.log("wait "+str((rtt*self.part_timeout_factor) + Resource.RETRY_GRACE_TIME))
# RNS.log("sleep "+str(sleep_time))
# RNS.log("wndw "+str(self.window))
# RNS.log("wndwr "+str(window_remaining))
# RNS.log("")
if sleep_time < 0:
if self.retries_left > 0:
@@ -438,7 +463,7 @@ class Resource:
self.cancel()
if sleep_time != None:
sleep(sleep_time)
sleep(min(sleep_time, Resource.WATCHDOG_MAX_SLEEP))
def assemble(self):
if not self.status == Resource.FAILED:
@@ -537,11 +562,15 @@ class Resource:
if self.req_resp == None:
self.req_resp = self.last_activity
rtt = self.req_resp-self.req_sent
self.part_timeout_factor = Resource.PART_TIMEOUT_FACTOR_AFTER_RTT
if self.rtt == None:
self.rtt = rtt
self.rtt = self.link.rtt
self.watchdog_job()
elif self.rtt < rtt:
self.rtt = rtt
elif rtt < self.rtt:
self.rtt = max(self.rtt - self.rtt*0.05, rtt)
elif rtt > self.rtt:
self.rtt = min(self.rtt + self.rtt*0.05, rtt)
if not self.status == Resource.FAILED:
self.status = Resource.TRANSFERRING
@@ -566,14 +595,21 @@ class Resource:
self.consecutive_completed_height = cp
cp += 1
if self.__progress_callback != None:
self.__progress_callback(self)
# TODO: Remove debug info
# RNS.log("outstanding_parts "+str(self.outstanding_parts))
# RNS.log("total_parts "+str(self.total_parts))
# RNS.log("received_count "+str(self.received_count))
i += 1
self.receiving_part = False
if self.__progress_callback != None:
self.__progress_callback(self)
if self.outstanding_parts == 0 and self.received_count == self.total_parts:
# TODO: Remove
#if self.outstanding_parts == 0 and self.received_count == self.total_parts:
if self.received_count == self.total_parts:
self.assemble()
elif self.outstanding_parts == 0:
# TODO: Figure out if there is a mathematically
@@ -691,6 +727,7 @@ class Resource:
if part_index % ResourceAdvertisement.HASHMAP_MAX_LEN != 0:
RNS.log("Resource sequencing error, cancelling transfer!", RNS.LOG_ERROR)
self.cancel()
return
else:
segment = part_index // ResourceAdvertisement.HASHMAP_MAX_LEN
@@ -740,16 +777,17 @@ class Resource:
self.link.resource_concluded(self)
self.callback(self)
def set_callback(self, callback):
self.callback = callback
def progress_callback(self, callback):
self.__progress_callback = callback
def progress(self):
def get_progress(self):
"""
:returns: The current progress of the resource transfer as a *float* between 0.0 and 1.0.
"""
if self.initiator:
# TODO: Remove
# progress = self.sent_parts / len(self.parts)
self.processed_parts = (self.segment_index-1)*math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
self.processed_parts += self.sent_parts
self.progress_total_parts = float(self.grand_total_parts)
@@ -770,24 +808,78 @@ class Resource:
class ResourceAdvertisement:
HASHMAP_MAX_LEN = 73
OVERHEAD = 128
HASHMAP_MAX_LEN = math.floor((RNS.Link.MDU-OVERHEAD)/Resource.MAPHASH_LEN)
COLLISION_GUARD_SIZE = 2*Resource.WINDOW_MAX+HASHMAP_MAX_LEN
def __init__(self, resource=None):
assert HASHMAP_MAX_LEN > 0, "The configured MTU is too small to include any map hashes in resource advertisments"
@staticmethod
def is_request(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
if adv.q != None and adv.u:
return True
else:
return False
@staticmethod
def is_response(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
if adv.q != None and adv.p:
return True
else:
return False
@staticmethod
def get_request_id(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.q
@staticmethod
def get_transfer_size(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.t
@staticmethod
def get_size(advertisement_packet):
adv = ResourceAdvertisement.unpack(advertisement_packet.plaintext)
return adv.d
def __init__(self, resource=None, request_id=None, is_response=False):
if resource != None:
self.t = resource.size # Transfer size
self.d = resource.total_size # Total uncompressed data size
self.n = len(resource.parts) # Number of parts
self.h = resource.hash # Resource hash
self.r = resource.random_hash # Resource random hash
self.o = resource.original_hash # First-segment hash
self.m = resource.hashmap # Resource hashmap
self.c = resource.compressed # Compression flag
self.e = resource.encrypted # Encryption flag
self.s = resource.split # Split flag
self.i = resource.segment_index # Segment index
self.l = resource.total_segments # Total segments
self.f = 0x00 | self.s << 2 | self.c << 1 | self.e # Flags
self.t = resource.size # Transfer size
self.d = resource.total_size # Total uncompressed data size
self.n = len(resource.parts) # Number of parts
self.h = resource.hash # Resource hash
self.r = resource.random_hash # Resource random hash
self.o = resource.original_hash # First-segment hash
self.m = resource.hashmap # Resource hashmap
self.c = resource.compressed # Compression flag
self.e = resource.encrypted # Encryption flag
self.s = resource.split # Split flag
self.i = resource.segment_index # Segment index
self.l = resource.total_segments # Total segments
self.q = resource.request_id # ID of associated request
self.u = False # Is request flag
self.p = False # Is response flag
if self.q != None:
if not resource.is_response:
self.u = True
self.p = False
else:
self.u = False
self.p = True
# Flags
self.f = 0x00 | self.p << 4 | self.u << 3 | self.s << 2 | self.c << 1 | self.e
def pack(self, segment=0):
hashmap_start = segment*ResourceAdvertisement.HASHMAP_MAX_LEN
@@ -806,12 +898,14 @@ class ResourceAdvertisement:
"o": self.o, # Original hash
"i": self.i, # Segment index
"l": self.l, # Total segments
"q": self.q, # Request ID
"f": self.f, # Resource flags
"m": hashmap
}
return umsgpack.packb(dictionary)
@staticmethod
def unpack(data):
dictionary = umsgpack.unpackb(data)
@@ -827,8 +921,11 @@ class ResourceAdvertisement:
adv.f = dictionary["f"]
adv.i = dictionary["i"]
adv.l = dictionary["l"]
adv.q = dictionary["q"]
adv.e = True if (adv.f & 0x01) == 0x01 else False
adv.c = True if ((adv.f >> 1) & 0x01) == 0x01 else False
adv.s = True if ((adv.f >> 2) & 0x01) == 0x01 else False
adv.u = True if ((adv.f >> 3) & 0x01) == 0x01 else False
adv.p = True if ((adv.f >> 4) & 0x01) == 0x01 else False
return adv
+103 -56
View File
@@ -36,15 +36,26 @@ class Reticulum:
other programs to use on demand.
"""
# The default RNS MTU is 500 bytes. This number has been chosen as
# a balance between compatibility with existing hardware devices
# on one hand, and the ability to use sufficiently high cryptographic
# key sizes on the other. In custom RNS network implementations, it
# is possible to raise this value, but doing so will completely break
# compatibility with all other RNS networks. An identical MTU is a
# prerequisite for peers to communicate in the same network.
# Future minimum will probably be locked in at 244 bytes to support
# networks with segments of different MTUs. Absolute minimum is 211.
MTU = 500
HEADER_MAXSIZE = 23
"""
The MTU that Reticulum adheres to, and will expect other peers to
adhere to. By default, the MTU is 500 bytes. In custom RNS network
implementations, it is possible to change this value, but doing so will
completely break compatibility with all other RNS networks. An identical
MTU is a prerequisite for peers to communicate in the same network.
Unless you really know what you are doing, the MTU should be left at
the default value.
"""
# Length of truncated hashes in bits.
TRUNCATED_HASHLENGTH = 80
HEADER_MINSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*1
HEADER_MAXSIZE = 2+1+(TRUNCATED_HASHLENGTH//8)*2
MDU = MTU - HEADER_MAXSIZE
router = None
@@ -84,7 +95,6 @@ class Reticulum:
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
Reticulum.__allow_unencrypted = False
Reticulum.__transport_enabled = False
Reticulum.__use_implicit_proof = True
@@ -191,20 +201,6 @@ class Reticulum:
Reticulum.__use_implicit_proof = True
if v == False:
Reticulum.__use_implicit_proof = False
if option == "allow_unencrypted":
v = self.config["reticulum"].as_bool(option)
if v == True:
RNS.log("", RNS.LOG_CRITICAL)
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
RNS.log("", RNS.LOG_CRITICAL)
RNS.log("Danger! Encryptionless links have been allowed in the config file!", RNS.LOG_CRITICAL)
RNS.log("Beware of the consequences! Any data sent over a link can potentially be intercepted,", RNS.LOG_CRITICAL)
RNS.log("read and modified! If you are not absolutely sure that you want this,", RNS.LOG_CRITICAL)
RNS.log("you should exit Reticulum NOW and change your config file!", RNS.LOG_CRITICAL)
RNS.log("", RNS.LOG_CRITICAL)
RNS.log("! ! ! ! ! ! ! ! !", RNS.LOG_CRITICAL)
RNS.log("", RNS.LOG_CRITICAL)
Reticulum.__allow_unencrypted = True
self.__start_local_interface()
@@ -217,13 +213,27 @@ class Reticulum:
try:
if ("interface_enabled" in c) and c.as_bool("interface_enabled") == True:
if c["type"] == "UDPInterface":
device = c["device"] if "device" in c else None
port = int(c["port"]) if "port" in c else None
listen_ip = c["listen_ip"] if "listen_ip" in c else None
listen_port = int(c["listen_port"]) if "listen_port" in c else None
forward_ip = c["forward_ip"] if "forward_ip" in c else None
forward_port = int(c["forward_port"]) if "forward_port" in c else None
if port != None:
if listen_port == None:
listen_port = port
if forward_port == None:
forward_port = port
interface = UDPInterface.UDPInterface(
RNS.Transport,
name,
c["listen_ip"],
int(c["listen_port"]),
c["forward_ip"],
int(c["forward_port"])
device,
listen_ip,
listen_port,
forward_ip,
forward_port
)
if "outgoing" in c and c.as_bool("outgoing") == True:
@@ -235,11 +245,20 @@ class Reticulum:
if c["type"] == "TCPServerInterface":
device = c["device"] if "device" in c else None
port = int(c["port"]) if "port" in c else None
listen_ip = c["listen_ip"] if "listen_ip" in c else None
listen_port = int(c["listen_port"]) if "listen_port" in c else None
if port != None:
listen_port = port
interface = TCPInterface.TCPServerInterface(
RNS.Transport,
name,
c["listen_ip"],
int(c["listen_port"])
device,
listen_ip,
listen_port
)
if "outgoing" in c and c.as_bool("outgoing") == True:
@@ -432,16 +451,6 @@ class Reticulum:
self.config.write()
self.__apply_config()
@staticmethod
def should_allow_unencrypted():
"""
Returns whether unencrypted links are allowed by the
current configuration.
:returns: True if the current running configuration allows downgrading links to plaintext. False if not.
"""
return Reticulum.__allow_unencrypted
@staticmethod
def should_use_implicit_proof():
"""
@@ -472,20 +481,12 @@ __default_rns_config__ = '''# This is the default Reticulum config file.
[reticulum]
# Don't allow unencrypted links by default.
# If you REALLY need to allow unencrypted links, for example
# for debug or regulatory purposes, this can be set to true.
# This directive is optional and can be removed for brevity.
allow_unencrypted = False
# If you enable Transport, your system will route traffic
# for other peers, pass announces and serve path requests.
# Unless you really know what you're doing, this should be
# done only for systems that are suited to act as transport
# nodes, ie. if they are stationary and always-on. This
# directive is optional and can be removed for brevity.
# This should be done for systems that are suited to act
# as transport nodes, ie. if they are stationary and
# always-on. This directive is optional and can be removed
# for brevity.
enable_transport = False
@@ -533,11 +534,11 @@ loglevel = 4
[interfaces]
# This interface enables communication with other
# Reticulum nodes on your local ethernet networks.
# It's enabled by default, and provides basic
# connectivity to other peers in your local ethernet
# broadcast domain. You can modify it to suit your
# needs or turn it off completely.
# local Reticulum nodes over UDP. You can modify it
# to suit your needs or turn it off completely.
# As a minimum, you should probably specify the
# network device you want to communicate on, such
# as eth0 or wlan0.
[[Default UDP Interface]]
type = UDPInterface
@@ -548,6 +549,38 @@ loglevel = 4
forward_ip = 255.255.255.255
forward_port = 4242
# The above configuration will allow communication
# within the local broadcast domains of all local
# IP interfaces. This is enabled by default as an
# easy way to get started, but you might want to
# consider altering it to something more specific.
# Instead of specifying listen_ip, listen_port,
# forward_ip and forward_port, you can also bind
# to a specific network device like below.
# device = eth0
# port = 4242
# Assuming the eth0 device has the address
# 10.55.0.72/24, the above configuration would
# be equivalent to the following manual setup.
# Note that we are both listening and forwarding to
# the broadcast address of the network segments.
# listen_ip = 10.55.0.255
# listen_port = 4242
# forward_ip = 10.55.0.255
# forward_port = 4242
# You can of course also communicate only with
# a single IP address
# listen_ip = 10.55.0.15
# listen_port = 4242
# forward_ip = 10.55.0.16
# forward_port = 4242
# This example demonstrates a TCP server interface.
# It will listen for incoming connections on the
@@ -557,9 +590,23 @@ loglevel = 4
type = TCPServerInterface
interface_enabled = False
outgoing = True
# This configuration will listen on all IP
# interfaces on port 4242
listen_ip = 0.0.0.0
listen_port = 4242
# Alternatively you can bind to a specific IP
# listen_ip = 10.0.0.88
# listen_port = 4242
# Or a specific network device
# device = eth0
# port = 4242
# To connect to a TCP server interface, you would
# naturally use the TCP client interface. Here's
+81 -36
View File
@@ -9,6 +9,10 @@ from time import sleep
from .vendor import umsgpack as umsgpack
class Transport:
"""
Through static methods of this class you can interact with Reticulums
Transport system.
"""
# Constants
BROADCAST = 0x00;
TRANSPORT = 0x01;
@@ -22,9 +26,10 @@ class Transport:
APP_NAME = "rnstransport"
# TODO: Document the addition of random windows
# and max local rebroadcasts.
PATHFINDER_M = 18 # Max hops
PATHFINDER_M = 128 # Max hops
"""
Maximum amount of hops that Reticulum will transport a packet.
"""
PATHFINDER_C = 2.0 # Decay constant
PATHFINDER_R = 1 # Retransmit retries
PATHFINDER_T = 10 # Retry grace period
@@ -51,6 +56,8 @@ class Transport:
receipts = [] # Receipts of all outgoing packets for proof processing
# TODO: "destination_table" should really be renamed to "path_table"
# Notes on memory usage: 1 megabyte of memory can store approximately
# 55.100 path table entries or approximately 22.300 link table entries.
announce_table = {} # A table for storing announces currently waiting to be retransmitted
destination_table = {} # A lookup table containing the next hop to a given destination
reverse_table = {} # A lookup table for storing packet hashes used to return proofs and replies
@@ -108,7 +115,7 @@ class Transport:
# Create transport-specific destinations
Transport.path_request_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "path", "request")
Transport.path_request_destination.packet_callback(Transport.path_request_handler)
Transport.path_request_destination.set_packet_callback(Transport.path_request_handler)
Transport.control_destinations.append(Transport.path_request_destination)
Transport.control_hashes.append(Transport.path_request_destination.hash)
@@ -180,7 +187,7 @@ class Transport:
while len(Transport.receipts) > Transport.MAX_RECEIPTS:
culled_receipt = Transport.receipts.pop(0)
culled_receipt.timeout = -1
receipt.check_timeout()
culled_receipt.check_timeout()
for receipt in Transport.receipts:
receipt.check_timeout()
@@ -246,8 +253,8 @@ class Transport:
# Cull the packet hashlist if it has reached max size
while (len(Transport.packet_hashlist) > Transport.hashlist_maxsize):
Transport.packet_hashlist.pop(0)
if len(Transport.packet_hashlist) > Transport.hashlist_maxsize:
Transport.packet_hashlist = Transport.packet_hashlist[len(Transport.packet_hashlist)-Transport.hashlist_maxsize:len(Transport.packet_hashlist)-1]
if time.time() > Transport.tables_last_culled + Transport.tables_cull_interval:
# Cull the reverse table according to timeout
@@ -338,8 +345,6 @@ class Transport:
new_raw += packet.raw[1:2]
new_raw += Transport.destination_table[packet.destination_hash][1]
new_raw += packet.raw[2:]
# TODO: Remove at some point
# RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
outbound_interface.processOutgoing(new_raw)
Transport.destination_table[packet.destination_hash][0] = time.time()
sent = True
@@ -359,8 +364,6 @@ class Transport:
new_raw += packet.raw[1:2]
new_raw += Transport.destination_table[packet.destination_hash][1]
new_raw += packet.raw[2:]
# TODO: Remove at some point
# RNS.log("Packet was inserted into transport via "+RNS.prettyhexrep(Transport.destination_table[packet.destination_hash][1])+" on: "+str(outbound_interface), RNS.LOG_EXTREME)
outbound_interface.processOutgoing(new_raw)
Transport.destination_table[packet.destination_hash][0] = time.time()
sent = True
@@ -377,6 +380,7 @@ class Transport:
# just the relevant interface if the packet has an attached
# interface, or belongs to a link.
else:
stored_hash = False
for interface in Transport.interfaces:
if interface.OUT:
should_transmit = True
@@ -389,8 +393,9 @@ class Transport:
should_transmit = False
if should_transmit:
RNS.log("Transmitting "+str(len(packet.raw))+" bytes on: "+str(interface), RNS.LOG_EXTREME)
RNS.log("Hash is "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME)
if not stored_hash:
Transport.packet_hashlist.append(packet.packet_hash)
interface.processOutgoing(packet.raw)
sent = True
@@ -398,7 +403,7 @@ class Transport:
packet.sent = True
packet.sent_at = time.time()
# Don't generate receipt if it has been explicitly disabled
# Don't generate receipt if it has been explicitly disabled
if (packet.create_receipt == True and
# Only generate receipts for DATA packets
packet.packet_type == RNS.Packet.DATA and
@@ -447,8 +452,7 @@ class Transport:
@staticmethod
def inbound(raw, interface=None):
while (Transport.jobs_running):
# TODO: Decrease this for performance
sleep(0.1)
sleep(0.01)
Transport.jobs_locked = True
@@ -457,8 +461,6 @@ class Transport:
packet.receiving_interface = interface
packet.hops += 1
RNS.log(str(interface)+" received packet with hash "+RNS.prettyhexrep(packet.packet_hash), RNS.LOG_EXTREME)
if len(Transport.local_client_interfaces) > 0:
if Transport.is_local_client_interface(interface):
@@ -523,11 +525,10 @@ class Transport:
# accordingly if we are.
if packet.transport_id != None and packet.packet_type != RNS.Packet.ANNOUNCE:
if packet.transport_id == Transport.identity.hash:
RNS.log("Received packet in transport for "+RNS.prettyhexrep(packet.destination_hash)+" with matching transport ID, transporting it...", RNS.LOG_DEBUG)
if packet.destination_hash in Transport.destination_table:
next_hop = Transport.destination_table[packet.destination_hash][1]
remaining_hops = Transport.destination_table[packet.destination_hash][2]
RNS.log("Next hop to destination is "+RNS.prettyhexrep(next_hop)+" with "+str(remaining_hops)+" hops remaining, transporting it.", RNS.LOG_DEBUG)
if remaining_hops > 1:
# Just increase hop count and transmit
new_raw = packet.raw[0:1]
@@ -652,7 +653,7 @@ class Transport:
# First, check that the announce is not for a destination
# local to this system, and that hops are less than the max
if (not any(packet.destination_hash == d.hash for d in Transport.destinations) and packet.hops < Transport.PATHFINDER_M+1):
random_blob = packet.data[RNS.Identity.DERKEYSIZE//8+10:RNS.Identity.DERKEYSIZE//8+20]
random_blob = packet.data[RNS.Identity.KEYSIZE//8+10:RNS.Identity.KEYSIZE//8+20]
random_blobs = []
if packet.destination_hash in Transport.destination_table:
random_blobs = Transport.destination_table[packet.destination_hash][4]
@@ -734,19 +735,39 @@ class Transport:
announce_context = RNS.Packet.NONE
announce_data = packet.data
new_announce = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = announce_context,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = attached_interface
)
if Transport.from_local_client(packet) and packet.context == RNS.Packet.PATH_RESPONSE:
for interface in Transport.interfaces:
if packet.receiving_interface != interface:
new_announce = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = announce_context,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = interface
)
new_announce.hops = packet.hops
new_announce.send()
else:
for local_interface in Transport.local_client_interfaces:
new_announce = RNS.Packet(
announce_destination,
announce_data,
RNS.Packet.ANNOUNCE,
context = announce_context,
header_type = RNS.Packet.HEADER_2,
transport_type = Transport.TRANSPORT,
transport_id = Transport.identity.hash,
attached_interface = local_interface
)
new_announce.hops = packet.hops
new_announce.send()
new_announce.hops = packet.hops
new_announce.send()
Transport.destination_table[packet.destination_hash] = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet]
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(packet.receiving_interface), RNS.LOG_VERBOSE)
@@ -864,12 +885,16 @@ class Transport:
if receipt.hash == proof_hash:
receipt_validated = receipt.validate_proof_packet(packet)
else:
# TODO: This looks like it should actually
# be rewritten when implicit proofs are added.
# In case of an implicit proof, we have
# to check every single outstanding receipt
receipt_validated = receipt.validate_proof_packet(packet)
if receipt_validated:
Transport.receipts.remove(receipt)
if receipt in Transport.receipts:
Transport.receipts.remove(receipt)
Transport.jobs_locked = False
@@ -880,8 +905,13 @@ class Transport:
for registered_destination in Transport.destinations:
if destination.hash == registered_destination.hash:
raise KeyError("Attempt to register an already registered destination.")
Transport.destinations.append(destination)
if Transport.owner.is_connected_to_shared_instance:
if destination.type == RNS.Destination.SINGLE:
destination.announce(path_response=True)
@staticmethod
def deregister_destination(destination):
if destination in Transport.destinations:
@@ -1028,6 +1058,17 @@ class Transport:
else:
return False
@staticmethod
def hops_to(destination_hash):
"""
:param destination_hash: A destination hash as *bytes*.
:returns: The number of hops to the specified destination, or ``RNS.Transport.PATHFINDER_M`` if the number of hops is unknown.
"""
if destination_hash in Transport.destination_table:
return Transport.destination_table[destination_hash][2]
else:
return Transport.PATHFINDER_M
@staticmethod
def request_path(destination_hash):
"""
@@ -1138,13 +1179,17 @@ class Transport:
@staticmethod
def exit_handler():
RNS.log("Saving packet hashlist to storage...", RNS.LOG_VERBOSE)
try:
if not RNS.Reticulum.transport_enabled():
Transport.packet_hashlist = []
else:
RNS.log("Saving packet hashlist to storage...", RNS.LOG_VERBOSE)
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
file = open(packet_hashlist_path, "wb")
file.write(umsgpack.packb(Transport.packet_hashlist))
file.close()
RNS.log("Done packet hashlist to storage", RNS.LOG_VERBOSE)
except Exception as e:
RNS.log("Could not save packet hashlist to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
+8 -3
View File
@@ -5,14 +5,16 @@ import time
import random
import threading
from ._version import __version__
from .Reticulum import Reticulum
from .Identity import Identity
from .Link import Link
from .Link import Link, RequestReceipt
from .Transport import Transport
from .Destination import Destination
from .Packet import Packet
from .Packet import PacketReceipt
from .Resource import Resource
from .Resource import Resource, ResourceAdvertisement
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
@@ -60,6 +62,9 @@ def loglevelname(level):
return "Unknown"
def version():
return __version__
def log(msg, level=3, _override_destination = False):
global _always_override_destination
@@ -68,7 +73,7 @@ def log(msg, level=3, _override_destination = False):
logstring = "["+time.strftime(logtimefmt)+"] ["+loglevelname(level)+"] "+msg
logging_lock.acquire()
if (logdest == LOG_STDOUT or _always_override_destination):
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
print(logstring)
logging_lock.release()
+1
View File
@@ -0,0 +1 @@
__version__ = "0.2.4"
+1 -1
View File
@@ -19,7 +19,7 @@ import sys
from codecs import BOM_UTF8, BOM_UTF16, BOM_UTF16_BE, BOM_UTF16_LE
import six
import RNS.vendor.six as six
__version__ = '5.0.6'
# imported lazily to avoid startup performance hit if it isn't used
+998
View File
@@ -0,0 +1,998 @@
# Copyright (c) 2010-2020 Benjamin Peterson
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
"""Utilities for writing code that runs on Python 2 and 3"""
from __future__ import absolute_import
import functools
import itertools
import operator
import sys
import types
__author__ = "Benjamin Peterson <benjamin@python.org>"
__version__ = "1.16.0"
# Useful for very coarse version differentiation.
PY2 = sys.version_info[0] == 2
PY3 = sys.version_info[0] == 3
PY34 = sys.version_info[0:2] >= (3, 4)
if PY3:
string_types = str,
integer_types = int,
class_types = type,
text_type = str
binary_type = bytes
MAXSIZE = sys.maxsize
else:
string_types = basestring,
integer_types = (int, long)
class_types = (type, types.ClassType)
text_type = unicode
binary_type = str
if sys.platform.startswith("java"):
# Jython always uses 32 bits.
MAXSIZE = int((1 << 31) - 1)
else:
# It's possible to have sizeof(long) != sizeof(Py_ssize_t).
class X(object):
def __len__(self):
return 1 << 31
try:
len(X())
except OverflowError:
# 32-bit
MAXSIZE = int((1 << 31) - 1)
else:
# 64-bit
MAXSIZE = int((1 << 63) - 1)
del X
if PY34:
from importlib.util import spec_from_loader
else:
spec_from_loader = None
def _add_doc(func, doc):
"""Add documentation to a function."""
func.__doc__ = doc
def _import_module(name):
"""Import module, returning the module after the last dot."""
__import__(name)
return sys.modules[name]
class _LazyDescr(object):
def __init__(self, name):
self.name = name
def __get__(self, obj, tp):
result = self._resolve()
setattr(obj, self.name, result) # Invokes __set__.
try:
# This is a bit ugly, but it avoids running this again by
# removing this descriptor.
delattr(obj.__class__, self.name)
except AttributeError:
pass
return result
class MovedModule(_LazyDescr):
def __init__(self, name, old, new=None):
super(MovedModule, self).__init__(name)
if PY3:
if new is None:
new = name
self.mod = new
else:
self.mod = old
def _resolve(self):
return _import_module(self.mod)
def __getattr__(self, attr):
_module = self._resolve()
value = getattr(_module, attr)
setattr(self, attr, value)
return value
class _LazyModule(types.ModuleType):
def __init__(self, name):
super(_LazyModule, self).__init__(name)
self.__doc__ = self.__class__.__doc__
def __dir__(self):
attrs = ["__doc__", "__name__"]
attrs += [attr.name for attr in self._moved_attributes]
return attrs
# Subclasses should override this
_moved_attributes = []
class MovedAttribute(_LazyDescr):
def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None):
super(MovedAttribute, self).__init__(name)
if PY3:
if new_mod is None:
new_mod = name
self.mod = new_mod
if new_attr is None:
if old_attr is None:
new_attr = name
else:
new_attr = old_attr
self.attr = new_attr
else:
self.mod = old_mod
if old_attr is None:
old_attr = name
self.attr = old_attr
def _resolve(self):
module = _import_module(self.mod)
return getattr(module, self.attr)
class _SixMetaPathImporter(object):
"""
A meta path importer to import six.moves and its submodules.
This class implements a PEP302 finder and loader. It should be compatible
with Python 2.5 and all existing versions of Python3
"""
def __init__(self, six_module_name):
self.name = six_module_name
self.known_modules = {}
def _add_module(self, mod, *fullnames):
for fullname in fullnames:
self.known_modules[self.name + "." + fullname] = mod
def _get_module(self, fullname):
return self.known_modules[self.name + "." + fullname]
def find_module(self, fullname, path=None):
if fullname in self.known_modules:
return self
return None
def find_spec(self, fullname, path, target=None):
if fullname in self.known_modules:
return spec_from_loader(fullname, self)
return None
def __get_module(self, fullname):
try:
return self.known_modules[fullname]
except KeyError:
raise ImportError("This loader does not know module " + fullname)
def load_module(self, fullname):
try:
# in case of a reload
return sys.modules[fullname]
except KeyError:
pass
mod = self.__get_module(fullname)
if isinstance(mod, MovedModule):
mod = mod._resolve()
else:
mod.__loader__ = self
sys.modules[fullname] = mod
return mod
def is_package(self, fullname):
"""
Return true, if the named module is a package.
We need this method to get correct spec objects with
Python 3.4 (see PEP451)
"""
return hasattr(self.__get_module(fullname), "__path__")
def get_code(self, fullname):
"""Return None
Required, if is_package is implemented"""
self.__get_module(fullname) # eventually raises ImportError
return None
get_source = get_code # same as get_code
def create_module(self, spec):
return self.load_module(spec.name)
def exec_module(self, module):
pass
_importer = _SixMetaPathImporter(__name__)
class _MovedItems(_LazyModule):
"""Lazy loading of moved objects"""
__path__ = [] # mark as package
_moved_attributes = [
MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"),
MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"),
MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"),
MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"),
MovedAttribute("intern", "__builtin__", "sys"),
MovedAttribute("map", "itertools", "builtins", "imap", "map"),
MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"),
MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"),
MovedAttribute("getoutput", "commands", "subprocess"),
MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"),
MovedAttribute("reduce", "__builtin__", "functools"),
MovedAttribute("shlex_quote", "pipes", "shlex", "quote"),
MovedAttribute("StringIO", "StringIO", "io"),
MovedAttribute("UserDict", "UserDict", "collections"),
MovedAttribute("UserList", "UserList", "collections"),
MovedAttribute("UserString", "UserString", "collections"),
MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"),
MovedAttribute("zip", "itertools", "builtins", "izip", "zip"),
MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"),
MovedModule("builtins", "__builtin__"),
MovedModule("configparser", "ConfigParser"),
MovedModule("collections_abc", "collections", "collections.abc" if sys.version_info >= (3, 3) else "collections"),
MovedModule("copyreg", "copy_reg"),
MovedModule("dbm_gnu", "gdbm", "dbm.gnu"),
MovedModule("dbm_ndbm", "dbm", "dbm.ndbm"),
MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread" if sys.version_info < (3, 9) else "_thread"),
MovedModule("http_cookiejar", "cookielib", "http.cookiejar"),
MovedModule("http_cookies", "Cookie", "http.cookies"),
MovedModule("html_entities", "htmlentitydefs", "html.entities"),
MovedModule("html_parser", "HTMLParser", "html.parser"),
MovedModule("http_client", "httplib", "http.client"),
MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"),
MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"),
MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"),
MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"),
MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"),
MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"),
MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"),
MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"),
MovedModule("cPickle", "cPickle", "pickle"),
MovedModule("queue", "Queue"),
MovedModule("reprlib", "repr"),
MovedModule("socketserver", "SocketServer"),
MovedModule("_thread", "thread", "_thread"),
MovedModule("tkinter", "Tkinter"),
MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"),
MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"),
MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"),
MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"),
MovedModule("tkinter_tix", "Tix", "tkinter.tix"),
MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"),
MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"),
MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"),
MovedModule("tkinter_colorchooser", "tkColorChooser",
"tkinter.colorchooser"),
MovedModule("tkinter_commondialog", "tkCommonDialog",
"tkinter.commondialog"),
MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"),
MovedModule("tkinter_font", "tkFont", "tkinter.font"),
MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"),
MovedModule("tkinter_tksimpledialog", "tkSimpleDialog",
"tkinter.simpledialog"),
MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"),
MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"),
MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"),
MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"),
MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"),
MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"),
]
# Add windows specific modules.
if sys.platform == "win32":
_moved_attributes += [
MovedModule("winreg", "_winreg"),
]
for attr in _moved_attributes:
setattr(_MovedItems, attr.name, attr)
if isinstance(attr, MovedModule):
_importer._add_module(attr, "moves." + attr.name)
del attr
_MovedItems._moved_attributes = _moved_attributes
moves = _MovedItems(__name__ + ".moves")
_importer._add_module(moves, "moves")
class Module_six_moves_urllib_parse(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_parse"""
_urllib_parse_moved_attributes = [
MovedAttribute("ParseResult", "urlparse", "urllib.parse"),
MovedAttribute("SplitResult", "urlparse", "urllib.parse"),
MovedAttribute("parse_qs", "urlparse", "urllib.parse"),
MovedAttribute("parse_qsl", "urlparse", "urllib.parse"),
MovedAttribute("urldefrag", "urlparse", "urllib.parse"),
MovedAttribute("urljoin", "urlparse", "urllib.parse"),
MovedAttribute("urlparse", "urlparse", "urllib.parse"),
MovedAttribute("urlsplit", "urlparse", "urllib.parse"),
MovedAttribute("urlunparse", "urlparse", "urllib.parse"),
MovedAttribute("urlunsplit", "urlparse", "urllib.parse"),
MovedAttribute("quote", "urllib", "urllib.parse"),
MovedAttribute("quote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote", "urllib", "urllib.parse"),
MovedAttribute("unquote_plus", "urllib", "urllib.parse"),
MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"),
MovedAttribute("urlencode", "urllib", "urllib.parse"),
MovedAttribute("splitquery", "urllib", "urllib.parse"),
MovedAttribute("splittag", "urllib", "urllib.parse"),
MovedAttribute("splituser", "urllib", "urllib.parse"),
MovedAttribute("splitvalue", "urllib", "urllib.parse"),
MovedAttribute("uses_fragment", "urlparse", "urllib.parse"),
MovedAttribute("uses_netloc", "urlparse", "urllib.parse"),
MovedAttribute("uses_params", "urlparse", "urllib.parse"),
MovedAttribute("uses_query", "urlparse", "urllib.parse"),
MovedAttribute("uses_relative", "urlparse", "urllib.parse"),
]
for attr in _urllib_parse_moved_attributes:
setattr(Module_six_moves_urllib_parse, attr.name, attr)
del attr
Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes
_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"),
"moves.urllib_parse", "moves.urllib.parse")
class Module_six_moves_urllib_error(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_error"""
_urllib_error_moved_attributes = [
MovedAttribute("URLError", "urllib2", "urllib.error"),
MovedAttribute("HTTPError", "urllib2", "urllib.error"),
MovedAttribute("ContentTooShortError", "urllib", "urllib.error"),
]
for attr in _urllib_error_moved_attributes:
setattr(Module_six_moves_urllib_error, attr.name, attr)
del attr
Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes
_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"),
"moves.urllib_error", "moves.urllib.error")
class Module_six_moves_urllib_request(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_request"""
_urllib_request_moved_attributes = [
MovedAttribute("urlopen", "urllib2", "urllib.request"),
MovedAttribute("install_opener", "urllib2", "urllib.request"),
MovedAttribute("build_opener", "urllib2", "urllib.request"),
MovedAttribute("pathname2url", "urllib", "urllib.request"),
MovedAttribute("url2pathname", "urllib", "urllib.request"),
MovedAttribute("getproxies", "urllib", "urllib.request"),
MovedAttribute("Request", "urllib2", "urllib.request"),
MovedAttribute("OpenerDirector", "urllib2", "urllib.request"),
MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"),
MovedAttribute("ProxyHandler", "urllib2", "urllib.request"),
MovedAttribute("BaseHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"),
MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"),
MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"),
MovedAttribute("FileHandler", "urllib2", "urllib.request"),
MovedAttribute("FTPHandler", "urllib2", "urllib.request"),
MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"),
MovedAttribute("UnknownHandler", "urllib2", "urllib.request"),
MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"),
MovedAttribute("urlretrieve", "urllib", "urllib.request"),
MovedAttribute("urlcleanup", "urllib", "urllib.request"),
MovedAttribute("URLopener", "urllib", "urllib.request"),
MovedAttribute("FancyURLopener", "urllib", "urllib.request"),
MovedAttribute("proxy_bypass", "urllib", "urllib.request"),
MovedAttribute("parse_http_list", "urllib2", "urllib.request"),
MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"),
]
for attr in _urllib_request_moved_attributes:
setattr(Module_six_moves_urllib_request, attr.name, attr)
del attr
Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes
_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"),
"moves.urllib_request", "moves.urllib.request")
class Module_six_moves_urllib_response(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_response"""
_urllib_response_moved_attributes = [
MovedAttribute("addbase", "urllib", "urllib.response"),
MovedAttribute("addclosehook", "urllib", "urllib.response"),
MovedAttribute("addinfo", "urllib", "urllib.response"),
MovedAttribute("addinfourl", "urllib", "urllib.response"),
]
for attr in _urllib_response_moved_attributes:
setattr(Module_six_moves_urllib_response, attr.name, attr)
del attr
Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes
_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"),
"moves.urllib_response", "moves.urllib.response")
class Module_six_moves_urllib_robotparser(_LazyModule):
"""Lazy loading of moved objects in six.moves.urllib_robotparser"""
_urllib_robotparser_moved_attributes = [
MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"),
]
for attr in _urllib_robotparser_moved_attributes:
setattr(Module_six_moves_urllib_robotparser, attr.name, attr)
del attr
Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes
_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"),
"moves.urllib_robotparser", "moves.urllib.robotparser")
class Module_six_moves_urllib(types.ModuleType):
"""Create a six.moves.urllib namespace that resembles the Python 3 namespace"""
__path__ = [] # mark as package
parse = _importer._get_module("moves.urllib_parse")
error = _importer._get_module("moves.urllib_error")
request = _importer._get_module("moves.urllib_request")
response = _importer._get_module("moves.urllib_response")
robotparser = _importer._get_module("moves.urllib_robotparser")
def __dir__(self):
return ['parse', 'error', 'request', 'response', 'robotparser']
_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"),
"moves.urllib")
def add_move(move):
"""Add an item to six.moves."""
setattr(_MovedItems, move.name, move)
def remove_move(name):
"""Remove item from six.moves."""
try:
delattr(_MovedItems, name)
except AttributeError:
try:
del moves.__dict__[name]
except KeyError:
raise AttributeError("no such move, %r" % (name,))
if PY3:
_meth_func = "__func__"
_meth_self = "__self__"
_func_closure = "__closure__"
_func_code = "__code__"
_func_defaults = "__defaults__"
_func_globals = "__globals__"
else:
_meth_func = "im_func"
_meth_self = "im_self"
_func_closure = "func_closure"
_func_code = "func_code"
_func_defaults = "func_defaults"
_func_globals = "func_globals"
try:
advance_iterator = next
except NameError:
def advance_iterator(it):
return it.next()
next = advance_iterator
try:
callable = callable
except NameError:
def callable(obj):
return any("__call__" in klass.__dict__ for klass in type(obj).__mro__)
if PY3:
def get_unbound_function(unbound):
return unbound
create_bound_method = types.MethodType
def create_unbound_method(func, cls):
return func
Iterator = object
else:
def get_unbound_function(unbound):
return unbound.im_func
def create_bound_method(func, obj):
return types.MethodType(func, obj, obj.__class__)
def create_unbound_method(func, cls):
return types.MethodType(func, None, cls)
class Iterator(object):
def next(self):
return type(self).__next__(self)
callable = callable
_add_doc(get_unbound_function,
"""Get the function out of a possibly unbound function""")
get_method_function = operator.attrgetter(_meth_func)
get_method_self = operator.attrgetter(_meth_self)
get_function_closure = operator.attrgetter(_func_closure)
get_function_code = operator.attrgetter(_func_code)
get_function_defaults = operator.attrgetter(_func_defaults)
get_function_globals = operator.attrgetter(_func_globals)
if PY3:
def iterkeys(d, **kw):
return iter(d.keys(**kw))
def itervalues(d, **kw):
return iter(d.values(**kw))
def iteritems(d, **kw):
return iter(d.items(**kw))
def iterlists(d, **kw):
return iter(d.lists(**kw))
viewkeys = operator.methodcaller("keys")
viewvalues = operator.methodcaller("values")
viewitems = operator.methodcaller("items")
else:
def iterkeys(d, **kw):
return d.iterkeys(**kw)
def itervalues(d, **kw):
return d.itervalues(**kw)
def iteritems(d, **kw):
return d.iteritems(**kw)
def iterlists(d, **kw):
return d.iterlists(**kw)
viewkeys = operator.methodcaller("viewkeys")
viewvalues = operator.methodcaller("viewvalues")
viewitems = operator.methodcaller("viewitems")
_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.")
_add_doc(itervalues, "Return an iterator over the values of a dictionary.")
_add_doc(iteritems,
"Return an iterator over the (key, value) pairs of a dictionary.")
_add_doc(iterlists,
"Return an iterator over the (key, [values]) pairs of a dictionary.")
if PY3:
def b(s):
return s.encode("latin-1")
def u(s):
return s
unichr = chr
import struct
int2byte = struct.Struct(">B").pack
del struct
byte2int = operator.itemgetter(0)
indexbytes = operator.getitem
iterbytes = iter
import io
StringIO = io.StringIO
BytesIO = io.BytesIO
del io
_assertCountEqual = "assertCountEqual"
if sys.version_info[1] <= 1:
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
_assertNotRegex = "assertNotRegexpMatches"
else:
_assertRaisesRegex = "assertRaisesRegex"
_assertRegex = "assertRegex"
_assertNotRegex = "assertNotRegex"
else:
def b(s):
return s
# Workaround for standalone backslash
def u(s):
return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape")
unichr = unichr
int2byte = chr
def byte2int(bs):
return ord(bs[0])
def indexbytes(buf, i):
return ord(buf[i])
iterbytes = functools.partial(itertools.imap, ord)
import StringIO
StringIO = BytesIO = StringIO.StringIO
_assertCountEqual = "assertItemsEqual"
_assertRaisesRegex = "assertRaisesRegexp"
_assertRegex = "assertRegexpMatches"
_assertNotRegex = "assertNotRegexpMatches"
_add_doc(b, """Byte literal""")
_add_doc(u, """Text literal""")
def assertCountEqual(self, *args, **kwargs):
return getattr(self, _assertCountEqual)(*args, **kwargs)
def assertRaisesRegex(self, *args, **kwargs):
return getattr(self, _assertRaisesRegex)(*args, **kwargs)
def assertRegex(self, *args, **kwargs):
return getattr(self, _assertRegex)(*args, **kwargs)
def assertNotRegex(self, *args, **kwargs):
return getattr(self, _assertNotRegex)(*args, **kwargs)
if PY3:
exec_ = getattr(moves.builtins, "exec")
def reraise(tp, value, tb=None):
try:
if value is None:
value = tp()
if value.__traceback__ is not tb:
raise value.with_traceback(tb)
raise value
finally:
value = None
tb = None
else:
def exec_(_code_, _globs_=None, _locs_=None):
"""Execute code in a namespace."""
if _globs_ is None:
frame = sys._getframe(1)
_globs_ = frame.f_globals
if _locs_ is None:
_locs_ = frame.f_locals
del frame
elif _locs_ is None:
_locs_ = _globs_
exec("""exec _code_ in _globs_, _locs_""")
exec_("""def reraise(tp, value, tb=None):
try:
raise tp, value, tb
finally:
tb = None
""")
if sys.version_info[:2] > (3,):
exec_("""def raise_from(value, from_value):
try:
raise value from from_value
finally:
value = None
""")
else:
def raise_from(value, from_value):
raise value
print_ = getattr(moves.builtins, "print", None)
if print_ is None:
def print_(*args, **kwargs):
"""The new-style print function for Python 2.4 and 2.5."""
fp = kwargs.pop("file", sys.stdout)
if fp is None:
return
def write(data):
if not isinstance(data, basestring):
data = str(data)
# If the file has an encoding, encode unicode with it.
if (isinstance(fp, file) and
isinstance(data, unicode) and
fp.encoding is not None):
errors = getattr(fp, "errors", None)
if errors is None:
errors = "strict"
data = data.encode(fp.encoding, errors)
fp.write(data)
want_unicode = False
sep = kwargs.pop("sep", None)
if sep is not None:
if isinstance(sep, unicode):
want_unicode = True
elif not isinstance(sep, str):
raise TypeError("sep must be None or a string")
end = kwargs.pop("end", None)
if end is not None:
if isinstance(end, unicode):
want_unicode = True
elif not isinstance(end, str):
raise TypeError("end must be None or a string")
if kwargs:
raise TypeError("invalid keyword arguments to print()")
if not want_unicode:
for arg in args:
if isinstance(arg, unicode):
want_unicode = True
break
if want_unicode:
newline = unicode("\n")
space = unicode(" ")
else:
newline = "\n"
space = " "
if sep is None:
sep = space
if end is None:
end = newline
for i, arg in enumerate(args):
if i:
write(sep)
write(arg)
write(end)
if sys.version_info[:2] < (3, 3):
_print = print_
def print_(*args, **kwargs):
fp = kwargs.get("file", sys.stdout)
flush = kwargs.pop("flush", False)
_print(*args, **kwargs)
if flush and fp is not None:
fp.flush()
_add_doc(reraise, """Reraise an exception.""")
if sys.version_info[0:2] < (3, 4):
# This does exactly the same what the :func:`py3:functools.update_wrapper`
# function does on Python versions after 3.2. It sets the ``__wrapped__``
# attribute on ``wrapper`` object and it doesn't raise an error if any of
# the attributes mentioned in ``assigned`` and ``updated`` are missing on
# ``wrapped`` object.
def _update_wrapper(wrapper, wrapped,
assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
for attr in assigned:
try:
value = getattr(wrapped, attr)
except AttributeError:
continue
else:
setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
wrapper.__wrapped__ = wrapped
return wrapper
_update_wrapper.__doc__ = functools.update_wrapper.__doc__
def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS,
updated=functools.WRAPPER_UPDATES):
return functools.partial(_update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
wraps.__doc__ = functools.wraps.__doc__
else:
wraps = functools.wraps
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(type):
def __new__(cls, name, this_bases, d):
if sys.version_info[:2] >= (3, 7):
# This version introduced PEP 560 that requires a bit
# of extra care (we mimic what is done by __build_class__).
resolved_bases = types.resolve_bases(bases)
if resolved_bases is not bases:
d['__orig_bases__'] = bases
else:
resolved_bases = bases
return meta(name, resolved_bases, d)
@classmethod
def __prepare__(cls, name, this_bases):
return meta.__prepare__(name, bases)
return type.__new__(metaclass, 'temporary_class', (), {})
def add_metaclass(metaclass):
"""Class decorator for creating a class with a metaclass."""
def wrapper(cls):
orig_vars = cls.__dict__.copy()
slots = orig_vars.get('__slots__')
if slots is not None:
if isinstance(slots, str):
slots = [slots]
for slots_var in slots:
orig_vars.pop(slots_var)
orig_vars.pop('__dict__', None)
orig_vars.pop('__weakref__', None)
if hasattr(cls, '__qualname__'):
orig_vars['__qualname__'] = cls.__qualname__
return metaclass(cls.__name__, cls.__bases__, orig_vars)
return wrapper
def ensure_binary(s, encoding='utf-8', errors='strict'):
"""Coerce **s** to six.binary_type.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> encoded to `bytes`
- `bytes` -> `bytes`
"""
if isinstance(s, binary_type):
return s
if isinstance(s, text_type):
return s.encode(encoding, errors)
raise TypeError("not expecting type '%s'" % type(s))
def ensure_str(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to `str`.
For Python 2:
- `unicode` -> encoded to `str`
- `str` -> `str`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
# Optimization: Fast return for the common case.
if type(s) is str:
return s
if PY2 and isinstance(s, text_type):
return s.encode(encoding, errors)
elif PY3 and isinstance(s, binary_type):
return s.decode(encoding, errors)
elif not isinstance(s, (text_type, binary_type)):
raise TypeError("not expecting type '%s'" % type(s))
return s
def ensure_text(s, encoding='utf-8', errors='strict'):
"""Coerce *s* to six.text_type.
For Python 2:
- `unicode` -> `unicode`
- `str` -> `unicode`
For Python 3:
- `str` -> `str`
- `bytes` -> decoded to `str`
"""
if isinstance(s, binary_type):
return s.decode(encoding, errors)
elif isinstance(s, text_type):
return s
else:
raise TypeError("not expecting type '%s'" % type(s))
def python_2_unicode_compatible(klass):
"""
A class decorator that defines __unicode__ and __str__ methods under Python 2.
Under Python 3 it does nothing.
To support Python 2 and 3 with a single code base, define a __str__ method
returning text and apply this decorator to the class.
"""
if PY2:
if '__str__' not in klass.__dict__:
raise ValueError("@python_2_unicode_compatible cannot be applied "
"to %s because it doesn't define __str__()." %
klass.__name__)
klass.__unicode__ = klass.__str__
klass.__str__ = lambda self: self.__unicode__().encode('utf-8')
return klass
# Complete the moves implementation.
# This code is at the end of this module to speed up module loading.
# Turn this module into a package.
__path__ = [] # required for PEP 302 and PEP 451
__package__ = __name__ # see PEP 366 @ReservedAssignment
if globals().get("__spec__") is not None:
__spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable
# Remove other six meta path importers, since they cause problems. This can
# happen if six is removed from sys.modules and then reloaded. (Setuptools does
# this for some reason.)
if sys.meta_path:
for i, importer in enumerate(sys.meta_path):
# Here's some real nastiness: Another "instance" of the six module might
# be floating around. Therefore, we can't use isinstance() to check for
# the six meta path importer, since the other six instance will have
# inserted an importer with different class.
if (type(importer).__name__ == "_SixMetaPathImporter" and
importer.name == __name__):
del sys.meta_path[i]
break
del i, importer
# Finally, add the importer to the meta path import hook.
sys.meta_path.append(_importer)
Binary file not shown.
+1 -1
View File
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 010a10c5bc670583cef4151858e38839
config: eda3a1317314f558917722e3479f8836
tags: 645f666f9bcd5a90fca523b33c5a78b7
Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

+23
View File
@@ -68,6 +68,29 @@ destination, and passing traffic back and forth over the link.
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Link.py>`_.
.. _example-identify:
Identification
==============
The *Identify* example explores identifying an intiator of a link, once
the link has been established.
.. literalinclude:: ../../Examples/Identify.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Identify.py>`_.
.. _example-request:
Requests & Responses
====================
The *Request* example explores sendig requests and receiving responses.
.. literalinclude:: ../../Examples/Request.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py>`_.
.. _example-filetransfer:
Filetransfer
@@ -1,7 +1,6 @@
********************
Getting Started Fast
********************
What do we want to do? Something! When do we want to do it? Right now! Let's go.
The best way to get started with the Reticulum Network Stack depends on what
you want to do. This guide will outline sensible starting paths for different
@@ -23,7 +22,7 @@ in the development for the messaging and information-sharing protocol
Develop a Program with Reticulum
===========================================
If you want to develop programs that use Reticulum, the easiest way to get
started is to install Reticulum via pip:
started is to install the latest release of Reticulum via pip:
.. code::
+2 -2
View File
@@ -10,9 +10,9 @@ the development of Reticulum itself.
whatis
gettingstartedfast
examples
reference
understanding
reference
examples
Indices and Tables
+12 -4
View File
@@ -39,7 +39,7 @@ Destination
Packet
------
.. autoclass:: RNS.Packet
.. autoclass:: RNS.Packet(destination, data, create_receipt = True)
:members:
.. _api-packetreceipt:
@@ -47,7 +47,7 @@ Packet
Packet Receipt
--------------
.. autoclass:: RNS.PacketReceipt
.. autoclass:: RNS.PacketReceipt()
:members:
.. _api-link:
@@ -55,7 +55,15 @@ Packet Receipt
Link
----
.. autoclass:: RNS.Link
.. autoclass:: RNS.Link(destination, established_callback=None, closed_callback = None)
:members:
.. _api-requestreceipt:
Request Receipt
---------------
.. autoclass:: RNS.RequestReceipt()
:members:
.. _api-resource:
@@ -63,7 +71,7 @@ Link
Resource
--------
.. autoclass:: RNS.Resource
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
:members:
.. _api-transport:
+141 -94
View File
@@ -157,12 +157,16 @@ destinations. Reticulum uses three different basic destination types, and one sp
Destination Naming
^^^^^^^^^^^^^^^^^^
Destinations are created and named in an easy to understand dotted notation of *aspects* , and
Destinations are created and named in an easy to understand dotted notation of *aspects*, and
represented on the network as a hash of this value. The hash is a SHA-256 truncated to 80 bits. The
top level aspect should always be a unique identifier for the application using the destination.
The next levels of aspects can be defined in any way by the creator of the application. For example,
a destination for a environmental monitoring application could be made up of the application name, a
device type and measurement type, like this:
The next levels of aspects can be defined in any way by the creator of the application.
Aspects can be as long and as plentiful as required, and a resulting long destination name will not
impact efficiency, as names are always represented as truncated SHA-256 hashes on the network.
As an example, a destination for a environmental monitoring application could be made up of the
application name, a device type and measurement type, like this:
.. code-block:: text
@@ -201,9 +205,8 @@ To recap, the different destination types should be used in the following situat
* **Single**
When private communication between two endpoints is needed. Supports multiple hops.
* **Group**
When private communication between two or more endpoints is needed. More efficient in
data usage than *single* destinations. Supports multiple hops indirectly, but must first be
established through a *single* destination.
When private communication between two or more endpoints is needed. Supports multiple hops
indirectly, but must first be established through a *single* destination.
* **Plain**
When plain-text communication is desirable, for example when broadcasting information.
@@ -214,9 +217,9 @@ an unknown public key from the network, as all participating nodes serve as a di
of public keys.
Note that public key information can be shared and verified in many other ways than using the
built-in methodology, and that it is therefore not required to use the announce/request functionality.
It is by far the easiest though, and should definitely be used if there is not a good reason for
doing it differently.
built-in *announce* functionality, and that it is therefore not required to use the announce/request
functionality to obtain public keys. It is by far the easiest though, and should definitely be used
if there is not a good reason for doing it differently.
.. _understanding-keyannouncements:
@@ -235,7 +238,7 @@ contain the following information:
* The announcers public key
* Application specific data, in this case the users nickname and availability status
* A random blob, making each new announce unique
* A signature of the above information, verifying authenticity
* An Ed25519 signature of the above information, verifying authenticity
With this information, any Reticulum node that receives it will be able to reconstruct an outgoing
destination to securely communicate with that destination. You might have noticed that there is one
@@ -244,8 +247,9 @@ the aspect names of the destination. These are intentionally left out to save ba
will be implicit in almost all cases. If a destination name is not entirely implicit, information can be
included in the application specific data part that will allow the receiver to infer the naming.
It is important to note that announcements will be forwarded throughout the network according to a
certain pattern. This will be detailed later.
It is important to note that announces will be forwarded throughout the network according to a
certain pattern. This will be detailed in the section
:ref:`The Announce Mechanism in Detail<understanding-announce>`.
Seeing how *single* destinations are always tied to a private/public key pair leads us to the next topic.
@@ -268,8 +272,8 @@ the identity first, and then link it to created destinations.
Building upon the simple messenger example, we could use an identity to represent the user of the
application. Destinations created will then be linked to this identity to allow communication to
reach the user. In such a case it is of great importance to store the users identity securely and
privately.
reach the user. In all cases it is of great importance to store the private keys associated with any
Reticulum Identity securely and privately.
.. _understanding-gettingfurther:
@@ -279,8 +283,9 @@ Getting Further
The above functions and principles form the core of Reticulum, and would suffice to create
functional networked applications in local clusters, for example over radio links where all interested
nodes can directly hear each other. But to be truly useful, we need a way to direct traffic over multiple
hops in the network. In the next sections, two concepts that allow this will be introduced, *paths* and
*links*.
hops in the network.
In the following sections, two concepts that allow this will be introduced, *paths* and *links*.
.. _understanding-transport:
@@ -298,70 +303,28 @@ useable over bandwidth-limited, high-latency links.
To overcome such challenges, Reticulums *Transport* system uses public-key cryptography to
implement the concept of *paths* that allow discovery of how to get information to a certain
destination, and *resources* that help make reliable data transfer more efficient.
destination. It is important to note that no single node in a Reticulum network knows the complete
path to a destination. Every Transport node participating in a Reticulum network will only
know what the most direct way to get a packet one hop closer to it's destination is.
.. _understanding-paths:
.. _understanding-announce:
Reaching the Destination
------------------------
The Announce Mechanism in Detail
--------------------------------
In networks with changing topology and trustless connectivity, nodes need a way to establish
*verified connectivity* with each other. Since the network is assumed to be trustless, Reticulum
must provide a way to guarantee that the peer you are communicating with is actually who you
expect. To do this, the following process is employed:
When an *announce* is transmitted by a node, it will be forwarded by any node receiving it, but
according to some specific rules:
* | First, the node that wishes to establish connectivity will send out a special packet, that
traverses the network and locates the desired destination. Along the way, the nodes that
forward the packet will take note of this *link request*.
* | If this exact announce has already been received before, ignore it.
* | Second, if the destination accepts the *link request* , it will send back a packet that proves the
authenticity of its identity (and the receipt of the link request) to the initiating node. All
nodes that initially forwarded the packet will also be able to verify this proof, and thus
accept the validity of the *link* throughout the network.
* | When the validity of the *link* has been accepted by forwarding nodes, these nodes will
remember the *link* , and it can subsequently be used by referring to a hash representing it.
* | As a part of the *link request* , a Diffie-Hellman key exchange takes place, that sets up an
efficient symmetrically encrypted tunnel between the two nodes, using elliptic curve
cryptography. As such, this mode of communication is preferred, even for situations when
nodes can directly communicate, when the amount of data to be exchanged numbers in the
tens of packets.
* | When a *link* has been set up, it automatically provides message receipt functionality, so the
sending node can obtain verified confirmation that the information reached the intended
recipient.
In a moment, we will discuss the specifics of how this methodology is implemented, but lets first
recap what purposes this serves. We first ensure that the node answering our request is actually the
one we want to communicate with, and not a malicious actor pretending to be so. At the same time
we establish an efficient encrypted channel. The setup of this is relatively cheap in terms of
bandwidth, so it can be used just for a short exchange, and then recreated as needed, which will also
rotate encryption keys, but the link can also be kept alive for longer periods of time, if this is
more suitable to the application. The amount of bandwidth used on keeping a link open is practically
negligible. The procedure also inserts the *link id* , a hash calculated from the link request packet,
into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each
other simply by referring to this *link id*.
Step 1: Pathfinding
^^^^^^^^^^^^^^^^^^^
The pathfinding method builds on the *announce* functionality discussed earlier. When an announce
is sent out by a node, it will be forwarded by any node receiving it, but according to some specific
rules:
* | If this announce has already been received before, ignore it.
* | Record into a table which node the announce was received from, and how many times in
* | If not, record into a table which node the announce was received from, and how many times in
total it has been retransmitted to get here.
* | If the announce has been retransmitted *m+1* times, it will not be forwarded. By default, *m* is
set to 18.
* | The announce will be assigned a delay *d* = c\ :sup:`h` seconds, where *c* is a decay constant, by
default 2, and *h* is the amount of times this packet has already been forwarded.
* | The announce will be assigned a delay *d* = c\ :sup:`h` seconds, where *c* is a decay constant, and *h* is the amount of times this packet has already been forwarded.
* | The packet will be given a priority *p = 1/d*.
@@ -370,10 +333,11 @@ rules:
not utilized by other traffic, the announce will be forwarded.
* | If no other nodes are heard retransmitting the announce with a greater hop count than when
it left this node, transmitting it will be retried *r* times. By default, *r* is set to 2. Retries follow
same rules as above, with the exception that it must wait for at least *d* = c\ :sup:`h+1` + t seconds, ie.,
the amount of time it would take the next node to retransmit the packet. By default, *t* is set to
10.
it left this node, transmitting it will be retried *r* times. By default, *r* is set to 1. Retries
follow same rules as above, with the exception that it must wait for at least *d* = c\ :sup:`h+1` +
t + rand(0, rw) seconds. This amount of time is equal to the amount of time it would take the next
node to retransmit the packet, plus a random window. By default, *t* is set to 10 seconds, and the
random window *rw* is set to 10 seconds.
* | If a newer announce from the same destination arrives, while an identical one is already in
the queue, the newest announce is discarded. If the newest announce contains different
@@ -392,14 +356,95 @@ distance of *Lavg =* 15 kilometers, an announce will be able to propagate outwar
kilometers in 34 minutes, and a *maximum announce radius* of 270 kilometers in approximately 3
days.
Step 2: Link Establishment
^^^^^^^^^^^^^^^^^^^^^^^^^^
.. _understanding-paths:
After seeing how the conditions for finding a path through the network are created, we will now
explore how two nodes can establish reliable communications over multiple hops. The *link* in
Reticulum terminology should not be viewed as a direct node-to-node link on the physical layer, but
as an abstract channel, that can be open for any amount of time, and can span an arbitrary number
of hops, where information will be exchanged between two nodes.
Reaching the Destination
------------------------
In networks with changing topology and trustless connectivity, nodes need a way to establish
*verified connectivity* with each other. Since the network is assumed to be trustless, Reticulum
must provide a way to guarantee that the peer you are communicating with is actually who you
expect. Reticulum offers two ways to do this.
For exchanges of small amounts of information, Reticulum offers the *Packet* API, which works exactly like you would expect - on a per packet level. The following process is employed when sending a packet:
* | A packet is always created with an associated destination and some payload data. When the packet is sent
to a *single* destination type, Reticulum will automatically create an ephemeral encryption key, perform
an ECDH key exchange with the destinations public key, and encrypt the information.
* | It is important to note that this key exchange does not require any network traffic. The sender already
knows the public key of the destination from an earlier received *announce*, and can thus perform the ECDH
key exchange locally, before sending the packet.
* | The public part of the newly generated ephemeral key-pair is included with the encrypted token, and sent
along with the encrypted payload data in the packet.
* | When the destination receives the packet, it can itself perform an ECDH key exchange and decrypt the
packet.
* | A new ephemeral key is used for every packet sent in this way, and forward secrecy is guaranteed on a
per packet level.
* | Once the packet has been received and decrypted by the addressed destination, that destination can opt
to *prove* its receipt of the packet. It does this by calculating the SHA-256 hash of the received packet,
and signing this hash with it's Ed25519 signing key. Transport nodes in the network can then direct this
*proof* back to the packets origin, where the signature can be verified against the destinations known
public signing key.
* | In case the packet is addressed to a *group* destination type, the packet will be encrypted with the
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a *plain*
destination type, the payload data will not be encrypted. Neither of these two destination types offer
forward secrecy. In general, it is recommended to always use the *single* destination type, unless it is
strictly necessary to use one of the others.
For exchanges of larger amounts of data, or when longer sessions of bidirectional communication is desired, Reticulum offers the *Link* API. To establish a *link*, the following process is employed:
* | First, the node that wishes to establish a link will send out a special packet, that
traverses the network and locates the desired destination. Along the way, the nodes that
forward the packet will take note of this *link request*.
* | Second, if the destination accepts the *link request* , it will send back a packet that proves the
authenticity of its identity (and the receipt of the link request) to the initiating node. All
nodes that initially forwarded the packet will also be able to verify this proof, and thus
accept the validity of the *link* throughout the network.
* | When the validity of the *link* has been accepted by forwarding nodes, these nodes will
remember the *link* , and it can subsequently be used by referring to a hash representing it.
* | As a part of the *link request* , a Diffie-Hellman key exchange takes place, that sets up an
efficiently encrypted tunnel between the two nodes, using elliptic curve cryptography. As such,
this mode of communication is preferred, even for situations when nodes can directly communicate,
when the amount of data to be exchanged numbers in the tens of packets.
* | When a *link* has been set up, it automatically provides message receipt functionality, through
the same *proof* mechanism discussed before, so the sending node can obtain verified confirmation
that the information reached the intended recipient.
In a moment, we will discuss the details of how this methodology is implemented, but lets first
recap what purposes this methodology serves. We first ensure that the node answering our request
is actually the one we want to communicate with, and not a malicious actor pretending to be so.
At the same time we establish an efficient encrypted channel. The setup of this is relatively cheap in
terms of bandwidth, so it can be used just for a short exchange, and then recreated as needed, which will
also rotate encryption keys. The link can also be kept alive for longer periods of time, if this is
more suitable to the application. The procedure also inserts the *link id* , a hash calculated from the link request packet, into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each other simply by referring to this *link id*.
The combined bandwidth cost of setting up a link is 3 packets totalling 237 bytes (more info in the
:ref:`Binary Packet Format<understanding-packetformat>` section). The amount of bandwidth used on keeping
a link open is practically negligible, at 0.62 bits per second. Even on a slow 1200 bits per second packet
radio channel, 100 concurrent links will still leave 95% channel capacity for actual data.
Link Establishment in Detail
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
After exploring the basics of the announce mechanism, finding a path through the network, and an overview
of the link establishment procedure, this section will go into greater detail about the Reticulum link
establishment process.
The *link* in Reticulum terminology should not be viewed as a direct node-to-node link on the
physical layer, but as an abstract channel, that can be open for any amount of time, and can span
an arbitrary number of hops, where information will be exchanged between two nodes.
* | When a node in the network wants to establish verified connectivity with another node, it
@@ -412,25 +457,25 @@ of hops, where information will be exchanged between two nodes.
considered as single public key for simplicity in this explanation.*
* | The *link request* is addressed to the destination hash of the desired destination, and
contains the following data: The newly generated X25519 public key *LKi*. The contents
are encrypted with the RSA public key of the destination and tramsitted over the network.
contains the following data: The newly generated X25519 public key *LKi*.
* | The broadcasted packet will be directed through the network according to the rules laid out
previously.
* | Any node that forwards the link request will store a *link id* in its *link table* , along with the
amount of hops the packet had taken when received. The link id is a hash of the entire link
request packet. If the path is not *proven* within some set amount of time, the entry will be
dropped from the *link table* again.
request packet. If the link request packet is not *proven* by the addressed destination within some
set amount of time, the entry will be dropped from the *link table* again.
* | When the destination receives the link request packet, it will decrypt it and decide whether to
accept the request. If it is accepted, the destination will also generate a new X25519 private/public
key pair, and perform a Diffie Hellman Key Exchange, deriving a new symmetric key that will be used
to encrypt the channel, once it has been established.
* | When the destination receives the link request packet, it will decide whether to accept the request.
If it is accepted, the destination will also generate a new X25519 private/public key pair, and
perform a Diffie Hellman Key Exchange, deriving a new symmetric key that will be used to encrypt the
channel, once it has been established.
* | A *link proof* packet is now constructed and transmitted over the network. This packet is
addressed to the *link id* of the *link*. It contains the following data: The newly generated X25519
public key *LKr* and an RSA-1024 signature of the *link id* and *LKr*.
public key *LKr* and an Ed25519 signature of the *link id* and *LKr* made by the signing key of
the addressed destination.
* | By verifying this *link proof* packet, all nodes that originally transported the *link request*
packet to the destination from the originator can now verify that the intended destination received
@@ -556,6 +601,8 @@ the light of Reticulums goal of equal access, doing so would need to be the subj
investigation of the consequences first.
.. _understanding-packetformat:
Binary Packet Format
--------------------
@@ -651,8 +698,8 @@ Binary Packet Format
wire size including all fields.
- Path Request : 33 bytes
- Announce : 323 bytes
- Link Request : 141 bytes
- Link Proof : 205 bytes
- Link RTT packet : 86 bytes
- Announce : 151 bytes
- Link Request : 77 bytes
- Link Proof : 77 bytes
- Link RTT packet : 83 bytes
- Link keepalive : 14 bytes
+16 -6
View File
@@ -2,7 +2,9 @@
What is Reticulum?
******************
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth.
Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
@@ -25,11 +27,11 @@ What does Reticulum Offer?
* Fully self-configuring multi-hop routing
* Asymmetric RSA encryption and signatures as basis for all communication
* Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
* Perfect Forward Secrecy on links with ephemereal Elliptic Curve Diffie-Hellman keys (on Curve25519)
* Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519
* Reticulum uses the Fernet specification for encryption on links and to group destinations
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for encryption
* AES-128 in CBC mode with PKCS7 padding
@@ -37,11 +39,13 @@ What does Reticulum Offer?
* IVs are generated through os.urandom()
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
* Unforgeable packet delivery confirmations
* A variety of supported interface types
* An intuitive and easy-to-use API
* An intuitive and developer-friendly API
* Reliable and efficient transfer of arbritrary amounts of data
@@ -51,10 +55,16 @@ What does Reticulum Offer?
* The API is very easy to use, and provides transfer progress
* Efficient link establishment
* Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes
* Low cost of keeping links open at only 0.62 bits per second
Where can Reticulum be Used?
============================
On practically any hardware that can support at least a half-duplex channel
Over practically any medium that can support at least a half-duplex channel
with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios,
modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes,
ad-hoc WiFi, free-space optical links and similar systems are all examples
+1 -1
View File
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '0.2.0 beta',
VERSION: '0.2.4 beta',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',
+654 -40
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Examples &#8212; Reticulum Network Stack 0.2.0 beta documentation</title>
<title>Examples &#8212; Reticulum Network Stack 0.2.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,8 +16,7 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="API Reference" href="reference.html" />
<link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
<link rel="prev" title="API Reference" href="reference.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
@@ -27,11 +26,8 @@
accesskey="I">index</a></li>
<li class="right" >
<a href="reference.html" title="API Reference"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Examples</a></li>
</ul>
</div>
@@ -380,7 +376,7 @@ over the network.</p>
<span class="c1"># We specify a callback that will get called every time</span>
<span class="c1"># the destination receives data.</span>
<span class="n">broadcast_destination</span><span class="o">.</span><span class="n">packet_callback</span><span class="p">(</span><span class="n">packet_callback</span><span class="p">)</span>
<span class="n">broadcast_destination</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">packet_callback</span><span class="p">)</span>
<span class="c1"># Everything&#39;s ready!</span>
<span class="c1"># Let&#39;s hand over control to the main loop</span>
@@ -522,7 +518,7 @@ the Packet interface.</p>
<span class="c1"># Tell the destination which function in our program to</span>
<span class="c1"># run when a packet is received. We do this so we can</span>
<span class="c1"># print a log message when the server receives a request</span>
<span class="n">echo_destination</span><span class="o">.</span><span class="n">packet_callback</span><span class="p">(</span><span class="n">server_callback</span><span class="p">)</span>
<span class="n">echo_destination</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">server_callback</span><span class="p">)</span>
<span class="c1"># Everything&#39;s ready!</span>
<span class="c1"># Let&#39;s Wait for client requests or user input</span>
@@ -640,12 +636,12 @@ the Packet interface.</p>
<span class="c1"># the packet times out.</span>
<span class="k">if</span> <span class="n">timeout</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">packet_receipt</span><span class="o">.</span><span class="n">set_timeout</span><span class="p">(</span><span class="n">timeout</span><span class="p">)</span>
<span class="n">packet_receipt</span><span class="o">.</span><span class="n">timeout_callback</span><span class="p">(</span><span class="n">packet_timed_out</span><span class="p">)</span>
<span class="n">packet_receipt</span><span class="o">.</span><span class="n">set_timeout_callback</span><span class="p">(</span><span class="n">packet_timed_out</span><span class="p">)</span>
<span class="c1"># We can then set a delivery callback on the receipt.</span>
<span class="c1"># This will get automatically called when a proof for</span>
<span class="c1"># this specific packet is received from the destination.</span>
<span class="n">packet_receipt</span><span class="o">.</span><span class="n">delivery_callback</span><span class="p">(</span><span class="n">packet_delivered</span><span class="p">)</span>
<span class="n">packet_receipt</span><span class="o">.</span><span class="n">set_delivery_callback</span><span class="p">(</span><span class="n">packet_delivered</span><span class="p">)</span>
<span class="c1"># Tell the user that the echo request was sent</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Sent echo request to &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
@@ -659,7 +655,7 @@ the Packet interface.</p>
<span class="c1"># receives a proof packet.</span>
<span class="k">def</span> <span class="nf">packet_delivered</span><span class="p">(</span><span class="n">receipt</span><span class="p">):</span>
<span class="k">if</span> <span class="n">receipt</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">PacketReceipt</span><span class="o">.</span><span class="n">DELIVERED</span><span class="p">:</span>
<span class="n">rtt</span> <span class="o">=</span> <span class="n">receipt</span><span class="o">.</span><span class="n">rtt</span><span class="p">()</span>
<span class="n">rtt</span> <span class="o">=</span> <span class="n">receipt</span><span class="o">.</span><span class="n">get_rtt</span><span class="p">()</span>
<span class="k">if</span> <span class="p">(</span><span class="n">rtt</span> <span class="o">&gt;=</span> <span class="mi">1</span><span class="p">):</span>
<span class="n">rtt</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">rtt</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>
<span class="n">rttstring</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">rtt</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; seconds&quot;</span>
@@ -803,7 +799,7 @@ destination, and passing traffic back and forth over the link.</p>
<span class="c1"># We configure a function that will get called every time</span>
<span class="c1"># a new client creates a link to this destination.</span>
<span class="n">server_destination</span><span class="o">.</span><span class="n">link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
<span class="c1"># Everything&#39;s ready!</span>
<span class="c1"># Let&#39;s Wait for client requests or user input</span>
@@ -835,8 +831,8 @@ destination, and passing traffic back and forth over the link.</p>
<span class="k">global</span> <span class="n">latest_client_link</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client connected&quot;</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">packet_callback</span><span class="p">(</span><span class="n">server_packet_received</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">server_packet_received</span><span class="p">)</span>
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="n">link</span>
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
@@ -908,12 +904,12 @@ destination, and passing traffic back and forth over the link.</p>
<span class="c1"># We set a callback that will get executed</span>
<span class="c1"># every time a packet is received over the</span>
<span class="c1"># link</span>
<span class="n">link</span><span class="o">.</span><span class="n">packet_callback</span><span class="p">(</span><span class="n">client_packet_received</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">client_packet_received</span><span class="p">)</span>
<span class="c1"># We&#39;ll also set up functions to inform the</span>
<span class="c1"># user when the link is established or closed</span>
<span class="n">link</span><span class="o">.</span><span class="n">link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
<span class="c1"># Everything is set up, so let&#39;s enter a loop</span>
<span class="c1"># for the user to interact with the example</span>
@@ -940,8 +936,18 @@ destination, and passing traffic back and forth over the link.</p>
<span class="c1"># If not, send the entered text over the link</span>
<span class="k">if</span> <span class="n">text</span> <span class="o">!=</span> <span class="s2">&quot;&quot;</span><span class="p">:</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Packet</span><span class="p">(</span><span class="n">server_link</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span><span class="o">.</span><span class="n">send</span><span class="p">()</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">MDU</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Packet</span><span class="p">(</span><span class="n">server_link</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span><span class="o">.</span><span class="n">send</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Cannot send this packet, the data size of &quot;</span><span class="o">+</span>
<span class="nb">str</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">))</span><span class="o">+</span><span class="s2">&quot; bytes exceeds the link packet MDU of &quot;</span><span class="o">+</span>
<span class="nb">str</span><span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">MDU</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; bytes&quot;</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span>
<span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Error while sending data over the link: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
@@ -1038,6 +1044,614 @@ destination, and passing traffic back and forth over the link.</p>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Link.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Link.py</a>.</p>
</div>
<div class="section" id="example-identify">
<span id="identification"></span><h2>Identification<a class="headerlink" href="#example-identify" title="Permalink to this headline"></a></h2>
<p>The <em>Identify</em> example explores identifying an intiator of a link, once
the link has been established.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1">##########################################################</span>
<span class="c1"># This RNS example demonstrates how to set up a link to #</span>
<span class="c1"># a destination, and identify the initiator to it&#39;s peer #</span>
<span class="c1">##########################################################</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">RNS</span>
<span class="c1"># Let&#39;s define an app name. We&#39;ll use this for all</span>
<span class="c1"># destinations we create. Since this echo example</span>
<span class="c1"># is part of a range of example utilities, we&#39;ll put</span>
<span class="c1"># them all within the app namespace &quot;example_utilities&quot;</span>
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">&quot;example_utilities&quot;</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Server Part #########################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># A reference to the latest client link that connected</span>
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># This initialisation is executed when the users chooses</span>
<span class="c1"># to run as a server</span>
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Randomly create a new identity for our link example</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="c1"># We create a destination that clients can connect to. We</span>
<span class="c1"># want clients to create links to this destination, so we</span>
<span class="c1"># need to create a &quot;single&quot; destination type.</span>
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
<span class="n">server_identity</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">IN</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
<span class="n">APP_NAME</span><span class="p">,</span>
<span class="s2">&quot;identifyexample&quot;</span>
<span class="p">)</span>
<span class="c1"># We configure a function that will get called every time</span>
<span class="c1"># a new client creates a link to this destination.</span>
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
<span class="c1"># Everything&#39;s ready!</span>
<span class="c1"># Let&#39;s Wait for client requests or user input</span>
<span class="n">server_loop</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">server_loop</span><span class="p">(</span><span class="n">destination</span><span class="p">):</span>
<span class="c1"># Let the user know that everything is ready</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Link identification example &quot;</span><span class="o">+</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
<span class="s2">&quot; running, waiting for a connection.&quot;</span>
<span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Hit enter to manually send an announce (Ctrl-C to quit)&quot;</span><span class="p">)</span>
<span class="c1"># We enter a loop that runs until the users exits.</span>
<span class="c1"># If the user hits enter, we will announce our server</span>
<span class="c1"># destination on the network, which will let clients</span>
<span class="c1"># know how to create messages directed towards it.</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Sent announce from &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
<span class="c1"># When a client establishes a link to our server</span>
<span class="c1"># destination, this function will be called with</span>
<span class="c1"># a reference to the link.</span>
<span class="k">def</span> <span class="nf">client_connected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="k">global</span> <span class="n">latest_client_link</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client connected&quot;</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">server_packet_received</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_remote_identified_callback</span><span class="p">(</span><span class="n">remote_identified</span><span class="p">)</span>
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="n">link</span>
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client disconnected&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">remote_identified</span><span class="p">(</span><span class="n">identity</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Remote identified as: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">identity</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">server_packet_received</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">packet</span><span class="p">):</span>
<span class="k">global</span> <span class="n">latest_client_link</span>
<span class="c1"># Get the originating identity for display</span>
<span class="n">remote_peer</span> <span class="o">=</span> <span class="s2">&quot;unidentified peer&quot;</span>
<span class="k">if</span> <span class="n">packet</span><span class="o">.</span><span class="n">link</span><span class="o">.</span><span class="n">get_remote_identity</span><span class="p">()</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
<span class="n">remote_peer</span> <span class="o">=</span> <span class="nb">str</span><span class="p">(</span><span class="n">packet</span><span class="o">.</span><span class="n">link</span><span class="o">.</span><span class="n">get_remote_identity</span><span class="p">())</span>
<span class="c1"># When data is received over any active link,</span>
<span class="c1"># it will all be directed to the last client</span>
<span class="c1"># that connected.</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Received data from &quot;</span><span class="o">+</span><span class="n">remote_peer</span><span class="o">+</span><span class="s2">&quot;: &quot;</span><span class="o">+</span><span class="n">text</span><span class="p">)</span>
<span class="n">reply_text</span> <span class="o">=</span> <span class="s2">&quot;I received </span><span class="se">\&quot;</span><span class="s2">&quot;</span><span class="o">+</span><span class="n">text</span><span class="o">+</span><span class="s2">&quot;</span><span class="se">\&quot;</span><span class="s2"> over the link from &quot;</span><span class="o">+</span><span class="n">remote_peer</span>
<span class="n">reply_data</span> <span class="o">=</span> <span class="n">reply_text</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Packet</span><span class="p">(</span><span class="n">latest_client_link</span><span class="p">,</span> <span class="n">reply_data</span><span class="p">)</span><span class="o">.</span><span class="n">send</span><span class="p">()</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Client Part #########################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># A reference to the server link</span>
<span class="n">server_link</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># A reference to the client identity</span>
<span class="n">client_identity</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># This initialisation is executed when the users chooses</span>
<span class="c1"># to run as a client</span>
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">):</span>
<span class="k">global</span> <span class="n">client_identity</span>
<span class="c1"># We need a binary representation of the destination</span>
<span class="c1"># hash that was entered on the command line</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">20</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;Destination length is invalid, must be 20 hexadecimal characters (10 bytes)&quot;</span><span class="p">)</span>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Create a new client identity</span>
<span class="n">client_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Client created new identity &quot;</span><span class="o">+</span>
<span class="nb">str</span><span class="p">(</span><span class="n">client_identity</span><span class="p">)</span>
<span class="p">)</span>
<span class="c1"># Check if we know a path to the destination</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Destination is not yet known. Requesting path and waiting for announce to arrive...&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="c1"># Recall the server identity</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">recall</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
<span class="c1"># Inform the user that we&#39;ll begin connecting</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Establishing link with server...&quot;</span><span class="p">)</span>
<span class="c1"># When the server identity is known, we set</span>
<span class="c1"># up a destination</span>
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
<span class="n">server_identity</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">OUT</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
<span class="n">APP_NAME</span><span class="p">,</span>
<span class="s2">&quot;identifyexample&quot;</span>
<span class="p">)</span>
<span class="c1"># And create a link</span>
<span class="n">link</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
<span class="c1"># We set a callback that will get executed</span>
<span class="c1"># every time a packet is received over the</span>
<span class="c1"># link</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">client_packet_received</span><span class="p">)</span>
<span class="c1"># We&#39;ll also set up functions to inform the</span>
<span class="c1"># user when the link is established or closed</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
<span class="c1"># Everything is set up, so let&#39;s enter a loop</span>
<span class="c1"># for the user to interact with the example</span>
<span class="n">client_loop</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">client_loop</span><span class="p">():</span>
<span class="k">global</span> <span class="n">server_link</span>
<span class="c1"># Wait for the link to become active</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">server_link</span><span class="p">:</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">should_quit</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&gt; &quot;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&quot; &quot;</span><span class="p">)</span>
<span class="n">text</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
<span class="c1"># Check if we should quit the example</span>
<span class="k">if</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;quit&quot;</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;q&quot;</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;exit&quot;</span><span class="p">:</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
<span class="c1"># If not, send the entered text over the link</span>
<span class="k">if</span> <span class="n">text</span> <span class="o">!=</span> <span class="s2">&quot;&quot;</span><span class="p">:</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="o">&lt;=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">MDU</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Packet</span><span class="p">(</span><span class="n">server_link</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span><span class="o">.</span><span class="n">send</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Cannot send this packet, the data size of &quot;</span><span class="o">+</span>
<span class="nb">str</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">))</span><span class="o">+</span><span class="s2">&quot; bytes exceeds the link packet MDU of &quot;</span><span class="o">+</span>
<span class="nb">str</span><span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">MDU</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; bytes&quot;</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span>
<span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Error while sending data over the link: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
<span class="c1"># This function is called when a link</span>
<span class="c1"># has been established with the server</span>
<span class="k">def</span> <span class="nf">link_established</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="c1"># We store a reference to the link</span>
<span class="c1"># instance for later use</span>
<span class="k">global</span> <span class="n">server_link</span><span class="p">,</span> <span class="n">client_identity</span>
<span class="n">server_link</span> <span class="o">=</span> <span class="n">link</span>
<span class="c1"># Inform the user that the server is</span>
<span class="c1"># connected</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link established with server, identifying to remote peer...&quot;</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">identify</span><span class="p">(</span><span class="n">client_identity</span><span class="p">)</span>
<span class="c1"># When a link is closed, we&#39;ll inform the</span>
<span class="c1"># user, and exit the program</span>
<span class="k">def</span> <span class="nf">link_closed</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="k">if</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The link timed out, exiting now&quot;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">DESTINATION_CLOSED</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The link was closed by the server, exiting now&quot;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1"># When a packet is received over the link, we</span>
<span class="c1"># simply print out the data.</span>
<span class="k">def</span> <span class="nf">client_packet_received</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">packet</span><span class="p">):</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Received data on the link: &quot;</span><span class="o">+</span><span class="n">text</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&gt; &quot;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&quot; &quot;</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Program Startup #####################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># This part of the program runs at startup,</span>
<span class="c1"># and parses input of from the user, and then</span>
<span class="c1"># starts up the desired program mode.</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&quot;Simple link example&quot;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;-s&quot;</span><span class="p">,</span>
<span class="s2">&quot;--server&quot;</span><span class="p">,</span>
<span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;wait for incoming link requests from clients&quot;</span>
<span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;--config&quot;</span><span class="p">,</span>
<span class="n">action</span><span class="o">=</span><span class="s2">&quot;store&quot;</span><span class="p">,</span>
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;path to alternative Reticulum config directory&quot;</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
<span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;destination&quot;</span><span class="p">,</span>
<span class="n">nargs</span><span class="o">=</span><span class="s2">&quot;?&quot;</span><span class="p">,</span>
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;hexadecimal hash of the server destination&quot;</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
<span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span><span class="p">:</span>
<span class="n">configarg</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">configarg</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">server</span><span class="p">:</span>
<span class="n">server</span><span class="p">(</span><span class="n">configarg</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span> <span class="o">==</span> <span class="kc">None</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">print_help</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">client</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span><span class="p">,</span> <span class="n">configarg</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Identify.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Identify.py</a>.</p>
</div>
<div class="section" id="requests-responses">
<span id="example-request"></span><h2>Requests &amp; Responses<a class="headerlink" href="#requests-responses" title="Permalink to this headline"></a></h2>
<p>The <em>Request</em> example explores sendig requests and receiving responses.</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1">##########################################################</span>
<span class="c1"># This RNS example demonstrates how to set perform #</span>
<span class="c1"># requests and receive responses over a link. #</span>
<span class="c1">##########################################################</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">random</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">import</span> <span class="nn">RNS</span>
<span class="c1"># Let&#39;s define an app name. We&#39;ll use this for all</span>
<span class="c1"># destinations we create. Since this echo example</span>
<span class="c1"># is part of a range of example utilities, we&#39;ll put</span>
<span class="c1"># them all within the app namespace &quot;example_utilities&quot;</span>
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">&quot;example_utilities&quot;</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Server Part #########################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># A reference to the latest client link that connected</span>
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">def</span> <span class="nf">random_text_generator</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">request_id</span><span class="p">,</span> <span class="n">remote_identity</span><span class="p">,</span> <span class="n">requested_at</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Generating response to request &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_id</span><span class="p">))</span>
<span class="n">texts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">&quot;They looked up&quot;</span><span class="p">,</span> <span class="s2">&quot;On each full moon&quot;</span><span class="p">,</span> <span class="s2">&quot;Becky was upset&quot;</span><span class="p">,</span> <span class="s2">&quot;Ill stay away from it&quot;</span><span class="p">,</span> <span class="s2">&quot;The pet shop stocks everything&quot;</span><span class="p">]</span>
<span class="k">return</span> <span class="n">texts</span><span class="p">[</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">texts</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">)]</span>
<span class="c1"># This initialisation is executed when the users chooses</span>
<span class="c1"># to run as a server</span>
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Randomly create a new identity for our link example</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
<span class="c1"># We create a destination that clients can connect to. We</span>
<span class="c1"># want clients to create links to this destination, so we</span>
<span class="c1"># need to create a &quot;single&quot; destination type.</span>
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
<span class="n">server_identity</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">IN</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
<span class="n">APP_NAME</span><span class="p">,</span>
<span class="s2">&quot;requestexample&quot;</span>
<span class="p">)</span>
<span class="c1"># We configure a function that will get called every time</span>
<span class="c1"># a new client creates a link to this destination.</span>
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
<span class="c1"># We register a request handler for handling incoming</span>
<span class="c1"># requests over any established links.</span>
<span class="n">server_destination</span><span class="o">.</span><span class="n">register_request_handler</span><span class="p">(</span>
<span class="s2">&quot;/random/text&quot;</span><span class="p">,</span>
<span class="n">response_generator</span> <span class="o">=</span> <span class="n">random_text_generator</span><span class="p">,</span>
<span class="n">allow</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">ALLOW_ALL</span>
<span class="p">)</span>
<span class="c1"># Everything&#39;s ready!</span>
<span class="c1"># Let&#39;s Wait for client requests or user input</span>
<span class="n">server_loop</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">server_loop</span><span class="p">(</span><span class="n">destination</span><span class="p">):</span>
<span class="c1"># Let the user know that everything is ready</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
<span class="s2">&quot;Request example &quot;</span><span class="o">+</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
<span class="s2">&quot; running, waiting for a connection.&quot;</span>
<span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Hit enter to manually send an announce (Ctrl-C to quit)&quot;</span><span class="p">)</span>
<span class="c1"># We enter a loop that runs until the users exits.</span>
<span class="c1"># If the user hits enter, we will announce our server</span>
<span class="c1"># destination on the network, which will let clients</span>
<span class="c1"># know how to create messages directed towards it.</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Sent announce from &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
<span class="c1"># When a client establishes a link to our server</span>
<span class="c1"># destination, this function will be called with</span>
<span class="c1"># a reference to the link.</span>
<span class="k">def</span> <span class="nf">client_connected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="k">global</span> <span class="n">latest_client_link</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client connected&quot;</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="n">link</span>
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client disconnected&quot;</span><span class="p">)</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Client Part #########################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># A reference to the server link</span>
<span class="n">server_link</span> <span class="o">=</span> <span class="kc">None</span>
<span class="c1"># This initialisation is executed when the users chooses</span>
<span class="c1"># to run as a client</span>
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">):</span>
<span class="c1"># We need a binary representation of the destination</span>
<span class="c1"># hash that was entered on the command line</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">20</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">&quot;Destination length is invalid, must be 20 hexadecimal characters (10 bytes)&quot;</span><span class="p">)</span>
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
<span class="k">except</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
<span class="c1"># We must first initialise Reticulum</span>
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
<span class="c1"># Check if we know a path to the destination</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Destination is not yet known. Requesting path and waiting for announce to arrive...&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="c1"># Recall the server identity</span>
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">recall</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
<span class="c1"># Inform the user that we&#39;ll begin connecting</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Establishing link with server...&quot;</span><span class="p">)</span>
<span class="c1"># When the server identity is known, we set</span>
<span class="c1"># up a destination</span>
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
<span class="n">server_identity</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">OUT</span><span class="p">,</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
<span class="n">APP_NAME</span><span class="p">,</span>
<span class="s2">&quot;requestexample&quot;</span>
<span class="p">)</span>
<span class="c1"># And create a link</span>
<span class="n">link</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
<span class="c1"># We&#39;ll set up functions to inform the</span>
<span class="c1"># user when the link is established or closed</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
<span class="c1"># Everything is set up, so let&#39;s enter a loop</span>
<span class="c1"># for the user to interact with the example</span>
<span class="n">client_loop</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">client_loop</span><span class="p">():</span>
<span class="k">global</span> <span class="n">server_link</span>
<span class="c1"># Wait for the link to become active</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">server_link</span><span class="p">:</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">while</span> <span class="ow">not</span> <span class="n">should_quit</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&gt; &quot;</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">&quot; &quot;</span><span class="p">)</span>
<span class="n">text</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
<span class="c1"># Check if we should quit the example</span>
<span class="k">if</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;quit&quot;</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;q&quot;</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">&quot;exit&quot;</span><span class="p">:</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">server_link</span><span class="o">.</span><span class="n">request</span><span class="p">(</span>
<span class="s2">&quot;/random/text&quot;</span><span class="p">,</span>
<span class="n">data</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
<span class="n">response_callback</span> <span class="o">=</span> <span class="n">got_response</span><span class="p">,</span>
<span class="n">failed_callback</span> <span class="o">=</span> <span class="n">request_failed</span>
<span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Error while sending request over the link: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">got_response</span><span class="p">(</span><span class="n">request_receipt</span><span class="p">):</span>
<span class="n">request_id</span> <span class="o">=</span> <span class="n">request_receipt</span><span class="o">.</span><span class="n">request_id</span>
<span class="n">response</span> <span class="o">=</span> <span class="n">request_receipt</span><span class="o">.</span><span class="n">response</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Got response for request &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot;: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">response</span><span class="p">))</span>
<span class="k">def</span> <span class="nf">request_received</span><span class="p">(</span><span class="n">request_receipt</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The request &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_receipt</span><span class="o">.</span><span class="n">request_id</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; was received by the remote peer.&quot;</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">request_failed</span><span class="p">(</span><span class="n">request_receipt</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The request &quot;</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_receipt</span><span class="o">.</span><span class="n">request_id</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; failed.&quot;</span><span class="p">)</span>
<span class="c1"># This function is called when a link</span>
<span class="c1"># has been established with the server</span>
<span class="k">def</span> <span class="nf">link_established</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="c1"># We store a reference to the link</span>
<span class="c1"># instance for later use</span>
<span class="k">global</span> <span class="n">server_link</span>
<span class="n">server_link</span> <span class="o">=</span> <span class="n">link</span>
<span class="c1"># Inform the user that the server is</span>
<span class="c1"># connected</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link established with server, hit enter to perform a request, or type in </span><span class="se">\&quot;</span><span class="s2">quit</span><span class="se">\&quot;</span><span class="s2"> to quit&quot;</span><span class="p">)</span>
<span class="c1"># When a link is closed, we&#39;ll inform the</span>
<span class="c1"># user, and exit the program</span>
<span class="k">def</span> <span class="nf">link_closed</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
<span class="k">if</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The link timed out, exiting now&quot;</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">DESTINATION_CLOSED</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;The link was closed by the server, exiting now&quot;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Link closed, exiting now&quot;</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="c1">##########################################################</span>
<span class="c1">#### Program Startup #####################################</span>
<span class="c1">##########################################################</span>
<span class="c1"># This part of the program runs at startup,</span>
<span class="c1"># and parses input of from the user, and then</span>
<span class="c1"># starts up the desired program mode.</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">&quot;__main__&quot;</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">&quot;Simple request/response example&quot;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;-s&quot;</span><span class="p">,</span>
<span class="s2">&quot;--server&quot;</span><span class="p">,</span>
<span class="n">action</span><span class="o">=</span><span class="s2">&quot;store_true&quot;</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;wait for incoming requests from clients&quot;</span>
<span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;--config&quot;</span><span class="p">,</span>
<span class="n">action</span><span class="o">=</span><span class="s2">&quot;store&quot;</span><span class="p">,</span>
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;path to alternative Reticulum config directory&quot;</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
<span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
<span class="s2">&quot;destination&quot;</span><span class="p">,</span>
<span class="n">nargs</span><span class="o">=</span><span class="s2">&quot;?&quot;</span><span class="p">,</span>
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
<span class="n">help</span><span class="o">=</span><span class="s2">&quot;hexadecimal hash of the server destination&quot;</span><span class="p">,</span>
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
<span class="p">)</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span><span class="p">:</span>
<span class="n">configarg</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">configarg</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">server</span><span class="p">:</span>
<span class="n">server</span><span class="p">(</span><span class="n">configarg</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span> <span class="o">==</span> <span class="kc">None</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">print_help</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">client</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span><span class="p">,</span> <span class="n">configarg</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="n">exit</span><span class="p">()</span>
</pre></div>
</div>
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py</a>.</p>
</div>
<div class="section" id="filetransfer">
<span id="example-filetransfer"></span><h2>Filetransfer<a class="headerlink" href="#filetransfer" title="Permalink to this headline"></a></h2>
<p>The <em>Filetransfer</em> example implements a basic file-server program that
@@ -1110,7 +1724,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="c1"># We configure a function that will get called every time</span>
<span class="c1"># a new client creates a link to this destination.</span>
<span class="n">server_destination</span><span class="o">.</span><span class="n">link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
<span class="c1"># Everything&#39;s ready!</span>
<span class="c1"># Let&#39;s Wait for client requests or user input</span>
@@ -1147,7 +1761,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isdir</span><span class="p">(</span><span class="n">serve_path</span><span class="p">):</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client connected, sending file list...&quot;</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
<span class="c1"># We pack a list of files for sending in a packet</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">umsgpack</span><span class="o">.</span><span class="n">packb</span><span class="p">(</span><span class="n">list_files</span><span class="p">())</span>
@@ -1159,8 +1773,8 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="n">list_packet</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Packet</span><span class="p">(</span><span class="n">link</span><span class="p">,</span> <span class="n">data</span><span class="p">)</span>
<span class="n">list_receipt</span> <span class="o">=</span> <span class="n">list_packet</span><span class="o">.</span><span class="n">send</span><span class="p">()</span>
<span class="n">list_receipt</span><span class="o">.</span><span class="n">set_timeout</span><span class="p">(</span><span class="n">APP_TIMEOUT</span><span class="p">)</span>
<span class="n">list_receipt</span><span class="o">.</span><span class="n">delivery_callback</span><span class="p">(</span><span class="n">list_delivered</span><span class="p">)</span>
<span class="n">list_receipt</span><span class="o">.</span><span class="n">timeout_callback</span><span class="p">(</span><span class="n">list_timeout</span><span class="p">)</span>
<span class="n">list_receipt</span><span class="o">.</span><span class="n">set_delivery_callback</span><span class="p">(</span><span class="n">list_delivered</span><span class="p">)</span>
<span class="n">list_receipt</span><span class="o">.</span><span class="n">set_timeout_callback</span><span class="p">(</span><span class="n">list_timeout</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Too many files in served directory!&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;You should implement a function to split the filelist over multiple packets.&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
@@ -1170,7 +1784,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="c1"># open until the client requests a file. We&#39;ll</span>
<span class="c1"># configure a function that get&#39;s called when</span>
<span class="c1"># the client sends a packet with a file request.</span>
<span class="n">link</span><span class="o">.</span><span class="n">packet_callback</span><span class="p">(</span><span class="n">client_request</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">client_request</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">&quot;Client connected, but served path no longer exists!&quot;</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
@@ -1180,7 +1794,12 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="k">def</span> <span class="nf">client_request</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">packet</span><span class="p">):</span>
<span class="k">global</span> <span class="n">serve_path</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">message</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&quot;utf-8&quot;</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="n">filename</span> <span class="o">=</span> <span class="kc">None</span>
<span class="k">if</span> <span class="n">filename</span> <span class="ow">in</span> <span class="n">list_files</span><span class="p">():</span>
<span class="k">try</span><span class="p">:</span>
<span class="c1"># If we have the requested file, we&#39;ll</span>
@@ -1299,18 +1918,18 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="c1"># We expect any normal data packets on the link</span>
<span class="c1"># to contain a list of served files, so we set</span>
<span class="c1"># a callback accordingly</span>
<span class="n">link</span><span class="o">.</span><span class="n">packet_callback</span><span class="p">(</span><span class="n">filelist_received</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_packet_callback</span><span class="p">(</span><span class="n">filelist_received</span><span class="p">)</span>
<span class="c1"># We&#39;ll also set up functions to inform the</span>
<span class="c1"># user when the link is established or closed</span>
<span class="n">link</span><span class="o">.</span><span class="n">link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
<span class="c1"># And set the link to automatically begin</span>
<span class="c1"># downloading advertised resources</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_resource_strategy</span><span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">ACCEPT_ALL</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">resource_started_callback</span><span class="p">(</span><span class="n">download_began</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">resource_concluded_callback</span><span class="p">(</span><span class="n">download_concluded</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_resource_started_callback</span><span class="p">(</span><span class="n">download_began</span><span class="p">)</span>
<span class="n">link</span><span class="o">.</span><span class="n">set_resource_concluded_callback</span><span class="p">(</span><span class="n">download_concluded</span><span class="p">)</span>
<span class="n">menu</span><span class="p">()</span>
@@ -1398,7 +2017,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="nb">print</span><span class="p">(</span><span class="s2">&quot;&quot;</span><span class="p">)</span>
<span class="k">while</span> <span class="n">menu_mode</span> <span class="o">==</span> <span class="s2">&quot;downloading&quot;</span><span class="p">:</span>
<span class="k">global</span> <span class="n">current_download</span>
<span class="n">percent</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">current_download</span><span class="o">.</span><span class="n">progress</span><span class="p">()</span> <span class="o">*</span> <span class="mf">100.0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">percent</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">current_download</span><span class="o">.</span><span class="n">get_progress</span><span class="p">()</span> <span class="o">*</span> <span class="mf">100.0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="nb">print</span><span class="p">((</span><span class="s2">&quot;</span><span class="se">\r</span><span class="s2">Progress: &quot;</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">percent</span><span class="p">)</span><span class="o">+</span><span class="s2">&quot; % &quot;</span><span class="p">),</span> <span class="n">end</span><span class="o">=</span><span class="s1">&#39; &#39;</span><span class="p">)</span>
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
@@ -1542,7 +2161,6 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<span class="n">saved_filename</span> <span class="o">=</span> <span class="n">current_filename</span>
<span class="k">if</span> <span class="n">resource</span><span class="o">.</span><span class="n">status</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Resource</span><span class="o">.</span><span class="n">COMPLETE</span><span class="p">:</span>
<span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">isfile</span><span class="p">(</span><span class="n">saved_filename</span><span class="p">):</span>
@@ -1661,17 +2279,16 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
<li><a class="reference internal" href="#broadcast">Broadcast</a></li>
<li><a class="reference internal" href="#echo">Echo</a></li>
<li><a class="reference internal" href="#link">Link</a></li>
<li><a class="reference internal" href="#example-identify">Identification</a></li>
<li><a class="reference internal" href="#requests-responses">Requests &amp; Responses</a></li>
<li><a class="reference internal" href="#filetransfer">Filetransfer</a></li>
</ul>
</li>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="gettingstartedfast.html"
title="previous chapter">Getting Started Fast</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="reference.html"
title="next chapter">API Reference</a></p>
title="previous chapter">API Reference</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -1701,11 +2318,8 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
>index</a></li>
<li class="right" >
<a href="reference.html" title="API Reference"
>next</a> |</li>
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Examples</a></li>
</ul>
</div>
+95 -46
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Index &#8212; Reticulum Network Stack 0.2.0 beta documentation</title>
<title>Index &#8212; Reticulum Network Stack 0.2.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -23,7 +23,7 @@
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
accesskey="I">index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</a></li>
</ul>
</div>
@@ -47,6 +47,7 @@
| <a href="#I"><strong>I</strong></a>
| <a href="#K"><strong>K</strong></a>
| <a href="#L"><strong>L</strong></a>
| <a href="#M"><strong>M</strong></a>
| <a href="#N"><strong>N</strong></a>
| <a href="#P"><strong>P</strong></a>
| <a href="#R"><strong>R</strong></a>
@@ -80,8 +81,12 @@
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.create_keys">create_keys() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Link.CURVE">CURVE (RNS.Link attribute)</a>
<li><a href="reference.html#RNS.Identity.CURVE">CURVE (RNS.Identity attribute)</a>
<ul>
<li><a href="reference.html#RNS.Link.CURVE">(RNS.Link attribute)</a>
</li>
</ul></li>
</ul></td>
</tr></table>
@@ -94,17 +99,13 @@
<li><a href="reference.html#RNS.Identity.decrypt">(RNS.Identity method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Link.DEFAULT_TIMEOUT">DEFAULT_TIMEOUT (RNS.Link attribute)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.PacketReceipt.delivery_callback">delivery_callback() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Transport.deregister_announce_handler">deregister_announce_handler() (RNS.Transport static method)</a>
</li>
<li><a href="reference.html#RNS.Destination">Destination (class in RNS)</a>
<li><a href="reference.html#RNS.Destination.deregister_request_handler">deregister_request_handler() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Link.disable_encryption">disable_encryption() (RNS.Link method)</a>
<li><a href="reference.html#RNS.Destination">Destination (class in RNS)</a>
</li>
</ul></td>
</tr></table>
@@ -119,11 +120,19 @@
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Packet.ENCRYPTED_MDU">ENCRYPTED_MDU (RNS.Packet attribute)</a>
</li>
<li><a href="reference.html#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">ESTABLISHMENT_TIMEOUT_PER_HOP (RNS.Link attribute)</a>
</li>
</ul></td>
</tr></table>
<h2 id="F">F</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Identity.from_bytes">from_bytes() (RNS.Identity static method)</a>
</li>
<li><a href="reference.html#RNS.Identity.from_file">from_file() (RNS.Identity static method)</a>
</li>
</ul></td>
@@ -144,14 +153,34 @@
<li><a href="reference.html#RNS.Identity.get_private_key">(RNS.Identity method)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.RequestReceipt.get_progress">get_progress() (RNS.RequestReceipt method)</a>
<ul>
<li><a href="reference.html#RNS.Resource.get_progress">(RNS.Resource method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Identity.get_public_key">get_public_key() (RNS.Identity method)</a>
</li>
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.get_remote_identity">get_remote_identity() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.RequestReceipt.get_request_id">get_request_id() (RNS.RequestReceipt method)</a>
</li>
<li><a href="reference.html#RNS.RequestReceipt.get_response">get_response() (RNS.RequestReceipt method)</a>
</li>
<li><a href="reference.html#RNS.RequestReceipt.get_response_time">get_response_time() (RNS.RequestReceipt method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.get_rtt">get_rtt() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
<ul>
<li><a href="reference.html#RNS.RequestReceipt.get_status">(RNS.RequestReceipt method)</a>
</li>
</ul></li>
</ul></td>
</tr></table>
@@ -160,11 +189,13 @@
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Transport.has_path">has_path() (RNS.Transport static method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.hash">hash() (RNS.Destination static method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.hash_from_name_and_identity">hash_from_name_and_identity() (RNS.Destination static method)</a>
</li>
<li><a href="reference.html#RNS.Transport.hops_to">hops_to() (RNS.Transport static method)</a>
</li>
</ul></td>
</tr></table>
@@ -172,10 +203,12 @@
<h2 id="I">I</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Identity">Identity (class in RNS)</a>
<li><a href="reference.html#RNS.Link.identify">identify() (RNS.Link method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Identity">Identity (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Link.inactive_for">inactive_for() (RNS.Link method)</a>
</li>
</ul></td>
@@ -198,21 +231,27 @@
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link">Link (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Destination.link_established_callback">link_established_callback() (RNS.Destination method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Destination.load_private_key">load_private_key() (RNS.Destination method)</a>
<ul>
<li><a href="reference.html#RNS.Identity.load_private_key">(RNS.Identity method)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Identity.load_public_key">load_public_key() (RNS.Identity method)</a>
</li>
</ul></td>
</tr></table>
<h2 id="M">M</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Reticulum.MTU">MTU (RNS.Reticulum attribute)</a>
</li>
</ul></td>
</tr></table>
<h2 id="N">N</h2>
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
@@ -230,19 +269,13 @@
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Packet">Packet (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Destination.packet_callback">packet_callback() (RNS.Destination method)</a>
<ul>
<li><a href="reference.html#RNS.Link.packet_callback">(RNS.Link method)</a>
</li>
</ul></li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.PacketReceipt">PacketReceipt (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Resource.progress">progress() (RNS.Resource method)</a>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Transport.PATHFINDER_M">PATHFINDER_M (RNS.Transport attribute)</a>
</li>
<li><a href="reference.html#RNS.Destination.proof_requested_callback">proof_requested_callback() (RNS.Destination method)</a>
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
</li>
</ul></td>
</tr></table>
@@ -256,23 +289,21 @@
</li>
<li><a href="reference.html#RNS.Transport.register_announce_handler">register_announce_handler() (RNS.Transport static method)</a>
</li>
<li><a href="reference.html#RNS.Transport.request_path">request_path() (RNS.Transport static method)</a>
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Packet.resend">resend() (RNS.Packet method)</a>
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Transport.request_path">request_path() (RNS.Transport static method)</a>
</li>
<li><a href="reference.html#RNS.RequestReceipt">RequestReceipt (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Packet.resend">resend() (RNS.Packet method)</a>
</li>
<li><a href="reference.html#RNS.Resource">Resource (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Link.resource_callback">resource_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.resource_concluded_callback">resource_concluded_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.resource_started_callback">resource_started_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Reticulum">Reticulum (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.rtt">rtt() (RNS.PacketReceipt method)</a>
</li>
</ul></td>
</tr></table>
@@ -283,16 +314,36 @@
<li><a href="reference.html#RNS.Packet.send">send() (RNS.Packet method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_default_app_data">set_default_app_data() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.set_delivery_callback">set_delivery_callback() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_link_established_callback">set_link_established_callback() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_packet_callback">set_packet_callback() (RNS.Destination method)</a>
<ul>
<li><a href="reference.html#RNS.Link.set_packet_callback">(RNS.Link method)</a>
</li>
</ul></li>
<li><a href="reference.html#RNS.Destination.set_proof_requested_callback">set_proof_requested_callback() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Destination.set_proof_strategy">set_proof_strategy() (RNS.Destination method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_resource_strategy">set_resource_strategy() (RNS.Link method)</a>
<li><a href="reference.html#RNS.Link.set_remote_identified_callback">set_remote_identified_callback() (RNS.Link method)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.set_resource_callback">set_resource_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_resource_concluded_callback">set_resource_concluded_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_resource_started_callback">set_resource_started_callback() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.Link.set_resource_strategy">set_resource_strategy() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.set_timeout">set_timeout() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.should_allow_unencrypted">should_allow_unencrypted() (RNS.Reticulum static method)</a>
<li><a href="reference.html#RNS.PacketReceipt.set_timeout_callback">set_timeout_callback() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.should_use_implicit_proof">should_use_implicit_proof() (RNS.Reticulum static method)</a>
</li>
@@ -309,15 +360,13 @@
<table style="width: 100%" class="indextable genindextable"><tr>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Link.teardown">teardown() (RNS.Link method)</a>
</li>
<li><a href="reference.html#RNS.PacketReceipt.timeout_callback">timeout_callback() (RNS.PacketReceipt method)</a>
</li>
<li><a href="reference.html#RNS.Identity.to_file">to_file() (RNS.Identity method)</a>
</li>
<li><a href="reference.html#RNS.Transport">Transport (class in RNS)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="reference.html#RNS.Transport">Transport (class in RNS)</a>
</li>
<li><a href="reference.html#RNS.Reticulum.transport_enabled">transport_enabled() (RNS.Reticulum static method)</a>
</li>
<li><a href="reference.html#RNS.Identity.truncated_hash">truncated_hash() (RNS.Identity static method)</a>
@@ -363,7 +412,7 @@
<li class="right" style="margin-right: 10px">
<a href="#" title="General Index"
>index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Index</a></li>
</ul>
</div>
+9 -10
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Getting Started Fast &#8212; Reticulum Network Stack 0.2.0 beta documentation</title>
<title>Getting Started Fast &#8212; Reticulum Network Stack 0.2.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,7 +16,7 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Examples" href="examples.html" />
<link rel="next" title="Understanding Reticulum" href="understanding.html" />
<link rel="prev" title="What is Reticulum?" href="whatis.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
@@ -26,12 +26,12 @@
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="examples.html" title="Examples"
<a href="understanding.html" title="Understanding Reticulum"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
@@ -43,7 +43,6 @@
<div class="section" id="getting-started-fast">
<h1>Getting Started Fast<a class="headerlink" href="#getting-started-fast" title="Permalink to this headline"></a></h1>
<p>What do we want to do? Something! When do we want to do it? Right now! Lets go.</p>
<p>The best way to get started with the Reticulum Network Stack depends on what
you want to do. This guide will outline sensible starting paths for different
scenarios.</p>
@@ -60,7 +59,7 @@ in the development for the messaging and information-sharing protocol
<div class="section" id="develop-a-program-with-reticulum">
<h2>Develop a Program with Reticulum<a class="headerlink" href="#develop-a-program-with-reticulum" title="Permalink to this headline"></a></h2>
<p>If you want to develop programs that use Reticulum, the easiest way to get
started is to install Reticulum via pip:</p>
started is to install the latest release of Reticulum via pip:</p>
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span>
</pre></div>
</div>
@@ -132,8 +131,8 @@ dont use pip, but try this recipe:</p>
<p class="topless"><a href="whatis.html"
title="previous chapter">What is Reticulum?</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="examples.html"
title="next chapter">Examples</a></p>
<p class="topless"><a href="understanding.html"
title="next chapter">Understanding Reticulum</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -162,12 +161,12 @@ dont use pip, but try this recipe:</p>
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="examples.html" title="Examples"
<a href="understanding.html" title="Understanding Reticulum"
>next</a> |</li>
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
</ul>
</div>
+30 -26
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Reticulum Network Stack Manual &#8212; Reticulum Network Stack 0.2.0 beta documentation</title>
<title>Reticulum Network Stack Manual &#8212; Reticulum Network Stack 0.2.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -27,7 +27,7 @@
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
accesskey="N">next</a> |</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
@@ -58,29 +58,6 @@ the development of Reticulum itself.</p>
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Examples</a><ul>
<li class="toctree-l2"><a class="reference internal" href="examples.html#minimal">Minimal</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#announce">Announce</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#broadcast">Broadcast</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#echo">Echo</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#link">Link</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#filetransfer">Filetransfer</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a><ul>
<li class="toctree-l2"><a class="reference internal" href="reference.html#classes">Classes</a><ul>
<li class="toctree-l3"><a class="reference internal" href="reference.html#reticulum">Reticulum</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#api-identity">Identity</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#destination">Destination</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#packet">Packet</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#packet-receipt">Packet Receipt</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#link">Link</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#resource">Resource</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#transport">Transport</a></li>
</ul>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a><ul>
<li class="toctree-l2"><a class="reference internal" href="understanding.html#motivation">Motivation</a></li>
<li class="toctree-l2"><a class="reference internal" href="understanding.html#goals">Goals</a></li>
@@ -92,6 +69,7 @@ the development of Reticulum itself.</p>
</ul>
</li>
<li class="toctree-l2"><a class="reference internal" href="understanding.html#reticulum-transport">Reticulum Transport</a><ul>
<li class="toctree-l3"><a class="reference internal" href="understanding.html#the-announce-mechanism-in-detail">The Announce Mechanism in Detail</a></li>
<li class="toctree-l3"><a class="reference internal" href="understanding.html#reaching-the-destination">Reaching the Destination</a></li>
<li class="toctree-l3"><a class="reference internal" href="understanding.html#resources">Resources</a></li>
</ul>
@@ -105,6 +83,32 @@ the development of Reticulum itself.</p>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a><ul>
<li class="toctree-l2"><a class="reference internal" href="reference.html#classes">Classes</a><ul>
<li class="toctree-l3"><a class="reference internal" href="reference.html#reticulum">Reticulum</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#api-identity">Identity</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#destination">Destination</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#packet">Packet</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#packet-receipt">Packet Receipt</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#link">Link</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#request-receipt">Request Receipt</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#resource">Resource</a></li>
<li class="toctree-l3"><a class="reference internal" href="reference.html#transport">Transport</a></li>
</ul>
</li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="examples.html">Examples</a><ul>
<li class="toctree-l2"><a class="reference internal" href="examples.html#minimal">Minimal</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#announce">Announce</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#broadcast">Broadcast</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#echo">Echo</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#link">Link</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#example-identify">Identification</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#requests-responses">Requests &amp; Responses</a></li>
<li class="toctree-l2"><a class="reference internal" href="examples.html#filetransfer">Filetransfer</a></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="indices-and-tables">
@@ -164,7 +168,7 @@ the development of Reticulum itself.</p>
<li class="right" >
<a href="whatis.html" title="What is Reticulum?"
>next</a> |</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
</ul>
</div>
Binary file not shown.
+307 -115
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>API Reference &#8212; Reticulum Network Stack 0.2.0 beta documentation</title>
<title>API Reference &#8212; Reticulum Network Stack 0.2.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,8 +16,8 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Understanding Reticulum" href="understanding.html" />
<link rel="prev" title="Examples" href="examples.html" />
<link rel="next" title="Examples" href="examples.html" />
<link rel="prev" title="Understanding Reticulum" href="understanding.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
@@ -26,12 +26,12 @@
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
<a href="examples.html" title="Examples"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="examples.html" title="Examples"
<a href="understanding.html" title="Understanding Reticulum"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
</ul>
</div>
@@ -72,16 +72,16 @@ terminated (unless killed forcibly).</p>
programs that use RNS starting and terminating at different times,
it will be advantageous to run a master RNS instance as a daemon for
other programs to use on demand.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Reticulum.should_allow_unencrypted">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">should_allow_unencrypted</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.should_allow_unencrypted" title="Permalink to this definition"></a></dt>
<dd><p>Returns whether unencrypted links are allowed by the
current configuration.</p>
<dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>True if the current running configuration allows downgrading links to plaintext. False if not.</p>
</dd>
</dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Reticulum.MTU">
<span class="sig-name descname"><span class="pre">MTU</span></span><em class="property"> <span class="pre">=</span> <span class="pre">500</span></em><a class="headerlink" href="#RNS.Reticulum.MTU" title="Permalink to this definition"></a></dt>
<dd><p>The MTU that Reticulum adheres to, and will expect other peers to
adhere to. By default, the MTU is 500 bytes. In custom RNS network
implementations, it is possible to change this value, but doing so will
completely break compatibility with all other RNS networks. An identical
MTU is a prerequisite for peers to communicate in the same network.</p>
<p>Unless you really know what you are doing, the MTU should be left at
the default value.</p>
</dd></dl>
<dl class="py method">
@@ -117,26 +117,32 @@ and pass announces over the network.</p>
<span id="identity"></span><h3>Identity<a class="headerlink" href="#api-identity" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Identity">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Identity</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">public_only</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Identity</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">create_keys</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity" title="Permalink to this definition"></a></dt>
<dd><p>This class is used to manage identities in Reticulum. It provides methods
for encryption, decryption, signatures and verification, and is the basis
for all encrypted communication over Reticulum networks.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>public_only</strong> Specifies whether this destination only holds a public key.</p>
<dd class="field-odd"><p><strong>create_keys</strong> Specifies whether new encryption and signing keys should be generated.</p>
</dd>
</dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.CURVE">
<span class="sig-name descname"><span class="pre">CURVE</span></span><em class="property"> <span class="pre">=</span> <span class="pre">'Curve25519'</span></em><a class="headerlink" href="#RNS.Identity.CURVE" title="Permalink to this definition"></a></dt>
<dd><p>The curve used for Elliptic Curve DH key exchanges</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.KEYSIZE">
<span class="sig-name descname"><span class="pre">KEYSIZE</span></span><em class="property"> <span class="pre">=</span> <span class="pre">1024</span></em><a class="headerlink" href="#RNS.Identity.KEYSIZE" title="Permalink to this definition"></a></dt>
<dd><p>RSA key size in bits.</p>
<span class="sig-name descname"><span class="pre">KEYSIZE</span></span><em class="property"> <span class="pre">=</span> <span class="pre">512</span></em><a class="headerlink" href="#RNS.Identity.KEYSIZE" title="Permalink to this definition"></a></dt>
<dd><p>X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Identity.TRUNCATED_HASHLENGTH">
<span class="sig-name descname"><span class="pre">TRUNCATED_HASHLENGTH</span></span><em class="property"> <span class="pre">=</span> <span class="pre">80</span></em><a class="headerlink" href="#RNS.Identity.TRUNCATED_HASHLENGTH" title="Permalink to this definition"></a></dt>
<dd><p>Constant specifying the truncated hash length (in bits) used by Reticulum
for addressable hashes. Non-configurable.</p>
for addressable hashes and other purposes. Non-configurable.</p>
</dd></dl>
<dl class="py method">
@@ -209,6 +215,21 @@ for addressable hashes. Non-configurable.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.from_bytes">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">from_bytes</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">prv_bytes</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.from_bytes" title="Permalink to this definition"></a></dt>
<dd><p>Create a new <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance from <em>bytes</em> of private key.
Can be used to load previously created and saved identities into Reticulum.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>prv_bytes</strong> The <em>bytes</em> of private a saved private key. <strong>HAZARD!</strong> Never use this to generate a new key by feeding random data in prv_bytes.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>A <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> instance, or <em>None</em> if the <em>bytes</em> data was invalid.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.from_file">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">from_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.from_file" title="Permalink to this definition"></a></dt>
@@ -224,6 +245,22 @@ Can be used to load previously created and saved identities into Reticulum.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.to_file">
<span class="sig-name descname"><span class="pre">to_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.to_file" title="Permalink to this definition"></a></dt>
<dd><p>Saves the identity to a file. This will write the private key to disk,
and anyone with access to this file will be able to decrypt all
communication for the identity. Be very careful with this method.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>path</strong> The full path specifying where to save the identity.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>True if the file was saved, otherwise False.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.get_private_key">
<span class="sig-name descname"><span class="pre">get_private_key</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.get_private_key" title="Permalink to this definition"></a></dt>
@@ -260,11 +297,11 @@ Can be used to load previously created and saved identities into Reticulum.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.load_public_key">
<span class="sig-name descname"><span class="pre">load_public_key</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">key</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.load_public_key" title="Permalink to this definition"></a></dt>
<span class="sig-name descname"><span class="pre">load_public_key</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">pub_bytes</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.load_public_key" title="Permalink to this definition"></a></dt>
<dd><p>Load a public key into the instance.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>prv_bytes</strong> The public key as <em>bytes</em>.</p>
<dd class="field-odd"><p><strong>pub_bytes</strong> The public key as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>True if the key was loaded, otherwise False.</p>
@@ -272,22 +309,6 @@ Can be used to load previously created and saved identities into Reticulum.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.to_file">
<span class="sig-name descname"><span class="pre">to_file</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.to_file" title="Permalink to this definition"></a></dt>
<dd><p>Saves the identity to a file. This will write the private key to disk,
and anyone with access to this file will be able to decrypt all
communication for the identity. Be very careful with this method.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>path</strong> The full path specifying where to save the identity.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>True if the file was saved, otherwise False.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.encrypt">
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition"></a></dt>
@@ -297,17 +318,17 @@ communication for the identity. Be very careful with this method.</p>
<dd class="field-odd"><p><strong>plaintext</strong> The plaintext to be encrypted as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>Ciphertext as <em>bytes</em>.</p>
<dd class="field-even"><p>Ciphertext token as <em>bytes</em>.</p>
</dd>
<dt class="field-odd">Raises</dt>
<dd class="field-odd"><p><em>KeyError</em> if the instance does not hold a public key</p>
<dd class="field-odd"><p><em>KeyError</em> if the instance does not hold a public key.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Identity.decrypt">
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition"></a></dt>
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition"></a></dt>
<dd><p>Decrypts information for the identity.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
@@ -317,7 +338,7 @@ communication for the identity. Be very careful with this method.</p>
<dd class="field-even"><p>Plaintext as <em>bytes</em>, or <em>None</em> if decryption fails.</p>
</dd>
<dt class="field-odd">Raises</dt>
<dd class="field-odd"><p><em>KeyError</em> if the instance does not hold a private key</p>
<dd class="field-odd"><p><em>KeyError</em> if the instance does not hold a private key.</p>
</dd>
</dl>
</dd></dl>
@@ -334,7 +355,7 @@ communication for the identity. Be very careful with this method.</p>
<dd class="field-even"><p>Signature as <em>bytes</em>.</p>
</dd>
<dt class="field-odd">Raises</dt>
<dd class="field-odd"><p><em>KeyError</em> if the instance does not hold a private key</p>
<dd class="field-odd"><p><em>KeyError</em> if the instance does not hold a private key.</p>
</dd>
</dl>
</dd></dl>
@@ -354,7 +375,7 @@ communication for the identity. Be very careful with this method.</p>
<dd class="field-even"><p>True if the signature is valid, otherwise False.</p>
</dd>
<dt class="field-odd">Raises</dt>
<dd class="field-odd"><p><em>KeyError</em> if the instance does not hold a public key</p>
<dd class="field-odd"><p><em>KeyError</em> if the instance does not hold a public key.</p>
</dd>
</dl>
</dd></dl>
@@ -377,7 +398,7 @@ encrypted communication with it.</p>
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>identity</strong> An instance of <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a>. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.</p></li>
<li><p><strong>direction</strong> <code class="docutils literal notranslate"><span class="pre">RNS.Destination.IN</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.OUT</span></code></p></li>
<li><p><strong>direction</strong> <code class="docutils literal notranslate"><span class="pre">RNS.Destination.IN</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.OUT</span></code>.</p></li>
<li><p><strong>type</strong> <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.PLAIN</span></code>.</p></li>
<li><p><strong>app_name</strong> A string specifying the app name.</p></li>
<li><p><strong>*aspects</strong> Any non-zero number of string arguments.</p></li>
@@ -427,8 +448,8 @@ encrypted communication with it.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.announce">
<span class="sig-name descname"><span class="pre">announce</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">app_data</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">path_response</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.announce" title="Permalink to this definition"></a></dt>
<dd><p>Creates an announce packet for this destination and broadcasts it on
all interfaces. Application specific data can be added to the announce.</p>
<dd><p>Creates an announce packet for this destination and broadcasts it on all
relevant interfaces. Application specific data can be added to the announce.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
@@ -440,8 +461,8 @@ all interfaces. Application specific data can be added to the announce.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.link_established_callback">
<span class="sig-name descname"><span class="pre">link_established_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.link_established_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Destination.set_link_established_callback">
<span class="sig-name descname"><span class="pre">set_link_established_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.set_link_established_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a link has been established to
this destination.</p>
<dl class="field-list simple">
@@ -452,8 +473,8 @@ this destination.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.packet_callback">
<span class="sig-name descname"><span class="pre">packet_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.packet_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Destination.set_packet_callback">
<span class="sig-name descname"><span class="pre">set_packet_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.set_packet_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a packet has been received by
this destination.</p>
<dl class="field-list simple">
@@ -464,8 +485,8 @@ this destination.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.proof_requested_callback">
<span class="sig-name descname"><span class="pre">proof_requested_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.proof_requested_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Destination.set_proof_requested_callback">
<span class="sig-name descname"><span class="pre">set_proof_requested_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.set_proof_requested_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a proof has been requested for
a packet sent to this destination. Allows control over when and if
proofs should be returned for received packets.</p>
@@ -487,6 +508,39 @@ proofs should be returned for received packets.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.register_request_handler">
<span class="sig-name descname"><span class="pre">register_request_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">response_generator</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allow</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">allowed_list</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.register_request_handler" title="Permalink to this definition"></a></dt>
<dd><p>Registers a request handler.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>path</strong> The path for the request handler to be registered.</p></li>
<li><p><strong>response_generator</strong> A function or method with the signature <em>response_generator(path, data, request_id, remote_identity, requested_at)</em> to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns <code class="docutils literal notranslate"><span class="pre">None</span></code>, no response will be sent.</p></li>
<li><p><strong>allow</strong> One of <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_NONE</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_ALL</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_LIST</span></code>. If <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_LIST</span></code> is set, the request handler will only respond to requests for identified peers in the supplied list.</p></li>
<li><p><strong>allowed_list</strong> A list of <em>bytes-like</em> <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> hashes.</p></li>
</ul>
</dd>
<dt class="field-even">Raises</dt>
<dd class="field-even"><p><code class="docutils literal notranslate"><span class="pre">ValueError</span></code> if any of the supplied arguments are invalid.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.deregister_request_handler">
<span class="sig-name descname"><span class="pre">deregister_request_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.deregister_request_handler" title="Permalink to this definition"></a></dt>
<dd><p>Deregisters a request handler.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>path</strong> The path for the request handler to be deregistered.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>True if the handler was deregistered, otherwise False.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Destination.create_keys">
<span class="sig-name descname"><span class="pre">create_keys</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.create_keys" title="Permalink to this definition"></a></dt>
@@ -591,23 +645,36 @@ unless other app_data is specified in the <em>announce</em> method.</p>
<span id="api-packet"></span><h3>Packet<a class="headerlink" href="#packet" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Packet">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Packet</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">packet_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">context</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">transport_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">header_type</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">0</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">transport_id</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">attached_interface</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">create_receipt</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet" title="Permalink to this definition"></a></dt>
<dd><p>The Packet class is used to create packet instances that can be
sent over a Reticulum network.</p>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Packet</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">create_receipt</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet" title="Permalink to this definition"></a></dt>
<dd><p>The Packet class is used to create packet instances that can be sent
over a Reticulum network. Packets to will automatically be encrypted if
they are adressed to a <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code> destination,
<code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> destination or a <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a>.</p>
<p>For <code class="docutils literal notranslate"><span class="pre">RNS.Destination.GROUP</span></code> destinations, Reticulum will use the
pre-shared key configured for the destination.</p>
<p>For <code class="docutils literal notranslate"><span class="pre">RNS.Destination.SINGLE</span></code> destinations and <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a>
destinations, reticulum will use ephemeral keys, and offers <strong>Forward Secrecy</strong>.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>destination</strong> A <a class="reference internal" href="#api-destination"><span class="std std-ref">RNS.Destination</span></a> instance to which the packet will be sent.</p></li>
<li><p><strong>data</strong> The data payload to be included in the packet as <em>bytes</em>.</p></li>
<li><p><strong>create_receipt</strong> Specifies whether a <a class="reference internal" href="#api-packetreceipt"><span class="std std-ref">RNS.PacketReceipt</span></a> should be created when instantiating the packet.</p></li>
<li><p><strong>type</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Defaults to <code class="docutils literal notranslate"><span class="pre">RNS.Packet.DATA</span></code>, and should not be specified.</p></li>
<li><p><strong>context</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
<li><p><strong>transport_type</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
<li><p><strong>transport_id</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
<li><p><strong>attached_interface</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>. Ignore.</p></li>
</ul>
</dd>
</dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Packet.ENCRYPTED_MDU">
<span class="sig-name descname"><span class="pre">ENCRYPTED_MDU</span></span><em class="property"> <span class="pre">=</span> <span class="pre">383</span></em><a class="headerlink" href="#RNS.Packet.ENCRYPTED_MDU" title="Permalink to this definition"></a></dt>
<dd><p>The maximum size of the payload data in a single encrypted packet</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Packet.PLAIN_MDU">
<span class="sig-name descname"><span class="pre">PLAIN_MDU</span></span><em class="property"> <span class="pre">=</span> <span class="pre">477</span></em><a class="headerlink" href="#RNS.Packet.PLAIN_MDU" title="Permalink to this definition"></a></dt>
<dd><p>The maximum size of the payload data in a single unencrypted packet</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Packet.send">
<span class="sig-name descname"><span class="pre">send</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet.send" title="Permalink to this definition"></a></dt>
@@ -637,11 +704,11 @@ sent over a Reticulum network.</p>
<span id="api-packetreceipt"></span><h3>Packet Receipt<a class="headerlink" href="#packet-receipt" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.PacketReceipt">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">PacketReceipt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">packet</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">PacketReceipt</span></span><a class="headerlink" href="#RNS.PacketReceipt" title="Permalink to this definition"></a></dt>
<dd><p>The PacketReceipt class is used to receive notifications about
<a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instances sent over the network. Instances
of this class should never be created manually, but always returned
from a the <em>send()</em> method of a <a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instance.</p>
of this class are never created manually, but always returned from
the <em>send()</em> method of a <a class="reference internal" href="#api-packet"><span class="std std-ref">RNS.Packet</span></a> instance.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.PacketReceipt.get_status">
<span class="sig-name descname"><span class="pre">get_status</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.get_status" title="Permalink to this definition"></a></dt>
@@ -653,8 +720,8 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.PacketReceipt.rtt">
<span class="sig-name descname"><span class="pre">rtt</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.rtt" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.PacketReceipt.get_rtt">
<span class="sig-name descname"><span class="pre">get_rtt</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.get_rtt" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The round-trip-time in seconds</p>
@@ -674,8 +741,8 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.PacketReceipt.delivery_callback">
<span class="sig-name descname"><span class="pre">delivery_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.delivery_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.PacketReceipt.set_delivery_callback">
<span class="sig-name descname"><span class="pre">set_delivery_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.set_delivery_callback" title="Permalink to this definition"></a></dt>
<dd><p>Sets a function that gets called if a successfull delivery has been proven.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
@@ -685,8 +752,8 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.PacketReceipt.timeout_callback">
<span class="sig-name descname"><span class="pre">timeout_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.timeout_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.PacketReceipt.set_timeout_callback">
<span class="sig-name descname"><span class="pre">set_timeout_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.PacketReceipt.set_timeout_callback" title="Permalink to this definition"></a></dt>
<dd><p>Sets a function that gets called if the delivery times out.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
@@ -702,15 +769,16 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
<span id="api-link"></span><h3>Link<a class="headerlink" href="#link" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Link">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Link</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">owner</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">peer_pub_bytes</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">peer_sig_pub_bytes</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link" title="Permalink to this definition"></a></dt>
<dd><p>This class.</p>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Link</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">established_callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">closed_callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link" title="Permalink to this definition"></a></dt>
<dd><p>This class is used to establish and manage links to other peers. When a
link instance is created, Reticulum will attempt to establish verified
connectivity with the specified destination.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>destination</strong> A <a class="reference internal" href="#api-destination"><span class="std std-ref">RNS.Destination</span></a> instance which to establish a link to.</p></li>
<li><p><strong>owner</strong> Internal use by <a class="reference internal" href="#api-transport"><span class="std std-ref">RNS.Transport</span></a>, ignore this argument.</p></li>
<li><p><strong>peer_pub_bytes</strong> Internal use, ignore this argument.</p></li>
<li><p><strong>peer_sig_pub_bytes</strong> Internal use, ignore this argument.</p></li>
<li><p><strong>established_callback</strong> An optional function or method with the signature <em>callback(link)</em> to be called when the link has been established.</p></li>
<li><p><strong>closed_callback</strong> An optional function or method with the signature <em>callback(link)</em> to be called when the link is closed.</p></li>
</ul>
</dd>
</dl>
@@ -721,17 +789,51 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.DEFAULT_TIMEOUT">
<span class="sig-name descname"><span class="pre">DEFAULT_TIMEOUT</span></span><em class="property"> <span class="pre">=</span> <span class="pre">15.0</span></em><a class="headerlink" href="#RNS.Link.DEFAULT_TIMEOUT" title="Permalink to this definition"></a></dt>
<dd><p>Default timeout for link establishment in seconds.</p>
<dt class="sig sig-object py" id="RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">
<span class="sig-name descname"><span class="pre">ESTABLISHMENT_TIMEOUT_PER_HOP</span></span><em class="property"> <span class="pre">=</span> <span class="pre">5</span></em><a class="headerlink" href="#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP" title="Permalink to this definition"></a></dt>
<dd><p>Default timeout for link establishment in seconds per hop to destination.</p>
</dd></dl>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Link.KEEPALIVE">
<span class="sig-name descname"><span class="pre">KEEPALIVE</span></span><em class="property"> <span class="pre">=</span> <span class="pre">180</span></em><a class="headerlink" href="#RNS.Link.KEEPALIVE" title="Permalink to this definition"></a></dt>
<span class="sig-name descname"><span class="pre">KEEPALIVE</span></span><em class="property"> <span class="pre">=</span> <span class="pre">360</span></em><a class="headerlink" href="#RNS.Link.KEEPALIVE" title="Permalink to this definition"></a></dt>
<dd><p>Interval for sending keep-alive packets on established links in seconds.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.identify">
<span class="sig-name descname"><span class="pre">identify</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identity</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.identify" title="Permalink to this definition"></a></dt>
<dd><p>Identifies the initiator of the link to the remote peer. This can only happen
once the link has been established, and is carried out over the encrypted link.
The identity is only revealed to the remote peer, and initiator anonymity is
thus preserved. This method can be used for authentication.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>identity</strong> An RNS.Identity instance to identify as.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.request">
<span class="sig-name descname"><span class="pre">request</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">path</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">data</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">response_callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">failed_callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">progress_callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">timeout</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.request" title="Permalink to this definition"></a></dt>
<dd><p>Sends a request to the remote peer.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>path</strong> The request path.</p></li>
<li><p><strong>response_callback</strong> An optional function or method with the signature <em>response_callback(request_receipt)</em> to be called when a response is received. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
<li><p><strong>failed_callback</strong> An optional function or method with the signature <em>failed_callback(request_receipt)</em> to be called when a request fails. See the <a class="reference internal" href="examples.html#example-request"><span class="std std-ref">Request Example</span></a> for more info.</p></li>
<li><p><strong>progress_callback</strong> An optional function or method with the signature <em>progress_callback(request_receipt)</em> to be called when progress is made receiving the response. Progress can be accessed as a float between 0.0 and 1.0 by the <em>request_receipt.progress</em> property.</p></li>
<li><p><strong>timeout</strong> An optional timeout in seconds for the request. If <em>None</em> is supplied it will be calculated based on link RTT.</p></li>
</ul>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>A <a class="reference internal" href="#api-requestreceipt"><span class="std std-ref">RNS.RequestReceipt</span></a> instance if the request was sent, or <em>False</em> if it was not.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.no_inbound_for">
<span class="sig-name descname"><span class="pre">no_inbound_for</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.no_inbound_for" title="Permalink to this definition"></a></dt>
@@ -762,6 +864,16 @@ from a the <em>send()</em> method of a <a class="reference internal" href="#api-
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.get_remote_identity">
<span class="sig-name descname"><span class="pre">get_remote_identity</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_remote_identity" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The identity of the remote peer, if it is known</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.teardown">
<span class="sig-name descname"><span class="pre">teardown</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.teardown" title="Permalink to this definition"></a></dt>
@@ -770,8 +882,8 @@ be used if a new link to the same destination is established.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.packet_callback">
<span class="sig-name descname"><span class="pre">packet_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.packet_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Link.set_packet_callback">
<span class="sig-name descname"><span class="pre">set_packet_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_packet_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a packet has been
received over this link.</p>
<dl class="field-list simple">
@@ -782,8 +894,8 @@ received over this link.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.resource_callback">
<span class="sig-name descname"><span class="pre">resource_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.resource_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Link.set_resource_callback">
<span class="sig-name descname"><span class="pre">set_resource_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_resource_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a resource has been
advertised over this link. If the function returns <em>True</em>
the resource will be accepted. If it returns <em>False</em> it will
@@ -796,8 +908,8 @@ be ignored.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.resource_started_callback">
<span class="sig-name descname"><span class="pre">resource_started_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.resource_started_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Link.set_resource_started_callback">
<span class="sig-name descname"><span class="pre">set_resource_started_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_resource_started_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a resource has begun
transferring over this link.</p>
<dl class="field-list simple">
@@ -808,8 +920,8 @@ transferring over this link.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.resource_concluded_callback">
<span class="sig-name descname"><span class="pre">resource_concluded_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.resource_concluded_callback" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Link.set_resource_concluded_callback">
<span class="sig-name descname"><span class="pre">set_resource_concluded_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_resource_concluded_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when a resource has concluded
transferring over this link.</p>
<dl class="field-list simple">
@@ -819,6 +931,18 @@ transferring over this link.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.set_remote_identified_callback">
<span class="sig-name descname"><span class="pre">set_remote_identified_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_remote_identified_callback" title="Permalink to this definition"></a></dt>
<dd><p>Registers a function to be called when an initiating peer has
identified over this link.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>callback</strong> A function or method with the signature <em>callback(identity)</em> to be called.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.set_resource_strategy">
<span class="sig-name descname"><span class="pre">set_resource_strategy</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">resource_strategy</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_resource_strategy" title="Permalink to this definition"></a></dt>
@@ -833,16 +957,65 @@ transferring over this link.</p>
</dl>
</dd></dl>
</dd></dl>
</div>
<div class="section" id="request-receipt">
<span id="api-requestreceipt"></span><h3>Request Receipt<a class="headerlink" href="#request-receipt" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.RequestReceipt">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">RequestReceipt</span></span><a class="headerlink" href="#RNS.RequestReceipt" title="Permalink to this definition"></a></dt>
<dd><p>An instance of this class is returned by the <code class="docutils literal notranslate"><span class="pre">request</span></code> method of <code class="docutils literal notranslate"><span class="pre">RNS.Link</span></code>
instances. It should never be instantiated manually. It provides methods to
check status, response time and response data when the request concludes.</p>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Link.disable_encryption">
<span class="sig-name descname"><span class="pre">disable_encryption</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.disable_encryption" title="Permalink to this definition"></a></dt>
<dd><p>HAZARDOUS. This will downgrade the link to encryptionless. All
information over the link will be sent in plaintext. Never use
this in production applications. Should only be used for debugging
purposes, and will disappear in a future version.</p>
<p>If encryptionless links are not explicitly allowed in the users
configuration file, Reticulum will terminate itself along with the
client application and throw an error message to the user.</p>
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_request_id">
<span class="sig-name descname"><span class="pre">get_request_id</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_request_id" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The request ID as <em>bytes</em>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_status">
<span class="sig-name descname"><span class="pre">get_status</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_status" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The current status of the request, one of <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.FAILED</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.SENT</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.DELIVERED</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.RequestReceipt.READY</span></code>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_progress">
<span class="sig-name descname"><span class="pre">get_progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_progress" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The progress of a response being received as a <em>float</em> between 0.0 and 1.0.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_response">
<span class="sig-name descname"><span class="pre">get_response</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_response" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The response as <em>bytes</em> if it is ready, otherwise <em>None</em>.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.RequestReceipt.get_response_time">
<span class="sig-name descname"><span class="pre">get_response_time</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RequestReceipt.get_response_time" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The response time of the request in seconds.</p>
</dd>
</dl>
</dd></dl>
</dd></dl>
@@ -852,7 +1025,7 @@ client application and throw an error message to the user.</p>
<span id="api-resource"></span><h3>Resource<a class="headerlink" href="#resource" title="Permalink to this headline"></a></h3>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Resource">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Resource</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">link</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">advertise</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">auto_compress</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">must_compress</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">progress_callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">segment_index</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">original_hash</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource" title="Permalink to this definition"></a></dt>
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Resource</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">data</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">link</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">advertise</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">auto_compress</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">True</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">progress_callback</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">timeout</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource" title="Permalink to this definition"></a></dt>
<dd><p>The Resource class allows transferring arbitrary amounts
of data over a link. It will automatically handle sequencing,
compression, coordination and checksumming.</p>
@@ -861,13 +1034,10 @@ compression, coordination and checksumming.</p>
<dd class="field-odd"><ul class="simple">
<li><p><strong>data</strong> The data to be transferred. Can be <em>bytes</em> or an open <em>file handle</em>. See the <a class="reference internal" href="examples.html#example-filetransfer"><span class="std std-ref">Filetransfer Example</span></a> for details.</p></li>
<li><p><strong>link</strong> The <a class="reference internal" href="#api-link"><span class="std std-ref">RNS.Link</span></a> instance on which to transfer the data.</p></li>
<li><p><strong>advertise</strong> Whether to automatically advertise the resource. Can be <em>True</em> or <em>False</em>.</p></li>
<li><p><strong>auto_compress</strong> Whether to auto-compress the resource. Can be <em>True</em> or <em>False</em>.</p></li>
<li><p><strong>auto_compress</strong> Whether the resource must be compressed. Can be <em>True</em> or <em>False</em>. Used for debugging, will disappear in the future.</p></li>
<li><p><strong>callback</strong> A <em>callable</em> with the signature <em>callback(resource)</em>. Will be called when the resource transfer concludes.</p></li>
<li><p><strong>progress_callback</strong> A <em>callable</em> with the signature <em>callback(resource)</em>. Will be called whenever the resource transfer progress is updated.</p></li>
<li><p><strong>segment_index</strong> Internal use, ignore.</p></li>
<li><p><strong>original_hash</strong> Internal use, ignore.</p></li>
<li><p><strong>advertise</strong> Optional. Whether to automatically advertise the resource. Can be <em>True</em> or <em>False</em>.</p></li>
<li><p><strong>auto_compress</strong> Optional. Whether to auto-compress the resource. Can be <em>True</em> or <em>False</em>.</p></li>
<li><p><strong>callback</strong> An optional <em>callable</em> with the signature <em>callback(resource)</em>. Will be called when the resource transfer concludes.</p></li>
<li><p><strong>progress_callback</strong> An optional <em>callable</em> with the signature <em>callback(resource)</em>. Will be called whenever the resource transfer progress is updated.</p></li>
</ul>
</dd>
</dl>
@@ -885,8 +1055,8 @@ the resource advertisement it will begin transferring.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Resource.progress">
<span class="sig-name descname"><span class="pre">progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.progress" title="Permalink to this definition"></a></dt>
<dt class="sig sig-object py" id="RNS.Resource.get_progress">
<span class="sig-name descname"><span class="pre">get_progress</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Resource.get_progress" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Returns</dt>
<dd class="field-odd"><p>The current progress of the resource transfer as a <em>float</em> between 0.0 and 1.0.</p>
@@ -902,7 +1072,15 @@ the resource advertisement it will begin transferring.</p>
<dl class="py class">
<dt class="sig sig-object py" id="RNS.Transport">
<em class="property"><span class="pre">class</span> </em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Transport</span></span><a class="headerlink" href="#RNS.Transport" title="Permalink to this definition"></a></dt>
<dd><dl class="py method">
<dd><p>Through static methods of this class you can interact with Reticulums
Transport system.</p>
<dl class="py attribute">
<dt class="sig sig-object py" id="RNS.Transport.PATHFINDER_M">
<span class="sig-name descname"><span class="pre">PATHFINDER_M</span></span><em class="property"> <span class="pre">=</span> <span class="pre">128</span></em><a class="headerlink" href="#RNS.Transport.PATHFINDER_M" title="Permalink to this definition"></a></dt>
<dd><p>Maximum amount of hops that Reticulum will transport a packet.</p>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.register_announce_handler">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">register_announce_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">handler</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.register_announce_handler" title="Permalink to this definition"></a></dt>
<dd><p>Registers an announce handler.</p>
@@ -937,6 +1115,19 @@ the resource advertisement it will begin transferring.</p>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.hops_to">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">hops_to</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.hops_to" title="Permalink to this definition"></a></dt>
<dd><dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><p><strong>destination_hash</strong> A destination hash as <em>bytes</em>.</p>
</dd>
<dt class="field-even">Returns</dt>
<dd class="field-even"><p>The number of hops to the specified destination, or <code class="docutils literal notranslate"><span class="pre">RNS.Transport.PATHFINDER_M</span></code> if the number of hops is unknown.</p>
</dd>
</dl>
</dd></dl>
<dl class="py method">
<dt class="sig sig-object py" id="RNS.Transport.request_path">
<em class="property"><span class="pre">static</span> </em><span class="sig-name descname"><span class="pre">request_path</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Transport.request_path" title="Permalink to this definition"></a></dt>
@@ -973,6 +1164,7 @@ will announce it.</p>
<li><a class="reference internal" href="#packet">Packet</a></li>
<li><a class="reference internal" href="#packet-receipt">Packet Receipt</a></li>
<li><a class="reference internal" href="#link">Link</a></li>
<li><a class="reference internal" href="#request-receipt">Request Receipt</a></li>
<li><a class="reference internal" href="#resource">Resource</a></li>
<li><a class="reference internal" href="#transport">Transport</a></li>
</ul>
@@ -982,11 +1174,11 @@ will announce it.</p>
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="examples.html"
title="previous chapter">Examples</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="understanding.html"
title="next chapter">Understanding Reticulum</a></p>
title="previous chapter">Understanding Reticulum</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="examples.html"
title="next chapter">Examples</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -1015,12 +1207,12 @@ will announce it.</p>
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="right" >
<a href="understanding.html" title="Understanding Reticulum"
<a href="examples.html" title="Examples"
>next</a> |</li>
<li class="right" >
<a href="examples.html" title="Examples"
<a href="understanding.html" title="Understanding Reticulum"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
</ul>
</div>
+3 -3
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Search &#8212; Reticulum Network Stack 0.2.0 beta documentation</title>
<title>Search &#8212; Reticulum Network Stack 0.2.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -29,7 +29,7 @@
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
accesskey="I">index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Search</a></li>
</ul>
</div>
@@ -85,7 +85,7 @@
<li class="right" style="margin-right: 10px">
<a href="genindex.html" title="General Index"
>index</a></li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Search</a></li>
</ul>
</div>
File diff suppressed because one or more lines are too long
+170 -109
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.2.0 beta documentation</title>
<title>Understanding Reticulum &#8212; Reticulum Network Stack 0.2.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -16,7 +16,8 @@
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="prev" title="API Reference" href="reference.html" />
<link rel="next" title="API Reference" href="reference.html" />
<link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
</head><body>
<div class="related" role="navigation" aria-label="related navigation">
<h3>Navigation</h3>
@@ -26,8 +27,11 @@
accesskey="I">index</a></li>
<li class="right" >
<a href="reference.html" title="API Reference"
accesskey="N">next</a> |</li>
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
@@ -201,12 +205,14 @@ when a node is directly reachable.</p>
</ul>
<div class="section" id="destination-naming">
<span id="understanding-destinationnaming"></span><h4>Destination Naming<a class="headerlink" href="#destination-naming" title="Permalink to this headline"></a></h4>
<p>Destinations are created and named in an easy to understand dotted notation of <em>aspects</em> , and
<p>Destinations are created and named in an easy to understand dotted notation of <em>aspects</em>, and
represented on the network as a hash of this value. The hash is a SHA-256 truncated to 80 bits. The
top level aspect should always be a unique identifier for the application using the destination.
The next levels of aspects can be defined in any way by the creator of the application. For example,
a destination for a environmental monitoring application could be made up of the application name, a
device type and measurement type, like this:</p>
The next levels of aspects can be defined in any way by the creator of the application.</p>
<p>Aspects can be as long and as plentiful as required, and a resulting long destination name will not
impact efficiency, as names are always represented as truncated SHA-256 hashes on the network.</p>
<p>As an example, a destination for a environmental monitoring application could be made up of the
application name, a device type and measurement type, like this:</p>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>app name : environmentlogger
aspects : remotesensor, temperature
@@ -242,9 +248,8 @@ receives.</p>
</dl>
</li>
<li><dl class="simple">
<dt><strong>Group</strong></dt><dd><p>When private communication between two or more endpoints is needed. More efficient in
data usage than <em>single</em> destinations. Supports multiple hops indirectly, but must first be
established through a <em>single</em> destination.</p>
<dt><strong>Group</strong></dt><dd><p>When private communication between two or more endpoints is needed. Supports multiple hops
indirectly, but must first be established through a <em>single</em> destination.</p>
</dd>
</dl>
</li>
@@ -260,9 +265,9 @@ nodes aware of your destinations public key, called the <em>announce</em>. It is
an unknown public key from the network, as all participating nodes serve as a distributed ledger
of public keys.</p>
<p>Note that public key information can be shared and verified in many other ways than using the
built-in methodology, and that it is therefore not required to use the announce/request functionality.
It is by far the easiest though, and should definitely be used if there is not a good reason for
doing it differently.</p>
built-in <em>announce</em> functionality, and that it is therefore not required to use the announce/request
functionality to obtain public keys. It is by far the easiest though, and should definitely be used
if there is not a good reason for doing it differently.</p>
</div>
</div>
<div class="section" id="public-key-announcements">
@@ -278,7 +283,7 @@ contain the following information:</p>
<li><p>The announcers public key</p></li>
<li><p>Application specific data, in this case the users nickname and availability status</p></li>
<li><p>A random blob, making each new announce unique</p></li>
<li><p>A signature of the above information, verifying authenticity</p></li>
<li><p>An Ed25519 signature of the above information, verifying authenticity</p></li>
</ul>
<p>With this information, any Reticulum node that receives it will be able to reconstruct an outgoing
destination to securely communicate with that destination. You might have noticed that there is one
@@ -286,8 +291,9 @@ piece of information lacking to reconstruct full knowledge of the announced dest
the aspect names of the destination. These are intentionally left out to save bandwidth, since they
will be implicit in almost all cases. If a destination name is not entirely implicit, information can be
included in the application specific data part that will allow the receiver to infer the naming.</p>
<p>It is important to note that announcements will be forwarded throughout the network according to a
certain pattern. This will be detailed later.</p>
<p>It is important to note that announces will be forwarded throughout the network according to a
certain pattern. This will be detailed in the section
<a class="reference internal" href="#understanding-announce"><span class="std std-ref">The Announce Mechanism in Detail</span></a>.</p>
<p>Seeing how <em>single</em> destinations are always tied to a private/public key pair leads us to the next topic.</p>
</div>
<div class="section" id="understanding-identities">
@@ -304,16 +310,16 @@ automatically. This may be desirable in some situations, but often you will prob
the identity first, and then link it to created destinations.</p>
<p>Building upon the simple messenger example, we could use an identity to represent the user of the
application. Destinations created will then be linked to this identity to allow communication to
reach the user. In such a case it is of great importance to store the users identity securely and
privately.</p>
reach the user. In all cases it is of great importance to store the private keys associated with any
Reticulum Identity securely and privately.</p>
</div>
<div class="section" id="getting-further">
<span id="understanding-gettingfurther"></span><h3>Getting Further<a class="headerlink" href="#getting-further" title="Permalink to this headline"></a></h3>
<p>The above functions and principles form the core of Reticulum, and would suffice to create
functional networked applications in local clusters, for example over radio links where all interested
nodes can directly hear each other. But to be truly useful, we need a way to direct traffic over multiple
hops in the network. In the next sections, two concepts that allow this will be introduced, <em>paths</em> and
<em>links</em>.</p>
hops in the network.</p>
<p>In the following sections, two concepts that allow this will be introduced, <em>paths</em> and <em>links</em>.</p>
</div>
</div>
<div class="section" id="reticulum-transport">
@@ -327,69 +333,20 @@ very limited. Existing routing protocols like BGP or OSPF carry too much overhea
useable over bandwidth-limited, high-latency links.</p>
<p>To overcome such challenges, Reticulums <em>Transport</em> system uses public-key cryptography to
implement the concept of <em>paths</em> that allow discovery of how to get information to a certain
destination, and <em>resources</em> that help make reliable data transfer more efficient.</p>
<div class="section" id="reaching-the-destination">
<span id="understanding-paths"></span><h3>Reaching the Destination<a class="headerlink" href="#reaching-the-destination" title="Permalink to this headline"></a></h3>
<p>In networks with changing topology and trustless connectivity, nodes need a way to establish
<em>verified connectivity</em> with each other. Since the network is assumed to be trustless, Reticulum
must provide a way to guarantee that the peer you are communicating with is actually who you
expect. To do this, the following process is employed:</p>
destination. It is important to note that no single node in a Reticulum network knows the complete
path to a destination. Every Transport node participating in a Reticulum network will only
know what the most direct way to get a packet one hop closer to its destination is.</p>
<div class="section" id="the-announce-mechanism-in-detail">
<span id="understanding-announce"></span><h3>The Announce Mechanism in Detail<a class="headerlink" href="#the-announce-mechanism-in-detail" title="Permalink to this headline"></a></h3>
<p>When an <em>announce</em> is transmitted by a node, it will be forwarded by any node receiving it, but
according to some specific rules:</p>
<ul>
<li><div class="line-block">
<div class="line">First, the node that wishes to establish connectivity will send out a special packet, that
traverses the network and locates the desired destination. Along the way, the nodes that
forward the packet will take note of this <em>link request</em>.</div>
<div class="line">If this exact announce has already been received before, ignore it.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">Second, if the destination accepts the <em>link request</em> , it will send back a packet that proves the
authenticity of its identity (and the receipt of the link request) to the initiating node. All
nodes that initially forwarded the packet will also be able to verify this proof, and thus
accept the validity of the <em>link</em> throughout the network.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">When the validity of the <em>link</em> has been accepted by forwarding nodes, these nodes will
remember the <em>link</em> , and it can subsequently be used by referring to a hash representing it.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">As a part of the <em>link request</em> , a Diffie-Hellman key exchange takes place, that sets up an
efficient symmetrically encrypted tunnel between the two nodes, using elliptic curve
cryptography. As such, this mode of communication is preferred, even for situations when
nodes can directly communicate, when the amount of data to be exchanged numbers in the
tens of packets.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">When a <em>link</em> has been set up, it automatically provides message receipt functionality, so the
sending node can obtain verified confirmation that the information reached the intended
recipient.</div>
</div>
</li>
</ul>
<p>In a moment, we will discuss the specifics of how this methodology is implemented, but lets first
recap what purposes this serves. We first ensure that the node answering our request is actually the
one we want to communicate with, and not a malicious actor pretending to be so. At the same time
we establish an efficient encrypted channel. The setup of this is relatively cheap in terms of
bandwidth, so it can be used just for a short exchange, and then recreated as needed, which will also
rotate encryption keys, but the link can also be kept alive for longer periods of time, if this is
more suitable to the application. The amount of bandwidth used on keeping a link open is practically
negligible. The procedure also inserts the <em>link id</em> , a hash calculated from the link request packet,
into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each
other simply by referring to this <em>link id</em>.</p>
<div class="section" id="step-1-pathfinding">
<h4>Step 1: Pathfinding<a class="headerlink" href="#step-1-pathfinding" title="Permalink to this headline"></a></h4>
<p>The pathfinding method builds on the <em>announce</em> functionality discussed earlier. When an announce
is sent out by a node, it will be forwarded by any node receiving it, but according to some specific
rules:</p>
<ul>
<li><div class="line-block">
<div class="line">If this announce has already been received before, ignore it.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">Record into a table which node the announce was received from, and how many times in
<div class="line">If not, record into a table which node the announce was received from, and how many times in
total it has been retransmitted to get here.</div>
</div>
</li>
@@ -399,8 +356,7 @@ set to 18.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">The announce will be assigned a delay <em>d</em> = c<sup>h</sup> seconds, where <em>c</em> is a decay constant, by
default 2, and <em>h</em> is the amount of times this packet has already been forwarded.</div>
<div class="line">The announce will be assigned a delay <em>d</em> = c<sup>h</sup> seconds, where <em>c</em> is a decay constant, and <em>h</em> is the amount of times this packet has already been forwarded.</div>
</div>
</li>
<li><div class="line-block">
@@ -415,10 +371,11 @@ not utilized by other traffic, the announce will be forwarded.</div>
</li>
<li><div class="line-block">
<div class="line">If no other nodes are heard retransmitting the announce with a greater hop count than when
it left this node, transmitting it will be retried <em>r</em> times. By default, <em>r</em> is set to 2. Retries follow
same rules as above, with the exception that it must wait for at least <em>d</em> = c<sup>h+1</sup> + t seconds, ie.,
the amount of time it would take the next node to retransmit the packet. By default, <em>t</em> is set to
10.</div>
it left this node, transmitting it will be retried <em>r</em> times. By default, <em>r</em> is set to 1. Retries
follow same rules as above, with the exception that it must wait for at least <em>d</em> = c<sup>h+1</sup> +
t + rand(0, rw) seconds. This amount of time is equal to the amount of time it would take the next
node to retransmit the packet, plus a random window. By default, <em>t</em> is set to 10 seconds, and the
random window <em>rw</em> is set to 10 seconds.</div>
</div>
</li>
<li><div class="line-block">
@@ -440,13 +397,111 @@ distance of <em>Lavg =</em> 15 kilometers, an announce will be able to propagate
kilometers in 34 minutes, and a <em>maximum announce radius</em> of 270 kilometers in approximately 3
days.</p>
</div>
<div class="section" id="step-2-link-establishment">
<h4>Step 2: Link Establishment<a class="headerlink" href="#step-2-link-establishment" title="Permalink to this headline"></a></h4>
<p>After seeing how the conditions for finding a path through the network are created, we will now
explore how two nodes can establish reliable communications over multiple hops. The <em>link</em> in
Reticulum terminology should not be viewed as a direct node-to-node link on the physical layer, but
as an abstract channel, that can be open for any amount of time, and can span an arbitrary number
of hops, where information will be exchanged between two nodes.</p>
<div class="section" id="reaching-the-destination">
<span id="understanding-paths"></span><h3>Reaching the Destination<a class="headerlink" href="#reaching-the-destination" title="Permalink to this headline"></a></h3>
<p>In networks with changing topology and trustless connectivity, nodes need a way to establish
<em>verified connectivity</em> with each other. Since the network is assumed to be trustless, Reticulum
must provide a way to guarantee that the peer you are communicating with is actually who you
expect. Reticulum offers two ways to do this.</p>
<p>For exchanges of small amounts of information, Reticulum offers the <em>Packet</em> API, which works exactly like you would expect - on a per packet level. The following process is employed when sending a packet:</p>
<ul>
<li><div class="line-block">
<div class="line">A packet is always created with an associated destination and some payload data. When the packet is sent
to a <em>single</em> destination type, Reticulum will automatically create an ephemeral encryption key, perform
an ECDH key exchange with the destinations public key, and encrypt the information.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">It is important to note that this key exchange does not require any network traffic. The sender already
knows the public key of the destination from an earlier received <em>announce</em>, and can thus perform the ECDH
key exchange locally, before sending the packet.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">The public part of the newly generated ephemeral key-pair is included with the encrypted token, and sent
along with the encrypted payload data in the packet.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">When the destination receives the packet, it can itself perform an ECDH key exchange and decrypt the
packet.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">A new ephemeral key is used for every packet sent in this way, and forward secrecy is guaranteed on a
per packet level.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">Once the packet has been received and decrypted by the addressed destination, that destination can opt
to <em>prove</em> its receipt of the packet. It does this by calculating the SHA-256 hash of the received packet,
and signing this hash with its Ed25519 signing key. Transport nodes in the network can then direct this
<em>proof</em> back to the packets origin, where the signature can be verified against the destinations known
public signing key.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">In case the packet is addressed to a <em>group</em> destination type, the packet will be encrypted with the
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a <em>plain</em>
destination type, the payload data will not be encrypted. Neither of these two destination types offer
forward secrecy. In general, it is recommended to always use the <em>single</em> destination type, unless it is
strictly necessary to use one of the others.</div>
</div>
</li>
</ul>
<p>For exchanges of larger amounts of data, or when longer sessions of bidirectional communication is desired, Reticulum offers the <em>Link</em> API. To establish a <em>link</em>, the following process is employed:</p>
<ul>
<li><div class="line-block">
<div class="line">First, the node that wishes to establish a link will send out a special packet, that
traverses the network and locates the desired destination. Along the way, the nodes that
forward the packet will take note of this <em>link request</em>.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">Second, if the destination accepts the <em>link request</em> , it will send back a packet that proves the
authenticity of its identity (and the receipt of the link request) to the initiating node. All
nodes that initially forwarded the packet will also be able to verify this proof, and thus
accept the validity of the <em>link</em> throughout the network.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">When the validity of the <em>link</em> has been accepted by forwarding nodes, these nodes will
remember the <em>link</em> , and it can subsequently be used by referring to a hash representing it.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">As a part of the <em>link request</em> , a Diffie-Hellman key exchange takes place, that sets up an
efficiently encrypted tunnel between the two nodes, using elliptic curve cryptography. As such,
this mode of communication is preferred, even for situations when nodes can directly communicate,
when the amount of data to be exchanged numbers in the tens of packets.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">When a <em>link</em> has been set up, it automatically provides message receipt functionality, through
the same <em>proof</em> mechanism discussed before, so the sending node can obtain verified confirmation
that the information reached the intended recipient.</div>
</div>
</li>
</ul>
<p>In a moment, we will discuss the details of how this methodology is implemented, but lets first
recap what purposes this methodology serves. We first ensure that the node answering our request
is actually the one we want to communicate with, and not a malicious actor pretending to be so.
At the same time we establish an efficient encrypted channel. The setup of this is relatively cheap in
terms of bandwidth, so it can be used just for a short exchange, and then recreated as needed, which will
also rotate encryption keys. The link can also be kept alive for longer periods of time, if this is
more suitable to the application. The procedure also inserts the <em>link id</em> , a hash calculated from the link request packet, into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each other simply by referring to this <em>link id</em>.</p>
<p>The combined bandwidth cost of setting up a link is 3 packets totalling 237 bytes (more info in the
<a class="reference internal" href="#understanding-packetformat"><span class="std std-ref">Binary Packet Format</span></a> section). The amount of bandwidth used on keeping
a link open is practically negligible, at 0.62 bits per second. Even on a slow 1200 bits per second packet
radio channel, 100 concurrent links will still leave 95% channel capacity for actual data.</p>
<div class="section" id="link-establishment-in-detail">
<h4>Link Establishment in Detail<a class="headerlink" href="#link-establishment-in-detail" title="Permalink to this headline"></a></h4>
<p>After exploring the basics of the announce mechanism, finding a path through the network, and an overview
of the link establishment procedure, this section will go into greater detail about the Reticulum link
establishment process.</p>
<p>The <em>link</em> in Reticulum terminology should not be viewed as a direct node-to-node link on the
physical layer, but as an abstract channel, that can be open for any amount of time, and can span
an arbitrary number of hops, where information will be exchanged between two nodes.</p>
<ul>
<li><div class="line-block">
<div class="line">When a node in the network wants to establish verified connectivity with another node, it
@@ -461,8 +516,7 @@ considered as single public key for simplicity in this explanation.</em></div>
</li>
<li><div class="line-block">
<div class="line">The <em>link request</em> is addressed to the destination hash of the desired destination, and
contains the following data: The newly generated X25519 public key <em>LKi</em>. The contents
are encrypted with the RSA public key of the destination and tramsitted over the network.</div>
contains the following data: The newly generated X25519 public key <em>LKi</em>.</div>
</div>
</li>
<li><div class="line-block">
@@ -473,21 +527,22 @@ previously.</div>
<li><div class="line-block">
<div class="line">Any node that forwards the link request will store a <em>link id</em> in its <em>link table</em> , along with the
amount of hops the packet had taken when received. The link id is a hash of the entire link
request packet. If the path is not <em>proven</em> within some set amount of time, the entry will be
dropped from the <em>link table</em> again.</div>
request packet. If the link request packet is not <em>proven</em> by the addressed destination within some
set amount of time, the entry will be dropped from the <em>link table</em> again.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">When the destination receives the link request packet, it will decrypt it and decide whether to
accept the request. If it is accepted, the destination will also generate a new X25519 private/public
key pair, and perform a Diffie Hellman Key Exchange, deriving a new symmetric key that will be used
to encrypt the channel, once it has been established.</div>
<div class="line">When the destination receives the link request packet, it will decide whether to accept the request.
If it is accepted, the destination will also generate a new X25519 private/public key pair, and
perform a Diffie Hellman Key Exchange, deriving a new symmetric key that will be used to encrypt the
channel, once it has been established.</div>
</div>
</li>
<li><div class="line-block">
<div class="line">A <em>link proof</em> packet is now constructed and transmitted over the network. This packet is
addressed to the <em>link id</em> of the <em>link</em>. It contains the following data: The newly generated X25519
public key <em>LKr</em> and an RSA-1024 signature of the <em>link id</em> and <em>LKr</em>.</div>
public key <em>LKr</em> and an Ed25519 signature of the <em>link id</em> and <em>LKr</em> made by the signing key of
the addressed destination.</div>
</div>
</li>
<li><div class="line-block">
@@ -615,7 +670,7 @@ the light of Reticulums goal of equal access, doing so would need to be the subj
investigation of the consequences first.</p>
</div>
<div class="section" id="binary-packet-format">
<h3>Binary Packet Format<a class="headerlink" href="#binary-packet-format" title="Permalink to this headline"></a></h3>
<span id="understanding-packetformat"></span><h3>Binary Packet Format<a class="headerlink" href="#binary-packet-format" title="Permalink to this headline"></a></h3>
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>== Reticulum Wire Format ======
A Reticulum packet is composed of the following fields:
@@ -706,10 +761,10 @@ proof 11
wire size including all fields.
- Path Request : 33 bytes
- Announce : 323 bytes
- Link Request : 141 bytes
- Link Proof : 205 bytes
- Link RTT packet : 86 bytes
- Announce : 151 bytes
- Link Request : 77 bytes
- Link Proof : 77 bytes
- Link RTT packet : 83 bytes
- Link keepalive : 14 bytes
</pre></div>
</div>
@@ -740,9 +795,9 @@ proof 11
</ul>
</li>
<li><a class="reference internal" href="#reticulum-transport">Reticulum Transport</a><ul>
<li><a class="reference internal" href="#the-announce-mechanism-in-detail">The Announce Mechanism in Detail</a></li>
<li><a class="reference internal" href="#reaching-the-destination">Reaching the Destination</a><ul>
<li><a class="reference internal" href="#step-1-pathfinding">Step 1: Pathfinding</a></li>
<li><a class="reference internal" href="#step-2-link-establishment">Step 2: Link Establishment</a></li>
<li><a class="reference internal" href="#link-establishment-in-detail">Link Establishment in Detail</a></li>
</ul>
</li>
<li><a class="reference internal" href="#resources">Resources</a></li>
@@ -760,8 +815,11 @@ proof 11
</ul>
<h4>Previous topic</h4>
<p class="topless"><a href="gettingstartedfast.html"
title="previous chapter">Getting Started Fast</a></p>
<h4>Next topic</h4>
<p class="topless"><a href="reference.html"
title="previous chapter">API Reference</a></p>
title="next chapter">API Reference</a></p>
<div role="note" aria-label="source link">
<h3>This Page</h3>
<ul class="this-page-menu">
@@ -791,8 +849,11 @@ proof 11
>index</a></li>
<li class="right" >
<a href="reference.html" title="API Reference"
>next</a> |</li>
<li class="right" >
<a href="gettingstartedfast.html" title="Getting Started Fast"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
</ul>
</div>
+17 -9
View File
@@ -5,7 +5,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>What is Reticulum? &#8212; Reticulum Network Stack 0.2.0 beta documentation</title>
<title>What is Reticulum? &#8212; Reticulum Network Stack 0.2.4 beta documentation</title>
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
<link rel="stylesheet" type="text/css" href="_static/classic.css" />
@@ -31,7 +31,7 @@
<li class="right" >
<a href="index.html" title="Reticulum Network Stack Manual"
accesskey="P">previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
@@ -43,7 +43,8 @@
<div class="section" id="what-is-reticulum">
<h1>What is Reticulum?<a class="headerlink" href="#what-is-reticulum" title="Permalink to this headline"></a></h1>
<p>Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.</p>
<p>Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth.</p>
<p>Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.</p>
<p>Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.</p>
<p>No kernel modules or drivers are required. Reticulum runs completely in userland, and can run on practically any system that runs Python 3.</p>
<div class="section" id="current-status">
@@ -59,18 +60,19 @@
<ul class="simple">
<li><p>Coordination-less globally unique adressing and identification</p></li>
<li><p>Fully self-configuring multi-hop routing</p></li>
<li><p>Asymmetric RSA encryption and signatures as basis for all communication</p></li>
<li><p>Perfect Forward Secrecy on links with ephemereal Elliptic Curve Diffie-Hellman keys (on Curve25519)</p></li>
<li><p>Reticulum uses the Fernet specification for encryption on links and to group destinations</p>
<li><p>Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication</p></li>
<li><p>Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519</p></li>
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for encryption</p>
<ul>
<li><p>AES-128 in CBC mode with PKCS7 padding</p></li>
<li><p>HMAC using SHA256 for authentication</p></li>
<li><p>IVs are generated through os.urandom()</p></li>
<li><p>Keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
</ul>
</li>
<li><p>Unforgeable packet delivery confirmations</p></li>
<li><p>A variety of supported interface types</p></li>
<li><p>An intuitive and easy-to-use API</p></li>
<li><p>An intuitive and developer-friendly API</p></li>
<li><p>Reliable and efficient transfer of arbritrary amounts of data</p>
<ul>
<li><p>Reticulum can handle a few bytes of data or files of many gigabytes</p></li>
@@ -78,11 +80,17 @@
<li><p>The API is very easy to use, and provides transfer progress</p></li>
</ul>
</li>
<li><p>Efficient link establishment</p>
<ul>
<li><p>Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes</p></li>
<li><p>Low cost of keeping links open at only 0.62 bits per second</p></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="where-can-reticulum-be-used">
<h2>Where can Reticulum be Used?<a class="headerlink" href="#where-can-reticulum-be-used" title="Permalink to this headline"></a></h2>
<p>On practically any hardware that can support at least a half-duplex channel
<p>Over practically any medium that can support at least a half-duplex channel
with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios,
modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes,
ad-hoc WiFi, free-space optical links and similar systems are all examples
@@ -174,7 +182,7 @@ network, and vice versa.</p>
<li class="right" >
<a href="index.html" title="Reticulum Network Stack Manual"
>previous</a> |</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.0 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.4 beta documentation</a> &#187;</li>
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
</ul>
</div>
+1 -1
View File
@@ -22,7 +22,7 @@ copyright = '2021, Mark Qvist'
author = 'Mark Qvist'
# The full version, including alpha/beta/rc tags
release = '0.2.0 beta'
release = '0.2.4 beta'
# -- General configuration ---------------------------------------------------
+23
View File
@@ -68,6 +68,29 @@ destination, and passing traffic back and forth over the link.
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Link.py>`_.
.. _example-identify:
Identification
==============
The *Identify* example explores identifying an intiator of a link, once
the link has been established.
.. literalinclude:: ../../Examples/Identify.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Identify.py>`_.
.. _example-request:
Requests & Responses
====================
The *Request* example explores sendig requests and receiving responses.
.. literalinclude:: ../../Examples/Request.py
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py>`_.
.. _example-filetransfer:
Filetransfer
+1 -2
View File
@@ -1,7 +1,6 @@
********************
Getting Started Fast
********************
What do we want to do? Something! When do we want to do it? Right now! Let's go.
The best way to get started with the Reticulum Network Stack depends on what
you want to do. This guide will outline sensible starting paths for different
@@ -23,7 +22,7 @@ in the development for the messaging and information-sharing protocol
Develop a Program with Reticulum
===========================================
If you want to develop programs that use Reticulum, the easiest way to get
started is to install Reticulum via pip:
started is to install the latest release of Reticulum via pip:
.. code::
+2 -2
View File
@@ -10,9 +10,9 @@ the development of Reticulum itself.
whatis
gettingstartedfast
examples
reference
understanding
reference
examples
Indices and Tables
+12 -4
View File
@@ -39,7 +39,7 @@ Destination
Packet
------
.. autoclass:: RNS.Packet
.. autoclass:: RNS.Packet(destination, data, create_receipt = True)
:members:
.. _api-packetreceipt:
@@ -47,7 +47,7 @@ Packet
Packet Receipt
--------------
.. autoclass:: RNS.PacketReceipt
.. autoclass:: RNS.PacketReceipt()
:members:
.. _api-link:
@@ -55,7 +55,15 @@ Packet Receipt
Link
----
.. autoclass:: RNS.Link
.. autoclass:: RNS.Link(destination, established_callback=None, closed_callback = None)
:members:
.. _api-requestreceipt:
Request Receipt
---------------
.. autoclass:: RNS.RequestReceipt()
:members:
.. _api-resource:
@@ -63,7 +71,7 @@ Link
Resource
--------
.. autoclass:: RNS.Resource
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
:members:
.. _api-transport:
+141 -94
View File
@@ -157,12 +157,16 @@ destinations. Reticulum uses three different basic destination types, and one sp
Destination Naming
^^^^^^^^^^^^^^^^^^
Destinations are created and named in an easy to understand dotted notation of *aspects* , and
Destinations are created and named in an easy to understand dotted notation of *aspects*, and
represented on the network as a hash of this value. The hash is a SHA-256 truncated to 80 bits. The
top level aspect should always be a unique identifier for the application using the destination.
The next levels of aspects can be defined in any way by the creator of the application. For example,
a destination for a environmental monitoring application could be made up of the application name, a
device type and measurement type, like this:
The next levels of aspects can be defined in any way by the creator of the application.
Aspects can be as long and as plentiful as required, and a resulting long destination name will not
impact efficiency, as names are always represented as truncated SHA-256 hashes on the network.
As an example, a destination for a environmental monitoring application could be made up of the
application name, a device type and measurement type, like this:
.. code-block:: text
@@ -201,9 +205,8 @@ To recap, the different destination types should be used in the following situat
* **Single**
When private communication between two endpoints is needed. Supports multiple hops.
* **Group**
When private communication between two or more endpoints is needed. More efficient in
data usage than *single* destinations. Supports multiple hops indirectly, but must first be
established through a *single* destination.
When private communication between two or more endpoints is needed. Supports multiple hops
indirectly, but must first be established through a *single* destination.
* **Plain**
When plain-text communication is desirable, for example when broadcasting information.
@@ -214,9 +217,9 @@ an unknown public key from the network, as all participating nodes serve as a di
of public keys.
Note that public key information can be shared and verified in many other ways than using the
built-in methodology, and that it is therefore not required to use the announce/request functionality.
It is by far the easiest though, and should definitely be used if there is not a good reason for
doing it differently.
built-in *announce* functionality, and that it is therefore not required to use the announce/request
functionality to obtain public keys. It is by far the easiest though, and should definitely be used
if there is not a good reason for doing it differently.
.. _understanding-keyannouncements:
@@ -235,7 +238,7 @@ contain the following information:
* The announcers public key
* Application specific data, in this case the users nickname and availability status
* A random blob, making each new announce unique
* A signature of the above information, verifying authenticity
* An Ed25519 signature of the above information, verifying authenticity
With this information, any Reticulum node that receives it will be able to reconstruct an outgoing
destination to securely communicate with that destination. You might have noticed that there is one
@@ -244,8 +247,9 @@ the aspect names of the destination. These are intentionally left out to save ba
will be implicit in almost all cases. If a destination name is not entirely implicit, information can be
included in the application specific data part that will allow the receiver to infer the naming.
It is important to note that announcements will be forwarded throughout the network according to a
certain pattern. This will be detailed later.
It is important to note that announces will be forwarded throughout the network according to a
certain pattern. This will be detailed in the section
:ref:`The Announce Mechanism in Detail<understanding-announce>`.
Seeing how *single* destinations are always tied to a private/public key pair leads us to the next topic.
@@ -268,8 +272,8 @@ the identity first, and then link it to created destinations.
Building upon the simple messenger example, we could use an identity to represent the user of the
application. Destinations created will then be linked to this identity to allow communication to
reach the user. In such a case it is of great importance to store the users identity securely and
privately.
reach the user. In all cases it is of great importance to store the private keys associated with any
Reticulum Identity securely and privately.
.. _understanding-gettingfurther:
@@ -279,8 +283,9 @@ Getting Further
The above functions and principles form the core of Reticulum, and would suffice to create
functional networked applications in local clusters, for example over radio links where all interested
nodes can directly hear each other. But to be truly useful, we need a way to direct traffic over multiple
hops in the network. In the next sections, two concepts that allow this will be introduced, *paths* and
*links*.
hops in the network.
In the following sections, two concepts that allow this will be introduced, *paths* and *links*.
.. _understanding-transport:
@@ -298,70 +303,28 @@ useable over bandwidth-limited, high-latency links.
To overcome such challenges, Reticulums *Transport* system uses public-key cryptography to
implement the concept of *paths* that allow discovery of how to get information to a certain
destination, and *resources* that help make reliable data transfer more efficient.
destination. It is important to note that no single node in a Reticulum network knows the complete
path to a destination. Every Transport node participating in a Reticulum network will only
know what the most direct way to get a packet one hop closer to it's destination is.
.. _understanding-paths:
.. _understanding-announce:
Reaching the Destination
------------------------
The Announce Mechanism in Detail
--------------------------------
In networks with changing topology and trustless connectivity, nodes need a way to establish
*verified connectivity* with each other. Since the network is assumed to be trustless, Reticulum
must provide a way to guarantee that the peer you are communicating with is actually who you
expect. To do this, the following process is employed:
When an *announce* is transmitted by a node, it will be forwarded by any node receiving it, but
according to some specific rules:
* | First, the node that wishes to establish connectivity will send out a special packet, that
traverses the network and locates the desired destination. Along the way, the nodes that
forward the packet will take note of this *link request*.
* | If this exact announce has already been received before, ignore it.
* | Second, if the destination accepts the *link request* , it will send back a packet that proves the
authenticity of its identity (and the receipt of the link request) to the initiating node. All
nodes that initially forwarded the packet will also be able to verify this proof, and thus
accept the validity of the *link* throughout the network.
* | When the validity of the *link* has been accepted by forwarding nodes, these nodes will
remember the *link* , and it can subsequently be used by referring to a hash representing it.
* | As a part of the *link request* , a Diffie-Hellman key exchange takes place, that sets up an
efficient symmetrically encrypted tunnel between the two nodes, using elliptic curve
cryptography. As such, this mode of communication is preferred, even for situations when
nodes can directly communicate, when the amount of data to be exchanged numbers in the
tens of packets.
* | When a *link* has been set up, it automatically provides message receipt functionality, so the
sending node can obtain verified confirmation that the information reached the intended
recipient.
In a moment, we will discuss the specifics of how this methodology is implemented, but lets first
recap what purposes this serves. We first ensure that the node answering our request is actually the
one we want to communicate with, and not a malicious actor pretending to be so. At the same time
we establish an efficient encrypted channel. The setup of this is relatively cheap in terms of
bandwidth, so it can be used just for a short exchange, and then recreated as needed, which will also
rotate encryption keys, but the link can also be kept alive for longer periods of time, if this is
more suitable to the application. The amount of bandwidth used on keeping a link open is practically
negligible. The procedure also inserts the *link id* , a hash calculated from the link request packet,
into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each
other simply by referring to this *link id*.
Step 1: Pathfinding
^^^^^^^^^^^^^^^^^^^
The pathfinding method builds on the *announce* functionality discussed earlier. When an announce
is sent out by a node, it will be forwarded by any node receiving it, but according to some specific
rules:
* | If this announce has already been received before, ignore it.
* | Record into a table which node the announce was received from, and how many times in
* | If not, record into a table which node the announce was received from, and how many times in
total it has been retransmitted to get here.
* | If the announce has been retransmitted *m+1* times, it will not be forwarded. By default, *m* is
set to 18.
* | The announce will be assigned a delay *d* = c\ :sup:`h` seconds, where *c* is a decay constant, by
default 2, and *h* is the amount of times this packet has already been forwarded.
* | The announce will be assigned a delay *d* = c\ :sup:`h` seconds, where *c* is a decay constant, and *h* is the amount of times this packet has already been forwarded.
* | The packet will be given a priority *p = 1/d*.
@@ -370,10 +333,11 @@ rules:
not utilized by other traffic, the announce will be forwarded.
* | If no other nodes are heard retransmitting the announce with a greater hop count than when
it left this node, transmitting it will be retried *r* times. By default, *r* is set to 2. Retries follow
same rules as above, with the exception that it must wait for at least *d* = c\ :sup:`h+1` + t seconds, ie.,
the amount of time it would take the next node to retransmit the packet. By default, *t* is set to
10.
it left this node, transmitting it will be retried *r* times. By default, *r* is set to 1. Retries
follow same rules as above, with the exception that it must wait for at least *d* = c\ :sup:`h+1` +
t + rand(0, rw) seconds. This amount of time is equal to the amount of time it would take the next
node to retransmit the packet, plus a random window. By default, *t* is set to 10 seconds, and the
random window *rw* is set to 10 seconds.
* | If a newer announce from the same destination arrives, while an identical one is already in
the queue, the newest announce is discarded. If the newest announce contains different
@@ -392,14 +356,95 @@ distance of *Lavg =* 15 kilometers, an announce will be able to propagate outwar
kilometers in 34 minutes, and a *maximum announce radius* of 270 kilometers in approximately 3
days.
Step 2: Link Establishment
^^^^^^^^^^^^^^^^^^^^^^^^^^
.. _understanding-paths:
After seeing how the conditions for finding a path through the network are created, we will now
explore how two nodes can establish reliable communications over multiple hops. The *link* in
Reticulum terminology should not be viewed as a direct node-to-node link on the physical layer, but
as an abstract channel, that can be open for any amount of time, and can span an arbitrary number
of hops, where information will be exchanged between two nodes.
Reaching the Destination
------------------------
In networks with changing topology and trustless connectivity, nodes need a way to establish
*verified connectivity* with each other. Since the network is assumed to be trustless, Reticulum
must provide a way to guarantee that the peer you are communicating with is actually who you
expect. Reticulum offers two ways to do this.
For exchanges of small amounts of information, Reticulum offers the *Packet* API, which works exactly like you would expect - on a per packet level. The following process is employed when sending a packet:
* | A packet is always created with an associated destination and some payload data. When the packet is sent
to a *single* destination type, Reticulum will automatically create an ephemeral encryption key, perform
an ECDH key exchange with the destinations public key, and encrypt the information.
* | It is important to note that this key exchange does not require any network traffic. The sender already
knows the public key of the destination from an earlier received *announce*, and can thus perform the ECDH
key exchange locally, before sending the packet.
* | The public part of the newly generated ephemeral key-pair is included with the encrypted token, and sent
along with the encrypted payload data in the packet.
* | When the destination receives the packet, it can itself perform an ECDH key exchange and decrypt the
packet.
* | A new ephemeral key is used for every packet sent in this way, and forward secrecy is guaranteed on a
per packet level.
* | Once the packet has been received and decrypted by the addressed destination, that destination can opt
to *prove* its receipt of the packet. It does this by calculating the SHA-256 hash of the received packet,
and signing this hash with it's Ed25519 signing key. Transport nodes in the network can then direct this
*proof* back to the packets origin, where the signature can be verified against the destinations known
public signing key.
* | In case the packet is addressed to a *group* destination type, the packet will be encrypted with the
pre-shared AES-128 key associated with the destination. In case the packet is addressed to a *plain*
destination type, the payload data will not be encrypted. Neither of these two destination types offer
forward secrecy. In general, it is recommended to always use the *single* destination type, unless it is
strictly necessary to use one of the others.
For exchanges of larger amounts of data, or when longer sessions of bidirectional communication is desired, Reticulum offers the *Link* API. To establish a *link*, the following process is employed:
* | First, the node that wishes to establish a link will send out a special packet, that
traverses the network and locates the desired destination. Along the way, the nodes that
forward the packet will take note of this *link request*.
* | Second, if the destination accepts the *link request* , it will send back a packet that proves the
authenticity of its identity (and the receipt of the link request) to the initiating node. All
nodes that initially forwarded the packet will also be able to verify this proof, and thus
accept the validity of the *link* throughout the network.
* | When the validity of the *link* has been accepted by forwarding nodes, these nodes will
remember the *link* , and it can subsequently be used by referring to a hash representing it.
* | As a part of the *link request* , a Diffie-Hellman key exchange takes place, that sets up an
efficiently encrypted tunnel between the two nodes, using elliptic curve cryptography. As such,
this mode of communication is preferred, even for situations when nodes can directly communicate,
when the amount of data to be exchanged numbers in the tens of packets.
* | When a *link* has been set up, it automatically provides message receipt functionality, through
the same *proof* mechanism discussed before, so the sending node can obtain verified confirmation
that the information reached the intended recipient.
In a moment, we will discuss the details of how this methodology is implemented, but lets first
recap what purposes this methodology serves. We first ensure that the node answering our request
is actually the one we want to communicate with, and not a malicious actor pretending to be so.
At the same time we establish an efficient encrypted channel. The setup of this is relatively cheap in
terms of bandwidth, so it can be used just for a short exchange, and then recreated as needed, which will
also rotate encryption keys. The link can also be kept alive for longer periods of time, if this is
more suitable to the application. The procedure also inserts the *link id* , a hash calculated from the link request packet, into the memory of forwarding nodes, which means that the communicating nodes can thereafter reach each other simply by referring to this *link id*.
The combined bandwidth cost of setting up a link is 3 packets totalling 237 bytes (more info in the
:ref:`Binary Packet Format<understanding-packetformat>` section). The amount of bandwidth used on keeping
a link open is practically negligible, at 0.62 bits per second. Even on a slow 1200 bits per second packet
radio channel, 100 concurrent links will still leave 95% channel capacity for actual data.
Link Establishment in Detail
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
After exploring the basics of the announce mechanism, finding a path through the network, and an overview
of the link establishment procedure, this section will go into greater detail about the Reticulum link
establishment process.
The *link* in Reticulum terminology should not be viewed as a direct node-to-node link on the
physical layer, but as an abstract channel, that can be open for any amount of time, and can span
an arbitrary number of hops, where information will be exchanged between two nodes.
* | When a node in the network wants to establish verified connectivity with another node, it
@@ -412,25 +457,25 @@ of hops, where information will be exchanged between two nodes.
considered as single public key for simplicity in this explanation.*
* | The *link request* is addressed to the destination hash of the desired destination, and
contains the following data: The newly generated X25519 public key *LKi*. The contents
are encrypted with the RSA public key of the destination and tramsitted over the network.
contains the following data: The newly generated X25519 public key *LKi*.
* | The broadcasted packet will be directed through the network according to the rules laid out
previously.
* | Any node that forwards the link request will store a *link id* in its *link table* , along with the
amount of hops the packet had taken when received. The link id is a hash of the entire link
request packet. If the path is not *proven* within some set amount of time, the entry will be
dropped from the *link table* again.
request packet. If the link request packet is not *proven* by the addressed destination within some
set amount of time, the entry will be dropped from the *link table* again.
* | When the destination receives the link request packet, it will decrypt it and decide whether to
accept the request. If it is accepted, the destination will also generate a new X25519 private/public
key pair, and perform a Diffie Hellman Key Exchange, deriving a new symmetric key that will be used
to encrypt the channel, once it has been established.
* | When the destination receives the link request packet, it will decide whether to accept the request.
If it is accepted, the destination will also generate a new X25519 private/public key pair, and
perform a Diffie Hellman Key Exchange, deriving a new symmetric key that will be used to encrypt the
channel, once it has been established.
* | A *link proof* packet is now constructed and transmitted over the network. This packet is
addressed to the *link id* of the *link*. It contains the following data: The newly generated X25519
public key *LKr* and an RSA-1024 signature of the *link id* and *LKr*.
public key *LKr* and an Ed25519 signature of the *link id* and *LKr* made by the signing key of
the addressed destination.
* | By verifying this *link proof* packet, all nodes that originally transported the *link request*
packet to the destination from the originator can now verify that the intended destination received
@@ -556,6 +601,8 @@ the light of Reticulums goal of equal access, doing so would need to be the subj
investigation of the consequences first.
.. _understanding-packetformat:
Binary Packet Format
--------------------
@@ -651,8 +698,8 @@ Binary Packet Format
wire size including all fields.
- Path Request : 33 bytes
- Announce : 323 bytes
- Link Request : 141 bytes
- Link Proof : 205 bytes
- Link RTT packet : 86 bytes
- Announce : 151 bytes
- Link Request : 77 bytes
- Link Proof : 77 bytes
- Link RTT packet : 83 bytes
- Link keepalive : 14 bytes
+16 -6
View File
@@ -2,7 +2,9 @@
What is Reticulum?
******************
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth. Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
Reticulum is a cryptography-based networking stack for wide-area networks built on readily available hardware, and can operate even with very high latency and extremely low bandwidth.
Reticulum allows you to build very wide-area networks with off-the-shelf tools, and offers end-to-end encryption, autoconfiguring cryptographically backed multi-hop transport, efficient addressing, unforgeable packet acknowledgements and more.
Reticulum is a complete networking stack, and does not use IP or higher layers, although it is easy to utilise IP (with TCP or UDP) as the underlying carrier for Reticulum. It is therefore trivial to tunnel Reticulum over the Internet or private IP networks. Reticulum is built directly on cryptographic principles, allowing resilience and stable functionality in open and trustless networks.
@@ -25,11 +27,11 @@ What does Reticulum Offer?
* Fully self-configuring multi-hop routing
* Asymmetric RSA encryption and signatures as basis for all communication
* Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
* Perfect Forward Secrecy on links with ephemereal Elliptic Curve Diffie-Hellman keys (on Curve25519)
* Forward Secrecy with ephemereal Elliptic Curve Diffie-Hellman keys on Curve25519
* Reticulum uses the Fernet specification for encryption on links and to group destinations
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for encryption
* AES-128 in CBC mode with PKCS7 padding
@@ -37,11 +39,13 @@ What does Reticulum Offer?
* IVs are generated through os.urandom()
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
* Unforgeable packet delivery confirmations
* A variety of supported interface types
* An intuitive and easy-to-use API
* An intuitive and developer-friendly API
* Reliable and efficient transfer of arbritrary amounts of data
@@ -51,10 +55,16 @@ What does Reticulum Offer?
* The API is very easy to use, and provides transfer progress
* Efficient link establishment
* Total bandwidth cost of setting up a link is only 3 packets, totalling 237 bytes
* Low cost of keeping links open at only 0.62 bits per second
Where can Reticulum be Used?
============================
On practically any hardware that can support at least a half-duplex channel
Over practically any medium that can support at least a half-duplex channel
with 1.000 bits per second throughput, and an MTU of 500 bytes. Data radios,
modems, LoRa radios, serial lines, AX.25 TNCs, amateur radio digital modes,
ad-hoc WiFi, free-space optical links and similar systems are all examples
+6 -4
View File
@@ -1,11 +1,13 @@
import setuptools
exec(open("RNS/_version.py", "r").read())
with open("README.md", "r") as fh:
long_description = fh.read()
setuptools.setup(
name="rns",
version="0.2.0",
version=__version__,
author="Mark Qvist",
author_email="mark@unsigned.io",
description="Self-configuring, encrypted and resilient mesh networking stack for LoRa, packet radio, WiFi and everything in between",
@@ -18,6 +20,6 @@ setuptools.setup(
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
install_requires=['cryptography>=3.4.7', 'pyserial'],
python_requires='>=3.5',
)
install_requires=['cryptography>=3.4.7', 'pyserial', 'netifaces>=0.10.4'],
python_requires='>=3.6',
)