Compare commits
73 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aaf0ace027 | |||
| d8b76b4bc5 | |||
| d29ff38a05 | |||
| 65e8487b39 | |||
| 6362e04567 | |||
| 711b754dcc | |||
| 1351316f17 | |||
| 7af14cec84 | |||
| 0687ee2231 | |||
| 872075a31e | |||
| d8f0380aa9 | |||
| 569f9bd2b1 | |||
| 450b88d0f0 | |||
| 1cb9df109a | |||
| 80455c9614 | |||
| c1e280d896 | |||
| 4a2925cdea | |||
| 7f38c32e90 | |||
| 8646be0dcf | |||
| 6b3cc07740 | |||
| 3b57b0013b | |||
| 24d8f39dd1 | |||
| 58e4bf3c80 | |||
| 1da8a0c8f1 | |||
| 8b8d4410ef | |||
| 7d804daa8f | |||
| ce00822cb0 | |||
| 6d6c91edaf | |||
| 8432cf40c2 | |||
| 5e21bdd233 | |||
| c7e5f4612a | |||
| f80e09cb5c | |||
| 91d94f2f6f | |||
| 53ca86ebfc | |||
| 534bb28900 | |||
| 0de5ec73ad | |||
| c0f627b50b | |||
| 5629a062a5 | |||
| 83232f0446 | |||
| aa794514b3 | |||
| 07cf180ea8 | |||
| 42a3d23e99 | |||
| d28c888d1c | |||
| 58d48c18f4 | |||
| ecf0c55fd6 | |||
| 32e4c262ef | |||
| f87a6a57df | |||
| 6373f159f8 | |||
| ad9f548eeb | |||
| 425f0153d0 | |||
| cd9daaefee | |||
| 0fe76d50f6 | |||
| 9562803bb3 | |||
| e9c89209c7 | |||
| cd8de64201 | |||
| 40f7a6d359 | |||
| 0c96508cca | |||
| 1fd59f1a02 | |||
| 0a0d0af821 | |||
| b694cbdc91 | |||
| 71c3333e10 | |||
| 972fcdee22 | |||
| 17dbfe6401 | |||
| 781cb4712d | |||
| cdb08325cc | |||
| 62d954d7bf | |||
| 4bbf1ae57d | |||
| 2678aeb6a1 | |||
| 6d441dac02 | |||
| 66b2be87f4 | |||
| 2e7126ef39 | |||
| c0f909850b | |||
| a199e4c929 |
@@ -358,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)
|
||||
|
||||
@@ -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()
|
||||
@@ -20,11 +20,11 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
|
||||
- Fully self-configuring multi-hop routing
|
||||
- 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
|
||||
- Reticulum uses the [Fernet](https://github.com/fernet/spec/blob/master/Spec.md) specification for on-the-wire / over-the-air encryption
|
||||
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
- 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
|
||||
@@ -34,7 +34,7 @@ For more info, see [unsigned.io/projects/reticulum](https://unsigned.io/projects
|
||||
- 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 240 bytes
|
||||
- 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?
|
||||
|
||||
@@ -22,7 +22,7 @@ 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.
|
||||
|
||||
@@ -34,12 +34,13 @@ class Identity:
|
||||
"""
|
||||
|
||||
# Non-configurable constants
|
||||
AES_HMAC_OVERHEAD = 58 # In bytes
|
||||
FERNET_VERSION = 0x80
|
||||
FERNET_OVERHEAD = 54 # In bytes
|
||||
AES128_BLOCKSIZE = 16 # In bytes
|
||||
HASHLENGTH = 256 # In bits
|
||||
SIGLENGTH = KEYSIZE # In bits
|
||||
|
||||
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 and other purposes. Non-configurable.
|
||||
@@ -64,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(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
|
||||
@@ -84,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
|
||||
@@ -182,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):
|
||||
"""
|
||||
@@ -197,21 +208,23 @@ class Identity:
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def from_bytes(prv_bytes):
|
||||
def to_file(self, path):
|
||||
"""
|
||||
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.
|
||||
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.
|
||||
|
||||
:param prv_bytes: The *bytes* of private a saved private key. **HAZARD!** Never not 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.
|
||||
:param path: The full path specifying where to save the identity.
|
||||
:returns: True if the file was saved, otherwise False.
|
||||
"""
|
||||
identity = Identity(create_keys=False)
|
||||
if identity.load_private_key(prv_bytes):
|
||||
return identity
|
||||
else:
|
||||
return None
|
||||
|
||||
try:
|
||||
with open(path, "wb") as key_file:
|
||||
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
|
||||
@@ -331,24 +344,6 @@ class Identity:
|
||||
self.hash = Identity.truncated_hash(self.get_public_key())
|
||||
self.hexhash = self.hash.hex()
|
||||
|
||||
def to_file(self, path):
|
||||
"""
|
||||
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.
|
||||
|
||||
:param path: The full path specifying where to save the identity.
|
||||
:returns: True if the file was saved, otherwise False.
|
||||
"""
|
||||
try:
|
||||
with open(path, "wb") as key_file:
|
||||
key_file.write(self.get_public_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 load(self, path):
|
||||
try:
|
||||
with open(path, "rb") as key_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,12 +290,21 @@ 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
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
def __str__(self):
|
||||
return "AX25KISSInterface["+self.name+"]"
|
||||
@@ -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"))
|
||||
@@ -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:
|
||||
@@ -277,7 +277,10 @@ class KISSInterface(Interface):
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
def __str__(self):
|
||||
return "KISSInterface["+self.name+"]"
|
||||
@@ -127,6 +127,10 @@ class LocalClientInterface(Interface):
|
||||
if self in RNS.Transport.local_client_interfaces:
|
||||
RNS.Transport.local_client_interfaces.remove(self)
|
||||
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "LocalInterface["+str(self.target_port)+"]"
|
||||
|
||||
@@ -463,7 +463,10 @@ class RNodeInterface(Interface):
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+self.name+"]"
|
||||
|
||||
@@ -130,7 +130,10 @@ class SerialInterface(Interface):
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self.name)+" is now offline. Restart Reticulum to attempt reconnection.", RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
def __str__(self):
|
||||
return "SerialInterface["+self.name+"]"
|
||||
|
||||
@@ -23,13 +23,21 @@ class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
class TCPClientInterface(Interface):
|
||||
RECONNECT_WAIT = 5
|
||||
RECONNECT_MAX_TRIES = None
|
||||
|
||||
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None):
|
||||
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None):
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.socket = None
|
||||
self.parent_interface = None
|
||||
self.name = name
|
||||
self.initiator = False
|
||||
|
||||
if max_reconnect_tries == None:
|
||||
self.max_reconnect_tries = TCPClientInterface.RECONNECT_MAX_TRIES
|
||||
else:
|
||||
self.max_reconnect_tries = max_reconnect_tries
|
||||
|
||||
if connected_socket != None:
|
||||
self.receives = True
|
||||
@@ -50,9 +58,42 @@ class TCPClientInterface(Interface):
|
||||
self.writing = False
|
||||
|
||||
if connected_socket == None:
|
||||
self.initiator = True
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
self.wants_tunnel = True
|
||||
|
||||
def reconnect(self):
|
||||
if self.initiator:
|
||||
attempts = 0
|
||||
while not self.online:
|
||||
attempts += 1
|
||||
|
||||
if self.max_reconnect_tries != None and attempts > self.max_reconnect_tries:
|
||||
RNS.log("Max reconnection attempts reached for "+str(self), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
break
|
||||
|
||||
try:
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.socket.connect((self.target_ip, self.target_port))
|
||||
self.online = True
|
||||
self.writing = False
|
||||
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
RNS.Transport.synthesize_tunnel(self)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Reconnection attempt for "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
time.sleep(TCPClientInterface.RECONNECT_WAIT)
|
||||
|
||||
else:
|
||||
RNS.log("Attempt to reconnect on a non-initiator TCP interface. This should not happen.", RNS.LOG_ERROR)
|
||||
raise IOError("Attempt to reconnect on a non-initiator TCP interface")
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.owner.inbound(data, self)
|
||||
@@ -104,24 +145,30 @@ class TCPClientInterface(Interface):
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
else:
|
||||
RNS.log("TCP socket for "+str(self)+" was closed, tearing down interface", RNS.LOG_VERBOSE)
|
||||
self.teardown()
|
||||
RNS.log("TCP socket for "+str(self)+" was closed, attempting to reconnect...", RNS.LOG_WARNING)
|
||||
self.online = False
|
||||
if self.initiator:
|
||||
self.reconnect()
|
||||
|
||||
break
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("An interface error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Tearing down "+str(self), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
|
||||
def teardown(self):
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down. Restart Reticulum to attempt to open this interface again.", RNS.LOG_ERROR)
|
||||
self.online = False
|
||||
self.OUT = False
|
||||
self.IN = False
|
||||
if self in RNS.Transport.interfaces:
|
||||
RNS.Transport.interfaces.remove(self)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "TCPInterface["+str(self.name)+"/"+str(self.target_ip)+":"+str(self.target_port)+"]"
|
||||
|
||||
@@ -27,12 +27,13 @@ class LinkCallbacks:
|
||||
|
||||
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 = RNS.Identity.CURVE
|
||||
"""
|
||||
@@ -42,16 +43,19 @@ class Link:
|
||||
ECPUBSIZE = 32+32
|
||||
KEYSIZE = 32
|
||||
|
||||
MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.AES_HMAC_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_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 = 360
|
||||
"""
|
||||
@@ -80,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
|
||||
@@ -102,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
|
||||
@@ -117,9 +122,8 @@ class Link:
|
||||
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
|
||||
@@ -128,13 +132,13 @@ class Link:
|
||||
self.destination = destination
|
||||
self.attached_interface = None
|
||||
self.__remote_identity = None
|
||||
self.__encryption_disabled = False
|
||||
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()
|
||||
|
||||
@@ -158,6 +162,12 @@ 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]
|
||||
@@ -222,29 +232,29 @@ class Link:
|
||||
self.had_outbound()
|
||||
|
||||
def validate_proof(self, packet):
|
||||
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)
|
||||
RNS.log("Sending RTT packet", RNS.LOG_EXTREME);
|
||||
rtt_packet.send()
|
||||
self.had_outbound()
|
||||
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()
|
||||
|
||||
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()
|
||||
else:
|
||||
RNS.log("Invalid link proof signature received by "+str(self)+". Ignoring.", RNS.LOG_DEBUG)
|
||||
|
||||
|
||||
def identify(self, identity):
|
||||
@@ -266,38 +276,53 @@ class Link:
|
||||
self.had_outbound()
|
||||
|
||||
|
||||
def request(self, path, data = None, response_callback = None, failed_callback = None):
|
||||
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: A 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: A 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 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()
|
||||
|
||||
return RequestReceipt(
|
||||
self,
|
||||
packet_receipt = request_packet.send(),
|
||||
response_callback = response_callback,
|
||||
failed_callback = failed_callback
|
||||
)
|
||||
|
||||
if packet_receipt == False:
|
||||
return False
|
||||
else:
|
||||
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)
|
||||
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
|
||||
failed_callback = failed_callback,
|
||||
progress_callback = progress_callback,
|
||||
timeout = timeout
|
||||
)
|
||||
|
||||
|
||||
@@ -394,6 +419,11 @@ class Link:
|
||||
self.shared_key = None
|
||||
self.derived_key = None
|
||||
|
||||
if self.destination != None:
|
||||
if self.destination.direction == RNS.Destination.IN:
|
||||
if self in self.destination.links:
|
||||
self.destination.links.remove(self)
|
||||
|
||||
if self.callbacks.link_closed != None:
|
||||
self.callbacks.link_closed(self)
|
||||
|
||||
@@ -411,9 +441,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
|
||||
@@ -421,10 +451,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()
|
||||
@@ -432,7 +466,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()
|
||||
@@ -462,53 +496,57 @@ class Link:
|
||||
self.had_outbound()
|
||||
|
||||
def handle_request(self, request_id, unpacked_request):
|
||||
requested_at = unpacked_request[0]
|
||||
path_hash = unpacked_request[1]
|
||||
request_data = unpacked_request[2]
|
||||
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]
|
||||
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 = 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
|
||||
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 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)
|
||||
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):
|
||||
remove = None
|
||||
for pending_request in self.pending_requests:
|
||||
if pending_request.request_id == request_id:
|
||||
remove = pending_request
|
||||
try:
|
||||
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)
|
||||
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
|
||||
break
|
||||
|
||||
if remove != None:
|
||||
self.pending_requests.remove(remove)
|
||||
if remove != None:
|
||||
self.pending_requests.remove(remove)
|
||||
|
||||
def request_resource_concluded(self, resource):
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
@@ -528,9 +566,12 @@ class Link:
|
||||
request_id = unpacked_response[0]
|
||||
response_data = unpacked_response[1]
|
||||
|
||||
self.handle_response(request_id, response_data)
|
||||
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
|
||||
@@ -589,7 +630,8 @@ class Link:
|
||||
unpacked_response = umsgpack.unpackb(packed_response)
|
||||
request_id = unpacked_response[0]
|
||||
response_data = unpacked_response[1]
|
||||
self.handle_response(request_id, response_data)
|
||||
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)
|
||||
|
||||
@@ -606,7 +648,13 @@ class Link:
|
||||
if RNS.ResourceAdvertisement.is_request(packet):
|
||||
RNS.Resource.accept(packet, callback=self.request_resource_concluded)
|
||||
elif RNS.ResourceAdvertisement.is_response(packet):
|
||||
RNS.Resource.accept(packet, callback=self.response_resource_concluded)
|
||||
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:
|
||||
@@ -624,7 +672,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)
|
||||
@@ -666,32 +718,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):
|
||||
@@ -799,68 +861,71 @@ 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)
|
||||
|
||||
|
||||
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
|
||||
READY = 0x03
|
||||
RECEIVING = 0x03
|
||||
READY = 0x04
|
||||
|
||||
def __init__(self, link, packet_receipt = None, resource = None, response_callback = None, failed_callback = None):
|
||||
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.link = link
|
||||
self.request_id = self.hash
|
||||
|
||||
self.response = None
|
||||
self.status = RequestReceipt.SENT
|
||||
self.sent_at = time.time()
|
||||
self.timeout = RNS.Packet.TIMEOUT
|
||||
self.concluded_at = None
|
||||
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
|
||||
@@ -870,6 +935,16 @@ class RequestReceipt():
|
||||
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()
|
||||
@@ -878,21 +953,85 @@ class RequestReceipt():
|
||||
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):
|
||||
self.response = 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)
|
||||
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
|
||||
|
||||
if self.callbacks.response != None:
|
||||
self.callbacks.response(self)
|
||||
|
||||
|
||||
class RequestReceiptCallbacks:
|
||||
def __init__(self):
|
||||
self.response = None
|
||||
self.failed = None
|
||||
self.failed = None
|
||||
self.progress = None
|
||||
@@ -20,11 +20,6 @@ class Packet:
|
||||
: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
|
||||
@@ -71,19 +66,18 @@ class Packet:
|
||||
# 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.AES_HMAC_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
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
|
||||
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:
|
||||
@@ -310,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
|
||||
@@ -329,13 +323,18 @@ class PacketReceipt:
|
||||
self.truncated_hash = packet.getTruncatedHash()
|
||||
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
|
||||
|
||||
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):
|
||||
"""
|
||||
:returns: The status of the associated :ref:`RNS.Packet<api-packet>` instance. Can be one of ``RNS.PacketReceipt.SENT``, ``RNS.PacketReceipt.DELIVERED``, ``RNS.PacketReceipt.FAILED`` or ``RNS.PacketReceipt.CULLED``.
|
||||
@@ -436,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:
|
||||
@@ -448,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):
|
||||
|
||||
@@ -15,14 +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 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 is_request: Internal use, ignore.
|
||||
:param is_response: 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
|
||||
@@ -36,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
|
||||
@@ -72,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
|
||||
@@ -136,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, callback=None, progress_callback=None, segment_index = 1, original_hash = None, request_id = None, is_response = False):
|
||||
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"):
|
||||
@@ -183,8 +184,8 @@ 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
|
||||
@@ -194,8 +195,14 @@ class Resource:
|
||||
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
|
||||
@@ -238,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
|
||||
@@ -370,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)
|
||||
@@ -391,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:
|
||||
@@ -443,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:
|
||||
@@ -542,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
|
||||
@@ -571,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
|
||||
@@ -696,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
|
||||
|
||||
@@ -751,7 +783,7 @@ class Resource:
|
||||
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.
|
||||
"""
|
||||
@@ -776,9 +808,12 @@ class Resource:
|
||||
|
||||
|
||||
class ResourceAdvertisement:
|
||||
HASHMAP_MAX_LEN = 70
|
||||
OVERHEAD = 128
|
||||
HASHMAP_MAX_LEN = math.floor((RNS.Link.MDU-OVERHEAD)/Resource.MAPHASH_LEN)
|
||||
COLLISION_GUARD_SIZE = 2*Resource.WINDOW_MAX+HASHMAP_MAX_LEN
|
||||
|
||||
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)
|
||||
@@ -804,6 +839,18 @@ class ResourceAdvertisement:
|
||||
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
|
||||
|
||||
@@ -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,10 +95,11 @@ 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
|
||||
|
||||
Reticulum.panic_on_interface_error = False
|
||||
|
||||
self.local_interface_port = 37428
|
||||
self.share_instance = True
|
||||
|
||||
@@ -185,26 +197,16 @@ class Reticulum:
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.__transport_enabled = True
|
||||
if option == "panic_on_interface_error":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.panic_on_interface_error = True
|
||||
if option == "use_implicit_proof":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
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()
|
||||
|
||||
@@ -309,7 +311,7 @@ class Reticulum:
|
||||
stopbits
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
@@ -350,7 +352,7 @@ class Reticulum:
|
||||
beacon_data
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
@@ -392,7 +394,7 @@ class Reticulum:
|
||||
flow_control
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
@@ -428,7 +430,7 @@ class Reticulum:
|
||||
id_callsign = id_callsign
|
||||
)
|
||||
|
||||
if "outgoing" in c and c["outgoing"].lower() == "true":
|
||||
if "outgoing" in c and c.as_bool("outgoing") == True:
|
||||
interface.OUT = True
|
||||
else:
|
||||
interface.OUT = False
|
||||
@@ -455,16 +457,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():
|
||||
"""
|
||||
@@ -495,14 +487,6 @@ __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.
|
||||
# This should be done for systems that are suited to act
|
||||
@@ -532,6 +516,14 @@ share_instance = Yes
|
||||
|
||||
shared_instance_port = 37428
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
# an optional directive, and can be left out for brevity.
|
||||
# This behaviour is disabled by default.
|
||||
|
||||
panic_on_interface_error = No
|
||||
|
||||
|
||||
[logging]
|
||||
# Valid log levels are 0 through 7:
|
||||
@@ -566,15 +558,30 @@ loglevel = 4
|
||||
type = UDPInterface
|
||||
interface_enabled = True
|
||||
outgoing = True
|
||||
device = eth0
|
||||
port = 4242
|
||||
listen_ip = 0.0.0.0
|
||||
listen_port = 4242
|
||||
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 network segments broadcast address.
|
||||
|
||||
# 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
|
||||
|
||||
@@ -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 the
|
||||
Transport system of Reticulum.
|
||||
"""
|
||||
# Constants
|
||||
BROADCAST = 0x00;
|
||||
TRANSPORT = 0x01;
|
||||
@@ -22,41 +26,45 @@ class Transport:
|
||||
|
||||
APP_NAME = "rnstransport"
|
||||
|
||||
PATHFINDER_M = 18 # Max hops
|
||||
PATHFINDER_C = 2.0 # Decay constant
|
||||
PATHFINDER_R = 1 # Retransmit retries
|
||||
PATHFINDER_T = 10 # Retry grace period
|
||||
PATHFINDER_RW = 10 # Random window for announce rebroadcast
|
||||
PATHFINDER_E = 60*15 # Path expiration in seconds
|
||||
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
|
||||
PATHFINDER_RW = 10 # Random window for announce rebroadcast
|
||||
PATHFINDER_E = 60*60*24*7 # Path expiration in seconds
|
||||
|
||||
# TODO: Calculate an optimal number for this in
|
||||
# various situations
|
||||
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
|
||||
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
|
||||
|
||||
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
PATH_REQUEST_RW = 2 # Path request random window
|
||||
PATH_REQUEST_GRACE = 0.35 # Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
PATH_REQUEST_RW = 2 # Path request random window
|
||||
|
||||
LINK_TIMEOUT = RNS.Link.KEEPALIVE * 2
|
||||
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after max 30 minutes
|
||||
DESTINATION_TIMEOUT = 60*60*24*7 # Destination table entries are removed if unused for one week
|
||||
MAX_RECEIPTS = 1024 # Maximum number of receipts to keep track of
|
||||
REVERSE_TIMEOUT = 30*60 # Reverse table entries are removed after max 30 minutes
|
||||
DESTINATION_TIMEOUT = PATHFINDER_E # Destination table entries are removed if unused for one week
|
||||
MAX_RECEIPTS = 1024 # Maximum number of receipts to keep track of
|
||||
|
||||
interfaces = [] # All active interfaces
|
||||
destinations = [] # All active destinations
|
||||
pending_links = [] # Links that are being established
|
||||
active_links = [] # Links that are active
|
||||
packet_hashlist = [] # A list of packet hashes for duplicate detection
|
||||
receipts = [] # Receipts of all outgoing packets for proof processing
|
||||
interfaces = [] # All active interfaces
|
||||
destinations = [] # All active destinations
|
||||
pending_links = [] # Links that are being established
|
||||
active_links = [] # Links that are active
|
||||
packet_hashlist = [] # A list of packet hashes for duplicate detection
|
||||
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
|
||||
link_table = {} # A lookup table containing hops for links
|
||||
held_announces = {} # A table containing temporarily held announce-table entries
|
||||
announce_handlers = [] # A table storing externally registered announce handlers
|
||||
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
|
||||
link_table = {} # A lookup table containing hops for links
|
||||
held_announces = {} # A table containing temporarily held announce-table entries
|
||||
announce_handlers = [] # A table storing externally registered announce handlers
|
||||
tunnels = {} # A table storing tunnels to other transport instances
|
||||
|
||||
# Transport control destinations are used
|
||||
# for control purposes like path requests
|
||||
@@ -112,12 +120,19 @@ class Transport:
|
||||
Transport.control_destinations.append(Transport.path_request_destination)
|
||||
Transport.control_hashes.append(Transport.path_request_destination.hash)
|
||||
|
||||
Transport.tunnel_synthesize_destination = RNS.Destination(None, RNS.Destination.IN, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize")
|
||||
Transport.tunnel_synthesize_destination.set_packet_callback(Transport.tunnel_synthesize_handler)
|
||||
Transport.control_destinations.append(Transport.tunnel_synthesize_handler)
|
||||
Transport.control_hashes.append(Transport.tunnel_synthesize_destination.hash)
|
||||
|
||||
thread = threading.Thread(target=Transport.jobloop)
|
||||
thread.setDaemon(True)
|
||||
thread.start()
|
||||
|
||||
if RNS.Reticulum.transport_enabled():
|
||||
destination_table_path = RNS.Reticulum.storagepath+"/destination_table"
|
||||
tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
|
||||
|
||||
if os.path.isfile(destination_table_path) and not Transport.owner.is_connected_to_shared_instance:
|
||||
serialised_destinations = []
|
||||
try:
|
||||
@@ -161,8 +176,64 @@ class Transport:
|
||||
except Exception as e:
|
||||
RNS.log("Could not load destination table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
if os.path.isfile(tunnel_table_path) and not Transport.owner.is_connected_to_shared_instance:
|
||||
serialised_tunnels = []
|
||||
try:
|
||||
file = open(tunnel_table_path, "rb")
|
||||
serialised_tunnels = umsgpack.unpackb(file.read())
|
||||
file.close()
|
||||
|
||||
for serialised_tunnel in serialised_tunnels:
|
||||
tunnel_id = serialised_tunnel[0]
|
||||
interface_hash = serialised_tunnel[1]
|
||||
serialised_paths = serialised_tunnel[2]
|
||||
expires = serialised_tunnel[3]
|
||||
|
||||
tunnel_paths = {}
|
||||
for serialised_entry in serialised_paths:
|
||||
destination_hash = serialised_entry[0]
|
||||
timestamp = serialised_entry[1]
|
||||
received_from = serialised_entry[2]
|
||||
hops = serialised_entry[3]
|
||||
expires = serialised_entry[4]
|
||||
random_blobs = serialised_entry[5]
|
||||
receiving_interface = Transport.find_interface_from_hash(serialised_entry[6])
|
||||
announce_packet = Transport.get_cached_packet(serialised_entry[7])
|
||||
|
||||
if announce_packet != None:
|
||||
announce_packet.unpack()
|
||||
# We increase the hops, since reading a packet
|
||||
# from cache is equivalent to receiving it again
|
||||
# over an interface. It is cached with it's non-
|
||||
# increased hop-count.
|
||||
announce_packet.hops += 1
|
||||
|
||||
tunnel_path = [timestamp, received_from, hops, expires, random_blobs, receiving_interface, announce_packet]
|
||||
tunnel_paths[destination_hash] = tunnel_path
|
||||
|
||||
tunnel = [tunnel_id, None, tunnel_paths, expires]
|
||||
Transport.tunnels[tunnel_id] = tunnel
|
||||
|
||||
if len(Transport.destination_table) == 1:
|
||||
specifier = "entry"
|
||||
else:
|
||||
specifier = "entries"
|
||||
|
||||
RNS.log("Loaded "+str(len(Transport.tunnels))+" tunnel table "+specifier+" from storage", RNS.LOG_VERBOSE)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not load tunnel table from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
|
||||
RNS.log("Transport instance "+str(Transport.identity)+" started")
|
||||
|
||||
# Synthesize tunnels for any interfaces wanting it
|
||||
for interface in Transport.interfaces:
|
||||
interface.tunnel_id = None
|
||||
if hasattr(interface, "wants_tunnel") and interface.wants_tunnel:
|
||||
Transport.synthesize_tunnel(interface)
|
||||
|
||||
@staticmethod
|
||||
def jobloop():
|
||||
while (True):
|
||||
@@ -180,7 +251,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 +317,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
|
||||
@@ -272,11 +343,41 @@ class Transport:
|
||||
if time.time() > destination_entry[0] + Transport.DESTINATION_TIMEOUT:
|
||||
stale_paths.append(destination_hash)
|
||||
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" timed out and was removed", RNS.LOG_DEBUG)
|
||||
|
||||
if not attached_interface in Transport.interfaces:
|
||||
elif not attached_interface in Transport.interfaces:
|
||||
stale_paths.append(destination_hash)
|
||||
RNS.log("Path to "+RNS.prettyhexrep(destination_hash)+" was removed since the attached interface no longer exists", RNS.LOG_DEBUG)
|
||||
|
||||
# Cull the tunnel table
|
||||
stale_tunnels = []
|
||||
ti = 0
|
||||
for tunnel_id in Transport.tunnels:
|
||||
tunnel_entry = Transport.tunnels[tunnel_id]
|
||||
|
||||
expires = tunnel_entry[3]
|
||||
if time.time() > expires:
|
||||
stale_tunnels.append(tunnel_id)
|
||||
RNS.log("Tunnel "+RNS.prettyhexrep(tunnel_id)+" timed out and was removed", RNS.LOG_DEBUG)
|
||||
else:
|
||||
stale_tunnel_paths = []
|
||||
tunnel_paths = tunnel_entry[2]
|
||||
for tunnel_path in tunnel_paths:
|
||||
tunnel_path_entry = tunnel_paths[tunnel_path]
|
||||
|
||||
if time.time() > tunnel_path_entry[0] + Transport.DESTINATION_TIMEOUT:
|
||||
stale_tunnel_paths.append(tunnel_path)
|
||||
RNS.log("Tunnel path to "+RNS.prettyhexrep(tunnel_path)+" timed out and was removed", RNS.LOG_DEBUG)
|
||||
|
||||
for tunnel_path in stale_tunnel_paths:
|
||||
tunnel_paths.pop(tunnel_path)
|
||||
ti += 1
|
||||
|
||||
|
||||
if ti > 0:
|
||||
if ti == 1:
|
||||
RNS.log("Removed "+str(ti)+" tunnel path", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Removed "+str(ti)+" tunnel paths", RNS.LOG_DEBUG)
|
||||
|
||||
i = 0
|
||||
for link_id in stale_links:
|
||||
Transport.link_table.pop(link_id)
|
||||
@@ -299,6 +400,17 @@ class Transport:
|
||||
else:
|
||||
RNS.log("Removed "+str(i)+" paths", RNS.LOG_DEBUG)
|
||||
|
||||
i = 0
|
||||
for tunnel_id in stale_tunnels:
|
||||
Transport.tunnels.pop(tunnel_id)
|
||||
i += 1
|
||||
|
||||
if i > 0:
|
||||
if i == 1:
|
||||
RNS.log("Removed "+str(i)+" tunnel", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log("Removed "+str(i)+" tunnels", RNS.LOG_DEBUG)
|
||||
|
||||
Transport.tables_last_culled = time.time()
|
||||
|
||||
except Exception as e:
|
||||
@@ -338,8 +450,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 +469,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
|
||||
@@ -390,11 +498,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)
|
||||
stored_hash = True
|
||||
|
||||
interface.processOutgoing(packet.raw)
|
||||
sent = True
|
||||
@@ -452,8 +558,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
|
||||
|
||||
@@ -462,8 +567,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):
|
||||
@@ -528,11 +631,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]
|
||||
@@ -739,23 +841,53 @@ 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()
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
Transport.destination_table[packet.destination_hash] = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet]
|
||||
new_announce.hops = packet.hops
|
||||
new_announce.send()
|
||||
|
||||
destination_table_entry = [now, received_from, announce_hops, expires, random_blobs, packet.receiving_interface, packet]
|
||||
Transport.destination_table[packet.destination_hash] = destination_table_entry
|
||||
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)
|
||||
|
||||
# If the receiving interface is a tunnel, we add the
|
||||
# announce to the tunnels table
|
||||
if hasattr(packet.receiving_interface, "tunnel_id") and packet.receiving_interface.tunnel_id != None:
|
||||
tunnel_entry = Transport.tunnels[packet.receiving_interface.tunnel_id]
|
||||
paths = tunnel_entry[2]
|
||||
paths[packet.destination_hash] = destination_table_entry
|
||||
expires = time.time() + Transport.DESTINATION_TIMEOUT
|
||||
tunnel_entry[3] = expires
|
||||
RNS.log("Path to "+RNS.prettyhexrep(packet.destination_hash)+" associated with tunnel "+RNS.prettyhexrep(packet.receiving_interface.tunnel_id), RNS.LOG_VERBOSE)
|
||||
|
||||
# Call externally registered callbacks from apps
|
||||
# wanting to know when an announce arrives
|
||||
for handler in Transport.announce_handlers:
|
||||
@@ -869,15 +1001,110 @@ 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
|
||||
|
||||
@staticmethod
|
||||
def synthesize_tunnel(interface):
|
||||
interface_hash = interface.get_hash()
|
||||
public_key = RNS.Transport.identity.get_public_key()
|
||||
random_hash = RNS.Identity.get_random_hash()
|
||||
|
||||
tunnel_id_data = public_key+interface_hash
|
||||
tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
|
||||
|
||||
signed_data = tunnel_id_data+random_hash
|
||||
signature = Transport.identity.sign(signed_data)
|
||||
|
||||
data = signed_data+signature
|
||||
|
||||
tnl_snth_dst = RNS.Destination(None, RNS.Destination.OUT, RNS.Destination.PLAIN, Transport.APP_NAME, "tunnel", "synthesize")
|
||||
|
||||
packet = RNS.Packet(tnl_snth_dst, data, packet_type = RNS.Packet.DATA, transport_type = RNS.Transport.BROADCAST, header_type = RNS.Packet.HEADER_1, attached_interface = interface)
|
||||
packet.send()
|
||||
|
||||
interface.wants_tunnel = False
|
||||
|
||||
@staticmethod
|
||||
def tunnel_synthesize_handler(data, packet):
|
||||
try:
|
||||
expected_length = RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8+RNS.Identity.SIGLENGTH//8
|
||||
if len(data) == expected_length:
|
||||
public_key = data[:RNS.Identity.KEYSIZE//8]
|
||||
interface_hash = data[RNS.Identity.KEYSIZE//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8]
|
||||
tunnel_id_data = public_key+interface_hash
|
||||
tunnel_id = RNS.Identity.full_hash(tunnel_id_data)
|
||||
random_hash = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8:RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8]
|
||||
|
||||
signature = data[RNS.Identity.KEYSIZE//8+RNS.Identity.HASHLENGTH//8+RNS.Reticulum.TRUNCATED_HASHLENGTH//8:expected_length]
|
||||
signed_data = tunnel_id_data+random_hash
|
||||
|
||||
remote_transport_identity = RNS.Identity(create_keys=False)
|
||||
remote_transport_identity.load_public_key(public_key)
|
||||
|
||||
if remote_transport_identity.validate(signature, signed_data):
|
||||
Transport.handle_tunnel(tunnel_id, packet.receiving_interface)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while validating tunnel establishment packet.", RNS.LOG_DEBUG)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
|
||||
|
||||
@staticmethod
|
||||
def handle_tunnel(tunnel_id, interface):
|
||||
expires = time.time() + Transport.DESTINATION_TIMEOUT
|
||||
if not tunnel_id in Transport.tunnels:
|
||||
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" established.", RNS.LOG_DEBUG)
|
||||
paths = {}
|
||||
tunnel_entry = [tunnel_id, interface, paths, expires]
|
||||
interface.tunnel_id = tunnel_id
|
||||
Transport.tunnels[tunnel_id] = tunnel_entry
|
||||
else:
|
||||
RNS.log("Tunnel endpoint "+RNS.prettyhexrep(tunnel_id)+" reappeared. Restoring paths...", RNS.LOG_DEBUG)
|
||||
tunnel_entry = Transport.tunnels[tunnel_id]
|
||||
tunnel_entry[1] = interface
|
||||
tunnel_entry[3] = expires
|
||||
interface.tunnel_id = tunnel_id
|
||||
paths = tunnel_entry[2]
|
||||
|
||||
for destination_hash, path_entry in paths.items():
|
||||
received_from = path_entry[1]
|
||||
announce_hops = path_entry[2]
|
||||
expires = path_entry[3]
|
||||
random_blobs = path_entry[4]
|
||||
receiving_interface = interface
|
||||
packet = path_entry[6]
|
||||
new_entry = [time.time(), received_from, announce_hops, expires, random_blobs, receiving_interface, packet]
|
||||
|
||||
should_add = False
|
||||
if destination_hash in Transport.destination_table:
|
||||
old_entry = Transport.destination_table[destination_hash]
|
||||
old_hops = old_entry[2]
|
||||
old_expires = old_entry[3]
|
||||
if announce_hops <= old_hops or time.time() > old_expires:
|
||||
should_add = True
|
||||
else:
|
||||
RNS.log("Did not restore path to "+RNS.prettyhexrep(packet.destination_hash)+" because a newer path with fewer hops exist", RNS.LOG_DEBUG)
|
||||
else:
|
||||
should_add = True
|
||||
|
||||
if should_add:
|
||||
Transport.destination_table[destination_hash] = new_entry
|
||||
RNS.log("Restored path to "+RNS.prettyhexrep(packet.destination_hash)+" is now "+str(announce_hops)+" hops away via "+RNS.prettyhexrep(received_from)+" on "+str(receiving_interface), RNS.LOG_VERBOSE)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def register_destination(destination):
|
||||
destination.MTU = RNS.Reticulum.MTU
|
||||
@@ -885,8 +1112,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:
|
||||
@@ -1033,6 +1265,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):
|
||||
"""
|
||||
@@ -1201,3 +1444,55 @@ class Transport:
|
||||
RNS.log("Done saving "+str(len(serialised_destinations))+" path table entries to storage", RNS.LOG_VERBOSE)
|
||||
except Exception as e:
|
||||
RNS.log("Could not save path table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Saving tunnel table to storage...", RNS.LOG_VERBOSE)
|
||||
try:
|
||||
serialised_tunnels = []
|
||||
for tunnel_id in Transport.tunnels:
|
||||
te = Transport.tunnels[tunnel_id]
|
||||
interface = te[1]
|
||||
tunnel_paths = te[2]
|
||||
expires = te[3]
|
||||
|
||||
if interface != None:
|
||||
interface_hash = interface.get_hash()
|
||||
else:
|
||||
interface_hash = None
|
||||
|
||||
serialised_paths = []
|
||||
for destination_hash in tunnel_paths:
|
||||
de = tunnel_paths[destination_hash]
|
||||
|
||||
timestamp = de[0]
|
||||
received_from = de[1]
|
||||
hops = de[2]
|
||||
expires = de[3]
|
||||
random_blobs = de[4]
|
||||
packet_hash = de[6].get_hash()
|
||||
|
||||
serialised_entry = [
|
||||
destination_hash,
|
||||
timestamp,
|
||||
received_from,
|
||||
hops,
|
||||
expires,
|
||||
random_blobs,
|
||||
interface_hash,
|
||||
packet_hash
|
||||
]
|
||||
|
||||
serialised_paths.append(serialised_entry)
|
||||
|
||||
Transport.cache(de[6], force_cache=True)
|
||||
|
||||
|
||||
serialised_tunnel = [tunnel_id, interface_hash, serialised_paths, expires]
|
||||
serialised_tunnels.append(serialised_tunnel)
|
||||
|
||||
tunnels_path = RNS.Reticulum.storagepath+"/tunnels"
|
||||
file = open(tunnels_path, "wb")
|
||||
file.write(umsgpack.packb(serialised_tunnels))
|
||||
file.close()
|
||||
RNS.log("Done saving "+str(len(serialised_tunnels))+" tunnel table entries to storage", RNS.LOG_VERBOSE)
|
||||
except Exception as e:
|
||||
RNS.log("Could not save tunnel table to storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -9,7 +9,7 @@ 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
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.2.2"
|
||||
__version__ = "0.2.5"
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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: 966ae7177c1d48c9ee15971994c623b5
|
||||
config: ca50760ac810704080bd9b8fd39823dc
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
|
After Width: | Height: | Size: 86 KiB |
@@ -12,13 +12,25 @@ If you simply want to try using a program built with Reticulum, you can take
|
||||
a look at `Nomad Network <https://github.com/markqvist/nomadnet>`_, which
|
||||
provides a basic encrypted communications suite built completely on Reticulum.
|
||||
|
||||
.. image:: screenshots/nomadnet3.png
|
||||
:target: _images/nomadnet3.png
|
||||
.. image:: screenshots/nomadnet_3.png
|
||||
:target: _images/nomadnet_3.png
|
||||
|
||||
`Nomad Network <https://github.com/markqvist/nomadnet>`_ is a user-facing client
|
||||
in the development for the messaging and information-sharing protocol
|
||||
`LXMF <https://github.com/markqvist/lxmf>`_, another project built with Reticulum.
|
||||
|
||||
You can install Nomad Network via pip:
|
||||
|
||||
.. code::
|
||||
|
||||
# Install
|
||||
pip3 install nomadnet
|
||||
|
||||
# And run
|
||||
nomadnet
|
||||
|
||||
|
||||
|
||||
Develop a Program with Reticulum
|
||||
===========================================
|
||||
If you want to develop programs that use Reticulum, the easiest way to get
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -429,7 +429,7 @@ terms of bandwidth, so it can be used just for a short exchange, and then recrea
|
||||
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 240 bytes (more info in the
|
||||
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.
|
||||
@@ -701,5 +701,5 @@ Binary Packet Format
|
||||
- Announce : 151 bytes
|
||||
- Link Request : 77 bytes
|
||||
- Link Proof : 77 bytes
|
||||
- Link RTT packet : 86 bytes
|
||||
- Link RTT packet : 83 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
@@ -31,7 +31,9 @@ What does Reticulum Offer?
|
||||
|
||||
* 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
|
||||
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
|
||||
|
||||
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
@@ -39,8 +41,6 @@ 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
|
||||
@@ -57,7 +57,7 @@ What does Reticulum Offer?
|
||||
|
||||
* Efficient link establishment
|
||||
|
||||
* Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes
|
||||
* 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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.2.2 beta',
|
||||
VERSION: '0.2.5 beta',
|
||||
LANGUAGE: 'None',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Examples — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Examples — Reticulum Network Stack 0.2.5 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="reference.html" title="API Reference"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -2017,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">""</span><span class="p">)</span>
|
||||
<span class="k">while</span> <span class="n">menu_mode</span> <span class="o">==</span> <span class="s2">"downloading"</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">"</span><span class="se">\r</span><span class="s2">Progress: "</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">" % "</span><span class="p">),</span> <span class="n">end</span><span class="o">=</span><span class="s1">' '</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>
|
||||
@@ -2319,7 +2319,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<li class="right" >
|
||||
<a href="reference.html" title="API Reference"
|
||||
>previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Examples</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Index — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Index — Reticulum Network Stack 0.2.5 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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</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>
|
||||
@@ -98,8 +99,6 @@
|
||||
<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.Transport.deregister_announce_handler">deregister_announce_handler() (RNS.Transport static method)</a>
|
||||
@@ -107,8 +106,6 @@
|
||||
<li><a href="reference.html#RNS.Destination.deregister_request_handler">deregister_request_handler() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination">Destination (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.disable_encryption">disable_encryption() (RNS.Link method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
@@ -125,6 +122,8 @@
|
||||
</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>
|
||||
@@ -152,20 +151,36 @@
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Identity.get_private_key">(RNS.Identity method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<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>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<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.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>
|
||||
|
||||
@@ -174,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>
|
||||
@@ -227,6 +244,14 @@
|
||||
</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>
|
||||
@@ -248,9 +273,9 @@
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
|
||||
<li><a href="reference.html#RNS.Transport.PATHFINDER_M">PATHFINDER_M (RNS.Transport attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.progress">progress() (RNS.Resource method)</a>
|
||||
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
@@ -266,11 +291,13 @@
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<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>
|
||||
@@ -317,8 +344,6 @@
|
||||
<li><a href="reference.html#RNS.PacketReceipt.set_timeout">set_timeout() (RNS.PacketReceipt method)</a>
|
||||
</li>
|
||||
<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_allow_unencrypted">should_allow_unencrypted() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.should_use_implicit_proof">should_use_implicit_proof() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
@@ -387,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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Index</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Getting Started Fast — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Getting Started Fast — Reticulum Network Stack 0.2.5 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="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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -51,10 +51,18 @@ scenarios.</p>
|
||||
<p>If you simply want to try using a program built with Reticulum, you can take
|
||||
a look at <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a>, which
|
||||
provides a basic encrypted communications suite built completely on Reticulum.</p>
|
||||
<a class="reference external image-reference" href="_images/nomadnet3.png"><img alt="_images/nomadnet3.png" src="_images/nomadnet3.png" /></a>
|
||||
<a class="reference external image-reference" href="_images/nomadnet_3.png"><img alt="_images/nomadnet_3.png" src="_images/nomadnet_3.png" /></a>
|
||||
<p><a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> is a user-facing client
|
||||
in the development for the messaging and information-sharing protocol
|
||||
<a class="reference external" href="https://github.com/markqvist/lxmf">LXMF</a>, another project built with Reticulum.</p>
|
||||
<p>You can install Nomad Network via pip:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">nomadnet</span>
|
||||
|
||||
<span class="c1"># And run</span>
|
||||
<span class="n">nomadnet</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<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>
|
||||
@@ -166,7 +174,7 @@ don’t use pip, but try this recipe:</p>
|
||||
<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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Getting Started Fast</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -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 — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Reticulum Network Stack Manual — Reticulum Network Stack 0.2.5 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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -91,6 +91,7 @@ the development of Reticulum itself.</p>
|
||||
<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>
|
||||
@@ -167,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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="#">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Reticulum Network Stack Manual</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>API Reference — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>API Reference — Reticulum Network Stack 0.2.5 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="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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</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">
|
||||
@@ -215,6 +215,21 @@ for addressable hashes and other purposes. 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>
|
||||
@@ -231,16 +246,17 @@ Can be used to load previously created and saved identities into Reticulum.</p>
|
||||
</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>
|
||||
<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>prv_bytes</strong> – The <em>bytes</em> of private a saved private key. <strong>HAZARD!</strong> Never not use this to generate a new key by feeding random data in prv_bytes.</p>
|
||||
<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>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 class="field-even"><p>True if the file was saved, otherwise False.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
@@ -293,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>
|
||||
@@ -398,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>
|
||||
@@ -645,7 +645,7 @@ 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>
|
||||
<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,
|
||||
@@ -660,11 +660,6 @@ destinations, reticulum will use ephemeral keys, and offers <strong>Forward Secr
|
||||
<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>
|
||||
@@ -709,11 +704,11 @@ destinations, reticulum will use ephemeral keys, and offers <strong>Forward Secr
|
||||
<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>
|
||||
@@ -774,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>
|
||||
@@ -793,9 +789,9 @@ 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">
|
||||
@@ -820,16 +816,21 @@ thus preserved. This method can be used for authentication.</p>
|
||||
|
||||
<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><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.request" title="Permalink to this definition">¶</a></dt>
|
||||
<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> – A 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> – A 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>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>
|
||||
|
||||
@@ -956,16 +957,65 @@ identified 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>
|
||||
@@ -975,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">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>, <em class="sig-param"><span class="n"><span class="pre">request_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">is_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.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>
|
||||
@@ -984,14 +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>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>is_request</strong> – Internal use, ignore.</p></li>
|
||||
<li><p><strong>is_response</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>
|
||||
@@ -1009,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>
|
||||
@@ -1026,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 the
|
||||
Transport system of Reticulum.</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>
|
||||
@@ -1061,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>
|
||||
@@ -1097,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>
|
||||
@@ -1144,7 +1212,7 @@ will announce it.</p>
|
||||
<li class="right" >
|
||||
<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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">API Reference</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Search — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Search — Reticulum Network Stack 0.2.5 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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Search</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Understanding Reticulum — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>Understanding Reticulum — Reticulum Network Stack 0.2.5 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="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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -490,7 +490,7 @@ At the same time we establish an efficient encrypted channel. The setup of this
|
||||
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 240 bytes (more info in the
|
||||
<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>
|
||||
@@ -764,7 +764,7 @@ proof 11
|
||||
- Announce : 151 bytes
|
||||
- Link Request : 77 bytes
|
||||
- Link Proof : 77 bytes
|
||||
- Link RTT packet : 86 bytes
|
||||
- Link RTT packet : 83 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -853,7 +853,7 @@ proof 11
|
||||
<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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Understanding Reticulum</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>What is Reticulum? — Reticulum Network Stack 0.2.2 beta documentation</title>
|
||||
<title>What is Reticulum? — Reticulum Network Stack 0.2.5 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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -62,12 +62,12 @@
|
||||
<li><p>Fully self-configuring multi-hop routing</p></li>
|
||||
<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>
|
||||
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for on-the-wire / over-the-air encryption</p>
|
||||
<ul>
|
||||
<li><p>All keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
|
||||
<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>
|
||||
@@ -82,7 +82,7 @@
|
||||
</li>
|
||||
<li><p>Efficient link establishment</p>
|
||||
<ul>
|
||||
<li><p>Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes</p></li>
|
||||
<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>
|
||||
@@ -182,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.2 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Reticulum Network Stack 0.2.5 beta documentation</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">What is Reticulum?</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,7 @@ copyright = '2021, Mark Qvist'
|
||||
author = 'Mark Qvist'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = '0.2.2 beta'
|
||||
release = '0.2.5 beta'
|
||||
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
@@ -12,13 +12,25 @@ If you simply want to try using a program built with Reticulum, you can take
|
||||
a look at `Nomad Network <https://github.com/markqvist/nomadnet>`_, which
|
||||
provides a basic encrypted communications suite built completely on Reticulum.
|
||||
|
||||
.. image:: screenshots/nomadnet3.png
|
||||
:target: _images/nomadnet3.png
|
||||
.. image:: screenshots/nomadnet_3.png
|
||||
:target: _images/nomadnet_3.png
|
||||
|
||||
`Nomad Network <https://github.com/markqvist/nomadnet>`_ is a user-facing client
|
||||
in the development for the messaging and information-sharing protocol
|
||||
`LXMF <https://github.com/markqvist/lxmf>`_, another project built with Reticulum.
|
||||
|
||||
You can install Nomad Network via pip:
|
||||
|
||||
.. code::
|
||||
|
||||
# Install
|
||||
pip3 install nomadnet
|
||||
|
||||
# And run
|
||||
nomadnet
|
||||
|
||||
|
||||
|
||||
Develop a Program with Reticulum
|
||||
===========================================
|
||||
If you want to develop programs that use Reticulum, the easiest way to get
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
Before Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 79 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 68 KiB |
@@ -429,7 +429,7 @@ terms of bandwidth, so it can be used just for a short exchange, and then recrea
|
||||
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 240 bytes (more info in the
|
||||
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.
|
||||
@@ -701,5 +701,5 @@ Binary Packet Format
|
||||
- Announce : 151 bytes
|
||||
- Link Request : 77 bytes
|
||||
- Link Proof : 77 bytes
|
||||
- Link RTT packet : 86 bytes
|
||||
- Link RTT packet : 83 bytes
|
||||
- Link keepalive : 14 bytes
|
||||
@@ -31,7 +31,9 @@ What does Reticulum Offer?
|
||||
|
||||
* 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
|
||||
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
|
||||
|
||||
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
@@ -39,8 +41,6 @@ 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
|
||||
@@ -57,7 +57,7 @@ What does Reticulum Offer?
|
||||
|
||||
* Efficient link establishment
|
||||
|
||||
* Total bandwidth cost of setting up a link is only 3 packets, totalling 240 bytes
|
||||
* 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
|
||||
|
||||
|
||||