mirror of
https://github.com/markqvist/Reticulum.git
synced 2026-06-22 12:03:04 -07:00
Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a2878f1722 | |||
| 748a7290a9 | |||
| 6e80a553c8 | |||
| ec7aa44a17 | |||
| 4fa335639c | |||
| 67195c0b14 | |||
| ad1e6a41ee | |||
| a56d93fc1e | |||
| b8aa6a3e44 | |||
| 1709cd929a | |||
| 4f4961257c | |||
| 1b48f43a0d | |||
| e5d446a54e | |||
| 0af768e742 | |||
| 1a7d20a8d6 | |||
| ec4f4d5a83 | |||
| 8cefa4b2a9 | |||
| 2331f1ea3e | |||
| be7dafa30c | |||
| 3e20cb1b67 | |||
| 097e136662 | |||
| e3a716224d | |||
| 80dc567a53 | |||
| c6576d6504 | |||
| 89d5d9517d | |||
| dc315653c0 | |||
| 746b403890 | |||
| fc619460f0 | |||
| cd0f82d9ad | |||
| 330c2aacac | |||
| 63da084bbe | |||
| cbbd8221ee | |||
| 1d18d53052 | |||
| ceccf3153b | |||
| bde33e7d84 | |||
| 93330d96a0 | |||
| d93ce62878 | |||
| eafa4aefbb | |||
| 53df2fa5e0 | |||
| abc657806d | |||
| a0f219f7f4 | |||
| 47eba03a4b | |||
| 3289cd1299 | |||
| ab5fcd7a5b | |||
| 45494f21aa | |||
| 5d677d2fb7 | |||
| 808082e300 | |||
| 97cfdfd023 | |||
| 9b15cf2295 | |||
| eaa68c2d04 | |||
| ac5ca78c77 | |||
| 5b17dbdfd6 | |||
| d4ed20c7d5 | |||
| a5093ea8f0 | |||
| f5cf438abd | |||
| bf6e73e163 | |||
| 503f475ca5 | |||
| 8506118aee | |||
| dfa295a90a | |||
| 3ace1583da | |||
| c62b66195d | |||
| b724836d2b | |||
| 1e1b9dc79e | |||
| c668a51e39 | |||
| 09b34d34c6 | |||
| 54e18e41c5 | |||
| 5550bca040 | |||
| f7a02351d4 | |||
| 3125b99043 | |||
| 158765abb7 | |||
| 81aa9ac5b6 | |||
| 55f5842587 | |||
| 38dd63a99a | |||
| 558cd6c4a7 | |||
| 15e6a1bfde | |||
| c1087e62fd | |||
| 9d924dcd6d | |||
| 163d2ed157 | |||
| 68f07ddd38 | |||
| d956b93c13 | |||
| 3036305662 | |||
| ee603ce68e | |||
| 989513cb46 | |||
| 7e52c37580 | |||
| 0984f92fa2 | |||
| 2ab2d8e9df | |||
| b828e0e858 | |||
| d4dd706bba | |||
| ed30fa3e0a | |||
| 5e2b3df623 | |||
| ae7dffdfc0 | |||
| 32b5c7a3af | |||
| 8b08658b7f | |||
| ee79c3a732 | |||
| 0e5f4aa08a | |||
| ec0407e5c8 | |||
| db1380c413 | |||
| 7e3979dac0 | |||
| c1b6bde4a7 | |||
| 8df89cc2d0 | |||
| 19adadf4cf | |||
| c30feb3fc2 | |||
| 4c81589d5b | |||
| c014357e24 | |||
| ec41dc1a03 | |||
| 463dfa6fb4 | |||
| 0354b5969d | |||
| fc225bd55d | |||
| 67562126fc | |||
| 9319d613f5 | |||
| 014994a788 | |||
| 0f8efe3de1 | |||
| 274a8ca76a | |||
| ea3ad6b287 | |||
| f095b9cb8e | |||
| 6f8d3e882a | |||
| aabb763cea | |||
| 04d2626809 | |||
| 823bfd537c | |||
| 434ebd2954 | |||
| 44782c3429 | |||
| 890846fa8d | |||
| 36c761e8dd | |||
| 4a4b625075 | |||
| 4223203134 | |||
| e6966fe19a | |||
| e81c22cf53 | |||
| c02e59e3ab | |||
| 5d5abf352b | |||
| ec9bb33d16 | |||
| f3e836cec8 | |||
| 8a50528111 | |||
| 9523595282 | |||
| a762af035a | |||
| 760ab981d0 | |||
| 7b43ff0cef | |||
| 996161e2f4 | |||
| bf633bba5d | |||
| 8337a5945d | |||
| a736b3adfc | |||
| 25127cd3c9 | |||
| ebf084cff0 | |||
| cd8fe95d91 | |||
| e2efc61208 | |||
| 5de63d5bf2 | |||
| c9d744f88a | |||
| 18e0dbddfa | |||
| 52c816cb27 | |||
| 582d2b91f5 | |||
| 28a0dbb0e0 | |||
| 2895806541 | |||
| 5b8de73143 | |||
| 212af2f43b | |||
| 1282061701 | |||
| 49dba483a9 | |||
| ebec63487f | |||
| 9373819234 | |||
| 04925d8004 | |||
| 4284084fef | |||
| 63ad2afe3f | |||
| 61712d322a | |||
| 3599066356 | |||
| 18c2a38b97 | |||
| f55004a574 | |||
| 1768ddc459 | |||
| d002a75f34 | |||
| 0b6d239551 | |||
| 926b811a84 | |||
| 2bc8e11ad5 | |||
| f5412f5c0b | |||
| 5470f752b4 | |||
| 48c006a94c | |||
| 8445417661 | |||
| 30248854ed | |||
| f34bc75588 | |||
| 3b23e2f37d | |||
| 7417cf5947 | |||
| 60d8da843c | |||
| f9667fd684 | |||
| d9269c6047 | |||
| 6521f839cd | |||
| d63bbcdc0a | |||
| c36c7186de | |||
| 6fec76205c | |||
| 715f4d9fcb | |||
| 8d7857c4e2 | |||
| c9a2b45368 | |||
| c57d927660 | |||
| 8d98c8751a | |||
| 527f6cc906 | |||
| a0d61f6441 |
@@ -15,7 +15,11 @@ Before creating a bug report on this issue tracker, you **must** read the [Contr
|
||||
- After reading the [Contribution Guidelines](https://github.com/markqvist/Reticulum/blob/master/Contributing.md), delete this section from your bug report.
|
||||
|
||||
**Describe the Bug**
|
||||
A clear and concise description of what the bug is.
|
||||
First of all: Is this really a bug? Is it reproducible?
|
||||
|
||||
If this is a request for help because something is not working as you expected, stop right here, and go to the [discussions](https://github.com/markqvist/Reticulum/discussions) instead, where you can post your questions and get help from other users.
|
||||
|
||||
If this really is a bug or issue with the software, remove this section of the template, and provide **a clear and concise description of what the bug is**.
|
||||
|
||||
**To Reproduce**
|
||||
Describe in detail how to reproduce the bug.
|
||||
@@ -24,7 +28,7 @@ Describe in detail how to reproduce the bug.
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Logs & Screenshots**
|
||||
Please include any relevant log output. If applicable, also add screenshots to help explain your problem.
|
||||
Please include any relevant log output. If applicable, also add screenshots to help explain your problem. In most cases, without any relevant log output, we will not be able to determine the cause of the bug, or reproduce it.
|
||||
|
||||
**System Information**
|
||||
- OS and version
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
name: Build Reticulum
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
tags:
|
||||
- "[0-9]+.[0-9]+.[0-9]+*"
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- .gitignore
|
||||
- LICENSE
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: make test
|
||||
|
||||
package:
|
||||
needs: test
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ contains(github.ref, '-') && 'development' || 'production' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: |
|
||||
python -m pip install -q build wheel setuptools
|
||||
make remove_symlinks
|
||||
make build_wheel
|
||||
make build_pure_wheel
|
||||
make create_symlinks
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: package
|
||||
path: dist/*.whl
|
||||
|
||||
# documentation:
|
||||
# needs: test
|
||||
# if: startsWith(github.ref, 'refs/tags/')
|
||||
# runs-on: ubuntu-latest
|
||||
# environment: ${{ contains(github.ref, '-') && 'development' || 'production' }}
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
# - uses: actions/setup-python@v5
|
||||
# with:
|
||||
# python-version: 3.x
|
||||
# - run: |
|
||||
# sudo apt-get -qq update && sudo apt-get -qq install latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended
|
||||
# python -m pip -q install sphinx sphinx-copybutton
|
||||
# cd docs && make latexpdf && make epub
|
||||
# - uses: actions/upload-artifact@v4
|
||||
# with:
|
||||
# name: documentation
|
||||
# path: |
|
||||
# docs/build/latex/*.pdf
|
||||
# docs/build/epub/*.epub
|
||||
|
||||
release:
|
||||
needs: [package]
|
||||
if: startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
environment: ${{ contains(github.ref, '-') && 'development' || 'production' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: .artifacts
|
||||
- uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
# .artifacts/package/**.whl
|
||||
# .artifacts/documentation/latex/reticulumnetworkstack.pdf
|
||||
# .artifacts/documentation/epub/ReticulumNetworkStack.epub
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
prerelease: ${{ contains(github.ref, '-') }}
|
||||
fail_on_unmatched_files: true
|
||||
@@ -1,28 +0,0 @@
|
||||
# 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
|
||||
+164
@@ -1,3 +1,167 @@
|
||||
### 2025-01-17: RNS β 0.9.0
|
||||
|
||||
This release lays the groundwork for future performance and resource utilisation optimisations. Most importantly, this release adds **link MTU autodiscovery**, which allow established links to use much higher MTUs than the base MTU of 500 bytes.
|
||||
|
||||
**Please note!** To actually use link MTU discovery, all transport nodes along the path must be upgraded to at least version `0.9.0`. Since this is the first release to add support for this feature, *it is currently **not** activated by default*, and no clients or applications will use it yet. Using link MTU autodiscovery by default will be enabled by default in RNS version `0.9.1`. Please upgrade your nodes!
|
||||
|
||||
Additionally, this release adds several new features, performance improvements and bug fixes, as well as support for RNodes running firmware version `1.81`.
|
||||
|
||||
**Changes**
|
||||
- Added MTU autoconfiguration on interfaces that support higher MTUs
|
||||
- Added link MTU autodiscovery and path clamping
|
||||
- Added dynamic SDU calculations based on link MTU to `Resource`, `Channel` and `Buffer`
|
||||
- Added resource EIFR continuity to split resource handling
|
||||
- Added interference status to `RNodeInterface`
|
||||
- Fixed a display bug in `rnstatus`
|
||||
- Added live traffic stats to `rnstatus`
|
||||
- Added T3S3 support to `rnodeconf`
|
||||
- Added Heltec T114 support to `rnodeconf`
|
||||
- Added LilyGO T-Echo support to `rnodeconf`
|
||||
- Added option to print device configuration to `rnodeconf`
|
||||
- Improved CPU utilisation and memory consumption
|
||||
- Improved `rnsd` restart time on systems with many interfaces
|
||||
- Improved `rncp` status output
|
||||
- Improved packet filter performance
|
||||
- Improved interface detachment handling
|
||||
- Improved resource transfer timing and performance
|
||||
- Improved Transport core efficiency
|
||||
- Improved reliability of ratchet reloads if I/O conflicts occur
|
||||
- Improved logging
|
||||
- Improved built-in profiler
|
||||
- Fixed a potential deadlock in logging
|
||||
- Fixed time formatters not handling negative times
|
||||
- Updated example code
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
1ee60634cf0627c45b93f4e6c9adaf1fcdf9c1a8dfd4dd3dcd499e029554ab4f rns-0.9.0-py3-none-any.whl
|
||||
b67eec583fdb224ba8174b317e66b8f7344e338e93760ed1a90f0bafea8cf09e rnspure-0.9.0-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2025-01-09: RNS β 0.8.9
|
||||
|
||||
This maintenance release adds a number of configuration options to `rnodeconf`.
|
||||
|
||||
**Changes**
|
||||
- Added noise floor output to `rnstatus` for supported interfaces
|
||||
- Added channel noise floor and CSMA parameter reporting to `RNodeInterface`
|
||||
- Added ability to set display rotation in `rnodeconf`
|
||||
- Added ability to configure interference avoidance to `rnodeconf`
|
||||
- Fixed missing console image install on Heltec V3 in `rnodeconf`
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
b54fe8bc296f83a3a70569c9d1e9db3096249789c18f8d0217671479fa6881a1 rns-0.8.9-py3-none-any.whl
|
||||
52fd992e5f9478d5a1f61f8f37dc0ee2d268fdd0b8a4e6656d33d632490afc5a rnspure-0.8.9-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-12-11: RNS β 0.8.8
|
||||
|
||||
This maintenance release adds a single API function and fixes a bug.
|
||||
|
||||
**Changes**
|
||||
- Allow announce handlers to receive announce packet hash
|
||||
- Fix packet RSSI/SNR/Q cache not being available on standalone instances
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
9c1755a81049c67b051ecb9fe4b2c5f7d98bf09d20ed52d6ce6a410298b0527b rns-0.8.8-py3-none-any.whl
|
||||
d8871d69cde4b0a0b99b383f324d651dc77a2f44ec9641be828902c778a8d128 rnspure-0.8.8-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-12-09: RNS β 0.8.7
|
||||
|
||||
This maintenance release adds support for OpenWRT packaging, and brings several minor improvements and bugfixes.
|
||||
|
||||
Thanks to @gretel and @jacobeva, who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Added support for packaging RNS to OpenWRT
|
||||
- Added ability to run `rnstatus` as application-local imported module
|
||||
- Added ability to reflect RNS log output to app-internal log handler callback
|
||||
- Added display read functionality to `RNodeInterface`
|
||||
- Fixed a regression in `RNodeMultiInterface` caused by earlier refactoring
|
||||
- Imrpoved documentation
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
e76ba8feeeae2c8df27e9906deebd7c721f0f0e887ad3fbd26df0212d6ce907a rns-0.8.7-py3-none-any.whl
|
||||
046608539bc235d52c970c7f3c54e7aa01a86016ae00263f8a55fc796b6939f5 rnspure-0.8.7-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-11-24: RNS β 0.8.6
|
||||
|
||||
This release adds full interface modularity and custom interface loading to RNS. Users can now easily create and use their own custom interfaces for communicating over practically anything. Support for IPv6 has also been added to the TCP-based interfaces.
|
||||
|
||||
In addition, several bugs have been fixed, and various internal improvements to code consistency and naming conventions have been carried out.
|
||||
|
||||
Thanks to @gretel and @deavmi, who contributed to this release!
|
||||
|
||||
**Changes**
|
||||
- Added ability to load and configure custom, user-supplied interfaces
|
||||
- Added IPv6 support to `TCPClientInterface` and `TCPServerInterface`
|
||||
- Added an init option to the API for requiring an existing shared instance
|
||||
- Changed `rnstatus` behaviour to only show status if Reticulum is already running
|
||||
- Fixed `KISSInterface` beacon length for compatibility with software modems
|
||||
- Fixed interface client count sometimes reporting incorrect values on TCP and I2P interfaces
|
||||
- Refactored and improved interface initialisation and configuration handling
|
||||
- Refactored interface code to be more consistent
|
||||
- Refactored various deprecated references and names
|
||||
- Updated documentation and manual
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
60be127f003cd7838149bf8f01020206f829a7bd192706a608e39d8d7193d07b rns-0.8.6-py3-none-any.whl
|
||||
d8701e19279d292b5b8af9da7c67b6ac88a992ca65109f8182c3e5c761a9ebeb rnspure-0.8.6-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-10-20: RNS β 0.8.5
|
||||
|
||||
This maintenance release fixes a number of bugs. Thanks to @faragher for contributing to this release!
|
||||
|
||||
**Changes**
|
||||
- Fixed missing close of file handles
|
||||
- Fixed invalid values returned from `get_snr()` and `get_q()` physical layer stats API functions
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
1757e809e083585bf4c23b6fe0f29954e5a1586ce14081099e38e606a75831df rns-0.8.5-py3-none-any.whl
|
||||
44254630634f4dbb1ce3242247fe8180379d27bff15d183263b1856fd662f88d rnspure-0.8.5-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 2024-10-11: RNS β 0.8.4
|
||||
|
||||
This release fixes a number of bugs and improves reliability of automatic reconnection when BLE-connected RNodes unexpectedly disappear or lose connection.
|
||||
|
||||
**Changes**
|
||||
- Improved RNode BLE reconnection realiability
|
||||
- Added RNode battery state to `rnstatus` output
|
||||
- Fixed resource transfer hanging for a long time over slow links if proof packet is lost
|
||||
- Fixed missing import on Android
|
||||
|
||||
**Release Hashes**
|
||||
```
|
||||
d3f7a9fddc6c1e59b1e4895756fe602408ac6ef09de377ee65ec62d09fff97a3 rns-0.8.4-py3-none-any.whl
|
||||
eb3843bcab1428be0adb097988991229a4c03156ab40cc9c6e2d9c590d8b850b rnspure-0.8.4-py3-none-any.whl
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
# MIT License - Copyright (c) 2024 Mark Qvist / unsigned.io
|
||||
|
||||
# This example illustrates creating a custom interface
|
||||
# definition, that can be loaded and used by Reticulum at
|
||||
# runtime. Any number of custom interfaces can be created
|
||||
# and loaded. To use the interface place it in the folder
|
||||
# ~/.reticulum/interfaces, and add an interface entry to
|
||||
# your Reticulum configuration file similar to this:
|
||||
|
||||
# [[Example Custom Interface]]
|
||||
# type = ExampleInterface
|
||||
# enabled = no
|
||||
# mode = gateway
|
||||
# port = /dev/ttyUSB0
|
||||
# speed = 115200
|
||||
# databits = 8
|
||||
# parity = none
|
||||
# stopbits = 1
|
||||
|
||||
from time import sleep
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
# This HDLC helper class is used by the interface
|
||||
# to delimit and packetize data over the physical
|
||||
# medium - in this case a serial connection.
|
||||
class HDLC():
|
||||
# This example interface packetizes data using
|
||||
# simplified HDLC framing, similar to PPP
|
||||
FLAG = 0x7E
|
||||
ESC = 0x7D
|
||||
ESC_MASK = 0x20
|
||||
|
||||
@staticmethod
|
||||
def escape(data):
|
||||
data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
|
||||
data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
|
||||
return data
|
||||
|
||||
# Let's define our custom interface class. It must
|
||||
# be a sub-class of the RNS "Interface" class.
|
||||
class ExampleInterface(Interface):
|
||||
# All interface classes must define a default
|
||||
# IFAC size, used in IFAC setup when the user
|
||||
# has not specified a custom IFAC size. This
|
||||
# option is specified in bytes.
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
# The following properties are local to this
|
||||
# particular interface implementation.
|
||||
owner = None
|
||||
port = None
|
||||
speed = None
|
||||
databits = None
|
||||
parity = None
|
||||
stopbits = None
|
||||
serial = None
|
||||
|
||||
# All Reticulum interfaces must have an __init__
|
||||
# method that takes 2 positional arguments:
|
||||
# The owner RNS Transport instance, and a dict
|
||||
# of configuration values.
|
||||
def __init__(self, owner, configuration):
|
||||
|
||||
# The following lines demonstrate handling
|
||||
# potential dependencies required for the
|
||||
# interface to function correctly.
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
import serial
|
||||
else:
|
||||
RNS.log("Using this interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
|
||||
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
|
||||
RNS.panic()
|
||||
|
||||
# We start out by initialising the super-class
|
||||
super().__init__()
|
||||
|
||||
# To make sure the configuration data is in the
|
||||
# correct format, we parse it through the following
|
||||
# method on the generic Interface class. This step
|
||||
# is required to ensure compatibility on all the
|
||||
# platforms that Reticulum supports.
|
||||
ifconf = Interface.get_config_obj(configuration)
|
||||
|
||||
# Read the interface name from the configuration
|
||||
# and set it on our interface instance.
|
||||
name = ifconf["name"]
|
||||
self.name = name
|
||||
|
||||
# We read configuration parameters from the supplied
|
||||
# configuration data, and provide default values in
|
||||
# case any are missing.
|
||||
port = ifconf["port"] if "port" in ifconf else None
|
||||
speed = int(ifconf["speed"]) if "speed" in ifconf else 9600
|
||||
databits = int(ifconf["databits"]) if "databits" in ifconf else 8
|
||||
parity = ifconf["parity"] if "parity" in ifconf else "N"
|
||||
stopbits = int(ifconf["stopbits"]) if "stopbits" in ifconf else 1
|
||||
|
||||
# In case no port is specified, we abort setup by
|
||||
# raising an exception.
|
||||
if port == None:
|
||||
raise ValueError(f"No port specified for {self}")
|
||||
|
||||
# All interfaces must supply a hardware MTU value
|
||||
# to the RNS Transport instance. This value should
|
||||
# be the maximum data packet payload size that the
|
||||
# underlying medium is capable of handling in all
|
||||
# cases without any segmentation.
|
||||
self.HW_MTU = 564
|
||||
|
||||
# We initially set the "online" property to false,
|
||||
# since the interface has not actually been fully
|
||||
# initialised and connected yet.
|
||||
self.online = False
|
||||
|
||||
# In this case, we can also set the indicated bit-
|
||||
# rate of the interface to the serial port speed.
|
||||
self.bitrate = speed
|
||||
|
||||
# Configure internal properties on the interface
|
||||
# according to the supplied configuration.
|
||||
self.pyserial = serial
|
||||
self.serial = None
|
||||
self.owner = owner
|
||||
self.port = port
|
||||
self.speed = speed
|
||||
self.databits = databits
|
||||
self.parity = serial.PARITY_NONE
|
||||
self.stopbits = stopbits
|
||||
self.timeout = 100
|
||||
|
||||
if parity.lower() == "e" or parity.lower() == "even":
|
||||
self.parity = serial.PARITY_EVEN
|
||||
|
||||
if parity.lower() == "o" or parity.lower() == "odd":
|
||||
self.parity = serial.PARITY_ODD
|
||||
|
||||
# Since all required parameters are now configured,
|
||||
# we will try opening the serial port.
|
||||
try:
|
||||
self.open_port()
|
||||
except Exception as e:
|
||||
RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
# If opening the port succeeded, run any post-open
|
||||
# configuration required.
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
else:
|
||||
raise IOError("Could not open serial port")
|
||||
|
||||
# Open the serial port with supplied configuration
|
||||
# parameters and store a reference to the open port.
|
||||
def open_port(self):
|
||||
RNS.log("Opening serial port "+self.port+"...", RNS.LOG_VERBOSE)
|
||||
self.serial = self.pyserial.Serial(
|
||||
port = self.port,
|
||||
baudrate = self.speed,
|
||||
bytesize = self.databits,
|
||||
parity = self.parity,
|
||||
stopbits = self.stopbits,
|
||||
xonxoff = False,
|
||||
rtscts = False,
|
||||
timeout = 0,
|
||||
inter_byte_timeout = None,
|
||||
write_timeout = None,
|
||||
dsrdtr = False,
|
||||
)
|
||||
|
||||
# The only thing required after opening the port
|
||||
# is to wait a small amount of time for the
|
||||
# hardware to initialise and then start a thread
|
||||
# that reads any incoming data from the device.
|
||||
def configure_device(self):
|
||||
sleep(0.5)
|
||||
thread = threading.Thread(target=self.read_loop)
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
self.online = True
|
||||
RNS.log("Serial port "+self.port+" is now open", RNS.LOG_VERBOSE)
|
||||
|
||||
|
||||
# This method will be called from our read-loop
|
||||
# whenever a full packet has been received over
|
||||
# the underlying medium.
|
||||
def process_incoming(self, data):
|
||||
# Update our received bytes counter
|
||||
self.rxb += len(data)
|
||||
|
||||
# And send the data packet to the Transport
|
||||
# instance for processing.
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
# The running Reticulum Transport instance will
|
||||
# call this method on the interface whenever the
|
||||
# interface must transmit a packet.
|
||||
def process_outgoing(self,data):
|
||||
if self.online:
|
||||
# First, escape and packetize the data
|
||||
# according to HDLC framing.
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
|
||||
# Then write the framed data to the port
|
||||
written = self.serial.write(data)
|
||||
|
||||
# Update the transmitted bytes counter
|
||||
# and ensure that all data was written
|
||||
self.txb += len(data)
|
||||
if written != len(data):
|
||||
raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
|
||||
|
||||
# This read loop runs in a thread and continously
|
||||
# receives bytes from the underlying serial port.
|
||||
# When a full packet has been received, it will
|
||||
# be sent to the process_incoming methed, which
|
||||
# will in turn pass it to the Transport instance.
|
||||
def read_loop(self):
|
||||
try:
|
||||
in_frame = False
|
||||
escape = False
|
||||
data_buffer = b""
|
||||
last_read_ms = int(time.time()*1000)
|
||||
|
||||
while self.serial.is_open:
|
||||
if self.serial.in_waiting:
|
||||
byte = ord(self.serial.read(1))
|
||||
last_read_ms = int(time.time()*1000)
|
||||
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < self.HW_MTU):
|
||||
if (byte == HDLC.ESC):
|
||||
escape = True
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.FLAG
|
||||
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.ESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
|
||||
else:
|
||||
time_since_last = int(time.time()*1000) - last_read_ms
|
||||
if len(data_buffer) > 0 and time_since_last > self.timeout:
|
||||
data_buffer = b""
|
||||
in_frame = False
|
||||
escape = False
|
||||
sleep(0.08)
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is now offline.", RNS.LOG_ERROR)
|
||||
|
||||
if RNS.Reticulum.panic_on_interface_error:
|
||||
RNS.panic()
|
||||
|
||||
RNS.log("Reticulum will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
|
||||
|
||||
self.online = False
|
||||
self.serial.close()
|
||||
self.reconnect_port()
|
||||
|
||||
# This method handles serial port disconnects.
|
||||
def reconnect_port(self):
|
||||
while not self.online:
|
||||
try:
|
||||
time.sleep(5)
|
||||
RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
self.open_port()
|
||||
if self.serial.is_open:
|
||||
self.configure_device()
|
||||
except Exception as e:
|
||||
RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
RNS.log("Reconnected serial port for "+str(self))
|
||||
|
||||
# Signal to Reticulum that this interface should
|
||||
# not perform any ingress limiting.
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
# We must provide a string representation of this
|
||||
# interface, that is used whenever the interface
|
||||
# is printed in logs or external programs.
|
||||
def __str__(self):
|
||||
return "ExampleInterface["+self.name+"]"
|
||||
|
||||
# Finally, register the defined interface class as the
|
||||
# target class for Reticulum to use as an interface
|
||||
interface_class = ExampleInterface
|
||||
@@ -25,9 +25,6 @@ def server(configpath):
|
||||
|
||||
# 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()
|
||||
|
||||
@@ -246,8 +246,8 @@ def link_established(link):
|
||||
|
||||
# Inform the user that the server is
|
||||
# connected
|
||||
RNS.log("Link established with server,sending...")
|
||||
rd = os.urandom(RNS.Link.MDU)
|
||||
RNS.log("Link established with server, sending...")
|
||||
rd = os.urandom(link.mdu)
|
||||
started = time.time()
|
||||
while link.status == RNS.Link.ACTIVE and data_sent < data_cap*1.25:
|
||||
RNS.Packet(server_link, rd, create_receipt=False).send()
|
||||
|
||||
@@ -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" 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>
|
||||
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/build.yml"><img align="right" src="https://github.com/markqvist/Reticulum/actions/workflows/build.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>
|
||||
@@ -42,6 +42,9 @@ 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 over heterogeneous carriers
|
||||
- Flexible scalability over heterogeneous topologies
|
||||
- Reticulum can carry data over any mixture of physical mediums and topologies
|
||||
- Low-bandwidth networks can co-exist and interoperate with large, high-bandwidth networks
|
||||
- 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
|
||||
@@ -53,8 +56,14 @@ For more info, see [reticulum.network](https://reticulum.network/) and [the FAQ
|
||||
- HMAC using SHA256 for authentication
|
||||
- IVs are generated through os.urandom()
|
||||
- Unforgeable packet delivery confirmations
|
||||
- A large variety of supported interface types
|
||||
- Flexible and extensible interface system
|
||||
- Reticulum includes a large variety of built-in interface types
|
||||
- Ability to load and utilise custom user- or community-supplied interface types
|
||||
- Easily create your own custom interfaces for communicating over anything
|
||||
- Authentication and virtual network segmentation on all supported interface types
|
||||
- An intuitive and easy-to-use API
|
||||
- Simpler and easier to use than sockets APIs and simpler, but more powerful
|
||||
- Makes building distributed and decentralised applications much simpler
|
||||
- Reliable and efficient transfer of arbitrary amounts of data
|
||||
- Reticulum can handle a few bytes of data or files of many gigabytes
|
||||
- Sequencing, compression, transfer coordination and checksumming are automatic
|
||||
@@ -172,11 +181,12 @@ program.
|
||||
|
||||
Reticulum implements a range of generalised interface types that covers most of
|
||||
the communications hardware that Reticulum can run over. If your hardware is
|
||||
not supported, it's relatively simple to implement an interface class. I will
|
||||
gratefully accept pull requests for custom interfaces if they are generally
|
||||
useful.
|
||||
not supported, it's [simple to implement a custom interface module](https://markqvist.github.io/Reticulum/manual/interfaces.html#custom-interfaces).
|
||||
|
||||
Currently, the following interfaces are supported:
|
||||
Pull requests for custom interfaces are gratefully accepted, provided they are
|
||||
generally useful and well-tested in real-world usage.
|
||||
|
||||
Currently, the following built-in interfaces are supported:
|
||||
|
||||
- Any Ethernet device
|
||||
- LoRa using [RNode](https://unsigned.io/rnode/)
|
||||
@@ -322,12 +332,12 @@ In the default installation configuration, the `X25519`, `Ed25519` and
|
||||
(via the [PyCA/cryptography](https://github.com/pyca/cryptography) package).
|
||||
The hashing functions `SHA-256` and `SHA-512` are provided by the standard
|
||||
Python [hashlib](https://docs.python.org/3/library/hashlib.html). The `HKDF`,
|
||||
`HMAC`, `Fernet` primitives, and the `PKCS7` padding function are always
|
||||
`HMAC`, `Token` primitives, and the `PKCS7` padding function are always
|
||||
provided by the following internal implementations:
|
||||
|
||||
- [HKDF.py](RNS/Cryptography/HKDF.py)
|
||||
- [HMAC.py](RNS/Cryptography/HMAC.py)
|
||||
- [Fernet.py](RNS/Cryptography/Fernet.py)
|
||||
- [Token.py](RNS/Cryptography/Token.py)
|
||||
- [PKCS7.py](RNS/Cryptography/PKCS7.py)
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -45,7 +45,8 @@ class StreamDataMessage(MessageBase):
|
||||
The stream id is limited to 2 bytes - 2 bit
|
||||
"""
|
||||
|
||||
MAX_DATA_LEN = RNS.Link.MDU - 2 - 6 # 2 for stream data message header, 6 for channel envelope
|
||||
OVERHEAD = 2 + 6 # 2 for stream data message header, 6 for channel envelope
|
||||
MAX_DATA_LEN = RNS.Link.MDU - OVERHEAD
|
||||
"""
|
||||
When the Buffer package is imported, this value is
|
||||
calculcated based on the value of OVERHEAD
|
||||
@@ -215,6 +216,7 @@ class RawChannelWriter(RawIOBase, AbstractContextManager):
|
||||
self._stream_id = stream_id
|
||||
self._channel = channel
|
||||
self._eof = False
|
||||
self._mdu = channel.mdu - StreamDataMessage.OVERHEAD
|
||||
|
||||
def write(self, __b: bytes) -> int | None:
|
||||
try:
|
||||
|
||||
+6
-3
@@ -602,7 +602,7 @@ class Channel(contextlib.AbstractContextManager):
|
||||
return envelope
|
||||
|
||||
@property
|
||||
def MDU(self):
|
||||
def mdu(self):
|
||||
"""
|
||||
Maximum Data Unit: the number of bytes available
|
||||
for a message to consume in a single send. This
|
||||
@@ -611,7 +611,10 @@ class Channel(contextlib.AbstractContextManager):
|
||||
|
||||
:return: number of bytes available
|
||||
"""
|
||||
return self._outlet.mdu - 6 # sizeof(msgtype) + sizeof(length) + sizeof(sequence)
|
||||
mdu = self._outlet.mdu - 6 # sizeof(msgtype) + sizeof(length) + sizeof(sequence)
|
||||
if mdu > 0xFFFF:
|
||||
mdu = 0xFFFF
|
||||
return mdu
|
||||
|
||||
|
||||
class LinkChannelOutlet(ChannelOutletBase):
|
||||
@@ -639,7 +642,7 @@ class LinkChannelOutlet(ChannelOutletBase):
|
||||
|
||||
@property
|
||||
def mdu(self):
|
||||
return self.link.MDU
|
||||
return self.link.mdu
|
||||
|
||||
@property
|
||||
def rtt(self):
|
||||
|
||||
@@ -48,7 +48,7 @@ def hkdf(length=None, derive_from=None, salt=None, context=None):
|
||||
derived = b""
|
||||
|
||||
for i in range(ceil(length / hash_len)):
|
||||
block = hmac_sha256(pseudorandom_key, block + context + bytes([i + 1]))
|
||||
block = hmac_sha256(pseudorandom_key, block + context + bytes([(i + 1)%(0xFF+1)]))
|
||||
derived += block
|
||||
|
||||
return derived[:length]
|
||||
|
||||
@@ -27,7 +27,7 @@ from RNS.Cryptography import HMAC
|
||||
from RNS.Cryptography import PKCS7
|
||||
from RNS.Cryptography.AES import AES_128_CBC
|
||||
|
||||
class Fernet():
|
||||
class Token():
|
||||
"""
|
||||
This class provides a slightly modified implementation of the Fernet spec
|
||||
found at: https://github.com/fernet/spec/blob/master/Spec.md
|
||||
@@ -37,7 +37,7 @@ class Fernet():
|
||||
not relevant to Reticulum. They are therefore stripped from this
|
||||
implementation, since they incur overhead and leak initiator metadata.
|
||||
"""
|
||||
FERNET_OVERHEAD = 48 # Bytes
|
||||
TOKEN_OVERHEAD = 48 # Bytes
|
||||
|
||||
@staticmethod
|
||||
def generate_key():
|
||||
@@ -5,7 +5,7 @@ from .Hashes import sha256
|
||||
from .Hashes import sha512
|
||||
from .HKDF import hkdf
|
||||
from .PKCS7 import PKCS7
|
||||
from .Fernet import Fernet
|
||||
from .Token import Token
|
||||
from .Provider import backend
|
||||
|
||||
import RNS.Cryptography.Provider as cp
|
||||
@@ -20,5 +20,7 @@ elif cp.PROVIDER == cp.PROVIDER_PYCA:
|
||||
from RNS.Cryptography.Proxies import Ed25519PrivateKeyProxy as Ed25519PrivateKey
|
||||
from RNS.Cryptography.Proxies import Ed25519PublicKeyProxy as Ed25519PublicKey
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
+17
-5
@@ -26,7 +26,7 @@ import time
|
||||
import threading
|
||||
import RNS
|
||||
|
||||
from RNS.Cryptography import Fernet
|
||||
from RNS.Cryptography import Token
|
||||
from .vendor import umsgpack as umsgpack
|
||||
|
||||
class Callbacks:
|
||||
@@ -424,7 +424,7 @@ class Destination:
|
||||
def _reload_ratchets(self, ratchets_path):
|
||||
if os.path.isfile(ratchets_path):
|
||||
with self.ratchet_file_lock:
|
||||
try:
|
||||
def load_attempt():
|
||||
ratchets_file = open(ratchets_path, "rb")
|
||||
persisted_data = umsgpack.unpackb(ratchets_file.read())
|
||||
if "signature" in persisted_data and "ratchets" in persisted_data:
|
||||
@@ -433,10 +433,22 @@ class Destination:
|
||||
self.ratchets_path = ratchets_path
|
||||
else:
|
||||
raise KeyError("Invalid ratchet file signature")
|
||||
|
||||
try:
|
||||
try:
|
||||
load_attempt()
|
||||
|
||||
except Exception as e:
|
||||
RNS.trace_exception(e)
|
||||
RNS.log(f"First ratchet reload attempt for {self} failed. Possible I/O conflict. Retrying in 500ms.", RNS.LOG_ERROR)
|
||||
time.sleep(0.5)
|
||||
load_attempt()
|
||||
RNS.log(f"Ratchet reload retry succeeded", RNS.LOG_DEBUG)
|
||||
|
||||
except Exception as e:
|
||||
self.ratchets = None
|
||||
self.ratchets_path = None
|
||||
RNS.trace_exception(e)
|
||||
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)
|
||||
@@ -525,8 +537,8 @@ class Destination:
|
||||
raise TypeError("A single destination holds keys through an Identity instance")
|
||||
|
||||
if self.type == Destination.GROUP:
|
||||
self.prv_bytes = Fernet.generate_key()
|
||||
self.prv = Fernet(self.prv_bytes)
|
||||
self.prv_bytes = Token.generate_key()
|
||||
self.prv = Token(self.prv_bytes)
|
||||
|
||||
def get_private_key(self):
|
||||
"""
|
||||
@@ -556,7 +568,7 @@ class Destination:
|
||||
|
||||
if self.type == Destination.GROUP:
|
||||
self.prv_bytes = key
|
||||
self.prv = Fernet(self.prv_bytes)
|
||||
self.prv = Token(self.prv_bytes)
|
||||
|
||||
def load_public_key(self, key):
|
||||
if self.type != Destination.SINGLE:
|
||||
|
||||
+45
-43
@@ -31,7 +31,7 @@ import threading
|
||||
from .vendor import umsgpack as umsgpack
|
||||
|
||||
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
|
||||
from RNS.Cryptography import Fernet
|
||||
from RNS.Cryptography import Token
|
||||
|
||||
|
||||
class Identity:
|
||||
@@ -66,7 +66,7 @@ class Identity:
|
||||
"""
|
||||
|
||||
# Non-configurable constants
|
||||
FERNET_OVERHEAD = RNS.Cryptography.Fernet.FERNET_OVERHEAD
|
||||
TOKEN_OVERHEAD = RNS.Cryptography.Token.TOKEN_OVERHEAD
|
||||
AES128_BLOCKSIZE = 16 # In bytes
|
||||
HASHLENGTH = 256 # In bits
|
||||
SIGLENGTH = KEYSIZE # In bits
|
||||
@@ -155,9 +155,9 @@ class Identity:
|
||||
storage_known_destinations = {}
|
||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||
try:
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
|
||||
storage_known_destinations = umsgpack.load(file)
|
||||
file.close()
|
||||
with open(RNS.Reticulum.storagepath+"/known_destinations","rb") as file:
|
||||
storage_known_destinations = umsgpack.load(file)
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -169,9 +169,9 @@ class Identity:
|
||||
RNS.log("Skipped recombining known destinations from disk, since an error occurred: "+str(e), RNS.LOG_WARNING)
|
||||
|
||||
RNS.log("Saving "+str(len(Identity.known_destinations))+" known destinations to storage...", RNS.LOG_DEBUG)
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","wb")
|
||||
umsgpack.dump(Identity.known_destinations, file)
|
||||
file.close()
|
||||
with open(RNS.Reticulum.storagepath+"/known_destinations","wb") as file:
|
||||
umsgpack.dump(Identity.known_destinations, file)
|
||||
|
||||
|
||||
save_time = time.time() - save_start
|
||||
if save_time < 1:
|
||||
@@ -191,9 +191,8 @@ class Identity:
|
||||
def load_known_destinations():
|
||||
if os.path.isfile(RNS.Reticulum.storagepath+"/known_destinations"):
|
||||
try:
|
||||
file = open(RNS.Reticulum.storagepath+"/known_destinations","rb")
|
||||
loaded_known_destinations = umsgpack.load(file)
|
||||
file.close()
|
||||
with open(RNS.Reticulum.storagepath+"/known_destinations","rb") as file:
|
||||
loaded_known_destinations = umsgpack.load(file)
|
||||
|
||||
Identity.known_destinations = {}
|
||||
for known_destination in loaded_known_destinations:
|
||||
@@ -267,31 +266,34 @@ class Identity:
|
||||
|
||||
@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 destination_hash in Identity.known_ratchets and Identity.known_ratchets[destination_hash] == ratchet:
|
||||
ratchet_exists = True
|
||||
else:
|
||||
ratchet_exists = False
|
||||
|
||||
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()}
|
||||
if not ratchet_exists:
|
||||
RNS.log(f"Remembering ratchet {RNS.prettyhexrep(Identity._get_ratchet_id(ratchet))} for {RNS.prettyhexrep(destination_hash)}", RNS.LOG_EXTREME)
|
||||
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)
|
||||
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)
|
||||
outpath = f"{ratchetdir}/{hexhash}.out"
|
||||
finalpath = f"{ratchetdir}/{hexhash}"
|
||||
with open(outpath, "wb") as ratchet_file:
|
||||
ratchet_file.write(umsgpack.packb(ratchet_data))
|
||||
os.replace(outpath, finalpath)
|
||||
|
||||
|
||||
threading.Thread(target=persist_job, daemon=True).start()
|
||||
|
||||
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)
|
||||
@@ -331,12 +333,12 @@ class Identity:
|
||||
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
|
||||
with open(ratchet_path, "rb") as ratchet_file:
|
||||
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)
|
||||
@@ -648,8 +650,8 @@ class Identity:
|
||||
context=self.get_context(),
|
||||
)
|
||||
|
||||
fernet = Fernet(derived_key)
|
||||
ciphertext = fernet.encrypt(plaintext)
|
||||
token = Token(derived_key)
|
||||
ciphertext = token.encrypt(plaintext)
|
||||
token = ephemeral_pub_bytes+ciphertext
|
||||
|
||||
return token
|
||||
@@ -686,8 +688,8 @@ class Identity:
|
||||
context=self.get_context(),
|
||||
)
|
||||
|
||||
fernet = Fernet(derived_key)
|
||||
plaintext = fernet.decrypt(ciphertext)
|
||||
token = Token(derived_key)
|
||||
plaintext = token.decrypt(ciphertext)
|
||||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = ratchet_id
|
||||
|
||||
@@ -711,8 +713,8 @@ class Identity:
|
||||
context=self.get_context(),
|
||||
)
|
||||
|
||||
fernet = Fernet(derived_key)
|
||||
plaintext = fernet.decrypt(ciphertext)
|
||||
token = Token(derived_key)
|
||||
plaintext = token.decrypt(ciphertext)
|
||||
if ratchet_id_receiver:
|
||||
ratchet_id_receiver.latest_ratchet_id = None
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import threading
|
||||
@@ -59,6 +59,7 @@ class AX25():
|
||||
class AX25KISSInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
BITRATE_GUESS = 1200
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
owner = None
|
||||
port = None
|
||||
@@ -68,7 +69,7 @@ class AX25KISSInterface(Interface):
|
||||
stopbits = None
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, callsign, ssid, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control):
|
||||
def __init__(self, owner, configuration):
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
import serial
|
||||
@@ -79,6 +80,25 @@ class AX25KISSInterface(Interface):
|
||||
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
preamble = int(c["preamble"]) if "preamble" in c else None
|
||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
parity = c["parity"] if "parity" in c else "N"
|
||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||
|
||||
callsign = c["callsign"] if "callsign" in c else ""
|
||||
ssid = int(c["ssid"]) if "ssid" in c else -1
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for serial interface")
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
self.pyserial = serial
|
||||
@@ -225,13 +245,13 @@ class AX25KISSInterface(Interface):
|
||||
raise IOError("Could not enable AX.25 KISS interface flow control")
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
if (len(data) > AX25.HEADER_SIZE):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data[AX25.HEADER_SIZE:], self)
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
datalen = len(data)
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
@@ -281,7 +301,7 @@ class AX25KISSInterface(Interface):
|
||||
if len(self.packet_queue) > 0:
|
||||
data = self.packet_queue.pop(0)
|
||||
self.interface_ready = True
|
||||
self.processOutgoing(data)
|
||||
self.process_outgoing(data)
|
||||
elif len(self.packet_queue) == 0:
|
||||
self.interface_ready = True
|
||||
|
||||
@@ -300,7 +320,7 @@ class AX25KISSInterface(Interface):
|
||||
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
|
||||
@@ -52,6 +52,7 @@ class KISS():
|
||||
class KISSInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
BITRATE_GUESS = 1200
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
owner = None
|
||||
port = None
|
||||
@@ -61,7 +62,7 @@ class KISSInterface(Interface):
|
||||
stopbits = None
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control, beacon_interval, beacon_data):
|
||||
def __init__(self, owner, configuration):
|
||||
import importlib
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.on_android = True
|
||||
@@ -83,6 +84,21 @@ class KISSInterface(Interface):
|
||||
raise SystemError("Android-specific interface was used on non-Android OS")
|
||||
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
preamble = int(c["preamble"]) if "preamble" in c else None
|
||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
parity = c["parity"] if "parity" in c else "N"
|
||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||
beacon_interval = int(c["beacon_interval"]) if "beacon_interval" in c and c["beacon_interval"] != None else None
|
||||
beacon_data = c["beacon_data"] if "beacon_data" in c else None
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
@@ -267,13 +283,13 @@ class KISSInterface(Interface):
|
||||
raise IOError("Could not enable KISS interface flow control")
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
def af():
|
||||
self.owner.inbound(data, self)
|
||||
threading.Thread(target=af, daemon=True).start()
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
datalen = len(data)
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
@@ -307,7 +323,7 @@ class KISSInterface(Interface):
|
||||
if len(self.packet_queue) > 0:
|
||||
data = self.packet_queue.pop(0)
|
||||
self.interface_ready = True
|
||||
self.processOutgoing(data)
|
||||
self.process_outgoing(data)
|
||||
elif len(self.packet_queue) == 0:
|
||||
self.interface_ready = True
|
||||
|
||||
@@ -328,7 +344,7 @@ class KISSInterface(Interface):
|
||||
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
@@ -373,7 +389,13 @@ class KISSInterface(Interface):
|
||||
if time.time() > self.first_tx + self.beacon_i:
|
||||
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.beacon_d.decode("utf-8")), RNS.LOG_DEBUG)
|
||||
self.first_tx = None
|
||||
self.processOutgoing(self.beacon_d)
|
||||
|
||||
# Pad to minimum length
|
||||
frame = bytearray(self.beacon_d)
|
||||
while len(frame) < 15:
|
||||
frame.append(0x00)
|
||||
|
||||
self.process_outgoing(bytes(frame))
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
|
||||
@@ -64,10 +64,12 @@ class KISS():
|
||||
CMD_STAT_CHTM = 0x25
|
||||
CMD_STAT_PHYPRM = 0x26
|
||||
CMD_STAT_BAT = 0x27
|
||||
CMD_STAT_CSMA = 0x28
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FB_EXT = 0x41
|
||||
CMD_FB_READ = 0x42
|
||||
CMD_DISP_READ = 0x66
|
||||
CMD_FB_WRITE = 0x43
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_PLATFORM = 0x48
|
||||
@@ -89,6 +91,7 @@ class KISS():
|
||||
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
|
||||
|
||||
@@ -237,9 +240,10 @@ class AndroidBluetoothManager():
|
||||
|
||||
class RNodeInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
FREQ_MIN = 137000000
|
||||
FREQ_MAX = 1020000000
|
||||
FREQ_MAX = 3000000000
|
||||
|
||||
RSSI_OFFSET = 157
|
||||
|
||||
@@ -260,6 +264,8 @@ class RNodeInterface(Interface):
|
||||
BATTERY_STATE_CHARGING = 0x02
|
||||
BATTERY_STATE_CHARGED = 0x03
|
||||
|
||||
DISPLAY_READ_INTERVAL = 1.0
|
||||
|
||||
@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):
|
||||
@@ -340,12 +346,27 @@ class RNodeInterface(Interface):
|
||||
serial.close()
|
||||
|
||||
|
||||
def __init__(
|
||||
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,
|
||||
ble_addr = None, ble_name = None, force_ble=False):
|
||||
def __init__(self, owner, configuration):
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
allow_bluetooth = c.as_bool("allow_bluetooth") if "allow_bluetooth" in c else False
|
||||
target_device_name = c["target_device_name"] if "target_device_name" in c else None
|
||||
target_device_address = c["target_device_address"] if "target_device_address" in c else None
|
||||
ble_name = c["ble_name"] if "ble_name" in c else None
|
||||
ble_addr = c["ble_addr"] if "ble_addr" in c else None
|
||||
force_ble = c["force_ble"] if "force_ble" in c else False
|
||||
frequency = int(c["frequency"]) if "frequency" in c else 0
|
||||
bandwidth = int(c["bandwidth"]) if "bandwidth" in c else 0
|
||||
txpower = int(c["txpower"]) if "txpower" in c else 0
|
||||
sf = int(c["spreadingfactor"]) if "spreadingfactor" in c else 0
|
||||
cr = int(c["codingrate"]) if "codingrate" in c else 0
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
id_interval = int(c["id_interval"]) if "id_interval" in c and c["id_interval"] != None else None
|
||||
id_callsign = c["id_callsign"] if "id_callsign" in c else None
|
||||
st_alock = float(c["airtime_limit_short"]) if "airtime_limit_short" in c and c["airtime_limit_short"] != None else None
|
||||
lt_alock = float(c["airtime_limit_long"]) if "airtime_limit_long" in c and c["airtime_limit_long"] != None else None
|
||||
port = c["port"] if "port" in c else None
|
||||
|
||||
import importlib
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.on_android = True
|
||||
@@ -445,12 +466,29 @@ class RNodeInterface(Interface):
|
||||
self.r_airtime_long = 0.0
|
||||
self.r_channel_load_short = 0.0
|
||||
self.r_channel_load_long = 0.0
|
||||
self.r_symbol_time_ms = None
|
||||
self.r_symbol_rate = None
|
||||
self.r_preamble_symbols = None
|
||||
self.r_premable_time_ms = None
|
||||
self.r_symbol_time_ms = None
|
||||
self.r_symbol_rate = None
|
||||
self.r_preamble_symbols = None
|
||||
self.r_premable_time_ms = None
|
||||
self.r_csma_slot_time_ms = None
|
||||
self.r_csma_difs_ms = None
|
||||
self.r_csma_cw_band = None
|
||||
self.r_csma_cw_min = None
|
||||
self.r_csma_cw_max = None
|
||||
self.r_current_rssi = None
|
||||
self.r_noise_floor = None
|
||||
|
||||
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
|
||||
self.r_battery_percent = 0
|
||||
self.r_framebuffer = b""
|
||||
self.r_framebuffer_readtime = 0
|
||||
self.r_framebuffer_latency = 0
|
||||
self.r_disp = b""
|
||||
self.r_disp_readtime = 0
|
||||
self.r_disp_latency = 0
|
||||
|
||||
self.should_read_display = False
|
||||
self.read_display_interval = RNodeInterface.DISPLAY_READ_INTERVAL
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
@@ -792,6 +830,33 @@ class RNodeInterface(Interface):
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while writing framebuffer data device")
|
||||
|
||||
def read_framebuffer(self):
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FB_READ])+bytes([0x01])+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
self.r_framebuffer_readtime = time.time()
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending framebuffer read command")
|
||||
|
||||
def read_display(self):
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_READ])+bytes([0x01])+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
self.r_disp_readtime = time.time()
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display read command")
|
||||
|
||||
def _read_display_job(self):
|
||||
while self.should_read_display:
|
||||
self.read_display()
|
||||
time.sleep(self.read_display_interval)
|
||||
|
||||
def start_display_updates(self):
|
||||
if not self.should_read_display:
|
||||
self.should_read_display = True
|
||||
threading.Thread(target=self._read_display_job, daemon=True).start()
|
||||
|
||||
def stop_display_updates(self):
|
||||
self.should_read_display = False
|
||||
|
||||
def hard_reset(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
|
||||
written = self.write_mux(kiss_command)
|
||||
@@ -940,7 +1005,7 @@ class RNodeInterface(Interface):
|
||||
except:
|
||||
self.bitrate = 0
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
|
||||
def af():
|
||||
@@ -948,7 +1013,7 @@ class RNodeInterface(Interface):
|
||||
threading.Thread(target=af, daemon=True).start()
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
datalen = len(data)
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
@@ -979,7 +1044,7 @@ class RNodeInterface(Interface):
|
||||
if len(self.packet_queue) > 0:
|
||||
data = self.packet_queue.pop(0)
|
||||
self.interface_ready = True
|
||||
self.processOutgoing(data)
|
||||
self.process_outgoing(data)
|
||||
elif len(self.packet_queue) == 0:
|
||||
self.interface_ready = True
|
||||
|
||||
@@ -1006,7 +1071,7 @@ class RNodeInterface(Interface):
|
||||
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
data_buffer = b""
|
||||
command_buffer = b""
|
||||
elif (byte == KISS.FEND):
|
||||
@@ -1182,16 +1247,31 @@ class RNodeInterface(Interface):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 8):
|
||||
if (len(command_buffer) == 11):
|
||||
ats = command_buffer[0] << 8 | command_buffer[1]
|
||||
atl = command_buffer[2] << 8 | command_buffer[3]
|
||||
cus = command_buffer[4] << 8 | command_buffer[5]
|
||||
cul = command_buffer[6] << 8 | command_buffer[7]
|
||||
crs = command_buffer[8]
|
||||
nfl = command_buffer[9]
|
||||
ntf = command_buffer[10]
|
||||
|
||||
self.r_airtime_short = ats/100.0
|
||||
self.r_airtime_long = atl/100.0
|
||||
self.r_channel_load_short = cus/100.0
|
||||
self.r_channel_load_long = cul/100.0
|
||||
self.r_current_rssi = crs-RNodeInterface.RSSI_OFFSET
|
||||
self.r_noise_floor = nfl-RNodeInterface.RSSI_OFFSET
|
||||
if ntf == 0xFF:
|
||||
self.r_interference = None
|
||||
else:
|
||||
self.r_interference = ntf-RNodeInterface.RSSI_OFFSET
|
||||
|
||||
if self.r_interference != None:
|
||||
RNS.log(f"{self} Radio detected interference at {self.r_interference} dBm", RNS.LOG_DEBUG)
|
||||
|
||||
# TODO: Remove debug
|
||||
# RNS.log(f"RSSI: {self.r_current_rssi}, Noise floor: {self.r_noise_floor}, Interference: {self.r_interference}", RNS.LOG_EXTREME)
|
||||
elif (command == KISS.CMD_STAT_PHYPRM):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
@@ -1203,22 +1283,49 @@ class RNodeInterface(Interface):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 10):
|
||||
if (len(command_buffer) == 12):
|
||||
lst = (command_buffer[0] << 8 | command_buffer[1])/1000.0
|
||||
lsr = command_buffer[2] << 8 | command_buffer[3]
|
||||
prs = command_buffer[4] << 8 | command_buffer[5]
|
||||
prt = command_buffer[6] << 8 | command_buffer[7]
|
||||
cst = command_buffer[8] << 8 | command_buffer[9]
|
||||
dft = command_buffer[10] << 8 | command_buffer[11]
|
||||
|
||||
if lst != self.r_symbol_time_ms or lsr != self.r_symbol_rate or prs != self.r_preamble_symbols or prt != self.r_premable_time_ms or cst != self.r_csma_slot_time_ms:
|
||||
if lst != self.r_symbol_time_ms or lsr != self.r_symbol_rate or prs != self.r_preamble_symbols or prt != self.r_premable_time_ms or cst != self.r_csma_slot_time_ms or dft != self.r_csma_difs_ms:
|
||||
self.r_symbol_time_ms = lst
|
||||
self.r_symbol_rate = lsr
|
||||
self.r_preamble_symbols = prs
|
||||
self.r_premable_time_ms = prt
|
||||
self.r_csma_slot_time_ms = cst
|
||||
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)
|
||||
self.r_csma_difs_ms = dft
|
||||
RNS.log(f"{self} Radio reporting symbol time is "+str(round(self.r_symbol_time_ms,2))+"ms ("+str(self.r_symbol_rate)+" baud)", RNS.LOG_DEBUG)
|
||||
RNS.log(f"{self} Radio reporting preamble is "+str(self.r_preamble_symbols)+" symbols ("+str(self.r_premable_time_ms)+"ms)", RNS.LOG_DEBUG)
|
||||
RNS.log(f"{self} Radio reporting CSMA slot time is "+str(self.r_csma_slot_time_ms)+"ms", RNS.LOG_DEBUG)
|
||||
RNS.log(f"{self} Radio reporting DIFS time is "+str(self.r_csma_difs_ms)+"ms", RNS.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_STAT_CSMA):
|
||||
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) == 3):
|
||||
cbw = command_buffer[0]
|
||||
cbl = command_buffer[1]
|
||||
cbh = command_buffer[2]
|
||||
|
||||
if cbw != self.r_csma_cw_band or cbl != self.r_csma_cw_min or cbh != self.r_csma_cw_max:
|
||||
self.r_csma_cw_band = cbw
|
||||
self.r_csma_cw_min = cbl
|
||||
self.r_csma_cw_max = cbh
|
||||
# TODO: Remove debug
|
||||
# RNS.log(f"{self} Radio reporting contention window band is {self.r_csma_cw_band}", RNS.LOG_EXTREME)
|
||||
# RNS.log(f"{self} Radio reporting minimum contention window is {self.r_csma_cw_min}", RNS.LOG_EXTREME)
|
||||
# RNS.log(f"{self} Radio reporting maximum contention window is {self.r_csma_cw_max}", RNS.LOG_EXTREME)
|
||||
elif (command == KISS.CMD_STAT_BAT):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
@@ -1254,6 +1361,9 @@ class RNodeInterface(Interface):
|
||||
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")
|
||||
@@ -1265,6 +1375,37 @@ class RNodeInterface(Interface):
|
||||
raise IOError("ESP32 reset")
|
||||
elif (command == KISS.CMD_READY):
|
||||
self.process_queue()
|
||||
|
||||
elif (command == KISS.CMD_FB_READ):
|
||||
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) == 512):
|
||||
self.r_framebuffer_latency = time.time() - self.r_framebuffer_readtime
|
||||
self.r_framebuffer = command_buffer
|
||||
|
||||
elif (command == KISS.CMD_DISP_READ):
|
||||
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) == 1024):
|
||||
self.r_disp_latency = time.time() - self.r_disp_readtime
|
||||
self.r_disp = command_buffer
|
||||
|
||||
elif (command == KISS.CMD_DETECT):
|
||||
if byte == KISS.DETECT_RESP:
|
||||
self.detected = True
|
||||
@@ -1284,7 +1425,7 @@ class RNodeInterface(Interface):
|
||||
if self.first_tx != None:
|
||||
if time.time() > self.first_tx + self.id_interval:
|
||||
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG)
|
||||
self.processOutgoing(self.id_callsign)
|
||||
self.process_outgoing(self.id_callsign)
|
||||
|
||||
if (time.time() - self.last_port_io > self.port_io_timeout):
|
||||
self.detect()
|
||||
|
||||
@@ -42,6 +42,7 @@ class HDLC():
|
||||
|
||||
class SerialInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
owner = None
|
||||
port = None
|
||||
@@ -51,7 +52,7 @@ class SerialInterface(Interface):
|
||||
stopbits = None
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
|
||||
def __init__(self, owner, configuration):
|
||||
import importlib
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
self.on_android = True
|
||||
@@ -74,6 +75,17 @@ class SerialInterface(Interface):
|
||||
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
parity = c["parity"] if "parity" in c else "N"
|
||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for serial interface")
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
self.pyserial = serial
|
||||
@@ -172,13 +184,13 @@ class SerialInterface(Interface):
|
||||
RNS.log("Serial port "+self.port+" is now open", RNS.LOG_VERBOSE)
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
def af():
|
||||
self.owner.inbound(data, self)
|
||||
threading.Thread(target=af, daemon=True).start()
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
if self.online:
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
written = self.serial.write(data)
|
||||
@@ -202,7 +214,7 @@ class SerialInterface(Interface):
|
||||
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
|
||||
@@ -23,5 +23,7 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
from collections import deque
|
||||
import socketserver
|
||||
import threading
|
||||
@@ -36,6 +36,7 @@ class AutoInterface(Interface):
|
||||
DEFAULT_DISCOVERY_PORT = 29716
|
||||
DEFAULT_DATA_PORT = 42671
|
||||
DEFAULT_GROUP_ID = "reticulum".encode("utf-8")
|
||||
DEFAULT_IFAC_SIZE = 16
|
||||
|
||||
SCOPE_LINK = "2"
|
||||
SCOPE_ADMIN = "4"
|
||||
@@ -86,7 +87,18 @@ class AutoInterface(Interface):
|
||||
|
||||
return socket.if_nametoindex(ifname)
|
||||
|
||||
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, multicast_address_type=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
|
||||
def __init__(self, owner, configuration):
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
group_id = c["group_id"] if "group_id" in c else None
|
||||
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
|
||||
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
|
||||
multicast_address_type = c["multicast_address_type"] if "multicast_address_type" in c else None
|
||||
data_port = int(c["data_port"]) if "data_port" in c else None
|
||||
allowed_interfaces = c.as_list("devices") if "devices" in c else None
|
||||
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
|
||||
configured_bitrate = c["configured_bitrate"] if "configured_bitrate" in c else None
|
||||
|
||||
from RNS.vendor.ifaddr import niwrapper
|
||||
super().__init__()
|
||||
self.netinfo = niwrapper
|
||||
@@ -287,7 +299,7 @@ class AutoInterface(Interface):
|
||||
addr_info = socket.getaddrinfo(local_addr, self.data_port, socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
address = addr_info[0][4]
|
||||
|
||||
udp_server = socketserver.UDPServer(address, self.handler_factory(self.processIncoming))
|
||||
udp_server = socketserver.UDPServer(address, self.handler_factory(self.process_incoming))
|
||||
self.interface_servers[ifname] = udp_server
|
||||
|
||||
thread = threading.Thread(target=udp_server.serve_forever)
|
||||
@@ -369,7 +381,7 @@ class AutoInterface(Interface):
|
||||
|
||||
RNS.log("Starting new UDP listener for "+str(self)+" "+str(ifname), RNS.LOG_DEBUG)
|
||||
|
||||
udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.processIncoming))
|
||||
udp_server = socketserver.UDPServer(listen_address, self.handler_factory(self.process_incoming))
|
||||
self.interface_servers[ifname] = udp_server
|
||||
|
||||
thread = threading.Thread(target=udp_server.serve_forever)
|
||||
@@ -443,22 +455,24 @@ class AutoInterface(Interface):
|
||||
def refresh_peer(self, addr):
|
||||
self.peers[addr][1] = time.time()
|
||||
|
||||
def processIncoming(self, data):
|
||||
data_hash = RNS.Identity.full_hash(data)
|
||||
deque_hit = False
|
||||
if data_hash in self.mif_deque:
|
||||
for te in self.mif_deque_times:
|
||||
if te[0] == data_hash and time.time() < te[1]+AutoInterface.MULTI_IF_DEQUE_TTL:
|
||||
deque_hit = True
|
||||
break
|
||||
def process_incoming(self, data):
|
||||
if self.online:
|
||||
data_hash = RNS.Identity.full_hash(data)
|
||||
deque_hit = False
|
||||
if data_hash in self.mif_deque:
|
||||
for te in self.mif_deque_times:
|
||||
if te[0] == data_hash and time.time() < te[1]+AutoInterface.MULTI_IF_DEQUE_TTL:
|
||||
deque_hit = True
|
||||
break
|
||||
|
||||
if not deque_hit:
|
||||
self.mif_deque.append(data_hash)
|
||||
self.mif_deque_times.append([data_hash, time.time()])
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
if not deque_hit:
|
||||
self.mif_deque.append(data_hash)
|
||||
self.mif_deque_times.append([data_hash, time.time()])
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
if self.online:
|
||||
for peer in self.peers:
|
||||
try:
|
||||
if self.outbound_udp_socket == None:
|
||||
@@ -480,6 +494,9 @@ class AutoInterface(Interface):
|
||||
def should_ingress_limit(self):
|
||||
return False
|
||||
|
||||
def detach(self):
|
||||
self.online = False
|
||||
|
||||
def __str__(self):
|
||||
return "AutoInterface["+self.name+"]"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import platform
|
||||
@@ -627,14 +627,14 @@ class I2PInterfacePeer(Interface):
|
||||
RNS.log("Attempt to reconnect on a non-initiator I2P interface. This should not happen.", RNS.LOG_ERROR)
|
||||
raise IOError("Attempt to reconnect on a non-initiator I2P interface")
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None and self.parent_count:
|
||||
self.parent_interface.rxb += len(data)
|
||||
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self, data):
|
||||
def process_outgoing(self, data):
|
||||
if self.online:
|
||||
while self.writing:
|
||||
time.sleep(0.001)
|
||||
@@ -732,7 +732,7 @@ class I2PInterfacePeer(Interface):
|
||||
# Read loop for KISS framing
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
@@ -759,7 +759,7 @@ class I2PInterfacePeer(Interface):
|
||||
# Read loop for HDLC framing
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
@@ -815,8 +815,8 @@ class I2PInterfacePeer(Interface):
|
||||
self.IN = False
|
||||
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
if self.parent_interface.clients > 0:
|
||||
self.parent_interface.clients -= 1
|
||||
while self in self.parent_interface.spawned_interfaces:
|
||||
self.parent_interface.spawned_interfaces.remove(self)
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
if not self.initiator:
|
||||
@@ -829,14 +829,28 @@ class I2PInterfacePeer(Interface):
|
||||
|
||||
class I2PInterface(Interface):
|
||||
BITRATE_GUESS = 256*1000
|
||||
DEFAULT_IFAC_SIZE = 16
|
||||
|
||||
def __init__(self, owner, name, rns_storagepath, peers, connectable = False, ifac_size = 16, ifac_netname = None, ifac_netkey = None):
|
||||
@property
|
||||
def clients(self):
|
||||
return len(self.spawned_interfaces)
|
||||
|
||||
def __init__(self, owner, configuration):
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
rns_storagepath = c["storagepath"]
|
||||
peers = c.as_list("peers") if "peers" in c else None
|
||||
connectable = c.as_bool("connectable") if "connectable" in c else False
|
||||
ifac_size = c["ifac_size"] if "ifac_size" in c else None
|
||||
ifac_netname = c["ifac_netname"] if "ifac_netname" in c else None
|
||||
ifac_netkey = c["ifac_netkey"] if "ifac_netkey" in c else None
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
self.online = False
|
||||
self.clients = 0
|
||||
self.spawned_interfaces = []
|
||||
self.owner = owner
|
||||
self.connectable = connectable
|
||||
self.i2p_tunneled = True
|
||||
@@ -956,10 +970,12 @@ class I2PInterface(Interface):
|
||||
spawned_interface.HW_MTU = self.HW_MTU
|
||||
RNS.log("Spawned new I2PInterface Peer: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
self.clients += 1
|
||||
while spawned_interface in self.spawned_interfaces:
|
||||
self.spawned_interfaces.remove(spawned_interface)
|
||||
self.spawned_interfaces.append(spawned_interface)
|
||||
spawned_interface.read_loop()
|
||||
|
||||
def processOutgoing(self, data):
|
||||
def process_outgoing(self, data):
|
||||
pass
|
||||
|
||||
def received_announce(self, from_spawned=False):
|
||||
|
||||
@@ -24,6 +24,7 @@ import RNS
|
||||
import time
|
||||
import threading
|
||||
from collections import deque
|
||||
from RNS.vendor.configobj import ConfigObj
|
||||
|
||||
class Interface:
|
||||
IN = False
|
||||
@@ -63,12 +64,16 @@ class Interface:
|
||||
IC_BURST_PENALTY = 5*60
|
||||
IC_HELD_RELEASE_INTERVAL = 30
|
||||
|
||||
AUTOCONFIGURE_MTU = False
|
||||
|
||||
def __init__(self):
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.created = time.time()
|
||||
self.online = False
|
||||
self.bitrate = 1e6
|
||||
self.rxb = 0
|
||||
self.txb = 0
|
||||
self.created = time.time()
|
||||
self.detached = False
|
||||
self.online = False
|
||||
self.bitrate = 62500
|
||||
self.HW_MTU = None
|
||||
|
||||
self.ingress_control = True
|
||||
self.ic_max_held_announces = Interface.MAX_HELD_ANNOUNCES
|
||||
@@ -116,6 +121,31 @@ class Interface:
|
||||
else:
|
||||
return False
|
||||
|
||||
def optimise_mtu(self):
|
||||
if self.AUTOCONFIGURE_MTU:
|
||||
if self.bitrate > 16_000_000:
|
||||
self.HW_MTU = 262144
|
||||
elif self.bitrate > 8_000_000:
|
||||
self.HW_MTU = 131072
|
||||
elif self.bitrate > 4_000_000:
|
||||
self.HW_MTU = 65536
|
||||
elif self.bitrate > 2_000_000:
|
||||
self.HW_MTU = 32768
|
||||
elif self.bitrate > 1_000_000:
|
||||
self.HW_MTU = 16384
|
||||
elif self.bitrate > 500_000:
|
||||
self.HW_MTU = 8192
|
||||
elif self.bitrate > 250_000:
|
||||
self.HW_MTU = 4096
|
||||
elif self.bitrate > 125_000:
|
||||
self.HW_MTU = 2048
|
||||
elif self.bitrate > 62_500:
|
||||
self.HW_MTU = 1024
|
||||
else:
|
||||
self.HW_MTU = None
|
||||
|
||||
RNS.log(f"{self} hardware MTU set to {self.HW_MTU}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
|
||||
def age(self):
|
||||
return time.time()-self.created
|
||||
|
||||
@@ -222,7 +252,7 @@ class Interface:
|
||||
wait_time = (tx_time / self.announce_cap)
|
||||
self.announce_allowed_at = now + wait_time
|
||||
|
||||
self.processOutgoing(selected["raw"])
|
||||
self.process_outgoing(selected["raw"])
|
||||
self.sent_announce()
|
||||
|
||||
if selected in self.announce_queue:
|
||||
@@ -238,4 +268,15 @@ class Interface:
|
||||
RNS.log("The announce queue for this interface has been cleared.", RNS.LOG_ERROR)
|
||||
|
||||
def detach(self):
|
||||
pass
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def get_config_obj(config_in):
|
||||
if type(config_in) == ConfigObj:
|
||||
return config_in
|
||||
else:
|
||||
try:
|
||||
return ConfigObj(config_in)
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not parse supplied configuration data. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
raise SystemError("Invalid configuration data supplied")
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import threading
|
||||
@@ -52,6 +52,7 @@ class KISS():
|
||||
class KISSInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
BITRATE_GUESS = 1200
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
owner = None
|
||||
port = None
|
||||
@@ -61,7 +62,7 @@ class KISSInterface(Interface):
|
||||
stopbits = None
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits, preamble, txtail, persistence, slottime, flow_control, beacon_interval, beacon_data):
|
||||
def __init__(self, owner, configuration):
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
import serial
|
||||
@@ -71,6 +72,24 @@ class KISSInterface(Interface):
|
||||
RNS.panic()
|
||||
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
preamble = int(c["preamble"]) if "preamble" in c else None
|
||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
parity = c["parity"] if "parity" in c else "N"
|
||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||
beacon_interval = int(c["id_interval"]) if "id_interval" in c else None
|
||||
beacon_data = c["id_callsign"] if "id_callsign" in c else None
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for serial interface")
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
@@ -217,12 +236,12 @@ class KISSInterface(Interface):
|
||||
raise IOError("Could not enable KISS interface flow control")
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
datalen = len(data)
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
@@ -256,7 +275,7 @@ class KISSInterface(Interface):
|
||||
if len(self.packet_queue) > 0:
|
||||
data = self.packet_queue.pop(0)
|
||||
self.interface_ready = True
|
||||
self.processOutgoing(data)
|
||||
self.process_outgoing(data)
|
||||
elif len(self.packet_queue) == 0:
|
||||
self.interface_ready = True
|
||||
|
||||
@@ -275,7 +294,7 @@ class KISSInterface(Interface):
|
||||
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
@@ -319,7 +338,13 @@ class KISSInterface(Interface):
|
||||
if time.time() > self.first_tx + self.beacon_i:
|
||||
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.beacon_d.decode("utf-8")), RNS.LOG_DEBUG)
|
||||
self.first_tx = None
|
||||
self.processOutgoing(self.beacon_d)
|
||||
|
||||
# Pad to minimum length
|
||||
frame = bytearray(self.beacon_d)
|
||||
while len(frame) < 15:
|
||||
frame.append(0x00)
|
||||
|
||||
self.process_outgoing(bytes(frame))
|
||||
|
||||
except Exception as e:
|
||||
self.online = False
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import socket
|
||||
@@ -52,14 +52,12 @@ class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
|
||||
class LocalClientInterface(Interface):
|
||||
RECONNECT_WAIT = 8
|
||||
AUTOCONFIGURE_MTU = True
|
||||
|
||||
def __init__(self, owner, name, target_port = None, connected_socket=None):
|
||||
super().__init__()
|
||||
|
||||
# TODO: Remove at some point
|
||||
# self.rxptime = 0
|
||||
|
||||
self.HW_MTU = 1064
|
||||
self.HW_MTU = 262144
|
||||
|
||||
self.online = False
|
||||
|
||||
@@ -89,7 +87,7 @@ class LocalClientInterface(Interface):
|
||||
self.connect()
|
||||
|
||||
self.owner = owner
|
||||
self.bitrate = 1000*1000*1000
|
||||
self.bitrate = 1_000_000_000
|
||||
self.online = True
|
||||
self.writing = False
|
||||
|
||||
@@ -152,7 +150,7 @@ class LocalClientInterface(Interface):
|
||||
raise IOError("Attempt to reconnect on a non-initiator local interface")
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.rxb += len(data)
|
||||
@@ -166,7 +164,7 @@ class LocalClientInterface(Interface):
|
||||
# duration = time.time() - processing_start
|
||||
# self.rxptime += duration
|
||||
|
||||
def processOutgoing(self, data):
|
||||
def process_outgoing(self, data):
|
||||
if self.online:
|
||||
try:
|
||||
self.writing = True
|
||||
@@ -176,8 +174,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])
|
||||
@@ -197,32 +195,31 @@ class LocalClientInterface(Interface):
|
||||
try:
|
||||
in_frame = False
|
||||
escape = False
|
||||
frame_buffer = b""
|
||||
data_in = b""
|
||||
data_buffer = b""
|
||||
|
||||
while True:
|
||||
data_in = self.socket.recv(4096)
|
||||
if len(data_in) > 0:
|
||||
pointer = 0
|
||||
while pointer < len(data_in):
|
||||
byte = data_in[pointer]
|
||||
pointer += 1
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < self.HW_MTU):
|
||||
if (byte == HDLC.ESC):
|
||||
escape = True
|
||||
frame_buffer += data_in
|
||||
flags_remaining = True
|
||||
while flags_remaining:
|
||||
frame_start = frame_buffer.find(HDLC.FLAG)
|
||||
if frame_start != -1:
|
||||
frame_end = frame_buffer.find(HDLC.FLAG, frame_start+1)
|
||||
if frame_end != -1:
|
||||
frame = frame_buffer[frame_start+1:frame_end]
|
||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
|
||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
|
||||
if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
|
||||
self.process_incoming(frame)
|
||||
frame_buffer = frame_buffer[frame_end:]
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.FLAG
|
||||
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.ESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
flags_remaining = False
|
||||
else:
|
||||
flags_remaining = False
|
||||
|
||||
else:
|
||||
self.online = False
|
||||
if self.is_connected_to_shared_instance and not self.detached:
|
||||
@@ -249,12 +246,14 @@ class LocalClientInterface(Interface):
|
||||
self.detached = True
|
||||
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
if self.socket != None:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
|
||||
|
||||
try:
|
||||
self.socket.close()
|
||||
if self.socket != None:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
|
||||
|
||||
@@ -292,6 +291,7 @@ class LocalClientInterface(Interface):
|
||||
|
||||
|
||||
class LocalServerInterface(Interface):
|
||||
AUTOCONFIGURE_MTU = True
|
||||
|
||||
def __init__(self, owner, bindport=None):
|
||||
super().__init__()
|
||||
@@ -350,7 +350,7 @@ class LocalServerInterface(Interface):
|
||||
self.clients += 1
|
||||
spawned_interface.read_loop()
|
||||
|
||||
def processOutgoing(self, data):
|
||||
def process_outgoing(self, data):
|
||||
pass
|
||||
|
||||
def received_announce(self, from_spawned=False):
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import threading
|
||||
@@ -46,16 +46,25 @@ class HDLC():
|
||||
class PipeInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
BITRATE_GUESS = 1*1000*1000
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
owner = None
|
||||
command = None
|
||||
|
||||
def __init__(self, owner, name, command, respawn_delay):
|
||||
def __init__(self, owner, configuration):
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
command = c["command"] if "command" in c else None
|
||||
respawn_delay = c.as_float("respawn_delay") if "respawn_delay" in c else None
|
||||
|
||||
if command == None:
|
||||
raise ValueError("No command specified for PipeInterface")
|
||||
|
||||
if respawn_delay == None:
|
||||
respawn_delay = 5
|
||||
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
self.owner = owner
|
||||
@@ -101,12 +110,12 @@ class PipeInterface(Interface):
|
||||
RNS.log("Subprocess pipe for "+str(self)+" is now connected", RNS.LOG_VERBOSE)
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
if self.online:
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
written = self.process.stdin.write(data)
|
||||
@@ -134,7 +143,7 @@ class PipeInterface(Interface):
|
||||
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import threading
|
||||
@@ -55,10 +55,12 @@ class KISS():
|
||||
CMD_STAT_CHTM = 0x25
|
||||
CMD_STAT_PHYPRM = 0x26
|
||||
CMD_STAT_BAT = 0x27
|
||||
CMD_STAT_CSMA = 0x28
|
||||
CMD_BLINK = 0x30
|
||||
CMD_RANDOM = 0x40
|
||||
CMD_FB_EXT = 0x41
|
||||
CMD_FB_READ = 0x42
|
||||
CMD_DISP_READ = 0x66
|
||||
CMD_FB_WRITE = 0x43
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_PLATFORM = 0x48
|
||||
@@ -80,6 +82,7 @@ class KISS():
|
||||
ERROR_EEPROM_LOCKED = 0x03
|
||||
ERROR_QUEUE_FULL = 0x04
|
||||
ERROR_MEMORY_LOW = 0x05
|
||||
ERROR_MODEM_TIMEOUT = 0x06
|
||||
|
||||
PLATFORM_AVR = 0x90
|
||||
PLATFORM_ESP32 = 0x80
|
||||
@@ -94,6 +97,7 @@ class KISS():
|
||||
|
||||
class RNodeInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
FREQ_MIN = 137000000
|
||||
FREQ_MAX = 3000000000
|
||||
@@ -116,7 +120,9 @@ class RNodeInterface(Interface):
|
||||
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):
|
||||
DISPLAY_READ_INTERVAL = 1.0
|
||||
|
||||
def __init__(self, owner, configuration):
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
raise SystemError("Invalid interface type. The Android-specific RNode interface must be used on Android")
|
||||
|
||||
@@ -130,6 +136,41 @@ class RNodeInterface(Interface):
|
||||
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
frequency = int(c["frequency"]) if "frequency" in c else 0
|
||||
bandwidth = int(c["bandwidth"]) if "bandwidth" in c else 0
|
||||
txpower = int(c["txpower"]) if "txpower" in c else 0
|
||||
sf = int(c["spreadingfactor"]) if "spreadingfactor" in c else 0
|
||||
cr = int(c["codingrate"]) if "codingrate" in c else 0
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
id_interval = int(c["id_interval"]) if "id_interval" in c else None
|
||||
id_callsign = c["id_callsign"] if "id_callsign" in c else None
|
||||
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
|
||||
|
||||
self.HW_MTU = 508
|
||||
|
||||
self.pyserial = serial
|
||||
@@ -144,6 +185,7 @@ class RNodeInterface(Interface):
|
||||
self.online = False
|
||||
self.detached = False
|
||||
self.reconnecting= False
|
||||
self.hw_errors = []
|
||||
|
||||
self.use_ble = False
|
||||
self.ble_name = ble_name
|
||||
@@ -193,12 +235,29 @@ class RNodeInterface(Interface):
|
||||
self.r_airtime_long = 0.0
|
||||
self.r_channel_load_short = 0.0
|
||||
self.r_channel_load_long = 0.0
|
||||
self.r_symbol_time_ms = None
|
||||
self.r_symbol_rate = None
|
||||
self.r_preamble_symbols = None
|
||||
self.r_premable_time_ms = None
|
||||
self.r_symbol_time_ms = None
|
||||
self.r_symbol_rate = None
|
||||
self.r_preamble_symbols = None
|
||||
self.r_premable_time_ms = None
|
||||
self.r_csma_slot_time_ms = None
|
||||
self.r_csma_difs_ms = None
|
||||
self.r_csma_cw_band = None
|
||||
self.r_csma_cw_min = None
|
||||
self.r_csma_cw_max = None
|
||||
self.r_current_rssi = None
|
||||
self.r_noise_floor = None
|
||||
|
||||
self.r_battery_state = RNodeInterface.BATTERY_STATE_UNKNOWN
|
||||
self.r_battery_percent = 0
|
||||
self.r_framebuffer = b""
|
||||
self.r_framebuffer_readtime = 0
|
||||
self.r_framebuffer_latency = 0
|
||||
self.r_disp = b""
|
||||
self.r_disp_readtime = 0
|
||||
self.r_disp_latency = 0
|
||||
|
||||
self.should_read_display = False
|
||||
self.read_display_interval = RNodeInterface.DISPLAY_READ_INTERVAL
|
||||
|
||||
self.packet_queue = []
|
||||
self.flow_control = flow_control
|
||||
@@ -289,6 +348,11 @@ class RNodeInterface(Interface):
|
||||
|
||||
else:
|
||||
RNS.log(f"Opening BLE connection for {self}...")
|
||||
if self.ble != None and self.ble.running == False:
|
||||
self.ble.close()
|
||||
self.ble.cleanup()
|
||||
self.ble = None
|
||||
|
||||
if self.ble == None:
|
||||
self.ble = BLEConnection(owner=self, target_name=self.ble_name, target_bt_addr=self.ble_addr)
|
||||
self.serial = self.ble
|
||||
@@ -297,8 +361,7 @@ class RNodeInterface(Interface):
|
||||
while not self.ble.connected and time.time() < open_time + self.ble.CONNECT_TIMEOUT:
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def configure_device(self):
|
||||
def reset_radio_state(self):
|
||||
self.r_frequency = None
|
||||
self.r_bandwidth = None
|
||||
self.r_txpower = None
|
||||
@@ -306,6 +369,10 @@ class RNodeInterface(Interface):
|
||||
self.r_cr = None
|
||||
self.r_state = None
|
||||
self.r_lock = None
|
||||
self.detected = False
|
||||
|
||||
def configure_device(self):
|
||||
self.reset_radio_state()
|
||||
sleep(2.0)
|
||||
|
||||
thread = threading.Thread(target=self.readLoop)
|
||||
@@ -408,7 +475,34 @@ class RNodeInterface(Interface):
|
||||
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while writing framebuffer data device")
|
||||
raise IOError("An IO error occurred while writing framebuffer data to device")
|
||||
|
||||
def read_framebuffer(self):
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_FB_READ])+bytes([0x01])+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
self.r_framebuffer_readtime = time.time()
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending framebuffer read command")
|
||||
|
||||
def read_display(self):
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_READ])+bytes([0x01])+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
self.r_disp_readtime = time.time()
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display read command")
|
||||
|
||||
def _read_display_job(self):
|
||||
while self.should_read_display:
|
||||
self.read_display()
|
||||
time.sleep(self.read_display_interval)
|
||||
|
||||
def start_display_updates(self):
|
||||
if not self.should_read_display:
|
||||
self.should_read_display = True
|
||||
threading.Thread(target=self._read_display_job, daemon=True).start()
|
||||
|
||||
def stop_display_updates(self):
|
||||
self.should_read_display = False
|
||||
|
||||
def hard_reset(self):
|
||||
kiss_command = bytes([KISS.FEND, KISS.CMD_RESET, 0xf8, KISS.FEND])
|
||||
@@ -512,7 +606,14 @@ class RNodeInterface(Interface):
|
||||
|
||||
def validateRadioState(self):
|
||||
RNS.log("Waiting for radio configuration validation for "+str(self)+"...", RNS.LOG_VERBOSE)
|
||||
sleep(0.25);
|
||||
if self.use_ble:
|
||||
sleep(1.00)
|
||||
else:
|
||||
sleep(0.25)
|
||||
|
||||
if self.use_ble and self.ble != None and self.ble.device_disappeared:
|
||||
RNS.log(f"Device disappeared during radio state validation for {self}", RNS.LOG_ERROR)
|
||||
return False
|
||||
|
||||
self.validcfg = True
|
||||
if (self.r_frequency != None and abs(self.frequency - int(self.r_frequency)) > 100):
|
||||
@@ -545,14 +646,14 @@ class RNodeInterface(Interface):
|
||||
except:
|
||||
self.bitrate = 0
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
self.r_stat_rssi = None
|
||||
self.r_stat_snr = None
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
datalen = len(data)
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
@@ -583,7 +684,7 @@ class RNodeInterface(Interface):
|
||||
if len(self.packet_queue) > 0:
|
||||
data = self.packet_queue.pop(0)
|
||||
self.interface_ready = True
|
||||
self.processOutgoing(data)
|
||||
self.process_outgoing(data)
|
||||
elif len(self.packet_queue) == 0:
|
||||
self.interface_ready = True
|
||||
|
||||
@@ -603,7 +704,7 @@ class RNodeInterface(Interface):
|
||||
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
data_buffer = b""
|
||||
command_buffer = b""
|
||||
elif (byte == KISS.FEND):
|
||||
@@ -778,16 +879,31 @@ class RNodeInterface(Interface):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 8):
|
||||
if (len(command_buffer) == 11):
|
||||
ats = command_buffer[0] << 8 | command_buffer[1]
|
||||
atl = command_buffer[2] << 8 | command_buffer[3]
|
||||
cus = command_buffer[4] << 8 | command_buffer[5]
|
||||
cul = command_buffer[6] << 8 | command_buffer[7]
|
||||
crs = command_buffer[8]
|
||||
nfl = command_buffer[9]
|
||||
ntf = command_buffer[10]
|
||||
|
||||
self.r_airtime_short = ats/100.0
|
||||
self.r_airtime_long = atl/100.0
|
||||
self.r_channel_load_short = cus/100.0
|
||||
self.r_channel_load_long = cul/100.0
|
||||
self.r_current_rssi = crs-RNodeInterface.RSSI_OFFSET
|
||||
self.r_noise_floor = nfl-RNodeInterface.RSSI_OFFSET
|
||||
if ntf == 0xFF:
|
||||
self.r_interference = None
|
||||
else:
|
||||
self.r_interference = ntf-RNodeInterface.RSSI_OFFSET
|
||||
|
||||
if self.r_interference != None:
|
||||
RNS.log(f"{self} Radio detected interference at {self.r_interference} dBm", RNS.LOG_DEBUG)
|
||||
|
||||
# TODO: Remove debug
|
||||
# RNS.log(f"RSSI: {self.r_current_rssi}, Noise floor: {self.r_noise_floor}, Interference: {self.r_interference}", RNS.LOG_EXTREME)
|
||||
elif (command == KISS.CMD_STAT_PHYPRM):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
@@ -799,22 +915,49 @@ class RNodeInterface(Interface):
|
||||
byte = KISS.FESC
|
||||
escape = False
|
||||
command_buffer = command_buffer+bytes([byte])
|
||||
if (len(command_buffer) == 10):
|
||||
if (len(command_buffer) == 12):
|
||||
lst = (command_buffer[0] << 8 | command_buffer[1])/1000.0
|
||||
lsr = command_buffer[2] << 8 | command_buffer[3]
|
||||
prs = command_buffer[4] << 8 | command_buffer[5]
|
||||
prt = command_buffer[6] << 8 | command_buffer[7]
|
||||
cst = command_buffer[8] << 8 | command_buffer[9]
|
||||
dft = command_buffer[10] << 8 | command_buffer[11]
|
||||
|
||||
if lst != self.r_symbol_time_ms or lsr != self.r_symbol_rate or prs != self.r_preamble_symbols or prt != self.r_premable_time_ms or cst != self.r_csma_slot_time_ms:
|
||||
if lst != self.r_symbol_time_ms or lsr != self.r_symbol_rate or prs != self.r_preamble_symbols or prt != self.r_premable_time_ms or cst != self.r_csma_slot_time_ms or dft != self.r_csma_difs_ms:
|
||||
self.r_symbol_time_ms = lst
|
||||
self.r_symbol_rate = lsr
|
||||
self.r_preamble_symbols = prs
|
||||
self.r_premable_time_ms = prt
|
||||
self.r_csma_slot_time_ms = cst
|
||||
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)
|
||||
self.r_csma_difs_ms = dft
|
||||
RNS.log(f"{self} Radio reporting symbol time is "+str(round(self.r_symbol_time_ms,2))+"ms ("+str(self.r_symbol_rate)+" baud)", RNS.LOG_DEBUG)
|
||||
RNS.log(f"{self} Radio reporting preamble is "+str(self.r_preamble_symbols)+" symbols ("+str(self.r_premable_time_ms)+"ms)", RNS.LOG_DEBUG)
|
||||
RNS.log(f"{self} Radio reporting CSMA slot time is "+str(self.r_csma_slot_time_ms)+"ms", RNS.LOG_DEBUG)
|
||||
RNS.log(f"{self} Radio reporting DIFS time is "+str(self.r_csma_difs_ms)+"ms", RNS.LOG_DEBUG)
|
||||
elif (command == KISS.CMD_STAT_CSMA):
|
||||
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) == 3):
|
||||
cbw = command_buffer[0]
|
||||
cbl = command_buffer[1]
|
||||
cbh = command_buffer[2]
|
||||
|
||||
if cbw != self.r_csma_cw_band or cbl != self.r_csma_cw_min or cbh != self.r_csma_cw_max:
|
||||
self.r_csma_cw_band = cbw
|
||||
self.r_csma_cw_min = cbl
|
||||
self.r_csma_cw_max = cbh
|
||||
# TODO: Remove debug
|
||||
# RNS.log(f"{self} Radio reporting contention window band is {self.r_csma_cw_band}", RNS.LOG_EXTREME)
|
||||
# RNS.log(f"{self} Radio reporting minimum contention window is {self.r_csma_cw_min}", RNS.LOG_EXTREME)
|
||||
# RNS.log(f"{self} Radio reporting maximum contention window is {self.r_csma_cw_max}", RNS.LOG_EXTREME)
|
||||
elif (command == KISS.CMD_STAT_BAT):
|
||||
if (byte == KISS.FESC):
|
||||
escape = True
|
||||
@@ -850,6 +993,9 @@ class RNodeInterface(Interface):
|
||||
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")
|
||||
@@ -861,6 +1007,36 @@ class RNodeInterface(Interface):
|
||||
raise IOError("ESP32 reset")
|
||||
elif (command == KISS.CMD_READY):
|
||||
self.process_queue()
|
||||
elif (command == KISS.CMD_FB_READ):
|
||||
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) == 512):
|
||||
self.r_framebuffer_latency = time.time() - self.r_framebuffer_readtime
|
||||
self.r_framebuffer = command_buffer
|
||||
|
||||
elif (command == KISS.CMD_DISP_READ):
|
||||
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) == 1024):
|
||||
self.r_disp_latency = time.time() - self.r_disp_readtime
|
||||
self.r_disp = command_buffer
|
||||
|
||||
elif (command == KISS.CMD_DETECT):
|
||||
if byte == KISS.DETECT_RESP:
|
||||
self.detected = True
|
||||
@@ -880,7 +1056,7 @@ class RNodeInterface(Interface):
|
||||
if self.first_tx != None:
|
||||
if time.time() > self.first_tx + self.id_interval:
|
||||
RNS.log("Interface "+str(self)+" is transmitting beacon data: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG)
|
||||
self.processOutgoing(self.id_callsign)
|
||||
self.process_outgoing(self.id_callsign)
|
||||
|
||||
sleep(0.08)
|
||||
|
||||
@@ -1006,11 +1182,13 @@ class BLEConnection():
|
||||
self.target_bt_addr = target_bt_addr
|
||||
self.scan_timeout = BLEConnection.SCAN_TIMEOUT
|
||||
self.ble_device = None
|
||||
self.last_client = None
|
||||
self.connected = False
|
||||
self.running = False
|
||||
self.should_run = False
|
||||
self.must_disconnect = False
|
||||
self.connect_job_running = False
|
||||
self.device_disappeared = False
|
||||
|
||||
import importlib
|
||||
if BLEConnection.bleak == None:
|
||||
@@ -1028,8 +1206,17 @@ class BLEConnection():
|
||||
self.should_run = True
|
||||
self.connection_thread = threading.Thread(target=self.connection_job, daemon=True).start()
|
||||
|
||||
def cleanup(self):
|
||||
try:
|
||||
if self.last_client != None:
|
||||
self.asyncio.run(self.last_client.disconnect())
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while disconnecting BLE device on cleanup for {self.owner}", RNS.LOG_ERROR)
|
||||
|
||||
self.should_run = False
|
||||
|
||||
def connection_job(self):
|
||||
while (self.should_run):
|
||||
while self.should_run:
|
||||
if self.ble_device == None:
|
||||
self.ble_device = self.find_target_device()
|
||||
|
||||
@@ -1039,6 +1226,10 @@ class BLEConnection():
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
self.cleanup()
|
||||
self.running = False
|
||||
RNS.log(f"BLE connection job for {self.owner} ended", RNS.LOG_DEBUG)
|
||||
|
||||
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)
|
||||
@@ -1052,6 +1243,7 @@ class BLEConnection():
|
||||
|
||||
self.connected = True
|
||||
self.ble_device = ble_client
|
||||
self.last_client = ble_client
|
||||
self.owner.port = str(f"ble://{ble_client.address}")
|
||||
|
||||
loop = self.asyncio.get_running_loop()
|
||||
@@ -1073,12 +1265,14 @@ class BLEConnection():
|
||||
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
|
||||
self.device_disappeared = True
|
||||
|
||||
def find_target_device(self):
|
||||
RNS.log(f"Searching for attachable BLE device for {self.owner}...", RNS.LOG_EXTREME)
|
||||
@@ -1102,7 +1296,13 @@ class BLEConnection():
|
||||
|
||||
return False
|
||||
|
||||
device = self.asyncio.run(self.bleak.BleakScanner.find_device_by_filter(device_filter, timeout=self.scan_timeout))
|
||||
device = None
|
||||
try:
|
||||
device = self.asyncio.run(self.bleak.BleakScanner.find_device_by_filter(device_filter, timeout=self.scan_timeout))
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while finding BLE device for {self.owner}: {e}", RNS.LOG_ERROR)
|
||||
self.should_run = False
|
||||
|
||||
return device
|
||||
|
||||
def device_bonded(self, device):
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import threading
|
||||
@@ -163,6 +163,7 @@ class KISS():
|
||||
|
||||
class RNodeMultiInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
CALLSIGN_MAX_LEN = 32
|
||||
|
||||
@@ -173,7 +174,7 @@ class RNodeMultiInterface(Interface):
|
||||
|
||||
MAX_SUBINTERFACES = 11
|
||||
|
||||
def __init__(self, owner, name, port, subint_config, id_interval = None, id_callsign = None):
|
||||
def __init__(self, owner, configuration):
|
||||
if RNS.vendor.platformutils.is_android():
|
||||
raise SystemError("Invalid interface type. The Android-specific RNode interface must be used on Android")
|
||||
|
||||
@@ -187,6 +188,75 @@ class RNodeMultiInterface(Interface):
|
||||
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
|
||||
count = 0
|
||||
enabled_count = 0
|
||||
|
||||
# Count how many interfaces are in the file
|
||||
for subinterface in c:
|
||||
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
|
||||
if isinstance(c[subinterface], dict):
|
||||
count += 1
|
||||
|
||||
# Count how many interfaces are enabled to allow for appropriate matrix sizing
|
||||
for subinterface in c:
|
||||
if isinstance(c[subinterface], dict):
|
||||
subinterface_config = c[subinterface]
|
||||
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
|
||||
enabled_count += 1
|
||||
|
||||
# Create an array with a row for each subinterface
|
||||
subint_config = [[0 for x in range(11)] for y in range(enabled_count)]
|
||||
subint_index = 0
|
||||
|
||||
for subinterface in c:
|
||||
if isinstance(c[subinterface], dict):
|
||||
subinterface_config = c[subinterface]
|
||||
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
|
||||
subint_config[subint_index][0] = subinterface
|
||||
|
||||
subint_vport = subinterface_config["vport"] if "vport" in subinterface_config else None
|
||||
subint_config[subint_index][1] = subint_vport
|
||||
|
||||
frequency = int(subinterface_config["frequency"]) if "frequency" in subinterface_config else None
|
||||
subint_config[subint_index][2] = frequency
|
||||
bandwidth = int(subinterface_config["bandwidth"]) if "bandwidth" in subinterface_config else None
|
||||
subint_config[subint_index][3] = bandwidth
|
||||
txpower = int(subinterface_config["txpower"]) if "txpower" in subinterface_config else None
|
||||
subint_config[subint_index][4] = txpower
|
||||
spreadingfactor = int(subinterface_config["spreadingfactor"]) if "spreadingfactor" in subinterface_config else None
|
||||
subint_config[subint_index][5] = spreadingfactor
|
||||
codingrate = int(subinterface_config["codingrate"]) if "codingrate" in subinterface_config else None
|
||||
subint_config[subint_index][6] = codingrate
|
||||
flow_control = subinterface_config.as_bool("flow_control") if "flow_control" in subinterface_config else False
|
||||
subint_config[subint_index][7] = flow_control
|
||||
st_alock = float(subinterface_config["airtime_limit_short"]) if "airtime_limit_short" in subinterface_config else None
|
||||
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
|
||||
if count == 0:
|
||||
raise ValueError("No subinterfaces configured for "+name)
|
||||
# if no subinterfaces are enabled
|
||||
elif enabled_count == 0:
|
||||
raise ValueError("No subinterfaces enabled for "+name)
|
||||
|
||||
id_interval = int(c["id_interval"]) if "id_interval" in c else None
|
||||
id_callsign = c["id_callsign"] if "id_callsign" in c else None
|
||||
port = c["port"] if "port" in c else None
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for "+name)
|
||||
|
||||
self.HW_MTU = 508
|
||||
|
||||
self.clients = 0
|
||||
@@ -494,7 +564,7 @@ class RNodeMultiInterface(Interface):
|
||||
RNS.log("Please update your RNode firmware with rnodeconf from https://github.com/markqvist/Reticulum/RNS/Utilities/rnodeconf.py")
|
||||
RNS.panic()
|
||||
|
||||
def processOutgoing(self, data, interface = None):
|
||||
def process_outgoing(self, data, interface = None):
|
||||
if interface is None:
|
||||
# do nothing if RNS tries to transmit on this interface directly
|
||||
pass
|
||||
@@ -542,7 +612,7 @@ class RNodeMultiInterface(Interface):
|
||||
command == KISS.CMD_INT10_DATA or
|
||||
command == KISS.CMD_INT11_DATA)):
|
||||
in_frame = False
|
||||
self.subinterfaces[KISS.int_data_cmd_to_index(command)].processIncoming(data_buffer)
|
||||
self.subinterfaces[KISS.int_data_cmd_to_index(command)].process_incoming(data_buffer)
|
||||
self.selected_index = KISS.int_data_cmd_to_index(command)
|
||||
data_buffer = b""
|
||||
command_buffer = b""
|
||||
@@ -821,7 +891,7 @@ class RNodeMultiInterface(Interface):
|
||||
for interface in self.subinterfaces:
|
||||
if interface != 0 and interface.online:
|
||||
interface_available = True
|
||||
self.subinterfaces[interface.index].processOutgoing(self.id_callsign)
|
||||
self.subinterfaces[interface.index].process_outgoing(self.id_callsign)
|
||||
|
||||
if interface_available:
|
||||
RNS.log("Interface "+str(self)+" is transmitting beacon data on all subinterfaces: "+str(self.id_callsign.decode("utf-8")), RNS.LOG_DEBUG)
|
||||
@@ -1127,13 +1197,13 @@ class RNodeSubInterface(Interface):
|
||||
except:
|
||||
self.bitrate = 0
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
self.r_stat_rssi = None
|
||||
self.r_stat_snr = None
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
if self.online:
|
||||
if self.interface_ready:
|
||||
if self.flow_control:
|
||||
@@ -1145,7 +1215,7 @@ class RNodeSubInterface(Interface):
|
||||
if self.parent_interface.first_tx == None:
|
||||
self.parent_interface.first_tx = time.time()
|
||||
self.txb += len(data)
|
||||
self.parent_interface.processOutgoing(data, self)
|
||||
self.parent_interface.process_outgoing(data, self)
|
||||
else:
|
||||
self.queue(data)
|
||||
|
||||
@@ -1157,7 +1227,7 @@ class RNodeSubInterface(Interface):
|
||||
if len(self.packet_queue) > 0:
|
||||
data = self.packet_queue.pop(0)
|
||||
self.interface_ready = True
|
||||
self.processOutgoing(data)
|
||||
self.process_outgoing(data)
|
||||
elif len(self.packet_queue) == 0:
|
||||
self.interface_ready = True
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
from time import sleep
|
||||
import sys
|
||||
import threading
|
||||
@@ -42,6 +42,7 @@ class HDLC():
|
||||
|
||||
class SerialInterface(Interface):
|
||||
MAX_CHUNK = 32768
|
||||
DEFAULT_IFAC_SIZE = 8
|
||||
|
||||
owner = None
|
||||
port = None
|
||||
@@ -51,7 +52,7 @@ class SerialInterface(Interface):
|
||||
stopbits = None
|
||||
serial = None
|
||||
|
||||
def __init__(self, owner, name, port, speed, databits, parity, stopbits):
|
||||
def __init__(self, owner, configuration):
|
||||
import importlib
|
||||
if importlib.util.find_spec('serial') != None:
|
||||
import serial
|
||||
@@ -62,6 +63,17 @@ class SerialInterface(Interface):
|
||||
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
parity = c["parity"] if "parity" in c else "N"
|
||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for serial interface")
|
||||
|
||||
self.HW_MTU = 564
|
||||
|
||||
self.pyserial = serial
|
||||
@@ -121,12 +133,12 @@ class SerialInterface(Interface):
|
||||
RNS.log("Serial port "+self.port+" is now open", RNS.LOG_VERBOSE)
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
if self.online:
|
||||
data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
|
||||
written = self.serial.write(data)
|
||||
@@ -149,7 +161,7 @@ class SerialInterface(Interface):
|
||||
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
|
||||
+170
-73
@@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2016-2022 Mark Qvist / unsigned.io
|
||||
# Copyright (c) 2016-2024 Mark Qvist / unsigned.io
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import platform
|
||||
@@ -30,6 +30,9 @@ import sys
|
||||
import os
|
||||
import RNS
|
||||
|
||||
class TCPInterface():
|
||||
HW_MTU = 262144
|
||||
|
||||
class HDLC():
|
||||
FLAG = 0x7E
|
||||
ESC = 0x7D
|
||||
@@ -58,8 +61,13 @@ class KISS():
|
||||
class ThreadingTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
pass
|
||||
|
||||
class ThreadingTCP6Server(socketserver.ThreadingMixIn, socketserver.TCPServer):
|
||||
address_family = socket.AF_INET6
|
||||
|
||||
class TCPClientInterface(Interface):
|
||||
BITRATE_GUESS = 10*1000*1000
|
||||
DEFAULT_IFAC_SIZE = 16
|
||||
AUTOCONFIGURE_MTU = True
|
||||
|
||||
RECONNECT_WAIT = 5
|
||||
RECONNECT_MAX_TRIES = None
|
||||
@@ -78,11 +86,21 @@ class TCPClientInterface(Interface):
|
||||
I2P_PROBE_INTERVAL = 9
|
||||
I2P_PROBES = 5
|
||||
|
||||
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False, i2p_tunneled = False, connect_timeout = None):
|
||||
def __init__(self, owner, configuration, connected_socket=None):
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
target_ip = c["target_host"] if "target_host" in c and c["target_host"] != None else None
|
||||
target_port = int(c["target_port"]) if "target_port" in c and c["target_host"] != None else None
|
||||
kiss_framing = False
|
||||
if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
|
||||
kiss_framing = True
|
||||
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
|
||||
connect_timeout = c.as_int("connect_timeout") if "connect_timeout" in c else None
|
||||
max_reconnect_tries = c.as_int("max_reconnect_tries") if "max_reconnect_tries" in c else None
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
self.HW_MTU = TCPInterface.HW_MTU
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
self.socket = None
|
||||
@@ -177,19 +195,21 @@ class TCPClientInterface(Interface):
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, TCP_KEEPIDLE, int(TCPClientInterface.I2P_PROBE_AFTER))
|
||||
|
||||
def detach(self):
|
||||
self.online = False
|
||||
if self.socket != None:
|
||||
if hasattr(self.socket, "close"):
|
||||
if callable(self.socket.close):
|
||||
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
|
||||
self.detached = True
|
||||
|
||||
try:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
if self.socket != None:
|
||||
self.socket.shutdown(socket.SHUT_RDWR)
|
||||
except Exception as e:
|
||||
RNS.log("Error while shutting down socket for "+str(self)+": "+str(e))
|
||||
|
||||
try:
|
||||
self.socket.close()
|
||||
if self.socket != None:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
RNS.log("Error while closing socket for "+str(self)+": "+str(e))
|
||||
|
||||
@@ -200,10 +220,14 @@ class TCPClientInterface(Interface):
|
||||
if initial:
|
||||
RNS.log("Establishing TCP connection for "+str(self)+"...", RNS.LOG_DEBUG)
|
||||
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
address_info = socket.getaddrinfo(self.target_ip, self.target_port, proto=socket.IPPROTO_TCP)[0]
|
||||
address_family = address_info[0]
|
||||
target_address = address_info[4]
|
||||
|
||||
self.socket = socket.socket(address_family, socket.SOCK_STREAM)
|
||||
self.socket.settimeout(TCPClientInterface.INITIAL_CONNECT_TIMEOUT)
|
||||
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
self.socket.connect((self.target_ip, self.target_port))
|
||||
self.socket.connect(target_address)
|
||||
self.socket.settimeout(None)
|
||||
self.online = True
|
||||
|
||||
@@ -265,15 +289,16 @@ class TCPClientInterface(Interface):
|
||||
RNS.log("Attempt to reconnect on a non-initiator TCP interface. This should not happen.", RNS.LOG_ERROR)
|
||||
raise IOError("Attempt to reconnect on a non-initiator TCP interface")
|
||||
|
||||
def processIncoming(self, data):
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.rxb += len(data)
|
||||
|
||||
self.owner.inbound(data, self)
|
||||
def process_incoming(self, data):
|
||||
if self.online and not self.detached:
|
||||
self.rxb += len(data)
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.rxb += len(data)
|
||||
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self, data):
|
||||
if self.online:
|
||||
def process_outgoing(self, data):
|
||||
if self.online and not self.detached:
|
||||
# while self.writing:
|
||||
# time.sleep(0.01)
|
||||
|
||||
@@ -301,22 +326,22 @@ class TCPClientInterface(Interface):
|
||||
try:
|
||||
in_frame = False
|
||||
escape = False
|
||||
frame_buffer = b""
|
||||
data_in = b""
|
||||
data_buffer = b""
|
||||
command = KISS.CMD_UNKNOWN
|
||||
|
||||
while True:
|
||||
data_in = self.socket.recv(4096)
|
||||
if len(data_in) > 0:
|
||||
pointer = 0
|
||||
while pointer < len(data_in):
|
||||
byte = data_in[pointer]
|
||||
pointer += 1
|
||||
|
||||
if self.kiss_framing:
|
||||
# Read loop for KISS framing
|
||||
if self.kiss_framing:
|
||||
# Read loop for KISS framing
|
||||
pointer = 0
|
||||
while pointer < len(data_in):
|
||||
byte = data_in[pointer]
|
||||
pointer += 1
|
||||
if (in_frame and byte == KISS.FEND and command == KISS.CMD_DATA):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
self.process_incoming(data_buffer)
|
||||
elif (byte == KISS.FEND):
|
||||
in_frame = True
|
||||
command = KISS.CMD_UNKNOWN
|
||||
@@ -339,25 +364,26 @@ class TCPClientInterface(Interface):
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
|
||||
else:
|
||||
# Read loop for HDLC framing
|
||||
if (in_frame and byte == HDLC.FLAG):
|
||||
in_frame = False
|
||||
self.processIncoming(data_buffer)
|
||||
elif (byte == HDLC.FLAG):
|
||||
in_frame = True
|
||||
data_buffer = b""
|
||||
elif (in_frame and len(data_buffer) < self.HW_MTU):
|
||||
if (byte == HDLC.ESC):
|
||||
escape = True
|
||||
else:
|
||||
# Read loop for standard HDLC framing
|
||||
frame_buffer += data_in
|
||||
flags_remaining = True
|
||||
while flags_remaining:
|
||||
frame_start = frame_buffer.find(HDLC.FLAG)
|
||||
if frame_start != -1:
|
||||
frame_end = frame_buffer.find(HDLC.FLAG, frame_start+1)
|
||||
if frame_end != -1:
|
||||
frame = frame_buffer[frame_start+1:frame_end]
|
||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
|
||||
frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
|
||||
if len(frame) > RNS.Reticulum.HEADER_MINSIZE:
|
||||
self.process_incoming(frame)
|
||||
frame_buffer = frame_buffer[frame_end:]
|
||||
else:
|
||||
if (escape):
|
||||
if (byte == HDLC.FLAG ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.FLAG
|
||||
if (byte == HDLC.ESC ^ HDLC.ESC_MASK):
|
||||
byte = HDLC.ESC
|
||||
escape = False
|
||||
data_buffer = data_buffer+bytes([byte])
|
||||
flags_remaining = False
|
||||
else:
|
||||
flags_remaining = False
|
||||
|
||||
else:
|
||||
self.online = False
|
||||
if self.initiator and not self.detached:
|
||||
@@ -394,7 +420,8 @@ class TCPClientInterface(Interface):
|
||||
self.IN = False
|
||||
|
||||
if hasattr(self, "parent_interface") and self.parent_interface != None:
|
||||
self.parent_interface.clients -= 1
|
||||
while self in self.parent_interface.spawned_interfaces:
|
||||
self.parent_interface.spawned_interfaces.remove(self)
|
||||
|
||||
if self in RNS.Transport.interfaces:
|
||||
if not self.initiator:
|
||||
@@ -402,31 +429,73 @@ class TCPClientInterface(Interface):
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "TCPInterface["+str(self.name)+"/"+str(self.target_ip)+":"+str(self.target_port)+"]"
|
||||
if ":" in self.target_ip:
|
||||
ip_str = f"[{self.target_ip}]"
|
||||
else:
|
||||
ip_str = f"{self.target_ip}"
|
||||
|
||||
return "TCPInterface["+str(self.name)+"/"+ip_str+":"+str(self.target_port)+"]"
|
||||
|
||||
|
||||
class TCPServerInterface(Interface):
|
||||
BITRATE_GUESS = 10*1000*1000
|
||||
BITRATE_GUESS = 10_000_000
|
||||
DEFAULT_IFAC_SIZE = 16
|
||||
AUTOCONFIGURE_MTU = True
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
def get_address_for_if(name, bind_port, prefer_ipv6=False):
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["addr"]
|
||||
if len(ifaddr) < 1:
|
||||
raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
|
||||
|
||||
if (prefer_ipv6 or not netinfo.AF_INET in ifaddr) and netinfo.AF_INET6 in ifaddr:
|
||||
bind_ip = ifaddr[netinfo.AF_INET6][0]["addr"]
|
||||
if bind_ip.lower().startswith("fe80::"):
|
||||
# We'll need to add the interface as scope for link-local addresses
|
||||
return TCPServerInterface.get_address_for_host(f"{bind_ip}%{name}", bind_port)
|
||||
else:
|
||||
return TCPServerInterface.get_address_for_host(bind_ip, bind_port)
|
||||
elif netinfo.AF_INET in ifaddr:
|
||||
bind_ip = ifaddr[netinfo.AF_INET][0]["addr"]
|
||||
return (bind_ip, bind_port)
|
||||
else:
|
||||
raise SystemError(f"No addresses available on specified kernel interface \"{name}\" for TCPServerInterface to bind to")
|
||||
|
||||
@staticmethod
|
||||
def get_broadcast_for_if(name):
|
||||
import RNS.vendor.ifaddr.niwrapper as netinfo
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["broadcast"]
|
||||
def get_address_for_host(name, bind_port):
|
||||
address_info = socket.getaddrinfo(name, bind_port, proto=socket.IPPROTO_TCP)[0]
|
||||
if address_info[0] == socket.AF_INET6:
|
||||
return (name, bind_port, address_info[4][2], address_info[4][3])
|
||||
elif address_info[0] == socket.AF_INET:
|
||||
return (name, bind_port)
|
||||
else:
|
||||
raise SystemError(f"No suitable kernel interface available for address \"{name}\" for TCPServerInterface to bind to")
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
|
||||
|
||||
@property
|
||||
def clients(self):
|
||||
return len(self.spawned_interfaces)
|
||||
|
||||
def __init__(self, owner, configuration):
|
||||
super().__init__()
|
||||
|
||||
self.HW_MTU = 1064
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
device = c["device"] if "device" in c else None
|
||||
port = int(c["port"]) if "port" in c else None
|
||||
bindip = c["listen_ip"] if "listen_ip" in c else None
|
||||
bindport = int(c["listen_port"]) if "listen_port" in c else None
|
||||
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
|
||||
prefer_ipv6 = c.as_bool("prefer_ipv6") if "prefer_ipv6" in c else False
|
||||
|
||||
if port != None:
|
||||
bindport = port
|
||||
|
||||
self.HW_MTU = TCPInterface.HW_MTU
|
||||
|
||||
self.online = False
|
||||
self.clients = 0
|
||||
self.spawned_interfaces = []
|
||||
|
||||
self.IN = True
|
||||
self.OUT = False
|
||||
@@ -436,24 +505,41 @@ class TCPServerInterface(Interface):
|
||||
self.i2p_tunneled = i2p_tunneled
|
||||
self.mode = RNS.Interfaces.Interface.Interface.MODE_FULL
|
||||
|
||||
if device != None:
|
||||
bindip = TCPServerInterface.get_address_for_if(device)
|
||||
|
||||
if (bindip != None and bindport != None):
|
||||
self.receives = True
|
||||
self.bind_ip = bindip
|
||||
if bindport == None:
|
||||
raise SystemError(f"No TCP port configured for interface \"{name}\"")
|
||||
else:
|
||||
self.bind_port = bindport
|
||||
|
||||
bind_address = None
|
||||
if device != None:
|
||||
bind_address = TCPServerInterface.get_address_for_if(device, self.bind_port, prefer_ipv6)
|
||||
else:
|
||||
if bindip == None:
|
||||
raise SystemError(f"No TCP bind IP configured for interface \"{name}\"")
|
||||
bind_address = TCPServerInterface.get_address_for_host(bindip, self.bind_port)
|
||||
|
||||
if bind_address != None:
|
||||
self.receives = True
|
||||
self.bind_ip = bind_address[0]
|
||||
|
||||
def handlerFactory(callback):
|
||||
def createHandler(*args, **keys):
|
||||
return TCPInterfaceHandler(callback, *args, **keys)
|
||||
return createHandler
|
||||
|
||||
self.owner = owner
|
||||
address = (self.bind_ip, self.bind_port)
|
||||
|
||||
ThreadingTCPServer.allow_reuse_address = True
|
||||
self.server = ThreadingTCPServer(address, handlerFactory(self.incoming_connection))
|
||||
if len(bind_address) == 4:
|
||||
try:
|
||||
ThreadingTCP6Server.allow_reuse_address = True
|
||||
self.server = ThreadingTCP6Server(bind_address, handlerFactory(self.incoming_connection))
|
||||
except Exception as e:
|
||||
RNS.log(f"Error while binding IPv6 socket for interface, the contained exception was: {e}", RNS.LOG_ERROR)
|
||||
raise SystemError("Could not bind IPv6 socket for interface. Please check the specified \"listen_ip\" configuration option")
|
||||
else:
|
||||
ThreadingTCPServer.allow_reuse_address = True
|
||||
self.server = ThreadingTCPServer(bind_address, handlerFactory(self.incoming_connection))
|
||||
self.server.daemon_threads = True
|
||||
|
||||
self.bitrate = TCPServerInterface.BITRATE_GUESS
|
||||
|
||||
@@ -463,17 +549,20 @@ class TCPServerInterface(Interface):
|
||||
|
||||
self.online = True
|
||||
|
||||
else:
|
||||
raise SystemError("Insufficient parameters to create TCP listener")
|
||||
|
||||
def incoming_connection(self, handler):
|
||||
RNS.log("Accepting incoming TCP connection", RNS.LOG_VERBOSE)
|
||||
interface_name = "Client on "+self.name
|
||||
spawned_interface = TCPClientInterface(self.owner, interface_name, target_ip=None, target_port=None, connected_socket=handler.request, i2p_tunneled=self.i2p_tunneled)
|
||||
spawned_configuration = {"name": "Client on "+self.name, "target_host": None, "target_port": None, "i2p_tunneled": self.i2p_tunneled}
|
||||
spawned_interface = TCPClientInterface(self.owner, spawned_configuration, connected_socket=handler.request)
|
||||
spawned_interface.OUT = self.OUT
|
||||
spawned_interface.IN = self.IN
|
||||
spawned_interface.target_ip = handler.client_address[0]
|
||||
spawned_interface.target_port = str(handler.client_address[1])
|
||||
spawned_interface.parent_interface = self
|
||||
spawned_interface.bitrate = self.bitrate
|
||||
spawned_interface.optimise_mtu()
|
||||
|
||||
spawned_interface.ifac_size = self.ifac_size
|
||||
spawned_interface.ifac_netname = self.ifac_netname
|
||||
@@ -503,7 +592,9 @@ class TCPServerInterface(Interface):
|
||||
spawned_interface.online = True
|
||||
RNS.log("Spawned new TCPClient Interface: "+str(spawned_interface), RNS.LOG_VERBOSE)
|
||||
RNS.Transport.interfaces.append(spawned_interface)
|
||||
self.clients += 1
|
||||
while spawned_interface in self.spawned_interfaces:
|
||||
self.spawned_interfaces.remove(spawned_interface)
|
||||
self.spawned_interfaces.append(spawned_interface)
|
||||
spawned_interface.read_loop()
|
||||
|
||||
def received_announce(self, from_spawned=False):
|
||||
@@ -512,18 +603,19 @@ class TCPServerInterface(Interface):
|
||||
def sent_announce(self, from_spawned=False):
|
||||
if from_spawned: self.oa_freq_deque.append(time.time())
|
||||
|
||||
def processOutgoing(self, data):
|
||||
def process_outgoing(self, data):
|
||||
pass
|
||||
|
||||
|
||||
def detach(self):
|
||||
self.detached = True
|
||||
self.online = False
|
||||
if self.server != None:
|
||||
if hasattr(self.server, "shutdown"):
|
||||
if callable(self.server.shutdown):
|
||||
try:
|
||||
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
|
||||
self.server.shutdown()
|
||||
self.detached = True
|
||||
self.server.server_close()
|
||||
self.server = None
|
||||
|
||||
except Exception as e:
|
||||
@@ -531,7 +623,12 @@ class TCPServerInterface(Interface):
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "TCPServerInterface["+self.name+"/"+self.bind_ip+":"+str(self.bind_port)+"]"
|
||||
if ":" in self.bind_ip:
|
||||
ip_str = f"[{self.bind_ip}]"
|
||||
else:
|
||||
ip_str = f"{self.bind_ip}"
|
||||
|
||||
return "TCPServerInterface["+self.name+"/"+ip_str+":"+str(self.bind_port)+"]"
|
||||
|
||||
|
||||
class TCPInterfaceHandler(socketserver.BaseRequestHandler):
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
|
||||
from .Interface import Interface
|
||||
from RNS.Interfaces.Interface import Interface
|
||||
import socketserver
|
||||
import threading
|
||||
import socket
|
||||
@@ -31,6 +31,7 @@ import RNS
|
||||
|
||||
class UDPInterface(Interface):
|
||||
BITRATE_GUESS = 10*1000*1000
|
||||
DEFAULT_IFAC_SIZE = 16
|
||||
|
||||
@staticmethod
|
||||
def get_address_for_if(name):
|
||||
@@ -44,9 +45,24 @@ class UDPInterface(Interface):
|
||||
ifaddr = netinfo.ifaddresses(name)
|
||||
return ifaddr[netinfo.AF_INET][0]["broadcast"]
|
||||
|
||||
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
|
||||
def __init__(self, owner, configuration):
|
||||
super().__init__()
|
||||
|
||||
c = Interface.get_config_obj(configuration)
|
||||
name = c["name"]
|
||||
device = c["device"] if "device" in c else None
|
||||
port = int(c["port"]) if "port" in c else None
|
||||
bindip = c["listen_ip"] if "listen_ip" in c else None
|
||||
bindport = int(c["listen_port"]) if "listen_port" in c else None
|
||||
forwardip = c["forward_ip"] if "forward_ip" in c else None
|
||||
forwardport = int(c["forward_port"]) if "forward_port" in c else None
|
||||
|
||||
if port != None:
|
||||
if bindport == None:
|
||||
bindport = port
|
||||
if forwardport == None:
|
||||
forwardport = port
|
||||
|
||||
self.HW_MTU = 1064
|
||||
|
||||
self.IN = True
|
||||
@@ -75,7 +91,7 @@ class UDPInterface(Interface):
|
||||
self.owner = owner
|
||||
address = (self.bind_ip, self.bind_port)
|
||||
socketserver.UDPServer.address_family = socket.AF_INET
|
||||
self.server = socketserver.UDPServer(address, handlerFactory(self.processIncoming))
|
||||
self.server = socketserver.UDPServer(address, handlerFactory(self.process_incoming))
|
||||
|
||||
thread = threading.Thread(target=self.server.serve_forever)
|
||||
thread.daemon = True
|
||||
@@ -89,11 +105,11 @@ class UDPInterface(Interface):
|
||||
self.forward_port = forwardport
|
||||
|
||||
|
||||
def processIncoming(self, data):
|
||||
def process_incoming(self, data):
|
||||
self.rxb += len(data)
|
||||
self.owner.inbound(data, self)
|
||||
|
||||
def processOutgoing(self,data):
|
||||
def process_outgoing(self,data):
|
||||
try:
|
||||
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
|
||||
@@ -24,5 +24,7 @@ import os
|
||||
import glob
|
||||
import RNS.Interfaces.Android
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
+123
-35
@@ -21,13 +21,14 @@
|
||||
# SOFTWARE.
|
||||
|
||||
from RNS.Cryptography import X25519PrivateKey, X25519PublicKey, Ed25519PrivateKey, Ed25519PublicKey
|
||||
from RNS.Cryptography import Fernet
|
||||
from RNS.Cryptography import Token
|
||||
from RNS.Channel import Channel, LinkChannelOutlet
|
||||
|
||||
from time import sleep
|
||||
from .vendor import umsgpack as umsgpack
|
||||
import threading
|
||||
import inspect
|
||||
import struct
|
||||
import math
|
||||
import time
|
||||
import RNS
|
||||
@@ -61,15 +62,16 @@ class Link:
|
||||
ECPUBSIZE = 32+32
|
||||
KEYSIZE = 32
|
||||
|
||||
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.FERNET_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
MDU = math.floor((RNS.Reticulum.MTU-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.TOKEN_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
|
||||
ESTABLISHMENT_TIMEOUT_PER_HOP = RNS.Reticulum.DEFAULT_PER_HOP_TIMEOUT
|
||||
"""
|
||||
Timeout for link establishment in seconds per hop to destination.
|
||||
"""
|
||||
|
||||
TRAFFIC_TIMEOUT_MIN_MS = 5
|
||||
TRAFFIC_TIMEOUT_FACTOR = 6
|
||||
LINK_MTU_SIZE = 3
|
||||
TRAFFIC_TIMEOUT_MIN_MS = 5
|
||||
TRAFFIC_TIMEOUT_FACTOR = 6
|
||||
KEEPALIVE_TIMEOUT_FACTOR = 4
|
||||
"""
|
||||
RTT timeout factor used in link timeout calculation.
|
||||
@@ -106,16 +108,46 @@ class Link:
|
||||
ACCEPT_ALL = 0x02
|
||||
resource_strategies = [ACCEPT_NONE, ACCEPT_APP, ACCEPT_ALL]
|
||||
|
||||
@staticmethod
|
||||
def mtu_bytes(mtu):
|
||||
return struct.pack(">I", mtu & 0xFFFFFF)[1:]
|
||||
|
||||
@staticmethod
|
||||
def mtu_from_lr_packet(packet):
|
||||
if len(packet.data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
|
||||
return (packet.data[Link.ECPUBSIZE] << 16) + (packet.data[Link.ECPUBSIZE+1] << 8) + (packet.data[Link.ECPUBSIZE+2])
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def mtu_from_lp_packet(packet):
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE:
|
||||
mtu_bytes = packet.data[RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE]
|
||||
return (mtu_bytes[0] << 16) + (mtu_bytes[1] << 8) + (mtu_bytes[2])
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def validate_request(owner, data, packet):
|
||||
if len(data) == (Link.ECPUBSIZE):
|
||||
if len(data) == Link.ECPUBSIZE or len(data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
|
||||
try:
|
||||
link = Link(owner = owner, peer_pub_bytes=data[:Link.ECPUBSIZE//2], peer_sig_pub_bytes=data[Link.ECPUBSIZE//2:Link.ECPUBSIZE])
|
||||
link.set_link_id(packet)
|
||||
|
||||
if len(data) == Link.ECPUBSIZE+Link.LINK_MTU_SIZE:
|
||||
RNS.log("Link request includes MTU signalling", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
try:
|
||||
link.mtu = Link.mtu_from_lr_packet(packet) or Reticulum.MTU
|
||||
except Exception as e:
|
||||
RNS.trace_exception(e)
|
||||
link.mtu = RNS.Reticulum.MTU
|
||||
|
||||
link.update_mdu()
|
||||
link.destination = packet.destination
|
||||
link.establishment_timeout = Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, packet.hops) + Link.KEEPALIVE
|
||||
link.establishment_cost += len(packet.raw)
|
||||
RNS.log("Validating link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_VERBOSE)
|
||||
RNS.log(f"Validating link request {RNS.prettyhexrep(link.link_id)}", RNS.LOG_VERBOSE)
|
||||
RNS.log(f"Link MTU configured to {RNS.prettysize(link.mtu)}", RNS.LOG_EXTREME)
|
||||
RNS.log(f"Establishment timeout is {RNS.prettytime(link.establishment_timeout)} for incoming link request "+RNS.prettyhexrep(link.link_id), RNS.LOG_EXTREME)
|
||||
link.handshake()
|
||||
link.attached_interface = packet.receiving_interface
|
||||
@@ -123,18 +155,18 @@ class Link:
|
||||
link.request_time = time.time()
|
||||
RNS.Transport.register_link(link)
|
||||
link.last_inbound = time.time()
|
||||
link.__update_phy_stats(packet, force_update=True)
|
||||
link.start_watchdog()
|
||||
|
||||
|
||||
RNS.log("Incoming link request "+str(link)+" accepted on "+str(link.attached_interface), RNS.LOG_DEBUG)
|
||||
return link
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Validating link request failed", RNS.LOG_VERBOSE)
|
||||
RNS.log("exc: "+str(e))
|
||||
RNS.log(f"Validating link request failed: {e}", RNS.LOG_VERBOSE)
|
||||
return None
|
||||
|
||||
else:
|
||||
RNS.log("Invalid link request payload size, dropping request", RNS.LOG_DEBUG)
|
||||
RNS.log(f"Invalid link request payload size of {len(data)} bytes, dropping request", RNS.LOG_DEBUG)
|
||||
return None
|
||||
|
||||
|
||||
@@ -142,10 +174,13 @@ class Link:
|
||||
if destination != None and destination.type != RNS.Destination.SINGLE:
|
||||
raise TypeError("Links can only be established to the \"single\" destination type")
|
||||
self.rtt = None
|
||||
self.mtu = RNS.Reticulum.MTU
|
||||
self.establishment_cost = 0
|
||||
self.establishment_rate = None
|
||||
self.callbacks = LinkCallbacks()
|
||||
self.resource_strategy = Link.ACCEPT_NONE
|
||||
self.last_resource_window = None
|
||||
self.last_resource_eifr = None
|
||||
self.outgoing_resources = []
|
||||
self.incoming_resources = []
|
||||
self.pending_requests = []
|
||||
@@ -188,7 +223,7 @@ class Link:
|
||||
self.prv = X25519PrivateKey.generate()
|
||||
self.sig_prv = Ed25519PrivateKey.generate()
|
||||
|
||||
self.fernet = None
|
||||
self.token = None
|
||||
|
||||
self.pub = self.prv.public_key()
|
||||
self.pub_bytes = self.pub.public_bytes()
|
||||
@@ -208,8 +243,13 @@ class Link:
|
||||
if closed_callback != None:
|
||||
self.set_link_closed_callback(closed_callback)
|
||||
|
||||
if (self.initiator):
|
||||
self.request_data = self.pub_bytes+self.sig_pub_bytes
|
||||
if self.initiator:
|
||||
link_mtu = b""
|
||||
nh_hw_mtu = RNS.Transport.next_hop_interface_hw_mtu(destination.hash)
|
||||
if RNS.Reticulum.link_mtu_discovery() and nh_hw_mtu:
|
||||
link_mtu = Link.mtu_bytes(nh_hw_mtu)
|
||||
RNS.log(f"Signalling link MTU of {RNS.prettysize(nh_hw_mtu)} for link", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
self.request_data = self.pub_bytes+self.sig_pub_bytes+link_mtu
|
||||
self.packet = RNS.Packet(destination, self.request_data, packet_type=RNS.Packet.LINKREQUEST)
|
||||
self.packet.pack()
|
||||
self.establishment_cost += len(self.packet.raw)
|
||||
@@ -233,8 +273,17 @@ class Link:
|
||||
if not hasattr(self.peer_pub, "curve"):
|
||||
self.peer_pub.curve = Link.CURVE
|
||||
|
||||
@staticmethod
|
||||
def link_id_from_lr_packet(packet):
|
||||
hashable_part = packet.get_hashable_part()
|
||||
if len(packet.data) > Link.ECPUBSIZE:
|
||||
diff = len(packet.data) - Link.ECPUBSIZE
|
||||
hashable_part = hashable_part[:-diff]
|
||||
|
||||
return RNS.Identity.truncated_hash(hashable_part)
|
||||
|
||||
def set_link_id(self, packet):
|
||||
self.link_id = packet.getTruncatedHash()
|
||||
self.link_id = Link.link_id_from_lr_packet(packet)
|
||||
self.hash = self.link_id
|
||||
|
||||
def handshake(self):
|
||||
@@ -253,10 +302,14 @@ class Link:
|
||||
|
||||
|
||||
def prove(self):
|
||||
signed_data = self.link_id+self.pub_bytes+self.sig_pub_bytes
|
||||
mtu_bytes = b""
|
||||
if self.mtu != RNS.Reticulum.MTU:
|
||||
mtu_bytes = Link.mtu_bytes(self.mtu)
|
||||
|
||||
signed_data = self.link_id+self.pub_bytes+self.sig_pub_bytes+mtu_bytes
|
||||
signature = self.owner.identity.sign(signed_data)
|
||||
|
||||
proof_data = signature+self.pub_bytes
|
||||
proof_data = signature+self.pub_bytes+mtu_bytes
|
||||
proof = RNS.Packet(self, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.LRPROOF)
|
||||
proof.send()
|
||||
self.establishment_cost += len(proof.raw)
|
||||
@@ -279,6 +332,14 @@ class Link:
|
||||
def validate_proof(self, packet):
|
||||
try:
|
||||
if self.status == Link.PENDING:
|
||||
mtu_bytes = b""
|
||||
confirmed_mtu = None
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2+Link.LINK_MTU_SIZE:
|
||||
confirmed_mtu = Link.mtu_from_lp_packet(packet)
|
||||
mtu_bytes = Link.mtu_bytes(confirmed_mtu)
|
||||
packet.data = packet.data[:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2]
|
||||
RNS.log(f"Destination confirmed link MTU of {RNS.prettysize(confirmed_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
|
||||
if self.initiator and len(packet.data) == RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2:
|
||||
peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+Link.ECPUBSIZE//2]
|
||||
peer_sig_pub_bytes = self.destination.identity.get_public_key()[Link.ECPUBSIZE//2:Link.ECPUBSIZE]
|
||||
@@ -286,7 +347,7 @@ class Link:
|
||||
self.handshake()
|
||||
|
||||
self.establishment_cost += len(packet.raw)
|
||||
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes
|
||||
signed_data = self.link_id+self.peer_pub_bytes+self.peer_sig_pub_bytes+mtu_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if self.destination.identity.validate(signature, signed_data):
|
||||
@@ -296,6 +357,8 @@ class Link:
|
||||
self.rtt = time.time() - self.request_time
|
||||
self.attached_interface = packet.receiving_interface
|
||||
self.__remote_identity = self.destination.identity
|
||||
self.mtu = confirmed_mtu or RNS.Reticulum.MTU
|
||||
self.update_mdu()
|
||||
self.status = Link.ACTIVE
|
||||
self.activated_at = time.time()
|
||||
self.last_proof = self.activated_at
|
||||
@@ -309,6 +372,7 @@ class Link:
|
||||
rtt_packet = RNS.Packet(self, rtt_data, context=RNS.Packet.LRRTT)
|
||||
rtt_packet.send()
|
||||
self.had_outbound()
|
||||
self.__update_phy_stats(packet)
|
||||
|
||||
if self.callbacks.link_established != None:
|
||||
thread = threading.Thread(target=self.callbacks.link_established, args=(self,))
|
||||
@@ -360,7 +424,7 @@ class Link:
|
||||
if timeout == None:
|
||||
timeout = self.rtt * self.traffic_timeout_factor + RNS.Resource.RESPONSE_MAX_GRACE_TIME*1.125
|
||||
|
||||
if len(packed_request) <= Link.MDU:
|
||||
if len(packed_request) <= self.mdu:
|
||||
request_packet = RNS.Packet(self, packed_request, RNS.Packet.DATA, context = RNS.Packet.REQUEST)
|
||||
packet_receipt = request_packet.send()
|
||||
|
||||
@@ -394,6 +458,10 @@ class Link:
|
||||
)
|
||||
|
||||
|
||||
def update_mdu(self):
|
||||
self.mdu = self.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE
|
||||
self.mdu = math.floor((self.mtu-RNS.Reticulum.IFAC_MIN_SIZE-RNS.Reticulum.HEADER_MINSIZE-RNS.Identity.TOKEN_OVERHEAD)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
|
||||
def rtt_packet(self, packet):
|
||||
try:
|
||||
measured_rtt = time.time() - self.request_time
|
||||
@@ -435,19 +503,28 @@ class Link:
|
||||
"""
|
||||
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||
"""
|
||||
return self.rssi
|
||||
if self.__track_phy_stats:
|
||||
return self.rssi
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_snr(self):
|
||||
"""
|
||||
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||
"""
|
||||
return self.rssi
|
||||
if self.__track_phy_stats:
|
||||
return self.snr
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_q(self):
|
||||
"""
|
||||
:returns: The physical layer *Link Quality* if available, otherwise ``None``. Physical layer statistics must be enabled on the link for this method to return a value.
|
||||
"""
|
||||
return self.rssi
|
||||
if self.__track_phy_stats:
|
||||
return self.q
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_establishment_rate(self):
|
||||
"""
|
||||
@@ -640,9 +717,14 @@ class Link:
|
||||
|
||||
sleep(sleep_time)
|
||||
|
||||
if not self.__track_phy_stats:
|
||||
self.rssi = None
|
||||
self.snr = None
|
||||
self.q = None
|
||||
|
||||
def __update_phy_stats(self, packet, query_shared = True):
|
||||
if self.__track_phy_stats:
|
||||
|
||||
def __update_phy_stats(self, packet, query_shared = True, force_update = False):
|
||||
if self.__track_phy_stats or force_update:
|
||||
if query_shared:
|
||||
reticulum = RNS.Reticulum.get_instance()
|
||||
if packet.rssi == None: packet.rssi = reticulum.get_packet_rssi(packet.packet_hash)
|
||||
@@ -694,7 +776,7 @@ class Link:
|
||||
if response != None:
|
||||
packed_response = umsgpack.packb([request_id, response])
|
||||
|
||||
if len(packed_response) <= Link.MDU:
|
||||
if len(packed_response) <= self.mdu:
|
||||
RNS.Packet(self, packed_response, RNS.Packet.DATA, context = RNS.Packet.RESPONSE).send()
|
||||
else:
|
||||
response_resource = RNS.Resource(packed_response, self, request_id = request_id, is_response = True)
|
||||
@@ -778,6 +860,8 @@ class Link:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
packet.ratchet_id = self.link_id
|
||||
if plaintext != None:
|
||||
self.__update_phy_stats(packet, query_shared=True)
|
||||
|
||||
if self.callbacks.packet != None:
|
||||
thread = threading.Thread(target=self.callbacks.packet, args=(plaintext, packet))
|
||||
thread.daemon = True
|
||||
@@ -785,19 +869,15 @@ class Link:
|
||||
|
||||
if self.destination.proof_strategy == RNS.Destination.PROVE_ALL:
|
||||
packet.prove()
|
||||
should_query = True
|
||||
|
||||
elif self.destination.proof_strategy == RNS.Destination.PROVE_APP:
|
||||
if self.destination.callbacks.proof_requested:
|
||||
try:
|
||||
if self.destination.callbacks.proof_requested(packet):
|
||||
packet.prove()
|
||||
should_query = True
|
||||
except Exception as e:
|
||||
RNS.log("Error while executing proof request callback from "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
self.__update_phy_stats(packet, query_shared=should_query)
|
||||
|
||||
elif packet.context == RNS.Packet.LINKIDENTIFY:
|
||||
plaintext = self.decrypt(packet.data)
|
||||
if plaintext != None:
|
||||
@@ -966,14 +1046,14 @@ class Link:
|
||||
|
||||
def encrypt(self, plaintext):
|
||||
try:
|
||||
if not self.fernet:
|
||||
if not self.token:
|
||||
try:
|
||||
self.fernet = Fernet(self.derived_key)
|
||||
self.token = Token(self.derived_key)
|
||||
except Exception as e:
|
||||
RNS.log("Could not instantiate Fernet while performin encryption on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
RNS.log("Could not instantiate token while performing encryption on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
raise e
|
||||
|
||||
return self.fernet.encrypt(plaintext)
|
||||
return self.token.encrypt(plaintext)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Encryption on link "+str(self)+" failed. The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -982,10 +1062,10 @@ class Link:
|
||||
|
||||
def decrypt(self, ciphertext):
|
||||
try:
|
||||
if not self.fernet:
|
||||
self.fernet = Fernet(self.derived_key)
|
||||
if not self.token:
|
||||
self.token = Token(self.derived_key)
|
||||
|
||||
return self.fernet.decrypt(ciphertext)
|
||||
return self.token.decrypt(ciphertext)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Decryption failed on link "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -1063,6 +1143,8 @@ class Link:
|
||||
|
||||
def resource_concluded(self, resource):
|
||||
if resource in self.incoming_resources:
|
||||
self.last_resource_window = resource.window
|
||||
self.last_resource_eifr = resource.eifr
|
||||
self.incoming_resources.remove(resource)
|
||||
if resource in self.outgoing_resources:
|
||||
self.outgoing_resources.remove(resource)
|
||||
@@ -1092,6 +1174,12 @@ class Link:
|
||||
|
||||
return False
|
||||
|
||||
def get_last_resource_window(self):
|
||||
return self.last_resource_window
|
||||
|
||||
def get_last_resource_eifr(self):
|
||||
return self.last_resource_eifr
|
||||
|
||||
def cancel_outgoing_resource(self, resource):
|
||||
if resource in self.outgoing_resources:
|
||||
self.outgoing_resources.remove(resource)
|
||||
|
||||
+37
-2
@@ -95,7 +95,7 @@ class Packet:
|
||||
# With an MTU of 500, the maximum of data we can
|
||||
# send in a single encrypted packet is given by
|
||||
# the below calculation; 383 bytes.
|
||||
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.FERNET_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
ENCRYPTED_MDU = math.floor((RNS.Reticulum.MDU-RNS.Identity.TOKEN_OVERHEAD-RNS.Identity.KEYSIZE//16)/RNS.Identity.AES128_BLOCKSIZE)*RNS.Identity.AES128_BLOCKSIZE - 1
|
||||
"""
|
||||
The maximum size of the payload data in a single encrypted packet
|
||||
"""
|
||||
@@ -137,7 +137,11 @@ class Packet:
|
||||
self.fromPacked = True
|
||||
self.create_receipt = False
|
||||
|
||||
self.MTU = RNS.Reticulum.MTU
|
||||
if destination and destination.type == RNS.Destination.LINK:
|
||||
self.MTU = destination.mtu
|
||||
else:
|
||||
self.MTU = RNS.Reticulum.MTU
|
||||
|
||||
self.sent_at = None
|
||||
self.packet_hash = None
|
||||
self.ratchet_id = None
|
||||
@@ -341,6 +345,33 @@ class Packet:
|
||||
|
||||
return hashable_part
|
||||
|
||||
def get_rssi(self):
|
||||
"""
|
||||
:returns: The physical layer *Received Signal Strength Indication* if available, otherwise ``None``.
|
||||
"""
|
||||
if self.rssi != None:
|
||||
return self.rssi
|
||||
else:
|
||||
return reticulum.get_packet_rssi(self.packet_hash)
|
||||
|
||||
def get_snr(self):
|
||||
"""
|
||||
:returns: The physical layer *Signal-to-Noise Ratio* if available, otherwise ``None``.
|
||||
"""
|
||||
if self.snr != None:
|
||||
return self.snr
|
||||
else:
|
||||
return reticulum.get_packet_snr(self.packet_hash)
|
||||
|
||||
def get_q(self):
|
||||
"""
|
||||
:returns: The physical layer *Link Quality* if available, otherwise ``None``.
|
||||
"""
|
||||
if self.q != None:
|
||||
return self.q
|
||||
else:
|
||||
return reticulum.get_packet_q(self.packet_hash)
|
||||
|
||||
class ProofDestination:
|
||||
def __init__(self, packet):
|
||||
self.hash = packet.get_hash()[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8];
|
||||
@@ -474,6 +505,10 @@ class PacketReceipt:
|
||||
return False
|
||||
elif len(proof) == PacketReceipt.IMPL_LENGTH:
|
||||
# This is an implicit proof
|
||||
|
||||
if not hasattr(self.destination, "identity"):
|
||||
return False
|
||||
|
||||
if self.destination.identity == None:
|
||||
return False
|
||||
|
||||
|
||||
+113
-26
@@ -118,6 +118,7 @@ class Resource:
|
||||
|
||||
PART_TIMEOUT_FACTOR = 4
|
||||
PART_TIMEOUT_FACTOR_AFTER_RTT = 2
|
||||
PROOF_TIMEOUT_FACTOR = 3
|
||||
MAX_RETRIES = 16
|
||||
MAX_ADV_RETRIES = 4
|
||||
SENDER_GRACE_TIME = 10.0
|
||||
@@ -160,9 +161,9 @@ 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.total_parts = int(math.ceil(resource.size/float(resource.sdu)))
|
||||
resource.received_count = 0
|
||||
resource.outstanding_parts = 0
|
||||
resource.parts = [None] * resource.total_parts
|
||||
@@ -185,6 +186,13 @@ class Resource:
|
||||
resource.waiting_for_hmu = False
|
||||
resource.receiving_part = False
|
||||
resource.consecutive_completed_height = -1
|
||||
|
||||
previous_window = resource.link.get_last_resource_window()
|
||||
previous_eifr = resource.link.get_last_resource_eifr()
|
||||
if previous_window:
|
||||
resource.window = previous_window
|
||||
if previous_eifr:
|
||||
resource.previous_eifr = previous_eifr
|
||||
|
||||
if not resource.link.has_incoming_resource(resource):
|
||||
resource.link.register_incoming_resource(resource)
|
||||
@@ -233,7 +241,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
|
||||
@@ -254,7 +261,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
|
||||
@@ -272,6 +278,10 @@ class Resource:
|
||||
|
||||
self.status = Resource.NONE
|
||||
self.link = link
|
||||
if self.link.mtu:
|
||||
self.sdu = self.link.mtu - RNS.Reticulum.HEADER_MAXSIZE - RNS.Reticulum.IFAC_MIN_SIZE
|
||||
else:
|
||||
self.sdu = link.mdu or Resource.SDU
|
||||
self.max_retries = Resource.MAX_RETRIES
|
||||
self.max_adv_retries = Resource.MAX_ADV_RETRIES
|
||||
self.retries_left = self.max_retries
|
||||
@@ -287,10 +297,14 @@ class Resource:
|
||||
self.req_sent = 0
|
||||
self.req_resp_rtt_rate = 0
|
||||
self.rtt_rxd_bytes_at_part_req = 0
|
||||
self.req_data_rtt_rate = 0
|
||||
self.eifr = None
|
||||
self.previous_eifr = None
|
||||
self.fast_rate_rounds = 0
|
||||
self.very_slow_rate_rounds = 0
|
||||
self.request_id = request_id
|
||||
self.is_response = is_response
|
||||
self.auto_compress = auto_compress
|
||||
|
||||
self.req_hashlist = []
|
||||
self.receiver_min_consecutive_height = 0
|
||||
@@ -347,7 +361,8 @@ class Resource:
|
||||
|
||||
self.size = len(self.data)
|
||||
self.sent_parts = 0
|
||||
hashmap_entries = int(math.ceil(self.size/float(Resource.SDU)))
|
||||
hashmap_entries = int(math.ceil(self.size/float(self.sdu)))
|
||||
self.total_parts = hashmap_entries
|
||||
|
||||
hashmap_ok = False
|
||||
while not hashmap_ok:
|
||||
@@ -368,7 +383,7 @@ class Resource:
|
||||
self.hashmap = b""
|
||||
collision_guard_list = []
|
||||
for i in range(0,hashmap_entries):
|
||||
data = self.data[i*Resource.SDU:(i+1)*Resource.SDU]
|
||||
data = self.data[i*self.sdu:(i+1)*self.sdu]
|
||||
map_hash = self.get_map_hash(data)
|
||||
|
||||
if map_hash in collision_guard_list:
|
||||
@@ -455,6 +470,22 @@ class Resource:
|
||||
|
||||
self.watchdog_job()
|
||||
|
||||
def update_eifr(self):
|
||||
if self.rtt == None:
|
||||
rtt = self.link.rtt
|
||||
else:
|
||||
rtt = self.rtt
|
||||
|
||||
if self.req_data_rtt_rate != 0:
|
||||
expected_inflight_rate = self.req_data_rtt_rate*8
|
||||
else:
|
||||
if self.previous_eifr != None:
|
||||
expected_inflight_rate = self.previous_eifr
|
||||
else:
|
||||
expected_inflight_rate = self.link.establishment_cost*8 / rtt
|
||||
|
||||
self.eifr = expected_inflight_rate
|
||||
|
||||
def watchdog_job(self):
|
||||
thread = threading.Thread(target=self.__watchdog_job)
|
||||
thread.daemon = True
|
||||
@@ -469,7 +500,6 @@ class Resource:
|
||||
sleep(0.025)
|
||||
|
||||
sleep_time = None
|
||||
|
||||
if self.status == Resource.ADVERTISED:
|
||||
sleep_time = (self.adv_sent+self.timeout+Resource.PROCESSING_GRACE)-time.time()
|
||||
if sleep_time < 0:
|
||||
@@ -493,17 +523,19 @@ class Resource:
|
||||
|
||||
elif self.status == Resource.TRANSFERRING:
|
||||
if not self.initiator:
|
||||
|
||||
if self.rtt == None:
|
||||
rtt = self.link.rtt
|
||||
else:
|
||||
rtt = self.rtt
|
||||
|
||||
window_remaining = self.outstanding_parts
|
||||
|
||||
retries_used = self.max_retries - self.retries_left
|
||||
extra_wait = retries_used * Resource.PER_RETRY_DELAY
|
||||
sleep_time = self.last_activity + (rtt*(self.part_timeout_factor+window_remaining)) + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
|
||||
|
||||
self.update_eifr()
|
||||
expected_tof_remaining = (self.outstanding_parts*self.sdu*8)/self.eifr
|
||||
|
||||
if self.req_resp_rtt_rate != 0:
|
||||
sleep_time = self.last_activity + self.part_timeout_factor*expected_tof_remaining + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
|
||||
else:
|
||||
sleep_time = self.last_activity + self.part_timeout_factor*((3*self.sdu)/self.eifr) + Resource.RETRY_GRACE_TIME + extra_wait - time.time()
|
||||
|
||||
# RNS.log(f"EIFR {RNS.prettyspeed(self.eifr)}, ETOF {RNS.prettyshorttime(expected_tof_remaining)} ", RNS.LOG_DEBUG, pt=True)
|
||||
# RNS.log(f"Resource ST {RNS.prettyshorttime(sleep_time)}, RTT {RNS.prettyshorttime(self.rtt or self.link.rtt)}, {self.outstanding_parts} left", RNS.LOG_DEBUG, pt=True)
|
||||
|
||||
if sleep_time < 0:
|
||||
if self.retries_left > 0:
|
||||
@@ -533,6 +565,10 @@ class Resource:
|
||||
sleep_time = 0.001
|
||||
|
||||
elif self.status == Resource.AWAITING_PROOF:
|
||||
# Decrease timeout factor since proof packets are
|
||||
# significantly smaller than full req/resp roundtrip
|
||||
self.timeout_factor = Resource.PROOF_TIMEOUT_FACTOR
|
||||
|
||||
sleep_time = self.last_part_sent + (self.rtt*self.timeout_factor+self.sender_grace_time) - time.time()
|
||||
if sleep_time < 0:
|
||||
if self.retries_left <= 0:
|
||||
@@ -550,7 +586,7 @@ class Resource:
|
||||
sleep_time = 0.001
|
||||
|
||||
if sleep_time == 0:
|
||||
RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_WARNING)
|
||||
RNS.log("Warning! Link watchdog sleep time of 0!", RNS.LOG_DEBUG)
|
||||
if sleep_time == None or sleep_time < 0:
|
||||
RNS.log("Timing error, cancelling resource transfer.", RNS.LOG_ERROR)
|
||||
self.cancel()
|
||||
@@ -624,6 +660,7 @@ class Resource:
|
||||
proof_data = self.hash+proof
|
||||
proof_packet = RNS.Packet(self.link, proof_data, packet_type=RNS.Packet.PROOF, context=RNS.Packet.RESOURCE_PRF)
|
||||
proof_packet.send()
|
||||
RNS.Transport.cache(proof_packet, force_cache=True)
|
||||
except Exception as e:
|
||||
RNS.log("Could not send proof packet, cancelling resource", RNS.LOG_DEBUG)
|
||||
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
|
||||
@@ -642,6 +679,7 @@ class Resource:
|
||||
request_id = self.request_id,
|
||||
is_response = self.is_response,
|
||||
advertise = False,
|
||||
auto_compress = self.auto_compress,
|
||||
)
|
||||
|
||||
def validate_proof(self, proof_data):
|
||||
@@ -765,6 +803,7 @@ class Resource:
|
||||
|
||||
if rtt != 0:
|
||||
self.req_data_rtt_rate = req_transferred/rtt
|
||||
self.update_eifr()
|
||||
self.rtt_rxd_bytes_at_part_req = self.rtt_rxd_bytes
|
||||
|
||||
if self.req_data_rtt_rate > Resource.RATE_FAST and self.fast_rate_rounds < Resource.FAST_RATE_THRESHOLD:
|
||||
@@ -920,6 +959,7 @@ class Resource:
|
||||
|
||||
if self.sent_parts == len(self.parts):
|
||||
self.status = Resource.AWAITING_PROOF
|
||||
self.retries_left = 3
|
||||
|
||||
if self.__progress_callback != None:
|
||||
try:
|
||||
@@ -963,21 +1003,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/self.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/self.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.
|
||||
|
||||
+212
-534
@@ -29,13 +29,14 @@ if get_platform() == "android":
|
||||
from .Interfaces import TCPInterface
|
||||
from .Interfaces import UDPInterface
|
||||
from .Interfaces import I2PInterface
|
||||
from .Interfaces import RNodeMultiInterface
|
||||
from .Interfaces.Android import RNodeInterface
|
||||
from .Interfaces.Android import SerialInterface
|
||||
from .Interfaces.Android import KISSInterface
|
||||
else:
|
||||
from .Interfaces import *
|
||||
from RNS.Interfaces import *
|
||||
|
||||
from .vendor.configobj import ConfigObj
|
||||
from RNS.vendor.configobj import ConfigObj
|
||||
import configparser
|
||||
import multiprocessing.connection
|
||||
import signal
|
||||
@@ -88,6 +89,16 @@ class Reticulum:
|
||||
the default value.
|
||||
"""
|
||||
|
||||
LINK_MTU_DISCOVERY = False
|
||||
"""
|
||||
Whether automatic link MTU discovery is enabled by default in this
|
||||
release. Link MTU discovery significantly increases throughput over
|
||||
fast links, but requires all intermediary hops to also support it.
|
||||
Support for this feature was added in RNS version 0.9.0. This option
|
||||
will become enabled by default in the near future. Please update your
|
||||
RNS instances.
|
||||
"""
|
||||
|
||||
MAX_QUEUED_ANNOUNCES = 16384
|
||||
QUEUED_ANNOUNCE_LIFE = 60*60*24
|
||||
|
||||
@@ -147,34 +158,37 @@ class Reticulum:
|
||||
configpath = ""
|
||||
storagepath = ""
|
||||
cachepath = ""
|
||||
interfacepath = ""
|
||||
|
||||
__instance = None
|
||||
|
||||
|
||||
__interface_detach_ran = False
|
||||
@staticmethod
|
||||
def exit_handler():
|
||||
# This exit handler is called whenever Reticulum is asked to
|
||||
# shut down, and will in turn call exit handlers in other
|
||||
# classes, saving necessary information to disk and carrying
|
||||
# out cleanup operations.
|
||||
|
||||
if not Reticulum.__interface_detach_ran:
|
||||
RNS.Transport.detach_interfaces()
|
||||
RNS.Transport.exit_handler()
|
||||
RNS.Identity.exit_handler()
|
||||
|
||||
if RNS.profiler_ran:
|
||||
RNS.profiler_results()
|
||||
if RNS.Profiler.ran():
|
||||
RNS.Profiler.results()
|
||||
|
||||
@staticmethod
|
||||
def sigint_handler(signal, frame):
|
||||
RNS.Transport.detach_interfaces()
|
||||
Reticulum.__interface_detach_ran = True
|
||||
RNS.exit()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def sigterm_handler(signal, frame):
|
||||
RNS.Transport.detach_interfaces()
|
||||
Reticulum.__interface_detach_ran = True
|
||||
RNS.exit()
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_instance():
|
||||
"""
|
||||
@@ -182,7 +196,7 @@ class Reticulum:
|
||||
"""
|
||||
return Reticulum.__instance
|
||||
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None):
|
||||
def __init__(self,configdir=None, loglevel=None, logdest=None, verbosity=None, require_shared_instance=False):
|
||||
"""
|
||||
Initialises and starts a Reticulum instance. This must be
|
||||
done before any other operations, and Reticulum will not
|
||||
@@ -211,14 +225,19 @@ class Reticulum:
|
||||
if logdest == RNS.LOG_FILE:
|
||||
RNS.logdest = RNS.LOG_FILE
|
||||
RNS.logfile = Reticulum.configdir+"/logfile"
|
||||
elif callable(logdest):
|
||||
RNS.logdest = RNS.LOG_CALLBACK
|
||||
RNS.logcall = logdest
|
||||
|
||||
Reticulum.configpath = Reticulum.configdir+"/config"
|
||||
Reticulum.storagepath = Reticulum.configdir+"/storage"
|
||||
Reticulum.cachepath = Reticulum.configdir+"/storage/cache"
|
||||
Reticulum.resourcepath = Reticulum.configdir+"/storage/resources"
|
||||
Reticulum.identitypath = Reticulum.configdir+"/storage/identities"
|
||||
Reticulum.interfacepath = Reticulum.configdir+"/interfaces"
|
||||
|
||||
Reticulum.__transport_enabled = False
|
||||
Reticulum.__link_mtu_discovery = Reticulum.LINK_MTU_DISCOVERY
|
||||
Reticulum.__remote_management_enabled = False
|
||||
Reticulum.__use_implicit_proof = True
|
||||
Reticulum.__allow_probes = False
|
||||
@@ -244,6 +263,8 @@ class Reticulum:
|
||||
RNS.loglevel = self.requested_loglevel
|
||||
|
||||
self.is_shared_instance = False
|
||||
self.shared_instance_interface = None
|
||||
self.require_shared = require_shared_instance
|
||||
self.is_connected_to_shared_instance = False
|
||||
self.is_standalone_instance = False
|
||||
self.jobs_thread = None
|
||||
@@ -262,6 +283,9 @@ class Reticulum:
|
||||
if not os.path.isdir(Reticulum.identitypath):
|
||||
os.makedirs(Reticulum.identitypath)
|
||||
|
||||
if not os.path.isdir(Reticulum.interfacepath):
|
||||
os.makedirs(Reticulum.interfacepath)
|
||||
|
||||
if os.path.isfile(self.configpath):
|
||||
try:
|
||||
self.config = ConfigObj(self.configpath)
|
||||
@@ -276,7 +300,8 @@ class Reticulum:
|
||||
time.sleep(1.5)
|
||||
|
||||
self.__apply_config()
|
||||
RNS.log("Configuration loaded from "+self.configpath, RNS.LOG_VERBOSE)
|
||||
RNS.log(f"Utilising cryptography backend \"{RNS.Cryptography.Provider.backend()}\"", RNS.LOG_VERBOSE)
|
||||
RNS.log(f"Configuration loaded from {self.configpath}", RNS.LOG_VERBOSE)
|
||||
|
||||
RNS.Identity.load_known_destinations()
|
||||
|
||||
@@ -328,11 +353,19 @@ class Reticulum:
|
||||
interface.bitrate = Reticulum._force_shared_instance_bitrate
|
||||
interface._force_bitrate = Reticulum._force_shared_instance_bitrate
|
||||
RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING)
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
interface.optimise_mtu()
|
||||
|
||||
self.is_shared_instance = True
|
||||
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
|
||||
self.__start_jobs()
|
||||
if self.require_shared == True:
|
||||
interface.detach()
|
||||
self.is_shared_instance = True
|
||||
RNS.log("Existing shared instance required, but this instance started as shared instance. Aborting startup.", RNS.LOG_VERBOSE)
|
||||
|
||||
else:
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
self.shared_instance_interface = interface
|
||||
self.is_shared_instance = True
|
||||
RNS.log("Started shared instance interface: "+str(interface), RNS.LOG_DEBUG)
|
||||
self.__start_jobs()
|
||||
|
||||
except Exception as e:
|
||||
try:
|
||||
@@ -346,6 +379,7 @@ class Reticulum:
|
||||
interface.bitrate = Reticulum._force_shared_instance_bitrate
|
||||
interface._force_bitrate = True
|
||||
RNS.log(f"Forcing shared instance bitrate of {RNS.prettyspeed(interface.bitrate)}", RNS.LOG_WARNING)
|
||||
interface.optimise_mtu()
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
self.is_shared_instance = False
|
||||
self.is_standalone_instance = False
|
||||
@@ -360,6 +394,10 @@ class Reticulum:
|
||||
self.is_shared_instance = False
|
||||
self.is_standalone_instance = True
|
||||
self.is_connected_to_shared_instance = False
|
||||
|
||||
if self.is_shared_instance and self.require_shared:
|
||||
raise SystemError("No shared instance available, but application that started Reticulum required it")
|
||||
|
||||
else:
|
||||
self.is_shared_instance = False
|
||||
self.is_standalone_instance = True
|
||||
@@ -402,6 +440,10 @@ class Reticulum:
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.__transport_enabled = True
|
||||
if option == "link_mtu_discovery":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
Reticulum.__link_mtu_discovery = True
|
||||
if option == "enable_remote_management":
|
||||
v = self.config["reticulum"].as_bool(option)
|
||||
if v == True:
|
||||
@@ -445,14 +487,7 @@ class Reticulum:
|
||||
if "interfaces" in self.config:
|
||||
for name in self.config["interfaces"]:
|
||||
if not name in interface_names:
|
||||
# TODO: We really need to generalise this way of instantiating
|
||||
# and configuring interfaces. Ideally, interfaces should just
|
||||
# have a conrfig dict passed to their init method, and return
|
||||
# a ready interface, onto which this routine can configure any
|
||||
# generic or extra parameters.
|
||||
|
||||
c = self.config["interfaces"][name]
|
||||
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
if "interface_mode" in c:
|
||||
@@ -555,516 +590,23 @@ class Reticulum:
|
||||
announce_cap = c.as_float("announce_cap")/100.0
|
||||
|
||||
try:
|
||||
interface = None
|
||||
|
||||
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
|
||||
if c["type"] == "AutoInterface":
|
||||
group_id = c["group_id"] if "group_id" in c else None
|
||||
discovery_scope = c["discovery_scope"] if "discovery_scope" in c else None
|
||||
discovery_port = int(c["discovery_port"]) if "discovery_port" in c else None
|
||||
multicast_address_type = c["multicast_address_type"] if "multicast_address_type" in c else None
|
||||
data_port = int(c["data_port"]) if "data_port" in c else None
|
||||
allowed_interfaces = c.as_list("devices") if "devices" in c else None
|
||||
ignored_interfaces = c.as_list("ignored_devices") if "ignored_devices" in c else None
|
||||
|
||||
interface = AutoInterface.AutoInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
group_id,
|
||||
discovery_scope,
|
||||
discovery_port,
|
||||
multicast_address_type,
|
||||
data_port,
|
||||
allowed_interfaces,
|
||||
ignored_interfaces
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 16
|
||||
|
||||
if c["type"] == "UDPInterface":
|
||||
device = c["device"] if "device" in c else None
|
||||
port = int(c["port"]) if "port" in c else None
|
||||
listen_ip = c["listen_ip"] if "listen_ip" in c else None
|
||||
listen_port = int(c["listen_port"]) if "listen_port" in c else None
|
||||
forward_ip = c["forward_ip"] if "forward_ip" in c else None
|
||||
forward_port = int(c["forward_port"]) if "forward_port" in c else None
|
||||
|
||||
if port != None:
|
||||
if listen_port == None:
|
||||
listen_port = port
|
||||
if forward_port == None:
|
||||
forward_port = port
|
||||
|
||||
interface = UDPInterface.UDPInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
device,
|
||||
listen_ip,
|
||||
listen_port,
|
||||
forward_ip,
|
||||
forward_port
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 16
|
||||
|
||||
if c["type"] == "TCPServerInterface":
|
||||
device = c["device"] if "device" in c else None
|
||||
port = int(c["port"]) if "port" in c else None
|
||||
listen_ip = c["listen_ip"] if "listen_ip" in c else None
|
||||
listen_port = int(c["listen_port"]) if "listen_port" in c else None
|
||||
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
|
||||
|
||||
if port != None:
|
||||
listen_port = port
|
||||
|
||||
interface = TCPInterface.TCPServerInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
device,
|
||||
listen_ip,
|
||||
listen_port,
|
||||
i2p_tunneled
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 16
|
||||
|
||||
if c["type"] == "TCPClientInterface":
|
||||
kiss_framing = False
|
||||
if "kiss_framing" in c and c.as_bool("kiss_framing") == True:
|
||||
kiss_framing = True
|
||||
i2p_tunneled = c.as_bool("i2p_tunneled") if "i2p_tunneled" in c else False
|
||||
tcp_connect_timeout = c.as_int("connect_timeout") if "connect_timeout" in c else None
|
||||
|
||||
|
||||
interface = TCPInterface.TCPClientInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
c["target_host"],
|
||||
int(c["target_port"]),
|
||||
kiss_framing = kiss_framing,
|
||||
i2p_tunneled = i2p_tunneled,
|
||||
connect_timeout = tcp_connect_timeout,
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 16
|
||||
|
||||
if c["type"] == "I2PInterface":
|
||||
i2p_peers = c.as_list("peers") if "peers" in c else None
|
||||
connectable = c.as_bool("connectable") if "connectable" in c else False
|
||||
|
||||
if ifac_size == None:
|
||||
ifac_size = 16
|
||||
|
||||
interface = I2PInterface.I2PInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
Reticulum.storagepath,
|
||||
i2p_peers,
|
||||
connectable = connectable,
|
||||
ifac_size = ifac_size,
|
||||
ifac_netname = ifac_netname,
|
||||
ifac_netkey = ifac_netkey,
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
|
||||
if c["type"] == "SerialInterface":
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
parity = c["parity"] if "parity" in c else "N"
|
||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for serial interface")
|
||||
|
||||
interface = SerialInterface.SerialInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
port,
|
||||
speed,
|
||||
databits,
|
||||
parity,
|
||||
stopbits
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 8
|
||||
|
||||
if c["type"] == "PipeInterface":
|
||||
command = c["command"] if "command" in c else None
|
||||
respawn_delay = c.as_float("respawn_delay") if "respawn_delay" in c else None
|
||||
|
||||
if command == None:
|
||||
raise ValueError("No command specified for PipeInterface")
|
||||
|
||||
interface = PipeInterface.PipeInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
command,
|
||||
respawn_delay,
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 8
|
||||
|
||||
if c["type"] == "KISSInterface":
|
||||
preamble = int(c["preamble"]) if "preamble" in c else None
|
||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
parity = c["parity"] if "parity" in c else "N"
|
||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||
beacon_interval = int(c["id_interval"]) if "id_interval" in c else None
|
||||
beacon_data = c["id_callsign"] if "id_callsign" in c else None
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for serial interface")
|
||||
|
||||
interface = KISSInterface.KISSInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
port,
|
||||
speed,
|
||||
databits,
|
||||
parity,
|
||||
stopbits,
|
||||
preamble,
|
||||
txtail,
|
||||
persistence,
|
||||
slottime,
|
||||
flow_control,
|
||||
beacon_interval,
|
||||
beacon_data
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 8
|
||||
|
||||
if c["type"] == "AX25KISSInterface":
|
||||
preamble = int(c["preamble"]) if "preamble" in c else None
|
||||
txtail = int(c["txtail"]) if "txtail" in c else None
|
||||
persistence = int(c["persistence"]) if "persistence" in c else None
|
||||
slottime = int(c["slottime"]) if "slottime" in c else None
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
port = c["port"] if "port" in c else None
|
||||
speed = int(c["speed"]) if "speed" in c else 9600
|
||||
databits = int(c["databits"]) if "databits" in c else 8
|
||||
parity = c["parity"] if "parity" in c else "N"
|
||||
stopbits = int(c["stopbits"]) if "stopbits" in c else 1
|
||||
|
||||
callsign = c["callsign"] if "callsign" in c else ""
|
||||
ssid = int(c["ssid"]) if "ssid" in c else -1
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for serial interface")
|
||||
|
||||
interface = AX25KISSInterface.AX25KISSInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
callsign,
|
||||
ssid,
|
||||
port,
|
||||
speed,
|
||||
databits,
|
||||
parity,
|
||||
stopbits,
|
||||
preamble,
|
||||
txtail,
|
||||
persistence,
|
||||
slottime,
|
||||
flow_control
|
||||
)
|
||||
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 8
|
||||
|
||||
if c["type"] == "RNodeInterface":
|
||||
frequency = int(c["frequency"]) if "frequency" in c else None
|
||||
bandwidth = int(c["bandwidth"]) if "bandwidth" in c else None
|
||||
txpower = int(c["txpower"]) if "txpower" in c else None
|
||||
spreadingfactor = int(c["spreadingfactor"]) if "spreadingfactor" in c else None
|
||||
codingrate = int(c["codingrate"]) if "codingrate" in c else None
|
||||
flow_control = c.as_bool("flow_control") if "flow_control" in c else False
|
||||
id_interval = int(c["id_interval"]) if "id_interval" in c else None
|
||||
id_callsign = c["id_callsign"] if "id_callsign" in c else None
|
||||
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,
|
||||
port,
|
||||
frequency = frequency,
|
||||
bandwidth = bandwidth,
|
||||
txpower = txpower,
|
||||
sf = spreadingfactor,
|
||||
cr = codingrate,
|
||||
flow_control = flow_control,
|
||||
id_interval = id_interval,
|
||||
id_callsign = id_callsign,
|
||||
st_alock = st_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:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 8
|
||||
|
||||
if c["type"] == "RNodeMultiInterface":
|
||||
count = 0
|
||||
enabled_count = 0
|
||||
|
||||
# Count how many interfaces are in the file
|
||||
for subinterface in c:
|
||||
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
|
||||
if not isinstance(c[subinterface], str):
|
||||
count += 1
|
||||
|
||||
# Count how many interfaces are enabled to allow for appropriate matrix sizing
|
||||
for subinterface in c:
|
||||
# if the retrieved entry is not a string, it must be a dictionary, which is what we want
|
||||
if not isinstance(c[subinterface], str):
|
||||
subinterface_config = self.config["interfaces"][name][subinterface]
|
||||
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
|
||||
enabled_count += 1
|
||||
|
||||
# Create an array with a row for each subinterface
|
||||
subint_config = [[0 for x in range(11)] for y in range(enabled_count)]
|
||||
subint_index = 0
|
||||
|
||||
for subinterface in c:
|
||||
# If the retrieved entry is not a string, it must be a dictionary, which is what we want
|
||||
if not isinstance(c[subinterface], str):
|
||||
subinterface_config = self.config["interfaces"][name][subinterface]
|
||||
if (("interface_enabled" in subinterface_config) and subinterface_config.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
|
||||
subint_config[subint_index][0] = subinterface
|
||||
|
||||
subint_vport = subinterface_config["vport"] if "vport" in subinterface_config else None
|
||||
subint_config[subint_index][1] = subint_vport
|
||||
|
||||
frequency = int(subinterface_config["frequency"]) if "frequency" in subinterface_config else None
|
||||
subint_config[subint_index][2] = frequency
|
||||
bandwidth = int(subinterface_config["bandwidth"]) if "bandwidth" in subinterface_config else None
|
||||
subint_config[subint_index][3] = bandwidth
|
||||
txpower = int(subinterface_config["txpower"]) if "txpower" in subinterface_config else None
|
||||
subint_config[subint_index][4] = txpower
|
||||
spreadingfactor = int(subinterface_config["spreadingfactor"]) if "spreadingfactor" in subinterface_config else None
|
||||
subint_config[subint_index][5] = spreadingfactor
|
||||
codingrate = int(subinterface_config["codingrate"]) if "codingrate" in subinterface_config else None
|
||||
subint_config[subint_index][6] = codingrate
|
||||
flow_control = subinterface_config.as_bool("flow_control") if "flow_control" in subinterface_config else False
|
||||
subint_config[subint_index][7] = flow_control
|
||||
st_alock = float(subinterface_config["airtime_limit_short"]) if "airtime_limit_short" in subinterface_config else None
|
||||
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
|
||||
if count == 0:
|
||||
raise ValueError("No subinterfaces configured for "+name)
|
||||
# if no subinterfaces are enabled
|
||||
elif enabled_count == 0:
|
||||
raise ValueError("No subinterfaces enabled for "+name)
|
||||
|
||||
id_interval = int(c["id_interval"]) if "id_interval" in c else None
|
||||
id_callsign = c["id_callsign"] if "id_callsign" in c else None
|
||||
port = c["port"] if "port" in c else None
|
||||
|
||||
if port == None:
|
||||
raise ValueError("No port specified for "+name)
|
||||
|
||||
interface = RNodeMultiInterface.RNodeMultiInterface(
|
||||
RNS.Transport,
|
||||
name,
|
||||
port,
|
||||
subint_config,
|
||||
id_interval = id_interval,
|
||||
id_callsign = id_callsign
|
||||
)
|
||||
|
||||
interface.IN = False
|
||||
interface.OUT = False
|
||||
|
||||
interface.mode = interface_mode
|
||||
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = 8
|
||||
|
||||
|
||||
def interface_post_init(interface):
|
||||
if interface != None:
|
||||
if "outgoing" in c and c.as_bool("outgoing") == False:
|
||||
interface.OUT = False
|
||||
else:
|
||||
interface.OUT = True
|
||||
|
||||
interface.mode = interface_mode
|
||||
interface.announce_cap = announce_cap
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
interface.optimise_mtu()
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
else:
|
||||
interface.ifac_size = interface.DEFAULT_IFAC_SIZE
|
||||
|
||||
interface.announce_rate_target = announce_rate_target
|
||||
interface.announce_rate_grace = announce_rate_grace
|
||||
interface.announce_rate_penalty = announce_rate_penalty
|
||||
@@ -1102,8 +644,102 @@ class Reticulum:
|
||||
|
||||
RNS.Transport.interfaces.append(interface)
|
||||
|
||||
if isinstance(interface, RNS.Interfaces.RNodeMultiInterface.RNodeMultiInterface):
|
||||
interface.start()
|
||||
interface = None
|
||||
if (("interface_enabled" in c) and c.as_bool("interface_enabled") == True) or (("enabled" in c) and c.as_bool("enabled") == True):
|
||||
interface_config = c
|
||||
interface_config["name"] = name
|
||||
interface_config["selected_interface_mode"] = interface_mode
|
||||
interface_config["configured_bitrate"] = configured_bitrate
|
||||
|
||||
if c["type"] == "AutoInterface":
|
||||
interface = AutoInterface.AutoInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "UDPInterface":
|
||||
interface = UDPInterface.UDPInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "TCPServerInterface":
|
||||
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface = TCPInterface.TCPServerInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "TCPClientInterface":
|
||||
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface = TCPInterface.TCPClientInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "I2PInterface":
|
||||
if interface_mode == Interface.Interface.MODE_ACCESS_POINT:
|
||||
RNS.log(str(interface)+" does not support Access Point mode, reverting to default mode: Full", RNS.LOG_WARNING)
|
||||
interface_mode = Interface.Interface.MODE_FULL
|
||||
|
||||
interface_config["storagepath"] = Reticulum.storagepath
|
||||
interface_config["ifac_netname"] = ifac_netname
|
||||
interface_config["ifac_netkey"] = ifac_netkey
|
||||
interface_config["ifac_size"] = ifac_size
|
||||
|
||||
interface = I2PInterface.I2PInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "SerialInterface":
|
||||
interface = SerialInterface.SerialInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "PipeInterface":
|
||||
interface = PipeInterface.PipeInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "KISSInterface":
|
||||
interface = KISSInterface.KISSInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "AX25KISSInterface":
|
||||
interface = AX25KISSInterface.AX25KISSInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "RNodeInterface":
|
||||
interface = RNodeInterface.RNodeInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
if c["type"] == "RNodeMultiInterface":
|
||||
interface = RNodeMultiInterface.RNodeMultiInterface(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
interface.start()
|
||||
|
||||
if interface == None:
|
||||
# Interface was not handled by any internal interface types,
|
||||
# attempt to load and initialise it from user-supplied modules
|
||||
interface_type = c["type"]
|
||||
interface_file = f"{interface_type}.py"
|
||||
interface_path = os.path.join(self.interfacepath, interface_file)
|
||||
if not os.path.isfile(interface_path):
|
||||
RNS.log(f"Could not locate external interface module \"{interface_file}\" in \"{self.interfacepath}\"", RNS.LOG_ERROR)
|
||||
|
||||
else:
|
||||
try:
|
||||
RNS.log(f"Loading external interface \"{interface_file}\" from \"{self.interfacepath}\"", RNS.LOG_NOTICE)
|
||||
interface_globals = {}
|
||||
interface_globals["Interface"] = Interface.Interface
|
||||
interface_globals["RNS"] = RNS
|
||||
with open(interface_path) as class_file:
|
||||
interface_code = class_file.read()
|
||||
exec(interface_code, interface_globals)
|
||||
interface_class = interface_globals["interface_class"]
|
||||
|
||||
if interface_class != None:
|
||||
interface = interface_class(RNS.Transport, interface_config)
|
||||
interface_post_init(interface)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"External interface initialisation failed for {interface_type} / {name}", RNS.LOG_ERROR)
|
||||
RNS.trace_exception(e)
|
||||
|
||||
else:
|
||||
RNS.log("Skipping disabled interface \""+name+"\"", RNS.LOG_DEBUG)
|
||||
@@ -1118,7 +754,7 @@ class Reticulum:
|
||||
|
||||
RNS.log("System interfaces are ready", RNS.LOG_VERBOSE)
|
||||
|
||||
def _add_interface(self,interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None, announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None):
|
||||
def _add_interface(self, interface, mode = None, configured_bitrate=None, ifac_size=None, ifac_netname=None, ifac_netkey=None, announce_cap=None, announce_rate_target=None, announce_rate_grace=None, announce_rate_penalty=None):
|
||||
if not self.is_connected_to_shared_instance:
|
||||
if interface != None and issubclass(type(interface), RNS.Interfaces.Interface.Interface):
|
||||
|
||||
@@ -1128,6 +764,7 @@ class Reticulum:
|
||||
|
||||
if configured_bitrate:
|
||||
interface.bitrate = configured_bitrate
|
||||
interface.optimise_mtu()
|
||||
|
||||
if ifac_size != None:
|
||||
interface.ifac_size = ifac_size
|
||||
@@ -1325,9 +962,12 @@ class Reticulum:
|
||||
if hasattr(interface, "r_channel_load_long"):
|
||||
ifstats["channel_load_long"] = interface.r_channel_load_long
|
||||
|
||||
if hasattr(interface, "r_noise_floor"):
|
||||
ifstats["noise_floor"] = interface.r_noise_floor
|
||||
|
||||
if hasattr(interface, "r_battery_state"):
|
||||
if interface.r_battery_state != 0x00:
|
||||
ifstats["battery_state"] = interface.r_battery_state
|
||||
ifstats["battery_state"] = interface.get_battery_state_string()
|
||||
|
||||
if hasattr(interface, "r_battery_percent"):
|
||||
ifstats["battery_percent"] = interface.r_battery_percent
|
||||
@@ -1338,6 +978,18 @@ class Reticulum:
|
||||
else:
|
||||
ifstats["bitrate"] = None
|
||||
|
||||
if hasattr(interface, "current_rx_speed"):
|
||||
if interface.current_rx_speed != None:
|
||||
ifstats["rxs"] = interface.current_rx_speed
|
||||
else:
|
||||
ifstats["rxs"] = 0
|
||||
|
||||
if hasattr(interface, "current_tx_speed"):
|
||||
if interface.current_tx_speed != None:
|
||||
ifstats["txs"] = interface.current_tx_speed
|
||||
else:
|
||||
ifstats["txs"] = 0
|
||||
|
||||
if hasattr(interface, "peers"):
|
||||
if interface.peers != None:
|
||||
ifstats["peers"] = len(interface.peers)
|
||||
@@ -1375,6 +1027,10 @@ class Reticulum:
|
||||
|
||||
stats = {}
|
||||
stats["interfaces"] = interfaces
|
||||
stats["rxb"] = RNS.Transport.traffic_rxb
|
||||
stats["txb"] = RNS.Transport.traffic_txb
|
||||
stats["rxs"] = RNS.Transport.speed_rx
|
||||
stats["txs"] = RNS.Transport.speed_tx
|
||||
if Reticulum.transport_enabled():
|
||||
stats["transport_id"] = RNS.Transport.identity.hash
|
||||
stats["transport_uptime"] = time.time()-RNS.Transport.start_time
|
||||
@@ -1563,6 +1219,14 @@ class Reticulum:
|
||||
|
||||
return None
|
||||
|
||||
def halt_interface(self, interface):
|
||||
pass
|
||||
|
||||
def resume_interface(self, interface):
|
||||
pass
|
||||
|
||||
def reload_interface(self, interface):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def should_use_implicit_proof():
|
||||
@@ -1587,6 +1251,20 @@ class Reticulum:
|
||||
"""
|
||||
return Reticulum.__transport_enabled
|
||||
|
||||
@staticmethod
|
||||
def link_mtu_discovery():
|
||||
"""
|
||||
Returns whether link MTU discovery is enabled for the running
|
||||
instance.
|
||||
|
||||
When link MTU discovery is enabled, Reticulum will
|
||||
automatically upgrade link MTUs to the highest supported
|
||||
value, increasing transfer speed and efficiency.
|
||||
|
||||
:returns: True if link MTU discovery is enabled, False if not.
|
||||
"""
|
||||
return Reticulum.__link_mtu_discovery
|
||||
|
||||
@staticmethod
|
||||
def remote_management_enabled():
|
||||
"""
|
||||
|
||||
+187
-47
@@ -25,6 +25,7 @@ import RNS
|
||||
import time
|
||||
import math
|
||||
import struct
|
||||
import inspect
|
||||
import threading
|
||||
from time import sleep
|
||||
from .vendor import umsgpack as umsgpack
|
||||
@@ -84,7 +85,8 @@ class Transport:
|
||||
destinations = [] # All active destinations
|
||||
pending_links = [] # Links that are being established
|
||||
active_links = [] # Links that are active
|
||||
packet_hashlist = [] # A list of packet hashes for duplicate detection
|
||||
packet_hashlist = set() # A list of packet hashes for duplicate detection
|
||||
packet_hashlist_prev = set()
|
||||
receipts = [] # Receipts of all outgoing packets for proof processing
|
||||
|
||||
# TODO: "destination_table" should really be renamed to "path_table"
|
||||
@@ -102,9 +104,9 @@ class Transport:
|
||||
path_requests = {} # A table for storing path request timestamps
|
||||
path_states = {} # A table for keeping track of path states
|
||||
|
||||
discovery_path_requests = {} # A table for keeping track of path requests on behalf of other nodes
|
||||
discovery_pr_tags = [] # A table for keeping track of tagged path requests
|
||||
max_pr_tags = 32000 # Maximum amount of unique path request tags to remember
|
||||
discovery_path_requests = {} # A table for keeping track of path requests on behalf of other nodes
|
||||
discovery_pr_tags = [] # A table for keeping track of tagged path requests
|
||||
max_pr_tags = 32000 # Maximum amount of unique path request tags to remember
|
||||
|
||||
# Transport control destinations are used
|
||||
# for control purposes like path requests
|
||||
@@ -140,6 +142,12 @@ class Transport:
|
||||
interface_last_jobs = 0.0
|
||||
interface_jobs_interval = 5.0
|
||||
|
||||
traffic_rxb = 0
|
||||
traffic_txb = 0
|
||||
speed_rx = 0
|
||||
speed_tx = 0
|
||||
traffic_captured = None
|
||||
|
||||
identity = None
|
||||
|
||||
@staticmethod
|
||||
@@ -164,7 +172,8 @@ class Transport:
|
||||
if os.path.isfile(packet_hashlist_path):
|
||||
try:
|
||||
file = open(packet_hashlist_path, "rb")
|
||||
Transport.packet_hashlist = umsgpack.unpackb(file.read())
|
||||
hashlist_data = umsgpack.unpackb(file.read())
|
||||
Transport.packet_hashlist = set(hashlist_data)
|
||||
file.close()
|
||||
except Exception as e:
|
||||
RNS.log("Could not load packet hashlist from storage, the contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -192,6 +201,9 @@ class Transport:
|
||||
thread = threading.Thread(target=Transport.jobloop, daemon=True)
|
||||
thread.start()
|
||||
|
||||
thread = threading.Thread(target=Transport.count_traffic_loop, daemon=True)
|
||||
thread.start()
|
||||
|
||||
if RNS.Reticulum.transport_enabled():
|
||||
destination_table_path = RNS.Reticulum.storagepath+"/destination_table"
|
||||
tunnel_table_path = RNS.Reticulum.storagepath+"/tunnels"
|
||||
@@ -318,6 +330,40 @@ class Transport:
|
||||
except Exception as e:
|
||||
RNS.log(f"Could not prioritize interfaces according to bitrate. The contained exception was: {e}", RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def count_traffic_loop():
|
||||
while True:
|
||||
time.sleep(1)
|
||||
try:
|
||||
rxb = 0; txb = 0;
|
||||
rxs = 0; txs = 0;
|
||||
for interface in Transport.interfaces:
|
||||
if not hasattr(interface, "parent_interface") or interface.parent_interface == None:
|
||||
if hasattr(interface, "transport_traffic_counter"):
|
||||
now = time.time(); irxb = interface.rxb; itxb = interface.txb
|
||||
tc = interface.transport_traffic_counter
|
||||
rx_diff = irxb - tc["rxb"]
|
||||
tx_diff = itxb - tc["txb"]
|
||||
ts_diff = now - tc["ts"]
|
||||
rxb += rx_diff; crxs = (rx_diff*8)/ts_diff
|
||||
txb += tx_diff; ctxs = (tx_diff*8)/ts_diff
|
||||
interface.current_rx_speed = crxs; rxs += crxs
|
||||
interface.current_tx_speed = ctxs; txs += ctxs
|
||||
tc["rxb"] = irxb;
|
||||
tc["txb"] = itxb;
|
||||
tc["ts"] = now;
|
||||
|
||||
else:
|
||||
interface.transport_traffic_counter = {"ts": time.time(), "rxb": interface.rxb, "txb": interface.txb}
|
||||
|
||||
Transport.traffic_rxb += rxb
|
||||
Transport.traffic_txb += txb
|
||||
Transport.speed_rx = rxs
|
||||
Transport.speed_tx = txs
|
||||
|
||||
except Exception as e:
|
||||
RNS.log(f"An error occurred while counting interface traffic: {e}", RNS.LOG_ERROR)
|
||||
|
||||
@staticmethod
|
||||
def jobloop():
|
||||
while (True):
|
||||
@@ -445,8 +491,9 @@ class Transport:
|
||||
|
||||
|
||||
# Cull the packet hashlist if it has reached its max size
|
||||
if len(Transport.packet_hashlist) > Transport.hashlist_maxsize:
|
||||
Transport.packet_hashlist = Transport.packet_hashlist[len(Transport.packet_hashlist)-Transport.hashlist_maxsize:len(Transport.packet_hashlist)-1]
|
||||
if len(Transport.packet_hashlist) > Transport.hashlist_maxsize//2:
|
||||
Transport.packet_hashlist_prev = Transport.packet_hashlist
|
||||
Transport.packet_hashlist = set()
|
||||
|
||||
# Cull the path request tags list if it has reached its max size
|
||||
if len(Transport.discovery_pr_tags) > Transport.max_pr_tags:
|
||||
@@ -743,10 +790,10 @@ class Transport:
|
||||
i += 1
|
||||
|
||||
# Send it
|
||||
interface.processOutgoing(masked_raw)
|
||||
interface.process_outgoing(masked_raw)
|
||||
|
||||
else:
|
||||
interface.processOutgoing(raw)
|
||||
interface.process_outgoing(raw)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("Error while transmitting on "+str(interface)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
@@ -982,10 +1029,10 @@ class Transport:
|
||||
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
if should_transmit:
|
||||
if not stored_hash:
|
||||
Transport.packet_hashlist.append(packet.packet_hash)
|
||||
Transport.packet_hashlist.add(packet.packet_hash)
|
||||
stored_hash = True
|
||||
|
||||
# TODO: Re-evaluate potential for blocking
|
||||
@@ -1051,7 +1098,7 @@ class Transport:
|
||||
RNS.log("Dropped invalid GROUP announce packet", RNS.LOG_DEBUG)
|
||||
return False
|
||||
|
||||
if not packet.packet_hash in Transport.packet_hashlist:
|
||||
if not packet.packet_hash in Transport.packet_hashlist and not packet.packet_hash in Transport.packet_hashlist_prev:
|
||||
return True
|
||||
else:
|
||||
if packet.packet_type == RNS.Packet.ANNOUNCE:
|
||||
@@ -1149,29 +1196,23 @@ class Transport:
|
||||
if hasattr(interface, "r_stat_rssi"):
|
||||
if interface.r_stat_rssi != None:
|
||||
packet.rssi = interface.r_stat_rssi
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi])
|
||||
|
||||
while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
|
||||
Transport.local_client_rssi_cache.pop(0)
|
||||
Transport.local_client_rssi_cache.append([packet.packet_hash, packet.rssi])
|
||||
while len(Transport.local_client_rssi_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
|
||||
Transport.local_client_rssi_cache.pop(0)
|
||||
|
||||
if hasattr(interface, "r_stat_snr"):
|
||||
if interface.r_stat_rssi != None:
|
||||
packet.snr = interface.r_stat_snr
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr])
|
||||
|
||||
while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
|
||||
Transport.local_client_snr_cache.pop(0)
|
||||
Transport.local_client_snr_cache.append([packet.packet_hash, packet.snr])
|
||||
while len(Transport.local_client_snr_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
|
||||
Transport.local_client_snr_cache.pop(0)
|
||||
|
||||
if hasattr(interface, "r_stat_q"):
|
||||
if interface.r_stat_q != None:
|
||||
packet.q = interface.r_stat_q
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
Transport.local_client_q_cache.append([packet.packet_hash, packet.q])
|
||||
|
||||
while len(Transport.local_client_q_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
|
||||
Transport.local_client_q_cache.pop(0)
|
||||
Transport.local_client_q_cache.append([packet.packet_hash, packet.q])
|
||||
while len(Transport.local_client_q_cache) > Transport.LOCAL_CLIENT_CACHE_MAXSIZE:
|
||||
Transport.local_client_q_cache.pop(0)
|
||||
|
||||
if len(Transport.local_client_interfaces) > 0:
|
||||
if Transport.is_local_client_interface(interface):
|
||||
@@ -1202,7 +1243,7 @@ class Transport:
|
||||
remember_packet_hash = False
|
||||
|
||||
if remember_packet_hash:
|
||||
Transport.packet_hashlist.append(packet.packet_hash)
|
||||
Transport.packet_hashlist.add(packet.packet_hash)
|
||||
# TODO: Enable when caching has been redesigned
|
||||
# Transport.cache(packet)
|
||||
|
||||
@@ -1287,6 +1328,24 @@ class Transport:
|
||||
now = time.time()
|
||||
proof_timeout = Transport.extra_link_proof_timeout(packet.receiving_interface)
|
||||
proof_timeout += now + RNS.Link.ESTABLISHMENT_TIMEOUT_PER_HOP * max(1, remaining_hops)
|
||||
|
||||
path_mtu = RNS.Link.mtu_from_lr_packet(packet)
|
||||
nh_mtu = outbound_interface.HW_MTU
|
||||
if path_mtu:
|
||||
if outbound_interface.HW_MTU == None:
|
||||
RNS.log(f"No next-hop HW MTU, disabling link MTU upgrade", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
path_mtu = None
|
||||
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
|
||||
elif not outbound_interface.AUTOCONFIGURE_MTU:
|
||||
RNS.log(f"Outbound interface doesn't support MTU autoconfiguration, disabling link MTU upgrade", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
path_mtu = None
|
||||
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]
|
||||
else:
|
||||
if nh_mtu < path_mtu:
|
||||
path_mtu = nh_mtu
|
||||
clamped_mtu = RNS.Link.mtu_bytes(path_mtu)
|
||||
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}: {RNS.hexrep(clamped_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
new_raw = new_raw[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
||||
|
||||
# Entry format is
|
||||
link_entry = [ now, # 0: Timestamp,
|
||||
@@ -1299,7 +1358,7 @@ class Transport:
|
||||
False, # 7: Validated
|
||||
proof_timeout] # 8: Proof timeout timestamp
|
||||
|
||||
Transport.link_table[packet.getTruncatedHash()] = link_entry
|
||||
Transport.link_table[RNS.Link.link_id_from_lr_packet(packet)] = link_entry
|
||||
|
||||
else:
|
||||
# Entry format is
|
||||
@@ -1349,7 +1408,7 @@ class Transport:
|
||||
# Add this packet to the filter hashlist if we
|
||||
# have determined that it's actually our turn
|
||||
# to process it.
|
||||
Transport.packet_hashlist.append(packet.packet_hash)
|
||||
Transport.packet_hashlist.add(packet.packet_hash)
|
||||
|
||||
new_raw = packet.raw[0:1]
|
||||
new_raw += struct.pack("!B", packet.hops)
|
||||
@@ -1402,7 +1461,8 @@ class Transport:
|
||||
now = time.time()
|
||||
if now < announce_entry[1]:
|
||||
RNS.log("Rebroadcasted announce for "+RNS.prettyhexrep(packet.destination_hash)+" has been passed on to another node, no further tries needed", RNS.LOG_DEBUG)
|
||||
Transport.announce_table.pop(packet.destination_hash)
|
||||
if packet.destination_hash in Transport.announce_table:
|
||||
Transport.announce_table.pop(packet.destination_hash)
|
||||
|
||||
else:
|
||||
received_from = packet.destination_hash
|
||||
@@ -1713,11 +1773,20 @@ class Transport:
|
||||
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)
|
||||
)
|
||||
|
||||
if len(inspect.signature(handler.received_announce).parameters) == 3:
|
||||
handler.received_announce(destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash))
|
||||
|
||||
elif len(inspect.signature(handler.received_announce).parameters) == 4:
|
||||
handler.received_announce(destination_hash=packet.destination_hash,
|
||||
announced_identity=announce_identity,
|
||||
app_data=RNS.Identity.recall_app_data(packet.destination_hash),
|
||||
announce_packet_hash = packet.packet_hash)
|
||||
else:
|
||||
raise TypeError("Invalid signature for announce handler callback")
|
||||
|
||||
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)
|
||||
@@ -1728,6 +1797,24 @@ class Transport:
|
||||
if packet.transport_id == None or packet.transport_id == Transport.identity.hash:
|
||||
for destination in Transport.destinations:
|
||||
if destination.hash == packet.destination_hash and destination.type == packet.destination_type:
|
||||
path_mtu = RNS.Link.mtu_from_lr_packet(packet)
|
||||
if packet.receiving_interface.AUTOCONFIGURE_MTU:
|
||||
nh_mtu = packet.receiving_interface.HW_MTU
|
||||
else:
|
||||
nh_mtu = RNS.Reticulum.MTU
|
||||
|
||||
if path_mtu:
|
||||
if packet.receiving_interface.HW_MTU == None:
|
||||
RNS.log(f"No next-hop HW MTU, disabling link MTU upgrade", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
path_mtu = None
|
||||
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]
|
||||
else:
|
||||
if nh_mtu < path_mtu:
|
||||
path_mtu = nh_mtu
|
||||
clamped_mtu = RNS.Link.mtu_bytes(path_mtu)
|
||||
RNS.log(f"Clamping link MTU to {RNS.prettysize(nh_mtu)}", RNS.LOG_DEBUG) # TODO: Remove debug
|
||||
packet.data = packet.data[:-RNS.Link.LINK_MTU_SIZE]+clamped_mtu
|
||||
|
||||
packet.destination = destination
|
||||
destination.receive(packet)
|
||||
|
||||
@@ -1738,7 +1825,16 @@ class Transport:
|
||||
if link.link_id == packet.destination_hash:
|
||||
if link.attached_interface == packet.receiving_interface:
|
||||
packet.link = link
|
||||
link.receive(packet)
|
||||
if packet.context == RNS.Packet.CACHE_REQUEST:
|
||||
cached_packet = Transport.get_cached_packet(packet.data)
|
||||
if cached_packet != None:
|
||||
cached_packet.unpack()
|
||||
RNS.Packet(destination=link, data=cached_packet.data,
|
||||
packet_type=cached_packet.packet_type, context=cached_packet.context).send()
|
||||
|
||||
Transport.jobs_locked = False
|
||||
else:
|
||||
link.receive(packet)
|
||||
else:
|
||||
# In the strange and rare case that an interface
|
||||
# is partly malfunctioning, and a link-associated
|
||||
@@ -1776,12 +1872,16 @@ class Transport:
|
||||
if packet.hops == link_entry[3]:
|
||||
if packet.receiving_interface == link_entry[2]:
|
||||
try:
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2:
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2 or len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE:
|
||||
mtu_bytes = b""
|
||||
if len(packet.data) == RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2+RNS.Link.LINK_MTU_SIZE:
|
||||
mtu_bytes = RNS.Link.mtu_bytes(RNS.Link.mtu_from_lp_packet(packet))
|
||||
|
||||
peer_pub_bytes = packet.data[RNS.Identity.SIGLENGTH//8:RNS.Identity.SIGLENGTH//8+RNS.Link.ECPUBSIZE//2]
|
||||
peer_identity = RNS.Identity.recall(link_entry[6])
|
||||
peer_sig_pub_bytes = peer_identity.get_public_key()[RNS.Link.ECPUBSIZE//2:RNS.Link.ECPUBSIZE]
|
||||
|
||||
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes
|
||||
signed_data = packet.destination_hash+peer_pub_bytes+peer_sig_pub_bytes+mtu_bytes
|
||||
signature = packet.data[:RNS.Identity.SIGLENGTH//8]
|
||||
|
||||
if peer_identity.validate(signature, signed_data):
|
||||
@@ -1823,7 +1923,7 @@ class Transport:
|
||||
# Add this packet to the filter hashlist if we
|
||||
# have determined that it's actually destined
|
||||
# for this system, and then validate the proof
|
||||
Transport.packet_hashlist.append(packet.packet_hash)
|
||||
Transport.packet_hashlist.add(packet.packet_hash)
|
||||
link.validate_proof(packet)
|
||||
|
||||
elif packet.context == RNS.Packet.RESOURCE_PRF:
|
||||
@@ -2012,8 +2112,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. 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.
|
||||
or *received_announce(destination_hash, announced_identity, app_data, announce_packet_hash)* 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"):
|
||||
@@ -2174,6 +2275,17 @@ class Transport:
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def next_hop_interface_hw_mtu(destination_hash):
|
||||
next_hop_interface = Transport.next_hop_interface(destination_hash)
|
||||
if next_hop_interface != None:
|
||||
if next_hop_interface.AUTOCONFIGURE_MTU:
|
||||
return next_hop_interface.HW_MTU
|
||||
else:
|
||||
return None
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def next_hop_per_bit_latency(destination_hash):
|
||||
next_hop_interface_bitrate = Transport.next_hop_interface_bitrate(destination_hash)
|
||||
@@ -2550,7 +2662,7 @@ class Transport:
|
||||
# Currently no rules are being applied
|
||||
# here, and all interfaces will be sent
|
||||
# the detach call on RNS teardown.
|
||||
if True:
|
||||
if not interface.detached:
|
||||
detachable_interfaces.append(interface)
|
||||
else:
|
||||
pass
|
||||
@@ -2559,17 +2671,45 @@ class Transport:
|
||||
# Currently no rules are being applied
|
||||
# here, and all interfaces will be sent
|
||||
# the detach call on RNS teardown.
|
||||
if True:
|
||||
if not interface.detached:
|
||||
detachable_interfaces.append(interface)
|
||||
else:
|
||||
pass
|
||||
|
||||
shared_instance_master = None
|
||||
local_interfaces = []
|
||||
detach_threads = []
|
||||
RNS.log("Detaching interfaces", RNS.LOG_DEBUG)
|
||||
for interface in detachable_interfaces:
|
||||
try:
|
||||
interface.detach()
|
||||
if type(interface) == RNS.Interfaces.LocalInterface.LocalServerInterface:
|
||||
shared_instance_master = interface
|
||||
elif type(interface) == RNS.Interfaces.LocalInterface.LocalClientInterface:
|
||||
local_interfaces.append(interface)
|
||||
else:
|
||||
def detach_job():
|
||||
RNS.log(f"Detaching {interface}", RNS.LOG_EXTREME)
|
||||
interface.detach()
|
||||
dt = threading.Thread(target=detach_job, daemon=True)
|
||||
dt.start()
|
||||
detach_threads.append(dt)
|
||||
|
||||
except Exception as e:
|
||||
RNS.log("An error occurred while detaching "+str(interface)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
|
||||
|
||||
for dt in detach_threads:
|
||||
dt.join()
|
||||
|
||||
RNS.log("Detaching local clients", RNS.LOG_DEBUG)
|
||||
for li in local_interfaces:
|
||||
li.detach()
|
||||
|
||||
RNS.log("Detaching shared instance", RNS.LOG_DEBUG)
|
||||
if shared_instance_master != None:
|
||||
shared_instance_master.detach()
|
||||
|
||||
RNS.log("All interfaces detached", RNS.LOG_DEBUG)
|
||||
|
||||
@staticmethod
|
||||
def shared_connection_disappeared():
|
||||
for link in Transport.active_links:
|
||||
@@ -2636,13 +2776,13 @@ class Transport:
|
||||
save_start = time.time()
|
||||
|
||||
if not RNS.Reticulum.transport_enabled():
|
||||
Transport.packet_hashlist = []
|
||||
Transport.packet_hashlist = set()
|
||||
else:
|
||||
RNS.log("Saving packet hashlist to storage...", RNS.LOG_DEBUG)
|
||||
|
||||
packet_hashlist_path = RNS.Reticulum.storagepath+"/packet_hashlist"
|
||||
file = open(packet_hashlist_path, "wb")
|
||||
file.write(umsgpack.packb(Transport.packet_hashlist))
|
||||
file.write(umsgpack.packb(list(Transport.packet_hashlist)))
|
||||
file.close()
|
||||
|
||||
save_time = time.time() - save_start
|
||||
|
||||
@@ -23,5 +23,7 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
+161
-56
@@ -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,59 @@ resource_done = False
|
||||
current_resource = None
|
||||
stats = []
|
||||
speed = 0.0
|
||||
phy_speed = 0.0
|
||||
phy_got_total = 0
|
||||
def sender_progress(resource):
|
||||
stats_max = 32
|
||||
global current_resource, stats, speed, resource_done
|
||||
global current_resource, stats, speed, phy_speed, phy_got_total, 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
|
||||
# phy_got_total += phy_diff
|
||||
|
||||
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 +380,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 +397,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 +426,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 +467,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 +514,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 +546,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 +568,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 +583,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, no_compress=False):
|
||||
global current_resource, resource_done, link, speed, show_phy_rates, phy_got_total, phy_speed
|
||||
from tempfile import TemporaryFile
|
||||
targetloglevel = 3+verbosity-quietness
|
||||
show_phy_rates = phy_rates
|
||||
|
||||
try:
|
||||
dest_len = (RNS.Reticulum.TRUNCATED_HASHLENGTH//8)*2
|
||||
@@ -536,14 +616,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 +646,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 +663,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,22 +692,25 @@ 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)
|
||||
auto_compress = True
|
||||
if no_compress:
|
||||
auto_compress = False
|
||||
resource = RNS.Resource(temp_file, link, callback = sender_progress, progress_callback = sender_progress, auto_compress = auto_compress)
|
||||
current_resource = resource
|
||||
|
||||
while resource.status < RNS.Resource.TRANSFERRING:
|
||||
@@ -637,28 +720,38 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
sys.stdout.flush()
|
||||
i = (i+1)%len(syms)
|
||||
|
||||
resource_started_at = time.time()
|
||||
|
||||
if resource.status > RNS.Resource.COMPLETE:
|
||||
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 and not resource_done:
|
||||
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
|
||||
@@ -667,6 +760,11 @@ def send(configdir, verbosity = 0, quietness = 0, destination = None, file = Non
|
||||
if not silent:
|
||||
i = progress_update(i)
|
||||
|
||||
resource_concluded_at = time.time()
|
||||
transfer_time = resource_concluded_at - resource_started_at
|
||||
speed = current_resource.total_size/transfer_time
|
||||
# phy_speed = phy_got_total/transfer_time
|
||||
|
||||
if not silent:
|
||||
i = progress_update(i, done=True)
|
||||
|
||||
@@ -674,13 +772,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)
|
||||
@@ -698,14 +795,17 @@ def main():
|
||||
parser.add_argument('-q', '--quiet', action='count', default=0, help="decrease verbosity")
|
||||
parser.add_argument("-S", '--silent', action='store_true', default=False, help="disable transfer progress output")
|
||||
parser.add_argument("-l", '--listen', action='store_true', default=False, help="listen for incoming transfer requests")
|
||||
parser.add_argument("-C", '--no-compress', action='store_true', default=False, help="disable automatic compression")
|
||||
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 +819,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 +836,8 @@ def main():
|
||||
file = args.file,
|
||||
timeout = args.w,
|
||||
silent = args.silent,
|
||||
phy_rates = args.phy_rates,
|
||||
save = args.save,
|
||||
)
|
||||
else:
|
||||
print("")
|
||||
@@ -750,6 +853,8 @@ def main():
|
||||
file = args.file,
|
||||
timeout = args.w,
|
||||
silent = args.silent,
|
||||
phy_rates = args.phy_rates,
|
||||
no_compress = args.no_compress,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
+293
-93
@@ -2,7 +2,7 @@
|
||||
|
||||
# MIT License
|
||||
#
|
||||
# Copyright (c) 2018-2022 Mark Qvist - unsigned.io/rnode
|
||||
# Copyright (c) 2018-2025 Mark Qvist
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -41,7 +41,7 @@ import RNS
|
||||
RNS.logtimefmt = "%H:%M:%S"
|
||||
RNS.compact_log_fmt = True
|
||||
|
||||
program_version = "2.2.0"
|
||||
program_version = "2.4.0"
|
||||
eth_addr = "0xFDabC71AC4c0C78C95aDDDe3B4FA19d6273c5E73"
|
||||
btc_addr = "35G9uWVzrpJJibzUwpNUQGQNFzLirhrYAH"
|
||||
xmr_addr = "87HcDx6jRSkMQ9nPRd5K9hGGpZLn2s7vWETjMaVM5KfV4TD36NcYa8J8WSxhTSvBzzFpqDwp2fg5GX2moZ7VAP9QMZCZGET"
|
||||
@@ -84,8 +84,11 @@ class KISS():
|
||||
CMD_NP_INT = 0x65
|
||||
CMD_DISP_ADR = 0x63
|
||||
CMD_DISP_BLNK = 0x64
|
||||
CMD_DISP_ROT = 0x67
|
||||
CMD_DISP_RCND = 0x68
|
||||
CMD_BT_CTRL = 0x46
|
||||
CMD_BT_PIN = 0x62
|
||||
CMD_DIS_IA = 0x69
|
||||
CMD_BOARD = 0x47
|
||||
CMD_PLATFORM = 0x48
|
||||
CMD_MCU = 0x49
|
||||
@@ -121,76 +124,80 @@ class KISS():
|
||||
return data
|
||||
|
||||
class ROM():
|
||||
PLATFORM_AVR = 0x90
|
||||
PLATFORM_ESP32 = 0x80
|
||||
PLATFORM_NRF52 = 0x70
|
||||
PLATFORM_AVR = 0x90
|
||||
PLATFORM_ESP32 = 0x80
|
||||
PLATFORM_NRF52 = 0x70
|
||||
|
||||
MCU_1284P = 0x91
|
||||
MCU_2560 = 0x92
|
||||
MCU_ESP32 = 0x81
|
||||
MCU_NRF52 = 0x71
|
||||
MCU_1284P = 0x91
|
||||
MCU_2560 = 0x92
|
||||
MCU_ESP32 = 0x81
|
||||
MCU_NRF52 = 0x71
|
||||
|
||||
PRODUCT_RNODE = 0x03
|
||||
MODEL_A1 = 0xA1
|
||||
MODEL_A6 = 0xA6
|
||||
MODEL_A4 = 0xA4
|
||||
MODEL_A9 = 0xA9
|
||||
MODEL_A3 = 0xA3
|
||||
MODEL_A8 = 0xA8
|
||||
MODEL_A2 = 0xA2
|
||||
MODEL_A7 = 0xA7
|
||||
MODEL_A5 = 0xA5
|
||||
MODEL_AA = 0xAA
|
||||
PRODUCT_RNODE = 0x03
|
||||
MODEL_A1 = 0xA1
|
||||
MODEL_A6 = 0xA6
|
||||
MODEL_A4 = 0xA4
|
||||
MODEL_A9 = 0xA9
|
||||
MODEL_A3 = 0xA3
|
||||
MODEL_A8 = 0xA8
|
||||
MODEL_A2 = 0xA2
|
||||
MODEL_A7 = 0xA7
|
||||
MODEL_A5 = 0xA5
|
||||
MODEL_AA = 0xAA
|
||||
MODEL_AC = 0xAC
|
||||
|
||||
PRODUCT_T32_10 = 0xB2
|
||||
MODEL_BA = 0xBA
|
||||
MODEL_BB = 0xBB
|
||||
PRODUCT_T32_10 = 0xB2
|
||||
MODEL_BA = 0xBA
|
||||
MODEL_BB = 0xBB
|
||||
|
||||
PRODUCT_T32_20 = 0xB0
|
||||
MODEL_B3 = 0xB3
|
||||
MODEL_B8 = 0xB8
|
||||
PRODUCT_T32_20 = 0xB0
|
||||
MODEL_B3 = 0xB3
|
||||
MODEL_B8 = 0xB8
|
||||
|
||||
PRODUCT_T32_21 = 0xB1
|
||||
MODEL_B4 = 0xB4
|
||||
MODEL_B9 = 0xB9
|
||||
MODEL_B4_TCXO = 0x04 # The TCXO model codes are only used here to select the
|
||||
MODEL_B9_TCXO = 0x09 # correct firmware, actual model codes in firmware is
|
||||
# still 0xB4 and 0xB9.
|
||||
PRODUCT_T32_21 = 0xB1
|
||||
MODEL_B4 = 0xB4
|
||||
MODEL_B9 = 0xB9
|
||||
MODEL_B4_TCXO = 0x04 # The TCXO model codes are only used here to select the correct firmware,
|
||||
MODEL_B9_TCXO = 0x09 # actual model codes in firmware is still 0xB4 and 0xB9.
|
||||
PRODUCT_H32_V2 = 0xC0
|
||||
MODEL_C4 = 0xC4
|
||||
MODEL_C9 = 0xC9
|
||||
|
||||
PRODUCT_H32_V2 = 0xC0
|
||||
MODEL_C4 = 0xC4
|
||||
MODEL_C9 = 0xC9
|
||||
PRODUCT_H32_V3 = 0xC1
|
||||
MODEL_C5 = 0xC5
|
||||
MODEL_CA = 0xCA
|
||||
|
||||
PRODUCT_H32_V3 = 0xC1
|
||||
MODEL_C5 = 0xC5
|
||||
MODEL_CA = 0xCA
|
||||
PRODUCT_TBEAM = 0xE0
|
||||
MODEL_E4 = 0xE4
|
||||
MODEL_E9 = 0xE9
|
||||
MODEL_E3 = 0xE3
|
||||
MODEL_E8 = 0xE8
|
||||
|
||||
PRODUCT_TBEAM = 0xE0
|
||||
MODEL_E4 = 0xE4
|
||||
MODEL_E9 = 0xE9
|
||||
MODEL_E3 = 0xE3
|
||||
MODEL_E8 = 0xE8
|
||||
PRODUCT_TBEAM_S_V1 = 0xEA
|
||||
MODEL_DB = 0xDB
|
||||
MODEL_DC = 0xDC
|
||||
|
||||
PRODUCT_TBEAM_S_V1= 0xEA
|
||||
MODEL_DB = 0xDB
|
||||
MODEL_DC = 0xDC
|
||||
PRODUCT_TDECK = 0xD0
|
||||
MODEL_D4 = 0xD4
|
||||
MODEL_D9 = 0xD9
|
||||
|
||||
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_RAK4631 = 0x10
|
||||
MODEL_11 = 0x11
|
||||
MODEL_12 = 0x12
|
||||
MODEL_13 = 0x13
|
||||
MODEL_14 = 0x14
|
||||
PRODUCT_OPENCOM_XL = 0x20
|
||||
MODEL_21 = 0x21
|
||||
PRODUCT_OPENCOM_XL = 0x20
|
||||
MODEL_21 = 0x21
|
||||
|
||||
PRODUCT_TECHO = 0x15
|
||||
MODEL_16 = 0x16
|
||||
MODEL_17 = 0x17
|
||||
|
||||
PRODUCT_TECHO = 0x15
|
||||
MODEL_T4 = 0x16
|
||||
MODEL_T9 = 0x17
|
||||
PRODUCT_HELTEC_T114 = 0xC2
|
||||
BOARD_HELTEC_T114 = 0x3C
|
||||
MODEL_C6 = 0xC6 # Heltec Mesh Node T114, 470-510 MHz (HT-n5262-LF)
|
||||
MODEL_C7 = 0xC7 # Heltec Mesh Node T114, 863-928 MHz (HT-n5262-HF)
|
||||
|
||||
PRODUCT_HMBRW = 0xF0
|
||||
MODEL_FF = 0xFF
|
||||
@@ -211,6 +218,17 @@ class ROM():
|
||||
ADDR_CONF_FREQ = 0xA3
|
||||
ADDR_CONF_OK = 0xA7
|
||||
|
||||
ADDR_CONF_BT = 0xB0
|
||||
ADDR_CONF_DSET = 0xB1
|
||||
ADDR_CONF_DINT = 0xB2
|
||||
ADDR_CONF_DADR = 0xB3
|
||||
ADDR_CONF_DBLK = 0xB4
|
||||
ADDR_CONF_DROT = 0xB8
|
||||
ADDR_CONF_PSET = 0xB5
|
||||
ADDR_CONF_PINT = 0xB6
|
||||
ADDR_CONF_BSET = 0xB7
|
||||
ADDR_CONF_DIA = 0xB9
|
||||
|
||||
INFO_LOCK_BYTE = 0x73
|
||||
CONF_OK_BYTE = 0x73
|
||||
|
||||
@@ -242,6 +260,7 @@ products = {
|
||||
ROM.PRODUCT_TECHO: "LilyGO T-Echo",
|
||||
ROM.PRODUCT_RAK4631: "RAK4631",
|
||||
ROM.PRODUCT_OPENCOM_XL: "openCom XL",
|
||||
ROM.PRODUCT_HELTEC_T114: "Heltec Mesh Node T114",
|
||||
}
|
||||
|
||||
platforms = {
|
||||
@@ -264,6 +283,7 @@ models = {
|
||||
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"],
|
||||
0xAC: [2400000000, 2500000000, 20, "2.4 - 2.5 GHz", "rnode_firmware_t3s3_sx1280_pa.zip", "SX1280"],
|
||||
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"],
|
||||
@@ -278,8 +298,10 @@ 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, 22, "420 - 520 MHz", "rnode_firmware_heltec32v3.zip", "SX1268"],
|
||||
0xCA: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_heltec32v3.zip", "SX1262"],
|
||||
0xC6: [420000000, 520000000, 22, "420 - 520 MHz", "rnode_firmware_heltec_t114.zip", "SX1268"],
|
||||
0xC7: [850000000, 950000000, 22, "850 - 950 MHz", "rnode_firmware_heltec_t114.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"],
|
||||
@@ -665,6 +687,30 @@ class RNode():
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display blanking timeout command to device")
|
||||
|
||||
def set_display_rotation(self, rotation):
|
||||
data = bytes([rotation & 0xFF])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_ROT])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display rotation command to device")
|
||||
|
||||
def recondition_display(self):
|
||||
data = bytes([0x01])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DISP_RCND])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending display recondition command to device")
|
||||
|
||||
def set_disable_interference_avoidance(self, ia_disabled):
|
||||
if ia_disabled:
|
||||
data = bytes([0x01])
|
||||
else:
|
||||
data = bytes([0x00])
|
||||
kiss_command = bytes([KISS.FEND])+bytes([KISS.CMD_DIS_IA])+data+bytes([KISS.FEND])
|
||||
written = self.serial.write(kiss_command)
|
||||
if written != len(kiss_command):
|
||||
raise IOError("An IO error occurred while sending interference avoidance configuration 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])
|
||||
@@ -1148,9 +1194,10 @@ def ensure_firmware_file(fw_filename):
|
||||
RNS.log("The selected firmware for this board is version "+selected_version)
|
||||
|
||||
else:
|
||||
RNS.log("Online firmware version check was disabled, but no firmware version specified for install.")
|
||||
RNS.log("use the --fw-version option to manually specify a version.")
|
||||
graceful_exit(98)
|
||||
if selected_version == None:
|
||||
RNS.log("Online firmware version check was disabled, but no firmware version specified for install.")
|
||||
RNS.log("use the --fw-version option to manually specify a version.")
|
||||
graceful_exit(98)
|
||||
|
||||
# if custom firmware url, use it
|
||||
if fw_url != None:
|
||||
@@ -1298,7 +1345,9 @@ def main():
|
||||
|
||||
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("-R", "--rotation", action="store", metavar="rotation", type=int, default=None, help="Set display rotation, valid values are 0 through 3")
|
||||
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("--recondition-display", action="store_true", help="Start display reconditioning")
|
||||
|
||||
parser.add_argument("--np", action="store", metavar="i", type=int, default=None, help="Set NeoPixel intensity (0-255)")
|
||||
|
||||
@@ -1308,6 +1357,11 @@ def main():
|
||||
parser.add_argument("--sf", action="store", metavar="factor", type=int, default=None, help="Spreading factor for TNC mode (7 - 12)")
|
||||
parser.add_argument("--cr", action="store", metavar="rate", type=int, default=None, help="Coding rate for TNC mode (5 - 8)")
|
||||
|
||||
parser.add_argument("-x", "--ia-enable", action="store_true", help="Enable interference avoidance")
|
||||
parser.add_argument("-X", "--ia-disable", action="store_true", help="Disable interference avoidance")
|
||||
|
||||
parser.add_argument("-c", "--config", action="store_true", help="Print device configuration")
|
||||
|
||||
parser.add_argument("--eeprom-backup", action="store_true", help="Backup EEPROM to file")
|
||||
parser.add_argument("--eeprom-dump", action="store_true", help="Dump EEPROM to console")
|
||||
parser.add_argument("--eeprom-wipe", action="store_true", help="Unlock and wipe EEPROM")
|
||||
@@ -1321,7 +1375,7 @@ def main():
|
||||
parser.add_argument("-r", "--rom", action="store_true", help="Bootstrap EEPROM without flashing firmware")
|
||||
parser.add_argument("-k", "--key", action="store_true", help="Generate a new signing key and exit") #
|
||||
parser.add_argument("-S", "--sign", action="store_true", help="Display public part of signing key")
|
||||
parser.add_argument("-H", "--firmware-hash", action="store", help="Display installed firmware hash")
|
||||
parser.add_argument("-H", "--firmware-hash", action="store", help="Set installed firmware hash")
|
||||
parser.add_argument("-K", "--get-target-firmware-hash", action="store_true", help=argparse.SUPPRESS) # Get target firmware hash from device
|
||||
parser.add_argument("-L", "--get-firmware-hash", action="store_true", help=argparse.SUPPRESS) # Get calculated firmware hash from device
|
||||
parser.add_argument("--platform", action="store", metavar="platform", type=str, default=None, help="Platform specification for device bootstrap")
|
||||
@@ -1641,30 +1695,31 @@ def main():
|
||||
|
||||
print("")
|
||||
print("What kind of device is this?\n")
|
||||
print("[1] A specific kind of RNode")
|
||||
print(" .")
|
||||
print(" / \\ Select this option if you have an RNode of a specific")
|
||||
print(" | type, built from a recipe or bought from a vendor.")
|
||||
print(" | Select this option if you have an RNode of a specific")
|
||||
print(" \\ / type, built from a recipe or bought from a vendor.")
|
||||
print(" '")
|
||||
print("[1] A specific kind of RNode")
|
||||
print("")
|
||||
print("[2] Homebrew RNode")
|
||||
print(" .")
|
||||
print(" / \\ Select this option if you have put toghether an RNode")
|
||||
print(" | of your own design, or if you are prototyping one.")
|
||||
print(" | Select this option if you have put toghether an RNode")
|
||||
print(" \\ / of your own design, or if you are prototyping one.")
|
||||
print(" '")
|
||||
print("[2] Homebrew RNode")
|
||||
print("")
|
||||
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)")
|
||||
print("[4] LilyGO LoRa32 v2.0")
|
||||
print("[5] LilyGO LoRa32 v1.0")
|
||||
print("[6] LilyGO T-Beam")
|
||||
print("[7] Heltec LoRa32 v2")
|
||||
print("[8] Heltec LoRa32 v3")
|
||||
print("[9] LilyGO LoRa T3S3")
|
||||
print(" | Select one of these options if you want to easily turn")
|
||||
print(" \\ / a supported development board into an RNode.")
|
||||
print(" '")
|
||||
print("[3] LilyGO LoRa32 v2.1 (aka T3 v1.6 / T3 v1.6.1)")
|
||||
print("[4] LilyGO LoRa32 v2.0")
|
||||
print("[5] LilyGO LoRa32 v1.0")
|
||||
print("[6] LilyGO T-Beam")
|
||||
print("[7] Heltec LoRa32 v2")
|
||||
print("[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.")
|
||||
print("[14] Heltec T114")
|
||||
print("")
|
||||
print("---------------------------------------------------------------------------")
|
||||
print("\nEnter the number that matches your device type:\n? ", end="")
|
||||
@@ -1673,7 +1728,7 @@ def main():
|
||||
try:
|
||||
c_dev = int(input())
|
||||
c_mod = False
|
||||
if c_dev < 1 or c_dev > 13:
|
||||
if c_dev < 1 or c_dev > 14:
|
||||
raise ValueError()
|
||||
elif c_dev == 1:
|
||||
selected_product = ROM.PRODUCT_RNODE
|
||||
@@ -1856,6 +1911,20 @@ def main():
|
||||
print("who would like to experiment with it. Hit enter to continue.")
|
||||
print("---------------------------------------------------------------------------")
|
||||
input()
|
||||
elif c_dev == 14:
|
||||
selected_product = ROM.PRODUCT_HELTEC_T114
|
||||
clear()
|
||||
print("")
|
||||
print("---------------------------------------------------------------------------")
|
||||
print(" Heltec T114 RNode Installer")
|
||||
print("")
|
||||
print("Important! Using RNode firmware on Heltec T114 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()
|
||||
@@ -1964,10 +2033,12 @@ def main():
|
||||
print("");
|
||||
print("[3] 433 MHz (with SX1268 chip)")
|
||||
print("[4] 868/915/923 MHz (with SX1262 chip)")
|
||||
print("");
|
||||
print("[5] 2.4 GHz (with SX1280 chip and PA)")
|
||||
print("\n? ", end="")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 4:
|
||||
if c_model < 1 or c_model > 5:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_A5
|
||||
@@ -1985,6 +2056,10 @@ def main():
|
||||
selected_model = ROM.MODEL_A6
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
elif c_model == 5:
|
||||
selected_model = ROM.MODEL_AC
|
||||
selected_mcu = ROM.MCU_ESP32
|
||||
selected_platform = ROM.PLATFORM_ESP32
|
||||
except Exception as e:
|
||||
print("That model does not exist, exiting now.")
|
||||
graceful_exit()
|
||||
@@ -2172,6 +2247,28 @@ def main():
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_HELTEC_T114:
|
||||
selected_mcu = ROM.MCU_NRF52
|
||||
print("\nWhat band is this Heltec T114 for?\n")
|
||||
print("[1] 433 MHz")
|
||||
print("[2] 868 MHz")
|
||||
print("[3] 915 MHz")
|
||||
print("[4] 923 MHz")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 4:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_C6
|
||||
selected_platform = ROM.PLATFORM_NRF52
|
||||
elif c_model > 1:
|
||||
selected_model = ROM.MODEL_C7
|
||||
selected_platform = ROM.PLATFORM_NRF52
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
exit()
|
||||
|
||||
elif selected_product == ROM.PRODUCT_RAK4631:
|
||||
selected_mcu = ROM.MCU_NRF52
|
||||
print("\nWhat band is this RAK4631 for?\n")
|
||||
@@ -2203,13 +2300,13 @@ def main():
|
||||
print("\n? ", end="")
|
||||
try:
|
||||
c_model = int(input())
|
||||
if c_model < 1 or c_model > 1:
|
||||
if c_model < 1 or c_model > 4:
|
||||
raise ValueError()
|
||||
elif c_model == 1:
|
||||
selected_model = ROM.MODEL_T4
|
||||
selected_model = ROM.MODEL_16
|
||||
selected_platform = ROM.PLATFORM_NRF52
|
||||
elif c_model > 1:
|
||||
selected_model = ROM.MODEL_T9
|
||||
selected_model = ROM.MODEL_17
|
||||
selected_platform = ROM.PLATFORM_NRF52
|
||||
except Exception as e:
|
||||
print("That band does not exist, exiting now.")
|
||||
@@ -2726,6 +2823,7 @@ def main():
|
||||
"0xe000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.boot_app0",
|
||||
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.bin",
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_heltec32v3.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_featheresp32.zip":
|
||||
@@ -2908,6 +3006,24 @@ def main():
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx127x.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_t3s3_sx1280_pa.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_sx1280_pa.boot_app0",
|
||||
"0x0", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.bootloader",
|
||||
"0x10000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.bin",
|
||||
"0x210000",UPD_DIR+"/"+selected_version+"/console_image.bin",
|
||||
"0x8000", UPD_DIR+"/"+selected_version+"/rnode_firmware_t3s3_sx1280_pa.partitions",
|
||||
]
|
||||
elif fw_filename == "rnode_firmware_tbeam_supreme.zip":
|
||||
return [
|
||||
sys.executable, flasher,
|
||||
@@ -3057,12 +3173,12 @@ def main():
|
||||
if args.platform == ROM.PLATFORM_ESP32:
|
||||
wants_fw_provision = True
|
||||
RNS.log("Waiting for ESP32 reset...")
|
||||
time.sleep(7)
|
||||
time.sleep(8)
|
||||
if args.platform == ROM.PLATFORM_NRF52:
|
||||
wants_fw_provision = True
|
||||
RNS.log("Waiting for NRF52 reset...")
|
||||
# Don't need to wait as long this time.
|
||||
time.sleep(5)
|
||||
time.sleep(6)
|
||||
else:
|
||||
RNS.log("Error from flasher ("+str(flash_status)+") while writing.")
|
||||
RNS.log("Some boards have trouble flashing at high speeds, and you can")
|
||||
@@ -3314,6 +3430,63 @@ def main():
|
||||
RNS.log("Firmware update file not found")
|
||||
graceful_exit()
|
||||
|
||||
if args.config:
|
||||
eeprom_reserved = 200
|
||||
if rnode.platform == ROM.PLATFORM_ESP32:
|
||||
eeprom_size = 296
|
||||
elif rnode.platform == ROM.PLATFORM_NRF52:
|
||||
eeprom_size = 296
|
||||
else:
|
||||
eeprom_size = 4096
|
||||
|
||||
eeprom_offset = eeprom_size-eeprom_reserved
|
||||
def ea(a):
|
||||
return a+eeprom_offset
|
||||
ec_bt = rnode.eeprom[ROM.ADDR_CONF_BT]
|
||||
ec_dint = rnode.eeprom[ROM.ADDR_CONF_DINT]
|
||||
ec_dadr = rnode.eeprom[ROM.ADDR_CONF_DADR]
|
||||
ec_dblk = rnode.eeprom[ROM.ADDR_CONF_DBLK]
|
||||
ec_drot = rnode.eeprom[ROM.ADDR_CONF_DROT]
|
||||
ec_pset = rnode.eeprom[ROM.ADDR_CONF_PSET]
|
||||
ec_pint = rnode.eeprom[ROM.ADDR_CONF_PINT]
|
||||
ec_bset = rnode.eeprom[ROM.ADDR_CONF_BSET]
|
||||
ec_dia = rnode.eeprom[ROM.ADDR_CONF_DIA]
|
||||
print("\nDevice configuration:")
|
||||
if ec_bt == 0x73:
|
||||
print(f" Bluetooth : Enabled")
|
||||
else:
|
||||
print(f" Bluetooth : Disabled")
|
||||
if ec_dia == 0x00:
|
||||
print(f" Interference avoidance : Enabled")
|
||||
else:
|
||||
print(f" Interference avoidance : Disabled")
|
||||
print( f" Display brightness : {ec_dint}")
|
||||
if ec_dadr == 0xFF:
|
||||
print(f" Display address : Default")
|
||||
else:
|
||||
print(f" Display address : {RNS.hexrep(ec_dadr, delimit=False)}")
|
||||
if ec_bset == 0x73 and ec_dblk != 0x00:
|
||||
print(f" Display blanking : {ec_dblk}s")
|
||||
else:
|
||||
print(f" Display blanking : Disabled")
|
||||
if ec_drot != 0xFF:
|
||||
if ec_drot == 0x00:
|
||||
rstr = "Landscape"
|
||||
if ec_drot == 0x01:
|
||||
rstr = "Portrait"
|
||||
if ec_drot == 0x02:
|
||||
rstr = "Landscape 180"
|
||||
if ec_drot == 0x03:
|
||||
rstr = "Portrait 180"
|
||||
print(f" Display rotation : {rstr}")
|
||||
else:
|
||||
print(f" Display rotation : Default")
|
||||
if ec_pset == 0x73:
|
||||
print(f" Neopixel Intensity : {ec_pint}")
|
||||
print("")
|
||||
|
||||
graceful_exit()
|
||||
|
||||
if args.eeprom_dump:
|
||||
RNS.log("EEPROM contents:")
|
||||
RNS.log(RNS.hexrep(rnode.eeprom))
|
||||
@@ -3354,6 +3527,31 @@ def main():
|
||||
RNS.log("Setting display timeout to "+str(di))
|
||||
rnode.set_display_blanking(di)
|
||||
|
||||
if isinstance(args.rotation, int):
|
||||
dr = args.rotation
|
||||
if dr < 0:
|
||||
dr = 0
|
||||
if dr > 3:
|
||||
dr = 3
|
||||
|
||||
RNS.log("Setting display rotation to "+str(dr))
|
||||
rnode.set_display_rotation(dr)
|
||||
|
||||
if isinstance(args.recondition_display, bool):
|
||||
if args.recondition_display:
|
||||
RNS.log("Starting display reconditioning")
|
||||
rnode.recondition_display()
|
||||
|
||||
if isinstance(args.ia_enable, bool):
|
||||
if args.ia_enable:
|
||||
RNS.log("Enabling interference avoidance")
|
||||
rnode.set_disable_interference_avoidance(False)
|
||||
|
||||
if isinstance(args.ia_disable, bool):
|
||||
if args.ia_disable:
|
||||
RNS.log("Disabling interference avoidance")
|
||||
rnode.set_disable_interference_avoidance(True)
|
||||
|
||||
if isinstance(args.np, int):
|
||||
di = args.np
|
||||
if di < 0:
|
||||
@@ -3475,7 +3673,7 @@ def main():
|
||||
elif rnode.platform == ROM.PLATFORM_NRF52:
|
||||
rnode_serial.close()
|
||||
RNS.log("Waiting for NRF52 reset...")
|
||||
time.sleep(14)
|
||||
time.sleep(18)
|
||||
selected_port = None
|
||||
ports = list_ports.comports()
|
||||
for port in ports:
|
||||
@@ -3686,7 +3884,9 @@ def main():
|
||||
if rnode.platform == ROM.PLATFORM_ESP32:
|
||||
rnode.hard_reset()
|
||||
RNS.log("Waiting for ESP32 reset...")
|
||||
time.sleep(6.5)
|
||||
time.sleep(7)
|
||||
if selected_model in [ROM.MODEL_AC, ROM.MODEL_A6, ROM.MODEL_A1, ROM.MODEL_AA, ROM.MODEL_A5]:
|
||||
time.sleep(5)
|
||||
|
||||
elif rnode.platform == ROM.PLATFORM_NRF52:
|
||||
# Wait a few seconds before hard resetting.
|
||||
@@ -3705,7 +3905,7 @@ def main():
|
||||
|
||||
# Give plenty of time for to allow for
|
||||
# potential e-ink display refresh too.
|
||||
time.sleep(14)
|
||||
time.sleep(20)
|
||||
|
||||
# After the hard reset, the port number will
|
||||
# change. We need to find the new port number,
|
||||
|
||||
@@ -42,6 +42,8 @@ def program_setup(configdir, verbosity = 0, quietness = 0, service = False):
|
||||
if reticulum.is_connected_to_shared_instance:
|
||||
RNS.log("Started rnsd version {version} connected to another shared local instance, this is probably NOT what you want!".format(version=__version__), RNS.LOG_WARNING)
|
||||
else:
|
||||
if RNS.Reticulum.get_instance().shared_instance_interface:
|
||||
RNS.Reticulum.get_instance().shared_instance_interface.server.daemon_threads = True
|
||||
RNS.log("Started rnsd version {version}".format(version=__version__), RNS.LOG_NOTICE)
|
||||
|
||||
while True:
|
||||
|
||||
+91
-12
@@ -135,8 +135,26 @@ def get_remote_status(destination_hash, include_lstats, identity, no_output=Fals
|
||||
|
||||
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False,
|
||||
lstats=False, sorting=None, sort_reverse=False, remote=None, management_identity=None,
|
||||
remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT):
|
||||
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
|
||||
remote_timeout=RNS.Transport.PATH_REQUEST_TIMEOUT, must_exit=True, rns_instance=None, traffic_totals=False):
|
||||
|
||||
if remote:
|
||||
require_shared = False
|
||||
else:
|
||||
require_shared = True
|
||||
|
||||
try:
|
||||
if rns_instance:
|
||||
reticulum = rns_instance
|
||||
must_exit = False
|
||||
else:
|
||||
reticulum = RNS.Reticulum(configdir=configdir, loglevel=3+verbosity, require_shared_instance=require_shared)
|
||||
|
||||
except Exception as e:
|
||||
print("No shared RNS instance available to get status from")
|
||||
if must_exit:
|
||||
exit(1)
|
||||
else:
|
||||
return
|
||||
|
||||
link_count = None
|
||||
stats = None
|
||||
@@ -164,7 +182,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
exit(20)
|
||||
if must_exit:
|
||||
exit(20)
|
||||
else:
|
||||
return
|
||||
|
||||
else:
|
||||
if lstats:
|
||||
@@ -193,7 +214,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
i[k] = RNS.hexrep(i[k], delimit=False)
|
||||
|
||||
print(json.dumps(stats))
|
||||
exit()
|
||||
if must_exit:
|
||||
exit()
|
||||
else:
|
||||
return
|
||||
|
||||
interfaces = stats["interfaces"]
|
||||
if sorting != None and isinstance(sorting, str):
|
||||
@@ -204,6 +228,10 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
interfaces.sort(key=lambda i: i["rxb"], reverse=not sort_reverse)
|
||||
if sorting == "tx":
|
||||
interfaces.sort(key=lambda i: i["txb"], reverse=not sort_reverse)
|
||||
if sorting == "rxs":
|
||||
interfaces.sort(key=lambda i: i["rxs"], reverse=not sort_reverse)
|
||||
if sorting == "txs":
|
||||
interfaces.sort(key=lambda i: i["txs"], reverse=not sort_reverse)
|
||||
if sorting == "traffic":
|
||||
interfaces.sort(key=lambda i: i["rxb"]+i["txb"], reverse=not sort_reverse)
|
||||
if sorting == "announces" or sorting == "announce":
|
||||
@@ -292,10 +320,17 @@ 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 "noise_floor" in ifstat:
|
||||
if ifstat["noise_floor"] != None:
|
||||
print(" Noise Fl. : {nfl} dBm".format(nfl=str(ifstat["noise_floor"])))
|
||||
else:
|
||||
print(" Noise Fl. : Unknown")
|
||||
|
||||
if "battery_percent" in ifstat and ifstat["battery_percent"] != None:
|
||||
try:
|
||||
bpi = int(ifstat["battery_percent"])
|
||||
print(" Battery : {bp}%".format(bp=bpi))
|
||||
bss = ifstat["battery_state"]
|
||||
print(f" Battery : {bpi}% ({bss})")
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -303,8 +338,8 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
print(" Airtime : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["airtime_short"]),atl=str(ifstat["airtime_long"])))
|
||||
|
||||
if "channel_load_short" in ifstat and "channel_load_long" in ifstat:
|
||||
print(" Ch.Load : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["channel_load_short"]),atl=str(ifstat["channel_load_long"])))
|
||||
|
||||
print(" Ch. Load : {ats}% (15s), {atl}% (1h)".format(ats=str(ifstat["channel_load_short"]),atl=str(ifstat["channel_load_long"])))
|
||||
|
||||
if "peers" in ifstat and ifstat["peers"] != None:
|
||||
print(" Peers : {np} reachable".format(np=ifstat["peers"]))
|
||||
|
||||
@@ -336,7 +371,21 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
print(" Announces : {iaf}↑".format(iaf=RNS.prettyfrequency(ifstat["outgoing_announce_frequency"])))
|
||||
print(" {iaf}↓".format(iaf=RNS.prettyfrequency(ifstat["incoming_announce_frequency"])))
|
||||
|
||||
print(" Traffic : {txb}↑\n {rxb}↓".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
|
||||
rxb_str = "↓"+RNS.prettysize(ifstat["rxb"])
|
||||
txb_str = "↑"+RNS.prettysize(ifstat["txb"])
|
||||
strdiff = len(rxb_str)-len(txb_str)
|
||||
if strdiff > 0:
|
||||
txb_str += " "*strdiff
|
||||
elif strdiff < 0:
|
||||
rxb_str += " "*-strdiff
|
||||
|
||||
rxstat = rxb_str
|
||||
txstat = txb_str
|
||||
if "rxs" in ifstat and "txs" in ifstat:
|
||||
rxstat += " "+RNS.prettyspeed(ifstat["rxs"])
|
||||
txstat += " "+RNS.prettyspeed(ifstat["txs"])
|
||||
|
||||
print(f" Traffic : {txstat}\n {rxstat}")
|
||||
|
||||
lstr = ""
|
||||
if link_count != None and lstats:
|
||||
@@ -346,6 +395,19 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
else:
|
||||
lstr = f" {link_count} entr{ms} in link table"
|
||||
|
||||
if traffic_totals:
|
||||
rxb_str = "↓"+RNS.prettysize(stats["rxb"])
|
||||
txb_str = "↑"+RNS.prettysize(stats["txb"])
|
||||
strdiff = len(rxb_str)-len(txb_str)
|
||||
if strdiff > 0:
|
||||
txb_str += " "*strdiff
|
||||
elif strdiff < 0:
|
||||
rxb_str += " "*-strdiff
|
||||
|
||||
rxstat = rxb_str+" "+RNS.prettyspeed(stats["rxs"])
|
||||
txstat = txb_str+" "+RNS.prettyspeed(stats["txs"])
|
||||
print(f"\n Totals : {txstat}\n {rxstat}")
|
||||
|
||||
if "transport_id" in stats and stats["transport_id"] != None:
|
||||
print("\n Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
|
||||
if "probe_responder" in stats and stats["probe_responder"] != None:
|
||||
@@ -363,9 +425,12 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=
|
||||
print("Could not get RNS status")
|
||||
else:
|
||||
print("Could not get RNS status from remote transport instance "+RNS.prettyhexrep(identity_hash))
|
||||
exit(1)
|
||||
if must_exit:
|
||||
exit(2)
|
||||
else:
|
||||
return
|
||||
|
||||
def main():
|
||||
def main(must_exit=True, rns_instance=None):
|
||||
try:
|
||||
parser = argparse.ArgumentParser(description="Reticulum Network Stack Status")
|
||||
parser.add_argument("--config", action="store", default=None, help="path to alternative Reticulum config directory", type=str)
|
||||
@@ -395,11 +460,19 @@ def main():
|
||||
default=False,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-t",
|
||||
"--totals",
|
||||
action="store_true",
|
||||
help="display traffic totals",
|
||||
default=False,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-s",
|
||||
"--sort",
|
||||
action="store",
|
||||
help="sort interfaces by [rate, traffic, rx, tx, announces, arx, atx, held]",
|
||||
help="sort interfaces by [rate, traffic, rx, tx, rxs, txs, announces, arx, atx, held]",
|
||||
default=None,
|
||||
type=str
|
||||
)
|
||||
@@ -471,11 +544,17 @@ def main():
|
||||
remote=args.R,
|
||||
management_identity=args.i,
|
||||
remote_timeout=args.w,
|
||||
must_exit=must_exit,
|
||||
rns_instance=rns_instance,
|
||||
traffic_totals=args.totals,
|
||||
)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
exit()
|
||||
if must_exit:
|
||||
exit()
|
||||
else:
|
||||
return
|
||||
|
||||
def speed_str(num, suffix='bps'):
|
||||
units = ['','k','M','G','T','P','E','Z']
|
||||
|
||||
@@ -28,8 +28,8 @@ import argparse
|
||||
import shlex
|
||||
import time
|
||||
import sys
|
||||
import tty
|
||||
import os
|
||||
#import tty
|
||||
|
||||
from RNS._version import __version__
|
||||
|
||||
|
||||
+206
-106
@@ -24,6 +24,7 @@ import os
|
||||
import sys
|
||||
import glob
|
||||
import time
|
||||
import datetime
|
||||
import random
|
||||
import threading
|
||||
|
||||
@@ -43,8 +44,10 @@ from .Resource import Resource, ResourceAdvertisement
|
||||
from .Cryptography import HKDF
|
||||
from .Cryptography import Hashes
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
|
||||
LOG_CRITICAL = 0
|
||||
LOG_ERROR = 1
|
||||
@@ -57,13 +60,16 @@ LOG_EXTREME = 7
|
||||
|
||||
LOG_STDOUT = 0x91
|
||||
LOG_FILE = 0x92
|
||||
LOG_CALLBACK = 0x93
|
||||
|
||||
LOG_MAXSIZE = 5*1024*1024
|
||||
|
||||
loglevel = LOG_NOTICE
|
||||
logfile = None
|
||||
logdest = LOG_STDOUT
|
||||
logcall = None
|
||||
logtimefmt = "%Y-%m-%d %H:%M:%S"
|
||||
logtimefmt_p = "%H:%M:%S.%f"
|
||||
compact_log_fmt = False
|
||||
|
||||
instance_random = random.Random()
|
||||
@@ -104,40 +110,51 @@ def timestamp_str(time_s):
|
||||
timestamp = time.localtime(time_s)
|
||||
return time.strftime(logtimefmt, timestamp)
|
||||
|
||||
def log(msg, level=3, _override_destination = False):
|
||||
def precise_timestamp_str(time_s):
|
||||
return datetime.datetime.now().strftime(logtimefmt_p)[:-3]
|
||||
|
||||
def log(msg, level=3, _override_destination = False, pt=False):
|
||||
global _always_override_destination, compact_log_fmt
|
||||
msg = str(msg)
|
||||
if loglevel >= level:
|
||||
if not compact_log_fmt:
|
||||
logstring = "["+timestamp_str(time.time())+"] ["+loglevelname(level)+"] "+msg
|
||||
if pt:
|
||||
logstring = "["+precise_timestamp_str(time.time())+"] ["+loglevelname(level)+"] "+msg
|
||||
else:
|
||||
logstring = "["+timestamp_str(time.time())+"] "+msg
|
||||
if not compact_log_fmt:
|
||||
logstring = "["+timestamp_str(time.time())+"] ["+loglevelname(level)+"] "+msg
|
||||
else:
|
||||
logstring = "["+timestamp_str(time.time())+"] "+msg
|
||||
|
||||
logging_lock.acquire()
|
||||
with logging_lock:
|
||||
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
|
||||
print(logstring)
|
||||
|
||||
if (logdest == LOG_STDOUT or _always_override_destination or _override_destination):
|
||||
print(logstring)
|
||||
logging_lock.release()
|
||||
elif (logdest == LOG_FILE and logfile != None):
|
||||
try:
|
||||
file = open(logfile, "a")
|
||||
file.write(logstring+"\n")
|
||||
file.close()
|
||||
|
||||
if os.path.getsize(logfile) > LOG_MAXSIZE:
|
||||
prevfile = logfile+".1"
|
||||
if os.path.isfile(prevfile):
|
||||
os.unlink(prevfile)
|
||||
os.rename(logfile, prevfile)
|
||||
|
||||
elif (logdest == LOG_FILE and logfile != None):
|
||||
try:
|
||||
file = open(logfile, "a")
|
||||
file.write(logstring+"\n")
|
||||
file.close()
|
||||
|
||||
if os.path.getsize(logfile) > LOG_MAXSIZE:
|
||||
prevfile = logfile+".1"
|
||||
if os.path.isfile(prevfile):
|
||||
os.unlink(prevfile)
|
||||
os.rename(logfile, prevfile)
|
||||
except Exception as e:
|
||||
_always_override_destination = True
|
||||
log("Exception occurred while writing log message to log file: "+str(e), LOG_CRITICAL)
|
||||
log("Dumping future log events to console!", LOG_CRITICAL)
|
||||
log(msg, level)
|
||||
|
||||
logging_lock.release()
|
||||
except Exception as e:
|
||||
logging_lock.release()
|
||||
_always_override_destination = True
|
||||
log("Exception occurred while writing log message to log file: "+str(e), LOG_CRITICAL)
|
||||
log("Dumping future log events to console!", LOG_CRITICAL)
|
||||
log(msg, level)
|
||||
elif logdest == LOG_CALLBACK:
|
||||
try:
|
||||
logcall(logstring)
|
||||
except Exception as e:
|
||||
_always_override_destination = True
|
||||
log("Exception occurred while calling external log handler: "+str(e), LOG_CRITICAL)
|
||||
log("Dumping future log events to console!", LOG_CRITICAL)
|
||||
log(msg, level)
|
||||
|
||||
|
||||
def rand():
|
||||
@@ -218,6 +235,11 @@ def prettydistance(m, suffix="m"):
|
||||
return "%.2f %s%s" % (num, last_unit, suffix)
|
||||
|
||||
def prettytime(time, verbose=False, compact=False):
|
||||
neg = False
|
||||
if time < 0:
|
||||
time = abs(time)
|
||||
neg = True
|
||||
|
||||
days = int(time // (24 * 3600))
|
||||
time = time % (24 * 3600)
|
||||
hours = int(time // 3600)
|
||||
@@ -268,10 +290,17 @@ def prettytime(time, verbose=False, compact=False):
|
||||
if tstr == "":
|
||||
return "0s"
|
||||
else:
|
||||
return tstr
|
||||
if not neg:
|
||||
return tstr
|
||||
else:
|
||||
return f"-{tstr}"
|
||||
|
||||
def prettyshorttime(time, verbose=False, compact=False):
|
||||
neg = False
|
||||
time = time*1e6
|
||||
if time < 0:
|
||||
time = abs(time)
|
||||
neg = True
|
||||
|
||||
seconds = int(time // 1e6); time %= 1e6
|
||||
milliseconds = int(time // 1e3); time %= 1e3
|
||||
@@ -315,7 +344,10 @@ def prettyshorttime(time, verbose=False, compact=False):
|
||||
if tstr == "":
|
||||
return "0us"
|
||||
else:
|
||||
return tstr
|
||||
if not neg:
|
||||
return tstr
|
||||
else:
|
||||
return f"-{tstr}"
|
||||
|
||||
def phyparams():
|
||||
print("Required Physical Layer MTU : "+str(Reticulum.MTU)+" bytes")
|
||||
@@ -333,93 +365,161 @@ def exit():
|
||||
print("")
|
||||
sys.exit(0)
|
||||
|
||||
class Profiler:
|
||||
_ran = False
|
||||
profilers = {}
|
||||
tags = {}
|
||||
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def get_profiler(tag=None, super_tag=None):
|
||||
if tag in Profiler.profilers:
|
||||
return Profiler.profilers[tag]
|
||||
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 = Profiler(tag, super_tag)
|
||||
Profiler.profilers[tag] = profiler
|
||||
return profiler
|
||||
|
||||
profiler_tags[tag]["threads"][thread_ident]["current_start"] = time.perf_counter()
|
||||
def __init__(self, tag=None, super_tag=None):
|
||||
self.paused = False
|
||||
self.pause_time = 0
|
||||
self.pause_started = None
|
||||
self.tag = tag
|
||||
self.super_tag = super_tag
|
||||
if self.super_tag in Profiler.profilers:
|
||||
self.super_profiler = Profiler.profilers[self.super_tag]
|
||||
self.pause_super = self.super_profiler.pause
|
||||
self.resume_super = self.super_profiler.resume
|
||||
else:
|
||||
def noop(self=None):
|
||||
pass
|
||||
self.super_profiler = None
|
||||
self.pause_super = noop
|
||||
self.resume_super = noop
|
||||
|
||||
except Exception as e:
|
||||
trace_exception(e)
|
||||
def __enter__(self):
|
||||
self.pause_super()
|
||||
tag = self.tag
|
||||
super_tag = self.super_tag
|
||||
thread_ident = threading.get_ident()
|
||||
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": []}
|
||||
|
||||
def profiler_results():
|
||||
from statistics import mean, median, stdev
|
||||
results = {}
|
||||
|
||||
for tag in profiler_tags:
|
||||
tag_captures = []
|
||||
tag_entry = profiler_tags[tag]
|
||||
Profiler.tags[tag]["threads"][thread_ident]["current_start"] = time.perf_counter()
|
||||
self.resume_super()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.pause_super()
|
||||
tag = self.tag
|
||||
super_tag = self.super_tag
|
||||
end = time.perf_counter() - self.pause_time
|
||||
self.pause_time = 0
|
||||
thread_ident = threading.get_ident()
|
||||
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
|
||||
self.resume_super()
|
||||
|
||||
def pause(self, pause_started=None):
|
||||
if not self.paused:
|
||||
self.paused = True
|
||||
self.pause_started = pause_started or time.perf_counter()
|
||||
self.pause_super(self.pause_started)
|
||||
|
||||
def resume(self):
|
||||
if self.paused:
|
||||
self.pause_time += time.perf_counter() - self.pause_started
|
||||
self.paused = False
|
||||
self.resume_super()
|
||||
|
||||
@staticmethod
|
||||
def ran():
|
||||
return Profiler._ran
|
||||
|
||||
@staticmethod
|
||||
def results():
|
||||
from statistics import mean, median, stdev
|
||||
results = {}
|
||||
|
||||
for thread_ident in tag_entry["threads"]:
|
||||
thread_entry = tag_entry["threads"][thread_ident]
|
||||
thread_captures = thread_entry["captures"]
|
||||
sample_count = len(thread_captures)
|
||||
for tag in Profiler.tags:
|
||||
tag_captures = []
|
||||
tag_entry = Profiler.tags[tag]
|
||||
|
||||
if sample_count > 2:
|
||||
thread_results = {
|
||||
"count": sample_count,
|
||||
"mean": mean(thread_captures),
|
||||
"median": median(thread_captures),
|
||||
"stdev": stdev(thread_captures)
|
||||
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 > 1:
|
||||
thread_results = {
|
||||
"count": sample_count,
|
||||
"mean": mean(thread_captures),
|
||||
"median": median(thread_captures),
|
||||
"stdev": stdev(thread_captures)
|
||||
}
|
||||
elif sample_count == 1:
|
||||
thread_results = {
|
||||
"count": sample_count,
|
||||
"mean": mean(thread_captures),
|
||||
"median": median(thread_captures),
|
||||
"stdev": None
|
||||
}
|
||||
|
||||
tag_captures.extend(thread_captures)
|
||||
|
||||
sample_count = len(tag_captures)
|
||||
if sample_count > 1:
|
||||
tag_results = {
|
||||
"name": tag,
|
||||
"super": tag_entry["super"],
|
||||
"count": len(tag_captures),
|
||||
"mean": mean(tag_captures),
|
||||
"median": median(tag_captures),
|
||||
"stdev": stdev(tag_captures)
|
||||
}
|
||||
elif sample_count == 1:
|
||||
tag_results = {
|
||||
"name": tag,
|
||||
"super": tag_entry["super"],
|
||||
"count": len(tag_captures),
|
||||
"mean": mean(tag_captures),
|
||||
"median": median(tag_captures),
|
||||
"stdev": None
|
||||
}
|
||||
|
||||
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)
|
||||
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"]; median = tag["median"]; stdev = tag["stdev"]
|
||||
print( f"{ind}{name}")
|
||||
print( f"{ind} Samples : {count}")
|
||||
if stdev != None:
|
||||
print(f"{ind} Mean : {prettyshorttime(mean)}")
|
||||
print(f"{ind} Median : {prettyshorttime(median)}")
|
||||
print(f"{ind} St.dev. : {prettyshorttime(stdev)}")
|
||||
print( f"{ind} Total : {prettyshorttime(mean*count)}")
|
||||
print("")
|
||||
|
||||
print("\nProfiler results:\n")
|
||||
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)
|
||||
tag = results[tag_name]
|
||||
if tag["super"] == None:
|
||||
print_results_recursive(tag, results)
|
||||
|
||||
|
||||
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)
|
||||
profile = Profiler.get_profiler
|
||||
+1
-1
@@ -1 +1 @@
|
||||
__version__ = "0.8.2"
|
||||
__version__ = "0.9.0"
|
||||
|
||||
Vendored
+4
-2
@@ -1,5 +1,7 @@
|
||||
import os
|
||||
import glob
|
||||
|
||||
modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
__all__ = [ os.path.basename(f)[:-3] for f in modules if not f.endswith('__init__.py')]
|
||||
py_modules = glob.glob(os.path.dirname(__file__)+"/*.py")
|
||||
pyc_modules = glob.glob(os.path.dirname(__file__)+"/*.pyc")
|
||||
modules = py_modules+pyc_modules
|
||||
__all__ = list(set([os.path.basename(f).replace(".pyc", "").replace(".py", "") for f in modules if not (f.endswith("__init__.py") or f.endswith("__init__.pyc"))]))
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
# Sphinx build info version 1
|
||||
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
|
||||
config: bd0a676b0f490cda5d841ab7cd7cade1
|
||||
config: e0cd5c3608b12712050d5c10fe86c594
|
||||
tags: 645f666f9bcd5a90fca523b33c5a78b7
|
||||
|
||||
@@ -125,4 +125,18 @@ interface to efficiently pass files of any size over a Reticulum :ref:`Link<api-
|
||||
|
||||
.. literalinclude:: ../../Examples/Filetransfer.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py>`_.
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py>`_.
|
||||
|
||||
.. _example-custominterface:
|
||||
|
||||
Custom Interfaces
|
||||
=================
|
||||
|
||||
The *ExampleInterface* demonstrates creating custom interfaces for Reticulum.
|
||||
Any number of custom interfaces can be loaded and utilised by Reticulum, and
|
||||
will be fully on-par with natively included interfaces, including all supported
|
||||
:ref:`interface modes<interfaces-modes>` and :ref:`common configuration options<interfaces-options>`.
|
||||
|
||||
.. literalinclude:: ../../Examples/ExampleInterface.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/ExampleInterface.py>`_.
|
||||
@@ -27,9 +27,14 @@ and install them offline using ``pip``:
|
||||
|
||||
pip install ./rns-0.5.1-py3-none-any.whl
|
||||
|
||||
For more detailed installation instructions, please see the
|
||||
:ref:`Platform-Specific Install Notes<install-guides>` section.
|
||||
|
||||
After installation is complete, it might be helpful to refer to the
|
||||
:ref:`Using Reticulum on Your System<using-main>` chapter.
|
||||
|
||||
Resolving Dependency & Installation Issues
|
||||
=============================================
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some platforms, there may not be binary packages available for all dependencies, and
|
||||
``pip`` installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:
|
||||
@@ -100,10 +105,12 @@ You can install Nomad Network via pip:
|
||||
# ... and run
|
||||
nomadnet
|
||||
|
||||
**Please Note**: If this is the very first time you use pip to install a program
|
||||
on your system, you might need to reboot your system for your program to become
|
||||
available. If you get a "command not found" error or similar when running the
|
||||
program, reboot your system and try again.
|
||||
.. note::
|
||||
If this is the very first time you use ``pip`` to install a program
|
||||
on your system, you might need to reboot your system for your program to become
|
||||
available. If you get a "command not found" error or similar when running the
|
||||
program, reboot your system and try again. In some cases, you may even need to
|
||||
manually add the ``pip`` install path to your ``PATH`` environment variable.
|
||||
|
||||
Sideband
|
||||
^^^^^^^^
|
||||
@@ -305,6 +312,20 @@ you are welcome to head over to the `GitHub discussion pages <https://github.com
|
||||
and propose adding an interface for the hardware.
|
||||
|
||||
|
||||
Creating and Using Custom Interfaces
|
||||
===========================================
|
||||
|
||||
While Reticulum includes a flexible and broad range of built-in interfaces, these
|
||||
will not cover every conceivable type of communications hardware that Reticulum
|
||||
can potentially use to communicate.
|
||||
|
||||
It is therefore possible to easily write your own interface modules, that can be
|
||||
loaded at run-time and used on-par with any of the built-in interface types.
|
||||
|
||||
For more information on this subject, and code examples to build on, please see
|
||||
the :ref:`Configuring Interfaces<interfaces-main>` chapter.
|
||||
|
||||
|
||||
Develop a Program with Reticulum
|
||||
===========================================
|
||||
If you want to develop programs that use Reticulum, the easiest way to get
|
||||
@@ -318,14 +339,8 @@ The above command will install Reticulum and dependencies, and you will be
|
||||
ready to import and use RNS in your own programs. The next step will most
|
||||
likely be to look at some :ref:`Example Programs<examples-main>`.
|
||||
|
||||
For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install pyserial
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
The entire Reticulum API is documented in the :ref:`API Reference<api-main>`
|
||||
chapter of this manual.
|
||||
|
||||
|
||||
Participate in Reticulum Development
|
||||
@@ -373,6 +388,7 @@ your first pull request, it is probably a good idea to introduce yourself on
|
||||
the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussions>`_,
|
||||
or ask one of the developers or maintainers for a good place to start.
|
||||
|
||||
.. _install-guides:
|
||||
|
||||
Platform-Specific Install Notes
|
||||
==============================================
|
||||
@@ -451,7 +467,7 @@ here at a later point. Until then you can use the `Sideband source code <https:/
|
||||
ARM64
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
binaries. On such systems, you may need to install ``python3-dev`` (or similar) before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
@@ -463,16 +479,8 @@ installing Reticulum or programs that depend on Reticulum.
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.
|
||||
With these packages installed, ``pip`` will be able to build any missing dependencies
|
||||
on your system locally.
|
||||
|
||||
|
||||
Debian Bookworm
|
||||
@@ -503,10 +511,152 @@ following section:
|
||||
[global]
|
||||
break-system-packages = true
|
||||
|
||||
Please note that the "break-system-packages" directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems.
|
||||
For a one-shot installation of Reticulum, without globally enabling the ``break-system-packages``
|
||||
option, you can use the following command:
|
||||
|
||||
.. code:: text
|
||||
|
||||
pip install rns --break-system-packages
|
||||
|
||||
.. note::
|
||||
The ``--break-system-packages`` directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.
|
||||
|
||||
|
||||
MacOS
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
To install Reticulum on macOS, you will need to have Python and the ``pip`` package
|
||||
manager installed.
|
||||
|
||||
Systems running macOS can vary quite widely in whether or not Python is pre-installed,
|
||||
and if it is, which version is installed, and whether the ``pip`` package manager is
|
||||
also installed and set up. If in doubt, you can `download and install <https://www.python.org/downloads/>`_
|
||||
Python manually.
|
||||
|
||||
When Python and ``pip`` is available on your system, simply open a terminal window
|
||||
and use one of the following commands:
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Reticulum and utilities with pip:
|
||||
pip3 install rns
|
||||
|
||||
# On some versions, you may need to use the
|
||||
# flag --break-system-packages to install:
|
||||
pip3 install rns --break-system-packages
|
||||
|
||||
.. note::
|
||||
The ``--break-system-packages`` directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.
|
||||
|
||||
Additionally, some version combinations of macOS and Python require you to
|
||||
manually add your installed ``pip`` packages directory to your `PATH` environment
|
||||
variable, before you can use installed commands in your terminal. Usually, adding
|
||||
the following line to your shell init script (for example ``~/.zshrc``) will be enough:
|
||||
|
||||
.. code::
|
||||
|
||||
export PATH=$PATH:~/Library/Python/3.9/bin
|
||||
|
||||
Adjust Python version and shell init script location according to your system.
|
||||
|
||||
|
||||
OpenWRT
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On OpenWRT systems with sufficient storage and memory, you can install
|
||||
Reticulum and related utilities using the `opkg` package manager and `pip`.
|
||||
|
||||
.. note::
|
||||
|
||||
At the time of releasing this manual, work is underway to create pre-built
|
||||
Reticulum packages for OpenWRT, with full configuration, service
|
||||
and ``uci`` integration. Please see the `feed-reticulum <https://github.com/gretel/feed-reticulum>`_
|
||||
and `reticulum-openwrt <https://github.com/gretel/reticulum-openwrt>`_
|
||||
repositories for more information.
|
||||
|
||||
To install Reticulum on OpenWRT, first log into a command line session, and
|
||||
then use the following instructions:
|
||||
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
opkg install python3 python3-pip python3-cryptography python3-pyserial
|
||||
|
||||
# Install Reticulum
|
||||
pip install rns
|
||||
|
||||
# Start rnsd with debug logging enabled
|
||||
rnsd -vvv
|
||||
|
||||
.. note::
|
||||
|
||||
The above instructions have been verified and tested on OpenWRT 21.02 only.
|
||||
It is likely that other versions may require slightly altered installation
|
||||
commands or package names. You will also need enough free space in your
|
||||
overlay FS, and enough free RAM to actually run Reticulum and any related
|
||||
programs and utilities.
|
||||
|
||||
Depending on your device configuration, you may need to adjust firewall rules
|
||||
for Reticulum connectivity to and from your device to work. Until proper
|
||||
packaging is ready, you will also need to manually create a service or startup
|
||||
script to automatically laucnh Reticulum at boot time.
|
||||
|
||||
Please also note that the `AutoInterface` requires link-local IPv6 addresses
|
||||
to be enabled for any Ethernet and WiFi devices you intend to use. If ``ip a``
|
||||
shows an address starting with ``fe80::`` for the device in question,
|
||||
``AutoInterface`` should work for that device.
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies. If Python and the
|
||||
`pip` package manager is not already installed, do that first, and then
|
||||
install Reticulum using `pip`.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
sudo apt install python3 python3-pip python3-cryptography python3-pyserial
|
||||
|
||||
# Install Reticulum
|
||||
pip install rns --break-system-packages
|
||||
|
||||
.. note::
|
||||
The ``--break-system-packages`` directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing required build dependencies,
|
||||
and is not detailed in this manual.
|
||||
|
||||
|
||||
RISC-V
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including RISC-V, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` (or similar) before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
With these packages installed, ``pip`` will be able to build any missing dependencies
|
||||
on your system locally.
|
||||
|
||||
|
||||
Ubuntu Lunar
|
||||
@@ -537,13 +687,52 @@ following section:
|
||||
[global]
|
||||
break-system-packages = true
|
||||
|
||||
Please note that the "break-system-packages" directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this _could_ in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems.
|
||||
For a one-shot installation of Reticulum, without globally enabling the ``break-system-packages``
|
||||
option, you can use the following command:
|
||||
|
||||
.. code:: text
|
||||
|
||||
pip install rns --break-system-packages
|
||||
|
||||
.. note::
|
||||
The ``--break-system-packages`` directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.
|
||||
|
||||
Windows
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On Windows operating systems, the easiest way to install Reticulum is by using the
|
||||
``pip`` package manager from the command line (either the command prompt or Windows
|
||||
Powershell).
|
||||
|
||||
If you don't already have Python installed, `download and install Python <https://www.python.org/downloads/>`_.
|
||||
At the time of publication of this manual, the recommended version is `Python 3.12.7 <https://www.python.org/downloads/release/python-3127>`_.
|
||||
|
||||
**Important!** When asked by the installer, make sure to add the Python program to
|
||||
your PATH environment variables. If you don't do this, you will not be able to
|
||||
use the ``pip`` installer, or run the included Reticulum utility programs (such as
|
||||
``rnsd`` and ``rnstatus``) from the command line.
|
||||
|
||||
After installing Python, open the command prompt or Windows Powershell, and type:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install rns
|
||||
|
||||
You can now use Reticulum and all included utility programs directly from your
|
||||
preferred command line interface.
|
||||
|
||||
Pure-Python Reticulum
|
||||
==============================================
|
||||
|
||||
.. warning::
|
||||
If you use the ``rnspure`` package to run Reticulum on systems that
|
||||
do not support `PyCA/cryptography <https://github.com/pyca/cryptography>`_, it is
|
||||
important that you read and understand the :ref:`Cryptographic Primitives <understanding-primitives>`
|
||||
section of this manual.
|
||||
|
||||
In some rare cases, and on more obscure system types, it is not possible to
|
||||
install one or more dependencies. In such situations,
|
||||
you can use the ``rnspure`` package instead of the ``rns`` package, or use ``pip``
|
||||
@@ -558,8 +747,3 @@ only if they are *needed* and *available*. If for example you want to use Reticu
|
||||
on a system that cannot support ``pyserial``, it is perfectly possible to do so using
|
||||
the `rnspure` package, but Reticulum will not be able to use serial-based interfaces.
|
||||
All other available modules will still be loaded when needed.
|
||||
|
||||
**Please Note!** If you use the `rnspure` package to run Reticulum on systems that
|
||||
do not support `PyCA/cryptography <https://github.com/pyca/cryptography>`_, it is
|
||||
important that you read and understand the :ref:`Cryptographic Primitives <understanding-primitives>`
|
||||
section of this manual.
|
||||
|
||||
@@ -75,8 +75,8 @@ completely from scratch, to your exact desired specifications, this chapter
|
||||
will explain the easiest possible approach to creating RNodes: Using common
|
||||
LoRa development boards. This approach can be boiled down to two simple steps:
|
||||
|
||||
1. Obtain one or more supported development boards
|
||||
2. Install the RNode firmware with the automated installer
|
||||
1. Obtain one or more :ref:`supported development boards<rnode-supported>`
|
||||
2. Install the RNode firmware with the :ref:`automated installer<rnode-installation>`
|
||||
|
||||
Once the firmware has been installed and provisioned by the install script, it
|
||||
is ready to use with any software that supports RNodes, including Reticulum.
|
||||
@@ -85,8 +85,8 @@ to the configuration.
|
||||
|
||||
.. _rnode-supported:
|
||||
|
||||
Supported Boards
|
||||
^^^^^^^^^^^^^^^^
|
||||
Supported Boards and Devices
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
To create one or more RNodes, you will need to obtain supported development
|
||||
boards. The following boards are supported by the auto-installer.
|
||||
|
||||
|
||||
@@ -19,6 +19,16 @@ types, have a look at the :ref:`Building Networks<networks-main>` chapter of thi
|
||||
manual.
|
||||
|
||||
|
||||
.. _interfaces-custom:
|
||||
|
||||
Custom Interfaces
|
||||
=================
|
||||
|
||||
In addition to the built-in interface types, Reticulum is **fully extensible** with
|
||||
custom, user- or community-supplied interfaces, and creating custom interface
|
||||
modules is straightforward. Please see the :ref:`custom interface<example-custominterface>`
|
||||
example for basic interface code to build upon.
|
||||
|
||||
.. _interfaces-auto:
|
||||
|
||||
Auto Interface
|
||||
@@ -152,11 +162,12 @@ It can take anywhere from a few seconds to a few minutes to establish
|
||||
I2P connections to the desired peers, so Reticulum handles the process
|
||||
in the background, and will output relevant events to the log.
|
||||
|
||||
**Please Note!** While the I2P interface is the simplest way to use
|
||||
Reticulum over I2P, it is also possible to tunnel the TCP server and
|
||||
client interfaces over I2P manually. This can be useful in situations
|
||||
where more control is needed, but requires manual tunnel setup through
|
||||
the I2P daemon configuration.
|
||||
.. note::
|
||||
While the I2P interface is the simplest way to use
|
||||
Reticulum over I2P, it is also possible to tunnel the TCP server and
|
||||
client interfaces over I2P manually. This can be useful in situations
|
||||
where more control is needed, but requires manual tunnel setup through
|
||||
the I2P daemon configuration.
|
||||
|
||||
It is important to note that the two methods are *interchangably compatible*.
|
||||
You can use the I2PInterface to connect to a TCPServerInterface that
|
||||
@@ -171,7 +182,7 @@ TCP Server Interface
|
||||
====================
|
||||
|
||||
The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
the Internet or private IPv4 and IPv6 networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
|
||||
.. code::
|
||||
@@ -200,8 +211,37 @@ configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
If you are using the interface on a device which has both IPv4 and IPv6 addresses available,
|
||||
you can use the ``prefer_ipv6`` option to bind to the IPv6 address:
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = True
|
||||
|
||||
device = eth0
|
||||
port = 4242
|
||||
prefer_ipv6 = True
|
||||
|
||||
To use the TCP Server Interface over `Yggdrasil <https://yggdrasil-network.github.io/>`_, you
|
||||
can simply specify the Yggdrasil ``tun`` device and a listening port, like so:
|
||||
|
||||
.. code::
|
||||
|
||||
[[Yggdrasil TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = yes
|
||||
device = tun0
|
||||
listen_port = 4343
|
||||
|
||||
.. note::
|
||||
The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -231,7 +271,7 @@ and restore connectivity after a failure, once the other end of a TCP interface
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface. The
|
||||
# target_host can either be an IP address or a hostname.
|
||||
# target_host can be a hostname or an IPv4 or IPv6 address.
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
@@ -239,6 +279,17 @@ and restore connectivity after a failure, once the other end of a TCP interface
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
To use the TCP Client Interface over `Yggdrasil <https://yggdrasil-network.github.io/>`_, simply
|
||||
specify the target Yggdrasil IPv6 address and port, like so:
|
||||
|
||||
.. code::
|
||||
|
||||
[[Yggdrasil TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = yes
|
||||
target_host = 201:5d78:af73:5caf:a4de:a79f:3278:71e5
|
||||
target_port = 4343
|
||||
|
||||
It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the ``kiss_framing`` option:
|
||||
@@ -262,8 +313,9 @@ never enable ``kiss_framing``, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.
|
||||
|
||||
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
.. note::
|
||||
The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -285,11 +337,12 @@ private and the internet. It can also allow broadcast communication
|
||||
over IP networks, so it can provide an easy way to enable connectivity
|
||||
with all other peers on a local area network.
|
||||
|
||||
*Please Note!* Using broadcast UDP traffic has performance implications,
|
||||
especially on WiFi. If your goal is simply to enable easy communication
|
||||
with all peers in your local Ethernet broadcast domain, the
|
||||
:ref:`Auto Interface<interfaces-auto>` performs better, and is even
|
||||
easier to use.
|
||||
.. warning::
|
||||
Using broadcast UDP traffic has performance implications,
|
||||
especially on WiFi. If your goal is simply to enable easy communication
|
||||
with all peers in your local Ethernet broadcast domain, the
|
||||
:ref:`Auto Interface<interfaces-auto>` performs better, and is even
|
||||
easier to use.
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -344,6 +397,11 @@ RNode LoRa Interface
|
||||
To use Reticulum over LoRa, the `RNode <https://unsigned.io/rnode/>`_ interface
|
||||
can be used, and offers full control over LoRa parameters.
|
||||
|
||||
.. warning::
|
||||
Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of how to add a LoRa interface
|
||||
@@ -431,6 +489,11 @@ RNode Multi Interface
|
||||
For RNodes that support multiple LoRa transceivers, the RNode
|
||||
Multi interface can be used to configure sub-interfaces individually.
|
||||
|
||||
.. warning::
|
||||
Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of how to add an RNode Multi interface
|
||||
@@ -454,89 +517,89 @@ Multi interface can be used to configure sub-interfaces individually.
|
||||
# id_interval = 600
|
||||
|
||||
# A subinterface
|
||||
[[[HIGHDATARATE]]]
|
||||
# Subinterfaces can be enabled and disabled in of themselves
|
||||
interface_enabled = True
|
||||
[[[High Datarate]]]
|
||||
# Subinterfaces can be enabled and disabled in of themselves
|
||||
interface_enabled = True
|
||||
|
||||
# Set frequency to 2.4GHz
|
||||
frequency = 2400000000
|
||||
# Set frequency to 2.4GHz
|
||||
frequency = 2400000000
|
||||
|
||||
# Set LoRa bandwidth to 1625 KHz
|
||||
bandwidth = 1625000
|
||||
# Set LoRa bandwidth to 1625 KHz
|
||||
bandwidth = 1625000
|
||||
|
||||
# Set TX power to 0 dBm (0.12 mW)
|
||||
txpower = 0
|
||||
# Set TX power to 0 dBm (0.12 mW)
|
||||
txpower = 0
|
||||
|
||||
# The virtual port, only the manufacturer
|
||||
# or the person who wrote the board config
|
||||
# can tell you what it will be for which
|
||||
# physical hardware interface
|
||||
vport = 1
|
||||
# The virtual port, only the manufacturer
|
||||
# or the person who wrote the board config
|
||||
# can tell you what it will be for which
|
||||
# physical hardware interface
|
||||
vport = 1
|
||||
|
||||
# Select spreading factor 5. Valid
|
||||
# range is 5 through 12, with 5
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 5
|
||||
# Select spreading factor 5. Valid
|
||||
# range is 5 through 12, with 5
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 5
|
||||
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# It is possible to limit the airtime
|
||||
# utilisation of an RNode by using the
|
||||
# following two configuration options.
|
||||
# The short-term limit is applied in a
|
||||
# window of approximately 15 seconds,
|
||||
# and the long-term limit is enforced
|
||||
# over a rolling 60 minute window. Both
|
||||
# options are specified in percent.
|
||||
# It is possible to limit the airtime
|
||||
# utilisation of an RNode by using the
|
||||
# following two configuration options.
|
||||
# The short-term limit is applied in a
|
||||
# window of approximately 15 seconds,
|
||||
# and the long-term limit is enforced
|
||||
# over a rolling 60 minute window. Both
|
||||
# options are specified in percent.
|
||||
|
||||
# airtime_limit_long = 100
|
||||
# airtime_limit_short = 100
|
||||
# airtime_limit_long = 100
|
||||
# airtime_limit_short = 100
|
||||
|
||||
[[[LOWDATARATE]]]
|
||||
# Subinterfaces can be enabled and disabled in of themselves
|
||||
interface_enabled = True
|
||||
[[[Low Datarate]]]
|
||||
# Subinterfaces can be enabled and disabled in of themselves
|
||||
interface_enabled = True
|
||||
|
||||
# Set frequency to 865.6 MHz
|
||||
frequency = 865600000
|
||||
# Set frequency to 865.6 MHz
|
||||
frequency = 865600000
|
||||
|
||||
# The virtual port, only the manufacturer
|
||||
# or the person who wrote the board config
|
||||
# can tell you what it will be for which
|
||||
# physical hardware interface
|
||||
vport = 0
|
||||
# The virtual port, only the manufacturer
|
||||
# or the person who wrote the board config
|
||||
# can tell you what it will be for which
|
||||
# physical hardware interface
|
||||
vport = 0
|
||||
|
||||
# Set LoRa bandwidth to 125 KHz
|
||||
bandwidth = 125000
|
||||
# Set LoRa bandwidth to 125 KHz
|
||||
bandwidth = 125000
|
||||
|
||||
# Set TX power to 0 dBm (0.12 mW)
|
||||
txpower = 0
|
||||
# Set TX power to 0 dBm (0.12 mW)
|
||||
txpower = 0
|
||||
|
||||
# Select spreading factor 7. Valid
|
||||
# range is 5 through 12, with 5
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 7
|
||||
# Select spreading factor 7. Valid
|
||||
# range is 5 through 12, with 5
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 7
|
||||
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# It is possible to limit the airtime
|
||||
# utilisation of an RNode by using the
|
||||
# following two configuration options.
|
||||
# The short-term limit is applied in a
|
||||
# window of approximately 15 seconds,
|
||||
# and the long-term limit is enforced
|
||||
# over a rolling 60 minute window. Both
|
||||
# options are specified in percent.
|
||||
# It is possible to limit the airtime
|
||||
# utilisation of an RNode by using the
|
||||
# following two configuration options.
|
||||
# The short-term limit is applied in a
|
||||
# window of approximately 15 seconds,
|
||||
# and the long-term limit is enforced
|
||||
# over a rolling 60 minute window. Both
|
||||
# options are specified in percent.
|
||||
|
||||
# airtime_limit_long = 100
|
||||
# airtime_limit_short = 100
|
||||
# airtime_limit_long = 100
|
||||
# airtime_limit_short = 100
|
||||
|
||||
.. _interfaces-serial:
|
||||
|
||||
@@ -598,6 +661,11 @@ radio modems and TNCs, including `OpenModem <https://unsigned.io/openmodem/>`_.
|
||||
KISS interfaces can also be configured to periodically send out beacons
|
||||
for station identification purposes.
|
||||
|
||||
.. warning::
|
||||
Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Packet Radio KISS Interface]]
|
||||
@@ -661,6 +729,11 @@ encapsulate in AX.25.
|
||||
A more efficient way is to use the plain KISS interface with the
|
||||
beaconing functionality described above.
|
||||
|
||||
.. warning::
|
||||
Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Packet Radio AX.25 KISS Interface]]
|
||||
|
||||
@@ -32,7 +32,9 @@ Provide Feedback
|
||||
================
|
||||
All feedback on the usage, functioning and potential dysfunctioning of any and
|
||||
all components of the system is very valuable to the continued development and
|
||||
improvement of Reticulum. Absolutely no automated analytics, telemetry, error
|
||||
improvement of Reticulum.
|
||||
|
||||
Absolutely no automated analytics, telemetry, error
|
||||
reporting or statistics is collected and reported by Reticulum under any
|
||||
circumstances, so we rely on old-fashioned human feedback.
|
||||
|
||||
|
||||
@@ -868,13 +868,17 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
|
||||
|
||||
* HKDF for key derivation
|
||||
|
||||
* Modified Fernet for encrypted tokens
|
||||
* Encrypted tokens are based on the Fernet spec
|
||||
|
||||
* AES-128 in CBC mode
|
||||
* Ephemeral keys derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* HMAC for message authentication
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
* No Version and Timestamp metadata included
|
||||
* HMAC using SHA256 for message authentication
|
||||
|
||||
* IVs are generated through os.urandom()
|
||||
|
||||
* No Fernet version and timestamp metadata fields
|
||||
|
||||
* SHA-256
|
||||
|
||||
@@ -884,12 +888,12 @@ In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES
|
||||
primitives are provided by `OpenSSL <https://www.openssl.org/>`_ (via the `PyCA/cryptography <https://github.com/pyca/cryptography>`_
|
||||
package). The hashing functions ``SHA-256`` and ``SHA-512`` are provided by the standard
|
||||
Python `hashlib <https://docs.python.org/3/library/hashlib.html>`_. The ``HKDF``, ``HMAC``,
|
||||
``Fernet`` primitives, and the ``PKCS7`` padding function are always provided by the
|
||||
``Token`` primitives, and the ``PKCS7`` padding function are always provided by the
|
||||
following internal implementations:
|
||||
|
||||
- ``RNS/Cryptography/HKDF.py``
|
||||
- ``RNS/Cryptography/HMAC.py``
|
||||
- ``RNS/Cryptography/Fernet.py``
|
||||
- ``RNS/Cryptography/Token.py``
|
||||
- ``RNS/Cryptography/PKCS7.py``
|
||||
|
||||
|
||||
@@ -900,6 +904,7 @@ with the OpenSSL backend being *much* faster. The most important consequence how
|
||||
potential loss of security by using primitives that has not seen the same amount of scrutiny,
|
||||
testing and review as those from OpenSSL.
|
||||
|
||||
If you want to use the internal pure-python primitives, it is **highly advisable** that you
|
||||
have a good understanding of the risks that this pose, and make an informed decision on whether
|
||||
those risks are acceptable to you.
|
||||
.. warning::
|
||||
If you want to use the internal pure-python primitives, it is **highly advisable** that you
|
||||
have a good understanding of the risks that this pose, and make an informed decision on whether
|
||||
those risks are acceptable to you.
|
||||
@@ -17,7 +17,7 @@ Reticulum enables secure digital communication that cannot be subjected to
|
||||
outside control, manipulation or censorship.
|
||||
|
||||
Reticulum enables the construction of both small and potentially planetary-scale
|
||||
networks, without any need for hierarchical or beaureucratic structures to control
|
||||
networks, without any need for hierarchical or bureaucratic structures to control
|
||||
or manage them, while ensuring individuals and communities full sovereignty
|
||||
over their own network segments.
|
||||
|
||||
@@ -43,19 +43,30 @@ considered complete and stable at the moment, but could change if absolutely war
|
||||
|
||||
What does Reticulum Offer?
|
||||
==========================
|
||||
|
||||
* Coordination-less globally unique addressing and identification
|
||||
|
||||
* Fully self-configuring multi-hop routing
|
||||
* Fully self-configuring multi-hop routing over heterogeneous carriers
|
||||
|
||||
* Complete initiator anonymity, communicate without revealing your identity
|
||||
* Flexible scalability over heterogeneous topologies
|
||||
|
||||
* Asymmetric encryption based on X25519, and Ed25519 signatures as a basis for all communication
|
||||
* Reticulum can carry data over any mixture of physical mediums and topologies
|
||||
|
||||
* Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
* Low-bandwidth networks can co-exist and interoperate with large, high-bandwidth networks
|
||||
|
||||
* 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
|
||||
* Initiator anonymity, communicate without revealing your identity
|
||||
|
||||
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
* Reticulum does not include source addresses on any packets
|
||||
|
||||
* Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
|
||||
|
||||
* 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:
|
||||
|
||||
* Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
@@ -63,13 +74,33 @@ 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
|
||||
* Flexible and extensible interface system
|
||||
|
||||
* An intuitive and developer-friendly API
|
||||
* Reticulum includes a large variety of built-in interface types
|
||||
|
||||
* Ability to load and utilise custom user- or community-supplied interface types
|
||||
|
||||
* Easily create your own custom interfaces for communicating over anything
|
||||
|
||||
* Authentication and virtual network segmentation on all supported interface types
|
||||
|
||||
* An intuitive and easy-to-use API
|
||||
|
||||
* Simpler and easier to use than sockets APIs and simpler, but more powerful
|
||||
|
||||
* Makes building distributed and decentralised applications much simpler
|
||||
|
||||
* Reliable and efficient transfer of arbitrary amounts of data
|
||||
|
||||
* Reticulum can handle a few bytes of data or files of many gigabytes
|
||||
|
||||
* 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
|
||||
|
||||
@@ -77,17 +108,7 @@ What does Reticulum Offer?
|
||||
|
||||
* Low cost of keeping links open at only 0.44 bits per second
|
||||
|
||||
* 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 is automatic
|
||||
|
||||
* The API is very easy to use, and provides transfer progress
|
||||
|
||||
* Authentication and virtual network segmentation on all supported interface types
|
||||
|
||||
* Flexible scalability allowing extremely low-bandwidth networks to co-exist and interoperate with large, high-bandwidth networks
|
||||
* Reliable sequential delivery with Channel and Buffer mechanisms
|
||||
|
||||
|
||||
Where can Reticulum be Used?
|
||||
@@ -118,7 +139,7 @@ network, and vice versa.
|
||||
|
||||
Interface Types and Devices
|
||||
===========================
|
||||
Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, it's relatively simple to implement an interface class. Currently, Reticulum can use the following devices and communication mediums:
|
||||
Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, it's simple to :ref:`implement an interface class<example-custominterface>`. Currently, Reticulum can use the following devices and communication mediums:
|
||||
|
||||
* Any Ethernet device
|
||||
|
||||
@@ -168,4 +189,4 @@ such. While it has been built with cryptography best-practices very foremost in
|
||||
mind, it has not yet been externally security audited, and there could very well be
|
||||
privacy-breaking bugs. To be considered secure, Reticulum needs a thorough
|
||||
security review by independent cryptographers and security researchers. If you
|
||||
want to help out with this, or can help sponsor an audit, please do get in touch.
|
||||
want to help out with this, or can help sponsor an audit, please do get in touch.
|
||||
@@ -1,6 +1,6 @@
|
||||
var DOCUMENTATION_OPTIONS = {
|
||||
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
|
||||
VERSION: '0.8.2 beta',
|
||||
VERSION: '0.9.0 beta',
|
||||
LANGUAGE: 'en',
|
||||
COLLAPSE_INDEX: false,
|
||||
BUILDER: 'html',
|
||||
|
||||
+314
-4
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Code Examples - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -3241,6 +3241,315 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
</div>
|
||||
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py">https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py</a>.</p>
|
||||
</section>
|
||||
<section id="custom-interfaces">
|
||||
<span id="example-custominterface"></span><h2>Custom Interfaces<a class="headerlink" href="#custom-interfaces" title="Permalink to this heading">#</a></h2>
|
||||
<p>The <em>ExampleInterface</em> demonstrates creating custom interfaces for Reticulum.
|
||||
Any number of custom interfaces can be loaded and utilised by Reticulum, and
|
||||
will be fully on-par with natively included interfaces, including all supported
|
||||
<a class="reference internal" href="interfaces.html#interfaces-modes"><span class="std std-ref">interface modes</span></a> and <a class="reference internal" href="interfaces.html#interfaces-options"><span class="std std-ref">common configuration options</span></a>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># MIT License - Copyright (c) 2024 Mark Qvist / unsigned.io</span>
|
||||
|
||||
<span class="c1"># This example illustrates creating a custom interface</span>
|
||||
<span class="c1"># definition, that can be loaded and used by Reticulum at</span>
|
||||
<span class="c1"># runtime. Any number of custom interfaces can be created</span>
|
||||
<span class="c1"># and loaded. To use the interface place it in the folder</span>
|
||||
<span class="c1"># ~/.reticulum/interfaces, and add an interface entry to</span>
|
||||
<span class="c1"># your Reticulum configuration file similar to this:</span>
|
||||
|
||||
<span class="c1"># [[Example Custom Interface]]</span>
|
||||
<span class="c1"># type = ExampleInterface</span>
|
||||
<span class="c1"># enabled = no</span>
|
||||
<span class="c1"># mode = gateway</span>
|
||||
<span class="c1"># port = /dev/ttyUSB0</span>
|
||||
<span class="c1"># speed = 115200</span>
|
||||
<span class="c1"># databits = 8</span>
|
||||
<span class="c1"># parity = none</span>
|
||||
<span class="c1"># stopbits = 1</span>
|
||||
|
||||
<span class="kn">from</span> <span class="nn">time</span> <span class="kn">import</span> <span class="n">sleep</span>
|
||||
<span class="kn">import</span> <span class="nn">sys</span>
|
||||
<span class="kn">import</span> <span class="nn">threading</span>
|
||||
<span class="kn">import</span> <span class="nn">time</span>
|
||||
|
||||
<span class="c1"># This HDLC helper class is used by the interface</span>
|
||||
<span class="c1"># to delimit and packetize data over the physical</span>
|
||||
<span class="c1"># medium - in this case a serial connection.</span>
|
||||
<span class="k">class</span> <span class="nc">HDLC</span><span class="p">():</span>
|
||||
<span class="c1"># This example interface packetizes data using</span>
|
||||
<span class="c1"># simplified HDLC framing, similar to PPP</span>
|
||||
<span class="n">FLAG</span> <span class="o">=</span> <span class="mh">0x7E</span>
|
||||
<span class="n">ESC</span> <span class="o">=</span> <span class="mh">0x7D</span>
|
||||
<span class="n">ESC_MASK</span> <span class="o">=</span> <span class="mh">0x20</span>
|
||||
|
||||
<span class="nd">@staticmethod</span>
|
||||
<span class="k">def</span> <span class="nf">escape</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="p">]),</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="p">,</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="o">^</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC_MASK</span><span class="p">]))</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">]),</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="p">,</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="o">^</span><span class="n">HDLC</span><span class="o">.</span><span class="n">ESC_MASK</span><span class="p">]))</span>
|
||||
<span class="k">return</span> <span class="n">data</span>
|
||||
|
||||
<span class="c1"># Let's define our custom interface class. It must</span>
|
||||
<span class="c1"># be a sub-class of the RNS "Interface" class.</span>
|
||||
<span class="k">class</span> <span class="nc">ExampleInterface</span><span class="p">(</span><span class="n">Interface</span><span class="p">):</span>
|
||||
<span class="c1"># All interface classes must define a default</span>
|
||||
<span class="c1"># IFAC size, used in IFAC setup when the user</span>
|
||||
<span class="c1"># has not specified a custom IFAC size. This</span>
|
||||
<span class="c1"># option is specified in bytes.</span>
|
||||
<span class="n">DEFAULT_IFAC_SIZE</span> <span class="o">=</span> <span class="mi">8</span>
|
||||
|
||||
<span class="c1"># The following properties are local to this</span>
|
||||
<span class="c1"># particular interface implementation.</span>
|
||||
<span class="n">owner</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">speed</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">databits</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">parity</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">stopbits</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="n">serial</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
|
||||
<span class="c1"># All Reticulum interfaces must have an __init__</span>
|
||||
<span class="c1"># method that takes 2 positional arguments:</span>
|
||||
<span class="c1"># The owner RNS Transport instance, and a dict</span>
|
||||
<span class="c1"># of configuration values.</span>
|
||||
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">owner</span><span class="p">,</span> <span class="n">configuration</span><span class="p">):</span>
|
||||
|
||||
<span class="c1"># The following lines demonstrate handling</span>
|
||||
<span class="c1"># potential dependencies required for the</span>
|
||||
<span class="c1"># interface to function correctly.</span>
|
||||
<span class="kn">import</span> <span class="nn">importlib</span>
|
||||
<span class="k">if</span> <span class="n">importlib</span><span class="o">.</span><span class="n">util</span><span class="o">.</span><span class="n">find_spec</span><span class="p">(</span><span class="s1">'serial'</span><span class="p">)</span> <span class="o">!=</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="kn">import</span> <span class="nn">serial</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Using this interface requires a serial communication module to be installed."</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_CRITICAL</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">"You can install one with the command: python3 -m pip install pyserial"</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_CRITICAL</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">panic</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># We start out by initialising the super-class</span>
|
||||
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># To make sure the configuration data is in the</span>
|
||||
<span class="c1"># correct format, we parse it through the following</span>
|
||||
<span class="c1"># method on the generic Interface class. This step</span>
|
||||
<span class="c1"># is required to ensure compatibility on all the</span>
|
||||
<span class="c1"># platforms that Reticulum supports.</span>
|
||||
<span class="n">ifconf</span> <span class="o">=</span> <span class="n">Interface</span><span class="o">.</span><span class="n">get_config_obj</span><span class="p">(</span><span class="n">configuration</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Read the interface name from the configuration</span>
|
||||
<span class="c1"># and set it on our interface instance.</span>
|
||||
<span class="n">name</span> <span class="o">=</span> <span class="n">ifconf</span><span class="p">[</span><span class="s2">"name"</span><span class="p">]</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">name</span>
|
||||
|
||||
<span class="c1"># We read configuration parameters from the supplied</span>
|
||||
<span class="c1"># configuration data, and provide default values in</span>
|
||||
<span class="c1"># case any are missing.</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="n">ifconf</span><span class="p">[</span><span class="s2">"port"</span><span class="p">]</span> <span class="k">if</span> <span class="s2">"port"</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="kc">None</span>
|
||||
<span class="n">speed</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ifconf</span><span class="p">[</span><span class="s2">"speed"</span><span class="p">])</span> <span class="k">if</span> <span class="s2">"speed"</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="mi">9600</span>
|
||||
<span class="n">databits</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ifconf</span><span class="p">[</span><span class="s2">"databits"</span><span class="p">])</span> <span class="k">if</span> <span class="s2">"databits"</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="mi">8</span>
|
||||
<span class="n">parity</span> <span class="o">=</span> <span class="n">ifconf</span><span class="p">[</span><span class="s2">"parity"</span><span class="p">]</span> <span class="k">if</span> <span class="s2">"parity"</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="s2">"N"</span>
|
||||
<span class="n">stopbits</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">ifconf</span><span class="p">[</span><span class="s2">"stopbits"</span><span class="p">])</span> <span class="k">if</span> <span class="s2">"stopbits"</span> <span class="ow">in</span> <span class="n">ifconf</span> <span class="k">else</span> <span class="mi">1</span>
|
||||
|
||||
<span class="c1"># In case no port is specified, we abort setup by</span>
|
||||
<span class="c1"># raising an exception.</span>
|
||||
<span class="k">if</span> <span class="n">port</span> <span class="o">==</span> <span class="kc">None</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="sa">f</span><span class="s2">"No port specified for </span><span class="si">{</span><span class="bp">self</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># All interfaces must supply a hardware MTU value</span>
|
||||
<span class="c1"># to the RNS Transport instance. This value should</span>
|
||||
<span class="c1"># be the maximum data packet payload size that the</span>
|
||||
<span class="c1"># underlying medium is capable of handling in all</span>
|
||||
<span class="c1"># cases without any segmentation.</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">HW_MTU</span> <span class="o">=</span> <span class="mi">564</span>
|
||||
|
||||
<span class="c1"># We initially set the "online" property to false,</span>
|
||||
<span class="c1"># since the interface has not actually been fully</span>
|
||||
<span class="c1"># initialised and connected yet.</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">online</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="c1"># In this case, we can also set the indicated bit-</span>
|
||||
<span class="c1"># rate of the interface to the serial port speed.</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">bitrate</span> <span class="o">=</span> <span class="n">speed</span>
|
||||
|
||||
<span class="c1"># Configure internal properties on the interface</span>
|
||||
<span class="c1"># according to the supplied configuration.</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">pyserial</span> <span class="o">=</span> <span class="n">serial</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">serial</span> <span class="o">=</span> <span class="kc">None</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">owner</span> <span class="o">=</span> <span class="n">owner</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">port</span> <span class="o">=</span> <span class="n">port</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">speed</span> <span class="o">=</span> <span class="n">speed</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">databits</span> <span class="o">=</span> <span class="n">databits</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">parity</span> <span class="o">=</span> <span class="n">serial</span><span class="o">.</span><span class="n">PARITY_NONE</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">stopbits</span> <span class="o">=</span> <span class="n">stopbits</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="mi">100</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">parity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"e"</span> <span class="ow">or</span> <span class="n">parity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"even"</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">parity</span> <span class="o">=</span> <span class="n">serial</span><span class="o">.</span><span class="n">PARITY_EVEN</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">parity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"o"</span> <span class="ow">or</span> <span class="n">parity</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"odd"</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">parity</span> <span class="o">=</span> <span class="n">serial</span><span class="o">.</span><span class="n">PARITY_ODD</span>
|
||||
|
||||
<span class="c1"># Since all required parameters are now configured,</span>
|
||||
<span class="c1"># we will try opening the serial port.</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">open_port</span><span class="p">()</span>
|
||||
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Could not open serial port for interface "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">),</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
|
||||
<span class="k">raise</span> <span class="n">e</span>
|
||||
|
||||
<span class="c1"># If opening the port succeeded, run any post-open</span>
|
||||
<span class="c1"># configuration required.</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">is_open</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">configure_device</span><span class="p">()</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">raise</span> <span class="ne">IOError</span><span class="p">(</span><span class="s2">"Could not open serial port"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Open the serial port with supplied configuration</span>
|
||||
<span class="c1"># parameters and store a reference to the open port.</span>
|
||||
<span class="k">def</span> <span class="nf">open_port</span><span class="p">(</span><span class="bp">self</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">"Opening serial port "</span><span class="o">+</span><span class="bp">self</span><span class="o">.</span><span class="n">port</span><span class="o">+</span><span class="s2">"..."</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="bp">self</span><span class="o">.</span><span class="n">serial</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">pyserial</span><span class="o">.</span><span class="n">Serial</span><span class="p">(</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">port</span><span class="p">,</span>
|
||||
<span class="n">baudrate</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">speed</span><span class="p">,</span>
|
||||
<span class="n">bytesize</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">databits</span><span class="p">,</span>
|
||||
<span class="n">parity</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">parity</span><span class="p">,</span>
|
||||
<span class="n">stopbits</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">stopbits</span><span class="p">,</span>
|
||||
<span class="n">xonxoff</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">rtscts</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
|
||||
<span class="n">timeout</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span>
|
||||
<span class="n">inter_byte_timeout</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">write_timeout</span> <span class="o">=</span> <span class="kc">None</span><span class="p">,</span>
|
||||
<span class="n">dsrdtr</span> <span class="o">=</span> <span class="kc">False</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="c1"># The only thing required after opening the port</span>
|
||||
<span class="c1"># is to wait a small amount of time for the</span>
|
||||
<span class="c1"># hardware to initialise and then start a thread</span>
|
||||
<span class="c1"># that reads any incoming data from the device.</span>
|
||||
<span class="k">def</span> <span class="nf">configure_device</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="n">sleep</span><span class="p">(</span><span class="mf">0.5</span><span class="p">)</span>
|
||||
<span class="n">thread</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">read_loop</span><span class="p">)</span>
|
||||
<span class="n">thread</span><span class="o">.</span><span class="n">daemon</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">thread</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">online</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Serial port "</span><span class="o">+</span><span class="bp">self</span><span class="o">.</span><span class="n">port</span><span class="o">+</span><span class="s2">" is now open"</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"># This method will be called from our read-loop</span>
|
||||
<span class="c1"># whenever a full packet has been received over</span>
|
||||
<span class="c1"># the underlying medium.</span>
|
||||
<span class="k">def</span> <span class="nf">process_incoming</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
|
||||
<span class="c1"># Update our received bytes counter</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">rxb</span> <span class="o">+=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># And send the data packet to the Transport</span>
|
||||
<span class="c1"># instance for processing.</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">owner</span><span class="o">.</span><span class="n">inbound</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="bp">self</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># The running Reticulum Transport instance will</span>
|
||||
<span class="c1"># call this method on the interface whenever the</span>
|
||||
<span class="c1"># interface must transmit a packet.</span>
|
||||
<span class="k">def</span> <span class="nf">process_outgoing</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">data</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">online</span><span class="p">:</span>
|
||||
<span class="c1"># First, escape and packetize the data</span>
|
||||
<span class="c1"># according to HDLC framing.</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">])</span><span class="o">+</span><span class="n">HDLC</span><span class="o">.</span><span class="n">escape</span><span class="p">(</span><span class="n">data</span><span class="p">)</span><span class="o">+</span><span class="nb">bytes</span><span class="p">([</span><span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">])</span>
|
||||
|
||||
<span class="c1"># Then write the framed data to the port</span>
|
||||
<span class="n">written</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Update the transmitted bytes counter</span>
|
||||
<span class="c1"># and ensure that all data was written</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">txb</span> <span class="o">+=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
<span class="k">if</span> <span class="n">written</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">):</span>
|
||||
<span class="k">raise</span> <span class="ne">IOError</span><span class="p">(</span><span class="s2">"Serial interface only wrote "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">written</span><span class="p">)</span><span class="o">+</span><span class="s2">" bytes of "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">data</span><span class="p">)))</span>
|
||||
|
||||
<span class="c1"># This read loop runs in a thread and continously</span>
|
||||
<span class="c1"># receives bytes from the underlying serial port.</span>
|
||||
<span class="c1"># When a full packet has been received, it will</span>
|
||||
<span class="c1"># be sent to the process_incoming methed, which</span>
|
||||
<span class="c1"># will in turn pass it to the Transport instance.</span>
|
||||
<span class="k">def</span> <span class="nf">read_loop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">in_frame</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="n">escape</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="n">data_buffer</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">""</span>
|
||||
<span class="n">last_read_ms</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span>
|
||||
|
||||
<span class="k">while</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">is_open</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">in_waiting</span><span class="p">:</span>
|
||||
<span class="n">byte</span> <span class="o">=</span> <span class="nb">ord</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span>
|
||||
<span class="n">last_read_ms</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">in_frame</span> <span class="ow">and</span> <span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">):</span>
|
||||
<span class="n">in_frame</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">process_incoming</span><span class="p">(</span><span class="n">data_buffer</span><span class="p">)</span>
|
||||
<span class="k">elif</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span><span class="p">):</span>
|
||||
<span class="n">in_frame</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="n">data_buffer</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">""</span>
|
||||
<span class="k">elif</span> <span class="p">(</span><span class="n">in_frame</span> <span class="ow">and</span> <span class="nb">len</span><span class="p">(</span><span class="n">data_buffer</span><span class="p">)</span> <span class="o"><</span> <span class="bp">self</span><span class="o">.</span><span class="n">HW_MTU</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span><span class="p">):</span>
|
||||
<span class="n">escape</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">escape</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span> <span class="o">^</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC_MASK</span><span class="p">):</span>
|
||||
<span class="n">byte</span> <span class="o">=</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">FLAG</span>
|
||||
<span class="k">if</span> <span class="p">(</span><span class="n">byte</span> <span class="o">==</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span> <span class="o">^</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC_MASK</span><span class="p">):</span>
|
||||
<span class="n">byte</span> <span class="o">=</span> <span class="n">HDLC</span><span class="o">.</span><span class="n">ESC</span>
|
||||
<span class="n">escape</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="n">data_buffer</span> <span class="o">=</span> <span class="n">data_buffer</span><span class="o">+</span><span class="nb">bytes</span><span class="p">([</span><span class="n">byte</span><span class="p">])</span>
|
||||
|
||||
<span class="k">else</span><span class="p">:</span>
|
||||
<span class="n">time_since_last</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span><span class="o">*</span><span class="mi">1000</span><span class="p">)</span> <span class="o">-</span> <span class="n">last_read_ms</span>
|
||||
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">data_buffer</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">time_since_last</span> <span class="o">></span> <span class="bp">self</span><span class="o">.</span><span class="n">timeout</span><span class="p">:</span>
|
||||
<span class="n">data_buffer</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">""</span>
|
||||
<span class="n">in_frame</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="n">escape</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="n">sleep</span><span class="p">(</span><span class="mf">0.08</span><span class="p">)</span>
|
||||
|
||||
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">online</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"A serial port error occurred, the contained exception was: "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"The interface "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span><span class="o">+</span><span class="s2">" experienced an unrecoverable error and is now offline."</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
|
||||
|
||||
<span class="k">if</span> <span class="n">RNS</span><span class="o">.</span><span class="n">Reticulum</span><span class="o">.</span><span class="n">panic_on_interface_error</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">panic</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">"Reticulum will attempt to reconnect the interface periodically."</span><span class="p">,</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
|
||||
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">online</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">reconnect_port</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># This method handles serial port disconnects.</span>
|
||||
<span class="k">def</span> <span class="nf">reconnect_port</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">while</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">online</span><span class="p">:</span>
|
||||
<span class="k">try</span><span class="p">:</span>
|
||||
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">5</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">"Attempting to reconnect serial port "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">port</span><span class="p">)</span><span class="o">+</span><span class="s2">" for "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span><span class="o">+</span><span class="s2">"..."</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="bp">self</span><span class="o">.</span><span class="n">open_port</span><span class="p">()</span>
|
||||
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">serial</span><span class="o">.</span><span class="n">is_open</span><span class="p">:</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">configure_device</span><span class="p">()</span>
|
||||
<span class="k">except</span> <span class="ne">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Error while reconnecting port, the contained exception was: "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">),</span> <span class="n">RNS</span><span class="o">.</span><span class="n">LOG_ERROR</span><span class="p">)</span>
|
||||
|
||||
<span class="n">RNS</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="s2">"Reconnected serial port for "</span><span class="o">+</span><span class="nb">str</span><span class="p">(</span><span class="bp">self</span><span class="p">))</span>
|
||||
|
||||
<span class="c1"># Signal to Reticulum that this interface should</span>
|
||||
<span class="c1"># not perform any ingress limiting.</span>
|
||||
<span class="k">def</span> <span class="nf">should_ingress_limit</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="kc">False</span>
|
||||
|
||||
<span class="c1"># We must provide a string representation of this</span>
|
||||
<span class="c1"># interface, that is used whenever the interface</span>
|
||||
<span class="c1"># is printed in logs or external programs.</span>
|
||||
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="s2">"ExampleInterface["</span><span class="o">+</span><span class="bp">self</span><span class="o">.</span><span class="n">name</span><span class="o">+</span><span class="s2">"]"</span>
|
||||
|
||||
<span class="c1"># Finally, register the defined interface class as the</span>
|
||||
<span class="c1"># target class for Reticulum to use as an interface</span>
|
||||
<span class="n">interface_class</span> <span class="o">=</span> <span class="n">ExampleInterface</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This example can also be found at <a class="reference external" href="https://github.com/markqvist/Reticulum/blob/master/Examples/ExampleInterface.py">https://github.com/markqvist/Reticulum/blob/master/Examples/ExampleInterface.py</a>.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
</article>
|
||||
@@ -3310,6 +3619,7 @@ interface to efficiently pass files of any size over a Reticulum <a class="refer
|
||||
<li><a class="reference internal" href="#channel">Channel</a></li>
|
||||
<li><a class="reference internal" href="#buffer">Buffer</a></li>
|
||||
<li><a class="reference internal" href="#filetransfer">Filetransfer</a></li>
|
||||
<li><a class="reference internal" href="#custom-interfaces">Custom Interfaces</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>An Explanation of Reticulum for Human Beings - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</title>
|
||||
<meta name="generator" content="sphinx-5.3.0, furo 2022.09.29.dev1"/><title>Index - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -402,10 +402,14 @@
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.Identity.get_public_key">get_public_key() (RNS.Identity method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_q">get_q() (RNS.Link method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Packet.get_q">(RNS.Packet method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Link.get_q">get_q() (RNS.Link method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Identity.get_random_hash">get_random_hash() (RNS.Identity static method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_remote_identity">get_remote_identity() (RNS.Link method)</a>
|
||||
@@ -417,13 +421,21 @@
|
||||
<li><a href="reference.html#RNS.RequestReceipt.get_response_time">get_response_time() (RNS.RequestReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_rssi">get_rssi() (RNS.Link method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Packet.get_rssi">(RNS.Packet method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.get_rtt">get_rtt() (RNS.PacketReceipt method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Resource.get_segments">get_segments() (RNS.Resource method)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Link.get_snr">get_snr() (RNS.Link method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Packet.get_snr">(RNS.Packet method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
<li><a href="reference.html#RNS.PacketReceipt.get_status">get_status() (RNS.PacketReceipt method)</a>
|
||||
|
||||
<ul>
|
||||
@@ -496,14 +508,18 @@
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Link">Link (class in RNS)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.LINK_MTU_DISCOVERY">LINK_MTU_DISCOVERY (RNS.Reticulum attribute)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.Reticulum.link_mtu_discovery">link_mtu_discovery() (RNS.Reticulum static method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Destination.load_private_key">load_private_key() (RNS.Destination method)</a>
|
||||
|
||||
<ul>
|
||||
<li><a href="reference.html#RNS.Identity.load_private_key">(RNS.Identity method)</a>
|
||||
</li>
|
||||
</ul></li>
|
||||
</ul></td>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Identity.load_public_key">load_public_key() (RNS.Identity method)</a>
|
||||
</li>
|
||||
</ul></td>
|
||||
@@ -514,7 +530,7 @@
|
||||
<h2>M</h2>
|
||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
||||
<td style="width: 33%; vertical-align: top;"><ul>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.MDU">MDU (RNS.Channel.Channel property)</a>
|
||||
<li><a href="reference.html#RNS.Channel.Channel.mdu">mdu (RNS.Channel.Channel property)</a>
|
||||
</li>
|
||||
<li><a href="reference.html#RNS.MessageBase">MessageBase (class in RNS)</a>
|
||||
</li>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Getting Started Fast - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -241,9 +241,12 @@ and install them offline using <code class="docutils literal notranslate"><span
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="o">./</span><span class="n">rns</span><span class="o">-</span><span class="mf">0.5.1</span><span class="o">-</span><span class="n">py3</span><span class="o">-</span><span class="n">none</span><span class="o">-</span><span class="nb">any</span><span class="o">.</span><span class="n">whl</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<p>For more detailed installation instructions, please see the
|
||||
<a class="reference internal" href="#install-guides"><span class="std std-ref">Platform-Specific Install Notes</span></a> section.</p>
|
||||
<p>After installation is complete, it might be helpful to refer to the
|
||||
<a class="reference internal" href="using.html#using-main"><span class="std std-ref">Using Reticulum on Your System</span></a> chapter.</p>
|
||||
<section id="resolving-dependency-installation-issues">
|
||||
<h2>Resolving Dependency & Installation Issues<a class="headerlink" href="#resolving-dependency-installation-issues" title="Permalink to this heading">#</a></h2>
|
||||
<h3>Resolving Dependency & Installation Issues<a class="headerlink" href="#resolving-dependency-installation-issues" title="Permalink to this heading">#</a></h3>
|
||||
<p>On some platforms, there may not be binary packages available for all dependencies, and
|
||||
<code class="docutils literal notranslate"><span class="pre">pip</span></code> installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:</p>
|
||||
@@ -261,6 +264,7 @@ be resolved by installing the development essentials packages for your platform:
|
||||
dependencies from source, and complete installation even on platforms that don’t have pre-
|
||||
compiled packages available.</p>
|
||||
</section>
|
||||
</section>
|
||||
<section id="try-using-a-reticulum-based-program">
|
||||
<h2>Try Using a Reticulum-based Program<a class="headerlink" href="#try-using-a-reticulum-based-program" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you simply want to try using a program built with Reticulum, a few different
|
||||
@@ -288,7 +292,8 @@ provides a complete encrypted communications suite built with Reticulum. It feat
|
||||
encrypted messaging (both direct and delayed-delivery for offline users), file sharing,
|
||||
and has a built-in text-browser and page server with support for dynamically rendered pages,
|
||||
user authentication and more.</p>
|
||||
<a class="reference external image-reference" href="_images/nomadnet_3.png"><img alt="_images/nomadnet_3.png" src="_images/nomadnet_3.png" /></a>
|
||||
<a class="reference external image-reference" href="_images/nomadnet_3.png"><img alt="_images/nomadnet_3.png" src="_images/nomadnet_3.png" />
|
||||
</a>
|
||||
<p><a class="reference external" href="https://github.com/markqvist/nomadnet">Nomad Network</a> is a user-facing client
|
||||
for the messaging and information-sharing protocol
|
||||
<a class="reference external" href="https://github.com/markqvist/lxmf">LXMF</a>, another project built with Reticulum.</p>
|
||||
@@ -300,17 +305,22 @@ for the messaging and information-sharing protocol
|
||||
<span class="n">nomadnet</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Please Note</strong>: If this is the very first time you use pip to install a program
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>If this is the very first time you use <code class="docutils literal notranslate"><span class="pre">pip</span></code> to install a program
|
||||
on your system, you might need to reboot your system for your program to become
|
||||
available. If you get a “command not found” error or similar when running the
|
||||
program, reboot your system and try again.</p>
|
||||
program, reboot your system and try again. In some cases, you may even need to
|
||||
manually add the <code class="docutils literal notranslate"><span class="pre">pip</span></code> install path to your <code class="docutils literal notranslate"><span class="pre">PATH</span></code> environment variable.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="sideband">
|
||||
<h3>Sideband<a class="headerlink" href="#sideband" title="Permalink to this heading">#</a></h3>
|
||||
<p>If you would rather use a program with a graphical user interface, you can take
|
||||
a look at <a class="reference external" href="https://unsigned.io/sideband">Sideband</a>, which is available for Android,
|
||||
Linux, macOS and Windows.</p>
|
||||
<a class="reference external image-reference" href="_images/sideband_devices.webp"><img alt="_images/sideband_devices.webp" class="align-center" src="_images/sideband_devices.webp" /></a>
|
||||
<a class="reference external image-reference" href="_images/sideband_devices.webp"><img alt="_images/sideband_devices.webp" class="align-center" src="_images/sideband_devices.webp" />
|
||||
</a>
|
||||
<p>Sideband allows you to communicate with other people or LXMF-compatible
|
||||
systems over Reticulum networks using LoRa, Packet Radio, WiFi, I2P, Encrypted QR
|
||||
Paper Messages, or anything else Reticulum supports. It also interoperates with
|
||||
@@ -321,7 +331,8 @@ the Nomad Network program.</p>
|
||||
<p>The <a class="reference external" href="https://github.com/liamcottle/reticulum-meshchat">Reticulum MeshChat</a> application
|
||||
is a user-friendly LXMF client for macOS and Windows, that also includes voice call
|
||||
functionality, and a range of other interesting functions.</p>
|
||||
<a class="reference external image-reference" href="_images/meshchat_1.webp"><img alt="_images/meshchat_1.webp" class="align-center" src="_images/meshchat_1.webp" /></a>
|
||||
<a class="reference external image-reference" href="_images/meshchat_1.webp"><img alt="_images/meshchat_1.webp" class="align-center" src="_images/meshchat_1.webp" />
|
||||
</a>
|
||||
<p>Reticulum MeshChat is of course also compatible with Sideband and Nomad Network, or
|
||||
any other LXMF client.</p>
|
||||
</section>
|
||||
@@ -456,6 +467,16 @@ refer to these additional external resources:</p>
|
||||
you are welcome to head over to the <a class="reference external" href="https://github.com/markqvist/Reticulum/discussions">GitHub discussion pages</a>
|
||||
and propose adding an interface for the hardware.</p>
|
||||
</section>
|
||||
<section id="creating-and-using-custom-interfaces">
|
||||
<h2>Creating and Using Custom Interfaces<a class="headerlink" href="#creating-and-using-custom-interfaces" title="Permalink to this heading">#</a></h2>
|
||||
<p>While Reticulum includes a flexible and broad range of built-in interfaces, these
|
||||
will not cover every conceivable type of communications hardware that Reticulum
|
||||
can potentially use to communicate.</p>
|
||||
<p>It is therefore possible to easily write your own interface modules, that can be
|
||||
loaded at run-time and used on-par with any of the built-in interface types.</p>
|
||||
<p>For more information on this subject, and code examples to build on, please see
|
||||
the <a class="reference internal" href="interfaces.html#interfaces-main"><span class="std std-ref">Configuring Interfaces</span></a> chapter.</p>
|
||||
</section>
|
||||
<section id="develop-a-program-with-reticulum">
|
||||
<h2>Develop a Program with Reticulum<a class="headerlink" href="#develop-a-program-with-reticulum" title="Permalink to this heading">#</a></h2>
|
||||
<p>If you want to develop programs that use Reticulum, the easiest way to get
|
||||
@@ -466,11 +487,8 @@ started is to install the latest release of Reticulum via pip:</p>
|
||||
<p>The above command will install Reticulum and dependencies, and you will be
|
||||
ready to import and use RNS in your own programs. The next step will most
|
||||
likely be to look at some <a class="reference internal" href="examples.html#examples-main"><span class="std std-ref">Example Programs</span></a>.</p>
|
||||
<p>For extended functionality, you can install optional dependencies:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">pyserial</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Further information can be found in the <a class="reference internal" href="reference.html#api-main"><span class="std std-ref">API Reference</span></a>.</p>
|
||||
<p>The entire Reticulum API is documented in the <a class="reference internal" href="reference.html#api-main"><span class="std std-ref">API Reference</span></a>
|
||||
chapter of this manual.</p>
|
||||
</section>
|
||||
<section id="participate-in-reticulum-development">
|
||||
<h2>Participate in Reticulum Development<a class="headerlink" href="#participate-in-reticulum-development" title="Permalink to this heading">#</a></h2>
|
||||
@@ -516,7 +534,7 @@ the <a class="reference external" href="https://github.com/markqvist/Reticulum/d
|
||||
or ask one of the developers or maintainers for a good place to start.</p>
|
||||
</section>
|
||||
<section id="platform-specific-install-notes">
|
||||
<h2>Platform-Specific Install Notes<a class="headerlink" href="#platform-specific-install-notes" title="Permalink to this heading">#</a></h2>
|
||||
<span id="install-guides"></span><h2>Platform-Specific Install Notes<a class="headerlink" href="#platform-specific-install-notes" title="Permalink to this heading">#</a></h2>
|
||||
<p>Some platforms require a slightly different installation procedure, or have
|
||||
various quirks that are worth being aware of. These are listed here.</p>
|
||||
<section id="android">
|
||||
@@ -581,7 +599,7 @@ here at a later point. Until then you can use the <a class="reference external"
|
||||
<section id="arm64">
|
||||
<h3>ARM64<a class="headerlink" href="#arm64" title="Permalink to this heading">#</a></h3>
|
||||
<p>On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> before
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> (or similar) before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
@@ -591,15 +609,8 @@ installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="raspberry-pi">
|
||||
<h3>Raspberry Pi<a class="headerlink" href="#raspberry-pi" title="Permalink to this heading">#</a></h3>
|
||||
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don’t always have packages available for some dependencies.</p>
|
||||
<p>While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.</p>
|
||||
<p>With these packages installed, <code class="docutils literal notranslate"><span class="pre">pip</span></code> will be able to build any missing dependencies
|
||||
on your system locally.</p>
|
||||
</section>
|
||||
<section id="debian-bookworm">
|
||||
<h3>Debian Bookworm<a class="headerlink" href="#debian-bookworm" title="Permalink to this heading">#</a></h3>
|
||||
@@ -625,10 +636,137 @@ following section:</p>
|
||||
break-system-packages = true
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Please note that the “break-system-packages” directive is a somewhat misleading choice
|
||||
<p>For a one-shot installation of Reticulum, without globally enabling the <code class="docutils literal notranslate"><span class="pre">break-system-packages</span></code>
|
||||
option, you can use the following command:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>pip install rns --break-system-packages
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code> directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this <em>could</em> in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems.</p>
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="macos">
|
||||
<h3>MacOS<a class="headerlink" href="#macos" title="Permalink to this heading">#</a></h3>
|
||||
<p>To install Reticulum on macOS, you will need to have Python and the <code class="docutils literal notranslate"><span class="pre">pip</span></code> package
|
||||
manager installed.</p>
|
||||
<p>Systems running macOS can vary quite widely in whether or not Python is pre-installed,
|
||||
and if it is, which version is installed, and whether the <code class="docutils literal notranslate"><span class="pre">pip</span></code> package manager is
|
||||
also installed and set up. If in doubt, you can <a class="reference external" href="https://www.python.org/downloads/">download and install</a>
|
||||
Python manually.</p>
|
||||
<p>When Python and <code class="docutils literal notranslate"><span class="pre">pip</span></code> is available on your system, simply open a terminal window
|
||||
and use one of the following commands:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Reticulum and utilities with pip:</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
|
||||
<span class="c1"># On some versions, you may need to use the</span>
|
||||
<span class="c1"># flag --break-system-packages to install:</span>
|
||||
<span class="n">pip3</span> <span class="n">install</span> <span class="n">rns</span> <span class="o">--</span><span class="k">break</span><span class="o">-</span><span class="n">system</span><span class="o">-</span><span class="n">packages</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code> directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this <em>could</em> in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.</p>
|
||||
</div>
|
||||
<p>Additionally, some version combinations of macOS and Python require you to
|
||||
manually add your installed <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages directory to your <cite>PATH</cite> environment
|
||||
variable, before you can use installed commands in your terminal. Usually, adding
|
||||
the following line to your shell init script (for example <code class="docutils literal notranslate"><span class="pre">~/.zshrc</span></code>) will be enough:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>export PATH=$PATH:~/Library/Python/3.9/bin
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Adjust Python version and shell init script location according to your system.</p>
|
||||
</section>
|
||||
<section id="openwrt">
|
||||
<h3>OpenWRT<a class="headerlink" href="#openwrt" title="Permalink to this heading">#</a></h3>
|
||||
<p>On OpenWRT systems with sufficient storage and memory, you can install
|
||||
Reticulum and related utilities using the <cite>opkg</cite> package manager and <cite>pip</cite>.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>At the time of releasing this manual, work is underway to create pre-built
|
||||
Reticulum packages for OpenWRT, with full configuration, service
|
||||
and <code class="docutils literal notranslate"><span class="pre">uci</span></code> integration. Please see the <a class="reference external" href="https://github.com/gretel/feed-reticulum">feed-reticulum</a>
|
||||
and <a class="reference external" href="https://github.com/gretel/reticulum-openwrt">reticulum-openwrt</a>
|
||||
repositories for more information.</p>
|
||||
</div>
|
||||
<p>To install Reticulum on OpenWRT, first log into a command line session, and
|
||||
then use the following instructions:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
|
||||
<span class="n">opkg</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">cryptography</span> <span class="n">python3</span><span class="o">-</span><span class="n">pyserial</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
|
||||
<span class="c1"># Start rnsd with debug logging enabled</span>
|
||||
<span class="n">rnsd</span> <span class="o">-</span><span class="n">vvv</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The above instructions have been verified and tested on OpenWRT 21.02 only.
|
||||
It is likely that other versions may require slightly altered installation
|
||||
commands or package names. You will also need enough free space in your
|
||||
overlay FS, and enough free RAM to actually run Reticulum and any related
|
||||
programs and utilities.</p>
|
||||
</div>
|
||||
<p>Depending on your device configuration, you may need to adjust firewall rules
|
||||
for Reticulum connectivity to and from your device to work. Until proper
|
||||
packaging is ready, you will also need to manually create a service or startup
|
||||
script to automatically laucnh Reticulum at boot time.</p>
|
||||
<p>Please also note that the <cite>AutoInterface</cite> requires link-local IPv6 addresses
|
||||
to be enabled for any Ethernet and WiFi devices you intend to use. If <code class="docutils literal notranslate"><span class="pre">ip</span> <span class="pre">a</span></code>
|
||||
shows an address starting with <code class="docutils literal notranslate"><span class="pre">fe80::</span></code> for the device in question,
|
||||
<code class="docutils literal notranslate"><span class="pre">AutoInterface</span></code> should work for that device.</p>
|
||||
</section>
|
||||
<section id="raspberry-pi">
|
||||
<h3>Raspberry Pi<a class="headerlink" href="#raspberry-pi" title="Permalink to this heading">#</a></h3>
|
||||
<p>It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don’t always have packages available for some dependencies. If Python and the
|
||||
<cite>pip</cite> package manager is not already installed, do that first, and then
|
||||
install Reticulum using <cite>pip</cite>.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install dependencies</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">cryptography</span> <span class="n">python3</span><span class="o">-</span><span class="n">pyserial</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span> <span class="o">--</span><span class="k">break</span><span class="o">-</span><span class="n">system</span><span class="o">-</span><span class="n">packages</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code> directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this <em>could</em> in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.</p>
|
||||
</div>
|
||||
<p>While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing required build dependencies,
|
||||
and is not detailed in this manual.</p>
|
||||
</section>
|
||||
<section id="risc-v">
|
||||
<h3>RISC-V<a class="headerlink" href="#risc-v" title="Permalink to this heading">#</a></h3>
|
||||
<p>On some architectures, including RISC-V, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install <code class="docutils literal notranslate"><span class="pre">python3-dev</span></code> (or similar) before
|
||||
installing Reticulum or programs that depend on Reticulum.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Install Python and development packages</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">update</span>
|
||||
<span class="n">sudo</span> <span class="n">apt</span> <span class="n">install</span> <span class="n">python3</span> <span class="n">python3</span><span class="o">-</span><span class="n">pip</span> <span class="n">python3</span><span class="o">-</span><span class="n">dev</span>
|
||||
|
||||
<span class="c1"># Install Reticulum</span>
|
||||
<span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>With these packages installed, <code class="docutils literal notranslate"><span class="pre">pip</span></code> will be able to build any missing dependencies
|
||||
on your system locally.</p>
|
||||
</section>
|
||||
<section id="ubuntu-lunar">
|
||||
<h3>Ubuntu Lunar<a class="headerlink" href="#ubuntu-lunar" title="Permalink to this heading">#</a></h3>
|
||||
@@ -654,14 +792,48 @@ following section:</p>
|
||||
break-system-packages = true
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Please note that the “break-system-packages” directive is a somewhat misleading choice
|
||||
<p>For a one-shot installation of Reticulum, without globally enabling the <code class="docutils literal notranslate"><span class="pre">break-system-packages</span></code>
|
||||
option, you can use the following command:</p>
|
||||
<div class="highlight-text notranslate"><div class="highlight"><pre><span></span>pip install rns --break-system-packages
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">--break-system-packages</span></code> directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this _could_ in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems.</p>
|
||||
allow installing <code class="docutils literal notranslate"><span class="pre">pip</span></code> packages user- and system-wide. While this <em>could</em> in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="windows">
|
||||
<h3>Windows<a class="headerlink" href="#windows" title="Permalink to this heading">#</a></h3>
|
||||
<p>On Windows operating systems, the easiest way to install Reticulum is by using the
|
||||
<code class="docutils literal notranslate"><span class="pre">pip</span></code> package manager from the command line (either the command prompt or Windows
|
||||
Powershell).</p>
|
||||
<p>If you don’t already have Python installed, <a class="reference external" href="https://www.python.org/downloads/">download and install Python</a>.
|
||||
At the time of publication of this manual, the recommended version is <a class="reference external" href="https://www.python.org/downloads/release/python-3127">Python 3.12.7</a>.</p>
|
||||
<p><strong>Important!</strong> When asked by the installer, make sure to add the Python program to
|
||||
your PATH environment variables. If you don’t do this, you will not be able to
|
||||
use the <code class="docutils literal notranslate"><span class="pre">pip</span></code> installer, or run the included Reticulum utility programs (such as
|
||||
<code class="docutils literal notranslate"><span class="pre">rnsd</span></code> and <code class="docutils literal notranslate"><span class="pre">rnstatus</span></code>) from the command line.</p>
|
||||
<p>After installing Python, open the command prompt or Windows Powershell, and type:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">pip</span> <span class="n">install</span> <span class="n">rns</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can now use Reticulum and all included utility programs directly from your
|
||||
preferred command line interface.</p>
|
||||
</section>
|
||||
</section>
|
||||
<section id="pure-python-reticulum">
|
||||
<h2>Pure-Python Reticulum<a class="headerlink" href="#pure-python-reticulum" title="Permalink to this heading">#</a></h2>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>If you use the <code class="docutils literal notranslate"><span class="pre">rnspure</span></code> package to run Reticulum on systems that
|
||||
do not support <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>, it is
|
||||
important that you read and understand the <a class="reference internal" href="understanding.html#understanding-primitives"><span class="std std-ref">Cryptographic Primitives</span></a>
|
||||
section of this manual.</p>
|
||||
</div>
|
||||
<p>In some rare cases, and on more obscure system types, it is not possible to
|
||||
install one or more dependencies. In such situations,
|
||||
you can use the <code class="docutils literal notranslate"><span class="pre">rnspure</span></code> package instead of the <code class="docutils literal notranslate"><span class="pre">rns</span></code> package, or use <code class="docutils literal notranslate"><span class="pre">pip</span></code>
|
||||
@@ -675,10 +847,6 @@ only if they are <em>needed</em> and <em>available</em>. If for example you want
|
||||
on a system that cannot support <code class="docutils literal notranslate"><span class="pre">pyserial</span></code>, it is perfectly possible to do so using
|
||||
the <cite>rnspure</cite> package, but Reticulum will not be able to use serial-based interfaces.
|
||||
All other available modules will still be loaded when needed.</p>
|
||||
<p><strong>Please Note!</strong> If you use the <cite>rnspure</cite> package to run Reticulum on systems that
|
||||
do not support <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>, it is
|
||||
important that you read and understand the <a class="reference internal" href="understanding.html#understanding-primitives"><span class="std std-ref">Cryptographic Primitives</span></a>
|
||||
section of this manual.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -739,8 +907,10 @@ section of this manual.</p>
|
||||
<div class="toc-tree">
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Getting Started Fast</a><ul>
|
||||
<li><a class="reference internal" href="#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
|
||||
<li><a class="reference internal" href="#standalone-reticulum-installation">Standalone Reticulum Installation</a><ul>
|
||||
<li><a class="reference internal" href="#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
|
||||
<li><a class="reference internal" href="#remote-shell">Remote Shell</a></li>
|
||||
<li><a class="reference internal" href="#nomad-network">Nomad Network</a></li>
|
||||
@@ -753,14 +923,19 @@ section of this manual.</p>
|
||||
<li><a class="reference internal" href="#connecting-reticulum-instances-over-the-internet">Connecting Reticulum Instances Over the Internet</a></li>
|
||||
<li><a class="reference internal" href="#connect-to-the-public-testnet">Connect to the Public Testnet</a></li>
|
||||
<li><a class="reference internal" href="#adding-radio-interfaces">Adding Radio Interfaces</a></li>
|
||||
<li><a class="reference internal" href="#creating-and-using-custom-interfaces">Creating and Using Custom Interfaces</a></li>
|
||||
<li><a class="reference internal" href="#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li><a class="reference internal" href="#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
<li><a class="reference internal" href="#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
|
||||
<li><a class="reference internal" href="#android">Android</a></li>
|
||||
<li><a class="reference internal" href="#arm64">ARM64</a></li>
|
||||
<li><a class="reference internal" href="#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li><a class="reference internal" href="#debian-bookworm">Debian Bookworm</a></li>
|
||||
<li><a class="reference internal" href="#macos">MacOS</a></li>
|
||||
<li><a class="reference internal" href="#openwrt">OpenWRT</a></li>
|
||||
<li><a class="reference internal" href="#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li><a class="reference internal" href="#risc-v">RISC-V</a></li>
|
||||
<li><a class="reference internal" href="#ubuntu-lunar">Ubuntu Lunar</a></li>
|
||||
<li><a class="reference internal" href="#windows">Windows</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#pure-python-reticulum">Pure-Python Reticulum</a></li>
|
||||
|
||||
+37
-23
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Communications Hardware - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -279,20 +279,21 @@ completely from scratch, to your exact desired specifications, this chapter
|
||||
will explain the easiest possible approach to creating RNodes: Using common
|
||||
LoRa development boards. This approach can be boiled down to two simple steps:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Obtain one or more supported development boards</p></li>
|
||||
<li><p>Install the RNode firmware with the automated installer</p></li>
|
||||
<li><p>Obtain one or more <a class="reference internal" href="#rnode-supported"><span class="std std-ref">supported development boards</span></a></p></li>
|
||||
<li><p>Install the RNode firmware with the <a class="reference internal" href="#rnode-installation"><span class="std std-ref">automated installer</span></a></p></li>
|
||||
</ol>
|
||||
<p>Once the firmware has been installed and provisioned by the install script, it
|
||||
is ready to use with any software that supports RNodes, including Reticulum.
|
||||
The device can be used with Reticulum by adding an <a class="reference internal" href="interfaces.html#interfaces-rnode"><span class="std std-ref">RNodeInterface</span></a>
|
||||
to the configuration.</p>
|
||||
</section>
|
||||
<section id="supported-boards">
|
||||
<span id="rnode-supported"></span><h3>Supported Boards<a class="headerlink" href="#supported-boards" title="Permalink to this heading">#</a></h3>
|
||||
<section id="supported-boards-and-devices">
|
||||
<span id="rnode-supported"></span><h3>Supported Boards and Devices<a class="headerlink" href="#supported-boards-and-devices" 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>
|
||||
<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>
|
||||
<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">
|
||||
@@ -301,7 +302,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -311,7 +313,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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="lilygo-t3s3">
|
||||
<h4>LilyGO T3S3<a class="headerlink" href="#lilygo-t3s3" title="Permalink to this heading">#</a></h4>
|
||||
@@ -321,7 +324,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -331,7 +335,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -341,7 +346,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -351,7 +357,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -361,7 +368,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -371,7 +379,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -381,7 +390,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -391,7 +401,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -401,7 +412,8 @@ boards. The following boards are supported by the auto-installer.</p>
|
||||
<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>
|
||||
<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>
|
||||
@@ -450,8 +462,10 @@ Most devices will behave like this by default, or allow it via configuration opt
|
||||
and start communicating over them using Reticulum. It is not necessary to enable any IP
|
||||
infrastructure such as DHCP servers, DNS or similar, as long as at least Ethernet is
|
||||
available, and packets are passed transparently over the physical WiFi-based devices.</p>
|
||||
<a class="reference internal image-reference" href="_images/radio_rblhg5.png"><img alt="_images/radio_rblhg5.png" src="_images/radio_rblhg5.png" style="width: 49%;" /></a>
|
||||
<a class="reference internal image-reference" href="_images/radio_is5ac.png"><img alt="_images/radio_is5ac.png" src="_images/radio_is5ac.png" style="width: 49%;" /></a>
|
||||
<a class="reference internal image-reference" href="_images/radio_rblhg5.png"><img alt="_images/radio_rblhg5.png" src="_images/radio_rblhg5.png" style="width: 49%;" />
|
||||
</a>
|
||||
<a class="reference internal image-reference" href="_images/radio_is5ac.png"><img alt="_images/radio_is5ac.png" src="_images/radio_is5ac.png" style="width: 49%;" />
|
||||
</a>
|
||||
<p>Below is a list of example WiFi (and similar) radios that work well for high capacity
|
||||
Reticulum links over long distances:</p>
|
||||
<ul class="simple">
|
||||
@@ -550,7 +564,7 @@ can be used with Reticulum. This includes virtual software modems such as
|
||||
<li><a class="reference internal" href="#combining-hardware-types">Combining Hardware Types</a></li>
|
||||
<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="#supported-boards-and-devices">Supported Boards and Devices</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>
|
||||
|
||||
+17
-8
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="#"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -241,8 +241,10 @@ to participate in the development of Reticulum itself.</p>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="gettingstartedfast.html">Getting Started Fast</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#standalone-reticulum-installation">Standalone Reticulum Installation</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#standalone-reticulum-installation">Standalone Reticulum Installation</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#resolving-dependency-installation-issues">Resolving Dependency & Installation Issues</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#try-using-a-reticulum-based-program">Try Using a Reticulum-based Program</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#remote-shell">Remote Shell</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#nomad-network">Nomad Network</a></li>
|
||||
@@ -255,14 +257,19 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#connecting-reticulum-instances-over-the-internet">Connecting Reticulum Instances Over the Internet</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#connect-to-the-public-testnet">Connect to the Public Testnet</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#adding-radio-interfaces">Adding Radio Interfaces</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#creating-and-using-custom-interfaces">Creating and Using Custom Interfaces</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#develop-a-program-with-reticulum">Develop a Program with Reticulum</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#participate-in-reticulum-development">Participate in Reticulum Development</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#platform-specific-install-notes">Platform-Specific Install Notes</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#android">Android</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#arm64">ARM64</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#debian-bookworm">Debian Bookworm</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#macos">MacOS</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#openwrt">OpenWRT</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#raspberry-pi">Raspberry Pi</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#risc-v">RISC-V</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#ubuntu-lunar">Ubuntu Lunar</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="gettingstartedfast.html#windows">Windows</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="gettingstartedfast.html#pure-python-reticulum">Pure-Python Reticulum</a></li>
|
||||
@@ -321,7 +328,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="hardware.html#combining-hardware-types">Combining Hardware Types</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="hardware.html#rnode">RNode</a><ul>
|
||||
<li class="toctree-l3"><a class="reference internal" href="hardware.html#creating-rnodes">Creating RNodes</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="hardware.html#supported-boards">Supported Boards</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="hardware.html#supported-boards-and-devices">Supported Boards and Devices</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="hardware.html#installation">Installation</a></li>
|
||||
<li class="toctree-l3"><a class="reference internal" href="hardware.html#usage-with-reticulum">Usage with Reticulum</a></li>
|
||||
</ul>
|
||||
@@ -333,6 +340,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="interfaces.html">Configuring Interfaces</a><ul>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#custom-interfaces">Custom Interfaces</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#auto-interface">Auto Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#i2p-interface">I2P Interface</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="interfaces.html#tcp-server-interface">TCP Server Interface</a></li>
|
||||
@@ -371,6 +379,7 @@ to participate in the development of Reticulum itself.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#channel">Channel</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#buffer">Buffer</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#filetransfer">Filetransfer</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="examples.html#custom-interfaces">Custom Interfaces</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="toctree-l1"><a class="reference internal" href="support.html">Support Reticulum</a><ul>
|
||||
|
||||
+153
-76
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Configuring Interfaces - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -233,6 +233,13 @@ and gives example configurations for the respective interface types.</p>
|
||||
<p>For a high-level overview of how networks can be formed over different interface
|
||||
types, have a look at the <a class="reference internal" href="networks.html#networks-main"><span class="std std-ref">Building Networks</span></a> chapter of this
|
||||
manual.</p>
|
||||
<section id="custom-interfaces">
|
||||
<span id="interfaces-custom"></span><h2>Custom Interfaces<a class="headerlink" href="#custom-interfaces" title="Permalink to this heading">#</a></h2>
|
||||
<p>In addition to the built-in interface types, Reticulum is <strong>fully extensible</strong> with
|
||||
custom, user- or community-supplied interfaces, and creating custom interface
|
||||
modules is straightforward. Please see the <a class="reference internal" href="examples.html#example-custominterface"><span class="std std-ref">custom interface</span></a>
|
||||
example for basic interface code to build upon.</p>
|
||||
</section>
|
||||
<section id="auto-interface">
|
||||
<span id="interfaces-auto"></span><h2>Auto Interface<a class="headerlink" href="#auto-interface" title="Permalink to this heading">#</a></h2>
|
||||
<p>The Auto Interface enables communication with other discoverable Reticulum
|
||||
@@ -346,11 +353,14 @@ list of I2P base32 addresses to the <code class="docutils literal notranslate"><
|
||||
<p>It can take anywhere from a few seconds to a few minutes to establish
|
||||
I2P connections to the desired peers, so Reticulum handles the process
|
||||
in the background, and will output relevant events to the log.</p>
|
||||
<p><strong>Please Note!</strong> While the I2P interface is the simplest way to use
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>While the I2P interface is the simplest way to use
|
||||
Reticulum over I2P, it is also possible to tunnel the TCP server and
|
||||
client interfaces over I2P manually. This can be useful in situations
|
||||
where more control is needed, but requires manual tunnel setup through
|
||||
the I2P daemon configuration.</p>
|
||||
</div>
|
||||
<p>It is important to note that the two methods are <em>interchangably compatible</em>.
|
||||
You can use the I2PInterface to connect to a TCPServerInterface that
|
||||
was manually tunneled over I2P, for example. This offers a high degree
|
||||
@@ -360,7 +370,7 @@ use-cases.</p>
|
||||
<section id="tcp-server-interface">
|
||||
<span id="interfaces-tcps"></span><h2>TCP Server Interface<a class="headerlink" href="#tcp-server-interface" title="Permalink to this heading">#</a></h2>
|
||||
<p>The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
the Internet or private IPv4 and IPv6 networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
|
||||
<span class="c1"># It will listen for incoming connections on the</span>
|
||||
@@ -387,8 +397,35 @@ configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
<span class="c1"># port = 4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
<p>If you are using the interface on a device which has both IPv4 and IPv6 addresses available,
|
||||
you can use the <code class="docutils literal notranslate"><span class="pre">prefer_ipv6</span></code> option to bind to the IPv6 address:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example demonstrates a TCP server interface.</span>
|
||||
<span class="c1"># It will listen for incoming connections on the</span>
|
||||
<span class="c1"># specified IP address and port number.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="n">device</span> <span class="o">=</span> <span class="n">eth0</span>
|
||||
<span class="n">port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
<span class="n">prefer_ipv6</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>To use the TCP Server Interface over <a class="reference external" href="https://yggdrasil-network.github.io/">Yggdrasil</a>, you
|
||||
can simply specify the Yggdrasil <code class="docutils literal notranslate"><span class="pre">tun</span></code> device and a listening port, like so:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Yggdrasil</span> <span class="n">TCP</span> <span class="n">Server</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">device</span> <span class="o">=</span> <span class="n">tun0</span>
|
||||
<span class="n">listen_port</span> <span class="o">=</span> <span class="mi">4343</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:</p>
|
||||
</div>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Server</span> <span class="n">on</span> <span class="n">I2P</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPServerInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
@@ -409,7 +446,7 @@ same TCP Server interface at the same time.</p>
|
||||
This means that Reticulum will gracefully handle IP links that go up and down,
|
||||
and restore connectivity after a failure, once the other end of a TCP interface reappears.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of a TCP Client interface. The</span>
|
||||
<span class="c1"># target_host can either be an IP address or a hostname.</span>
|
||||
<span class="c1"># target_host can be a hostname or an IPv4 or IPv6 address.</span>
|
||||
|
||||
<span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
@@ -418,6 +455,15 @@ and restore connectivity after a failure, once the other end of a TCP interface
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4242</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>To use the TCP Client Interface over <a class="reference external" href="https://yggdrasil-network.github.io/">Yggdrasil</a>, simply
|
||||
specify the target Yggdrasil IPv6 address and port, like so:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Yggdrasil</span> <span class="n">TCP</span> <span class="n">Client</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
<span class="n">target_host</span> <span class="o">=</span> <span class="mi">201</span><span class="p">:</span><span class="mi">5</span><span class="n">d78</span><span class="p">:</span><span class="n">af73</span><span class="p">:</span><span class="mi">5</span><span class="n">caf</span><span class="p">:</span><span class="n">a4de</span><span class="p">:</span><span class="n">a79f</span><span class="p">:</span><span class="mi">3278</span><span class="p">:</span><span class="mf">71e5</span>
|
||||
<span class="n">target_port</span> <span class="o">=</span> <span class="mi">4343</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code> option:</p>
|
||||
@@ -438,8 +484,11 @@ and programs like soundmodems and similar over TCP. When using the
|
||||
never enable <code class="docutils literal notranslate"><span class="pre">kiss_framing</span></code>, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.</p>
|
||||
<p><strong>Please Note!</strong> The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:</p>
|
||||
</div>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">TCP</span> <span class="n">Client</span> <span class="n">over</span> <span class="n">I2P</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">TCPClientInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="n">yes</span>
|
||||
@@ -455,11 +504,14 @@ you must use the i2p_tunneled option:</p>
|
||||
private and the internet. It can also allow broadcast communication
|
||||
over IP networks, so it can provide an easy way to enable connectivity
|
||||
with all other peers on a local area network.</p>
|
||||
<p><em>Please Note!</em> Using broadcast UDP traffic has performance implications,
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>Using broadcast UDP traffic has performance implications,
|
||||
especially on WiFi. If your goal is simply to enable easy communication
|
||||
with all peers in your local Ethernet broadcast domain, the
|
||||
<a class="reference internal" href="#interfaces-auto"><span class="std std-ref">Auto Interface</span></a> performs better, and is even
|
||||
easier to use.</p>
|
||||
</div>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># This example enables communication with other</span>
|
||||
<span class="c1"># local Reticulum peers over UDP.</span>
|
||||
|
||||
@@ -508,6 +560,12 @@ easier to use.</p>
|
||||
<span id="interfaces-rnode"></span><h2>RNode LoRa Interface<a class="headerlink" href="#rnode-lora-interface" title="Permalink to this heading">#</a></h2>
|
||||
<p>To use Reticulum over LoRa, the <a class="reference external" href="https://unsigned.io/rnode/">RNode</a> interface
|
||||
can be used, and offers full control over LoRa parameters.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.</p>
|
||||
</div>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of how to add a LoRa interface</span>
|
||||
<span class="c1"># using the RNode LoRa transceiver.</span>
|
||||
|
||||
@@ -590,6 +648,12 @@ can be used, and offers full control over LoRa parameters.</p>
|
||||
<span id="interfaces-rnode-multi"></span><h2>RNode Multi Interface<a class="headerlink" href="#rnode-multi-interface" title="Permalink to this heading">#</a></h2>
|
||||
<p>For RNodes that support multiple LoRa transceivers, the RNode
|
||||
Multi interface can be used to configure sub-interfaces individually.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.</p>
|
||||
</div>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Here's an example of how to add an RNode Multi interface</span>
|
||||
<span class="c1"># using the RNode LoRa transceiver.</span>
|
||||
|
||||
@@ -611,89 +675,89 @@ Multi interface can be used to configure sub-interfaces individually.</p>
|
||||
<span class="c1"># id_interval = 600</span>
|
||||
|
||||
<span class="c1"># A subinterface</span>
|
||||
<span class="p">[[[</span><span class="n">HIGHDATARATE</span><span class="p">]]]</span>
|
||||
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="p">[[[</span><span class="n">High</span> <span class="n">Datarate</span><span class="p">]]]</span>
|
||||
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Set frequency to 2.4GHz</span>
|
||||
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">2400000000</span>
|
||||
<span class="c1"># Set frequency to 2.4GHz</span>
|
||||
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">2400000000</span>
|
||||
|
||||
<span class="c1"># Set LoRa bandwidth to 1625 KHz</span>
|
||||
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">1625000</span>
|
||||
<span class="c1"># Set LoRa bandwidth to 1625 KHz</span>
|
||||
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">1625000</span>
|
||||
|
||||
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
|
||||
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
|
||||
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
|
||||
<span class="c1"># The virtual port, only the manufacturer</span>
|
||||
<span class="c1"># or the person who wrote the board config</span>
|
||||
<span class="c1"># can tell you what it will be for which</span>
|
||||
<span class="c1"># physical hardware interface</span>
|
||||
<span class="n">vport</span> <span class="o">=</span> <span class="mi">1</span>
|
||||
<span class="c1"># The virtual port, only the manufacturer</span>
|
||||
<span class="c1"># or the person who wrote the board config</span>
|
||||
<span class="c1"># can tell you what it will be for which</span>
|
||||
<span class="c1"># physical hardware interface</span>
|
||||
<span class="n">vport</span> <span class="o">=</span> <span class="mi">1</span>
|
||||
|
||||
<span class="c1"># Select spreading factor 5. Valid</span>
|
||||
<span class="c1"># range is 5 through 12, with 5</span>
|
||||
<span class="c1"># being the fastest and 12 having</span>
|
||||
<span class="c1"># the longest range.</span>
|
||||
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">5</span>
|
||||
<span class="c1"># Select spreading factor 5. Valid</span>
|
||||
<span class="c1"># range is 5 through 12, with 5</span>
|
||||
<span class="c1"># being the fastest and 12 having</span>
|
||||
<span class="c1"># the longest range.</span>
|
||||
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">5</span>
|
||||
|
||||
<span class="c1"># Select coding rate 5. Valid range</span>
|
||||
<span class="c1"># is 5 throough 8, with 5 being the</span>
|
||||
<span class="c1"># fastest, and 8 the longest range.</span>
|
||||
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
|
||||
<span class="c1"># Select coding rate 5. Valid range</span>
|
||||
<span class="c1"># is 5 throough 8, with 5 being the</span>
|
||||
<span class="c1"># fastest, and 8 the longest range.</span>
|
||||
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
|
||||
|
||||
<span class="c1"># It is possible to limit the airtime</span>
|
||||
<span class="c1"># utilisation of an RNode by using the</span>
|
||||
<span class="c1"># following two configuration options.</span>
|
||||
<span class="c1"># The short-term limit is applied in a</span>
|
||||
<span class="c1"># window of approximately 15 seconds,</span>
|
||||
<span class="c1"># and the long-term limit is enforced</span>
|
||||
<span class="c1"># over a rolling 60 minute window. Both</span>
|
||||
<span class="c1"># options are specified in percent.</span>
|
||||
<span class="c1"># It is possible to limit the airtime</span>
|
||||
<span class="c1"># utilisation of an RNode by using the</span>
|
||||
<span class="c1"># following two configuration options.</span>
|
||||
<span class="c1"># The short-term limit is applied in a</span>
|
||||
<span class="c1"># window of approximately 15 seconds,</span>
|
||||
<span class="c1"># and the long-term limit is enforced</span>
|
||||
<span class="c1"># over a rolling 60 minute window. Both</span>
|
||||
<span class="c1"># options are specified in percent.</span>
|
||||
|
||||
<span class="c1"># airtime_limit_long = 100</span>
|
||||
<span class="c1"># airtime_limit_short = 100</span>
|
||||
<span class="c1"># airtime_limit_long = 100</span>
|
||||
<span class="c1"># airtime_limit_short = 100</span>
|
||||
|
||||
<span class="p">[[[</span><span class="n">LOWDATARATE</span><span class="p">]]]</span>
|
||||
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="p">[[[</span><span class="n">Low</span> <span class="n">Datarate</span><span class="p">]]]</span>
|
||||
<span class="c1"># Subinterfaces can be enabled and disabled in of themselves</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="c1"># Set frequency to 865.6 MHz</span>
|
||||
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">865600000</span>
|
||||
<span class="c1"># Set frequency to 865.6 MHz</span>
|
||||
<span class="n">frequency</span> <span class="o">=</span> <span class="mi">865600000</span>
|
||||
|
||||
<span class="c1"># The virtual port, only the manufacturer</span>
|
||||
<span class="c1"># or the person who wrote the board config</span>
|
||||
<span class="c1"># can tell you what it will be for which</span>
|
||||
<span class="c1"># physical hardware interface</span>
|
||||
<span class="n">vport</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
<span class="c1"># The virtual port, only the manufacturer</span>
|
||||
<span class="c1"># or the person who wrote the board config</span>
|
||||
<span class="c1"># can tell you what it will be for which</span>
|
||||
<span class="c1"># physical hardware interface</span>
|
||||
<span class="n">vport</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
|
||||
<span class="c1"># Set LoRa bandwidth to 125 KHz</span>
|
||||
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">125000</span>
|
||||
<span class="c1"># Set LoRa bandwidth to 125 KHz</span>
|
||||
<span class="n">bandwidth</span> <span class="o">=</span> <span class="mi">125000</span>
|
||||
|
||||
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
|
||||
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
<span class="c1"># Set TX power to 0 dBm (0.12 mW)</span>
|
||||
<span class="n">txpower</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
|
||||
<span class="c1"># Select spreading factor 7. Valid</span>
|
||||
<span class="c1"># range is 5 through 12, with 5</span>
|
||||
<span class="c1"># being the fastest and 12 having</span>
|
||||
<span class="c1"># the longest range.</span>
|
||||
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">7</span>
|
||||
<span class="c1"># Select spreading factor 7. Valid</span>
|
||||
<span class="c1"># range is 5 through 12, with 5</span>
|
||||
<span class="c1"># being the fastest and 12 having</span>
|
||||
<span class="c1"># the longest range.</span>
|
||||
<span class="n">spreadingfactor</span> <span class="o">=</span> <span class="mi">7</span>
|
||||
|
||||
<span class="c1"># Select coding rate 5. Valid range</span>
|
||||
<span class="c1"># is 5 throough 8, with 5 being the</span>
|
||||
<span class="c1"># fastest, and 8 the longest range.</span>
|
||||
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
|
||||
<span class="c1"># Select coding rate 5. Valid range</span>
|
||||
<span class="c1"># is 5 throough 8, with 5 being the</span>
|
||||
<span class="c1"># fastest, and 8 the longest range.</span>
|
||||
<span class="n">codingrate</span> <span class="o">=</span> <span class="mi">5</span>
|
||||
|
||||
<span class="c1"># It is possible to limit the airtime</span>
|
||||
<span class="c1"># utilisation of an RNode by using the</span>
|
||||
<span class="c1"># following two configuration options.</span>
|
||||
<span class="c1"># The short-term limit is applied in a</span>
|
||||
<span class="c1"># window of approximately 15 seconds,</span>
|
||||
<span class="c1"># and the long-term limit is enforced</span>
|
||||
<span class="c1"># over a rolling 60 minute window. Both</span>
|
||||
<span class="c1"># options are specified in percent.</span>
|
||||
<span class="c1"># It is possible to limit the airtime</span>
|
||||
<span class="c1"># utilisation of an RNode by using the</span>
|
||||
<span class="c1"># following two configuration options.</span>
|
||||
<span class="c1"># The short-term limit is applied in a</span>
|
||||
<span class="c1"># window of approximately 15 seconds,</span>
|
||||
<span class="c1"># and the long-term limit is enforced</span>
|
||||
<span class="c1"># over a rolling 60 minute window. Both</span>
|
||||
<span class="c1"># options are specified in percent.</span>
|
||||
|
||||
<span class="c1"># airtime_limit_long = 100</span>
|
||||
<span class="c1"># airtime_limit_short = 100</span>
|
||||
<span class="c1"># airtime_limit_long = 100</span>
|
||||
<span class="c1"># airtime_limit_short = 100</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -744,6 +808,12 @@ Reticulum will try to respawn the program after waiting for <code class="docutil
|
||||
radio modems and TNCs, including <a class="reference external" href="https://unsigned.io/openmodem/">OpenModem</a>.
|
||||
KISS interfaces can also be configured to periodically send out beacons
|
||||
for station identification purposes.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.</p>
|
||||
</div>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">KISSInterface</span>
|
||||
<span class="n">interface_enabled</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
@@ -801,6 +871,12 @@ layer for anything, and it incurs extra overhead on every packet to
|
||||
encapsulate in AX.25.</p>
|
||||
<p>A more efficient way is to use the plain KISS interface with the
|
||||
beaconing functionality described above.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.</p>
|
||||
</div>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">[[</span><span class="n">Packet</span> <span class="n">Radio</span> <span class="n">AX</span><span class="mf">.25</span> <span class="n">KISS</span> <span class="n">Interface</span><span class="p">]]</span>
|
||||
<span class="nb">type</span> <span class="o">=</span> <span class="n">AX25KISSInterface</span>
|
||||
|
||||
@@ -1226,6 +1302,7 @@ to <code class="docutils literal notranslate"><span class="pre">30</span></code>
|
||||
<div class="toc-tree">
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Configuring Interfaces</a><ul>
|
||||
<li><a class="reference internal" href="#custom-interfaces">Custom Interfaces</a></li>
|
||||
<li><a class="reference internal" href="#auto-interface">Auto Interface</a></li>
|
||||
<li><a class="reference internal" href="#i2p-interface">I2P Interface</a></li>
|
||||
<li><a class="reference internal" href="#tcp-server-interface">TCP Server Interface</a></li>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Building Networks - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
|
||||
Binary file not shown.
+72
-10
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>API Reference - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 beta documentation</span>
|
||||
|
||||
</a><form class="sidebar-search-container" method="get" action="search.html" role="search">
|
||||
<input class="sidebar-search" placeholder="Search" name="q" aria-label="Search">
|
||||
@@ -228,7 +228,7 @@ This chapter lists and explains all classes exposed by the Reticulum Network Sta
|
||||
<p id="api-reticulum"><h3> Reticulum </h3></p>
|
||||
<dl class="py class">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum">
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
|
||||
<em class="property"><span class="pre">class</span><span class="w"> </span></em><span class="sig-prename descclassname"><span class="pre">RNS.</span></span><span class="sig-name descname"><span class="pre">Reticulum</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">configdir</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">loglevel</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">logdest</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">verbosity</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">require_shared_instance</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">False</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>This class is used to initialise access to Reticulum within a
|
||||
program. You must create exactly one instance of this class before
|
||||
carrying out any other RNS operations, such as creating destinations
|
||||
@@ -261,6 +261,17 @@ MTU is a prerequisite for peers to communicate in the same network.</p>
|
||||
the default value.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum.LINK_MTU_DISCOVERY">
|
||||
<span class="sig-name descname"><span class="pre">LINK_MTU_DISCOVERY</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">False</span></em><a class="headerlink" href="#RNS.Reticulum.LINK_MTU_DISCOVERY" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Whether automatic link MTU discovery is enabled by default in this
|
||||
release. Link MTU discovery significantly increases throughput over
|
||||
fast links, but requires all intermediary hops to also support it.
|
||||
Support for this feature was added in RNS version 0.9.0. This option
|
||||
will become enabled by default in the near future. Please update your
|
||||
RNS instances.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py attribute">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum.ANNOUNCE_CAP">
|
||||
<span class="sig-name descname"><span class="pre">ANNOUNCE_CAP</span></span><em class="property"><span class="w"> </span><span class="p"><span class="pre">=</span></span><span class="w"> </span><span class="pre">2</span></em><a class="headerlink" href="#RNS.Reticulum.ANNOUNCE_CAP" title="Permalink to this definition">#</a></dt>
|
||||
@@ -319,6 +330,21 @@ and pass announces over the network.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum.link_mtu_discovery">
|
||||
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">link_mtu_discovery</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.link_mtu_discovery" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Returns whether link MTU discovery is enabled for the running
|
||||
instance.</p>
|
||||
<p>When link MTU discovery is enabled, Reticulum will
|
||||
automatically upgrade link MTUs to the highest supported
|
||||
value, increasing transfer speed and efficiency.</p>
|
||||
<dl class="field-list simple">
|
||||
<dt class="field-odd">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p>True if link MTU discovery is enabled, False if not.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Reticulum.remote_management_enabled">
|
||||
<em class="property"><span class="pre">static</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">remote_management_enabled</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Reticulum.remote_management_enabled" title="Permalink to this definition">#</a></dt>
|
||||
@@ -1029,6 +1055,36 @@ ephemeral keys, and offers <strong>Forward Secrecy</strong>.</p>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Packet.get_rssi">
|
||||
<span class="sig-name descname"><span class="pre">get_rssi</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet.get_rssi" title="Permalink to this definition">#</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p>The physical layer <em>Received Signal Strength Indication</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Packet.get_snr">
|
||||
<span class="sig-name descname"><span class="pre">get_snr</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet.get_snr" title="Permalink to this definition">#</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p>The physical layer <em>Signal-to-Noise Ratio</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py method">
|
||||
<dt class="sig sig-object py" id="RNS.Packet.get_q">
|
||||
<span class="sig-name descname"><span class="pre">get_q</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#RNS.Packet.get_q" title="Permalink to this definition">#</a></dt>
|
||||
<dd><dl class="field-list simple">
|
||||
<dt class="field-odd">Returns<span class="colon">:</span></dt>
|
||||
<dd class="field-odd"><p>The physical layer <em>Link Quality</em> if available, otherwise <code class="docutils literal notranslate"><span class="pre">None</span></code>.</p>
|
||||
</dd>
|
||||
</dl>
|
||||
</dd></dl>
|
||||
|
||||
</dd></dl>
|
||||
|
||||
<p id="api-packetreceipt"><h3> Packet Receipt </h3></p>
|
||||
@@ -1670,8 +1726,8 @@ returning handler will not be called.</p>
|
||||
</dd></dl>
|
||||
|
||||
<dl class="py property">
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel.MDU">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">MDU</span></span><a class="headerlink" href="#RNS.Channel.Channel.MDU" title="Permalink to this definition">#</a></dt>
|
||||
<dt class="sig sig-object py" id="RNS.Channel.Channel.mdu">
|
||||
<em class="property"><span class="pre">property</span><span class="w"> </span></em><span class="sig-name descname"><span class="pre">mdu</span></span><a class="headerlink" href="#RNS.Channel.Channel.mdu" title="Permalink to this definition">#</a></dt>
|
||||
<dd><p>Maximum Data Unit: the number of bytes available
|
||||
for a message to consume in a single send. This
|
||||
value is adjusted from the <code class="docutils literal notranslate"><span class="pre">Link</span></code> MDU to accommodate
|
||||
@@ -1913,8 +1969,9 @@ Transport system of Reticulum.</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. 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>
|
||||
or <em>received_announce(destination_hash, announced_identity, app_data, announce_packet_hash)</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>
|
||||
@@ -2053,11 +2110,13 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#">API Reference</a><ul>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum"><code class="docutils literal notranslate"><span class="pre">Reticulum</span></code></a><ul>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.MTU"><code class="docutils literal notranslate"><span class="pre">MTU</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.LINK_MTU_DISCOVERY"><code class="docutils literal notranslate"><span class="pre">LINK_MTU_DISCOVERY</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.ANNOUNCE_CAP"><code class="docutils literal notranslate"><span class="pre">ANNOUNCE_CAP</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.MINIMUM_BITRATE"><code class="docutils literal notranslate"><span class="pre">MINIMUM_BITRATE</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.get_instance"><code class="docutils literal notranslate"><span class="pre">get_instance()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.should_use_implicit_proof"><code class="docutils literal notranslate"><span class="pre">should_use_implicit_proof()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.transport_enabled"><code class="docutils literal notranslate"><span class="pre">transport_enabled()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.link_mtu_discovery"><code class="docutils literal notranslate"><span class="pre">link_mtu_discovery()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Reticulum.remote_management_enabled"><code class="docutils literal notranslate"><span class="pre">remote_management_enabled()</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@@ -2120,6 +2179,9 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#RNS.Packet.PLAIN_MDU"><code class="docutils literal notranslate"><span class="pre">PLAIN_MDU</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Packet.send"><code class="docutils literal notranslate"><span class="pre">send()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Packet.resend"><code class="docutils literal notranslate"><span class="pre">resend()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Packet.get_rssi"><code class="docutils literal notranslate"><span class="pre">get_rssi()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Packet.get_snr"><code class="docutils literal notranslate"><span class="pre">get_snr()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Packet.get_q"><code class="docutils literal notranslate"><span class="pre">get_q()</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.PacketReceipt"><code class="docutils literal notranslate"><span class="pre">PacketReceipt</span></code></a><ul>
|
||||
@@ -2188,7 +2250,7 @@ will announce it.</p>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.remove_message_handler"><code class="docutils literal notranslate"><span class="pre">remove_message_handler()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.is_ready_to_send"><code class="docutils literal notranslate"><span class="pre">is_ready_to_send()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.send"><code class="docutils literal notranslate"><span class="pre">send()</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.MDU"><code class="docutils literal notranslate"><span class="pre">MDU</span></code></a></li>
|
||||
<li><a class="reference internal" href="#RNS.Channel.Channel.mdu"><code class="docutils literal notranslate"><span class="pre">mdu</span></code></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#RNS.MessageBase"><code class="docutils literal notranslate"><span class="pre">MessageBase</span></code></a><ul>
|
||||
|
||||
@@ -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.8.2 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.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Support Reticulum - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -248,7 +248,8 @@ organisation? Make them a reality quickly by sponsoring their implementation.</p
|
||||
<h2>Provide Feedback<a class="headerlink" href="#provide-feedback" title="Permalink to this heading">#</a></h2>
|
||||
<p>All feedback on the usage, functioning and potential dysfunctioning of any and
|
||||
all components of the system is very valuable to the continued development and
|
||||
improvement of Reticulum. Absolutely no automated analytics, telemetry, error
|
||||
improvement of Reticulum.</p>
|
||||
<p>Absolutely no automated analytics, telemetry, error
|
||||
reporting or statistics is collected and reported by Reticulum under any
|
||||
circumstances, so we rely on old-fashioned human feedback.</p>
|
||||
</section>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Understanding Reticulum - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -1069,11 +1069,13 @@ 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>Modified Fernet for encrypted tokens</p>
|
||||
<li><p>Encrypted tokens are based on the Fernet spec</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>
|
||||
<li><p>Ephemeral keys 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 message authentication</p></li>
|
||||
<li><p>IVs are generated through os.urandom()</p></li>
|
||||
<li><p>No Fernet version and timestamp metadata fields</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>SHA-256</p></li>
|
||||
@@ -1083,12 +1085,12 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
|
||||
primitives are provided by <a class="reference external" href="https://www.openssl.org/">OpenSSL</a> (via the <a class="reference external" href="https://github.com/pyca/cryptography">PyCA/cryptography</a>
|
||||
package). The hashing functions <code class="docutils literal notranslate"><span class="pre">SHA-256</span></code> and <code class="docutils literal notranslate"><span class="pre">SHA-512</span></code> are provided by the standard
|
||||
Python <a class="reference external" href="https://docs.python.org/3/library/hashlib.html">hashlib</a>. The <code class="docutils literal notranslate"><span class="pre">HKDF</span></code>, <code class="docutils literal notranslate"><span class="pre">HMAC</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">Fernet</span></code> primitives, and the <code class="docutils literal notranslate"><span class="pre">PKCS7</span></code> padding function are always provided by the
|
||||
<code class="docutils literal notranslate"><span class="pre">Token</span></code> primitives, and the <code class="docutils literal notranslate"><span class="pre">PKCS7</span></code> padding function are always provided by the
|
||||
following internal implementations:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/HKDF.py</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/HMAC.py</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/Fernet.py</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/Token.py</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">RNS/Cryptography/PKCS7.py</span></code></p></li>
|
||||
</ul>
|
||||
<p>Reticulum also includes a complete implementation of all necessary primitives in pure Python.
|
||||
@@ -1097,9 +1099,12 @@ instead use the internal pure-python primitives. A trivial consequence of this i
|
||||
with the OpenSSL backend being <em>much</em> faster. The most important consequence however, is the
|
||||
potential loss of security by using primitives that has not seen the same amount of scrutiny,
|
||||
testing and review as those from OpenSSL.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>If you want to use the internal pure-python primitives, it is <strong>highly advisable</strong> that you
|
||||
have a good understanding of the risks that this pose, and make an informed decision on whether
|
||||
those risks are acceptable to you.</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>Using Reticulum on Your System - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
|
||||
+49
-24
@@ -2,11 +2,11 @@
|
||||
<html class="no-js" lang="en">
|
||||
<head><meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
||||
<meta name="color-scheme" content="light dark"><meta name="generator" content="Docutils 0.18.1: http://docutils.sourceforge.net/" />
|
||||
<meta name="color-scheme" content="light dark"><meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<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.8.2 beta documentation</title>
|
||||
<title>What is Reticulum? - Reticulum Network Stack 0.9.0 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.8.2 beta documentation</div></a>
|
||||
<a href="index.html"><div class="brand">Reticulum Network Stack 0.9.0 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.8.2 beta documentation</span>
|
||||
<span class="sidebar-brand-text">Reticulum Network Stack 0.9.0 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">
|
||||
@@ -235,7 +235,7 @@ respect and empower the autonomy and sovereignty of communities and individuals.
|
||||
Reticulum enables secure digital communication that cannot be subjected to
|
||||
outside control, manipulation or censorship.</p>
|
||||
<p>Reticulum enables the construction of both small and potentially planetary-scale
|
||||
networks, without any need for hierarchical or beaureucratic structures to control
|
||||
networks, without any need for hierarchical or bureaucratic structures to control
|
||||
or manage them, while ensuring individuals and communities full sovereignty
|
||||
over their own network segments.</p>
|
||||
<p>Reticulum is a <strong>complete networking stack</strong>, and does not need IP or higher
|
||||
@@ -258,37 +258,62 @@ considered complete and stable at the moment, but could change if absolutely war
|
||||
<h2>What does Reticulum Offer?<a class="headerlink" href="#what-does-reticulum-offer" title="Permalink to this heading">#</a></h2>
|
||||
<ul class="simple">
|
||||
<li><p>Coordination-less globally unique addressing and identification</p></li>
|
||||
<li><p>Fully self-configuring multi-hop routing</p></li>
|
||||
<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 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>
|
||||
<li><p>Fully self-configuring multi-hop routing over heterogeneous carriers</p></li>
|
||||
<li><p>Flexible scalability over heterogeneous topologies</p>
|
||||
<ul>
|
||||
<li><p>Keys are ephemeral and derived from an ECDH key exchange on Curve25519</p></li>
|
||||
<li><p>Reticulum can carry data over any mixture of physical mediums and topologies</p></li>
|
||||
<li><p>Low-bandwidth networks can co-exist and interoperate with large, high-bandwidth networks</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Initiator anonymity, communicate without revealing your identity</p>
|
||||
<ul>
|
||||
<li><p>Reticulum does not include source addresses on any packets</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication</p>
|
||||
<ul>
|
||||
<li><p>The foundational Reticulum Identity Keys are 512-bit Elliptic Curve keysets</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Forward Secrecy is available for all communication types, both for single packets and over links</p></li>
|
||||
<li><p>Reticulum uses the following format for encrypted tokens:</p>
|
||||
<ul>
|
||||
<li><p>Ephemeral per-packet and link keys 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>
|
||||
<li><p>A variety of supported interface types</p></li>
|
||||
<li><p>An intuitive and developer-friendly API</p></li>
|
||||
<li><p>Flexible and extensible interface system</p>
|
||||
<ul>
|
||||
<li><p>Reticulum includes a large variety of built-in interface types</p></li>
|
||||
<li><p>Ability to load and utilise custom user- or community-supplied interface types</p></li>
|
||||
<li><p>Easily create your own custom interfaces for communicating over anything</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Authentication and virtual network segmentation on all supported interface types</p></li>
|
||||
<li><p>An intuitive and easy-to-use API</p>
|
||||
<ul>
|
||||
<li><p>Simpler and easier to use than sockets APIs and simpler, but more powerful</p></li>
|
||||
<li><p>Makes building distributed and decentralised applications much simpler</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Reliable and efficient transfer of arbitrary amounts of data</p>
|
||||
<ul>
|
||||
<li><p>Reticulum can handle a few bytes of data or files of many gigabytes</p></li>
|
||||
<li><p>Sequencing, compression, transfer coordination and checksumming are automatic</p></li>
|
||||
<li><p>The API is very easy to use, and provides transfer progress</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Lightweight, flexible and expandable Request/Response mechanism</p></li>
|
||||
<li><p>Efficient link establishment</p>
|
||||
<ul>
|
||||
<li><p>Total cost of setting up an encrypted and verified link is only 3 packets, totalling 297 bytes</p></li>
|
||||
<li><p>Low cost of keeping links open at only 0.44 bits per second</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Reliable and efficient transfer of arbitrary amounts of data</p>
|
||||
<ul>
|
||||
<li><p>Reticulum can handle a few bytes of data or files of many gigabytes</p></li>
|
||||
<li><p>Sequencing, transfer coordination and checksumming is automatic</p></li>
|
||||
<li><p>The API is very easy to use, and provides transfer progress</p></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><p>Authentication and virtual network segmentation on all supported interface types</p></li>
|
||||
<li><p>Flexible scalability allowing extremely low-bandwidth networks to co-exist and interoperate with large, high-bandwidth networks</p></li>
|
||||
<li><p>Reliable sequential delivery with Channel and Buffer mechanisms</p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="where-can-reticulum-be-used">
|
||||
@@ -316,7 +341,7 @@ network, and vice versa.</p>
|
||||
</section>
|
||||
<section id="interface-types-and-devices">
|
||||
<h2>Interface Types and Devices<a class="headerlink" href="#interface-types-and-devices" title="Permalink to this heading">#</a></h2>
|
||||
<p>Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, it’s relatively simple to implement an interface class. Currently, Reticulum can use the following devices and communication mediums:</p>
|
||||
<p>Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, it’s simple to <a class="reference internal" href="examples.html#example-custominterface"><span class="std std-ref">implement an interface class</span></a>. Currently, Reticulum can use the following devices and communication mediums:</p>
|
||||
<ul class="simple">
|
||||
<li><p>Any Ethernet device</p>
|
||||
<ul>
|
||||
|
||||
@@ -125,4 +125,18 @@ interface to efficiently pass files of any size over a Reticulum :ref:`Link<api-
|
||||
|
||||
.. literalinclude:: ../../Examples/Filetransfer.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py>`_.
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/Filetransfer.py>`_.
|
||||
|
||||
.. _example-custominterface:
|
||||
|
||||
Custom Interfaces
|
||||
=================
|
||||
|
||||
The *ExampleInterface* demonstrates creating custom interfaces for Reticulum.
|
||||
Any number of custom interfaces can be loaded and utilised by Reticulum, and
|
||||
will be fully on-par with natively included interfaces, including all supported
|
||||
:ref:`interface modes<interfaces-modes>` and :ref:`common configuration options<interfaces-options>`.
|
||||
|
||||
.. literalinclude:: ../../Examples/ExampleInterface.py
|
||||
|
||||
This example can also be found at `<https://github.com/markqvist/Reticulum/blob/master/Examples/ExampleInterface.py>`_.
|
||||
@@ -27,9 +27,14 @@ and install them offline using ``pip``:
|
||||
|
||||
pip install ./rns-0.5.1-py3-none-any.whl
|
||||
|
||||
For more detailed installation instructions, please see the
|
||||
:ref:`Platform-Specific Install Notes<install-guides>` section.
|
||||
|
||||
After installation is complete, it might be helpful to refer to the
|
||||
:ref:`Using Reticulum on Your System<using-main>` chapter.
|
||||
|
||||
Resolving Dependency & Installation Issues
|
||||
=============================================
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some platforms, there may not be binary packages available for all dependencies, and
|
||||
``pip`` installation may fail with an error message. In these cases, the issue can usually
|
||||
be resolved by installing the development essentials packages for your platform:
|
||||
@@ -100,10 +105,12 @@ You can install Nomad Network via pip:
|
||||
# ... and run
|
||||
nomadnet
|
||||
|
||||
**Please Note**: If this is the very first time you use pip to install a program
|
||||
on your system, you might need to reboot your system for your program to become
|
||||
available. If you get a "command not found" error or similar when running the
|
||||
program, reboot your system and try again.
|
||||
.. note::
|
||||
If this is the very first time you use ``pip`` to install a program
|
||||
on your system, you might need to reboot your system for your program to become
|
||||
available. If you get a "command not found" error or similar when running the
|
||||
program, reboot your system and try again. In some cases, you may even need to
|
||||
manually add the ``pip`` install path to your ``PATH`` environment variable.
|
||||
|
||||
Sideband
|
||||
^^^^^^^^
|
||||
@@ -305,6 +312,20 @@ you are welcome to head over to the `GitHub discussion pages <https://github.com
|
||||
and propose adding an interface for the hardware.
|
||||
|
||||
|
||||
Creating and Using Custom Interfaces
|
||||
===========================================
|
||||
|
||||
While Reticulum includes a flexible and broad range of built-in interfaces, these
|
||||
will not cover every conceivable type of communications hardware that Reticulum
|
||||
can potentially use to communicate.
|
||||
|
||||
It is therefore possible to easily write your own interface modules, that can be
|
||||
loaded at run-time and used on-par with any of the built-in interface types.
|
||||
|
||||
For more information on this subject, and code examples to build on, please see
|
||||
the :ref:`Configuring Interfaces<interfaces-main>` chapter.
|
||||
|
||||
|
||||
Develop a Program with Reticulum
|
||||
===========================================
|
||||
If you want to develop programs that use Reticulum, the easiest way to get
|
||||
@@ -318,14 +339,8 @@ The above command will install Reticulum and dependencies, and you will be
|
||||
ready to import and use RNS in your own programs. The next step will most
|
||||
likely be to look at some :ref:`Example Programs<examples-main>`.
|
||||
|
||||
For extended functionality, you can install optional dependencies:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install pyserial
|
||||
|
||||
|
||||
Further information can be found in the :ref:`API Reference<api-main>`.
|
||||
The entire Reticulum API is documented in the :ref:`API Reference<api-main>`
|
||||
chapter of this manual.
|
||||
|
||||
|
||||
Participate in Reticulum Development
|
||||
@@ -373,6 +388,7 @@ your first pull request, it is probably a good idea to introduce yourself on
|
||||
the `disucssion forum on GitHub <https://github.com/markqvist/Reticulum/discussions>`_,
|
||||
or ask one of the developers or maintainers for a good place to start.
|
||||
|
||||
.. _install-guides:
|
||||
|
||||
Platform-Specific Install Notes
|
||||
==============================================
|
||||
@@ -451,7 +467,7 @@ here at a later point. Until then you can use the `Sideband source code <https:/
|
||||
ARM64
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including ARM64, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` before
|
||||
binaries. On such systems, you may need to install ``python3-dev`` (or similar) before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
@@ -463,16 +479,8 @@ installing Reticulum or programs that depend on Reticulum.
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing some packages, and is not
|
||||
detailed in this manual.
|
||||
With these packages installed, ``pip`` will be able to build any missing dependencies
|
||||
on your system locally.
|
||||
|
||||
|
||||
Debian Bookworm
|
||||
@@ -503,10 +511,152 @@ following section:
|
||||
[global]
|
||||
break-system-packages = true
|
||||
|
||||
Please note that the "break-system-packages" directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems.
|
||||
For a one-shot installation of Reticulum, without globally enabling the ``break-system-packages``
|
||||
option, you can use the following command:
|
||||
|
||||
.. code:: text
|
||||
|
||||
pip install rns --break-system-packages
|
||||
|
||||
.. note::
|
||||
The ``--break-system-packages`` directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.
|
||||
|
||||
|
||||
MacOS
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
To install Reticulum on macOS, you will need to have Python and the ``pip`` package
|
||||
manager installed.
|
||||
|
||||
Systems running macOS can vary quite widely in whether or not Python is pre-installed,
|
||||
and if it is, which version is installed, and whether the ``pip`` package manager is
|
||||
also installed and set up. If in doubt, you can `download and install <https://www.python.org/downloads/>`_
|
||||
Python manually.
|
||||
|
||||
When Python and ``pip`` is available on your system, simply open a terminal window
|
||||
and use one of the following commands:
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Reticulum and utilities with pip:
|
||||
pip3 install rns
|
||||
|
||||
# On some versions, you may need to use the
|
||||
# flag --break-system-packages to install:
|
||||
pip3 install rns --break-system-packages
|
||||
|
||||
.. note::
|
||||
The ``--break-system-packages`` directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.
|
||||
|
||||
Additionally, some version combinations of macOS and Python require you to
|
||||
manually add your installed ``pip`` packages directory to your `PATH` environment
|
||||
variable, before you can use installed commands in your terminal. Usually, adding
|
||||
the following line to your shell init script (for example ``~/.zshrc``) will be enough:
|
||||
|
||||
.. code::
|
||||
|
||||
export PATH=$PATH:~/Library/Python/3.9/bin
|
||||
|
||||
Adjust Python version and shell init script location according to your system.
|
||||
|
||||
|
||||
OpenWRT
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On OpenWRT systems with sufficient storage and memory, you can install
|
||||
Reticulum and related utilities using the `opkg` package manager and `pip`.
|
||||
|
||||
.. note::
|
||||
|
||||
At the time of releasing this manual, work is underway to create pre-built
|
||||
Reticulum packages for OpenWRT, with full configuration, service
|
||||
and ``uci`` integration. Please see the `feed-reticulum <https://github.com/gretel/feed-reticulum>`_
|
||||
and `reticulum-openwrt <https://github.com/gretel/reticulum-openwrt>`_
|
||||
repositories for more information.
|
||||
|
||||
To install Reticulum on OpenWRT, first log into a command line session, and
|
||||
then use the following instructions:
|
||||
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
opkg install python3 python3-pip python3-cryptography python3-pyserial
|
||||
|
||||
# Install Reticulum
|
||||
pip install rns
|
||||
|
||||
# Start rnsd with debug logging enabled
|
||||
rnsd -vvv
|
||||
|
||||
.. note::
|
||||
|
||||
The above instructions have been verified and tested on OpenWRT 21.02 only.
|
||||
It is likely that other versions may require slightly altered installation
|
||||
commands or package names. You will also need enough free space in your
|
||||
overlay FS, and enough free RAM to actually run Reticulum and any related
|
||||
programs and utilities.
|
||||
|
||||
Depending on your device configuration, you may need to adjust firewall rules
|
||||
for Reticulum connectivity to and from your device to work. Until proper
|
||||
packaging is ready, you will also need to manually create a service or startup
|
||||
script to automatically laucnh Reticulum at boot time.
|
||||
|
||||
Please also note that the `AutoInterface` requires link-local IPv6 addresses
|
||||
to be enabled for any Ethernet and WiFi devices you intend to use. If ``ip a``
|
||||
shows an address starting with ``fe80::`` for the device in question,
|
||||
``AutoInterface`` should work for that device.
|
||||
|
||||
Raspberry Pi
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
It is currently recommended to use a 64-bit version of the Raspberry Pi OS
|
||||
if you want to run Reticulum on Raspberry Pi computers, since 32-bit versions
|
||||
don't always have packages available for some dependencies. If Python and the
|
||||
`pip` package manager is not already installed, do that first, and then
|
||||
install Reticulum using `pip`.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install dependencies
|
||||
sudo apt install python3 python3-pip python3-cryptography python3-pyserial
|
||||
|
||||
# Install Reticulum
|
||||
pip install rns --break-system-packages
|
||||
|
||||
.. note::
|
||||
The ``--break-system-packages`` directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.
|
||||
|
||||
While it is possible to install and run Reticulum on 32-bit Rasperry Pi OSes,
|
||||
it will require manually configuring and installing required build dependencies,
|
||||
and is not detailed in this manual.
|
||||
|
||||
|
||||
RISC-V
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On some architectures, including RISC-V, not all dependencies have precompiled
|
||||
binaries. On such systems, you may need to install ``python3-dev`` (or similar) before
|
||||
installing Reticulum or programs that depend on Reticulum.
|
||||
|
||||
.. code::
|
||||
|
||||
# Install Python and development packages
|
||||
sudo apt update
|
||||
sudo apt install python3 python3-pip python3-dev
|
||||
|
||||
# Install Reticulum
|
||||
python3 -m pip install rns
|
||||
|
||||
With these packages installed, ``pip`` will be able to build any missing dependencies
|
||||
on your system locally.
|
||||
|
||||
|
||||
Ubuntu Lunar
|
||||
@@ -537,13 +687,52 @@ following section:
|
||||
[global]
|
||||
break-system-packages = true
|
||||
|
||||
Please note that the "break-system-packages" directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this _could_ in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems.
|
||||
For a one-shot installation of Reticulum, without globally enabling the ``break-system-packages``
|
||||
option, you can use the following command:
|
||||
|
||||
.. code:: text
|
||||
|
||||
pip install rns --break-system-packages
|
||||
|
||||
.. note::
|
||||
The ``--break-system-packages`` directive is a somewhat misleading choice
|
||||
of words. Setting it will of course not break any system packages, but will simply
|
||||
allow installing ``pip`` packages user- and system-wide. While this *could* in rare
|
||||
cases lead to version conflicts, it does not generally pose any problems, especially
|
||||
not in the case of installing Reticulum.
|
||||
|
||||
Windows
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
On Windows operating systems, the easiest way to install Reticulum is by using the
|
||||
``pip`` package manager from the command line (either the command prompt or Windows
|
||||
Powershell).
|
||||
|
||||
If you don't already have Python installed, `download and install Python <https://www.python.org/downloads/>`_.
|
||||
At the time of publication of this manual, the recommended version is `Python 3.12.7 <https://www.python.org/downloads/release/python-3127>`_.
|
||||
|
||||
**Important!** When asked by the installer, make sure to add the Python program to
|
||||
your PATH environment variables. If you don't do this, you will not be able to
|
||||
use the ``pip`` installer, or run the included Reticulum utility programs (such as
|
||||
``rnsd`` and ``rnstatus``) from the command line.
|
||||
|
||||
After installing Python, open the command prompt or Windows Powershell, and type:
|
||||
|
||||
.. code::
|
||||
|
||||
pip install rns
|
||||
|
||||
You can now use Reticulum and all included utility programs directly from your
|
||||
preferred command line interface.
|
||||
|
||||
Pure-Python Reticulum
|
||||
==============================================
|
||||
|
||||
.. warning::
|
||||
If you use the ``rnspure`` package to run Reticulum on systems that
|
||||
do not support `PyCA/cryptography <https://github.com/pyca/cryptography>`_, it is
|
||||
important that you read and understand the :ref:`Cryptographic Primitives <understanding-primitives>`
|
||||
section of this manual.
|
||||
|
||||
In some rare cases, and on more obscure system types, it is not possible to
|
||||
install one or more dependencies. In such situations,
|
||||
you can use the ``rnspure`` package instead of the ``rns`` package, or use ``pip``
|
||||
@@ -558,8 +747,3 @@ only if they are *needed* and *available*. If for example you want to use Reticu
|
||||
on a system that cannot support ``pyserial``, it is perfectly possible to do so using
|
||||
the `rnspure` package, but Reticulum will not be able to use serial-based interfaces.
|
||||
All other available modules will still be loaded when needed.
|
||||
|
||||
**Please Note!** If you use the `rnspure` package to run Reticulum on systems that
|
||||
do not support `PyCA/cryptography <https://github.com/pyca/cryptography>`_, it is
|
||||
important that you read and understand the :ref:`Cryptographic Primitives <understanding-primitives>`
|
||||
section of this manual.
|
||||
|
||||
@@ -75,8 +75,8 @@ completely from scratch, to your exact desired specifications, this chapter
|
||||
will explain the easiest possible approach to creating RNodes: Using common
|
||||
LoRa development boards. This approach can be boiled down to two simple steps:
|
||||
|
||||
1. Obtain one or more supported development boards
|
||||
2. Install the RNode firmware with the automated installer
|
||||
1. Obtain one or more :ref:`supported development boards<rnode-supported>`
|
||||
2. Install the RNode firmware with the :ref:`automated installer<rnode-installation>`
|
||||
|
||||
Once the firmware has been installed and provisioned by the install script, it
|
||||
is ready to use with any software that supports RNodes, including Reticulum.
|
||||
@@ -85,8 +85,8 @@ to the configuration.
|
||||
|
||||
.. _rnode-supported:
|
||||
|
||||
Supported Boards
|
||||
^^^^^^^^^^^^^^^^
|
||||
Supported Boards and Devices
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
To create one or more RNodes, you will need to obtain supported development
|
||||
boards. The following boards are supported by the auto-installer.
|
||||
|
||||
|
||||
+155
-82
@@ -19,6 +19,16 @@ types, have a look at the :ref:`Building Networks<networks-main>` chapter of thi
|
||||
manual.
|
||||
|
||||
|
||||
.. _interfaces-custom:
|
||||
|
||||
Custom Interfaces
|
||||
=================
|
||||
|
||||
In addition to the built-in interface types, Reticulum is **fully extensible** with
|
||||
custom, user- or community-supplied interfaces, and creating custom interface
|
||||
modules is straightforward. Please see the :ref:`custom interface<example-custominterface>`
|
||||
example for basic interface code to build upon.
|
||||
|
||||
.. _interfaces-auto:
|
||||
|
||||
Auto Interface
|
||||
@@ -152,11 +162,12 @@ It can take anywhere from a few seconds to a few minutes to establish
|
||||
I2P connections to the desired peers, so Reticulum handles the process
|
||||
in the background, and will output relevant events to the log.
|
||||
|
||||
**Please Note!** While the I2P interface is the simplest way to use
|
||||
Reticulum over I2P, it is also possible to tunnel the TCP server and
|
||||
client interfaces over I2P manually. This can be useful in situations
|
||||
where more control is needed, but requires manual tunnel setup through
|
||||
the I2P daemon configuration.
|
||||
.. note::
|
||||
While the I2P interface is the simplest way to use
|
||||
Reticulum over I2P, it is also possible to tunnel the TCP server and
|
||||
client interfaces over I2P manually. This can be useful in situations
|
||||
where more control is needed, but requires manual tunnel setup through
|
||||
the I2P daemon configuration.
|
||||
|
||||
It is important to note that the two methods are *interchangably compatible*.
|
||||
You can use the I2PInterface to connect to a TCPServerInterface that
|
||||
@@ -171,7 +182,7 @@ TCP Server Interface
|
||||
====================
|
||||
|
||||
The TCP Server interface is suitable for allowing other peers to connect over
|
||||
the Internet or private IP networks. When a TCP server interface has been
|
||||
the Internet or private IPv4 and IPv6 networks. When a TCP server interface has been
|
||||
configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
|
||||
.. code::
|
||||
@@ -200,8 +211,37 @@ configured, other Reticulum peers can connect to it with a TCP Client interface.
|
||||
# device = eth0
|
||||
# port = 4242
|
||||
|
||||
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
If you are using the interface on a device which has both IPv4 and IPv6 addresses available,
|
||||
you can use the ``prefer_ipv6`` option to bind to the IPv6 address:
|
||||
|
||||
.. code::
|
||||
|
||||
# This example demonstrates a TCP server interface.
|
||||
# It will listen for incoming connections on the
|
||||
# specified IP address and port number.
|
||||
|
||||
[[TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = True
|
||||
|
||||
device = eth0
|
||||
port = 4242
|
||||
prefer_ipv6 = True
|
||||
|
||||
To use the TCP Server Interface over `Yggdrasil <https://yggdrasil-network.github.io/>`_, you
|
||||
can simply specify the Yggdrasil ``tun`` device and a listening port, like so:
|
||||
|
||||
.. code::
|
||||
|
||||
[[Yggdrasil TCP Server Interface]]
|
||||
type = TCPServerInterface
|
||||
interface_enabled = yes
|
||||
device = tun0
|
||||
listen_port = 4343
|
||||
|
||||
.. note::
|
||||
The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -231,7 +271,7 @@ and restore connectivity after a failure, once the other end of a TCP interface
|
||||
.. code::
|
||||
|
||||
# Here's an example of a TCP Client interface. The
|
||||
# target_host can either be an IP address or a hostname.
|
||||
# target_host can be a hostname or an IPv4 or IPv6 address.
|
||||
|
||||
[[TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
@@ -239,6 +279,17 @@ and restore connectivity after a failure, once the other end of a TCP interface
|
||||
target_host = 127.0.0.1
|
||||
target_port = 4242
|
||||
|
||||
To use the TCP Client Interface over `Yggdrasil <https://yggdrasil-network.github.io/>`_, simply
|
||||
specify the target Yggdrasil IPv6 address and port, like so:
|
||||
|
||||
.. code::
|
||||
|
||||
[[Yggdrasil TCP Client Interface]]
|
||||
type = TCPClientInterface
|
||||
interface_enabled = yes
|
||||
target_host = 201:5d78:af73:5caf:a4de:a79f:3278:71e5
|
||||
target_port = 4343
|
||||
|
||||
It is also possible to use this interface type to connect via other programs
|
||||
or hardware devices that expose a KISS interface on a TCP port, for example
|
||||
software-based soundmodems. To do this, use the ``kiss_framing`` option:
|
||||
@@ -262,8 +313,9 @@ never enable ``kiss_framing``, since this will disable internal reliability and
|
||||
recovery mechanisms that greatly improves performance over unreliable and
|
||||
intermittent TCP links.
|
||||
|
||||
**Please Note!** The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
.. note::
|
||||
The TCP interfaces support tunneling over I2P, but to do so reliably,
|
||||
you must use the i2p_tunneled option:
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -285,11 +337,12 @@ private and the internet. It can also allow broadcast communication
|
||||
over IP networks, so it can provide an easy way to enable connectivity
|
||||
with all other peers on a local area network.
|
||||
|
||||
*Please Note!* Using broadcast UDP traffic has performance implications,
|
||||
especially on WiFi. If your goal is simply to enable easy communication
|
||||
with all peers in your local Ethernet broadcast domain, the
|
||||
:ref:`Auto Interface<interfaces-auto>` performs better, and is even
|
||||
easier to use.
|
||||
.. warning::
|
||||
Using broadcast UDP traffic has performance implications,
|
||||
especially on WiFi. If your goal is simply to enable easy communication
|
||||
with all peers in your local Ethernet broadcast domain, the
|
||||
:ref:`Auto Interface<interfaces-auto>` performs better, and is even
|
||||
easier to use.
|
||||
|
||||
.. code::
|
||||
|
||||
@@ -344,6 +397,11 @@ RNode LoRa Interface
|
||||
To use Reticulum over LoRa, the `RNode <https://unsigned.io/rnode/>`_ interface
|
||||
can be used, and offers full control over LoRa parameters.
|
||||
|
||||
.. warning::
|
||||
Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of how to add a LoRa interface
|
||||
@@ -431,6 +489,11 @@ RNode Multi Interface
|
||||
For RNodes that support multiple LoRa transceivers, the RNode
|
||||
Multi interface can be used to configure sub-interfaces individually.
|
||||
|
||||
.. warning::
|
||||
Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.
|
||||
|
||||
.. code::
|
||||
|
||||
# Here's an example of how to add an RNode Multi interface
|
||||
@@ -454,89 +517,89 @@ Multi interface can be used to configure sub-interfaces individually.
|
||||
# id_interval = 600
|
||||
|
||||
# A subinterface
|
||||
[[[HIGHDATARATE]]]
|
||||
# Subinterfaces can be enabled and disabled in of themselves
|
||||
interface_enabled = True
|
||||
[[[High Datarate]]]
|
||||
# Subinterfaces can be enabled and disabled in of themselves
|
||||
interface_enabled = True
|
||||
|
||||
# Set frequency to 2.4GHz
|
||||
frequency = 2400000000
|
||||
# Set frequency to 2.4GHz
|
||||
frequency = 2400000000
|
||||
|
||||
# Set LoRa bandwidth to 1625 KHz
|
||||
bandwidth = 1625000
|
||||
# Set LoRa bandwidth to 1625 KHz
|
||||
bandwidth = 1625000
|
||||
|
||||
# Set TX power to 0 dBm (0.12 mW)
|
||||
txpower = 0
|
||||
# Set TX power to 0 dBm (0.12 mW)
|
||||
txpower = 0
|
||||
|
||||
# The virtual port, only the manufacturer
|
||||
# or the person who wrote the board config
|
||||
# can tell you what it will be for which
|
||||
# physical hardware interface
|
||||
vport = 1
|
||||
# The virtual port, only the manufacturer
|
||||
# or the person who wrote the board config
|
||||
# can tell you what it will be for which
|
||||
# physical hardware interface
|
||||
vport = 1
|
||||
|
||||
# Select spreading factor 5. Valid
|
||||
# range is 5 through 12, with 5
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 5
|
||||
# Select spreading factor 5. Valid
|
||||
# range is 5 through 12, with 5
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 5
|
||||
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# It is possible to limit the airtime
|
||||
# utilisation of an RNode by using the
|
||||
# following two configuration options.
|
||||
# The short-term limit is applied in a
|
||||
# window of approximately 15 seconds,
|
||||
# and the long-term limit is enforced
|
||||
# over a rolling 60 minute window. Both
|
||||
# options are specified in percent.
|
||||
# It is possible to limit the airtime
|
||||
# utilisation of an RNode by using the
|
||||
# following two configuration options.
|
||||
# The short-term limit is applied in a
|
||||
# window of approximately 15 seconds,
|
||||
# and the long-term limit is enforced
|
||||
# over a rolling 60 minute window. Both
|
||||
# options are specified in percent.
|
||||
|
||||
# airtime_limit_long = 100
|
||||
# airtime_limit_short = 100
|
||||
# airtime_limit_long = 100
|
||||
# airtime_limit_short = 100
|
||||
|
||||
[[[LOWDATARATE]]]
|
||||
# Subinterfaces can be enabled and disabled in of themselves
|
||||
interface_enabled = True
|
||||
[[[Low Datarate]]]
|
||||
# Subinterfaces can be enabled and disabled in of themselves
|
||||
interface_enabled = True
|
||||
|
||||
# Set frequency to 865.6 MHz
|
||||
frequency = 865600000
|
||||
# Set frequency to 865.6 MHz
|
||||
frequency = 865600000
|
||||
|
||||
# The virtual port, only the manufacturer
|
||||
# or the person who wrote the board config
|
||||
# can tell you what it will be for which
|
||||
# physical hardware interface
|
||||
vport = 0
|
||||
# The virtual port, only the manufacturer
|
||||
# or the person who wrote the board config
|
||||
# can tell you what it will be for which
|
||||
# physical hardware interface
|
||||
vport = 0
|
||||
|
||||
# Set LoRa bandwidth to 125 KHz
|
||||
bandwidth = 125000
|
||||
# Set LoRa bandwidth to 125 KHz
|
||||
bandwidth = 125000
|
||||
|
||||
# Set TX power to 0 dBm (0.12 mW)
|
||||
txpower = 0
|
||||
# Set TX power to 0 dBm (0.12 mW)
|
||||
txpower = 0
|
||||
|
||||
# Select spreading factor 7. Valid
|
||||
# range is 5 through 12, with 5
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 7
|
||||
# Select spreading factor 7. Valid
|
||||
# range is 5 through 12, with 5
|
||||
# being the fastest and 12 having
|
||||
# the longest range.
|
||||
spreadingfactor = 7
|
||||
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
# Select coding rate 5. Valid range
|
||||
# is 5 throough 8, with 5 being the
|
||||
# fastest, and 8 the longest range.
|
||||
codingrate = 5
|
||||
|
||||
# It is possible to limit the airtime
|
||||
# utilisation of an RNode by using the
|
||||
# following two configuration options.
|
||||
# The short-term limit is applied in a
|
||||
# window of approximately 15 seconds,
|
||||
# and the long-term limit is enforced
|
||||
# over a rolling 60 minute window. Both
|
||||
# options are specified in percent.
|
||||
# It is possible to limit the airtime
|
||||
# utilisation of an RNode by using the
|
||||
# following two configuration options.
|
||||
# The short-term limit is applied in a
|
||||
# window of approximately 15 seconds,
|
||||
# and the long-term limit is enforced
|
||||
# over a rolling 60 minute window. Both
|
||||
# options are specified in percent.
|
||||
|
||||
# airtime_limit_long = 100
|
||||
# airtime_limit_short = 100
|
||||
# airtime_limit_long = 100
|
||||
# airtime_limit_short = 100
|
||||
|
||||
.. _interfaces-serial:
|
||||
|
||||
@@ -598,6 +661,11 @@ radio modems and TNCs, including `OpenModem <https://unsigned.io/openmodem/>`_.
|
||||
KISS interfaces can also be configured to periodically send out beacons
|
||||
for station identification purposes.
|
||||
|
||||
.. warning::
|
||||
Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Packet Radio KISS Interface]]
|
||||
@@ -661,6 +729,11 @@ encapsulate in AX.25.
|
||||
A more efficient way is to use the plain KISS interface with the
|
||||
beaconing functionality described above.
|
||||
|
||||
.. warning::
|
||||
Radio frequency spectrum is a legally controlled resource, and legislation
|
||||
varies widely around the world. It is your responsibility to be aware of any
|
||||
relevant regulation for your location, and to make decisions accordingly.
|
||||
|
||||
.. code::
|
||||
|
||||
[[Packet Radio AX.25 KISS Interface]]
|
||||
|
||||
@@ -32,7 +32,9 @@ Provide Feedback
|
||||
================
|
||||
All feedback on the usage, functioning and potential dysfunctioning of any and
|
||||
all components of the system is very valuable to the continued development and
|
||||
improvement of Reticulum. Absolutely no automated analytics, telemetry, error
|
||||
improvement of Reticulum.
|
||||
|
||||
Absolutely no automated analytics, telemetry, error
|
||||
reporting or statistics is collected and reported by Reticulum under any
|
||||
circumstances, so we rely on old-fashioned human feedback.
|
||||
|
||||
|
||||
@@ -868,13 +868,17 @@ both on general-purpose CPUs and on microcontrollers. The necessary primitives a
|
||||
|
||||
* HKDF for key derivation
|
||||
|
||||
* Modified Fernet for encrypted tokens
|
||||
* Encrypted tokens are based on the Fernet spec
|
||||
|
||||
* AES-128 in CBC mode
|
||||
* Ephemeral keys derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* HMAC for message authentication
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
* No Version and Timestamp metadata included
|
||||
* HMAC using SHA256 for message authentication
|
||||
|
||||
* IVs are generated through os.urandom()
|
||||
|
||||
* No Fernet version and timestamp metadata fields
|
||||
|
||||
* SHA-256
|
||||
|
||||
@@ -884,12 +888,12 @@ In the default installation configuration, the ``X25519``, ``Ed25519`` and ``AES
|
||||
primitives are provided by `OpenSSL <https://www.openssl.org/>`_ (via the `PyCA/cryptography <https://github.com/pyca/cryptography>`_
|
||||
package). The hashing functions ``SHA-256`` and ``SHA-512`` are provided by the standard
|
||||
Python `hashlib <https://docs.python.org/3/library/hashlib.html>`_. The ``HKDF``, ``HMAC``,
|
||||
``Fernet`` primitives, and the ``PKCS7`` padding function are always provided by the
|
||||
``Token`` primitives, and the ``PKCS7`` padding function are always provided by the
|
||||
following internal implementations:
|
||||
|
||||
- ``RNS/Cryptography/HKDF.py``
|
||||
- ``RNS/Cryptography/HMAC.py``
|
||||
- ``RNS/Cryptography/Fernet.py``
|
||||
- ``RNS/Cryptography/Token.py``
|
||||
- ``RNS/Cryptography/PKCS7.py``
|
||||
|
||||
|
||||
@@ -900,6 +904,7 @@ with the OpenSSL backend being *much* faster. The most important consequence how
|
||||
potential loss of security by using primitives that has not seen the same amount of scrutiny,
|
||||
testing and review as those from OpenSSL.
|
||||
|
||||
If you want to use the internal pure-python primitives, it is **highly advisable** that you
|
||||
have a good understanding of the risks that this pose, and make an informed decision on whether
|
||||
those risks are acceptable to you.
|
||||
.. warning::
|
||||
If you want to use the internal pure-python primitives, it is **highly advisable** that you
|
||||
have a good understanding of the risks that this pose, and make an informed decision on whether
|
||||
those risks are acceptable to you.
|
||||
+45
-24
@@ -17,7 +17,7 @@ Reticulum enables secure digital communication that cannot be subjected to
|
||||
outside control, manipulation or censorship.
|
||||
|
||||
Reticulum enables the construction of both small and potentially planetary-scale
|
||||
networks, without any need for hierarchical or beaureucratic structures to control
|
||||
networks, without any need for hierarchical or bureaucratic structures to control
|
||||
or manage them, while ensuring individuals and communities full sovereignty
|
||||
over their own network segments.
|
||||
|
||||
@@ -43,19 +43,30 @@ considered complete and stable at the moment, but could change if absolutely war
|
||||
|
||||
What does Reticulum Offer?
|
||||
==========================
|
||||
|
||||
* Coordination-less globally unique addressing and identification
|
||||
|
||||
* Fully self-configuring multi-hop routing
|
||||
* Fully self-configuring multi-hop routing over heterogeneous carriers
|
||||
|
||||
* Complete initiator anonymity, communicate without revealing your identity
|
||||
* Flexible scalability over heterogeneous topologies
|
||||
|
||||
* Asymmetric encryption based on X25519, and Ed25519 signatures as a basis for all communication
|
||||
* Reticulum can carry data over any mixture of physical mediums and topologies
|
||||
|
||||
* Forward Secrecy by using ephemeral Elliptic Curve Diffie-Hellman keys on Curve25519
|
||||
* Low-bandwidth networks can co-exist and interoperate with large, high-bandwidth networks
|
||||
|
||||
* 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
|
||||
* Initiator anonymity, communicate without revealing your identity
|
||||
|
||||
* Keys are ephemeral and derived from an ECDH key exchange on Curve25519
|
||||
* Reticulum does not include source addresses on any packets
|
||||
|
||||
* Asymmetric X25519 encryption and Ed25519 signatures as a basis for all communication
|
||||
|
||||
* 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:
|
||||
|
||||
* Ephemeral per-packet and link keys and derived from an ECDH key exchange on Curve25519
|
||||
|
||||
* AES-128 in CBC mode with PKCS7 padding
|
||||
|
||||
@@ -63,13 +74,33 @@ 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
|
||||
* Flexible and extensible interface system
|
||||
|
||||
* An intuitive and developer-friendly API
|
||||
* Reticulum includes a large variety of built-in interface types
|
||||
|
||||
* Ability to load and utilise custom user- or community-supplied interface types
|
||||
|
||||
* Easily create your own custom interfaces for communicating over anything
|
||||
|
||||
* Authentication and virtual network segmentation on all supported interface types
|
||||
|
||||
* An intuitive and easy-to-use API
|
||||
|
||||
* Simpler and easier to use than sockets APIs and simpler, but more powerful
|
||||
|
||||
* Makes building distributed and decentralised applications much simpler
|
||||
|
||||
* Reliable and efficient transfer of arbitrary amounts of data
|
||||
|
||||
* Reticulum can handle a few bytes of data or files of many gigabytes
|
||||
|
||||
* 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
|
||||
|
||||
@@ -77,17 +108,7 @@ What does Reticulum Offer?
|
||||
|
||||
* Low cost of keeping links open at only 0.44 bits per second
|
||||
|
||||
* 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 is automatic
|
||||
|
||||
* The API is very easy to use, and provides transfer progress
|
||||
|
||||
* Authentication and virtual network segmentation on all supported interface types
|
||||
|
||||
* Flexible scalability allowing extremely low-bandwidth networks to co-exist and interoperate with large, high-bandwidth networks
|
||||
* Reliable sequential delivery with Channel and Buffer mechanisms
|
||||
|
||||
|
||||
Where can Reticulum be Used?
|
||||
@@ -118,7 +139,7 @@ network, and vice versa.
|
||||
|
||||
Interface Types and Devices
|
||||
===========================
|
||||
Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, it's relatively simple to implement an interface class. Currently, Reticulum can use the following devices and communication mediums:
|
||||
Reticulum implements a range of generalised interface types that covers the communications hardware that Reticulum can run over. If your hardware is not supported, it's simple to :ref:`implement an interface class<example-custominterface>`. Currently, Reticulum can use the following devices and communication mediums:
|
||||
|
||||
* Any Ethernet device
|
||||
|
||||
@@ -168,4 +189,4 @@ such. While it has been built with cryptography best-practices very foremost in
|
||||
mind, it has not yet been externally security audited, and there could very well be
|
||||
privacy-breaking bugs. To be considered secure, Reticulum needs a thorough
|
||||
security review by independent cryptographers and security researchers. If you
|
||||
want to help out with this, or can help sponsor an audit, please do get in touch.
|
||||
want to help out with this, or can help sponsor an audit, please do get in touch.
|
||||
+2
-2
@@ -333,7 +333,7 @@ class TestLink(unittest.TestCase):
|
||||
resource_size = 5*1000*1000
|
||||
data = os.urandom(resource_size)
|
||||
print("Sending "+self.size_str(resource_size)+" resource...")
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout)
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout, auto_compress=False)
|
||||
start = time.time()
|
||||
|
||||
# This is a hack, don't do it. Use the callbacks instead.
|
||||
@@ -380,7 +380,7 @@ class TestLink(unittest.TestCase):
|
||||
resource_size = 50*1000*1000
|
||||
data = os.urandom(resource_size)
|
||||
print("Sending "+self.size_str(resource_size)+" resource...")
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback)
|
||||
resource = RNS.Resource(data, l1, timeout=resource_timeout, callback=self.lr_callback, auto_compress=False)
|
||||
start = time.time()
|
||||
|
||||
TestLink.large_resource_status = resource.status
|
||||
|
||||
Reference in New Issue
Block a user