Compare commits
145 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d63bbcdc0a | |||
| c36c7186de | |||
| 6fec76205c | |||
| 715f4d9fcb | |||
| 8d7857c4e2 | |||
| c9a2b45368 | |||
| c57d927660 | |||
| 8d98c8751a | |||
| 527f6cc906 | |||
| a0d61f6441 | |||
| c5687f190b | |||
| 44d1f6d0e5 | |||
| ac09bc3567 | |||
| a41bce012b | |||
| 83a2999d29 | |||
| 4465fa9882 | |||
| ce974db084 | |||
| e6c1dc075b | |||
| 9602f67b06 | |||
| ef798e0d54 | |||
| 5cd8d229fb | |||
| d4808b7ff1 | |||
| 3dc8729e70 | |||
| f500a063dc | |||
| eca1e53b55 | |||
| 53226d7035 | |||
| 7363c9c821 | |||
| bb8b8b4f81 | |||
| 0f0f459321 | |||
| df887f6d63 | |||
| b526e3554c | |||
| 903ab53fc9 | |||
| f461a7827b | |||
| 62091b28b0 | |||
| 48045856bf | |||
| 6ba5efcb42 | |||
| a505441b98 | |||
| 976e5543e1 | |||
| fcc7b50ac6 | |||
| 72971d1aef | |||
| 9a8d46ab21 | |||
| 8adab7ee7d | |||
| b5bde99322 | |||
| 560c8e164c | |||
| e059363f1d | |||
| 4930477b99 | |||
| 312489e4dc | |||
| 43d8fdb423 | |||
| 1c56385473 | |||
| 787af92ade | |||
| 131dbd2813 | |||
| 9df81ce365 | |||
| 490a56450a | |||
| 52a5156304 | |||
| 538e7320fd | |||
| 2d351a59e9 | |||
| 2269d6cef9 | |||
| 813edc8b17 | |||
| 099e344996 | |||
| 42319a092d | |||
| cdee3b6191 | |||
| e41d8ff296 | |||
| 946bea8825 | |||
| ba856ea1c4 | |||
| 9a97195b8c | |||
| 3e4172b697 | |||
| 66163776c2 | |||
| 3dbde726c1 | |||
| 97ae4d74b3 | |||
| c71ece6b8e | |||
| 1e45a002e1 | |||
| 68e64523b5 | |||
| d9e6145034 | |||
| a91e67129e | |||
| 76362bad4a | |||
| 421b5ef32e | |||
| 8d61ee8a81 | |||
| 2329181c88 | |||
| 8ea0dc65c4 | |||
| bba67836f0 | |||
| a666bb6e73 | |||
| 7b7ebbec90 | |||
| 8b3523dee0 | |||
| 2901ed2bae | |||
| 34010c94d1 | |||
| a4b5248a4c | |||
| 75272d77a5 | |||
| d4ad4589dd | |||
| 8d45ad36eb | |||
| 2a0d411869 | |||
| b9421347ef | |||
| ffec78d49a | |||
| 356ae378f9 | |||
| 28e3919dbd | |||
| 58a19610c4 | |||
| 50b1eae380 | |||
| c119ef4273 | |||
| b506ca94d0 | |||
| a072a5b074 | |||
| 3a580e74de | |||
| 9a20a3929a | |||
| fe054fd03c | |||
| 4524a17e67 | |||
| 8a82d6bfeb | |||
| 971f5ffadd | |||
| 6a392fdb0f | |||
| b42e075be0 | |||
| 4bc8a0b69b | |||
| 9ef10a7b3e | |||
| 320704f812 | |||
| c5e5986b89 | |||
| 5c6ee07d66 | |||
| 3eb8d92028 | |||
| ef3baf2cd9 | |||
| f2f936d846 | |||
| 6599e210de | |||
| d21dda2830 | |||
| 6ac393bbcd | |||
| 0c04663942 | |||
| bfa216de54 | |||
| a4b1606921 | |||
| ad0db9c95c | |||
| 2fdcbec860 | |||
| dd889d16d4 | |||
| a11f14e75f | |||
| c32086c6f1 | |||
| 9d744e2317 | |||
| d64064691a | |||
| 54eaff203f | |||
| 2bf75f60bc | |||
| 3f64141455 | |||
| b4ac3df2d0 | |||
| 8193f3621c | |||
| 5166596375 | |||
| 063ea2bb7a | |||
| 625db2622d | |||
| a8bc468e21 | |||
| 95c4269869 | |||
| 65a40aefb6 | |||
| a840bd4aaf | |||
| 7f2154110c | |||
| 9bc957e442 | |||
| 6d5ef3a511 | |||
| dec9145d65 | |||
| b3536f16e8 |
@@ -0,0 +1,28 @@
|
||||
# This workflow will install Python dependencies, run tests and lint with a single version of Python
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
||||
|
||||
name: Test suite
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python 3.10
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: "3.10"
|
||||
- name: Test
|
||||
run: |
|
||||
make test
|
||||
@@ -1,6 +1,139 @@
|
||||
### 2024-10-10: RNS β 0.8.3
|
||||
|
||||
This release fixes a bug in resource transfer progress calculation, improves RNode error handling, and brings minor improvements to the `rncp` utility.
|
||||
|
||||
**Changes**
|
||||
- Fixed a bug in resource transfer progress calculations
|
||||
- Added physical layer transfer rate output option to `rncp`
|
||||
- Added save directory option to `rncp`
|
||||
- Improved path handling for the fetch-jail option of of `rncp`
|
||||
- Added error detection for modem communication timeouts on connected RNode devices
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
54ddab32769081045db5fe45b27492cc012bf2fad64bc65ed37011f3651469fb rns-0.8.3-py3-none-any.whl
|
||||
a04915111d65b05a5f2ef2687ed208813034196c0c5e711cb01e6db72faa23ef rnspure-0.8.3-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-10-06: RNS β 0.8.2
|
||||
|
||||
This release adds several new boards to `rnodeconf`, fixes a range of bugs and improves transport reliability.
|
||||
|
||||
Thanks to @jacobeva, @prusnak and @deavmi who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Added support for T-Beam Supreme devices to `rnodeconf`
|
||||
- Added support for T3S3 devices to `rnodeconf`
|
||||
- Added support for T-Deck devices to `rnodeconf`
|
||||
- Added support for new hardware error codes from connected RNodes
|
||||
- Added the ability to control the display on nRF52-based RNodes
|
||||
- Improved resource transfers over very slow links, by adding more suitable `MAX_WINDOW` cap if link speed is continously below threshold.
|
||||
- Improved `rnodeconf` flashing so manual resets for some devices are no longer required
|
||||
- Added edge case handling for receiving a link proof after the link had timed out and been closed, but before it having been purged from active links table
|
||||
- Updated supported hardware section of the manual with new boards
|
||||
- Tuned path request timing for roaming instances
|
||||
- Fixed a bug that caused RNS to fail to initialise in Termux on Android
|
||||
- Fixed a bug in RNodeInterface firmware version comparison
|
||||
- Fixed a bug in the serial framing of RNodeMultiInterface
|
||||
- Fixed a bug in sub-interface spawning of RNodeMultiInterface
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
db720a727a09c0c9d76288dec5a995a30146e65d6a4c5c034f47fb60a78f4962 rns-0.8.2-py3-none-any.whl
|
||||
ee412535edba48817551658247fb0c843d17e1c97cad9d2a819a7fc627c5ba28 rnspure-0.8.2-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-10-02: RNS β 0.8.1
|
||||
|
||||
This release adds BLE support to RNodeInterface, and support for configuring additional options to `rnodeconf`.
|
||||
|
||||
**Changes**
|
||||
- Added Bluetooth Low Energy support to RNodeInterface
|
||||
- Added RNode battery information to `rnstatus` output
|
||||
- Added display blanking configuration to `rnodeconf`
|
||||
- Added NeoPixel intensity configuration to `rnodeconf`
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
f4b6b99b67d6b33b8a4562e5d5d5ac54c76814fff26e6c7a79950b82bd80123f rns-0.8.1-py3-none-any.whl
|
||||
c2e540b4bf0f272bb51ae3e33a02f9c07f2619746d069d7ed83d88017bf7ea30 rnspure-0.8.1-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-09-25: RNS β 0.8.0
|
||||
|
||||
This maintenance release improves the interface statistics API, and updates documentation.
|
||||
|
||||
**Changes**
|
||||
- Added additional information to interface statistics
|
||||
- Updated documentation
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
fa5ff6d98230693be6805bb9a94585a6f54ec0af9cba15b771d4e676f140dc43 rns-0.8.0-py3-none-any.whl
|
||||
ba20f688b69ae861c8aced251e10242a358fea15da6c22df10d4fc8846c9bf48 rnspure-0.8.0-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-09-24: RNS β 0.7.9
|
||||
|
||||
This maintenance release improves transport reliability in certain (rare) cases.
|
||||
|
||||
**Changes**
|
||||
- Added handling of a transport edge-case
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
4c20c46df021d366386d497145024396f904666b0de22a92f9e5c937886ea39d rns-0.7.9-py3-none-any.whl
|
||||
97d26282df929eca732a15523bc9d7f66387a93ffd911e8063c94c3f8f6ad73c rnspure-0.7.9-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-09-18: RNS β 0.7.8
|
||||
|
||||
This maintenance release adds support for the openCom XL to `rnodeconf`, fixes a number of bugs, and also includes a few fine-tunings of timing parameters.
|
||||
|
||||
Thanks to @liamcottle and @jacobeva for contributing to this release!
|
||||
|
||||
**Changes**
|
||||
- Added interface prioritisation according to reported bitrate
|
||||
- Added support for openCom XL to `rnodeconf`
|
||||
- Added performance profiler to built-in debugging tools
|
||||
- Tuned link traffic timeouts
|
||||
- Fixed a module import error in AX25KissInterface
|
||||
- Fixed a missing exception on erroneous destination initialisation
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
33fb9443e3b327d1a9125baa52d8ec3208a089dda62f749b819e0a94c06730f9 rns-0.7.8-py3-none-any.whl
|
||||
cdced2adef4ead146239d0510fe2b9d62f69136bcd54b22d1080686fb56f9927 rnspure-0.7.8-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-09-09: RNS β 0.7.7
|
||||
|
||||
This release adds support for automatic encryption key ratcheting for all packets, not just those sent over Reticulum links. In practical terms, this adds forward secrecy to packets sent with the raw `Packet` API.
|
||||
|
||||
In this release, the ratchets feature must be enabled on a per-destination basis by calling the `enable_ratchets` method on the relevant destination. In a future release, ratchets may become the default option, but for backwards-compatibility, it is currently optional. For more information, read the API documentation.
|
||||
|
||||
**Please note!** Versions of RNS prior to `0.7.7` will not be able to pass announces for destinations with ratchets enabled! If you use applications that can use ratchets (for example, LXMF version `0.5.0` and up), it is important that you update all transport instances on your network to `0.7.7`.
|
||||
|
||||
Thanks to @deavmi, @faragher, @jacobeva, @jeremy and @jeremybox for contributing to this release!
|
||||
|
||||
**Changes**
|
||||
- Added key ratchet rotation and signalling
|
||||
- Added ratchet API to documentation
|
||||
- Added initial support for flashing T-Echo devices to `rnodeconf`
|
||||
- Added remote management config options to example config
|
||||
- Added automtic integration tests to source repository
|
||||
- Fixed a regression that caused RNS not to work on Python versions lower than 3.10
|
||||
- Fixed missing `establishment_rate` property init on Link objects
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
0a3ab6dc82567a19adabe737358daee3002b60beda8ac0bf228f2a0c134ff6d8 rns-0.7.7-py3-none-any.whl
|
||||
89b33fe9ab923139d3f5d43726d92817642be05a8c9d328c3becfc3c409e4b4b rnspure-0.7.7-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-05-18: RNS β 0.7.6
|
||||
|
||||
This release add support for RNodes with multiple radio transceivers, courtesy of @jacobeva. It also brings a number of functionality and performance improvements, and fixes several bugs.
|
||||
This release adds support for RNodes with multiple radio transceivers, courtesy of @jacobeva. It also brings a number of functionality and performance improvements, and fixes several bugs.
|
||||
|
||||
Thanks to @jacobeva, @faragher, @nathmo, @jschulthess and @liamcottle for contributing to this release!
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
# of the packet. #
|
||||
##########################################################
|
||||
|
||||
import os
|
||||
import argparse
|
||||
import RNS
|
||||
|
||||
@@ -28,19 +27,8 @@ def server(configpath):
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Load identity from file if it exist or randomley create
|
||||
if configpath:
|
||||
ifilepath = "%s/storage/identitiesy/%s" % (configpath,APP_NAME)
|
||||
else:
|
||||
ifilepath = "%s/storage/identities/%s" % (RNS.Reticulum.configdir,APP_NAME)
|
||||
if os.path.exists(ifilepath):
|
||||
# Load identity from file
|
||||
server_identity = RNS.Identity.from_file(ifilepath)
|
||||
RNS.log("loaded identity from file: "+ifilepath, RNS.LOG_VERBOSE)
|
||||
else:
|
||||
# Randomly create a new identity for our echo example
|
||||
server_identity = RNS.Identity()
|
||||
RNS.log("created new identity", RNS.LOG_VERBOSE)
|
||||
# Randomly create a new identity for our echo server
|
||||
server_identity = RNS.Identity()
|
||||
|
||||
# We create a destination that clients can query. We want
|
||||
# to be able to verify echo replies to our clients, so we
|
||||
@@ -340,4 +328,4 @@ if __name__ == "__main__":
|
||||
client(args.destination, configarg, timeout=timeoutarg)
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
exit()
|
||||
@@ -28,20 +28,8 @@ def server(configpath):
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# Load identity from file if it exist or randomley create
|
||||
if configpath:
|
||||
ifilepath = "%s/storage/identitiesy/%s" % (configpath,APP_NAME)
|
||||
else:
|
||||
ifilepath = "%s/storage/identities/%s" % (RNS.Reticulum.configdir,APP_NAME)
|
||||
RNS.log("ifilepath: %s" % ifilepath)
|
||||
if os.path.exists(ifilepath):
|
||||
# Load identity from file
|
||||
server_identity = RNS.Identity.from_file(ifilepath)
|
||||
RNS.log("loaded identity from file: "+ifilepath, RNS.LOG_VERBOSE)
|
||||
else:
|
||||
# Randomly create a new identity for our link example
|
||||
server_identity = RNS.Identity()
|
||||
RNS.log("created new identity", RNS.LOG_VERBOSE)
|
||||
# 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
|
||||
@@ -70,7 +58,7 @@ def server_loop(destination):
|
||||
" running, waiting for a connection."
|
||||
)
|
||||
|
||||
RNS.log("Hit enter to manually send an announce (Ctrl-C or \"quit\" to quit)")
|
||||
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
|
||||
@@ -80,12 +68,6 @@ def server_loop(destination):
|
||||
entered = input()
|
||||
destination.announce()
|
||||
RNS.log("Sent announce from "+RNS.prettyhexrep(destination.hash))
|
||||
if entered == "quit":
|
||||
if latest_client_link:
|
||||
latest_client_link.teardown()
|
||||
break
|
||||
print("")
|
||||
exit()
|
||||
|
||||
# When a client establishes a link to our server
|
||||
# destination, this function will be called with
|
||||
@@ -306,4 +288,4 @@ if __name__ == "__main__":
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
exit()
|
||||
@@ -0,0 +1,343 @@
|
||||
##########################################################
|
||||
# This RNS example demonstrates a simple client/server #
|
||||
# echo utility that uses ratchets to rotate encryption #
|
||||
# keys everytime an announce is sent. #
|
||||
##########################################################
|
||||
|
||||
import argparse
|
||||
import RNS
|
||||
|
||||
# Let's define an app name. We'll use this for all
|
||||
# destinations we create. Since this echo example
|
||||
# is part of a range of example utilities, we'll put
|
||||
# them all within the app namespace "example_utilities"
|
||||
APP_NAME = "example_utilities"
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Server Part #########################################
|
||||
##########################################################
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a server
|
||||
def server(configpath):
|
||||
global reticulum
|
||||
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# TODO: Remove
|
||||
RNS.loglevel = RNS.LOG_DEBUG
|
||||
|
||||
# Randomly create a new identity for our echo server
|
||||
server_identity = RNS.Identity()
|
||||
|
||||
# We create a destination that clients can query. We want
|
||||
# to be able to verify echo replies to our clients, so we
|
||||
# create a "single" destination that can receive encrypted
|
||||
# messages. This way the client can send a request and be
|
||||
# certain that no-one else than this destination was able
|
||||
# to read it.
|
||||
echo_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.IN,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"ratchet",
|
||||
"echo",
|
||||
"request"
|
||||
)
|
||||
|
||||
# Enable ratchets on the destination by providing a file
|
||||
# path to store ratchets. In this example, we will just
|
||||
# use a temporary file, but in real-world applications,
|
||||
# it's extremely important to keep this file secure, since
|
||||
# it contains encryption keys for the destination.
|
||||
destination_hexhash = RNS.hexrep(echo_destination.hash, delimit=False)
|
||||
echo_destination.enable_ratchets(f"/tmp/{destination_hexhash}.ratchets")
|
||||
|
||||
# We configure the destination to automatically prove all
|
||||
# packets addressed to it. By doing this, RNS will automatically
|
||||
# generate a proof for each incoming packet and transmit it
|
||||
# back to the sender of that packet.
|
||||
echo_destination.set_proof_strategy(RNS.Destination.PROVE_ALL)
|
||||
|
||||
# Tell the destination which function in our program to
|
||||
# run when a packet is received. We do this so we can
|
||||
# print a log message when the server receives a request
|
||||
echo_destination.set_packet_callback(server_callback)
|
||||
|
||||
# Everything's ready!
|
||||
# Let's Wait for client requests or user input
|
||||
announceLoop(echo_destination)
|
||||
|
||||
|
||||
def announceLoop(destination):
|
||||
# Let the user know that everything is ready
|
||||
RNS.log(
|
||||
"Ratcheted echo server "+
|
||||
RNS.prettyhexrep(destination.hash)+
|
||||
" running, 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))
|
||||
|
||||
|
||||
def server_callback(message, packet):
|
||||
global reticulum
|
||||
|
||||
# Tell the user that we received an echo request, and
|
||||
# that we are going to send a reply to the requester.
|
||||
# Sending the proof is handled automatically, since we
|
||||
# set up the destination to prove all incoming packets.
|
||||
|
||||
reception_stats = ""
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
reception_rssi = reticulum.get_packet_rssi(packet.packet_hash)
|
||||
reception_snr = reticulum.get_packet_snr(packet.packet_hash)
|
||||
|
||||
if reception_rssi != None:
|
||||
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
||||
|
||||
if reception_snr != None:
|
||||
reception_stats += " [SNR "+str(reception_snr)+" dBm]"
|
||||
|
||||
else:
|
||||
if packet.rssi != None:
|
||||
reception_stats += " [RSSI "+str(packet.rssi)+" dBm]"
|
||||
|
||||
if packet.snr != None:
|
||||
reception_stats += " [SNR "+str(packet.snr)+" dB]"
|
||||
|
||||
RNS.log("Received packet from echo client, proof sent"+reception_stats)
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Client Part #########################################
|
||||
##########################################################
|
||||
|
||||
# This initialisation is executed when the users chooses
|
||||
# to run as a client
|
||||
def client(destination_hexhash, configpath, timeout=None):
|
||||
global reticulum
|
||||
|
||||
# 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 Exception as e:
|
||||
RNS.log("Invalid destination entered. Check your input!")
|
||||
RNS.log(str(e)+"\n")
|
||||
exit()
|
||||
|
||||
# We must first initialise Reticulum
|
||||
reticulum = RNS.Reticulum(configpath)
|
||||
|
||||
# We override the loglevel to provide feedback when
|
||||
# an announce is received
|
||||
if RNS.loglevel < RNS.LOG_INFO:
|
||||
RNS.loglevel = RNS.LOG_INFO
|
||||
|
||||
# Tell the user that the client is ready!
|
||||
RNS.log(
|
||||
"Echo client ready, hit enter to send echo request to "+
|
||||
destination_hexhash+
|
||||
" (Ctrl-C to quit)"
|
||||
)
|
||||
|
||||
# We enter a loop that runs until the user exits.
|
||||
# If the user hits enter, we will try to send an
|
||||
# echo request to the destination specified on the
|
||||
# command line.
|
||||
while True:
|
||||
input()
|
||||
|
||||
# Let's first check if RNS knows a path to the destination.
|
||||
# If it does, we'll load the server identity and create a packet
|
||||
if RNS.Transport.has_path(destination_hash):
|
||||
|
||||
# To address the server, we need to know it's public
|
||||
# key, so we check if Reticulum knows this destination.
|
||||
# This is done by calling the "recall" method of the
|
||||
# Identity module. If the destination is known, it will
|
||||
# return an Identity instance that can be used in
|
||||
# outgoing destinations.
|
||||
server_identity = RNS.Identity.recall(destination_hash)
|
||||
|
||||
# We got the correct identity instance from the
|
||||
# recall method, so let's create an outgoing
|
||||
# destination. We use the naming convention:
|
||||
# example_utilities.ratchet.echo.request
|
||||
# This matches the naming we specified in the
|
||||
# server part of the code.
|
||||
request_destination = RNS.Destination(
|
||||
server_identity,
|
||||
RNS.Destination.OUT,
|
||||
RNS.Destination.SINGLE,
|
||||
APP_NAME,
|
||||
"ratchet",
|
||||
"echo",
|
||||
"request"
|
||||
)
|
||||
|
||||
# The destination is ready, so let's create a packet.
|
||||
# We set the destination to the request_destination
|
||||
# that was just created, and the only data we add
|
||||
# is a random hash.
|
||||
echo_request = RNS.Packet(request_destination, RNS.Identity.get_random_hash())
|
||||
|
||||
# Send the packet! If the packet is successfully
|
||||
# sent, it will return a PacketReceipt instance.
|
||||
packet_receipt = echo_request.send()
|
||||
|
||||
# If the user specified a timeout, we set this
|
||||
# timeout on the packet receipt, and configure
|
||||
# a callback function, that will get called if
|
||||
# the packet times out.
|
||||
if timeout != None:
|
||||
packet_receipt.set_timeout(timeout)
|
||||
packet_receipt.set_timeout_callback(packet_timed_out)
|
||||
|
||||
# We can then set a delivery callback on the receipt.
|
||||
# This will get automatically called when a proof for
|
||||
# this specific packet is received from the destination.
|
||||
packet_receipt.set_delivery_callback(packet_delivered)
|
||||
|
||||
# Tell the user that the echo request was sent
|
||||
RNS.log("Sent echo request to "+RNS.prettyhexrep(request_destination.hash))
|
||||
else:
|
||||
# If we do not know this destination, tell the
|
||||
# user to wait for an announce to arrive.
|
||||
RNS.log("Destination is not yet known. Requesting path...")
|
||||
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
|
||||
# receives a proof packet.
|
||||
def packet_delivered(receipt):
|
||||
global reticulum
|
||||
|
||||
if receipt.status == RNS.PacketReceipt.DELIVERED:
|
||||
rtt = receipt.get_rtt()
|
||||
if (rtt >= 1):
|
||||
rtt = round(rtt, 3)
|
||||
rttstring = str(rtt)+" seconds"
|
||||
else:
|
||||
rtt = round(rtt*1000, 3)
|
||||
rttstring = str(rtt)+" milliseconds"
|
||||
|
||||
reception_stats = ""
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
reception_rssi = reticulum.get_packet_rssi(receipt.proof_packet.packet_hash)
|
||||
reception_snr = reticulum.get_packet_snr(receipt.proof_packet.packet_hash)
|
||||
|
||||
if reception_rssi != None:
|
||||
reception_stats += " [RSSI "+str(reception_rssi)+" dBm]"
|
||||
|
||||
if reception_snr != None:
|
||||
reception_stats += " [SNR "+str(reception_snr)+" dB]"
|
||||
|
||||
else:
|
||||
if receipt.proof_packet != None:
|
||||
if receipt.proof_packet.rssi != None:
|
||||
reception_stats += " [RSSI "+str(receipt.proof_packet.rssi)+" dBm]"
|
||||
|
||||
if receipt.proof_packet.snr != None:
|
||||
reception_stats += " [SNR "+str(receipt.proof_packet.snr)+" dB]"
|
||||
|
||||
RNS.log(
|
||||
"Valid reply received from "+
|
||||
RNS.prettyhexrep(receipt.destination.hash)+
|
||||
", round-trip time is "+rttstring+
|
||||
reception_stats
|
||||
)
|
||||
|
||||
# This function is called if a packet times out.
|
||||
def packet_timed_out(receipt):
|
||||
if receipt.status == RNS.PacketReceipt.FAILED:
|
||||
RNS.log("Packet "+RNS.prettyhexrep(receipt.hash)+" timed out")
|
||||
|
||||
|
||||
##########################################################
|
||||
#### Program Startup #####################################
|
||||
##########################################################
|
||||
|
||||
# This part of the program gets run at startup,
|
||||
# and parses input from the user, and then starts
|
||||
# the desired program mode.
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Simple ratcheted echo server and client utility")
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--server",
|
||||
action="store_true",
|
||||
help="wait for incoming packets from clients"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--timeout",
|
||||
action="store",
|
||||
metavar="s",
|
||||
default=None,
|
||||
help="set a reply timeout in seconds",
|
||||
type=float
|
||||
)
|
||||
|
||||
parser.add_argument("--config",
|
||||
action="store",
|
||||
default=None,
|
||||
help="path to alternative Reticulum config directory",
|
||||
type=str
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"destination",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="hexadecimal hash of the server destination",
|
||||
type=str
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.server:
|
||||
configarg=None
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
server(configarg)
|
||||
else:
|
||||
if args.config:
|
||||
configarg = args.config
|
||||
else:
|
||||
configarg = None
|
||||
|
||||
if args.timeout:
|
||||
timeoutarg = float(args.timeout)
|
||||
else:
|
||||
timeoutarg = None
|
||||
|
||||
if (args.destination == None):
|
||||
print("")
|
||||
parser.print_help()
|
||||
print("")
|
||||
else:
|
||||
client(args.destination, configarg, timeout=timeoutarg)
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
@@ -2,7 +2,7 @@ all: release
|
||||
|
||||
test:
|
||||
@echo Running tests...
|
||||
python -m tests.all
|
||||
python3 -m tests.all
|
||||
|
||||
clean:
|
||||
@echo Cleaning...
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Reticulum Network Stack β <img align="right" src="https://static.pepy.tech/personalized-badge/rns?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Installs"/>
|
||||
Reticulum Network Stack β <img align="right" src="https://static.pepy.tech/personalized-badge/rns?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Installs" style="padding-left:10px"/><a href="https://github.com/markqvist/reticulum/actions/workflows/python-app.yml"><img align="right" src="https://github.com/markqvist/reticulum/actions/workflows/python-app.yml/badge.svg"/></a>
|
||||
==========
|
||||
|
||||
<p align="center"><img width="200" src="https://raw.githubusercontent.com/markqvist/Reticulum/master/docs/source/graphics/rns_logo_512.png"></p>
|
||||
@@ -41,21 +41,23 @@ For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ
|
||||
|
||||
## Notable Features
|
||||
- Coordination-less globally unique addressing and identification
|
||||
- Fully self-configuring multi-hop routing
|
||||
- Fully self-configuring multi-hop routing over heterogeneous carriers
|
||||
- Initiator anonymity, communicate without revealing your identity
|
||||
- Reticulum does not include source addresses on any packets
|
||||
- Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
|
||||
- Forward Secrecy with ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
- The foundational Reticulum Identity Keys are 512-bit Elliptic Curve keysets
|
||||
- Forward Secrecy is available for all communication types, both for single packets and over links
|
||||
- Reticulum uses the following format for encrypted tokens:
|
||||
- Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
- Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
|
||||
- AES-128 in CBC mode with PKCS7 padding
|
||||
- HMAC using SHA256 for authentication
|
||||
- IVs are generated through os.urandom()
|
||||
- Unforgeable packet delivery confirmations
|
||||
- A variety of supported interface types
|
||||
- A large variety of supported interface types
|
||||
- An intuitive and easy-to-use API
|
||||
- Reliable and efficient transfer of arbitrary amounts of data
|
||||
- Reticulum can handle a few bytes of data or files of many gigabytes
|
||||
- Sequencing, transfer coordination and checksumming are automatic
|
||||
- Sequencing, compression, transfer coordination and checksumming are automatic
|
||||
- The API is very easy to use, and provides transfer progress
|
||||
- Lightweight, flexible and expandable Request/Response mechanism
|
||||
- Efficient link establishment
|
||||
@@ -298,17 +300,20 @@ Are certain features in the development roadmap are important to you or your
|
||||
organisation? Make them a reality quickly by sponsoring their implementation.
|
||||
|
||||
## Cryptographic Primitives
|
||||
Reticulum uses a simple suite of efficient, strong and modern cryptographic
|
||||
Reticulum uses a simple suite of efficient, strong and well-tested cryptographic
|
||||
primitives, with widely available implementations that can be used both on
|
||||
general-purpose CPUs and on microcontrollers. The necessary primitives are:
|
||||
general-purpose CPUs and on microcontrollers. The utilised primitives are:
|
||||
|
||||
- Ed25519 for signatures
|
||||
- X22519 for ECDH key exchanges
|
||||
- Reticulum Identity Keys are 512-bit Curve25519 keysets
|
||||
- A 256-bit Ed25519 key for signatures
|
||||
- A 256-bit X22519 key for ECDH key exchanges
|
||||
- HKDF for key derivation
|
||||
- Modified Fernet for encrypted tokens
|
||||
- AES-128 in CBC mode
|
||||
- HMAC for message authentication
|
||||
- No Fernet version and timestamp fields
|
||||
- Encrypted tokens are based on the [Fernet spec](https://github.com/fernet/spec/)
|
||||
- Ephemeral keys derived from an ECDH key exchange on Curve25519
|
||||
- AES-128 in CBC mode with PKCS7 padding
|
||||
- HMAC using SHA256 for message authentication
|
||||
- IVs are generated through os.urandom()
|
||||
- No Fernet version and timestamp metadata fields
|
||||
- SHA-256
|
||||
- SHA-512
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors
|
||||
# Copyright (c) 2016-2024 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
|
||||
@@ -20,11 +20,14 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
import os
|
||||
import math
|
||||
import time
|
||||
import threading
|
||||
import RNS
|
||||
|
||||
from RNS.Cryptography import Fernet
|
||||
from .vendor import umsgpack as umsgpack
|
||||
|
||||
class Callbacks:
|
||||
def __init__(self):
|
||||
@@ -38,14 +41,14 @@ class Destination:
|
||||
instances are used both to create outgoing and incoming endpoints. The
|
||||
destination type will decide if encryption, and what type, is used in
|
||||
communication with the endpoint. A destination can also announce its
|
||||
presence on the network, which will also distribute necessary keys for
|
||||
presence on the network, which will distribute necessary keys for
|
||||
encrypted communication with it.
|
||||
|
||||
:param identity: An instance of :ref:`RNS.Identity<api-identity>`. Can hold only public keys for an outgoing destination, or holding private keys for an ingoing.
|
||||
:param direction: ``RNS.Destination.IN`` or ``RNS.Destination.OUT``.
|
||||
:param type: ``RNS.Destination.SINGLE``, ``RNS.Destination.GROUP`` or ``RNS.Destination.PLAIN``.
|
||||
:param app_name: A string specifying the app name.
|
||||
:param \*aspects: Any non-zero number of string arguments.
|
||||
:param \\*aspects: Any non-zero number of string arguments.
|
||||
"""
|
||||
|
||||
# Constants
|
||||
@@ -71,6 +74,16 @@ class Destination:
|
||||
|
||||
PR_TAG_WINDOW = 30
|
||||
|
||||
RATCHET_COUNT = 512
|
||||
"""
|
||||
The default number of generated ratchet keys a destination will retain, if it has ratchets enabled.
|
||||
"""
|
||||
|
||||
RATCHET_INTERVAL = 30*60
|
||||
"""
|
||||
The minimum interval between rotating ratchet keys, in seconds.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def expand_name(identity, app_name, *aspects):
|
||||
"""
|
||||
@@ -137,6 +150,14 @@ class Destination:
|
||||
self.type = type
|
||||
self.direction = direction
|
||||
self.proof_strategy = Destination.PROVE_NONE
|
||||
self.ratchets = None
|
||||
self.ratchets_path = None
|
||||
self.ratchet_interval = Destination.RATCHET_INTERVAL
|
||||
self.ratchet_file_lock = threading.Lock()
|
||||
self.retained_ratchets = Destination.RATCHET_COUNT
|
||||
self.latest_ratchet_time = None
|
||||
self.latest_ratchet_id = None
|
||||
self.__enforce_ratchets = False
|
||||
self.mtu = 0
|
||||
|
||||
self.path_responses = {}
|
||||
@@ -146,6 +167,9 @@ class Destination:
|
||||
identity = RNS.Identity()
|
||||
aspects = aspects+(identity.hexhash,)
|
||||
|
||||
if identity == None and direction == Destination.OUT and self.type != Destination.PLAIN:
|
||||
raise ValueError("Can't create outbound SINGLE destination without an identity")
|
||||
|
||||
if identity != None and self.type == Destination.PLAIN:
|
||||
raise TypeError("Selected destination type PLAIN cannot hold an identity")
|
||||
|
||||
@@ -170,6 +194,39 @@ class Destination:
|
||||
"""
|
||||
return "<"+self.name+"/"+self.hexhash+">"
|
||||
|
||||
def _clean_ratchets(self):
|
||||
if self.ratchets != None:
|
||||
if len (self.ratchets) > self.retained_ratchets:
|
||||
self.ratchets = self.ratchets[:Destination.RATCHET_COUNT]
|
||||
|
||||
def _persist_ratchets(self):
|
||||
try:
|
||||
with self.ratchet_file_lock:
|
||||
packed_ratchets = umsgpack.packb(self.ratchets)
|
||||
persisted_data = {"signature": self.sign(packed_ratchets), "ratchets": packed_ratchets}
|
||||
ratchets_file = open(self.ratchets_path, "wb")
|
||||
ratchets_file.write(umsgpack.packb(persisted_data))
|
||||
ratchets_file.close()
|
||||
except Exception as e:
|
||||
self.ratchets = None
|
||||
self.ratchets_path = None
|
||||
raise OSError("Could not write ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
def rotate_ratchets(self):
|
||||
if self.ratchets != None:
|
||||
now = time.time()
|
||||
if now > self.latest_ratchet_time+self.ratchet_interval:
|
||||
RNS.log("Rotating ratchets for "+str(self), RNS.LOG_DEBUG)
|
||||
new_ratchet = RNS.Identity._generate_ratchet()
|
||||
self.ratchets.insert(0, new_ratchet)
|
||||
self.latest_ratchet_time = now
|
||||
self._clean_ratchets()
|
||||
self._persist_ratchets()
|
||||
return True
|
||||
else:
|
||||
raise SystemError("Cannot rotate ratchet on "+str(self)+", ratchets are not enabled")
|
||||
|
||||
return False
|
||||
|
||||
def announce(self, app_data=None, path_response=False, attached_interface=None, tag=None, send=True):
|
||||
"""
|
||||
@@ -185,6 +242,7 @@ class Destination:
|
||||
if self.direction != Destination.IN:
|
||||
raise TypeError("Only IN destination types can be announced")
|
||||
|
||||
ratchet = b""
|
||||
now = time.time()
|
||||
stale_responses = []
|
||||
for entry_tag in self.path_responses:
|
||||
@@ -211,6 +269,11 @@ class Destination:
|
||||
destination_hash = self.hash
|
||||
random_hash = RNS.Identity.get_random_hash()[0:5]+int(time.time()).to_bytes(5, "big")
|
||||
|
||||
if self.ratchets != None:
|
||||
self.rotate_ratchets()
|
||||
ratchet = RNS.Identity._ratchet_public_bytes(self.ratchets[0])
|
||||
RNS.Identity._remember_ratchet(self.hash, ratchet)
|
||||
|
||||
if app_data == None and self.default_app_data != None:
|
||||
if isinstance(self.default_app_data, bytes):
|
||||
app_data = self.default_app_data
|
||||
@@ -219,13 +282,12 @@ class Destination:
|
||||
if isinstance(returned_app_data, bytes):
|
||||
app_data = returned_app_data
|
||||
|
||||
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash
|
||||
signed_data = self.hash+self.identity.get_public_key()+self.name_hash+random_hash+ratchet
|
||||
if app_data != None:
|
||||
signed_data += app_data
|
||||
|
||||
signature = self.identity.sign(signed_data)
|
||||
|
||||
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+signature
|
||||
announce_data = self.identity.get_public_key()+self.name_hash+random_hash+ratchet+signature
|
||||
|
||||
if app_data != None:
|
||||
announce_data += app_data
|
||||
@@ -237,8 +299,13 @@ class Destination:
|
||||
else:
|
||||
announce_context = RNS.Packet.NONE
|
||||
|
||||
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context, attached_interface = attached_interface)
|
||||
if ratchet:
|
||||
context_flag = RNS.Packet.FLAG_SET
|
||||
else:
|
||||
context_flag = RNS.Packet.FLAG_UNSET
|
||||
|
||||
announce_packet = RNS.Packet(self, announce_data, RNS.Packet.ANNOUNCE, context = announce_context,
|
||||
attached_interface = attached_interface, context_flag=context_flag)
|
||||
if send:
|
||||
announce_packet.send()
|
||||
else:
|
||||
@@ -298,7 +365,6 @@ class Destination:
|
||||
else:
|
||||
self.proof_strategy = proof_strategy
|
||||
|
||||
|
||||
def register_request_handler(self, path, response_generator = None, allow = ALLOW_NONE, allowed_list = None):
|
||||
"""
|
||||
Registers a request handler.
|
||||
@@ -320,7 +386,6 @@ class Destination:
|
||||
request_handler = [path, response_generator, allow, allowed_list]
|
||||
self.request_handlers[path_hash] = request_handler
|
||||
|
||||
|
||||
def deregister_request_handler(self, path):
|
||||
"""
|
||||
Deregisters a request handler.
|
||||
@@ -335,14 +400,13 @@ class Destination:
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def receive(self, packet):
|
||||
if packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||
plaintext = packet.data
|
||||
self.incoming_link_request(plaintext, packet)
|
||||
else:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
packet.ratchet_id = self.latest_ratchet_id
|
||||
if plaintext != None:
|
||||
if packet.packet_type == RNS.Packet.DATA:
|
||||
if self.callbacks.packet != None:
|
||||
@@ -351,13 +415,103 @@ class Destination:
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing receive callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
|
||||
def incoming_link_request(self, data, packet):
|
||||
if self.accept_link_requests:
|
||||
link = RNS.Link.validate_request(self, data, packet)
|
||||
if link != None:
|
||||
self.links.append(link)
|
||||
|
||||
def _reload_ratchets(self, ratchets_path):
|
||||
if os.path.isfile(ratchets_path):
|
||||
with self.ratchet_file_lock:
|
||||
try:
|
||||
ratchets_file = open(ratchets_path, "rb")
|
||||
persisted_data = umsgpack.unpackb(ratchets_file.read())
|
||||
if "signature" in persisted_data and "ratchets" in persisted_data:
|
||||
if self.identity.validate(persisted_data["signature"], persisted_data["ratchets"]):
|
||||
self.ratchets = umsgpack.unpackb(persisted_data["ratchets"])
|
||||
self.ratchets_path = ratchets_path
|
||||
else:
|
||||
raise KeyError("Invalid ratchet file signature")
|
||||
|
||||
except Exception as e:
|
||||
self.ratchets = None
|
||||
self.ratchets_path = None
|
||||
raise OSError("Could not read ratchet file contents for "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
else:
|
||||
RNS.log("No existing ratchet data found, initialising new ratchet file for "+str(self), RNS.LOG_DEBUG)
|
||||
self.ratchets = []
|
||||
self.ratchets_path = ratchets_path
|
||||
self._persist_ratchets()
|
||||
|
||||
def enable_ratchets(self, ratchets_path):
|
||||
"""
|
||||
Enables ratchets on the destination. When ratchets are enabled, Reticulum will automatically rotate
|
||||
the keys used to encrypt packets to this destination, and include the latest ratchet key in announces.
|
||||
|
||||
Enabling ratchets on a destination will provide forward secrecy for packets sent to that destination,
|
||||
even when sent outside a ``Link``. The normal Reticulum ``Link`` establishment procedure already performs
|
||||
its own ephemeral key exchange for each link establishment, which means that ratchets are not necessary
|
||||
to provide forward secrecy for links.
|
||||
|
||||
Enabling ratchets will have a small impact on announce size, adding 32 bytes to every sent announce.
|
||||
|
||||
:param ratchets_path: The path to a file to store ratchet data in.
|
||||
:returns: True if the operation succeeded, otherwise False.
|
||||
"""
|
||||
if ratchets_path != None:
|
||||
self.latest_ratchet_time = 0
|
||||
self._reload_ratchets(ratchets_path)
|
||||
|
||||
# TODO: Remove at some point
|
||||
RNS.log("Ratchets enabled on "+str(self), RNS.LOG_DEBUG)
|
||||
return True
|
||||
|
||||
else:
|
||||
raise ValueError("No ratchet file path specified for "+str(self))
|
||||
|
||||
def enforce_ratchets(self):
|
||||
"""
|
||||
When ratchet enforcement is enabled, this destination will never accept packets that use its
|
||||
base Identity key for encryption, but only accept packets encrypted with one of the retained
|
||||
ratchet keys.
|
||||
"""
|
||||
if self.ratchets != None:
|
||||
self.__enforce_ratchets = True
|
||||
RNS.log("Ratchets enforced on "+str(self), RNS.LOG_DEBUG)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_retained_ratchets(self, retained_ratchets):
|
||||
"""
|
||||
Sets the number of previously generated ratchet keys this destination will retain,
|
||||
and try to use when decrypting incoming packets. Defaults to ``Destination.RATCHET_COUNT``.
|
||||
|
||||
:param retained_ratchets: The number of generated ratchets to retain.
|
||||
:returns: True if the operation succeeded, False if not.
|
||||
"""
|
||||
if isinstance(retained_ratchets, int) and retained_ratchets > 0:
|
||||
self.retained_ratchets = retained_ratchets
|
||||
self._clean_ratchets()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def set_ratchet_interval(self, interval):
|
||||
"""
|
||||
Sets the minimum interval in seconds between ratchet key rotation.
|
||||
Defaults to ``Destination.RATCHET_INTERVAL``.
|
||||
|
||||
:param interval: The minimum interval in seconds.
|
||||
:returns: True if the operation succeeded, False if not.
|
||||
"""
|
||||
if isinstance(interval, int) and interval > 0:
|
||||
self.ratchet_interval = interval
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def create_keys(self):
|
||||
"""
|
||||
For a ``RNS.Destination.GROUP`` type destination, creates a new symmetric key.
|
||||
@@ -374,7 +528,6 @@ class Destination:
|
||||
self.prv_bytes = Fernet.generate_key()
|
||||
self.prv = Fernet(self.prv_bytes)
|
||||
|
||||
|
||||
def get_private_key(self):
|
||||
"""
|
||||
For a ``RNS.Destination.GROUP`` type destination, returns the symmetric private key.
|
||||
@@ -388,7 +541,6 @@ class Destination:
|
||||
else:
|
||||
return self.prv_bytes
|
||||
|
||||
|
||||
def load_private_key(self, key):
|
||||
"""
|
||||
For a ``RNS.Destination.GROUP`` type destination, loads a symmetric private key.
|
||||
@@ -412,7 +564,6 @@ class Destination:
|
||||
else:
|
||||
raise TypeError("A single destination holds keys through an Identity instance")
|
||||
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
"""
|
||||
Encrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
|
||||
@@ -424,7 +575,10 @@ class Destination:
|
||||
return plaintext
|
||||
|
||||
if self.type == Destination.SINGLE and self.identity != None:
|
||||
return self.identity.encrypt(plaintext)
|
||||
selected_ratchet = RNS.Identity.get_ratchet(self.hash)
|
||||
if selected_ratchet:
|
||||
self.latest_ratchet_id = RNS.Identity._get_ratchet_id(selected_ratchet)
|
||||
return self.identity.encrypt(plaintext, ratchet=selected_ratchet)
|
||||
|
||||
if self.type == Destination.GROUP:
|
||||
if hasattr(self, "prv") and self.prv != None:
|
||||
@@ -436,8 +590,6 @@ class Destination:
|
||||
else:
|
||||
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
||||
|
||||
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
"""
|
||||
Decrypts information for ``RNS.Destination.SINGLE`` or ``RNS.Destination.GROUP`` type destination.
|
||||
@@ -449,7 +601,28 @@ class Destination:
|
||||
return ciphertext
|
||||
|
||||
if self.type == Destination.SINGLE and self.identity != None:
|
||||
return self.identity.decrypt(ciphertext)
|
||||
if self.ratchets:
|
||||
decrypted = None
|
||||
try:
|
||||
decrypted = self.identity.decrypt(ciphertext, ratchets=self.ratchets, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
|
||||
except:
|
||||
decrypted = None
|
||||
|
||||
if not decrypted:
|
||||
try:
|
||||
RNS.log(f"Decryption with ratchets failed on {self}, reloading ratchets from storage and retrying", RNS.LOG_ERROR)
|
||||
self._reload_ratchets(self.ratchets_path)
|
||||
decrypted = self.identity.decrypt(ciphertext, ratchets=self.ratchets, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
|
||||
except Exception as e:
|
||||
RNS.log(f"Decryption still failing after ratchet reload. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
RNS.log("Decryption succeeded after ratchet reload", RNS.LOG_NOTICE)
|
||||
|
||||
return decrypted
|
||||
|
||||
else:
|
||||
return self.identity.decrypt(ciphertext, ratchets=None, enforce_ratchets=self.__enforce_ratchets, ratchet_id_receiver=self)
|
||||
|
||||
if self.type == Destination.GROUP:
|
||||
if hasattr(self, "prv") and self.prv != None:
|
||||
@@ -461,7 +634,6 @@ class Destination:
|
||||
else:
|
||||
raise ValueError("No private key held by GROUP destination. Did you create or load one?")
|
||||
|
||||
|
||||
def sign(self, message):
|
||||
"""
|
||||
Signs information for ``RNS.Destination.SINGLE`` type destination.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
# Copyright (c) 2016-2024 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
|
||||
@@ -26,6 +26,7 @@ import RNS
|
||||
import time
|
||||
import atexit
|
||||
import hashlib
|
||||
import threading
|
||||
|
||||
from .vendor import umsgpack as umsgpack
|
||||
|
||||
@@ -49,8 +50,20 @@ class Identity:
|
||||
|
||||
KEYSIZE = 256*2
|
||||
"""
|
||||
X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
|
||||
"""
|
||||
X.25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.
|
||||
"""
|
||||
|
||||
RATCHETSIZE = 256
|
||||
"""
|
||||
X.25519 ratchet key size in bits.
|
||||
"""
|
||||
|
||||
RATCHET_EXPIRY = 60*60*24*30
|
||||
"""
|
||||
The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
|
||||
announced ratchet, and remember it for up to ``RATCHET_EXPIRY`` since receiving it, after which it will be discarded.
|
||||
If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.
|
||||
"""
|
||||
|
||||
# Non-configurable constants
|
||||
FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD
|
||||
@@ -67,6 +80,9 @@ class Identity:
|
||||
|
||||
# Storage
|
||||
known_destinations = {}
|
||||
known_ratchets = {}
|
||||
|
||||
ratchet_persist_lock = threading.Lock()
|
||||
|
||||
@staticmethod
|
||||
def remember(packet_hash, destination_hash, public_key, app_data = None):
|
||||
@@ -197,7 +213,7 @@ class Identity:
|
||||
Get a SHA-256 hash of passed data.
|
||||
|
||||
:param data: Data to be hashed as *bytes*.
|
||||
:returns: SHA-256 hash as *bytes*
|
||||
:returns: SHA-256 hash as *bytes*.
|
||||
"""
|
||||
return RNS.Cryptography.sha256(data)
|
||||
|
||||
@@ -207,7 +223,7 @@ class Identity:
|
||||
Get a truncated SHA-256 hash of passed data.
|
||||
|
||||
:param data: Data to be hashed as *bytes*.
|
||||
:returns: Truncated SHA-256 hash as *bytes*
|
||||
:returns: Truncated SHA-256 hash as *bytes*.
|
||||
"""
|
||||
return Identity.full_hash(data)[:(Identity.TRUNCATED_HASHLENGTH//8)]
|
||||
|
||||
@@ -217,24 +233,158 @@ class Identity:
|
||||
Get a random SHA-256 hash.
|
||||
|
||||
:param data: Data to be hashed as *bytes*.
|
||||
:returns: Truncated SHA-256 hash of random data as *bytes*
|
||||
:returns: Truncated SHA-256 hash of random data as *bytes*.
|
||||
"""
|
||||
return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8))
|
||||
|
||||
@staticmethod
|
||||
def current_ratchet_id(destination_hash):
|
||||
"""
|
||||
Get the ID of the currently used ratchet key for a given destination hash
|
||||
|
||||
:param destination_hash: A destination hash as *bytes*.
|
||||
:returns: A ratchet ID as *bytes* or *None*.
|
||||
"""
|
||||
ratchet = Identity.get_ratchet(destination_hash)
|
||||
if ratchet == None:
|
||||
return None
|
||||
else:
|
||||
return Identity._get_ratchet_id(ratchet)
|
||||
|
||||
@staticmethod
|
||||
def _get_ratchet_id(ratchet_pub_bytes):
|
||||
return Identity.full_hash(ratchet_pub_bytes)[:Identity.NAME_HASH_LENGTH//8]
|
||||
|
||||
@staticmethod
|
||||
def _ratchet_public_bytes(ratchet):
|
||||
return X25519PrivateKey.from_private_bytes(ratchet).public_key().public_bytes()
|
||||
|
||||
@staticmethod
|
||||
def _generate_ratchet():
|
||||
ratchet_prv = X25519PrivateKey.generate()
|
||||
ratchet_pub = ratchet_prv.public_key()
|
||||
return ratchet_prv.private_bytes()
|
||||
|
||||
@staticmethod
|
||||
def _remember_ratchet(destination_hash, ratchet):
|
||||
# TODO: Remove at some point, and only log new ratchets
|
||||
RNS.log(f"Remembering ratchet {RNS.prettyhexrep(Identity._get_ratchet_id(ratchet))} for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_EXTREME)
|
||||
try:
|
||||
Identity.known_ratchets[destination_hash] = ratchet
|
||||
|
||||
if not RNS.Transport.owner.is_connected_to_shared_instance:
|
||||
def persist_job():
|
||||
with Identity.ratchet_persist_lock:
|
||||
hexhash = RNS.hexrep(destination_hash, delimit=False)
|
||||
ratchet_data = {"ratchet": ratchet, "received": time.time()}
|
||||
|
||||
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
||||
|
||||
if not os.path.isdir(ratchetdir):
|
||||
os.makedirs(ratchetdir)
|
||||
|
||||
outpath = f"{ratchetdir}/{hexhash}.out"
|
||||
finalpath = f"{ratchetdir}/{hexhash}"
|
||||
ratchet_file = open(outpath, "wb")
|
||||
ratchet_file.write(umsgpack.packb(ratchet_data))
|
||||
ratchet_file.close()
|
||||
os.replace(outpath, finalpath)
|
||||
|
||||
|
||||
threading.Thread(target=persist_job, daemon=True).start()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not persist ratchet for {RNS.prettyhexrep(destination_hash)} to storage.", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}")
|
||||
RNS.trace_exception(e)
|
||||
|
||||
@staticmethod
|
||||
def _clean_ratchets():
|
||||
RNS.log("Cleaning ratchets...", RNS.LOG_DEBUG)
|
||||
try:
|
||||
now = time.time()
|
||||
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
||||
if os.path.isdir(ratchetdir):
|
||||
for filename in os.listdir(ratchetdir):
|
||||
try:
|
||||
expired = False
|
||||
with open(f"{ratchetdir}/{filename}", "rb") as rf:
|
||||
ratchet_data = umsgpack.unpackb(rf.read())
|
||||
if now > ratchet_data["received"]+Identity.RATCHET_EXPIRY:
|
||||
expired = True
|
||||
|
||||
if expired:
|
||||
os.unlink(f"{ratchetdir}/{filename}")
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while cleaning ratchets, in the processing of {ratchetdir}/{filename}.", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while cleaning ratchets. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def get_ratchet(destination_hash):
|
||||
if not destination_hash in Identity.known_ratchets:
|
||||
ratchetdir = RNS.Reticulum.storagepath+"/ratchets"
|
||||
hexhash = RNS.hexrep(destination_hash, delimit=False)
|
||||
ratchet_path = f"{ratchetdir}/{hexhash}"
|
||||
if os.path.isfile(ratchet_path):
|
||||
try:
|
||||
ratchet_file = open(ratchet_path, "rb")
|
||||
ratchet_data = umsgpack.unpackb(ratchet_file.read())
|
||||
if time.time() < ratchet_data["received"]+Identity.RATCHET_EXPIRY and len(ratchet_data["ratchet"]) == Identity.RATCHETSIZE//8:
|
||||
Identity.known_ratchets[destination_hash] = ratchet_data["ratchet"]
|
||||
else:
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while loading ratchet data for {RNS.prettyhexrep(destination_hash)} from storage.", RNS.LOG_ERROR)
|
||||
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
return None
|
||||
|
||||
if destination_hash in Identity.known_ratchets:
|
||||
return Identity.known_ratchets[destination_hash]
|
||||
else:
|
||||
RNS.log(f"Could not load ratchet for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_DEBUG)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def validate_announce(packet, only_validate_signature=False):
|
||||
try:
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
keysize = Identity.KEYSIZE//8
|
||||
ratchetsize = Identity.RATCHETSIZE//8
|
||||
name_hash_len = Identity.NAME_HASH_LENGTH//8
|
||||
sig_len = Identity.SIGLENGTH//8
|
||||
destination_hash = packet.destination_hash
|
||||
public_key = packet.data[:Identity.KEYSIZE//8]
|
||||
name_hash = packet.data[Identity.KEYSIZE//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8]
|
||||
random_hash = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10]
|
||||
signature = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10:Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8]
|
||||
app_data = b""
|
||||
if len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
|
||||
app_data = packet.data[Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:]
|
||||
|
||||
signed_data = destination_hash+public_key+name_hash+random_hash+app_data
|
||||
# Get public key bytes from announce
|
||||
public_key = packet.data[:keysize]
|
||||
|
||||
# If the packet context flag is set,
|
||||
# this announce contains a new ratchet
|
||||
if packet.context_flag == RNS.Packet.FLAG_SET:
|
||||
name_hash = packet.data[keysize:keysize+name_hash_len ]
|
||||
random_hash = packet.data[keysize+name_hash_len:keysize+name_hash_len+10]
|
||||
ratchet = packet.data[keysize+name_hash_len+10:keysize+name_hash_len+10+ratchetsize]
|
||||
signature = packet.data[keysize+name_hash_len+10+ratchetsize:keysize+name_hash_len+10+ratchetsize+sig_len]
|
||||
app_data = b""
|
||||
if len(packet.data) > keysize+name_hash_len+10+sig_len+ratchetsize:
|
||||
app_data = packet.data[keysize+name_hash_len+10+sig_len+ratchetsize:]
|
||||
|
||||
# If the packet context flag is not set,
|
||||
# this announce does not contain a ratchet
|
||||
else:
|
||||
ratchet = b""
|
||||
name_hash = packet.data[keysize:keysize+name_hash_len]
|
||||
random_hash = packet.data[keysize+name_hash_len:keysize+name_hash_len+10]
|
||||
signature = packet.data[keysize+name_hash_len+10:keysize+name_hash_len+10+sig_len]
|
||||
app_data = b""
|
||||
if len(packet.data) > keysize+name_hash_len+10+sig_len:
|
||||
app_data = packet.data[keysize+name_hash_len+10+sig_len:]
|
||||
|
||||
signed_data = destination_hash+public_key+name_hash+random_hash+ratchet+app_data
|
||||
|
||||
if not len(packet.data) > Identity.KEYSIZE//8+Identity.NAME_HASH_LENGTH//8+10+Identity.SIGLENGTH//8:
|
||||
app_data = None
|
||||
@@ -281,6 +431,9 @@ class Identity:
|
||||
else:
|
||||
RNS.log("Valid announce for "+RNS.prettyhexrep(destination_hash)+" "+str(packet.hops)+" hops away, received on "+str(packet.receiving_interface)+signal_str, RNS.LOG_EXTREME)
|
||||
|
||||
if ratchet:
|
||||
Identity._remember_ratchet(destination_hash, ratchet)
|
||||
|
||||
return True
|
||||
|
||||
else:
|
||||
@@ -469,7 +622,7 @@ class Identity:
|
||||
def get_context(self):
|
||||
return None
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
def encrypt(self, plaintext, ratchet=None):
|
||||
"""
|
||||
Encrypts information for the identity.
|
||||
|
||||
@@ -481,7 +634,12 @@ class Identity:
|
||||
ephemeral_key = X25519PrivateKey.generate()
|
||||
ephemeral_pub_bytes = ephemeral_key.public_key().public_bytes()
|
||||
|
||||
shared_key = ephemeral_key.exchange(self.pub)
|
||||
if ratchet != None:
|
||||
target_public_key = X25519PublicKey.from_public_bytes(ratchet)
|
||||
else:
|
||||
target_public_key = self.pub
|
||||
|
||||
shared_key = ephemeral_key.exchange(target_public_key)
|
||||
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=32,
|
||||
@@ -499,7 +657,7 @@ class Identity:
|
||||
raise KeyError("Encryption failed because identity does not hold a public key")
|
||||
|
||||
|
||||
def decrypt(self, ciphertext_token):
|
||||
def decrypt(self, ciphertext_token, ratchets=None, enforce_ratchets=False, ratchet_id_receiver=None):
|
||||
"""
|
||||
Decrypts information for the identity.
|
||||
|
||||
@@ -513,22 +671,55 @@ class Identity:
|
||||
try:
|
||||
peer_pub_bytes = ciphertext_token[:Identity.KEYSIZE//8//2]
|
||||
peer_pub = X25519PublicKey.from_public_bytes(peer_pub_bytes)
|
||||
|
||||
shared_key = self.prv.exchange(peer_pub)
|
||||
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=32,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context(),
|
||||
)
|
||||
|
||||
fernet = Fernet(derived_key)
|
||||
ciphertext = ciphertext_token[Identity.KEYSIZE//8//2:]
|
||||
plaintext = fernet.decrypt(ciphertext)
|
||||
|
||||
if ratchets:
|
||||
for ratchet in ratchets:
|
||||
try:
|
||||
ratchet_prv = X25519PrivateKey.from_private_bytes(ratchet)
|
||||
ratchet_id = Identity._get_ratchet_id(ratchet_prv.public_key().public_bytes())
|
||||
shared_key = ratchet_prv.exchange(peer_pub)
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=32,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context(),
|
||||
)
|
||||
|
||||
fernet = Fernet(derived_key)
|
||||
plaintext = fernet.decrypt(ciphertext)
|
||||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = ratchet_id
|
||||
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
if enforce_ratchets and plaintext == None:
|
||||
RNS.log("Decryption with ratchet enforcement by "+RNS.prettyhexrep(self.hash)+" failed. Dropping packet.", RNS.LOG_DEBUG)
|
||||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = None
|
||||
return None
|
||||
|
||||
if plaintext == None:
|
||||
shared_key = self.prv.exchange(peer_pub)
|
||||
derived_key = RNS.Cryptography.hkdf(
|
||||
length=32,
|
||||
derive_from=shared_key,
|
||||
salt=self.get_salt(),
|
||||
context=self.get_context(),
|
||||
)
|
||||
|
||||
fernet = Fernet(derived_key)
|
||||
plaintext = fernet.decrypt(ciphertext)
|
||||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = None
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Decryption by "+RNS.prettyhexrep(self.hash)+" failed: "+str(e), RNS.LOG_DEBUG)
|
||||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = None
|
||||
|
||||
return plaintext;
|
||||
else:
|
||||
|
||||
@@ -96,7 +96,7 @@ class AX25KISSInterface(Interface):
|
||||
self.stopbits = stopbits
|
||||
self.timeout = 100
|
||||
self.online = False
|
||||
self.bitrate = KISSInterface.BITRATE_GUESS
|
||||
self.bitrate = AX25KISSInterface.BITRATE_GUESS
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
|
||||
@@ -28,6 +28,15 @@ import time
|
||||
import math
|
||||
import RNS
|
||||
|
||||
try:
|
||||
from able import BluetoothDispatcher, GATT_SUCCESS
|
||||
except Exception as e:
|
||||
GATT_SUCCESS = 0x00
|
||||
class BluetoothDispatcher():
|
||||
def __init__(**kwargs):
|
||||
RNS.log("Attempt to initialise BLE connectivity, but Android BLE support library is unavailable", RNS.LOG_ERROR)
|
||||
raise OSError("No BLE support available")
|
||||
|
||||
class KISS():
|
||||
FEND = 0xC0
|
||||
FESC = 0xDB
|
||||
@@ -54,6 +63,7 @@ class KISS():
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_STAT_CHTM = 0x25
|
||||
CMD_STAT_PHYPRM = 0x26
|
||||
CMD_STAT_BAT = 0x27
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FB_EXT = 0x41
|
||||
@@ -77,10 +87,15 @@ class KISS():
|
||||
ERROR_INITRADIO = 0x01
|
||||
ERROR_TXFAILED = 0x02
|
||||
ERROR_EEPROM_LOCKED = 0x03
|
||||
ERROR_QUEUE_FULL = 0x04
|
||||
ERROR_MEMORY_LOW = 0x05
|
||||
ERROR_MODEM_TIMEOUT = 0x06
|
||||
ERROR_INVALID_FIRMWARE = 0x10
|
||||
ERROR_INVALID_BLE_MTU = 0x20
|
||||
|
||||
PLATFORM_AVR = 0x90
|
||||
PLATFORM_ESP32 = 0x80
|
||||
PLATFORM_NRF52 = 0x70
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
@@ -89,6 +104,10 @@ class KISS():
|
||||
return data
|
||||
|
||||
class AndroidBluetoothManager():
|
||||
DEVICE_TYPE_CLASSIC = 1
|
||||
DEVICE_TYPE_LE = 2
|
||||
DEVICE_TYPE_DUAL = 3
|
||||
|
||||
def __init__(self, owner, target_device_name = None, target_device_address = None):
|
||||
from jnius import autoclass
|
||||
self.owner = owner
|
||||
@@ -237,6 +256,11 @@ class RNodeInterface(Interface):
|
||||
Q_SNR_MAX = 6
|
||||
Q_SNR_STEP = 2
|
||||
|
||||
BATTERY_STATE_UNKNOWN = 0x00
|
||||
BATTERY_STATE_DISCHARGING = 0x01
|
||||
BATTERY_STATE_CHARGING = 0x02
|
||||
BATTERY_STATE_CHARGED = 0x03
|
||||
|
||||
@classmethod
|
||||
def bluetooth_control(device_serial = None, port = None, enable_bluetooth = False, disable_bluetooth = False, pairing_mode = False):
|
||||
if (port != None or device_serial != None) and (enable_bluetooth or disable_bluetooth or pairing_mode):
|
||||
@@ -321,7 +345,8 @@ class RNodeInterface(Interface):
|
||||
self, owner, name, port, frequency = None, bandwidth = None, txpower = None,
|
||||
sf = None, cr = None, flow_control = False, id_interval = None,
|
||||
allow_bluetooth = False, target_device_name = None,
|
||||
target_device_address = None, id_callsign = None, st_alock = None, lt_alock = None):
|
||||
target_device_address = None, id_callsign = None, st_alock = None, lt_alock = None,
|
||||
ble_addr = None, ble_name = None, force_ble=False):
|
||||
import importlib
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.on_android = True
|
||||
@@ -368,9 +393,19 @@ class RNodeInterface(Interface):
|
||||
self.stopbits = 1
|
||||
self.timeout = 150
|
||||
self.online = False
|
||||
self.detached = False
|
||||
self.hw_errors = []
|
||||
self.allow_bluetooth = allow_bluetooth
|
||||
|
||||
self.use_ble = False
|
||||
self.ble_name = ble_name
|
||||
self.ble_addr = ble_addr
|
||||
self.ble = None
|
||||
self.ble_rx_lock = threading.Lock()
|
||||
self.ble_tx_lock = threading.Lock()
|
||||
self.ble_rx_queue= b""
|
||||
self.ble_tx_queue= b""
|
||||
|
||||
self.frequency = frequency
|
||||
self.bandwidth = bandwidth
|
||||
self.txpower = txpower
|
||||
@@ -391,6 +426,7 @@ class RNodeInterface(Interface):
|
||||
self.last_id = 0
|
||||
self.first_tx = None
|
||||
self.reconnect_w = RNodeInterface.RECONNECT_WAIT
|
||||
self.reconnect_lock = threading.Lock()
|
||||
|
||||
self.r_frequency = None
|
||||
self.r_bandwidth = None
|
||||
@@ -414,6 +450,8 @@ class RNodeInterface(Interface):
|
||||
self.r_symbol_rate = None
|
||||
self.r_preamble_symbols = None
|
||||
self.r_premable_time_ms = None
|
||||
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
|
||||
self.r_battery_percent = 0
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
@@ -423,6 +461,9 @@ class RNodeInterface(Interface):
|
||||
self.port_io_timeout = RNodeInterface.PORT_IO_TIMEOUT
|
||||
self.last_imagedata = None
|
||||
|
||||
if force_ble or self.ble_addr != None or self.ble_name != None:
|
||||
self.use_ble = True
|
||||
|
||||
self.validcfg = True
|
||||
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
|
||||
RNS.log("Invalid frequency configured for "+str(self), RNS.LOG_ERROR)
|
||||
@@ -488,9 +529,7 @@ class RNodeInterface(Interface):
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if len(self.hw_errors) == 0:
|
||||
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()
|
||||
thread = threading.Thread(target=self.reconnect_port, daemon=True).start()
|
||||
|
||||
|
||||
def read_mux(self, len=None):
|
||||
@@ -514,88 +553,116 @@ class RNodeInterface(Interface):
|
||||
else:
|
||||
raise IOError("No ports available for writing")
|
||||
|
||||
# def reset_ble(self):
|
||||
# RNS.log(f"Clearing previous connection instance: "+str(self.ble))
|
||||
# del self.ble
|
||||
# self.ble = None
|
||||
# self.serial = None
|
||||
# self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
|
||||
# self.serial = self.ble
|
||||
# RNS.log(f"New connection instance: "+str(self.ble))
|
||||
|
||||
def open_port(self):
|
||||
if self.port != None:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
# Get device parameters
|
||||
from usb4a import usb
|
||||
device = usb.get_usb_device(self.port)
|
||||
if device:
|
||||
vid = device.getVendorId()
|
||||
pid = device.getProductId()
|
||||
if not self.use_ble:
|
||||
if self.port != None:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
# Get device parameters
|
||||
from usb4a import usb
|
||||
device = usb.get_usb_device(self.port)
|
||||
if device:
|
||||
vid = device.getVendorId()
|
||||
pid = device.getProductId()
|
||||
|
||||
# Driver overrides for speficic chips
|
||||
proxy = self.pyserial.get_serial_port
|
||||
if vid == 0x1A86 and pid == 0x55D4:
|
||||
# Force CDC driver for Qinheng CH34x
|
||||
RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
|
||||
from usbserial4a.cdcacmserial4a import CdcAcmSerial
|
||||
proxy = CdcAcmSerial
|
||||
# Driver overrides for speficic chips
|
||||
proxy = self.pyserial.get_serial_port
|
||||
if vid == 0x1A86 and pid == 0x55D4:
|
||||
# Force CDC driver for Qinheng CH34x
|
||||
RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
|
||||
from usbserial4a.cdcacmserial4a import CdcAcmSerial
|
||||
proxy = CdcAcmSerial
|
||||
|
||||
self.serial = proxy(
|
||||
self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = None,
|
||||
inter_byte_timeout = None,
|
||||
# write_timeout = wtimeout,
|
||||
dsrdtr = False,
|
||||
)
|
||||
self.serial = proxy(
|
||||
self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = None,
|
||||
inter_byte_timeout = None,
|
||||
# write_timeout = wtimeout,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
if vid == 0x0403:
|
||||
# Hardware parameters for FTDI devices @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
self.serial.timeout = 0.1
|
||||
elif vid == 0x10C4:
|
||||
# Hardware parameters for SiLabs CP210x @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
self.serial.timeout = 0.012
|
||||
elif vid == 0x1A86 and pid == 0x55D4:
|
||||
# Hardware parameters for Qinheng CH34x @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
self.serial.timeout = 0.1
|
||||
else:
|
||||
# Default values
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
self.serial.timeout = 0.1
|
||||
if vid == 0x0403:
|
||||
# Hardware parameters for FTDI devices @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
self.serial.timeout = 0.1
|
||||
elif vid == 0x10C4:
|
||||
# Hardware parameters for SiLabs CP210x @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
self.serial.timeout = 0.012
|
||||
elif vid == 0x1A86 and pid == 0x55D4:
|
||||
# Hardware parameters for Qinheng CH34x @ 115200 baud
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 64
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 12
|
||||
self.serial.timeout = 0.1
|
||||
else:
|
||||
# Default values
|
||||
self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
|
||||
self.serial.USB_READ_TIMEOUT_MILLIS = 100
|
||||
self.serial.timeout = 0.1
|
||||
|
||||
RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
|
||||
|
||||
elif self.allow_bluetooth:
|
||||
if self.bt_manager == None:
|
||||
self.bt_manager = AndroidBluetoothManager(
|
||||
owner = self,
|
||||
target_device_name = self.bt_target_device_name,
|
||||
target_device_address = self.bt_target_device_address
|
||||
)
|
||||
elif self.allow_bluetooth:
|
||||
if self.bt_manager == None:
|
||||
self.bt_manager = AndroidBluetoothManager(
|
||||
owner = self,
|
||||
target_device_name = self.bt_target_device_name,
|
||||
target_device_address = self.bt_target_device_address
|
||||
)
|
||||
|
||||
if self.bt_manager != None:
|
||||
self.bt_manager.connect_any_device()
|
||||
if self.bt_manager != None:
|
||||
self.bt_manager.connect_any_device()
|
||||
|
||||
else:
|
||||
if self.ble == None:
|
||||
self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
|
||||
self.serial = self.ble
|
||||
|
||||
open_time = time.time()
|
||||
while not self.ble.connected and time.time() < open_time + self.ble.CONNECT_TIMEOUT:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
self.resetRadioState()
|
||||
sleep(2.0)
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
thread = threading.Thread(target=self.readLoop, daemon=True).start()
|
||||
|
||||
self.detect()
|
||||
sleep(0.5)
|
||||
if not self.use_ble:
|
||||
sleep(0.5)
|
||||
else:
|
||||
ble_detect_timeout = 5
|
||||
detect_time = time.time()
|
||||
while not self.detected and time.time() < detect_time + ble_detect_timeout:
|
||||
time.sleep(0.1)
|
||||
if self.detected:
|
||||
detect_time = RNS.prettytime(time.time()-detect_time)
|
||||
else:
|
||||
RNS.log(f"RNode detect timed out over {self.port}", RNS.LOG_ERROR)
|
||||
|
||||
if not self.detected:
|
||||
raise IOError("Could not detect device")
|
||||
else:
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52:
|
||||
self.display = True
|
||||
|
||||
if not self.firmware_ok:
|
||||
@@ -654,6 +721,9 @@ class RNodeInterface(Interface):
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
time.sleep(0.15)
|
||||
|
||||
if self.use_ble:
|
||||
time.sleep(1)
|
||||
|
||||
def detect(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
@@ -807,16 +877,19 @@ class RNodeInterface(Interface):
|
||||
raise IOError("An IO error occurred while configuring radio state for "+str(self))
|
||||
|
||||
def validate_firmware(self):
|
||||
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
|
||||
self.firmware_ok = True
|
||||
if (self.maj_version > RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
self.firmware_ok = True
|
||||
else:
|
||||
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
|
||||
self.firmware_ok = True
|
||||
|
||||
if self.firmware_ok:
|
||||
return
|
||||
|
||||
RNS.log("The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version), RNS.LOG_ERROR)
|
||||
RNS.log("This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN), RNS.LOG_ERROR)
|
||||
RNS.log("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/rnodeconfigutil/")
|
||||
RNS.log("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/reticulum/")
|
||||
error_description = "The firmware version of the connected RNode is "+str(self.maj_version)+"."+str(self.min_version)+". "
|
||||
error_description += "This version of Reticulum requires at least version "+str(RNodeInterface.REQUIRED_FW_VER_MAJ)+"."+str(RNodeInterface.REQUIRED_FW_VER_MIN)+". "
|
||||
error_description += "Please update your RNode firmware with rnodeconf from: https://github.com/markqvist/rnodeconfigutil/"
|
||||
@@ -824,7 +897,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)
|
||||
if not self.platform == KISS.PLATFORM_ESP32:
|
||||
sleep(1.00);
|
||||
else:
|
||||
@@ -852,6 +925,13 @@ class RNodeInterface(Interface):
|
||||
else:
|
||||
return False
|
||||
|
||||
def resetRadioState(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
|
||||
|
||||
def updateBitrate(self):
|
||||
try:
|
||||
@@ -1140,6 +1220,25 @@ class RNodeInterface(Interface):
|
||||
RNS.log(str(self)+" Radio reporting symbol time is "+str(round(self.r_symbol_time_ms,2))+"ms (at "+str(self.r_symbol_rate)+" baud)", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" Radio reporting preamble is "+str(self.r_preamble_symbols)+" symbols ("+str(self.r_premable_time_ms)+"ms)", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" Radio reporting CSMA slot time is "+str(self.r_csma_slot_time_ms)+"ms", RNS.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_STAT_BAT):
|
||||
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) == 2):
|
||||
bat_percent = command_buffer[1]
|
||||
if bat_percent > 100:
|
||||
bat_percent = 100
|
||||
if bat_percent < 0:
|
||||
bat_percent = 0
|
||||
self.r_battery_state = command_buffer[0]
|
||||
self.r_battery_percent = bat_percent
|
||||
elif (command == KISS.CMD_RANDOM):
|
||||
self.r_random = byte
|
||||
elif (command == KISS.CMD_PLATFORM):
|
||||
@@ -1153,6 +1252,12 @@ class RNodeInterface(Interface):
|
||||
elif (byte == KISS.ERROR_TXFAILED):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
elif (byte == KISS.ERROR_MEMORY_LOW):
|
||||
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+"): Memory exhausted", RNS.LOG_ERROR)
|
||||
self.hw_errors.append({"error": KISS.ERROR_MEMORY_LOW, "description": "Memory exhausted on connected device"})
|
||||
elif (byte == KISS.ERROR_MODEM_TIMEOUT):
|
||||
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+"): Modem communication timed out", RNS.LOG_ERROR)
|
||||
self.hw_errors.append({"error": KISS.ERROR_MODEM_TIMEOUT, "description": "Modem communication timed out on connected device"})
|
||||
else:
|
||||
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Unknown hardware failure")
|
||||
@@ -1212,48 +1317,319 @@ class RNodeInterface(Interface):
|
||||
if self.bt_manager != None:
|
||||
self.bt_manager.close()
|
||||
|
||||
self.reconnect_port()
|
||||
if not self.detached:
|
||||
self.reconnect_port()
|
||||
|
||||
def reconnect_port(self):
|
||||
while not self.online and len(self.hw_errors) == 0:
|
||||
try:
|
||||
time.sleep(self.reconnect_w)
|
||||
if self.serial != None and self.port != None:
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_EXTREME)
|
||||
if self.reconnect_lock.locked():
|
||||
RNS.log("Dropping superflous reconnect port job")
|
||||
return
|
||||
|
||||
if self.bt_manager != None:
|
||||
RNS.log("Attempting to reconnect Bluetooth device for "+str(self)+"...", RNS.LOG_EXTREME)
|
||||
with self.reconnect_lock:
|
||||
while not self.online and len(self.hw_errors) == 0:
|
||||
try:
|
||||
time.sleep(self.reconnect_w)
|
||||
if self.serial != None and self.port != None:
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_EXTREME)
|
||||
|
||||
self.open_port()
|
||||
if self.bt_manager != None:
|
||||
RNS.log("Attempting to reconnect Bluetooth device for "+str(self)+"...", RNS.LOG_EXTREME)
|
||||
|
||||
if hasattr(self, "serial") and self.serial != None and self.serial.is_open:
|
||||
self.configure_device()
|
||||
if self.online:
|
||||
if self.last_imagedata != None:
|
||||
self.display_image(self.last_imagedata)
|
||||
self.enable_external_framebuffer()
|
||||
|
||||
elif hasattr(self, "bt_manager") and self.bt_manager != None and self.bt_manager.connected:
|
||||
self.configure_device()
|
||||
if self.online:
|
||||
if self.last_imagedata != None:
|
||||
self.display_image(self.last_imagedata)
|
||||
self.enable_external_framebuffer()
|
||||
self.open_port()
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting RNode, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
if hasattr(self, "serial") and self.serial != None and self.serial.is_open:
|
||||
self.configure_device()
|
||||
if self.online:
|
||||
if self.last_imagedata != None:
|
||||
self.display_image(self.last_imagedata)
|
||||
self.enable_external_framebuffer()
|
||||
|
||||
elif hasattr(self, "bt_manager") and self.bt_manager != None and self.bt_manager.connected:
|
||||
self.configure_device()
|
||||
if self.online:
|
||||
if self.last_imagedata != None:
|
||||
self.display_image(self.last_imagedata)
|
||||
self.enable_external_framebuffer()
|
||||
|
||||
if self.online:
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting RNode, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
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()
|
||||
|
||||
if self.use_ble:
|
||||
self.ble.close()
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def get_battery_state(self):
|
||||
return self.r_battery_state
|
||||
|
||||
def get_battery_state_string(self):
|
||||
if self.r_battery_state == RNodeInterface.BATTERY_STATE_CHARGED:
|
||||
return "charged"
|
||||
elif self.r_battery_state == RNodeInterface.BATTERY_STATE_CHARGING:
|
||||
return "charging"
|
||||
elif self.r_battery_state == RNodeInterface.BATTERY_STATE_DISCHARGING:
|
||||
return "discharging"
|
||||
else:
|
||||
return "unknown"
|
||||
|
||||
def get_battery_percent(self):
|
||||
return self.r_battery_percent
|
||||
|
||||
def ble_receive(self, data):
|
||||
with self.ble_rx_lock:
|
||||
self.ble_rx_queue += data
|
||||
|
||||
def ble_waiting(self):
|
||||
return len(self.ble_tx_queue) > 0
|
||||
|
||||
def get_ble_waiting(self, n):
|
||||
with self.ble_tx_lock:
|
||||
data = self.ble_tx_queue[:n]
|
||||
self.ble_tx_queue = self.ble_tx_queue[n:]
|
||||
return data
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+str(self.name)+"]"
|
||||
|
||||
class BLEConnection(BluetoothDispatcher):
|
||||
UART_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
|
||||
UART_RX_CHAR_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"
|
||||
UART_TX_CHAR_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"
|
||||
MAX_GATT_ATTR_LEN = 512
|
||||
BASE_MTU = 20
|
||||
TARGET_MTU = 512
|
||||
|
||||
MTU_TIMEOUT = 4.0
|
||||
CONNECT_TIMEOUT = 7.0
|
||||
RECONNECT_WAIT = 1.0
|
||||
|
||||
@property
|
||||
def is_open(self):
|
||||
return self.connected
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
return len(self.owner.ble_rx_queue) > 0
|
||||
|
||||
def write(self, data_bytes):
|
||||
with self.owner.ble_tx_lock:
|
||||
self.owner.ble_tx_queue += data_bytes
|
||||
return len(data_bytes)
|
||||
|
||||
def read(self):
|
||||
with self.owner.ble_rx_lock:
|
||||
data = self.owner.ble_rx_queue
|
||||
self.owner.ble_rx_queue = b""
|
||||
return data
|
||||
|
||||
def close(self):
|
||||
try:
|
||||
if self.connected:
|
||||
RNS.log(f"Disconnecting BLE device from {self.owner}", RNS.LOG_DEBUG)
|
||||
# RNS.log("Waiting for BLE write buffer to empty...")
|
||||
timeout = time.time() + 10
|
||||
while self.owner.ble_waiting() and self.write_thread != None and time.time() < timeout:
|
||||
time.sleep(0.1)
|
||||
# if time.time() > timeout:
|
||||
# RNS.log("Writing timed out")
|
||||
# else:
|
||||
# RNS.log("Writing concluded")
|
||||
|
||||
self.rx_char = None
|
||||
self.tx_char = None
|
||||
self.mtu = BLEConnection.BASE_MTU
|
||||
self.mtu_requested_time = None
|
||||
|
||||
if self.write_thread != None:
|
||||
# RNS.log("Waiting for write thread to finish...")
|
||||
while self.write_thread != None:
|
||||
time.sleep(0.1)
|
||||
|
||||
# RNS.log("Writing finished, closing GATT connection")
|
||||
self.close_gatt()
|
||||
|
||||
with self.owner.ble_rx_lock:
|
||||
self.owner.ble_rx_queue = b""
|
||||
|
||||
with self.owner.ble_tx_lock:
|
||||
self.owner.ble_tx_queue = b""
|
||||
|
||||
self.connected = False
|
||||
self.ble_device = None
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while closing BLE connection for {self.owner}: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
def __init__(self, owner=None, target_name=None, target_bt_addr=None):
|
||||
super(BLEConnection, self).__init__()
|
||||
self.owner = owner
|
||||
self.target_name = target_name
|
||||
self.target_bt_addr = target_bt_addr
|
||||
self.connect_timeout = BLEConnection.CONNECT_TIMEOUT
|
||||
self.ble_device = None
|
||||
self.rx_char = None
|
||||
self.tx_char = None
|
||||
self.connected = False
|
||||
self.was_connected = False
|
||||
self.connected_time = None
|
||||
self.mtu_requested_time = None
|
||||
self.running = False
|
||||
self.should_run = False
|
||||
self.connect_job_running = False
|
||||
self.write_thread = None
|
||||
self.mtu = BLEConnection.BASE_MTU
|
||||
self.target_mtu = BLEConnection.TARGET_MTU
|
||||
|
||||
self.bt_manager = AndroidBluetoothManager(owner=self)
|
||||
|
||||
self.should_run = True
|
||||
self.connection_thread = threading.Thread(target=self.connection_job, daemon=True).start()
|
||||
|
||||
def write_loop(self):
|
||||
try:
|
||||
while self.connected and self.rx_char != None:
|
||||
if self.owner.ble_waiting():
|
||||
data = self.owner.get_ble_waiting(self.mtu)
|
||||
self.write_characteristic(self.rx_char, data)
|
||||
else:
|
||||
time.sleep(0.1)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred in {self} write loop: {e}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
self.write_thread = None
|
||||
|
||||
def connection_job(self):
|
||||
while self.should_run:
|
||||
if self.bt_manager.bt_enabled():
|
||||
if self.ble_device == None:
|
||||
self.ble_device = self.find_target_device()
|
||||
|
||||
if self.ble_device != None:
|
||||
if not self.connected:
|
||||
if self.was_connected:
|
||||
RNS.log(f"Throttling BLE reconnect for {BLEConnection.RECONNECT_WAIT} seconds", RNS.LOG_DEBUG)
|
||||
time.sleep(BLEConnection.RECONNECT_WAIT)
|
||||
|
||||
self.connect_device()
|
||||
|
||||
else:
|
||||
if self.connected:
|
||||
RNS.log("Bluetooth was disabled, closing active BLE device connection", RNS.LOG_ERROR)
|
||||
self.close()
|
||||
|
||||
time.sleep(2)
|
||||
|
||||
def connect_device(self):
|
||||
if self.ble_device != None and self.bt_manager.bt_enabled():
|
||||
RNS.log(f"Trying to connect BLE device {self.ble_device.getName()} / {self.ble_device.getAddress()} for {self.owner}...", RNS.LOG_DEBUG)
|
||||
self.mtu = BLEConnection.BASE_MTU
|
||||
self.connect_by_device_address(self.ble_device.getAddress())
|
||||
end = time.time() + BLEConnection.CONNECT_TIMEOUT
|
||||
while time.time() < end and not self.connected:
|
||||
time.sleep(0.25)
|
||||
|
||||
if self.connected:
|
||||
self.owner.port = f"ble://{self.ble_device.getAddress()}"
|
||||
self.write_thread = threading.Thread(target=self.write_loop, daemon=True)
|
||||
self.write_thread.start()
|
||||
else:
|
||||
RNS.log(f"BLE device connection timed out for {self.owner}", RNS.LOG_DEBUG)
|
||||
if self.mtu_requested_time:
|
||||
RNS.log("MTU update timeout, tearing down connection")
|
||||
self.owner.hw_errors.append({"error": KISS.ERROR_INVALID_BLE_MTU, "description": "The Bluetooth Low Energy transfer MTU could not be configured for the connected device, and communication has failed. Restart Reticulum and any connected applications to retry connecting."})
|
||||
self.close()
|
||||
self.should_run = False
|
||||
|
||||
self.close_gatt()
|
||||
|
||||
self.connect_job_running = False
|
||||
|
||||
def device_disconnected(self):
|
||||
RNS.log(f"BLE device for {self.owner} disconnected", RNS.LOG_NOTICE)
|
||||
self.connected = False
|
||||
self.ble_device = None
|
||||
self.close_gatt()
|
||||
|
||||
def find_target_device(self):
|
||||
found_device = None
|
||||
potential_devices = self.bt_manager.get_paired_devices()
|
||||
|
||||
if self.target_bt_addr != None:
|
||||
for device in potential_devices:
|
||||
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
|
||||
if str(device.getAddress()).replace(":", "").lower() == str(self.target_bt_addr).replace(":", "").lower():
|
||||
found_device = device
|
||||
break
|
||||
|
||||
if not found_device and self.target_name != None:
|
||||
for device in potential_devices:
|
||||
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
|
||||
if device.getName().lower() == self.target_name.lower():
|
||||
found_device = device
|
||||
break
|
||||
|
||||
if not found_device:
|
||||
for device in potential_devices:
|
||||
if (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_LE) or (device.getType() == AndroidBluetoothManager.DEVICE_TYPE_DUAL):
|
||||
if device.getName().startswith("RNode "):
|
||||
found_device = device
|
||||
break
|
||||
|
||||
return found_device
|
||||
|
||||
def on_connection_state_change(self, status, state):
|
||||
if status == GATT_SUCCESS and state:
|
||||
self.discover_services()
|
||||
else:
|
||||
self.device_disconnected()
|
||||
|
||||
def on_services(self, status, services):
|
||||
if status == GATT_SUCCESS:
|
||||
self.rx_char = services.search(BLEConnection.UART_RX_CHAR_UUID)
|
||||
|
||||
if self.rx_char is not None:
|
||||
self.tx_char = services.search(BLEConnection.UART_TX_CHAR_UUID)
|
||||
|
||||
if self.tx_char is not None:
|
||||
if self.enable_notifications(self.tx_char):
|
||||
RNS.log("Enabled notifications for BLE TX characteristic", RNS.LOG_DEBUG)
|
||||
|
||||
RNS.log(f"Requesting BLE connection MTU update to {self.target_mtu}", RNS.LOG_DEBUG)
|
||||
self.mtu_requested_time = time.time()
|
||||
self.request_mtu(self.target_mtu)
|
||||
|
||||
else:
|
||||
RNS.log("Could not enable notifications for BLE TX characteristic", RNS.LOG_ERROR)
|
||||
|
||||
else:
|
||||
RNS.log("BLE device service discovery failure", RNS.LOG_ERROR)
|
||||
|
||||
def on_mtu_changed(self, mtu, status):
|
||||
if status == GATT_SUCCESS:
|
||||
self.mtu = min(mtu-5, BLEConnection.MAX_GATT_ATTR_LEN)
|
||||
RNS.log(f"BLE MTU updated to {self.mtu} for {self.owner}", RNS.LOG_DEBUG)
|
||||
self.connected = True
|
||||
self.was_connected = True
|
||||
self.connected_time = time.time()
|
||||
self.mtu_requested_time = None
|
||||
|
||||
else:
|
||||
RNS.log(f"MTU update request did not succeed, mtu={mtu}, status={status}", RNS.LOG_ERROR)
|
||||
|
||||
def on_characteristic_changed(self, characteristic):
|
||||
if characteristic.getUuid().toString() == BLEConnection.UART_TX_CHAR_UUID:
|
||||
recvd = bytes(characteristic.getValue())
|
||||
self.owner.ble_receive(recvd)
|
||||
@@ -176,8 +176,8 @@ class LocalClientInterface(Interface):
|
||||
self.send_lock = Lock()
|
||||
|
||||
with self.send_lock:
|
||||
# RNS.log(f"Simulating latency of {RNS.prettytime(s)} for {len(data)} bytes", RNS.LOG_EXTREME)
|
||||
s = len(data) / self.bitrate * 8
|
||||
RNS.log(f"Simulating latency of {RNS.prettytime(s)} for {len(data)} bytes")
|
||||
time.sleep(s)
|
||||
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
|
||||
@@ -54,6 +54,7 @@ class KISS():
|
||||
CMD_STAT_SNR = 0x24
|
||||
CMD_STAT_CHTM = 0x25
|
||||
CMD_STAT_PHYPRM = 0x26
|
||||
CMD_STAT_BAT = 0x27
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FB_EXT = 0x41
|
||||
@@ -77,9 +78,13 @@ class KISS():
|
||||
ERROR_INITRADIO = 0x01
|
||||
ERROR_TXFAILED = 0x02
|
||||
ERROR_EEPROM_LOCKED = 0x03
|
||||
ERROR_QUEUE_FULL = 0x04
|
||||
ERROR_MEMORY_LOW = 0x05
|
||||
ERROR_MODEM_TIMEOUT = 0x06
|
||||
|
||||
PLATFORM_AVR = 0x90
|
||||
PLATFORM_ESP32 = 0x80
|
||||
PLATFORM_NRF52 = 0x70
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
@@ -107,7 +112,12 @@ class RNodeInterface(Interface):
|
||||
Q_SNR_MAX = 6
|
||||
Q_SNR_STEP = 2
|
||||
|
||||
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None, st_alock = None, lt_alock = None):
|
||||
BATTERY_STATE_UNKNOWN = 0x00
|
||||
BATTERY_STATE_DISCHARGING = 0x01
|
||||
BATTERY_STATE_CHARGING = 0x02
|
||||
BATTERY_STATE_CHARGED = 0x03
|
||||
|
||||
def __init__(self, owner, name, port, frequency = None, bandwidth = None, txpower = None, sf = None, cr = None, flow_control = False, id_interval = None, id_callsign = None, st_alock = None, lt_alock = None, ble_addr = None, ble_name = None, force_ble=False):
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
raise SystemError("Invalid interface type. The Android-specific RNode interface must be used on Android")
|
||||
|
||||
@@ -136,6 +146,15 @@ class RNodeInterface(Interface):
|
||||
self.detached = False
|
||||
self.reconnecting= False
|
||||
|
||||
self.use_ble = False
|
||||
self.ble_name = ble_name
|
||||
self.ble_addr = ble_addr
|
||||
self.ble = None
|
||||
self.ble_rx_lock = threading.Lock()
|
||||
self.ble_tx_lock = threading.Lock()
|
||||
self.ble_rx_queue= b""
|
||||
self.ble_tx_queue= b""
|
||||
|
||||
self.frequency = frequency
|
||||
self.bandwidth = bandwidth
|
||||
self.txpower = txpower
|
||||
@@ -179,12 +198,17 @@ class RNodeInterface(Interface):
|
||||
self.r_symbol_rate = None
|
||||
self.r_preamble_symbols = None
|
||||
self.r_premable_time_ms = None
|
||||
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
|
||||
self.r_battery_percent = 0
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
self.interface_ready = False
|
||||
self.announce_rate_target = None
|
||||
|
||||
if force_ble or self.ble_addr != None or self.ble_name != None:
|
||||
self.use_ble = True
|
||||
|
||||
self.validcfg = True
|
||||
if (self.frequency < RNodeInterface.FREQ_MIN or self.frequency > RNodeInterface.FREQ_MAX):
|
||||
RNS.log("Invalid frequency configured for "+str(self), RNS.LOG_ERROR)
|
||||
@@ -248,20 +272,31 @@ class RNodeInterface(Interface):
|
||||
|
||||
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
self.serial = self.pyserial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.pyserial.PARITY_NONE,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
if not self.use_ble:
|
||||
RNS.log("Opening serial port "+self.port+"...")
|
||||
self.serial = self.pyserial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.pyserial.PARITY_NONE,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
else:
|
||||
RNS.log(f"Opening BLE connection for {self}...")
|
||||
if self.ble == None:
|
||||
self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
|
||||
self.serial = self.ble
|
||||
|
||||
open_time = time.time()
|
||||
while not self.ble.connected and time.time() < open_time + self.ble.CONNECT_TIMEOUT:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
@@ -279,13 +314,23 @@ class RNodeInterface(Interface):
|
||||
thread.start()
|
||||
|
||||
self.detect()
|
||||
sleep(0.2)
|
||||
if not self.use_ble:
|
||||
sleep(0.2)
|
||||
else:
|
||||
ble_detect_timeout = 5
|
||||
detect_time = time.time()
|
||||
while not self.detected and time.time() < detect_time + ble_detect_timeout:
|
||||
time.sleep(0.1)
|
||||
if self.detected:
|
||||
detect_time = RNS.prettytime(time.time()-detect_time)
|
||||
else:
|
||||
RNS.log(f"RNode detect timed out over {self.port}", RNS.LOG_ERROR)
|
||||
|
||||
if not self.detected:
|
||||
RNS.log("Could not detect device for "+str(self), RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
else:
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52:
|
||||
self.display = True
|
||||
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
@@ -313,6 +358,9 @@ class RNodeInterface(Interface):
|
||||
self.setLTALock()
|
||||
self.setRadioState(KISS.RADIO_STATE_ON)
|
||||
|
||||
if self.use_ble:
|
||||
time.sleep(2)
|
||||
|
||||
def detect(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_DETECT, KISS.DETECT_REQ, KISS.FEND, KISS.CMD_FW_VERSION, 0x00, KISS.FEND, KISS.CMD_PLATFORM, 0x00, KISS.FEND, KISS.CMD_MCU, 0x00, KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
@@ -447,9 +495,12 @@ class RNodeInterface(Interface):
|
||||
raise IOError("An IO error occurred while configuring radio state for "+str(self))
|
||||
|
||||
def validate_firmware(self):
|
||||
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
|
||||
self.firmware_ok = True
|
||||
if (self.maj_version > RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
self.firmware_ok = True
|
||||
else:
|
||||
if (self.maj_version >= RNodeInterface.REQUIRED_FW_VER_MAJ):
|
||||
if (self.min_version >= RNodeInterface.REQUIRED_FW_VER_MIN):
|
||||
self.firmware_ok = True
|
||||
|
||||
if self.firmware_ok:
|
||||
return
|
||||
@@ -622,7 +673,6 @@ class RNodeInterface(Interface):
|
||||
self.r_state = byte
|
||||
if self.r_state:
|
||||
pass
|
||||
#RNS.log(str(self)+" Radio reporting state is online", RNS.LOG_DEBUG)
|
||||
else:
|
||||
RNS.log(str(self)+" Radio reporting state is offline", RNS.LOG_DEBUG)
|
||||
|
||||
@@ -766,6 +816,25 @@ class RNodeInterface(Interface):
|
||||
RNS.log(str(self)+" Radio reporting symbol time is "+str(round(self.r_symbol_time_ms,2))+"ms (at "+str(self.r_symbol_rate)+" baud)", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" Radio reporting preamble is "+str(self.r_preamble_symbols)+" symbols ("+str(self.r_premable_time_ms)+"ms)", RNS.LOG_DEBUG)
|
||||
RNS.log(str(self)+" Radio reporting CSMA slot time is "+str(self.r_csma_slot_time_ms)+"ms", RNS.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_STAT_BAT):
|
||||
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) == 2):
|
||||
bat_percent = command_buffer[1]
|
||||
if bat_percent > 100:
|
||||
bat_percent = 100
|
||||
if bat_percent < 0:
|
||||
bat_percent = 0
|
||||
self.r_battery_state = command_buffer[0]
|
||||
self.r_battery_percent = bat_percent
|
||||
elif (command == KISS.CMD_RANDOM):
|
||||
self.r_random = byte
|
||||
elif (command == KISS.CMD_PLATFORM):
|
||||
@@ -779,6 +848,12 @@ class RNodeInterface(Interface):
|
||||
elif (byte == KISS.ERROR_TXFAILED):
|
||||
RNS.log(str(self)+" hardware TX error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Hardware transmit failure")
|
||||
elif (byte == KISS.ERROR_MEMORY_LOW):
|
||||
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+"): Memory exhausted", RNS.LOG_ERROR)
|
||||
self.hw_errors.append({"error": KISS.ERROR_MEMORY_LOW, "description": "Memory exhausted on connected device"})
|
||||
elif (byte == KISS.ERROR_MODEM_TIMEOUT):
|
||||
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+"): Modem communication timed out", RNS.LOG_ERROR)
|
||||
self.hw_errors.append({"error": KISS.ERROR_MODEM_TIMEOUT, "description": "Modem communication timed out on connected device"})
|
||||
else:
|
||||
RNS.log(str(self)+" hardware error (code "+RNS.hexrep(byte)+")", RNS.LOG_ERROR)
|
||||
raise IOError("Unknown hardware failure")
|
||||
@@ -853,9 +928,195 @@ class RNodeInterface(Interface):
|
||||
self.disable_external_framebuffer()
|
||||
self.setRadioState(KISS.RADIO_STATE_OFF)
|
||||
self.leave()
|
||||
|
||||
if self.use_ble:
|
||||
self.ble.close()
|
||||
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def get_battery_state(self):
|
||||
return self.r_battery_state
|
||||
|
||||
def get_battery_state_string(self):
|
||||
if self.r_battery_state == RNodeInterface.BATTERY_STATE_CHARGED:
|
||||
return "charged"
|
||||
elif self.r_battery_state == RNodeInterface.BATTERY_STATE_CHARGING:
|
||||
return "charging"
|
||||
elif self.r_battery_state == RNodeInterface.BATTERY_STATE_DISCHARGING:
|
||||
return "discharging"
|
||||
else:
|
||||
return "unknown"
|
||||
|
||||
def get_battery_percent(self):
|
||||
return self.r_battery_percent
|
||||
|
||||
def ble_receive(self, data):
|
||||
with self.ble_rx_lock:
|
||||
self.ble_rx_queue += data
|
||||
|
||||
def ble_waiting(self):
|
||||
return len(self.ble_tx_queue) > 0
|
||||
|
||||
def get_ble_waiting(self, n):
|
||||
with self.ble_tx_lock:
|
||||
data = self.ble_tx_queue[:n]
|
||||
self.ble_tx_queue = self.ble_tx_queue[n:]
|
||||
return data
|
||||
|
||||
def __str__(self):
|
||||
return "RNodeInterface["+str(self.name)+"]"
|
||||
|
||||
class BLEConnection():
|
||||
UART_SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||
UART_RX_CHAR_UUID = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||
UART_TX_CHAR_UUID = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
|
||||
bleak = None
|
||||
|
||||
SCAN_TIMEOUT = 2.0
|
||||
CONNECT_TIMEOUT = 5.0
|
||||
|
||||
@property
|
||||
def is_open(self):
|
||||
return self.connected
|
||||
|
||||
@property
|
||||
def in_waiting(self):
|
||||
buflen = len(self.owner.ble_rx_queue)
|
||||
return buflen > 0
|
||||
|
||||
def write(self, data_bytes):
|
||||
with self.owner.ble_tx_lock:
|
||||
self.owner.ble_tx_queue += data_bytes
|
||||
return len(data_bytes)
|
||||
|
||||
def read(self, n):
|
||||
with self.owner.ble_rx_lock:
|
||||
data = self.owner.ble_rx_queue[:n]
|
||||
self.owner.ble_rx_queue = self.owner.ble_rx_queue[n:]
|
||||
return data
|
||||
|
||||
def close(self):
|
||||
if self.connected and self.ble_device:
|
||||
RNS.log(f"Disconnecting BLE device from {self.owner}", RNS.LOG_DEBUG)
|
||||
self.must_disconnect = True
|
||||
|
||||
while self.connect_job_running:
|
||||
time.sleep(0.1)
|
||||
|
||||
def __init__(self, owner=None, target_name=None, target_bt_addr=None):
|
||||
self.owner = owner
|
||||
self.target_name = target_name
|
||||
self.target_bt_addr = target_bt_addr
|
||||
self.scan_timeout = BLEConnection.SCAN_TIMEOUT
|
||||
self.ble_device = None
|
||||
self.connected = False
|
||||
self.running = False
|
||||
self.should_run = False
|
||||
self.must_disconnect = False
|
||||
self.connect_job_running = False
|
||||
|
||||
import importlib
|
||||
if BLEConnection.bleak == None:
|
||||
if importlib.util.find_spec("bleak") != None:
|
||||
import bleak
|
||||
BLEConnection.bleak = bleak
|
||||
|
||||
import asyncio
|
||||
BLEConnection.asyncio = asyncio
|
||||
else:
|
||||
RNS.log("Using the RNode interface over BLE requires a the \"bleak\" module to be installed.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install one with the command: python3 -m pip install bleak", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
self.should_run = True
|
||||
self.connection_thread = threading.Thread(target=self.connection_job, daemon=True).start()
|
||||
|
||||
def connection_job(self):
|
||||
while (self.should_run):
|
||||
if self.ble_device == None:
|
||||
self.ble_device = self.find_target_device()
|
||||
|
||||
if type(self.ble_device) == self.bleak.backends.device.BLEDevice:
|
||||
if not self.connected:
|
||||
self.connect_device()
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
def connect_device(self):
|
||||
if self.ble_device != None and type(self.ble_device) == self.bleak.backends.device.BLEDevice:
|
||||
RNS.log(f"Connecting BLE device {self.ble_device} for {self.owner}...", RNS.LOG_DEBUG)
|
||||
|
||||
async def connect_job():
|
||||
self.connect_job_running = True
|
||||
async with self.bleak.BleakClient(self.ble_device, disconnected_callback=self.device_disconnected) as ble_client:
|
||||
def handle_rx(device, data):
|
||||
if self.owner != None:
|
||||
self.owner.ble_receive(data)
|
||||
|
||||
self.connected = True
|
||||
self.ble_device = ble_client
|
||||
self.owner.port = str(f"ble://{ble_client.address}")
|
||||
|
||||
loop = self.asyncio.get_running_loop()
|
||||
uart_service = ble_client.services.get_service(BLEConnection.UART_SERVICE_UUID)
|
||||
rx_characteristic = uart_service.get_characteristic(BLEConnection.UART_RX_CHAR_UUID)
|
||||
await ble_client.start_notify(BLEConnection.UART_TX_CHAR_UUID, handle_rx)
|
||||
|
||||
while self.connected:
|
||||
if self.owner != None and self.owner.ble_waiting():
|
||||
outbound_data = self.owner.get_ble_waiting(rx_characteristic.max_write_without_response_size)
|
||||
await ble_client.write_gatt_char(rx_characteristic, outbound_data, response=False)
|
||||
elif self.must_disconnect:
|
||||
await ble_client.disconnect()
|
||||
else:
|
||||
await self.asyncio.sleep(0.1)
|
||||
|
||||
|
||||
try:
|
||||
self.asyncio.run(connect_job())
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not connect BLE device {self.ble_device} for {self.owner}. Possibly missing authentication.", RNS.LOG_ERROR)
|
||||
self.connect_job_running = False
|
||||
|
||||
def device_disconnected(self, device):
|
||||
RNS.log(f"BLE device for {self.owner} disconnected", RNS.LOG_NOTICE)
|
||||
self.connected = False
|
||||
self.ble_device = None
|
||||
|
||||
def find_target_device(self):
|
||||
RNS.log(f"Searching for attachable BLE device for {self.owner}...", RNS.LOG_EXTREME)
|
||||
def device_filter(device: self.bleak.backends.device.BLEDevice, adv: self.bleak.backends.scanner.AdvertisementData):
|
||||
if BLEConnection.UART_SERVICE_UUID.lower() in adv.service_uuids:
|
||||
if self.device_bonded(device):
|
||||
if self.target_bt_addr == None and self.target_name == None:
|
||||
if device.name.startswith("RNode "):
|
||||
return True
|
||||
|
||||
if self.target_bt_addr == None or (device.address != None and device.address == self.target_bt_addr):
|
||||
if self.target_name == None or (device.name != None and device.name == self.target_name):
|
||||
return True
|
||||
|
||||
else:
|
||||
if self.target_bt_addr != None and device.address == self.target_bt_addr:
|
||||
RNS.log(f"Can't connect to target device {self.target_bt_addr} over BLE, device is not bonded", RNS.LOG_ERROR)
|
||||
|
||||
elif self.target_name != None and device.name == self.target_name:
|
||||
RNS.log(f"Can't connect to target device {self.target_name} over BLE, device is not bonded", RNS.LOG_ERROR)
|
||||
|
||||
return False
|
||||
|
||||
device = self.asyncio.run(self.bleak.BleakScanner.find_device_by_filter(device_filter, timeout=self.scan_timeout))
|
||||
return device
|
||||
|
||||
def device_bonded(self, device):
|
||||
try:
|
||||
if hasattr(device, "details"):
|
||||
if "props" in device.details and "Bonded" in device.details["props"]:
|
||||
if device.details["props"]["Bonded"] == True:
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while determining device bond status for {device}, the contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
return False
|
||||
|
||||
@@ -70,7 +70,7 @@ class KISS():
|
||||
CMD_INT1_DATA = 0x10
|
||||
CMD_INT2_DATA = 0x20
|
||||
CMD_INT3_DATA = 0x70
|
||||
CMD_INT4_DATA = 0x80
|
||||
CMD_INT4_DATA = 0x75
|
||||
CMD_INT5_DATA = 0x90
|
||||
CMD_INT6_DATA = 0xA0
|
||||
CMD_INT7_DATA = 0xB0
|
||||
@@ -82,8 +82,8 @@ class KISS():
|
||||
CMD_SEL_INT0 = 0x1E
|
||||
CMD_SEL_INT1 = 0x1F
|
||||
CMD_SEL_INT2 = 0x2F
|
||||
CMD_SEL_INT3 = 0x7F
|
||||
CMD_SEL_INT4 = 0x8F
|
||||
CMD_SEL_INT3 = 0x74
|
||||
CMD_SEL_INT4 = 0x7F
|
||||
CMD_SEL_INT5 = 0x9F
|
||||
CMD_SEL_INT6 = 0xAF
|
||||
CMD_SEL_INT7 = 0xBF
|
||||
@@ -106,6 +106,7 @@ class KISS():
|
||||
|
||||
PLATFORM_AVR = 0x90
|
||||
PLATFORM_ESP32 = 0x80
|
||||
PLATFORM_NRF52 = 0x70
|
||||
|
||||
SX127X = 0x00
|
||||
SX1276 = 0x01
|
||||
@@ -116,40 +117,42 @@ class KISS():
|
||||
SX1280 = 0x21
|
||||
|
||||
def int_data_cmd_to_index(int_data_cmd):
|
||||
match int_data_cmd:
|
||||
case KISS.CMD_INT0_DATA:
|
||||
return 0
|
||||
case KISS.CMD_INT1_DATA:
|
||||
return 1
|
||||
case KISS.CMD_INT2_DATA:
|
||||
return 2
|
||||
case KISS.CMD_INT3_DATA:
|
||||
return 3
|
||||
case KISS.CMD_INT4_DATA:
|
||||
return 4
|
||||
case KISS.CMD_INT5_DATA:
|
||||
return 5
|
||||
case KISS.CMD_INT6_DATA:
|
||||
return 6
|
||||
case KISS.CMD_INT7_DATA:
|
||||
return 7
|
||||
case KISS.CMD_INT8_DATA:
|
||||
return 8
|
||||
case KISS.CMD_INT9_DATA:
|
||||
return 9
|
||||
case KISS.CMD_INT10_DATA:
|
||||
return 10
|
||||
case KISS.CMD_INT11_DATA:
|
||||
return 11
|
||||
if int_data_cmd == KISS.CMD_INT0_DATA:
|
||||
return 0
|
||||
elif int_data_cmd == KISS.CMD_INT1_DATA:
|
||||
return 1
|
||||
elif int_data_cmd == KISS.CMD_INT2_DATA:
|
||||
return 2
|
||||
elif int_data_cmd == KISS.CMD_INT3_DATA:
|
||||
return 3
|
||||
elif int_data_cmd == KISS.CMD_INT4_DATA:
|
||||
return 4
|
||||
elif int_data_cmd == KISS.CMD_INT5_DATA:
|
||||
return 5
|
||||
elif int_data_cmd == KISS.CMD_INT6_DATA:
|
||||
return 6
|
||||
elif int_data_cmd == KISS.CMD_INT7_DATA:
|
||||
return 7
|
||||
elif int_data_cmd == KISS.CMD_INT8_DATA:
|
||||
return 8
|
||||
elif int_data_cmd == KISS.CMD_INT9_DATA:
|
||||
return 9
|
||||
elif int_data_cmd == KISS.CMD_INT10_DATA:
|
||||
return 10
|
||||
elif int_data_cmd == KISS.CMD_INT11_DATA:
|
||||
return 11
|
||||
else:
|
||||
return 0
|
||||
|
||||
def interface_type_to_str(interface_type):
|
||||
match interface_type:
|
||||
case KISS.SX126X | KISS.SX1262:
|
||||
if interface_type == KISS.SX126X or interface_type == KISS.SX1262:
|
||||
return "SX126X"
|
||||
case KISS.SX127X | KISS.SX1276 | KISS.SX1278:
|
||||
return "SX127X"
|
||||
case KISS.SX128X | KISS.SX1280:
|
||||
return "SX128X"
|
||||
elif interface_type == KISS.SX127X or interface_type == KISS.SX1276 or interface_type == KISS.SX1278:
|
||||
return "SX127X"
|
||||
elif interface_type == KISS.SX128X or interface_type == KISS.SX1280:
|
||||
return "SX128X"
|
||||
else:
|
||||
return "SX127X"
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
@@ -164,7 +167,7 @@ class RNodeMultiInterface(Interface):
|
||||
CALLSIGN_MAX_LEN = 32
|
||||
|
||||
REQUIRED_FW_VER_MAJ = 1
|
||||
REQUIRED_FW_VER_MIN = 73
|
||||
REQUIRED_FW_VER_MIN = 74
|
||||
|
||||
RECONNECT_WAIT = 5
|
||||
|
||||
@@ -247,6 +250,7 @@ class RNodeMultiInterface(Interface):
|
||||
if (not self.validcfg):
|
||||
raise ValueError("The configuration for "+str(self)+" contains errors, interface is offline")
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
self.open_port()
|
||||
|
||||
@@ -295,7 +299,7 @@ class RNodeMultiInterface(Interface):
|
||||
RNS.log("Could not detect device for "+str(self), RNS.LOG_ERROR)
|
||||
self.serial.close()
|
||||
else:
|
||||
if self.platform == KISS.PLATFORM_ESP32:
|
||||
if self.platform == KISS.PLATFORM_ESP32 or self.platform == KISS.PLATFORM_NRF52:
|
||||
self.display = True
|
||||
|
||||
RNS.log("Serial port "+self.port+" is now open")
|
||||
@@ -321,8 +325,8 @@ class RNodeMultiInterface(Interface):
|
||||
lt_alock=subint[9]
|
||||
)
|
||||
|
||||
interface.OUT = self.OUT
|
||||
interface.IN = self.IN
|
||||
interface.OUT = subint[10]
|
||||
interface.IN = True
|
||||
|
||||
interface.announce_rate_target = self.announce_rate_target
|
||||
interface.mode = self.mode
|
||||
@@ -915,46 +919,45 @@ class RNodeSubInterface(Interface):
|
||||
|
||||
super().__init__()
|
||||
|
||||
match index:
|
||||
case 0:
|
||||
sel_cmd = KISS.CMD_SEL_INT0
|
||||
data_cmd= KISS.CMD_INT0_DATA
|
||||
case 1:
|
||||
sel_cmd = KISS.CMD_SEL_INT1
|
||||
data_cmd= KISS.CMD_INT1_DATA
|
||||
case 2:
|
||||
sel_cmd = KISS.CMD_SEL_INT2
|
||||
data_cmd= KISS.CMD_INT2_DATA
|
||||
case 3:
|
||||
sel_cmd = KISS.CMD_SEL_INT3
|
||||
data_cmd= KISS.CMD_INT3_DATA
|
||||
case 4:
|
||||
sel_cmd = KISS.CMD_SEL_INT4
|
||||
data_cmd= KISS.CMD_INT4_DATA
|
||||
case 5:
|
||||
sel_cmd = KISS.CMD_SEL_INT5
|
||||
data_cmd= KISS.CMD_INT5_DATA
|
||||
case 6:
|
||||
sel_cmd = KISS.CMD_SEL_INT6
|
||||
data_cmd= KISS.CMD_INT6_DATA
|
||||
case 7:
|
||||
sel_cmd = KISS.CMD_SEL_INT7
|
||||
data_cmd= KISS.CMD_INT7_DATA
|
||||
case 8:
|
||||
sel_cmd = KISS.CMD_SEL_INT8
|
||||
data_cmd= KISS.CMD_INT8_DATA
|
||||
case 9:
|
||||
sel_cmd = KISS.CMD_SEL_INT9
|
||||
data_cmd= KISS.CMD_INT9_DATA
|
||||
case 10:
|
||||
sel_cmd = KISS.CMD_SEL_INT10
|
||||
data_cmd= KISS.CMD_INT10_DATA
|
||||
case 11:
|
||||
sel_cmd = KISS.CMD_SEL_INT11
|
||||
data_cmd= KISS.CMD_INT11_DATA
|
||||
case _:
|
||||
sel_cmd = KISS.CMD_SEL_INT0
|
||||
data_cmd= KISS.CMD_INT0_DATA
|
||||
if index == 0:
|
||||
sel_cmd = KISS.CMD_SEL_INT0
|
||||
data_cmd= KISS.CMD_INT0_DATA
|
||||
elif index == 1:
|
||||
sel_cmd = KISS.CMD_SEL_INT1
|
||||
data_cmd= KISS.CMD_INT1_DATA
|
||||
elif index == 2:
|
||||
sel_cmd = KISS.CMD_SEL_INT2
|
||||
data_cmd= KISS.CMD_INT2_DATA
|
||||
elif index == 3:
|
||||
sel_cmd = KISS.CMD_SEL_INT3
|
||||
data_cmd= KISS.CMD_INT3_DATA
|
||||
elif index == 4:
|
||||
sel_cmd = KISS.CMD_SEL_INT4
|
||||
data_cmd= KISS.CMD_INT4_DATA
|
||||
elif index == 5:
|
||||
sel_cmd = KISS.CMD_SEL_INT5
|
||||
data_cmd= KISS.CMD_INT5_DATA
|
||||
elif index == 6:
|
||||
sel_cmd = KISS.CMD_SEL_INT6
|
||||
data_cmd= KISS.CMD_INT6_DATA
|
||||
elif index == 7:
|
||||
sel_cmd = KISS.CMD_SEL_INT7
|
||||
data_cmd= KISS.CMD_INT7_DATA
|
||||
elif index == 8:
|
||||
sel_cmd = KISS.CMD_SEL_INT8
|
||||
data_cmd= KISS.CMD_INT8_DATA
|
||||
elif index == 9:
|
||||
sel_cmd = KISS.CMD_SEL_INT9
|
||||
data_cmd= KISS.CMD_INT9_DATA
|
||||
elif index == 10:
|
||||
sel_cmd = KISS.CMD_SEL_INT10
|
||||
data_cmd= KISS.CMD_INT10_DATA
|
||||
elif index == 11:
|
||||
sel_cmd = KISS.CMD_SEL_INT11
|
||||
data_cmd= KISS.CMD_INT11_DATA
|
||||
else:
|
||||
sel_cmd = KISS.CMD_SEL_INT0
|
||||
data_cmd= KISS.CMD_INT0_DATA
|
||||
|
||||
self.owner = owner
|
||||
self.name = name
|
||||
@@ -1005,6 +1008,11 @@ class RNodeSubInterface(Interface):
|
||||
self.parent_interface = parent_interface
|
||||
self.announce_rate_target = None
|
||||
|
||||
self.mode = None
|
||||
self.announce_cap = None
|
||||
self.bitrate = None
|
||||
self.ifac_size = None
|
||||
|
||||
# add this interface to the subinterfaces array
|
||||
self.parent_interface.subinterfaces[index] = self
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
# Copyright (c) 2016-2024 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
|
||||
@@ -68,6 +68,7 @@ class Link:
|
||||
Timeout for link establishment in seconds per hop to destination.
|
||||
"""
|
||||
|
||||
TRAFFIC_TIMEOUT_MIN_MS = 5
|
||||
TRAFFIC_TIMEOUT_FACTOR = 6
|
||||
KEEPALIVE_TIMEOUT_FACTOR = 4
|
||||
"""
|
||||
@@ -124,7 +125,7 @@ class Link:
|
||||
link.last_inbound = time.time()
|
||||
link.start_watchdog()
|
||||
|
||||
RNS.log("Incoming link request "+str(link)+" accepted", RNS.LOG_DEBUG)
|
||||
RNS.log("Incoming link request "+str(link)+" accepted on "+str(link.attached_interface), RNS.LOG_DEBUG)
|
||||
return link
|
||||
|
||||
except Exception as e:
|
||||
@@ -142,6 +143,7 @@ class Link:
|
||||
raise TypeError("Links can only be established to the \"single\" destination type")
|
||||
self.rtt = None
|
||||
self.establishment_cost = 0
|
||||
self.establishment_rate = None
|
||||
self.callbacks = LinkCallbacks()
|
||||
self.resource_strategy = Link.ACCEPT_NONE
|
||||
self.outgoing_resources = []
|
||||
@@ -760,7 +762,7 @@ class Link:
|
||||
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])):
|
||||
if packet.receiving_interface != self.attached_interface:
|
||||
RNS.log("Link-associated packet received on unexpected interface! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
|
||||
RNS.log(f"Link-associated packet received on unexpected interface {packet.receiving_interface} instead of {self.attached_interface}! Someone might be trying to manipulate your communication!", RNS.LOG_ERROR)
|
||||
else:
|
||||
self.last_inbound = time.time()
|
||||
if packet.context != RNS.Packet.KEEPALIVE:
|
||||
@@ -774,6 +776,7 @@ class Link:
|
||||
should_query = False
|
||||
if packet.context == RNS.Packet.NONE:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
packet.ratchet_id = self.link_id
|
||||
if plaintext != None:
|
||||
if self.callbacks.packet != None:
|
||||
thread = threading.Thread(target=self.callbacks.packet, args=(plaintext, packet))
|
||||
@@ -900,6 +903,11 @@ class Link:
|
||||
if not packet.packet_hash in resource.req_hashlist:
|
||||
resource.req_hashlist.append(packet.packet_hash)
|
||||
resource.request(plaintext)
|
||||
|
||||
# TODO: Test and possibly enable this at some point
|
||||
# def request_job():
|
||||
# resource.request(plaintext)
|
||||
# threading.Thread(target=request_job, daemon=True).start()
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_HMU:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
# Copyright (c) 2016-2024 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
|
||||
@@ -83,6 +83,10 @@ class Packet:
|
||||
LRRTT = 0xFE # Packet is a link request round-trip time measurement
|
||||
LRPROOF = 0xFF # Packet is a link request proof
|
||||
|
||||
# Context flag values
|
||||
FLAG_SET = 0x01
|
||||
FLAG_UNSET = 0x00
|
||||
|
||||
# This is used to calculate allowable
|
||||
# payload sizes
|
||||
HEADER_MAXSIZE = RNS.Reticulum.HEADER_MAXSIZE
|
||||
@@ -102,7 +106,9 @@ class Packet:
|
||||
|
||||
TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
|
||||
|
||||
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST, header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True):
|
||||
def __init__(self, destination, data, packet_type = DATA, context = NONE, transport_type = RNS.Transport.BROADCAST,
|
||||
header_type = HEADER_1, transport_id = None, attached_interface = None, create_receipt = True, context_flag=FLAG_UNSET):
|
||||
|
||||
if destination != None:
|
||||
if transport_type == None:
|
||||
transport_type = RNS.Transport.BROADCAST
|
||||
@@ -111,6 +117,7 @@ class Packet:
|
||||
self.packet_type = packet_type
|
||||
self.transport_type = transport_type
|
||||
self.context = context
|
||||
self.context_flag = context_flag
|
||||
|
||||
self.hops = 0;
|
||||
self.destination = destination
|
||||
@@ -133,6 +140,7 @@ class Packet:
|
||||
self.MTU = RNS.Reticulum.MTU
|
||||
self.sent_at = None
|
||||
self.packet_hash = None
|
||||
self.ratchet_id = None
|
||||
|
||||
self.attached_interface = attached_interface
|
||||
self.receiving_interface = None
|
||||
@@ -142,9 +150,10 @@ 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 << 2) | self.packet_type
|
||||
packed_flags = (self.header_type << 6) | (self.context_flag << 5) | (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
|
||||
packed_flags = (self.header_type << 6) | (self.context_flag << 5) | (self.transport_type << 4) | (self.destination.type << 2) | self.packet_type
|
||||
|
||||
return packed_flags
|
||||
|
||||
def pack(self):
|
||||
@@ -187,6 +196,8 @@ class Packet:
|
||||
# In all other cases, we encrypt the packet
|
||||
# with the destination's encryption method
|
||||
self.ciphertext = self.destination.encrypt(self.data)
|
||||
if hasattr(self.destination, "latest_ratchet_id"):
|
||||
self.ratchet_id = self.destination.latest_ratchet_id
|
||||
|
||||
if self.header_type == Packet.HEADER_2:
|
||||
if self.transport_id != None:
|
||||
@@ -216,7 +227,8 @@ class Packet:
|
||||
self.hops = self.raw[1]
|
||||
|
||||
self.header_type = (self.flags & 0b01000000) >> 6
|
||||
self.transport_type = (self.flags & 0b00110000) >> 4
|
||||
self.context_flag = (self.flags & 0b00100000) >> 5
|
||||
self.transport_type = (self.flags & 0b00010000) >> 4
|
||||
self.destination_type = (self.flags & 0b00001100) >> 2
|
||||
self.packet_type = (self.flags & 0b00000011)
|
||||
|
||||
@@ -369,7 +381,7 @@ class PacketReceipt:
|
||||
self.proof_packet = None
|
||||
|
||||
if packet.destination.type == RNS.Destination.LINK:
|
||||
self.timeout = packet.destination.rtt * packet.destination.traffic_timeout_factor
|
||||
self.timeout = max(packet.destination.rtt * packet.destination.traffic_timeout_factor, RNS.Link.TRAFFIC_TIMEOUT_MIN_MS/1000)
|
||||
else:
|
||||
self.timeout = RNS.Reticulum.get_instance().get_first_hop_timeout(self.destination.hash)
|
||||
self.timeout += Packet.TIMEOUT_PER_HOP * RNS.Transport.hops_to(self.destination.hash)
|
||||
@@ -409,6 +421,7 @@ class PacketReceipt:
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while evaluating external delivery callback for "+str(link), RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
return True
|
||||
else:
|
||||
@@ -440,7 +453,7 @@ class PacketReceipt:
|
||||
# This is an explicit proof
|
||||
proof_hash = proof[:RNS.Identity.HASHLENGTH//8]
|
||||
signature = proof[RNS.Identity.HASHLENGTH//8:RNS.Identity.HASHLENGTH//8+RNS.Identity.SIGLENGTH//8]
|
||||
if proof_hash == self.hash:
|
||||
if proof_hash == self.hash and hasattr(self.destination, "identity") and self.destination.identity != None:
|
||||
proof_valid = self.destination.identity.validate(signature, self.hash)
|
||||
if proof_valid:
|
||||
self.status = PacketReceipt.DELIVERED
|
||||
|
||||
@@ -54,6 +54,9 @@ class Resource:
|
||||
# The maximum window size for transfers on slow links
|
||||
WINDOW_MAX_SLOW = 10
|
||||
|
||||
# The maximum window size for transfers on very slow links
|
||||
WINDOW_MAX_VERY_SLOW = 4
|
||||
|
||||
# The maximum window size for transfers on fast links
|
||||
WINDOW_MAX_FAST = 75
|
||||
|
||||
@@ -65,12 +68,22 @@ class Resource:
|
||||
# rounds, the fast link window size will be allowed.
|
||||
FAST_RATE_THRESHOLD = WINDOW_MAX_SLOW - WINDOW - 2
|
||||
|
||||
# If the very slow rate is sustained for this many request
|
||||
# rounds, window will be capped to the very slow limit.
|
||||
VERY_SLOW_RATE_THRESHOLD = 2
|
||||
|
||||
# If the RTT rate is higher than this value,
|
||||
# the max window size for fast links will be used.
|
||||
# The default is 50 Kbps (the value is stored in
|
||||
# bytes per second, hence the "/ 8").
|
||||
RATE_FAST = (50*1000) / 8
|
||||
|
||||
# If the RTT rate is lower than this value,
|
||||
# the window size will be capped at .
|
||||
# The default is 50 Kbps (the value is stored in
|
||||
# bytes per second, hence the "/ 8").
|
||||
RATE_VERY_SLOW = (2*1000) / 8
|
||||
|
||||
# The minimum allowed flexibility of the window size.
|
||||
# The difference between window_max and window_min
|
||||
# will never be smaller than this value.
|
||||
@@ -147,7 +160,7 @@ class Resource:
|
||||
resource.encrypted = True if resource.flags & 0x01 else False
|
||||
resource.compressed = True if resource.flags >> 1 & 0x01 else False
|
||||
resource.initiator = False
|
||||
resource.callback = callback
|
||||
resource.callback = callback
|
||||
resource.__progress_callback = progress_callback
|
||||
resource.total_parts = int(math.ceil(resource.size/float(Resource.SDU)))
|
||||
resource.received_count = 0
|
||||
@@ -220,7 +233,6 @@ class Resource:
|
||||
data_size = os.stat(data.name).st_size
|
||||
|
||||
self.total_size = data_size
|
||||
self.grand_total_parts = math.ceil(data_size/Resource.SDU)
|
||||
|
||||
if data_size <= Resource.MAX_EFFICIENT_SIZE:
|
||||
self.total_segments = 1
|
||||
@@ -241,7 +253,6 @@ class Resource:
|
||||
|
||||
elif isinstance(data, bytes):
|
||||
data_size = len(data)
|
||||
self.grand_total_parts = math.ceil(data_size/Resource.SDU)
|
||||
self.total_size = data_size
|
||||
|
||||
resource_data = data
|
||||
@@ -275,6 +286,7 @@ class Resource:
|
||||
self.req_resp_rtt_rate = 0
|
||||
self.rtt_rxd_bytes_at_part_req = 0
|
||||
self.fast_rate_rounds = 0
|
||||
self.very_slow_rate_rounds = 0
|
||||
self.request_id = request_id
|
||||
self.is_response = is_response
|
||||
|
||||
@@ -334,6 +346,7 @@ class Resource:
|
||||
self.size = len(self.data)
|
||||
self.sent_parts = 0
|
||||
hashmap_entries = int(math.ceil(self.size/float(Resource.SDU)))
|
||||
self.total_parts = hashmap_entries
|
||||
|
||||
hashmap_ok = False
|
||||
while not hashmap_ok:
|
||||
@@ -759,6 +772,12 @@ class Resource:
|
||||
if self.fast_rate_rounds == Resource.FAST_RATE_THRESHOLD:
|
||||
self.window_max = Resource.WINDOW_MAX_FAST
|
||||
|
||||
if self.fast_rate_rounds == 0 and self.req_data_rtt_rate < Resource.RATE_VERY_SLOW and self.very_slow_rate_rounds < Resource.VERY_SLOW_RATE_THRESHOLD:
|
||||
self.very_slow_rate_rounds += 1
|
||||
|
||||
if self.very_slow_rate_rounds == Resource.VERY_SLOW_RATE_THRESHOLD:
|
||||
self.window_max = Resource.WINDOW_MAX_VERY_SLOW
|
||||
|
||||
self.request_next()
|
||||
else:
|
||||
self.receiving_part = False
|
||||
@@ -943,21 +962,68 @@ class Resource:
|
||||
"""
|
||||
if self.status == RNS.Resource.COMPLETE and self.segment_index == self.total_segments:
|
||||
return 1.0
|
||||
|
||||
elif self.initiator:
|
||||
self.processed_parts = (self.segment_index-1)*math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
|
||||
self.processed_parts += self.sent_parts
|
||||
self.progress_total_parts = float(self.grand_total_parts)
|
||||
else:
|
||||
self.processed_parts = (self.segment_index-1)*math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
|
||||
self.processed_parts += self.received_count
|
||||
if self.split:
|
||||
self.progress_total_parts = float(math.ceil(self.total_size/Resource.SDU))
|
||||
else:
|
||||
if not self.split:
|
||||
self.processed_parts = self.sent_parts
|
||||
self.progress_total_parts = float(self.total_parts)
|
||||
|
||||
else:
|
||||
is_last_segment = self.segment_index != self.total_segments
|
||||
total_segments = self.total_segments
|
||||
processed_segments = self.segment_index-1
|
||||
|
||||
current_segment_parts = self.total_parts
|
||||
max_parts_per_segment = math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
|
||||
|
||||
previously_processed_parts = processed_segments*max_parts_per_segment
|
||||
|
||||
if current_segment_parts < max_parts_per_segment:
|
||||
current_segment_factor = max_parts_per_segment / current_segment_parts
|
||||
else:
|
||||
current_segment_factor = 1
|
||||
|
||||
self.processed_parts = previously_processed_parts + self.sent_parts*current_segment_factor
|
||||
self.progress_total_parts = self.total_segments*max_parts_per_segment
|
||||
|
||||
else:
|
||||
if not self.split:
|
||||
self.processed_parts = self.received_count
|
||||
self.progress_total_parts = float(self.total_parts)
|
||||
|
||||
else:
|
||||
is_last_segment = self.segment_index != self.total_segments
|
||||
total_segments = self.total_segments
|
||||
processed_segments = self.segment_index-1
|
||||
|
||||
current_segment_parts = self.total_parts
|
||||
max_parts_per_segment = math.ceil(Resource.MAX_EFFICIENT_SIZE/Resource.SDU)
|
||||
|
||||
previously_processed_parts = processed_segments*max_parts_per_segment
|
||||
|
||||
if current_segment_parts < max_parts_per_segment:
|
||||
current_segment_factor = max_parts_per_segment / current_segment_parts
|
||||
else:
|
||||
current_segment_factor = 1
|
||||
|
||||
self.processed_parts = previously_processed_parts + self.received_count*current_segment_factor
|
||||
self.progress_total_parts = self.total_segments*max_parts_per_segment
|
||||
|
||||
|
||||
progress = min(1.0, self.processed_parts / self.progress_total_parts)
|
||||
return progress
|
||||
|
||||
def get_segment_progress(self):
|
||||
if self.status == RNS.Resource.COMPLETE and self.segment_index == self.total_segments:
|
||||
return 1.0
|
||||
elif self.initiator:
|
||||
processed_parts = self.sent_parts
|
||||
else:
|
||||
processed_parts = self.received_count
|
||||
|
||||
progress = min(1.0, processed_parts / self.total_parts)
|
||||
return progress
|
||||
|
||||
def get_transfer_size(self):
|
||||
"""
|
||||
:returns: The number of bytes needed to transfer the resource.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
# Copyright (c) 2016-2024 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
|
||||
@@ -160,6 +160,9 @@ class Reticulum:
|
||||
RNS.Transport.exit_handler()
|
||||
RNS.Identity.exit_handler()
|
||||
|
||||
if RNS.profiler_ran:
|
||||
RNS.profiler_results()
|
||||
|
||||
@staticmethod
|
||||
def sigint_handler(signal, frame):
|
||||
RNS.Transport.detach_interfaces()
|
||||
@@ -295,6 +298,7 @@ class Reticulum:
|
||||
|
||||
def __start_jobs(self):
|
||||
if self.jobs_thread == None:
|
||||
RNS.Identity._clean_ratchets()
|
||||
self.jobs_thread = threading.Thread(target=self.__jobs)
|
||||
self.jobs_thread.daemon = True
|
||||
self.jobs_thread.start()
|
||||
@@ -912,11 +916,28 @@ class Reticulum:
|
||||
st_alock = float(c["airtime_limit_short"]) if "airtime_limit_short" in c else None
|
||||
lt_alock = float(c["airtime_limit_long"]) if "airtime_limit_long" in c else None
|
||||
|
||||
force_ble = False
|
||||
ble_name = None
|
||||
ble_addr = None
|
||||
|
||||
port = c["port"] if "port" in c else None
|
||||
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for RNode interface")
|
||||
|
||||
if port != None:
|
||||
ble_uri_scheme = "ble://"
|
||||
if port.lower().startswith(ble_uri_scheme):
|
||||
force_ble = True
|
||||
ble_string = port[len(ble_uri_scheme):]
|
||||
port = None
|
||||
if len(ble_string) == 0:
|
||||
pass
|
||||
elif len(ble_string.split(":")) == 6 and len(ble_string) == 17:
|
||||
ble_addr = ble_string
|
||||
else:
|
||||
ble_name = ble_string
|
||||
|
||||
interface = RNodeInterface.RNodeInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
@@ -930,7 +951,10 @@ class Reticulum:
|
||||
id_interval = id_interval,
|
||||
id_callsign = id_callsign,
|
||||
st_alock = st_alock,
|
||||
lt_alock = lt_alock
|
||||
lt_alock = lt_alock,
|
||||
ble_addr = ble_addr,
|
||||
ble_name = ble_name,
|
||||
force_ble = force_ble,
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
@@ -967,7 +991,7 @@ class Reticulum:
|
||||
enabled_count += 1
|
||||
|
||||
# Create an array with a row for each subinterface
|
||||
subint_config = [[0 for x in range(10)] for y in range(enabled_count)]
|
||||
subint_config = [[0 for x in range(11)] for y in range(enabled_count)]
|
||||
subint_index = 0
|
||||
|
||||
for subinterface in c:
|
||||
@@ -996,6 +1020,11 @@ class Reticulum:
|
||||
subint_config[subint_index][8] = st_alock
|
||||
lt_alock = float(subinterface_config["airtime_limit_long"]) if "airtime_limit_long" in subinterface_config else None
|
||||
subint_config[subint_index][9] = lt_alock
|
||||
|
||||
if "outgoing" in subinterface_config and subinterface_config.as_bool("outgoing") == False:
|
||||
subint_config[subint_index][10] = False
|
||||
else:
|
||||
subint_config[subint_index][10] = True
|
||||
subint_index += 1
|
||||
|
||||
# if no subinterfaces are defined
|
||||
@@ -1021,10 +1050,8 @@ class Reticulum:
|
||||
id_callsign = id_callsign
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
interface.IN = False
|
||||
interface.OUT = False
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
@@ -1075,6 +1102,9 @@ class Reticulum:
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
if isinstance(interface, RNS.Interfaces.RNodeMultiInterface.RNodeMultiInterface):
|
||||
interface.start()
|
||||
|
||||
else:
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
|
||||
|
||||
@@ -1254,6 +1284,10 @@ class Reticulum:
|
||||
else:
|
||||
ifstats["clients"] = None
|
||||
|
||||
if hasattr(interface, "parent_interface") and interface.parent_interface != None:
|
||||
ifstats["parent_interface_name"] = str(interface.parent_interface)
|
||||
ifstats["parent_interface_hash"] = interface.parent_interface.get_hash()
|
||||
|
||||
if hasattr(interface, "i2p") and hasattr(interface, "connectable"):
|
||||
if interface.connectable:
|
||||
ifstats["i2p_connectable"] = True
|
||||
@@ -1291,6 +1325,13 @@ class Reticulum:
|
||||
if hasattr(interface, "r_channel_load_long"):
|
||||
ifstats["channel_load_long"] = interface.r_channel_load_long
|
||||
|
||||
if hasattr(interface, "r_battery_state"):
|
||||
if interface.r_battery_state != 0x00:
|
||||
ifstats["battery_state"] = interface.r_battery_state
|
||||
|
||||
if hasattr(interface, "r_battery_percent"):
|
||||
ifstats["battery_percent"] = interface.r_battery_percent
|
||||
|
||||
if hasattr(interface, "bitrate"):
|
||||
if interface.bitrate != None:
|
||||
ifstats["bitrate"] = interface.bitrate
|
||||
@@ -1319,6 +1360,9 @@ class Reticulum:
|
||||
ifstats["announce_queue"] = None
|
||||
|
||||
ifstats["name"] = str(interface)
|
||||
ifstats["short_name"] = str(interface.name)
|
||||
ifstats["hash"] = interface.get_hash()
|
||||
ifstats["type"] = str(type(interface).__name__)
|
||||
ifstats["rxb"] = interface.rxb
|
||||
ifstats["txb"] = interface.txb
|
||||
ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2023 Mark Qvist / unsigned.io and contributors.
|
||||
# Copyright (c) 2016-2024 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
|
||||
@@ -52,20 +52,20 @@ class Transport:
|
||||
Maximum amount of hops that Reticulum will transport a packet.
|
||||
"""
|
||||
|
||||
PATHFINDER_R = 1 # Retransmit retries
|
||||
PATHFINDER_G = 5 # Retry grace period
|
||||
PATHFINDER_RW = 0.5 # Random window for announce rebroadcast
|
||||
PATHFINDER_E = 60*60*24*7 # Path expiration of one week
|
||||
AP_PATH_TIME = 60*60*24 # Path expiration of one day for Access Point paths
|
||||
ROAMING_PATH_TIME = 60*60*6 # Path expiration of 6 hours for Roaming paths
|
||||
PATHFINDER_R = 1 # Retransmit retries
|
||||
PATHFINDER_G = 5 # Retry grace period
|
||||
PATHFINDER_RW = 0.5 # Random window for announce rebroadcast
|
||||
PATHFINDER_E = 60*60*24*7 # Path expiration of one week
|
||||
AP_PATH_TIME = 60*60*24 # Path expiration of one day for Access Point paths
|
||||
ROAMING_PATH_TIME = 60*60*6 # Path expiration of 6 hours for Roaming paths
|
||||
|
||||
# TODO: Calculate an optimal number for this in
|
||||
# various situations
|
||||
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
|
||||
LOCAL_REBROADCASTS_MAX = 2 # How many local rebroadcasts of an announce is allowed
|
||||
|
||||
PATH_REQUEST_TIMEOUT = 15 # Default timuout for client path requests in seconds
|
||||
PATH_REQUEST_GRACE = 0.4 # Grace time before a path announcement is made, allows directly reachable peers to respond first
|
||||
PATH_REQUEST_RG = 0.6 # Extra grace time for roaming-mode interfaces to allow more suitable peers to respond first
|
||||
PATH_REQUEST_RG = 1.5 # Extra grace time for roaming-mode interfaces to allow more suitable peers to respond first
|
||||
PATH_REQUEST_MI = 20 # Minimum interval in seconds for automated path requests
|
||||
|
||||
STATE_UNKNOWN = 0x00
|
||||
@@ -301,12 +301,23 @@ class Transport:
|
||||
RNS.log("Transport instance "+str(Transport.identity)+" started", RNS.LOG_VERBOSE)
|
||||
Transport.start_time = time.time()
|
||||
|
||||
# Sort interfaces according to bitrate
|
||||
Transport.prioritize_interfaces()
|
||||
|
||||
# Synthesize tunnels for any interfaces wanting it
|
||||
for interface in Transport.interfaces:
|
||||
interface.tunnel_id = None
|
||||
if hasattr(interface, "wants_tunnel") and interface.wants_tunnel:
|
||||
Transport.synthesize_tunnel(interface)
|
||||
|
||||
@staticmethod
|
||||
def prioritize_interfaces():
|
||||
try:
|
||||
Transport.interfaces.sort(key=lambda interface: interface.bitrate, reverse=True)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not prioritize interfaces according to bitrate. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def jobloop():
|
||||
while (True):
|
||||
@@ -403,7 +414,8 @@ class Transport:
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = attached_interface
|
||||
attached_interface = attached_interface,
|
||||
context_flag = packet.context_flag,
|
||||
)
|
||||
|
||||
new_packet.hops = announce_entry[4]
|
||||
@@ -663,6 +675,7 @@ class Transport:
|
||||
Transport.tables_last_culled = time.time()
|
||||
|
||||
if time.time() > Transport.interface_last_jobs + Transport.interface_jobs_interval:
|
||||
Transport.prioritize_interfaces()
|
||||
for interface in Transport.interfaces:
|
||||
interface.process_held_announces()
|
||||
Transport.interface_last_jobs = time.time()
|
||||
@@ -1343,8 +1356,10 @@ class Transport:
|
||||
new_raw += packet.raw[2:]
|
||||
Transport.transmit(outbound_interface, new_raw)
|
||||
Transport.link_table[packet.destination_hash][0] = time.time()
|
||||
else:
|
||||
pass
|
||||
|
||||
# TODO: Test and possibly enable this at some point
|
||||
# Transport.jobs_locked = False
|
||||
# return
|
||||
|
||||
|
||||
# Announce handling. Handles logic related to incoming
|
||||
@@ -1601,7 +1616,8 @@ class Transport:
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = local_interface
|
||||
attached_interface = local_interface,
|
||||
context_flag = packet.context_flag,
|
||||
)
|
||||
|
||||
new_announce.hops = packet.hops
|
||||
@@ -1618,7 +1634,8 @@ class Transport:
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = local_interface
|
||||
attached_interface = local_interface,
|
||||
context_flag = packet.context_flag,
|
||||
)
|
||||
|
||||
new_announce.hops = packet.hops
|
||||
@@ -1649,7 +1666,8 @@ class Transport:
|
||||
header_type = RNS.Packet.HEADER_2,
|
||||
transport_type = Transport.TRANSPORT,
|
||||
transport_id = Transport.identity.hash,
|
||||
attached_interface = attached_interface
|
||||
attached_interface = attached_interface,
|
||||
context_flag = packet.context_flag,
|
||||
)
|
||||
|
||||
new_announce.hops = packet.hops
|
||||
@@ -1671,30 +1689,39 @@ class Transport:
|
||||
|
||||
# Call externally registered callbacks from apps
|
||||
# wanting to know when an announce arrives
|
||||
if packet.context != RNS.Packet.PATH_RESPONSE:
|
||||
for handler in Transport.announce_handlers:
|
||||
try:
|
||||
# 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
|
||||
for handler in Transport.announce_handlers:
|
||||
try:
|
||||
# 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:
|
||||
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
|
||||
|
||||
# If this is a path response, check whether the
|
||||
# handler wants to receive it.
|
||||
if packet.context == RNS.Packet.PATH_RESPONSE:
|
||||
if hasattr(handler, "receive_path_responses") and handler.receive_path_responses == True:
|
||||
pass
|
||||
else:
|
||||
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
|
||||
if execute_callback:
|
||||
handler.received_announce(
|
||||
destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash)
|
||||
)
|
||||
except Exception as e:
|
||||
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
execute_callback = False
|
||||
|
||||
if execute_callback:
|
||||
handler.received_announce(
|
||||
destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash)
|
||||
)
|
||||
except Exception as e:
|
||||
RNS.log("Error while processing external announce callback.", RNS.LOG_ERROR)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
# Handling for link requests to local destinations
|
||||
elif packet.packet_type == RNS.Packet.LINKREQUEST:
|
||||
@@ -1709,8 +1736,19 @@ class Transport:
|
||||
if packet.destination_type == RNS.Destination.LINK:
|
||||
for link in Transport.active_links:
|
||||
if link.link_id == packet.destination_hash:
|
||||
packet.link = link
|
||||
link.receive(packet)
|
||||
if link.attached_interface == packet.receiving_interface:
|
||||
packet.link = link
|
||||
link.receive(packet)
|
||||
else:
|
||||
# In the strange and rare case that an interface
|
||||
# is partly malfunctioning, and a link-associated
|
||||
# packet is being received on an interface that
|
||||
# has failed sending, and transport has failed over
|
||||
# to another path, we remove this packet hash from
|
||||
# the filter hashlist so the link can receive the
|
||||
# packet when it finally arrives over another path.
|
||||
while packet.packet_hash in Transport.packet_hashlist:
|
||||
Transport.packet_hashlist.remove(packet.packet_hash)
|
||||
else:
|
||||
for destination in Transport.destinations:
|
||||
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
|
||||
@@ -1973,7 +2011,9 @@ class Transport:
|
||||
"""
|
||||
Registers an announce handler.
|
||||
|
||||
:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)* callable. See the :ref:`Announce Example<example-announce>` for more info.
|
||||
:param handler: Must be an object with an *aspect_filter* attribute and a *received_announce(destination_hash, announced_identity, app_data)*
|
||||
callable. Can optionally have a *receive_path_responses* attribute set to ``True``, to also receive all path responses, in addition to live
|
||||
announces. See the :ref:`Announce Example<example-announce>` for more info.
|
||||
"""
|
||||
if hasattr(handler, "received_announce") and callable(handler.received_announce):
|
||||
if hasattr(handler, "aspect_filter"):
|
||||
|
||||
@@ -35,13 +35,18 @@ APP_NAME = "rncp"
|
||||
allow_all = False
|
||||
allow_fetch = False
|
||||
fetch_jail = None
|
||||
save_path = None
|
||||
show_phy_rates = False
|
||||
allowed_identity_hashes = []
|
||||
|
||||
REQ_FETCH_NOT_ALLOWED = 0xF0
|
||||
|
||||
es = " "
|
||||
erase_str = "\33[2K\r"
|
||||
|
||||
def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identity = False,
|
||||
limit = None, disable_auth = None, fetch_allowed = False, jail = None, announce = False):
|
||||
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail
|
||||
limit = None, disable_auth = None, fetch_allowed = False, jail = None, save = None, announce = False):
|
||||
global allow_all, allow_fetch, allowed_identity_hashes, fetch_jail, save_path
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
allow_fetch = fetch_allowed
|
||||
@@ -56,6 +61,20 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
|
||||
fetch_jail = os.path.abspath(os.path.expanduser(jail))
|
||||
RNS.log("Restricting fetch requests to paths under \""+fetch_jail+"\"", RNS.LOG_VERBOSE)
|
||||
|
||||
if save != None:
|
||||
sp = os.path.abspath(os.path.expanduser(save))
|
||||
if os.path.isdir(sp):
|
||||
if os.access(sp, os.W_OK):
|
||||
save_path = sp
|
||||
else:
|
||||
RNS.log("Output directory not writable", RNS.LOG_ERROR)
|
||||
exit(4)
|
||||
else:
|
||||
RNS.log("Output directory not found", RNS.LOG_ERROR)
|
||||
exit(3)
|
||||
|
||||
RNS.log("Saving received files in \""+save_path+"\"", RNS.LOG_VERBOSE)
|
||||
|
||||
identity_path = RNS.Reticulum.identitypath+"/"+APP_NAME
|
||||
if os.path.isfile(identity_path):
|
||||
identity = RNS.Identity.from_file(identity_path)
|
||||
@@ -130,10 +149,15 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
|
||||
if not allow_fetch:
|
||||
return REQ_FETCH_NOT_ALLOWED
|
||||
|
||||
file_path = os.path.abspath(os.path.expanduser(data))
|
||||
if fetch_jail:
|
||||
if not file_path.startswith(jail):
|
||||
if data.startswith(fetch_jail+"/"):
|
||||
data = data.replace(fetch_jail+"/", "")
|
||||
file_path = os.path.abspath(os.path.expanduser(f"{fetch_jail}/{data}"))
|
||||
if not file_path.startswith(fetch_jail+"/"):
|
||||
RNS.log(f"Disallowing fetch request for {file_path} outside of fetch jail {fetch_jail}", RNS.LOG_WARNING)
|
||||
return REQ_FETCH_NOT_ALLOWED
|
||||
else:
|
||||
file_path = os.path.abspath(os.path.expanduser(f"{data}"))
|
||||
|
||||
target_link = None
|
||||
for link in RNS.Transport.active_links:
|
||||
@@ -168,7 +192,13 @@ def listen(configdir, verbosity = 0, quietness = 0, allowed = [], display_identi
|
||||
|
||||
|
||||
destination.set_link_established_callback(client_link_established)
|
||||
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
|
||||
if allow_fetch:
|
||||
if allow_all:
|
||||
RNS.log("Allowing unauthenticated fetch requests", RNS.LOG_WARNING)
|
||||
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_ALL)
|
||||
else:
|
||||
destination.register_request_handler("fetch_file", response_generator=fetch_request, allow=RNS.Destination.ALLOW_LIST, allowed_list=allowed_identity_hashes)
|
||||
|
||||
print("rncp listening on "+RNS.prettyhexrep(destination.hash))
|
||||
|
||||
if announce >= 0:
|
||||
@@ -227,6 +257,7 @@ def receive_resource_started(resource):
|
||||
print("Starting resource transfer "+RNS.prettyhexrep(resource.hash)+id_str)
|
||||
|
||||
def receive_resource_concluded(resource):
|
||||
global save_path
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
print(str(resource)+" completed")
|
||||
|
||||
@@ -235,12 +266,20 @@ def receive_resource_concluded(resource):
|
||||
filename = resource.data.read(filename_len).decode("utf-8")
|
||||
|
||||
counter = 0
|
||||
saved_filename = filename
|
||||
while os.path.isfile(saved_filename):
|
||||
if save_path:
|
||||
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
|
||||
if not saved_filename.startswith(save_path+"/"):
|
||||
RNS.log(f"Invalid save path {saved_filename}, ignoring", RNS.LOG_ERROR)
|
||||
return
|
||||
else:
|
||||
saved_filename = filename
|
||||
|
||||
full_save_path = saved_filename
|
||||
while os.path.isfile(full_save_path):
|
||||
counter += 1
|
||||
saved_filename = filename+"."+str(counter)
|
||||
full_save_path = saved_filename+"."+str(counter)
|
||||
|
||||
file = open(saved_filename, "wb")
|
||||
file = open(full_save_path, "wb")
|
||||
file.write(resource.data.read())
|
||||
file.close()
|
||||
|
||||
@@ -254,33 +293,57 @@ resource_done = False
|
||||
current_resource = None
|
||||
stats = []
|
||||
speed = 0.0
|
||||
phy_speed = 0.0
|
||||
def sender_progress(resource):
|
||||
stats_max = 32
|
||||
global current_resource, stats, speed, resource_done
|
||||
global current_resource, stats, speed, phy_speed, resource_done
|
||||
current_resource = resource
|
||||
|
||||
now = time.time()
|
||||
got = current_resource.get_progress()*current_resource.total_size
|
||||
entry = [now, got]
|
||||
got = current_resource.get_progress()*current_resource.get_data_size()
|
||||
phy_got = current_resource.get_segment_progress()*current_resource.get_transfer_size()
|
||||
|
||||
entry = [now, got, phy_got]
|
||||
stats.append(entry)
|
||||
|
||||
while len(stats) > stats_max:
|
||||
stats.pop(0)
|
||||
|
||||
span = now - stats[0][0]
|
||||
if span == 0:
|
||||
speed = 0
|
||||
phy_speed = 0
|
||||
|
||||
else:
|
||||
diff = got - stats[0][1]
|
||||
speed = diff/span
|
||||
|
||||
phy_diff = phy_got - stats[0][2]
|
||||
if phy_diff > 0:
|
||||
phy_speed = phy_diff/span
|
||||
|
||||
if resource.status < RNS.Resource.COMPLETE:
|
||||
resource_done = False
|
||||
else:
|
||||
resource_done = True
|
||||
|
||||
link = None
|
||||
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
|
||||
global current_resource, resource_done, link, speed
|
||||
def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False, save=None):
|
||||
global current_resource, resource_done, link, speed, show_phy_rates, save_path
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
show_phy_rates = phy_rates
|
||||
|
||||
if save:
|
||||
sp = os.path.abspath(os.path.expanduser(save))
|
||||
if os.path.isdir(sp):
|
||||
if os.access(sp, os.W_OK):
|
||||
save_path = sp
|
||||
else:
|
||||
RNS.log("Output directory not writable", RNS.LOG_ERROR)
|
||||
exit(4)
|
||||
else:
|
||||
RNS.log("Output directory not found", RNS.LOG_ERROR)
|
||||
exit(3)
|
||||
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
@@ -315,7 +378,7 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
||||
if silent:
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
|
||||
else:
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=es)
|
||||
sys.stdout.flush()
|
||||
|
||||
i = 0
|
||||
@@ -332,13 +395,13 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
||||
if silent:
|
||||
print("Path not found")
|
||||
else:
|
||||
print("\r \rPath not found")
|
||||
print(f"{erase_str}Path not found")
|
||||
exit(1)
|
||||
else:
|
||||
if silent:
|
||||
print("Establishing link with "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
|
||||
print(f"{erase_str}Establishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=es)
|
||||
|
||||
listener_identity = RNS.Identity.recall(destination_hash)
|
||||
listener_destination = RNS.Destination(
|
||||
@@ -361,13 +424,13 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
||||
if silent:
|
||||
print("Could not establish link with "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \rCould not establish link with "+RNS.prettyhexrep(destination_hash))
|
||||
print(f"{erase_str}Could not establish link with "+RNS.prettyhexrep(destination_hash))
|
||||
exit(1)
|
||||
else:
|
||||
if silent:
|
||||
print("Requesting file from remote...")
|
||||
else:
|
||||
print("\r \rRequesting file from remote ", end=" ")
|
||||
print(f"{erase_str}Requesting file from remote ", end=es)
|
||||
|
||||
link.identify(identity)
|
||||
|
||||
@@ -402,18 +465,25 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
||||
|
||||
def fetch_resource_concluded(resource):
|
||||
nonlocal resource_resolved, resource_status
|
||||
global save_path
|
||||
if resource.status == RNS.Resource.COMPLETE:
|
||||
if resource.total_size > 4:
|
||||
filename_len = int.from_bytes(resource.data.read(2), "big")
|
||||
filename = resource.data.read(filename_len).decode("utf-8")
|
||||
|
||||
counter = 0
|
||||
saved_filename = filename
|
||||
while os.path.isfile(saved_filename):
|
||||
if save_path:
|
||||
saved_filename = os.path.abspath(os.path.expanduser(save_path+"/"+filename))
|
||||
|
||||
else:
|
||||
saved_filename = filename
|
||||
|
||||
full_save_path = saved_filename
|
||||
while os.path.isfile(full_save_path):
|
||||
counter += 1
|
||||
saved_filename = filename+"."+str(counter)
|
||||
|
||||
file = open(saved_filename, "wb")
|
||||
full_save_path = saved_filename+"."+str(counter)
|
||||
|
||||
file = open(full_save_path, "wb")
|
||||
file.write(resource.data.read())
|
||||
file.close()
|
||||
resource_status = "completed"
|
||||
@@ -442,31 +512,31 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
if request_status == "fetch_not_allowed":
|
||||
if not silent: print("\r \r", end="")
|
||||
if not silent: print(f"{erase_str}", end="")
|
||||
print("Fetch request failed, fetching the file "+str(file)+" was not allowed by the remote")
|
||||
link.teardown()
|
||||
time.sleep(0.15)
|
||||
exit(0)
|
||||
elif request_status == "not_found":
|
||||
if not silent: print("\r \r", end="")
|
||||
if not silent: print(f"{erase_str}", end="")
|
||||
print("Fetch request failed, the file "+str(file)+" was not found on the remote")
|
||||
link.teardown()
|
||||
time.sleep(0.15)
|
||||
exit(0)
|
||||
elif request_status == "remote_error":
|
||||
if not silent: print("\r \r", end="")
|
||||
if not silent: print(f"{erase_str}", end="")
|
||||
print("Fetch request failed due to an error on the remote system")
|
||||
link.teardown()
|
||||
time.sleep(0.15)
|
||||
exit(0)
|
||||
elif request_status == "unknown":
|
||||
if not silent: print("\r \r", end="")
|
||||
if not silent: print(f"{erase_str}", end="")
|
||||
print("Fetch request failed due to an unknown error (probably not authorised)")
|
||||
link.teardown()
|
||||
time.sleep(0.15)
|
||||
exit(0)
|
||||
elif request_status == "found":
|
||||
if not silent: print("\r \r", end="")
|
||||
if not silent: print(f"{erase_str}", end="")
|
||||
|
||||
while not resource_resolved:
|
||||
if not silent:
|
||||
@@ -474,13 +544,21 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
||||
if current_resource:
|
||||
prg = current_resource.get_progress()
|
||||
percent = round(prg * 100.0, 1)
|
||||
stat_str = str(percent)+"% - " + size_str(int(prg*current_resource.total_size)) + " of " + size_str(current_resource.total_size) + " - " +size_str(speed, "b")+"ps"
|
||||
if prg != 1.0:
|
||||
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
|
||||
if show_phy_rates:
|
||||
pss = size_str(phy_speed, "b")
|
||||
phy_str = f" ({pss}ps at physical layer)"
|
||||
else:
|
||||
print("\r \rTransfer complete "+stat_str, end=" ")
|
||||
phy_str = ""
|
||||
ps = size_str(int(prg*current_resource.total_size))
|
||||
ts = size_str(current_resource.total_size)
|
||||
ss = size_str(speed, "b")
|
||||
stat_str = f"{percent}% - {ps} of {ts} - {ss}ps{phy_str}"
|
||||
if prg != 1.0:
|
||||
print(f"{erase_str}Transferring file {syms[i]} {stat_str}", end=es)
|
||||
else:
|
||||
print(f"{erase_str}Transfer complete {stat_str}", end=es)
|
||||
else:
|
||||
print("\r \rWaiting for transfer to start "+syms[i]+" ", end=" ")
|
||||
print(f"{erase_str}Waiting for transfer to start {syms[i]} ", end=es)
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
@@ -488,13 +566,12 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
||||
if silent:
|
||||
print("The transfer failed")
|
||||
else:
|
||||
print("\r \rThe transfer failed")
|
||||
print(f"{erase_str}The transfer failed")
|
||||
exit(1)
|
||||
else:
|
||||
if silent:
|
||||
print(str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
#print("\r \r"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
|
||||
print("\n"+str(file)+" fetched from "+RNS.prettyhexrep(destination_hash))
|
||||
link.teardown()
|
||||
time.sleep(0.15)
|
||||
@@ -504,10 +581,11 @@ def fetch(configdir, verbosity = 0, quietness = 0, destination = None, file = No
|
||||
exit(0)
|
||||
|
||||
|
||||
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False):
|
||||
global current_resource, resource_done, link, speed
|
||||
def send(configdir, verbosity = 0, quietness = 0, destination = None, file = None, timeout = RNS.Transport.PATH_REQUEST_TIMEOUT, silent=False, phy_rates=False):
|
||||
global current_resource, resource_done, link, speed, show_phy_rates
|
||||
from tempfile import TemporaryFile
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
show_phy_rates = phy_rates
|
||||
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
@@ -536,14 +614,14 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
print("Filename exceeds max size, cannot send")
|
||||
exit(1)
|
||||
else:
|
||||
print("Preparing file...", end=" ")
|
||||
print("Preparing file...", end=es)
|
||||
|
||||
temp_file.write(filename_len.to_bytes(2, "big"))
|
||||
temp_file.write(filename_bytes)
|
||||
temp_file.write(real_file.read())
|
||||
temp_file.seek(0)
|
||||
|
||||
print("\r \r", end="")
|
||||
print(f"{erase_str}", end="")
|
||||
|
||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=targetloglevel)
|
||||
|
||||
@@ -566,7 +644,7 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
if silent:
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested")
|
||||
else:
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=" ")
|
||||
print("Path to "+RNS.prettyhexrep(destination_hash)+" requested ", end=es)
|
||||
sys.stdout.flush()
|
||||
|
||||
i = 0
|
||||
@@ -583,13 +661,13 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
if silent:
|
||||
print("Path not found")
|
||||
else:
|
||||
print("\r \rPath not found")
|
||||
print(f"{erase_str}Path not found")
|
||||
exit(1)
|
||||
else:
|
||||
if silent:
|
||||
print("Establishing link with "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \rEstablishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=" ")
|
||||
print(f"{erase_str}Establishing link with "+RNS.prettyhexrep(destination_hash)+" ", end=es)
|
||||
|
||||
receiver_identity = RNS.Identity.recall(destination_hash)
|
||||
receiver_destination = RNS.Destination(
|
||||
@@ -612,19 +690,19 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
if silent:
|
||||
print("Link establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
|
||||
else:
|
||||
print("\r \rLink establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
|
||||
print(f"{erase_str}Link establishment with "+RNS.prettyhexrep(destination_hash)+" timed out")
|
||||
exit(1)
|
||||
elif not RNS.Transport.has_path(destination_hash):
|
||||
if silent:
|
||||
print("No path found to "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \rNo path found to "+RNS.prettyhexrep(destination_hash))
|
||||
print(f"{erase_str}No path found to "+RNS.prettyhexrep(destination_hash))
|
||||
exit(1)
|
||||
else:
|
||||
if silent:
|
||||
print("Advertising file resource...")
|
||||
else:
|
||||
print("\r \rAdvertising file resource ", end=" ")
|
||||
print(f"{erase_str}Advertising file resource ", end=es)
|
||||
|
||||
link.identify(identity)
|
||||
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress)
|
||||
@@ -642,23 +720,32 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
if silent:
|
||||
print("File was not accepted by "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
print("\r \rFile was not accepted by "+RNS.prettyhexrep(destination_hash))
|
||||
print(f"{erase_str}File was not accepted by "+RNS.prettyhexrep(destination_hash))
|
||||
exit(1)
|
||||
else:
|
||||
if silent:
|
||||
print("Transferring file...")
|
||||
else:
|
||||
print("\r \rTransferring file ", end=" ")
|
||||
print(f"{erase_str}Transferring file ", end=es)
|
||||
|
||||
def progress_update(i, done=False):
|
||||
time.sleep(0.1)
|
||||
prg = current_resource.get_progress()
|
||||
percent = round(prg * 100.0, 1)
|
||||
stat_str = str(percent)+"% - " + size_str(int(prg*current_resource.total_size)) + " of " + size_str(current_resource.total_size) + " - " +size_str(speed, "b")+"ps"
|
||||
if not done:
|
||||
print("\r \rTransferring file "+syms[i]+" "+stat_str, end=" ")
|
||||
if show_phy_rates:
|
||||
pss = size_str(phy_speed, "b")
|
||||
phy_str = f" ({pss}ps at physical layer)"
|
||||
else:
|
||||
print("\r \rTransfer complete "+stat_str, end=" ")
|
||||
phy_str = ""
|
||||
es = " "
|
||||
cs = size_str(int(prg*current_resource.total_size))
|
||||
ts = size_str(current_resource.total_size)
|
||||
ss = size_str(speed, "b")
|
||||
stat_str = f"{percent}% - {cs} of {ts} - {ss}ps{phy_str}"
|
||||
if not done:
|
||||
print(f"{erase_str}Transferring file "+syms[i]+" "+stat_str, end=es)
|
||||
else:
|
||||
print(f"{erase_str}Transfer complete "+stat_str, end=es)
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
return i
|
||||
@@ -674,13 +761,12 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
if silent:
|
||||
print("The transfer failed")
|
||||
else:
|
||||
print("\r \rThe transfer failed")
|
||||
print(f"{erase_str}The transfer failed")
|
||||
exit(1)
|
||||
else:
|
||||
if silent:
|
||||
print(str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
|
||||
else:
|
||||
# print("\r \r"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
|
||||
print("\n"+str(file_path)+" copied to "+RNS.prettyhexrep(destination_hash))
|
||||
link.teardown()
|
||||
time.sleep(0.25)
|
||||
@@ -701,11 +787,13 @@ def main():
|
||||
parser.add_argument("-F", '--allow-fetch', action='store_true', default=False, help="allow authenticated clients to fetch files")
|
||||
parser.add_argument("-f", '--fetch', action='store_true', default=False, help="fetch file from remote listener instead of sending")
|
||||
parser.add_argument("-j", "--jail", metavar="path", action="store", default=None, help="restrict fetch requests to specified path", type=str)
|
||||
parser.add_argument("-s", "--save", metavar="path", action="store", default=None, help="save received files in specified path", type=str)
|
||||
parser.add_argument("-b", action='store', metavar="seconds", default=-1, help="announce interval, 0 to only announce at startup", type=int)
|
||||
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="allow this identity", type=str)
|
||||
parser.add_argument('-a', metavar="allowed_hash", dest="allowed", action='append', help="allow this identity (or add in ~/.rncp/allowed_identities)", type=str)
|
||||
parser.add_argument('-n', '--no-auth', action='store_true', default=False, help="accept requests from anyone")
|
||||
parser.add_argument('-p', '--print-identity', action='store_true', default=False, help="print identity and destination info and exit")
|
||||
parser.add_argument("-w", action="store", metavar="seconds", type=float, help="sender timeout before giving up", default=RNS.Transport.PATH_REQUEST_TIMEOUT)
|
||||
parser.add_argument('-P', '--phy-rates', action='store_true', default=False, help="display physical layer transfer rates")
|
||||
# parser.add_argument("--limit", action="store", metavar="files", type=float, help="maximum number of files to accept", default=None)
|
||||
parser.add_argument("--version", action="version", version="rncp {version}".format(version=__version__))
|
||||
|
||||
@@ -719,6 +807,7 @@ def main():
|
||||
allowed = args.allowed,
|
||||
fetch_allowed = args.allow_fetch,
|
||||
jail = args.jail,
|
||||
save = args.save,
|
||||
display_identity=args.print_identity,
|
||||
# limit=args.limit,
|
||||
disable_auth=args.no_auth,
|
||||
@@ -735,6 +824,8 @@ def main():
|
||||
file = args.file,
|
||||
timeout = args.w,
|
||||
silent = args.silent,
|
||||
phy_rates = args.phy_rates,
|
||||
save = args.save,
|
||||
)
|
||||
else:
|
||||
print("")
|
||||
@@ -750,6 +841,7 @@ def main():
|
||||
file = args.file,
|
||||
timeout = args.w,
|
||||
silent = args.silent,
|
||||
phy_rates = args.phy_rates,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
@@ -41,7 +41,7 @@ import RNS
|
||||
RNS.logtimefmt = "%H:%M:%S"
|
||||
RNS.compact_log_fmt = True
|
||||
|
||||
program_version = "2.1.3"
|
||||
program_version = "2.2.0"
|
||||
eth_addr = "0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73"
|
||||
btc_addr = "35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH"
|
||||
xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET"
|
||||
@@ -81,7 +81,9 @@ class KISS():
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_DISP_INT = 0x45
|
||||
CMD_NP_INT = 0x65
|
||||
CMD_DISP_ADR = 0x63
|
||||
CMD_DISP_BLNK = 0x64
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_BT_PIN = 0x62
|
||||
CMD_BOARD = 0x47
|
||||
@@ -128,10 +130,6 @@ class ROM():
|
||||
MCU_ESP32 = 0x81
|
||||
MCU_NRF52 = 0x71
|
||||
|
||||
PRODUCT_RAK4631 = 0x10
|
||||
MODEL_11 = 0x11
|
||||
MODEL_12 = 0x12
|
||||
|
||||
PRODUCT_RNODE = 0x03
|
||||
MODEL_A1 = 0xA1
|
||||
MODEL_A6 = 0xA6
|
||||
@@ -141,6 +139,8 @@ class ROM():
|
||||
MODEL_A8 = 0xA8
|
||||
MODEL_A2 = 0xA2
|
||||
MODEL_A7 = 0xA7
|
||||
MODEL_A5 = 0xA5
|
||||
MODEL_AA = 0xAA
|
||||
|
||||
PRODUCT_T32_10 = 0xB2
|
||||
MODEL_BA = 0xBA
|
||||
@@ -170,6 +170,27 @@ class ROM():
|
||||
MODEL_E9 = 0xE9
|
||||
MODEL_E3 = 0xE3
|
||||
MODEL_E8 = 0xE8
|
||||
|
||||
PRODUCT_TBEAM_S_V1= 0xEA
|
||||
MODEL_DB = 0xDB
|
||||
MODEL_DC = 0xDC
|
||||
|
||||
PRODUCT_TDECK = 0xD0
|
||||
MODEL_D4 = 0xD4
|
||||
MODEL_D9 = 0xD9
|
||||
|
||||
PRODUCT_RAK4631 = 0x10
|
||||
MODEL_11 = 0x11
|
||||
MODEL_12 = 0x12
|
||||
MODEL_13 = 0x13
|
||||
MODEL_14 = 0x14
|
||||
PRODUCT_OPENCOM_XL = 0x20
|
||||
MODEL_21 = 0x21
|
||||
|
||||
|
||||
PRODUCT_TECHO = 0x15
|
||||
MODEL_T4 = 0x16
|
||||
MODEL_T9 = 0x17
|
||||
|
||||
PRODUCT_HMBRW = 0xF0
|
||||
MODEL_FF = 0xFF
|
||||
@@ -196,25 +217,31 @@ class ROM():
|
||||
BOARD_RNODE = 0x31
|
||||
BOARD_HMBRW = 0x32
|
||||
BOARD_TBEAM = 0x33
|
||||
BOARD_TDECK = 0x3B
|
||||
BOARD_HUZZAH32 = 0x34
|
||||
BOARD_GENERIC_ESP32 = 0x35
|
||||
BOARD_LORA32_V2_0 = 0x36
|
||||
BOARD_LORA32_V2_1 = 0x37
|
||||
BOARD_TECHO = 0x43
|
||||
BOARD_RAK4631 = 0x51
|
||||
|
||||
MANUAL_FLASH_MODELS = [MODEL_A1, MODEL_A6]
|
||||
MANUAL_FLASH_MODELS = []
|
||||
|
||||
mapped_product = ROM.PRODUCT_RNODE
|
||||
products = {
|
||||
ROM.PRODUCT_RNODE: "RNode",
|
||||
ROM.PRODUCT_HMBRW: "Hombrew RNode",
|
||||
ROM.PRODUCT_TBEAM: "LilyGO T-Beam",
|
||||
ROM.PRODUCT_TBEAM_S_V1:"LilyGO T-Beam Supreme",
|
||||
ROM.PRODUCT_TDECK: "LilyGO T-Deck",
|
||||
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",
|
||||
ROM.PRODUCT_H32_V3: "Heltec LoRa32 v3",
|
||||
ROM.PRODUCT_TECHO: "LilyGO T-Echo",
|
||||
ROM.PRODUCT_RAK4631: "RAK4631",
|
||||
ROM.PRODUCT_OPENCOM_XL: "openCom XL",
|
||||
}
|
||||
|
||||
platforms = {
|
||||
@@ -231,12 +258,12 @@ mcus = {
|
||||
}
|
||||
|
||||
models = {
|
||||
0x11: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
|
||||
0x12: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
|
||||
0xA4: [410000000, 525000000, 14, "410 - 525 MHz", "rnode_firmware.hex", "SX1278"],
|
||||
0xA9: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware.hex", "SX1276"],
|
||||
0xA1: [410000000, 525000000, 22, "410 - 525 MHz", "rnode_firmware_t3s3.zip", "SX1268"],
|
||||
0xA6: [820000000, 1020000000, 22, "820 - 960 MHz", "rnode_firmware_t3s3.zip", "SX1262"],
|
||||
0xA5: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_t3s3_sx127x.zip", "SX1278"],
|
||||
0xAA: [820000000, 1020000000, 17, "820 - 960 MHz", "rnode_firmware_t3s3_sx127x.zip", "SX1276"],
|
||||
0xA2: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_ng21.zip", "SX1278"],
|
||||
0xA7: [820000000, 1020000000, 17, "820 - 1020 MHz", "rnode_firmware_ng21.zip", "SX1276"],
|
||||
0xA3: [410000000, 525000000, 17, "410 - 525 MHz", "rnode_firmware_ng20.zip", "SX1278"],
|
||||
@@ -251,12 +278,23 @@ models = {
|
||||
0xBB: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_lora32v10.zip", "SX1276"],
|
||||
0xC4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_heltec32v2.zip", "SX1278"],
|
||||
0xC9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_heltec32v2.zip", "SX1276"],
|
||||
0xC5: [470000000, 510000000, 21, "470 - 510 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"],
|
||||
0xCA: [863000000, 928000000, 21, "863 - 928 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"],
|
||||
0xC5: [420000000, 520000000, 21, "420 - 520 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"],
|
||||
0xCA: [850000000, 950000000, 21, "850 - 950 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"],
|
||||
0xE4: [420000000, 520000000, 17, "420 - 520 MHz", "rnode_firmware_tbeam.zip", "SX1278"],
|
||||
0xE9: [850000000, 950000000, 17, "850 - 950 MHz", "rnode_firmware_tbeam.zip", "SX1276"],
|
||||
0xD4: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tdeck.zip", "SX1268"],
|
||||
0xD9: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tdeck.zip", "SX1262"],
|
||||
0xDB: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tbeam_supreme.zip", "SX1268"],
|
||||
0xDC: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tbeam_supreme.zip", "SX1262"],
|
||||
0xE3: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_tbeam_sx1262.zip", "SX1268"],
|
||||
0xE8: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_tbeam_sx1262.zip", "SX1262"],
|
||||
0x11: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
|
||||
0x12: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631.zip", "SX1262"],
|
||||
0x13: [430000000, 510000000, 22, "430 - 510 MHz", "rnode_firmware_rak4631_sx1280.zip", "SX1262 + SX1280"],
|
||||
0x14: [779000000, 928000000, 22, "779 - 928 MHz", "rnode_firmware_rak4631_sx1280.zip", "SX1262 + SX1280"],
|
||||
0x16: [779000000, 928000000, 22, "430 - 510 Mhz", "rnode_firmware_techo.zip", "SX1262"],
|
||||
0x17: [779000000, 928000000, 22, "779 - 928 Mhz", "rnode_firmware_techo.zip", "SX1262"],
|
||||
0x21: [820000000, 960000000, 22, "820 - 960 MHz", "rnode_firmware_opencom_xl.zip", "SX1262 + SX1280"],
|
||||
0xFE: [100000000, 1100000000, 17, "(Band capabilities unknown)", None, "Unknown"],
|
||||
0xFF: [100000000, 1100000000, 14, "(Band capabilities unknown)", None, "Unknown"],
|
||||
}
|
||||
@@ -620,6 +658,20 @@ class RNode():
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display intensity command to device")
|
||||
|
||||
def set_display_blanking(self, blanking_timeout):
|
||||
data = bytes([blanking_timeout & 0xFF])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_BLNK])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display blanking timeout command to device")
|
||||
|
||||
def set_neopixel_intensity(self, intensity):
|
||||
data = bytes([intensity & 0xFF])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_NP_INT])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending NeoPixel intensity command to device")
|
||||
|
||||
def set_display_address(self, address):
|
||||
data = bytes([address & 0xFF])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_ADR])+data+bytes([KISS.FEND])
|
||||
@@ -1142,6 +1194,7 @@ def ensure_firmware_file(fw_filename):
|
||||
pass
|
||||
else:
|
||||
RNS.log("")
|
||||
RNS.log(f"Firmware hash {file_hash} but should be {selected_hash}, possibly due to download corruption.")
|
||||
RNS.log("Firmware corrupt. Try clearing the local firmware cache with: rnodeconf --clear-cache")
|
||||
graceful_exit(96)
|
||||
|
||||
@@ -1244,8 +1297,11 @@ def main():
|
||||
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("-t", "--timeout", action="store", metavar="s", type=int, default=None, help="Set display timeout in seconds, 0 to disable")
|
||||
parser.add_argument("--display-addr", action="store", metavar="byte", type=str, default=None, help="Set display address as hex byte (00 - FF)")
|
||||
|
||||
parser.add_argument("--np", action="store", metavar="i", type=int, default=None, help="Set NeoPixel 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")
|
||||
@@ -1603,6 +1659,9 @@ def main():
|
||||
print("[8] Heltec LoRa32 v3")
|
||||
print("[9] LilyGO LoRa T3S3")
|
||||
print("[10] RAK4631")
|
||||
print("[11] LilyGo T-Echo")
|
||||
print("[12] LilyGO T-Beam Supreme")
|
||||
print("[13] LilyGO T-Deck")
|
||||
print(" .")
|
||||
print(" / \\ Select one of these options if you want to easily turn")
|
||||
print(" | a supported development board into an RNode.")
|
||||
@@ -1614,7 +1673,7 @@ def main():
|
||||
try:
|
||||
c_dev = int(input())
|
||||
c_mod = False
|
||||
if c_dev < 1 or c_dev > 10:
|
||||
if c_dev < 1 or c_dev > 13:
|
||||
raise ValueError()
|
||||
elif c_dev == 1:
|
||||
selected_product = ROM.PRODUCT_RNODE
|
||||
@@ -1651,6 +1710,38 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 12:
|
||||
selected_product = ROM.PRODUCT_TBEAM_S_V1
|
||||
clear()
|
||||
print("")
|
||||
print("---------------------------------------------------------------------------")
|
||||
print(" T-Beam Supreme RNode Installer")
|
||||
print("")
|
||||
print("The RNode firmware can currently be installed on T-Beam Supreme devices")
|
||||
print("using the SX1262 and SX1268 transceiver chips.")
|
||||
print("")
|
||||
print("Important! Using RNode firmware on T-Beam 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. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 13:
|
||||
selected_product = ROM.PRODUCT_TDECK
|
||||
clear()
|
||||
print("")
|
||||
print("---------------------------------------------------------------------------")
|
||||
print(" T-Deck RNode Installer")
|
||||
print("")
|
||||
print("The RNode firmware can currently be installed on T-Deck devices using the")
|
||||
print("SX1262 and SX1268 transceiver chips.")
|
||||
print("")
|
||||
print("Important! Using RNode firmware on T-Beam 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. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 4:
|
||||
selected_product = ROM.PRODUCT_T32_20
|
||||
clear()
|
||||
@@ -1721,8 +1812,6 @@ def main():
|
||||
print("Important! Using RNode firmware on T3S3 devices should currently be")
|
||||
print("considered experimental. It is not intended for production or critical use.")
|
||||
print("")
|
||||
print("Please note that Bluetooth is currently not implemented on this board.")
|
||||
print("")
|
||||
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
@@ -1737,8 +1826,6 @@ def main():
|
||||
print("Important! Using RNode firmware on Heltec devices should currently be")
|
||||
print("considered experimental. It is not intended for production or critical use.")
|
||||
print("")
|
||||
print("Please note that Bluetooth is currently not implemented on this board.")
|
||||
print("")
|
||||
print("The currently supplied firmware is provided AS-IS as a courtesey to those")
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
@@ -1756,6 +1843,19 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 11:
|
||||
selected_product = ROM.PRODUCT_TECHO
|
||||
clear()
|
||||
print("")
|
||||
print("---------------------------------------------------------------------------")
|
||||
print(" LilyGo T-Echo RNode Installer")
|
||||
print("")
|
||||
print("Important! Using RNode firmware on LilyGo T-Echo 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. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
except Exception as e:
|
||||
print("That device type does not exist, exiting now.")
|
||||
graceful_exit()
|
||||
@@ -1858,19 +1958,30 @@ def main():
|
||||
print("That model does not exist, exiting now.")
|
||||
graceful_exit()
|
||||
else:
|
||||
print("\nWhat model is this T3S3?\n")
|
||||
print("[1] 410 - 525 MHz (with SX1268 chip)")
|
||||
print("[2] 820 - 1020 MHz (with SX1262 chip)")
|
||||
print("\nWhat band is this T3S3 for?\n")
|
||||
print("[1] 433 MHz (with SX1278 chip)")
|
||||
print("[2] 868/915/923 MHz (with SX1276 chip)")
|
||||
print("");
|
||||
print("[3] 433 MHz (with SX1268 chip)")
|
||||
print("[4] 868/915/923 MHz (with SX1262 chip)")
|
||||
print("\n? ", end="")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 2:
|
||||
if c_model < 1 or c_model > 4:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_A1
|
||||
selected_model = ROM.MODEL_A5
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model == 2:
|
||||
selected_model = ROM.MODEL_AA
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model == 3:
|
||||
selected_model = ROM.MODEL_A1
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model == 4:
|
||||
selected_model = ROM.MODEL_A6
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
@@ -1907,6 +2018,46 @@ def main():
|
||||
print("That band does not exist, exiting now.")
|
||||
graceful_exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_TBEAM_S_V1:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this T-Beam Supreme for?\n")
|
||||
print("[1] 433 MHz (with SX1268 chip)")
|
||||
print("[2] 868/915/923 MHz (with SX1262 chip)")
|
||||
print("\n? ", end="")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 2:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_DB
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model == 2:
|
||||
selected_model = ROM.MODEL_DC
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
graceful_exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_TDECK:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this T-Deck for?\n")
|
||||
print("[1] 433 MHz (with SX1268 chip)")
|
||||
print("[2] 868/915/923 MHz (with SX1262 chip)")
|
||||
print("\n? ", end="")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 2:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_D4
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model == 2:
|
||||
selected_model = ROM.MODEL_D9
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
graceful_exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_T32_10:
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
print("\nWhat band is this LoRa32 for?\n")
|
||||
@@ -2042,6 +2193,27 @@ def main():
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
graceful_exit()
|
||||
elif selected_product == ROM.PRODUCT_TECHO:
|
||||
selected_mcu = ROM.MCU_NRF52
|
||||
print("\nWhat band is this T-Echo 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 > 1:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_T4
|
||||
selected_platform = ROM.PLATFORM_NRF52
|
||||
elif c_model > 1:
|
||||
selected_model = ROM.MODEL_T9
|
||||
selected_platform = ROM.PLATFORM_NRF52
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
graceful_exit()
|
||||
|
||||
if selected_model != ROM.MODEL_FF and selected_model != ROM.MODEL_FE:
|
||||
fw_filename = models[selected_model][4]
|
||||
@@ -2718,6 +2890,60 @@ def main():
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_t3s3_sx127x.zip":
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32s3",
|
||||
"--port", args.port,
|
||||
"--baud", args.baud_flash,
|
||||
"--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_t3s3_sx127x.boot_app0",
|
||||
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.bin",
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_tbeam_supreme.zip":
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32s3",
|
||||
"--port", args.port,
|
||||
"--baud", args.baud_flash,
|
||||
"--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_tbeam_supreme.boot_app0",
|
||||
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.bin",
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tbeam_supreme.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_tdeck.zip":
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
"--chip", "esp32s3",
|
||||
"--port", args.port,
|
||||
"--baud", args.baud_flash,
|
||||
"--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_tdeck.boot_app0",
|
||||
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.bin",
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_tdeck.partitions",
|
||||
]
|
||||
elif fw_filename == "extracted_rnode_firmware.zip":
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
@@ -3116,6 +3342,27 @@ def main():
|
||||
RNS.log("Setting display intensity to "+str(di))
|
||||
rnode.set_display_intensity(di)
|
||||
|
||||
if isinstance(args.timeout, int):
|
||||
di = args.timeout
|
||||
if di < 0:
|
||||
di = 0
|
||||
if di > 255:
|
||||
di = 255
|
||||
if di == 0:
|
||||
RNS.log("Disabling display blanking")
|
||||
else:
|
||||
RNS.log("Setting display timeout to "+str(di))
|
||||
rnode.set_display_blanking(di)
|
||||
|
||||
if isinstance(args.np, int):
|
||||
di = args.np
|
||||
if di < 0:
|
||||
di = 0
|
||||
if di > 255:
|
||||
di = 255
|
||||
RNS.log("Setting NeoPixel intensity to "+str(di))
|
||||
rnode.set_neopixel_intensity(di)
|
||||
|
||||
if isinstance(args.display_addr, str):
|
||||
set_addr = False
|
||||
try:
|
||||
|
||||
@@ -123,6 +123,18 @@ instance_control_port = 37429
|
||||
# rpc_key = e5c032d3ec4e64a6aca9927ba8ab73336780f6d71790
|
||||
|
||||
|
||||
# It is possible to allow remote management of Reticulum
|
||||
# systems using the various built-in utilities, such as
|
||||
# rnstatus and rnpath. You will need to specify one or
|
||||
# more Reticulum Identity hashes for authenticating the
|
||||
# queries from client programs. For this purpose, you can
|
||||
# use existing identity files, or generate new ones with
|
||||
# the rnid utility.
|
||||
|
||||
# enable_remote_management = yes
|
||||
# remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
|
||||
|
||||
|
||||
# You can configure Reticulum to panic and forcibly close
|
||||
# if an unrecoverable interface error occurs, such as the
|
||||
# hardware device for an interface disappearing. This is
|
||||
@@ -282,6 +294,23 @@ loglevel = 4
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# It is also possible to use BLE devices
|
||||
# instead of wired serial ports. The
|
||||
# target RNode must be paired with the
|
||||
# host device before connecting. BLE
|
||||
# devices can be connected by name,
|
||||
# BLE MAC address or by any available.
|
||||
|
||||
# Connect to specific device by name
|
||||
# port = ble://RNode 3B87
|
||||
|
||||
# Or by BLE MAC address
|
||||
# port = ble://F4:12:73:29:4E:89
|
||||
|
||||
# Or connect to the first available,
|
||||
# paired device
|
||||
# port = ble://
|
||||
|
||||
# Set frequency to 867.2 MHz
|
||||
frequency = 867200000
|
||||
|
||||
|
||||
@@ -292,6 +292,13 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
if "bitrate" in ifstat and ifstat["bitrate"] != None:
|
||||
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
|
||||
|
||||
if "battery_percent" in ifstat and ifstat["battery_percent"] != None:
|
||||
try:
|
||||
bpi = int(ifstat["battery_percent"])
|
||||
print(" Battery : {bp}%".format(bp=bpi))
|
||||
except:
|
||||
pass
|
||||
|
||||
if "airtime_short" in ifstat and "airtime_long" in ifstat:
|
||||
print(" Airtime : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["airtime_short"]),atl=str(ifstat["airtime_long"])))
|
||||
|
||||
|
||||
@@ -270,6 +270,53 @@ def prettytime(time, verbose=False, compact=False):
|
||||
else:
|
||||
return tstr
|
||||
|
||||
def prettyshorttime(time, verbose=False, compact=False):
|
||||
time = time*1e6
|
||||
|
||||
seconds = int(time // 1e6); time %= 1e6
|
||||
milliseconds = int(time // 1e3); time %= 1e3
|
||||
|
||||
if compact:
|
||||
microseconds = int(time)
|
||||
else:
|
||||
microseconds = round(time, 2)
|
||||
|
||||
ss = "" if seconds == 1 else "s"
|
||||
sms = "" if milliseconds == 1 else "s"
|
||||
sus = "" if microseconds == 1 else "s"
|
||||
|
||||
displayed = 0
|
||||
components = []
|
||||
if seconds > 0 and ((not compact) or displayed < 2):
|
||||
components.append(str(seconds)+" second"+ss if verbose else str(seconds)+"s")
|
||||
displayed += 1
|
||||
|
||||
if milliseconds > 0 and ((not compact) or displayed < 2):
|
||||
components.append(str(milliseconds)+" millisecond"+sms if verbose else str(milliseconds)+"ms")
|
||||
displayed += 1
|
||||
|
||||
if microseconds > 0 and ((not compact) or displayed < 2):
|
||||
components.append(str(microseconds)+" microsecond"+sus if verbose else str(microseconds)+"µs")
|
||||
displayed += 1
|
||||
|
||||
i = 0
|
||||
tstr = ""
|
||||
for c in components:
|
||||
i += 1
|
||||
if i == 1:
|
||||
pass
|
||||
elif i < len(components):
|
||||
tstr += ", "
|
||||
elif i == len(components):
|
||||
tstr += " and "
|
||||
|
||||
tstr += c
|
||||
|
||||
if tstr == "":
|
||||
return "0us"
|
||||
else:
|
||||
return tstr
|
||||
|
||||
def phyparams():
|
||||
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
|
||||
print("Plaintext Packet MDU : "+str(Packet.PLAIN_MDU)+" bytes")
|
||||
@@ -285,3 +332,94 @@ def panic():
|
||||
def exit():
|
||||
print("")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
profiler_ran = False
|
||||
profiler_tags = {}
|
||||
def profiler(tag=None, capture=False, super_tag=None):
|
||||
global profiler_ran, profiler_tags
|
||||
try:
|
||||
thread_ident = threading.get_ident()
|
||||
|
||||
if capture:
|
||||
end = time.perf_counter()
|
||||
if tag in profiler_tags and thread_ident in profiler_tags[tag]["threads"]:
|
||||
if profiler_tags[tag]["threads"][thread_ident]["current_start"] != None:
|
||||
begin = profiler_tags[tag]["threads"][thread_ident]["current_start"]
|
||||
profiler_tags[tag]["threads"][thread_ident]["current_start"] = None
|
||||
profiler_tags[tag]["threads"][thread_ident]["captures"].append(end-begin)
|
||||
if not profiler_ran:
|
||||
profiler_ran = True
|
||||
|
||||
else:
|
||||
if not tag in profiler_tags:
|
||||
profiler_tags[tag] = {"threads": {}, "super": super_tag}
|
||||
if not thread_ident in profiler_tags[tag]["threads"]:
|
||||
profiler_tags[tag]["threads"][thread_ident] = {"current_start": None, "captures": []}
|
||||
|
||||
profiler_tags[tag]["threads"][thread_ident]["current_start"] = time.perf_counter()
|
||||
|
||||
except Exception as e:
|
||||
trace_exception(e)
|
||||
|
||||
def profiler_results():
|
||||
from statistics import mean, median, stdev
|
||||
results = {}
|
||||
|
||||
for tag in profiler_tags:
|
||||
tag_captures = []
|
||||
tag_entry = profiler_tags[tag]
|
||||
|
||||
for thread_ident in tag_entry["threads"]:
|
||||
thread_entry = tag_entry["threads"][thread_ident]
|
||||
thread_captures = thread_entry["captures"]
|
||||
sample_count = len(thread_captures)
|
||||
|
||||
if sample_count > 2:
|
||||
thread_results = {
|
||||
"count": sample_count,
|
||||
"mean": mean(thread_captures),
|
||||
"median": median(thread_captures),
|
||||
"stdev": stdev(thread_captures)
|
||||
}
|
||||
|
||||
tag_captures.extend(thread_captures)
|
||||
|
||||
sample_count = len(tag_captures)
|
||||
if sample_count > 2:
|
||||
tag_results = {
|
||||
"name": tag,
|
||||
"super": tag_entry["super"],
|
||||
"count": len(tag_captures),
|
||||
"mean": mean(tag_captures),
|
||||
"median": median(tag_captures),
|
||||
"stdev": stdev(tag_captures)
|
||||
}
|
||||
|
||||
results[tag] = tag_results
|
||||
|
||||
def print_results_recursive(tag, results, level=0):
|
||||
print_tag_results(tag, level+1)
|
||||
|
||||
for tag_name in results:
|
||||
sub_tag = results[tag_name]
|
||||
if sub_tag["super"] == tag["name"]:
|
||||
print_results_recursive(sub_tag, results, level=level+1)
|
||||
|
||||
|
||||
def print_tag_results(tag, level):
|
||||
ind = " "*level
|
||||
name = tag["name"]; count = tag["count"]
|
||||
mean = tag["mean"]; tag["median"]; stdev = tag["stdev"]
|
||||
print(f"{ind}{name}")
|
||||
print(f"{ind} Samples : {count}")
|
||||
print(f"{ind} Mean : {prettyshorttime(mean)}")
|
||||
print(f"{ind} Median : {prettyshorttime(median)}")
|
||||
print(f"{ind} St.dev. : {prettyshorttime(stdev)}")
|
||||
print("")
|
||||
|
||||
print("\nProfiler results:\n")
|
||||
for tag_name in results:
|
||||
tag = results[tag_name]
|
||||
if tag["super"] == None:
|
||||
print_results_recursive(tag, results)
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.7.6"
|
||||
__version__ = "0.8.3"
|
||||
|
||||
@@ -14,12 +14,15 @@ 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.7.x` release cycle aims at completing
|
||||
- The current `0.8.x` release cycle aims at completing
|
||||
- [ ] Hot-pluggable interface system
|
||||
- [ ] External interface plugins
|
||||
- [ ] Network-wide path balancing and multi-pathing
|
||||
- [ ] Expanded hardware support
|
||||
- [ ] Overhauling and updating the documentation
|
||||
- [ ] Distributed Destination Naming System
|
||||
- [ ] Create a standalone RNS Daemon app for Android
|
||||
- [ ] Network-wide path balancing
|
||||
- [ ] Add automatic retries to all use cases of the `Request` API
|
||||
- [ ] A standalone RNS Daemon app for Android
|
||||
- [ ] Addding automatic retries to all use cases of the `Request` API
|
||||
- [ ] Performance and memory optimisations of the Python reference implementation
|
||||
- [ ] Fixing bugs discovered while operating Reticulum systems and applications
|
||||
|
||||
@@ -35,17 +38,9 @@ These efforts are aimed at improving the ease of which Reticulum is understood,
|
||||
- Update announce description
|
||||
- Add in-depth explanation of the IFAC system
|
||||
- Software
|
||||
- Update Sideband screenshots
|
||||
- Update Sideband description
|
||||
- Update NomadNet screenshots
|
||||
- Update Sideband screenshots
|
||||
- Installation
|
||||
- [x] Add a *Reticulum On Raspberry Pi* section
|
||||
- [x] Update *Reticulum On Android* section if necessary
|
||||
- [x] Update Android install documentation.
|
||||
- Update software descriptions and screenshots
|
||||
- Communications hardware section
|
||||
- Add information about RNode external displays.
|
||||
- [x] Packet radio modems.
|
||||
- Possibly add other relevant types here as well.
|
||||
- Setup *Best Practices For...* / *Installation Examples* section.
|
||||
- Home or office (example)
|
||||
@@ -65,6 +60,8 @@ 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.
|
||||
|
||||
- Add support for user-supplied external interface drivers
|
||||
- Add interface hot-plug and live up/down control to running instances
|
||||
- Add automatic retries to all use cases of the `Request` API
|
||||
- Network-wide path balancing
|
||||
- Distributed Destination Naming System
|
||||
@@ -82,10 +79,10 @@ These effors seek to make Reticulum easier to use and operate, and to expand the
|
||||
### Interfaceability
|
||||
These efforts aim to expand the types of physical and virtual interfaces that Reticulum can natively use to transport data.
|
||||
|
||||
- Filesystem interface
|
||||
- Plain ESP32 devices (ESP-Now, WiFi, Bluetooth, etc.)
|
||||
- More LoRa transceivers
|
||||
- AT-compatible modems
|
||||
- Filesystem interface
|
||||
- Direct SDR Support
|
||||
- Optical mediums
|
||||
- IR Transceivers
|
||||
@@ -105,7 +102,7 @@ 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
|
||||
- [ ] 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.
|
||||
- [x] 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
|
||||
|
||||
|
||||
@@ -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: 4a5b669a6756de77e9b7c6b7dc5d5702
|
||||
config: af530171c45cf8c468a96fde91201b07
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 562 KiB After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 345 KiB |
|
After Width: | Height: | Size: 229 KiB |
@@ -90,77 +90,151 @@ Supported Boards
|
||||
To create one or more RNodes, you will need to obtain supported development
|
||||
boards. The following boards are supported by the auto-installer.
|
||||
|
||||
LilyGO LoRa32 v2.1
|
||||
""""""""""""""""""
|
||||
.. image:: graphics/board_t3v21.png
|
||||
:width: 46%
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_tbeam_supreme.png
|
||||
:width: 75%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
|
||||
LilyGO LoRa32 v2.0
|
||||
""""""""""""""""""
|
||||
.. image:: graphics/board_t3v20.png
|
||||
:width: 46%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
|
||||
LilyGO T-Beam
|
||||
LilyGO T-Beam Supreme
|
||||
"""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_tbeam.png
|
||||
:width: 75%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
LilyGO T-Beam
|
||||
"""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268, SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
Heltec LoRa32 v2.0
|
||||
""""""""""""""""""
|
||||
.. image:: graphics/board_heltec32.png
|
||||
:width: 58%
|
||||
.. image:: graphics/board_t3s3.png
|
||||
:width: 50%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
LilyGO T3S3
|
||||
"""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268, SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_rak4631.png
|
||||
:width: 45%
|
||||
:align: center
|
||||
|
||||
RAK4631-based Boards
|
||||
""""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268
|
||||
- **Device Platform** nRF52
|
||||
- **Manufacturer** `RAK Wireless <https://www.rakwireless.com>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_rnodev2.png
|
||||
:width: 68%
|
||||
:align: center
|
||||
|
||||
Unsigned RNode v2.x
|
||||
"""""""""""""""""""
|
||||
.. image:: graphics/board_rnodev2.png
|
||||
:width: 58%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `unsigned.io <https://unsigned.io>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_t3v21.png
|
||||
:width: 46%
|
||||
:align: center
|
||||
|
||||
LilyGO LoRa32 v2.1
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_t3v20.png
|
||||
:width: 46%
|
||||
:align: center
|
||||
|
||||
LilyGO LoRa32 v2.0
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_t3v10.png
|
||||
:width: 46%
|
||||
:align: center
|
||||
|
||||
LilyGO LoRa32 v1.0
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_tdeck.png
|
||||
:width: 45%
|
||||
:align: center
|
||||
|
||||
LilyGO T-Deck
|
||||
"""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_heltec32v30.png
|
||||
:width: 58%
|
||||
:align: center
|
||||
|
||||
Heltec LoRa32 v3.0
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262 and SX1268
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_heltec32v20.png
|
||||
:width: 58%
|
||||
:align: center
|
||||
|
||||
Heltec LoRa32 v2.0
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
|
||||
|
||||
------------
|
||||
|
||||
Unsigned RNode v1.x
|
||||
"""""""""""""""""""
|
||||
.. image:: graphics/board_rnode.png
|
||||
:width: 50%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
Unsigned RNode v1.x
|
||||
"""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** AVR ATmega1284p
|
||||
- **Manufacturer** `unsigned.io <https://unsigned.io>`_
|
||||
|
||||
------------
|
||||
|
||||
.. _rnode-installation:
|
||||
|
||||
@@ -194,10 +268,8 @@ Usage with Reticulum
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
When the devices have been installed and provisioned, you can use them with Reticulum
|
||||
by adding the :ref:`relevant interface section<interfaces-rnode>` to the configuration
|
||||
file of Reticulum. For v1.x firmwares, you will have to specify all interface parameters,
|
||||
such as serial port and on-air parameters. For v2.x firmwares, you just need to specify
|
||||
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
|
||||
RNode, using the parameters stored in the RNode itself.
|
||||
file of Reticulum. In the configuraion you can specify all interface parameters,
|
||||
such as serial port and on-air parameters.
|
||||
|
||||
|
||||
WiFi-based Hardware
|
||||
|
||||
@@ -358,6 +358,23 @@ can be used, and offers full control over LoRa parameters.
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# It is also possible to use BLE devices
|
||||
# instead of wired serial ports. The
|
||||
# target RNode must be paired with the
|
||||
# host device before connecting. BLE
|
||||
# devices can be connected by name,
|
||||
# BLE MAC address or by any available.
|
||||
|
||||
# Connect to specific device by name
|
||||
# port = ble://RNode 3B87
|
||||
|
||||
# Or by BLE MAC address
|
||||
# port = ble://F4:12:73:29:4E:89
|
||||
|
||||
# Or connect to the first available,
|
||||
# paired device
|
||||
# port = ble://
|
||||
|
||||
# Set frequency to 867.2 MHz
|
||||
frequency = 867200000
|
||||
|
||||
|
||||
@@ -134,10 +134,11 @@ be sufficient, even far into the future.
|
||||
By default Reticulum encrypts all data using elliptic curve cryptography and AES. Any packet sent to a
|
||||
destination is encrypted with a per-packet derived key. Reticulum can also set up an encrypted
|
||||
channel to a destination, called a *Link*. Both data sent over Links and single packets offer
|
||||
*Initiator Anonymity*, and links additionally offer *Forward Secrecy* by using an Elliptic Curve
|
||||
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. The multi-hop transport,
|
||||
coordination, verification and reliability layers are fully autonomous and also based on elliptic
|
||||
curve cryptography.
|
||||
*Initiator Anonymity*. Links additionally offer *Forward Secrecy* by default, employing an Elliptic Curve
|
||||
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. Asymmetric, link-less
|
||||
packet communication can also provide forward secrecy, with automatic key ratcheting, by enabling
|
||||
ratchets on a per-destination basis. The multi-hop transport, coordination, verification and reliability
|
||||
layers are fully autonomous and also based on elliptic curve cryptography.
|
||||
|
||||
Reticulum also offers symmetric key encryption for group-oriented communications, as well as
|
||||
unencrypted packets for local broadcast purposes.
|
||||
@@ -431,7 +432,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 destination's public key, and encrypt the information.
|
||||
an ECDH key exchange with the destination's public key (or ratchet key, if available), 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
|
||||
@@ -693,7 +694,8 @@ Wire Format
|
||||
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
|
||||
|
||||
* The HEADER field is 2 bytes long.
|
||||
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
|
||||
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
|
||||
[Destination Type] and [Packet Type]
|
||||
* Byte 2: Number of hops
|
||||
|
||||
* Interface Access Code field if the IFAC flag was set.
|
||||
@@ -725,12 +727,16 @@ Wire Format
|
||||
type 2 1 Two byte header, two 16 byte address fields
|
||||
|
||||
|
||||
Context Flag
|
||||
-----------------
|
||||
unset 0 The context flag is used for various types
|
||||
set 1 of signalling, depending on packet context
|
||||
|
||||
|
||||
Propagation Types
|
||||
-----------------
|
||||
broadcast 00
|
||||
transport 01
|
||||
reserved 10
|
||||
reserved 11
|
||||
broadcast 0
|
||||
transport 1
|
||||
|
||||
|
||||
Destination Types
|
||||
@@ -862,12 +868,14 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
|
||||
|
||||
* HKDF for key derivation
|
||||
|
||||
* Fernet for encrypted tokens
|
||||
* Modified Fernet for encrypted tokens
|
||||
|
||||
* AES-128 in CBC mode
|
||||
|
||||
* HMAC for message authentication
|
||||
|
||||
* No Version and Timestamp metadata included
|
||||
|
||||
* SHA-256
|
||||
|
||||
* SHA-512
|
||||
|
||||
@@ -312,8 +312,9 @@ Filter output to only show some interfaces:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnstatus.py [-h] [--config CONFIG] [--version] [-a] [-A] [-s SORT]
|
||||
[-r] [-j] [-v] [filter]
|
||||
usage: rnstatus [-h] [--config CONFIG] [--version] [-a] [-A]
|
||||
[-l] [-s SORT] [-r] [-j] [-R hash] [-i path]
|
||||
[-w seconds] [-v] [filter]
|
||||
|
||||
Reticulum Network Stack Status
|
||||
|
||||
@@ -326,9 +327,13 @@ Filter output to only show some interfaces:
|
||||
--version show program's version number and exit
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-l, --link-stats show link stats
|
||||
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
|
||||
-r, --reverse reverse sorting
|
||||
-j, --json output in JSON format
|
||||
-R hash transport identity hash of remote instance to get status from
|
||||
-i path path to identity used for remote management
|
||||
-w seconds timeout before giving up on remote queries
|
||||
-v, --verbose
|
||||
|
||||
|
||||
@@ -452,8 +457,9 @@ Resolve path to a destination:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D]
|
||||
[-x] [-w seconds] [-v] [destination]
|
||||
usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-m hops]
|
||||
[-r] [-d] [-D] [-x] [-w seconds] [-R hash] [-i path]
|
||||
[-W seconds] [-j] [-v] [destination]
|
||||
|
||||
Reticulum Path Discovery Utility
|
||||
|
||||
@@ -465,11 +471,16 @@ Resolve path to a destination:
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-t, --table show all known paths
|
||||
-m hops, --max hops maximum hops to filter path table by
|
||||
-r, --rates show announce rate info
|
||||
-d, --drop remove the path to a destination
|
||||
-D, --drop-announces drop all queued announces
|
||||
-x, --drop-via drop all paths via specified transport instance
|
||||
-w seconds timeout before giving up
|
||||
-R hash transport identity hash of remote instance to manage
|
||||
-i path path to identity used for remote management
|
||||
-W seconds timeout before giving up on remote queries
|
||||
-j, --json output in JSON format
|
||||
-v, --verbose
|
||||
|
||||
|
||||
@@ -524,20 +535,27 @@ these as part of the result as well.
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [-s SIZE]
|
||||
usage: rnprobe [-h] [--config CONFIG] [-s SIZE] [-n PROBES]
|
||||
[-t seconds] [-w seconds] [--version] [-v]
|
||||
[full_name] [destination_hash]
|
||||
|
||||
Reticulum Probe Utility
|
||||
|
||||
positional arguments:
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-s SIZE, --size SIZE size of probe packet payload in bytes
|
||||
--version show program's version number and exit
|
||||
-n PROBES, --probes PROBES
|
||||
number of probes to send
|
||||
-t seconds, --timeout seconds
|
||||
timeout before giving up
|
||||
-w seconds, --wait seconds
|
||||
time between each probe
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
|
||||
|
||||
@@ -578,8 +596,9 @@ Or fetch a file from the remote system:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rncp.py [-h] [--config path] [-v] [-q] [-S] [-l] [-f] [-b seconds]
|
||||
[-a allowed_hash] [-n] [-p] [-w seconds] [--version] [file] [destination]
|
||||
usage: rncp [-h] [--config path] [-v] [-q] [-S] [-l] [-F] [-f]
|
||||
[-j path] [-b seconds] [-a allowed_hash] [-n] [-p]
|
||||
[-w seconds] [--version] [file] [destination]
|
||||
|
||||
Reticulum File Transfer Utility
|
||||
|
||||
@@ -594,10 +613,12 @@ Or fetch a file from the remote system:
|
||||
-q, --quiet decrease verbosity
|
||||
-S, --silent disable transfer progress output
|
||||
-l, --listen listen for incoming transfer requests
|
||||
-F, --allow-fetch allow authenticated clients to fetch files
|
||||
-f, --fetch fetch file from remote listener instead of sending
|
||||
-j path, --jail path restrict fetch requests to specified path
|
||||
-b seconds announce interval, 0 to only announce at startup
|
||||
-a allowed_hash accept from this identity
|
||||
-n, --no-auth accept files and fetches from anyone
|
||||
-a allowed_hash allow this identity
|
||||
-n, --no-auth accept requests from anyone
|
||||
-p, --print-identity print identity and destination info and exit
|
||||
-w seconds sender timeout before giving up
|
||||
--version show program's version number and exit
|
||||
@@ -685,15 +706,19 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnodeconf.py [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-e]
|
||||
[-E] [-C] [--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
|
||||
[--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate]
|
||||
[--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [-P]
|
||||
[--trust-key hexbytes] [--version] [port]
|
||||
usage: rnodeconf [-h] [-i] [-a] [-u] [-U] [--fw-version version]
|
||||
[--fw-url url] [--nocheck] [-e] [-E] [-C]
|
||||
[--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
|
||||
[--display-addr byte] [--freq Hz] [--bw Hz] [--txp dBm]
|
||||
[--sf factor] [--cr rate] [--eeprom-backup] [--eeprom-dump]
|
||||
[--eeprom-wipe] [-P] [--trust-key hexbytes] [--version] [-f]
|
||||
[-r] [-k] [-S] [-H FIRMWARE_HASH] [--platform platform]
|
||||
[--product product] [--model model] [--hwrev revision]
|
||||
[port]
|
||||
|
||||
RNode Configuration and firmware utility. This program allows you to change various
|
||||
settings and startup modes of RNode. It can also install, flash and update the firmware
|
||||
on supported devices.
|
||||
RNode Configuration and firmware utility. This program allows you to change
|
||||
various settings and startup modes of RNode. It can also install, flash and
|
||||
update the firmware on supported devices.
|
||||
|
||||
positional arguments:
|
||||
port serial port where RNode is attached
|
||||
@@ -703,20 +728,26 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
|
||||
-i, --info Show device info
|
||||
-a, --autoinstall Automatic installation on various supported devices
|
||||
-u, --update Update firmware to the latest version
|
||||
-U, --force-update Update to specified firmware even if version matches or is older than installed version
|
||||
--fw-version version Use a specific firmware version for update or autoinstall
|
||||
-U, --force-update Update to specified firmware even if version matches
|
||||
or is older than installed version
|
||||
--fw-version version Use a specific firmware version for update or
|
||||
autoinstall
|
||||
--fw-url url Use an alternate firmware download URL
|
||||
--nocheck Don't check for firmware updates online
|
||||
-e, --extract Extract firmware from connected RNode for later use
|
||||
-E, --use-extracted Use the extracted firmware for autoinstallation or update
|
||||
-E, --use-extracted Use the extracted firmware for autoinstallation or
|
||||
update
|
||||
-C, --clear-cache Clear locally cached firmware files
|
||||
--baud-flash baud_flash
|
||||
Set specific baud rate when flashing device. Default is 921600
|
||||
Set specific baud rate when flashing device. Default
|
||||
is 921600
|
||||
-N, --normal Switch device to normal mode
|
||||
-T, --tnc Switch device to TNC mode
|
||||
-b, --bluetooth-on Turn device bluetooth on
|
||||
-B, --bluetooth-off Turn device bluetooth off
|
||||
-p, --bluetooth-pair Put device into bluetooth pairing mode
|
||||
-D i, --display i Set display intensity (0-255)
|
||||
--display-addr byte Set display address as hex byte (00 - FF)
|
||||
--freq Hz Frequency in Hz for TNC mode
|
||||
--bw Hz Bandwidth in Hz for TNC mode
|
||||
--txp dBm TX power in dBm for TNC mode
|
||||
@@ -728,10 +759,46 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
|
||||
-P, --public Display public part of signing key
|
||||
--trust-key hexbytes Public key to trust for device verification
|
||||
--version Print program version and exit
|
||||
-f, --flash Flash firmware and bootstrap EEPROM
|
||||
-r, --rom Bootstrap EEPROM without flashing firmware
|
||||
-k, --key Generate a new signing key and exit
|
||||
-S, --sign Display public part of signing key
|
||||
-H FIRMWARE_HASH, --firmware-hash FIRMWARE_HASH
|
||||
Display installed firmware hash
|
||||
--platform platform Platform specification for device bootstrap
|
||||
--product product Product specification for device bootstrap
|
||||
--model model Model code for device bootstrap
|
||||
--hwrev revision Hardware revision for device bootstrap
|
||||
|
||||
|
||||
For more information on how to create your own RNodes, please read the :ref:`Creating RNodes<rnode-creating>`
|
||||
section of this manual.
|
||||
|
||||
Remote Management
|
||||
-----------------
|
||||
|
||||
It is possible to allow remote management of Reticulum
|
||||
systems using the various built-in utilities, such as
|
||||
``rnstatus`` and ``rnpath``. To do so, you will need to set
|
||||
the ``enable_remote_management`` directive in the ``[reticulum]``
|
||||
section of the configuration file. You will also need to specify
|
||||
one or more Reticulum Identity hashes for authenticating the
|
||||
queries from client programs. For this purpose, you can use
|
||||
existing identity files, or generate new ones with the rnid utility.
|
||||
|
||||
The following is a truncated example of enabling remote management
|
||||
in the Reticulum configuration file:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[reticulum]
|
||||
...
|
||||
enable_remote_management = yes
|
||||
remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
|
||||
...
|
||||
|
||||
For a complete example configuration, you can run ``rnsd --exampleconfig``.
|
||||
|
||||
Improving System Configuration
|
||||
------------------------------
|
||||
|
||||
|
||||
@@ -53,9 +53,9 @@ What does Reticulum Offer?
|
||||
|
||||
* Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
|
||||
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
|
||||
* Reticulum uses a modified version of the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
|
||||
|
||||
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
@@ -63,6 +63,8 @@ What does Reticulum Offer?
|
||||
|
||||
* IVs are generated through os.urandom()
|
||||
|
||||
* No Version and Timestamp metadata included
|
||||
|
||||
* Unforgeable packet delivery confirmations
|
||||
|
||||
* A variety of supported interface types
|
||||
@@ -99,7 +101,8 @@ of the types of interfaces Reticulum was designed for.
|
||||
An open-source LoRa-based interface called `RNode <https://unsigned.io/rnode>`_
|
||||
has been designed as an example transceiver that is very suitable for
|
||||
Reticulum. It is possible to build it yourself, to transform a common LoRa
|
||||
development board into one, or it can be purchased as a complete transceiver.
|
||||
development board into one, or it can be purchased as a complete transceiver
|
||||
from various vendors.
|
||||
|
||||
Reticulum can also be encapsulated over existing IP networks, so there's
|
||||
nothing stopping you from using it over wired Ethernet or your local WiFi
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.7.6 beta',
|
||||
VERSION: '0.8.3 beta',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
@@ -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.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Code Examples - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -656,7 +656,6 @@ the Packet interface.</p>
|
||||
<span class="c1"># of the packet. #</span>
|
||||
<span class="c1">##########################################################</span>
|
||||
|
||||
<span class="kn">import</span> <span class="nn">os</span>
|
||||
<span class="kn">import</span> <span class="nn">argparse</span>
|
||||
<span class="kn">import</span> <span class="nn">RNS</span>
|
||||
|
||||
@@ -679,19 +678,8 @@ the Packet interface.</p>
|
||||
<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"># Load identity from file if it exist or randomley create</span>
|
||||
<span class="k">if</span> <span class="n">configpath</span><span class="p">:</span>
|
||||
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">"</span><span class="si">%s</span><span class="s2">/storage/identitiesy/</span><span class="si">%s</span><span class="s2">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">configpath</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">"</span><span class="si">%s</span><span class="s2">/storage/identities/</span><span class="si">%s</span><span class="s2">"</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">configdir</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">ifilepath</span><span class="p">):</span>
|
||||
<span class="c1"># Load identity from file</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">from_file</span><span class="p">(</span><span class="n">ifilepath</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">"loaded identity from file: "</span><span class="o">+</span><span class="n">ifilepath</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="c1"># Randomly create a new identity for our echo 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="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"created new identity"</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
|
||||
<span class="c1"># Randomly create a new identity for our echo server</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 query. We want</span>
|
||||
<span class="c1"># to be able to verify echo replies to our clients, so we</span>
|
||||
@@ -1030,20 +1018,8 @@ destination, and passing traffic back and forth over the link.</p>
|
||||
<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"># Load identity from file if it exist or randomley create</span>
|
||||
<span class="k">if</span> <span class="n">configpath</span><span class="p">:</span>
|
||||
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">"</span><span class="si">%s</span><span class="s2">/storage/identitiesy/</span><span class="si">%s</span><span class="s2">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">configpath</span><span class="p">,</span><span class="n">APP_NAME</span><span class="p">)</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">ifilepath</span> <span class="o">=</span> <span class="s2">"</span><span class="si">%s</span><span class="s2">/storage/identities/</span><span class="si">%s</span><span class="s2">"</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">configdir</span><span class="p">,</span><span class="n">APP_NAME</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">"ifilepath: </span><span class="si">%s</span><span class="s2">"</span> <span class="o">%</span> <span class="n">ifilepath</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">ifilepath</span><span class="p">):</span>
|
||||
<span class="c1"># Load identity from file</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">from_file</span><span class="p">(</span><span class="n">ifilepath</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">"loaded identity from file: "</span><span class="o">+</span><span class="n">ifilepath</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</span><span class="p">)</span>
|
||||
<span class="k">else</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="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"created new identity"</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_VERBOSE</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>
|
||||
@@ -1072,7 +1048,7 @@ destination, and passing traffic back and forth over the link.</p>
|
||||
<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 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="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>
|
||||
@@ -1082,12 +1058,6 @@ destination, and passing traffic back and forth over the link.</p>
|
||||
<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="k">if</span> <span class="n">entered</span> <span class="o">==</span> <span class="s2">"quit"</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="n">latest_client_link</span><span class="p">:</span>
|
||||
<span class="n">latest_client_link</span><span class="o">.</span><span class="n">teardown</span><span class="p">()</span>
|
||||
<span class="k">break</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>
|
||||
|
||||
<span class="c1"># When a client establishes a link to our server</span>
|
||||
<span class="c1"># destination, this function will be called with</span>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
|
||||
@@ -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.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -293,6 +293,8 @@
|
||||
<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.current_ratchet_id">current_ratchet_id() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.CURVE">CURVE (RNS.Identity attribute)</a>
|
||||
|
||||
@@ -330,6 +332,8 @@
|
||||
<h2>E</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.enable_ratchets">enable_ratchets() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.encrypt">encrypt() (RNS.Destination method)</a>
|
||||
|
||||
<ul>
|
||||
@@ -339,6 +343,8 @@
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Packet.ENCRYPTED_MDU">ENCRYPTED_MDU (RNS.Packet attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.enforce_ratchets">enforce_ratchets() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP">ESTABLISHMENT_TIMEOUT_PER_HOP (RNS.Link attribute)</a>
|
||||
</li>
|
||||
@@ -568,6 +574,14 @@
|
||||
<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.Destination.RATCHET_COUNT">RATCHET_COUNT (RNS.Destination attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.RATCHET_EXPIRY">RATCHET_EXPIRY (RNS.Identity attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.RATCHET_INTERVAL">RATCHET_INTERVAL (RNS.Destination attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.RATCHETSIZE">RATCHETSIZE (RNS.Identity attribute)</a>
|
||||
</li>
|
||||
<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>
|
||||
@@ -580,12 +594,12 @@
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.register_message_type">register_message_type() (RNS.Channel.Channel method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.register_request_handler">register_request_handler() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.remote_management_enabled">remote_management_enabled() (RNS.Reticulum static 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>
|
||||
@@ -633,6 +647,8 @@
|
||||
<li><a href="reference.html#RNS.Destination.set_proof_requested_callback">set_proof_requested_callback() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.set_proof_strategy">set_proof_strategy() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.set_ratchet_interval">set_ratchet_interval() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.set_remote_identified_callback">set_remote_identified_callback() (RNS.Link method)</a>
|
||||
</li>
|
||||
@@ -645,6 +661,8 @@
|
||||
<li><a href="reference.html#RNS.Link.set_resource_started_callback">set_resource_started_callback() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.set_resource_strategy">set_resource_strategy() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Destination.set_retained_ratchets">set_retained_ratchets() (RNS.Destination method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.set_timeout">set_timeout() (RNS.PacketReceipt method)</a>
|
||||
</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="Using Reticulum on Your System" href="using.html" /><link rel="prev" title="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<link rel="index" title="Index" href="genindex.html" /><link rel="search" title="Search" href="search.html" /><link rel="next" title="Configuring Interfaces" href="interfaces.html" /><link rel="prev" title="Understanding Reticulum" href="understanding.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -291,65 +291,126 @@ to the configuration.</p>
|
||||
<span id="rnode-supported"></span><h3>Supported Boards<a class="headerlink" href="#supported-boards" title="Permalink to this heading">#</a></h3>
|
||||
<p>To create one or more RNodes, you will need to obtain supported development
|
||||
boards. The following boards are supported by the auto-installer.</p>
|
||||
<section id="lilygo-lora32-v2-1">
|
||||
<h4>LilyGO LoRa32 v2.1<a class="headerlink" href="#lilygo-lora32-v2-1" title="Permalink to this heading">#</a></h4>
|
||||
<a class="reference internal image-reference" href="_images/board_t3v21.png"><img alt="_images/board_t3v21.png" class="align-center" src="_images/board_t3v21.png" style="width: 46%;" /></a>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_tbeam_supreme.png"><img alt="_images/board_tbeam_supreme.png" class="align-center" src="_images/board_tbeam_supreme.png" style="width: 75%;" /></a>
|
||||
<section id="lilygo-t-beam-supreme">
|
||||
<h4>LilyGO T-Beam Supreme<a class="headerlink" href="#lilygo-t-beam-supreme" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Supported Firmware Lines</strong> v1.x & v2.x</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="lilygo-lora32-v2-0">
|
||||
<h4>LilyGO LoRa32 v2.0<a class="headerlink" href="#lilygo-lora32-v2-0" title="Permalink to this heading">#</a></h4>
|
||||
<a class="reference internal image-reference" href="_images/board_t3v20.png"><img alt="_images/board_t3v20.png" class="align-center" src="_images/board_t3v20.png" style="width: 46%;" /></a>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Supported Firmware Lines</strong> v1.x & v2.x</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_tbeam.png"><img alt="_images/board_tbeam.png" class="align-center" src="_images/board_tbeam.png" style="width: 75%;" /></a>
|
||||
</section>
|
||||
<section id="lilygo-t-beam">
|
||||
<h4>LilyGO T-Beam<a class="headerlink" href="#lilygo-t-beam" title="Permalink to this heading">#</a></h4>
|
||||
<a class="reference internal image-reference" href="_images/board_tbeam.png"><img alt="_images/board_tbeam.png" class="align-center" src="_images/board_tbeam.png" style="width: 75%;" /></a>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Supported Firmware Lines</strong> v1.x & v2.x</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268, SX1276 and SX1278</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_t3s3.png"><img alt="_images/board_t3s3.png" class="align-center" src="_images/board_t3s3.png" style="width: 50%;" /></a>
|
||||
</section>
|
||||
<section id="heltec-lora32-v2-0">
|
||||
<h4>Heltec LoRa32 v2.0<a class="headerlink" href="#heltec-lora32-v2-0" title="Permalink to this heading">#</a></h4>
|
||||
<a class="reference internal image-reference" href="_images/board_heltec32.png"><img alt="_images/board_heltec32.png" class="align-center" src="_images/board_heltec32.png" style="width: 58%;" /></a>
|
||||
<section id="lilygo-t3s3">
|
||||
<h4>LilyGO T3S3<a class="headerlink" href="#lilygo-t3s3" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Supported Firmware Lines</strong> v1.x & v2.x</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268, SX1276 and SX1278</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://heltec.org">Heltec Automation</a></p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_rak4631.png"><img alt="_images/board_rak4631.png" class="align-center" src="_images/board_rak4631.png" style="width: 45%;" /></a>
|
||||
</section>
|
||||
<section id="rak4631-based-boards">
|
||||
<h4>RAK4631-based Boards<a class="headerlink" href="#rak4631-based-boards" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268</p></li>
|
||||
<li><p><strong>Device Platform</strong> nRF52</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://www.rakwireless.com">RAK Wireless</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_rnodev2.png"><img alt="_images/board_rnodev2.png" class="align-center" src="_images/board_rnodev2.png" style="width: 68%;" /></a>
|
||||
</section>
|
||||
<section id="unsigned-rnode-v2-x">
|
||||
<h4>Unsigned RNode v2.x<a class="headerlink" href="#unsigned-rnode-v2-x" title="Permalink to this heading">#</a></h4>
|
||||
<a class="reference internal image-reference" href="_images/board_rnodev2.png"><img alt="_images/board_rnodev2.png" class="align-center" src="_images/board_rnodev2.png" style="width: 58%;" /></a>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Supported Firmware Lines</strong> v1.x & v2.x</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://unsigned.io">unsigned.io</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_t3v21.png"><img alt="_images/board_t3v21.png" class="align-center" src="_images/board_t3v21.png" style="width: 46%;" /></a>
|
||||
</section>
|
||||
<section id="lilygo-lora32-v2-1">
|
||||
<h4>LilyGO LoRa32 v2.1<a class="headerlink" href="#lilygo-lora32-v2-1" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_t3v20.png"><img alt="_images/board_t3v20.png" class="align-center" src="_images/board_t3v20.png" style="width: 46%;" /></a>
|
||||
</section>
|
||||
<section id="lilygo-lora32-v2-0">
|
||||
<h4>LilyGO LoRa32 v2.0<a class="headerlink" href="#lilygo-lora32-v2-0" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_t3v10.png"><img alt="_images/board_t3v10.png" class="align-center" src="_images/board_t3v10.png" style="width: 46%;" /></a>
|
||||
</section>
|
||||
<section id="lilygo-lora32-v1-0">
|
||||
<h4>LilyGO LoRa32 v1.0<a class="headerlink" href="#lilygo-lora32-v1-0" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_tdeck.png"><img alt="_images/board_tdeck.png" class="align-center" src="_images/board_tdeck.png" style="width: 45%;" /></a>
|
||||
</section>
|
||||
<section id="lilygo-t-deck">
|
||||
<h4>LilyGO T-Deck<a class="headerlink" href="#lilygo-t-deck" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1262, SX1268</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://lilygo.cn">LilyGO</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_heltec32v30.png"><img alt="_images/board_heltec32v30.png" class="align-center" src="_images/board_heltec32v30.png" style="width: 58%;" /></a>
|
||||
</section>
|
||||
<section id="heltec-lora32-v3-0">
|
||||
<h4>Heltec LoRa32 v3.0<a class="headerlink" href="#heltec-lora32-v3-0" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1262 and SX1268</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://heltec.org">Heltec Automation</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_heltec32v20.png"><img alt="_images/board_heltec32v20.png" class="align-center" src="_images/board_heltec32v20.png" style="width: 58%;" /></a>
|
||||
</section>
|
||||
<section id="heltec-lora32-v2-0">
|
||||
<h4>Heltec LoRa32 v2.0<a class="headerlink" href="#heltec-lora32-v2-0" title="Permalink to this heading">#</a></h4>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
|
||||
<li><p><strong>Device Platform</strong> ESP32</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://heltec.org">Heltec Automation</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
<a class="reference internal image-reference" href="_images/board_rnode.png"><img alt="_images/board_rnode.png" class="align-center" src="_images/board_rnode.png" style="width: 50%;" /></a>
|
||||
</section>
|
||||
<section id="unsigned-rnode-v1-x">
|
||||
<h4>Unsigned RNode v1.x<a class="headerlink" href="#unsigned-rnode-v1-x" title="Permalink to this heading">#</a></h4>
|
||||
<a class="reference internal image-reference" href="_images/board_rnode.png"><img alt="_images/board_rnode.png" class="align-center" src="_images/board_rnode.png" style="width: 50%;" /></a>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Supported Firmware Lines</strong> v1.x</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276</p></li>
|
||||
<li><p><strong>Transceiver IC</strong> Semtech SX1276 and SX1278</p></li>
|
||||
<li><p><strong>Device Platform</strong> AVR ATmega1284p</p></li>
|
||||
<li><p><strong>Manufacturer</strong> <a class="reference external" href="https://unsigned.io">unsigned.io</a></p></li>
|
||||
</ul>
|
||||
<hr class="docutils" />
|
||||
</section>
|
||||
</section>
|
||||
<section id="installation">
|
||||
@@ -375,10 +436,8 @@ auto-install and configure your devices.</p>
|
||||
<span id="rnode-usage"></span><h3>Usage with Reticulum<a class="headerlink" href="#usage-with-reticulum" title="Permalink to this heading">#</a></h3>
|
||||
<p>When the devices have been installed and provisioned, you can use them with Reticulum
|
||||
by adding the <a class="reference internal" href="interfaces.html#interfaces-rnode"><span class="std std-ref">relevant interface section</span></a> to the configuration
|
||||
file of Reticulum. For v1.x firmwares, you will have to specify all interface parameters,
|
||||
such as serial port and on-air parameters. For v2.x firmwares, you just need to specify
|
||||
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
|
||||
RNode, using the parameters stored in the RNode itself.</p>
|
||||
file of Reticulum. In the configuraion you can specify all interface parameters,
|
||||
such as serial port and on-air parameters.</p>
|
||||
</section>
|
||||
</section>
|
||||
<section id="wifi-based-hardware">
|
||||
@@ -492,11 +551,17 @@ can be used with Reticulum. This includes virtual software modems such as
|
||||
<li><a class="reference internal" href="#rnode">RNode</a><ul>
|
||||
<li><a class="reference internal" href="#creating-rnodes">Creating RNodes</a></li>
|
||||
<li><a class="reference internal" href="#supported-boards">Supported Boards</a><ul>
|
||||
<li><a class="reference internal" href="#lilygo-t-beam-supreme">LilyGO T-Beam Supreme</a></li>
|
||||
<li><a class="reference internal" href="#lilygo-t-beam">LilyGO T-Beam</a></li>
|
||||
<li><a class="reference internal" href="#lilygo-t3s3">LilyGO T3S3</a></li>
|
||||
<li><a class="reference internal" href="#rak4631-based-boards">RAK4631-based Boards</a></li>
|
||||
<li><a class="reference internal" href="#unsigned-rnode-v2-x">Unsigned RNode v2.x</a></li>
|
||||
<li><a class="reference internal" href="#lilygo-lora32-v2-1">LilyGO LoRa32 v2.1</a></li>
|
||||
<li><a class="reference internal" href="#lilygo-lora32-v2-0">LilyGO LoRa32 v2.0</a></li>
|
||||
<li><a class="reference internal" href="#lilygo-t-beam">LilyGO T-Beam</a></li>
|
||||
<li><a class="reference internal" href="#lilygo-lora32-v1-0">LilyGO LoRa32 v1.0</a></li>
|
||||
<li><a class="reference internal" href="#lilygo-t-deck">LilyGO T-Deck</a></li>
|
||||
<li><a class="reference internal" href="#heltec-lora32-v3-0">Heltec LoRa32 v3.0</a></li>
|
||||
<li><a class="reference internal" href="#heltec-lora32-v2-0">Heltec LoRa32 v2.0</a></li>
|
||||
<li><a class="reference internal" href="#unsigned-rnode-v2-x">Unsigned RNode v2.x</a></li>
|
||||
<li><a class="reference internal" href="#unsigned-rnode-v1-x">Unsigned RNode v1.x</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="What is Reticulum?" href="whatis.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -281,6 +281,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#the-rnodeconf-utility">The rnodeconf Utility</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="using.html#remote-management">Remote Management</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="using.html#improving-system-configuration">Improving System Configuration</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#fixed-serial-port-names">Fixed Serial Port Names</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="using.html#reticulum-as-a-system-service">Reticulum as a System Service</a></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="Building Networks" href="networks.html" /><link rel="prev" title="Communications Hardware" href="hardware.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -520,6 +520,23 @@ can be used, and offers full control over LoRa parameters.</p>
|
||||
<span class="c1"># Serial port for the device</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
|
||||
|
||||
<span class="c1"># It is also possible to use BLE devices</span>
|
||||
<span class="c1"># instead of wired serial ports. The</span>
|
||||
<span class="c1"># target RNode must be paired with the</span>
|
||||
<span class="c1"># host device before connecting. BLE</span>
|
||||
<span class="c1"># devices can be connected by name,</span>
|
||||
<span class="c1"># BLE MAC address or by any available.</span>
|
||||
|
||||
<span class="c1"># Connect to specific device by name</span>
|
||||
<span class="c1"># port = ble://RNode 3B87</span>
|
||||
|
||||
<span class="c1"># Or by BLE MAC address</span>
|
||||
<span class="c1"># port = ble://F4:12:73:29:4E:89</span>
|
||||
|
||||
<span class="c1"># Or connect to the first available,</span>
|
||||
<span class="c1"># paired device</span>
|
||||
<span class="c1"># port = ble://</span>
|
||||
|
||||
<span class="c1"># Set frequency to 867.2 MHz</span>
|
||||
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">867200000</span>
|
||||
|
||||
|
||||
@@ -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="Configuring Interfaces" href="interfaces.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Building Networks - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
|
||||
@@ -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.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>API Reference - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -356,7 +356,21 @@ for all encrypted communication over Reticulum networks.</p>
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.KEYSIZE">
|
||||
<span class="sig-name descname"><span class="pre">KEYSIZE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">512</span></em><a class="headerlink" href="#RNS.Identity.KEYSIZE" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>X25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.</p>
|
||||
<dd><p>X.25519 key size in bits. A complete key is the concatenation of a 256 bit encryption key, and a 256 bit signing key.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.RATCHETSIZE">
|
||||
<span class="sig-name descname"><span class="pre">RATCHETSIZE</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">256</span></em><a class="headerlink" href="#RNS.Identity.RATCHETSIZE" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>X.25519 ratchet key size in bits.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.RATCHET_EXPIRY">
|
||||
<span class="sig-name descname"><span class="pre">RATCHET_EXPIRY</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">2592000</span></em><a class="headerlink" href="#RNS.Identity.RATCHET_EXPIRY" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>The expiry time for received ratchets in seconds, defaults to 30 days. Reticulum will always use the most recently
|
||||
announced ratchet, and remember it for up to <code class="docutils literal notranslate"><span class="pre">RATCHET_EXPIRY</span></code> since receiving it, after which it will be discarded.
|
||||
If a newer ratchet is announced in the meantime, it will be replace the already known ratchet.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
@@ -403,7 +417,7 @@ for addressable hashes and other purposes. Non-configurable.</p>
|
||||
<dd class="field-odd"><p><strong>data</strong> – Data to be hashed as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>SHA-256 hash as <em>bytes</em></p>
|
||||
<dd class="field-even"><p>SHA-256 hash as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
@@ -417,7 +431,7 @@ for addressable hashes and other purposes. Non-configurable.</p>
|
||||
<dd class="field-odd"><p><strong>data</strong> – Data to be hashed as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>Truncated SHA-256 hash as <em>bytes</em></p>
|
||||
<dd class="field-even"><p>Truncated SHA-256 hash as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
@@ -431,7 +445,21 @@ for addressable hashes and other purposes. Non-configurable.</p>
|
||||
<dd class="field-odd"><p><strong>data</strong> – Data to be hashed as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>Truncated SHA-256 hash of random data as <em>bytes</em></p>
|
||||
<dd class="field-even"><p>Truncated SHA-256 hash of random data as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.current_ratchet_id">
|
||||
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">current_ratchet_id</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">destination_hash</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.current_ratchet_id" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Get the ID of the currently used ratchet key for a given destination hash</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p><strong>destination_hash</strong> – A destination hash as <em>bytes</em>.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>A ratchet ID as <em>bytes</em> or <em>None</em>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
@@ -532,7 +560,7 @@ communication for the identity. Be very careful with this method.</p>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.encrypt">
|
||||
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.encrypt" title="Permalink to this definition">#</a></dt>
|
||||
<span class="sig-name descname"><span class="pre">encrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">plaintext</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchet</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.Identity.encrypt" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Encrypts information for the identity.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
@@ -549,7 +577,7 @@ communication for the identity. Be very careful with this method.</p>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Identity.decrypt">
|
||||
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Identity.decrypt" title="Permalink to this definition">#</a></dt>
|
||||
<span class="sig-name descname"><span class="pre">decrypt</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ciphertext_token</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchets</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">enforce_ratchets</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">ratchet_id_receiver</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.Identity.decrypt" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Decrypts information for the identity.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
@@ -611,7 +639,7 @@ communication for the identity. Be very careful with this method.</p>
|
||||
instances are used both to create outgoing and incoming endpoints. The
|
||||
destination type will decide if encryption, and what type, is used in
|
||||
communication with the endpoint. A destination can also announce its
|
||||
presence on the network, which will also distribute necessary keys for
|
||||
presence on the network, which will distribute necessary keys for
|
||||
encrypted communication with it.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
@@ -624,6 +652,18 @@ encrypted communication with it.</p>
|
||||
</ul>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.RATCHET_COUNT">
|
||||
<span class="sig-name descname"><span class="pre">RATCHET_COUNT</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">512</span></em><a class="headerlink" href="#RNS.Destination.RATCHET_COUNT" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>The default number of generated ratchet keys a destination will retain, if it has ratchets enabled.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.RATCHET_INTERVAL">
|
||||
<span class="sig-name descname"><span class="pre">RATCHET_INTERVAL</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">1800</span></em><a class="headerlink" href="#RNS.Destination.RATCHET_INTERVAL" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>The minimum interval between rotating ratchet keys, in seconds.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.expand_name">
|
||||
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">expand_name</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">identity</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">app_name</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">*</span></span><span class="n"><span class="pre">aspects</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.expand_name" title="Permalink to this definition">#</a></dt>
|
||||
@@ -774,6 +814,64 @@ proofs should be returned for received packets.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.enable_ratchets">
|
||||
<span class="sig-name descname"><span class="pre">enable_ratchets</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">ratchets_path</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.enable_ratchets" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Enables ratchets on the destination. When ratchets are enabled, Reticulum will automatically rotate
|
||||
the keys used to encrypt packets to this destination, and include the latest ratchet key in announces.</p>
|
||||
<p>Enabling ratchets on a destination will provide forward secrecy for packets sent to that destination,
|
||||
even when sent outside a <code class="docutils literal notranslate"><span class="pre">Link</span></code>. The normal Reticulum <code class="docutils literal notranslate"><span class="pre">Link</span></code> establishment procedure already performs
|
||||
its own ephemeral key exchange for each link establishment, which means that ratchets are not necessary
|
||||
to provide forward secrecy for links.</p>
|
||||
<p>Enabling ratchets will have a small impact on announce size, adding 32 bytes to every sent announce.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p><strong>ratchets_path</strong> – The path to a file to store ratchet data in.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>True if the operation succeeded, otherwise False.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.enforce_ratchets">
|
||||
<span class="sig-name descname"><span class="pre">enforce_ratchets</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.enforce_ratchets" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>When ratchet enforcement is enabled, this destination will never accept packets that use its
|
||||
base Identity key for encryption, but only accept packets encrypted with one of the retained
|
||||
ratchet keys.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.set_retained_ratchets">
|
||||
<span class="sig-name descname"><span class="pre">set_retained_ratchets</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">retained_ratchets</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.set_retained_ratchets" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Sets the number of previously generated ratchet keys this destination will retain,
|
||||
and try to use when decrypting incoming packets. Defaults to <code class="docutils literal notranslate"><span class="pre">Destination.RATCHET_COUNT</span></code>.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p><strong>retained_ratchets</strong> – The number of generated ratchets to retain.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>True if the operation succeeded, False if not.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.set_ratchet_interval">
|
||||
<span class="sig-name descname"><span class="pre">set_ratchet_interval</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">interval</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.set_ratchet_interval" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Sets the minimum interval in seconds between ratchet key rotation.
|
||||
Defaults to <code class="docutils literal notranslate"><span class="pre">Destination.RATCHET_INTERVAL</span></code>.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p><strong>interval</strong> – The minimum interval in seconds.</p>
|
||||
</dd>
|
||||
<dt class="field-even">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-even"><p>True if the operation succeeded, False if not.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Destination.create_keys">
|
||||
<span class="sig-name descname"><span class="pre">create_keys</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Destination.create_keys" title="Permalink to this definition">#</a></dt>
|
||||
@@ -1814,7 +1912,9 @@ Transport system of Reticulum.</p>
|
||||
<dd><p>Registers an announce handler.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Parameters<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p><strong>handler</strong> – Must be an object with an <em>aspect_filter</em> attribute and a <em>received_announce(destination_hash, announced_identity, app_data)</em> callable. See the <a class="reference internal" href="examples.html#example-announce"><span class="std std-ref">Announce Example</span></a> for more info.</p>
|
||||
<dd class="field-odd"><p><strong>handler</strong> – Must be an object with an <em>aspect_filter</em> attribute and a <em>received_announce(destination_hash, announced_identity, app_data)</em>
|
||||
callable. Can optionally have a <em>receive_path_responses</em> attribute set to <code class="docutils literal notranslate"><span class="pre">True</span></code>, to also receive all path responses, in addition to live
|
||||
announces. See the <a class="reference internal" href="examples.html#example-announce"><span class="std std-ref">Announce Example</span></a> for more info.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
@@ -1964,12 +2064,15 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#RNS.Identity"><code class="docutils literal notranslate"><span class="pre">Identity</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.Identity.CURVE"><code class="docutils literal notranslate"><span class="pre">CURVE</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.KEYSIZE"><code class="docutils literal notranslate"><span class="pre">KEYSIZE</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.RATCHETSIZE"><code class="docutils literal notranslate"><span class="pre">RATCHETSIZE</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.RATCHET_EXPIRY"><code class="docutils literal notranslate"><span class="pre">RATCHET_EXPIRY</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.TRUNCATED_HASHLENGTH"><code class="docutils literal notranslate"><span class="pre">TRUNCATED_HASHLENGTH</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.recall"><code class="docutils literal notranslate"><span class="pre">recall()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.recall_app_data"><code class="docutils literal notranslate"><span class="pre">recall_app_data()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.full_hash"><code class="docutils literal notranslate"><span class="pre">full_hash()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.truncated_hash"><code class="docutils literal notranslate"><span class="pre">truncated_hash()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.get_random_hash"><code class="docutils literal notranslate"><span class="pre">get_random_hash()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.current_ratchet_id"><code class="docutils literal notranslate"><span class="pre">current_ratchet_id()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.from_bytes"><code class="docutils literal notranslate"><span class="pre">from_bytes()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.from_file"><code class="docutils literal notranslate"><span class="pre">from_file()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Identity.to_file"><code class="docutils literal notranslate"><span class="pre">to_file()</span></code></a></li>
|
||||
@@ -1984,6 +2087,8 @@ will announce it.</p>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.Destination"><code class="docutils literal notranslate"><span class="pre">Destination</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.Destination.RATCHET_COUNT"><code class="docutils literal notranslate"><span class="pre">RATCHET_COUNT</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.RATCHET_INTERVAL"><code class="docutils literal notranslate"><span class="pre">RATCHET_INTERVAL</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.expand_name"><code class="docutils literal notranslate"><span class="pre">expand_name()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.app_and_aspects_from_name"><code class="docutils literal notranslate"><span class="pre">app_and_aspects_from_name()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.hash_from_name_and_identity"><code class="docutils literal notranslate"><span class="pre">hash_from_name_and_identity()</span></code></a></li>
|
||||
@@ -1996,6 +2101,10 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#RNS.Destination.set_proof_strategy"><code class="docutils literal notranslate"><span class="pre">set_proof_strategy()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.register_request_handler"><code class="docutils literal notranslate"><span class="pre">register_request_handler()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.deregister_request_handler"><code class="docutils literal notranslate"><span class="pre">deregister_request_handler()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.enable_ratchets"><code class="docutils literal notranslate"><span class="pre">enable_ratchets()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.enforce_ratchets"><code class="docutils literal notranslate"><span class="pre">enforce_ratchets()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.set_retained_ratchets"><code class="docutils literal notranslate"><span class="pre">set_retained_ratchets()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.set_ratchet_interval"><code class="docutils literal notranslate"><span class="pre">set_ratchet_interval()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.create_keys"><code class="docutils literal notranslate"><span class="pre">create_keys()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.get_private_key"><code class="docutils literal notranslate"><span class="pre">get_private_key()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Destination.load_private_key"><code class="docutils literal notranslate"><span class="pre">load_private_key()</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.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.7.6 beta documentation</title><link rel="stylesheet" type="text/css" href="_static/pygments.css" />
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Search - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
|
||||
@@ -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.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
|
||||
@@ -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.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -360,10 +360,11 @@ be sufficient, even far into the future.</p>
|
||||
<p>By default Reticulum encrypts all data using elliptic curve cryptography and AES. Any packet sent to a
|
||||
destination is encrypted with a per-packet derived key. Reticulum can also set up an encrypted
|
||||
channel to a destination, called a <em>Link</em>. Both data sent over Links and single packets offer
|
||||
<em>Initiator Anonymity</em>, and links additionally offer <em>Forward Secrecy</em> by using an Elliptic Curve
|
||||
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. The multi-hop transport,
|
||||
coordination, verification and reliability layers are fully autonomous and also based on elliptic
|
||||
curve cryptography.</p>
|
||||
<em>Initiator Anonymity</em>. Links additionally offer <em>Forward Secrecy</em> by default, employing an Elliptic Curve
|
||||
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. Asymmetric, link-less
|
||||
packet communication can also provide forward secrecy, with automatic key ratcheting, by enabling
|
||||
ratchets on a per-destination basis. The multi-hop transport, coordination, verification and reliability
|
||||
layers are fully autonomous and also based on elliptic curve cryptography.</p>
|
||||
<p>Reticulum also offers symmetric key encryption for group-oriented communications, as well as
|
||||
unencrypted packets for local broadcast purposes.</p>
|
||||
<p>Reticulum can connect to a variety of interfaces such as radio modems, data radios and serial ports,
|
||||
@@ -639,7 +640,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 destination’s public key, and encrypt the information.</div>
|
||||
an ECDH key exchange with the destination’s public key (or ratchet key, if available), and encrypt the information.</div>
|
||||
</div>
|
||||
</li>
|
||||
<li><div class="line-block">
|
||||
@@ -927,7 +928,8 @@ A Reticulum packet is composed of the following fields:
|
||||
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
|
||||
|
||||
* The HEADER field is 2 bytes long.
|
||||
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
|
||||
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
|
||||
[Destination Type] and [Packet Type]
|
||||
* Byte 2: Number of hops
|
||||
|
||||
* Interface Access Code field if the IFAC flag was set.
|
||||
@@ -959,12 +961,16 @@ type 1 0 Two byte header, one 16 byte address field
|
||||
type 2 1 Two byte header, two 16 byte address fields
|
||||
|
||||
|
||||
Context Flag
|
||||
-----------------
|
||||
unset 0 The context flag is used for various types
|
||||
set 1 of signalling, depending on packet context
|
||||
|
||||
|
||||
Propagation Types
|
||||
-----------------
|
||||
broadcast 00
|
||||
transport 01
|
||||
reserved 10
|
||||
reserved 11
|
||||
broadcast 0
|
||||
transport 1
|
||||
|
||||
|
||||
Destination Types
|
||||
@@ -1063,10 +1069,11 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
|
||||
<li><p>Ed25519 for signatures</p></li>
|
||||
<li><p>X25519 for ECDH key exchanges</p></li>
|
||||
<li><p>HKDF for key derivation</p></li>
|
||||
<li><p>Fernet for encrypted tokens</p>
|
||||
<li><p>Modified Fernet for encrypted tokens</p>
|
||||
<ul>
|
||||
<li><p>AES-128 in CBC mode</p></li>
|
||||
<li><p>HMAC for message authentication</p></li>
|
||||
<li><p>No Version and Timestamp metadata included</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>SHA-256</p></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="Understanding Reticulum" href="understanding.html" /><link rel="prev" title="Getting Started Fast" href="gettingstartedfast.html" />
|
||||
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -492,8 +492,9 @@ Reticulum Transport Instance <5245a8efe1788c6a1cd36144a270e13b> running
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnstatus.py [-h] [--config CONFIG] [--version] [-a] [-A] [-s SORT]
|
||||
[-r] [-j] [-v] [filter]
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnstatus [-h] [--config CONFIG] [--version] [-a] [-A]
|
||||
[-l] [-s SORT] [-r] [-j] [-R hash] [-i path]
|
||||
[-w seconds] [-v] [filter]
|
||||
|
||||
Reticulum Network Stack Status
|
||||
|
||||
@@ -506,9 +507,13 @@ options:
|
||||
--version show program's version number and exit
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-l, --link-stats show link stats
|
||||
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
|
||||
-r, --reverse reverse sorting
|
||||
-j, --json output in JSON format
|
||||
-R hash transport identity hash of remote instance to get status from
|
||||
-i path path to identity used for remote management
|
||||
-w seconds timeout before giving up on remote queries
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -609,8 +614,9 @@ Path found, destination <c89b4da064bf66d280f0e4d8abfd9806> is 4 hops away
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D]
|
||||
[-x] [-w seconds] [-v] [destination]
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-m hops]
|
||||
[-r] [-d] [-D] [-x] [-w seconds] [-R hash] [-i path]
|
||||
[-W seconds] [-j] [-v] [destination]
|
||||
|
||||
Reticulum Path Discovery Utility
|
||||
|
||||
@@ -622,11 +628,16 @@ options:
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-t, --table show all known paths
|
||||
-m hops, --max hops maximum hops to filter path table by
|
||||
-r, --rates show announce rate info
|
||||
-d, --drop remove the path to a destination
|
||||
-D, --drop-announces drop all queued announces
|
||||
-x, --drop-via drop all paths via specified transport instance
|
||||
-w seconds timeout before giving up
|
||||
-R hash transport identity hash of remote instance to manage
|
||||
-i path path to identity used for remote management
|
||||
-W seconds timeout before giving up on remote queries
|
||||
-j, --json output in JSON format
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -669,20 +680,27 @@ Round-trip time is 1.809 seconds over 1 hop [RSSI -73 dBm] [SNR 12.0 dB]
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [-s SIZE]
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnprobe [-h] [--config CONFIG] [-s SIZE] [-n PROBES]
|
||||
[-t seconds] [-w seconds] [--version] [-v]
|
||||
[full_name] [destination_hash]
|
||||
|
||||
Reticulum Probe Utility
|
||||
|
||||
positional arguments:
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-s SIZE, --size SIZE size of probe packet payload in bytes
|
||||
--version show program's version number and exit
|
||||
-n PROBES, --probes PROBES
|
||||
number of probes to send
|
||||
-t seconds, --timeout seconds
|
||||
timeout before giving up
|
||||
-w seconds, --wait seconds
|
||||
time between each probe
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -710,8 +728,9 @@ and simply running the program in listener mode:</p>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rncp.py [-h] [--config path] [-v] [-q] [-S] [-l] [-f] [-b seconds]
|
||||
[-a allowed_hash] [-n] [-p] [-w seconds] [--version] [file] [destination]
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rncp [-h] [--config path] [-v] [-q] [-S] [-l] [-F] [-f]
|
||||
[-j path] [-b seconds] [-a allowed_hash] [-n] [-p]
|
||||
[-w seconds] [--version] [file] [destination]
|
||||
|
||||
Reticulum File Transfer Utility
|
||||
|
||||
@@ -726,10 +745,12 @@ options:
|
||||
-q, --quiet decrease verbosity
|
||||
-S, --silent disable transfer progress output
|
||||
-l, --listen listen for incoming transfer requests
|
||||
-F, --allow-fetch allow authenticated clients to fetch files
|
||||
-f, --fetch fetch file from remote listener instead of sending
|
||||
-j path, --jail path restrict fetch requests to specified path
|
||||
-b seconds announce interval, 0 to only announce at startup
|
||||
-a allowed_hash accept from this identity
|
||||
-n, --no-auth accept files and fetches from anyone
|
||||
-a allowed_hash allow this identity
|
||||
-n, --no-auth accept requests from anyone
|
||||
-p, --print-identity print identity and destination info and exit
|
||||
-w seconds sender timeout before giving up
|
||||
--version show program's version number and exit
|
||||
@@ -800,15 +821,19 @@ optional arguments:
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">rnodeconf</span></code> utility allows you to inspect and configure existing <a class="reference internal" href="hardware.html#rnode-main"><span class="std std-ref">RNodes</span></a>, and
|
||||
to create and provision new <a class="reference internal" href="hardware.html#rnode-main"><span class="std std-ref">RNodes</span></a> from any supported hardware devices.</p>
|
||||
<p><strong>All Command-Line Options</strong></p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnodeconf.py [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-e]
|
||||
[-E] [-C] [--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
|
||||
[--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate]
|
||||
[--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [-P]
|
||||
[--trust-key hexbytes] [--version] [port]
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>usage: rnodeconf [-h] [-i] [-a] [-u] [-U] [--fw-version version]
|
||||
[--fw-url url] [--nocheck] [-e] [-E] [-C]
|
||||
[--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
|
||||
[--display-addr byte] [--freq Hz] [--bw Hz] [--txp dBm]
|
||||
[--sf factor] [--cr rate] [--eeprom-backup] [--eeprom-dump]
|
||||
[--eeprom-wipe] [-P] [--trust-key hexbytes] [--version] [-f]
|
||||
[-r] [-k] [-S] [-H FIRMWARE_HASH] [--platform platform]
|
||||
[--product product] [--model model] [--hwrev revision]
|
||||
[port]
|
||||
|
||||
RNode Configuration and firmware utility. This program allows you to change various
|
||||
settings and startup modes of RNode. It can also install, flash and update the firmware
|
||||
on supported devices.
|
||||
RNode Configuration and firmware utility. This program allows you to change
|
||||
various settings and startup modes of RNode. It can also install, flash and
|
||||
update the firmware on supported devices.
|
||||
|
||||
positional arguments:
|
||||
port serial port where RNode is attached
|
||||
@@ -818,20 +843,26 @@ options:
|
||||
-i, --info Show device info
|
||||
-a, --autoinstall Automatic installation on various supported devices
|
||||
-u, --update Update firmware to the latest version
|
||||
-U, --force-update Update to specified firmware even if version matches or is older than installed version
|
||||
--fw-version version Use a specific firmware version for update or autoinstall
|
||||
-U, --force-update Update to specified firmware even if version matches
|
||||
or is older than installed version
|
||||
--fw-version version Use a specific firmware version for update or
|
||||
autoinstall
|
||||
--fw-url url Use an alternate firmware download URL
|
||||
--nocheck Don't check for firmware updates online
|
||||
-e, --extract Extract firmware from connected RNode for later use
|
||||
-E, --use-extracted Use the extracted firmware for autoinstallation or update
|
||||
-E, --use-extracted Use the extracted firmware for autoinstallation or
|
||||
update
|
||||
-C, --clear-cache Clear locally cached firmware files
|
||||
--baud-flash baud_flash
|
||||
Set specific baud rate when flashing device. Default is 921600
|
||||
Set specific baud rate when flashing device. Default
|
||||
is 921600
|
||||
-N, --normal Switch device to normal mode
|
||||
-T, --tnc Switch device to TNC mode
|
||||
-b, --bluetooth-on Turn device bluetooth on
|
||||
-B, --bluetooth-off Turn device bluetooth off
|
||||
-p, --bluetooth-pair Put device into bluetooth pairing mode
|
||||
-D i, --display i Set display intensity (0-255)
|
||||
--display-addr byte Set display address as hex byte (00 - FF)
|
||||
--freq Hz Frequency in Hz for TNC mode
|
||||
--bw Hz Bandwidth in Hz for TNC mode
|
||||
--txp dBm TX power in dBm for TNC mode
|
||||
@@ -843,12 +874,43 @@ options:
|
||||
-P, --public Display public part of signing key
|
||||
--trust-key hexbytes Public key to trust for device verification
|
||||
--version Print program version and exit
|
||||
-f, --flash Flash firmware and bootstrap EEPROM
|
||||
-r, --rom Bootstrap EEPROM without flashing firmware
|
||||
-k, --key Generate a new signing key and exit
|
||||
-S, --sign Display public part of signing key
|
||||
-H FIRMWARE_HASH, --firmware-hash FIRMWARE_HASH
|
||||
Display installed firmware hash
|
||||
--platform platform Platform specification for device bootstrap
|
||||
--product product Product specification for device bootstrap
|
||||
--model model Model code for device bootstrap
|
||||
--hwrev revision Hardware revision for device bootstrap
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>For more information on how to create your own RNodes, please read the <a class="reference internal" href="hardware.html#rnode-creating"><span class="std std-ref">Creating RNodes</span></a>
|
||||
section of this manual.</p>
|
||||
</section>
|
||||
</section>
|
||||
<section id="remote-management">
|
||||
<h2>Remote Management<a class="headerlink" href="#remote-management" title="Permalink to this heading">#</a></h2>
|
||||
<p>It is possible to allow remote management of Reticulum
|
||||
systems using the various built-in utilities, such as
|
||||
<code class="docutils literal notranslate"><span class="pre">rnstatus</span></code> and <code class="docutils literal notranslate"><span class="pre">rnpath</span></code>. To do so, you will need to set
|
||||
the <code class="docutils literal notranslate"><span class="pre">enable_remote_management</span></code> directive in the <code class="docutils literal notranslate"><span class="pre">[reticulum]</span></code>
|
||||
section of the configuration file. You will also need to specify
|
||||
one or more Reticulum Identity hashes for authenticating the
|
||||
queries from client programs. For this purpose, you can use
|
||||
existing identity files, or generate new ones with the rnid utility.</p>
|
||||
<p>The following is a truncated example of enabling remote management
|
||||
in the Reticulum configuration file:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>[reticulum]
|
||||
...
|
||||
enable_remote_management = yes
|
||||
remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
|
||||
...
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>For a complete example configuration, you can run <code class="docutils literal notranslate"><span class="pre">rnsd</span> <span class="pre">--exampleconfig</span></code>.</p>
|
||||
</section>
|
||||
<section id="improving-system-configuration">
|
||||
<h2>Improving System Configuration<a class="headerlink" href="#improving-system-configuration" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you are setting up a system for permanent use with Reticulum, there is a
|
||||
@@ -1037,6 +1099,7 @@ systemctl --user enable rnsd.service
|
||||
<li><a class="reference internal" href="#the-rnodeconf-utility">The rnodeconf Utility</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#remote-management">Remote Management</a></li>
|
||||
<li><a class="reference internal" href="#improving-system-configuration">Improving System Configuration</a><ul>
|
||||
<li><a class="reference internal" href="#fixed-serial-port-names">Fixed Serial Port Names</a></li>
|
||||
<li><a class="reference internal" href="#reticulum-as-a-system-service">Reticulum as a System Service</a><ul>
|
||||
|
||||
@@ -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.3.0, furo 2022.09.29.dev1"/>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.7.6 beta documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.8.3 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=189ec851f9bb375a2509b67be1f64f0cf212b702" />
|
||||
<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.7.6 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.8.3 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.7.6 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.8.3 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">
|
||||
@@ -262,12 +262,13 @@ considered complete and stable at the moment, but could change if absolutely war
|
||||
<li><p>Complete initiator anonymity, communicate without revealing your identity</p></li>
|
||||
<li><p>Asymmetric encryption based on X25519, and Ed25519 signatures as a basis for all communication</p></li>
|
||||
<li><p>Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519</p></li>
|
||||
<li><p>Reticulum uses the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for on-the-wire / over-the-air encryption</p>
|
||||
<li><p>Reticulum uses a modified version of the <a class="reference external" href="https://github.com/fernet/spec/blob/master/Spec.md">Fernet</a> specification for on-the-wire / over-the-air encryption</p>
|
||||
<ul>
|
||||
<li><p>All keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
|
||||
<li><p>Keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
|
||||
<li><p>AES-128 in CBC mode with PKCS7 padding</p></li>
|
||||
<li><p>HMAC using SHA256 for authentication</p></li>
|
||||
<li><p>IVs are generated through os.urandom()</p></li>
|
||||
<li><p>No Version and Timestamp metadata included</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Unforgeable packet delivery confirmations</p></li>
|
||||
@@ -300,7 +301,8 @@ of the types of interfaces Reticulum was designed for.</p>
|
||||
<p>An open-source LoRa-based interface called <a class="reference external" href="https://unsigned.io/rnode">RNode</a>
|
||||
has been designed as an example transceiver that is very suitable for
|
||||
Reticulum. It is possible to build it yourself, to transform a common LoRa
|
||||
development board into one, or it can be purchased as a complete transceiver.</p>
|
||||
development board into one, or it can be purchased as a complete transceiver
|
||||
from various vendors.</p>
|
||||
<p>Reticulum can also be encapsulated over existing IP networks, so there’s
|
||||
nothing stopping you from using it over wired Ethernet or your local WiFi
|
||||
network, where it’ll work just as well. In fact, one of the strengths of
|
||||
|
||||
|
Before Width: | Height: | Size: 191 KiB After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 562 KiB After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 252 KiB |
|
After Width: | Height: | Size: 345 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 229 KiB |
|
After Width: | Height: | Size: 33 KiB |
@@ -90,77 +90,151 @@ Supported Boards
|
||||
To create one or more RNodes, you will need to obtain supported development
|
||||
boards. The following boards are supported by the auto-installer.
|
||||
|
||||
LilyGO LoRa32 v2.1
|
||||
""""""""""""""""""
|
||||
.. image:: graphics/board_t3v21.png
|
||||
:width: 46%
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_tbeam_supreme.png
|
||||
:width: 75%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
|
||||
LilyGO LoRa32 v2.0
|
||||
""""""""""""""""""
|
||||
.. image:: graphics/board_t3v20.png
|
||||
:width: 46%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
|
||||
LilyGO T-Beam
|
||||
LilyGO T-Beam Supreme
|
||||
"""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_tbeam.png
|
||||
:width: 75%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
LilyGO T-Beam
|
||||
"""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268, SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
Heltec LoRa32 v2.0
|
||||
""""""""""""""""""
|
||||
.. image:: graphics/board_heltec32.png
|
||||
:width: 58%
|
||||
.. image:: graphics/board_t3s3.png
|
||||
:width: 50%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
LilyGO T3S3
|
||||
"""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268, SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_rak4631.png
|
||||
:width: 45%
|
||||
:align: center
|
||||
|
||||
RAK4631-based Boards
|
||||
""""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268
|
||||
- **Device Platform** nRF52
|
||||
- **Manufacturer** `RAK Wireless <https://www.rakwireless.com>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_rnodev2.png
|
||||
:width: 68%
|
||||
:align: center
|
||||
|
||||
Unsigned RNode v2.x
|
||||
"""""""""""""""""""
|
||||
.. image:: graphics/board_rnodev2.png
|
||||
:width: 58%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x & v2.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `unsigned.io <https://unsigned.io>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_t3v21.png
|
||||
:width: 46%
|
||||
:align: center
|
||||
|
||||
LilyGO LoRa32 v2.1
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_t3v20.png
|
||||
:width: 46%
|
||||
:align: center
|
||||
|
||||
LilyGO LoRa32 v2.0
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_t3v10.png
|
||||
:width: 46%
|
||||
:align: center
|
||||
|
||||
LilyGO LoRa32 v1.0
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_tdeck.png
|
||||
:width: 45%
|
||||
:align: center
|
||||
|
||||
LilyGO T-Deck
|
||||
"""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262, SX1268
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `LilyGO <https://lilygo.cn>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_heltec32v30.png
|
||||
:width: 58%
|
||||
:align: center
|
||||
|
||||
Heltec LoRa32 v3.0
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1262 and SX1268
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
|
||||
|
||||
------------
|
||||
|
||||
.. image:: graphics/board_heltec32v20.png
|
||||
:width: 58%
|
||||
:align: center
|
||||
|
||||
Heltec LoRa32 v2.0
|
||||
""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** ESP32
|
||||
- **Manufacturer** `Heltec Automation <https://heltec.org>`_
|
||||
|
||||
------------
|
||||
|
||||
Unsigned RNode v1.x
|
||||
"""""""""""""""""""
|
||||
.. image:: graphics/board_rnode.png
|
||||
:width: 50%
|
||||
:align: center
|
||||
|
||||
- **Supported Firmware Lines** v1.x
|
||||
- **Transceiver IC** Semtech SX1276
|
||||
Unsigned RNode v1.x
|
||||
"""""""""""""""""""
|
||||
- **Transceiver IC** Semtech SX1276 and SX1278
|
||||
- **Device Platform** AVR ATmega1284p
|
||||
- **Manufacturer** `unsigned.io <https://unsigned.io>`_
|
||||
|
||||
------------
|
||||
|
||||
.. _rnode-installation:
|
||||
|
||||
@@ -194,10 +268,8 @@ Usage with Reticulum
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
When the devices have been installed and provisioned, you can use them with Reticulum
|
||||
by adding the :ref:`relevant interface section<interfaces-rnode>` to the configuration
|
||||
file of Reticulum. For v1.x firmwares, you will have to specify all interface parameters,
|
||||
such as serial port and on-air parameters. For v2.x firmwares, you just need to specify
|
||||
the Connection ID of the RNode, and Reticulum will automatically locate and connect to the
|
||||
RNode, using the parameters stored in the RNode itself.
|
||||
file of Reticulum. In the configuraion you can specify all interface parameters,
|
||||
such as serial port and on-air parameters.
|
||||
|
||||
|
||||
WiFi-based Hardware
|
||||
|
||||
@@ -358,6 +358,23 @@ can be used, and offers full control over LoRa parameters.
|
||||
# Serial port for the device
|
||||
port = /dev/ttyUSB0
|
||||
|
||||
# It is also possible to use BLE devices
|
||||
# instead of wired serial ports. The
|
||||
# target RNode must be paired with the
|
||||
# host device before connecting. BLE
|
||||
# devices can be connected by name,
|
||||
# BLE MAC address or by any available.
|
||||
|
||||
# Connect to specific device by name
|
||||
# port = ble://RNode 3B87
|
||||
|
||||
# Or by BLE MAC address
|
||||
# port = ble://F4:12:73:29:4E:89
|
||||
|
||||
# Or connect to the first available,
|
||||
# paired device
|
||||
# port = ble://
|
||||
|
||||
# Set frequency to 867.2 MHz
|
||||
frequency = 867200000
|
||||
|
||||
|
||||
@@ -134,10 +134,11 @@ be sufficient, even far into the future.
|
||||
By default Reticulum encrypts all data using elliptic curve cryptography and AES. Any packet sent to a
|
||||
destination is encrypted with a per-packet derived key. Reticulum can also set up an encrypted
|
||||
channel to a destination, called a *Link*. Both data sent over Links and single packets offer
|
||||
*Initiator Anonymity*, and links additionally offer *Forward Secrecy* by using an Elliptic Curve
|
||||
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. The multi-hop transport,
|
||||
coordination, verification and reliability layers are fully autonomous and also based on elliptic
|
||||
curve cryptography.
|
||||
*Initiator Anonymity*. Links additionally offer *Forward Secrecy* by default, employing an Elliptic Curve
|
||||
Diffie Hellman key exchange on Curve25519 to derive per-link ephemeral keys. Asymmetric, link-less
|
||||
packet communication can also provide forward secrecy, with automatic key ratcheting, by enabling
|
||||
ratchets on a per-destination basis. The multi-hop transport, coordination, verification and reliability
|
||||
layers are fully autonomous and also based on elliptic curve cryptography.
|
||||
|
||||
Reticulum also offers symmetric key encryption for group-oriented communications, as well as
|
||||
unencrypted packets for local broadcast purposes.
|
||||
@@ -431,7 +432,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 destination's public key, and encrypt the information.
|
||||
an ECDH key exchange with the destination's public key (or ratchet key, if available), 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
|
||||
@@ -693,7 +694,8 @@ Wire Format
|
||||
[HEADER 2 bytes] [ADDRESSES 16/32 bytes] [CONTEXT 1 byte] [DATA 0-465 bytes]
|
||||
|
||||
* The HEADER field is 2 bytes long.
|
||||
* Byte 1: [IFAC Flag], [Header Type], [Propagation Type], [Destination Type] and [Packet Type]
|
||||
* Byte 1: [IFAC Flag], [Header Type], [Context Flag], [Propagation Type],
|
||||
[Destination Type] and [Packet Type]
|
||||
* Byte 2: Number of hops
|
||||
|
||||
* Interface Access Code field if the IFAC flag was set.
|
||||
@@ -725,12 +727,16 @@ Wire Format
|
||||
type 2 1 Two byte header, two 16 byte address fields
|
||||
|
||||
|
||||
Context Flag
|
||||
-----------------
|
||||
unset 0 The context flag is used for various types
|
||||
set 1 of signalling, depending on packet context
|
||||
|
||||
|
||||
Propagation Types
|
||||
-----------------
|
||||
broadcast 00
|
||||
transport 01
|
||||
reserved 10
|
||||
reserved 11
|
||||
broadcast 0
|
||||
transport 1
|
||||
|
||||
|
||||
Destination Types
|
||||
@@ -862,12 +868,14 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
|
||||
|
||||
* HKDF for key derivation
|
||||
|
||||
* Fernet for encrypted tokens
|
||||
* Modified Fernet for encrypted tokens
|
||||
|
||||
* AES-128 in CBC mode
|
||||
|
||||
* HMAC for message authentication
|
||||
|
||||
* No Version and Timestamp metadata included
|
||||
|
||||
* SHA-256
|
||||
|
||||
* SHA-512
|
||||
|
||||
@@ -312,8 +312,9 @@ Filter output to only show some interfaces:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnstatus.py [-h] [--config CONFIG] [--version] [-a] [-A] [-s SORT]
|
||||
[-r] [-j] [-v] [filter]
|
||||
usage: rnstatus [-h] [--config CONFIG] [--version] [-a] [-A]
|
||||
[-l] [-s SORT] [-r] [-j] [-R hash] [-i path]
|
||||
[-w seconds] [-v] [filter]
|
||||
|
||||
Reticulum Network Stack Status
|
||||
|
||||
@@ -326,9 +327,13 @@ Filter output to only show some interfaces:
|
||||
--version show program's version number and exit
|
||||
-a, --all show all interfaces
|
||||
-A, --announce-stats show announce stats
|
||||
-l, --link-stats show link stats
|
||||
-s SORT, --sort SORT sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]
|
||||
-r, --reverse reverse sorting
|
||||
-j, --json output in JSON format
|
||||
-R hash transport identity hash of remote instance to get status from
|
||||
-i path path to identity used for remote management
|
||||
-w seconds timeout before giving up on remote queries
|
||||
-v, --verbose
|
||||
|
||||
|
||||
@@ -452,8 +457,9 @@ Resolve path to a destination:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnpath.py [-h] [--config CONFIG] [--version] [-t] [-r] [-d] [-D]
|
||||
[-x] [-w seconds] [-v] [destination]
|
||||
usage: rnpath [-h] [--config CONFIG] [--version] [-t] [-m hops]
|
||||
[-r] [-d] [-D] [-x] [-w seconds] [-R hash] [-i path]
|
||||
[-W seconds] [-j] [-v] [destination]
|
||||
|
||||
Reticulum Path Discovery Utility
|
||||
|
||||
@@ -465,11 +471,16 @@ Resolve path to a destination:
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
--version show program's version number and exit
|
||||
-t, --table show all known paths
|
||||
-m hops, --max hops maximum hops to filter path table by
|
||||
-r, --rates show announce rate info
|
||||
-d, --drop remove the path to a destination
|
||||
-D, --drop-announces drop all queued announces
|
||||
-x, --drop-via drop all paths via specified transport instance
|
||||
-w seconds timeout before giving up
|
||||
-R hash transport identity hash of remote instance to manage
|
||||
-i path path to identity used for remote management
|
||||
-W seconds timeout before giving up on remote queries
|
||||
-j, --json output in JSON format
|
||||
-v, --verbose
|
||||
|
||||
|
||||
@@ -524,20 +535,27 @@ these as part of the result as well.
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnprobe [-h] [--config CONFIG] [--version] [-v] [-s SIZE]
|
||||
usage: rnprobe [-h] [--config CONFIG] [-s SIZE] [-n PROBES]
|
||||
[-t seconds] [-w seconds] [--version] [-v]
|
||||
[full_name] [destination_hash]
|
||||
|
||||
Reticulum Probe Utility
|
||||
|
||||
positional arguments:
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
full_name full destination name in dotted notation
|
||||
destination_hash hexadecimal hash of the destination
|
||||
|
||||
optional arguments:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--config CONFIG path to alternative Reticulum config directory
|
||||
-s SIZE, --size SIZE size of probe packet payload in bytes
|
||||
--version show program's version number and exit
|
||||
-n PROBES, --probes PROBES
|
||||
number of probes to send
|
||||
-t seconds, --timeout seconds
|
||||
timeout before giving up
|
||||
-w seconds, --wait seconds
|
||||
time between each probe
|
||||
--version show program's version number and exit
|
||||
-v, --verbose
|
||||
|
||||
|
||||
@@ -578,8 +596,9 @@ Or fetch a file from the remote system:
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rncp.py [-h] [--config path] [-v] [-q] [-S] [-l] [-f] [-b seconds]
|
||||
[-a allowed_hash] [-n] [-p] [-w seconds] [--version] [file] [destination]
|
||||
usage: rncp [-h] [--config path] [-v] [-q] [-S] [-l] [-F] [-f]
|
||||
[-j path] [-b seconds] [-a allowed_hash] [-n] [-p]
|
||||
[-w seconds] [--version] [file] [destination]
|
||||
|
||||
Reticulum File Transfer Utility
|
||||
|
||||
@@ -594,10 +613,12 @@ Or fetch a file from the remote system:
|
||||
-q, --quiet decrease verbosity
|
||||
-S, --silent disable transfer progress output
|
||||
-l, --listen listen for incoming transfer requests
|
||||
-F, --allow-fetch allow authenticated clients to fetch files
|
||||
-f, --fetch fetch file from remote listener instead of sending
|
||||
-j path, --jail path restrict fetch requests to specified path
|
||||
-b seconds announce interval, 0 to only announce at startup
|
||||
-a allowed_hash accept from this identity
|
||||
-n, --no-auth accept files and fetches from anyone
|
||||
-a allowed_hash allow this identity
|
||||
-n, --no-auth accept requests from anyone
|
||||
-p, --print-identity print identity and destination info and exit
|
||||
-w seconds sender timeout before giving up
|
||||
--version show program's version number and exit
|
||||
@@ -685,15 +706,19 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
|
||||
|
||||
.. code:: text
|
||||
|
||||
usage: rnodeconf.py [-h] [-i] [-a] [-u] [-U] [--fw-version version] [--nocheck] [-e]
|
||||
[-E] [-C] [--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
|
||||
[--freq Hz] [--bw Hz] [--txp dBm] [--sf factor] [--cr rate]
|
||||
[--eeprom-backup] [--eeprom-dump] [--eeprom-wipe] [-P]
|
||||
[--trust-key hexbytes] [--version] [port]
|
||||
usage: rnodeconf [-h] [-i] [-a] [-u] [-U] [--fw-version version]
|
||||
[--fw-url url] [--nocheck] [-e] [-E] [-C]
|
||||
[--baud-flash baud_flash] [-N] [-T] [-b] [-B] [-p] [-D i]
|
||||
[--display-addr byte] [--freq Hz] [--bw Hz] [--txp dBm]
|
||||
[--sf factor] [--cr rate] [--eeprom-backup] [--eeprom-dump]
|
||||
[--eeprom-wipe] [-P] [--trust-key hexbytes] [--version] [-f]
|
||||
[-r] [-k] [-S] [-H FIRMWARE_HASH] [--platform platform]
|
||||
[--product product] [--model model] [--hwrev revision]
|
||||
[port]
|
||||
|
||||
RNode Configuration and firmware utility. This program allows you to change various
|
||||
settings and startup modes of RNode. It can also install, flash and update the firmware
|
||||
on supported devices.
|
||||
RNode Configuration and firmware utility. This program allows you to change
|
||||
various settings and startup modes of RNode. It can also install, flash and
|
||||
update the firmware on supported devices.
|
||||
|
||||
positional arguments:
|
||||
port serial port where RNode is attached
|
||||
@@ -703,20 +728,26 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
|
||||
-i, --info Show device info
|
||||
-a, --autoinstall Automatic installation on various supported devices
|
||||
-u, --update Update firmware to the latest version
|
||||
-U, --force-update Update to specified firmware even if version matches or is older than installed version
|
||||
--fw-version version Use a specific firmware version for update or autoinstall
|
||||
-U, --force-update Update to specified firmware even if version matches
|
||||
or is older than installed version
|
||||
--fw-version version Use a specific firmware version for update or
|
||||
autoinstall
|
||||
--fw-url url Use an alternate firmware download URL
|
||||
--nocheck Don't check for firmware updates online
|
||||
-e, --extract Extract firmware from connected RNode for later use
|
||||
-E, --use-extracted Use the extracted firmware for autoinstallation or update
|
||||
-E, --use-extracted Use the extracted firmware for autoinstallation or
|
||||
update
|
||||
-C, --clear-cache Clear locally cached firmware files
|
||||
--baud-flash baud_flash
|
||||
Set specific baud rate when flashing device. Default is 921600
|
||||
Set specific baud rate when flashing device. Default
|
||||
is 921600
|
||||
-N, --normal Switch device to normal mode
|
||||
-T, --tnc Switch device to TNC mode
|
||||
-b, --bluetooth-on Turn device bluetooth on
|
||||
-B, --bluetooth-off Turn device bluetooth off
|
||||
-p, --bluetooth-pair Put device into bluetooth pairing mode
|
||||
-D i, --display i Set display intensity (0-255)
|
||||
--display-addr byte Set display address as hex byte (00 - FF)
|
||||
--freq Hz Frequency in Hz for TNC mode
|
||||
--bw Hz Bandwidth in Hz for TNC mode
|
||||
--txp dBm TX power in dBm for TNC mode
|
||||
@@ -728,10 +759,46 @@ to create and provision new :ref:`RNodes<rnode-main>` from any supported hardwar
|
||||
-P, --public Display public part of signing key
|
||||
--trust-key hexbytes Public key to trust for device verification
|
||||
--version Print program version and exit
|
||||
-f, --flash Flash firmware and bootstrap EEPROM
|
||||
-r, --rom Bootstrap EEPROM without flashing firmware
|
||||
-k, --key Generate a new signing key and exit
|
||||
-S, --sign Display public part of signing key
|
||||
-H FIRMWARE_HASH, --firmware-hash FIRMWARE_HASH
|
||||
Display installed firmware hash
|
||||
--platform platform Platform specification for device bootstrap
|
||||
--product product Product specification for device bootstrap
|
||||
--model model Model code for device bootstrap
|
||||
--hwrev revision Hardware revision for device bootstrap
|
||||
|
||||
|
||||
For more information on how to create your own RNodes, please read the :ref:`Creating RNodes<rnode-creating>`
|
||||
section of this manual.
|
||||
|
||||
Remote Management
|
||||
-----------------
|
||||
|
||||
It is possible to allow remote management of Reticulum
|
||||
systems using the various built-in utilities, such as
|
||||
``rnstatus`` and ``rnpath``. To do so, you will need to set
|
||||
the ``enable_remote_management`` directive in the ``[reticulum]``
|
||||
section of the configuration file. You will also need to specify
|
||||
one or more Reticulum Identity hashes for authenticating the
|
||||
queries from client programs. For this purpose, you can use
|
||||
existing identity files, or generate new ones with the rnid utility.
|
||||
|
||||
The following is a truncated example of enabling remote management
|
||||
in the Reticulum configuration file:
|
||||
|
||||
.. code:: text
|
||||
|
||||
[reticulum]
|
||||
...
|
||||
enable_remote_management = yes
|
||||
remote_management_allowed = 9fb6d773498fb3feda407ed8ef2c3229, 2d882c5586e548d79b5af27bca1776dc
|
||||
...
|
||||
|
||||
For a complete example configuration, you can run ``rnsd --exampleconfig``.
|
||||
|
||||
Improving System Configuration
|
||||
------------------------------
|
||||
|
||||
|
||||
@@ -53,9 +53,9 @@ What does Reticulum Offer?
|
||||
|
||||
* Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
|
||||
* Reticulum uses the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
|
||||
* Reticulum uses a modified version of the `Fernet <https://github.com/fernet/spec/blob/master/Spec.md>`_ specification for on-the-wire / over-the-air encryption
|
||||
|
||||
* All keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
@@ -63,6 +63,8 @@ What does Reticulum Offer?
|
||||
|
||||
* IVs are generated through os.urandom()
|
||||
|
||||
* No Version and Timestamp metadata included
|
||||
|
||||
* Unforgeable packet delivery confirmations
|
||||
|
||||
* A variety of supported interface types
|
||||
@@ -99,7 +101,8 @@ of the types of interfaces Reticulum was designed for.
|
||||
An open-source LoRa-based interface called `RNode <https://unsigned.io/rnode>`_
|
||||
has been designed as an example transceiver that is very suitable for
|
||||
Reticulum. It is possible to build it yourself, to transform a common LoRa
|
||||
development board into one, or it can be purchased as a complete transceiver.
|
||||
development board into one, or it can be purchased as a complete transceiver
|
||||
from various vendors.
|
||||
|
||||
Reticulum can also be encapsulated over existing IP networks, so there's
|
||||
nothing stopping you from using it over wired Ethernet or your local WiFi
|
||||
|
||||