mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-23 04:16:12 -07:00
Compare commits
152 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ec77a10d3 | |||
| 55a9c5ef71 | |||
| 6d3ba31993 | |||
| d3f4a674aa | |||
| 599ab20ed0 | |||
| dcf33e125b | |||
| 01600b96a4 | |||
| 64bdc4c18c | |||
| 0889b8a7c5 | |||
| 1b2fee3ab8 | |||
| da7a4433c0 | |||
| 5e5d89cc92 | |||
| a3bee4baa9 | |||
| fab83ec399 | |||
| b740e36985 | |||
| 29693c6fe2 | |||
| 72638f40a6 | |||
| 8d29e83d90 | |||
| 53b325d34d | |||
| d31cf6e297 | |||
| e386a5d08b | |||
| d467ed9ece | |||
| 892a467d74 | |||
| 4366e71f34 | |||
| 7e9998b4fd | |||
| 79abe93139 | |||
| d69d4b3920 | |||
| 3300541181 | |||
| 3848059f19 | |||
| 30021d89cb | |||
| 29019724bd | |||
| ba7838c04e | |||
| af16c68e47 | |||
| bda5717051 | |||
| fac4973329 | |||
| 90cfaa4e82 | |||
| 443aa575df | |||
| 619771c3a3 | |||
| 18a56cfd52 | |||
| 55c39ff27c | |||
| 159c7a9a52 | |||
| af8edc335b | |||
| 4d3ea37bc3 | |||
| 226004da94 | |||
| 47b358351f | |||
| f5d77a1dfb | |||
| 9c9f0a20f9 | |||
| 6d9d410a70 | |||
| d8f3ad8d3f | |||
| a1b75b9746 | |||
| 80f3bfaece | |||
| 37b2d8a6ec | |||
| 777fea9cea | |||
| bbfdd37935 | |||
| 07484725a0 | |||
| 709b126a67 | |||
| 28e6302b3d | |||
| 27861e96f8 | |||
| e36312a3cb | |||
| 5b5dbdaa91 | |||
| 99dc97365f | |||
| aac2b9f987 | |||
| 067c275c46 | |||
| 58004d7c05 | |||
| aa0d9c5c13 | |||
| 9e46950e28 | |||
| a6551fc019 | |||
| a06ae40797 | |||
| 1db08438df | |||
| 89aa51ab61 | |||
| ddb7a92c15 | |||
| e273900e87 | |||
| d2d121d49f | |||
| 9963cf37b8 | |||
| 72300cc821 | |||
| 8168d9bb92 | |||
| 8f0151fed6 | |||
| d3c4928eda | |||
| 68f95cd80b | |||
| 42935c8238 | |||
| 118acf77b8 | |||
| 661964277f | |||
| 464dc23ff0 | |||
| 44dc2d06c6 | |||
| c00b592ed9 | |||
| e005826151 | |||
| a61b15cf6a | |||
| fe3a3e22f7 | |||
| 68cb4a6740 | |||
| 9f06bed34c | |||
| 3b1936ef48 | |||
| 5b3d26a90a | |||
| b381a61be8 | |||
| 1e2fa2068c | |||
| c604214bb9 | |||
| e738c9561a | |||
| 994d1c8ee5 | |||
| ce21800537 | |||
| d02cdd5471 | |||
| 7018e412d5 | |||
| 94f7505076 | |||
| b82ecf047a | |||
| f21b93403a | |||
| 59c88bc43b | |||
| 8e98c1b038 | |||
| 4d3570fe4c | |||
| 3706769c33 | |||
| ce91c34b21 | |||
| e37aa5e51a | |||
| 80af0f4539 | |||
| fc818f00f1 | |||
| a55d39b7d4 | |||
| 8e264359db | |||
| cbaeaa9f81 | |||
| 323c2285ce | |||
| 5b6d0ec337 | |||
| 2bbb0f5ec2 | |||
| e385c79abd | |||
| 86faf6c28d | |||
| 6d8a3f09e5 | |||
| 1e88a390f4 | |||
| e9ae255f84 | |||
| 42dfee8557 | |||
| 177e724457 | |||
| 1b55ac7f24 | |||
| 5447ed85c1 | |||
| d7aacba797 | |||
| b92ddeccff | |||
| 6fac96ec18 | |||
| 53ceafcebd | |||
| 4df67304d6 | |||
| ac07ba1368 | |||
| ece064d46e | |||
| 86ae42a049 | |||
| 08e480387b | |||
| f4241ae9c2 | |||
| b6928b7d83 | |||
| 3b2fbe02c6 | |||
| a38bde7801 | |||
| df132d1d59 | |||
| 143f7fa683 | |||
| feb614d186 | |||
| 159be78f23 | |||
| 4a6c6568e2 | |||
| e64fa08c74 | |||
| 6651976423 | |||
| 5decf22b8b | |||
| a731a8b047 | |||
| 9bb9571fc9 | |||
| 6ecae615de | |||
| 72ca6316f6 | |||
| 0f023cc533 |
+82
-1
@@ -1,3 +1,84 @@
|
||||
### 2023-03-08: RNS β 0.5.1
|
||||
|
||||
This maintenance release brings a number of bugfixes and improvements. Thanks to @VioletEternity, who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Removed dependency on netifaces module
|
||||
- Added ability to configure RNode display intensity to rnodeconf
|
||||
- Added preliminary rnodeconf flasher/autoinstaller support for T3 v1.0 boards
|
||||
- Fixed a bug that caused AutoInterface discovery scopes to fail
|
||||
- Fixed rnodeconf firmware extraction for unverifiable devices
|
||||
- Improved setting rnsd verbosity from command line
|
||||
- Improved support for shared instances on Windows
|
||||
- Improved rnodeconf support on Windows
|
||||
- Improved rnodeconf zip-file handling
|
||||
- Fixed a potentail race condition in announce queue handling for AutoInterface
|
||||
- Various minor bugfixes
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
01d76e03f93e427d9c0b95ab5d07e84ed39047e912b8afa6d619a65ac6b5e05b rns-0.5.1-py3-none-any.whl
|
||||
2cfe431bec1160410b80bbcbf87eb2ab0d5abe5c6546f41eaf3f0f5faf9b2140 rnspure-0.5.1-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-03-08: RNS β 0.5.0
|
||||
|
||||
This release brings two major new additions to the Reticulum API: The Channel and Buffer classes, that provides reliable delivery, and streams over Reticulum. Thanks to @acehoss, @erethon, @gdt and @faragher, who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Added the Buffer class to the API
|
||||
- Added the Channel class to the API
|
||||
- Improved error messages for offline RNode flashing
|
||||
- Improved RNode reconnection when serial device disappears
|
||||
- Fixed embedded scope identifier handling for AutoInterface on BSD
|
||||
- Fixed AutoInterface not ignoring lo0 on BSD
|
||||
- Fixed a bug causing JSON output from rnstatus to fail
|
||||
- Fixed invalid installation of test suite into root module path
|
||||
- Added EPUB version of the documentation
|
||||
- Updated documentation
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
0aaf8c0b0b58f07071de5ecd432f4d9cc176b9614419c828b81ad71aa7151624 rns-0.5.0-py3-none-any.whl
|
||||
f310a5192c2df7665339c5998ae13815a647283af75b95ad7acbee8c20989954 rnspure-0.5.0-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-02-17: RNS β 0.4.9
|
||||
|
||||
This maintenance release contains a number of bugfixes and minor improvements, along with a few additions to the API.
|
||||
|
||||
**Changes**
|
||||
- Added JSON output mode to rnstatus
|
||||
- Added Link ID to response_generator callback
|
||||
- Added Link establishment rate calculation
|
||||
- Added get_establishment_rate call to Link API
|
||||
- Fixed a number of typos in programs and documentation
|
||||
- Fixed some broken links in documentation
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
b44eaed796dcd194bec7a541aaeeb1685b07b2ffce068ca268841e6a8661717f rns-0.4.9-py3-none-any.whl
|
||||
a15f965a27d208493485724486eb6bc6268d699f2a22ae4fb816bb9b979330fc rnspure-0.4.9-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-02-04: RNS β 0.4.8
|
||||
|
||||
This release introduces the useful `rnid` utility, which makes it possible to use Reticulum Identities for offline file encryption, decryption, signing and validation. The IFAC system has also been significantly improved, and several outdated parts of the documentation was updated and fixed. Thanks to @Erethon and @jooray who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Added header and payload masking to the IFAC system
|
||||
- Added `rnid` utility for encrypting, decrypting, signing and validating with Reticulum Identities
|
||||
- Added Bluetooth pairing PIN output to `rnodeconf` utility
|
||||
- Fixed a bug in announce callback handling
|
||||
- Fixed a inconsistency in header flag handling since IFACs were introduced
|
||||
- Updated documentation and manual
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
fbbd55ee43a68c18491f5deabed51085c46fadca7e1bda823ad455c2f7c95a51 rns-0.4.8-py3-none-any.whl
|
||||
335b0d5dd1d2aacd0d8810191aa09567ecf5d3aa990c446f3e3b1bbf7fce1387 rnspure-0.4.8-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2023-01-14: RNS β 0.4.7
|
||||
|
||||
This maintenance release adds support for using the `rnodeconf` utility to replicate RNode devices, and bootstrap device creation using only tools and software packages obtained from an RNode Bootstrap Console.
|
||||
@@ -662,4 +743,4 @@ This was the first publicly available pre-release alpha of Reticulum.
|
||||
|
||||
### 2016-05-29: Inintial Repository Commit
|
||||
|
||||
The first commit to the Reticulum reference implementation was 9a9630cfd29e11ace3f12716ddb4dff0e5419b4b, which occurred on Sunday, the 22nd of May 2016.
|
||||
The first commit to the Reticulum reference implementation was 9a9630cfd29e11ace3f12716ddb4dff0e5419b4b, which occurred on Sunday, the 22nd of May 2016.
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
# Contributing to Reticulum
|
||||
|
||||
Welcome, and thank you for your interest in contributing to Reticulum!
|
||||
|
||||
Apart from writing code, there are many ways in which you can contribute. Before getting started, please read these guidelines.
|
||||
|
||||
## Asking Questions
|
||||
|
||||
If you want to ask a question, do not open an issue.
|
||||
|
||||
Instead, ask away on the [discussions](https://github.com/markqvist/Reticulum/discussions) or on the [Reticulum Matrix channel](https://unsigned.io/contact.html#reticulum:matrix.org) at `#reticulum:matrix.org`
|
||||
|
||||
## Providing Feedback
|
||||
|
||||
Likewise, feedback, ideas and feature requests are a very welcome way to contribute, and should also be posted on the [discussions](https://github.com/markqvist/Reticulum/discussions), or on the [Reticulum Matrix channel](https://unsigned.io/contact.html#reticulum:matrix.org) at `#reticulum:matrix.org`
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
If you have found a bug or issue in Reticulum, please report it on the [issue tracker](https://github.com/markqvist/Reticulum/issues).
|
||||
|
||||
## Writing Code
|
||||
|
||||
If you are interested in contributing code, fixing open issues or adding features, please coordinate the effort with the maintainer or one of the main developers first, to ensure your efforts are in alignment with the [Roadmap](./Roadmap.md) and current development focus.
|
||||
@@ -0,0 +1,323 @@
|
||||
##########################################################
|
||||
# This RNS example demonstrates how to set up a link to #
|
||||
# a destination, and pass binary data over it using a #
|
||||
# channel buffer. #
|
||||
##########################################################
|
||||
from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
import RNS
|
||||
from RNS.vendor import umsgpack
|
||||
|
||||
# Let's define an app name. We'll use this for all
|
||||
# destinations we create. Since this echo example
|
||||
# is part of a range of example utilities, we'll put
|
||||
# them all within the app namespace "example_utilities"
|
||||
APP_NAME = "example_utilities"
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Server Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the latest client link that connected
|
||||
latest_client_link = None
|
||||
|
||||
# A reference to the latest buffer object
|
||||
latest_buffer = None
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a server
|
||||
def server(configpath):
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Randomly create a new identity for our 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,
|
||||
"bufferexample"
|
||||
)
|
||||
|
||||
# We configure a function that will get called every time
|
||||
# a new client creates a link to this destination.
|
||||
server_destination.set_link_established_callback(client_connected)
|
||||
|
||||
# Everything's ready!
|
||||
# Let's Wait for client requests or user input
|
||||
server_loop(server_destination)
|
||||
|
||||
def server_loop(destination):
|
||||
# Let the user know that everything is ready
|
||||
RNS.log(
|
||||
"Link buffer example "+
|
||||
RNS.prettyhexrep(destination.hash)+
|
||||
" running, waiting for a connection."
|
||||
)
|
||||
|
||||
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
|
||||
|
||||
# We enter a loop that runs until the users exits.
|
||||
# If the user hits enter, we will announce our server
|
||||
# destination on the network, which will let clients
|
||||
# know how to create messages directed towards it.
|
||||
while True:
|
||||
entered = input()
|
||||
destination.announce()
|
||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||
|
||||
# When a client establishes a link to our server
|
||||
# destination, this function will be called with
|
||||
# a reference to the link.
|
||||
def client_connected(link):
|
||||
global latest_client_link, latest_buffer
|
||||
latest_client_link = link
|
||||
|
||||
RNS.log("Client connected")
|
||||
link.set_link_closed_callback(client_disconnected)
|
||||
|
||||
# If a new connection is received, the old reader
|
||||
# needs to be disconnected.
|
||||
if latest_buffer:
|
||||
latest_buffer.close()
|
||||
|
||||
|
||||
# Create buffer objects.
|
||||
# The stream_id parameter to these functions is
|
||||
# a bit like a file descriptor, except that it
|
||||
# is unique to the *receiver*.
|
||||
#
|
||||
# In this example, both the reader and the writer
|
||||
# use stream_id = 0, but there are actually two
|
||||
# separate unidirectional streams flowing in
|
||||
# opposite directions.
|
||||
#
|
||||
channel = link.get_channel()
|
||||
latest_buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, server_buffer_ready)
|
||||
|
||||
def client_disconnected(link):
|
||||
RNS.log("Client disconnected")
|
||||
|
||||
def server_buffer_ready(ready_bytes: int):
|
||||
"""
|
||||
Callback from buffer when buffer has data available
|
||||
|
||||
:param ready_bytes: The number of bytes ready to read
|
||||
"""
|
||||
global latest_buffer
|
||||
|
||||
data = latest_buffer.read(ready_bytes)
|
||||
data = data.decode("utf-8")
|
||||
|
||||
RNS.log("Received data over the buffer: " + data)
|
||||
|
||||
reply_message = "I received \""+data+"\" over the buffer"
|
||||
reply_message = reply_message.encode("utf-8")
|
||||
latest_buffer.write(reply_message)
|
||||
latest_buffer.flush()
|
||||
|
||||
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Client Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the server link
|
||||
server_link = None
|
||||
|
||||
# A reference to the buffer object, needed to share the
|
||||
# object from the link connected callback to the client
|
||||
# loop.
|
||||
buffer = None
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a client
|
||||
def client(destination_hexhash, configpath):
|
||||
# We need a binary representation of the destination
|
||||
# hash that was entered on the command line
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
if len(destination_hexhash) != dest_len:
|
||||
raise ValueError(
|
||||
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
|
||||
)
|
||||
|
||||
destination_hash = bytes.fromhex(destination_hexhash)
|
||||
except:
|
||||
RNS.log("Invalid destination entered. Check your input!\n")
|
||||
exit()
|
||||
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Check if we know a path to the destination
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
while not RNS.Transport.has_path(destination_hash):
|
||||
time.sleep(0.1)
|
||||
|
||||
# Recall the server identity
|
||||
server_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
# Inform the user that we'll begin connecting
|
||||
RNS.log("Establishing link with server...")
|
||||
|
||||
# When the server identity is known, we set
|
||||
# up a destination
|
||||
server_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"bufferexample"
|
||||
)
|
||||
|
||||
# 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:
|
||||
print("> ", end=" ")
|
||||
text = input()
|
||||
|
||||
# Check if we should quit the example
|
||||
if text == "quit" or text == "q" or text == "exit":
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
else:
|
||||
# Otherwise, encode the text and write it to the buffer.
|
||||
text = text.encode("utf-8")
|
||||
buffer.write(text)
|
||||
# Flush the buffer to force the data to be sent.
|
||||
buffer.flush()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while sending data over the link buffer: "+str(e))
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
# This function is called when a link
|
||||
# has been established with the server
|
||||
def link_established(link):
|
||||
# We store a reference to the link
|
||||
# instance for later use
|
||||
global server_link, buffer
|
||||
server_link = link
|
||||
|
||||
# Create buffer, see server_client_connected() for
|
||||
# more detail about setting up the buffer.
|
||||
channel = link.get_channel()
|
||||
buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, client_buffer_ready)
|
||||
|
||||
# Inform the user that the server is
|
||||
# connected
|
||||
RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
|
||||
|
||||
# When a link is closed, we'll inform the
|
||||
# user, and exit the program
|
||||
def link_closed(link):
|
||||
if link.teardown_reason == RNS.Link.TIMEOUT:
|
||||
RNS.log("The link timed out, exiting now")
|
||||
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
|
||||
RNS.log("The link was closed by the server, exiting now")
|
||||
else:
|
||||
RNS.log("Link closed, exiting now")
|
||||
|
||||
RNS.Reticulum.exit_handler()
|
||||
time.sleep(1.5)
|
||||
os._exit(0)
|
||||
|
||||
# When the buffer has new data, read it and write it to the terminal.
|
||||
def client_buffer_ready(ready_bytes: int):
|
||||
global buffer
|
||||
data = buffer.read(ready_bytes)
|
||||
RNS.log("Received data over the link buffer: " + data.decode("utf-8"))
|
||||
print("> ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Program Startup #####################################
|
||||
##########################################################
|
||||
|
||||
# This part of the program runs at startup,
|
||||
# and parses input of from the user, and then
|
||||
# starts up the desired program mode.
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Simple buffer example")
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--server",
|
||||
action="store_true",
|
||||
help="wait for incoming link requests from clients"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
action="store",
|
||||
default=None,
|
||||
help="path to alternative Reticulum config directory",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="hexadecimal hash of the server destination",
|
||||
type=str
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
if args.server:
|
||||
server(configarg)
|
||||
else:
|
||||
if (args.destination == None):
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
else:
|
||||
client(args.destination, configarg)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
@@ -0,0 +1,390 @@
|
||||
##########################################################
|
||||
# This RNS example demonstrates how to set up a link to #
|
||||
# a destination, and pass structured messages over it #
|
||||
# using a channel. #
|
||||
##########################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
|
||||
import RNS
|
||||
from RNS.vendor import umsgpack
|
||||
|
||||
# Let's define an app name. We'll use this for all
|
||||
# destinations we create. Since this echo example
|
||||
# is part of a range of example utilities, we'll put
|
||||
# them all within the app namespace "example_utilities"
|
||||
APP_NAME = "example_utilities"
|
||||
|
||||
##########################################################
|
||||
#### Shared Objects ######################################
|
||||
##########################################################
|
||||
|
||||
# Channel data must be structured in a subclass of
|
||||
# MessageBase. This ensures that the channel will be able
|
||||
# to serialize and deserialize the object and multiplex it
|
||||
# with other objects. Both ends of a link will need the
|
||||
# same object definitions to be able to communicate over
|
||||
# a channel.
|
||||
#
|
||||
# Note: The objects we wish to use over the channel must
|
||||
# be registered with the channel, and each link has a
|
||||
# different channel instance. See the client_connected
|
||||
# and link_established functions in this example to see
|
||||
# how message types are registered.
|
||||
|
||||
# Let's make a simple message class called StringMessage
|
||||
# that will convey a string with a timestamp.
|
||||
|
||||
class StringMessage(RNS.MessageBase):
|
||||
# The MSGTYPE class variable needs to be assigned a
|
||||
# 2 byte integer value. This identifier allows the
|
||||
# channel to look up your message's constructor when a
|
||||
# message arrives over the channel.
|
||||
#
|
||||
# MSGTYPE must be unique across all message types we
|
||||
# register with the channel. MSGTYPEs >= 0xf000 are
|
||||
# reserved for the system.
|
||||
MSGTYPE = 0x0101
|
||||
|
||||
# The constructor of our object must be callable with
|
||||
# no arguments. We can have parameters, but they must
|
||||
# have a default assignment.
|
||||
#
|
||||
# This is needed so the channel can create an empty
|
||||
# version of our message into which the incoming
|
||||
# message can be unpacked.
|
||||
def __init__(self, data=None):
|
||||
self.data = data
|
||||
self.timestamp = datetime.now()
|
||||
|
||||
# Finally, our message needs to implement functions
|
||||
# the channel can call to pack and unpack our message
|
||||
# to/from the raw packet payload. We'll use the
|
||||
# umsgpack package bundled with RNS. We could also use
|
||||
# the struct package bundled with Python if we wanted
|
||||
# more control over the structure of the packed bytes.
|
||||
#
|
||||
# Also note that packed message objects must fit
|
||||
# entirely in one packet. The number of bytes
|
||||
# available for message payloads can be queried from
|
||||
# the channel using the Channel.MDU property. The
|
||||
# channel MDU is slightly less than the link MDU due
|
||||
# to encoding the message header.
|
||||
|
||||
# The pack function encodes the message contents into
|
||||
# a byte stream.
|
||||
def pack(self) -> bytes:
|
||||
return umsgpack.packb((self.data, self.timestamp))
|
||||
|
||||
# And the unpack function decodes a byte stream into
|
||||
# the message contents.
|
||||
def unpack(self, raw):
|
||||
self.data, self.timestamp = umsgpack.unpackb(raw)
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Server Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the latest client link that connected
|
||||
latest_client_link = None
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a server
|
||||
def server(configpath):
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Randomly create a new identity for our link example
|
||||
server_identity = RNS.Identity()
|
||||
|
||||
# We create a destination that clients can connect to. We
|
||||
# want clients to create links to this destination, so we
|
||||
# need to create a "single" destination type.
|
||||
server_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.IN,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"channelexample"
|
||||
)
|
||||
|
||||
# We configure a function that will get called every time
|
||||
# a new client creates a link to this destination.
|
||||
server_destination.set_link_established_callback(client_connected)
|
||||
|
||||
# Everything's ready!
|
||||
# Let's Wait for client requests or user input
|
||||
server_loop(server_destination)
|
||||
|
||||
def server_loop(destination):
|
||||
# Let the user know that everything is ready
|
||||
RNS.log(
|
||||
"Link example "+
|
||||
RNS.prettyhexrep(destination.hash)+
|
||||
" running, waiting for a connection."
|
||||
)
|
||||
|
||||
RNS.log("Hit enter to manually send an announce (Ctrl-C to quit)")
|
||||
|
||||
# We enter a loop that runs until the users exits.
|
||||
# If the user hits enter, we will announce our server
|
||||
# destination on the network, which will let clients
|
||||
# know how to create messages directed towards it.
|
||||
while True:
|
||||
entered = input()
|
||||
destination.announce()
|
||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||
|
||||
# When a client establishes a link to our server
|
||||
# destination, this function will be called with
|
||||
# a reference to the link.
|
||||
def client_connected(link):
|
||||
global latest_client_link
|
||||
latest_client_link = link
|
||||
|
||||
RNS.log("Client connected")
|
||||
link.set_link_closed_callback(client_disconnected)
|
||||
|
||||
# Register message types and add callback to channel
|
||||
channel = link.get_channel()
|
||||
channel.register_message_type(StringMessage)
|
||||
channel.add_message_handler(server_message_received)
|
||||
|
||||
def client_disconnected(link):
|
||||
RNS.log("Client disconnected")
|
||||
|
||||
def server_message_received(message):
|
||||
"""
|
||||
A message handler
|
||||
@param message: An instance of a subclass of MessageBase
|
||||
@return: True if message was handled
|
||||
"""
|
||||
global latest_client_link
|
||||
# When a message is received over any active link,
|
||||
# the replies will all be directed to the last client
|
||||
# that connected.
|
||||
|
||||
# In a message handler, any deserializable message
|
||||
# that arrives over the link's channel will be passed
|
||||
# to all message handlers, unless a preceding handler indicates it
|
||||
# has handled the message.
|
||||
#
|
||||
#
|
||||
if isinstance(message, StringMessage):
|
||||
RNS.log("Received data on the link: " + message.data + " (message created at " + str(message.timestamp) + ")")
|
||||
|
||||
reply_message = StringMessage("I received \""+message.data+"\" over the link")
|
||||
latest_client_link.get_channel().send(reply_message)
|
||||
|
||||
# Incoming messages are sent to each message
|
||||
# handler added to the channel, in the order they
|
||||
# were added.
|
||||
# If any message handler returns True, the message
|
||||
# is considered handled and any subsequent
|
||||
# handlers are skipped.
|
||||
return True
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Client Part #########################################
|
||||
##########################################################
|
||||
|
||||
# A reference to the server link
|
||||
server_link = None
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a client
|
||||
def client(destination_hexhash, configpath):
|
||||
# We need a binary representation of the destination
|
||||
# hash that was entered on the command line
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
if len(destination_hexhash) != dest_len:
|
||||
raise ValueError(
|
||||
"Destination length is invalid, must be {hex} hexadecimal characters ({byte} bytes).".format(hex=dest_len, byte=dest_len//2)
|
||||
)
|
||||
|
||||
destination_hash = bytes.fromhex(destination_hexhash)
|
||||
except:
|
||||
RNS.log("Invalid destination entered. Check your input!\n")
|
||||
exit()
|
||||
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Check if we know a path to the destination
|
||||
if not RNS.Transport.has_path(destination_hash):
|
||||
RNS.log("Destination is not yet known. Requesting path and waiting for announce to arrive...")
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
while not RNS.Transport.has_path(destination_hash):
|
||||
time.sleep(0.1)
|
||||
|
||||
# Recall the server identity
|
||||
server_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
# Inform the user that we'll begin connecting
|
||||
RNS.log("Establishing link with server...")
|
||||
|
||||
# When the server identity is known, we set
|
||||
# up a destination
|
||||
server_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"channelexample"
|
||||
)
|
||||
|
||||
# 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:
|
||||
print("> ", end=" ")
|
||||
text = input()
|
||||
|
||||
# Check if we should quit the example
|
||||
if text == "quit" or text == "q" or text == "exit":
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
# If not, send the entered text over the link
|
||||
if text != "":
|
||||
message = StringMessage(text)
|
||||
packed_size = len(message.pack())
|
||||
channel = server_link.get_channel()
|
||||
if channel.is_ready_to_send():
|
||||
if packed_size <= channel.MDU:
|
||||
channel.send(message)
|
||||
else:
|
||||
RNS.log(
|
||||
"Cannot send this packet, the data size of "+
|
||||
str(packed_size)+" bytes exceeds the link packet MDU of "+
|
||||
str(channel.MDU)+" bytes",
|
||||
RNS.LOG_ERROR
|
||||
)
|
||||
else:
|
||||
RNS.log("Channel is not ready to send, please wait for " +
|
||||
"pending messages to complete.", RNS.LOG_ERROR)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while sending data over the link: "+str(e))
|
||||
should_quit = True
|
||||
server_link.teardown()
|
||||
|
||||
# This function is called when a link
|
||||
# has been established with the server
|
||||
def link_established(link):
|
||||
# We store a reference to the link
|
||||
# instance for later use
|
||||
global server_link
|
||||
server_link = link
|
||||
|
||||
# Register messages and add handler to channel
|
||||
channel = link.get_channel()
|
||||
channel.register_message_type(StringMessage)
|
||||
channel.add_message_handler(client_message_received)
|
||||
|
||||
# Inform the user that the server is
|
||||
# connected
|
||||
RNS.log("Link established with server, enter some text to send, or \"quit\" to quit")
|
||||
|
||||
# When a link is closed, we'll inform the
|
||||
# user, and exit the program
|
||||
def link_closed(link):
|
||||
if link.teardown_reason == RNS.Link.TIMEOUT:
|
||||
RNS.log("The link timed out, exiting now")
|
||||
elif link.teardown_reason == RNS.Link.DESTINATION_CLOSED:
|
||||
RNS.log("The link was closed by the server, exiting now")
|
||||
else:
|
||||
RNS.log("Link closed, exiting now")
|
||||
|
||||
RNS.Reticulum.exit_handler()
|
||||
time.sleep(1.5)
|
||||
os._exit(0)
|
||||
|
||||
# When a packet is received over the channel, we
|
||||
# simply print out the data.
|
||||
def client_message_received(message):
|
||||
if isinstance(message, StringMessage):
|
||||
RNS.log("Received data on the link: " + message.data + " (message created at " + str(message.timestamp) + ")")
|
||||
print("> ", end=" ")
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Program Startup #####################################
|
||||
##########################################################
|
||||
|
||||
# This part of the program runs at startup,
|
||||
# and parses input of from the user, and then
|
||||
# starts up the desired program mode.
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Simple channel example")
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--server",
|
||||
action="store_true",
|
||||
help="wait for incoming link requests from clients"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--config",
|
||||
action="store",
|
||||
default=None,
|
||||
help="path to alternative Reticulum config directory",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="hexadecimal hash of the server destination",
|
||||
type=str
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
if args.server:
|
||||
server(configarg)
|
||||
else:
|
||||
if (args.destination == None):
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
else:
|
||||
client(args.destination, configarg)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
@@ -210,6 +210,7 @@ def client(destination_hexhash, configpath, timeout=None):
|
||||
# If we do not know this destination, tell the
|
||||
# user to wait for an announce to arrive.
|
||||
RNS.log("Destination is not yet known. Requesting path...")
|
||||
RNS.log("Hit enter to manually retry once an announce is received.")
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
|
||||
# This function is called when our reply destination
|
||||
|
||||
+2
-2
@@ -23,8 +23,8 @@ APP_NAME = "example_utilities"
|
||||
# A reference to the latest client link that connected
|
||||
latest_client_link = None
|
||||
|
||||
def random_text_generator(path, data, request_id, remote_identity, requested_at):
|
||||
RNS.log("Generating response to request "+RNS.prettyhexrep(request_id))
|
||||
def random_text_generator(path, data, request_id, link_id, remote_identity, requested_at):
|
||||
RNS.log("Generating response to request "+RNS.prettyhexrep(request_id)+" on link "+RNS.prettyhexrep(link_id))
|
||||
texts = ["They looked up", "On each full moon", "Becky was upset", "I’ll stay away from it", "The pet shop stocks everything"]
|
||||
return texts[random.randint(0, len(texts)-1)]
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ documentation:
|
||||
make -C docs html
|
||||
|
||||
manual:
|
||||
make -C docs latexpdf
|
||||
make -C docs latexpdf epub
|
||||
|
||||
release: test remove_symlinks build_wheel build_pure_wheel documentation manual create_symlinks
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ For more info, see [reticulum.network](https://reticulum.network/)
|
||||
- Total bandwidth cost of setting up an encrypted link is 3 packets totaling 297 bytes
|
||||
- Low cost of keeping links open at only 0.44 bits per second
|
||||
|
||||
## Development Roadmap
|
||||
## Roadmap
|
||||
While Reticulum is already a fully featured and functional networking stack, many improvements and additions are actively being worked on, and planned for the future.
|
||||
|
||||
To learn more about the direction and future of Reticulum, please see the [Development Roadmap](./Roadmap.md).
|
||||
@@ -71,6 +71,7 @@ To learn more about the direction and future of Reticulum, please see the [Devel
|
||||
If you want to quickly get an idea of what Reticulum can do, take a look at the
|
||||
following resources.
|
||||
|
||||
- You can use the [rnsh](https://github.com/acehoss/rnsh) program to establish remote shell sessions over Reticulum.
|
||||
- For an off-grid, encrypted and resilient mesh communications platform, see [Nomad Network](https://github.com/markqvist/NomadNet)
|
||||
- The Android, Linux and macOS app [Sideband](https://github.com/markqvist/Sideband) has a graphical interface and focuses on ease of use.
|
||||
- [LXMF](https://github.com/markqvist/lxmf) is a distributed, delay and disruption tolerant message transfer protocol built on Reticulum
|
||||
@@ -156,7 +157,7 @@ useful.
|
||||
Currently, the following interfaces are supported:
|
||||
|
||||
- Any Ethernet device
|
||||
- LoRa using [RNode](https://unsigned.io/projects/rnode/)
|
||||
- LoRa using [RNode](https://unsigned.io/rnode/)
|
||||
- Packet Radio TNCs (with or without AX.25)
|
||||
- KISS-compatible hardware and software modems
|
||||
- Any device with a serial port
|
||||
@@ -189,7 +190,6 @@ these dependencies, and when the `rns` package is installed with `pip`, they
|
||||
will be downloaded and installed as well.
|
||||
|
||||
- [PyCA/cryptography](https://github.com/pyca/cryptography)
|
||||
- [netifaces](https://github.com/al45tair/netifaces)
|
||||
- [pyserial](https://github.com/pyserial/pyserial)
|
||||
|
||||
On more unusual systems, and in some rare cases, it might not be possible to
|
||||
@@ -227,31 +227,25 @@ I2P. Just add one of the following interfaces to your Reticulum configuration
|
||||
file:
|
||||
|
||||
```
|
||||
# TCP/IP interface to the Dublin Hub
|
||||
# TCP/IP interface to the RNS Dublin Hub
|
||||
[[RNS Testnet Dublin]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = dublin.connect.reticulum.network
|
||||
target_port = 4965
|
||||
|
||||
# TCP/IP interface to the Frankfurt Hub
|
||||
[[RNS Testnet Frankfurt]]
|
||||
# TCP/IP interface to the BetweenTheBorders Hub (community-provided)
|
||||
[[RNS Testnet BetweenTheBorders]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = frankfurt.connect.reticulum.network
|
||||
target_port = 5377
|
||||
target_host = betweentheborders.com
|
||||
target_port = 4242
|
||||
|
||||
# Interface to I2P Hub A
|
||||
[[RNS Testnet I2P Hub A]]
|
||||
# Interface to Testnet I2P Hub
|
||||
[[RNS Testnet I2P Hub]]
|
||||
type = I2PInterface
|
||||
enabled = yes
|
||||
peers = pmlm3l5rpympihoy2o5ago43kluei2jjjzsalcuiuylbve3mwi2a.b32.i2p
|
||||
|
||||
# Interface to I2P Hub B
|
||||
[[RNS Testnet I2P Hub B]]
|
||||
type = I2PInterface
|
||||
enabled = yes
|
||||
peers = iwoqtz22dsr73aemwpw7guocplsjjoamyl7sogj33qtcd6ds4mza.b32.i2p
|
||||
```
|
||||
|
||||
The testnet also contains a number of [Nomad Network](https://github.com/markqvist/nomadnet) nodes, and LXMF propagation nodes.
|
||||
@@ -340,8 +334,8 @@ projects:
|
||||
- [Curve25519.py](https://gist.github.com/nickovs/cc3c22d15f239a2640c185035c06f8a3#file-curve25519-py) by [Nicko van Someren](https://gist.github.com/nickovs), *Public Domain*
|
||||
- [I2Plib](https://github.com/l-n-s/i2plib) by [Viktor Villainov](https://github.com/l-n-s)
|
||||
- [PySerial](https://github.com/pyserial/pyserial) by Chris Liechti, *BSD License*
|
||||
- [Netifaces](https://github.com/al45tair/netifaces) by [Alastair Houghton](https://github.com/al45tair), *MIT License*
|
||||
- [Configobj](https://github.com/DiffSK/configobj) by Michael Foord, Nicola Larosa, Rob Dennis & Eli Courtwright, *BSD License*
|
||||
- [Six](https://github.com/benjaminp/six) by [Benjamin Peterson](https://github.com/benjaminp), *MIT License*
|
||||
- [ifaddr](https://github.com/pydron/ifaddr) by [Pydron](https://github.com/pydron), *MIT License*
|
||||
- [Umsgpack.py](https://github.com/vsergeev/u-msgpack-python) by [Ivan A. Sergeev](https://github.com/vsergeev)
|
||||
- [Python](https://www.python.org)
|
||||
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
from __future__ import annotations
|
||||
import sys
|
||||
import threading
|
||||
from threading import RLock
|
||||
import struct
|
||||
from RNS.Channel import Channel, MessageBase, SystemMessageTypes
|
||||
import RNS
|
||||
from io import RawIOBase, BufferedRWPair, BufferedReader, BufferedWriter
|
||||
from typing import Callable
|
||||
from contextlib import AbstractContextManager
|
||||
|
||||
|
||||
class StreamDataMessage(MessageBase):
|
||||
MSGTYPE = SystemMessageTypes.SMT_STREAM_DATA
|
||||
"""
|
||||
Message type for ``Channel``. ``StreamDataMessage``
|
||||
uses a system-reserved message type.
|
||||
"""
|
||||
|
||||
STREAM_ID_MAX = 0x7fff # 32767
|
||||
"""
|
||||
The stream id is limited to 2 bytes - 1 bit
|
||||
"""
|
||||
|
||||
MAX_DATA_LEN = RNS.Link.MDU - 2 - 6 # 2 for stream data message header, 6 for channel envelope
|
||||
"""
|
||||
When the Buffer package is imported, this value is
|
||||
calculcated based on the value of OVERHEAD
|
||||
"""
|
||||
|
||||
def __init__(self, stream_id: int = None, data: bytes = None, eof: bool = False):
|
||||
"""
|
||||
This class is used to encapsulate binary stream
|
||||
data to be sent over a ``Channel``.
|
||||
|
||||
:param stream_id: id of stream relative to receiver
|
||||
:param data: binary data
|
||||
:param eof: set to True if signalling End of File
|
||||
"""
|
||||
super().__init__()
|
||||
if stream_id is not None and stream_id > self.STREAM_ID_MAX:
|
||||
raise ValueError("stream_id must be 0-32767")
|
||||
self.stream_id = stream_id
|
||||
self.data = data or bytes()
|
||||
self.eof = eof
|
||||
|
||||
def pack(self) -> bytes:
|
||||
if self.stream_id is None:
|
||||
raise ValueError("stream_id")
|
||||
header_val = (0x7fff & self.stream_id) | (0x8000 if self.eof else 0x0000)
|
||||
return bytes(struct.pack(">H", header_val) + (self.data if self.data else bytes()))
|
||||
|
||||
def unpack(self, raw):
|
||||
self.stream_id = struct.unpack(">H", raw[:2])[0]
|
||||
self.eof = (0x8000 & self.stream_id) > 0
|
||||
self.stream_id = self.stream_id & 0x7fff
|
||||
self.data = raw[2:]
|
||||
|
||||
|
||||
class RawChannelReader(RawIOBase, AbstractContextManager):
|
||||
"""
|
||||
An implementation of RawIOBase that receives
|
||||
binary stream data sent over a ``Channel``.
|
||||
|
||||
This class generally need not be instantiated directly.
|
||||
Use :func:`RNS.Buffer.create_reader`,
|
||||
:func:`RNS.Buffer.create_writer`, and
|
||||
:func:`RNS.Buffer.create_bidirectional_buffer` functions
|
||||
to create buffered streams with optional callbacks.
|
||||
|
||||
For additional information on the API of this
|
||||
object, see the Python documentation for
|
||||
``RawIOBase``.
|
||||
"""
|
||||
def __init__(self, stream_id: int, channel: Channel):
|
||||
"""
|
||||
Create a raw channel reader.
|
||||
|
||||
:param stream_id: local stream id to receive at
|
||||
:param channel: ``Channel`` object to receive from
|
||||
"""
|
||||
self._stream_id = stream_id
|
||||
self._channel = channel
|
||||
self._lock = RLock()
|
||||
self._buffer = bytearray()
|
||||
self._eof = False
|
||||
self._channel._register_message_type(StreamDataMessage, is_system_type=True)
|
||||
self._channel.add_message_handler(self._handle_message)
|
||||
self._listeners: [Callable[[int], None]] = []
|
||||
|
||||
def add_ready_callback(self, cb: Callable[[int], None]):
|
||||
"""
|
||||
Add a function to be called when new data is available.
|
||||
The function should have the signature ``(ready_bytes: int) -> None``
|
||||
|
||||
:param cb: function to call
|
||||
"""
|
||||
with self._lock:
|
||||
self._listeners.append(cb)
|
||||
|
||||
def remove_ready_callback(self, cb: Callable[[int], None]):
|
||||
"""
|
||||
Remove a function added with :func:`RNS.RawChannelReader.add_ready_callback()`
|
||||
|
||||
:param cb: function to remove
|
||||
"""
|
||||
with self._lock:
|
||||
self._listeners.remove(cb)
|
||||
|
||||
def _handle_message(self, message: MessageBase):
|
||||
if isinstance(message, StreamDataMessage):
|
||||
if message.stream_id == self._stream_id:
|
||||
with self._lock:
|
||||
if message.data is not None:
|
||||
self._buffer.extend(message.data)
|
||||
if message.eof:
|
||||
self._eof = True
|
||||
for listener in self._listeners:
|
||||
try:
|
||||
listener(len(self._buffer))
|
||||
except Exception as ex:
|
||||
RNS.log("Error calling RawChannelReader(" + str(self._stream_id) + ") callback: " + str(ex))
|
||||
return True
|
||||
return False
|
||||
|
||||
def _read(self, __size: int) -> bytes | None:
|
||||
with self._lock:
|
||||
result = self._buffer[:__size]
|
||||
self._buffer = self._buffer[__size:]
|
||||
return result if len(result) > 0 or self._eof else None
|
||||
|
||||
def readinto(self, __buffer: bytearray) -> int | None:
|
||||
ready = self._read(len(__buffer))
|
||||
if ready is not None:
|
||||
__buffer[:len(ready)] = ready
|
||||
return len(ready) if ready is not None else None
|
||||
|
||||
def writable(self) -> bool:
|
||||
return False
|
||||
|
||||
def seekable(self) -> bool:
|
||||
return False
|
||||
|
||||
def readable(self) -> bool:
|
||||
return True
|
||||
|
||||
def close(self):
|
||||
with self._lock:
|
||||
self._channel.remove_message_handler(self._handle_message)
|
||||
self._listeners.clear()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
|
||||
|
||||
class RawChannelWriter(RawIOBase, AbstractContextManager):
|
||||
"""
|
||||
An implementation of RawIOBase that receives
|
||||
binary stream data sent over a channel.
|
||||
|
||||
This class generally need not be instantiated directly.
|
||||
Use :func:`RNS.Buffer.create_reader`,
|
||||
:func:`RNS.Buffer.create_writer`, and
|
||||
:func:`RNS.Buffer.create_bidirectional_buffer` functions
|
||||
to create buffered streams with optional callbacks.
|
||||
|
||||
For additional information on the API of this
|
||||
object, see the Python documentation for
|
||||
``RawIOBase``.
|
||||
"""
|
||||
def __init__(self, stream_id: int, channel: Channel):
|
||||
"""
|
||||
Create a raw channel writer.
|
||||
|
||||
:param stream_id: remote stream id to sent do
|
||||
:param channel: ``Channel`` object to send on
|
||||
"""
|
||||
self._stream_id = stream_id
|
||||
self._channel = channel
|
||||
self._eof = False
|
||||
|
||||
def write(self, __b: bytes) -> int | None:
|
||||
try:
|
||||
chunk = bytes(__b[:StreamDataMessage.MAX_DATA_LEN])
|
||||
message = StreamDataMessage(self._stream_id, chunk, self._eof)
|
||||
self._channel.send(message)
|
||||
return len(chunk)
|
||||
except RNS.Channel.ChannelException as cex:
|
||||
if cex.type != RNS.Channel.CEType.ME_LINK_NOT_READY:
|
||||
raise
|
||||
return 0
|
||||
|
||||
def close(self):
|
||||
self._eof = True
|
||||
self.write(bytes())
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
self.close()
|
||||
return False
|
||||
|
||||
def seekable(self) -> bool:
|
||||
return False
|
||||
|
||||
def readable(self) -> bool:
|
||||
return False
|
||||
|
||||
def writable(self) -> bool:
|
||||
return True
|
||||
|
||||
class Buffer:
|
||||
"""
|
||||
Static functions for creating buffered streams that send
|
||||
and receive over a ``Channel``.
|
||||
|
||||
These functions use ``BufferedReader``, ``BufferedWriter``,
|
||||
and ``BufferedRWPair`` to add buffering to
|
||||
``RawChannelReader`` and ``RawChannelWriter``.
|
||||
"""
|
||||
@staticmethod
|
||||
def create_reader(stream_id: int, channel: Channel,
|
||||
ready_callback: Callable[[int], None] | None = None) -> BufferedReader:
|
||||
"""
|
||||
Create a buffered reader that reads binary data sent
|
||||
over a ``Channel``, with an optional callback when
|
||||
new data is available.
|
||||
|
||||
Callback signature: ``(ready_bytes: int) -> None``
|
||||
|
||||
For more information on the reader-specific functions
|
||||
of this object, see the Python documentation for
|
||||
``BufferedReader``
|
||||
|
||||
:param stream_id: the local stream id to receive from
|
||||
:param channel: the channel to receive on
|
||||
:param ready_callback: function to call when new data is available
|
||||
:return: a BufferedReader object
|
||||
"""
|
||||
reader = RawChannelReader(stream_id, channel)
|
||||
if ready_callback:
|
||||
reader.add_ready_callback(ready_callback)
|
||||
return BufferedReader(reader)
|
||||
|
||||
@staticmethod
|
||||
def create_writer(stream_id: int, channel: Channel) -> BufferedWriter:
|
||||
"""
|
||||
Create a buffered writer that writes binary data over
|
||||
a ``Channel``.
|
||||
|
||||
For more information on the writer-specific functions
|
||||
of this object, see the Python documentation for
|
||||
``BufferedWriter``
|
||||
|
||||
:param stream_id: the remote stream id to send to
|
||||
:param channel: the channel to send on
|
||||
:return: a BufferedWriter object
|
||||
"""
|
||||
writer = RawChannelWriter(stream_id, channel)
|
||||
return BufferedWriter(writer)
|
||||
|
||||
@staticmethod
|
||||
def create_bidirectional_buffer(receive_stream_id: int, send_stream_id: int, channel: Channel,
|
||||
ready_callback: Callable[[int], None] | None = None) -> BufferedRWPair:
|
||||
"""
|
||||
Create a buffered reader/writer pair that reads and
|
||||
writes binary data over a ``Channel``, with an
|
||||
optional callback when new data is available.
|
||||
|
||||
Callback signature: ``(ready_bytes: int) -> None``
|
||||
|
||||
For more information on the reader-specific functions
|
||||
of this object, see the Python documentation for
|
||||
``BufferedRWPair``
|
||||
|
||||
:param receive_stream_id: the local stream id to receive at
|
||||
:param send_stream_id: the remote stream id to send to
|
||||
:param channel: the channel to send and receive on
|
||||
:param ready_callback: function to call when new data is available
|
||||
:return: a BufferedRWPair object
|
||||
"""
|
||||
reader = RawChannelReader(receive_stream_id, channel)
|
||||
if ready_callback:
|
||||
reader.add_ready_callback(ready_callback)
|
||||
writer = RawChannelWriter(send_stream_id, channel)
|
||||
return BufferedRWPair(reader, writer)
|
||||
+541
@@ -0,0 +1,541 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from __future__ import annotations
|
||||
import collections
|
||||
import enum
|
||||
import threading
|
||||
import time
|
||||
from types import TracebackType
|
||||
from typing import Type, Callable, TypeVar, Generic, NewType
|
||||
import abc
|
||||
import contextlib
|
||||
import struct
|
||||
import RNS
|
||||
from abc import ABC, abstractmethod
|
||||
TPacket = TypeVar("TPacket")
|
||||
|
||||
class SystemMessageTypes(enum.IntEnum):
|
||||
SMT_STREAM_DATA = 0xff00
|
||||
|
||||
class ChannelOutletBase(ABC, Generic[TPacket]):
|
||||
"""
|
||||
An abstract transport layer interface used by Channel.
|
||||
|
||||
DEPRECATED: This was created for testing; eventually
|
||||
Channel will use Link or a LinkBase interface
|
||||
directly.
|
||||
"""
|
||||
@abstractmethod
|
||||
def send(self, raw: bytes) -> TPacket:
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def resend(self, packet: TPacket) -> TPacket:
|
||||
raise NotImplemented()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def mdu(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def rtt(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_usable(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def get_packet_state(self, packet: TPacket) -> MessageState:
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def timed_out(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def __str__(self):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def set_packet_timeout_callback(self, packet: TPacket, callback: Callable[[TPacket], None] | None,
|
||||
timeout: float | None = None):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def set_packet_delivered_callback(self, packet: TPacket, callback: Callable[[TPacket], None] | None):
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def get_packet_id(self, packet: TPacket) -> any:
|
||||
raise NotImplemented()
|
||||
|
||||
|
||||
class CEType(enum.IntEnum):
|
||||
"""
|
||||
ChannelException type codes
|
||||
"""
|
||||
ME_NO_MSG_TYPE = 0
|
||||
ME_INVALID_MSG_TYPE = 1
|
||||
ME_NOT_REGISTERED = 2
|
||||
ME_LINK_NOT_READY = 3
|
||||
ME_ALREADY_SENT = 4
|
||||
ME_TOO_BIG = 5
|
||||
|
||||
|
||||
class ChannelException(Exception):
|
||||
"""
|
||||
An exception thrown by Channel, with a type code.
|
||||
"""
|
||||
def __init__(self, ce_type: CEType, *args):
|
||||
super().__init__(args)
|
||||
self.type = ce_type
|
||||
|
||||
|
||||
class MessageState(enum.IntEnum):
|
||||
"""
|
||||
Set of possible states for a Message
|
||||
"""
|
||||
MSGSTATE_NEW = 0
|
||||
MSGSTATE_SENT = 1
|
||||
MSGSTATE_DELIVERED = 2
|
||||
MSGSTATE_FAILED = 3
|
||||
|
||||
|
||||
class MessageBase(abc.ABC):
|
||||
"""
|
||||
Base type for any messages sent or received on a Channel.
|
||||
Subclasses must define the two abstract methods as well as
|
||||
the ``MSGTYPE`` class variable.
|
||||
"""
|
||||
# MSGTYPE must be unique within all classes sent over a
|
||||
# channel. Additionally, MSGTYPE > 0xf000 are reserved.
|
||||
MSGTYPE = None
|
||||
"""
|
||||
Defines a unique identifier for a message class.
|
||||
|
||||
* Must be unique within all classes registered with a ``Channel``
|
||||
* Must be less than ``0xf000``. Values greater than or equal to ``0xf000`` are reserved.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def pack(self) -> bytes:
|
||||
"""
|
||||
Create and return the binary representation of the message
|
||||
|
||||
:return: binary representation of message
|
||||
"""
|
||||
raise NotImplemented()
|
||||
|
||||
@abstractmethod
|
||||
def unpack(self, raw: bytes):
|
||||
"""
|
||||
Populate message from binary representation
|
||||
|
||||
:param raw: binary representation
|
||||
"""
|
||||
raise NotImplemented()
|
||||
|
||||
|
||||
MessageCallbackType = NewType("MessageCallbackType", Callable[[MessageBase], bool])
|
||||
|
||||
|
||||
class Envelope:
|
||||
"""
|
||||
Internal wrapper used to transport messages over a channel and
|
||||
track its state within the channel framework.
|
||||
"""
|
||||
def unpack(self, message_factories: dict[int, Type]) -> MessageBase:
|
||||
msgtype, self.sequence, length = struct.unpack(">HHH", self.raw[:6])
|
||||
raw = self.raw[6:]
|
||||
ctor = message_factories.get(msgtype, None)
|
||||
if ctor is None:
|
||||
raise ChannelException(CEType.ME_NOT_REGISTERED, f"Unable to find constructor for Channel MSGTYPE {hex(msgtype)}")
|
||||
message = ctor()
|
||||
message.unpack(raw)
|
||||
return message
|
||||
|
||||
def pack(self) -> bytes:
|
||||
if self.message.__class__.MSGTYPE is None:
|
||||
raise ChannelException(CEType.ME_NO_MSG_TYPE, f"{self.message.__class__} lacks MSGTYPE")
|
||||
data = self.message.pack()
|
||||
self.raw = struct.pack(">HHH", self.message.MSGTYPE, self.sequence, len(data)) + data
|
||||
return self.raw
|
||||
|
||||
def __init__(self, outlet: ChannelOutletBase, message: MessageBase = None, raw: bytes = None, sequence: int = None):
|
||||
self.ts = time.time()
|
||||
self.id = id(self)
|
||||
self.message = message
|
||||
self.raw = raw
|
||||
self.packet: TPacket = None
|
||||
self.sequence = sequence
|
||||
self.outlet = outlet
|
||||
self.tries = 0
|
||||
self.tracked = False
|
||||
|
||||
|
||||
class Channel(contextlib.AbstractContextManager):
|
||||
"""
|
||||
Provides reliable delivery of messages over
|
||||
a link.
|
||||
|
||||
``Channel`` differs from ``Request`` and
|
||||
``Resource`` in some important ways:
|
||||
|
||||
**Continuous**
|
||||
Messages can be sent or received as long as
|
||||
the ``Link`` is open.
|
||||
**Bi-directional**
|
||||
Messages can be sent in either direction on
|
||||
the ``Link``; neither end is the client or
|
||||
server.
|
||||
**Size-constrained**
|
||||
Messages must be encoded into a single packet.
|
||||
|
||||
``Channel`` is similar to ``Packet``, except that it
|
||||
provides reliable delivery (automatic retries) as well
|
||||
as a structure for exchanging several types of
|
||||
messages over the ``Link``.
|
||||
|
||||
``Channel`` is not instantiated directly, but rather
|
||||
obtained from a ``Link`` with ``get_channel()``.
|
||||
"""
|
||||
def __init__(self, outlet: ChannelOutletBase):
|
||||
"""
|
||||
|
||||
@param outlet:
|
||||
"""
|
||||
self._outlet = outlet
|
||||
self._lock = threading.RLock()
|
||||
self._tx_ring: collections.deque[Envelope] = collections.deque()
|
||||
self._rx_ring: collections.deque[Envelope] = collections.deque()
|
||||
self._message_callbacks: [MessageCallbackType] = []
|
||||
self._next_sequence = 0
|
||||
self._message_factories: dict[int, Type[MessageBase]] = {}
|
||||
self._max_tries = 5
|
||||
|
||||
def __enter__(self) -> Channel:
|
||||
return self
|
||||
|
||||
def __exit__(self, __exc_type: Type[BaseException] | None, __exc_value: BaseException | None,
|
||||
__traceback: TracebackType | None) -> bool | None:
|
||||
self._shutdown()
|
||||
return False
|
||||
|
||||
def register_message_type(self, message_class: Type[MessageBase]):
|
||||
"""
|
||||
Register a message class for reception over a ``Channel``.
|
||||
|
||||
Message classes must extend ``MessageBase``.
|
||||
|
||||
:param message_class: Class to register
|
||||
"""
|
||||
self._register_message_type(message_class, is_system_type=False)
|
||||
|
||||
def _register_message_type(self, message_class: Type[MessageBase], *, is_system_type: bool = False):
|
||||
with self._lock:
|
||||
if not issubclass(message_class, MessageBase):
|
||||
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
|
||||
f"{message_class} is not a subclass of {MessageBase}.")
|
||||
if message_class.MSGTYPE is None:
|
||||
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
|
||||
f"{message_class} has invalid MSGTYPE class attribute.")
|
||||
if message_class.MSGTYPE >= 0xf000 and not is_system_type:
|
||||
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
|
||||
f"{message_class} has system-reserved message type.")
|
||||
try:
|
||||
message_class()
|
||||
except Exception as ex:
|
||||
raise ChannelException(CEType.ME_INVALID_MSG_TYPE,
|
||||
f"{message_class} raised an exception when constructed with no arguments: {ex}")
|
||||
|
||||
self._message_factories[message_class.MSGTYPE] = message_class
|
||||
|
||||
def add_message_handler(self, callback: MessageCallbackType):
|
||||
"""
|
||||
Add a handler for incoming messages. A handler
|
||||
has the following signature:
|
||||
|
||||
``(message: MessageBase) -> bool``
|
||||
|
||||
Handlers are processed in the order they are
|
||||
added. If any handler returns True, processing
|
||||
of the message stops; handlers after the
|
||||
returning handler will not be called.
|
||||
|
||||
:param callback: Function to call
|
||||
"""
|
||||
with self._lock:
|
||||
if callback not in self._message_callbacks:
|
||||
self._message_callbacks.append(callback)
|
||||
|
||||
def remove_message_handler(self, callback: MessageCallbackType):
|
||||
"""
|
||||
Remove a handler added with ``add_message_handler``.
|
||||
|
||||
:param callback: handler to remove
|
||||
"""
|
||||
with self._lock:
|
||||
if callback in self._message_callbacks:
|
||||
self._message_callbacks.remove(callback)
|
||||
|
||||
def _shutdown(self):
|
||||
with self._lock:
|
||||
self._message_callbacks.clear()
|
||||
self._clear_rings()
|
||||
|
||||
def _clear_rings(self):
|
||||
with self._lock:
|
||||
for envelope in self._tx_ring:
|
||||
if envelope.packet is not None:
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, None)
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, None)
|
||||
self._tx_ring.clear()
|
||||
self._rx_ring.clear()
|
||||
|
||||
def _emplace_envelope(self, envelope: Envelope, ring: collections.deque[Envelope]) -> bool:
|
||||
with self._lock:
|
||||
i = 0
|
||||
for existing in ring:
|
||||
if existing.sequence > envelope.sequence \
|
||||
and not existing.sequence // 2 > envelope.sequence: # account for overflow
|
||||
ring.insert(i, envelope)
|
||||
return True
|
||||
if existing.sequence == envelope.sequence:
|
||||
RNS.log(f"Envelope: Emplacement of duplicate envelope sequence.", RNS.LOG_EXTREME)
|
||||
return False
|
||||
i += 1
|
||||
envelope.tracked = True
|
||||
ring.append(envelope)
|
||||
return True
|
||||
|
||||
def _prune_rx_ring(self):
|
||||
with self._lock:
|
||||
# Implementation for fixed window = 1
|
||||
stale = list(sorted(self._rx_ring, key=lambda env: env.sequence, reverse=True))[1:]
|
||||
for env in stale:
|
||||
env.tracked = False
|
||||
self._rx_ring.remove(env)
|
||||
|
||||
def _run_callbacks(self, message: MessageBase):
|
||||
with self._lock:
|
||||
cbs = self._message_callbacks.copy()
|
||||
|
||||
for cb in cbs:
|
||||
try:
|
||||
if cb(message):
|
||||
return
|
||||
except Exception as ex:
|
||||
RNS.log(f"Channel: Error running message callback: {ex}", RNS.LOG_ERROR)
|
||||
|
||||
def _receive(self, raw: bytes):
|
||||
try:
|
||||
envelope = Envelope(outlet=self._outlet, raw=raw)
|
||||
with self._lock:
|
||||
message = envelope.unpack(self._message_factories)
|
||||
prev_env = self._rx_ring[0] if len(self._rx_ring) > 0 else None
|
||||
if prev_env and envelope.sequence != (prev_env.sequence + 1) % 0x10000:
|
||||
RNS.log("Channel: Out of order packet received", RNS.LOG_DEBUG)
|
||||
return
|
||||
is_new = self._emplace_envelope(envelope, self._rx_ring)
|
||||
self._prune_rx_ring()
|
||||
if not is_new:
|
||||
RNS.log("Channel: Duplicate message received", RNS.LOG_DEBUG)
|
||||
return
|
||||
RNS.log(f"Message received: {message}", RNS.LOG_DEBUG)
|
||||
threading.Thread(target=self._run_callbacks, name="Message Callback", args=[message], daemon=True).start()
|
||||
except Exception as ex:
|
||||
RNS.log(f"Channel: Error receiving data: {ex}")
|
||||
|
||||
def is_ready_to_send(self) -> bool:
|
||||
"""
|
||||
Check if ``Channel`` is ready to send.
|
||||
|
||||
:return: True if ready
|
||||
"""
|
||||
if not self._outlet.is_usable:
|
||||
RNS.log("Channel: Link is not usable.", RNS.LOG_EXTREME)
|
||||
return False
|
||||
|
||||
with self._lock:
|
||||
for envelope in self._tx_ring:
|
||||
if envelope.outlet == self._outlet and (not envelope.packet
|
||||
or self._outlet.get_packet_state(envelope.packet) == MessageState.MSGSTATE_SENT):
|
||||
# TODO: Check if this should be enabled with some kind of
|
||||
# rate limiting, since it currently floods log output when
|
||||
# messages are waiting.
|
||||
# RNS.log("Channel: Link has a pending message.", RNS.LOG_EXTREME)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _packet_tx_op(self, packet: TPacket, op: Callable[[TPacket], bool]):
|
||||
with self._lock:
|
||||
envelope = next(filter(lambda e: self._outlet.get_packet_id(e.packet) == self._outlet.get_packet_id(packet),
|
||||
self._tx_ring), None)
|
||||
if envelope and op(envelope):
|
||||
envelope.tracked = False
|
||||
if envelope in self._tx_ring:
|
||||
self._tx_ring.remove(envelope)
|
||||
else:
|
||||
RNS.log("Channel: Envelope not found in TX ring", RNS.LOG_DEBUG)
|
||||
if not envelope:
|
||||
RNS.log("Channel: Spurious message received.", RNS.LOG_EXTREME)
|
||||
|
||||
def _packet_delivered(self, packet: TPacket):
|
||||
self._packet_tx_op(packet, lambda env: True)
|
||||
|
||||
def _get_packet_timeout_time(self, tries: int) -> float:
|
||||
return pow(2, tries - 1) * max(self._outlet.rtt, 0.01) * 5
|
||||
|
||||
def _packet_timeout(self, packet: TPacket):
|
||||
def retry_envelope(envelope: Envelope) -> bool:
|
||||
if envelope.tries >= self._max_tries:
|
||||
RNS.log("Channel: Retry count exceeded, tearing down Link.", RNS.LOG_ERROR)
|
||||
self._shutdown() # start on separate thread?
|
||||
self._outlet.timed_out()
|
||||
return True
|
||||
envelope.tries += 1
|
||||
self._outlet.resend(envelope.packet)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
return False
|
||||
|
||||
if self._outlet.get_packet_state(packet) != MessageState.MSGSTATE_DELIVERED:
|
||||
self._packet_tx_op(packet, retry_envelope)
|
||||
|
||||
def send(self, message: MessageBase) -> Envelope:
|
||||
"""
|
||||
Send a message. If a message send is attempted and
|
||||
``Channel`` is not ready, an exception is thrown.
|
||||
|
||||
:param message: an instance of a ``MessageBase`` subclass
|
||||
"""
|
||||
envelope: Envelope | None = None
|
||||
with self._lock:
|
||||
if not self.is_ready_to_send():
|
||||
raise ChannelException(CEType.ME_LINK_NOT_READY, f"Link is not ready")
|
||||
envelope = Envelope(self._outlet, message=message, sequence=self._next_sequence)
|
||||
self._next_sequence = (self._next_sequence + 1) % 0x10000
|
||||
self._emplace_envelope(envelope, self._tx_ring)
|
||||
if envelope is None:
|
||||
raise BlockingIOError()
|
||||
|
||||
envelope.pack()
|
||||
if len(envelope.raw) > self._outlet.mdu:
|
||||
raise ChannelException(CEType.ME_TOO_BIG, f"Packed message too big for packet: {len(envelope.raw)} > {self._outlet.mdu}")
|
||||
envelope.packet = self._outlet.send(envelope.raw)
|
||||
envelope.tries += 1
|
||||
self._outlet.set_packet_delivered_callback(envelope.packet, self._packet_delivered)
|
||||
self._outlet.set_packet_timeout_callback(envelope.packet, self._packet_timeout, self._get_packet_timeout_time(envelope.tries))
|
||||
return envelope
|
||||
|
||||
@property
|
||||
def MDU(self):
|
||||
"""
|
||||
Maximum Data Unit: the number of bytes available
|
||||
for a message to consume in a single send. This
|
||||
value is adjusted from the ``Link`` MDU to accommodate
|
||||
message header information.
|
||||
|
||||
:return: number of bytes available
|
||||
"""
|
||||
return self._outlet.mdu - 6 # sizeof(msgtype) + sizeof(length) + sizeof(sequence)
|
||||
|
||||
|
||||
class LinkChannelOutlet(ChannelOutletBase):
|
||||
"""
|
||||
An implementation of ChannelOutletBase for RNS.Link.
|
||||
Allows Channel to send packets over an RNS Link with
|
||||
Packets.
|
||||
|
||||
:param link: RNS Link to wrap
|
||||
"""
|
||||
def __init__(self, link: RNS.Link):
|
||||
self.link = link
|
||||
|
||||
def send(self, raw: bytes) -> RNS.Packet:
|
||||
packet = RNS.Packet(self.link, raw, context=RNS.Packet.CHANNEL)
|
||||
if self.link.status == RNS.Link.ACTIVE:
|
||||
packet.send()
|
||||
return packet
|
||||
|
||||
def resend(self, packet: RNS.Packet) -> RNS.Packet:
|
||||
RNS.log("Resending packet " + RNS.prettyhexrep(packet.packet_hash), RNS.LOG_DEBUG)
|
||||
if not packet.resend():
|
||||
RNS.log("Failed to resend packet", RNS.LOG_ERROR)
|
||||
return packet
|
||||
|
||||
@property
|
||||
def mdu(self):
|
||||
return self.link.MDU
|
||||
|
||||
@property
|
||||
def rtt(self):
|
||||
return self.link.rtt
|
||||
|
||||
@property
|
||||
def is_usable(self):
|
||||
return True # had issues looking at Link.status
|
||||
|
||||
def get_packet_state(self, packet: TPacket) -> MessageState:
|
||||
if packet.receipt == None:
|
||||
return MessageState.MSGSTATE_FAILED
|
||||
|
||||
status = packet.receipt.get_status()
|
||||
if status == RNS.PacketReceipt.SENT:
|
||||
return MessageState.MSGSTATE_SENT
|
||||
if status == RNS.PacketReceipt.DELIVERED:
|
||||
return MessageState.MSGSTATE_DELIVERED
|
||||
if status == RNS.PacketReceipt.FAILED:
|
||||
return MessageState.MSGSTATE_FAILED
|
||||
else:
|
||||
raise Exception(f"Unexpected receipt state: {status}")
|
||||
|
||||
def timed_out(self):
|
||||
self.link.teardown()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.__class__.__name__}({self.link})"
|
||||
|
||||
def set_packet_timeout_callback(self, packet: RNS.Packet, callback: Callable[[RNS.Packet], None] | None,
|
||||
timeout: float | None = None):
|
||||
if timeout and packet.receipt:
|
||||
packet.receipt.set_timeout(timeout)
|
||||
|
||||
def inner(receipt: RNS.PacketReceipt):
|
||||
callback(packet)
|
||||
|
||||
if packet and packet.receipt:
|
||||
packet.receipt.set_timeout_callback(inner if callback else None)
|
||||
|
||||
def set_packet_delivered_callback(self, packet: RNS.Packet, callback: Callable[[RNS.Packet], None] | None):
|
||||
def inner(receipt: RNS.PacketReceipt):
|
||||
callback(packet)
|
||||
|
||||
if packet and packet.receipt:
|
||||
packet.receipt.set_delivery_callback(inner if callback else None)
|
||||
|
||||
def get_packet_id(self, packet: RNS.Packet) -> any:
|
||||
return packet.get_hash()
|
||||
@@ -39,9 +39,6 @@ def hkdf(length=None, derive_from=None, salt=None, context=None):
|
||||
if salt == None or len(salt) == 0:
|
||||
salt = bytes([0] * hash_len)
|
||||
|
||||
if salt == None:
|
||||
salt = b""
|
||||
|
||||
if context == None:
|
||||
context = b""
|
||||
|
||||
|
||||
+1
-1
@@ -296,7 +296,7 @@ class Destination:
|
||||
Registers a request handler.
|
||||
|
||||
:param path: The path for the request handler to be registered.
|
||||
:param response_generator: A function or method with the signature *response_generator(path, data, request_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
|
||||
:param response_generator: A function or method with the signature *response_generator(path, data, request_id, link_id, remote_identity, requested_at)* to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns ``None``, no response will be sent.
|
||||
:param allow: One of ``RNS.Destination.ALLOW_NONE``, ``RNS.Destination.ALLOW_ALL`` or ``RNS.Destination.ALLOW_LIST``. If ``RNS.Destination.ALLOW_LIST`` is set, the request handler will only respond to requests for identified peers in the supplied list.
|
||||
:param allowed_list: A list of *bytes-like* :ref:`RNS.Identity<api-identity>` hashes.
|
||||
:raises: ``ValueError`` if any of the supplied arguments are invalid.
|
||||
|
||||
@@ -1003,7 +1003,7 @@ class RNodeInterface(Interface):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Radio initialisation failure")
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
elif (byte == KISS.ERROR_TXFAILED):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
else:
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
from .Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
@@ -43,6 +44,7 @@ class AutoInterface(Interface):
|
||||
|
||||
PEERING_TIMEOUT = 7.5
|
||||
|
||||
ALL_IGNORE_IFS = ["lo0"]
|
||||
DARWIN_IGNORE_IFS = ["awdl0", "llw0", "lo0", "en5"]
|
||||
ANDROID_IGNORE_IFS = ["dummy0", "lo", "tun0"]
|
||||
|
||||
@@ -53,16 +55,25 @@ class AutoInterface(Interface):
|
||||
return AutoInterfaceHandler(callback, *args, **keys)
|
||||
return create_handler
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
else:
|
||||
RNS.log("Using AutoInterface requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
def descope_linklocal(self, link_local_addr):
|
||||
# Drop scope specifier expressd as %ifname (macOS)
|
||||
link_local_addr = link_local_addr.split("%")[0]
|
||||
# Drop embedded scope specifier (NetBSD, OpenBSD)
|
||||
link_local_addr = re.sub(r"fe80:[0-9a-f]*::","fe80::", link_local_addr)
|
||||
return link_local_addr
|
||||
|
||||
def list_interfaces(self):
|
||||
ifs = self.netinfo.interfaces()
|
||||
return ifs
|
||||
|
||||
def list_addresses(self, ifname):
|
||||
ifas = self.netinfo.ifaddresses(ifname)
|
||||
return ifas
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
|
||||
from RNS.vendor.ifaddr import niwrapper
|
||||
self.netinfo = niwrapper
|
||||
|
||||
self.netifaces = netifaces
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
|
||||
@@ -139,7 +150,7 @@ class AutoInterface(Interface):
|
||||
self.mcast_discovery_address = "ff1"+self.discovery_scope+":"+gt
|
||||
|
||||
suitable_interfaces = 0
|
||||
for ifname in self.netifaces.interfaces():
|
||||
for ifname in self.list_interfaces():
|
||||
if RNS.vendor.platformutils.is_darwin() and ifname in AutoInterface.DARWIN_IGNORE_IFS and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" skipping Darwin AWDL or tethering interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif RNS.vendor.platformutils.is_darwin() and ifname == "lo0":
|
||||
@@ -148,19 +159,21 @@ class AutoInterface(Interface):
|
||||
RNS.log(str(self)+" skipping Android system interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif ifname in self.ignored_interfaces:
|
||||
RNS.log(str(self)+" ignoring disallowed interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
elif ifname in AutoInterface.ALL_IGNORE_IFS:
|
||||
RNS.log(str(self)+" skipping interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
else:
|
||||
if len(self.allowed_interfaces) > 0 and not ifname in self.allowed_interfaces:
|
||||
RNS.log(str(self)+" ignoring interface "+str(ifname)+" since it was not allowed", RNS.LOG_EXTREME)
|
||||
else:
|
||||
addresses = self.netifaces.ifaddresses(ifname)
|
||||
if self.netifaces.AF_INET6 in addresses:
|
||||
addresses = self.list_addresses(ifname)
|
||||
if self.netinfo.AF_INET6 in addresses:
|
||||
link_local_addr = None
|
||||
for address in addresses[self.netifaces.AF_INET6]:
|
||||
for address in addresses[self.netinfo.AF_INET6]:
|
||||
if "addr" in address:
|
||||
if address["addr"].startswith("fe80:"):
|
||||
link_local_addr = address["addr"]
|
||||
self.link_local_addresses.append(link_local_addr.split("%")[0])
|
||||
self.adopted_interfaces[ifname] = link_local_addr.split("%")[0]
|
||||
link_local_addr = self.descope_linklocal(address["addr"])
|
||||
self.link_local_addresses.append(link_local_addr)
|
||||
self.adopted_interfaces[ifname] = link_local_addr
|
||||
self.multicast_echoes[ifname] = time.time()
|
||||
RNS.log(str(self)+" Selecting link-local address "+str(link_local_addr)+" for interface "+str(ifname), RNS.LOG_EXTREME)
|
||||
|
||||
@@ -185,7 +198,11 @@ class AutoInterface(Interface):
|
||||
discovery_socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mcast_group)
|
||||
|
||||
# Bind socket
|
||||
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
if self.discovery_scope == AutoInterface.SCOPE_LINK:
|
||||
addr_info = socket.getaddrinfo(mcast_addr+"%"+ifname, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
else:
|
||||
addr_info = socket.getaddrinfo(mcast_addr, self.discovery_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
|
||||
discovery_socket.bind(addr_info[0][4])
|
||||
|
||||
# Set up thread for discovery packets
|
||||
@@ -203,6 +220,11 @@ class AutoInterface(Interface):
|
||||
else:
|
||||
self.receives = True
|
||||
|
||||
if configured_bitrate != None:
|
||||
self.bitrate = configured_bitrate
|
||||
else:
|
||||
self.bitrate = AutoInterface.BITRATE_GUESS
|
||||
|
||||
peering_wait = self.announce_interval*1.2
|
||||
RNS.log(str(self)+" discovering peers for "+str(round(peering_wait, 2))+" seconds...", RNS.LOG_VERBOSE)
|
||||
|
||||
@@ -227,11 +249,6 @@ class AutoInterface(Interface):
|
||||
|
||||
time.sleep(peering_wait)
|
||||
|
||||
if configured_bitrate != None:
|
||||
self.bitrate = configured_bitrate
|
||||
else:
|
||||
self.bitrate = AutoInterface.BITRATE_GUESS
|
||||
|
||||
self.online = True
|
||||
|
||||
|
||||
@@ -272,13 +289,13 @@ class AutoInterface(Interface):
|
||||
for ifname in self.adopted_interfaces:
|
||||
# Check that the link-local address has not changed
|
||||
try:
|
||||
addresses = self.netifaces.ifaddresses(ifname)
|
||||
if self.netifaces.AF_INET6 in addresses:
|
||||
addresses = self.list_addresses(ifname)
|
||||
if self.netinfo.AF_INET6 in addresses:
|
||||
link_local_addr = None
|
||||
for address in addresses[self.netifaces.AF_INET6]:
|
||||
for address in addresses[self.netinfo.AF_INET6]:
|
||||
if "addr" in address:
|
||||
if address["addr"].startswith("fe80:"):
|
||||
link_local_addr = address["addr"].split("%")[0]
|
||||
link_local_addr = self.descope_linklocal(address["addr"])
|
||||
if link_local_addr != self.adopted_interfaces[ifname]:
|
||||
old_link_local_address = self.adopted_interfaces[ifname]
|
||||
RNS.log("Replacing link-local address "+str(old_link_local_address)+" for "+str(ifname)+" with "+str(link_local_addr), RNS.LOG_DEBUG)
|
||||
|
||||
@@ -41,7 +41,13 @@ class HDLC():
|
||||
return data
|
||||
|
||||
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
def server_bind(self):
|
||||
if RNS.vendor.platformutils.is_windows():
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
|
||||
else:
|
||||
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.socket.bind(self.server_address)
|
||||
self.server_address = self.socket.getsockname()
|
||||
|
||||
class LocalClientInterface(Interface):
|
||||
RECONNECT_WAIT = 3
|
||||
@@ -86,6 +92,8 @@ class LocalClientInterface(Interface):
|
||||
self.online = True
|
||||
self.writing = False
|
||||
|
||||
self._force_bitrate = False
|
||||
|
||||
self.announce_rate_target = None
|
||||
self.announce_rate_grace = None
|
||||
self.announce_rate_penalty = None
|
||||
@@ -137,6 +145,9 @@ class LocalClientInterface(Interface):
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
if self._force_bitrate:
|
||||
time.sleep(len(data) / self.bitrate * 8)
|
||||
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.rxb += len(data)
|
||||
@@ -154,6 +165,8 @@ class LocalClientInterface(Interface):
|
||||
if self.online:
|
||||
try:
|
||||
self.writing = True
|
||||
if self._force_bitrate:
|
||||
time.sleep(len(data) / self.bitrate * 8)
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
self.socket.sendall(data)
|
||||
self.writing = False
|
||||
@@ -292,7 +305,6 @@ class LocalServerInterface(Interface):
|
||||
|
||||
address = (self.bind_ip, self.bind_port)
|
||||
|
||||
ThreadingTCPServer.allow_reuse_address = True
|
||||
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
|
||||
@@ -125,6 +125,8 @@ class RNodeInterface(Interface):
|
||||
self.stopbits = 1
|
||||
self.timeout = 100
|
||||
self.online = False
|
||||
self.detached = False
|
||||
self.reconnecting= False
|
||||
|
||||
self.frequency = frequency
|
||||
self.bandwidth = bandwidth
|
||||
@@ -210,9 +212,10 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
if not self.detached and not self.reconnecting:
|
||||
thread = threading.Thread(target=self.reconnect_port)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
|
||||
def open_port(self):
|
||||
@@ -233,7 +236,15 @@ class RNodeInterface(Interface):
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
self.r_frequency = None
|
||||
self.r_bandwidth = None
|
||||
self.r_txpower = None
|
||||
self.r_sf = None
|
||||
self.r_cr = None
|
||||
self.r_state = None
|
||||
self.r_lock = None
|
||||
sleep(2.0)
|
||||
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
@@ -242,7 +253,8 @@ class RNodeInterface(Interface):
|
||||
sleep(0.2)
|
||||
|
||||
if not self.detected:
|
||||
raise IOError("Could not detect device")
|
||||
RNS.log("Could not detect device for "+str(self), RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
else:
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
self.display = True
|
||||
@@ -260,7 +272,6 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Make sure that your hardware actually supports the parameters specified in the configuration", RNS.LOG_ERROR)
|
||||
RNS.log("Aborting RNode startup", RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
raise IOError("RNode interface did not pass configuration validation")
|
||||
|
||||
|
||||
def initRadio(self):
|
||||
@@ -395,7 +406,7 @@ class RNodeInterface(Interface):
|
||||
|
||||
|
||||
def validateRadioState(self):
|
||||
RNS.log("Wating for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
RNS.log("Waiting for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
sleep(0.25);
|
||||
|
||||
self.validcfg = True
|
||||
@@ -620,7 +631,7 @@ class RNodeInterface(Interface):
|
||||
if (byte == KISS.ERROR_INITRADIO):
|
||||
RNS.log(str(self)+" hardware initialisation error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Radio initialisation failure")
|
||||
elif (byte == KISS.ERROR_INITRADIO):
|
||||
elif (byte == KISS.ERROR_TXFAILED):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
else:
|
||||
@@ -668,11 +679,17 @@ class RNodeInterface(Interface):
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
try:
|
||||
self.serial.close()
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if not self.detached and not self.reconnecting:
|
||||
self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
self.reconnecting = True
|
||||
while not self.online and not self.detached:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
@@ -682,10 +699,12 @@ class RNodeInterface(Interface):
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
self.reconnecting = False
|
||||
if self.online:
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.disable_external_framebuffer()
|
||||
self.setRadioState(KISS.RADIO_STATE_OFF)
|
||||
self.leave()
|
||||
|
||||
@@ -408,25 +408,15 @@ class TCPServerInterface(Interface):
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["addr"]
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["broadcast"]
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
|
||||
self.rxb = 0
|
||||
|
||||
@@ -34,25 +34,15 @@ class UDPInterface(Interface):
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['addr']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["addr"]
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
import importlib
|
||||
if importlib.util.find_spec('netifaces') != None:
|
||||
import netifaces
|
||||
return netifaces.ifaddresses(name)[netifaces.AF_INET][0]['broadcast']
|
||||
else:
|
||||
RNS.log("Getting interface addresses from device names requires the netifaces module.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install it with the command: python3 -m pip install netifaces", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["broadcast"]
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
|
||||
self.rxb = 0
|
||||
|
||||
+52
-9
@@ -22,10 +22,12 @@
|
||||
|
||||
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
|
||||
from RNS.Cryptography import Fernet
|
||||
from RNS.Channel import Channel, LinkChannelOutlet
|
||||
|
||||
from time import sleep
|
||||
from .vendor import umsgpack as umsgpack
|
||||
import threading
|
||||
import inspect
|
||||
import math
|
||||
import time
|
||||
import RNS
|
||||
@@ -162,6 +164,7 @@ class Link:
|
||||
self.destination = destination
|
||||
self.attached_interface = None
|
||||
self.__remote_identity = None
|
||||
self._channel = None
|
||||
if self.destination == None:
|
||||
self.initiator = False
|
||||
self.prv = X25519PrivateKey.generate()
|
||||
@@ -276,6 +279,10 @@ class Link:
|
||||
self.activated_at = time.time()
|
||||
RNS.Transport.activate_link(self)
|
||||
RNS.log("Link "+str(self)+" established with "+str(self.destination)+", RTT is "+str(round(self.rtt, 3))+"s", RNS.LOG_VERBOSE)
|
||||
|
||||
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
|
||||
self.establishment_rate = self.establishment_cost/self.rtt
|
||||
|
||||
rtt_data = umsgpack.packb(self.rtt)
|
||||
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
|
||||
rtt_packet.send()
|
||||
@@ -362,11 +369,6 @@ class Link:
|
||||
|
||||
def rtt_packet(self, packet):
|
||||
try:
|
||||
# TODO: This is crude, we should use the delta
|
||||
# to model a more representative per-bit round
|
||||
# trip time, and use that to set a sensible RTT
|
||||
# expectancy for the link. This will have to do
|
||||
# for now though.
|
||||
measured_rtt = time.time() - self.request_time
|
||||
plaintext = self.decrypt(packet.data)
|
||||
rtt = umsgpack.unpackb(plaintext)
|
||||
@@ -374,13 +376,28 @@ class Link:
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
|
||||
|
||||
if self.owner.callbacks.link_established != None:
|
||||
self.owner.callbacks.link_established(self)
|
||||
if self.rtt != None and self.establishment_cost != None and self.rtt > 0 and self.establishment_cost > 0:
|
||||
self.establishment_rate = self.establishment_cost/self.rtt
|
||||
|
||||
try:
|
||||
if self.owner.callbacks.link_established != None:
|
||||
self.owner.callbacks.link_established(self)
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred in external link establishment callback. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error occurred while processing RTT packet, tearing down link. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
self.teardown()
|
||||
|
||||
def get_establishment_rate(self):
|
||||
"""
|
||||
:returns: The data transfer rate at which the link establishment procedure ocurred, in bits per second.
|
||||
"""
|
||||
if self.establishment_rate != None:
|
||||
return self.establishment_rate*8
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_salt(self):
|
||||
return self.link_id
|
||||
|
||||
@@ -450,6 +467,8 @@ class Link:
|
||||
resource.cancel()
|
||||
for resource in self.outgoing_resources:
|
||||
resource.cancel()
|
||||
if self._channel:
|
||||
self._channel._shutdown()
|
||||
|
||||
self.prv = None
|
||||
self.pub = None
|
||||
@@ -568,7 +587,13 @@ class Link:
|
||||
|
||||
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 len(inspect.signature(response_generator).parameters) == 5:
|
||||
response = response_generator(path, request_data, request_id, self.__remote_identity, requested_at)
|
||||
elif len(inspect.signature(response_generator).parameters) == 6:
|
||||
response = response_generator(path, request_data, request_id, self.link_id, self.__remote_identity, requested_at)
|
||||
else:
|
||||
raise TypeError("Invalid signature for response generator callback")
|
||||
|
||||
if response != None:
|
||||
packed_response = umsgpack.packb([request_id, response])
|
||||
|
||||
@@ -624,6 +649,16 @@ class Link:
|
||||
if pending_request.request_id == resource.request_id:
|
||||
pending_request.request_timed_out(None)
|
||||
|
||||
def get_channel(self):
|
||||
"""
|
||||
Get the ``Channel`` for this link.
|
||||
|
||||
:return: ``Channel`` object
|
||||
"""
|
||||
if self._channel is None:
|
||||
self._channel = Channel(LinkChannelOutlet(self))
|
||||
return self._channel
|
||||
|
||||
def receive(self, packet):
|
||||
self.watchdog_lock = True
|
||||
if not self.status == Link.CLOSED and not (self.initiator and packet.context == RNS.Packet.KEEPALIVE and packet.data == bytes([0xFF])):
|
||||
@@ -770,6 +805,14 @@ class Link:
|
||||
for resource in self.incoming_resources:
|
||||
resource.receive_part(packet)
|
||||
|
||||
elif packet.context == RNS.Packet.CHANNEL:
|
||||
if not self._channel:
|
||||
RNS.log(f"Channel data received without open channel", RNS.LOG_DEBUG)
|
||||
else:
|
||||
packet.prove()
|
||||
plaintext = self.decrypt(packet.data)
|
||||
self._channel._receive(plaintext)
|
||||
|
||||
elif packet.packet_type == RNS.Packet.PROOF:
|
||||
if packet.context == RNS.Packet.RESOURCE_PRF:
|
||||
resource_hash = packet.data[0:RNS.Identity.HASHLENGTH//8]
|
||||
|
||||
+4
-5
@@ -58,9 +58,7 @@ class Packet:
|
||||
# Header types
|
||||
HEADER_1 = 0x00 # Normal header format
|
||||
HEADER_2 = 0x01 # Header format used for packets in transport
|
||||
HEADER_3 = 0x02 # Reserved
|
||||
HEADER_4 = 0x03 # Reserved
|
||||
header_types = [HEADER_1, HEADER_2, HEADER_3, HEADER_4]
|
||||
header_types = [HEADER_1, HEADER_2]
|
||||
|
||||
# Packet context types
|
||||
NONE = 0x00 # Generic data packet
|
||||
@@ -77,6 +75,7 @@ class Packet:
|
||||
PATH_RESPONSE = 0x0B # Packet is a response to a path request
|
||||
COMMAND = 0x0C # Packet is a command
|
||||
COMMAND_STATUS = 0x0D # Packet is a status of an executed command
|
||||
CHANNEL = 0x0E # Packet contains link channel data
|
||||
KEEPALIVE = 0xFA # Packet is a keepalive packet
|
||||
LINKIDENTIFY = 0xFB # Packet is a link peer identification proof
|
||||
LINKCLOSE = 0xFC # Packet is a link close message
|
||||
@@ -142,7 +141,7 @@ class Packet:
|
||||
|
||||
def get_packed_flags(self):
|
||||
if self.context == Packet.LRPROOF:
|
||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | RNS.Destination.LINK | self.packet_type
|
||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (RNS.Destination.LINK << 2) | self.packet_type
|
||||
else:
|
||||
packed_flags = (self.header_type << 6) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
|
||||
return packed_flags
|
||||
@@ -215,7 +214,7 @@ class Packet:
|
||||
self.flags = self.raw[0]
|
||||
self.hops = self.raw[1]
|
||||
|
||||
self.header_type = (self.flags & 0b11000000) >> 6
|
||||
self.header_type = (self.flags & 0b01000000) >> 6
|
||||
self.transport_type = (self.flags & 0b00110000) >> 4
|
||||
self.destination_type = (self.flags & 0b00001100) >> 2
|
||||
self.packet_type = (self.flags & 0b00000011)
|
||||
|
||||
+5
-2
@@ -117,7 +117,7 @@ class Reticulum:
|
||||
# from interface speed, but a better general approach would most
|
||||
# probably be to let Reticulum somehow continously build a map of
|
||||
# per-hop latencies and use this map for the timeout calculation.
|
||||
DEFAULT_PER_HOP_TIMEOUT = 5
|
||||
DEFAULT_PER_HOP_TIMEOUT = 6
|
||||
|
||||
# Length of truncated hashes in bits.
|
||||
TRUNCATED_HASHLENGTH = 128
|
||||
@@ -167,7 +167,7 @@ class Reticulum:
|
||||
RNS.exit()
|
||||
|
||||
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None):
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
|
||||
"""
|
||||
Initialises and starts a Reticulum instance. This must be
|
||||
done before any other operations, and Reticulum will not
|
||||
@@ -211,6 +211,7 @@ class Reticulum:
|
||||
self.ifac_salt = Reticulum.IFAC_SALT
|
||||
|
||||
self.requested_loglevel = loglevel
|
||||
self.requested_verbosity = verbosity
|
||||
if self.requested_loglevel != None:
|
||||
if self.requested_loglevel > RNS.LOG_EXTREME:
|
||||
self.requested_loglevel = RNS.LOG_EXTREME
|
||||
@@ -337,6 +338,8 @@ class Reticulum:
|
||||
value = self.config["logging"][option]
|
||||
if option == "loglevel" and self.requested_loglevel == None:
|
||||
RNS.loglevel = int(value)
|
||||
if self.requested_verbosity != None:
|
||||
RNS.loglevel += self.requested_verbosity
|
||||
if RNS.loglevel < 0:
|
||||
RNS.loglevel = 0
|
||||
if RNS.loglevel > 7:
|
||||
|
||||
+52
-5
@@ -584,14 +584,39 @@ class Transport:
|
||||
try:
|
||||
if hasattr(interface, "ifac_identity") and interface.ifac_identity != None:
|
||||
# Calculate packet access code
|
||||
ifac = interface.ifac_identity.sign(raw)[-interface.ifac_size:]
|
||||
ifac = interface.ifac_identity.sign(raw)[-interface.ifac_size:]
|
||||
|
||||
# Generate mask
|
||||
mask = RNS.Cryptography.hkdf(
|
||||
length=len(raw)+interface.ifac_size,
|
||||
derive_from=ifac,
|
||||
salt=interface.ifac_key,
|
||||
context=None,
|
||||
)
|
||||
|
||||
# Set IFAC flag
|
||||
new_header = bytes([raw[0] | 0x80, raw[1]])
|
||||
|
||||
# Assemble new payload with IFAC and send it
|
||||
# Assemble new payload with IFAC
|
||||
new_raw = new_header+ifac+raw[2:]
|
||||
interface.processOutgoing(new_raw)
|
||||
|
||||
# Mask payload
|
||||
i = 0; masked_raw = b""
|
||||
for byte in new_raw:
|
||||
if i == 0:
|
||||
# Mask first header byte, but make sure the
|
||||
# IFAC flag is still set
|
||||
masked_raw += bytes([byte ^ mask[i] | 0x80])
|
||||
elif i == 1 or i > interface.ifac_size+1:
|
||||
# Mask second header byte and payload
|
||||
masked_raw += bytes([byte ^ mask[i]])
|
||||
else:
|
||||
# Don't mask the IFAC itself
|
||||
masked_raw += bytes([byte])
|
||||
i += 1
|
||||
|
||||
# Send it
|
||||
interface.processOutgoing(masked_raw)
|
||||
|
||||
else:
|
||||
interface.processOutgoing(raw)
|
||||
@@ -857,6 +882,8 @@ class Transport:
|
||||
return True
|
||||
if packet.context == RNS.Packet.CACHE_REQUEST:
|
||||
return True
|
||||
if packet.context == RNS.Packet.CHANNEL:
|
||||
return True
|
||||
|
||||
if packet.destination_type == RNS.Destination.PLAIN:
|
||||
if packet.packet_type != RNS.Packet.ANNOUNCE:
|
||||
@@ -905,6 +932,26 @@ class Transport:
|
||||
# Extract IFAC
|
||||
ifac = raw[2:2+interface.ifac_size]
|
||||
|
||||
# Generate mask
|
||||
mask = RNS.Cryptography.hkdf(
|
||||
length=len(raw),
|
||||
derive_from=ifac,
|
||||
salt=interface.ifac_key,
|
||||
context=None,
|
||||
)
|
||||
|
||||
# Unmask payload
|
||||
i = 0; unmasked_raw = b""
|
||||
for byte in raw:
|
||||
if i <= 1 or i > interface.ifac_size+1:
|
||||
# Unmask header bytes and payload
|
||||
unmasked_raw += bytes([byte ^ mask[i]])
|
||||
else:
|
||||
# Don't unmask IFAC itself
|
||||
unmasked_raw += bytes([byte])
|
||||
i += 1
|
||||
raw = unmasked_raw
|
||||
|
||||
# Unset IFAC flag
|
||||
new_header = bytes([raw[0] & 0x7f, raw[1]])
|
||||
|
||||
@@ -1427,12 +1474,12 @@ class Transport:
|
||||
# Check that the announced destination matches
|
||||
# the handlers aspect filter
|
||||
execute_callback = False
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash)
|
||||
if handler.aspect_filter == None:
|
||||
# If the handlers aspect filter is set to
|
||||
# None, we execute the callback in all cases
|
||||
execute_callback = True
|
||||
else:
|
||||
announce_identity = RNS.Identity.recall(packet.destination_hash)
|
||||
handler_expected_hash = RNS.Destination.hash_from_name_and_identity(handler.aspect_filter, announce_identity)
|
||||
if packet.destination_hash == handler_expected_hash:
|
||||
execute_callback = True
|
||||
@@ -2359,4 +2406,4 @@ class Transport:
|
||||
@staticmethod
|
||||
def exit_handler():
|
||||
if not Transport.owner.is_connected_to_shared_instance:
|
||||
Transport.persist_data()
|
||||
Transport.persist_data()
|
||||
|
||||
@@ -0,0 +1,519 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2023 Mark Qvist / unsigned.io
|
||||
#
|
||||
# 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.
|
||||
|
||||
import RNS
|
||||
import argparse
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import base64
|
||||
|
||||
from RNS._version import __version__
|
||||
|
||||
APP_NAME = "rnid"
|
||||
|
||||
SIG_EXT = "rsg"
|
||||
ENCRYPT_EXT = "rfe"
|
||||
CHUNK_SIZE = 16*1024*1024
|
||||
|
||||
def spin(until=None, msg=None, timeout=None):
|
||||
i = 0
|
||||
syms = "⢄⢂⢁⡁⡈⡐⡠"
|
||||
if timeout != None:
|
||||
timeout = time.time()+timeout
|
||||
|
||||
print(msg+" ", end=" ")
|
||||
while (timeout == None or time.time()<timeout) and not until():
|
||||
time.sleep(0.1)
|
||||
print(("\b\b"+syms[i]+" "), end="")
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
print("\r"+" "*len(msg)+" \r", end="")
|
||||
|
||||
if timeout != None and time.time() > timeout:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def main():
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Reticulum Identity & Encryption Utility")
|
||||
# parser.add_argument("file", nargs="?", default=None, help="input file path", type=str)
|
||||
|
||||
parser.add_argument("--config", metavar="path", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
parser.add_argument("-i", "--identity", metavar="identity", action="store", default=None, help="hexadecimal Reticulum Destination hash or path to Identity file", type=str)
|
||||
parser.add_argument("-g", "--generate", metavar="path", action="store", default=None, help="generate a new Identity")
|
||||
parser.add_argument("-v", "--verbose", action="count", default=0, help="increase verbosity")
|
||||
parser.add_argument("-q", "--quiet", action="count", default=0, help="decrease verbosity")
|
||||
|
||||
parser.add_argument("-a", "--announce", metavar="aspects", action="store", default=None, help="announce a destination based on this Identity")
|
||||
parser.add_argument("-H", "--hash", metavar="aspects", action="store", default=None, help="show destination hash5s for other aspects for this Identity")
|
||||
parser.add_argument("-e", "--encrypt", metavar="path", action="store", default=None, help="encrypt file")
|
||||
parser.add_argument("-d", "--decrypt", metavar="path", action="store", default=None, help="decrypt file")
|
||||
parser.add_argument("-s", "--sign", metavar="path", action="store", default=None, help="sign file")
|
||||
parser.add_argument("-V", "--validate", metavar="path", action="store", default=None, help="validate signature")
|
||||
|
||||
parser.add_argument("-r", "--read", metavar="path", action="store", default=None, help="input file path", type=str)
|
||||
parser.add_argument("-w", "--write", metavar="path", action="store", default=None, help="output file path", type=str)
|
||||
parser.add_argument("-f", "--force", action="store_true", default=None, help="write output even if it overwrites existing files")
|
||||
parser.add_argument("-I", "--stdin", action="store_true", default=False, help=argparse.SUPPRESS) # "read input from STDIN instead of file"
|
||||
parser.add_argument("-O", "--stdout", action="store_true", default=False, help=argparse.SUPPRESS) # help="write output to STDOUT instead of file",
|
||||
|
||||
parser.add_argument("-R", "--request", action="store_true", default=False, help="request unknown Identities from the network")
|
||||
parser.add_argument("-t", action="store", metavar="seconds", type=float, help="identity request timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
|
||||
parser.add_argument("-p", "--print-identity", action="store_true", default=False, help="print identity info and exit")
|
||||
parser.add_argument("-P", "--print-private", action="store_true", default=False, help="allow displaying private keys")
|
||||
|
||||
parser.add_argument("-b", "--base64", action="store_true", default=False, help=argparse.SUPPRESS) # help="Use base64-encoded input and output")
|
||||
|
||||
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
ops = 0;
|
||||
for t in [args.encrypt, args.decrypt, args.validate, args.sign]:
|
||||
if t:
|
||||
ops += 1
|
||||
|
||||
if ops > 1:
|
||||
RNS.log("This utility currently only supports one of the encrypt, decrypt, sign or verify operations per invocation", RNS.LOG_ERROR)
|
||||
exit(1)
|
||||
|
||||
if not args.read:
|
||||
if args.encrypt:
|
||||
args.read = args.encrypt
|
||||
if args.decrypt:
|
||||
args.read = args.decrypt
|
||||
if args.sign:
|
||||
args.read = args.sign
|
||||
|
||||
identity_str = args.identity
|
||||
if not args.generate and not identity_str:
|
||||
print("\nNo identity provided, cannot continue\n")
|
||||
parser.print_help()
|
||||
print("")
|
||||
exit(2)
|
||||
|
||||
else:
|
||||
targetloglevel = 4
|
||||
verbosity = args.verbose
|
||||
quietness = args.quiet
|
||||
if verbosity != 0 or quietness != 0:
|
||||
targetloglevel = targetloglevel+verbosity-quietness
|
||||
|
||||
# Start Reticulum
|
||||
reticulum = RNS.Reticulum(configdir=args.config, loglevel=targetloglevel)
|
||||
RNS.compact_log_fmt = True
|
||||
if args.stdout:
|
||||
RNS.loglevel = -1
|
||||
|
||||
if args.generate:
|
||||
identity = RNS.Identity()
|
||||
if not args.force and os.path.isfile(args.generate):
|
||||
RNS.log("Identity file "+str(args.generate)+" already exists. Not overwriting.", RNS.LOG_ERROR)
|
||||
exit(3)
|
||||
else:
|
||||
try:
|
||||
identity.to_file(args.generate)
|
||||
RNS.log("New identity written to "+str(args.generate))
|
||||
exit(0)
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while saving the generated Identity.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
exit(4)
|
||||
|
||||
identity = None
|
||||
if len(identity_str) == RNS.Reticulum.TRUNCATED_HASHLENGTH//8*2 and not os.path.isfile(identity_str):
|
||||
# Try recalling Identity from hex-encoded hash
|
||||
try:
|
||||
destination_hash = bytes.fromhex(identity_str)
|
||||
identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
if identity == None:
|
||||
if not args.request:
|
||||
RNS.log("Could not recall Identity for "+RNS.prettyhexrep(destination_hash)+".", RNS.LOG_ERROR)
|
||||
RNS.log("You can query the network for unknown Identities with the -R option.", RNS.LOG_ERROR)
|
||||
exit(5)
|
||||
else:
|
||||
RNS.Transport.request_path(destination_hash)
|
||||
def spincheck():
|
||||
return RNS.Identity.recall(destination_hash) != None
|
||||
spin(spincheck, "Requesting unknown Identity for "+RNS.prettyhexrep(destination_hash), args.t)
|
||||
|
||||
if not spincheck():
|
||||
RNS.log("Identity request timed out", RNS.LOG_ERROR)
|
||||
exit(6)
|
||||
else:
|
||||
RNS.log("Received Identity "+str(identity)+" for destination "+RNS.prettyhexrep(destination_hash)+" from the network")
|
||||
identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
else:
|
||||
RNS.log("Recalled Identity "+str(identity)+" for destination "+RNS.prettyhexrep(destination_hash))
|
||||
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Invalid hexadecimal hash provided", RNS.LOG_ERROR)
|
||||
exit(7)
|
||||
|
||||
|
||||
else:
|
||||
# Try loading Identity from file
|
||||
if not os.path.isfile(identity_str):
|
||||
RNS.log("Specified Identity file not found")
|
||||
exit(8)
|
||||
else:
|
||||
try:
|
||||
identity = RNS.Identity.from_file(identity_str)
|
||||
RNS.log("Loaded Identity "+str(identity)+" from "+str(identity_str))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not decode Identity from specified file")
|
||||
exit(9)
|
||||
|
||||
if identity != None:
|
||||
if args.hash:
|
||||
try:
|
||||
aspects = args.hash.split(".")
|
||||
if not len(aspects) > 1:
|
||||
RNS.log("Invalid destination aspects specified", RNS.LOG_ERROR)
|
||||
exit(32)
|
||||
else:
|
||||
app_name = aspects[0]
|
||||
aspects = aspects[1:]
|
||||
if identity.pub != None:
|
||||
destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, app_name, *aspects)
|
||||
RNS.log("The "+str(args.hash)+" destination for this Identity is "+RNS.prettyhexrep(destination.hash))
|
||||
RNS.log("The full destination specifier is "+str(destination))
|
||||
time.sleep(0.25)
|
||||
exit(0)
|
||||
else:
|
||||
raise KeyError("No public key known")
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while attempting to send the announce.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
exit(0)
|
||||
|
||||
if args.announce:
|
||||
try:
|
||||
aspects = args.announce.split(".")
|
||||
if not len(aspects) > 1:
|
||||
RNS.log("Invalid destination aspects specified", RNS.LOG_ERROR)
|
||||
exit(32)
|
||||
else:
|
||||
app_name = aspects[0]
|
||||
aspects = aspects[1:]
|
||||
if identity.prv != None:
|
||||
destination = RNS.Destination(identity, RNS.Destination.IN, RNS.Destination.SINGLE, app_name, *aspects)
|
||||
RNS.log("Created destination "+str(destination))
|
||||
RNS.log("Announcing destination "+RNS.prettyhexrep(destination.hash))
|
||||
destination.announce()
|
||||
time.sleep(0.25)
|
||||
exit(0)
|
||||
else:
|
||||
destination = RNS.Destination(identity, RNS.Destination.OUT, RNS.Destination.SINGLE, app_name, *aspects)
|
||||
RNS.log("The "+str(args.announce)+" destination for this Identity is "+RNS.prettyhexrep(destination.hash))
|
||||
RNS.log("The full destination specifier is "+str(destination))
|
||||
RNS.log("Cannot announce this destination, since the private key is not held")
|
||||
time.sleep(0.25)
|
||||
exit(33)
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while attempting to send the announce.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
exit(0)
|
||||
|
||||
if args.print_identity:
|
||||
RNS.log("Public Key : "+RNS.hexrep(identity.pub_bytes, delimit=False))
|
||||
if identity.prv:
|
||||
if args.print_private:
|
||||
RNS.log("Private Key : "+RNS.hexrep(identity.prv_bytes, delimit=False))
|
||||
else:
|
||||
RNS.log("Private Key : Hidden")
|
||||
exit(0)
|
||||
|
||||
if args.validate:
|
||||
if not args.read and args.validate.lower().endswith("."+SIG_EXT):
|
||||
args.read = str(args.validate).replace("."+SIG_EXT, "")
|
||||
|
||||
if not os.path.isfile(args.validate):
|
||||
RNS.log("Signature file "+str(args.read)+" not found", RNS.LOG_ERROR)
|
||||
exit(10)
|
||||
|
||||
if not os.path.isfile(args.read):
|
||||
RNS.log("Input file "+str(args.read)+" not found", RNS.LOG_ERROR)
|
||||
exit(11)
|
||||
|
||||
data_input = None
|
||||
if args.read:
|
||||
if not os.path.isfile(args.read):
|
||||
RNS.log("Input file "+str(args.read)+" not found", RNS.LOG_ERROR)
|
||||
exit(12)
|
||||
else:
|
||||
try:
|
||||
data_input = open(args.read, "rb")
|
||||
except Exception as e:
|
||||
RNS.log("Could not open input file for reading", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
exit(13)
|
||||
|
||||
# TODO: Actually expand this to a good solution
|
||||
# probably need to create a wrapper that takes
|
||||
# into account not closing stdin when done
|
||||
# elif args.stdin:
|
||||
# data_input = sys.stdin
|
||||
|
||||
data_output = None
|
||||
if args.encrypt and not args.write and not args.stdout and args.read:
|
||||
args.write = str(args.read)+"."+ENCRYPT_EXT
|
||||
|
||||
if args.decrypt and not args.write and not args.stdout and args.read and args.read.lower().endswith("."+ENCRYPT_EXT):
|
||||
args.write = str(args.read).replace("."+ENCRYPT_EXT, "")
|
||||
|
||||
if args.sign and identity.prv == None:
|
||||
RNS.log("Specified Identity does not hold a private key. Cannot sign.", RNS.LOG_ERROR)
|
||||
exit(14)
|
||||
|
||||
if args.sign and not args.write and not args.stdout and args.read:
|
||||
args.write = str(args.read)+"."+SIG_EXT
|
||||
|
||||
if args.write:
|
||||
if not args.force and os.path.isfile(args.write):
|
||||
RNS.log("Output file "+str(args.write)+" already exists. Not overwriting.", RNS.LOG_ERROR)
|
||||
exit(15)
|
||||
else:
|
||||
try:
|
||||
data_output = open(args.write, "wb")
|
||||
except Exception as e:
|
||||
RNS.log("Could not open output file for writing", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
exit(15)
|
||||
|
||||
# TODO: Actually expand this to a good solution
|
||||
# probably need to create a wrapper that takes
|
||||
# into account not closing stdout when done
|
||||
# elif args.stdout:
|
||||
# data_output = sys.stdout
|
||||
|
||||
if args.sign:
|
||||
if identity.prv == None:
|
||||
RNS.log("Specified Identity does not hold a private key. Cannot sign.", RNS.LOG_ERROR)
|
||||
exit(16)
|
||||
|
||||
if not data_input:
|
||||
if not args.stdout:
|
||||
RNS.log("Signing requested, but no input data specified", RNS.LOG_ERROR)
|
||||
exit(17)
|
||||
else:
|
||||
if not data_output:
|
||||
if not args.stdout:
|
||||
RNS.log("Signing requested, but no output specified", RNS.LOG_ERROR)
|
||||
exit(18)
|
||||
|
||||
if not args.stdout:
|
||||
RNS.log("Signing "+str(args.read))
|
||||
|
||||
try:
|
||||
data_output.write(identity.sign(data_input.read()))
|
||||
data_output.close()
|
||||
data_input.close()
|
||||
|
||||
if not args.stdout:
|
||||
if args.read:
|
||||
RNS.log("File "+str(args.read)+" signed with "+str(identity)+" to "+str(args.write))
|
||||
exit(0)
|
||||
|
||||
except Exception as e:
|
||||
if not args.stdout:
|
||||
RNS.log("An error ocurred while encrypting data.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
try:
|
||||
data_output.close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
data_input.close()
|
||||
except:
|
||||
pass
|
||||
exit(19)
|
||||
|
||||
if args.validate:
|
||||
if not data_input:
|
||||
if not args.stdout:
|
||||
RNS.log("Signature verification requested, but no input data specified", RNS.LOG_ERROR)
|
||||
exit(20)
|
||||
else:
|
||||
# if not args.stdout:
|
||||
# RNS.log("Verifying "+str(args.validate)+" for "+str(args.read))
|
||||
|
||||
try:
|
||||
try:
|
||||
sig_input = open(args.validate, "rb")
|
||||
except Exception as e:
|
||||
RNS.log("An error ocurred while opening "+str(args.validate)+".", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
exit(21)
|
||||
|
||||
|
||||
validated = identity.validate(sig_input.read(), data_input.read())
|
||||
sig_input.close()
|
||||
data_input.close()
|
||||
|
||||
if not validated:
|
||||
if not args.stdout:
|
||||
RNS.log("Signature "+str(args.validate)+" for file "+str(args.read)+" is invalid", RNS.LOG_ERROR)
|
||||
exit(22)
|
||||
else:
|
||||
if not args.stdout:
|
||||
RNS.log("Signature "+str(args.validate)+" for file "+str(args.read)+" made by Identity "+str(identity)+" is valid")
|
||||
exit(0)
|
||||
|
||||
except Exception as e:
|
||||
if not args.stdout:
|
||||
RNS.log("An error ocurred while validating signature.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
try:
|
||||
data_output.close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
data_input.close()
|
||||
except:
|
||||
pass
|
||||
exit(23)
|
||||
|
||||
if args.encrypt:
|
||||
if not data_input:
|
||||
if not args.stdout:
|
||||
RNS.log("Encryption requested, but no input data specified", RNS.LOG_ERROR)
|
||||
exit(24)
|
||||
else:
|
||||
if not data_output:
|
||||
if not args.stdout:
|
||||
RNS.log("Encryption requested, but no output specified", RNS.LOG_ERROR)
|
||||
exit(25)
|
||||
|
||||
if not args.stdout:
|
||||
RNS.log("Encrypting "+str(args.read))
|
||||
|
||||
try:
|
||||
more_data = True
|
||||
while more_data:
|
||||
chunk = data_input.read(CHUNK_SIZE)
|
||||
if chunk:
|
||||
data_output.write(identity.encrypt(chunk))
|
||||
else:
|
||||
more_data = False
|
||||
data_output.close()
|
||||
data_input.close()
|
||||
if not args.stdout:
|
||||
if args.read:
|
||||
RNS.log("File "+str(args.read)+" encrypted for "+str(identity)+" to "+str(args.write))
|
||||
exit(0)
|
||||
|
||||
except Exception as e:
|
||||
if not args.stdout:
|
||||
RNS.log("An error ocurred while encrypting data.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
try:
|
||||
data_output.close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
data_input.close()
|
||||
except:
|
||||
pass
|
||||
exit(26)
|
||||
|
||||
if args.decrypt:
|
||||
if identity.prv == None:
|
||||
RNS.log("Specified Identity does not hold a private key. Cannot decrypt.", RNS.LOG_ERROR)
|
||||
exit(27)
|
||||
|
||||
if not data_input:
|
||||
if not args.stdout:
|
||||
RNS.log("Decryption requested, but no input data specified", RNS.LOG_ERROR)
|
||||
exit(28)
|
||||
else:
|
||||
if not data_output:
|
||||
if not args.stdout:
|
||||
RNS.log("Decryption requested, but no output specified", RNS.LOG_ERROR)
|
||||
exit(29)
|
||||
|
||||
if not args.stdout:
|
||||
RNS.log("Decrypting "+str(args.read)+"...")
|
||||
|
||||
try:
|
||||
more_data = True
|
||||
while more_data:
|
||||
chunk = data_input.read(CHUNK_SIZE)
|
||||
if chunk:
|
||||
plaintext = identity.decrypt(chunk)
|
||||
if plaintext == None:
|
||||
if not args.stdout:
|
||||
RNS.log("Data could not be decrypted with the specified Identity")
|
||||
exit(30)
|
||||
else:
|
||||
data_output.write(plaintext)
|
||||
else:
|
||||
more_data = False
|
||||
data_output.close()
|
||||
data_input.close()
|
||||
if not args.stdout:
|
||||
if args.read:
|
||||
RNS.log("File "+str(args.read)+" decrypted with "+str(identity)+" to "+str(args.write))
|
||||
exit(0)
|
||||
|
||||
except Exception as e:
|
||||
if not args.stdout:
|
||||
RNS.log("An error ocurred while decrypting data.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
try:
|
||||
data_output.close()
|
||||
except:
|
||||
pass
|
||||
try:
|
||||
data_input.close()
|
||||
except:
|
||||
pass
|
||||
exit(31)
|
||||
|
||||
if True:
|
||||
pass
|
||||
|
||||
elif False:
|
||||
pass
|
||||
|
||||
else:
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit(255)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+214
-44
@@ -25,6 +25,7 @@
|
||||
from time import sleep
|
||||
import argparse
|
||||
import threading
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import struct
|
||||
@@ -32,6 +33,7 @@ import datetime
|
||||
import time
|
||||
import math
|
||||
import hashlib
|
||||
import zipfile
|
||||
from urllib.request import urlretrieve
|
||||
from importlib import util
|
||||
import RNS
|
||||
@@ -39,7 +41,7 @@ import RNS
|
||||
RNS.logtimefmt = "%H:%M:%S"
|
||||
RNS.compact_log_fmt = True
|
||||
|
||||
program_version = "2.1.0"
|
||||
program_version = "2.1.2"
|
||||
eth_addr = "0x81F7B979fEa6134bA9FD5c701b3501A2e61E897a"
|
||||
btc_addr = "3CPmacGm34qYvR6XWLVEJmi2aNe3PZqUuq"
|
||||
xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET"
|
||||
@@ -77,7 +79,9 @@ class KISS():
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_DISP_INT = 0x45
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_BT_PIN = 0x62
|
||||
CMD_BOARD = 0x47
|
||||
CMD_PLATFORM = 0x48
|
||||
CMD_MCU = 0x49
|
||||
@@ -128,6 +132,10 @@ class ROM():
|
||||
MODEL_A2 = 0xA2
|
||||
MODEL_A7 = 0xA7
|
||||
|
||||
PRODUCT_T32_10 = 0xB2
|
||||
MODEL_BA = 0xBA
|
||||
MODEL_BB = 0xBB
|
||||
|
||||
PRODUCT_T32_20 = 0xB0
|
||||
MODEL_B3 = 0xB3
|
||||
MODEL_B8 = 0xB8
|
||||
@@ -179,6 +187,7 @@ products = {
|
||||
ROM.PRODUCT_RNODE: "RNode",
|
||||
ROM.PRODUCT_HMBRW: "Hombrew RNode",
|
||||
ROM.PRODUCT_TBEAM: "LilyGO T-Beam",
|
||||
ROM.PRODUCT_T32_10: "LilyGO LoRa32 v1.0",
|
||||
ROM.PRODUCT_T32_20: "LilyGO LoRa32 v2.0",
|
||||
ROM.PRODUCT_T32_21: "LilyGO LoRa32 v2.1",
|
||||
ROM.PRODUCT_H32_V2: "Heltec LoRa32 v2",
|
||||
@@ -206,6 +215,8 @@ models = {
|
||||
0xB8: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v20.zip"],
|
||||
0xB4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v21.zip"],
|
||||
0xB9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v21.zip"],
|
||||
0xBA: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_lora32v10.zip"],
|
||||
0xBB: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v10.zip"],
|
||||
0xC4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_heltec32v2.zip"],
|
||||
0xC9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_heltec32v2.zip"],
|
||||
0xE4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_tbeam.zip"],
|
||||
@@ -386,6 +397,21 @@ class RNode():
|
||||
RNS.log("Radio reporting bandwidth is "+str(self.r_bandwidth/1000.0)+" KHz")
|
||||
self.updateBitrate()
|
||||
|
||||
elif (command == KISS.CMD_BT_PIN):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == KISS.TFEND):
|
||||
byte = KISS.FEND
|
||||
if (byte == KISS.TFESC):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 4):
|
||||
self.r_bt_pin = command_buffer[0] << 24 | command_buffer[1] << 16 | command_buffer[2] << 8 | command_buffer[3]
|
||||
RNS.log("Bluetooth pairing PIN is: {:06d}".format(self.r_bt_pin))
|
||||
|
||||
elif (command == KISS.CMD_DEV_HASH):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
@@ -540,6 +566,13 @@ class RNode():
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending host left command to device")
|
||||
|
||||
def set_display_intensity(self, intensity):
|
||||
data = bytes([intensity & 0xFF])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_INT])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending bluetooth enable command to device")
|
||||
|
||||
def enable_bluetooth(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_BT_CTRL, 0x01, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
@@ -815,6 +848,9 @@ class RNode():
|
||||
print(" WARNING! This device is NOT verifiable and should NOT be trusted.")
|
||||
print(" Someone could have added privacy-breaking or malicious code to it.")
|
||||
print(" ")
|
||||
print(" Please verify the signing key is present on this machine.")
|
||||
print(" Autogenerated keys will not match another machine's signature.")
|
||||
print(" ")
|
||||
print(" Proceed at your own risk and responsibility! If you created this")
|
||||
print(" device yourself, please read the documentation on how to sign your")
|
||||
print(" device to avoid this warning.")
|
||||
@@ -865,6 +901,7 @@ class RNode():
|
||||
selected_version = None
|
||||
selected_hash = None
|
||||
firmware_version_url = "https://unsigned.io/firmware/latest/?v="+program_version+"&variant="
|
||||
fallback_firmware_version_url = "https://github.com/markqvist/rnode_firmware/releases/latest/download/release.json"
|
||||
def ensure_firmware_file(fw_filename):
|
||||
global selected_version, selected_hash, upd_nocheck
|
||||
if fw_filename == "extracted_rnode_firmware.zip":
|
||||
@@ -903,11 +940,35 @@ def ensure_firmware_file(fw_filename):
|
||||
if selected_version == None:
|
||||
if not upd_nocheck:
|
||||
try:
|
||||
urlretrieve(firmware_version_url+fw_filename, UPD_DIR+"/"+fw_filename+".version.latest")
|
||||
try:
|
||||
urlretrieve(firmware_version_url+fw_filename, UPD_DIR+"/"+fw_filename+".version.latest")
|
||||
except Exception as e:
|
||||
RNS.log("")
|
||||
RNS.log("WARNING!")
|
||||
RNS.log("Failed to retrieve latest version information for your board from the default server")
|
||||
RNS.log("Will retry using the following fallback URL: "+fallback_firmware_version_url)
|
||||
RNS.log("")
|
||||
RNS.log("Hit enter if you want to proceed")
|
||||
input()
|
||||
try:
|
||||
urlretrieve(fallback_firmware_version_url, UPD_DIR+"/fallback_release_info.json")
|
||||
import json
|
||||
with open(UPD_DIR+"/fallback_release_info.json", "rb") as rif:
|
||||
rdat = json.loads(rif.read())
|
||||
variant = rdat[fw_filename]
|
||||
with open(UPD_DIR+"/"+fw_filename+".version.latest", "wb") as verf:
|
||||
inf_str = str(variant["version"])+" "+str(variant["hash"])
|
||||
verf.write(inf_str.encode("utf-8"))
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while trying fallback URL: "+str(e))
|
||||
raise e
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Failed to retrive latest version information for your board.")
|
||||
RNS.log("Check your internet connection and try again.")
|
||||
RNS.log("If you don't have Internet access currently, use the --fw-version option to manually specify a version.")
|
||||
RNS.log("You can also use --extract to copy the firmware from a known-good RNode of the same model.")
|
||||
exit()
|
||||
|
||||
import shutil
|
||||
@@ -932,6 +993,7 @@ def ensure_firmware_file(fw_filename):
|
||||
os.makedirs(UPD_DIR+"/"+selected_version)
|
||||
|
||||
if not os.path.isfile(UPD_DIR+"/"+selected_version+"/"+fw_filename):
|
||||
RNS.log("Firmware "+UPD_DIR+"/"+selected_version+"/"+fw_filename+" not found.")
|
||||
RNS.log("Downloading missing firmware file: "+fw_filename+" for version "+selected_version)
|
||||
urlretrieve(update_target_url, UPD_DIR+"/"+selected_version+"/"+fw_filename)
|
||||
RNS.log("Firmware file downloaded")
|
||||
@@ -946,6 +1008,7 @@ def ensure_firmware_file(fw_filename):
|
||||
selected_hash = release_info.split()[1]
|
||||
except Exception as e:
|
||||
RNS.log("Could not read locally cached release information.")
|
||||
RNS.log("Ensure "+UPD_DIR+"/"+selected_version+"/"+fw_filename+".version exists and has the correct format and hash.")
|
||||
RNS.log("You can clear the cache with the --clear-cache option and try again.")
|
||||
|
||||
if selected_hash == None:
|
||||
@@ -1044,6 +1107,8 @@ def main():
|
||||
parser.add_argument("-B", "--bluetooth-off", action="store_true", help="Turn device bluetooth off")
|
||||
parser.add_argument("-p", "--bluetooth-pair", action="store_true", help="Put device into bluetooth pairing mode")
|
||||
|
||||
parser.add_argument("-D", "--display", action="store", metavar="i", type=int, default=None, help="Set display intensity (0-255)")
|
||||
|
||||
parser.add_argument("--freq", action="store", metavar="Hz", type=int, default=None, help="Frequency in Hz for TNC mode")
|
||||
parser.add_argument("--bw", action="store", metavar="Hz", type=int, default=None, help="Bandwidth in Hz for TNC mode")
|
||||
parser.add_argument("--txp", action="store", metavar="dBm", type=int, default=None, help="TX power in dBm for TNC mode")
|
||||
@@ -1092,6 +1157,11 @@ def main():
|
||||
|
||||
if args.fw_version != None:
|
||||
selected_version = args.fw_version
|
||||
try:
|
||||
check_float = float(selected_version)
|
||||
except ValueError:
|
||||
RNS.log("Selected version \""+selected_version+"\" does not appear to be a number.")
|
||||
exit()
|
||||
|
||||
if args.force_update:
|
||||
force_update = True
|
||||
@@ -1198,7 +1268,16 @@ def main():
|
||||
else:
|
||||
RNS.log("Could not detect a connected RNode")
|
||||
|
||||
if rnode.provisioned and rnode.signature_valid:
|
||||
if rnode.provisioned:
|
||||
if not rnode.signature_valid:
|
||||
print("\nThe device signature in this RNode is unknown and cannot be verified. It is still")
|
||||
print("possible to extract the firmware from it, but you should make absolutely sure that")
|
||||
print("it comes from a trusted source. It is possible that someone could have modified the")
|
||||
print("firmware. If that is the case, these modifications will propagate to any new RNodes")
|
||||
print("descendent from this one!")
|
||||
print("\nHit enter if you are sure you want to continue.")
|
||||
input()
|
||||
|
||||
if rnode.firmware_hash != None:
|
||||
extracted_hash = rnode.firmware_hash
|
||||
extracted_version = rnode.version
|
||||
@@ -1216,11 +1295,11 @@ def main():
|
||||
hash_f.close()
|
||||
|
||||
extraction_parts = [
|
||||
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
|
||||
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
|
||||
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
|
||||
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
|
||||
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port /dev/ttyACM1 --baud 921600 --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
|
||||
("bootloader", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x1000 0x4650 \""+EXT_DIR+"/extracted_rnode_firmware.bootloader\""),
|
||||
("partition table", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x8000 0xC00 \""+EXT_DIR+"/extracted_rnode_firmware.partitions\""),
|
||||
("app boot", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0xe000 0x2000 \""+EXT_DIR+"/extracted_rnode_firmware.boot_app0\""),
|
||||
("application image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x10000 0x200000 \""+EXT_DIR+"/extracted_rnode_firmware.bin\""),
|
||||
("console image", "python \""+CNF_DIR+"/recovery_esptool.py\" --chip esp32 --port "+port_path+" --baud 921600 --before default_reset --after hard_reset read_flash 0x210000 0x1F0000 \""+EXT_DIR+"/extracted_console_image.bin\""),
|
||||
]
|
||||
import subprocess, shlex
|
||||
for part, command in extraction_parts:
|
||||
@@ -1347,10 +1426,11 @@ def main():
|
||||
print(" / \\ Select this option if you have put toghether an RNode")
|
||||
print(" | of your own design, or if you are prototyping one.")
|
||||
print("")
|
||||
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6.1)")
|
||||
print("[4] LilyGO LoRa32 v2.0 (aka T3 v1.6)")
|
||||
print("[5] LilyGO T-Beam")
|
||||
print("[6] Heltec LoRa32 v2")
|
||||
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)")
|
||||
print("[4] LilyGO LoRa32 v2.0")
|
||||
print("[5] LilyGO LoRa32 v1.0")
|
||||
print("[6] LilyGO T-Beam")
|
||||
print("[7] Heltec LoRa32 v2")
|
||||
print(" .")
|
||||
print(" / \\ Select one of these options if you want to easily turn")
|
||||
print(" | a supported development board into an RNode.")
|
||||
@@ -1382,7 +1462,7 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 5:
|
||||
elif c_dev == 6:
|
||||
selected_product = ROM.PRODUCT_TBEAM
|
||||
clear()
|
||||
print("")
|
||||
@@ -1412,6 +1492,25 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 5:
|
||||
selected_product = ROM.PRODUCT_T32_10
|
||||
clear()
|
||||
print("")
|
||||
print("---------------------------------------------------------------------------")
|
||||
print(" LilyGO LoRa32 v1.0 RNode Installer")
|
||||
print("")
|
||||
print("Important! Using RNode firmware on LoRa32 devices should currently be")
|
||||
print("considered experimental. It is not intended for production or critical use.")
|
||||
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
|
||||
print("who would like to experiment with it.")
|
||||
print("")
|
||||
print("Please Note! This device is known to have a faulty battery charging circuit,")
|
||||
print("which can result in overcharging and damaging batteries. If at all possible,")
|
||||
print("it is recommended to avoid this device.")
|
||||
print("")
|
||||
print("Hit enter if you're sure you wish to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 3:
|
||||
selected_product = ROM.PRODUCT_T32_21
|
||||
clear()
|
||||
@@ -1425,7 +1524,7 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 6:
|
||||
elif c_dev == 7:
|
||||
selected_product = ROM.PRODUCT_H32_V2
|
||||
clear()
|
||||
print("")
|
||||
@@ -1557,6 +1656,28 @@ def main():
|
||||
print("That band does not exist, exiting now.")
|
||||
exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_T32_10:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this LoRa32 for?\n")
|
||||
print("[1] 433 MHz")
|
||||
print("[2] 868 MHz")
|
||||
print("[3] 915 MHz")
|
||||
print("[4] 923 MHz")
|
||||
print("\n? ", end="")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 4:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_BA
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model > 1:
|
||||
selected_model = ROM.MODEL_BB
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_T32_20:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this LoRa32 for?\n")
|
||||
@@ -1878,7 +1999,7 @@ def main():
|
||||
if fw_filename == "rnode_firmware_tbeam.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1896,7 +2017,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1911,10 +2032,46 @@ def main():
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_lora32v10.zip":
|
||||
if numeric_version >= 1.59:
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
"--flash_mode", "dio",
|
||||
"--flash_freq", "80m",
|
||||
"--flash_size", "4MB",
|
||||
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.boot_app0",
|
||||
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.bin",
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v10.partitions",
|
||||
]
|
||||
else:
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
"--before", "default_reset",
|
||||
"--after", "hard_reset",
|
||||
"write_flash", "-z",
|
||||
"--flash_mode", "dio",
|
||||
"--flash_freq", "80m",
|
||||
"--flash_size", "4MB",
|
||||
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.boot_app0",
|
||||
"0x1000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_lora32v20.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_lora32v20.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1932,7 +2089,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1950,7 +2107,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_lora32v21.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1968,7 +2125,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -1986,7 +2143,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_heltec32v2.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2004,7 +2161,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2022,7 +2179,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_featheresp32.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2040,7 +2197,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2058,7 +2215,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_esp32_generic.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2076,7 +2233,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2094,7 +2251,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_ng20.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2112,7 +2269,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2130,7 +2287,7 @@ def main():
|
||||
elif fw_filename == "rnode_firmware_ng21.zip":
|
||||
if numeric_version >= 1.55:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2148,7 +2305,7 @@ def main():
|
||||
]
|
||||
else:
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2165,7 +2322,7 @@ def main():
|
||||
]
|
||||
elif fw_filename == "extracted_rnode_firmware.zip":
|
||||
return [
|
||||
flasher,
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32",
|
||||
"--port", args.port,
|
||||
"--baud", "921600",
|
||||
@@ -2236,12 +2393,13 @@ def main():
|
||||
try:
|
||||
if fw_filename.endswith(".zip"):
|
||||
RNS.log("Decompressing firmware...")
|
||||
unzip_status = call(get_flasher_call("unzip", fw_filename))
|
||||
if unzip_status == 0:
|
||||
RNS.log("Firmware decompressed")
|
||||
else:
|
||||
RNS.log("Could not extract firmware from downloaded zip file")
|
||||
try:
|
||||
with zipfile.ZipFile(fw_src+fw_filename) as zip:
|
||||
zip.extractall(fw_src)
|
||||
except Exception as e:
|
||||
RNS.log("Could not decompress firmware from downloaded zip file")
|
||||
exit()
|
||||
RNS.log("Firmware decompressed")
|
||||
|
||||
RNS.log("Flashing RNode firmware to device on "+args.port)
|
||||
from subprocess import call
|
||||
@@ -2363,13 +2521,15 @@ def main():
|
||||
ensure_firmware_file(fw_filename)
|
||||
|
||||
if fw_filename.endswith(".zip") and not fw_filename == "extracted_rnode_firmware.zip":
|
||||
RNS.log("Extracting firmware...")
|
||||
unzip_status = call(get_flasher_call("unzip", fw_filename))
|
||||
if unzip_status == 0:
|
||||
RNS.log("Firmware extracted")
|
||||
else:
|
||||
RNS.log("Could not extract firmware from downloaded zip file")
|
||||
RNS.log("Decompressing firmware...")
|
||||
fw_src = UPD_DIR+"/"+selected_version+"/"
|
||||
try:
|
||||
with zipfile.ZipFile(fw_src+fw_filename) as zip:
|
||||
zip.extractall(fw_src)
|
||||
except Exception as e:
|
||||
RNS.log("Could not decompress firmware from downloaded zip file")
|
||||
exit()
|
||||
RNS.log("Firmware decompressed")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Could not obtain firmware package for your board")
|
||||
@@ -2470,6 +2630,15 @@ def main():
|
||||
RNS.log("but file could not be written to disk.")
|
||||
exit()
|
||||
|
||||
if isinstance(args.display, int):
|
||||
di = args.display
|
||||
if di < 0:
|
||||
di = 0
|
||||
if di > 255:
|
||||
di = 255
|
||||
RNS.log("Setting display intensity to "+str(di))
|
||||
rnode.set_display_intensity(di)
|
||||
|
||||
if args.bluetooth_on:
|
||||
RNS.log("Enabling Bluetooth...")
|
||||
rnode.enable_bluetooth()
|
||||
@@ -2481,8 +2650,9 @@ def main():
|
||||
rnode.leave()
|
||||
|
||||
if args.bluetooth_pair:
|
||||
RNS.log("Putting device into Bluetooth pairing mode...")
|
||||
RNS.log("Putting device into Bluetooth pairing mode. Press enter to exit when done.")
|
||||
rnode.bluetooth_pair()
|
||||
input()
|
||||
rnode.leave()
|
||||
|
||||
if args.info:
|
||||
@@ -2896,4 +3066,4 @@ def extract_recovery_esptool():
|
||||
exit(181)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
||||
|
||||
@@ -30,15 +30,15 @@ from RNS._version import __version__
|
||||
|
||||
|
||||
def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
targetverbosity = verbosity-quietness
|
||||
|
||||
if service:
|
||||
targetlogdest = RNS.LOG_FILE
|
||||
targetloglevel = None
|
||||
targetverbosity = None
|
||||
else:
|
||||
targetlogdest = RNS.LOG_STDOUT
|
||||
|
||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel, logdest=targetlogdest)
|
||||
reticulum = RNS.Reticulum(configdir=configdir, verbosity=targetverbosity, logdest=targetlogdest)
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
RNS.log("Started rnsd version {version} connected to another shared local instance, this is probably NOT what you want!".format(version=__version__), RNS.LOG_WARNING)
|
||||
else:
|
||||
|
||||
@@ -46,7 +46,7 @@ def size_str(num, suffix='B'):
|
||||
|
||||
return "%.2f%s%s" % (num, last_unit, suffix)
|
||||
|
||||
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
|
||||
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None,json=False):
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
|
||||
|
||||
stats = None
|
||||
@@ -56,6 +56,21 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None):
|
||||
pass
|
||||
|
||||
if stats != None:
|
||||
if json:
|
||||
import json
|
||||
for s in stats:
|
||||
if isinstance(stats[s], bytes):
|
||||
stats[s] = RNS.hexrep(stats[s], delimit=False)
|
||||
|
||||
for i in stats[s]:
|
||||
if isinstance(i, dict):
|
||||
for k in i:
|
||||
if isinstance(i[k], bytes):
|
||||
i[k] = RNS.hexrep(i[k], delimit=False)
|
||||
|
||||
print(json.dumps(stats))
|
||||
exit()
|
||||
|
||||
for ifstat in stats["interfaces"]:
|
||||
name = ifstat["name"]
|
||||
|
||||
@@ -175,6 +190,14 @@ def main():
|
||||
help="show all interfaces",
|
||||
default=False
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-j",
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="output in JSON format",
|
||||
default=False
|
||||
)
|
||||
|
||||
parser.add_argument('-v', '--verbose', action='count', default=0)
|
||||
|
||||
@@ -187,7 +210,7 @@ def main():
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter)
|
||||
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter, json=args.json)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
|
||||
+3
-1
@@ -32,6 +32,8 @@ from ._version import __version__
|
||||
from .Reticulum import Reticulum
|
||||
from .Identity import Identity
|
||||
from .Link import Link, RequestReceipt
|
||||
from .Channel import MessageBase
|
||||
from .Buffer import Buffer, RawChannelReader, RawChannelWriter
|
||||
from .Transport import Transport
|
||||
from .Destination import Destination
|
||||
from .Packet import Packet
|
||||
@@ -227,7 +229,7 @@ def phyparams():
|
||||
print("Plaintext Packet MDU : "+str(Packet.PLAIN_MDU)+" bytes")
|
||||
print("Encrypted Packet MDU : "+str(Packet.ENCRYPTED_MDU)+" bytes")
|
||||
print("Link Curve : "+str(Link.CURVE))
|
||||
print("Link Packet MDU : "+str(Packet.ENCRYPTED_MDU)+" bytes")
|
||||
print("Link Packet MDU : "+str(Link.MDU)+" bytes")
|
||||
print("Link Public Key Size : "+str(Link.ECPUBSIZE*8)+" bits")
|
||||
print("Link Private Key Size : "+str(Link.KEYSIZE*8)+" bits")
|
||||
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.4.7"
|
||||
__version__ = "0.5.1"
|
||||
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
import os
|
||||
|
||||
from RNS.vendor.ifaddr._shared import Adapter, IP
|
||||
|
||||
if os.name == "nt":
|
||||
from RNS.vendor.ifaddr._win32 import get_adapters
|
||||
elif os.name == "posix":
|
||||
from RNS.vendor.ifaddr._posix import get_adapters
|
||||
else:
|
||||
raise RuntimeError("Unsupported Operating System: %s" % os.name)
|
||||
|
||||
__all__ = ['Adapter', 'IP', 'get_adapters']
|
||||
Vendored
+93
@@ -0,0 +1,93 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
import os
|
||||
import ctypes.util
|
||||
import ipaddress
|
||||
import collections
|
||||
import socket
|
||||
|
||||
from typing import Iterable, Optional
|
||||
|
||||
import RNS.vendor.ifaddr._shared as shared
|
||||
|
||||
class ifaddrs(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
ifaddrs._fields_ = [
|
||||
('ifa_next', ctypes.POINTER(ifaddrs)),
|
||||
('ifa_name', ctypes.c_char_p),
|
||||
('ifa_flags', ctypes.c_uint),
|
||||
('ifa_addr', ctypes.POINTER(shared.sockaddr)),
|
||||
('ifa_netmask', ctypes.POINTER(shared.sockaddr)),
|
||||
]
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library("socket" if os.uname()[0] == "SunOS" else "c"), use_errno=True) # type: ignore
|
||||
|
||||
|
||||
def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]:
|
||||
|
||||
addr0 = addr = ctypes.POINTER(ifaddrs)()
|
||||
retval = libc.getifaddrs(ctypes.byref(addr))
|
||||
if retval != 0:
|
||||
eno = ctypes.get_errno()
|
||||
raise OSError(eno, os.strerror(eno))
|
||||
|
||||
ips = collections.OrderedDict()
|
||||
|
||||
def add_ip(adapter_name: str, ip: Optional[shared.IP]) -> None:
|
||||
if adapter_name not in ips:
|
||||
index = None # type: Optional[int]
|
||||
try:
|
||||
# Mypy errors on this when the Windows CI runs:
|
||||
# error: Module has no attribute "if_nametoindex"
|
||||
index = socket.if_nametoindex(adapter_name) # type: ignore
|
||||
except (OSError, AttributeError):
|
||||
pass
|
||||
ips[adapter_name] = shared.Adapter(adapter_name, adapter_name, [], index=index)
|
||||
if ip is not None:
|
||||
ips[adapter_name].ips.append(ip)
|
||||
|
||||
while addr:
|
||||
name = addr[0].ifa_name.decode(encoding='UTF-8')
|
||||
ip_addr = shared.sockaddr_to_ip(addr[0].ifa_addr)
|
||||
if ip_addr:
|
||||
if addr[0].ifa_netmask and not addr[0].ifa_netmask[0].sa_familiy:
|
||||
addr[0].ifa_netmask[0].sa_familiy = addr[0].ifa_addr[0].sa_familiy
|
||||
netmask = shared.sockaddr_to_ip(addr[0].ifa_netmask)
|
||||
if isinstance(netmask, tuple):
|
||||
netmaskStr = str(netmask[0])
|
||||
prefixlen = shared.ipv6_prefixlength(ipaddress.IPv6Address(netmaskStr))
|
||||
else:
|
||||
assert netmask is not None, f'sockaddr_to_ip({addr[0].ifa_netmask}) returned None'
|
||||
netmaskStr = str('0.0.0.0/' + netmask)
|
||||
prefixlen = ipaddress.IPv4Network(netmaskStr).prefixlen
|
||||
ip = shared.IP(ip_addr, prefixlen, name)
|
||||
add_ip(name, ip)
|
||||
else:
|
||||
if include_unconfigured:
|
||||
add_ip(name, None)
|
||||
addr = addr[0].ifa_next
|
||||
|
||||
libc.freeifaddrs(addr0)
|
||||
|
||||
return ips.values()
|
||||
Vendored
+198
@@ -0,0 +1,198 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
import ctypes
|
||||
import socket
|
||||
import ipaddress
|
||||
import platform
|
||||
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
class Adapter(object):
|
||||
"""
|
||||
Represents a network interface device controller (NIC), such as a
|
||||
network card. An adapter can have multiple IPs.
|
||||
|
||||
On Linux aliasing (multiple IPs per physical NIC) is implemented
|
||||
by creating 'virtual' adapters, each represented by an instance
|
||||
of this class. Each of those 'virtual' adapters can have both
|
||||
a IPv4 and an IPv6 IP address.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, nice_name: str, ips: List['IP'], index: Optional[int] = None) -> None:
|
||||
|
||||
#: Unique name that identifies the adapter in the system.
|
||||
#: On Linux this is of the form of `eth0` or `eth0:1`, on
|
||||
#: Windows it is a UUID in string representation, such as
|
||||
#: `{846EE342-7039-11DE-9D20-806E6F6E6963}`.
|
||||
self.name = name
|
||||
|
||||
#: Human readable name of the adpater. On Linux this
|
||||
#: is currently the same as :attr:`name`. On Windows
|
||||
#: this is the name of the device.
|
||||
self.nice_name = nice_name
|
||||
|
||||
#: List of :class:`ifaddr.IP` instances in the order they were
|
||||
#: reported by the system.
|
||||
self.ips = ips
|
||||
|
||||
#: Adapter index as used by some API (e.g. IPv6 multicast group join).
|
||||
self.index = index
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Adapter(name={name}, nice_name={nice_name}, ips={ips}, index={index})".format(
|
||||
name=repr(self.name), nice_name=repr(self.nice_name), ips=repr(self.ips), index=repr(self.index)
|
||||
)
|
||||
|
||||
|
||||
# Type of an IPv4 address (a string in "xxx.xxx.xxx.xxx" format)
|
||||
_IPv4Address = str
|
||||
|
||||
# Type of an IPv6 address (a three-tuple `(ip, flowinfo, scope_id)`)
|
||||
_IPv6Address = Tuple[str, int, int]
|
||||
|
||||
|
||||
class IP(object):
|
||||
"""
|
||||
Represents an IP address of an adapter.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: Union[_IPv4Address, _IPv6Address], network_prefix: int, nice_name: str) -> None:
|
||||
|
||||
#: IP address. For IPv4 addresses this is a string in
|
||||
#: "xxx.xxx.xxx.xxx" format. For IPv6 addresses this
|
||||
#: is a three-tuple `(ip, flowinfo, scope_id)`, where
|
||||
#: `ip` is a string in the usual collon separated
|
||||
#: hex format.
|
||||
self.ip = ip
|
||||
|
||||
#: Number of bits of the IP that represent the
|
||||
#: network. For a `255.255.255.0` netmask, this
|
||||
#: number would be `24`.
|
||||
self.network_prefix = network_prefix
|
||||
|
||||
#: Human readable name for this IP.
|
||||
#: On Linux is this currently the same as the adapter name.
|
||||
#: On Windows this is the name of the network connection
|
||||
#: as configured in the system control panel.
|
||||
self.nice_name = nice_name
|
||||
|
||||
@property
|
||||
def is_IPv4(self) -> bool:
|
||||
"""
|
||||
Returns `True` if this IP is an IPv4 address and `False`
|
||||
if it is an IPv6 address.
|
||||
"""
|
||||
return not isinstance(self.ip, tuple)
|
||||
|
||||
@property
|
||||
def is_IPv6(self) -> bool:
|
||||
"""
|
||||
Returns `True` if this IP is an IPv6 address and `False`
|
||||
if it is an IPv4 address.
|
||||
"""
|
||||
return isinstance(self.ip, tuple)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "IP(ip={ip}, network_prefix={network_prefix}, nice_name={nice_name})".format(
|
||||
ip=repr(self.ip), network_prefix=repr(self.network_prefix), nice_name=repr(self.nice_name)
|
||||
)
|
||||
|
||||
|
||||
if platform.system() == "Darwin" or "BSD" in platform.system():
|
||||
|
||||
# BSD derived systems use marginally different structures
|
||||
# than either Linux or Windows.
|
||||
# I still keep it in `shared` since we can use
|
||||
# both structures equally.
|
||||
|
||||
class sockaddr(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sa_data', ctypes.c_uint8 * 14),
|
||||
]
|
||||
|
||||
class sockaddr_in(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sin_port', ctypes.c_uint16),
|
||||
('sin_addr', ctypes.c_uint8 * 4),
|
||||
('sin_zero', ctypes.c_uint8 * 8),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_len', ctypes.c_uint8),
|
||||
('sa_familiy', ctypes.c_uint8),
|
||||
('sin6_port', ctypes.c_uint16),
|
||||
('sin6_flowinfo', ctypes.c_uint32),
|
||||
('sin6_addr', ctypes.c_uint8 * 16),
|
||||
('sin6_scope_id', ctypes.c_uint32),
|
||||
]
|
||||
|
||||
else:
|
||||
|
||||
class sockaddr(ctypes.Structure): # type: ignore
|
||||
_fields_ = [('sa_familiy', ctypes.c_uint16), ('sa_data', ctypes.c_uint8 * 14)]
|
||||
|
||||
class sockaddr_in(ctypes.Structure): # type: ignore
|
||||
_fields_ = [
|
||||
('sin_familiy', ctypes.c_uint16),
|
||||
('sin_port', ctypes.c_uint16),
|
||||
('sin_addr', ctypes.c_uint8 * 4),
|
||||
('sin_zero', ctypes.c_uint8 * 8),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure): # type: ignore
|
||||
_fields_ = [
|
||||
('sin6_familiy', ctypes.c_uint16),
|
||||
('sin6_port', ctypes.c_uint16),
|
||||
('sin6_flowinfo', ctypes.c_uint32),
|
||||
('sin6_addr', ctypes.c_uint8 * 16),
|
||||
('sin6_scope_id', ctypes.c_uint32),
|
||||
]
|
||||
|
||||
|
||||
def sockaddr_to_ip(sockaddr_ptr: 'ctypes.pointer[sockaddr]') -> Optional[Union[_IPv4Address, _IPv6Address]]:
|
||||
if sockaddr_ptr:
|
||||
if sockaddr_ptr[0].sa_familiy == socket.AF_INET:
|
||||
ipv4 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in))
|
||||
ippacked = bytes(bytearray(ipv4[0].sin_addr))
|
||||
ip = str(ipaddress.ip_address(ippacked))
|
||||
return ip
|
||||
elif sockaddr_ptr[0].sa_familiy == socket.AF_INET6:
|
||||
ipv6 = ctypes.cast(sockaddr_ptr, ctypes.POINTER(sockaddr_in6))
|
||||
flowinfo = ipv6[0].sin6_flowinfo
|
||||
ippacked = bytes(bytearray(ipv6[0].sin6_addr))
|
||||
ip = str(ipaddress.ip_address(ippacked))
|
||||
scope_id = ipv6[0].sin6_scope_id
|
||||
return (ip, flowinfo, scope_id)
|
||||
return None
|
||||
|
||||
|
||||
def ipv6_prefixlength(address: ipaddress.IPv6Address) -> int:
|
||||
prefix_length = 0
|
||||
for i in range(address.max_prefixlen):
|
||||
if int(address) >> i & 1:
|
||||
prefix_length = prefix_length + 1
|
||||
return prefix_length
|
||||
Vendored
+145
@@ -0,0 +1,145 @@
|
||||
# Copyright (c) 2014 Stefan C. Mueller
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
import ctypes
|
||||
from ctypes import wintypes
|
||||
from typing import Iterable, List
|
||||
|
||||
import RNS.vendor.ifaddr._shared as shared
|
||||
|
||||
NO_ERROR = 0
|
||||
ERROR_BUFFER_OVERFLOW = 111
|
||||
MAX_ADAPTER_NAME_LENGTH = 256
|
||||
MAX_ADAPTER_DESCRIPTION_LENGTH = 128
|
||||
MAX_ADAPTER_ADDRESS_LENGTH = 8
|
||||
AF_UNSPEC = 0
|
||||
|
||||
|
||||
class SOCKET_ADDRESS(ctypes.Structure):
|
||||
_fields_ = [('lpSockaddr', ctypes.POINTER(shared.sockaddr)), ('iSockaddrLength', wintypes.INT)]
|
||||
|
||||
|
||||
class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
IP_ADAPTER_UNICAST_ADDRESS._fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('Flags', wintypes.DWORD),
|
||||
('Next', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
|
||||
('Address', SOCKET_ADDRESS),
|
||||
('PrefixOrigin', ctypes.c_uint),
|
||||
('SuffixOrigin', ctypes.c_uint),
|
||||
('DadState', ctypes.c_uint),
|
||||
('ValidLifetime', wintypes.ULONG),
|
||||
('PreferredLifetime', wintypes.ULONG),
|
||||
('LeaseLifetime', wintypes.ULONG),
|
||||
('OnLinkPrefixLength', ctypes.c_uint8),
|
||||
]
|
||||
|
||||
|
||||
class IP_ADAPTER_ADDRESSES(ctypes.Structure):
|
||||
pass
|
||||
|
||||
|
||||
IP_ADAPTER_ADDRESSES._fields_ = [
|
||||
('Length', wintypes.ULONG),
|
||||
('IfIndex', wintypes.DWORD),
|
||||
('Next', ctypes.POINTER(IP_ADAPTER_ADDRESSES)),
|
||||
('AdapterName', ctypes.c_char_p),
|
||||
('FirstUnicastAddress', ctypes.POINTER(IP_ADAPTER_UNICAST_ADDRESS)),
|
||||
('FirstAnycastAddress', ctypes.c_void_p),
|
||||
('FirstMulticastAddress', ctypes.c_void_p),
|
||||
('FirstDnsServerAddress', ctypes.c_void_p),
|
||||
('DnsSuffix', ctypes.c_wchar_p),
|
||||
('Description', ctypes.c_wchar_p),
|
||||
('FriendlyName', ctypes.c_wchar_p),
|
||||
]
|
||||
|
||||
|
||||
iphlpapi = ctypes.windll.LoadLibrary("Iphlpapi") # type: ignore
|
||||
|
||||
|
||||
def enumerate_interfaces_of_adapter(
|
||||
nice_name: str, address: IP_ADAPTER_UNICAST_ADDRESS
|
||||
) -> Iterable[shared.IP]:
|
||||
|
||||
# Iterate through linked list and fill list
|
||||
addresses = [] # type: List[IP_ADAPTER_UNICAST_ADDRESS]
|
||||
while True:
|
||||
addresses.append(address)
|
||||
if not address.Next:
|
||||
break
|
||||
address = address.Next[0]
|
||||
|
||||
for address in addresses:
|
||||
ip = shared.sockaddr_to_ip(address.Address.lpSockaddr)
|
||||
assert ip is not None, f'sockaddr_to_ip({address.Address.lpSockaddr}) returned None'
|
||||
network_prefix = address.OnLinkPrefixLength
|
||||
yield shared.IP(ip, network_prefix, nice_name)
|
||||
|
||||
|
||||
def get_adapters(include_unconfigured: bool = False) -> Iterable[shared.Adapter]:
|
||||
|
||||
# Call GetAdaptersAddresses() with error and buffer size handling
|
||||
|
||||
addressbuffersize = wintypes.ULONG(15 * 1024)
|
||||
retval = ERROR_BUFFER_OVERFLOW
|
||||
while retval == ERROR_BUFFER_OVERFLOW:
|
||||
addressbuffer = ctypes.create_string_buffer(addressbuffersize.value)
|
||||
retval = iphlpapi.GetAdaptersAddresses(
|
||||
wintypes.ULONG(AF_UNSPEC),
|
||||
wintypes.ULONG(0),
|
||||
None,
|
||||
ctypes.byref(addressbuffer),
|
||||
ctypes.byref(addressbuffersize),
|
||||
)
|
||||
if retval != NO_ERROR:
|
||||
raise ctypes.WinError() # type: ignore
|
||||
|
||||
# Iterate through adapters fill array
|
||||
address_infos = [] # type: List[IP_ADAPTER_ADDRESSES]
|
||||
address_info = IP_ADAPTER_ADDRESSES.from_buffer(addressbuffer)
|
||||
while True:
|
||||
address_infos.append(address_info)
|
||||
if not address_info.Next:
|
||||
break
|
||||
address_info = address_info.Next[0]
|
||||
|
||||
# Iterate through unicast addresses
|
||||
result = [] # type: List[shared.Adapter]
|
||||
for adapter_info in address_infos:
|
||||
|
||||
# We don't expect non-ascii characters here, so encoding shouldn't matter
|
||||
name = adapter_info.AdapterName.decode()
|
||||
nice_name = adapter_info.Description
|
||||
index = adapter_info.IfIndex
|
||||
|
||||
if adapter_info.FirstUnicastAddress:
|
||||
ips = enumerate_interfaces_of_adapter(
|
||||
adapter_info.FriendlyName, adapter_info.FirstUnicastAddress[0]
|
||||
)
|
||||
ips = list(ips)
|
||||
result.append(shared.Adapter(name, nice_name, ips, index=index))
|
||||
elif include_unconfigured:
|
||||
result.append(shared.Adapter(name, nice_name, [], index=index))
|
||||
|
||||
return result
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
import ipaddress
|
||||
import RNS.vendor.ifaddr
|
||||
import socket
|
||||
|
||||
from typing import List
|
||||
|
||||
AF_INET6 = socket.AF_INET6.value
|
||||
AF_INET = socket.AF_INET.value
|
||||
|
||||
def interfaces() -> List[str]:
|
||||
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
|
||||
return [a.name for a in adapters]
|
||||
|
||||
def ifaddresses(ifname) -> dict:
|
||||
adapters = RNS.vendor.ifaddr.get_adapters(include_unconfigured=True)
|
||||
ifa = {}
|
||||
for a in adapters:
|
||||
if a.name == ifname:
|
||||
ipv4s = []
|
||||
ipv6s = []
|
||||
for ip in a.ips:
|
||||
t = {}
|
||||
if ip.is_IPv4:
|
||||
net = ipaddress.ip_network(str(ip.ip)+"/"+str(ip.network_prefix), strict=False)
|
||||
t["addr"] = ip.ip
|
||||
t["prefix"] = ip.network_prefix
|
||||
t["broadcast"] = str(net.broadcast_address)
|
||||
ipv4s.append(t)
|
||||
if ip.is_IPv6:
|
||||
t["addr"] = ip.ip[0]
|
||||
ipv6s.append(t)
|
||||
|
||||
if len(ipv4s) > 0:
|
||||
ifa[AF_INET] = ipv4s
|
||||
if len(ipv6s) > 0:
|
||||
ifa[AF_INET6] = ipv6s
|
||||
|
||||
return ifa
|
||||
Vendored
+7
-30
@@ -14,24 +14,12 @@ This document outlines the currently established development roadmap for Reticul
|
||||
## Currently Active Work Areas
|
||||
For each release cycle of Reticulum, improvements and additions from the five [Primary Efforts](#primary-efforts) are selected as active work areas, and can be expected to be included in the upcoming releases within that cycle. While not entirely set in stone for each release cycle, they serve as a pointer of what to expect in the near future.
|
||||
|
||||
- The current `0.4.x` release cycle aims at completing:
|
||||
- [x] Improve storage persist call on local client connect/disconnect
|
||||
- [x] Better path invalidation on roaming interfaces
|
||||
- [x] Improved roaming support on Android
|
||||
- [ ] Updating the documentation to reflect recent changes and improvements
|
||||
- [ ] Add bluetooth pairing code output to rnodeconf
|
||||
- [ ] Transit traffic display in rnstatus
|
||||
- [ ] JSON output mode for rnstatus
|
||||
- [ ] Add `rnid` utility
|
||||
- [ ] Add `rnsign` utility
|
||||
- [ ] Add `rncrypt` utility
|
||||
- [ ] Create a standalone RNS Daemon app for Android
|
||||
- Targets for related applications
|
||||
- [x] Add offline & paper message transport to LXMF
|
||||
- [x] Implement paper messaging in Nomad Network
|
||||
- [x] Implement paper messaging in Sideband
|
||||
- [x] Add spatial and multi-interface roaming support in Sideband
|
||||
- [x] Expand device support in Sideband to support older Android devices
|
||||
- The current `0.5.x` release cycle aims at completing
|
||||
- [ ] Overhauling and updating the documentation
|
||||
- [ ] Performance and memory optimisations of the Python reference implementation
|
||||
- [ ] Fixing potential bugs
|
||||
- [ ] Add automatic retries to all use cases of the `Request` API
|
||||
- [ ] Improve performance and efficiency of the `Buffer` and `Channel` API
|
||||
|
||||
## Primary Efforts
|
||||
The development path for Reticulum is currently laid out in five distinct areas: *Comprehensibility*, *Universality*, *Functionality*, *Usability & Utility* and *Interfaceability*. Conceptualising the development of Reticulum into these areas serves to advance the implementation and work towards the Foundational Goals & Values of Reticulum.
|
||||
@@ -50,7 +38,6 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
|
||||
- Update NomadNet screenshots
|
||||
- Update Sideband screenshots
|
||||
- Installation
|
||||
- Install docs for fedora, needs `python3-netifaces`
|
||||
- Add a *Reticulum On Raspberry Pi* section
|
||||
- Update *Reticulum On Android* section if necessary
|
||||
- Update Android install documentation.
|
||||
@@ -66,7 +53,6 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
|
||||
### Universality
|
||||
These efforts seek to broaden the universality of the Reticulum software and hardware ecosystem by continously diversifying platform support, and by improving the overall availability and ease of deployment of the Reticulum stack.
|
||||
|
||||
- Improved roaming support on Android
|
||||
- OpenWRT support
|
||||
- Create a standalone RNS Daemon app for Android
|
||||
- A lightweight and portable C implementation for microcontrollers, µRNS
|
||||
@@ -77,10 +63,7 @@ These efforts seek to broaden the universality of the Reticulum software and har
|
||||
### Functionality
|
||||
These efforts aim to expand and improve the core functionality and reliability of Reticulum.
|
||||
|
||||
- Improve storage persist call on local client connect/disconnect
|
||||
- Faster path invalidation on physical topography changes
|
||||
- Better path invalidation on roaming interfaces
|
||||
- Add a `Buffer` class to the API, for handling stream-like buffers over Reticulum
|
||||
- Add automatic retries to all use cases of the `Request` API
|
||||
- Network-wide path balancing
|
||||
- Distributed Destination Naming System
|
||||
- Globally routable multicast
|
||||
@@ -93,12 +76,7 @@ These effors seek to make Reticulum easier to use and operate, and to expand the
|
||||
- Add bluetooth pairing code output to rnodeconf
|
||||
- Easy way to share interface configurations, see [#19](https://github.com/markqvist/Reticulum/discussions/19)
|
||||
- Transit traffic display in rnstatus
|
||||
- JSON output mode for rnstatus
|
||||
- rnid utility
|
||||
- rnsign utility
|
||||
- rncrypt utility
|
||||
- rnsconfig utility
|
||||
- Expand rnx utility to true interactive remote shell
|
||||
|
||||
### Interfaceability
|
||||
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
|
||||
@@ -126,7 +104,6 @@ The Reticulum ecosystem is enriched by several other software and hardware proje
|
||||
This section lists, in no particular order, various important efforts that would be beneficial to the goals of Reticulum.
|
||||
|
||||
- The [RNode](https://unsigned.io/rnode/) project
|
||||
- [ ] Evolve RNode into a self-replicating system, so that anyone can use an existing RNode to create more RNodes, and bootstrap functional networks based on Reticulum, even in absence of the Internet.
|
||||
- [ ] Create a WebUSB-based bootstrapping utility, and integrate this directly into the [RNode Bootstrap Console](#), both on-device, and on an Internet-reachable copy. This will make it much easier to create new RNodes for average users.
|
||||
|
||||
## Release History
|
||||
|
||||
@@ -30,3 +30,8 @@ help:
|
||||
cp -r build/latex/reticulumnetworkstack.pdf ./Reticulum\ Manual.pdf; \
|
||||
echo "PDF Manual Generated"; \
|
||||
fi
|
||||
|
||||
@if [ $@ = "epub" ]; then \
|
||||
cp -r build/epub/ReticulumNetworkStack.epub ./Reticulum\ Manual.epub; \
|
||||
echo "EPUB Manual Generated"; \
|
||||
fi
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -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: afc56b8a3660cd0f3eeb6ca077eaba56
|
||||
config: ab776c37991b695fba62f8d653f98b5e
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -92,6 +92,28 @@ The *Request* example explores sendig requests and receiving responses.
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py>`_.
|
||||
|
||||
.. _example-channel:
|
||||
|
||||
Channel
|
||||
=======
|
||||
|
||||
The *Channel* example explores using a ``Channel`` to send structured
|
||||
data between peers of a ``Link``.
|
||||
|
||||
.. literalinclude:: ../../Examples/Channel.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py>`_.
|
||||
|
||||
Buffer
|
||||
======
|
||||
|
||||
The *Buffer* example explores using buffered readers and writers to send
|
||||
binary data between peers of a ``Link``.
|
||||
|
||||
.. literalinclude:: ../../Examples/Buffer.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py>`_.
|
||||
|
||||
.. _example-filetransfer:
|
||||
|
||||
Filetransfer
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
********************************************
|
||||
An Explanation of Reticulum for Human Beings
|
||||
********************************************
|
||||
|
||||
@@ -10,22 +10,45 @@ scenarios.
|
||||
Standalone Reticulum Installation
|
||||
=============================================
|
||||
If you simply want to install Reticulum and related utilities on a system,
|
||||
the easiest way is via ``pip``:
|
||||
the easiest way is via the ``pip`` package manager:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install rns
|
||||
|
||||
If you no not already have pip installed, you can install it using the package manager
|
||||
If you do not already have pip installed, you can install it using the package manager
|
||||
of your system with a command like ``sudo apt install python3-pip``,
|
||||
``sudo pamac install python-pip`` or similar. You can also dowload the Reticulum release
|
||||
wheels from GitHub, or other release channels, and install them offline using ``pip``:
|
||||
``sudo pamac install python-pip`` or similar.
|
||||
|
||||
You can also dowload the Reticulum release wheels from GitHub, or other release channels,
|
||||
and install them offline using ``pip``:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install ./rns-0.4.6-py3-none-any.whl
|
||||
pip install ./rns-0.5.1-py3-none-any.whl
|
||||
|
||||
|
||||
Resolving Dependency & Installation Issues
|
||||
=============================================
|
||||
On some platforms, there may not be binary packages available for all dependencies, and
|
||||
``pip`` installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:
|
||||
|
||||
.. code::
|
||||
|
||||
# Debian / Ubuntu / Derivatives
|
||||
sudo apt install build-essential
|
||||
|
||||
# Arch / Manjaro / Derivatives
|
||||
sudo pamac install base-devel
|
||||
|
||||
# Fedora
|
||||
sudo dnf groupinstall "Development Tools" "Development Libraries"
|
||||
|
||||
With the base development packages installed, ``pip`` should be able to compile any missing
|
||||
dependencies from source, and complete installation even on platforms that don't have pre-
|
||||
compiled packages available.
|
||||
|
||||
Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
|
||||
@@ -42,6 +65,13 @@ transceivers or infrastructure just to try it out. Launching the programs on sep
|
||||
devices connected to the same WiFi network is enough to get started, and physical
|
||||
radio interfaces can then be added later.
|
||||
|
||||
Remote Shell
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The `rnsh <https://github.com/acehoss/rnsh>`_ program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how ``ssh`` works.
|
||||
|
||||
Nomad Network
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
@@ -115,8 +145,8 @@ Creating a Network With Reticulum
|
||||
=============================================
|
||||
To create a network, you will need to specify one or more *interfaces* for
|
||||
Reticulum to use. This is done in the Reticulum configuration file, which by
|
||||
default is located at ``~/.reticulum/config``. You can edit this file by hand,
|
||||
or use the interactive ``rnsconfig`` utility.
|
||||
default is located at ``~/.reticulum/config``. You can get an example
|
||||
configuration file with all options via ``rnsd --exampleconfig``.
|
||||
|
||||
When Reticulum is started for the first time, it will create a default
|
||||
configuration file, with one active interface. This default interface uses
|
||||
@@ -197,7 +227,7 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
|
||||
target_port = 4965
|
||||
|
||||
# TCP/IP interface to the Frankfurt hub
|
||||
[[RNS Testnet Dublin]]
|
||||
[[RNS Testnet Frankfurt]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = frankfurt.connect.reticulum.network
|
||||
@@ -234,7 +264,7 @@ chapter for a guide. If you prefer purchasing a ready-made unit, you can refer t
|
||||
refer to these additional external resources:
|
||||
|
||||
* `How To Make Your Own RNodes <https://unsigned.io/how-to-make-your-own-rnodes/>`_
|
||||
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/>`_
|
||||
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-supported-devices/>`_
|
||||
* `Private, Secure and Uncensorable Messaging Over a LoRa Mesh <https://unsigned.io/private-messaging-over-lora/>`_
|
||||
* `RNode Firmware <https://github.com/markqvist/RNode_Firmware/>`_
|
||||
|
||||
@@ -261,7 +291,7 @@ For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install pyserial netifaces
|
||||
pip3 install pyserial
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
@@ -276,7 +306,7 @@ don't use pip, but try this recipe:
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
pip3 install cryptography pyserial netifaces
|
||||
pip3 install cryptography pyserial
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/markqvist/Reticulum.git
|
||||
@@ -313,30 +343,14 @@ the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussi
|
||||
or ask one of the developers or maintainers for a good place to start.
|
||||
|
||||
|
||||
Reticulum on ARM64
|
||||
Platform-Specific Install Notes
|
||||
==============================================
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Reticulum on Raspberry Pi
|
||||
==============================================
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
Reticulum on Android
|
||||
==============================================
|
||||
Android
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using an app like `Sideband <https://unsigned.io/sideband>`_.
|
||||
|
||||
@@ -402,6 +416,74 @@ It is also possible to include Reticulum in apps compiled and distributed as
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and startig point.
|
||||
|
||||
|
||||
ARM64
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.
|
||||
|
||||
|
||||
Debian Bookworm
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Debian released after April 2023, it is no longer possible
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
|
||||
Ubuntu Lunar
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Ubuntu released after April 2023, it is no longer possible
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
|
||||
Pure-Python Reticulum
|
||||
==============================================
|
||||
In some rare cases, and on more obscure system types, it is not possible to
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
******************************
|
||||
Reticulum Network Stack Manual
|
||||
******************************
|
||||
|
||||
This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.
|
||||
|
||||
.. only:: html
|
||||
.. only:: builder_html
|
||||
|
||||
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
|
||||
|
||||
.. only:: builder_html
|
||||
|
||||
Table Of Contents
|
||||
=================
|
||||
|
||||
@@ -121,6 +121,76 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
|
||||
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
|
||||
:members:
|
||||
|
||||
.. _api-channel:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| Channel |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
Channel
|
||||
-------
|
||||
|
||||
.. autoclass:: RNS.Channel.Channel()
|
||||
:members:
|
||||
|
||||
.. _api-messsagebase:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| MessageBase |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
MessageBase
|
||||
-----------
|
||||
|
||||
.. autoclass:: RNS.MessageBase()
|
||||
:members:
|
||||
|
||||
.. _api-buffer:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| Buffer |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
Buffer
|
||||
------
|
||||
|
||||
.. autoclass:: RNS.Buffer
|
||||
:members:
|
||||
|
||||
.. _api-rawchannelreader:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| RawChannelReader |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
RawChannelReader
|
||||
----------------
|
||||
|
||||
.. autoclass:: RNS.RawChannelReader
|
||||
:members: __init__, add_ready_callback, remove_ready_callback
|
||||
|
||||
.. _api-rawchannelwriter:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| RawChannelWriter |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
RawChannelWriter
|
||||
----------------
|
||||
|
||||
.. autoclass:: RNS.RawChannelWriter
|
||||
:members: __init__
|
||||
|
||||
.. _api-transport:
|
||||
|
||||
.. only:: html
|
||||
|
||||
@@ -107,13 +107,13 @@ guide the design of Reticulum:
|
||||
Introduction & Basic Functionality
|
||||
==================================
|
||||
|
||||
Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at it’s
|
||||
Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at its
|
||||
core a *message oriented* system. It is suited for both local point-to-point or point-to-multipoint
|
||||
scenarios where all nodes are within range of each other, as well as scenarios where packets need
|
||||
to be transported over multiple hops in a complex network to reach the recipient.
|
||||
|
||||
Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
|
||||
Reticulum uses the singular concept of *destinations*. Any application using Reticulum as it’s
|
||||
Reticulum uses the singular concept of *destinations*. Any application using Reticulum as its
|
||||
networking stack will need to create one or more destinations to receive data, and know the
|
||||
destinations it needs to send data to.
|
||||
|
||||
@@ -220,7 +220,7 @@ packet.
|
||||
|
||||
In actual use of *single* destination naming, it is advisable not to use any uniquely identifying
|
||||
features in aspect naming. Aspect names should be general terms describing what kind of destination
|
||||
is represented. The uniquely identifying aspect is always achieved by the appending the public key,
|
||||
is represented. The uniquely identifying aspect is always achieved by appending the public key,
|
||||
which expands the destination into a uniquely identifiable one. Reticulum does this automatically.
|
||||
|
||||
Any destination on a Reticulum network can be addressed and reached simply by knowing its
|
||||
@@ -239,7 +239,7 @@ To recap, the different destination types should be used in the following situat
|
||||
* **Plain**
|
||||
When plain-text communication is desirable, for example when broadcasting information, or for local discovery purposes.
|
||||
|
||||
To communicate with a *single* destination, you need to know it’s public key. Any method for
|
||||
To communicate with a *single* destination, you need to know its public key. Any method for
|
||||
obtaining the public key is valid, but Reticulum includes a simple mechanism for making other
|
||||
nodes aware of your destinations public key, called the *announce*. It is also possible to request
|
||||
an unknown public key from the network, as all transport instances serve as a distributed ledger
|
||||
@@ -287,7 +287,7 @@ In Reticulum, destinations are allowed to move around the network at will. This
|
||||
protocols such as IP, where an address is always expected to stay within the network segment it was assigned in.
|
||||
This limitation does not exist in Reticulum, and any destination is *completely portable* over the entire topography
|
||||
of the network, and *can even be moved to other Reticulum networks* than the one it was created in, and
|
||||
still become reachable. To update it's reachability, a destination simply needs to send an announce on any
|
||||
still become reachable. To update its reachability, a destination simply needs to send an announce on any
|
||||
networks it is part of. After a short while, it will be globally reachable in the network.
|
||||
|
||||
Seeing how *single* destinations are always tied to a private/public key pair leads us to the next topic.
|
||||
@@ -368,7 +368,7 @@ If it is a *Transport Node*, it should be given the configuration directive ``en
|
||||
The Announce Mechanism in Detail
|
||||
--------------------------------
|
||||
|
||||
When an *announce* for a destination is transmitted by from a Reticulum instance, it will be forwarded by
|
||||
When an *announce* for a destination is transmitted by a Reticulum instance, it will be forwarded by
|
||||
any transport node receiving it, but according to some specific rules:
|
||||
|
||||
|
||||
@@ -385,7 +385,7 @@ any transport node receiving it, but according to some specific rules:
|
||||
announces is set at 2%, but can be configured on a per-interface basis.
|
||||
|
||||
* | If any given interface does not have enough bandwidth available for retransmitting the announce,
|
||||
the announce will be assigned a priority inversely proportional to it's hop count, and be inserted
|
||||
the announce will be assigned a priority inversely proportional to its hop count, and be inserted
|
||||
into a queue managed by the interface.
|
||||
|
||||
* | When the interface has bandwidth available for processing an announce, it will prioritise announces
|
||||
@@ -431,7 +431,7 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
|
||||
|
||||
* | A packet is always created with an associated destination and some payload data. When the packet is sent
|
||||
to a *single* destination type, Reticulum will automatically create an ephemeral encryption key, perform
|
||||
an ECDH key exchange with the destinations public key, and encrypt the information.
|
||||
an ECDH key exchange with the destination's public key, and encrypt the information.
|
||||
|
||||
* | It is important to note that this key exchange does not require any network traffic. The sender already
|
||||
knows the public key of the destination from an earlier received *announce*, and can thus perform the ECDH
|
||||
@@ -447,8 +447,8 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
|
||||
|
||||
* | Once the packet has been received and decrypted by the addressed destination, that destination can opt
|
||||
to *prove* its receipt of the packet. It does this by calculating the SHA-256 hash of the received packet,
|
||||
and signing this hash with it's Ed25519 signing key. Transport nodes in the network can then direct this
|
||||
*proof* back to the packets origin, where the signature can be verified against the destinations known
|
||||
and signing this hash with its Ed25519 signing key. Transport nodes in the network can then direct this
|
||||
*proof* back to the packets origin, where the signature can be verified against the destination's known
|
||||
public signing key.
|
||||
|
||||
* | In case the packet is addressed to a *group* destination type, the packet will be encrypted with the
|
||||
@@ -465,7 +465,7 @@ For exchanges of larger amounts of data, or when longer sessions of bidirectiona
|
||||
forward the packet will take note of this *link request*.
|
||||
|
||||
* | Second, if the destination accepts the *link request* , it will send back a packet that proves the
|
||||
authenticity of it’s identity (and the receipt of the link request) to the initiating node. All
|
||||
authenticity of its identity (and the receipt of the link request) to the initiating node. All
|
||||
nodes that initially forwarded the packet will also be able to verify this proof, and thus
|
||||
accept the validity of the *link* throughout the network.
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ output.
|
||||
|
||||
# Run rnx on the listening system, specifying which identities
|
||||
# are allowed to execute commands
|
||||
rncp --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
rnx --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
|
||||
# From another system, run a command
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 "cat /proc/cpuinfo"
|
||||
@@ -565,4 +565,4 @@ If you want to automatically start ``rnsd`` at boot, run:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo systemctl enable rnsd
|
||||
sudo systemctl enable rnsd
|
||||
|
||||
@@ -162,7 +162,7 @@ Caveat Emptor
|
||||
==============
|
||||
Reticulum is an experimental networking stack, and should be considered as
|
||||
such. While it has been built with cryptography best-practices very foremost in
|
||||
mind, it has not been externally security audited, and there could very well be
|
||||
mind, it has not yet been externally security audited, and there could very well be
|
||||
privacy-breaking bugs. To be considered secure, Reticulum needs a thorough
|
||||
security review by independent cryptographers and security researchers. If you
|
||||
want to help out, or help sponsor an audit, please do get in touch.
|
||||
want to help out with this, or can help sponsor an audit, please do get in touch.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.4.7 beta',
|
||||
VERSION: '0.5.1 beta',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
+738
-6
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Support Reticulum" href="support.html" /><link rel="prev" title="Building Networks" href="networks.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Code Examples - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -861,6 +861,7 @@ the Packet interface.</p>
|
||||
<span class="c1"># If we do not know this destination, tell the</span>
|
||||
<span class="c1"># user to wait for an announce to arrive.</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Destination is not yet known. Requesting path..."</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Hit enter to manually retry once an announce is received."</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># This function is called when our reply destination</span>
|
||||
@@ -1632,8 +1633,8 @@ the link has been established.</p>
|
||||
<span class="c1"># A reference to the latest client link that connected</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">random_text_generator</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">request_id</span><span class="p">,</span> <span class="n">remote_identity</span><span class="p">,</span> <span class="n">requested_at</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Generating response to request "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_id</span><span class="p">))</span>
|
||||
<span class="k">def</span> <span class="nf">random_text_generator</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">request_id</span><span class="p">,</span> <span class="n">link_id</span><span class="p">,</span> <span class="n">remote_identity</span><span class="p">,</span> <span class="n">requested_at</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Generating response to request "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">request_id</span><span class="p">)</span><span class="o">+</span><span class="s2">" on link "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">link_id</span><span class="p">))</span>
|
||||
<span class="n">texts</span> <span class="o">=</span> <span class="p">[</span><span class="s2">"They looked up"</span><span class="p">,</span> <span class="s2">"On each full moon"</span><span class="p">,</span> <span class="s2">"Becky was upset"</span><span class="p">,</span> <span class="s2">"I’ll stay away from it"</span><span class="p">,</span> <span class="s2">"The pet shop stocks everything"</span><span class="p">]</span>
|
||||
<span class="k">return</span> <span class="n">texts</span><span class="p">[</span><span class="n">random</span><span class="o">.</span><span class="n">randint</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">texts</span><span class="p">)</span><span class="o">-</span><span class="mi">1</span><span class="p">)]</span>
|
||||
|
||||
@@ -1898,6 +1899,735 @@ the link has been established.</p>
|
||||
</div>
|
||||
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py</a>.</p>
|
||||
</section>
|
||||
<section id="channel">
|
||||
<span id="example-channel"></span><h2>Channel<a class="headerlink" href="#channel" title="Permalink to this heading">#</a></h2>
|
||||
<p>The <em>Channel</em> example explores using a <code class="docutils literal notranslate"><span class="pre">Channel</span></code> to send structured
|
||||
data between peers of a <code class="docutils literal notranslate"><span class="pre">Link</span></code>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1">##########################################################</span>
|
||||
<span class="c1"># This RNS example demonstrates how to set up a link to #</span>
|
||||
<span class="c1"># a destination, and pass structured messages over it #</span>
|
||||
<span class="c1"># using a channel. #</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">os</span>
|
||||
<span class="kn">import</span> <span class="nn">sys</span>
|
||||
<span class="kn">import</span> <span class="nn">time</span>
|
||||
<span class="kn">import</span> <span class="nn">argparse</span>
|
||||
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">RNS</span>
|
||||
<span class="kn">from</span> <span class="nn">RNS.vendor</span> <span class="kn">import</span> <span class="n">umsgpack</span>
|
||||
|
||||
<span class="c1"># Let's define an app name. We'll use this for all</span>
|
||||
<span class="c1"># destinations we create. Since this echo example</span>
|
||||
<span class="c1"># is part of a range of example utilities, we'll put</span>
|
||||
<span class="c1"># them all within the app namespace "example_utilities"</span>
|
||||
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">"example_utilities"</span>
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Shared Objects ######################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># Channel data must be structured in a subclass of</span>
|
||||
<span class="c1"># MessageBase. This ensures that the channel will be able</span>
|
||||
<span class="c1"># to serialize and deserialize the object and multiplex it</span>
|
||||
<span class="c1"># with other objects. Both ends of a link will need the</span>
|
||||
<span class="c1"># same object definitions to be able to communicate over</span>
|
||||
<span class="c1"># a channel.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># Note: The objects we wish to use over the channel must</span>
|
||||
<span class="c1"># be registered with the channel, and each link has a</span>
|
||||
<span class="c1"># different channel instance. See the client_connected</span>
|
||||
<span class="c1"># and link_established functions in this example to see</span>
|
||||
<span class="c1"># how message types are registered.</span>
|
||||
|
||||
<span class="c1"># Let's make a simple message class called StringMessage</span>
|
||||
<span class="c1"># that will convey a string with a timestamp.</span>
|
||||
|
||||
<span class="k">class</span> <span class="nc">StringMessage</span><span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">MessageBase</span><span class="p">):</span>
|
||||
<span class="c1"># The MSGTYPE class variable needs to be assigned a</span>
|
||||
<span class="c1"># 2 byte integer value. This identifier allows the</span>
|
||||
<span class="c1"># channel to look up your message's constructor when a</span>
|
||||
<span class="c1"># message arrives over the channel.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># MSGTYPE must be unique across all message types we</span>
|
||||
<span class="c1"># register with the channel. MSGTYPEs >= 0xf000 are</span>
|
||||
<span class="c1"># reserved for the system.</span>
|
||||
<span class="n">MSGTYPE</span> <span class="o">=</span> <span class="mh">0x0101</span>
|
||||
|
||||
<span class="c1"># The constructor of our object must be callable with</span>
|
||||
<span class="c1"># no arguments. We can have parameters, but they must</span>
|
||||
<span class="c1"># have a default assignment.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># This is needed so the channel can create an empty</span>
|
||||
<span class="c1"># version of our message into which the incoming</span>
|
||||
<span class="c1"># message can be unpacked.</span>
|
||||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="n">data</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span> <span class="o">=</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># Finally, our message needs to implement functions</span>
|
||||
<span class="c1"># the channel can call to pack and unpack our message</span>
|
||||
<span class="c1"># to/from the raw packet payload. We'll use the</span>
|
||||
<span class="c1"># umsgpack package bundled with RNS. We could also use</span>
|
||||
<span class="c1"># the struct package bundled with Python if we wanted</span>
|
||||
<span class="c1"># more control over the structure of the packed bytes.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># Also note that packed message objects must fit</span>
|
||||
<span class="c1"># entirely in one packet. The number of bytes</span>
|
||||
<span class="c1"># available for message payloads can be queried from</span>
|
||||
<span class="c1"># the channel using the Channel.MDU property. The</span>
|
||||
<span class="c1"># channel MDU is slightly less than the link MDU due</span>
|
||||
<span class="c1"># to encoding the message header.</span>
|
||||
|
||||
<span class="c1"># The pack function encodes the message contents into</span>
|
||||
<span class="c1"># a byte stream.</span>
|
||||
<span class="k">def</span> <span class="nf">pack</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">bytes</span><span class="p">:</span>
|
||||
<span class="k">return</span> <span class="n">umsgpack</span><span class="o">.</span><span class="n">packb</span><span class="p">((</span><span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># And the unpack function decodes a byte stream into</span>
|
||||
<span class="c1"># the message contents.</span>
|
||||
<span class="k">def</span> <span class="nf">unpack</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">raw</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">timestamp</span> <span class="o">=</span> <span class="n">umsgpack</span><span class="o">.</span><span class="n">unpackb</span><span class="p">(</span><span class="n">raw</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Server Part #########################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># A reference to the latest client link that connected</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a server</span>
|
||||
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Randomly create a new identity for our link example</span>
|
||||
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We create a destination that clients can connect to. We</span>
|
||||
<span class="c1"># want clients to create links to this destination, so we</span>
|
||||
<span class="c1"># need to create a "single" destination type.</span>
|
||||
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">server_identity</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">IN</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
|
||||
<span class="n">APP_NAME</span><span class="p">,</span>
|
||||
<span class="s2">"channelexample"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># We configure a function that will get called every time</span>
|
||||
<span class="c1"># a new client creates a link to this destination.</span>
|
||||
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Everything's ready!</span>
|
||||
<span class="c1"># Let's Wait for client requests or user input</span>
|
||||
<span class="n">server_loop</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_loop</span><span class="p">(</span><span class="n">destination</span><span class="p">):</span>
|
||||
<span class="c1"># Let the user know that everything is ready</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
|
||||
<span class="s2">"Link example "</span><span class="o">+</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
|
||||
<span class="s2">" running, waiting for a connection."</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Hit enter to manually send an announce (Ctrl-C to quit)"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># We enter a loop that runs until the users exits.</span>
|
||||
<span class="c1"># If the user hits enter, we will announce our server</span>
|
||||
<span class="c1"># destination on the network, which will let clients</span>
|
||||
<span class="c1"># know how to create messages directed towards it.</span>
|
||||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||||
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
|
||||
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Sent announce from "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># When a client establishes a link to our server</span>
|
||||
<span class="c1"># destination, this function will be called with</span>
|
||||
<span class="c1"># a reference to the link.</span>
|
||||
<span class="k">def</span> <span class="nf">client_connected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">latest_client_link</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="n">link</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Client connected"</span><span class="p">)</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Register message types and add callback to channel</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">register_message_type</span><span class="p">(</span><span class="n">StringMessage</span><span class="p">)</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">add_message_handler</span><span class="p">(</span><span class="n">server_message_received</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Client disconnected"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_message_received</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
|
||||
<span class="sd">"""</span>
|
||||
<span class="sd"> A message handler</span>
|
||||
<span class="sd"> @param message: An instance of a subclass of MessageBase</span>
|
||||
<span class="sd"> @return: True if message was handled</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">global</span> <span class="n">latest_client_link</span>
|
||||
<span class="c1"># When a message is received over any active link,</span>
|
||||
<span class="c1"># the replies will all be directed to the last client</span>
|
||||
<span class="c1"># that connected.</span>
|
||||
|
||||
<span class="c1"># In a message handler, any deserializable message</span>
|
||||
<span class="c1"># that arrives over the link's channel will be passed</span>
|
||||
<span class="c1"># to all message handlers, unless a preceding handler indicates it</span>
|
||||
<span class="c1"># has handled the message.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">StringMessage</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received data on the link: "</span> <span class="o">+</span> <span class="n">message</span><span class="o">.</span><span class="n">data</span> <span class="o">+</span> <span class="s2">" (message created at "</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">timestamp</span><span class="p">)</span> <span class="o">+</span> <span class="s2">")"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">reply_message</span> <span class="o">=</span> <span class="n">StringMessage</span><span class="p">(</span><span class="s2">"I received </span><span class="se">\"</span><span class="s2">"</span><span class="o">+</span><span class="n">message</span><span class="o">.</span><span class="n">data</span><span class="o">+</span><span class="s2">"</span><span class="se">\"</span><span class="s2"> over the link"</span><span class="p">)</span>
|
||||
<span class="n">latest_client_link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">reply_message</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Incoming messages are sent to each message</span>
|
||||
<span class="c1"># handler added to the channel, in the order they</span>
|
||||
<span class="c1"># were added.</span>
|
||||
<span class="c1"># If any message handler returns True, the message</span>
|
||||
<span class="c1"># is considered handled and any subsequent</span>
|
||||
<span class="c1"># handlers are skipped.</span>
|
||||
<span class="k">return</span> <span class="kc">True</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Client Part #########################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># A reference to the server link</span>
|
||||
<span class="n">server_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a client</span>
|
||||
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="c1"># We need a binary representation of the destination</span>
|
||||
<span class="c1"># hash that was entered on the command line</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
|
||||
<span class="s2">"Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes)."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
|
||||
<span class="k">except</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">exit</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Check if we know a path to the destination</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Destination is not yet known. Requesting path and waiting for announce to arrive..."</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Recall the server identity</span>
|
||||
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">recall</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Inform the user that we'll begin connecting</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Establishing link with server..."</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When the server identity is known, we set</span>
|
||||
<span class="c1"># up a destination</span>
|
||||
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">server_identity</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">OUT</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
|
||||
<span class="n">APP_NAME</span><span class="p">,</span>
|
||||
<span class="s2">"channelexample"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># And create a link</span>
|
||||
<span class="n">link</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># We'll also set up functions to inform the</span>
|
||||
<span class="c1"># user when the link is established or closed</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Everything is set up, so let's enter a loop</span>
|
||||
<span class="c1"># for the user to interact with the example</span>
|
||||
<span class="n">client_loop</span><span class="p">()</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">client_loop</span><span class="p">():</span>
|
||||
<span class="k">global</span> <span class="n">server_link</span>
|
||||
|
||||
<span class="c1"># Wait for the link to become active</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">server_link</span><span class="p">:</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">should_quit</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"> "</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">" "</span><span class="p">)</span>
|
||||
<span class="n">text</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># Check if we should quit the example</span>
|
||||
<span class="k">if</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"quit"</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"q"</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"exit"</span><span class="p">:</span>
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># If not, send the entered text over the link</span>
|
||||
<span class="k">if</span> <span class="n">text</span> <span class="o">!=</span> <span class="s2">""</span><span class="p">:</span>
|
||||
<span class="n">message</span> <span class="o">=</span> <span class="n">StringMessage</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
|
||||
<span class="n">packed_size</span> <span class="o">=</span> <span class="nb">len</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">pack</span><span class="p">())</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">server_link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="k">if</span> <span class="n">channel</span><span class="o">.</span><span class="n">is_ready_to_send</span><span class="p">():</span>
|
||||
<span class="k">if</span> <span class="n">packed_size</span> <span class="o"><=</span> <span class="n">channel</span><span class="o">.</span><span class="n">MDU</span><span class="p">:</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
|
||||
<span class="s2">"Cannot send this packet, the data size of "</span><span class="o">+</span>
|
||||
<span class="nb">str</span><span class="p">(</span><span class="n">packed_size</span><span class="p">)</span><span class="o">+</span><span class="s2">" bytes exceeds the link packet MDU of "</span><span class="o">+</span>
|
||||
<span class="nb">str</span><span class="p">(</span><span class="n">channel</span><span class="o">.</span><span class="n">MDU</span><span class="p">)</span><span class="o">+</span><span class="s2">" bytes"</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Channel is not ready to send, please wait for "</span> <span class="o">+</span>
|
||||
<span class="s2">"pending messages to complete."</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
|
||||
|
||||
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Error while sending data over the link: "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># This function is called when a link</span>
|
||||
<span class="c1"># has been established with the server</span>
|
||||
<span class="k">def</span> <span class="nf">link_established</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="c1"># We store a reference to the link</span>
|
||||
<span class="c1"># instance for later use</span>
|
||||
<span class="k">global</span> <span class="n">server_link</span>
|
||||
<span class="n">server_link</span> <span class="o">=</span> <span class="n">link</span>
|
||||
|
||||
<span class="c1"># Register messages and add handler to channel</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">register_message_type</span><span class="p">(</span><span class="n">StringMessage</span><span class="p">)</span>
|
||||
<span class="n">channel</span><span class="o">.</span><span class="n">add_message_handler</span><span class="p">(</span><span class="n">client_message_received</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Inform the user that the server is</span>
|
||||
<span class="c1"># connected</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Link established with server, enter some text to send, or </span><span class="se">\"</span><span class="s2">quit</span><span class="se">\"</span><span class="s2"> to quit"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When a link is closed, we'll inform the</span>
|
||||
<span class="c1"># user, and exit the program</span>
|
||||
<span class="k">def</span> <span class="nf">link_closed</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The link timed out, exiting now"</span><span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">DESTINATION_CLOSED</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The link was closed by the server, exiting now"</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Link closed, exiting now"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When a packet is received over the channel, we</span>
|
||||
<span class="c1"># simply print out the data.</span>
|
||||
<span class="k">def</span> <span class="nf">client_message_received</span><span class="p">(</span><span class="n">message</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="n">StringMessage</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received data on the link: "</span> <span class="o">+</span> <span class="n">message</span><span class="o">.</span><span class="n">data</span> <span class="o">+</span> <span class="s2">" (message created at "</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">message</span><span class="o">.</span><span class="n">timestamp</span><span class="p">)</span> <span class="o">+</span> <span class="s2">")"</span><span class="p">)</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"> "</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">" "</span><span class="p">)</span>
|
||||
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Program Startup #####################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># This part of the program runs at startup,</span>
|
||||
<span class="c1"># and parses input of from the user, and then</span>
|
||||
<span class="c1"># starts up the desired program mode.</span>
|
||||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">"Simple channel example"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"-s"</span><span class="p">,</span>
|
||||
<span class="s2">"--server"</span><span class="p">,</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="s2">"store_true"</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"wait for incoming link requests from clients"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"--config"</span><span class="p">,</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="s2">"store"</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"path to alternative Reticulum config directory"</span><span class="p">,</span>
|
||||
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"destination"</span><span class="p">,</span>
|
||||
<span class="n">nargs</span><span class="o">=</span><span class="s2">"?"</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"hexadecimal hash of the server destination"</span><span class="p">,</span>
|
||||
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span><span class="p">:</span>
|
||||
<span class="n">configarg</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">configarg</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">server</span><span class="p">:</span>
|
||||
<span class="n">server</span><span class="p">(</span><span class="n">configarg</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span> <span class="o">==</span> <span class="kc">None</span><span class="p">):</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">print_help</span><span class="p">()</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">client</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span><span class="p">,</span> <span class="n">configarg</span><span class="p">)</span>
|
||||
|
||||
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">exit</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py</a>.</p>
|
||||
</section>
|
||||
<section id="buffer">
|
||||
<h2>Buffer<a class="headerlink" href="#buffer" title="Permalink to this heading">#</a></h2>
|
||||
<p>The <em>Buffer</em> example explores using buffered readers and writers to send
|
||||
binary data between peers of a <code class="docutils literal notranslate"><span class="pre">Link</span></code>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1">##########################################################</span>
|
||||
<span class="c1"># This RNS example demonstrates how to set up a link to #</span>
|
||||
<span class="c1"># a destination, and pass binary data over it using a #</span>
|
||||
<span class="c1"># channel buffer. #</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="n">annotations</span>
|
||||
<span class="kn">import</span> <span class="nn">os</span>
|
||||
<span class="kn">import</span> <span class="nn">sys</span>
|
||||
<span class="kn">import</span> <span class="nn">time</span>
|
||||
<span class="kn">import</span> <span class="nn">argparse</span>
|
||||
<span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">RNS</span>
|
||||
<span class="kn">from</span> <span class="nn">RNS.vendor</span> <span class="kn">import</span> <span class="n">umsgpack</span>
|
||||
|
||||
<span class="c1"># Let's define an app name. We'll use this for all</span>
|
||||
<span class="c1"># destinations we create. Since this echo example</span>
|
||||
<span class="c1"># is part of a range of example utilities, we'll put</span>
|
||||
<span class="c1"># them all within the app namespace "example_utilities"</span>
|
||||
<span class="n">APP_NAME</span> <span class="o">=</span> <span class="s2">"example_utilities"</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Server Part #########################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># A reference to the latest client link that connected</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># A reference to the latest buffer object</span>
|
||||
<span class="n">latest_buffer</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a server</span>
|
||||
<span class="k">def</span> <span class="nf">server</span><span class="p">(</span><span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Randomly create a new identity for our example</span>
|
||||
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We create a destination that clients can connect to. We</span>
|
||||
<span class="c1"># want clients to create links to this destination, so we</span>
|
||||
<span class="c1"># need to create a "single" destination type.</span>
|
||||
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">server_identity</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">IN</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
|
||||
<span class="n">APP_NAME</span><span class="p">,</span>
|
||||
<span class="s2">"bufferexample"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># We configure a function that will get called every time</span>
|
||||
<span class="c1"># a new client creates a link to this destination.</span>
|
||||
<span class="n">server_destination</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">client_connected</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Everything's ready!</span>
|
||||
<span class="c1"># Let's Wait for client requests or user input</span>
|
||||
<span class="n">server_loop</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_loop</span><span class="p">(</span><span class="n">destination</span><span class="p">):</span>
|
||||
<span class="c1"># Let the user know that everything is ready</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span>
|
||||
<span class="s2">"Link buffer example "</span><span class="o">+</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">)</span><span class="o">+</span>
|
||||
<span class="s2">" running, waiting for a connection."</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Hit enter to manually send an announce (Ctrl-C to quit)"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># We enter a loop that runs until the users exits.</span>
|
||||
<span class="c1"># If the user hits enter, we will announce our server</span>
|
||||
<span class="c1"># destination on the network, which will let clients</span>
|
||||
<span class="c1"># know how to create messages directed towards it.</span>
|
||||
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
|
||||
<span class="n">entered</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
|
||||
<span class="n">destination</span><span class="o">.</span><span class="n">announce</span><span class="p">()</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Sent announce from "</span><span class="o">+</span><span class="n">RNS</span><span class="o">.</span><span class="n">prettyhexrep</span><span class="p">(</span><span class="n">destination</span><span class="o">.</span><span class="n">hash</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># When a client establishes a link to our server</span>
|
||||
<span class="c1"># destination, this function will be called with</span>
|
||||
<span class="c1"># a reference to the link.</span>
|
||||
<span class="k">def</span> <span class="nf">client_connected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">latest_client_link</span><span class="p">,</span> <span class="n">latest_buffer</span>
|
||||
<span class="n">latest_client_link</span> <span class="o">=</span> <span class="n">link</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Client connected"</span><span class="p">)</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">client_disconnected</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># If a new connection is received, the old reader</span>
|
||||
<span class="c1"># needs to be disconnected.</span>
|
||||
<span class="k">if</span> <span class="n">latest_buffer</span><span class="p">:</span>
|
||||
<span class="n">latest_buffer</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="c1"># Create buffer objects.</span>
|
||||
<span class="c1"># The stream_id parameter to these functions is</span>
|
||||
<span class="c1"># a bit like a file descriptor, except that it</span>
|
||||
<span class="c1"># is unique to the *receiver*.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="c1"># In this example, both the reader and the writer</span>
|
||||
<span class="c1"># use stream_id = 0, but there are actually two</span>
|
||||
<span class="c1"># separate unidirectional streams flowing in</span>
|
||||
<span class="c1"># opposite directions.</span>
|
||||
<span class="c1">#</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="n">latest_buffer</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Buffer</span><span class="o">.</span><span class="n">create_bidirectional_buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">channel</span><span class="p">,</span> <span class="n">server_buffer_ready</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">client_disconnected</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Client disconnected"</span><span class="p">)</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">server_buffer_ready</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
|
||||
<span class="sd">"""</span>
|
||||
<span class="sd"> Callback from buffer when buffer has data available</span>
|
||||
|
||||
<span class="sd"> :param ready_bytes: The number of bytes ready to read</span>
|
||||
<span class="sd"> """</span>
|
||||
<span class="k">global</span> <span class="n">latest_buffer</span>
|
||||
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">latest_buffer</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">)</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received data over the buffer: "</span> <span class="o">+</span> <span class="n">data</span><span class="p">)</span>
|
||||
|
||||
<span class="n">reply_message</span> <span class="o">=</span> <span class="s2">"I received </span><span class="se">\"</span><span class="s2">"</span><span class="o">+</span><span class="n">data</span><span class="o">+</span><span class="s2">"</span><span class="se">\"</span><span class="s2"> over the buffer"</span>
|
||||
<span class="n">reply_message</span> <span class="o">=</span> <span class="n">reply_message</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
|
||||
<span class="n">latest_buffer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">reply_message</span><span class="p">)</span>
|
||||
<span class="n">latest_buffer</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
|
||||
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Client Part #########################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># A reference to the server link</span>
|
||||
<span class="n">server_link</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># A reference to the buffer object, needed to share the</span>
|
||||
<span class="c1"># object from the link connected callback to the client</span>
|
||||
<span class="c1"># loop.</span>
|
||||
<span class="n">buffer</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># This initialisation is executed when the users chooses</span>
|
||||
<span class="c1"># to run as a client</span>
|
||||
<span class="k">def</span> <span class="nf">client</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">,</span> <span class="n">configpath</span><span class="p">):</span>
|
||||
<span class="c1"># We need a binary representation of the destination</span>
|
||||
<span class="c1"># hash that was entered on the command line</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">dest_len</span> <span class="o">=</span> <span class="p">(</span><span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">TRUNCATED_HASHLENGTH</span><span class="o">//</span><span class="mi">8</span><span class="p">)</span><span class="o">*</span><span class="mi">2</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span> <span class="o">!=</span> <span class="n">dest_len</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span>
|
||||
<span class="s2">"Destination length is invalid, must be </span><span class="si">{hex}</span><span class="s2"> hexadecimal characters (</span><span class="si">{byte}</span><span class="s2"> bytes)."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">hex</span><span class="o">=</span><span class="n">dest_len</span><span class="p">,</span> <span class="n">byte</span><span class="o">=</span><span class="n">dest_len</span><span class="o">//</span><span class="mi">2</span><span class="p">)</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">destination_hash</span> <span class="o">=</span> <span class="nb">bytes</span><span class="o">.</span><span class="n">fromhex</span><span class="p">(</span><span class="n">destination_hexhash</span><span class="p">)</span>
|
||||
<span class="k">except</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Invalid destination entered. Check your input!</span><span class="se">\n</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="n">exit</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We must first initialise Reticulum</span>
|
||||
<span class="n">reticulum</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="p">(</span><span class="n">configpath</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Check if we know a path to the destination</span>
|
||||
<span class="k">if</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Destination is not yet known. Requesting path and waiting for announce to arrive..."</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">request_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Transport</span><span class="o">.</span><span class="n">has_path</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">):</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Recall the server identity</span>
|
||||
<span class="n">server_identity</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Identity</span><span class="o">.</span><span class="n">recall</span><span class="p">(</span><span class="n">destination_hash</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Inform the user that we'll begin connecting</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Establishing link with server..."</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When the server identity is known, we set</span>
|
||||
<span class="c1"># up a destination</span>
|
||||
<span class="n">server_destination</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="p">(</span>
|
||||
<span class="n">server_identity</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">OUT</span><span class="p">,</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Destination</span><span class="o">.</span><span class="n">SINGLE</span><span class="p">,</span>
|
||||
<span class="n">APP_NAME</span><span class="p">,</span>
|
||||
<span class="s2">"bufferexample"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># And create a link</span>
|
||||
<span class="n">link</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="p">(</span><span class="n">server_destination</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># We'll also set up functions to inform the</span>
|
||||
<span class="c1"># user when the link is established or closed</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_established_callback</span><span class="p">(</span><span class="n">link_established</span><span class="p">)</span>
|
||||
<span class="n">link</span><span class="o">.</span><span class="n">set_link_closed_callback</span><span class="p">(</span><span class="n">link_closed</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Everything is set up, so let's enter a loop</span>
|
||||
<span class="c1"># for the user to interact with the example</span>
|
||||
<span class="n">client_loop</span><span class="p">()</span>
|
||||
|
||||
<span class="k">def</span> <span class="nf">client_loop</span><span class="p">():</span>
|
||||
<span class="k">global</span> <span class="n">server_link</span>
|
||||
|
||||
<span class="c1"># Wait for the link to become active</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">server_link</span><span class="p">:</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">0.1</span><span class="p">)</span>
|
||||
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="n">should_quit</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"> "</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">" "</span><span class="p">)</span>
|
||||
<span class="n">text</span> <span class="o">=</span> <span class="nb">input</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># Check if we should quit the example</span>
|
||||
<span class="k">if</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"quit"</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"q"</span> <span class="ow">or</span> <span class="n">text</span> <span class="o">==</span> <span class="s2">"exit"</span><span class="p">:</span>
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># Otherwise, encode the text and write it to the buffer.</span>
|
||||
<span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span>
|
||||
<span class="n">buffer</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">text</span><span class="p">)</span>
|
||||
<span class="c1"># Flush the buffer to force the data to be sent.</span>
|
||||
<span class="n">buffer</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Error while sending data over the link buffer: "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">))</span>
|
||||
<span class="n">should_quit</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">server_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># This function is called when a link</span>
|
||||
<span class="c1"># has been established with the server</span>
|
||||
<span class="k">def</span> <span class="nf">link_established</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="c1"># We store a reference to the link</span>
|
||||
<span class="c1"># instance for later use</span>
|
||||
<span class="k">global</span> <span class="n">server_link</span><span class="p">,</span> <span class="n">buffer</span>
|
||||
<span class="n">server_link</span> <span class="o">=</span> <span class="n">link</span>
|
||||
|
||||
<span class="c1"># Create buffer, see server_client_connected() for</span>
|
||||
<span class="c1"># more detail about setting up the buffer.</span>
|
||||
<span class="n">channel</span> <span class="o">=</span> <span class="n">link</span><span class="o">.</span><span class="n">get_channel</span><span class="p">()</span>
|
||||
<span class="n">buffer</span> <span class="o">=</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Buffer</span><span class="o">.</span><span class="n">create_bidirectional_buffer</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">channel</span><span class="p">,</span> <span class="n">client_buffer_ready</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Inform the user that the server is</span>
|
||||
<span class="c1"># connected</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Link established with server, enter some text to send, or </span><span class="se">\"</span><span class="s2">quit</span><span class="se">\"</span><span class="s2"> to quit"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When a link is closed, we'll inform the</span>
|
||||
<span class="c1"># user, and exit the program</span>
|
||||
<span class="k">def</span> <span class="nf">link_closed</span><span class="p">(</span><span class="n">link</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">TIMEOUT</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The link timed out, exiting now"</span><span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="n">link</span><span class="o">.</span><span class="n">teardown_reason</span> <span class="o">==</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Link</span><span class="o">.</span><span class="n">DESTINATION_CLOSED</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The link was closed by the server, exiting now"</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Link closed, exiting now"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">exit_handler</span><span class="p">()</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mf">1.5</span><span class="p">)</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">_exit</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># When the buffer has new data, read it and write it to the terminal.</span>
|
||||
<span class="k">def</span> <span class="nf">client_buffer_ready</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">:</span> <span class="nb">int</span><span class="p">):</span>
|
||||
<span class="k">global</span> <span class="n">buffer</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">buffer</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">ready_bytes</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Received data over the link buffer: "</span> <span class="o">+</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">))</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"> "</span><span class="p">,</span> <span class="n">end</span><span class="o">=</span><span class="s2">" "</span><span class="p">)</span>
|
||||
<span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="c1">##########################################################</span>
|
||||
<span class="c1">#### Program Startup #####################################</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="c1"># This part of the program runs at startup,</span>
|
||||
<span class="c1"># and parses input of from the user, and then</span>
|
||||
<span class="c1"># starts up the desired program mode.</span>
|
||||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">parser</span> <span class="o">=</span> <span class="n">argparse</span><span class="o">.</span><span class="n">ArgumentParser</span><span class="p">(</span><span class="n">description</span><span class="o">=</span><span class="s2">"Simple buffer example"</span><span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"-s"</span><span class="p">,</span>
|
||||
<span class="s2">"--server"</span><span class="p">,</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="s2">"store_true"</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"wait for incoming link requests from clients"</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"--config"</span><span class="p">,</span>
|
||||
<span class="n">action</span><span class="o">=</span><span class="s2">"store"</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"path to alternative Reticulum config directory"</span><span class="p">,</span>
|
||||
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">add_argument</span><span class="p">(</span>
|
||||
<span class="s2">"destination"</span><span class="p">,</span>
|
||||
<span class="n">nargs</span><span class="o">=</span><span class="s2">"?"</span><span class="p">,</span>
|
||||
<span class="n">default</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">help</span><span class="o">=</span><span class="s2">"hexadecimal hash of the server destination"</span><span class="p">,</span>
|
||||
<span class="nb">type</span><span class="o">=</span><span class="nb">str</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="n">args</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span><span class="p">:</span>
|
||||
<span class="n">configarg</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">config</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">configarg</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">args</span><span class="o">.</span><span class="n">server</span><span class="p">:</span>
|
||||
<span class="n">server</span><span class="p">(</span><span class="n">configarg</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span> <span class="o">==</span> <span class="kc">None</span><span class="p">):</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">parser</span><span class="o">.</span><span class="n">print_help</span><span class="p">()</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">client</span><span class="p">(</span><span class="n">args</span><span class="o">.</span><span class="n">destination</span><span class="p">,</span> <span class="n">configarg</span><span class="p">)</span>
|
||||
|
||||
<span class="k">except</span> <span class="ne">KeyboardInterrupt</span><span class="p">:</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">""</span><span class="p">)</span>
|
||||
<span class="n">exit</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py</a>.</p>
|
||||
</section>
|
||||
<section id="filetransfer">
|
||||
<span id="example-filetransfer"></span><h2>Filetransfer<a class="headerlink" href="#filetransfer" title="Permalink to this heading">#</a></h2>
|
||||
<p>The <em>Filetransfer</em> example implements a basic file-server program that
|
||||
@@ -2543,7 +3273,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -2578,6 +3308,8 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<li><a class="reference internal" href="#link">Link</a></li>
|
||||
<li><a class="reference internal" href="#example-identify">Identification</a></li>
|
||||
<li><a class="reference internal" href="#requests-responses">Requests & Responses</a></li>
|
||||
<li><a class="reference internal" href="#channel">Channel</a></li>
|
||||
<li><a class="reference internal" href="#buffer">Buffer</a></li>
|
||||
<li><a class="reference internal" href="#filetransfer">Filetransfer</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
<!doctype html>
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.17.1: http://docutils.sourceforge.net/" />
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/custom.css" />
|
||||
|
||||
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
--color-code-background: #f8f8f8;
|
||||
--color-code-foreground: black;
|
||||
|
||||
}
|
||||
@media not print {
|
||||
body[data-theme="dark"] {
|
||||
--color-code-background: #202020;
|
||||
--color-code-foreground: #d0d0d0;
|
||||
--color-background-primary: #202b38;
|
||||
--color-background-secondary: #161f27;
|
||||
--color-foreground-primary: #dbdbdb;
|
||||
--color-foreground-secondary: #a9b1ba;
|
||||
--color-brand-primary: #41adff;
|
||||
--color-background-hover: #161f27;
|
||||
--color-api-name: #ffbe85;
|
||||
--color-api-pre-name: #efae75;
|
||||
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body:not([data-theme="light"]) {
|
||||
--color-code-background: #202020;
|
||||
--color-code-foreground: #d0d0d0;
|
||||
--color-background-primary: #202b38;
|
||||
--color-background-secondary: #161f27;
|
||||
--color-foreground-primary: #dbdbdb;
|
||||
--color-foreground-secondary: #a9b1ba;
|
||||
--color-brand-primary: #41adff;
|
||||
--color-background-hover: #161f27;
|
||||
--color-api-name: #ffbe85;
|
||||
--color-api-pre-name: #efae75;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</style></head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
document.body.dataset.theme = localStorage.getItem("theme") || "auto";
|
||||
</script>
|
||||
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
|
||||
<symbol id="svg-toc" viewBox="0 0 24 24">
|
||||
<title>Contents</title>
|
||||
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 1024 1024">
|
||||
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM115.4 518.9L271.7 642c5.8 4.6 14.4.5 14.4-6.9V388.9c0-7.4-8.5-11.5-14.4-6.9L115.4 505.1a8.74 8.74 0 0 0 0 13.8z"/>
|
||||
</svg>
|
||||
</symbol>
|
||||
<symbol id="svg-menu" viewBox="0 0 24 24">
|
||||
<title>Menu</title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-menu">
|
||||
<line x1="3" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="3" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="3" y1="18" x2="21" y2="18"></line>
|
||||
</svg>
|
||||
</symbol>
|
||||
<symbol id="svg-arrow-right" viewBox="0 0 24 24">
|
||||
<title>Expand</title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather-chevron-right">
|
||||
<polyline points="9 18 15 12 9 6"></polyline>
|
||||
</svg>
|
||||
</symbol>
|
||||
<symbol id="svg-sun" viewBox="0 0 24 24">
|
||||
<title>Light mode</title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="feather-sun">
|
||||
<circle cx="12" cy="12" r="5"></circle>
|
||||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||||
</svg>
|
||||
</symbol>
|
||||
<symbol id="svg-moon" viewBox="0 0 24 24">
|
||||
<title>Dark mode</title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-moon">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
|
||||
</svg>
|
||||
</symbol>
|
||||
<symbol id="svg-sun-half" viewBox="0 0 24 24">
|
||||
<title>Auto light/dark mode</title>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" class="icon-tabler-shadow">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path d="M13 12h5" />
|
||||
<path d="M13 15h4" />
|
||||
<path d="M13 18h1" />
|
||||
<path d="M13 9h4" />
|
||||
<path d="M13 6h1" />
|
||||
</svg>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<input type="checkbox" class="sidebar-toggle" name="__navigation" id="__navigation">
|
||||
<input type="checkbox" class="sidebar-toggle" name="__toc" id="__toc">
|
||||
<label class="overlay sidebar-overlay" for="__navigation">
|
||||
<div class="visually-hidden">Hide navigation sidebar</div>
|
||||
</label>
|
||||
<label class="overlay toc-overlay" for="__toc">
|
||||
<div class="visually-hidden">Hide table of contents sidebar</div>
|
||||
</label>
|
||||
|
||||
|
||||
|
||||
<div class="page">
|
||||
<header class="mobile-header">
|
||||
<div class="header-left">
|
||||
<label class="nav-overlay-icon" for="__navigation">
|
||||
<div class="visually-hidden">Toggle site navigation sidebar</div>
|
||||
<i class="icon"><svg><use href="#svg-menu"></use></svg></i>
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
<button class="theme-toggle">
|
||||
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
|
||||
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
|
||||
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
|
||||
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
<label class="toc-overlay-icon toc-header-icon no-toc" for="__toc">
|
||||
<div class="visually-hidden">Toggle table of contents sidebar</div>
|
||||
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
|
||||
</label>
|
||||
</div>
|
||||
</header>
|
||||
<aside class="sidebar-drawer">
|
||||
<div class="sidebar-container">
|
||||
|
||||
<div class="sidebar-sticky"><a class="sidebar-brand centered" href="index.html">
|
||||
|
||||
<div class="sidebar-logo-container">
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
<input type="hidden" name="check_keywords" value="yes">
|
||||
<input type="hidden" name="area" value="default">
|
||||
</form>
|
||||
<div id="searchbox"></div><div class="sidebar-scroll"><div class="sidebar-tree">
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="whatis.html">What is Reticulum?</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="using.html">Using Reticulum on Your System</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="understanding.html">Understanding Reticulum</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="hardware.html">Communications Hardware</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Supported Interfaces</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="networks.html">Building Networks</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="examples.html">Code Examples</a></li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li class="toctree-l1"><a class="reference internal" href="reference.html">API Reference</a></li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</aside>
|
||||
<div class="main">
|
||||
<div class="content">
|
||||
<div class="article-container">
|
||||
<a href="#" class="back-to-top muted-link">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||
<path d="M13 20h-2V8l-5.5 5.5-1.42-1.42L12 4.16l7.92 7.92-1.42 1.42L13 8v12z"></path>
|
||||
</svg>
|
||||
<span>Back to top</span>
|
||||
</a>
|
||||
<div class="content-icon-container">
|
||||
<div class="theme-toggle-container theme-toggle-content">
|
||||
<button class="theme-toggle">
|
||||
<div class="visually-hidden">Toggle Light / Dark / Auto color theme</div>
|
||||
<svg class="theme-icon-when-auto"><use href="#svg-sun-half"></use></svg>
|
||||
<svg class="theme-icon-when-dark"><use href="#svg-moon"></use></svg>
|
||||
<svg class="theme-icon-when-light"><use href="#svg-sun"></use></svg>
|
||||
</button>
|
||||
</div>
|
||||
<label class="toc-overlay-icon toc-content-icon no-toc" for="__toc">
|
||||
<div class="visually-hidden">Toggle table of contents sidebar</div>
|
||||
<i class="icon"><svg><use href="#svg-toc"></use></svg></i>
|
||||
</label>
|
||||
</div>
|
||||
<article role="main">
|
||||
<section id="an-explanation-of-reticulum-for-human-beings">
|
||||
<h1>An Explanation of Reticulum for Human Beings<a class="headerlink" href="#an-explanation-of-reticulum-for-human-beings" title="Permalink to this heading">#</a></h1>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
</div>
|
||||
<footer>
|
||||
|
||||
<div class="related-pages">
|
||||
|
||||
|
||||
</div>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
</div>
|
||||
<div class="right-details">
|
||||
<div class="icons">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
</div>
|
||||
<aside class="toc-drawer no-toc">
|
||||
|
||||
|
||||
|
||||
</aside>
|
||||
</div>
|
||||
</div><script data-url_root="./" id="documentation_options" src="_static/documentation_options.js"></script>
|
||||
<script src="_static/jquery.js"></script>
|
||||
<script src="_static/underscore.js"></script>
|
||||
<script src="_static/_sphinx_javascript_frameworks_compat.js"></script>
|
||||
<script src="_static/doctools.js"></script>
|
||||
<script src="_static/sphinx_highlight.js"></script>
|
||||
<script src="_static/scripts/furo.js"></script>
|
||||
<script src="_static/clipboard.min.js"></script>
|
||||
<script src="_static/copybutton.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+89
-13
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="#" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Index - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -139,7 +139,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -165,7 +165,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -222,18 +222,36 @@
|
||||
|
||||
<section class="genindex-section">
|
||||
<h1 id="index">Index</h1>
|
||||
<div class="genindex-jumpbox"><a href="#A"><strong>A</strong></a> | <a href="#C"><strong>C</strong></a> | <a href="#D"><strong>D</strong></a> | <a href="#E"><strong>E</strong></a> | <a href="#F"><strong>F</strong></a> | <a href="#G"><strong>G</strong></a> | <a href="#H"><strong>H</strong></a> | <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> | <a href="#S"><strong>S</strong></a> | <a href="#T"><strong>T</strong></a> | <a href="#V"><strong>V</strong></a></div>
|
||||
<div class="genindex-jumpbox"><a href="#_"><strong>_</strong></a> | <a href="#A"><strong>A</strong></a> | <a href="#B"><strong>B</strong></a> | <a href="#C"><strong>C</strong></a> | <a href="#D"><strong>D</strong></a> | <a href="#E"><strong>E</strong></a> | <a href="#F"><strong>F</strong></a> | <a href="#G"><strong>G</strong></a> | <a href="#H"><strong>H</strong></a> | <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> | <a href="#S"><strong>S</strong></a> | <a href="#T"><strong>T</strong></a> | <a href="#U"><strong>U</strong></a> | <a href="#V"><strong>V</strong></a></div>
|
||||
</section>
|
||||
<section id="_" class="genindex-section">
|
||||
<h2>_</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.RawChannelReader.__init__">__init__() (RNS.RawChannelReader method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.RawChannelWriter.__init__">(RNS.RawChannelWriter method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="A" class="genindex-section">
|
||||
<h2>A</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.accepts_links">accepts_links() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.advertise">advertise() (RNS.Resource method)</a>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.add_message_handler">add_message_handler() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RawChannelReader.add_ready_callback">add_ready_callback() (RNS.RawChannelReader method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Resource.advertise">advertise() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.announce">announce() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.ANNOUNCE_CAP">ANNOUNCE_CAP (RNS.Reticulum attribute)</a>
|
||||
@@ -244,17 +262,35 @@
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="B" class="genindex-section">
|
||||
<h2>B</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Buffer">Buffer (class in RNS)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="C" class="genindex-section">
|
||||
<h2>C</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Resource.cancel">cancel() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Channel.Channel">Channel (class in RNS.Channel)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.clear_default_app_data">clear_default_app_data() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Buffer.create_bidirectional_buffer">create_bidirectional_buffer() (RNS.Buffer static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.create_keys">create_keys() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Buffer.create_reader">create_reader() (RNS.Buffer static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Buffer.create_writer">create_writer() (RNS.Buffer static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.CURVE">CURVE (RNS.Identity attribute)</a>
|
||||
|
||||
@@ -330,7 +366,11 @@
|
||||
<h2>G</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Link.get_channel">get_channel() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.get_data_size">get_data_size() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_establishment_rate">get_establishment_rate() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.get_hash">get_hash() (RNS.Resource method)</a>
|
||||
</li>
|
||||
@@ -349,11 +389,11 @@
|
||||
</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>
|
||||
@@ -409,6 +449,8 @@
|
||||
<li><a href="reference.html#RNS.Link.inactive_for">inactive_for() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.is_compressed">is_compressed() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.is_ready_to_send">is_ready_to_send() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
@@ -454,6 +496,14 @@
|
||||
<h2>M</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.MDU">MDU (RNS.Channel.Channel property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.MessageBase">MessageBase (class in RNS)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.MessageBase.MSGTYPE">MSGTYPE (RNS.MessageBase attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.MTU">MTU (RNS.Reticulum attribute)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
@@ -482,12 +532,14 @@
|
||||
<h2>P</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Packet">Packet (class in RNS)</a>
|
||||
<li><a href="reference.html#RNS.MessageBase.pack">pack() (RNS.MessageBase method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt">PacketReceipt (class in RNS)</a>
|
||||
<li><a href="reference.html#RNS.Packet">Packet (class in RNS)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.PacketReceipt">PacketReceipt (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Transport.PATHFINDER_M">PATHFINDER_M (RNS.Transport attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Packet.PLAIN_MDU">PLAIN_MDU (RNS.Packet attribute)</a>
|
||||
@@ -500,18 +552,28 @@
|
||||
<h2>R</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.RawChannelReader">RawChannelReader (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RawChannelWriter">RawChannelWriter (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.recall">recall() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.recall_app_data">recall_app_data() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Transport.register_announce_handler">register_announce_handler() (RNS.Transport static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.register_message_type">register_message_type() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
|
||||
<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.Channel.Channel.remove_message_handler">remove_message_handler() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.RawChannelReader.remove_ready_callback">remove_ready_callback() (RNS.RawChannelReader method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.request">request() (RNS.Link method)</a>
|
||||
</li>
|
||||
<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>
|
||||
@@ -530,8 +592,12 @@
|
||||
<h2>S</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Packet.send">send() (RNS.Packet method)</a>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.send">send() (RNS.Channel.Channel method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Packet.send">(RNS.Packet method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.Destination.set_default_app_data">set_default_app_data() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.set_delivery_callback">set_delivery_callback() (RNS.PacketReceipt method)</a>
|
||||
@@ -604,6 +670,16 @@
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="U" class="genindex-section">
|
||||
<h2>U</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.MessageBase.unpack">unpack() (RNS.MessageBase method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
</tr></table>
|
||||
</section>
|
||||
|
||||
<section id="V" class="genindex-section">
|
||||
<h2>V</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
@@ -626,7 +702,7 @@
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -229,18 +229,38 @@ scenarios.</p>
|
||||
<section id="standalone-reticulum-installation">
|
||||
<h2>Standalone Reticulum Installation<a class="headerlink" href="#standalone-reticulum-installation" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you simply want to install Reticulum and related utilities on a system,
|
||||
the easiest way is via <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
the easiest way is via the <code class="docutils literal notranslate"><span class="pre">pip</span></code> package manager:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If you no not already have pip installed, you can install it using the package manager
|
||||
<p>If you do not already have pip installed, you can install it using the package manager
|
||||
of your system with a command like <code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">apt</span> <span class="pre">install</span> <span class="pre">python3-pip</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pamac</span> <span class="pre">install</span> <span class="pre">python-pip</span></code> or similar. You can also dowload the Reticulum release
|
||||
wheels from GitHub, or other release channels, and install them offline using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.4.6</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
|
||||
<code class="docutils literal notranslate"><span class="pre">sudo</span> <span class="pre">pamac</span> <span class="pre">install</span> <span class="pre">python-pip</span></code> or similar.</p>
|
||||
<p>You can also dowload the Reticulum release wheels from GitHub, or other release channels,
|
||||
and install them offline using <code class="docutils literal notranslate"><span class="pre">pip</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.5.1</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="resolving-dependency-installation-issues">
|
||||
<h2>Resolving Dependency & Installation Issues<a class="headerlink" href="#resolving-dependency-installation-issues" title="Permalink to this heading">#</a></h2>
|
||||
<p>On some platforms, there may not be binary packages available for all dependencies, and
|
||||
<code class="docutils literal notranslate"><span class="pre">pip</span></code> installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Debian / Ubuntu / Derivatives</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">build</span><span class="o">-</span><span class="n">essential</span>
|
||||
|
||||
<span class="c1"># Arch / Manjaro / Derivatives</span>
|
||||
<span class="n">sudo</span> <span class="n">pamac</span> <span class="n">install</span> <span class="n">base</span><span class="o">-</span><span class="n">devel</span>
|
||||
|
||||
<span class="c1"># Fedora</span>
|
||||
<span class="n">sudo</span> <span class="n">dnf</span> <span class="n">groupinstall</span> <span class="s2">"Development Tools"</span> <span class="s2">"Development Libraries"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>With the base development packages installed, <code class="docutils literal notranslate"><span class="pre">pip</span></code> should be able to compile any missing
|
||||
dependencies from source, and complete installation even on platforms that don’t have pre-
|
||||
compiled packages available.</p>
|
||||
</section>
|
||||
<section id="try-using-a-reticulum-based-program">
|
||||
<h2>Try Using a Reticulum-based Program<a class="headerlink" href="#try-using-a-reticulum-based-program" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you simply want to try using a program built with Reticulum, a few different
|
||||
@@ -253,6 +273,12 @@ links, such as local WiFi, wired Ethernet, the Internet, or any combination.</p>
|
||||
transceivers or infrastructure just to try it out. Launching the programs on separate
|
||||
devices connected to the same WiFi network is enough to get started, and physical
|
||||
radio interfaces can then be added later.</p>
|
||||
<section id="remote-shell">
|
||||
<h3>Remote Shell<a class="headerlink" href="#remote-shell" title="Permalink to this heading">#</a></h3>
|
||||
<p>The <a class="reference external" href="https://github.com/acehoss/rnsh">rnsh</a> program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how <code class="docutils literal notranslate"><span class="pre">ssh</span></code> works.</p>
|
||||
</section>
|
||||
<section id="nomad-network">
|
||||
<h3>Nomad Network<a class="headerlink" href="#nomad-network" title="Permalink to this heading">#</a></h3>
|
||||
<p>The terminal-based program <a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a>
|
||||
@@ -304,8 +330,8 @@ network status and connectivity.</p>
|
||||
<h2>Creating a Network With Reticulum<a class="headerlink" href="#creating-a-network-with-reticulum" title="Permalink to this heading">#</a></h2>
|
||||
<p>To create a network, you will need to specify one or more <em>interfaces</em> for
|
||||
Reticulum to use. This is done in the Reticulum configuration file, which by
|
||||
default is located at <code class="docutils literal notranslate"><span class="pre">~/.reticulum/config</span></code>. You can edit this file by hand,
|
||||
or use the interactive <code class="docutils literal notranslate"><span class="pre">rnsconfig</span></code> utility.</p>
|
||||
default is located at <code class="docutils literal notranslate"><span class="pre">~/.reticulum/config</span></code>. You can get an example
|
||||
configuration file with all options via <code class="docutils literal notranslate"><span class="pre">rnsd</span> <span class="pre">--exampleconfig</span></code>.</p>
|
||||
<p>When Reticulum is started for the first time, it will create a default
|
||||
configuration file, with one active interface. This default interface uses
|
||||
your existing Ethernet and WiFi networks (if any), and only allows you to
|
||||
@@ -369,7 +395,7 @@ by adding one of the following interfaces to your <code class="docutils literal
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4965</span>
|
||||
|
||||
<span class="c1"># TCP/IP interface to the Frankfurt hub</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Dublin</span><span class="p">]]</span>
|
||||
<span class="p">[[</span><span class="n">RNS</span> <span class="n">Testnet</span> <span class="n">Frankfurt</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="n">frankfurt</span><span class="o">.</span><span class="n">connect</span><span class="o">.</span><span class="n">reticulum</span><span class="o">.</span><span class="n">network</span>
|
||||
@@ -404,7 +430,7 @@ chapter for a guide. If you prefer purchasing a ready-made unit, you can refer t
|
||||
refer to these additional external resources:</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://unsigned.io/how-to-make-your-own-rnodes/">How To Make Your Own RNodes</a></p></li>
|
||||
<li><p><a class="reference external" href="https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/">Installing RNode Firmware on Compatible LoRa Devices</a></p></li>
|
||||
<li><p><a class="reference external" href="https://unsigned.io/installing-rnode-firmware-on-supported-devices/">Installing RNode Firmware on Compatible LoRa Devices</a></p></li>
|
||||
<li><p><a class="reference external" href="https://unsigned.io/private-messaging-over-lora/">Private, Secure and Uncensorable Messaging Over a LoRa Mesh</a></p></li>
|
||||
<li><p><a class="reference external" href="https://github.com/markqvist/RNode_Firmware/">RNode Firmware</a></p></li>
|
||||
</ul>
|
||||
@@ -424,7 +450,7 @@ started is to install the latest release of Reticulum via pip:</p>
|
||||
ready to import and use RNS in your own programs. The next step will most
|
||||
likely be to look at some <a class="reference internal" href="examples.html#examples-main"><span class="std std-ref">Example Programs</span></a>.</p>
|
||||
<p>For extended functionality, you can install optional dependencies:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip3</span> <span class="n">install</span> <span class="n">pyserial</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Further information can be found in the <a class="reference internal" href="reference.html#api-main"><span class="std std-ref">API Reference</span></a>.</p>
|
||||
@@ -435,7 +461,7 @@ likely be to look at some <a class="reference internal" href="examples.html#exam
|
||||
utilities, you’ll want to get the latest source from GitHub. In that case,
|
||||
don’t use pip, but try this recipe:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span> <span class="n">netifaces</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">cryptography</span> <span class="n">pyserial</span>
|
||||
|
||||
<span class="c1"># Clone repository</span>
|
||||
<span class="n">git</span> <span class="n">clone</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">markqvist</span><span class="o">/</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">git</span>
|
||||
@@ -472,28 +498,12 @@ your first pull request, it is probably a good idea to introduce yourself on
|
||||
the <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions">disucssion forum on GitHub</a>,
|
||||
or ask one of the developers or maintainers for a good place to start.</p>
|
||||
</section>
|
||||
<section id="reticulum-on-arm64">
|
||||
<h2>Reticulum on ARM64<a class="headerlink" href="#reticulum-on-arm64" title="Permalink to this heading">#</a></h2>
|
||||
<p>On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="reticulum-on-raspberry-pi">
|
||||
<h2>Reticulum on Raspberry Pi<a class="headerlink" href="#reticulum-on-raspberry-pi" title="Permalink to this heading">#</a></h2>
|
||||
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don’t always have packages available for some dependencies.</p>
|
||||
</section>
|
||||
<section id="reticulum-on-android">
|
||||
<h2>Reticulum on Android<a class="headerlink" href="#reticulum-on-android" title="Permalink to this heading">#</a></h2>
|
||||
<section id="platform-specific-install-notes">
|
||||
<h2>Platform-Specific Install Notes<a class="headerlink" href="#platform-specific-install-notes" title="Permalink to this heading">#</a></h2>
|
||||
<p>Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.</p>
|
||||
<section id="android">
|
||||
<h3>Android<a class="headerlink" href="#android" title="Permalink to this heading">#</a></h3>
|
||||
<p>Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using an app like <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>.</p>
|
||||
<p>For more control and features, you can use Reticulum and related programs via
|
||||
@@ -551,6 +561,66 @@ locally on your device using the following command:</p>
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point. Until then you can use the <a class="reference external" href="https://github.com/markqvist/sideband">Sideband source code</a> as an example and startig point.</p>
|
||||
</section>
|
||||
<section id="arm64">
|
||||
<h3>ARM64<a class="headerlink" href="#arm64" title="Permalink to this heading">#</a></h3>
|
||||
<p>On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="raspberry-pi">
|
||||
<h3>Raspberry Pi<a class="headerlink" href="#raspberry-pi" title="Permalink to this heading">#</a></h3>
|
||||
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don’t always have packages available for some dependencies.</p>
|
||||
<p>While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.</p>
|
||||
</section>
|
||||
<section id="debian-bookworm">
|
||||
<h3>Debian Bookworm<a class="headerlink" href="#debian-bookworm" title="Permalink to this heading">#</a></h3>
|
||||
<p>On versions of Debian released after April 2023, it is no longer possible
|
||||
to use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement <code class="docutils literal notranslate"><span class="pre">pipx</span></code> command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install pipx</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">pipx</span>
|
||||
|
||||
<span class="c1"># Make installed programs available on the command line</span>
|
||||
<span class="n">pipx</span> <span class="n">ensurepath</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">pipx</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="ubuntu-lunar">
|
||||
<h3>Ubuntu Lunar<a class="headerlink" href="#ubuntu-lunar" title="Permalink to this heading">#</a></h3>
|
||||
<p>On versions of Ubuntu released after April 2023, it is no longer possible
|
||||
to use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement <code class="docutils literal notranslate"><span class="pre">pipx</span></code> command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install pipx</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">pipx</span>
|
||||
|
||||
<span class="c1"># Make installed programs available on the command line</span>
|
||||
<span class="n">pipx</span> <span class="n">ensurepath</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">pipx</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
<section id="pure-python-reticulum">
|
||||
<h2>Pure-Python Reticulum<a class="headerlink" href="#pure-python-reticulum" title="Permalink to this heading">#</a></h2>
|
||||
<p>In some rare cases, and on more obscure system types, it is not possible to
|
||||
@@ -604,7 +674,7 @@ section of this manual.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -633,7 +703,9 @@ section of this manual.</p>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Getting Started Fast</a><ul>
|
||||
<li><a class="reference internal" href="#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
|
||||
<li><a class="reference internal" href="#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
<li><a class="reference internal" href="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
|
||||
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
|
||||
<li><a class="reference internal" href="#nomad-network">Nomad Network</a></li>
|
||||
<li><a class="reference internal" href="#sideband">Sideband</a></li>
|
||||
</ul>
|
||||
@@ -645,9 +717,14 @@ section of this manual.</p>
|
||||
<li><a class="reference internal" href="#adding-radio-interfaces">Adding Radio Interfaces</a></li>
|
||||
<li><a class="reference internal" href="#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li><a class="reference internal" href="#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-arm64">Reticulum on ARM64</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-raspberry-pi">Reticulum on Raspberry Pi</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-on-android">Reticulum on Android</a></li>
|
||||
<li><a class="reference internal" href="#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
|
||||
<li><a class="reference internal" href="#android">Android</a></li>
|
||||
<li><a class="reference internal" href="#arm64">ARM64</a></li>
|
||||
<li><a class="reference internal" href="#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li><a class="reference internal" href="#debian-bookworm">Debian Bookworm</a></li>
|
||||
<li><a class="reference internal" href="#ubuntu-lunar">Ubuntu Lunar</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#pure-python-reticulum">Pure-Python Reticulum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Supported Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -442,7 +442,7 @@ connectivity for client devices.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
+22
-7
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -226,6 +226,7 @@
|
||||
<p>This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.</p>
|
||||
<p>This manual is also available in <a class="reference external" href="https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf">PDF</a> and <a class="reference external" href="https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub">EPUB</a> formats.</p>
|
||||
<section id="table-of-contents">
|
||||
<h2>Table Of Contents<a class="headerlink" href="#table-of-contents" title="Permalink to this heading">#</a></h2>
|
||||
</section>
|
||||
@@ -241,7 +242,9 @@ to participate in the development of Reticulum itself.</p>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#remote-shell">Remote Shell</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#nomad-network">Nomad Network</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#sideband">Sideband</a></li>
|
||||
</ul>
|
||||
@@ -253,9 +256,14 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#adding-radio-interfaces">Adding Radio Interfaces</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-arm64">Reticulum on ARM64</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-raspberry-pi">Reticulum on Raspberry Pi</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#reticulum-on-android">Reticulum on Android</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#android">Android</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#arm64">ARM64</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#debian-bookworm">Debian Bookworm</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#ubuntu-lunar">Ubuntu Lunar</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#pure-python-reticulum">Pure-Python Reticulum</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -353,6 +361,8 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#link">Link</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#example-identify">Identification</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#requests-responses">Requests & Responses</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#channel">Channel</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#buffer">Buffer</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#filetransfer">Filetransfer</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -375,6 +385,11 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Link"><code class="docutils literal notranslate"><span class="pre">Link</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.RequestReceipt"><code class="docutils literal notranslate"><span class="pre">RequestReceipt</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Resource"><code class="docutils literal notranslate"><span class="pre">Resource</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Channel.Channel"><code class="docutils literal notranslate"><span class="pre">Channel</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.MessageBase"><code class="docutils literal notranslate"><span class="pre">MessageBase</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Buffer"><code class="docutils literal notranslate"><span class="pre">Buffer</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.RawChannelReader"><code class="docutils literal notranslate"><span class="pre">RawChannelReader</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.RawChannelWriter"><code class="docutils literal notranslate"><span class="pre">RawChannelWriter</span></code></a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="reference.html#RNS.Transport"><code class="docutils literal notranslate"><span class="pre">Transport</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -408,7 +423,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Supported Interfaces - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Supported Interfaces - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -954,7 +954,7 @@ that a large span of network types can seamlessly <em>co-exist</em> and intercon
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Code Examples" href="examples.html" /><link rel="prev" title="Supported Interfaces" href="interfaces.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Building Networks - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -421,7 +421,7 @@ connected outliers are now an integral part of the network.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
Binary file not shown.
+376
-7
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="prev" title="Support Reticulum" href="support.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>API Reference - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -228,7 +228,7 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
|
||||
<p id="api-reticulum"><h3> Reticulum </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</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">loglevel</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">logdest</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.Reticulum" title="Permalink to this definition">#</a></dt>
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</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">loglevel</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">logdest</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">verbosity</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.Reticulum" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>This class is used to initialise access to Reticulum within a
|
||||
program. You must create exactly one instance of this class before
|
||||
carrying out any other RNS operations, such as creating destinations
|
||||
@@ -720,7 +720,7 @@ proofs should be returned for received packets.</p>
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>path</strong> – The path for the request handler to be registered.</p></li>
|
||||
<li><p><strong>response_generator</strong> – A function or method with the signature <em>response_generator(path, data, request_id, remote_identity, requested_at)</em> to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns <code class="docutils literal notranslate"><span class="pre">None</span></code>, no response will be sent.</p></li>
|
||||
<li><p><strong>response_generator</strong> – A function or method with the signature <em>response_generator(path, data, request_id, link_id, remote_identity, requested_at)</em> to be called. Whatever this funcion returns will be sent as a response to the requester. If the function returns <code class="docutils literal notranslate"><span class="pre">None</span></code>, no response will be sent.</p></li>
|
||||
<li><p><strong>allow</strong> – One of <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_NONE</span></code>, <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_ALL</span></code> or <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_LIST</span></code>. If <code class="docutils literal notranslate"><span class="pre">RNS.Destination.ALLOW_LIST</span></code> is set, the request handler will only respond to requests for identified peers in the supplied list.</p></li>
|
||||
<li><p><strong>allowed_list</strong> – A list of <em>bytes-like</em> <a class="reference internal" href="#api-identity"><span class="std std-ref">RNS.Identity</span></a> hashes.</p></li>
|
||||
</ul>
|
||||
@@ -991,7 +991,7 @@ and encrypted connectivity with the specified destination.</p>
|
||||
|
||||
<dl class="py attribute">
|
||||
<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="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">5</span></em><a class="headerlink" href="#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP" title="Permalink to this definition">#</a></dt>
|
||||
<span class="sig-name descname"><span class="pre">ESTABLISHMENT_TIMEOUT_PER_HOP</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">6</span></em><a class="headerlink" href="#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Timeout for link establishment in seconds per hop to destination.</p>
|
||||
</dd></dl>
|
||||
|
||||
@@ -1057,6 +1057,16 @@ thus preserved. This method can be used for authentication.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Link.get_establishment_rate">
|
||||
<span class="sig-name descname"><span class="pre">get_establishment_rate</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_establishment_rate" 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 data transfer rate at which the link establishment procedure ocurred, in bits per second.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Link.no_inbound_for">
|
||||
<span class="sig-name descname"><span class="pre">no_inbound_for</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.no_inbound_for" title="Permalink to this definition">#</a></dt>
|
||||
@@ -1104,6 +1114,17 @@ thus preserved. This method can be used for authentication.</p>
|
||||
be used if a new link to the same destination is established.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Link.get_channel">
|
||||
<span class="sig-name descname"><span class="pre">get_channel</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.get_channel" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Get the <code class="docutils literal notranslate"><span class="pre">Channel</span></code> for this link.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p><code class="docutils literal notranslate"><span class="pre">Channel</span></code> object</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Link.set_link_closed_callback">
|
||||
<span class="sig-name descname"><span class="pre">set_link_closed_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Link.set_link_closed_callback" title="Permalink to this definition">#</a></dt>
|
||||
@@ -1357,6 +1378,321 @@ the resource advertisement it will begin transferring.</p>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<p id="api-channel"><h3> Channel </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.Channel.</span></span><span class="sig-name descname"><span class="pre">Channel</span></span><a class="headerlink" href="#RNS.Channel.Channel" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Provides reliable delivery of messages over
|
||||
a link.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">Channel</span></code> differs from <code class="docutils literal notranslate"><span class="pre">Request</span></code> and
|
||||
<code class="docutils literal notranslate"><span class="pre">Resource</span></code> in some important ways:</p>
|
||||
<blockquote>
|
||||
<div><dl class="simple">
|
||||
<dt><strong>Continuous</strong></dt><dd><p>Messages can be sent or received as long as
|
||||
the <code class="docutils literal notranslate"><span class="pre">Link</span></code> is open.</p>
|
||||
</dd>
|
||||
<dt><strong>Bi-directional</strong></dt><dd><p>Messages can be sent in either direction on
|
||||
the <code class="docutils literal notranslate"><span class="pre">Link</span></code>; neither end is the client or
|
||||
server.</p>
|
||||
</dd>
|
||||
<dt><strong>Size-constrained</strong></dt><dd><p>Messages must be encoded into a single packet.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</div></blockquote>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">Channel</span></code> is similar to <code class="docutils literal notranslate"><span class="pre">Packet</span></code>, except that it
|
||||
provides reliable delivery (automatic retries) as well
|
||||
as a structure for exchanging several types of
|
||||
messages over the <code class="docutils literal notranslate"><span class="pre">Link</span></code>.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">Channel</span></code> is not instantiated directly, but rather
|
||||
obtained from a <code class="docutils literal notranslate"><span class="pre">Link</span></code> with <code class="docutils literal notranslate"><span class="pre">get_channel()</span></code>.</p>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel.register_message_type">
|
||||
<span class="sig-name descname"><span class="pre">register_message_type</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">message_class</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Type</span><span class="p"><span class="pre">[</span></span><a class="reference internal" href="#RNS.MessageBase" title="RNS.Channel.MessageBase"><span class="pre">MessageBase</span></a><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Channel.Channel.register_message_type" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Register a message class for reception over a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>.</p>
|
||||
<p>Message classes must extend <code class="docutils literal notranslate"><span class="pre">MessageBase</span></code>.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>message_class</strong> – Class to register</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel.add_message_handler">
|
||||
<span class="sig-name descname"><span class="pre">add_message_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">MessageCallbackType</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Channel.Channel.add_message_handler" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Add a handler for incoming messages. A handler
|
||||
has the following signature:</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">(message:</span> <span class="pre">MessageBase)</span> <span class="pre">-></span> <span class="pre">bool</span></code></p>
|
||||
<p>Handlers are processed in the order they are
|
||||
added. If any handler returns True, processing
|
||||
of the message stops; handlers after the
|
||||
returning handler will not be called.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>callback</strong> – Function to call</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel.remove_message_handler">
|
||||
<span class="sig-name descname"><span class="pre">remove_message_handler</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">MessageCallbackType</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Channel.Channel.remove_message_handler" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Remove a handler added with <code class="docutils literal notranslate"><span class="pre">add_message_handler</span></code>.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>callback</strong> – handler to remove</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel.is_ready_to_send">
|
||||
<span class="sig-name descname"><span class="pre">is_ready_to_send</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">bool</span></span></span><a class="headerlink" href="#RNS.Channel.Channel.is_ready_to_send" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Check if <code class="docutils literal notranslate"><span class="pre">Channel</span></code> is ready to send.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>True if ready</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel.send">
|
||||
<span class="sig-name descname"><span class="pre">send</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">message</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.MessageBase" title="RNS.Channel.MessageBase"><span class="pre">MessageBase</span></a></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">Envelope</span></span></span><a class="headerlink" href="#RNS.Channel.Channel.send" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Send a message. If a message send is attempted and
|
||||
<code class="docutils literal notranslate"><span class="pre">Channel</span></code> is not ready, an exception is thrown.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>message</strong> – an instance of a <code class="docutils literal notranslate"><span class="pre">MessageBase</span></code> subclass</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel.MDU">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">MDU</span></span><a class="headerlink" href="#RNS.Channel.Channel.MDU" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Maximum Data Unit: the number of bytes available
|
||||
for a message to consume in a single send. This
|
||||
value is adjusted from the <code class="docutils literal notranslate"><span class="pre">Link</span></code> MDU to accommodate
|
||||
message header information.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>number of bytes available</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<p id="api-messsagebase"><h3> MessageBase </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.MessageBase">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">MessageBase</span></span><a class="headerlink" href="#RNS.MessageBase" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Base type for any messages sent or received on a Channel.
|
||||
Subclasses must define the two abstract methods as well as
|
||||
the <code class="docutils literal notranslate"><span class="pre">MSGTYPE</span></code> class variable.</p>
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.MessageBase.MSGTYPE">
|
||||
<span class="sig-name descname"><span class="pre">MSGTYPE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">None</span></em><a class="headerlink" href="#RNS.MessageBase.MSGTYPE" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Defines a unique identifier for a message class.</p>
|
||||
<ul class="simple">
|
||||
<li><p>Must be unique within all classes registered with a <code class="docutils literal notranslate"><span class="pre">Channel</span></code></p></li>
|
||||
<li><p>Must be less than <code class="docutils literal notranslate"><span class="pre">0xf000</span></code>. Values greater than or equal to <code class="docutils literal notranslate"><span class="pre">0xf000</span></code> are reserved.</p></li>
|
||||
</ul>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.MessageBase.pack">
|
||||
<em class="property"><span class="pre">abstract</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">pack</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">bytes</span></span></span><a class="headerlink" href="#RNS.MessageBase.pack" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Create and return the binary representation of the message</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns</dt>
|
||||
<dd class="field-odd"><p>binary representation of message</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.MessageBase.unpack">
|
||||
<em class="property"><span class="pre">abstract</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">unpack</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">raw</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">bytes</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.MessageBase.unpack" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Populate message from binary representation</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>raw</strong> – binary representation</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<p id="api-buffer"><h3> Buffer </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Buffer">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Buffer</span></span><a class="headerlink" href="#RNS.Buffer" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Static functions for creating buffered streams that send
|
||||
and receive over a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>.</p>
|
||||
<p>These functions use <code class="docutils literal notranslate"><span class="pre">BufferedReader</span></code>, <code class="docutils literal notranslate"><span class="pre">BufferedWriter</span></code>,
|
||||
and <code class="docutils literal notranslate"><span class="pre">BufferedRWPair</span></code> to add buffering to
|
||||
<code class="docutils literal notranslate"><span class="pre">RawChannelReader</span></code> and <code class="docutils literal notranslate"><span class="pre">RawChannelWriter</span></code>.</p>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Buffer.create_reader">
|
||||
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">create_reader</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">ready_callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Callable</span><span class="p"><span class="pre">[</span></span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">None</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">BufferedReader</span></span></span><a class="headerlink" href="#RNS.Buffer.create_reader" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Create a buffered reader that reads binary data sent
|
||||
over a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>, with an optional callback when
|
||||
new data is available.</p>
|
||||
<p>Callback signature: <code class="docutils literal notranslate"><span class="pre">(ready_bytes:</span> <span class="pre">int)</span> <span class="pre">-></span> <span class="pre">None</span></code></p>
|
||||
<p>For more information on the reader-specific functions
|
||||
of this object, see the Python documentation for
|
||||
<code class="docutils literal notranslate"><span class="pre">BufferedReader</span></code></p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>stream_id</strong> – the local stream id to receive from</p></li>
|
||||
<li><p><strong>channel</strong> – the channel to receive on</p></li>
|
||||
<li><p><strong>ready_callback</strong> – function to call when new data is available</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>a BufferedReader object</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Buffer.create_writer">
|
||||
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">create_writer</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">BufferedWriter</span></span></span><a class="headerlink" href="#RNS.Buffer.create_writer" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Create a buffered writer that writes binary data over
|
||||
a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>.</p>
|
||||
<p>For more information on the writer-specific functions
|
||||
of this object, see the Python documentation for
|
||||
<code class="docutils literal notranslate"><span class="pre">BufferedWriter</span></code></p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>stream_id</strong> – the remote stream id to send to</p></li>
|
||||
<li><p><strong>channel</strong> – the channel to send on</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>a BufferedWriter object</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Buffer.create_bidirectional_buffer">
|
||||
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">create_bidirectional_buffer</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">receive_stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">send_stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em>, <em class="sig-param"><span class="n"><span class="pre">ready_callback</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Optional</span><span class="p"><span class="pre">[</span></span><span class="pre">Callable</span><span class="p"><span class="pre">[</span></span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">None</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">]</span></span></span><span class="w"> </span><span class="o"><span class="pre">=</span></span><span class="w"> </span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">→</span> <span class="sig-return-typehint"><span class="pre">BufferedRWPair</span></span></span><a class="headerlink" href="#RNS.Buffer.create_bidirectional_buffer" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Create a buffered reader/writer pair that reads and
|
||||
writes binary data over a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>, with an
|
||||
optional callback when new data is available.</p>
|
||||
<p>Callback signature: <code class="docutils literal notranslate"><span class="pre">(ready_bytes:</span> <span class="pre">int)</span> <span class="pre">-></span> <span class="pre">None</span></code></p>
|
||||
<p>For more information on the reader-specific functions
|
||||
of this object, see the Python documentation for
|
||||
<code class="docutils literal notranslate"><span class="pre">BufferedRWPair</span></code></p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>receive_stream_id</strong> – the local stream id to receive at</p></li>
|
||||
<li><p><strong>send_stream_id</strong> – the remote stream id to send to</p></li>
|
||||
<li><p><strong>channel</strong> – the channel to send and receive on</p></li>
|
||||
<li><p><strong>ready_callback</strong> – function to call when new data is available</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
<dt class="field-even">Returns</dt>
|
||||
<dd class="field-even"><p>a BufferedRWPair object</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<p id="api-rawchannelreader"><h3> RawChannelReader </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.RawChannelReader">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">RawChannelReader</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RawChannelReader" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>An implementation of RawIOBase that receives
|
||||
binary stream data sent over a <code class="docutils literal notranslate"><span class="pre">Channel</span></code>.</p>
|
||||
<blockquote>
|
||||
<div><p>This class generally need not be instantiated directly.
|
||||
Use <a class="reference internal" href="#RNS.Buffer.create_reader" title="RNS.Buffer.create_reader"><code class="xref py py-func docutils literal notranslate"><span class="pre">RNS.Buffer.create_reader()</span></code></a>,
|
||||
<a class="reference internal" href="#RNS.Buffer.create_writer" title="RNS.Buffer.create_writer"><code class="xref py py-func docutils literal notranslate"><span class="pre">RNS.Buffer.create_writer()</span></code></a>, and
|
||||
<a class="reference internal" href="#RNS.Buffer.create_bidirectional_buffer" title="RNS.Buffer.create_bidirectional_buffer"><code class="xref py py-func docutils literal notranslate"><span class="pre">RNS.Buffer.create_bidirectional_buffer()</span></code></a> functions
|
||||
to create buffered streams with optional callbacks.</p>
|
||||
<p>For additional information on the API of this
|
||||
object, see the Python documentation for
|
||||
<code class="docutils literal notranslate"><span class="pre">RawIOBase</span></code>.</p>
|
||||
</div></blockquote>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.RawChannelReader.__init__">
|
||||
<span class="sig-name descname"><span class="pre">__init__</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RawChannelReader.__init__" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Create a raw channel reader.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>stream_id</strong> – local stream id to receive at</p></li>
|
||||
<li><p><strong>channel</strong> – <code class="docutils literal notranslate"><span class="pre">Channel</span></code> object to receive from</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.RawChannelReader.add_ready_callback">
|
||||
<span class="sig-name descname"><span class="pre">add_ready_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">cb</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Callable</span><span class="p"><span class="pre">[</span></span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">None</span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RawChannelReader.add_ready_callback" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Add a function to be called when new data is available.
|
||||
The function should have the signature <code class="docutils literal notranslate"><span class="pre">(ready_bytes:</span> <span class="pre">int)</span> <span class="pre">-></span> <span class="pre">None</span></code></p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>cb</strong> – function to call</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.RawChannelReader.remove_ready_callback">
|
||||
<span class="sig-name descname"><span class="pre">remove_ready_callback</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">cb</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">Callable</span><span class="p"><span class="pre">[</span></span><span class="p"><span class="pre">[</span></span><span class="pre">int</span><span class="p"><span class="pre">]</span></span><span class="p"><span class="pre">,</span></span><span class="w"> </span><span class="pre">None</span><span class="p"><span class="pre">]</span></span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RawChannelReader.remove_ready_callback" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Remove a function added with <a class="reference internal" href="#RNS.RawChannelReader.add_ready_callback" title="RNS.RawChannelReader.add_ready_callback"><code class="xref py py-func docutils literal notranslate"><span class="pre">RNS.RawChannelReader.add_ready_callback()</span></code></a></p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><p><strong>cb</strong> – function to remove</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<p id="api-rawchannelwriter"><h3> RawChannelWriter </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.RawChannelWriter">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">RawChannelWriter</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RawChannelWriter" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>An implementation of RawIOBase that receives
|
||||
binary stream data sent over a channel.</p>
|
||||
<blockquote>
|
||||
<div><p>This class generally need not be instantiated directly.
|
||||
Use <a class="reference internal" href="#RNS.Buffer.create_reader" title="RNS.Buffer.create_reader"><code class="xref py py-func docutils literal notranslate"><span class="pre">RNS.Buffer.create_reader()</span></code></a>,
|
||||
<a class="reference internal" href="#RNS.Buffer.create_writer" title="RNS.Buffer.create_writer"><code class="xref py py-func docutils literal notranslate"><span class="pre">RNS.Buffer.create_writer()</span></code></a>, and
|
||||
<a class="reference internal" href="#RNS.Buffer.create_bidirectional_buffer" title="RNS.Buffer.create_bidirectional_buffer"><code class="xref py py-func docutils literal notranslate"><span class="pre">RNS.Buffer.create_bidirectional_buffer()</span></code></a> functions
|
||||
to create buffered streams with optional callbacks.</p>
|
||||
<p>For additional information on the API of this
|
||||
object, see the Python documentation for
|
||||
<code class="docutils literal notranslate"><span class="pre">RawIOBase</span></code>.</p>
|
||||
</div></blockquote>
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.RawChannelWriter.__init__">
|
||||
<span class="sig-name descname"><span class="pre">__init__</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">stream_id</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">int</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">channel</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><a class="reference internal" href="#RNS.Channel.Channel" title="RNS.Channel.Channel"><span class="pre">Channel</span></a></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.RawChannelWriter.__init__" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Create a raw channel writer.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters</dt>
|
||||
<dd class="field-odd"><ul class="simple">
|
||||
<li><p><strong>stream_id</strong> – remote stream id to sent do</p></li>
|
||||
<li><p><strong>channel</strong> – <code class="docutils literal notranslate"><span class="pre">Channel</span></code> object to send on</p></li>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<p id="api-transport"><h3> Transport </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Transport">
|
||||
@@ -1484,7 +1820,7 @@ will announce it.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
@@ -1588,11 +1924,13 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#RNS.Link.STALE_TIME"><code class="docutils literal notranslate"><span class="pre">STALE_TIME</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.identify"><code class="docutils literal notranslate"><span class="pre">identify()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.request"><code class="docutils literal notranslate"><span class="pre">request()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.get_establishment_rate"><code class="docutils literal notranslate"><span class="pre">get_establishment_rate()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.no_inbound_for"><code class="docutils literal notranslate"><span class="pre">no_inbound_for()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.no_outbound_for"><code class="docutils literal notranslate"><span class="pre">no_outbound_for()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.inactive_for"><code class="docutils literal notranslate"><span class="pre">inactive_for()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.get_remote_identity"><code class="docutils literal notranslate"><span class="pre">get_remote_identity()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.teardown"><code class="docutils literal notranslate"><span class="pre">teardown()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.get_channel"><code class="docutils literal notranslate"><span class="pre">get_channel()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.set_link_closed_callback"><code class="docutils literal notranslate"><span class="pre">set_link_closed_callback()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.set_packet_callback"><code class="docutils literal notranslate"><span class="pre">set_packet_callback()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Link.set_resource_callback"><code class="docutils literal notranslate"><span class="pre">set_resource_callback()</span></code></a></li>
|
||||
@@ -1622,6 +1960,37 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#RNS.Resource.is_compressed"><code class="docutils literal notranslate"><span class="pre">is_compressed()</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel"><code class="docutils literal notranslate"><span class="pre">Channel</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.register_message_type"><code class="docutils literal notranslate"><span class="pre">register_message_type()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.add_message_handler"><code class="docutils literal notranslate"><span class="pre">add_message_handler()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.remove_message_handler"><code class="docutils literal notranslate"><span class="pre">remove_message_handler()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.is_ready_to_send"><code class="docutils literal notranslate"><span class="pre">is_ready_to_send()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.send"><code class="docutils literal notranslate"><span class="pre">send()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.MDU"><code class="docutils literal notranslate"><span class="pre">MDU</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.MessageBase"><code class="docutils literal notranslate"><span class="pre">MessageBase</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.MessageBase.MSGTYPE"><code class="docutils literal notranslate"><span class="pre">MSGTYPE</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.MessageBase.pack"><code class="docutils literal notranslate"><span class="pre">pack()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.MessageBase.unpack"><code class="docutils literal notranslate"><span class="pre">unpack()</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.Buffer"><code class="docutils literal notranslate"><span class="pre">Buffer</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.Buffer.create_reader"><code class="docutils literal notranslate"><span class="pre">create_reader()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Buffer.create_writer"><code class="docutils literal notranslate"><span class="pre">create_writer()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Buffer.create_bidirectional_buffer"><code class="docutils literal notranslate"><span class="pre">create_bidirectional_buffer()</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.RawChannelReader"><code class="docutils literal notranslate"><span class="pre">RawChannelReader</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.RawChannelReader.__init__"><code class="docutils literal notranslate"><span class="pre">__init__()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.RawChannelReader.add_ready_callback"><code class="docutils literal notranslate"><span class="pre">add_ready_callback()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.RawChannelReader.remove_ready_callback"><code class="docutils literal notranslate"><span class="pre">remove_ready_callback()</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.RawChannelWriter"><code class="docutils literal notranslate"><span class="pre">RawChannelWriter</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.RawChannelWriter.__init__"><code class="docutils literal notranslate"><span class="pre">__init__()</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.Transport"><code class="docutils literal notranslate"><span class="pre">Transport</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.Transport.PATHFINDER_M"><code class="docutils literal notranslate"><span class="pre">PATHFINDER_M</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Transport.register_announce_handler"><code class="docutils literal notranslate"><span class="pre">register_announce_handler()</span></code></a></li>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="#" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.4.7 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/><title>Search - Reticulum Network Stack 0.5.1 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo-extensions.css?digest=30d1aed668e5c3a91c3e3bf6a60b675221979f0e" />
|
||||
@@ -138,7 +138,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -164,7 +164,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="#" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -241,7 +241,7 @@
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="API Reference" href="reference.html" /><link rel="prev" title="Code Examples" href="examples.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -288,7 +288,7 @@ report issues, suggest functionality and contribute code to Reticulum.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Communications Hardware" href="hardware.html" /><link rel="prev" title="Using Reticulum on Your System" href="using.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -337,12 +337,12 @@ needs to be purchased.</p>
|
||||
</section>
|
||||
<section id="introduction-basic-functionality">
|
||||
<span id="understanding-basicfunctionality"></span><h2>Introduction & Basic Functionality<a class="headerlink" href="#introduction-basic-functionality" title="Permalink to this heading">#</a></h2>
|
||||
<p>Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at it’s
|
||||
<p>Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at its
|
||||
core a <em>message oriented</em> system. It is suited for both local point-to-point or point-to-multipoint
|
||||
scenarios where all nodes are within range of each other, as well as scenarios where packets need
|
||||
to be transported over multiple hops in a complex network to reach the recipient.</p>
|
||||
<p>Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
|
||||
Reticulum uses the singular concept of <em>destinations</em>. Any application using Reticulum as it’s
|
||||
Reticulum uses the singular concept of <em>destinations</em>. Any application using Reticulum as its
|
||||
networking stack will need to create one or more destinations to receive data, and know the
|
||||
destinations it needs to send data to.</p>
|
||||
<p>All destinations in Reticulum are _represented_ as a 16 byte hash. This hash is derived from truncating a full
|
||||
@@ -442,7 +442,7 @@ addressable, because their public keys will differ.</p></li>
|
||||
</ul>
|
||||
<p>In actual use of <em>single</em> destination naming, it is advisable not to use any uniquely identifying
|
||||
features in aspect naming. Aspect names should be general terms describing what kind of destination
|
||||
is represented. The uniquely identifying aspect is always achieved by the appending the public key,
|
||||
is represented. The uniquely identifying aspect is always achieved by appending the public key,
|
||||
which expands the destination into a uniquely identifiable one. Reticulum does this automatically.</p>
|
||||
<p>Any destination on a Reticulum network can be addressed and reached simply by knowing its
|
||||
destination hash (and public key, but if the public key is not known, it can be requested from the
|
||||
@@ -468,7 +468,7 @@ indirectly, but must first be established through a <em>single</em> destination.
|
||||
</dl>
|
||||
</li>
|
||||
</ul>
|
||||
<p>To communicate with a <em>single</em> destination, you need to know it’s public key. Any method for
|
||||
<p>To communicate with a <em>single</em> destination, you need to know its public key. Any method for
|
||||
obtaining the public key is valid, but Reticulum includes a simple mechanism for making other
|
||||
nodes aware of your destinations public key, called the <em>announce</em>. It is also possible to request
|
||||
an unknown public key from the network, as all transport instances serve as a distributed ledger
|
||||
@@ -509,7 +509,7 @@ certain pattern. This will be detailed in the section
|
||||
protocols such as IP, where an address is always expected to stay within the network segment it was assigned in.
|
||||
This limitation does not exist in Reticulum, and any destination is <em>completely portable</em> over the entire topography
|
||||
of the network, and <em>can even be moved to other Reticulum networks</em> than the one it was created in, and
|
||||
still become reachable. To update it’s reachability, a destination simply needs to send an announce on any
|
||||
still become reachable. To update its reachability, a destination simply needs to send an announce on any
|
||||
networks it is part of. After a short while, it will be globally reachable in the network.</p>
|
||||
<p>Seeing how <em>single</em> destinations are always tied to a private/public key pair leads us to the next topic.</p>
|
||||
</section>
|
||||
@@ -565,7 +565,7 @@ is the default setting.</p>
|
||||
</section>
|
||||
<section id="the-announce-mechanism-in-detail">
|
||||
<span id="understanding-announce"></span><h3>The Announce Mechanism in Detail<a class="headerlink" href="#the-announce-mechanism-in-detail" title="Permalink to this heading">#</a></h3>
|
||||
<p>When an <em>announce</em> for a destination is transmitted by from a Reticulum instance, it will be forwarded by
|
||||
<p>When an <em>announce</em> for a destination is transmitted by a Reticulum instance, it will be forwarded by
|
||||
any transport node receiving it, but according to some specific rules:</p>
|
||||
<ul>
|
||||
<li><div class="line-block">
|
||||
@@ -590,7 +590,7 @@ announces is set at 2%, but can be configured on a per-interface basis.</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">If any given interface does not have enough bandwidth available for retransmitting the announce,
|
||||
the announce will be assigned a priority inversely proportional to it’s hop count, and be inserted
|
||||
the announce will be assigned a priority inversely proportional to its hop count, and be inserted
|
||||
into a queue managed by the interface.</div>
|
||||
</div>
|
||||
</li>
|
||||
@@ -639,7 +639,7 @@ expect. Reticulum offers two ways to do this.</p>
|
||||
<li><div class="line-block">
|
||||
<div class="line">A packet is always created with an associated destination and some payload data. When the packet is sent
|
||||
to a <em>single</em> destination type, Reticulum will automatically create an ephemeral encryption key, perform
|
||||
an ECDH key exchange with the destinations public key, and encrypt the information.</div>
|
||||
an ECDH key exchange with the destination’s public key, and encrypt the information.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
@@ -665,8 +665,8 @@ packet.</div>
|
||||
<li><div class="line-block">
|
||||
<div class="line">Once the packet has been received and decrypted by the addressed destination, that destination can opt
|
||||
to <em>prove</em> its receipt of the packet. It does this by calculating the SHA-256 hash of the received packet,
|
||||
and signing this hash with it’s Ed25519 signing key. Transport nodes in the network can then direct this
|
||||
<em>proof</em> back to the packets origin, where the signature can be verified against the destinations known
|
||||
and signing this hash with its Ed25519 signing key. Transport nodes in the network can then direct this
|
||||
<em>proof</em> back to the packets origin, where the signature can be verified against the destination’s known
|
||||
public signing key.</div>
|
||||
</div>
|
||||
</li>
|
||||
@@ -689,7 +689,7 @@ forward the packet will take note of this <em>link request</em>.</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
<div class="line">Second, if the destination accepts the <em>link request</em> , it will send back a packet that proves the
|
||||
authenticity of it’s identity (and the receipt of the link request) to the initiating node. All
|
||||
authenticity of its identity (and the receipt of the link request) to the initiating node. All
|
||||
nodes that initially forwarded the packet will also be able to verify this proof, and thus
|
||||
accept the validity of the <em>link</em> throughout the network.</div>
|
||||
</div>
|
||||
@@ -1126,7 +1126,7 @@ those risks are acceptable to you.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -557,7 +557,7 @@ execute commands on remote systems over Reticulum, and to view returned command
|
||||
output.</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span># Run rnx on the listening system, specifying which identities
|
||||
# are allowed to execute commands
|
||||
rncp --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
rnx --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
|
||||
# From another system, run a command
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 "cat /proc/cpuinfo"
|
||||
@@ -757,7 +757,7 @@ WantedBy=multi-user.target
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Getting Started Fast" href="gettingstartedfast.html" /><link rel="prev" title="Reticulum Network Stack Manual" href="index.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.2.2, furo 2022.09.29"/>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.4.7 beta documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.5.1 beta documentation</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/styles/furo.css?digest=d81277517bee4d6b0349d71bb2661d4890b5617c" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/copybutton.css" />
|
||||
@@ -141,7 +141,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="header-center">
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.4.7 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.5.1 beta documentation</div></a>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<div class="theme-toggle-container theme-toggle-header">
|
||||
@@ -167,7 +167,7 @@
|
||||
<img class="sidebar-logo" src="_static/rns_logo_512.png" alt="Logo"/>
|
||||
</div>
|
||||
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.4.7 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.5.1 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder=Search name="q" aria-label="Search">
|
||||
@@ -354,10 +354,10 @@ network, and vice versa.</p>
|
||||
<h2>Caveat Emptor<a class="headerlink" href="#caveat-emptor" title="Permalink to this heading">#</a></h2>
|
||||
<p>Reticulum is an experimental networking stack, and should be considered as
|
||||
such. While it has been built with cryptography best-practices very foremost in
|
||||
mind, it has not been externally security audited, and there could very well be
|
||||
mind, it has not yet been externally security audited, and there could very well be
|
||||
privacy-breaking bugs. To be considered secure, Reticulum needs a thorough
|
||||
security review by independent cryptographers and security researchers. If you
|
||||
want to help out, or help sponsor an audit, please do get in touch.</p>
|
||||
want to help out with this, or can help sponsor an audit, please do get in touch.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -390,7 +390,7 @@ want to help out, or help sponsor an audit, please do get in touch.</p>
|
||||
<div class="bottom-of-page">
|
||||
<div class="left-details">
|
||||
<div class="copyright">
|
||||
Copyright © 2022, Mark Qvist
|
||||
Copyright © 2023, Mark Qvist
|
||||
</div>
|
||||
Generated with <a href="https://www.sphinx-doc.org/">Sphinx</a> and
|
||||
<a href="https://github.com/pradyunsg/furo">Furo</a>
|
||||
|
||||
+4
-1
@@ -10,9 +10,12 @@ sys.path.insert(0, os.path.abspath('../..'))
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
project = 'Reticulum Network Stack'
|
||||
copyright = '2022, Mark Qvist'
|
||||
copyright = '2023, Mark Qvist'
|
||||
author = 'Mark Qvist'
|
||||
|
||||
exec(open("../../RNS/_version.py", "r").read())
|
||||
version = __version__
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
import RNS
|
||||
release = RNS._version.__version__+" beta"
|
||||
|
||||
@@ -92,6 +92,28 @@ The *Request* example explores sendig requests and receiving responses.
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Request.py>`_.
|
||||
|
||||
.. _example-channel:
|
||||
|
||||
Channel
|
||||
=======
|
||||
|
||||
The *Channel* example explores using a ``Channel`` to send structured
|
||||
data between peers of a ``Link``.
|
||||
|
||||
.. literalinclude:: ../../Examples/Channel.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Channel.py>`_.
|
||||
|
||||
Buffer
|
||||
======
|
||||
|
||||
The *Buffer* example explores using buffered readers and writers to send
|
||||
binary data between peers of a ``Link``.
|
||||
|
||||
.. literalinclude:: ../../Examples/Buffer.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Buffer.py>`_.
|
||||
|
||||
.. _example-filetransfer:
|
||||
|
||||
Filetransfer
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
********************************************
|
||||
An Explanation of Reticulum for Human Beings
|
||||
********************************************
|
||||
|
||||
@@ -10,22 +10,45 @@ scenarios.
|
||||
Standalone Reticulum Installation
|
||||
=============================================
|
||||
If you simply want to install Reticulum and related utilities on a system,
|
||||
the easiest way is via ``pip``:
|
||||
the easiest way is via the ``pip`` package manager:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install rns
|
||||
|
||||
If you no not already have pip installed, you can install it using the package manager
|
||||
If you do not already have pip installed, you can install it using the package manager
|
||||
of your system with a command like ``sudo apt install python3-pip``,
|
||||
``sudo pamac install python-pip`` or similar. You can also dowload the Reticulum release
|
||||
wheels from GitHub, or other release channels, and install them offline using ``pip``:
|
||||
``sudo pamac install python-pip`` or similar.
|
||||
|
||||
You can also dowload the Reticulum release wheels from GitHub, or other release channels,
|
||||
and install them offline using ``pip``:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install ./rns-0.4.6-py3-none-any.whl
|
||||
pip install ./rns-0.5.1-py3-none-any.whl
|
||||
|
||||
|
||||
Resolving Dependency & Installation Issues
|
||||
=============================================
|
||||
On some platforms, there may not be binary packages available for all dependencies, and
|
||||
``pip`` installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:
|
||||
|
||||
.. code::
|
||||
|
||||
# Debian / Ubuntu / Derivatives
|
||||
sudo apt install build-essential
|
||||
|
||||
# Arch / Manjaro / Derivatives
|
||||
sudo pamac install base-devel
|
||||
|
||||
# Fedora
|
||||
sudo dnf groupinstall "Development Tools" "Development Libraries"
|
||||
|
||||
With the base development packages installed, ``pip`` should be able to compile any missing
|
||||
dependencies from source, and complete installation even on platforms that don't have pre-
|
||||
compiled packages available.
|
||||
|
||||
Try Using a Reticulum-based Program
|
||||
=============================================
|
||||
|
||||
@@ -42,6 +65,13 @@ transceivers or infrastructure just to try it out. Launching the programs on sep
|
||||
devices connected to the same WiFi network is enough to get started, and physical
|
||||
radio interfaces can then be added later.
|
||||
|
||||
Remote Shell
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The `rnsh <https://github.com/acehoss/rnsh>`_ program lets you establish fully interactive
|
||||
remote shell sessions over Reticulum. It also allows you to pipe any program to or from a
|
||||
remote system, and is similar to how ``ssh`` works.
|
||||
|
||||
Nomad Network
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
@@ -115,8 +145,8 @@ Creating a Network With Reticulum
|
||||
=============================================
|
||||
To create a network, you will need to specify one or more *interfaces* for
|
||||
Reticulum to use. This is done in the Reticulum configuration file, which by
|
||||
default is located at ``~/.reticulum/config``. You can edit this file by hand,
|
||||
or use the interactive ``rnsconfig`` utility.
|
||||
default is located at ``~/.reticulum/config``. You can get an example
|
||||
configuration file with all options via ``rnsd --exampleconfig``.
|
||||
|
||||
When Reticulum is started for the first time, it will create a default
|
||||
configuration file, with one active interface. This default interface uses
|
||||
@@ -197,7 +227,7 @@ by adding one of the following interfaces to your ``.reticulum/config`` file:
|
||||
target_port = 4965
|
||||
|
||||
# TCP/IP interface to the Frankfurt hub
|
||||
[[RNS Testnet Dublin]]
|
||||
[[RNS Testnet Frankfurt]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = frankfurt.connect.reticulum.network
|
||||
@@ -234,7 +264,7 @@ chapter for a guide. If you prefer purchasing a ready-made unit, you can refer t
|
||||
refer to these additional external resources:
|
||||
|
||||
* `How To Make Your Own RNodes <https://unsigned.io/how-to-make-your-own-rnodes/>`_
|
||||
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-t-beam-and-lora32-devices/>`_
|
||||
* `Installing RNode Firmware on Compatible LoRa Devices <https://unsigned.io/installing-rnode-firmware-on-supported-devices/>`_
|
||||
* `Private, Secure and Uncensorable Messaging Over a LoRa Mesh <https://unsigned.io/private-messaging-over-lora/>`_
|
||||
* `RNode Firmware <https://github.com/markqvist/RNode_Firmware/>`_
|
||||
|
||||
@@ -261,7 +291,7 @@ For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip3 install pyserial netifaces
|
||||
pip3 install pyserial
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
@@ -276,7 +306,7 @@ don't use pip, but try this recipe:
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
pip3 install cryptography pyserial netifaces
|
||||
pip3 install cryptography pyserial
|
||||
|
||||
# Clone repository
|
||||
git clone https://github.com/markqvist/Reticulum.git
|
||||
@@ -313,30 +343,14 @@ the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussi
|
||||
or ask one of the developers or maintainers for a good place to start.
|
||||
|
||||
|
||||
Reticulum on ARM64
|
||||
Platform-Specific Install Notes
|
||||
==============================================
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Reticulum on Raspberry Pi
|
||||
==============================================
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
Reticulum on Android
|
||||
==============================================
|
||||
Android
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Reticulum can be used on Android in different ways. The easiest way to get
|
||||
started is using an app like `Sideband <https://unsigned.io/sideband>`_.
|
||||
|
||||
@@ -402,6 +416,74 @@ It is also possible to include Reticulum in apps compiled and distributed as
|
||||
Android APKs. A detailed tutorial and example source code will be included
|
||||
here at a later point. Until then you can use the `Sideband source code <https://github.com/markqvist/sideband>`_ as an example and startig point.
|
||||
|
||||
|
||||
ARM64
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.
|
||||
|
||||
|
||||
Debian Bookworm
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Debian released after April 2023, it is no longer possible
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
|
||||
Ubuntu Lunar
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On versions of Ubuntu released after April 2023, it is no longer possible
|
||||
to use ``pip`` to install packages onto your system. Unfortunately, you will need to
|
||||
use the replacement ``pipx`` command instead, which places installed packages in an
|
||||
isolated environment. This should not negatively affect Reticulum, but installation
|
||||
via this method is not fully tested yet.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install pipx
|
||||
sudo apt install pipx
|
||||
|
||||
# Make installed programs available on the command line
|
||||
pipx ensurepath
|
||||
|
||||
# Install Reticulum
|
||||
pipx install rns
|
||||
|
||||
|
||||
Pure-Python Reticulum
|
||||
==============================================
|
||||
In some rare cases, and on more obscure system types, it is not possible to
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
******************************
|
||||
Reticulum Network Stack Manual
|
||||
******************************
|
||||
|
||||
This manual aims to provide you with all the information you need to
|
||||
understand Reticulum, build networks or develop programs using it, or
|
||||
to participate in the development of Reticulum itself.
|
||||
|
||||
.. only:: html
|
||||
.. only:: builder_html
|
||||
|
||||
This manual is also available in `PDF <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.pdf>`_ and `EPUB <https://github.com/markqvist/Reticulum/releases/latest/download/Reticulum.Manual.epub>`_ formats.
|
||||
|
||||
.. only:: builder_html
|
||||
|
||||
Table Of Contents
|
||||
=================
|
||||
|
||||
@@ -121,6 +121,76 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
|
||||
.. autoclass:: RNS.Resource(data, link, advertise=True, auto_compress=True, callback=None, progress_callback=None, timeout=None)
|
||||
:members:
|
||||
|
||||
.. _api-channel:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| Channel |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
Channel
|
||||
-------
|
||||
|
||||
.. autoclass:: RNS.Channel.Channel()
|
||||
:members:
|
||||
|
||||
.. _api-messsagebase:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| MessageBase |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
MessageBase
|
||||
-----------
|
||||
|
||||
.. autoclass:: RNS.MessageBase()
|
||||
:members:
|
||||
|
||||
.. _api-buffer:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| Buffer |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
Buffer
|
||||
------
|
||||
|
||||
.. autoclass:: RNS.Buffer
|
||||
:members:
|
||||
|
||||
.. _api-rawchannelreader:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| RawChannelReader |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
RawChannelReader
|
||||
----------------
|
||||
|
||||
.. autoclass:: RNS.RawChannelReader
|
||||
:members: __init__, add_ready_callback, remove_ready_callback
|
||||
|
||||
.. _api-rawchannelwriter:
|
||||
|
||||
.. only:: html
|
||||
|
||||
|start-h3| RawChannelWriter |end-h3|
|
||||
|
||||
.. only:: latex
|
||||
|
||||
RawChannelWriter
|
||||
----------------
|
||||
|
||||
.. autoclass:: RNS.RawChannelWriter
|
||||
:members: __init__
|
||||
|
||||
.. _api-transport:
|
||||
|
||||
.. only:: html
|
||||
|
||||
@@ -107,13 +107,13 @@ guide the design of Reticulum:
|
||||
Introduction & Basic Functionality
|
||||
==================================
|
||||
|
||||
Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at it’s
|
||||
Reticulum is a networking stack suited for high-latency, low-bandwidth links. Reticulum is at its
|
||||
core a *message oriented* system. It is suited for both local point-to-point or point-to-multipoint
|
||||
scenarios where all nodes are within range of each other, as well as scenarios where packets need
|
||||
to be transported over multiple hops in a complex network to reach the recipient.
|
||||
|
||||
Reticulum does away with the idea of addresses and ports known from IP, TCP and UDP. Instead
|
||||
Reticulum uses the singular concept of *destinations*. Any application using Reticulum as it’s
|
||||
Reticulum uses the singular concept of *destinations*. Any application using Reticulum as its
|
||||
networking stack will need to create one or more destinations to receive data, and know the
|
||||
destinations it needs to send data to.
|
||||
|
||||
@@ -220,7 +220,7 @@ packet.
|
||||
|
||||
In actual use of *single* destination naming, it is advisable not to use any uniquely identifying
|
||||
features in aspect naming. Aspect names should be general terms describing what kind of destination
|
||||
is represented. The uniquely identifying aspect is always achieved by the appending the public key,
|
||||
is represented. The uniquely identifying aspect is always achieved by appending the public key,
|
||||
which expands the destination into a uniquely identifiable one. Reticulum does this automatically.
|
||||
|
||||
Any destination on a Reticulum network can be addressed and reached simply by knowing its
|
||||
@@ -239,7 +239,7 @@ To recap, the different destination types should be used in the following situat
|
||||
* **Plain**
|
||||
When plain-text communication is desirable, for example when broadcasting information, or for local discovery purposes.
|
||||
|
||||
To communicate with a *single* destination, you need to know it’s public key. Any method for
|
||||
To communicate with a *single* destination, you need to know its public key. Any method for
|
||||
obtaining the public key is valid, but Reticulum includes a simple mechanism for making other
|
||||
nodes aware of your destinations public key, called the *announce*. It is also possible to request
|
||||
an unknown public key from the network, as all transport instances serve as a distributed ledger
|
||||
@@ -287,7 +287,7 @@ In Reticulum, destinations are allowed to move around the network at will. This
|
||||
protocols such as IP, where an address is always expected to stay within the network segment it was assigned in.
|
||||
This limitation does not exist in Reticulum, and any destination is *completely portable* over the entire topography
|
||||
of the network, and *can even be moved to other Reticulum networks* than the one it was created in, and
|
||||
still become reachable. To update it's reachability, a destination simply needs to send an announce on any
|
||||
still become reachable. To update its reachability, a destination simply needs to send an announce on any
|
||||
networks it is part of. After a short while, it will be globally reachable in the network.
|
||||
|
||||
Seeing how *single* destinations are always tied to a private/public key pair leads us to the next topic.
|
||||
@@ -368,7 +368,7 @@ If it is a *Transport Node*, it should be given the configuration directive ``en
|
||||
The Announce Mechanism in Detail
|
||||
--------------------------------
|
||||
|
||||
When an *announce* for a destination is transmitted by from a Reticulum instance, it will be forwarded by
|
||||
When an *announce* for a destination is transmitted by a Reticulum instance, it will be forwarded by
|
||||
any transport node receiving it, but according to some specific rules:
|
||||
|
||||
|
||||
@@ -385,7 +385,7 @@ any transport node receiving it, but according to some specific rules:
|
||||
announces is set at 2%, but can be configured on a per-interface basis.
|
||||
|
||||
* | If any given interface does not have enough bandwidth available for retransmitting the announce,
|
||||
the announce will be assigned a priority inversely proportional to it's hop count, and be inserted
|
||||
the announce will be assigned a priority inversely proportional to its hop count, and be inserted
|
||||
into a queue managed by the interface.
|
||||
|
||||
* | When the interface has bandwidth available for processing an announce, it will prioritise announces
|
||||
@@ -431,7 +431,7 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
|
||||
|
||||
* | A packet is always created with an associated destination and some payload data. When the packet is sent
|
||||
to a *single* destination type, Reticulum will automatically create an ephemeral encryption key, perform
|
||||
an ECDH key exchange with the destinations public key, and encrypt the information.
|
||||
an ECDH key exchange with the destination's public key, and encrypt the information.
|
||||
|
||||
* | It is important to note that this key exchange does not require any network traffic. The sender already
|
||||
knows the public key of the destination from an earlier received *announce*, and can thus perform the ECDH
|
||||
@@ -447,8 +447,8 @@ For exchanges of small amounts of information, Reticulum offers the *Packet* API
|
||||
|
||||
* | Once the packet has been received and decrypted by the addressed destination, that destination can opt
|
||||
to *prove* its receipt of the packet. It does this by calculating the SHA-256 hash of the received packet,
|
||||
and signing this hash with it's Ed25519 signing key. Transport nodes in the network can then direct this
|
||||
*proof* back to the packets origin, where the signature can be verified against the destinations known
|
||||
and signing this hash with its Ed25519 signing key. Transport nodes in the network can then direct this
|
||||
*proof* back to the packets origin, where the signature can be verified against the destination's known
|
||||
public signing key.
|
||||
|
||||
* | In case the packet is addressed to a *group* destination type, the packet will be encrypted with the
|
||||
@@ -465,7 +465,7 @@ For exchanges of larger amounts of data, or when longer sessions of bidirectiona
|
||||
forward the packet will take note of this *link request*.
|
||||
|
||||
* | Second, if the destination accepts the *link request* , it will send back a packet that proves the
|
||||
authenticity of it’s identity (and the receipt of the link request) to the initiating node. All
|
||||
authenticity of its identity (and the receipt of the link request) to the initiating node. All
|
||||
nodes that initially forwarded the packet will also be able to verify this proof, and thus
|
||||
accept the validity of the *link* throughout the network.
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ output.
|
||||
|
||||
# Run rnx on the listening system, specifying which identities
|
||||
# are allowed to execute commands
|
||||
rncp --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
rnx --listen -a 941bed5e228775e5a8079fc38b1ccf3f -a 1b03013c25f1c2ca068a4f080b844a10
|
||||
|
||||
# From another system, run a command
|
||||
rnx 7a55144adf826958a9529a3bcf08b149 "cat /proc/cpuinfo"
|
||||
@@ -565,4 +565,4 @@ If you want to automatically start ``rnsd`` at boot, run:
|
||||
|
||||
.. code:: text
|
||||
|
||||
sudo systemctl enable rnsd
|
||||
sudo systemctl enable rnsd
|
||||
|
||||
@@ -162,7 +162,7 @@ Caveat Emptor
|
||||
==============
|
||||
Reticulum is an experimental networking stack, and should be considered as
|
||||
such. While it has been built with cryptography best-practices very foremost in
|
||||
mind, it has not been externally security audited, and there could very well be
|
||||
mind, it has not yet been externally security audited, and there could very well be
|
||||
privacy-breaking bugs. To be considered secure, Reticulum needs a thorough
|
||||
security review by independent cryptographers and security researchers. If you
|
||||
want to help out, or help sponsor an audit, please do get in touch.
|
||||
want to help out with this, or can help sponsor an audit, please do get in touch.
|
||||
|
||||
@@ -20,7 +20,10 @@ if pure_python:
|
||||
long_description = long_description.replace("</p>", "</p>"+pure_notice)
|
||||
else:
|
||||
pkg_name = "rns"
|
||||
requirements = ['cryptography>=3.4.7', 'pyserial>=3.5', 'netifaces']
|
||||
requirements = ['cryptography>=3.4.7', 'pyserial>=3.5']
|
||||
|
||||
excluded_modules = exclude=["tests.*", "tests"]
|
||||
packages = setuptools.find_packages(exclude=excluded_modules)
|
||||
|
||||
setuptools.setup(
|
||||
name=pkg_name,
|
||||
@@ -31,7 +34,7 @@ setuptools.setup(
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
url="https://reticulum.network/",
|
||||
packages=setuptools.find_packages(),
|
||||
packages=packages,
|
||||
classifiers=[
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
@@ -43,11 +46,12 @@ setuptools.setup(
|
||||
'rnstatus=RNS.Utilities.rnstatus:main',
|
||||
'rnprobe=RNS.Utilities.rnprobe:main',
|
||||
'rnpath=RNS.Utilities.rnpath:main',
|
||||
'rnid=RNS.Utilities.rnid:main',
|
||||
'rncp=RNS.Utilities.rncp:main',
|
||||
'rnx=RNS.Utilities.rnx:main',
|
||||
'rnodeconf=RNS.Utilities.rnodeconf:main',
|
||||
]
|
||||
},
|
||||
install_requires=requirements,
|
||||
python_requires='>=3.6',
|
||||
python_requires='>=3.7',
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ from .hashes import TestSHA256
|
||||
from .hashes import TestSHA512
|
||||
from .identity import TestIdentity
|
||||
from .link import TestLink
|
||||
from .channel import TestChannel
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
@@ -0,0 +1,503 @@
|
||||
from __future__ import annotations
|
||||
import threading
|
||||
import RNS
|
||||
from RNS.Channel import MessageState, ChannelOutletBase, Channel, MessageBase
|
||||
import RNS.Buffer
|
||||
from RNS.vendor import umsgpack
|
||||
from typing import Callable
|
||||
import contextlib
|
||||
import typing
|
||||
import types
|
||||
import time
|
||||
import uuid
|
||||
import unittest
|
||||
|
||||
|
||||
class Packet:
|
||||
timeout = 1.0
|
||||
|
||||
def __init__(self, raw: bytes):
|
||||
self.state = MessageState.MSGSTATE_NEW
|
||||
self.raw = raw
|
||||
self.packet_id = uuid.uuid4()
|
||||
self.tries = 0
|
||||
self.timeout_id = None
|
||||
self.lock = threading.RLock()
|
||||
self.instances = 0
|
||||
self.timeout_callback: Callable[[Packet], None] | None = None
|
||||
self.delivered_callback: Callable[[Packet], None] | None = None
|
||||
|
||||
def set_timeout(self, callback: Callable[[Packet], None] | None, timeout: float):
|
||||
with self.lock:
|
||||
if timeout is not None:
|
||||
self.timeout = timeout
|
||||
self.timeout_callback = callback
|
||||
|
||||
|
||||
def send(self):
|
||||
self.tries += 1
|
||||
self.state = MessageState.MSGSTATE_SENT
|
||||
|
||||
def elapsed(timeout: float, timeout_id: uuid.uuid4):
|
||||
with self.lock:
|
||||
self.instances += 1
|
||||
try:
|
||||
time.sleep(timeout)
|
||||
with self.lock:
|
||||
if self.timeout_id == timeout_id:
|
||||
self.timeout_id = None
|
||||
self.state = MessageState.MSGSTATE_FAILED
|
||||
if self.timeout_callback:
|
||||
self.timeout_callback(self)
|
||||
finally:
|
||||
with self.lock:
|
||||
self.instances -= 1
|
||||
|
||||
self.timeout_id = uuid.uuid4()
|
||||
threading.Thread(target=elapsed, name="Packet Timeout", args=[self.timeout, self.timeout_id],
|
||||
daemon=True).start()
|
||||
|
||||
def clear_timeout(self):
|
||||
self.timeout_id = None
|
||||
|
||||
def set_delivered_callback(self, callback: Callable[[Packet], None]):
|
||||
self.delivered_callback = callback
|
||||
|
||||
def delivered(self):
|
||||
with self.lock:
|
||||
self.state = MessageState.MSGSTATE_DELIVERED
|
||||
self.timeout_id = None
|
||||
if self.delivered_callback:
|
||||
self.delivered_callback(self)
|
||||
|
||||
|
||||
class ChannelOutletTest(ChannelOutletBase):
|
||||
def get_packet_state(self, packet: Packet) -> MessageState:
|
||||
return packet.state
|
||||
|
||||
def set_packet_timeout_callback(self, packet: Packet, callback: Callable[[Packet], None] | None,
|
||||
timeout: float | None = None):
|
||||
packet.set_timeout(callback, timeout)
|
||||
|
||||
def set_packet_delivered_callback(self, packet: Packet, callback: Callable[[Packet], None] | None):
|
||||
packet.set_delivered_callback(callback)
|
||||
|
||||
def get_packet_id(self, packet: Packet) -> any:
|
||||
return packet.packet_id
|
||||
|
||||
def __init__(self, mdu: int, rtt: float):
|
||||
self.link_id = uuid.uuid4()
|
||||
self.timeout_callbacks = 0
|
||||
self._mdu = mdu
|
||||
self._rtt = rtt
|
||||
self._usable = True
|
||||
self.packets = []
|
||||
self.lock = threading.RLock()
|
||||
self.packet_callback: Callable[[ChannelOutletBase, bytes], None] | None = None
|
||||
|
||||
def send(self, raw: bytes) -> Packet:
|
||||
with self.lock:
|
||||
packet = Packet(raw)
|
||||
packet.send()
|
||||
self.packets.append(packet)
|
||||
return packet
|
||||
|
||||
def resend(self, packet: Packet) -> Packet:
|
||||
with self.lock:
|
||||
packet.send()
|
||||
return packet
|
||||
|
||||
@property
|
||||
def mdu(self):
|
||||
return self._mdu
|
||||
|
||||
@property
|
||||
def rtt(self):
|
||||
return self._rtt
|
||||
|
||||
@property
|
||||
def is_usable(self):
|
||||
return self._usable
|
||||
|
||||
def timed_out(self):
|
||||
self.timeout_callbacks += 1
|
||||
|
||||
def __str__(self):
|
||||
return str(self.link_id)
|
||||
|
||||
|
||||
class MessageTest(MessageBase):
|
||||
MSGTYPE = 0xabcd
|
||||
|
||||
def __init__(self):
|
||||
self.id = str(uuid.uuid4())
|
||||
self.data = "test"
|
||||
self.not_serialized = str(uuid.uuid4())
|
||||
|
||||
def pack(self) -> bytes:
|
||||
return umsgpack.packb((self.id, self.data))
|
||||
|
||||
def unpack(self, raw):
|
||||
self.id, self.data = umsgpack.unpackb(raw)
|
||||
|
||||
|
||||
class SystemMessage(MessageBase):
|
||||
MSGTYPE = 0xf000
|
||||
|
||||
def pack(self) -> bytes:
|
||||
return bytes()
|
||||
|
||||
def unpack(self, raw):
|
||||
pass
|
||||
|
||||
|
||||
class ProtocolHarness(contextlib.AbstractContextManager):
|
||||
def __init__(self, rtt: float):
|
||||
self.outlet = ChannelOutletTest(mdu=500, rtt=rtt)
|
||||
self.channel = Channel(self.outlet)
|
||||
Packet.timeout = self.channel._get_packet_timeout_time(1)
|
||||
|
||||
def cleanup(self):
|
||||
self.channel._shutdown()
|
||||
|
||||
def __exit__(self, __exc_type: typing.Type[BaseException], __exc_value: BaseException,
|
||||
__traceback: types.TracebackType) -> bool:
|
||||
# self._log.debug(f"__exit__({__exc_type}, {__exc_value}, {__traceback})")
|
||||
self.cleanup()
|
||||
return False
|
||||
|
||||
|
||||
class TestChannel(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
print("")
|
||||
self.rtt = 0.01
|
||||
self.h = ProtocolHarness(self.rtt)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.h.cleanup()
|
||||
|
||||
def test_send_one_retry(self):
|
||||
print("Channel test one retry")
|
||||
message = MessageTest()
|
||||
|
||||
self.assertEqual(0, len(self.h.outlet.packets))
|
||||
|
||||
envelope = self.h.channel.send(message)
|
||||
|
||||
self.assertIsNotNone(envelope)
|
||||
self.assertIsNotNone(envelope.raw)
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
self.assertIsNotNone(envelope.packet)
|
||||
self.assertTrue(envelope in self.h.channel._tx_ring)
|
||||
self.assertTrue(envelope.tracked)
|
||||
|
||||
packet = self.h.outlet.packets[0]
|
||||
|
||||
self.assertEqual(envelope.packet, packet)
|
||||
self.assertEqual(1, envelope.tries)
|
||||
self.assertEqual(1, packet.tries)
|
||||
self.assertEqual(1, packet.instances)
|
||||
self.assertEqual(MessageState.MSGSTATE_SENT, packet.state)
|
||||
self.assertEqual(envelope.raw, packet.raw)
|
||||
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(1) * 1.1)
|
||||
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
self.assertEqual(2, envelope.tries)
|
||||
self.assertEqual(2, packet.tries)
|
||||
self.assertEqual(1, packet.instances)
|
||||
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(2) * 1.1)
|
||||
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
self.assertEqual(self.h.outlet.packets[0], packet)
|
||||
self.assertEqual(3, envelope.tries)
|
||||
self.assertEqual(3, packet.tries)
|
||||
self.assertEqual(1, packet.instances)
|
||||
self.assertEqual(MessageState.MSGSTATE_SENT, packet.state)
|
||||
|
||||
packet.delivered()
|
||||
|
||||
self.assertEqual(MessageState.MSGSTATE_DELIVERED, packet.state)
|
||||
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(3) * 1.1)
|
||||
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
self.assertEqual(3, envelope.tries)
|
||||
self.assertEqual(3, packet.tries)
|
||||
self.assertEqual(0, packet.instances)
|
||||
self.assertFalse(envelope.tracked)
|
||||
|
||||
def test_send_timeout(self):
|
||||
print("Channel test retry count exceeded")
|
||||
message = MessageTest()
|
||||
|
||||
self.assertEqual(0, len(self.h.outlet.packets))
|
||||
|
||||
envelope = self.h.channel.send(message)
|
||||
|
||||
self.assertIsNotNone(envelope)
|
||||
self.assertIsNotNone(envelope.raw)
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
self.assertIsNotNone(envelope.packet)
|
||||
self.assertTrue(envelope in self.h.channel._tx_ring)
|
||||
self.assertTrue(envelope.tracked)
|
||||
|
||||
packet = self.h.outlet.packets[0]
|
||||
|
||||
self.assertEqual(envelope.packet, packet)
|
||||
self.assertEqual(1, envelope.tries)
|
||||
self.assertEqual(1, packet.tries)
|
||||
self.assertEqual(1, packet.instances)
|
||||
self.assertEqual(MessageState.MSGSTATE_SENT, packet.state)
|
||||
self.assertEqual(envelope.raw, packet.raw)
|
||||
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(1))
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(2))
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(3))
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(4))
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(5) * 1.1)
|
||||
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
self.assertEqual(5, envelope.tries)
|
||||
self.assertEqual(5, packet.tries)
|
||||
self.assertEqual(0, packet.instances)
|
||||
self.assertEqual(MessageState.MSGSTATE_FAILED, packet.state)
|
||||
self.assertFalse(envelope.tracked)
|
||||
|
||||
def test_multiple_handler(self):
|
||||
print("Channel test multiple handler short circuit")
|
||||
|
||||
handler1_called = 0
|
||||
handler1_return = True
|
||||
handler2_called = 0
|
||||
|
||||
def handler1(msg: MessageBase):
|
||||
nonlocal handler1_called, handler1_return
|
||||
self.assertIsInstance(msg, MessageTest)
|
||||
handler1_called += 1
|
||||
return handler1_return
|
||||
|
||||
def handler2(msg: MessageBase):
|
||||
nonlocal handler2_called
|
||||
self.assertIsInstance(msg, MessageTest)
|
||||
handler2_called += 1
|
||||
|
||||
message = MessageTest()
|
||||
self.h.channel.register_message_type(MessageTest)
|
||||
self.h.channel.add_message_handler(handler1)
|
||||
self.h.channel.add_message_handler(handler2)
|
||||
envelope = RNS.Channel.Envelope(self.h.outlet, message, sequence=0)
|
||||
raw = envelope.pack()
|
||||
self.h.channel._receive(raw)
|
||||
|
||||
self.assertEqual(1, handler1_called)
|
||||
self.assertEqual(0, handler2_called)
|
||||
|
||||
handler1_return = False
|
||||
envelope = RNS.Channel.Envelope(self.h.outlet, message, sequence=1)
|
||||
raw = envelope.pack()
|
||||
self.h.channel._receive(raw)
|
||||
|
||||
self.assertEqual(2, handler1_called)
|
||||
self.assertEqual(1, handler2_called)
|
||||
|
||||
def test_system_message_check(self):
|
||||
print("Channel test register system message")
|
||||
with self.assertRaises(RNS.Channel.ChannelException):
|
||||
self.h.channel.register_message_type(SystemMessage)
|
||||
self.h.channel._register_message_type(SystemMessage, is_system_type=True)
|
||||
|
||||
|
||||
def eat_own_dog_food(self, message: MessageBase, checker: typing.Callable[[MessageBase], None]):
|
||||
decoded: [MessageBase] = []
|
||||
|
||||
def handle_message(message: MessageBase):
|
||||
decoded.append(message)
|
||||
|
||||
self.h.channel.register_message_type(message.__class__)
|
||||
self.h.channel.add_message_handler(handle_message)
|
||||
self.assertEqual(len(self.h.outlet.packets), 0)
|
||||
|
||||
envelope = self.h.channel.send(message)
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(1) * 0.5)
|
||||
|
||||
self.assertIsNotNone(envelope)
|
||||
self.assertIsNotNone(envelope.raw)
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
self.assertIsNotNone(envelope.packet)
|
||||
self.assertTrue(envelope in self.h.channel._tx_ring)
|
||||
self.assertTrue(envelope.tracked)
|
||||
|
||||
packet = self.h.outlet.packets[0]
|
||||
|
||||
self.assertEqual(envelope.packet, packet)
|
||||
self.assertEqual(1, envelope.tries)
|
||||
self.assertEqual(1, packet.tries)
|
||||
self.assertEqual(1, packet.instances)
|
||||
self.assertEqual(MessageState.MSGSTATE_SENT, packet.state)
|
||||
self.assertEqual(envelope.raw, packet.raw)
|
||||
|
||||
packet.delivered()
|
||||
|
||||
self.assertEqual(MessageState.MSGSTATE_DELIVERED, packet.state)
|
||||
|
||||
time.sleep(self.h.channel._get_packet_timeout_time(1))
|
||||
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
self.assertEqual(1, envelope.tries)
|
||||
self.assertEqual(1, packet.tries)
|
||||
self.assertEqual(0, packet.instances)
|
||||
self.assertFalse(envelope.tracked)
|
||||
|
||||
self.assertEqual(len(self.h.outlet.packets), 1)
|
||||
self.assertEqual(MessageState.MSGSTATE_DELIVERED, packet.state)
|
||||
self.assertFalse(envelope.tracked)
|
||||
self.assertEqual(0, len(decoded))
|
||||
|
||||
self.h.channel._receive(packet.raw)
|
||||
|
||||
self.assertEqual(1, len(decoded))
|
||||
|
||||
rx_message = decoded[0]
|
||||
|
||||
self.assertIsNotNone(rx_message)
|
||||
self.assertIsInstance(rx_message, message.__class__)
|
||||
checker(rx_message)
|
||||
|
||||
def test_send_receive_message_test(self):
|
||||
print("Channel test send and receive message")
|
||||
message = MessageTest()
|
||||
|
||||
def check(rx_message: MessageBase):
|
||||
self.assertIsInstance(rx_message, message.__class__)
|
||||
self.assertEqual(message.id, rx_message.id)
|
||||
self.assertEqual(message.data, rx_message.data)
|
||||
self.assertNotEqual(message.not_serialized, rx_message.not_serialized)
|
||||
|
||||
self.eat_own_dog_food(message, check)
|
||||
|
||||
def test_buffer_small_bidirectional(self):
|
||||
data = "Hello\n"
|
||||
with RNS.Buffer.create_bidirectional_buffer(0, 0, self.h.channel) as buffer:
|
||||
count = buffer.write(data.encode("utf-8"))
|
||||
buffer.flush()
|
||||
|
||||
self.assertEqual(len(data), count)
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
|
||||
packet = self.h.outlet.packets[0]
|
||||
self.h.channel._receive(packet.raw)
|
||||
result = buffer.readline()
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(len(result), len(data))
|
||||
|
||||
decoded = result.decode("utf-8")
|
||||
|
||||
self.assertEqual(data, decoded)
|
||||
|
||||
def test_buffer_big(self):
|
||||
writer = RNS.Buffer.create_writer(15, self.h.channel)
|
||||
reader = RNS.Buffer.create_reader(15, self.h.channel)
|
||||
data = "01234556789"*1024 # 10 KB
|
||||
count = 0
|
||||
write_finished = False
|
||||
|
||||
def write_thread():
|
||||
nonlocal count, write_finished
|
||||
count = writer.write(data.encode("utf-8"))
|
||||
writer.flush()
|
||||
writer.close()
|
||||
write_finished = True
|
||||
threading.Thread(target=write_thread, name="Write Thread", daemon=True).start()
|
||||
|
||||
while not write_finished or next(filter(lambda x: x.state != MessageState.MSGSTATE_DELIVERED,
|
||||
self.h.outlet.packets), None) is not None:
|
||||
with self.h.outlet.lock:
|
||||
for packet in self.h.outlet.packets:
|
||||
if packet.state != MessageState.MSGSTATE_DELIVERED:
|
||||
self.h.channel._receive(packet.raw)
|
||||
packet.delivered()
|
||||
time.sleep(0.0001)
|
||||
|
||||
self.assertEqual(len(data), count)
|
||||
|
||||
read_finished = False
|
||||
result = bytes()
|
||||
|
||||
def read_thread():
|
||||
nonlocal read_finished, result
|
||||
result = reader.read()
|
||||
read_finished = True
|
||||
threading.Thread(target=read_thread, name="Read Thread", daemon=True).start()
|
||||
|
||||
timeout_at = time.time() + 7
|
||||
while not read_finished and time.time() < timeout_at:
|
||||
time.sleep(0.001)
|
||||
|
||||
self.assertTrue(read_finished)
|
||||
self.assertEqual(len(data), len(result))
|
||||
|
||||
decoded = result.decode("utf-8")
|
||||
|
||||
self.assertSequenceEqual(data, decoded)
|
||||
|
||||
def test_buffer_small_with_callback(self):
|
||||
callbacks = 0
|
||||
last_cb_value = None
|
||||
|
||||
def callback(ready: int):
|
||||
nonlocal callbacks, last_cb_value
|
||||
callbacks += 1
|
||||
last_cb_value = ready
|
||||
|
||||
data = "Hello\n"
|
||||
with RNS.RawChannelWriter(0, self.h.channel) as writer, RNS.RawChannelReader(0, self.h.channel) as reader:
|
||||
reader.add_ready_callback(callback)
|
||||
count = writer.write(data.encode("utf-8"))
|
||||
writer.flush()
|
||||
|
||||
self.assertEqual(len(data), count)
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
|
||||
packet = self.h.outlet.packets[0]
|
||||
self.h.channel._receive(packet.raw)
|
||||
packet.delivered()
|
||||
|
||||
self.assertEqual(1, callbacks)
|
||||
self.assertEqual(len(data), last_cb_value)
|
||||
|
||||
result = reader.readline()
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertEqual(len(result), len(data))
|
||||
|
||||
decoded = result.decode("utf-8")
|
||||
|
||||
self.assertEqual(data, decoded)
|
||||
self.assertEqual(1, len(self.h.outlet.packets))
|
||||
|
||||
result = reader.read(1)
|
||||
|
||||
self.assertIsNone(result)
|
||||
self.assertTrue(self.h.channel.is_ready_to_send())
|
||||
|
||||
writer.close()
|
||||
|
||||
self.assertEqual(2, len(self.h.outlet.packets))
|
||||
|
||||
packet = self.h.outlet.packets[1]
|
||||
self.h.channel._receive(packet.raw)
|
||||
packet.delivered()
|
||||
|
||||
result = reader.read(1)
|
||||
|
||||
self.assertIsNotNone(result)
|
||||
self.assertTrue(len(result) == 0)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
+219
@@ -4,8 +4,14 @@ import subprocess
|
||||
import shlex
|
||||
import threading
|
||||
import time
|
||||
from unittest import skipIf
|
||||
import RNS
|
||||
import os
|
||||
from tests.channel import MessageTest
|
||||
from RNS.Channel import MessageBase
|
||||
from RNS.Buffer import StreamDataMessage
|
||||
from RNS.Interfaces.LocalInterface import LocalClientInterface
|
||||
from math import ceil
|
||||
|
||||
APP_NAME = "rns_unit_tests"
|
||||
|
||||
@@ -346,6 +352,203 @@ class TestLink(unittest.TestCase):
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
def test_10_channel_round_trip(self):
|
||||
global c_rns
|
||||
init_rns(self)
|
||||
print("")
|
||||
print("Channel round trip test")
|
||||
|
||||
# TODO: Load this from public bytes only
|
||||
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
|
||||
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
|
||||
|
||||
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
|
||||
|
||||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(1)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
received = []
|
||||
|
||||
def handle_message(message: MessageBase):
|
||||
received.append(message)
|
||||
|
||||
test_message = MessageTest()
|
||||
test_message.data = "Hello"
|
||||
|
||||
channel = l1.get_channel()
|
||||
channel.register_message_type(MessageTest)
|
||||
channel.add_message_handler(handle_message)
|
||||
channel.send(test_message)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
self.assertEqual(1, len(received))
|
||||
|
||||
rx_message = received[0]
|
||||
|
||||
self.assertIsInstance(rx_message, MessageTest)
|
||||
self.assertEqual("Hello back", rx_message.data)
|
||||
self.assertEqual(test_message.id, rx_message.id)
|
||||
self.assertNotEqual(test_message.not_serialized, rx_message.not_serialized)
|
||||
self.assertEqual(1, len(l1._channel._rx_ring))
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
self.assertEqual(0, len(l1._channel._rx_ring))
|
||||
|
||||
def test_11_buffer_round_trip(self):
|
||||
global c_rns
|
||||
init_rns(self)
|
||||
print("")
|
||||
print("Buffer round trip test")
|
||||
|
||||
# TODO: Load this from public bytes only
|
||||
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
|
||||
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
|
||||
|
||||
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
|
||||
|
||||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
time.sleep(1)
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
buffer = None
|
||||
|
||||
received = []
|
||||
def handle_data(ready_bytes: int):
|
||||
data = buffer.read(ready_bytes)
|
||||
received.append(data)
|
||||
|
||||
channel = l1.get_channel()
|
||||
buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, handle_data)
|
||||
|
||||
buffer.write("Hi there".encode("utf-8"))
|
||||
buffer.flush()
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
self.assertEqual(1 , len(received))
|
||||
|
||||
rx_message = received[0].decode("utf-8")
|
||||
|
||||
self.assertEqual("Hi there back at you", rx_message)
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
|
||||
def test_12_buffer_round_trip_big(self, local_bitrate = None):
|
||||
global c_rns
|
||||
init_rns(self)
|
||||
print("")
|
||||
print("Buffer round trip test")
|
||||
|
||||
local_interface = next(filter(lambda iface: isinstance(iface, LocalClientInterface), RNS.Transport.interfaces), None)
|
||||
self.assertIsNotNone(local_interface)
|
||||
original_bitrate = local_interface.bitrate
|
||||
|
||||
try:
|
||||
if local_bitrate is not None:
|
||||
local_interface.bitrate = local_bitrate
|
||||
local_interface._force_bitrate = True
|
||||
print("Forcing local bitrate of " + str(local_bitrate) + " bps (" + str(round(local_bitrate/8, 0)) + " B/s)")
|
||||
|
||||
# TODO: Load this from public bytes only
|
||||
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
|
||||
self.assertEqual(id1.hash, bytes.fromhex(fixed_keys[0][1]))
|
||||
|
||||
dest = RNS.Destination(id1, RNS.Destination.OUT, RNS.Destination.SINGLE, APP_NAME, "link", "establish")
|
||||
|
||||
self.assertEqual(dest.hash, bytes.fromhex("fb48da0e82e6e01ba0c014513f74540d"))
|
||||
|
||||
l1 = RNS.Link(dest)
|
||||
# delay a reasonable time for link to come up at current bitrate
|
||||
link_sleep = max(RNS.Link.MDU * 3 / local_interface.bitrate * 8, 2)
|
||||
timeout_at = time.time() + link_sleep
|
||||
print("Waiting " + str(round(link_sleep, 1)) + " sec for link to come up")
|
||||
while l1.status != RNS.Link.ACTIVE and time.time() < timeout_at:
|
||||
time.sleep(0.01)
|
||||
|
||||
self.assertEqual(l1.status, RNS.Link.ACTIVE)
|
||||
|
||||
buffer = None
|
||||
received = []
|
||||
def handle_data(ready_bytes: int):
|
||||
data = buffer.read(ready_bytes)
|
||||
received.append(data)
|
||||
|
||||
channel = l1.get_channel()
|
||||
buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, handle_data)
|
||||
|
||||
# try to make the message big enough to split across packets, but
|
||||
# small enough to make the test complete in a reasonable amount of time
|
||||
seed_text = "0123456789"
|
||||
message = seed_text*ceil(min(max(local_interface.bitrate / 8,
|
||||
StreamDataMessage.MAX_DATA_LEN * 2 / len(seed_text)),
|
||||
1000))
|
||||
# the return message will have an appendage string " back at you"
|
||||
# for every StreamDataMessage that arrives. To verify, we need
|
||||
# to insert that string every MAX_DATA_LEN and also at the end.
|
||||
expected_rx_message = ""
|
||||
for i in range(0, len(message)):
|
||||
if i > 0 and (i % StreamDataMessage.MAX_DATA_LEN) == 0:
|
||||
expected_rx_message += " back at you"
|
||||
expected_rx_message += message[i]
|
||||
expected_rx_message += " back at you"
|
||||
|
||||
# since the segments will be received at max length for a
|
||||
# StreamDataMessage, the appended text will end up in a
|
||||
# separate packet.
|
||||
expected_chunk_count = ceil(len(message)/StreamDataMessage.MAX_DATA_LEN * 2)
|
||||
print("Sending " + str(len(message)) + " bytes, receiving " + str(len(expected_rx_message)) + " bytes, " +
|
||||
"expecting " + str(expected_chunk_count) + " chunks of " + str(StreamDataMessage.MAX_DATA_LEN) + " bytes")
|
||||
transfer_sleep = max(expected_chunk_count * 3 * c_rns.MTU / local_interface.bitrate * 8, 3)
|
||||
print("Will take up to " + str(round(transfer_sleep, 0)) + " seconds to transfer")
|
||||
expected_ready_time = time.time() + transfer_sleep
|
||||
buffer.write(message.encode("utf-8"))
|
||||
buffer.flush()
|
||||
# delay a reasonable time for the send and receive
|
||||
# a chunk each way plus a little more for a proof each way
|
||||
while time.time() < expected_ready_time and len(received) < expected_chunk_count:
|
||||
time.sleep(0.1)
|
||||
# sleep for at least one more chunk round trip in case there
|
||||
# are more chunks than expected
|
||||
if time.time() < expected_ready_time:
|
||||
time.sleep(max(c_rns.MTU * 2 / local_interface.bitrate * 8, 1))
|
||||
|
||||
# Why does this not always work out correctly?
|
||||
# self.assertEqual(expected_chunk_count, len(received))
|
||||
|
||||
data = bytearray()
|
||||
for rx in received:
|
||||
data.extend(rx)
|
||||
|
||||
rx_message = data.decode("utf-8")
|
||||
|
||||
self.assertEqual(len(expected_rx_message), len(rx_message))
|
||||
for i in range(0, len(expected_rx_message)):
|
||||
self.assertEqual(expected_rx_message[i], rx_message[i])
|
||||
self.assertEqual(expected_rx_message, rx_message)
|
||||
|
||||
l1.teardown()
|
||||
time.sleep(0.5)
|
||||
self.assertEqual(l1.status, RNS.Link.CLOSED)
|
||||
finally:
|
||||
local_interface.bitrate = original_bitrate
|
||||
local_interface._force_bitrate = False
|
||||
|
||||
# Run with
|
||||
# RUN_SLOW_TESTS=1 python tests/link.py TestLink.test_13_buffer_round_trip_big_slow
|
||||
# Or
|
||||
# make RUN_SLOW_TESTS=1 test
|
||||
@skipIf(int(os.getenv('RUN_SLOW_TESTS', 0)) < 1, "Not running slow tests")
|
||||
def test_13_buffer_round_trip_big_slow(self):
|
||||
self.test_12_buffer_round_trip_big(local_bitrate=410)
|
||||
|
||||
def size_str(self, num, suffix='B'):
|
||||
units = ['','K','M','G','T','P','E','Z']
|
||||
@@ -404,6 +607,22 @@ def targets(yp=False):
|
||||
link.set_resource_strategy(RNS.Link.ACCEPT_ALL)
|
||||
link.set_resource_started_callback(resource_started)
|
||||
link.set_resource_concluded_callback(resource_concluded)
|
||||
channel = link.get_channel()
|
||||
|
||||
def handle_message(message):
|
||||
message.data = message.data + " back"
|
||||
channel.send(message)
|
||||
channel.register_message_type(MessageTest)
|
||||
channel.add_message_handler(handle_message)
|
||||
|
||||
buffer = None
|
||||
|
||||
def handle_buffer(ready_bytes: int):
|
||||
data = buffer.read(ready_bytes)
|
||||
buffer.write((data.decode("utf-8") + " back at you").encode("utf-8"))
|
||||
buffer.flush()
|
||||
|
||||
buffer = RNS.Buffer.create_bidirectional_buffer(0, 0, channel, handle_buffer)
|
||||
|
||||
m_rns = RNS.Reticulum("./tests/rnsconfig")
|
||||
id1 = RNS.Identity.from_bytes(bytes.fromhex(fixed_keys[0][0]))
|
||||
|
||||
Reference in New Issue
Block a user